@hasna/accounts 0.1.19 → 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 +167 -98
- package/dist/index.js +156 -87
- package/dist/lib/safe-path.d.ts.map +1 -1
- package/dist/mcp.js +142 -73
- package/dist/storage.js +107 -38
- 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";
|
|
4032
|
+
import { join as join2 } from "node:path";
|
|
4033
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,7 +4229,7 @@ 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 });
|
|
4164
4233
|
if (existsSync2(path))
|
|
4165
4234
|
chmodSync(path, 384);
|
|
4166
4235
|
writeFileSync(path, JSON.stringify(store, null, 2) + `
|
|
@@ -4169,7 +4238,7 @@ function saveStore(store) {
|
|
|
4169
4238
|
}
|
|
4170
4239
|
// src/lib/tools.ts
|
|
4171
4240
|
import { homedir as homedir2 } from "node:os";
|
|
4172
|
-
import { join as
|
|
4241
|
+
import { join as join3 } from "node:path";
|
|
4173
4242
|
var BUILTIN_TOOLS = [
|
|
4174
4243
|
{
|
|
4175
4244
|
id: "claude",
|
|
@@ -4178,7 +4247,7 @@ var BUILTIN_TOOLS = [
|
|
|
4178
4247
|
extraEnv: {
|
|
4179
4248
|
TELEGRAM_STATE_DIR: "{profileDir}/channels/telegram"
|
|
4180
4249
|
},
|
|
4181
|
-
defaultDir:
|
|
4250
|
+
defaultDir: join3(homedir2(), ".claude"),
|
|
4182
4251
|
bin: "claude",
|
|
4183
4252
|
loginHint: "run /login inside Claude, then /exit when done",
|
|
4184
4253
|
resumeArgs: ["--continue"],
|
|
@@ -4198,7 +4267,7 @@ var BUILTIN_TOOLS = [
|
|
|
4198
4267
|
id: "codex-app",
|
|
4199
4268
|
label: "Codex App",
|
|
4200
4269
|
envVar: "CODEX_HOME",
|
|
4201
|
-
defaultDir:
|
|
4270
|
+
defaultDir: join3(homedir2(), ".codex"),
|
|
4202
4271
|
bin: "/Applications/Codex.app/Contents/MacOS/Codex",
|
|
4203
4272
|
loginHint: "sign in inside Codex.app, then quit the app when the profile is ready",
|
|
4204
4273
|
launchArgs: ["--user-data-dir={profileDir}/electron-user-data"],
|
|
@@ -4208,7 +4277,7 @@ var BUILTIN_TOOLS = [
|
|
|
4208
4277
|
id: "codex",
|
|
4209
4278
|
label: "Codex CLI",
|
|
4210
4279
|
envVar: "CODEX_HOME",
|
|
4211
|
-
defaultDir:
|
|
4280
|
+
defaultDir: join3(homedir2(), ".codex"),
|
|
4212
4281
|
bin: "codex",
|
|
4213
4282
|
loginArgs: ["login"],
|
|
4214
4283
|
loginHint: "complete the Codex login flow for this CODEX_HOME",
|
|
@@ -4221,7 +4290,7 @@ var BUILTIN_TOOLS = [
|
|
|
4221
4290
|
id: "takumi",
|
|
4222
4291
|
label: "Takumi",
|
|
4223
4292
|
envVar: "TAKUMI_CONFIG_DIR",
|
|
4224
|
-
defaultDir:
|
|
4293
|
+
defaultDir: join3(homedir2(), ".takumi"),
|
|
4225
4294
|
bin: "takumi",
|
|
4226
4295
|
loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
|
|
4227
4296
|
resumeArgs: ["--continue"],
|
|
@@ -4241,7 +4310,7 @@ var BUILTIN_TOOLS = [
|
|
|
4241
4310
|
id: "gemini",
|
|
4242
4311
|
label: "Gemini CLI",
|
|
4243
4312
|
envVar: "GEMINI_CONFIG_DIR",
|
|
4244
|
-
defaultDir:
|
|
4313
|
+
defaultDir: join3(homedir2(), ".gemini"),
|
|
4245
4314
|
bin: "gemini",
|
|
4246
4315
|
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR",
|
|
4247
4316
|
permissionArgs: {
|
|
@@ -4259,7 +4328,7 @@ var BUILTIN_TOOLS = [
|
|
|
4259
4328
|
XDG_CONFIG_HOME: "{profileDir}/xdg-config",
|
|
4260
4329
|
XDG_DATA_HOME: "{profileDir}/xdg-data"
|
|
4261
4330
|
},
|
|
4262
|
-
defaultDir:
|
|
4331
|
+
defaultDir: join3(homedir2(), ".config", "opencode"),
|
|
4263
4332
|
bin: "opencode",
|
|
4264
4333
|
loginArgs: ["auth", "login"],
|
|
4265
4334
|
loginHint: "complete opencode auth login for this isolated config/data root",
|
|
@@ -4269,7 +4338,7 @@ var BUILTIN_TOOLS = [
|
|
|
4269
4338
|
id: "cursor",
|
|
4270
4339
|
label: "Cursor Agent",
|
|
4271
4340
|
envVar: "CURSOR_CONFIG_DIR",
|
|
4272
|
-
defaultDir:
|
|
4341
|
+
defaultDir: join3(homedir2(), ".cursor"),
|
|
4273
4342
|
bin: "cursor-agent",
|
|
4274
4343
|
loginArgs: ["login"],
|
|
4275
4344
|
loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
|
|
@@ -4278,7 +4347,7 @@ var BUILTIN_TOOLS = [
|
|
|
4278
4347
|
id: "pi",
|
|
4279
4348
|
label: "Pi Coding Agent",
|
|
4280
4349
|
envVar: "PI_CODING_AGENT_HOME",
|
|
4281
|
-
defaultDir:
|
|
4350
|
+
defaultDir: join3(homedir2(), ".pi"),
|
|
4282
4351
|
bin: "pi",
|
|
4283
4352
|
loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
|
|
4284
4353
|
},
|
|
@@ -4286,7 +4355,7 @@ var BUILTIN_TOOLS = [
|
|
|
4286
4355
|
id: "hermes",
|
|
4287
4356
|
label: "Hermes",
|
|
4288
4357
|
envVar: "HERMES_HOME",
|
|
4289
|
-
defaultDir:
|
|
4358
|
+
defaultDir: join3(homedir2(), ".hermes"),
|
|
4290
4359
|
bin: "hermes",
|
|
4291
4360
|
loginHint: "complete Hermes auth in this HERMES_HOME",
|
|
4292
4361
|
permissionArgs: {
|
|
@@ -4298,7 +4367,7 @@ var BUILTIN_TOOLS = [
|
|
|
4298
4367
|
id: "kimi",
|
|
4299
4368
|
label: "Kimi Code",
|
|
4300
4369
|
envVar: "KIMI_CODE_HOME",
|
|
4301
|
-
defaultDir:
|
|
4370
|
+
defaultDir: join3(homedir2(), ".kimi-code"),
|
|
4302
4371
|
bin: "kimi",
|
|
4303
4372
|
loginArgs: ["login"],
|
|
4304
4373
|
loginHint: "complete kimi login for this KIMI_CODE_HOME",
|
|
@@ -4313,7 +4382,7 @@ var BUILTIN_TOOLS = [
|
|
|
4313
4382
|
id: "grok",
|
|
4314
4383
|
label: "Grok Build",
|
|
4315
4384
|
envVar: "HOME",
|
|
4316
|
-
defaultDir:
|
|
4385
|
+
defaultDir: join3(homedir2(), ".grok"),
|
|
4317
4386
|
bin: "grok",
|
|
4318
4387
|
loginArgs: ["login"],
|
|
4319
4388
|
loginHint: "complete grok login in this process-scoped HOME; prefer launch/shell over exporting HOME globally"
|
|
@@ -4419,11 +4488,11 @@ function removeCustomTool(id) {
|
|
|
4419
4488
|
}
|
|
4420
4489
|
// src/lib/claude-auth.ts
|
|
4421
4490
|
import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4422
|
-
import { dirname as dirname3, join as
|
|
4491
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
4423
4492
|
|
|
4424
4493
|
// src/lib/claude-layout.ts
|
|
4425
4494
|
import { homedir as homedir3 } from "node:os";
|
|
4426
|
-
import { dirname as dirname2, join as
|
|
4495
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
4427
4496
|
var CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
4428
4497
|
var ACCOUNTS_AUTH_DIR = ".accounts-auth";
|
|
4429
4498
|
var OAUTH_SNAPSHOT = "oauth-account.json";
|
|
@@ -4435,32 +4504,32 @@ function liveClaudeBase() {
|
|
|
4435
4504
|
}
|
|
4436
4505
|
function liveClaudePaths() {
|
|
4437
4506
|
const base = liveClaudeBase();
|
|
4438
|
-
const configDir =
|
|
4507
|
+
const configDir = join4(base, ".claude");
|
|
4439
4508
|
return {
|
|
4440
4509
|
configDir,
|
|
4441
|
-
homeJson:
|
|
4442
|
-
credentialsFile:
|
|
4510
|
+
homeJson: join4(base, ".claude.json"),
|
|
4511
|
+
credentialsFile: join4(configDir, ".credentials.json")
|
|
4443
4512
|
};
|
|
4444
4513
|
}
|
|
4445
4514
|
function profileAccountJsonPaths(profileDir, tool) {
|
|
4446
4515
|
if (!tool.accountFile)
|
|
4447
4516
|
return [];
|
|
4448
|
-
const paths = [
|
|
4517
|
+
const paths = [join4(profileDir, tool.accountFile)];
|
|
4449
4518
|
if (profileDir === tool.defaultDir)
|
|
4450
|
-
paths.push(
|
|
4519
|
+
paths.push(join4(dirname2(profileDir), tool.accountFile));
|
|
4451
4520
|
return paths;
|
|
4452
4521
|
}
|
|
4453
4522
|
function profileAuthDir(profileDir) {
|
|
4454
|
-
return
|
|
4523
|
+
return join4(profileDir, ACCOUNTS_AUTH_DIR);
|
|
4455
4524
|
}
|
|
4456
4525
|
function profileOAuthSnapshot(profileDir) {
|
|
4457
|
-
return
|
|
4526
|
+
return join4(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
|
|
4458
4527
|
}
|
|
4459
4528
|
function profileCredentialsSnapshot(profileDir) {
|
|
4460
|
-
return
|
|
4529
|
+
return join4(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
|
|
4461
4530
|
}
|
|
4462
4531
|
function profileKeychainSnapshot(profileDir) {
|
|
4463
|
-
return
|
|
4532
|
+
return join4(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
|
|
4464
4533
|
}
|
|
4465
4534
|
|
|
4466
4535
|
// src/lib/keychain.ts
|
|
@@ -4614,7 +4683,7 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
|
|
|
4614
4683
|
}
|
|
4615
4684
|
}
|
|
4616
4685
|
function sanitizeSettingsFile(configDir, stayUnder) {
|
|
4617
|
-
const settingsPath =
|
|
4686
|
+
const settingsPath = join5(configDir, "settings.json");
|
|
4618
4687
|
const settings = readJsonFile(settingsPath);
|
|
4619
4688
|
if (!settings)
|
|
4620
4689
|
return false;
|
|
@@ -4661,7 +4730,7 @@ function liveOAuthEmail() {
|
|
|
4661
4730
|
}
|
|
4662
4731
|
function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
4663
4732
|
const authDir = profileAuthDir(profileDir);
|
|
4664
|
-
assertSafeWritePath(
|
|
4733
|
+
assertSafeWritePath(join5(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
4665
4734
|
mkdirSync3(authDir, { recursive: true });
|
|
4666
4735
|
const live = liveClaudePaths();
|
|
4667
4736
|
const oauth = readOAuthFromPaths([live.homeJson]);
|
|
@@ -4683,14 +4752,14 @@ function snapshotClaudeAuthToProfile(profileDir, tool) {
|
|
|
4683
4752
|
}
|
|
4684
4753
|
function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
|
|
4685
4754
|
const authDir = profileAuthDir(profileDir);
|
|
4686
|
-
assertSafeWritePath(
|
|
4755
|
+
assertSafeWritePath(join5(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
4687
4756
|
mkdirSync3(authDir, { recursive: true });
|
|
4688
4757
|
const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
|
|
4689
4758
|
const oauthSnap = profileOAuthSnapshot(profileDir);
|
|
4690
4759
|
if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
|
|
4691
4760
|
writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
|
|
4692
4761
|
}
|
|
4693
|
-
const credFile =
|
|
4762
|
+
const credFile = join5(profileDir, ".credentials.json");
|
|
4694
4763
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
4695
4764
|
if (existsSync3(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
|
|
4696
4765
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
@@ -4752,7 +4821,7 @@ function hasAuthSnapshot(profileDir) {
|
|
|
4752
4821
|
|
|
4753
4822
|
// src/lib/codex-app.ts
|
|
4754
4823
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4755
|
-
import { join as
|
|
4824
|
+
import { join as join6 } from "node:path";
|
|
4756
4825
|
var FILE_CREDENTIALS_LINE = 'cli_auth_credentials_store = "file"';
|
|
4757
4826
|
function insertRootConfigLine(config, line) {
|
|
4758
4827
|
if (config.trim() === "")
|
|
@@ -4779,7 +4848,7 @@ ${after}${after.endsWith(`
|
|
|
4779
4848
|
}
|
|
4780
4849
|
function ensureCodexAppProfileConfig(profileDir) {
|
|
4781
4850
|
mkdirSync4(profileDir, { recursive: true });
|
|
4782
|
-
const configPath =
|
|
4851
|
+
const configPath = join6(profileDir, "config.toml");
|
|
4783
4852
|
const current = existsSync4(configPath) ? readFileSync3(configPath, "utf8") : "";
|
|
4784
4853
|
if (/^\s*cli_auth_credentials_store\s*=/.test(current))
|
|
4785
4854
|
return;
|
|
@@ -4815,13 +4884,13 @@ function formatExportLines(env) {
|
|
|
4815
4884
|
}
|
|
4816
4885
|
// src/lib/detect.ts
|
|
4817
4886
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
4818
|
-
import { dirname as dirname4, join as
|
|
4887
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
4819
4888
|
function detectEmail(dir, tool) {
|
|
4820
4889
|
if (!tool.accountFile || !tool.emailPath)
|
|
4821
4890
|
return;
|
|
4822
|
-
const candidates = [
|
|
4891
|
+
const candidates = [join7(dir, tool.accountFile)];
|
|
4823
4892
|
if (dir === tool.defaultDir)
|
|
4824
|
-
candidates.push(
|
|
4893
|
+
candidates.push(join7(dirname4(dir), tool.accountFile));
|
|
4825
4894
|
for (const file of candidates) {
|
|
4826
4895
|
const email = readEmail(file, tool.emailPath);
|
|
4827
4896
|
if (email)
|
|
@@ -4849,7 +4918,7 @@ function readEmail(file, path) {
|
|
|
4849
4918
|
}
|
|
4850
4919
|
// src/lib/profiles.ts
|
|
4851
4920
|
import { homedir as homedir4 } from "node:os";
|
|
4852
|
-
import { isAbsolute, join as
|
|
4921
|
+
import { isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4853
4922
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, rmSync } from "node:fs";
|
|
4854
4923
|
function nowIso() {
|
|
4855
4924
|
return new Date().toISOString();
|
|
@@ -4859,8 +4928,8 @@ function expandPath(p) {
|
|
|
4859
4928
|
if (out === "~")
|
|
4860
4929
|
out = homedir4();
|
|
4861
4930
|
else if (out.startsWith("~/"))
|
|
4862
|
-
out =
|
|
4863
|
-
return
|
|
4931
|
+
out = join8(homedir4(), out.slice(2));
|
|
4932
|
+
return isAbsolute2(out) ? out : resolve2(process.cwd(), out);
|
|
4864
4933
|
}
|
|
4865
4934
|
function listProfiles(toolId) {
|
|
4866
4935
|
const profiles = loadStore().profiles;
|
|
@@ -4871,8 +4940,8 @@ function profileMatches(name, toolId) {
|
|
|
4871
4940
|
return loadStore().profiles.filter((p) => p.name === name && (!toolId || p.tool === toolId));
|
|
4872
4941
|
}
|
|
4873
4942
|
function isManagedProfileDir(dir) {
|
|
4874
|
-
const rel =
|
|
4875
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
4943
|
+
const rel = relative2(resolve2(profilesDir()), resolve2(dir));
|
|
4944
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
4876
4945
|
}
|
|
4877
4946
|
function findProfile(name, toolId) {
|
|
4878
4947
|
const matches = profileMatches(name, toolId);
|
|
@@ -4901,7 +4970,7 @@ function addProfile(opts) {
|
|
|
4901
4970
|
if (store.profiles.some((p) => p.name === name && p.tool === toolId)) {
|
|
4902
4971
|
throw new AccountsError(`a ${toolId} profile named "${name}" already exists`);
|
|
4903
4972
|
}
|
|
4904
|
-
const dir = opts.dir ? expandPath(opts.dir) :
|
|
4973
|
+
const dir = opts.dir ? expandPath(opts.dir) : join8(profilesDir(), toolId, name);
|
|
4905
4974
|
if (store.profiles.some((p) => p.dir === dir)) {
|
|
4906
4975
|
throw new AccountsError(`a profile already uses config dir ${dir}`);
|
|
4907
4976
|
}
|
|
@@ -5042,9 +5111,9 @@ function currentProfile(toolId) {
|
|
|
5042
5111
|
}
|
|
5043
5112
|
// src/lib/apply-lock.ts
|
|
5044
5113
|
import { closeSync, existsSync as existsSync7, mkdirSync as mkdirSync6, openSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5045
|
-
import { join as
|
|
5114
|
+
import { join as join9 } from "node:path";
|
|
5046
5115
|
function lockPath() {
|
|
5047
|
-
return
|
|
5116
|
+
return join9(accountsHome(), ".apply.lock");
|
|
5048
5117
|
}
|
|
5049
5118
|
function withApplyLock(fn) {
|
|
5050
5119
|
const home = accountsHome();
|
|
@@ -5114,7 +5183,7 @@ function applyProfileUnlocked(name, toolId) {
|
|
|
5114
5183
|
}
|
|
5115
5184
|
// src/lib/import-profile.ts
|
|
5116
5185
|
import { cpSync, existsSync as existsSync8 } from "node:fs";
|
|
5117
|
-
import { join as
|
|
5186
|
+
import { join as join10 } from "node:path";
|
|
5118
5187
|
function importProfile(opts) {
|
|
5119
5188
|
const toolId = opts.tool ?? DEFAULT_TOOL;
|
|
5120
5189
|
const tool = getTool(toolId);
|
|
@@ -5124,7 +5193,7 @@ function importProfile(opts) {
|
|
|
5124
5193
|
throw new AccountsError(`config dir does not exist: ${sourceDir}`);
|
|
5125
5194
|
}
|
|
5126
5195
|
if (opts.copy) {
|
|
5127
|
-
const targetDir =
|
|
5196
|
+
const targetDir = join10(profilesDir(), toolId, name);
|
|
5128
5197
|
if (existsSync8(targetDir)) {
|
|
5129
5198
|
throw new AccountsError(`managed copy target already exists: ${targetDir}`);
|
|
5130
5199
|
}
|
|
@@ -5230,20 +5299,20 @@ import { spawn } from "node:child_process";
|
|
|
5230
5299
|
import { createHash } from "node:crypto";
|
|
5231
5300
|
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5232
5301
|
import { createConnection, createServer } from "node:net";
|
|
5233
|
-
import { basename, join as
|
|
5302
|
+
import { basename, join as join11 } from "node:path";
|
|
5234
5303
|
var STATE_SUFFIX = ".json";
|
|
5235
5304
|
function supervisorDir() {
|
|
5236
|
-
return
|
|
5305
|
+
return join11(accountsHome(), "supervisors");
|
|
5237
5306
|
}
|
|
5238
5307
|
function supervisorStatePath(toolId) {
|
|
5239
|
-
return
|
|
5308
|
+
return join11(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
|
|
5240
5309
|
}
|
|
5241
5310
|
function supervisorSocketPath(toolId) {
|
|
5242
5311
|
if (process.platform === "win32") {
|
|
5243
5312
|
const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
|
|
5244
5313
|
return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
|
|
5245
5314
|
}
|
|
5246
|
-
return
|
|
5315
|
+
return join11(supervisorDir(), `${toolId}.sock`);
|
|
5247
5316
|
}
|
|
5248
5317
|
function nowIso2() {
|
|
5249
5318
|
return new Date().toISOString();
|
|
@@ -5620,7 +5689,7 @@ async function pickProfile(opts = {}) {
|
|
|
5620
5689
|
}
|
|
5621
5690
|
// src/lib/hook.ts
|
|
5622
5691
|
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5623
|
-
import { join as
|
|
5692
|
+
import { join as join12 } from "node:path";
|
|
5624
5693
|
var HOOK_FILE = "claude-hook.sh";
|
|
5625
5694
|
var MARKER = "# accounts-claude-hook";
|
|
5626
5695
|
var NAME_PATTERN = "^[a-z0-9][a-z0-9-]*$";
|
|
@@ -5628,7 +5697,7 @@ function shellQuotePath(path) {
|
|
|
5628
5697
|
return `'${path.replace(/'/g, `'\\''`)}'`;
|
|
5629
5698
|
}
|
|
5630
5699
|
function hookPath() {
|
|
5631
|
-
return
|
|
5700
|
+
return join12(accountsHome(), HOOK_FILE);
|
|
5632
5701
|
}
|
|
5633
5702
|
function hookScript() {
|
|
5634
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"}
|