argsbarg 6.1.2 → 6.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -1
- package/README.md +17 -19
- package/bin/argsbarg +10 -0
- package/docs/README.md +4 -3
- package/docs/ai-skills.md +4 -2
- package/docs/bundled-docs.md +50 -25
- package/docs/cli-program.md +52 -10
- package/docs/config-schema.md +10 -11
- package/docs/configure.md +2 -0
- package/docs/decisions.md +40 -0
- package/docs/developing.md +43 -5
- package/docs/http-server.md +171 -0
- package/docs/json-schema-subset.md +51 -0
- package/docs/mcp.md +4 -2
- package/docs/output-schema.md +55 -62
- package/examples/formats.ts +6 -6
- package/examples/full-example/Formula/full-example.rb +35 -0
- package/examples/full-example/README.md +20 -21
- package/examples/full-example/docs/README.md +1 -1
- package/examples/full-example/docs/cli-schema.json +1790 -98
- package/examples/full-example/docs/cli.md +1990 -0
- package/examples/full-example/docs/http.md +28 -29
- package/examples/full-example/docs/mcp.md +8 -22
- package/examples/full-example/docs/openapi.json +783 -50
- package/examples/full-example/docs/skill.md +10 -10
- package/examples/full-example/justfile +11 -1
- package/examples/full-example/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
- package/examples/full-example/src/commands/render-json/__generated__/index.ts +5 -0
- package/examples/full-example/src/commands/render-json/command.test.ts +46 -0
- package/examples/full-example/src/commands/render-json/command.ts +30 -0
- package/examples/full-example/src/commands/render-json/types.ts +9 -0
- package/examples/full-example/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
- package/examples/full-example/src/commands/status/__generated__/index.ts +2 -2
- package/examples/full-example/src/commands/status/command.ts +5 -13
- package/examples/full-example/src/commands/status/types.ts +1 -14
- package/examples/full-example/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
- package/examples/full-example/src/commands/workspaces/__generated__/index.ts +5 -0
- package/examples/full-example/src/commands/workspaces/command.test.ts +58 -0
- package/examples/full-example/src/commands/workspaces/command.ts +94 -0
- package/examples/full-example/src/commands/workspaces/types.ts +6 -0
- package/examples/full-example/src/db/index.test.ts +86 -0
- package/examples/full-example/src/db/index.ts +101 -0
- package/examples/full-example/src/db/migrate.test.ts +35 -0
- package/examples/full-example/src/db/migrate.ts +69 -0
- package/examples/full-example/src/db/migrations/001_workspaces.sql +6 -0
- package/examples/full-example/src/db/tables/workspaces.ts +66 -0
- package/examples/full-example/src/program.ts +11 -36
- package/examples/full-example/src/types/argsbarg.d.ts +11 -0
- package/examples/full-example/src/types/md.d.ts +4 -0
- package/examples/full-example/tsconfig.json +5 -2
- package/examples/mcp-test.ts +1 -2
- package/examples/minimal.ts +1 -7
- package/examples/nested.ts +1 -2
- package/examples/option-required.ts +1 -1
- package/examples/servers.ts +4 -5
- package/index.d.ts +431 -136
- package/package.json +19 -2
- package/src/builtins/builtins.test.ts +7 -7
- package/src/builtins/completion-bash.ts +1 -1
- package/src/builtins/completion-fish.ts +1 -1
- package/src/builtins/completion-group.ts +4 -4
- package/src/builtins/completion-simulate-shared.ts +9 -0
- package/src/builtins/completion-zsh.ts +1 -1
- package/src/builtins/config.test.ts +3 -3
- package/src/builtins/config.ts +9 -9
- package/src/builtins/configure-copy.ts +2 -2
- package/src/builtins/configure.ts +4 -4
- package/src/builtins/dispatch.ts +19 -18
- package/src/builtins/export.ts +7 -5
- package/src/builtins/http.ts +68 -0
- package/src/builtins/mcp.ts +28 -4
- package/src/builtins/presentation.ts +6 -6
- package/src/builtins/registry.ts +6 -6
- package/src/builtins/scopes.ts +2 -2
- package/src/builtins/version.ts +1 -1
- package/src/cli-tool/full-example-capabilities.test.ts +10 -15
- package/src/cli-tool/main.ts +1 -1
- package/src/cli-tool/program.ts +3 -2
- package/src/cli-tool/prompt.ts +1 -1
- package/src/cli-tool/run-schemagen.ts +1 -3
- package/src/cli-tool/schemagen/cleanup.ts +6 -7
- package/src/cli-tool/schemagen/discover-schema-roots.ts +66 -120
- package/src/cli-tool/schemagen/index.ts +2 -2
- package/src/cli-tool/schemagen/names.ts +8 -13
- package/src/cli-tool/schemagen/run.ts +21 -28
- package/src/cli-tool/schemagen/schemagen.test.ts +136 -46
- package/src/config/bindings.test.ts +1 -1
- package/src/config/bindings.ts +1 -1
- package/src/config/bootstrap.test.ts +1 -1
- package/src/config/bootstrap.ts +36 -4
- package/src/config/context.test.ts +1 -1
- package/src/config/context.ts +1 -1
- package/src/config/entry.ts +1 -1
- package/src/config/file.test.ts +1 -1
- package/src/config/file.ts +3 -3
- package/src/config/manifest.ts +1 -1
- package/src/config/resolve.test.ts +1 -1
- package/src/config/resolve.ts +1 -1
- package/src/config/schema.ts +1 -1
- package/src/config/validate.ts +1 -1
- package/src/{install → configure/artifacts}/binary-placement.test.ts +1 -1
- package/src/{install → configure/artifacts}/binary-placement.ts +1 -1
- package/src/{install → configure/artifacts}/gh-release-update.ts +1 -1
- package/src/{install → configure/artifacts}/install-validate.test.ts +3 -3
- package/src/{install → configure/artifacts}/mcp-config.ts +1 -1
- package/src/{install → configure/artifacts}/mcp-opencode.test.ts +1 -1
- package/src/{install → configure/artifacts}/mcp-opencode.ts +1 -1
- package/src/{install → configure/artifacts}/paths.ts +5 -5
- package/src/configure/artifacts/plan.ts +24 -0
- package/src/{install → configure/artifacts}/status.test.ts +1 -1
- package/src/{install → configure/artifacts}/status.ts +2 -2
- package/src/{install → configure/artifacts}/target-base.ts +1 -1
- package/src/{install → configure/artifacts}/target-detect.ts +1 -1
- package/src/{install → configure/artifacts}/target-effective.ts +3 -9
- package/src/{install → configure/artifacts}/target-mcp-cli.ts +1 -1
- package/src/{install → configure/artifacts}/target-mcp-json.ts +1 -1
- package/src/{install → configure/artifacts}/target-plan-build.ts +2 -2
- package/src/{install → configure/artifacts}/target-registry.ts +2 -2
- package/src/{install → configure/artifacts}/target-scope.ts +3 -3
- package/src/{install → configure/artifacts}/target-skill.ts +1 -1
- package/src/{install → configure/artifacts}/target-types.ts +2 -2
- package/src/{install → configure/artifacts}/targets/app.ts +5 -5
- package/src/{install → configure/artifacts}/targets/chatgpt-mcp.ts +2 -2
- package/src/{install → configure/artifacts}/targets/claude-code-mcp.ts +2 -2
- package/src/{install → configure/artifacts}/targets/claude-desktop-mcp.ts +2 -2
- package/src/{install → configure/artifacts}/targets/claude-skill.ts +2 -2
- package/src/{install → configure/artifacts}/targets/codex-mcp.ts +2 -2
- package/src/{install → configure/artifacts}/targets/codex-skill.ts +2 -2
- package/src/{install → configure/artifacts}/targets/configure.ts +5 -5
- package/src/{install → configure/artifacts}/targets/cursor-mcp.ts +2 -2
- package/src/{install → configure/artifacts}/targets/cursor-skill.ts +2 -2
- package/src/{install → configure/artifacts}/targets/index.ts +1 -1
- package/src/{install → configure/artifacts}/targets/openclaw-mcp.ts +2 -2
- package/src/{install → configure/artifacts}/targets/openclaw-skill.ts +3 -3
- package/src/{install → configure/artifacts}/targets/opencode-mcp.ts +5 -5
- package/src/{install → configure/artifacts}/targets/opencode-skill.ts +3 -3
- package/src/{install → configure/artifacts}/targets.test.ts +1 -1
- package/src/{install → configure/artifacts}/uninstall.ts +1 -1
- package/src/configure/configure.test.ts +11 -11
- package/src/configure/index.ts +14 -14
- package/src/configure/prompt.ts +2 -2
- package/src/{context.ts → core/context.ts} +26 -20
- package/src/{json-leaf.test.ts → core/json-leaf.test.ts} +4 -4
- package/src/{leaf-inputs.test.ts → core/leaf-inputs.test.ts} +7 -7
- package/src/{leaf-inputs.ts → core/leaf-inputs.ts} +16 -12
- package/src/{parse.test.ts → core/parse.test.ts} +97 -109
- package/src/{parse.ts → core/parse.ts} +129 -31
- package/src/{schema.ts → core/schema.ts} +25 -13
- package/src/{types.ts → core/types.ts} +225 -35
- package/src/{validate.ts → core/validate.ts} +39 -29
- package/src/docs/builtin.ts +8 -19
- package/src/docs/{api-guide.test.ts → cli-guide.test.ts} +21 -21
- package/src/docs/{api-guide.ts → cli-guide.ts} +45 -16
- package/src/docs/docs.test.ts +76 -41
- package/src/docs/http-guide.ts +37 -34
- package/src/docs/mcp-guide.ts +12 -14
- package/src/docs/mcp-resources.test.ts +2 -3
- package/src/docs/mcp-resources.ts +6 -11
- package/src/docs/resolve.ts +22 -30
- package/src/docs/save.ts +3 -3
- package/src/exports/cli.ts +47 -0
- package/src/exports/headless.ts +13 -0
- package/src/exports/http.ts +6 -0
- package/src/exports/mcp.ts +6 -0
- package/src/{headless.test.ts → headless/routing.test.ts} +3 -3
- package/src/{headless.ts → headless/routing.ts} +3 -3
- package/src/headless/tool-call.ts +114 -46
- package/src/help.test.ts +152 -0
- package/src/help.ts +3 -3
- package/src/hooks/builtin.ts +20 -0
- package/src/hooks/run.ts +142 -0
- package/src/http/openapi.ts +182 -0
- package/src/http/readiness.ts +78 -0
- package/src/{api → http}/result.ts +16 -5
- package/src/http/routes.ts +329 -0
- package/src/http/server.ts +225 -0
- package/src/index.ts +36 -25
- package/src/log/ecs.test.ts +43 -0
- package/src/log/ecs.ts +59 -0
- package/src/log/emitter.ts +166 -0
- package/src/mcp/bundle.ts +2 -2
- package/src/mcp/claude.test.ts +1 -1
- package/src/mcp/claude.ts +4 -4
- package/src/{hidden-mcpb.test.ts → mcp/hidden-mcpb.test.ts} +10 -9
- package/src/mcp/result.ts +2 -2
- package/src/mcp/server.ts +54 -6
- package/src/mcp/tools.ts +9 -20
- package/src/{capabilities.ts → runtime/capabilities.ts} +11 -11
- package/src/{cli-errors.ts → runtime/cli-errors.ts} +4 -4
- package/src/{cli.ts → runtime/cli.ts} +159 -49
- package/src/runtime/exposure.ts +102 -0
- package/src/{invoke.test.ts → runtime/invoke.test.ts} +31 -7
- package/src/server/context.ts +25 -0
- package/src/server/overrides.ts +112 -0
- package/src/skill/generate.ts +8 -8
- package/src/skill/hint.ts +1 -1
- package/src/skill/install.ts +2 -2
- package/src/skill/naming.ts +1 -1
- package/src/{test-fixtures.ts → test/fixtures.ts} +3 -2
- package/src/{config.integration.test.ts → test/integration/config.test.ts} +8 -8
- package/src/{api.integration.test.ts → test/integration/http.test.ts} +170 -67
- package/src/{mcp.integration.test.ts → test/integration/mcp.test.ts} +11 -57
- package/docs/api-server.md +0 -141
- package/examples/full-example/docs/api.md +0 -511
- package/examples/full-example/src/commands/status/__generated__/outputSchema.json +0 -28
- package/examples/full-example/src/config/__generated__/configSchema.json +0 -40
- package/examples/full-example/src/config/__generated__/index.ts +0 -5
- package/examples/full-example/src/config/types.ts +0 -24
- package/src/api/openapi.ts +0 -117
- package/src/api/server.ts +0 -120
- package/src/builtins/api.ts +0 -38
- package/src/hidden.ts +0 -30
- package/src/install/plan.ts +0 -53
- /package/src/{install → configure/artifacts}/detect-installed.ts +0 -0
- /package/src/{install → configure/artifacts}/gh-release-update.test.ts +0 -0
- /package/src/{install → configure/artifacts}/mcp-codex.test.ts +0 -0
- /package/src/{install → configure/artifacts}/mcp-codex.ts +0 -0
- /package/src/{install → configure/artifacts}/mcp-openclaw.test.ts +0 -0
- /package/src/{install → configure/artifacts}/mcp-openclaw.ts +0 -0
- /package/src/{install → configure/artifacts}/normalize-uninstall.ts +0 -0
- /package/src/{install → configure/artifacts}/normalize.ts +0 -0
- /package/src/{install → configure/artifacts}/opts.ts +0 -0
- /package/src/{install → configure/artifacts}/shell.ts +0 -0
- /package/src/{formats.test.ts → core/formats.test.ts} +0 -0
- /package/src/{formats.ts → core/formats.ts} +0 -0
- /package/src/{respond.ts → core/respond.ts} +0 -0
- /package/src/{types.test.ts → core/types.test.ts} +0 -0
- /package/src/{api → http}/schema-deref.test.ts +0 -0
- /package/src/{api → http}/schema-deref.ts +0 -0
package/src/log/ecs.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Elastic Common Schema (ECS) JSON log line formatting for framework observability.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Severity label for ECS `log.level`. */
|
|
6
|
+
export type EcsLogLevel = "debug" | "info" | "warn" | "error";
|
|
7
|
+
|
|
8
|
+
/** Fields merged into every ECS log line. */
|
|
9
|
+
export interface EcsServiceFields {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Input for one ECS log event. */
|
|
15
|
+
export interface EcsLogEvent {
|
|
16
|
+
level: EcsLogLevel;
|
|
17
|
+
message: string;
|
|
18
|
+
action?: string;
|
|
19
|
+
labels?: Record<string, string | number | boolean>;
|
|
20
|
+
error?: unknown;
|
|
21
|
+
fields?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function errorFields(error: unknown): Record<string, unknown> {
|
|
25
|
+
if (error instanceof Error) {
|
|
26
|
+
return {
|
|
27
|
+
"error.message": error.message,
|
|
28
|
+
"error.type": error.name,
|
|
29
|
+
...(error.stack ? { "error.stack_trace": error.stack } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { "error.message": String(error) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Formats one ECS-compatible JSON log line (newline omitted). */
|
|
36
|
+
export function formatEcsLine(service: EcsServiceFields, event: EcsLogEvent): string {
|
|
37
|
+
const line: Record<string, unknown> = {
|
|
38
|
+
"@timestamp": new Date().toISOString(),
|
|
39
|
+
"log.level": event.level,
|
|
40
|
+
message: event.message,
|
|
41
|
+
"service.name": service.name,
|
|
42
|
+
"service.version": service.version,
|
|
43
|
+
};
|
|
44
|
+
if (event.action) {
|
|
45
|
+
line["event.action"] = event.action;
|
|
46
|
+
}
|
|
47
|
+
if (event.labels) {
|
|
48
|
+
for (const [key, value] of Object.entries(event.labels)) {
|
|
49
|
+
line[`labels.${key}`] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (event.fields) {
|
|
53
|
+
Object.assign(line, event.fields);
|
|
54
|
+
}
|
|
55
|
+
if (event.error !== undefined) {
|
|
56
|
+
Object.assign(line, errorFields(event.error));
|
|
57
|
+
}
|
|
58
|
+
return JSON.stringify(line);
|
|
59
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Framework log emitter: ECS json or human text to stderr with optional file tee.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import type { CliProgram } from "~/core/types.ts";
|
|
8
|
+
import { type EcsLogEvent, type EcsLogLevel, formatEcsLine } from "./ecs.ts";
|
|
9
|
+
|
|
10
|
+
/** Resolved logging options for a server or invoke session. */
|
|
11
|
+
export interface ResolvedLogConfig {
|
|
12
|
+
format: "json" | "text";
|
|
13
|
+
file?: string;
|
|
14
|
+
access: boolean;
|
|
15
|
+
errors: boolean;
|
|
16
|
+
dev: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Options for {@link LogEmitter}. */
|
|
20
|
+
export interface LogEmitterOpts {
|
|
21
|
+
program: CliProgram;
|
|
22
|
+
resolved: ResolvedLogConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const OBSCURE_CLIENT_MESSAGE = "An unexpected error occurred.";
|
|
26
|
+
|
|
27
|
+
/** Fixed client message when `obscureUnexpected` hides internal failures. */
|
|
28
|
+
export function obscureUnexpectedClientMessage(): string {
|
|
29
|
+
return OBSCURE_CLIENT_MESSAGE;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Merges program defaults with CLI flag / serve overrides. */
|
|
33
|
+
export function resolveLogConfig(program: CliProgram, overrides: Partial<ResolvedLogConfig> = {}): ResolvedLogConfig {
|
|
34
|
+
const log = program.log;
|
|
35
|
+
return {
|
|
36
|
+
format: overrides.format ?? log?.format ?? "json",
|
|
37
|
+
file: overrides.file ?? log?.file,
|
|
38
|
+
access: overrides.access ?? log?.access ?? true,
|
|
39
|
+
errors: overrides.errors ?? log?.errors ?? true,
|
|
40
|
+
dev: overrides.dev ?? false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Tee framework logs to stderr and an optional append-only file. */
|
|
45
|
+
export class LogEmitter {
|
|
46
|
+
private readonly service: { name: string; version: string };
|
|
47
|
+
private readonly resolved: ResolvedLogConfig;
|
|
48
|
+
|
|
49
|
+
constructor(opts: LogEmitterOpts) {
|
|
50
|
+
this.service = { name: opts.program.key, version: opts.program.version };
|
|
51
|
+
this.resolved = opts.resolved;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get config(): ResolvedLogConfig {
|
|
55
|
+
return this.resolved;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Emits one log event to stderr (and optional file). */
|
|
59
|
+
emit(event: EcsLogEvent): void {
|
|
60
|
+
const line = this.formatLine(event);
|
|
61
|
+
process.stderr.write(`${line}\n`);
|
|
62
|
+
this.appendFile(line);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Human startup line or ECS/json event for lifecycle milestones. */
|
|
66
|
+
emitLifecycle(message: string, action: string, labels?: Record<string, string | number | boolean>): void {
|
|
67
|
+
if (this.resolved.format === "text") {
|
|
68
|
+
process.stderr.write(`${message}\n`);
|
|
69
|
+
this.appendFile(message);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.emit({ level: "info", message, action, labels });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Access log for one HTTP request or MCP RPC. */
|
|
76
|
+
emitAccess(fields: {
|
|
77
|
+
method: string;
|
|
78
|
+
path: string;
|
|
79
|
+
status: number;
|
|
80
|
+
durationMs: number;
|
|
81
|
+
requestId?: string;
|
|
82
|
+
clientIp?: string;
|
|
83
|
+
}): void {
|
|
84
|
+
if (!this.resolved.access) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.resolved.format === "text") {
|
|
88
|
+
const rid = fields.requestId ? ` ${fields.requestId}` : "";
|
|
89
|
+
const line = `${fields.method} ${fields.path} ${fields.status} ${fields.durationMs}ms${rid}`;
|
|
90
|
+
process.stderr.write(`${line}\n`);
|
|
91
|
+
this.appendFile(line);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.emit({
|
|
95
|
+
level: "info",
|
|
96
|
+
message: `${fields.method} ${fields.path}`,
|
|
97
|
+
action: "http.access",
|
|
98
|
+
labels: {
|
|
99
|
+
...(fields.requestId ? { request_id: fields.requestId } : {}),
|
|
100
|
+
...(fields.clientIp ? { client_ip: fields.clientIp } : {}),
|
|
101
|
+
http_method: fields.method,
|
|
102
|
+
http_path: fields.path,
|
|
103
|
+
http_status: fields.status,
|
|
104
|
+
duration_ms: fields.durationMs,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Error log after the hook pipeline (real stack always included). */
|
|
110
|
+
emitInvokeError(
|
|
111
|
+
failureKind: string,
|
|
112
|
+
error: unknown,
|
|
113
|
+
clientMessage: string,
|
|
114
|
+
labels?: Record<string, string | number | boolean>,
|
|
115
|
+
): void {
|
|
116
|
+
if (!this.resolved.errors) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.emit({
|
|
120
|
+
level: failureKind === "unexpected" ? "error" : "warn",
|
|
121
|
+
message: clientMessage,
|
|
122
|
+
action: "invoke.error",
|
|
123
|
+
labels: { failure_kind: failureKind, ...labels },
|
|
124
|
+
error,
|
|
125
|
+
});
|
|
126
|
+
if (this.resolved.dev && error instanceof Error && error.stack) {
|
|
127
|
+
process.stderr.write(`${error.stack}\n`);
|
|
128
|
+
this.appendFile(error.stack);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private formatLine(event: EcsLogEvent): string {
|
|
133
|
+
if (this.resolved.format === "text") {
|
|
134
|
+
return this.formatTextLine(event);
|
|
135
|
+
}
|
|
136
|
+
return formatEcsLine(this.service, event);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private formatTextLine(event: EcsLogEvent): string {
|
|
140
|
+
const level = event.level.toUpperCase();
|
|
141
|
+
const action = event.action ? ` [${event.action}]` : "";
|
|
142
|
+
let line = `${level}${action}: ${event.message}`;
|
|
143
|
+
if (event.error instanceof Error && event.error.stack) {
|
|
144
|
+
line = `${line}\n${event.error.stack}`;
|
|
145
|
+
}
|
|
146
|
+
return line;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private appendFile(line: string): void {
|
|
150
|
+
const file = this.resolved.file;
|
|
151
|
+
if (!file) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
156
|
+
appendFileSync(file, `${line}\n`, "utf8");
|
|
157
|
+
} catch {
|
|
158
|
+
// Best-effort file tee; stderr already has the line.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Maps ECS level strings for quick call sites. */
|
|
164
|
+
export function ecsLevel(level: EcsLogLevel): EcsLogLevel {
|
|
165
|
+
return level;
|
|
166
|
+
}
|
package/src/mcp/bundle.ts
CHANGED
|
@@ -6,8 +6,8 @@ Expects `dist/<program.key>` as the compiled binary input.
|
|
|
6
6
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { basename, join, resolve } from "node:path";
|
|
9
|
-
import { buildProgramUserConfig } from "
|
|
10
|
-
import type { CliMcpBundleConfig, CliProgram } from "
|
|
9
|
+
import { buildProgramUserConfig } from "~/config/manifest.ts";
|
|
10
|
+
import type { CliMcpBundleConfig, CliProgram } from "~/core/types.ts";
|
|
11
11
|
import { packClaudePlugin } from "./claude.ts";
|
|
12
12
|
import { collectMcpTools, mcpServerId } from "./tools.ts";
|
|
13
13
|
import { zipStore } from "./zip.ts";
|
package/src/mcp/claude.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { execSync } from "node:child_process";
|
|
|
7
7
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import type { CliProgram } from "
|
|
10
|
+
import type { CliProgram } from "~/core/types.ts";
|
|
11
11
|
import {
|
|
12
12
|
defaultClaudePluginPaths,
|
|
13
13
|
generatePluginManifest,
|
package/src/mcp/claude.ts
CHANGED
|
@@ -17,10 +17,10 @@ import {
|
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
19
|
import { basename, join, relative, resolve } from "node:path";
|
|
20
|
-
import { buildPluginMcpEnvMapping, buildProgramUserConfig } from "
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
20
|
+
import { buildPluginMcpEnvMapping, buildProgramUserConfig } from "~/config/manifest.ts";
|
|
21
|
+
import type { CliMcpBundleConfig, CliProgram } from "~/core/types.ts";
|
|
22
|
+
import { generatePluginSkillBundle } from "~/skill/generate.ts";
|
|
23
|
+
import { applyPluginSkillHint } from "~/skill/hint.ts";
|
|
24
24
|
import { defaultMcpBundlePaths, type PackMcpBundleOpts } from "./bundle.ts";
|
|
25
25
|
import { mcpServerId } from "./tools.ts";
|
|
26
26
|
import { type ZipFileEntry, zipStore } from "./zip.ts";
|
|
@@ -6,13 +6,13 @@ import { describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { exportPresentationBuiltins } from "
|
|
10
|
-
import { cliParseRoot, cliPresentationRoot } from "
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
9
|
+
import { exportPresentationBuiltins } from "~/builtins/export.ts";
|
|
10
|
+
import { cliParseRoot, cliPresentationRoot } from "~/builtins/presentation.ts";
|
|
11
|
+
import { cliSchemaExport } from "~/core/schema.ts";
|
|
12
|
+
import { CliOptionKind, type CliProgram } from "~/core/types.ts";
|
|
13
|
+
import { cliHelpRender } from "~/help.ts";
|
|
14
|
+
import { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle, runMcpBundle } from "./bundle.ts";
|
|
15
|
+
import { collectMcpTools } from "./tools.ts";
|
|
16
16
|
|
|
17
17
|
const hiddenFixture: CliProgram = {
|
|
18
18
|
key: "myapp",
|
|
@@ -27,7 +27,8 @@ const hiddenFixture: CliProgram = {
|
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
key: "secret",
|
|
30
|
-
hidden: true,
|
|
30
|
+
cli: { hidden: true },
|
|
31
|
+
mcpTool: { hidden: true },
|
|
31
32
|
description: "Hidden command.",
|
|
32
33
|
handler: () => {},
|
|
33
34
|
},
|
|
@@ -42,7 +43,7 @@ const hiddenFixture: CliProgram = {
|
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
45
|
name: "secret-flag",
|
|
45
|
-
hidden: true,
|
|
46
|
+
cli: { hidden: true },
|
|
46
47
|
description: "Hidden option.",
|
|
47
48
|
kind: CliOptionKind.Presence,
|
|
48
49
|
},
|
package/src/mcp/result.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
This module builds MCP tools/call success results from handler respond payloads.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { encodeRespondBodyBase64 } from "
|
|
6
|
-
import type { CliRespondOptions } from "
|
|
5
|
+
import { encodeRespondBodyBase64 } from "~/core/respond.ts";
|
|
6
|
+
import type { CliRespondOptions } from "~/core/types.ts";
|
|
7
7
|
|
|
8
8
|
/** Text content block in an MCP tool result. */
|
|
9
9
|
export interface McpTextContent {
|
package/src/mcp/server.ts
CHANGED
|
@@ -3,8 +3,9 @@ This module implements the MCP JSON-RPC server over stdio: initialize, tools,
|
|
|
3
3
|
resources, and ping. Responses are newline-delimited JSON on stdout only.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import { executeHeadlessToolCall, lookupHeadlessTool } from "
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { executeHeadlessToolCall, headlessFailureMcpMessage, lookupHeadlessTool } from "~/headless/tool-call.ts";
|
|
8
|
+
import type { Cli } from "~/runtime/cli.ts";
|
|
8
9
|
import { allMcpResources, collectMcpTools, resolveMcpServerInfo } from "./tools.ts";
|
|
9
10
|
|
|
10
11
|
const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
@@ -37,6 +38,12 @@ function writeError(id: string | number | null | undefined, code: number, messag
|
|
|
37
38
|
/** Handles one NDJSON request line. */
|
|
38
39
|
async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
39
40
|
const root = cli.program;
|
|
41
|
+
const requestId = randomUUID();
|
|
42
|
+
const started = performance.now();
|
|
43
|
+
const hooks = cli.server?.mcpHooks ?? root.mcpServer?.hooks;
|
|
44
|
+
const emitter = cli.server?.emitter;
|
|
45
|
+
const obscureUnexpected = cli.server?.mcp?.obscureUnexpected ?? root.mcpServer?.errors?.obscureUnexpected ?? false;
|
|
46
|
+
|
|
40
47
|
let req: JsonRpcRequest;
|
|
41
48
|
try {
|
|
42
49
|
req = JSON.parse(line) as JsonRpcRequest;
|
|
@@ -46,17 +53,40 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
46
53
|
|
|
47
54
|
const id = req.id;
|
|
48
55
|
const hasId = id !== undefined;
|
|
56
|
+
const method = req.method ?? "";
|
|
57
|
+
const params = (req.params ?? {}) as Record<string, unknown>;
|
|
58
|
+
const wireCtx = { rpcMethod: method, requestId };
|
|
59
|
+
|
|
60
|
+
await hooks?.onRequest?.(wireCtx);
|
|
61
|
+
|
|
62
|
+
const finish = async (failureKind?: string, error?: unknown): Promise<void> => {
|
|
63
|
+
const durationMs = Math.round(performance.now() - started);
|
|
64
|
+
if (failureKind && error !== undefined) {
|
|
65
|
+
await hooks?.onError?.({
|
|
66
|
+
...wireCtx,
|
|
67
|
+
failureKind: failureKind as import("~/core/types.ts").InvokeFailureKind,
|
|
68
|
+
error,
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
await hooks?.onResponse?.({ ...wireCtx, durationMs });
|
|
72
|
+
}
|
|
73
|
+
emitter?.emitAccess({
|
|
74
|
+
method: "MCP",
|
|
75
|
+
path: method,
|
|
76
|
+
status: failureKind ? 500 : 200,
|
|
77
|
+
durationMs,
|
|
78
|
+
requestId,
|
|
79
|
+
});
|
|
80
|
+
};
|
|
49
81
|
|
|
50
82
|
if (req.jsonrpc !== "2.0") {
|
|
51
83
|
if (hasId) {
|
|
52
84
|
writeError(id, -32600, "Invalid Request");
|
|
53
85
|
}
|
|
86
|
+
await finish("validation", new Error("Invalid Request"));
|
|
54
87
|
return;
|
|
55
88
|
}
|
|
56
89
|
|
|
57
|
-
const method = req.method ?? "";
|
|
58
|
-
const params = (req.params ?? {}) as Record<string, unknown>;
|
|
59
|
-
|
|
60
90
|
if (method === "notifications/initialized") {
|
|
61
91
|
return;
|
|
62
92
|
}
|
|
@@ -77,11 +107,13 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
77
107
|
serverInfo: { name: info.name, version: info.version },
|
|
78
108
|
},
|
|
79
109
|
});
|
|
110
|
+
await finish();
|
|
80
111
|
return;
|
|
81
112
|
}
|
|
82
113
|
|
|
83
114
|
if (method === "ping") {
|
|
84
115
|
writeResponse({ jsonrpc: "2.0", id, result: {} });
|
|
116
|
+
await finish();
|
|
85
117
|
return;
|
|
86
118
|
}
|
|
87
119
|
|
|
@@ -93,6 +125,7 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
93
125
|
...(t.outputSchema === undefined ? {} : { outputSchema: t.outputSchema }),
|
|
94
126
|
}));
|
|
95
127
|
writeResponse({ jsonrpc: "2.0", id, result: { tools } });
|
|
128
|
+
await finish();
|
|
96
129
|
return;
|
|
97
130
|
}
|
|
98
131
|
|
|
@@ -100,17 +133,20 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
100
133
|
const name = params.name;
|
|
101
134
|
if (typeof name !== "string") {
|
|
102
135
|
writeError(id, -32602, "Invalid params: name required");
|
|
136
|
+
await finish("validation", new Error("Invalid params: name required"));
|
|
103
137
|
return;
|
|
104
138
|
}
|
|
105
139
|
const rawArgs = params.arguments;
|
|
106
140
|
if (rawArgs !== undefined && (typeof rawArgs !== "object" || rawArgs === null || Array.isArray(rawArgs))) {
|
|
107
141
|
writeError(id, -32602, "Invalid params: arguments must be an object");
|
|
142
|
+
await finish("validation", new Error("Invalid params: arguments must be an object"));
|
|
108
143
|
return;
|
|
109
144
|
}
|
|
110
145
|
const lookup = lookupHeadlessTool(root, name);
|
|
111
146
|
if (!lookup.ok) {
|
|
112
147
|
if (lookup.kind === "unknown") {
|
|
113
148
|
writeError(id, -32602, lookup.message);
|
|
149
|
+
await finish("unknown_route", new Error(lookup.message));
|
|
114
150
|
return;
|
|
115
151
|
}
|
|
116
152
|
writeResponse({
|
|
@@ -121,6 +157,7 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
121
157
|
isError: true,
|
|
122
158
|
},
|
|
123
159
|
});
|
|
160
|
+
await finish("missing_config", new Error(lookup.message));
|
|
124
161
|
return;
|
|
125
162
|
}
|
|
126
163
|
const invokeResult = await executeHeadlessToolCall(
|
|
@@ -128,6 +165,7 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
128
165
|
lookup.tool,
|
|
129
166
|
(rawArgs ?? {}) as Record<string, unknown>,
|
|
130
167
|
"mcp",
|
|
168
|
+
{ rpcMethod: method, toolName: name, requestId },
|
|
131
169
|
);
|
|
132
170
|
if (invokeResult.ok) {
|
|
133
171
|
writeResponse({
|
|
@@ -135,16 +173,19 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
135
173
|
id,
|
|
136
174
|
result: invokeResult.mcpResult,
|
|
137
175
|
});
|
|
176
|
+
await finish();
|
|
138
177
|
return;
|
|
139
178
|
}
|
|
179
|
+
const text = headlessFailureMcpMessage(invokeResult, obscureUnexpected);
|
|
140
180
|
writeResponse({
|
|
141
181
|
jsonrpc: "2.0",
|
|
142
182
|
id,
|
|
143
183
|
result: {
|
|
144
|
-
content: [{ type: "text", text
|
|
184
|
+
content: [{ type: "text", text }],
|
|
145
185
|
isError: true,
|
|
146
186
|
},
|
|
147
187
|
});
|
|
188
|
+
await finish(invokeResult.failureKind ?? "invoke", new Error(invokeResult.message));
|
|
148
189
|
return;
|
|
149
190
|
}
|
|
150
191
|
|
|
@@ -156,6 +197,7 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
156
197
|
mimeType: r.mimeType,
|
|
157
198
|
}));
|
|
158
199
|
writeResponse({ jsonrpc: "2.0", id, result: { resources } });
|
|
200
|
+
await finish();
|
|
159
201
|
return;
|
|
160
202
|
}
|
|
161
203
|
|
|
@@ -163,12 +205,14 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
163
205
|
const uri = params.uri;
|
|
164
206
|
if (typeof uri !== "string") {
|
|
165
207
|
writeError(id, -32602, "Invalid params: uri required");
|
|
208
|
+
await finish("validation", new Error("Invalid params: uri required"));
|
|
166
209
|
return;
|
|
167
210
|
}
|
|
168
211
|
const all = allMcpResources(root);
|
|
169
212
|
const found = all.find((r) => r.uri === uri);
|
|
170
213
|
if (!found) {
|
|
171
214
|
writeError(id, -32602, `Unknown resource: ${uri}`);
|
|
215
|
+
await finish("unknown_route", new Error(`Unknown resource: ${uri}`));
|
|
172
216
|
return;
|
|
173
217
|
}
|
|
174
218
|
let text: string;
|
|
@@ -177,6 +221,7 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
177
221
|
} catch (err) {
|
|
178
222
|
const message = err instanceof Error ? err.message : String(err);
|
|
179
223
|
writeError(id, -32603, `Resource load failed: ${message}`);
|
|
224
|
+
await finish("unexpected", err);
|
|
180
225
|
return;
|
|
181
226
|
}
|
|
182
227
|
writeResponse({
|
|
@@ -192,13 +237,16 @@ async function handleRequestLine(cli: Cli, line: string): Promise<void> {
|
|
|
192
237
|
],
|
|
193
238
|
},
|
|
194
239
|
});
|
|
240
|
+
await finish();
|
|
195
241
|
return;
|
|
196
242
|
}
|
|
197
243
|
|
|
198
244
|
writeError(id, -32601, "Method not found");
|
|
245
|
+
await finish("unknown_route", new Error("Method not found"));
|
|
199
246
|
} catch (err) {
|
|
200
247
|
const message = err instanceof Error ? err.message : "Internal error";
|
|
201
248
|
writeError(id, -32603, message);
|
|
249
|
+
await finish("unexpected", err);
|
|
202
250
|
}
|
|
203
251
|
}
|
|
204
252
|
|
package/src/mcp/tools.ts
CHANGED
|
@@ -3,11 +3,8 @@ This module maps CliProgram leaf nodes to MCP tool definitions and converts
|
|
|
3
3
|
flat JSON tool arguments into argv for Cli.invoke.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { visibleOptions } from "../hidden.ts";
|
|
9
|
-
import { collectOptionDefs } from "../parse.ts";
|
|
10
|
-
import { cliSchemaJson } from "../schema.ts";
|
|
6
|
+
import { collectOptionDefs } from "~/core/parse.ts";
|
|
7
|
+
import { cliSchemaJson } from "~/core/schema.ts";
|
|
11
8
|
import {
|
|
12
9
|
type CliLeaf,
|
|
13
10
|
type CliNode,
|
|
@@ -19,7 +16,10 @@ import {
|
|
|
19
16
|
isCliLeaf,
|
|
20
17
|
isJsonLeaf,
|
|
21
18
|
leafOutputSchema,
|
|
22
|
-
} from "
|
|
19
|
+
} from "~/core/types.ts";
|
|
20
|
+
import { docsMcpResources } from "~/docs/mcp-resources.ts";
|
|
21
|
+
import { cliResolveNotes } from "~/help.ts";
|
|
22
|
+
import { isMcpHidden, visibleOptions } from "~/runtime/exposure.ts";
|
|
23
23
|
|
|
24
24
|
const DURATION_PATTERN = "^\\d+[hdms]?$";
|
|
25
25
|
|
|
@@ -28,7 +28,7 @@ export function defaultMcpSchemaUri(mcpId: string): string {
|
|
|
28
28
|
return `${mcpId}://schema`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export { defaultDocsTopicResourceUri, resolveDocsTopicResourceUri } from "
|
|
31
|
+
export { defaultDocsTopicResourceUri, resolveDocsTopicResourceUri } from "~/docs/mcp-resources.ts";
|
|
32
32
|
|
|
33
33
|
/** Sanitizes a command key segment for MCP tool names and server identity. */
|
|
34
34
|
export function sanitizeToolSegment(key: string): string {
|
|
@@ -44,8 +44,6 @@ export function mcpServerId(root: CliProgram): string {
|
|
|
44
44
|
export interface McpToolDef {
|
|
45
45
|
/** MCP tool name (underscore-separated, sanitized segments). */
|
|
46
46
|
name: string;
|
|
47
|
-
/** HTTP API tool name (hyphen-separated path; preserves command key spelling). */
|
|
48
|
-
apiName: string;
|
|
49
47
|
/** Tool description from the leaf command. */
|
|
50
48
|
description: string;
|
|
51
49
|
/** Command path segments from the program root. */
|
|
@@ -72,14 +70,6 @@ export function mcpToolName(root: CliProgram, path: string[]): string {
|
|
|
72
70
|
return path.map(sanitizeToolSegment).join("_");
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
/** Builds the HTTP API tool name for a leaf at the given path (hyphen-joined, unsanitized). */
|
|
76
|
-
export function apiToolName(root: CliProgram, path: string[]): string {
|
|
77
|
-
if (path.length === 0) {
|
|
78
|
-
return root.key;
|
|
79
|
-
}
|
|
80
|
-
return path.join("-");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
73
|
/** JSON Schema property for one option. */
|
|
84
74
|
function optionProperty(opt: CliOption): Record<string, unknown> {
|
|
85
75
|
const base: Record<string, unknown> = { description: opt.description };
|
|
@@ -122,7 +112,7 @@ function optionProperty(opt: CliOption): Record<string, unknown> {
|
|
|
122
112
|
}
|
|
123
113
|
}
|
|
124
114
|
|
|
125
|
-
function formatMcpOptionValue(opt: CliOption, val: unknown): string | { error: string } {
|
|
115
|
+
export function formatMcpOptionValue(opt: CliOption, val: unknown): string | { error: string } {
|
|
126
116
|
if (opt.format === CliValueFormat.CommaList) {
|
|
127
117
|
if (Array.isArray(val)) {
|
|
128
118
|
const items = val.map(String).filter(Boolean);
|
|
@@ -238,13 +228,12 @@ export function collectMcpTools(root: CliProgram): McpToolDef[] {
|
|
|
238
228
|
if (cmd.key === "completion" || cmd.key === "configure" || cmd.key === "mcp" || cmd.key === "version") {
|
|
239
229
|
return;
|
|
240
230
|
}
|
|
241
|
-
if (cmd
|
|
231
|
+
if (isMcpHidden(cmd)) {
|
|
242
232
|
return;
|
|
243
233
|
}
|
|
244
234
|
const outputSchema = leafOutputSchema(cmd);
|
|
245
235
|
out.push({
|
|
246
236
|
name: mcpToolName(root, path),
|
|
247
|
-
apiName: apiToolName(root, path),
|
|
248
237
|
description: resolveToolDescription(root, path, cmd),
|
|
249
238
|
path,
|
|
250
239
|
leaf: cmd,
|
|
@@ -3,12 +3,12 @@ Internal capability resolver — decides which platform builtins are active for
|
|
|
3
3
|
Not exported from the public package barrel.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { configCommandsEnabled } from "
|
|
7
|
-
import type { CliProgram } from "
|
|
6
|
+
import { configCommandsEnabled } from "~/config/entry.ts";
|
|
7
|
+
import type { CliProgram } from "~/core/types.ts";
|
|
8
8
|
|
|
9
9
|
/** Platform builtins derived from program config and runtime. */
|
|
10
10
|
export interface CliCapabilities {
|
|
11
|
-
|
|
11
|
+
http: boolean;
|
|
12
12
|
completion: boolean;
|
|
13
13
|
mcp: boolean;
|
|
14
14
|
configure: boolean;
|
|
@@ -20,11 +20,11 @@ export interface CliCapabilities {
|
|
|
20
20
|
export function resolveCapabilities(program: CliProgram): CliCapabilities {
|
|
21
21
|
const configure = program.configure?.enabled !== false;
|
|
22
22
|
return {
|
|
23
|
-
|
|
23
|
+
http: program.httpServer?.enabled === true,
|
|
24
24
|
completion: program.completion?.enabled !== false,
|
|
25
25
|
mcp: program.mcpServer?.enabled === true,
|
|
26
26
|
configure,
|
|
27
|
-
docs: program.docs?.enabled
|
|
27
|
+
docs: program.docs?.enabled !== false,
|
|
28
28
|
configCommands: configCommandsEnabled(program),
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -44,8 +44,8 @@ export function reservedCommandNames(caps: CliCapabilities): string[] {
|
|
|
44
44
|
if (caps.mcp) {
|
|
45
45
|
names.push("mcp");
|
|
46
46
|
}
|
|
47
|
-
if (caps.
|
|
48
|
-
names.push("
|
|
47
|
+
if (caps.http) {
|
|
48
|
+
names.push("http");
|
|
49
49
|
}
|
|
50
50
|
return names;
|
|
51
51
|
}
|
|
@@ -65,14 +65,14 @@ export function skipsRequiredAppConfigExit(path: string[], caps: CliCapabilities
|
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export type CapabilityFeature = "
|
|
68
|
+
export type CapabilityFeature = "http" | "mcp" | "configure" | "docs" | "completion";
|
|
69
69
|
|
|
70
70
|
/** Stderr message when a disabled built-in is invoked from the CLI. */
|
|
71
71
|
export function capabilityDeniedMessage(feature: CapabilityFeature): string {
|
|
72
72
|
switch (feature) {
|
|
73
73
|
case "completion":
|
|
74
74
|
return "Shell completion is not available for this app.\n";
|
|
75
|
-
case "
|
|
75
|
+
case "http":
|
|
76
76
|
return "HTTP API is not available for this app.\n";
|
|
77
77
|
case "mcp":
|
|
78
78
|
return "MCP is not available for this app.\n";
|
|
@@ -97,8 +97,8 @@ export function assertBuiltinAllowed(argv: string[], caps: CliCapabilities): voi
|
|
|
97
97
|
process.stderr.write(capabilityDeniedMessage("mcp"));
|
|
98
98
|
process.exit(1);
|
|
99
99
|
}
|
|
100
|
-
if (first === "
|
|
101
|
-
process.stderr.write(capabilityDeniedMessage("
|
|
100
|
+
if (first === "http" && !caps.http) {
|
|
101
|
+
process.stderr.write(capabilityDeniedMessage("http"));
|
|
102
102
|
process.exit(1);
|
|
103
103
|
}
|
|
104
104
|
if (first === "configure" && !caps.configure) {
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Handler error helper with contextual help.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { cliPresentationRoot } from "
|
|
6
|
-
import type { CliContext } from "
|
|
7
|
-
import { cliHelpRender } from "
|
|
5
|
+
import { cliPresentationRoot } from "~/builtins/presentation.ts";
|
|
6
|
+
import type { CliContext } from "~/core/context.ts";
|
|
7
|
+
import { cliHelpRender } from "~/help.ts";
|
|
8
8
|
|
|
9
9
|
export function cliErrWithHelp(ctx: CliContext, msg: string): never {
|
|
10
|
-
if (ctx.invocation === "
|
|
10
|
+
if (ctx.invocation === "http" || ctx.invocation === "mcp") {
|
|
11
11
|
throw new Error(msg);
|
|
12
12
|
}
|
|
13
13
|
const color = process.stderr.isTTY;
|