@orc-brain/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/autoLoop.d.ts +70 -0
- package/dist/autoLoop.d.ts.map +1 -0
- package/dist/autoLoop.js +178 -0
- package/dist/autoLoop.js.map +1 -0
- package/dist/backpressure.d.ts +38 -0
- package/dist/backpressure.d.ts.map +1 -0
- package/dist/backpressure.js +103 -0
- package/dist/backpressure.js.map +1 -0
- package/dist/budgetTracker.d.ts +53 -0
- package/dist/budgetTracker.d.ts.map +1 -0
- package/dist/budgetTracker.js +93 -0
- package/dist/budgetTracker.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +107 -0
- package/dist/config.js.map +1 -0
- package/dist/escalation.d.ts +43 -0
- package/dist/escalation.d.ts.map +1 -0
- package/dist/escalation.js +81 -0
- package/dist/escalation.js.map +1 -0
- package/dist/eventBus.d.ts +30 -0
- package/dist/eventBus.d.ts.map +1 -0
- package/dist/eventBus.js +42 -0
- package/dist/eventBus.js.map +1 -0
- package/dist/goalJudge.d.ts +92 -0
- package/dist/goalJudge.d.ts.map +1 -0
- package/dist/goalJudge.js +254 -0
- package/dist/goalJudge.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/modelRouter.d.ts +40 -0
- package/dist/modelRouter.d.ts.map +1 -0
- package/dist/modelRouter.js +114 -0
- package/dist/modelRouter.js.map +1 -0
- package/dist/orchestrator.d.ts +242 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +975 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/pacing.d.ts +34 -0
- package/dist/pacing.d.ts.map +1 -0
- package/dist/pacing.js +49 -0
- package/dist/pacing.js.map +1 -0
- package/dist/planValidation.d.ts +24 -0
- package/dist/planValidation.d.ts.map +1 -0
- package/dist/planValidation.js +134 -0
- package/dist/planValidation.js.map +1 -0
- package/dist/planner.d.ts +70 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +210 -0
- package/dist/planner.js.map +1 -0
- package/dist/plugins/host.d.ts +39 -0
- package/dist/plugins/host.d.ts.map +1 -0
- package/dist/plugins/host.js +109 -0
- package/dist/plugins/host.js.map +1 -0
- package/dist/plugins/registry.d.ts +71 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +269 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/plugins/secrets.d.ts +30 -0
- package/dist/plugins/secrets.d.ts.map +1 -0
- package/dist/plugins/secrets.js +99 -0
- package/dist/plugins/secrets.js.map +1 -0
- package/dist/preflight.d.ts +38 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +108 -0
- package/dist/preflight.js.map +1 -0
- package/dist/reporting.d.ts +41 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +229 -0
- package/dist/reporting.js.map +1 -0
- package/dist/safety/denyRules.d.ts +57 -0
- package/dist/safety/denyRules.d.ts.map +1 -0
- package/dist/safety/denyRules.js +498 -0
- package/dist/safety/denyRules.js.map +1 -0
- package/dist/safety/envClassifier.d.ts +45 -0
- package/dist/safety/envClassifier.d.ts.map +1 -0
- package/dist/safety/envClassifier.js +99 -0
- package/dist/safety/envClassifier.js.map +1 -0
- package/dist/safety/index.d.ts +83 -0
- package/dist/safety/index.d.ts.map +1 -0
- package/dist/safety/index.js +184 -0
- package/dist/safety/index.js.map +1 -0
- package/dist/safety/limitSignals.d.ts +36 -0
- package/dist/safety/limitSignals.d.ts.map +1 -0
- package/dist/safety/limitSignals.js +79 -0
- package/dist/safety/limitSignals.js.map +1 -0
- package/dist/safety/paths.d.ts +32 -0
- package/dist/safety/paths.d.ts.map +1 -0
- package/dist/safety/paths.js +102 -0
- package/dist/safety/paths.js.map +1 -0
- package/dist/safety/redact.d.ts +17 -0
- package/dist/safety/redact.d.ts.map +1 -0
- package/dist/safety/redact.js +85 -0
- package/dist/safety/redact.js.map +1 -0
- package/dist/spawnEnv.d.ts +18 -0
- package/dist/spawnEnv.d.ts.map +1 -0
- package/dist/spawnEnv.js +43 -0
- package/dist/spawnEnv.js.map +1 -0
- package/dist/store/auditLog.d.ts +28 -0
- package/dist/store/auditLog.d.ts.map +1 -0
- package/dist/store/auditLog.js +48 -0
- package/dist/store/auditLog.js.map +1 -0
- package/dist/store/index.d.ts +140 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +648 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/schema.d.ts +17 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +197 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/system.d.ts +69 -0
- package/dist/system.d.ts.map +1 -0
- package/dist/system.js +135 -0
- package/dist/system.js.map +1 -0
- package/dist/workerManager.d.ts +80 -0
- package/dist/workerManager.d.ts.map +1 -0
- package/dist/workerManager.js +280 -0
- package/dist/workerManager.js.map +1 -0
- package/dist/worktrees.d.ts +74 -0
- package/dist/worktrees.d.ts.map +1 -0
- package/dist/worktrees.js +210 -0
- package/dist/worktrees.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginHost implementation (spec 003 §R1, §R4, §R6): the narrow, safe facade
|
|
3
|
+
* a plugin gets at `init`. Every callback a plugin registers is exception-
|
|
4
|
+
* isolated (§N2) and every subscription is tracked so `system.close()` can
|
|
5
|
+
* detach plugins before the store closes.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExternalRef, ExternalTask, Goal, PluginHost } from "@orc-brain/shared";
|
|
8
|
+
import type { Store } from "../store/index.js";
|
|
9
|
+
import type { AuditSink } from "../safety/index.js";
|
|
10
|
+
import type { EventBus } from "../eventBus.js";
|
|
11
|
+
import type { SecretStore } from "./secrets.js";
|
|
12
|
+
/** Feature-flow entrypoint injected by the composition root (spec 003 §R4). */
|
|
13
|
+
export type CreateFeatureGoalFn = (projectId: string, input: {
|
|
14
|
+
objective: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
external_ref?: ExternalRef | null;
|
|
17
|
+
}) => Goal;
|
|
18
|
+
/** Dependencies for {@link createPluginHost}. */
|
|
19
|
+
export interface PluginHostDeps {
|
|
20
|
+
pluginName: string;
|
|
21
|
+
settings: Record<string, unknown>;
|
|
22
|
+
store: Store;
|
|
23
|
+
bus: EventBus;
|
|
24
|
+
audit: AuditSink;
|
|
25
|
+
secrets: SecretStore;
|
|
26
|
+
createFeatureGoal: CreateFeatureGoalFn;
|
|
27
|
+
log?: (msg: string) => void;
|
|
28
|
+
/** Registers an unsubscribe so the registry can detach the plugin (§N2). */
|
|
29
|
+
trackUnsubscribe: (unsubscribe: () => void) => void;
|
|
30
|
+
}
|
|
31
|
+
/** Builds the goal title/objective from an external task (spec 003 §R4). */
|
|
32
|
+
export declare function externalTaskToFeatureInput(task: ExternalTask): {
|
|
33
|
+
title: string;
|
|
34
|
+
objective: string;
|
|
35
|
+
external_ref: ExternalRef;
|
|
36
|
+
};
|
|
37
|
+
/** Constructs the host handed to one plugin's `init` (spec 003 §R1). */
|
|
38
|
+
export declare function createPluginHost(deps: PluginHostDeps): PluginHost;
|
|
39
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/plugins/host.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,IAAI,EACJ,UAAU,EAIX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACnC,KACE,IAAI,CAAC;AAEV,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,4EAA4E;IAC5E,gBAAgB,EAAE,CAAC,WAAW,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACrD;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,YAAY,GAAG;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,WAAW,CAAC;CAC3B,CAsBA;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAiFjE"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginHost implementation (spec 003 §R1, §R4, §R6): the narrow, safe facade
|
|
3
|
+
* a plugin gets at `init`. Every callback a plugin registers is exception-
|
|
4
|
+
* isolated (§N2) and every subscription is tracked so `system.close()` can
|
|
5
|
+
* detach plugins before the store closes.
|
|
6
|
+
*/
|
|
7
|
+
import { redactString, redactValue } from "../safety/redact.js";
|
|
8
|
+
/** Builds the goal title/objective from an external task (spec 003 §R4). */
|
|
9
|
+
export function externalTaskToFeatureInput(task) {
|
|
10
|
+
const titleLine = `${task.identifier}: ${task.title}`;
|
|
11
|
+
// Same truncation limit as the feature flow (spec 002 §R4).
|
|
12
|
+
const title = titleLine.length > 80 ? titleLine.slice(0, 77) + "…" : titleLine;
|
|
13
|
+
const objective = [
|
|
14
|
+
titleLine,
|
|
15
|
+
"",
|
|
16
|
+
...(task.description.trim() ? [task.description.trim(), ""] : []),
|
|
17
|
+
`Origin: ${task.url}`,
|
|
18
|
+
].join("\n");
|
|
19
|
+
return {
|
|
20
|
+
title,
|
|
21
|
+
objective,
|
|
22
|
+
external_ref: {
|
|
23
|
+
provider: task.provider,
|
|
24
|
+
id: task.id,
|
|
25
|
+
identifier: task.identifier,
|
|
26
|
+
url: task.url,
|
|
27
|
+
title: task.title,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Constructs the host handed to one plugin's `init` (spec 003 §R1). */
|
|
32
|
+
export function createPluginHost(deps) {
|
|
33
|
+
const actor = `plugin:${deps.pluginName}`;
|
|
34
|
+
const log = deps.log ?? ((msg) => console.log(msg));
|
|
35
|
+
const audit = (action, detail) => {
|
|
36
|
+
deps.audit.record({
|
|
37
|
+
ts: new Date().toISOString(),
|
|
38
|
+
actor,
|
|
39
|
+
run_id: null,
|
|
40
|
+
task_id: null,
|
|
41
|
+
session_id: null,
|
|
42
|
+
kind: "plugin",
|
|
43
|
+
tool_name: null,
|
|
44
|
+
tool_input_hash: null,
|
|
45
|
+
tool_input: null,
|
|
46
|
+
decision: null,
|
|
47
|
+
rule_id: null,
|
|
48
|
+
detail: redactValue({ action, detail: detail ?? null }),
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
log: (msg) => log(`[${actor}] ${redactString(msg)}`),
|
|
53
|
+
audit,
|
|
54
|
+
reportSync: (action, info) => {
|
|
55
|
+
audit(action, {
|
|
56
|
+
ref: info.ref ?? null,
|
|
57
|
+
ok: info.ok,
|
|
58
|
+
detail: info.detail ?? null,
|
|
59
|
+
run_id: info.run_id ?? null,
|
|
60
|
+
});
|
|
61
|
+
deps.bus.publish({
|
|
62
|
+
run_id: info.run_id ?? null,
|
|
63
|
+
type: "plugin.sync",
|
|
64
|
+
payload: {
|
|
65
|
+
plugin: deps.pluginName,
|
|
66
|
+
action,
|
|
67
|
+
...(info.ref ? { ref: info.ref } : {}),
|
|
68
|
+
ok: info.ok,
|
|
69
|
+
...(info.detail ? { detail: redactString(info.detail) } : {}),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
getSecret: (key) => deps.secrets.get(key),
|
|
74
|
+
settings: deps.settings,
|
|
75
|
+
subscribe: (fn) => {
|
|
76
|
+
// A plugin callback must never break bus delivery or the dispatcher
|
|
77
|
+
// (§N2). The bus already isolates subscriber exceptions; this keeps the
|
|
78
|
+
// guarantee even if that ever changes, and scopes the error to the actor.
|
|
79
|
+
const unsubscribe = deps.bus.subscribe((event) => {
|
|
80
|
+
try {
|
|
81
|
+
fn(event);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
audit("subscriber_error", {
|
|
85
|
+
event_type: event.type,
|
|
86
|
+
error: err instanceof Error ? err.message : String(err),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
deps.trackUnsubscribe(unsubscribe);
|
|
91
|
+
return unsubscribe;
|
|
92
|
+
},
|
|
93
|
+
listProjects: async () => deps.store.listProjects(),
|
|
94
|
+
getGoal: async (id) => deps.store.getGoal(id),
|
|
95
|
+
getRun: async (id) => deps.store.getRun(id),
|
|
96
|
+
listScopesByGoal: async (goalId) => deps.store.listScopesByGoal(goalId),
|
|
97
|
+
createGoalFromExternalTask: async (projectId, task) => {
|
|
98
|
+
const input = externalTaskToFeatureInput(task);
|
|
99
|
+
const goal = deps.createFeatureGoal(projectId, input);
|
|
100
|
+
audit("goal_imported", {
|
|
101
|
+
goal_id: goal.id,
|
|
102
|
+
project_id: projectId,
|
|
103
|
+
ref: input.external_ref,
|
|
104
|
+
});
|
|
105
|
+
return goal;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/plugins/host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin registry + loader (spec 003 §R3): reads `<stateDir>/plugins.json`,
|
|
3
|
+
* dynamically imports each declared module (absolute path or builtin alias),
|
|
4
|
+
* validates its manifest, and initializes it with a {@link PluginHost}. A
|
|
5
|
+
* malformed file or a plugin that throws on import/init is contained — it is
|
|
6
|
+
* marked `status: "error"` and the orchestrator boots regardless (§N2).
|
|
7
|
+
* Nothing loads unless the operator wrote it into `plugins.json` (§11 trust
|
|
8
|
+
* model). Tests inject fake modules via `pluginModules` — no dynamic import.
|
|
9
|
+
*/
|
|
10
|
+
import { type ExternalTask, type Goal, type OrcPluginModule, type PluginStatus, type TaskProvider } from "@orc-brain/shared";
|
|
11
|
+
import type { Store } from "../store/index.js";
|
|
12
|
+
import type { AuditSink } from "../safety/index.js";
|
|
13
|
+
import type { EventBus } from "../eventBus.js";
|
|
14
|
+
import type { SecretStore } from "./secrets.js";
|
|
15
|
+
import { type CreateFeatureGoalFn } from "./host.js";
|
|
16
|
+
/** Dependencies for {@link PluginRegistry}. */
|
|
17
|
+
export interface PluginRegistryDeps {
|
|
18
|
+
store: Store;
|
|
19
|
+
bus: EventBus;
|
|
20
|
+
audit: AuditSink;
|
|
21
|
+
secrets: SecretStore;
|
|
22
|
+
createFeatureGoal: CreateFeatureGoalFn;
|
|
23
|
+
/** Path to the declarations file; missing file means no plugins. */
|
|
24
|
+
pluginsFile: string;
|
|
25
|
+
/** Injectable modules keyed by declaration name (tests; spec 003 §R3). */
|
|
26
|
+
modules?: Record<string, OrcPluginModule>;
|
|
27
|
+
log?: (msg: string) => void;
|
|
28
|
+
}
|
|
29
|
+
/** Loads, tracks, and exposes declared plugins (spec 003 §R3). */
|
|
30
|
+
export declare class PluginRegistry {
|
|
31
|
+
private readonly deps;
|
|
32
|
+
/** Resolves when every declared plugin has loaded or errored. */
|
|
33
|
+
readonly ready: Promise<void>;
|
|
34
|
+
private readonly entries;
|
|
35
|
+
private readonly log;
|
|
36
|
+
constructor(deps: PluginRegistryDeps);
|
|
37
|
+
/** Audits a registry-level (not per-plugin) failure and logs it. */
|
|
38
|
+
private fileError;
|
|
39
|
+
/** Parses `plugins.json`; a malformed file yields no plugins, not a crash. */
|
|
40
|
+
private readDeclarations;
|
|
41
|
+
/** Resolves a declaration to a plugin module (injected → builtin → path). */
|
|
42
|
+
private resolveModule;
|
|
43
|
+
private loadAll;
|
|
44
|
+
/** Imports, validates, and initializes one enabled plugin (spec 003 §R3). */
|
|
45
|
+
private loadOne;
|
|
46
|
+
/** Load status of every declared plugin (`GET /api/plugins`, §R3). */
|
|
47
|
+
list(): PluginStatus[];
|
|
48
|
+
private activeEntry;
|
|
49
|
+
/** The named plugin's task provider, when active and capable (§R3, §R7). */
|
|
50
|
+
getTaskProvider(name: string): TaskProvider | null;
|
|
51
|
+
/** Active plugins exposing the `task-provider` capability (§R7). */
|
|
52
|
+
listTaskProviders(): Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
capabilities: string[];
|
|
55
|
+
}>;
|
|
56
|
+
/** True when a declared plugin key exists (for the secrets route, §R8). */
|
|
57
|
+
has(name: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Imports an external task as a goal through the plugin's own host (so the
|
|
60
|
+
* audit actor is the plugin), then fires the plugin's `onTaskImported` hook
|
|
61
|
+
* fire-and-forget (spec 003 §R7, §R12 import comment).
|
|
62
|
+
*/
|
|
63
|
+
importTask(name: string, task: ExternalTask, projectId: string): Promise<Goal>;
|
|
64
|
+
/**
|
|
65
|
+
* Detaches every plugin: unsubscribes bus listeners synchronously (so no
|
|
66
|
+
* late callback touches a closed store) and fires `close` hooks
|
|
67
|
+
* fire-and-forget (spec 003 §R3; called from `system.close()`).
|
|
68
|
+
*/
|
|
69
|
+
closeAll(): void;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/plugins/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,IAAI,EAET,KAAK,eAAe,EAGpB,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAmBvE,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,kEAAkE;AAClE,qBAAa,cAAc;IAMb,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;gBAEf,IAAI,EAAE,kBAAkB;IAQrD,oEAAoE;IACpE,OAAO,CAAC,SAAS;IAkBjB,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB;IA0CxB,6EAA6E;YAC/D,aAAa;YAmBb,OAAO;IA4BrB,6EAA6E;YAC/D,OAAO;IAoDrB,sEAAsE;IACtE,IAAI,IAAI,YAAY,EAAE;IAWtB,OAAO,CAAC,WAAW;IAOnB,4EAA4E;IAC5E,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IASlD,oEAAoE;IACpE,iBAAiB,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAcpE,2EAA2E;IAC3E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;;;;OAIG;IACG,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,YAAY,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAkBhB;;;;OAIG;IACH,QAAQ,IAAI,IAAI;CAajB"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin registry + loader (spec 003 §R3): reads `<stateDir>/plugins.json`,
|
|
3
|
+
* dynamically imports each declared module (absolute path or builtin alias),
|
|
4
|
+
* validates its manifest, and initializes it with a {@link PluginHost}. A
|
|
5
|
+
* malformed file or a plugin that throws on import/init is contained — it is
|
|
6
|
+
* marked `status: "error"` and the orchestrator boots regardless (§N2).
|
|
7
|
+
* Nothing loads unless the operator wrote it into `plugins.json` (§11 trust
|
|
8
|
+
* model). Tests inject fake modules via `pluginModules` — no dynamic import.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { isAbsolute } from "node:path";
|
|
12
|
+
import { pathToFileURL } from "node:url";
|
|
13
|
+
import { PLUGIN_API_VERSION, } from "@orc-brain/shared";
|
|
14
|
+
import { registerStrippedEnvKeys } from "../spawnEnv.js";
|
|
15
|
+
import { registerSecretValue } from "../safety/redact.js";
|
|
16
|
+
import { createPluginHost } from "./host.js";
|
|
17
|
+
/** Builtin aliases resolvable without an absolute path (spec 003 §R3, §R13). */
|
|
18
|
+
const BUILTIN_SPECIFIERS = {
|
|
19
|
+
linear: "@orc-brain/plugin-linear",
|
|
20
|
+
};
|
|
21
|
+
const KEBAB_RE = /^[a-z][a-z0-9-]*$/;
|
|
22
|
+
/** Loads, tracks, and exposes declared plugins (spec 003 §R3). */
|
|
23
|
+
export class PluginRegistry {
|
|
24
|
+
deps;
|
|
25
|
+
/** Resolves when every declared plugin has loaded or errored. */
|
|
26
|
+
ready;
|
|
27
|
+
entries = [];
|
|
28
|
+
log;
|
|
29
|
+
constructor(deps) {
|
|
30
|
+
this.deps = deps;
|
|
31
|
+
this.log = deps.log ?? ((msg) => console.warn(msg));
|
|
32
|
+
this.ready = this.loadAll().catch((err) => {
|
|
33
|
+
// loadAll contains per-plugin errors; this catches only file-level ones.
|
|
34
|
+
this.fileError(err instanceof Error ? err.message : String(err));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Audits a registry-level (not per-plugin) failure and logs it. */
|
|
38
|
+
fileError(message) {
|
|
39
|
+
this.log(`orc: plugins.json error — ${message}`);
|
|
40
|
+
this.deps.audit.record({
|
|
41
|
+
ts: new Date().toISOString(),
|
|
42
|
+
actor: "plugin-registry",
|
|
43
|
+
run_id: null,
|
|
44
|
+
task_id: null,
|
|
45
|
+
session_id: null,
|
|
46
|
+
kind: "plugin",
|
|
47
|
+
tool_name: null,
|
|
48
|
+
tool_input_hash: null,
|
|
49
|
+
tool_input: null,
|
|
50
|
+
decision: null,
|
|
51
|
+
rule_id: null,
|
|
52
|
+
detail: { action: "load_error", message },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Parses `plugins.json`; a malformed file yields no plugins, not a crash. */
|
|
56
|
+
readDeclarations() {
|
|
57
|
+
if (!existsSync(this.deps.pluginsFile))
|
|
58
|
+
return [];
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(readFileSync(this.deps.pluginsFile, "utf8"));
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
this.fileError(`not valid JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const list = parsed?.plugins;
|
|
68
|
+
if (!Array.isArray(list)) {
|
|
69
|
+
this.fileError(`expected { "plugins": [...] }`);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const out = [];
|
|
73
|
+
for (const raw of list) {
|
|
74
|
+
const d = raw;
|
|
75
|
+
if (typeof d?.name !== "string" ||
|
|
76
|
+
typeof d?.specifier !== "string" ||
|
|
77
|
+
typeof d?.enabled !== "boolean") {
|
|
78
|
+
this.fileError(`invalid entry ${JSON.stringify(raw)} — need { name, specifier, enabled }`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
out.push({
|
|
82
|
+
name: d.name,
|
|
83
|
+
specifier: d.specifier,
|
|
84
|
+
enabled: d.enabled,
|
|
85
|
+
settings: d.settings && typeof d.settings === "object"
|
|
86
|
+
? d.settings
|
|
87
|
+
: undefined,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/** Resolves a declaration to a plugin module (injected → builtin → path). */
|
|
93
|
+
async resolveModule(decl) {
|
|
94
|
+
const injected = this.deps.modules?.[decl.name];
|
|
95
|
+
if (injected)
|
|
96
|
+
return injected;
|
|
97
|
+
const builtin = BUILTIN_SPECIFIERS[decl.specifier];
|
|
98
|
+
if (builtin)
|
|
99
|
+
return (await import(builtin));
|
|
100
|
+
if (!isAbsolute(decl.specifier)) {
|
|
101
|
+
throw new Error(`specifier must be an absolute path or a builtin alias (${Object.keys(BUILTIN_SPECIFIERS).join(", ")}), got "${decl.specifier}"`);
|
|
102
|
+
}
|
|
103
|
+
return (await import(pathToFileURL(decl.specifier).href));
|
|
104
|
+
}
|
|
105
|
+
async loadAll() {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const decl of this.readDeclarations()) {
|
|
108
|
+
const entry = {
|
|
109
|
+
decl,
|
|
110
|
+
plugin: null,
|
|
111
|
+
host: null,
|
|
112
|
+
status: "error",
|
|
113
|
+
unsubscribes: [],
|
|
114
|
+
};
|
|
115
|
+
this.entries.push(entry);
|
|
116
|
+
if (!KEBAB_RE.test(decl.name)) {
|
|
117
|
+
entry.error = `plugin name must be kebab-case, got "${decl.name}"`;
|
|
118
|
+
}
|
|
119
|
+
else if (seen.has(decl.name)) {
|
|
120
|
+
entry.error = `duplicate plugin name "${decl.name}"`;
|
|
121
|
+
}
|
|
122
|
+
else if (!decl.enabled) {
|
|
123
|
+
seen.add(decl.name);
|
|
124
|
+
entry.status = "disabled";
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
seen.add(decl.name);
|
|
128
|
+
await this.loadOne(entry);
|
|
129
|
+
}
|
|
130
|
+
if (entry.error) {
|
|
131
|
+
this.fileError(`plugin "${decl.name}": ${entry.error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Imports, validates, and initializes one enabled plugin (spec 003 §R3). */
|
|
136
|
+
async loadOne(entry) {
|
|
137
|
+
const { decl } = entry;
|
|
138
|
+
try {
|
|
139
|
+
const module = await this.resolveModule(decl);
|
|
140
|
+
if (typeof module.default !== "function") {
|
|
141
|
+
throw new Error("module has no default-export plugin factory");
|
|
142
|
+
}
|
|
143
|
+
const plugin = module.default(decl.settings ?? {});
|
|
144
|
+
const manifest = plugin?.manifest;
|
|
145
|
+
if (!manifest || typeof manifest.name !== "string") {
|
|
146
|
+
throw new Error("plugin has no manifest");
|
|
147
|
+
}
|
|
148
|
+
if (manifest.apiVersion !== PLUGIN_API_VERSION) {
|
|
149
|
+
throw new Error(`plugin apiVersion ${manifest.apiVersion} does not match host ${PLUGIN_API_VERSION}`);
|
|
150
|
+
}
|
|
151
|
+
if (manifest.name !== decl.name) {
|
|
152
|
+
throw new Error(`manifest name "${manifest.name}" does not match declared name "${decl.name}"`);
|
|
153
|
+
}
|
|
154
|
+
// Secret hygiene before init (spec 003 §R5): declared keys never reach
|
|
155
|
+
// worker envs, and their current values are redacted everywhere.
|
|
156
|
+
for (const key of manifest.secrets ?? []) {
|
|
157
|
+
registerStrippedEnvKeys([key]);
|
|
158
|
+
const value = this.deps.secrets.get(key);
|
|
159
|
+
if (value)
|
|
160
|
+
registerSecretValue(value);
|
|
161
|
+
}
|
|
162
|
+
const host = createPluginHost({
|
|
163
|
+
pluginName: manifest.name,
|
|
164
|
+
settings: decl.settings ?? {},
|
|
165
|
+
store: this.deps.store,
|
|
166
|
+
bus: this.deps.bus,
|
|
167
|
+
audit: this.deps.audit,
|
|
168
|
+
secrets: this.deps.secrets,
|
|
169
|
+
createFeatureGoal: this.deps.createFeatureGoal,
|
|
170
|
+
log: this.log,
|
|
171
|
+
trackUnsubscribe: (fn) => entry.unsubscribes.push(fn),
|
|
172
|
+
});
|
|
173
|
+
await plugin.init(host);
|
|
174
|
+
entry.plugin = plugin;
|
|
175
|
+
entry.host = host;
|
|
176
|
+
entry.status = "active";
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
// A broken plugin must never take the orchestrator down (§N2).
|
|
180
|
+
for (const unsubscribe of entry.unsubscribes.splice(0))
|
|
181
|
+
unsubscribe();
|
|
182
|
+
entry.error = err instanceof Error ? err.message : String(err);
|
|
183
|
+
entry.status = "error";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Load status of every declared plugin (`GET /api/plugins`, §R3). */
|
|
187
|
+
list() {
|
|
188
|
+
return this.entries.map((e) => ({
|
|
189
|
+
name: e.decl.name,
|
|
190
|
+
version: e.plugin?.manifest.version ?? null,
|
|
191
|
+
capabilities: e.plugin?.manifest.capabilities ?? [],
|
|
192
|
+
enabled: e.decl.enabled,
|
|
193
|
+
status: e.status,
|
|
194
|
+
...(e.error ? { error: e.error } : {}),
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
activeEntry(name) {
|
|
198
|
+
return (this.entries.find((e) => e.decl.name === name && e.status === "active") ??
|
|
199
|
+
null);
|
|
200
|
+
}
|
|
201
|
+
/** The named plugin's task provider, when active and capable (§R3, §R7). */
|
|
202
|
+
getTaskProvider(name) {
|
|
203
|
+
const entry = this.activeEntry(name);
|
|
204
|
+
if (!entry?.plugin?.taskProvider)
|
|
205
|
+
return null;
|
|
206
|
+
if (!entry.plugin.manifest.capabilities.includes("task-provider")) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return entry.plugin.taskProvider;
|
|
210
|
+
}
|
|
211
|
+
/** Active plugins exposing the `task-provider` capability (§R7). */
|
|
212
|
+
listTaskProviders() {
|
|
213
|
+
return this.entries
|
|
214
|
+
.filter((e) => e.status === "active" &&
|
|
215
|
+
e.plugin?.taskProvider &&
|
|
216
|
+
e.plugin.manifest.capabilities.includes("task-provider"))
|
|
217
|
+
.map((e) => ({
|
|
218
|
+
name: e.decl.name,
|
|
219
|
+
capabilities: e.plugin.manifest.capabilities,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
/** True when a declared plugin key exists (for the secrets route, §R8). */
|
|
223
|
+
has(name) {
|
|
224
|
+
return this.entries.some((e) => e.decl.name === name);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Imports an external task as a goal through the plugin's own host (so the
|
|
228
|
+
* audit actor is the plugin), then fires the plugin's `onTaskImported` hook
|
|
229
|
+
* fire-and-forget (spec 003 §R7, §R12 import comment).
|
|
230
|
+
*/
|
|
231
|
+
async importTask(name, task, projectId) {
|
|
232
|
+
const entry = this.activeEntry(name);
|
|
233
|
+
if (!entry?.host)
|
|
234
|
+
throw new Error(`plugin "${name}" is not active`);
|
|
235
|
+
const goal = await entry.host.createGoalFromExternalTask(projectId, task);
|
|
236
|
+
const hook = entry.plugin?.onTaskImported?.bind(entry.plugin);
|
|
237
|
+
if (hook) {
|
|
238
|
+
void Promise.resolve()
|
|
239
|
+
.then(() => hook(task, goal))
|
|
240
|
+
.catch((err) => {
|
|
241
|
+
entry.host?.audit("on_task_imported_error", {
|
|
242
|
+
goal_id: goal.id,
|
|
243
|
+
error: err instanceof Error ? err.message : String(err),
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return goal;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Detaches every plugin: unsubscribes bus listeners synchronously (so no
|
|
251
|
+
* late callback touches a closed store) and fires `close` hooks
|
|
252
|
+
* fire-and-forget (spec 003 §R3; called from `system.close()`).
|
|
253
|
+
*/
|
|
254
|
+
closeAll() {
|
|
255
|
+
for (const entry of this.entries) {
|
|
256
|
+
for (const unsubscribe of entry.unsubscribes.splice(0))
|
|
257
|
+
unsubscribe();
|
|
258
|
+
const close = entry.plugin?.close?.bind(entry.plugin);
|
|
259
|
+
if (close) {
|
|
260
|
+
void Promise.resolve()
|
|
261
|
+
.then(() => close())
|
|
262
|
+
.catch(() => {
|
|
263
|
+
// Shutdown best-effort; the process is going away.
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/plugins/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based plugin secret store (spec 003 §R5): `<stateDir>/secrets.json`,
|
|
3
|
+
* mode 0600. This is the only credential home in orc-brain — model-provider
|
|
4
|
+
* keys stay banned (they are stripped/refused elsewhere); this store exists
|
|
5
|
+
* for plugin (non-model) secrets like `LINEAR_API_KEY`. Resolution order for
|
|
6
|
+
* reads: secrets file, then `process.env` fallback. Values never appear in
|
|
7
|
+
* `listKeys`, logs, or errors.
|
|
8
|
+
*/
|
|
9
|
+
/** Reads/writes `<stateDir>/secrets.json` with 0600 enforcement (§R5). */
|
|
10
|
+
export declare class SecretStore {
|
|
11
|
+
private readonly env;
|
|
12
|
+
readonly path: string;
|
|
13
|
+
private warned;
|
|
14
|
+
constructor(stateDir: string, env?: NodeJS.ProcessEnv);
|
|
15
|
+
/**
|
|
16
|
+
* A pre-existing file that is group/world-readable is refused — reads
|
|
17
|
+
* ignore it and writes throw until the operator fixes the mode (§R5,
|
|
18
|
+
* spec 003 §8 edge cases).
|
|
19
|
+
*/
|
|
20
|
+
private insecureMode;
|
|
21
|
+
private readFile;
|
|
22
|
+
private writeFile;
|
|
23
|
+
/** Secrets file first, then `process.env[key]` fallback (§R5). */
|
|
24
|
+
get(key: string): string | undefined;
|
|
25
|
+
set(key: string, value: string): void;
|
|
26
|
+
unset(key: string): void;
|
|
27
|
+
/** Key names present in the file (never values). Env fallbacks not listed. */
|
|
28
|
+
listKeys(): string[];
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/plugins/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,0EAA0E;AAC1E,qBAAa,WAAW;IAMpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IALtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,MAAM,CAAS;gBAGrB,QAAQ,EAAE,MAAM,EACC,GAAG,oBAAc;IAKpC;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,QAAQ;IAqBhB,OAAO,CAAC,SAAS;IAQjB,kEAAkE;IAClE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAerC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAWxB,8EAA8E;IAC9E,QAAQ,IAAI,MAAM,EAAE;CAGrB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based plugin secret store (spec 003 §R5): `<stateDir>/secrets.json`,
|
|
3
|
+
* mode 0600. This is the only credential home in orc-brain — model-provider
|
|
4
|
+
* keys stay banned (they are stripped/refused elsewhere); this store exists
|
|
5
|
+
* for plugin (non-model) secrets like `LINEAR_API_KEY`. Resolution order for
|
|
6
|
+
* reads: secrets file, then `process.env` fallback. Values never appear in
|
|
7
|
+
* `listKeys`, logs, or errors.
|
|
8
|
+
*/
|
|
9
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
/** Env-style secret key names: SCREAMING_SNAKE_CASE. */
|
|
12
|
+
const KEY_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
13
|
+
/** Reads/writes `<stateDir>/secrets.json` with 0600 enforcement (§R5). */
|
|
14
|
+
export class SecretStore {
|
|
15
|
+
env;
|
|
16
|
+
path;
|
|
17
|
+
warned = false;
|
|
18
|
+
constructor(stateDir, env = process.env) {
|
|
19
|
+
this.env = env;
|
|
20
|
+
this.path = join(stateDir, "secrets.json");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A pre-existing file that is group/world-readable is refused — reads
|
|
24
|
+
* ignore it and writes throw until the operator fixes the mode (§R5,
|
|
25
|
+
* spec 003 §8 edge cases).
|
|
26
|
+
*/
|
|
27
|
+
insecureMode() {
|
|
28
|
+
if (!existsSync(this.path))
|
|
29
|
+
return false;
|
|
30
|
+
if (process.platform === "win32")
|
|
31
|
+
return false; // no POSIX modes
|
|
32
|
+
const mode = statSync(this.path).mode & 0o777;
|
|
33
|
+
if ((mode & 0o077) === 0)
|
|
34
|
+
return false;
|
|
35
|
+
if (!this.warned) {
|
|
36
|
+
this.warned = true;
|
|
37
|
+
console.warn(`orc: refusing ${this.path} — mode ${mode.toString(8)} is group/world-readable; run: chmod 600 ${this.path}`);
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
readFile() {
|
|
42
|
+
if (!existsSync(this.path) || this.insecureMode())
|
|
43
|
+
return {};
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(readFileSync(this.path, "utf8"));
|
|
46
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
51
|
+
if (typeof v === "string")
|
|
52
|
+
out[k] = v;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
if (!this.warned) {
|
|
58
|
+
this.warned = true;
|
|
59
|
+
console.warn(`orc: ${this.path} is not valid JSON; treating as empty`);
|
|
60
|
+
}
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
writeFile(secrets) {
|
|
65
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
66
|
+
writeFileSync(this.path, JSON.stringify(secrets, null, 2) + "\n", {
|
|
67
|
+
mode: 0o600,
|
|
68
|
+
});
|
|
69
|
+
chmodSync(this.path, 0o600); // `mode` only applies on creation
|
|
70
|
+
}
|
|
71
|
+
/** Secrets file first, then `process.env[key]` fallback (§R5). */
|
|
72
|
+
get(key) {
|
|
73
|
+
return this.readFile()[key] ?? this.env[key];
|
|
74
|
+
}
|
|
75
|
+
set(key, value) {
|
|
76
|
+
if (!KEY_RE.test(key)) {
|
|
77
|
+
throw new Error(`secret key must be SCREAMING_SNAKE_CASE (e.g. LINEAR_API_KEY), got "${key}"`);
|
|
78
|
+
}
|
|
79
|
+
if (!value)
|
|
80
|
+
throw new Error("secret value must be non-empty");
|
|
81
|
+
if (this.insecureMode()) {
|
|
82
|
+
throw new Error(`refusing to write ${this.path}: file is group/world-readable — run: chmod 600 ${this.path}`);
|
|
83
|
+
}
|
|
84
|
+
this.writeFile({ ...this.readFile(), [key]: value });
|
|
85
|
+
}
|
|
86
|
+
unset(key) {
|
|
87
|
+
if (this.insecureMode()) {
|
|
88
|
+
throw new Error(`refusing to write ${this.path}: file is group/world-readable — run: chmod 600 ${this.path}`);
|
|
89
|
+
}
|
|
90
|
+
const secrets = this.readFile();
|
|
91
|
+
delete secrets[key];
|
|
92
|
+
this.writeFile(secrets);
|
|
93
|
+
}
|
|
94
|
+
/** Key names present in the file (never values). Env fallbacks not listed. */
|
|
95
|
+
listKeys() {
|
|
96
|
+
return Object.keys(this.readFile()).sort();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=secrets.js.map
|