@mnemonik/cli 7.107.3 → 7.108.1

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/dist/router.js CHANGED
@@ -5271,7 +5271,8 @@ import {
5271
5271
  runProjectCommand
5272
5272
  } from "./project.js";
5273
5273
  import { evaluateRoot } from "./project/eligibility.js";
5274
- import { grantTransport, grantHost } from "./auth/status.js";
5274
+ import { accountActions } from "./transport/server.js";
5275
+ import { grantTransport, grantHost, grantSummaryLines } from "./auth/status.js";
5275
5276
  import { createCliAuth } from "./auth/index.js";
5276
5277
  import { runEditorLogin } from "./auth/pkce.js";
5277
5278
  import { currentInstallSession, ensureInstallSession } from "./auth/installSession.js";
@@ -5307,6 +5308,51 @@ var removedFolderLine = (name2) => ` \u2713 ${name2} is no longer connected.`;
5307
5308
  var projectDeletionWarning = (name2) => `Deleting ${name2} removes its memories, code index and summaries for everyone. This cannot be undone.`;
5308
5309
  var projectDeletedLine = (name2) => `Deleted ${name2}.`;
5309
5310
  var NOTHING_DELETED_LINE = "Nothing was deleted.";
5311
+ var NOTHING_REMOVED_LINE = "Nothing was removed.";
5312
+ var SCANNER_UNINSTALL_PROMPT = "Uninstalling background indexing means your code on this machine is no longer\nindexed. Agents here lose code search and file context until you set it up\nagain with mnemonik scanner enable. This also withdraws your indexing consent\nfor this machine. Type yes to continue.";
5313
+ var SCANNER_REMOVED_LINE = "Background indexing removed from this machine.";
5314
+ async function revokeScannerCredential(deps, output, stateDir, saved) {
5315
+ const state = saved ?? JSON.parse(await readFile3(`${stateDir}/scanner/state.json`, "utf8"));
5316
+ const bearer = await ensureCliAuth(deps, output, false);
5317
+ const response = await (deps.grantFetch ?? fetch)(
5318
+ `${apiOrigin()}/api/v1/component-credentials/${encodeURIComponent(state.config.credentialFamilyId)}/revoke`,
5319
+ { method: "POST", headers: { Authorization: `Bearer ${bearer}` } }
5320
+ );
5321
+ if (!response.ok) throw new Error(`revoke_failed_${response.status}`);
5322
+ }
5323
+ async function confirmScannerOptOut(parsed, deps, output) {
5324
+ if (parsed.flags.has("confirm")) return void 0;
5325
+ if (parsed.flags.has("json") || parsed.flags.has("non-interactive"))
5326
+ return actionRequired2(output, parsed.flags.has("json"), "Rerun with --confirm", "--confirm");
5327
+ output.line(SCANNER_UNINSTALL_PROMPT);
5328
+ const readline = createInterface({ input: deps.input ?? process.stdin, terminal: false });
5329
+ const answer = String((await readline[Symbol.asyncIterator]().next()).value ?? "").trim();
5330
+ readline.close();
5331
+ if (/^(?:y|yes)$/iu.test(answer)) return void 0;
5332
+ output.line(NOTHING_REMOVED_LINE);
5333
+ return 130;
5334
+ }
5335
+ async function uninstallScannerOptOut(deps, output, service, json) {
5336
+ const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
5337
+ const saved = await readFile3(`${stateDir}/scanner/state.json`, "utf8").then(
5338
+ (text) => JSON.parse(text),
5339
+ (error) => {
5340
+ if (error.code === "ENOENT") return void 0;
5341
+ throw error;
5342
+ }
5343
+ );
5344
+ if (saved)
5345
+ try {
5346
+ await revokeScannerCredential(deps, output, stateDir, saved);
5347
+ } catch (error) {
5348
+ output.error(humanReason(error.message));
5349
+ return 3;
5350
+ }
5351
+ await service.uninstall();
5352
+ if (json) output.json({ status: "uninstalled", consent: saved ? "withdrawn" : "none" });
5353
+ else output.line(SCANNER_REMOVED_LINE);
5354
+ return 0;
5355
+ }
5310
5356
  var dataDeletePrompt = (projectId) => `This deletes everything background indexing has sent for ${projectId} from your account. Type yes to continue.`;
