@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/dist/index.js CHANGED
@@ -4938,6 +4938,366 @@ function switchProfile(name, opts = {}) {
4938
4938
  message
4939
4939
  };
4940
4940
  }
4941
+ // src/lib/supervisor.ts
4942
+ import { spawn } from "node:child_process";
4943
+ import { createHash } from "node:crypto";
4944
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
4945
+ import { createConnection, createServer } from "node:net";
4946
+ import { basename, join as join9 } from "node:path";
4947
+ var STATE_SUFFIX = ".json";
4948
+ function supervisorDir() {
4949
+ return join9(accountsHome(), "supervisors");
4950
+ }
4951
+ function supervisorStatePath(toolId) {
4952
+ return join9(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
4953
+ }
4954
+ function supervisorSocketPath(toolId) {
4955
+ if (process.platform === "win32") {
4956
+ const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
4957
+ return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
4958
+ }
4959
+ return join9(supervisorDir(), `${toolId}.sock`);
4960
+ }
4961
+ function nowIso2() {
4962
+ return new Date().toISOString();
4963
+ }
4964
+ function parseState(raw) {
4965
+ const data = JSON.parse(raw);
4966
+ if (data.version !== 1 || typeof data.tool !== "string" || typeof data.profile !== "string" || typeof data.pid !== "number" || typeof data.socketPath !== "string" || !Array.isArray(data.command)) {
4967
+ return;
4968
+ }
4969
+ return data;
4970
+ }
4971
+ function readSupervisorState(toolId) {
4972
+ const path = supervisorStatePath(toolId);
4973
+ if (!existsSync8(path))
4974
+ return;
4975
+ try {
4976
+ return parseState(readFileSync4(path, "utf8"));
4977
+ } catch {
4978
+ return;
4979
+ }
4980
+ }
4981
+ function listSupervisorStates() {
4982
+ const dir = supervisorDir();
4983
+ if (!existsSync8(dir))
4984
+ return [];
4985
+ return readdirSync(dir).filter((name) => name.endsWith(STATE_SUFFIX)).map((name) => basename(name, STATE_SUFFIX)).map((toolId) => readSupervisorState(toolId)).filter((state) => state !== undefined);
4986
+ }
4987
+ function writeSupervisorState(state) {
4988
+ mkdirSync6(supervisorDir(), { recursive: true });
4989
+ writeFileSync4(supervisorStatePath(state.tool), JSON.stringify(state, null, 2) + `
4990
+ `, { mode: 384 });
4991
+ }
4992
+ function removeSupervisorFiles(toolId) {
4993
+ rmSync2(supervisorStatePath(toolId), { force: true });
4994
+ if (process.platform !== "win32")
4995
+ rmSync2(supervisorSocketPath(toolId), { force: true });
4996
+ }
4997
+ function processAlive(pid) {
4998
+ try {
4999
+ process.kill(pid, 0);
5000
+ return true;
5001
+ } catch {
5002
+ return false;
5003
+ }
5004
+ }
5005
+ function knownTool(id) {
5006
+ try {
5007
+ return getTool(id);
5008
+ } catch {
5009
+ return;
5010
+ }
5011
+ }
5012
+ function resolveSupervisorLaunch(target, opts = {}) {
5013
+ const targetTool = knownTool(target);
5014
+ if (opts.profile) {
5015
+ const profile2 = getProfile(opts.profile, opts.tool ?? targetTool?.id);
5016
+ if (targetTool && profile2.tool !== targetTool.id) {
5017
+ throw new AccountsError(`profile "${profile2.name}" belongs to ${profile2.tool}, not ${targetTool.id}`);
5018
+ }
5019
+ return { profile: profile2, tool: getTool(profile2.tool), targetKind: targetTool ? "tool" : "profile" };
5020
+ }
5021
+ if (targetTool && !opts.tool) {
5022
+ const profile2 = currentProfile(targetTool.id) ?? appliedProfile(targetTool.id);
5023
+ if (!profile2) {
5024
+ throw new AccountsError(`no active ${targetTool.label} profile. Run \`accounts use <name> --tool ${targetTool.id}\` or pass --profile.`);
5025
+ }
5026
+ return { profile: profile2, tool: targetTool, targetKind: "tool" };
5027
+ }
5028
+ const profile = getProfile(target, opts.tool);
5029
+ return { profile, tool: getTool(profile.tool), targetKind: "profile" };
5030
+ }
5031
+ function exitCode(code, signal) {
5032
+ if (code !== null)
5033
+ return code;
5034
+ if (signal === "SIGINT")
5035
+ return 130;
5036
+ if (signal === "SIGTERM")
5037
+ return 143;
5038
+ return signal ? 1 : 0;
5039
+ }
5040
+ function killChildProcess(child, signal) {
5041
+ if (!child.pid)
5042
+ return;
5043
+ if (process.platform !== "win32") {
5044
+ try {
5045
+ process.kill(-child.pid, signal);
5046
+ return;
5047
+ } catch {}
5048
+ }
5049
+ child.kill(signal);
5050
+ }
5051
+ function wait(ms) {
5052
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
5053
+ }
5054
+ async function listen(server, socketPath) {
5055
+ await new Promise((resolve3, reject) => {
5056
+ const onError = (err) => {
5057
+ server.off("listening", onListening);
5058
+ reject(err);
5059
+ };
5060
+ const onListening = () => {
5061
+ server.off("error", onError);
5062
+ resolve3();
5063
+ };
5064
+ server.once("error", onError);
5065
+ server.once("listening", onListening);
5066
+ server.listen(socketPath);
5067
+ });
5068
+ }
5069
+ async function sendSupervisorRequest(toolId, request, opts = {}) {
5070
+ const timeoutMs = opts.timeoutMs ?? 1500;
5071
+ const socketPath = supervisorSocketPath(toolId);
5072
+ return await new Promise((resolve3, reject) => {
5073
+ const socket = createConnection(socketPath);
5074
+ let buffer = "";
5075
+ let settled = false;
5076
+ const finish = (value) => {
5077
+ if (settled)
5078
+ return;
5079
+ settled = true;
5080
+ clearTimeout(timer);
5081
+ socket.destroy();
5082
+ resolve3(value);
5083
+ };
5084
+ const fail = (err) => {
5085
+ if (settled)
5086
+ return;
5087
+ settled = true;
5088
+ clearTimeout(timer);
5089
+ socket.destroy();
5090
+ if (opts.allowMissing && (err.code === "ENOENT" || err.code === "ECONNREFUSED")) {
5091
+ resolve3(undefined);
5092
+ } else {
5093
+ reject(new AccountsError(`could not contact accounts supervisor for ${toolId}: ${err.message}`));
5094
+ }
5095
+ };
5096
+ const timer = setTimeout(() => {
5097
+ fail(Object.assign(new Error(`timed out after ${timeoutMs}ms`), { code: "ETIMEDOUT" }));
5098
+ }, timeoutMs);
5099
+ socket.setEncoding("utf8");
5100
+ socket.once("connect", () => {
5101
+ socket.write(JSON.stringify(request) + `
5102
+ `);
5103
+ });
5104
+ socket.once("error", fail);
5105
+ socket.on("data", (chunk) => {
5106
+ buffer += chunk;
5107
+ const newline = buffer.indexOf(`
5108
+ `);
5109
+ if (newline === -1)
5110
+ return;
5111
+ try {
5112
+ finish(JSON.parse(buffer.slice(0, newline)));
5113
+ } catch (err) {
5114
+ fail(err);
5115
+ }
5116
+ });
5117
+ socket.once("end", () => {
5118
+ if (!settled)
5119
+ fail(new Error("connection closed without a response"));
5120
+ });
5121
+ });
5122
+ }
5123
+ async function runSupervisedTool(initialProfile, tool, initialArgs = [], opts = {}) {
5124
+ const socketPath = supervisorSocketPath(tool.id);
5125
+ const existing = readSupervisorState(tool.id);
5126
+ if (existing && processAlive(existing.pid)) {
5127
+ throw new AccountsError(`an accounts supervisor for ${tool.label} is already running (pid ${existing.pid})`);
5128
+ }
5129
+ removeSupervisorFiles(tool.id);
5130
+ mkdirSync6(supervisorDir(), { recursive: true });
5131
+ const startedAt = nowIso2();
5132
+ const restartDelayMs = opts.restartDelayMs ?? 350;
5133
+ const log = opts.log ?? (() => {
5134
+ return;
5135
+ });
5136
+ const server = createServer();
5137
+ let profile = initialProfile;
5138
+ let childArgs = initialArgs;
5139
+ let child;
5140
+ let stopping = false;
5141
+ let restarting = false;
5142
+ let settled = false;
5143
+ const state = () => ({
5144
+ version: 1,
5145
+ tool: tool.id,
5146
+ profile: profile.name,
5147
+ pid: process.pid,
5148
+ ...child?.pid ? { childPid: child.pid } : {},
5149
+ socketPath,
5150
+ command: [tool.bin, ...childArgs],
5151
+ startedAt,
5152
+ updatedAt: nowIso2()
5153
+ });
5154
+ const persist = () => writeSupervisorState(state());
5155
+ const stopChild = async () => {
5156
+ const target = child;
5157
+ if (!target || target.exitCode !== null)
5158
+ return;
5159
+ await new Promise((resolve3) => {
5160
+ let done2 = false;
5161
+ const finish = () => {
5162
+ if (done2)
5163
+ return;
5164
+ done2 = true;
5165
+ clearTimeout(killTimer);
5166
+ resolve3();
5167
+ };
5168
+ const killTimer = setTimeout(() => {
5169
+ try {
5170
+ killChildProcess(target, "SIGKILL");
5171
+ } catch {
5172
+ finish();
5173
+ }
5174
+ }, 2500);
5175
+ target.once("exit", finish);
5176
+ try {
5177
+ killChildProcess(target, "SIGTERM");
5178
+ } catch {
5179
+ finish();
5180
+ }
5181
+ });
5182
+ };
5183
+ const cleanup = () => {
5184
+ server.close();
5185
+ removeSupervisorFiles(tool.id);
5186
+ process.off("SIGINT", onSigint);
5187
+ process.off("SIGTERM", onSigterm);
5188
+ };
5189
+ let resolveRun;
5190
+ const done = new Promise((resolve3) => {
5191
+ resolveRun = resolve3;
5192
+ });
5193
+ const finishRun = (code) => {
5194
+ if (settled)
5195
+ return;
5196
+ settled = true;
5197
+ cleanup();
5198
+ resolveRun(code);
5199
+ };
5200
+ const startChild = (nextProfile, nextArgs) => {
5201
+ profile = nextProfile;
5202
+ childArgs = nextArgs;
5203
+ useProfile(profile.name, tool.id);
5204
+ const env = profileEnv(profile, tool);
5205
+ log(`accounts supervisor: starting ${tool.bin} for ${profile.name}`);
5206
+ const proc = spawn(tool.bin, childArgs, {
5207
+ stdio: opts.stdio ?? "inherit",
5208
+ env: { ...process.env, ...env, ACCOUNTS_SUPERVISOR: "1", ACCOUNTS_ACTIVE: profile.name },
5209
+ detached: process.platform !== "win32"
5210
+ });
5211
+ child = proc;
5212
+ persist();
5213
+ proc.once("error", (err) => {
5214
+ log(`accounts supervisor: failed to start ${tool.bin}: ${err.message}`);
5215
+ if (!restarting && !stopping)
5216
+ finishRun(1);
5217
+ });
5218
+ proc.once("exit", (code, signal) => {
5219
+ if (child === proc)
5220
+ child = undefined;
5221
+ persist();
5222
+ if (restarting || stopping)
5223
+ return;
5224
+ finishRun(exitCode(code, signal));
5225
+ });
5226
+ };
5227
+ const restartWith = async (result) => {
5228
+ restarting = true;
5229
+ try {
5230
+ await wait(restartDelayMs);
5231
+ await stopChild();
5232
+ startChild(getProfile(result.profile.name, tool.id), result.command.slice(1));
5233
+ } finally {
5234
+ restarting = false;
5235
+ }
5236
+ };
5237
+ const shutdown = async (code) => {
5238
+ if (stopping)
5239
+ return;
5240
+ stopping = true;
5241
+ await stopChild();
5242
+ finishRun(code);
5243
+ };
5244
+ const handleRequest = async (request) => {
5245
+ if (request.type === "status")
5246
+ return { ok: true, state: state() };
5247
+ if (request.type === "stop") {
5248
+ setTimeout(() => void shutdown(0), 25);
5249
+ return { ok: true, stopping: true, state: state() };
5250
+ }
5251
+ if (request.type !== "switch_profile")
5252
+ return { ok: false, error: "unknown supervisor request" };
5253
+ if (request.tool && request.tool !== tool.id) {
5254
+ return { ok: false, error: `this supervisor runs ${tool.id}, not ${request.tool}` };
5255
+ }
5256
+ try {
5257
+ const result = switchProfile(request.name, {
5258
+ tool: tool.id,
5259
+ mode: request.mode ?? "auto",
5260
+ resume: request.resume ?? true,
5261
+ args: request.args ?? []
5262
+ });
5263
+ log(`accounts supervisor: switching ${tool.id} to ${result.profile.name}`);
5264
+ setTimeout(() => void restartWith(result), 0);
5265
+ return { ok: true, queued: true, result, state: state(), restartDelayMs };
5266
+ } catch (err) {
5267
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
5268
+ }
5269
+ };
5270
+ server.on("connection", (socket) => {
5271
+ socket.setEncoding("utf8");
5272
+ let buffer = "";
5273
+ socket.on("data", (chunk) => {
5274
+ buffer += chunk;
5275
+ const newline = buffer.indexOf(`
5276
+ `);
5277
+ if (newline === -1)
5278
+ return;
5279
+ const line = buffer.slice(0, newline);
5280
+ buffer = buffer.slice(newline + 1);
5281
+ (async () => {
5282
+ let response;
5283
+ try {
5284
+ response = await handleRequest(JSON.parse(line));
5285
+ } catch (err) {
5286
+ response = { ok: false, error: err instanceof Error ? err.message : String(err) };
5287
+ }
5288
+ socket.end(JSON.stringify(response) + `
5289
+ `);
5290
+ })();
5291
+ });
5292
+ });
5293
+ const onSigint = () => void shutdown(130);
5294
+ const onSigterm = () => void shutdown(143);
5295
+ process.once("SIGINT", onSigint);
5296
+ process.once("SIGTERM", onSigterm);
5297
+ await listen(server, socketPath);
5298
+ startChild(profile, childArgs);
5299
+ return await done;
5300
+ }
4941
5301
  // src/lib/pick.ts
4942
5302
  import * as readline from "node:readline/promises";
4943
5303
  import { stdin as input, stdout as output } from "node:process";
@@ -4971,8 +5331,8 @@ async function pickProfile(opts = {}) {
4971
5331
  return { profile, mode };
4972
5332
  }
4973
5333
  // src/lib/hook.ts
4974
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
4975
- import { join as join9 } from "node:path";
5334
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
5335
+ import { join as join10 } from "node:path";
4976
5336
  var HOOK_FILE = "claude-hook.sh";
4977
5337
  var MARKER = "# accounts-claude-hook";
4978
5338
  var NAME_PATTERN = "^[a-z0-9][a-z0-9-]*$";
@@ -4980,7 +5340,7 @@ function shellQuotePath(path) {
4980
5340
  return `'${path.replace(/'/g, `'\\''`)}'`;
4981
5341
  }
4982
5342
  function hookPath() {
4983
- return join9(accountsHome(), HOOK_FILE);
5343
+ return join10(accountsHome(), HOOK_FILE);
4984
5344
  }
4985
5345
  function hookScript() {
4986
5346
  const quotedHook = shellQuotePath(hookPath());
@@ -5010,16 +5370,16 @@ claude() {
5010
5370
  }
5011
5371
  function installHook() {
5012
5372
  const path = hookPath();
5013
- mkdirSync6(accountsHome(), { recursive: true });
5014
- const created = !existsSync8(path);
5015
- writeFileSync4(path, hookScript(), { mode: 493 });
5373
+ mkdirSync7(accountsHome(), { recursive: true });
5374
+ const created = !existsSync9(path);
5375
+ writeFileSync5(path, hookScript(), { mode: 493 });
5016
5376
  return { path, created };
5017
5377
  }
5018
5378
  function uninstallHook() {
5019
5379
  const path = hookPath();
5020
- if (!existsSync8(path))
5380
+ if (!existsSync9(path))
5021
5381
  return false;
5022
- const content = readFileSync4(path, "utf8");
5382
+ const content = readFileSync5(path, "utf8");
5023
5383
  if (!content.includes(MARKER))
5024
5384
  return false;
5025
5385
  unlinkSync3(path);
@@ -5038,17 +5398,24 @@ export {
5038
5398
  uninstallHook,
5039
5399
  toolDefSchema,
5040
5400
  switchProfile,
5401
+ supervisorStatePath,
5402
+ supervisorSocketPath,
5403
+ supervisorDir,
5041
5404
  storeSchema,
5042
5405
  storePath,
5043
5406
  snapshotLiveAuthToProfile,
5044
5407
  snapshotClaudeAuthToProfile,
5045
5408
  shellSnippet,
5409
+ sendSupervisorRequest,
5046
5410
  saveStore,
5411
+ runSupervisedTool,
5047
5412
  restoreClaudeAuthFromProfile,
5413
+ resolveSupervisorLaunch,
5048
5414
  renameProfile,
5049
5415
  removeProfile,
5050
5416
  removeCustomTool,
5051
5417
  redetectEmail,
5418
+ readSupervisorState,
5052
5419
  readClaudeKeychain,
5053
5420
  profilesDir,
5054
5421
  profileSchema,
@@ -5058,6 +5425,7 @@ export {
5058
5425
  pickProfile,
5059
5426
  loadStore,
5060
5427
  listTools,
5428
+ listSupervisorStates,
5061
5429
  listProfiles,
5062
5430
  keychainSupported,
5063
5431
  isSafeProfileName,
@@ -0,0 +1,69 @@
1
+ import { type StdioOptions } from "node:child_process";
2
+ import type { Profile, ToolDef } from "../types.js";
3
+ import { type SwitchMode, type SwitchResult } from "./switch.js";
4
+ export interface SupervisorState {
5
+ version: 1;
6
+ tool: string;
7
+ profile: string;
8
+ pid: number;
9
+ childPid?: number;
10
+ socketPath: string;
11
+ command: string[];
12
+ startedAt: string;
13
+ updatedAt: string;
14
+ }
15
+ export type SupervisorRequest = {
16
+ type: "status";
17
+ } | {
18
+ type: "switch_profile";
19
+ name: string;
20
+ tool?: string;
21
+ mode?: SwitchMode;
22
+ resume?: boolean;
23
+ args?: string[];
24
+ } | {
25
+ type: "stop";
26
+ };
27
+ export type SupervisorResponse = {
28
+ ok: true;
29
+ state: SupervisorState;
30
+ } | {
31
+ ok: true;
32
+ queued: true;
33
+ result: SwitchResult;
34
+ state: SupervisorState;
35
+ restartDelayMs: number;
36
+ } | {
37
+ ok: true;
38
+ stopping: true;
39
+ state: SupervisorState;
40
+ } | {
41
+ ok: false;
42
+ error: string;
43
+ };
44
+ export interface SupervisorLaunchPlan {
45
+ profile: Profile;
46
+ tool: ToolDef;
47
+ targetKind: "tool" | "profile";
48
+ }
49
+ export interface RunSupervisorOptions {
50
+ stdio?: StdioOptions;
51
+ restartDelayMs?: number;
52
+ log?: (message: string) => void;
53
+ }
54
+ export interface SupervisorClientOptions {
55
+ timeoutMs?: number;
56
+ allowMissing?: boolean;
57
+ }
58
+ export declare function supervisorDir(): string;
59
+ export declare function supervisorStatePath(toolId: string): string;
60
+ export declare function supervisorSocketPath(toolId: string): string;
61
+ export declare function readSupervisorState(toolId: string): SupervisorState | undefined;
62
+ export declare function listSupervisorStates(): SupervisorState[];
63
+ export declare function resolveSupervisorLaunch(target: string, opts?: {
64
+ profile?: string;
65
+ tool?: string;
66
+ }): SupervisorLaunchPlan;
67
+ export declare function sendSupervisorRequest(toolId: string, request: SupervisorRequest, opts?: SupervisorClientOptions): Promise<SupervisorResponse | undefined>;
68
+ export declare function runSupervisedTool(initialProfile: Profile, tool: ToolDef, initialArgs?: string[], opts?: RunSupervisorOptions): Promise<number>;
69
+ //# sourceMappingURL=supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../src/lib/supervisor.ts"],"names":[],"mappings":"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"}
package/dist/mcp.js CHANGED
@@ -17295,6 +17295,103 @@ function switchProfile(name, opts = {}) {
17295
17295
  };
17296
17296
  }
17297
17297
 
17298
+ // src/lib/supervisor.ts
17299
+ import { createHash } from "node:crypto";
17300
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, readdirSync, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
17301
+ import { createConnection, createServer } from "node:net";
17302
+ import { basename, join as join6 } from "node:path";
17303
+ var STATE_SUFFIX = ".json";
17304
+ function supervisorDir() {
17305
+ return join6(accountsHome(), "supervisors");
17306
+ }
17307
+ function supervisorStatePath(toolId) {
17308
+ return join6(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
17309
+ }
17310
+ function supervisorSocketPath(toolId) {
17311
+ if (process.platform === "win32") {
17312
+ const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
17313
+ return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
17314
+ }
17315
+ return join6(supervisorDir(), `${toolId}.sock`);
17316
+ }
17317
+ function parseState(raw) {
17318
+ const data = JSON.parse(raw);
17319
+ if (data.version !== 1 || typeof data.tool !== "string" || typeof data.profile !== "string" || typeof data.pid !== "number" || typeof data.socketPath !== "string" || !Array.isArray(data.command)) {
17320
+ return;
17321
+ }
17322
+ return data;
17323
+ }
17324
+ function readSupervisorState(toolId) {
17325
+ const path = supervisorStatePath(toolId);
17326
+ if (!existsSync5(path))
17327
+ return;
17328
+ try {
17329
+ return parseState(readFileSync3(path, "utf8"));
17330
+ } catch {
17331
+ return;
17332
+ }
17333
+ }
17334
+ function listSupervisorStates() {
17335
+ const dir = supervisorDir();
17336
+ if (!existsSync5(dir))
17337
+ return [];
17338
+ return readdirSync(dir).filter((name) => name.endsWith(STATE_SUFFIX)).map((name) => basename(name, STATE_SUFFIX)).map((toolId) => readSupervisorState(toolId)).filter((state) => state !== undefined);
17339
+ }
17340
+ async function sendSupervisorRequest(toolId, request, opts = {}) {
17341
+ const timeoutMs = opts.timeoutMs ?? 1500;
17342
+ const socketPath = supervisorSocketPath(toolId);
17343
+ return await new Promise((resolve2, reject) => {
17344
+ const socket = createConnection(socketPath);
17345
+ let buffer = "";
17346
+ let settled = false;
17347
+ const finish = (value) => {
17348
+ if (settled)
17349
+ return;
17350
+ settled = true;
17351
+ clearTimeout(timer);
17352
+ socket.destroy();
17353
+ resolve2(value);
17354
+ };
17355
+ const fail = (err) => {
17356
+ if (settled)
17357
+ return;
17358
+ settled = true;
17359
+ clearTimeout(timer);
17360
+ socket.destroy();
17361
+ if (opts.allowMissing && (err.code === "ENOENT" || err.code === "ECONNREFUSED")) {
17362
+ resolve2(undefined);
17363
+ } else {
17364
+ reject(new AccountsError(`could not contact accounts supervisor for ${toolId}: ${err.message}`));
17365
+ }
17366
+ };
17367
+ const timer = setTimeout(() => {
17368
+ fail(Object.assign(new Error(`timed out after ${timeoutMs}ms`), { code: "ETIMEDOUT" }));
17369
+ }, timeoutMs);
17370
+ socket.setEncoding("utf8");
17371
+ socket.once("connect", () => {
17372
+ socket.write(JSON.stringify(request) + `
17373
+ `);
17374
+ });
17375
+ socket.once("error", fail);
17376
+ socket.on("data", (chunk) => {
17377
+ buffer += chunk;
17378
+ const newline = buffer.indexOf(`
17379
+ `);
17380
+ if (newline === -1)
17381
+ return;
17382
+ try {
17383
+ finish(JSON.parse(buffer.slice(0, newline)));
17384
+ } catch (err) {
17385
+ fail(err);
17386
+ }
17387
+ });
17388
+ socket.once("end", () => {
17389
+ if (!settled)
17390
+ fail(new Error("connection closed without a response"));
17391
+ });
17392
+ });
17393
+ }
17394
+
17298
17395
  // src/mcp.ts
17299
17396
  function ok(data) {
17300
17397
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
@@ -17302,7 +17399,7 @@ function ok(data) {
17302
17399
  function fail(message) {
17303
17400
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
17304
17401
  }
17305
- var server = new Server({ name: "accounts", version: "0.1.4" }, { capabilities: { tools: {} } });
17402
+ var server = new Server({ name: "accounts", version: "0.1.5" }, { capabilities: { tools: {} } });
17306
17403
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
17307
17404
  tools: [
17308
17405
  {
@@ -17320,9 +17417,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
17320
17417
  description: "Show active and live/applied profile for a tool.",
17321
17418
  inputSchema: { type: "object", properties: { tool: { type: "string" } }, required: ["tool"] }
17322
17419
  },
17420
+ {
17421
+ name: "supervisor_status",
17422
+ description: "Show accounts-run supervisors that can restart an agent process after profile switches.",
17423
+ inputSchema: { type: "object", properties: { tool: { type: "string" } } }
17424
+ },
17323
17425
  {
17324
17426
  name: "switch_profile",
17325
- description: "Switch to a profile. For Claude this applies live/default auth. Returns restart/resume handoff command; MCP does not kill the parent agent process.",
17427
+ description: "Switch to a profile. If the current agent was started with accounts run, the supervisor restarts it under the new profile; otherwise this returns a restart/resume handoff command.",
17326
17428
  inputSchema: {
17327
17429
  type: "object",
17328
17430
  properties: {
@@ -17351,17 +17453,43 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
17351
17453
  return fail("tool is required");
17352
17454
  return ok({ tool, active: currentProfile(tool) ?? null, applied: appliedProfile(tool) ?? null });
17353
17455
  }
17456
+ case "supervisor_status": {
17457
+ const tool = args["tool"];
17458
+ const states = typeof tool === "string" ? listSupervisorStates().filter((state) => state.tool === tool) : listSupervisorStates();
17459
+ return ok({ supervisors: states });
17460
+ }
17354
17461
  case "switch_profile": {
17355
17462
  const name = args["name"];
17356
17463
  if (typeof name !== "string")
17357
17464
  return fail("name is required");
17465
+ const profile = getProfile(name, typeof args["tool"] === "string" ? args["tool"] : undefined);
17466
+ const resume = args["resume"] !== false;
17467
+ const switchArgs = Array.isArray(args["args"]) ? args["args"].filter((value) => typeof value === "string") : undefined;
17468
+ const supervisor = await sendSupervisorRequest(profile.tool, {
17469
+ type: "switch_profile",
17470
+ name: profile.name,
17471
+ tool: profile.tool,
17472
+ mode: typeof args["mode"] === "string" ? args["mode"] : "auto",
17473
+ resume,
17474
+ args: switchArgs
17475
+ }, { allowMissing: true });
17476
+ if (supervisor) {
17477
+ if (!supervisor.ok)
17478
+ return fail(supervisor.error);
17479
+ return ok({
17480
+ supervised: true,
17481
+ ...supervisor,
17482
+ instruction: "Profile switch queued. The accounts supervisor will close this agent process and restart it under the selected profile."
17483
+ });
17484
+ }
17358
17485
  const result = switchProfile(name, {
17359
- tool: typeof args["tool"] === "string" ? args["tool"] : undefined,
17486
+ tool: profile.tool,
17360
17487
  mode: typeof args["mode"] === "string" ? args["mode"] : "auto",
17361
- resume: args["resume"] === true,
17362
- args: Array.isArray(args["args"]) ? args["args"].filter((value) => typeof value === "string") : undefined
17488
+ resume,
17489
+ args: switchArgs
17363
17490
  });
17364
17491
  return ok({
17492
+ supervised: false,
17365
17493
  ...result,
17366
17494
  instruction: result.restartRequired ? "Exit the current agent session and run commandLine to resume under the selected profile." : "Profile switched."
17367
17495
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/accounts",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Manage and switch between multiple Claude Code (and other AI coding tool) profiles/accounts locally — isolated config dirs, per-account email, one-command switching.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",