@event4u/agent-switch 2.1.0 → 2.3.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 +166 -73
- package/dist/activity.js +99 -0
- package/dist/args.js +23 -3
- package/dist/auth-status.js +159 -0
- package/dist/balance.js +55 -0
- package/dist/browsers.js +90 -0
- package/dist/daemon.js +95 -42
- package/dist/desktop-sessions.js +115 -0
- package/dist/desktop-settings.js +90 -0
- package/dist/dir-resolve.js +178 -0
- package/dist/doctor.js +239 -69
- package/dist/gui-settings.js +67 -0
- package/dist/history.js +20 -0
- package/dist/hooks.js +21 -0
- package/dist/index.js +879 -68
- package/dist/launchers.js +228 -0
- package/dist/locks.js +61 -0
- package/dist/mappings.js +48 -9
- package/dist/pools.js +157 -0
- package/dist/profiles.js +181 -67
- package/dist/protection.js +100 -0
- package/dist/rebind.js +115 -12
- package/dist/service.js +40 -9
- package/dist/sessions.js +82 -13
- package/dist/share.js +92 -13
- package/dist/shellenv.js +122 -21
- package/dist/single-load.js +5 -0
- package/dist/token-freshen.js +11 -0
- package/package.json +1 -1
package/dist/shellenv.js
CHANGED
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
* A side effect worth having: a genuinely broken agent-switch now surfaces its
|
|
35
35
|
* error instead of silently degrading the shell onto the default config dir.
|
|
36
36
|
*/
|
|
37
|
+
import { spawnSync } from "node:child_process";
|
|
38
|
+
import * as fs from "node:fs";
|
|
39
|
+
import * as path from "node:path";
|
|
37
40
|
import { allProviders } from "./providers.js";
|
|
38
41
|
import { GUARD_REFUSED_EXIT } from "./single-load.js";
|
|
39
42
|
export const SHELLS = ["zsh", "bash", "fish", "powershell"];
|
|
@@ -59,11 +62,56 @@ export function detectShell(requested, env = process.env, platform = process.pla
|
|
|
59
62
|
return "zsh";
|
|
60
63
|
return "zsh"; // sensible POSIX default when $SHELL is unset
|
|
61
64
|
}
|
|
65
|
+
// The CLI the running app activated (`agent-switch activate` writes its path to
|
|
66
|
+
// <home>/active-cli), so a dev build and an installed app each bring their own;
|
|
67
|
+
// a missing or vanished path falls back to agent-switch on PATH.
|
|
68
|
+
const POSIX_BIN = `__agent_switch_bin() {
|
|
69
|
+
local p
|
|
70
|
+
p="$(cat "\${AGENT_SWITCH_HOME:-$HOME/.agent-switch}/active-cli" 2>/dev/null)"
|
|
71
|
+
if [ -n "$p" ] && [ -x "$p" ]; then printf '%s\\n' "$p"
|
|
72
|
+
elif command -v agent-switch >/dev/null 2>&1; then printf 'agent-switch\\n'
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
__agent_switch() {
|
|
76
|
+
local as
|
|
77
|
+
as="$(__agent_switch_bin)"
|
|
78
|
+
command "\${as:-agent-switch}" "$@"
|
|
79
|
+
}`;
|
|
80
|
+
const FISH_BIN = `function __agent_switch_bin
|
|
81
|
+
set -l home $HOME/.agent-switch
|
|
82
|
+
set -q AGENT_SWITCH_HOME; and set home $AGENT_SWITCH_HOME
|
|
83
|
+
if test -r $home/active-cli
|
|
84
|
+
set -l p (cat $home/active-cli)
|
|
85
|
+
if test -x "$p"
|
|
86
|
+
echo $p
|
|
87
|
+
return
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
type -q agent-switch; and echo agent-switch
|
|
91
|
+
end
|
|
92
|
+
function __agent_switch
|
|
93
|
+
set -l as (__agent_switch_bin)
|
|
94
|
+
test -n "$as"; or set as agent-switch
|
|
95
|
+
command $as $argv
|
|
96
|
+
end`;
|
|
97
|
+
const POWERSHELL_BIN = `function __agent_switch_bin {
|
|
98
|
+
$home_ = if ($env:AGENT_SWITCH_HOME) { $env:AGENT_SWITCH_HOME } else { Join-Path $HOME '.agent-switch' }
|
|
99
|
+
$p = Get-Content -Path (Join-Path $home_ 'active-cli') -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
100
|
+
if ($p -and (Test-Path $p)) { return $p }
|
|
101
|
+
if (Get-Command agent-switch -ErrorAction SilentlyContinue) { return 'agent-switch' }
|
|
102
|
+
return $null
|
|
103
|
+
}
|
|
104
|
+
function __agent_switch {
|
|
105
|
+
$as = __agent_switch_bin
|
|
106
|
+
if (-not $as) { $as = 'agent-switch' }
|
|
107
|
+
if ($as -like '*.js') { & node $as @args } else { & $as @args }
|
|
108
|
+
}`;
|
|
62
109
|
function posixWrapper(binary, envVar, id) {
|
|
63
110
|
return `${binary}() {
|
|
64
|
-
local dir rc
|
|
65
|
-
|
|
66
|
-
|
|
111
|
+
local dir rc as
|
|
112
|
+
as="$(__agent_switch_bin)"
|
|
113
|
+
[ -n "$as" ] || { command ${binary} "$@"; return; }
|
|
114
|
+
dir="$(command "$as" dir --provider ${id} --guard)"
|
|
67
115
|
rc=$?
|
|
68
116
|
if [ $rc -eq ${GUARD_REFUSED_EXIT} ]; then return $rc; fi
|
|
69
117
|
if [ -n "$dir" ]; then
|
|
@@ -75,11 +123,12 @@ function posixWrapper(binary, envVar, id) {
|
|
|
75
123
|
}
|
|
76
124
|
function fishWrapper(binary, envVar, id) {
|
|
77
125
|
return `function ${binary}
|
|
78
|
-
|
|
126
|
+
set -l as (__agent_switch_bin)
|
|
127
|
+
if test -z "$as"
|
|
79
128
|
command ${binary} $argv
|
|
80
129
|
return
|
|
81
130
|
end
|
|
82
|
-
set -l dir (command
|
|
131
|
+
set -l dir (command $as dir --provider ${id} --guard)
|
|
83
132
|
set -l rc $status
|
|
84
133
|
if test $rc -eq ${GUARD_REFUSED_EXIT}
|
|
85
134
|
return $rc
|
|
@@ -95,8 +144,9 @@ function powershellWrapper(binary, envVar, id) {
|
|
|
95
144
|
return `function ${binary} {
|
|
96
145
|
$exe = Get-Command ${binary} -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
97
146
|
if (-not $exe) { Write-Error '${binary} not found on PATH.'; return }
|
|
98
|
-
|
|
99
|
-
$
|
|
147
|
+
$as = __agent_switch_bin
|
|
148
|
+
if (-not $as) { & $exe.Source @args; return }
|
|
149
|
+
$dir = (__agent_switch dir --provider ${id} --guard)
|
|
100
150
|
if ($LASTEXITCODE -eq ${GUARD_REFUSED_EXIT}) { return }
|
|
101
151
|
if ($dir) {
|
|
102
152
|
$prev = Get-Item -Path Env:\\${envVar} -ErrorAction SilentlyContinue
|
|
@@ -107,8 +157,8 @@ function powershellWrapper(binary, envVar, id) {
|
|
|
107
157
|
}
|
|
108
158
|
}`;
|
|
109
159
|
}
|
|
110
|
-
function build(header, wrappers, asw) {
|
|
111
|
-
return [header, ...wrappers, asw].join("\n");
|
|
160
|
+
function build(header, bin, wrappers, asw) {
|
|
161
|
+
return [header, bin, ...wrappers, asw].join("\n");
|
|
112
162
|
}
|
|
113
163
|
export function shellenvScript(shell) {
|
|
114
164
|
const providers = allProviders();
|
|
@@ -117,34 +167,85 @@ export function shellenvScript(shell) {
|
|
|
117
167
|
const asw = `# Convenience: "asw" lists all; "asw work" switches claude; "asw codex work" switches a provider
|
|
118
168
|
function asw
|
|
119
169
|
if test (count $argv) -eq 0
|
|
120
|
-
|
|
170
|
+
__agent_switch list
|
|
121
171
|
else if contains -- $argv[1] claude codex antigravity
|
|
122
|
-
|
|
172
|
+
__agent_switch use $argv[2] --provider $argv[1]
|
|
123
173
|
else
|
|
124
|
-
|
|
174
|
+
__agent_switch use $argv
|
|
125
175
|
end
|
|
126
176
|
end`;
|
|
127
|
-
return build(header, providers.map((p) => fishWrapper(p.binary, p.envVar, p.id)), asw);
|
|
177
|
+
return build(header, FISH_BIN, providers.map((p) => fishWrapper(p.binary, p.envVar, p.id)), asw);
|
|
128
178
|
}
|
|
129
179
|
if (shell === "powershell") {
|
|
130
180
|
const header = `# agent-switch shell integration — add to $PROFILE: agent-switch shellenv --shell powershell | Out-String | Invoke-Expression`;
|
|
131
181
|
const asw = `# Convenience: "asw" lists all; "asw work" switches claude; "asw codex work" switches a provider
|
|
132
182
|
function asw {
|
|
133
|
-
if ($args.Count -eq 0) {
|
|
134
|
-
if (@('claude','codex','antigravity') -contains $args[0]) {
|
|
135
|
-
else {
|
|
183
|
+
if ($args.Count -eq 0) { __agent_switch list; return }
|
|
184
|
+
if (@('claude','codex','antigravity') -contains $args[0]) { __agent_switch use $args[1] --provider $args[0] }
|
|
185
|
+
else { __agent_switch use @args }
|
|
136
186
|
}`;
|
|
137
|
-
return build(header, providers.map((p) => powershellWrapper(p.binary, p.envVar, p.id)), asw);
|
|
187
|
+
return build(header, POWERSHELL_BIN, providers.map((p) => powershellWrapper(p.binary, p.envVar, p.id)), asw);
|
|
138
188
|
}
|
|
139
189
|
// zsh + bash share the POSIX snippet.
|
|
140
190
|
const header = `# agent-switch shell integration — add to your rc file: eval "$(agent-switch shellenv)"`;
|
|
141
191
|
const asw = `# Convenience: "asw" lists all; "asw work" switches claude; "asw codex work" switches a provider
|
|
142
192
|
asw() {
|
|
143
|
-
if [ $# -eq 0 ]; then
|
|
193
|
+
if [ $# -eq 0 ]; then __agent_switch list; return; fi
|
|
144
194
|
case "$1" in
|
|
145
|
-
claude|codex|antigravity)
|
|
146
|
-
*)
|
|
195
|
+
claude|codex|antigravity) __agent_switch use "$2" --provider "$1" ;;
|
|
196
|
+
*) __agent_switch use "$@" ;;
|
|
147
197
|
esac
|
|
148
198
|
}`;
|
|
149
|
-
return build(header, providers.map((p) => posixWrapper(p.binary, p.envVar, p.id)), asw);
|
|
199
|
+
return build(header, POSIX_BIN, providers.map((p) => posixWrapper(p.binary, p.envVar, p.id)), asw);
|
|
200
|
+
}
|
|
201
|
+
/** The line that loads the integration, per shell (same as the docs table). */
|
|
202
|
+
export const SHELLENV_LINE = {
|
|
203
|
+
zsh: 'eval "$(agent-switch shellenv)"',
|
|
204
|
+
bash: 'eval "$(agent-switch shellenv)"',
|
|
205
|
+
fish: "agent-switch shellenv --shell fish | source",
|
|
206
|
+
powershell: "agent-switch shellenv --shell powershell | Out-String | Invoke-Expression",
|
|
207
|
+
};
|
|
208
|
+
/** The startup file `shellenv install` writes to. macOS opens login shells,
|
|
209
|
+
* which read ~/.bash_profile rather than ~/.bashrc. For PowerShell only the
|
|
210
|
+
* shell knows its profile — Windows PowerShell 5.1 and a Documents folder
|
|
211
|
+
* redirected to OneDrive both live elsewhere — so it is asked first. */
|
|
212
|
+
export function rcFileFor(shell, home, platform = process.platform, askProfile = powershellProfile) {
|
|
213
|
+
if (shell === "zsh")
|
|
214
|
+
return path.join(home, ".zshrc");
|
|
215
|
+
if (shell === "bash")
|
|
216
|
+
return path.join(home, platform === "darwin" ? ".bash_profile" : ".bashrc");
|
|
217
|
+
if (shell === "fish")
|
|
218
|
+
return path.join(home, ".config", "fish", "config.fish");
|
|
219
|
+
return askProfile() ?? path.join(home, "Documents", "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
220
|
+
}
|
|
221
|
+
/** Which PowerShell to ask first: the one this command runs under. Windows
|
|
222
|
+
* PowerShell 5.1 puts only `WindowsPowerShell` paths on PSModulePath; pwsh adds
|
|
223
|
+
* its own `PowerShell\7` (or `Documents\PowerShell`) ones. */
|
|
224
|
+
export function powershellOrder(env = process.env) {
|
|
225
|
+
const p = env.PSModulePath ?? "";
|
|
226
|
+
const pwsh = /[\\/]PowerShell[\\/](7|Modules)/i.test(p);
|
|
227
|
+
return !pwsh && /WindowsPowerShell/i.test(p) ? ["powershell", "pwsh"] : ["pwsh", "powershell"];
|
|
228
|
+
}
|
|
229
|
+
/** `$PROFILE` from the PowerShell the user runs, then the other one; null when
|
|
230
|
+
* neither answers. */
|
|
231
|
+
export function powershellProfile() {
|
|
232
|
+
for (const exe of powershellOrder()) {
|
|
233
|
+
const r = spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", "$PROFILE"], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
234
|
+
const out = r.status === 0 ? r.stdout.trim() : "";
|
|
235
|
+
if (out)
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
/** Append the loader line to the shell's startup file, once. `present` says
|
|
241
|
+
* whether any known startup file already loads it (the doctor check). */
|
|
242
|
+
export function installShellIntegration(shell, home, present) {
|
|
243
|
+
const file = rcFileFor(shell, home);
|
|
244
|
+
if (present)
|
|
245
|
+
return { shell, file, alreadyInstalled: true };
|
|
246
|
+
const before = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
247
|
+
const sep = before === "" || before.endsWith("\n") ? "" : "\n";
|
|
248
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
249
|
+
fs.writeFileSync(file, `${before}${sep}\n# agent-switch shell integration\n${SHELLENV_LINE[shell]}\n`);
|
|
250
|
+
return { shell, file, alreadyInstalled: false };
|
|
150
251
|
}
|
package/dist/single-load.js
CHANGED
|
@@ -25,7 +25,12 @@ import * as path from "node:path";
|
|
|
25
25
|
import { liveSessionPids } from "./api.js";
|
|
26
26
|
import { pidCwd } from "./sessions.js";
|
|
27
27
|
export const GUARD_POLICIES = ["block", "warn", "off"];
|
|
28
|
+
/** What a state file without a `guard` key meant before 2.2 — kept so an
|
|
29
|
+
* upgrade never changes a user's effective guard. */
|
|
28
30
|
export const DEFAULT_GUARD_POLICY = "block";
|
|
31
|
+
/** A fresh install (no state file and no profile yet) starts on `warn`: a second load is
|
|
32
|
+
* reported, never refused, so nothing changes how a tool starts. */
|
|
33
|
+
export const FRESH_INSTALL_GUARD_POLICY = "warn";
|
|
29
34
|
export function isGuardPolicy(v) {
|
|
30
35
|
return typeof v === "string" && GUARD_POLICIES.includes(v);
|
|
31
36
|
}
|
package/dist/token-freshen.js
CHANGED
|
@@ -152,3 +152,14 @@ export function freshAccessToken(configDir, deps = defaultFreshenDeps()) {
|
|
|
152
152
|
const creds = deps.readCredential(configDir);
|
|
153
153
|
return creds ? accessTokenOf(creds) : null;
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The token for a usage read. A profile that shares its login with the default
|
|
157
|
+
* install (seeded by `import`) is never refreshed: whichever side refreshes
|
|
158
|
+
* first invalidates the other, so its stored token is used only while valid.
|
|
159
|
+
*/
|
|
160
|
+
export function usageToken(configDir, deps, sharesDefaultLogin) {
|
|
161
|
+
if (!sharesDefaultLogin)
|
|
162
|
+
return freshAccessToken(configDir, deps);
|
|
163
|
+
const creds = deps.readCredential(configDir);
|
|
164
|
+
return creds && !needsFreshen(creds, deps.now(), deps.bufferMs) ? accessTokenOf(creds) : null;
|
|
165
|
+
}
|
package/package.json
CHANGED