@cotal-ai/cli 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/command.d.ts.map +1 -1
  2. package/dist/command.js +14 -3
  3. package/dist/command.js.map +1 -1
  4. package/dist/commands/clean.d.ts +40 -0
  5. package/dist/commands/clean.d.ts.map +1 -0
  6. package/dist/commands/clean.js +138 -0
  7. package/dist/commands/clean.js.map +1 -0
  8. package/dist/commands/down.d.ts +15 -5
  9. package/dist/commands/down.d.ts.map +1 -1
  10. package/dist/commands/down.js +201 -66
  11. package/dist/commands/down.js.map +1 -1
  12. package/dist/commands/endpoints.d.ts +5 -0
  13. package/dist/commands/endpoints.d.ts.map +1 -0
  14. package/dist/commands/endpoints.js +42 -0
  15. package/dist/commands/endpoints.js.map +1 -0
  16. package/dist/commands/ext.d.ts +4 -4
  17. package/dist/commands/ext.d.ts.map +1 -1
  18. package/dist/commands/ext.js +327 -90
  19. package/dist/commands/ext.js.map +1 -1
  20. package/dist/commands/history.d.ts +3 -2
  21. package/dist/commands/history.d.ts.map +1 -1
  22. package/dist/commands/history.js +6 -21
  23. package/dist/commands/history.js.map +1 -1
  24. package/dist/commands/setup.js +2 -2
  25. package/dist/commands/setup.js.map +1 -1
  26. package/dist/commands/spawn-manifest.d.ts.map +1 -1
  27. package/dist/commands/spawn-manifest.js +23 -10
  28. package/dist/commands/spawn-manifest.js.map +1 -1
  29. package/dist/commands/spawn.d.ts +1 -1
  30. package/dist/commands/spawn.d.ts.map +1 -1
  31. package/dist/commands/spawn.js +3 -2
  32. package/dist/commands/spawn.js.map +1 -1
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +25 -26
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/commands/up.d.ts.map +1 -1
  37. package/dist/commands/up.js +7 -5
  38. package/dist/commands/up.js.map +1 -1
  39. package/dist/ext-loader.d.ts +12 -1
  40. package/dist/ext-loader.d.ts.map +1 -1
  41. package/dist/ext-loader.js +92 -8
  42. package/dist/ext-loader.js.map +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +70 -7
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/control.d.ts.map +1 -1
  47. package/dist/lib/control.js +8 -2
  48. package/dist/lib/control.js.map +1 -1
  49. package/dist/lib/manifest/model.d.ts +1 -1
  50. package/dist/lib/manifest/model.d.ts.map +1 -1
  51. package/dist/lib/manifest/schema.d.ts +1 -5
  52. package/dist/lib/manifest/schema.d.ts.map +1 -1
  53. package/dist/lib/manifest/schema.js +2 -1
  54. package/dist/lib/manifest/schema.js.map +1 -1
  55. package/dist/ui.d.ts +5 -0
  56. package/dist/ui.d.ts.map +1 -1
  57. package/dist/ui.js +12 -0
  58. package/dist/ui.js.map +1 -1
  59. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAuD7F,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B;;;oCAGgC;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;uFAEuF;AACvF,wBAAsB,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DxG"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA8D7F,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B;;;oCAGgC;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;uFAEuF;AACvF,wBAAsB,MAAM,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqExG"}
package/dist/command.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { commandUsage, parseCommandArgs } from "@cotal-ai/core";
2
- import { c } from "./ui.js";
3
- import { isExtensionStub, materializeExtensionCommand, overlayExtensions, setCommandSurface } from "./ext-loader.js";
2
+ import { c, staleStoreHint } from "./ui.js";
3
+ import { isExtensionStub, materializeExtension, materializeExtensionCommand, overlayExtensions, setCommandSurface, setInstalledExtensionsEnabled, } from "./ext-loader.js";
4
4
  /** Display order for the help groups — an explicit ranking, NOT registration order: modules
5
5
  * self-register on import and the dev runner (tsx) doesn't guarantee entry-import evaluation
6
6
  * order, so the daemon surface could otherwise print above Setup. Groups not listed here
@@ -54,6 +54,7 @@ function isArgError(e) {
54
54
  * The single entry point a composition root calls — no hardcoded command list.
55
55
  * Parsing happens HERE, from the command's declared specs; `run` gets parsed args. */
