@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.
Files changed (158) hide show
  1. package/README.md +13 -7
  2. package/dist/approvals.d.ts +87 -0
  3. package/dist/approvals.d.ts.map +1 -0
  4. package/dist/approvals.js +315 -0
  5. package/dist/approvals.js.map +1 -0
  6. package/dist/commands/agent-new.d.ts +22 -0
  7. package/dist/commands/agent-new.d.ts.map +1 -0
  8. package/dist/commands/agent-new.js +154 -0
  9. package/dist/commands/agent-new.js.map +1 -0
  10. package/dist/commands/agents.d.ts +4 -0
  11. package/dist/commands/agents.d.ts.map +1 -0
  12. package/dist/commands/agents.js +90 -0
  13. package/dist/commands/agents.js.map +1 -0
  14. package/dist/commands/chat.d.ts +4 -0
  15. package/dist/commands/chat.d.ts.map +1 -0
  16. package/dist/commands/chat.js +224 -0
  17. package/dist/commands/chat.js.map +1 -0
  18. package/dist/commands/credential.d.ts +22 -0
  19. package/dist/commands/credential.d.ts.map +1 -0
  20. package/dist/commands/credential.js +93 -0
  21. package/dist/commands/credential.js.map +1 -0
  22. package/dist/commands/dashboard.d.ts +14 -0
  23. package/dist/commands/dashboard.d.ts.map +1 -0
  24. package/dist/commands/dashboard.js +195 -0
  25. package/dist/commands/dashboard.js.map +1 -0
  26. package/dist/commands/doctor.d.ts +40 -0
  27. package/dist/commands/doctor.d.ts.map +1 -0
  28. package/dist/commands/doctor.js +357 -0
  29. package/dist/commands/doctor.js.map +1 -0
  30. package/dist/commands/grants.d.ts +19 -0
  31. package/dist/commands/grants.d.ts.map +1 -0
  32. package/dist/commands/grants.js +128 -0
  33. package/dist/commands/grants.js.map +1 -0
  34. package/dist/commands/logs.d.ts +9 -0
  35. package/dist/commands/logs.d.ts.map +1 -0
  36. package/dist/commands/logs.js +58 -0
  37. package/dist/commands/logs.js.map +1 -0
  38. package/dist/commands/memory.d.ts +20 -0
  39. package/dist/commands/memory.d.ts.map +1 -0
  40. package/dist/commands/memory.js +110 -0
  41. package/dist/commands/memory.js.map +1 -0
  42. package/dist/commands/plugins.d.ts +91 -0
  43. package/dist/commands/plugins.d.ts.map +1 -0
  44. package/dist/commands/plugins.js +359 -0
  45. package/dist/commands/plugins.js.map +1 -0
  46. package/dist/commands/restart.d.ts +4 -0
  47. package/dist/commands/restart.d.ts.map +1 -0
  48. package/dist/commands/restart.js +25 -0
  49. package/dist/commands/restart.js.map +1 -0
  50. package/dist/commands/schedules.d.ts +11 -0
  51. package/dist/commands/schedules.d.ts.map +1 -0
  52. package/dist/commands/schedules.js +50 -0
  53. package/dist/commands/schedules.js.map +1 -0
  54. package/dist/commands/serve.d.ts +4 -0
  55. package/dist/commands/serve.d.ts.map +1 -0
  56. package/dist/commands/serve.js +612 -0
  57. package/dist/commands/serve.js.map +1 -0
  58. package/dist/commands/service.d.ts +9 -0
  59. package/dist/commands/service.d.ts.map +1 -0
  60. package/dist/commands/service.js +97 -0
  61. package/dist/commands/service.js.map +1 -0
  62. package/dist/commands/session.d.ts +9 -0
  63. package/dist/commands/session.d.ts.map +1 -0
  64. package/dist/commands/session.js +25 -0
  65. package/dist/commands/session.js.map +1 -0
  66. package/dist/commands/setup.d.ts +5 -0
  67. package/dist/commands/setup.d.ts.map +1 -0
  68. package/dist/commands/setup.js +2412 -0
  69. package/dist/commands/setup.js.map +1 -0
  70. package/dist/commands/skills.d.ts +15 -0
  71. package/dist/commands/skills.d.ts.map +1 -0
  72. package/dist/commands/skills.js +272 -0
  73. package/dist/commands/skills.js.map +1 -0
  74. package/dist/commands/template.d.ts +4 -0
  75. package/dist/commands/template.d.ts.map +1 -0
  76. package/dist/commands/template.js +575 -0
  77. package/dist/commands/template.js.map +1 -0
  78. package/dist/commands/update.d.ts +21 -0
  79. package/dist/commands/update.d.ts.map +1 -0
  80. package/dist/commands/update.js +291 -0
  81. package/dist/commands/update.js.map +1 -0
  82. package/dist/daemon.d.ts +74 -0
  83. package/dist/daemon.d.ts.map +1 -0
  84. package/dist/daemon.js +192 -0
  85. package/dist/daemon.js.map +1 -0
  86. package/dist/environment.d.ts +108 -0
  87. package/dist/environment.d.ts.map +1 -0
  88. package/dist/environment.js +2 -0
  89. package/dist/environment.js.map +1 -0
  90. package/dist/events.d.ts +17 -0
  91. package/dist/events.d.ts.map +1 -0
  92. package/dist/events.js +87 -0
  93. package/dist/events.js.map +1 -0
  94. package/dist/help.d.ts +2 -0
  95. package/dist/help.d.ts.map +1 -0
  96. package/dist/help.js +266 -0
  97. package/dist/help.js.map +1 -0
  98. package/dist/index.d.ts +31 -624
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +38 -6981
  101. package/dist/index.js.map +1 -1
  102. package/dist/io.d.ts +16 -0
  103. package/dist/io.d.ts.map +1 -0
  104. package/dist/io.js +45 -0
  105. package/dist/io.js.map +1 -0
  106. package/dist/loaders.d.ts +31 -0
  107. package/dist/loaders.d.ts.map +1 -0
  108. package/dist/loaders.js +56 -0
  109. package/dist/loaders.js.map +1 -0
  110. package/dist/main.d.ts +8 -0
  111. package/dist/main.d.ts.map +1 -0
  112. package/dist/main.js +214 -0
  113. package/dist/main.js.map +1 -0
  114. package/dist/npm.d.ts +66 -0
  115. package/dist/npm.d.ts.map +1 -0
  116. package/dist/npm.js +201 -0
  117. package/dist/npm.js.map +1 -0
  118. package/dist/parse.d.ts +231 -0
  119. package/dist/parse.d.ts.map +1 -0
  120. package/dist/parse.js +1082 -0
  121. package/dist/parse.js.map +1 -0
  122. package/dist/plugin-catalog.d.ts +35 -0
  123. package/dist/plugin-catalog.d.ts.map +1 -0
  124. package/dist/plugin-catalog.js +58 -0
  125. package/dist/plugin-catalog.js.map +1 -0
  126. package/dist/preflight.d.ts +60 -0
  127. package/dist/preflight.d.ts.map +1 -0
  128. package/dist/preflight.js +102 -0
  129. package/dist/preflight.js.map +1 -0
  130. package/dist/prompter.d.ts +84 -0
  131. package/dist/prompter.d.ts.map +1 -0
  132. package/dist/prompter.js +347 -0
  133. package/dist/prompter.js.map +1 -0
  134. package/dist/roster.d.ts +57 -0
  135. package/dist/roster.d.ts.map +1 -0
  136. package/dist/roster.js +78 -0
  137. package/dist/roster.js.map +1 -0
  138. package/dist/runtime.d.ts +59 -0
  139. package/dist/runtime.d.ts.map +1 -0
  140. package/dist/runtime.js +266 -0
  141. package/dist/runtime.js.map +1 -0
  142. package/dist/slack.d.ts +26 -0
  143. package/dist/slack.d.ts.map +1 -0
  144. package/dist/slack.js +113 -0
  145. package/dist/slack.js.map +1 -0
  146. package/dist/source.d.ts +24 -0
  147. package/dist/source.d.ts.map +1 -0
  148. package/dist/source.js +80 -0
  149. package/dist/source.js.map +1 -0
  150. package/dist/supervisor.d.ts +69 -0
  151. package/dist/supervisor.d.ts.map +1 -0
  152. package/dist/supervisor.js +218 -0
  153. package/dist/supervisor.js.map +1 -0
  154. package/dist/trusted-config.d.ts +71 -0
  155. package/dist/trusted-config.d.ts.map +1 -0
  156. package/dist/trusted-config.js +118 -0
  157. package/dist/trusted-config.js.map +1 -0
  158. 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