argsbarg 6.3.0 → 6.3.1
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 +11 -3
- package/docs/configure.md +1 -1
- package/examples/full-example/justfile +1 -5
- package/examples/full-example-json/justfile +1 -5
- 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,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [6.3.1] - 2026-08-07
|
|
11
|
+
|
|
12
|
+
|
|
10
13
|
## [6.3.0] - 2026-08-07
|
|
11
14
|
|
|
12
15
|
### Added
|
|
@@ -14,10 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
17
|
- **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
18
|
- **`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
19
|
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`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.
|
|
23
|
+
|
|
17
24
|
### Changed
|
|
18
25
|
|
|
19
|
-
- **Breaking: `configure --sync` → `configure --refresh`** — renames the non-interactive agent-artifact refresh flag (skills, MCP, config bootstrap).
|
|
20
|
-
- **Copy-template justfiles** — `install-local` uses `brew install --force`; uninstall/untap recipes prefix `NONINTERACTIVE=1` so agents and CI skip Homebrew confirm prompts (including post-uninstall untap). `install-production` unchanged.
|
|
26
|
+
- **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.
|
|
27
|
+
- **Copy-template justfiles** — `install-local` uses `brew reinstall || brew install --force`; uninstall/untap recipes prefix `NONINTERACTIVE=1` so agents and CI skip Homebrew confirm prompts (including post-uninstall untap). `install-production` unchanged.
|
|
21
28
|
|
|
22
29
|
## [6.2.2] - 2026-08-07
|
|
23
30
|
|
|
@@ -915,7 +922,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
915
922
|
- 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
923
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
917
924
|
|
|
918
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v6.3.
|
|
925
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v6.3.1...HEAD
|
|
926
|
+
[6.3.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.3.1
|
|
919
927
|
[6.3.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.3.0
|
|
920
928
|
[6.2.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.2.2
|
|
921
929
|
[6.2.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v6.2.1
|
package/docs/configure.md
CHANGED
|
@@ -35,7 +35,7 @@ just build
|
|
|
35
35
|
just install-local # same formula as production; temporary file:// URL during brew install (`just install` is an alias)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Dev flow matches release: formula `install` copies the binary and generates completions; `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`).
|
|
38
|
+
Dev flow matches release: formula `install` copies the binary and generates completions; `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
|
|
|
@@ -66,17 +66,13 @@ 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
70
|
install-local: build
|
|
75
71
|
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
76
72
|
mkdir -p {{tap_parent}}
|
|
77
73
|
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
78
74
|
bun scripts/dev-formula.ts install
|
|
79
|
-
brew install --force --formula {{tap}}/{{cli_key}}
|
|
75
|
+
NONINTERACTIVE=1 brew reinstall --formula {{tap}}/{{cli_key}} || NONINTERACTIVE=1 brew install --force --formula {{tap}}/{{cli_key}}
|
|
80
76
|
bun scripts/dev-formula.ts reset
|
|
81
77
|
@echo ""
|
|
82
78
|
@echo "Next: {{cli_key}} configure"
|
|
@@ -76,17 +76,13 @@ 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
80
|
install-local: build
|
|
85
81
|
@NONINTERACTIVE=1 brew untap {{tap}} 2>/dev/null || true
|
|
86
82
|
mkdir -p {{tap_parent}}
|
|
87
83
|
ln -sfn '{{justfile_directory()}}' {{tap_path}}
|
|
88
84
|
bun scripts/dev-formula.ts install
|
|
89
|
-
brew install --force --formula {{tap}}/{{cli_key}}
|
|
85
|
+
NONINTERACTIVE=1 brew reinstall --formula {{tap}}/{{cli_key}} || NONINTERACTIVE=1 brew install --force --formula {{tap}}/{{cli_key}}
|
|
90
86
|
bun scripts/dev-formula.ts reset
|
|
91
87
|
@echo ""
|
|
92
88
|
@echo "Next: {{cli_key}} configure"
|
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"], {
|