@persistmemory/cli 0.8.1 → 0.9.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
@@ -1469,7 +1469,8 @@ function pathsFor(home = homedir()) {
1469
1469
  return {
1470
1470
  dir,
1471
1471
  config: join(dir, "config.json"),
1472
- credentials: join(dir, "credentials.json")
1472
+ credentials: join(dir, "credentials.json"),
1473
+ agentPid: join(dir, "agent.pid")
1473
1474
  };
1474
1475
  }
1475
1476
  function readConfig(paths) {
@@ -1709,7 +1710,7 @@ function shortDate(iso) {
1709
1710
  }
1710
1711
 
1711
1712
  // src/help.ts
1712
- var VERSION = true ? "0.8.1" : versionFromManifest();
1713
+ var VERSION = true ? "0.9.0" : versionFromManifest();
1713
1714
  var PACKAGE = "@persistmemory/cli";
1714
1715
  var HELP = `
1715
1716
  pm \u2014 PersistMemory from your terminal
@@ -1778,9 +1779,10 @@ var HELP = `
1778
1779
  approve every request first, and see the
1779
1780
  exact path or command before you do.
1780
1781
  Stays in the foreground; background it
1781
- and stop it later with:
1782
+ with:
1782
1783
  nohup pm agent </dev/null >> ~/agent.log 2>&1 &
1783
- pkill -f "pm agent"
1784
+ agent stop stop the agent running on this machine.
1785
+ Lets the request in flight finish first.
1784
1786
  agent --root <dir> [--root ...] narrow it to these folders, for this run
1785
1787
  agent enable [hostname] turn this machine back on after it was
1786
1788
  switched off for going quiet. Asks you to
@@ -2513,7 +2515,8 @@ async function readStdin() {
2513
2515
  import { hostname } from "node:os";
2514
2516
  import { homedir as homedir2 } from "node:os";
2515
2517
  import { basename, join as join6, resolve as resolve3 } from "node:path";
2516
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync, statSync as statSync5, writeFileSync as writeFileSync5 } from "node:fs";
2518
+ import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
2519
+ import { execFileSync } from "node:child_process";
2517
2520
 
2518
2521
  // src/files.ts
2519
2522
  import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync2, writeFileSync as writeFileSync4 } from "node:fs";
@@ -7287,8 +7290,8 @@ var ENDS = /,|\s+(?:and|then|so|but|because|which|where|that|with|to)\s+/i;
7287
7290
  var ASKS_FOR_A_SPACE = /\b(?:make|create|start|open|add|set\s?up|setup|spin\s?up|new)\b[^.;!?]{0,40}?\b(?:work[\s-]?)?spaces?\b/gi;
7288
7291
  var NAMED_A_SPACE = /\b(?:make|create|start|open|add|set\s?up|setup|spin\s?up|new)\b(?:\s+(?:me|us))?(?:\s+(?:a|an|the|my|our|another|one|new|second|separate|empty))*\s+(?:work[\s-]?)?spaces?\b(?:\s*(?:called|named|titled|for|about))?\s*(?:[:\-–—]\s*)?([^.;!?]*)/gi;
