@stratusagent/cli 0.11.2 → 0.11.3
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/README.md +13 -7
- package/dist/approvals.d.ts +87 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +315 -0
- package/dist/approvals.js.map +1 -0
- package/dist/commands/agent-new.d.ts +22 -0
- package/dist/commands/agent-new.d.ts.map +1 -0
- package/dist/commands/agent-new.js +154 -0
- package/dist/commands/agent-new.js.map +1 -0
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +90 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/chat.d.ts +4 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +224 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/credential.d.ts +22 -0
- package/dist/commands/credential.d.ts.map +1 -0
- package/dist/commands/credential.js +93 -0
- package/dist/commands/credential.js.map +1 -0
- package/dist/commands/dashboard.d.ts +14 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +195 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/doctor.d.ts +40 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +357 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/grants.d.ts +19 -0
- package/dist/commands/grants.d.ts.map +1 -0
- package/dist/commands/grants.js +128 -0
- package/dist/commands/grants.js.map +1 -0
- package/dist/commands/logs.d.ts +9 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +58 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/memory.d.ts +20 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +110 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/plugins.d.ts +91 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +359 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/restart.d.ts +4 -0
- package/dist/commands/restart.d.ts.map +1 -0
- package/dist/commands/restart.js +25 -0
- package/dist/commands/restart.js.map +1 -0
- package/dist/commands/schedules.d.ts +11 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +50 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/serve.d.ts +4 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +612 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/service.d.ts +9 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +97 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/session.d.ts +9 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +25 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +2412 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/skills.d.ts +15 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +272 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/template.d.ts +4 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +575 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/update.d.ts +21 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +291 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/daemon.d.ts +74 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +192 -0
- package/dist/daemon.js.map +1 -0
- package/dist/environment.d.ts +108 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +2 -0
- package/dist/environment.js.map +1 -0
- package/dist/events.d.ts +17 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +87 -0
- package/dist/events.js.map +1 -0
- package/dist/help.d.ts +2 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +266 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +31 -624
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -6981
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +16 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +45 -0
- package/dist/io.js.map +1 -0
- package/dist/loaders.d.ts +31 -0
- package/dist/loaders.d.ts.map +1 -0
- package/dist/loaders.js +56 -0
- package/dist/loaders.js.map +1 -0
- package/dist/main.d.ts +8 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +214 -0
- package/dist/main.js.map +1 -0
- package/dist/npm.d.ts +66 -0
- package/dist/npm.d.ts.map +1 -0
- package/dist/npm.js +201 -0
- package/dist/npm.js.map +1 -0
- package/dist/parse.d.ts +231 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +1082 -0
- package/dist/parse.js.map +1 -0
- package/dist/plugin-catalog.d.ts +35 -0
- package/dist/plugin-catalog.d.ts.map +1 -0
- package/dist/plugin-catalog.js +58 -0
- package/dist/plugin-catalog.js.map +1 -0
- package/dist/preflight.d.ts +60 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +102 -0
- package/dist/preflight.js.map +1 -0
- package/dist/prompter.d.ts +84 -0
- package/dist/prompter.d.ts.map +1 -0
- package/dist/prompter.js +347 -0
- package/dist/prompter.js.map +1 -0
- package/dist/roster.d.ts +57 -0
- package/dist/roster.d.ts.map +1 -0
- package/dist/roster.js +78 -0
- package/dist/roster.js.map +1 -0
- package/dist/runtime.d.ts +59 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +266 -0
- package/dist/runtime.js.map +1 -0
- package/dist/slack.d.ts +26 -0
- package/dist/slack.d.ts.map +1 -0
- package/dist/slack.js +113 -0
- package/dist/slack.js.map +1 -0
- package/dist/source.d.ts +24 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +80 -0
- package/dist/source.js.map +1 -0
- package/dist/supervisor.d.ts +69 -0
- package/dist/supervisor.d.ts.map +1 -0
- package/dist/supervisor.js +218 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/trusted-config.d.ts +71 -0
- package/dist/trusted-config.d.ts.map +1 -0
- package/dist/trusted-config.js +118 -0
- package/dist/trusted-config.js.map +1 -0
- package/package.json +25 -23
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../../src/commands/credential.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CliStreams, CliEnvironment } from '../environment.ts';
|
|
2
|
+
import type { ParsedDashboardCommand } from '../parse.ts';
|
|
3
|
+
export declare const openExternalUrl: (url: string) => Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* `stratus dashboard` — open the web UI against a running daemon, starting
|
|
6
|
+
* one in the foreground when there is none.
|
|
7
|
+
*
|
|
8
|
+
* The daemon is started by calling `runServe`, not by rebuilding its wiring:
|
|
9
|
+
* channels, approvals, the credential preflight, and the log writer are all
|
|
10
|
+
* decisions `serve` already makes, and a second copy of them here would drift
|
|
11
|
+
* the first time either side gained a rule.
|
|
12
|
+
*/
|
|
13
|
+
export declare const runDashboard: (command: ParsedDashboardCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
14
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/commands/dashboard.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAK1D,eAAO,MAAM,eAAe,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,IAAI,CAa/D,CAAC;AAmCF;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,SAAS,sBAAsB,EAC/B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CA0JhB,CAAC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { gatewayToken, readGatewayInfo, gatewayAnswering, HomeHeldError } from "../daemon.js";
|
|
3
|
+
import { writeLine } from "../io.js";
|
|
4
|
+
import { runServe } from "./serve.js";
|
|
5
|
+
const DASHBOARD_TITLE = 'Stratus Agent Dashboard';
|
|
6
|
+
export const openExternalUrl = async (url) => {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
9
|
+
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
10
|
+
await new Promise((resolve, reject) => {
|
|
11
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: platform !== 'win32' });
|
|
12
|
+
child.once('error', reject);
|
|
13
|
+
child.once('spawn', () => {
|
|
14
|
+
child.unref();
|
|
15
|
+
resolve();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A one-time URL for the browser.
|
|
21
|
+
*
|
|
22
|
+
* This is the whole reason the exchange exists: the CLI can read the token
|
|
23
|
+
* file and a page cannot, so the CLI lends its authority for exactly one
|
|
24
|
+
* short-lived trip.
|
|
25
|
+
*/
|
|
26
|
+
const mintDashboardUrl = async (env, base, fetchImpl) => {
|
|
27
|
+
const token = await gatewayToken(env, undefined);
|
|
28
|
+
const response = await fetchImpl(`${base}/api/v1/auth/ott`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { authorization: `Bearer ${token}` },
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`The gateway at ${base} refused to open a dashboard session (HTTP ${response.status}).`);
|
|
34
|
+
}
|
|
35
|
+
const payload = await response.json();
|
|
36
|
+
// The relative form joined to the base we already reached, in preference to
|
|
37
|
+
// the absolute one: this command knows exactly which address answered, and
|
|
38
|
+
// the daemon can only infer it from headers.
|
|
39
|
+
if (payload.path) {
|
|
40
|
+
return `${base.replace(/\/+$/, '')}${payload.path}`;
|
|
41
|
+
}
|
|
42
|
+
if (!payload.url) {
|
|
43
|
+
throw new Error(`The gateway at ${base} did not return a dashboard URL.`);
|
|
44
|
+
}
|
|
45
|
+
return payload.url;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* `stratus dashboard` — open the web UI against a running daemon, starting
|
|
49
|
+
* one in the foreground when there is none.
|
|
50
|
+
*
|
|
51
|
+
* The daemon is started by calling `runServe`, not by rebuilding its wiring:
|
|
52
|
+
* channels, approvals, the credential preflight, and the log writer are all
|
|
53
|
+
* decisions `serve` already makes, and a second copy of them here would drift
|
|
54
|
+
* the first time either side gained a rule.
|
|
55
|
+
*/
|
|
56
|
+
export const runDashboard = async (command, streams, env = {}) => {
|
|
57
|
+
const fetchImpl = env.fetch ?? globalThis.fetch;
|
|
58
|
+
if (typeof fetchImpl !== 'function') {
|
|
59
|
+
writeLine(streams.stderr, 'Error: fetch is unavailable, so the dashboard cannot reach a gateway.');
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
const existing = await readGatewayInfo(env);
|
|
63
|
+
let base = existing && await gatewayAnswering(env, existing.url, fetchImpl) ? existing.url : undefined;
|
|
64
|
+
const ownDaemon = new AbortController();
|
|
65
|
+
let serving;
|
|
66
|
+
if (!base) {
|
|
67
|
+
writeLine(streams.stdout, 'No daemon is running — starting one. It stops when you do.');
|
|
68
|
+
writeLine(streams.stdout, 'Run `stratus service install` to keep one running instead.');
|
|
69
|
+
// One attempt at a daemon of our own. `runServe` can reject before it
|
|
70
|
+
// publishes anything — an unreadable roster, a session store that will
|
|
71
|
+
// not open — and a rejected promise nobody is watching for fifteen
|
|
72
|
+
// seconds is an unhandled rejection, which terminates the process
|
|
73
|
+
// instead of reaching the message below. Captured rather than
|
|
74
|
+
// swallowed, so the reason the daemon gave is the reason this command
|
|
75
|
+
// reports.
|
|
76
|
+
let daemonFailure;
|
|
77
|
+
let daemonSettled = false;
|
|
78
|
+
let daemonExit = Promise.resolve();
|
|
79
|
+
let attemptedAt = 0;
|
|
80
|
+
const attempt = () => {
|
|
81
|
+
daemonFailure = undefined;
|
|
82
|
+
daemonSettled = false;
|
|
83
|
+
attemptedAt = Date.now();
|
|
84
|
+
serving = runServe({
|
|
85
|
+
command: 'serve',
|
|
86
|
+
events: false,
|
|
87
|
+
// Explicitly on. A trusted config may set `api.enabled: false` —
|
|
88
|
+
// a reasonable thing for a headless box — but this command exists
|
|
89
|
+
// to open the dashboard, and honouring it here would start a
|
|
90
|
+
// daemon with no API and then time out waiting for the one it
|
|
91
|
+
// promised.
|
|
92
|
+
api: true,
|
|
93
|
+
...(command.port !== undefined ? { apiPort: command.port } : {}),
|
|
94
|
+
// Passed whenever it was asked for, including when it is the
|
|
95
|
+
// default. `--host 127.0.0.1` against a config saying `0.0.0.0`
|
|
96
|
+
// is an operator narrowing the bind, and dropping it because it
|
|
97
|
+
// matched the default did the reverse of what they typed.
|
|
98
|
+
...(command.host !== undefined ? { apiHost: command.host } : {}),
|
|
99
|
+
},
|
|
100
|
+
// The daemon's own chatter belongs on stderr here: stdout is where
|
|
101
|
+
// this command says where to point a browser, and interleaving the
|
|
102
|
+
// two makes the one line that matters hard to find.
|
|
103
|
+
{ stdout: streams.stderr, stderr: streams.stderr }, { ...env, shutdownSignal: ownDaemon.signal });
|
|
104
|
+
daemonExit = serving.then(() => { daemonSettled = true; }, (error) => { daemonFailure = error; daemonSettled = true; });
|
|
105
|
+
};
|
|
106
|
+
attempt();
|
|
107
|
+
// Gated on a daemon actually answering, not on a delay: one publishes
|
|
108
|
+
// where it bound the moment it binds, and anything less would be a race
|
|
109
|
+
// dressed up as a timeout. It also stops the moment our daemon gives up
|
|
110
|
+
// — except when it gave up because another one holds the home. An
|
|
111
|
+
// installed service still starting has not published its address yet
|
|
112
|
+
// and is about to: that is the daemon to wait for. And a daemon still
|
|
113
|
+
// draining may simply exit, with nothing replacing it — so while the
|
|
114
|
+
// home is held, our own attempt is repeated about once a second, and
|
|
115
|
+
// takes the home the moment it is free.
|
|
116
|
+
const startedBy = Date.now() + 15_000;
|
|
117
|
+
while (!base && Date.now() < startedBy) {
|
|
118
|
+
const info = await readGatewayInfo(env);
|
|
119
|
+
if (info && await gatewayAnswering(env, info.url, fetchImpl)) {
|
|
120
|
+
base = info.url;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
if (daemonSettled) {
|
|
124
|
+
if (!(daemonFailure instanceof HomeHeldError)) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
if (Date.now() - attemptedAt >= 1_000) {
|
|
128
|
+
attempt();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
132
|
+
}
|
|
133
|
+
if (!base) {
|
|
134
|
+
ownDaemon.abort();
|
|
135
|
+
await daemonExit;
|
|
136
|
+
writeLine(streams.stderr, daemonFailure instanceof HomeHeldError
|
|
137
|
+
? 'Error: another daemon holds this home but never published its address. It may still be starting or '
|
|
138
|
+
+ 'draining — try again in a moment, or check `stratus service status`.'
|
|
139
|
+
: daemonFailure
|
|
140
|
+
? `Error: the daemon could not start: ${daemonFailure instanceof Error ? daemonFailure.message : String(daemonFailure)}`
|
|
141
|
+
: 'Error: the daemon did not start serving its control API. Is @stratusagent/control-api installed?');
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
if (daemonFailure instanceof HomeHeldError) {
|
|
145
|
+
// The daemon that answered is the one that held the home. Ours never
|
|
146
|
+
// started, so there is nothing of ours to wait for or to stop: from
|
|
147
|
+
// here this is the found-a-running-daemon case.
|
|
148
|
+
serving = undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let url;
|
|
152
|
+
try {
|
|
153
|
+
url = await mintDashboardUrl(env, base, fetchImpl);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
ownDaemon.abort();
|
|
157
|
+
await serving?.catch(() => 0);
|
|
158
|
+
writeLine(streams.stderr, `Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
writeLine(streams.stdout, `${DASHBOARD_TITLE} ready at ${base}`);
|
|
162
|
+
writeLine(streams.stdout, 'That link signs one browser in and can only be used once.');
|
|
163
|
+
if (command.openBrowser) {
|
|
164
|
+
try {
|
|
165
|
+
await (env.openExternal ?? openExternalUrl)(url);
|
|
166
|
+
writeLine(streams.stdout, 'Opened your default browser.');
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
writeLine(streams.stderr, `Warning: Could not open the browser automatically: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
|
+
writeLine(streams.stdout, `Open this yourself: ${url}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
writeLine(streams.stdout, `Open this to sign in: ${url}`);
|
|
175
|
+
}
|
|
176
|
+
if (!serving) {
|
|
177
|
+
// Someone else owns the daemon; this command's job is done.
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
writeLine(streams.stdout, 'Press Ctrl+C to stop the daemon.');
|
|
181
|
+
if (env.dashboardAutoShutdownMs !== undefined) {
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, env.dashboardAutoShutdownMs));
|
|
183
|
+
ownDaemon.abort();
|
|
184
|
+
}
|
|
185
|
+
// An attempt still in flight when the daemon answered may yet be refused
|
|
186
|
+
// — the holder published while ours was claiming. The dashboard found a
|
|
187
|
+
// daemon; that refusal is not its failure.
|
|
188
|
+
return serving.catch((error) => {
|
|
189
|
+
if (error instanceof HomeHeldError) {
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../../src/commands/dashboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CredentialProviderName } from '@stratusagent/state';
|
|
2
|
+
import type { CliStreams, CliEnvironment } from '../environment.ts';
|
|
3
|
+
import type { ParsedDoctorCommand } from '../parse.ts';
|
|
4
|
+
/** One resolved setting, with the thing that decided it. */
|
|
5
|
+
interface DoctorSetting {
|
|
6
|
+
value: string;
|
|
7
|
+
source: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DoctorReport {
|
|
10
|
+
configInUse?: string;
|
|
11
|
+
/** Config files that exist but lost to `configInUse`, nearest first. */
|
|
12
|
+
configShadowed: string[];
|
|
13
|
+
provider: DoctorSetting;
|
|
14
|
+
model?: DoctorSetting;
|
|
15
|
+
soul?: DoctorSetting;
|
|
16
|
+
agent?: string;
|
|
17
|
+
/** The model a failing run retries on, when one is configured. */
|
|
18
|
+
fallback?: {
|
|
19
|
+
provider: string;
|
|
20
|
+
model: string;
|
|
21
|
+
};
|
|
22
|
+
signIns: Array<{
|
|
23
|
+
provider: CredentialProviderName;
|
|
24
|
+
status: string;
|
|
25
|
+
}>;
|
|
26
|
+
slackAgents: string[];
|
|
27
|
+
slackPackageInstalled: boolean;
|
|
28
|
+
rosterCount: number;
|
|
29
|
+
problems: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `stratus doctor` — what a run would resolve to right now, and which file
|
|
33
|
+
* or variable decided each part. Every "why is it using X" question needs
|
|
34
|
+
* the precedence chain, and reading it out of the source is not something
|
|
35
|
+
* anyone should have to do.
|
|
36
|
+
*/
|
|
37
|
+
export declare const collectDoctorReport: (command: ParsedDoctorCommand, warn: (message: string) => void, env?: CliEnvironment) => Promise<DoctorReport>;
|
|
38
|
+
export declare const runDoctor: (command: ParsedDoctorCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,EAgBL,KAAK,sBAAsB,EAI5B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,4DAA4D;AAC5D,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,OAAO,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,sBAAsB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAC9B,SAAS,mBAAmB,EAC5B,MAAM,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,MAAK,cAAmB,KACvB,OAAO,CAAC,YAAY,CAsTtB,CAAC;AAEF,eAAO,MAAM,SAAS,GACpB,SAAS,mBAAmB,EAC5B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CAqEhB,CAAC"}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { agentsDirPath, defaultApiKeyEnvName, DEFAULT_STRATUS_AGENT, globalConfigPath, loadChannelCredentials, loadCredentials, loadRosterSouls, loadSoulFile, readNonEmptyString, readProcessEnv, readWorkingDirectory, DEFAULT_CONFIG_FILENAME, loadConfigFile, resolveRuntimeConfig as resolveStateRuntimeConfig, CREDENTIAL_PROVIDER_NAMES, } from '@stratusagent/state';
|
|
4
|
+
import { readServiceCommand } from "../service.js";
|
|
5
|
+
import { serviceEnvFor } from "../daemon.js";
|
|
6
|
+
import { writeLine, pathExists } from "../io.js";
|
|
7
|
+
import { loadSlackAdapter } from "../loaders.js";
|
|
8
|
+
/**
|
|
9
|
+
* `stratus doctor` — what a run would resolve to right now, and which file
|
|
10
|
+
* or variable decided each part. Every "why is it using X" question needs
|
|
11
|
+
* the precedence chain, and reading it out of the source is not something
|
|
12
|
+
* anyone should have to do.
|
|
13
|
+
*/
|
|
14
|
+
export const collectDoctorReport = async (command, warn, env = {}) => {
|
|
15
|
+
const processEnv = readProcessEnv(env);
|
|
16
|
+
const cwd = readWorkingDirectory(env);
|
|
17
|
+
const problems = [];
|
|
18
|
+
// Config discovery, spelled out rather than delegated: doctor has to
|
|
19
|
+
// report the files that LOST as well as the one in use, which is the
|
|
20
|
+
// whole point when a stray project config is the answer.
|
|
21
|
+
// Which of the two named it, not just that one did: telling someone to
|
|
22
|
+
// fix STRATUS_CONFIG when --config is what is set leaves the real
|
|
23
|
+
// override in place — the same mistake as provider and key attribution,
|
|
24
|
+
// in the one place a typo is most likely.
|
|
25
|
+
const explicitSource = command.configPath !== undefined
|
|
26
|
+
? { name: '--config', value: command.configPath }
|
|
27
|
+
: readNonEmptyString(processEnv.STRATUS_CONFIG)
|
|
28
|
+
? { name: 'STRATUS_CONFIG', value: String(processEnv.STRATUS_CONFIG) }
|
|
29
|
+
: undefined;
|
|
30
|
+
const explicit = explicitSource?.value;
|
|
31
|
+
const candidates = explicitSource
|
|
32
|
+
? [{ path: path.resolve(cwd, explicitSource.value), label: explicitSource.name }]
|
|
33
|
+
: [
|
|
34
|
+
{ path: path.join(cwd, DEFAULT_CONFIG_FILENAME), label: 'project' },
|
|
35
|
+
{ path: globalConfigPath(env), label: 'global' },
|
|
36
|
+
];
|
|
37
|
+
const present = [];
|
|
38
|
+
let unreadable;
|
|
39
|
+
for (const candidate of candidates) {
|
|
40
|
+
try {
|
|
41
|
+
await readFile(candidate.path, 'utf8');
|
|
42
|
+
present.push(candidate);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const code = error.code;
|
|
46
|
+
if (code !== 'ENOENT') {
|
|
47
|
+
// resolveConfigLocation throws here rather than trying the next
|
|
48
|
+
// candidate, so discovery stops: a lower-priority file that a real
|
|
49
|
+
// run never reaches must not be reported as the config in use.
|
|
50
|
+
unreadable = candidate;
|
|
51
|
+
problems.push(`${candidate.path} exists but cannot be read (${error.message}). Every run fails here — discovery never reaches a lower-priority config.`);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if (explicit) {
|
|
55
|
+
// Discovery treats a missing candidate as "try the next one", but
|
|
56
|
+
// an explicitly named config has no next one: a real run calls
|
|
57
|
+
// loadConfigFile on it and fails. Reporting built-in defaults here
|
|
58
|
+
// would describe a run that cannot happen, and hide the typo.
|
|
59
|
+
problems.push(`${candidate.path} does not exist, but ${candidate.label} names it — every run with this setting fails. `
|
|
60
|
+
+ 'Fix the path, or drop the setting to fall back to config discovery.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const winner = unreadable ?? present[0];
|
|
65
|
+
const shadowed = unreadable ? [] : present.slice(1);
|
|
66
|
+
if (winner && shadowed.length > 0 && winner.label.startsWith('project')) {
|
|
67
|
+
problems.push(`${winner.path} outranks ${shadowed.map((entry) => entry.path).join(' and ')} for runs started in this directory. `
|
|
68
|
+
+ 'Run from elsewhere, or pass --config to pick one explicitly.');
|
|
69
|
+
}
|
|
70
|
+
let fileConfig = {};
|
|
71
|
+
let configFatal = unreadable !== undefined;
|
|
72
|
+
if (winner && !unreadable) {
|
|
73
|
+
try {
|
|
74
|
+
fileConfig = await loadConfigFile(winner.path);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// resolveRuntimeConfig propagates this — no run reaches provider or
|
|
78
|
+
// credential resolution, so presenting defaults as resolved settings
|
|
79
|
+
// would describe a run that cannot happen.
|
|
80
|
+
configFatal = true;
|
|
81
|
+
problems.push(`${winner.path} could not be parsed (${error.message}). Every run fails until it is fixed or removed.`);
|
|
82
|
+
warn(`ignoring unreadable config ${winner.path}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Which variable supplied a value, not just the value: naming the wrong
|
|
86
|
+
// one sends the reader to unset something that was never the cause,
|
|
87
|
+
// leaving the real override in place.
|
|
88
|
+
const envPick = (name) => {
|
|
89
|
+
const value = readNonEmptyString(processEnv[name]);
|
|
90
|
+
return typeof value === 'string' ? { name, value } : undefined;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* The verdict comes from the resolver, not from a second copy of its
|
|
94
|
+
* rules. Everything below only *attributes* values to the file or
|
|
95
|
+
* variable that supplied them — which the resolver does not report —
|
|
96
|
+
* while whether a run works at all, and what it bills, is whatever
|
|
97
|
+
* resolveRuntimeConfig actually returns or throws.
|
|
98
|
+
*/
|
|
99
|
+
const envSoul = envPick('STRATUS_SOUL');
|
|
100
|
+
const soulValue = envSoul?.value ?? fileConfig.soul;
|
|
101
|
+
const soulSource = envSoul ? envSoul.name : (winner ? winner.path : '');
|
|
102
|
+
const soulPath = typeof soulValue === 'string' ? path.resolve(cwd, soulValue) : undefined;
|
|
103
|
+
// Loaded here for ATTRIBUTION only — the soul's frontmatter is what
|
|
104
|
+
// explains a provider nobody wrote in a config file. Whether a run works
|
|
105
|
+
// is still the resolver's verdict below.
|
|
106
|
+
const soul = soulPath ? await loadSoulFile(soulPath).catch(() => undefined) : undefined;
|
|
107
|
+
let resolved;
|
|
108
|
+
if (!configFatal) {
|
|
109
|
+
try {
|
|
110
|
+
resolved = await resolveStateRuntimeConfig(command.configPath ? { configPath: command.configPath } : {}, env);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
+
problems.push(`No run can start: ${message}`);
|
|
115
|
+
if (soulPath && /soul/i.test(message)) {
|
|
116
|
+
// Worth saying explicitly: a soul that fails to load is fatal, not
|
|
117
|
+
// a quiet downgrade to the built-in agent.
|
|
118
|
+
problems.push(`Every run fails until ${soulPath} is fixed or the soul setting is removed — the built-in agent is not substituted.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const envProviderPick = envPick('STRATUS_PROVIDER');
|
|
123
|
+
const providerSource = envProviderPick
|
|
124
|
+
? envProviderPick.name
|
|
125
|
+
: soul?.provider
|
|
126
|
+
? `${soulPath} (soul frontmatter)`
|
|
127
|
+
: fileConfig.provider
|
|
128
|
+
? (winner?.path ?? 'config')
|
|
129
|
+
: 'built-in default — nothing set a provider';
|
|
130
|
+
// When resolution fails the intended provider is still worth reporting:
|
|
131
|
+
// "why is it anthropic" is exactly the question, and the problems below
|
|
132
|
+
// already say no run can start.
|
|
133
|
+
const intendedProvider = envProviderPick?.value ?? soul?.provider ?? fileConfig.provider ?? 'demo';
|
|
134
|
+
const provider = resolved
|
|
135
|
+
? { value: resolved.provider, source: providerSource }
|
|
136
|
+
: { value: `${intendedProvider} (unreachable)`, source: providerSource };
|
|
137
|
+
if (provider.value === 'demo') {
|
|
138
|
+
// Setup writes the config file, which is the LOWEST precedence of the
|
|
139
|
+
// three — so when an env var or a soul chose demo, "run setup" is
|
|
140
|
+
// advice that changes nothing and leaves every run on demo.
|
|
141
|
+
const fix = envProviderPick
|
|
142
|
+
? `Unset ${envProviderPick.name}; it outranks both your config and any soul.`
|
|
143
|
+
: soul?.provider === 'demo'
|
|
144
|
+
? `Change or remove the provider pin in ${soulPath}; a soul outranks the config file.`
|
|
145
|
+
: fileConfig.provider === 'demo'
|
|
146
|
+
? `Change "provider" in ${winner?.path}, or run \`stratus setup\` → Providers.`
|
|
147
|
+
: 'Run `stratus setup` → Providers and sign in, then Save & finish.';
|
|
148
|
+
problems.push(`Provider is the offline demo model, so replies are canned. ${fix}`);
|
|
149
|
+
}
|
|
150
|
+
let model;
|
|
151
|
+
if (resolved && resolved.provider !== 'demo') {
|
|
152
|
+
const envModel = envPick('STRATUS_MODEL');
|
|
153
|
+
// A soul's model belongs to the soul's provider, and the config's model
|
|
154
|
+
// to the config's provider — either can be stranded by an override.
|
|
155
|
+
const soulModelApplies = soul?.provider === undefined || soul.provider === resolved.provider;
|
|
156
|
+
const configModelApplies = (fileConfig.provider ?? 'openai') === resolved.provider;
|
|
157
|
+
model = {
|
|
158
|
+
value: resolved.model,
|
|
159
|
+
source: envModel
|
|
160
|
+
? envModel.name
|
|
161
|
+
: soulModelApplies && soul?.model === resolved.model
|
|
162
|
+
? `${soulPath} (soul frontmatter)`
|
|
163
|
+
: configModelApplies && fileConfig.model === resolved.model
|
|
164
|
+
? (winner?.path ?? 'config')
|
|
165
|
+
: 'built-in default for this provider',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const credentials = await loadCredentials(env);
|
|
169
|
+
const signIns = [];
|
|
170
|
+
const usedKeyEnvVar = resolved && resolved.provider !== 'demo' ? resolved.apiKeyEnvVar : undefined;
|
|
171
|
+
const usesAuthToken = resolved?.provider === 'anthropic' && resolved.authToken !== undefined;
|
|
172
|
+
const fallback = resolved && resolved.provider !== 'demo' ? resolved.fallback : undefined;
|
|
173
|
+
for (const target of CREDENTIAL_PROVIDER_NAMES) {
|
|
174
|
+
const stored = credentials[target];
|
|
175
|
+
const label = stored?.type === 'oauth_token'
|
|
176
|
+
? (target === 'codex' ? 'ChatGPT sign-in (codex login)' : 'Claude subscription (Pro/Max)')
|
|
177
|
+
: 'API key';
|
|
178
|
+
const isDefault = resolved?.provider === target;
|
|
179
|
+
const isFallback = fallback?.provider === target;
|
|
180
|
+
// A provider no resolved run consults is reported, never diagnosed:
|
|
181
|
+
// flagging an override on a credential nothing reads is a false alarm.
|
|
182
|
+
if (!isDefault && !isFallback) {
|
|
183
|
+
signIns.push({
|
|
184
|
+
provider: target,
|
|
185
|
+
status: stored
|
|
186
|
+
? `${label} (unused — ${resolved ? resolved.provider : 'nothing'} serves your runs)`
|
|
187
|
+
: 'not signed in',
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const envVar = isDefault ? usedKeyEnvVar : defaultApiKeyEnvName(target);
|
|
192
|
+
const viaEnv = isDefault
|
|
193
|
+
? usedKeyEnvVar !== undefined
|
|
194
|
+
: fallback?.apiKey !== undefined && fallback.apiKey === readNonEmptyString(processEnv[String(envVar)]);
|
|
195
|
+
const where = isDefault ? 'runs are' : 'fallback runs are';
|
|
196
|
+
if (stored && viaEnv) {
|
|
197
|
+
signIns.push({ provider: target, status: `${label}, overridden by ${envVar} in your environment` });
|
|
198
|
+
// The costly case: an env key silently demotes a subscription to
|
|
199
|
+
// per-token billing, and nothing in a normal run says so.
|
|
200
|
+
problems.push(stored.type === 'oauth_token'
|
|
201
|
+
? `${envVar} in your environment outranks your saved ${target} subscription sign-in, so ${where} billed per token instead of through your plan. Unset it (check your shell profile) to use the subscription.`
|
|
202
|
+
: `${envVar} in your environment outranks your saved ${target} sign-in. Unset it to use the one \`stratus setup\` stored.`);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (viaEnv) {
|
|
206
|
+
signIns.push({ provider: target, status: `using ${envVar} from your environment` });
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (stored) {
|
|
210
|
+
signIns.push({ provider: target, status: isDefault && usesAuthToken ? `${label} — runs go through the Claude Code runtime` : label });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
signIns.push({ provider: target, status: 'not signed in' });
|
|
214
|
+
}
|
|
215
|
+
// A fallback the resolver dropped is the quiet failure: the config still
|
|
216
|
+
// names one, but a failing primary has nothing to retry on.
|
|
217
|
+
if (fileConfig.fallbackModel !== undefined && resolved && resolved.provider !== 'demo' && !fallback) {
|
|
218
|
+
problems.push(`A fallback model (${fileConfig.fallbackModel}) is configured but could not be resolved — usually no sign-in for its provider, or an endpoint its saved key is not sent to. `
|
|
219
|
+
+ 'A failing primary model has nothing to retry on.');
|
|
220
|
+
}
|
|
221
|
+
const channels = await loadChannelCredentials(env);
|
|
222
|
+
const slackAgents = Object.keys(channels.slack ?? {}).sort();
|
|
223
|
+
const slackPackageInstalled = (await loadSlackAdapter()) !== undefined;
|
|
224
|
+
if (slackAgents.length > 0 && !slackPackageInstalled) {
|
|
225
|
+
problems.push(`Slack tokens are stored for ${slackAgents.length} agent(s) but @stratusagent/channel-slack is not installed, so \`stratus serve\` skips them. `
|
|
226
|
+
+ 'Install it with: npm install -g @stratusagent/channel-slack');
|
|
227
|
+
}
|
|
228
|
+
let rosterCount = 0;
|
|
229
|
+
try {
|
|
230
|
+
rosterCount = (await readdir(agentsDirPath(env))).filter((file) => file.endsWith('.md')).length;
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (error.code !== 'ENOENT') {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Tokens keyed to an agent the roster no longer has are loaded and
|
|
238
|
+
// skipped on every start, silently.
|
|
239
|
+
let rosterEntries = [];
|
|
240
|
+
let rosterLoaded = true;
|
|
241
|
+
try {
|
|
242
|
+
rosterEntries = await loadRosterSouls(env, warn);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
// Doctor exists to name problems, so a roster it cannot load is a
|
|
246
|
+
// finding to report — not an exception that replaces the report.
|
|
247
|
+
rosterLoaded = false;
|
|
248
|
+
problems.push(error instanceof Error ? error.message : String(error));
|
|
249
|
+
}
|
|
250
|
+
const rosterIds = new Set(rosterEntries.map((entry) => entry.soul.agent.id));
|
|
251
|
+
rosterIds.add(DEFAULT_STRATUS_AGENT.id);
|
|
252
|
+
if (soul) {
|
|
253
|
+
rosterIds.add(soul.agent.id);
|
|
254
|
+
}
|
|
255
|
+
// Only against a roster that actually loaded. "No agent has this id" is
|
|
256
|
+
// a claim about the roster, and a refused one cannot support it — every
|
|
257
|
+
// stored token would be reported as orphaned, and this advises clearing
|
|
258
|
+
// them. Advice is not deletion, but an operator who follows it loses the
|
|
259
|
+
// same credentials by hand.
|
|
260
|
+
const orphans = rosterLoaded ? slackAgents.filter((id) => !rosterIds.has(id)) : [];
|
|
261
|
+
if (orphans.length > 0) {
|
|
262
|
+
problems.push(`Slack tokens are stored for ${orphans.join(', ')}, which no agent matches — \`stratus serve\` skips them. `
|
|
263
|
+
+ 'Clear them from `stratus setup` → Channels.');
|
|
264
|
+
}
|
|
265
|
+
// The invisible failure doctor exists to surface: the service unit embeds
|
|
266
|
+
// absolute node and entrypoint paths, and upgrading node (an nvm version
|
|
267
|
+
// directory, say) leaves the unit pointing at an interpreter that no
|
|
268
|
+
// longer exists. The service stops working and nothing else says so.
|
|
269
|
+
const unitCommand = await readServiceCommand(serviceEnvFor(env));
|
|
270
|
+
if (unitCommand?.execPath !== undefined && !(await pathExists(unitCommand.execPath))) {
|
|
271
|
+
problems.push(`The service unit points at a node interpreter that no longer exists (${unitCommand.execPath}), so stratusd cannot start. `
|
|
272
|
+
+ 'Run `stratus update` (or `stratus service install`) to rewrite it with current paths.');
|
|
273
|
+
}
|
|
274
|
+
if (unitCommand?.scriptPath !== undefined && !(await pathExists(unitCommand.scriptPath))) {
|
|
275
|
+
problems.push(`The service unit points at a CLI entrypoint that no longer exists (${unitCommand.scriptPath}), so stratusd cannot start. `
|
|
276
|
+
+ 'Run `stratus update` (or `stratus service install`) to rewrite it with current paths.');
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
...(winner ? { configInUse: winner.path } : {}),
|
|
280
|
+
configShadowed: shadowed.map((entry) => entry.path),
|
|
281
|
+
provider,
|
|
282
|
+
...(model ? { model } : {}),
|
|
283
|
+
...(soulPath ? { soul: { value: soulPath, source: soulSource } } : {}),
|
|
284
|
+
...(soul ? { agent: `${soul.agent.name} (${soul.agent.id})` } : {}),
|
|
285
|
+
...(fallback ? { fallback: { provider: fallback.provider, model: fallback.model } } : {}),
|
|
286
|
+
signIns,
|
|
287
|
+
slackAgents,
|
|
288
|
+
slackPackageInstalled,
|
|
289
|
+
rosterCount,
|
|
290
|
+
problems,
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
export const runDoctor = async (command, streams, env = {}) => {
|
|
294
|
+
const report = await collectDoctorReport(command, (message) => {
|
|
295
|
+
writeLine(streams.stderr, `Warning: ${message}.`);
|
|
296
|
+
}, env);
|
|
297
|
+
if (command.format === 'json') {
|
|
298
|
+
writeLine(streams.stdout, JSON.stringify(report, null, 2));
|
|
299
|
+
return report.problems.length > 0 ? 1 : 0;
|
|
300
|
+
}
|
|
301
|
+
const field = (label, setting) => {
|
|
302
|
+
if (!setting) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
writeLine(streams.stdout, ` ${label.padEnd(10)}${setting.value}`);
|
|
306
|
+
writeLine(streams.stdout, ` ${''.padEnd(10)}from ${setting.source}`);
|
|
307
|
+
};
|
|
308
|
+
writeLine(streams.stdout, 'Stratus Agent — what a run would use right now');
|
|
309
|
+
writeLine(streams.stdout);
|
|
310
|
+
field('provider', report.provider);
|
|
311
|
+
field('model', report.model);
|
|
312
|
+
field('soul', report.soul);
|
|
313
|
+
if (report.agent) {
|
|
314
|
+
writeLine(streams.stdout, ` ${'agent'.padEnd(10)}${report.agent}`);
|
|
315
|
+
}
|
|
316
|
+
else if (report.soul) {
|
|
317
|
+
// A soul is configured but did not load — saying "built-in" here would
|
|
318
|
+
// describe a run that cannot happen.
|
|
319
|
+
writeLine(streams.stdout, ` ${'agent'.padEnd(10)}unresolved — the configured soul could not be read`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
writeLine(streams.stdout, ` ${'agent'.padEnd(10)}${DEFAULT_STRATUS_AGENT.name} (built-in — no soul configured)`);
|
|
323
|
+
}
|
|
324
|
+
writeLine(streams.stdout);
|
|
325
|
+
writeLine(streams.stdout, 'Files');
|
|
326
|
+
writeLine(streams.stdout, ` config ${report.configInUse ?? 'none found — built-in defaults apply'}`);
|
|
327
|
+
for (const shadowedPath of report.configShadowed) {
|
|
328
|
+
writeLine(streams.stdout, ` (${shadowedPath} exists but is outranked)`);
|
|
329
|
+
}
|
|
330
|
+
writeLine(streams.stdout, ` agents ${report.rosterCount} soul file${report.rosterCount === 1 ? '' : 's'}`);
|
|
331
|
+
if (report.fallback) {
|
|
332
|
+
writeLine(streams.stdout, ` ${'fallback'.padEnd(10)}${report.fallback.provider} · ${report.fallback.model}`);
|
|
333
|
+
writeLine(streams.stdout, ` ${''.padEnd(10)}used when the default model errors mid-run`);
|
|
334
|
+
}
|
|
335
|
+
writeLine(streams.stdout);
|
|
336
|
+
writeLine(streams.stdout, 'Sign-ins');
|
|
337
|
+
for (const entry of report.signIns) {
|
|
338
|
+
writeLine(streams.stdout, ` ${entry.provider.padEnd(10)}${entry.status}`);
|
|
339
|
+
}
|
|
340
|
+
writeLine(streams.stdout);
|
|
341
|
+
writeLine(streams.stdout, 'Channels');
|
|
342
|
+
writeLine(streams.stdout, ` slack ${report.slackAgents.length === 0
|
|
343
|
+
? 'no agents connected'
|
|
344
|
+
: `${report.slackAgents.length} connected (${report.slackAgents.join(', ')})`}`);
|
|
345
|
+
writeLine(streams.stdout, ` @stratusagent/channel-slack ${report.slackPackageInstalled ? 'installed' : 'not installed'}`);
|
|
346
|
+
writeLine(streams.stdout);
|
|
347
|
+
if (report.problems.length === 0) {
|
|
348
|
+
writeLine(streams.stdout, 'No problems found.');
|
|
349
|
+
return 0;
|
|
350
|
+
}
|
|
351
|
+
writeLine(streams.stdout, `${report.problems.length} problem${report.problems.length === 1 ? '' : 's'} found:`);
|
|
352
|
+
for (const problem of report.problems) {
|
|
353
|
+
writeLine(streams.stdout, ` ! ${problem}`);
|
|
354
|
+
}
|
|
355
|
+
return 1;
|
|
356
|
+
};
|
|
357
|
+
//# sourceMappingURL=doctor.js.map
|