@niroai/niro 0.11.0 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,134 @@
2
2
 
3
3
  Notable changes to `@niroai/niro`. Dates are release dates.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.13.0 — 2026-09-08
8
+
9
+ ### Upgrading
10
+
11
+ You do not have `niro upgrade` yet, so this one takes two commands:
12
+
13
+ ```bash
14
+ npm install -g @niroai/niro@latest
15
+ niro upgrade --finish
16
+ ```
17
+
18
+ The first gets the package. The second is the half npm cannot do: it re-runs the installer for
19
+ every assistant you have configured and restarts the background watcher. Then restart your
20
+ assistant. Skipping the second command leaves you on this version with the previous version's
21
+ hooks, skills and instruction files, which is the exact problem this release exists to fix.
22
+
23
+ From here on it is one command, `niro upgrade`, which does all of it.
24
+
25
+ ### `niro upgrade` gets the new version AND the things a new version cannot bring with it
26
+
27
+ Upgrading was `npm install -g @niroai/niro@latest`, and every release note in this file said so and
28
+ nothing else. That command is not enough, and has never been enough. The MCP entry is fine: it runs
29
+ plain `niro` off your `PATH` with no version in it, so the new server is picked up for free. But the
30
+ hooks, the skills and the CLAUDE.md/AGENTS.md/rules text are written into your home folder as fixed
31
+ text at install time, not read from the package as it runs. npm cannot touch them. Nothing told you
32
+ that, and nothing re-ran the installer, so a machine that installed once in June was still running
33
+ June's instructions in September while reporting the current version. The background watcher had the
34
+ same problem from the other end: it is a long-running process holding the code it loaded at start,
35
+ so replacing files on disk left it on the old version until something restarted it.
36
+
37
+ `niro upgrade` does all three. It replaces the package, re-runs the installer for every assistant it
38
+ can prove is configured, and restarts the watcher if it is running.
39
+
40
+ It runs as two processes, and the reason is worth writing down. Every command in this CLI is loaded
41
+ lazily, inside its own action handler, and `npm install -g` replaces the folder those files are
42
+ loaded from. One process that ran npm and then carried on would be running the modules it loaded
43
+ before the swap alongside modules loaded after it. So phase one runs npm, executes the NEW binary
44
+ with `--finish`, and stops, requiring nothing in between; phase two is a fresh process reading the
45
+ new tree. `niro start` has always dodged the same trap the same way, by only installing globally
46
+ when it is running from the npx cache and then handing over.
47
+
48
+ Which assistants to refresh is worked out by looking, because nothing on disk records what you
49
+ chose. All 14 clients are covered, because the installer leaves a file this repo owns in every case:
50
+ eight write a JSON config whose path `setup.js` builds, and the other six also get a marker appended
51
+ to a markdown file written here and stripped by `teardown.js` (`~/.claude/CLAUDE.md`,
52
+ `~/.gemini/GEMINI.md`, `~/.copilot/copilot-instructions.md`, `~/.factory/AGENTS.md`), plus codex's
53
+ `[mcp_servers.niro]` table and goose's `extensions:` entry. The marker is appended only after the
54
+ registration command succeeded, so its presence means the client really was configured. Detection is
55
+ read-only, and it is deliberately strict: a client whose entry is `enabled: false` or
56
+ `disabled: true` is one you switched off, so it is left alone rather than switched back on; a
57
+ marker has to be on a line of its own, because a sentence mentioning it is not an install; and every
58
+ path has to be absolute.
59
+
60
+ `niro upgrade --finish` on its own skips npm and only refreshes the assistant files and the watcher,
61
+ which is what you want if you already ran `npm install -g` by hand.
62
+
63
+ Without a usable home folder it checks nothing and says so. Every path would otherwise resolve
64
+ against the current folder, which is how the reverted serve-time self-heal came to write into a
65
+ project's own `.claude/settings.json` (see the 2026-07-30 revert). "Usable" means absolute, and it
66
+ also means rejecting a `HOME` set to the empty string even when `USERPROFILE` would fill in, because
67
+ four of the client path builders read `HOME` directly and would otherwise hand back a relative path.
68
+ This command never writes a settings file itself: refreshing hooks means spawning the existing
69
+ installer, once per client.
70
+
71
+ It refuses to upgrade a copy you are not running. The first real run of this command was from a
72
+ checkout linked with `npm link`, and `npm install -g` deleted that link and put the published
73
+ package in its place. So before npm is allowed to touch anything, `niro upgrade` looks at which
74
+ copy is running: the global package folder being a symlink means `npm link`, and it stops and says
75
+ so; a checkout or an npx copy that is not the global install stops too. `--force` overrides either.
76
+
77
+ It also asks the version it just installed whether it has an `upgrade` command before handing over
78
+ to it, because the newest published version can predate the one you are running. Without that
79
+ check the hand-over fails with a bare `error: unknown command 'upgrade'` and the advice printed is
80
+ to re-run a command that does not exist. Now it prints the two commands that do work.
81
+
82
+ Failures are visible. Each one has its own exit code (npm failed, no binary after npm, hand-over
83
+ failed, refresh incomplete), an interrupted npm is reported as interrupted rather than as "nothing
84
+ changed", a hand-over that cannot start prints why, and the hand-over carries a marker so a version
85
+ that does not understand `--finish` cannot loop back into running npm again.
86
+
87
+
88
+ ## 0.12.0 — 2026-09-06
89
+
90
+ ### Before an edit to indexed source, Claude is asked to map the method and to verify those flows afterwards
91
+
92
+ Every instruction file niro writes tells the assistant to map who depends on a method before changing
93
+ it. In practice the assistant finds the file, reads it, and edits; the rule is read early in a session
94
+ and forgotten by the middle of it. And the map was always meant for afterwards: the point is to go back
95
+ to the callers and flows once the edit is made and check they still hold.
96
+
97
+ Claude Code now gets a third niro hook, on Edit and Write. Before an edit to source in an indexed repo
98
+ it puts a short reminder in front of the assistant: decide whether the change can alter behaviour (a log
99
+ line or a comment cannot, say so and go ahead); if it can, map who depends on the method now with
100
+ get_blast_radius or get_impact_analysis and keep that list as the checklist for after the edit; and do
101
+ not re-query the graph to verify, because it updates only after the edit is synced and rebuilt. If an
102
+ impact call already named the file this session, the reminder instead says to go back to that list
103
+ after the edit. Each reminder appears once per file per session. It is advisory: it never approves or
104
+ refuses an edit. The evidence comes from the bridge, which writes one line per answered impact call
105
+ under `~/.niro/impact/`. New files, tests, config and docs are never mentioned. Off switch:
106
+ `touch ~/.niro/impact-gate-off`. `niro mcp uninstall` removes the hook and the evidence files.
107
+
108
+ The same three points were added to every instruction surface niro writes: the rule sentence in each
109
+ client's always-on file, the niro skill, the Cursor, Cline and Kiro rules, and the PostCompact and
110
+ prompt hooks. The clients without hooks read them there. Details: `docs/usecases/impact-gate.md`.
111
+
112
+ ### `niro mcp install` now refreshes Claude Code hooks on a machine where niro was already set up
113
+
114
+ Re-running the install on a machine with an existing registration failed at the first step, because
115
+ `claude mcp add` refuses a name that already exists, and the installer read that refusal as "Claude
116
+ Code is not installed" and stopped before writing any hook or the CLAUDE.md section. So the step the
117
+ out-of-date notice asks for did nothing for anyone who had installed once. The installer now reads the
118
+ existing registration first: same URL, it keeps it; a different `--url`, it replaces it; none, it adds
119
+ it. Hooks and sections are written in every case.
120
+
121
+ ### CommonJS and ES-module files (.cjs, .mjs, .cts, .mts) are now uploaded for indexing
122
+
123
+ When niro decided which files to upload for indexing, it skipped `.cjs`, `.mjs`, `.cts` and `.mts`,
124
+ so a `build.cjs` or an `index.mjs` was never indexed and never appeared in the graph. Those four
125
+ extensions are now included, matching the server's own file filter.
126
+
127
+ ### The niro rules now name get_exception_flow
128
+
129
+ The explore-first rule niro writes into each client now mentions `get_exception_flow`, the tool that
130
+ reports which exceptions can escape a method (and who must handle one). It is served by the niro
131
+ backend; this only makes the assistants aware it exists.
132
+
5
133
  ## 0.11.0 — 2026-09-03
