@doow/cli 0.1.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 (43) hide show
  1. package/README.md +75 -0
  2. package/dist/cjs/auth/api-key.js +159 -0
  3. package/dist/cjs/auth/api-key.js.map +1 -0
  4. package/dist/cjs/auth/detect.js +173 -0
  5. package/dist/cjs/auth/detect.js.map +1 -0
  6. package/dist/cjs/auth/device-flow.js +135 -0
  7. package/dist/cjs/auth/device-flow.js.map +1 -0
  8. package/dist/cjs/auth/keyring.js +118 -0
  9. package/dist/cjs/auth/keyring.js.map +1 -0
  10. package/dist/cjs/auth/pkce.js +243 -0
  11. package/dist/cjs/auth/pkce.js.map +1 -0
  12. package/dist/cjs/auth/refresh.js +203 -0
  13. package/dist/cjs/auth/refresh.js.map +1 -0
  14. package/dist/cjs/config/env.js +44 -0
  15. package/dist/cjs/config/env.js.map +1 -0
  16. package/dist/cjs/config/store.js +178 -0
  17. package/dist/cjs/config/store.js.map +1 -0
  18. package/dist/cjs/index.js +48 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cli.cjs +34372 -0
  21. package/dist/cli.cjs.map +1 -0
  22. package/dist/esm/auth/api-key.js +154 -0
  23. package/dist/esm/auth/api-key.js.map +1 -0
  24. package/dist/esm/auth/detect.js +150 -0
  25. package/dist/esm/auth/detect.js.map +1 -0
  26. package/dist/esm/auth/device-flow.js +132 -0
  27. package/dist/esm/auth/device-flow.js.map +1 -0
  28. package/dist/esm/auth/keyring.js +116 -0
  29. package/dist/esm/auth/keyring.js.map +1 -0
  30. package/dist/esm/auth/pkce.js +220 -0
  31. package/dist/esm/auth/pkce.js.map +1 -0
  32. package/dist/esm/auth/refresh.js +198 -0
  33. package/dist/esm/auth/refresh.js.map +1 -0
  34. package/dist/esm/config/env.js +38 -0
  35. package/dist/esm/config/env.js.map +1 -0
  36. package/dist/esm/config/store.js +166 -0
  37. package/dist/esm/config/store.js.map +1 -0
  38. package/dist/esm/index.js +15 -0
  39. package/dist/esm/index.js.map +1 -0
  40. package/dist/mcp.cjs +8 -0
  41. package/dist/mcp.cjs.map +1 -0
  42. package/dist/types/index.d.ts +369 -0
  43. package/package.json +62 -0
