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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: full_example
|
|
3
|
-
description: Operates the full-example CLI (echo, status). Use when the user mentions full-example, echo, status, or related tasks.
|
|
3
|
+
description: Operates the full-example CLI (echo, render-json, status, workspaces get, workspaces post, and 4 more). Use when the user mentions full-example, echo, render-json, status, or related tasks.
|
|
4
4
|
---
|
|
5
5
|
<!-- Generated by full-example docs skill --save; do not edit. -->
|
|
6
6
|
|
|
@@ -20,14 +20,14 @@ full-example <subcommand> [options] [args]
|
|
|
20
20
|
## Commands
|
|
21
21
|
|
|
22
22
|
- **`full-example echo`** — Echo a message (MCP-friendly leaf).
|
|
23
|
-
- **`full-example
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
23
|
+
- **`full-example render-json`** — Echo a JSON message (schema-first JSON leaf demo).
|
|
24
|
+
- **`full-example status`** — Show app version. (flags: --json)
|
|
25
|
+
- **`full-example workspaces get`** — List workspaces.
|
|
26
|
+
- **`full-example workspaces post`** — Create a workspace.
|
|
27
|
+
- **`full-example workspaces :id get`** — Get one workspace.
|
|
28
|
+
- **`full-example workspaces :id put`** — Replace a workspace.
|
|
29
|
+
- **`full-example workspaces :id patch`** — Patch a workspace name.
|
|
30
|
+
- **`full-example workspaces :id delete`** — Delete a workspace.
|
|
31
31
|
|
|
32
32
|
## Pitfalls
|
|
33
33
|
|
|
@@ -35,7 +35,7 @@ full-example <subcommand> [options] [args]
|
|
|
35
35
|
|
|
36
36
|
## Reference
|
|
37
37
|
|
|
38
|
-
For full detail, open `reference.md` in this skill directory (same as `full-example docs
|
|
38
|
+
For full detail, open `reference.md` in this skill directory (same as `full-example docs cli`).
|
|
39
39
|
|
|
40
40
|
## Cursor install location
|
|
41
41
|
|
|
@@ -21,6 +21,16 @@ build:
|
|
|
21
21
|
bun build ./src/index.ts --compile --outfile=dist/{{cli_key}}
|
|
22
22
|
@rm -f .*.bun-build
|
|
23
23
|
|
|
24
|
+
# Apply SQL migrations in src/db/migrations/ to a SQLite database file
|
|
25
|
+
migrate DB="./workspaces.db":
|
|
26
|
+
#!/usr/bin/env bash
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
mkdir -p "$(dirname '{{DB}}')"
|
|
29
|
+
for f in $(ls src/db/migrations/*.sql | sort); do
|
|
30
|
+
echo "==> $f"
|
|
31
|
+
sqlite3 '{{DB}}' < "$f"
|
|
32
|
+
done
|
|
33
|
+
|
|
24
34
|
# Run schemagen, typecheck, and format
|
|
25
35
|
check: schemagen format typecheck
|
|
26
36
|
|
|
@@ -31,7 +41,7 @@ dev *ARGS:
|
|
|
31
41
|
# Regenerate consumer docs under ./docs/
|
|
32
42
|
docgen: schemagen
|
|
33
43
|
@just run docs cli-schema --save
|
|
34
|
-
@just run docs
|
|
44
|
+
@just run docs cli --save
|
|
35
45
|
@just run docs skill --save
|
|
36
46
|
@just run docs mcp --save
|
|
37
47
|
@just run docs http --save
|
package/examples/full-example/src/commands/render-json/__generated__/RenderJsonInputSchema.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"message": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "Message to echo back."
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"required": [
|
|
11
|
+
"message"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"definitions": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Cli, type CliProgram } from "argsbarg";
|
|
3
|
+
import { renderJsonCommand, renderJsonTestProgram } from "./command.ts";
|
|
4
|
+
|
|
5
|
+
const baseProgram = {
|
|
6
|
+
key: "full-example",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
description: "Demo.",
|
|
9
|
+
httpServer: { enabled: true },
|
|
10
|
+
commands: [],
|
|
11
|
+
} satisfies CliProgram;
|
|
12
|
+
|
|
13
|
+
describe("render-json command", () => {
|
|
14
|
+
const program = renderJsonTestProgram(baseProgram);
|
|
15
|
+
const cli = new Cli(program);
|
|
16
|
+
|
|
17
|
+
test("HTTP invoke returns echoed message", async () => {
|
|
18
|
+
const result = await cli.invoke(["render-json"], {
|
|
19
|
+
invocation: "http",
|
|
20
|
+
toolArgs: { message: "hello" },
|
|
21
|
+
});
|
|
22
|
+
expect(result.kind).toBe("ok");
|
|
23
|
+
expect(result.response?.body).toEqual({ message: "hello" });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("rejects invalid input before handler via inputSchema", async () => {
|
|
27
|
+
let handlerCalled = false;
|
|
28
|
+
const badProgram = renderJsonTestProgram({
|
|
29
|
+
...baseProgram,
|
|
30
|
+
commands: [
|
|
31
|
+
{
|
|
32
|
+
...renderJsonCommand,
|
|
33
|
+
handler: () => {
|
|
34
|
+
handlerCalled = true;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
const result = await new Cli(badProgram).invoke(["render-json"], {
|
|
40
|
+
invocation: "http",
|
|
41
|
+
toolArgs: { message: 123 },
|
|
42
|
+
});
|
|
43
|
+
expect(result.kind).toBe("error");
|
|
44
|
+
expect(handlerCalled).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Render-json leaf — JSON body demo with schemagen inputSchema and ctx.inputsAs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { CliLeaf, CliProgram } from "argsbarg";
|
|
6
|
+
import { RenderJsonInputSchema } from "./__generated__";
|
|
7
|
+
import type { RenderJsonInput } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const renderJsonCommand = {
|
|
10
|
+
key: "render-json",
|
|
11
|
+
description: "Echo a JSON message (schema-first JSON leaf demo).",
|
|
12
|
+
kind: "json",
|
|
13
|
+
inputSchema: RenderJsonInputSchema,
|
|
14
|
+
handler: (ctx) => {
|
|
15
|
+
const { message } = ctx.inputsAs<RenderJsonInput>();
|
|
16
|
+
if (ctx.invocation === "cli") {
|
|
17
|
+
console.log(message);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
return { message };
|
|
21
|
+
},
|
|
22
|
+
} satisfies CliLeaf;
|
|
23
|
+
|
|
24
|
+
/** Program stub for colocated tests. */
|
|
25
|
+
export function renderJsonTestProgram(base: CliProgram): CliProgram {
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
commands: [renderJsonCommand],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"version": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "App version from program root."
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"required": [
|
|
11
|
+
"version"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"definitions": {}
|
|
15
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated by argsbarg schemagen — do not edit by hand.
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import StatusJsonOutputSchemaJson from "./StatusJsonOutputSchema.json";
|
|
4
4
|
|
|
5
|
-
export const
|
|
5
|
+
export const StatusJsonOutputSchema = StatusJsonOutputSchemaJson as Record<string, unknown>;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Status leaf — demonstrates outputSchema
|
|
2
|
+
Status leaf — demonstrates outputSchema.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { type CliLeaf, CliOptionKind } from "argsbarg";
|
|
6
|
-
import {
|
|
6
|
+
import { StatusJsonOutputSchema } from "./__generated__";
|
|
7
7
|
import type { StatusJsonOutput } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
export const statusCommand = {
|
|
10
10
|
key: "status",
|
|
11
|
-
description: "Show
|
|
11
|
+
description: "Show app version.",
|
|
12
12
|
options: [
|
|
13
13
|
{
|
|
14
14
|
name: "json",
|
|
@@ -16,21 +16,13 @@ export const statusCommand = {
|
|
|
16
16
|
kind: CliOptionKind.Presence,
|
|
17
17
|
},
|
|
18
18
|
],
|
|
19
|
-
outputSchema,
|
|
19
|
+
outputSchema: StatusJsonOutputSchema,
|
|
20
20
|
handler: (ctx) => {
|
|
21
|
-
const out: StatusJsonOutput = {
|
|
22
|
-
defaultRegion: ctx.appConfig.get("defaultRegion") as string | undefined,
|
|
23
|
-
maxRetries: ctx.appConfig.get("maxRetries") as number | undefined,
|
|
24
|
-
apiTokenSet: ctx.appConfig.get("apiToken") !== undefined,
|
|
25
|
-
version: ctx.program.version,
|
|
26
|
-
};
|
|
21
|
+
const out: StatusJsonOutput = { version: ctx.program.version };
|
|
27
22
|
if (ctx.hasFlag("json")) {
|
|
28
23
|
console.log(JSON.stringify(out, null, 2));
|
|
29
24
|
} else {
|
|
30
25
|
console.log(`version=${out.version}`);
|
|
31
|
-
console.log(`region=${out.defaultRegion ?? "(not set)"}`);
|
|
32
|
-
console.log(`maxRetries=${out.maxRetries ?? "(not set)"}`);
|
|
33
|
-
console.log(`apiToken=${out.apiTokenSet ? "set" : "missing"}`);
|
|
34
26
|
}
|
|
35
27
|
},
|
|
36
28
|
} satisfies CliLeaf;
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
/** JSON stdout for `full-example status --json`. */
|
|
2
|
+
/** @sg */
|
|
2
3
|
export interface StatusJsonOutput {
|
|
3
|
-
/** Resolved AWS region. */
|
|
4
|
-
defaultRegion?: string;
|
|
5
|
-
/** Resolved retry count. */
|
|
6
|
-
maxRetries?: number;
|
|
7
|
-
/** Whether apiToken is set (value never included). */
|
|
8
|
-
apiTokenSet: boolean;
|
|
9
4
|
/** App version from program root. */
|
|
10
5
|
version: string;
|
|
11
6
|
}
|
|
12
|
-
|
|
13
|
-
/** Returns status JSON (identity helper for schema generation). */
|
|
14
|
-
export function buildStatusJson(output: StatusJsonOutput): StatusJsonOutput {
|
|
15
|
-
return output;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Schemagen root for leaf outputSchema. */
|
|
19
|
-
export type outputType = StatusJsonOutput;
|
package/examples/full-example/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"name": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "Workspace display name."
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"required": [
|
|
11
|
+
"name"
|
|
12
|
+
],
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"definitions": {}
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Cli, type CliProgram } from "argsbarg";
|
|
3
|
+
import { AppDb } from "~/db";
|
|
4
|
+
import { workspacesTestProgram } from "./command.ts";
|
|
5
|
+
|
|
6
|
+
const baseProgram = {
|
|
7
|
+
key: "full-example",
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
description: "Demo.",
|
|
10
|
+
httpServer: { enabled: true },
|
|
11
|
+
commands: [],
|
|
12
|
+
hooks: {
|
|
13
|
+
beforeInvoke: AppDb.attach,
|
|
14
|
+
},
|
|
15
|
+
} satisfies CliProgram;
|
|
16
|
+
|
|
17
|
+
describe("workspaces command", () => {
|
|
18
|
+
const program = workspacesTestProgram(baseProgram);
|
|
19
|
+
const cli = new Cli(program);
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
AppDb.resetForTests();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("GET workspaces lists empty collection", async () => {
|
|
26
|
+
const result = await cli.invoke(["workspaces", "get"], { invocation: "http" });
|
|
27
|
+
expect(result.kind).toBe("ok");
|
|
28
|
+
expect(result.response?.body).toEqual({ workspaces: [] });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("POST workspaces creates resource", async () => {
|
|
32
|
+
const created = await cli.invoke(["workspaces", "post"], {
|
|
33
|
+
invocation: "http",
|
|
34
|
+
toolArgs: { name: "qa2" },
|
|
35
|
+
});
|
|
36
|
+
expect(created.kind).toBe("ok");
|
|
37
|
+
const body = created.response?.body as { id: string; name: string };
|
|
38
|
+
expect(body.name).toBe("qa2");
|
|
39
|
+
expect(body.id.length).toBeGreaterThan(0);
|
|
40
|
+
|
|
41
|
+
const got = await cli.invoke(["workspaces", body.id, "get"], { invocation: "http" });
|
|
42
|
+
expect(got.kind).toBe("ok");
|
|
43
|
+
expect(got.response?.body).toEqual(body);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("CLI workspaces :id get resolves path param", async () => {
|
|
47
|
+
const created = await cli.invoke(["workspaces", "post"], {
|
|
48
|
+
invocation: "http",
|
|
49
|
+
toolArgs: { name: "cli-ws" },
|
|
50
|
+
});
|
|
51
|
+
expect(created.kind).toBe("ok");
|
|
52
|
+
const id = (created.response?.body as { id: string }).id;
|
|
53
|
+
|
|
54
|
+
const got = await cli.invoke(["workspaces", id, "get"], { invocation: "http" });
|
|
55
|
+
expect(got.kind).toBe("ok");
|
|
56
|
+
expect(got.response?.body).toEqual({ id, name: "cli-ws" });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Workspaces CRUD — demonstrates REST verbs, :id param routers, and ctx.inputs path params.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type CliProgram, type CliRouter, cliErrWithHelp } from "argsbarg";
|
|
6
|
+
import { WorkspaceNameInputSchema } from "./__generated__";
|
|
7
|
+
|
|
8
|
+
function notFound(ctx: Parameters<typeof cliErrWithHelp>[0], id: string): never {
|
|
9
|
+
cliErrWithHelp(ctx, `Workspace not found: ${id}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const workspacesCommand = {
|
|
13
|
+
key: "workspaces",
|
|
14
|
+
description: "Workspace collection and CRUD.",
|
|
15
|
+
commands: [
|
|
16
|
+
{
|
|
17
|
+
key: "get",
|
|
18
|
+
description: "List workspaces.",
|
|
19
|
+
handler: (ctx) => ({ workspaces: ctx.locals.db.workspaces.list() }),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: "post",
|
|
23
|
+
description: "Create a workspace.",
|
|
24
|
+
inputSchema: WorkspaceNameInputSchema,
|
|
25
|
+
handler: (ctx) => {
|
|
26
|
+
const { name } = ctx.inputsAs<{ name: string }>();
|
|
27
|
+
return ctx.locals.db.workspaces.create(name);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: ":id",
|
|
32
|
+
description: "One workspace by id.",
|
|
33
|
+
commands: [
|
|
34
|
+
{
|
|
35
|
+
key: "get",
|
|
36
|
+
description: "Get one workspace.",
|
|
37
|
+
handler: (ctx) => {
|
|
38
|
+
const id = ctx.inputsAs<{ id: string }>().id;
|
|
39
|
+
const ws = ctx.locals.db.workspaces.get(id);
|
|
40
|
+
if (!ws) {
|
|
41
|
+
notFound(ctx, id);
|
|
42
|
+
}
|
|
43
|
+
return ws;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "put",
|
|
48
|
+
description: "Replace a workspace.",
|
|
49
|
+
inputSchema: WorkspaceNameInputSchema,
|
|
50
|
+
handler: (ctx) => {
|
|
51
|
+
const { id, name } = ctx.inputsAs<{ id: string; name: string }>();
|
|
52
|
+
const ws = ctx.locals.db.workspaces.replace(id, name);
|
|
53
|
+
if (!ws) {
|
|
54
|
+
notFound(ctx, id);
|
|
55
|
+
}
|
|
56
|
+
return ws;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "patch",
|
|
61
|
+
description: "Patch a workspace name.",
|
|
62
|
+
inputSchema: WorkspaceNameInputSchema,
|
|
63
|
+
handler: (ctx) => {
|
|
64
|
+
const { id, name } = ctx.inputsAs<{ id: string; name: string }>();
|
|
65
|
+
const ws = ctx.locals.db.workspaces.patch(id, name);
|
|
66
|
+
if (!ws) {
|
|
67
|
+
notFound(ctx, id);
|
|
68
|
+
}
|
|
69
|
+
return ws;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: "delete",
|
|
74
|
+
description: "Delete a workspace.",
|
|
75
|
+
handler: (ctx) => {
|
|
76
|
+
const id = ctx.inputsAs<{ id: string }>().id;
|
|
77
|
+
if (!ctx.locals.db.workspaces.delete(id)) {
|
|
78
|
+
notFound(ctx, id);
|
|
79
|
+
}
|
|
80
|
+
ctx.respond({ status: 204, body: "" });
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
} satisfies CliRouter;
|
|
87
|
+
|
|
88
|
+
/** Test program with workspaces registered. */
|
|
89
|
+
export function workspacesTestProgram(base: CliProgram): CliProgram {
|
|
90
|
+
return {
|
|
91
|
+
...base,
|
|
92
|
+
commands: [workspacesCommand],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import type { ReadinessContext } from "argsbarg";
|
|
3
|
+
import { AppDb } from ".";
|
|
4
|
+
|
|
5
|
+
describe("AppDb", () => {
|
|
6
|
+
let appDb: AppDb;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
appDb = AppDb.open();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
appDb.close();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("workspace CRUD round trip", () => {
|
|
17
|
+
const workspaces = appDb.workspaces;
|
|
18
|
+
expect(workspaces.list()).toEqual([]);
|
|
19
|
+
const created = workspaces.create("alpha");
|
|
20
|
+
expect(workspaces.get(created.id)).toEqual(created);
|
|
21
|
+
expect(workspaces.list()).toEqual([created]);
|
|
22
|
+
expect(workspaces.patch(created.id, "beta")).toEqual({ ...created, name: "beta" });
|
|
23
|
+
expect(workspaces.replace(created.id, "gamma")).toEqual({ id: created.id, name: "gamma" });
|
|
24
|
+
expect(workspaces.delete(created.id)).toBe(true);
|
|
25
|
+
expect(workspaces.get(created.id)).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("ping succeeds on open database", () => {
|
|
29
|
+
expect(() => appDb.ping()).not.toThrow();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("AppDb.openWithRetry", () => {
|
|
34
|
+
test("opens an in-memory database", () => {
|
|
35
|
+
const appDb = AppDb.openWithRetry(1);
|
|
36
|
+
try {
|
|
37
|
+
appDb.workspaces.create("retry-ok");
|
|
38
|
+
expect(appDb.workspaces.list()).toHaveLength(1);
|
|
39
|
+
} finally {
|
|
40
|
+
appDb.close();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("AppDb.attach", () => {
|
|
46
|
+
test("sets ctx.locals.db", () => {
|
|
47
|
+
const locals = {} as import("argsbarg").CliLocals;
|
|
48
|
+
AppDb.attach({ locals, invocation: "cli" });
|
|
49
|
+
locals.db.workspaces.create("attached");
|
|
50
|
+
expect(locals.db.workspaces.list()).toHaveLength(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("AppDb.checkReadiness", () => {
|
|
55
|
+
test("returns false before server database is initialized", () => {
|
|
56
|
+
const runtime = {
|
|
57
|
+
state: {},
|
|
58
|
+
program: { key: "t", description: "d" },
|
|
59
|
+
surface: "http" as const,
|
|
60
|
+
};
|
|
61
|
+
const ctx = {
|
|
62
|
+
program: runtime.program,
|
|
63
|
+
surface: "http" as const,
|
|
64
|
+
appConfig: { read: () => ({}) },
|
|
65
|
+
runtime,
|
|
66
|
+
} as unknown as ReadinessContext;
|
|
67
|
+
expect(AppDb.checkReadiness(ctx)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("returns true when sqlite responds", () => {
|
|
71
|
+
AppDb.resetForTests();
|
|
72
|
+
const runtime = {
|
|
73
|
+
state: { db: AppDb.open() },
|
|
74
|
+
program: { key: "t", description: "d" },
|
|
75
|
+
surface: "http" as const,
|
|
76
|
+
};
|
|
77
|
+
const ctx = {
|
|
78
|
+
program: runtime.program,
|
|
79
|
+
surface: "http" as const,
|
|
80
|
+
appConfig: { read: () => ({}) },
|
|
81
|
+
runtime,
|
|
82
|
+
} as unknown as ReadinessContext;
|
|
83
|
+
expect(AppDb.checkReadiness(ctx)).toBe(true);
|
|
84
|
+
(runtime.state.db as AppDb).close();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
App-wide in-memory SQLite database.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
import type { InvokeHookContext, ReadinessContext } from "argsbarg";
|
|
7
|
+
import { migrate } from "./migrate.ts";
|
|
8
|
+
import { WorkspacesTable } from "./tables/workspaces.ts";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_RETRY_DELAY_MS = 200;
|
|
11
|
+
const MAX_RETRY_DELAY_MS = 5000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Single SQLite database for this app.
|
|
15
|
+
* Migrations run on construction; table accessors hang off {@link workspaces}.
|
|
16
|
+
*/
|
|
17
|
+
export class AppDb {
|
|
18
|
+
/** CLI singleton; server invocations use `runtime.state.db` instead. */
|
|
19
|
+
private static db: AppDb | undefined;
|
|
20
|
+
|
|
21
|
+
/** Workspace rows and queries for this connection. */
|
|
22
|
+
readonly workspaces: WorkspacesTable;
|
|
23
|
+
|
|
24
|
+
constructor(readonly sqlite: Database) {
|
|
25
|
+
migrate(sqlite);
|
|
26
|
+
this.workspaces = new WorkspacesTable(sqlite);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Open a fresh in-memory database with foreign keys enabled. */
|
|
30
|
+
static open(): AppDb {
|
|
31
|
+
const sqlite = new Database(":memory:", { create: true });
|
|
32
|
+
sqlite.run("PRAGMA foreign_keys = ON");
|
|
33
|
+
return new AppDb(sqlite);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Open with bounded backoff, retrying until SQLite accepts connections (HTTP/MCP startup). */
|
|
37
|
+
static openWithRetry(delayMs = DEFAULT_RETRY_DELAY_MS): AppDb {
|
|
38
|
+
let attempt = 0;
|
|
39
|
+
while (true) {
|
|
40
|
+
try {
|
|
41
|
+
const appDb = AppDb.open();
|
|
42
|
+
appDb.ping();
|
|
43
|
+
return appDb;
|
|
44
|
+
} catch {
|
|
45
|
+
attempt++;
|
|
46
|
+
Bun.sleepSync(Math.min(delayMs * attempt, MAX_RETRY_DELAY_MS));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Shared database for non-server invocations (lazy CLI singleton). */
|
|
52
|
+
static openDb(): AppDb {
|
|
53
|
+
AppDb.db ??= AppDb.open();
|
|
54
|
+
return AppDb.db;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Replace the CLI singleton with a fresh in-memory database (tests). */
|
|
58
|
+
static resetForTests(): void {
|
|
59
|
+
AppDb.db?.close();
|
|
60
|
+
AppDb.db = AppDb.open();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wire `ctx.locals.db` before handlers run.
|
|
65
|
+
* Server runtimes open with retry into `runtime.state.db`; CLI uses {@link openDb}.
|
|
66
|
+
*/
|
|
67
|
+
static attach(
|
|
68
|
+
ctx: Pick<InvokeHookContext, "locals" | "invocation"> & { runtime?: InvokeHookContext["runtime"] },
|
|
69
|
+
): void {
|
|
70
|
+
if (ctx.runtime && (ctx.invocation === "http" || ctx.invocation === "mcp")) {
|
|
71
|
+
ctx.runtime.state.db ??= AppDb.openWithRetry();
|
|
72
|
+
ctx.locals.db = ctx.runtime.state.db;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
ctx.locals.db = AppDb.openDb();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Readiness probe: ping `runtime.state.db` when the server database is open. */
|
|
79
|
+
static checkReadiness(ctx: ReadinessContext): boolean {
|
|
80
|
+
const appDb = ctx.runtime.state.db;
|
|
81
|
+
if (!appDb) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
appDb.ping();
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Verify SQLite responds to a trivial query. */
|
|
93
|
+
ping(): void {
|
|
94
|
+
this.sqlite.query("SELECT 1 AS ok").get();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Close the underlying database handle. */
|
|
98
|
+
close(): void {
|
|
99
|
+
this.sqlite.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
3
|
+
import { appliedMigrationVersion, listMigrationFiles, migrate } from "./migrate.ts";
|
|
4
|
+
|
|
5
|
+
function openTestDb(): Database {
|
|
6
|
+
const db = new Database(":memory:", { create: true });
|
|
7
|
+
db.run("PRAGMA foreign_keys = ON");
|
|
8
|
+
return db;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("migrate", () => {
|
|
12
|
+
let db: Database;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
db = openTestDb();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
db.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("lists migration files in version order", () => {
|
|
23
|
+
const files = listMigrationFiles();
|
|
24
|
+
expect(files.length).toBeGreaterThan(0);
|
|
25
|
+
expect(files[0]?.name).toBe("001_workspaces.sql");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("applies pending migrations once", () => {
|
|
29
|
+
expect(appliedMigrationVersion(db)).toBe(0);
|
|
30
|
+
expect(migrate(db)).toBe(1);
|
|
31
|
+
expect(appliedMigrationVersion(db)).toBe(1);
|
|
32
|
+
expect(migrate(db)).toBe(0);
|
|
33
|
+
expect(db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'workspaces'").get()).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
});
|