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.
Files changed (229) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +17 -19
  3. package/bin/argsbarg +10 -0
  4. package/docs/README.md +4 -3
  5. package/docs/ai-skills.md +4 -2
  6. package/docs/bundled-docs.md +50 -25
  7. package/docs/cli-program.md +52 -10
  8. package/docs/config-schema.md +10 -11
  9. package/docs/configure.md +2 -0
  10. package/docs/decisions.md +40 -0
  11. package/docs/developing.md +43 -5
  12. package/docs/http-server.md +171 -0
  13. package/docs/json-schema-subset.md +51 -0
  14. package/docs/mcp.md +4 -2
  15. package/docs/output-schema.md +55 -62
  16. package/examples/formats.ts +6 -6
  17. package/examples/full-example/Formula/full-example.rb +35 -0
  18. package/examples/full-example/README.md +20 -21
  19. package/examples/full-example/docs/README.md +1 -1
  20. package/examples/full-example/docs/cli-schema.json +1790 -98
  21. package/examples/full-example/docs/cli.md +1990 -0
  22. package/examples/full-example/docs/http.md +28 -29
  23. package/examples/full-example/docs/mcp.md +8 -22
  24. package/examples/full-example/docs/openapi.json +783 -50
  25. package/examples/full-example/docs/skill.md +10 -10
  26. package/examples/full-example/justfile +11 -1
  27. package/examples/full-example/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
  28. package/examples/full-example/src/commands/render-json/__generated__/index.ts +5 -0
  29. package/examples/full-example/src/commands/render-json/command.test.ts +46 -0
  30. package/examples/full-example/src/commands/render-json/command.ts +30 -0
  31. package/examples/full-example/src/commands/render-json/types.ts +9 -0
  32. package/examples/full-example/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
  33. package/examples/full-example/src/commands/status/__generated__/index.ts +2 -2
  34. package/examples/full-example/src/commands/status/command.ts +5 -13
  35. package/examples/full-example/src/commands/status/types.ts +1 -14
  36. package/examples/full-example/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
  37. package/examples/full-example/src/commands/workspaces/__generated__/index.ts +5 -0
  38. package/examples/full-example/src/commands/workspaces/command.test.ts +58 -0
  39. package/examples/full-example/src/commands/workspaces/command.ts +94 -0
  40. package/examples/full-example/src/commands/workspaces/types.ts +6 -0
  41. package/examples/full-example/src/db/index.test.ts +86 -0
  42. package/examples/full-example/src/db/index.ts +101 -0
  43. package/examples/full-example/src/db/migrate.test.ts +35 -0
  44. package/examples/full-example/src/db/migrate.ts +69 -0
  45. package/examples/full-example/src/db/migrations/001_workspaces.sql +6 -0
  46. package/examples/full-example/src/db/tables/workspaces.ts +66 -0
  47. package/examples/full-example/src/program.ts +11 -36
  48. package/examples/full-example/src/types/argsbarg.d.ts +11 -0
  49. package/examples/full-example/src/types/md.d.ts +4 -0
  50. package/examples/full-example/tsconfig.json +5 -2
  51. package/examples/mcp-test.ts +1 -2
  52. package/examples/minimal.ts +1 -7
  53. package/examples/nested.ts +1 -2
  54. package/examples/option-required.ts +1 -1
  55. package/examples/servers.ts +4 -5
  56. package/index.d.ts +431 -136
  57. package/package.json +19 -2
  58. package/src/builtins/builtins.test.ts +7 -7
  59. package/src/builtins/completion-bash.ts +1 -1
  60. package/src/builtins/completion-fish.ts +1 -1
  61. package/src/builtins/completion-group.ts +4 -4
  62. package/src/builtins/completion-simulate-shared.ts +9 -0
  63. package/src/builtins/completion-zsh.ts +1 -1
  64. package/src/builtins/config.test.ts +3 -3
  65. package/src/builtins/config.ts +9 -9
  66. package/src/builtins/configure-copy.ts +2 -2
  67. package/src/builtins/configure.ts +4 -4
  68. package/src/builtins/dispatch.ts +19 -18
  69. package/src/builtins/export.ts +7 -5
  70. package/src/builtins/http.ts +68 -0
  71. package/src/builtins/mcp.ts +28 -4
  72. package/src/builtins/presentation.ts +6 -6
  73. package/src/builtins/registry.ts +6 -6
  74. package/src/builtins/scopes.ts +2 -2
  75. package/src/builtins/version.ts +1 -1
  76. package/src/cli-tool/full-example-capabilities.test.ts +10 -15
  77. package/src/cli-tool/main.ts +1 -1
  78. package/src/cli-tool/program.ts +3 -2
  79. package/src/cli-tool/prompt.ts +1 -1
  80. package/src/cli-tool/run-schemagen.ts +1 -3
  81. package/src/cli-tool/schemagen/cleanup.ts +6 -7
  82. package/src/cli-tool/schemagen/discover-schema-roots.ts +66 -120
  83. package/src/cli-tool/schemagen/index.ts +2 -2
  84. package/src/cli-tool/schemagen/names.ts +8 -13
  85. package/src/cli-tool/schemagen/run.ts +21 -28
  86. package/src/cli-tool/schemagen/schemagen.test.ts +136 -46
  87. package/src/config/bindings.test.ts +1 -1
  88. package/src/config/bindings.ts +1 -1
  89. package/src/config/bootstrap.test.ts +1 -1
  90. package/src/config/bootstrap.ts +36 -4
  91. package/src/config/context.test.ts +1 -1
  92. package/src/config/context.ts +1 -1
  93. package/src/config/entry.ts +1 -1
  94. package/src/config/file.test.ts +1 -1
  95. package/src/config/file.ts +3 -3
  96. package/src/config/manifest.ts +1 -1
  97. package/src/config/resolve.test.ts +1 -1
  98. package/src/config/resolve.ts +1 -1
  99. package/src/config/schema.ts +1 -1
  100. package/src/config/validate.ts +1 -1
  101. package/src/{install → configure/artifacts}/binary-placement.test.ts +1 -1
  102. package/src/{install → configure/artifacts}/binary-placement.ts +1 -1
  103. package/src/{install → configure/artifacts}/gh-release-update.ts +1 -1
  104. package/src/{install → configure/artifacts}/install-validate.test.ts +3 -3
  105. package/src/{install → configure/artifacts}/mcp-config.ts +1 -1
  106. package/src/{install → configure/artifacts}/mcp-opencode.test.ts +1 -1
  107. package/src/{install → configure/artifacts}/mcp-opencode.ts +1 -1
  108. package/src/{install → configure/artifacts}/paths.ts +5 -5
  109. package/src/configure/artifacts/plan.ts +24 -0
  110. package/src/{install → configure/artifacts}/status.test.ts +1 -1
  111. package/src/{install → configure/artifacts}/status.ts +2 -2
  112. package/src/{install → configure/artifacts}/target-base.ts +1 -1
  113. package/src/{install → configure/artifacts}/target-detect.ts +1 -1
  114. package/src/{install → configure/artifacts}/target-effective.ts +3 -9
  115. package/src/{install → configure/artifacts}/target-mcp-cli.ts +1 -1
  116. package/src/{install → configure/artifacts}/target-mcp-json.ts +1 -1
  117. package/src/{install → configure/artifacts}/target-plan-build.ts +2 -2
  118. package/src/{install → configure/artifacts}/target-registry.ts +2 -2
  119. package/src/{install → configure/artifacts}/target-scope.ts +3 -3
  120. package/src/{install → configure/artifacts}/target-skill.ts +1 -1
  121. package/src/{install → configure/artifacts}/target-types.ts +2 -2
  122. package/src/{install → configure/artifacts}/targets/app.ts +5 -5
  123. package/src/{install → configure/artifacts}/targets/chatgpt-mcp.ts +2 -2
  124. package/src/{install → configure/artifacts}/targets/claude-code-mcp.ts +2 -2
  125. package/src/{install → configure/artifacts}/targets/claude-desktop-mcp.ts +2 -2
  126. package/src/{install → configure/artifacts}/targets/claude-skill.ts +2 -2
  127. package/src/{install → configure/artifacts}/targets/codex-mcp.ts +2 -2
  128. package/src/{install → configure/artifacts}/targets/codex-skill.ts +2 -2
  129. package/src/{install → configure/artifacts}/targets/configure.ts +5 -5
  130. package/src/{install → configure/artifacts}/targets/cursor-mcp.ts +2 -2
  131. package/src/{install → configure/artifacts}/targets/cursor-skill.ts +2 -2
  132. package/src/{install → configure/artifacts}/targets/index.ts +1 -1
  133. package/src/{install → configure/artifacts}/targets/openclaw-mcp.ts +2 -2
  134. package/src/{install → configure/artifacts}/targets/openclaw-skill.ts +3 -3
  135. package/src/{install → configure/artifacts}/targets/opencode-mcp.ts +5 -5
  136. package/src/{install → configure/artifacts}/targets/opencode-skill.ts +3 -3
  137. package/src/{install → configure/artifacts}/targets.test.ts +1 -1
  138. package/src/{install → configure/artifacts}/uninstall.ts +1 -1
  139. package/src/configure/configure.test.ts +11 -11
  140. package/src/configure/index.ts +14 -14
  141. package/src/configure/prompt.ts +2 -2
  142. package/src/{context.ts → core/context.ts} +26 -20
  143. package/src/{json-leaf.test.ts → core/json-leaf.test.ts} +4 -4
  144. package/src/{leaf-inputs.test.ts → core/leaf-inputs.test.ts} +7 -7
  145. package/src/{leaf-inputs.ts → core/leaf-inputs.ts} +16 -12
  146. package/src/{parse.test.ts → core/parse.test.ts} +97 -109
  147. package/src/{parse.ts → core/parse.ts} +129 -31
  148. package/src/{schema.ts → core/schema.ts} +25 -13
  149. package/src/{types.ts → core/types.ts} +225 -35
  150. package/src/{validate.ts → core/validate.ts} +39 -29
  151. package/src/docs/builtin.ts +8 -19
  152. package/src/docs/{api-guide.test.ts → cli-guide.test.ts} +21 -21
  153. package/src/docs/{api-guide.ts → cli-guide.ts} +45 -16
  154. package/src/docs/docs.test.ts +76 -41
  155. package/src/docs/http-guide.ts +37 -34
  156. package/src/docs/mcp-guide.ts +12 -14
  157. package/src/docs/mcp-resources.test.ts +2 -3
  158. package/src/docs/mcp-resources.ts +6 -11
  159. package/src/docs/resolve.ts +22 -30
  160. package/src/docs/save.ts +3 -3
  161. package/src/exports/cli.ts +47 -0
  162. package/src/exports/headless.ts +13 -0
  163. package/src/exports/http.ts +6 -0
  164. package/src/exports/mcp.ts +6 -0
  165. package/src/{headless.test.ts → headless/routing.test.ts} +3 -3
  166. package/src/{headless.ts → headless/routing.ts} +3 -3
  167. package/src/headless/tool-call.ts +114 -46
  168. package/src/help.test.ts +152 -0
  169. package/src/help.ts +3 -3
  170. package/src/hooks/builtin.ts +20 -0
  171. package/src/hooks/run.ts +142 -0
  172. package/src/http/openapi.ts +182 -0
  173. package/src/http/readiness.ts +78 -0
  174. package/src/{api → http}/result.ts +16 -5
  175. package/src/http/routes.ts +329 -0
  176. package/src/http/server.ts +225 -0
  177. package/src/index.ts +36 -25
  178. package/src/log/ecs.test.ts +43 -0
  179. package/src/log/ecs.ts +59 -0
  180. package/src/log/emitter.ts +166 -0
  181. package/src/mcp/bundle.ts +2 -2
  182. package/src/mcp/claude.test.ts +1 -1
  183. package/src/mcp/claude.ts +4 -4
  184. package/src/{hidden-mcpb.test.ts → mcp/hidden-mcpb.test.ts} +10 -9
  185. package/src/mcp/result.ts +2 -2
  186. package/src/mcp/server.ts +54 -6
  187. package/src/mcp/tools.ts +9 -20
  188. package/src/{capabilities.ts → runtime/capabilities.ts} +11 -11
  189. package/src/{cli-errors.ts → runtime/cli-errors.ts} +4 -4
  190. package/src/{cli.ts → runtime/cli.ts} +159 -49
  191. package/src/runtime/exposure.ts +102 -0
  192. package/src/{invoke.test.ts → runtime/invoke.test.ts} +31 -7
  193. package/src/server/context.ts +25 -0
  194. package/src/server/overrides.ts +112 -0
  195. package/src/skill/generate.ts +8 -8
  196. package/src/skill/hint.ts +1 -1
  197. package/src/skill/install.ts +2 -2
  198. package/src/skill/naming.ts +1 -1
  199. package/src/{test-fixtures.ts → test/fixtures.ts} +3 -2
  200. package/src/{config.integration.test.ts → test/integration/config.test.ts} +8 -8
  201. package/src/{api.integration.test.ts → test/integration/http.test.ts} +170 -67
  202. package/src/{mcp.integration.test.ts → test/integration/mcp.test.ts} +11 -57
  203. package/docs/api-server.md +0 -141
  204. package/examples/full-example/docs/api.md +0 -511
  205. package/examples/full-example/src/commands/status/__generated__/outputSchema.json +0 -28
  206. package/examples/full-example/src/config/__generated__/configSchema.json +0 -40
  207. package/examples/full-example/src/config/__generated__/index.ts +0 -5
  208. package/examples/full-example/src/config/types.ts +0 -24
  209. package/src/api/openapi.ts +0 -117
  210. package/src/api/server.ts +0 -120
  211. package/src/builtins/api.ts +0 -38
  212. package/src/hidden.ts +0 -30
  213. package/src/install/plan.ts +0 -53
  214. /package/src/{install → configure/artifacts}/detect-installed.ts +0 -0
  215. /package/src/{install → configure/artifacts}/gh-release-update.test.ts +0 -0
  216. /package/src/{install → configure/artifacts}/mcp-codex.test.ts +0 -0
  217. /package/src/{install → configure/artifacts}/mcp-codex.ts +0 -0
  218. /package/src/{install → configure/artifacts}/mcp-openclaw.test.ts +0 -0
  219. /package/src/{install → configure/artifacts}/mcp-openclaw.ts +0 -0
  220. /package/src/{install → configure/artifacts}/normalize-uninstall.ts +0 -0
  221. /package/src/{install → configure/artifacts}/normalize.ts +0 -0
  222. /package/src/{install → configure/artifacts}/opts.ts +0 -0
  223. /package/src/{install → configure/artifacts}/shell.ts +0 -0
  224. /package/src/{formats.test.ts → core/formats.test.ts} +0 -0
  225. /package/src/{formats.ts → core/formats.ts} +0 -0
  226. /package/src/{respond.ts → core/respond.ts} +0 -0
  227. /package/src/{types.test.ts → core/types.test.ts} +0 -0
  228. /package/src/{api → http}/schema-deref.test.ts +0 -0
  229. /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 "../config/manifest.ts";
