argsbarg 3.4.2 → 3.6.0

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 (73) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +24 -8
  3. package/biome.json +29 -6
  4. package/bun.lock +22 -0
  5. package/docs/cli-program.md +75 -1
  6. package/docs/install.md +1 -1
  7. package/docs/mcp.md +45 -1
  8. package/docs/templates/cursor/rules/cli-program.mdc +15 -21
  9. package/index.d.ts +95 -50
  10. package/justfile +24 -6
  11. package/package.json +4 -2
  12. package/scripts/release.ts +26 -9
  13. package/src/builtins/builtins.test.ts +9 -4
  14. package/src/builtins/completion-bash.ts +74 -50
  15. package/src/builtins/completion-fish.ts +3 -8
  16. package/src/builtins/completion-group.ts +1 -1
  17. package/src/builtins/completion-zsh.ts +80 -42
  18. package/src/builtins/dispatch.ts +20 -16
  19. package/src/builtins/export.ts +19 -10
  20. package/src/builtins/index.ts +9 -4
  21. package/src/builtins/install.ts +10 -10
  22. package/src/builtins/mcp.ts +1 -1
  23. package/src/builtins/presentation.ts +8 -8
  24. package/src/builtins/scopes.ts +1 -1
  25. package/src/builtins/version.ts +1 -1
  26. package/src/completion.ts +4 -4
  27. package/src/context.ts +91 -15
  28. package/src/docs/api-guide.test.ts +2 -2
  29. package/src/docs/api-guide.ts +19 -5
  30. package/src/docs/builtin.ts +27 -8
  31. package/src/docs/docs.test.ts +18 -11
  32. package/src/docs/mcp-guide.ts +108 -25
  33. package/src/docs/resolve.ts +10 -3
  34. package/src/docs/save.ts +11 -3
  35. package/src/formats.test.ts +35 -0
  36. package/src/formats.ts +135 -0
  37. package/src/headless.test.ts +8 -16
  38. package/src/help.ts +73 -43
  39. package/src/hidden-mcpb.test.ts +7 -6
  40. package/src/hidden.ts +2 -2
  41. package/src/index.test.ts +120 -96
  42. package/src/index.ts +36 -24
  43. package/src/install/binary.ts +12 -5
  44. package/src/install/completions.ts +7 -3
  45. package/src/install/detect-installed.ts +29 -4
  46. package/src/install/gh-release-update.ts +31 -23
  47. package/src/install/index.ts +69 -19
  48. package/src/install/install.test.ts +31 -8
  49. package/src/install/mcp-codex.test.ts +57 -0
  50. package/src/install/mcp-codex.ts +125 -0
  51. package/src/install/mcp-config.ts +12 -5
  52. package/src/install/mcp-opencode.test.ts +98 -0
  53. package/src/install/mcp-opencode.ts +149 -0
  54. package/src/install/paths.ts +29 -3
  55. package/src/install/plan.ts +73 -6
  56. package/src/install/shell.ts +1 -4
  57. package/src/install/status.ts +12 -6
  58. package/src/install/uninstall.ts +38 -4
  59. package/src/install/update.test.ts +2 -2
  60. package/src/install/update.ts +3 -1
  61. package/src/invoke.ts +12 -9
  62. package/src/mcp/bundle.ts +36 -8
  63. package/src/mcp/env.ts +7 -13
  64. package/src/mcp/server.ts +12 -6
  65. package/src/mcp/tools.ts +83 -18
  66. package/src/mcp.ts +3 -3
  67. package/src/parse.ts +129 -27
  68. package/src/runtime.ts +22 -12
  69. package/src/schema.ts +11 -5
  70. package/src/skill/generate.ts +4 -4
  71. package/src/skill/install.ts +6 -2
  72. package/src/types.ts +24 -0
  73. package/src/validate.ts +75 -16
package/justfile CHANGED
@@ -1,11 +1,24 @@
1
1
  # https://github.com/casey/just — run `just` to list recipes.
2
2
 
3
+ set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
4
+
3
5
  _:
4
6
  @just --list
