nx_shelltool.nx
buildroot/runtime/nx_shelltool.nx
about
nx_shelltool.nx -- the INVOCABLE + MCP-READY surface for the shell-replacement tier (07-15
operator: "build our nishi grep and glob and search ... as full MCP API state of the art ...
available to these workstreams ... anything else that uses wsl when we could just use nishi").
The S1-S9 organs (nx_grep etc.) are LIBS (functions, no main) -> not callable as tools + not on
MCP. This is the ONE CLI that COMPOSES the canonical nx_grep (per NISHI_SHELL_REPLACEMENT_ROADMAP
"consumers compose nx_grep", no proliferation) into filesystem verbs a workstream can invoke over
MCP tools/call instead of the Grep/Glob/Bash builtins + WSL:
nx_shelltool grep <pattern> <dir> [ext] -> recursive; prints path:lineno:line for matches
nx_shelltool glob <glob-pat> <dir> -> recursive; prints paths whose NAME matches (* wildcard)
nx_shelltool find <substr> <dir> -> recursive; prints paths whose NAME contains substr
Composes nx_grep_any (canonical match). Read-only. Exit 0; 2 usage; 3 baddir; 5 budget-partial.
SEQ1292 ROOT FIX (2026-07-30, incident 2026-07-29 sitewide outage): every scan now runs under a
SCAN BUDGET -- an unbounded walk over a multi-GB store starved the whole hub (and the old code
mmap'd 1MiB per file, never unmapped => RSS grew linearly with files scanned = the thrash vector).
Budget source: shelltool_budget.conf in CWD (keys max_bytes= deadline_ms= max_files=), else
derived defaults: max_bytes 512MiB (~1.4% of the 36GB hub, seconds of sequential NAS IO),
deadline_ms 20000 (the proven WC_FETCH_BUDGET_MS wall-clock precedent), max_files 200000.
Checked per file + per getdents batch so exceeding STOPS IO, prints partial counts + a structured
NX-SHELLTOOL BUDGET-EXCEEDED line, exit 5 -- never a silent truncation, never a host wedge.
Buffers: ONE reused 1MiB read buffer; per-level walk buffers munmap'd; overlong paths (>4095)
skipped LOUDLY via skipped_toolong= in the summary.
license_tier: ORIGINAL expect_exit: 0
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_grep_rt.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const S_EXIT_BADDIR: i64 = 3 // <dir> not a readable directory (loud, distinct from usage=2) |
| 28 | const S_EXIT_BUDGET: i64 = 5 // budget exceeded -> PARTIAL results (loud, distinct) |
| 29 | const S_PROBE_CAP: i64 = 4096 // one-getdents probe buffer (dir-ness check) |
| 30 | const S_DEF_MAX_BYTES: i64 = 536870912 // 512MiB derived: ~1.4% hub RAM, seconds of NAS IO (seq1292) |
| 31 | const S_DEF_DEADLINE_MS: i64 = 20000 // proven wall-clock budget precedent (WC_FETCH_BUDGET_MS) |
| 32 | const S_DEF_MAX_FILES: i64 = 200000 // > any legitimate single-tree scan observed to date |
| 37 | const S_FD_STDOUT: i64 = 1 |
| 38 | const S_MODE_644: i64 = 420 |
| 39 | const S_EXIT_BADOUT: i64 = 6 // out= refused/unwritable (loud, distinct from usage/baddir/budget) |
| 40 | const S_NUMBUF: i64 = 24 |
| 41 | const S_OUTKEY_LEN: i64 = 4 // len("out=") |
| 42 | const S_OUT_MINLEN: i64 = 5 // at least one name char + ".out" |
| 43 | const S_DOT: i64 = 46 |
| 44 | const S_SLASH: i64 = 47 |
| 45 | const S_CH_O: i64 = 111 |
| 46 | const S_CH_U: i64 = 117 |
| 47 | const S_CH_T: i64 = 116 |
| 48 | const S_BUD_OFD: i64 = 9 // bud[] already threads the scan context to every emit site -- |
| 49 | const S_BUD_OBYTES: i64 = 10 // reusing 2 spare slots avoids changing 3 function signatures |
functions
| 51 | func s_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 52 | func s_write(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } calls 1: sys_write |
| 53 | func s_putn(v: i64) -> i64 |
| 64 | func s_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 65 | func s_starts(s: *u8, pre: *u8) -> i64 { var i: i64 = 0; while pre[i] != (0 as u8) { if s[i] != pre[i] { return 0 } i = i + 1 } return 1 } called by 1: main |
| 68 | func s_fputs(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return n } |
| 69 | func s_fputn(fd: i64, v: i64) -> i64 |
| 85 | func s_out_ok(p: *u8) -> i64 |
| 97 | func s_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } called by 1: main |
| 100 | func s_read(path: *u8, buf: *u8, cap: i64) -> i64 |
| 111 | func s_num_after(buf: *u8, n: i64, key: *u8) -> i64 |
| 138 | func s_ends_with(name: *u8, nn: i64, ext: *u8, en: i64) -> i64 called by 1: s_walk |
| 147 | func s_glob(pat: *u8, pn: i64, str: *u8, sn: i64) -> i64 called by 1: s_walk |
| 168 | func s_bud_ok(bud: *i64) -> i64 |
| 178 | func s_grep_file(path: *u8, pat: *u8, pn: i64, gbuf: *u8, bud: *i64) -> i64 |
| 210 | func s_walk(dir: *u8, mode: i64, pat: *u8, pn: i64, ext: *u8, en: i64, gbuf: *u8, bud: *i64) -> i64 |
| 267 | func s_dir_ok(dir: *u8) -> i64 |
| 278 | func main(argc: i64, argv: *i64) -> i64 |