argsbarg 6.2.2 → 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.
Files changed (52) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +2 -2
  3. package/docs/README.md +2 -2
  4. package/docs/ai-skills.md +7 -7
  5. package/docs/bundled-docs.md +2 -2
  6. package/docs/cli-program.md +4 -4
  7. package/docs/configure.md +36 -14
  8. package/docs/developing.md +7 -3
  9. package/docs/distribution-homebrew.md +2 -2
  10. package/docs/mcp.md +3 -3
  11. package/examples/full-example/.cursor/hooks/run-tests-on-stop.ts +56 -0
  12. package/examples/full-example/.cursor/hooks.json +12 -0
  13. package/examples/full-example/Formula/full-example.rb +1 -1
  14. package/examples/full-example/docs/cli-schema.json +6 -6
  15. package/examples/full-example/docs/cli.md +6 -6
  16. package/examples/full-example/docs/mcp.md +2 -2
  17. package/examples/full-example/docs/skill.md +1 -1
  18. package/examples/full-example/justfile +10 -14
  19. package/examples/full-example/scripts/formula-shared.ts +1 -1
  20. package/examples/full-example-json/.cursor/hooks/run-tests-on-stop.ts +56 -0
  21. package/examples/full-example-json/.cursor/hooks.json +12 -0
  22. package/examples/full-example-json/Formula/full-example-json.rb +1 -1
  23. package/examples/full-example-json/docs/cli-schema.json +27 -27
  24. package/examples/full-example-json/docs/cli.md +27 -27
  25. package/examples/full-example-json/docs/mcp.md +2 -2
  26. package/examples/full-example-json/docs/skill.md +1 -1
  27. package/examples/full-example-json/justfile +10 -14
  28. package/examples/full-example-json/scripts/formula-shared.ts +1 -1
  29. package/index.d.ts +25 -4
  30. package/package.json +1 -1
  31. package/src/builtins/builtins.test.ts +7 -7
  32. package/src/builtins/config.test.ts +16 -16
  33. package/src/builtins/configure-copy.ts +2 -2
  34. package/src/builtins/configure.ts +4 -4
  35. package/src/config/context.test.ts +12 -12
  36. package/src/config/file.test.ts +4 -4
  37. package/src/configure/artifacts/binary-placement.test.ts +4 -4
  38. package/src/configure/artifacts/mcp-codex.test.ts +5 -5
  39. package/src/configure/artifacts/mcp-openclaw.test.ts +5 -5
  40. package/src/configure/artifacts/mcp-opencode.test.ts +5 -5
  41. package/src/configure/artifacts/status.test.ts +5 -5
  42. package/src/configure/configure.test.ts +90 -17
  43. package/src/configure/index.ts +45 -13
  44. package/src/core/parse.test.ts +7 -7
  45. package/src/core/types.ts +21 -4
  46. package/src/docs/docs.test.ts +2 -2
  47. package/src/docs/mcp-guide.ts +2 -2
  48. package/src/index.ts +2 -0
  49. package/src/paths/host.test.ts +58 -0
  50. package/src/paths/host.ts +12 -3
  51. package/src/skill/generate.ts +1 -1
  52. package/src/test/integration/config.test.ts +6 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argsbarg",
3
- "version": "6.2.2",
3
+ "version": "6.3.1",
4
4
  "main": "./src/index.ts",
5
5
  "module": "./src/index.ts",
