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
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24.13.0
@@ -0,0 +1,22 @@
1
+ default_install_hook_types: [pre-commit]
2
+
3
+ repos:
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v6.0.0
6
+ hooks:
7
+ - id: trailing-whitespace
8
+ - id: end-of-file-fixer
9
+ - id: check-added-large-files
10
+ exclude: 'uv.lock'
11
+ - id: check-yaml
12
+ - repo: https://github.com/astral-sh/uv-pre-commit
13
+ rev: 0.7.22
14
+ hooks:
15
+ - id: uv-lock
16
+ - repo: local
17
+ hooks:
18
+ - id: ruff
19
+ name: "Python formatter + import sorter (ruff)"
20
+ entry: bash -c 'uv run ruff check --select UP006,UP007,I,F401 --fix --force-exclude --config pyproject.toml "$@" && uv run ruff format --force-exclude --config pyproject.toml "$@"' --
21
+ language: system
22
+ types: [python]
@@ -0,0 +1,4 @@
1
+ dist
2
+ node_modules
3
+ coverage
4
+ *.md
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "trailingComma": "es5",
5
+ "tabWidth": 2,
6
+ "printWidth": 100
7
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,13 @@
1
+ For information about the high-level goal and motivations, see README.md.
2
+
3
+ You are a highly intelligent and experienced software creator that codes in a straightforward way, prefers simplicity and avoids overengineering.
4
+
5
+ Typescript style guide:
6
+
7
+ - Use modern Typescript code.
8
+ - Prefer functional, stateless logic as much as possible.
9
+ - Use immutable data structures as much as possible.
10
+ - Do not use abbreviations in variable (class, function, ...) names. It's fine for names to be somewhat verbose.
11
+ - Omit docstrings if they don't add any value beyond what can be obviously inferred from the function signature / class name.
12
+ - Do not throw builtin errors; always replace them with dedicated error subclasses.
13
+ - When done, validate your changes by running `npm lint` and `npm test`.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2026 Imbue (Generally Intelligent, Inc.)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # Latchkey
2
+
3
+ Turn browser logins into usable credentials for local agents.
4
+
5
+ ## Quick example
6
+
7
+ ```
8
+ latchkey curl -X POST 'https://slack.com/api/conversations.create' \
9
+ -H 'Content-Type: application/json' \
10
+ -d '{"name":"something-urgent"}'
11
+ ```
12
+
13
+ ## Overview
14
+
15
+ Latchkey is a command-line tool that injects credentials to curl requests to known public APIs.
16
+
17
+ - `latchkey services`
18
+ - Get a list of known and supported third-party services (Slack, Discord, Linear, GitHub, etc.).
19
+ - `latchkey curl <arguments>`
20
+ - Automatically inject credentials to your otherwise standard curl calls to public APIs.
21
+ - (The first time you access a service, a browser pop-up with a login screen appears.)
22
+
23
+ Latchkey is primarily designed for AI agents. By invoking Latchkey, agents can
24
+ prompt the user to authenticate when needed, then continue interacting with
25
+ third-party APIs using standard curl syntax - no custom integrations or embedded
26
+ credentials required.
27
+
28
+ Unlike OAuth-based flows or typical MCP-style integrations, Latchkey does not
29
+ introduce an intermediary between the agent and the service. Requests are made
30
+ directly on the user’s behalf, which enables greater flexibility at the cost of
31
+ formal delegation: agents authenticate as the user.
32
+
33
+ If a service you need isn’t supported yet, contributions are welcome. Adding
34
+ support typically involves writing a small browser automation class that
35
+ extracts API credentials after login. See the [development docs](docs/development.md)
36
+ for details.
37
+
38
+ ## Installation
39
+
40
+ ### Prerequisites
41
+
42
+ - `curl`, `node` and `npm` need to be present on your system in reasonably recent versions.
43
+ - The browser requires a graphical environment.
44
+
45
+ ### Steps
46
+
47
+ ```
48
+ npm install -g latchkey
49
+ ```
50
+
51
+ **nvm users**: Global packages are per node version. If you switch versions, reinstall with `npm install -g latchkey`
52
+
53
+ ## Integrations
54
+
55
+ Warning: giving AI agents access to your API credentials is potentially
56
+ dangerous. They will be able to perform most of the actions you can. Only do this if
57
+ you're willing to accept the risks.
58
+
59
+
60
+ ### OpenCode
61
+ ```
62
+ mkdir -p ~/.opencode/skills/latchkey
63
+ cp integrations/SKILL.md ~/.opencode/skills/latchkey/SKILL.md
64
+ ```
65
+
66
+ ### Claude Code
67
+ ```
68
+ mkdir -p ~/.claude/skills/latchkey
69
+ cp integrations/SKILL.md ~/.claude/skills/latchkey/SKILL.md
70
+ ```
71
+
72
+ ### Codex
73
+ ```
74
+ mkdir -p ~/.codex/skills/latchkey
75
+ cp integrations/SKILL.md ~/.codex/skills/latchkey/SKILL.md
76
+ ```
77
+
78
+
79
+ ## Direct usage
80
+
81
+ Let's revisit the initial example:
82
+
83
+ ```
84
+ latchkey curl -X POST 'https://slack.com/api/conversations.create' \
85
+ -H 'Content-Type: application/json' \
86
+ -d '{"name":"something-urgent"}'
87
+ ```
88
+
89
+ Notice that `-H 'Authorization: Bearer ...'` is absent. This is because Latchkey:
90
+
91
+ - Opens the browser with a login screen.
92
+ - After the user logs in, Latchkey extracts the necessary API credentials from the browser session.
93
+ - The browser is closed, the credentials are injected into the arguments, and `curl` is invoked.
94
+ - The credentials are stored so that they can be reused the next time.
95
+
96
+ Otherwise, `latchkey curl` passes your arguments straight
97
+ through to `curl` so you can use the same interface you are used
98
+ to. The return code, stdin and stdout are passed back from curl
99
+ to the caller of `latchkey`.
100
+
101
+ ### Remembering API credentials
102
+
103
+ Your API credentials and browser state are stored by default
104
+ under `~/.latchkey`. When a functioning keyring is detected
105
+ (which is the case on most systems), the data is properly
106
+ encrypted.
107
+
108
+
109
+ ### Inspecting the status of stored credentials
110
+
111
+ Calling `latchkey status <service_name>` will give you
112
+ information about the status of remembered credentials for the
113
+ given service. Possible results are:
114
+
115
+ - `missing`
116
+ - `invalid`
117
+ - `valid`
118
+
119
+ ### Clearing credentials
120
+
121
+ Remembered API credentials can expire. The caller of `latchkey
122
+ curl` will typically notice this because the calls will start returning
123
+ HTTP 401 or 403. To verify that, first call `latchkey status`, e.g.:
124
+
125
+ ```
126
+ latchkey status discord
127
+ ```
128
+
129
+ If the result is `invalid` , meaning the Unauthorized/Forbidden responses are
130
+ caused by invalid or expired credentials rather than insufficient permissions,
131
+ force a new login in the next `latchkey curl` call by clearing the remembered
132
+ API credentials for the service in question, e.g.:
133
+
134
+ ```
135
+ latchkey clear discord
136
+ ```
137
+
138
+ The next `latchkey curl` call will then trigger a new login flow.
139
+
140
+ To clear all stored data (both the credentials store and browser
141
+ state file), run:
142
+
143
+ ```
144
+ latchkey clear
145
+ ```
146
+
147
+ ### Advanced configuration
148
+
149
+ You can set these environment variables to override certain
150
+ defaults:
151
+
152
+ - `LATCHKEY_STORE`: path to the (typically encrypted) file
153
+ containing stored API credentials
154
+ - `LATCHKEY_BROWSER_STATE`: path to the (typically encrypted) file
155
+ containing the state (cookies, local storage, etc.) of
156
+ the browser used for the login popup
157
+ - `LATCHKEY_CURL_PATH`: path to the curl binary
158
+ - `LATCHKEY_KEYRING_SERVICE_NAME`, `LATCHKEY_KEYRING_ACCOUNT_NAME`: identifiers that are used to store the encryption password in your keyring
159
+
160
+
161
+ ## Disclaimers
162
+
163
+ - Invoking `latchkey curl ...` can sometimes have side effects in the form of
164
+ new API keys being created on your accounts (through browser automation).
165
+ - Using agents for automated access may be prohibited by some services' ToS.
166
+ - We reserve the right to change the license of future releases of Latchkey.
167
+ - Latchkey was not tested on Windows.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * CLI tool for encrypting and decrypting latchkey files.
4
+ *
5
+ * This is a developer utility for inspecting and modifying encrypted
6
+ * credential and browser state files.
7
+ *
8
+ * Usage:
9
+ * npx tsx scripts/cryptFile.ts decrypt <file> # Decrypt file in place
10
+ * npx tsx scripts/cryptFile.ts encrypt <file> # Encrypt file in place
11
+ *
12
+ * The encryption key is sourced from:
13
+ * 1. LATCHKEY_ENCRYPTION_KEY environment variable
14
+ * 2. System keychain
15
+ *
16
+ * Examples:
17
+ * npx tsx scripts/cryptFile.ts decrypt ~/.latchkey/credentials.json
18
+ * npx tsx scripts/cryptFile.ts encrypt ~/.latchkey/credentials.json
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=cryptFile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptFile.d.ts","sourceRoot":"","sources":["../../scripts/cryptFile.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * CLI tool for encrypting and decrypting latchkey files.
4
+ *
5
+ * This is a developer utility for inspecting and modifying encrypted
6
+ * credential and browser state files.
7
+ *
8
+ * Usage:
9
+ * npx tsx scripts/cryptFile.ts decrypt <file> # Decrypt file in place
10
+ * npx tsx scripts/cryptFile.ts encrypt <file> # Encrypt file in place
11
+ *
12
+ * The encryption key is sourced from:
13
+ * 1. LATCHKEY_ENCRYPTION_KEY environment variable
14
+ * 2. System keychain
15
+ *
16
+ * Examples:
17
+ * npx tsx scripts/cryptFile.ts decrypt ~/.latchkey/credentials.json
18
+ * npx tsx scripts/cryptFile.ts encrypt ~/.latchkey/credentials.json
19
+ */
20
+ import { program } from 'commander';
21
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
22
+ import { CONFIG } from '../src/config.js';
23
+ import { EncryptedStorage } from '../src/encryptedStorage.js';
24
+ import { encrypt, generateKey } from '../src/encryption.js';
25
+ import { isKeychainAvailable, retrieveFromKeychain } from '../src/keychain.js';
26
+ const ENCRYPTED_FILE_PREFIX = 'LATCHKEY_ENCRYPTED:';
27
+ function getEncryptionKey() {
28
+ // 1. Check environment variable via Config
29
+ if (CONFIG.encryptionKeyOverride) {
30
+ return CONFIG.encryptionKeyOverride;
31
+ }
32
+ // 2. Check keychain
33
+ if (isKeychainAvailable(CONFIG.serviceName, CONFIG.accountName)) {
34
+ const keychainKey = retrieveFromKeychain(CONFIG.serviceName, CONFIG.accountName);
35
+ if (keychainKey) {
36
+ return keychainKey;
37
+ }
38
+ }
39
+ console.error(`\
40
+ Error: No encryption key available.
41
+ Set LATCHKEY_ENCRYPTION_KEY or ensure the system keychain has a stored key.
42
+
43
+ To generate a new key:
44
+ export LATCHKEY_ENCRYPTION_KEY="${generateKey()}"`);
45
+ process.exit(1);
46
+ }
47
+ function decryptCommand(filePath) {
48
+ if (!existsSync(filePath)) {
49
+ console.error(`Error: File not found: ${filePath}`);
50
+ process.exit(1);
51
+ }
52
+ const rawContent = readFileSync(filePath, 'utf-8');
53
+ if (!rawContent.startsWith(ENCRYPTED_FILE_PREFIX)) {
54
+ console.error(`Error: File is not encrypted: ${filePath}`);
55
+ process.exit(1);
56
+ }
57
+ const storage = new EncryptedStorage({
58
+ serviceName: CONFIG.serviceName,
59
+ accountName: CONFIG.accountName,
60
+ });
61
+ const content = storage.readFile(filePath);
62
+ if (content === null) {
63
+ console.error(`Error: Could not read file: ${filePath}`);
64
+ process.exit(1);
65
+ }
66
+ writeFileSync(filePath, content, { encoding: 'utf-8', mode: 0o600 });
67
+ console.error(`Decrypted: ${filePath}`);
68
+ }
69
+ function encryptCommand(filePath) {
70
+ if (!existsSync(filePath)) {
71
+ console.error(`Error: File not found: ${filePath}`);
72
+ process.exit(1);
73
+ }
74
+ const content = readFileSync(filePath, 'utf-8');
75
+ if (content.startsWith(ENCRYPTED_FILE_PREFIX)) {
76
+ console.error(`Error: File is already encrypted: ${filePath}`);
77
+ process.exit(1);
78
+ }
79
+ const key = getEncryptionKey();
80
+ const encryptedData = encrypt(content, key);
81
+ const dataToWrite = ENCRYPTED_FILE_PREFIX + encryptedData;
82
+ writeFileSync(filePath, dataToWrite, { encoding: 'utf-8', mode: 0o600 });
83
+ console.error(`Encrypted: ${filePath}`);
84
+ }
85
+ program.name('cryptFile').description(`\
86
+ CLI tool for encrypting and decrypting latchkey files.
87
+
88
+ The encryption key is sourced from:
89
+ 1. LATCHKEY_ENCRYPTION_KEY environment variable
90
+ 2. System keychain`);
91
+ program
92
+ .command('decrypt')
93
+ .description('Decrypt file in place')
94
+ .argument('<file>', 'Path to the encrypted file')
95
+ .action((filePath) => {
96
+ decryptCommand(filePath);
97
+ });
98
+ program
99
+ .command('encrypt')
100
+ .description('Encrypt an unencrypted file in place')
101
+ .argument('<file>', 'Path to the file to encrypt')
102
+ .action((filePath) => {
103
+ encryptCommand(filePath);
104
+ });
105
+ program.parse();
106
+ //# sourceMappingURL=cryptFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cryptFile.js","sourceRoot":"","sources":["../../scripts/cryptFile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * CLI tool for encrypting and decrypting latchkey files.
4
+ *
5
+ * This is a developer utility for inspecting and modifying encrypted
6
+ * credential and browser state files.
7
+ *
8
+ * Usage:
9
+ * npx tsx scripts/encryptFile.ts decrypt <file> # Decrypt file to stdout
10
+ * npx tsx scripts/encryptFile.ts encrypt <file> # Encrypt file in place
11
+ *
12
+ * The encryption key is sourced from:
13
+ * 1. LATCHKEY_ENCRYPTION_KEY environment variable
14
+ * 2. System keychain
15
+ *
16
+ * Examples:
17
+ * npx tsx scripts/encryptFile.ts decrypt ~/.latchkey/credentials.json
18
+ * npx tsx scripts/encryptFile.ts encrypt ~/.latchkey/credentials.json
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=encryptFile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryptFile.d.ts","sourceRoot":"","sources":["../../scripts/encryptFile.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * CLI tool for encrypting and decrypting latchkey files.
4
+ *
5
+ * This is a developer utility for inspecting and modifying encrypted
6
+ * credential and browser state files.
7
+ *
8
+ * Usage:
9
+ * npx tsx scripts/encryptFile.ts decrypt <file> # Decrypt file to stdout
10
+ * npx tsx scripts/encryptFile.ts encrypt <file> # Encrypt file in place
11
+ *
12
+ * The encryption key is sourced from:
13
+ * 1. LATCHKEY_ENCRYPTION_KEY environment variable
14
+ * 2. System keychain
15
+ *
16
+ * Examples:
17
+ * npx tsx scripts/encryptFile.ts decrypt ~/.latchkey/credentials.json
18
+ * npx tsx scripts/encryptFile.ts encrypt ~/.latchkey/credentials.json
19
+ */
20
+ import { program } from 'commander';
21
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
22
+ import { CONFIG } from '../src/config.js';
23
+ import { EncryptedStorage } from '../src/encryptedStorage.js';
24
+ import { encrypt, generateKey } from '../src/encryption.js';
25
+ import { isKeychainAvailable, retrieveFromKeychain } from '../src/keychain.js';
26
+ const ENCRYPTED_FILE_PREFIX = 'LATCHKEY_ENCRYPTED:';
27
+ function getEncryptionKey() {
28
+ // 1. Check environment variable via Config
29
+ if (CONFIG.encryptionKeyOverride) {
30
+ return CONFIG.encryptionKeyOverride;
31
+ }
32
+ // 2. Check keychain
33
+ if (isKeychainAvailable(CONFIG.serviceName, CONFIG.accountName)) {
34
+ const keychainKey = retrieveFromKeychain(CONFIG.serviceName, CONFIG.accountName);
35
+ if (keychainKey) {
36
+ return keychainKey;
37
+ }
38
+ }
39
+ console.error(`\
40
+ Error: No encryption key available.
41
+ Set LATCHKEY_ENCRYPTION_KEY or ensure the system keychain has a stored key.
42
+
43
+ To generate a new key:
44
+ export LATCHKEY_ENCRYPTION_KEY="${generateKey()}"`);
45
+ process.exit(1);
46
+ }
47
+ function decryptCommand(filePath) {
48
+ if (!existsSync(filePath)) {
49
+ console.error(`Error: File not found: ${filePath}`);
50
+ process.exit(1);
51
+ }
52
+ const storage = new EncryptedStorage({
53
+ serviceName: CONFIG.serviceName,
54
+ accountName: CONFIG.accountName,
55
+ });
56
+ const content = storage.readFile(filePath);
57
+ if (content === null) {
58
+ console.error(`Error: Could not read file: ${filePath}`);
59
+ process.exit(1);
60
+ }
61
+ // Output to stdout
62
+ process.stdout.write(content);
63
+ }
64
+ function encryptCommand(filePath) {
65
+ if (!existsSync(filePath)) {
66
+ console.error(`Error: File not found: ${filePath}`);
67
+ process.exit(1);
68
+ }
69
+ const content = readFileSync(filePath, 'utf-8');
70
+ if (content.startsWith(ENCRYPTED_FILE_PREFIX)) {
71
+ console.error(`Error: File is already encrypted: ${filePath}`);
72
+ process.exit(1);
73
+ }
74
+ const key = getEncryptionKey();
75
+ const encryptedData = encrypt(content, key);
76
+ const dataToWrite = ENCRYPTED_FILE_PREFIX + encryptedData;
77
+ writeFileSync(filePath, dataToWrite, { encoding: 'utf-8', mode: 0o600 });
78
+ console.error(`Encrypted: ${filePath}`);
79
+ }
80
+ program.name('encryptFile').description(`\
81
+ CLI tool for encrypting and decrypting latchkey files.
82
+
83
+ The encryption key is sourced from:
84
+ 1. LATCHKEY_ENCRYPTION_KEY environment variable
85
+ 2. System keychain`);
86
+ program
87
+ .command('decrypt')
88
+ .description('Decrypt file and print to stdout')
89
+ .argument('<file>', 'Path to the encrypted file')
90
+ .action((filePath) => {
91
+ decryptCommand(filePath);
92
+ });
93
+ program
94
+ .command('encrypt')
95
+ .description('Encrypt an unencrypted file in place')
96
+ .argument('<file>', 'Path to the file to encrypt')
97
+ .action((filePath) => {
98
+ encryptCommand(filePath);
99
+ });
100
+ program.parse();
101
+ //# sourceMappingURL=encryptFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryptFile.js","sourceRoot":"","sources":["../../scripts/encryptFile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * Record browser requests and responses during a login session.
4
+ *
5
+ * This script opens a browser at a service's login URL and records all HTTP
6
+ * requests and responses (including their headers and timing). When you close the
7
+ * browser, the recording is saved. This is useful for recording login flows that
8
+ * can be replayed later for testing credentials extraction.
9
+ *
10
+ * Usage:
11
+ * npx tsx scripts/recordBrowserSession.ts <service_name> [recording_name]
12
+ *
13
+ * Examples:
14
+ * npx tsx scripts/recordBrowserSession.ts slack
15
+ * npx tsx scripts/recordBrowserSession.ts discord custom_session.json
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=recordBrowserSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordBrowserSession.d.ts","sourceRoot":"","sources":["../../scripts/recordBrowserSession.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}