@wirecat/cli-core 0.470.0 → 0.471.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/CHANGELOG.md +25 -0
- package/dist/cli/app-doctor-command.d.ts +107 -0
- package/dist/cli/app-doctor-command.js +101 -0
- package/dist/cli/app-mcp-command.d.ts +23 -0
- package/dist/cli/app-mcp-command.js +49 -0
- package/dist/cli/index.d.ts +4 -2
- package/dist/cli/index.js +2 -1
- package/dist/cli/messenger/mcp-command.d.ts +29 -3
- package/dist/cli/messenger/mcp-command.js +59 -39
- package/dist/cli/shared-command-exports.d.ts +348 -0
- package/dist/cli/shared-command-exports.js +4 -0
- package/dist/mcp/agent.js +14 -2
- package/dist/mcp/app-server.d.ts +42 -0
- package/dist/mcp/app-server.js +162 -0
- package/dist/mcp/instructions.d.ts +12 -0
- package/dist/mcp/instructions.js +23 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.471.0 — 27.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`appMcpCommand(app, configuration, { name, untrusted, permissionExample, skill?, signedIn? })` — the shared
|
|
15
|
+
`mcp` for a CLI that is not a messenger** (gcal, drive and zoom each had their own): `mcp serve | revoke | server
|
|
16
|
+
start|stop|status | config | add | check`, the same commands, flags and answers as a messenger's `mcpCommand`,
|
|
17
|
+
with `--profiles <names|all>` (no profile is ever picked), `--http`, `--port`, `--public-url` and `--permission`
|
|
18
|
+
(a key no command checks is refused). Every command of the program is a tool behind `<cli>_tools_search`,
|
|
19
|
+
`<cli>_read` and `<cli>_write`, run in this process through the CLI's own `run`, under the profile's permissions
|
|
20
|
+
and its `configuration.permissions` keys; `mcp` itself is left out. The instructions come from the new
|
|
21
|
+
`appInstructions`, which requires the kinds of text other people wrote (`untrusted`, WC-MCP-11). Both kinds of
|
|
22
|
+
CLI now build the group from one `mcpGroup(host: McpHost)`.
|
|
23
|
+
- **`appDoctorCommand(app, configuration, { oauthClient? })` — the shared `doctor` for a CLI that is not a
|
|
24
|
+
messenger**: version, runtime, paths, `store: { path, exists }`, `oauth_client` (`wirecat` or `own`, or the
|
|
25
|
+
error), `read_only`, `profile_lock`, the config file with its issues — permission keys no command checks
|
|
26
|
+
included — the profiles and the config file's modes. It opens no keyring, store or network. `appDoctorReport` is
|
|
27
|
+
the same report for a CLI that renders its own.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Each MCP call has its own `run_id`.** A command an agent ran through `<cli>_read` or `<cli>_write`
|
|
32
|
+
(`runCommandForAgent`) answered with the server process's run id, so two calls of one session had the same
|
|
33
|
+
`meta.run_id`. Each call now runs in a run of its own, `mcp <command>`, keeping the caller's trace.
|
|
34
|
+
|
|
10
35
|
## 0.470.0 — 27.10.2026
|
|
11
36
|
|
|
12
37
|
### Changed — may break callers
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { type AppIdentity } from "../ports/app.js";
|
|
3
|
+
import type { Configuration } from "./settings.js";
|
|
4
|
+
export interface AppDoctorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Which OAuth client the CLI signs in with — WireCat's or one of the owner's own — read from the environment and
|
|
7
|
+
* files only. A throw is reported as its message.
|
|
8
|
+
*/
|
|
9
|
+
oauthClient?: (env: NodeJS.ProcessEnv) => {
|
|
10
|
+
client: "wirecat" | "own";
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Run when everything else is broken, so a problem in the config file or the OAuth setting is a field, never a throw.
|
|
15
|
+
* It reads no keyring, opens no store and calls no service: each can hang or prompt. `fixes` are the `chmod` commands
|
|
16
|
+
* that make the config private; they go to stderr, not into the answer.
|
|
17
|
+
*/
|
|
18
|
+
export declare const appDoctorReport: (app: AppIdentity, configuration: Configuration, env: NodeJS.ProcessEnv, command: Command, options?: AppDoctorOptions) => {
|
|
19
|
+
report: {
|
|
20
|
+
version: string;
|
|
21
|
+
runtime: {
|
|
22
|
+
node: string;
|
|
23
|
+
platform: NodeJS.Platform;
|
|
24
|
+
};
|
|
25
|
+
paths: {
|
|
26
|
+
config: string;
|
|
27
|
+
state: string;
|
|
28
|
+
cache: string;
|
|
29
|
+
};
|
|
30
|
+
store: {
|
|
31
|
+
path: string;
|
|
32
|
+
exists: boolean;
|
|
33
|
+
};
|
|
34
|
+
oauth_client?: {
|
|
35
|
+
client: "wirecat" | "own" | null;
|
|
36
|
+
error?: string;
|
|
37
|
+
} | undefined;
|
|
38
|
+
read_only: string | null;
|
|
39
|
+
profile_lock: string | null;
|
|
40
|
+
config: {
|
|
41
|
+
path: string;
|
|
42
|
+
found: boolean;
|
|
43
|
+
issues: import("./config-issues.js").ConfigIssue[];
|
|
44
|
+
};
|
|
45
|
+
profiles: string[];
|
|
46
|
+
files: {
|
|
47
|
+
checked: false;
|
|
48
|
+
reason: string;
|
|
49
|
+
} | {
|
|
50
|
+
checked: true;
|
|
51
|
+
ok: boolean;
|
|
52
|
+
problems: {
|
|
53
|
+
path: string;
|
|
54
|
+
mode: string;
|
|
55
|
+
want: string;
|
|
56
|
+
}[];
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
fixes: string[] | never[];
|
|
60
|
+
} | {
|
|
61
|
+
report: {
|
|
62
|
+
version: string;
|
|
63
|
+
runtime: {
|
|
64
|
+
node: string;
|
|
65
|
+
platform: NodeJS.Platform;
|
|
66
|
+
};
|
|
67
|
+
paths: {
|
|
68
|
+
config: string;
|
|
69
|
+
state: string;
|
|
70
|
+
cache: string;
|
|
71
|
+
};
|
|
72
|
+
store: {
|
|
73
|
+
path: string;
|
|
74
|
+
exists: boolean;
|
|
75
|
+
};
|
|
76
|
+
oauth_client?: {
|
|
77
|
+
client: "wirecat" | "own" | null;
|
|
78
|
+
error?: string;
|
|
79
|
+
} | undefined;
|
|
80
|
+
read_only: string | null;
|
|
81
|
+
profile_lock: string | null;
|
|
82
|
+
config: {
|
|
83
|
+
path: string;
|
|
84
|
+
found: boolean;
|
|
85
|
+
issues: never[];
|
|
86
|
+
};
|
|
87
|
+
settings: {
|
|
88
|
+
error: string;
|
|
89
|
+
};
|
|
90
|
+
profiles: null;
|
|
91
|
+
files: {
|
|
92
|
+
checked: false;
|
|
93
|
+
reason: string;
|
|
94
|
+
} | {
|
|
95
|
+
checked: true;
|
|
96
|
+
ok: boolean;
|
|
97
|
+
problems: {
|
|
98
|
+
path: string;
|
|
99
|
+
mode: string;
|
|
100
|
+
want: string;
|
|
101
|
+
}[];
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
fixes: string[] | never[];
|
|
105
|
+
};
|
|
106
|
+
/** `doctor` for a CLI that is not a messenger: gcal, drive, zoom. A messenger's is `doctorCommand(messenger)`. */
|
|
107
|
+
export declare const appDoctorCommand: (app: AppIdentity, configuration: Configuration, options?: AppDoctorOptions) => Command;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { CliError, configFilePath, resolvePaths } from "@wirecat/cli-base";
|
|
4
|
+
import { annotate } from "@wirecat/cli-base/commands";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { envName, readOnlyLockOf } from "../ports/app.js";
|
|
7
|
+
import { storePath } from "../store/path.js";
|
|
8
|
+
import { environmentOf, outputFor } from "./context.js";
|
|
9
|
+
import { LOCAL_READ } from "./effects.js";
|
|
10
|
+
import { itemOf, renderAnswer } from "./envelope.js";
|
|
11
|
+
import { privateFiles } from "./messenger/private-files.js";
|
|
12
|
+
import { unknownPermissionIssues } from "./permission-keys.js";
|
|
13
|
+
import { profileFree } from "./profile.js";
|
|
14
|
+
const failure = (error) => {
|
|
15
|
+
if (error instanceof CliError)
|
|
16
|
+
return error.message;
|
|
17
|
+
throw error;
|
|
18
|
+
};
|
|
19
|
+
const configFiles = (path) => {
|
|
20
|
+
const files = privateFiles({ files: [path], dirs: [dirname(path)] });
|
|
21
|
+
if (!files.checked)
|
|
22
|
+
return { files, fixes: [] };
|
|
23
|
+
return {
|
|
24
|
+
files: { ...files, problems: files.problems.map(({ path, mode, want }) => ({ path, mode, want })) },
|
|
25
|
+
fixes: files.problems.map((problem) => problem.fix),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Run when everything else is broken, so a problem in the config file or the OAuth setting is a field, never a throw.
|
|
30
|
+
* It reads no keyring, opens no store and calls no service: each can hang or prompt. `fixes` are the `chmod` commands
|
|
31
|
+
* that make the config private; they go to stderr, not into the answer.
|
|
32
|
+
*/
|
|
33
|
+
export const appDoctorReport = (app, configuration, env, command, options = {}) => {
|
|
34
|
+
const paths = resolvePaths({ appName: app.appName, prefix: app.envPrefix, env });
|
|
35
|
+
const store = resolve(storePath(env));
|
|
36
|
+
let oauth;
|
|
37
|
+
if (options.oauthClient)
|
|
38
|
+
try {
|
|
39
|
+
oauth = options.oauthClient(env);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
oauth = { client: null, error: failure(error) };
|
|
43
|
+
}
|
|
44
|
+
const common = {
|
|
45
|
+
version: app.version,
|
|
46
|
+
runtime: { node: process.versions.node, platform: process.platform },
|
|
47
|
+
paths: { config: paths.config, state: paths.state, cache: paths.cache },
|
|
48
|
+
store: { path: store, exists: existsSync(store) },
|
|
49
|
+
...(oauth ? { oauth_client: oauth } : {}),
|
|
50
|
+
read_only: readOnlyLockOf(app, env) ?? null,
|
|
51
|
+
profile_lock: env[envName(app, "PROFILE_LOCK")]?.trim() || null,
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
const settings = configuration.resolveSettings({}, { env, readOnly: true, needsProfile: false, withIssues: true });
|
|
55
|
+
const { files, fixes } = configFiles(settings.configPath);
|
|
56
|
+
return {
|
|
57
|
+
report: {
|
|
58
|
+
...common,
|
|
59
|
+
config: {
|
|
60
|
+
path: settings.configPath,
|
|
61
|
+
found: settings.configFound,
|
|
62
|
+
issues: [
|
|
63
|
+
...(settings.configIssues ?? []),
|
|
64
|
+
...unknownPermissionIssues(command, settings, app.command, configuration.permissions),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
profiles: settings.configuredProfiles,
|
|
68
|
+
files,
|
|
69
|
+
},
|
|
70
|
+
fixes,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const file = configFilePath(paths.config);
|
|
75
|
+
const { files, fixes } = configFiles(file);
|
|
76
|
+
return {
|
|
77
|
+
report: {
|
|
78
|
+
...common,
|
|
79
|
+
config: { path: file, found: existsSync(file), issues: [] },
|
|
80
|
+
settings: { error: failure(error) },
|
|
81
|
+
profiles: null,
|
|
82
|
+
files,
|
|
83
|
+
},
|
|
84
|
+
fixes,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/** `doctor` for a CLI that is not a messenger: gcal, drive, zoom. A messenger's is `doctorCommand(messenger)`. */
|
|
89
|
+
export const appDoctorCommand = (app, configuration, options = {}) => profileFree(annotate(new Command("doctor"), {
|
|
90
|
+
effects: LOCAL_READ,
|
|
91
|
+
examples: [`${app.command} doctor`, `${app.command} doctor --json`],
|
|
92
|
+
}))
|
|
93
|
+
.description("check this installation: version, paths, config, profiles, store; no keyring or network")
|
|
94
|
+
.action(function () {
|
|
95
|
+
const env = environmentOf(this).env ?? process.env;
|
|
96
|
+
const { report, fixes } = appDoctorReport(app, configuration, env, this, options);
|
|
97
|
+
const output = outputFor(this);
|
|
98
|
+
renderAnswer(this, output, itemOf(report));
|
|
99
|
+
for (const fix of fixes)
|
|
100
|
+
output.renderer.note(`to make it private: ${fix}`);
|
|
101
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { AppServerOptions } from "../mcp/app-server.js";
|
|
3
|
+
import type { AppIdentity } from "../ports/app.js";
|
|
4
|
+
import type { Configuration } from "./settings.js";
|
|
5
|
+
export interface AppMcpOptions {
|
|
6
|
+
/** `Google Calendar`: what the instructions call the service. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** The kinds of text other people wrote that the answers carry: `event titles, descriptions, attendee names`. */
|
|
9
|
+
untrusted: AppServerOptions["untrusted"];
|
|
10
|
+
/** One of the CLI's permission keys for `--permission`'s help: `calendar.events.delete=deny`. */
|
|
11
|
+
permissionExample: string;
|
|
12
|
+
skill?: URL;
|
|
13
|
+
/** What the CLI alone knows stops a command for a profile, such as an OAuth grant without the scope. */
|
|
14
|
+
availability?: AppServerOptions["availability"];
|
|
15
|
+
/** Whether the profile can reach the service, read from disk; left out, `mcp check` does not say. */
|
|
16
|
+
signedIn?: (profile: string, env: NodeJS.ProcessEnv) => boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `mcp serve | revoke | server | config | add | check` for a CLI that is not a messenger — the same commands, flags
|
|
20
|
+
* and answers as a messenger's `mcpCommand`, with `--profiles` and `--permission`, served by the CLI's own program
|
|
21
|
+
* (`createAppServer`). gcal, drive and zoom mount this instead of an `mcp` of their own.
|
|
22
|
+
*/
|
|
23
|
+
export declare const appMcpCommand: (app: AppIdentity, configuration: Configuration, options: AppMcpOptions) => Command;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { baseContext } from "./context.js";
|
|
2
|
+
import { mcpGroup } from "./messenger/mcp-command.js";
|
|
3
|
+
import { refuseUnknownPermissionKeys } from "./permission-keys.js";
|
|
4
|
+
/**
|
|
5
|
+
* `mcp serve | revoke | server | config | add | check` for a CLI that is not a messenger — the same commands, flags
|
|
6
|
+
* and answers as a messenger's `mcpCommand`, with `--profiles` and `--permission`, served by the CLI's own program
|
|
7
|
+
* (`createAppServer`). gcal, drive and zoom mount this instead of an `mcp` of their own.
|
|
8
|
+
*/
|
|
9
|
+
export const appMcpCommand = (app, configuration, options) => {
|
|
10
|
+
const server = () => ({
|
|
11
|
+
app,
|
|
12
|
+
name: options.name,
|
|
13
|
+
untrusted: options.untrusted,
|
|
14
|
+
resolveSettings: configuration.resolveSettings,
|
|
15
|
+
permissions: configuration.permissions,
|
|
16
|
+
...(options.skill ? { skill: options.skill } : {}),
|
|
17
|
+
...(options.availability ? { availability: options.availability } : {}),
|
|
18
|
+
});
|
|
19
|
+
// `mcp serve` has no key of its own, so the shared check before a run never looks at the keys --permission names.
|
|
20
|
+
const checked = (self, context) => refuseUnknownPermissionKeys(self, context.settings, app.command, configuration.permissions);
|
|
21
|
+
const host = {
|
|
22
|
+
app,
|
|
23
|
+
name: options.name,
|
|
24
|
+
resolveSettings: configuration.resolveSettings,
|
|
25
|
+
context: (self, given) => baseContext(self, (flags, resolve) => configuration.resolveSettings(flags, { ...resolve, needsProfile: given?.needsProfile ?? true })),
|
|
26
|
+
permissionExample: options.permissionExample,
|
|
27
|
+
serveStdio: async (self, context) => {
|
|
28
|
+
checked(self, context);
|
|
29
|
+
const { serveAppOverStdio } = await import("../mcp/app-server.js");
|
|
30
|
+
await serveAppOverStdio(self, context, server());
|
|
31
|
+
},
|
|
32
|
+
serveHttp: async (self, context, http) => {
|
|
33
|
+
checked(self, context);
|
|
34
|
+
const { serveAppOverHttp } = await import("../mcp/app-server.js");
|
|
35
|
+
await serveAppOverHttp(self, context, server(), http);
|
|
36
|
+
},
|
|
37
|
+
instructions: async (profiles) => {
|
|
38
|
+
const { appInstructions } = await import("../mcp/instructions.js");
|
|
39
|
+
return appInstructions({
|
|
40
|
+
command: app.command,
|
|
41
|
+
name: options.name,
|
|
42
|
+
profile: profiles,
|
|
43
|
+
untrusted: options.untrusted,
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
signedIn: (_self, profile, env) => options.signedIn?.(profile, env),
|
|
47
|
+
};
|
|
48
|
+
return mcpGroup(host);
|
|
49
|
+
};
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export { TASK_PAIR_QUERY, TASK_QUERY } from "../services/tasks.js";
|
|
|
40
40
|
export { answerStdinOption, type Need, type SignInSteps, type SignInStepsOptions, signInSteps, } from "../terminal/sign-in-steps.js";
|
|
41
41
|
export { type AgentPolicy, type AgentTask, type EvaluationResult, evaluateAgent, type Observation, } from "./agent-evaluation.js";
|
|
42
42
|
export { answerEntity } from "./answer-entity.js";
|
|
43
|
+
export { type AppDoctorOptions, appDoctorReport } from "./app-doctor-command.js";
|
|
44
|
+
export type { AppMcpOptions } from "./app-mcp-command.js";
|
|
43
45
|
export type { ApiCommandInput, ApiCommands } from "./bot/api.js";
|
|
44
46
|
export { apiFlagOf, apiJson, apiOptionKey, apiPlainJson, checkApiBody, checkApiParameter, parseApiJson, readApiBody, } from "./bot/api-input.js";
|
|
45
47
|
export { prepareRpcApiBody, type RpcApiBody } from "./bot/api-rpc.js";
|
|
@@ -90,7 +92,7 @@ export { terminalAsker } from "./messenger/ask.js";
|
|
|
90
92
|
export type { CompletionOptions } from "./messenger/complete-command.js";
|
|
91
93
|
export { type ConnectOptions, connectFor, type Messenger, type MessengerContext, messengerContext, type ProfileState, type ReadConnection, type StoredMessages, } from "./messenger/context.js";
|
|
92
94
|
export { COMMAND_NEEDS } from "./messenger/lacks.js";
|
|
93
|
-
export { httpTokenFile, type McpEnvironment, serverEntry } from "./messenger/mcp-command.js";
|
|
95
|
+
export { httpTokenFile, type McpEnvironment, type McpHost, serverEntry } from "./messenger/mcp-command.js";
|
|
94
96
|
export { type MessageItem, messageItem, type ReadMessage } from "./messenger/message-items.js";
|
|
95
97
|
export { type ModeProblem, type PrivateFiles, privateFiles, withSqliteSidecars } from "./messenger/private-files.js";
|
|
96
98
|
export { buildReport as buildDoctorReport, type Report as DoctorReport, type ReportContext as DoctorReportContext, reportFileName as doctorReportFileName, } from "./messenger/report.js";
|
|
@@ -111,7 +113,7 @@ export { fieldsOf, projectFields } from "./result-fields.js";
|
|
|
111
113
|
export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
|
|
112
114
|
export { type PermissionAnswer, readSetupAnswers, type SetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
|
113
115
|
export { AGENTS, type Agent, agentFor, screenFor } from "./setup-screen.js";
|
|
114
|
-
export { accountCommand, addMeetingBatchCommand, addMeetingPreparationCommand, addMeetingProjectCandidatesCommand, addMeetingSeriesInventoryCommand, attachmentsCommand, botAdminsCommand, botCommand, botMcpCommand, botMembersCommand, botMessagesCommand, callsCommand, chatsCommand, commandsCommand, completeCommand, configCommand, contactsCommand, conversationsCommand, deleteCommand, doctorCommand, editCommand, floodCommand, forwardCommand, generatedApiCommand, guideCommand, imagesCommand, inboxCommand, markReadCommand, mcpCommand, mentionsCommand, messagesCommand, metadataCommand, modelCatalogueCommand, modelsCommand, pinCommand, pollsCommand, reactionsCommand, recipientsCommand, repliesCommand, reviewCommand, runsCommand, scheduleCommand, searchCommand, searchesCommand, sendCommand, sendsCommand, serveCommand, serverCommand, skillCommand, starCommands, statsCommand, stickersCommand, storeCommand, tagsCommand, tasksCommand, tasksStatsCommand, topicsCommand, unpinCommand, upgradeCommand, watchCommand, } from "./shared-command-exports.js";
|
|
116
|
+
export { accountCommand, addMeetingBatchCommand, addMeetingPreparationCommand, addMeetingProjectCandidatesCommand, addMeetingSeriesInventoryCommand, appDoctorCommand, appMcpCommand, attachmentsCommand, botAdminsCommand, botCommand, botMcpCommand, botMembersCommand, botMessagesCommand, callsCommand, chatsCommand, commandsCommand, completeCommand, configCommand, contactsCommand, conversationsCommand, deleteCommand, doctorCommand, editCommand, floodCommand, forwardCommand, generatedApiCommand, guideCommand, imagesCommand, inboxCommand, markReadCommand, mcpCommand, mentionsCommand, messagesCommand, metadataCommand, modelCatalogueCommand, modelsCommand, pinCommand, pollsCommand, reactionsCommand, recipientsCommand, repliesCommand, reviewCommand, runsCommand, scheduleCommand, searchCommand, searchesCommand, sendCommand, sendsCommand, serveCommand, serverCommand, skillCommand, starCommands, statsCommand, stickersCommand, storeCommand, tagsCommand, tasksCommand, tasksStatsCommand, topicsCommand, unpinCommand, upgradeCommand, watchCommand, } from "./shared-command-exports.js";
|
|
115
117
|
export { assertSharedContracts, customizeSharedCommand, hasSharedContract, overrideSharedContract, type SharedContractOverride, sharedContractOverrides, sharedContracts, } from "./shared-contracts.js";
|
|
116
118
|
export { runnable, withSafety } from "./steps.js";
|
|
117
119
|
export { type StoreInfo, sharedStoreCommand, storeInfo, storeInfoCommand } from "./store-info.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -35,6 +35,7 @@ export { TASK_PAIR_QUERY, TASK_QUERY } from "../services/tasks.js";
|
|
|
35
35
|
export { answerStdinOption, signInSteps, } from "../terminal/sign-in-steps.js";
|
|
36
36
|
export { evaluateAgent, } from "./agent-evaluation.js";
|
|
37
37
|
export { answerEntity } from "./answer-entity.js";
|
|
38
|
+
export { appDoctorReport } from "./app-doctor-command.js";
|
|
38
39
|
export { apiFlagOf, apiJson, apiOptionKey, apiPlainJson, checkApiBody, checkApiParameter, parseApiJson, readApiBody, } from "./bot/api-input.js";
|
|
39
40
|
export { prepareRpcApiBody } from "./bot/api-rpc.js";
|
|
40
41
|
export { botContext, botWords } from "./bot/context.js";
|
|
@@ -102,7 +103,7 @@ export { fieldsOf, projectFields } from "./result-fields.js";
|
|
|
102
103
|
export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
|
|
103
104
|
export { readSetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
|
104
105
|
export { AGENTS, agentFor, screenFor } from "./setup-screen.js";
|
|
105
|
-
export { accountCommand, addMeetingBatchCommand, addMeetingPreparationCommand, addMeetingProjectCandidatesCommand, addMeetingSeriesInventoryCommand, attachmentsCommand, botAdminsCommand, botCommand, botMcpCommand, botMembersCommand, botMessagesCommand, callsCommand, chatsCommand, commandsCommand, completeCommand, configCommand, contactsCommand, conversationsCommand, deleteCommand, doctorCommand, editCommand, floodCommand, forwardCommand, generatedApiCommand, guideCommand, imagesCommand, inboxCommand, markReadCommand, mcpCommand, mentionsCommand, messagesCommand, metadataCommand, modelCatalogueCommand, modelsCommand, pinCommand, pollsCommand, reactionsCommand, recipientsCommand, repliesCommand, reviewCommand, runsCommand, scheduleCommand, searchCommand, searchesCommand, sendCommand, sendsCommand, serveCommand, serverCommand, skillCommand, starCommands, statsCommand, stickersCommand, storeCommand, tagsCommand, tasksCommand, tasksStatsCommand, topicsCommand, unpinCommand, upgradeCommand, watchCommand, } from "./shared-command-exports.js";
|
|
106
|
+
export { accountCommand, addMeetingBatchCommand, addMeetingPreparationCommand, addMeetingProjectCandidatesCommand, addMeetingSeriesInventoryCommand, appDoctorCommand, appMcpCommand, attachmentsCommand, botAdminsCommand, botCommand, botMcpCommand, botMembersCommand, botMessagesCommand, callsCommand, chatsCommand, commandsCommand, completeCommand, configCommand, contactsCommand, conversationsCommand, deleteCommand, doctorCommand, editCommand, floodCommand, forwardCommand, generatedApiCommand, guideCommand, imagesCommand, inboxCommand, markReadCommand, mcpCommand, mentionsCommand, messagesCommand, metadataCommand, modelCatalogueCommand, modelsCommand, pinCommand, pollsCommand, reactionsCommand, recipientsCommand, repliesCommand, reviewCommand, runsCommand, scheduleCommand, searchCommand, searchesCommand, sendCommand, sendsCommand, serveCommand, serverCommand, skillCommand, starCommands, statsCommand, stickersCommand, storeCommand, tagsCommand, tasksCommand, tasksStatsCommand, topicsCommand, unpinCommand, upgradeCommand, watchCommand, } from "./shared-command-exports.js";
|
|
106
107
|
export { assertSharedContracts, customizeSharedCommand, hasSharedContract, overrideSharedContract, sharedContractOverrides, sharedContracts, } from "./shared-contracts.js";
|
|
107
108
|
export { runnable, withSafety } from "./steps.js";
|
|
108
109
|
export { sharedStoreCommand, storeInfo, storeInfoCommand } from "./store-info.js";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import type { ServerSystem } from "../../background/system.js";
|
|
4
|
+
import type { HttpOptions } from "../../mcp/http/serve.js";
|
|
4
5
|
import { type AppIdentity } from "../../ports/app.js";
|
|
5
|
-
import { type BaseEnvironment } from "../context.js";
|
|
6
|
-
import { type Settings } from "../settings.js";
|
|
6
|
+
import { type BaseContext, type BaseEnvironment } from "../context.js";
|
|
7
|
+
import { type GlobalFlags, type ResolveOptions, type Settings } from "../settings.js";
|
|
7
8
|
import { type Messenger } from "./context.js";
|
|
8
9
|
/** Where the running CLI really lives; a test hands in its own. */
|
|
9
10
|
export interface McpEnvironment extends BaseEnvironment {
|
|
@@ -38,7 +39,7 @@ export interface ProfileSet {
|
|
|
38
39
|
* takes the place of `<PREFIX>_PROFILE`; under `<PREFIX>_PROFILE_LOCK` it may name the locked profile only
|
|
39
40
|
* (WC-ACCT-5).
|
|
40
41
|
*/
|
|
41
|
-
export declare const servedProfiles: (self: Command, messenger: Messenger, { settings, env }: {
|
|
42
|
+
export declare const servedProfiles: (self: Command, messenger: Pick<Messenger, "app" | "resolveSettings">, { settings, env }: {
|
|
42
43
|
settings: Pick<Settings, "profile" | "sources" | "profilesHere">;
|
|
43
44
|
env: NodeJS.ProcessEnv;
|
|
44
45
|
}, given: string | undefined, words: string) => {
|
|
@@ -48,7 +49,32 @@ export declare const servedProfiles: (self: Command, messenger: Messenger, { set
|
|
|
48
49
|
};
|
|
49
50
|
/** Where `mcp serve --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
|
|
50
51
|
export declare const httpTokenFile: (app: AppIdentity, profile: string, env: NodeJS.ProcessEnv) => string;
|
|
52
|
+
/**
|
|
53
|
+
* What the `mcp` group needs of the CLI it serves. A messenger serves through its one connection (`messengerHost`);
|
|
54
|
+
* a CLI that is not a messenger through its own program (`appMcpCommand`). The commands, flags and answers are the
|
|
55
|
+
* same in both, so every WireCat CLI has one `mcp`.
|
|
56
|
+
*/
|
|
57
|
+
export interface McpHost {
|
|
58
|
+
app: AppIdentity;
|
|
59
|
+
/** `Telegram`, `Google Calendar`: what the instructions call the service. */
|
|
60
|
+
name: string;
|
|
61
|
+
resolveSettings: (flags: GlobalFlags, options?: ResolveOptions) => Settings;
|
|
62
|
+
context: (self: Command, options?: {
|
|
63
|
+
needsProfile?: boolean;
|
|
64
|
+
}) => BaseContext;
|
|
65
|
+
/** A permission key of this CLI for `--permission`'s help: `messages.send=deny`. */
|
|
66
|
+
permissionExample: string;
|
|
67
|
+
serveStdio: (self: Command, context: BaseContext) => Promise<void>;
|
|
68
|
+
serveHttp: (self: Command, context: BaseContext, http: Omit<HttpOptions, "onCode" | "onError" | "appName">) => Promise<void>;
|
|
69
|
+
/** The instructions of the server of several profiles, whose tools each take `profile`. */
|
|
70
|
+
instructions: (profiles: readonly string[]) => string | Promise<string>;
|
|
71
|
+
/** Whether the profile can reach the service; `undefined` where the CLI cannot tell without connecting. */
|
|
72
|
+
signedIn: (self: Command, profile: string, env: NodeJS.ProcessEnv) => boolean | undefined;
|
|
73
|
+
/** A profile that is a bot, or not signed in here; the server of several profiles leaves bots out. */
|
|
74
|
+
profileState?: Messenger["profileState"];
|
|
75
|
+
}
|
|
51
76
|
export declare const mcpCommand: (messenger: Messenger) => Command;
|
|
77
|
+
export declare const mcpGroup: (host: McpHost) => Command;
|
|
52
78
|
/** How the startup and status lines name a set, so the owner sees which accounts an agent reaches. */
|
|
53
79
|
export declare const profileSetLabel: (set: ProfileSet) => string;
|
|
54
80
|
/**
|
|
@@ -66,13 +66,42 @@ export const servedProfiles = (self, messenger, { settings, env }, given, words)
|
|
|
66
66
|
const withProfiles = (command) => command.option("--profiles <names|all>", "serve several profiles from one server instead of one: names, comma-separated, or all for every profile");
|
|
67
67
|
/** Where `mcp serve --http` keeps the hashes of the tokens it issued — per profile, in the state folder. */
|
|
68
68
|
export const httpTokenFile = (app, profile, env) => join(resolvePaths({ appName: app.appName, prefix: app.envPrefix, env }).state, "mcp-http", `${profile}.json`);
|
|
69
|
-
const withFlags = (command) => command.option("--permission <key=level>",
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const withFlags = (command, example) => command.option("--permission <key=level>", `make a permission stricter for this server only, like ${example}; it never loosens the profile; repeat for more keys`, (value, previous = []) => [...previous, value]);
|
|
70
|
+
const messengerHost = (messenger) => ({
|
|
71
|
+
app: messenger.app,
|
|
72
|
+
name: messenger.name ?? messenger.app.command,
|
|
73
|
+
resolveSettings: messenger.resolveSettings,
|
|
74
|
+
...(messenger.profileState ? { profileState: messenger.profileState } : {}),
|
|
75
|
+
context: (self, options) => messengerContext(self, messenger, options),
|
|
76
|
+
permissionExample: "messages.send=deny",
|
|
77
|
+
serveStdio: async (self, context) => {
|
|
78
|
+
// Loaded here, not at the top: every other command would otherwise pay for the SDK.
|
|
79
|
+
const { serveOverStdio } = await import("../../mcp/server.js");
|
|
80
|
+
await serveOverStdio(self, context, messenger, {});
|
|
81
|
+
},
|
|
82
|
+
serveHttp: async (self, context, http) => {
|
|
83
|
+
const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
|
|
84
|
+
await serveOverHttpUntilStopped(self, context, messenger, {}, http);
|
|
85
|
+
},
|
|
86
|
+
instructions: async (profiles) => {
|
|
87
|
+
const { instructions } = await import("../../mcp/instructions.js");
|
|
88
|
+
return instructions({
|
|
89
|
+
command: messenger.app.command,
|
|
90
|
+
name: messenger.name ?? messenger.app.command,
|
|
91
|
+
profile: profiles,
|
|
92
|
+
writes: [],
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
signedIn: (self, profile, env) => (messenger.accountOf?.(self) ?? recalledAccount(messenger.app, messenger.provider, profile, env)) !== undefined,
|
|
96
|
+
});
|
|
97
|
+
export const mcpCommand = (messenger) => mcpGroup(messengerHost(messenger));
|
|
98
|
+
export const mcpGroup = (host) => {
|
|
99
|
+
const { app } = host;
|
|
100
|
+
const flagged = (command) => withFlags(command, host.permissionExample);
|
|
72
101
|
const command = annotate(new Command("mcp"), { effects: LOCAL_READ, mcp: false })
|
|
73
102
|
.summary("serve this profile's commands to an AI agent, and set that up")
|
|
74
103
|
.description(`serve this profile to an agent over MCP and set it up — \`claude mcp add ${app.command} -- ${app.command} <profile> mcp serve\``);
|
|
75
|
-
command.addCommand(withProfiles(
|
|
104
|
+
command.addCommand(withProfiles(flagged(annotate(new Command("serve"), {
|
|
76
105
|
effects: REMOTE_WRITE,
|
|
77
106
|
lifetime: "persistent",
|
|
78
107
|
mcp: false,
|
|
@@ -89,10 +118,10 @@ export const mcpCommand = (messenger) => {
|
|
|
89
118
|
.option("--public-url <url>", "the tunnel's https address the browser apps use, e.g. https://<name>.ts.net or https://<name>.ts.net/tg")
|
|
90
119
|
.action(async function () {
|
|
91
120
|
const flags = this.optsWithGlobals();
|
|
92
|
-
const context =
|
|
93
|
-
const served = servedProfiles(this,
|
|
121
|
+
const context = host.context(this, { needsProfile: false });
|
|
122
|
+
const served = servedProfiles(this, host, context, flags.profiles, "mcp serve");
|
|
94
123
|
if ("set" in served)
|
|
95
|
-
return serveProfiles(this, context,
|
|
124
|
+
return serveProfiles(this, context, host, flags, served.set);
|
|
96
125
|
const { profile } = served;
|
|
97
126
|
if (flags.http) {
|
|
98
127
|
const publicUrl = publicUrlOf(flags.publicUrl, {
|
|
@@ -100,9 +129,8 @@ export const mcpCommand = (messenger) => {
|
|
|
100
129
|
reaches: `the browser apps reach ${app.command}`,
|
|
101
130
|
path: true,
|
|
102
131
|
});
|
|
103
|
-
const { serveOverHttpUntilStopped } = await import("../../mcp/server.js");
|
|
104
132
|
const tokenFile = httpTokenFile(app, profile, context.env);
|
|
105
|
-
await
|
|
133
|
+
await host.serveHttp(this, context, {
|
|
106
134
|
publicUrl,
|
|
107
135
|
port: portOf(flags.port, DEFAULT_PORT),
|
|
108
136
|
tokenFile,
|
|
@@ -111,9 +139,7 @@ export const mcpCommand = (messenger) => {
|
|
|
111
139
|
});
|
|
112
140
|
return;
|
|
113
141
|
}
|
|
114
|
-
|
|
115
|
-
const { serveOverStdio } = await import("../../mcp/server.js");
|
|
116
|
-
await serveOverStdio(this, context, messenger, {});
|
|
142
|
+
await host.serveStdio(this, context);
|
|
117
143
|
}));
|
|
118
144
|
command.addCommand(withProfiles(annotate(new Command("revoke"), {
|
|
119
145
|
effects: LOCAL_WRITE,
|
|
@@ -122,8 +148,8 @@ export const mcpCommand = (messenger) => {
|
|
|
122
148
|
})
|
|
123
149
|
.summary("forget every login given to a browser app")
|
|
124
150
|
.description("forget every login `mcp serve --http` gave a browser app, of this profile or of the server `--profiles` names; each app must log in again")).action(async function () {
|
|
125
|
-
const context =
|
|
126
|
-
const served = servedProfiles(this,
|
|
151
|
+
const context = host.context(this, { needsProfile: false });
|
|
152
|
+
const served = servedProfiles(this, host, context, this.opts().profiles, "mcp revoke");
|
|
127
153
|
const { revokeAll } = await import("../../mcp/http/oauth.js");
|
|
128
154
|
revokeAll(httpTokenFile(app, "set" in served ? served.set.key : served.profile, context.env));
|
|
129
155
|
renderAnswer(this, context, itemOf("set" in served
|
|
@@ -135,9 +161,9 @@ export const mcpCommand = (messenger) => {
|
|
|
135
161
|
permission: true,
|
|
136
162
|
profiles: true,
|
|
137
163
|
target: (self) => {
|
|
138
|
-
const context =
|
|
164
|
+
const context = host.context(self, { needsProfile: false });
|
|
139
165
|
const { env } = context;
|
|
140
|
-
const served = servedProfiles(self,
|
|
166
|
+
const served = servedProfiles(self, host, context, self.opts().profiles, `mcp server ${self.name()}`);
|
|
141
167
|
if ("set" in served) {
|
|
142
168
|
const { set } = served;
|
|
143
169
|
return {
|
|
@@ -162,20 +188,20 @@ export const mcpCommand = (messenger) => {
|
|
|
162
188
|
version: app.version,
|
|
163
189
|
};
|
|
164
190
|
},
|
|
165
|
-
answer: (self, item, lines) => renderAnswer(self,
|
|
191
|
+
answer: (self, item, lines) => renderAnswer(self, host.context(self, { needsProfile: false }), itemOf(item), () => lines.join("\n")),
|
|
166
192
|
system: (self) => environmentOf(self).system,
|
|
167
193
|
}));
|
|
168
|
-
command.addCommand(withProfiles(
|
|
194
|
+
command.addCommand(withProfiles(flagged(annotate(new Command("config"), {
|
|
169
195
|
effects: LOCAL_READ,
|
|
170
196
|
examples: [`${app.command} personal mcp config`, `${app.command} mcp config --profiles all`],
|
|
171
197
|
})
|
|
172
198
|
.summary("print the local MCP configuration of this profile, or of those --profiles names")
|
|
173
199
|
.description("print the mcpServers entry for Claude Desktop, Cursor and others, with full paths; writes nothing"))).action(async function () {
|
|
174
200
|
const flags = this.optsWithGlobals();
|
|
175
|
-
const context =
|
|
201
|
+
const context = host.context(this, { needsProfile: false });
|
|
176
202
|
const { env } = context;
|
|
177
203
|
const given = environmentOf(this).mcp ?? {};
|
|
178
|
-
const served = servedProfiles(this,
|
|
204
|
+
const served = servedProfiles(this, host, context, flags.profiles, "mcp config");
|
|
179
205
|
const entry = serverEntry(app, {
|
|
180
206
|
...("set" in served ? { profiles: served.set.option } : { profile: served.profile }),
|
|
181
207
|
flags,
|
|
@@ -190,7 +216,7 @@ export const mcpCommand = (messenger) => {
|
|
|
190
216
|
: [],
|
|
191
217
|
}), () => JSON.stringify(entry.config, null, 2));
|
|
192
218
|
}));
|
|
193
|
-
command.addCommand(
|
|
219
|
+
command.addCommand(flagged(grantsAuthority(annotate(new Command("add"), {
|
|
194
220
|
effects: LOCAL_WRITE,
|
|
195
221
|
examples: [`${app.command} personal mcp add codex --allow-writes`],
|
|
196
222
|
}))
|
|
@@ -199,7 +225,7 @@ export const mcpCommand = (messenger) => {
|
|
|
199
225
|
.option("--allow-writes", "acknowledge that this profile offers writing tools")).action(async function (client) {
|
|
200
226
|
if (client !== "codex" && client !== "claude-code")
|
|
201
227
|
throw new CliError("validation_error", "choose codex or claude-code");
|
|
202
|
-
const context =
|
|
228
|
+
const context = host.context(this);
|
|
203
229
|
const { settings, env } = context;
|
|
204
230
|
const given = environmentOf(this).mcp ?? {};
|
|
205
231
|
const configuration = serverEntry(app, {
|
|
@@ -224,11 +250,11 @@ export const mcpCommand = (messenger) => {
|
|
|
224
250
|
throw new CliError("validation_error", error instanceof Error ? error.message : "MCP setup failed");
|
|
225
251
|
}
|
|
226
252
|
}));
|
|
227
|
-
command.addCommand(
|
|
253
|
+
command.addCommand(flagged(annotate(new Command("check"), {
|
|
228
254
|
effects: LOCAL_READ,
|
|
229
255
|
examples: [`${app.command} personal mcp check`],
|
|
230
256
|
}).description("check this profile's local MCP handshake and tool list")).action(async function () {
|
|
231
|
-
const context =
|
|
257
|
+
const context = host.context(this);
|
|
232
258
|
const { settings, env } = context;
|
|
233
259
|
const given = environmentOf(this).mcp ?? {};
|
|
234
260
|
const configuration = serverEntry(app, {
|
|
@@ -247,8 +273,8 @@ export const mcpCommand = (messenger) => {
|
|
|
247
273
|
const { tools, potentialWrites } = await probeStdio(entry, {
|
|
248
274
|
start: stderr.start,
|
|
249
275
|
});
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
276
|
+
const signedIn = host.signedIn(this, settings.profile, env);
|
|
277
|
+
if (signedIn === false) {
|
|
252
278
|
const setup = rootOf(this).commands.some((one) => one.name() === "setup")
|
|
253
279
|
? ` — log in with \`${app.command} ${settings.profile} setup\``
|
|
254
280
|
: "";
|
|
@@ -256,9 +282,9 @@ export const mcpCommand = (messenger) => {
|
|
|
256
282
|
`tools will fail${setup}`);
|
|
257
283
|
}
|
|
258
284
|
renderAnswer(this, context, itemOf({
|
|
259
|
-
healthy:
|
|
285
|
+
healthy: signedIn !== false,
|
|
260
286
|
profile: settings.profile,
|
|
261
|
-
signed_in:
|
|
287
|
+
signed_in: signedIn ?? null,
|
|
262
288
|
tools: tools.length,
|
|
263
289
|
potential_writes: potentialWrites.length,
|
|
264
290
|
}));
|
|
@@ -282,11 +308,11 @@ export const profileSetLabel = (set) => {
|
|
|
282
308
|
* `<tool> <profile> mcp serve` (`src/mcp/all-profiles.ts`). Nothing picks a profile: each tool takes one, and the
|
|
283
309
|
* agent is told to ask.
|
|
284
310
|
*/
|
|
285
|
-
const serveProfiles = async (self, context,
|
|
286
|
-
const { app } =
|
|
311
|
+
const serveProfiles = async (self, context, host, flags, set) => {
|
|
312
|
+
const { app } = host;
|
|
287
313
|
const { env } = context;
|
|
288
314
|
const tokenFile = httpTokenFile(app, set.key, env);
|
|
289
|
-
const state = (profile) =>
|
|
315
|
+
const state = (profile) => host.profileState?.(profile, env) ?? "signed-in";
|
|
290
316
|
const bots = set.option === EVERY_PROFILE ? [] : set.profiles().filter((profile) => state(profile) === "bot");
|
|
291
317
|
if (bots.length > 0)
|
|
292
318
|
throw new CliError("validation_error", `${bots.join(", ")} ${bots.length === 1 ? "is a bot profile" : "are bot profiles"}, served by \`${app.command} <profile> bot mcp serve\``);
|
|
@@ -298,22 +324,16 @@ const serveProfiles = async (self, context, messenger, flags, set) => {
|
|
|
298
324
|
const execPath = given.execPath ?? process.execPath;
|
|
299
325
|
const scriptPath = given.scriptPath ?? realpathSync(process.argv[1] ?? "");
|
|
300
326
|
const tightened = (flags.permission ?? []).flatMap((entry) => ["--permission", entry]);
|
|
301
|
-
const { instructions } = await import("../../mcp/instructions.js");
|
|
302
327
|
const options = {
|
|
303
328
|
command: app.command,
|
|
304
329
|
version: app.version,
|
|
305
|
-
name:
|
|
330
|
+
name: host.name,
|
|
306
331
|
profiles,
|
|
307
332
|
signedIn: (profile) => state(profile) === "signed-in",
|
|
308
333
|
signIn: (profile) => `profile ${profile} is not signed in — run \`${app.command} ${profile} setup\`, or sign in from WireCat Desktop, ` +
|
|
309
334
|
"then try again. If the owner did not choose this profile, ask which one.",
|
|
310
335
|
child: (profile) => ({ command: execPath, args: [scriptPath, profile, "mcp", "serve", ...tightened], env }),
|
|
311
|
-
instructions: instructions(
|
|
312
|
-
command: app.command,
|
|
313
|
-
name: messenger.name ?? app.command,
|
|
314
|
-
profile: profiles(),
|
|
315
|
-
writes: [],
|
|
316
|
-
}),
|
|
336
|
+
instructions: await host.instructions(profiles()),
|
|
317
337
|
note: (line) => context.renderer.note(line),
|
|
318
338
|
};
|
|
319
339
|
if (flags.http) {
|