@hasna/accounts 0.1.4 → 0.1.5

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
@@ -47,6 +47,11 @@ accounts apply personal
47
47
  # Or terminal-only (parallel sessions OK):
48
48
  accounts launch work --tool claude
49
49
  eval "$(accounts env personal --tool claude)" # other terminal
50
+
51
+ # Or supervised: lets MCP switch/restart this Claude process automatically
52
+ accounts use work --tool claude
53
+ accounts run claude --resume
54
+ accounts switch personal --tool claude --supervisor # from another terminal
50
55
  ```
51
56
 
52
57
  After `accounts login <name> --tool claude`, `accounts` snapshots the auth Claude
@@ -92,6 +97,7 @@ Implementation details: [docs/IMPLEMENT.md](docs/IMPLEMENT.md).
92
97
  | `accounts apply <name> --tool claude` | Apply profile auth to live Claude paths (requires snapshot; Claude-only). |
93
98
  | `accounts pick` | Interactive picker; default applies. `--env`, `--no-act`. |
94
99
  | `accounts switch <name> --tool <tool>` | Switch profile and print a restart/resume command. Add `--resume`; add `--launch` to run it. |
100
+ | `accounts switch <name> --tool <tool> --supervisor` | Ask a running `accounts run <tool>` supervisor to restart under that profile. |
95
101
  | `accounts use <name> --tool <tool>` | Mark profile active; prints apply/env hints. |
96
102
  | `accounts list` (`ls`) | List profiles (`●` active, `◉` applied, `●◉` both). |
97
103
  | `accounts show <name> --tool <tool>` | Profile details including active/applied flags. |
@@ -99,7 +105,11 @@ Implementation details: [docs/IMPLEMENT.md](docs/IMPLEMENT.md).
99
105
  | `accounts active [tool]` | Print active profile name (scripting). |
100
106
  | `accounts applied [tool]` | Print applied profile name (scripting). |
101
107
  | `accounts env [name] --tool <tool>` | Print one or more `export ...` lines for the profile. |
102
- | `accounts launch\|run <name> --tool <tool>` | Launch tool with profile env. |
108
+ | `accounts launch <name> --tool <tool>` | Launch tool once with profile env. |
109
+ | `accounts run <tool> [args...]` | Run a tool under the supervisor so MCP/CLI can switch and restart it. |
110
+ | `accounts supervisor status [tool]` | Show running supervisors. |
111
+ | `accounts supervisor switch <name> --tool <tool>` | Switch a running supervisor to another profile. |
112
+ | `accounts supervisor stop <tool>` | Stop a running supervisor and its child process. |
103
113
  | `accounts shell <name> --tool <tool>` | Subshell with profile env. |
104
114
  | `accounts hook install` | Install `claude()` wrapper — see [docs/hook.md](docs/hook.md). |
105
115
  | `accounts hook uninstall` | Remove hook script. |
@@ -125,16 +135,29 @@ Add it to Claude/Codex/opencode/Cursor MCP config as a command server named
125
135
  - `current_profile`
126
136
  - `switch_profile`
127
137
 
128
- `switch_profile` applies Claude live auth when the target profile is Claude and
129
- returns a restart handoff command. MCP servers cannot safely kill their parent
130
- agent process, so the tool returns an instruction such as: exit this session and
131
- run `CLAUDE_CONFIG_DIR=... claude --continue`.
138
+ For automatic agent restarts, start the agent through `accounts run`:
139
+
140
+ ```bash
141
+ accounts use account001 --tool claude
142
+ accounts run claude --resume
143
+ ```
144
+
145
+ When `switch_profile` is called from that Claude session, `accounts-mcp` contacts
146
+ the supervisor. The supervisor applies/switches the profile, closes the current
147
+ Claude process, and restarts it with the selected profile. Claude uses
148
+ `claude --continue`; Codex uses `codex resume --last`; opencode uses
149
+ `opencode --continue`; custom tools can define `resumeArgs`.
150
+
151
+ If the agent was not started through `accounts run`, MCP falls back to the safe
152
+ handoff behavior and returns a command such as:
153
+ `CLAUDE_CONFIG_DIR=... claude --continue`.
132
154
 
133
155
  Human equivalent:
134
156
 
135
157
  ```bash
136
158
  accounts switch account001 --tool claude --resume
137
159
  accounts switch account001 --tool claude --resume --launch
160
+ accounts switch account001 --tool claude --supervisor
138
161
  accounts switch codex-work --tool codex --resume
139
162
  accounts switch ops --tool opencode --resume
