argsbarg 6.1.1 → 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 +72 -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 +76 -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 +440 -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/core/json-leaf.test.ts +156 -0
- package/src/{leaf-inputs.test.ts → core/leaf-inputs.test.ts} +7 -7
- package/src/{leaf-inputs.ts → core/leaf-inputs.ts} +76 -16
- package/src/{parse.test.ts → core/parse.test.ts} +97 -109
- package/src/{parse.ts → core/parse.ts} +173 -25
- package/src/{schema.ts → core/schema.ts} +25 -13
- package/src/{types.ts → core/types.ts} +238 -35
- package/src/{validate.ts → core/validate.ts} +51 -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 +54 -18
- 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 +38 -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 +18 -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} +160 -50
- 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/index.ts
CHANGED
|
@@ -7,40 +7,21 @@ It gives consumers one stable import path without forcing them to know the inter
|
|
|
7
7
|
module layout.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export { generateOpenApi, openApiJson } from "./api/openapi.ts";
|
|
11
|
-
export { Cli, type CliInvokeKind, type CliInvokeResult } from "./cli.ts";
|
|
12
|
-
export { cliErrWithHelp } from "./cli-errors.ts";
|
|
13
10
|
export { displayAppConfigPath, resolveAppConfigPath } from "./config/file.ts";
|
|
14
|
-
export type { CliLeafInputs } from "./context.ts";
|
|
15
|
-
export { CliContext } from "./context.ts";
|
|
11
|
+
export type { CliLeafInputs } from "./core/context.ts";
|
|
12
|
+
export { CliContext } from "./core/context.ts";
|
|
16
13
|
export {
|
|
17
14
|
parseCommaList,
|
|
18
15
|
parseDate,
|
|
19
16
|
parseDateTime,
|
|
20
17
|
parseDurationMs,
|
|
21
|
-
} from "./formats.ts";
|
|
22
|
-
export type { HeadlessContext } from "./headless.ts";
|
|
23
|
-
export {
|
|
24
|
-
formatDryRunMessage,
|
|
25
|
-
requireYesInNonTty,
|
|
26
|
-
shouldRunHeadless,
|
|
27
|
-
shouldRunHeadlessWithPositionals,
|
|
28
|
-
shouldRunHeadlessWithYes,
|
|
29
|
-
wantsExplicitJson,
|
|
30
|
-
} from "./headless.ts";
|
|
18
|
+
} from "./core/formats.ts";
|
|
31
19
|
export {
|
|
32
20
|
LeafInputError,
|
|
33
|
-
loadLeafInputs,
|
|
34
21
|
preloadPipableJson,
|
|
35
22
|
readJsonOptionValue,
|
|
36
|
-
|
|
37
|
-
readLeafInputsAsync,
|
|
38
|
-
} from "./leaf-inputs.ts";
|
|
39
|
-
export type { McpBundlePaths, PackMcpBundleOpts } from "./mcp/bundle.ts";
|
|
40
|
-
export { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "./mcp/bundle.ts";
|
|
23
|
+
} from "./core/leaf-inputs.ts";
|
|
41
24
|
export type {
|
|
42
|
-
CliApiResponseConfig,
|
|
43
|
-
CliApiServerConfig,
|
|
44
25
|
CliAppConfig,
|
|
45
26
|
CliAppConfigEntry,
|
|
46
27
|
CliAppConfigResolveContext,
|
|
@@ -49,25 +30,57 @@ export type {
|
|
|
49
30
|
CliConfigureTargets,
|
|
50
31
|
CliDocsConfig,
|
|
51
32
|
CliDocsTopic,
|
|
33
|
+
ClientErrorOverride,
|
|
52
34
|
CliHandler,
|
|
35
|
+
CliHttpServerConfig,
|
|
36
|
+
CliHttpWireContext,
|
|
37
|
+
CliHttpWireHooks,
|
|
53
38
|
CliInvocation,
|
|
39
|
+
CliInvokeHookResult,
|
|
40
|
+
CliLeafKind,
|
|
41
|
+
CliLocals,
|
|
42
|
+
CliLogConfig,
|
|
54
43
|
CliMcpBundleConfig,
|
|
55
44
|
CliMcpResource,
|
|
56
45
|
CliMcpServerConfig,
|
|
57
46
|
CliMcpToolConfig,
|
|
47
|
+
CliMcpWireContext,
|
|
48
|
+
CliMcpWireHooks,
|
|
58
49
|
CliOption,
|
|
59
50
|
CliPositional,
|
|
60
51
|
CliProgram,
|
|
52
|
+
CliProgramHooks,
|
|
61
53
|
CliRespondBody,
|
|
62
54
|
CliRespondOptions,
|
|
55
|
+
ErrorHookContext,
|
|
63
56
|
InstallAgentIntegration,
|
|
64
57
|
InstallTargetSpec,
|
|
58
|
+
InvokeFailureKind,
|
|
59
|
+
InvokeHookContext,
|
|
60
|
+
ReadinessContext,
|
|
65
61
|
ResolvedInstallTarget,
|
|
66
|
-
|
|
62
|
+
ServerRuntime,
|
|
63
|
+
ServerState,
|
|
64
|
+
} from "./core/types.ts";
|
|
67
65
|
export {
|
|
68
66
|
CliFallbackMode,
|
|
69
67
|
CliOptionKind,
|
|
70
68
|
CliSchemaValidationError,
|
|
71
69
|
CliValueFormat,
|
|
72
|
-
|
|
70
|
+
isJsonLeaf,
|
|
71
|
+
} from "./core/types.ts";
|
|
72
|
+
export type { HeadlessContext } from "./headless/routing.ts";
|
|
73
|
+
export {
|
|
74
|
+
formatDryRunMessage,
|
|
75
|
+
requireYesInNonTty,
|
|
76
|
+
shouldRunHeadless,
|
|
77
|
+
shouldRunHeadlessWithPositionals,
|
|
78
|
+
shouldRunHeadlessWithYes,
|
|
79
|
+
wantsExplicitJson,
|
|
80
|
+
} from "./headless/routing.ts";
|
|
81
|
+
export { generateOpenApi, openApiJson } from "./http/openapi.ts";
|
|
82
|
+
export type { McpBundlePaths, PackMcpBundleOpts } from "./mcp/bundle.ts";
|
|
83
|
+
export { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "./mcp/bundle.ts";
|
|
84
|
+
export { Cli, type CliInvokeKind, type CliInvokeResult } from "./runtime/cli.ts";
|
|
85
|
+
export { cliErrWithHelp } from "./runtime/cli-errors.ts";
|
|
73
86
|
export { isInteractiveTty } from "./utils.ts";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Unit tests for ECS log line formatting.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { formatEcsLine } from "./ecs.ts";
|
|
7
|
+
|
|
8
|
+
describe("formatEcsLine", () => {
|
|
9
|
+
test("includes service fields and event action", () => {
|
|
10
|
+
const line = formatEcsLine(
|
|
11
|
+
{ name: "myapp", version: "7.0.0" },
|
|
12
|
+
{
|
|
13
|
+
level: "info",
|
|
14
|
+
message: "HTTP API listening",
|
|
15
|
+
action: "http.server.start",
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
const parsed = JSON.parse(line) as Record<string, unknown>;
|
|
19
|
+
expect(parsed["service.name"]).toBe("myapp");
|
|
20
|
+
expect(parsed["service.version"]).toBe("7.0.0");
|
|
21
|
+
expect(parsed["event.action"]).toBe("http.server.start");
|
|
22
|
+
expect(parsed.message).toBe("HTTP API listening");
|
|
23
|
+
expect(parsed["log.level"]).toBe("info");
|
|
24
|
+
expect(typeof parsed["@timestamp"]).toBe("string");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("includes error stack fields", () => {
|
|
28
|
+
const err = new Error("boom");
|
|
29
|
+
const line = formatEcsLine(
|
|
30
|
+
{ name: "app", version: "1.0.0" },
|
|
31
|
+
{
|
|
32
|
+
level: "error",
|
|
33
|
+
message: "invoke failed",
|
|
34
|
+
action: "invoke.error",
|
|
35
|
+
error: err,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
const parsed = JSON.parse(line) as Record<string, unknown>;
|
|
39
|
+
expect(parsed["error.message"]).toBe("boom");
|
|
40
|
+
expect(parsed["error.type"]).toBe("Error");
|
|
41
|
+
expect(String(parsed["error.stack_trace"])).toContain("boom");
|
|
42
|
+
});
|
|
43
|
+
});
|
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
|
|