7289
7292
  var SPACE_NAMED_FIRST = /\b(?:make|create|start|open|add|set\s?up|setup|spin\s?up|new)\b(?:\s+(?:a|an|the|my|our|another|one|new|second|separate|empty))*\s+((?:[\p{L}\p{N}][\p{L}\p{N}&'.-]*\s+){0,2}[\p{L}\p{N}][\p{L}\p{N}&'.-]*)\s+(?:work\s?)?spaces?\b/giu;
7290
- function flatten(said2) {
7291
- return said2.normalize("NFKC").replace(/[‘’‚‛′]/g, "'").replace(/[“”„‟″«»]/g, '"').replace(/\s+/g, " ").trim();
7293
+ function flatten(said3) {
7294
+ return said3.normalize("NFKC").replace(/[‘’‚‛′]/g, "'").replace(/[“”„‟″«»]/g, '"').replace(/\s+/g, " ").trim();
7292
7295
  }
7293
7296
  function tidy(name) {
7294
7297
  return name.replace(/^["']+|["']+$/g, "").replace(/[.,;:!?]+$/g, "").replace(/\s+/g, " ").trim().toLowerCase();
@@ -7319,9 +7322,9 @@ function readings(tail) {
7319
7322
  }
7320
7323
  return found;
7321
7324
  }
7322
- function spacesTheyAskedToMake(said2) {
7323
- if (typeof said2 !== "string") return [];
7324
- const text = flatten(said2);
7325
+ function spacesTheyAskedToMake(said3) {
7326
+ if (typeof said3 !== "string") return [];
7327
+ const text = flatten(said3);
7325
7328
  if (text === "" || text.length > AT_MOST) return [];
7326
7329
  if ([...text.matchAll(ASKS_FOR_A_SPACE)].length !== 1) return [];
7327
7330
  const named = [...text.matchAll(NAMED_A_SPACE)];
@@ -7334,11 +7337,11 @@ function spacesTheyAskedToMake(said2) {
7334
7337
  const only = tidy((first[0]?.[1] ?? "").replace(LEADING, ""));
7335
7338
  return nameable(only) ? [only] : [];
7336
7339
  }
7337
- function askedToMakeIt(said2, name) {
7338
- if (typeof said2 !== "string" || typeof name !== "string") return false;
7340
+ function askedToMakeIt(said3, name) {
7341
+ if (typeof said3 !== "string" || typeof name !== "string") return false;
7339
7342
  const wanted2 = tidy(name);
7340
7343
  if (!nameable(wanted2)) return false;
7341
- return spacesTheyAskedToMake(said2).includes(wanted2);
7344
+ return spacesTheyAskedToMake(said3).includes(wanted2);
7342
7345
  }
7343
7346
 
7344
7347
  // ../tools/src/catalogue.ts
@@ -7790,10 +7793,10 @@ var TOOLS = [
7790
7793
  },
7791
7794
  ...context.origin?.askedBy ? { confirmedBy: context.origin.askedBy } : {}
7792
7795
  });
7793
- const said2 = `Forwarded to ${String(args.to)}. It went whole, with everything that was attached to it.`;
7796
+ const said3 = `Forwarded to ${String(args.to)}. It went whole, with everything that was attached to it.`;
7794
7797
  return {
7795
- model: `${said2} Report what happened and stop. Do not forward anything else and do not offer to \u2014 another message, or another address, is a new decision that is theirs to make.`,
7796
- person: `${said2} There is no unsending it \u2014 if it went to the wrong address, tell them.`
7798
+ model: `${said3} Report what happened and stop. Do not forward anything else and do not offer to \u2014 another message, or another address, is a new decision that is theirs to make.`,
7799
+ person: `${said3} There is no unsending it \u2014 if it went to the wrong address, tell them.`
7797
7800
  };
7798
7801
  }
7799
7802
  },
@@ -8237,15 +8240,15 @@ var TOOLS = [
8237
8240
  a wider search that still finds the file beats a refusal.
8238
8241
  */
8239
8242
  argsFrom: (rest) => {
8240
- const said2 = rest.trim();
8241
- if (!said2) return void 0;
8242
- const at = said2.lastIndexOf(" in ");
8243
+ const said3 = rest.trim();
8244
+ if (!said3) return void 0;
8245
+ const at = said3.lastIndexOf(" in ");
8243
8246
  if (at > 0) {
8244
- const what = said2.slice(0, at).trim();
8245
- const where = said2.slice(at + 4).trim();
8247
+ const what = said3.slice(0, at).trim();
8248
+ const where = said3.slice(at + 4).trim();
8246
8249
  if (what && where && !where.includes(" ")) return { what, in: where };
8247
8250
  }
8248
- return { what: said2 };
8251
+ return { what: said3 };
8249
8252
  },
8250
8253
  missing: "Say what to look for, like: /find deployment notes in ~/Documents"
8251
8254
  },
@@ -9112,10 +9115,10 @@ var TOOLS = [
9112
9115
  ...context.origin?.askedBy ? { askedBy: context.origin.askedBy } : {}
9113
9116
  });
9114
9117
  if (!offered.ok) return offered.error;
9115
- const said2 = `Invited ${offered.value.email} to \u201C${held.space.name}\u201D as ${offered.value.role}. They cannot see anything yet \u2014 the invitation has to be accepted first.`;
9118
+ const said3 = `Invited ${offered.value.email} to \u201C${held.space.name}\u201D as ${offered.value.role}. They cannot see anything yet \u2014 the invitation has to be accepted first.`;
9116
9119
  return {
9117
- model: `${said2} That grant covers EVERYTHING in that Space, now and later. Report what happened and stop. Do not share anything else, and do not offer to \u2014 another Space, another address, or a wider role is a new decision that is theirs to make.`,
9118
- person: `${said2} While it stands they will see everything in \u201C${held.space.name}\u201D, including memories filed into it after today. Undo it with /unshare ${held.space.name} ${offered.value.email}`
9120
+ model: `${said3} That grant covers EVERYTHING in that Space, now and later. Report what happened and stop. Do not share anything else, and do not offer to \u2014 another Space, another address, or a wider role is a new decision that is theirs to make.`,
9121
+ person: `${said3} While it stands they will see everything in \u201C${held.space.name}\u201D, including memories filed into it after today. Undo it with /unshare ${held.space.name} ${offered.value.email}`
9119
9122
  };
9120
9123
  }
9121
9124
  },