10
- import type { CliMcpBundleConfig, CliProgram } from "../types.ts";
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";
@@ -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 "../types.ts";
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 "../config/manifest.ts";
21
- import { generatePluginSkillBundle } from "../skill/generate.ts";
22
- import { applyPluginSkillHint } from "../skill/hint.ts";
23
- import type { CliMcpBundleConfig, CliProgram } from "../types.ts";
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 "./builtins/export.ts";
10
- import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
11
- import { cliHelpRender } from "./help.ts";
12
- import { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle, runMcpBundle } from "./mcp/bundle.ts";
13
- import { collectMcpTools } from "./mcp/tools.ts";
14
- import { cliSchemaExport } from "./schema.ts";
15
- import { CliOptionKind, type CliProgram } from "./types.ts";
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 "../respond.ts";
6
- import type { CliRespondOptions } from "../types.ts";
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 type { Cli } from "../cli.ts";
7
- import { executeHeadlessToolCall, lookupHeadlessTool } from "../headless/tool-call.ts";
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: invokeResult.message }],
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 { docsMcpResources } from "../docs/mcp-resources.ts";
7
- import { cliResolveNotes } from "../help.ts";
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 "../types.ts";
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 "../docs/mcp-resources.ts";
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.hidden || cmd.mcpTool?.enabled === false) {
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 "./config/entry.ts";
7
- import type { CliProgram } from "./types.ts";
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
- api: boolean;
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
- api: program.apiServer?.enabled === true,
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 === true,
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.api) {
48
- names.push("api");
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 = "api" | "mcp" | "configure" | "docs" | "completion";
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 "api":
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 === "api" && !caps.api) {
101
- process.stderr.write(capabilityDeniedMessage("api"));
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 "./builtins/presentation.ts";
6
- import type { CliContext } from "./context.ts";
7
- import { cliHelpRender } from "./help.ts";
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 === "api" || ctx.invocation === "mcp") {
10
+ if (ctx.invocation === "http" || ctx.invocation === "mcp") {
11
11
  throw new Error(msg);
12
12
  }
13
13
  const color = process.stderr.isTTY;