@stratusagent/cli 0.13.5 → 0.13.7
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 +4 -2
- package/dist/commands/agent-new.d.ts.map +1 -1
- package/dist/commands/agent-new.js +3 -11
- package/dist/commands/agent-new.js.map +1 -1
- package/dist/commands/channel.d.ts.map +1 -1
- package/dist/commands/channel.js +5 -0
- package/dist/commands/channel.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +157 -7
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +81 -14
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/plugins.d.ts.map +1 -1
- package/dist/commands/plugins.js +2 -2
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/serve.d.ts +8 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +12 -5
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/service.d.ts.map +1 -1
- package/dist/commands/service.js +8 -2
- package/dist/commands/service.js.map +1 -1
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.d.ts.map +1 -0
- package/dist/commands/sessions.js +68 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +136 -127
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/signin.d.ts.map +1 -1
- package/dist/commands/signin.js +32 -3
- package/dist/commands/signin.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +92 -25
- package/dist/commands/update.js.map +1 -1
- package/dist/companions.d.ts.map +1 -1
- package/dist/companions.js +2 -2
- package/dist/companions.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +1 -0
- package/dist/daemon.js.map +1 -1
- package/dist/environment.d.ts +7 -0
- package/dist/environment.d.ts.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +22 -10
- package/dist/help.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +18 -7
- package/dist/main.js.map +1 -1
- package/dist/npm.d.ts +17 -1
- package/dist/npm.d.ts.map +1 -1
- package/dist/npm.js +21 -6
- package/dist/npm.js.map +1 -1
- package/dist/parse.d.ts +18 -2
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +84 -2
- package/dist/parse.js.map +1 -1
- package/dist/plugin-catalog.d.ts +0 -9
- package/dist/plugin-catalog.d.ts.map +1 -1
- package/dist/plugin-catalog.js +3 -14
- package/dist/plugin-catalog.js.map +1 -1
- package/dist/plugin-index.d.ts +136 -0
- package/dist/plugin-index.d.ts.map +1 -0
- package/dist/plugin-index.js +293 -0
- package/dist/plugin-index.js.map +1 -0
- package/dist/plugin-updates.d.ts +54 -0
- package/dist/plugin-updates.d.ts.map +1 -0
- package/dist/plugin-updates.js +68 -0
- package/dist/plugin-updates.js.map +1 -0
- package/dist/promotion.d.ts.map +1 -1
- package/dist/promotion.js +4 -3
- package/dist/promotion.js.map +1 -1
- package/dist/provider-packages.d.ts +34 -0
- package/dist/provider-packages.d.ts.map +1 -0
- package/dist/{harness-packages.js → provider-packages.js} +19 -16
- package/dist/provider-packages.js.map +1 -0
- package/dist/roster.d.ts.map +1 -1
- package/dist/roster.js +19 -2
- package/dist/roster.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +5 -0
- package/dist/runtime.js.map +1 -1
- package/dist/service.d.ts +11 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +129 -37
- package/dist/service.js.map +1 -1
- package/dist/slack.d.ts.map +1 -1
- package/dist/slack.js +4 -0
- package/dist/slack.js.map +1 -1
- package/package.json +22 -25
- package/dist/harness-packages.d.ts +0 -31
- package/dist/harness-packages.d.ts.map +0 -1
- package/dist/harness-packages.js.map +0 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { escapeControlCharacters } from '@stratusagent/core';
|
|
2
|
+
import { callRunningGateway, gatewayErrorMessage, noRunningDaemonMessage, runningGatewayBase, } from "../daemon.js";
|
|
3
|
+
import { writeLine } from "../io.js";
|
|
4
|
+
/** How many sessions a listing shows when no --limit is given. */
|
|
5
|
+
export const DEFAULT_SESSIONS_LIMIT = 20;
|
|
6
|
+
/**
|
|
7
|
+
* `stratus sessions` — what the running daemon's sessions are doing, newest
|
|
8
|
+
* first: running, waiting on an approval, failed, or done.
|
|
9
|
+
*
|
|
10
|
+
* A thin read of `GET /api/v1/sessions`, which filters by agent and cuts
|
|
11
|
+
* to a limit but knows nothing of status. So a status filter reads every
|
|
12
|
+
* row and filters here: cutting first would answer "no failed sessions"
|
|
13
|
+
* whenever the newest few happened to be running. Without one, the route
|
|
14
|
+
* does the cutting, asked for one row more than is shown so the listing
|
|
15
|
+
* can say there are more.
|
|
16
|
+
*/
|
|
17
|
+
export const runSessions = async (command, streams, env = {}) => {
|
|
18
|
+
const base = await runningGatewayBase(env, command);
|
|
19
|
+
if (!base) {
|
|
20
|
+
writeLine(streams.stderr, `Error: ${noRunningDaemonMessage(env)}`);
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
const limit = command.limit ?? DEFAULT_SESSIONS_LIMIT;
|
|
24
|
+
const query = new URLSearchParams();
|
|
25
|
+
if (command.agentId !== undefined) {
|
|
26
|
+
query.set('agent', command.agentId);
|
|
27
|
+
}
|
|
28
|
+
if (command.status === undefined) {
|
|
29
|
+
query.set('limit', String(limit + 1));
|
|
30
|
+
}
|
|
31
|
+
const search = query.toString();
|
|
32
|
+
let rows;
|
|
33
|
+
try {
|
|
34
|
+
const response = await callRunningGateway(env, command, base, `/api/v1/sessions${search ? `?${search}` : ''}`, undefined, 'GET');
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
writeLine(streams.stderr, `Error: ${await gatewayErrorMessage(response)}`);
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
rows = (await response.json()).sessions ?? [];
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
writeLine(streams.stderr, `Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const matching = command.status === undefined ? rows : rows.filter((row) => row.status === command.status);
|
|
46
|
+
const shown = matching.slice(0, limit);
|
|
47
|
+
const more = matching.length > limit;
|
|
48
|
+
if (command.format === 'json') {
|
|
49
|
+
writeLine(streams.stdout, JSON.stringify({ sessions: shown, more }));
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
if (shown.length === 0) {
|
|
53
|
+
writeLine(streams.stdout, command.status === undefined ? 'No sessions.' : `No ${command.status} sessions.`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
// Escaped like `stratus memory list`: an id is whatever a channel or an
|
|
57
|
+
// API caller named the session, and this goes to a terminal.
|
|
58
|
+
const lines = shown.map((row) => [row.status, row.agentId, row.updatedAt ?? '-', row.id].map(escapeControlCharacters));
|
|
59
|
+
const widths = [0, 1, 2].map((column) => Math.max(...lines.map((cells) => cells[column]?.length ?? 0)));
|
|
60
|
+
for (const cells of lines) {
|
|
61
|
+
writeLine(streams.stdout, cells.map((cell, column) => (column < 3 ? cell.padEnd(widths[column] ?? 0) : cell)).join(' '));
|
|
62
|
+
}
|
|
63
|
+
if (more) {
|
|
64
|
+
writeLine(streams.stdout, `Showing the newest ${limit}. --limit <n> shows more.`);
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAsEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,mBAAmB,CAAC;AAInF,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAavE,eAAO,MAAM,oBAAoB,QAKrB,CAAC;AAkFb,eAAO,MAAM,QAAQ,GACnB,SAAS,kBAAkB,EAC3B,SAAS,UAAU,EACnB,MAAK,cAAmB,KACvB,OAAO,CAAC,MAAM,CAo+FhB,CAAC"}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { providerPackagesMissingMessage, installProviderPackages, missingProviderPackages } from "../provider-packages.js";
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { preflightPlugin, readPluginManifest, PluginConfigError, PluginManifestError, } from '@stratusagent/plugins';
|
|
5
|
-
import { DEFAULT_ANTHROPIC_MODEL } from '@stratusagent/provider-anthropic';
|
|
6
|
-
import { DEFAULT_CODEX_MODEL } from '@stratusagent/providers';
|
|
7
5
|
import { formatSoul, parseSoul } from '@stratusagent/agents';
|
|
8
|
-
import { claimSoulFile, collectAvailableModels as collectModels, credentialsPath, defaultApiKeyEnvName, DEFAULT_ANTHROPIC_BASE_URL, DEFAULT_OPENAI_BASE_URL,
|
|
6
|
+
import { claimSoulFile, collectAvailableModels as collectModels, credentialsPath, defaultApiKeyEnvName, DEFAULT_ANTHROPIC_BASE_URL, DEFAULT_OPENAI_BASE_URL, defaultModelFor as builtInModelDefault, DEFAULT_STRATUS_AGENT, globalConfigPath, loadChannelCredentials, loadChannelTransportSecrets, loadCredentials, loadRosterSouls, loadSoulFile, isRegisteredProviderName, languageFor, readNonEmptyString, registeredProviderNameOf, readProcessEnv, readWorkingDirectory, resolveAgentApprovals, resolveRuntimeConfig, DEFAULT_CONFIG_FILENAME, loadConfigFile, saveChannelCredentials, saveChannelTransportSecrets, removeChannelTransportSecrets, CREDENTIAL_NAME_PATTERN, saveConfigFile, saveCredentials, resolveAgentPrincipals, CREDENTIAL_PROVIDER_NAMES, verifyProviderKey, } from '@stratusagent/state';
|
|
9
7
|
import { installService, readServiceStatus, servicePlatform, serviceUnitPath, uninstallService, } from "../service.js";
|
|
10
8
|
import { classifyApprovalReach, unattendedReachParts } from "../approvals.js";
|
|
11
9
|
import { serviceEnvFor } from "../daemon.js";
|
|
12
10
|
import { writeLine } from "../io.js";
|
|
13
11
|
import { packageInstalled } from "../loaders.js";
|
|
14
12
|
import { isPackageName, isInstallableSpecifier, defaultPackageInstaller } from "../npm.js";
|
|
15
|
-
import {
|
|
13
|
+
import { FIRST_PARTY_CAPABILITY_PACKAGES } from "../plugin-catalog.js";
|
|
14
|
+
import { checkPluginReleases, currentPlatformHost, installSpecifier, readPluginIndex, releaseVerdict } from "../plugin-index.js";
|
|
16
15
|
import { menuPrefixWidth, quoteShellArg, stratusHeaderLines, createSetupPrompter, } from "../prompter.js";
|
|
17
16
|
import { soulGrantsTool } from "../roster.js";
|
|
18
17
|
import { runSingleLoop, printSessionSummary, formatRuntimeBanner } from "../runtime.js";
|
|
@@ -145,11 +144,9 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
145
144
|
header: stratusHeaderLines,
|
|
146
145
|
consumeNotices: () => recentNotices.splice(0),
|
|
147
146
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
? DEFAULT_CODEX_MODEL
|
|
152
|
-
: DEFAULT_ANTHROPIC_MODEL;
|
|
147
|
+
// The demo model has none of its own; setup names Anthropic's for it,
|
|
148
|
+
// as it always has.
|
|
149
|
+
const builtInDefaultModel = (provider) => builtInModelDefault(provider === 'demo' ? 'anthropic' : provider);
|
|
153
150
|
// A contributed provider's default is its own, so setup has none to
|
|
154
151
|
// name for it — and none to write into a config or a soul.
|
|
155
152
|
const defaultModelFor = (provider) => isRegisteredProviderName(provider) ? undefined : builtInDefaultModel(provider);
|
|
@@ -1524,11 +1521,16 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
1524
1521
|
: 'installed, not enabled';
|
|
1525
1522
|
return fitMenuRow(short.padEnd(18) + status, reserved);
|
|
1526
1523
|
});
|
|
1527
|
-
options.
|
|
1528
|
-
|
|
1524
|
+
const more = options.length;
|
|
1525
|
+
options.push('More plugins… (checks npm)', 'Back');
|
|
1526
|
+
const choice = await prompter.select('Plugins — what your agents can do (installing one grants nothing on its own)', options);
|
|
1529
1527
|
if (choice.kind !== 'index' || choice.index === options.length - 1) {
|
|
1530
1528
|
return;
|
|
1531
1529
|
}
|
|
1530
|
+
if (choice.index === more) {
|
|
1531
|
+
await chooseMorePlugins();
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1532
1534
|
const name = packages[choice.index];
|
|
1533
1535
|
if (!name) {
|
|
1534
1536
|
return;
|
|
@@ -1536,7 +1538,70 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
1536
1538
|
await choosePlugin(name);
|
|
1537
1539
|
}
|
|
1538
1540
|
};
|
|
1539
|
-
|
|
1541
|
+
/**
|
|
1542
|
+
* The plugins that release apart from this CLI, from the curated index
|
|
1543
|
+
* (`plugin-index.ts`). Asked for, never fetched on its own: it runs npm,
|
|
1544
|
+
* which reaches the registry, and the main menu has to work offline.
|
|
1545
|
+
*
|
|
1546
|
+
* An entry is offered only when its latest release would install and
|
|
1547
|
+
* load here (`releaseVerdict` says what that takes), and the install is
|
|
1548
|
+
* pinned to that release. The rest are named with the reason, so a plugin
|
|
1549
|
+
* missing from the list is not a mystery. Picking one goes to the same
|
|
1550
|
+
* install-then-enable path as every other row, which still asks first and
|
|
1551
|
+
* still reads what it contributes from its own manifest.
|
|
1552
|
+
*/
|
|
1553
|
+
const chooseMorePlugins = async () => {
|
|
1554
|
+
writeLine(streams.stdout);
|
|
1555
|
+
writeLine(streams.stdout, 'Checking npm for plugins…');
|
|
1556
|
+
const index = await readPluginIndex(env);
|
|
1557
|
+
if (index.source === 'bundled') {
|
|
1558
|
+
writeLine(streams.stdout, 'npm has no plugin index this CLI can read, so this is the list it shipped with.');
|
|
1559
|
+
}
|
|
1560
|
+
const listed = new Set(pluginPackages());
|
|
1561
|
+
const candidates = index.entries.filter((entry) => !listed.has(entry.name));
|
|
1562
|
+
const releases = await checkPluginReleases(candidates.map((entry) => entry.name), env);
|
|
1563
|
+
const host = currentPlatformHost();
|
|
1564
|
+
const offered = [];
|
|
1565
|
+
const skipped = [];
|
|
1566
|
+
for (const entry of candidates) {
|
|
1567
|
+
const verdict = releaseVerdict(entry.name, releases.get(entry.name) ?? { status: 'unknown' }, host);
|
|
1568
|
+
if (verdict.offer) {
|
|
1569
|
+
offered.push({ ...entry, version: verdict.version });
|
|
1570
|
+
}
|
|
1571
|
+
else {
|
|
1572
|
+
// At least one space after a name longer than the column: a
|
|
1573
|
+
// third-party name has no @stratusagent/ to shorten.
|
|
1574
|
+
skipped.push(` ${entry.name.replace('@stratusagent/', '').padEnd(17)} ${verdict.reason}`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
if (skipped.length > 0) {
|
|
1578
|
+
writeLine(streams.stdout, 'Not offered here:');
|
|
1579
|
+
for (const line of skipped) {
|
|
1580
|
+
writeLine(streams.stdout, line);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (offered.length === 0) {
|
|
1584
|
+
writeLine(streams.stdout, 'There are no more plugins to offer here right now.');
|
|
1585
|
+
await prompter.ask('Press Enter to return to the menu… ');
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
const reserved = menuPrefixWidth(offered.length + 1);
|
|
1589
|
+
const options = offered.map((entry) => fitMenuRow(`${entry.name.replace('@stratusagent/', '').padEnd(17)} ${entry.description}`, reserved));
|
|
1590
|
+
options.push('Back');
|
|
1591
|
+
const choice = await prompter.select('More plugins — installing one grants nothing on its own', options);
|
|
1592
|
+
if (choice.kind !== 'index' || choice.index === options.length - 1) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const entry = offered[choice.index];
|
|
1596
|
+
if (entry) {
|
|
1597
|
+
await choosePlugin(entry.name, installSpecifier(entry.name, entry.version));
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
/**
|
|
1601
|
+
* `install` is what `npm install -g` is handed, when it isn't the bare
|
|
1602
|
+
* name: More plugins pins the version it checked.
|
|
1603
|
+
*/
|
|
1604
|
+
const choosePlugin = async (name, install = name) => {
|
|
1540
1605
|
const setup = PLUGIN_SETUP[name];
|
|
1541
1606
|
const installed = packageInstalled(name, env);
|
|
1542
1607
|
const enabled = pluginEnabled(name);
|
|
@@ -1605,15 +1670,15 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
1605
1670
|
disablePlugin(name);
|
|
1606
1671
|
return;
|
|
1607
1672
|
}
|
|
1608
|
-
writeLine(streams.stdout, `Running: npm install -g ${
|
|
1609
|
-
const result = await (env.packageInstaller ?? defaultPackageInstaller)([
|
|
1673
|
+
writeLine(streams.stdout, `Running: npm install -g ${install}`);
|
|
1674
|
+
const result = await (env.packageInstaller ?? defaultPackageInstaller)([install])
|
|
1610
1675
|
.catch((error) => ({
|
|
1611
1676
|
ok: false,
|
|
1612
1677
|
message: error instanceof Error ? error.message : String(error),
|
|
1613
1678
|
}));
|
|
1614
1679
|
if (!result.ok) {
|
|
1615
1680
|
writeLine(streams.stderr, `Could not install: ${result.message}`);
|
|
1616
|
-
writeLine(streams.stderr, `Run \`npm install -g ${
|
|
1681
|
+
writeLine(streams.stderr, `Run \`npm install -g ${install}\` yourself, then re-run setup to enable it.`);
|
|
1617
1682
|
return;
|
|
1618
1683
|
}
|
|
1619
1684
|
writeLine(streams.stdout, `Installed ${name}.`);
|
|
@@ -2359,116 +2424,53 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2359
2424
|
};
|
|
2360
2425
|
};
|
|
2361
2426
|
/**
|
|
2362
|
-
*
|
|
2363
|
-
* it
|
|
2364
|
-
*
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
if (slackAgents > 0 && !packageInstalled('@stratusagent/channel-slack', env)) {
|
|
2372
|
-
groups.push({
|
|
2373
|
-
id: 'slack',
|
|
2374
|
-
label: 'Slack channel',
|
|
2375
|
-
why: `Slack tokens are stored for ${slackAgents} agent(s), but nothing connects to Slack without it`,
|
|
2376
|
-
packages: ['@stratusagent/channel-slack'],
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
// Both, because the dashboard is what was offered: the control API is
|
|
2380
|
-
// the port and the dashboard is the page served on it. Only the missing
|
|
2381
|
-
// half is installed, so accepting this on a machine that already has
|
|
2382
|
-
// the API does not reinstall it.
|
|
2383
|
-
const dashboardPackages = ['@stratusagent/control-api', '@stratusagent/dashboard']
|
|
2384
|
-
.filter((name) => !packageInstalled(name, env));
|
|
2385
|
-
if (dashboardPackages.length > 0) {
|
|
2386
|
-
groups.push({
|
|
2387
|
-
id: 'dashboard',
|
|
2388
|
-
label: 'Web dashboard',
|
|
2389
|
-
why: '`stratus dashboard` needs it, and it opens an authenticated port on 127.0.0.1',
|
|
2390
|
-
packages: dashboardPackages,
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
return groups;
|
|
2394
|
-
};
|
|
2395
|
-
/**
|
|
2396
|
-
* Offers the packages above. Declining is a real answer and prints the
|
|
2397
|
-
* command; the control API in particular binds a port, and installing it
|
|
2398
|
-
* is how an operator says they want one open, so this asks rather than
|
|
2399
|
-
* deciding for them.
|
|
2427
|
+
* The Slack channel package, when this setup has Slack tokens stored and
|
|
2428
|
+
* it is not installed. Setup knows that before the daemon does, so it is
|
|
2429
|
+
* the one place that can offer the install rather than leave the gap to
|
|
2430
|
+
* be found in a log after the fact.
|
|
2431
|
+
*
|
|
2432
|
+
* The dashboard is deliberately not offered here: its control API binds a
|
|
2433
|
+
* port, and the time to ask about that is when someone runs
|
|
2434
|
+
* `stratus dashboard`, which offers its own packages (card 308).
|
|
2435
|
+
* Declining is a real answer and prints the command.
|
|
2400
2436
|
*/
|
|
2401
2437
|
const offerOptionalPackages = async () => {
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2438
|
+
const slackPackage = '@stratusagent/channel-slack';
|
|
2439
|
+
const slackAgents = Object.keys(state.channels.slack ?? {}).length;
|
|
2440
|
+
if (slackAgents === 0 || packageInstalled(slackPackage, env)) {
|
|
2404
2441
|
return;
|
|
2405
2442
|
}
|
|
2406
2443
|
writeLine(streams.stdout);
|
|
2407
|
-
writeLine(streams.stdout,
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
writeLine(streams.stdout, `
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
}
|
|
2427
|
-
const only = groups[answer.index - 1];
|
|
2428
|
-
return only ? [only] : [];
|
|
2429
|
-
})();
|
|
2430
|
-
// Held before the install rather than derived after it: picking one
|
|
2431
|
-
// group is not a decision about the other, and the daemon starts a few
|
|
2432
|
-
// lines below and warns about exactly what is still missing — the
|
|
2433
|
-
// notice this whole offer exists to pre-empt.
|
|
2434
|
-
const declined = groups.filter((group) => !chosen.includes(group));
|
|
2435
|
-
if (chosen.length > 0) {
|
|
2436
|
-
const packages = chosen.flatMap((group) => group.packages);
|
|
2437
|
-
writeLine(streams.stdout, `Running: npm install -g ${packages.join(' ')}`);
|
|
2438
|
-
// Never fails setup — not even its exit code, unlike the always-on
|
|
2439
|
-
// service step below: the config and credentials are already written,
|
|
2440
|
-
// and a package that did not install is a warning at the next start,
|
|
2441
|
-
// not a broken machine.
|
|
2442
|
-
const result = await (env.packageInstaller ?? defaultPackageInstaller)(packages)
|
|
2443
|
-
.catch((error) => ({
|
|
2444
|
-
ok: false,
|
|
2445
|
-
message: error instanceof Error ? error.message : String(error),
|
|
2446
|
-
}));
|
|
2447
|
-
if (result.ok) {
|
|
2448
|
-
writeLine(streams.stdout, `Installed ${packages.join(' ')}.`);
|
|
2449
|
-
// npm's exit code, not a second resolve: a package written into the
|
|
2450
|
-
// global prefix a moment ago need not be resolvable from THIS
|
|
2451
|
-
// process, whose module resolution was fixed when it started.
|
|
2452
|
-
if (chosen.some((group) => group.id === 'dashboard')) {
|
|
2453
|
-
dashboardReady = true;
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
else {
|
|
2457
|
-
writeLine(streams.stderr, `Could not install: ${result.message}`);
|
|
2458
|
-
writeLine(streams.stderr, `Setup is saved either way — run \`npm install -g ${packages.join(' ')}\` yourself.`);
|
|
2459
|
-
}
|
|
2444
|
+
writeLine(streams.stdout, 'One optional package is not installed:');
|
|
2445
|
+
writeLine(streams.stdout, ` ${slackPackage}`);
|
|
2446
|
+
writeLine(streams.stdout, ` Slack tokens are stored for ${slackAgents} agent(s), but nothing connects to Slack without it.`);
|
|
2447
|
+
const answer = await prompter.select('Install now with npm install -g?', ['Install it now', 'Skip']);
|
|
2448
|
+
if (answer.kind !== 'index' || answer.index !== 0) {
|
|
2449
|
+
writeLine(streams.stdout, `Skipped. Install it yourself with: npm install -g ${slackPackage}`);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
writeLine(streams.stdout, `Running: npm install -g ${slackPackage}`);
|
|
2453
|
+
// Never fails setup — not even its exit code, unlike the always-on
|
|
2454
|
+
// service step below: the config and credentials are already written,
|
|
2455
|
+
// and a package that did not install is a warning at the next start,
|
|
2456
|
+
// not a broken machine.
|
|
2457
|
+
const result = await (env.packageInstaller ?? defaultPackageInstaller)([slackPackage])
|
|
2458
|
+
.catch((error) => ({
|
|
2459
|
+
ok: false,
|
|
2460
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2461
|
+
}));
|
|
2462
|
+
if (result.ok) {
|
|
2463
|
+
writeLine(streams.stdout, `Installed ${slackPackage}.`);
|
|
2460
2464
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
writeLine(streams.
|
|
2464
|
-
? `Still missing. Install with: npm install -g ${rest}`
|
|
2465
|
-
: `Skipped. Install them yourself with: npm install -g ${rest}`);
|
|
2465
|
+
else {
|
|
2466
|
+
writeLine(streams.stderr, `Could not install: ${result.message}`);
|
|
2467
|
+
writeLine(streams.stderr, `Setup is saved either way — run \`npm install -g ${slackPackage}\` yourself.`);
|
|
2466
2468
|
}
|
|
2467
2469
|
};
|
|
2468
2470
|
/**
|
|
2469
2471
|
* Write everything the menu decided, then report whether the always-on
|
|
2470
2472
|
* service step it performed — installing the service, or removing one the
|
|
2471
|
-
* user chose not to run — and the install of the provider
|
|
2473
|
+
* user chose not to run — and the install of the provider package the
|
|
2472
2474
|
* config runs on both succeeded.
|
|
2473
2475
|
*
|
|
2474
2476
|
* A boolean rather than a throw: the service is the one optional part of
|
|
@@ -2620,14 +2622,15 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2620
2622
|
// already present, instead of warning about them in a log nobody reads
|
|
2621
2623
|
// until the dashboard fails.
|
|
2622
2624
|
// The provider package comes first, and without a question: choosing
|
|
2623
|
-
//
|
|
2624
|
-
//
|
|
2625
|
-
//
|
|
2625
|
+
// the provider was the decision, and the CLI carries none of the
|
|
2626
|
+
// packages (the Anthropic adapter, the claude-code and codex harnesses),
|
|
2627
|
+
// so a daemon started without it fails every turn. Through the saved
|
|
2628
|
+
// files, so a soul's own pin counts.
|
|
2626
2629
|
// A failed install is carried like a failed service step: everything is
|
|
2627
2630
|
// saved, but the exit code and the closing line must not call a config
|
|
2628
2631
|
// whose every turn fails ready.
|
|
2629
|
-
const
|
|
2630
|
-
const
|
|
2632
|
+
const providerPackages = await missingProviderPackages(env, configPath, { asService: true });
|
|
2633
|
+
const providerInstallFailed = !(await installProviderPackages(providerPackages, streams, env));
|
|
2631
2634
|
await offerOptionalPackages();
|
|
2632
2635
|
// Last, so the daemon starts against the config and credentials that
|
|
2633
2636
|
// were just written rather than the ones it would have found a moment
|
|
@@ -2676,13 +2679,20 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2676
2679
|
ok: false,
|
|
2677
2680
|
messages: [`Could not install the always-on service: ${error instanceof Error ? error.message : String(error)}`],
|
|
2678
2681
|
}));
|
|
2682
|
+
// Installed but not running is a failed step too: the always-on
|
|
2683
|
+
// service is the part of setup that is meant to be answering.
|
|
2684
|
+
const running = !('running' in result) || result.running === true;
|
|
2679
2685
|
for (const message of result.messages) {
|
|
2680
|
-
writeLine(result.ok ? streams.stdout : streams.stderr, message);
|
|
2686
|
+
writeLine(result.ok && running ? streams.stdout : streams.stderr, message);
|
|
2681
2687
|
}
|
|
2682
2688
|
if (!result.ok) {
|
|
2683
2689
|
writeLine(streams.stderr, `Setup is saved either way — start the daemon yourself with \`${serveCommand()}\`.`);
|
|
2684
2690
|
serviceStepFailed = true;
|
|
2685
2691
|
}
|
|
2692
|
+
else if (!running) {
|
|
2693
|
+
writeLine(streams.stderr, 'Setup is saved either way. Check `stratus service status` once you have looked at the logs.');
|
|
2694
|
+
serviceStepFailed = true;
|
|
2695
|
+
}
|
|
2686
2696
|
}
|
|
2687
2697
|
writeLine(streams.stdout);
|
|
2688
2698
|
// Exported STRATUS_* variables outrank the config file, so warn when one
|
|
@@ -2723,8 +2733,8 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2723
2733
|
...(flagged.has('STRATUS_MODEL') ? { model: String(state.model ?? defaultModelFor(state.provider)) } : {}),
|
|
2724
2734
|
...(flagged.has('STRATUS_BASE_URL') ? { baseUrl: state.baseUrl ?? DEFAULT_OPENAI_BASE_URL } : {}),
|
|
2725
2735
|
};
|
|
2726
|
-
const readBackFailure =
|
|
2727
|
-
?
|
|
2736
|
+
const readBackFailure = providerInstallFailed
|
|
2737
|
+
? providerPackagesMissingMessage(providerPackages)
|
|
2728
2738
|
: await resolveRuntimeConfig(readBackSelection, env).then(() => undefined, (error) => (error instanceof Error ? error.message : String(error)));
|
|
2729
2739
|
const failedReadBack = () => {
|
|
2730
2740
|
if (readBackFailure === undefined) {
|
|
@@ -2764,10 +2774,9 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2764
2774
|
}
|
|
2765
2775
|
}
|
|
2766
2776
|
writeLine(streams.stdout, ` stratus run${extraFlags}${runConfigFlag}${shadowConfigFlag} "say hello"`);
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
return !serviceStepFailed && !harnessInstallFailed;
|
|
2777
|
+
// Whether or not its packages are here: the command offers them itself.
|
|
2778
|
+
writeLine(streams.stdout, ' stratus dashboard');
|
|
2779
|
+
return !serviceStepFailed && !providerInstallFailed;
|
|
2771
2780
|
};
|
|
2772
2781
|
try {
|
|
2773
2782
|
if (!interactive) {
|