latchkey 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 (176) hide show
  1. package/.nvmrc +1 -0
  2. package/.pre-commit-config.yaml +22 -0
  3. package/.prettierignore +4 -0
  4. package/.prettierrc +7 -0
  5. package/CLAUDE.md +13 -0
  6. package/LICENSE +7 -0
  7. package/README.md +167 -0
  8. package/dist/scripts/cryptFile.d.ts +21 -0
  9. package/dist/scripts/cryptFile.d.ts.map +1 -0
  10. package/dist/scripts/cryptFile.js +106 -0
  11. package/dist/scripts/cryptFile.js.map +1 -0
  12. package/dist/scripts/encryptFile.d.ts +21 -0
  13. package/dist/scripts/encryptFile.d.ts.map +1 -0
  14. package/dist/scripts/encryptFile.js +101 -0
  15. package/dist/scripts/encryptFile.js.map +1 -0
  16. package/dist/scripts/recordBrowserSession.d.ts +18 -0
  17. package/dist/scripts/recordBrowserSession.d.ts.map +1 -0
  18. package/dist/scripts/recordBrowserSession.js +213 -0
  19. package/dist/scripts/recordBrowserSession.js.map +1 -0
  20. package/dist/src/apiCredentialStore.d.ts +19 -0
  21. package/dist/src/apiCredentialStore.d.ts.map +1 -0
  22. package/dist/src/apiCredentialStore.js +65 -0
  23. package/dist/src/apiCredentialStore.js.map +1 -0
  24. package/dist/src/apiCredentials.d.ts +134 -0
  25. package/dist/src/apiCredentials.d.ts.map +1 -0
  26. package/dist/src/apiCredentials.js +139 -0
  27. package/dist/src/apiCredentials.js.map +1 -0
  28. package/dist/src/browserConfig.d.ts +90 -0
  29. package/dist/src/browserConfig.d.ts.map +1 -0
  30. package/dist/src/browserConfig.js +259 -0
  31. package/dist/src/browserConfig.js.map +1 -0
  32. package/dist/src/browserState.d.ts +8 -0
  33. package/dist/src/browserState.d.ts.map +1 -0
  34. package/dist/src/browserState.js +21 -0
  35. package/dist/src/browserState.js.map +1 -0
  36. package/dist/src/cli.d.ts +6 -0
  37. package/dist/src/cli.d.ts.map +1 -0
  38. package/dist/src/cli.js +25 -0
  39. package/dist/src/cli.js.map +1 -0
  40. package/dist/src/cliCommands.d.ts +29 -0
  41. package/dist/src/cliCommands.d.ts.map +1 -0
  42. package/dist/src/cliCommands.js +264 -0
  43. package/dist/src/cliCommands.js.map +1 -0
  44. package/dist/src/config.d.ts +35 -0
  45. package/dist/src/config.d.ts.map +1 -0
  46. package/dist/src/config.js +96 -0
  47. package/dist/src/config.js.map +1 -0
  48. package/dist/src/curl.d.ts +29 -0
  49. package/dist/src/curl.d.ts.map +1 -0
  50. package/dist/src/curl.js +53 -0
  51. package/dist/src/curl.js.map +1 -0
  52. package/dist/src/encryptedStorage.d.ts +39 -0
  53. package/dist/src/encryptedStorage.d.ts.map +1 -0
  54. package/dist/src/encryptedStorage.js +128 -0
  55. package/dist/src/encryptedStorage.js.map +1 -0
  56. package/dist/src/encryption.d.ts +28 -0
  57. package/dist/src/encryption.d.ts.map +1 -0
  58. package/dist/src/encryption.js +86 -0
  59. package/dist/src/encryption.js.map +1 -0
  60. package/dist/src/index.d.ts +14 -0
  61. package/dist/src/index.d.ts.map +1 -0
  62. package/dist/src/index.js +17 -0
  63. package/dist/src/index.js.map +1 -0
  64. package/dist/src/keychain.d.ts +33 -0
  65. package/dist/src/keychain.d.ts.map +1 -0
  66. package/dist/src/keychain.js +94 -0
  67. package/dist/src/keychain.js.map +1 -0
  68. package/dist/src/playwrightUtils.d.ts +27 -0
  69. package/dist/src/playwrightUtils.d.ts.map +1 -0
  70. package/dist/src/playwrightUtils.js +122 -0
  71. package/dist/src/playwrightUtils.js.map +1 -0
  72. package/dist/src/registry.d.ts +12 -0
  73. package/dist/src/registry.d.ts.map +1 -0
  74. package/dist/src/registry.js +30 -0
  75. package/dist/src/registry.js.map +1 -0
  76. package/dist/src/services/base.d.ts +98 -0
  77. package/dist/src/services/base.d.ts.map +1 -0
  78. package/dist/src/services/base.js +137 -0
  79. package/dist/src/services/base.js.map +1 -0
  80. package/dist/src/services/discord.d.ts +20 -0
  81. package/dist/src/services/discord.d.ts.map +1 -0
  82. package/dist/src/services/discord.js +55 -0
  83. package/dist/src/services/discord.js.map +1 -0
  84. package/dist/src/services/dropbox.d.ts +23 -0
  85. package/dist/src/services/dropbox.d.ts.map +1 -0
  86. package/dist/src/services/dropbox.js +136 -0
  87. package/dist/src/services/dropbox.js.map +1 -0
  88. package/dist/src/services/github.d.ts +23 -0
  89. package/dist/src/services/github.d.ts.map +1 -0
  90. package/dist/src/services/github.js +110 -0
  91. package/dist/src/services/github.js.map +1 -0
  92. package/dist/src/services/index.d.ts +12 -0
  93. package/dist/src/services/index.d.ts.map +1 -0
  94. package/dist/src/services/index.js +11 -0
  95. package/dist/src/services/index.js.map +1 -0
  96. package/dist/src/services/linear.d.ts +23 -0
  97. package/dist/src/services/linear.d.ts.map +1 -0
  98. package/dist/src/services/linear.js +110 -0
  99. package/dist/src/services/linear.js.map +1 -0
  100. package/dist/src/services/slack.d.ts +21 -0
  101. package/dist/src/services/slack.d.ts.map +1 -0
  102. package/dist/src/services/slack.js +67 -0
  103. package/dist/src/services/slack.js.map +1 -0
  104. package/dist/tests/apiCredentialStore.test.d.ts +2 -0
  105. package/dist/tests/apiCredentialStore.test.d.ts.map +1 -0
  106. package/dist/tests/apiCredentialStore.test.js +130 -0
  107. package/dist/tests/apiCredentialStore.test.js.map +1 -0
  108. package/dist/tests/apiCredentials.test.d.ts +2 -0
  109. package/dist/tests/apiCredentials.test.d.ts.map +1 -0
  110. package/dist/tests/apiCredentials.test.js +169 -0
  111. package/dist/tests/apiCredentials.test.js.map +1 -0
  112. package/dist/tests/cli.test.d.ts +2 -0
  113. package/dist/tests/cli.test.d.ts.map +1 -0
  114. package/dist/tests/cli.test.js +584 -0
  115. package/dist/tests/cli.test.js.map +1 -0
  116. package/dist/tests/encryptedStorage.test.d.ts +2 -0
  117. package/dist/tests/encryptedStorage.test.d.ts.map +1 -0
  118. package/dist/tests/encryptedStorage.test.js +126 -0
  119. package/dist/tests/encryptedStorage.test.js.map +1 -0
  120. package/dist/tests/encryption.test.d.ts +2 -0
  121. package/dist/tests/encryption.test.d.ts.map +1 -0
  122. package/dist/tests/encryption.test.js +121 -0
  123. package/dist/tests/encryption.test.js.map +1 -0
  124. package/dist/tests/lint.test.d.ts +2 -0
  125. package/dist/tests/lint.test.d.ts.map +1 -0
  126. package/dist/tests/lint.test.js +18 -0
  127. package/dist/tests/lint.test.js.map +1 -0
  128. package/dist/tests/registry.test.d.ts +2 -0
  129. package/dist/tests/registry.test.d.ts.map +1 -0
  130. package/dist/tests/registry.test.js +85 -0
  131. package/dist/tests/registry.test.js.map +1 -0
  132. package/dist/tests/servicesAgainstRecordings.test.d.ts +20 -0
  133. package/dist/tests/servicesAgainstRecordings.test.d.ts.map +1 -0
  134. package/dist/tests/servicesAgainstRecordings.test.js +157 -0
  135. package/dist/tests/servicesAgainstRecordings.test.js.map +1 -0
  136. package/dist/tests/typecheck.test.d.ts +2 -0
  137. package/dist/tests/typecheck.test.d.ts.map +1 -0
  138. package/dist/tests/typecheck.test.js +18 -0
  139. package/dist/tests/typecheck.test.js.map +1 -0
  140. package/docs/development.md +94 -0
  141. package/eslint.config.js +30 -0
  142. package/integrations/SKILL.md +62 -0
  143. package/package.json +68 -0
  144. package/scripts/cryptFile.ts +123 -0
  145. package/scripts/recordBrowserSession.ts +280 -0
  146. package/scripts/tsconfig.json +10 -0
  147. package/src/apiCredentialStore.ts +87 -0
  148. package/src/apiCredentials.ts +180 -0
  149. package/src/cli.ts +32 -0
  150. package/src/cliCommands.ts +321 -0
  151. package/src/config.ts +115 -0
  152. package/src/curl.ts +78 -0
  153. package/src/encryptedStorage.ts +161 -0
  154. package/src/encryption.ts +106 -0
  155. package/src/index.ts +65 -0
  156. package/src/keychain.ts +105 -0
  157. package/src/playwrightUtils.ts +143 -0
  158. package/src/registry.ts +35 -0
  159. package/src/services/base.ts +234 -0
  160. package/src/services/discord.ts +73 -0
  161. package/src/services/dropbox.ts +173 -0
  162. package/src/services/github.ts +139 -0
  163. package/src/services/index.ts +13 -0
  164. package/src/services/linear.ts +134 -0
  165. package/src/services/slack.ts +85 -0
  166. package/tests/apiCredentialStore.test.ts +162 -0
  167. package/tests/apiCredentials.test.ts +195 -0
  168. package/tests/cli.test.ts +798 -0
  169. package/tests/encryptedStorage.test.ts +173 -0
  170. package/tests/encryption.test.ts +169 -0
  171. package/tests/lint.test.ts +19 -0
  172. package/tests/registry.test.ts +103 -0
  173. package/tests/servicesAgainstRecordings.test.ts +230 -0
  174. package/tests/typecheck.test.ts +19 -0
  175. package/tsconfig.json +24 -0
  176. package/vitest.config.ts +13 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Browser discovery and configuration management for Latchkey.
