@executor-js/plugin-file-secrets 0.0.1-beta.6 → 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 CHANGED
@@ -1,23 +1,23 @@
1
- # @executor/plugin-file-secrets
1
+ # @executor-js/plugin-file-secrets
2
2
 
3
3
  File-backed secret store for the executor. Persists secrets to a single JSON file at an XDG-compliant path so they survive between process restarts — useful for local development, CLIs, and scripts where a system keychain isn't available.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```sh
8
- bun add @executor/sdk @executor/plugin-file-secrets
8
+ bun add @executor-js/sdk @executor-js/plugin-file-secrets
9
9
  # or
10
- npm install @executor/sdk @executor/plugin-file-secrets
10
+ npm install @executor-js/sdk @executor-js/plugin-file-secrets
11
11
  ```
12
12
 
13
13
  ## Usage
14
14
 
15
15
  ```ts
16
- import { createExecutor } from "@executor/sdk";
17
- import { fileSecretsPlugin } from "@executor/plugin-file-secrets";
16
+ import { createExecutor } from "@executor-js/sdk";
17
+ import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets";
18
18
 
19
19
  const executor = await createExecutor({
20
- scope: { name: "my-app" },
20
+ onElicitation: "accept-all",
21
21
  plugins: [fileSecretsPlugin()] as const,
22
22
  });
23
23
 
@@ -26,29 +26,29 @@ await executor.secrets.set({
26
26
  id: "api-key",
27
27
  name: "My API Key",
28
28
  value: "secret123",
29
- purpose: "authentication",
29
+ scope: executor.scopes[0]!.id,
30
30
  });
31
31
 
32
32
  // Read it back
33
- const value = await executor.secrets.resolve("api-key");
33
+ const value = await executor.secrets.get("api-key");
34
34
 
35
35
  // Check where it's stored
36
36
  console.log("Secret file:", executor.fileSecrets.filePath);
37
37
  ```
38
38
 
39
- Secrets written through `executor.secrets.set(...)` become available to every other plugin that resolves them, so you can (for example) store a GitHub token here and have `@executor/plugin-openapi` or `@executor/plugin-graphql` pick it up via `{ secretId, prefix }` headers.
39
+ Secrets written through `executor.secrets.set(...)` become available to every other plugin that resolves them, so you can (for example) store a GitHub token here and have `@executor-js/plugin-openapi` or `@executor-js/plugin-graphql` pick it up via `{ secretId, prefix }` headers.
40
40
 
41
41
  ## Using with Effect
42
42
 
43
- If you're building on `@executor/sdk` (the raw Effect entry), import this plugin from its `/core` subpath instead:
43
+ 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:
44
44
 
45
45
  ```ts
