@niroai/niro 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +123 -302
  3. package/bin/niro.js +12 -1
  4. package/docs/agent-antigravity.md +37 -0
  5. package/docs/agent-claude-code.md +48 -0
  6. package/docs/agent-claude-desktop.md +47 -0
  7. package/docs/agent-cline.md +42 -0
  8. package/docs/agent-codex.md +45 -0
  9. package/docs/agent-copilot-cli.md +42 -0
  10. package/docs/agent-cursor.md +44 -0
  11. package/docs/agent-factory.md +42 -0
  12. package/docs/agent-gemini-cli.md +42 -0
  13. package/docs/agent-goose.md +42 -0
  14. package/docs/agent-kiro.md +37 -0
  15. package/docs/agent-opencode.md +37 -0
  16. package/docs/agent-vscode.md +45 -0
  17. package/docs/agent-windsurf.md +38 -0
  18. package/docs/answer-shapes.md +69 -0
  19. package/docs/builds-and-updates.md +68 -0
  20. package/docs/cloud-README.md +372 -0
  21. package/docs/configuring-a-folder.md +51 -0
  22. package/docs/connect-ci.md +24 -0
  23. package/docs/connect-overview.md +20 -0
  24. package/docs/connect-review-bots.md +17 -0
  25. package/docs/connect-team-server.md +12 -0
  26. package/docs/connect-terminal.md +49 -0
  27. package/docs/connect-web-agents.md +9 -0
  28. package/docs/connect-your-ai-assistant.md +59 -175
  29. package/docs/coverage-audit-and-consent.md +51 -0
  30. package/docs/evaluate-what-niro-did.md +271 -0
  31. package/docs/faq-assistants-and-mcp.md +122 -0
  32. package/docs/faq-feedback-and-receipts.md +33 -0
  33. package/docs/faq-indexing.md +127 -0
  34. package/docs/faq-install-and-setup.md +103 -0
  35. package/docs/faq-licence-and-plans.md +86 -0
  36. package/docs/faq-login-and-account.md +69 -0
  37. package/docs/faq-offline.md +34 -0
  38. package/docs/faq-performance.md +33 -0
  39. package/docs/faq-privacy-and-security.md +113 -0
  40. package/docs/faq-uninstall.md +47 -0
  41. package/docs/faq-updates.md +59 -0
  42. package/docs/faq-worktrees-and-branches.md +59 -0
  43. package/docs/faq-wrong-or-missing-answers.md +275 -0
  44. package/docs/hosted-only.md +33 -0
  45. package/docs/how-the-graph-works.md +47 -0
  46. package/docs/index.md +157 -0
  47. package/docs/licence-and-plans.md +72 -0
  48. package/docs/lifecycle-debug.md +48 -0
  49. package/docs/lifecycle-onboarding.md +32 -0
  50. package/docs/lifecycle-overview.md +77 -0
  51. package/docs/lifecycle-plan-and-design.md +44 -0
  52. package/docs/lifecycle-review.md +30 -0
  53. package/docs/lifecycle-security-and-privacy.md +35 -0
  54. package/docs/lifecycle-test.md +28 -0
  55. package/docs/lifecycle-write-code.md +35 -0
  56. package/docs/llms-full.txt +8015 -0
  57. package/docs/llms.txt +126 -0
  58. package/docs/playbook-add-a-feature.md +108 -0
  59. package/docs/playbook-change-safely.md +93 -0
  60. package/docs/playbook-data-and-events.md +107 -0
  61. package/docs/playbook-debug-after-a-change.md +96 -0
  62. package/docs/playbook-debug-config.md +95 -0
  63. package/docs/playbook-debug-exception.md +124 -0
  64. package/docs/playbook-debug-failing-service-call.md +119 -0
  65. package/docs/playbook-debug-slow-path.md +80 -0
  66. package/docs/playbook-debug-wrong-value.md +137 -0
  67. package/docs/playbook-onboard.md +95 -0
  68. package/docs/playbook-review-a-change.md +72 -0
  69. package/docs/playbook-test-a-change.md +73 -0
  70. package/docs/reference-cli.md +113 -0
  71. package/docs/reference-config-files.md +42 -0
  72. package/docs/reference-engine-commands.md +56 -0
  73. package/docs/reference-environment-variables.md +89 -0
  74. package/docs/reference-exit-codes.md +67 -0
  75. package/docs/reference-limits.md +75 -0
  76. package/docs/reference-mcp-tools.md +326 -0
  77. package/docs/reference-messages.md +180 -0
  78. package/docs/secrets-and-masking.md +48 -0
  79. package/docs/supported-c.md +130 -0
  80. package/docs/supported-cpp.md +157 -0
  81. package/docs/supported-csharp.md +211 -0
  82. package/docs/supported-go.md +258 -0
  83. package/docs/supported-java.md +226 -0
  84. package/docs/supported-javascript.md +223 -0
  85. package/docs/supported-kotlin.md +124 -0
  86. package/docs/supported-overview.md +89 -0
  87. package/docs/supported-php.md +157 -0
  88. package/docs/supported-python.md +224 -0
  89. package/docs/supported-ruby.md +162 -0
  90. package/docs/supported-rust.md +150 -0
  91. package/docs/supported-swift.md +133 -0
  92. package/docs/supported-typescript.md +220 -0
  93. package/docs/tokens-speed-and-accuracy.md +99 -0
  94. package/docs/troubleshooting.md +42 -0
  95. package/docs/uninstall.md +74 -0
  96. package/docs/what-leaves-your-machine.md +71 -0
  97. package/docs/where-things-are-stored.md +59 -0
  98. package/docs/worktrees-and-branches.md +55 -0
  99. package/package.json +5 -3
  100. package/src/commands/add-repo.js +5 -0
  101. package/src/commands/auditSend.js +67 -0
  102. package/src/commands/build.js +6 -0
  103. package/src/commands/docsSend.js +73 -0
  104. package/src/commands/engine.js +120 -0
  105. package/src/commands/engineRoutes.js +105 -0
  106. package/src/commands/guide.js +254 -0
  107. package/src/commands/init.js +16 -7
  108. package/src/commands/initLocal.js +96 -0
  109. package/src/commands/journalSend.js +47 -0
  110. package/src/commands/login.js +89 -12
  111. package/src/commands/mcp.js +22 -6
  112. package/src/commands/plan.js +99 -0
  113. package/src/commands/repoPush.js +33 -0
  114. package/src/commands/requestIntegration.js +38 -0
  115. package/src/commands/share.js +56 -0
  116. package/src/commands/start.js +451 -371
  117. package/src/commands/status.js +57 -0
  118. package/src/commands/takeover.js +6 -0
  119. package/src/commands/uninstall.js +180 -0
  120. package/src/commands/upgrade.js +38 -8
  121. package/src/commands/watch.js +420 -6
  122. package/src/index.js +174 -3
  123. package/src/lib/agentApi.js +32 -1
  124. package/src/lib/api.js +20 -6
  125. package/src/lib/auditUpload.js +293 -0
  126. package/src/lib/branchDrift.js +149 -0
  127. package/src/lib/choose.js +116 -0
  128. package/src/lib/cloudGuard.js +74 -0
  129. package/src/lib/credentials.js +2 -2
  130. package/src/lib/docsMarked.js +55 -0
  131. package/src/lib/docsState.js +213 -0
  132. package/src/lib/docsUpload.js +481 -0
  133. package/src/lib/engine.js +619 -0
  134. package/src/lib/folderSync.js +2 -1
  135. package/src/lib/graphCheck.js +40 -0
  136. package/src/lib/guide.js +293 -0
  137. package/src/lib/guideSearch.js +252 -0
  138. package/src/lib/integrationRequest.js +89 -0
  139. package/src/lib/journalUpload.js +147 -0
  140. package/src/lib/languages.js +129 -0
  141. package/src/lib/lease.js +375 -0
  142. package/src/lib/localBuild.js +285 -0
  143. package/src/lib/localEngine.js +210 -0
  144. package/src/lib/localOnboard.js +124 -0
  145. package/src/lib/localWorktrees.js +243 -0
  146. package/src/lib/machineCheck.js +58 -0
  147. package/src/lib/mcpClients.js +10 -1
  148. package/src/lib/migrationNotice.js +118 -0
  149. package/src/lib/modelFetch.js +153 -0
  150. package/src/lib/nestedOutput.js +23 -9
  151. package/src/lib/onboardingLog.js +34 -0
  152. package/src/lib/planUsage.js +165 -0
  153. package/src/lib/progressBar.js +46 -0
  154. package/src/lib/prompt.js +1 -1
  155. package/src/lib/redact.js +155 -0
  156. package/src/lib/release.js +33 -0
  157. package/src/lib/repoPush.js +495 -0
  158. package/src/lib/secret.js +1 -1
  159. package/src/lib/service.js +372 -63
  160. package/src/lib/shareCode.js +367 -0
  161. package/src/lib/shareExclusions.js +114 -0
  162. package/src/lib/shareFiles.js +129 -0
  163. package/src/lib/spinner.js +9 -5
  164. package/src/lib/watchDaemon.js +33 -10
  165. package/src/lib/watchState.js +12 -1
  166. package/src/lib/windowsPaths.js +67 -0
  167. package/src/mcp/lineCheck.js +382 -0
  168. package/src/mcp/lineName.js +92 -0
  169. package/src/mcp/localBackend.js +751 -0
  170. package/src/mcp/localMode.js +383 -0
  171. package/src/mcp/localSkill.js +189 -0
  172. package/src/mcp/server.js +610 -79
  173. package/src/mcp/setup.js +519 -131
  174. package/src/mcp/shellSearchHook.js +509 -0
  175. package/src/mcp/teardown.js +173 -91
  176. package/src/mcp/usingNiro.js +39 -0
  177. package/docs/working-on-branches.md +0 -162
