@mnemonik/cli 7.104.11 → 7.105.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/dist/router.js CHANGED
@@ -517,17 +517,311 @@ var init_repositoryFingerprint = __esm({
517
517
  });
518
518
 
519
519
  // packages/shared/dist/projectIdentityFile.js
520
+ import { lstat, readFile } from "node:fs/promises";
521
+ import { join as join2 } from "node:path";
522
+ function parseIdentityFile(text) {
523
+ let value;
524
+ try {
525
+ value = JSON.parse(text);
526
+ } catch (error) {
527
+ return { kind: "malformed", detail: `unparseable JSON: ${error.message}` };
528
+ }
529
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
530
+ return { kind: "malformed", detail: "identity file must contain a JSON object" };
531
+ }
532
+ const record = value;
533
+ if (record.schemaVersion !== 1) {
534
+ return { kind: "unknown_version", version: record.schemaVersion };
535
+ }
536
+ const unknownKey = Object.keys(record).find((key) => !IDENTITY_KEYS.has(key));
537
+ if (unknownKey)
538
+ return { kind: "malformed", detail: `unknown key "${unknownKey}"` };
539
+ if (!isCanonicalUuid(record.projectId)) {
540
+ return { kind: "malformed", detail: '"projectId" must be a canonical RFC 4122 UUID string' };
541
+ }
542
+ if ("projectName" in record && typeof record.projectName !== "string") {
543
+ return { kind: "malformed", detail: '"projectName" must be a string when present' };
544
+ }
545
+ if ("repositoryFingerprint" in record && (!record.repositoryFingerprint || typeof record.repositoryFingerprint !== "object" || Array.isArray(record.repositoryFingerprint))) {
546
+ return { kind: "malformed", detail: '"repositoryFingerprint" must be an object when present' };
547
+ }
548
+ const fingerprint = record.repositoryFingerprint;
549
+ const unknownFingerprintKey = fingerprint ? Object.keys(fingerprint).find((key) => !FINGERPRINT_KEYS.has(key)) : void 0;
550
+ if (unknownFingerprintKey) {
551
+ return {
552
+ kind: "malformed",
553
+ detail: `unknown repositoryFingerprint key "${unknownFingerprintKey}"`
554
+ };
555
+ }
556
+ if (fingerprint && (fingerprint.algorithmVersion !== 1 || typeof fingerprint.hash !== "string")) {
557
+ return {
558
+ kind: "malformed",
559
+ detail: '"repositoryFingerprint" must contain algorithmVersion 1 and a string hash'
560
+ };
561
+ }
562
+ const repositoryFingerprint = fingerprint ? { algorithmVersion: 1, hash: fingerprint.hash } : void 0;
563
+ return {
564
+ kind: "ok",
565
+ identity: {
566
+ schemaVersion: 1,
567
+ projectId: record.projectId,
568
+ ...record.projectName === void 0 ? {} : { projectName: record.projectName },
569
+ ...repositoryFingerprint ? { repositoryFingerprint } : {}
570
+ }
571
+ };
572
+ }
573
+ async function readIdentityFile(dir, options = {}) {
574
+ const path = join2(dir, ".mnemonik.json");
575
+ try {
576
+ const stat = await lstat(path);
577
+ if (stat.isSymbolicLink())
578
+ return { kind: "malformed", detail: "symlink" };
579
+ if (!stat.isFile())
580
+ return { kind: "malformed", detail: "identity path is not a regular file" };
581
+ const text = await readFile(path, "utf8");
582
+ if (options.selectedRoot) {
583
+ try {
584
+ const value = JSON.parse(text);
585
+ if (value && typeof value === "object" && !Array.isArray(value) && !("projectId" in value))
586
+ return { kind: "absent" };
587
+ } catch {
588
+ }
589
+ }
590
+ return parseIdentityFile(text);
591
+ } catch (error) {
592
+ if (error.code === "ENOENT")
593
+ return { kind: "absent" };
594
+ return { kind: "malformed", detail: `cannot read identity file: ${error.message}` };
595
+ }
596
+ }
597
+ var CANONICAL_UUID, isCanonicalUuid, IDENTITY_KEYS, FINGERPRINT_KEYS;
520
598
  var init_projectIdentityFile = __esm({
521
599
  "packages/shared/dist/projectIdentityFile.js"() {
522
600
  "use strict";
601
+ CANONICAL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
602
+ isCanonicalUuid = (value) => typeof value === "string" && CANONICAL_UUID.test(value);
603
+ IDENTITY_KEYS = /* @__PURE__ */ new Set([
604
+ "schemaVersion",
605
+ "projectId",
606
+ "projectName",
607
+ "repositoryFingerprint"
608
+ ]);
609
+ FINGERPRINT_KEYS = /* @__PURE__ */ new Set(["algorithmVersion", "hash"]);
523
610
  }
524
611
  });
525
612
 
526
613
  // packages/shared/dist/repositoryRoot.js