46
- import { fileSecretsPlugin } from "@executor/plugin-file-secrets";
46
+ import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets/core";
47
47
  ```
48
48
 
49
49
  ## Security note
50
50
 
51
- Secrets are stored unencrypted in a plain JSON file. Use [`@executor/plugin-keychain`](https://www.npmjs.com/package/@executor/plugin-keychain) for OS-keychain-backed storage, or [`@executor/plugin-onepassword`](https://www.npmjs.com/package/@executor/plugin-onepassword) for 1Password-backed storage when you need encryption at rest.
51
+ Secrets are stored unencrypted in a plain JSON file. Use [`@executor-js/plugin-keychain`](https://www.npmjs.com/package/@executor-js/plugin-keychain) for OS-keychain-backed storage, or [`@executor-js/plugin-onepassword`](https://www.npmjs.com/package/@executor-js/plugin-onepassword) for 1Password-backed storage when you need encryption at rest.
52
52
 
53
53
  ## Status
54
54
 
@@ -0,0 +1,118 @@
1
+ // src/index.ts
2
+ import { Effect, Schema } from "effect";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import {
6
+ definePlugin,
7
+ StorageError
8
+ } from "@executor-js/sdk/core";
9
+ var APP_NAME = "executor";
10
+ var xdgDataHome = () => {
11
+ if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();
12
+ if (process.platform === "win32") {
13
+ return process.env.LOCALAPPDATA || process.env.APPDATA || path.join(process.env.USERPROFILE || "~", "AppData", "Local");
14
+ }
15
+ return path.join(process.env.HOME || "~", ".local", "share");
16
+ };
17
+ var authDir = (overrideDir) => overrideDir ?? path.join(xdgDataHome(), APP_NAME);
18
+ var authFilePath = (overrideDir) => path.join(authDir(overrideDir), "auth.json");
19
+ var ScopedAuthFile = Schema.Record(
20
+ Schema.String,
21
+ Schema.Record(Schema.String, Schema.String)
22
+ );
23
+ var decodeScopedAuthFile = Schema.decodeUnknownSync(ScopedAuthFile);
24
+ var readFullFile = (filePath) => {
25
+ if (!fs.existsSync(filePath)) return {};
26
+ let raw;
27
+ try {
28
+ raw = fs.readFileSync(filePath, "utf-8");
29
+ } catch (cause) {
30
+ if (cause.code === "ENOENT") return {};
31
+ throw cause;
32
+ }
33
+ return decodeScopedAuthFile(JSON.parse(raw));
34
+ };
35
+ var readScopeSecrets = (filePath, scopeId) => readFullFile(filePath)[scopeId] ?? {};
36
+ var writeScopeSecrets = (filePath, scopeId, secrets) => {
37
+ const dir = path.dirname(filePath);
38
+ if (!fs.existsSync(dir)) {
39
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
40
+ }
41
+ const full = readFullFile(filePath);
42
+ if (Object.keys(secrets).length === 0) {
43
+ delete full[scopeId];
44
+ } else {
45
+ full[scopeId] = secrets;
46
+ }
47
+ const tmp = `${filePath}.tmp`;
48
+ fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 384 });
49
+ fs.renameSync(tmp, filePath);
50
+ };
51
+ var toStorageError = (cause) => new StorageError({
52
+ message: cause instanceof Error ? cause.message : String(cause),
53
+ cause
54
+ });
55
+ var makeScopedProvider = (filePath, listScope) => ({
56
+ key: "file",
57
+ writable: true,
58
+ get: (secretId, scope) => Effect.try({
59
+ try: () => {
60
+ const data = readScopeSecrets(filePath, scope);
61
+ return data[secretId] ?? null;
62
+ },
63
+ catch: toStorageError
64
+ }),
65
+ has: (secretId, scope) => Effect.try({
66
+ try: () => {
67
+ const data = readScopeSecrets(filePath, scope);
68
+ return secretId in data;
69
+ },
70
+ catch: toStorageError
71
+ }),
72
+ set: (secretId, value, scope) => Effect.try({
73
+ try: () => {
74
+ const data = readScopeSecrets(filePath, scope);
75
+ data[secretId] = value;
76
+ writeScopeSecrets(filePath, scope, data);
77
+ },
78
+ catch: toStorageError
79
+ }),
80
+ delete: (secretId, scope) => Effect.try({
81
+ try: () => {
82
+ const data = readScopeSecrets(filePath, scope);
83
+ const had = secretId in data;
84
+ delete data[secretId];
85
+ if (had) writeScopeSecrets(filePath, scope, data);
86
+ return had;
87
+ },
88
+ catch: toStorageError
89
+ }),
90
+ list: () => Effect.try({
91
+ try: () => {
92
+ const data = readScopeSecrets(filePath, listScope);
93
+ return Object.keys(data).map((k) => ({ id: k, name: k }));
94
+ },
95
+ catch: toStorageError
96
+ })
97
+ });
98
+ var resolveFilePath = (config) => authFilePath(config?.directory);
99
+ var fileSecretsPlugin = definePlugin(
100
+ (options) => ({
101
+ id: "fileSecrets",
102
+ storage: () => ({}),
103
+ extension: (_ctx) => ({
104
+ filePath: resolveFilePath(options)
105
+ }),
106
+ secretProviders: (ctx) => [
107
+ // list() falls back to the innermost scope for display; per-call
108
+ // get/set/delete honor the scope arg threaded from the secrets facade.
109
+ makeScopedProvider(resolveFilePath(options), ctx.scopes[0].id)
110
+ ]
111
+ })
112
+ );
113
+
114
+ export {
115
+ xdgDataHome,
116
+ fileSecretsPlugin
117
+ };
118
+ //# sourceMappingURL=chunk-6YB3FO4B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nimport {\n definePlugin,\n StorageError,\n type PluginCtx,\n type SecretProvider,\n} from \"@executor-js/sdk/core\";\n\n// ---------------------------------------------------------------------------\n// XDG data dir resolution\n// ---------------------------------------------------------------------------\n\nconst APP_NAME = \"executor\";\n\nexport const xdgDataHome = (): string => {\n if (process.env.XDG_DATA_HOME?.trim()) return process.env.XDG_DATA_HOME.trim();\n if (process.platform === \"win32\") {\n return (\n process.env.LOCALAPPDATA ||\n process.env.APPDATA ||\n path.join(process.env.USERPROFILE || \"~\", \"AppData\", \"Local\")\n );\n }\n return path.join(process.env.HOME || \"~\", \".local\", \"share\");\n};\n\nconst authDir = (overrideDir?: string): string =>\n overrideDir ?? path.join(xdgDataHome(), APP_NAME);\n\nconst authFilePath = (overrideDir?: string): string =>\n path.join(authDir(overrideDir), \"auth.json\");\n\n// ---------------------------------------------------------------------------\n// Schema for the auth file\n//\n// Top-level keys are scope IDs, values are { secretId: secretValue } maps.\n// { \"web-a1b2c3d4\": { \"github-token\": \"ghp_xxx\" } }\n// ---------------------------------------------------------------------------\n\nconst ScopedAuthFile = Schema.Record(\n Schema.String,\n Schema.Record(Schema.String, Schema.String),\n);\nconst decodeScopedAuthFile = Schema.decodeUnknownSync(ScopedAuthFile);\n\n// ---------------------------------------------------------------------------\n// File I/O with restricted permissions\n//\n// These helpers throw on real I/O or decode failures — the provider wraps\n// every call in `Effect.try` so those throws surface as typed\n// `StorageError` on the Effect error channel. Previously `readFullFile`\n// used a blanket `try { ... } catch { return {}; }` which masked JSON\n// parse errors, schema decode failures, and permission errors as\n// \"empty file\", making misconfigured installs silently return null from\n// every `get`.\n// ---------------------------------------------------------------------------\n\nconst readFullFile = (filePath: string): Record<string, Record<string, string>> => {\n if (!fs.existsSync(filePath)) return {};\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, \"utf-8\");\n } catch (cause) {\n // Treat \"file disappeared between existsSync and readFileSync\" as\n // absence — anything else (EACCES, EISDIR, …) propagates.\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw cause;\n }\n return decodeScopedAuthFile(JSON.parse(raw));\n};\n\nconst readScopeSecrets = (filePath: string, scopeId: string): Record<string, string> =>\n readFullFile(filePath)[scopeId] ?? {};\n\nconst writeScopeSecrets = (\n filePath: string,\n scopeId: string,\n secrets: Record<string, string>,\n): void => {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const full = readFullFile(filePath);\n if (Object.keys(secrets).length === 0) {\n delete full[scopeId];\n } else {\n full[scopeId] = secrets;\n }\n const tmp = `${filePath}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 0o600 });\n fs.renameSync(tmp, filePath);\n};\n\n// ---------------------------------------------------------------------------\n// Plugin config\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsPluginConfig {\n /** Override the directory for auth.json (default: XDG data dir) */\n readonly directory?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin extension — public API on executor.fileSecrets\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsExtension {\n /** Path to the auth file */\n readonly filePath: string;\n}\n\n// ---------------------------------------------------------------------------\n// Provider factory (internal)\n// ---------------------------------------------------------------------------\n\nconst toStorageError = (cause: unknown) =>\n new StorageError({\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n });\n\n// Scope arg is honored at every call: the auth.json is partitioned by\n// scope id, so read/write/delete route to `file[scope][secretId]`. The\n// provider is a singleton per executor; scope routing happens via the\n// arg passed from the executor's secrets facade.\n//\n// `list` enumerates the innermost scope the provider was configured\n// for — the executor's fallback/list path passes scope separately but\n// the SecretProvider.list signature is scope-agnostic. That's fine for\n// the current use: `list` feeds `secrets.list()` which already walks\n// the stack at the caller layer. Innermost-first is the display default.\nconst makeScopedProvider = (\n filePath: string,\n listScope: string,\n): SecretProvider => ({\n key: \"file\",\n writable: true,\n\n get: (secretId, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n return data[secretId] ?? null;\n },\n catch: toStorageError,\n }),\n\n has: (secretId, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n return secretId in data;\n },\n catch: toStorageError,\n }),\n\n set: (secretId, value, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n data[secretId] = value;\n writeScopeSecrets(filePath, scope, data);\n },\n catch: toStorageError,\n }),\n\n delete: (secretId, scope) =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, scope);\n const had = secretId in data;\n delete data[secretId];\n if (had) writeScopeSecrets(filePath, scope, data);\n return had;\n },\n catch: toStorageError,\n }),\n\n list: () =>\n Effect.try({\n try: () => {\n const data = readScopeSecrets(filePath, listScope);\n return Object.keys(data).map((k) => ({ id: k, name: k }));\n },\n catch: toStorageError,\n }),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n//\n// Compute the scoped file path identically in `extension` (for `filePath`)\n// and `secretProviders` (for the provider's read/write). Both receive ctx\n// and both are called once per createExecutor.\n// ---------------------------------------------------------------------------\n\nconst resolveFilePath = (config: FileSecretsPluginConfig | undefined): string =>\n authFilePath(config?.directory);\n\nexport const fileSecretsPlugin = definePlugin(\n (options?: FileSecretsPluginConfig) => ({\n id: \"fileSecrets\" as const,\n storage: () => ({}),\n\n extension: (_ctx): FileSecretsExtension => ({\n filePath: resolveFilePath(options),\n }),\n\n secretProviders: (ctx: PluginCtx<unknown>) => [\n // list() falls back to the innermost scope for display; per-call\n // get/set/delete honor the scope arg threaded from the secrets facade.\n makeScopedProvider(resolveFilePath(options), ctx.scopes[0]!.id as string),\n ],\n }),\n);\n"],"mappings":"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","names":[]}
package/dist/core.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
- fileSecretsPlugin
3
- } from "./chunk-PBQRMQCD.js";
2
+ fileSecretsPlugin,
3
+ xdgDataHome
4
+ } from "./chunk-6YB3FO4B.js";
4
5
  export {
5
- fileSecretsPlugin
6
+ fileSecretsPlugin,
7
+ xdgDataHome
6
8
  };