@@ -0,0 +1,154 @@
1
+ import { getApiUrl } from '../config/env.js';
2
+ import { createCredentialStore } from './keyring.js';
3
+
4
+ /**
5
+ * api-key.ts
6
+ *
7
+ * PAT (Personal Access Token) authentication for CI/scripting contexts.
8
+ *
9
+ * Provides:
10
+ * - validateApiKey — pure format check (dak_ prefix, length ≥ 20)
11
+ * - authenticateWithApiKey — store key + optionally verify against API
12
+ * - readTokenFromStdin — read a piped token from stdin
13
+ * - resolveAuth — precedence chain: flag > env > stdin > stored > none
14
+ */
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+ const DAK_PREFIX = 'dak_';
19
+ const DAK_MIN_LENGTH = 20;
20
+ const REFRESH_BUFFER_SECONDS = 60;
21
+ // ---------------------------------------------------------------------------
22
+ // validateApiKey
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Returns true if key starts with 'dak_' (case-sensitive) and is at least
26
+ * 20 characters total. Pure function — no network call.
27
+ */
28
+ function validateApiKey(key) {
29
+ return key.startsWith(DAK_PREFIX) && key.length >= DAK_MIN_LENGTH;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // authenticateWithApiKey
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Validates the key format, stores it in the credential store, and optionally
36
+ * verifies it works by hitting GET /v1/auth/capabilities.
37
+ *
38
+ * @throws {Error} if the key does not have the dak_ prefix
39
+ * @throws {Error} if verify is true and the capabilities request fails
40
+ */
41
+ async function authenticateWithApiKey(options) {
42
+ const { key, profileName = 'default', verify = true } = options;
43
+ const apiUrl = options.apiUrl ?? getApiUrl();
44
+ const store = options.credentialStore ?? (await createCredentialStore());
45
+ // Validate format first
46
+ if (!validateApiKey(key)) {
47
+ throw new Error(`Invalid API key format. Keys must start with '${DAK_PREFIX}' and be at least ${DAK_MIN_LENGTH} characters long.`);
48
+ }
49
+ // Optionally verify before storing
50
+ if (verify) {
51
+ const res = await fetch(`${apiUrl}/v1/auth/capabilities`, {
52
+ headers: { Authorization: `Bearer ${key}` },
53
+ });
54
+ if (!res.ok) {
55
+ const body = await res.text().catch(() => '');
56
+ throw new Error(`API key verification failed: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
57
+ }
58
+ await store.set(profileName, { apiKey: key });
59
+ return { apiKey: key, verified: true };
60
+ }
61
+ await store.set(profileName, { apiKey: key });
62
+ return { apiKey: key, verified: false };
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // readTokenFromStdin
66
+ // ---------------------------------------------------------------------------
67
+ /**
68
+ * Reads a token from piped stdin input.
69
+ *
70
+ * @throws {Error} if stdin is a TTY (not piped)
71
+ * @throws {Error} if the resulting string is empty after trim
72
+ */
73
+ async function readTokenFromStdin() {
74
+ if (process.stdin.isTTY) {
75
+ throw new Error('--token-stdin requires piped input (e.g., echo $TOKEN | doow login --token-stdin)');
76
+ }
77
+ const parts = [];
78
+ for await (const chunk of process.stdin) {
79
+ if (Buffer.isBuffer(chunk)) {
80
+ parts.push(chunk.toString('utf-8'));
81
+ }
82
+ else {
83
+ parts.push(String(chunk));
84
+ }
85
+ }
86
+ const token = parts.join('').trim();
87
+ if (!token) {
88
+ throw new Error('No token received on stdin');
89
+ }
90
+ return token;
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // resolveAuth
94
+ // ---------------------------------------------------------------------------
95
+ /**
96
+ * Resolves the active auth context by walking the precedence chain:
97
+ * --api-key flag > DOOW_API_KEY env > --token-stdin > stored profile > none
98
+ */
99
+ async function resolveAuth(options = {}) {
100
+ const { apiKeyFlag, tokenStdin = false, profileName = 'default' } = options;
101
+ const store = options.credentialStore ?? (await createCredentialStore());
102
+ // 1. --api-key flag
103
+ if (apiKeyFlag !== undefined && apiKeyFlag !== '') {
104
+ if (!validateApiKey(apiKeyFlag)) {
105
+ throw new Error(`Invalid API key format. Keys must start with '${DAK_PREFIX}' and be at least ${DAK_MIN_LENGTH} characters long.`);
106
+ }
107
+ return { type: 'api-key', token: apiKeyFlag, source: '--api-key flag' };
108
+ }
109
+ // 2. DOOW_API_KEY env var
110
+ const envKey = process.env['DOOW_API_KEY'];
111
+ if (envKey && validateApiKey(envKey)) {
112
+ return { type: 'api-key', token: envKey, source: 'DOOW_API_KEY env' };
113
+ }
114
+ // 3. --token-stdin
115
+ if (tokenStdin) {
116
+ const token = await readTokenFromStdin();
117
+ const type = validateApiKey(token) ? 'api-key' : 'oauth-token';
118
+ return { type, token, source: 'stdin' };
119
+ }
120
+ // 4. Stored credentials for the profile
121
+ const creds = await store.get(profileName);
122
+ if (creds?.apiKey) {
123
+ return { type: 'api-key', token: creds.apiKey, source: 'stored profile' };
124
+ }
125
+ if (creds?.accessToken) {
126
+ const token = creds.accessToken;
127
+ const needsRefresh = isTokenExpiredOrExpiring(creds.expiresAt);
128
+ return {
129
+ type: 'oauth-token',
130
+ token,
131
+ source: 'stored profile',
132
+ ...(needsRefresh ? { needsRefresh: true } : {}),
133
+ };
134
+ }
135
+ // 5. Nothing found
136
+ return { type: 'none', source: 'none' };
137
+ }
138
+ // ---------------------------------------------------------------------------
139
+ // Private helpers
140
+ // ---------------------------------------------------------------------------
141
+ /**
142
+ * Returns true if the ISO-8601 expiresAt string is either absent, already
143
+ * past, or within 60 seconds of now.
144
+ */
145
+ function isTokenExpiredOrExpiring(expiresAt) {
146
+ if (!expiresAt)
147
+ return true;
148
+ const expiryMs = new Date(expiresAt).getTime();
149
+ const nowPlusBuffer = Date.now() + REFRESH_BUFFER_SECONDS * 1000;
150
+ return expiryMs <= nowPlusBuffer;
151
+ }
152
+
153
+ export { authenticateWithApiKey, readTokenFromStdin, resolveAuth, validateApiKey };
154
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.js","sources":["../../../../src/auth/api-key.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ import * as http from 'node:http';
2
+ import { isCI, isTTY } from '../config/env.js';
3
+ import { validateApiKey, authenticateWithApiKey } from './api-key.js';
4
+ import { executePkceFlow } from './pkce.js';
5
+ import { executeDeviceFlow } from './device-flow.js';
6
+
7
+ /**
8
+ * detect.ts
9
+ *
10
+ * S122 — Auth auto-detection for the Doow CLI.
11
+ *
12
+ * Determines whether to use PKCE, device, or API-key authentication based on
13
+ * the current runtime environment, then executes the appropriate flow.
14
+ *
15
+ * Detection order:
16
+ * 1. --api-key provided + valid format → api-key
17
+ * 2. --device flag → device
18
+ * 3. Running inside CI ($CI set) → device
19
+ * 4. Non-interactive stdout (!isTTY) → device
20
+ * 5. Can bind 127.0.0.1 on a free port → pkce
21
+ * 6. Fallback → device
22
+ */
23
+ // ---------------------------------------------------------------------------
24
+ // canBindLocalhost
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Tests whether the process can bind a TCP server on 127.0.0.1 using an
28
+ * OS-assigned ephemeral port. The server is closed immediately on success.
29
+ *
30
+ * Returns true → PKCE callback server will work.
31
+ * Returns false → Network stack can't bind (containers with restricted network
32
+ * policies, permission denied, etc.) — use device flow instead.
33
+ */
34
+ function canBindLocalhost() {
35
+ return new Promise((resolve) => {
36
+ const server = http.createServer();
37
+ server.once('error', () => {
38
+ resolve(false);
39
+ });
40
+ server.listen(0, '127.0.0.1', () => {
41
+ server.close(() => resolve(true));
42
+ });
43
+ });
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // detectAuthMethod
47
+ // ---------------------------------------------------------------------------
48
+ /**
49
+ * Returns the best authentication method for the current environment.
50
+ */
51
+ async function detectAuthMethod(options = {}) {
52
+ const { forceDevice = false, apiKey } = options;
53
+ // 1. API key provided and format-valid → skip OAuth entirely
54
+ if (apiKey !== undefined && apiKey !== '' && validateApiKey(apiKey)) {
55
+ return 'api-key';
56
+ }
57
+ // 2. Explicit --device flag
58
+ if (forceDevice) {
59
+ return 'device';
60
+ }
61
+ // 3. CI environment — browser is not available
62
+ if (isCI()) {
63
+ return 'device';
64
+ }
65
+ // 4. Non-interactive (stdout is not a TTY) — can't drive a browser login
66
+ if (!isTTY()) {
67
+ return 'device';
68
+ }
69
+ // 5. Try to bind a local port — if it works, PKCE callback server will too
70
+ const canBind = await canBindLocalhost();
71
+ if (canBind) {
72
+ return 'pkce';
73
+ }
74
+ // 6. Safe fallback
75
+ return 'device';
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // isServerBindError
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Returns true when an error looks like a localhost server bind failure —
82
+ * the only case where PKCE should automatically fall back to device flow.
83
+ *
84
+ * We intentionally do NOT fall back on application-level errors such as
85
+ * CSRF mismatch, token exchange failures, or user cancellation.
86
+ */
87
+ function isServerBindError(err) {
88
+ if (!(err instanceof Error))
89
+ return false;
90
+ const msg = err.message.toLowerCase();
91
+ return (msg.includes('failed to start local callback server') ||
92
+ msg.includes('failed to determine callback server port') ||
93
+ msg.includes('failed to open browser') ||
94
+ msg.includes('eaddrinuse') ||
95
+ msg.includes('eacces') ||
96
+ msg.includes('permission denied'));
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // executeAutoLogin
100
+ // ---------------------------------------------------------------------------
101
+ /**
102
+ * The main login orchestrator.
103
+ *
104
+ * 1. Detects the best auth method.
105
+ * 2. Executes the corresponding flow.
106
+ * 3. If PKCE fails with a server bind error, automatically retries with device flow.
107
+ */
108
+ async function executeAutoLogin(options = {}) {
109
+ const { forceDevice, apiKey, apiUrl, profileName, credentialStore, timeout } = options;
110
+ const method = await detectAuthMethod({ forceDevice, apiKey });
111
+ if (method === 'api-key') {
112
+ // apiKey is guaranteed non-empty here (detectAuthMethod validated it)
113
+ await authenticateWithApiKey({
114
+ key: apiKey,
115
+ apiUrl,
116
+ profileName,
117
+ credentialStore,
118
+ });
119
+ return { method: 'api-key', apiKey };
120
+ }
121
+ if (method === 'pkce') {
122
+ try {
123
+ const result = await executePkceFlow({ apiUrl, profileName, credentialStore, timeout });
124
+ return {
125
+ method: 'pkce',
126
+ accessToken: result.accessToken,
127
+ refreshToken: result.refreshToken,
128
+ expiresAt: result.expiresAt,
129
+ };
130
+ }
131
+ catch (err) {
132
+ // Only fall back on server bind errors — not on CSRF or token exchange errors
133
+ if (!isServerBindError(err)) {
134
+ throw err;
135
+ }
136
+ // Fall through to device flow below
137
+ }
138
+ }
139
+ // device flow (either detected or PKCE fallback)
140
+ const result = await executeDeviceFlow({ apiUrl, profileName, credentialStore });
141
+ return {
142
+ method: 'device',
143
+ accessToken: result.accessToken,
144
+ refreshToken: result.refreshToken,
145
+ expiresAt: result.expiresAt,
146
+ };
147
+ }
148
+
149
+ export { canBindLocalhost, detectAuthMethod, executeAutoLogin };
150
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sources":["../../../../src/auth/detect.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import { getApiUrl, shouldShowUI } from '../config/env.js';
2
+ import { createCredentialStore } from './keyring.js';
3
+
4
+ /**
5
+ * device-flow.ts
6
+ *
7
+ * RFC 8628 OAuth 2.0 Device Authorization flow for headless/SSH/container
8
+ * environments where a browser cannot be opened on the same machine.
9
+ *
10
+ * Steps:
11
+ * 1. POST /v1/auth/device/authorize → get device_code + user_code
12
+ * 2. Display verification URI + user_code on stderr
13
+ * 3. Optionally open the browser (best-effort)
14
+ * 4. Poll POST /v1/auth/device/token until granted or expired
15
+ * 5. Store tokens in the credential store
16
+ */
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+ /** Sleep for `ms` milliseconds — real timer in production, override in tests. */
21
+ function sleep(ms) {
22
+ return new Promise((resolve) => setTimeout(resolve, ms));
23
+ }
24
+ /**
25
+ * Build an ISO-8601 expiry timestamp from a seconds-from-now value.
26
+ * Exported for test visibility only.
27
+ */
28
+ function expiresAtFromSecondsIn(secondsIn) {
29
+ return new Date(Date.now() + secondsIn * 1000).toISOString();
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Core function
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Execute the RFC 8628 device authorization flow.
36
+ *
37
+ * All user-facing output goes to stderr so stdout stays clean for piping.
38
+ *
39
+ * @throws {Error} if authorization fails or the device code expires.
40
+ */
41
+ async function executeDeviceFlow(options = {}) {
42
+ const apiUrl = options.apiUrl ?? getApiUrl();
43
+ const profileName = options.profileName ?? 'default';
44
+ const store = options.credentialStore ?? (await createCredentialStore());
45
+ const openUrl = options.openUrl ??
46
+ (async (url) => {
47
+ const { default: open } = await import('open');
48
+ await open(url);
49
+ });
50
+ // -------------------------------------------------------------------------
51
+ // Step 1: Request device authorization
52
+ // -------------------------------------------------------------------------
53
+ const authorizeRes = await fetch(`${apiUrl}/v1/auth/device/authorize`, {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({ client_id: 'doow-cli' }),
57
+ });
58
+ if (!authorizeRes.ok) {
59
+ const body = await authorizeRes.text().catch(() => '');
60
+ throw new Error(`Device authorization request failed: HTTP ${authorizeRes.status}${body ? ` — ${body}` : ''}`);
61
+ }
62
+ const auth = (await authorizeRes.json());
63
+ // -------------------------------------------------------------------------
64
+ // Step 2: Display instructions on stderr
65
+ // -------------------------------------------------------------------------
66
+ process.stderr.write(`\nTo sign in, open this URL in any browser:\n ${auth.verification_uri}\n\nThen enter code: ${auth.user_code}\n\n`);
67
+ // Step 2b: Best-effort browser open — never throw on failure
68
+ if (shouldShowUI()) {
69
+ try {
70
+ await openUrl(auth.verification_uri);
71
+ }
72
+ catch {
73
+ // Silently ignore — user can open manually
74
+ }
75
+ }
76
+ // -------------------------------------------------------------------------
77
+ // Step 3: Poll for token
78
+ // -------------------------------------------------------------------------
79
+ let pollInterval = auth.interval; // seconds; RFC 8628 §3.5
80
+ while (true) {
81
+ await sleep(pollInterval * 1000);
82
+ const tokenRes = await fetch(`${apiUrl}/v1/auth/device/token`, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({
86
+ device_code: auth.device_code,
87
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
88
+ }),
89
+ });
90
+ if (tokenRes.ok) {
91
+ // Success — stop polling
92
+ const tokens = (await tokenRes.json());
93
+ // -----------------------------------------------------------------------
94
+ // Step 4: Store tokens
95
+ // -----------------------------------------------------------------------
96
+ const expiresAt = expiresAtFromSecondsIn(tokens.expires_in);
97
+ await store.set(profileName, {
98
+ accessToken: tokens.access_token,
99
+ refreshToken: tokens.refresh_token,
100
+ expiresAt,
101
+ });
102
+ // -----------------------------------------------------------------------
103
+ // Step 5: Return result
104
+ // -----------------------------------------------------------------------
105
+ return {
106
+ accessToken: tokens.access_token,
107
+ refreshToken: tokens.refresh_token,
108
+ expiresAt,
109
+ };
110
+ }
111
+ // Non-200 — parse RFC 8628 error body
112
+ const errBody = (await tokenRes.json().catch(() => ({ error: 'unknown' })));
113
+ switch (errBody.error) {
114
+ case 'authorization_pending':
115
+ // User hasn't approved yet — keep polling at current interval
116
+ continue;
117
+ case 'slow_down':
118
+ // RFC 8628 §3.5: increase interval by 5 seconds
119
+ pollInterval += 5;
120
+ continue;
121
+ case 'expired_token':
122
+ throw new Error('Device code expired. Run doow login --device again.');
123
+ case 'access_denied':
124
+ throw new Error('Authorization denied by user.');
125
+ default:
126
+ throw new Error(`Token polling failed: ${errBody.error}${errBody.error_description ? ` — ${errBody.error_description}` : ''}`);
127
+ }
128
+ }
129
+ }
130
+
131
+ export { executeDeviceFlow, expiresAtFromSecondsIn };
132
+ //# sourceMappingURL=device-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.js","sources":["../../../../src/auth/device-flow.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import { clearProfileCredentials, setProfileCredentials, getProfileCredentials } from '../config/store.js';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Constants
5
+ // ---------------------------------------------------------------------------
6
+ const SERVICE_NAME = 'doow-cli';
7
+ const PROBE_TIMEOUT_MS = 3_000;
8
+ const OP_TIMEOUT_MS = 3_000;
9
+ // Module-level flag — only warn once per process lifetime.
10
+ let hasPrintedFallbackWarning = false;
11
+ // ---------------------------------------------------------------------------
12
+ // Timeout helper
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Races `promise` against a rejection that fires after `ms` milliseconds.
16
+ * Avoids AbortSignal.timeout which requires Node >=17.3.
17
+ */
18
+ function withTimeout(promise, ms) {
19
+ return Promise.race([
20
+ promise,
21
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)),
22
+ ]);
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // One-time fallback warning
26
+ // ---------------------------------------------------------------------------
27
+ function printFallbackWarning() {
28
+ if (hasPrintedFallbackWarning)
29
+ return;
30
+ hasPrintedFallbackWarning = true;
31
+ process.stderr.write('⚠ System keyring unavailable — credentials stored in ~/.doow/credentials.json (chmod 0600)\n');
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // File-backed store (wraps S118 store.ts functions)
35
+ // ---------------------------------------------------------------------------
36
+ function createFileStore() {
37
+ return {
38
+ async get(profileName) {
39
+ return getProfileCredentials(profileName);
40
+ },
41
+ async set(profileName, creds) {
42
+ return setProfileCredentials(profileName, creds);
43
+ },
44
+ async clear(profileName) {
45
+ return clearProfileCredentials(profileName);
46
+ },
47
+ backend() {
48
+ return 'file';
49
+ },
50
+ };
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Keyring-backed store
54
+ // ---------------------------------------------------------------------------
55
+ function createKeyringStore(keytar) {
56
+ return {
57
+ async get(profileName) {
58
+ try {
59
+ const raw = await withTimeout(keytar.getPassword(SERVICE_NAME, profileName), OP_TIMEOUT_MS);
60
+ if (raw == null)
61
+ return undefined;
62
+ return JSON.parse(raw);
63
+ }
64
+ catch {
65
+ return undefined;
66
+ }
67
+ },
68
+ async set(profileName, creds) {
69
+ await withTimeout(keytar.setPassword(SERVICE_NAME, profileName, JSON.stringify(creds)), OP_TIMEOUT_MS);
70
+ },
71
+ async clear(profileName) {
72
+ await withTimeout(keytar.deletePassword(SERVICE_NAME, profileName), OP_TIMEOUT_MS);
73
+ },
74
+ backend() {
75
+ return 'keyring';
76
+ },
77
+ };
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Factory
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Creates a CredentialStore backed by the system keyring when available,
84
+ * falling back to file storage silently when keytar is not installed or
85
+ * the system keyring does not respond within 3 seconds.
86
+ *
87
+ * Call this once at startup and reuse the returned instance.
88
+ */
89
+ async function createCredentialStore() {
90
+ // Step 1: Try loading keytar (optional peer dependency).
91
+ let keytar;
92
+ try {
93
+ // Dynamic import so missing keytar never crashes the module at load time.
94
+ // @ts-expect-error — keytar is an optional peer dep and may not be installed.
95
+ const mod = await import('keytar');
96
+ // Support both default-export and named-export module shapes.
97
+ keytar = (mod.default ?? mod);
98
+ }
99
+ catch {
100
+ // keytar not installed — silent, no warning needed (not an error condition).
101
+ return createFileStore();
102
+ }
103
+ // Step 2: Probe the keyring with a 3s timeout to confirm it's responsive.
104
+ try {
105
+ await withTimeout(keytar.findPassword(SERVICE_NAME), PROBE_TIMEOUT_MS);
106
+ }
107
+ catch {
108
+ printFallbackWarning();
109
+ return createFileStore();
110
+ }
111
+ // Step 3: Keyring is available and responsive — use it.
112
+ return createKeyringStore(keytar);
113
+ }
114
+
115
+ export { createCredentialStore };
116
+ //# sourceMappingURL=keyring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyring.js","sources":["../../../../src/auth/keyring.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}