@hasna/accounts 0.1.4 → 0.1.6
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 +42 -9
- package/dist/cli.js +493 -13
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +411 -8
- package/dist/lib/supervisor.d.ts +69 -0
- package/dist/lib/supervisor.d.ts.map +1 -0
- package/dist/lib/tools.d.ts.map +1 -1
- package/dist/mcp.js +168 -5
- package/package.json +5 -1
package/dist/mcp.js
CHANGED
|
@@ -16811,6 +16811,25 @@ var BUILTIN_TOOLS = [
|
|
|
16811
16811
|
loginHint: "complete the Codex login flow for this CODEX_HOME",
|
|
16812
16812
|
resumeArgs: ["resume", "--last"]
|
|
16813
16813
|
},
|
|
16814
|
+
{
|
|
16815
|
+
id: "takumi",
|
|
16816
|
+
label: "Takumi",
|
|
16817
|
+
envVar: "TAKUMI_CONFIG_DIR",
|
|
16818
|
+
defaultDir: join2(homedir2(), ".takumi"),
|
|
16819
|
+
bin: "takumi",
|
|
16820
|
+
loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
|
|
16821
|
+
resumeArgs: ["--continue"],
|
|
16822
|
+
accountFile: ".claude.json",
|
|
16823
|
+
emailPath: ["oauthAccount", "emailAddress"]
|
|
16824
|
+
},
|
|
16825
|
+
{
|
|
16826
|
+
id: "gemini",
|
|
16827
|
+
label: "Gemini CLI",
|
|
16828
|
+
envVar: "GEMINI_CONFIG_DIR",
|
|
16829
|
+
defaultDir: join2(homedir2(), ".gemini"),
|
|
16830
|
+
bin: "gemini",
|
|
16831
|
+
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR"
|
|
16832
|
+
},
|
|
16814
16833
|
{
|
|
16815
16834
|
id: "opencode",
|
|
16816
16835
|
label: "opencode",
|
|
@@ -16834,6 +16853,22 @@ var BUILTIN_TOOLS = [
|
|
|
16834
16853
|
loginArgs: ["login"],
|
|
16835
16854
|
loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
|
|
16836
16855
|
},
|
|
16856
|
+
{
|
|
16857
|
+
id: "pi",
|
|
16858
|
+
label: "Pi Coding Agent",
|
|
16859
|
+
envVar: "PI_CODING_AGENT_HOME",
|
|
16860
|
+
defaultDir: join2(homedir2(), ".pi"),
|
|
16861
|
+
bin: "pi",
|
|
16862
|
+
loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
|
|
16863
|
+
},
|
|
16864
|
+
{
|
|
16865
|
+
id: "hermes",
|
|
16866
|
+
label: "Hermes",
|
|
16867
|
+
envVar: "HERMES_HOME",
|
|
16868
|
+
defaultDir: join2(homedir2(), ".hermes"),
|
|
16869
|
+
bin: "hermes",
|
|
16870
|
+
loginHint: "complete Hermes auth in this HERMES_HOME"
|
|
16871
|
+
},
|
|
16837
16872
|
{
|
|
16838
16873
|
id: "kimi",
|
|
16839
16874
|
label: "Kimi Code",
|
|
@@ -17295,6 +17330,103 @@ function switchProfile(name, opts = {}) {
|
|
|
17295
17330
|
};
|
|
17296
17331
|
}
|
|
17297
17332
|
|
|
17333
|
+
// src/lib/supervisor.ts
|
|
17334
|
+
import { createHash } from "node:crypto";
|
|
17335
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, readdirSync, rmSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
17336
|
+
import { createConnection, createServer } from "node:net";
|
|
17337
|
+
import { basename, join as join6 } from "node:path";
|
|
17338
|
+
var STATE_SUFFIX = ".json";
|
|
17339
|
+
function supervisorDir() {
|
|
17340
|
+
return join6(accountsHome(), "supervisors");
|
|
17341
|
+
}
|
|
17342
|
+
function supervisorStatePath(toolId) {
|
|
17343
|
+
return join6(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
|
|
17344
|
+
}
|
|
17345
|
+
function supervisorSocketPath(toolId) {
|
|
17346
|
+
if (process.platform === "win32") {
|
|
17347
|
+
const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
|
|
17348
|
+
return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
|
|
17349
|
+
}
|
|
17350
|
+
return join6(supervisorDir(), `${toolId}.sock`);
|
|
17351
|
+
}
|
|
17352
|
+
function parseState(raw) {
|
|
17353
|
+
const data = JSON.parse(raw);
|
|
17354
|
+
if (data.version !== 1 || typeof data.tool !== "string" || typeof data.profile !== "string" || typeof data.pid !== "number" || typeof data.socketPath !== "string" || !Array.isArray(data.command)) {
|
|
17355
|
+
return;
|
|
17356
|
+
}
|
|
17357
|
+
return data;
|
|
17358
|
+
}
|
|
17359
|
+
function readSupervisorState(toolId) {
|
|
17360
|
+
const path = supervisorStatePath(toolId);
|
|
17361
|
+
if (!existsSync5(path))
|
|
17362
|
+
return;
|
|
17363
|
+
try {
|
|
17364
|
+
return parseState(readFileSync3(path, "utf8"));
|
|
17365
|
+
} catch {
|
|
17366
|
+
return;
|
|
17367
|
+
}
|
|
17368
|
+
}
|
|
17369
|
+
function listSupervisorStates() {
|
|
17370
|
+
const dir = supervisorDir();
|
|
17371
|
+
if (!existsSync5(dir))
|
|
17372
|
+
return [];
|
|
17373
|
+
return readdirSync(dir).filter((name) => name.endsWith(STATE_SUFFIX)).map((name) => basename(name, STATE_SUFFIX)).map((toolId) => readSupervisorState(toolId)).filter((state) => state !== undefined);
|
|
17374
|
+
}
|
|
17375
|
+
async function sendSupervisorRequest(toolId, request, opts = {}) {
|
|
17376
|
+
const timeoutMs = opts.timeoutMs ?? 1500;
|
|
17377
|
+
const socketPath = supervisorSocketPath(toolId);
|
|
17378
|
+
return await new Promise((resolve2, reject) => {
|
|
17379
|
+
const socket = createConnection(socketPath);
|
|
17380
|
+
let buffer = "";
|
|
17381
|
+
let settled = false;
|
|
17382
|
+
const finish = (value) => {
|
|
17383
|
+
if (settled)
|
|
17384
|
+
return;
|
|
17385
|
+
settled = true;
|
|
17386
|
+
clearTimeout(timer);
|
|
17387
|
+
socket.destroy();
|
|
17388
|
+
resolve2(value);
|
|
17389
|
+
};
|
|
17390
|
+
const fail = (err) => {
|
|
17391
|
+
if (settled)
|
|
17392
|
+
return;
|
|
17393
|
+
settled = true;
|
|
17394
|
+
clearTimeout(timer);
|
|
17395
|
+
socket.destroy();
|
|
17396
|
+
if (opts.allowMissing && (err.code === "ENOENT" || err.code === "ECONNREFUSED")) {
|
|
17397
|
+
resolve2(undefined);
|
|
17398
|
+
} else {
|
|
17399
|
+
reject(new AccountsError(`could not contact accounts supervisor for ${toolId}: ${err.message}`));
|
|
17400
|
+
}
|
|
17401
|
+
};
|
|
17402
|
+
const timer = setTimeout(() => {
|
|
17403
|
+
fail(Object.assign(new Error(`timed out after ${timeoutMs}ms`), { code: "ETIMEDOUT" }));
|
|
17404
|
+
}, timeoutMs);
|
|
17405
|
+
socket.setEncoding("utf8");
|
|
17406
|
+
socket.once("connect", () => {
|
|
17407
|
+
socket.write(JSON.stringify(request) + `
|
|
17408
|
+
`);
|
|
17409
|
+
});
|
|
17410
|
+
socket.once("error", fail);
|
|
17411
|
+
socket.on("data", (chunk) => {
|
|
17412
|
+
buffer += chunk;
|
|
17413
|
+
const newline = buffer.indexOf(`
|
|
17414
|
+
`);
|
|
17415
|
+
if (newline === -1)
|
|
17416
|
+
return;
|
|
17417
|
+
try {
|
|
17418
|
+
finish(JSON.parse(buffer.slice(0, newline)));
|
|
17419
|
+
} catch (err) {
|
|
17420
|
+
fail(err);
|
|
17421
|
+
}
|
|
17422
|
+
});
|
|
17423
|
+
socket.once("end", () => {
|
|
17424
|
+
if (!settled)
|
|
17425
|
+
fail(new Error("connection closed without a response"));
|
|
17426
|
+
});
|
|
17427
|
+
});
|
|
17428
|
+
}
|
|
17429
|
+
|
|
17298
17430
|
// src/mcp.ts
|
|
17299
17431
|
function ok(data) {
|
|
17300
17432
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -17302,7 +17434,7 @@ function ok(data) {
|
|
|
17302
17434
|
function fail(message) {
|
|
17303
17435
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
17304
17436
|
}
|
|
17305
|
-
var server = new Server({ name: "accounts", version: "0.1.
|
|
17437
|
+
var server = new Server({ name: "accounts", version: "0.1.6" }, { capabilities: { tools: {} } });
|
|
17306
17438
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
17307
17439
|
tools: [
|
|
17308
17440
|
{
|
|
@@ -17320,9 +17452,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
17320
17452
|
description: "Show active and live/applied profile for a tool.",
|
|
17321
17453
|
inputSchema: { type: "object", properties: { tool: { type: "string" } }, required: ["tool"] }
|
|
17322
17454
|
},
|
|
17455
|
+
{
|
|
17456
|
+
name: "supervisor_status",
|
|
17457
|
+
description: "Show accounts-run supervisors that can restart an agent process after profile switches.",
|
|
17458
|
+
inputSchema: { type: "object", properties: { tool: { type: "string" } } }
|
|
17459
|
+
},
|
|
17323
17460
|
{
|
|
17324
17461
|
name: "switch_profile",
|
|
17325
|
-
description: "Switch to a profile.
|
|
17462
|
+
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
17463
|
inputSchema: {
|
|
17327
17464
|
type: "object",
|
|
17328
17465
|
properties: {
|
|
@@ -17351,17 +17488,43 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
17351
17488
|
return fail("tool is required");
|
|
17352
17489
|
return ok({ tool, active: currentProfile(tool) ?? null, applied: appliedProfile(tool) ?? null });
|
|
17353
17490
|
}
|
|
17491
|
+
case "supervisor_status": {
|
|
17492
|
+
const tool = args["tool"];
|
|
17493
|
+
const states = typeof tool === "string" ? listSupervisorStates().filter((state) => state.tool === tool) : listSupervisorStates();
|
|
17494
|
+
return ok({ supervisors: states });
|
|
17495
|
+
}
|
|
17354
17496
|
case "switch_profile": {
|
|
17355
17497
|
const name = args["name"];
|
|
17356
17498
|
if (typeof name !== "string")
|
|
17357
17499
|
return fail("name is required");
|
|
17500
|
+
const profile = getProfile(name, typeof args["tool"] === "string" ? args["tool"] : undefined);
|
|
17501
|
+
const resume = args["resume"] !== false;
|
|
17502
|
+
const switchArgs = Array.isArray(args["args"]) ? args["args"].filter((value) => typeof value === "string") : undefined;
|
|
17503
|
+
const supervisor = await sendSupervisorRequest(profile.tool, {
|
|
17504
|
+
type: "switch_profile",
|
|
17505
|
+
name: profile.name,
|
|
17506
|
+
tool: profile.tool,
|
|
17507
|
+
mode: typeof args["mode"] === "string" ? args["mode"] : "auto",
|
|
17508
|
+
resume,
|
|
17509
|
+
args: switchArgs
|
|
17510
|
+
}, { allowMissing: true });
|
|
17511
|
+
if (supervisor) {
|
|
17512
|
+
if (!supervisor.ok)
|
|
17513
|
+
return fail(supervisor.error);
|
|
17514
|
+
return ok({
|
|
17515
|
+
supervised: true,
|
|
17516
|
+
...supervisor,
|
|
17517
|
+
instruction: "Profile switch queued. The accounts supervisor will close this agent process and restart it under the selected profile."
|
|
17518
|
+
});
|
|
17519
|
+
}
|
|
17358
17520
|
const result = switchProfile(name, {
|
|
17359
|
-
tool:
|
|
17521
|
+
tool: profile.tool,
|
|
17360
17522
|
mode: typeof args["mode"] === "string" ? args["mode"] : "auto",
|
|
17361
|
-
resume
|
|
17362
|
-
args:
|
|
17523
|
+
resume,
|
|
17524
|
+
args: switchArgs
|
|
17363
17525
|
});
|
|
17364
17526
|
return ok({
|
|
17527
|
+
supervised: false,
|
|
17365
17528
|
...result,
|
|
17366
17529
|
instruction: result.restartRequired ? "Exit the current agent session and run commandLine to resume under the selected profile." : "Profile switched."
|
|
17367
17530
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/accounts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
"claude",
|
|
31
31
|
"claude-code",
|
|
32
32
|
"codex",
|
|
33
|
+
"takumi",
|
|
34
|
+
"gemini",
|
|
33
35
|
"opencode",
|
|
34
36
|
"cursor-agent",
|
|
37
|
+
"pi",
|
|
38
|
+
"hermes",
|
|
35
39
|
"kimi-code",
|
|
36
40
|
"grok-build",
|
|
37
41
|
"profile",
|