7
9
  //# sourceMappingURL=core.js.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type ExecutorPlugin } from "@executor-js/sdk";
1
+ export declare const xdgDataHome: () => string;
2
2
  export interface FileSecretsPluginConfig {
3
3
  /** Override the directory for auth.json (default: XDG data dir) */
4
4
  readonly directory?: string;
@@ -7,6 +7,4 @@ export interface FileSecretsExtension {
7
7
  /** Path to the auth file */
8
8
  readonly filePath: string;
9
9
  }
10
- declare const PLUGIN_KEY = "fileSecrets";
11
- export declare const fileSecretsPlugin: (config?: FileSecretsPluginConfig) => ExecutorPlugin<typeof PLUGIN_KEY, FileSecretsExtension>;
12
- export {};
10
+ export declare const fileSecretsPlugin: import("@executor-js/sdk/core").ConfiguredPlugin<"fileSecrets", FileSecretsExtension, {}, FileSecretsPluginConfig, undefined>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  fileSecretsPlugin
3
- } from "./chunk-PBQRMQCD.js";
3
+ } from "./chunk-6YB3FO4B.js";
4
4
 
5
5
  // src/promise.ts
6
6
  var fileSecretsPlugin2 = (config) => fileSecretsPlugin(config);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/promise.ts"],"sourcesContent":["import { fileSecretsPlugin as fileSecretsPluginEffect } from \"./index\";\n\nexport type { FileSecretsPluginConfig } from \"./index\";\n\nexport const fileSecretsPlugin = (config?: { readonly directory?: string }) =>\n fileSecretsPluginEffect(config);\n"],"mappings":";;;;;AAIO,IAAMA,qBAAoB,CAAC,WAChC,kBAAwB,MAAM;","names":["fileSecretsPlugin"]}
