@hasna/accounts 0.1.6 → 0.1.8

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)) {
@@ -4551,7 +4561,7 @@ function currentProfile(toolId) {
4551
4561
  return store.profiles.find((p) => p.name === name);
4552
4562
  }
4553
4563
  // src/lib/claude-auth.ts
4554
- 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";
4555
4565
  import { dirname as dirname4, join as join6 } from "node:path";
4556
4566
 
4557
4567
  // src/lib/claude-layout.ts
@@ -4673,14 +4683,26 @@ function writeJsonFile(path, data, stayUnder) {
4673
4683
  `, { mode: 384 });
4674
4684
  }
4675
4685
  function readOAuthFromPaths(paths) {
4686
+ return findOAuthSource(paths)?.oauth;
4687
+ }
4688
+ function findOAuthSource(paths) {
4676
4689
  for (const p of paths) {
4677
4690
  const data = readJsonFile(p);
4678
4691
  const oauth = data?.oauthAccount;
4679
4692
  if (oauth && typeof oauth === "object")
4680
- return oauth;
4693
+ return { path: p, oauth };
4681
4694
  }
4682
4695
  return;
4683
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
+ }
4684
4706
  function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
4685
4707
  const primary = paths[0];
4686
4708
  if (!primary)
@@ -4704,6 +4726,12 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
4704
4726
  }
4705
4727
  }
4706
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
+ }
4707
4735
  function snapshotLiveAuthToProfile(profileDir, _tool) {
4708
4736
  const authDir = profileAuthDir(profileDir);
4709
4737
  assertSafeWritePath(join6(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
@@ -4727,19 +4755,19 @@ function snapshotClaudeAuthToProfile(profileDir, tool) {
4727
4755
  snapshotLiveAuthToProfile(profileDir, tool);
4728
4756
  }
4729
4757
  function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
4730
- if (!opts.overwrite && hasAuthSnapshot(profileDir))
4731
- return;
4732
4758
  const authDir = profileAuthDir(profileDir);
4733
4759
  assertSafeWritePath(join6(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
4734
4760
  mkdirSync4(authDir, { recursive: true });
4735
- const oauth = readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool));
4736
- if (oauth)
4737
- 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
+ }
4738
4766
  const credFile = join6(profileDir, ".credentials.json");
4739
- if (existsSync5(credFile)) {
4740
- const dest = profileCredentialsSnapshot(profileDir);
4741
- assertSafeWritePath(dest, { mustStayUnder: profileDir });
4742
- 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);
4743
4771
  }
4744
4772
  }
4745
4773
  function profileHasAuth(profileDir, tool) {
@@ -4829,6 +4857,9 @@ function withApplyLock(fn) {
4829
4857
  }
4830
4858
 
4831
4859
  // src/lib/apply.ts
4860
+ function singleMatch(profiles) {
4861
+ return profiles.length === 1 ? profiles[0] : undefined;
4862
+ }
4832
4863
  function appliedProfile(toolId) {
4833
4864
  const store = loadStore();
4834
4865
  const name = store.applied[toolId];
@@ -4850,11 +4881,10 @@ function applyProfileUnlocked(name, toolId) {
4850
4881
  }
4851
4882
  const store = loadStore();
4852
4883
  const previous = store.applied[tool.id];
4853
- if (previous && previous !== name) {
4854
- const prevProfile = store.profiles.find((p) => p.name === previous && p.tool === tool.id);
4855
- if (prevProfile)
4856
- snapshotLiveAuthToProfile(prevProfile.dir, tool);
4857
- }
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);
4858
4888
  ensureProfileAuthSnapshot(profile.dir, tool);
4859
4889
  restoreClaudeAuthFromProfile(profile.dir, tool, name);
4860
4890
  store.applied[tool.id] = name;
@@ -0,0 +1,67 @@
1
+ import type { Profile } from "../types.js";
2
+ export type AgentEntry = Record<string, unknown>;
3
+ /** Minimal profile shape needed to query agents (allows synthetic entries). */
4
+ export type ProfileLike = Pick<Profile, "name" | "tool" | "dir"> & {
5
+ email?: string;
6
+ };
7
+ export interface ProfileAgents {
8
+ profile: string;
9
+ tool: string;
10
+ email?: string;
11
+ dir: string;
12
+ agents: AgentEntry[];
13
+ error?: string;
14
+ }
15
+ export interface AgentsRunnerResult {
16
+ ok: boolean;
17
+ raw: string;
18
+ error?: string;
19
+ }
20
+ export type AgentsRunner = (profile: ProfileLike) => AgentsRunnerResult;
21
+ export interface ProcessInfo {
22
+ pid: number;
23
+ ppid: number;
24
+ command: string;
25
+ configDir?: string;
26
+ }
27
+ export type ProcessScanner = () => ProcessInfo[];
28
+ /**
29
+ * Extract the first top-level JSON array from output that may be wrapped in
30
+ * pty/ANSI noise (`claude agents --json` only works on a TTY, so we run it
31
+ * under `script` and the JSON arrives surrounded by control sequences).
32
+ */
33
+ export declare function extractJsonArray(raw: string): unknown[] | undefined;
34
+ /**
35
+ * Run `<bin> agents --json` for a profile's config dir under a pseudo-TTY.
36
+ * Claude Code switches to print-mode argument parsing when stdout is not a
37
+ * TTY and never reaches the `agents` subcommand, so a plain pipe won't work.
38
+ */
39
+ export declare function runClaudeAgentsJson(profile: ProfileLike, timeoutMs?: number): AgentsRunnerResult;
40
+ /**
41
+ * True when a `ps` command line looks like a real agent session process for
42
+ * the tool — not a daemon, pty host, pre-warmed spare, shell snapshot, our
43
+ * own `agents` listing invocation, or an `accounts` wrapper.
44
+ */
45
+ export declare function isToolSessionCommand(command: string, bin: string): boolean;
46
+ /** Scan running processes for agent sessions of a tool (pid, ppid, command, config dir). */
47
+ export declare function scanToolProcesses(toolId?: string): ProcessInfo[];
48
+ export interface ListAgentsOptions {
49
+ tool?: string;
50
+ profile?: string;
51
+ backgroundOnly?: boolean;
52
+ runner?: AgentsRunner;
53
+ /** Override the tool's default config dir (used by tests). */
54
+ defaultDir?: string;
55
+ processScanner?: ProcessScanner;
56
+ }
57
+ /**
58
+ * List agent sessions for every profile of a tool (default: claude).
59
+ *
60
+ * Besides registered profiles this also queries the tool's DEFAULT config
61
+ * dir (e.g. ~/.claude) as a synthetic "(default)" entry — headless sessions
62
+ * started without the accounts CLI live there — and cross-checks the daemon
63
+ * listings against a process scan, reporting session processes no daemon
64
+ * knows about under "(untracked)".
65
+ */
66
+ export declare function listAgentsAcrossProfiles(opts?: ListAgentsOptions): ProfileAgents[];
67
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/lib/agents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF,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,WAAW,KAAK,kBAAkB,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,WAAW,EAAE,CAAC;AAEjD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,SAAS,CA+BnE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,SAAS,GAAG,kBAAkB,CAqBhG;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAqB1E;AAED,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,MAAM,SAAW,GAAG,WAAW,EAAE,CAsBlE;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;IACtB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,iBAAsB,GAAG,aAAa,EAAE,CAyEtF"}
@@ -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"}
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)) {
@@ -16956,7 +16966,7 @@ function currentProfile(toolId) {
16956
16966
  }
16957
16967
 
16958
16968
  // src/lib/claude-auth.ts
16959
- 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";
16960
16970
  import { dirname as dirname3, join as join4 } from "node:path";
16961
16971
 
16962
16972
  // src/lib/claude-layout.ts
@@ -17078,14 +17088,26 @@ function writeJsonFile(path, data, stayUnder) {
17078
17088
  `, { mode: 384 });
17079
17089
  }
