@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.
- package/dist/command.d.ts.map +1 -1
- package/dist/command.js +14 -3
- package/dist/command.js.map +1 -1
- package/dist/commands/clean.d.ts +40 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +138 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/down.d.ts +15 -5
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +201 -66
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/endpoints.d.ts +5 -0
- package/dist/commands/endpoints.d.ts.map +1 -0
- package/dist/commands/endpoints.js +42 -0
- package/dist/commands/endpoints.js.map +1 -0
- package/dist/commands/ext.d.ts +4 -4
- package/dist/commands/ext.d.ts.map +1 -1
- package/dist/commands/ext.js +327 -90
- package/dist/commands/ext.js.map +1 -1
- package/dist/commands/history.d.ts +3 -2
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +6 -21
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/setup.js +2 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/spawn-manifest.d.ts.map +1 -1
- package/dist/commands/spawn-manifest.js +23 -10
- package/dist/commands/spawn-manifest.js.map +1 -1
- package/dist/commands/spawn.d.ts +1 -1
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +3 -2
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +25 -26
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +7 -5
- package/dist/commands/up.js.map +1 -1
- package/dist/ext-loader.d.ts +12 -1
- package/dist/ext-loader.d.ts.map +1 -1
- package/dist/ext-loader.js +92 -8
- package/dist/ext-loader.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/control.d.ts.map +1 -1
- package/dist/lib/control.js +8 -2
- package/dist/lib/control.js.map +1 -1
- package/dist/lib/manifest/model.d.ts +1 -1
- package/dist/lib/manifest/model.d.ts.map +1 -1
- package/dist/lib/manifest/schema.d.ts +1 -5
- package/dist/lib/manifest/schema.d.ts.map +1 -1
- package/dist/lib/manifest/schema.js +2 -1
- package/dist/lib/manifest/schema.js.map +1 -1
- package/dist/ui.d.ts +5 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +12 -0
- package/dist/ui.js.map +1 -1
- package/package.json +3 -3
package/dist/command.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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);
|
package/dist/command.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|
package/dist/commands/down.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { type ParsedArgs } from "@cotal-ai/core";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
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":"
|
|
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"}
|
package/dist/commands/down.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
7
|
+
import { cotalRoot } from "../lib/paths.js";
|
|
6
8
|
import { resolveSpace } from "../lib/status.js";
|
|
7
9
|
import { downManifest } from "./down-manifest.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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((
|
|
55
|
-
const
|
|
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
|
|
108
|
+
catch (e) {
|
|
109
|
+
return e.code === "EPERM";
|
|
62
110
|
}
|
|
63
111
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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, "
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|