1
+ {"version":3,"sources":["../src/promise.ts"],"sourcesContent":["import { type Plugin } from \"@executor-js/sdk/core\";\n\nimport {\n fileSecretsPlugin as fileSecretsPluginEffect,\n type FileSecretsExtension,\n type FileSecretsPluginConfig,\n} from \"./index\";\n\nexport type { FileSecretsPluginConfig } from \"./index\";\n\n// Explicit return type so the emitted dist/promise.d.ts references\n// `import(\"@executor-js/sdk/core\").Plugin` (where `Plugin` lives) rather\n// than `import(\"@executor-js/sdk\").Plugin` (the Promise surface, which\n// doesn't re-export Plugin).\nexport const fileSecretsPlugin = (\n config?: FileSecretsPluginConfig,\n): Plugin<\"fileSecrets\", FileSecretsExtension, Record<string, never>, undefined> =>\n fileSecretsPluginEffect(config);\n"],"mappings":";;;;;AAcO,IAAMA,qBAAoB,CAC/B,WAEA,kBAAwB,MAAM;","names":["fileSecretsPlugin"]}
package/dist/promise.d.ts CHANGED
@@ -1,4 +1,4 @@
1
+ import { type Plugin } from "@executor-js/sdk/core";
2
+ import { type FileSecretsExtension, type FileSecretsPluginConfig } from "./index";
1
3
  export type { FileSecretsPluginConfig } from "./index";
