@hasna/accounts 0.1.5 → 0.1.7

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
@@ -4085,6 +4085,16 @@ function assertSafeWritePath(filePath, opts) {
4085
4085
  }
4086
4086
 
4087
4087
  // src/storage.ts
4088
+ var ACCOUNTS_STORAGE_ENV = {
4089
+ mode: "HASNA_ACCOUNTS_STORAGE_MODE",
4090
+ s3Bucket: "HASNA_ACCOUNTS_S3_BUCKET",
4091
+ s3Prefix: "HASNA_ACCOUNTS_S3_PREFIX",
4092
+ awsRegion: "HASNA_ACCOUNTS_AWS_REGION",
4093
+ s3Endpoint: "HASNA_ACCOUNTS_S3_ENDPOINT",
4094
+ s3ForcePathStyle: "HASNA_ACCOUNTS_S3_FORCE_PATH_STYLE",
4095
+ machineId: "HASNA_ACCOUNTS_MACHINE_ID"
4096
+ };
4097
+ var STORAGE_MODE_ENV = ACCOUNTS_STORAGE_ENV.mode;
4088
4098
  function validateEnvPath(value, label) {
4089
4099
  const trimmed = value.trim();
4090
4100
  if (!trimmed || trimmed.includes("\x00") || /[\r\n]/.test(trimmed)) {
@@ -4177,6 +4187,25 @@ var BUILTIN_TOOLS = [
4177
4187
  loginHint: "complete the Codex login flow for this CODEX_HOME",
4178
4188
  resumeArgs: ["resume", "--last"]
4179
4189
  },
4190
+ {
4191
+ id: "takumi",
4192
+ label: "Takumi",
4193
+ envVar: "TAKUMI_CONFIG_DIR",
4194
+ defaultDir: join2(homedir2(), ".takumi"),
4195
+ bin: "takumi",
4196
+ loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
4197
+ resumeArgs: ["--continue"],
4198
+ accountFile: ".claude.json",
4199
+ emailPath: ["oauthAccount", "emailAddress"]
4200
+ },
4201
+ {
4202
+ id: "gemini",
4203
+ label: "Gemini CLI",
4204
+ envVar: "GEMINI_CONFIG_DIR",
4205
+ defaultDir: join2(homedir2(), ".gemini"),
4206
+ bin: "gemini",
4207
+ loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR"
4208
+ },
4180
4209
  {
4181
4210
  id: "opencode",
4182
4211
  label: "opencode",
@@ -4200,6 +4229,22 @@ var BUILTIN_TOOLS = [
4200
4229
  loginArgs: ["login"],
4201
4230
  loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
4202
4231
  },
4232
+ {
4233
+ id: "pi",
4234
+ label: "Pi Coding Agent",
4235
+ envVar: "PI_CODING_AGENT_HOME",
4236
+ defaultDir: join2(homedir2(), ".pi"),
4237
+ bin: "pi",
4238
+ loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
4239
+ },
4240
+ {
4241
+ id: "hermes",
4242
+ label: "Hermes",
4243
+ envVar: "HERMES_HOME",
4244
+ defaultDir: join2(homedir2(), ".hermes"),
4245
+ bin: "hermes",
4246
+ loginHint: "complete Hermes auth in this HERMES_HOME"
4247
+ },
4203
4248
  {
4204
4249
  id: "kimi",
4205
4250
  label: "Kimi Code",
@@ -4516,7 +4561,7 @@ function currentProfile(toolId) {
4516
4561
  return store.profiles.find((p) => p.name === name);
4517
4562
  }
4518
4563
  // src/lib/claude-auth.ts
4519
- import { copyFileSync, existsSync as existsSync5, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
4564
+ import { copyFileSync, existsSync as existsSync5, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
4520
4565
  import { dirname as dirname4, join as join6 } from "node:path";
4521
4566
 
4522
4567
  // src/lib/claude-layout.ts
@@ -4638,14 +4683,26 @@ function writeJsonFile(path, data, stayUnder) {
4638
4683
  `, { mode: 384 });
4639
4684
  }
4640
4685
  function readOAuthFromPaths(paths) {
4686
+ return findOAuthSource(paths)?.oauth;
4687
+ }
4688
+ function findOAuthSource(paths) {
4641
4689
  for (const p of paths) {
4642
4690
  const data = readJsonFile(p);
4643
4691
  const oauth = data?.oauthAccount;
4644
4692
  if (oauth && typeof oauth === "object")
4645
- return oauth;
4693
+ return { path: p, oauth };
4646
4694
  }
4647
4695
  return;
4648
4696
  }
4697
+ function snapshotIsStale(sourcePath, snapshotPath) {
4698
+ if (!existsSync5(snapshotPath))
4699
+ return true;
4700
+ try {
4701
+ return statSync(sourcePath).mtimeMs > statSync(snapshotPath).mtimeMs;
4702
+ } catch {
4703
+ return false;
4704
+ }
4705
+ }
4649
4706
  function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
4650
4707
  const primary = paths[0];
4651
4708
  if (!primary)
@@ -4669,6 +4726,12 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
4669
4726
  }
4670
4727
  }
4671
4728
  }
4729
+ function liveOAuthEmail() {
4730
+ const live = liveClaudePaths();
4731
+ const oauth = readOAuthFromPaths([live.homeJson]);
4732
+ const email = oauth?.emailAddress;
4733
+ return typeof email === "string" && email ? email : undefined;
4734
+ }
4672
4735
  function snapshotLiveAuthToProfile(profileDir, _tool) {
4673
4736
  const authDir = profileAuthDir(profileDir);
4674
4737
  assertSafeWritePath(join6(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
@@ -4692,19 +4755,19 @@ function snapshotClaudeAuthToProfile(profileDir, tool) {
4692
4755
  snapshotLiveAuthToProfile(profileDir, tool);
4693
4756
  }
4694
4757
  function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
4695
- if (!opts.overwrite && hasAuthSnapshot(profileDir))
4696
- return;
4697
4758
  const authDir = profileAuthDir(profileDir);
4698
4759
  assertSafeWritePath(join6(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
4699
4760
  mkdirSync4(authDir, { recursive: true });
4700
- const oauth = readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool));
4701
- if (oauth)
4702
- writeJsonFile(profileOAuthSnapshot(profileDir), { oauthAccount: oauth }, profileDir);
4761
+ const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
4762
+ const oauthSnap = profileOAuthSnapshot(profileDir);
4763
+ if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
4764
+ writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
4765
+ }
4703
4766
  const credFile = join6(profileDir, ".credentials.json");
4704
- if (existsSync5(credFile)) {
4705
- const dest = profileCredentialsSnapshot(profileDir);
4706
- assertSafeWritePath(dest, { mustStayUnder: profileDir });
4707
- copyFileSync(credFile, dest);
4767
+ const credSnap = profileCredentialsSnapshot(profileDir);
4768
+ if (existsSync5(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
4769
+ assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
4770
+ copyFileSync(credFile, credSnap);
4708
4771
  }
4709
4772
  }
4710
4773
  function profileHasAuth(profileDir, tool) {
@@ -4794,6 +4857,9 @@ function withApplyLock(fn) {
4794
4857
  }
4795
4858
 
4796
4859
  // src/lib/apply.ts
4860
+ function singleMatch(profiles) {
4861
+ return profiles.length === 1 ? profiles[0] : undefined;
4862
+ }
4797
4863
  function appliedProfile(toolId) {
4798
4864
  const store = loadStore();
4799
4865
  const name = store.applied[toolId];
@@ -4815,11 +4881,10 @@ function applyProfileUnlocked(name, toolId) {
4815
4881
  }
4816
4882
  const store = loadStore();
4817
4883
  const previous = store.applied[tool.id];
4818
- if (previous && previous !== name) {
4819
- const prevProfile = store.profiles.find((p) => p.name === previous && p.tool === tool.id);
4820
- if (prevProfile)
4821
- snapshotLiveAuthToProfile(prevProfile.dir, tool);
4822
- }
4884
+ const liveEmail = liveOAuthEmail();
4885
+ const owner = liveEmail && singleMatch(store.profiles.filter((p) => p.tool === tool.id && p.email === liveEmail)) || (previous ? store.profiles.find((p) => p.name === previous && p.tool === tool.id) : undefined);
4886
+ if (owner)
4887
+ snapshotLiveAuthToProfile(owner.dir, tool);
4823
4888
  ensureProfileAuthSnapshot(profile.dir, tool);
4824
4889
  restoreClaudeAuthFromProfile(profile.dir, tool, name);
4825
4890
  store.applied[tool.id] = name;
@@ -0,0 +1,37 @@
1
+ import type { Profile } from "../types.js";
2
+ export type AgentEntry = Record<string, unknown>;
3
+ export interface ProfileAgents {
4
+ profile: string;
5
+ tool: string;
6
+ email?: string;
7
+ dir: string;
8
+ agents: AgentEntry[];
9
+ error?: string;
10
+ }
11
+ export interface AgentsRunnerResult {
12
+ ok: boolean;
13
+ raw: string;
14
+ error?: string;
15
+ }
16
+ export type AgentsRunner = (profile: Profile) => AgentsRunnerResult;
17
+ /**
18
+ * Extract the first top-level JSON array from output that may be wrapped in
19
+ * pty/ANSI noise (`claude agents --json` only works on a TTY, so we run it
20
+ * under `script` and the JSON arrives surrounded by control sequences).
21
+ */
22
+ export declare function extractJsonArray(raw: string): unknown[] | undefined;
23
+ /**
24
+ * Run `<bin> agents --json` for a profile's config dir under a pseudo-TTY.
25
+ * Claude Code switches to print-mode argument parsing when stdout is not a
26
+ * TTY and never reaches the `agents` subcommand, so a plain pipe won't work.
27
+ */
28
+ export declare function runClaudeAgentsJson(profile: Profile, timeoutMs?: number): AgentsRunnerResult;
29
+ export interface ListAgentsOptions {
30
+ tool?: string;
31
+ profile?: string;
32
+ backgroundOnly?: boolean;
33
+ runner?: AgentsRunner;
34
+ }
35
+ /** List agent sessions for every profile of a tool (default: claude). */
36
+ export declare function listAgentsAcrossProfiles(opts?: ListAgentsOptions): ProfileAgents[];
37
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/lib/agents.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,kBAAkB,CAAC;AAEpE;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,SAAS,CA+BnE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,SAAS,GAAG,kBAAkB,CAqB5F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,yEAAyE;AACzE,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,iBAAsB,GAAG,aAAa,EAAE,CAwBtF"}
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/lib/apply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAa3C,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAKlE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAEnG"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/lib/apply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAkB3C,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAKlE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAEnG"}
@@ -1,9 +1,17 @@
1
1
  import type { ToolDef } from "../types.js";
2
+ /** Email address of the account currently authenticated on the live Claude paths. */
3
+ export declare function liveOAuthEmail(): string | undefined;
2
4
  /** Snapshot live Claude auth into a profile directory (used when switching away on apply). */
3
5
  export declare function snapshotLiveAuthToProfile(profileDir: string, _tool: ToolDef): void;
4
6
  /** @deprecated Use snapshotLiveAuthToProfile */
5
7
  export declare function snapshotClaudeAuthToProfile(profileDir: string, tool: ToolDef): void;
6
- /** Build auth snapshots from files already present in the profile config dir. */
8
+ /**
9
+ * Build auth snapshots from files already present in the profile config dir.
10
+ * Snapshots are refreshed per-file whenever the source in the profile dir is
11
+ * newer than the existing snapshot — a running tool rotates its OAuth tokens
12
+ * in place, and restoring a login-time snapshot over rotated tokens logs the
13
+ * account out (rotated-out refresh tokens are revoked server-side).
14
+ */
7
15
  export declare function ensureProfileAuthSnapshot(profileDir: string, tool: ToolDef, opts?: {
8
16
  overwrite?: boolean;
9
17
  }): void;
@@ -1 +1 @@
1
- {"version":3,"file":"claude-auth.d.ts","sourceRoot":"","sources":["../../src/lib/claude-auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA2E3C,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAmBlF;AAED,gDAAgD;AAChD,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAEnF;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,IAAI,CAeN;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAEzE;AAED,6DAA6D;AAC7D,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAqDN;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAM3D"}
1
+ {"version":3,"file":"claude-auth.d.ts","sourceRoot":"","sources":["../../src/lib/claude-auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAyF3C,qFAAqF;AACrF,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAKnD;AAED,8FAA8F;AAC9F,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAmBlF;AAED,gDAAgD;AAChD,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAEnF;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,IAAI,CAiBN;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAEzE;AAED,6DAA6D;AAC7D,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAqDN;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAM3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/lib/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAgC,MAAM,aAAa,CAAC;AAGzE;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,OAAO,EA+DlC,CAAC;AAEF,eAAO,MAAM,YAAY,WAAW,CAAC;AAIrC,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,oFAAoF;AACpF,wBAAgB,SAAS,IAAI,OAAO,EAAE,CAMrC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAS3C;AAED,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAanD;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAWjD"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/lib/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAgC,MAAM,aAAa,CAAC;AAGzE;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,OAAO,EAkGlC,CAAC;AAEF,eAAO,MAAM,YAAY,WAAW,CAAC;AAIrC,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,oFAAoF;AACpF,wBAAgB,SAAS,IAAI,OAAO,EAAE,CAMrC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAS3C;AAED,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAanD;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAWjD"}
package/dist/mcp.js CHANGED
@@ -16721,6 +16721,16 @@ function assertSafeWritePath(filePath, opts) {
16721
16721
  }
16722
16722
 
16723
16723
  // src/storage.ts
16724
+ var ACCOUNTS_STORAGE_ENV = {
16725
+ mode: "HASNA_ACCOUNTS_STORAGE_MODE",
16726
+ s3Bucket: "HASNA_ACCOUNTS_S3_BUCKET",
16727
+ s3Prefix: "HASNA_ACCOUNTS_S3_PREFIX",
16728
+ awsRegion: "HASNA_ACCOUNTS_AWS_REGION",
16729
+ s3Endpoint: "HASNA_ACCOUNTS_S3_ENDPOINT",
16730
+ s3ForcePathStyle: "HASNA_ACCOUNTS_S3_FORCE_PATH_STYLE",
16731
+ machineId: "HASNA_ACCOUNTS_MACHINE_ID"
16732
+ };
16733
+ var STORAGE_MODE_ENV = ACCOUNTS_STORAGE_ENV.mode;
16724
16734
  function validateEnvPath(value, label) {
16725
16735
  const trimmed = value.trim();
16726
16736
  if (!trimmed || trimmed.includes("\x00") || /[\r\n]/.test(trimmed)) {
@@ -16811,6 +16821,25 @@ var BUILTIN_TOOLS = [
16811
16821
  loginHint: "complete the Codex login flow for this CODEX_HOME",
16812
16822
  resumeArgs: ["resume", "--last"]
16813
16823
  },
16824
+ {
16825
+ id: "takumi",
16826
+ label: "Takumi",
16827
+ envVar: "TAKUMI_CONFIG_DIR",
16828
+ defaultDir: join2(homedir2(), ".takumi"),
16829
+ bin: "takumi",
16830
+ loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
16831
+ resumeArgs: ["--continue"],
16832
+ accountFile: ".claude.json",
16833
+ emailPath: ["oauthAccount", "emailAddress"]
16834
+ },
16835
+ {
16836
+ id: "gemini",
16837
+ label: "Gemini CLI",
16838
+ envVar: "GEMINI_CONFIG_DIR",
16839
+ defaultDir: join2(homedir2(), ".gemini"),
16840
+ bin: "gemini",
16841
+ loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR"
16842
+ },
16814
16843
  {
16815
16844
  id: "opencode",
16816
16845
  label: "opencode",
@@ -16834,6 +16863,22 @@ var BUILTIN_TOOLS = [
16834
16863
  loginArgs: ["login"],
16835
16864
  loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
16836
16865
  },
16866
+ {
16867
+ id: "pi",
16868
+ label: "Pi Coding Agent",
16869
+ envVar: "PI_CODING_AGENT_HOME",
16870
+ defaultDir: join2(homedir2(), ".pi"),
16871
+ bin: "pi",
16872
+ loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
16873
+ },
16874
+ {
16875
+ id: "hermes",
16876
+ label: "Hermes",
16877
+ envVar: "HERMES_HOME",
16878
+ defaultDir: join2(homedir2(), ".hermes"),
16879
+ bin: "hermes",
16880
+ loginHint: "complete Hermes auth in this HERMES_HOME"
16881
+ },
16837
16882
  {
16838
16883
  id: "kimi",
16839
16884
  label: "Kimi Code",
@@ -16921,7 +16966,7 @@ function currentProfile(toolId) {
16921
16966
  }
16922
16967
 
16923
16968
  // src/lib/claude-auth.ts
16924
- import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
16969
+ import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
16925
16970
  import { dirname as dirname3, join as join4 } from "node:path";
16926
16971
 
16927
16972
  // src/lib/claude-layout.ts
@@ -17043,14 +17088,26 @@ function writeJsonFile(path, data, stayUnder) {
17043
17088
  `, { mode: 384 });
17044
17089
  }
17045
17090
  function readOAuthFromPaths(paths) {
17091
+ return findOAuthSource(paths)?.oauth;
17092
+ }
17093
+ function findOAuthSource(paths) {
17046
17094
  for (const p of paths) {
17047
17095
  const data = readJsonFile(p);
17048
17096
  const oauth = data?.oauthAccount;
17049
17097
  if (oauth && typeof oauth === "object")
17050
- return oauth;
17098
+ return { path: p, oauth };
17051
17099
  }
17052
17100
  return;
17053
17101
  }
17102
+ function snapshotIsStale(sourcePath, snapshotPath) {
17103
+ if (!existsSync3(snapshotPath))
17104
+ return true;
17105
+ try {
17106
+ return statSync(sourcePath).mtimeMs > statSync(snapshotPath).mtimeMs;
17107
+ } catch {
17108
+ return false;
17109
+ }
17110
+ }
17054
17111
  function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
17055
17112
  const primary = paths[0];
17056
17113
  if (!primary)
@@ -17074,6 +17131,12 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
17074
17131
  }
17075
17132
  }
17076
17133
  }
17134
+ function liveOAuthEmail() {
17135
+ const live = liveClaudePaths();
17136
+ const oauth = readOAuthFromPaths([live.homeJson]);
17137
+ const email2 = oauth?.emailAddress;
17138
+ return typeof email2 === "string" && email2 ? email2 : undefined;
17139
+ }
17077
17140
  function snapshotLiveAuthToProfile(profileDir, _tool) {
17078
17141
  const authDir = profileAuthDir(profileDir);
17079
17142
  assertSafeWritePath(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
@@ -17094,19 +17157,19 @@ function snapshotLiveAuthToProfile(profileDir, _tool) {
17094
17157
  }
17095
17158
  }
17096
17159
  function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
17097
- if (!opts.overwrite && hasAuthSnapshot(profileDir))
17098
- return;
17099
17160
  const authDir = profileAuthDir(profileDir);
17100
17161
  assertSafeWritePath(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
17101
17162
  mkdirSync3(authDir, { recursive: true });
17102
- const oauth = readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool));
17103
- if (oauth)
17104
- writeJsonFile(profileOAuthSnapshot(profileDir), { oauthAccount: oauth }, profileDir);
17163
+ const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
17164
+ const oauthSnap = profileOAuthSnapshot(profileDir);
17165
+ if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
17166
+ writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
17167
+ }
17105
17168
  const credFile = join4(profileDir, ".credentials.json");
17106
- if (existsSync3(credFile)) {
17107
- const dest = profileCredentialsSnapshot(profileDir);
17108
- assertSafeWritePath(dest, { mustStayUnder: profileDir });
17109
- copyFileSync(credFile, dest);
17169
+ const credSnap = profileCredentialsSnapshot(profileDir);
17170
+ if (existsSync3(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
17171
+ assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
17172
+ copyFileSync(credFile, credSnap);
17110
17173
  }
17111
17174
  }
17112
17175
  function profileHasAuth(profileDir, tool) {
@@ -17196,6 +17259,9 @@ function withApplyLock(fn) {
17196
17259
  }
17197
17260
 
17198
17261
  // src/lib/apply.ts
17262
+ function singleMatch(profiles) {
17263
+ return profiles.length === 1 ? profiles[0] : undefined;
17264
+ }
17199
17265
  function appliedProfile(toolId) {
17200
17266
  const store = loadStore();
17201
17267
  const name = store.applied[toolId];
@@ -17217,11 +17283,10 @@ function applyProfileUnlocked(name, toolId) {
17217
17283
  }
17218
17284
  const store = loadStore();
17219
17285
  const previous = store.applied[tool.id];
17220
- if (previous && previous !== name) {
17221
- const prevProfile = store.profiles.find((p) => p.name === previous && p.tool === tool.id);
17222
- if (prevProfile)
17223
- snapshotLiveAuthToProfile(prevProfile.dir, tool);
17224
- }
17286
+ const liveEmail = liveOAuthEmail();
17287
+ const owner = liveEmail && singleMatch(store.profiles.filter((p) => p.tool === tool.id && p.email === liveEmail)) || (previous ? store.profiles.find((p) => p.name === previous && p.tool === tool.id) : undefined);
17288
+ if (owner)
17289
+ snapshotLiveAuthToProfile(owner.dir, tool);
17225
17290
  ensureProfileAuthSnapshot(profile.dir, tool);
17226
17291
  restoreClaudeAuthFromProfile(profile.dir, tool, name);
17227
17292
  store.applied[tool.id] = name;
@@ -17399,7 +17464,7 @@ function ok(data) {
17399
17464
  function fail(message) {
17400
17465
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
17401
17466
  }
17402
- var server = new Server({ name: "accounts", version: "0.1.5" }, { capabilities: { tools: {} } });
17467
+ var server = new Server({ name: "accounts", version: "0.1.7" }, { capabilities: { tools: {} } });
17403
17468
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
17404
17469
  tools: [
17405
17470
  {
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,69 @@
1
1
  import { type Store } from "./types.js";
2
+ export declare const ACCOUNTS_STORAGE_ENV: {
3
+ readonly mode: "HASNA_ACCOUNTS_STORAGE_MODE";
4
+ readonly s3Bucket: "HASNA_ACCOUNTS_S3_BUCKET";
5
+ readonly s3Prefix: "HASNA_ACCOUNTS_S3_PREFIX";
6
+ readonly awsRegion: "HASNA_ACCOUNTS_AWS_REGION";
7
+ readonly s3Endpoint: "HASNA_ACCOUNTS_S3_ENDPOINT";
8
+ readonly s3ForcePathStyle: "HASNA_ACCOUNTS_S3_FORCE_PATH_STYLE";
9
+ readonly machineId: "HASNA_ACCOUNTS_MACHINE_ID";
10
+ };
11
+ export declare const ACCOUNTS_STORAGE_FALLBACK_ENV: {
12
+ readonly mode: "ACCOUNTS_STORAGE_MODE";
13
+ readonly s3Bucket: "ACCOUNTS_S3_BUCKET";
14
+ readonly s3Prefix: "ACCOUNTS_S3_PREFIX";
15
+ readonly awsRegion: "ACCOUNTS_AWS_REGION";
16
+ readonly s3Endpoint: "ACCOUNTS_S3_ENDPOINT";
17
+ readonly s3ForcePathStyle: "ACCOUNTS_S3_FORCE_PATH_STYLE";
18
+ readonly machineId: "ACCOUNTS_MACHINE_ID";
19
+ };
20
+ export declare const STORAGE_MODE_ENV: "HASNA_ACCOUNTS_STORAGE_MODE";
21
+ export declare const STORAGE_TABLES: readonly [];
22
+ export type AccountsStorageMode = "local" | "remote" | "hybrid";
23
+ export interface AccountsStorageConfig {
24
+ mode: AccountsStorageMode;
25
+ s3Bucket?: string;
26
+ s3Prefix: string;
27
+ awsRegion?: string;
28
+ s3Endpoint?: string;
29
+ s3ForcePathStyle?: boolean;
30
+ machineId: string;
31
+ }
32
+ export interface AccountsStorageStatus {
33
+ configured: boolean;
34
+ mode: AccountsStorageMode;
35
+ local: {
36
+ home: string;
37
+ storePath: string;
38
+ profilesDir: string;
39
+ };
40
+ remote: {
41
+ configured: boolean;
42
+ bucketEnv: string;
43
+ bucket?: string;
44
+ prefix: string;
45
+ regionEnv: string;
46
+ endpointConfigured: boolean;
47
+ };
48
+ env: typeof ACCOUNTS_STORAGE_ENV;
49
+ fallbackEnv: typeof ACCOUNTS_STORAGE_FALLBACK_ENV;
50
+ tables: readonly [];
51
+ }
52
+ export interface AccountsStorageSnapshot {
53
+ schemaVersion: 1;
54
+ source: "accounts";
55
+ createdAt: string;
56
+ machineId: string;
57
+ store: Store;
58
+ }
59
+ export interface AccountsStorageSyncResult {
60
+ mode: AccountsStorageMode;
61
+ pushed: number;
62
+ pulled: number;
63
+ skipped: boolean;
64
+ key: string;
65
+ reason?: string;
66
+ }
2
67
  /** Base directory for all accounts state. Override with `ACCOUNTS_HOME`. */
3
68
  export declare function accountsHome(): string;
4
69
  /** Path to the registry file. Override with `ACCOUNTS_STORE_PATH`. */
@@ -7,4 +72,13 @@ export declare function storePath(): string;
7
72
  export declare function profilesDir(): string;
8
73
  export declare function loadStore(): Store;
9
74
  export declare function saveStore(store: Store): void;
75
+ export declare function getAccountsStorageConfig(env?: NodeJS.ProcessEnv): AccountsStorageConfig;
76
+ export declare function getAccountsStorageStatus(env?: NodeJS.ProcessEnv): AccountsStorageStatus;
77
+ export declare function createAccountsStorageSnapshot(env?: NodeJS.ProcessEnv): AccountsStorageSnapshot;
78
+ export declare function restoreAccountsStorageSnapshot(snapshot: AccountsStorageSnapshot): void;
79
+ export declare function accountsStorageSnapshotKey(env?: NodeJS.ProcessEnv): string;
80
+ export declare function storagePush(env?: NodeJS.ProcessEnv): Promise<AccountsStorageSyncResult>;
81
+ export declare function storagePull(env?: NodeJS.ProcessEnv): Promise<AccountsStorageSyncResult>;
82
+ export declare function storageSync(env?: NodeJS.ProcessEnv): Promise<AccountsStorageSyncResult>;
83
+ export declare const getStorageStatus: typeof getAccountsStorageStatus;
10
84
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAiD,MAAM,YAAY,CAAC;AAWvF,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,CAK5C"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}