@cotal-ai/cli 0.15.0 → 0.16.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.
@@ -0,0 +1,277 @@
1
+ import * as p from "@clack/prompts";
2
+ import { findMesh } from "@cotal-ai/workspace";
3
+ import { bold, brand, brandBold, dim, ok } from "../lib/theme.js";
4
+ import { abortIfCancel } from "../lib/cancel.js";
5
+ import { candidateTarget, checkEnforcement, checkMode, checkRoot, checkServer, checkTrust, isDir, probeEnforcement, spacesAtRoot, verifyTarget, writeRecord, } from "./meshes-add.js";
6
+ /** The real terminal, via clack. Cancellation (Ctrl-C / Esc) exits here rather than returning a
7
+ * sentinel the wizard body would have to thread through every branch. */
8
+ export function clackIO() {
9
+ return {
10
+ intro: (m) => p.intro(m),
11
+ outro: (m) => p.outro(m),
12
+ note: (body, title) => p.note(body, title),
13
+ cancel: (m) => p.cancel(m),
14
+ log: { info: (m) => p.log.info(m), warn: (m) => p.log.warn(m), error: (m) => p.log.error(m) },
15
+ spinner: () => {
16
+ const s = p.spinner();
17
+ return { start: (m) => s.start(m), stop: (m) => s.stop(m) };
18
+ },
19
+ text: async (opts) => abortIfCancel(await p.text(opts)),
20
+ select: async (opts) => abortIfCancel(await p.select(opts)),
21
+ confirm: async (opts) => abortIfCancel(await p.confirm({ ...opts, initialValue: true })),
22
+ };
23
+ }
24
+ /** Whether a guided run is possible at all: both ends of the pipe must be a real terminal. Without
25
+ * this a missing flag would hang a script waiting on input nobody is there to give; the flag form
26
+ * keeps failing loud instead. */
27
+ export function canPrompt() {
28
+ return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY) && process.env.COTAL_NO_PROMPT !== "1";
29
+ }
30
+ /** Run the guided registration. Returns false if the operator backed out (the caller exits 0 —
31
+ * cancelling is not a failure). */
32
+ export async function addWizard(seed, cwd, io = clackIO()) {
33
+ io.intro(brandBold("Register a mesh"));
34
+ io.note("A mesh running somewhere else - another machine, a shared broker, a hosted space - so that\n`cotal spawn` and `--space` can reach it from any folder on this machine.\n\nNothing is written until you confirm.", brand("What this does"));
35
+ // TWO different decisions, deliberately not one flag. `--force` conflates them for the scripted
36
+ // form (documented there), but here they are reached separately and must stay separate:
37
+ // `unverified` means the operator accepted a record nothing confirmed; `replace` means an
38
+ // existing record for that name is being overwritten. Folding replace into unverified would
39
+ // silently skip the broker checks for anyone who picked "replace that record" — a record
40
+ // presented as checked that never was, which is the failure this command exists to refuse.
41
+ let unverified = Boolean(seed.force);
42
+ let replace = Boolean(seed.force);
43
+ // ── 1. the one thing that cannot be derived ────────────────────────────────────────────────────
44
+ let server = seed.server;
45
+ let enforces = "unreachable";
46
+ for (;;) {
47
+ if (!server) {
48
+ server = await io.text({
49
+ message: "Broker URL of the mesh you want to reach",
50
+ placeholder: "nats://10.0.0.5:4222",
51
+ validate: (v) => {
52
+ if (!v)
53
+ return "Required - this is the address the mesh's broker listens on.";
54
+ const r = checkServer(v);
55
+ return r.ok ? undefined : r.message.replace(/^✗ (--server )?/, "");
56
+ },
57
+ });
58
+ }
59
+ else {
60
+ const pre = checkServer(server);
61
+ if (!pre.ok) {
62
+ io.log.error(pre.message);
63
+ server = undefined;
64
+ continue;
65
+ }
66
+ }
67
+ const spin = io.spinner();
68
+ spin.start(`Asking ${server} what it is`);
69
+ enforces = await probeEnforcement(server);
70
+ spin.stop(enforces === "auth"
71
+ ? `${ok("✓")} ${server} answered - it ${bold("requires credentials")}`
72
+ : enforces === "open"
73
+ ? `${ok("✓")} ${server} answered - it is ${bold("open")} (accepts anyone)`
74
+ : `${server} did not answer`);
75
+ if (enforces !== "unreachable")
76
+ break;
77
+ const next = await io.select({
78
+ message: "No broker answered there.",
79
+ options: [
80
+ { value: "retype", label: "Use a different address", hint: "typo, wrong port, VPN not up" },
81
+ { value: "anyway", label: "Register it anyway", hint: "the mesh is down right now; nothing is verified" },
82
+ { value: "cancel", label: "Cancel" },
83
+ ],
84
+ });
85
+ if (next === "cancel")
86
+ return cancelled(io);
87
+ if (next === "anyway") {
88
+ unverified = true;
89
+ break;
90
+ }
91
+ server = undefined;
92
+ }
93
+ // ── 2. where this mesh's local trust + personas live ───────────────────────────────────────────
94
+ // A LOOP, not a recursive re-entry: "point at a different folder" has to actually ask for one.
95
+ // Clearing the answer and inferring again would land on the same project root inside a project,
96
+ // re-hit the same dead end, and offer the same useless choice forever.
97
+ const inferred = checkRoot(seed.root, cwd);
98
+ let root = inferred.ok ? inferred.value : undefined;
99
+ if (root && !seed.root)
100
+ io.log.info(`Using this project as its local folder: ${bold(root)}`);
101
+ // The cwd is only a sensible default the FIRST time. After the operator has been told this
102
+ // folder holds no credentials, offering it back pre-filled makes them clear the field to answer
103
+ // the question at all — so the recovery ask starts empty and insists on a real path.
104
+ let defaultRoot = cwd;
105
+ let accounts = [];
106
+ for (;;) {
107
+ while (!root)
108
+ root = await askRoot(io, cwd, defaultRoot);
109
+ const inventory = spacesAtRoot(root);
110
+ if (!inventory.ok) {
111
+ // Unreadable trust is not "no trust": say so and let them point somewhere else.
112
+ io.log.error(inventory.message);
113
+ root = undefined;
114
+ defaultRoot = undefined;
115
+ continue;
116
+ }
117
+ accounts = inventory.value;
118
+ // An auth broker with no trust here cannot be registered at all — say what is missing and where
119
+ // it comes from, rather than failing after more questions.
120
+ if (enforces !== "auth" || accounts.length > 0)
121
+ break;
122
+ io.log.warn(`That broker requires credentials, and ${bold(root)} holds none.\n` +
123
+ dim("Copy the mesh's .cotal/auth from the machine it runs on (or `cotal mint` there), then point at that folder."));
124
+ const next = await io.select({
125
+ message: "That folder holds no credentials for this broker. What next?",
126
+ options: [
127
+ { value: "root", label: "Point at a different folder", hint: "one that already holds its credentials" },
128
+ { value: "cancel", label: "Cancel" },
129
+ ],
130
+ });
131
+ if (next === "cancel")
132
+ return cancelled(io);
133
+ root = undefined; // ask for a path; never re-infer, or this offers the same dead end again
134
+ defaultRoot = undefined; // …and never offer the folder we just rejected as the default
135
+ }
136
+ // ── 3. which space ────────────────────────────────────────────────────────────────────────────
137
+ // The clash gate runs for EVERY resolved name, including one that came in on the command line.
138
+ // Gating it on "did we just ask?" let `cotal meshes add <already-registered>` overwrite silently —
139
+ // something the flag form refuses outright.
140
+ let space = seed.space;
141
+ for (;;) {
142
+ while (!space) {
143
+ if (accounts.length > 0) {
144
+ const picked = await io.select({
145
+ message: "Which space on that broker?",
146
+ options: [
147
+ ...accounts.map((s) => ({ value: s, label: s, hint: "credentials for this space are already here" })),
148
+ { value: null, label: "Another name…" }, // null, not a sentinel string: no space name can collide with it
149
+ ],
150
+ });
151
+ if (picked !== null)
152
+ space = picked;
153
+ }
154
+ if (!space) {
155
+ space = await io.text({
156
+ message: "Space name on that broker",
157
+ placeholder: "the name the mesh was started with",
158
+ validate: (v) => (v ? undefined : "Required - it must match the space name where the mesh runs."),
159
+ });
160
+ }
161
+ }
162
+ const clash = findMesh(space);
163
+ if (!clash || replace)
164
+ break;
165
+ const next = await io.select({
166
+ message: `"${space}" is already registered here (${clash.server}).`,
167
+ options: [
168
+ { value: "replace", label: "Replace that record", hint: clash.root },
169
+ { value: "rename", label: "Use a different name" },
170
+ { value: "cancel", label: "Cancel" },
171
+ ],
172
+ });
173
+ if (next === "cancel")
174
+ return cancelled(io);
175
+ if (next === "rename")
176
+ space = undefined;
177
+ else {
178
+ replace = true;
179
+ break;
180
+ }
181
+ }
182
+ // ── 4. mode + trust, decided rather than asked ─────────────────────────────────────────────────
183
+ // The probe's answer is the best evidence of the mode — but only when the broker actually
184
+ // answered. An UNREACHABLE broker knows nothing, and reading "not auth" out of silence would
185
+ // record an auth mesh as open (the exact wrong-mode failure this command is built to refuse); so
186
+ // fall back to the same on-disk inference the flag form uses.
187
+ const modeHint = seed.mode ?? (enforces === "unreachable" ? undefined : enforces);
188
+ const modeCheck = checkMode(space, root, accounts, modeHint);
189
+ if (!modeCheck.ok) {
190
+ io.log.error(modeCheck.message);
191
+ return cancelled(io, "Nothing was registered.");
192
+ }
193
+ const mode = modeCheck.value;
194
+ const trust = checkTrust(mode, root, space);
195
+ if (!trust.ok) {
196
+ io.log.error(trust.message);
197
+ return cancelled(io, "Nothing was registered.");
198
+ }
199
+ if (!unverified) {
200
+ const match = checkEnforcement(mode, enforces, server, space, root);
201
+ if (!match.ok) {
202
+ io.log.error(match.message);
203
+ return cancelled(io, "Nothing was registered.");
204
+ }
205
+ }
206
+ // ── 5. verify for real, then confirm ──────────────────────────────────────────────────────────
207
+ if (!unverified) {
208
+ const target = candidateTarget(space, server, root, mode, trust.value);
209
+ const spin = io.spinner();
210
+ spin.start(mode === "auth" ? `Checking this folder's credentials for "${space}"` : `Checking the connection to "${space}"`);
211
+ const verified = await verifyTarget(target);
212
+ spin.stop(verified.ok ? `${ok("✓")} ${mode === "auth" ? "Credentials accepted" : "Connected"}` : "Could not connect");
213
+ if (!verified.ok) {
214
+ io.log.error(verified.message);
215
+ const next = await io.select({
216
+ message: "The check did not pass. What next?",
217
+ options: [
218
+ { value: "anyway", label: "Register it anyway", hint: "the record will say it was not verified" },
219
+ { value: "cancel", label: "Cancel" },
220
+ ],
221
+ });
222
+ if (next === "cancel")
223
+ return cancelled(io, "Nothing was registered.");
224
+ unverified = true;
225
+ }
226
+ }
227
+ io.note([
228
+ `${dim("space ")} ${bold(space)}`,
229
+ `${dim("broker")} ${server}`,
230
+ `${dim("mode ")} ${mode}${unverified ? dim(" (not verified - the broker was not reachable/checked)") : dim(mode === "auth" ? " (broker enforces credentials)" : " (broker is open)")}`,
231
+ ...(replace ? [`${dim("note ")} ${bold("replaces")} the existing record for "${space}"`] : []),
232
+ `${dim("folder")} ${root}`,
233
+ ].join("\n"), brand("About to record"));
234
+ const go = await io.confirm({ message: "Register this mesh?" });
235
+ if (!go)
236
+ return cancelled(io, "Nothing was registered.");
237
+ const entry = { space, server: server, root, mode, origin: "manual", ts: new Date().toISOString() };
238
+ const result = writeRecord(entry);
239
+ // Padded from the rendered widths, not a guess: the commands carry the space name, so a
240
+ // hard-coded gutter misaligns for every name but the one it was written against.
241
+ const rows = [
242
+ result.adoptedCurrent
243
+ ? ["cotal spawn", "launch an agent into it (it is now the default)"]
244
+ : [`cotal use ${space}`, "make it the default a bare spawn joins"],
245
+ [`cotal status --space ${space}`, "check it"],
246
+ [`cotal meshes rm ${space}`, "forget it again (never stops the mesh)"],
247
+ ];
248
+ const width = Math.max(...rows.map(([cmd]) => cmd.length));
249
+ io.note(rows.map(([cmd, why]) => `${bold(cmd.padEnd(width))} ${dim(why)}`).join("\n"), brand("What you can do now"));
250
+ io.outro(ok(`Registered "${space}"${result.keptCurrent ? ` - "${result.keptCurrent}" is still the default` : ""}`));
251
+ return true;
252
+ }
253
+ /** Ask for the folder, re-asking until it resolves. `defaultTo` is accepted on an empty answer;
254
+ * without one, an empty answer is a non-answer rather than a silent fallback. */
255
+ async function askRoot(io, cwd, defaultTo) {
256
+ for (;;) {
257
+ const typed = await io.text({
258
+ message: "Local folder for this mesh's credentials and personas",
259
+ placeholder: defaultTo ?? "the folder holding its .cotal/auth",
260
+ validate: (v) => {
261
+ const candidate = v || defaultTo;
262
+ if (!candidate)
263
+ return "Required - the folder that holds this mesh's .cotal/auth and .cotal/agents.";
264
+ return isDir(candidate) ? undefined : "Not a directory - it must already exist.";
265
+ },
266
+ });
267
+ const r = checkRoot(typed || defaultTo, cwd);
268
+ if (r.ok)
269
+ return r.value;
270
+ io.log.error(r.message);
271
+ }
272
+ }
273
+ function cancelled(io, message = "Cancelled.") {
274
+ io.cancel(message);
275
+ return false;
276
+ }
277
+ //# sourceMappingURL=meshes-wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meshes-wizard.js","sourceRoot":"","sources":["../../src/commands/meshes-wizard.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,24 @@
1
- import type { ParsedArgs } from "@cotal-ai/core";
2
- /** `cotal meshes` list the running meshes (one `cotal up` each), with a `*` on the `current`
3
- * default. The kubectl `get-contexts` analogue: how you see what a bare `cotal spawn` would join,
4
- * and which `--space` names are available. Prunes dead entries first. */
1
+ import { type CompletionResult, type ParsedArgs } from "@cotal-ai/core";
2
+ export declare const meshesFlags: readonly [{
3
+ readonly name: "server";
4
+ readonly type: "string";
5
+ readonly value: "<url>";
6
+ readonly description: "add: the mesh's broker URL (required)";
7
+ }, {
8
+ readonly name: "root";
9
+ readonly type: "string";
10
+ readonly value: "<dir>";
11
+ readonly description: "add: folder holding this mesh's .cotal/auth + .cotal/agents (default: this project)";
12
+ }, {
13
+ readonly name: "mode";
14
+ readonly type: "string";
15
+ readonly value: "<auth|open>";
16
+ readonly description: "add: how the broker authenticates (default: inferred from --root)";
17
+ }, {
18
+ readonly name: "force";
19
+ readonly type: "boolean";
20
+ readonly description: "add: record without verifying, replacing any existing record · rm: drop a running mesh's record";
21
+ }];
5
22
  export declare function meshes(args: ParsedArgs): Promise<void>;