5
7
 
6
- # typecheck the codebase
7
- check-types:
8
- bun x tsc
8
+ # check the codebase
9
+ check: typecheck format
10
+
11
+ # Update local consumer apps: npm i argsbarg@latest, build, docgen
12
+ consumers-sync *apps:
13
+ #!/usr/bin/env bash
14
+ root="$(cd "{{justfile_directory()}}" && pwd)"
15
+ ss="$root/../../ss"
16
+ apps=(idp-trees sqsp-qa-tools sqsp-i18n-tools)
17
+ for app in "${apps[@]}"; do
18
+ dir="$(cd "$ss/$app" && pwd)"
19
+ echo "==> $app ($dir)"
20
+ (cd "$dir" && bun i argsbarg@latest && just build && just docgen && just install)
21
+ done
9
22
 
10
23
  # run the minimal example
11
24
  example *ARGS:
@@ -17,16 +30,20 @@ example-watch *ARGS:
17
30
 
18
31
  # format the codebase
19
32
  format:
20
- bun x biome format ./src ./scripts --write
33
+ bun run biome check ./src ./scripts --write
21
34
 
22
35
  # lint the codebase
23
36
  lint:
24
- bun x biome check ./src ./scripts
37
+ bun run biome check ./src ./scripts
25
38
 
26
39
  # Typecheck, lint, then run the test suite.
27
- test: check-types format lint
40
+ test: check
28
41
  bun test
29
42
 
43
+ # typecheck the codebase
44
+ typecheck:
45
+ bun run tsc --noEmit
46
+
30
47
  # generate type declarations for the package
31
48
  typegen:
32
49
  bunx dts-bundle-generator --out-file index.d.ts src/index.ts
@@ -34,3 +51,4 @@ typegen:
34
51
  # publish to github and npm
35
52
  release bump: test typegen
36
53
  bun scripts/release.ts {{bump}}
54
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "3.4.2",
3
+ "version": "3.6.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "bun": ">=1.3"
@@ -21,6 +21,8 @@
21
21
  }
22
22
  },
23
23
  "devDependencies": {
24
- "@types/bun": "^1.3.12"
24
+ "@biomejs/biome": "^2.5.0",
25
+ "@types/bun": "^1.3.12",
26
+ "typescript": "^5.9.3"
25
27
  }
26
28
  }
@@ -12,7 +12,7 @@
12
12
  * repo changes (`git add -A`), not only the version and CHANGELOG edits from this script.
13
13
  */
14
14
 
15
- import { unlink } from "fs/promises";
15
+ import { unlink } from "node:fs/promises";
16
16
 
17
17
  /** Returns the parent directory of an absolute file path. */