@@ -9260,8 +9263,8 @@ var TOOLS = [
9260
9263
  ...context.origin?.askedBy ? { askedBy: context.origin.askedBy } : {}
9261
9264
  });
9262
9265
  if (!changed.ok) return changed.error;
9263
- const said2 = `${changed.value.email} is now ${changed.value.role} on \u201C${held.space.name}\u201D.`;
9264
- return changed.value.role === "owner" ? `${said2} As an owner they can share it onward and revoke anybody, including you.` : said2;
9266
+ const said3 = `${changed.value.email} is now ${changed.value.role} on \u201C${held.space.name}\u201D.`;
9267
+ return changed.value.role === "owner" ? `${said3} As an owner they can share it onward and revoke anybody, including you.` : said3;
9265
9268
  }
9266
9269
  },
9267
9270
  /* --------------------- which Space this is filing into --------------------- */
@@ -9515,11 +9518,11 @@ RIGHT NOW: ${where} is filing into ${from}.`;
9515
9518
  Both conditions, and every unclear case is false. See
9516
9519
  `their-own-words.ts` for what this does not cover.
9517
9520
  */
9518
- directly(args, facts, said2) {
9521
+ directly(args, facts, said3) {
9519
9522
  if (facts["creating"] !== true) return false;
9520
9523
  const wanted2 = oneLine2(args.space, 200);
9521
9524
  if (wanted2 === void 0) return false;
9522
- return askedToMakeIt(said2, wanted2);
9525
+ return askedToMakeIt(said3, wanted2);
9523
9526
  }
9524
9527
  },
9525
9528
  description: "Files everything sent from this conversation into one Space from now on, and answers questions asked here from that Space alone. NOTHING IS KEPT AT ALL until this is done: there is no default Space and nothing picks one, so a conversation with no Space chosen refuses every note. Use it when the person says something like 'work in Acme', 'put all of this in my Work space', or when they have been told nothing is being kept and they name where it should go. Pass no Space to stop working in one, which means nothing sent here is kept until they choose again. Set `create` ONLY when they are asking for a Space that does not exist yet \u2014 usually because the account has none at all \u2014 and never to resolve a name you are unsure of. WHAT HAPPENS WHEN YOU CALL THIS DEPENDS ON THE ACT, AND THE ANSWER SAYS WHICH: moving into a Space that already exists is put in front of the person and they have to release it themselves, so say it is waiting for them and stop; making a NEW one, when they asked for it in their own message, simply happens, and the answer says so. Read what comes back and repeat what it says. Never say something is done unless it says so, never say something is waiting when it says it is done, and never invent a confirmation code.",
@@ -9939,11 +9942,31 @@ function avoided(path) {
9939
9942
  return path.split(sep).some((segment) => segment.startsWith(".") || NEVER2.has(segment));
9940
9943
  }
9941
9944
 
9942
- // src/commands/agent.ts
9945
+ // src/said.ts
9943
9946
  async function said(response, fallback) {
9944
- const body = await response.json().catch(() => void 0);
9945
- const message2 = typeof body === "object" && body !== null && "error" in body ? body.error?.message : void 0;
9946
- return message2 ?? `${fallback} (${response.status})`;
9947
+ const raw = await response.text().catch(() => "");
9948
+ return reasonFrom(raw, response.status, fallback);
9949
+ }
9950
+ function reasonFrom(raw, status2, fallback) {
9951
+ let body;
9952
+ try {
9953
+ body = JSON.parse(raw);
9954
+ } catch {
9955
+ body = void 0;
9956
+ }
9957
+ if (typeof body?.error === "string") {
9958
+ const description = body.error_description?.trim();
9959
+ return description ? `${description} (${body.error})` : `${body.error} (${status2})`;
9960
+ }
9961
+ const message2 = typeof body?.error === "object" ? body.error?.message?.trim() : void 0;
9962
+ if (message2) return message2;
9963
+ const excerpt = raw.trim().replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").slice(0, 160);
9964
+ return excerpt ? `${fallback} (${status2}): ${excerpt}` : `${fallback} (${status2})`;
9965
+ }
9966
+
9967
+ // src/commands/agent.ts
9968
+ async function said2(response, fallback) {
9969
+ return said(response, fallback);
9947
9970
  }
9948
9971
  function locate(roots, requested) {
9949
9972
  const expanded = requested.startsWith("~") ? resolve3(homedir2(), requested.slice(1).replace(/^[/\\]/, "")) : requested;
@@ -10018,7 +10041,7 @@ async function answer(context, apiUrl, token, roots, request) {
10018
10041
  try {
10019
10042
  fetched = await fetch(request.sourceUrl);
10020
10043
  if (!fetched.ok) {
10021
- return { ok: false, error: await said(fetched, "The file could not be fetched") };
10044
+ return { ok: false, error: await said2(fetched, "The file could not be fetched") };
10022
10045
  }
10023
10046
  downloaded = Buffer.from(await fetched.arrayBuffer());
10024
10047
  } catch {
@@ -10087,7 +10110,7 @@ async function upload(apiUrl, token, filename, bytes) {
10087
10110
  })
10088
10111
  });
10089
10112
  if (!grant.ok) {
10090
- return { ok: false, error: await said(grant, "could not get an upload url") };
10113
+ return { ok: false, error: await said2(grant, "could not get an upload url") };
10091
10114
  }
10092
10115
  const { uploadUrl, contentType, maxBytes } = await grant.json();
10093
10116
  const limit = maxBytes ?? MAX_TRANSFER_BYTES;
@@ -10143,7 +10166,7 @@ async function enableCommand(context) {
10143
10166
  headers: { authorization: `Bearer ${await token()}` }
10144
10167
  });
10145
10168
  if (!listed.ok) {
10146
- context.error(await said(listed, "could not read this account's machines"));
10169
+ context.error(await said2(listed, "could not read this account's machines"));
10147
10170
  return 1;
10148
10171
  }
10149
10172
  const { items } = await listed.json();
@@ -10186,7 +10209,7 @@ Run this on ${machine.hostname} itself, or turn it back on from your dashboard.`
10186
10209
  { method: "POST", headers: { authorization: `Bearer ${await token()}` } }