3
+ *
4
+ * This module handles:
5
+ * - Discovering system-installed Chrome/Chromium browsers
6
+ * - Finding Playwright's bundled Chromium
7
+ * - Downloading Chromium via Playwright when needed
8
+ * - Persisting the discovered browser path
9
+ */
10
+ import { z } from 'zod';
11
+ /**
12
+ * Schema for the browser configuration file.
13
+ */
14
+ declare const BrowserConfigSchema: z.ZodObject<{
15
+ executablePath: z.ZodString;
16
+ source: z.ZodEnum<["system", "playwright", "downloaded"]>;
17
+ discoveredAt: z.ZodString;
18
+ }, "strip", z.ZodTypeAny, {
19
+ executablePath: string;
20
+ source: "system" | "playwright" | "downloaded";
21
+ discoveredAt: string;
22
+ }, {
23
+ executablePath: string;
24
+ source: "system" | "playwright" | "downloaded";
25
+ discoveredAt: string;
26
+ }>;
27
+ export type BrowserConfig = z.infer<typeof BrowserConfigSchema>;
28
+ export declare class BrowserNotFoundError extends Error {
29
+ constructor(message: string);
30
+ }
31
+ export declare class BrowserConfigError extends Error {
32
+ constructor(message: string);
33
+ }
34
+ /**
35
+ * Get the default path for the browser configuration file.
36
+ */
37
+ export declare function getDefaultBrowserConfigPath(): string;
38
+ /**
39
+ * Find a system-installed Chrome/Chromium browser.
40
+ * Returns the path to the executable if found, null otherwise.
41
+ */
42
+ export declare function findSystemBrowser(): string | null;
43
+ /**
44
+ * Find Chrome/Chromium installed by Playwright.
45
+ * Returns the path to the executable if found, null otherwise.
46
+ */
47
+ export declare function findPlaywrightBrowser(): Promise<string | null>;
48
+ /**
49
+ * Download Chromium using Playwright's browser installation mechanism.
50
+ * Returns the path to the downloaded executable.
51
+ */
52
+ export declare function downloadPlaywrightBrowser(): Promise<string>;
53
+ /**
54
+ * Options for browser discovery.
55
+ */
56
+ export interface DiscoverBrowserOptions {
57
+ /** Skip discovery of system-installed browsers. Default: false */
58
+ skipSystemBrowser?: boolean;
59
+ }
60
+ /**
61
+ * Discover a Chrome/Chromium browser using the following priority:
62
+ * 1. System-installed browser (unless skipSystemBrowser is true)
63
+ * 2. Playwright's bundled browser
64
+ * 3. Download via Playwright
65
+ *
66
+ * Returns the browser configuration with the discovered path.
67
+ */
68
+ export declare function discoverBrowser(options?: DiscoverBrowserOptions): Promise<BrowserConfig>;
69
+ /**
70
+ * Save browser configuration to disk.
71
+ */
72
+ export declare function saveBrowserConfig(configPath: string, config: BrowserConfig): void;
73
+ /**
74
+ * Load browser configuration from disk.
75
+ * Returns null if the file doesn't exist or is invalid.
76
+ */
77
+ export declare function loadBrowserConfig(configPath: string): BrowserConfig | null;
78
+ /**
79
+ * Ensure a browser is available and return its configuration.
80
+ * If a valid configuration exists, it is returned.
81
+ * Otherwise, a new browser is discovered and the configuration is saved.
82
+ */
83
+ export declare function ensureBrowser(configPath?: string, options?: DiscoverBrowserOptions): Promise<BrowserConfig>;
84
+ /**
85
+ * Get the browser executable path from the stored configuration.
86
+ * Returns null if no valid configuration exists.
87
+ */
88
+ export declare function getBrowserExecutablePath(configPath?: string): string | null;
89
+ export {};
90
+ //# sourceMappingURL=browserConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserConfig.d.ts","sourceRoot":"","sources":["../../src/browserConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,QAAA,MAAM,mBAAmB;;;;;;;;;;;;EAIvB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAoED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CA2BjD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYpE;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,MAAM,CAAC,CAejE;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,aAAa,CAAC,CAiClG;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAQjF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmB1E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,GAAE,MAAsC,EAClD,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,aAAa,CAAC,CAWxB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,GAAE,MAAsC,GACjD,MAAM,GAAG,IAAI,CAGf"}
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Browser discovery and configuration management for Latchkey.
3
+ *
4
+ * This module handles:
5
+ * - Discovering system-installed Chrome/Chromium browsers
6
+ * - Finding Playwright's bundled Chromium
7
+ * - Downloading Chromium via Playwright when needed
8
+ * - Persisting the discovered browser path
9
+ */
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
11
+ import { homedir, platform } from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+ import { z } from 'zod';
14
+ import { LATCHKEY_SKIP_SYSTEM_BROWSER_ENV_VAR } from './config.js';
15
+ /**
16
+ * Schema for the browser configuration file.
17
+ */
18
+ const BrowserConfigSchema = z.object({
19
+ executablePath: z.string(),
20
+ source: z.enum(['system', 'playwright', 'downloaded']),
21
+ discoveredAt: z.string().datetime(),
22
+ });
23
+ export class BrowserNotFoundError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'BrowserNotFoundError';
27
+ }
28
+ }
29
+ export class BrowserConfigError extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = 'BrowserConfigError';
33
+ }
34
+ }
35
+ /**
36
+ * Get the default path for the browser configuration file.
37
+ */
38
+ export function getDefaultBrowserConfigPath() {
39
+ return join(homedir(), '.latchkey', 'browser.json');
40
+ }
41
+ /**
42
+ * System Chrome/Chromium installation paths by platform.
43
+ * These are the standard locations where browsers are installed.
44
+ */
45
+ const SYSTEM_CHROME_PATHS = {
46
+ darwin: [
47
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
48
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
49
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
50
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
51
+ ],
52
+ linux: [
53
+ '/opt/google/chrome/chrome',
54
+ '/opt/google/chrome-beta/chrome',
55
+ '/opt/google/chrome-unstable/chrome',
56
+ '/usr/bin/google-chrome',
57
+ '/usr/bin/google-chrome-stable',
58
+ '/usr/bin/chromium',
59
+ '/usr/bin/chromium-browser',
60
+ '/opt/microsoft/msedge/msedge',
61
+ ],
62
+ win32: [
63
+ // These are relative paths that will be joined with common Windows prefixes
64
+ '\\Google\\Chrome\\Application\\chrome.exe',
65
+ '\\Google\\Chrome Beta\\Application\\chrome.exe',
66
+ '\\Google\\Chrome SxS\\Application\\chrome.exe',
67
+ '\\Chromium\\Application\\chrome.exe',
68
+ '\\Microsoft\\Edge\\Application\\msedge.exe',
69
+ ],
70
+ };
71
+ /**
72
+ * Get Windows path prefixes from environment variables.
73
+ */
74
+ function getWindowsPrefixes() {
75
+ const prefixes = [];
76
+ const env = process.env;
77
+ if (env.LOCALAPPDATA) {
78
+ prefixes.push(env.LOCALAPPDATA);
79
+ }
80
+ if (env.PROGRAMFILES) {
81
+ prefixes.push(env.PROGRAMFILES);
82
+ }
83
+ if (env['PROGRAMFILES(X86)']) {
84
+ prefixes.push(env['PROGRAMFILES(X86)']);
85
+ }
86
+ if (env.HOMEDRIVE) {
87
+ prefixes.push(join(env.HOMEDRIVE, 'Program Files'));
88
+ prefixes.push(join(env.HOMEDRIVE, 'Program Files (x86)'));
89
+ }
90
+ return prefixes;
91
+ }
92
+ /**
93
+ * Check if a file exists and is accessible.
94
+ */
95
+ function canAccessFile(filePath) {
96
+ try {
97
+ return existsSync(filePath);
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Find a system-installed Chrome/Chromium browser.
105
+ * Returns the path to the executable if found, null otherwise.
106
+ */
107
+ export function findSystemBrowser() {
108
+ const currentPlatform = platform();
109
+ const paths = SYSTEM_CHROME_PATHS[currentPlatform];
110
+ if (!paths) {
111
+ return null;
112
+ }
113
+ if (currentPlatform === 'win32') {
114
+ const prefixes = getWindowsPrefixes();
115
+ for (const prefix of prefixes) {
116
+ for (const suffix of paths) {
117
+ const fullPath = join(prefix, suffix);
118
+ if (canAccessFile(fullPath)) {
119
+ return fullPath;
120
+ }
121
+ }
122
+ }
123
+ }
124
+ else {
125
+ for (const path of paths) {
126
+ if (canAccessFile(path)) {
127
+ return path;
128
+ }
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Find Chrome/Chromium installed by Playwright.
135
+ * Returns the path to the executable if found, null otherwise.
136
+ */
137
+ export async function findPlaywrightBrowser() {
138
+ try {
139
+ const { chromium } = await import('playwright');
140
+ const executablePath = chromium.executablePath();
141
+ if (executablePath && canAccessFile(executablePath)) {
142
+ return executablePath;
143
+ }
144
+ }
145
+ catch {
146
+ // Playwright not available or browser not installed
147
+ }
148
+ return null;
149
+ }
150
+ /**
151
+ * Download Chromium using Playwright's browser installation mechanism.
152
+ * Returns the path to the downloaded executable.
153
+ */
154
+ export async function downloadPlaywrightBrowser() {
155
+ const { installBrowsersForNpmInstall } = await import('playwright-core/lib/server/registry/index');
156
+ await installBrowsersForNpmInstall(['chromium']);
157
+ // After installation, get the path
158
+ const browserPath = await findPlaywrightBrowser();
159
+ if (!browserPath) {
160
+ throw new BrowserNotFoundError('Failed to locate Chromium after download. The installation may have failed.');
161
+ }
162
+ return browserPath;
163
+ }
164
+ /**
165
+ * Discover a Chrome/Chromium browser using the following priority:
166
+ * 1. System-installed browser (unless skipSystemBrowser is true)
167
+ * 2. Playwright's bundled browser
168
+ * 3. Download via Playwright
169
+ *
170
+ * Returns the browser configuration with the discovered path.
171
+ */
172
+ export async function discoverBrowser(options = {}) {
173
+ const skipSystem = options.skipSystemBrowser ?? process.env[LATCHKEY_SKIP_SYSTEM_BROWSER_ENV_VAR] === '1';
174
+ // 1. Try system browser first (unless skipped)
175
+ if (!skipSystem) {
176
+ const systemPath = findSystemBrowser();
177
+ if (systemPath) {
178
+ return {
179
+ executablePath: systemPath,
180
+ source: 'system',
181
+ discoveredAt: new Date().toISOString(),
182
+ };
183
+ }
184
+ }
185
+ // 2. Try Playwright's bundled browser
186
+ const playwrightPath = await findPlaywrightBrowser();
187
+ if (playwrightPath) {
188
+ return {
189
+ executablePath: playwrightPath,
190
+ source: 'playwright',
191
+ discoveredAt: new Date().toISOString(),
192
+ };
193
+ }
194
+ // 3. Download via Playwright
195
+ const downloadedPath = await downloadPlaywrightBrowser();
196
+ return {
197
+ executablePath: downloadedPath,
198
+ source: 'downloaded',
199
+ discoveredAt: new Date().toISOString(),
200
+ };
201
+ }
202
+ /**
203
+ * Save browser configuration to disk.
204
+ */
205
+ export function saveBrowserConfig(configPath, config) {
206
+ const directory = dirname(configPath);
207
+ if (!existsSync(directory)) {
208
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
209
+ }
210
+ const content = JSON.stringify(config, null, 2);
211
+ writeFileSync(configPath, content, { encoding: 'utf-8', mode: 0o600 });
212
+ }
213
+ /**
214
+ * Load browser configuration from disk.
215
+ * Returns null if the file doesn't exist or is invalid.
216
+ */
217
+ export function loadBrowserConfig(configPath) {
218
+ if (!existsSync(configPath)) {
219
+ return null;
220
+ }
221
+ try {
222
+ const content = readFileSync(configPath, 'utf-8');
223
+ const data = JSON.parse(content);
224
+ const config = BrowserConfigSchema.parse(data);
225
+ // Verify the browser still exists
226
+ if (!canAccessFile(config.executablePath)) {
227
+ return null;
228
+ }
229
+ return config;
230
+ }
231
+ catch {
232
+ return null;
233
+ }
234
+ }
235
+ /**
236
+ * Ensure a browser is available and return its configuration.
237
+ * If a valid configuration exists, it is returned.
238
+ * Otherwise, a new browser is discovered and the configuration is saved.
239
+ */
240
+ export async function ensureBrowser(configPath = getDefaultBrowserConfigPath(), options = {}) {
241
+ // Try to load existing configuration
242
+ const existingConfig = loadBrowserConfig(configPath);
243
+ if (existingConfig) {
244
+ return existingConfig;
245
+ }
246
+ // Discover and save new configuration
247
+ const config = await discoverBrowser(options);
248
+ saveBrowserConfig(configPath, config);
249
+ return config;
250
+ }
251
+ /**
252
+ * Get the browser executable path from the stored configuration.
253
+ * Returns null if no valid configuration exists.
254
+ */
255
+ export function getBrowserExecutablePath(configPath = getDefaultBrowserConfigPath()) {
256
+ const config = loadBrowserConfig(configPath);
257
+ return config?.executablePath ?? null;
258
+ }
259
+ //# sourceMappingURL=browserConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserConfig.js","sourceRoot":"","sources":["../../src/browserConfig.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Browser state management utilities.
3
+ */
4
+ /**
5
+ * Get the browser state path from the LATCHKEY_BROWSER_STATE environment variable.
6
+ */
7
+ export declare function getBrowserStatePath(): string | null;
8
+ //# sourceMappingURL=browserState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserState.d.ts","sourceRoot":"","sources":["../../src/browserState.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAUnD"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Browser state management utilities.
3
+ */
4
+ import { homedir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ const LATCHKEY_BROWSER_STATE_ENV_VAR = 'LATCHKEY_BROWSER_STATE';
7
+ /**
8
+ * Get the browser state path from the LATCHKEY_BROWSER_STATE environment variable.
9
+ */
10
+ export function getBrowserStatePath() {
11
+ const envValue = process.env[LATCHKEY_BROWSER_STATE_ENV_VAR];
12
+ if (envValue) {
13
+ // Expand ~ to home directory
14
+ if (envValue.startsWith('~')) {
15
+ return resolve(homedir(), envValue.slice(2));
16
+ }
17
+ return resolve(envValue);
18
+ }
19
+ return null;
20
+ }
21
+ //# sourceMappingURL=browserState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserState.js","sourceRoot":"","sources":["../../src/browserState.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,8BAA8B,GAAG,wBAAwB,CAAC;AAEhE;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC7D,IAAI,QAAQ,EAAE,CAAC;QACb,6BAA6B;QAC7B,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Command-line interface entry point for latchkey.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA;;GAEG"}
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Command-line interface entry point for latchkey.
4
+ */
5
+ import { program } from 'commander';
6
+ import { registerCommands, createDefaultDependencies } from './cliCommands.js';
7
+ import { InsecureFilePermissionsError } from './config.js';
8
+ const deps = createDefaultDependencies();
9
+ try {
10
+ deps.config.checkSensitiveFilePermissions();
11
+ }
12
+ catch (error) {
13
+ if (error instanceof InsecureFilePermissionsError) {
14
+ console.error(`Error: ${error.message}`);
15
+ process.exit(1);
16
+ }
17
+ throw error;
18
+ }
19
+ program
20
+ .name('latchkey')
21
+ .description('A command-line tool that injects API credentials to curl requests to known public APIs.')
22
+ .version('0.1.0');
23
+ registerCommands(program, deps);
24
+ program.parse();
25
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,IAAI,GAAG,yBAAyB,EAAE,CAAC;AAEzC,IAAI,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,6BAA6B,EAAE,CAAC;AAC9C,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,IAAI,KAAK,YAAY,4BAA4B,EAAE,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,CAAC;AACd,CAAC;AAED,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CACV,yFAAyF,CAC1F;KACA,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEhC,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * CLI command implementations with dependency injection for testability.
3
+ */
4
+ import type { Command } from 'commander';
5
+ import { Config } from './config.js';
6
+ import type { CurlResult } from './curl.js';
7
+ import { Registry } from './registry.js';
8
+ /**
9
+ * Dependencies that can be injected for testing.
10
+ */
11
+ export interface CliDependencies {
12
+ readonly registry: Registry;
13
+ readonly config: Config;
14
+ readonly runCurl: (args: readonly string[]) => CurlResult;
15
+ readonly confirm: (message: string) => Promise<boolean>;
16
+ readonly exit: (code: number) => never;
17
+ readonly log: (message: string) => void;
18
+ readonly errorLog: (message: string) => void;
19
+ }
20
+ /**
21
+ * Default implementation of CLI dependencies.
22
+ */
23
+ export declare function createDefaultDependencies(): CliDependencies;
24
+ export declare function extractUrlFromCurlArguments(args: string[]): string | null;
25
+ /**
26
+ * Register all CLI commands on the given program.
27
+ */
28
+ export declare function registerCommands(program: Command, deps: CliDependencies): void;
29
+ //# sourceMappingURL=cliCommands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cliCommands.d.ts","sourceRoot":"","sources":["../../src/cliCommands.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,OAAO,EAAE,MAAM,EAAU,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAY,MAAM,eAAe,CAAC;AAOnD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,UAAU,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,CAc3D;AAMD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAyBzE;AAsFD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,CAuJ9E"}