614
+ import { lstat as lstat2, readFile as readFile2 } from "node:fs/promises";
615
+ import { execFile } from "node:child_process";
616
+ import { homedir } from "node:os";
617
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
618
+ function gitEnvironment() {
619
+ const env = {};
620
+ for (const key of GIT_ENV_KEYS) {
621
+ if (process.env[key] !== void 0)
622
+ env[key] = process.env[key];
623
+ }
624
+ env.LC_ALL = "C";
625
+ return env;
626
+ }
627
+ function runGit(cwd, args2) {
628
+ return new Promise((resolvePromise, reject) => {
629
+ execFile("git", args2, { cwd, timeout: GIT_TIMEOUT_MS, encoding: "utf8", env: gitEnvironment() }, (error, stdout, stderr) => {
630
+ if (error)
631
+ reject(Object.assign(error, { stdout, stderr }));
632
+ else
633
+ resolvePromise({ stdout, stderr });
634
+ });
635
+ });
636
+ }
637
+ async function boundaryAt(path) {
638
+ try {
639
+ const stat = await lstat2(join3(path, ".git"));
640
+ if (stat.isDirectory())
641
+ return { path, kind: "directory" };
642
+ if (stat.isFile())
643
+ return { path, kind: "file" };
644
+ } catch (error) {
645
+ const code = error.code;
646
+ if (code !== "ENOENT" && code !== "ENOTDIR")
647
+ throw error;
648
+ }
649
+ return void 0;
650
+ }
651
+ async function confirmedBoundaryAt(path) {
652
+ const boundary = await boundaryAt(path);
653
+ if (!boundary)
654
+ return void 0;
655
+ try {
656
+ const { stdout } = await runGit(path, ["rev-parse", "--git-dir"]);
657
+ return stdout.trim() ? boundary : void 0;
658
+ } catch {
659
+ return void 0;
660
+ }
661
+ }
662
+ async function boundariesBetween(cwd, root) {
663
+ const boundaries = [];
664
+ let directory = cwd;
665
+ while (directory !== root) {
666
+ const boundary = await confirmedBoundaryAt(directory);
667
+ if (boundary)
668
+ boundaries.push(boundary);
669
+ const parent = dirname2(directory);
670
+ if (parent === directory)
671
+ break;
672
+ directory = parent;
673
+ }
674
+ return boundaries;
675
+ }
676
+ async function containingRepository(root) {
677
+ let directory = dirname2(root);
678
+ while (true) {
679
+ const boundary = await confirmedBoundaryAt(directory);
680
+ if (boundary)
681
+ return boundary;
682
+ const parent = dirname2(directory);
683
+ if (parent === directory)
684
+ return void 0;
685
+ directory = parent;
686
+ }
687
+ }
688
+ async function canonicalBoundaryRoot(boundary) {
689
+ if (boundary.kind === "directory")
690
+ return boundary.path;
691
+ const pointer = /^gitdir:\s*(.+)\s*$/im.exec(await readFile2(join3(boundary.path, ".git"), "utf8"))?.[1];
692
+ if (!pointer)
693
+ return boundary.path;
694
+ const gitDir = resolve(boundary.path, pointer);
695
+ try {
696
+ const commonDir = resolve(gitDir, (await readFile2(join3(gitDir, "commondir"), "utf8")).trim());
697
+ return dirname2(commonDir);
698
+ } catch (error) {
699
+ if (error.code === "ENOENT")
700
+ return boundary.path;
701
+ throw error;
702
+ }
703
+ }
704
+ async function resolveRepositoryRoot(cwd) {
705
+ const absoluteCwd = resolve(cwd);
706
+ try {
707
+ const { stdout } = await runGit(absoluteCwd, [
708
+ "rev-parse",
709
+ "--show-toplevel",
710
+ "--git-common-dir"
711
+ ]);
712
+ const [rootLine, commonLine, ...extra] = stdout.trim().split(/\r?\n/);
713
+ if (!rootLine || !commonLine || extra.length) {
714
+ return { kind: "git_unavailable", detail: "git rev-parse returned an unexpected response" };
715
+ }
716
+ const root = resolve(rootLine);
717
+ const commonDir = resolve(root, commonLine);
718
+ const isLinkedWorktree = commonDir !== join3(root, ".git");
719
+ const nested = await boundariesBetween(absoluteCwd, root);
720
+ if (!isLinkedWorktree && await containingRepository(root)) {
721
+ const boundary = await boundaryAt(root);
722
+ if (boundary)
723
+ nested.push(boundary);
724
+ }
725
+ return {
726
+ kind: "git",
727
+ root,
728
+ commonDir,
729
+ isLinkedWorktree,
730
+ nested
731
+ };
732
+ } catch (error) {
733
+ const failure = error;
734
+ if (failure.code === 128 && /not a git repository/i.test(failure.stderr ?? failure.message)) {
735
+ return { kind: "plain", root: absoluteCwd };
736
+ }
737
+ return { kind: "git_unavailable", detail: failure.message };
738
+ }
739
+ }
740
+ async function readPlainIdentity(start2) {
741
+ const home = resolve(homedir());
742
+ let directory = start2;
743
+ while (true) {
744
+ const result = await readIdentityFile(directory);
745
+ if (result.kind !== "absent")
746
+ return { root: directory, result };
747
+ if (directory === home)
748
+ break;
749
+ const parent = dirname2(directory);
750
+ if (parent === directory)
751
+ break;
752
+ directory = parent;
753
+ }
754
+ return { root: start2, result: { kind: "absent" } };
755
+ }
756
+ function withBase(repository, root, nested) {
757
+ return { root, repository, nested };
758
+ }
759
+ async function resolveProjectIdentity(cwd, options = {}) {
760
+ const resolved = await resolveRepositoryRoot(cwd);
761
+ const repository = resolved.kind === "git_unavailable" ? { kind: "plain", root: resolve(cwd) } : resolved;
762
+ if (repository.kind === "plain") {
763
+ const { root, result } = options.selectedRoot ? { root: repository.root, result: await readIdentityFile(repository.root, options) } : await readPlainIdentity(repository.root);
764
+ const base2 = withBase(repository, root, []);
765
+ if (result.kind === "ok")
766
+ return { ...base2, ...result };
767
+ if (result.kind === "unknown_version")
768
+ return { ...base2, ...result, path: root };
769
+ if (result.kind === "malformed")
770
+ return { ...base2, ...result, path: root };
771
+ return { ...base2, kind: "absent" };
772
+ }
773
+ const ownRoot = repository.isLinkedWorktree ? dirname2(repository.commonDir) : repository.root;
774
+ const outer = repository.isLinkedWorktree ? void 0 : await containingRepository(repository.root);
775
+ const own = await readIdentityFile(ownRoot, options);
776
+ if (!outer || options.selectedRoot || own.kind === "ok") {
777
+ const base2 = withBase(repository, ownRoot, repository.nested);
778
+ if (own.kind === "ok")
779
+ return { ...base2, ...own };
780
+ if (own.kind === "unknown_version")
781
+ return { ...base2, ...own, path: ownRoot };
782
+ if (own.kind === "malformed")
783
+ return { ...base2, ...own, path: ownRoot };
784
+ return { ...base2, kind: "absent" };
785
+ }
786
+ const nested = repository.nested;
787
+ const parentRoot = await canonicalBoundaryRoot(outer);
788
+ const parent = await readIdentityFile(parentRoot);
789
+ const base = withBase(repository, parentRoot, nested);
790
+ if (own.kind === "unknown_version")
791
+ return { ...base, ...own, path: ownRoot };
792
+ if (own.kind === "malformed")
793
+ return { ...base, ...own, path: ownRoot };
794
+ if (parent.kind === "unknown_version")
795
+ return { ...base, ...parent, path: parentRoot };
796
+ if (parent.kind === "malformed")
797
+ return { ...base, ...parent, path: parentRoot };
798
+ if (!options.allowNestedInherit) {
799
+ return {
800
+ ...base,
801
+ kind: "nested",
802
+ ...parent.kind === "ok" ? { parentIdentity: parent.identity } : {}
803
+ };
804
+ }
805
+ if (parent.kind === "ok")
806
+ return { ...base, kind: "ok", identity: parent.identity };
807
+ return { ...base, kind: "absent" };
808
+ }
809
+ var GIT_TIMEOUT_MS, GIT_ENV_KEYS;
527
810
  var init_repositoryRoot = __esm({
528
811
  "packages/shared/dist/repositoryRoot.js"() {
529
812
  "use strict";
530
813
  init_projectIdentityFile();
814
+ GIT_TIMEOUT_MS = 2e3;
815
+ GIT_ENV_KEYS = [
816
+ "PATH",
817
+ "HOME",
818
+ "LANG",
819
+ "LC_ALL",
820
+ "SYSTEMROOT",
821
+ "TEMP",
822
+ "TMP",
823
+ "USERPROFILE"
824
+ ];
531
825
  }
532
826
  });