10187
10210
  );
10188
10211
  if (!enabled.ok) {
10189
- context.error(await said(enabled, "could not turn that machine back on"));
10212
+ context.error(await said2(enabled, "could not turn that machine back on"));
10190
10213
  return 1;
10191
10214
  }
10192
10215
  const result = await enabled.json();
@@ -10199,8 +10222,94 @@ Run this on ${machine.hostname} itself, or turn it back on from your dashboard.`
10199
10222
  );
10200
10223
  return 0;
10201
10224
  }
10225
+ function agentIsRunningAs(pid) {
10226
+ try {
10227
+ process.kill(pid, 0);
10228
+ } catch {
10229
+ return false;
10230
+ }
10231
+ if (process.platform === "win32") return void 0;
10232
+ try {
10233
+ const line2 = execFileSync("ps", ["-p", String(pid), "-o", "command="], {
10234
+ encoding: "utf8",
10235
+ timeout: 5e3
10236
+ });
10237
+ return /\bpm\b|persistmemory|\bagent\b/i.test(line2);
10238
+ } catch {
10239
+ return void 0;
10240
+ }
10241
+ }
10242
+ function processState(pid) {
10243
+ if (process.platform === "win32") return void 0;
10244
+ try {
10245
+ return execFileSync("ps", ["-p", String(pid), "-o", "stat="], {
10246
+ encoding: "utf8",
10247
+ timeout: 5e3
10248
+ }).trim();
10249
+ } catch {
10250
+ return void 0;
10251
+ }
10252
+ }
10253
+ async function stopCommand(context) {
10254
+ const file = context.paths.agentPid;
10255
+ if (!existsSync5(file)) {
10256
+ context.print("No agent is running on this machine.");
10257
+ context.print('If one is running from another home directory, stop it with: pkill -f "pm agent"');
10258
+ return 0;
10259
+ }
10260
+ const pid = Number.parseInt(readFileSync6(file, "utf8").trim(), 10);
10261
+ if (!Number.isInteger(pid) || pid <= 0) {
10262
+ unlinkSync2(file);
10263
+ context.error("The agent's pid file was unreadable, so it has been removed.");
10264
+ context.error('If an agent is still running, stop it with: pkill -f "pm agent"');
10265
+ return 1;
10266
+ }
10267
+ const mine = agentIsRunningAs(pid);
10268
+ if (mine === false) {
10269
+ unlinkSync2(file);
10270
+ context.print("No agent is running. Cleared a stale pid file.");
10271
+ return 0;
10272
+ }
10273
+ if (mine === void 0) {
10274
+ context.error(`Found a pid file naming process ${pid}, but could not confirm it is the agent.`);
10275
+ context.error("Refusing to signal a process this command cannot identify.");
10276
+ context.error(`If that is the agent, stop it with: kill ${pid}`);
10277
+ return 1;
10278
+ }
10279
+ try {
10280
+ process.kill(pid, "SIGCONT");
10281
+ } catch {
10282
+ }
10283
+ process.kill(pid, "SIGTERM");
10284
+ context.print(`Asked the agent (${pid}) to stop. Any request in flight will finish first.`);
10285
+ for (let waited = 0; waited < 1e4; waited += 250) {
10286
+ await new Promise((r) => setTimeout(r, 250));
10287
+ if (agentIsRunningAs(pid) === false) {
10288
+ if (existsSync5(file)) unlinkSync2(file);
10289
+ context.print("Stopped.");
10290
+ return 0;
10291
+ }
10292
+ }
10293
+ if (processState(pid)?.startsWith("T")) {
10294
+ context.error(`The agent (${pid}) is suspended and did not respond to being resumed.`);
10295
+ context.error(`Stop it with: kill -CONT ${pid} && kill -9 ${pid}`);
10296
+ return 1;
10297
+ }
10298
+ context.print("Still finishing a request. It will exit on its own; nothing more to do.");
10299
+ return 0;
10300
+ }
10202
10301
  async function agentCommand(context) {
10203
10302
  if (context.args.words[1] === "enable") return enableCommand(context);
10303
+ if (context.args.words[1] === "stop") return stopCommand(context);
10304
+ const word = context.args.words[1];
10305
+ if (word !== void 0) {
10306
+ context.error(`pm agent: there is no "${word}" subcommand.`);
10307
+ context.error("");
10308
+ context.error(" pm agent start answering requests here");
10309
+ context.error(" pm agent stop stop the agent running here");
10310
+ context.error(" pm agent enable [host] turn a machine back on after it was switched off");
10311
+ return 2;
10312
+ }
10204
10313
  const write3 = (message2, error = false) => {
10205
10314
  const at = (/* @__PURE__ */ new Date()).toISOString();
10206
10315
  for (const line2 of message2.split("\n")) {
@@ -10241,7 +10350,20 @@ async function agentCommand(context) {
10241
10350
  const every = Math.max(2, asked ?? 5) * 1e3;
10242
10351
  write3(`Answering as ${name}, from: ${roots.join(", ")}`);
10243
10352
  write3("Nothing outside those folders can be read. Ctrl-C to stop.");
10353
+ writeFileSync5(context.paths.agentPid, `${process.pid}
10354
+ `, { mode: 384 });
10355
+ const forgetPid = () => {
10356
+ try {
10357
+ if (existsSync5(context.paths.agentPid)) {
10358
+ const held = Number.parseInt(readFileSync6(context.paths.agentPid, "utf8").trim(), 10);
10359
+ if (held === process.pid) unlinkSync2(context.paths.agentPid);
10360
+ }
10361
+ } catch {
10362
+ }
10363
+ };
10364
+ process.on("exit", forgetPid);
10244
10365
  let running = true;
10366
+ let signedOut = false;
10245
10367
  const stop = () => {
10246
10368
  if (!running) return;
10247
10369
  running = false;
@@ -10281,7 +10403,7 @@ async function agentCommand(context) {
10281
10403
  version: VERSION
10282
10404
  });
10283
10405
  if (!beat.ok) {
10284
- complain2(await said(beat, "the service refused this machine"));
10406
+ complain2(await said2(beat, "the service refused this machine"));
10285
10407
  } else {
10286
10408
  const state = await beat.json();
10287
10409
  if (state.status === "disabled") {
@@ -10298,7 +10420,7 @@ async function agentCommand(context) {
10298
10420
  }
10299
10421
  const claimed = await call("claim", { hostname: name, limit: 5 });
10300
10422
  if (!claimed.ok) {
10301
- complain2(await said(claimed, "could not pick up work"));
10423
+ complain2(await said2(claimed, "could not pick up work"));
10302
10424
  } else {
10303
10425
  working();
10304
10426
  const { items } = await claimed.json();
@@ -10312,18 +10434,28 @@ async function agentCommand(context) {
10312
10434
  outcome.ok ? { result: { attachToken: outcome.attachToken } } : { error: outcome.error }
10313
10435
  );
10314
10436
  if (!done.ok) {
10315
- write3(`Stored, but the service did not record it: ${await said(done, "refused")}`, true);
10437
+ write3(`Stored, but the service did not record it: ${await said2(done, "refused")}`, true);
10316
10438
  continue;
10317
10439
  }
10318
10440
  write3(outcome.ok ? `Sent ${outcome.bytes} bytes` : `Refused: ${outcome.error}`);
10319
10441
  }
10320
10442
  }
10321
10443
  } catch (error) {
10322
- complain2(error instanceof Error ? error.message : "connection failed");
10444
+ const message2 = error instanceof Error ? error.message : "connection failed";
10445
+ if (/invalid_grant/i.test(message2)) {
10446
+ write3(`This machine is signed out: ${message2}`, true);
10447
+ write3("Sign in again with: pm auth login", true);
10448
+ write3("The agent is stopping \u2014 retrying cannot renew a revoked token.", true);
10449
+ running = false;
10450
+ signedOut = true;
10451
+ continue;
10452
+ }
10453
+ complain2(message2);
10323
10454
  }
10324
10455
  if (running) await new Promise((r) => setTimeout(r, every));
10325
10456
  }
10326
- return 0;
10457
+ forgetPid();
10458
+ return signedOut ? 1 : 0;
10327
10459
  }
10328
10460
 
10329
10461
  // src/commands/google.ts
@@ -10346,8 +10478,7 @@ async function callApi(context, path, init = {}) {
10346
10478
  });
10347
10479
  }
10348
10480
  async function complain(context, response) {
10349
- const body = await response.json().catch(() => void 0);
10350
- context.error(body?.error?.message ?? `That failed (${response.status}).`);
10481
+ context.error(await said(response, "That failed"));
10351
10482
  return response.status === 409 ? 3 : 1;
10352
10483
  }
10353
10484
  async function driveCommand(context) {
@@ -10548,8 +10679,7 @@ async function collectCommand(context) {
10548
10679
  { headers: { authorization: `Bearer ${credential.token}` } }
10549
10680
  );
10550
10681
  if (!link.ok) {
10551
- const body = await link.json().catch(() => void 0);
10552
- context.error(body?.error?.message ?? `Could not prepare that file (${link.status}).`);
10682
+ context.error(await said(link, "Could not prepare that file"));
10553
10683
  return 1;
10554
10684
  }
10555
10685
  const { downloadUrl, filename } = await link.json();
@@ -10644,8 +10774,7 @@ async function setupCommand(context) {
10644
10774
  }
10645
10775
  async function chooseSpace(context, client, spaces) {
10646
10776
  const { print, error } = context;
10647
- const found = findWorkspace();
10648
- const current = found?.config.space;
10777
+ const current = findWorkspace()?.config.space;
10649
10778
  const named = stringFlag(context.args, "space");
10650
10779
  const creating = stringFlag(context.args, "new-space");
10651
10780
  if (creating !== void 0) {
@@ -10667,48 +10796,46 @@ async function chooseSpace(context, client, spaces) {
10667
10796
  print(` Pass --space "<name>" or --new-space "<name>" to pick one.`);
10668
10797
  return 0;
10669
10798
  }
10670
- if (current) {
10671
- print(` This folder currently uses the Space "${current.name}".`);
10799
+ const live = current ? spaces.find((one) => one.id === current.id) : void 0;
10800
+ if (current && !live) {
10801
+ print(` This folder points at a Space called "${current.name}", which is no longer`);
10802
+ print(" in your account. Choose another one.");
10803
+ print("");
10804
+ } else if (live) {
10805
+ print(` This folder currently uses the Space "${live.name}".`);
10806
+ print("");
10807
+ }
10808
+ if (spaces.length === 0) {
10809
+ print(" You have no Spaces yet. Every memory is filed in one \u2014 a capture with");
10810
+ print(" no Space is refused, and names the Spaces you have \u2014 so this makes the first.");
10672
10811
  print("");
10812
+ return finish(context, await askForNewSpace(context, client), current);
10673
10813
  }
10674
10814
  print(" Which Space should this folder use?");
10675
10815
  print("");
10676
10816
  spaces.forEach((space, index) => {
10677
- const mark = current?.id === space.id ? " (current)" : "";
10817
+ const mark = live?.id === space.id ? " (current)" : "";
10678
10818
  const count = space.memoryCount === void 0 ? "" : `, ${space.memoryCount} memories`;
10679
10819
  print(` ${index + 1}. ${space.name}${mark} ${space.kind}${count}`);
10680
10820
  });
10681
10821
  const createIndex = spaces.length + 1;
10682
- const noneIndex = spaces.length + 2;
10683
10822
  print(` ${createIndex}. Create a new Space`);
10684
- print(` ${noneIndex}. No Space \u2014 use everything in my account`);
10685
10823
  print("");
10686
- const fallback = current ? "keep the current one" : String(createIndex);
10687
- const answer3 = await context.ask(` Choose 1-${noneIndex} [${fallback}]: `);
10824
+ const fallback = live ? "keep the current one" : String(createIndex);
10825
+ const answer3 = await context.ask(` Choose 1-${createIndex} [${fallback}]: `);
10688
10826
  if (answer3 === "") {
10689
- if (current) {
10827
+ if (live) {
10690
10828
  print("");
10691
- print(` Keeping "${current.name}".`);
10829
+ print(` Keeping "${live.name}".`);
10692
10830
  return 0;
10693
10831
  }
10694
10832
  return finish(context, await askForNewSpace(context, client), current);
10695
10833
  }
10696
10834
  const choice = Number(answer3);
10697
- if (!Number.isInteger(choice) || choice < 1 || choice > noneIndex) {
10835
+ if (!Number.isInteger(choice) || choice < 1 || choice > createIndex) {
10698
10836
  error(`"${answer3}" is not one of the choices. Nothing was changed.`);
10699
10837
  return 2;
10700
10838
  }
10701
- if (choice === noneIndex) {
10702
- if (found) {
10703
- writeWorkspace(found.dir, {});
10704
- print("");
10705
- print(` Cleared the Space in ${WORKSPACE_FILE}. This folder now uses everything.`);
10706
- } else {
10707
- print("");
10708
- print(" No Space. This folder uses everything in your account.");
10709
- }
10710
- return 0;
10711
- }
10712
10839
  if (choice === createIndex) {
10713
10840
  return finish(context, await askForNewSpace(context, client), current);
10714
10841
  }
@@ -11002,8 +11129,8 @@ Delete the file it runs from: ${processPath()}`
11002
11129
  }
