@namzu/cli 8.2.0 → 8.3.0

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.
Files changed (52) hide show
  1. package/dist/doctor/checks/credentials.d.ts.map +1 -1
  2. package/dist/doctor/checks/credentials.js +2 -0
  3. package/dist/doctor/checks/credentials.js.map +1 -1
  4. package/dist/integrations/providers/credential-store.d.ts +118 -0
  5. package/dist/integrations/providers/credential-store.d.ts.map +1 -0
  6. package/dist/integrations/providers/credential-store.js +322 -0
  7. package/dist/integrations/providers/credential-store.js.map +1 -0
  8. package/dist/integrations/providers/discover.d.ts +37 -10
  9. package/dist/integrations/providers/discover.d.ts.map +1 -1
  10. package/dist/integrations/providers/discover.js +43 -13
  11. package/dist/integrations/providers/discover.js.map +1 -1
  12. package/dist/integrations/providers/identity.d.ts +75 -0
  13. package/dist/integrations/providers/identity.d.ts.map +1 -0
  14. package/dist/integrations/providers/identity.js +75 -0
  15. package/dist/integrations/providers/identity.js.map +1 -0
  16. package/dist/integrations/providers/index.d.ts +4 -1
  17. package/dist/integrations/providers/index.d.ts.map +1 -1
  18. package/dist/integrations/providers/index.js +4 -1
  19. package/dist/integrations/providers/index.js.map +1 -1
  20. package/dist/integrations/providers/oauth.d.ts +37 -7
  21. package/dist/integrations/providers/oauth.d.ts.map +1 -1
  22. package/dist/integrations/providers/oauth.js +62 -17
  23. package/dist/integrations/providers/oauth.js.map +1 -1
  24. package/dist/integrations/providers/subscription-login.d.ts +123 -0
  25. package/dist/integrations/providers/subscription-login.d.ts.map +1 -0
  26. package/dist/integrations/providers/subscription-login.js +348 -0
  27. package/dist/integrations/providers/subscription-login.js.map +1 -0
  28. package/dist/tui/App.d.ts.map +1 -1
  29. package/dist/tui/App.js +103 -1
  30. package/dist/tui/App.js.map +1 -1
  31. package/dist/tui/Picker.js +4 -0
  32. package/dist/tui/Picker.js.map +1 -1
  33. package/dist/tui/agent.d.ts.map +1 -1
  34. package/dist/tui/agent.js +15 -7
  35. package/dist/tui/agent.js.map +1 -1
  36. package/dist/tui/credential-entry.d.ts +16 -12
  37. package/dist/tui/credential-entry.d.ts.map +1 -1
  38. package/dist/tui/credential-entry.js +16 -12
  39. package/dist/tui/credential-entry.js.map +1 -1
  40. package/dist/tui/login-prompt.d.ts +41 -0
  41. package/dist/tui/login-prompt.d.ts.map +1 -0
  42. package/dist/tui/login-prompt.js +69 -0
  43. package/dist/tui/login-prompt.js.map +1 -0
  44. package/dist/tui/open-browser.d.ts +24 -0
  45. package/dist/tui/open-browser.d.ts.map +1 -0
  46. package/dist/tui/open-browser.js +57 -0
  47. package/dist/tui/open-browser.js.map +1 -0
  48. package/dist/tui/slashCommands.d.ts +16 -0
  49. package/dist/tui/slashCommands.d.ts.map +1 -1
  50. package/dist/tui/slashCommands.js +13 -0
  51. package/dist/tui/slashCommands.js.map +1 -1
  52. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAA;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAwCpC,CAAA"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/doctor/checks/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAA;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB,EAAE,WA0CpC,CAAA"}