56
56
  export async function runCli(registry, argv, opts = {}) {
57
+ setInstalledExtensionsEnabled(Boolean(opts.extensions));
57
58
  let commands;
58
59
  try {
59
60
  commands = opts.extensions ? overlayExtensions(registry) : registry.all("command");
@@ -96,7 +97,12 @@ export async function runCli(registry, argv, opts = {}) {
96
97
  }
97
98
  }
98
99
  try {
99
- await cmd.run(parseCommandArgs(cmd, rest));
100
+ const parsed = parseCommandArgs(cmd, rest);
101
+ if (opts.extensions && cmd.requiredExtensions) {
102
+ for (const ref of cmd.requiredExtensions(parsed))
103
+ await materializeExtension(ref);
104
+ }
105
+ await cmd.run(parsed);
100
106
  }
101
107
  catch (e) {
102
108
  // A bad flag/arg prints the command's help, not a stack trace. Trim node's verbose
@@ -113,6 +119,11 @@ export async function runCli(registry, argv, opts = {}) {
113
119
  // COTAL_DEBUG for diagnosis.
114
120
  const msg = e instanceof Error ? e.message : String(e);
115
121
  console.error(c.red(`✗ ${msg}`));
122
+ // Known failure signatures get a one-line operator hint (e.g. a stale-store durable
123
+ // collision names the `cotal clean` reset) - the error itself stays verbatim.
124
+ const hint = staleStoreHint(msg);
125
+ if (hint)
126
+ console.error(c.dim(` ↳ ${hint}`));
116
127
  if (process.env.COTAL_DEBUG && e instanceof Error && e.stack)
117
128
  console.error(c.dim(e.stack));
118
129
  process.exit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAA+B,MAAM,gBAAgB,CAAC;AAC7F,OAAO,EAAE,CAAC,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,2BAA2B,EAC3B,iBAAiB,EACjB,iBAAiB,EACjB,6BAA6B,GAC9B,MAAM,iBAAiB,CAAC;AAEzB;;;4EAG4E;AAC5E,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAEjG,SAAS,IAAI,CAAC,QAAmB;IAC/B,+FAA+F;IAC/F,kFAAkF;IAClF,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC5C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,IAAI,UAAU,CAAC;QAClC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE;QACzB,MAAM,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,2CAA2C,CAAC;IACxE,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;QACpC,GAAG,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAC9B,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,GAAG,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;IACtF,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACnB,CAAC;AAED;sDACsD;AACtD,SAAS,WAAW,CAAC,GAAY;IAC/B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO;IAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC;IACtE,CAAC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,yFAAyF;AACzF,SAAS,UAAU,CAAC,CAAU;IAC5B,MAAM,IAAI,GAAI,CAAuB,EAAE,IAAI,CAAC;IAC5C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACvE,CAAC;AAWD;;uFAEuF;AACvF,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAkB,EAAE,IAAc,EAAE,OAAsB,EAAE;IACvF,6BAA6B,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACxD,IAAI,QAAmB,CAAC;IACxB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAU,SAAS,CAAC,CAAC;IAC9F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,2FAA2F;QAC3F,6EAA6E;QAC7E,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAM,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,gEAAgE;IAC7F,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAChF,IAAI,CAAC,QAAQ,CAAC,CAAC;QACf,OAAO;IACT,CAAC;IACD,IAAI,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,CAAC;QACjD,IAAI,CAAC,QAAQ,CAAC,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,8FAA8F;IAC9F,6FAA6F;IAC7F,2FAA2F;IAC3F,iEAAiE;IACjE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACnF,WAAW,CAAC,GAAG,CAAC,CAAC;QACjB,OAAO;IACT,CAAC;IACD,4FAA4F;IAC5F,wFAAwF;IACxF,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,2BAA2B,CAAC,GAAG,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAM,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,kBAAkB,EAAE,CAAC;YAC9C,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC;gBAAE,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACpF,CAAC;QACD,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,mFAAmF;QACnF,uFAAuF;QACvF,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,GAAG,GAAI,CAAW,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/C,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;YACjC,WAAW,CAAC,GAAG,CAAC,CAAC;YACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,4FAA4F;QAC5F,6FAA6F;QAC7F,sFAAsF;QACtF,6BAA6B;QAC7B,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;QACjC,oFAAoF;QACpF,8EAA8E;QAC9E,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,IAAI;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9C,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,KAAK;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { type CompletionResult, type ParsedArgs } from "@cotal-ai/core";
2
+ /** `cotal clean <history|store|all>` - one configurable cleanup verb.
3
+ * - `history`: purge the retained message backlog on the RUNNING broker (CHAT, plus DMs with
4
+ * `--dms`) via the least-privilege purger cred. `cotal history clear` is a thin alias.
5
+ * - `store`: delete the STOPPED mesh's JetStream store (`.cotal/nats` or `--store-dir`) - streams,
6
+ * durables, and messages. The reset for stale on-disk state (e.g. durables minted by an older,
7
+ * incompatible protocol generation).
8
+ * - `all`: `store` plus the space identity (`.cotal/auth`) and every local cred derived from it;
9
+ * the next `cotal up` mints a fresh identity.
10
+ * `history` needs the mesh UP (it is a live purge); `store`/`all` need it DOWN (files can't be
11
+ * deleted under a live server) and refuse loudly while any recorded process is still alive.
12
+ * Personas (`.cotal/agents`) are source and are never touched. */
13
+ export declare function clean(args: ParsedArgs): Promise<void>;
14
+ /** The live-purge half, shared with the `cotal history clear` alias. Resolves the running mesh (from
15
+ * any dir) + a least-privilege PURGER cred - `--creds` is a raw off-registry connect. Purge-only and
16
+ * destructive, so it mints exactly the purge grant (STREAM.PURGE on CHAT + DM), not the broad
17
+ * operator cred. In USER MODE the purge rides a one-shot "purger" VIEW bearer, exchange-gated on
18
+ * ledger scope "admin" (the refusal names the exact re-grant). */
19
+ export declare function purgeHistory(values: {
20
+ server?: string;
21
+ space?: string;
22
+ creds?: string;
23
+ dms?: boolean;
24
+ }): Promise<void>;
25
+ /** The recorded mesh process still alive under this root, as a "label (pid N)" string - or
26
+ * undefined when everything is stopped. A stale pidfile (recorded pid no longer alive) does not
27
+ * block: a crashed broker must not wedge its own cleanup. Liveness rides the shared hardened
28
+ * probe (`pidfileState`): pid > 0 only, EPERM counts as alive. */
29
+ export declare function liveMeshProcess(root: string): string | undefined;
30
+ /** Delete the stopped mesh's local state and return what was removed (paths relative to the root).
31
+ * `store`: the JetStream store directory. `all` adds the space identity (`.cotal/auth`), the
32
+ * locally persisted creds/markers tied to it - all invalid once the identity regenerates, and
33
+ * re-minted by the next fresh `cotal up` - plus crash residue a normal `down` would have swept
34
+ * (stale pidfiles, `run/`). Callers guard liveness first (`liveMeshProcess`). */
35
+ export declare function removeLocalState(root: string, opts: {
36
+ includeAuth: boolean;
37
+ storeDir?: string;
38
+ }): string[];
39
+ export declare function cleanComplete(argv: string[]): CompletionResult;
40
+ //# sourceMappingURL=clean.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clean.d.ts","sourceRoot":"","sources":["../../src/commands/clean.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAU3F;;;;;;;;;;mEAUmE;AACnE,wBAAsB,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B3D;AAED;;;;mEAImE;AACnE,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5H;AAED;;;mEAGmE;AACnE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMhE;AAED;;;;kFAIkF;AAClF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAgC1G;AAaD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAG9D"}
@@ -0,0 +1,138 @@
1
+ import { existsSync, rmSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { clearSpaceHistory } from "@cotal-ai/core";
4
+ import { removeMeshesByRoot, resolveSpace } from "@cotal-ai/workspace";
5
+ import { connectOrExit, userViewAuthOrExit } from "../lib/connect.js";
6
+ import { c } from "../ui.js";
7
+ import { cotalRoot } from "../lib/paths.js";
8
+ import { pidfileState, pidfileTargets } from "./down.js";
9
+ const TARGETS = ["history", "store", "all"];
10
+ /** `cotal clean <history|store|all>` - one configurable cleanup verb.
11
+ * - `history`: purge the retained message backlog on the RUNNING broker (CHAT, plus DMs with
12
+ * `--dms`) via the least-privilege purger cred. `cotal history clear` is a thin alias.
13
+ * - `store`: delete the STOPPED mesh's JetStream store (`.cotal/nats` or `--store-dir`) - streams,
14
+ * durables, and messages. The reset for stale on-disk state (e.g. durables minted by an older,
15
+ * incompatible protocol generation).
16
+ * - `all`: `store` plus the space identity (`.cotal/auth`) and every local cred derived from it;
17
+ * the next `cotal up` mints a fresh identity.
18
+ * `history` needs the mesh UP (it is a live purge); `store`/`all` need it DOWN (files can't be
19
+ * deleted under a live server) and refuse loudly while any recorded process is still alive.
20
+ * Personas (`.cotal/agents`) are source and are never touched. */
21
+ export async function clean(args) {
22
+ const target = args.positionals[0];
23
+ const values = args.values;
24
+ if (!target || !TARGETS.includes(target))
25
+ return usage();
26
+ if (!values.force) {
27
+ console.error(c.red(`refusing to clean ${target} without --force`));
28
+ console.error(c.dim(`usage: ${USAGE}`));
29
+ process.exit(1);
30
+ }
31
+ if (target === "history")
32
+ return purgeHistory(values);
33
+ const root = cotalRoot();
34
+ const running = liveMeshProcess(root);
35
+ if (running) {
36
+ console.error(c.red(`✗ the mesh is still running (${running}) - stop it first: \`cotal down\`, then \`cotal clean ${target}\``));
37
+ process.exit(1);
38
+ }
39
+ const removed = removeLocalState(root, { includeAuth: target === "all", storeDir: values["store-dir"] });
40
+ // Full reset also drops the mesh from the machine registry (and the `current` pointer),
41
+ // keyed by ROOT - see removeMeshesByRoot for why a space-name key would be wrong. A normal
42
+ // `down` already did this; after a crash it is exactly the ghost entry to clear.
43
+ if (target === "all")
44
+ removeMeshesByRoot(root);
45
+ if (removed.length === 0) {
46
+ console.log(c.dim("nothing to clean - no local state found"));
47
+ return;
48
+ }
49
+ for (const path of removed)
50
+ console.log(c.green(`✓ removed ${path}`));
51
+ if (target === "all")
52
+ console.log(c.dim("a fresh space identity is minted on the next `cotal up`"));
53
+ }
54
+ /** The live-purge half, shared with the `cotal history clear` alias. Resolves the running mesh (from
55
+ * any dir) + a least-privilege PURGER cred - `--creds` is a raw off-registry connect. Purge-only and
56
+ * destructive, so it mints exactly the purge grant (STREAM.PURGE on CHAT + DM), not the broad
57
+ * operator cred. In USER MODE the purge rides a one-shot "purger" VIEW bearer, exchange-gated on
58
+ * ledger scope "admin" (the refusal names the exact re-grant). */
59
+ export async function purgeHistory(values) {
60
+ const conn = await connectOrExit(values, "purger");
61
+ const user = conn.bearer ? await userViewAuthOrExit(conn, "purger") : undefined;
62
+ const { server, space, creds } = conn;
63
+ const result = await clearSpaceHistory({
64
+ servers: server,
65
+ space,
66
+ ...(user ? { bearer: user.bearer, sentinelCreds: user.sentinelCreds } : { creds }),
67
+ includeDms: values.dms,
68
+ });
69
+ const dm = result.dm === undefined ? "" : `, ${result.dm} DM message${result.dm === 1 ? "" : "s"}`;
70
+ console.log(c.green(`✓ cleared ${result.chat} channel message${result.chat === 1 ? "" : "s"}${dm} from "${space}"`));
71
+ }
72
+ /** The recorded mesh process still alive under this root, as a "label (pid N)" string - or
73
+ * undefined when everything is stopped. A stale pidfile (recorded pid no longer alive) does not
74
+ * block: a crashed broker must not wedge its own cleanup. Liveness rides the shared hardened
75
+ * probe (`pidfileState`): pid > 0 only, EPERM counts as alive. */
76
+ export function liveMeshProcess(root) {
77
+ for (const [file, label] of pidfileTargets(resolveSpace(root))) {
78
+ const s = pidfileState(join(root, ".cotal", file));
79
+ if (s.live)
80
+ return `${label}, pid ${s.pid}`;
81
+ }
82
+ return undefined;
83
+ }
84
+ /** Delete the stopped mesh's local state and return what was removed (paths relative to the root).
85
+ * `store`: the JetStream store directory. `all` adds the space identity (`.cotal/auth`), the
86
+ * locally persisted creds/markers tied to it - all invalid once the identity regenerates, and
87
+ * re-minted by the next fresh `cotal up` - plus crash residue a normal `down` would have swept
88
+ * (stale pidfiles, `run/`). Callers guard liveness first (`liveMeshProcess`). */
89
+ export function removeLocalState(root, opts) {
90
+ const removed = [];
91
+ const rm = (path, label) => {
92
+ if (!existsSync(path))
93
+ return;
94
+ rmSync(path, { recursive: true, force: true });
95
+ removed.push(label);
96
+ };
97
+ // The space must be read before `.cotal/auth` goes - it names the auth service's pidfile.
98
+ const space = resolveSpace(root);
99
+ const storeDir = opts.storeDir ? resolve(opts.storeDir) : join(root, ".cotal", "nats");
100
+ rm(storeDir, opts.storeDir ? storeDir : ".cotal/nats (JetStream store)");
101
+ if (opts.includeAuth) {
102
+ rm(join(root, ".cotal", "auth"), ".cotal/auth (space identity + creds)");
103
+ // Creds/records signed by (or tied to) the deleted identity: stale the moment it is gone.
104
+ // The fresh-`up` path re-mints every one of these (keep in sync with `provisionMembershipCreds`
105
+ // in up.ts); sweeping them keeps `doctor auth` honest in between and guarantees no
106
+ // old-operator material survives the reset.
107
+ for (const f of [
108
+ "delivery.creds",
109
+ "manager.delivery-aware",
110
+ "membership-observer.creds",
111
+ "membership-rw.creds",
112
+ "connection-evictor.creds",
113
+ "membership.json",
114
+ "renewal.json",
115
+ ])
116
+ rm(join(root, ".cotal", f), `.cotal/${f}`);
117
+ // Crash residue: after a clean `down` none of this exists; after a crash the dead pidfiles and
118
+ // transient launch artifacts are exactly the leftovers a "full local reset" must not keep.
119
+ for (const [file] of pidfileTargets(space))
120
+ rm(join(root, ".cotal", file), `.cotal/${file} (stale pidfile)`);
121
+ rm(join(root, ".cotal", "run"), ".cotal/run (launch artifacts)");
122
+ }
123
+ return removed;
124
+ }
125
+ const USAGE = "cotal clean <history|store|all> --force [--dms] [--space <s>] [--server <url>] [--creds <path>] [--store-dir <dir>]";
126
+ function usage() {
127
+ console.error(c.red(`usage: ${USAGE}`));
128
+ console.error(c.dim(" history purge the message backlog on the running broker (--dms to include DMs)"));
129
+ console.error(c.dim(" store delete the stopped mesh's JetStream store (.cotal/nats)"));
130
+ console.error(c.dim(" all store + the space identity (.cotal/auth) - full local reset"));
131
+ process.exit(1);
132
+ }
133
+ export function cleanComplete(argv) {
134
+ if (argv.length <= 1)
135
+ return { items: TARGETS.map((value) => ({ value })), directive: "nofiles" };
136
+ return { items: [], directive: "nofiles" };
137
+ }
138
+ //# sourceMappingURL=clean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clean.js","sourceRoot":"","sources":["../../src/commands/clean.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,17 @@
1
- import { type ParsedArgs } from "@cotal-ai/core";
2
- /** Stop the background processes started by `cotal up --detach`: the manager, the
3
- * delivery daemon, the web dashboard, and the mesh. With `-f <cotal.yaml>` (or `--run <id>`) it does
4
- * an ownership-scoped teardown of a `spawn -f` deploy instead removing ONLY what that run created
5
- * (its agents + the channels it added), from the ledger, never the whole shared mesh. */
1
+ import { type CompletionResult, type ParsedArgs } from "@cotal-ai/core";
2
+ /** Complete selective component names without importing installed packages. */
3
+ export declare function downComplete(argv: string[]): CompletionResult;
4
+ /** Stop the whole local stack by default, or only named self-registered process components. The
5
+ * manifest forms remain ownership-scoped deploy teardown and cannot be mixed with components. */
6
6
  export declare function down(args: ParsedArgs): Promise<void>;
7
+ export declare const isAlive: (pid: number) => boolean;
8
+ /** Compatibility inventory for `clean`; lifecycle commands use the registered descriptors above. */
9
+ export declare function pidfileTargets(space: string): Array<[file: string, label: string]>;
10
+ export type PidfileState = {
11
+ pid?: number;
12
+ live: boolean;
13
+ note?: string;
14
+ };
15
+ /** Shared hardened pid probe: positive integers only, and EPERM means the process exists. */
16
+ export declare function pidfileState(path: string): PidfileState;
7
17
  //# sourceMappingURL=down.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"down.d.ts","sourceRoot":"","sources":["../../src/commands/down.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOjD;;;0FAG0F;AAC1F,wBAAsB,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC1D"}
1
+ {"version":3,"file":"down.d.ts","sourceRoot":"","sources":["../../src/commands/down.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAaxE,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAS7D;AAED;kGACkG;AAClG,wBAAsB,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E1D;AAOD,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,OAErC,CAAC;AA4FF,oGAAoG;AACpG,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAQlF;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAOvD"}
@@ -1,97 +1,232 @@
1
- import { existsSync, readFileSync, rmSync } from "node:fs";
1
+ import { closeSync, existsSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
2
3
  import {} from "@cotal-ai/core";
3
- import { clearCurrent, getCurrent, removeMesh } from "@cotal-ai/workspace";
4
+ import { localProcessPath, removeMeshesByRoot, } from "@cotal-ai/workspace";
5
+ import { extensionNames, localProcessSurface } from "../ext-loader.js";
4
6
  import { c } from "../ui.js";
5
- import { cotalPath } from "../lib/paths.js";
7
+ import { cotalRoot } from "../lib/paths.js";
6
8
  import { resolveSpace } from "../lib/status.js";
7
9
  import { downManifest } from "./down-manifest.js";
8
- /** Stop the background processes started by `cotal up --detach`: the manager, the
9
- * delivery daemon, the web dashboard, and the mesh. With `-f <cotal.yaml>` (or `--run <id>`) it does
10
- * an ownership-scoped teardown of a `spawn -f` deploy instead removing ONLY what that run created
11
- * (its agents + the channels it added), from the ledger, never the whole shared mesh. */
10
+ /** Complete selective component names without importing installed packages. */
11
+ export function downComplete(argv) {
12
+ if (argv.some((word) => word === "-f" || word === "--file" || word === "--run"))
13
+ return { items: [], directive: "nofiles" };
14
+ if ((argv[argv.length - 1] ?? "").startsWith("-"))
15
+ return { items: [], directive: "nofiles" };
16
+ const used = new Set(argv.slice(0, -1).filter((word) => !word.startsWith("-")));
17
+ return {
18
+ items: extensionNames("local-process").filter((name) => !used.has(name)).map((value) => ({ value })),
19
+ directive: "nofiles",
20
+ };
21
+ }
22
+ /** Stop the whole local stack by default, or only named self-registered process components. The
23
+ * manifest forms remain ownership-scoped deploy teardown and cannot be mixed with components. */
12
24
  export async function down(args) {
13
25
  const values = args.values;
26
+ const requested = [...new Set(args.positionals)];
27
+ if ((values.file || values.run) && requested.length) {
28
+ throw new Error("component names cannot be combined with --file or --run");
29
+ }
14
30
  if (values.file || values.run) {
15
31
  await downManifest(values.file ?? "<run>", { run: values.run, dryRun: Boolean(values["dry-run"]) });
16
32
  return;
17
33
  }
18
- // The auth service's pid file is SPACE-scoped (auth-service.<space>.pid) — resolve the space
19
- // first, so `down` can only ever stop THIS folder's space's daemon, never another space's.
20
- const space = resolveSpace(process.cwd());
21
- const targets = [
22
- ["manager.pid", "manager"],
23
- ["delivery.pid", "delivery daemon"],
24
- [`auth-service.${encodeURIComponent(space)}.pid`, "user-auth service"],
25
- ["web.pid", "web dashboard"],
26
- ["nats.pid", "nats-server"],
27
- ];
34
+ const all = localProcessSurface();
35
+ const known = all.map((component) => component.name).sort();
36
+ const unknown = requested.filter((name) => !known.includes(name));
37
+ if (unknown.length)
38
+ throw new Error(`unknown component${unknown.length > 1 ? "s" : ""} ${unknown.map((name) => JSON.stringify(name)).join(", ")} - known: ${known.join(", ")}`);
39
+ const selected = (requested.length ? requested.map((name) => all.find((part) => part.name === name)) : all)
40
+ .sort((a, b) => Number(Boolean(a.stopLast)) - Number(Boolean(b.stopLast)) || (a.order ?? 50) - (b.order ?? 50));
41
+ const context = { root: cotalRoot(), space: resolveSpace(process.cwd()) };
42
+ if (selected.some((component) => component.stopLast)) {
43
+ const selectedNames = new Set(selected.map((component) => component.name));
44
+ const dependants = all.filter((component) => !selectedNames.has(component.name) && processAlive(component, context));
45
+ if (dependants.length) {
46
+ throw new Error(`cannot stop ${selected.filter((component) => component.stopLast).map((component) => component.name).join(", ")} while ${dependants.map((component) => component.name).join(", ")} ${dependants.length === 1 ? "is" : "are"} still running - name them too, or run bare \`cotal down\``);
47
+ }
48
+ }
49
+ if (values["dry-run"]) {
50
+ const recorded = selected.filter((component) => processRecorded(component, context));
51
+ for (const component of recorded)
52
+ console.log(c.dim(`would stop ${component.label}`));
53
+ if (!recorded.length) {
54
+ const target = requested.length ? requested.join(", ") : "the local stack";
55
+ console.error(c.red(`Nothing running for ${target} (no recorded pidfiles).`));
56
+ process.exit(1);
57
+ }
58
+ console.log(c.dim("Dry run - nothing was changed. Re-run without --dry-run to stop these components."));
59
+ return;
60
+ }
28
61
  let any = false;
29
- // Sequential, in declared order (manager → delivery → web → nats): the manager's graceful shutdown
30
- // releases its lease OVER nats, so nats must outlive it. Each stop awaits the real exit.
31
- for (const [file, label] of targets) {
32
- const pidPath = cotalPath(file);
33
- if (!existsSync(pidPath))
62
+ let allStopped = true;
63
+ for (const component of selected) {
64
+ any = processRecorded(component, context) || any;
65
+ if (component.stopLast && !allStopped) {
66
+ console.error(c.red(`✗ not stopping ${component.label} because an earlier component did not stop`));
34
67
  continue;
35
- any = true;
36
- await stop(pidPath, label);
68
+ }
69
+ try {
70
+ await stop(component, context);
71
+ }
72
+ catch (e) {
73
+ allStopped = false;
74
+ console.error(c.red(`✗ ${e.message}`));
75
+ }
76
+ }
77
+ if (!allStopped) {
78
+ console.error(c.red("✗ not cleanly stopped - keeping artifacts and the registry entry"));
79
+ process.exitCode = 1;
80
+ return;
81
+ }
82
+ for (const component of selected) {
83
+ for (const artifact of component.artifacts ?? [])
84
+ rmSync(localProcessPath(artifact, context), { force: true });
85
+ }
86
+ // The broker owns the mesh registry entry and transient whole-mesh launch material. Selective
87
+ // control-plane shutdown leaves both intact so `cotal up` can heal only what was stopped.
88
+ if (selected.some((component) => component.clearsMesh)) {
89
+ rmSync(join(context.root, ".cotal", "run"), { recursive: true, force: true });
90
+ removeMeshesByRoot(context.root);
37
91
  }
38
- // Non-pid control-plane artifacts: the delivery daemon's scoped creds + the manager's delivery-aware
39
- // marker. Drop them with the processes so a stale creds file / marker never lingers.
40
- for (const f of ["delivery.creds", "manager.delivery-aware"])
41
- rmSync(cotalPath(f), { force: true });
42
- // Transient `cotal up -f` launch artifacts (launch specs + materialized runtime personas). `up -f`
43
- // owns the whole mesh, so tearing it down clears all run dirs — they're never authoritative source.
44
- rmSync(cotalPath("run"), { recursive: true, force: true });
45
- // Drop this folder's mesh from the registry (and the `current` pointer if it was the default).
46
- removeMesh(space);
47
- if (getCurrent() === space)
48
- clearCurrent();
49
92
  if (!any) {
50
- console.error(c.red("Nothing running here (no .cotal/*.pid). Was it started with `cotal up` / `cotal setup`?"));
93
+ const target = requested.length ? requested.join(", ") : "the local stack";
94
+ console.error(c.red(`Nothing running for ${target} (no recorded pidfiles).`));
51
95
  process.exit(1);
52
96
  }
53
97
  }
54
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
55
- const isAlive = (pid) => {
98
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
99
+ const parsePid = (raw) => {
100
+ const pid = Number(raw.trim());
101
+ return Number.isInteger(pid) && pid > 0 ? pid : undefined;
102
+ };
103
+ export const isAlive = (pid) => {
56
104
  try {
57
105
  process.kill(pid, 0);
58
106
  return true;
59
107
  }
60
- catch {
61
- return false;
108
+ catch (e) {
109
+ return e.code === "EPERM";
62
110
  }
63
111
  };
64
- /** Stop one recorded process and AWAIT its actual exit — SIGTERM starts a graceful shutdown (a
65
- * manager reaps its agents and releases its JetStream lease over NATS, which isn't instant), so
66
- * returning before the process is really gone would let callers (and `up`-style smokes) race a
67
- * still-dying manager. Poll the pid, escalate to SIGKILL if it overstays, then confirm it's gone. */
68
- async function stop(pidPath, label) {
69
- const pid = Number(readFileSync(pidPath, "utf8").trim());
70
- // Drop the pidfile up front so a concurrent `down` can't double-signal a reused pid.
71
- rmSync(pidPath, { force: true });
72
- if (!Number.isFinite(pid))
73
- return;
74
- try {
75
- process.kill(pid, "SIGTERM");
112
+ function processRecorded(component, context) {
113
+ return existsSync(localProcessPath(component.pidFile, context)) || (component.artifacts ?? []).map((artifact) => localProcessPath(artifact, context)).some(existsSync);
114
+ }
115
+ function processAlive(component, context) {
116
+ const pidPath = localProcessPath(component.pidFile, context);
117
+ if (!existsSync(pidPath))
118
+ return false;
119
+ const pid = parsePid(readFileSync(pidPath, "utf8"));
120
+ return pid !== undefined && isAlive(pid);
121
+ }
122
+ /** Stop one recorded process and await its actual exit before the next dependency is stopped. */
123
+ async function stop(component, context) {
124
+ const pidPath = localProcessPath(component.pidFile, context);
125
+ const found = processRecorded(component, context);
126
+ if (!existsSync(pidPath))
127
+ return found;
128
+ const rawPid = readFileSync(pidPath, "utf8").trim();
129
+ if (rawPid.startsWith("removing:")) {
130
+ const owner = parsePid(rawPid.slice("removing:".length));
131
+ throw new Error(owner && isAlive(owner)
132
+ ? `${component.name} extension removal is in progress (pid ${owner})`
133
+ : `${component.name} has a stale extension-removal reservation at ${pidPath} - remove that file and retry`);
76
134
  }
77
- catch {
78
- console.log(c.dim(`${label} (pid ${pid}) was not running.`));
79
- return;
135
+ const pid = parsePid(rawPid);
136
+ const marker = `${pidPath}.stopping`;
137
+ let markerFd;
138
+ for (;;) {
139
+ try {
140
+ markerFd = openSync(marker, "wx", 0o600);
141
+ writeFileSync(markerFd, String(process.pid));
142
+ break;
143
+ }
144
+ catch (e) {
145
+ if (markerFd !== undefined) {
146
+ closeSync(markerFd);
147
+ rmSync(marker, { force: true });
148
+ markerFd = undefined;
149
+ }
150
+ if (e.code !== "EEXIST")
151
+ throw e;
152
+ let owner;
153
+ try {
154
+ owner = parsePid(readFileSync(marker, "utf8"));
155
+ }
156
+ catch (readErr) {
157
+ if (readErr.code === "ENOENT")
158
+ continue;
159
+ throw readErr;
160
+ }
161
+ if (owner && isAlive(owner))
162
+ throw new Error(`${component.name} is already being stopped by another \`cotal down\` (pid ${owner})`);
163
+ // A dead owner cannot finish cleanup. Reclaim its marker and retry the exclusive create.
164
+ rmSync(marker, { force: true });
165
+ }
80
166
  }
81
- const graceDeadline = Date.now() + 15_000;
82
- while (isAlive(pid) && Date.now() < graceDeadline)
83
- await sleep(100);
84
- if (isAlive(pid)) {
167
+ closeSync(markerFd);
168
+ let stopped = false;
169
+ try {
170
+ if (pid === undefined) {
171
+ console.log(c.dim(`${component.label} had an invalid pidfile.`));
172
+ stopped = true;
173
+ return true;
174
+ }
85
175
  try {
86
- process.kill(pid, "SIGKILL");
176
+ process.kill(pid, "SIGTERM");
177
+ }
178
+ catch {
179
+ if (isAlive(pid))
180
+ throw new Error(`could not signal ${component.label} (pid ${pid})`);
181
+ console.log(c.dim(`${component.label} (pid ${pid}) was not running.`));
182
+ stopped = true;
183
+ return true;
87
184
  }
88
- catch { /* raced to exit */ }
89
- const hardDeadline = Date.now() + 3_000;
90
- while (isAlive(pid) && Date.now() < hardDeadline)
185
+ const graceDeadline = Date.now() + 15_000;
186
+ while (isAlive(pid) && Date.now() < graceDeadline)
91
187
  await sleep(100);
188
+ if (isAlive(pid)) {
189
+ try {
190
+ process.kill(pid, "SIGKILL");
191
+ }
192
+ catch { /* raced to exit */ }
193
+ const hardDeadline = Date.now() + 3_000;
194
+ while (isAlive(pid) && Date.now() < hardDeadline)
195
+ await sleep(100);
196
+ }
197
+ if (isAlive(pid))
198
+ throw new Error(`${component.label} (pid ${pid}) did not exit; its pidfile was preserved`);
199
+ stopped = true;
200
+ console.log(c.green(`✓ stopped ${component.label} (pid ${pid})`));
201
+ return true;
202
+ }
203
+ finally {
204
+ if (stopped || pid === undefined || !isAlive(pid)) {
205
+ rmSync(pidPath, { force: true });
206
+ }
207
+ rmSync(marker, { force: true });
92
208
  }
93
- console.log(isAlive(pid)
94
- ? c.red(`✗ ${label} (pid ${pid}) did not exit`)
95
- : c.green(`✓ stopped ${label} (pid ${pid})`));
209
+ }
210
+ /** Compatibility inventory for `clean`; lifecycle commands use the registered descriptors above. */
211
+ export function pidfileTargets(space) {
212
+ return [
213
+ ["manager.pid", "manager"],
214
+ ["delivery.pid", "delivery daemon"],
215
+ [`auth-service.${encodeURIComponent(space)}.pid`, "user-auth service"],
216
+ ["web.pid", "web dashboard"],
217
+ ["nats.pid", "nats-server"],
218
+ ];
219
+ }
220
+ /** Shared hardened pid probe: positive integers only, and EPERM means the process exists. */
221
+ export function pidfileState(path) {
222
+ if (!existsSync(path))
223
+ return { live: false, note: "no pidfile" };
224
+ const raw = readFileSync(path, "utf8").trim();
225
+ if (raw.startsWith("removing:"))
226
+ return { live: false, note: "extension removal in progress" };
227
+ const pid = parsePid(raw);
228
+ if (!pid)
229
+ return { live: false, note: "bad pidfile" };
230
+ return isAlive(pid) ? { pid, live: true } : { pid, live: false, note: "stale pidfile" };
96
231
  }
97
232
  //# sourceMappingURL=down.js.map