17080
17090
  function readOAuthFromPaths(paths) {
17091
+ return findOAuthSource(paths)?.oauth;
17092
+ }
17093
+ function findOAuthSource(paths) {
17081
17094
  for (const p of paths) {
17082
17095
  const data = readJsonFile(p);
17083
17096
  const oauth = data?.oauthAccount;
17084
17097
  if (oauth && typeof oauth === "object")
17085
- return oauth;
17098
+ return { path: p, oauth };
17086
17099
  }
17087
17100
  return;
17088
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
+ }
17089
17111
  function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
17090
17112
  const primary = paths[0];
17091
17113
  if (!primary)
@@ -17109,6 +17131,12 @@ function mergeOAuthInto(paths, oauth, allowDelete, stayUnder) {
17109
17131
  }
17110
17132
  }
17111
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
+ }
17112
17140
  function snapshotLiveAuthToProfile(profileDir, _tool) {
17113
17141
  const authDir = profileAuthDir(profileDir);
17114
17142
  assertSafeWritePath(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
@@ -17129,19 +17157,19 @@ function snapshotLiveAuthToProfile(profileDir, _tool) {
17129
17157
  }
17130
17158
  }
17131
17159
  function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
17132
- if (!opts.overwrite && hasAuthSnapshot(profileDir))
17133
- return;
17134
17160
  const authDir = profileAuthDir(profileDir);
17135
17161
  assertSafeWritePath(join4(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
17136
17162
  mkdirSync3(authDir, { recursive: true });
17137
- const oauth = readOAuthFromPaths(profileAccountJsonPaths(profileDir, tool));
17138
- if (oauth)
17139
- 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
+ }
17140
17168
  const credFile = join4(profileDir, ".credentials.json");
17141
- if (existsSync3(credFile)) {
17142
- const dest = profileCredentialsSnapshot(profileDir);
17143
- assertSafeWritePath(dest, { mustStayUnder: profileDir });
17144
- 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);
17145
17173
  }
17146
17174
  }
