Skip to content

Commit 485a1bb

Browse files
UnbreakableMJclaude
andcommitted
Add Ion and POSIX/Bash-Brush ports of fill-keys
bin/fill-keys.sh (POSIX sh — Bash/Brush/dash/ash) and bin/fill-keys.ion (Ion) mirror bin/fill-keys.nu exactly: same four placeholders → env vars, hidden-input prompt fallback, gitignored dist/ mirror, partial-fill and non-interactive/agent safety. The shell ports use `sd -s` (literal) for substitution; the Nushell port uses native string ops. Notable shell quirks handled: Ion's `test -t` is unreliable (uses `tty -s`), Ion eats -h/--help, and Ion's `test` needs the POSIX x-prefix guard for --leading operands; POSIX prompts read from /dev/tty so the here-doc data on stdin isn't consumed. README + CLAUDE.md updated; both scripts carry SPDX headers (reuse lint clean, 23/23). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b4981ad commit 485a1bb

4 files changed

Lines changed: 376 additions & 7 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,4 @@ Every host template declares all nine. Two groups:
7676

7777
## Tooling
7878

79-
`bin/fill-keys.nu` (Nushell) substitutes the four placeholder tokens (`YOUR_CONTEXT7_API_KEY`, `YOUR_BRAVE_API_KEY`, `YOUR_GITHUB_PAT`, `/path/to/your/workspace`) with values from env vars (prompting for any unset ones when interactive) and writes filled copies into a **gitignored `dist/` mirror**it never edits the tracked templates, so the no-secrets rule holds. It has an explicit host-file list (the docs and VS Code's `${input:}`/`${workspaceFolder}` fields are deliberately left alone). If you add or rename a host config, update that list. It's the one executable (`755`) file here; everything else is `644` data.
79+
`bin/fill-keys.{nu,sh,ion}` substitute the four placeholder tokens (`YOUR_CONTEXT7_API_KEY`, `YOUR_BRAVE_API_KEY`, `YOUR_GITHUB_PAT`, `/path/to/your/workspace`) with values from env vars (prompting for any unset ones when interactive) and write filled copies into a **gitignored `dist/` mirror**they never edit the tracked templates, so the no-secrets rule holds. There are three parallel ports (Nushell, POSIX/Bash/Brush, Ion) with identical behavior — **change all three together**, plus their shared host-file list (the docs and VS Code's `${input:}`/`${workspaceFolder}` fields are deliberately left out). The `.sh`/`.ion` ports shell out to `sd` (literal `-s` mode); the `.nu` port uses native string ops. These are the only executables (`755`); everything else is `644` data. Shell-specific gotchas worth knowing if you edit them: Ion's `test -t` is unreliable (use `tty -s`), Ion eats `-h`/`--help`, and Ion's `test` needs the POSIX `x`-prefix guard for `--`-leading operands.

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,16 @@ need a token or path filled in before they work (see Notes).
4848

4949
## Filling in your keys
5050

51-
`bin/fill-keys.nu` (Nushell) substitutes the placeholders with your real values and
52-
writes ready-to-use copies into a gitignored `dist/` mirror — the tracked templates are
53-
never modified, so no secret is ever committed.
51+
`bin/fill-keys.*` substitutes the placeholders with your real values and writes
52+
ready-to-use copies into a gitignored `dist/` mirror — the tracked templates are never
53+
modified, so no secret is ever committed. The same tool is provided for three shells
54+
(pick whichever you run); all behave identically:
55+
56+
| Script | Shell | Notes |
57+
|--------|-------|-------|
58+
| `bin/fill-keys.nu` | Nushell | no external deps (native string ops) |
59+
| `bin/fill-keys.sh` | POSIX sh / Bash / Brush | needs [`sd`](https://github.com/chmln/sd) |
60+
| `bin/fill-keys.ion` | Ion | needs `sd`; Ion eats `-h`/`--help` itself, so see the file header for usage |
5461

5562
| Env var | Fills | Server |
5663
|---------|-------|--------|
@@ -61,12 +68,12 @@ never modified, so no secret is ever committed.
6168

6269
```sh
6370
# Provide values via env vars (any you omit are prompted for, or left as placeholders
64-
# when run non-interactively):
71+
# when run non-interactively). Use whichever script matches your shell:
6572
CONTEXT7_API_KEY=ctx7sk-... BRAVE_API_KEY=... GITHUB_PAT=ghp_... \
6673
WORKSPACE_PATH=/home/you/work \
67-
nu bin/fill-keys.nu
74+
nu bin/fill-keys.nu # or: sh bin/fill-keys.sh / ion bin/fill-keys.ion
6875

69-
nu bin/fill-keys.nu --help # options
76+
nu bin/fill-keys.nu --help # options (Nushell/POSIX; Ion: see header)
7077
nu bin/fill-keys.nu --out /tmp/mcp # write somewhere else
7178
```
7279

bin/fill-keys.ion

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env ion
2+
# SPDX-FileCopyrightText: 2026 Mohamed Hammad <Mohamed.Hammad@SpacecraftSoftware.org>
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
#
5+
# fill-keys.ion — Ion (Redox) port of fill-keys.nu.
6+
#
7+
# Fills the MCP config templates' placeholders with real values and writes ready-to-use
8+
# copies into a gitignored output dir (default: dist/). Values come from env vars; any
9+
# unset ones are prompted for (hidden input) when interactive, or left as placeholders
10+
# when not (CI/agent). The tracked templates are never modified.
11+
#
12+
# Env var Fills placeholder Server
13+
# CONTEXT7_API_KEY YOUR_CONTEXT7_API_KEY context7
14+
# BRAVE_API_KEY YOUR_BRAVE_API_KEY brave-search
15+
# GITHUB_PAT YOUR_GITHUB_PAT github
16+
# WORKSPACE_PATH /path/to/your/workspace filesystem
17+
#
18+
# Requires: sd (cargo install sd / nix run nixpkgs#sd).
19+
#
20+
# Note: `test "x$v" = "x..."` uses the POSIX x-prefix guard — Ion's `test` treats a bare
21+
# leading `--word` operand as an option flag, so comparands must not start with `-`.
22+
23+
fn usage
24+
echo "Usage: fill-keys.ion [--out DIR]"
25+
echo ""
26+
echo " --out DIR output directory (gitignored), relative to the repo root [default: dist]"
27+
echo " -h, --help show this help"
28+
echo ""
29+
echo "Env vars: CONTEXT7_API_KEY, BRAVE_API_KEY, GITHUB_PAT, WORKSPACE_PATH"
30+
end
31+
32+
# Hidden/plain prompt on the controlling terminal; echoes the typed value to stdout.
33+
fn promptval label:str secret:str
34+
if test "x$secret" = "x1"
35+
let old = $(stty -g < /dev/tty)
36+
stty -echo < /dev/tty
37+
printf '%s' $label > /dev/tty
38+
let reply = $(head -n 1 < /dev/tty)
39+
stty $old < /dev/tty
40+
printf '\n' > /dev/tty
41+
echo $reply
42+
else
43+
printf '%s' $label > /dev/tty
44+
let reply = $(head -n 1 < /dev/tty)
45+
echo $reply
46+
end
47+
end
48+
49+
# --- argument parsing ---
50+
# @args[0] is the script path; Ion only lets us read the args array whole (@args),
51+
# so capture the first element in the loop. (Ion eats -h/--help as its own flags, so
52+
# the --help branch below is best-effort; see the header comment for usage.)
53+
let out = "dist"
54+
let want_out = 0
55+
let idx = 0
56+
let script = ""
57+
for a in @args
58+
if test "x$idx" = "x0"
59+
let script = $a
60+
let idx = 1
61+
else if test "x$want_out" = "x1"
62+
let out = $a
63+
let want_out = 0
64+
else if test "x$a" = "x--out"
65+
let want_out = 1
66+
else if test "x$a" = "x--help"
67+
usage
68+
exit 0
69+
else if test "x$a" = "x-h"
70+
usage
71+
exit 0
72+
end
73+
end
74+
75+
# --- sd preflight ---
76+
let have_sd = 0
77+
if exists -b sd
78+
let have_sd = 1
79+
end
80+
if test "x$have_sd" = "x0"
81+
printf 'error: sd not found. Install it: cargo install sd (or: nix run nixpkgs#sd)\n' > /dev/stderr
82+
exit 1
83+
end
84+
85+
# --- paths ---
86+
let sdir = $(dirname $script)
87+
let repo = $(cd $sdir/.. && pwd)
88+
let first = $(printf '%s' $out | head -c 1)
89+
let out_root = "$repo/$out"
90+
if test "x$first" = "x/"
91+
let out_root = $out
92+
end
93+
94+
# --- interactive detection (tty -s is reliable in Ion; test -t is not) ---
95+
let interactive = 0
96+
if tty -s
97+
let interactive = 1
98+
end
99+
if exists -s CI
100+
let interactive = 0
101+
end
102+
if exists -s CLAUDECODE
103+
let interactive = 0
104+
end
105+
106+
# --- resolve each token: env var, else prompt, else empty ---
107+
let v_ctx = ""
108+
if exists -s CONTEXT7_API_KEY
109+
let v_ctx = $CONTEXT7_API_KEY
110+
end
111+
if test -z "$v_ctx" && test "x$interactive" = "x1"
112+
let v_ctx = $(promptval "CONTEXT7_API_KEY [blank to skip]: " "1")
113+
end
114+
115+
let v_brave = ""
116+
if exists -s BRAVE_API_KEY
117+
let v_brave = $BRAVE_API_KEY
118+
end
119+
if test -z "$v_brave" && test "x$interactive" = "x1"
120+
let v_brave = $(promptval "BRAVE_API_KEY [blank to skip]: " "1")
121+
end
122+
123+
let v_pat = ""
124+
if exists -s GITHUB_PAT
125+
let v_pat = $GITHUB_PAT
126+
end
127+
if test -z "$v_pat" && test "x$interactive" = "x1"
128+
let v_pat = $(promptval "GITHUB_PAT [blank to skip]: " "1")
129+
end
130+
131+
let v_ws = ""
132+
if exists -s WORKSPACE_PATH
133+
let v_ws = $WORKSPACE_PATH
134+
end
135+
if test -z "$v_ws" && test "x$interactive" = "x1"
136+
let v_ws = $(promptval "WORKSPACE_PATH [blank to skip]: " "0")
137+
end
138+
139+
let filled = 0
140+
let filled_names = ""
141+
let skipped_names = ""
142+
if test -n "$v_ctx"
143+
let filled += 1
144+
let filled_names ++= " CONTEXT7_API_KEY"
145+
else
146+
let skipped_names ++= " CONTEXT7_API_KEY"
147+
end
148+
if test -n "$v_brave"
149+
let filled += 1
150+
let filled_names ++= " BRAVE_API_KEY"
151+
else
152+
let skipped_names ++= " BRAVE_API_KEY"
153+
end
154+
if test -n "$v_pat"
155+
let filled += 1
156+
let filled_names ++= " GITHUB_PAT"
157+
else
158+
let skipped_names ++= " GITHUB_PAT"
159+
end
160+
if test -n "$v_ws"
161+
let filled += 1
162+
let filled_names ++= " WORKSPACE_PATH"
163+
else
164+
let skipped_names ++= " WORKSPACE_PATH"
165+
end
166+
167+
# --- write filled copies ---
168+
let written = 0
169+
for src in [ "Antigravity/mcp_config.json" "VSCode/mcp.json" "GitHubCopilotCLI/mcp-config.json" "ClaudeCode/.mcp.json" "OpenClaude/.mcp.json" "Codex/config.toml" "Grok/config.toml" "Kimi/config.toml" "Gemini/settings.json" "Gemini/mcp-server-enablement.json" "Qwen/settings.json" "OpenCode/opencode.jsonc" "Goose/config.yaml" ]
170+
let from = "$repo/$src"
171+
let have = 0
172+
if exists -f $from
173+
let have = 1
174+
end
175+
if test "x$have" = "x0"
176+
printf 'error: missing template %s\n' $src > /dev/stderr
177+
exit 1
178+
end
179+
let dst = "$out_root/$src"
180+
mkdir -p $(dirname $dst)
181+
cp $from $dst
182+
if test -n "$v_ctx"
183+
sd -s "YOUR_CONTEXT7_API_KEY" $v_ctx $dst
184+
end
185+
if test -n "$v_brave"
186+
sd -s "YOUR_BRAVE_API_KEY" $v_brave $dst
187+
end
188+
if test -n "$v_pat"
189+
sd -s "YOUR_GITHUB_PAT" $v_pat $dst
190+
end
191+
if test -n "$v_ws"
192+
sd -s "/path/to/your/workspace" $v_ws $dst
193+
end
194+
let written += 1
195+
end
196+
197+
# --- summary ---
198+
printf 'Filled %s of 4 placeholders into %s/ (%s files)\n' $filled $out_root $written
199+
if test -n "$filled_names"
200+
printf 'Filled:%s\n' $filled_names
201+
end
202+
if test -n "$skipped_names"
203+
printf 'Left as placeholders, those servers stay inert:%s\n' $skipped_names
204+
end
205+
echo ""
206+
echo "Install destinations:"
207+
for line in [ "Antigravity/mcp_config.json -> Antigravity, editor-managed" "VSCode/mcp.json -> <workspace>/.vscode/mcp.json" "GitHubCopilotCLI/mcp-config.json -> ~/.copilot/mcp-config.json" "ClaudeCode/.mcp.json -> ~/.claude.json mcpServers" "OpenClaude/.mcp.json -> ~/.openclaude.json mcpServers" "Codex/config.toml -> ~/.codex/config.toml" "Grok/config.toml -> ~/.grok/config.toml" "Kimi/config.toml -> ~/.kimi-code/config.toml" "Gemini/settings.json -> ~/.gemini/settings.json" "Gemini/mcp-server-enablement.json -> ~/.gemini/mcp-server-enablement.json" "Qwen/settings.json -> ~/.qwen/settings.json" "OpenCode/opencode.jsonc -> ~/.config/opencode/opencode.jsonc" "Goose/config.yaml -> ~/.config/goose/config.yaml" ]
208+
echo " $line"
209+
end

0 commit comments

Comments
 (0)