@stratusagent/cli 0.13.6 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/bin.js +8 -1
- package/dist/bin.js.map +1 -1
- 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/credential.d.ts +1 -1
- package/dist/commands/credential.d.ts.map +1 -1
- package/dist/commands/credential.js +59 -10
- package/dist/commands/credential.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +132 -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/events.d.ts +14 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +94 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +8 -3
- package/dist/commands/logs.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/schedules.d.ts.map +1 -1
- package/dist/commands/schedules.js +6 -1
- package/dist/commands/schedules.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 +44 -7
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +137 -126
- 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 +71 -16
- 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/environment.d.ts +12 -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 +33 -11
- 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/io.d.ts +24 -0
- package/dist/io.d.ts.map +1 -1
- package/dist/io.js +29 -0
- package/dist/io.js.map +1 -1
- package/dist/loaders.d.ts +7 -0
- package/dist/loaders.d.ts.map +1 -1
- package/dist/loaders.js +17 -9
- package/dist/loaders.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +19 -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 +27 -3
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +59 -7
- 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 +4 -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/provider-packages.d.ts +42 -0
- package/dist/provider-packages.d.ts.map +1 -0
- package/dist/{harness-packages.js → provider-packages.js} +28 -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.map +1 -1
- package/dist/service.js +67 -1
- 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/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +1 -0
- package/dist/supervisor.js.map +1 -1
- package/dist/trusted-config.d.ts +8 -1
- package/dist/trusted-config.d.ts.map +1 -1
- package/dist/trusted-config.js +19 -0
- package/dist/trusted-config.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
package/dist/commands/setup.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { providerPackagesMissingMessage, installProviderPackages, missingProviderPackages, uninstalledProviderPackages } 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}.`);
|
|
@@ -2320,6 +2385,15 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2320
2385
|
if (!runtime) {
|
|
2321
2386
|
return;
|
|
2322
2387
|
}
|
|
2388
|
+
// The CLI carries no provider package, and this row comes before Save,
|
|
2389
|
+
// which installs the one picked. Without this, the first hello on a
|
|
2390
|
+
// fresh install failed on the missing package and told someone already
|
|
2391
|
+
// in setup to choose the provider again in setup. Installed the way
|
|
2392
|
+
// Save does it, without a question: choosing the provider was the
|
|
2393
|
+
// decision. A failed install has already said so, with the command.
|
|
2394
|
+
if (!(await installProviderPackages(uninstalledProviderPackages([runtime], env), streams, env))) {
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2323
2397
|
writeLine(streams.stdout, `Running a quick hello (${formatRuntimeBanner(runtime).replace('Starting Stratus Agent local loop with ', '')})…`);
|
|
2324
2398
|
try {
|
|
2325
2399
|
const session = await runSingleLoop('Say hello and introduce yourself in one short sentence.', streams, { events: false, runtime, env });
|
|
@@ -2359,116 +2433,53 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2359
2433
|
};
|
|
2360
2434
|
};
|
|
2361
2435
|
/**
|
|
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.
|
|
2436
|
+
* The Slack channel package, when this setup has Slack tokens stored and
|
|
2437
|
+
* it is not installed. Setup knows that before the daemon does, so it is
|
|
2438
|
+
* the one place that can offer the install rather than leave the gap to
|
|
2439
|
+
* be found in a log after the fact.
|
|
2440
|
+
*
|
|
2441
|
+
* The dashboard is deliberately not offered here: its control API binds a
|
|
2442
|
+
* port, and the time to ask about that is when someone runs
|
|
2443
|
+
* `stratus dashboard`, which offers its own packages (card 308).
|
|
2444
|
+
* Declining is a real answer and prints the command.
|
|
2400
2445
|
*/
|
|
2401
2446
|
const offerOptionalPackages = async () => {
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2447
|
+
const slackPackage = '@stratusagent/channel-slack';
|
|
2448
|
+
const slackAgents = Object.keys(state.channels.slack ?? {}).length;
|
|
2449
|
+
if (slackAgents === 0 || packageInstalled(slackPackage, env)) {
|
|
2404
2450
|
return;
|
|
2405
2451
|
}
|
|
2406
2452
|
writeLine(streams.stdout);
|
|
2407
|
-
writeLine(streams.stdout,
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
writeLine(streams.stdout, `
|
|
2413
|
-
|
|
2414
|
-
const answer = await prompter.select('Install now with npm install -g?', groups.length === 1
|
|
2415
|
-
? ['Install it now', 'Skip']
|
|
2416
|
-
: ['Install all of them now', ...groups.map((group) => `Install the ${group.label} only`), 'Skip']);
|
|
2417
|
-
const chosen = (() => {
|
|
2418
|
-
if (answer.kind !== 'index') {
|
|
2419
|
-
return [];
|
|
2420
|
-
}
|
|
2421
|
-
if (groups.length === 1) {
|
|
2422
|
-
return answer.index === 0 ? groups : [];
|
|
2423
|
-
}
|
|
2424
|
-
if (answer.index === 0) {
|
|
2425
|
-
return groups;
|
|
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
|
-
}
|
|
2453
|
+
writeLine(streams.stdout, 'One optional package is not installed:');
|
|
2454
|
+
writeLine(streams.stdout, ` ${slackPackage}`);
|
|
2455
|
+
writeLine(streams.stdout, ` Slack tokens are stored for ${slackAgents} agent(s), but nothing connects to Slack without it.`);
|
|
2456
|
+
const answer = await prompter.select('Install now with npm install -g?', ['Install it now', 'Skip']);
|
|
2457
|
+
if (answer.kind !== 'index' || answer.index !== 0) {
|
|
2458
|
+
writeLine(streams.stdout, `Skipped. Install it yourself with: npm install -g ${slackPackage}`);
|
|
2459
|
+
return;
|
|
2460
2460
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2461
|
+
writeLine(streams.stdout, `Running: npm install -g ${slackPackage}`);
|
|
2462
|
+
// Never fails setup — not even its exit code, unlike the always-on
|
|
2463
|
+
// service step below: the config and credentials are already written,
|
|
2464
|
+
// and a package that did not install is a warning at the next start,
|
|
2465
|
+
// not a broken machine.
|
|
2466
|
+
const result = await (env.packageInstaller ?? defaultPackageInstaller)([slackPackage])
|
|
2467
|
+
.catch((error) => ({
|
|
2468
|
+
ok: false,
|
|
2469
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2470
|
+
}));
|
|
2471
|
+
if (result.ok) {
|
|
2472
|
+
writeLine(streams.stdout, `Installed ${slackPackage}.`);
|
|
2473
|
+
}
|
|
2474
|
+
else {
|
|
2475
|
+
writeLine(streams.stderr, `Could not install: ${result.message}`);
|
|
2476
|
+
writeLine(streams.stderr, `Setup is saved either way — run \`npm install -g ${slackPackage}\` yourself.`);
|
|
2466
2477
|
}
|
|
2467
2478
|
};
|
|
2468
2479
|
/**
|
|
2469
2480
|
* Write everything the menu decided, then report whether the always-on
|
|
2470
2481
|
* service step it performed — installing the service, or removing one the
|
|
2471
|
-
* user chose not to run — and the install of the provider
|
|
2482
|
+
* user chose not to run — and the install of the provider package the
|
|
2472
2483
|
* config runs on both succeeded.
|
|
2473
2484
|
*
|
|
2474
2485
|
* A boolean rather than a throw: the service is the one optional part of
|
|
@@ -2620,14 +2631,15 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2620
2631
|
// already present, instead of warning about them in a log nobody reads
|
|
2621
2632
|
// until the dashboard fails.
|
|
2622
2633
|
// The provider package comes first, and without a question: choosing
|
|
2623
|
-
//
|
|
2624
|
-
//
|
|
2625
|
-
//
|
|
2634
|
+
// the provider was the decision, and the CLI carries none of the
|
|
2635
|
+
// packages (the Anthropic adapter, the claude-code and codex harnesses),
|
|
2636
|
+
// so a daemon started without it fails every turn. Through the saved
|
|
2637
|
+
// files, so a soul's own pin counts.
|
|
2626
2638
|
// A failed install is carried like a failed service step: everything is
|
|
2627
2639
|
// saved, but the exit code and the closing line must not call a config
|
|
2628
2640
|
// whose every turn fails ready.
|
|
2629
|
-
const
|
|
2630
|
-
const
|
|
2641
|
+
const providerPackages = await missingProviderPackages(env, configPath, { asService: true });
|
|
2642
|
+
const providerInstallFailed = !(await installProviderPackages(providerPackages, streams, env));
|
|
2631
2643
|
await offerOptionalPackages();
|
|
2632
2644
|
// Last, so the daemon starts against the config and credentials that
|
|
2633
2645
|
// were just written rather than the ones it would have found a moment
|
|
@@ -2730,8 +2742,8 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2730
2742
|
...(flagged.has('STRATUS_MODEL') ? { model: String(state.model ?? defaultModelFor(state.provider)) } : {}),
|
|
2731
2743
|
...(flagged.has('STRATUS_BASE_URL') ? { baseUrl: state.baseUrl ?? DEFAULT_OPENAI_BASE_URL } : {}),
|
|
2732
2744
|
};
|
|
2733
|
-
const readBackFailure =
|
|
2734
|
-
?
|
|
2745
|
+
const readBackFailure = providerInstallFailed
|
|
2746
|
+
? providerPackagesMissingMessage(providerPackages)
|
|
2735
2747
|
: await resolveRuntimeConfig(readBackSelection, env).then(() => undefined, (error) => (error instanceof Error ? error.message : String(error)));
|
|
2736
2748
|
const failedReadBack = () => {
|
|
2737
2749
|
if (readBackFailure === undefined) {
|
|
@@ -2771,10 +2783,9 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2771
2783
|
}
|
|
2772
2784
|
}
|
|
2773
2785
|
writeLine(streams.stdout, ` stratus run${extraFlags}${runConfigFlag}${shadowConfigFlag} "say hello"`);
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
return !serviceStepFailed && !harnessInstallFailed;
|
|
2786
|
+
// Whether or not its packages are here: the command offers them itself.
|
|
2787
|
+
writeLine(streams.stdout, ' stratus dashboard');
|
|
2788
|
+
return !serviceStepFailed && !providerInstallFailed;
|
|
2778
2789
|
};
|
|
2779
2790
|
try {
|
|
2780
2791
|
if (!interactive) {
|