@monster-orange/den 0.2.8 → 0.4.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/CHANGELOG.md +1731 -0
- package/README.md +4 -2
- package/bin/den +2 -1
- package/docs/distributions.md +730 -6
- package/make/den.mk +19 -4
- package/make/journeys.mk +2 -1
- package/make/local_dev.mk +6 -13
- package/make/secrets-1password.mk +18 -0
- package/make/test.mk +1 -1
- package/package.json +12 -2
- package/schemas/den.plugin.v1.json +156 -0
- package/schemas/den.theme.v1.json +269 -0
- package/scripts/auth-configure.ts +16 -8
- package/scripts/heal-config.ts +0 -5
- package/scripts/setup-1password.ts +41 -19
- package/scripts/setup-secrets.ts +67 -101
- package/src/pillars/api/global-setup.ts +1 -1
- package/src/pillars/journeys/sandbox.ts +23 -18
- package/src/pillars/journeys/tools/browser-session.ts +2 -2
- package/src/scripts/setup-default-workspace.ts +2 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +173 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +192 -0
- package/src/shared/agents/runtimes.ts +47 -0
- package/src/shared/auth/headless-login.ts +1 -1
- package/src/shared/auth/sudo-vault.ts +93 -31
- package/src/shared/auth/token-providers/index.ts +7 -3
- package/src/shared/brand.ts +7 -0
- package/src/shared/config-error.ts +33 -3
- package/src/shared/constants.ts +3 -24
- package/src/shared/den-config.ts +257 -5
- package/src/shared/edge/config.ts +147 -21
- package/src/shared/edge/exec.ts +31 -0
- package/src/shared/edge/included.ts +169 -0
- package/src/shared/edge/render.ts +69 -14
- package/src/shared/edge/runtime.ts +128 -121
- package/src/shared/edge/status.ts +68 -8
- package/src/shared/edge/tls.ts +328 -0
- package/src/shared/edge/types.ts +25 -9
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/default-workspace.ts +16 -1
- package/src/shared/environment-manager/health.ts +73 -38
- package/src/shared/environment-manager/scope.ts +168 -0
- package/src/shared/environment-manager/service-runner.ts +789 -43
- package/src/shared/environment-manager/up-targets.ts +132 -0
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/health/rollup.ts +48 -0
- package/src/shared/open-url.ts +21 -2
- package/src/shared/org-sources.ts +78 -59
- package/src/shared/org.ts +53 -5
- package/src/shared/plugins/manifest.ts +406 -0
- package/src/shared/plugins/store.ts +358 -0
- package/src/shared/process/legacy-adopt.ts +285 -0
- package/src/shared/process/process-group.ts +202 -0
- package/src/shared/process/spawn-streaming.ts +7 -0
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/secrets/doppler.ts +1 -1
- package/src/shared/secrets/index.ts +279 -50
- package/src/shared/secrets/none.ts +56 -0
- package/src/shared/secrets/strategies.ts +202 -0
- package/src/shared/secrets/types.ts +1 -1
- package/src/shared/sessions/client.ts +277 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +79 -0
- package/src/shared/sessions/host.ts +667 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -0
- package/src/shared/smoke/git-remote.ts +40 -0
- package/src/shared/stacks/manifest-commands.ts +1 -1
- package/src/shared/stacks/namespace.ts +178 -0
- package/src/shared/stacks/owners.ts +303 -0
- package/src/shared/stacks/render.ts +8 -3
- package/src/shared/stacks/runtime.ts +100 -16
- package/src/shared/stacks/status.ts +63 -12
- package/src/shared/strategies/registry.ts +89 -0
- package/src/shared/theme/contract.ts +538 -0
- package/src/shared/types/index.ts +12 -3
- package/src/ui/cli/agents-command.ts +812 -0
- package/src/ui/cli/create.ts +24 -7
- package/src/ui/cli/doctor-command.ts +260 -0
- package/src/ui/cli/edge-command.ts +56 -10
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +18 -5
- package/src/ui/cli/health-display.ts +25 -0
- package/src/ui/cli/index.ts +580 -60
- package/src/ui/cli/notice.ts +20 -0
- package/src/ui/cli/plugin-command.ts +849 -0
- package/src/ui/cli/sessions-command.ts +718 -0
- package/src/ui/cli/stack-command.ts +63 -2
- package/src/ui/cli/test-runner.ts +11 -9
- package/src/ui/cli/theme-command.ts +136 -0
- package/src/ui/cli/web-command.ts +47 -23
- package/src/ui/mcp/api.ts +28 -2
- package/src/ui/mcp/scope.ts +145 -20
- package/src/ui/mcp/server.ts +27 -2
- package/src/ui/mcp/tools.ts +94 -12
- package/src/ui/server/appearance-prefs.ts +95 -0
- package/src/ui/server/audit.ts +35 -0
- package/src/ui/server/daemon-reaper.ts +29 -1
- package/src/ui/server/engine-stamp.ts +83 -0
- package/src/ui/server/github-service.ts +23 -44
- package/src/ui/server/graph-page.ts +20 -0
- package/src/ui/server/graph-prefs.ts +44 -1
- package/src/ui/server/http-errors.ts +293 -0
- package/src/ui/server/http-helpers.ts +22 -3
- package/src/ui/server/index.ts +539 -51
- package/src/ui/server/public/appearance.js +531 -0
- package/src/ui/server/public/edge-page.js +248 -0
- package/src/ui/server/public/edge.html +251 -0
- package/src/ui/server/public/environments.html +65 -83
- package/src/ui/server/public/error-page.js +247 -0
- package/src/ui/server/public/error.html +260 -0
- package/src/ui/server/public/graph-model.js +332 -14
- package/src/ui/server/public/graph.css +141 -47
- package/src/ui/server/public/graph.js +223 -44
- package/src/ui/server/public/header.js +396 -600
- package/src/ui/server/public/health-fit.js +248 -0
- package/src/ui/server/public/health-rollup.js +128 -0
- package/src/ui/server/public/i18n/es.js +287 -0
- package/src/ui/server/public/i18n/ja.js +276 -0
- package/src/ui/server/public/index.html +139 -438
- package/src/ui/server/public/local-dev.html +3468 -4044
- package/src/ui/server/public/logs.html +71 -208
- package/src/ui/server/public/panel.js +14 -2
- package/src/ui/server/public/release.html +175 -389
- package/src/ui/server/public/services.html +596 -770
- package/src/ui/server/public/settings-page.js +759 -0
- package/src/ui/server/public/settings-registry.js +521 -0
- package/src/ui/server/public/settings.html +590 -0
- package/src/ui/server/public/shell.js +3869 -0
- package/src/ui/server/public/topbar.js +1413 -0
- package/src/ui/server/public/version-label.js +12 -0
- package/src/ui/server/public/workspace-picker.js +74 -34
- package/src/ui/server/readonly-routes.ts +37 -3
- package/src/ui/server/services-list.ts +94 -17
- package/src/ui/server/settings-distribution.ts +74 -0
- package/src/ui/server/shell-prefs.ts +192 -0
- package/src/ui/server/theme-routes.ts +292 -0
- package/src/ui/server/theme-runtime.ts +407 -0
- package/src/ui/server/ui-assets.ts +16 -1
- package/src/ui/server/ui-language.ts +50 -0
- package/src/ui/server/workspace-routes.ts +48 -18
- package/themes/den/den-theme.json +72 -0
- package/themes/den/favicon.ico +0 -0
- package/themes/den/mark.svg +6 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<cli> doctor --probe` (AG spec §5a-env): check, through the real session
|
|
3
|
+
* host and backend, what environment a session actually gets. Opt-in: plain
|
|
4
|
+
* `doctor` never spawns anything.
|
|
5
|
+
*
|
|
6
|
+
* For a plain session (as `sessions start` builds it, no --keep-env) and
|
|
7
|
+
* for each available runtime's agent env (as `agents start` builds it, the
|
|
8
|
+
* runtime's launch env included, but running the probe instead of the
|
|
9
|
+
* runtime's CLI), it starts a short session running a tiny node script that
|
|
10
|
+
* prints each key name and one flag: whether the name or value is
|
|
11
|
+
* credential-shaped (secret-env.ts, with the redactor's patterns). Values
|
|
12
|
+
* never leave the probe process.
|
|
13
|
+
*
|
|
14
|
+
* A finding is any runtime's scrubEnv name, or any credential-shaped
|
|
15
|
+
* variable, in either env.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as os from "os";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
import {
|
|
21
|
+
agentSessionEnv,
|
|
22
|
+
allScrubEnv,
|
|
23
|
+
getRuntime,
|
|
24
|
+
LaunchError,
|
|
25
|
+
runtimeIds,
|
|
26
|
+
type LaunchInput,
|
|
27
|
+
type Runtime,
|
|
28
|
+
} from "../../shared/agents/runtimes.js";
|
|
29
|
+
import { credentialPatternData } from "../../shared/secret-env.js";
|
|
30
|
+
import type { HostClient } from "../../shared/sessions/client.js";
|
|
31
|
+
import { matchesScrub } from "../../shared/sessions/env.js";
|
|
32
|
+
import { sessionName } from "../../shared/sessions/names.js";
|
|
33
|
+
import type { PtyLoad } from "../../shared/sessions/pty.js";
|
|
34
|
+
|
|
35
|
+
const MARK = "DENPROBE";
|
|
36
|
+
const PROBE_ID = "env-probe";
|
|
37
|
+
/** How long one probe session gets to print and exit. */
|
|
38
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
39
|
+
|
|
40
|
+
/** The probe: key names and a 0/1 flag per key, then an end mark. No values. */
|
|
41
|
+
export function probeScript(): string {
|
|
42
|
+
const p = JSON.stringify(credentialPatternData());
|
|
43
|
+
return [
|
|
44
|
+
`const P = ${p};`,
|
|
45
|
+
`const re = (l) => l.map((r) => new RegExp(r.source, r.flags));`,
|
|
46
|
+
`const N = re(P.name), V = re(P.value);`,
|
|
47
|
+
`for (const k of Object.keys(process.env).sort()) {`,
|
|
48
|
+
` const v = process.env[k] || "";`,
|
|
49
|
+
` const c = N.some((r) => r.test(k)) || (v !== "" && V.some((r) => r.test(v)));`,
|
|
50
|
+
` process.stdout.write("${MARK} " + k + " " + (c ? 1 : 0) + "\\n");`,
|
|
51
|
+
`}`,
|
|
52
|
+
`process.stdout.write("${MARK}-END\\n");`,
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ProbeKey {
|
|
57
|
+
name: string;
|
|
58
|
+
credentialShaped: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The probe's report from its output; null when it did not finish. */
|
|
62
|
+
export function parseProbe(output: string): ProbeKey[] | null {
|
|
63
|
+
const keys: ProbeKey[] = [];
|
|
64
|
+
let ended = false;
|
|
65
|
+
for (const raw of output.split(/\r?\n/)) {
|
|
66
|
+
const line = raw.replace(/\r/g, "").trim();
|
|
67
|
+
if (line === `${MARK}-END`) ended = true;
|
|
68
|
+
const m = new RegExp(`^${MARK} ([A-Za-z_][A-Za-z0-9_]*) ([01])$`).exec(
|
|
69
|
+
line,
|
|
70
|
+
);
|
|
71
|
+
if (m) keys.push({ name: m[1], credentialShaped: m[2] === "1" });
|
|
72
|
+
}
|
|
73
|
+
return ended ? keys : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ProbeFinding {
|
|
77
|
+
key: string;
|
|
78
|
+
why: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ProbeResult {
|
|
82
|
+
/** "plain session", or "agent (<runtime>)". */
|
|
83
|
+
target: string;
|
|
84
|
+
/** False: not probed (the reason says why); never a failure. */
|
|
85
|
+
ran: boolean;
|
|
86
|
+
reason?: string;
|
|
87
|
+
keys?: number;
|
|
88
|
+
findings: ProbeFinding[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ProbeDeps {
|
|
92
|
+
dist: string;
|
|
93
|
+
cli: string;
|
|
94
|
+
env: NodeJS.ProcessEnv;
|
|
95
|
+
pty: () => PtyLoad;
|
|
96
|
+
connect: () => Promise<HostClient>;
|
|
97
|
+
/** A plain session's env, as `sessions start` builds it. */
|
|
98
|
+
plainEnv: (caller: NodeJS.ProcessEnv) => Record<string, string>;
|
|
99
|
+
/** An agent's env, as `agents start` builds it. */
|
|
100
|
+
agentEnv?: (
|
|
101
|
+
caller: NodeJS.ProcessEnv,
|
|
102
|
+
rt: Runtime,
|
|
103
|
+
launch: Record<string, string>,
|
|
104
|
+
agentId: string,
|
|
105
|
+
) => Record<string, string>;
|
|
106
|
+
/** Runtimes to probe (default: every registered one that is available). */
|
|
107
|
+
runtimes?: () => Runtime[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A launch input for the probe: nothing is written, nothing it names runs. */
|
|
111
|
+
function probeInput(d: ProbeDeps, dir: string): LaunchInput {
|
|
112
|
+
return {
|
|
113
|
+
persona: {
|
|
114
|
+
id: PROBE_ID,
|
|
115
|
+
role: "",
|
|
116
|
+
prompt: "",
|
|
117
|
+
standing: false,
|
|
118
|
+
tools: [],
|
|
119
|
+
context: [],
|
|
120
|
+
argv: [PROBE_ID],
|
|
121
|
+
source: "doctor --probe",
|
|
122
|
+
},
|
|
123
|
+
workspace: { name: "doctor", dir },
|
|
124
|
+
agentId: PROBE_ID,
|
|
125
|
+
worktree: dir,
|
|
126
|
+
mcp: { name: d.cli, command: [d.cli], env: {}, scope: "doctor" },
|
|
127
|
+
// Never written: the probe uses only the launch's env.
|
|
128
|
+
filesDir: path.join(dir, PROBE_ID),
|
|
129
|
+
env: d.env,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function findings(keys: ProbeKey[]): ProbeFinding[] {
|
|
134
|
+
const scrub = allScrubEnv();
|
|
135
|
+
const out: ProbeFinding[] = [];
|
|
136
|
+
for (const k of keys) {
|
|
137
|
+
if (matchesScrub(k.name, scrub))
|
|
138
|
+
out.push({ key: k.name, why: "a runtime's scrubEnv name" });
|
|
139
|
+
else if (k.credentialShaped)
|
|
140
|
+
out.push({ key: k.name, why: "credential-shaped" });
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Run the probe in one session with `env`; its report, or why not. */
|
|
146
|
+
async function probeOnce(
|
|
147
|
+
d: ProbeDeps,
|
|
148
|
+
c: HostClient,
|
|
149
|
+
target: string,
|
|
150
|
+
slug: string,
|
|
151
|
+
env: Record<string, string>,
|
|
152
|
+
cwd: string,
|
|
153
|
+
): Promise<ProbeResult> {
|
|
154
|
+
const info = await c.start({
|
|
155
|
+
name: sessionName(d.dist, "doctor", `${PROBE_ID}-${slug}`),
|
|
156
|
+
cwd,
|
|
157
|
+
argv: [process.execPath, "-e", probeScript()],
|
|
158
|
+
env,
|
|
159
|
+
cols: 200,
|
|
160
|
+
rows: 24,
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
const deadline = Date.now() + PROBE_TIMEOUT_MS;
|
|
164
|
+
for (;;) {
|
|
165
|
+
const s = (await c.list()).find((x) => x.ref.id === info.ref.id);
|
|
166
|
+
if (!s || s.exited) break;
|
|
167
|
+
if (Date.now() > deadline) {
|
|
168
|
+
await c.kill(info.ref.id, "KILL").catch(() => undefined);
|
|
169
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
173
|
+
}
|
|
174
|
+
const keys = parseProbe(
|
|
175
|
+
(await c.scrollback(info.ref.id, 1024 * 1024)).toString("utf-8"),
|
|
176
|
+
);
|
|
177
|
+
if (!keys)
|
|
178
|
+
return {
|
|
179
|
+
target,
|
|
180
|
+
ran: true,
|
|
181
|
+
findings: [{ key: "-", why: "the probe did not finish" }],
|
|
182
|
+
};
|
|
183
|
+
return { target, ran: true, keys: keys.length, findings: findings(keys) };
|
|
184
|
+
} finally {
|
|
185
|
+
await c.forget(info.ref.id).catch(() => undefined);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Probe a plain session and each available runtime's agent env. */
|
|
190
|
+
export async function runEnvProbe(d: ProbeDeps): Promise<ProbeResult[]> {
|
|
191
|
+
const pty = d.pty();
|
|
192
|
+
if (!pty.available)
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
target: "sessions",
|
|
196
|
+
ran: false,
|
|
197
|
+
reason: `den-pty is unavailable: ${pty.reason}`,
|
|
198
|
+
findings: [],
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
const runtimes =
|
|
202
|
+
d.runtimes?.() ??
|
|
203
|
+
runtimeIds()
|
|
204
|
+
.map((id) => getRuntime(id))
|
|
205
|
+
.filter((rt): rt is Runtime => !!rt);
|
|
206
|
+
const agentEnv = d.agentEnv ?? agentSessionEnv;
|
|
207
|
+
// The probe writes nothing: its cwd is the temp dir, as it is.
|
|
208
|
+
const dir = fs.realpathSync(os.tmpdir());
|
|
209
|
+
const c = await d.connect();
|
|
210
|
+
try {
|
|
211
|
+
const out: ProbeResult[] = [
|
|
212
|
+
await probeOnce(d, c, "plain session", "plain", d.plainEnv(d.env), dir),
|
|
213
|
+
];
|
|
214
|
+
for (const rt of runtimes) {
|
|
215
|
+
const target = `agent (${rt.id})`;
|
|
216
|
+
const det = rt.detect(d.env);
|
|
217
|
+
if (!det.available) {
|
|
218
|
+
out.push({
|
|
219
|
+
target,
|
|
220
|
+
ran: false,
|
|
221
|
+
reason: `${rt.id} is unavailable: ${det.why ?? "unknown"}`,
|
|
222
|
+
findings: [],
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
let launch: Record<string, string>;
|
|
227
|
+
try {
|
|
228
|
+
launch = rt.launch(probeInput(d, dir)).env;
|
|
229
|
+
} catch (e) {
|
|
230
|
+
if (!(e instanceof LaunchError)) throw e;
|
|
231
|
+
out.push({ target, ran: false, reason: e.message, findings: [] });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
out.push(
|
|
235
|
+
await probeOnce(
|
|
236
|
+
d,
|
|
237
|
+
c,
|
|
238
|
+
target,
|
|
239
|
+
rt.id,
|
|
240
|
+
agentEnv(d.env, rt, launch, PROBE_ID),
|
|
241
|
+
dir,
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
} finally {
|
|
247
|
+
c.close();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** One line per result, one per finding; key names only. */
|
|
252
|
+
export function formatProbe(results: ProbeResult[]): string[] {
|
|
253
|
+
const lines: string[] = [];
|
|
254
|
+
const width = Math.max(...results.map((r) => r.target.length));
|
|
255
|
+
for (const r of results) {
|
|
256
|
+
const t = r.target.padEnd(width);
|
|
257
|
+
if (!r.ran)
|
|
258
|
+
lines.push(` ${t} - not probed: ${r.reason ?? "unavailable"}`);
|
|
259
|
+
else if (!r.findings.length)
|
|
260
|
+
lines.push(` ${t} ✓ clean (${r.keys ?? 0} variables)`);
|
|
261
|
+
else
|
|
262
|
+
for (const f of r.findings)
|
|
263
|
+
lines.push(` ${t} ✗ ${f.key} reaches it (${f.why})`);
|
|
264
|
+
}
|
|
265
|
+
return lines;
|
|
266
|
+
}
|
|
@@ -12,6 +12,7 @@ export type CommandName =
|
|
|
12
12
|
| "services"
|
|
13
13
|
| "graph"
|
|
14
14
|
| "register"
|
|
15
|
+
| "relocate"
|
|
15
16
|
| "use-local"
|
|
16
17
|
| "use-managed"
|
|
17
18
|
| "setup"
|
|
@@ -63,9 +64,10 @@ const WORKSPACE_READ_COMMANDS = new Set([
|
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Commands that edit one workspace's entries without changing what runs:
|
|
66
|
-
* `-w` picks the workspace to write,
|
|
67
|
+
* `-w` picks the workspace to write, whether or not it is active, and leaves
|
|
68
|
+
* focus alone.
|
|
67
69
|
*/
|
|
68
|
-
const WORKSPACE_EDIT_COMMANDS = new Set(["register"]);
|
|
70
|
+
const WORKSPACE_EDIT_COMMANDS = new Set(["register", "relocate"]);
|
|
69
71
|
|
|
70
72
|
const WORKSPACE_COMMANDS = new Set([
|
|
71
73
|
...WORKSPACE_MUTATION_COMMANDS,
|
|
@@ -75,10 +77,18 @@ const WORKSPACE_COMMANDS = new Set([
|
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
79
|
* Commands that read `-w`/`--workspace` themselves, so it is left in their
|
|
78
|
-
* args: `setup --plan -w a -w b` takes several,
|
|
79
|
-
* scopes the MCP server
|
|
80
|
+
* args: `setup --plan -w a -w b` takes several, `mcp --workspace <ws>`
|
|
81
|
+
* scopes the MCP server, and `sessions`/`agents` name a session's
|
|
82
|
+
* workspace. None of them sets the CLI's workspace override.
|
|
80
83
|
*/
|
|
81
|
-
const OWN_WORKSPACE_FLAG_COMMANDS = new Set([
|
|
84
|
+
const OWN_WORKSPACE_FLAG_COMMANDS = new Set([
|
|
85
|
+
"setup",
|
|
86
|
+
"mcp",
|
|
87
|
+
// A session's workspace is part of its name (AG-3a); -w never sets the
|
|
88
|
+
// CLI's workspace override here.
|
|
89
|
+
"sessions",
|
|
90
|
+
"agents",
|
|
91
|
+
]);
|
|
82
92
|
|
|
83
93
|
/** Whether `-w <name>` on this command requires `<name>` to be active. */
|
|
84
94
|
export function workspaceFlagRequiresActive(command: string): boolean {
|
|
@@ -196,6 +206,9 @@ const COMMAND_FLAGS: Record<string, readonly string[]> = {
|
|
|
196
206
|
logs: [],
|
|
197
207
|
setup: ["-f", "--force", "--plan", "-w", "--workspace", "--all-active"],
|
|
198
208
|
sync: [],
|
|
209
|
+
// The only override for an edge another state dir owns; a typo'd one
|
|
210
|
+
// must not be ignored.
|
|
211
|
+
edge: ["--take-over"],
|
|
199
212
|
};
|
|
200
213
|
|
|
201
214
|
/**
|
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import type { ServiceHealthResult } from "../../shared/environment-manager/health.js";
|
|
3
3
|
|
|
4
4
|
import { product } from "../../shared/product.js";
|
|
5
|
+
import type { RepoRollup } from "../../shared/health/rollup.js";
|
|
5
6
|
type HealthState = ServiceHealthResult["health"];
|
|
6
7
|
type HealthReasonInput = Pick<ServiceHealthResult, "health" | "healthDetail">;
|
|
7
8
|
|
|
@@ -62,3 +63,27 @@ export function formatHealthReason(result: HealthReasonInput): string {
|
|
|
62
63
|
const reason = healthReasonText(result);
|
|
63
64
|
return reason ? chalk.gray(reason.slice(0, 48)) : "";
|
|
64
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A repo row's rolled-up state (rollupRepoHealth), the one the web UI's
|
|
69
|
+
* header and graph show: "● down", "● degraded", "● ok", "● unknown".
|
|
70
|
+
*/
|
|
71
|
+
export function formatRollupLabel(r: RepoRollup): string {
|
|
72
|
+
if (r.state === "down") return chalk.red("● down");
|
|
73
|
+
if (r.state === "degraded") return chalk.yellow("● degraded");
|
|
74
|
+
if (r.state === "ok") return chalk.green("● ok");
|
|
75
|
+
return chalk.gray("● unknown");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Why, for a repo row: the down services ("mailpit down"), and how many
|
|
80
|
+
* services aren't checked ("25 not checked"). Unchecked never means down.
|
|
81
|
+
*/
|
|
82
|
+
export function rollupReasonText(r: RepoRollup): string {
|
|
83
|
+
return [
|
|
84
|
+
r.down.length ? `${r.down.join(", ")} down` : "",
|
|
85
|
+
r.unchecked ? `${r.unchecked} not checked` : "",
|
|
86
|
+
]
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join("; ");
|
|
89
|
+
}
|