agent-shim 7.10.2 → 7.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,47 +82,47 @@ The full mechanics, including the credential block, the merge algorithm, conditi
82
82
  ```
83
83
  agent-shim <noun> <verb> [name] [options] # nouns: identity, profile, pool, provider, rule
84
84
 
85
- agent-shim identity add <name>
85
+ agent-shim identity add <name> [--json]
86
86
  agent-shim identity list [--json]
87
87
  agent-shim identity show <name> [--json]
88
- agent-shim identity set <name> [--default-profile <profile> | --no-default-profile] [--[no-]allow-ambient-credential]
89
- agent-shim identity set <name> [--credential <source>]... [--credential-target <bearer|apiKey|oauthToken>] [--no-credential]
90
- agent-shim identity remove <name> [--yes]
91
- agent-shim identity use <name>
88
+ agent-shim identity set <name> [--default-profile <profile> | --no-default-profile] [--[no-]allow-ambient-credential] [--json]
89
+ agent-shim identity set <name> [--credential <source>]... [--credential-target <bearer|apiKey|oauthToken>] [--no-credential] [--json]
90
+ agent-shim identity remove <name> [--yes] [--json]
91
+ agent-shim identity use <name> [--json]
92
92
  agent-shim @<name> # shorthand for `identity use <name>`
93
93
  agent-shim identity resolve-conflicts <name> # interactively resolve a retained superseded farm's conflicts
94
94
 
95
- agent-shim profile add [name] [--extends <profile>]... [--description <text>] # interactive with no options on a terminal
96
- agent-shim profile set <name> [--category <category>=<bool>]... [--entry <category>/<path>=<bool>]...
97
- agent-shim profile set <name> [--extends <profile>]... [--no-extends] [--description <text> | --no-description]
98
- agent-shim profile set <name> [--[no-]launch-skip-permissions] [--[no-]launch-remote-control] [--[no-]launch-headroom] [--[no-]launch-track-usage]
99
- agent-shim profile set <name> [--launch-provider <provider> | --no-launch-provider]
95
+ agent-shim profile add [name] [--extends <profile>]... [--description <text>] # interactive with no options on a terminal [--json]
96
+ agent-shim profile set <name> [--category <category>=<bool>]... [--entry <category>/<path>=<bool>]... [--json]
97
+ agent-shim profile set <name> [--extends <profile>]... [--no-extends] [--description <text> | --no-description] [--json]
98
+ agent-shim profile set <name> [--[no-]launch-skip-permissions] [--[no-]launch-remote-control] [--[no-]launch-headroom] [--[no-]launch-track-usage] [--json]
99
+ agent-shim profile set <name> [--launch-provider <provider> | --no-launch-provider] [--json]
100
100
  agent-shim profile list [--json]
101
101
  agent-shim profile show <name> [--json]
102
- agent-shim profile remove <name> [--yes]
103
- agent-shim profile use <name> # the global default configuration profile
102
+ agent-shim profile remove <name> [--yes] [--json]
103
+ agent-shim profile use <name> # the global default configuration profile [--json]
104
104
 
105
- agent-shim pool add <name> --identity <identity>...
106
- agent-shim pool set <name> --identity <identity>...
105
+ agent-shim pool add <name> --identity <identity>... [--json]
106
+ agent-shim pool set <name> --identity <identity>... [--json]
107
107
  agent-shim pool list [--json]
108
108
  agent-shim pool show <name> [--json]
109
- agent-shim pool remove <name> [--yes]
110
- agent-shim pool use <name>
109
+ agent-shim pool remove <name> [--yes] [--json]
110
+ agent-shim pool use <name> [--json]
111
111
  agent-shim pool pick <name> [--json]
112
112
 
113
- agent-shim provider add <name> --display-name <name> (--base-url <url> | --kind codex) (--credential <source>)... [--credential-target <bearer|apiKey>] [--env KEY=VALUE]...
113
+ agent-shim provider add <name> --display-name <name> (--base-url <url> | --kind codex) (--credential <source>)... [--credential-target <bearer|apiKey>] [--env KEY=VALUE]... [--json]
114
114
  [--codex-default-model <model>] [--codex-model <tier=model>]... [--codex-effort <none|low|medium|high>]
115
- agent-shim provider set <name> [--display-name <name>] ([--base-url <url>] | --kind codex) [--credential <source>]... [--credential-target <bearer|apiKey>] [--env KEY=VALUE]... [--unset-env KEY]...
115
+ agent-shim provider set <name> [--display-name <name>] ([--base-url <url>] | --kind codex) [--credential <source>]... [--credential-target <bearer|apiKey>] [--env KEY=VALUE]... [--unset-env KEY]... [--json]
116
116
  [--codex-default-model <model>] [--codex-model <tier=model>]... [--codex-effort <none|low|medium|high>]
117
117
  agent-shim provider list [--json]
118
118
  agent-shim provider show <name> [--json]
119
- agent-shim provider remove <name> [--yes]
119
+ agent-shim provider remove <name> [--yes] [--json]
120
120
 
121
- agent-shim rule add <path> [--config-profile <profile>] [--identity <identity>]
122
- agent-shim rule set <path> [--config-profile <profile> | --no-config-profile] [--identity <identity> | --no-identity]
121
+ agent-shim rule add <path> [--config-profile <profile>] [--identity <identity>] [--json]
122
+ agent-shim rule set <path> [--config-profile <profile> | --no-config-profile] [--identity <identity> | --no-identity] [--json]
123
123
  agent-shim rule list [--json]
124
124
  agent-shim rule show <path> [--json]
125
- agent-shim rule remove <path> [--yes]
125
+ agent-shim rule remove <path> [--yes] [--json]
126
126
 
127
127
  agent-shim configure [path] [--identity <identity>]
128
128
  agent-shim check [path] [--identity <identity>] [--json] [--strict]
@@ -144,7 +144,7 @@ agent-shim run [@<identity>] [launch flags] [claude arguments]
144
144
  claude @<identity> ... # the same, once `agent-shim shim enable` has run
145
145
  ```