6
134
 
7
135
  ### The watcher stops pausing and resuming your uploads over and over
package/README.md CHANGED
@@ -45,7 +45,9 @@ Then restart your assistant and ask it something:
45
45
  ```bash
46
46
  niro status
47
47
  ```
48
- Is my index up to date?
48
+ Is my index up to date? It answers about the project this folder is synced to **on the branch you
49
+ are on**, so once you have run `niro new-temp-project` it reports that copy rather than the shared
50
+ project. Name one explicitly to ask about a different project: `niro status <project-id>`.
49
51
 
50
52
  ```bash
51
53
  niro start
@@ -65,7 +67,15 @@ Every project in your account.
65
67
  ```bash
66
68
  niro build
67
69
  ```
68
- Rebuild the index by hand and watch it finish.
70
+ Rebuild the index by hand and watch it finish. Like `niro status`, it picks the project for the
71
+ branch you are on, so on a branch with a temporary project it rebuilds that one. Name a project to
72
+ rebuild a different one.
73
+
74
+ ```bash
75
+ niro upgrade
76
+ ```
77
+ Get the latest version, and bring your hooks, skills and watcher with it. See
78
+ [Upgrading](#upgrading).
69
79
 
70
80
  ```bash
71
81
  niro doctor
@@ -224,7 +234,10 @@ Detach a repo from a project. Nothing is deleted.
224
234
  ```bash
225
235
  niro remove --project --confirm-name <name>
226
236
  ```