@@ -45,6 +45,8 @@ export const credentialSourcesCheck = {
45
45
  return `${d.entry.id} (env · ${d.source.envName})`;
46
46
  case 'keychain':
47
47
  return `${d.entry.id} (keychain · ${d.source.service})`;
48
+ case 'stored':
49
+ return `${d.entry.id} (signed in · ${d.source.path})`;
48
50
  case 'probe':
49
51
  return `${d.entry.id} (local · ${d.source.url.replace(/^https?:\/\//, '')})`;
50
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../src/doctor/checks/credentials.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAA;AAE5E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAgB;IAClD,EAAE,EAAE,uBAAuB;IAC3B,QAAQ,EAAE,WAAW;IACrB,GAAG,EAAE,KAAK,IAAgC,EAAE;QAC3C,IAAI,QAAuD,CAAA;QAC3D,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,iBAAiB,EAAE,CAAA;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,oEAAoE;YACpE,sEAAsE;YACtE,OAAO;gBACN,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,gCAAgC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aAC3F,CAAA;QACF,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACN,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,yBAAyB;gBAClC,WAAW,EACV,yXAAyX;aAC1X,CAAA;QACF,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACvB,KAAK,KAAK;oBACT,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,CAAA;gBACnD,KAAK,UAAU;oBACd,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,CAAA;gBACxD,KAAK,OAAO;oBACX,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,GAAG,CAAA;YAC9E,CAAC;QACF,CAAC,CAAC,CAAA;QACF,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,kCAAkC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SAC/E,CAAA;IACF,CAAC;CACD,CAAA"}
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../src/doctor/checks/credentials.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAA;AAE5E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAgB;IAClD,EAAE,EAAE,uBAAuB;IAC3B,QAAQ,EAAE,WAAW;IACrB,GAAG,EAAE,KAAK,IAAgC,EAAE;QAC3C,IAAI,QAAuD,CAAA;QAC3D,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,iBAAiB,EAAE,CAAA;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,oEAAoE;YACpE,sEAAsE;YACtE,OAAO;gBACN,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,gCAAgC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aAC3F,CAAA;QACF,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACN,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,yBAAyB;gBAClC,WAAW,EACV,yXAAyX;aAC1X,CAAA;QACF,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACvB,KAAK,KAAK;oBACT,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,CAAA;gBACnD,KAAK,UAAU;oBACd,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,CAAA;gBACxD,KAAK,QAAQ;oBACZ,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAA;gBACtD,KAAK,OAAO;oBACX,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,GAAG,CAAA;YAC9E,CAAC;QACF,CAAC,CAAC,CAAA;QACF,OAAO;YACN,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,kCAAkC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SAC/E,CAAA;IACF,CAAC;CACD,CAAA"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * `~/.namzu/credentials.json` — the subscription credential namzu obtained itself.
3
+ *
4
+ * ## Why a file, said as a choice rather than a default
5
+ *
6
+ * The durable home a credential deserves is the operating system's own secret
7
+ * store. namzu does not have one. The helper next door reads the macOS login
8
+ * Keychain and belongs to a DIFFERENT product's entry — namzu borrows a token
9
+ * from it — so writing namzu's own secret there would file our secret under
10
+ * somebody else's name. On the platform this was asked for the store is not
11
+ * that one at all, and no cross-platform binding exists in this package.
12
+ *
13
+ * So the options were: ship no durable credential, bind a native store per
14
+ * platform, or own a file. This owns a file, and pays for it by making the
15
+ * file's protection a CHECKED property rather than an assumption:
16
+ *
17
+ * - it is created `0600` from birth (`wx` + mode, never a world-readable
18
+ * window), inside a `0700` directory;
19
+ * - after writing, the protection is READ BACK and asserted;
20
+ * - if the assertion cannot be made, the file is deleted and the write
21
+ * throws. A credential store that cannot prove it is private is a worse
22
+ * outcome than no credential store, and refusing is the rule this
23
+ * repository already holds (`docs/conventions/refuse-do-not-degrade.md`).
24
+ *
25
+ * The assertion is per-platform because the underlying protection is:
26
+ *
27
+ * - POSIX: `chmod 0600`, then `stat` and require no group/other bits.
28
+ * - Windows: POSIX modes do not exist — `fs.chmod` there only toggles the
29
+ * read-only attribute, so a `0600` that "succeeded" would prove nothing.
30
+ * The equivalent is a discretionary ACL, so inheritance is removed and a
31
+ * single full-control entry is granted to the current user's SID; the ACL
32
+ * is then saved back as SDDL and required to contain exactly that one
33
+ * allow entry and nothing else.
34
+ *
35
+ * ## What is in the file
36
+ *
37
+ * { "version": 1,
38
+ * "subscription": { "accessToken", "refreshToken", "expiresAt", "scopes" } }
39
+ *
40
+ * `subscription` is the credential a person's plan grants, obtained by the
41
+ * login flow in `subscription-login.ts` and refreshed in place by `oauth.ts`.
42
+ * Nothing else writes here.
43
+ */
44
+ import type { AgentOAuthCredential } from './keychain.js';
45
+ export declare const CREDENTIALS_FILE_VERSION: 1;
46
+ /** Thrown when a credential could not be stored *privately*. Never carries a secret. */
47
+ export declare class CredentialStoreError extends Error {
48
+ readonly name = "CredentialStoreError";
49
+ }
50
+ export declare function credentialsPath(home?: string): string;
51
+ /**
52
+ * The stored subscription credential, or `null` when there is none.
53
+ *
54
+ * Non-throwing on every shape of absence and corruption — a store that
55
+ * refuses to parse is "no credential", which is what discovery can act on.
56
+ * It does NOT swallow a permission error into `null` silently; that is still
57
+ * absence from the reader's point of view, and the login path is what tells
58
+ * the operator to try again.
59
+ */
60
+ export declare function readStoredSubscriptionCredential(home?: string): AgentOAuthCredential | null;
61
+ /**
62
+ * Write the credential, privately, or throw having written nothing that lasts.
63
+ *
64
+ * Ordering matters and is the whole point: the temporary file is created with
65
+ * the restrictive mode, secured, asserted, and only then renamed into place.
66
+ * A file that appears at the final path and is tightened afterwards has a
67
+ * window in which it is readable, and that window is the defect this ordering
68
+ * removes.
69
+ */
70
+ export declare function writeStoredSubscriptionCredential(cred: AgentOAuthCredential, home?: string): string;
71
+ /** Remove the stored credential. Absence is success. */
72
+ export declare function clearStoredSubscriptionCredential(home?: string): void;
73
+ /**
74
+ * Make `path` readable and writable by its owner and by nobody else, and
75
+ * PROVE it. Throws `CredentialStoreError` when the proof cannot be produced.
76
+ *
77
+ * Exported because it is the load-bearing half of this module and a security
78
+ * property nobody can check by reading the caller.
79
+ */
80
+ export declare function restrictToOwner(path: string): void;
81
+ /**
82
+ * Require a POSIX mode to grant nothing to group or other.
83
+ *
84
+ * Split out for the same reason `assertSoleOwnerSddl` is: it is the actual
85
+ * assertion, and one that only ever executes on some platforms is one most
86
+ * contributors never see fail. As a pure function it is checked everywhere,
87
+ * including by whoever is on the operating system where the branch that calls
88
+ * it is unreachable.
89
+ */
90
+ export declare function assertOwnerOnlyMode(mode: number, path: string): void;
91
+ /**
92
+ * The file's discretionary access-control list, as SDDL, or `null`.
93
+ *
94
+ * `null` off Windows and on any failure to read. Exported so a test can make
95
+ * the Windows arm a real assertion instead of "the write did not throw" —
96
+ * without it, deleting the entire protection step would kill no test on the
97
+ * platform the protection is FOR, and the only coverage would be the POSIX
98
+ * branch running somewhere else.
99
+ *
100
+ * `icacls /save` writes a UTF-16 file whose second line is the descriptor.
101
+ * SDDL names principals by security identifier, so nothing here depends on
102
+ * the machine's display language.
103
+ */
104
+ export declare function readAclSddl(path: string): string | null;
105
+ /** The current account's security identifier, or `null` off Windows / on failure. */
106
+ export declare function currentUserSid(): string | null;
107
+ /**
108
+ * Require an SDDL discretionary ACL to grant exactly one principal, and that
109
+ * principal to be `sid`.
110
+ *
111
+ * Split out from the spawning above because it is the actual assertion, and
112
+ * an assertion that can only run on one operating system is an assertion
113
+ * nobody checks. Every entry is examined, not just the first: an ACL reading
114
+ * `(A;;FA;;;US)(A;;FA;;;WD)` grants us AND everyone, and a check that stopped
115
+ * at a match would call that private.
116
+ */
117
+ export declare function assertSoleOwnerSddl(sddl: string, sid: string, path: string): void;
118
+ //# sourceMappingURL=credential-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/credential-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAiBH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAKzD,eAAO,MAAM,wBAAwB,EAAG,CAAU,CAAA;AAElD,wFAAwF;AACxF,qBAAa,oBAAqB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,0BAAyB;CAC/C;AAED,wBAAgB,eAAe,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAC/C,IAAI,GAAE,MAAkB,GACtB,oBAAoB,GAAG,IAAI,CA2B7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAChD,IAAI,EAAE,oBAAoB,EAC1B,IAAI,GAAE,MAAkB,GACtB,MAAM,CAiDR;AAED,wDAAwD;AACxD,wBAAgB,iCAAiC,CAAC,IAAI,GAAE,MAAkB,GAAG,IAAI,CAEhF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiBlD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAMpE;AAgDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBvD;AAED,qFAAqF;AACrF,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAc9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAoCjF"}
@@ -0,0 +1,322 @@
1
+ /**
2
+ * `~/.namzu/credentials.json` — the subscription credential namzu obtained itself.
3
+ *
4
+ * ## Why a file, said as a choice rather than a default
5
+ *
6
+ * The durable home a credential deserves is the operating system's own secret
7
+ * store. namzu does not have one. The helper next door reads the macOS login
8
+ * Keychain and belongs to a DIFFERENT product's entry — namzu borrows a token
9
+ * from it — so writing namzu's own secret there would file our secret under
10
+ * somebody else's name. On the platform this was asked for the store is not
11
+ * that one at all, and no cross-platform binding exists in this package.
12
+ *
13
+ * So the options were: ship no durable credential, bind a native store per
14
+ * platform, or own a file. This owns a file, and pays for it by making the
15
+ * file's protection a CHECKED property rather than an assumption:
16
+ *
17
+ * - it is created `0600` from birth (`wx` + mode, never a world-readable
18
+ * window), inside a `0700` directory;
19
+ * - after writing, the protection is READ BACK and asserted;
20
+ * - if the assertion cannot be made, the file is deleted and the write
21
+ * throws. A credential store that cannot prove it is private is a worse
22
+ * outcome than no credential store, and refusing is the rule this
23
+ * repository already holds (`docs/conventions/refuse-do-not-degrade.md`).
24
+ *
25
+ * The assertion is per-platform because the underlying protection is:
26
+ *
27
+ * - POSIX: `chmod 0600`, then `stat` and require no group/other bits.
28
+ * - Windows: POSIX modes do not exist — `fs.chmod` there only toggles the
29
+ * read-only attribute, so a `0600` that "succeeded" would prove nothing.
30
+ * The equivalent is a discretionary ACL, so inheritance is removed and a
31
+ * single full-control entry is granted to the current user's SID; the ACL
32
+ * is then saved back as SDDL and required to contain exactly that one
33
+ * allow entry and nothing else.
34
+ *
35
+ * ## What is in the file
36
+ *
37
+ * { "version": 1,
38
+ * "subscription": { "accessToken", "refreshToken", "expiresAt", "scopes" } }
39
+ *
40
+ * `subscription` is the credential a person's plan grants, obtained by the
41
+ * login flow in `subscription-login.ts` and refreshed in place by `oauth.ts`.
42
+ * Nothing else writes here.
43
+ */
44
+ import { execFileSync } from 'node:child_process';
45
+ import { randomBytes } from 'node:crypto';
46
+ import { closeSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeSync, } from 'node:fs';
47
+ import { homedir, platform, tmpdir } from 'node:os';
48
+ import { dirname, join } from 'node:path';
49
+ const FILE_MODE = 0o600;
50
+ const DIR_MODE = 0o700;
51
+ export const CREDENTIALS_FILE_VERSION = 1;
52
+ /** Thrown when a credential could not be stored *privately*. Never carries a secret. */
53
+ export class CredentialStoreError extends Error {
54
+ name = 'CredentialStoreError';
55
+ }
56
+ export function credentialsPath(home = homedir()) {
57
+ return join(home, '.namzu', 'credentials.json');
58
+ }
59
+ /**
60
+ * The stored subscription credential, or `null` when there is none.
61
+ *
62
+ * Non-throwing on every shape of absence and corruption — a store that
63
+ * refuses to parse is "no credential", which is what discovery can act on.
64
+ * It does NOT swallow a permission error into `null` silently; that is still
65
+ * absence from the reader's point of view, and the login path is what tells
66
+ * the operator to try again.
67
+ */
68
+ export function readStoredSubscriptionCredential(home = homedir()) {
69
+ let raw;
70
+ try {
71
+ raw = readFileSync(credentialsPath(home), 'utf8');
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(raw);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ if (typeof parsed !== 'object' || parsed === null)
84
+ return null;
85
+ const sub = parsed.subscription;
86
+ if (typeof sub !== 'object' || sub === null)
87
+ return null;
88
+ const rec = sub;
89
+ const accessToken = rec.accessToken;
90
+ if (typeof accessToken !== 'string' || accessToken.length === 0)
91
+ return null;
92
+ return {
93
+ accessToken,
94
+ refreshToken: typeof rec.refreshToken === 'string' ? rec.refreshToken : undefined,
95
+ expiresAt: typeof rec.expiresAt === 'number' ? rec.expiresAt : undefined,
96
+ scopes: Array.isArray(rec.scopes)
97
+ ? rec.scopes.filter((s) => typeof s === 'string')
98
+ : undefined,
99
+ };
100
+ }
101
+ /**
102
+ * Write the credential, privately, or throw having written nothing that lasts.
103
+ *
104
+ * Ordering matters and is the whole point: the temporary file is created with
105
+ * the restrictive mode, secured, asserted, and only then renamed into place.
106
+ * A file that appears at the final path and is tightened afterwards has a
107
+ * window in which it is readable, and that window is the defect this ordering
108
+ * removes.
109
+ */
110
+ export function writeStoredSubscriptionCredential(cred, home = homedir()) {
111
+ const path = credentialsPath(home);
112
+ mkdirSync(dirname(path), { recursive: true, mode: DIR_MODE });
113
+ const tmp = `${path}.tmp.${process.pid}.${randomBytes(6).toString('hex')}`;
114
+ const body = `${JSON.stringify({
115
+ version: CREDENTIALS_FILE_VERSION,
116
+ subscription: {
117
+ accessToken: cred.accessToken,
118
+ ...(cred.refreshToken ? { refreshToken: cred.refreshToken } : {}),
119
+ ...(cred.expiresAt ? { expiresAt: cred.expiresAt } : {}),
120
+ ...(cred.scopes ? { scopes: cred.scopes } : {}),
121
+ },
122
+ }, null, 2)}\n`;
123
+ let fd;
124
+ try {
125
+ // `wx` — refuse an existing path rather than truncate one, so a name
126
+ // collision can never hand us a file somebody else already opened.
127
+ fd = openSync(tmp, 'wx', FILE_MODE);
128
+ writeSync(fd, body);
129
+ }
130
+ catch (err) {
131
+ if (fd !== undefined)
132
+ closeSync(fd);
133
+ rmSync(tmp, { force: true });
134
+ throw new CredentialStoreError(`could not write ${path}: ${err instanceof Error ? err.message : String(err)}`);
135
+ }
136
+ closeSync(fd);
137
+ try {
138
+ restrictToOwner(tmp);
139
+ }
140
+ catch (err) {
141
+ rmSync(tmp, { force: true });
142
+ throw err;
143
+ }
144
+ try {
145
+ renameSync(tmp, path);
146
+ }
147
+ catch (err) {
148
+ rmSync(tmp, { force: true });
149
+ throw new CredentialStoreError(`could not place ${path}: ${err instanceof Error ? err.message : String(err)}`);
150
+ }
151
+ return path;
152
+ }
153
+ /** Remove the stored credential. Absence is success. */
154
+ export function clearStoredSubscriptionCredential(home = homedir()) {
155
+ rmSync(credentialsPath(home), { force: true });
156
+ }
157
+ /**
158
+ * Make `path` readable and writable by its owner and by nobody else, and
159
+ * PROVE it. Throws `CredentialStoreError` when the proof cannot be produced.
160
+ *
161
+ * Exported because it is the load-bearing half of this module and a security
162
+ * property nobody can check by reading the caller.
163
+ */
164
+ export function restrictToOwner(path) {
165
+ if (platform() === 'win32') {
166
+ restrictToOwnerWindows(path);
167
+ return;
168
+ }
169
+ // POSIX. `openSync` already asked for the mode; a umask cannot loosen it,
170
+ // but an inherited ACL or an exotic filesystem can, so this reads it back
171
+ // instead of trusting the request.
172
+ let mode;
173
+ try {
174
+ mode = statSync(path).mode;
175
+ }
176
+ catch (err) {
177
+ throw new CredentialStoreError(`could not read back the permissions of ${path}: ${err instanceof Error ? err.message : String(err)}`);
178
+ }
179
+ assertOwnerOnlyMode(mode, path);
180
+ }
181
+ /**
182
+ * Require a POSIX mode to grant nothing to group or other.
183
+ *
184
+ * Split out for the same reason `assertSoleOwnerSddl` is: it is the actual
185
+ * assertion, and one that only ever executes on some platforms is one most
186
+ * contributors never see fail. As a pure function it is checked everywhere,
187
+ * including by whoever is on the operating system where the branch that calls
188
+ * it is unreachable.
189
+ */
190
+ export function assertOwnerOnlyMode(mode, path) {
191
+ if ((mode & 0o077) !== 0) {
192
+ throw new CredentialStoreError(`${path} is readable beyond its owner (mode ${(mode & 0o777).toString(8)}) and the filesystem would not tighten it — refusing to keep a credential there.`);
193
+ }
194
+ }
195
+ /**
196
+ * The Windows half: remove inheritance, grant the current user's SID full
197
+ * control, then read the resulting ACL back as SDDL and require it to be
198
+ * exactly that one allow entry.
199
+ *
200
+ * Both helpers are invoked by absolute path under `%SystemRoot%\System32`.
201
+ * Resolving them through `PATH` is how a same-named executable earlier on the
202
+ * path gets to answer a security question on our behalf, and on a developer
203
+ * machine `PATH` routinely carries a POSIX toolchain that shadows these names.
204
+ */
205
+ function restrictToOwnerWindows(path) {
206
+ const system32 = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32');
207
+ const run = (exe, args) => {
208
+ try {
209
+ return execFileSync(join(system32, exe), [...args], {
210
+ encoding: 'utf8',
211
+ timeout: 10_000,
212
+ stdio: ['ignore', 'pipe', 'ignore'],
213
+ windowsHide: true,
214
+ });
215
+ }
216
+ catch (err) {
217
+ throw new CredentialStoreError(`${exe} could not secure ${path} (${err instanceof Error ? err.message : String(err)}). namzu will not keep a credential in a file it cannot prove is private.`);
218
+ }
219
+ };
220
+ // `whoami /user /fo csv /nh` prints "DOMAIN\user","S-1-5-21-…"
221
+ const sid = currentUserSid();
222
+ if (!sid) {
223
+ throw new CredentialStoreError(`could not determine the current account's security identifier, so the protection of ${path} cannot be established.`);
224
+ }
225
+ run('icacls.exe', [path, '/inheritance:r', '/grant:r', `*${sid}:F`]);
226
+ const saved = readAclSddl(path);
227
+ if (saved === null) {
228
+ throw new CredentialStoreError(`could not read back the access-control list of ${path}, so its privacy is unestablished.`);
229
+ }
230
+ assertSoleOwnerSddl(saved, sid, path);
231
+ }
232
+ /**
233
+ * The file's discretionary access-control list, as SDDL, or `null`.
234
+ *
235
+ * `null` off Windows and on any failure to read. Exported so a test can make
236
+ * the Windows arm a real assertion instead of "the write did not throw" —
237
+ * without it, deleting the entire protection step would kill no test on the
238
+ * platform the protection is FOR, and the only coverage would be the POSIX
239
+ * branch running somewhere else.
240
+ *
241
+ * `icacls /save` writes a UTF-16 file whose second line is the descriptor.
242
+ * SDDL names principals by security identifier, so nothing here depends on
243
+ * the machine's display language.
244
+ */
245
+ export function readAclSddl(path) {
246
+ if (platform() !== 'win32')
247
+ return null;
248
+ const system32 = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32');
249
+ const aclPath = join(tmpdir(), `namzu-acl-${randomBytes(6).toString('hex')}`);
250
+ try {
251
+ execFileSync(join(system32, 'icacls.exe'), [path, '/save', aclPath], {
252
+ encoding: 'utf8',
253
+ timeout: 10_000,
254
+ stdio: ['ignore', 'pipe', 'ignore'],
255
+ windowsHide: true,
256
+ });
257
+ return readFileSync(aclPath, 'utf16le');
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ finally {
263
+ rmSync(aclPath, { force: true });
264
+ }
265
+ }
266
+ /** The current account's security identifier, or `null` off Windows / on failure. */
267
+ export function currentUserSid() {
268
+ if (platform() !== 'win32')
269
+ return null;
270
+ const system32 = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32');
271
+ try {
272
+ const out = execFileSync(join(system32, 'whoami.exe'), ['/user', '/fo', 'csv', '/nh'], {
273
+ encoding: 'utf8',
274
+ timeout: 10_000,
275
+ stdio: ['ignore', 'pipe', 'ignore'],
276
+ windowsHide: true,
277
+ });
278
+ return out.match(/"(S-1-[0-9-]+)"/)?.[1] ?? null;
279
+ }
280
+ catch {
281
+ return null;
282
+ }
283
+ }
284
+ /**
285
+ * Require an SDDL discretionary ACL to grant exactly one principal, and that
286
+ * principal to be `sid`.
287
+ *
288
+ * Split out from the spawning above because it is the actual assertion, and
289
+ * an assertion that can only run on one operating system is an assertion
290
+ * nobody checks. Every entry is examined, not just the first: an ACL reading
291
+ * `(A;;FA;;;US)(A;;FA;;;WD)` grants us AND everyone, and a check that stopped
292
+ * at a match would call that private.
293
+ */
294
+ export function assertSoleOwnerSddl(sddl, sid, path) {
295
+ const dacl = sddl.match(/D:([A-Z]*)((?:\([^)]*\))*)/);
296
+ const flags = dacl?.[1] ?? '';
297
+ const body = dacl?.[2] ?? '';
298
+ if (!dacl || body.length === 0) {
299
+ throw new CredentialStoreError(`the access-control list of ${path} could not be read back, so its privacy is unestablished.`);
300
+ }
301
+ if (!flags.includes('P')) {
302
+ throw new CredentialStoreError(`${path} still inherits permissions from its parent folder, so accounts other than yours may read it.`);
303
+ }
304
+ const aces = [...body.matchAll(/\(([^)]*)\)/g)].map((m) => (m[1] ?? '').split(';'));
305
+ if (aces.length === 0) {
306
+ throw new CredentialStoreError(`the access-control list of ${path} could not be read back, so its privacy is unestablished.`);
307
+ }
308
+ for (const ace of aces) {
309
+ const type = ace[0] ?? '';
310
+ const trustee = ace[5] ?? '';
311
+ // Only an ALLOW entry grants anything; a DENY entry narrows and is safe.
312
+ if (type !== 'A' && type !== 'AI')
313
+ continue;
314
+ if (trustee.toUpperCase() !== sid.toUpperCase()) {
315
+ throw new CredentialStoreError(`${path} grants access to an account other than yours (${trustee}) — refusing to keep a credential there.`);
316
+ }
317
+ }
318
+ if (!aces.some((ace) => (ace[0] ?? '') === 'A' || (ace[0] ?? '') === 'AI')) {
319
+ throw new CredentialStoreError(`${path} ended with no account able to read it, which is not a credential store — refusing.`);
320
+ }
321
+ }
322
+ //# sourceMappingURL=credential-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../../src/integrations/providers/credential-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACN,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,EACR,SAAS,GACT,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAIzC,MAAM,SAAS,GAAG,KAAK,CAAA;AACvB,MAAM,QAAQ,GAAG,KAAK,CAAA;AAEtB,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAU,CAAA;AAElD,wFAAwF;AACxF,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC5B,IAAI,GAAG,sBAAsB,CAAA;CAC/C;AAED,MAAM,UAAU,eAAe,CAAC,OAAe,OAAO,EAAE;IACvD,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAA;AAChD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gCAAgC,CAC/C,OAAe,OAAO,EAAE;IAExB,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACJ,GAAG,GAAG,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAA;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;IACD,IAAI,MAAe,CAAA;IACnB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC9D,MAAM,GAAG,GAAI,MAAqC,CAAC,YAAY,CAAA;IAC/D,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IACxD,MAAM,GAAG,GAAG,GAA8B,CAAA;IAC1C,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,CAAA;IACnC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC5E,OAAO;QACN,WAAW;QACX,YAAY,EAAE,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;QACjF,SAAS,EAAE,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACxE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YAChC,CAAC,CAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAc;YAC/D,CAAC,CAAC,SAAS;KACZ,CAAA;AACF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iCAAiC,CAChD,IAA0B,EAC1B,OAAe,OAAO,EAAE;IAExB,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IAClC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAA;IAC7D,MAAM,GAAG,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAA;IAC1E,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAC7B;QACC,OAAO,EAAE,wBAAwB;QACjC,YAAY,EAAE;YACb,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/C;KACD,EACD,IAAI,EACJ,CAAC,CACD,IAAI,CAAA;IAEL,IAAI,EAAsB,CAAA;IAC1B,IAAI,CAAC;QACJ,qEAAqE;QACrE,mEAAmE;QACnE,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;QACnC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS,CAAC,EAAE,CAAC,CAAA;QACnC,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,MAAM,IAAI,oBAAoB,CAC7B,mBAAmB,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9E,CAAA;IACF,CAAC;IACD,SAAS,CAAC,EAAE,CAAC,CAAA;IAEb,IAAI,CAAC;QACJ,eAAe,CAAC,GAAG,CAAC,CAAA;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,MAAM,GAAG,CAAA;IACV,CAAC;IAED,IAAI,CAAC;QACJ,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5B,MAAM,IAAI,oBAAoB,CAC7B,mBAAmB,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9E,CAAA;IACF,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iCAAiC,CAAC,OAAe,OAAO,EAAE;IACzE,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC3C,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC5B,sBAAsB,CAAC,IAAI,CAAC,CAAA;QAC5B,OAAM;IACP,CAAC;IACD,0EAA0E;IAC1E,0EAA0E;IAC1E,mCAAmC;IACnC,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACJ,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAA;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,oBAAoB,CAC7B,0CAA0C,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACrG,CAAA;IACF,CAAC;IACD,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AAChC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,IAAY;IAC7D,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,oBAAoB,CAC7B,GAAG,IAAI,uCAAuC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,kFAAkF,CAC1J,CAAA;IACF,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,sBAAsB,CAAC,IAAY;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,aAAa,EAAE,UAAU,CAAC,CAAA;IAC1E,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,IAAuB,EAAU,EAAE;QAC5D,IAAI,CAAC;YACJ,OAAO,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;gBACnD,QAAQ,EAAE,MAAM;gBAChB,OAAO,EAAE,MAAM;gBACf,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;gBACnC,WAAW,EAAE,IAAI;aACjB,CAAC,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,oBAAoB,CAC7B,GAAG,GAAG,qBAAqB,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,2EAA2E,CAC/J,CAAA;QACF,CAAC;IACF,CAAC,CAAA;IAED,gEAAgE;IAChE,MAAM,GAAG,GAAG,cAAc,EAAE,CAAA;IAC5B,IAAI,CAAC,GAAG,EAAE,CAAC;QACV,MAAM,IAAI,oBAAoB,CAC7B,uFAAuF,IAAI,yBAAyB,CACpH,CAAA;IACF,CAAC;IAED,GAAG,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC,CAAA;IAEpE,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC/B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,oBAAoB,CAC7B,kDAAkD,IAAI,oCAAoC,CAC1F,CAAA;IACF,CAAC;IACD,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,IAAI,QAAQ,EAAE,KAAK,OAAO;QAAE,OAAO,IAAI,CAAA;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,aAAa,EAAE,UAAU,CAAC,CAAA;IAC1E,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC7E,IAAI,CAAC;QACJ,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;YACpE,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,WAAW,EAAE,IAAI;SACjB,CAAC,CAAA;QACF,OAAO,YAAY,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACjC,CAAC;AACF,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc;IAC7B,IAAI,QAAQ,EAAE,KAAK,OAAO;QAAE,OAAO,IAAI,CAAA;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,aAAa,EAAE,UAAU,CAAC,CAAA;IAC1E,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE;YACtF,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,WAAW,EAAE,IAAI;SACjB,CAAC,CAAA;QACF,OAAO,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IACjD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAA;IACZ,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,GAAW,EAAE,IAAY;IAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC7B,MAAM,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,oBAAoB,CAC7B,8BAA8B,IAAI,2DAA2D,CAC7F,CAAA;IACF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,oBAAoB,CAC7B,GAAG,IAAI,+FAA+F,CACtG,CAAA;IACF,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IACnF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAoB,CAC7B,8BAA8B,IAAI,2DAA2D,CAC7F,CAAA;IACF,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACzB,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAC5B,yEAAyE;QACzE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;YAAE,SAAQ;QAC3C,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;YACjD,MAAM,IAAI,oBAAoB,CAC7B,GAAG,IAAI,kDAAkD,OAAO,0CAA0C,CAC1G,CAAA;QACF,CAAC;IACF,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,oBAAoB,CAC7B,GAAG,IAAI,qFAAqF,CAC5F,CAAA;IACF,CAAC;AACF,CAAC"}
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Credential discoverer for LLM provider clients.
3
3
  *
4
- * For each entry in `PROVIDER_REGISTRY`, ask three questions in order:
4
+ * For each entry in `PROVIDER_REGISTRY`, ask four questions in order:
5
5
  * 1. Is one of its env vars set in `process.env`?
6
- * 2. Is there an OAuth credential in the login Keychain? (macOS only, and
6
+ * 2. Is there a subscription credential in namzu's own store — the one the
7
+ * login flow writes? (every platform, and only `anthropic` consumes it.)
8
+ * 3. Is there an OAuth credential in the login Keychain? (macOS only, and
7
9
  * only `anthropic` consumes it.)
8
- * 3. Is the probe URL (if any) reachable right now?
10
+ * 4. Is the probe URL (if any) reachable right now?
9
11
  *
10
12
  * The header used to say "three" and list two, and the one it omitted was the
11
13
  * Keychain — the question that reads a secret off the machine, so the one a
@@ -13,11 +15,18 @@
13
15
  * tell that the list stopped being maintained; in a file about credentials that
14
16
  * is worth more than a typo.
15
17
  *
16
- * **The Keychain path is macOS-only and that is a gap, not a nuance.**
17
- * `readAgentKeychainCredential` returns `null` on any other platform before it
18
- * looks at anything, so on Windows and Linux exactly two doors exist: an
19
- * environment variable, and a reachable local server. An operator whose
20
- * credential lives in their OS credential store gets no help from namzu there.
18
+ * **The Keychain path is macOS-only, and question 2 is why that is no longer a
19
+ * hole.** `readAgentKeychainCredential` returns `null` on any other platform
20
+ * before it looks at anything; it reads a credential belonging to a
21
+ * co-installed tool, so it can only ever help someone who has that tool, on
22
+ * that operating system. namzu's own store is asked first among the credential
23
+ * sources and works everywhere, which is what makes signing in from inside
24
+ * namzu useful on a machine that has neither.
25
+ *
26
+ * Order between the two matters when both answer. namzu's own store wins,
27
+ * because it is the one namzu wrote and refreshes; preferring a borrowed
28
+ * credential over the operator's own sign-in would make the login look
29
+ * ineffective on precisely the machine where they bothered to run it.
21
30
  *
22
31
  * The first positive answer per provider wins; subsequent sources are
23
32
  * recorded as "also available from" so the picker can show alternatives
@@ -51,6 +60,7 @@
51
60
  * Discovery is non-throwing. Network probes have short timeouts. The
52
61
  * picker can render immediately and refine if discovery completes later.
53
62
  */
63
+ import type { CredentialOrigin } from './oauth.js';
54
64
  import { type ProviderId, type ProviderRegistryEntry } from './registry.js';
55
65
  export type DetectionSource = {
56
66
  readonly kind: 'env';
@@ -62,6 +72,15 @@ export type DetectionSource = {
62
72
  readonly kind: 'keychain';
63
73
  readonly service: string;
64
74
  }
75
+ /**
76
+ * namzu's own credential store — a subscription the operator signed in to
77
+ * from inside namzu. Carries the path because "where did this come from"
78
+ * is the first question asked of a credential nobody typed.
79
+ */
80
+ | {
81
+ readonly kind: 'stored';
82
+ readonly path: string;
83
+ }
65
84
  /**
66
85
  * Typed into a running namzu and held in memory for this session.
67
86
  *
@@ -83,12 +102,18 @@ export interface DetectedProvider {
83
102
  readonly baseUrl?: string;
84
103
  /**
85
104
  * OAuth refresh metadata, present only when `apiKey` is an OAuth access
86
- * token carrying a refresh token + expiry (the Keychain source). Lets
87
- * the session layer renew a lapsed token instead of failing with a 401.
105
+ * token carrying a refresh token + expiry (namzu's own store, or the
106
+ * Keychain). Lets the session layer renew a lapsed token instead of
107
+ * failing with a 401.
108
+ *
109
+ * `origin` travels with it because the refresh has to be written BACK, and
110
+ * the two sources are not interchangeable: one is namzu's file and the
111
+ * other is a co-installed tool's Keychain entry.
88
112
  */
89
113
  readonly oauth?: {
90
114
  readonly refreshToken?: string;
91
115
  readonly expiresAt?: number;
116
+ readonly origin?: CredentialOrigin;
92
117
  };
93
118
  /** Other sources that also satisfy this provider — informational. */
94
119
  readonly alternatives: readonly DetectionSource[];
@@ -106,6 +131,8 @@ export interface DiscoverOptions {
106
131
  readonly skipProbes?: boolean;
107
132
  /** Skip the macOS Keychain read (tests, non-darwin runs). */
108
133
  readonly skipKeychain?: boolean;
134
+ /** Skip namzu's own credential store (tests, and `--no-stored-credential`). */
135
+ readonly skipStored?: boolean;
109
136
  }
110
137
  export declare function discoverProviders(opts?: DiscoverOptions): Promise<readonly DetectedProvider[]>;
111
138
  /** Resolve a single detected provider by id from the discovered list. */
@@ -1 +1 @@
1
- {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAGH,OAAO,EAAqB,KAAK,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE9F,MAAM,MAAM,eAAe,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACzD;;;;;;;GAOG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAE/B,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAA;IACrC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChF,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAA;CACjD;AAED,MAAM,WAAW,eAAe;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IAChC,sCAAsC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAC/B;AAID,wBAAsB,iBAAiB,CACtC,IAAI,GAAE,eAAoB,GACxB,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAmDtC;AAmBD,yEAAyE;AACzE,wBAAgB,YAAY,CAC3B,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,EAAE,EAAE,UAAU,GACZ,gBAAgB,GAAG,IAAI,CAEzB"}
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EAAqB,KAAK,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE9F,MAAM,MAAM,eAAe,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACzD;;;;GAIG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACpD;;;;;;;GAOG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAE/B,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAA;IACrC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QAChB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;QAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;QAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;KAClC,CAAA;IACD,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAA;CACjD;AAED,MAAM,WAAW,eAAe;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IAChC,sCAAsC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC7B;AAID,wBAAsB,iBAAiB,CACtC,IAAI,GAAE,eAAoB,GACxB,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAsEtC;AAmBD,yEAAyE;AACzE,wBAAgB,YAAY,CAC3B,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,EAAE,EAAE,UAAU,GACZ,gBAAgB,GAAG,IAAI,CAEzB"}