2
- export declare const fileSecretsPlugin: (config?: {
3
- readonly directory?: string;
4
- }) => import("@executor-js/sdk").ExecutorPlugin<"fileSecrets", import("./index").FileSecretsExtension>;
4
+ export declare const fileSecretsPlugin: (config?: FileSecretsPluginConfig) => Plugin<"fileSecrets", FileSecretsExtension, Record<string, never>, undefined>;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@executor-js/plugin-file-secrets",
3
- "version": "0.0.1-beta.6",
3
+ "version": "0.0.2",
4
4
  "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/plugins/file-secrets",
5
5
  "bugs": {
6
6
  "url": "https://github.com/RhysSullivan/executor/issues"
@@ -40,13 +40,13 @@
40
40
  "typecheck:slow": "bunx tsc --noEmit -p tsconfig.json"
41
41
  },
42
42
  "dependencies": {
43
- "@executor-js/sdk": "0.0.1-beta.6",
44
- "effect": "^3.21.0"
43
+ "@executor-js/sdk": "0.0.2",
44
+ "effect": "4.0.0-beta.59"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^24.3.1",
48
48
  "bun-types": "^1.2.22",
49
49
  "tsup": "^8.5.0",
50
- "vitest": "^4.1.3"
50
+ "vitest": "^4.1.5"
51
51
  }
52
52
  }