6
6
  "dependencies": {
@@ -9,7 +9,7 @@ import { cliBuiltinDocsGroup } from "../docs/builtin.ts";
9
9
  import { resolveCapabilities } from "../runtime/capabilities.ts";
10
10
  import { completionBashScript, completionFishScript, completionZshScript } from ".";
11
11
  import { cliBuiltinConfigureCommand, configureBuiltinOptions } from "./configure.ts";
12
- import { configureCommandDescription, configureSyncOptionDescription } from "./configure-copy.ts";
12
+ import { configureCommandDescription, configureRefreshOptionDescription } from "./configure-copy.ts";
13
13
  import { exportPresentationBuiltins } from "./export.ts";
14
14
  import { cliBuiltinMcpCommand } from "./mcp.ts";
15
15
  import { cliParseRoot, cliPresentationRoot } from "./presentation.ts";
@@ -43,7 +43,7 @@ describe("builtins help copy", () => {
43
43
  expect(configure.description).toContain("MCP config");
44
44
  expect(configure.notes).toContain("brew upgrade");
45
45
  const names = configureBuiltinOptions(fixture).map((o) => o.name);
46
- expect(names).toContain("sync");
46
+ expect(names).toContain("refresh");
47
47
  expect(names).toContain("remove-all");
48
48
  expect(names).toContain("status");
49
49
  const yesOpt = configureBuiltinOptions(fixture).find((o) => o.name === "yes");
@@ -54,7 +54,7 @@ describe("builtins help copy", () => {
54
54
  const caps = resolveCapabilities(noMcp);
55
55
  expect(configureCommandDescription(noMcp, caps)).toBe("Set up agent skills for this app (binary via Homebrew).");
56
56
  expect(configureCommandDescription(noMcp, caps)).not.toContain("MCP");
57
- expect(configureSyncOptionDescription(noMcp, caps)).not.toContain("MCP");
57
+ expect(configureRefreshOptionDescription(noMcp, caps)).not.toContain("MCP");
58
58
  const configure = cliBuiltinConfigureCommand(noMcp);
59
59
  expect(configure.description).not.toContain("MCP");
60
60
  });
@@ -63,7 +63,7 @@ describe("builtins help copy", () => {
63
63
  test("configure notes mention brew upgrade and interactive configure", () => {
64
64
  const configure = cliBuiltinConfigureCommand(fixture);
65
65
  expect(configure.notes).toContain("brew upgrade");
66
- expect(configure.notes).toContain("configure --sync --yes");
66
+ expect(configure.notes).toContain("configure --refresh --yes");
67
67
  expect(configure.notes).toContain(`${fixture.key} configure`);
68
68
 
69
69
  const withConfig: CliProgram = {
@@ -77,11 +77,11 @@ describe("builtins help copy", () => {
77
77
 
78
78
  test("configure -y parses as --yes", () => {
79
79
  const root = cliParseRoot(fixture);
80
- const pr = postParseValidate(root, parse(root, ["configure", "-y", "--sync"]));
80
+ const pr = postParseValidate(root, parse(root, ["configure", "-y", "--refresh"]));
81
81
  expect(pr.kind).toBe(ParseKind.Ok);
82
82
  if (pr.kind === ParseKind.Ok) {
83
83
  expect(pr.opts.yes).toBe("1");
84
- expect(pr.opts.sync).toBe("1");
84
+ expect(pr.opts.refresh).toBe("1");
85
85
  }
86
86
  });
87
87
 
@@ -134,7 +134,7 @@ describe("completion emitters", () => {
134
134
  const schema = cliPresentationRoot(fixture);
135
135
  const bash = completionBashScript(schema);
136
136
  expect(bash).toContain("hello");
137
- expect(bash).toContain("--sync");
137
+ expect(bash).toContain("--refresh");
138
138
  });
139
139
 
140
140
  test("zsh script registers compdef", () => {
@@ -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 prevHome = process.env.HOME;
34
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
47
- else process.env.HOME = prevHome;
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 prevHome = process.env.HOME;
57
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
70
- else process.env.HOME = prevHome;
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 prevHome = process.env.HOME;
80
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
92
- else process.env.HOME = prevHome;
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 prevHome = process.env.HOME;
101
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
118
- else process.env.HOME = prevHome;
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 });
@@ -37,7 +37,7 @@ export function configureCommandDescription(program: CliProgram, caps: CliCapabi
37
37
  return `Set up ${prose(program, caps)} for this app (binary via Homebrew).`;
38
38
  }
39
39
 
40
- export function configureSyncOptionDescription(program: CliProgram, caps: CliCapabilities): string {
40
+ export function configureRefreshOptionDescription(program: CliProgram, caps: CliCapabilities): string {
41
41
  return `Refresh installed ${short(program, caps)}. Used by Homebrew post_install.`;
42
42
  }
43
43
 
@@ -54,7 +54,7 @@ export function configureCommandNotes(program: CliProgram, _caps: CliCapabilitie
54
54
  "Set up agent artifacts after the binary is installed via Homebrew (see README for tap install).",
55
55
  "",
56
56
  "Homebrew post_install runs:",
57
- ` ${app} configure --sync --yes`,
57
+ ` ${app} configure --refresh --yes`,
58
58
  "",
59
59
  "Interactive setup (per target):",
60
60
  ` ${app} configure`,
@@ -12,7 +12,7 @@ import { configureConfigSubcommands } from "./config.ts";
12
12
  import {
13
13
  configureCommandDescription,
14
14
  configureCommandNotes,
15
- configureSyncOptionDescription,
15
+ configureRefreshOptionDescription,
16
16
  } from "./configure-copy.ts";
17
17
 
18
18
  /** Hidden fallback leaf for bare `configure` (interactive / flag modes). */
@@ -23,8 +23,8 @@ export function configureBuiltinOptions(root: CliProgram): CliOption[] {
23
23
  const caps = resolveCapabilities(root);
24
24
  const opts: CliOption[] = [
25
25
  {
26
- name: "sync",
27
- description: configureSyncOptionDescription(root, caps),
26
+ name: "refresh",
27
+ description: configureRefreshOptionDescription(root, caps),
28
28
  kind: CliOptionKind.Presence,
29
29
  },
30
30
  {
@@ -50,7 +50,7 @@ export function configureBuiltinOptions(root: CliProgram): CliOption[] {
50
50
  },
51
51
  {
52
52
  name: "yes",
53
- description: "Skip confirmation (required for --sync, --remove-all, --remove-config).",
53
+ description: "Skip confirmation (required for --refresh, --remove-all, --remove-config).",
54
54
  kind: CliOptionKind.Presence,
55
55
  shortName: "y",
56
56
  },
@@ -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 prevHome = process.env.HOME;
33
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
52
- else process.env.HOME = prevHome;
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 prevHome = process.env.HOME;
86
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
99
- else process.env.HOME = prevHome;
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 prevHome = process.env.HOME;
109
- process.env.HOME = dir;
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 (prevHome === undefined) delete process.env.HOME;
123
- else process.env.HOME = prevHome;
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
  });
@@ -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 prevHome = process.env.HOME;
41
- process.env.HOME = home;
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 (prevHome === undefined) delete process.env.HOME;
46
- else process.env.HOME = prevHome;
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 prevHome = process.env.HOME;
88
- process.env.HOME = home;
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 (prevHome === undefined) delete process.env.HOME;
94
- else process.env.HOME = prevHome;
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 prevHome: string | undefined;
12
+ let prevTestHome: string | undefined;
13
13
 
14
14
  beforeEach(() => {
15
15
  home = mkdtempSync(join(tmpdir(), "argsbarg-codex-"));
16
- prevHome = process.env.HOME;
17
- process.env.HOME = home;
16
+ prevTestHome = process.env.TEST_USER_HOME;
17
+ process.env.TEST_USER_HOME = home;
18
18
  });
19
19
 
20
20
  afterEach(() => {
21
- if (prevHome === undefined) delete process.env.HOME;
22
- else process.env.HOME = prevHome;
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 prevHome: string | undefined;
12
+ let prevTestHome: string | undefined;
13
13
 
14
14
  beforeEach(() => {
15
15
  home = mkdtempSync(join(tmpdir(), "argsbarg-openclaw-"));
16
- prevHome = process.env.HOME;
17
- process.env.HOME = home;
16
+ prevTestHome = process.env.TEST_USER_HOME;
17
+ process.env.TEST_USER_HOME = home;
18
18
  });
19
19
 
20
20
  afterEach(() => {
21
- if (prevHome === undefined) delete process.env.HOME;
22
- else process.env.HOME = prevHome;
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 prevHome: string | undefined;
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
- prevHome = process.env.HOME;
34
+ prevTestHome = process.env.TEST_USER_HOME;
35
35
  prevXdg = process.env.XDG_CONFIG_HOME;
36
- process.env.HOME = home;
36
+ process.env.TEST_USER_HOME = home;
37
37
  delete process.env.XDG_CONFIG_HOME;
38
38
  });
39
39
 
40
40
  afterEach(() => {
41
- if (prevHome === undefined) delete process.env.HOME;
42
- else process.env.HOME = prevHome;
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 prevHome: string | undefined;
15
+ let prevTestHome: string | undefined;
16
16
 
17
17
  beforeEach(() => {
18
18
  home = mkdtempSync(join(tmpdir(), "argsbarg-status-"));
19
- prevHome = process.env.HOME;
20
- process.env.HOME = home;
19
+ prevTestHome = process.env.TEST_USER_HOME;
20
+ process.env.TEST_USER_HOME = home;
21
21
  });
22
22
 
23
23
  afterEach(() => {
24
- if (prevHome === undefined) delete process.env.HOME;
25
- else process.env.HOME = prevHome;
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 prevHome: string | undefined;
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
- prevHome = process.env.HOME;
41
+ prevTestHome = process.env.TEST_USER_HOME;
42
42
  prevXdg = process.env.XDG_CONFIG_HOME;
43
- process.env.HOME = home;
43
+ process.env.TEST_USER_HOME = home;
44
44
  delete process.env.XDG_CONFIG_HOME;
45
45
  });
46
46
 
47
47
  afterEach(() => {
48
- if (prevHome === undefined) delete process.env.HOME;
49
- else process.env.HOME = prevHome;
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 });
@@ -55,12 +55,12 @@ afterEach(() => {
55
55
  /** Tests for configure opts. */
56
56
  describe("configure opts", () => {
57
57
  test("validate rejects multiple modes", () => {
58
- const opts = parseConfigureOpts({ sync: "1", status: "1" });
58
+ const opts = parseConfigureOpts({ refresh: "1", status: "1" });
59
59
  expect(validateConfigureOpts(opts)).toContain("only one");
60
60
  });
61
61
 
62
- test("sync requires --yes", () => {
63
- const opts = parseConfigureOpts({ sync: "1" });
62
+ test("refresh requires --yes", () => {
63
+ const opts = parseConfigureOpts({ refresh: "1" });
64
64
  expect(validateConfigureOpts(opts)).toContain("--yes");
65
65
  });
66
66
 
@@ -92,12 +92,12 @@ describe("configure mutation summary", () => {
92
92
  expect(msg).toBe("Installed 1 artifact.");
93
93
  });
94
94
 
95
- test("sync mode uses synced verb and artifact count", () => {
95
+ test("refresh mode uses refreshed verb and artifact count", () => {
96
96
  const msg = formatConfigureMutationSummary(
97
97
  { paths: ["a", "b", "c"], installed: 3, removed: 0, configured: 0 },
98
- { sync: true },
98
+ { refresh: true },
99
99
  );
100
- expect(msg).toBe("Synced 3 artifacts.");
100
+ expect(msg).toBe("Refreshed 3 artifacts.");
101
101
  });
102
102
 
103
103
  test("remove-all uses removed verb", () => {
@@ -147,8 +147,8 @@ describe("detect installed", () => {
147
147
  });
148
148
  });
149
149
 
150
- /** Tests for sync plan. */
151
- describe("sync plan", () => {
150
+ /** Tests for refresh plan. */
151
+ describe("refresh plan", () => {
152
152
  test("buildUpdatePlan greenfield includes skill when enabled", () => {
153
153
  const paths = resolveInstallPaths(fixture);
154
154
  const plan = buildUpdatePlan(fixture, paths, parseInstallOpts({ reinstall: "1", yes: "1" }));
@@ -229,19 +229,92 @@ describe("app config wizard", () => {
229
229
  });
230
230
  });
231
231
 
232
- describe("configure --sync bootstrap", () => {
232
+ describe("configure --refresh bootstrap", () => {
233
233
  test("creates config.json for apps without appConfig", async () => {
234
234
  const program: CliProgram = {
235
- key: "syncboot",
235
+ key: "refreshboot",
236
236
  version: "0.0.0",
237
- description: "Sync bootstrap test.",
237
+ description: "Refresh bootstrap test.",
238
238
  skill: { enabled: true },
239
239
  handler: () => {},
240
240
  configure: { enabled: true },
241
241
  };
242
242
  expect(appConfigFileExists(program)).toBe(false);
243
- const result = await new Cli(program).invoke(["configure", "--sync", "--yes"]);
243
+ const result = await new Cli(program).invoke(["configure", "--refresh", "--yes"]);
244
244
  expect(result.exitCode).toBe(0);
245
245
  expect(appConfigFileExists(program)).toBe(true);
246
246
  });
247
247
  });
248
+
249
+ describe("configure lifecycle hooks", () => {
250
+ test("afterRefresh runs after --refresh plan", async () => {
251
+ const calls: string[] = [];
252
+ const program: CliProgram = {
253
+ ...fixture,
254
+ key: "hookrefresh",
255
+ configure: {
256
+ afterRefresh: async (ctx) => {
257
+ calls.push(`after:${ctx.paths.mcpName}:dry=${ctx.dry}`);
258
+ },
259
+ },
260
+ };
261
+ const result = await new Cli(program).invoke(["configure", "--refresh", "--yes"]);
262
+ expect(result.exitCode).toBe(0);
263
+ expect(calls).toEqual([`after:hookrefresh:dry=false`]);
264
+ });
265
+
266
+ test("beforeRemoveAll runs before --remove-all plan", async () => {
267
+ const paths = resolveInstallPaths({ ...fixture, key: "hookremove" });
268
+ mkdirSync(paths.agentsSkillDir, { recursive: true });
269
+ writeFileSync(join(paths.agentsSkillDir, "SKILL.md"), "# test\n", "utf8");
270
+
271
+ const calls: string[] = [];
272
+ const program: CliProgram = {
273
+ ...fixture,
274
+ key: "hookremove",
275
+ configure: {
276
+ beforeRemoveAll: async (ctx) => {
277
+ calls.push(`before:${ctx.paths.skillDirName}`);
278
+ expect(existsSync(ctx.paths.agentsSkillDir)).toBe(true);
279
+ },
280
+ },
281
+ };
282
+ const result = await new Cli(program).invoke(["configure", "--remove-all", "--yes"]);
283
+ expect(result.exitCode).toBe(0);
284
+ expect(calls).toEqual(["before:hookremove"]);
285
+ expect(existsSync(paths.agentsSkillDir)).toBe(false);
286
+ });
287
+
288
+ test("hooks receive dry from --dry", async () => {
289
+ const calls: string[] = [];
290
+ const program: CliProgram = {
291
+ ...fixture,
292
+ key: "hookdry",
293
+ configure: {
294
+ afterRefresh: (ctx) => {
295
+ calls.push(`dry=${ctx.dry}`);
296
+ },
297
+ },
298
+ };
299
+ const result = await new Cli(program).invoke(["configure", "--refresh", "--yes", "--dry"]);
300
+ expect(result.exitCode).toBe(0);
301
+ expect(calls).toEqual(["dry=true"]);
302
+ });
303
+
304
+ test("beforeRemoveAll is not called for --remove-config", async () => {
305
+ let called = false;
306
+ const program: CliProgram = {
307
+ ...fixture,
308
+ key: "hookcfg",
309
+ appConfig: { entries: { token: { description: "Token." } } },
310
+ configure: {
311
+ beforeRemoveAll: () => {
312
+ called = true;
313
+ },
314
+ },
315
+ };
316
+ const result = await new Cli(program).invoke(["configure", "--remove-config", "--yes"]);
317
+ expect(result.exitCode).toBe(0);
318
+ expect(called).toBe(false);
319
+ });
320
+ });