18
18
  function parentDir(absolute: string): string {
@@ -96,9 +96,13 @@ function githubRepoBaseFromOrigin(origin: string): string | null {
96
96
  function releasedSemverVersionsFromChangelog(md: string): string[] {
97
97
  const re = /^## \[(\d+\.\d+\.\d+)\] /gm;
98
98
  const out: string[] = [];
99
- let m: RegExpExecArray | null;
100
- while ((m = re.exec(md)) !== null) {
101
- out.push(m[1]!);
99
+ let m = re.exec(md);
100
+ while (m !== null) {
101
+ const version = m[1];
102
+ if (version !== undefined) {
103
+ out.push(version);
104
+ }
105
+ m = re.exec(md);
102
106
  }
103
107
  return out;
104
108
  }
@@ -108,14 +112,18 @@ function releasedSemverVersionsFromChangelog(md: string): string[] {
108
112
  * (`[...]: http...`) at the end of the file.
109
113
  */
110
114
  function stripChangelogLinkDefinitions(md: string): string {
111
- let s = md.replace(/\r?\n## Links\r?\n/, "\n");
115
+ const s = md.replace(/\r?\n## Links\r?\n/, "\n");
112
116
  const lines = s.split(/\r?\n/);
113
117
  let i = lines.length;
114
118
  while (i > 0 && lines[i - 1] === "") {
115
119
  i -= 1;
116
120
  }
117
121
  const refLine = /^\[[^\]]+\]: .+$/;
118
- while (i > 0 && refLine.test(lines[i - 1]!)) {
122
+ while (i > 0) {
123
+ const line = lines[i - 1];
124
+ if (line === undefined || !refLine.test(line)) {
125
+ break;
126
+ }
119
127
  i -= 1;
120
128
  }
121
129
  while (i > 0 && lines[i - 1] === "") {
@@ -143,7 +151,10 @@ function appendChangelogLinkDefinitions(md: string, repoBase: string): string {
143
151
  if (versions.length === 0) {
144
152
  return `${body}\n`;
145
153
  }
146
- const newest = versions[0]!;
154
+ const newest = versions[0];
155
+ if (newest === undefined) {
156
+ return `${body}\n`;
157
+ }
147
158
  const lines = [
148
159
  "",
149
160
  "",
@@ -178,7 +189,9 @@ function promoteChangelog(content: string, version: string, date: string): strin
178
189
  const bodyStart = lineEnd + 1;
179
190
  const nextIdx = content.indexOf("\n## [", bodyStart);
180
191
  const body =
181
- nextIdx === -1 ? content.slice(bodyStart).trimEnd() : content.slice(bodyStart, nextIdx).trimEnd();
192
+ nextIdx === -1
193
+ ? content.slice(bodyStart).trimEnd()
194
+ : content.slice(bodyStart, nextIdx).trimEnd();
182
195
  const tail = nextIdx === -1 ? "" : content.slice(nextIdx + 1);
183
196
  const before = content.slice(0, idx);
184
197
  const newBlock = `${header}\n\n## [${version}] - ${date}\n${body}\n\n`;
@@ -240,7 +253,11 @@ try {
240
253
  run("git tag", ["git", "tag", "-a", tag, "-m", msg], repoRoot);
241
254
  run("git push", ["git", "push"], repoRoot);
242
255
  run("git push tags", ["git", "push", "--tags"], repoRoot);
243
- run("gh release", ["gh", "release", "create", tag, "--title", tag, "--notes-file", notesPath], repoRoot);
256
+ run(
257
+ "gh release",
258
+ ["gh", "release", "create", tag, "--title", tag, "--notes-file", notesPath],
259
+ repoRoot,
260
+ );
244
261
  run("npm publish", ["npm", "publish"], repoRoot);
245
262
  } finally {
246
263
  await unlink(notesPath).catch(() => {});
@@ -1,10 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import type { CliProgram } from "../types.ts";
3
+ import { exportPresentationBuiltins } from "./export.ts";
4
+ import { completionBashScript, completionFishScript, completionZshScript } from "./index.ts";
2
5
  import { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
3
6
  import { cliBuiltinMcpCommand } from "./mcp.ts";
4
7
  import { cliPresentationRoot } from "./presentation.ts";
5
- import { completionBashScript, completionFishScript, completionZshScript } from "./index.ts";
6
- import { exportPresentationBuiltins } from "./export.ts";
7
- import { CliProgram } from "../types.ts";
8
8
 
9
9
  const fixture: CliProgram = {
10
10
  key: "myapp",
@@ -114,7 +114,12 @@ describe("completion emitters", () => {
114
114
  });
115
115
 
116
116
  test("zsh script registers compdef", () => {
117
- const schema = cliPresentationRoot({ key: "zapp", version: "0.0.0", description: "z", handler: () => {} });
117
+ const schema = cliPresentationRoot({
118
+ key: "zapp",
119
+ version: "0.0.0",
120
+ description: "z",
121
+ handler: () => {},
122
+ });
118
123
  const zsh = completionZshScript(schema);
119
124
  expect(zsh).toContain("#compdef zapp");
120
125
  expect(zsh).toContain("compdef _zapp zapp");
@@ -1,4 +1,4 @@
1
- import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
1
+ import { CliOptionKind, type CliRouter } from "../types.ts";
2
2
  import { collectScopes, type ScopeRec } from "./scopes.ts";
3
3
  import {
4
4
  escShellSingleQuoted,
@@ -10,19 +10,24 @@ import {
10
10
 
11
11
  function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
12
12
  let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
13
- o += " local sid=\"$1\" w=\"$2\" nw=\"$3\"\n";
13
+ o += ' local sid="$1" w="$2" nw="$3"\n';
14
14
  o += " case $sid in\n";
15
15
  for (const [i, sc] of scopes.entries()) {
16
- o += " " + i + ")\n";
16
+ o += ` ${i})\n`;
17
17
  o += " case $w in\n";
18
- o += " " + kHelpLong + "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n".replace(/\$\{kHelpLong\}/g, kHelpLong).replace(/\$\{kHelpShort\}/g, kHelpShort);
18
+ o +=
19
+ " " +
20
+ kHelpLong +
21
+ "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n"
22
+ .replace(/\$\{kHelpLong\}/g, kHelpLong)
23
+ .replace(/\$\{kHelpShort\}/g, kHelpShort);
19
24
  for (const op of sc.opts) {
20
- const base = "--" + op.name;
25
+ const base = `--${op.name}`;
21
26
  if (op.kind === "presence") {
22
- o += " " + base + "|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base);
27
+ o += ` ${base}${"|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base)}`;
23
28
  } else {
24
- o += " " + base + "=*) echo 1 ;;\n";
25
- o += " " + base + ") echo 2 ;;\n";
29
+ o += ` ${base}=*) echo 1 ;;\n`;
30
+ o += ` ${base}) echo 2 ;;\n`;
26
31
  }
27
32
  }
28
33
  o += " *) echo 0 ;;\n";
@@ -37,10 +42,10 @@ function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
37
42
 
38
43
  function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
39
44
  let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
40
- o += " local sid=\"$1\" w=\"$2\"\n";
45
+ o += ' local sid="$1" w="$2"\n';
41
46
  o += " case $sid in\n";
42
47
  for (const [i, sc] of scopes.entries()) {
43
- o += " " + i + ")\n";
48
+ o += ` ${i})\n`;
44
49
  o += " local rest=${w#-}\n";
45
50
  o += " local ch\n";
46
51
  o += " local saw=0\n";
@@ -52,16 +57,16 @@ function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
52
57
  for (const op of sc.opts) {
53
58
  if (!op.shortName) continue;
54
59
  if (op.kind === "presence") {
55
- boolChars += op.shortName + "|";
60
+ boolChars += `${op.shortName}|`;
56
61
  } else {
57
- o += " " + op.shortName + ")\n";
62
+ o += ` ${op.shortName})\n`;
58
63
  o += " if [[ $saw -ne 0 || -n $rest ]]; then echo 0; return; fi\n";
59
64
  o += " echo 2; return ;;\n";
60
65
  }
61
66
  }
62
67
  if (boolChars.length > 0) {
63
68
  boolChars = boolChars.slice(0, -1);
64
- o += " " + boolChars + ") ;;\n";
69
+ o += ` ${boolChars}) ;;\n`;
65
70
  }
66
71
  o += " *) echo 0; return ;;\n";
67
72
  o += " esac\n";
@@ -76,18 +81,22 @@ function emitConsumeShort(ident: string, scopes: ScopeRec[]): string {
76
81
  return o;
77
82
  }
78
83
 
79
- function emitMatchChild(ident: string, scopes: ScopeRec[], pathIndex: Record<string, number>): string {
84
+ function emitMatchChild(
85
+ ident: string,
86
+ scopes: ScopeRec[],
87
+ pathIndex: Record<string, number>,
88
+ ): string {
80
89
  let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
81
- o += " local sid=\"$1\" w=\"$2\"\n";
90
+ o += ' local sid="$1" w="$2"\n';
82
91
  o += " case $sid in\n";
83
92
  for (const [sid, sc] of scopes.entries()) {
84
93
  if (sc.kids.length === 0) continue;
85
- o += " " + sid + ")\n";
94
+ o += ` ${sid})\n`;
86
95
  o += " case $w in\n";
87
96
  for (const ch of sc.kids) {
88
- const childPath = sc.path === "" ? ch.key : sc.path + "/" + ch.key;
97
+ const childPath = sc.path === "" ? ch.key : `${sc.path}/${ch.key}`;
89
98
  const cid = pathIndex[childPath] ?? 0;
90
- o += " " + ch.key + ") echo " + cid + "; return 0 ;;\n";
99
+ o += ` ${ch.key}) echo ${cid}; return 0 ;;\n`;
91
100
  }
92
101
  o += " esac\n";
93
102
  o += " ;;\n";
@@ -102,12 +111,15 @@ function emitSimulate(ident: string): string {
102
111
  let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
103
112
  o += " local i=1 sid=0 w steps next\n";
104
113
  o += " while (( i < COMP_CWORD )); do\n";
105
- o += " w=\"${COMP_WORDS[i]}\"\n";
106
- o += " if [[ $w == " + kHelpShort + " || $w == " + kHelpLong + " ]]; then\n";
114
+ o += ' w="${COMP_WORDS[i]}"\n';
115
+ o += ` if [[ $w == ${kHelpShort} || $w == ${kHelpLong} ]]; then\n`;
107
116
  o += " ((i++)); continue\n";
108
117
  o += " fi\n";
109
118
  o += " if [[ $w == --* ]]; then\n";
110
- o += " steps=$(_${ident}_nac_consume_long \"$sid\" \"$w\" \"${COMP_WORDS[i+1]}\")\n".replace("${ident}", ident);
119
+ o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${COMP_WORDS[i+1]}")\n'.replace(
120
+ "${ident}",
121
+ ident,
122
+ );
111
123
  o += " case $steps in\n";
112
124
  o += " 0) break ;;\n";
113
125
  o += " 1) ((i++)) ;;\n";
@@ -117,7 +129,7 @@ function emitSimulate(ident: string): string {
117
129
  o += " continue\n";
118
130
  o += " fi\n";
119
131
  o += " if [[ $w == -* ]]; then\n";
120
- o += " steps=$(_${ident}_nac_consume_short \"$sid\" \"$w\")\n".replace("${ident}", ident);
132
+ o += ' steps=$(_${ident}_nac_consume_short "$sid" "$w")\n'.replace("${ident}", ident);
121
133
  o += " case $steps in\n";
122
134
  o += " 0) break ;;\n";
123
135
  o += " 1) ((i++)) ;;\n";
@@ -126,7 +138,7 @@ function emitSimulate(ident: string): string {
126
138
  o += " esac\n";
127
139
  o += " continue\n";
128
140
  o += " fi\n";
129
- o += " next=$(_${ident}_nac_match_child \"$sid\" \"$w\") || break\n".replace("${ident}", ident);
141
+ o += ' next=$(_${ident}_nac_match_child "$sid" "$w") || break\n'.replace("${ident}", ident);
130
142
  o += " sid=$next\n";
131
143
  o += " ((i++))\n";
132
144
  o += " done\n";
@@ -137,16 +149,23 @@ function emitSimulate(ident: string): string {
137
149
 
138
150
  function emitEnumReplyBash(ident: string, scopes: ScopeRec[]): string {
139
151
  let o = "_${ident}_nac_enum_reply() {\n".replace("${ident}", ident);
140
- o += " local sid=\"$1\" prev=\"$2\" cur=\"$3\"\n";
152
+ o += ' local sid="$1" prev="$2" cur="$3"\n';
141
153
  o += " case $sid in\n";
142
154
  for (const [i, sc] of scopes.entries()) {
143
- const enumOpts = sc.opts.filter((op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0);
155
+ const enumOpts = sc.opts.filter(
156
+ (op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0,
157
+ );
144
158
  if (enumOpts.length === 0) continue;
145
- o += " " + i + ")\n";
159
+ o += ` ${i})\n`;
146
160
  o += " case $prev in\n";
147
161
  for (const op of enumOpts) {
148
162
  const words = (op.choices ?? []).map((c) => escShellSingleQuoted(c)).join(" ");
149
- o += " --" + op.name + ") COMPREPLY=( $(compgen -W '" + words + "' -- \"$cur\") ); return 0 ;;\n";
163
+ o +=
164
+ " --" +
165
+ op.name +
166
+ ") COMPREPLY=( $(compgen -W '" +
167
+ words +
168
+ '\' -- "$cur") ); return 0 ;;\n';
150
169
  }
151
170
  o += " esac\n";
152
171
  o += " ;;\n";
@@ -160,34 +179,39 @@ function emitEnumReplyBash(ident: string, scopes: ScopeRec[]): string {
160
179
  function emitMainBodyBash(schema: CliRouter, ident: string): string {
161
180
  const main = mainName(schema.key);
162
181
  let o = "_${main}() {\n".replace("${main}", main);
163
- o += " local cur=\"${COMP_WORDS[COMP_CWORD]}\"\n";
164
- o += " local prev=\"${COMP_WORDS[COMP_CWORD-1]:-}\"\n";
182
+ o += ' local cur="${COMP_WORDS[COMP_CWORD]}"\n';
183
+ o += ' local prev="${COMP_WORDS[COMP_CWORD-1]:-}"\n';
165
184
  o += " _${ident}_nac_simulate\n".replace("${ident}", ident);
166
185
  o += " local sid=$REPLY_SID\n";
167
- o += " if _${ident}_nac_enum_reply \"$sid\" \"$prev\" \"$cur\"; then return; fi\n".replace("${ident}", ident);
186
+ o += ' if _${ident}_nac_enum_reply "$sid" "$prev" "$cur"; then return; fi\n'.replace(
187
+ "${ident}",
188
+ ident,
189
+ );
168
190
  o += " if [[ $cur == -* ]]; then\n";
169
- o += " local oname=\"A_${ident}_${sid}_opts\"\n".replace("${ident}", ident);
191
+ o += ' local oname="A_${ident}_${sid}_opts"\n'.replace("${ident}", ident);
170
192
  o += " local -a optsarr\n";
171
- o += " local -n optsref=\"$oname\"\n";
172
- o += " COMPREPLY=( $(compgen -W \"${optsref[*]}\" -- \"$cur\") )\n";
193
+ o += ' local -n optsref="$oname"\n';
194
+ o += ' COMPREPLY=( $(compgen -W "${optsref[*]}" -- "$cur") )\n';
173
195
  o += " else\n";
174
- o += " local lname=\"A_${ident}_${sid}_leaf\"\n".replace("${ident}", ident);
175
- o += " local -n leafref=\"$lname\"\n";
196
+ o += ' local lname="A_${ident}_${sid}_leaf"\n'.replace("${ident}", ident);
197
+ o += ' local -n leafref="$lname"\n';
176
198
  o += " if [[ $leafref -eq 0 ]]; then\n";
177
- o += " local cname=\"A_${ident}_${sid}_cmds\"\n".replace("${ident}", ident);
199
+ o += ' local cname="A_${ident}_${sid}_cmds"\n'.replace("${ident}", ident);
178
200
  o += " local -a cmdsarr\n";
179
- o += " local -n cmdsref=\"$cname\"\n";
180
- o += " COMPREPLY=( $(compgen -W \"${cmdsref[*]}\" -- \"$cur\") )\n";
201
+ o += ' local -n cmdsref="$cname"\n';
202
+ o += ' COMPREPLY=( $(compgen -W "${cmdsref[*]}" -- "$cur") )\n';
181
203
  o += " else\n";
182
- o += " local pname=\"A_${ident}_${sid}_pos\"\n".replace("${ident}", ident);
183
- o += " local -n posref=\"$pname\"\n";
204
+ o += ' local pname="A_${ident}_${sid}_pos"\n'.replace("${ident}", ident);
205
+ o += ' local -n posref="$pname"\n';
184
206
  o += " if [[ $posref -eq 1 ]]; then\n";
185
207
  o += " compopt -o filenames\n";
186
208
  o += " fi\n";
187
209
  o += " fi\n";
188
210
  o += " fi\n";
189
211
  o += "}\n\n";
190
- o += "complete -F _${main} ${schema.key}\n".replace("${main}", main).replace("${schema.key}", schema.key);
212
+ o += "complete -F _${main} ${schema.key}\n"
213
+ .replace("${main}", main)
214
+ .replace("${schema.key}", schema.key);
191
215
  return o;
192
216
  }
193
217
 
@@ -200,23 +224,23 @@ export function completionBashScript(schema: CliRouter): string {
200
224
  pathIndex[s.path] = i;
201
225
  }
202
226
 
203
- let out = "# Generated bash completion for " + schema.key + ".\n\n";
227
+ let out = `# Generated bash completion for ${schema.key}.\n\n`;
204
228
 
205
229
  for (const [i, sc] of scopes.entries()) {
206
- out += "A_" + ident + "_" + i + "_opts=()\n";
207
- out += "A_" + ident + "_" + i + "_opts+=('" + kHelpLong + "' '" + kHelpShort + "')\n";
230
+ out += `A_${ident}_${i}_opts=()\n`;
231
+ out += `A_${ident}_${i}_opts+=('${kHelpLong}' '${kHelpShort}')\n`;
208
232
  for (const o of sc.opts) {
209
- out += "A_" + ident + "_" + i + "_opts+=('--" + o.name + "')\n";
233
+ out += `A_${ident}_${i}_opts+=('--${o.name}')\n`;
210
234
  if (o.shortName) {
211
- out += "A_" + ident + "_" + i + "_opts+=('-" + o.shortName + "')\n";
235
+ out += `A_${ident}_${i}_opts+=('-${o.shortName}')\n`;
212
236
  }
213
237
  }
214
- out += "A_" + ident + "_" + i + "_leaf=" + (sc.kids.length === 0 ? "1" : "0") + "\n";
215
- out += "A_" + ident + "_" + i + "_pos=" + (sc.wantsFiles ? "1" : "0") + "\n";
238
+ out += `A_${ident}_${i}_leaf=${sc.kids.length === 0 ? "1" : "0"}\n`;
239
+ out += `A_${ident}_${i}_pos=${sc.wantsFiles ? "1" : "0"}\n`;
216
240
  if (sc.kids.length > 0) {
217
- out += "A_" + ident + "_" + i + "_cmds=(";
241
+ out += `A_${ident}_${i}_cmds=(`;
218
242
  for (const ch of sc.kids) {
219
- out += " '" + ch.key + "'";
243
+ out += ` '${ch.key}'`;
220
244
  }
221
245
  out += ")\n";
222
246
  }
@@ -1,11 +1,6 @@
1
- import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
1
+ import { CliOptionKind, type CliRouter } from "../types.ts";
2
2
  import { collectScopes } from "./scopes.ts";
3
- import {
4
- escFishSingleQuoted,
5
- identToken,
6
- kHelpLong,
7
- kHelpShort,
8
- } from "./shell-helpers.ts";
3
+ import { escFishSingleQuoted, identToken } from "./shell-helpers.ts";
9
4
 
10
5
  function scopeCondition(ident: string, scopeIndex: number, path: string): string {
11
6
  const fn = `__${ident}_scope_${scopeIndex}`;
@@ -30,7 +25,7 @@ export function completionFishScript(schema: CliRouter): string {
30
25
  const app = schema.key;
31
26
  const scopes = collectScopes(schema);
32
27
 
33
- let out = "# Fish completion for " + app + "\n\n";
28
+ let out = `# Fish completion for ${app}\n\n`;
34
29
 
35
30
  for (const [i, sc] of scopes.entries()) {
36
31
  out += scopeCondition(ident, i, sc.path);
@@ -1,5 +1,5 @@
1
1
  import { resolveCapabilities } from "../capabilities.ts";
2
- import { type CliLeaf, type CliProgram, type CliRouter } from "../types.ts";
2
+ import type { CliProgram, CliRouter } from "../types.ts";
3
3
 
4
4
  /**
5
5
  * Builds the static `completion` / `bash` / `zsh` / `fish` command subtree (merged into the program root at runtime).