23
+ export declare function meshesComplete(argv: string[]): CompletionResult;
6
24
  //# sourceMappingURL=meshes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"meshes.d.ts","sourceRoot":"","sources":["../../src/commands/meshes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD;;0EAE0E;AAC1E,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB5D"}
1
+ {"version":3,"file":"meshes.d.ts","sourceRoot":"","sources":["../../src/commands/meshes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAkC,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA6CxG,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;EAKgB,CAAC;AAIzC,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5D;AAmLD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAW/D"}
@@ -1,25 +1,221 @@
1
- import { getCurrent, loadMeshes } from "@cotal-ai/workspace";
1
+ import {} from "@cotal-ai/core";
2
+ import { authDir, clearCurrent, findMesh, getCurrent, loadMeshes, removeMesh, } from "@cotal-ai/workspace";
2
3
  import { c } from "../ui.js";
3
4
  import { pruneStaleMeshes } from "../lib/meshes.js";
4
- /** `cotal meshes` list the running meshes (one `cotal up` each), with a `*` on the `current`
5
- * default. The kubectl `get-contexts` analogue: how you see what a bare `cotal spawn` would join,
6
- * and which `--space` names are available. Prunes dead entries first. */
5
+ import { completingFlagValue } from "../lib/completion.js";
6
+ import { liveMeshOwner } from "./clean.js";
7
+ import { candidateTarget, checkEnforcement, checkMode, checkRoot, checkServer, checkTrust, probeEnforcement, spacesAtRoot, verifyTarget, writeRecord, } from "./meshes-add.js";
8
+ import { addWizard, canPrompt } from "./meshes-wizard.js";
9
+ /**
10
+ * `cotal meshes` — the registry of meshes this machine can reach, and the two verbs that maintain
11
+ * it by hand.
12
+ *
13
+ * cotal meshes list (the kubectl `get-contexts` analogue)
14
+ * cotal meshes add <space> --server register a mesh this machine did NOT start
15
+ * cotal meshes rm <space> … drop records (never stops anything)
16
+ *
17
+ * `up` and `down` still write and clear their own records; `add`/`rm` exist for the meshes they
18
+ * cannot speak for — one running on another machine, a shared broker, a hosted space. Those records
19
+ * are marked `manual` and are never auto-pruned (see `pruneMesh`), because this machine has no way
20
+ * to write them back: a dead broker under one is reported `offline`, not deleted.
21
+ */
22
+ const SUBCOMMANDS = ["list", "add", "rm", "remove"];
23
+ export const meshesFlags = [
24
+ { name: "server", type: "string", value: "<url>", description: "add: the mesh's broker URL (required)" },
25
+ { name: "root", type: "string", value: "<dir>", description: "add: folder holding this mesh's .cotal/auth + .cotal/agents (default: this project)" },
26
+ { name: "mode", type: "string", value: "<auth|open>", description: "add: how the broker authenticates (default: inferred from --root)" },
27
+ { name: "force", type: "boolean", description: "add: record without verifying, replacing any existing record · rm: drop a running mesh's record" },
28
+ ];
7
29
  export async function meshes(args) {
8
- await pruneStaleMeshes();
30
+ const sub = args.positionals[0];
31
+ const v = args.values;
32
+ if (sub === "add")
33
+ return addMesh(args.positionals.slice(1), v);
34
+ if (sub === "rm" || sub === "remove")
35
+ return removeMeshes(args.positionals.slice(1), v);
36
+ if (sub !== undefined && sub !== "list") {
37
+ console.error(c.red(`✗ unknown subcommand "${sub}" - usage: cotal meshes [list | add <space> --server <url> | rm <space> …]`));
38
+ process.exit(1);
39
+ }
40
+ return listMeshes();
41
+ }
42
+ // ---- list -----------------------------------------------------------------------------------
43
+ /** The registered meshes, one per line, with a `*` on the `current` default. This is how you see
44
+ * what a bare `cotal spawn` would join and which `--space` names exist. The sweep runs first, so
45
+ * a mesh this machine started and lost is gone from the list; an operator-registered one whose
46
+ * broker is down stays, tagged `offline` — it is still the mesh you meant, just not up. */
47
+ async function listMeshes() {
48
+ const sweep = await pruneStaleMeshes();
9
49
  const all = loadMeshes();
10
50
  if (all.length === 0) {
11
- console.log(c.dim("no meshes running - start one with `cotal up`"));
51
+ console.log(c.dim("no meshes registered - `cotal up` starts one here, `cotal meshes add <space> --server <url>` registers one running elsewhere"));
12
52
  return;
13
53
  }
14
54
  const current = getCurrent();
15
- const pad = Math.max(...all.map((m) => m.space.length));
55
+ const offline = new Set(sweep.offline);
56
+ const width = (pick, header) => Math.max(header.length, ...all.map((m) => pick(m).length));
57
+ const wSpace = width((m) => m.space, "SPACE");
58
+ const wServer = width((m) => m.server, "SERVER");
59
+ const wMode = width((m) => m.mode, "MODE");
60
+ console.log(c.dim(` ${"SPACE".padEnd(wSpace)} ${"SERVER".padEnd(wServer)} ${"MODE".padEnd(wMode)} ROOT`));
16
61
  for (const m of all) {
17
62
  const marker = m.space === current ? c.green("*") : " ";
18
- console.log(`${marker} ${m.space.padEnd(pad)} ${c.dim(`${m.server} ${m.mode} ${m.root}`)}`);
63
+ const tags = [
64
+ ...(m.origin === "manual" ? [c.dim("registered")] : []),
65
+ ...(offline.has(m.space) ? [c.yellow("offline")] : []),
66
+ ];
67
+ console.log(`${marker} ${m.space.padEnd(wSpace)} ${c.dim(`${m.server.padEnd(wServer)} ${m.mode.padEnd(wMode)} ${m.root}`)}` +
68
+ (tags.length ? ` ${tags.join(c.dim(" · "))}` : ""));
19
69
  }
20
- // A `current` that no longer matches any running mesh (its broker went down) shows no `*` — say
70
+ // A `current` that no longer matches any recorded mesh (its broker went down) shows no `*` — say
21
71
  // why, so a bare `cotal spawn` still reporting "multiple meshes" isn't a mystery.
22
72
  if (current && !all.some((m) => m.space === current))
23
73
  console.log(c.dim(`\nnote: default "${current}" is not running - \`cotal use <name>\` to set a live one`));
24
74
  }
75
+ // ---- add ------------------------------------------------------------------------------------
76
+ /** `cotal meshes add <space> --server <url>` — register a mesh this machine did not start, so
77
+ * `--space`, `cotal use` and a bare `cotal spawn` can reach it from any directory. The record
78
+ * holds a broker URL, a local root and a mode; trust material itself stays in that root's
79
+ * `.cotal/auth`, exactly as it does for a mesh started here. */
80
+ async function addMesh(positionals, v) {
81
+ const space = positionals[0];
82
+ if (positionals.length > 1) {
83
+ console.error(c.red("usage: cotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open]"));
84
+ process.exit(1);
85
+ }
86
+ // GUIDED FORM. A registration needs four facts, three of which the machine can find out — so a
87
+ // command missing either irreducible one, on a real terminal, is an operator who would rather be
88
+ // asked than read a usage line. Scripts and agents are unaffected: without a TTY (or with
89
+ // COTAL_NO_PROMPT=1) the flag form's fail-loud sentences stand, which is what every smoke asserts.
90
+ if ((!space || !v.server) && canPrompt()) {
91
+ const done = await addWizard({ ...(space ? { space } : {}), ...(v.server ? { server: v.server } : {}), ...(v.root ? { root: v.root } : {}),
92
+ ...(v.mode ? { mode: v.mode } : {}), ...(v.force ? { force: true } : {}) }, process.cwd());
93
+ if (!done)
94
+ process.exitCode = 0; // backing out of a wizard is not a failure
95
+ return;
96
+ }
97
+ if (!space) {
98
+ console.error(c.red("usage: cotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open]"));
99
+ process.exit(1);
100
+ }
101
+ if (!v.server) {
102
+ console.error(c.red("✗ --server <url> is required - a mesh you did not start here has no address to infer (e.g. --server nats://10.0.0.5:4222)"));
103
+ process.exit(1);
104
+ }
105
+ const server = take(checkServer(v.server));
106
+ const root = take(checkRoot(v.root, process.cwd()));
107
+ const accounts = take(spacesAtRoot(root));
108
+ const mode = take(checkMode(space, root, accounts, v.mode));
109
+ if (mode === "open" && accounts.includes(space))
110
+ console.log(c.dim(`note: ${authDir(root)} holds trust for "${space}", but --mode open records a credless connect`));
111
+ const auth = take(checkTrust(mode, root, space));
112
+ const existing = findMesh(space);
113
+ if (existing && !v.force) {
114
+ console.error(c.red(`✗ "${space}" is already registered at ${existing.server} (${existing.root}) - \`cotal meshes rm ${space}\` first, or --force to replace it`));
115
+ process.exit(1);
116
+ }
117
+ // VERIFY BEFORE RECORDING. A wrong address, a broker that wants auth, creds that don't open this
118
+ // space, an expired cred — all one probe away here, and every one of them would otherwise surface
119
+ // as a confusing failure at the first `cotal spawn` against a record that looks fine. `--force`
120
+ // is the explicit escape (registering a mesh that is currently down), and it says so on the
121
+ // success line rather than pretending the mesh was checked.
122
+ if (!v.force) {
123
+ take(checkEnforcement(mode, await probeEnforcement(server), server, space, root));
124
+ const verified = await verifyTarget(candidateTarget(space, server, root, mode, auth));
125
+ if (!verified.ok) {
126
+ console.error(c.red(verified.message));
127
+ console.error(c.dim("nothing was registered - fix the above, or `--force` to record it without verifying (e.g. the mesh is down right now)"));
128
+ process.exit(1);
129
+ }
130
+ }
131
+ const result = writeRecord({ space, server, root, mode, origin: "manual", ts: new Date().toISOString() });
132
+ console.log(c.green(`✓ registered "${space}"`),
133
+ // "recorded without verifying" describes THIS registration, not a durable property of the
134
+ // record: verification is point-in-time — a verified record loses it the moment a port is
135
+ // reused — so it is reported as what just happened rather than persisted as a stored claim
136
+ // that would quietly decay into a false one.
137
+ c.dim(`${server} ${mode} ${root}${v.force ? " (recorded without verifying)" : ""}`));
138
+ if (result.adoptedCurrent) {
139
+ console.log(c.dim(`it is now the default mesh - \`cotal spawn\` from any directory joins it`));
140
+ return;
141
+ }
142
+ if (result.keptCurrent)
143
+ console.log(c.dim(`current is still "${result.keptCurrent}" - \`cotal use ${space}\` to switch`));
144
+ }
145
+ /** Take a rule's value, or print its sentence and exit — the flag form's whole error posture. */
146
+ function take(r) {
147
+ if (r.ok)
148
+ return r.value;
149
+ console.error(c.red(r.message));
150
+ process.exit(1);
151
+ }
152
+ // ---- rm -------------------------------------------------------------------------------------
153
+ /** `cotal meshes rm <space> …` — drop records. This never stops a mesh: it removes what THIS
154
+ * machine remembers about one. For a mesh running here that distinction is a footgun (the broker
155
+ * keeps running with nothing pointing at it), so those are refused in favour of `cotal down`. */
156
+ async function removeMeshes(names, v) {
157
+ if (names.length === 0) {
158
+ console.error(c.red("usage: cotal meshes rm <space> [<space> …]"));
159
+ process.exit(1);
160
+ }
161
+ let failed = false;
162
+ let clearedCurrent = false;
163
+ for (const space of names) {
164
+ const m = findMesh(space);
165
+ if (!m) {
166
+ console.error(c.red(`✗ no mesh named "${space}" is registered - see \`cotal meshes\``));
167
+ failed = true;
168
+ continue;
169
+ }
170
+ // Refuse only when THIS MACHINE demonstrably runs the mesh — a live recorded pid under its
171
+ // root. Reachability was the wrong test: any broker on that address answers, including a
172
+ // reused port or a foreign NATS, which produced a refusal plus a `cotal down` instruction that
173
+ // would stop nothing. Local process ownership is the fact the refusal actually claims.
174
+ //
175
+ // It is asked only of records this machine started. Pidfiles are ROOT-scoped, and a root is
176
+ // shared on purpose here: `add` defaults `--root` to the project you run it in, so a local mesh
177
+ // and a registration for a remote one routinely live under one root. A pid there belongs to
178
+ // whichever mesh owns the root — never to the remote broker — so asking this of a hand-registered
179
+ // record can only produce a false "it is running here". That is safe to skip precisely because
180
+ // provenance is now decided at the call site that started the broker: anything this machine
181
+ // actually runs is stamped `up` and does reach the check.
182
+ //
183
+ // Skipped entirely under `--force`: the probe itself throws on a multi-tenant or unreadable
184
+ // root, which must not defeat the documented override. Keyed on the entry's OWN space rather
185
+ // than one re-resolved from the root, which on a multi-tenant root can name another tenant.
186
+ const running = m.origin === "manual" || v.force ? undefined : liveMeshOwner(m.root, m.space);
187
+ if (running) {
188
+ console.error(c.red(`✗ "${space}" is running from ${m.root} (${running}) - \`cotal down\` there stops it and drops the record; --force drops the record only, leaving the mesh running`));
189
+ failed = true;
190
+ continue;
191
+ }
192
+ removeMesh(space);
193
+ if (getCurrent() === space) {
194
+ clearCurrent();
195
+ clearedCurrent = true;
196
+ }
197
+ console.log(c.green(`✓ unregistered "${space}"`), c.dim(m.server));
198
+ }
199
+ if (clearedCurrent)
200
+ console.log(c.dim("there is no default mesh now - `cotal use <name>` to set one"));
201
+ if (failed)
202
+ process.exit(1);
203
+ }
204
+ // ---- completion -----------------------------------------------------------------------------
205
+ export function meshesComplete(argv) {
206
+ const flag = completingFlagValue(argv, meshesFlags);
207
+ if (flag?.name === "mode")
208
+ return { items: [{ value: "auth" }, { value: "open" }], directive: "nofiles" };
209
+ if (flag?.name === "root")
210
+ return { items: [], directive: "default" }; // a directory
211
+ if (flag?.name === "server")
212
+ return { items: [], directive: "nofiles" };
213
+ const sub = argv[0];
214
+ if (argv.length <= 1)
215
+ return { items: SUBCOMMANDS.map((value) => ({ value })), directive: "nofiles" };
216
+ // `rm` completes on what's registered; `add` names a mesh that by definition isn't.
217
+ if (sub === "rm" || sub === "remove")
218
+ return { items: loadMeshes().map((m) => ({ value: m.space })), directive: "nofiles" };
219
+ return { items: [], directive: "nofiles" };
220
+ }
25
221
  //# sourceMappingURL=meshes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"meshes.js","sourceRoot":"","sources":["../../src/commands/meshes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;0EAE0E;AAC1E,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAgB;IAC3C,MAAM,gBAAgB,EAAE,CAAC;IACzB,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,gGAAgG;IAChG,kFAAkF;IAClF,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,OAAO,2DAA2D,CAAC,CAAC,CAAC;AAC/G,CAAC"}
1
+ {"version":3,"file":"meshes.js","sourceRoot":"","sources":["../../src/commands/meshes.ts"],"names":[],"mappings":"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"}
@@ -169,14 +169,18 @@ async function printRegistry() {
169
169
  const current = getCurrent();
170
170
  section("Recorded Meshes");
171
171
  if (!meshes.length) {
172
- console.log(c.dim(" none - start one with `cotal up --detach`"));
172
+ console.log(c.dim(" none - start one with `cotal up --detach`, or register one running elsewhere with `cotal meshes add`"));
173
173
  return;
174
174
  }
175
175
  const pad = Math.max(...meshes.map((m) => m.space.length));
176
176
  await Promise.all(meshes.map(async (m) => {
177
177
  const mark = m.space === current ? c.green("*") : " ";
178
178
  const live = await isReachable(m.server);
179
- console.log(` ${mark} ${m.space.padEnd(pad)} ${live ? c.green("reachable") : c.red("down")} ${c.dim(`${m.mode} ${m.server} ${m.root}`)}`);
179
+ // A `down` record means two different things, and the repair differs: a mesh this machine
180
+ // started can be re-`up`ed here, one registered by hand runs somewhere this machine doesn't
181
+ // control (and, unlike the others, its record is never swept away for it).
182
+ const origin = m.origin === "manual" ? c.dim(" registered") : "";
183
+ console.log(` ${mark} ${m.space.padEnd(pad)} ${live ? c.green("reachable") : c.red("down")} ${c.dim(`${m.mode} ${m.server} ${m.root}`)}${origin}`);
180
184
  }));
181
185
  if (current && !meshes.some((m) => m.space === current))
182
186
  console.log(c.dim(` note: current mesh "${current}" is not recorded`));