@hasna/accounts 0.1.11 → 0.1.13
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 +12 -4
- package/dist/cli.js +203 -20
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +426 -279
- package/dist/lib/claude-auth.d.ts +4 -0
- package/dist/lib/claude-auth.d.ts.map +1 -1
- package/dist/lib/env.d.ts.map +1 -1
- package/dist/lib/supervisor.d.ts +1 -0
- package/dist/lib/supervisor.d.ts.map +1 -1
- package/dist/lib/switch.d.ts +2 -0
- package/dist/lib/switch.d.ts.map +1 -1
- package/dist/lib/tools.d.ts +6 -0
- package/dist/lib/tools.d.ts.map +1 -1
- package/dist/mcp.js +151 -8
- package/dist/storage.js +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3999,6 +3999,7 @@ var toolDefSchema = exports_external.object({
|
|
|
3999
3999
|
loginArgs: exports_external.array(exports_external.string()).optional(),
|
|
4000
4000
|
loginHint: exports_external.string().optional(),
|
|
4001
4001
|
resumeArgs: exports_external.array(exports_external.string()).optional(),
|
|
4002
|
+
permissionArgs: exports_external.record(exports_external.array(exports_external.string())).optional(),
|
|
4002
4003
|
accountFile: exports_external.string().optional(),
|
|
4003
4004
|
emailPath: exports_external.array(exports_external.string()).optional()
|
|
4004
4005
|
});
|
|
@@ -4174,6 +4175,15 @@ var BUILTIN_TOOLS = [
|
|
|
4174
4175
|
bin: "claude",
|
|
4175
4176
|
loginHint: "run /login inside Claude, then /exit when done",
|
|
4176
4177
|
resumeArgs: ["--continue"],
|
|
4178
|
+
permissionArgs: {
|
|
4179
|
+
dangerous: ["--dangerously-skip-permissions"],
|
|
4180
|
+
"allow-dangerous": ["--allow-dangerously-skip-permissions"],
|
|
4181
|
+
bypass: ["--permission-mode", "bypassPermissions"],
|
|
4182
|
+
auto: ["--permission-mode", "auto"],
|
|
4183
|
+
"accept-edits": ["--permission-mode", "acceptEdits"],
|
|
4184
|
+
"dont-ask": ["--permission-mode", "dontAsk"],
|
|
4185
|
+
plan: ["--permission-mode", "plan"]
|
|
4186
|
+
},
|
|
4177
4187
|
accountFile: ".claude.json",
|
|
4178
4188
|
emailPath: ["oauthAccount", "emailAddress"]
|
|
4179
4189
|
},
|
|
@@ -4185,7 +4195,10 @@ var BUILTIN_TOOLS = [
|
|
|
4185
4195
|
bin: "codex",
|
|
4186
4196
|
loginArgs: ["login"],
|
|
4187
4197
|
loginHint: "complete the Codex login flow for this CODEX_HOME",
|
|
4188
|
-
resumeArgs: ["resume", "--last"]
|
|
4198
|
+
resumeArgs: ["resume", "--last"],
|
|
4199
|
+
permissionArgs: {
|
|
4200
|
+
dangerous: ["--dangerously-bypass-approvals-and-sandbox"]
|
|
4201
|
+
}
|
|
4189
4202
|
},
|
|
4190
4203
|
{
|
|
4191
4204
|
id: "takumi",
|
|
@@ -4195,6 +4208,15 @@ var BUILTIN_TOOLS = [
|
|
|
4195
4208
|
bin: "takumi",
|
|
4196
4209
|
loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
|
|
4197
4210
|
resumeArgs: ["--continue"],
|
|
4211
|
+
permissionArgs: {
|
|
4212
|
+
dangerous: ["--dangerously-skip-permissions"],
|
|
4213
|
+
"allow-dangerous": ["--allow-dangerously-skip-permissions"],
|
|
4214
|
+
bypass: ["--permission-mode", "bypassPermissions"],
|
|
4215
|
+
auto: ["--permission-mode", "auto"],
|
|
4216
|
+
"accept-edits": ["--permission-mode", "acceptEdits"],
|
|
4217
|
+
"dont-ask": ["--permission-mode", "dontAsk"],
|
|
4218
|
+
plan: ["--permission-mode", "plan"]
|
|
4219
|
+
},
|
|
4198
4220
|
accountFile: ".claude.json",
|
|
4199
4221
|
emailPath: ["oauthAccount", "emailAddress"]
|
|
4200
4222
|
},
|
|
@@ -4204,7 +4226,13 @@ var BUILTIN_TOOLS = [
|
|
|
4204
4226
|
envVar: "GEMINI_CONFIG_DIR",
|
|
4205
4227
|
defaultDir: join2(homedir2(), ".gemini"),
|
|
4206
4228
|
bin: "gemini",
|
|
4207
|
-
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR"
|
|
4229
|
+
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR",
|
|
4230
|
+
permissionArgs: {
|
|
4231
|
+
dangerous: ["--yolo"],
|
|
4232
|
+
yolo: ["--yolo"],
|
|
4233
|
+
"auto-edit": ["--approval-mode", "auto_edit"],
|
|
4234
|
+
plan: ["--approval-mode", "plan"]
|
|
4235
|
+
}
|
|
4208
4236
|
},
|
|
4209
4237
|
{
|
|
4210
4238
|
id: "opencode",
|
|
@@ -4243,7 +4271,11 @@ var BUILTIN_TOOLS = [
|
|
|
4243
4271
|
envVar: "HERMES_HOME",
|
|
4244
4272
|
defaultDir: join2(homedir2(), ".hermes"),
|
|
4245
4273
|
bin: "hermes",
|
|
4246
|
-
loginHint: "complete Hermes auth in this HERMES_HOME"
|
|
4274
|
+
loginHint: "complete Hermes auth in this HERMES_HOME",
|
|
4275
|
+
permissionArgs: {
|
|
4276
|
+
dangerous: ["--yolo"],
|
|
4277
|
+
yolo: ["--yolo"]
|
|
4278
|
+
}
|
|
4247
4279
|
},
|
|
4248
4280
|
{
|
|
4249
4281
|
id: "kimi",
|
|
@@ -4252,7 +4284,13 @@ var BUILTIN_TOOLS = [
|
|
|
4252
4284
|
defaultDir: join2(homedir2(), ".kimi-code"),
|
|
4253
4285
|
bin: "kimi",
|
|
4254
4286
|
loginArgs: ["login"],
|
|
4255
|
-
loginHint: "complete kimi login for this KIMI_CODE_HOME"
|
|
4287
|
+
loginHint: "complete kimi login for this KIMI_CODE_HOME",
|
|
4288
|
+
permissionArgs: {
|
|
4289
|
+
dangerous: ["--yolo"],
|
|
4290
|
+
yolo: ["--yolo"],
|
|
4291
|
+
auto: ["--auto"],
|
|
4292
|
+
plan: ["--plan"]
|
|
4293
|
+
}
|
|
4256
4294
|
},
|
|
4257
4295
|
{
|
|
4258
4296
|
id: "grok",
|
|
@@ -4265,6 +4303,43 @@ var BUILTIN_TOOLS = [
|
|
|
4265
4303
|
}
|
|
4266
4304
|
];
|
|
4267
4305
|
var DEFAULT_TOOL = "claude";
|
|
4306
|
+
var PERMISSION_ALIASES = new Map([
|
|
4307
|
+
["danger", "dangerous"],
|
|
4308
|
+
["dangerously-skip-permissions", "dangerous"],
|
|
4309
|
+
["skip-permissions", "dangerous"],
|
|
4310
|
+
["skip", "dangerous"],
|
|
4311
|
+
["bypasspermissions", "bypass"],
|
|
4312
|
+
["bypass-permissions", "bypass"],
|
|
4313
|
+
["acceptedits", "accept-edits"],
|
|
4314
|
+
["accept-edit", "accept-edits"],
|
|
4315
|
+
["autoedit", "auto-edit"],
|
|
4316
|
+
["auto-edits", "auto-edit"],
|
|
4317
|
+
["auto_edit", "auto-edit"],
|
|
4318
|
+
["dontask", "dont-ask"],
|
|
4319
|
+
["dont-ask-permissions", "dont-ask"]
|
|
4320
|
+
]);
|
|
4321
|
+
function normalizePermissionPreset(value) {
|
|
4322
|
+
const normalized = value.trim().replace(/^--/, "").replaceAll("_", "-").toLowerCase();
|
|
4323
|
+
return PERMISSION_ALIASES.get(normalized) ?? normalized;
|
|
4324
|
+
}
|
|
4325
|
+
function permissionArgsFor(tool, permissions) {
|
|
4326
|
+
if (!permissions)
|
|
4327
|
+
return [];
|
|
4328
|
+
const preset = normalizePermissionPreset(permissions);
|
|
4329
|
+
if (preset === "default" || preset === "none" || preset === "off")
|
|
4330
|
+
return [];
|
|
4331
|
+
const args = tool.permissionArgs?.[preset];
|
|
4332
|
+
if (!args) {
|
|
4333
|
+
const supported = Object.keys(tool.permissionArgs ?? {}).sort();
|
|
4334
|
+
const suffix = supported.length > 0 ? ` Supported permissions: ${supported.join(", ")}.` : " No permission presets are configured.";
|
|
4335
|
+
throw new AccountsError(`tool "${tool.id}" does not support permissions "${permissions}".${suffix}`);
|
|
4336
|
+
}
|
|
4337
|
+
return args;
|
|
4338
|
+
}
|
|
4339
|
+
function mergeToolArgs(tool, args, opts = {}) {
|
|
4340
|
+
const permissionArgs = permissionArgsFor(tool, opts.permissions).filter((arg) => !args.includes(arg));
|
|
4341
|
+
return [...permissionArgs, ...args];
|
|
4342
|
+
}
|
|
4268
4343
|
var BUILTIN_IDS = new Set(BUILTIN_TOOLS.map((t) => t.id));
|
|
4269
4344
|
function isBuiltinTool(id) {
|
|
4270
4345
|
return BUILTIN_IDS.has(id);
|
|
@@ -4317,256 +4392,13 @@ function removeCustomTool(id) {
|
|
|
4317
4392
|
store.tools.splice(idx, 1);
|
|
4318
4393
|
saveStore(store);
|
|
4319
4394
|
}
|
|
4320
|
-
// src/lib/env.ts
|
|
4321
|
-
function renderTemplate(value, profile) {
|
|
4322
|
-
return value.replaceAll("{profileDir}", profile.dir).replaceAll("{profileName}", profile.name).replaceAll("{toolId}", profile.tool);
|
|
4323
|
-
}
|
|
4324
|
-
function profileEnv(profile, tool) {
|
|
4325
|
-
const env = {
|
|
4326
|
-
[tool.envVar]: profile.dir
|
|
4327
|
-
};
|
|
4328
|
-
for (const [name, value] of Object.entries(tool.extraEnv ?? {})) {
|
|
4329
|
-
env[name] = renderTemplate(value, profile);
|
|
4330
|
-
}
|
|
4331
|
-
return env;
|
|
4332
|
-
}
|
|
4333
|
-
function formatEnvAssignments(env) {
|
|
4334
|
-
return Object.entries(env).map(([name, value]) => `${name}=${JSON.stringify(value)}`).join(" ");
|
|
4335
|
-
}
|
|
4336
|
-
function formatExportLines(env) {
|
|
4337
|
-
return Object.entries(env).map(([name, value]) => `export ${name}=${JSON.stringify(value)}`).join(`
|
|
4338
|
-
`);
|
|
4339
|
-
}
|
|
4340
|
-
// src/lib/detect.ts
|
|
4341
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
4342
|
-
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4343
|
-
function detectEmail(dir, tool) {
|
|
4344
|
-
if (!tool.accountFile || !tool.emailPath)
|
|
4345
|
-
return;
|
|
4346
|
-
const candidates = [join3(dir, tool.accountFile)];
|
|
4347
|
-
if (dir === tool.defaultDir)
|
|
4348
|
-
candidates.push(join3(dirname2(dir), tool.accountFile));
|
|
4349
|
-
for (const file of candidates) {
|
|
4350
|
-
const email = readEmail(file, tool.emailPath);
|
|
4351
|
-
if (email)
|
|
4352
|
-
return email;
|
|
4353
|
-
}
|
|
4354
|
-
return;
|
|
4355
|
-
}
|
|
4356
|
-
function readEmail(file, path) {
|
|
4357
|
-
if (!existsSync3(file))
|
|
4358
|
-
return;
|
|
4359
|
-
let cursor;
|
|
4360
|
-
try {
|
|
4361
|
-
cursor = JSON.parse(readFileSync2(file, "utf8"));
|
|
4362
|
-
} catch {
|
|
4363
|
-
return;
|
|
4364
|
-
}
|
|
4365
|
-
for (const key of path) {
|
|
4366
|
-
if (cursor && typeof cursor === "object" && key in cursor) {
|
|
4367
|
-
cursor = cursor[key];
|
|
4368
|
-
} else {
|
|
4369
|
-
return;
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
return typeof cursor === "string" && cursor.includes("@") ? cursor : undefined;
|
|
4373
|
-
}
|
|
4374
|
-
// src/lib/profiles.ts
|
|
4375
|
-
import { homedir as homedir3 } from "node:os";
|
|
4376
|
-
import { isAbsolute, join as join4, resolve as resolve2 } from "node:path";
|
|
4377
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync } from "node:fs";
|
|
4378
|
-
function nowIso() {
|
|
4379
|
-
return new Date().toISOString();
|
|
4380
|
-
}
|
|
4381
|
-
function expandPath(p) {
|
|
4382
|
-
let out = p;
|
|
4383
|
-
if (out === "~")
|
|
4384
|
-
out = homedir3();
|
|
4385
|
-
else if (out.startsWith("~/"))
|
|
4386
|
-
out = join4(homedir3(), out.slice(2));
|
|
4387
|
-
return isAbsolute(out) ? out : resolve2(process.cwd(), out);
|
|
4388
|
-
}
|
|
4389
|
-
function listProfiles(toolId) {
|
|
4390
|
-
const profiles = loadStore().profiles;
|
|
4391
|
-
const filtered = toolId ? profiles.filter((p) => p.tool === toolId) : profiles;
|
|
4392
|
-
return filtered.slice().sort((a, b) => a.tool.localeCompare(b.tool) || a.name.localeCompare(b.name));
|
|
4393
|
-
}
|
|
4394
|
-
function profileMatches(name, toolId) {
|
|
4395
|
-
return loadStore().profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4396
|
-
}
|
|
4397
|
-
function findProfile(name, toolId) {
|
|
4398
|
-
const matches = profileMatches(name, toolId);
|
|
4399
|
-
return matches.length === 1 ? matches[0] : undefined;
|
|
4400
|
-
}
|
|
4401
|
-
function getProfile(name, toolId) {
|
|
4402
|
-
const matches = profileMatches(name, toolId);
|
|
4403
|
-
if (matches.length === 0) {
|
|
4404
|
-
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4405
|
-
throw new AccountsError(`no profile named "${name}"${suffix}. Run \`accounts list\` to see profiles.`);
|
|
4406
|
-
}
|
|
4407
|
-
if (matches.length > 1) {
|
|
4408
|
-
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4409
|
-
}
|
|
4410
|
-
const profile = matches[0];
|
|
4411
|
-
return profile;
|
|
4412
|
-
}
|
|
4413
|
-
function addProfile(opts) {
|
|
4414
|
-
const name = opts.name;
|
|
4415
|
-
const nameCheck = profileNameSchema.safeParse(name);
|
|
4416
|
-
if (!nameCheck.success)
|
|
4417
|
-
throw new AccountsError(nameCheck.error.issues[0]?.message ?? "invalid profile name");
|
|
4418
|
-
const toolId = opts.tool ?? DEFAULT_TOOL;
|
|
4419
|
-
const tool = getTool(toolId);
|
|
4420
|
-
const store = loadStore();
|
|
4421
|
-
if (store.profiles.some((p) => p.name === name && p.tool === toolId)) {
|
|
4422
|
-
throw new AccountsError(`a ${toolId} profile named "${name}" already exists`);
|
|
4423
|
-
}
|
|
4424
|
-
const dir = opts.dir ? expandPath(opts.dir) : join4(profilesDir(), toolId, name);
|
|
4425
|
-
if (store.profiles.some((p) => p.dir === dir)) {
|
|
4426
|
-
throw new AccountsError(`a profile already uses config dir ${dir}`);
|
|
4427
|
-
}
|
|
4428
|
-
mkdirSync3(dir, { recursive: true });
|
|
4429
|
-
const email = opts.email ?? detectEmail(dir, tool);
|
|
4430
|
-
const profile = {
|
|
4431
|
-
name,
|
|
4432
|
-
tool: toolId,
|
|
4433
|
-
...email ? { email } : {},
|
|
4434
|
-
dir,
|
|
4435
|
-
...opts.description ? { description: opts.description } : {},
|
|
4436
|
-
createdAt: nowIso()
|
|
4437
|
-
};
|
|
4438
|
-
store.profiles.push(profile);
|
|
4439
|
-
saveStore(store);
|
|
4440
|
-
return profile;
|
|
4441
|
-
}
|
|
4442
|
-
function removeProfile(name, opts = {}) {
|
|
4443
|
-
const options = typeof opts === "boolean" ? { purge: opts } : opts;
|
|
4444
|
-
const store = loadStore();
|
|
4445
|
-
const matches = store.profiles.map((profile2, idx2) => ({ profile: profile2, idx: idx2 })).filter(({ profile: profile2 }) => profile2.name === name && (!options.tool || profile2.tool === options.tool));
|
|
4446
|
-
if (matches.length === 0) {
|
|
4447
|
-
const suffix = options.tool ? ` for tool "${options.tool}"` : "";
|
|
4448
|
-
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4449
|
-
}
|
|
4450
|
-
if (matches.length > 1) {
|
|
4451
|
-
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map(({ profile: profile2 }) => profile2.tool).join(", ")}); pass --tool`);
|
|
4452
|
-
}
|
|
4453
|
-
const idx = matches[0].idx;
|
|
4454
|
-
const profile = store.profiles[idx];
|
|
4455
|
-
store.profiles.splice(idx, 1);
|
|
4456
|
-
if (store.current[profile.tool] === name)
|
|
4457
|
-
delete store.current[profile.tool];
|
|
4458
|
-
if (store.applied[profile.tool] === name)
|
|
4459
|
-
delete store.applied[profile.tool];
|
|
4460
|
-
saveStore(store);
|
|
4461
|
-
let purged = false;
|
|
4462
|
-
let purgeNote;
|
|
4463
|
-
if (options.purge) {
|
|
4464
|
-
const managed = profile.dir.startsWith(profilesDir());
|
|
4465
|
-
const isDefault = profile.dir === getTool(profile.tool).defaultDir;
|
|
4466
|
-
if (managed && !isDefault && existsSync4(profile.dir)) {
|
|
4467
|
-
rmSync(profile.dir, { recursive: true, force: true });
|
|
4468
|
-
purged = true;
|
|
4469
|
-
} else {
|
|
4470
|
-
purgeNote = `refused to delete ${profile.dir} (not a managed profile dir); remove it manually if intended`;
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
return { profile, purged, purgeNote };
|
|
4474
|
-
}
|
|
4475
|
-
function renameProfile(oldName, newName, toolId) {
|
|
4476
|
-
const nameCheck = profileNameSchema.safeParse(newName);
|
|
4477
|
-
if (!nameCheck.success)
|
|
4478
|
-
throw new AccountsError(nameCheck.error.issues[0]?.message ?? "invalid profile name");
|
|
4479
|
-
const store = loadStore();
|
|
4480
|
-
const matches = store.profiles.filter((p) => p.name === oldName && (!toolId || p.tool === toolId));
|
|
4481
|
-
if (matches.length === 0) {
|
|
4482
|
-
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4483
|
-
throw new AccountsError(`no profile named "${oldName}"${suffix}`);
|
|
4484
|
-
}
|
|
4485
|
-
if (matches.length > 1) {
|
|
4486
|
-
throw new AccountsError(`profile "${oldName}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4487
|
-
}
|
|
4488
|
-
const profile = matches[0];
|
|
4489
|
-
if (store.profiles.some((p) => p.name === newName && p.tool === profile.tool)) {
|
|
4490
|
-
throw new AccountsError(`a ${profile.tool} profile named "${newName}" already exists`);
|
|
4491
|
-
}
|
|
4492
|
-
if (store.current[profile.tool] === oldName)
|
|
4493
|
-
store.current[profile.tool] = newName;
|
|
4494
|
-
if (store.applied[profile.tool] === oldName)
|
|
4495
|
-
store.applied[profile.tool] = newName;
|
|
4496
|
-
profile.name = newName;
|
|
4497
|
-
saveStore(store);
|
|
4498
|
-
return profile;
|
|
4499
|
-
}
|
|
4500
|
-
function updateProfile(name, opts) {
|
|
4501
|
-
const store = loadStore();
|
|
4502
|
-
const matches = store.profiles.filter((p) => p.name === name && (!opts.tool || p.tool === opts.tool));
|
|
4503
|
-
if (matches.length === 0) {
|
|
4504
|
-
const suffix = opts.tool ? ` for tool "${opts.tool}"` : "";
|
|
4505
|
-
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4506
|
-
}
|
|
4507
|
-
if (matches.length > 1) {
|
|
4508
|
-
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4509
|
-
}
|
|
4510
|
-
const profile = matches[0];
|
|
4511
|
-
if (opts.email !== undefined)
|
|
4512
|
-
profile.email = opts.email;
|
|
4513
|
-
if (opts.description !== undefined)
|
|
4514
|
-
profile.description = opts.description;
|
|
4515
|
-
if (opts.dir !== undefined) {
|
|
4516
|
-
const dir = expandPath(opts.dir);
|
|
4517
|
-
mkdirSync3(dir, { recursive: true });
|
|
4518
|
-
profile.dir = dir;
|
|
4519
|
-
}
|
|
4520
|
-
saveStore(store);
|
|
4521
|
-
return profile;
|
|
4522
|
-
}
|
|
4523
|
-
function redetectEmail(name, toolId) {
|
|
4524
|
-
const store = loadStore();
|
|
4525
|
-
const matches = store.profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4526
|
-
if (matches.length === 0) {
|
|
4527
|
-
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4528
|
-
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4529
|
-
}
|
|
4530
|
-
if (matches.length > 1) {
|
|
4531
|
-
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4532
|
-
}
|
|
4533
|
-
const profile = matches[0];
|
|
4534
|
-
const email = detectEmail(profile.dir, getTool(profile.tool));
|
|
4535
|
-
if (email)
|
|
4536
|
-
profile.email = email;
|
|
4537
|
-
saveStore(store);
|
|
4538
|
-
return profile;
|
|
4539
|
-
}
|
|
4540
|
-
function useProfile(name, toolId) {
|
|
4541
|
-
const store = loadStore();
|
|
4542
|
-
const matches = store.profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4543
|
-
if (matches.length === 0) {
|
|
4544
|
-
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4545
|
-
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4546
|
-
}
|
|
4547
|
-
if (matches.length > 1) {
|
|
4548
|
-
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4549
|
-
}
|
|
4550
|
-
const profile = matches[0];
|
|
4551
|
-
store.current[profile.tool] = name;
|
|
4552
|
-
profile.lastUsedAt = nowIso();
|
|
4553
|
-
saveStore(store);
|
|
4554
|
-
return { profile, toolId: profile.tool };
|
|
4555
|
-
}
|
|
4556
|
-
function currentProfile(toolId) {
|
|
4557
|
-
const store = loadStore();
|
|
4558
|
-
const name = store.current[toolId];
|
|
4559
|
-
if (!name)
|
|
4560
|
-
return;
|
|
4561
|
-
return store.profiles.find((p) => p.name === name);
|
|
4562
|
-
}
|
|
4563
4395
|
// src/lib/claude-auth.ts
|
|
4564
|
-
import { copyFileSync, existsSync as
|
|
4565
|
-
import { dirname as
|
|
4396
|
+
import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4397
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
4566
4398
|
|
|
4567
4399
|
// src/lib/claude-layout.ts
|
|
4568
|
-
import { homedir as
|
|
4569
|
-
import { dirname as
|
|
4400
|
+
import { homedir as homedir3 } from "node:os";
|
|
4401
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
4570
4402
|
var CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
4571
4403
|
var ACCOUNTS_AUTH_DIR = ".accounts-auth";
|
|
4572
4404
|
var OAUTH_SNAPSHOT = "oauth-account.json";
|
|
@@ -4574,36 +4406,36 @@ var CREDENTIALS_SNAPSHOT = "credentials.json";
|
|
|
4574
4406
|
var KEYCHAIN_SNAPSHOT = "keychain.json";
|
|
4575
4407
|
function liveClaudeBase() {
|
|
4576
4408
|
const testBase = process.env.ACCOUNTS_TEST_LIVE_DIR;
|
|
4577
|
-
return testBase && testBase.trim() ? testBase :
|
|
4409
|
+
return testBase && testBase.trim() ? testBase : homedir3();
|
|
4578
4410
|
}
|
|
4579
4411
|
function liveClaudePaths() {
|
|
4580
4412
|
const base = liveClaudeBase();
|
|
4581
|
-
const configDir =
|
|
4413
|
+
const configDir = join3(base, ".claude");
|
|
4582
4414
|
return {
|
|
4583
4415
|
configDir,
|
|
4584
|
-
homeJson:
|
|
4585
|
-
credentialsFile:
|
|
4416
|
+
homeJson: join3(base, ".claude.json"),
|
|
4417
|
+
credentialsFile: join3(configDir, ".credentials.json")
|
|
4586
4418
|
};
|
|
4587
4419
|
}
|
|
4588
4420
|
function profileAccountJsonPaths(profileDir, tool) {
|
|
4589
4421
|
if (!tool.accountFile)
|
|
4590
4422
|
return [];
|
|
4591
|
-
const paths = [
|
|
4423
|
+
const paths = [join3(profileDir, tool.accountFile)];
|
|
4592
4424
|
if (profileDir === tool.defaultDir)
|
|
4593
|
-
paths.push(
|
|
4425
|
+
paths.push(join3(dirname2(profileDir), tool.accountFile));
|
|
4594
4426
|
return paths;
|
|
4595
4427
|
}
|
|
4596
4428
|
function profileAuthDir(profileDir) {
|
|
4597
|
-
return
|
|
4429
|
+
return join3(profileDir, ACCOUNTS_AUTH_DIR);
|
|
4598
4430
|
}
|
|
4599
4431
|
function profileOAuthSnapshot(profileDir) {
|
|
4600
|
-
return
|
|
4432
|
+
return join3(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
|
|
4601
4433
|
}
|
|
4602
4434
|
function profileCredentialsSnapshot(profileDir) {
|
|
4603
|
-
return
|
|
4435
|
+
return join3(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
|
|
4604
4436
|
}
|
|
4605
4437
|
function profileKeychainSnapshot(profileDir) {
|
|
4606
|
-
return
|
|
4438
|
+
return join3(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
|
|
4607
4439
|
}
|
|
4608
4440
|
|
|
4609
4441
|
// src/lib/keychain.ts
|
|
@@ -4667,24 +4499,38 @@ function writeClaudeKeychain(cred) {
|
|
|
4667
4499
|
}
|
|
4668
4500
|
|
|
4669
4501
|
// src/lib/claude-auth.ts
|
|
4502
|
+
var CLAUDE_API_AUTH_ENV_KEYS = [
|
|
4503
|
+
"ANTHROPIC_API_KEY",
|
|
4504
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
4505
|
+
"ANTHROPIC_BASE_URL",
|
|
4506
|
+
"CLAUDE_CODE_API_KEY_HELPER",
|
|
4507
|
+
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS",
|
|
4508
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
4509
|
+
"CLAUDE_CODE_USE_VERTEX"
|
|
4510
|
+
];
|
|
4670
4511
|
function readJsonFile(path) {
|
|
4671
|
-
if (!
|
|
4512
|
+
if (!existsSync3(path))
|
|
4672
4513
|
return;
|
|
4673
4514
|
try {
|
|
4674
|
-
return JSON.parse(
|
|
4515
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
4675
4516
|
} catch {
|
|
4676
4517
|
return;
|
|
4677
4518
|
}
|
|
4678
4519
|
}
|
|
4679
4520
|
function writeJsonFile(path, data, stayUnder) {
|
|
4680
4521
|
assertSafeWritePath(path, stayUnder ? { mustStayUnder: stayUnder } : undefined);
|
|
4681
|
-
|
|
4522
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
4682
4523
|
writeFileSync2(path, JSON.stringify(data, null, 2) + `
|
|
4683
4524
|
`, { mode: 384 });
|
|
4684
4525
|
}
|
|
4685
4526
|
function readOAuthFromPaths(paths) {
|
|
4686
4527
|
return findOAuthSource(paths)?.oauth;
|
|
4687
4528
|
}
|
|
4529
|
+
function readOAuthSnapshot(profileDir) {
|
|
4530
|
+
const snap = readJsonFile(profileOAuthSnapshot(profileDir));
|
|
4531
|
+
const oauth = snap?.oauthAccount;
|
|
4532
|
+
return oauth && typeof oauth === "object" ? oauth : undefined;
|
|
4533
|
+
}
|
|
4688
4534
|
function findOAuthSource(paths) {
|
|
4689
4535
|
for (const p of paths) {
|
|
4690
4536
|
const data = readJsonFile(p);
|
|
@@ -4695,7 +4541,7 @@ function findOAuthSource(paths) {
|
|
|
4695
4541
|
return;
|
|
4696
4542
|
}
|
|
4697
4543
|
function snapshotIsStale(sourcePath, snapshotPath) {
|
|
4698
|
-
if (!
|
|
4544
|
+
if (!existsSync3(snapshotPath))
|
|
4699
4545
|
return true;
|
|
4700
4546
|
try {
|
|
4701
4547
|
return statSync(sourcePath).mtimeMs > statSync(snapshotPath).mtimeMs;
|
|
@@ -4726,6 +4572,46 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
|
|
|
4726
4572
|
}
|
|
4727
4573
|
}
|
|
4728
4574
|
}
|
|
4575
|
+
function sanitizeSettingsFile(configDir, stayUnder) {
|
|
4576
|
+
const settingsPath = join4(configDir, "settings.json");
|
|
4577
|
+
const settings = readJsonFile(settingsPath);
|
|
4578
|
+
if (!settings)
|
|
4579
|
+
return false;
|
|
4580
|
+
let changed = false;
|
|
4581
|
+
if ("apiKeyHelper" in settings) {
|
|
4582
|
+
delete settings.apiKeyHelper;
|
|
4583
|
+
changed = true;
|
|
4584
|
+
}
|
|
4585
|
+
const env = settings.env;
|
|
4586
|
+
if (env && typeof env === "object" && !Array.isArray(env)) {
|
|
4587
|
+
const envRecord = env;
|
|
4588
|
+
for (const key of CLAUDE_API_AUTH_ENV_KEYS) {
|
|
4589
|
+
if (key in envRecord) {
|
|
4590
|
+
delete envRecord[key];
|
|
4591
|
+
changed = true;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
if (changed)
|
|
4596
|
+
writeJsonFile(settingsPath, settings, stayUnder);
|
|
4597
|
+
return changed;
|
|
4598
|
+
}
|
|
4599
|
+
function sanitizeClaudeProfileApiSettings(profileDir, tool) {
|
|
4600
|
+
if (tool.id !== "claude")
|
|
4601
|
+
return false;
|
|
4602
|
+
return sanitizeSettingsFile(profileDir, profileDir);
|
|
4603
|
+
}
|
|
4604
|
+
function sanitizeClaudeOAuthProfileSettings(profileDir, tool) {
|
|
4605
|
+
if (tool.id !== "claude")
|
|
4606
|
+
return false;
|
|
4607
|
+
if (!readOAuthSnapshot(profileDir) && !readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool))) {
|
|
4608
|
+
return false;
|
|
4609
|
+
}
|
|
4610
|
+
return sanitizeClaudeProfileApiSettings(profileDir, tool);
|
|
4611
|
+
}
|
|
4612
|
+
function sanitizeLiveClaudeOAuthSettings() {
|
|
4613
|
+
return sanitizeSettingsFile(liveClaudePaths().configDir, liveClaudeBase());
|
|
4614
|
+
}
|
|
4729
4615
|
function liveOAuthEmail() {
|
|
4730
4616
|
const live = liveClaudePaths();
|
|
4731
4617
|
const oauth = readOAuthFromPaths([live.homeJson]);
|
|
@@ -4734,13 +4620,13 @@ function liveOAuthEmail() {
|
|
|
4734
4620
|
}
|
|
4735
4621
|
function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
4736
4622
|
const authDir = profileAuthDir(profileDir);
|
|
4737
|
-
assertSafeWritePath(
|
|
4738
|
-
|
|
4623
|
+
assertSafeWritePath(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
4624
|
+
mkdirSync3(authDir, { recursive: true });
|
|
4739
4625
|
const live = liveClaudePaths();
|
|
4740
4626
|
const oauth = readOAuthFromPaths([live.homeJson]);
|
|
4741
4627
|
if (oauth)
|
|
4742
4628
|
writeJsonFile(profileOAuthSnapshot(profileDir), { oauthAccount: oauth }, profileDir);
|
|
4743
|
-
if (
|
|
4629
|
+
if (existsSync3(live.credentialsFile)) {
|
|
4744
4630
|
const dest = profileCredentialsSnapshot(profileDir);
|
|
4745
4631
|
assertSafeWritePath(dest, { mustStayUnder: profileDir });
|
|
4746
4632
|
copyFileSync(live.credentialsFile, dest);
|
|
@@ -4756,19 +4642,20 @@ function snapshotClaudeAuthToProfile(profileDir, tool) {
|
|
|
4756
4642
|
}
|
|
4757
4643
|
function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
|
|
4758
4644
|
const authDir = profileAuthDir(profileDir);
|
|
4759
|
-
assertSafeWritePath(
|
|
4760
|
-
|
|
4645
|
+
assertSafeWritePath(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
4646
|
+
mkdirSync3(authDir, { recursive: true });
|
|
4761
4647
|
const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
|
|
4762
4648
|
const oauthSnap = profileOAuthSnapshot(profileDir);
|
|
4763
4649
|
if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
|
|
4764
4650
|
writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
|
|
4765
4651
|
}
|
|
4766
|
-
const credFile =
|
|
4652
|
+
const credFile = join4(profileDir, ".credentials.json");
|
|
4767
4653
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
4768
|
-
if (
|
|
4654
|
+
if (existsSync3(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
|
|
4769
4655
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
4770
4656
|
copyFileSync(credFile, credSnap);
|
|
4771
4657
|
}
|
|
4658
|
+
sanitizeClaudeOAuthProfileSettings(profileDir, tool);
|
|
4772
4659
|
}
|
|
4773
4660
|
function profileHasAuth(profileDir, tool) {
|
|
4774
4661
|
return hasAuthSnapshot(profileDir) || !!readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool));
|
|
@@ -4781,21 +4668,23 @@ function restoreClaudeAuthFromProfile(profileDir, tool, profileName) {
|
|
|
4781
4668
|
ensureProfileAuthSnapshot(profileDir, tool);
|
|
4782
4669
|
const live = liveClaudePaths();
|
|
4783
4670
|
const liveRoot = liveClaudeBase();
|
|
4784
|
-
|
|
4671
|
+
mkdirSync3(live.configDir, { recursive: true });
|
|
4785
4672
|
const oauthSnap = readJsonFile(profileOAuthSnapshot(profileDir));
|
|
4786
4673
|
const oauth = oauthSnap?.oauthAccount && typeof oauthSnap.oauthAccount === "object" ? oauthSnap.oauthAccount : readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool));
|
|
4787
4674
|
if (!oauth) {
|
|
4788
4675
|
throw new AccountsError("profile has no OAuth account data to apply");
|
|
4789
4676
|
}
|
|
4677
|
+
sanitizeClaudeOAuthProfileSettings(profileDir, tool);
|
|
4678
|
+
sanitizeLiveClaudeOAuthSettings();
|
|
4790
4679
|
assertSafeWritePath(live.homeJson, { mustStayUnder: liveRoot });
|
|
4791
4680
|
mergeOAuthInto([live.homeJson], oauth, false, liveRoot);
|
|
4792
4681
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
4793
|
-
if (
|
|
4682
|
+
if (existsSync3(credSnap)) {
|
|
4794
4683
|
assertSafeWritePath(live.credentialsFile, { mustStayUnder: liveRoot });
|
|
4795
4684
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
4796
4685
|
copyFileSync(credSnap, live.credentialsFile);
|
|
4797
|
-
writeFileSync2(live.credentialsFile,
|
|
4798
|
-
} else if (
|
|
4686
|
+
writeFileSync2(live.credentialsFile, readFileSync2(live.credentialsFile), { mode: 384 });
|
|
4687
|
+
} else if (existsSync3(live.credentialsFile)) {
|
|
4799
4688
|
if (!lstatSync2(live.credentialsFile).isSymbolicLink())
|
|
4800
4689
|
unlinkSync(live.credentialsFile);
|
|
4801
4690
|
}
|
|
@@ -4817,9 +4706,257 @@ function restoreClaudeAuthFromProfile(profileDir, tool, profileName) {
|
|
|
4817
4706
|
}
|
|
4818
4707
|
}
|
|
4819
4708
|
function hasAuthSnapshot(profileDir) {
|
|
4820
|
-
return
|
|
4709
|
+
return existsSync3(profileOAuthSnapshot(profileDir)) || existsSync3(profileCredentialsSnapshot(profileDir)) || existsSync3(profileKeychainSnapshot(profileDir));
|
|
4821
4710
|
}
|
|
4822
4711
|
|
|
4712
|
+
// src/lib/env.ts
|
|
4713
|
+
function renderTemplate(value, profile) {
|
|
4714
|
+
return value.replaceAll("{profileDir}", profile.dir).replaceAll("{profileName}", profile.name).replaceAll("{toolId}", profile.tool);
|
|
4715
|
+
}
|
|
4716
|
+
function profileEnv(profile, tool) {
|
|
4717
|
+
const env = {
|
|
4718
|
+
[tool.envVar]: profile.dir
|
|
4719
|
+
};
|
|
4720
|
+
for (const [name, value] of Object.entries(tool.extraEnv ?? {})) {
|
|
4721
|
+
env[name] = renderTemplate(value, profile);
|
|
4722
|
+
}
|
|
4723
|
+
if (tool.id === "claude") {
|
|
4724
|
+
sanitizeClaudeProfileApiSettings(profile.dir, tool);
|
|
4725
|
+
for (const key of CLAUDE_API_AUTH_ENV_KEYS)
|
|
4726
|
+
env[key] = "";
|
|
4727
|
+
}
|
|
4728
|
+
return env;
|
|
4729
|
+
}
|
|
4730
|
+
function formatEnvAssignments(env) {
|
|
4731
|
+
return Object.entries(env).map(([name, value]) => `${name}=${JSON.stringify(value)}`).join(" ");
|
|
4732
|
+
}
|
|
4733
|
+
function formatExportLines(env) {
|
|
4734
|
+
return Object.entries(env).map(([name, value]) => `export ${name}=${JSON.stringify(value)}`).join(`
|
|
4735
|
+
`);
|
|
4736
|
+
}
|
|
4737
|
+
// src/lib/detect.ts
|
|
4738
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
4739
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
4740
|
+
function detectEmail(dir, tool) {
|
|
4741
|
+
if (!tool.accountFile || !tool.emailPath)
|
|
4742
|
+
return;
|
|
4743
|
+
const candidates = [join5(dir, tool.accountFile)];
|
|
4744
|
+
if (dir === tool.defaultDir)
|
|
4745
|
+
candidates.push(join5(dirname4(dir), tool.accountFile));
|
|
4746
|
+
for (const file of candidates) {
|
|
4747
|
+
const email = readEmail(file, tool.emailPath);
|
|
4748
|
+
if (email)
|
|
4749
|
+
return email;
|
|
4750
|
+
}
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4753
|
+
function readEmail(file, path) {
|
|
4754
|
+
if (!existsSync4(file))
|
|
4755
|
+
return;
|
|
4756
|
+
let cursor;
|
|
4757
|
+
try {
|
|
4758
|
+
cursor = JSON.parse(readFileSync3(file, "utf8"));
|
|
4759
|
+
} catch {
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
for (const key of path) {
|
|
4763
|
+
if (cursor && typeof cursor === "object" && key in cursor) {
|
|
4764
|
+
cursor = cursor[key];
|
|
4765
|
+
} else {
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
return typeof cursor === "string" && cursor.includes("@") ? cursor : undefined;
|
|
4770
|
+
}
|
|
4771
|
+
// src/lib/profiles.ts
|
|
4772
|
+
import { homedir as homedir4 } from "node:os";
|
|
4773
|
+
import { isAbsolute, join as join6, resolve as resolve2 } from "node:path";
|
|
4774
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, rmSync } from "node:fs";
|
|
4775
|
+
function nowIso() {
|
|
4776
|
+
return new Date().toISOString();
|
|
4777
|
+
}
|
|
4778
|
+
function expandPath(p) {
|
|
4779
|
+
let out = p;
|
|
4780
|
+
if (out === "~")
|
|
4781
|
+
out = homedir4();
|
|
4782
|
+
else if (out.startsWith("~/"))
|
|
4783
|
+
out = join6(homedir4(), out.slice(2));
|
|
4784
|
+
return isAbsolute(out) ? out : resolve2(process.cwd(), out);
|
|
4785
|
+
}
|
|
4786
|
+
function listProfiles(toolId) {
|
|
4787
|
+
const profiles = loadStore().profiles;
|
|
4788
|
+
const filtered = toolId ? profiles.filter((p) => p.tool === toolId) : profiles;
|
|
4789
|
+
return filtered.slice().sort((a, b) => a.tool.localeCompare(b.tool) || a.name.localeCompare(b.name));
|
|
4790
|
+
}
|
|
4791
|
+
function profileMatches(name, toolId) {
|
|
4792
|
+
return loadStore().profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4793
|
+
}
|
|
4794
|
+
function findProfile(name, toolId) {
|
|
4795
|
+
const matches = profileMatches(name, toolId);
|
|
4796
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
4797
|
+
}
|
|
4798
|
+
function getProfile(name, toolId) {
|
|
4799
|
+
const matches = profileMatches(name, toolId);
|
|
4800
|
+
if (matches.length === 0) {
|
|
4801
|
+
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4802
|
+
throw new AccountsError(`no profile named "${name}"${suffix}. Run \`accounts list\` to see profiles.`);
|
|
4803
|
+
}
|
|
4804
|
+
if (matches.length > 1) {
|
|
4805
|
+
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4806
|
+
}
|
|
4807
|
+
const profile = matches[0];
|
|
4808
|
+
return profile;
|
|
4809
|
+
}
|
|
4810
|
+
function addProfile(opts) {
|
|
4811
|
+
const name = opts.name;
|
|
4812
|
+
const nameCheck = profileNameSchema.safeParse(name);
|
|
4813
|
+
if (!nameCheck.success)
|
|
4814
|
+
throw new AccountsError(nameCheck.error.issues[0]?.message ?? "invalid profile name");
|
|
4815
|
+
const toolId = opts.tool ?? DEFAULT_TOOL;
|
|
4816
|
+
const tool = getTool(toolId);
|
|
4817
|
+
const store = loadStore();
|
|
4818
|
+
if (store.profiles.some((p) => p.name === name && p.tool === toolId)) {
|
|
4819
|
+
throw new AccountsError(`a ${toolId} profile named "${name}" already exists`);
|
|
4820
|
+
}
|
|
4821
|
+
const dir = opts.dir ? expandPath(opts.dir) : join6(profilesDir(), toolId, name);
|
|
4822
|
+
if (store.profiles.some((p) => p.dir === dir)) {
|
|
4823
|
+
throw new AccountsError(`a profile already uses config dir ${dir}`);
|
|
4824
|
+
}
|
|
4825
|
+
mkdirSync4(dir, { recursive: true });
|
|
4826
|
+
const email = opts.email ?? detectEmail(dir, tool);
|
|
4827
|
+
const profile = {
|
|
4828
|
+
name,
|
|
4829
|
+
tool: toolId,
|
|
4830
|
+
...email ? { email } : {},
|
|
4831
|
+
dir,
|
|
4832
|
+
...opts.description ? { description: opts.description } : {},
|
|
4833
|
+
createdAt: nowIso()
|
|
4834
|
+
};
|
|
4835
|
+
store.profiles.push(profile);
|
|
4836
|
+
saveStore(store);
|
|
4837
|
+
return profile;
|
|
4838
|
+
}
|
|
4839
|
+
function removeProfile(name, opts = {}) {
|
|
4840
|
+
const options = typeof opts === "boolean" ? { purge: opts } : opts;
|
|
4841
|
+
const store = loadStore();
|
|
4842
|
+
const matches = store.profiles.map((profile2, idx2) => ({ profile: profile2, idx: idx2 })).filter(({ profile: profile2 }) => profile2.name === name && (!options.tool || profile2.tool === options.tool));
|
|
4843
|
+
if (matches.length === 0) {
|
|
4844
|
+
const suffix = options.tool ? ` for tool "${options.tool}"` : "";
|
|
4845
|
+
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4846
|
+
}
|
|
4847
|
+
if (matches.length > 1) {
|
|
4848
|
+
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map(({ profile: profile2 }) => profile2.tool).join(", ")}); pass --tool`);
|
|
4849
|
+
}
|
|
4850
|
+
const idx = matches[0].idx;
|
|
4851
|
+
const profile = store.profiles[idx];
|
|
4852
|
+
store.profiles.splice(idx, 1);
|
|
4853
|
+
if (store.current[profile.tool] === name)
|
|
4854
|
+
delete store.current[profile.tool];
|
|
4855
|
+
if (store.applied[profile.tool] === name)
|
|
4856
|
+
delete store.applied[profile.tool];
|
|
4857
|
+
saveStore(store);
|
|
4858
|
+
let purged = false;
|
|
4859
|
+
let purgeNote;
|
|
4860
|
+
if (options.purge) {
|
|
4861
|
+
const managed = profile.dir.startsWith(profilesDir());
|
|
4862
|
+
const isDefault = profile.dir === getTool(profile.tool).defaultDir;
|
|
4863
|
+
if (managed && !isDefault && existsSync5(profile.dir)) {
|
|
4864
|
+
rmSync(profile.dir, { recursive: true, force: true });
|
|
4865
|
+
purged = true;
|
|
4866
|
+
} else {
|
|
4867
|
+
purgeNote = `refused to delete ${profile.dir} (not a managed profile dir); remove it manually if intended`;
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
return { profile, purged, purgeNote };
|
|
4871
|
+
}
|
|
4872
|
+
function renameProfile(oldName, newName, toolId) {
|
|
4873
|
+
const nameCheck = profileNameSchema.safeParse(newName);
|
|
4874
|
+
if (!nameCheck.success)
|
|
4875
|
+
throw new AccountsError(nameCheck.error.issues[0]?.message ?? "invalid profile name");
|
|
4876
|
+
const store = loadStore();
|
|
4877
|
+
const matches = store.profiles.filter((p) => p.name === oldName && (!toolId || p.tool === toolId));
|
|
4878
|
+
if (matches.length === 0) {
|
|
4879
|
+
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4880
|
+
throw new AccountsError(`no profile named "${oldName}"${suffix}`);
|
|
4881
|
+
}
|
|
4882
|
+
if (matches.length > 1) {
|
|
4883
|
+
throw new AccountsError(`profile "${oldName}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4884
|
+
}
|
|
4885
|
+
const profile = matches[0];
|
|
4886
|
+
if (store.profiles.some((p) => p.name === newName && p.tool === profile.tool)) {
|
|
4887
|
+
throw new AccountsError(`a ${profile.tool} profile named "${newName}" already exists`);
|
|
4888
|
+
}
|
|
4889
|
+
if (store.current[profile.tool] === oldName)
|
|
4890
|
+
store.current[profile.tool] = newName;
|
|
4891
|
+
if (store.applied[profile.tool] === oldName)
|
|
4892
|
+
store.applied[profile.tool] = newName;
|
|
4893
|
+
profile.name = newName;
|
|
4894
|
+
saveStore(store);
|
|
4895
|
+
return profile;
|
|
4896
|
+
}
|
|
4897
|
+
function updateProfile(name, opts) {
|
|
4898
|
+
const store = loadStore();
|
|
4899
|
+
const matches = store.profiles.filter((p) => p.name === name && (!opts.tool || p.tool === opts.tool));
|
|
4900
|
+
if (matches.length === 0) {
|
|
4901
|
+
const suffix = opts.tool ? ` for tool "${opts.tool}"` : "";
|
|
4902
|
+
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4903
|
+
}
|
|
4904
|
+
if (matches.length > 1) {
|
|
4905
|
+
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4906
|
+
}
|
|
4907
|
+
const profile = matches[0];
|
|
4908
|
+
if (opts.email !== undefined)
|
|
4909
|
+
profile.email = opts.email;
|
|
4910
|
+
if (opts.description !== undefined)
|
|
4911
|
+
profile.description = opts.description;
|
|
4912
|
+
if (opts.dir !== undefined) {
|
|
4913
|
+
const dir = expandPath(opts.dir);
|
|
4914
|
+
mkdirSync4(dir, { recursive: true });
|
|
4915
|
+
profile.dir = dir;
|
|
4916
|
+
}
|
|
4917
|
+
saveStore(store);
|
|
4918
|
+
return profile;
|
|
4919
|
+
}
|
|
4920
|
+
function redetectEmail(name, toolId) {
|
|
4921
|
+
const store = loadStore();
|
|
4922
|
+
const matches = store.profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4923
|
+
if (matches.length === 0) {
|
|
4924
|
+
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4925
|
+
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4926
|
+
}
|
|
4927
|
+
if (matches.length > 1) {
|
|
4928
|
+
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4929
|
+
}
|
|
4930
|
+
const profile = matches[0];
|
|
4931
|
+
const email = detectEmail(profile.dir, getTool(profile.tool));
|
|
4932
|
+
if (email)
|
|
4933
|
+
profile.email = email;
|
|
4934
|
+
saveStore(store);
|
|
4935
|
+
return profile;
|
|
4936
|
+
}
|
|
4937
|
+
function useProfile(name, toolId) {
|
|
4938
|
+
const store = loadStore();
|
|
4939
|
+
const matches = store.profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4940
|
+
if (matches.length === 0) {
|
|
4941
|
+
const suffix = toolId ? ` for tool "${toolId}"` : "";
|
|
4942
|
+
throw new AccountsError(`no profile named "${name}"${suffix}`);
|
|
4943
|
+
}
|
|
4944
|
+
if (matches.length > 1) {
|
|
4945
|
+
throw new AccountsError(`profile "${name}" exists for multiple tools (${matches.map((p) => p.tool).join(", ")}); pass --tool`);
|
|
4946
|
+
}
|
|
4947
|
+
const profile = matches[0];
|
|
4948
|
+
store.current[profile.tool] = name;
|
|
4949
|
+
profile.lastUsedAt = nowIso();
|
|
4950
|
+
saveStore(store);
|
|
4951
|
+
return { profile, toolId: profile.tool };
|
|
4952
|
+
}
|
|
4953
|
+
function currentProfile(toolId) {
|
|
4954
|
+
const store = loadStore();
|
|
4955
|
+
const name = store.current[toolId];
|
|
4956
|
+
if (!name)
|
|
4957
|
+
return;
|
|
4958
|
+
return store.profiles.find((p) => p.name === name);
|
|
4959
|
+
}
|
|
4823
4960
|
// src/lib/apply-lock.ts
|
|
4824
4961
|
import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync5, openSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4825
4962
|
import { join as join7 } from "node:path";
|
|
@@ -4970,7 +5107,8 @@ function commandLine(env, command) {
|
|
|
4970
5107
|
return `${formatEnvAssignments(env)} ${command.map(shellQuote).join(" ")}`.trim();
|
|
4971
5108
|
}
|
|
4972
5109
|
function commandFor(tool, opts) {
|
|
4973
|
-
|
|
5110
|
+
const args = [...opts.resume ? tool.resumeArgs ?? [] : [], ...opts.args ?? []];
|
|
5111
|
+
return [tool.bin, ...mergeToolArgs(tool, args, { permissions: opts.permissions })];
|
|
4974
5112
|
}
|
|
4975
5113
|
function switchProfile(name, opts = {}) {
|
|
4976
5114
|
const profile = getProfile(name, opts.tool);
|
|
@@ -4999,6 +5137,7 @@ function switchProfile(name, opts = {}) {
|
|
|
4999
5137
|
exports: formatExportLines(env),
|
|
5000
5138
|
command,
|
|
5001
5139
|
commandLine: commandLine(env, command),
|
|
5140
|
+
...opts.permissions ? { permissions: normalizePermissionPreset(opts.permissions) } : {},
|
|
5002
5141
|
restartRequired,
|
|
5003
5142
|
message
|
|
5004
5143
|
};
|
|
@@ -5323,7 +5462,8 @@ async function runSupervisedTool(initialProfile, tool, initialArgs = [], opts =
|
|
|
5323
5462
|
tool: tool.id,
|
|
5324
5463
|
mode: request.mode ?? "auto",
|
|
5325
5464
|
resume: request.resume ?? true,
|
|
5326
|
-
args: request.args ?? []
|
|
5465
|
+
args: request.args ?? [],
|
|
5466
|
+
permissions: request.permissions
|
|
5327
5467
|
});
|
|
5328
5468
|
log(`accounts supervisor: switching ${tool.id} to ${result.profile.name}`);
|
|
5329
5469
|
setTimeout(() => void restartWith(result), 0);
|
|
@@ -5473,6 +5613,9 @@ export {
|
|
|
5473
5613
|
shellSnippet,
|
|
5474
5614
|
sendSupervisorRequest,
|
|
5475
5615
|
saveStore,
|
|
5616
|
+
sanitizeLiveClaudeOAuthSettings,
|
|
5617
|
+
sanitizeClaudeProfileApiSettings,
|
|
5618
|
+
sanitizeClaudeOAuthProfileSettings,
|
|
5476
5619
|
runSupervisedTool,
|
|
5477
5620
|
restoreClaudeAuthFromProfile,
|
|
5478
5621
|
resolveSupervisorLaunch,
|
|
@@ -5488,6 +5631,9 @@ export {
|
|
|
5488
5631
|
profileHasAuth,
|
|
5489
5632
|
profileEnv,
|
|
5490
5633
|
pickProfile,
|
|
5634
|
+
permissionArgsFor,
|
|
5635
|
+
normalizePermissionPreset,
|
|
5636
|
+
mergeToolArgs,
|
|
5491
5637
|
loadStore,
|
|
5492
5638
|
listTools,
|
|
5493
5639
|
listSupervisorStates,
|
|
@@ -5517,6 +5663,7 @@ export {
|
|
|
5517
5663
|
addCustomTool,
|
|
5518
5664
|
accountsHome,
|
|
5519
5665
|
DEFAULT_TOOL,
|
|
5666
|
+
CLAUDE_API_AUTH_ENV_KEYS,
|
|
5520
5667
|
BUILTIN_TOOLS,
|
|
5521
5668
|
AccountsError
|
|
5522
5669
|
};
|