@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/mcp.js
CHANGED
|
@@ -16665,57 +16665,126 @@ class AccountsError extends Error {
|
|
|
16665
16665
|
|
|
16666
16666
|
// src/storage.ts
|
|
16667
16667
|
import { homedir } from "node:os";
|
|
16668
|
-
import { join } from "node:path";
|
|
16669
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
16668
|
+
import { join as join2 } from "node:path";
|
|
16669
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
16670
16670
|
|
|
16671
16671
|
// src/lib/safe-path.ts
|
|
16672
16672
|
import { existsSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
16673
|
-
import { dirname, resolve } from "node:path";
|
|
16673
|
+
import { dirname, isAbsolute, join, parse as parse5, relative, resolve, sep } from "node:path";
|
|
16674
|
+
function lstatIfExists(path) {
|
|
16675
|
+
try {
|
|
16676
|
+
return lstatSync(path);
|
|
16677
|
+
} catch (err) {
|
|
16678
|
+
if (err.code !== "ENOENT")
|
|
16679
|
+
throw err;
|
|
16680
|
+
return;
|
|
16681
|
+
}
|
|
16682
|
+
}
|
|
16674
16683
|
function throwIfSymlink(path, label) {
|
|
16675
|
-
if (
|
|
16684
|
+
if (lstatIfExists(path)?.isSymbolicLink()) {
|
|
16676
16685
|
throw new AccountsError(`${label}: ${path}`);
|
|
16677
16686
|
}
|
|
16678
16687
|
}
|
|
16679
|
-
function
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
}
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
16695
|
-
|
|
16696
|
-
}
|
|
16697
|
-
}
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
|
|
16688
|
+
function isAllowedSystemDirectorySymlink(path) {
|
|
16689
|
+
if (path !== "/var" && path !== "/tmp")
|
|
16690
|
+
return false;
|
|
16691
|
+
try {
|
|
16692
|
+
return realpathSync(path) === `/private${path}`;
|
|
16693
|
+
} catch {
|
|
16694
|
+
return false;
|
|
16695
|
+
}
|
|
16696
|
+
}
|
|
16697
|
+
function assertDirectory(path, label, opts) {
|
|
16698
|
+
const stat = lstatIfExists(path);
|
|
16699
|
+
if (!stat) {
|
|
16700
|
+
throw new AccountsError(`refusing to write under missing directory: ${path}`);
|
|
16701
|
+
}
|
|
16702
|
+
if (stat.isSymbolicLink()) {
|
|
16703
|
+
if (opts?.allowSystemSymlink && isAllowedSystemDirectorySymlink(path))
|
|
16704
|
+
return;
|
|
16705
|
+
throw new AccountsError(`${label}: ${path}`);
|
|
16706
|
+
}
|
|
16707
|
+
if (!stat.isDirectory()) {
|
|
16708
|
+
throw new AccountsError(`refusing to write under non-directory path: ${path}`);
|
|
16709
|
+
}
|
|
16710
|
+
}
|
|
16711
|
+
function assertInsideBase(absPath, base, originalPath) {
|
|
16712
|
+
const rel = relative(base, absPath);
|
|
16713
|
+
if (rel === "")
|
|
16714
|
+
return rel;
|
|
16715
|
+
if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) {
|
|
16716
|
+
throw new AccountsError(`refusing to write outside profile directory: ${originalPath}`);
|
|
16717
|
+
}
|
|
16718
|
+
return rel;
|
|
16719
|
+
}
|
|
16720
|
+
function assertExistingDirectoryComponentsSafe(path, label) {
|
|
16721
|
+
const root = parse5(path).root;
|
|
16722
|
+
const rel = relative(root, path);
|
|
16723
|
+
const segments = rel ? rel.split(sep).filter(Boolean) : [];
|
|
16724
|
+
let cursor = root;
|
|
16725
|
+
assertDirectory(cursor, label, { allowSystemSymlink: true });
|
|
16726
|
+
for (const segment of segments) {
|
|
16727
|
+
cursor = join(cursor, segment);
|
|
16728
|
+
if (!lstatIfExists(cursor))
|
|
16729
|
+
return;
|
|
16730
|
+
assertDirectory(cursor, label, { allowSystemSymlink: true });
|
|
16731
|
+
}
|
|
16732
|
+
}
|
|
16733
|
+
function ensureBoundaryRootSafe(base) {
|
|
16734
|
+
const missing = [];
|
|
16735
|
+
let cursor = base;
|
|
16736
|
+
while (!lstatIfExists(cursor)) {
|
|
16737
|
+
missing.unshift(cursor);
|
|
16738
|
+
const parent = dirname(cursor);
|
|
16739
|
+
if (parent === cursor)
|
|
16740
|
+
break;
|
|
16741
|
+
cursor = parent;
|
|
16742
|
+
}
|
|
16743
|
+
assertDirectory(cursor, "refusing to use symlink base directory");
|
|
16744
|
+
for (const dir of missing) {
|
|
16745
|
+
if (lstatIfExists(dir)) {
|
|
16746
|
+
assertDirectory(dir, "refusing to use symlink base directory");
|
|
16747
|
+
} else {
|
|
16748
|
+
mkdirSync(dir);
|
|
16749
|
+
assertDirectory(dir, "refusing to use symlink base directory");
|
|
16750
|
+
}
|
|
16751
|
+
}
|
|
16752
|
+
assertDirectory(base, "refusing to use symlink base directory");
|
|
16753
|
+
}
|
|
16754
|
+
function ensureDirectoryChainSafe(parent, startAt) {
|
|
16755
|
+
const root = startAt ?? parse5(parent).root;
|
|
16756
|
+
const rel = relative(root, parent);
|
|
16757
|
+
const segments = rel ? rel.split(sep).filter(Boolean) : [];
|
|
16758
|
+
let cursor = root;
|
|
16759
|
+
if (startAt)
|
|
16760
|
+
assertDirectory(startAt, "refusing to write under symlink directory");
|
|
16761
|
+
for (const segment of segments) {
|
|
16762
|
+
cursor = join(cursor, segment);
|
|
16763
|
+
if (lstatIfExists(cursor)) {
|
|
16764
|
+
assertDirectory(cursor, "refusing to write under symlink directory");
|
|
16765
|
+
} else {
|
|
16766
|
+
mkdirSync(cursor);
|
|
16767
|
+
assertDirectory(cursor, "refusing to write under symlink directory");
|
|
16705
16768
|
}
|
|
16706
16769
|
}
|
|
16707
16770
|
}
|
|
16708
16771
|
function assertSafeWritePath(filePath, opts) {
|
|
16709
16772
|
const absFile = resolve(filePath);
|
|
16710
16773
|
const parent = dirname(absFile);
|
|
16711
|
-
|
|
16712
|
-
|
|
16774
|
+
const base = opts?.mustStayUnder ? resolve(opts.mustStayUnder) : undefined;
|
|
16775
|
+
if (base) {
|
|
16776
|
+
assertInsideBase(absFile, base, filePath);
|
|
16777
|
+
assertExistingDirectoryComponentsSafe(base, "refusing to use symlink base directory");
|
|
16778
|
+
ensureBoundaryRootSafe(base);
|
|
16779
|
+
ensureDirectoryChainSafe(parent, base);
|
|
16780
|
+
} else {
|
|
16781
|
+
ensureDirectoryChainSafe(parent);
|
|
16782
|
+
}
|
|
16713
16783
|
throwIfSymlink(absFile, "refusing to write through symlink");
|
|
16714
|
-
assertDirChainSafe(absFile, opts?.mustStayUnder);
|
|
16715
16784
|
const resolved = realpathSync(existsSync(absFile) ? absFile : parent);
|
|
16716
|
-
if (
|
|
16717
|
-
const
|
|
16718
|
-
if (resolved !==
|
|
16785
|
+
if (base) {
|
|
16786
|
+
const realBase = realpathSync(base);
|
|
16787
|
+
if (resolved !== realBase && !resolved.startsWith(realBase + sep)) {
|
|
16719
16788
|
throw new AccountsError(`refusing to write outside profile directory: ${filePath}`);
|
|
16720
16789
|
}
|
|
16721
16790
|
}
|
|
@@ -16744,13 +16813,13 @@ function accountsHome() {
|
|
|
16744
16813
|
const override = process.env.ACCOUNTS_HOME;
|
|
16745
16814
|
if (override && override.trim())
|
|
16746
16815
|
return validateEnvPath(override, "ACCOUNTS_HOME");
|
|
16747
|
-
return
|
|
16816
|
+
return join2(homedir(), ".hasna", "accounts");
|
|
16748
16817
|
}
|
|
16749
16818
|
function storePath() {
|
|
16750
16819
|
const override = process.env.ACCOUNTS_STORE_PATH;
|
|
16751
16820
|
if (override && override.trim())
|
|
16752
16821
|
return validateEnvPath(override, "ACCOUNTS_STORE_PATH");
|
|
16753
|
-
return
|
|
16822
|
+
return join2(accountsHome(), "accounts.json");
|
|
16754
16823
|
}
|
|
16755
16824
|
var EMPTY_STORE = { version: 1, current: {}, applied: {}, profiles: [], tools: [] };
|
|
16756
16825
|
function loadStore() {
|
|
@@ -16793,14 +16862,17 @@ function loadStore() {
|
|
|
16793
16862
|
function saveStore(store) {
|
|
16794
16863
|
const path = storePath();
|
|
16795
16864
|
assertSafeWritePath(path, { mustStayUnder: accountsHome() });
|
|
16796
|
-
mkdirSync2(
|
|
16865
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
16866
|
+
if (existsSync2(path))
|
|
16867
|
+
chmodSync(path, 384);
|
|
16797
16868
|
writeFileSync(path, JSON.stringify(store, null, 2) + `
|
|
16798
16869
|
`, { mode: 384 });
|
|
16870
|
+
chmodSync(path, 384);
|
|
16799
16871
|
}
|
|
16800
16872
|
|
|
16801
16873
|
// src/lib/tools.ts
|
|
16802
16874
|
import { homedir as homedir2 } from "node:os";
|
|
16803
|
-
import { join as
|
|
16875
|
+
import { join as join3 } from "node:path";
|
|
16804
16876
|
var BUILTIN_TOOLS = [
|
|
16805
16877
|
{
|
|
16806
16878
|
id: "claude",
|
|
@@ -16809,7 +16881,7 @@ var BUILTIN_TOOLS = [
|
|
|
16809
16881
|
extraEnv: {
|
|
16810
16882
|
TELEGRAM_STATE_DIR: "{profileDir}/channels/telegram"
|
|
16811
16883
|
},
|
|
16812
|
-
defaultDir:
|
|
16884
|
+
defaultDir: join3(homedir2(), ".claude"),
|
|
16813
16885
|
bin: "claude",
|
|
16814
16886
|
loginHint: "run /login inside Claude, then /exit when done",
|
|
16815
16887
|
resumeArgs: ["--continue"],
|
|
@@ -16829,7 +16901,7 @@ var BUILTIN_TOOLS = [
|
|
|
16829
16901
|
id: "codex-app",
|
|
16830
16902
|
label: "Codex App",
|
|
16831
16903
|
envVar: "CODEX_HOME",
|
|
16832
|
-
defaultDir:
|
|
16904
|
+
defaultDir: join3(homedir2(), ".codex"),
|
|
16833
16905
|
bin: "/Applications/Codex.app/Contents/MacOS/Codex",
|
|
16834
16906
|
loginHint: "sign in inside Codex.app, then quit the app when the profile is ready",
|
|
16835
16907
|
launchArgs: ["--user-data-dir={profileDir}/electron-user-data"],
|
|
@@ -16839,7 +16911,7 @@ var BUILTIN_TOOLS = [
|
|
|
16839
16911
|
id: "codex",
|
|
16840
16912
|
label: "Codex CLI",
|
|
16841
16913
|
envVar: "CODEX_HOME",
|
|
16842
|
-
defaultDir:
|
|
16914
|
+
defaultDir: join3(homedir2(), ".codex"),
|
|
16843
16915
|
bin: "codex",
|
|
16844
16916
|
loginArgs: ["login"],
|
|
16845
16917
|
loginHint: "complete the Codex login flow for this CODEX_HOME",
|
|
@@ -16852,7 +16924,7 @@ var BUILTIN_TOOLS = [
|
|
|
16852
16924
|
id: "takumi",
|
|
16853
16925
|
label: "Takumi",
|
|
16854
16926
|
envVar: "TAKUMI_CONFIG_DIR",
|
|
16855
|
-
defaultDir:
|
|
16927
|
+
defaultDir: join3(homedir2(), ".takumi"),
|
|
16856
16928
|
bin: "takumi",
|
|
16857
16929
|
loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
|
|
16858
16930
|
resumeArgs: ["--continue"],
|
|
@@ -16872,7 +16944,7 @@ var BUILTIN_TOOLS = [
|
|
|
16872
16944
|
id: "gemini",
|
|
16873
16945
|
label: "Gemini CLI",
|
|
16874
16946
|
envVar: "GEMINI_CONFIG_DIR",
|
|
16875
|
-
defaultDir:
|
|
16947
|
+
defaultDir: join3(homedir2(), ".gemini"),
|
|
16876
16948
|
bin: "gemini",
|
|
16877
16949
|
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR",
|
|
16878
16950
|
permissionArgs: {
|
|
@@ -16890,7 +16962,7 @@ var BUILTIN_TOOLS = [
|
|
|
16890
16962
|
XDG_CONFIG_HOME: "{profileDir}/xdg-config",
|
|
16891
16963
|
XDG_DATA_HOME: "{profileDir}/xdg-data"
|
|
16892
16964
|
},
|
|
16893
|
-
defaultDir:
|
|
16965
|
+
defaultDir: join3(homedir2(), ".config", "opencode"),
|
|
16894
16966
|
bin: "opencode",
|
|
16895
16967
|
loginArgs: ["auth", "login"],
|
|
16896
16968
|
loginHint: "complete opencode auth login for this isolated config/data root",
|
|
@@ -16900,7 +16972,7 @@ var BUILTIN_TOOLS = [
|
|
|
16900
16972
|
id: "cursor",
|
|
16901
16973
|
label: "Cursor Agent",
|
|
16902
16974
|
envVar: "CURSOR_CONFIG_DIR",
|
|
16903
|
-
defaultDir:
|
|
16975
|
+
defaultDir: join3(homedir2(), ".cursor"),
|
|
16904
16976
|
bin: "cursor-agent",
|
|
16905
16977
|
loginArgs: ["login"],
|
|
16906
16978
|
loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
|
|
@@ -16909,7 +16981,7 @@ var BUILTIN_TOOLS = [
|
|
|
16909
16981
|
id: "pi",
|
|
16910
16982
|
label: "Pi Coding Agent",
|
|
16911
16983
|
envVar: "PI_CODING_AGENT_HOME",
|
|
16912
|
-
defaultDir:
|
|
16984
|
+
defaultDir: join3(homedir2(), ".pi"),
|
|
16913
16985
|
bin: "pi",
|
|
16914
16986
|
loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
|
|
16915
16987
|
},
|
|
@@ -16917,7 +16989,7 @@ var BUILTIN_TOOLS = [
|
|
|
16917
16989
|
id: "hermes",
|
|
16918
16990
|
label: "Hermes",
|
|
16919
16991
|
envVar: "HERMES_HOME",
|
|
16920
|
-
defaultDir:
|
|
16992
|
+
defaultDir: join3(homedir2(), ".hermes"),
|
|
16921
16993
|
bin: "hermes",
|
|
16922
16994
|
loginHint: "complete Hermes auth in this HERMES_HOME",
|
|
16923
16995
|
permissionArgs: {
|
|
@@ -16929,7 +17001,7 @@ var BUILTIN_TOOLS = [
|
|
|
16929
17001
|
id: "kimi",
|
|
16930
17002
|
label: "Kimi Code",
|
|
16931
17003
|
envVar: "KIMI_CODE_HOME",
|
|
16932
|
-
defaultDir:
|
|
17004
|
+
defaultDir: join3(homedir2(), ".kimi-code"),
|
|
16933
17005
|
bin: "kimi",
|
|
16934
17006
|
loginArgs: ["login"],
|
|
16935
17007
|
loginHint: "complete kimi login for this KIMI_CODE_HOME",
|
|
@@ -16944,7 +17016,7 @@ var BUILTIN_TOOLS = [
|
|
|
16944
17016
|
id: "grok",
|
|
16945
17017
|
label: "Grok Build",
|
|
16946
17018
|
envVar: "HOME",
|
|
16947
|
-
defaultDir:
|
|
17019
|
+
defaultDir: join3(homedir2(), ".grok"),
|
|
16948
17020
|
bin: "grok",
|
|
16949
17021
|
loginArgs: ["login"],
|
|
16950
17022
|
loginHint: "complete grok login in this process-scoped HOME; prefer launch/shell over exporting HOME globally"
|
|
@@ -17064,11 +17136,11 @@ function currentProfile(toolId) {
|
|
|
17064
17136
|
|
|
17065
17137
|
// src/lib/claude-auth.ts
|
|
17066
17138
|
import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
17067
|
-
import { dirname as dirname3, join as
|
|
17139
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
17068
17140
|
|
|
17069
17141
|
// src/lib/claude-layout.ts
|
|
17070
17142
|
import { homedir as homedir3 } from "node:os";
|
|
17071
|
-
import { dirname as dirname2, join as
|
|
17143
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
17072
17144
|
var CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
17073
17145
|
var ACCOUNTS_AUTH_DIR = ".accounts-auth";
|
|
17074
17146
|
var OAUTH_SNAPSHOT = "oauth-account.json";
|
|
@@ -17080,32 +17152,32 @@ function liveClaudeBase() {
|
|
|
17080
17152
|
}
|
|
17081
17153
|
function liveClaudePaths() {
|
|
17082
17154
|
const base = liveClaudeBase();
|
|
17083
|
-
const configDir =
|
|
17155
|
+
const configDir = join4(base, ".claude");
|
|
17084
17156
|
return {
|
|
17085
17157
|
configDir,
|
|
17086
|
-
homeJson:
|
|
17087
|
-
credentialsFile:
|
|
17158
|
+
homeJson: join4(base, ".claude.json"),
|
|
17159
|
+
credentialsFile: join4(configDir, ".credentials.json")
|
|
17088
17160
|
};
|
|
17089
17161
|
}
|
|
17090
17162
|
function profileAccountJsonPaths(profileDir, tool) {
|
|
17091
17163
|
if (!tool.accountFile)
|
|
17092
17164
|
return [];
|
|
17093
|
-
const paths = [
|
|
17165
|
+
const paths = [join4(profileDir, tool.accountFile)];
|
|
17094
17166
|
if (profileDir === tool.defaultDir)
|
|
17095
|
-
paths.push(
|
|
17167
|
+
paths.push(join4(dirname2(profileDir), tool.accountFile));
|
|
17096
17168
|
return paths;
|
|
17097
17169
|
}
|
|
17098
17170
|
function profileAuthDir(profileDir) {
|
|
17099
|
-
return
|
|
17171
|
+
return join4(profileDir, ACCOUNTS_AUTH_DIR);
|
|
17100
17172
|
}
|
|
17101
17173
|
function profileOAuthSnapshot(profileDir) {
|
|
17102
|
-
return
|
|
17174
|
+
return join4(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
|
|
17103
17175
|
}
|
|
17104
17176
|
function profileCredentialsSnapshot(profileDir) {
|
|
17105
|
-
return
|
|
17177
|
+
return join4(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
|
|
17106
17178
|
}
|
|
17107
17179
|
function profileKeychainSnapshot(profileDir) {
|
|
17108
|
-
return
|
|
17180
|
+
return join4(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
|
|
17109
17181
|
}
|
|
17110
17182
|
|
|
17111
17183
|
// src/lib/keychain.ts
|
|
@@ -17259,7 +17331,7 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
|
|
|
17259
17331
|
}
|
|
17260
17332
|
}
|
|
17261
17333
|
function sanitizeSettingsFile(configDir, stayUnder) {
|
|
17262
|
-
const settingsPath =
|
|
17334
|
+
const settingsPath = join5(configDir, "settings.json");
|
|
17263
17335
|
const settings = readJsonFile(settingsPath);
|
|
17264
17336
|
if (!settings)
|
|
17265
17337
|
return false;
|
|
@@ -17306,7 +17378,7 @@ function liveOAuthEmail() {
|
|
|
17306
17378
|
}
|
|
17307
17379
|
function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
17308
17380
|
const authDir = profileAuthDir(profileDir);
|
|
17309
|
-
assertSafeWritePath(
|
|
17381
|
+
assertSafeWritePath(join5(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
17310
17382
|
mkdirSync3(authDir, { recursive: true });
|
|
17311
17383
|
const live = liveClaudePaths();
|
|
17312
17384
|
const oauth = readOAuthFromPaths([live.homeJson]);
|
|
@@ -17325,14 +17397,14 @@ function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
|
17325
17397
|
}
|
|
17326
17398
|
function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
|
|
17327
17399
|
const authDir = profileAuthDir(profileDir);
|
|
17328
|
-
assertSafeWritePath(
|
|
17400
|
+
assertSafeWritePath(join5(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
17329
17401
|
mkdirSync3(authDir, { recursive: true });
|
|
17330
17402
|
const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
|
|
17331
17403
|
const oauthSnap = profileOAuthSnapshot(profileDir);
|
|
17332
17404
|
if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
|
|
17333
17405
|
writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
|
|
17334
17406
|
}
|
|
17335
|
-
const credFile =
|
|
17407
|
+
const credFile = join5(profileDir, ".credentials.json");
|
|
17336
17408
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
17337
17409
|
if (existsSync3(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
|
|
17338
17410
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
@@ -17394,9 +17466,9 @@ function hasAuthSnapshot(profileDir) {
|
|
|
17394
17466
|
|
|
17395
17467
|
// src/lib/apply-lock.ts
|
|
17396
17468
|
import { closeSync, existsSync as existsSync4, mkdirSync as mkdirSync4, openSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
17397
|
-
import { join as
|
|
17469
|
+
import { join as join6 } from "node:path";
|
|
17398
17470
|
function lockPath() {
|
|
17399
|
-
return
|
|
17471
|
+
return join6(accountsHome(), ".apply.lock");
|
|
17400
17472
|
}
|
|
17401
17473
|
function withApplyLock(fn) {
|
|
17402
17474
|
const home = accountsHome();
|
|
@@ -17467,7 +17539,7 @@ function applyProfileUnlocked(name, toolId) {
|
|
|
17467
17539
|
|
|
17468
17540
|
// src/lib/codex-app.ts
|
|
17469
17541
|
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
17470
|
-
import { join as
|
|
17542
|
+
import { join as join7 } from "node:path";
|
|
17471
17543
|
var FILE_CREDENTIALS_LINE = 'cli_auth_credentials_store = "file"';
|
|
17472
17544
|
function insertRootConfigLine(config2, line) {
|
|
17473
17545
|
if (config2.trim() === "")
|
|
@@ -17494,7 +17566,7 @@ ${after}${after.endsWith(`
|
|
|
17494
17566
|
}
|
|
17495
17567
|
function ensureCodexAppProfileConfig(profileDir) {
|
|
17496
17568
|
mkdirSync5(profileDir, { recursive: true });
|
|
17497
|
-
const configPath =
|
|
17569
|
+
const configPath = join7(profileDir, "config.toml");
|
|
17498
17570
|
const current = existsSync5(configPath) ? readFileSync3(configPath, "utf8") : "";
|
|
17499
17571
|
if (/^\s*cli_auth_credentials_store\s*=/.test(current))
|
|
17500
17572
|
return;
|
|
@@ -17579,20 +17651,20 @@ function switchProfile(name, opts = {}) {
|
|
|
17579
17651
|
import { createHash } from "node:crypto";
|
|
17580
17652
|
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, readdirSync, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
17581
17653
|
import { createConnection, createServer } from "node:net";
|
|
17582
|
-
import { basename, join as
|
|
17654
|
+
import { basename, join as join8 } from "node:path";
|
|
17583
17655
|
var STATE_SUFFIX = ".json";
|
|
17584
17656
|
function supervisorDir() {
|
|
17585
|
-
return
|
|
17657
|
+
return join8(accountsHome(), "supervisors");
|
|
17586
17658
|
}
|
|
17587
17659
|
function supervisorStatePath(toolId) {
|
|
17588
|
-
return
|
|
17660
|
+
return join8(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
|
|
17589
17661
|
}
|
|
17590
17662
|
function supervisorSocketPath(toolId) {
|
|
17591
17663
|
if (process.platform === "win32") {
|
|
17592
17664
|
const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
|
|
17593
17665
|
return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
|
|
17594
17666
|
}
|
|
17595
|
-
return
|
|
17667
|
+
return join8(supervisorDir(), `${toolId}.sock`);
|
|
17596
17668
|
}
|
|
17597
17669
|
function parseState(raw) {
|
|
17598
17670
|
const data = JSON.parse(raw);
|
package/dist/storage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAAiD,MAAM,YAAY,CAAC;AAGvF,eAAO,MAAM,oBAAoB;;;;;;;;CAQvB,CAAC;AAEX,eAAO,MAAM,6BAA6B;;;;;;;;CAQhC,CAAC;AAEX,eAAO,MAAM,gBAAgB,+BAA4B,CAAC;AAC1D,eAAO,MAAM,cAAc,aAAc,CAAC;AAE1C,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,EAAE;QACN,UAAU,EAAE,OAAO,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,kBAAkB,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,GAAG,EAAE,OAAO,oBAAoB,CAAC;IACjC,WAAW,EAAE,OAAO,6BAA6B,CAAC;IAClD,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,CAAC,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAUD,4EAA4E;AAC5E,wBAAgB,YAAY,IAAI,MAAM,CAIrC;AAED,sEAAsE;AACtE,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAID,wBAAgB,SAAS,IAAI,KAAK,CA+BjC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/dist/storage.js
CHANGED
|
@@ -18,8 +18,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
18
18
|
// src/storage.ts
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { hostname } from "node:os";
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join as join2 } from "node:path";
|
|
22
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
23
23
|
|
|
24
24
|
// node_modules/zod/v3/external.js
|
|
25
25
|
var exports_external = {};
|
|
@@ -4038,52 +4038,121 @@ class AccountsError extends Error {
|
|
|
4038
4038
|
|
|
4039
4039
|
// src/lib/safe-path.ts
|
|
4040
4040
|
import { existsSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
4041
|
-
import { dirname, resolve } from "node:path";
|
|
4041
|
+
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
4042
|
+
function lstatIfExists(path) {
|
|
4043
|
+
try {
|
|
4044
|
+
return lstatSync(path);
|
|
4045
|
+
} catch (err) {
|
|
4046
|
+
if (err.code !== "ENOENT")
|
|
4047
|
+
throw err;
|
|
4048
|
+
return;
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4042
4051
|
function throwIfSymlink(path, label) {
|
|
4043
|
-
if (
|
|
4052
|
+
if (lstatIfExists(path)?.isSymbolicLink()) {
|
|
4044
4053
|
throw new AccountsError(`${label}: ${path}`);
|
|
4045
4054
|
}
|
|
4046
4055
|
}
|
|
4047
|
-
function
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
}
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4056
|
+
function isAllowedSystemDirectorySymlink(path) {
|
|
4057
|
+
if (path !== "/var" && path !== "/tmp")
|
|
4058
|
+
return false;
|
|
4059
|
+
try {
|
|
4060
|
+
return realpathSync(path) === `/private${path}`;
|
|
4061
|
+
} catch {
|
|
4062
|
+
return false;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
function assertDirectory(path, label, opts) {
|
|
4066
|
+
const stat = lstatIfExists(path);
|
|
4067
|
+
if (!stat) {
|
|
4068
|
+
throw new AccountsError(`refusing to write under missing directory: ${path}`);
|
|
4069
|
+
}
|
|
4070
|
+
if (stat.isSymbolicLink()) {
|
|
4071
|
+
if (opts?.allowSystemSymlink && isAllowedSystemDirectorySymlink(path))
|
|
4072
|
+
return;
|
|
4073
|
+
throw new AccountsError(`${label}: ${path}`);
|
|
4074
|
+
}
|
|
4075
|
+
if (!stat.isDirectory()) {
|
|
4076
|
+
throw new AccountsError(`refusing to write under non-directory path: ${path}`);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
function assertInsideBase(absPath, base, originalPath) {
|
|
4080
|
+
const rel = relative(base, absPath);
|
|
4081
|
+
if (rel === "")
|
|
4082
|
+
return rel;
|
|
4083
|
+
if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) {
|
|
4084
|
+
throw new AccountsError(`refusing to write outside profile directory: ${originalPath}`);
|
|
4085
|
+
}
|
|
4086
|
+
return rel;
|
|
4087
|
+
}
|
|
4088
|
+
function assertExistingDirectoryComponentsSafe(path, label) {
|
|
4089
|
+
const root = parse(path).root;
|
|
4090
|
+
const rel = relative(root, path);
|
|
4091
|
+
const segments = rel ? rel.split(sep).filter(Boolean) : [];
|
|
4092
|
+
let cursor = root;
|
|
4093
|
+
assertDirectory(cursor, label, { allowSystemSymlink: true });
|
|
4094
|
+
for (const segment of segments) {
|
|
4095
|
+
cursor = join(cursor, segment);
|
|
4096
|
+
if (!lstatIfExists(cursor))
|
|
4097
|
+
return;
|
|
4098
|
+
assertDirectory(cursor, label, { allowSystemSymlink: true });
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
function ensureBoundaryRootSafe(base) {
|
|
4102
|
+
const missing = [];
|
|
4103
|
+
let cursor = base;
|
|
4104
|
+
while (!lstatIfExists(cursor)) {
|
|
4105
|
+
missing.unshift(cursor);
|
|
4106
|
+
const parent = dirname(cursor);
|
|
4107
|
+
if (parent === cursor)
|
|
4108
|
+
break;
|
|
4109
|
+
cursor = parent;
|
|
4110
|
+
}
|
|
4111
|
+
assertDirectory(cursor, "refusing to use symlink base directory");
|
|
4112
|
+
for (const dir of missing) {
|
|
4113
|
+
if (lstatIfExists(dir)) {
|
|
4114
|
+
assertDirectory(dir, "refusing to use symlink base directory");
|
|
4115
|
+
} else {
|
|
4116
|
+
mkdirSync(dir);
|
|
4117
|
+
assertDirectory(dir, "refusing to use symlink base directory");
|
|
4064
4118
|
}
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4119
|
+
}
|
|
4120
|
+
assertDirectory(base, "refusing to use symlink base directory");
|
|
4121
|
+
}
|
|
4122
|
+
function ensureDirectoryChainSafe(parent, startAt) {
|
|
4123
|
+
const root = startAt ?? parse(parent).root;
|
|
4124
|
+
const rel = relative(root, parent);
|
|
4125
|
+
const segments = rel ? rel.split(sep).filter(Boolean) : [];
|
|
4126
|
+
let cursor = root;
|
|
4127
|
+
if (startAt)
|
|
4128
|
+
assertDirectory(startAt, "refusing to write under symlink directory");
|
|
4129
|
+
for (const segment of segments) {
|
|
4130
|
+
cursor = join(cursor, segment);
|
|
4131
|
+
if (lstatIfExists(cursor)) {
|
|
4132
|
+
assertDirectory(cursor, "refusing to write under symlink directory");
|
|
4133
|
+
} else {
|
|
4134
|
+
mkdirSync(cursor);
|
|
4135
|
+
assertDirectory(cursor, "refusing to write under symlink directory");
|
|
4073
4136
|
}
|
|
4074
4137
|
}
|
|
4075
4138
|
}
|
|
4076
4139
|
function assertSafeWritePath(filePath, opts) {
|
|
4077
4140
|
const absFile = resolve(filePath);
|
|
4078
4141
|
const parent = dirname(absFile);
|
|
4079
|
-
|
|
4080
|
-
|
|
4142
|
+
const base = opts?.mustStayUnder ? resolve(opts.mustStayUnder) : undefined;
|
|
4143
|
+
if (base) {
|
|
4144
|
+
assertInsideBase(absFile, base, filePath);
|
|
4145
|
+
assertExistingDirectoryComponentsSafe(base, "refusing to use symlink base directory");
|
|
4146
|
+
ensureBoundaryRootSafe(base);
|
|
4147
|
+
ensureDirectoryChainSafe(parent, base);
|
|
4148
|
+
} else {
|
|
4149
|
+
ensureDirectoryChainSafe(parent);
|
|
4150
|
+
}
|
|
4081
4151
|
throwIfSymlink(absFile, "refusing to write through symlink");
|
|
4082
|
-
assertDirChainSafe(absFile, opts?.mustStayUnder);
|
|
4083
4152
|
const resolved = realpathSync(existsSync(absFile) ? absFile : parent);
|
|
4084
|
-
if (
|
|
4085
|
-
const
|
|
4086
|
-
if (resolved !==
|
|
4153
|
+
if (base) {
|
|
4154
|
+
const realBase = realpathSync(base);
|
|
4155
|
+
if (resolved !== realBase && !resolved.startsWith(realBase + sep)) {
|
|
4087
4156
|
throw new AccountsError(`refusing to write outside profile directory: ${filePath}`);
|
|
4088
4157
|
}
|
|
4089
4158
|
}
|
|
@@ -4122,16 +4191,16 @@ function accountsHome() {
|
|
|
4122
4191
|
const override = process.env.ACCOUNTS_HOME;
|
|
4123
4192
|
if (override && override.trim())
|
|
4124
4193
|
return validateEnvPath(override, "ACCOUNTS_HOME");
|
|
4125
|
-
return
|
|
4194
|
+
return join2(homedir(), ".hasna", "accounts");
|
|
4126
4195
|
}
|
|
4127
4196
|
function storePath() {
|
|
4128
4197
|
const override = process.env.ACCOUNTS_STORE_PATH;
|
|
4129
4198
|
if (override && override.trim())
|
|
4130
4199
|
return validateEnvPath(override, "ACCOUNTS_STORE_PATH");
|
|
4131
|
-
return
|
|
4200
|
+
return join2(accountsHome(), "accounts.json");
|
|
4132
4201
|
}
|
|
4133
4202
|
function profilesDir() {
|
|
4134
|
-
return
|
|
4203
|
+
return join2(accountsHome(), "profiles");
|
|
4135
4204
|
}
|
|
4136
4205
|
var EMPTY_STORE = { version: 1, current: {}, applied: {}, profiles: [], tools: [] };
|
|
4137
4206
|
function loadStore() {
|
|
@@ -4174,9 +4243,12 @@ function loadStore() {
|
|
|
4174
4243
|
function saveStore(store) {
|
|
4175
4244
|
const path = storePath();
|
|
4176
4245
|
assertSafeWritePath(path, { mustStayUnder: accountsHome() });
|
|
4177
|
-
mkdirSync2(
|
|
4246
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
4247
|
+
if (existsSync2(path))
|
|
4248
|
+
chmodSync(path, 384);
|
|
4178
4249
|
writeFileSync(path, JSON.stringify(store, null, 2) + `
|
|
4179
4250
|
`, { mode: 384 });
|
|
4251
|
+
chmodSync(path, 384);
|
|
4180
4252
|
}
|
|
4181
4253
|
function firstEnv(env, primary, fallback) {
|
|
4182
4254
|
return env[primary] || env[fallback] || undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/accounts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Manage and switch between multiple Claude Code (and other AI coding tool) profiles/accounts locally — isolated config dirs, per-account email, one-command switching.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|