146
146
 
147
- Every `list`, `show`, `check`, `doctor` and `headroom status` prints text by default and JSON with `--json`. Prompts appear only when standard input is a terminal; without one, a command that needs input fails with the option that supplies it (a `remove` needs `--yes`). Failures print as `agent-shim: <message>` and exit 1, usage errors exit 2, a selected provider or identity whose credential block yields no token exits 64, and `AGENT_SHIM_DEBUG=1` adds stack traces to unexpected errors. `agent-shim completion <bash|zsh|fish>` prints a shell completion script.
147
+ Every `list`, `show`, `check`, `doctor` and `headroom status` prints text by default and JSON with `--json`, and so do the mutating commands (`add`, `set`, `remove` and `use` on identities, profiles, providers, pools and rules), which print one object naming the `action`, the `kind`, the `name` and, for a creation or update, the stored `value`. Prompts appear only when standard input is a terminal; without one, a command that needs input fails with the option that supplies it (a `remove` needs `--yes`). Failures print as `agent-shim: <message>` and exit 1, usage errors exit 2, a selected provider or identity whose credential block yields no token exits 64, and `AGENT_SHIM_DEBUG=1` adds stack traces to unexpected errors. `agent-shim completion <bash|zsh|fish>` prints a shell completion script.
148
148
 
149
149
  `agent-shim check [path]` resolves the full cascade for a directory without touching the farm, and is the primary way to answer "why is X shared or hidden here". `agent-shim doctor` audits the whole `~/.agent-shim` config graph at once: every identity, profile, `extends` chain, provider (naming the exact replacement for a file still in the format before the credential block) and directory rule, plus whether the right `agent-shim` is the one actually on `PATH`.
150
150
 
