@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/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 (existsSync(path) && lstatSync(path).isSymbolicLink()) {
4048
+ if (lstatIfExists(path)?.isSymbolicLink()) {
4040
4049
  throw new AccountsError(`${label}: ${path}`);
4041
4050
  }
4042
4051
  }
4043
- function assertDirChainSafe(targetFile, mustStayUnder) {
4044
- const absFile = resolve(targetFile);
4045
- const parent = dirname(absFile);
4046
- const base = mustStayUnder ? resolve(mustStayUnder) : undefined;
4047
- if (base) {
4048
- const rel = absFile.startsWith(base + "/") ? absFile.slice(base.length + 1) : "";
4049
- if (!rel && absFile !== base) {
4050
- throw new AccountsError(`refusing to write outside profile directory: ${targetFile}`);
4051
- }
4052
- const segments = rel.split("/").filter(Boolean);
4053
- if (segments.some((s) => s === "..")) {
4054
- throw new AccountsError(`refusing path traversal: ${targetFile}`);
4055
- }
4056
- let cursor = base;
4057
- for (let i = 0;i < segments.length - 1; i++) {
4058
- cursor = resolve(cursor, segments[i]);
4059
- throwIfSymlink(cursor, "refusing to write under symlink directory");
4060
- }
4061
- } else {
4062
- let cursor = parent;
4063
- for (;; ) {
4064
- throwIfSymlink(cursor, "refusing to write under symlink directory");
4065
- const next = dirname(cursor);
4066
- if (next === cursor)
4067
- break;
4068
- cursor = next;
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
- if (!existsSync(parent))
4076
- mkdirSync(parent, { recursive: true });
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 (opts?.mustStayUnder) {
4081
- const base = realpathSync(resolve(opts.mustStayUnder));
4082
- if (resolved !== base && !resolved.startsWith(base + "/")) {
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 join(homedir(), ".hasna", "accounts");
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 join(accountsHome(), "accounts.json");
4186
+ return join2(accountsHome(), "accounts.json");
4118
4187
  }
4119
4188
  function profilesDir() {
4120
- return join(accountsHome(), "profiles");
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(join(path, ".."), { recursive: true });
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 join2 } from "node:path";
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: join2(homedir2(), ".claude"),
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: join2(homedir2(), ".codex"),
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: join2(homedir2(), ".codex"),
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: join2(homedir2(), ".takumi"),
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: join2(homedir2(), ".gemini"),
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: join2(homedir2(), ".config", "opencode"),
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: join2(homedir2(), ".cursor"),
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: join2(homedir2(), ".pi"),
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: join2(homedir2(), ".hermes"),
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: join2(homedir2(), ".kimi-code"),
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: join2(homedir2(), ".grok"),
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 join4 } from "node:path";
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 join3 } from "node:path";
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 = join3(base, ".claude");
4507
+ const configDir = join4(base, ".claude");
4436
4508
  return {
4437
4509
  configDir,
4438
- homeJson: join3(base, ".claude.json"),
4439
- credentialsFile: join3(configDir, ".credentials.json")
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 = [join3(profileDir, tool.accountFile)];
4517
+ const paths = [join4(profileDir, tool.accountFile)];
4446
4518
  if (profileDir === tool.defaultDir)
4447
- paths.push(join3(dirname2(profileDir), tool.accountFile));
4519
+ paths.push(join4(dirname2(profileDir), tool.accountFile));
4448
4520
  return paths;
4449
4521
  }
4450
4522
  function profileAuthDir(profileDir) {
4451
- return join3(profileDir, ACCOUNTS_AUTH_DIR);
4523
+ return join4(profileDir, ACCOUNTS_AUTH_DIR);
4452
4524
  }
4453
4525
  function profileOAuthSnapshot(profileDir) {
4454
- return join3(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
4526
+ return join4(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
4455
4527
  }
4456
4528
  function profileCredentialsSnapshot(profileDir) {
4457
- return join3(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
4529
+ return join4(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
4458
4530
  }
4459
4531
  function profileKeychainSnapshot(profileDir) {
4460
- return join3(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
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 = join4(configDir, "settings.json");
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(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
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(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
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 = join4(profileDir, ".credentials.json");
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 join5 } from "node:path";
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 = join5(profileDir, "config.toml");
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 join6 } from "node:path";
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 = [join6(dir, tool.accountFile)];
4891
+ const candidates = [join7(dir, tool.accountFile)];
4820
4892
  if (dir === tool.defaultDir)
4821
- candidates.push(join6(dirname4(dir), tool.accountFile));
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 join7, relative, resolve as resolve2 } from "node:path";
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 = join7(homedir4(), out.slice(2));
4860
- return isAbsolute(out) ? out : resolve2(process.cwd(), out);
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 = relative(resolve2(profilesDir()), resolve2(dir));
4872
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
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) : join7(profilesDir(), toolId, name);
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 join8 } from "node:path";
5114
+ import { join as join9 } from "node:path";
5043
5115
  function lockPath() {
5044
- return join8(accountsHome(), ".apply.lock");
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 join9 } from "node:path";
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 = join9(profilesDir(), toolId, name);
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 join10 } from "node:path";
5302
+ import { basename, join as join11 } from "node:path";
5231
5303
  var STATE_SUFFIX = ".json";
5232
5304
  function supervisorDir() {
5233
- return join10(accountsHome(), "supervisors");
5305
+ return join11(accountsHome(), "supervisors");
5234
5306
  }
5235
5307
  function supervisorStatePath(toolId) {
5236
- return join10(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
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 join10(supervisorDir(), `${toolId}.sock`);
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 join11 } from "node:path";
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 join11(accountsHome(), HOOK_FILE);
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":"AAyCA,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAiB/F"}
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"}