ruvnet-brain 3.9.134-dev → 4.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/.claude-plugin/marketplace.json +14 -0
- package/README.md +5 -5
- package/bin/install.mjs +382 -36
- package/console/CONTRACT.md +172 -0
- package/console/activity.js +753 -0
- package/console/app.js +4189 -0
- package/console/architecture.html +1221 -0
- package/console/assets/depth-1.webp +0 -0
- package/console/assets/depth-2.webp +0 -0
- package/console/assets/depth-3.webp +0 -0
- package/console/assets/harness-vs-plain.svg +259 -0
- package/console/assets/hero.webp +0 -0
- package/console/assets/memory.webp +0 -0
- package/console/assets/metaharness.svg +247 -0
- package/console/index.html +777 -0
- package/console/install-architecture.html +162 -0
- package/console/install-mockup.html +543 -0
- package/console/style.css +2144 -0
- package/console/tips.css +926 -0
- package/console/tips.html +858 -0
- package/console/tips.js +128 -0
- package/docs/RELEASE-NOTES-4.0.md +88 -0
- package/kb/model-requirements.mjs +37 -6
- package/kb/zip-extract.mjs +53 -14
- package/keys/ruvnet-brain-signing.pub.pem +3 -0
- package/package.json +14 -22
- package/plugin/.claude-plugin/marketplace.json +14 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/.codex-plugin/plugin.json +21 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/brain-console.md +16 -0
- package/plugin/commands/configure.md +33 -0
- package/plugin/commands/rvbc.md +79 -0
- package/plugin/commands/rvcb.md +16 -0
- package/plugin/commands/whats-new.md +57 -0
- package/plugin/hooks/codex-hooks.json +160 -0
- package/plugin/hooks/hook-contracts.json +77 -0
- package/plugin/hooks/hooks.json +202 -0
- package/plugin/mcp/managed-cli-interface.mjs +47 -4
- package/plugin/mcp/server.mjs +56 -6
- package/plugin/scripts/anticipate.sh +534 -0
- package/plugin/scripts/codex-hook-adapter.mjs +96 -0
- package/plugin/scripts/continuation-gate.mjs +267 -0
- package/plugin/scripts/design-wall.sh +137 -0
- package/plugin/scripts/detach.mjs +182 -0
- package/plugin/scripts/first-session-worker.mjs +38 -0
- package/plugin/scripts/gate-receipt.sh +35 -0
- package/plugin/scripts/ground-before-write.sh +199 -0
- package/plugin/scripts/ground-ruvnet.sh +517 -0
- package/plugin/scripts/grounding-stamp.sh +113 -0
- package/plugin/scripts/grounding-substance.mjs +595 -0
- package/plugin/scripts/hijack-ruvnet.sh +81 -0
- package/plugin/scripts/hook-input.mjs +558 -0
- package/plugin/scripts/hook-shim-bash.mjs +55 -0
- package/plugin/scripts/hook-shim.mjs +303 -0
- package/plugin/scripts/host-update.mjs +58 -0
- package/plugin/scripts/kling-preflight.sh +146 -0
- package/plugin/scripts/learn-capture.sh +173 -0
- package/plugin/scripts/learn-flush.mjs +155 -0
- package/plugin/scripts/lesson-hooks.sh +213 -0
- package/plugin/scripts/md-stamp.mjs +219 -0
- package/plugin/scripts/protect-brain-state.sh +84 -0
- package/plugin/scripts/route-dispatch.sh +147 -0
- package/plugin/scripts/routing-outcome-capture.mjs +89 -0
- package/plugin/scripts/runtime-preferences.mjs +269 -0
- package/plugin/scripts/session-start-core.mjs +477 -0
- package/plugin/scripts/session-start.sh +13 -0
- package/plugin/scripts/signal-watch.mjs +193 -0
- package/plugin/scripts/unprompted-runtime.mjs +377 -0
- package/plugin/scripts/update-apply.mjs +419 -0
- package/plugin/scripts/verify-interface.sh +53 -0
- package/plugin/scripts/version-bump-gate.sh +112 -0
- package/plugin/skills/brain-build/SKILL.md +123 -0
- package/plugin/skills/brain-console/SKILL.md +22 -0
- package/plugin/skills/brain-prompt/SKILL.md +83 -0
- package/plugin/skills/brain-score/SKILL.md +101 -0
- package/plugin/skills/release-proof/SKILL.md +81 -0
- package/plugin/skills/release-proof/agents/openai.yaml +4 -0
- package/plugin/skills/release-proof/references/receipt-contract.md +38 -0
- package/plugin/skills/release-proof/scripts/release-proof.mjs +210 -0
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +121 -0
- package/plugin/skills/ruvnet-brain/SKILL.md +234 -0
- package/plugin/skills/rvbc/SKILL.md +23 -0
- package/plugin/skills/savings/SKILL.md +46 -0
- package/plugin/skills/whats-new/SKILL.md +22 -0
- package/scripts/adr-backfill.mjs +107 -0
- package/scripts/advocacy-outcomes.mjs +808 -0
- package/scripts/agentdb-context.mjs +216 -0
- package/scripts/agentdb-fleet-doctor.mjs +101 -0
- package/scripts/ascii-drift.mjs +236 -0
- package/scripts/behavioral-l1-l4.mjs +210 -0
- package/scripts/brain-capability-check.mjs +72 -0
- package/scripts/brain-grade-groundtruth.mjs +100 -0
- package/scripts/brain-latency-50.mjs +227 -0
- package/scripts/brain-novice-50.mjs +189 -0
- package/scripts/brain-stamp.mjs +94 -0
- package/scripts/brain-state.mjs +212 -0
- package/scripts/build-bundle.mjs +522 -0
- package/scripts/build-concepts.mjs +132 -0
- package/scripts/build-l2.mjs +71 -0
- package/scripts/build-primer.mjs +73 -0
- package/scripts/build-symbols.mjs +68 -0
- package/scripts/calibrate-router.mjs +97 -0
- package/scripts/capability-audit.mjs +321 -0
- package/scripts/capability-registry.mjs +876 -0
- package/scripts/check-indexation.mjs +108 -0
- package/scripts/check-legibility.mjs +189 -0
- package/scripts/ci/build-fixture-kb.mjs +67 -0
- package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
- package/scripts/ci/learning-replay-recorder.mjs +59 -0
- package/scripts/ci/mutate-hook-timeout.mjs +70 -0
- package/scripts/ci/stranger-fixture-stage.mjs +17 -0
- package/scripts/ci/stranger-scenario.mjs +228 -0
- package/scripts/ci/stranger-timeout.mjs +25 -0
- package/scripts/ci-verdict.mjs +29 -0
- package/scripts/claims-verify.mjs +710 -0
- package/scripts/clear-claude-tmp.sh +31 -0
- package/scripts/console-engine.mjs +434 -0
- package/scripts/console-engine.test.mjs +125 -0
- package/scripts/corpus-qa.mjs +250 -0
- package/scripts/correction-detect-embed.mjs +346 -0
- package/scripts/correction-detect-measure.mjs +270 -0
- package/scripts/correction-detect.mjs +686 -0
- package/scripts/count-chunks.mjs +54 -0
- package/scripts/described-questions.json +30 -0
- package/scripts/design-grade.mjs +58 -0
- package/scripts/dev-plugin-link.sh +105 -0
- package/scripts/distill-project.mjs +200 -0
- package/scripts/doc-currency.mjs +801 -0
- package/scripts/eval-brain.mjs +244 -0
- package/scripts/fix-metaharness-memretrieve.mjs +121 -0
- package/scripts/full-hints.mjs +87 -0
- package/scripts/gate.sh +39 -0
- package/scripts/gates.mjs +146 -0
- package/scripts/gen-console-images.mjs +54 -0
- package/scripts/gen-images.mjs +47 -0
- package/scripts/git-clone-refresh.mjs +52 -0
- package/scripts/git-hooks/pre-push +126 -0
- package/scripts/goal-match.mjs +398 -0
- package/scripts/goldie-research.mjs +223 -0
- package/scripts/goldie-weekly.sh +67 -0
- package/scripts/health-repair.mjs +250 -0
- package/scripts/helix-scenario-questions.json +10 -0
- package/scripts/ingest-gists.mjs +230 -0
- package/scripts/ingest-meeting.mjs +115 -0
- package/scripts/ingest-repo.mjs +79 -0
- package/scripts/install-npx-witness.sh +49 -0
- package/scripts/issue-fix.mjs +639 -0
- package/scripts/issue-watch.mjs +276 -0
- package/scripts/issue4-close-note.md +31 -0
- package/scripts/key-canary.mjs +91 -0
- package/scripts/latency-to-surface.mjs +233 -0
- package/scripts/learning-enable.mjs +380 -0
- package/scripts/learning-replay.mjs +1570 -0
- package/scripts/learnings.mjs +62 -0
- package/scripts/lesson-gate.mjs +680 -0
- package/scripts/lesson-lifecycle.mjs +449 -0
- package/scripts/lesson-promote.mjs +262 -0
- package/scripts/lesson-ratify.mjs +98 -0
- package/scripts/lesson-seed.mjs +252 -0
- package/scripts/lesson-store.mjs +447 -0
- package/scripts/loop-checkpoint.mjs +86 -0
- package/scripts/memdb-health.sh +14 -0
- package/scripts/memory-doctor.mjs +271 -0
- package/scripts/model-catalog.mjs +79 -0
- package/scripts/nightly-controller.mjs +66 -0
- package/scripts/nightly-gists.sh +72 -0
- package/scripts/nightly-wrapper.sh +180 -0
- package/scripts/notify.sh +12 -0
- package/scripts/npx-witness.sh +56 -0
- package/scripts/onboarding-console.mjs +2749 -0
- package/scripts/private-fence.mjs +69 -0
- package/scripts/proactivity-metrics.mjs +118 -0
- package/scripts/proof-questions.json +56 -0
- package/scripts/prove.mjs +95 -0
- package/scripts/proxy/claude-proxied.sh +57 -0
- package/scripts/proxy/proxy-revert.sh +59 -0
- package/scripts/proxy/proxy-up.sh +60 -0
- package/scripts/proxy/proxy-verify.mjs +142 -0
- package/scripts/published-surface-probe.mjs +241 -0
- package/scripts/qe/card-lane-gate.mjs +162 -0
- package/scripts/qe/session-start-gate.mjs +229 -0
- package/scripts/qe/ux-suite.mjs +323 -0
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/record-lesson.mjs +113 -0
- package/scripts/refresh-model-catalog.mjs +99 -0
- package/scripts/release-proof.mjs +9 -0
- package/scripts/release-vector.mjs +281 -0
- package/scripts/release.mjs +395 -0
- package/scripts/remedy-registry.mjs +247 -0
- package/scripts/rerank-cap-eval.mjs +265 -0
- package/scripts/rerank-cap-warm-ab.mjs +129 -0
- package/scripts/route-cheap.mjs +20 -15
- package/scripts/router-utilization.mjs +182 -0
- package/scripts/routing-flywheel.mjs +596 -0
- package/scripts/rvf-generation.mjs +104 -0
- package/scripts/rvf-index-audit.mjs +138 -0
- package/scripts/self-update.mjs +508 -0
- package/scripts/selfcheck.mjs +7 -1
- package/scripts/sign-bundle.mjs +69 -0
- package/scripts/signal-watch.mjs +171 -0
- package/scripts/stack-sync.mjs +469 -0
- package/scripts/stamp-existing-rvf-generations.mjs +53 -0
- package/scripts/stamp-sweep.mjs +144 -0
- package/scripts/status-honesty.mjs +102 -0
- package/scripts/sync-version.mjs +217 -0
- package/scripts/token-report.mjs +102 -0
- package/scripts/top100-benchmark.mjs +479 -0
- package/scripts/top100-corpus.mjs +112 -0
- package/scripts/top100-semantic-assertions.mjs +449 -0
- package/scripts/update-apply.mjs +9 -0
- package/scripts/upgrade-notice.mjs +14 -0
- package/scripts/verify-bundle.mjs +51 -0
- package/scripts/verify-channels.mjs +184 -0
- package/scripts/verify-model-catalog.mjs +104 -0
- package/scripts/verify-nightly-close-issue4.sh +31 -0
- package/scripts/version.mjs +40 -0
- package/scripts/wired-check.mjs +864 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// runtime-preferences.mjs — one policy boundary for every live Brain control.
|
|
3
|
+
//
|
|
4
|
+
// The console records intent. Runtime code asks this module what is allowed. Keeping that read in
|
|
5
|
+
// one place prevents routing, QE, learning, and first-open initialization from each inventing a
|
|
6
|
+
// subtly different default. Secrets are deliberately separate: ordinary settings and their backup
|
|
7
|
+
// journal must never contain an API key.
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_HOME = os.homedir();
|
|
15
|
+
const PROJECT_FILE = '.swarm/ruvnet-brain-settings.json';
|
|
16
|
+
const PROJECT_KEYS = Object.freeze([
|
|
17
|
+
'routing',
|
|
18
|
+
'qeFleet',
|
|
19
|
+
'learningScope',
|
|
20
|
+
'autoApply',
|
|
21
|
+
'advocacy',
|
|
22
|
+
'provider',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const readJSON = (file) => {
|
|
26
|
+
try {
|
|
27
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
28
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function preferencePaths({ env = process.env, cwd = process.cwd() } = {}) {
|
|
35
|
+
const home = env.HOME || DEFAULT_HOME;
|
|
36
|
+
return {
|
|
37
|
+
config: env.RUVNET_BRAIN_CONFIG_FILE
|
|
38
|
+
|| path.join(home, '.claude', 'ruvnet-brain', 'config.json'),
|
|
39
|
+
settings: env.RUVNET_SETTINGS_FILE
|
|
40
|
+
|| path.join(home, '.config', 'ruvnet-brain', 'settings.json'),
|
|
41
|
+
secrets: env.RUVNET_BRAIN_SECRETS_FILE
|
|
42
|
+
|| path.join(home, '.config', 'ruvnet-brain', 'secrets.enc.json'),
|
|
43
|
+
project: env.RUVNET_BRAIN_PROJECT_SETTINGS_FILE
|
|
44
|
+
|| path.join(cwd, PROJECT_FILE),
|
|
45
|
+
ageIdentity: env.SOPS_AGE_KEY_FILE
|
|
46
|
+
|| path.join(home, '.config', 'sops', 'age', 'keys.txt'),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function validConfig(raw = {}) {
|
|
51
|
+
return {
|
|
52
|
+
provider: ['auto', 'anthropic', 'openai', 'codex', 'google', 'xai'].includes(raw.provider)
|
|
53
|
+
? raw.provider : null,
|
|
54
|
+
routing: raw.routing === 'auto' || raw.routing === 'off' ? raw.routing : null,
|
|
55
|
+
qeFleet: typeof raw.qeFleet === 'boolean' ? raw.qeFleet : null,
|
|
56
|
+
nightly: typeof raw.nightly === 'boolean' ? raw.nightly : null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validSettings(raw = {}) {
|
|
61
|
+
const source = raw.settings && typeof raw.settings === 'object' ? raw.settings : raw;
|
|
62
|
+
return {
|
|
63
|
+
learningScope: ['off', 'project', 'user'].includes(source.learningScope)
|
|
64
|
+
? source.learningScope : 'project',
|
|
65
|
+
autoApply: source.autoApply === true,
|
|
66
|
+
newProjectDefaults: source.newProjectDefaults === true,
|
|
67
|
+
advocacy: Number.isInteger(source.advocacy) && source.advocacy >= 1 && source.advocacy <= 5
|
|
68
|
+
? source.advocacy : 3,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validProject(raw = {}) {
|
|
73
|
+
const out = {};
|
|
74
|
+
if (raw.routing === 'auto' || raw.routing === 'off') out.routing = raw.routing;
|
|
75
|
+
if (typeof raw.qeFleet === 'boolean') out.qeFleet = raw.qeFleet;
|
|
76
|
+
if (['off', 'project', 'user'].includes(raw.learningScope)) out.learningScope = raw.learningScope;
|
|
77
|
+
if (typeof raw.autoApply === 'boolean') out.autoApply = raw.autoApply;
|
|
78
|
+
if (Number.isInteger(raw.advocacy) && raw.advocacy >= 1 && raw.advocacy <= 5) out.advocacy = raw.advocacy;
|
|
79
|
+
if (['auto', 'anthropic', 'openai', 'codex', 'google', 'xai'].includes(raw.provider)) out.provider = raw.provider;
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function loadRuntimePreferences(options = {}) {
|
|
84
|
+
const paths = preferencePaths(options);
|
|
85
|
+
const config = validConfig(readJSON(paths.config) || {});
|
|
86
|
+
const settings = validSettings(readJSON(paths.settings) || {});
|
|
87
|
+
const projectRaw = readJSON(paths.project);
|
|
88
|
+
const project = projectRaw ? validProject(projectRaw.values || projectRaw) : {};
|
|
89
|
+
return {
|
|
90
|
+
values: { ...config, ...settings, ...project },
|
|
91
|
+
chosen: {
|
|
92
|
+
routing: config.routing !== null || Object.hasOwn(project, 'routing'),
|
|
93
|
+
qeFleet: config.qeFleet !== null || Object.hasOwn(project, 'qeFleet'),
|
|
94
|
+
nightly: config.nightly !== null,
|
|
95
|
+
},
|
|
96
|
+
paths,
|
|
97
|
+
projectInherited: !!projectRaw,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function commandExists(command, env) {
|
|
102
|
+
const probe = spawnSync(command, ['--version'], {
|
|
103
|
+
env,
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
shell: false,
|
|
106
|
+
timeout: 3000,
|
|
107
|
+
});
|
|
108
|
+
return !probe.error && probe.status === 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function decryptSecrets(paths, env) {
|
|
112
|
+
if (!fs.existsSync(paths.secrets) || !commandExists('sops', env)) return {};
|
|
113
|
+
const run = spawnSync('sops', ['decrypt', '--input-type', 'json', '--output-type', 'json', paths.secrets], {
|
|
114
|
+
env,
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
shell: false,
|
|
117
|
+
timeout: 10_000,
|
|
118
|
+
});
|
|
119
|
+
if (run.status !== 0) return {};
|
|
120
|
+
return readJSONString(run.stdout);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readJSONString(text) {
|
|
124
|
+
try {
|
|
125
|
+
const value = JSON.parse(text);
|
|
126
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
127
|
+
} catch {
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function openRouterCredentialStatus(options = {}) {
|
|
133
|
+
const env = options.env || process.env;
|
|
134
|
+
if (typeof env.OPENROUTER_API_KEY === 'string' && env.OPENROUTER_API_KEY.trim()) {
|
|
135
|
+
return { configured: true, source: 'environment', legacyPlaintext: false };
|
|
136
|
+
}
|
|
137
|
+
const paths = preferencePaths(options);
|
|
138
|
+
const encrypted = decryptSecrets(paths, env);
|
|
139
|
+
if (typeof encrypted.OPENROUTER_API_KEY === 'string' && encrypted.OPENROUTER_API_KEY.trim()) {
|
|
140
|
+
return { configured: true, source: 'sops-age', legacyPlaintext: false };
|
|
141
|
+
}
|
|
142
|
+
const legacy = readJSON(paths.config);
|
|
143
|
+
return {
|
|
144
|
+
configured: typeof legacy?.openrouterKey === 'string' && legacy.openrouterKey.trim().length > 8,
|
|
145
|
+
source: legacy?.openrouterKey ? 'legacy-plaintext' : null,
|
|
146
|
+
legacyPlaintext: !!legacy?.openrouterKey,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function runtimeChildEnv(options = {}) {
|
|
151
|
+
const env = { ...(options.env || process.env) };
|
|
152
|
+
if (typeof env.OPENROUTER_API_KEY === 'string' && env.OPENROUTER_API_KEY.trim()) return env;
|
|
153
|
+
const paths = preferencePaths({ ...options, env });
|
|
154
|
+
const encrypted = decryptSecrets(paths, env);
|
|
155
|
+
if (typeof encrypted.OPENROUTER_API_KEY === 'string' && encrypted.OPENROUTER_API_KEY.trim()) {
|
|
156
|
+
env.OPENROUTER_API_KEY = encrypted.OPENROUTER_API_KEY.trim();
|
|
157
|
+
return env;
|
|
158
|
+
}
|
|
159
|
+
// Read-only migration compatibility. The legacy value is consumed but never copied into a new
|
|
160
|
+
// file or returned to the caller; the console marks it for migration.
|
|
161
|
+
const legacy = readJSON(paths.config);
|
|
162
|
+
if (typeof legacy?.openrouterKey === 'string' && legacy.openrouterKey.trim()) {
|
|
163
|
+
env.OPENROUTER_API_KEY = legacy.openrouterKey.trim();
|
|
164
|
+
}
|
|
165
|
+
return env;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ageRecipient(identityFile, env) {
|
|
169
|
+
if (!fs.existsSync(identityFile) || !commandExists('age-keygen', env)) return null;
|
|
170
|
+
const run = spawnSync('age-keygen', ['-y', identityFile], {
|
|
171
|
+
env,
|
|
172
|
+
encoding: 'utf8',
|
|
173
|
+
shell: false,
|
|
174
|
+
timeout: 5000,
|
|
175
|
+
});
|
|
176
|
+
return run.status === 0 ? run.stdout.trim() : null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function saveOpenRouterCredential(secret, options = {}) {
|
|
180
|
+
if (typeof secret !== 'string' || secret.trim().length < 9) {
|
|
181
|
+
return { ok: false, log: 'OpenRouter key was not saved — enter a complete key.' };
|
|
182
|
+
}
|
|
183
|
+
const env = options.env || process.env;
|
|
184
|
+
const paths = preferencePaths({ ...options, env });
|
|
185
|
+
if (!commandExists('sops', env) || !commandExists('age-keygen', env)) {
|
|
186
|
+
return { ok: false, log: 'OpenRouter key was not saved — SOPS and age are required for encrypted storage. OPENROUTER_API_KEY in your environment remains supported.' };
|
|
187
|
+
}
|
|
188
|
+
const recipient = ageRecipient(paths.ageIdentity, env);
|
|
189
|
+
if (!recipient) {
|
|
190
|
+
return { ok: false, log: 'OpenRouter key was not saved — no SOPS age identity is configured. Set SOPS_AGE_KEY_FILE or use OPENROUTER_API_KEY.' };
|
|
191
|
+
}
|
|
192
|
+
const input = JSON.stringify({ OPENROUTER_API_KEY: secret.trim() });
|
|
193
|
+
const run = spawnSync('sops', [
|
|
194
|
+
'encrypt',
|
|
195
|
+
'--input-type', 'json',
|
|
196
|
+
'--output-type', 'json',
|
|
197
|
+
'--age', recipient,
|
|
198
|
+
'/dev/stdin',
|
|
199
|
+
], {
|
|
200
|
+
env,
|
|
201
|
+
input,
|
|
202
|
+
encoding: 'utf8',
|
|
203
|
+
shell: false,
|
|
204
|
+
timeout: 10_000,
|
|
205
|
+
});
|
|
206
|
+
if (run.status !== 0 || !run.stdout.trim()) {
|
|
207
|
+
return { ok: false, log: 'OpenRouter key was not saved — SOPS encryption failed. The plaintext was not written to disk.' };
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
fs.mkdirSync(path.dirname(paths.secrets), { recursive: true, mode: 0o700 });
|
|
211
|
+
let backup = null;
|
|
212
|
+
if (fs.existsSync(paths.secrets)) {
|
|
213
|
+
backup = `${paths.secrets}.bak-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
|
214
|
+
fs.copyFileSync(paths.secrets, backup, fs.constants.COPYFILE_EXCL);
|
|
215
|
+
fs.chmodSync(backup, 0o600);
|
|
216
|
+
}
|
|
217
|
+
const tmp = `${paths.secrets}.tmp-${process.pid}-${Date.now()}`;
|
|
218
|
+
fs.writeFileSync(tmp, run.stdout, { flag: 'wx', mode: 0o600 });
|
|
219
|
+
fs.renameSync(tmp, paths.secrets);
|
|
220
|
+
fs.chmodSync(paths.secrets, 0o600);
|
|
221
|
+
return {
|
|
222
|
+
ok: true,
|
|
223
|
+
log: 'OpenRouter key saved with SOPS+age encryption.',
|
|
224
|
+
path: paths.secrets,
|
|
225
|
+
backup,
|
|
226
|
+
existed: backup !== null,
|
|
227
|
+
};
|
|
228
|
+
} catch {
|
|
229
|
+
return { ok: false, log: 'OpenRouter key was encrypted but could not be saved. No plaintext file was created.' };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function seedProjectDefaults(options = {}) {
|
|
234
|
+
const state = loadRuntimePreferences(options);
|
|
235
|
+
if (!state.values.newProjectDefaults) return { ok: true, action: 'disabled', path: state.paths.project };
|
|
236
|
+
const cwd = options.cwd || process.cwd();
|
|
237
|
+
const looksLikeProject = ['.git', 'package.json', 'Cargo.toml', 'pyproject.toml', 'go.mod']
|
|
238
|
+
.some((name) => fs.existsSync(path.join(cwd, name)));
|
|
239
|
+
if (!looksLikeProject && options.env?.RUVNET_ALLOW_NONPROJECT_DEFAULTS !== '1') {
|
|
240
|
+
return { ok: true, action: 'not-a-project', path: state.paths.project };
|
|
241
|
+
}
|
|
242
|
+
if (fs.existsSync(state.paths.project)) return { ok: true, action: 'already-initialized', path: state.paths.project };
|
|
243
|
+
const values = {};
|
|
244
|
+
for (const key of PROJECT_KEYS) {
|
|
245
|
+
const value = state.values[key];
|
|
246
|
+
if (value !== null && value !== undefined) values[key] = value;
|
|
247
|
+
}
|
|
248
|
+
const payload = {
|
|
249
|
+
version: 1,
|
|
250
|
+
inheritedAt: new Date().toISOString(),
|
|
251
|
+
source: 'user-defaults',
|
|
252
|
+
values,
|
|
253
|
+
};
|
|
254
|
+
try {
|
|
255
|
+
fs.mkdirSync(path.dirname(state.paths.project), { recursive: true, mode: 0o700 });
|
|
256
|
+
fs.writeFileSync(state.paths.project, `${JSON.stringify(payload, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
257
|
+
return { ok: true, action: 'created', path: state.paths.project, values };
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error?.code === 'EEXIST') return { ok: true, action: 'already-initialized', path: state.paths.project };
|
|
260
|
+
return { ok: false, action: 'failed', path: state.paths.project, error: error?.message || String(error) };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (process.argv.includes('--learning-scope')) {
|
|
265
|
+
process.stdout.write(`${loadRuntimePreferences().values.learningScope}\n`);
|
|
266
|
+
} else if (process.argv.includes('--seed-project')) {
|
|
267
|
+
const result = seedProjectDefaults();
|
|
268
|
+
if (!result.ok) process.exitCode = 1;
|
|
269
|
+
}
|