package/dist/cli.cjs CHANGED
@@ -54858,6 +54858,13 @@ function date4(params) {
54858
54858
  function printJson(value) {
54859
54859
  console.log(JSON.stringify(value, null, 2));
54860
54860
  }
54861
+ function reportMutation(json2, report, printText) {
54862
+ if (json2 === true) {
54863
+ printJson(report);
54864
+ } else {
54865
+ printText();
54866
+ }
54867
+ }
54861
54868
  function withExamples(command, examples) {
54862
54869
  return command.addHelpText("after", `
54863
54870
  Examples:
@@ -60584,30 +60591,36 @@ function registerProfileCommand(program2, deps) {
60584
60591
  withExamples(
60585
60592
  profile.command("add [name]").description(
60586
60593
  "Create a new configuration profile. Fails if one with this name already exists. With no options on a terminal, walks through choosing its categories."
60587
- ).option("--extends <profile>", "A profile this one extends (repeatable, in order).", collectRepeated).option("--description <text>", "A free-text description stored in the profile.").action(async (name, options) => {
60594
+ ).option("--extends <profile>", "A profile this one extends (repeatable, in order).", collectRepeated).option("--description <text>", "A free-text description stored in the profile.").option("--json", "Print the result as JSON.").action(async (name, allOptions) => {
60595
+ const { json: json2, ...options } = allOptions;
60588
60596
  const hasOptions = options.extends !== void 0 || options.description !== void 0;
60589
60597
  if (!hasOptions && deps.isInteractive()) {
60590
60598
  const result = await runProfileWizard(deps.prompts, name === void 0 ? { paths } : { paths, createName: name });
60591
60599
  if (result === void 0) {
60592
60600
  throw new PromptCancelledError();
60593
60601
  }
60594
- console.log(`Created configuration profile "${result.name}".`);
60602
+ reportMutation(json2, { action: "created", kind: "profile", name: result.name, value: readProfile(paths, result.name) }, () => {
60603
+ console.log(`Created configuration profile "${result.name}".`);
60604
+ });
60595
60605
  return;
60596
60606
  }
60597
60607
  if (name === void 0) {
60598
60608
  throw new UsageError("missing required argument 'name' (standard input is not a terminal, so there is nothing to prompt on).");
60599
60609
  }
60600
- createProfile(paths, name, options.extends, options.description);
60601
- console.log(`Created configuration profile "${name}".`);
60610
+ const created = createProfile(paths, name, options.extends, options.description);
60611
+ reportMutation(json2, { action: "created", kind: "profile", name, value: created }, () => {
60612
+ console.log(`Created configuration profile "${name}".`);
60613
+ });
60602
60614
  }),
60603
60615
  ["agent-shim profile add client-acme", "agent-shim profile add client-acme --extends work-default --extends strict"]
60604
60616
  );
60605
60617
  withExamples(
60606
60618
  profile.command("set <name>").description(
60607
60619
  "Update a configuration profile's categories, entries, extends list, description or launch settings. With no options on a terminal, walks through its categories."
60608
- ).option("--category <category=bool>", "Share (true) or hide (false) a whole category (repeatable).", collectBoolPair).option("--entry <path=bool>", "Share (true) or hide (false) one <category>/<path> entry (repeatable).", collectBoolPair).option("--extends <profile>", "Replace the extends list with these profiles (repeatable, in order).", collectRepeated).option("--no-extends", "Clear the extends list.").option("--description <text>", "Replace the description.").option("--no-description", "Clear the description.").option("--launch-skip-permissions", "Launches under this profile skip permission prompts.").option("--no-launch-skip-permissions", "Launches under this profile keep permission prompts.").option("--launch-remote-control", "Launches under this profile enable Remote Control.").option("--no-launch-remote-control", "Launches under this profile leave Remote Control off.").option("--launch-headroom", "Launches under this profile route through headroom.").option("--no-launch-headroom", "Launches under this profile route direct.").option("--launch-track-usage", "Launches under this profile route through the front door so their requests and quota are recorded, with no headroom needed.").option("--no-launch-track-usage", "Launches under this profile are not recorded unless a provider or headroom routes them anyway.").option("--launch-provider <provider>", "Launches under this profile route through this provider.").option("--no-launch-provider", "Clear this profile's provider selection.").action(async (name, options) => {
60620
+ ).option("--category <category=bool>", "Share (true) or hide (false) a whole category (repeatable).", collectBoolPair).option("--entry <path=bool>", "Share (true) or hide (false) one <category>/<path> entry (repeatable).", collectBoolPair).option("--extends <profile>", "Replace the extends list with these profiles (repeatable, in order).", collectRepeated).option("--no-extends", "Clear the extends list.").option("--description <text>", "Replace the description.").option("--no-description", "Clear the description.").option("--launch-skip-permissions", "Launches under this profile skip permission prompts.").option("--no-launch-skip-permissions", "Launches under this profile keep permission prompts.").option("--launch-remote-control", "Launches under this profile enable Remote Control.").option("--no-launch-remote-control", "Launches under this profile leave Remote Control off.").option("--launch-headroom", "Launches under this profile route through headroom.").option("--no-launch-headroom", "Launches under this profile route direct.").option("--launch-track-usage", "Launches under this profile route through the front door so their requests and quota are recorded, with no headroom needed.").option("--no-launch-track-usage", "Launches under this profile are not recorded unless a provider or headroom routes them anyway.").option("--launch-provider <provider>", "Launches under this profile route through this provider.").option("--no-launch-provider", "Clear this profile's provider selection.").option("--json", "Print the result as JSON.").action(async (name, allOptions) => {
60621
+ const { json: json2, ...options } = allOptions;
60609
60622
  requireProfileExists(paths, name);
60610
- if (Object.values(options).every((value) => value === void 0)) {
60623
+ if (Object.entries(allOptions).every(([key, value]) => key === "json" || value === void 0)) {
60611
60624
  if (!deps.isInteractive()) {
60612
60625
  throw new UsageError(
60613
60626
  "Nothing to change: pass --category, --entry, --extends, --description or a --launch-* option (standard input is not a terminal, so there is nothing to prompt on)."
@@ -60619,7 +60632,9 @@ function registerProfileCommand(program2, deps) {
60619
60632
  } else {
60620
60633
  applyProfileSet(paths, name, options);
60621
60634
  }
60622
- console.log(`Updated configuration profile "${name}".`);
60635
+ reportMutation(json2, { action: "updated", kind: "profile", name, value: readProfile(paths, name) }, () => {
60636
+ console.log(`Updated configuration profile "${name}".`);
60637
+ });
60623
60638
  }),
60624
60639
  [
60625
60640
  "agent-shim profile set client-acme --category history=false --category knowledge=true",
@@ -60675,19 +60690,23 @@ function registerProfileCommand(program2, deps) {
60675
60690
  ["agent-shim profile show client-acme", "agent-shim profile show client-acme --json"]
60676
60691
  );
60677
60692
  withExamples(
60678
- profile.command("remove <name>").description("Delete a configuration profile's file. References to it by name are left for `agent-shim doctor` to report.").option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").action(async (name, options) => {
60693
+ profile.command("remove <name>").description("Delete a configuration profile's file. References to it by name are left for `agent-shim doctor` to report.").option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").option("--json", "Print the result as JSON.").action(async (name, options) => {
60679
60694
  requireProfileExists(paths, name);
60680
60695
  await confirmRemoval(deps, options.yes, `configuration profile "${name}"`);
60681
60696
  removeProfile(paths, name);
60682
- console.log(`Removed configuration profile "${name}".`);
60697
+ reportMutation(options.json, { action: "removed", kind: "profile", name }, () => {
60698
+ console.log(`Removed configuration profile "${name}".`);
60699
+ });
60683
60700
  }),
60684
60701
  ["agent-shim profile remove client-acme --yes"]
60685
60702
  );
60686
60703
  withExamples(
60687
- profile.command("use <name>").description("Make a configuration profile the global default, used when no directory rule or identity selects one.").action(async (name) => {
60704
+ profile.command("use <name>").description("Make a configuration profile the global default, used when no directory rule or identity selects one.").option("--json", "Print the result as JSON.").action(async (name, options) => {
60688
60705
  await ensureProfileExists(deps, name);
60689
60706
  setGlobalDefaultProfile(paths, name);
60690
- console.log(`Global default configuration profile is now "${name}".`);
60707
+ reportMutation(options.json, { action: "selected", kind: "profile", name }, () => {
60708
+ console.log(`Global default configuration profile is now "${name}".`);
60709
+ });
60691
60710
  }),
60692
60711
  ["agent-shim profile use work-default"]
60693
60712
  );
@@ -60732,7 +60751,7 @@ async function runIdentityWizard(prompts, paths, name, options = {}) {
60732
60751
  prompts.outro(`Identity "${name}" is set up and active.`);
60733
60752
  return true;
60734
60753
  }
60735
- async function selectIdentity(deps, name) {
60754
+ async function selectIdentity(deps, name, json2) {
60736
60755
  if (poolNameOf(name) !== void 0 || identityExists(deps.paths, name)) {
60737
60756
  useIdentity(deps.paths, name);
60738
60757
  } else if (deps.isInteractive()) {
@@ -60742,7 +60761,9 @@ async function selectIdentity(deps, name) {
60742
60761
  } else {
60743
60762
  throw new IdentityNotFoundError(name);
60744
60763
  }
60745
- console.log(`Active identity is now "${name}".`);
60764
+ reportMutation(json2, { action: "selected", kind: "identity", name }, () => {
60765
+ console.log(`Active identity is now "${name}".`);
60766
+ });
60746
60767
  }
60747
60768
  async function tryRunAtIdentityShortcut(deps, argv) {
60748
60769
  if (argv.length !== 1) {
@@ -60784,9 +60805,11 @@ function registerIdentityCommand(program2, deps) {
60784
60805
  ["agent-shim identity add work", "agent-shim identity list"]
60785
60806
  );
60786
60807
  withExamples(
60787
- identity.command("add <name>").description("Create a new identity. Fails if one with this name already exists.").action((name) => {
60788
- addIdentity(paths, name);
60789
- console.log(`Created identity "${name}".`);
60808
+ identity.command("add <name>").description("Create a new identity. Fails if one with this name already exists.").option("--json", "Print the result as JSON.").action((name, options) => {
60809
+ const created = addIdentity(paths, name);
60810
+ reportMutation(options.json, { action: "created", kind: "identity", name, value: created }, () => {
60811
+ console.log(`Created identity "${name}".`);
60812
+ });
60790
60813
  }),
60791
60814
  ["agent-shim identity add work"]
60792
60815
  );
@@ -60833,7 +60856,7 @@ function registerIdentityCommand(program2, deps) {
60833
60856
  }),
60834
60857
  ["agent-shim identity show work", "agent-shim identity show work --json"]
60835
60858
  );
60836
- const identitySet = identity.command("set <name>").description("Update an identity's settings.").option("--default-profile <profile>", "Configuration profile this identity uses when nothing more specific selects one.").option("--no-default-profile", "Clear this identity's default configuration profile.").option("--allow-ambient-credential", "Allow this identity to launch even with an ambient credential env var set.").option("--no-allow-ambient-credential", "Disallow ambient credential env vars for this identity (the default).").option(
60859
+ const identitySet = identity.command("set <name>").description("Update an identity's settings.").option("--json", "Print the result as JSON.").option("--default-profile <profile>", "Configuration profile this identity uses when nothing more specific selects one.").option("--no-default-profile", "Clear this identity's default configuration profile.").option("--allow-ambient-credential", "Allow this identity to launch even with an ambient credential env var set.").option("--no-allow-ambient-credential", "Disallow ambient credential env vars for this identity (the default).").option(
60837
60860
  "--credential <source>",
60838
60861
  `Authenticate this identity from a token instead of its stored login, replacing any existing sources (repeatable, in the order tried): ${CREDENTIAL_SOURCE_SYNTAX}.`,
60839
60862
  collectCredentialSource
@@ -60845,8 +60868,9 @@ function registerIdentityCommand(program2, deps) {
60845
60868
  );
60846
60869
  addCredentialCacheOptions(identitySet);
60847
60870
  withExamples(
60848
- identitySet.action(async (name, options) => {
60849
- if (Object.values(options).every((value) => value === void 0)) {
60871
+ identitySet.action(async (name, allOptions) => {
60872
+ const { json: json2, ...options } = allOptions;
60873
+ if (Object.entries(allOptions).every(([key, value]) => key === "json" || value === void 0)) {
60850
60874
  throw new UsageError(
60851
60875
  "Nothing to change: pass --default-profile, --no-default-profile, --allow-ambient-credential, --no-allow-ambient-credential, --credential, --no-credential, --credential-target or a --credential-cache option."
60852
60876
  );
@@ -60854,25 +60878,24 @@ function registerIdentityCommand(program2, deps) {
60854
60878
  if (!identityExists(paths, name)) {
60855
60879
  throw new IdentityNotFoundError(name);
60856
60880
  }
60881
+ const messages = [];
60857
60882
  if (options.defaultProfile !== void 0) {
60858
60883
  const profileName = options.defaultProfile === false ? void 0 : options.defaultProfile;
60859
60884
  if (profileName !== void 0) {
60860
60885
  await ensureProfileExists(deps, profileName);
60861
60886
  }
60862
60887
  setDefaultConfigProfile(paths, name, profileName);
60863
- console.log(
60888
+ messages.push(
60864
60889
  profileName === void 0 ? `Identity "${name}" no longer has a default configuration profile.` : `Identity "${name}" now defaults to configuration profile "${profileName}".`
60865
60890
  );
60866
60891
  }
60867
60892
  if (options.allowAmbientCredential !== void 0) {
60868
60893
  setAllowAmbientCredential(paths, name, options.allowAmbientCredential);
60869
- console.log(
60870
- `Identity "${name}" ${options.allowAmbientCredential ? "now allows" : "no longer allows"} an ambient credential.`
60871
- );
60894
+ messages.push(`Identity "${name}" ${options.allowAmbientCredential ? "now allows" : "no longer allows"} an ambient credential.`);
60872
60895
  }
60873
60896
  if (options.credential === false) {
60874
60897
  setIdentityCredential(paths, name, false);
60875
- console.log(`Identity "${name}" no longer has a credential and uses its stored login.`);
60898
+ messages.push(`Identity "${name}" no longer has a credential and uses its stored login.`);
60876
60899
  } else if (options.credential !== void 0 || options.credentialTarget !== void 0 || cacheChange(options, void 0) !== void 0) {
60877
60900
  const cache = cacheChange(options, readIdentity(paths, name)?.credential?.cache);
60878
60901
  const updated = setIdentityCredential(paths, name, {
@@ -60881,9 +60904,14 @@ function registerIdentityCommand(program2, deps) {
60881
60904
  ...cache === void 0 ? {} : { cache }
60882
60905
  });
60883
60906
  if (updated.credential !== void 0) {
60884
- console.log(`Identity "${name}" now authenticates with credential ${describeCredential(updated.credential)}.`);
60907
+ messages.push(`Identity "${name}" now authenticates with credential ${describeCredential(updated.credential)}.`);
60885
60908
  }
60886
60909
  }
60910
+ reportMutation(json2, { action: "updated", kind: "identity", name, value: readIdentity(paths, name) }, () => {
60911
+ for (const message of messages) {
60912
+ console.log(message);
60913
+ }
60914
+ });
60887
60915
  }),
60888
60916
  [
60889
60917
  "agent-shim identity set work --default-profile client-acme",
@@ -60895,19 +60923,21 @@ function registerIdentityCommand(program2, deps) {
60895
60923
  withExamples(
60896
60924
  identity.command("remove <name>").description(
60897
60925
  "Delete an identity and its directory, including its credentials and anything it does not share with ~/.claude. Shared data in ~/.claude is kept."
60898
- ).option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").action(async (name, options) => {
60926
+ ).option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").option("--json", "Print the result as JSON.").action(async (name, options) => {
60899
60927
  if (!identityExists(paths, name)) {
60900
60928
  throw new IdentityNotFoundError(name);
60901
60929
  }
60902
60930
  await confirmRemoval(deps, options.yes, `identity "${name}" and its directory ${import_node_path20.default.join(paths.identitiesDir, name)}`);
60903
60931
  removeIdentity(paths, name);
60904
- console.log(`Removed identity "${name}".`);
60932
+ reportMutation(options.json, { action: "removed", kind: "identity", name }, () => {
60933
+ console.log(`Removed identity "${name}".`);
60934
+ });
60905
60935
  }),
60906
60936
  ["agent-shim identity remove old-client --yes"]
60907
60937
  );
60908
60938
  withExamples(
60909
- identity.command("use <name>").description("Select the active identity, used by every launch that names none. Offers to create it on a terminal if it does not exist.").action(async (name) => {
60910
- await selectIdentity(deps, name);
60939
+ identity.command("use <name>").description("Select the active identity, used by every launch that names none. Offers to create it on a terminal if it does not exist.").option("--json", "Print the result as JSON.").action(async (name, options) => {
60940
+ await selectIdentity(deps, name, options.json);
60911
60941
  }),
60912
60942
  ["agent-shim identity use work", "agent-shim @work"]
60913
60943
  );
@@ -60955,7 +60985,7 @@ function registerIdentityCommand(program2, deps) {
60955
60985
  // package.json
60956
60986
  var package_default = {
60957
60987
  name: "agent-shim",
60958
- version: "7.10.2",
60988
+ version: "7.12.0",
60959
60989
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
60960
60990
  license: "MIT",
60961
60991
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -64534,11 +64564,17 @@ ${request6.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
64534
64564
  const listener = await effects.listenLoopback(preferredPort, (socket) => {
64535
64565
  handleConnect(socket, { config: config2, effects, sessions, ledger });
64536
64566
  });
64567
+ const apiSession = sessions.find((session) => session.host === config2.routedHost);
64568
+ const transparent = config2.transparentPort === void 0 || apiSession === void 0 ? void 0 : await effects.listenLoopback(config2.transparentPort, (socket) => {
64569
+ apiSession.tlsAcceptor.accept(socket, config2.transparentCapability ?? "");
64570
+ });
64537
64571
  return {
64538
64572
  port: listener.port,
64573
+ ...transparent === void 0 ? {} : { transparentPort: transparent.port },
64539
64574
  close: async () => {
64540
64575
  ledger.close();
64541
64576
  await listener.close();
64577
+ await transparent?.close();
64542
64578
  for (const { httpSession, tlsAcceptor } of sessions) {
64543
64579
  tlsAcceptor.close();
64544
64580
  httpSession?.close();
@@ -66779,6 +66815,11 @@ function realFrontDoorSupervisorPorts(paths) {
66779
66815
  return { port: handle.port, close: handle.close };
66780
66816
  },
66781
66817
  startConnectListener: async (preferredPort) => {
66818
+ const transparentPort = process.env.AGENT_SHIM_TRANSPARENT_SURFACE === void 0 ? void 0 : Number(process.env.AGENT_SHIM_TRANSPARENT_SURFACE);
66819
+ const transparentCapability = transparentPort === void 0 || Number.isNaN(transparentPort) ? void 0 : (0, import_node_crypto13.randomUUID)();
66820
+ if (transparentCapability !== void 0) {
66821
+ writeFrontDoorSession(realFarmFs, paths.frontdoorSessionsDir, { pid: process.pid, startedAt: Date.now(), token: transparentCapability });
66822
+ }
66782
66823
  const leafFor = createLeafCache(loadCa(), () => /* @__PURE__ */ new Date());
66783
66824
  const capture = captureFromEnv(process.env, paths.logsDir);
66784
66825
  if (capture !== void 0) {
@@ -66811,6 +66852,7 @@ function realFrontDoorSupervisorPorts(paths) {
66811
66852
  leafFor,
66812
66853
  upstreamFor: (host) => ({ host, port: HTTPS_PORT, tls: true }),
66813
66854
  isLiveCapability: isLiveToken,
66855
+ ...transparentPort === void 0 || Number.isNaN(transparentPort) ? {} : { transparentPort, ...transparentCapability === void 0 ? {} : { transparentCapability } },
66814
66856
  limits: CONNECT_LIMITS,
66815
66857
  ...capture === void 0 ? {} : { capture }
66816
66858
  },
@@ -68586,20 +68628,24 @@ function registerPoolCommand(program2, deps) {
68586
68628
  ["agent-shim pool add subs --identity work --identity personal", "agent-shim pool pick subs"]
68587
68629
  );
68588
68630
  withExamples(
68589
- pool.command("add <name>").description("Define a pool. Fails if one with this name already exists.").option("--identity <name>", "A member identity (repeatable).", collectRepeated).action((name, options) => {
68631
+ pool.command("add <name>").description("Define a pool. Fails if one with this name already exists.").option("--identity <name>", "A member identity (repeatable).", collectRepeated).option("--json", "Print the result as JSON.").action((name, options) => {
68590
68632
  const members2 = membersOf(options);
68591
68633
  requireIdentities(paths, members2);
68592
- addPool(paths, name, members2);
68593
- console.log(`Created pool "${name}" with ${members2.join(", ")}.`);
68634
+ const created = addPool(paths, name, members2);
68635
+ reportMutation(options.json, { action: "created", kind: "pool", name, value: created }, () => {
68636
+ console.log(`Created pool "${name}" with ${members2.join(", ")}.`);
68637
+ });
68594
68638
  }),
68595
68639
  ["agent-shim pool add subs --identity work --identity personal"]
68596
68640
  );
68597
68641
  withExamples(
68598
- pool.command("set <name>").description("Replace a pool's members.").option("--identity <name>", "A member identity (repeatable); the full new list.", collectRepeated).action((name, options) => {
68642
+ pool.command("set <name>").description("Replace a pool's members.").option("--identity <name>", "A member identity (repeatable); the full new list.", collectRepeated).option("--json", "Print the result as JSON.").action((name, options) => {
68599
68643
  const members2 = membersOf(options);
68600
68644
  requireIdentities(paths, members2);
68601
- setPool(paths, name, members2);
68602
- console.log(`Pool "${name}" now has ${members2.join(", ")}.`);
68645
+ const updated = setPool(paths, name, members2);
68646
+ reportMutation(options.json, { action: "updated", kind: "pool", name, value: updated }, () => {
68647
+ console.log(`Pool "${name}" now has ${members2.join(", ")}.`);
68648
+ });
68603
68649
  }),
68604
68650
  ["agent-shim pool set subs --identity work --identity personal --identity spare"]
68605
68651
  );
@@ -68635,18 +68681,22 @@ function registerPoolCommand(program2, deps) {
68635
68681
  ["agent-shim pool show subs"]
68636
68682
  );
68637
68683
  withExamples(
68638
- pool.command("remove <name>").description("Delete a pool. Its member identities are untouched.").option("--yes", "Skip the confirmation prompt.").action(async (name, options) => {
68684
+ pool.command("remove <name>").description("Delete a pool. Its member identities are untouched.").option("--yes", "Skip the confirmation prompt.").option("--json", "Print the result as JSON.").action(async (name, options) => {
68639
68685
  requirePool(paths, name);
68640
68686
  await confirmRemoval(deps, options.yes, `pool "${name}"`);
68641
68687
  removePool(paths, name);
68642
- console.log(`Removed pool "${name}".`);
68688
+ reportMutation(options.json, { action: "removed", kind: "pool", name }, () => {
68689
+ console.log(`Removed pool "${name}".`);
68690
+ });
68643
68691
  }),
68644
68692
  ["agent-shim pool remove subs --yes"]
68645
68693
  );
68646
68694
  withExamples(
68647
- pool.command("use <name>").description("Make launches with no other selection pick a member of this pool.").action((name) => {
68695
+ pool.command("use <name>").description("Make launches with no other selection pick a member of this pool.").option("--json", "Print the result as JSON.").action((name, options) => {
68648
68696
  useIdentity(paths, `${POOL_SELECTOR_PREFIX}${name}`);
68649
- console.log(`Active selection is now pool "${name}".`);
68697
+ reportMutation(options.json, { action: "selected", kind: "pool", name }, () => {
68698
+ console.log(`Active selection is now pool "${name}".`);
68699
+ });
68650
68700
  }),
68651
68701
  ["agent-shim pool use subs"]
68652
68702
  );
@@ -68707,7 +68757,7 @@ function registerProviderCommand(program2, deps) {
68707
68757
  new Option("--credential-target <target>", "Where the token goes: bearer sets ANTHROPIC_AUTH_TOKEN (default), apiKey sets ANTHROPIC_API_KEY.").choices(
68708
68758
  PROVIDER_CREDENTIAL_TARGETS
68709
68759
  )
68710
- ).option("--env <KEY=VALUE>", "Extra environment entry for the child (repeatable).", collectStringPair).option("--codex-default-model <model>", "Codex providers: the codex model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: the codex model one tier (fable, opus, sonnet or haiku) maps to (repeatable).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: the reasoning effort when a request asks for none the backend accepts.").choices(CODEX_EFFORTS)).action((name, options) => {
68760
+ ).option("--env <KEY=VALUE>", "Extra environment entry for the child (repeatable).", collectStringPair).option("--codex-default-model <model>", "Codex providers: the codex model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: the codex model one tier (fable, opus, sonnet or haiku) maps to (repeatable).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: the reasoning effort when a request asks for none the backend accepts.").choices(CODEX_EFFORTS)).option("--json", "Print the result as JSON.").action((name, options) => {
68711
68761
  const codex = codexConfigFromOptions(options);
68712
68762
  const created = addProvider(paths, name, {
68713
68763
  ...options.kind === void 0 ? {} : { kind: options.kind },
@@ -68718,7 +68768,9 @@ function registerProviderCommand(program2, deps) {
68718
68768
  ...options.env === void 0 ? {} : { env: options.env },
68719
68769
  ...codex === void 0 ? {} : { codex }
68720
68770
  });
68721
- console.log(`Created provider "${name}" (${describeProviderEndpoint(created)}, credential ${describeCredential(created.credential)}).`);
68771
+ reportMutation(options.json, { action: "created", kind: "provider", name, value: created }, () => {
68772
+ console.log(`Created provider "${name}" (${describeProviderEndpoint(created)}, credential ${describeCredential(created.credential)}).`);
68773
+ });
68722
68774
  }),
68723
68775
  [
68724
68776
  "agent-shim provider add z --display-name z.ai --base-url https://api.z.ai/api/anthropic --credential env:Z_API_TOKEN",
@@ -68740,8 +68792,9 @@ function registerProviderCommand(program2, deps) {
68740
68792
  ).option("--env <KEY=VALUE>", "Add or replace an environment entry for the child (repeatable).", collectStringPair).option("--unset-env <KEY>", "Remove an environment entry (repeatable).", collectRepeated).option("--codex-default-model <model>", "Codex providers: replace the model a request maps to when no tier matches.").option("--codex-model <tier=model>", "Codex providers: set the codex model one tier maps to (repeatable; other tiers are kept).", collectStringPair).addOption(new Option("--codex-effort <effort>", "Codex providers: replace the default reasoning effort.").choices(CODEX_EFFORTS));
68741
68793
  addCredentialCacheOptions(providerSet);
68742
68794
  withExamples(
68743
- providerSet.action((name, options) => {
68744
- if (Object.values(options).every((value) => value === void 0)) {
68795
+ providerSet.option("--json", "Print the result as JSON.").action((name, allOptions) => {
68796
+ const { json: json2, ...options } = allOptions;
68797
+ if (Object.entries(allOptions).every(([key, value]) => key === "json" || value === void 0)) {
68745
68798
  throw new UsageError(
68746
68799
  "Nothing to change: pass --display-name, --base-url, --credential, --credential-target, a --credential-cache option, --env, --unset-env, --codex-default-model, --codex-model or --codex-effort."
68747
68800
  );
@@ -68758,7 +68811,9 @@ function registerProviderCommand(program2, deps) {
68758
68811
  ...options.env === void 0 ? {} : { env: options.env },
68759
68812
  ...options.unsetEnv === void 0 ? {} : { unsetEnv: options.unsetEnv }
68760
68813
  });
68761
- console.log(`Updated provider "${name}" (${describeProviderEndpoint(updated)}, credential ${describeCredential(updated.credential)}).`);
68814
+ reportMutation(json2, { action: "updated", kind: "provider", name, value: updated }, () => {
68815
+ console.log(`Updated provider "${name}" (${describeProviderEndpoint(updated)}, credential ${describeCredential(updated.credential)}).`);
68816
+ });
68762
68817
  }),
68763
68818
  [
68764
68819
  "agent-shim provider set z --base-url https://api.z.ai/api/anthropic",
@@ -68812,13 +68867,15 @@ function registerProviderCommand(program2, deps) {
68812
68867
  ["agent-shim provider show z", "agent-shim provider show z --json"]
68813
68868
  );
68814
68869
  withExamples(
68815
- provider.command("remove <name>").description("Delete a provider definition.").option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").action(async (name, options) => {
68870
+ provider.command("remove <name>").description("Delete a provider definition.").option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").option("--json", "Print the result as JSON.").action(async (name, options) => {
68816
68871
  if (!providerExists(paths, name)) {
68817
68872
  throw new ProviderNotFoundError(name);
68818
68873
  }
68819
68874
  await confirmRemoval(deps, options.yes, `provider "${name}"`);
68820
68875
  removeProvider(paths, name);
68821
- console.log(`Removed provider "${name}".`);
68876
+ reportMutation(options.json, { action: "removed", kind: "provider", name }, () => {
68877
+ console.log(`Removed provider "${name}".`);
68878
+ });
68822
68879
  }),
68823
68880
  ["agent-shim provider remove z --yes"]
68824
68881
  );
@@ -68844,21 +68901,27 @@ function registerRuleCommand(program2, deps) {
68844
68901
  ["agent-shim rule add ~/work/acme --config-profile client-acme", "agent-shim rule list"]
68845
68902
  );
68846
68903
  withExamples(
68847
- rule.command("add <path>").description("Add a directory rule for a path. Fails if a rule for that exact path already exists.").option("--config-profile <profile>", "Configuration profile to select under this path.").option("--identity <identity>", "Identity to pin under this path.").action(async (rulePath, options) => {
68904
+ rule.command("add <path>").description("Add a directory rule for a path. Fails if a rule for that exact path already exists.").option("--config-profile <profile>", "Configuration profile to select under this path.").option("--identity <identity>", "Identity to pin under this path.").option("--json", "Print the result as JSON.").action(async (rulePath, allOptions) => {
68905
+ const { json: json2, ...options } = allOptions;
68848
68906
  await checkRuleTargets(deps, options);
68849
- addDirectoryRule(paths, rulePath, options);
68850
- console.log(`Added directory rule for "${rulePath}".`);
68907
+ const created = addDirectoryRule(paths, rulePath, options);
68908
+ reportMutation(json2, { action: "created", kind: "rule", name: rulePath, value: created }, () => {
68909
+ console.log(`Added directory rule for "${rulePath}".`);
68910
+ });
68851
68911
  }),
68852
68912
  ["agent-shim rule add ~/work/acme --config-profile client-acme", "agent-shim rule add ~/personal --identity personal"]
68853
68913
  );
68854
68914
  withExamples(
68855
- rule.command("set <path>").description("Update the directory rule for a path.").option("--config-profile <profile>", "Configuration profile to select under this path.").option("--no-config-profile", "Stop selecting a configuration profile under this path.").option("--identity <identity>", "Identity to pin under this path.").option("--no-identity", "Stop pinning an identity under this path.").action(async (rulePath, options) => {
68915
+ rule.command("set <path>").description("Update the directory rule for a path.").option("--config-profile <profile>", "Configuration profile to select under this path.").option("--no-config-profile", "Stop selecting a configuration profile under this path.").option("--identity <identity>", "Identity to pin under this path.").option("--no-identity", "Stop pinning an identity under this path.").option("--json", "Print the result as JSON.").action(async (rulePath, allOptions) => {
68916
+ const { json: json2, ...options } = allOptions;
68856
68917
  if (options.configProfile === void 0 && options.identity === void 0) {
68857
68918
  throw new UsageError("Nothing to change: pass --config-profile, --no-config-profile, --identity or --no-identity.");
68858
68919
  }
68859
68920
  await checkRuleTargets(deps, options);
68860
- updateDirectoryRule(paths, rulePath, options);
68861
- console.log(`Updated directory rule for "${rulePath}".`);
68921
+ const updated = updateDirectoryRule(paths, rulePath, options);
68922
+ reportMutation(json2, { action: "updated", kind: "rule", name: rulePath, value: updated }, () => {
68923
+ console.log(`Updated directory rule for "${rulePath}".`);
68924
+ });
68862
68925
  }),
68863
68926
  ["agent-shim rule set ~/work/acme --identity work", "agent-shim rule set ~/work/acme --no-config-profile"]
68864
68927
  );
@@ -68895,13 +68958,15 @@ function registerRuleCommand(program2, deps) {
68895
68958
  ["agent-shim rule show ~/work/acme"]
68896
68959
  );
68897
68960
  withExamples(
68898
- rule.command("remove <path>").description("Remove the directory rule for a path.").option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").action(async (rulePath, options) => {
68961
+ rule.command("remove <path>").description("Remove the directory rule for a path.").option("--yes", "Remove without asking for confirmation (required when standard input is not a terminal).").option("--json", "Print the result as JSON.").action(async (rulePath, options) => {
68899
68962
  if (!listDirectoryRules(paths).some((entry) => entry.path === rulePath)) {
68900
68963
  throw new DirectoryRuleNotFoundError(rulePath);
68901
68964
  }
68902
68965
  await confirmRemoval(deps, options.yes, `the directory rule for "${rulePath}"`);
68903
68966
  removeDirectoryRule(paths, rulePath);
68904
- console.log(`Removed directory rule for "${rulePath}".`);
68967
+ reportMutation(options.json, { action: "removed", kind: "rule", name: rulePath }, () => {
68968
+ console.log(`Removed directory rule for "${rulePath}".`);
68969
+ });
68905
68970
  }),
68906
68971
  ["agent-shim rule remove ~/work/acme --yes"]
68907
68972
  );
package/dist/index.cjs CHANGED
@@ -2940,11 +2940,17 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
2940
2940
  const listener = await effects.listenLoopback(preferredPort, (socket) => {
2941
2941
  handleConnect(socket, { config, effects, sessions, ledger });
2942
2942
  });
2943
+ const apiSession = sessions.find((session) => session.host === config.routedHost);
2944
+ const transparent = config.transparentPort === void 0 || apiSession === void 0 ? void 0 : await effects.listenLoopback(config.transparentPort, (socket) => {
2945
+ apiSession.tlsAcceptor.accept(socket, config.transparentCapability ?? "");
2946
+ });
2943
2947
  return {
2944
2948
  port: listener.port,
2949
+ ...transparent === void 0 ? {} : { transparentPort: transparent.port },
2945
2950
  close: async () => {
2946
2951
  ledger.close();
2947
2952
  await listener.close();
2953
+ await transparent?.close();
2948
2954
  for (const { httpSession, tlsAcceptor } of sessions) {
2949
2955
  tlsAcceptor.close();
2950
2956
  httpSession?.close();
package/dist/index.mjs CHANGED
@@ -2777,11 +2777,17 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
2777
2777
  const listener = await effects.listenLoopback(preferredPort, (socket) => {
2778
2778
  handleConnect(socket, { config, effects, sessions, ledger });
2779
2779
  });
2780
+ const apiSession = sessions.find((session) => session.host === config.routedHost);
2781
+ const transparent = config.transparentPort === void 0 || apiSession === void 0 ? void 0 : await effects.listenLoopback(config.transparentPort, (socket) => {
2782
+ apiSession.tlsAcceptor.accept(socket, config.transparentCapability ?? "");
2783
+ });
2780
2784
  return {
2781
2785
  port: listener.port,
2786
+ ...transparent === void 0 ? {} : { transparentPort: transparent.port },
2782
2787
  close: async () => {
2783
2788
  ledger.close();
2784
2789
  await listener.close();
2790
+ await transparent?.close();
2785
2791
  for (const { httpSession, tlsAcceptor } of sessions) {
2786
2792
  tlsAcceptor.close();
2787
2793
  httpSession?.close();
@@ -15,6 +15,17 @@ export interface CommandDeps {
15
15
  }
16
16
  /** Prints `value` as indented JSON on standard output: the one `--json` output format every `list`, `show`, `check`, `doctor` and `headroom status` command shares. */
17
17
  export declare function printJson(value: unknown): void;
18
+ /** What a mutating command did, as the one object `--json` prints in place of its text output. */
19
+ export interface MutationReport {
20
+ readonly action: "created" | "updated" | "removed" | "selected";
21
+ /** The noun the command works on: `identity`, `profile`, `provider`, `pool` or `rule`. */
22
+ readonly kind: string;
23
+ readonly name: string;
24
+ /** The stored object after the change; absent for a removal and for a selection. */
25
+ readonly value?: unknown;
26
+ }
27
+ /** Reports a mutating command's outcome: the `MutationReport` as JSON under `--json`, otherwise whatever `printText` prints. */
28
+ export declare function reportMutation(json: boolean | undefined, report: MutationReport, printText: () => void): void;
18
29
  /**
19
30
  * Appends an `Examples:` block to `command`'s help output, one `$ agent-shim ...` line per entry. Every command registers at least one, so `--help` on any of them shows a runnable invocation, not just its option list.
20
31
  */
@@ -171,6 +171,14 @@ export interface ConnectServerConfig {
171
171
  readonly upstreamFor: (host: string) => ConnectForwardTarget;
172
172
  /** Whether a capability presented as a CONNECT request's proxy credential belongs to a live launch: the same constant-time check against the same registry the routed pipeline's admission makes, read fresh on every call because launches come and go. */
173
173
  readonly isLiveCapability: (token: string) => boolean;
174
+ /**
175
+ * Binds a second listener that serves the intercept hosts' sessions to connections arriving pre-established, with no CONNECT handshake to authenticate: what an operating-system redirect (a pf rule sending the real hosts' address to this port) delivers. Only the API host's session is reachable this way, never a tap host's, because a redirect can name only addresses and the control plane shares the API host's address today; if that ever splits, the transparent listener must not follow it blindly. There is no capability to check on this surface: the connection carried no proxy credential, so every request rides the pipeline anonymously, admitted the way a tunnel's own header-less requests are (by nothing, which is exactly what they present), and the surface exists solely for clients that ignore proxies and would otherwise be invisible to the door entirely.
176
+ */
177
+ readonly transparentPort?: number;
178
+ /**
179
+ * The capability the transparent surface's sessions carry: a real, registered token minted by the door for its own pid, so header-less traffic that arrives by redirect is admitted and attributed like any launch's instead of being refused for a credential it cannot possibly present. Absent along with `transparentPort`.
180
+ */
181
+ readonly transparentCapability?: string;
174
182
  /** The intercept hosts that are never HTTP-parsed: terminated, tapped at the byte level, and pumped to their real host. */
175
183
  readonly tapHosts: readonly string[];
176
184
  /** The connection deadlines and caps; production passes `CONNECT_LIMITS`. */
@@ -181,6 +189,8 @@ export interface ConnectServerConfig {
181
189
  /** A running connect surface. */
182
190
  export interface ConnectServerHandle {
183
191
  readonly port: number;
192
+ /** The transparent listener's port, when one was requested: what an operating-system redirect should point at. */
193
+ readonly transparentPort?: number;
184
194
  /** Stops the listener, drops every live tunnel and terminated session, and resolves once the port is released. */
185
195
  readonly close: () => Promise<void>;
186
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "7.10.2",
3
+ "version": "7.12.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",