140
163
  ```
@@ -156,6 +179,9 @@ then invokes the real `claude` binary. Full behavior and footguns: [docs/hook.md
156
179
  ~/.hasna/accounts/
157
180
  accounts.json # registry: profiles, current, applied (mode 600)
158
181
  claude-hook.sh # optional shell wrapper
182
+ supervisors/
183
+ claude.sock # local control socket for `accounts run claude`
184
+ claude.json # supervisor pid/profile/command metadata
159
185
  profiles/
160
186
  claude/<name>/ # managed config dir
161
187
  claude/<name>/.accounts-auth/ # auth snapshots for apply mode
@@ -181,6 +207,8 @@ Overrides: `ACCOUNTS_HOME`, `ACCOUNTS_STORE_PATH`.
181
207
  For Grok Build, prefer `accounts launch` or `accounts shell`; exporting `HOME`
182
208
  globally is intentionally not recommended.
183
209
 
210
+ Custom tools can join supervised resume switching with `accounts tools add ... --resume-arg <arg>`.
211
+
184
212
  ## Library
185
213
 
186
214
  ```ts
package/dist/cli.js CHANGED
@@ -2082,9 +2082,9 @@ var require_commander = __commonJS((exports) => {
2082
2082
 
2083
2083
  // src/cli.ts
2084
2084
  import { spawnSync } from "node:child_process";
2085
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
2085
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
2086
2086
  import { homedir as homedir5 } from "node:os";
2087
- import { dirname as dirname5, join as join10 } from "node:path";
2087
+ import { dirname as dirname5, join as join11 } from "node:path";
2088
2088
  import { fileURLToPath } from "node:url";
2089
2089
 
2090
2090
  // node_modules/commander/esm.mjs
@@ -7623,6 +7623,367 @@ function switchProfile(name, opts = {}) {
7623
7623
  };
7624
7624
  }
7625
7625
 
7626
+ // src/lib/supervisor.ts
7627
+ import { spawn } from "node:child_process";
7628
+ import { createHash } from "node:crypto";
7629
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
7630
+ import { createConnection, createServer } from "node:net";
7631
+ import { basename, join as join10 } from "node:path";
7632
+ var STATE_SUFFIX = ".json";
7633
+ function supervisorDir() {
7634
+ return join10(accountsHome(), "supervisors");
7635
+ }
7636
+ function supervisorStatePath(toolId) {
7637
+ return join10(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
7638
+ }
7639
+ function supervisorSocketPath(toolId) {
7640
+ if (process.platform === "win32") {
7641
+ const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
7642
+ return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
7643
+ }
7644
+ return join10(supervisorDir(), `${toolId}.sock`);
7645
+ }
7646
+ function nowIso2() {
7647
+ return new Date().toISOString();
7648
+ }
7649
+ function parseState(raw) {
7650
+ const data = JSON.parse(raw);
7651
+ if (data.version !== 1 || typeof data.tool !== "string" || typeof data.profile !== "string" || typeof data.pid !== "number" || typeof data.socketPath !== "string" || !Array.isArray(data.command)) {
7652
+ return;
7653
+ }
7654
+ return data;
7655
+ }
7656
+ function readSupervisorState(toolId) {
7657
+ const path = supervisorStatePath(toolId);
7658
+ if (!existsSync9(path))
7659
+ return;
7660
+ try {
7661
+ return parseState(readFileSync5(path, "utf8"));
7662
+ } catch {
7663
+ return;
7664
+ }
7665
+ }
7666
+ function listSupervisorStates() {
7667
+ const dir = supervisorDir();
7668
+ if (!existsSync9(dir))
7669
+ return [];
7670
+ return readdirSync(dir).filter((name) => name.endsWith(STATE_SUFFIX)).map((name) => basename(name, STATE_SUFFIX)).map((toolId) => readSupervisorState(toolId)).filter((state) => state !== undefined);
7671
+ }
7672
+ function writeSupervisorState(state) {
7673
+ mkdirSync7(supervisorDir(), { recursive: true });
7674
+ writeFileSync5(supervisorStatePath(state.tool), JSON.stringify(state, null, 2) + `
7675
+ `, { mode: 384 });
7676
+ }
7677
+ function removeSupervisorFiles(toolId) {
7678
+ rmSync2(supervisorStatePath(toolId), { force: true });
7679
+ if (process.platform !== "win32")
7680
+ rmSync2(supervisorSocketPath(toolId), { force: true });
7681
+ }
7682
+ function processAlive(pid) {
7683
+ try {
7684
+ process.kill(pid, 0);
7685
+ return true;
7686
+ } catch {
7687
+ return false;
7688
+ }
7689
+ }
7690
+ function knownTool(id) {
7691
+ try {
7692
+ return getTool(id);
7693
+ } catch {
7694
+ return;
7695
+ }
7696
+ }
7697
+ function resolveSupervisorLaunch(target, opts = {}) {
7698
+ const targetTool = knownTool(target);
7699
+ if (opts.profile) {
7700
+ const profile2 = getProfile(opts.profile, opts.tool ?? targetTool?.id);
7701
+ if (targetTool && profile2.tool !== targetTool.id) {
7702
+ throw new AccountsError(`profile "${profile2.name}" belongs to ${profile2.tool}, not ${targetTool.id}`);
7703
+ }
7704
+ return { profile: profile2, tool: getTool(profile2.tool), targetKind: targetTool ? "tool" : "profile" };
7705
+ }
7706
+ if (targetTool && !opts.tool) {
7707
+ const profile2 = currentProfile(targetTool.id) ?? appliedProfile(targetTool.id);
7708
+ if (!profile2) {
7709
+ throw new AccountsError(`no active ${targetTool.label} profile. Run \`accounts use <name> --tool ${targetTool.id}\` or pass --profile.`);
7710
+ }
7711
+ return { profile: profile2, tool: targetTool, targetKind: "tool" };
7712
+ }
7713
+ const profile = getProfile(target, opts.tool);
7714
+ return { profile, tool: getTool(profile.tool), targetKind: "profile" };
7715
+ }
7716
+ function exitCode(code, signal) {
7717
+ if (code !== null)
7718
+ return code;
7719
+ if (signal === "SIGINT")
7720
+ return 130;
7721
+ if (signal === "SIGTERM")
7722
+ return 143;
7723
+ return signal ? 1 : 0;
7724
+ }
7725
+ function killChildProcess(child, signal) {
7726
+ if (!child.pid)
7727
+ return;
7728
+ if (process.platform !== "win32") {
7729
+ try {
7730
+ process.kill(-child.pid, signal);
7731
+ return;
7732
+ } catch {}
7733
+ }
7734
+ child.kill(signal);
7735
+ }
7736
+ function wait(ms) {
7737
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
7738
+ }
7739
+ async function listen(server, socketPath) {
7740
+ await new Promise((resolve3, reject) => {
7741
+ const onError = (err) => {
7742
+ server.off("listening", onListening);
7743
+ reject(err);
7744
+ };
7745
+ const onListening = () => {
7746
+ server.off("error", onError);
7747
+ resolve3();
7748
+ };
7749
+ server.once("error", onError);
7750
+ server.once("listening", onListening);
7751
+ server.listen(socketPath);
7752
+ });
7753
+ }
7754
+ async function sendSupervisorRequest(toolId, request, opts = {}) {
7755
+ const timeoutMs = opts.timeoutMs ?? 1500;
7756
+ const socketPath = supervisorSocketPath(toolId);
7757
+ return await new Promise((resolve3, reject) => {
7758
+ const socket = createConnection(socketPath);
7759
+ let buffer = "";
7760
+ let settled = false;
7761
+ const finish = (value) => {
7762
+ if (settled)
7763
+ return;
7764
+ settled = true;
7765
+ clearTimeout(timer);
7766
+ socket.destroy();
7767
+ resolve3(value);
7768
+ };
7769
+ const fail = (err) => {
7770
+ if (settled)
7771
+ return;
7772
+ settled = true;
7773
+ clearTimeout(timer);
7774
+ socket.destroy();
7775
+ if (opts.allowMissing && (err.code === "ENOENT" || err.code === "ECONNREFUSED")) {
7776
+ resolve3(undefined);
7777
+ } else {
7778
+ reject(new AccountsError(`could not contact accounts supervisor for ${toolId}: ${err.message}`));
7779
+ }
7780
+ };
7781
+ const timer = setTimeout(() => {
7782
+ fail(Object.assign(new Error(`timed out after ${timeoutMs}ms`), { code: "ETIMEDOUT" }));
7783
+ }, timeoutMs);
7784
+ socket.setEncoding("utf8");
7785
+ socket.once("connect", () => {
7786
+ socket.write(JSON.stringify(request) + `
7787
+ `);
7788
+ });
7789
+ socket.once("error", fail);
7790
+ socket.on("data", (chunk) => {
7791
+ buffer += chunk;
7792
+ const newline = buffer.indexOf(`
7793
+ `);
7794
+ if (newline === -1)
7795
+ return;
7796
+ try {
7797
+ finish(JSON.parse(buffer.slice(0, newline)));
7798
+ } catch (err) {
7799
+ fail(err);
7800
+ }
7801
+ });
7802
+ socket.once("end", () => {
7803
+ if (!settled)
7804
+ fail(new Error("connection closed without a response"));
7805
+ });
7806
+ });
7807
+ }
7808
+ async function runSupervisedTool(initialProfile, tool, initialArgs = [], opts = {}) {
7809
+ const socketPath = supervisorSocketPath(tool.id);
7810
+ const existing = readSupervisorState(tool.id);
7811
+ if (existing && processAlive(existing.pid)) {
7812
+ throw new AccountsError(`an accounts supervisor for ${tool.label} is already running (pid ${existing.pid})`);
7813
+ }
7814
+ removeSupervisorFiles(tool.id);
7815
+ mkdirSync7(supervisorDir(), { recursive: true });
7816
+ const startedAt = nowIso2();
7817
+ const restartDelayMs = opts.restartDelayMs ?? 350;
7818
+ const log = opts.log ?? (() => {
7819
+ return;
7820
+ });
7821
+ const server = createServer();
7822
+ let profile = initialProfile;
7823
+ let childArgs = initialArgs;
7824
+ let child;
7825
+ let stopping = false;
7826
+ let restarting = false;
7827
+ let settled = false;
7828
+ const state = () => ({
7829
+ version: 1,
7830
+ tool: tool.id,
7831
+ profile: profile.name,
7832
+ pid: process.pid,
7833
+ ...child?.pid ? { childPid: child.pid } : {},
7834
+ socketPath,
7835
+ command: [tool.bin, ...childArgs],
7836
+ startedAt,
7837
+ updatedAt: nowIso2()
7838
+ });
7839
+ const persist = () => writeSupervisorState(state());
7840
+ const stopChild = async () => {
7841
+ const target = child;
7842
+ if (!target || target.exitCode !== null)
7843
+ return;
7844
+ await new Promise((resolve3) => {
7845
+ let done2 = false;
7846
+ const finish = () => {
7847
+ if (done2)
7848
+ return;
7849
+ done2 = true;
7850
+ clearTimeout(killTimer);
7851
+ resolve3();
7852
+ };
7853
+ const killTimer = setTimeout(() => {
7854
+ try {
7855
+ killChildProcess(target, "SIGKILL");
7856
+ } catch {
7857
+ finish();
7858
+ }
7859
+ }, 2500);
7860
+ target.once("exit", finish);
7861
+ try {
7862
+ killChildProcess(target, "SIGTERM");
7863
+ } catch {
7864
+ finish();
7865
+ }
7866
+ });
7867
+ };
7868
+ const cleanup = () => {
7869
+ server.close();
7870
+ removeSupervisorFiles(tool.id);
7871
+ process.off("SIGINT", onSigint);
7872
+ process.off("SIGTERM", onSigterm);
7873
+ };
7874
+ let resolveRun;
7875
+ const done = new Promise((resolve3) => {
7876
+ resolveRun = resolve3;
7877
+ });
7878
+ const finishRun = (code) => {
7879
+ if (settled)
7880
+ return;
7881
+ settled = true;
7882
+ cleanup();
7883
+ resolveRun(code);
7884
+ };
7885
+ const startChild = (nextProfile, nextArgs) => {
7886
+ profile = nextProfile;
7887
+ childArgs = nextArgs;
7888
+ useProfile(profile.name, tool.id);
7889
+ const env2 = profileEnv(profile, tool);
7890
+ log(`accounts supervisor: starting ${tool.bin} for ${profile.name}`);
7891
+ const proc = spawn(tool.bin, childArgs, {
7892
+ stdio: opts.stdio ?? "inherit",
7893
+ env: { ...process.env, ...env2, ACCOUNTS_SUPERVISOR: "1", ACCOUNTS_ACTIVE: profile.name },
7894
+ detached: process.platform !== "win32"
7895
+ });
7896
+ child = proc;
7897
+ persist();
7898
+ proc.once("error", (err) => {
7899
+ log(`accounts supervisor: failed to start ${tool.bin}: ${err.message}`);
7900
+ if (!restarting && !stopping)
7901
+ finishRun(1);
7902
+ });
7903
+ proc.once("exit", (code, signal) => {
7904
+ if (child === proc)
7905
+ child = undefined;
7906
+ persist();
7907
+ if (restarting || stopping)
7908
+ return;
7909
+ finishRun(exitCode(code, signal));
7910
+ });
7911
+ };
7912
+ const restartWith = async (result) => {
7913
+ restarting = true;
7914
+ try {
7915
+ await wait(restartDelayMs);
7916
+ await stopChild();
7917
+ startChild(getProfile(result.profile.name, tool.id), result.command.slice(1));
7918
+ } finally {
7919
+ restarting = false;
7920
+ }
7921
+ };
7922
+ const shutdown = async (code) => {
7923
+ if (stopping)
7924
+ return;
7925
+ stopping = true;
7926
+ await stopChild();
7927
+ finishRun(code);
7928
+ };
7929
+ const handleRequest = async (request) => {
7930
+ if (request.type === "status")
7931
+ return { ok: true, state: state() };
7932
+ if (request.type === "stop") {
7933
+ setTimeout(() => void shutdown(0), 25);
7934
+ return { ok: true, stopping: true, state: state() };
7935
+ }
7936
+ if (request.type !== "switch_profile")
7937
+ return { ok: false, error: "unknown supervisor request" };
7938
+ if (request.tool && request.tool !== tool.id) {
7939
+ return { ok: false, error: `this supervisor runs ${tool.id}, not ${request.tool}` };
7940
+ }
7941
+ try {
7942
+ const result = switchProfile(request.name, {
7943
+ tool: tool.id,
7944
+ mode: request.mode ?? "auto",
7945
+ resume: request.resume ?? true,
7946
+ args: request.args ?? []
7947
+ });
7948
+ log(`accounts supervisor: switching ${tool.id} to ${result.profile.name}`);
7949
+ setTimeout(() => void restartWith(result), 0);
7950
+ return { ok: true, queued: true, result, state: state(), restartDelayMs };
7951
+ } catch (err) {
7952
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
7953
+ }
7954
+ };
7955
+ server.on("connection", (socket) => {
7956
+ socket.setEncoding("utf8");
7957
+ let buffer = "";
7958
+ socket.on("data", (chunk) => {
7959
+ buffer += chunk;
7960
+ const newline = buffer.indexOf(`
7961
+ `);
7962
+ if (newline === -1)
7963
+ return;
7964
+ const line = buffer.slice(0, newline);
7965
+ buffer = buffer.slice(newline + 1);
7966
+ (async () => {
7967
+ let response;
7968
+ try {
7969
+ response = await handleRequest(JSON.parse(line));
7970
+ } catch (err) {
7971
+ response = { ok: false, error: err instanceof Error ? err.message : String(err) };
7972
+ }
7973
+ socket.end(JSON.stringify(response) + `
7974
+ `);
7975
+ })();
7976
+ });
7977
+ });
7978
+ const onSigint = () => void shutdown(130);
7979
+ const onSigterm = () => void shutdown(143);
7980
+ process.once("SIGINT", onSigint);
7981
+ process.once("SIGTERM", onSigterm);
7982
+ await listen(server, socketPath);
7983
+ startChild(profile, childArgs);
7984
+ return await done;
7985
+ }
7986
+
7626
7987
  // src/cli.ts
7627
7988
  var program2 = new Command;
7628
7989
  function die(message) {
@@ -7632,7 +7993,11 @@ function die(message) {
7632
7993
  function action(fn) {
7633
7994
  return (...args) => {
7634
7995
  try {
7635
- fn(...args);
7996
+ Promise.resolve(fn(...args)).catch((err) => {
7997
+ if (err instanceof AccountsError)
7998
+ die(err.message);
7999
+ throw err;
8000
+ });
7636
8001
  } catch (err) {
7637
8002
  if (err instanceof AccountsError)
7638
8003
  die(err.message);
@@ -7685,7 +8050,7 @@ program2.command("show").argument("<name>", "profile name").description("show fu
7685
8050
  console.log(` tool: ${p.tool} (${getTool(p.tool).label})`);
7686
8051
  console.log(` active: ${active ? source_default.green("yes") : source_default.dim("no")}`);
7687
8052
  console.log(` applied: ${isApplied ? source_default.magenta("yes") : source_default.dim("no")}`);
7688
- console.log(` config dir: ${p.dir}${existsSync9(p.dir) ? "" : source_default.red(" [missing]")}`);
8053
+ console.log(` config dir: ${p.dir}${existsSync10(p.dir) ? "" : source_default.red(" [missing]")}`);
7689
8054
  console.log(` email: ${p.email ?? source_default.dim("(none)")}`);
7690
8055
  console.log(` created: ${p.createdAt}`);
7691
8056
  if (p.lastUsedAt)
@@ -7778,7 +8143,27 @@ program2.command("applied").argument("[tool]", "tool id (default: claude)").desc
7778
8143
  die(`no applied profile for "${tool}". Run \`accounts apply <name>\` first.`);
7779
8144
  console.log(p.name);
7780
8145
  }));
7781
- program2.command("switch").argument("<name>", "profile name").argument("[args...]", "extra args passed when printing/launching the tool").description("switch to a profile and print a restart/resume command; use --launch to run it").option("-t, --tool <tool>", "tool when the profile name exists for multiple tools").option("--mode <mode>", "switch mode: auto, apply, env, active", "auto").option("--resume", "include the tool's resume/continue args in the handoff command").option("--launch", "launch the tool after switching").option("--json", "output JSON").action(action((name, args, opts) => {
8146
+ program2.command("switch").argument("<name>", "profile name").argument("[args...]", "extra args passed when printing/launching the tool").description("switch to a profile and print a restart/resume command; use --launch to run it").option("-t, --tool <tool>", "tool when the profile name exists for multiple tools").option("--mode <mode>", "switch mode: auto, apply, env, active", "auto").option("--resume", "include the tool's resume/continue args in the handoff command").option("--launch", "launch the tool after switching").option("--supervisor", "ask a running accounts supervisor to restart the tool").option("--json", "output JSON").action(action(async (name, args, opts) => {
8147
+ if (opts.supervisor && opts.launch)
8148
+ die("--supervisor and --launch cannot be used together");
8149
+ if (opts.supervisor) {
8150
+ const profile = getProfile(name, opts.tool);
8151
+ const response = await sendSupervisorRequest(profile.tool, { type: "switch_profile", name: profile.name, tool: profile.tool, mode: opts.mode, resume: opts.resume ?? true, args }, { allowMissing: true });
8152
+ if (!response) {
8153
+ die(`no running accounts supervisor for ${getTool(profile.tool).label}. Start one with \`accounts run ${profile.tool}\`.`);
8154
+ }
8155
+ if (!response.ok)
8156
+ die(response.error);
8157
+ if (opts.json) {
8158
+ console.log(JSON.stringify(response, null, 2));
8159
+ } else if ("queued" in response) {
8160
+ console.log(source_default.green(`✓ queued supervisor switch to ${source_default.bold(response.result.profile.name)}`));
8161
+ console.log(source_default.dim(` ${response.state.command.join(" ")} will restart in ${response.restartDelayMs}ms`));
8162
+ } else {
8163
+ console.log(source_default.green("✓ supervisor responded"));
8164
+ }
8165
+ return;
8166
+ }
7782
8167
  const result = switchProfile(name, { tool: opts.tool, mode: opts.mode, resume: opts.resume, args });
7783
8168
  if (opts.json) {
7784
8169
  console.log(JSON.stringify(result, null, 2));
@@ -7803,7 +8188,7 @@ program2.command("switch").argument("<name>", "profile name").argument("[args...
7803
8188
  }
7804
8189
  }));
7805
8190
  var hook = program2.command("hook").description("install a shell wrapper for claude");
7806
- hook.command("install").description(`write ${join10(accountsHome(), "claude-hook.sh")}`).action(action(() => {
8191
+ hook.command("install").description(`write ${join11(accountsHome(), "claude-hook.sh")}`).action(action(() => {
7807
8192
  const { path, created } = installHook();
7808
8193
  console.log(source_default.green(created ? `✓ installed hook at ${path}` : `✓ updated hook at ${path}`));
7809
8194
  console.log(source_default.dim(` add to ~/.zshrc: ${shellSnippet()}`));
@@ -7825,7 +8210,7 @@ program2.command("env").argument("[name]", "profile name (defaults to the active
7825
8210
  const tool = getTool(profile.tool);
7826
8211
  console.log(formatExportLines(profileEnv(profile, tool)));
7827
8212
  }));
7828
- program2.command("launch").alias("run").argument("<name>", "profile name").argument("[args...]", "extra args passed to the tool binary").description("launch the tool's binary with the profile's config dir active").option("-t, --tool <tool>", "tool when the profile name exists for multiple tools").action(action((name, args, opts) => {
8213
+ program2.command("launch").argument("<name>", "profile name").argument("[args...]", "extra args passed to the tool binary").description("launch the tool's binary with the profile's config dir active").option("-t, --tool <tool>", "tool when the profile name exists for multiple tools").action(action((name, args, opts) => {
7829
8214
  const profile = getProfile(name, opts.tool);
7830
8215
  const tool = getTool(profile.tool);
7831
8216
  const env2 = profileEnv(profile, tool);
@@ -7839,6 +8224,65 @@ program2.command("launch").alias("run").argument("<name>", "profile name").argum
7839
8224
  die(`failed to launch ${tool.bin}: ${res.error.message}`);
7840
8225
  process.exit(res.status ?? 0);
7841
8226
  }));
8227
+ program2.command("run").argument("<target>", "tool id to supervise (claude, codex, opencode...) or a profile name").argument("[args...]", "extra args passed to the tool binary").description("run a tool under the accounts supervisor so MCP can switch/restart it").option("-p, --profile <name>", "profile to run when target is a tool id").option("-t, --tool <tool>", "tool when target is a profile name").option("--resume", "start with the tool's resume/continue args").action(action(async (target, args, opts) => {
8228
+ const plan = resolveSupervisorLaunch(target, { profile: opts.profile, tool: opts.tool });
8229
+ const runArgs = [...opts.resume ? plan.tool.resumeArgs ?? [] : [], ...args];
8230
+ console.error(source_default.green(`✓ accounts supervisor running ${plan.tool.label} as ${source_default.bold(plan.profile.name)}`));
8231
+ console.error(source_default.dim(` control: accounts supervisor status ${plan.tool.id}`));
8232
+ console.error(source_default.dim(` switch: accounts switch <profile> --tool ${plan.tool.id} --supervisor`));
8233
+ const code = await runSupervisedTool(plan.profile, plan.tool, runArgs, {
8234
+ log: (message) => console.error(source_default.dim(message))
8235
+ });
8236
+ process.exit(code);
8237
+ }));
8238
+ var supervisor = program2.command("supervisor").description("inspect and control accounts-run supervisors");
8239
+ supervisor.command("status").argument("[tool]", "tool id").description("show running supervisor state").option("--json", "output JSON").action(action(async (toolId, opts) => {
8240
+ const state = toolId ? readSupervisorState(toolId) : undefined;
8241
+ const states = toolId ? state ? [state] : [] : listSupervisorStates();
8242
+ const live = [];
8243
+ for (const state2 of states) {
8244
+ const response = await sendSupervisorRequest(state2.tool, { type: "status" }, { allowMissing: true });
8245
+ live.push(response?.ok && "state" in response ? response.state : { ...state2, stale: true });
8246
+ }
8247
+ if (opts.json) {
8248
+ console.log(JSON.stringify(live, null, 2));
8249
+ return;
8250
+ }
8251
+ if (live.length === 0) {
8252
+ console.log(source_default.dim("no accounts supervisors running"));
8253
+ return;
8254
+ }
8255
+ for (const state2 of live) {
8256
+ const stale = "stale" in state2 ? source_default.yellow(" stale") : "";
8257
+ const child = state2.childPid ? ` child:${state2.childPid}` : "";
8258
+ console.log(`${source_default.cyan(state2.tool.padEnd(10))} ${source_default.bold(state2.profile)} pid:${state2.pid}${child}${stale}`);
8259
+ console.log(source_default.dim(` ${state2.command.join(" ")}`));
8260
+ }
8261
+ }));
8262
+ supervisor.command("switch").argument("<name>", "profile name").argument("[args...]", "extra args passed after resume/continue args").description("switch a running supervisor to another profile").option("-t, --tool <tool>", "tool when the profile name exists for multiple tools").option("--mode <mode>", "switch mode: auto, apply, env, active", "auto").option("--no-resume", "restart without the tool's resume/continue args").option("--json", "output JSON").action(action(async (name, args, opts) => {
8263
+ const profile = getProfile(name, opts.tool);
8264
+ const response = await sendSupervisorRequest(profile.tool, { type: "switch_profile", name: profile.name, tool: profile.tool, mode: opts.mode, resume: opts.resume !== false, args }, { allowMissing: true });
8265
+ if (!response)
8266
+ die(`no running accounts supervisor for ${getTool(profile.tool).label}`);
8267
+ if (!response.ok)
8268
+ die(response.error);
8269
+ if (opts.json) {
8270
+ console.log(JSON.stringify(response, null, 2));
8271
+ return;
8272
+ }
8273
+ if ("queued" in response) {
8274
+ console.log(source_default.green(`✓ queued supervisor switch to ${source_default.bold(response.result.profile.name)}`));
8275
+ console.log(source_default.dim(` restart command: ${response.result.commandLine}`));
8276
+ }
8277
+ }));
8278
+ supervisor.command("stop").argument("<tool>", "tool id").description("stop a running supervisor and its child process").action(action(async (toolId) => {
8279
+ const response = await sendSupervisorRequest(toolId, { type: "stop" }, { allowMissing: true });
8280
+ if (!response)
8281
+ die(`no running accounts supervisor for ${toolId}`);
8282
+ if (!response.ok)
8283
+ die(response.error);
8284
+ console.log(source_default.green(`✓ stopping ${toolId} supervisor`));
8285
+ }));
7842
8286
  program2.command("shell").argument("<name>", "profile name").description("open a subshell with the profile's config dir active").option("-t, --tool <tool>", "tool when the profile name exists for multiple tools").action(action((name, opts) => {
7843
8287
  const profile = getProfile(name, opts.tool);
7844
8288
  const tool = getTool(profile.tool);
@@ -7901,7 +8345,7 @@ tools.command("list", { isDefault: true }).description("list supported tools (bu
7901
8345
  console.log(`${source_default.cyan(t.id.padEnd(10))} ${t.label.padEnd(16)} ${source_default.dim(envNames)} → ${source_default.dim(t.defaultDir)} ${tag}`);
7902
8346
  }
7903
8347
  }));
7904
- tools.command("add").argument("<id>", "tool id, e.g. cursor").description("register a custom tool/app so profiles can target it").requiredOption("--label <label>", 'display name, e.g. "Cursor"').requiredOption("--env-var <VAR>", "env var that points the tool at its config dir").requiredOption("--bin <bin>", "binary to launch").option("--default-dir <path>", "default config dir (default: ~/.<id>)").option("--extra-env <VAR=VALUE...>", "additional env var templates; supports {profileDir}, {profileName}, {toolId}").option("--login-arg <arg...>", "arguments for `accounts login <profile> --tool <id>`").option("--account-file <file>", "file inside the config dir holding the email").option("--email-path <path>", "dot-path to the email inside that file (e.g. account.email)").action(action((id, opts) => {
8348
+ tools.command("add").argument("<id>", "tool id, e.g. cursor").description("register a custom tool/app so profiles can target it").requiredOption("--label <label>", 'display name, e.g. "Cursor"').requiredOption("--env-var <VAR>", "env var that points the tool at its config dir").requiredOption("--bin <bin>", "binary to launch").option("--default-dir <path>", "default config dir (default: ~/.<id>)").option("--extra-env <VAR=VALUE...>", "additional env var templates; supports {profileDir}, {profileName}, {toolId}").option("--login-arg <arg...>", "arguments for `accounts login <profile> --tool <id>`").option("--resume-arg <arg...>", "arguments for supervised resume/restart, e.g. --continue").option("--account-file <file>", "file inside the config dir holding the email").option("--email-path <path>", "dot-path to the email inside that file (e.g. account.email)").action(action((id, opts) => {
7905
8349
  const extraEnv = {};
7906
8350
  for (const entry of opts.extraEnv ?? []) {
7907
8351
  const idx = entry.indexOf("=");
@@ -7914,9 +8358,10 @@ tools.command("add").argument("<id>", "tool id, e.g. cursor").description("regis
7914
8358
  label: opts.label,
7915
8359
  envVar: opts.envVar,
7916
8360
  bin: opts.bin,
7917
- defaultDir: opts.defaultDir ? expandPath(opts.defaultDir) : join10(homedir5(), `.${id}`),
8361
+ defaultDir: opts.defaultDir ? expandPath(opts.defaultDir) : join11(homedir5(), `.${id}`),
7918
8362
  ...Object.keys(extraEnv).length > 0 ? { extraEnv } : {},
7919
8363
  ...opts.loginArg ? { loginArgs: opts.loginArg } : {},
8364
+ ...opts.resumeArg ? { resumeArgs: opts.resumeArg } : {},
7920
8365
  ...opts.accountFile ? { accountFile: opts.accountFile } : {},
7921
8366
  ...opts.emailPath ? { emailPath: opts.emailPath.split(".") } : {}
7922
8367
  };
@@ -7934,7 +8379,7 @@ program2.command("doctor").description("check the store and profile dirs for pro
7934
8379
  const profiles = listProfiles();
7935
8380
  let problems = 0;
7936
8381
  for (const p of profiles) {
7937
- const missing = !existsSync9(p.dir);
8382
+ const missing = !existsSync10(p.dir);
7938
8383
  const noEmail = !p.email;
7939
8384
  if (missing) {
7940
8385
  console.log(source_default.red(` ✗ ${p.name}: config dir missing (${p.dir})`));
@@ -7982,9 +8427,9 @@ program2.parseAsync(process.argv);
7982
8427
  function getVersion() {
7983
8428
  try {
7984
8429
  const here = dirname5(fileURLToPath(import.meta.url));
7985
- for (const candidate of [join10(here, "..", "package.json"), join10(here, "package.json")]) {
7986
- if (existsSync9(candidate)) {
7987
- const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
8430
+ for (const candidate of [join11(here, "..", "package.json"), join11(here, "package.json")]) {
8431
+ if (existsSync10(candidate)) {
8432
+ const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
7988
8433
  if (pkg.version)
7989
8434
  return pkg.version;
7990
8435
  }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export { finalizeLogin } from "./lib/login.js";
12
12
  export type { FinalizeLoginResult } from "./lib/login.js";
13
13
  export { switchProfile } from "./lib/switch.js";
14
14
  export type { SwitchMode, SwitchOptions, SwitchResult } from "./lib/switch.js";
15
+ export { listSupervisorStates, readSupervisorState, resolveSupervisorLaunch, runSupervisedTool, sendSupervisorRequest, supervisorDir, supervisorSocketPath, supervisorStatePath, } from "./lib/supervisor.js";
16
+ export type { RunSupervisorOptions, SupervisorClientOptions, SupervisorLaunchPlan, SupervisorRequest, SupervisorResponse, SupervisorState, } from "./lib/supervisor.js";
15
17
  export { pickProfile } from "./lib/pick.js";
16
18
  export type { PickOptions, PickResult } from "./lib/pick.js";
17
19
  export { installHook, uninstallHook, hookPath, hookScript, shellSnippet } from "./lib/hook.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EACL,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EACL,UAAU,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EACL,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACT,aAAa,EACb,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EACL,UAAU,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}