argsbarg 6.3.0 → 6.3.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 +15 -3
- package/docs/configure.md +2 -2
- package/docs/distribution-homebrew.md +5 -4
- package/examples/full-example/justfile +9 -30
- package/examples/full-example-json/justfile +9 -30
- package/index.d.ts +5 -0
- package/package.json +1 -1
- package/src/builtins/config.test.ts +16 -16
- package/src/config/context.test.ts +12 -12
- package/src/config/file.test.ts +4 -4
- package/src/configure/artifacts/binary-placement.test.ts +4 -4
- package/src/configure/artifacts/mcp-codex.test.ts +5 -5
- package/src/configure/artifacts/mcp-openclaw.test.ts +5 -5
- package/src/configure/artifacts/mcp-opencode.test.ts +5 -5
- package/src/configure/artifacts/status.test.ts +5 -5
- package/src/configure/configure.test.ts +5 -5
- package/src/core/parse.test.ts +7 -7
- package/src/index.ts +1 -0
- package/src/paths/host.test.ts +58 -0
- package/src/paths/host.ts +12 -3
- package/src/test/integration/config.test.ts +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [6.3.2] - 2026-08-07
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [6.3.1] - 2026-08-07
|
|
14
|
+
|
|
15
|
+
|
|
10
16
|
## [6.3.0] - 2026-08-07
|
|
11
17
|
|
|
12
18
|
### Added
|
|
@@ -14,10 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
20
|
- **Cursor `stop` hook** — `.cursor/hooks.json` runs `just test` after agent edits to `justfile` or `*.{ts,tsx,js,jsx}` (excluding `node_modules/`, `dist/`, `.cursor/`); test failures auto-submit up to 20 follow-ups. Shipped in copy templates and argsbarg repo root (Cursor-only; not in `AGENTS.md`).
|
|
15
21
|
- **`configure` lifecycle hooks** — `program.configure.afterRefresh` and `program.configure.beforeRemoveAll` for app-specific agent artifact setup/teardown around `configure --refresh` and `configure --remove-all`.
|
|
16
22
|
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`userHome()`** — resolves home from `TEST_USER_HOME` (tests) or platform defaults (`/Users/$USER`, `/home/$USER`, `USERPROFILE`). Never reads `$HOME`, fixing skills installing under Homebrew's `post_install` sandbox.
|
|
26
|
+
|
|
17
27
|
### Changed
|
|
18
28
|
|
|
19
|
-
- **Breaking: `configure --sync` → `configure --refresh`** — renames the non-interactive agent-artifact refresh flag (skills, MCP, config bootstrap).
|
|
20
|
-
- **Copy-template justfiles** — `install-local`
|
|
29
|
+
- **Breaking: `configure --sync` → `configure --refresh`** — renames the non-interactive agent-artifact refresh flag (skills, MCP, config bootstrap). No `--sync` alias. Copy-template justfiles no longer ship a `refresh-artifacts` recipe; run `configure --refresh --yes` directly.
|
|
30
|
+
- **Copy-template justfiles** — `install-local` depends on `uninstall` then `build` (`brew reinstall || brew install --force`). Dropped `uninstall-artifacts`, `uninstall-formula`, `uninstall-release`, and `uninstall-release-tap` (use `uninstall` + `uninstall-config`). Brew recipes prefix `HOMEBREW_NO_ASK=1`.
|
|
21
31
|
|
|
22
32
|
## [6.2.2] - 2026-08-07
|
|
23
33
|
|
|
@@ -915,7 +925,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
915
925
|
- Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
|
|
916
926
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
917
927
|
|
|
918
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v6.3.
|
|
928
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v6.3.2...HEAD
|
|
929
|
+
[6.3.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.3.2
|
|
930
|
+
[6.3.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.3.1
|
|
919
931
|
[6.3.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.3.0
|
|
920
932
|
[6.2.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.2.2
|
|
921
933
|
[6.2.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.2.1
|
package/docs/configure.md
CHANGED
|
@@ -32,10 +32,10 @@ To remove app config only (keep skills/MCP), run `configure --remove-config --ye
|
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
just build
|
|
35
|
-
just install-local #
|
|
35
|
+
just install-local # uninstall, then build + brew install (`just install` is an alias)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Dev flow matches release: formula `
|
|
38
|
+
Dev flow matches release: `install-local` runs `uninstall` first (keg + untap; formula hook runs `configure --remove-all`), then stages the dev formula and installs. `post_install` runs `<key> configure --refresh --yes` for skills/MCP. Use `just reinstall-local` to swap the binary into Cellar during tight edit cycles (skips completions and `post_install`). Run `<key> configure --refresh --yes` (or `just run configure --refresh --yes`) to refresh agent artifacts without touching the binary.
|
|
39
39
|
|
|
40
40
|
## Quick reference
|
|
41
41
|
|
|
@@ -116,7 +116,7 @@ ArgsBarg provides an optimized workflow for developers to build, package, and te
|
|
|
116
116
|
# 1. Build the local release binary
|
|
117
117
|
just build
|
|
118
118
|
|
|
119
|
-
# 2.
|
|
119
|
+
# 2. Uninstall any existing formula/tap, stage and install locally (bypasses GitHub, uses file://)
|
|
120
120
|
just install-local
|
|
121
121
|
|
|
122
122
|
# 3. Swap updated binaries quickly during tight edit cycles
|
|
@@ -130,9 +130,10 @@ just uninstall
|
|
|
130
130
|
|
|
131
131
|
To ensure you test the exact formula that will be shipped to production, `just install-local` runs:
|
|
132
132
|
|
|
133
|
-
1. `
|
|
134
|
-
2. `
|
|
135
|
-
3. `
|
|
133
|
+
1. `just uninstall` — Remove any existing keg and untap (formula `uninstall` hook runs `configure --remove-all`).
|
|
134
|
+
2. `bun scripts/dev-formula.ts install` — Safely backs up your production formula and writes a temporary local dev formula using a `file://` URL pointing to your build directory.
|
|
135
|
+
3. `brew reinstall || brew install --force` — Installs the package locally using Homebrew.
|
|
136
|
+
4. `bun scripts/dev-formula.ts reset` — Automatically restores your production formula on disk.
|
|
136
137
|
|
|
137
138
|
---
|
|
138
139
|
|
|
@@ -66,17 +66,12 @@ http:
|
|
|
66
66
|
# Alias for backward compatibility
|
|
67
67
|
install: install-local
|
|
68
68
|
|
|
69
|
-
# Refresh skills and MCP without reinstalling the formula
|
|
70
|
-
refresh-artifacts:
|
|
71
|
-
just run configure --refresh --yes
|
|
72
|
-
|
|
73
69
|
# Dev install: build, stage dev formula, brew install, restore release formula
|
|
74
|
-
install-local: build
|
|
75
|
-
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
70
|
+
install-local: uninstall build
|
|
76
71
|
mkdir -p {{tap_parent}}
|
|
77
72
|
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
78
73
|
bun scripts/dev-formula.ts install
|
|
79
|
-
brew install --force --formula {{tap}}/{{cli_key}}
|
|
74
|
+
HOMEBREW_NO_ASK=1 brew reinstall --formula {{tap}}/{{cli_key}} || HOMEBREW_NO_ASK=1 brew install --force --formula {{tap}}/{{cli_key}}
|
|
80
75
|
bun scripts/dev-formula.ts reset
|
|
81
76
|
@echo ""
|
|
82
77
|
@echo "Next: {{cli_key}} configure"
|
|
@@ -121,10 +116,10 @@ release *ARGS:
|
|
|
121
116
|
|
|
122
117
|
# Install release formula from tap and run formula test
|
|
123
118
|
test-release:
|
|
124
|
-
|
|
119
|
+
HOMEBREW_NO_ASK=1 brew untap {{tap}} 2>/dev/null || true
|
|
125
120
|
mkdir -p {{tap_parent}}
|
|
126
121
|
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
127
|
-
|
|
122
|
+
HOMEBREW_NO_ASK=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
128
123
|
brew install --formula {{tap}}/{{cli_key}}
|
|
129
124
|
brew test {{cli_key}}
|
|
130
125
|
|
|
@@ -132,28 +127,12 @@ test-release:
|
|
|
132
127
|
typecheck:
|
|
133
128
|
bun run tsc --noEmit
|
|
134
129
|
|
|
135
|
-
# Undo
|
|
136
|
-
uninstall:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
just run configure --remove-all --yes
|
|
130
|
+
# Undo dev/Homebrew install (formula uninstall hook runs configure --remove-all)
|
|
131
|
+
uninstall:
|
|
132
|
+
@HOMEBREW_NO_ASK=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
133
|
+
@HOMEBREW_NO_ASK=1 brew uninstall --formula {{tap}}/{{cli_key}}-local 2>/dev/null || true
|
|
134
|
+
@HOMEBREW_NO_ASK=1 brew untap {{tap}} 2>/dev/null || true
|
|
141
135
|
|
|
142
136
|
# Remove app config file only
|
|
143
137
|
uninstall-config:
|
|
144
138
|
just run configure --remove-config --yes
|
|
145
|
-
|
|
146
|
-
# Remove formula and untap
|
|
147
|
-
uninstall-formula:
|
|
148
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
149
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}}-local 2>/dev/null || true
|
|
150
|
-
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
151
|
-
|
|
152
|
-
# Remove release formula (does not untap)
|
|
153
|
-
uninstall-release:
|
|
154
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
155
|
-
|
|
156
|
-
# Remove release formula and untap
|
|
157
|
-
uninstall-release-tap:
|
|
158
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
159
|
-
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
@@ -76,17 +76,12 @@ http:
|
|
|
76
76
|
# Alias for backward compatibility
|
|
77
77
|
install: install-local
|
|
78
78
|
|
|
79
|
-
# Refresh skills and MCP without reinstalling the formula
|
|
80
|
-
refresh-artifacts:
|
|
81
|
-
just run configure --refresh --yes
|
|
82
|
-
|
|
83
79
|
# Dev install: build, stage dev formula, brew install, restore release formula
|
|
84
|
-
install-local: build
|
|
85
|
-
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
80
|
+
install-local: uninstall build
|
|
86
81
|
mkdir -p {{tap_parent}}
|
|
87
82
|
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
88
83
|
bun scripts/dev-formula.ts install
|
|
89
|
-
brew install --force --formula {{tap}}/{{cli_key}}
|
|
84
|
+
HOMEBREW_NO_ASK=1 brew reinstall --formula {{tap}}/{{cli_key}} || HOMEBREW_NO_ASK=1 brew install --force --formula {{tap}}/{{cli_key}}
|
|
90
85
|
bun scripts/dev-formula.ts reset
|
|
91
86
|
@echo ""
|
|
92
87
|
@echo "Next: {{cli_key}} configure"
|
|
@@ -133,10 +128,10 @@ release *ARGS:
|
|
|
133
128
|
|
|
134
129
|
# Install release formula from tap and run formula test
|
|
135
130
|
test-release:
|
|
136
|
-
|
|
131
|
+
HOMEBREW_NO_ASK=1 brew untap {{tap}} 2>/dev/null || true
|
|
137
132
|
mkdir -p {{tap_parent}}
|
|
138
133
|
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
139
|
-
|
|
134
|
+
HOMEBREW_NO_ASK=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
140
135
|
brew install --formula {{tap}}/{{cli_key}}
|
|
141
136
|
brew test {{cli_key}}
|
|
142
137
|
|
|
@@ -144,28 +139,12 @@ test-release:
|
|
|
144
139
|
typecheck:
|
|
145
140
|
bun run tsc --noEmit
|
|
146
141
|
|
|
147
|
-
# Undo
|
|
148
|
-
uninstall:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
just run configure --remove-all --yes
|
|
142
|
+
# Undo dev/Homebrew install (formula uninstall hook runs configure --remove-all)
|
|
143
|
+
uninstall:
|
|
144
|
+
@HOMEBREW_NO_ASK=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
145
|
+
@HOMEBREW_NO_ASK=1 brew uninstall --formula {{tap}}/{{cli_key}}-local 2>/dev/null || true
|
|
146
|
+
@HOMEBREW_NO_ASK=1 brew untap {{tap}} 2>/dev/null || true
|
|
153
147
|
|
|
154
148
|
# Remove app config file only
|
|
155
149
|
uninstall-config:
|
|
156
150
|
just run configure --remove-config --yes
|
|
157
|
-
|
|
158
|
-
# Remove formula and untap
|
|
159
|
-
uninstall-formula:
|
|
160
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
161
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}}-local 2>/dev/null || true
|
|
162
|
-
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
163
|
-
|
|
164
|
-
# Remove release formula (does not untap)
|
|
165
|
-
uninstall-release:
|
|
166
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
167
|
-
|
|
168
|
-
# Remove release formula and untap
|
|
169
|
-
uninstall-release-tap:
|
|
170
|
-
@NONINTERACTIVE=1 brew uninstall --formula {{tap}}/{{cli_key}} 2>/dev/null || true
|
|
171
|
-
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
package/index.d.ts
CHANGED
|
@@ -909,6 +909,11 @@ export interface PackMcpBundleOpts {
|
|
|
909
909
|
* Requires the compiled binary to exist.
|
|
910
910
|
*/
|
|
911
911
|
export declare function packMcpBundle(program: CliProgram, opts?: PackMcpBundleOpts): string;
|
|
912
|
+
/**
|
|
913
|
+
* Resolves the user home directory without depending on `$HOME`.
|
|
914
|
+
* This is helpful for when homebrew post-install hooks run with a temporary `$HOME`.
|
|
915
|
+
*/
|
|
916
|
+
export declare function userHome(): string;
|
|
912
917
|
/** JSON-safe command node (no handlers). */
|
|
913
918
|
export interface CliSchemaExport {
|
|
914
919
|
key: string;
|
package/package.json
CHANGED
|
@@ -30,8 +30,8 @@ describe("builtins/config", () => {
|
|
|
30
30
|
/** Tests that config get redacts sensitive values. */
|
|
31
31
|
test("config get redacts sensitive values", async () => {
|
|
32
32
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
33
|
-
const
|
|
34
|
-
process.env.
|
|
33
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
34
|
+
process.env.TEST_USER_HOME = dir;
|
|
35
35
|
const prev = process.env.API_TOKEN;
|
|
36
36
|
delete process.env.API_TOKEN;
|
|
37
37
|
try {
|
|
@@ -43,8 +43,8 @@ describe("builtins/config", () => {
|
|
|
43
43
|
expect(result.exitCode).toBe(0);
|
|
44
44
|
expect(result.stdout.trim()).toBe("REDACTED");
|
|
45
45
|
} finally {
|
|
46
|
-
if (
|
|
47
|
-
else process.env.
|
|
46
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
47
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
48
48
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
49
49
|
rmSync(dir, { recursive: true, force: true });
|
|
50
50
|
}
|
|
@@ -53,8 +53,8 @@ describe("builtins/config", () => {
|
|
|
53
53
|
/** Tests that config get --json redacts sensitive as { set: true }. */
|
|
54
54
|
test("config get --json redacts sensitive as { set: true }", async () => {
|
|
55
55
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
56
|
-
const
|
|
57
|
-
process.env.
|
|
56
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
57
|
+
process.env.TEST_USER_HOME = dir;
|
|
58
58
|
const prev = process.env.API_TOKEN;
|
|
59
59
|
delete process.env.API_TOKEN;
|
|
60
60
|
try {
|
|
@@ -66,8 +66,8 @@ describe("builtins/config", () => {
|
|
|
66
66
|
expect(result.exitCode).toBe(0);
|
|
67
67
|
expect(JSON.parse(result.stdout)).toEqual({ set: true });
|
|
68
68
|
} finally {
|
|
69
|
-
if (
|
|
70
|
-
else process.env.
|
|
69
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
70
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
71
71
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
72
72
|
rmSync(dir, { recursive: true, force: true });
|
|
73
73
|
}
|
|
@@ -76,8 +76,8 @@ describe("builtins/config", () => {
|
|
|
76
76
|
/** Tests that config set writes and resolves without required exit. */
|
|
77
77
|
test("config set writes and resolves without required exit", async () => {
|
|
78
78
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
79
|
-
const
|
|
80
|
-
process.env.
|
|
79
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
80
|
+
process.env.TEST_USER_HOME = dir;
|
|
81
81
|
const prev = process.env.API_TOKEN;
|
|
82
82
|
delete process.env.API_TOKEN;
|
|
83
83
|
try {
|
|
@@ -88,8 +88,8 @@ describe("builtins/config", () => {
|
|
|
88
88
|
const get = await new Cli(program).invoke(["configure", "get", "port"]);
|
|
89
89
|
expect(get.stdout.trim()).toBe("9090");
|
|
90
90
|
} finally {
|
|
91
|
-
if (
|
|
92
|
-
else process.env.
|
|
91
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
92
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
93
93
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
94
94
|
rmSync(dir, { recursive: true, force: true });
|
|
95
95
|
}
|
|
@@ -97,8 +97,8 @@ describe("builtins/config", () => {
|
|
|
97
97
|
|
|
98
98
|
test("config set --from-env stores binding without literal", async () => {
|
|
99
99
|
const dir = mkdtempSync(join(tmpdir(), "cfg-from-env-"));
|
|
100
|
-
const
|
|
101
|
-
process.env.
|
|
100
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
101
|
+
process.env.TEST_USER_HOME = dir;
|
|
102
102
|
const prev = process.env.API_TOKEN;
|
|
103
103
|
process.env.API_TOKEN = "from-env";
|
|
104
104
|
try {
|
|
@@ -114,8 +114,8 @@ describe("builtins/config", () => {
|
|
|
114
114
|
expect(onDisk.apiToken).toBeUndefined();
|
|
115
115
|
expect(readBindings(onDisk).apiToken).toBe("env");
|
|
116
116
|
} finally {
|
|
117
|
-
if (
|
|
118
|
-
else process.env.
|
|
117
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
118
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
119
119
|
if (prev === undefined) delete process.env.API_TOKEN;
|
|
120
120
|
else process.env.API_TOKEN = prev;
|
|
121
121
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -29,8 +29,8 @@ describe("config/context", () => {
|
|
|
29
29
|
/** Tests that AppConfigSnapshot get, require, read, set. */
|
|
30
30
|
test("AppConfigSnapshot get, require, read, set", () => {
|
|
31
31
|
const dir = mkdtempSync(join(tmpdir(), "ctx-test-"));
|
|
32
|
-
const
|
|
33
|
-
process.env.
|
|
32
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
33
|
+
process.env.TEST_USER_HOME = dir;
|
|
34
34
|
const prevToken = process.env.API_TOKEN;
|
|
35
35
|
delete process.env.API_TOKEN;
|
|
36
36
|
try {
|
|
@@ -48,8 +48,8 @@ describe("config/context", () => {
|
|
|
48
48
|
expect(ctx.get("note")).toBe("updated");
|
|
49
49
|
expect(ctx.read().note).toBe("updated");
|
|
50
50
|
} finally {
|
|
51
|
-
if (
|
|
52
|
-
else process.env.
|
|
51
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
52
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
53
53
|
if (prevToken === undefined) delete process.env.API_TOKEN;
|
|
54
54
|
else process.env.API_TOKEN = prevToken;
|
|
55
55
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -82,8 +82,8 @@ describe("config/context", () => {
|
|
|
82
82
|
|
|
83
83
|
test("AppConfigSnapshot unsafe read/write", () => {
|
|
84
84
|
const dir = mkdtempSync(join(tmpdir(), "ctx-unsafe-"));
|
|
85
|
-
const
|
|
86
|
-
process.env.
|
|
85
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
86
|
+
process.env.TEST_USER_HOME = dir;
|
|
87
87
|
const prevToken = process.env.API_TOKEN;
|
|
88
88
|
delete process.env.API_TOKEN;
|
|
89
89
|
try {
|
|
@@ -95,8 +95,8 @@ describe("config/context", () => {
|
|
|
95
95
|
ctx.setUnsafe("note", "raw");
|
|
96
96
|
expect(ctx.getUnsafe("note")).toBe("raw");
|
|
97
97
|
} finally {
|
|
98
|
-
if (
|
|
99
|
-
else process.env.
|
|
98
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
99
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
100
100
|
if (prevToken === undefined) delete process.env.API_TOKEN;
|
|
101
101
|
else process.env.API_TOKEN = prevToken;
|
|
102
102
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -105,8 +105,8 @@ describe("config/context", () => {
|
|
|
105
105
|
|
|
106
106
|
test("EmptyAppConfigSnapshot unsafe read/write", () => {
|
|
107
107
|
const dir = mkdtempSync(join(tmpdir(), "ctx-empty-unsafe-"));
|
|
108
|
-
const
|
|
109
|
-
process.env.
|
|
108
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
109
|
+
process.env.TEST_USER_HOME = dir;
|
|
110
110
|
try {
|
|
111
111
|
const programWithoutConfig: CliProgram = {
|
|
112
112
|
key: "rawapp",
|
|
@@ -119,8 +119,8 @@ describe("config/context", () => {
|
|
|
119
119
|
expect(empty.getUnsafe("custom")).toBe(42);
|
|
120
120
|
expect(empty.readUnsafe().custom).toBe(42);
|
|
121
121
|
} finally {
|
|
122
|
-
if (
|
|
123
|
-
else process.env.
|
|
122
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
123
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
124
124
|
rmSync(dir, { recursive: true, force: true });
|
|
125
125
|
}
|
|
126
126
|
});
|
package/src/config/file.test.ts
CHANGED
|
@@ -37,13 +37,13 @@ const program: CliProgram = {
|
|
|
37
37
|
|
|
38
38
|
function withHome<T>(fn: (home: string) => T): T {
|
|
39
39
|
const home = mkdtempSync(join(tmpdir(), "cfg-test-"));
|
|
40
|
-
const
|
|
41
|
-
process.env.
|
|
40
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
41
|
+
process.env.TEST_USER_HOME = home;
|
|
42
42
|
try {
|
|
43
43
|
return fn(home);
|
|
44
44
|
} finally {
|
|
45
|
-
if (
|
|
46
|
-
else process.env.
|
|
45
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
46
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
47
47
|
rmSync(home, { recursive: true, force: true });
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -84,14 +84,14 @@ describe("isAppInstalled", () => {
|
|
|
84
84
|
/** Tests that false when not on PATH and no local copy. */
|
|
85
85
|
test("false when not on PATH and no local copy", () => {
|
|
86
86
|
const home = mkdtempSync(join(tmpdir(), "argsbarg-placement-home-"));
|
|
87
|
-
const
|
|
88
|
-
process.env.
|
|
87
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
88
|
+
process.env.TEST_USER_HOME = home;
|
|
89
89
|
process.env.PATH = tmp;
|
|
90
90
|
try {
|
|
91
91
|
expect(isAppInstalled(program)).toBe(false);
|
|
92
92
|
} finally {
|
|
93
|
-
if (
|
|
94
|
-
else process.env.
|
|
93
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
94
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
95
95
|
rmSync(home, { recursive: true, force: true });
|
|
96
96
|
}
|
|
97
97
|
});
|
|
@@ -9,17 +9,17 @@ import { join } from "node:path";
|
|
|
9
9
|
import { codexMcpHasServer, readCodexMcpEntry, resolveCodexConfigPath } from "./mcp-codex.ts";
|
|
10
10
|
|
|
11
11
|
let home: string;
|
|
12
|
-
let
|
|
12
|
+
let prevTestHome: string | undefined;
|
|
13
13
|
|
|
14
14
|
beforeEach(() => {
|
|
15
15
|
home = mkdtempSync(join(tmpdir(), "argsbarg-codex-"));
|
|
16
|
-
|
|
17
|
-
process.env.
|
|
16
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
17
|
+
process.env.TEST_USER_HOME = home;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
afterEach(() => {
|
|
21
|
-
if (
|
|
22
|
-
else process.env.
|
|
21
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
22
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
23
23
|
rmSync(home, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -9,17 +9,17 @@ import { join } from "node:path";
|
|
|
9
9
|
import { openclawMcpHasServer, resolveOpenclawConfigPath } from "./mcp-openclaw.ts";
|
|
10
10
|
|
|
11
11
|
let home: string;
|
|
12
|
-
let
|
|
12
|
+
let prevTestHome: string | undefined;
|
|
13
13
|
|
|
14
14
|
beforeEach(() => {
|
|
15
15
|
home = mkdtempSync(join(tmpdir(), "argsbarg-openclaw-"));
|
|
16
|
-
|
|
17
|
-
process.env.
|
|
16
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
17
|
+
process.env.TEST_USER_HOME = home;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
afterEach(() => {
|
|
21
|
-
if (
|
|
22
|
-
else process.env.
|
|
21
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
22
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
23
23
|
rmSync(home, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -26,20 +26,20 @@ const fixture: CliProgram = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
let home: string;
|
|
29
|
-
let
|
|
29
|
+
let prevTestHome: string | undefined;
|
|
30
30
|
let prevXdg: string | undefined;
|
|
31
31
|
|
|
32
32
|
beforeEach(() => {
|
|
33
33
|
home = mkdtempSync(join(tmpdir(), "argsbarg-opencode-"));
|
|
34
|
-
|
|
34
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
35
35
|
prevXdg = process.env.XDG_CONFIG_HOME;
|
|
36
|
-
process.env.
|
|
36
|
+
process.env.TEST_USER_HOME = home;
|
|
37
37
|
delete process.env.XDG_CONFIG_HOME;
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
afterEach(() => {
|
|
41
|
-
if (
|
|
42
|
-
else process.env.
|
|
41
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
42
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
43
43
|
if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
44
44
|
else process.env.XDG_CONFIG_HOME = prevXdg;
|
|
45
45
|
rmSync(home, { recursive: true, force: true });
|
|
@@ -12,17 +12,17 @@ import { printInstallStatus } from "./status.ts";
|
|
|
12
12
|
import { resolveInstallTargetPreview } from "./target-scope.ts";
|
|
13
13
|
|
|
14
14
|
let home: string;
|
|
15
|
-
let
|
|
15
|
+
let prevTestHome: string | undefined;
|
|
16
16
|
|
|
17
17
|
beforeEach(() => {
|
|
18
18
|
home = mkdtempSync(join(tmpdir(), "argsbarg-status-"));
|
|
19
|
-
|
|
20
|
-
process.env.
|
|
19
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
20
|
+
process.env.TEST_USER_HOME = home;
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
afterEach(() => {
|
|
24
|
-
if (
|
|
25
|
-
else process.env.
|
|
24
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
25
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
26
26
|
rmSync(home, { recursive: true, force: true });
|
|
27
27
|
});
|
|
28
28
|
|
|
@@ -33,20 +33,20 @@ const fixture: CliProgram = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
let home: string;
|
|
36
|
-
let
|
|
36
|
+
let prevTestHome: string | undefined;
|
|
37
37
|
let prevXdg: string | undefined;
|
|
38
38
|
|
|
39
39
|
beforeEach(() => {
|
|
40
40
|
home = mkdtempSync(join(tmpdir(), "argsbarg-configure-"));
|
|
41
|
-
|
|
41
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
42
42
|
prevXdg = process.env.XDG_CONFIG_HOME;
|
|
43
|
-
process.env.
|
|
43
|
+
process.env.TEST_USER_HOME = home;
|
|
44
44
|
delete process.env.XDG_CONFIG_HOME;
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
afterEach(() => {
|
|
48
|
-
if (
|
|
49
|
-
else process.env.
|
|
48
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
49
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
50
50
|
if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
51
51
|
else process.env.XDG_CONFIG_HOME = prevXdg;
|
|
52
52
|
rmSync(home, { recursive: true, force: true });
|
package/src/core/parse.test.ts
CHANGED
|
@@ -1212,20 +1212,20 @@ test("cliSkillInstall writes project agent skill files", () => {
|
|
|
1212
1212
|
}
|
|
1213
1213
|
});
|
|
1214
1214
|
|
|
1215
|
-
/** CliSkillInstall global uses
|
|
1216
|
-
test("cliSkillInstall global uses
|
|
1215
|
+
/** CliSkillInstall global uses TEST_USER_HOME agents skills directory. */
|
|
1216
|
+
test("cliSkillInstall global uses TEST_USER_HOME agents skills directory", () => {
|
|
1217
1217
|
const home = mkdtempSync(join(tmpdir(), "argsbarg-home-"));
|
|
1218
|
-
const
|
|
1219
|
-
process.env.
|
|
1218
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
1219
|
+
process.env.TEST_USER_HOME = home;
|
|
1220
1220
|
try {
|
|
1221
1221
|
const files = cliSkillInstall(nestedMcpFixture, { global: true, rimraf: true });
|
|
1222
1222
|
expect(files.some((f) => f.includes(join(home, ".agents", "skills", "nested.ts")))).toBe(true);
|
|
1223
1223
|
expect(existsSync(join(home, ".agents", "skills", "nested.ts", "SKILL.md"))).toBe(true);
|
|
1224
1224
|
} finally {
|
|
1225
|
-
if (
|
|
1226
|
-
delete process.env.
|
|
1225
|
+
if (prevTestHome === undefined) {
|
|
1226
|
+
delete process.env.TEST_USER_HOME;
|
|
1227
1227
|
} else {
|
|
1228
|
-
process.env.
|
|
1228
|
+
process.env.TEST_USER_HOME = prevTestHome;
|
|
1229
1229
|
}
|
|
1230
1230
|
rmSync(home, { recursive: true, force: true });
|
|
1231
1231
|
}
|
package/src/index.ts
CHANGED
|
@@ -84,6 +84,7 @@ export type { EcsLogEvent, LogEnrichContext } from "./log/ecs.ts";
|
|
|
84
84
|
export { ECS_VERSION, formatEcsLine } from "./log/ecs.ts";
|
|
85
85
|
export type { McpBundlePaths, PackMcpBundleOpts } from "./mcp/bundle.ts";
|
|
86
86
|
export { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "./mcp/bundle.ts";
|
|
87
|
+
export { userHome } from "./paths/host.ts";
|
|
87
88
|
export { Cli, type CliInvokeKind, type CliInvokeResult } from "./runtime/cli.ts";
|
|
88
89
|
export { cliErrWithHelp } from "./runtime/cli-errors.ts";
|
|
89
90
|
export { isInteractiveTty } from "./utils.ts";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for paths/host module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir, userInfo } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { userHome } from "./host.ts";
|
|
10
|
+
|
|
11
|
+
describe("userHome", () => {
|
|
12
|
+
let prevTestHome: string | undefined;
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
16
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("uses TEST_USER_HOME when set", () => {
|
|
20
|
+
const home = mkdtempSync(join(tmpdir(), "argsbarg-test-home-"));
|
|
21
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
22
|
+
process.env.TEST_USER_HOME = home;
|
|
23
|
+
try {
|
|
24
|
+
expect(userHome()).toBe(home);
|
|
25
|
+
} finally {
|
|
26
|
+
rmSync(home, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("ignores sandboxed HOME (Homebrew post_install)", () => {
|
|
31
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
32
|
+
delete process.env.TEST_USER_HOME;
|
|
33
|
+
const prevHome = process.env.HOME;
|
|
34
|
+
process.env.HOME = "/private/tmp/sqsp-workspaces-postinstall-20260807-fake";
|
|
35
|
+
try {
|
|
36
|
+
const resolved = userHome();
|
|
37
|
+
expect(resolved).not.toBe(process.env.HOME);
|
|
38
|
+
if (process.platform === "darwin" && process.env.USER) {
|
|
39
|
+
expect(resolved).toBe(`/Users/${process.env.USER}`);
|
|
40
|
+
expect(existsSync(resolved)).toBe(true);
|
|
41
|
+
} else {
|
|
42
|
+
expect(resolved).toBe(userInfo().homedir);
|
|
43
|
+
}
|
|
44
|
+
} finally {
|
|
45
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
46
|
+
else process.env.HOME = prevHome;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("resolves macOS default home when TEST_USER_HOME unset", () => {
|
|
51
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
52
|
+
delete process.env.TEST_USER_HOME;
|
|
53
|
+
if (process.platform !== "darwin" || !process.env.USER) return;
|
|
54
|
+
const expected = `/Users/${process.env.USER}`;
|
|
55
|
+
if (!existsSync(expected)) return;
|
|
56
|
+
expect(userHome()).toBe(expected);
|
|
57
|
+
});
|
|
58
|
+
});
|
package/src/paths/host.ts
CHANGED
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
Shared host path primitives for install, config, and skill modules.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { userInfo } from "node:os";
|
|
6
7
|
import { join } from "node:path";
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the user home directory without depending on `$HOME`.
|
|
11
|
+
* This is helpful for when homebrew post-install hooks run with a temporary `$HOME`.
|
|
12
|
+
*/
|
|
9
13
|
export function userHome(): string {
|
|
10
|
-
|
|
14
|
+
const user = process.env.USER ?? process.env.LOGNAME;
|
|
15
|
+
return (
|
|
16
|
+
[process.env.TEST_USER_HOME, user && `/Users/${user}`, user && `/home/${user}`, process.env.USERPROFILE].find(
|
|
17
|
+
(p) => p && existsSync(p),
|
|
18
|
+
) ?? userInfo().homedir
|
|
19
|
+
);
|
|
11
20
|
}
|
|
12
21
|
|
|
13
22
|
/** Expands a leading `~` or `~/` in a path using {@link userHome}. */
|
|
@@ -13,8 +13,8 @@ import { mcpRequest, testProgram } from "../fixtures.ts";
|
|
|
13
13
|
/** Tests that bootstrapAppConfig prefers host env over config file. */
|
|
14
14
|
test("bootstrapAppConfig prefers host env over config file", () => {
|
|
15
15
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-env-"));
|
|
16
|
-
const
|
|
17
|
-
process.env.
|
|
16
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
17
|
+
process.env.TEST_USER_HOME = dir;
|
|
18
18
|
process.env.FOO = "original";
|
|
19
19
|
try {
|
|
20
20
|
const p = testProgram({
|
|
@@ -36,8 +36,8 @@ test("bootstrapAppConfig prefers host env over config file", () => {
|
|
|
36
36
|
expect(process.env.FOO).toBe("original");
|
|
37
37
|
expect(process.env.BAR).toBe("bar");
|
|
38
38
|
} finally {
|
|
39
|
-
if (
|
|
40
|
-
else process.env.
|
|
39
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
40
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
41
41
|
delete process.env.FOO;
|
|
42
42
|
delete process.env.BAR;
|
|
43
43
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -106,7 +106,7 @@ test("MCP config file loads and exports vars for tool handlers", async () => {
|
|
|
106
106
|
],
|
|
107
107
|
{
|
|
108
108
|
script: "src/test/mcp-integration-fixture.ts",
|
|
109
|
-
env: {
|
|
109
|
+
env: { TEST_USER_HOME: dir, ARGS_TEST_SECRET: "present" },
|
|
110
110
|
},
|
|
111
111
|
);
|
|
112
112
|
const res = responses.get(15) as {
|
|
@@ -148,7 +148,7 @@ const program = {
|
|
|
148
148
|
await new Cli(program).run(process.argv.slice(2));
|
|
149
149
|
`,
|
|
150
150
|
);
|
|
151
|
-
const env = { ...process.env,
|
|
151
|
+
const env = { ...process.env, TEST_USER_HOME: dir } as Record<string, string | undefined>;
|
|
152
152
|
delete env.DOCS_SKIP_RUN_TOKEN;
|
|
153
153
|
try {
|
|
154
154
|
const proc = Bun.spawn(["bun", "run", mainPath, "docs", "cli"], {
|