argsbarg 6.1.10 → 6.2.2
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 +22 -1
- package/README.md +49 -49
- package/docs/README.md +5 -4
- package/docs/ai-skills.md +36 -23
- package/docs/bundled-docs.md +1 -1
- package/docs/cli-program.md +19 -20
- package/docs/config-schema.md +3 -3
- package/docs/configure.md +12 -16
- package/docs/developing.md +9 -9
- package/docs/mcp.md +21 -57
- package/docs/output-schema.md +3 -3
- package/examples/formats.ts +5 -6
- package/examples/full-example/AGENTS.md +75 -0
- package/examples/full-example/CLAUDE.md +1 -0
- package/examples/full-example/README.md +7 -69
- package/examples/full-example/docs/README.md +1 -1
- package/examples/full-example/docs/cli-schema.json +1 -1659
- package/examples/full-example/docs/cli.md +2 -1538
- package/examples/full-example/docs/http.md +0 -7
- package/examples/full-example/docs/mcp.md +23 -59
- package/examples/full-example/docs/openapi.json +2 -782
- package/examples/full-example/docs/skill.md +18 -14
- package/examples/full-example/justfile +5 -17
- package/examples/full-example/scripts/create-identity.ts +2 -1
- package/examples/full-example/src/commands/status/command.test.ts +2 -2
- package/examples/full-example/src/commands/status/command.ts +7 -6
- package/examples/full-example/src/program.ts +4 -10
- package/examples/full-example-json/AGENTS.md +86 -0
- package/examples/full-example-json/CLAUDE.md +1 -0
- package/examples/full-example-json/Formula/.gitkeep +0 -0
- package/examples/full-example-json/Formula/full-example-json.rb +35 -0
- package/examples/full-example-json/README.md +27 -0
- package/examples/full-example-json/biome.json +22 -0
- package/examples/full-example-json/bun.lock +48 -0
- package/examples/full-example-json/docs/README.md +27 -0
- package/examples/full-example-json/docs/cli-schema.json +2145 -0
- package/examples/full-example-json/docs/cli.md +1990 -0
- package/examples/full-example-json/docs/http.md +92 -0
- package/examples/full-example-json/docs/mcp.md +116 -0
- package/examples/full-example-json/docs/openapi.json +1246 -0
- package/examples/full-example-json/docs/skill.md +57 -0
- package/examples/full-example-json/justfile +171 -0
- package/examples/full-example-json/package.json +22 -0
- package/examples/full-example-json/scripts/create-identity.ts +12 -0
- package/examples/full-example-json/scripts/dev-formula.ts +97 -0
- package/examples/full-example-json/scripts/formula-shared.test.ts +68 -0
- package/examples/full-example-json/scripts/formula-shared.ts +170 -0
- package/examples/full-example-json/scripts/print-identity.ts +28 -0
- package/examples/full-example-json/scripts/release.ts +212 -0
- package/examples/full-example-json/src/commands/echo/command.ts +26 -0
- package/examples/full-example-json/src/commands/status/command.test.ts +10 -0
- package/examples/full-example-json/src/commands/status/command.ts +28 -0
- package/examples/full-example-json/src/index.ts +10 -0
- package/examples/full-example-json/src/program.ts +33 -0
- package/examples/full-example-json/src/types/md.d.ts +4 -0
- package/examples/full-example-json/tsconfig.json +17 -0
- package/examples/minimal.ts +17 -17
- package/examples/nested.ts +10 -10
- package/examples/option-required.ts +13 -13
- package/examples/servers.ts +10 -10
- package/index.d.ts +17 -43
- package/package.json +1 -1
- package/src/cli-tool/create.test.ts +44 -68
- package/src/cli-tool/create.ts +81 -17
- package/src/cli-tool/full-example-capabilities.test.ts +33 -18
- package/src/cli-tool/post-create.ts +31 -17
- package/src/cli-tool/program.ts +16 -7
- package/src/cli-tool/prompt.ts +27 -0
- package/src/cli-tool/run-create.ts +19 -7
- package/src/cli-tool/schemagen/schemagen.test.ts +3 -3
- package/src/configure/artifacts/install-validate.test.ts +20 -33
- package/src/configure/artifacts/paths.ts +9 -53
- package/src/configure/artifacts/status.test.ts +13 -16
- package/src/configure/artifacts/status.ts +5 -22
- package/src/configure/artifacts/target-base.ts +6 -15
- package/src/configure/artifacts/target-effective.ts +16 -54
- package/src/configure/artifacts/target-mcp-json.ts +2 -5
- package/src/configure/artifacts/target-registry.ts +0 -7
- package/src/configure/artifacts/target-scope.ts +7 -17
- package/src/configure/artifacts/target-skill.ts +6 -15
- package/src/configure/artifacts/target-types.ts +6 -54
- package/src/configure/artifacts/targets/agents-mcp.ts +11 -0
- package/src/configure/artifacts/targets/configure.ts +1 -5
- package/src/configure/artifacts/targets/index.ts +4 -44
- package/src/configure/artifacts/targets/skill.ts +12 -0
- package/src/configure/artifacts/targets.test.ts +21 -59
- package/src/configure/configure.test.ts +35 -46
- package/src/configure/index.ts +19 -19
- package/src/configure/prompt.ts +2 -12
- package/src/core/parse.test.ts +21 -32
- package/src/core/types.ts +18 -44
- package/src/core/validate.ts +28 -45
- package/src/docs/docs.test.ts +4 -4
- package/src/docs/mcp-guide.ts +41 -71
- package/src/docs/resolve.ts +1 -1
- package/src/exports/cli.ts +1 -1
- package/src/index.ts +1 -1
- package/src/skill/generate.ts +26 -45
- package/src/skill/install.ts +18 -38
- package/src/skill/naming.ts +3 -27
- package/src/test/integration/config.test.ts +3 -3
- package/src/test/integration/mcp.test.ts +4 -4
- package/{examples/mcp-test.ts → src/test/mcp-integration-fixture.ts} +20 -22
- package/src/configure/artifacts/target-mcp-cli.ts +0 -127
- package/src/configure/artifacts/targets/chatgpt-mcp.ts +0 -12
- package/src/configure/artifacts/targets/claude-code-mcp.ts +0 -15
- package/src/configure/artifacts/targets/claude-desktop-mcp.ts +0 -12
- package/src/configure/artifacts/targets/claude-skill.ts +0 -16
- package/src/configure/artifacts/targets/codex-mcp.ts +0 -25
- package/src/configure/artifacts/targets/codex-skill.ts +0 -14
- package/src/configure/artifacts/targets/cursor-mcp.ts +0 -15
- package/src/configure/artifacts/targets/cursor-skill.ts +0 -16
- package/src/configure/artifacts/targets/openclaw-mcp.ts +0 -25
- package/src/configure/artifacts/targets/openclaw-skill.ts +0 -17
- package/src/configure/artifacts/targets/opencode-mcp.ts +0 -96
- package/src/configure/artifacts/targets/opencode-skill.ts +0 -15
- /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/RenderJsonInputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/command.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/command.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/StatusJsonOutputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/index.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrate.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrate.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrations/001_workspaces.sql +0 -0
- /package/examples/{full-example → full-example-json}/src/db/tables/workspaces.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/types/argsbarg.d.ts +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: full-example-json
|
|
3
|
+
name: full-example-json
|
|
4
|
+
description: Operates the full-example-json CLI (echo, render-json, status, workspaces get, workspaces post, and 4 more). Use when the user mentions full-example-json, echo, render-json, status, or related tasks.
|
|
5
|
+
enabled: true
|
|
6
|
+
---
|
|
7
|
+
<!-- Generated by full-example-json docs skill --save; do not edit. -->
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# full-example-json
|
|
11
|
+
|
|
12
|
+
Argsbarg schema-first copy template (@sg schemagen, JSON schemas, REST CRUD)
|
|
13
|
+
|
|
14
|
+
## Execution
|
|
15
|
+
|
|
16
|
+
Invoke via shell:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
full-example-json <subcommand> [options] [args]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
- **`full-example-json echo`** — Echo a message (MCP-friendly leaf).
|
|
25
|
+
- **`full-example-json render-json`** — Echo a JSON message (schema-first JSON leaf demo).
|
|
26
|
+
- **`full-example-json status`** — Show app version. (flags: --json)
|
|
27
|
+
- **`full-example-json workspaces get`** — List workspaces.
|
|
28
|
+
- **`full-example-json workspaces post`** — Create a workspace.
|
|
29
|
+
- **`full-example-json workspaces :id get`** — Get one workspace.
|
|
30
|
+
- **`full-example-json workspaces :id put`** — Replace a workspace.
|
|
31
|
+
- **`full-example-json workspaces :id patch`** — Patch a workspace name.
|
|
32
|
+
- **`full-example-json workspaces :id delete`** — Delete a workspace.
|
|
33
|
+
|
|
34
|
+
## Pitfalls
|
|
35
|
+
|
|
36
|
+
- Pass `--` before arguments that look like flags.
|
|
37
|
+
|
|
38
|
+
## Reference
|
|
39
|
+
|
|
40
|
+
For full detail, open `reference.md` in this skill directory (same as `full-example-json docs cli`).
|
|
41
|
+
|
|
42
|
+
## Install location
|
|
43
|
+
|
|
44
|
+
Install follows the https://dotagentsprotocol.com:
|
|
45
|
+
|
|
46
|
+
- Auto-install: `full-example-json configure --sync --yes` when `skill.enabled` → `~/.agents/skills/full-example-json/`
|
|
47
|
+
- Cursor and most coding agents read `~/.agents/skills/` natively
|
|
48
|
+
|
|
49
|
+
**Claude Code (manual):** symlink or copy into Claude's skill directory:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
mkdir -p ~/.claude/skills
|
|
53
|
+
ln -sf ~/.agents/skills/full-example-json ~/.claude/skills/full-example-json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Project override (optional): `.agents/skills/full-example-json/`
|
|
57
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
|
|
2
|
+
|
|
3
|
+
export PATH := justfile_directory() + "/node_modules/.bin:" + env_var("PATH")
|
|
4
|
+
|
|
5
|
+
cli_key := `bun scripts/print-identity.ts key`
|
|
6
|
+
tap_org := `bun scripts/print-identity.ts tapOrg`
|
|
7
|
+
tap_repo := `bun scripts/print-identity.ts tapRepo`
|
|
8
|
+
tap := `bun scripts/print-identity.ts tap`
|
|
9
|
+
release_repo := `bun scripts/print-identity.ts releaseRepo`
|
|
10
|
+
tap_git_url := "git@github.com:" + release_repo + ".git"
|
|
11
|
+
brew_prefix := `brew --prefix`
|
|
12
|
+
tap_parent := brew_prefix + "/Library/Taps/" + tap_org
|
|
13
|
+
tap_path := tap_parent + "/homebrew-" + tap_repo
|
|
14
|
+
|
|
15
|
+
# List available recipes (default)
|
|
16
|
+
_:
|
|
17
|
+
@just --list
|
|
18
|
+
|
|
19
|
+
# Compile the CLI binary to dist/full-example
|
|
20
|
+
build:
|
|
21
|
+
bun build ./src/index.ts --compile --outfile=dist/{{cli_key}}
|
|
22
|
+
@rm -f .*.bun-build
|
|
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
|
+
|
|
34
|
+
# Run schemagen, typecheck, and format
|
|
35
|
+
check: schemagen format typecheck
|
|
36
|
+
|
|
37
|
+
# demo the HTTP API
|
|
38
|
+
demo-http:
|
|
39
|
+
#!/usr/bin/env bash
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
bun ./src/index.ts http --port 13000 &
|
|
42
|
+
trap 'kill $! 2>/dev/null || true' EXIT
|
|
43
|
+
until curl -sf http://127.0.0.1:13000/health/liveness >/dev/null; do sleep 0.1; done
|
|
44
|
+
|
|
45
|
+
# demo a CLI command
|
|
46
|
+
demo-cli:
|
|
47
|
+
@just run {{cli_key}} status
|
|
48
|
+
|
|
49
|
+
# demo a CLI command
|
|
50
|
+
demo-help:
|
|
51
|
+
@just run {{cli_key}} --help
|
|
52
|
+
|
|
53
|
+
# Run the CLI from source with optional args; restarts on file changes
|
|
54
|
+
dev *ARGS:
|
|
55
|
+
bun --watch ./src/index.ts {{ARGS}}
|
|
56
|
+
|
|
57
|
+
# Regenerate consumer docs under ./docs/
|
|
58
|
+
docgen: schemagen
|
|
59
|
+
@just run docs cli-schema --save
|
|
60
|
+
@just run docs cli --save
|
|
61
|
+
@just run docs skill --save
|
|
62
|
+
@just run docs mcp --save
|
|
63
|
+
@just run docs http --save
|
|
64
|
+
@just run docs openapi --save
|
|
65
|
+
|
|
66
|
+
alias fmt := format
|
|
67
|
+
|
|
68
|
+
# Format and lint sources (auto-fix)
|
|
69
|
+
format:
|
|
70
|
+
bun run biome check ./src ./scripts --write --unsafe
|
|
71
|
+
|
|
72
|
+
# Run the HTTP server
|
|
73
|
+
http:
|
|
74
|
+
@just run http
|
|
75
|
+
|
|
76
|
+
# Alias for backward compatibility
|
|
77
|
+
install: install-local
|
|
78
|
+
|
|
79
|
+
# Refresh skills and MCP without reinstalling the formula
|
|
80
|
+
sync-artifacts:
|
|
81
|
+
just run configure --sync --yes
|
|
82
|
+
|
|
83
|
+
# Dev install: build, stage dev formula, brew install, restore release formula
|
|
84
|
+
install-local: build
|
|
85
|
+
@brew untap {{tap}} 2>/dev/null || true
|
|
86
|
+
mkdir -p {{tap_parent}}
|
|
87
|
+
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
88
|
+
bun scripts/dev-formula.ts install
|
|
89
|
+
brew install --formula {{tap}}/{{cli_key}}
|
|
90
|
+
bun scripts/dev-formula.ts reset
|
|
91
|
+
@echo ""
|
|
92
|
+
@echo "Next: {{cli_key}} configure"
|
|
93
|
+
|
|
94
|
+
# Remove local dev install, then install from GitHub tap (requires gh auth login)
|
|
95
|
+
install-production: uninstall
|
|
96
|
+
brew tap {{release_repo}} {{tap_git_url}}
|
|
97
|
+
brew install --formula {{release_repo}}/{{cli_key}}
|
|
98
|
+
@echo ""
|
|
99
|
+
@echo "Next: {{cli_key}} configure"
|
|
100
|
+
|
|
101
|
+
# Alias for backward compatibility
|
|
102
|
+
reinstall: reinstall-local
|
|
103
|
+
|
|
104
|
+
# Rebuild binary and swap into Cellar (run install-local first)
|
|
105
|
+
reinstall-local: build
|
|
106
|
+
install -m 755 dist/{{cli_key}} "$(brew --prefix {{cli_key}})/bin/{{cli_key}}"
|
|
107
|
+
|
|
108
|
+
# Lint sources without writing
|
|
109
|
+
lint:
|
|
110
|
+
bun run biome check ./src ./scripts
|
|
111
|
+
|
|
112
|
+
# Run the CLI from source once
|
|
113
|
+
run *ARGS:
|
|
114
|
+
bun ./src/index.ts {{ARGS}}
|
|
115
|
+
|
|
116
|
+
# Generate JSON Schema artifacts from TypeScript types
|
|
117
|
+
schemagen:
|
|
118
|
+
argsbarg schemagen
|
|
119
|
+
|
|
120
|
+
# Install bun/npm dependencies
|
|
121
|
+
# Install bun/npm dependencies and generate schemas
|
|
122
|
+
setup:
|
|
123
|
+
bun install
|
|
124
|
+
just schemagen
|
|
125
|
+
|
|
126
|
+
# Run unit tests (after check)
|
|
127
|
+
test: check
|
|
128
|
+
bun test .
|
|
129
|
+
|
|
130
|
+
# Bump version, build, publish; or pass --purge to delete stale GitHub releases
|
|
131
|
+
release *ARGS:
|
|
132
|
+
bun scripts/release.ts {{ARGS}}
|
|
133
|
+
|
|
134
|
+
# Install release formula from tap and run formula test
|
|
135
|
+
test-release:
|
|
136
|
+
brew untap {{tap}} 2>/dev/null || true
|
|
137
|
+
mkdir -p {{tap_parent}}
|
|
138
|
+
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
139
|
+
brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
140
|
+
brew install --formula {{tap}}/{{cli_key}}
|
|
141
|
+
brew test {{cli_key}}
|
|
142
|
+
|
|
143
|
+
# Typecheck without emitting build artifacts
|
|
144
|
+
typecheck:
|
|
145
|
+
bun run tsc --noEmit
|
|
146
|
+
|
|
147
|
+
# Undo just install-local: agent artifacts first, then formula
|
|
148
|
+
uninstall: uninstall-artifacts uninstall-formula
|
|
149
|
+
|
|
150
|
+
# Remove agent artifacts only (skills, MCP)
|
|
151
|
+
uninstall-artifacts:
|
|
152
|
+
just run configure --remove-all --yes
|
|
153
|
+
|
|
154
|
+
# Remove app config file only
|
|
155
|
+
uninstall-config:
|
|
156
|
+
just run configure --remove-config --yes
|
|
157
|
+
|
|
158
|
+
# Remove formula and untap
|
|
159
|
+
uninstall-formula:
|
|
160
|
+
@brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
161
|
+
@brew uninstall --formula {{tap}}/{{cli_key}}-local 2>/dev/null || true
|
|
162
|
+
@brew untap {{tap}} 2>/dev/null || true
|
|
163
|
+
|
|
164
|
+
# Remove release formula (does not untap)
|
|
165
|
+
uninstall-release:
|
|
166
|
+
@brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
167
|
+
|
|
168
|
+
# Remove release formula and untap
|
|
169
|
+
uninstall-release-tap:
|
|
170
|
+
@brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
171
|
+
@brew untap {{tap}} 2>/dev/null || true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "full-example-json",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"description": "Argsbarg schema-first copy template (@sg schemagen; not published to npm).",
|
|
7
|
+
"engines": {
|
|
8
|
+
"bun": ">=1.3"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"biome": "biome",
|
|
12
|
+
"start": "bun run src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"argsbarg": "file:../.."
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@biomejs/biome": "^2.5.0",
|
|
19
|
+
"@types/bun": "^1.3.12",
|
|
20
|
+
"typescript": "^5.9.3"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** CLI identity — substituted by `argsbarg create`. */
|
|
2
|
+
|
|
3
|
+
export const createIdentity = {
|
|
4
|
+
key: "full-example-json",
|
|
5
|
+
className: "FullExampleJson",
|
|
6
|
+
tap: "bdombro/bun-argsbarg",
|
|
7
|
+
homepage: "https://github.com/bdombro/bun-argsbarg",
|
|
8
|
+
releaseRepo: "bdombro/bun-argsbarg",
|
|
9
|
+
desc: "Argsbarg schema-first copy template (@sg schemagen, JSON schemas, REST CRUD)",
|
|
10
|
+
envPrefix: "FULL_EXAMPLE_JSON",
|
|
11
|
+
template: "json",
|
|
12
|
+
} as const;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Stage or restore the dev Homebrew formula for `just install-local`.
|
|
4
|
+
* `install` — back up release `Formula/full-example.rb` and write a `file://` dev formula.
|
|
5
|
+
* `reset` — restore the release formula from backup.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { createIdentity } from "./create-identity.ts";
|
|
12
|
+
import { renderDevFormula } from "./formula-shared.ts";
|
|
13
|
+
|
|
14
|
+
const { key } = createIdentity;
|
|
15
|
+
const root = join(import.meta.dir, "..");
|
|
16
|
+
const stagingDir = join(root, "Formula", ".staging");
|
|
17
|
+
const stagingPath = join(stagingDir, key);
|
|
18
|
+
const formulaPath = join(root, "Formula", `${key}.rb`);
|
|
19
|
+
const backupPath = join(stagingDir, `${key}.rb.bak`);
|
|
20
|
+
const programPath = join(root, "src/program.ts");
|
|
21
|
+
|
|
22
|
+
type Command = "install" | "reset";
|
|
23
|
+
|
|
24
|
+
function usage(): never {
|
|
25
|
+
process.stderr.write("Usage: bun scripts/dev-formula.ts <install|reset>\n");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseCommand(argv: string[]): Command {
|
|
30
|
+
const cmd = argv[2];
|
|
31
|
+
if (cmd === "install" || cmd === "reset") {
|
|
32
|
+
return cmd;
|
|
33
|
+
}
|
|
34
|
+
usage();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function backupReleaseFormula(): void {
|
|
38
|
+
if (!existsSync(formulaPath)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
42
|
+
copyFileSync(formulaPath, backupPath);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function restoreReleaseFormula(): void {
|
|
46
|
+
if (!existsSync(backupPath)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
copyFileSync(backupPath, formulaPath);
|
|
50
|
+
unlinkSync(backupPath);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readVersion(): string {
|
|
54
|
+
const content = readFileSync(programPath, "utf8");
|
|
55
|
+
const match = /version:\s*"([^"]+)"/.exec(content);
|
|
56
|
+
if (!match?.[1]) {
|
|
57
|
+
process.stderr.write(`Could not read version from ${programPath}\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
return match[1];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function stageDevFormula(): void {
|
|
64
|
+
const distPath = join(root, "dist", key);
|
|
65
|
+
if (!existsSync(distPath)) {
|
|
66
|
+
process.stderr.write(`Missing binary: ${distPath} (run just build first)\n`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
70
|
+
copyFileSync(distPath, stagingPath);
|
|
71
|
+
chmodSync(stagingPath, 0o755);
|
|
72
|
+
|
|
73
|
+
const sha256 = createHash("sha256").update(readFileSync(stagingPath)).digest("hex");
|
|
74
|
+
writeFileSync(formulaPath, renderDevFormula(stagingPath, readVersion(), sha256), "utf8");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function install(): void {
|
|
78
|
+
backupReleaseFormula();
|
|
79
|
+
stageDevFormula();
|
|
80
|
+
console.log(`Wrote dev formula: ${formulaPath}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function reset(): void {
|
|
84
|
+
if (!existsSync(backupPath)) {
|
|
85
|
+
console.log("No dev formula backup to restore.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
restoreReleaseFormula();
|
|
89
|
+
console.log(`Restored ${formulaPath}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const command = parseCommand(process.argv);
|
|
93
|
+
if (command === "install") {
|
|
94
|
+
install();
|
|
95
|
+
} else {
|
|
96
|
+
reset();
|
|
97
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import {
|
|
8
|
+
buildReleaseArchive,
|
|
9
|
+
releaseArchiveName,
|
|
10
|
+
releaseFormulaUrl,
|
|
11
|
+
selectStaleReleaseTags,
|
|
12
|
+
} from "./formula-shared.ts";
|
|
13
|
+
|
|
14
|
+
describe("releaseArchiveName", () => {
|
|
15
|
+
test("returns key.zip", () => {
|
|
16
|
+
expect(releaseArchiveName()).toBe("full-example-json.zip");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("releaseFormulaUrl", () => {
|
|
21
|
+
test("points at zip asset on GitHub releases", () => {
|
|
22
|
+
expect(releaseFormulaUrl("1.2.3")).toBe(
|
|
23
|
+
"https://github.com/bdombro/bun-argsbarg/releases/download/v1.2.3/full-example-json.zip",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("selectStaleReleaseTags", () => {
|
|
29
|
+
test("returns empty for zero or one release", () => {
|
|
30
|
+
expect(selectStaleReleaseTags([])).toEqual([]);
|
|
31
|
+
expect(selectStaleReleaseTags([{ tagName: "v1.0.0", publishedAt: "2026-01-01T00:00:00Z" }])).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("keeps newest by publishedAt and returns the rest", () => {
|
|
35
|
+
const releases = [
|
|
36
|
+
{ tagName: "v1.0.0", publishedAt: "2026-01-01T00:00:00Z" },
|
|
37
|
+
{ tagName: "v1.1.0", publishedAt: "2026-02-01T00:00:00Z" },
|
|
38
|
+
{ tagName: "v1.0.1", publishedAt: "2026-01-15T00:00:00Z" },
|
|
39
|
+
];
|
|
40
|
+
expect(selectStaleReleaseTags(releases)).toEqual(["v1.0.1", "v1.0.0"]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("buildReleaseArchive", () => {
|
|
45
|
+
const workDirs: string[] = [];
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
for (const dir of workDirs.splice(0)) {
|
|
49
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("creates zip with binary at archive root and matching sha256", async () => {
|
|
54
|
+
const work = mkdtempSync(join(tmpdir(), "argsbarg-release-zip-"));
|
|
55
|
+
workDirs.push(work);
|
|
56
|
+
const binaryPath = join(work, "full-example-json");
|
|
57
|
+
writeFileSync(binaryPath, "#!/bin/sh\necho hi\n", { mode: 0o755 });
|
|
58
|
+
|
|
59
|
+
const { archivePath, sha256 } = await buildReleaseArchive(binaryPath);
|
|
60
|
+
|
|
61
|
+
expect(archivePath).toBe(`${binaryPath}.zip`);
|
|
62
|
+
const onDisk = createHash("sha256").update(readFileSync(archivePath)).digest("hex");
|
|
63
|
+
expect(sha256).toBe(onDisk);
|
|
64
|
+
|
|
65
|
+
const listing = execFileSync("unzip", ["-l", archivePath], { encoding: "utf8" });
|
|
66
|
+
expect(listing).toContain("full-example-json");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** Shared Ruby fragments embedded in Homebrew formulae. */
|
|
2
|
+
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { $ } from "bun";
|
|
6
|
+
import { createIdentity } from "./create-identity.ts";
|
|
7
|
+
|
|
8
|
+
const { key, className, desc, homepage, releaseRepo } = createIdentity;
|
|
9
|
+
|
|
10
|
+
/** GitHub release row used by {@link selectStaleReleaseTags}. */
|
|
11
|
+
export interface ReleaseTag {
|
|
12
|
+
tagName: string;
|
|
13
|
+
publishedAt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const formulaInstallRuby = `def install
|
|
17
|
+
bin.install "${key}"
|
|
18
|
+
chmod 0755, bin/"${key}"
|
|
19
|
+
generate_completions_from_executable(bin/"${key}", "completion", base_name: "${key}")
|
|
20
|
+
end`;
|
|
21
|
+
|
|
22
|
+
export const formulaPostInstallRuby = `def post_install
|
|
23
|
+
system bin/"${key}", "configure", "--sync", "--yes"
|
|
24
|
+
end`;
|
|
25
|
+
|
|
26
|
+
export const formulaUninstallRuby = `def uninstall
|
|
27
|
+
system bin/"${key}", "configure", "--remove-all", "--yes"
|
|
28
|
+
end`;
|
|
29
|
+
|
|
30
|
+
export const formulaCaveatsRuby = `def caveats
|
|
31
|
+
<<~EOS
|
|
32
|
+
Run \`${key} configure\` to set up agent artifacts and app config (interactive).
|
|
33
|
+
Restart MCP chat apps (Cursor, Claude Desktop, etc.) after install or upgrade so they load the updated server.
|
|
34
|
+
EOS
|
|
35
|
+
end`;
|
|
36
|
+
|
|
37
|
+
export const formulaTestRuby = `test do
|
|
38
|
+
assert_match version.to_s, shell_output("#{bin}/${key} version")
|
|
39
|
+
assert_predicate bash_completion/"${key}", :exist?
|
|
40
|
+
assert_predicate zsh_completion/"_${key}", :exist?
|
|
41
|
+
assert_predicate fish_completion/"${key}.fish", :exist?
|
|
42
|
+
end`;
|
|
43
|
+
|
|
44
|
+
export interface FormulaCoords {
|
|
45
|
+
url: string;
|
|
46
|
+
urlStanza: string;
|
|
47
|
+
version: string;
|
|
48
|
+
/** SHA-256 hex digest of the release archive at `url` (zip for GitHub releases). */
|
|
49
|
+
sha256: string;
|
|
50
|
+
/** When true, embed {@link githubPrivateReleaseDownloadStrategyRuby} in the formula class. */
|
|
51
|
+
privateRelease?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Release asset filename on GitHub (zip containing the bare binary at archive root). */
|
|
55
|
+
export function releaseArchiveName(): string {
|
|
56
|
+
return `${key}.zip`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Build `{binaryPath}.zip` from a compiled binary; return archive path and sha256 of the zip. */
|
|
60
|
+
export async function buildReleaseArchive(binaryPath: string): Promise<{ archivePath: string; sha256: string }> {
|
|
61
|
+
const archivePath = `${binaryPath}.zip`;
|
|
62
|
+
const result = await $`zip -j -9 ${archivePath} ${binaryPath}`.nothrow();
|
|
63
|
+
if (result.exitCode !== 0) {
|
|
64
|
+
throw new Error(`zip failed: ${result.stderr}`);
|
|
65
|
+
}
|
|
66
|
+
const sha256 = createHash("sha256").update(readFileSync(archivePath)).digest("hex");
|
|
67
|
+
return { archivePath, sha256 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Tags to delete when keeping only the newest release (sorted by `publishedAt` desc). */
|
|
71
|
+
export function selectStaleReleaseTags(releases: ReleaseTag[]): string[] {
|
|
72
|
+
if (releases.length <= 1) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const sorted = [...releases].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt));
|
|
76
|
+
return sorted.slice(1).map((r) => r.tagName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** GitHub `org/repo` slug for release and purge commands. */
|
|
80
|
+
export const releaseRepoSlug = releaseRepo;
|
|
81
|
+
|
|
82
|
+
/** Resolves private GitHub release assets via the API at download time. */
|
|
83
|
+
export const githubPrivateReleaseDownloadStrategyRuby = ` # Private/internal releases: default CurlDownloadStrategy cannot fetch non-public
|
|
84
|
+
# GitHub release assets. Resolve the asset via the API and authenticate with
|
|
85
|
+
# GitHub::API.credentials (set up via \`gh auth login\` or HOMEBREW_GITHUB_API_TOKEN).
|
|
86
|
+
class GitHubPrivateReleaseDownloadStrategy < CurlDownloadStrategy
|
|
87
|
+
def initialize(url, name, version, **meta)
|
|
88
|
+
super
|
|
89
|
+
pattern = %r{https://github\\.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(\\S+)}
|
|
90
|
+
match = url.match(pattern)
|
|
91
|
+
raise CurlDownloadStrategyError, "Invalid GitHub release URL: #{url}" unless match
|
|
92
|
+
@owner, @repo, @tag, @filename = match.captures
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def _fetch(url:, resolved_url: resolved_download_url, timeout:)
|
|
96
|
+
curl_download resolved_download_url,
|
|
97
|
+
"--header", "Accept: application/octet-stream",
|
|
98
|
+
"--header", "Authorization: Bearer #{GitHub::API.credentials}",
|
|
99
|
+
to: temporary_path
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def resolved_download_url
|
|
105
|
+
@resolved_download_url ||= begin
|
|
106
|
+
asset = GitHub.get_release(@owner, @repo, @tag).fetch("assets")
|
|
107
|
+
.find { |a| a["name"] == @filename }
|
|
108
|
+
raise CurlDownloadStrategyError, "Release asset not found: #{@filename}" unless asset
|
|
109
|
+
asset.fetch("url")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end`;
|
|
113
|
+
|
|
114
|
+
/** Homebrew `url` for local dev staging paths. */
|
|
115
|
+
export function devUrlStanza(url: string): string {
|
|
116
|
+
return `url "${url}"`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Homebrew `url` for GitHub release assets (uses {@link githubPrivateReleaseDownloadStrategyRuby}). */
|
|
120
|
+
export function releaseUrlStanza(url: string): string {
|
|
121
|
+
return `url "${url}",
|
|
122
|
+
using: GitHubPrivateReleaseDownloadStrategy`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function releaseFormulaUrl(version: string): string {
|
|
126
|
+
return `https://github.com/${releaseRepo}/releases/download/v${version}/${releaseArchiveName()}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function renderFormula(coords: FormulaCoords): string {
|
|
130
|
+
const strategyBlock = coords.privateRelease ? `\n${githubPrivateReleaseDownloadStrategyRuby}\n\n ` : "";
|
|
131
|
+
return `class ${className} < Formula
|
|
132
|
+
desc "${desc}"
|
|
133
|
+
homepage "${homepage}"
|
|
134
|
+
version "${coords.version}"
|
|
135
|
+
sha256 "${coords.sha256}"
|
|
136
|
+
|
|
137
|
+
${formulaInstallRuby}
|
|
138
|
+
|
|
139
|
+
${formulaPostInstallRuby}
|
|
140
|
+
|
|
141
|
+
${formulaUninstallRuby}
|
|
142
|
+
|
|
143
|
+
${formulaCaveatsRuby}
|
|
144
|
+
|
|
145
|
+
${formulaTestRuby}
|
|
146
|
+
${strategyBlock}${coords.urlStanza}
|
|
147
|
+
end
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function renderReleaseFormula(version: string, sha256: string): string {
|
|
152
|
+
const url = releaseFormulaUrl(version);
|
|
153
|
+
return renderFormula({
|
|
154
|
+
url,
|
|
155
|
+
urlStanza: releaseUrlStanza(url),
|
|
156
|
+
version,
|
|
157
|
+
sha256,
|
|
158
|
+
privateRelease: true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function renderDevFormula(stagingPath: string, version: string, sha256: string): string {
|
|
163
|
+
const url = `file://${stagingPath}`;
|
|
164
|
+
return renderFormula({
|
|
165
|
+
url,
|
|
166
|
+
urlStanza: devUrlStanza(url),
|
|
167
|
+
version,
|
|
168
|
+
sha256,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Print a field from scripts/create-identity.ts for justfile backticks. */
|
|
3
|
+
|
|
4
|
+
import { createIdentity } from "./create-identity.ts";
|
|
5
|
+
|
|
6
|
+
const field = process.argv[2];
|
|
7
|
+
if (!field) {
|
|
8
|
+
console.error("Usage: bun scripts/print-identity.ts <key|className|tap|tapOrg|tapRepo|releaseRepo|envPrefix>");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const [tapOrg, tapRepo] = createIdentity.tap.split("/");
|
|
13
|
+
const values: Record<string, string> = {
|
|
14
|
+
key: createIdentity.key,
|
|
15
|
+
className: createIdentity.className,
|
|
16
|
+
tap: createIdentity.tap,
|
|
17
|
+
tapOrg: tapOrg ?? "",
|
|
18
|
+
tapRepo: tapRepo ?? "",
|
|
19
|
+
envPrefix: createIdentity.envPrefix,
|
|
20
|
+
releaseRepo: createIdentity.releaseRepo,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const value = values[field];
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
console.error(`Unknown field: ${field}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
process.stdout.write(value);
|