5311
5357
  var stillWatchedLine = (root) => `The folder is still being indexed. Run mnemonik remove ${root} to stop that.`;
5312
5358
  var identityFileKeptLine = "This folder's .mnemonik.json still points at the deleted project. Connecting the folder again creates a new project.";
@@ -5364,6 +5410,9 @@ var values = /* @__PURE__ */ new Set([
5364
5410
  ]);
5365
5411
  var help = helpScreen([]) ?? "";
5366
5412
  function parse(args2) {
5413
+ return { ...parseArguments(args2), json: args2.includes("--json") };
5414
+ }
5415
+ function parseArguments(args2) {
5367
5416
  const positionals = [];
5368
5417
  const flags2 = /* @__PURE__ */ new Map();
5369
5418
  const namedConfirm = args2[0] === "project" && args2[1] === "delete";
@@ -5404,6 +5453,42 @@ function parse(args2) {
5404
5453
  }
5405
5454
  return { positionals, flags: flags2 };
5406
5455
  }
5456
+ function commandPath(positionals) {
5457
+ for (let length = positionals.length; length > 0; length--)
5458
+ if (helpScreen(positionals.slice(0, length))) return positionals.slice(0, length);
5459
+ return [];
5460
+ }
5461
+ function usageFailure(output, parsed, failure, human) {
5462
+ if (parsed.json) {
5463
+ const command = failure.reason === "unknown_command" ? parsed.positionals[0] ?? "" : (failure.path ?? commandPath(parsed.positionals)).join(" ");
5464
+ output.json({
5465
+ status: "usage_error",
5466
+ reason: failure.reason,
5467
+ command,
5468
+ ...failure.detail ? { detail: failure.detail } : {},
5469
+ action: failure.reason === "unknown_command" || !command ? "mnemonik --help" : `mnemonik ${command} --help`
5470
+ });
5471
+ } else if (typeof human === "string") output.error(human);
5472
+ else human();
5473
+ return 2;
5474
+ }
5475
+ function flagError(output, parsed, message) {
5476
+ const [, kind, detail] = /^(Unknown flag|Missing value for flag)(?: combination)?: (\S+)/u.exec(
5477
+ message
5478
+ ) ?? [void 0, "Unknown flag"];
5479
+ const reason = kind === "Unknown flag" ? "invalid_flag" : "invalid_value";
5480
+ return usageFailure(output, parsed, { reason, detail }, message);
5481
+ }
5482
+ function usageReason(positionals, path) {
5483
+ if (!path.length) return { reason: "unknown_command" };
5484
+ for (let index = 1; index < path.length; index++)
5485
+ if (positionals[index] !== path[index])
5486
+ return positionals[index] === void 0 ? { reason: index === 1 ? "missing_subcommand" : "missing_argument" } : { reason: "unknown_subcommand", detail: positionals[index] };
5487
+ const extra = positionals[path.length];
5488
+ if (extra !== void 0)
5489
+ return { reason: path.length === 1 ? "unknown_subcommand" : "invalid_value", detail: extra };
5490
+ return { reason: path.length === 1 ? "missing_subcommand" : "missing_argument" };
5491
+ }
5407
5492
  function allowed(parsed, names) {
5408
5493
  const permitted = /* @__PURE__ */ new Set(["json", "non-interactive", "no-browser", "help", ...names]);
5409
5494
  for (const name2 of parsed.flags.keys())
@@ -5444,9 +5529,9 @@ function auth(deps, output, noBrowser) {
5444
5529
  print: (line) => output.line(line)
5445
5530
  });
5446
5531
  }
