memgineering 0.27.0 → 0.28.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/index.js CHANGED
@@ -6865,10 +6865,10 @@ var init_cloud_pointer = __esm({
6865
6865
  });
6866
6866
 
6867
6867
  // src/commands/link.ts
6868
- import { readFile as readFile19 } from "fs/promises";
6869
- import { join as join19 } from "path";
6870
- import { Command as Command13 } from "commander";
6871
- import prompts2 from "prompts";
6868
+ import { readFile as readFile20 } from "fs/promises";
6869
+ import { join as join20 } from "path";
6870
+ import { Command as Command14 } from "commander";
6871
+ import prompts3 from "prompts";
6872
6872
  function sameBasename(all, entry) {
6873
6873
  const base2 = (entry.split("/").pop() ?? entry).normalize("NFC").toLowerCase();
6874
6874
  return all.filter((p) => (p.split("/").pop() ?? "").normalize("NFC").toLowerCase() === base2);
@@ -6890,7 +6890,7 @@ function spreadAcrossFolders(rows, max) {
6890
6890
  return picked;
6891
6891
  }
6892
6892
  function linkCommand() {
6893
- return new Command13("link").description("read a notes folder as a brain \u2014 audits its rules first").argument("<path>", "path to the notes folder").option("--yes", "skip the confirmation").option("--dry-run", "show exactly what would be stored, then stop").option(
6893
+ return new Command14("link").description("read a notes folder as a brain \u2014 audits its rules first").argument("<path>", "path to the notes folder").option("--yes", "skip the confirmation").option("--dry-run", "show exactly what would be stored, then stop").option(
6894
6894
  "--observations <dir>",
6895
6895
  "where `remember` writes, relative to the folder",
6896
6896
  DEFAULT_OBSERVATIONS_DIR
@@ -7070,7 +7070,7 @@ async function recordLink(adapter, observationsDir) {
7070
7070
  }
7071
7071
  async function refuseSilentRules(path, denied, excluded, unsupported) {
7072
7072
  const denyEntries = (await LocalFileReadAdapter.readDenyFile(path, ".memgdeny")).entries;
7073
- const rawDenyLines = await readFile19(join19(path, ".memgdeny"), "utf8").then(
7073
+ const rawDenyLines = await readFile20(join20(path, ".memgdeny"), "utf8").then(
7074
7074
  (t) => t.split("\n").map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#"))
7075
7075
  ).catch(() => []);
7076
7076
  const asWritten = (folded) => rawDenyLines.find(
@@ -7216,7 +7216,7 @@ Brain: ${root}`));
7216
7216
  "\n This tool writes into your notes without asking each time \u2014 new\n observations, and changes to the memory block of a note. Every one is\n recorded and reversible: memgineering log \xB7 memgineering undo"
7217
7217
  )
7218
7218
  );
7219
- const { ok } = await prompts2({
7219
+ const { ok } = await prompts3({
7220
7220
  type: "confirm",
7221
7221
  name: "ok",
7222
7222
  message: "Build the index for this folder?",
@@ -7297,9 +7297,9 @@ var init_open_browser = __esm({
7297
7297
  });
7298
7298
 
7299
7299
  // src/commands/login.ts
7300
- import { Command as Command21 } from "commander";
7300
+ import { Command as Command22 } from "commander";
7301
7301
  function loginCommand() {
7302
- return new Command21("login").description("sign in to a hosted brain").option(
7302
+ return new Command22("login").description("sign in to a hosted brain").option(
7303
7303
  "--emit-only",
7304
7304
  "start the sign-in, open the browser, and return immediately \u2014 THE path an agent should use on a user\u2019s behalf; the next command claims the token automatically. Prefer this over the plain form, whose wait for a human click will outlast an agent\u2019s command timeout"
7305
7305
  ).option("--no-browser", "do not try to open a browser \u2014 just print the link").option("--print-url", "print the approval link even when a browser opened").addHelpText(
@@ -7499,9 +7499,9 @@ var push_exports = {};
7499
7499
  __export(push_exports, {
7500
7500
  pushCommand: () => pushCommand
7501
7501
  });
7502
- import { Command as Command36 } from "commander";
7502
+ import { Command as Command37 } from "commander";
7503
7503
  function pushCommand() {
7504
- return new Command36("push").description("upload the brain on this machine to your hosted one").option("--vault <path>", "which local brain to upload").option("--brain <name>", "the hosted brain to upload into (created if it does not exist)").option("--dry-run", "list what would be uploaded and send nothing").action(async (opts) => {
7504
+ return new Command37("push").description("upload the brain on this machine to your hosted one").option("--vault <path>", "which local brain to upload").option("--brain <name>", "the hosted brain to upload into (created if it does not exist)").option("--dry-run", "list what would be uploaded and send nothing").action(async (opts) => {
7505
7505
  const { brain: local } = await resolveBrain(opts.vault);
7506
7506
  const adapter = await openBrainOrExplain(local.root);
7507
7507
  const scan = await adapter.scan();
@@ -7753,8 +7753,8 @@ var init_guard_shell = __esm({
7753
7753
  });
7754
7754
 
7755
7755
  // src/lib/hook-refresh.ts
7756
- import { chmod, lstat as lstat5, readFile as readFile25 } from "fs/promises";
7757
- import { join as join25 } from "path";
7756
+ import { chmod, lstat as lstat5, readFile as readFile26 } from "fs/promises";
7757
+ import { join as join26 } from "path";
7758
7758
  function moveShippedCommands(parsed, moves) {
7759
7759
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
7760
7760
  const settings = parsed;
@@ -7785,10 +7785,10 @@ function moveShippedCommands(parsed, moves) {
7785
7785
  return { next: changed === 0 ? settings : { ...settings, hooks: nextHooks }, changed };
7786
7786
  }
7787
7787
  function withHookFileLock(fn, waitMs = 0) {
7788
- return withPathLock(join25(brandHome(), "hook-files.lock"), fn, waitMs);
7788
+ return withPathLock(join26(brandHome(), "hook-files.lock"), fn, waitMs);
7789
7789
  }
7790
7790
  async function readSettings(path) {
7791
- const raw = await readFile25(path, "utf8").catch(() => null);
7791
+ const raw = await readFile26(path, "utf8").catch(() => null);
7792
7792
  if (raw === null) return void 0;
7793
7793
  try {
7794
7794
  return JSON.parse(raw);
@@ -8024,7 +8024,7 @@ var init_setup_brain = __esm({
8024
8024
  });
8025
8025
 
8026
8026
  // src/commands/setup-signin.ts
8027
- import prompts4 from "prompts";
8027
+ import prompts5 from "prompts";
8028
8028
  async function runSignInStep(opts) {
8029
8029
  const summary = await signInSummary();
8030
8030
  if (summary.signedIn) return { status: "signed-in", displayName: summary.displayName };
@@ -8051,7 +8051,7 @@ async function runSignInStep(opts) {
8051
8051
  }
8052
8052
  if (opts.interactive) {
8053
8053
  printHuman(c.bold("\nmemgineering \u2014 setup\n"));
8054
- const { wants } = await prompts4({
8054
+ const { wants } = await prompts5({
8055
8055
  type: "toggle",
8056
8056
  name: "wants",
8057
8057
  message: "Sign in to a memgineering account first?",
@@ -8181,37 +8181,37 @@ var init_setup_signin = __esm({
8181
8181
  });
8182
8182
 
8183
8183
  // src/lib/next-step.ts
8184
- import { mkdir as mkdir16, readFile as readFile26, writeFile as writeFile14 } from "fs/promises";
8185
- import { join as join26 } from "path";
8184
+ import { mkdir as mkdir17, readFile as readFile27, writeFile as writeFile14 } from "fs/promises";
8185
+ import { join as join27 } from "path";
8186
8186
  function isSetUp(cfg) {
8187
8187
  return Object.keys(cfg.system.installed).length > 0;
8188
8188
  }
8189
8189
  function connectedMarker(at) {
8190
- return join26(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
8190
+ return join27(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
8191
8191
  }
8192
8192
  async function connectedNoticeShown(at) {
8193
- return readFile26(connectedMarker(at), "utf8").then(
8193
+ return readFile27(connectedMarker(at), "utf8").then(
8194
8194
  () => true,
8195
8195
  () => false
8196
8196
  );
8197
8197
  }
8198
8198
  async function markConnectedNoticeShown(at) {
8199
8199
  try {
8200
- await mkdir16(brandHome(), { recursive: true });
8200
+ await mkdir17(brandHome(), { recursive: true });
8201
8201
  await writeFile14(connectedMarker(at), `${(/* @__PURE__ */ new Date()).toISOString()}
8202
8202
  `);
8203
8203
  } catch {
8204
8204
  }
8205
8205
  }
8206
8206
  async function claimFullSetupNotice() {
8207
- const marker = join26(brandHome(), "setup-notice-shown");
8207
+ const marker = join27(brandHome(), "setup-notice-shown");
8208
8208
  try {
8209
- await readFile26(marker, "utf8");
8209
+ await readFile27(marker, "utf8");
8210
8210
  return false;
8211
8211
  } catch {
8212
8212
  }
8213
8213
  try {
8214
- await mkdir16(brandHome(), { recursive: true });
8214
+ await mkdir17(brandHome(), { recursive: true });
8215
8215
  await writeFile14(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
8216
8216
  `);
8217
8217
  } catch {
@@ -8378,7 +8378,7 @@ Synced instead? \`memgineering login\`, then \`memgineering create <name>\`.`;
8378
8378
  // src/lib/provenance.ts
8379
8379
  import { execFile, spawn as spawn2 } from "child_process";
8380
8380
  import { realpath as realpath3 } from "fs/promises";
8381
- import { dirname as dirname9, join as join27, resolve as resolve14 } from "path";
8381
+ import { dirname as dirname9, join as join28, resolve as resolve14 } from "path";
8382
8382
  import { fileURLToPath as fileURLToPath2 } from "url";
8383
8383
  import { promisify } from "util";
8384
8384
  async function detectProvenance(selfPath, opts = {}) {
@@ -8516,7 +8516,7 @@ var init_provenance = __esm({
8516
8516
  kind: "npm",
8517
8517
  bin: "npm",
8518
8518
  prefixArgs: ["prefix", "-g"],
8519
- moduleRoot: (prefix) => process.platform === "win32" ? join27(prefix, "node_modules") : join27(prefix, "lib", "node_modules"),
8519
+ moduleRoot: (prefix) => process.platform === "win32" ? join28(prefix, "node_modules") : join28(prefix, "lib", "node_modules"),
8520
8520
  install: "npm i -g memgineering@latest"
8521
8521
  },
8522
8522
  {
@@ -8553,9 +8553,9 @@ __export(setup_cloud_exports, {
8553
8553
  });
8554
8554
  import { spawn as spawn3 } from "child_process";
8555
8555
  import { realpathSync as realpathSync2 } from "fs";
8556
- import { readdir as readdir9, stat as stat8 } from "fs/promises";
8556
+ import { readdir as readdir9, stat as stat9 } from "fs/promises";
8557
8557
  import { homedir as homedir5 } from "os";
8558
- import { basename as basename4, dirname as dirname10, join as join28, resolve as resolve15, sep as sep4 } from "path";
8558
+ import { basename as basename4, dirname as dirname10, join as join29, resolve as resolve15, sep as sep4 } from "path";
8559
8559
  async function report2(setupToken, state, detail, code) {
8560
8560
  try {
8561
8561
  await apiRequest({
@@ -8573,7 +8573,7 @@ async function report2(setupToken, state, detail, code) {
8573
8573
  }
8574
8574
  async function folderCandidates() {
8575
8575
  const home = homedir5();
8576
- const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join28(home, d));
8576
+ const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join29(home, d));
8577
8577
  const found = [];
8578
8578
  const countNotes = async (dir) => {
8579
8579
  let count = 0;
@@ -8582,7 +8582,7 @@ async function folderCandidates() {
8582
8582
  if (entry.name.startsWith(".")) continue;
8583
8583
  if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) count += 1;
8584
8584
  else if (entry.isDirectory()) {
8585
- const inner = await readdir9(join28(dir, entry.name)).catch(() => []);
8585
+ const inner = await readdir9(join29(dir, entry.name)).catch(() => []);
8586
8586
  count += inner.filter((n) => /\.(md|markdown)$/i.test(n)).length;
8587
8587
  }
8588
8588
  if (count > 999) break;
@@ -8590,7 +8590,7 @@ async function folderCandidates() {
8590
8590
  return count;
8591
8591
  };
8592
8592
  for (const root of roots) {
8593
- const info = await stat8(root).catch(() => null);
8593
+ const info = await stat9(root).catch(() => null);
8594
8594
  if (!info?.isDirectory()) continue;
8595
8595
  const notes = await countNotes(root);
8596
8596
  if (notes > 0) found.push({ path: root, note_count: notes });
@@ -8935,8 +8935,8 @@ function insideFolder(target, folder) {
8935
8935
  return landed === root || landed.startsWith(root + sep4);
8936
8936
  }
8937
8937
  async function askBeforeUpload(q) {
8938
- const { default: prompts6 } = await import("prompts");
8939
- const { yes } = await prompts6({
8938
+ const { default: prompts7 } = await import("prompts");
8939
+ const { yes } = await prompts7({
8940
8940
  type: "confirm",
8941
8941
  name: "yes",
8942
8942
  message: `Upload the notes in ${q.path} to the brain on ${q.email ?? "this account"}?`,
@@ -8974,14 +8974,14 @@ var init_setup_cloud = __esm({
8974
8974
 
8975
8975
  // src/commands/setup.ts
8976
8976
  import { createHash as createHash9 } from "crypto";
8977
- import { access as access2, mkdir as mkdir17, readFile as readFile27 } from "fs/promises";
8978
- import { dirname as dirname11, join as join29 } from "path";
8977
+ import { access as access2, mkdir as mkdir18, readFile as readFile28 } from "fs/promises";
8978
+ import { dirname as dirname11, join as join30 } from "path";
8979
8979
  import { fileURLToPath as fileURLToPath3 } from "url";
8980
- import { Command as Command38 } from "commander";
8981
- import prompts5 from "prompts";
8980
+ import { Command as Command39 } from "commander";
8981
+ import prompts6 from "prompts";
8982
8982
  import { homedir as homedir6 } from "os";
8983
8983
  function setupCommand() {
8984
- return new Command38("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
8984
+ return new Command39("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
8985
8985
  "--wait",
8986
8986
  "with --web: block until the screen is finished, even with nobody watching the terminal"
8987
8987
  ).option(
@@ -9002,9 +9002,11 @@ function setupCommand() {
9002
9002
  "",
9003
9003
  "Chat apps with no shell need no setup. They read and write the brain on your",
9004
9004
  `account (not a folder on this machine) at ${HOSTED_CONNECTOR_URL}:`,
9005
- " ChatGPT: Settings \u2192 Developer mode \u2192 add an MCP server with that URL, sign in.",
9006
9005
  " Claude.ai, Claude Desktop, Cowork: Customize \u2192 Connectors \u2192 Add custom",
9007
- " connector with that URL, sign in."
9006
+ " connector with that URL, sign in.",
9007
+ " ChatGPT: Settings \u2192 Developer mode \u2192 add an MCP server with that URL, sign in",
9008
+ " (paused for now: the connect screen refuses ChatGPT while its notice is",
9009
+ " verified; use Claude or Gemini meanwhile)."
9008
9010
  ].join("\n")
9009
9011
  ).action(async (opts) => {
9010
9012
  const detected = await detectTools();
@@ -9277,7 +9279,7 @@ function setupCommand() {
9277
9279
  }
9278
9280
  });
9279
9281
  if (mode.kind === "human" && brains.length === 0 && !opts.dryRun && isInteractive()) {
9280
- const { path } = await prompts5({
9282
+ const { path } = await prompts6({
9281
9283
  type: "text",
9282
9284
  name: "path",
9283
9285
  message: "Path to your notes folder (blank to skip)"
@@ -9342,7 +9344,7 @@ async function applySetup(choice, dryRun) {
9342
9344
  }
9343
9345
  async function write2() {
9344
9346
  for (const tool of choice.tools) {
9345
- const hubPath = join29(agentHome(), tool.hubFile);
9347
+ const hubPath = join30(agentHome(), tool.hubFile);
9346
9348
  const written = await injectBlock(
9347
9349
  hubPath,
9348
9350
  hub + tailFor(tool, choice.hook),
@@ -9357,8 +9359,8 @@ async function applySetup(choice, dryRun) {
9357
9359
  });
9358
9360
  if (!dryRun && written.after !== null) mark(hubPath, written.after);
9359
9361
  for (const skill of BRAND.subSkills) {
9360
- const content = await loadAsset(join29(skill, "SKILL.md"));
9361
- const path = join29(agentHome(), tool.skillsDir, skill, "SKILL.md");
9362
+ const content = await loadAsset(join30(skill, "SKILL.md"));
9363
+ const path = join30(agentHome(), tool.skillsDir, skill, "SKILL.md");
9362
9364
  const out = await writeIfDifferent(path, content, dryRun);
9363
9365
  results.push({
9364
9366
  tool: tool.label,
@@ -9448,7 +9450,7 @@ async function hooksArmed(tools) {
9448
9450
  const capable = tools.filter((t) => t.hooks && t.hooksFile);
9449
9451
  if (capable.length === 0) return void 0;
9450
9452
  for (const tool of capable) {
9451
- const raw = await readFile27(join29(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
9453
+ const raw = await readFile28(join30(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
9452
9454
  () => ""
9453
9455
  );
9454
9456
  if (hooksFor([tool]).some((spec) => raw.includes(spec.signature))) return "on";
@@ -9468,7 +9470,7 @@ async function resolveMode(opts) {
9468
9470
  return { kind: "human", chosen: true };
9469
9471
  }
9470
9472
  if (!isInteractive() || isJsonMode()) return { kind: "agent", chosen: false };
9471
- const { mode } = await prompts5({
9473
+ const { mode } = await prompts6({
9472
9474
  type: "select",
9473
9475
  name: "mode",
9474
9476
  message: "How would you like to set this up?",
@@ -9488,7 +9490,7 @@ async function detectTools() {
9488
9490
  const home = agentHome();
9489
9491
  const found = [];
9490
9492
  for (const tool of TOOLS) {
9491
- if (await exists2(join29(home, tool.dir))) found.push(tool);
9493
+ if (await exists2(join30(home, tool.dir))) found.push(tool);
9492
9494
  }
9493
9495
  return found;
9494
9496
  }
@@ -9535,7 +9537,7 @@ async function askHuman(detected, opts) {
9535
9537
  printHuman(c.bold("\nmemgineering \u2014 setup\n"));
9536
9538
  printHuman(c.gray(" Found: ") + detected.map((t) => t.label).join(", "));
9537
9539
  printHuman("");
9538
- const answers = await prompts5([
9540
+ const answers = await prompts6([
9539
9541
  {
9540
9542
  type: "multiselect",
9541
9543
  name: "tools",
@@ -9588,10 +9590,10 @@ async function injectBlock(filePath, content, dryRun, weWroteThis) {
9588
9590
  const block = `${BRAND.hubMarkerStart}
9589
9591
  ${content.trimEnd()}
9590
9592
  ${BRAND.hubMarkerEnd}`;
9591
- const existing = await readFile27(filePath, "utf8").catch(() => null);
9593
+ const existing = await readFile28(filePath, "utf8").catch(() => null);
9592
9594
  if (existing === null) {
9593
9595
  if (!dryRun) {
9594
- await mkdir17(dirname11(filePath), { recursive: true });
9596
+ await mkdir18(dirname11(filePath), { recursive: true });
9595
9597
  await writeThenRename(filePath, `${block}
9596
9598
  `);
9597
9599
  }
@@ -9631,11 +9633,11 @@ function looksLikeOurGuide(text) {
9631
9633
  return text.includes("memgineering recall") && text.includes("memgineering remember");
9632
9634
  }
9633
9635
  async function writeIfDifferent(filePath, content, dryRun) {
9634
- const existing = await readFile27(filePath, "utf8").catch(() => null);
9636
+ const existing = await readFile28(filePath, "utf8").catch(() => null);
9635
9637
  const after = fingerprint(content);
9636
9638
  if (existing === content) return { outcome: "unchanged", before: existing, after };
9637
9639
  if (!dryRun) {
9638
- await mkdir17(dirname11(filePath), { recursive: true });
9640
+ await mkdir18(dirname11(filePath), { recursive: true });
9639
9641
  await writeThenRename(filePath, content);
9640
9642
  }
9641
9643
  return { outcome: existing === null ? "injected" : "updated", before: existing, after };
@@ -9652,7 +9654,7 @@ async function refreshOwnHookCommands() {
9652
9654
  const changed = [];
9653
9655
  const skipped = [];
9654
9656
  for (const tool of TOOLS.filter((t) => t.hooks && t.hooksFile && DEFAULT_HOOK_TOOLS.includes(t.id))) {
9655
- const path = join29(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9657
+ const path = join30(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9656
9658
  const outcome = await refreshHookFile(path, moves);
9657
9659
  if (outcome === "changed") changed.push(path);
9658
9660
  else if (outcome === "symlink" || outcome === "refused") skipped.push({ path, why: outcome });
@@ -9679,8 +9681,8 @@ function hooksFor(tools) {
9679
9681
  );
9680
9682
  }
9681
9683
  async function installHook(spec, tool, dryRun) {
9682
- const settingsPath = join29(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9683
- const raw = await readFile27(settingsPath, "utf8").catch(() => null);
9684
+ const settingsPath = join30(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9685
+ const raw = await readFile28(settingsPath, "utf8").catch(() => null);
9684
9686
  let settings = {};
9685
9687
  if (raw !== null) {
9686
9688
  try {
@@ -9722,15 +9724,15 @@ async function installHook(spec, tool, dryRun) {
9722
9724
  }
9723
9725
  const next = { ...settings, hooks: { ...hooks, [spec.event]: existingGroups } };
9724
9726
  if (!dryRun) {
9725
- await mkdir17(dirname11(settingsPath), { recursive: true });
9727
+ await mkdir18(dirname11(settingsPath), { recursive: true });
9726
9728
  await writeThenRename(settingsPath, `${JSON.stringify(next, null, 2)}
9727
9729
  `);
9728
9730
  }
9729
9731
  return raw === null ? "injected" : "updated";
9730
9732
  }
9731
9733
  async function removeHook(spec, tool, dryRun) {
9732
- const settingsPath = join29(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9733
- const raw = await readFile27(settingsPath, "utf8").catch(() => null);
9734
+ const settingsPath = join30(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9735
+ const raw = await readFile28(settingsPath, "utf8").catch(() => null);
9734
9736
  if (raw === null) return "unchanged";
9735
9737
  let parsed;
9736
9738
  try {
@@ -9754,7 +9756,7 @@ async function removeHook(spec, tool, dryRun) {
9754
9756
  return "removed";
9755
9757
  }
9756
9758
  async function loadAsset(relPath) {
9757
- const raw = await readFile27(join29(await assetRoot(), relPath), "utf8");
9759
+ const raw = await readFile28(join30(await assetRoot(), relPath), "utf8");
9758
9760
  if (relPath === BRAND.hubFileName && raw.startsWith("---")) {
9759
9761
  const end = raw.indexOf("\n---", 4);
9760
9762
  if (end !== -1) return raw.slice(end + 4).trimStart();
@@ -9763,12 +9765,12 @@ async function loadAsset(relPath) {
9763
9765
  }
9764
9766
  async function assetRoot() {
9765
9767
  const here = dirname11(fileURLToPath3(import.meta.url));
9766
- const bundled = join29(here, "..", "assets");
9767
- if (await exists2(join29(bundled, BRAND.hubFileName))) return bundled;
9768
+ const bundled = join30(here, "..", "assets");
9769
+ if (await exists2(join30(bundled, BRAND.hubFileName))) return bundled;
9768
9770
  let dir = here;
9769
9771
  for (; ; ) {
9770
- const candidate = join29(dir, "packages", "skills");
9771
- if (await exists2(join29(candidate, BRAND.hubFileName))) return candidate;
9772
+ const candidate = join30(dir, "packages", "skills");
9773
+ if (await exists2(join30(candidate, BRAND.hubFileName))) return candidate;
9772
9774
  const parent = dirname11(dir);
9773
9775
  if (parent === dir) break;
9774
9776
  dir = parent;
@@ -9963,7 +9965,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
9963
9965
  import net from "net";
9964
9966
 
9965
9967
  // src/program.ts
9966
- import { Command as Command46 } from "commander";
9968
+ import { Command as Command47 } from "commander";
9967
9969
 
9968
9970
  // src/help.ts
9969
9971
  init_ui();
@@ -10031,6 +10033,7 @@ var COMMAND_GROUPS = [
10031
10033
  "create",
10032
10034
  "rename",
10033
10035
  "delete-brain",
10036
+ "delete-account",
10034
10037
  "push",
10035
10038
  "pull"
10036
10039
  ]
@@ -11525,6 +11528,11 @@ var YankResponseSchema = z19.object({
11525
11528
  yank_reason: z19.string().max(280).nullable(),
11526
11529
  effective_current_version: SemverSchema
11527
11530
  });
11531
+ var RestorePackageResponseSchema = z19.object({
11532
+ full_slug: FullSlugSchema,
11533
+ status: z19.literal("restored"),
11534
+ restored_at: IsoDateTimeSchema
11535
+ });
11528
11536
  var ResolveVersionQuerySchema = z19.object({
11529
11537
  range: SemverRangeSchema
11530
11538
  });
@@ -11569,7 +11577,15 @@ var UserProfileResponseSchema = PublicUserSchema.extend({
11569
11577
  * responses omit both. Optional so pre-P1 clients ignore them.
11570
11578
  */
11571
11579
  viewer_is_following: z19.boolean().optional(),
11572
- viewer_is_self: z19.boolean().optional()
11580
+ viewer_is_self: z19.boolean().optional(),
11581
+ /**
11582
+ * Whether other people can see this profile (migration 0031). A profile
11583
+ * becomes public with its owner's first public publish, comment or star,
11584
+ * and stays public. Anyone else asking for a non-public profile gets the
11585
+ * unknown-user `404`, so `false` only ever reaches the owner. Absent from
11586
+ * servers that predate it; treat absent as public.
11587
+ */
11588
+ is_public: z19.boolean().optional()
11573
11589
  });
11574
11590
  var UserPackagesQuerySchema = z19.object({
11575
11591
  sort: SortModeSchema.default("stars"),
@@ -11597,6 +11613,81 @@ var ListFollowersResponseSchema = z19.object({
11597
11613
  results: PublicUserSchema.array(),
11598
11614
  next_cursor: z19.string().nullable()
11599
11615
  });
11616
+ var MeResponseSchema = SelfUserSchema.extend({
11617
+ /**
11618
+ * Whether the caller's registry profile is visible to other people
11619
+ * (migration 0031): false until their first public publish, comment or
11620
+ * star. Absent from servers that predate it; treat absent as public.
11621
+ */
11622
+ profile_public: z19.boolean().optional()
11623
+ });
11624
+ var DeleteMeRequestSchema = z19.object({
11625
+ confirm_username: z19.string().min(1).max(32)
11626
+ });
11627
+ var AccountExportUserSchema = z19.object({
11628
+ id: z19.string(),
11629
+ username: z19.string(),
11630
+ display_name: z19.string().nullable(),
11631
+ email: z19.string().nullable(),
11632
+ bio: z19.string().nullable(),
11633
+ avatar_url: z19.string().nullable(),
11634
+ primary_provider: z19.string(),
11635
+ role: z19.string(),
11636
+ created_at: z19.string(),
11637
+ updated_at: z19.string(),
11638
+ last_active_at: z19.string().nullable()
11639
+ });
11640
+ var AccountExportSchema = z19.object({
11641
+ schema_version: z19.literal(1),
11642
+ exported_at: z19.string(),
11643
+ user: AccountExportUserSchema,
11644
+ packages: z19.array(
11645
+ z19.object({
11646
+ id: z19.string(),
11647
+ slug: z19.string(),
11648
+ full_slug: z19.string(),
11649
+ type: z19.string(),
11650
+ tier: z19.string(),
11651
+ title: z19.string(),
11652
+ description: z19.string(),
11653
+ content: z19.string(),
11654
+ current_version: z19.string(),
11655
+ visibility: z19.string(),
11656
+ created_at: z19.string(),
11657
+ updated_at: z19.string(),
11658
+ deleted_at: z19.string().nullable()
11659
+ })
11660
+ ),
11661
+ package_versions: z19.array(
11662
+ z19.object({
11663
+ id: z19.string(),
11664
+ package_id: z19.string(),
11665
+ version: z19.string(),
11666
+ content: z19.string(),
11667
+ review_status: z19.string(),
11668
+ published_at: z19.string(),
11669
+ yanked_at: z19.string().nullable()
11670
+ })
11671
+ ),
11672
+ comments: z19.array(
11673
+ z19.object({
11674
+ id: z19.string(),
11675
+ package_id: z19.string(),
11676
+ body: z19.string(),
11677
+ kind: z19.string(),
11678
+ title: z19.string().nullable(),
11679
+ status: z19.string().nullable(),
11680
+ parent_id: z19.string().nullable(),
11681
+ created_at: z19.string(),
11682
+ edited_at: z19.string().nullable(),
11683
+ deleted_at: z19.string().nullable()
11684
+ })
11685
+ ),
11686
+ stars: z19.array(z19.object({ package_id: z19.string(), created_at: z19.string() })),
11687
+ follows: z19.array(
11688
+ z19.object({ follower_id: z19.string(), following_id: z19.string(), created_at: z19.string() })
11689
+ )
11690
+ });
11600
11691
 
11601
11692
  // ../../packages/contracts/src/context.ts
11602
11693
  import { z as z20 } from "zod";
@@ -12078,6 +12169,23 @@ var KordisClient = class {
12078
12169
  me() {
12079
12170
  return this.request("/api/me");
12080
12171
  }
12172
+ /**
12173
+ * The account's own data — the one registry answer that carries its `email`.
12174
+ * A GET, so it is retried once on a failed fetch like every other GET.
12175
+ */
12176
+ exportMe() {
12177
+ return this.request("/api/me/export");
12178
+ }
12179
+ /**
12180
+ * Delete the signed-in registry account. Irreversible: the server compares
12181
+ * `confirm_username` with the current username and answers 204. The account
12182
+ * is renamed `deleted-<id>`; its published packages and versions stay at their
12183
+ * existing addresses, which still contain the old username. Never retried
12184
+ * (a DELETE), like every write here.
12185
+ */
12186
+ deleteMe(body) {
12187
+ return this.request("/api/me", { method: "DELETE", body });
12188
+ }
12081
12189
  listMyPackages(opts = {}) {
12082
12190
  return this.request("/api/me/packages", {
12083
12191
  query: {
@@ -12109,6 +12217,18 @@ var KordisClient = class {
12109
12217
  deletePackage(username, slug) {
12110
12218
  return this.request(`/api/packages/${seg(username)}/${seg(slug)}`, { method: "DELETE" });
12111
12219
  }
12220
+ /**
12221
+ * Undo a delete. Author only, within 30 days of the deletion and before the
12222
+ * package's content is deleted; refusals are `conflict` (not deleted, too
12223
+ * late, content already deleted), `not_authorized` and `not_found`, each
12224
+ * with a hint.
12225
+ */
12226
+ restorePackage(username, slug) {
12227
+ return this.request(
12228
+ `/api/packages/${seg(username)}/${seg(slug)}/restore`,
12229
+ { method: "POST" }
12230
+ );
12231
+ }
12112
12232
  getContent(username, slug, opts = {}) {
12113
12233
  return this.request(
12114
12234
  `/api/packages/${seg(username)}/${seg(slug)}/content`,
@@ -16890,88 +17010,316 @@ function nextStep3(renamed) {
16890
17010
  return `Other machines that point at this brain keep showing the old name until their next command against it; nothing on them breaks \u2014 the pointer holds the id. \`--brain "${oneLine(renamed.renamed_from, WIDTH.title)}"\` no longer matches anywhere; use the new name or the id.`;
16891
17011
  }
16892
17012
 
16893
- // src/commands/delete-brain.ts
17013
+ // src/commands/delete-account.ts
17014
+ import { Command as Command12 } from "commander";
17015
+ import prompts from "prompts";
16894
17016
  init_errors();
16895
17017
  init_api_client();
16896
- init_cloud_brain();
17018
+
17019
+ // src/lib/account-deletion-marker.ts
17020
+ init_atomic();
17021
+ init_brand();
17022
+ import { mkdir as mkdir12, readFile as readFile18, stat as stat5, unlink as unlink5 } from "fs/promises";
17023
+ import { join as join19 } from "path";
17024
+ import { z as z25 } from "zod";
17025
+ var SideSchema = z25.object({
17026
+ api_url: z25.string().min(1),
17027
+ /** When the delete request was sent — set before it goes, so a lost answer is visible. */
17028
+ requested_at: z25.string().min(1).nullable().default(null),
17029
+ /** When the server confirmed it. Null while that side is still to do. */
17030
+ deleted_at: z25.string().min(1).nullable().default(null)
17031
+ });
17032
+ var MarkerSchema = z25.object({
17033
+ version: z25.literal(1),
17034
+ started_at: z25.string().min(1),
17035
+ /** How the two accounts were matched before anything was deleted. */
17036
+ matched_by: z25.enum(["email", "federation"]),
17037
+ brain: SideSchema.extend({ account_id: z25.string().min(1) }),
17038
+ registry: SideSchema.extend({
17039
+ user_id: z25.string().min(1),
17040
+ username: z25.string().min(1)
17041
+ })
17042
+ });
17043
+ function accountDeletionMarkerPath() {
17044
+ return join19(brandHome(), "delete-account.json");
17045
+ }
17046
+ async function readAccountDeletionMarker() {
17047
+ try {
17048
+ const parsed = MarkerSchema.safeParse(
17049
+ JSON.parse(await readFile18(accountDeletionMarkerPath(), "utf8"))
17050
+ );
17051
+ return parsed.success ? parsed.data : null;
17052
+ } catch {
17053
+ return null;
17054
+ }
17055
+ }
17056
+ async function accountDeletionMarkerExists() {
17057
+ return stat5(accountDeletionMarkerPath()).then(
17058
+ () => true,
17059
+ () => false
17060
+ );
17061
+ }
17062
+ async function saveAccountDeletionMarker(marker) {
17063
+ await mkdir12(brandHome(), { recursive: true });
17064
+ await writeThenRename(accountDeletionMarkerPath(), `${JSON.stringify(marker, null, 2)}
17065
+ `);
17066
+ }
17067
+ async function clearAccountDeletionMarker() {
17068
+ await unlink5(accountDeletionMarkerPath()).catch(() => void 0);
17069
+ }
17070
+
17071
+ // src/commands/delete-account.ts
16897
17072
  init_config();
17073
+ init_credentials();
17074
+ init_pending_login();
17075
+
17076
+ // src/lib/registry-session.ts
17077
+ init_errors();
17078
+ init_credentials();
17079
+ init_version();
17080
+ function registryApiUrl(stored) {
17081
+ const override = process.env["KORDIS_API_URL"];
17082
+ return override !== void 0 && override !== "" ? override : stored;
17083
+ }
17084
+ async function requireRegistryCredentials() {
17085
+ const credentials = await readCredentials();
17086
+ const registry = credentials?.registry;
17087
+ if (!registry) {
17088
+ throw memgError(
17089
+ "not_linked",
17090
+ "this machine holds no registry identity",
17091
+ credentials === null ? "Nothing was sent. Sign in with `memgineering login` \u2014 it links the registry identity at the same time, so one sign-in covers both." : "Nothing was sent. This machine is signed in to a brain, and the registry half of that sign-in is missing \u2014 which is what a credential file written before federation looks like. Run `memgineering login` again; it links the registry identity too."
17092
+ );
17093
+ }
17094
+ return registry;
17095
+ }
17096
+ var RACE_WAIT_MS = 2e3;
17097
+ var RACE_POLL_MS = 50;
17098
+ function lostRace(err) {
17099
+ return err instanceof KordisApiError && err.status === 401 && isRefreshRace({ error: { code: err.code, flags: err.flags } });
17100
+ }
17101
+ async function newerRegistry(sent) {
17102
+ const deadline = Date.now() + RACE_WAIT_MS;
17103
+ for (; ; ) {
17104
+ const registry = (await readCredentials())?.registry;
17105
+ if (registry !== void 0 && registry.refresh_token !== sent) return registry;
17106
+ if (Date.now() >= deadline) return null;
17107
+ await new Promise((resolve17) => setTimeout(resolve17, RACE_POLL_MS));
17108
+ }
17109
+ }
17110
+ async function refreshRegistrySession(stored, opts = {}) {
17111
+ const attempt = async (registry) => {
17112
+ const baseUrl = (opts.apiUrlOf ?? ((r) => registryApiUrl(r.api_url)))(registry);
17113
+ const anonymous = new KordisClient({
17114
+ baseUrl,
17115
+ userAgent: `memgineering/${VERSION}`,
17116
+ ...opts.fetchImpl ? { fetch: opts.fetchImpl } : {},
17117
+ ...opts.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: opts.requestTimeoutMs }
17118
+ });
17119
+ return { registry, baseUrl, tokens: await anonymous.refreshSession(registry.refresh_token) };
17120
+ };
17121
+ try {
17122
+ return await attempt(stored);
17123
+ } catch (err) {
17124
+ if (!lostRace(err)) throw err;
17125
+ const newer = await newerRegistry(stored.refresh_token);
17126
+ if (newer === null) throw err;
17127
+ return attempt(newer);
17128
+ }
17129
+ }
17130
+ function refusal(err) {
17131
+ if (lostRace(err)) {
17132
+ return memgError(
17133
+ "not_authenticated",
17134
+ err.message,
17135
+ "This machine's registry sign-in was refreshed by another request at the same moment, so this command could not use it. Run this command again."
17136
+ );
17137
+ }
17138
+ const busyOrUnreachable = err instanceof KordisNetworkError || err instanceof KordisApiError && (err.status === 429 || err.status >= 500);
17139
+ return (busyOrUnreachable ? adaptRegistryError(err) : null) ?? memgError(
17140
+ "not_linked",
17141
+ `the registry refused this machine's stored sign-in: ${err instanceof Error ? err.message : String(err)}`,
17142
+ "Nothing was sent. The link between this brain and your registry account has expired or been revoked. `memgineering login` re-links both."
17143
+ );
17144
+ }
17145
+ async function openRegistrySession(fetchImpl) {
17146
+ const stored = await requireRegistryCredentials();
17147
+ let refreshed;
17148
+ try {
17149
+ refreshed = await refreshRegistrySession(stored, fetchImpl ? { fetchImpl } : {});
17150
+ } catch (err) {
17151
+ throw refusal(err);
17152
+ }
17153
+ const { registry, baseUrl, tokens } = refreshed;
17154
+ const credentialsRotated = await persistRotatedRefresh(
17155
+ registry,
17156
+ tokens.refresh_token,
17157
+ tokens.session_id
17158
+ );
17159
+ const client = new KordisClient({
17160
+ baseUrl,
17161
+ userAgent: `memgineering/${VERSION}`,
17162
+ getAccessToken: () => tokens.access_token,
17163
+ ...fetchImpl ? { fetch: fetchImpl } : {}
17164
+ });
17165
+ return { client, username: registry.username, apiUrl: baseUrl, credentialsRotated };
17166
+ }
17167
+ async function persistRotatedRefresh(sent, refreshToken, sessionId) {
17168
+ if (refreshToken === sent.refresh_token && sessionId === sent.session_id) return false;
17169
+ try {
17170
+ return await saveRotatedRegistry(sent.refresh_token, {
17171
+ refresh_token: refreshToken,
17172
+ session_id: sessionId
17173
+ });
17174
+ } catch {
17175
+ return false;
17176
+ }
17177
+ }
17178
+
17179
+ // src/commands/delete-account.ts
16898
17180
  init_ui();
16899
- import { Command as Command12 } from "commander";
16900
- import prompts from "prompts";
16901
- function deleteBrainCommand() {
16902
- return new Command12("delete-brain").description("delete a hosted brain and everything in it, for good").argument("<name-or-id>", "which hosted brain to delete, by its name or id").option("--confirm <exact-name>", "the brain\u2019s name, typed exactly, authorising the deletion").addHelpText(
17181
+ var DELETE_ACCOUNT_PHRASE = "delete my account";
17182
+ var SUPPORT_ADDRESS = "support@memgineering.com";
17183
+ function deleteAccountCommand() {
17184
+ return new Command12("delete-account").description("delete your hosted brain account and your registry account, for good").option(
17185
+ "--confirm <phrase>",
17186
+ `the sentence "${DELETE_ACCOUNT_PHRASE}", typed exactly, authorising the deletion`
17187
+ ).addHelpText(
16903
17188
  "after",
16904
- "\nIrreversible, and server-side: the brain, every note in it, its ledger and its\nmeasurements. `memgineering unlink --brain` is the other thing \u2014 it only stops\nthis machine reading a brain and deletes nothing. Run this ONLY when the person\nhas explicitly asked for this brain to be deleted, and pass --confirm with the\nexact name. A wrongly-named brain is a rename, not a delete."
16905
- ).action(async (named, opts) => {
16906
- if (named.startsWith("--")) {
17189
+ `
17190
+ Irreversible, and server-side, in this order: the hosted brain account (every
17191
+ brain on it, every note, every token), then the registry account made by the
17192
+ same Google sign-in (profile, stars, follows, comment text, sign-ins; the account
17193
+ is renamed deleted-\u2026, and published packages stay at their existing addresses).
17194
+ Then this machine is signed out.
17195
+ Folders on this disk are never touched. Run this ONLY when the person has
17196
+ explicitly asked for their account to be deleted, and pass --confirm "${DELETE_ACCOUNT_PHRASE}".
17197
+ If a run stops halfway, run it again: it finishes the side that is left.
17198
+ \`memgineering delete-brain\` deletes one brain and keeps the account;
17199
+ \`memgineering logout\` only signs this machine out.`
17200
+ ).action(async (opts) => {
17201
+ const confirmFlag = opts.confirm;
17202
+ if (confirmFlag !== void 0 && confirmFlag.startsWith("--")) {
16907
17203
  throw memgError(
16908
17204
  "invalid_input",
16909
- `\`delete-brain\` was given the flag \`${oneLine(named, 32)}\` as a brain name`,
16910
- 'Nothing was deleted. The first argument is WHICH BRAIN to delete, and the flag written there was read as its name. Put the name or id first \u2014 `memgineering delete-brain <name-or-id> --confirm "<name>"`. If a brain really is called that, pass its id instead; `memgineering brains` lists them.'
17205
+ `\`--confirm\` was given the flag \`${oneLine(confirmFlag, WIDTH.title)}\` as its sentence`,
17206
+ `Nothing was deleted. \`--confirm\` takes the sentence "${DELETE_ACCOUNT_PHRASE}", and the flag written after it was read as that sentence. Put the sentence, in quotes, immediately after \`--confirm\`.`
16911
17207
  );
16912
17208
  }
16913
- const confirmFlag = opts.confirm;
16914
- if (confirmFlag !== void 0 && confirmFlag.startsWith("--")) {
17209
+ const server = apiUrl();
17210
+ const stored = await readCredentials();
17211
+ const saved = await readAccountDeletionMarker();
17212
+ const marker = saved !== null && (stored === null || stored.account_id === saved.brain.account_id) ? saved : null;
17213
+ const brainDone = marker?.brain.deleted_at != null;
17214
+ const registryDone = marker?.registry.deleted_at != null;
17215
+ if (!brainDone) requireBrainSignIn(stored, server);
17216
+ if (!registryDone && stored?.registry === void 0) {
17217
+ throw noRegistrySignIn(marker);
17218
+ }
17219
+ if (confirmFlag !== void 0 && confirmFlag !== DELETE_ACCOUNT_PHRASE) {
16915
17220
  throw memgError(
16916
17221
  "invalid_input",
16917
- `\`--confirm\` was given the flag \`${oneLine(confirmFlag, WIDTH.title)}\` as a name`,
16918
- "Nothing was deleted. `--confirm` takes the brain\u2019s own name, typed exactly, and the flag written after it was read as that name. Put the name immediately after `--confirm`, then pass the other flag separately."
17222
+ "the confirmation is not the sentence this needs",
17223
+ `Nothing was deleted. The sentence is "${DELETE_ACCOUNT_PHRASE}" \u2014 exactly that, lower case, no punctuation. It deletes the hosted brain account with every brain and note on it, and the registry account; none of it comes back. Pass --confirm "${DELETE_ACCOUNT_PHRASE}" only if the person explicitly asked for their account to be deleted.`
16919
17224
  );
16920
17225
  }
16921
- const server = apiUrl();
16922
- const token = await tokenFor2(server);
16923
- const brains = await listCloudBrains(token, server);
16924
- const target = findCloudBrain(brains, named);
16925
- if (!target) throw noSuchBrain2(named, brains);
16926
- const authorised = await authorise(target, confirmFlag);
16927
- if (authorised === void 0) {
16928
- printHuman(c.yellow("Cancelled \u2014 nothing was deleted."));
16929
- return;
17226
+ if (confirmFlag === void 0 && !isInteractive()) {
17227
+ throw memgError(
17228
+ "invalid_input",
17229
+ "cannot ask for confirmation here \u2014 this is not an interactive terminal",
17230
+ `Nothing was deleted. Two ways forward: run this in a terminal the person can see and let them type "${DELETE_ACCOUNT_PHRASE}"; or, once they have explicitly asked for their account to be deleted, pass --confirm "${DELETE_ACCOUNT_PHRASE}". Deciding for them is the one thing this gate exists to prevent.`
17231
+ );
16930
17232
  }
16931
- const deleted = await deleteCloudBrain({ token, server, id: target.id, authorised });
16932
- const cfg = await loadConfig();
16933
- const pointer = cfg.brain.cloud;
16934
- const pointerCleared = pointer !== void 0 && pointer.id === deleted.deleted && pointer.api_url === server;
16935
- if (pointerCleared) {
16936
- await saveConfig({
16937
- ...cfg,
16938
- brain: {
16939
- brains: cfg.brain.brains,
16940
- ...cfg.brain.default === void 0 ? {} : { default: cfg.brain.default }
16941
- }
16942
- });
17233
+ const brain = brainDone ? null : await brainAccount(stored, marker);
17234
+ const registry = registryDone ? null : await registryAccount(stored?.registry, marker);
17235
+ const match = matchAccounts({ stored, marker, brain, registry });
17236
+ if (confirmFlag === void 0) {
17237
+ const typed = await retypeThePhrase({ brain, registry, marker, match });
17238
+ if (typed !== DELETE_ACCOUNT_PHRASE) {
17239
+ printHuman(c.yellow("Cancelled \u2014 nothing was deleted."));
17240
+ return;
17241
+ }
16943
17242
  }
16944
- const hint = deleted.hint ?? "The brain, its notes and its ledger are gone. `memgineering brains` lists what is left.";
17243
+ const record = marker ?? {
17244
+ version: 1,
17245
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
17246
+ matched_by: match.by === "earlier-run" ? match.matched_by : match.by,
17247
+ brain: {
17248
+ api_url: stored.api_url,
17249
+ account_id: brain.id,
17250
+ requested_at: null,
17251
+ deleted_at: null
17252
+ },
17253
+ registry: {
17254
+ api_url: registry.apiUrl,
17255
+ user_id: registry.id,
17256
+ username: registry.username,
17257
+ requested_at: null,
17258
+ deleted_at: null
17259
+ }
17260
+ };
17261
+ try {
17262
+ await saveAccountDeletionMarker(record);
17263
+ } catch (err) {
17264
+ throw memgError(
17265
+ "filesystem_error",
17266
+ `could not write ${accountDeletionMarkerPath()}`,
17267
+ `Nothing was deleted. This command records its progress there so a run that stops halfway can be finished, and it will not start without it. Free the disk or fix the permissions of that folder, then run this again. Underlying cause: ${err instanceof Error ? err.message : String(err)}`
17268
+ );
17269
+ }
17270
+ if (brain !== null) {
17271
+ record.brain.requested_at = (/* @__PURE__ */ new Date()).toISOString();
17272
+ await saveBestEffort(record);
17273
+ await deleteBrainAccount(stored, registry);
17274
+ record.brain.deleted_at = (/* @__PURE__ */ new Date()).toISOString();
17275
+ await saveBestEffort(record);
17276
+ }
17277
+ if (registry !== null) {
17278
+ record.registry.requested_at = (/* @__PURE__ */ new Date()).toISOString();
17279
+ await saveBestEffort(record);
17280
+ await deleteRegistryAccount(registry);
17281
+ record.registry.deleted_at = (/* @__PURE__ */ new Date()).toISOString();
17282
+ await saveBestEffort(record);
17283
+ }
17284
+ const pointerCleared = await signOutHere(record.brain.api_url);
17285
+ await clearAccountDeletionMarker();
17286
+ const hint = "Both accounts are gone and this machine is signed out. Signing in again with the same Google account creates a new, empty account. Folders on this disk were not touched.";
16945
17287
  printDual({
16946
17288
  json: {
16947
- deleted: { id: deleted.deleted, name: deleted.name },
16948
- /** Null when the server said nothing — which is not the same as zero. */
16949
- notes_deleted: deleted.notes_deleted ?? null,
16950
- /** Whether this machine had been reading it, and has now stopped. */
17289
+ deleted: true,
17290
+ brain: {
17291
+ status: brain === null ? "deleted-earlier" : "deleted",
17292
+ account_id: record.brain.account_id,
17293
+ api_url: record.brain.api_url
17294
+ },
17295
+ registry: {
17296
+ status: registry === null ? "deleted-earlier" : "deleted",
17297
+ user_id: record.registry.user_id,
17298
+ username: record.registry.username,
17299
+ api_url: record.registry.api_url
17300
+ },
17301
+ matched: match,
17302
+ credentials_cleared: true,
16951
17303
  pointer_cleared: pointerCleared,
17304
+ local_folders: "untouched",
16952
17305
  hint
16953
17306
  },
16954
17307
  human: () => {
17308
+ printHuman(c.green("\u2713 Account deleted."));
16955
17309
  printHuman(
16956
- c.green(`\u2713 deleted \u201C${oneLine(deleted.name, WIDTH.title)}\u201D`) + c.gray(` (${oneLine(deleted.deleted, WIDTH.handle)})`)
17310
+ c.gray(
17311
+ brain === null ? " brain the hosted account was deleted by an earlier run" : " brain the hosted account, every brain and note on it, and every token \u2014 gone"
17312
+ )
16957
17313
  );
16958
17314
  printHuman(
16959
17315
  c.gray(
16960
- deleted.notes_deleted === void 0 ? " its notes, its ledger and its measurements went with it" : ` ${deleted.notes_deleted} note(s) went with it, along with its ledger and its measurements`
17316
+ ` registry ${oneLine(record.registry.username, WIDTH.title)} \u2014 ` + (registry === null ? "deleted by an earlier run" : "profile, stars, follows, comment text and sign-ins removed; renamed deleted-\u2026; published packages stay at their existing addresses")
16961
17317
  )
16962
17318
  );
17319
+ printHuman(c.gray(` matched ${matchLine(match)}`));
17320
+ printHuman(c.gray(" here signed out; this machine\u2019s credentials are removed"));
16963
17321
  if (pointerCleared) {
16964
- printHuman(
16965
- c.yellow(
16966
- `
16967
- \u26A0 THIS MACHINE NO LONGER READS A HOSTED BRAIN \u2014 it was reading \u201C${oneLine(deleted.name, WIDTH.title)}\u201D, which is now deleted.`
16968
- )
16969
- );
16970
- printHuman(
16971
- c.gray(
16972
- " `memgineering brains` lists what is left; `use --brain <name>` points at one.\n `recall --local` still reads a folder, if one is linked."
16973
- )
16974
- );
17322
+ printHuman(c.gray(" and it no longer points at a hosted brain"));
16975
17323
  }
16976
17324
  printHuman(c.gray(`
16977
17325
  ${hint}`));
@@ -16979,7 +17327,408 @@ ${hint}`));
16979
17327
  });
16980
17328
  });
16981
17329
  }
16982
- async function tokenFor2(server) {
17330
+ function requireBrainSignIn(stored, server) {
17331
+ if (stored !== null && stored.api_url === server) return;
17332
+ if (stored !== null) {
17333
+ throw memgError(
17334
+ "not_signed_in",
17335
+ `this machine is signed in to ${oneLine(stored.api_url, WIDTH.path)}, not ${oneLine(server, WIDTH.path)}`,
17336
+ "Nothing was deleted. The account to delete is on the server this machine signed in to. If MEMGINEERING_API_URL points somewhere else, unset it and run this again."
17337
+ );
17338
+ }
17339
+ throw memgError(
17340
+ "not_signed_in",
17341
+ "this machine is not signed in to a memgineering account",
17342
+ `Nothing was deleted, and there may be nothing to delete. A brain kept in a folder on this machine has no account \u2014 it is the person\u2019s own files, and this command never touches them; deleting the folder deletes it (\`memgineering unlink <folder>\` first makes this tool forget it). To delete a hosted account, sign in to it first \u2014 \`memgineering login --emit-only\`, show the person the code it prints, and run this again once they approve. Without a sign-in, mail ${SUPPORT_ADDRESS} from the Google address of the account.`
17343
+ );
17344
+ }
17345
+ function noRegistrySignIn(marker) {
17346
+ if (marker !== null) {
17347
+ return memgError(
17348
+ "not_linked",
17349
+ "the brain account is already deleted, and this machine holds no registry sign-in to finish with",
17350
+ `The hosted brain account was deleted by an earlier run of this command and cannot be undone. The registry account \`${oneLine(marker.registry.username, WIDTH.title)}\` still needs deleting, and that needs this machine's registry sign-in, which is gone. Mail ${SUPPORT_ADDRESS} from the Google address of the account, naming \`${oneLine(marker.registry.username, WIDTH.title)}\`, and it is deleted by hand.`
17351
+ );
17352
+ }
17353
+ return memgError(
17354
+ "not_linked",
17355
+ "this machine is signed in to a brain account but holds no registry sign-in",
17356
+ `Nothing was deleted. This command deletes the brain account and the registry account together, and will not delete one alone. The registry half of the sign-in is missing \u2014 which is what a sign-in made before the two were linked looks like. Run \`memgineering logout\` and then \`memgineering login\` (it links the registry account too), then run this again. Or mail ${SUPPORT_ADDRESS} from the Google address of the account.`
17357
+ );
17358
+ }
17359
+ async function brainAccount(stored, marker) {
17360
+ let body;
17361
+ try {
17362
+ body = (await apiRequest({
17363
+ method: "GET",
17364
+ path: "/v1/me",
17365
+ token: stored.token,
17366
+ baseUrl: stored.api_url
17367
+ })).body;
17368
+ } catch (err) {
17369
+ if (!(err instanceof MemgError)) throw err;
17370
+ if (err.code === "not_authenticated" && marker?.brain.requested_at) {
17371
+ throw memgError(
17372
+ "not_authenticated",
17373
+ "this machine\u2019s brain sign-in no longer works, after an earlier run asked for the account to be deleted",
17374
+ `Nothing more was deleted. An earlier run of this command asked the brain server to delete this account at ${marker.brain.requested_at} and did not hear the answer. A sign-in that stops working is what a deleted account looks like, and also what a revoked sign-in looks like, so this command will not guess and delete the registry account on that basis. Mail ${SUPPORT_ADDRESS} from the Google address of the account, naming registry account \`${oneLine(marker.registry.username, WIDTH.title)}\`, and both are finished by hand.`
17375
+ );
17376
+ }
17377
+ if (err.code === "not_authenticated") {
17378
+ const name = stored.registry === void 0 ? "" : ` \`${oneLine(stored.registry.username, WIDTH.title)}\``;
17379
+ const file = await accountDeletionMarkerExists() ? ` A progress file from an earlier run is on disk (${accountDeletionMarkerPath()}) but could not be used for this account.` : "";
17380
+ throw memgError(
17381
+ "not_authenticated",
17382
+ "the brain server no longer accepts this machine\u2019s sign-in",
17383
+ `This run deleted nothing. Either the sign-in was revoked or has expired \u2014 then \`memgineering login --emit-only\`, show the person the code it prints, and run this again once they approve \u2014 or this account was already deleted by an earlier run of this command whose progress file is gone.${file} If that may be so, the registry account${name} may still exist: mail ${SUPPORT_ADDRESS} from the Google address of the account${name === "" ? "" : `, naming${name}`}.`
17384
+ );
17385
+ }
17386
+ if (err.code === "server_unreachable") {
17387
+ throw memgError(
17388
+ "server_unreachable",
17389
+ err.message,
17390
+ `This run deleted nothing \u2014 neither the brain account nor the registry account. The brain server could not be reached: check the connection, or MEMGINEERING_API_URL if the brain server is somewhere else, then run this again.`
17391
+ );
17392
+ }
17393
+ throw new MemgError({
17394
+ code: err.code,
17395
+ message: err.message,
17396
+ hint: `This run deleted nothing \u2014 neither the brain account nor the registry account. ${err.hint}`
17397
+ });
17398
+ }
17399
+ const account = body?.account;
17400
+ if (!account || typeof account.id !== "string") {
17401
+ throw memgError(
17402
+ "server_refused",
17403
+ `${stored.api_url} answered /v1/me without an account`,
17404
+ "Nothing was deleted. This is not an answer a memgineering brain server gives. If MEMGINEERING_API_URL points at something else, unset it and run this again."
17405
+ );
17406
+ }
17407
+ if (account.id !== stored.account_id) {
17408
+ throw mismatch(
17409
+ `this machine's credentials name brain account ${oneLine(stored.account_id, WIDTH.handle)}, and its token belongs to ${oneLine(account.id, WIDTH.handle)}`
17410
+ );
17411
+ }
17412
+ return {
17413
+ id: account.id,
17414
+ email: typeof account.email === "string" && account.email !== "" ? account.email : null,
17415
+ display_name: typeof account.display_name === "string" ? account.display_name : null
17416
+ };
17417
+ }
17418
+ async function registryAccount(snapshot, marker) {
17419
+ let session;
17420
+ try {
17421
+ session = await openRegistrySession();
17422
+ } catch (err) {
17423
+ if (err instanceof MemgError && err.code === "not_linked") {
17424
+ throw memgError(
17425
+ "not_linked",
17426
+ err.message,
17427
+ marker !== null && marker.brain.deleted_at !== null ? `The hosted brain account was deleted by an earlier run and cannot be undone. The registry account \`${oneLine(marker.registry.username, WIDTH.title)}\` still needs deleting, and this machine's registry sign-in no longer works \u2014 if an earlier run's registry request reached the server, the account may already be gone, which ends sign-ins the same way. Mail ${SUPPORT_ADDRESS} from the Google address of the account, naming \`${oneLine(marker.registry.username, WIDTH.title)}\`, and it is finished by hand.` : `Nothing was deleted. The registry sign-in on this machine has expired or been revoked. Run \`memgineering logout\` and then \`memgineering login\` (it links the registry account again), then run this again. Or mail ${SUPPORT_ADDRESS} from the Google address of the account.`
17428
+ );
17429
+ }
17430
+ throw withNothingDeleted(err, marker);
17431
+ }
17432
+ let exported;
17433
+ try {
17434
+ exported = await session.client.exportMe();
17435
+ } catch (err) {
17436
+ throw withNothingDeleted(err, marker);
17437
+ }
17438
+ const user = exported?.user;
17439
+ if (!user || typeof user.id !== "string" || typeof user.username !== "string") {
17440
+ throw memgError(
17441
+ "server_refused",
17442
+ `${session.apiUrl} answered /api/me/export without an account`,
17443
+ "Nothing was deleted. This is not an answer the registry gives. If KORDIS_API_URL points at something else, unset it and run this again."
17444
+ );
17445
+ }
17446
+ if (snapshot.user_id !== user.id) {
17447
+ throw mismatch(
17448
+ `this run began with a registry sign-in for registry account ${oneLine(snapshot.user_id, WIDTH.handle)}, and the registry answered for ${oneLine(user.id, WIDTH.handle)}`
17449
+ );
17450
+ }
17451
+ return {
17452
+ client: session.client,
17453
+ apiUrl: session.apiUrl,
17454
+ id: user.id,
17455
+ username: user.username,
17456
+ email: typeof user.email === "string" && user.email !== "" ? user.email : null,
17457
+ linkedAt: snapshot.linked_at
17458
+ };
17459
+ }
17460
+ function withNothingDeleted(err, marker) {
17461
+ const adapted = err instanceof MemgError ? err : adaptRegistryError(err);
17462
+ if (adapted === null) return err;
17463
+ const prefix = marker !== null && marker.brain.deleted_at !== null ? "Nothing more was deleted: the brain account is already gone, and the registry account is not deleted yet \u2014 run this again once the registry answers." : "Nothing was deleted \u2014 neither the brain account nor the registry account.";
17464
+ return new MemgError({
17465
+ code: adapted.code,
17466
+ message: adapted.message,
17467
+ hint: `${prefix} ${adapted.hint}`
17468
+ });
17469
+ }
17470
+ function matchAccounts(args) {
17471
+ const { marker, brain, registry } = args;
17472
+ if (marker !== null) {
17473
+ if (registry !== null && registry.id !== marker.registry.user_id) {
17474
+ throw mismatch(
17475
+ `an earlier run matched registry account \`${oneLine(marker.registry.username, WIDTH.title)}\`, and this machine's registry sign-in is now for \`${oneLine(registry.username, WIDTH.title)}\``
17476
+ );
17477
+ }
17478
+ return { by: "earlier-run", matched_by: marker.matched_by };
17479
+ }
17480
+ const b = brain;
17481
+ const r = registry;
17482
+ if (b.email !== null && r.email !== null) {
17483
+ if (normaliseEmail(b.email) !== normaliseEmail(r.email)) {
17484
+ throw mismatch(
17485
+ `the brain account's email is ${oneLine(b.email, WIDTH.title)} and the registry account's is ${oneLine(r.email, WIDTH.title)}`
17486
+ );
17487
+ }
17488
+ return { by: "email", email: b.email };
17489
+ }
17490
+ return { by: "federation", linked_at: r.linkedAt };
17491
+ }
17492
+ function normaliseEmail(email) {
17493
+ return email.trim().toLowerCase();
17494
+ }
17495
+ function mismatch(detail) {
17496
+ return memgError(
17497
+ "account_mismatch",
17498
+ `the brain account and the registry account do not look like the same person: ${detail}`,
17499
+ `Nothing was deleted. This command deletes both accounts together and only when they are one person's. Run \`memgineering logout\` and then \`memgineering login\` with the Google account whose accounts should be deleted, then run this again. If they really are one person's, mail ${SUPPORT_ADDRESS} from that Google address.`
17500
+ );
17501
+ }
17502
+ function matchLine(match) {
17503
+ switch (match.by) {
17504
+ case "email":
17505
+ return `same person \u2014 both accounts carry ${oneLine(match.email, WIDTH.title)}`;
17506
+ case "federation":
17507
+ return `same person \u2014 one of the two accounts has no email on record, so the match is this machine's registry sign-in, which the brain sign-in issued for the same Google account (linked ${oneLine(match.linked_at, WIDTH.title)})`;
17508
+ case "earlier-run":
17509
+ return `same person \u2014 matched by ${match.matched_by} before an earlier run deleted anything`;
17510
+ }
17511
+ }
17512
+ async function retypeThePhrase(args) {
17513
+ const { brain, registry, marker, match } = args;
17514
+ printHuman(c.bold("\nDelete your account?"));
17515
+ if (brain !== null) {
17516
+ const who = brain.email ?? brain.display_name ?? brain.id;
17517
+ printHuman(
17518
+ c.gray(
17519
+ ` brain ${oneLine(who, WIDTH.title)} \u2014 every brain on it, every note, every token`
17520
+ )
17521
+ );
17522
+ } else {
17523
+ printHuman(c.gray(" brain already deleted by an earlier run"));
17524
+ }
17525
+ const username = registry?.username ?? marker?.registry.username ?? "";
17526
+ printHuman(
17527
+ c.gray(
17528
+ registry !== null ? ` registry ${oneLine(username, WIDTH.title)} \u2014 profile, stars, follows, comment text, sign-ins` : ` registry ${oneLine(username, WIDTH.title)} \u2014 already deleted by an earlier run`
17529
+ )
17530
+ );
17531
+ printHuman(c.gray(` matched ${matchLine(match)}`));
17532
+ printHuman(
17533
+ c.yellow(
17534
+ "\n None of this comes back. The registry account is renamed deleted-\u2026; published\n packages stay at their existing addresses. Folders on this disk are not\n touched. Signing in again later makes a new, empty account."
17535
+ )
17536
+ );
17537
+ const { typed } = await prompts({
17538
+ type: "text",
17539
+ name: "typed",
17540
+ message: `Type "${DELETE_ACCOUNT_PHRASE}" to delete both accounts`
17541
+ });
17542
+ return typed;
17543
+ }
17544
+ async function deleteBrainAccount(stored, registry) {
17545
+ try {
17546
+ await apiRequest({
17547
+ method: "DELETE",
17548
+ path: "/v1/account",
17549
+ token: stored.token,
17550
+ baseUrl: stored.api_url,
17551
+ body: { confirm: DELETE_ACCOUNT_PHRASE }
17552
+ });
17553
+ return;
17554
+ } catch (err) {
17555
+ if (!(err instanceof MemgError)) throw err;
17556
+ if (err.code === "server_unreachable" && await brainTokenIsDead(stored)) {
17557
+ return;
17558
+ }
17559
+ const registryLine = registry === null ? "" : ` The registry account \`${oneLine(registry.username, WIDTH.title)}\` was not touched.`;
17560
+ if (err.code === "server_unreachable") {
17561
+ throw new MemgError({
17562
+ code: err.code,
17563
+ message: err.message,
17564
+ hint: `The brain server did not confirm the deletion, and the request may have reached it.${registryLine} Check the connection, then run this command again: it asks the server first. If it then says this machine's brain sign-in no longer works, the brain account was deleted \u2014 it will not delete the registry account on that basis; it says how to finish by mail.`
17565
+ });
17566
+ }
17567
+ throw new MemgError({
17568
+ code: err.code,
17569
+ message: err.message,
17570
+ hint: `The brain account was not confirmed deleted.${registryLine} ${err.hint} Then run this again.`
17571
+ });
17572
+ }
17573
+ }
17574
+ async function brainTokenIsDead(stored) {
17575
+ try {
17576
+ await apiRequest({
17577
+ method: "GET",
17578
+ path: "/v1/me",
17579
+ token: stored.token,
17580
+ baseUrl: stored.api_url
17581
+ });
17582
+ return false;
17583
+ } catch (err) {
17584
+ return err instanceof MemgError && err.code === "not_authenticated";
17585
+ }
17586
+ }
17587
+ async function deleteRegistryAccount(registry) {
17588
+ try {
17589
+ await registry.client.deleteMe({ confirm_username: registry.username });
17590
+ return;
17591
+ } catch (err) {
17592
+ if (err instanceof KordisNetworkError && await registrySessionIsDead(registry)) {
17593
+ return;
17594
+ }
17595
+ const adapted = adaptRegistryError(err) ?? memgError(
17596
+ "server_refused",
17597
+ err instanceof Error ? err.message : String(err),
17598
+ "The registry refused the request without saying why."
17599
+ );
17600
+ const name = oneLine(registry.username, WIDTH.title);
17601
+ const unconfirmed = err instanceof KordisNetworkError;
17602
+ const said = err instanceof KordisApiError && err.hint !== void 0 && err.hint !== "" ? ` The registry said: ${err.hint}` : "";
17603
+ throw memgError(
17604
+ "account_partly_deleted",
17605
+ unconfirmed ? `the hosted brain account is deleted; the registry did not confirm deleting \`${name}\` \u2014 ${adapted.code}: ${oneLine(adapted.message, WIDTH.text)}` : `the hosted brain account is deleted; the registry account \`${name}\` is not \u2014 ${adapted.code}: ${oneLine(adapted.message, WIDTH.text)}`,
17606
+ `The brain half is done and cannot be undone. ${unconfirmed ? `The registry did not answer, so it is not known whether \`${name}\` was deleted.` : `The registry account \`${name}\` still exists.`}${said}
17607
+ Run \`memgineering delete-account --confirm "${DELETE_ACCOUNT_PHRASE}"\` again: it skips the brain and deletes only the registry account${unconfirmed ? " if it is still there; if this machine's registry sign-in then no longer works, the account is gone or the sign-in was revoked, and it says how to finish by mail" : ""}. If it keeps failing, mail ${SUPPORT_ADDRESS} from the Google address of the account, naming \`${name}\`.`
17608
+ );
17609
+ }
17610
+ }
17611
+ async function registrySessionIsDead(registry) {
17612
+ try {
17613
+ await registry.client.exportMe();
17614
+ return false;
17615
+ } catch (err) {
17616
+ return err instanceof KordisApiError && err.status === 401;
17617
+ }
17618
+ }
17619
+ async function signOutHere(brainApiUrl) {
17620
+ await clearPendingLogin();
17621
+ await clearSetupFollowUps();
17622
+ await clearCredentials();
17623
+ const cfg = await loadConfig();
17624
+ const pointer = cfg.brain.cloud;
17625
+ if (pointer === void 0 || pointer.api_url !== brainApiUrl) return false;
17626
+ await saveConfig({
17627
+ ...cfg,
17628
+ brain: {
17629
+ brains: cfg.brain.brains,
17630
+ ...cfg.brain.default === void 0 ? {} : { default: cfg.brain.default }
17631
+ }
17632
+ });
17633
+ return true;
17634
+ }
17635
+ async function saveBestEffort(marker) {
17636
+ try {
17637
+ await saveAccountDeletionMarker(marker);
17638
+ } catch {
17639
+ }
17640
+ }
17641
+
17642
+ // src/commands/delete-brain.ts
17643
+ init_errors();
17644
+ init_api_client();
17645
+ init_cloud_brain();
17646
+ init_config();
17647
+ init_ui();
17648
+ import { Command as Command13 } from "commander";
17649
+ import prompts2 from "prompts";
17650
+ function deleteBrainCommand() {
17651
+ return new Command13("delete-brain").description("delete a hosted brain and everything in it, for good").argument("<name-or-id>", "which hosted brain to delete, by its name or id").option("--confirm <exact-name>", "the brain\u2019s name, typed exactly, authorising the deletion").addHelpText(
17652
+ "after",
17653
+ "\nIrreversible, and server-side: the brain, every note in it, its ledger and its\nmeasurements. `memgineering unlink --brain` is the other thing \u2014 it only stops\nthis machine reading a brain and deletes nothing. Run this ONLY when the person\nhas explicitly asked for this brain to be deleted, and pass --confirm with the\nexact name. A wrongly-named brain is a rename, not a delete."
17654
+ ).action(async (named, opts) => {
17655
+ if (named.startsWith("--")) {
17656
+ throw memgError(
17657
+ "invalid_input",
17658
+ `\`delete-brain\` was given the flag \`${oneLine(named, 32)}\` as a brain name`,
17659
+ 'Nothing was deleted. The first argument is WHICH BRAIN to delete, and the flag written there was read as its name. Put the name or id first \u2014 `memgineering delete-brain <name-or-id> --confirm "<name>"`. If a brain really is called that, pass its id instead; `memgineering brains` lists them.'
17660
+ );
17661
+ }
17662
+ const confirmFlag = opts.confirm;
17663
+ if (confirmFlag !== void 0 && confirmFlag.startsWith("--")) {
17664
+ throw memgError(
17665
+ "invalid_input",
17666
+ `\`--confirm\` was given the flag \`${oneLine(confirmFlag, WIDTH.title)}\` as a name`,
17667
+ "Nothing was deleted. `--confirm` takes the brain\u2019s own name, typed exactly, and the flag written after it was read as that name. Put the name immediately after `--confirm`, then pass the other flag separately."
17668
+ );
17669
+ }
17670
+ const server = apiUrl();
17671
+ const token = await tokenFor2(server);
17672
+ const brains = await listCloudBrains(token, server);
17673
+ const target = findCloudBrain(brains, named);
17674
+ if (!target) throw noSuchBrain2(named, brains);
17675
+ const authorised = await authorise(target, confirmFlag);
17676
+ if (authorised === void 0) {
17677
+ printHuman(c.yellow("Cancelled \u2014 nothing was deleted."));
17678
+ return;
17679
+ }
17680
+ const deleted = await deleteCloudBrain({ token, server, id: target.id, authorised });
17681
+ const cfg = await loadConfig();
17682
+ const pointer = cfg.brain.cloud;
17683
+ const pointerCleared = pointer !== void 0 && pointer.id === deleted.deleted && pointer.api_url === server;
17684
+ if (pointerCleared) {
17685
+ await saveConfig({
17686
+ ...cfg,
17687
+ brain: {
17688
+ brains: cfg.brain.brains,
17689
+ ...cfg.brain.default === void 0 ? {} : { default: cfg.brain.default }
17690
+ }
17691
+ });
17692
+ }
17693
+ const hint = deleted.hint ?? "The brain, its notes and its ledger are gone. `memgineering brains` lists what is left.";
17694
+ printDual({
17695
+ json: {
17696
+ deleted: { id: deleted.deleted, name: deleted.name },
17697
+ /** Null when the server said nothing — which is not the same as zero. */
17698
+ notes_deleted: deleted.notes_deleted ?? null,
17699
+ /** Whether this machine had been reading it, and has now stopped. */
17700
+ pointer_cleared: pointerCleared,
17701
+ hint
17702
+ },
17703
+ human: () => {
17704
+ printHuman(
17705
+ c.green(`\u2713 deleted \u201C${oneLine(deleted.name, WIDTH.title)}\u201D`) + c.gray(` (${oneLine(deleted.deleted, WIDTH.handle)})`)
17706
+ );
17707
+ printHuman(
17708
+ c.gray(
17709
+ deleted.notes_deleted === void 0 ? " its notes, its ledger and its measurements went with it" : ` ${deleted.notes_deleted} note(s) went with it, along with its ledger and its measurements`
17710
+ )
17711
+ );
17712
+ if (pointerCleared) {
17713
+ printHuman(
17714
+ c.yellow(
17715
+ `
17716
+ \u26A0 THIS MACHINE NO LONGER READS A HOSTED BRAIN \u2014 it was reading \u201C${oneLine(deleted.name, WIDTH.title)}\u201D, which is now deleted.`
17717
+ )
17718
+ );
17719
+ printHuman(
17720
+ c.gray(
17721
+ " `memgineering brains` lists what is left; `use --brain <name>` points at one.\n `recall --local` still reads a folder, if one is linked."
17722
+ )
17723
+ );
17724
+ }
17725
+ printHuman(c.gray(`
17726
+ ${hint}`));
17727
+ }
17728
+ });
17729
+ });
17730
+ }
17731
+ async function tokenFor2(server) {
16983
17732
  const credentials = await requireCredentials(server).catch((err) => {
16984
17733
  if (err instanceof MemgError && err.code === "not_signed_in") {
16985
17734
  throw memgError(
@@ -17036,7 +17785,7 @@ Delete \u201C${oneLine(target.name, WIDTH.title)}\u201D?`));
17036
17785
  "\n `memgineering unlink --brain` is the other thing: it stops this machine\n reading a brain and deletes nothing. A wrongly-named brain is a rename."
17037
17786
  )
17038
17787
  );
17039
- const { typed } = await prompts({
17788
+ const { typed } = await prompts2({
17040
17789
  type: "text",
17041
17790
  name: "typed",
17042
17791
  message: "Type the brain\u2019s name exactly to delete it"
@@ -17070,21 +17819,21 @@ async function deleteCloudBrain(args) {
17070
17819
 
17071
17820
  // src/commands/curation.ts
17072
17821
  init_src();
17073
- import { stat as stat5 } from "fs/promises";
17822
+ import { stat as stat6 } from "fs/promises";
17074
17823
  import { resolve as resolve8 } from "path";
17075
- import { Command as Command14 } from "commander";
17824
+ import { Command as Command15 } from "commander";
17076
17825
 
17077
17826
  // ../../packages/storage-adapter/src/local-file/ignore-file.ts
17078
17827
  init_atomic();
17079
17828
  init_exclude();
17080
17829
  init_walk();
17081
17830
  import { createHash as createHash6 } from "crypto";
17082
- import { readFile as readFile18 } from "fs/promises";
17831
+ import { readFile as readFile19 } from "fs/promises";
17083
17832
  var FILE = ".memgignore";
17084
17833
  var hashOf = (content) => createHash6("sha256").update(Buffer.from(content, "utf8")).digest("hex");
17085
17834
  async function readIgnoreFileState(root) {
17086
17835
  try {
17087
- const content = await readFile18(resolveInside(root, FILE), "utf8");
17836
+ const content = await readFile19(resolveInside(root, FILE), "utf8");
17088
17837
  return { content, exists: true, hash: hashOf(content) };
17089
17838
  } catch (err) {
17090
17839
  if (err?.code === "ENOENT") {
@@ -17174,7 +17923,7 @@ init_vault();
17174
17923
  init_ui();
17175
17924
  init_link();
17176
17925
  function lifecycleCommand(action) {
17177
- return new Command14(action).description(
17926
+ return new Command15(action).description(
17178
17927
  action === "retire" ? "mark a memory as no longer current \u2014 the note itself is not changed" : "mark a retired memory as current again"
17179
17928
  ).argument("<ref>", "handle, id, path, or exact title").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (ref, opts) => {
17180
17929
  const where = await resolveTarget({
@@ -17261,7 +18010,7 @@ async function lifecycleLocal(vault, action, ref, reason) {
17261
18010
  return { entry, record };
17262
18011
  }
17263
18012
  function ignoreCommand(mode) {
17264
- return new Command14(mode).description(
18013
+ return new Command15(mode).description(
17265
18014
  mode === "exclude" ? "stop reading a note entirely \u2014 the note itself is not changed" : "read a note again after excluding it"
17266
18015
  ).argument("<path>", "path inside the brain, e.g. decisions/launch-date.md").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the note is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(async (path, opts) => {
17267
18016
  const rationale = sanitizeRationale(opts.reason);
@@ -17386,7 +18135,7 @@ async function mustMatchAFile(root, adapter, path) {
17386
18135
  const wanted = path.normalize("NFC").replace(/^\/+/, "");
17387
18136
  const absolute = resolve8(root, wanted);
17388
18137
  if (isInside(absolute, root)) {
17389
- const found = await stat5(absolute).catch(() => null);
18138
+ const found = await stat6(absolute).catch(() => null);
17390
18139
  if (found?.isFile()) return wanted;
17391
18140
  }
17392
18141
  const hits = sameBasename(await adapter.list(""), path);
@@ -17403,14 +18152,14 @@ var unexcludeCommand = () => ignoreCommand("unexclude");
17403
18152
 
17404
18153
  // src/commands/scope-validity.ts
17405
18154
  init_src();
17406
- import { Command as Command15 } from "commander";
18155
+ import { Command as Command16 } from "commander";
17407
18156
  init_errors();
17408
18157
  init_index_build();
17409
18158
  init_cloud_brain();
17410
18159
  init_vault();
17411
18160
  init_ui();
17412
18161
  function rescopeCommand() {
17413
- return new Command15("rescope").description("change which project a memory binds in \u2014 nothing it says changes").argument("<ref>", "handle, id, path, or exact title").option("--scope <name>", "the project it binds in from now on").option("--global", "it binds everywhere \u2014 say this rather than leaving the scope off").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
18162
+ return new Command16("rescope").description("change which project a memory binds in \u2014 nothing it says changes").argument("<ref>", "handle, id, path, or exact title").option("--scope <name>", "the project it binds in from now on").option("--global", "it binds everywhere \u2014 say this rather than leaving the scope off").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
17414
18163
  async (ref, opts) => {
17415
18164
  const scope = scopeAskedFor(opts);
17416
18165
  const where = await resolveTarget({
@@ -17446,7 +18195,7 @@ Undo: \`${undoHint(record.op_id, opts)}\``));
17446
18195
  );
17447
18196
  }
17448
18197
  function expireCommand() {
17449
- return new Command15("expire").description("give a memory an end date \u2014 it is NOT retired, and recall still returns it").argument("<ref>", "handle, id, path, or exact title").requiredOption("--until <date>", "the day it stops being current, e.g. 2026-10-31").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
18198
+ return new Command16("expire").description("give a memory an end date \u2014 it is NOT retired, and recall still returns it").argument("<ref>", "handle, id, path, or exact title").requiredOption("--until <date>", "the day it stops being current, e.g. 2026-10-31").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
17450
18199
  async (ref, opts) => {
17451
18200
  const where = await resolveTarget({
17452
18201
  ...opts.local === void 0 ? {} : { local: opts.local },
@@ -17665,13 +18414,13 @@ Undo: \`memgineering undo ${result.op_id}\``));
17665
18414
 
17666
18415
  // src/commands/evidence.ts
17667
18416
  init_src();
17668
- import { Command as Command16 } from "commander";
18417
+ import { Command as Command17 } from "commander";
17669
18418
  init_index_build();
17670
18419
  init_cloud_brain();
17671
18420
  init_vault();
17672
18421
  init_ui();
17673
18422
  function evidenceCommand() {
17674
- return new Command16("evidence").description("how much a memory has been used, and why it last changed").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
18423
+ return new Command17("evidence").description("how much a memory has been used, and why it last changed").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
17675
18424
  "--brain <name>",
17676
18425
  "read from one existing hosted brain by name or id, without changing the pointer"
17677
18426
  ).action(async (ref, opts) => {
@@ -17960,9 +18709,9 @@ function ago(iso, now) {
17960
18709
  init_brand();
17961
18710
  init_config();
17962
18711
  import { createHash as createHash7 } from "crypto";
17963
- import { mkdir as mkdir12, readdir as readdir8, stat as stat6, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
17964
- import { dirname as dirname6, isAbsolute as isAbsolute3, join as join20, resolve as resolve9 } from "path";
17965
- import { Command as Command17 } from "commander";
18712
+ import { mkdir as mkdir13, readdir as readdir8, stat as stat7, unlink as unlink6, writeFile as writeFile10 } from "fs/promises";
18713
+ import { dirname as dirname6, isAbsolute as isAbsolute3, join as join21, resolve as resolve9 } from "path";
18714
+ import { Command as Command18 } from "commander";
17966
18715
 
17967
18716
  // src/lib/rules.ts
17968
18717
  init_src();
@@ -18034,7 +18783,7 @@ init_deadline();
18034
18783
  init_cloud_brain();
18035
18784
  init_vault();
18036
18785
  function guardCommand() {
18037
- return new Command17("guard").description("hook entry \u2014 show the user\u2019s rules before the first edit of a session").option("--context-dir <path>", "where the work is happening, when stdin does not say").option("--at <event>", "pre-tool-use | stop", "pre-tool-use").action(async (opts) => {
18786
+ return new Command18("guard").description("hook entry \u2014 show the user\u2019s rules before the first edit of a session").option("--context-dir <path>", "where the work is happening, when stdin does not say").option("--at <event>", "pre-tool-use | stop", "pre-tool-use").action(async (opts) => {
18038
18787
  try {
18039
18788
  const out = await withDeadline(HOOK_DEADLINE_MS, async () => {
18040
18789
  const payload = await readPayload();
@@ -18181,7 +18930,7 @@ async function readPayload() {
18181
18930
  return null;
18182
18931
  }
18183
18932
  }
18184
- var guardDir = () => join20(brandHome(), "guard");
18933
+ var guardDir = () => join21(brandHome(), "guard");
18185
18934
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
18186
18935
  "Write",
18187
18936
  "Edit",
@@ -18202,7 +18951,7 @@ async function misdeliveryNotice(toolName, filePath, sessionId) {
18202
18951
  return 'This file is this harness\u2019s own memory \u2014 no other tool the user works with will ever read it. Durable facts about the user belong in their brain, which every tool shares: memgineering remember "<the fact>" --reason "<why>" (add --rule if they stated a standing rule). Write it there instead, or in addition.';
18203
18952
  }
18204
18953
  function markerFor(sessionId, brain) {
18205
- return join20(
18954
+ return join21(
18206
18955
  guardDir(),
18207
18956
  `${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
18208
18957
  );
@@ -18210,7 +18959,7 @@ function markerFor(sessionId, brain) {
18210
18959
  async function claimSession(sessionId, brain) {
18211
18960
  const marker = markerFor(sessionId, brain);
18212
18961
  try {
18213
- await mkdir12(guardDir(), { recursive: true, mode: 448 });
18962
+ await mkdir13(guardDir(), { recursive: true, mode: 448 });
18214
18963
  await writeFile10(marker, "", { flag: "wx", mode: 384 });
18215
18964
  } catch {
18216
18965
  return false;
@@ -18219,10 +18968,10 @@ async function claimSession(sessionId, brain) {
18219
18968
  return true;
18220
18969
  }
18221
18970
  async function releaseSession(sessionId, brain) {
18222
- await unlink5(markerFor(sessionId, brain)).catch(() => void 0);
18971
+ await unlink6(markerFor(sessionId, brain)).catch(() => void 0);
18223
18972
  }
18224
18973
  async function claimExists(sessionId, brain) {
18225
- return stat6(markerFor(sessionId, brain)).then(() => true).catch(() => false);
18974
+ return stat7(markerFor(sessionId, brain)).then(() => true).catch(() => false);
18226
18975
  }
18227
18976
  var MARKER_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
18228
18977
  async function pruneOldMarkers() {
@@ -18230,10 +18979,10 @@ async function pruneOldMarkers() {
18230
18979
  const dir = guardDir();
18231
18980
  const now = Date.now();
18232
18981
  for (const name of await readdir8(dir)) {
18233
- const path = join20(dir, name);
18982
+ const path = join21(dir, name);
18234
18983
  if (!isAbsolute3(path)) continue;
18235
- const info = await stat6(path).catch(() => null);
18236
- if (info && now - info.mtimeMs > MARKER_TTL_MS) await unlink5(path).catch(() => void 0);
18984
+ const info = await stat7(path).catch(() => null);
18985
+ if (info && now - info.mtimeMs > MARKER_TTL_MS) await unlink6(path).catch(() => void 0);
18237
18986
  }
18238
18987
  await pruneFast(now);
18239
18988
  } catch {
@@ -18277,15 +19026,15 @@ init_link();
18277
19026
  // src/commands/log.ts
18278
19027
  init_src();
18279
19028
  init_walk();
18280
- import { lstat as lstat4, readFile as readFile20 } from "fs/promises";
18281
- import { Command as Command19 } from "commander";
19029
+ import { lstat as lstat4, readFile as readFile21 } from "fs/promises";
19030
+ import { Command as Command20 } from "commander";
18282
19031
  init_errors();
18283
19032
  init_cloud_brain();
18284
19033
 
18285
19034
  // src/commands/retract.ts
18286
19035
  init_errors();
18287
19036
  init_cloud_brain();
18288
- import { Command as Command18 } from "commander";
19037
+ import { Command as Command19 } from "commander";
18289
19038
  init_vault();
18290
19039
  init_ui();
18291
19040
  var CONTENT_VERBS = /* @__PURE__ */ new Set([
@@ -18356,7 +19105,7 @@ function parseInstant(flag2, raw) {
18356
19105
  return { ms, iso: new Date(ms).toISOString() };
18357
19106
  }
18358
19107
  function retractCommand() {
18359
- return new Command18("retract").description(
19108
+ return new Command19("retract").description(
18360
19109
  "retire every memory one writer or one stretch of time wrote \u2014 shows the set first; --yes applies it"
18361
19110
  ).option("--actor <actor>", 'writes recorded under this actor, e.g. "mcp:ChatGPT" or "agent"').option(
18362
19111
  "--token <id>",
@@ -18571,7 +19320,7 @@ ${failed.length} could not be retired:`);
18571
19320
  init_vault();
18572
19321
  init_ui();
18573
19322
  function logCommand() {
18574
- return new Command19("log").description("what changed in this brain, and what can still be undone").option("--limit <n>", "how many operations", (v) => parseInt(v, 10), 20).option("--verbose", "include hashes and reverse patches").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
19323
+ return new Command20("log").description("what changed in this brain, and what can still be undone").option("--limit <n>", "how many operations", (v) => parseInt(v, 10), 20).option("--verbose", "include hashes and reverse patches").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
18575
19324
  "--brain <name>",
18576
19325
  "read one existing hosted brain by name or id, without changing the pointer"
18577
19326
  ).option("--actor <actor>", 'only operations recorded under this actor, e.g. "mcp:ChatGPT" or "agent"').option("--token <id>", "only operations made with this token (id or 5+ char prefix) \u2014 hosted brains").option("--since <when>", "only operations at or after this date / ISO instant").option("--until <when>", "only operations before this date / ISO instant").action(async (opts) => {
@@ -18843,16 +19592,16 @@ async function readOrNull(root, path) {
18843
19592
  if (absolute === null) return null;
18844
19593
  const info = await lstat4(absolute).catch(() => null);
18845
19594
  if (!info || !info.isFile()) return null;
18846
- return readFile20(absolute, "utf8").catch(() => null);
19595
+ return readFile21(absolute, "utf8").catch(() => null);
18847
19596
  }
18848
19597
 
18849
19598
  // src/commands/project.ts
18850
19599
  init_errors();
18851
19600
  import { resolve as resolve10 } from "path";
18852
- import { Command as Command20 } from "commander";
19601
+ import { Command as Command21 } from "commander";
18853
19602
  init_ui();
18854
19603
  function projectCommand() {
18855
- return new Command20("project").description("show or set what the work in this folder is called").argument("[name]", "what to call it \u2014 omit to show what this folder answers to").option("--here <dir>", "act on this directory instead of the current one").option("--unset", "forget the name set here and go back to what the repository says").addHelpText(
19604
+ return new Command21("project").description("show or set what the work in this folder is called").argument("[name]", "what to call it \u2014 omit to show what this folder answers to").option("--here <dir>", "act on this directory instead of the current one").option("--unset", "forget the name set here and go back to what the repository says").addHelpText(
18856
19605
  "after",
18857
19606
  [
18858
19607
  "",
@@ -18979,115 +19728,10 @@ init_api_client();
18979
19728
  init_credentials();
18980
19729
  init_config();
18981
19730
  init_pending_login();
18982
- import { Command as Command22 } from "commander";
18983
-
18984
- // src/lib/registry-session.ts
18985
- init_errors();
18986
- init_credentials();
18987
- init_version();
18988
- function registryApiUrl(stored) {
18989
- const override = process.env["KORDIS_API_URL"];
18990
- return override !== void 0 && override !== "" ? override : stored;
18991
- }
18992
- async function requireRegistryCredentials() {
18993
- const credentials = await readCredentials();
18994
- const registry = credentials?.registry;
18995
- if (!registry) {
18996
- throw memgError(
18997
- "not_linked",
18998
- "this machine holds no registry identity",
18999
- credentials === null ? "Nothing was sent. Sign in with `memgineering login` \u2014 it links the registry identity at the same time, so one sign-in covers both." : "Nothing was sent. This machine is signed in to a brain, and the registry half of that sign-in is missing \u2014 which is what a credential file written before federation looks like. Run `memgineering login` again; it links the registry identity too."
19000
- );
19001
- }
19002
- return registry;
19003
- }
19004
- var RACE_WAIT_MS = 2e3;
19005
- var RACE_POLL_MS = 50;
19006
- function lostRace(err) {
19007
- return err instanceof KordisApiError && err.status === 401 && isRefreshRace({ error: { code: err.code, flags: err.flags } });
19008
- }
19009
- async function newerRegistry(sent) {
19010
- const deadline = Date.now() + RACE_WAIT_MS;
19011
- for (; ; ) {
19012
- const registry = (await readCredentials())?.registry;
19013
- if (registry !== void 0 && registry.refresh_token !== sent) return registry;
19014
- if (Date.now() >= deadline) return null;
19015
- await new Promise((resolve17) => setTimeout(resolve17, RACE_POLL_MS));
19016
- }
19017
- }
19018
- async function refreshRegistrySession(stored, opts = {}) {
19019
- const attempt = async (registry) => {
19020
- const baseUrl = (opts.apiUrlOf ?? ((r) => registryApiUrl(r.api_url)))(registry);
19021
- const anonymous = new KordisClient({
19022
- baseUrl,
19023
- userAgent: `memgineering/${VERSION}`,
19024
- ...opts.fetchImpl ? { fetch: opts.fetchImpl } : {},
19025
- ...opts.requestTimeoutMs === void 0 ? {} : { requestTimeoutMs: opts.requestTimeoutMs }
19026
- });
19027
- return { registry, baseUrl, tokens: await anonymous.refreshSession(registry.refresh_token) };
19028
- };
19029
- try {
19030
- return await attempt(stored);
19031
- } catch (err) {
19032
- if (!lostRace(err)) throw err;
19033
- const newer = await newerRegistry(stored.refresh_token);
19034
- if (newer === null) throw err;
19035
- return attempt(newer);
19036
- }
19037
- }
19038
- function refusal(err) {
19039
- if (lostRace(err)) {
19040
- return memgError(
19041
- "not_authenticated",
19042
- err.message,
19043
- "This machine's registry sign-in was refreshed by another request at the same moment, so this command could not use it. Run this command again."
19044
- );
19045
- }
19046
- const busyOrUnreachable = err instanceof KordisNetworkError || err instanceof KordisApiError && (err.status === 429 || err.status >= 500);
19047
- return (busyOrUnreachable ? adaptRegistryError(err) : null) ?? memgError(
19048
- "not_linked",
19049
- `the registry refused this machine's stored sign-in: ${err instanceof Error ? err.message : String(err)}`,
19050
- "Nothing was sent. The link between this brain and your registry account has expired or been revoked. `memgineering login` re-links both."
19051
- );
19052
- }
19053
- async function openRegistrySession(fetchImpl) {
19054
- const stored = await requireRegistryCredentials();
19055
- let refreshed;
19056
- try {
19057
- refreshed = await refreshRegistrySession(stored, fetchImpl ? { fetchImpl } : {});
19058
- } catch (err) {
19059
- throw refusal(err);
19060
- }
19061
- const { registry, baseUrl, tokens } = refreshed;
19062
- const credentialsRotated = await persistRotatedRefresh(
19063
- registry,
19064
- tokens.refresh_token,
19065
- tokens.session_id
19066
- );
19067
- const client = new KordisClient({
19068
- baseUrl,
19069
- userAgent: `memgineering/${VERSION}`,
19070
- getAccessToken: () => tokens.access_token,
19071
- ...fetchImpl ? { fetch: fetchImpl } : {}
19072
- });
19073
- return { client, username: registry.username, apiUrl: baseUrl, credentialsRotated };
19074
- }
19075
- async function persistRotatedRefresh(sent, refreshToken, sessionId) {
19076
- if (refreshToken === sent.refresh_token && sessionId === sent.session_id) return false;
19077
- try {
19078
- return await saveRotatedRegistry(sent.refresh_token, {
19079
- refresh_token: refreshToken,
19080
- session_id: sessionId
19081
- });
19082
- } catch {
19083
- return false;
19084
- }
19085
- }
19086
-
19087
- // src/commands/logout.ts
19731
+ import { Command as Command23 } from "commander";
19088
19732
  init_ui();
19089
19733
  function logoutCommand() {
19090
- return new Command22("logout").description("sign out of your account \u2014 the pointer and your notes stay").option("--all", "revoke every token on the account, not just this machine\u2019s").option(
19734
+ return new Command23("logout").description("sign out of your account \u2014 the pointer and your notes stay").option("--all", "revoke every token on the account, not just this machine\u2019s").option(
19091
19735
  "--local",
19092
19736
  "forget the token here without telling the server \u2014 only for a server you cannot reach"
19093
19737
  ).action(async (opts) => {
@@ -19096,9 +19740,18 @@ function logoutCommand() {
19096
19740
  await clearPendingLogin();
19097
19741
  await clearSetupFollowUps();
19098
19742
  if (!credentials) {
19743
+ const hint = opts.all ? NOT_SIGNED_IN_ALL_HINT : void 0;
19099
19744
  printDual({
19100
- json: { signed_out: true, was_signed_in: false, api_url: base2 },
19101
- human: () => printHuman("Not signed in \u2014 nothing to do.")
19745
+ json: {
19746
+ signed_out: true,
19747
+ was_signed_in: false,
19748
+ api_url: base2,
19749
+ ...hint ? { hint } : {}
19750
+ },
19751
+ human: () => {
19752
+ printHuman("Not signed in \u2014 nothing to do.");
19753
+ if (hint) printHuman(c.gray(` ${hint}`));
19754
+ }
19102
19755
  });
19103
19756
  return;
19104
19757
  }
@@ -19118,6 +19771,8 @@ function logoutCommand() {
19118
19771
  return;
19119
19772
  }
19120
19773
  let revoked = null;
19774
+ let registrySessions = null;
19775
+ let revokedBefore = null;
19121
19776
  try {
19122
19777
  if (opts.all) {
19123
19778
  const res = await apiRequest({
@@ -19127,6 +19782,8 @@ function logoutCommand() {
19127
19782
  baseUrl: credentials.api_url
19128
19783
  });
19129
19784
  revoked = res.body?.revoked ?? null;
19785
+ registrySessions = readRegistrySessions(res.body?.registry_sessions);
19786
+ revokedBefore = typeof res.body?.revoked_before === "string" ? res.body.revoked_before : null;
19130
19787
  } else {
19131
19788
  const me = await apiRequest({
19132
19789
  method: "GET",
@@ -19156,7 +19813,7 @@ function logoutCommand() {
19156
19813
  }
19157
19814
  }
19158
19815
  const registry = (await readCredentials())?.registry ?? credentials.registry;
19159
- const registryRevoked = await revokeRegistrySession(registry);
19816
+ const registryRevoked = registrySessions?.status === "revoked" ? true : await revokeRegistrySession(registry);
19160
19817
  await clearCredentials();
19161
19818
  printDual({
19162
19819
  json: {
@@ -19165,7 +19822,9 @@ function logoutCommand() {
19165
19822
  revoked_on_server: true,
19166
19823
  revoked,
19167
19824
  all: opts.all === true,
19168
- api_url: credentials.api_url
19825
+ api_url: credentials.api_url,
19826
+ ...registrySessions ? { registry_sessions: registrySessions } : {},
19827
+ ...revokedBefore ? { revoked_before: revokedBefore } : {}
19169
19828
  },
19170
19829
  human: () => {
19171
19830
  printHuman(opts.all ? "Signed out everywhere." : "Signed out.");
@@ -19174,6 +19833,8 @@ function logoutCommand() {
19174
19833
  opts.all ? " Every token on the account is dead. `memgineering login` starts a new one." : " This machine\u2019s token is revoked. Other machines are unaffected."
19175
19834
  )
19176
19835
  );
19836
+ const registryLine = registrySessions ? registrySessionsLine(registrySessions) : null;
19837
+ if (registryLine !== null) printHuman(registryLine);
19177
19838
  printHuman(
19178
19839
  c.gray(
19179
19840
  " Your notes are untouched: signing out takes a token, not a brain.\n This machine still remembers which brain it was reading, so `memgineering\n login` picks up where you left off. `memgineering unlink --brain` forgets it."
@@ -19183,6 +19844,34 @@ function logoutCommand() {
19183
19844
  });
19184
19845
  });
19185
19846
  }
19847
+ var NOT_SIGNED_IN_ALL_HINT = 'Nothing is signed in on this machine, so nothing could be ended from here. To disconnect everything, press "disconnect everything" on the website, or run `memgineering login` and then `memgineering logout --all`.';
19848
+ function readRegistrySessions(raw) {
19849
+ if (typeof raw !== "object" || raw === null) return null;
19850
+ const r = raw;
19851
+ if (r["status"] === "revoked" && typeof r["count"] === "number") {
19852
+ return { status: "revoked", count: r["count"] };
19853
+ }
19854
+ if (r["status"] === "not_linked") return { status: "not_linked" };
19855
+ if (r["status"] === "unreachable" && typeof r["hint"] === "string") {
19856
+ return { status: "unreachable", hint: r["hint"] };
19857
+ }
19858
+ return null;
19859
+ }
19860
+ function registrySessionsLine(r) {
19861
+ switch (r.status) {
19862
+ case "revoked":
19863
+ if (r.count === 0) return null;
19864
+ return c.gray(
19865
+ ` Registry sign-ins ended too: ${r.count}. Anything approved before now cannot sign back in.`
19866
+ );
19867
+ case "not_linked":
19868
+ return c.gray(
19869
+ " No registry account is linked to this Google account; nothing to end there."
19870
+ );
19871
+ case "unreachable":
19872
+ return c.yellow(` Registry sign-ins could not be ended: ${oneLineWhole(r.hint)}`);
19873
+ }
19874
+ }
19186
19875
  async function revokeRegistrySession(reg) {
19187
19876
  if (!reg) return false;
19188
19877
  try {
@@ -19206,12 +19895,12 @@ async function revokeRegistrySession(reg) {
19206
19895
 
19207
19896
  // src/commands/onboard.ts
19208
19897
  init_src();
19209
- import { Command as Command23 } from "commander";
19898
+ import { Command as Command24 } from "commander";
19210
19899
  init_cloud_brain();
19211
19900
  init_vault();
19212
19901
  init_ui();
19213
19902
  function onboardCommand() {
19214
- return new Command23("onboard").description("what to ask so the brain knows who it belongs to").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
19903
+ return new Command24("onboard").description("what to ask so the brain knows who it belongs to").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
19215
19904
  "--brain <name>",
19216
19905
  "read one existing hosted brain by name or id, without changing the pointer"
19217
19906
  ).action(async (opts) => {
@@ -19357,11 +20046,11 @@ init_api_client();
19357
20046
  init_config();
19358
20047
  init_cloud_brain();
19359
20048
  init_ui();
19360
- import { mkdir as mkdir13, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
19361
- import { dirname as dirname7, isAbsolute as isAbsolute4, join as join21, resolve as resolve11, sep as sep2 } from "path";
19362
- import { Command as Command24 } from "commander";
20049
+ import { mkdir as mkdir14, readFile as readFile22, writeFile as writeFile11 } from "fs/promises";
20050
+ import { dirname as dirname7, isAbsolute as isAbsolute4, join as join22, resolve as resolve11, sep as sep2 } from "path";
20051
+ import { Command as Command25 } from "commander";
19363
20052
  function pullCommand() {
19364
- return new Command24("pull").description("download your hosted brain to a folder on this machine").argument("[destination]", "folder to write into (created if missing)").option("--brain <name>", "which hosted brain to download").option("--dry-run", "say what would be written and write nothing").action(async (destination, opts) => {
20053
+ return new Command25("pull").description("download your hosted brain to a folder on this machine").argument("[destination]", "folder to write into (created if missing)").option("--brain <name>", "which hosted brain to download").option("--dry-run", "say what would be written and write nothing").action(async (destination, opts) => {
19365
20054
  const { brain: source, others } = await resolveSource(opts.brain);
19366
20055
  const target = resolve11(destination ?? `./${oneLine(source.name, WIDTH.title)}`);
19367
20056
  const first = await cloudListNotes(source, { limit: 1 });
@@ -19426,7 +20115,7 @@ function pullCommand() {
19426
20115
  already.push(note.path);
19427
20116
  continue;
19428
20117
  }
19429
- await mkdir13(dirname7(file), { recursive: true });
20118
+ await mkdir14(dirname7(file), { recursive: true });
19430
20119
  await writeFile11(file, note.content ?? "", "utf8");
19431
20120
  written.push(note.path);
19432
20121
  if (note.excluded === true) excluded.push(note.path);
@@ -19541,7 +20230,7 @@ function safeJoin(root, relative3) {
19541
20230
  "Nothing was written for it. This should not happen \u2014 please report it."
19542
20231
  );
19543
20232
  }
19544
- const full = resolve11(join21(root, relative3));
20233
+ const full = resolve11(join22(root, relative3));
19545
20234
  if (full !== root && !full.startsWith(root + sep2)) {
19546
20235
  throw memgError(
19547
20236
  "server_refused",
@@ -19553,7 +20242,7 @@ function safeJoin(root, relative3) {
19553
20242
  }
19554
20243
  async function exists(file) {
19555
20244
  try {
19556
- await readFile21(file);
20245
+ await readFile22(file);
19557
20246
  return true;
19558
20247
  } catch {
19559
20248
  return false;
@@ -19561,8 +20250,8 @@ async function exists(file) {
19561
20250
  }
19562
20251
 
19563
20252
  // src/commands/publish.ts
19564
- import { Command as Command26 } from "commander";
19565
- import { z as z32 } from "zod";
20253
+ import { Command as Command27 } from "commander";
20254
+ import { z as z33 } from "zod";
19566
20255
  init_src();
19567
20256
  init_errors();
19568
20257
 
@@ -19709,9 +20398,9 @@ import semver from "semver";
19709
20398
  init_parser();
19710
20399
 
19711
20400
  // ../../packages/harness-schema/src/deps.ts
19712
- import { z as z25 } from "zod";
20401
+ import { z as z26 } from "zod";
19713
20402
  var StringDepRe = /^([a-z0-9](-?[a-z0-9])*\/[a-z0-9](-?[a-z0-9])*)(?:@(.+))?$/;
19714
- var ObjectDepSchema = z25.object({
20403
+ var ObjectDepSchema = z26.object({
19715
20404
  full_slug: FullSlugSchema,
19716
20405
  version_range: SemverRangeSchema.default("*")
19717
20406
  });
@@ -19746,33 +20435,33 @@ function normaliseDep(entry, idx) {
19746
20435
  init_parser();
19747
20436
 
19748
20437
  // ../../packages/harness-schema/src/types/skill.ts
19749
- import { z as z26 } from "zod";
20438
+ import { z as z27 } from "zod";
19750
20439
  var SKILL_DESCRIPTION_SPEC_MAX = 1024;
19751
20440
  var SKILL_DESCRIPTION_HARD_MAX = 2048;
19752
20441
  var SKILL_COMPATIBILITY_MAX = 500;
19753
- var MetadataValueSchema = z26.union([z26.string(), z26.number(), z26.boolean()]).transform((v) => String(v));
19754
- var SkillSpecFrontmatterSchema = z26.object({
19755
- name: z26.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be lowercase-with-hyphens (Agent Skills spec)"),
19756
- description: z26.string().min(1).max(SKILL_DESCRIPTION_HARD_MAX),
20442
+ var MetadataValueSchema = z27.union([z27.string(), z27.number(), z27.boolean()]).transform((v) => String(v));
20443
+ var SkillSpecFrontmatterSchema = z27.object({
20444
+ name: z27.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "name must be lowercase-with-hyphens (Agent Skills spec)"),
20445
+ description: z27.string().min(1).max(SKILL_DESCRIPTION_HARD_MAX),
19757
20446
  // The spec puts no length limit on license (it is an SPDX identifier or a
19758
20447
  // short phrase); the old 64-char cap here was Kordis-invented.
19759
- license: z26.string().optional(),
19760
- compatibility: z26.string().max(SKILL_COMPATIBILITY_MAX).optional(),
20448
+ license: z27.string().optional(),
20449
+ compatibility: z27.string().max(SKILL_COMPATIBILITY_MAX).optional(),
19761
20450
  // Spec form is a space-separated STRING. The array form is what Kordis
19762
20451
  // accepted before the spec settled — still parsed, flagged
19763
20452
  // `legacy_frontmatter`, and re-emitted unchanged by the translators.
19764
- "allowed-tools": z26.union([z26.string(), z26.array(z26.string())]).optional(),
19765
- metadata: z26.record(z26.string(), MetadataValueSchema).optional()
20453
+ "allowed-tools": z27.union([z27.string(), z27.array(z27.string())]).optional(),
20454
+ metadata: z27.record(z27.string(), MetadataValueSchema).optional()
19766
20455
  }).strict();
19767
- var LegacySkillExtrasSchema = z26.object({
20456
+ var LegacySkillExtrasSchema = z27.object({
19768
20457
  version: SemverSchema.optional(),
19769
- homepage: z26.string().url().optional(),
19770
- model: z26.string().optional(),
20458
+ homepage: z27.string().url().optional(),
20459
+ model: z27.string().optional(),
19771
20460
  // model override hint
19772
- "disable-model-invocation": z26.boolean().optional(),
19773
- title: z26.string().min(1).max(200).optional(),
19774
- tags: z26.array(TagSchema).max(16).optional(),
19775
- tools: z26.array(ToolSlugSchema).optional()
20461
+ "disable-model-invocation": z27.boolean().optional(),
20462
+ title: z27.string().min(1).max(200).optional(),
20463
+ tags: z27.array(TagSchema).max(16).optional(),
20464
+ tools: z27.array(ToolSlugSchema).optional()
19776
20465
  });
19777
20466
  var LEGACY_SKILL_EXTRA_KEYS = [
19778
20467
  "version",
@@ -19787,10 +20476,10 @@ var LEGACY_KEY_SET = new Set(LEGACY_SKILL_EXTRA_KEYS);
19787
20476
  var NONSTANDARD_HINT = 'These are not Agent Skills fields. Move them to `metadata` (string values: `metadata: {version: "1.2.3", tags: "a, b", tools: "claude-code, codex"}`). Accepted with a warning for now; a future release will make this an error and will say so in its changelog.';
19788
20477
  var METADATA_READBACK = [
19789
20478
  { field: "version", schema: SemverSchema, list: false },
19790
- { field: "homepage", schema: z26.string().url(), list: false },
19791
- { field: "model", schema: z26.string().min(1), list: false },
19792
- { field: "tags", schema: z26.array(TagSchema).max(16), list: true },
19793
- { field: "tools", schema: z26.array(ToolSlugSchema), list: true }
20479
+ { field: "homepage", schema: z27.string().url(), list: false },
20480
+ { field: "model", schema: z27.string().min(1), list: false },
20481
+ { field: "tags", schema: z27.array(TagSchema).max(16), list: true },
20482
+ { field: "tools", schema: z27.array(ToolSlugSchema), list: true }
19794
20483
  ];
19795
20484
  function splitList(raw) {
19796
20485
  return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -19885,30 +20574,30 @@ function parseSkillFrontmatter(raw) {
19885
20574
  }
19886
20575
 
19887
20576
  // ../../packages/harness-schema/src/types/mcp-server.ts
19888
- import { z as z27 } from "zod";
19889
- var McpTransportSchema = z27.union([
19890
- z27.object({
19891
- type: z27.literal("stdio"),
19892
- command: z27.string().min(1),
19893
- args: z27.array(z27.string()).optional().default([]),
19894
- env: z27.record(z27.string()).optional().default({})
20577
+ import { z as z28 } from "zod";
20578
+ var McpTransportSchema = z28.union([
20579
+ z28.object({
20580
+ type: z28.literal("stdio"),
20581
+ command: z28.string().min(1),
20582
+ args: z28.array(z28.string()).optional().default([]),
20583
+ env: z28.record(z28.string()).optional().default({})
19895
20584
  }),
19896
- z27.object({
19897
- type: z27.enum(["http", "sse"]),
19898
- url: z27.string().url(),
19899
- headers: z27.record(z27.string()).optional().default({})
20585
+ z28.object({
20586
+ type: z28.enum(["http", "sse"]),
20587
+ url: z28.string().url(),
20588
+ headers: z28.record(z28.string()).optional().default({})
19900
20589
  })
19901
20590
  ]);
19902
- var McpCapabilitiesSchema = z27.object({
19903
- tools: z27.array(z27.string()).optional().default([]),
19904
- resources: z27.array(z27.string()).optional().default([]),
19905
- prompts: z27.array(z27.string()).optional().default([])
20591
+ var McpCapabilitiesSchema = z28.object({
20592
+ tools: z28.array(z28.string()).optional().default([]),
20593
+ resources: z28.array(z28.string()).optional().default([]),
20594
+ prompts: z28.array(z28.string()).optional().default([])
19906
20595
  });
19907
- var McpServerFrontmatterSchema = z27.object({
19908
- name: z27.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
19909
- description: z27.string().min(1).max(2048),
20596
+ var McpServerFrontmatterSchema = z28.object({
20597
+ name: z28.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
20598
+ description: z28.string().min(1).max(2048),
19910
20599
  version: SemverSchema,
19911
- mcp_spec_version: z27.string().default("2026-03"),
20600
+ mcp_spec_version: z28.string().default("2026-03"),
19912
20601
  transport: McpTransportSchema,
19913
20602
  capabilities: McpCapabilitiesSchema.optional().default({
19914
20603
  tools: [],
@@ -19916,34 +20605,34 @@ var McpServerFrontmatterSchema = z27.object({
19916
20605
  prompts: []
19917
20606
  }),
19918
20607
  /** Human-readable setup / auth instructions. Surfaced in `kordis info`. */
19919
- auth_notes: z27.string().optional(),
20608
+ auth_notes: z28.string().optional(),
19920
20609
  /** Required env vars with description + whether they must be secret. */
19921
- env_schema: z27.record(
19922
- z27.object({
19923
- description: z27.string(),
19924
- required: z27.boolean().default(true),
19925
- secret: z27.boolean().default(false)
20610
+ env_schema: z28.record(
20611
+ z28.object({
20612
+ description: z28.string(),
20613
+ required: z28.boolean().default(true),
20614
+ secret: z28.boolean().default(false)
19926
20615
  })
19927
20616
  ).optional(),
19928
- homepage: z27.string().url().optional(),
19929
- repository: z27.string().url().optional(),
19930
- license: z27.string().max(64).optional(),
19931
- title: z27.string().min(1).max(200).optional()
20617
+ homepage: z28.string().url().optional(),
20618
+ repository: z28.string().url().optional(),
20619
+ license: z28.string().max(64).optional(),
20620
+ title: z28.string().min(1).max(200).optional()
19932
20621
  }).strict();
19933
20622
 
19934
20623
  // ../../packages/harness-schema/src/types/project-rules.ts
19935
- import { z as z28 } from "zod";
19936
- var ProjectRulesFrontmatterSchema = z28.object({
19937
- title: z28.string().min(1).max(200),
19938
- description: z28.string().min(1).max(2048),
20624
+ import { z as z29 } from "zod";
20625
+ var ProjectRulesFrontmatterSchema = z29.object({
20626
+ title: z29.string().min(1).max(200),
20627
+ description: z29.string().min(1).max(2048),
19939
20628
  version: SemverSchema.optional(),
19940
- scope: z28.enum(["root", "nested", "globs"]).default("root"),
19941
- target_path: z28.string().optional(),
19942
- globs: z28.array(z28.string()).optional(),
19943
- target_tools: z28.array(ToolSlugSchema).optional().default([]),
20629
+ scope: z29.enum(["root", "nested", "globs"]).default("root"),
20630
+ target_path: z29.string().optional(),
20631
+ globs: z29.array(z29.string()).optional(),
20632
+ target_tools: z29.array(ToolSlugSchema).optional().default([]),
19944
20633
  /** When true, installation is idempotent via `<!-- kordis:... -->` markers. */
19945
- mergeable: z28.boolean().default(true),
19946
- license: z28.string().max(64).optional()
20634
+ mergeable: z29.boolean().default(true),
20635
+ license: z29.string().max(64).optional()
19947
20636
  }).strict().refine((v) => v.scope !== "nested" || !!v.target_path, {
19948
20637
  message: 'scope="nested" requires target_path'
19949
20638
  }).refine((v) => v.scope !== "globs" || v.globs && v.globs.length > 0, {
@@ -19951,32 +20640,32 @@ var ProjectRulesFrontmatterSchema = z28.object({
19951
20640
  });
19952
20641
 
19953
20642
  // ../../packages/harness-schema/src/types/custom-agent.ts
19954
- import { z as z29 } from "zod";
19955
- var CustomAgentFrontmatterSchema = z29.object({
19956
- name: z29.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
19957
- description: z29.string().min(1).max(2048),
19958
- title: z29.string().min(1).max(200).optional(),
20643
+ import { z as z30 } from "zod";
20644
+ var CustomAgentFrontmatterSchema = z30.object({
20645
+ name: z30.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
20646
+ description: z30.string().min(1).max(2048),
20647
+ title: z30.string().min(1).max(200).optional(),
19959
20648
  version: SemverSchema.optional(),
19960
20649
  /** System prompt that defines the agent's persona + rules. Required. */
19961
- system_prompt: z29.string().min(1).max(5e4),
20650
+ system_prompt: z30.string().min(1).max(5e4),
19962
20651
  /** Optional model override — string passed verbatim to the host. */
19963
- model: z29.string().optional(),
20652
+ model: z30.string().optional(),
19964
20653
  /** Tools this agent can call. Host-specific names (no uniform namespace). */
19965
- allowed_tools: z29.array(z29.string()).optional().default([]),
19966
- disallowed_tools: z29.array(z29.string()).optional().default([]),
20654
+ allowed_tools: z30.array(z30.string()).optional().default([]),
20655
+ disallowed_tools: z30.array(z30.string()).optional().default([]),
19967
20656
  /** Fork/context behaviour hint (Claude: `context: fork` etc.). */
19968
- context: z29.enum(["inherit", "fork", "isolated"]).optional(),
20657
+ context: z30.enum(["inherit", "fork", "isolated"]).optional(),
19969
20658
  /** Known-good tool runtimes. Empty = untested everywhere. */
19970
- tool_compat: z29.array(ToolSlugSchema).optional().default([]),
20659
+ tool_compat: z30.array(ToolSlugSchema).optional().default([]),
19971
20660
  /** Pre-load skills when this agent activates. */
19972
- preload_skills: z29.array(z29.string()).optional().default([]),
19973
- license: z29.string().max(64).optional(),
19974
- homepage: z29.string().url().optional()
20661
+ preload_skills: z30.array(z30.string()).optional().default([]),
20662
+ license: z30.string().max(64).optional(),
20663
+ homepage: z30.string().url().optional()
19975
20664
  }).strict();
19976
20665
 
19977
20666
  // ../../packages/harness-schema/src/types/hook.ts
19978
- import { z as z30 } from "zod";
19979
- var HookEventSchema = z30.enum([
20667
+ import { z as z31 } from "zod";
20668
+ var HookEventSchema = z31.enum([
19980
20669
  "PreToolUse",
19981
20670
  "PostToolUse",
19982
20671
  "UserPromptSubmit",
@@ -19988,78 +20677,78 @@ var HookEventSchema = z30.enum([
19988
20677
  "SubagentStop",
19989
20678
  "Background"
19990
20679
  ]);
19991
- var HookActionSchema = z30.union([
19992
- z30.object({
19993
- type: z30.literal("command"),
19994
- command: z30.string().min(1),
19995
- timeout_ms: z30.number().int().positive().max(6e5).optional(),
19996
- blocking: z30.boolean().default(false)
20680
+ var HookActionSchema = z31.union([
20681
+ z31.object({
20682
+ type: z31.literal("command"),
20683
+ command: z31.string().min(1),
20684
+ timeout_ms: z31.number().int().positive().max(6e5).optional(),
20685
+ blocking: z31.boolean().default(false)
19997
20686
  }),
19998
- z30.object({
19999
- type: z30.literal("http"),
20000
- url: z30.string().url(),
20001
- method: z30.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
20002
- headers: z30.record(z30.string()).optional().default({}),
20003
- timeout_ms: z30.number().int().positive().max(6e5).optional(),
20004
- blocking: z30.boolean().default(false)
20687
+ z31.object({
20688
+ type: z31.literal("http"),
20689
+ url: z31.string().url(),
20690
+ method: z31.enum(["GET", "POST", "PUT", "PATCH"]).default("POST"),
20691
+ headers: z31.record(z31.string()).optional().default({}),
20692
+ timeout_ms: z31.number().int().positive().max(6e5).optional(),
20693
+ blocking: z31.boolean().default(false)
20005
20694
  })
20006
20695
  ]);
20007
- var HookBindingSchema = z30.object({
20696
+ var HookBindingSchema = z31.object({
20008
20697
  event: HookEventSchema,
20009
- matcher: z30.string().optional(),
20698
+ matcher: z31.string().optional(),
20010
20699
  action: HookActionSchema
20011
20700
  });
20012
- var HookFrontmatterSchema = z30.object({
20013
- name: z30.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
20014
- description: z30.string().min(1).max(2048),
20015
- title: z30.string().min(1).max(200).optional(),
20701
+ var HookFrontmatterSchema = z31.object({
20702
+ name: z31.string().min(1).max(64).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/),
20703
+ description: z31.string().min(1).max(2048),
20704
+ title: z31.string().min(1).max(200).optional(),
20016
20705
  version: SemverSchema.optional(),
20017
20706
  /** One or more hook bindings. */
20018
- hooks: z30.array(HookBindingSchema).min(1),
20707
+ hooks: z31.array(HookBindingSchema).min(1),
20019
20708
  /** Declared to surface in search filters — always "claude-code" for now. */
20020
- tool_compat: z30.array(z30.enum(["claude-code"])).default(["claude-code"]),
20021
- license: z30.string().max(64).optional()
20709
+ tool_compat: z31.array(z31.enum(["claude-code"])).default(["claude-code"]),
20710
+ license: z31.string().max(64).optional()
20022
20711
  }).strict();
20023
20712
 
20024
20713
  // ../../packages/harness-schema/src/types/judgment-pattern.ts
20025
- import { z as z31 } from "zod";
20026
- var FailureModeSchema = z31.object({
20027
- symptom: z31.string().min(1).max(600).describe("the observable sign the judgment was ignored"),
20028
- correction: z31.string().min(1).max(1200).describe("what to do once the symptom shows")
20714
+ import { z as z32 } from "zod";
20715
+ var FailureModeSchema = z32.object({
20716
+ symptom: z32.string().min(1).max(600).describe("the observable sign the judgment was ignored"),
20717
+ correction: z32.string().min(1).max(1200).describe("what to do once the symptom shows")
20029
20718
  }).strict();
20030
- var AdaptHintsSchema = z31.object({
20719
+ var AdaptHintsSchema = z32.object({
20031
20720
  /**
20032
20721
  * Parts that transplant verbatim into ANY harness — the invariant core.
20033
20722
  * Required, min 1: a judgment-pattern with no universal part has nothing
20034
20723
  * to transplant, which makes it a skill/project-rules in disguise, not a
20035
20724
  * judgment worth the dedicated type.
20036
20725
  */
20037
- universal: z31.array(z31.string().min(1).max(400)).min(1, "adapt_hints.universal must list at least one transplantable, harness-agnostic part").max(20),
20726
+ universal: z32.array(z32.string().min(1).max(400)).min(1, "adapt_hints.universal must list at least one transplantable, harness-agnostic part").max(20),
20038
20727
  /** Parts the adapting agent must re-express in the host's stack/conventions. */
20039
- context_specific: z31.array(z31.string().min(1).max(400)).max(20).optional()
20728
+ context_specific: z32.array(z32.string().min(1).max(400)).max(20).optional()
20040
20729
  }).strict();
20041
- var JudgmentPatternFrontmatterSchema = z31.object({
20042
- name: z31.string().min(1).max(64).regex(
20730
+ var JudgmentPatternFrontmatterSchema = z32.object({
20731
+ name: z32.string().min(1).max(64).regex(
20043
20732
  /^[a-z0-9]+(-[a-z0-9]+)*$/,
20044
20733
  'name must be lowercase-with-hyphens, e.g. "additive-only-migrations"'
20045
20734
  ),
20046
- description: z31.string().min(1).max(2048),
20735
+ description: z32.string().min(1).max(2048),
20047
20736
  version: SemverSchema.optional(),
20048
- title: z31.string().min(1).max(200).optional(),
20737
+ title: z32.string().min(1).max(200).optional(),
20049
20738
  /** The recurring situation / trap this judgment addresses. */
20050
- problem: z31.string().min(1).max(4e3),
20739
+ problem: z32.string().min(1).max(4e3),
20051
20740
  /**
20052
20741
  * The judgment itself — free-form Markdown. The agent is the interpreter,
20053
20742
  * so this stays unstructured prose (decision tree, heuristics, rationale).
20054
20743
  * This is the part that gets transplanted.
20055
20744
  */
20056
- decision_logic: z31.string().min(1).max(2e4),
20745
+ decision_logic: z32.string().min(1).max(2e4),
20057
20746
  /** Triggers — when this judgment should fire. At least one. */
20058
- when_to_apply: z31.array(z31.string().min(1).max(400)).min(1).max(12),
20747
+ when_to_apply: z32.array(z32.string().min(1).max(400)).min(1).max(12),
20059
20748
  /** Counter-triggers — when applying it would be wrong. */
20060
- when_not_to_apply: z31.array(z31.string().min(1).max(400)).max(12).optional(),
20749
+ when_not_to_apply: z32.array(z32.string().min(1).max(400)).max(12).optional(),
20061
20750
  /** How it fails in practice, and how to recover — the hard-won part. */
20062
- failure_modes: z31.array(FailureModeSchema).max(12).optional(),
20751
+ failure_modes: z32.array(FailureModeSchema).max(12).optional(),
20063
20752
  /**
20064
20753
  * Transplant steering — the field unique to judgment-pattern. Required:
20065
20754
  * `adapt_hints.universal` (min 1) is what makes this a judgment-pattern
@@ -20067,13 +20756,13 @@ var JudgmentPatternFrontmatterSchema = z31.object({
20067
20756
  */
20068
20757
  adapt_hints: AdaptHintsSchema,
20069
20758
  /** Where this judgment came from (project, incident, paper). Provenance. */
20070
- origin: z31.string().min(1).max(600).optional(),
20759
+ origin: z32.string().min(1).max(600).optional(),
20071
20760
  /** Proof it works (anti-spam lever for AI review). */
20072
- evidence: z31.string().min(1).max(2e3).optional(),
20761
+ evidence: z32.string().min(1).max(2e3).optional(),
20073
20762
  // Kordis-forwarded cross-type fields (kept minimal, like skill).
20074
- tags: z31.array(TagSchema).max(16).optional(),
20075
- tools: z31.array(ToolSlugSchema).optional(),
20076
- license: z31.string().max(64).optional()
20763
+ tags: z32.array(TagSchema).max(16).optional(),
20764
+ tools: z32.array(ToolSlugSchema).optional(),
20765
+ license: z32.string().max(64).optional()
20077
20766
  }).strict();
20078
20767
 
20079
20768
  // ../../packages/harness-schema/src/validator.ts
@@ -20392,7 +21081,7 @@ function isSameBrainNote(publishedOrigin, memoryId) {
20392
21081
  }
20393
21082
 
20394
21083
  // src/commands/registry/read.ts
20395
- import { Command as Command25 } from "commander";
21084
+ import { Command as Command26 } from "commander";
20396
21085
  init_errors();
20397
21086
  init_credentials();
20398
21087
 
@@ -20415,7 +21104,7 @@ function untrustedFenceNotice(sentinel) {
20415
21104
  init_ui();
20416
21105
  init_version();
20417
21106
  function readCommand() {
20418
- return new Command25("read").description("read a judgment from the public layer into your context (nothing is installed)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was read").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
21107
+ return new Command26("read").description("read a judgment from the public layer into your context (nothing is installed)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was read").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
20419
21108
  "after",
20420
21109
  "\nEnvironment:\n KORDIS_API_URL=<url> read from this registry instead of the linked one\n"
20421
21110
  ).action(async (rawSlug, opts) => {
@@ -20563,7 +21252,7 @@ async function recordUsage(verb, detail, where = {}) {
20563
21252
  init_vault();
20564
21253
  init_ui();
20565
21254
  function publishCommand() {
20566
- return new Command26("publish").description("publish one decision from this brain to the public layer").argument("<ref>", "handle, id, path, or exact title of the note to publish").option("--slug <name>", "the package name (default: derived from the note title)").option("--yes", "actually send it (without this, nothing leaves this machine)").option("--reason <text>", "why this is being published \u2014 the user's own words").option("--no-lineage", "do not propose lineage from what this machine has adapted").option(
21255
+ return new Command27("publish").description("publish one decision from this brain to the public layer").argument("<ref>", "handle, id, path, or exact title of the note to publish").option("--slug <name>", "the package name (default: derived from the note title)").option("--yes", "actually send it (without this, nothing leaves this machine)").option("--reason <text>", "why this is being published \u2014 the user's own words").option("--no-lineage", "do not propose lineage from what this machine has adapted").option(
20567
21256
  "--bump <kind>",
20568
21257
  "patch | minor | major \u2014 publish the next version of a package this note already produced"
20569
21258
  ).option("--wait <ms>", "how long to wait for the AI review before answering (default 3000)").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").addHelpText(
@@ -20704,7 +21393,7 @@ async function hostedAssertion(target, ref) {
20704
21393
  const cloud = await openCloudBrain(target);
20705
21394
  const found = await cloudOpen(cloud, ref, await projectAskedFrom());
20706
21395
  const memory = MemorySchema.safeParse(found.memory);
20707
- const claims = z32.array(ClaimSchema).safeParse(found.claims);
21396
+ const claims = z33.array(ClaimSchema).safeParse(found.claims);
20708
21397
  if (!memory.success || !claims.success) {
20709
21398
  throw memgError(
20710
21399
  "server_refused",
@@ -20889,13 +21578,13 @@ function parseWait(raw) {
20889
21578
  }
20890
21579
 
20891
21580
  // src/commands/registry/adapt.ts
20892
- import { Command as Command27 } from "commander";
21581
+ import { Command as Command28 } from "commander";
20893
21582
  init_errors();
20894
21583
 
20895
21584
  // src/lib/harness-context.ts
20896
21585
  init_brand();
20897
- import { access, readFile as readFile22 } from "fs/promises";
20898
- import { join as join22 } from "path";
21586
+ import { access, readFile as readFile23 } from "fs/promises";
21587
+ import { join as join23 } from "path";
20899
21588
  var PER_FILE_CAP = 16 * 1024;
20900
21589
  var TOTAL_CAP = 48 * 1024;
20901
21590
  var TRUNCATION_RESERVE = 64;
@@ -20940,13 +21629,13 @@ function extractHeadings(content) {
20940
21629
  }
20941
21630
  async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
20942
21631
  const candidates = [
20943
- { path: join22(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
20944
- { path: join22(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
20945
- { path: join22(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
20946
- { path: join22(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
20947
- { path: join22(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" },
20948
- { path: join22(home, ".codex", "AGENTS.md"), display: "~/.codex/AGENTS.md", scope: "global" },
20949
- { path: join22(home, ".gemini", "GEMINI.md"), display: "~/.gemini/GEMINI.md", scope: "global" }
21632
+ { path: join23(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
21633
+ { path: join23(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
21634
+ { path: join23(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
21635
+ { path: join23(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
21636
+ { path: join23(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" },
21637
+ { path: join23(home, ".codex", "AGENTS.md"), display: "~/.codex/AGENTS.md", scope: "global" },
21638
+ { path: join23(home, ".gemini", "GEMINI.md"), display: "~/.gemini/GEMINI.md", scope: "global" }
20950
21639
  ];
20951
21640
  const files2 = [];
20952
21641
  let totalBytes = 0;
@@ -20954,7 +21643,7 @@ async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
20954
21643
  if (!await pathExists(cand.path)) continue;
20955
21644
  let raw;
20956
21645
  try {
20957
- raw = await readFile22(cand.path, "utf8");
21646
+ raw = await readFile23(cand.path, "utf8");
20958
21647
  } catch {
20959
21648
  continue;
20960
21649
  }
@@ -21025,7 +21714,7 @@ function renderBrief(res, harness) {
21025
21714
  return out.join("\n");
21026
21715
  }
21027
21716
  function adaptCommand() {
21028
- return new Command27("adapt").description("fit a public judgment to your existing harness (prints a brief; writes nothing)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was adapted").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
21717
+ return new Command28("adapt").description("fit a public judgment to your existing harness (prints a brief; writes nothing)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was adapted").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
21029
21718
  "after",
21030
21719
  "\nEnvironment:\n KORDIS_API_URL=<url> read from this registry instead of the linked one\n"
21031
21720
  ).action(async (rawSlug, opts) => {
@@ -21073,10 +21762,10 @@ function adaptCommand() {
21073
21762
  }
21074
21763
 
21075
21764
  // src/commands/registry/info.ts
21076
- import { Command as Command28 } from "commander";
21765
+ import { Command as Command29 } from "commander";
21077
21766
  init_ui();
21078
21767
  function infoCommand() {
21079
- return new Command28("info").description("what a package in the public layer is, without reading it").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").addHelpText(
21768
+ return new Command29("info").description("what a package in the public layer is, without reading it").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").addHelpText(
21080
21769
  "after",
21081
21770
  "\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
21082
21771
  ).action(async (rawSlug) => {
@@ -21162,7 +21851,7 @@ function renderCanonical(component) {
21162
21851
  }
21163
21852
 
21164
21853
  // src/commands/registry/search.ts
21165
- import { Command as Command29 } from "commander";
21854
+ import { Command as Command30 } from "commander";
21166
21855
  init_errors();
21167
21856
  init_ui();
21168
21857
  var SEARCHABLE_TYPES = PHASE_1_TYPES;
@@ -21170,7 +21859,7 @@ var LIMIT_MIN = 1;
21170
21859
  var LIMIT_MAX = 100;
21171
21860
  var LIMIT_DEFAULT = 20;
21172
21861
  function searchCommand() {
21173
- return new Command29("search").description("find a judgment in the public layer (titles only; nothing is read or installed)").argument("[query...]", "words to match against title and description").option("--type <type>", `narrow by kind: ${SEARCHABLE_TYPES.join("|")}`).option("--limit <n>", `how many results (${LIMIT_MIN}-${LIMIT_MAX})`, String(LIMIT_DEFAULT)).addHelpText(
21862
+ return new Command30("search").description("find a judgment in the public layer (titles only; nothing is read or installed)").argument("[query...]", "words to match against title and description").option("--type <type>", `narrow by kind: ${SEARCHABLE_TYPES.join("|")}`).option("--limit <n>", `how many results (${LIMIT_MIN}-${LIMIT_MAX})`, String(LIMIT_DEFAULT)).addHelpText(
21174
21863
  "after",
21175
21864
  "\nEnvironment:\n KORDIS_API_URL=<url> search this registry instead of the linked one\n"
21176
21865
  ).action(async (query, opts) => {
@@ -21348,11 +22037,11 @@ function parseLimit2(raw) {
21348
22037
  }
21349
22038
 
21350
22039
  // src/commands/registry/pull.ts
21351
- import { Command as Command30 } from "commander";
21352
- import prompts3 from "prompts";
22040
+ import { Command as Command31 } from "commander";
22041
+ import prompts4 from "prompts";
21353
22042
  import { existsSync, lstatSync, realpathSync } from "fs";
21354
- import { mkdir as mkdir15, readFile as readFile24, stat as stat7, writeFile as writeFile13 } from "fs/promises";
21355
- import { dirname as dirname8, join as join24, resolve as resolve12, sep as sep3 } from "path";
22043
+ import { mkdir as mkdir16, readFile as readFile25, stat as stat8, writeFile as writeFile13 } from "fs/promises";
22044
+ import { dirname as dirname8, join as join25, resolve as resolve12, sep as sep3 } from "path";
21356
22045
 
21357
22046
  // ../../packages/translators/src/skill/identity.ts
21358
22047
  function buildSkillFile(input, outputPath) {
@@ -22077,17 +22766,17 @@ init_ui();
22077
22766
 
22078
22767
  // src/lib/lockfile.ts
22079
22768
  init_brand();
22080
- import { copyFile, mkdir as mkdir14, readFile as readFile23, writeFile as writeFile12, constants as fsConstants } from "fs/promises";
22769
+ import { copyFile, mkdir as mkdir15, readFile as readFile24, writeFile as writeFile12, constants as fsConstants } from "fs/promises";
22081
22770
  import { homedir as homedir4 } from "os";
22082
- import { join as join23 } from "path";
22771
+ import { join as join24 } from "path";
22083
22772
  function effectiveRange(entry) {
22084
22773
  return entry.requestedRange ?? `^${entry.version}`;
22085
22774
  }
22086
22775
  function lockfilePath() {
22087
- return join23(brandHome(), "installed.json");
22776
+ return join24(brandHome(), "installed.json");
22088
22777
  }
22089
22778
  function kordisLockfilePath() {
22090
- return join23(process.env["KORDIS_HOME"] ?? join23(homedir4(), ".kordis"), "installed.json");
22779
+ return join24(process.env["KORDIS_HOME"] ?? join24(homedir4(), ".kordis"), "installed.json");
22091
22780
  }
22092
22781
  function lockfileDisplayPath() {
22093
22782
  const path = lockfilePath();
@@ -22098,13 +22787,13 @@ async function migrateFromKordis() {
22098
22787
  const source = kordisLockfilePath();
22099
22788
  const destination = lockfilePath();
22100
22789
  try {
22101
- await mkdir14(brandHome(), { recursive: true });
22790
+ await mkdir15(brandHome(), { recursive: true });
22102
22791
  await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
22103
22792
  } catch {
22104
22793
  return;
22105
22794
  }
22106
22795
  try {
22107
- const raw = await readFile23(destination, "utf8");
22796
+ const raw = await readFile24(destination, "utf8");
22108
22797
  const parsed = JSON.parse(raw);
22109
22798
  if (parsed.migratedFrom === void 0) {
22110
22799
  parsed.migratedFrom = source;
@@ -22115,18 +22804,18 @@ async function migrateFromKordis() {
22115
22804
  }
22116
22805
  async function readLockfile() {
22117
22806
  try {
22118
- return JSON.parse(await readFile23(lockfilePath(), "utf8"));
22807
+ return JSON.parse(await readFile24(lockfilePath(), "utf8"));
22119
22808
  } catch {
22120
22809
  await migrateFromKordis();
22121
22810
  }
22122
22811
  try {
22123
- return JSON.parse(await readFile23(lockfilePath(), "utf8"));
22812
+ return JSON.parse(await readFile24(lockfilePath(), "utf8"));
22124
22813
  } catch {
22125
22814
  return { packages: {} };
22126
22815
  }
22127
22816
  }
22128
22817
  async function writeLockfile(lf) {
22129
- await mkdir14(brandHome(), { recursive: true });
22818
+ await mkdir15(brandHome(), { recursive: true });
22130
22819
  await writeFile12(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
22131
22820
  encoding: "utf8",
22132
22821
  mode: 384
@@ -22134,7 +22823,7 @@ async function writeLockfile(lf) {
22134
22823
  }
22135
22824
  async function recordInstalled(fullSlug, entry) {
22136
22825
  const home = brandHome();
22137
- await withFileLock(join23(home, "installed.lock"), home, async () => {
22826
+ await withFileLock(join24(home, "installed.lock"), home, async () => {
22138
22827
  const lf = await readLockfile();
22139
22828
  lf.packages[fullSlug] = entry;
22140
22829
  await writeLockfile(lf);
@@ -22197,7 +22886,7 @@ async function promptForExecutableApproval(review) {
22197
22886
  );
22198
22887
  printHuman(c.gray(" \u2500\u2500 END EXECUTABLE CONFIG \u2500\u2500"));
22199
22888
  }
22200
- const { confirmed } = await prompts3({
22889
+ const { confirmed } = await prompts4({
22201
22890
  type: "confirm",
22202
22891
  name: "confirmed",
22203
22892
  message: "Install and activate these exact executable settings?",
@@ -22290,7 +22979,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
22290
22979
  );
22291
22980
  for (const f of translated.files) {
22292
22981
  const abs = safeInstallPath(installRoot, f.path);
22293
- await mkdir15(dirname8(abs), { recursive: true });
22982
+ await mkdir16(dirname8(abs), { recursive: true });
22294
22983
  await applyEmittedFile(abs, f, fullSlug, notes);
22295
22984
  written.push(abs);
22296
22985
  }
@@ -22319,7 +23008,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
22319
23008
  };
22320
23009
  }
22321
23010
  function registryPullCommand() {
22322
- return new Command30("pull").description("install a package from the public layer into this project").argument(
23011
+ return new Command31("pull").description("install a package from the public layer into this project").argument(
22323
23012
  "<full_slug>",
22324
23013
  "package identifier {username}/{slug}, optionally @{version} or @{range}"
22325
23014
  ).option("--tool <name>", "target tool format (default: work it out from this directory)").option("--target <path>", "install into this directory instead of the current one").option("--with-recommends", "also install recommended (depth-0) dependencies", false).option(
@@ -22446,7 +23135,7 @@ var TOOL_MARKERS = [
22446
23135
  async function detectTool(root) {
22447
23136
  for (const [path, tool] of TOOL_MARKERS) {
22448
23137
  try {
22449
- await stat7(join24(root, path));
23138
+ await stat8(join25(root, path));
22450
23139
  return tool;
22451
23140
  } catch {
22452
23141
  }
@@ -22462,7 +23151,7 @@ function safeInstallPath(installRoot, relPath) {
22462
23151
  "Nothing was written. The package emits an absolute file path. Tell the author to publish it with relative paths only; if you did not publish it, treat it as an attempt to write outside your project."
22463
23152
  );
22464
23153
  }
22465
- const abs = join24(installRoot, relPath);
23154
+ const abs = join25(installRoot, relPath);
22466
23155
  const resolvedRoot = resolve12(installRoot);
22467
23156
  const resolvedAbs = resolve12(abs);
22468
23157
  if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep3)) {
@@ -22528,7 +23217,7 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
22528
23217
  }
22529
23218
  let existingRaw = null;
22530
23219
  try {
22531
- existingRaw = await readFile24(abs, "utf8");
23220
+ existingRaw = await readFile25(abs, "utf8");
22532
23221
  } catch (err) {
22533
23222
  if (err.code !== "ENOENT") throw err;
22534
23223
  }
@@ -22559,10 +23248,10 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
22559
23248
  // src/commands/registry/update.ts
22560
23249
  init_errors();
22561
23250
  init_ui();
22562
- import { Command as Command31 } from "commander";
23251
+ import { Command as Command32 } from "commander";
22563
23252
  import semver2 from "semver";
22564
23253
  function registryUpdateCommand() {
22565
- return new Command31("update").description("update installed packages to their latest in-range versions").argument("[full_slug]", "package identifier {username}/{slug} (omit to update all)").option("--dry-run", "show what would be updated without doing it", false).addHelpText(
23254
+ return new Command32("update").description("update installed packages to their latest in-range versions").argument("[full_slug]", "package identifier {username}/{slug} (omit to update all)").option("--dry-run", "show what would be updated without doing it", false).addHelpText(
22566
23255
  "after",
22567
23256
  "\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
22568
23257
  ).action(async (fullSlugArg, opts) => {
@@ -22696,10 +23385,10 @@ function registryUpdateCommand() {
22696
23385
 
22697
23386
  // src/commands/registry/outdated.ts
22698
23387
  init_ui();
22699
- import { Command as Command32 } from "commander";
23388
+ import { Command as Command33 } from "commander";
22700
23389
  import semver3 from "semver";
22701
23390
  function outdatedCommand() {
22702
- return new Command32("outdated").description("check installed packages for available updates").addHelpText(
23391
+ return new Command33("outdated").description("check installed packages for available updates").addHelpText(
22703
23392
  "after",
22704
23393
  "\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
22705
23394
  ).action(async () => {
@@ -22791,9 +23480,9 @@ function outdatedCommand() {
22791
23480
  // src/commands/registry/versions.ts
22792
23481
  init_errors();
22793
23482
  init_ui();
22794
- import { Command as Command33 } from "commander";
23483
+ import { Command as Command34 } from "commander";
22795
23484
  function versionsCommand() {
22796
- return new Command33("versions").description("list every published version of a package").argument("<full_slug>", "package identifier {username}/{slug}").addHelpText(
23485
+ return new Command34("versions").description("list every published version of a package").argument("<full_slug>", "package identifier {username}/{slug}").addHelpText(
22797
23486
  "after",
22798
23487
  "\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
22799
23488
  ).action(async (fullSlug) => {
@@ -22857,9 +23546,9 @@ function relativeTime(isoString) {
22857
23546
  // src/commands/registry/diff.ts
22858
23547
  init_errors();
22859
23548
  init_ui();
22860
- import { Command as Command34 } from "commander";
23549
+ import { Command as Command35 } from "commander";
22861
23550
  function diffCommand() {
22862
- return new Command34("diff").description("show what changed between two versions of a package").argument(
23551
+ return new Command35("diff").description("show what changed between two versions of a package").argument(
22863
23552
  "<full_slug>",
22864
23553
  "package identifier {username}/{slug}, or {username}/{slug}@{from}..{to}"
22865
23554
  ).argument("[from]", "older version (omit to use the second-latest)").argument("[to]", "newer version (omit to use the latest)").addHelpText(
@@ -22936,7 +23625,7 @@ function diffCommand() {
22936
23625
  }
22937
23626
 
22938
23627
  // src/commands/registry/list.ts
22939
- import { Command as Command35 } from "commander";
23628
+ import { Command as Command36 } from "commander";
22940
23629
  init_errors();
22941
23630
  init_ui();
22942
23631
  var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
@@ -22959,7 +23648,7 @@ function parseUsername(raw) {
22959
23648
  return parsed.data;
22960
23649
  }
22961
23650
  function listCommand() {
22962
- return new Command35("list").description("list packages \u2014 your own with no argument, or a public user\u2019s").argument("[username]", "whose packages to list (omit for your own)").option("--limit <n>", "maximum results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "page number for your own packages (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "continue a public listing from a previous result").option(
23651
+ return new Command36("list").description("list packages \u2014 your own with no argument, or a public user\u2019s").argument("[username]", "whose packages to list (omit for your own)").option("--limit <n>", "maximum results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "page number for your own packages (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "continue a public listing from a previous result").option(
22963
23652
  "--sort <mode>",
22964
23653
  "latest|popular for your own; stars|downloads|recent|updated for a public listing",
22965
23654
  "latest"
@@ -23058,7 +23747,7 @@ init_push();
23058
23747
  init_src();
23059
23748
  init_exclude();
23060
23749
  init_config();
23061
- import { Command as Command37 } from "commander";
23750
+ import { Command as Command38 } from "commander";
23062
23751
  import { basename as basename3, resolve as resolve13 } from "path";
23063
23752
  init_index_build();
23064
23753
  init_cloud_brain();
@@ -23067,7 +23756,7 @@ init_held_back();
23067
23756
  init_vault();
23068
23757
  init_ui();
23069
23758
  function recallCommand() {
23070
- return new Command37("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--local", "search the brain on this machine, even when signed in to a hosted one").option(
23759
+ return new Command38("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--local", "search the brain on this machine, even when signed in to a hosted one").option(
23071
23760
  "--brain <name>",
23072
23761
  "search one existing hosted brain by name or id, without changing the pointer"
23073
23762
  ).option("--scope <scope>", "restrict to one scope").option("--limit <n>", "max cards", (v) => parseInt(v, 10), 8).option(
@@ -23742,13 +24431,13 @@ init_setup();
23742
24431
  init_ui();
23743
24432
  init_deadline();
23744
24433
  init_src();
23745
- import { Command as Command39 } from "commander";
24434
+ import { Command as Command40 } from "commander";
23746
24435
  init_index_build();
23747
24436
  init_cloud_brain();
23748
24437
  init_vault();
23749
24438
  init_ui();
23750
24439
  function resurfaceCommand() {
23751
- return new Command39("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
24440
+ return new Command40("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
23752
24441
  "--brain <name>",
23753
24442
  "read one existing hosted brain by name or id, without changing the pointer"
23754
24443
  ).option(
@@ -23975,11 +24664,11 @@ rank by project yet, so that is not one of the signals above.`
23975
24664
  }
23976
24665
 
23977
24666
  // src/commands/rules.ts
23978
- import { Command as Command40 } from "commander";
24667
+ import { Command as Command41 } from "commander";
23979
24668
  init_cloud_brain();
23980
24669
  init_ui();
23981
24670
  function rulesCommand() {
23982
- return new Command40("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
24671
+ return new Command41("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
23983
24672
  "--brain <name>",
23984
24673
  "read one existing hosted brain by name or id, without changing the pointer"
23985
24674
  ).action(async (opts) => {
@@ -24146,24 +24835,24 @@ init_next_step();
24146
24835
  init_provenance();
24147
24836
  import { spawn as spawn4 } from "child_process";
24148
24837
  import { randomBytes as randomBytes2 } from "crypto";
24149
- import { mkdir as mkdir19, open, readFile as readFile29, rename as rename5, rm as rm8, stat as stat9, unlink as unlink6, writeFile as writeFile16 } from "fs/promises";
24150
- import { join as join31 } from "path";
24151
- import { Command as Command41 } from "commander";
24838
+ import { mkdir as mkdir20, open, readFile as readFile30, rename as rename5, rm as rm8, stat as stat10, unlink as unlink7, writeFile as writeFile16 } from "fs/promises";
24839
+ import { join as join32 } from "path";
24840
+ import { Command as Command42 } from "commander";
24152
24841
 
24153
24842
  // src/lib/update-check.ts
24154
24843
  init_atomic();
24155
24844
  init_brand();
24156
24845
  import { execFile as execFile2 } from "child_process";
24157
- import { mkdir as mkdir18, readFile as readFile28, writeFile as writeFile15 } from "fs/promises";
24158
- import { join as join30 } from "path";
24846
+ import { mkdir as mkdir19, readFile as readFile29, writeFile as writeFile15 } from "fs/promises";
24847
+ import { join as join31 } from "path";
24159
24848
  import { promisify as promisify2 } from "util";
24160
24849
  import semver4 from "semver";
24161
- import { z as z33 } from "zod";
24850
+ import { z as z34 } from "zod";
24162
24851
  var run2 = promisify2(execFile2);
24163
- var PolicySchema = z33.object({
24164
- latest: z33.string().optional(),
24165
- minimum_supported: z33.string().optional(),
24166
- notice: z33.string().optional()
24852
+ var PolicySchema = z34.object({
24853
+ latest: z34.string().optional(),
24854
+ minimum_supported: z34.string().optional(),
24855
+ notice: z34.string().optional()
24167
24856
  });
24168
24857
  var POLICY_URL = "https://memgineering.com/version-policy.json";
24169
24858
  var NOTICE_MAX = 300;
@@ -24228,19 +24917,19 @@ function shouldCheck(lastCheck, now) {
24228
24917
  if (!Number.isFinite(last)) return true;
24229
24918
  return now.getTime() - last > 864e5;
24230
24919
  }
24231
- var UpdateStateSchema = z33.object({
24232
- at: z33.string(),
24233
- ok: z33.boolean(),
24234
- from: z33.string(),
24235
- to: z33.string().nullable(),
24236
- message: z33.string().nullable()
24920
+ var UpdateStateSchema = z34.object({
24921
+ at: z34.string(),
24922
+ ok: z34.boolean(),
24923
+ from: z34.string(),
24924
+ to: z34.string().nullable(),
24925
+ message: z34.string().nullable()
24237
24926
  });
24238
24927
  function statePath() {
24239
- return join30(brandHome(), "update-state.json");
24928
+ return join31(brandHome(), "update-state.json");
24240
24929
  }
24241
24930
  async function readUpdateState() {
24242
24931
  try {
24243
- const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile28(statePath(), "utf8")));
24932
+ const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile29(statePath(), "utf8")));
24244
24933
  return parsed.success ? parsed.data : null;
24245
24934
  } catch {
24246
24935
  return null;
@@ -24248,7 +24937,7 @@ async function readUpdateState() {
24248
24937
  }
24249
24938
  async function writeUpdateState(state) {
24250
24939
  try {
24251
- await mkdir18(brandHome(), { recursive: true });
24940
+ await mkdir19(brandHome(), { recursive: true });
24252
24941
  await writeThenRename(statePath(), `${JSON.stringify(state, null, 2)}
24253
24942
  `);
24254
24943
  } catch {
@@ -24304,7 +24993,7 @@ function shortMessage(err) {
24304
24993
  // src/commands/update.ts
24305
24994
  init_ui();
24306
24995
  function updateCommand() {
24307
- return new Command41("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
24996
+ return new Command42("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
24308
24997
  const inherited = process.env[UPDATE_CLAIM_ENV] || void 0;
24309
24998
  try {
24310
24999
  await runUpdate(opts, inherited);
@@ -24360,7 +25049,7 @@ async function runUpdate(opts, inherited) {
24360
25049
  }
24361
25050
  const held = await holdUpdateSlot(inherited);
24362
25051
  if (held === null && await blockedBy() === "stuck") {
24363
- const lock = join31(brandHome(), RECLAIM_LOCK);
25052
+ const lock = join32(brandHome(), RECLAIM_LOCK);
24364
25053
  const hint = `A previous update did not finish, and clearing what it left is blocked by ${lock}. If no memgineering update is running, delete that folder and run \`memgineering update\` again.`;
24365
25054
  printDual({
24366
25055
  json: { ...status, provenance: provenance.kind, updated: false, reason: "update_claim_stuck", hint },
@@ -24475,14 +25164,14 @@ function newClaimToken() {
24475
25164
  }
24476
25165
  async function claimOwner() {
24477
25166
  try {
24478
- return (await readFile29(join31(brandHome(), "update-running"), "utf8")).trim() || null;
25167
+ return (await readFile30(join32(brandHome(), "update-running"), "utf8")).trim() || null;
24479
25168
  } catch {
24480
25169
  return null;
24481
25170
  }
24482
25171
  }
24483
25172
  async function releaseUpdateSlot(token) {
24484
25173
  if (await claimOwner() !== token) return;
24485
- await unlink6(join31(brandHome(), "update-running")).catch(() => void 0);
25174
+ await unlink7(join32(brandHome(), "update-running")).catch(() => void 0);
24486
25175
  }
24487
25176
  async function holdUpdateSlot(inherited) {
24488
25177
  if (inherited !== void 0 && await claimOwner() === inherited) return inherited;
@@ -24494,9 +25183,9 @@ async function holdUpdateSlot(inherited) {
24494
25183
  var HOOK_VERBS = /* @__PURE__ */ new Set(["guard", "resurface"]);
24495
25184
  var UPDATE_CLAIM_MS = 15 * 6e4;
24496
25185
  async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
24497
- const path = join31(brandHome(), "update-running");
25186
+ const path = join32(brandHome(), "update-running");
24498
25187
  try {
24499
- await mkdir19(brandHome(), { recursive: true });
25188
+ await mkdir20(brandHome(), { recursive: true });
24500
25189
  const handle = await open(path, "wx");
24501
25190
  await handle.writeFile(`${token}
24502
25191
  `);
@@ -24506,12 +25195,12 @@ async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
24506
25195
  if (err?.code !== "EEXIST") return false;
24507
25196
  }
24508
25197
  try {
24509
- if (Date.now() - (await stat9(path)).mtimeMs >= UPDATE_CLAIM_MS) {
25198
+ if (Date.now() - (await stat10(path)).mtimeMs >= UPDATE_CLAIM_MS) {
24510
25199
  await hooks.afterObserve?.();
24511
25200
  await withReclaimLock(async () => {
24512
- if (Date.now() - (await stat9(path)).mtimeMs < UPDATE_CLAIM_MS) return;
25201
+ if (Date.now() - (await stat10(path)).mtimeMs < UPDATE_CLAIM_MS) return;
24513
25202
  await hooks.afterRecheck?.();
24514
- await unlink6(path);
25203
+ await unlink7(path);
24515
25204
  });
24516
25205
  }
24517
25206
  } catch {
@@ -24522,10 +25211,10 @@ var RECLAIM_LOCK = "update-running.reclaim";
24522
25211
  var RECLAIM_LOCK_MAX_MS = 10 * 6e4;
24523
25212
  async function withReclaimLock(fn) {
24524
25213
  const home = brandHome();
24525
- const lock = join31(home, RECLAIM_LOCK);
24526
- const staging = join31(home, `${RECLAIM_LOCK}.${process.pid}.${randomBytes2(4).toString("hex")}`);
24527
- await mkdir19(staging, { recursive: true });
24528
- await writeFile16(join31(staging, "owner"), `${process.pid} ${Date.now()}
25214
+ const lock = join32(home, RECLAIM_LOCK);
25215
+ const staging = join32(home, `${RECLAIM_LOCK}.${process.pid}.${randomBytes2(4).toString("hex")}`);
25216
+ await mkdir20(staging, { recursive: true });
25217
+ await writeFile16(join32(staging, "owner"), `${process.pid} ${Date.now()}
24529
25218
  `);
24530
25219
  try {
24531
25220
  await rename5(staging, lock);
@@ -24543,11 +25232,11 @@ async function withReclaimLock(fn) {
24543
25232
  }
24544
25233
  async function breakAbandonedLock(lock) {
24545
25234
  try {
24546
- const seen = (await readFile29(join31(lock, "owner"), "utf8")).trim();
25235
+ const seen = (await readFile30(join32(lock, "owner"), "utf8")).trim();
24547
25236
  if (!lockAbandoned(seen)) return;
24548
25237
  const aside = `${lock}.dead.${randomBytes2(4).toString("hex")}`;
24549
25238
  await rename5(lock, aside);
24550
- const moved = (await readFile29(join31(aside, "owner"), "utf8").catch(() => "")).trim();
25239
+ const moved = (await readFile30(join32(aside, "owner"), "utf8").catch(() => "")).trim();
24551
25240
  if (moved !== seen) {
24552
25241
  await rename5(aside, lock).catch(() => void 0);
24553
25242
  return;
@@ -24571,9 +25260,9 @@ function lockAbandoned(owner, now = Date.now()) {
24571
25260
  }
24572
25261
  async function blockedBy() {
24573
25262
  try {
24574
- const old = Date.now() - (await stat9(join31(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
25263
+ const old = Date.now() - (await stat10(join32(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
24575
25264
  if (old) {
24576
- await stat9(join31(brandHome(), RECLAIM_LOCK));
25265
+ await stat10(join32(brandHome(), RECLAIM_LOCK));
24577
25266
  return "stuck";
24578
25267
  }
24579
25268
  } catch {
@@ -24605,16 +25294,16 @@ function reportBelowMinimum(below) {
24605
25294
 
24606
25295
  // src/commands/undo.ts
24607
25296
  init_src();
24608
- import { readFile as readFile30, rm as rm9 } from "fs/promises";
24609
- import { join as join32 } from "path";
24610
- import { Command as Command42 } from "commander";
25297
+ import { readFile as readFile31, rm as rm9 } from "fs/promises";
25298
+ import { join as join33 } from "path";
25299
+ import { Command as Command43 } from "commander";
24611
25300
  init_atomic();
24612
25301
  init_errors();
24613
25302
  init_cloud_brain();
24614
25303
  init_vault();
24615
25304
  init_ui();
24616
25305
  function undoCommand() {
24617
- return new Command42("undo").description("take back the last change, or a named one").argument("[op]", "operation id (default: the most recent)").option("--reason <text>", "why it is being taken back \u2014 kept in the ledger").option("--vault <path>", "which brain to act on").option("--local", "act on the brain on this machine, even when signed in to a hosted one").action(
25306
+ return new Command43("undo").description("take back the last change, or a named one").argument("[op]", "operation id (default: the most recent)").option("--reason <text>", "why it is being taken back \u2014 kept in the ledger").option("--vault <path>", "which brain to act on").option("--local", "act on the brain on this machine, even when signed in to a hosted one").action(
24618
25307
  async (opId, opts) => {
24619
25308
  const rationale = sanitizeRationale(opts.reason);
24620
25309
  const where = await resolveTarget({
@@ -24703,8 +25392,8 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
24703
25392
  );
24704
25393
  }
24705
25394
  const record = await withWriteLock(brain.root, async () => {
24706
- const absolute = join32(brain.root, target.target);
24707
- const current = await readFile30(absolute, "utf8").catch(() => null);
25395
+ const absolute = join33(brain.root, target.target);
25396
+ const current = await readFile31(absolute, "utf8").catch(() => null);
24708
25397
  const actualHash = current === null ? null : hashContent(current);
24709
25398
  if (actualHash !== target.hash_after) {
24710
25399
  throw memgError(
@@ -24766,7 +25455,7 @@ function minutesSince(iso) {
24766
25455
  return Math.max(0, Math.floor((Date.now() - at) / 6e4));
24767
25456
  }
24768
25457
  async function reverse(brain, op, current) {
24769
- const absolute = join32(brain.root, op.target);
25458
+ const absolute = join33(brain.root, op.target);
24770
25459
  if (op.verb === "remember") {
24771
25460
  if (current === null) {
24772
25461
  throw memgError(
@@ -24823,7 +25512,7 @@ async function conflictHint(vaultRoot, op) {
24823
25512
  Read what it says now, then decide what should stay.`;
24824
25513
  const snapshot = await readSnapshot(vaultRoot, op.op_id);
24825
25514
  return snapshot === null ? base2 : `${base2}
24826
- The text from before that change is still on this machine: ${join32(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
25515
+ The text from before that change is still on this machine: ${join33(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
24827
25516
  }
24828
25517
 
24829
25518
  // src/commands/brains.ts
@@ -24832,11 +25521,11 @@ init_api_client();
24832
25521
  init_cloud_brain();
24833
25522
  init_config();
24834
25523
  init_ui();
24835
- import { Command as Command43 } from "commander";
25524
+ import { Command as Command44 } from "commander";
24836
25525
  import stringWidth from "string-width";
24837
25526
  var NAME_WIDTH2 = 48;
24838
25527
  function brainsCommand() {
24839
- return new Command43("brains").description("list the hosted brains on your account, and which one this machine reads").addHelpText(
25528
+ return new Command44("brains").description("list the hosted brains on your account, and which one this machine reads").addHelpText(
24840
25529
  "after",
24841
25530
  "\nReads only. `memgineering use --brain <name>` points this machine at one of\nthem. `memgineering create <name>` makes a new empty one, and\n`push --brain <name>` makes one from a folder on this machine."
24842
25531
  ).action(async () => {
@@ -24916,7 +25605,7 @@ init_credentials();
24916
25605
  init_cloud_brain();
24917
25606
  init_config();
24918
25607
  import { isAbsolute as isAbsolute5, relative as relative2, resolve as resolve16 } from "path";
24919
- import { Command as Command44 } from "commander";
25608
+ import { Command as Command45 } from "commander";
24920
25609
  init_vault();
24921
25610
  init_ui();
24922
25611
  function lostDefaultLines(missing) {
@@ -24935,7 +25624,7 @@ var VIA_MEANS = {
24935
25624
  default: "because it is your default brain (`memgineering use --default <path>` changes it)"
24936
25625
  };
24937
25626
  function useCommand() {
24938
- return new Command44("use").description(
25627
+ return new Command45("use").description(
24939
25628
  "say which brain answers here \u2014 `--brain <name>` for the whole machine, or a path to bind this folder"
24940
25629
  ).argument("[brain]", "path to the brain \u2014 omit to show what this folder resolves to").option("--brain <name>", "point this machine at a hosted brain, by its name or id").option("--here <dir>", "write the pointer in this directory instead of the cwd").option(
24941
25630
  "--default",
@@ -25243,9 +25932,9 @@ async function pointAtHosted(named, opts, path) {
25243
25932
  // src/commands/whoami.ts
25244
25933
  init_api_client();
25245
25934
  init_ui();
25246
- import { Command as Command45 } from "commander";
25935
+ import { Command as Command46 } from "commander";
25247
25936
  function whoamiCommand() {
25248
- return new Command45("whoami").description("show which account this machine is signed in as").action(async () => {
25937
+ return new Command46("whoami").description("show which account this machine is signed in as").action(async () => {
25249
25938
  const base2 = apiUrl();
25250
25939
  const credentials = await requireCredentials(base2);
25251
25940
  const res = await apiRequest({
@@ -25320,7 +26009,7 @@ var NO_FLAT_ALIAS = new Set(HOMONYM_VERBS);
25320
26009
  // src/program.ts
25321
26010
  init_version();
25322
26011
  function buildProgram() {
25323
- const program = new Command46("memgineering").version(VERSION).description("memgineering \u2014 one memory for the AI you connect").option("--json", "emit JSON to stdout instead of markdown", false).hook("preAction", async (thisCommand, actionCommand) => {
26012
+ const program = new Command47("memgineering").version(VERSION).description("memgineering \u2014 one memory for the AI you connect").option("--json", "emit JSON to stdout instead of markdown", false).hook("preAction", async (thisCommand, actionCommand) => {
25324
26013
  const opts = thisCommand.optsWithGlobals();
25325
26014
  if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
25326
26015
  await announceNextStep(commandPath(actionCommand), "verb");
@@ -25349,7 +26038,7 @@ function buildProgram() {
25349
26038
  diffCommand,
25350
26039
  listCommand
25351
26040
  ];
25352
- const registry = new Command46("registry").description(
26041
+ const registry = new Command47("registry").description(
25353
26042
  "the public layer: find, read, adapt, publish, install and keep packages current"
25354
26043
  );
25355
26044
  for (const factory of publicLayer) registry.addCommand(factory());
@@ -25387,6 +26076,7 @@ function buildProgram() {
25387
26076
  program.addCommand(setupCommand());
25388
26077
  program.addCommand(loginCommand());
25389
26078
  program.addCommand(logoutCommand());
26079
+ program.addCommand(deleteAccountCommand());
25390
26080
  program.addCommand(whoamiCommand());
25391
26081
  program.addCommand(updateCommand());
25392
26082
  makeParseErrorsActionable(program);
@@ -25406,6 +26096,7 @@ var WITHOUT_NEXT_STEP = /* @__PURE__ */ new Set([
25406
26096
  "setup",
25407
26097
  "login",
25408
26098
  "logout",
26099
+ "delete-account",
25409
26100
  "whoami",
25410
26101
  "update",
25411
26102
  "guard",