17147
17175
  function profileHasAuth(profileDir, tool) {
@@ -17231,6 +17259,9 @@ function withApplyLock(fn) {
17231
17259
  }
17232
17260
 
17233
17261
  // src/lib/apply.ts
17262
+ function singleMatch(profiles) {
17263
+ return profiles.length === 1 ? profiles[0] : undefined;
17264
+ }
17234
17265
  function appliedProfile(toolId) {
17235
17266
  const store = loadStore();
17236
17267
  const name = store.applied[toolId];
@@ -17252,11 +17283,10 @@ function applyProfileUnlocked(name, toolId) {
17252
17283
  }
17253
17284
  const store = loadStore();
17254
17285
  const previous = store.applied[tool.id];
17255
- if (previous && previous !== name) {
17256
- const prevProfile = store.profiles.find((p) => p.name === previous && p.tool === tool.id);
17257
- if (prevProfile)
17258
- snapshotLiveAuthToProfile(prevProfile.dir, tool);
17259
- }
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);
17260
17290
  ensureProfileAuthSnapshot(profile.dir, tool);
17261
17291
  restoreClaudeAuthFromProfile(profile.dir, tool, name);
17262
17292
  store.applied[tool.id] = name;
@@ -17434,7 +17464,7 @@ function ok(data) {
17434
17464
  function fail(message) {
17435
17465
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
17436
17466
  }
17437
- var server = new Server({ name: "accounts", version: "0.1.6" }, { capabilities: { tools: {} } });
17467
+ var server = new Server({ name: "accounts", version: "0.1.8" }, { capabilities: { tools: {} } });
17438
17468
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
17439
17469
  tools: [
17440
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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"}