@mnemonik/cli 7.107.3 → 7.108.2

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({
@@ -5926,6 +6025,7 @@ async function doctorCommand(parsed, deps, output) {
5926
6025
  renderStatusSummaries(document, output, { diagnostics: true });
5927
6026
  await renderRefusals(refusalStateDir(deps), output, true);
5928
6027
  }
6028
+ await reportCurrentInstallation(deps, output);
5929
6029
  return document.installation.state === "READY" ? 0 : document.installation.state === "FAILED" ? 1 : 3;
5930
6030
  }
5931
6031
  async function collectCurrentInstallation(deps, output) {
@@ -6000,9 +6100,11 @@ async function deleteProjectCommand(parsed, rest, deps, output) {
6000
6100
  const send = deps.grantFetch ?? fetch;
6001
6101
  const listed = await send(`${apiOrigin()}/api/v1/users/me/projects?days=0&limit=200`, {
6002
6102
  headers: { Authorization: `Bearer ${bearer}` }
6003
- });
6004
- if (!listed.ok) {
6005
- output.error("Mnemonik could not be reached from this machine.");
6103
+ }).catch(() => void 0);
6104
+ if (!listed?.ok) {
6105
+ const reason = !listed ? "unreachable" : listed.status === 401 || listed.status === 403 ? "renew" : "server_error";
6106
+ if (json) output.json({ status: "action_required", reason, action: accountActions[reason] });
6107
+ else output.error(humanReason(reason));
6006
6108
  return 3;
6007
6109
  }
6008
6110
  const listing = await listed.json().catch(() => []);
@@ -6085,10 +6187,19 @@ async function runCli(args2, deps = {}) {
6085
6187
  const output = new Output(stdout, stderr, {
6086
6188
  home: deps.home ?? homedir3()
6087
6189
  });
6088
- const usageError = (positionals) => {
6089
- stderr.write(helpScreen(positionals) ?? help);
6090
- return 2;
6091
- };
6190
+ const usageError = (positionals, failure = usageReason(
6191
+ parsed.positionals,
6192
+ positionals
6193
+ )) => usageFailure(
6194
+ output,
6195
+ parsed,
6196
+ { path: positionals, ...failure },
6197
+ () => stderr.write(helpScreen(positionals) ?? help)
6198
+ );
6199
+ const argumentError = (screen, words, given, most) => usageError(
6200
+ screen,
6201
+ given.length > most ? { reason: "invalid_value", detail: given[most], path: words } : { reason: "missing_argument", path: words }
6202
+ );
6092
6203
  if (helpIndex !== -1) {
6093
6204
  const positionals = args2.slice(0, helpIndex).filter((argument) => !argument.startsWith("--"));
6094
6205
  const screen = helpScreen(positionals);
@@ -6099,10 +6210,10 @@ async function runCli(args2, deps = {}) {
6099
6210
  stderr.write(
6100
6211
  "WARNING: MNEMONIK_DEV_RELEASE_DIR uses development artifacts; readiness remains LIMITED.\n"
6101
6212
  );
6102
- if (parsed.error) return output.error(parsed.error), 2;
6213
+ if (parsed.error) return flagError(output, parsed, parsed.error);
6103
6214
  if (parsed.flags.has("version")) {
6104
6215
  if (parsed.positionals.length || parsed.flags.size !== 1)
6105
- return output.error("Unknown flag combination: --version"), 2;
6216
+ return flagError(output, parsed, "Unknown flag combination: --version");
6106
6217
  output.line(await packageVersion());
6107
6218
  return 0;
6108
6219
  }
@@ -6124,7 +6235,12 @@ async function runCli(args2, deps = {}) {
6124
6235
  ...action === "list" ? [] : ["accept-indexing", "apply"]
6125
6236
  ]);
6126
6237
  if (invalid || !["add", "remove", "list"].includes(action ?? "") || actionArguments.length !== (action === "list" ? 0 : 1))
6127
- return invalid ? (output.error(invalid), 2) : usageError([command]);
6238
+ return invalid ? flagError(output, parsed, invalid) : !["add", "remove", "list"].includes(action ?? "") ? usageError([command]) : argumentError(
6239
+ [command],
6240
+ command === "roots" ? [command, action ?? ""] : [command],
6241
+ actionArguments.map(String),
6242
+ action === "list" ? 0 : 1
6243
+ );
6128
6244
  const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
6129
6245
  const saved = JSON.parse(
6130
6246
  await readFile3(`${stateDir}/scanner/state.json`, "utf8").catch(() => "null")
@@ -6208,16 +6324,13 @@ async function runCli(args2, deps = {}) {
6208
6324
  if (command === "auth" && subcommand === "logout" && parsed.flags.get("component") === "scanner") {
6209
6325
  const invalid = allowed(parsed, ["component"]);
6210
6326
  if (invalid || rest.length)
6211
- return invalid ? (output.error(invalid), 2) : usageError(["auth", "logout"]);
6327
+ return invalid ? flagError(output, parsed, invalid) : usageError(["auth", "logout"]);
6212
6328
  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}` } }
6329
+ await revokeScannerCredential(
6330
+ deps,
6331
+ output,
6332
+ deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home)
6219
6333
  );
6220
- if (!response.ok) throw new Error(`revoke_failed_${response.status}`);
6221
6334
  const result = {
6222
6335
  status: "revoked",
6223
6336
  verbs: ["revoke access"],
@@ -6239,7 +6352,7 @@ async function runCli(args2, deps = {}) {
6239
6352
  const invalid = allowed(parsed, ["project", "confirm"]);
6240
6353
  const project = parsed.flags.get("project");
6241
6354
  if (invalid || subcommand !== "delete" || rest.length || typeof project !== "string")
6242
- return invalid ? (output.error(invalid), 2) : usageError(["data", "delete"]);
6355
+ return invalid ? flagError(output, parsed, invalid) : usageError(["data", "delete"]);
6243
6356
  if (!parsed.flags.has("confirm")) {
6244
6357
  if (parsed.flags.has("non-interactive") || parsed.flags.has("json"))
6245
6358
  return actionRequired2(
@@ -6273,7 +6386,12 @@ async function runCli(args2, deps = {}) {
6273
6386
  }
6274
6387
  if ((command === "update" || command === "uninstall") && parsed.flags.get("component") === "scanner") {
6275
6388
  const invalid = allowed(parsed, ["component", ...command === "uninstall" ? ["confirm"] : []]);
6276
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError([command]);
6389
+ if (invalid || subcommand)
6390
+ return invalid ? flagError(output, parsed, invalid) : usageError([command]);
6391
+ if (command === "uninstall") {
6392
+ const declined = await confirmScannerOptOut(parsed, deps, output);
6393
+ if (declined !== void 0) return declined;
6394
+ }
6277
6395
  try {
6278
6396
  const options = {
6279
6397
  stateDir: deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home),
@@ -6304,19 +6422,13 @@ async function runCli(args2, deps = {}) {
6304
6422
  output.line(
6305
6423
  result.status === "updated" ? "Mnemonik updated." : "Mnemonik is up to date."
6306
6424
  );
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
- }
6425
+ } else
6426
+ return await uninstallScannerOptOut(
6427
+ deps,
6428
+ output,
6429
+ scannerService(options),
6430
+ parsed.flags.has("json")
6431
+ );
6320
6432
  return 0;
6321
6433
  } catch (error) {
6322
6434
  const failure = scannerFailure(error);
@@ -6350,7 +6462,8 @@ async function runCli(args2, deps = {}) {
6350
6462
  parsed,
6351
6463
  command === "repair" ? ["host", "component", "apply"] : command === "update" ? ["host", "automatic"] : ["host", "component", "confirm"]
6352
6464
  );
6353
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError([command]);
6465
+ if (invalid || subcommand)
6466
+ return invalid ? flagError(output, parsed, invalid) : usageError([command]);
6354
6467
  if (command === "uninstall" && parsed.flags.has("non-interactive")) {
6355
6468
  const missing = requireConsent(parsed, output, ["confirm"]);
6356
6469
  if (missing !== void 0) return missing;
@@ -6360,7 +6473,8 @@ async function runCli(args2, deps = {}) {
6360
6473
  }
6361
6474
  if (command === "update") {
6362
6475
  const invalid = allowed(parsed, ["automatic"]);
6363
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError(["update"]);
6476
+ if (invalid || subcommand)
6477
+ return invalid ? flagError(output, parsed, invalid) : usageError(["update"]);
6364
6478
  const runtimeUpdate = deps.runtimeUpdate;
6365
6479
  if (!runtimeUpdate)
6366
6480
  return placeholder(
@@ -6393,7 +6507,12 @@ async function runCli(args2, deps = {}) {
6393
6507
  const invalid = allowed(parsed, subcommand === "preview" ? ["out"] : []);
6394
6508
  const bundleId = rest[0];
6395
6509
  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"]);
6510
+ return invalid ? flagError(output, parsed, invalid) : subcommand === "preview" || subcommand === "send" ? argumentError(
6511
+ ["diagnostics"],
6512
+ ["diagnostics", subcommand],
6513
+ rest,
6514
+ subcommand === "send" ? 1 : 0
6515
+ ) : usageError(["diagnostics"]);
6397
6516
  try {
6398
6517
  const result = subcommand === "preview" ? await previewDiagnostics(
6399
6518
  parsed.flags.get("out"),
@@ -6434,7 +6553,8 @@ Preview: ${preview.path}
6434
6553
  }
6435
6554
  if (command === "status") {
6436
6555
  const invalid = allowed(parsed, []);
6437
- if (invalid || subcommand) return invalid ? (output.error(invalid), 2) : usageError(["status"]);
6556
+ if (invalid || subcommand)
6557
+ return invalid ? flagError(output, parsed, invalid) : usageError(["status"]);
6438
6558
  const document = await collectCurrentInstallation(deps, output);
6439
6559
  const version = await packageVersion();
6440
6560
  const store = new RuntimeStore(
@@ -6453,9 +6573,9 @@ Preview: ${preview.path}
6453
6573
  }
6454
6574
  if (command === "connect") {
6455
6575
  const invalid = allowed(parsed, []);
6456
- if (invalid) return output.error(invalid), 2;
6576
+ if (invalid) return flagError(output, parsed, invalid);
6457
6577
  if (!subcommand || rest.length || !hostOrder.includes(subcommand))
6458
- return usageError(["connect"]);
6578
+ return subcommand && !hostOrder.includes(subcommand) ? usageError(["connect"], { reason: "invalid_value", detail: subcommand }) : argumentError(["connect"], ["connect"], [subcommand ?? "", ...rest].filter(Boolean), 1);
6459
6579
  const host = subcommand;
6460
6580
  const editor = (await localEditorStatus(deps.home ?? homedir3())).find(
6461
6581
  (candidate) => candidate.host === host
@@ -6493,21 +6613,28 @@ Preview: ${preview.path}
6493
6613
  if (subcommand === "delete") {
6494
6614
  const unknown = allowed(parsed, ["confirm"]);
6495
6615
  if (unknown || rest.length > 1)
6496
- return unknown ? (output.error(unknown), 2) : usageError(["project", "delete"]);
6616
+ return unknown ? flagError(output, parsed, unknown) : argumentError(["project", "delete"], ["project", "delete"], rest, 1);
6497
6617
  return deleteProjectCommand(parsed, rest, deps, output);
6498
6618
  }
6499
6619
  const invalid = allowed(
6500
6620
  parsed,
6501
6621
  subcommand === "ensure" ? ["agent"] : subcommand === "status" ? [] : subcommand === "link" ? ["apply", "non-git", "confirm-mismatch", "replace"] : ["apply", "non-git", "owner"]
6502
6622
  );
6503
- if (invalid) return output.error(invalid), 2;
6623
+ if (invalid) return flagError(output, parsed, invalid);
6504
6624
  if (subcommand === "ensure") {
6505
6625
  if (rest.length) return usageError(["project", "ensure"]);
6506
6626
  return ensureProjectForAgent({
6507
6627
  output,
6508
6628
  cwd: deps.cwd ?? process.cwd(),
6509
6629
  executor: deps.projectExecutor,
6510
- input: deps.input ?? process.stdin
6630
+ input: deps.input ?? process.stdin,
6631
+ handoff: parsed.flags.has("agent"),
6632
+ // The same sign-in reader status and every other command use.
6633
+ runtime: {
6634
+ stateDir: deps.projectStateDir ?? deps.installStateDir,
6635
+ fetch: deps.grantFetch,
6636
+ getCliBearer: auth(deps, output, true).getCliBearer
6637
+ }
6511
6638
  });
6512
6639
  }
6513
6640
  if (subcommand !== "status" && subcommand !== "setup" && parsed.flags.has("non-interactive")) {
@@ -6515,7 +6642,12 @@ Preview: ${preview.path}
6515
6642
  if (missing !== void 0) return missing;
6516
6643
  }
6517
6644
  if (subcommand === "link" && (rest.length < 1 || rest.length > 2) || subcommand !== "link" && rest.length > 1)
6518
- return usageError(["project", subcommand]);
6645
+ return argumentError(
6646
+ ["project", subcommand],
6647
+ ["project", subcommand],
6648
+ rest,
6649
+ subcommand === "link" ? 2 : 1
6650
+ );
6519
6651
  return runProjectCommand(
6520
6652
  {
6521
6653
  command: subcommand,
@@ -6543,17 +6675,25 @@ Preview: ${preview.path}
6543
6675
  if (command === "identity") {
6544
6676
  if (subcommand !== "migrate") return usageError(["identity", "migrate"]);
6545
6677
  const invalid = allowed(parsed, ["report", "backup", "apply", "verify", "rollback"]);
6546
- if (invalid) return output.error(invalid), 2;
6678
+ if (invalid) return flagError(output, parsed, invalid);
6547
6679
  const selected = ["report", "backup", "apply", "verify", "rollback"].filter(
6548
6680
  (flag) => parsed.flags.has(flag)
6549
6681
  );
6550
6682
  if (selected.length > 1)
6551
- return output.error(
6683
+ return usageFailure(
6684
+ output,
6685
+ parsed,
6686
+ { reason: "invalid_flag", detail: `--${selected[1]}` },
6552
6687
  "Choose one migration phase: --report, --backup, --apply, --verify, or --rollback"
6553
- ), 2;
6688
+ );
6554
6689
  const mode = selected[0] ?? "report";
6555
6690
  if (rest.length && mode !== "report" && mode !== "backup")
6556
- return output.error("Paths are only accepted by --report and --backup"), 2;
6691
+ return usageFailure(
6692
+ output,
6693
+ parsed,
6694
+ { reason: "invalid_value", detail: rest[0] },
6695
+ "Paths are only accepted by --report and --backup"
6696
+ );
6557
6697
  let result;
6558
6698
  try {
6559
6699
  result = await runIdentityMigration({
@@ -6597,10 +6737,14 @@ Preview: ${preview.path}
6597
6737
  );
6598
6738
  const invalid = allowed(
6599
6739
  parsed,
6600
- subcommand === "enable" ? ["accept-indexing", "apply", "scan-roots", "exclusions", "no-browser"] : subcommand === "export-preview" ? ["out"] : []
6740
+ subcommand === "enable" ? ["accept-indexing", "apply", "scan-roots", "exclusions", "no-browser"] : subcommand === "export-preview" ? ["out"] : subcommand === "uninstall" ? ["confirm"] : []
6601
6741
  );
6602
- if (invalid) return output.error(invalid), 2;
6742
+ if (invalid) return flagError(output, parsed, invalid);
6603
6743
  if (subcommand === "enable") return enableCommand(parsed, deps, output);
6744
+ if (subcommand === "uninstall") {
6745
+ const declined = await confirmScannerOptOut(parsed, deps, output);
6746
+ if (declined !== void 0) return declined;
6747
+ }
6604
6748
  if (subcommand === "status" && deps.scannerStatus) {
6605
6749
  const status = await deps.scannerStatus();
6606
6750
  const omitted = status.repositories.filter((repository) => !repository.selected).map((repository) => ({
@@ -6691,10 +6835,12 @@ Preview: ${preview.path}
6691
6835
  }
6692
6836
  await service.start();
6693
6837
  } else if (subcommand === "stop") {
6838
+ await controlScanner("stop", options).catch(() => void 0);
6694
6839
  await service.stop();
6695
6840
  if ((deps.scannerService?.platform ?? process.platform) === "darwin")
6696
6841
  output.line("Background indexing will start again when this Mac restarts.");
6697
- } else if (subcommand === "uninstall") await service.uninstall();
6842
+ } else if (subcommand === "uninstall")
6843
+ return await uninstallScannerOptOut(deps, output, service, json);
6698
6844
  const includesStatus = ["status", "pause", "resume"].includes(subcommand);
6699
6845
  const supervisor = includesStatus ? await service.status() : void 0;
6700
6846
  const receipt = includesStatus ? await scannerReceipt(options.stateDir) : void 0;
@@ -6737,16 +6883,29 @@ Preview: ${preview.path}
6737
6883
  "reopen-install",
6738
6884
  ...subcommand === "logout" ? ["confirm"] : []
6739
6885
  ]);
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"]
6886
+ if (invalid || rest.length || !["login", "renew", "status", "logout"].includes(subcommand ?? "") || parsed.flags.has("reopen-install") && subcommand !== "login" && subcommand !== "renew")
6887
+ return invalid ? flagError(output, parsed, invalid) : usageError(
6888
+ subcommand && helpScreen(["auth", subcommand]) ? ["auth", subcommand] : ["auth"],
6889
+ !rest.length && ["login", "renew", "status", "logout"].includes(subcommand ?? "") ? { reason: "invalid_flag", detail: "--reopen-install" } : void 0
6743
6890
  );
6744
6891
  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;
6892
+ if (host && !hostOrder.includes(host))
6893
+ return usageFailure(
6894
+ output,
6895
+ parsed,
6896
+ { reason: "invalid_value", detail: String(host) },
6897
+ "Invalid host"
6898
+ );
6899
+ if (subcommand === "login" || subcommand === "renew") {
6900
+ if (host)
6901
+ return usageFailure(
6902
+ output,
6903
+ parsed,
6904
+ { reason: "invalid_flag", detail: "--host" },
6905
+ "Use mnemonik connect <host> for a host login"
6906
+ );
6748
6907
  const noBrowser = parsed.flags.has("no-browser");
6749
- const bearer = await ensureCliAuth(deps, output, noBrowser);
6908
+ const bearer = await ensureCliAuth(deps, output, noBrowser, true, subcommand === "renew");
6750
6909
  if (parsed.flags.has("reopen-install")) {
6751
6910
  const current = await currentInstallSession(bearer, deps.grantFetch);
6752
6911
  if (current) {
@@ -6806,11 +6965,17 @@ Preview: ${preview.path}
6806
6965
  return (!grantHostName || hostOrder.includes(grantHostName)) && (!host || grantHostName === host);
6807
6966
  }).map((g) => ({ ...g, host: grantHost(g) ?? g.clientName ?? g.clientId }));
6808
6967
  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
- );
6968
+ else if (!grants.length)
6969
+ output.line(
6970
+ host ? `${launchHostLabels[host]} is not signed in.` : humanReason("not_signed_in")
6971
+ );
6972
+ else {
6973
+ output.signedInAs(status.email);
6974
+ for (const line of grantSummaryLines(grants, launchHostLabels)) output.line(line);
6975
+ output.line(
6976
+ "Older sign-ins stay valid until `mnemonik auth logout`. `mnemonik auth status --json` lists every one."
6977
+ );
6978
+ }
6814
6979
  return 0;
6815
6980
  }
6816
6981
  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 +7001,7 @@ Preview: ${preview.path}
6836
7001
  if (command === "logout") {
6837
7002
  if (subcommand) return usageError(["logout"]);
6838
7003
  const invalid = allowed(parsed, []);
6839
- if (invalid) return output.error(invalid), 2;
7004
+ if (invalid) return flagError(output, parsed, invalid);
6840
7005
  await auth(deps, output, false).logout();
6841
7006
  if (parsed.flags.has("json")) output.json({ status: "logged_out" });
6842
7007
  else output.line("Logged out.");
@@ -6849,6 +7014,9 @@ export {
6849
7014
  CODEX_SIGNED_IN_MESSAGE,
6850
7015
  CONNECT_NOT_APPROVED_MESSAGE,
6851
7016
  NOTHING_DELETED_LINE,
7017
+ NOTHING_REMOVED_LINE,
7018
+ SCANNER_REMOVED_LINE,
7019
+ SCANNER_UNINSTALL_PROMPT,
6852
7020
  alreadyConnectedFolderLine,
6853
7021
  connectFolderPrompt,
6854
7022
  connectedFolderLine,