@@ -0,0 +1,71 @@
1
+ # What leaves your machine
2
+
3
+ **Niro builds and answers from the graph on your computer.** A few things are sent to Niro's services. This page lists each one, what is in it, and what decides whether it is sent.
4
+
5
+ ## The short version
6
+
7
+ Niro shows this text in `niro status` and `niro plan`:
8
+
9
+ > Your code stays on this machine, except, for each line the audit flags, the method that holds it and one line for each name it uses, which are sent with secrets masked. A record of which Niro tools your assistant used is also sent, to improve Niro and to show you its value. Niro's tools answer your assistant from the graph on this machine and never send the contents of your files.
10
+ <!-- source: src/lib/planUsage.js:116-119 (AUDIT_CLAUSE, JOURNAL_SENTENCE, TOOLS_SENTENCE), :131-134 (sharingText) -->
11
+
12
+ When **Help improve Niro** (code sharing) is on for your account, one more thing is sent, by itself: the code of every folder Niro indexes on your computer, redacted first. `niro plan` shows which way it is:
13
+
14
+ ```
15
+ Help improve Niro on
16
+ ✓ Your code is indexed and answered on this machine
17
+ ✓ Sent in the background: a record of which Niro tools ran, and the methods the audit flags with the lines they reference (secrets, author and company names removed)
18
+ ✓ Code shared in the background: the files Niro indexes, redacted here, deleted after processing, never used to train AI
19
+ ```
20
+
21
+ With sharing off, the last line reads `✗ Code sharing is off`, and no code is shared.
22
+ <!-- source: src/lib/planUsage.js sharingBlock; src/commands/plan.js -->
23
+
24
+ ## Everything that is sent
25
+
26
+ | What | Where to | What is in it | When |
27
+ |---|---|---|---|
28
+ | **Sign-in** | Niro's account service | Your sign-in through the browser | When you run `niro start` or `niro login` |
29
+ | **Your account details** | Niro's account service | Your sign-in, to read your account's email and identity | When you run `niro whoami` |
30
+ | **Sign-out** | Niro's account service | Your sign-in token, so the service withdraws it | When you run `niro logout` |
31
+ | **Old hosted projects check** (only on a computer that used hosted Niro before) | Niro's account service | Your sign-in, to list your hosted projects. For a hosted project that list no longer has, its id and its repositories' ids, to ask whether they still exist. No code | Every 6 hours, by the background watcher, while old hosted registrations are still listed in `~/.niro/projects.json` |
32
+ | **Licence check** | Niro's billing service | A random number and your engine's version, with your sign-in | Each time the engine starts, and when its licence is renewed |
33
+ | **Engine download** | Niro's billing service, then a download link | Nothing about your code. The download is checked against its published fingerprint (SHA-256) | When `niro start` or `niro engine install` installs the engine |
34
+ | **Coverage audit** | Niro's audit service | For each line the build could not read: the file's path inside the repository, the method that holds the line, and one line per name it uses. Every other line is blank, and secrets are masked, in the path too. Separately, a list of the libraries and kinds of files the build saw, which carries no file names or paths | Every 5 minutes, on every plan. See [coverage-audit-and-consent.md](coverage-audit-and-consent.md) |
35
+ | **Tool-usage record** | Niro's assistant service | Which tools your agent used, how long they took, failures, a short task summary, and the names, paths and search text asked about. Free text is masked first | Every 5 minutes |
36
+ | **Shared code** (only while Help improve Niro is on) | Niro's indexing service | The files Niro indexes in each indexed folder: the source files it parses and the config, build and text files it reads. Never a file your `.gitignore` covers (unless git tracks it), a real `.env`, a key or key store, a tool's token file, or `node_modules` and other build output. Secrets are masked and author and company names removed first. The first time, a folder's whole indexed set; after that, only files that changed, and a note of files that were removed | Every 5 minutes, by the background watcher, only while your account's lease says sharing is on. A lease that cannot be read sends nothing. A folder you exclude with `niro share exclude <folder>` is never sent. See [licence-and-plans.md](licence-and-plans.md) |
37
+ | **Whole repository** (only if you ask) | Niro's indexing service | The same files and the same redaction as shared code, for one folder. This is how you send a repository to Niro for support | Only when you type `niro audit-push-repo` and answer yes. It works with code sharing on or off, on every plan. For a folder you excluded with `niro share exclude`, it also asks "Send it anyway?" and sends only on yes |
38
+ <!-- source: src/commands/login.js:1-14 and src/lib/api.js:10 (sign-in); src/commands/whoami.js:48 (/api/users/details); src/commands/logout.js:19-20 (token revoke); src/commands/watch.js:200, :379-384, :412-424 and src/commands/cleanup.js:185, :245-298 (the sweep: GET /api/projects every 6 hours, first after 60 s), src/commands/cleanup.js:174-182 and :459-466 with src/lib/agentApi.js:264-265, :288-297 (a gone project's id, and its repositories' ids, through GET /api/projects/{id} and GET /api/agent/repos/{gitId}/status); src/lib/lease.js:42-44, :172-176 (licence check); src/lib/engine.js:48, :93-96, :473, :551 (download); src/lib/auditUpload.js:27-30, :47, :168-244 and src/lib/redact.js:89-121 (audit); niro-local src/niro_local/cli.py:2413-2415, src/niro_local/audit/queue.py:838-846, src/niro_local/audit/collect.py:208-216 (each item carries the file's masked path); src/lib/journalUpload.js:9-37 and src/lib/planUsage.js comment above AUDIT_CLAUSE (journal); src/lib/repoPush.js (whole repository); src/lib/shareCode.js and src/lib/shareFiles.js (shared code: the gate, the engine's file list from `niro-local indexed-files`, the redaction, changed files only), src/lib/shareExclusions.js (`niro share exclude`); niro-local src/niro_local/share_files.py (which files); src/commands/watch.js startLocalUploads (every 5 minutes) -->
39
+
40
+ ## What is not sent
41
+
42
+ - **The contents of your files, when your agent asks Niro a question.** The engine on your computer answers.
43
+ <!-- source: src/lib/planUsage.js:118; src/mcp/server.js:359 -->
44
+ - **Your code to a language model (an LLM).** This release does not include LLM documentation.
45
+ <!-- source: src/lib/release.js:33 (llmDocs: false) -->
46
+ - **The files of folders you once set up for hosted Niro.** A computer that used hosted Niro still lists those folders in `~/.niro/projects.json`. The background watcher never syncs them in this release: it uploads none of their files, also when that list changes. A folder the engine on your computer indexes is built there instead.
47
+ <!-- source: src/commands/watch.js:238-240 (hostedFolders: no hosted folder without the cloud), :176 (the re-read when projects.json changes uses that list), :243 (so does start-up), :662-674 (startWatch: a locally indexed folder goes to the local watch, any other returns before the sync); src/lib/release.js:33 (cloud: false); test/watchHostedGuard.test.js -->
48
+ - **Anything not in the table above.** The table lists every call `niro` makes to Niro's services in this release. How long each `niro start` step took is kept on your computer only. `niro upgrade` makes no check call of its own: it runs npm, which talks to the npm registry.
49
+ <!-- source: the calls in this release: src/lib/api.js, src/lib/lease.js, src/lib/engine.js, src/lib/auditUpload.js, src/lib/journalUpload.js, src/lib/repoPush.js, src/lib/shareCode.js, src/commands/whoami.js:48, src/commands/logout.js:19-20, src/commands/cleanup.js:245-298 (src/lib/agentApi.js is used by `niro audit-push-repo` (src/lib/repoPush.js), by the background code sharing (src/lib/shareCode.js), and by the sweep's repository check, src/commands/cleanup.js:466; its sync and upload calls are reached otherwise only from the watcher's sync, which never starts without the cloud, src/commands/watch.js:671-674 and src/lib/release.js:33, and from the cloud commands, which this release does not register, src/index.js:475-482, as are the callers of src/lib/tempProject.js); src/lib/onboardingLog.js:1-6 ("Kept on the machine only"); src/commands/upgrade.js (npm install -g @niroai/niro@latest) -->
50
+
51
+ ## How secrets are hidden
52
+
53
+ Everything that leaves is masked on your computer first: secret values are replaced with placeholders. In audit snippets and in shared code, the names of authors and companies are replaced with `<author>` and `<company>`. See [secrets-and-masking.md](secrets-and-masking.md).
54
+ <!-- source: niro-local src/niro_local/redact/mask.py:1-75; src/lib/redact.js:1-18 -->
55
+
56
+ ## Niro's Data Processing Agreement
57
+
58
+ Niro's Data Processing Agreement (DPA) sets out how Niro handles customer data. Version 3.0 is dated 2026-08-16. In short:
59
+ <!-- source: Niro wiki, Legal collection, "Data Processing Agreement" v3.0 (status "Current. Authoritative version", classification "Public. Issued to customers", last_reviewed 2026-08-16) -->
60
+
61
+ - **No training on your content.** Niro does not use your code to train or fine-tune an AI model. (Clause 10.1.)
62
+ - **Outside AI providers.** Where Niro uses an outside AI provider, the provider is set up so that it does not keep your content or train on it. A masking step removes secrets and common personal data first. The DPA says this is best effort, not a guarantee. (Clause 10.2.)
63
+ - **Small extracts kept to fix parsing.** To fix a parsing defect, Niro may keep a small extract of code, masked, for at most 90 days. (Clause 10.3.)
64
+ - **Usage signals.** Niro records whether its answers were useful. Niro may use these signals to improve the service, including its own models. (Clause 10.4.)
65
+ - **Opting out.** You can ask in writing to switch off the extracts in 10.3 and the signals in 10.4. Niro confirms within 10 business days. (Clause 10.5.)
66
+ - **Breaches.** Niro tells you within 72 hours of learning about a data breach that affects your data. (Clause 7.1.)
67
+ - **When you stop using Niro.** You can ask for your data back within 30 days. After that, Niro deletes it within 30 days. Copies in backups stay protected until the normal backup cycle deletes them. (Clause 8.)
68
+ - **Account data** is deleted 90 days after the account is closed. (Annex 1.)
69
+
70
+ To delete your account, email **help@niro.ai**. Ask the same address for a copy of the DPA.
71
+ <!-- source: account deletion by email to help@niro.ai: Niraj, 2026-11-10 -->
@@ -0,0 +1,59 @@
1
+ # Where things are stored
2
+
3
+ **Niro keeps its files in two folders in your home folder.** `~/.niro` belongs to the `niro` command. `~/.niro-local` belongs to the local engine, which holds the graph. Both are readable by your user account only.
4
+ <!-- source: src/lib/credentials.js:51-72 (credentials 0600, folder 0700); niro-local src/niro_local/registry.py:53-104 (home 0700, files 0600, umask 077) -->
5
+
6
+ ## `~/.niro`
7
+
8
+ | File or folder | What it holds |
9
+ |---|---|
10
+ | `credentials.json` | Your sign-in. Readable by you only |
11
+ | `config.json` | Settings: the service addresses and the console address you signed in through. See [reference-config-files.md](reference-config-files.md) |
12
+ | `bin/<version>/` | The downloaded local engine. Set `NIRO_ENGINE_HOME` to keep it elsewhere |
13
+ | `local-worktrees.json` | The worktrees the watcher has seen, and its build retries |
14
+ | `onboarding.json` | How long each step of `niro start` took, and how it ended. Kept on this computer only |
15
+ | `periodic.json` | When each regular job last ran |
16
+ | `audit-repos.json` | Folders sent with `niro audit-push-repo` or shared in the background, and the workspace each went to |
17
+ | `shared-code.json` | For code sharing: per folder, a fingerprint (SHA-256), size and time of each file last sent, so only changed files are sent again. No file contents. Readable by you only |
18
+ | `share-excluded.json` | The folders kept out of code sharing (`niro share exclude`). Readable by you only |
19
+ | `journal-refused/` | Tool-usage records the service refused, kept for you to look at |
20
+ | `receipts/` | Used by older versions only. Removed when you remove Niro from Claude Code, or by `niro uninstall` |
21
+ | `impact/` | The recent-edits log the impact check uses |
22
+ | `receipts-off`, `impact-gate-off` | Empty files whose presence turns the end-of-task line or the impact check off |
23
+ | `projects.json` | Used by hosted Niro only |
24
+ <!-- source: src/lib/credentials.js:51-72; src/lib/config.js:16-38; src/lib/engine.js:44; src/lib/localWorktrees.js:28; src/lib/onboardingLog.js:1-13; src/lib/periodicClaim.js:29-30; src/lib/repoPush.js:78; src/lib/journalUpload.js:120-129; src/lib/receiptStore.js:64-75 and src/mcp/teardown.js:396 (receipts/ is removed with Niro's Claude Code entry); src/lib/impactLog.js:59-92; src/lib/receiptStore.js:33; src/lib/watchState.js:1-30 -->
25
+
26
+ ## `~/.niro-local`
27
+
28
+ Set `NIRO_LOCAL_HOME` to keep this folder elsewhere.
29
+ <!-- source: niro-local src/niro_local/registry.py:40-51 -->
30
+
31
+ | File | What it holds |
32
+ |---|---|
33
+ | `graph.db` | The graph, for every project on this computer |
34
+ | `build-state.db` | What each build needs to be quick next time: a fingerprint (hash), size and time of each file, and what reading each file found |
35
+ | `registry.db` | Your projects, their folders and branches, and your settings for them |
36
+ | `journal.db` | The record of which tools your agent used, waiting to be sent |
37
+ | `audit-queue.db` | Coverage audit findings, waiting to be sent |
38
+ | `docs.db` | For LLM documentation, which this release does not include |
39
+ | `indexed-folders`, `locks/` | The list of indexed folders, and lock files that stop two builds from clashing |
40
+ <!-- source: niro-local src/niro_local/home_files.py:16-17; src/niro_local/registry.py:132, :167-171; src/niro_local/journal/store.py:194; src/niro_local/audit/queue.py:654; src/niro_local/reuse/store.py:109; src/niro_local/build/state.py:1-40 -->
41
+
42
+ There is one `graph.db` and one `build-state.db` for all projects. To check the graph file, run `niro db status`. To check that it opens, run `niro db check`.
43
+ <!-- source: niro-local src/niro_local/home_files.py:1-8; src/commands/engineRoutes.js:36 (db) -->
44
+
45
+ **Is your source code stored?** The graph does not keep your source. It keeps names, places and links. The build state keeps fingerprints of files, and what reading them found. That can include short values written in the code, such as a default value. All of it stays on this computer.
46
+ <!-- source: niro-local src/niro_local/build/state.py:1-40; niro-local docs/NIRO-LOCAL-ISSUES.md:863 (a default written in source stays in build-state.db, on the machine only) -->
47
+
48
+ ## Files written into your coding agents
49
+
50
+ `niro mcp install` writes into each agent's own settings. The files are listed on each agent's page, for example [agent-cursor.md](agent-cursor.md).
51
+
52
+ ## The background watcher
53
+
54
+ The watcher is run by PM2, which keeps its list of programs in `~/.pm2/dump.pm2`. Niro makes that file readable by you only. On macOS, starting the watcher at login uses a file in `~/Library/LaunchAgents`.
55
+ <!-- source: src/lib/service.js:36, :120, :264, :316 -->
56
+
57
+ ## What `niro uninstall` removes
58
+
59
+ It removes `~/.niro`, `~/.niro-local`, the watcher and the agent settings. See [uninstall.md](uninstall.md).
@@ -0,0 +1,55 @@
1
+ # Worktrees and branches
2
+
3
+ **Niro keeps one graph per folder and per branch.** When you switch branch, Niro answers from that branch's graph. A git worktree is a second folder checked out from the same repository. When you open a new worktree, Niro builds it too.
4
+
5
+ ## How Niro knows your branch
6
+
7
+ Niro reads the file `.git/HEAD` in the folder. It never runs git.
8
+ <!-- source: niro-local src/niro_local/repository_identity.py:1-24, :219-251 -->
9
+
10
+
11
+ - **Detached HEAD.** If you are not on a named branch, Niro answers from the folder's last used graph, and says so.
12
+ - **No git.** A folder outside git has no branch. Give it one when you add it: `niro init --branch <name>`.
13
+ <!-- source: niro-local src/niro_local/repository_identity.py:1-24, :219-251; src/niro_local/mcp/server.py:724; src/index.js:490-502 (--branch) -->
14
+
15
+ ## Switching branch
16
+
17
+ - **To a branch Niro has seen before.** Niro switches to that branch's graph. Each answer then carries one line saying which project and build it came from.
18
+ <!-- source: niro-local src/niro_local/mcp/server.py:701-757, :734 -->
19
+ - **To a new branch of a repository Niro already indexes.** Niro makes a **temporary project** for it. It copies only this folder's graph from the main project, so the first answers are quick, and then builds the folder from its own files. Until that build is done, answers carry a note saying the graph was copied and not yet built.
20
+ <!-- source: niro-local src/niro_local/temporary.py:175-240; src/niro_local/offer.py:393-397; src/niro_local/mcp/server.py:775 -->
21
+ - It is named after the project and the branch, for example `shop-feature-login`.
22
+ - Your settings for the folder are copied with it: route rules, environment values and base address.
23
+ <!-- source: niro-local src/niro_local/temporary.py:175-184, :187-240 -->
24
+ - **All folders of one project must be on one branch.** If they are not, Niro says "A project's folders must all be on one branch".
25
+ <!-- source: niro-local src/niro_local/offer.py:243; src/niro_local/onboard.py:267 -->
26
+
27
+ ## New worktrees
28
+
29
+ The background watcher builds a new worktree of a repository Niro already indexes. Worktrees that already existed when Niro first saw the repository are noted, but not built. Build one of those with `niro build` in its folder.
30
+ <!-- source: src/lib/localWorktrees.js:1-21; src/commands/watch.js:278-292 -->
31
+
32
+ If a worktree's build fails, the watcher tries again after 2 minutes and after 10 minutes. After 3 tries it stops, and logs "Gave up building <folder> after N tries; run `niro build` there to see why."
33
+ <!-- source: src/lib/localWorktrees.js:71-72, :198 -->
34
+
35
+ ## Which folder an agent's questions are about
36
+
37
+ **Niro's tools answer for the folder the agent's session started in.** If you work in another folder (another worktree, or another service), two things help:
38
+
39
+ - run `niro status` there to see whether it is indexed;
40
+ - run `niro build <folder>` after editing it.
41
+ <!-- source: src/mcp/localMode.js:39 (RELEASE_WORKTREE_SENTENCE) (RELEASE_WORKTREE_SENTENCE) -->
42
+
43
+ ## Clean-up
44
+
45
+ Niro removes old copies by itself. It does this at the end of a build, never while answering a question:
46
+ <!-- source: niro-local src/niro_local/cleanup.py:8-11 ("A build ends by calling `sweep`"; "An MCP answer never touches any of this") -->
47
+
48
+
49
+ - **A temporary project** is removed after 14 days with no build and no question. It is kept while a build is running, while one of its folders is on that branch, or while a folder of it is missing.
50
+ - **The graph of a folder deleted from disk** is removed 14 days after Niro first saw it missing.
51
+ - **To remove missing folders now,** run `niro clean`.
52
+ <!-- source: niro-local src/niro_local/cleanup.py:37-58, :116-147; src/commands/engineRoutes.js:35 (clean) -->
53
+
54
+ Change the 14 days with `NIRO_LOCAL_BRANCH_INDEX_IDLE_DAYS` and `NIRO_LOCAL_MISSING_FOLDER_DAYS`. Set either to 0 to turn that clean-up off.
55
+ <!-- source: niro-local src/niro_local/cleanup.py:37-40, :47-58 -->
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@niroai/niro",
3
- "version": "0.16.0",
4
- "description": "Niro \u2014 one package: the niro CLI plus the Niro MCP server (code intelligence for AI coding assistants).",
3
+ "version": "0.18.0",
4
+ "description": "Niro — one package: the niro CLI plus the Niro MCP server (code intelligence for AI coding assistants).",
5
5
  "keywords": [
6
6
  "niro",
7
7
  "mcp",
@@ -21,12 +21,14 @@
21
21
  },
22
22
  "main": "src/index.js",
23
23
  "scripts": {
24
- "test": "node --test"
24
+ "test": "node --require ./test/_noRealDaemon.js --require ./test/_cloudOn.js --test"
25
25
  },
26
26
  "files": [
27
27
  "bin",
28
28
  "src",
29
29
  "docs/*.md",
30
+ "docs/llms.txt",
31
+ "docs/llms-full.txt",
30
32
  "README.md",
31
33
  "CHANGELOG.md"
32
34
  ],
@@ -26,6 +26,7 @@ const color = require("../lib/color");
26
26
  const watchDaemon = require("../lib/watchDaemon");
27
27
  const { login } = require("./login");
28
28
  const onboardTarget = require("../lib/onboardTarget");
29
+ const cloudGuard = require("../lib/cloudGuard");
29
30
 
30
31
  async function addRepo(pathArg, opts = {}) {
31
32
  const targetRaw = pathArg || ".";
@@ -52,6 +53,10 @@ async function addRepo(pathArg, opts = {}) {
52
53
  process.exitCode = 1;
53
54
  return;
54
55
  }
56
+ // Before the login, for the same reason as the gate below: this command uploads the
57
+ // folder, and a folder the engine on this machine already indexes must not be uploaded
58
+ // and indexed a second time.
59
+ if (await cloudGuard.refuseIfIndexedLocally(targetDir, { command: "add repo" })) return;
55
60
  // Same onboarding gate as `niro init` — this command uploads a folder too, so it must
56
61
  // refuse $HOME / the filesystem root on the same terms. A folder this backend already
57
62
  // has a repo for is exempt from the BUILD-DESCRIPTOR half: that half gates new
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `niro audit-send` — hand the local engine's coverage-audit findings to niro-audit.
3
+ *
4
+ * The engine finds the places where the graph does not match the source, during the build it
5
+ * was already doing, and queues them. This sends them. It exists as a command of its own so
6
+ * the path can be run and watched by hand; in normal use the watcher calls the same library
7
+ * after a build, next to the MCP journal upload.
8
+ *
9
+ * Prints counts and never a payload: those carry the customer's source.
10
+ */
11
+ const auditUpload = require("../lib/auditUpload");
12
+ const color = require("../lib/color");
13
+
14
+ async function auditSend(opts = {}) {
15
+ const limit = Number(opts.limit) || 50;
16
+ console.log(`Sending audit findings to ${auditUpload.auditUrl()}`);
17
+
18
+ const result = await auditUpload.uploadPending({ limit });
19
+ if (result.skipped) {
20
+ console.log(` Nothing sent: ${result.skipped}.`);
21
+ return result;
22
+ }
23
+
24
+ const parts = [];
25
+ if (result.submitted) parts.push(`${result.submitted} file(s) submitted`);
26
+ if (result.duplicate) parts.push(`${result.duplicate} already held`);
27
+ if (result.resolved) parts.push(`${result.resolved} closed as clean`);
28
+ if (result.catalogs) parts.push(`${result.catalogs} catalogue list(s) of extensions and libraries sent`);
29
+ if (result.notText) parts.push(`${result.notText} flagged file(s) dropped, not UTF-8 text`);
30
+ if (result.notSnippet) parts.push(`${result.notSnippet} flagged file(s) dropped, could not be sent as a snippet`);
31
+ if (parts.length === 0 && !result.unmasked) parts.push("nothing waiting");
32
+ // Only when there is something to say: it printed a bare " " line before.
33
+ if (parts.length > 0) console.log(` ${parts.join(", ")}`);
34
+
35
+ if (result.unmasked) {
36
+ // Lines from an engine older than on-machine masking: never sent unmasked, so they wait.
37
+ console.log(color.yellow(
38
+ ` ${result.unmasked} item(s) not sent: the local engine is too old to mask secrets. Run \`niro upgrade\`.`
39
+ ));
40
+ }
41
+
42
+ if (result.catalogsWaiting) {
43
+ // An audit service from before the catalogue route: the lists wait on this machine until it takes them.
44
+ console.log(` ${result.catalogsWaiting} catalogue list(s) kept: the audit service does not take them yet`);
45
+ }
46
+
47
+ if (result.closedNothing) {
48
+ // A resolve that matched nothing answers 200, so without this it would read as a clean
49
+ // close. It usually means the server never had an open item for that file.
50
+ console.log(
51
+ ` ${result.closedNothing} close request(s) matched nothing on the server`
52
+ );
53
+ }
54
+
55
+ if (result.failed) {
56
+ // Loud, because a silent failure here looks like a clean run and the findings would sit
57
+ // in the queue for ever. They stay queued on purpose and are retried next time.
58
+ console.log(color.yellow(` ${result.failed} item(s) could not be sent, still queued:`));
59
+ for (const failure of result.failures.slice(0, 5)) console.log(` ${failure}`);
60
+ if (result.failures.length > 5) {
61
+ console.log(` ... and ${result.failures.length - 5} more`);
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+
67
+ module.exports = { auditSend };
@@ -3,6 +3,7 @@ const credentials = require("../lib/credentials");
3
3
  const spinner = require("../lib/spinner");
4
4
  const color = require("../lib/color");
5
5
  const { resolveProjectId } = require("./_resolveProject");
6
+ const cloudGuard = require("../lib/cloudGuard");
6
7
 
7
8
  const TERMINAL = new Set(["COMPLETED", "FAILED", "CANCELLED"]);
8
9
 
@@ -15,6 +16,11 @@ function colorizeStatus(st) {
15
16
  }
16
17
 
17
18
  async function build(projectIdArg, opts = {}) {
19
+ // Before the credentials check, so a folder indexed on this machine is refused without
20
+ // first costing a login. Keyed on the FOLDER and not on the project id: a cloud build
21
+ // triggered from inside a locally indexed folder is the mistake this catches, whether the
22
+ // id was typed or came from the `.niro-project` pin.
23
+ if (await cloudGuard.refuseIfIndexedLocally(process.cwd(), { command: "build" })) return;
18
24
  credentials.requireCreds();
19
25
  const projectId = await resolveProjectId(projectIdArg, { apiUrl: opts.apiUrl });
20
26
  const force = opts.fullRebuild !== false;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `niro docs-send`: send the local engine's documentation requests through Niro, once.
3
+ *
4
+ * For folders built with `niro-local build --llm`. The engine writes one request per batch of up
5
+ * to 12 methods; this sends them to ai-assistant with this machine's login and its licence lease,
6
+ * and hands the answers back to the engine. The watch daemon does the same about every ten
7
+ * minutes; this command exists so the path can be run and watched by hand.
8
+ *
9
+ * Prints counts and never a request or an answer: both carry or describe the customer's code.
10
+ */
11
+ const docsUpload = require("../lib/docsUpload");
12
+ const { assistantUrl } = require("../lib/journalUpload");
13
+ const color = require("../lib/color");
14
+
15
+ /** What a stopped round means, in words for a person. */
16
+ const STOPPED = {
17
+ busy: "Another documentation run is going on this machine; this one did nothing.",
18
+ "no-engine": "The local engine is not installed on this machine, so there is nothing to send.",
19
+ "engine-refused": "The local engine did not hand over any requests",
20
+ "no-lease": "Could not get a licence lease from Niro, so nothing was sent",
21
+ "lease-refused": "Niro would not issue a licence lease, so nothing was sent",
22
+ unauthorized: "Niro did not accept this machine's login. Run `niro login` and try again.",
23
+ forbidden: "Niro refused documentation for this account",
24
+ };
25
+
26
+ function parseLimit(value) {
27
+ if (value === undefined) return docsUpload.DEFAULT_LIMIT;
28
+ const n = Number(value);
29
+ if (!Number.isInteger(n) || n < 1) {
30
+ throw new Error(`--limit must be a whole number of 1 or more, not "${value}".`);
31
+ }
32
+ return n;
33
+ }
34
+
35
+ async function docsSend(opts = {}) {
36
+ const limit = parseLimit(opts.limit);
37
+ console.log(`Sending documentation requests to ${assistantUrl()}`);
38
+
39
+ const result = await docsUpload.sendPending({ limit, log: console.log });
40
+
41
+ if (result.stopped) {
42
+ const text = STOPPED[result.stopped] || `Stopped (${result.stopped})`;
43
+ const line = result.reason && !text.endsWith(".") ? `${text}: ${result.reason}` : text;
44
+ console.log(color.yellow(` ${line}`));
45
+ }
46
+ if (result.model && result.model.error) {
47
+ console.log(color.yellow(` The embedding model could not be fetched: ${result.model.error}`));
48
+ } else if (result.model && result.model.fetched) {
49
+ console.log(` Fetched ${result.model.fetched} embedding model file(s)`);
50
+ }
51
+ if (result.marked === 0) {
52
+ console.log(" no folder is built with --llm, so there is nothing to document");
53
+ } else if (result.lines === 0 && !result.stopped) {
54
+ console.log(" nothing waiting");
55
+ } else if (result.lines > 0) {
56
+ const parts = [`${result.sent} batch(es) sent`];
57
+ // Not said when the import failed: the engine stored nothing, so nothing was documented.
58
+ if (!result.importFailed) parts.push(`${result.documented} method(s) documented`);
59
+ if (result.waiting) parts.push(`${result.waiting} batch(es) waiting after earlier failures`);
60
+ console.log(` ${parts.join(", ")}`);
61
+ }
62
+ if (result.failed) {
63
+ console.log(color.yellow(` ${result.failed} batch(es) failed and will be asked for again later:`));
64
+ for (const failure of result.failures.slice(0, 5)) console.log(` ${failure}`);
65
+ if (result.failures.length > 5) console.log(` ... and ${result.failures.length - 5} more`);
66
+ }
67
+ if (result.importFailed) {
68
+ console.log(color.yellow(` The engine could not store the answers: ${result.importFailed}`));
69
+ }
70
+ return result;
71
+ }
72
+
73
+ module.exports = { docsSend, parseLimit };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * `niro engine` — put the local engine on this machine, or say where it is.
3
+ *
4
+ * The local engine indexes a repository on the developer's own machine instead of uploading it.
5
+ * It is a compiled binary of its own, not part of this npm package, so it is downloaded on
6
+ * first use from Niro (Niraj, 2026-10-14) and cached under ~/.niro/bin.
7
+ *
8
+ * **Why this command exists at all**, when the plan says the download is called from the
9
+ * wizard, from `niro build` and from the MCP bridge: none of those three asks for the engine
10
+ * yet. `niro build` still builds in the cloud, and whether it keeps that option is an open
11
+ * decision; the bridge does not route to a local graph until the routing phase. Downloading an
12
+ * engine from a command that does not use it would be 60 MB nobody asked for. So the download
13
+ * is deliberate for now, and the three call sites come with the phases that need them.
14
+ */
15
+ const engine = require("../lib/engine");
16
+ const graphCheck = require("../lib/graphCheck");
17
+ const color = require("../lib/color");
18
+
19
+ /** How each way of finding the engine is named to the developer. */
20
+ const WHERE_FROM = {
21
+ env: " (from NIRO_LOCAL_BIN)",
22
+ path: " (on your PATH)",
23
+ cache: "",
24
+ };
25
+
26
+ async function install(opts = {}) {
27
+ const already = engine.installed();
28
+ let where;
29
+ try {
30
+ where = await engine.ensure({
31
+ log: (line) => console.log(` ${line}`),
32
+ force: !!opts.force,
33
+ latest: true,
34
+ });
35
+ if (already && where === already) {
36
+ // The published version is the one here. Asked of the service each time, so an old one is never left in place
37
+ // by being "already here".
38
+ console.log(` The local engine is here: ${already}`);
39
+ await sayIfItCannotRun();
40
+ return;
41
+ }
42
+ } catch (err) {
43
+ if (already && !opts.force) {
44
+ // No connection, or the service said no: the engine here keeps working.
45
+ console.log(` The local engine is here: ${already}`);
46
+ console.log(` Could not check for a newer one: ${err.message}`);
47
+ await sayIfItCannotRun();
48
+ return;
49
+ }
50
+ // An answer from Niro, not a fault: the plan does not include the engine, or no engine is
51
+ // published for this platform yet. Said in the service's own words, with no route and no
52
+ // status code, and the command still ends non-zero so a script can tell.
53
+ if (!err || err.code !== "EENGINEREFUSED") throw err;
54
+ console.log(` ${err.message}`);
55
+ console.log(" Nothing was installed.");
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+ console.log(` ${color.green("✓")} The local engine is ready: ${where}`);
60
+ await sayIfItCannotRun();
61
+ }
62
+
63
+ /**
64
+ * The engine is here. Can it open its graph on this machine? Asked after the download, because
65
+ * the question is put to the engine itself (`niro-local db check`). When it cannot, its own
66
+ * words are shown and the command ends non-zero; nothing else is offered and nothing is
67
+ * uploaded (Niraj, 2026-10-31, D2: the cloud offer is dropped).
68
+ */
69
+ async function sayIfItCannotRun() {
70
+ const usable = await graphCheck.graphDatabaseUsable();
71
+ if (usable.ok || usable.missing) return;
72
+ console.log("");
73
+ console.log(color.yellow(" The local engine cannot open its graph on this machine:"));
74
+ for (const line of usable.problem.split("\n")) {
75
+ console.log(` ${line}`);
76
+ }
77
+ console.log("");
78
+ process.exitCode = 1;
79
+ }
80
+
81
+ async function status() {
82
+ let which = null;
83
+ try {
84
+ which = engine.label();
85
+ } catch (err) {
86
+ console.log(` ${color.red("✗")} ${err.message}`);
87
+ return;
88
+ }
89
+ const found = engine.resolve();
90
+ console.log(` platform ${which}`);
91
+ if (!found) {
92
+ console.log(` engine not installed. Run: niro engine install`);
93
+ } else {
94
+ // Where it came from is said out loud, because the three sources behave differently:
95
+ // only the cache is replaced by `niro engine install`, and a developer who has forgotten
96
+ // an exported variable has no other way to find out which engine is answering.
97
+ console.log(` engine ${found.path}${WHERE_FROM[found.source] || ""}`);
98
+ // A machine holds one version. The cache is what niro starts; a copy on the PATH is what a person gets by typing
99
+ // the name, and an older one refuses a home a newer one has used.
100
+ const onPath = found.source === "path" ? null : engine.onThePath(engine.EXECUTABLE);
101
+ if (onPath && onPath !== found.path) {
102
+ console.log(` ${color.yellow("another")} ${onPath} is also on your PATH. niro does not use it. Remove it, so`);
103
+ console.log(" that one version is on this machine.");
104
+ }
105
+ }
106
+ console.log(` cache ${engine.home()}`);
107
+ // Whether it can actually serve a graph here, which is a different question from whether
108
+ // it is installed.
109
+ const usable = await graphCheck.graphDatabaseUsable();
110
+ if (usable.ok) {
111
+ console.log(` database ready`);
112
+ } else if (usable.missing) {
113
+ console.log(` database unknown until the engine is installed`);
114
+ } else {
115
+ const first = usable.problem.split("\n")[0].replace(/^error:\s*/, "");
116
+ console.log(` database cannot run: ${first}`);
117
+ }
118
+ }
119
+
120
+ module.exports = { install, status };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The local engine's commands, offered as niro commands (Niraj, 2026-11-06: "ensure all commands and
3
+ * options of niro-local are available to niro so that if user fires niro instead of niro-local, they
4
+ * are routed to niro-local").
5
+ *
6
+ * In a release, people have `niro` and not `niro-local`: the engine sits in niro-cli's cache, and a
7
+ * released engine builds and answers only with the licence niro passes it. So `niro env list`,
8
+ * `niro ui`, `niro ask get_call_chain method_fqn=X` and the rest run the engine with exactly the
9
+ * words typed after the command name, attached to the person's terminal, and niro exits with the
10
+ * engine's exit code.
11
+ *
12
+ * The rule for names (approved by Niraj, 2026-11-06, "2 okay with your advice"):
13
+ * - niro keeps its own meaning for the four names both have: `init`, `build`, `status`, `mcp`.
14
+ * `niro build` also takes the engine's `--full` and `--project`, and `niro status` shows the
15
+ * engine's status after its own (both in src/index.js).
16
+ * - every other engine command passes straight through, below.
17
+ * - `remove` and `projects` are free only while the cloud is off: with it on they are niro's
18
+ * cloud commands, so they are routed only then.
19
+ *
20
+ * Only `ask` and `docs` carry the licence: of the engine's commands, those are the ones that
21
+ * refuse without one (`build` and `mcp` too, but those names are niro's). The rest write nothing to
22
+ * the graph, and a lease request each time someone lists their settings is not worth making.
23
+ */
24
+ const localEngine = require("../lib/localEngine");
25
+
26
+ /** name, what it does (the engine's own help line), and whether it needs the licence. */
27
+ const ENGINE_COMMANDS = [
28
+ ["ask", "Ask one graph tool from the shell and print its answer (no tool: list them). `niro ask --help` for more.", true],
29
+ ["ui", "Look at a folder's graph in a browser; runs until you stop it (Ctrl-C).", false],
30
+ ["env", "The settings this folder's code reads: find them, list them, and give them values for the graph.", false],
31
+ ["base-url", "Show or change the address this folder's service answers on.", false],
32
+ ["route-rule", "Say where a path goes when the project does not: list, add, remove, replace, unconnected.", false],
33
+ ["modules", "Which folders inside this one are their own deployable.", false],
34
+ ["clean", "Remove the index of every folder missing from disk, now, and give the space back.", false],
35
+ ["db", "The graph file: its status, a check that it opens, or its removal.", false],
36
+ ["journal", "What the MCP server was asked, waiting to be sent (`niro journal-send` sends it).", false],
37
+ ["audit", "What the last builds found wrong with their own graph (`niro audit-send` sends it).", false],
38
+ ["impact", "What Niro did for you in the last 7 and 30 days: numbers and good moments (`--days N`, `--project <name>`, `--json`).", false],
39
+ ["docs", "LLM documentation for folders built with --llm: requests out, answers in.", true],
40
+ ];
41
+
42
+ /** Free only while the cloud is off; niro's own cloud commands when it is on. */
43
+ const LOCAL_ONLY_RELEASE_COMMANDS = [
44
+ ["remove", "Stop indexing a folder: its graph is removed and it is forgotten (`--path <folder>`, `--project <name>`).", false],
45
+ ["projects", "List every project on this machine and its folders (`--json` for a program).", false],
46
+ ];
47
+
48
+ /**
49
+ * The words typed after `name`, untouched, for the engine. Read from the command line itself rather
50
+ * than from what the parser collected, which would reorder options and operands. niro's one option
51
+ * before a command, `--api-url`, is refused for every command but login before this runs.
52
+ */
53
+ function argsAfter(name, argv = process.argv) {
54
+ const at = argv.indexOf(name, 2);
55
+ return at === -1 ? [] : argv.slice(at + 1);
56
+ }
57
+
58
+ /** Said when there is no engine to start. Same words as `niro init` and `niro build`. */
59
+ const NO_ENGINE = "The local engine is not installed. Run `niro start` once to set up this machine.";
60
+
61
+ /**
62
+ * Run engine command `name` with `args`, attached, and set niro's exit code to the engine's.
63
+ * An engine stopped by a signal stops niro with the same signal, so a shell sees the same status
64
+ * it would have seen from the engine.
65
+ */
66
+ async function routeToEngine(name, args, { licensed, runAttached = localEngine.runAttached, kill = process.kill } = {}) {
67
+ const result = await runAttached([name, ...args], { licensed });
68
+ if (result.error) {
69
+ console.error(result.error.code === "ENOENT" ? NO_ENGINE : `Could not start the local engine: ${result.error.message}`);
70
+ process.exitCode = 1;
71
+ return result;
72
+ }
73
+ if (result.signal) {
74
+ const number = require("os").constants.signals[result.signal];
75
+ process.exitCode = 128 + (number || 0);
76
+ try {
77
+ kill(process.pid, result.signal);
78
+ } catch {
79
+ // The exit code above stands.
80
+ }
81
+ return result;
82
+ }
83
+ process.exitCode = result.code == null ? 1 : result.code;
84
+ return result;
85
+ }
86
+
87
+ /**
88
+ * Register the routed commands on `program`. Each takes any words and options and leaves `--help`
89
+ * to the engine, so `niro env --help` shows the engine's own options.
90
+ */
91
+ function register(program, { cloud, llmDocs }) {
92
+ const commands = cloud ? ENGINE_COMMANDS : [...ENGINE_COMMANDS, ...LOCAL_ONLY_RELEASE_COMMANDS];
93
+ for (const [name, description, licensed] of commands) {
94
+ program
95
+ .command(name, { hidden: name === "docs" && !llmDocs })
96
+ .description(description)
97
+ .argument("[args...]")
98
+ .helpOption(false)
99
+ .allowUnknownOption(true)
100
+ .allowExcessArguments(true)
101
+ .action(() => routeToEngine(name, argsAfter(name), { licensed }));
102
+ }
103
+ }
104
+
105
+ module.exports = { ENGINE_COMMANDS, LOCAL_ONLY_RELEASE_COMMANDS, argsAfter, register, routeToEngine, NO_ENGINE };