@@ -1,79 +0,0 @@
1
- // src/index.ts
2
- import { Effect, Schema } from "effect";
3
- import { definePlugin } from "@executor-js/sdk";
4
- import * as fs from "fs";
5
- import * as path from "path";
6
- var APP_NAME = "executor";
7
- var xdgDataHome = () => process.env.XDG_DATA_HOME?.trim() || path.join(process.env.HOME || process.env.USERPROFILE || "~", ".local", "share");
8
- var authDir = (overrideDir) => overrideDir ?? path.join(xdgDataHome(), APP_NAME);
9
- var authFilePath = (overrideDir) => path.join(authDir(overrideDir), "auth.json");
10
- var ScopedAuthFile = Schema.Record({
11
- key: Schema.String,
12
- value: Schema.Record({ key: Schema.String, value: Schema.String })
13
- });
14
- var decodeScopedAuthFile = Schema.decodeUnknownSync(ScopedAuthFile);
15
- var readFullFile = (filePath) => {
16
- try {
17
- if (!fs.existsSync(filePath)) return {};
18
- const raw = fs.readFileSync(filePath, "utf-8");
19
- return decodeScopedAuthFile(JSON.parse(raw));
20
- } catch {
21
- return {};
22
- }
23
- };
24
- var readScopeSecrets = (filePath, scopeId) => readFullFile(filePath)[scopeId] ?? {};
25
- var writeScopeSecrets = (filePath, scopeId, secrets) => {
26
- const dir = path.dirname(filePath);
27
- if (!fs.existsSync(dir)) {
28
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
29
- }
30
- const full = readFullFile(filePath);
31
- if (Object.keys(secrets).length === 0) {
32
- delete full[scopeId];
33
- } else {
34
- full[scopeId] = secrets;
35
- }
36
- const tmp = `${filePath}.tmp`;
37
- fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 384 });
38
- fs.renameSync(tmp, filePath);
39
- };
40
- var makeScopedProvider = (filePath, scopeId) => ({
41
- key: "file",
42
- writable: true,
43
- get: (secretId) => Effect.sync(() => {
44
- const data = readScopeSecrets(filePath, scopeId);
45
- return data[secretId] ?? null;
46
- }),
47
- set: (secretId, value) => Effect.sync(() => {
48
- const data = readScopeSecrets(filePath, scopeId);
49
- data[secretId] = value;
50
- writeScopeSecrets(filePath, scopeId, data);
51
- }),
52
- delete: (secretId) => Effect.sync(() => {
53
- const data = readScopeSecrets(filePath, scopeId);
54
- const had = secretId in data;
55
- delete data[secretId];
56
- if (had) writeScopeSecrets(filePath, scopeId, data);
57
- return had;
58
- }),
59
- list: () => Effect.sync(() => {
60
- const data = readScopeSecrets(filePath, scopeId);
61
- return Object.keys(data).map((k) => ({ id: k, name: k }));
62
- })
63
- });
64
- var PLUGIN_KEY = "fileSecrets";
65
- var fileSecretsPlugin = (config) => definePlugin({
66
- key: PLUGIN_KEY,
67
- init: (ctx) => Effect.gen(function* () {
68
- const filePath = authFilePath(config?.directory);
69
- yield* ctx.secrets.addProvider(makeScopedProvider(filePath, ctx.scope.id));
70
- return {
71
- extension: { filePath }
72
- };
73
- })
74
- });
75
-
76
- export {
77
- fileSecretsPlugin
78
- };
79
- //# sourceMappingURL=chunk-PBQRMQCD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\";\nimport { definePlugin, type ExecutorPlugin, type SecretProvider } from \"@executor/sdk\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// XDG data dir resolution\n// ---------------------------------------------------------------------------\n\nconst APP_NAME = \"executor\";\n\nconst xdgDataHome = (): string =>\n process.env.XDG_DATA_HOME?.trim() ||\n path.join(process.env.HOME || process.env.USERPROFILE || \"~\", \".local\", \"share\");\n\nconst authDir = (overrideDir?: string): string => overrideDir ?? path.join(xdgDataHome(), APP_NAME);\n\nconst authFilePath = (overrideDir?: string): string => path.join(authDir(overrideDir), \"auth.json\");\n\n// ---------------------------------------------------------------------------\n// Schema for the auth file\n//\n// Top-level keys are scope IDs, values are { secretId: secretValue } maps.\n// { \"web-a1b2c3d4\": { \"github-token\": \"ghp_xxx\" } }\n// ---------------------------------------------------------------------------\n\nconst ScopedAuthFile = Schema.Record({\n key: Schema.String,\n value: Schema.Record({ key: Schema.String, value: Schema.String }),\n});\nconst decodeScopedAuthFile = Schema.decodeUnknownSync(ScopedAuthFile);\n\n// ---------------------------------------------------------------------------\n// File I/O with restricted permissions\n// ---------------------------------------------------------------------------\n\nconst readFullFile = (filePath: string): Record<string, Record<string, string>> => {\n try {\n if (!fs.existsSync(filePath)) return {};\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return decodeScopedAuthFile(JSON.parse(raw));\n } catch {\n return {};\n }\n};\n\nconst readScopeSecrets = (filePath: string, scopeId: string): Record<string, string> =>\n readFullFile(filePath)[scopeId] ?? {};\n\nconst writeScopeSecrets = (\n filePath: string,\n scopeId: string,\n secrets: Record<string, string>,\n): void => {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const full = readFullFile(filePath);\n if (Object.keys(secrets).length === 0) {\n delete full[scopeId];\n } else {\n full[scopeId] = secrets;\n }\n const tmp = `${filePath}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(full, null, 2), { mode: 0o600 });\n fs.renameSync(tmp, filePath);\n};\n\n// ---------------------------------------------------------------------------\n// Plugin config\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsPluginConfig {\n /** Override the directory for auth.json (default: XDG data dir) */\n readonly directory?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin extension — public API on executor.fileSecrets\n// ---------------------------------------------------------------------------\n\nexport interface FileSecretsExtension {\n /** Path to the auth file */\n readonly filePath: string;\n}\n\n// ---------------------------------------------------------------------------\n// Provider factory (internal)\n// ---------------------------------------------------------------------------\n\nconst makeScopedProvider = (filePath: string, scopeId: string): SecretProvider => ({\n key: \"file\",\n writable: true,\n\n get: (secretId) =>\n Effect.sync(() => {\n const data = readScopeSecrets(filePath, scopeId);\n return data[secretId] ?? null;\n }),\n\n set: (secretId, value) =>\n Effect.sync(() => {\n const data = readScopeSecrets(filePath, scopeId);\n data[secretId] = value;\n writeScopeSecrets(filePath, scopeId, data);\n }),\n\n delete: (secretId) =>\n Effect.sync(() => {\n const data = readScopeSecrets(filePath, scopeId);\n const had = secretId in data;\n delete data[secretId];\n if (had) writeScopeSecrets(filePath, scopeId, data);\n return had;\n }),\n\n list: () =>\n Effect.sync(() => {\n const data = readScopeSecrets(filePath, scopeId);\n return Object.keys(data).map((k) => ({ id: k, name: k }));\n }),\n});\n\n// ---------------------------------------------------------------------------\n// Plugin definition\n// ---------------------------------------------------------------------------\n\nconst PLUGIN_KEY = \"fileSecrets\";\n\nexport const fileSecretsPlugin = (\n config?: FileSecretsPluginConfig,\n): ExecutorPlugin<typeof PLUGIN_KEY, FileSecretsExtension> =>\n definePlugin({\n key: PLUGIN_KEY,\n init: (ctx) =>\n Effect.gen(function* () {\n const filePath = authFilePath(config?.directory);\n\n yield* ctx.secrets.addProvider(makeScopedProvider(filePath, ctx.scope.id));\n\n return {\n extension: { filePath },\n };\n }),\n });\n"],"mappings":"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","names":[]}