11003
11130
  const result = run2(manager, ["uninstall", "-g", PACKAGE]);
11004
11131
  if (result.status !== 0) {
11005
- const said2 = npmSaid(result);
11006
- if (said2) context.error(said2);
11132
+ const said3 = npmSaid(result);
11133
+ if (said3) context.error(said3);
11007
11134
  context.error(`npm could not remove ${PACKAGE}.`);
11008
11135
  if (alsoDisconnect) reportDisconnect(context, disconnected, stubborn);
11009
11136
  return result.status ?? 1;
@@ -11039,8 +11166,8 @@ function reportDisconnect(context, disconnected, stubborn) {
11039
11166
  }
11040
11167
  function npmSaid(result) {
11041
11168
  if (result.error) return result.error.message;
11042
- const said2 = [result.stdout, result.stderr].map((stream) => (stream ?? "").trim()).filter((stream) => stream.length > 0).join("\n");
11043
- return said2.length > 0 ? said2 : void 0;
11169
+ const said3 = [result.stdout, result.stderr].map((stream) => (stream ?? "").trim()).filter((stream) => stream.length > 0).join("\n");
11170
+ return said3.length > 0 ? said3 : void 0;
11044
11171
  }
11045
11172
  async function deleteCommand(context) {
11046
11173
  if (!existsSync8(context.paths.dir)) {
@@ -11476,7 +11603,7 @@ function say(context, working, profile, what, surface) {
11476
11603
  what === "changed" ? `No longer working in a Space on ${where}.` : `No Space chosen on ${where}.`
11477
11604
  );
11478
11605
  context.print(
11479
- surface === "email" ? "Mail forwarded to your ingest address is HELD and not filed \u2014 there is no default and nothing picks one. Each held message is kept whole and readable, and forwarding it again once you have chosen is what captures it." : "Nothing `pm remember` sends is kept \u2014 there is no default and nothing picks one. Questions are still answered, from everything."
11606
+ surface === "email" ? "Mail forwarded to your ingest address is HELD and not filed \u2014 there is no default and nothing picks one. Each held message is kept whole and readable, and forwarding it again once you have chosen is what captures it." : "`pm remember` from here is REFUSED, naming the Spaces you have \u2014 it is not silently dropped, and nothing is lost \u2014 there is no default and nothing picks one. Questions are still answered, from everything."
11480
11607
  );
11481
11608
  context.print(
11482
11609
  surface === "email" ? 'Run `pm space --email "Work"` to choose, or `pm spaces create "Work"` first.' : 'Run `pm space "Work"` to choose, or `pm spaces create "Work"` if you have none.'