@cotal-ai/cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/channels.d.ts +4 -2
- package/dist/commands/channels.d.ts.map +1 -1
- package/dist/commands/channels.js +17 -18
- package/dist/commands/channels.js.map +1 -1
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +13 -29
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +7 -0
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +6 -13
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/join.d.ts.map +1 -1
- package/dist/commands/join.js +20 -7
- package/dist/commands/join.js.map +1 -1
- package/dist/commands/meshes.d.ts +5 -0
- package/dist/commands/meshes.d.ts.map +1 -0
- package/dist/commands/meshes.js +25 -0
- package/dist/commands/meshes.js.map +1 -0
- package/dist/commands/spawn.d.ts +4 -2
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +54 -22
- package/dist/commands/spawn.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +112 -5
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/use.d.ts +7 -0
- package/dist/commands/use.d.ts.map +1 -0
- package/dist/commands/use.js +27 -0
- package/dist/commands/use.js.map +1 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +57 -27
- package/dist/commands/web.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/connect.d.ts +72 -0
- package/dist/lib/connect.d.ts.map +1 -0
- package/dist/lib/connect.js +115 -0
- package/dist/lib/connect.js.map +1 -0
- package/dist/lib/meshes.d.ts +8 -0
- package/dist/lib/meshes.d.ts.map +1 -0
- package/dist/lib/meshes.js +15 -0
- package/dist/lib/meshes.js.map +1 -0
- package/dist/lib/onboard.js +6 -6
- package/dist/lib/onboard.js.map +1 -1
- package/dist/lib/transient.d.ts +7 -6
- package/dist/lib/transient.d.ts.map +1 -1
- package/dist/lib/transient.js +6 -25
- package/dist/lib/transient.js.map +1 -1
- package/dist/web/graph.html +204 -0
- package/dist/web/graph.js +453 -0
- package/dist/web/index.html +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type MeshTarget, type Profile, type SpaceAuth } from "@cotal-ai/core";
|
|
2
|
+
/**
|
|
3
|
+
* The one way every command that touches a running mesh figures out WHICH mesh + with what creds,
|
|
4
|
+
* and confirms it's actually up — so `spawn`, `send`/`dm`/`msg`/`ask`, `console`, `join`, `web`,
|
|
5
|
+
* `channels`, `history`, and `personas --running` all behave identically from any directory instead
|
|
6
|
+
* of each re-deriving it from a cwd walk-up (which mistook `$HOME/.cotal` for a space and crashed
|
|
7
|
+
* with a raw NATS auth violation). Two escape hatches take a RAW off-registry connection (no
|
|
8
|
+
* registry lookup, no stale-prune): explicit `--creds`, and `--server` + an unregistered `--space`
|
|
9
|
+
* (an open remote mesh that has no creds to pass).
|
|
10
|
+
*/
|
|
11
|
+
export interface ConnectFlags {
|
|
12
|
+
server?: string;
|
|
13
|
+
space?: string;
|
|
14
|
+
/** Explicit creds file — triggers a raw off-registry connection (see {@link connectOrExit}). */
|
|
15
|
+
creds?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Raw NATS auth for an off-registry connection — a join link / --token / --user+--pass / --creds.
|
|
18
|
+
* Structurally matches what `probeConnect` accepts. */
|
|
19
|
+
export interface RawAuth {
|
|
20
|
+
token?: string;
|
|
21
|
+
user?: string;
|
|
22
|
+
pass?: string;
|
|
23
|
+
creds?: string;
|
|
24
|
+
tls?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface Connection {
|
|
27
|
+
server: string;
|
|
28
|
+
space: string;
|
|
29
|
+
creds?: string;
|
|
30
|
+
/** The mesh's trust material when resolved from the registry on an auth mesh — undefined for an
|
|
31
|
+
* open mesh or a raw off-registry connection (`--creds` or `--server`+unregistered `--space`).
|
|
32
|
+
* (web keeps it for its per-delete manager mint.) */
|
|
33
|
+
auth?: SpaceAuth;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve where a mesh-touching command connects + with what creds.
|
|
37
|
+
* • Explicit `--creds` → a RAW off-registry connection: straight to `--server` (default loopback)
|
|
38
|
+
* as `--space`, with those creds. No registry lookup, no stale-prune, plain reachability message
|
|
39
|
+
* (the user is deliberately off-registry — e.g. a remote mesh that isn't locally recorded).
|
|
40
|
+
* • Otherwise → resolve the running mesh from the registry (works from any dir), mint `role` creds
|
|
41
|
+
* on an auth mesh, and preflight with the registry's stale-prune.
|
|
42
|
+
*/
|
|
43
|
+
export declare function connectOrExit(flags: ConnectFlags, role: Profile): Promise<Connection>;
|
|
44
|
+
/** Reachability check for a RAW (off-registry) connection — one plain sentence, never a registry/
|
|
45
|
+
* stale-entry message and never a prune. Used by the `--creds` escape hatch and `join`'s explicit
|
|
46
|
+
* (link/token/creds) path, both of which connect to a broker the user named, not the registry. */
|
|
47
|
+
export declare function reachableOrExit(server: string, auth?: RawAuth): Promise<void>;
|
|
48
|
+
/** Resolve the mesh a command targets, exiting with one human sentence on an unresolved/ambiguous
|
|
49
|
+
* registry rather than a stack trace. Prunes dead registry entries first so a crashed mesh doesn't
|
|
50
|
+
* block a bare command or get offered by `--space`. */
|
|
51
|
+
export declare function resolveTargetOrExit(flags: {
|
|
52
|
+
server?: string;
|
|
53
|
+
space?: string;
|
|
54
|
+
}): Promise<MeshTarget>;
|
|
55
|
+
/** The five distinct ways a preflight fails. Each also says whether the target OWNS its registry
|
|
56
|
+
* entry (→ prune): `fromRegistry` means the server+mode came from a registry record (incl. a
|
|
57
|
+
* `local-recorded` project matched by root), so a definitive failure is a stale-entry signal. */
|
|
58
|
+
export type PreflightFailure = "unreachable" | "registry-creds-rejected" | "registry-open-now-auth" | "creds-rejected" | "open-wants-auth";
|
|
59
|
+
/** Pure decision for {@link preflightOrExit} — separated from I/O so the whole branch tree is
|
|
60
|
+
* unit-testable (it's the riskiest new logic: a wrong branch prunes a LIVE registry entry). A
|
|
61
|
+
* non-registry source (`flag-server`/`local-space`, or a raw `--creds` connection) is NEVER pruned;
|
|
62
|
+
* the user owns that diagnosis. */
|
|
63
|
+
export declare function classifyPreflightFailure(source: MeshTarget["source"], reason: "auth-required" | "unreachable", hasAuth: boolean): {
|
|
64
|
+
prune: boolean;
|
|
65
|
+
kind: PreflightFailure;
|
|
66
|
+
};
|
|
67
|
+
/** Confirm the resolved mesh is up and accepts these creds — replaces the raw NATS "Authorization
|
|
68
|
+
* Violation" trace with one sentence, and prunes the entry if the broker is gone / mismatched.
|
|
69
|
+
* Probes with `probeCreds` when given (the caller's `--creds`/minted creds); otherwise mints a
|
|
70
|
+
* throwaway identity from the target's own trust material. */
|
|
71
|
+
export declare function preflightOrExit(target: MeshTarget, probeCreds?: string): Promise<void>;
|
|
72
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/lib/connect.ts"],"names":[],"mappings":"AACA,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAIxB;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;wDACwD;AACxD,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;0DAEsD;IACtD,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAoB3F;AAED;;mGAEmG;AACnG,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,OAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvF;AAED;;wDAEwD;AACxD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgBtB;AAED;;kGAEkG;AAClG,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,yBAAyB,GACzB,wBAAwB,GACxB,gBAAgB,GAChB,iBAAiB,CAAC;AAEtB;;;oCAGoC;AACpC,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,MAAM,EAAE,eAAe,GAAG,aAAa,EACvC,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAW5C;AAiBD;;;+DAG+D;AAC/D,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5F"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { DEFAULT_SERVER, DEFAULT_SPACE, findMesh, getCurrent, mintCreds, newIdentity, probeConnect, removeMesh, resolveMeshTarget, } from "@cotal-ai/core";
|
|
3
|
+
import { c } from "../ui.js";
|
|
4
|
+
import { pruneStaleMeshes } from "./meshes.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve where a mesh-touching command connects + with what creds.
|
|
7
|
+
* • Explicit `--creds` → a RAW off-registry connection: straight to `--server` (default loopback)
|
|
8
|
+
* as `--space`, with those creds. No registry lookup, no stale-prune, plain reachability message
|
|
9
|
+
* (the user is deliberately off-registry — e.g. a remote mesh that isn't locally recorded).
|
|
10
|
+
* • Otherwise → resolve the running mesh from the registry (works from any dir), mint `role` creds
|
|
11
|
+
* on an auth mesh, and preflight with the registry's stale-prune.
|
|
12
|
+
*/
|
|
13
|
+
export async function connectOrExit(flags, role) {
|
|
14
|
+
if (flags.creds) {
|
|
15
|
+
const server = flags.server ?? DEFAULT_SERVER;
|
|
16
|
+
const space = flags.space ?? DEFAULT_SPACE;
|
|
17
|
+
const creds = readFileSync(flags.creds, "utf8");
|
|
18
|
+
await reachableOrExit(server, { creds });
|
|
19
|
+
return { server, space, creds };
|
|
20
|
+
}
|
|
21
|
+
// A raw OPEN remote mesh: explicit `--server` + a `--space` that isn't locally registered. Naming
|
|
22
|
+
// both is as deliberate as `--creds`, but an open broker has no creds to pass — connect bare,
|
|
23
|
+
// off-registry (no registry lookup, no prune). A registered `--space` still goes through the
|
|
24
|
+
// resolver below (which honors `--server` as an override); `--server` alone resolves there too.
|
|
25
|
+
if (flags.server && flags.space && !findMesh(flags.space)) {
|
|
26
|
+
await reachableOrExit(flags.server, {});
|
|
27
|
+
return { server: flags.server, space: flags.space };
|
|
28
|
+
}
|
|
29
|
+
const target = await resolveTargetOrExit({ server: flags.server, space: flags.space });
|
|
30
|
+
const creds = target.auth ? await mintCreds(target.auth, newIdentity(), role) : undefined;
|
|
31
|
+
await preflightOrExit(target, creds);
|
|
32
|
+
return { server: target.server, space: target.space, creds, auth: target.auth };
|
|
33
|
+
}
|
|
34
|
+
/** Reachability check for a RAW (off-registry) connection — one plain sentence, never a registry/
|
|
35
|
+
* stale-entry message and never a prune. Used by the `--creds` escape hatch and `join`'s explicit
|
|
36
|
+
* (link/token/creds) path, both of which connect to a broker the user named, not the registry. */
|
|
37
|
+
export async function reachableOrExit(server, auth = {}) {
|
|
38
|
+
const probe = await probeConnect(server, auth);
|
|
39
|
+
if (probe.ok)
|
|
40
|
+
return;
|
|
41
|
+
console.error(c.red(probe.reason === "auth-required"
|
|
42
|
+
? `✗ credentials rejected at ${server} — check your creds, or the broker wants different auth`
|
|
43
|
+
: `✗ can't reach a broker at ${server} — is it running? (\`cotal up\`)`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
/** Resolve the mesh a command targets, exiting with one human sentence on an unresolved/ambiguous
|
|
47
|
+
* registry rather than a stack trace. Prunes dead registry entries first so a crashed mesh doesn't
|
|
48
|
+
* block a bare command or get offered by `--space`. */
|
|
49
|
+
export async function resolveTargetOrExit(flags) {
|
|
50
|
+
await pruneStaleMeshes();
|
|
51
|
+
let target;
|
|
52
|
+
try {
|
|
53
|
+
target = resolveMeshTarget(process.cwd(), flags);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
console.error(c.red(`✗ ${e.message}`));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
// If a dangling `current` was silently bypassed — it named a mesh that's since gone and we fell
|
|
60
|
+
// back to the only live one — say so. The N>1 case errors loudly; this is the one spot that would
|
|
61
|
+
// otherwise quietly redirect a stale default.
|
|
62
|
+
const cur = getCurrent();
|
|
63
|
+
if (cur && !findMesh(cur) && target.source === "registry")
|
|
64
|
+
console.error(c.dim(`note: default mesh "${cur}" is down — using "${target.space}"`));
|
|
65
|
+
return target;
|
|
66
|
+
}
|
|
67
|
+
/** Pure decision for {@link preflightOrExit} — separated from I/O so the whole branch tree is
|
|
68
|
+
* unit-testable (it's the riskiest new logic: a wrong branch prunes a LIVE registry entry). A
|
|
69
|
+
* non-registry source (`flag-server`/`local-space`, or a raw `--creds` connection) is NEVER pruned;
|
|
70
|
+
* the user owns that diagnosis. */
|
|
71
|
+
export function classifyPreflightFailure(source, reason, hasAuth) {
|
|
72
|
+
const fromRegistry = source === "registry" ||
|
|
73
|
+
source === "current" ||
|
|
74
|
+
source === "flag-space" ||
|
|
75
|
+
source === "local-recorded";
|
|
76
|
+
if (reason === "unreachable")
|
|
77
|
+
return { prune: fromRegistry, kind: "unreachable" };
|
|
78
|
+
if (fromRegistry && hasAuth)
|
|
79
|
+
return { prune: true, kind: "registry-creds-rejected" };
|
|
80
|
+
if (fromRegistry)
|
|
81
|
+
return { prune: true, kind: "registry-open-now-auth" };
|
|
82
|
+
if (hasAuth)
|
|
83
|
+
return { prune: false, kind: "creds-rejected" };
|
|
84
|
+
return { prune: false, kind: "open-wants-auth" };
|
|
85
|
+
}
|
|
86
|
+
function preflightMessage(kind, t, pruned) {
|
|
87
|
+
switch (kind) {
|
|
88
|
+
case "unreachable":
|
|
89
|
+
return `✗ no mesh running at ${t.server}${pruned ? " (stale registry entry — removed)" : ""} — run \`cotal up\``;
|
|
90
|
+
case "registry-creds-rejected":
|
|
91
|
+
return `✗ mesh "${t.space}" at ${t.server} no longer matches its registry entry (credentials rejected — port reused?) — re-run \`cotal up\` from ${t.root}, or \`cotal meshes\` to see what's live`;
|
|
92
|
+
case "registry-open-now-auth":
|
|
93
|
+
return `✗ open mesh "${t.space}" at ${t.server} no longer matches its registry entry (broker now requires auth — port reused?) — re-run \`cotal up\` from ${t.root}, or \`cotal meshes\` to see what's live`;
|
|
94
|
+
case "creds-rejected":
|
|
95
|
+
return `✗ credentials for "${t.space}" were rejected at ${t.server} — a different mesh may be running there. Run \`cotal meshes\` to check, or \`cotal up\` here to start yours`;
|
|
96
|
+
case "open-wants-auth":
|
|
97
|
+
return `✗ broker at ${t.server} requires auth, but this mesh is open (no trust material) — use \`--space <name>\` for an auth mesh, or run \`cotal up\` here without \`--open\``;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Confirm the resolved mesh is up and accepts these creds — replaces the raw NATS "Authorization
|
|
101
|
+
* Violation" trace with one sentence, and prunes the entry if the broker is gone / mismatched.
|
|
102
|
+
* Probes with `probeCreds` when given (the caller's `--creds`/minted creds); otherwise mints a
|
|
103
|
+
* throwaway identity from the target's own trust material. */
|
|
104
|
+
export async function preflightOrExit(target, probeCreds) {
|
|
105
|
+
const creds = probeCreds ?? (target.auth ? await mintCreds(target.auth, newIdentity(), "manager") : undefined);
|
|
106
|
+
const probe = await probeConnect(target.server, creds ? { creds } : {});
|
|
107
|
+
if (probe.ok)
|
|
108
|
+
return;
|
|
109
|
+
const { prune, kind } = classifyPreflightFailure(target.source, probe.reason, Boolean(target.auth));
|
|
110
|
+
if (prune)
|
|
111
|
+
removeMesh(target.space);
|
|
112
|
+
console.error(c.red(preflightMessage(kind, target, prune)));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/lib/connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop registry entries whose broker is gone — a `cotal up` that crashed or was `kill -9`'d without
|
|
3
|
+
* `cotal down` leaves a record behind. Probe each in parallel; only `unreachable` (refused/timeout)
|
|
4
|
+
* is stale, an auth broker answering `auth-required` is alive. Called by `spawn`/`use`/`meshes`
|
|
5
|
+
* before they act on the registry — never by completion (a `<TAB>` must not open the network).
|
|
6
|
+
*/
|
|
7
|
+
export declare function pruneStaleMeshes(): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=meshes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meshes.d.ts","sourceRoot":"","sources":["../../src/lib/meshes.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAOtD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadMeshes, probeConnect, removeMesh } from "@cotal-ai/core";
|
|
2
|
+
/**
|
|
3
|
+
* Drop registry entries whose broker is gone — a `cotal up` that crashed or was `kill -9`'d without
|
|
4
|
+
* `cotal down` leaves a record behind. Probe each in parallel; only `unreachable` (refused/timeout)
|
|
5
|
+
* is stale, an auth broker answering `auth-required` is alive. Called by `spawn`/`use`/`meshes`
|
|
6
|
+
* before they act on the registry — never by completion (a `<TAB>` must not open the network).
|
|
7
|
+
*/
|
|
8
|
+
export async function pruneStaleMeshes() {
|
|
9
|
+
await Promise.all(loadMeshes().map(async (m) => {
|
|
10
|
+
const r = await probeConnect(m.server);
|
|
11
|
+
if (!r.ok && r.reason === "unreachable")
|
|
12
|
+
removeMesh(m.space);
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=meshes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meshes.js","sourceRoot":"","sources":["../../src/lib/meshes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,OAAO,CAAC,GAAG,CACf,UAAU,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC3B,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,aAAa;YAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
|
package/dist/lib/onboard.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { join } from "node:path";
|
|
3
|
+
import { homeCotalDir } from "@cotal-ai/core";
|
|
4
4
|
/** Machine-level "I've onboarded before" marker. Its presence flips `cotal` from the
|
|
5
5
|
* full first-run flow to the compact ensure+status run. Lives next to the materialized
|
|
6
|
-
* plugin marketplace under ~/.cotal. */
|
|
7
|
-
const MARKER = join(
|
|
6
|
+
* plugin marketplace under ~/.cotal (COTAL_HOME-overridable, via {@link homeCotalDir}). */
|
|
7
|
+
const MARKER = () => join(homeCotalDir(), "onboarded.json");
|
|
8
8
|
export function isOnboarded() {
|
|
9
|
-
return existsSync(MARKER);
|
|
9
|
+
return existsSync(MARKER());
|
|
10
10
|
}
|
|
11
11
|
export function markOnboarded(version) {
|
|
12
|
-
mkdirSync(
|
|
13
|
-
writeFileSync(MARKER, JSON.stringify({ version, ts: new Date().toISOString() }, null, 2));
|
|
12
|
+
mkdirSync(homeCotalDir(), { recursive: true, mode: 0o700 });
|
|
13
|
+
writeFileSync(MARKER(), JSON.stringify({ version, ts: new Date().toISOString() }, null, 2));
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=onboard.js.map
|
package/dist/lib/onboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/lib/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/lib/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;4FAE4F;AAC5F,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,gBAAgB,CAAC,CAAC;AAE5D,MAAM,UAAU,WAAW;IACzB,OAAO,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,SAAS,CAAC,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,aAAa,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9F,CAAC"}
|
package/dist/lib/transient.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { CotalEndpoint } from "@cotal-ai/core";
|
|
2
2
|
/**
|
|
3
3
|
* A one-shot, write-capable connection for the headless commands that touch the live mesh
|
|
4
|
-
* (`dm`/`msg`/`ask`, and `personas list --running`).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* (`dm`/`msg`/`ask`, and `personas list --running`). Resolution + creds + reachability all go through
|
|
5
|
+
* the shared `connectOrExit` (so these work from any directory, and an explicit `--creds` is a raw
|
|
6
|
+
* off-registry connection). Opens a transient endpoint that never joins the roster, does the one
|
|
7
|
+
* thing, stops.
|
|
7
8
|
*/
|
|
8
9
|
export interface ConnectValues {
|
|
9
10
|
space?: string;
|
|
10
11
|
server?: string;
|
|
11
12
|
creds?: string;
|
|
12
13
|
}
|
|
13
|
-
/** Resolve where to connect
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
/** Resolve where to connect + with what credentials (`--creds` → raw off-registry; else the running
|
|
15
|
+
* mesh's minted manager creds). Fail-loud — an unresolved registry or an unreachable/auth-mismatched
|
|
16
|
+
* broker exits with one sentence, never degrades. */
|
|
16
17
|
export declare function resolveConnect(values: ConnectValues): Promise<{
|
|
17
18
|
server: string;
|
|
18
19
|
space: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transient.d.ts","sourceRoot":"","sources":["../../src/lib/transient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transient.d.ts","sourceRoot":"","sources":["../../src/lib/transient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI/C;;;;;;GAMG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;sDAEsD;AACtD,wBAAsB,cAAc,CAClC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAG5D;AAED;iGACiG;AACjG,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,EAAE,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAe/C"}
|
package/dist/lib/transient.js
CHANGED
|
@@ -1,30 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CotalEndpoint, isReachable, DEFAULT_SERVER, DEFAULT_SPACE, authDir, loadSpaceAuth, mintCreds, newIdentity, } from "@cotal-ai/core";
|
|
1
|
+
import { CotalEndpoint } from "@cotal-ai/core";
|
|
3
2
|
import { c } from "../ui.js";
|
|
4
|
-
import {
|
|
5
|
-
/** Resolve where to connect
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
import { connectOrExit } from "./connect.js";
|
|
4
|
+
/** Resolve where to connect + with what credentials (`--creds` → raw off-registry; else the running
|
|
5
|
+
* mesh's minted manager creds). Fail-loud — an unresolved registry or an unreachable/auth-mismatched
|
|
6
|
+
* broker exits with one sentence, never degrades. */
|
|
8
7
|
export async function resolveConnect(values) {
|
|
9
|
-
const server = values
|
|
10
|
-
let creds = values.creds ? readFileSync(values.creds, "utf8") : undefined;
|
|
11
|
-
let space = values.space;
|
|
12
|
-
if (!creds) {
|
|
13
|
-
const auth = loadSpaceAuth(authDir(cotalRoot()));
|
|
14
|
-
if (auth) {
|
|
15
|
-
if (space && space !== auth.space) {
|
|
16
|
-
console.error(c.red(`Auth here is for space "${auth.space}", not "${space}". Use --space ${auth.space} (or pass --creds).`));
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
space = auth.space;
|
|
20
|
-
creds = await mintCreds(auth, newIdentity(), "manager");
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
space = space ?? DEFAULT_SPACE;
|
|
24
|
-
if (!(await isReachable(server, { creds }))) {
|
|
25
|
-
console.error(c.red(`Can't reach NATS at ${server}. Run: cotal up`));
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
8
|
+
const { server, space, creds } = await connectOrExit(values, "manager");
|
|
28
9
|
return { server, space, creds };
|
|
29
10
|
}
|
|
30
11
|
/** Open a transient endpoint: it watches presence (so name→id resolution and the live roster work)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transient.js","sourceRoot":"","sources":["../../src/lib/transient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"transient.js","sourceRoot":"","sources":["../../src/lib/transient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAgB7C;;sDAEsD;AACtD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAqB;IAErB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACxE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAClC,CAAC;AAED;iGACiG;AACjG,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAqB,EACrB,IAAY;IAEZ,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,EAAE,GAAG,IAAI,aAAa,CAAC;QAC3B,KAAK;QACL,OAAO,EAAE,MAAM;QACf,KAAK;QACL,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,KAAK;QACd,gBAAgB,EAAE,KAAK;QACvB,aAAa,EAAE,IAAI;QACnB,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE;KACjC,CAAC,CAAC;IACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Cotal · graph</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link
|
|
10
|
+
href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;500;600;700&display=swap"
|
|
11
|
+
rel="stylesheet"
|
|
12
|
+
/>
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #131a26; --panel: #1b2330cc; --line: #38414f; --fg: #eef2f7;
|
|
16
|
+
--dim: #8b949e; --faint: #6e7681;
|
|
17
|
+
--chat: #58a6ff; --unicast: #d29922; --anycast: #3fb950;
|
|
18
|
+
--working: #3fb950; --waiting: #e3b341; --idle: #6e7681; --red: #f85149;
|
|
19
|
+
--font: "Work Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
html, body { height: 100%; margin: 0; }
|
|
23
|
+
body {
|
|
24
|
+
font-family: var(--font); font-size: 13px; background: var(--bg); color: var(--fg);
|
|
25
|
+
overflow: hidden; -webkit-font-smoothing: antialiased;
|
|
26
|
+
}
|
|
27
|
+
#graph { position: fixed; inset: 0; display: block; cursor: grab; }
|
|
28
|
+
#graph.hover { cursor: pointer; }
|
|
29
|
+
|
|
30
|
+
/* floating glass surfaces */
|
|
31
|
+
.glass {
|
|
32
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
|
33
|
+
backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ── header (top, floating) ── */
|
|
37
|
+
header {
|
|
38
|
+
position: fixed; top: 14px; left: 14px; right: 14px; z-index: 10;
|
|
39
|
+
display: flex; align-items: center; gap: 16px; padding: 9px 14px;
|
|
40
|
+
}
|
|
41
|
+
.brand { display: flex; align-items: center; gap: 9px; }
|
|
42
|
+
.brand .mark { width: 9px; height: 9px; border-radius: 50%; background: var(--chat);
|
|
43
|
+
box-shadow: 0 0 10px var(--chat); }
|
|
44
|
+
.brand .title { font-size: 16px; font-weight: 700; }
|
|
45
|
+
.brand .space { font-size: 13px; color: var(--dim); }
|
|
46
|
+
.navlink {
|
|
47
|
+
font-size: 12px; font-weight: 600; color: var(--dim); text-decoration: none;
|
|
48
|
+
padding: 5px 11px; border-radius: 8px; border: 1px solid var(--line);
|
|
49
|
+
}
|
|
50
|
+
.navlink:hover { color: var(--fg); border-color: var(--chat); }
|
|
51
|
+
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px;
|
|
52
|
+
border-radius: 20px; background: #1f2d24; }
|
|
53
|
+
.pill .d { width: 7px; height: 7px; border-radius: 50%; background: var(--working); }
|
|
54
|
+
.pill .t { font-size: 11px; font-weight: 600; color: var(--working); }
|
|
55
|
+
.pill.down { background: #2a1717; } .pill.down .d { background: var(--red); }
|
|
56
|
+
.pill.down .t { color: var(--red); }
|
|
57
|
+
/* membership-freshness pill states */
|
|
58
|
+
.pill.stale { background: #2a2417; } .pill.stale .d { background: var(--waiting); }
|
|
59
|
+
.pill.stale .t { color: var(--waiting); }
|
|
60
|
+
.pill.off { background: #1f242c; } .pill.off .d { background: var(--faint); }
|
|
61
|
+
.pill.off .t { color: var(--dim); }
|
|
62
|
+
|
|
63
|
+
.ctrls { margin-left: auto; display: flex; align-items: center; gap: 7px; }
|
|
64
|
+
.grp { display: flex; align-items: center; gap: 4px; padding-right: 7px;
|
|
65
|
+
margin-right: 0; border-right: 1px solid var(--line); }
|
|
66
|
+
.grp:last-child { border-right: 0; padding-right: 0; }
|
|
67
|
+
.chip {
|
|
68
|
+
font-size: 11px; padding: 4px 9px; border-radius: 13px; cursor: pointer;
|
|
69
|
+
color: var(--faint); user-select: none; border: 1px solid transparent; white-space: nowrap;
|
|
70
|
+
}
|
|
71
|
+
.chip:hover { color: var(--fg); }
|
|
72
|
+
.chip.on { background: #ffffff10; border-color: var(--line); color: var(--fg); }
|
|
73
|
+
.chip.mode.chat.on { color: var(--chat); } .chip.mode.unicast.on { color: var(--unicast); }
|
|
74
|
+
.chip.mode.anycast.on { color: var(--anycast); }
|
|
75
|
+
.chip.pause.on { color: var(--waiting); border-color: var(--waiting); }
|
|
76
|
+
|
|
77
|
+
/* ── legend (bottom-left, collapsible) ── */
|
|
78
|
+
#legend { position: fixed; left: 14px; bottom: 14px; z-index: 10; padding: 0;
|
|
79
|
+
max-width: 200px; overflow: hidden; }
|
|
80
|
+
#legend .ltoggle {
|
|
81
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
82
|
+
width: 100%; padding: 7px 12px; cursor: pointer; appearance: none;
|
|
83
|
+
font-size: 10px; font-weight: 600; letter-spacing: .6px; color: var(--faint);
|
|
84
|
+
text-transform: uppercase; background: transparent; border: 0;
|
|
85
|
+
text-align: left; font-family: inherit;
|
|
86
|
+
}
|
|
87
|
+
#legend .ltoggle:hover { color: var(--dim); }
|
|
88
|
+
#legend .ltoggle .chev { font-size: 9px; opacity: .5; transition: transform .2s ease; }
|
|
89
|
+
#legend.collapsed .ltoggle .chev { transform: rotate(-90deg); }
|
|
90
|
+
#legend .lbody { display: flex; flex-direction: column; gap: 5px; padding: 2px 12px 10px; }
|
|
91
|
+
#legend.collapsed .lbody { display: none; }
|
|
92
|
+
#legend .ls { font-size: 9px; font-weight: 600; letter-spacing: .5px; color: var(--faint);
|
|
93
|
+
text-transform: uppercase; opacity: .65; margin-top: 3px; }
|
|
94
|
+
#legend .ls:first-child { margin-top: 0; }
|
|
95
|
+
#legend .row { display: flex; align-items: center; gap: 9px; font-size: 11px; color: var(--dim); }
|
|
96
|
+
#legend .sw { width: 14px; height: 2px; border-radius: 1px; flex: none; }
|
|
97
|
+
#legend .sw-dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
98
|
+
#legend .ring { width: 8px; height: 8px; border-radius: 50%; flex: none;
|
|
99
|
+
background: transparent; box-shadow: 0 0 0 1.5px currentColor inset; }
|
|
100
|
+
|
|
101
|
+
/* ── detail panel (right, floating) ── */
|
|
102
|
+
#detail {
|
|
103
|
+
position: fixed; top: 64px; right: 14px; bottom: 14px; width: 308px; z-index: 10;
|
|
104
|
+
padding: 16px 16px 18px; overflow-y: auto; display: none; flex-direction: column; gap: 14px;
|
|
105
|
+
scrollbar-width: thin; scrollbar-color: var(--line) transparent;
|
|
106
|
+
}
|
|
107
|
+
#detail::-webkit-scrollbar { width: 6px; }
|
|
108
|
+
#detail::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
|
|
109
|
+
#detail.open { display: flex; }
|
|
110
|
+
#detail .x { position: absolute; top: 10px; right: 10px; cursor: pointer; color: var(--faint);
|
|
111
|
+
font-size: 13px; width: 22px; height: 22px; display: flex; align-items: center;
|
|
112
|
+
justify-content: center; border-radius: 5px; line-height: 1; }
|
|
113
|
+
#detail .x:hover { color: var(--fg); background: #ffffff12; }
|
|
114
|
+
.d-kind { font-size: 10px; font-weight: 600; letter-spacing: .6px; color: var(--faint);
|
|
115
|
+
text-transform: uppercase; }
|
|
116
|
+
.d-who { font-size: 16px; font-weight: 700; line-height: 1.25; padding-right: 26px; }
|
|
117
|
+
.d-who .role { font-size: 11px; font-weight: 500; color: var(--faint); margin-left: 6px; }
|
|
118
|
+
.d-status { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
|
|
119
|
+
font-weight: 500; padding: 3px 10px; border-radius: 20px; background: #ffffff08;
|
|
120
|
+
color: var(--dim); width: fit-content; }
|
|
121
|
+
.d-status .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor;
|
|
122
|
+
box-shadow: 0 0 6px currentColor; }
|
|
123
|
+
.d-status.working { background: #16231a; color: var(--working); }
|
|
124
|
+
.d-status.waiting { background: #2a2117; color: var(--waiting); }
|
|
125
|
+
.d-status.idle { color: var(--dim); }
|
|
126
|
+
.d-status.offline { color: var(--faint); }
|
|
127
|
+
.d-section { display: flex; flex-direction: column; gap: 5px; }
|
|
128
|
+
.d-label { font-size: 9.5px; font-weight: 600; letter-spacing: .5px; color: var(--faint);
|
|
129
|
+
text-transform: uppercase; }
|
|
130
|
+
.d-block { font-size: 12px; line-height: 1.55; padding: 9px 11px; border-radius: 7px;
|
|
131
|
+
background: #ffffff08; border: 1px solid var(--line); white-space: pre-wrap; word-break: break-word; }
|
|
132
|
+
.d-block.muted { color: var(--dim); font-style: italic; }
|
|
133
|
+
.d-rows { display: flex; flex-direction: column; gap: 5px; }
|
|
134
|
+
.d-row { display: flex; gap: 10px; font-size: 11.5px; line-height: 1.4; }
|
|
135
|
+
.d-row .k { color: var(--faint); min-width: 64px; flex: none; }
|
|
136
|
+
.d-row .v { color: var(--fg); word-break: break-word; }
|
|
137
|
+
.d-msgs { display: flex; flex-direction: column; gap: 5px; }
|
|
138
|
+
.d-msg { font-size: 11px; line-height: 1.45; padding: 7px 10px 8px; border-radius: 6px;
|
|
139
|
+
background: #ffffff06; border-left: 2px solid var(--line); }
|
|
140
|
+
.d-msg .mhead { display: flex; align-items: baseline; gap: 6px; margin-bottom: 3px; }
|
|
141
|
+
.d-msg .m { font-size: 9px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; }
|
|
142
|
+
.d-msg .who { color: var(--fg); font-weight: 600; }
|
|
143
|
+
.d-msg .tgt { color: var(--dim); }
|
|
144
|
+
.d-msg .body { color: var(--dim); word-break: break-word; }
|
|
145
|
+
.d-msg.empty { text-align: center; color: var(--faint); font-style: italic; border-left-color: transparent; }
|
|
146
|
+
/* member / subscription tag rows in the detail panel */
|
|
147
|
+
.d-tags { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
148
|
+
.mtag { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--fg);
|
|
149
|
+
padding: 3px 8px; border-radius: 13px; background: #ffffff08; border: 1px solid var(--line); }
|
|
150
|
+
.mtag .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; box-shadow: 0 0 5px currentColor; }
|
|
151
|
+
.mtag .act { font-size: 9px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase; color: var(--chat); }
|
|
152
|
+
.mtag .off { font-size: 9px; font-weight: 600; letter-spacing: .3px; text-transform: uppercase; color: var(--faint); }
|
|
153
|
+
.ctag { font-size: 11px; color: var(--chat); padding: 3px 8px; border-radius: 13px;
|
|
154
|
+
background: #58a6ff14; border: 1px solid #58a6ff33; }
|
|
155
|
+
.ctag.off { color: var(--dim); background: #ffffff06; border-color: var(--line); }
|
|
156
|
+
.hint { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 9;
|
|
157
|
+
font-size: 11.5px; color: var(--faint); pointer-events: none; transition: opacity .4s; }
|
|
158
|
+
</style>
|
|
159
|
+
</head>
|
|
160
|
+
<body>
|
|
161
|
+
<canvas id="graph"></canvas>
|
|
162
|
+
|
|
163
|
+
<header class="glass">
|
|
164
|
+
<span class="brand">
|
|
165
|
+
<span class="mark"></span>
|
|
166
|
+
<span class="title">Cotal</span>
|
|
167
|
+
<span class="space" id="space"></span>
|
|
168
|
+
</span>
|
|
169
|
+
<a class="navlink" href="/">← Monitor</a>
|
|
170
|
+
<span class="pill down" id="conn"><span class="d"></span><span class="t">connecting</span></span>
|
|
171
|
+
<span class="pill off" id="feed" hidden><span class="d"></span><span class="t">membership</span></span>
|
|
172
|
+
<div class="ctrls">
|
|
173
|
+
<div class="grp" id="modes">
|
|
174
|
+
<span class="chip mode chat on" data-mode="chat">channel</span>
|
|
175
|
+
<span class="chip mode unicast on" data-mode="unicast">direct</span>
|
|
176
|
+
<span class="chip mode anycast on" data-mode="anycast">anycast</span>
|
|
177
|
+
</div>
|
|
178
|
+
<span class="chip pause" id="pause">⏸ pause</span>
|
|
179
|
+
</div>
|
|
180
|
+
</header>
|
|
181
|
+
|
|
182
|
+
<div id="legend" class="glass">
|
|
183
|
+
<button class="ltoggle" id="legendToggle" type="button"><span>legend</span><span class="chev">▾</span></button>
|
|
184
|
+
<div class="lbody">
|
|
185
|
+
<span class="ls">traffic</span>
|
|
186
|
+
<span class="row"><span class="sw" style="background:var(--chat)"></span> channel post → hub</span>
|
|
187
|
+
<span class="row"><span class="sw" style="background:var(--unicast)"></span> direct message</span>
|
|
188
|
+
<span class="row"><span class="sw sw-dot" style="background:var(--anycast)"></span> anycast (to a role)</span>
|
|
189
|
+
<span class="ls">membership</span>
|
|
190
|
+
<span class="row"><span class="sw" style="background:#8493a8"></span> subscribed (live)</span>
|
|
191
|
+
<span class="row"><span class="sw" style="background:#5a6472;opacity:.7"></span> member · offline</span>
|
|
192
|
+
<span class="ls">agents</span>
|
|
193
|
+
<span class="row"><span class="ring" style="color:var(--working)"></span> working</span>
|
|
194
|
+
<span class="row"><span class="ring" style="color:var(--waiting)"></span> waiting · needs input</span>
|
|
195
|
+
<span class="row"><span class="ring" style="color:var(--idle)"></span> idle / offline</span>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<aside id="detail" class="glass"></aside>
|
|
200
|
+
<div class="hint" id="hint">click a node for detail · scroll to zoom · drag to pan</div>
|
|
201
|
+
|
|
202
|
+
<script src="/graph.js"></script>
|
|
203
|
+
</body>
|
|
204
|
+
</html>
|