533
827
 
@@ -2183,13 +2477,13 @@ async function Module2(moduleArg = {}) {
2183
2477
  }
2184
2478
  readAsync = /* @__PURE__ */ __name(async (url) => {
2185
2479
  if (isFileURI(url)) {
2186
- return new Promise((resolve, reject) => {
2480
+ return new Promise((resolve2, reject) => {
2187
2481
  var xhr = new XMLHttpRequest();
2188
2482
  xhr.open("GET", url, true);
2189
2483
  xhr.responseType = "arraybuffer";
2190
2484
  xhr.onload = () => {
2191
2485
  if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
2192
- resolve(xhr.response);
2486
+ resolve2(xhr.response);
2193
2487
  return;
2194
2488
  }
2195
2489
  reject(xhr.status);
@@ -2385,9 +2679,9 @@ async function Module2(moduleArg = {}) {
2385
2679
  __name(receiveInstantiationResult, "receiveInstantiationResult");
2386
2680
  var info22 = getWasmImports();
2387
2681
  if (Module["instantiateWasm"]) {
2388
- return new Promise((resolve, reject) => {
2682
+ return new Promise((resolve2, reject) => {
2389
2683
  Module["instantiateWasm"](info22, (mod, inst) => {
2390
- resolve(receiveInstance(mod, inst));
2684
+ resolve2(receiveInstance(mod, inst));
2391
2685
  });
2392
2686
  });
2393
2687
  }
@@ -3718,8 +4012,8 @@ async function Module2(moduleArg = {}) {
3718
4012
  if (runtimeInitialized) {
3719
4013
  moduleRtn = Module;
3720
4014
  } else {
3721
- moduleRtn = new Promise((resolve, reject) => {
3722
- readyPromiseResolve = resolve;
4015
+ moduleRtn = new Promise((resolve2, reject) => {
4016
+ readyPromiseResolve = resolve2;
3723
4017
  readyPromiseReject = reject;
3724
4018
  });
3725
4019
  }
@@ -4866,10 +5160,10 @@ function apiOrigin(env = process.env) {
4866
5160
 
4867
5161
  // packages/local-setup/dist/storage.js
4868
5162
  init_hookRuntime();
4869
- import { homedir } from "node:os";
4870
- import { dirname as dirname2, join as join2, win32, posix } from "node:path";
5163
+ import { homedir as homedir2 } from "node:os";
5164
+ import { dirname as dirname3, join as join4, win32, posix } from "node:path";
4871
5165
  init_hookRuntime();
4872
- function stateDirectory(platform = process.platform, env = process.env, home = homedir()) {
5166
+ function stateDirectory(platform = process.platform, env = process.env, home = homedir2()) {
4873
5167
  if (env.MNEMONIK_STATE_DIR)
4874
5168
  return env.MNEMONIK_STATE_DIR;
4875
5169
  if (platform === "win32")
@@ -4956,12 +5250,12 @@ import {
4956
5250
  import { hostOrder, launchHostLabels } from "./install/adapters.js";
4957
5251
  import { readInstallVersions } from "./install/ownership.js";
4958
5252
  import { ensureLauncher, removeLauncher, LauncherError } from "./launcher.js";
4959
- import { readFile, realpath } from "node:fs/promises";
5253
+ import { readFile as readFile3, realpath } from "node:fs/promises";
4960
5254
  import { createInterface } from "node:readline";
4961
5255
  import { RuntimeStore, updateRuntime } from "./runtime/store.js";
4962
5256
  import { updateCli, cliUpdateHint } from "./runtime/selfUpdate.js";
4963
- import { homedir as homedir2 } from "node:os";
4964
- import { join as join3 } from "node:path";
5257
+ import { homedir as homedir3 } from "node:os";
5258
+ import { join as join5 } from "node:path";
4965
5259
  import { Output } from "./output.js";
4966
5260
  import { SCANNER_RESTART_MESSAGE } from "./scanner/service.js";
4967
5261
  import { SCANNER_FAILURE_MESSAGE, SCANNER_RETRY_MESSAGE } from "./screens/journey.js";
@@ -4978,6 +5272,7 @@ import {
4978
5272
  import { evaluateRoot } from "./project/eligibility.js";
4979
5273
  import { grantTransport, grantHost } from "./auth/status.js";
4980
5274
  import { createCliAuth } from "./auth/index.js";
5275
+ import { runEditorLogin } from "./auth/pkce.js";
4981
5276
  import { currentInstallSession, ensureInstallSession } from "./auth/installSession.js";
4982
5277
  import { runIdentityMigration } from "./identity/migrate.js";
4983
5278
  import { renderScannerStatus } from "./scanner/picker.js";
@@ -5005,7 +5300,14 @@ import {
5005
5300
  var connectFolderPrompt = (name2) => `Connect ${name2} to Mnemonik? [Y/n]`;
5006
5301
  var removeFolderPrompt = (name2) => `Stop indexing ${name2}? Its memories stay in your account. [y/N]`;
5007
5302
  var connectedFolderLine = (name2) => ` \u2713 Connected ${name2}.`;
5303
+ var alreadyConnectedFolderLine = (name2) => ` \u2713 ${name2} is already connected and watched.`;
5008
5304
  var removedFolderLine = (name2) => ` \u2713 ${name2} is no longer connected.`;
5305
+ var projectDeletionWarning = (name2) => `Deleting ${name2} removes its memories, code index and summaries for everyone. This cannot be undone.`;
5306
+ var projectDeletedLine = (name2) => `Deleted ${name2}.`;
5307
+ var stillWatchedLine = (root) => `The folder is still being indexed. Run mnemonik remove ${root} to stop that.`;
5308
+ var identityFileKeptLine = "This folder's .mnemonik.json still points at the deleted project. Connecting the folder again creates a new project.";
5309
+ var CODEX_SIGNED_IN_MESSAGE = "Codex is signed in to Mnemonik.";
5310
+ var CONNECT_NOT_APPROVED_MESSAGE = "Sign-in timed out. Run mnemonik connect codex to try again.";
5009
5311
  function maintenanceExitCode(results) {
5010
5312
  if (results.some((result) => result.status === "FAILED")) return 1;
5011
5313
  return results.every((result) => result.status === "READY") ? 0 : 3;
@@ -5064,7 +5366,7 @@ Commands:
5064
5366
  install
5065
5367
  status
5066
5368
  connect <${hostOrder.join("|")}>
5067
- project <init|setup|status|link|ensure>
5369
+ project <init|setup|status|link|ensure|delete>
5068
5370
  add <folder>
5069
5371
  remove <folder>
5070
5372
  data delete --project <id>
@@ -5083,6 +5385,7 @@ Install consent: --accept-indexing --accept-limited --apply`;
5083
5385
  function parse(args2) {
5084
5386
  const positionals = [];
5085
5387
  const flags2 = /* @__PURE__ */ new Map();
5388
+ const namedConfirm = args2[0] === "project" && args2[1] === "delete";
5086
5389
  for (let index = 0; index < args2.length; index++) {
5087
5390
  const argument = args2[index] ?? "";
5088
5391
  if (!argument.startsWith("--")) {
@@ -5096,8 +5399,17 @@ function parse(args2) {
5096
5399
  continue;
5097
5400
  }
5098
5401
  if (booleans.has(name2)) {
5099
- if (inline !== void 0) return { positionals, flags: flags2, error: `Unknown flag: ${argument}` };
5100
- flags2.set(name2 === "accept-scanner" ? "accept-indexing" : name2, true);
5402
+ const takesName = name2 === "confirm" && namedConfirm;
5403
+ if (inline !== void 0 && !takesName)
5404
+ return { positionals, flags: flags2, error: `Unknown flag: ${argument}` };
5405
+ const next = args2[index + 1];
5406
+ let value = true;
5407
+ if (takesName && inline !== void 0) value = inline;
5408
+ else if (takesName && next !== void 0 && !next.startsWith("--")) {
5409
+ value = next;
5410
+ index++;
5411
+ }
5412
+ flags2.set(name2 === "accept-scanner" ? "accept-indexing" : name2, value);
5101
5413
  continue;
5102
5414
  }
5103
5415
  if (values.has(name2)) {
@@ -5179,7 +5491,7 @@ async function hostDependencies(deps, output, state) {
5179
5491
  if (!cliAuth.accountEmail) throw new Error("account_identity_failed");
5180
5492
  const email = await cliAuth.accountEmail(bearer);
5181
5493
  const installation = JSON.parse(
5182
- await readFile(join3(state, "installation.json"), "utf8").catch(() => "{}")
5494
+ await readFile3(join5(state, "installation.json"), "utf8").catch(() => "{}")
5183
5495
  );
5184
5496
  return {
5185
5497
  stateDir: state,
@@ -5196,7 +5508,7 @@ async function hostDependencies(deps, output, state) {
5196
5508
  async function updateHostDependencies(deps, state) {
5197
5509
  if (deps.hostManagement) return { ...deps.hostManagement, stateDir: state };
5198
5510
  const installation = JSON.parse(
5199
- await readFile(join3(state, "installation.json"), "utf8").catch(() => "{}")
5511
+ await readFile3(join5(state, "installation.json"), "utf8").catch(() => "{}")
5200
5512
  );
5201
5513
  return {
5202
5514
  stateDir: state,
@@ -5204,7 +5516,7 @@ async function updateHostDependencies(deps, state) {
5204
5516
  };
5205
5517
  }
5206
5518
  async function packageVersion() {
5207
- const contents = await readFile(new URL("../package.json", import.meta.url), "utf8");
5519
+ const contents = await readFile3(new URL("../package.json", import.meta.url), "utf8");
5208
5520
  return JSON.parse(contents).version;
5209
5521
  }
5210
5522
  async function runHostCommand(command, parsed, deps, output, scannerSelected = false) {
@@ -5236,7 +5548,7 @@ async function runHostCommand(command, parsed, deps, output, scannerSelected = f
5236
5548
  component: component2,
5237
5549
  host: name2,
5238
5550
  scope: "user",
5239
- home: deps.home ?? homedir2(),
5551
+ home: deps.home ?? homedir3(),
5240
5552
  projectRoot: deps.cwd ?? process.cwd()
5241
5553
  }))
5242
5554
  );
@@ -5305,7 +5617,7 @@ async function runHostCommand(command, parsed, deps, output, scannerSelected = f
5305
5617
  noBrowser: parsed.flags.has("no-browser"),
5306
5618
  source: dependencies.source ?? (updatedCli ? (host2) => hostSource(
5307
5619
  host2,
5308
- join3(updatedCli.directory, "node_modules/@mnemonik/cli/package.json")
5620
+ join5(updatedCli.directory, "node_modules/@mnemonik/cli/package.json")
5309
5621
  ) : void 0),
5310
5622
  instruction: json ? void 0 : (text) => output.line(text),
5311
5623
  apply: parsed.flags.has("apply")
@@ -5316,7 +5628,7 @@ async function runHostCommand(command, parsed, deps, output, scannerSelected = f
5316
5628
  let scanner;
5317
5629
  let launcher;
5318
5630
  let scannerRecovery;
5319
- if (all && await readFile(`${state}/scanner/state.json`).then(
5631
+ if (all && await readFile3(`${state}/scanner/state.json`).then(
5320
5632
  () => true,
5321
5633
  () => false
5322
5634
  )) {
@@ -5358,7 +5670,7 @@ async function runHostCommand(command, parsed, deps, output, scannerSelected = f
5358
5670
  if (fullUninstall && hostExit === 0) {
5359
5671
  const installed = await Promise.all(
5360
5672
  [new RuntimeStore(state).pointerPath("scanner"), `${state}/scanner/state.json`].map(
5361
- (path) => readFile(path).then(
5673
+ (path) => readFile3(path).then(
5362
5674
  () => true,
5363
5675
  () => false
5364
5676
  )
@@ -5402,18 +5714,29 @@ async function runHostCommand(command, parsed, deps, output, scannerSelected = f
5402
5714
  ...remaining ? { remaining } : {}
5403
5715
  });
5404
5716
  else if (command === "update") {
5405
- if (failed || hostsFailed) output.error(updateFailureMessage);
5406
- else if (codexTrustPending) {
5717
+ if (failed || hostsFailed) {
5718
+ if (cli?.status === "UPDATED") output.line("The mnemonik command updated.");
5719
+ if (result.reports.length > 0 && !hostsFailed) output.line("Your editors updated.");
5720
+ if (scanner?.status === "UPDATED") output.line("The scanner updated.");
5721
+ if (cli?.status === "FAILED") {
5722
+ output.error("The mnemonik command could not update.");
5723
+ output.error("Run npx -y @mnemonik/cli@latest install to update it.");
5724
+ }
5725
+ if (hostsFailed) {
5726
+ output.error("Your editors could not update.");
5727
+ output.error("Run mnemonik repair, then start a new session in each editor.");
5728
+ }
5729
+ if (scanner?.status === "FAILED") {
5730
+ output.error(scannerRecovery?.message ?? "The scanner could not update.");
5731
+ output.error(scannerRecovery?.action ?? SCANNER_RETRY_MESSAGE);
5732
+ }
5733
+ } else if (codexTrustPending) {
5407
5734
  output.line("Mnemonik updated.");
5408
5735
  output.line(CODEX_TRUST_MESSAGE.sentence);
5409
5736
  output.line(CODEX_TRUST_MESSAGE.nextStep);
5410
5737
  } else if (cli?.status === "UPDATED" || result.reports.length > 0 || scanner?.status === "UPDATED")
5411
5738
  output.line("Mnemonik updated.");
5412
5739
  else output.line("Mnemonik is up to date.");
5413
- if (scannerRecovery) {
5414
- output.error(scannerRecovery.message);
5415
- if (scannerRecovery.action) output.error(scannerRecovery.action);
5416
- }
5417
5740
  } else {
5418
5741
  for (const target of result.results)
5419
5742
  output.line(target.status === "READY" ? "Done." : humanReason(target.reason));
@@ -5591,7 +5914,7 @@ async function doctorCommand(parsed, deps, output) {
5591
5914
  const invalid = allowed(parsed, []);
5592
5915
  if (invalid) return output.error(invalid), 2;
5593
5916
  const result = await runPreflight({ cwd: deps.cwd, home: deps.home, ...deps.preflight });
5594
- output.setContext({ home: deps.home ?? homedir2(), projectRoot: result.project.root });
5917
+ output.setContext({ home: deps.home ?? homedir3(), projectRoot: result.project.root });
5595
5918
  const document = await collectStatusDocument({
5596
5919
  preflight: result,
5597
5920
  cwd: deps.cwd ?? process.cwd(),
@@ -5628,10 +5951,10 @@ async function collectCurrentInstallation(deps, output) {
5628
5951
  ...deps.preflight,
5629
5952
  fetch: async () => new Response(null, { status: 204 })
5630
5953
  });
5631
- output.setContext({ home: deps.home ?? homedir2(), projectRoot: result.project.root });
5954
+ output.setContext({ home: deps.home ?? homedir3(), projectRoot: result.project.root });
5632
5955
  const hostStateDir = deps.hostManagement?.stateDir ?? deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
5633
5956
  const localConditions = deps.projectHookConditions ? [] : [
5634
- ...await localInstallationConditions(deps.home ?? homedir2(), hostStateDir, deps.launcher),
5957
+ ...await localInstallationConditions(deps.home ?? homedir3(), hostStateDir, deps.launcher),
5635
5958
  // Installed Codex hooks that Codex has not trusted cannot run.
5636
5959
  ...await (deps.codexTrustConditions ?? (() => codexTrustConditions({
5637
5960
  stateDir: hostStateDir,
@@ -5675,6 +5998,96 @@ async function reportCurrentInstallation(deps, output, document) {
5675
5998
  ).catch(() => {
5676
5999
  });
5677
6000
  }
6001
+ async function deleteProjectCommand(parsed, rest, deps, output) {
6002
+ const json = parsed.flags.has("json");
6003
+ const cwd = deps.cwd ?? process.cwd();
6004
+ const resolve2 = deps.projectResolver?.resolveProjectIdentity ?? resolveProjectIdentity;
6005
+ const here = await resolve2(cwd, { allowNestedInherit: false });
6006
+ const hereId = here.kind === "ok" ? here.identity.projectId : void 0;
6007
+ const root = "root" in here ? here.root : cwd;
6008
+ const target = rest[0] ?? hereId;
6009
+ if (!target) {
6010
+ output.error(
6011
+ "This folder is not connected to a project. Run mnemonik project delete <name> instead."
6012
+ );
6013
+ return 3;
6014
+ }
6015
+ const bearer = await ensureCliAuth(deps, output, parsed.flags.has("no-browser"), false);
6016
+ const send = deps.grantFetch ?? fetch;
6017
+ const listed = await send(`${apiOrigin()}/api/v1/users/me/projects?days=0&limit=200`, {
6018
+ headers: { Authorization: `Bearer ${bearer}` }
6019
+ });
6020
+ if (!listed.ok) {
6021
+ output.error("Mnemonik could not be reached from this machine.");
6022
+ return 3;
6023
+ }
6024
+ const listing = await listed.json().catch(() => []);
6025
+ const projects = Array.isArray(listing) ? listing : [];
6026
+ const project = projects.find(
6027
+ (candidate) => candidate.id === target || candidate.name?.toLowerCase() === target.toLowerCase()
6028
+ );
6029
+ if (!project) {
6030
+ output.error(`There is no project called ${target} in your account.`);
6031
+ return 3;
6032
+ }
6033
+ const supplied = parsed.flags.get("confirm");
6034
+ let typed = typeof supplied === "string" ? supplied : "";
6035
+ if (typeof supplied !== "string") {
6036
+ if (parsed.flags.has("non-interactive") || json) {
6037
+ output.error(
6038
+ `To skip this check, run mnemonik project delete ${project.name} --confirm "${project.name}".`
6039
+ );
6040
+ return 3;
6041
+ }
6042
+ output.line(projectDeletionWarning(project.name));
6043
+ output.line("Type the project name to confirm.");
6044
+ const readline = createInterface({ input: deps.input ?? process.stdin, terminal: false });
6045
+ typed = String((await readline[Symbol.asyncIterator]().next()).value ?? "").trim();
6046
+ readline.close();
6047
+ if (typed.toLowerCase() !== project.name.toLowerCase()) {
6048
+ output.line("Nothing was deleted.");
6049
+ return 130;
6050
+ }
6051
+ }
6052
+ if (typed.toLowerCase() !== project.name.toLowerCase()) {
6053
+ output.error("That is not the project name. Nothing was deleted.");
6054
+ return 3;
6055
+ }
6056
+ const response = await send(`${apiOrigin()}/api/v1/projects/${encodeURIComponent(project.id)}`, {
6057
+ method: "DELETE",
6058
+ headers: { Authorization: `Bearer ${bearer}`, "content-type": "application/json" },
6059
+ body: JSON.stringify({ confirmProjectName: typed })
6060
+ });
6061
+ if (!response.ok) {
6062
+ output.error(
6063
+ response.status === 403 ? `Only the owner of ${project.name} can delete it.` : response.status === 404 ? `There is no project called ${target} in your account.` : `${project.name} could not be deleted. Try again in a moment.`
6064
+ );
6065
+ return 3;
6066
+ }
6067
+ const result = await response.json().catch(() => ({ success: true }));
6068
+ const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
6069
+ const saved = JSON.parse(
6070
+ await readFile3(`${stateDir}/scanner/state.json`, "utf8").catch(() => "null")
6071
+ );
6072
+ let stillWatched = false;
6073
+ if (hereId === project.id && saved?.config.roots.includes(root)) {
6074
+ const update = await updateScannerRoots({
6075
+ stateDir,
6076
+ bearer,
6077
+ add: [],
6078
+ remove: [root],
6079
+ fetch: deps.grantFetch
6080
+ }).catch(() => void 0);
6081
+ stillWatched = update?.status !== "updated";
6082
+ }
6083
+ if (json) output.json(result);
6084
+ else {
6085
+ output.line(projectDeletedLine(project.name));
6086
+ if (stillWatched) output.line(stillWatchedLine(root));
6087
+ if (hereId === project.id) output.line(identityFileKeptLine);
6088
+ }
6089
+ return 0;
6090
+ }
5678
6091
  async function runCli(args2, deps = {}) {
5679
6092
  const parsed = parse(args2);
5680
6093
  const silent = parsed.flags.has("automatic");
@@ -5683,7 +6096,7 @@ async function runCli(args2, deps = {}) {
5683
6096
  const stdout = silent ? discard : deps.stdout ?? process.stdout;
5684
6097
  const stderr = silent ? discard : deps.stderr ?? process.stderr;
5685
6098
  const output = new Output(stdout, stderr, {
5686
- home: deps.home ?? homedir2()
6099
+ home: deps.home ?? homedir3()
5687
6100
  });
5688
6101
  if (process.env.MNEMONIK_DEV_RELEASE_DIR && !silent)
5689
6102
  stderr.write(
@@ -5715,7 +6128,7 @@ async function runCli(args2, deps = {}) {
5715
6128
  return output.error(invalid ?? "Usage: mnemonik add <folder> or mnemonik remove <folder>"), 2;
5716
6129
  const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
5717
6130
  const saved = JSON.parse(
5718
- await readFile(`${stateDir}/scanner/state.json`, "utf8").catch(() => "null")
6131
+ await readFile3(`${stateDir}/scanner/state.json`, "utf8").catch(() => "null")
5719
6132
  );
5720
6133
  if (!saved) return actionRequired2(output, parsed.flags.has("json"), "mnemonik install");
5721
6134
  if (action === "list") {
@@ -5728,6 +6141,11 @@ async function runCli(args2, deps = {}) {
5728
6141
  const pathArgument = actionArguments[0] ?? "";
5729
6142
  const requested = action === "add" ? await realpath(pathArgument) : saved.config.roots.find((root) => root === pathArgument) ?? pathArgument;
5730
6143
  const name2 = requested.split(/[\\/]/u).filter(Boolean).at(-1) ?? requested;
6144
+ if (action === "add" && saved.config.roots.includes(requested)) {
6145
+ if (parsed.flags.has("json")) output.json(saved.config.roots);
6146
+ else output.line(alreadyConnectedFolderLine(name2));
6147
+ return 0;
6148
+ }
5731
6149
  if (!parsed.flags.has("non-interactive") && !parsed.flags.has("json")) {
5732
6150
  output.line(action === "add" ? connectFolderPrompt(name2) : removeFolderPrompt(name2));
5733
6151
  const readline = createInterface({ input: deps.input ?? process.stdin, terminal: false });
@@ -5793,7 +6211,7 @@ async function runCli(args2, deps = {}) {
5793
6211
  if (invalid || rest.length) return output.error(invalid ?? "Unexpected argument"), 2;
5794
6212
  try {
5795
6213
  const stateDir = deps.installStateDir ?? stateDirectory(process.platform, process.env, deps.home);
5796
- const saved = JSON.parse(await readFile(`${stateDir}/scanner/state.json`, "utf8"));
6214
+ const saved = JSON.parse(await readFile3(`${stateDir}/scanner/state.json`, "utf8"));
5797
6215
  const bearer = await ensureCliAuth(deps, output, false);
5798
6216
  const response = await (deps.grantFetch ?? fetch)(
5799
6217
  `${apiOrigin()}/api/v1/component-credentials/${encodeURIComponent(saved.config.credentialFamilyId)}/revoke`,
@@ -6030,9 +6448,25 @@ Preview: ${preview.path}
6030
6448
  if (!subcommand || rest.length || !hostOrder.includes(subcommand))
6031
6449
  return output.error(`Usage: mnemonik connect <${hostOrder.join("|")}>`), 2;
6032
6450
  const host = subcommand;
6033
- const editor = (await localEditorStatus(deps.home ?? homedir2())).find(
6451
+ const editor = (await localEditorStatus(deps.home ?? homedir3())).find(
6034
6452
  (candidate) => candidate.host === host
6035
6453
  );
6454
+ if (host === "codex" && editor?.mcp === "ready" && !parsed.flags.has("json")) {
6455
+ const bearer = await auth(deps, output, false).getCliBearer().catch(() => void 0);
6456
+ if (typeof bearer === "string") {
6457
+ const outcome = await runEditorLogin({
6458
+ command: ["codex", "mcp", "login", "mnemonik"],
6459
+ apiOrigin: apiOrigin(),
6460
+ issuer: process.env.MNEMONIK_OAUTH_ISSUER ?? "https://auth.mnemonik.ai",
6461
+ bearer: () => Promise.resolve(bearer),
6462
+ print: (line) => void output.line(line),
6463
+ fetch: deps.grantFetch,
6464
+ ...deps.editorLogin
6465
+ });
6466
+ if (outcome === "signed_in") return output.line(CODEX_SIGNED_IN_MESSAGE), 0;
6467
+ if (outcome === "not_approved") return output.line(CONNECT_NOT_APPROVED_MESSAGE), 1;
6468
+ }
6469
+ }
6036
6470
  const reason = editor?.mcp === "disabled" ? `${editor.name} connection is turned off.` : editor?.mcp === "ready" ? "Finish signing in to Mnemonik in the editor." : `${launchHostLabels[host]} connection is missing.`;
6037
6471
  const actions = editor?.mcp === "ready" ? editorAuthorizationRows([host]) : [
6038
6472
  editor?.mcp === "disabled" ? mcpTurnOnAction[host] : "Run mnemonik install to set it up again."
@@ -6045,8 +6479,14 @@ Preview: ${preview.path}
6045
6479
  return 3;
6046
6480
  }
6047
6481
  if (command === "project") {
6048
- if (!subcommand || !["init", "setup", "status", "link", "ensure"].includes(subcommand))
6049
- return output.error("Usage: mnemonik project <init|setup|status|link|ensure>"), 2;
6482
+ if (!subcommand || !["init", "setup", "status", "link", "ensure", "delete"].includes(subcommand))
6483
+ return output.error("Usage: mnemonik project <init|setup|status|link|ensure|delete>"), 2;
6484
+ if (subcommand === "delete") {
6485
+ const unknown = allowed(parsed, ["confirm"]);
6486
+ if (unknown || rest.length > 1)
6487
+ return output.error(unknown ?? "Usage: mnemonik project delete [<project id or name>]"), 2;
6488
+ return deleteProjectCommand(parsed, rest, deps, output);
6489
+ }
6050
6490
  const invalid = allowed(
6051
6491
  parsed,
6052
6492
  subcommand === "ensure" ? ["agent"] : subcommand === "status" ? [] : subcommand === "link" ? ["apply", "non-git", "confirm-mismatch", "replace", "owner"] : ["apply", "non-git", "owner"]
@@ -6393,11 +6833,18 @@ Preview: ${preview.path}
6393
6833
  }
6394
6834
  var serializeReadiness2 = (input) => devReadiness(serializeReadiness(input));
6395
6835
  export {
6836
+ CODEX_SIGNED_IN_MESSAGE,
6837
+ CONNECT_NOT_APPROVED_MESSAGE,
6838
+ alreadyConnectedFolderLine,
6396
6839
  connectFolderPrompt,
6397
6840
  connectedFolderLine,
6398
6841
  help,
6842
+ identityFileKeptLine,
6399
6843
  maintenanceExitCode,
6844
+ projectDeletedLine,
6845
+ projectDeletionWarning,
6400
6846
  removeFolderPrompt,
6401
6847
  removedFolderLine,
6402
- runCli
6848
+ runCli,
6849
+ stillWatchedLine
6403
6850
  };