227
- Delete a whole project. Destructive, hence the confirmation.
237
+ Delete a whole project. Destructive, hence the confirmation. It refuses on a temporary project and
238
+ sends you to `niro discard-temp-project` instead, because that removes the copy **and** points this
239
+ folder back at the shared project; deleting it here would leave the folder aimed at a project that
240
+ no longer exists. `niro delete` refuses for the same reason.
228
241
 
229
242
  ---
230
243
 
@@ -247,6 +260,58 @@ Watch this folder in the foreground instead, for one session.
247
260
 
248
261
  ---
249
262
 
263
+ ## Upgrading
264
+
265
+ **Check which you are on first:**
266
+
267
+ ```bash
268
+ niro --version
269
+ ```
270
+
271
+ **On 0.13.0 or newer**, one command:
272
+
273
+ ```bash
274
+ niro upgrade
275
+ ```
276
+
277
+ Replaces the package with the latest published version, then puts back the two things replacing the
278
+ package cannot reach: it re-runs the installer for every assistant you have configured, and
279
+ restarts the background watcher. Restart your assistant afterwards.
280
+
281
+ **On anything older than 0.13.0**, you do not have `niro upgrade` yet, so it is two commands, once:
282
+
283
+ ```bash
284
+ npm install -g @niroai/niro@latest
285
+ niro upgrade --finish
286
+ ```
287
+
288
+ The first gets the package. The second does the half npm cannot: your hooks, skills and the
289
+ watcher. After that, `niro upgrade` on its own is enough. Do not stop after the first command:
290
+ that leaves you on the new version running the old version's hooks, skills and instruction files,
291
+ which is the problem `niro upgrade` exists to solve.
292
+
293
+ **Why it is more than an npm install.** The hooks, skills and instruction files are written into
294
+ your home folder as fixed text when you install, not read out of the package as it runs, so a newer
295
+ package does not change them. The watcher is a long-running process still holding the code it
296
+ started with. The MCP config is the one part that needs nothing done to it: it runs plain `niro`
297
+ from your `PATH` with no version pinned in it, so the new server is picked up on its own.
298
+
299
+ **Restart your assistant last.** Skills and hooks are read when it starts, so nothing above reaches
300
+ a session that is already open.
301
+
302
+ Fully by hand, if you would rather:
303
+
304
+ ```bash
305
+ npm install -g @niroai/niro@latest
306
+ niro mcp install # once per assistant you use
307
+ niro service install # only if you run the watcher
308
+ ```
309
+
310
+ `niro start` upgrades nothing. On a machine that has connected an assistant once it skips the
311
+ connect step, so the old hooks and skills stay where they are.
312
+
313
+ ---
314
+
250
315
  ## Reference
251
316
 
252
317
  Add `--help` to any command for its flags. Add `--json` to `whoami`, `info`, `status`,
