argsbarg 5.1.8 → 5.1.10
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 +29 -1
- package/docs/cli-program.md +3 -1
- package/docs/config-schema.md +5 -3
- package/docs/configure.md +13 -7
- package/docs/distribution-homebrew.md +17 -3
- package/examples/full-example/justfile +6 -5
- package/examples/full-example/scripts/formula-shared.test.ts +68 -0
- package/examples/full-example/scripts/formula-shared.ts +39 -1
- package/examples/full-example/scripts/release.ts +212 -0
- package/index.d.ts +10 -1
- package/package.json +2 -1
- package/src/builtins/builtins.test.ts +1 -3
- package/src/builtins/completion-bash.ts +6 -27
- package/src/builtins/completion-group.ts +1 -2
- package/src/builtins/completion-simulate-shared.ts +2 -10
- package/src/builtins/completion-zsh.ts +7 -33
- package/src/builtins/config.test.ts +28 -0
- package/src/builtins/config.ts +40 -10
- package/src/builtins/configure-copy.ts +3 -6
- package/src/builtins/dispatch.ts +2 -10
- package/src/builtins/export.ts +1 -3
- package/src/builtins/presentation.ts +2 -7
- package/src/builtins/registry.ts +1 -5
- package/src/cli-tool/create.test.ts +3 -6
- package/src/cli-tool/create.ts +5 -22
- package/src/cli-tool/post-create.ts +1 -3
- package/src/cli-tool/program.ts +1 -2
- package/src/cli-tool/run-create.ts +2 -8
- package/src/cli.ts +6 -27
- package/src/config/bindings.test.ts +72 -0
- package/src/config/bindings.ts +115 -0
- package/src/config/bootstrap.ts +80 -65
- package/src/config/context.test.ts +45 -0
- package/src/config/context.ts +74 -14
- package/src/config/entry.ts +1 -3
- package/src/config/file.test.ts +46 -3
- package/src/config/file.ts +59 -19
- package/src/config/resolve.ts +1 -4
- package/src/config/schema.ts +1 -3
- package/src/config/validate.test.ts +3 -11
- package/src/config/validate.ts +30 -30
- package/src/config.integration.test.ts +1 -5
- package/src/configure/configure.test.ts +19 -5
- package/src/configure/index.ts +17 -15
- package/src/docs/api-guide.ts +1 -6
- package/src/docs/builtin.ts +1 -5
- package/src/docs/docs.test.ts +2 -6
- package/src/docs/mcp-guide.ts +3 -13
- package/src/docs/mcp-resources.test.ts +1 -3
- package/src/docs/mcp-resources.ts +1 -6
- package/src/docs/save.ts +2 -10
- package/src/formats.test.ts +1 -7
- package/src/formats.ts +1 -5
- package/src/headless.test.ts +5 -17
- package/src/help.ts +9 -51
- package/src/hidden-mcpb.test.ts +3 -12
- package/src/hidden.ts +1 -3
- package/src/install/binary-placement.test.ts +2 -6
- package/src/install/binary-placement.ts +1 -4
- package/src/install/gh-release-update.ts +5 -22
- package/src/install/mcp-codex.ts +1 -2
- package/src/install/mcp-config.ts +2 -12
- package/src/install/mcp-opencode.test.ts +1 -3
- package/src/install/mcp-opencode.ts +3 -15
- package/src/install/plan.ts +2 -10
- package/src/install/status.ts +3 -5
- package/src/install/target-base.ts +2 -11
- package/src/install/target-detect.ts +1 -5
- package/src/install/target-effective.ts +3 -13
- package/src/install/target-mcp-cli.ts +4 -26
- package/src/install/target-mcp-json.ts +3 -14
- package/src/install/target-plan-build.ts +1 -5
- package/src/install/target-registry.ts +13 -21
- package/src/install/target-scope.ts +8 -25
- package/src/install/target-types.ts +1 -6
- package/src/install/targets/app.ts +2 -2
- package/src/install/targets/opencode-mcp.ts +1 -6
- package/src/install/targets.test.ts +3 -10
- package/src/install/uninstall.ts +2 -9
- package/src/invoke.test.ts +1 -3
- package/src/mcp/bundle.ts +4 -19
- package/src/mcp/claude.test.ts +3 -3
- package/src/mcp/claude.ts +4 -17
- package/src/mcp/env.ts +1 -3
- package/src/mcp/server.ts +3 -12
- package/src/mcp/tools.ts +2 -11
- package/src/mcp.integration.test.ts +4 -10
- package/src/parse.test.ts +9 -34
- package/src/parse.ts +10 -44
- package/src/schema.ts +2 -10
- package/src/skill/hint.ts +1 -5
- package/src/skill/install.ts +1 -5
- package/src/test-fixtures.ts +1 -3
- package/src/validate.ts +29 -90
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { CliOptionKind, type CliRouter } from "../types.ts";
|
|
2
2
|
import { emitConsumeLong, emitConsumeShort, emitMatchChild } from "./completion-simulate-shared.ts";
|
|
3
3
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
4
|
-
import {
|
|
5
|
-
escShellSingleQuoted,
|
|
6
|
-
identToken,
|
|
7
|
-
kHelpLong,
|
|
8
|
-
kHelpShort,
|
|
9
|
-
mainName,
|
|
10
|
-
} from "./shell-helpers.ts";
|
|
4
|
+
import { escShellSingleQuoted, identToken, kHelpLong, kHelpShort, mainName } from "./shell-helpers.ts";
|
|
11
5
|
|
|
12
6
|
function emitSimulate(ident: string): string {
|
|
13
7
|
let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
|
|
@@ -18,10 +12,7 @@ function emitSimulate(ident: string): string {
|
|
|
18
12
|
o += " ((i++)); continue\n";
|
|
19
13
|
o += " fi\n";
|
|
20
14
|
o += " if [[ $w == --* ]]; then\n";
|
|
21
|
-
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${COMP_WORDS[i+1]}")\n'.replace(
|
|
22
|
-
"${ident}",
|
|
23
|
-
ident,
|
|
24
|
-
);
|
|
15
|
+
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${COMP_WORDS[i+1]}")\n'.replace("${ident}", ident);
|
|
25
16
|
o += " case $steps in\n";
|
|
26
17
|
o += " 0) break ;;\n";
|
|
27
18
|
o += " 1) ((i++)) ;;\n";
|
|
@@ -54,20 +45,13 @@ function emitEnumReplyBash(ident: string, scopes: ScopeRec[]): string {
|
|
|
54
45
|
o += ' local sid="$1" prev="$2" cur="$3"\n';
|
|
55
46
|
o += " case $sid in\n";
|
|
56
47
|
for (const [i, sc] of scopes.entries()) {
|
|
57
|
-
const enumOpts = sc.opts.filter(
|
|
58
|
-
(op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0,
|
|
59
|
-
);
|
|
48
|
+
const enumOpts = sc.opts.filter((op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0);
|
|
60
49
|
if (enumOpts.length === 0) continue;
|
|
61
50
|
o += ` ${i})\n`;
|
|
62
51
|
o += " case $prev in\n";
|
|
63
52
|
for (const op of enumOpts) {
|
|
64
53
|
const words = (op.choices ?? []).map((c) => escShellSingleQuoted(c)).join(" ");
|
|
65
|
-
o +=
|
|
66
|
-
" --" +
|
|
67
|
-
op.name +
|
|
68
|
-
") COMPREPLY=( $(compgen -W '" +
|
|
69
|
-
words +
|
|
70
|
-
'\' -- "$cur") ); return 0 ;;\n';
|
|
54
|
+
o += ` --${op.name}) COMPREPLY=( $(compgen -W '${words}' -- "$cur") ); return 0 ;;\n`;
|
|
71
55
|
}
|
|
72
56
|
o += " esac\n";
|
|
73
57
|
o += " ;;\n";
|
|
@@ -85,10 +69,7 @@ function emitMainBodyBash(schema: CliRouter, ident: string): string {
|
|
|
85
69
|
o += ' local prev="${COMP_WORDS[COMP_CWORD-1]:-}"\n';
|
|
86
70
|
o += " _${ident}_nac_simulate\n".replace("${ident}", ident);
|
|
87
71
|
o += " local sid=$REPLY_SID\n";
|
|
88
|
-
o += ' if _${ident}_nac_enum_reply "$sid" "$prev" "$cur"; then return; fi\n'.replace(
|
|
89
|
-
"${ident}",
|
|
90
|
-
ident,
|
|
91
|
-
);
|
|
72
|
+
o += ' if _${ident}_nac_enum_reply "$sid" "$prev" "$cur"; then return; fi\n'.replace("${ident}", ident);
|
|
92
73
|
o += " if [[ $cur == -* ]]; then\n";
|
|
93
74
|
o += ' local oname="A_${ident}_${sid}_opts"\n'.replace("${ident}", ident);
|
|
94
75
|
o += " local -a optsarr\n";
|
|
@@ -111,9 +92,7 @@ function emitMainBodyBash(schema: CliRouter, ident: string): string {
|
|
|
111
92
|
o += " fi\n";
|
|
112
93
|
o += " fi\n";
|
|
113
94
|
o += "}\n\n";
|
|
114
|
-
o += "complete -F _${main} ${schema.key}\n"
|
|
115
|
-
.replace("${main}", main)
|
|
116
|
-
.replace("${schema.key}", schema.key);
|
|
95
|
+
o += "complete -F _${main} ${schema.key}\n".replace("${main}", main).replace("${schema.key}", schema.key);
|
|
117
96
|
return o;
|
|
118
97
|
}
|
|
119
98
|
|
|
@@ -45,7 +45,6 @@ export function cliBuiltinCompletionGroup(program: CliProgram): import("../types
|
|
|
45
45
|
],
|
|
46
46
|
};
|
|
47
47
|
router.notes =
|
|
48
|
-
"Completions are installed by Homebrew during formula install.\n\n" +
|
|
49
|
-
"See: https://docs.brew.sh/Shell-Completion";
|
|
48
|
+
"Completions are installed by Homebrew during formula install.\n\n" + "See: https://docs.brew.sh/Shell-Completion";
|
|
50
49
|
return router;
|
|
51
50
|
}
|
|
@@ -44,11 +44,7 @@ export function emitConsumeLong(ident: string, scopes: ScopeRec[]): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Emits `_<ident>_nac_consume_short`; dialect selects substring syntax. */
|
|
47
|
-
export function emitConsumeShort(
|
|
48
|
-
ident: string,
|
|
49
|
-
scopes: ScopeRec[],
|
|
50
|
-
dialect: CompletionShellDialect,
|
|
51
|
-
): string {
|
|
47
|
+
export function emitConsumeShort(ident: string, scopes: ScopeRec[], dialect: CompletionShellDialect): string {
|
|
52
48
|
const firstChar = dialect === "bash" ? "ch=${rest:0:1}" : "ch=${rest[1,1]}";
|
|
53
49
|
const restAdvance = dialect === "bash" ? "rest=${rest:1}" : "rest=${rest[2,-1]}";
|
|
54
50
|
|
|
@@ -93,11 +89,7 @@ export function emitConsumeShort(
|
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
/** Emits `_<ident>_nac_match_child` — identical for bash and zsh. */
|
|
96
|
-
export function emitMatchChild(
|
|
97
|
-
ident: string,
|
|
98
|
-
scopes: ScopeRec[],
|
|
99
|
-
pathIndex: Record<string, number>,
|
|
100
|
-
): string {
|
|
92
|
+
export function emitMatchChild(ident: string, scopes: ScopeRec[], pathIndex: Record<string, number>): string {
|
|
101
93
|
let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
|
|
102
94
|
o += ' local sid="$1" w="$2"\n';
|
|
103
95
|
o += " case $sid in\n";
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { CliOptionKind, type CliRouter } from "../types.ts";
|
|
2
2
|
import { emitConsumeLong, emitConsumeShort, emitMatchChild } from "./completion-simulate-shared.ts";
|
|
3
3
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
4
|
-
import {
|
|
5
|
-
escShellSingleQuoted,
|
|
6
|
-
identToken,
|
|
7
|
-
kHelpLong,
|
|
8
|
-
kHelpShort,
|
|
9
|
-
mainName,
|
|
10
|
-
} from "./shell-helpers.ts";
|
|
4
|
+
import { escShellSingleQuoted, identToken, kHelpLong, kHelpShort, mainName } from "./shell-helpers.ts";
|
|
11
5
|
|
|
12
6
|
function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
|
|
13
7
|
let out = "";
|
|
@@ -25,19 +19,9 @@ function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
|
|
|
25
19
|
escShellSingleQuoted("Show help for this command.") +
|
|
26
20
|
"'";
|
|
27
21
|
for (const o of sc.opts) {
|
|
28
|
-
out +=
|
|
29
|
-
" '" +
|
|
30
|
-
escShellSingleQuoted(`--${o.name}`) +
|
|
31
|
-
":" +
|
|
32
|
-
escShellSingleQuoted(o.description) +
|
|
33
|
-
"'";
|
|
22
|
+
out += ` '${escShellSingleQuoted(`--${o.name}`)}:${escShellSingleQuoted(o.description)}'`;
|
|
34
23
|
if (o.shortName) {
|
|
35
|
-
out +=
|
|
36
|
-
" '" +
|
|
37
|
-
escShellSingleQuoted(`-${o.shortName}`) +
|
|
38
|
-
":" +
|
|
39
|
-
escShellSingleQuoted(o.description) +
|
|
40
|
-
"'";
|
|
24
|
+
out += ` '${escShellSingleQuoted(`-${o.shortName}`)}:${escShellSingleQuoted(o.description)}'`;
|
|
41
25
|
}
|
|
42
26
|
}
|
|
43
27
|
out += ")\n";
|
|
@@ -63,10 +47,7 @@ function emitSimulateZsh(ident: string): string {
|
|
|
63
47
|
o += " ((i++)); continue\n";
|
|
64
48
|
o += " fi\n";
|
|
65
49
|
o += " if [[ $w == --* ]]; then\n";
|
|
66
|
-
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${words[i+1]}")\n'.replace(
|
|
67
|
-
"${ident}",
|
|
68
|
-
ident,
|
|
69
|
-
);
|
|
50
|
+
o += ' steps=$(_${ident}_nac_consume_long "$sid" "$w" "${words[i+1]}")\n'.replace("${ident}", ident);
|
|
70
51
|
o += " case $steps in\n";
|
|
71
52
|
o += " 0) break ;;\n";
|
|
72
53
|
o += " 1) ((i++)) ;;\n";
|
|
@@ -99,9 +80,7 @@ function emitEnumReplyZsh(ident: string, scopes: ScopeRec[]): string {
|
|
|
99
80
|
o += " local sid=$1 prev=$2\n";
|
|
100
81
|
o += " case $sid in\n";
|
|
101
82
|
for (const [i, sc] of scopes.entries()) {
|
|
102
|
-
const enumOpts = sc.opts.filter(
|
|
103
|
-
(op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0,
|
|
104
|
-
);
|
|
83
|
+
const enumOpts = sc.opts.filter((op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0);
|
|
105
84
|
if (enumOpts.length === 0) continue;
|
|
106
85
|
o += ` ${i})\n`;
|
|
107
86
|
o += " case $prev in\n";
|
|
@@ -124,10 +103,7 @@ function emitMainBodyZsh(schema: CliRouter, ident: string): string {
|
|
|
124
103
|
o += ' local curcontext="$curcontext" ret=1\n';
|
|
125
104
|
o += " _${ident}_nac_simulate\n".replace("${ident}", ident);
|
|
126
105
|
o += " local sid=$REPLY_SID\n";
|
|
127
|
-
o += ' if _${ident}_nac_enum_reply "$sid" "$words[CURRENT-1]"; then return 0; fi\n'.replace(
|
|
128
|
-
"${ident}",
|
|
129
|
-
ident,
|
|
130
|
-
);
|
|
106
|
+
o += ' if _${ident}_nac_enum_reply "$sid" "$words[CURRENT-1]"; then return 0; fi\n'.replace("${ident}", ident);
|
|
131
107
|
o += " if [[ $PREFIX == -* ]]; then\n";
|
|
132
108
|
o += " local -a optsarr\n";
|
|
133
109
|
o += ' local oname="A_${ident}_${sid}_opts"\n'.replace("${ident}", ident);
|
|
@@ -149,9 +125,7 @@ function emitMainBodyZsh(schema: CliRouter, ident: string): string {
|
|
|
149
125
|
o += " fi\n";
|
|
150
126
|
o += " return ret\n";
|
|
151
127
|
o += "}\n\n";
|
|
152
|
-
o += "compdef _${main} ${schema.key}\n"
|
|
153
|
-
.replace("${main}", main)
|
|
154
|
-
.replace("${schema.key}", schema.key);
|
|
128
|
+
o += "compdef _${main} ${schema.key}\n".replace("${main}", main).replace("${schema.key}", schema.key);
|
|
155
129
|
return o;
|
|
156
130
|
}
|
|
157
131
|
|
|
@@ -6,6 +6,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
+
import { readBindings } from "../config/bindings.ts";
|
|
9
10
|
import { resolveAppConfigPath, writeAppConfigFile } from "../config/file.ts";
|
|
10
11
|
import { Cli, type CliProgram } from "../index.ts";
|
|
11
12
|
|
|
@@ -93,4 +94,31 @@ describe("builtins/config", () => {
|
|
|
93
94
|
rmSync(dir, { recursive: true, force: true });
|
|
94
95
|
}
|
|
95
96
|
});
|
|
97
|
+
|
|
98
|
+
test("config set --from-env stores binding without literal", async () => {
|
|
99
|
+
const dir = mkdtempSync(join(tmpdir(), "cfg-from-env-"));
|
|
100
|
+
const prevHome = process.env.HOME;
|
|
101
|
+
process.env.HOME = dir;
|
|
102
|
+
const prev = process.env.API_TOKEN;
|
|
103
|
+
process.env.API_TOKEN = "from-env";
|
|
104
|
+
try {
|
|
105
|
+
const program = configFixture();
|
|
106
|
+
writeAppConfigFile(program, { apiToken: "seed" }, { partial: true });
|
|
107
|
+
const result = await new Cli(program).invoke(["configure", "set", "apiToken", "--from-env"]);
|
|
108
|
+
expect(result.exitCode).toBe(0);
|
|
109
|
+
const raw = await new Cli(program).invoke(["configure", "get", "apiToken"]);
|
|
110
|
+
expect(raw.stdout.trim()).toBe("REDACTED");
|
|
111
|
+
const configPath = resolveAppConfigPath(program);
|
|
112
|
+
const { readFileSync } = await import("node:fs");
|
|
113
|
+
const onDisk = JSON.parse(readFileSync(configPath, "utf8")) as Record<string, unknown>;
|
|
114
|
+
expect(onDisk.apiToken).toBeUndefined();
|
|
115
|
+
expect(readBindings(onDisk).apiToken).toBe("env");
|
|
116
|
+
} finally {
|
|
117
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
118
|
+
else process.env.HOME = prevHome;
|
|
119
|
+
if (prev === undefined) delete process.env.API_TOKEN;
|
|
120
|
+
else process.env.API_TOKEN = prev;
|
|
121
|
+
rmSync(dir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
96
124
|
});
|
package/src/builtins/config.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Built-in `configure get` / `configure set` subcommands.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { clearFileValue, setBinding } from "../config/bindings.ts";
|
|
5
6
|
import { bootstrapAppConfig } from "../config/bootstrap.ts";
|
|
6
7
|
import {
|
|
7
8
|
configCommandsEnabled,
|
|
@@ -28,12 +29,7 @@ const PRETTY_OPTION: CliOption = {
|
|
|
28
29
|
kind: CliOptionKind.Presence,
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
function configGetOutput(
|
|
32
|
-
program: CliProgram,
|
|
33
|
-
key: string | undefined,
|
|
34
|
-
json: boolean,
|
|
35
|
-
pretty: boolean,
|
|
36
|
-
): void {
|
|
32
|
+
function configGetOutput(program: CliProgram, key: string | undefined, json: boolean, pretty: boolean): void {
|
|
37
33
|
const appConfig = program.appConfig;
|
|
38
34
|
if (!appConfig) {
|
|
39
35
|
return;
|
|
@@ -106,6 +102,31 @@ function configGetOutput(
|
|
|
106
102
|
}
|
|
107
103
|
}
|
|
108
104
|
|
|
105
|
+
const FROM_ENV_OPTION: CliOption = {
|
|
106
|
+
name: "from-env",
|
|
107
|
+
description: "Bind this key to its mapped environment variable (no literal value stored).",
|
|
108
|
+
kind: CliOptionKind.Presence,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function configSetFromEnv(program: CliProgram, key: string): void {
|
|
112
|
+
const appConfig = program.appConfig;
|
|
113
|
+
if (!appConfig) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const entry = appConfig.entries[key];
|
|
117
|
+
if (!entry?.env) {
|
|
118
|
+
process.stderr.write(`Configuration key '${key}' has no env mapping for --from-env.\n`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const hostEnv = captureMappedHostEnv(program);
|
|
122
|
+
const { fileData } = bootstrapAppConfig(program, { validateFile: true });
|
|
123
|
+
let next = clearFileValue(fileData, key);
|
|
124
|
+
next = setBinding(next, key, "env");
|
|
125
|
+
writeAppConfigFile(program, next, { partial: true });
|
|
126
|
+
const resolved = resolveAppConfig(program, next, hostEnv);
|
|
127
|
+
exportConfigToEnv(program, resolved, hostEnv);
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
function configSetRun(program: CliProgram, key: string, rawValue: string, useJson: boolean): void {
|
|
110
131
|
const appConfig = program.appConfig;
|
|
111
132
|
if (!appConfig) {
|
|
@@ -135,8 +156,8 @@ function configSetRun(program: CliProgram, key: string, rawValue: string, useJso
|
|
|
135
156
|
|
|
136
157
|
const hostEnv = captureMappedHostEnv(program);
|
|
137
158
|
const { fileData } = bootstrapAppConfig(program, { validateFile: true });
|
|
138
|
-
const next = { ...fileData, [key]: parsed };
|
|
139
|
-
writeAppConfigFile(program, next);
|
|
159
|
+
const next = setBinding({ ...fileData, [key]: parsed }, key, "file");
|
|
160
|
+
writeAppConfigFile(program, next, { partial: true });
|
|
140
161
|
const resolved = resolveAppConfig(program, next, hostEnv);
|
|
141
162
|
exportConfigToEnv(program, resolved, hostEnv);
|
|
142
163
|
}
|
|
@@ -166,7 +187,7 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
|
|
|
166
187
|
return {
|
|
167
188
|
key: "set",
|
|
168
189
|
description: "Write one configuration key to the config file.",
|
|
169
|
-
options: [JSON_OPTION],
|
|
190
|
+
options: [JSON_OPTION, FROM_ENV_OPTION],
|
|
170
191
|
mcpTool: mcpSetEnabled ? undefined : { enabled: false },
|
|
171
192
|
positionals: [
|
|
172
193
|
{
|
|
@@ -191,10 +212,19 @@ function configSetLeaf(program: CliProgram, mcpSetEnabled: boolean): CliLeaf {
|
|
|
191
212
|
process.stderr.write("configure set requires a key.\n");
|
|
192
213
|
process.exit(1);
|
|
193
214
|
}
|
|
215
|
+
if (ctx.hasFlag("from-env")) {
|
|
216
|
+
const raw = ctx.args[1];
|
|
217
|
+
if (raw !== undefined && raw.length > 0) {
|
|
218
|
+
process.stderr.write("configure set --from-env does not accept a value.\n");
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
configSetFromEnv(program, key);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
194
224
|
const raw = ctx.args[1];
|
|
195
225
|
if (raw === undefined || raw.length === 0) {
|
|
196
226
|
if (!ctx.hasFlag("json")) {
|
|
197
|
-
process.stderr.write("configure set requires a value (or --
|
|
227
|
+
process.stderr.write("configure set requires a value (or --from-env).\n");
|
|
198
228
|
process.exit(1);
|
|
199
229
|
}
|
|
200
230
|
process.stderr.write("configure set requires a value.\n");
|
|
@@ -20,7 +20,7 @@ function enabledKinds(program: CliProgram, caps: CliCapabilities): Kind[] {
|
|
|
20
20
|
|
|
21
21
|
function joinEnglish(items: string[]): string {
|
|
22
22
|
if (items.length === 0) return "agent artifacts";
|
|
23
|
-
if (items.length === 1) return items[0]
|
|
23
|
+
if (items.length === 1) return items[0] ?? "agent artifacts";
|
|
24
24
|
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
25
25
|
return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
|
|
26
26
|
}
|
|
@@ -41,7 +41,7 @@ export function configureSyncOptionDescription(program: CliProgram, caps: CliCap
|
|
|
41
41
|
return `Refresh installed ${short(program, caps)}. Used by Homebrew post_install.`;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function docsSkillTopicDescription(
|
|
44
|
+
export function docsSkillTopicDescription(_program: CliProgram, caps: CliCapabilities): string {
|
|
45
45
|
if (caps.configure) {
|
|
46
46
|
return "Print a reference agent SKILL; run `configure` to install an optimized copy.";
|
|
47
47
|
}
|
|
@@ -78,9 +78,6 @@ export function configureCommandNotes(program: CliProgram, _caps: CliCapabilitie
|
|
|
78
78
|
if (program.appConfig) {
|
|
79
79
|
lines.push("Remove app config only:", ` ${app} configure --remove-config --yes`, "");
|
|
80
80
|
}
|
|
81
|
-
lines.push(
|
|
82
|
-
"Use --dry to preview changes without writing files.",
|
|
83
|
-
"Use --json for machine-readable output.",
|
|
84
|
-
);
|
|
81
|
+
lines.push("Use --dry to preview changes without writing files.", "Use --json for machine-readable output.");
|
|
85
82
|
return lines.join("\n");
|
|
86
83
|
}
|
package/src/builtins/dispatch.ts
CHANGED
|
@@ -11,11 +11,7 @@ import { completionBashScript } from "./completion-bash.ts";
|
|
|
11
11
|
import { completionFishScript } from "./completion-fish.ts";
|
|
12
12
|
import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
|
|
13
13
|
import { completionZshScript } from "./completion-zsh.ts";
|
|
14
|
-
import {
|
|
15
|
-
CONFIGURE_RUN_KEY,
|
|
16
|
-
cliBuiltinConfigureCommand,
|
|
17
|
-
isConfigureConfigPath,
|
|
18
|
-
} from "./configure.ts";
|
|
14
|
+
import { CONFIGURE_RUN_KEY, cliBuiltinConfigureCommand, isConfigureConfigPath } from "./configure.ts";
|
|
19
15
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
20
16
|
import { cliPresentationRoot } from "./presentation.ts";
|
|
21
17
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
@@ -35,11 +31,7 @@ function completionSchema(program: CliProgram, opts: DispatchBuiltinOpts): CliRo
|
|
|
35
31
|
/**
|
|
36
32
|
* Handles built-in commands after parse.
|
|
37
33
|
*/
|
|
38
|
-
export async function dispatchBuiltin(
|
|
39
|
-
program: CliProgram,
|
|
40
|
-
pr: ParseResult,
|
|
41
|
-
opts: DispatchBuiltinOpts,
|
|
42
|
-
): Promise<void> {
|
|
34
|
+
export async function dispatchBuiltin(program: CliProgram, pr: ParseResult, opts: DispatchBuiltinOpts): Promise<void> {
|
|
43
35
|
if (pr.kind !== ParseKind.Ok) {
|
|
44
36
|
return;
|
|
45
37
|
}
|
package/src/builtins/export.ts
CHANGED
|
@@ -41,9 +41,7 @@ function exportBuiltinNode(cmd: CliNode): CliSchemaExport | null {
|
|
|
41
41
|
if (cmd.fallbackMode !== undefined) {
|
|
42
42
|
out.fallbackMode = cmd.fallbackMode;
|
|
43
43
|
}
|
|
44
|
-
const children = cmd.commands
|
|
45
|
-
.map((ch) => exportBuiltinNode(ch))
|
|
46
|
-
.filter((ch): ch is CliSchemaExport => ch !== null);
|
|
44
|
+
const children = cmd.commands.map((ch) => exportBuiltinNode(ch)).filter((ch): ch is CliSchemaExport => ch !== null);
|
|
47
45
|
if (children.length > 0) {
|
|
48
46
|
out.commands = children;
|
|
49
47
|
}
|
|
@@ -64,9 +64,7 @@ export function cliPresentationRoot(program: CliProgram): CliRouter {
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const userCommands = program.commands
|
|
68
|
-
.map((ch) => presentationNode(ch))
|
|
69
|
-
.filter((ch): ch is CliNode => ch !== null);
|
|
67
|
+
const userCommands = program.commands.map((ch) => presentationNode(ch)).filter((ch): ch is CliNode => ch !== null);
|
|
70
68
|
|
|
71
69
|
return {
|
|
72
70
|
key: program.key,
|
|
@@ -80,10 +78,7 @@ export function cliPresentationRoot(program: CliProgram): CliRouter {
|
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
/** Root help notes: consumer `program.notes` plus agent discovery when `docs` is enabled. */
|
|
83
|
-
export function presentationRootNotes(
|
|
84
|
-
program: CliProgram,
|
|
85
|
-
caps: CliCapabilities,
|
|
86
|
-
): string | undefined {
|
|
81
|
+
export function presentationRootNotes(program: CliProgram, caps: CliCapabilities): string | undefined {
|
|
87
82
|
const parts: string[] = [];
|
|
88
83
|
if ((program.notes ?? "").trim().length > 0) {
|
|
89
84
|
parts.push((program.notes ?? "").trim());
|
package/src/builtins/registry.ts
CHANGED
|
@@ -8,11 +8,7 @@ import { cliBuiltinVersionCommand } from "./version.ts";
|
|
|
8
8
|
|
|
9
9
|
type BuiltinFactory = (program: CliProgram) => CliNode | null;
|
|
10
10
|
|
|
11
|
-
function pushBuiltin(
|
|
12
|
-
builtins: CliNode[],
|
|
13
|
-
program: CliProgram,
|
|
14
|
-
factory: BuiltinFactory | null,
|
|
15
|
-
): void {
|
|
11
|
+
function pushBuiltin(builtins: CliNode[], program: CliProgram, factory: BuiltinFactory | null): void {
|
|
16
12
|
if (!factory) {
|
|
17
13
|
return;
|
|
18
14
|
}
|
|
@@ -52,13 +52,10 @@ describe("argsbarg create", () => {
|
|
|
52
52
|
|
|
53
53
|
/** ResolveCreateOptions derives identity defaults from key. */
|
|
54
54
|
test("resolveCreateOptions derives identity defaults from key", () => {
|
|
55
|
-
expect(resolveCreateOptions({ key: "1password", releaseRepo: "org/1password" }).className).toBe(
|
|
56
|
-
|
|
55
|
+
expect(resolveCreateOptions({ key: "1password", releaseRepo: "org/1password" }).className).toBe("App1password");
|
|
56
|
+
expect(resolveCreateOptions({ key: "my-cli", className: "Custom", releaseRepo: "org/my-cli" }).className).toBe(
|
|
57
|
+
"Custom",
|
|
57
58
|
);
|
|
58
|
-
expect(
|
|
59
|
-
resolveCreateOptions({ key: "my-cli", className: "Custom", releaseRepo: "org/my-cli" })
|
|
60
|
-
.className,
|
|
61
|
-
).toBe("Custom");
|
|
62
59
|
const opts = resolveCreateOptions({ key: "at1", releaseRepo: "bdombro/at1" });
|
|
63
60
|
expect(opts.tap).toBe("bdombro/at1");
|
|
64
61
|
expect(opts.releaseRepo).toBe("bdombro/at1");
|
package/src/cli-tool/create.ts
CHANGED
|
@@ -70,9 +70,7 @@ function tapLibraryParts(tap: string): { org: string; repo: string } {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/** Parse `scripts/create-identity.ts` for inference and template defaults. */
|
|
73
|
-
export function parseCreateIdentityFile(
|
|
74
|
-
path: string,
|
|
75
|
-
): Partial<CreateOptions> & { envPrefix?: string } {
|
|
73
|
+
export function parseCreateIdentityFile(path: string): Partial<CreateOptions> & { envPrefix?: string } {
|
|
76
74
|
if (!existsSync(path)) return {};
|
|
77
75
|
const text = readFileSync(path, "utf8");
|
|
78
76
|
const pick = (field: string) => text.match(new RegExp(`${field}:\\s*"([^"]*)"`))?.[1];
|
|
@@ -109,18 +107,8 @@ export function templateIdentity(): {
|
|
|
109
107
|
};
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
export function inferCreateOptions(
|
|
113
|
-
|
|
114
|
-
partial: Partial<CreateOptions>,
|
|
115
|
-
): Partial<CreateOptions> {
|
|
116
|
-
if (
|
|
117
|
-
partial.key &&
|
|
118
|
-
partial.className &&
|
|
119
|
-
partial.homepage &&
|
|
120
|
-
partial.releaseRepo &&
|
|
121
|
-
partial.desc &&
|
|
122
|
-
partial.tap
|
|
123
|
-
) {
|
|
110
|
+
export function inferCreateOptions(baseDir: string, partial: Partial<CreateOptions>): Partial<CreateOptions> {
|
|
111
|
+
if (partial.key && partial.className && partial.homepage && partial.releaseRepo && partial.desc && partial.tap) {
|
|
124
112
|
return partial;
|
|
125
113
|
}
|
|
126
114
|
const fromIdentity = parseCreateIdentityFile(join(baseDir, CREATE_IDENTITY_REL));
|
|
@@ -146,19 +134,14 @@ function assertReleaseRepoFormat(releaseRepo: string): void {
|
|
|
146
134
|
}
|
|
147
135
|
}
|
|
148
136
|
|
|
149
|
-
export function resolveCreateOptions(
|
|
150
|
-
partial: Partial<CreateOptions>,
|
|
151
|
-
baseDir?: string,
|
|
152
|
-
): CreateOptions {
|
|
137
|
+
export function resolveCreateOptions(partial: Partial<CreateOptions>, baseDir?: string): CreateOptions {
|
|
153
138
|
const merged = baseDir ? inferCreateOptions(baseDir, partial) : partial;
|
|
154
139
|
const tmpl = templateIdentity();
|
|
155
140
|
const key = merged.key ?? tmpl.key ?? "full-example";
|
|
156
141
|
const className = merged.className ?? classNameFromKey(key);
|
|
157
142
|
const releaseRepo = merged.releaseRepo;
|
|
158
143
|
if (!releaseRepo) {
|
|
159
|
-
throw new Error(
|
|
160
|
-
"GitHub release repo (org/repo) is required. Pass --release-repo or use the interactive wizard.",
|
|
161
|
-
);
|
|
144
|
+
throw new Error("GitHub release repo (org/repo) is required. Pass --release-repo or use the interactive wizard.");
|
|
162
145
|
}
|
|
163
146
|
assertReleaseRepoFormat(releaseRepo);
|
|
164
147
|
const devTemplate = merged.devTemplate ?? (baseDir ? isDevTemplateDir(baseDir) : false);
|
|
@@ -69,9 +69,7 @@ export async function runPostCreate(targetDir: string, dryRun: boolean): Promise
|
|
|
69
69
|
run: () => {
|
|
70
70
|
if (dryRun) return;
|
|
71
71
|
if (shouldSkipGitBootstrap(abs)) {
|
|
72
|
-
process.stderr.write(
|
|
73
|
-
"Skipping git bootstrap (existing repo or nested in git work tree).\n",
|
|
74
|
-
);
|
|
72
|
+
process.stderr.write("Skipping git bootstrap (existing repo or nested in git work tree).\n");
|
|
75
73
|
return;
|
|
76
74
|
}
|
|
77
75
|
let proc = Bun.spawnSync(["git", "init"], {
|
package/src/cli-tool/program.ts
CHANGED
|
@@ -40,8 +40,7 @@ export const program = {
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
name: "desc",
|
|
43
|
-
description:
|
|
44
|
-
"App + formula description (default: <ClassName> CLI; stored in create-identity.ts).",
|
|
43
|
+
description: "App + formula description (default: <ClassName> CLI; stored in create-identity.ts).",
|
|
45
44
|
kind: CliOptionKind.String,
|
|
46
45
|
},
|
|
47
46
|
{ name: "force", description: "Overwrite existing files.", kind: CliOptionKind.Presence },
|
|
@@ -39,9 +39,7 @@ function collectInteractiveOptions(
|
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
process.stderr.write(`\nTarget: ${baseDir}\n`);
|
|
42
|
-
process.stderr.write(
|
|
43
|
-
`Key: ${opts.key} Class: ${opts.className} Tap: ${opts.tap} Release: ${opts.releaseRepo}\n`,
|
|
44
|
-
);
|
|
42
|
+
process.stderr.write(`Key: ${opts.key} Class: ${opts.className} Tap: ${opts.tap} Release: ${opts.releaseRepo}\n`);
|
|
45
43
|
process.stderr.write(`Homepage: ${opts.homepage}\n\n`);
|
|
46
44
|
const tree = renderCreateTree(opts);
|
|
47
45
|
process.stderr.write(`Files (${tree.size}):\n`);
|
|
@@ -101,11 +99,7 @@ export async function runCreate(input: Partial<CreateOptions> & { dir?: string }
|
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
async function runCreateApply(
|
|
105
|
-
baseDir: string,
|
|
106
|
-
opts: CreateOptions,
|
|
107
|
-
dryRun: boolean,
|
|
108
|
-
): Promise<number> {
|
|
102
|
+
async function runCreateApply(baseDir: string, opts: CreateOptions, dryRun: boolean): Promise<number> {
|
|
109
103
|
if (dryRun) {
|
|
110
104
|
const written = applyCreate(baseDir, { ...opts, dryRun: true, check: false });
|
|
111
105
|
process.stdout.write(`Would write ${written.length} file(s) under ${baseDir}\n`);
|
package/src/cli.ts
CHANGED
|
@@ -11,12 +11,9 @@ import {
|
|
|
11
11
|
resolveCapabilities,
|
|
12
12
|
skipsRequiredAppConfigExit,
|
|
13
13
|
} from "./capabilities.ts";
|
|
14
|
-
import {
|
|
15
|
-
bootstrapAppConfig,
|
|
16
|
-
type EnsureAppConfigOpts,
|
|
17
|
-
ensureAppConfig,
|
|
18
|
-
} from "./config/bootstrap.ts";
|
|
14
|
+
import { bootstrapAppConfig, type EnsureAppConfigOpts, ensureAppConfig } from "./config/bootstrap.ts";
|
|
19
15
|
import { type AnyAppConfigSnapshot, createAppConfigSnapshot } from "./config/context.ts";
|
|
16
|
+
import { readAppConfigFileRaw, resolveAppConfigPath } from "./config/file.ts";
|
|
20
17
|
import { effectiveJsonSchema } from "./config/schema.ts";
|
|
21
18
|
import { CliContext } from "./context.ts";
|
|
22
19
|
import { cliHelpRender } from "./help.ts";
|
|
@@ -124,15 +121,7 @@ export class Cli {
|
|
|
124
121
|
exitOnMissing: !skipRequiredConfig,
|
|
125
122
|
});
|
|
126
123
|
|
|
127
|
-
const ctx = new CliContext(
|
|
128
|
-
this.program.key,
|
|
129
|
-
pr.path,
|
|
130
|
-
pr.args,
|
|
131
|
-
pr.opts,
|
|
132
|
-
this.program,
|
|
133
|
-
"cli",
|
|
134
|
-
snapshot,
|
|
135
|
-
);
|
|
124
|
+
const ctx = new CliContext(this.program.key, pr.path, pr.args, pr.opts, this.program, "cli", snapshot);
|
|
136
125
|
try {
|
|
137
126
|
await Promise.resolve(leaf.handler(ctx));
|
|
138
127
|
process.exit(0);
|
|
@@ -171,15 +160,7 @@ export class Cli {
|
|
|
171
160
|
exitOnMissing: false,
|
|
172
161
|
});
|
|
173
162
|
|
|
174
|
-
const ctx = new CliContext(
|
|
175
|
-
this.program.key,
|
|
176
|
-
pr.path,
|
|
177
|
-
pr.args,
|
|
178
|
-
pr.opts,
|
|
179
|
-
this.program,
|
|
180
|
-
"mcp",
|
|
181
|
-
snapshot,
|
|
182
|
-
);
|
|
163
|
+
const ctx = new CliContext(this.program.key, pr.path, pr.args, pr.opts, this.program, "mcp", snapshot);
|
|
183
164
|
|
|
184
165
|
let stdout = "";
|
|
185
166
|
let stderr = "";
|
|
@@ -293,9 +274,7 @@ export class Cli {
|
|
|
293
274
|
): PreparedDispatch | { error: ParseResult } {
|
|
294
275
|
const program = this.program;
|
|
295
276
|
let parseRoot: CliNode;
|
|
296
|
-
let completionParseRoot: CliRouter = opts?.presentationFallback
|
|
297
|
-
? this.presentationRoot
|
|
298
|
-
: this.parseRootMerged;
|
|
277
|
+
let completionParseRoot: CliRouter = opts?.presentationFallback ? this.presentationRoot : this.parseRootMerged;
|
|
299
278
|
let isLeafCompletionIntercept = false;
|
|
300
279
|
|
|
301
280
|
if (isCliLeaf(program)) {
|
|
@@ -387,7 +366,7 @@ export class Cli {
|
|
|
387
366
|
const bootstrap = ensureAppConfig(this.program, opts);
|
|
388
367
|
const snapshot = bootstrap
|
|
389
368
|
? createAppConfigSnapshot(this.program, bootstrap.fileData, bootstrap.resolved)
|
|
390
|
-
: createAppConfigSnapshot(this.program,
|
|
369
|
+
: createAppConfigSnapshot(this.program, readAppConfigFileRaw(resolveAppConfigPath(this.program)), {});
|
|
391
370
|
this._appConfig = snapshot;
|
|
392
371
|
return snapshot;
|
|
393
372
|
}
|