@executor-js/plugin-keychain 0.0.1 → 0.0.2
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.
- package/README.md +11 -11
- package/dist/{chunk-XGNRIWKJ.js → chunk-5MER5KRB.js} +60 -23
- package/dist/chunk-5MER5KRB.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/promise.d.ts +3 -3
- package/dist/provider.d.ts +1 -1
- package/package.json +5 -5
- package/dist/chunk-XGNRIWKJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @executor/plugin-keychain
|
|
1
|
+
# @executor-js/plugin-keychain
|
|
2
2
|
|
|
3
3
|
OS-keychain-backed secret store for the executor. Reads and writes secrets to:
|
|
4
4
|
|
|
@@ -11,19 +11,19 @@ Secrets are encrypted at rest by the operating system and never touch your proje
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
bun add @executor/sdk @executor/plugin-keychain
|
|
14
|
+
bun add @executor-js/sdk @executor-js/plugin-keychain
|
|
15
15
|
# or
|
|
16
|
-
npm install @executor/sdk @executor/plugin-keychain
|
|
16
|
+
npm install @executor-js/sdk @executor-js/plugin-keychain
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { createExecutor } from "@executor/sdk";
|
|
23
|
-
import { keychainPlugin } from "@executor/plugin-keychain";
|
|
22
|
+
import { createExecutor } from "@executor-js/sdk";
|
|
23
|
+
import { keychainPlugin } from "@executor-js/plugin-keychain";
|
|
24
24
|
|
|
25
25
|
const executor = await createExecutor({
|
|
26
|
-
|
|
26
|
+
onElicitation: "accept-all",
|
|
27
27
|
plugins: [keychainPlugin()] as const,
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -33,21 +33,21 @@ if (executor.keychain.isSupported) {
|
|
|
33
33
|
id: "github-token",
|
|
34
34
|
name: "GitHub Token",
|
|
35
35
|
value: "ghp_...",
|
|
36
|
-
|
|
36
|
+
scope: executor.scopes[0]!.id,
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
const value = await executor.secrets.
|
|
39
|
+
const value = await executor.secrets.get("github-token");
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Secrets written through this plugin are available to every other plugin that resolves secrets by ID — so you can store a token once and use it across `@executor/plugin-openapi`, `@executor/plugin-graphql`, etc. via `{ secretId, prefix }` headers.
|
|
43
|
+
Secrets written through this plugin are available to every other plugin that resolves secrets by ID — so you can store a token once and use it across `@executor-js/plugin-openapi`, `@executor-js/plugin-graphql`, etc. via `{ secretId, prefix }` headers.
|
|
44
44
|
|
|
45
45
|
## Using with Effect
|
|
46
46
|
|
|
47
|
-
If you're building on `@executor/sdk` (the raw Effect entry), import this plugin from its `/core` subpath instead:
|
|
47
|
+
If you're building on `@executor-js/sdk/core` (the raw Effect entry), import this plugin from its `/core` subpath instead — it returns the Effect-shaped plugin with `Effect.Effect<...>`-returning methods rather than promisified wrappers:
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
|
-
import { keychainPlugin } from "@executor/plugin-keychain";
|
|
50
|
+
import { keychainPlugin } from "@executor-js/plugin-keychain/core";
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## Status
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Effect as Effect3 } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
definePlugin
|
|
5
|
+
} from "@executor-js/sdk/core";
|
|
4
6
|
|
|
5
7
|
// src/keyring.ts
|
|
8
|
+
import { createRequire } from "module";
|
|
6
9
|
import { Effect } from "effect";
|
|
7
10
|
|
|
8
11
|
// src/errors.ts
|
|
@@ -17,12 +20,21 @@ var isSupportedPlatform = () => process.platform === "darwin" || process.platfor
|
|
|
17
20
|
var displayName = () => process.platform === "darwin" ? "macOS Keychain" : process.platform === "win32" ? "Windows Credential Manager" : "Desktop Keyring";
|
|
18
21
|
var resolveServiceName = (explicit) => explicit?.trim() || process.env[SERVICE_NAME_ENV]?.trim() || DEFAULT_SERVICE_NAME;
|
|
19
22
|
var entryCtorPromise = null;
|
|
23
|
+
var loadEntryCtor = async () => {
|
|
24
|
+
const directPath = process.env.EXECUTOR_KEYRING_NATIVE_PATH;
|
|
25
|
+
if (directPath) {
|
|
26
|
+
const req = createRequire(import.meta.url);
|
|
27
|
+
return req(directPath).Entry;
|
|
28
|
+
}
|
|
29
|
+
const { Entry } = await import("@napi-rs/keyring");
|
|
30
|
+
return Entry;
|
|
31
|
+
};
|
|
20
32
|
var loadEntry = () => Effect.tryPromise({
|
|
21
33
|
try: async () => {
|
|
22
34
|
if (!isSupportedPlatform()) {
|
|
23
35
|
throw new Error(`unsupported platform '${process.platform}'`);
|
|
24
36
|
}
|
|
25
|
-
entryCtorPromise ??=
|
|
37
|
+
entryCtorPromise ??= loadEntryCtor();
|
|
26
38
|
return await entryCtorPromise;
|
|
27
39
|
},
|
|
28
40
|
catch: (cause) => new KeychainError({
|
|
@@ -70,35 +82,60 @@ var deletePassword = (serviceName, account) => Effect.flatMap(
|
|
|
70
82
|
|
|
71
83
|
// src/provider.ts
|
|
72
84
|
import { Effect as Effect2 } from "effect";
|
|
85
|
+
import { StorageError } from "@executor-js/sdk/core";
|
|
86
|
+
var toStorageError = (cause) => new StorageError({ message: cause.message, cause: cause.cause ?? cause });
|
|
73
87
|
var makeKeychainProvider = (serviceName) => ({
|
|
74
88
|
key: "keychain",
|
|
75
89
|
writable: true,
|
|
76
|
-
get: (secretId) => getPassword(serviceName, secretId).pipe(Effect2.
|
|
77
|
-
set: (secretId, value) => setPassword(serviceName, secretId, value).pipe(Effect2.
|
|
78
|
-
delete: (secretId) => deletePassword(serviceName, secretId).pipe(Effect2.
|
|
90
|
+
get: (secretId, _scope) => getPassword(serviceName, secretId).pipe(Effect2.mapError(toStorageError)),
|
|
91
|
+
set: (secretId, value, _scope) => setPassword(serviceName, secretId, value).pipe(Effect2.mapError(toStorageError)),
|
|
92
|
+
delete: (secretId, _scope) => deletePassword(serviceName, secretId).pipe(Effect2.mapError(toStorageError)),
|
|
79
93
|
// Keychain doesn't support enumerating — you need to know the account name
|
|
80
94
|
list: void 0
|
|
81
95
|
});
|
|
82
96
|
|
|
83
97
|
// src/index.ts
|
|
84
|
-
var
|
|
85
|
-
var
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
var PROBE_ACCOUNT = "__executor_keychain_probe__";
|
|
99
|
+
var PROBE_VALUE = "probe";
|
|
100
|
+
var scopedServiceName = (ctx, options) => `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0].id}`;
|
|
101
|
+
var keychainPlugin = definePlugin(
|
|
102
|
+
(options) => ({
|
|
103
|
+
id: "keychain",
|
|
104
|
+
storage: () => ({}),
|
|
105
|
+
extension: (ctx) => {
|
|
106
|
+
const serviceName = scopedServiceName(ctx, options);
|
|
107
|
+
return {
|
|
108
|
+
displayName: displayName(),
|
|
109
|
+
isSupported: isSupportedPlatform(),
|
|
110
|
+
has: (id) => getPassword(serviceName, id).pipe(
|
|
111
|
+
Effect3.map((v) => v !== null),
|
|
112
|
+
Effect3.orElseSucceed(() => false)
|
|
113
|
+
)
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
secretProviders: (ctx) => Effect3.gen(function* () {
|
|
117
|
+
const serviceName = scopedServiceName(ctx, options);
|
|
118
|
+
const reachable = yield* setPassword(
|
|
119
|
+
serviceName,
|
|
120
|
+
PROBE_ACCOUNT,
|
|
121
|
+
PROBE_VALUE
|
|
122
|
+
).pipe(
|
|
123
|
+
Effect3.andThen(
|
|
124
|
+
deletePassword(serviceName, PROBE_ACCOUNT).pipe(
|
|
125
|
+
Effect3.catch(() => Effect3.void)
|
|
126
|
+
)
|
|
127
|
+
),
|
|
128
|
+
Effect3.as(true),
|
|
129
|
+
Effect3.catch(
|
|
130
|
+
(cause) => Effect3.logWarning(
|
|
131
|
+
`keychain unavailable, skipping provider registration: ${cause.message}`
|
|
132
|
+
).pipe(Effect3.as(false))
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
return reachable ? [makeKeychainProvider(serviceName)] : [];
|
|
136
|
+
})
|
|
100
137
|
})
|
|
101
|
-
|
|
138
|
+
);
|
|
102
139
|
|
|
103
140
|
export {
|
|
104
141
|
KeychainError,
|
|
@@ -107,4 +144,4 @@ export {
|
|
|
107
144
|
makeKeychainProvider,
|
|
108
145
|
keychainPlugin
|
|
109
146
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
147
|
+
//# sourceMappingURL=chunk-5MER5KRB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/keyring.ts","../src/errors.ts","../src/provider.ts"],"sourcesContent":["import { Effect } from \"effect\";\n\nimport {\n definePlugin,\n type PluginCtx,\n type SecretProvider,\n} from \"@executor-js/sdk/core\";\n\nimport {\n deletePassword,\n displayName,\n getPassword,\n isSupportedPlatform,\n resolveServiceName,\n setPassword,\n} from \"./keyring\";\nimport { makeKeychainProvider } from \"./provider\";\n\n// Probe the keychain by writing and then deleting a sentinel entry. A\n// read-only probe isn't enough — on some Linux environments (WSL2,\n// headless CI) `getPassword` for a missing key returns null without\n// error, but `setPassword` fails because the secret-service backend\n// isn't actually reachable. Writing is the capability the executor\n// cares about, so test it directly.\nconst PROBE_ACCOUNT = \"__executor_keychain_probe__\";\nconst PROBE_VALUE = \"probe\";\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport { KeychainError } from \"./errors\";\nexport { makeKeychainProvider } from \"./provider\";\nexport { isSupportedPlatform, displayName } from \"./keyring\";\n\n// ---------------------------------------------------------------------------\n// Plugin config\n// ---------------------------------------------------------------------------\n\nexport interface KeychainPluginConfig {\n /** Override the keychain service name (default: \"executor\") */\n readonly serviceName?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin extension — public API on executor.keychain\n// ---------------------------------------------------------------------------\n\nexport interface KeychainExtension {\n /** Human-readable name for the keychain on this platform */\n readonly displayName: string;\n\n /** Whether the current platform supports system keychain */\n readonly isSupported: boolean;\n\n /** Check if a secret exists in the system keychain */\n readonly has: (id: string) => Effect.Effect<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n// ---------------------------------------------------------------------------\n\n// Scope the keychain service name to the current executor scope so each\n// folder / workspace gets its own set of keychain entries. Computed\n// identically in `extension` and `secretProviders` — both receive ctx and\n// both are called once per createExecutor, so the derivation stays pure.\nconst scopedServiceName = (\n ctx: PluginCtx<unknown>,\n options: KeychainPluginConfig | undefined,\n): string =>\n `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id as string}`;\n\nexport const keychainPlugin = definePlugin<\n \"keychain\",\n KeychainExtension,\n {},\n undefined,\n KeychainPluginConfig\n>(\n (options?: KeychainPluginConfig) => ({\n id: \"keychain\" as const,\n storage: () => ({}),\n\n extension: (ctx): KeychainExtension => {\n const serviceName = scopedServiceName(ctx, options);\n return {\n displayName: displayName(),\n isSupported: isSupportedPlatform(),\n has: (id) =>\n getPassword(serviceName, id).pipe(\n Effect.map((v) => v !== null),\n Effect.orElseSucceed(() => false),\n ),\n };\n },\n\n secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> =>\n Effect.gen(function* () {\n const serviceName = scopedServiceName(ctx, options);\n const reachable = yield* setPassword(\n serviceName,\n PROBE_ACCOUNT,\n PROBE_VALUE,\n ).pipe(\n Effect.andThen(\n deletePassword(serviceName, PROBE_ACCOUNT).pipe(\n Effect.catch(() => Effect.void),\n ),\n ),\n Effect.as(true),\n Effect.catch((cause) =>\n Effect.logWarning(\n `keychain unavailable, skipping provider registration: ${cause.message}`,\n ).pipe(Effect.as(false)),\n ),\n );\n return reachable ? [makeKeychainProvider(serviceName)] : [];\n }),\n }),\n);\n","import { createRequire } from \"node:module\";\n\nimport { Effect } from \"effect\";\n\nimport { KeychainError } from \"./errors\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SERVICE_NAME = \"executor\";\nconst SERVICE_NAME_ENV = \"EXECUTOR_KEYCHAIN_SERVICE_NAME\";\n\n// ---------------------------------------------------------------------------\n// Platform helpers\n// ---------------------------------------------------------------------------\n\nexport const isSupportedPlatform = () =>\n process.platform === \"darwin\" || process.platform === \"linux\" || process.platform === \"win32\";\n\nexport const displayName = () =>\n process.platform === \"darwin\"\n ? \"macOS Keychain\"\n : process.platform === \"win32\"\n ? \"Windows Credential Manager\"\n : \"Desktop Keyring\";\n\nexport const resolveServiceName = (explicit?: string): string =>\n explicit?.trim() || process.env[SERVICE_NAME_ENV]?.trim() || DEFAULT_SERVICE_NAME;\n\n// ---------------------------------------------------------------------------\n// Lazy-load @napi-rs/keyring (native module)\n// ---------------------------------------------------------------------------\n\ntype EntryConstructor = (typeof import(\"@napi-rs/keyring\"))[\"Entry\"];\n\nlet entryCtorPromise: Promise<EntryConstructor> | null = null;\n\n// In compiled bun binaries (`bun build --compile`) `.node` modules aren't\n// included in bunfs and there's no node_modules at runtime, so\n// @napi-rs/keyring's loader can't find its platform-specific binding.\n// `apps/cli/src/build.ts` copies the .node next to the executor and\n// `apps/cli/src/main.ts` exports its absolute path here. We load it\n// directly because @napi-rs/keyring@1.2.0's NAPI_RS_NATIVE_LIBRARY_PATH\n// branch is buggy (assigns to a local that gets overwritten before return).\nconst loadEntryCtor = async (): Promise<EntryConstructor> => {\n const directPath = process.env.EXECUTOR_KEYRING_NATIVE_PATH;\n if (directPath) {\n const req = createRequire(import.meta.url);\n return (req(directPath) as { Entry: EntryConstructor }).Entry;\n }\n const { Entry } = await import(\"@napi-rs/keyring\");\n return Entry;\n};\n\nconst loadEntry = (): Effect.Effect<EntryConstructor, KeychainError> =>\n Effect.tryPromise({\n try: async () => {\n if (!isSupportedPlatform()) {\n throw new Error(`unsupported platform '${process.platform}'`);\n }\n entryCtorPromise ??= loadEntryCtor();\n return await entryCtorPromise;\n },\n catch: (cause) =>\n new KeychainError({\n message: `Failed loading native keyring: ${cause instanceof Error ? cause.message : String(cause)}`,\n cause,\n }),\n });\n\nconst createEntry = (serviceName: string, account: string) =>\n Effect.flatMap(loadEntry(), (Entry) =>\n Effect.try({\n try: () => new Entry(serviceName, account),\n catch: (cause) =>\n new KeychainError({\n message: `Failed creating keyring entry: ${cause instanceof Error ? cause.message : String(cause)}`,\n cause,\n }),\n }),\n );\n\n// ---------------------------------------------------------------------------\n// Low-level keychain operations\n// ---------------------------------------------------------------------------\n\nexport const getPassword = (\n serviceName: string,\n account: string,\n): Effect.Effect<string | null, KeychainError> =>\n Effect.flatMap(createEntry(serviceName, account), (entry) =>\n Effect.try({\n try: () => entry.getPassword(),\n catch: () => new KeychainError({ message: `Failed reading secret for account '${account}'` }),\n }),\n );\n\nexport const setPassword = (\n serviceName: string,\n account: string,\n value: string,\n): Effect.Effect<void, KeychainError> =>\n Effect.flatMap(createEntry(serviceName, account), (entry) =>\n Effect.try({\n try: () => entry.setPassword(value),\n catch: (cause) =>\n new KeychainError({\n message: `Failed writing secret: ${cause instanceof Error ? cause.message : String(cause)}`,\n cause,\n }),\n }).pipe(Effect.asVoid),\n );\n\nexport const deletePassword = (\n serviceName: string,\n account: string,\n): Effect.Effect<boolean, KeychainError> =>\n Effect.flatMap(createEntry(serviceName, account), (entry) =>\n Effect.try({\n try: () => {\n entry.deletePassword();\n return true;\n },\n catch: () =>\n new KeychainError({ message: `Failed deleting secret for account '${account}'` }),\n }),\n );\n","import { Data } from \"effect\";\n\nexport class KeychainError extends Data.TaggedError(\"KeychainError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n","import { Effect } from \"effect\";\n\nimport { StorageError, type SecretProvider } from \"@executor-js/sdk/core\";\n\nimport { getPassword, setPassword, deletePassword } from \"./keyring\";\n\n// ---------------------------------------------------------------------------\n// SecretProvider adapter — bridges keyring into SDK resolution chain\n//\n// The underlying `@napi-rs/keyring` sync API encodes \"no entry\" as an\n// ordinary return value (`getPassword()` → `null`, `deletePassword()` →\n// `false`), and only throws on real failures (keychain locked, permission\n// denied, platform init failure, etc.). `keyring.ts` wraps those thrown\n// failures as `KeychainError`. We translate `KeychainError` →\n// `StorageError` so the HTTP edge can capture it to telemetry and surface\n// an opaque `InternalError({ traceId })` — previously `orElseSucceed`\n// silently converted every failure into \"nothing found\", which made it\n// impossible to debug why secrets weren't resolving.\n// ---------------------------------------------------------------------------\n\nconst toStorageError = (cause: { readonly message: string; readonly cause?: unknown }) =>\n new StorageError({ message: cause.message, cause: cause.cause ?? cause });\n\n// Scope arg is ignored — keychain partitions by `serviceName`, which the\n// host fixes per executor at construction time. A future refactor could\n// fold `scope` into the service name, but today a keychain provider\n// instance is already one-scope.\nexport const makeKeychainProvider = (serviceName: string): SecretProvider => ({\n key: \"keychain\",\n writable: true,\n get: (secretId, _scope) =>\n getPassword(serviceName, secretId).pipe(Effect.mapError(toStorageError)),\n set: (secretId, value, _scope) =>\n setPassword(serviceName, secretId, value).pipe(Effect.mapError(toStorageError)),\n delete: (secretId, _scope) =>\n deletePassword(serviceName, secretId).pipe(Effect.mapError(toStorageError)),\n // Keychain doesn't support enumerating — you need to know the account name\n list: undefined,\n});\n"],"mappings":";AAAA,SAAS,UAAAA,eAAc;AAEvB;AAAA,EACE;AAAA,OAGK;;;ACNP,SAAS,qBAAqB;AAE9B,SAAS,cAAc;;;ACFvB,SAAS,YAAY;AAEd,IAAM,gBAAN,cAA4B,KAAK,YAAY,eAAe,EAGhE;AAAC;;;ADKJ,IAAM,uBAAuB;AAC7B,IAAM,mBAAmB;AAMlB,IAAM,sBAAsB,MACjC,QAAQ,aAAa,YAAY,QAAQ,aAAa,WAAW,QAAQ,aAAa;AAEjF,IAAM,cAAc,MACzB,QAAQ,aAAa,WACjB,mBACA,QAAQ,aAAa,UACnB,+BACA;AAED,IAAM,qBAAqB,CAAC,aACjC,UAAU,KAAK,KAAK,QAAQ,IAAI,gBAAgB,GAAG,KAAK,KAAK;AAQ/D,IAAI,mBAAqD;AASzD,IAAM,gBAAgB,YAAuC;AAC3D,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,YAAY;AACd,UAAM,MAAM,cAAc,YAAY,GAAG;AACzC,WAAQ,IAAI,UAAU,EAAkC;AAAA,EAC1D;AACA,QAAM,EAAE,MAAM,IAAI,MAAM,OAAO,kBAAkB;AACjD,SAAO;AACT;AAEA,IAAM,YAAY,MAChB,OAAO,WAAW;AAAA,EAChB,KAAK,YAAY;AACf,QAAI,CAAC,oBAAoB,GAAG;AAC1B,YAAM,IAAI,MAAM,yBAAyB,QAAQ,QAAQ,GAAG;AAAA,IAC9D;AACA,yBAAqB,cAAc;AACnC,WAAO,MAAM;AAAA,EACf;AAAA,EACA,OAAO,CAAC,UACN,IAAI,cAAc;AAAA,IAChB,SAAS,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,IACjG;AAAA,EACF,CAAC;AACL,CAAC;AAEH,IAAM,cAAc,CAAC,aAAqB,YACxC,OAAO;AAAA,EAAQ,UAAU;AAAA,EAAG,CAAC,UAC3B,OAAO,IAAI;AAAA,IACT,KAAK,MAAM,IAAI,MAAM,aAAa,OAAO;AAAA,IACzC,OAAO,CAAC,UACN,IAAI,cAAc;AAAA,MAChB,SAAS,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACjG;AAAA,IACF,CAAC;AAAA,EACL,CAAC;AACH;AAMK,IAAM,cAAc,CACzB,aACA,YAEA,OAAO;AAAA,EAAQ,YAAY,aAAa,OAAO;AAAA,EAAG,CAAC,UACjD,OAAO,IAAI;AAAA,IACT,KAAK,MAAM,MAAM,YAAY;AAAA,IAC7B,OAAO,MAAM,IAAI,cAAc,EAAE,SAAS,sCAAsC,OAAO,IAAI,CAAC;AAAA,EAC9F,CAAC;AACH;AAEK,IAAM,cAAc,CACzB,aACA,SACA,UAEA,OAAO;AAAA,EAAQ,YAAY,aAAa,OAAO;AAAA,EAAG,CAAC,UACjD,OAAO,IAAI;AAAA,IACT,KAAK,MAAM,MAAM,YAAY,KAAK;AAAA,IAClC,OAAO,CAAC,UACN,IAAI,cAAc;AAAA,MAChB,SAAS,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACzF;AAAA,IACF,CAAC;AAAA,EACL,CAAC,EAAE,KAAK,OAAO,MAAM;AACvB;AAEK,IAAM,iBAAiB,CAC5B,aACA,YAEA,OAAO;AAAA,EAAQ,YAAY,aAAa,OAAO;AAAA,EAAG,CAAC,UACjD,OAAO,IAAI;AAAA,IACT,KAAK,MAAM;AACT,YAAM,eAAe;AACrB,aAAO;AAAA,IACT;AAAA,IACA,OAAO,MACL,IAAI,cAAc,EAAE,SAAS,uCAAuC,OAAO,IAAI,CAAC;AAAA,EACpF,CAAC;AACH;;;AE/HF,SAAS,UAAAC,eAAc;AAEvB,SAAS,oBAAyC;AAkBlD,IAAM,iBAAiB,CAAC,UACtB,IAAI,aAAa,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,SAAS,MAAM,CAAC;AAMnE,IAAM,uBAAuB,CAAC,iBAAyC;AAAA,EAC5E,KAAK;AAAA,EACL,UAAU;AAAA,EACV,KAAK,CAAC,UAAU,WACd,YAAY,aAAa,QAAQ,EAAE,KAAKC,QAAO,SAAS,cAAc,CAAC;AAAA,EACzE,KAAK,CAAC,UAAU,OAAO,WACrB,YAAY,aAAa,UAAU,KAAK,EAAE,KAAKA,QAAO,SAAS,cAAc,CAAC;AAAA,EAChF,QAAQ,CAAC,UAAU,WACjB,eAAe,aAAa,QAAQ,EAAE,KAAKA,QAAO,SAAS,cAAc,CAAC;AAAA;AAAA,EAE5E,MAAM;AACR;;;AHdA,IAAM,gBAAgB;AACtB,IAAM,cAAc;AA0CpB,IAAM,oBAAoB,CACxB,KACA,YAEA,GAAG,mBAAmB,SAAS,WAAW,CAAC,IAAI,IAAI,OAAO,CAAC,EAAG,EAAY;AAErE,IAAM,iBAAiB;AAAA,EAO5B,CAAC,aAAoC;AAAA,IACnC,IAAI;AAAA,IACJ,SAAS,OAAO,CAAC;AAAA,IAEjB,WAAW,CAAC,QAA2B;AACrC,YAAM,cAAc,kBAAkB,KAAK,OAAO;AAClD,aAAO;AAAA,QACL,aAAa,YAAY;AAAA,QACzB,aAAa,oBAAoB;AAAA,QACjC,KAAK,CAAC,OACJ,YAAY,aAAa,EAAE,EAAE;AAAA,UAC3BC,QAAO,IAAI,CAAC,MAAM,MAAM,IAAI;AAAA,UAC5BA,QAAO,cAAc,MAAM,KAAK;AAAA,QAClC;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,iBAAiB,CAAC,QAChBA,QAAO,IAAI,aAAa;AACtB,YAAM,cAAc,kBAAkB,KAAK,OAAO;AAClD,YAAM,YAAY,OAAO;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE;AAAA,QACAA,QAAO;AAAA,UACL,eAAe,aAAa,aAAa,EAAE;AAAA,YACzCA,QAAO,MAAM,MAAMA,QAAO,IAAI;AAAA,UAChC;AAAA,QACF;AAAA,QACAA,QAAO,GAAG,IAAI;AAAA,QACdA,QAAO;AAAA,UAAM,CAAC,UACZA,QAAO;AAAA,YACL,yDAAyD,MAAM,OAAO;AAAA,UACxE,EAAE,KAAKA,QAAO,GAAG,KAAK,CAAC;AAAA,QACzB;AAAA,MACF;AACA,aAAO,YAAY,CAAC,qBAAqB,WAAW,CAAC,IAAI,CAAC;AAAA,IAC5D,CAAC;AAAA,EACL;AACF;","names":["Effect","Effect","Effect","Effect"]}
|
package/dist/core.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { type SecretId, type ExecutorPlugin } from "@executor-js/sdk";
|
|
3
2
|
export { KeychainError } from "./errors";
|
|
4
3
|
export { makeKeychainProvider } from "./provider";
|
|
5
4
|
export { isSupportedPlatform, displayName } from "./keyring";
|
|
@@ -13,7 +12,6 @@ export interface KeychainExtension {
|
|
|
13
12
|
/** Whether the current platform supports system keychain */
|
|
14
13
|
readonly isSupported: boolean;
|
|
15
14
|
/** Check if a secret exists in the system keychain */
|
|
16
|
-
readonly has: (
|
|
15
|
+
readonly has: (id: string) => Effect.Effect<boolean>;
|
|
17
16
|
}
|
|
18
|
-
declare const
|
|
19
|
-
export declare const keychainPlugin: (config?: KeychainPluginConfig) => ExecutorPlugin<typeof PLUGIN_KEY, KeychainExtension>;
|
|
17
|
+
export declare const keychainPlugin: import("@executor-js/sdk/core").ConfiguredPlugin<"keychain", KeychainExtension, {}, KeychainPluginConfig, undefined>;
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/promise.ts"],"sourcesContent":["import { keychainPlugin as keychainPluginEffect } from \"./index\";\n\nexport type { KeychainPluginConfig } from \"./index\";\n\nexport const keychainPlugin = (config?:
|
|
1
|
+
{"version":3,"sources":["../src/promise.ts"],"sourcesContent":["import { type Plugin } from \"@executor-js/sdk/core\";\n\nimport {\n keychainPlugin as keychainPluginEffect,\n type KeychainExtension,\n type KeychainPluginConfig,\n} from \"./index\";\n\nexport type { KeychainPluginConfig } from \"./index\";\n\n// Explicit return type so the emitted dist/promise.d.ts references\n// `import(\"@executor-js/sdk/core\").Plugin` rather than the Promise-surface\n// root specifier (which doesn't re-export Plugin).\nexport const keychainPlugin = (\n config?: KeychainPluginConfig,\n): Plugin<\"keychain\", KeychainExtension, Record<string, never>, undefined> =>\n keychainPluginEffect(config);\n"],"mappings":";;;;;AAaO,IAAMA,kBAAiB,CAC5B,WAEA,eAAqB,MAAM;","names":["keychainPlugin"]}
|
package/dist/promise.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
import { type Plugin } from "@executor-js/sdk/core";
|
|
2
|
+
import { type KeychainExtension, type KeychainPluginConfig } from "./index";
|
|
1
3
|
export type { KeychainPluginConfig } from "./index";
|
|
2
|
-
export declare const keychainPlugin: (config?:
|
|
3
|
-
readonly serviceName?: string;
|
|
4
|
-
}) => import("@executor-js/sdk").ExecutorPlugin<"keychain", import("./index").KeychainExtension>;
|
|
4
|
+
export declare const keychainPlugin: (config?: KeychainPluginConfig) => Plugin<"keychain", KeychainExtension, Record<string, never>, undefined>;
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SecretProvider } from "@executor-js/sdk/core";
|
|
2
2
|
export declare const makeKeychainProvider: (serviceName: string) => SecretProvider;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@executor-js/plugin-keychain",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/keychain",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/RhysSullivan/executor/issues"
|
|
@@ -40,15 +40,15 @@
|
|
|
40
40
|
"typecheck:slow": "bunx tsc --noEmit -p tsconfig.json"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@executor-js/sdk": "0.0.
|
|
43
|
+
"@executor-js/sdk": "0.0.2",
|
|
44
44
|
"@napi-rs/keyring": "^1.2.0",
|
|
45
|
-
"effect": "
|
|
45
|
+
"effect": "4.0.0-beta.59"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@effect/vitest": "
|
|
48
|
+
"@effect/vitest": "4.0.0-beta.59",
|
|
49
49
|
"@types/node": "^24.3.1",
|
|
50
50
|
"bun-types": "^1.2.22",
|
|
51
51
|
"tsup": "^8.5.0",
|
|
52
|
-
"vitest": "^4.1.
|
|
52
|
+
"vitest": "^4.1.5"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/keyring.ts","../src/errors.ts","../src/provider.ts"],"sourcesContent":["import { Effect } from \"effect\";\n\nimport { definePlugin, type SecretId, type ExecutorPlugin } from \"@executor/sdk\";\n\nimport { displayName, isSupportedPlatform, resolveServiceName } from \"./keyring\";\nimport { getPassword } from \"./keyring\";\nimport { makeKeychainProvider } from \"./provider\";\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport { KeychainError } from \"./errors\";\nexport { makeKeychainProvider } from \"./provider\";\nexport { isSupportedPlatform, displayName } from \"./keyring\";\n\n// ---------------------------------------------------------------------------\n// Plugin config\n// ---------------------------------------------------------------------------\n\nexport interface KeychainPluginConfig {\n /** Override the keychain service name (default: \"executor\") */\n readonly serviceName?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin extension — public API on executor.keychain\n// ---------------------------------------------------------------------------\n\nexport interface KeychainExtension {\n /** Human-readable name for the keychain on this platform */\n readonly displayName: string;\n\n /** Whether the current platform supports system keychain */\n readonly isSupported: boolean;\n\n /** Check if a secret exists in the system keychain */\n readonly has: (secretId: SecretId) => Effect.Effect<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n// ---------------------------------------------------------------------------\n\nconst PLUGIN_KEY = \"keychain\";\n\nexport const keychainPlugin = (\n config?: KeychainPluginConfig,\n): ExecutorPlugin<typeof PLUGIN_KEY, KeychainExtension> =>\n definePlugin({\n key: PLUGIN_KEY,\n init: (ctx) =>\n Effect.gen(function* () {\n // Scope the service name to the current scope so each folder gets its own keychain entries\n const baseServiceName = resolveServiceName(config?.serviceName);\n const serviceName = `${baseServiceName}/${ctx.scope.id}`;\n\n yield* ctx.secrets.addProvider(makeKeychainProvider(serviceName));\n\n const extension: KeychainExtension = {\n displayName: displayName(),\n isSupported: isSupportedPlatform(),\n\n has: (secretId) =>\n getPassword(serviceName, secretId).pipe(\n Effect.map((v) => v !== null),\n Effect.orElseSucceed(() => false),\n ),\n };\n\n return { extension };\n }),\n });\n","import { Effect } from \"effect\";\n\nimport { KeychainError } from \"./errors\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SERVICE_NAME = \"executor\";\nconst SERVICE_NAME_ENV = \"EXECUTOR_KEYCHAIN_SERVICE_NAME\";\n\n// ---------------------------------------------------------------------------\n// Platform helpers\n// ---------------------------------------------------------------------------\n\nexport const isSupportedPlatform = () =>\n process.platform === \"darwin\" || process.platform === \"linux\" || process.platform === \"win32\";\n\nexport const displayName = () =>\n process.platform === \"darwin\"\n ? \"macOS Keychain\"\n : process.platform === \"win32\"\n ? \"Windows Credential Manager\"\n : \"Desktop Keyring\";\n\nexport const resolveServiceName = (explicit?: string): string =>\n explicit?.trim() || process.env[SERVICE_NAME_ENV]?.trim() || DEFAULT_SERVICE_NAME;\n\n// ---------------------------------------------------------------------------\n// Lazy-load @napi-rs/keyring (native module)\n// ---------------------------------------------------------------------------\n\ntype EntryConstructor = (typeof import(\"@napi-rs/keyring\"))[\"Entry\"];\n\nlet entryCtorPromise: Promise<EntryConstructor> | null = null;\n\nconst loadEntry = (): Effect.Effect<EntryConstructor, KeychainError> =>\n Effect.tryPromise({\n try: async () => {\n if (!isSupportedPlatform()) {\n throw new Error(`unsupported platform '${process.platform}'`);\n }\n entryCtorPromise ??= import(\"@napi-rs/keyring\").then(({ Entry }) => Entry);\n return await entryCtorPromise;\n },\n catch: (cause) =>\n new KeychainError({\n message: `Failed loading native keyring: ${cause instanceof Error ? cause.message : String(cause)}`,\n cause,\n }),\n });\n\nconst createEntry = (serviceName: string, account: string) =>\n Effect.flatMap(loadEntry(), (Entry) =>\n Effect.try({\n try: () => new Entry(serviceName, account),\n catch: (cause) =>\n new KeychainError({\n message: `Failed creating keyring entry: ${cause instanceof Error ? cause.message : String(cause)}`,\n cause,\n }),\n }),\n );\n\n// ---------------------------------------------------------------------------\n// Low-level keychain operations\n// ---------------------------------------------------------------------------\n\nexport const getPassword = (\n serviceName: string,\n account: string,\n): Effect.Effect<string | null, KeychainError> =>\n Effect.flatMap(createEntry(serviceName, account), (entry) =>\n Effect.try({\n try: () => entry.getPassword(),\n catch: () => new KeychainError({ message: `Failed reading secret for account '${account}'` }),\n }),\n );\n\nexport const setPassword = (\n serviceName: string,\n account: string,\n value: string,\n): Effect.Effect<void, KeychainError> =>\n Effect.flatMap(createEntry(serviceName, account), (entry) =>\n Effect.try({\n try: () => entry.setPassword(value),\n catch: (cause) =>\n new KeychainError({\n message: `Failed writing secret: ${cause instanceof Error ? cause.message : String(cause)}`,\n cause,\n }),\n }).pipe(Effect.asVoid),\n );\n\nexport const deletePassword = (\n serviceName: string,\n account: string,\n): Effect.Effect<boolean, KeychainError> =>\n Effect.flatMap(createEntry(serviceName, account), (entry) =>\n Effect.try({\n try: () => {\n entry.deletePassword();\n return true;\n },\n catch: () =>\n new KeychainError({ message: `Failed deleting secret for account '${account}'` }),\n }),\n );\n","import { Data } from \"effect\";\n\nexport class KeychainError extends Data.TaggedError(\"KeychainError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n","import { Effect } from \"effect\";\n\nimport type { SecretProvider } from \"@executor/sdk\";\n\nimport { getPassword, setPassword, deletePassword } from \"./keyring\";\n\n// ---------------------------------------------------------------------------\n// SecretProvider adapter — bridges keyring into SDK resolution chain\n// ---------------------------------------------------------------------------\n\nexport const makeKeychainProvider = (serviceName: string): SecretProvider => ({\n key: \"keychain\",\n writable: true,\n get: (secretId) => getPassword(serviceName, secretId).pipe(Effect.orElseSucceed(() => null)),\n set: (secretId, value) =>\n setPassword(serviceName, secretId, value).pipe(Effect.orElseSucceed(() => undefined)),\n delete: (secretId) =>\n deletePassword(serviceName, secretId).pipe(Effect.orElseSucceed(() => false)),\n // Keychain doesn't support enumerating — you need to know the account name\n list: undefined,\n});\n"],"mappings":"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","names":["Effect","Effect","Effect","Effect"]}
|