@@ -295,6 +360,10 @@ does nothing until you do.
295
360
 
296
361
  **Auto-sync is not running.** `npm install -g pm2`, then `niro service install`.
297
362
 
363
+ **A command or feature in the changelog is missing.** You are on an older version. Check with
364
+ `niro --version`, then see [Upgrading](#upgrading): before 0.13.0 it takes two commands, after it
365
+ one. Plain `npm install -g` on its own always leaves your hooks and skills on the old version.
366
+
298
367
  **A folder was refused.** Niro will not index your home folder, or a folder with no build file
299
368
  in it. Point it at the repo you meant.
300
369
 
@@ -46,6 +46,14 @@ It creates your own private copy of the project inside Niro.
46
46
 
47
47
  From then on, answers come from YOUR code.
48
48
 
49
+ The commands follow too. `niro status` and `niro build` act on the copy for the branch you are on,
50
+ not on the shared project, so "is my index up to date" and "rebuild it" mean what you expect while
51
+ you are working on a branch. Name a project explicitly if you want a different one.
52
+
53
+ `niro delete` and `niro remove --project` will not delete a private copy. They send you to
54
+ `niro discard-temp-project`, which removes the copy AND points your folder back at the shared
55
+ project. Deleting it any other way leaves your folder aimed at something that is gone.
56
+
49
57
  ## Switching branches
50
58
 
51
59
  Nothing to do. Niro checks which branch you are on for every question.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niroai/niro",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
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,11 +7,16 @@
7
7
  * - an alias or git URL -> resolved via GET /api/projects/resolve.
8
8
  * If an alias/URL maps to MULTIPLE projects, a clear ambiguity error is
9
9
  * thrown listing the candidates (we never silently pick the first).
10
- * 2. Walk up from cwd looking for .niro-project (same logic the MCP bridge uses).
10
+ * 2. The project THIS FOLDER IS CURRENTLY SYNCED TO, when it was recorded for the
11
+ * branch that is checked out. See resolveFromSyncedFolder for why this comes
12
+ * before the pin.
13
+ * 3. Walk up from cwd looking for .niro-project (same logic the MCP bridge uses).
11
14
  */
12
15
  const api = require("../lib/api");
13
16
  const prompt = require("../lib/prompt");
14
17
  const niroProjectFile = require("../lib/niroProjectFile");
18
+ const git = require("../lib/git");
19
+ const watchState = require("../lib/watchState");
15
20
  const { normalizeProjects, resolveContext } = require("../lib/resolveContext");
16
21
 
17
22
  // A canonical project_id is a UUID. Anything that doesn't look like one and
@@ -119,6 +124,36 @@ async function resolveProjectFromGit(opts = {}) {
119
124
  throw e;
120
125
  }
121
126
 
127
+ /**
128
+ * The project this folder is actually synced to right now, when the recorded branch is the
129
+ * one checked out. Null when nothing is recorded, when the branch has moved on, or on any error.
130
+ *
131
+ * <p>Why this has to come BEFORE the .niro-project pin. The pin holds the SHARED project's id and
132
+ * never changes. When a developer runs `niro new-temp-project`, the folder starts syncing to a
133
+ * private temporary project that indexes their branch, and only ~/.niro/projects.json records it.
134
+ * Reading the pin therefore answers about a project the developer is not working in. Measured on
135
+ * 2026-09-08: in a repo on branch feat/shipping-wrap-rethrow, `niro status` reported the shared
136
+ * project with "Updated 2026-09-06T09:28", two days stale, while the branch's own project had
137
+ * finished a build four minutes earlier. `niro build` had the same fault and would have rebuilt the
138
+ * shared project.
139
+ *
140
+ * <p>The branch comparison is the safety catch. An entry recorded on another branch must not
141
+ * redirect the command, so a mismatch falls through to the pin, which is the old behaviour.
142
+ */
143
+ function resolveFromSyncedFolder(opts = {}) {
144
+ try {
145
+ const ctx = git.inspect(process.cwd());
146
+ if (!ctx || !ctx.repoRoot) return null;
147
+ const entry = watchState.get(ctx.repoRoot, opts.apiUrl || api.currentBaseUrl());
148
+ if (!entry || !entry.projectId) return null;
149
+ if (entry.branch && ctx.branch && entry.branch !== ctx.branch) return null;
150
+ return entry.projectId;
151
+ } catch (_) {
152
+ // Never block a command on this: falling through to the pin is the previous behaviour.
153
+ return null;
154
+ }
155
+ }
156
+
122
157
  async function resolveProjectId(explicit, opts = {}) {
123
158
  if (explicit) {
124
159
  // A UUID is used directly — unchanged behavior. Anything else is an
@@ -126,7 +161,10 @@ async function resolveProjectId(explicit, opts = {}) {
126
161
  if (looksLikeProjectId(explicit)) return explicit;
127
162
  return resolveAliasOrUrl(explicit, opts);
128
163
  }
129
- // No explicit arg: prefer a .niro-project pin (walk up from cwd).
164
+ // No explicit arg: the project this folder is synced to on THIS branch wins over the pin.
165
+ const fromFolder = resolveFromSyncedFolder(opts);
166
+ if (fromFolder) return fromFolder;
167
+ // Then a .niro-project pin (walk up from cwd).
130
168
  const fromFile = readNiroProjectFile(process.cwd());
131
169
  if (fromFile) {
132
170
  // A pin that is already a project_id is used directly. A pin holding an
@@ -139,4 +177,22 @@ async function resolveProjectId(explicit, opts = {}) {
139
177
  return resolveProjectFromGit(opts);
140
178
  }
141
179
 
142
- module.exports = { resolveProjectId, readNiroProjectFile, resolveAliasOrUrl, looksLikeProjectId };
180
+ /**
181
+ * The temporary project this id names, or null when it is an ordinary shared project.
182
+ *
183
+ * <p>Used by the destructive commands. Deleting a temporary project through `niro delete` leaves
184
+ * the folder pinned to a project that is gone and skips the local cleanup that
185
+ * `niro discard-temp-project` does, so those commands send the developer there instead.
186
+ * Never throws: if the project cannot be fetched, the caller carries on as before.
187
+ */
188
+ async function tempProjectSourceId(projectId, opts = {}) {
189
+ try {
190
+ const project = await api.get(`/api/projects/${encodeURIComponent(projectId)}`, { apiUrl: opts.apiUrl });
191
+ return (project && (project.cloneOfProjectId || project.clone_of_project_id)) || null;
192
+ } catch (_) {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ module.exports = { resolveProjectId, readNiroProjectFile, resolveAliasOrUrl, looksLikeProjectId,
198
+ resolveFromSyncedFolder, tempProjectSourceId };
@@ -14,12 +14,27 @@ const api = require("../lib/api");
14
14
  const credentials = require("../lib/credentials");
15
15
  const prompt = require("../lib/prompt");
16
16
  const color = require("../lib/color");
17
- const { resolveProjectId, readNiroProjectFile } = require("./_resolveProject");
17
+ const { resolveProjectId, readNiroProjectFile, tempProjectSourceId } = require("./_resolveProject");
18
18
 
19
19
  async function deleteProject(projectIdArg, opts = {}) {
20
20
  credentials.requireCreds();
21
21
  const projectId = await resolveProjectId(projectIdArg, { apiUrl: opts.apiUrl });
22
22
 
23
+ // A temporary project is not deleted through here. It belongs to a folder and a branch, and
24
+ // `discard-temp-project` is what unwinds both: it removes the project AND points the folder back
25
+ // at the shared one, repairing the local pin. Deleting it server-side does neither, so the folder
26
+ // is left aimed at a project that no longer exists. Refusing costs one command; the alternative is
27
+ // a broken checkout the developer then has to work out how to repair.
28
+ const cloneOf = await tempProjectSourceId(projectId, opts);
29
+ if (cloneOf) {
30
+ console.log(`${color.yellow("⚠")} ${color.bold(projectId)} is a temporary project for your branch, not a shared one.`);
31
+ console.log(color.dim(` It is a private copy of ${cloneOf}.`));
32
+ console.log(` Use ${color.bold("niro discard-temp-project")} in this folder instead — that removes it AND`);
33
+ console.log(` points this folder back at the shared project. ${color.dim("Nothing has been deleted.")}`);
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+
23
38
  if (!opts.yes) {
24
39
  console.log(`${color.yellow("⚠")} About to delete project ${color.bold(projectId)}.`);
25
40
  console.log(color.dim(" This removes the project and its graph from the server. Registered repos stay."));
@@ -2,19 +2,28 @@
2
2
  * `niro mcp install` / `niro mcp uninstall` / `niro mcp serve` — drive the in-package MCP server.
3
3
  *
4
4
  * install(): spawn the bundled editor-config wizard as a child so it can prompt.
5
+ * Takes optional wizard arguments; see runWizard.
5
6
  * uninstall(): spawn the teardown wizard — the exact inverse of install.
6
7
  * serve(): run the in-package stdio bridge directly in this process so the
7
8
  * editor's MCP stdio talks straight to it.
8
9
  */
9
10
  const path = require("path");
10
- const { spawn } = require("child_process");
11
+ const childProcess = require("child_process");
11
12
 
12
- /** Spawn a bundled wizard (setup.js / teardown.js) as a child so it can prompt on the TTY. */
13
- function runWizard(scriptName, label) {
13
+ /**
14
+ * Spawn a bundled wizard (setup.js / teardown.js) as a child so it can prompt on the TTY.
15
+ *
16
+ * `args` overrides what is forwarded to the wizard. Callers that are themselves a `niro mcp ...`
17
+ * command leave it out and the user's own flags after `niro mcp install` are passed through.
18
+ * `niro upgrade` passes its own `--client <id>`, because the flags on its command line
19
+ * (`--finish`, `--from`) are not the wizard's.
20
+ */
21
+ function runWizard(scriptName, label, args) {
22
+ const wizardArgs = Array.isArray(args) ? args : process.argv.slice(4);
14
23
  return new Promise((resolve, reject) => {
15
- const child = spawn(
24
+ const child = childProcess.spawn(
16
25
  process.execPath,
17
- [path.join(__dirname, "..", "mcp", scriptName), ...process.argv.slice(4)],
26
+ [path.join(__dirname, "..", "mcp", scriptName), ...wizardArgs],
18
27
  { stdio: "inherit" }
19
28
  );
20
29
  child.on("error", reject);
@@ -25,8 +34,8 @@ function runWizard(scriptName, label) {
25
34
  });
26
35
  }
27
36
 
28
- async function install() {
29
- return runWizard("setup.js", "setup");
37
+ async function install(args) {
38
+ return runWizard("setup.js", "setup", args);
30
39
  }
31
40
 
32
41
  async function uninstall() {
@@ -57,14 +57,27 @@ async function remove(opts = {}) {
57
57
  // --project: destructive. Fetch the project so we can match --confirm-name
58
58
  // against either the alias or the project_id.
59
59
  let alias = null;
60
+ let cloneOf = null;
60
61
  try {
61
62
  const project = await api.get(`/api/projects/${encodeURIComponent(projectId)}`, { apiUrl: opts.apiUrl });
62
63
  alias = (project && (project.alias)) || null;
64
+ cloneOf = (project && (project.cloneOfProjectId || project.clone_of_project_id)) || null;
63
65
  } catch (err) {
64
66
  // If we can't read the project, fall back to matching the id only.
65
67
  alias = null;
66
68
  }
67
69
 
70
+ // Same rule as `niro delete`: a temporary project is unwound by discard-temp-project, which also
71
+ // points this folder back at the shared project. Removing it here would leave the folder aimed at
72
+ // a project that is gone. Read from the fetch above rather than a second request.
73
+ if (cloneOf) {
74
+ console.log(`${color.yellow("⚠")} ${color.bold(projectId)} is a temporary project for your branch, not a shared one.`);
75
+ console.log(color.dim(` It is a private copy of ${cloneOf}.`));
76
+ console.log(` Use ${color.bold("niro discard-temp-project")} in this folder instead. ${color.dim("Nothing has been removed.")}`);
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+
68
81
  const confirm = opts.confirmName == null ? null : String(opts.confirmName).trim();
69
82
  const matches = confirm != null && (confirm === projectId || (alias != null && confirm === alias));
70
83
  if (!matches) {
@@ -560,6 +560,7 @@ module.exports = {
560
560
  printNextSteps,
561
561
  BOOK_A_CALL,
562
562
  runningFromNpxCache,
563
+ globalNiroPath,
563
564
  isOlder,
564
565
  describeProblem,
565
566
  expandHome,