@stratusagent/cli 0.11.6 → 0.11.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/dist/approvals.d.ts.map +1 -1
- package/dist/approvals.js +30 -1
- package/dist/approvals.js.map +1 -1
- package/dist/commands/channel.d.ts +17 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +115 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/chat.js +3 -3
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +50 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/grants.d.ts.map +1 -1
- package/dist/commands/grants.js +30 -7
- package/dist/commands/grants.js.map +1 -1
- package/dist/commands/health.d.ts +18 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +95 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +91 -19
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/service.d.ts +13 -0
- package/dist/commands/service.d.ts.map +1 -1
- package/dist/commands/service.js +36 -2
- package/dist/commands/service.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +189 -8
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.d.ts +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +14 -3
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +27 -3
- package/dist/commands/update.js.map +1 -1
- package/dist/daemon.d.ts +3 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +4 -1
- package/dist/daemon.js.map +1 -1
- package/dist/environment.d.ts +4 -0
- package/dist/environment.d.ts.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +20 -0
- package/dist/events.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +48 -24
- 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/loaders.d.ts +11 -0
- package/dist/loaders.d.ts.map +1 -1
- package/dist/loaders.js +8 -0
- package/dist/loaders.js.map +1 -1
- package/dist/logs.d.ts +20 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +37 -0
- package/dist/logs.js.map +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +25 -3
- package/dist/main.js.map +1 -1
- package/dist/npm.d.ts +3 -2
- package/dist/npm.d.ts.map +1 -1
- package/dist/npm.js +5 -2
- package/dist/npm.js.map +1 -1
- package/dist/parse.d.ts +30 -11
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +114 -10
- package/dist/parse.js.map +1 -1
- package/dist/prompter.d.ts +2 -2
- package/dist/prompter.d.ts.map +1 -1
- package/dist/prompter.js +4 -3
- package/dist/prompter.js.map +1 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +27 -4
- package/dist/runtime.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +2 -0
- package/dist/service.js.map +1 -1
- package/dist/supervisor.d.ts +9 -0
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +16 -1
- package/dist/supervisor.js.map +1 -1
- package/package.json +28 -26
package/dist/commands/setup.js
CHANGED
|
@@ -4,7 +4,7 @@ import { preflightPlugin, readPluginManifest, PluginConfigError, PluginManifestE
|
|
|
4
4
|
import { DEFAULT_ANTHROPIC_MODEL } from '@stratusagent/provider-anthropic';
|
|
5
5
|
import { DEFAULT_CODEX_MODEL } from '@stratusagent/provider-codex';
|
|
6
6
|
import { formatSoul, parseSoul } from '@stratusagent/agents';
|
|
7
|
-
import { claimSoulFile, collectAvailableModels as collectModels, credentialsPath, defaultApiKeyEnvName, DEFAULT_ANTHROPIC_BASE_URL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_STRATUS_AGENT, globalConfigPath, loadChannelCredentials, loadCredentials, loadRosterSouls, loadSoulFile, isRegisteredProviderName, readNonEmptyString, registeredProviderNameOf, readProcessEnv, readWorkingDirectory, resolveAgentApprovals, DEFAULT_CONFIG_FILENAME, loadConfigFile, saveChannelCredentials, saveConfigFile, saveCredentials, CREDENTIAL_PROVIDER_NAMES, verifyProviderKey, } from '@stratusagent/state';
|
|
7
|
+
import { claimSoulFile, collectAvailableModels as collectModels, credentialsPath, defaultApiKeyEnvName, DEFAULT_ANTHROPIC_BASE_URL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DEFAULT_STRATUS_AGENT, globalConfigPath, loadChannelCredentials, loadChannelTransportSecrets, loadCredentials, loadRosterSouls, loadSoulFile, isRegisteredProviderName, languageFor, readNonEmptyString, registeredProviderNameOf, readProcessEnv, readWorkingDirectory, resolveAgentApprovals, DEFAULT_CONFIG_FILENAME, loadConfigFile, saveChannelCredentials, saveChannelTransportSecrets, removeChannelTransportSecrets, CREDENTIAL_NAME_PATTERN, saveConfigFile, saveCredentials, CREDENTIAL_PROVIDER_NAMES, verifyProviderKey, } from '@stratusagent/state';
|
|
8
8
|
import { installService, readServiceStatus, servicePlatform, serviceUnitPath, uninstallService, } from "../service.js";
|
|
9
9
|
import { classifyApprovalReach, unattendedReachParts } from "../approvals.js";
|
|
10
10
|
import { serviceEnvFor } from "../daemon.js";
|
|
@@ -97,6 +97,7 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
97
97
|
...(existing.vision !== undefined ? { vision: existing.vision } : {}),
|
|
98
98
|
...(existing.promptCache !== undefined ? { promptCache: existing.promptCache } : {}),
|
|
99
99
|
...(existing.promptCacheTtl !== undefined ? { promptCacheTtl: existing.promptCacheTtl } : {}),
|
|
100
|
+
...(existing.language !== undefined ? { language: existing.language } : {}),
|
|
100
101
|
...(existing.plugins !== undefined ? { plugins: existing.plugins } : {}),
|
|
101
102
|
...(existing.approvals !== undefined ? { approvals: existing.approvals } : {}),
|
|
102
103
|
...(existing.api !== undefined ? { api: existing.api } : {}),
|
|
@@ -116,6 +117,8 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
116
117
|
credentialsDirty: false,
|
|
117
118
|
channels: await loadChannelCredentials(env),
|
|
118
119
|
channelsDirty: false,
|
|
120
|
+
pluginChannelSecrets: {},
|
|
121
|
+
pluginChannelEdits: [],
|
|
119
122
|
// On by default: setup's whole promise is that you finish it and the
|
|
120
123
|
// agents are working. An always-on runtime you have to remember to
|
|
121
124
|
// start is not always-on, and every Slack app configured here stays
|
|
@@ -221,12 +224,12 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
221
224
|
}
|
|
222
225
|
return state.soulPath;
|
|
223
226
|
};
|
|
224
|
-
const channelsSummary = () => {
|
|
227
|
+
const channelsSummary = (pluginKinds) => {
|
|
225
228
|
const connected = Object.keys(state.channels.slack ?? {}).length;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return
|
|
229
|
+
const slack = connected === 0
|
|
230
|
+
? 'Slack: not connected'
|
|
231
|
+
: `Slack: ${connected} agent${connected === 1 ? '' : 's'} connected`;
|
|
232
|
+
return pluginKinds.length === 0 ? slack : `${slack}; also ${pluginKinds.join(', ')}`;
|
|
230
233
|
};
|
|
231
234
|
const storeCredential = (provider, credential) => {
|
|
232
235
|
state.credentials[provider] = credential;
|
|
@@ -526,7 +529,7 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
526
529
|
if ((providerPart === 'anthropic' || providerPart === 'openai' || providerPart === 'codex') && id) {
|
|
527
530
|
return { provider: providerPart, id };
|
|
528
531
|
}
|
|
529
|
-
writeLine(streams.stdout, 'Use provider:model, e.g. anthropic:claude-opus-5 or codex:gpt-5.5.');
|
|
532
|
+
writeLine(streams.stdout, 'Use provider:model, e.g. anthropic:claude-opus-5-5 or codex:gpt-5.5.');
|
|
530
533
|
return undefined;
|
|
531
534
|
}
|
|
532
535
|
// A typed id that appears in the collected list belongs to that
|
|
@@ -705,7 +708,15 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
705
708
|
...(codexSubscription ? { codexSubscription: true } : {}),
|
|
706
709
|
};
|
|
707
710
|
};
|
|
711
|
+
// The language the saved config would run in, soul over config, carried
|
|
712
|
+
// onto whichever variant the test run builds: it is part of what the
|
|
713
|
+
// quick hello is there to exercise.
|
|
708
714
|
const buildTestRuntime = async () => {
|
|
715
|
+
const runtime = await buildTestRuntimeVariant();
|
|
716
|
+
const language = runtime ? languageFor(runtime.soul, state) : undefined;
|
|
717
|
+
return runtime && language !== undefined ? { ...runtime, language } : runtime;
|
|
718
|
+
};
|
|
719
|
+
const buildTestRuntimeVariant = async () => {
|
|
709
720
|
let soul;
|
|
710
721
|
if (state.soulPath) {
|
|
711
722
|
try {
|
|
@@ -1166,6 +1177,7 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
1166
1177
|
// The qualified form the loader stages them under, which is what a
|
|
1167
1178
|
// `skills:` entry has to match.
|
|
1168
1179
|
skills: manifest.contributes.skills.map((skill) => `${manifest.packageName}:${skill.id}`),
|
|
1180
|
+
channels: manifest.contributes.channels.map((channel) => channel.name),
|
|
1169
1181
|
};
|
|
1170
1182
|
}
|
|
1171
1183
|
catch {
|
|
@@ -1936,7 +1948,159 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
1936
1948
|
state.approvals = { ...(state.approvals ?? {}), mode: 'remote', agents };
|
|
1937
1949
|
}
|
|
1938
1950
|
};
|
|
1951
|
+
/**
|
|
1952
|
+
* The channel kinds the enabled plugins declare, Slack's own aside, each
|
|
1953
|
+
* with the config key of every plugin declaring it — from manifests,
|
|
1954
|
+
* never by loading a package, as the Plugins row reads them. Every one,
|
|
1955
|
+
* not the first: two plugins may carry the same kind for different agents
|
|
1956
|
+
* (channels key on agent and kind), and an agent bound by the second one's
|
|
1957
|
+
* config is as bound as one bound by the first's.
|
|
1958
|
+
*/
|
|
1959
|
+
const pluginChannelKinds = async () => {
|
|
1960
|
+
const kinds = [];
|
|
1961
|
+
for (const name of pluginPackages()) {
|
|
1962
|
+
if (!pluginEnabled(name)) {
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
for (const kind of (await pluginContributions(name))?.channels ?? []) {
|
|
1966
|
+
if (kind === 'slack') {
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
const entry = kinds.find((candidate) => candidate.kind === kind);
|
|
1970
|
+
if (entry === undefined) {
|
|
1971
|
+
kinds.push({ kind, plugins: [name] });
|
|
1972
|
+
}
|
|
1973
|
+
else if (!entry.plugins.includes(name)) {
|
|
1974
|
+
entry.plugins.push(name);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
return kinds;
|
|
1979
|
+
};
|
|
1980
|
+
/**
|
|
1981
|
+
* Slack alone, as it always was, until a plugin declares a channel: then
|
|
1982
|
+
* the row asks which channel first. A setup with no channel plugin sees
|
|
1983
|
+
* exactly the screens it saw before there could be one.
|
|
1984
|
+
*/
|
|
1939
1985
|
const chooseChannels = async () => {
|
|
1986
|
+
const kinds = await pluginChannelKinds();
|
|
1987
|
+
if (kinds.length === 0) {
|
|
1988
|
+
await chooseSlackChannel();
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
while (true) {
|
|
1992
|
+
const options = [
|
|
1993
|
+
'Slack'.padEnd(20) + 'built in',
|
|
1994
|
+
...kinds.map(({ kind, plugins }) => kind.padEnd(20) + `from ${plugins.join(', ')}`),
|
|
1995
|
+
'Back',
|
|
1996
|
+
];
|
|
1997
|
+
const choice = await prompter.select('Channels', options);
|
|
1998
|
+
if (choice.kind !== 'index' || choice.index === options.length - 1) {
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
const picked = kinds[choice.index - 1];
|
|
2002
|
+
if (picked === undefined) {
|
|
2003
|
+
await chooseSlackChannel();
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
await choosePluginChannel(picked.kind, picked.plugins);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
/**
|
|
2011
|
+
* One plugin channel's agents, and whether each is bound — by secrets
|
|
2012
|
+
* stored under `channels.<kind>.<agentId>`, or by an entry under the
|
|
2013
|
+
* plugin's own config `agents` block. Either is a binding: a channel that
|
|
2014
|
+
* needs no secret (iMessage on this Mac) is bound by config alone, so a
|
|
2015
|
+
* menu that knew only secrets would call it not connected.
|
|
2016
|
+
*
|
|
2017
|
+
* Setup stores secrets and never writes the plugin's config: which keys
|
|
2018
|
+
* that block takes is the plugin's schema, which only its README states.
|
|
2019
|
+
*/
|
|
2020
|
+
const choosePluginChannel = async (kind, plugins) => {
|
|
2021
|
+
const secretsFor = async () => {
|
|
2022
|
+
state.pluginChannelSecrets[kind] ??= await loadChannelTransportSecrets(env, kind);
|
|
2023
|
+
return state.pluginChannelSecrets[kind];
|
|
2024
|
+
};
|
|
2025
|
+
while (true) {
|
|
2026
|
+
const { entries: roster, loaded: rosterLoaded } = await channelRoster();
|
|
2027
|
+
const stored = await secretsFor();
|
|
2028
|
+
// Any plugin carrying this kind binds the agents its own block lists.
|
|
2029
|
+
const inConfig = (id) => plugins.some((plugin) => {
|
|
2030
|
+
const configured = state.plugins?.[plugin]?.agents;
|
|
2031
|
+
return typeof configured === 'object' && configured !== null && !Array.isArray(configured) && Object.hasOwn(configured, id);
|
|
2032
|
+
});
|
|
2033
|
+
const options = roster.map((entry) => {
|
|
2034
|
+
const id = entry.soul.agent.id;
|
|
2035
|
+
const bindings = [
|
|
2036
|
+
...(stored[id] !== undefined ? [`✓ secrets: ${Object.keys(stored[id]).sort().join(', ')}`] : []),
|
|
2037
|
+
...(inConfig(id) ? ['✓ in its plugin config'] : []),
|
|
2038
|
+
];
|
|
2039
|
+
return `${entry.soul.agent.name} (${id})`.padEnd(34) + (bindings.length > 0 ? bindings.join('; ') : '— not connected');
|
|
2040
|
+
});
|
|
2041
|
+
// Only against a roster that loaded, for the reason the Slack menu
|
|
2042
|
+
// gives: an unreadable roster would make every binding look orphaned.
|
|
2043
|
+
const orphans = rosterLoaded
|
|
2044
|
+
? Object.keys(stored).filter((id) => !roster.some((entry) => entry.soul.agent.id === id))
|
|
2045
|
+
: [];
|
|
2046
|
+
for (const id of orphans) {
|
|
2047
|
+
options.push(`${id}`.padEnd(34) + '! secrets without a matching agent');
|
|
2048
|
+
}
|
|
2049
|
+
options.push('Back');
|
|
2050
|
+
const blocks = plugins.map((plugin) => `plugins["${plugin}"].agents`).join(' or ');
|
|
2051
|
+
const choice = await prompter.select(`Channels — ${kind} (from ${plugins.join(', ')})`, options, {
|
|
2052
|
+
footnote: `Bound by secrets stored here, or by an entry under ${blocks} in your config — the plugin's README says which. Run \`${serveCommand()}\` afterwards.`,
|
|
2053
|
+
});
|
|
2054
|
+
if (choice.kind !== 'index' || choice.index === options.length - 1) {
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
const agentId = choice.index < roster.length
|
|
2058
|
+
? roster[choice.index]?.soul.agent.id
|
|
2059
|
+
: orphans[choice.index - roster.length];
|
|
2060
|
+
if (agentId === undefined) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
const actions = [
|
|
2064
|
+
...(choice.index < roster.length ? [stored[agentId] !== undefined ? 'Replace the stored secrets' : 'Store secrets'] : []),
|
|
2065
|
+
...(stored[agentId] !== undefined ? ['Remove the stored secrets'] : []),
|
|
2066
|
+
'Back',
|
|
2067
|
+
];
|
|
2068
|
+
const action = await prompter.select(`${agentId} on ${kind}`, actions);
|
|
2069
|
+
if (action.kind !== 'index' || actions[action.index] === 'Back') {
|
|
2070
|
+
continue;
|
|
2071
|
+
}
|
|
2072
|
+
if (actions[action.index] === 'Remove the stored secrets') {
|
|
2073
|
+
delete stored[agentId];
|
|
2074
|
+
state.pluginChannelEdits.push({ kind, agentId });
|
|
2075
|
+
writeLine(streams.stdout, `Removed ${agentId}'s ${kind} secrets.`);
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
const names = (await prompter.ask(`Secret names for ${kind}, separated by spaces (its README lists them): `))
|
|
2079
|
+
.split(/\s+/)
|
|
2080
|
+
.filter((name) => name.length > 0);
|
|
2081
|
+
if (names.length === 0) {
|
|
2082
|
+
continue;
|
|
2083
|
+
}
|
|
2084
|
+
const bad = names.find((name, index) => !CREDENTIAL_NAME_PATTERN.test(name) || names.indexOf(name) !== index);
|
|
2085
|
+
if (bad !== undefined) {
|
|
2086
|
+
writeLine(streams.stderr, `${bad} is not a secret name, or is named twice. Nothing was stored.`);
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
const secrets = {};
|
|
2090
|
+
for (const name of names) {
|
|
2091
|
+
secrets[name] = await prompter.askSecret(`${name} (not echoed): `);
|
|
2092
|
+
}
|
|
2093
|
+
const empty = names.find((name) => (secrets[name] ?? '').length === 0);
|
|
2094
|
+
if (empty !== undefined) {
|
|
2095
|
+
writeLine(streams.stderr, `${empty} was empty, so nothing was stored. A binding missing a secret would never come online.`);
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
stored[agentId] = secrets;
|
|
2099
|
+
state.pluginChannelEdits.push({ kind, agentId, secrets });
|
|
2100
|
+
writeLine(streams.stdout, `Stored ${names.join(', ')} for ${agentId} on ${kind}; saved when you finish.`);
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
const chooseSlackChannel = async () => {
|
|
1940
2104
|
while (true) {
|
|
1941
2105
|
const { entries: roster, loaded: rosterLoaded } = await channelRoster();
|
|
1942
2106
|
const slack = state.channels.slack ?? {};
|
|
@@ -2230,6 +2394,9 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2230
2394
|
if (state.promptCacheTtl !== undefined) {
|
|
2231
2395
|
config.promptCacheTtl = state.promptCacheTtl;
|
|
2232
2396
|
}
|
|
2397
|
+
if (state.language !== undefined) {
|
|
2398
|
+
config.language = state.language;
|
|
2399
|
+
}
|
|
2233
2400
|
// `plugins` and `approvals` have menus above; `api`, `principals`, and
|
|
2234
2401
|
// `slack` do not and are written back exactly as they were read. Both cases
|
|
2235
2402
|
// land here the same way, because the menus edit this state rather
|
|
@@ -2294,6 +2461,19 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2294
2461
|
? `Saved Slack tokens for ${connected} agent${connected === 1 ? '' : 's'} to ${credentialsPath(env)} — run \`${serveCommand()}\` to bring them online.`
|
|
2295
2462
|
: `Removed the stored Slack tokens from ${credentialsPath(env)}.`);
|
|
2296
2463
|
}
|
|
2464
|
+
if (state.pluginChannelEdits.length > 0) {
|
|
2465
|
+
// In the order they were made, so a store then a remove of the same
|
|
2466
|
+
// binding ends removed, as the menu last showed it.
|
|
2467
|
+
for (const edit of state.pluginChannelEdits) {
|
|
2468
|
+
if (edit.secrets !== undefined) {
|
|
2469
|
+
await saveChannelTransportSecrets(env, edit.kind, edit.agentId, edit.secrets);
|
|
2470
|
+
}
|
|
2471
|
+
else {
|
|
2472
|
+
await removeChannelTransportSecrets(env, edit.kind, edit.agentId);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
writeLine(streams.stdout, `Saved your channel secrets to ${credentialsPath(env)} — run \`${serveCommand()}\` to bring them online.`);
|
|
2476
|
+
}
|
|
2297
2477
|
// Before the service block below, deliberately. A package installed
|
|
2298
2478
|
// after the daemon starts is invisible to it — installing does not
|
|
2299
2479
|
// reload a running process — so offering here is what makes the
|
|
@@ -2431,12 +2611,13 @@ export const runSetup = async (command, streams, env = {}) => {
|
|
|
2431
2611
|
// corrupts every redraw after the first arrow key. Wrapping is not
|
|
2432
2612
|
// the cosmetic cost I took it for when I let this line grow.
|
|
2433
2613
|
const approvals = fitMenuRow((await approvalsSummary()).join('; '));
|
|
2614
|
+
const channelKinds = (await pluginChannelKinds()).map((entry) => entry.kind);
|
|
2434
2615
|
const choice = await prompter.select('', [
|
|
2435
2616
|
`Providers ${providersSummary()}`,
|
|
2436
2617
|
`Models ${modelsSummary()}`,
|
|
2437
2618
|
`Agent ${agentSummary()}`,
|
|
2438
2619
|
`Plugins ${fitMenuRow(pluginsSummary())}`,
|
|
2439
|
-
`Channels ${channelsSummary()}`,
|
|
2620
|
+
`Channels ${fitMenuRow(channelsSummary(channelKinds))}`,
|
|
2440
2621
|
`Approvals ${approvals}`,
|
|
2441
2622
|
`Always on ${serviceSummary()}`,
|
|
2442
2623
|
'Test run say hello with the current settings',
|