code wiki / (root) / nx_shelltool.nx

nx_shelltool.nx

buildroot/runtime/nx_shelltool.nx

17819 B363 linesdepth 7pulls 7 transitivereach 0 importersview sourcekind tool
docsdependenciesstructsconstsfunctions

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

nx_syscalls.nx nx_grep_rt.nx nx_shelltool.nx

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

main s_puts sys_write s_streq s_dir_ok sys_openat_rd sys_mmap sys_getdents64 sys_close sys_munmap sys_mmap ↻ s_read sys_openat_rd ↻ sys_read sys_close ↻ s_num_after s_slen sys_munmap ↻ sys_clock_now_us sys_now_us sys_mmap ↻ sys_clock_gettime_mono s_starts s_slen ↻ s_out_ok s_slen ↻ sys_openat_wr s_walk sys_openat_rd ↻ sys_mmap ↻ s_slen ↻ s_bud_ok sys_clock_now_us ↻ sys_getdents64 ↻ dirent_reclen dirent_name dirent_type s_walk ↻ s_ends_with s_grep_file

structs

none

consts

27const S_EXIT_BADDIR: i64 = 3 // <dir> not a readable directory (loud, distinct from usage=2)
28const S_EXIT_BUDGET: i64 = 5 // budget exceeded -> PARTIAL results (loud, distinct)
29const S_PROBE_CAP: i64 = 4096 // one-getdents probe buffer (dir-ness check)
30const S_DEF_MAX_BYTES: i64 = 536870912 // 512MiB derived: ~1.4% hub RAM, seconds of NAS IO (seq1292)
31const S_DEF_DEADLINE_MS: i64 = 20000 // proven wall-clock budget precedent (WC_FETCH_BUDGET_MS)
32const S_DEF_MAX_FILES: i64 = 200000 // > any legitimate single-tree scan observed to date
37const S_FD_STDOUT: i64 = 1
38const S_MODE_644: i64 = 420
39const S_EXIT_BADOUT: i64 = 6 // out= refused/unwritable (loud, distinct from usage/baddir/budget)
40const S_NUMBUF: i64 = 24
41const S_OUTKEY_LEN: i64 = 4 // len("out=")
42const S_OUT_MINLEN: i64 = 5 // at least one name char + ".out"
43const S_DOT: i64 = 46
44const S_SLASH: i64 = 47
45const S_CH_O: i64 = 111
46const S_CH_U: i64 = 117
47const S_CH_T: i64 = 116
48const S_BUD_OFD: i64 = 9 // bud[] already threads the scan context to every emit site --
49const S_BUD_OBYTES: i64 = 10 // reusing 2 spare slots avoids changing 3 function signatures

functions

51func 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 }
called by 1: main calls 1: sys_write
52func s_write(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 }
calls 1: sys_write
53func s_putn(v: i64) -> i64
called by 1: main calls 3: sys_writesys_mmapsys_munmap
64func s_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
65func 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
68func 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 }
called by 2: s_grep_files_walk calls 1: sys_write
69func s_fputn(fd: i64, v: i64) -> i64
85func s_out_ok(p: *u8) -> i64
called by 1: main calls 1: s_slen
97func 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
100func s_read(path: *u8, buf: *u8, cap: i64) -> i64
111func s_num_after(buf: *u8, n: i64, key: *u8) -> i64
called by 1: main calls 1: s_slen
138func s_ends_with(name: *u8, nn: i64, ext: *u8, en: i64) -> i64
called by 1: s_walk
147func s_glob(pat: *u8, pn: i64, str: *u8, sn: i64) -> i64
called by 1: s_walk
168func s_bud_ok(bud: *i64) -> i64
called by 2: s_grep_files_walk calls 1: sys_clock_now_us
178func s_grep_file(path: *u8, pat: *u8, pn: i64, gbuf: *u8, bud: *i64) -> i64
210func s_walk(dir: *u8, mode: i64, pat: *u8, pn: i64, ext: *u8, en: i64, gbuf: *u8, bud: *i64) -> i64
267func s_dir_ok(dir: *u8) -> i64
278func main(argc: i64, argv: *i64) -> i64