toolcraft 0.0.5 → 0.0.7

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 (149) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +77 -59
  4. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.d.ts +2 -0
  5. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +15 -0
  6. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.d.ts +2 -0
  7. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +13 -0
  8. package/node_modules/@poe-code/agent-defs/dist/agents/codex.d.ts +2 -0
  9. package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +14 -0
  10. package/node_modules/@poe-code/agent-defs/dist/agents/goose.d.ts +2 -0
  11. package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +14 -0
  12. package/node_modules/@poe-code/agent-defs/dist/agents/index.d.ts +7 -0
  13. package/node_modules/@poe-code/agent-defs/dist/agents/index.js +7 -0
  14. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.d.ts +2 -0
  15. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +15 -0
  16. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.d.ts +2 -0
  17. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +14 -0
  18. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.d.ts +2 -0
  19. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +13 -0
  20. package/node_modules/@poe-code/agent-defs/dist/index.d.ts +5 -0
  21. package/node_modules/@poe-code/agent-defs/dist/index.js +3 -0
  22. package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +3 -0
  23. package/node_modules/@poe-code/agent-defs/dist/registry.js +26 -0
  24. package/node_modules/@poe-code/agent-defs/dist/specifier.d.ts +7 -0
  25. package/node_modules/@poe-code/agent-defs/dist/specifier.js +27 -0
  26. package/node_modules/@poe-code/agent-defs/dist/types.d.ts +16 -0
  27. package/node_modules/@poe-code/agent-defs/dist/types.js +1 -0
  28. package/node_modules/@poe-code/agent-defs/package.json +20 -0
  29. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +5 -0
  30. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +552 -0
  31. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.d.ts +17 -0
  32. package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +58 -0
  33. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.d.ts +7 -0
  34. package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +46 -0
  35. package/node_modules/@poe-code/config-mutations/dist/formats/index.d.ts +13 -0
  36. package/node_modules/@poe-code/config-mutations/dist/formats/index.js +49 -0
  37. package/node_modules/@poe-code/config-mutations/dist/formats/json.d.ts +31 -0
  38. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +140 -0
  39. package/node_modules/@poe-code/config-mutations/dist/formats/toml.d.ts +2 -0
  40. package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +72 -0
  41. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.d.ts +2 -0
  42. package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +73 -0
  43. package/node_modules/@poe-code/config-mutations/dist/fs-utils.d.ts +18 -0
  44. package/node_modules/@poe-code/config-mutations/dist/fs-utils.js +45 -0
  45. package/node_modules/@poe-code/config-mutations/dist/index.d.ts +8 -0
  46. package/node_modules/@poe-code/config-mutations/dist/index.js +8 -0
  47. package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.d.ts +47 -0
  48. package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.js +34 -0
  49. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +52 -0
  50. package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +46 -0
  51. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +40 -0
  52. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.js +32 -0
  53. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +7 -0
  54. package/node_modules/@poe-code/config-mutations/dist/template/render.js +28 -0
  55. package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.d.ts +7 -0
  56. package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.js +21 -0
  57. package/node_modules/@poe-code/config-mutations/dist/testing/index.d.ts +3 -0
  58. package/node_modules/@poe-code/config-mutations/dist/testing/index.js +2 -0
  59. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.d.ts +25 -0
  60. package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +170 -0
  61. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +156 -0
  62. package/node_modules/@poe-code/config-mutations/dist/types.js +6 -0
  63. package/node_modules/@poe-code/config-mutations/package.json +33 -0
  64. package/node_modules/@poe-code/file-lock/README.md +52 -0
  65. package/node_modules/@poe-code/file-lock/dist/index.d.ts +1 -0
  66. package/node_modules/@poe-code/file-lock/dist/index.js +1 -0
  67. package/node_modules/@poe-code/file-lock/dist/lock.d.ts +27 -0
  68. package/node_modules/@poe-code/file-lock/dist/lock.js +203 -0
  69. package/node_modules/@poe-code/file-lock/package.json +23 -0
  70. package/node_modules/auth-store/README.md +47 -0
  71. package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
  72. package/node_modules/auth-store/dist/create-secret-store.js +35 -0
  73. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +39 -0
  74. package/node_modules/auth-store/dist/encrypted-file-store.js +156 -0
  75. package/node_modules/auth-store/dist/index.d.ts +7 -0
  76. package/node_modules/auth-store/dist/index.js +4 -0
  77. package/node_modules/auth-store/dist/keychain-store.d.ts +22 -0
  78. package/node_modules/auth-store/dist/keychain-store.js +111 -0
  79. package/node_modules/auth-store/dist/provider-store.d.ts +10 -0
  80. package/node_modules/auth-store/dist/provider-store.js +28 -0
  81. package/node_modules/auth-store/dist/types.d.ts +20 -0
  82. package/node_modules/auth-store/dist/types.js +1 -0
  83. package/node_modules/auth-store/package.json +25 -0
  84. package/node_modules/mcp-oauth/README.md +31 -0
  85. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +14 -0
  86. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +97 -0
  87. package/node_modules/mcp-oauth/dist/client/authorization-state.d.ts +8 -0
  88. package/node_modules/mcp-oauth/dist/client/authorization-state.js +34 -0
  89. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.d.ts +3 -0
  90. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +491 -0
  91. package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +20 -0
  92. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +169 -0
  93. package/node_modules/mcp-oauth/dist/client/pkce.d.ts +2 -0
  94. package/node_modules/mcp-oauth/dist/client/pkce.js +7 -0
  95. package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +40 -0
  96. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +143 -0
  97. package/node_modules/mcp-oauth/dist/client/types.d.ts +113 -0
  98. package/node_modules/mcp-oauth/dist/client/types.js +1 -0
  99. package/node_modules/mcp-oauth/dist/index.d.ts +10 -0
  100. package/node_modules/mcp-oauth/dist/index.js +7 -0
  101. package/node_modules/mcp-oauth/dist/resource-indicator.d.ts +1 -0
  102. package/node_modules/mcp-oauth/dist/resource-indicator.js +11 -0
  103. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +27 -0
  104. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +259 -0
  105. package/node_modules/mcp-oauth/dist/types.compile-check.d.ts +1 -0
  106. package/node_modules/mcp-oauth/dist/types.compile-check.js +22 -0
  107. package/node_modules/mcp-oauth/package.json +31 -0
  108. package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +4 -0
  109. package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
  110. package/node_modules/tiny-mcp-client/dist/index.js +1 -0
  111. package/node_modules/tiny-mcp-client/dist/internal.d.ts +547 -0
  112. package/node_modules/tiny-mcp-client/dist/internal.js +2404 -0
  113. package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.d.ts +1 -0
  114. package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.js +37 -0
  115. package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.d.ts +1 -0
  116. package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.js +50 -0
  117. package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.d.ts +1 -0
  118. package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.js +50 -0
  119. package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.d.ts +1 -0
  120. package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.js +51 -0
  121. package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.d.ts +1 -0
  122. package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +89 -0
  123. package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.d.ts +1 -0
  124. package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.js +56 -0
  125. package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.d.ts +1 -0
  126. package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.js +145 -0
  127. package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +24 -0
  128. package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +385 -0
  129. package/node_modules/tiny-mcp-client/package.json +22 -0
  130. package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +823 -0
  131. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +882 -0
  132. package/node_modules/tiny-mcp-client/src/index.ts +94 -0
  133. package/node_modules/tiny-mcp-client/src/internal.ts +3566 -0
  134. package/node_modules/tiny-mcp-client/src/jsonrpc-types.compile-check.ts +66 -0
  135. package/node_modules/tiny-mcp-client/src/mcp-client-http-transport.integration.test.ts +222 -0
  136. package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +1294 -0
  137. package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +143 -0
  138. package/node_modules/tiny-mcp-client/src/mcp-lifecycle-types.compile-check.ts +65 -0
  139. package/node_modules/tiny-mcp-client/src/mcp-prompt-types.compile-check.ts +66 -0
  140. package/node_modules/tiny-mcp-client/src/mcp-resource-types.compile-check.ts +70 -0
  141. package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +117 -0
  142. package/node_modules/tiny-mcp-client/src/mcp-transport-types.compile-check.ts +75 -0
  143. package/node_modules/tiny-mcp-client/src/mcp-utility-types.compile-check.ts +181 -0
  144. package/node_modules/tiny-mcp-client/src/mock-servers.test.ts +980 -0
  145. package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +583 -0
  146. package/node_modules/tiny-mcp-client/src/transports.test.ts +8139 -0
  147. package/node_modules/tiny-mcp-client/src/utilities.test.ts +372 -0
  148. package/node_modules/tiny-mcp-client/tsconfig.json +11 -0
  149. package/package.json +24 -11