5447
- async function ensureCliAuth(deps, output, noBrowser, showIdentity = true) {
5532
+ async function ensureCliAuth(deps, output, noBrowser, showIdentity = true, fresh = false) {
5448
5533
  const cliAuth = auth(deps, output, noBrowser);
5449
- let bearer = await cliAuth.getCliBearer().catch((error) => {
5534
+ let bearer = fresh ? { status: "ACTION_REQUIRED", reason: "renew" } : await cliAuth.getCliBearer().catch((error) => {
5450
5535
  if (isCredentialSessionUnavailableError(error))
5451
5536
  return { status: "ACTION_REQUIRED", reason: error.reason };
5452
5537
  throw error;
@@ -5507,13 +5592,27 @@ async function runHostCommand(command, parsed, deps, output, scannerSelected = f
5507
5592
  const component = parsed.flags.get("component");
5508
5593
  const fullUninstall = command === "uninstall" && !host && !component;
5509
5594
  if (host && !hostOrder.includes(host) || component && !["hooks", "mcp"].includes(String(component)))
5510
- return output.error("Invalid host or component"), 2;
5595
+ return usageFailure(
5596
+ output,
5597
+ parsed,
5598
+ {
5599
+ reason: "invalid_value",
5600
+ detail: String(host && !hostOrder.includes(host) ? host : component)
5601
+ },
5602
+ "Invalid host or component"
5603
+ );
5511
5604
  if (command === "uninstall") await abandonInterrupted(state);
5512
5605
  let selections;
5513
5606
  if (command === "install") {
5514
5607
  const names = String(parsed.flags.get("hosts") ?? hostOrder.join(",")).split(",");
5515
- if (names.some((name2) => !hostOrder.includes(name2)))
5516
- return output.error("Invalid hosts"), 2;
5608
+ const unknownHost = names.find((name2) => !hostOrder.includes(name2));
5609
+ if (unknownHost !== void 0)
5610
+ return usageFailure(
5611
+ output,
5612
+ parsed,
5613
+ { reason: "invalid_value", detail: unknownHost },
5614
+ "Invalid hosts"
5615
+ );
5517
5616
  const components = String(parsed.flags.get("components") ?? "hooks,mcp").split(",");
5518
5617
  if (components.some((c) => !["hooks", "mcp"].includes(c)))
5519
5618
  return placeholder(output, json, "install scanner", "scanner setup");
@@ -5843,7 +5942,7 @@ async function installCommand(parsed, deps, output) {
5843
5942
  "no-browser",
5844
5943
  "dry-run"
5845
5944
  ]);
5846
- if (invalid) return output.error(invalid), 2;
5945
+ if (invalid) return flagError(output, parsed, invalid);
5847
5946
  if (!deps.install && !parsed.flags.has("dry-run")) {
5848
5947
  const { joinedInstall } = await import("./install/journey.js");
5849
5948
  const state = deps.hostManagement?.stateDir ?? deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
@@ -5895,7 +5994,7 @@ function refusalStateDir(deps) {
5895
5994
  }
5896
5995
  async function doctorCommand(parsed, deps, output) {
5897
5996
  const invalid = allowed(parsed, []);
5898
- if (invalid) return output.error(invalid), 2;
5997
+ if (invalid) return flagError(output, parsed, invalid);
5899
5998
  const result = await runPreflight({ cwd: deps.cwd, home: deps.home, ...deps.preflight });
5900
5999
  output.setContext({ home: deps.home ?? homedir3(), projectRoot: result.project.root });
5901
6000
  const document = await collectStatusDocument({
@@ -6000,9 +6099,11 @@ async function deleteProjectCommand(parsed, rest, deps, output) {
6000
6099
  const send = deps.grantFetch ?? fetch;
6001
6100
  const listed = await send(`${apiOrigin()}/api/v1/users/me/projects?days=0&limit=200`, {
6002
6101
  headers: { Authorization: `Bearer ${bearer}` }
6003
- });
6004
- if (!listed.ok) {
6005
- output.error("Mnemonik could not be reached from this machine.");
6102
+ }).catch(() => void 0);
6103
+ if (!listed?.ok) {
6104
+ const reason = !listed ? "unreachable" : listed.status === 401 || listed.status === 403 ? "renew" : "server_error";
6105
+ if (json) output.json({ status: "action_required", reason, action: accountActions[reason] });
6106
+ else output.error(humanReason(reason));
6006
6107
  return 3;
6007
6108
  }
6008
6109
  const listing = await listed.json().catch(() => []);
@@ -6085,10 +6186,19 @@ async function runCli(args2, deps = {}) {
6085
6186
  const output = new Output(stdout, stderr, {
6086
6187
  home: deps.home ?? homedir3()
6087
6188
  });
6088
- const usageError = (positionals) => {
6089
- stderr.write(helpScreen(positionals) ?? help);
6090
- return 2;
6091
- };
6189
+ const usageError = (positionals, failure = usageReason(
6190
+ parsed.positionals,
6191
+ positionals
6192
+ )) => usageFailure(
6193
+ output,
6194
+ parsed,
6195
+ { path: positionals, ...failure },
6196
+ () => stderr.write(helpScreen(positionals) ?? help)
6197
+ );
6198
+ const argumentError = (screen, words, given, most) => usageError(
6199
+ screen,
6200
+ given.length > most ? { reason: "invalid_value", detail: given[most], path: words } : { reason: "missing_argument", path: words }
6201
+ );
6092
6202
  if (helpIndex !== -1) {
6093
6203
  const positionals = args2.slice(0, helpIndex).filter((argument) => !argument.startsWith("--"));
6094
6204
  const screen = helpScreen(positionals);
@@ -6099,10 +6209,10 @@ async function runCli(args2, deps = {}) {
6099
6209
  stderr.write(
6100
6210
  "WARNING: MNEMONIK_DEV_RELEASE_DIR uses development artifacts; readiness remains LIMITED.\n"
6101
6211
  );
6102
- if (parsed.error) return output.error(parsed.error), 2;
6212
+ if (parsed.error) return flagError(output, parsed, parsed.error);
6103
6213
  if (parsed.flags.has("version")) {
6104
6214
  if (parsed.positionals.length || parsed.flags.size !== 1)
6105
- return output.error("Unknown flag combination: --version"), 2;
6215
+ return flagError(output, parsed, "Unknown flag combination: --version");
6106
6216
  output.line(await packageVersion());
6107
6217
  return 0;
6108
6218
  }
@@ -6124,7 +6234,12 @@ async function runCli(args2, deps = {}) {
6124
6234
  ...action === "list" ? [] : ["accept-indexing", "apply"]
6125
6235
  ]);
6126
6236
  if (invalid || !["add", "remove", "list"].includes(action ?? "") || actionArguments.length !== (action === "list" ? 0 : 1))
6127
- return invalid ? (output.error(invalid), 2) : usageError([command]);
6237
+ return invalid ? flagError(output, parsed, invalid) : !["add", "remove", "list"].includes(action ?? "") ? usageError([command]) : argumentError(
6238
+ [command],
6239
+ command === "roots" ? [command, action ?? ""] : [command],
6240
+ actionArguments.map(String),
6241
+ action === "list" ? 0 : 1
6242
+ );
6128
6243
  const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
6129
6244
  const saved = JSON.parse(
6130
6245
  await readFile3(`${stateDir}/scanner/state.json`, "utf8").catch(() => "null")
@@ -6208,16 +6323,13 @@ async function runCli(args2, deps = {}) {
6208
6323
  if (command === "auth" && subcommand === "logout" && parsed.flags.get("component") === "scanner") {
6209
6324
  const invalid = allowed(parsed, ["component"]);
6210
6325
  if (invalid || rest.length)
6211
- return invalid ? (output.error(invalid), 2) : usageError(["auth", "logout"]);
6326
+ return invalid ? flagError(output, parsed, invalid) : usageError(["auth", "logout"]);
6212
6327
  try {
6213
- const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
6214
- const saved = JSON.parse(await readFile3(`${stateDir}/scanner/state.json`, "utf8"));
6215
- const bearer = await ensureCliAuth(deps, output, false);
6216
- const response = await (deps.grantFetch ?? fetch)(
6217
- `${apiOrigin()}/api/v1/component-credentials/${encodeURIComponent(saved.config.credentialFamilyId)}/revoke`,
6218
- { method: "POST", headers: { Authorization: `Bearer ${bearer}` } }
6328
+ await revokeScannerCredential(
6329
+ deps,
6330
+ output,
6331
+ deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home)
6219
6332
  );
6220
- if (!response.ok) throw new Error(`revoke_failed_${response.status}`);
6221
6333
  const result = {
6222
6334
  status: "revoked",
6223
6335
  verbs: ["revoke access"],
@@ -6239,7 +6351,7 @@ async function runCli(args2, deps = {}) {
6239
6351
  const invalid = allowed(parsed, ["project", "confirm"]);
6240
6352
  const project = parsed.flags.get("project");
6241
6353
  if (invalid || subcommand !== "delete" || rest.length || typeof project !== "string")
6242
- return invalid ? (output.error(invalid), 2) : usageError(["data", "delete"]);
6354
+ return invalid ? flagError(output, parsed, invalid) : usageError(["data", "delete"]);
6243
6355
  if (!parsed.flags.has("confirm")) {
6244
6356
  if (parsed.flags.has("non-interactive") || parsed.flags.has("json"))
6245
6357
  return actionRequired2(
@@ -6273,7 +6385,12 @@ async function runCli(args2, deps = {}) {
6273
6385
  }
6274
6386
  if ((command === "update" || command === "uninstall") && parsed.flags.get("component") === "scanner") {
6275
6387
  const invalid = allowed(parsed, ["component", ...command === "uninstall" ? ["confirm"] : []]);
6276
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError([command]);
6388
+ if (invalid || subcommand)
6389
+ return invalid ? flagError(output, parsed, invalid) : usageError([command]);
6390
+ if (command === "uninstall") {
6391
+ const declined = await confirmScannerOptOut(parsed, deps, output);
6392
+ if (declined !== void 0) return declined;
6393
+ }
6277
6394
  try {
6278
6395
  const options = {
6279
6396
  stateDir: deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home),
@@ -6304,19 +6421,13 @@ async function runCli(args2, deps = {}) {
6304
6421
  output.line(
6305
6422
  result.status === "updated" ? "Mnemonik updated." : "Mnemonik is up to date."
6306
6423
  );
6307
- } else {
6308
- await scannerService(options).uninstall();
6309
- const result = {
6310
- status: "uninstalled",
6311
- verbs: ["stop collection", "remove local software"],
6312
- retained: ["credentials", "cloud data", "consent"]
6313
- };
6314
- if (parsed.flags.has("json")) output.json(result);
6315
- else
6316
- output.line(
6317
- "Stopped collection; removed local software. Credentials, cloud data and consent retained."
6318
- );
6319
- }
6424
+ } else
6425
+ return await uninstallScannerOptOut(
6426
+ deps,
6427
+ output,
6428
+ scannerService(options),
6429
+ parsed.flags.has("json")
6430
+ );
6320
6431
  return 0;
6321
6432
  } catch (error) {
6322
6433
  const failure = scannerFailure(error);
@@ -6350,7 +6461,8 @@ async function runCli(args2, deps = {}) {
6350
6461
  parsed,
6351
6462
  command === "repair" ? ["host", "component", "apply"] : command === "update" ? ["host", "automatic"] : ["host", "component", "confirm"]
6352
6463
  );
6353
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError([command]);
6464
+ if (invalid || subcommand)
6465
+ return invalid ? flagError(output, parsed, invalid) : usageError([command]);
6354
6466
  if (command === "uninstall" && parsed.flags.has("non-interactive")) {
6355
6467
  const missing = requireConsent(parsed, output, ["confirm"]);
6356
6468
  if (missing !== void 0) return missing;
@@ -6360,7 +6472,8 @@ async function runCli(args2, deps = {}) {
6360
6472
  }
6361
6473
  if (command === "update") {
6362
6474
  const invalid = allowed(parsed, ["automatic"]);
6363
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError(["update"]);
6475
+ if (invalid || subcommand)
6476
+ return invalid ? flagError(output, parsed, invalid) : usageError(["update"]);
6364
6477
  const runtimeUpdate = deps.runtimeUpdate;
6365
6478
  if (!runtimeUpdate)
6366
6479
  return placeholder(
@@ -6393,7 +6506,12 @@ async function runCli(args2, deps = {}) {
6393
6506
  const invalid = allowed(parsed, subcommand === "preview" ? ["out"] : []);
6394
6507
  const bundleId = rest[0];
6395
6508
  if (invalid || !["preview", "send"].includes(subcommand ?? "") || subcommand === "preview" && rest.length || subcommand === "send" && (rest.length !== 1 || !bundleId))
6396
- return invalid ? (output.error(invalid), 2) : usageError(["diagnostics"]);
6509
+ return invalid ? flagError(output, parsed, invalid) : subcommand === "preview" || subcommand === "send" ? argumentError(
6510
+ ["diagnostics"],
6511
+ ["diagnostics", subcommand],
6512
+ rest,
6513
+ subcommand === "send" ? 1 : 0
6514
+ ) : usageError(["diagnostics"]);
6397
6515
  try {
6398
6516
  const result = subcommand === "preview" ? await previewDiagnostics(
6399
6517
  parsed.flags.get("out"),
@@ -6434,7 +6552,8 @@ Preview: ${preview.path}
6434
6552
  }
6435
6553
  if (command === "status") {
6436
6554
  const invalid = allowed(parsed, []);
6437
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError(["status"]);
6555
+ if (invalid || subcommand)
6556
+ return invalid ? flagError(output, parsed, invalid) : usageError(["status"]);
6438
6557
  const document = await collectCurrentInstallation(deps, output);
6439
6558
  const version = await packageVersion();
6440
6559
  const store = new RuntimeStore(
@@ -6453,9 +6572,9 @@ Preview: ${preview.path}
6453
6572
  }
6454
6573
  if (command === "connect") {
6455
6574
  const invalid = allowed(parsed, []);
6456
- if (invalid) return output.error(invalid), 2;
6575
+ if (invalid) return flagError(output, parsed, invalid);
6457
6576
  if (!subcommand || rest.length || !hostOrder.includes(subcommand))
6458
- return usageError(["connect"]);
6577
+ return subcommand && !hostOrder.includes(subcommand) ? usageError(["connect"], { reason: "invalid_value", detail: subcommand }) : argumentError(["connect"], ["connect"], [subcommand ?? "", ...rest].filter(Boolean), 1);
6459
6578
  const host = subcommand;
6460
6579
  const editor = (await localEditorStatus(deps.home ?? homedir3())).find(
6461
6580
  (candidate) => candidate.host === host
@@ -6493,21 +6612,28 @@ Preview: ${preview.path}
6493
6612
  if (subcommand === "delete") {
6494
6613
  const unknown = allowed(parsed, ["confirm"]);
6495
6614
  if (unknown || rest.length > 1)
6496
- return unknown ? (output.error(unknown), 2) : usageError(["project", "delete"]);
6615
+ return unknown ? flagError(output, parsed, unknown) : argumentError(["project", "delete"], ["project", "delete"], rest, 1);
6497
6616
  return deleteProjectCommand(parsed, rest, deps, output);
6498
6617
  }
6499
6618
  const invalid = allowed(
6500
6619
  parsed,
6501
6620
  subcommand === "ensure" ? ["agent"] : subcommand === "status" ? [] : subcommand === "link" ? ["apply", "non-git", "confirm-mismatch", "replace"] : ["apply", "non-git", "owner"]
6502
6621
  );
6503
- if (invalid) return output.error(invalid), 2;
6622
+ if (invalid) return flagError(output, parsed, invalid);
6504
6623
  if (subcommand === "ensure") {
6505
6624
  if (rest.length) return usageError(["project", "ensure"]);
6506
6625
  return ensureProjectForAgent({
6507
6626
  output,
6508
6627
  cwd: deps.cwd ?? process.cwd(),
6509
6628
  executor: deps.projectExecutor,
6510
- input: deps.input ?? process.stdin
6629
+ input: deps.input ?? process.stdin,
6630
+ handoff: parsed.flags.has("agent"),
6631
+ // The same sign-in reader status and every other command use.
6632
+ runtime: {
6633
+ stateDir: deps.projectStateDir ?? deps.installStateDir,
6634
+ fetch: deps.grantFetch,
6635
+ getCliBearer: auth(deps, output, true).getCliBearer
6636
+ }
6511
6637
  });
6512
6638
  }
6513
6639
  if (subcommand !== "status" && subcommand !== "setup" && parsed.flags.has("non-interactive")) {
@@ -6515,7 +6641,12 @@ Preview: ${preview.path}
6515
6641
  if (missing !== void 0) return missing;
6516
6642
  }
6517
6643
  if (subcommand === "link" && (rest.length < 1 || rest.length > 2) || subcommand !== "link" && rest.length > 1)
6518
- return usageError(["project", subcommand]);
6644
+ return argumentError(
6645
+ ["project", subcommand],
6646
+ ["project", subcommand],
6647
+ rest,
6648
+ subcommand === "link" ? 2 : 1
6649
+ );
6519
6650
  return runProjectCommand(
6520
6651
  {
6521
6652
  command: subcommand,
@@ -6543,17 +6674,25 @@ Preview: ${preview.path}
6543
6674
  if (command === "identity") {
6544
6675
  if (subcommand !== "migrate") return usageError(["identity", "migrate"]);
6545
6676
  const invalid = allowed(parsed, ["report", "backup", "apply", "verify", "rollback"]);
6546
- if (invalid) return output.error(invalid), 2;
6677
+ if (invalid) return flagError(output, parsed, invalid);
6547
6678
  const selected = ["report", "backup", "apply", "verify", "rollback"].filter(
6548
6679
  (flag) => parsed.flags.has(flag)
6549
6680
  );
6550
6681
  if (selected.length > 1)
6551
- return output.error(
6682
+ return usageFailure(
6683
+ output,
6684
+ parsed,
6685
+ { reason: "invalid_flag", detail: `--${selected[1]}` },
6552
6686
  "Choose one migration phase: --report, --backup, --apply, --verify, or --rollback"
6553
- ), 2;
6687
+ );
6554
6688
  const mode = selected[0] ?? "report";
6555
6689
  if (rest.length && mode !== "report" && mode !== "backup")
6556
- return output.error("Paths are only accepted by --report and --backup"), 2;
6690
+ return usageFailure(
6691
+ output,
6692
+ parsed,
6693
+ { reason: "invalid_value", detail: rest[0] },
6694
+ "Paths are only accepted by --report and --backup"
6695
+ );
6557
6696
  let result;
6558
6697
  try {
6559
6698
  result = await runIdentityMigration({
@@ -6597,10 +6736,14 @@ Preview: ${preview.path}
6597
6736
  );
6598
6737
  const invalid = allowed(
6599
6738
  parsed,
6600
- subcommand === "enable" ? ["accept-indexing", "apply", "scan-roots", "exclusions", "no-browser"] : subcommand === "export-preview" ? ["out"] : []
6739
+ subcommand === "enable" ? ["accept-indexing", "apply", "scan-roots", "exclusions", "no-browser"] : subcommand === "export-preview" ? ["out"] : subcommand === "uninstall" ? ["confirm"] : []
6601
6740
  );
6602
- if (invalid) return output.error(invalid), 2;
6741
+ if (invalid) return flagError(output, parsed, invalid);
6603
6742
  if (subcommand === "enable") return enableCommand(parsed, deps, output);
6743
+ if (subcommand === "uninstall") {
6744
+ const declined = await confirmScannerOptOut(parsed, deps, output);
6745
+ if (declined !== void 0) return declined;
6746
+ }
6604
6747
  if (subcommand === "status" && deps.scannerStatus) {
6605
6748
  const status = await deps.scannerStatus();
6606
6749
  const omitted = status.repositories.filter((repository) => !repository.selected).map((repository) => ({
@@ -6691,10 +6834,12 @@ Preview: ${preview.path}
6691
6834
  }
6692
6835
  await service.start();
6693
6836
  } else if (subcommand === "stop") {
6837
+ await controlScanner("stop", options).catch(() => void 0);
6694
6838
  await service.stop();
6695
6839
  if ((deps.scannerService?.platform ?? process.platform) === "darwin")
6696
6840
  output.line("Background indexing will start again when this Mac restarts.");
6697
- } else if (subcommand === "uninstall") await service.uninstall();
6841
+ } else if (subcommand === "uninstall")
6842
+ return await uninstallScannerOptOut(deps, output, service, json);
6698
6843
  const includesStatus = ["status", "pause", "resume"].includes(subcommand);
6699
6844
  const supervisor = includesStatus ? await service.status() : void 0;
6700
6845
  const receipt = includesStatus ? await scannerReceipt(options.stateDir) : void 0;
@@ -6737,16 +6882,29 @@ Preview: ${preview.path}
6737
6882
  "reopen-install",
6738
6883
  ...subcommand === "logout" ? ["confirm"] : []
6739
6884
  ]);
6740
- if (invalid || rest.length || !["login", "status", "logout"].includes(subcommand ?? "") || parsed.flags.has("reopen-install") && subcommand !== "login")
6741
- return invalid ? (output.error(invalid), 2) : usageError(
6742
- subcommand && helpScreen(["auth", subcommand]) ? ["auth", subcommand] : ["auth"]
6885
+ if (invalid || rest.length || !["login", "renew", "status", "logout"].includes(subcommand ?? "") || parsed.flags.has("reopen-install") && subcommand !== "login" && subcommand !== "renew")
6886
+ return invalid ? flagError(output, parsed, invalid) : usageError(
6887
+ subcommand && helpScreen(["auth", subcommand]) ? ["auth", subcommand] : ["auth"],
6888
+ !rest.length && ["login", "renew", "status", "logout"].includes(subcommand ?? "") ? { reason: "invalid_flag", detail: "--reopen-install" } : void 0
6743
6889
  );
6744
6890
  const host = parsed.flags.get("host");
6745
- if (host && !hostOrder.includes(host)) return output.error("Invalid host"), 2;
6746
- if (subcommand === "login") {
6747
- if (host) return output.error("Use mnemonik connect <host> for a host login"), 2;
6891
+ if (host && !hostOrder.includes(host))
6892
+ return usageFailure(
6893
+ output,
6894
+ parsed,
6895
+ { reason: "invalid_value", detail: String(host) },
6896
+ "Invalid host"
6897
+ );
6898
+ if (subcommand === "login" || subcommand === "renew") {
6899
+ if (host)
6900
+ return usageFailure(
6901
+ output,
6902
+ parsed,
6903
+ { reason: "invalid_flag", detail: "--host" },
6904
+ "Use mnemonik connect <host> for a host login"
6905
+ );
6748
6906
  const noBrowser = parsed.flags.has("no-browser");
6749
- const bearer = await ensureCliAuth(deps, output, noBrowser);
6907
+ const bearer = await ensureCliAuth(deps, output, noBrowser, true, subcommand === "renew");
6750
6908
  if (parsed.flags.has("reopen-install")) {
6751
6909
  const current = await currentInstallSession(bearer, deps.grantFetch);
6752
6910
  if (current) {
@@ -6806,11 +6964,17 @@ Preview: ${preview.path}
6806
6964
  return (!grantHostName || hostOrder.includes(grantHostName)) && (!host || grantHostName === host);
6807
6965
  }).map((g) => ({ ...g, host: grantHost(g) ?? g.clientName ?? g.clientId }));
6808
6966
  if (parsed.flags.has("json")) output.json({ account: status.account, grants });
6809
- else
6810
- for (const g of grants)
6811
- output.line(
6812
- `${g.host}: ${g.id}; created ${g.createdAt}; last used ${g.lastUsedAt ?? "never"}; scopes ${g.scopes.join(", ")}`
6813
- );
6967
+ else if (!grants.length)
6968
+ output.line(
6969
+ host ? `${launchHostLabels[host]} is not signed in.` : humanReason("not_signed_in")
6970
+ );
6971
+ else {
6972
+ output.signedInAs(status.email);
6973
+ for (const line of grantSummaryLines(grants, launchHostLabels)) output.line(line);
6974
+ output.line(
6975
+ "Older sign-ins stay valid until `mnemonik auth logout`. `mnemonik auth status --json` lists every one."
6976
+ );
6977
+ }
6814
6978
  return 0;
6815
6979
  }
6816
6980
  const confirmed = parsed.flags.has("confirm") || await managed.offerRevoke?.(host) || !parsed.flags.has("json") && !parsed.flags.has("non-interactive") && (await chooseHostProfile(deps.input ?? process.stdin, output, [
@@ -6836,7 +7000,7 @@ Preview: ${preview.path}
6836
7000
  if (command === "logout") {
6837
7001
  if (subcommand) return usageError(["logout"]);
6838
7002
  const invalid = allowed(parsed, []);
6839
- if (invalid) return output.error(invalid), 2;
7003
+ if (invalid) return flagError(output, parsed, invalid);
6840
7004
  await auth(deps, output, false).logout();
6841
7005
  if (parsed.flags.has("json")) output.json({ status: "logged_out" });
6842
7006
  else output.line("Logged out.");
@@ -6849,6 +7013,9 @@ export {
6849
7013
  CODEX_SIGNED_IN_MESSAGE,
6850
7014
  CONNECT_NOT_APPROVED_MESSAGE,
6851
7015
  NOTHING_DELETED_LINE,
7016
+ NOTHING_REMOVED_LINE,
7017
+ SCANNER_REMOVED_LINE,
7018
+ SCANNER_UNINSTALL_PROMPT,
6852
7019
  alreadyConnectedFolderLine,
6853
7020
  connectFolderPrompt,
6854
7021
  connectedFolderLine,