@hasna/accounts 0.1.18 → 0.1.20
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/cli.js +171 -99
- package/dist/index.js +160 -88
- package/dist/lib/safe-path.d.ts.map +1 -1
- package/dist/mcp.js +146 -74
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +111 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4029,57 +4029,126 @@ class AccountsError extends Error {
|
|
|
4029
4029
|
}
|
|
4030
4030
|
// src/storage.ts
|
|
4031
4031
|
import { homedir } from "node:os";
|
|
4032
|
-
import { join } from "node:path";
|
|
4033
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
4032
|
+
import { join as join2 } from "node:path";
|
|
4033
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
4034
4034
|
|
|
4035
4035
|
// src/lib/safe-path.ts
|
|
4036
4036
|
import { existsSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
4037
|
-
import { dirname, resolve } from "node:path";
|
|
4037
|
+
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
4038
|
+
function lstatIfExists(path) {
|
|
4039
|
+
try {
|
|
4040
|
+
return lstatSync(path);
|
|
4041
|
+
} catch (err) {
|
|
4042
|
+
if (err.code !== "ENOENT")
|
|
4043
|
+
throw err;
|
|
4044
|
+
return;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4038
4047
|
function throwIfSymlink(path, label) {
|
|
4039
|
-
if (
|
|
4048
|
+
if (lstatIfExists(path)?.isSymbolicLink()) {
|
|
4040
4049
|
throw new AccountsError(`${label}: ${path}`);
|
|
4041
4050
|
}
|
|
4042
4051
|
}
|
|
4043
|
-
function
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
}
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
}
|
|
4061
|
-
}
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4052
|
+
function isAllowedSystemDirectorySymlink(path) {
|
|
4053
|
+
if (path !== "/var" && path !== "/tmp")
|
|
4054
|
+
return false;
|
|
4055
|
+
try {
|
|
4056
|
+
return realpathSync(path) === `/private${path}`;
|
|
4057
|
+
} catch {
|
|
4058
|
+
return false;
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
function assertDirectory(path, label, opts) {
|
|
4062
|
+
const stat = lstatIfExists(path);
|
|
4063
|
+
if (!stat) {
|
|
4064
|
+
throw new AccountsError(`refusing to write under missing directory: ${path}`);
|
|
4065
|
+
}
|
|
4066
|
+
if (stat.isSymbolicLink()) {
|
|
4067
|
+
if (opts?.allowSystemSymlink && isAllowedSystemDirectorySymlink(path))
|
|
4068
|
+
return;
|
|
4069
|
+
throw new AccountsError(`${label}: ${path}`);
|
|
4070
|
+
}
|
|
4071
|
+
if (!stat.isDirectory()) {
|
|
4072
|
+
throw new AccountsError(`refusing to write under non-directory path: ${path}`);
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
function assertInsideBase(absPath, base, originalPath) {
|
|
4076
|
+
const rel = relative(base, absPath);
|
|
4077
|
+
if (rel === "")
|
|
4078
|
+
return rel;
|
|
4079
|
+
if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) {
|
|
4080
|
+
throw new AccountsError(`refusing to write outside profile directory: ${originalPath}`);
|
|
4081
|
+
}
|
|
4082
|
+
return rel;
|
|
4083
|
+
}
|
|
4084
|
+
function assertExistingDirectoryComponentsSafe(path, label) {
|
|
4085
|
+
const root = parse(path).root;
|
|
4086
|
+
const rel = relative(root, path);
|
|
4087
|
+
const segments = rel ? rel.split(sep).filter(Boolean) : [];
|
|
4088
|
+
let cursor = root;
|
|
4089
|
+
assertDirectory(cursor, label, { allowSystemSymlink: true });
|
|
4090
|
+
for (const segment of segments) {
|
|
4091
|
+
cursor = join(cursor, segment);
|
|
4092
|
+
if (!lstatIfExists(cursor))
|
|
4093
|
+
return;
|
|
4094
|
+
assertDirectory(cursor, label, { allowSystemSymlink: true });
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
function ensureBoundaryRootSafe(base) {
|
|
4098
|
+
const missing = [];
|
|
4099
|
+
let cursor = base;
|
|
4100
|
+
while (!lstatIfExists(cursor)) {
|
|
4101
|
+
missing.unshift(cursor);
|
|
4102
|
+
const parent = dirname(cursor);
|
|
4103
|
+
if (parent === cursor)
|
|
4104
|
+
break;
|
|
4105
|
+
cursor = parent;
|
|
4106
|
+
}
|
|
4107
|
+
assertDirectory(cursor, "refusing to use symlink base directory");
|
|
4108
|
+
for (const dir of missing) {
|
|
4109
|
+
if (lstatIfExists(dir)) {
|
|
4110
|
+
assertDirectory(dir, "refusing to use symlink base directory");
|
|
4111
|
+
} else {
|
|
4112
|
+
mkdirSync(dir);
|
|
4113
|
+
assertDirectory(dir, "refusing to use symlink base directory");
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
assertDirectory(base, "refusing to use symlink base directory");
|
|
4117
|
+
}
|
|
4118
|
+
function ensureDirectoryChainSafe(parent, startAt) {
|
|
4119
|
+
const root = startAt ?? parse(parent).root;
|
|
4120
|
+
const rel = relative(root, parent);
|
|
4121
|
+
const segments = rel ? rel.split(sep).filter(Boolean) : [];
|
|
4122
|
+
let cursor = root;
|
|
4123
|
+
if (startAt)
|
|
4124
|
+
assertDirectory(startAt, "refusing to write under symlink directory");
|
|
4125
|
+
for (const segment of segments) {
|
|
4126
|
+
cursor = join(cursor, segment);
|
|
4127
|
+
if (lstatIfExists(cursor)) {
|
|
4128
|
+
assertDirectory(cursor, "refusing to write under symlink directory");
|
|
4129
|
+
} else {
|
|
4130
|
+
mkdirSync(cursor);
|
|
4131
|
+
assertDirectory(cursor, "refusing to write under symlink directory");
|
|
4069
4132
|
}
|
|
4070
4133
|
}
|
|
4071
4134
|
}
|
|
4072
4135
|
function assertSafeWritePath(filePath, opts) {
|
|
4073
4136
|
const absFile = resolve(filePath);
|
|
4074
4137
|
const parent = dirname(absFile);
|
|
4075
|
-
|
|
4076
|
-
|
|
4138
|
+
const base = opts?.mustStayUnder ? resolve(opts.mustStayUnder) : undefined;
|
|
4139
|
+
if (base) {
|
|
4140
|
+
assertInsideBase(absFile, base, filePath);
|
|
4141
|
+
assertExistingDirectoryComponentsSafe(base, "refusing to use symlink base directory");
|
|
4142
|
+
ensureBoundaryRootSafe(base);
|
|
4143
|
+
ensureDirectoryChainSafe(parent, base);
|
|
4144
|
+
} else {
|
|
4145
|
+
ensureDirectoryChainSafe(parent);
|
|
4146
|
+
}
|
|
4077
4147
|
throwIfSymlink(absFile, "refusing to write through symlink");
|
|
4078
|
-
assertDirChainSafe(absFile, opts?.mustStayUnder);
|
|
4079
4148
|
const resolved = realpathSync(existsSync(absFile) ? absFile : parent);
|
|
4080
|
-
if (
|
|
4081
|
-
const
|
|
4082
|
-
if (resolved !==
|
|
4149
|
+
if (base) {
|
|
4150
|
+
const realBase = realpathSync(base);
|
|
4151
|
+
if (resolved !== realBase && !resolved.startsWith(realBase + sep)) {
|
|
4083
4152
|
throw new AccountsError(`refusing to write outside profile directory: ${filePath}`);
|
|
4084
4153
|
}
|
|
4085
4154
|
}
|
|
@@ -4108,16 +4177,16 @@ function accountsHome() {
|
|
|
4108
4177
|
const override = process.env.ACCOUNTS_HOME;
|
|
4109
4178
|
if (override && override.trim())
|
|
4110
4179
|
return validateEnvPath(override, "ACCOUNTS_HOME");
|
|
4111
|
-
return
|
|
4180
|
+
return join2(homedir(), ".hasna", "accounts");
|
|
4112
4181
|
}
|
|
4113
4182
|
function storePath() {
|
|
4114
4183
|
const override = process.env.ACCOUNTS_STORE_PATH;
|
|
4115
4184
|
if (override && override.trim())
|
|
4116
4185
|
return validateEnvPath(override, "ACCOUNTS_STORE_PATH");
|
|
4117
|
-
return
|
|
4186
|
+
return join2(accountsHome(), "accounts.json");
|
|
4118
4187
|
}
|
|
4119
4188
|
function profilesDir() {
|
|
4120
|
-
return
|
|
4189
|
+
return join2(accountsHome(), "profiles");
|
|
4121
4190
|
}
|
|
4122
4191
|
var EMPTY_STORE = { version: 1, current: {}, applied: {}, profiles: [], tools: [] };
|
|
4123
4192
|
function loadStore() {
|
|
@@ -4160,13 +4229,16 @@ function loadStore() {
|
|
|
4160
4229
|
function saveStore(store) {
|
|
4161
4230
|
const path = storePath();
|
|
4162
4231
|
assertSafeWritePath(path, { mustStayUnder: accountsHome() });
|
|
4163
|
-
mkdirSync2(
|
|
4232
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
4233
|
+
if (existsSync2(path))
|
|
4234
|
+
chmodSync(path, 384);
|
|
4164
4235
|
writeFileSync(path, JSON.stringify(store, null, 2) + `
|
|
4165
4236
|
`, { mode: 384 });
|
|
4237
|
+
chmodSync(path, 384);
|
|
4166
4238
|
}
|
|
4167
4239
|
// src/lib/tools.ts
|
|
4168
4240
|
import { homedir as homedir2 } from "node:os";
|
|
4169
|
-
import { join as
|
|
4241
|
+
import { join as join3 } from "node:path";
|
|
4170
4242
|
var BUILTIN_TOOLS = [
|
|
4171
4243
|
{
|
|
4172
4244
|
id: "claude",
|
|
@@ -4175,7 +4247,7 @@ var BUILTIN_TOOLS = [
|
|
|
4175
4247
|
extraEnv: {
|
|
4176
4248
|
TELEGRAM_STATE_DIR: "{profileDir}/channels/telegram"
|
|
4177
4249
|
},
|
|
4178
|
-
defaultDir:
|
|
4250
|
+
defaultDir: join3(homedir2(), ".claude"),
|
|
4179
4251
|
bin: "claude",
|
|
4180
4252
|
loginHint: "run /login inside Claude, then /exit when done",
|
|
4181
4253
|
resumeArgs: ["--continue"],
|
|
@@ -4195,7 +4267,7 @@ var BUILTIN_TOOLS = [
|
|
|
4195
4267
|
id: "codex-app",
|
|
4196
4268
|
label: "Codex App",
|
|
4197
4269
|
envVar: "CODEX_HOME",
|
|
4198
|
-
defaultDir:
|
|
4270
|
+
defaultDir: join3(homedir2(), ".codex"),
|
|
4199
4271
|
bin: "/Applications/Codex.app/Contents/MacOS/Codex",
|
|
4200
4272
|
loginHint: "sign in inside Codex.app, then quit the app when the profile is ready",
|
|
4201
4273
|
launchArgs: ["--user-data-dir={profileDir}/electron-user-data"],
|
|
@@ -4205,7 +4277,7 @@ var BUILTIN_TOOLS = [
|
|
|
4205
4277
|
id: "codex",
|
|
4206
4278
|
label: "Codex CLI",
|
|
4207
4279
|
envVar: "CODEX_HOME",
|
|
4208
|
-
defaultDir:
|
|
4280
|
+
defaultDir: join3(homedir2(), ".codex"),
|
|
4209
4281
|
bin: "codex",
|
|
4210
4282
|
loginArgs: ["login"],
|
|
4211
4283
|
loginHint: "complete the Codex login flow for this CODEX_HOME",
|
|
@@ -4218,7 +4290,7 @@ var BUILTIN_TOOLS = [
|
|
|
4218
4290
|
id: "takumi",
|
|
4219
4291
|
label: "Takumi",
|
|
4220
4292
|
envVar: "TAKUMI_CONFIG_DIR",
|
|
4221
|
-
defaultDir:
|
|
4293
|
+
defaultDir: join3(homedir2(), ".takumi"),
|
|
4222
4294
|
bin: "takumi",
|
|
4223
4295
|
loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
|
|
4224
4296
|
resumeArgs: ["--continue"],
|
|
@@ -4238,7 +4310,7 @@ var BUILTIN_TOOLS = [
|
|
|
4238
4310
|
id: "gemini",
|
|
4239
4311
|
label: "Gemini CLI",
|
|
4240
4312
|
envVar: "GEMINI_CONFIG_DIR",
|
|
4241
|
-
defaultDir:
|
|
4313
|
+
defaultDir: join3(homedir2(), ".gemini"),
|
|
4242
4314
|
bin: "gemini",
|
|
4243
4315
|
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR",
|
|
4244
4316
|
permissionArgs: {
|
|
@@ -4256,7 +4328,7 @@ var BUILTIN_TOOLS = [
|
|
|
4256
4328
|
XDG_CONFIG_HOME: "{profileDir}/xdg-config",
|
|
4257
4329
|
XDG_DATA_HOME: "{profileDir}/xdg-data"
|
|
4258
4330
|
},
|
|
4259
|
-
defaultDir:
|
|
4331
|
+
defaultDir: join3(homedir2(), ".config", "opencode"),
|
|
4260
4332
|
bin: "opencode",
|
|
4261
4333
|
loginArgs: ["auth", "login"],
|
|
4262
4334
|
loginHint: "complete opencode auth login for this isolated config/data root",
|
|
@@ -4266,7 +4338,7 @@ var BUILTIN_TOOLS = [
|
|
|
4266
4338
|
id: "cursor",
|
|
4267
4339
|
label: "Cursor Agent",
|
|
4268
4340
|
envVar: "CURSOR_CONFIG_DIR",
|
|
4269
|
-
defaultDir:
|
|
4341
|
+
defaultDir: join3(homedir2(), ".cursor"),
|
|
4270
4342
|
bin: "cursor-agent",
|
|
4271
4343
|
loginArgs: ["login"],
|
|
4272
4344
|
loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
|
|
@@ -4275,7 +4347,7 @@ var BUILTIN_TOOLS = [
|
|
|
4275
4347
|
id: "pi",
|
|
4276
4348
|
label: "Pi Coding Agent",
|
|
4277
4349
|
envVar: "PI_CODING_AGENT_HOME",
|
|
4278
|
-
defaultDir:
|
|
4350
|
+
defaultDir: join3(homedir2(), ".pi"),
|
|
4279
4351
|
bin: "pi",
|
|
4280
4352
|
loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
|
|
4281
4353
|
},
|
|
@@ -4283,7 +4355,7 @@ var BUILTIN_TOOLS = [
|
|
|
4283
4355
|
id: "hermes",
|
|
4284
4356
|
label: "Hermes",
|
|
4285
4357
|
envVar: "HERMES_HOME",
|
|
4286
|
-
defaultDir:
|
|
4358
|
+
defaultDir: join3(homedir2(), ".hermes"),
|
|
4287
4359
|
bin: "hermes",
|
|
4288
4360
|
loginHint: "complete Hermes auth in this HERMES_HOME",
|
|
4289
4361
|
permissionArgs: {
|
|
@@ -4295,7 +4367,7 @@ var BUILTIN_TOOLS = [
|
|
|
4295
4367
|
id: "kimi",
|
|
4296
4368
|
label: "Kimi Code",
|
|
4297
4369
|
envVar: "KIMI_CODE_HOME",
|
|
4298
|
-
defaultDir:
|
|
4370
|
+
defaultDir: join3(homedir2(), ".kimi-code"),
|
|
4299
4371
|
bin: "kimi",
|
|
4300
4372
|
loginArgs: ["login"],
|
|
4301
4373
|
loginHint: "complete kimi login for this KIMI_CODE_HOME",
|
|
@@ -4310,7 +4382,7 @@ var BUILTIN_TOOLS = [
|
|
|
4310
4382
|
id: "grok",
|
|
4311
4383
|
label: "Grok Build",
|
|
4312
4384
|
envVar: "HOME",
|
|
4313
|
-
defaultDir:
|
|
4385
|
+
defaultDir: join3(homedir2(), ".grok"),
|
|
4314
4386
|
bin: "grok",
|
|
4315
4387
|
loginArgs: ["login"],
|
|
4316
4388
|
loginHint: "complete grok login in this process-scoped HOME; prefer launch/shell over exporting HOME globally"
|
|
@@ -4416,11 +4488,11 @@ function removeCustomTool(id) {
|
|
|
4416
4488
|
}
|
|
4417
4489
|
// src/lib/claude-auth.ts
|
|
4418
4490
|
import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4419
|
-
import { dirname as dirname3, join as
|
|
4491
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
4420
4492
|
|
|
4421
4493
|
// src/lib/claude-layout.ts
|
|
4422
4494
|
import { homedir as homedir3 } from "node:os";
|
|
4423
|
-
import { dirname as dirname2, join as
|
|
4495
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
4424
4496
|
var CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
4425
4497
|
var ACCOUNTS_AUTH_DIR = ".accounts-auth";
|
|
4426
4498
|
var OAUTH_SNAPSHOT = "oauth-account.json";
|
|
@@ -4432,32 +4504,32 @@ function liveClaudeBase() {
|
|
|
4432
4504
|
}
|
|
4433
4505
|
function liveClaudePaths() {
|
|
4434
4506
|
const base = liveClaudeBase();
|
|
4435
|
-
const configDir =
|
|
4507
|
+
const configDir = join4(base, ".claude");
|
|
4436
4508
|
return {
|
|
4437
4509
|
configDir,
|
|
4438
|
-
homeJson:
|
|
4439
|
-
credentialsFile:
|
|
4510
|
+
homeJson: join4(base, ".claude.json"),
|
|
4511
|
+
credentialsFile: join4(configDir, ".credentials.json")
|
|
4440
4512
|
};
|
|
4441
4513
|
}
|
|
4442
4514
|
function profileAccountJsonPaths(profileDir, tool) {
|
|
4443
4515
|
if (!tool.accountFile)
|
|
4444
4516
|
return [];
|
|
4445
|
-
const paths = [
|
|
4517
|
+
const paths = [join4(profileDir, tool.accountFile)];
|
|
4446
4518
|
if (profileDir === tool.defaultDir)
|
|
4447
|
-
paths.push(
|
|
4519
|
+
paths.push(join4(dirname2(profileDir), tool.accountFile));
|
|
4448
4520
|
return paths;
|
|
4449
4521
|
}
|
|
4450
4522
|
function profileAuthDir(profileDir) {
|
|
4451
|
-
return
|
|
4523
|
+
return join4(profileDir, ACCOUNTS_AUTH_DIR);
|
|
4452
4524
|
}
|
|
4453
4525
|
function profileOAuthSnapshot(profileDir) {
|
|
4454
|
-
return
|
|
4526
|
+
return join4(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
|
|
4455
4527
|
}
|
|
4456
4528
|
function profileCredentialsSnapshot(profileDir) {
|
|
4457
|
-
return
|
|
4529
|
+
return join4(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
|
|
4458
4530
|
}
|
|
4459
4531
|
function profileKeychainSnapshot(profileDir) {
|
|
4460
|
-
return
|
|
4532
|
+
return join4(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
|
|
4461
4533
|
}
|
|
4462
4534
|
|
|
4463
4535
|
// src/lib/keychain.ts
|
|
@@ -4611,7 +4683,7 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
|
|
|
4611
4683
|
}
|
|
4612
4684
|
}
|
|
4613
4685
|
function sanitizeSettingsFile(configDir, stayUnder) {
|
|
4614
|
-
const settingsPath =
|
|
4686
|
+
const settingsPath = join5(configDir, "settings.json");
|
|
4615
4687
|
const settings = readJsonFile(settingsPath);
|
|
4616
4688
|
if (!settings)
|
|
4617
4689
|
return false;
|
|
@@ -4658,7 +4730,7 @@ function liveOAuthEmail() {
|
|
|
4658
4730
|
}
|
|
4659
4731
|
function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
4660
4732
|
const authDir = profileAuthDir(profileDir);
|
|
4661
|
-
assertSafeWritePath(
|
|
4733
|
+
assertSafeWritePath(join5(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
4662
4734
|
mkdirSync3(authDir, { recursive: true });
|
|
4663
4735
|
const live = liveClaudePaths();
|
|
4664
4736
|
const oauth = readOAuthFromPaths([live.homeJson]);
|
|
@@ -4680,14 +4752,14 @@ function snapshotClaudeAuthToProfile(profileDir, tool) {
|
|
|
4680
4752
|
}
|
|
4681
4753
|
function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
|
|
4682
4754
|
const authDir = profileAuthDir(profileDir);
|
|
4683
|
-
assertSafeWritePath(
|
|
4755
|
+
assertSafeWritePath(join5(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
4684
4756
|
mkdirSync3(authDir, { recursive: true });
|
|
4685
4757
|
const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
|
|
4686
4758
|
const oauthSnap = profileOAuthSnapshot(profileDir);
|
|
4687
4759
|
if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
|
|
4688
4760
|
writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
|
|
4689
4761
|
}
|
|
4690
|
-
const credFile =
|
|
4762
|
+
const credFile = join5(profileDir, ".credentials.json");
|
|
4691
4763
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
4692
4764
|
if (existsSync3(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
|
|
4693
4765
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
@@ -4749,7 +4821,7 @@ function hasAuthSnapshot(profileDir) {
|
|
|
4749
4821
|
|
|
4750
4822
|
// src/lib/codex-app.ts
|
|
4751
4823
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4752
|
-
import { join as
|
|
4824
|
+
import { join as join6 } from "node:path";
|
|
4753
4825
|
var FILE_CREDENTIALS_LINE = 'cli_auth_credentials_store = "file"';
|
|
4754
4826
|
function insertRootConfigLine(config, line) {
|
|
4755
4827
|
if (config.trim() === "")
|
|
@@ -4776,7 +4848,7 @@ ${after}${after.endsWith(`
|
|
|
4776
4848
|
}
|
|
4777
4849
|
function ensureCodexAppProfileConfig(profileDir) {
|
|
4778
4850
|
mkdirSync4(profileDir, { recursive: true });
|
|
4779
|
-
const configPath =
|
|
4851
|
+
const configPath = join6(profileDir, "config.toml");
|
|
4780
4852
|
const current = existsSync4(configPath) ? readFileSync3(configPath, "utf8") : "";
|
|
4781
4853
|
if (/^\s*cli_auth_credentials_store\s*=/.test(current))
|
|
4782
4854
|
return;
|
|
@@ -4812,13 +4884,13 @@ function formatExportLines(env) {
|
|
|
4812
4884
|
}
|
|
4813
4885
|
// src/lib/detect.ts
|
|
4814
4886
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
4815
|
-
import { dirname as dirname4, join as
|
|
4887
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
4816
4888
|
function detectEmail(dir, tool) {
|
|
4817
4889
|
if (!tool.accountFile || !tool.emailPath)
|
|
4818
4890
|
return;
|
|
4819
|
-
const candidates = [
|
|
4891
|
+
const candidates = [join7(dir, tool.accountFile)];
|
|
4820
4892
|
if (dir === tool.defaultDir)
|
|
4821
|
-
candidates.push(
|
|
4893
|
+
candidates.push(join7(dirname4(dir), tool.accountFile));
|
|
4822
4894
|
for (const file of candidates) {
|
|
4823
4895
|
const email = readEmail(file, tool.emailPath);
|
|
4824
4896
|
if (email)
|
|
@@ -4846,7 +4918,7 @@ function readEmail(file, path) {
|
|
|
4846
4918
|
}
|
|
4847
4919
|
// src/lib/profiles.ts
|
|
4848
4920
|
import { homedir as homedir4 } from "node:os";
|
|
4849
|
-
import { isAbsolute, join as
|
|
4921
|
+
import { isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4850
4922
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, rmSync } from "node:fs";
|
|
4851
4923
|
function nowIso() {
|
|
4852
4924
|
return new Date().toISOString();
|
|
@@ -4856,8 +4928,8 @@ function expandPath(p) {
|
|
|
4856
4928
|
if (out === "~")
|
|
4857
4929
|
out = homedir4();
|
|
4858
4930
|
else if (out.startsWith("~/"))
|
|
4859
|
-
out =
|
|
4860
|
-
return
|
|
4931
|
+
out = join8(homedir4(), out.slice(2));
|
|
4932
|
+
return isAbsolute2(out) ? out : resolve2(process.cwd(), out);
|
|
4861
4933
|
}
|
|
4862
4934
|
function listProfiles(toolId) {
|
|
4863
4935
|
const profiles = loadStore().profiles;
|
|
@@ -4868,8 +4940,8 @@ function profileMatches(name, toolId) {
|
|
|
4868
4940
|
return loadStore().profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4869
4941
|
}
|
|
4870
4942
|
function isManagedProfileDir(dir) {
|
|
4871
|
-
const rel =
|
|
4872
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
4943
|
+
const rel = relative2(resolve2(profilesDir()), resolve2(dir));
|
|
4944
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
4873
4945
|
}
|
|
4874
4946
|
function findProfile(name, toolId) {
|
|
4875
4947
|
const matches = profileMatches(name, toolId);
|
|
@@ -4898,7 +4970,7 @@ function addProfile(opts) {
|
|
|
4898
4970
|
if (store.profiles.some((p) => p.name === name && p.tool === toolId)) {
|
|
4899
4971
|
throw new AccountsError(`a ${toolId} profile named "${name}" already exists`);
|
|
4900
4972
|
}
|
|
4901
|
-
const dir = opts.dir ? expandPath(opts.dir) :
|
|
4973
|
+
const dir = opts.dir ? expandPath(opts.dir) : join8(profilesDir(), toolId, name);
|
|
4902
4974
|
if (store.profiles.some((p) => p.dir === dir)) {
|
|
4903
4975
|
throw new AccountsError(`a profile already uses config dir ${dir}`);
|
|
4904
4976
|
}
|
|
@@ -5039,9 +5111,9 @@ function currentProfile(toolId) {
|
|
|
5039
5111
|
}
|
|
5040
5112
|
// src/lib/apply-lock.ts
|
|
5041
5113
|
import { closeSync, existsSync as existsSync7, mkdirSync as mkdirSync6, openSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5042
|
-
import { join as
|
|
5114
|
+
import { join as join9 } from "node:path";
|
|
5043
5115
|
function lockPath() {
|
|
5044
|
-
return
|
|
5116
|
+
return join9(accountsHome(), ".apply.lock");
|
|
5045
5117
|
}
|
|
5046
5118
|
function withApplyLock(fn) {
|
|
5047
5119
|
const home = accountsHome();
|
|
@@ -5111,7 +5183,7 @@ function applyProfileUnlocked(name, toolId) {
|
|
|
5111
5183
|
}
|
|
5112
5184
|
// src/lib/import-profile.ts
|
|
5113
5185
|
import { cpSync, existsSync as existsSync8 } from "node:fs";
|
|
5114
|
-
import { join as
|
|
5186
|
+
import { join as join10 } from "node:path";
|
|
5115
5187
|
function importProfile(opts) {
|
|
5116
5188
|
const toolId = opts.tool ?? DEFAULT_TOOL;
|
|
5117
5189
|
const tool = getTool(toolId);
|
|
@@ -5121,7 +5193,7 @@ function importProfile(opts) {
|
|
|
5121
5193
|
throw new AccountsError(`config dir does not exist: ${sourceDir}`);
|
|
5122
5194
|
}
|
|
5123
5195
|
if (opts.copy) {
|
|
5124
|
-
const targetDir =
|
|
5196
|
+
const targetDir = join10(profilesDir(), toolId, name);
|
|
5125
5197
|
if (existsSync8(targetDir)) {
|
|
5126
5198
|
throw new AccountsError(`managed copy target already exists: ${targetDir}`);
|
|
5127
5199
|
}
|
|
@@ -5227,20 +5299,20 @@ import { spawn } from "node:child_process";
|
|
|
5227
5299
|
import { createHash } from "node:crypto";
|
|
5228
5300
|
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5229
5301
|
import { createConnection, createServer } from "node:net";
|
|
5230
|
-
import { basename, join as
|
|
5302
|
+
import { basename, join as join11 } from "node:path";
|
|
5231
5303
|
var STATE_SUFFIX = ".json";
|
|
5232
5304
|
function supervisorDir() {
|
|
5233
|
-
return
|
|
5305
|
+
return join11(accountsHome(), "supervisors");
|
|
5234
5306
|
}
|
|
5235
5307
|
function supervisorStatePath(toolId) {
|
|
5236
|
-
return
|
|
5308
|
+
return join11(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
|
|
5237
5309
|
}
|
|
5238
5310
|
function supervisorSocketPath(toolId) {
|
|
5239
5311
|
if (process.platform === "win32") {
|
|
5240
5312
|
const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
|
|
5241
5313
|
return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
|
|
5242
5314
|
}
|
|
5243
|
-
return
|
|
5315
|
+
return join11(supervisorDir(), `${toolId}.sock`);
|
|
5244
5316
|
}
|
|
5245
5317
|
function nowIso2() {
|
|
5246
5318
|
return new Date().toISOString();
|
|
@@ -5617,7 +5689,7 @@ async function pickProfile(opts = {}) {
|
|
|
5617
5689
|
}
|
|
5618
5690
|
// src/lib/hook.ts
|
|
5619
5691
|
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5620
|
-
import { join as
|
|
5692
|
+
import { join as join12 } from "node:path";
|
|
5621
5693
|
var HOOK_FILE = "claude-hook.sh";
|
|
5622
5694
|
var MARKER = "# accounts-claude-hook";
|
|
5623
5695
|
var NAME_PATTERN = "^[a-z0-9][a-z0-9-]*$";
|
|
@@ -5625,7 +5697,7 @@ function shellQuotePath(path) {
|
|
|
5625
5697
|
return `'${path.replace(/'/g, `'\\''`)}'`;
|
|
5626
5698
|
}
|
|
5627
5699
|
function hookPath() {
|
|
5628
|
-
return
|
|
5700
|
+
return join12(accountsHome(), HOOK_FILE);
|
|
5629
5701
|
}
|
|
5630
5702
|
function hookScript() {
|
|
5631
5703
|
const quotedHook = shellQuotePath(hookPath());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-path.d.ts","sourceRoot":"","sources":["../../src/lib/safe-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"safe-path.d.ts","sourceRoot":"","sources":["../../src/lib/safe-path.ts"],"names":[],"mappings":"AA2GA,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAuB/F"}
|