@@ -0,0 +1,203 @@
1
+ import * as fsPromises from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ export class LockTimeoutError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "LockTimeoutError";
7
+ }
8
+ }
9
+ function createAbortError() {
10
+ const error = new Error("The operation was aborted.");
11
+ error.name = "AbortError";
12
+ return error;
13
+ }
14
+ function throwIfAborted(signal) {
15
+ if (signal?.aborted) {
16
+ throw createAbortError();
17
+ }
18
+ }
19
+ function sleep(ms, signal) {
20
+ if (!signal) {
21
+ return new Promise((resolve) => setTimeout(resolve, ms));
22
+ }
23
+ if (signal.aborted) {
24
+ return Promise.reject(createAbortError());
25
+ }
26
+ return new Promise((resolve, reject) => {
27
+ const timeoutId = setTimeout(() => {
28
+ signal.removeEventListener("abort", onAbort);
29
+ resolve();
30
+ }, ms);
31
+ const onAbort = () => {
32
+ clearTimeout(timeoutId);
33
+ signal.removeEventListener("abort", onAbort);
34
+ reject(createAbortError());
35
+ };
36
+ signal.addEventListener("abort", onAbort, { once: true });
37
+ });
38
+ }
39
+ function backoff(attempt, minTimeout, maxTimeout) {
40
+ const delay = Math.min(maxTimeout, minTimeout * 2 ** attempt);
41
+ return delay + Math.random() * delay * 0.1;
42
+ }
43
+ function hasErrorCode(error, code) {
44
+ return (!!error &&
45
+ typeof error === "object" &&
46
+ "code" in error &&
47
+ error.code === code);
48
+ }
49
+ function hasAnyErrorCode(error, codes) {
50
+ return codes.some((code) => hasErrorCode(error, code));
51
+ }
52
+ function isPidRunning(pid) {
53
+ try {
54
+ process.kill(pid, 0);
55
+ return true;
56
+ }
57
+ catch (error) {
58
+ return !hasErrorCode(error, "ESRCH");
59
+ }
60
+ }
61
+ function createDefaultFs() {
62
+ return {
63
+ open: (path, flags) => fsPromises.open(path, flags),
64
+ readFile: (path, encoding) => fsPromises.readFile(path, encoding),
65
+ stat: fsPromises.stat,
66
+ unlink: fsPromises.unlink
67
+ };
68
+ }
69
+ async function removeLockFile(fs, lockPath, signal) {
70
+ for (let attempt = 0; attempt <= 4; attempt += 1) {
71
+ throwIfAborted(signal);
72
+ try {
73
+ await fs.unlink(lockPath);
74
+ return;
75
+ }
76
+ catch (error) {
77
+ if (hasErrorCode(error, "ENOENT")) {
78
+ return;
79
+ }
80
+ if (!hasAnyErrorCode(error, ["EPERM", "EBUSY"]) || attempt === 4) {
81
+ throw error;
82
+ }
83
+ }
84
+ await sleep(25 * 2 ** attempt, signal);
85
+ }
86
+ }
87
+ function parseLockMetadata(content) {
88
+ try {
89
+ const parsed = JSON.parse(content);
90
+ if (!parsed || typeof parsed !== "object" || !("host" in parsed) || !("pid" in parsed)) {
91
+ return undefined;
92
+ }
93
+ const { host, pid } = parsed;
94
+ if (typeof host === "string" && typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0) {
95
+ return {
96
+ host,
97
+ pid
98
+ };
99
+ }
100
+ }
101
+ catch (ignoredError) {
102
+ void ignoredError;
103
+ }
104
+ return undefined;
105
+ }
106
+ async function readLockMetadata(fs, lockPath) {
107
+ if (!fs.readFile) {
108
+ return undefined;
109
+ }
110
+ try {
111
+ return parseLockMetadata(await fs.readFile(lockPath, "utf8"));
112
+ }
113
+ catch (error) {
114
+ if (hasErrorCode(error, "ENOENT")) {
115
+ return null;
116
+ }
117
+ return undefined;
118
+ }
119
+ }
120
+ async function shouldReclaimLock(options) {
121
+ const metadata = await readLockMetadata(options.fs, options.lockPath);
122
+ if (metadata === null) {
123
+ return "missing";
124
+ }
125
+ if (metadata?.host === os.hostname()) {
126
+ return !options.isPidRunning(metadata.pid);
127
+ }
128
+ return Date.now() - options.stat.mtimeMs > options.staleMs;
129
+ }
130
+ async function writeLockMetadata(handle) {
131
+ try {
132
+ await handle.writeFile(JSON.stringify({ pid: process.pid, host: os.hostname(), acquiredAt: new Date().toISOString() }), { encoding: "utf8" });
133
+ }
134
+ catch (ignoredError) {
135
+ void ignoredError;
136
+ }
137
+ try {
138
+ await handle.close();
139
+ }
140
+ catch (ignoredError) {
141
+ void ignoredError;
142
+ }
143
+ }
144
+ export async function acquireFileLock(filePath, options = {}) {
145
+ const fs = options.fs ?? createDefaultFs();
146
+ const retries = options.retries ?? 20;
147
+ const minTimeout = options.minTimeout ?? 25;
148
+ const maxTimeout = options.maxTimeout ?? 250;
149
+ const staleMs = options.staleMs ?? 1_000;
150
+ const pidIsRunning = options.isPidRunning ?? isPidRunning;
151
+ const lockPath = `${filePath}.lock`;
152
+ let attempt = 0;
153
+ while (attempt <= retries) {
154
+ throwIfAborted(options.signal);
155
+ try {
156
+ const handle = await fs.open(lockPath, "wx");
157
+ await writeLockMetadata(handle);
158
+ let released = false;
159
+ return async () => {
160
+ if (released) {
161
+ return;
162
+ }
163
+ released = true;
164
+ await removeLockFile(fs, lockPath, options.signal);
165
+ };
166
+ }
167
+ catch (error) {
168
+ if (!hasErrorCode(error, "EEXIST")) {
169
+ throw error;
170
+ }
171
+ }
172
+ let stat;
173
+ try {
174
+ stat = await fs.stat(lockPath);
175
+ }
176
+ catch (statError) {
177
+ if (hasErrorCode(statError, "ENOENT")) {
178
+ continue;
179
+ }
180
+ throw statError;
181
+ }
182
+ const reclaimLock = await shouldReclaimLock({
183
+ fs,
184
+ isPidRunning: pidIsRunning,
185
+ lockPath,
186
+ staleMs,
187
+ stat
188
+ });
189
+ if (reclaimLock === "missing") {
190
+ continue;
191
+ }
192
+ if (reclaimLock) {
193
+ await removeLockFile(fs, lockPath, options.signal);
194
+ continue;
195
+ }
196
+ if (attempt >= retries) {
197
+ break;
198
+ }
199
+ await sleep(backoff(attempt, minTimeout, maxTimeout), options.signal);
200
+ attempt += 1;
201
+ }
202
+ throw new LockTimeoutError(`Failed to acquire lock on "${filePath}".`);
203
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@poe-code/file-lock",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "cd ../.. && vitest run packages/file-lock/src",
17
+ "test:unit": "cd ../.. && vitest run packages/file-lock/src"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "dependencies": {}
23
+ }
@@ -0,0 +1,47 @@
1
+ # auth-store
2
+
3
+ Generic encrypted secret storage with platform-aware backends.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { createSecretStore } from "auth-store";
9
+
10
+ const { store, backend } = createSecretStore({
11
+ backendEnvVar: "MY_AUTH_BACKEND",
12
+ fileStore: {
13
+ salt: "my-app:encrypted-store:v1",
14
+ defaultDirectory: ".my-app",
15
+ defaultFileName: "credentials.enc"
16
+ },
17
+ keychainStore: {
18
+ service: "my-app",
19
+ account: "api-key"
20
+ }
21
+ });
22
+
23
+ await store.set("secret-value");
24
+ const value = await store.get(); // "secret-value"
25
+ await store.delete();
26
+ ```
27
+
28
+ ## Backends
29
+
30
+ | `backendEnvVar` value | Platform | Backend |
31
+ | --------------------- | -------- | -------------- |
32
+ | _(unset)_ | any | Encrypted file |
33
+ | `file` | any | Encrypted file |
34
+ | `keychain` | macOS | macOS Keychain |
35
+ | `keychain` | other | Error |
36
+
37
+ ### Encrypted file
38
+
39
+ - AES-256-GCM with machine-derived key (hostname + username via scrypt)
40
+ - Configurable salt, directory, and file name
41
+ - File permissions: `0600`
42
+ - Random IV per write
43
+
44
+ ### macOS Keychain
45
+
46
+ - Uses the `security` CLI (`add-generic-password`, `find-generic-password`, `delete-generic-password`)
47
+ - Configurable service and account names
@@ -0,0 +1,2 @@
1
+ import type { CreateSecretStoreInput, CreateSecretStoreResult } from "./types.js";
2
+ export declare function createSecretStore(input: CreateSecretStoreInput): CreateSecretStoreResult;
@@ -0,0 +1,35 @@
1
+ import { EncryptedFileStore } from "./encrypted-file-store.js";
2
+ import { KeychainStore } from "./keychain-store.js";
3
+ const DEFAULT_BACKEND_ENV_VAR = "AUTH_BACKEND";
4
+ const MACOS_PLATFORM = "darwin";
5
+ const storeFactories = {
6
+ file: (input) => {
7
+ if (!input.fileStore) {
8
+ throw new Error("fileStore configuration is required for file backend");
9
+ }
10
+ return new EncryptedFileStore(input.fileStore);
11
+ },
12
+ keychain: (input) => {
13
+ if (!input.keychainStore) {
14
+ throw new Error("keychainStore configuration is required for keychain backend");
15
+ }
16
+ return new KeychainStore(input.keychainStore);
17
+ }
18
+ };
19
+ export function createSecretStore(input) {
20
+ const backend = resolveBackend(input);
21
+ const platform = input.platform ?? process.platform;
22
+ if (backend === "keychain" && platform !== MACOS_PLATFORM) {
23
+ throw new Error(`Keychain backend is only supported on macOS. Current platform: ${platform}`);
24
+ }
25
+ const store = storeFactories[backend](input);
26
+ return { backend, store };
27
+ }
28
+ function resolveBackend(input) {
29
+ const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
30
+ const configuredBackend = input.backend ?? input.env?.[envVar] ?? process.env[envVar];
31
+ if (configuredBackend === "keychain") {
32
+ return "keychain";
33
+ }
34
+ return "file";
35
+ }
@@ -0,0 +1,39 @@
1
+ import type { SecretStore } from "./types.js";
2
+ export interface MachineIdentity {
3
+ hostname: string;
4
+ username: string;
5
+ }
6
+ export interface EncryptedFileStoreFileSystem {
7
+ readFile(path: string, encoding: BufferEncoding): Promise<string>;
8
+ writeFile(path: string, data: string | NodeJS.ArrayBufferView, options?: {
9
+ encoding?: BufferEncoding;
10
+ }): Promise<void>;
11
+ mkdir(path: string, options?: {
12
+ recursive?: boolean;
13
+ }): Promise<void | string | undefined>;
14
+ unlink(path: string): Promise<void>;
15
+ chmod(path: string, mode: number): Promise<void>;
16
+ }
17
+ export interface EncryptedFileStoreInput {
18
+ fs?: EncryptedFileStoreFileSystem;
19
+ filePath?: string;
20
+ salt: string;
21
+ defaultDirectory?: string;
22
+ defaultFileName?: string;
23
+ getMachineIdentity?: () => MachineIdentity | Promise<MachineIdentity>;
24
+ getHomeDirectory?: () => string;
25
+ getRandomBytes?: (size: number) => Buffer;
26
+ }
27
+ export declare class EncryptedFileStore implements SecretStore {
28
+ private readonly fs;
29
+ private readonly filePath;
30
+ private readonly salt;
31
+ private readonly getMachineIdentity;
32
+ private readonly getRandomBytes;
33
+ private keyPromise;
34
+ constructor(input: EncryptedFileStoreInput);
35
+ get(): Promise<string | null>;
36
+ set(value: string): Promise<void>;
37
+ delete(): Promise<void>;
38
+ private getEncryptionKey;
39
+ }
@@ -0,0 +1,156 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, scrypt } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import { homedir, hostname, userInfo } from "node:os";
4
+ import path from "node:path";
5
+ const derivedKeyCache = new Map();
6
+ const ENCRYPTION_ALGORITHM = "aes-256-gcm";
7
+ const ENCRYPTION_VERSION = 1;
8
+ const ENCRYPTION_KEY_BYTES = 32;
9
+ const ENCRYPTION_IV_BYTES = 12;
10
+ const ENCRYPTION_AUTH_TAG_BYTES = 16;
11
+ const ENCRYPTION_FILE_MODE = 0o600;
12
+ export class EncryptedFileStore {
13
+ fs;
14
+ filePath;
15
+ salt;
16
+ getMachineIdentity;
17
+ getRandomBytes;
18
+ keyPromise = null;
19
+ constructor(input) {
20
+ this.fs = input.fs ?? fs;
21
+ this.salt = input.salt;
22
+ this.filePath = input.filePath ?? path.join((input.getHomeDirectory ?? homedir)(), input.defaultDirectory ?? ".auth-store", input.defaultFileName ?? "credentials.enc");
23
+ this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
24
+ this.getRandomBytes = input.getRandomBytes ?? randomBytes;
25
+ }
26
+ async get() {
27
+ let rawDocument;
28
+ try {
29
+ rawDocument = await this.fs.readFile(this.filePath, "utf8");
30
+ }
31
+ catch (error) {
32
+ if (isNotFoundError(error)) {
33
+ return null;
34
+ }
35
+ throw error;
36
+ }
37
+ const document = parseEncryptedDocument(rawDocument);
38
+ if (!document) {
39
+ return null;
40
+ }
41
+ const key = await this.getEncryptionKey();
42
+ try {
43
+ const iv = Buffer.from(document.iv, "base64");
44
+ const authTag = Buffer.from(document.authTag, "base64");
45
+ const ciphertext = Buffer.from(document.ciphertext, "base64");
46
+ if (iv.byteLength !== ENCRYPTION_IV_BYTES ||
47
+ authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
48
+ return null;
49
+ }
50
+ const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
51
+ decipher.setAuthTag(authTag);
52
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
53
+ return plaintext.toString("utf8");
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ async set(value) {
60
+ const key = await this.getEncryptionKey();
61
+ const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
62
+ const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);
63
+ const ciphertext = Buffer.concat([
64
+ cipher.update(value, "utf8"),
65
+ cipher.final()
66
+ ]);
67
+ const authTag = cipher.getAuthTag();
68
+ const document = {
69
+ version: ENCRYPTION_VERSION,
70
+ iv: iv.toString("base64"),
71
+ authTag: authTag.toString("base64"),
72
+ ciphertext: ciphertext.toString("base64")
73
+ };
74
+ await this.fs.mkdir(path.dirname(this.filePath), { recursive: true });
75
+ await this.fs.writeFile(this.filePath, JSON.stringify(document), {
76
+ encoding: "utf8"
77
+ });
78
+ await this.fs.chmod(this.filePath, ENCRYPTION_FILE_MODE);
79
+ }
80
+ async delete() {
81
+ try {
82
+ await this.fs.unlink(this.filePath);
83
+ }
84
+ catch (error) {
85
+ if (!isNotFoundError(error)) {
86
+ throw error;
87
+ }
88
+ }
89
+ }
90
+ getEncryptionKey() {
91
+ if (!this.keyPromise) {
92
+ this.keyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt);
93
+ }
94
+ return this.keyPromise;
95
+ }
96
+ }
97
+ function defaultMachineIdentity() {
98
+ return {
99
+ hostname: hostname(),
100
+ username: userInfo().username
101
+ };
102
+ }
103
+ async function deriveEncryptionKey(getMachineIdentity, salt) {
104
+ const machineIdentity = await getMachineIdentity();
105
+ const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
106
+ const cacheKey = `${secret}:${salt}`;
107
+ const cached = derivedKeyCache.get(cacheKey);
108
+ if (cached) {
109
+ return cached;
110
+ }
111
+ const keyPromise = new Promise((resolve, reject) => {
112
+ scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
113
+ if (error) {
114
+ reject(error);
115
+ return;
116
+ }
117
+ resolve(Buffer.from(derivedKey));
118
+ });
119
+ });
120
+ derivedKeyCache.set(cacheKey, keyPromise);
121
+ return keyPromise;
122
+ }
123
+ function parseEncryptedDocument(raw) {
124
+ try {
125
+ const parsed = JSON.parse(raw);
126
+ if (!isRecord(parsed)) {
127
+ return null;
128
+ }
129
+ if (parsed.version !== ENCRYPTION_VERSION) {
130
+ return null;
131
+ }
132
+ if (typeof parsed.iv !== "string" ||
133
+ typeof parsed.authTag !== "string" ||
134
+ typeof parsed.ciphertext !== "string") {
135
+ return null;
136
+ }
137
+ return {
138
+ version: parsed.version,
139
+ iv: parsed.iv,
140
+ authTag: parsed.authTag,
141
+ ciphertext: parsed.ciphertext
142
+ };
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ function isRecord(value) {
149
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
150
+ }
151
+ function isNotFoundError(error) {
152
+ return Boolean(error &&
153
+ typeof error === "object" &&
154
+ "code" in error &&
155
+ error.code === "ENOENT");
156
+ }
@@ -0,0 +1,7 @@
1
+ export { createSecretStore } from "./create-secret-store.js";
2
+ export { EncryptedFileStore } from "./encrypted-file-store.js";
3
+ export { KeychainStore } from "./keychain-store.js";
4
+ export { key, MigratingSecretStore } from "./provider-store.js";
5
+ export type { SecretStore, StoreBackend, CreateSecretStoreInput, CreateSecretStoreResult } from "./types.js";
6
+ export type { MachineIdentity, EncryptedFileStoreInput, EncryptedFileStoreFileSystem } from "./encrypted-file-store.js";
7
+ export type { KeychainStoreInput, KeychainCommandRunner, KeychainCommandResult } from "./keychain-store.js";
@@ -0,0 +1,4 @@
1
+ export { createSecretStore } from "./create-secret-store.js";
2
+ export { EncryptedFileStore } from "./encrypted-file-store.js";
3
+ export { KeychainStore } from "./keychain-store.js";
4
+ export { key, MigratingSecretStore } from "./provider-store.js";
@@ -0,0 +1,22 @@
1
+ import type { SecretStore } from "./types.js";
2
+ export interface KeychainCommandResult {
3
+ stdout: string;
4
+ stderr: string;
5
+ exitCode: number;
6
+ }
7
+ export type KeychainCommandRunner = (command: string, args: string[]) => Promise<KeychainCommandResult>;
8
+ export interface KeychainStoreInput {
9
+ runCommand?: KeychainCommandRunner;
10
+ service: string;
11
+ account: string;
12
+ }
13
+ export declare class KeychainStore implements SecretStore {
14
+ private readonly runCommand;
15
+ private readonly service;
16
+ private readonly account;
17
+ constructor(input: KeychainStoreInput);
18
+ get(): Promise<string | null>;
19
+ set(value: string): Promise<void>;
20
+ delete(): Promise<void>;
21
+ private executeSecurityCommand;
22
+ }
@@ -0,0 +1,111 @@
1
+ import { spawn } from "node:child_process";
2
+ const SECURITY_CLI = "security";
3
+ const KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
4
+ export class KeychainStore {
5
+ runCommand;
6
+ service;
7
+ account;
8
+ constructor(input) {
9
+ this.runCommand = input.runCommand ?? runSecurityCommand;
10
+ this.service = input.service;
11
+ this.account = input.account;
12
+ }
13
+ async get() {
14
+ const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
15
+ if (result.exitCode === 0) {
16
+ return stripTrailingLineBreak(result.stdout);
17
+ }
18
+ if (isKeychainEntryNotFound(result)) {
19
+ return null;
20
+ }
21
+ throw createSecurityCliFailure("read secret from macOS Keychain", result);
22
+ }
23
+ async set(value) {
24
+ const result = await this.executeSecurityCommand([
25
+ "add-generic-password",
26
+ "-s",
27
+ this.service,
28
+ "-a",
29
+ this.account,
30
+ "-w",
31
+ value,
32
+ "-U"
33
+ ], "store secret in macOS Keychain");
34
+ if (result.exitCode !== 0) {
35
+ throw createSecurityCliFailure("store secret in macOS Keychain", result);
36
+ }
37
+ }
38
+ async delete() {
39
+ const result = await this.executeSecurityCommand(["delete-generic-password", "-s", this.service, "-a", this.account], "delete secret from macOS Keychain");
40
+ if (result.exitCode === 0 || isKeychainEntryNotFound(result)) {
41
+ return;
42
+ }
43
+ throw createSecurityCliFailure("delete secret from macOS Keychain", result);
44
+ }
45
+ async executeSecurityCommand(args, operation) {
46
+ try {
47
+ return await this.runCommand(SECURITY_CLI, args);
48
+ }
49
+ catch (error) {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ throw new Error(`Failed to ${operation}: ${message}`);
52
+ }
53
+ }
54
+ }
55
+ function runSecurityCommand(command, args) {
56
+ return new Promise((resolve) => {
57
+ const child = spawn(command, args, {
58
+ stdio: ["ignore", "pipe", "pipe"]
59
+ });
60
+ let stdout = "";
61
+ let stderr = "";
62
+ child.stdout?.setEncoding("utf8");
63
+ child.stdout?.on("data", (chunk) => {
64
+ stdout += chunk.toString();
65
+ });
66
+ child.stderr?.setEncoding("utf8");
67
+ child.stderr?.on("data", (chunk) => {
68
+ stderr += chunk.toString();
69
+ });
70
+ child.on("error", (error) => {
71
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
72
+ resolve({
73
+ stdout,
74
+ stderr: stderr ? `${stderr}${message}` : message,
75
+ exitCode: 127
76
+ });
77
+ });
78
+ child.on("close", (code) => {
79
+ resolve({
80
+ stdout,
81
+ stderr,
82
+ exitCode: code ?? 0
83
+ });
84
+ });
85
+ });
86
+ }
87
+ function stripTrailingLineBreak(value) {
88
+ if (value.endsWith("\r\n")) {
89
+ return value.slice(0, -2);
90
+ }
91
+ if (value.endsWith("\n") || value.endsWith("\r")) {
92
+ return value.slice(0, -1);
93
+ }
94
+ return value;
95
+ }
96
+ function isKeychainEntryNotFound(result) {
97
+ if (result.exitCode === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE) {
98
+ return true;
99
+ }
100
+ const output = `${result.stderr}\n${result.stdout}`.toLowerCase();
101
+ return (output.includes("could not be found") ||
102
+ output.includes("item not found") ||
103
+ output.includes("errsecitemnotfound"));
104
+ }
105
+ function createSecurityCliFailure(operation, result) {
106
+ const details = result.stderr.trim() || result.stdout.trim();
107
+ if (details) {
108
+ return new Error(`Failed to ${operation}: security exited with code ${result.exitCode}: ${details}`);
109
+ }
110
+ return new Error(`Failed to ${operation}: security exited with code ${result.exitCode}`);
111
+ }
@@ -0,0 +1,10 @@
1
+ import type { SecretStore } from "./types.js";
2
+ export declare function key(providerId: string): string;
3
+ export declare class MigratingSecretStore implements SecretStore {
4
+ private readonly store;
5
+ private readonly legacyStore;
6
+ constructor(store: SecretStore, legacyStore?: SecretStore | null);
7
+ get(): Promise<string | null>;
8
+ set(value: string): Promise<void>;
9
+ delete(): Promise<void>;
10
+ }