dual-brain 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dual-brain.mjs +159 -82
- package/package.json +1 -1
- package/src/awareness.mjs +35 -6
- package/src/dispatch.mjs +0 -1
- package/src/doctor.mjs +6 -6
- package/src/health.mjs +3 -1
- package/src/pipeline.mjs +2 -2
- package/src/profile.mjs +39 -124
- package/src/replit.mjs +1 -1
package/bin/dual-brain.mjs
CHANGED
|
@@ -946,9 +946,7 @@ async function cmdStatus(args = []) {
|
|
|
946
946
|
const archive = replit.getSessionArchive(cwd);
|
|
947
947
|
const archiveCount = Array.isArray(archive) ? archive.length : (archive?.count ?? 0);
|
|
948
948
|
console.log(` session archive: ${archiveCount} session${archiveCount !== 1 ? 's' : ''}`);
|
|
949
|
-
|
|
950
|
-
const anthropicPresent = replit.hasSecret('ANTHROPIC_API_KEY');
|
|
951
|
-
console.log(` secrets : OPENAI_API_KEY=${openaiPresent ? 'set' : 'unset'} ANTHROPIC_API_KEY=${anthropicPresent ? 'set' : 'unset'}`);
|
|
949
|
+
// Subscription-only: no API key secrets to check
|
|
952
950
|
}
|
|
953
951
|
} catch { /* replit.mjs not available or not in Replit — skip silently */ }
|
|
954
952
|
|
|
@@ -1042,43 +1040,58 @@ async function installGlobal() {
|
|
|
1042
1040
|
return;
|
|
1043
1041
|
}
|
|
1044
1042
|
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
];
|
|
1065
|
-
const postToolHooks = [
|
|
1066
|
-
{ matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'cost-logger.mjs')} ${DB_MARKER}` }] },
|
|
1067
|
-
{ matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'auto-update-wrapper.mjs')} ${DB_MARKER}` }] },
|
|
1068
|
-
];
|
|
1069
|
-
|
|
1070
|
-
// Remove any existing dual-brain hooks (idempotent)
|
|
1071
|
-
const isDBHook = (entry) => entry.hooks?.some(h => h.command?.includes(DB_MARKER));
|
|
1072
|
-
existing.hooks.PreToolUse = existing.hooks.PreToolUse.filter(e => !isDBHook(e));
|
|
1073
|
-
existing.hooks.PostToolUse = existing.hooks.PostToolUse.filter(e => !isDBHook(e));
|
|
1043
|
+
// Check if project-local hooks already exist (avoids double-firing)
|
|
1044
|
+
const projectLocalSettings = join(pkgRoot, '.claude', 'settings.local.json');
|
|
1045
|
+
const hasProjectLocalHooks = (() => {
|
|
1046
|
+
if (!existsSync(projectLocalSettings)) return false;
|
|
1047
|
+
try {
|
|
1048
|
+
const content = readFileSync(projectLocalSettings, 'utf8');
|
|
1049
|
+
return content.includes('dual-brain') || content.includes('head-guard');
|
|
1050
|
+
} catch { return false; }
|
|
1051
|
+
})();
|
|
1052
|
+
|
|
1053
|
+
if (hasProjectLocalHooks) {
|
|
1054
|
+
console.log(' hooks already configured project-locally, skipping global hooks');
|
|
1055
|
+
console.log(' (project .claude/settings.local.json already contains dual-brain hooks)');
|
|
1056
|
+
} else {
|
|
1057
|
+
// Load existing settings (merge, never clobber)
|
|
1058
|
+
let existing = {};
|
|
1059
|
+
if (existsSync(globalSettingsPath)) {
|
|
1060
|
+
try { existing = JSON.parse(readFileSync(globalSettingsPath, 'utf8')); } catch {}
|
|
1061
|
+
}
|
|
1074
1062
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1063
|
+
// Ensure hooks structure exists
|
|
1064
|
+
if (!existing.hooks) existing.hooks = {};
|
|
1065
|
+
if (!existing.hooks.PreToolUse) existing.hooks.PreToolUse = [];
|
|
1066
|
+
if (!existing.hooks.PostToolUse) existing.hooks.PostToolUse = [];
|
|
1078
1067
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1068
|
+
// Define dual-brain hooks with ownership marker
|
|
1069
|
+
const DB_MARKER = '# dual-brain-managed';
|
|
1070
|
+
const preToolHooks = [
|
|
1071
|
+
{ matcher: 'Edit', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
|
|
1072
|
+
{ matcher: 'Write', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
|
|
1073
|
+
{ matcher: 'NotebookEdit',hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
|
|
1074
|
+
{ matcher: 'Bash', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
|
|
1075
|
+
{ matcher: 'Agent', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'enforce-tier.mjs')} ${DB_MARKER}` }] },
|
|
1076
|
+
];
|
|
1077
|
+
const postToolHooks = [
|
|
1078
|
+
{ matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'cost-logger.mjs')} ${DB_MARKER}` }] },
|
|
1079
|
+
{ matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'auto-update-wrapper.mjs')} ${DB_MARKER}` }] },
|
|
1080
|
+
];
|
|
1081
|
+
|
|
1082
|
+
// Remove any existing dual-brain hooks (idempotent)
|
|
1083
|
+
const isDBHook = (entry) => entry.hooks?.some(h => h.command?.includes(DB_MARKER));
|
|
1084
|
+
existing.hooks.PreToolUse = existing.hooks.PreToolUse.filter(e => !isDBHook(e));
|
|
1085
|
+
existing.hooks.PostToolUse = existing.hooks.PostToolUse.filter(e => !isDBHook(e));
|
|
1086
|
+
|
|
1087
|
+
// Add dual-brain hooks
|
|
1088
|
+
existing.hooks.PreToolUse.push(...preToolHooks);
|
|
1089
|
+
existing.hooks.PostToolUse.push(...postToolHooks);
|
|
1090
|
+
|
|
1091
|
+
// Write merged settings
|
|
1092
|
+
mkdirSync(globalClaudeDir, { recursive: true });
|
|
1093
|
+
writeFileSync(globalSettingsPath, JSON.stringify(existing, null, 2) + '\n');
|
|
1094
|
+
}
|
|
1082
1095
|
|
|
1083
1096
|
// Write minimal global CLAUDE.md (only if none exists, or append section)
|
|
1084
1097
|
const globalClaudeMd = join(globalClaudeDir, 'CLAUDE.md');
|
|
@@ -1093,12 +1106,16 @@ async function installGlobal() {
|
|
|
1093
1106
|
}
|
|
1094
1107
|
}
|
|
1095
1108
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1109
|
+
if (!hasProjectLocalHooks) {
|
|
1110
|
+
console.log(' + dual-brain hooks installed globally');
|
|
1111
|
+
console.log(' hooks dir: ' + hooksDir);
|
|
1112
|
+
console.log(' settings: ' + globalSettingsPath);
|
|
1113
|
+
console.log('');
|
|
1114
|
+
console.log(' All new Claude sessions will load dual-brain hooks.');
|
|
1115
|
+
console.log(' Run "dual-brain uninstall --global" to remove.');
|
|
1116
|
+
}
|
|
1117
|
+
console.log(' + global CLAUDE.md updated');
|
|
1118
|
+
console.log(' path: ' + globalClaudeDir);
|
|
1102
1119
|
}
|
|
1103
1120
|
|
|
1104
1121
|
async function uninstallGlobal() {
|
|
@@ -1981,13 +1998,9 @@ function buildProviderStatusLine(profile, auth, envReport = null) {
|
|
|
1981
1998
|
const GREEN = '\x1b[32m●\x1b[0m';
|
|
1982
1999
|
const RED = '\x1b[31m●\x1b[0m';
|
|
1983
2000
|
|
|
1984
|
-
//
|
|
1985
|
-
const claudeAvailable =
|
|
1986
|
-
|
|
1987
|
-
: auth.claude.found;
|
|
1988
|
-
const openaiAvailable = envReport
|
|
1989
|
-
? envReport.secrets.OPENAI_API_KEY || auth.openai.found
|
|
1990
|
-
: auth.openai.found;
|
|
2001
|
+
// Subscription-only detection — no API key secrets
|
|
2002
|
+
const claudeAvailable = auth.claude.found;
|
|
2003
|
+
const openaiAvailable = auth.openai.found;
|
|
1991
2004
|
|
|
1992
2005
|
const claudeDot = claudeAvailable ? GREEN : RED;
|
|
1993
2006
|
const openaiDot = openaiAvailable ? GREEN : RED;
|
|
@@ -2169,6 +2182,15 @@ async function mainScreen(rl, ask) {
|
|
|
2169
2182
|
dashSpinner = fx.spinner('Loading dashboard...').start();
|
|
2170
2183
|
}
|
|
2171
2184
|
|
|
2185
|
+
// ── One-time default shell prompt for returning users (never asked before) ─
|
|
2186
|
+
if (profile.setupComplete && !profile.defaultShellAsked) {
|
|
2187
|
+
if (dashSpinner) { dashSpinner.stop(); dashSpinner = null; }
|
|
2188
|
+
const wantsDefault = await askDefaultShell(cwd, rl, fx);
|
|
2189
|
+
profile.defaultShellAsked = true;
|
|
2190
|
+
profile.isDefaultShell = wantsDefault;
|
|
2191
|
+
saveProfile(profile, { cwd });
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2172
2194
|
const claudeSub = profile?.providers?.claude;
|
|
2173
2195
|
const openaiSub = profile?.providers?.openai;
|
|
2174
2196
|
|
|
@@ -2308,13 +2330,9 @@ async function mainScreen(rl, ask) {
|
|
|
2308
2330
|
envReport = scanEnvironment(cwd);
|
|
2309
2331
|
} catch { /* non-fatal */ }
|
|
2310
2332
|
|
|
2311
|
-
// ── Studio Console: resolve provider availability
|
|
2312
|
-
const claudeAvail =
|
|
2313
|
-
|
|
2314
|
-
: auth.claude.found;
|
|
2315
|
-
const openaiAvail = envReport
|
|
2316
|
-
? envReport.secrets?.OPENAI_API_KEY || auth.openai.found
|
|
2317
|
-
: auth.openai.found;
|
|
2333
|
+
// ── Studio Console: resolve provider availability (subscription-only) ───
|
|
2334
|
+
const claudeAvail = auth.claude.found;
|
|
2335
|
+
const openaiAvail = auth.openai.found;
|
|
2318
2336
|
|
|
2319
2337
|
// ── Box 2 — Workspace: gather git data ───────────────────────────────────
|
|
2320
2338
|
let gitBranch = 'unknown';
|
|
@@ -3994,6 +4012,62 @@ function saveWizardCredentials(cwd, detectedProviders) {
|
|
|
3994
4012
|
* @param {object} rl readline interface
|
|
3995
4013
|
* @returns {object|null} profile object to save, or null if cancelled/skipped
|
|
3996
4014
|
*/
|
|
4015
|
+
function setAsDefaultShell(cwd) {
|
|
4016
|
+
const root = cwd || process.cwd();
|
|
4017
|
+
const replitPath = join(root, '.replit');
|
|
4018
|
+
if (!existsSync(replitPath)) return;
|
|
4019
|
+
|
|
4020
|
+
let content = readFileSync(replitPath, 'utf8');
|
|
4021
|
+
const newOnBoot = 'onBoot = "source /home/runner/workspace/.replit-tools/scripts/setup-claude-code.sh 2>/dev/null || true; ln -sf /home/runner/workspace/.replit-tools/.npm-persistent/.npmrc ~/.npmrc 2>/dev/null || true; dual-brain install --global 2>/dev/null || true"';
|
|
4022
|
+
|
|
4023
|
+
if (content.match(/^onBoot\s*=/m)) {
|
|
4024
|
+
content = content.replace(/^onBoot\s*=.*$/m, newOnBoot);
|
|
4025
|
+
} else {
|
|
4026
|
+
content += '\n' + newOnBoot + '\n';
|
|
4027
|
+
}
|
|
4028
|
+
writeFileSync(replitPath, content);
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
function removeAsDefaultShell(cwd) {
|
|
4032
|
+
const root = cwd || process.cwd();
|
|
4033
|
+
const replitPath = join(root, '.replit');
|
|
4034
|
+
if (!existsSync(replitPath)) return;
|
|
4035
|
+
|
|
4036
|
+
let content = readFileSync(replitPath, 'utf8');
|
|
4037
|
+
const origOnBoot = 'onBoot = "source /home/runner/workspace/.replit-tools/scripts/setup-claude-code.sh 2>/dev/null || true"';
|
|
4038
|
+
if (content.match(/^onBoot\s*=/m)) {
|
|
4039
|
+
content = content.replace(/^onBoot\s*=.*$/m, origOnBoot);
|
|
4040
|
+
writeFileSync(replitPath, content);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
async function askDefaultShell(cwd, rl, fx) {
|
|
4045
|
+
const cl = fx.colors || {};
|
|
4046
|
+
const DIM = cl.dim || '';
|
|
4047
|
+
const BOLD = cl.bold || '';
|
|
4048
|
+
const GRAY = cl.gray || '';
|
|
4049
|
+
const GREEN = cl.green || '';
|
|
4050
|
+
const RST = cl.reset || '';
|
|
4051
|
+
|
|
4052
|
+
process.stdout.write('\n');
|
|
4053
|
+
process.stdout.write(` ${BOLD}Shell startup${RST}\n\n`);
|
|
4054
|
+
process.stdout.write(` ${DIM}dual-brain can start automatically when your shell opens.${RST}\n`);
|
|
4055
|
+
process.stdout.write(` ${DIM}This modifies .replit onBoot. You can change it anytime in Settings.${RST}\n\n`);
|
|
4056
|
+
process.stdout.write(` ${GRAY}[y]${RST} Yes, set as default ${GRAY}[n]${RST} No, I'll run it manually\n\n`);
|
|
4057
|
+
|
|
4058
|
+
const answer = await new Promise(res => rl.question(' ', (a) => res(a.trim().toLowerCase())));
|
|
4059
|
+
const yes = !answer || answer.startsWith('y');
|
|
4060
|
+
|
|
4061
|
+
if (yes) {
|
|
4062
|
+
setAsDefaultShell(cwd);
|
|
4063
|
+
process.stdout.write(` ${GREEN}+${RST} ${DIM}dual-brain will start on boot. Change anytime in Settings.${RST}\n`);
|
|
4064
|
+
} else {
|
|
4065
|
+
process.stdout.write(` ${DIM}No problem. Run dual-brain anytime from the command line.${RST}\n`);
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
return yes;
|
|
4069
|
+
}
|
|
4070
|
+
|
|
3997
4071
|
async function runOnboardingWizard(_detection, cwd, rl) {
|
|
3998
4072
|
const fx = await getFx();
|
|
3999
4073
|
const cl = fx.colors || {};
|
|
@@ -4108,12 +4182,10 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4108
4182
|
let claudeAuthLabel = null;
|
|
4109
4183
|
let claudeAuthType = null;
|
|
4110
4184
|
if (claudeReady) {
|
|
4111
|
-
if (caps.claude.source === 'claude-code') {
|
|
4185
|
+
if (caps.claude.source === 'claude-code' || caps.claude.source === 'claude-dir') {
|
|
4112
4186
|
claudeAuthLabel = 'CLI OAuth'; claudeAuthType = 'cli_oauth';
|
|
4113
|
-
} else if (caps.claude.source === 'env-key') {
|
|
4114
|
-
claudeAuthLabel = 'API key'; claudeAuthType = 'api_key';
|
|
4115
4187
|
} else {
|
|
4116
|
-
claudeAuthLabel = caps.claude.source || 'detected'; claudeAuthType = '
|
|
4188
|
+
claudeAuthLabel = caps.claude.source || 'detected'; claudeAuthType = 'cli_oauth';
|
|
4117
4189
|
}
|
|
4118
4190
|
fx.success(`Claude CLI found · ${claudeAuthLabel}`);
|
|
4119
4191
|
}
|
|
@@ -4121,10 +4193,7 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4121
4193
|
// OpenAI / Codex
|
|
4122
4194
|
let openaiAuthLabel = null;
|
|
4123
4195
|
let openaiAuthType = null;
|
|
4124
|
-
if (openaiReady) {
|
|
4125
|
-
openaiAuthLabel = 'API key'; openaiAuthType = 'api_key';
|
|
4126
|
-
fx.success('OpenAI detected · API key');
|
|
4127
|
-
} else if (codexAvailable) {
|
|
4196
|
+
if (openaiReady || codexAvailable) {
|
|
4128
4197
|
openaiAuthLabel = 'CLI OAuth'; openaiAuthType = 'cli_oauth';
|
|
4129
4198
|
fx.success('OpenAI Codex CLI found · authenticated');
|
|
4130
4199
|
}
|
|
@@ -4167,7 +4236,6 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4167
4236
|
} else if (noProvChoice === 'o') {
|
|
4168
4237
|
process.stdout.write('\n');
|
|
4169
4238
|
dimLine('Run: codex login');
|
|
4170
|
-
dimLine('Or add OPENAI_API_KEY to Replit Secrets if using API key auth.');
|
|
4171
4239
|
dimLine('Then re-run: dual-brain init');
|
|
4172
4240
|
process.stdout.write('\n');
|
|
4173
4241
|
}
|
|
@@ -4187,9 +4255,7 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4187
4255
|
if (claudeReady) {
|
|
4188
4256
|
process.stdout.write(` ${GRAY}Claude${RST} ${claudeAuthLabel} ${GREEN}✓ authenticated${RST}\n`);
|
|
4189
4257
|
}
|
|
4190
|
-
if (openaiReady) {
|
|
4191
|
-
process.stdout.write(` ${GRAY}OpenAI${RST} API key ${GREEN}✓ OPENAI_API_KEY${RST}\n`);
|
|
4192
|
-
} else if (codexAvailable) {
|
|
4258
|
+
if (openaiReady || codexAvailable) {
|
|
4193
4259
|
process.stdout.write(` ${GRAY}OpenAI${RST} CLI OAuth ${GREEN}✓ authenticated${RST}\n`);
|
|
4194
4260
|
}
|
|
4195
4261
|
|
|
@@ -4214,8 +4280,8 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4214
4280
|
process.stdout.write('\n');
|
|
4215
4281
|
} else if (provChoice === 'a') {
|
|
4216
4282
|
process.stdout.write('\n');
|
|
4217
|
-
if (!claudeReady) dimLine('Claude: run `claude
|
|
4218
|
-
if (!openaiReady && !codexAvailable) dimLine('OpenAI:
|
|
4283
|
+
if (!claudeReady) dimLine('Claude: run `claude login` to authenticate');
|
|
4284
|
+
if (!openaiReady && !codexAvailable) dimLine('OpenAI: run `codex login` to authenticate');
|
|
4219
4285
|
process.stdout.write('\n');
|
|
4220
4286
|
process.stdout.write(` ${GRAY}[Enter]${RST} continue with current providers\n\n`);
|
|
4221
4287
|
await singleKey(['\r', 'q']);
|
|
@@ -4238,10 +4304,10 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4238
4304
|
}
|
|
4239
4305
|
if (finalOpenaiEnabled) {
|
|
4240
4306
|
credEntries.push({
|
|
4241
|
-
id:
|
|
4307
|
+
id: 'openai-codex',
|
|
4242
4308
|
provider: 'openai',
|
|
4243
|
-
auth_type:
|
|
4244
|
-
source:
|
|
4309
|
+
auth_type: 'cli_oauth',
|
|
4310
|
+
source: 'cli_oauth',
|
|
4245
4311
|
owner: 'user',
|
|
4246
4312
|
scope: 'local',
|
|
4247
4313
|
plan_hint: null,
|
|
@@ -4263,12 +4329,6 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4263
4329
|
const styleMap = { '1': 'auto', '2': 'quality-first', '3': 'cost-saver', '\r': 'auto' };
|
|
4264
4330
|
const chosenBias = styleMap[styleKey] || 'auto';
|
|
4265
4331
|
|
|
4266
|
-
// Metered API note (non-blocking)
|
|
4267
|
-
if (openaiReady && caps.openai.metered) {
|
|
4268
|
-
process.stdout.write('\n');
|
|
4269
|
-
dimLine('OpenAI API key detected — usage is metered, guardrails enabled');
|
|
4270
|
-
}
|
|
4271
|
-
|
|
4272
4332
|
process.stdout.write('\n');
|
|
4273
4333
|
|
|
4274
4334
|
// Init living docs (non-fatal)
|
|
@@ -4294,7 +4354,7 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4294
4354
|
|
|
4295
4355
|
finalProfile.providers.claude = { enabled: finalClaudeEnabled };
|
|
4296
4356
|
finalProfile.providers.openai = { enabled: finalOpenaiEnabled };
|
|
4297
|
-
finalProfile.apiGuardrail =
|
|
4357
|
+
finalProfile.apiGuardrail = false;
|
|
4298
4358
|
finalProfile.setupComplete = true;
|
|
4299
4359
|
|
|
4300
4360
|
const enabledCount = [finalClaudeEnabled, finalOpenaiEnabled].filter(Boolean).length;
|
|
@@ -4302,6 +4362,23 @@ async function runOnboardingWizard(_detection, cwd, rl) {
|
|
|
4302
4362
|
finalProfile.bias = chosenBias;
|
|
4303
4363
|
finalProfile.workStyle = chosenBias;
|
|
4304
4364
|
|
|
4365
|
+
// Ask about default shell (only on first wizard run)
|
|
4366
|
+
if (!finalProfile.defaultShellAsked) {
|
|
4367
|
+
const wantsDefault = await askDefaultShell(cwd, rl, fx);
|
|
4368
|
+
finalProfile.defaultShellAsked = true;
|
|
4369
|
+
finalProfile.isDefaultShell = wantsDefault;
|
|
4370
|
+
saveProfile(finalProfile, { cwd });
|
|
4371
|
+
|
|
4372
|
+
// Also run global install if they said yes
|
|
4373
|
+
if (wantsDefault) {
|
|
4374
|
+
try {
|
|
4375
|
+
execSync('node ' + join(dirname(fileURLToPath(import.meta.url)), 'dual-brain.mjs') + ' install --global', {
|
|
4376
|
+
cwd, stdio: 'pipe', timeout: 10000,
|
|
4377
|
+
});
|
|
4378
|
+
} catch {}
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4305
4382
|
return finalProfile;
|
|
4306
4383
|
}
|
|
4307
4384
|
|
package/package.json
CHANGED
package/src/awareness.mjs
CHANGED
|
@@ -49,8 +49,6 @@ function detectContainerType() {
|
|
|
49
49
|
|
|
50
50
|
function scanSecrets() {
|
|
51
51
|
const keys = [
|
|
52
|
-
'OPENAI_API_KEY',
|
|
53
|
-
'ANTHROPIC_API_KEY',
|
|
54
52
|
'NPM_TOKEN',
|
|
55
53
|
'DATABASE_URL',
|
|
56
54
|
'GITHUB_TOKEN',
|
|
@@ -282,8 +280,6 @@ export function formatEnvironment(report) {
|
|
|
282
280
|
if (toolEntries.length) lines.push(`Tools: ${toolEntries.join(' ')}`);
|
|
283
281
|
|
|
284
282
|
const secretMap = {
|
|
285
|
-
OPENAI_API_KEY: 'OpenAI',
|
|
286
|
-
ANTHROPIC_API_KEY: 'Anthropic',
|
|
287
283
|
NPM_TOKEN: 'npm',
|
|
288
284
|
GITHUB_TOKEN: 'GitHub',
|
|
289
285
|
DATABASE_URL: 'PostgreSQL',
|
|
@@ -335,8 +331,6 @@ export function getCapabilitySummary(report) {
|
|
|
335
331
|
if (report.tools.gh?.available) caps.push('github-cli');
|
|
336
332
|
if (report.tools.rg?.available) caps.push('ripgrep');
|
|
337
333
|
|
|
338
|
-
if (report.secrets.OPENAI_API_KEY) caps.push('openai-key');
|
|
339
|
-
if (report.secrets.ANTHROPIC_API_KEY) caps.push('anthropic-key');
|
|
340
334
|
|
|
341
335
|
if (report.replitTools.installed) {
|
|
342
336
|
for (const c of report.replitTools.capabilities) {
|
|
@@ -394,3 +388,38 @@ export function detectAmbiguity(prompt, context = {}) {
|
|
|
394
388
|
|
|
395
389
|
return { isAmbiguous: false, reason: null };
|
|
396
390
|
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Detect whether a user prompt is too vague to act on confidently.
|
|
394
|
+
*
|
|
395
|
+
* Checks for:
|
|
396
|
+
* - Very short prompts (under 10 chars)
|
|
397
|
+
* - No file paths, function names, or specific identifiers
|
|
398
|
+
* - Pronoun-only references without antecedents ("fix that thing", "change it")
|
|
399
|
+
*
|
|
400
|
+
* @param {string} prompt — the user's raw prompt
|
|
401
|
+
* @returns {{ ambiguous: boolean, reason: string|null, confidence: number }}
|
|
402
|
+
*/
|
|
403
|
+
export function isAmbiguous(prompt) {
|
|
404
|
+
if (!prompt || typeof prompt !== 'string') return { ambiguous: true, reason: 'empty-prompt', confidence: 1.0 };
|
|
405
|
+
|
|
406
|
+
const trimmed = prompt.trim();
|
|
407
|
+
if (trimmed.length < 10) return { ambiguous: true, reason: 'too-short', confidence: 0.9 };
|
|
408
|
+
|
|
409
|
+
// Check for file paths, function names, specific identifiers
|
|
410
|
+
const hasSpecifics = /[a-zA-Z_]\w*\.(mjs|js|ts|tsx|jsx|py|go|rs|java|rb|css|html|json|yaml|yml|md|sh)/.test(trimmed)
|
|
411
|
+
|| /[a-zA-Z_]\w*\(\)/.test(trimmed) // function calls
|
|
412
|
+
|| /`[^`]+`/.test(trimmed) // backtick-quoted identifiers
|
|
413
|
+
|| /"[^"]{3,}"/.test(trimmed) // quoted strings
|
|
414
|
+
|| /\b(line|function|class|method|variable|module|component|endpoint|route|table|column)\s+\w+/i.test(trimmed);
|
|
415
|
+
|
|
416
|
+
// Vague pronoun patterns
|
|
417
|
+
const vaguePatterns = /^(fix|change|update|do|make|help|look at|check)\s+(this|that|it|the thing|stuff|things?)$/i;
|
|
418
|
+
if (vaguePatterns.test(trimmed)) return { ambiguous: true, reason: 'vague-reference', confidence: 0.85 };
|
|
419
|
+
|
|
420
|
+
if (!hasSpecifics && trimmed.split(/\s+/).length < 5) {
|
|
421
|
+
return { ambiguous: true, reason: 'lacks-specifics', confidence: 0.7 };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return { ambiguous: false, reason: null, confidence: 0.1 };
|
|
425
|
+
}
|
package/src/dispatch.mjs
CHANGED
|
@@ -345,7 +345,6 @@ async function detectRuntime() {
|
|
|
345
345
|
claudeAvailable && codexAvailable ? 'claude-code'
|
|
346
346
|
: claudeAvailable ? 'claude-code'
|
|
347
347
|
: codexAvailable ? 'codex-cli'
|
|
348
|
-
: process.env.CLAUDE_API_KEY || process.env.ANTHROPIC_API_KEY ? 'standalone'
|
|
349
348
|
: 'none';
|
|
350
349
|
|
|
351
350
|
_runtimeCache = { claudeAvailable, codexAvailable, runtime };
|
package/src/doctor.mjs
CHANGED
|
@@ -564,13 +564,13 @@ const VERIFIERS = {
|
|
|
564
564
|
try { execSync('which claude', { stdio: 'pipe', timeout: 2000 }); return { status: 'verified', evidence: 'claude CLI found', probe: 'which claude' }; }
|
|
565
565
|
catch { return { status: 'failed', evidence: 'claude CLI not found', probe: 'which claude' }; }
|
|
566
566
|
}},
|
|
567
|
-
'openai-key': { ttl:
|
|
568
|
-
|
|
569
|
-
return { status:
|
|
567
|
+
'openai-key': { ttl: TTL_TOOL, fn: () => {
|
|
568
|
+
try { execSync('which codex', { stdio: 'pipe', timeout: 2000 }); return { status: 'verified', evidence: 'codex CLI found (subscription auth)', probe: 'which codex' }; }
|
|
569
|
+
catch { return { status: 'failed', evidence: 'codex CLI not found — run: codex login', probe: 'which codex' }; }
|
|
570
570
|
}},
|
|
571
|
-
'anthropic-key': { ttl:
|
|
572
|
-
|
|
573
|
-
return { status:
|
|
571
|
+
'anthropic-key': { ttl: TTL_TOOL, fn: () => {
|
|
572
|
+
try { execSync('which claude', { stdio: 'pipe', timeout: 2000 }); return { status: 'verified', evidence: 'claude CLI found (subscription auth)', probe: 'which claude' }; }
|
|
573
|
+
catch { return { status: 'failed', evidence: 'claude CLI not found — run: claude login', probe: 'which claude' }; }
|
|
574
574
|
}},
|
|
575
575
|
'git-available': { ttl: TTL_TOOL, fn: () => {
|
|
576
576
|
try { const v = execSync('git --version', { stdio: 'pipe', timeout: 2000 }).toString().trim(); return { status: 'verified', evidence: v, probe: 'git --version' }; }
|
package/src/health.mjs
CHANGED
|
@@ -94,6 +94,8 @@ export async function getAuthHealthStatus(cwd) {
|
|
|
94
94
|
return { ok: false, detail: 'Auth: no credentials found (direct check)', source: 'unknown' };
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const HEALTH_CHECK_TIMEOUT_MS = 5000;
|
|
98
|
+
|
|
97
99
|
const HEALTH_FILE = '.dualbrain/health.json';
|
|
98
100
|
|
|
99
101
|
// Cooldown ladder in minutes: index = attempts - 1, capped at last entry
|
|
@@ -327,7 +329,7 @@ export function resetHealth(cwd) {
|
|
|
327
329
|
* @returns {Promise<{ ok: boolean, status: 'ok'|'timeout'|'error', detail?: string }>}
|
|
328
330
|
*/
|
|
329
331
|
export async function pingProvider(url, opts = {}) {
|
|
330
|
-
const timeoutMs = opts.timeoutMs ??
|
|
332
|
+
const timeoutMs = opts.timeoutMs ?? HEALTH_CHECK_TIMEOUT_MS;
|
|
331
333
|
const controller = new AbortController();
|
|
332
334
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
333
335
|
try {
|
package/src/pipeline.mjs
CHANGED
|
@@ -876,8 +876,8 @@ export async function runPipeline(trigger, prompt, options = {}) {
|
|
|
876
876
|
try {
|
|
877
877
|
const { suggestModel, getRegistryAge } = await import('./models.mjs');
|
|
878
878
|
const availableProviders = [];
|
|
879
|
-
if (run.environment?.
|
|
880
|
-
if (run.environment?.
|
|
879
|
+
if (run.environment?.claudeCode?.isInsideClaude || run.environment?.tools?.claude?.available) availableProviders.push('anthropic');
|
|
880
|
+
if (run.environment?.tools?.codex?.available) availableProviders.push('openai');
|
|
881
881
|
|
|
882
882
|
const intent = run.promptAnalysis?.intent?.type || 'execute';
|
|
883
883
|
const risk = run.plan?.risk || 'medium';
|
package/src/profile.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* getHeadModel(profile) → suggested head model string
|
|
16
16
|
* detectCapabilities(cwd) → what we can actually verify
|
|
17
17
|
* getOnboardingMessage(caps, ws) → honest 2-3 line status message
|
|
18
|
-
*
|
|
18
|
+
* detectCapabilities(cwd) → available providers (subscription-based only)
|
|
19
19
|
*
|
|
20
20
|
* CLI:
|
|
21
21
|
* node src/profile.mjs # show current profile
|
|
@@ -136,7 +136,7 @@ function detectEnvironment() {
|
|
|
136
136
|
* @param {string} [cwd]
|
|
137
137
|
* @returns {Promise<{
|
|
138
138
|
* claude: { available: boolean, source: string|null },
|
|
139
|
-
* openai: { available: boolean, source: string|null
|
|
139
|
+
* openai: { available: boolean, source: string|null },
|
|
140
140
|
* codex: { available: boolean, source: string|null },
|
|
141
141
|
* replitTools: { available: boolean, checkpoints: boolean },
|
|
142
142
|
* }>}
|
|
@@ -144,18 +144,15 @@ function detectEnvironment() {
|
|
|
144
144
|
async function detectCapabilities(cwd) {
|
|
145
145
|
const root = cwd || process.cwd();
|
|
146
146
|
|
|
147
|
-
// --- Claude: running inside Claude Code
|
|
147
|
+
// --- Claude: running inside Claude Code session or CLI installed ---
|
|
148
148
|
let claudeAvailable = false;
|
|
149
149
|
let claudeSource = null;
|
|
150
150
|
|
|
151
151
|
if (process.env.CLAUDE_CODE) {
|
|
152
152
|
claudeAvailable = true;
|
|
153
153
|
claudeSource = 'claude-code';
|
|
154
|
-
} else if (process.env.ANTHROPIC_API_KEY?.length > 0) {
|
|
155
|
-
claudeAvailable = true;
|
|
156
|
-
claudeSource = 'api-key';
|
|
157
154
|
} else {
|
|
158
|
-
// Check for ~/.claude directory (Claude Code installation)
|
|
155
|
+
// Check for ~/.claude directory (Claude Code installation) or Replit Claude
|
|
159
156
|
const claudeDir = join(homedir(), '.claude');
|
|
160
157
|
const replitClaudeDir = join(root, '.replit-tools', '.claude-persistent');
|
|
161
158
|
if (existsSync(claudeDir) || existsSync(replitClaudeDir)) {
|
|
@@ -164,9 +161,6 @@ async function detectCapabilities(cwd) {
|
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
163
|
|
|
167
|
-
// --- OpenAI: check for OPENAI_API_KEY presence (metered billing) ---
|
|
168
|
-
const openaiAvailable = !!process.env.OPENAI_API_KEY?.length;
|
|
169
|
-
|
|
170
164
|
// --- Codex: check if 'codex' is in PATH ---
|
|
171
165
|
let codexAvailable = false;
|
|
172
166
|
let codexSource = null;
|
|
@@ -200,9 +194,8 @@ async function detectCapabilities(cwd) {
|
|
|
200
194
|
source: claudeSource,
|
|
201
195
|
},
|
|
202
196
|
openai: {
|
|
203
|
-
available:
|
|
204
|
-
source:
|
|
205
|
-
metered: openaiAvailable && !codexAvailable,
|
|
197
|
+
available: codexAvailable,
|
|
198
|
+
source: codexAvailable ? 'codex-cli' : null,
|
|
206
199
|
},
|
|
207
200
|
codex: {
|
|
208
201
|
available: codexAvailable,
|
|
@@ -215,18 +208,6 @@ async function detectCapabilities(cwd) {
|
|
|
215
208
|
};
|
|
216
209
|
}
|
|
217
210
|
|
|
218
|
-
/**
|
|
219
|
-
* Return true if any metered API key is detected.
|
|
220
|
-
* When true, the system defaults to conservative API usage and should
|
|
221
|
-
* confirm before expensive operations.
|
|
222
|
-
*
|
|
223
|
-
* @param {ReturnType<typeof detectCapabilities> extends Promise<infer T> ? T : never} capabilities
|
|
224
|
-
* @returns {boolean}
|
|
225
|
-
*/
|
|
226
|
-
function needsApiGuardrail(capabilities) {
|
|
227
|
-
return !!(capabilities?.openai?.metered);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
211
|
/**
|
|
231
212
|
* Generate an honest 2-3 line onboarding/status message based on
|
|
232
213
|
* what we can actually verify.
|
|
@@ -237,9 +218,8 @@ function needsApiGuardrail(capabilities) {
|
|
|
237
218
|
*/
|
|
238
219
|
function getOnboardingMessage(capabilities, workStyle = 'balanced') {
|
|
239
220
|
const found = [];
|
|
240
|
-
if (capabilities?.claude?.available) found.push('Claude
|
|
241
|
-
if (capabilities?.
|
|
242
|
-
if (capabilities?.codex?.available && !capabilities?.openai?.available) found.push('Codex CLI');
|
|
221
|
+
if (capabilities?.claude?.available) found.push('Claude · subscription');
|
|
222
|
+
if (capabilities?.codex?.available) found.push('OpenAI · Codex subscription');
|
|
243
223
|
|
|
244
224
|
const styleLabels = {
|
|
245
225
|
'balanced': 'Balanced — smart routing, reviews on important changes',
|
|
@@ -258,16 +238,11 @@ function getOnboardingMessage(capabilities, workStyle = 'balanced') {
|
|
|
258
238
|
lines.push(`Found: ${found.join(', ')}`);
|
|
259
239
|
lines.push(` Mode: ${modeLabel}`);
|
|
260
240
|
|
|
261
|
-
// Tip: suggest
|
|
262
|
-
if (capabilities?.claude?.available && !capabilities?.
|
|
241
|
+
// Tip: suggest Codex if only Claude is available
|
|
242
|
+
if (capabilities?.claude?.available && !capabilities?.codex?.available) {
|
|
263
243
|
lines.push(' Tip: Run codex login for dual-brain collaboration');
|
|
264
244
|
}
|
|
265
245
|
|
|
266
|
-
// Warn about metered billing
|
|
267
|
-
if (capabilities?.openai?.metered) {
|
|
268
|
-
lines.push(' Note: OpenAI API key detected — usage is metered, guardrails enabled');
|
|
269
|
-
}
|
|
270
|
-
|
|
271
246
|
return lines.join('\n');
|
|
272
247
|
}
|
|
273
248
|
|
|
@@ -393,9 +368,8 @@ async function runOnboarding(opts = {}) {
|
|
|
393
368
|
|
|
394
369
|
// Show what we found honestly
|
|
395
370
|
const foundProviders = [];
|
|
396
|
-
if (capabilities.claude.available) foundProviders.push('Claude
|
|
397
|
-
if (capabilities.
|
|
398
|
-
if (capabilities.codex.available && !capabilities.openai.available) foundProviders.push('Codex CLI');
|
|
371
|
+
if (capabilities.claude.available) foundProviders.push('Claude · subscription');
|
|
372
|
+
if (capabilities.codex.available) foundProviders.push('OpenAI · Codex subscription');
|
|
399
373
|
|
|
400
374
|
if (foundProviders.length > 0) {
|
|
401
375
|
process.stdout.write(`Detected: ${foundProviders.join(', ')}\n\n`);
|
|
@@ -405,15 +379,14 @@ async function runOnboarding(opts = {}) {
|
|
|
405
379
|
|
|
406
380
|
// Enable providers based on what's available
|
|
407
381
|
profile.providers.claude.enabled = capabilities.claude.available;
|
|
408
|
-
profile.providers.openai.enabled = capabilities.
|
|
409
|
-
profile.apiGuardrail = needsApiGuardrail(capabilities);
|
|
382
|
+
profile.providers.openai.enabled = capabilities.codex.available;
|
|
410
383
|
|
|
411
384
|
// If detection missed something, ask
|
|
412
|
-
if (!capabilities.claude.available && !capabilities.
|
|
413
|
-
const q1 = (await ask('Which AI providers do you have access to?\n (1) Claude
|
|
385
|
+
if (!capabilities.claude.available && !capabilities.codex.available) {
|
|
386
|
+
const q1 = (await ask('Which AI providers do you have access to?\n (1) Claude only (2) OpenAI Codex only (3) Both (4) Neither\n> ')).trim();
|
|
414
387
|
if (q1 === '1') { profile.providers.claude.enabled = true; }
|
|
415
|
-
else if (q1 === '2') { profile.providers.claude.enabled = false; profile.providers.openai.enabled = true;
|
|
416
|
-
else if (q1 === '3') { profile.providers.claude.enabled = true; profile.providers.openai.enabled = true;
|
|
388
|
+
else if (q1 === '2') { profile.providers.claude.enabled = false; profile.providers.openai.enabled = true; }
|
|
389
|
+
else if (q1 === '3') { profile.providers.claude.enabled = true; profile.providers.openai.enabled = true; }
|
|
417
390
|
}
|
|
418
391
|
|
|
419
392
|
const q3 = (await ask('\nDefault work style?\n (1) Save usage (2) Balanced (3) Best quality\n> ')).trim();
|
|
@@ -546,19 +519,16 @@ async function autoSetup(cwd) {
|
|
|
546
519
|
result.actions.push(`Claude: available (${capabilities.claude.source})`);
|
|
547
520
|
} else {
|
|
548
521
|
profile.providers.claude.enabled = false;
|
|
549
|
-
result.warnings.push('Claude not detected —
|
|
522
|
+
result.warnings.push('Claude not detected — run: claude login');
|
|
550
523
|
}
|
|
551
524
|
|
|
552
525
|
// OpenAI / Codex
|
|
553
|
-
if (capabilities.
|
|
526
|
+
if (capabilities.codex.available) {
|
|
554
527
|
profile.providers.openai.enabled = true;
|
|
555
|
-
result.actions.push('
|
|
556
|
-
} else if (capabilities.codex.available) {
|
|
557
|
-
profile.providers.openai.enabled = true;
|
|
558
|
-
result.actions.push('Codex CLI: available');
|
|
528
|
+
result.actions.push('Codex CLI: available (subscription)');
|
|
559
529
|
} else {
|
|
560
530
|
profile.providers.openai.enabled = false;
|
|
561
|
-
result.warnings.push('OpenAI not detected —
|
|
531
|
+
result.warnings.push('OpenAI not detected — run: codex login');
|
|
562
532
|
}
|
|
563
533
|
|
|
564
534
|
// Mode
|
|
@@ -568,7 +538,6 @@ async function autoSetup(cwd) {
|
|
|
568
538
|
: 'solo-openai';
|
|
569
539
|
profile.bias = 'balanced';
|
|
570
540
|
profile.workStyle = 'balanced';
|
|
571
|
-
profile.apiGuardrail = needsApiGuardrail(capabilities);
|
|
572
541
|
profile.capabilities = capabilities;
|
|
573
542
|
profile.detectedAt = new Date().toISOString();
|
|
574
543
|
|
|
@@ -909,17 +878,6 @@ export async function checkCredentialHealth(cred, cwd = process.cwd()) {
|
|
|
909
878
|
} catch {
|
|
910
879
|
health = 'healthy'; // cli works, auth check unavailable
|
|
911
880
|
}
|
|
912
|
-
} else if (cred.auth_type === 'api_key') {
|
|
913
|
-
if (cred.source === 'replit_secret') {
|
|
914
|
-
try {
|
|
915
|
-
const { hasSecret } = await import('./replit.mjs');
|
|
916
|
-
health = hasSecret(cred.env_var || cred.id.toUpperCase().replace(/-/g, '_')) ? 'healthy' : 'unhealthy';
|
|
917
|
-
} catch { health = 'unknown'; }
|
|
918
|
-
} else {
|
|
919
|
-
// env source
|
|
920
|
-
const varName = cred.env_var || cred.id.toUpperCase().replace(/-/g, '_');
|
|
921
|
-
health = (process.env[varName] && process.env[varName].length > 0) ? 'healthy' : 'unhealthy';
|
|
922
|
-
}
|
|
923
881
|
}
|
|
924
882
|
} catch { health = 'unknown'; }
|
|
925
883
|
return { ...cred, health, last_checked_at: new Date().toISOString() };
|
|
@@ -949,64 +907,24 @@ export async function detectCredentials(cwd = process.cwd()) {
|
|
|
949
907
|
});
|
|
950
908
|
}
|
|
951
909
|
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
auth_type: 'api_key',
|
|
958
|
-
source: 'env',
|
|
959
|
-
env_var: 'ANTHROPIC_API_KEY',
|
|
960
|
-
owner: 'user',
|
|
961
|
-
scope: 'local',
|
|
962
|
-
plan_hint: null,
|
|
963
|
-
enabled: true,
|
|
964
|
-
health: 'healthy',
|
|
965
|
-
last_checked_at: new Date().toISOString(),
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// OPENAI_API_KEY (env)
|
|
970
|
-
if (process.env.OPENAI_API_KEY) {
|
|
910
|
+
// Codex CLI (subscription-based OpenAI access)
|
|
911
|
+
try {
|
|
912
|
+
execSync('which codex', { stdio: 'pipe', timeout: 2000 });
|
|
913
|
+
let codexHealth = 'unknown';
|
|
914
|
+
try { execSync('codex --version', { stdio: 'pipe', timeout: 3000 }); codexHealth = 'healthy'; } catch { codexHealth = 'degraded'; }
|
|
971
915
|
found.push({
|
|
972
|
-
id: 'openai-
|
|
916
|
+
id: 'openai-codex-cli',
|
|
973
917
|
provider: 'openai',
|
|
974
|
-
auth_type: '
|
|
975
|
-
source: '
|
|
976
|
-
env_var: 'OPENAI_API_KEY',
|
|
918
|
+
auth_type: 'cli_oauth',
|
|
919
|
+
source: 'local_cli',
|
|
977
920
|
owner: 'user',
|
|
978
921
|
scope: 'local',
|
|
979
922
|
plan_hint: null,
|
|
980
923
|
enabled: true,
|
|
981
|
-
health:
|
|
924
|
+
health: codexHealth,
|
|
982
925
|
last_checked_at: new Date().toISOString(),
|
|
983
926
|
});
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
// Replit secrets
|
|
987
|
-
try {
|
|
988
|
-
const { hasSecret } = await import('./replit.mjs');
|
|
989
|
-
const secretsToCheck = ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'];
|
|
990
|
-
for (const name of secretsToCheck) {
|
|
991
|
-
if (!process.env[name] && hasSecret(name)) {
|
|
992
|
-
const provider = name.startsWith('OPENAI') ? 'openai' : 'claude';
|
|
993
|
-
const id = name.toLowerCase().replace(/_/g, '-') + '-replit';
|
|
994
|
-
found.push({
|
|
995
|
-
id,
|
|
996
|
-
provider,
|
|
997
|
-
auth_type: 'api_key',
|
|
998
|
-
source: 'replit_secret',
|
|
999
|
-
env_var: name,
|
|
1000
|
-
owner: 'user',
|
|
1001
|
-
scope: 'workspace',
|
|
1002
|
-
plan_hint: null,
|
|
1003
|
-
enabled: true,
|
|
1004
|
-
health: 'healthy',
|
|
1005
|
-
last_checked_at: new Date().toISOString(),
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
} catch { /* replit.mjs unavailable */ }
|
|
927
|
+
} catch { /* codex not in PATH */ }
|
|
1010
928
|
|
|
1011
929
|
return found;
|
|
1012
930
|
}
|
|
@@ -1120,12 +1038,12 @@ export async function getCapabilityManifest(cwd = process.cwd()) {
|
|
|
1120
1038
|
budget: { pressure5h: 0, pressure7d: 0 }, source: 'none' };
|
|
1121
1039
|
|
|
1122
1040
|
try {
|
|
1123
|
-
// available: claude CLI or
|
|
1041
|
+
// available: CLAUDE_CODE env, claude CLI, or replit-tools claude dir
|
|
1124
1042
|
const claudeDir = join(homedir(), '.claude');
|
|
1125
1043
|
const replitClaudeDir = join(cwd, '.replit-tools', '.claude-persistent');
|
|
1126
|
-
if (process.env.CLAUDE_CODE
|
|
1044
|
+
if (process.env.CLAUDE_CODE) {
|
|
1127
1045
|
claudeProvider.available = true;
|
|
1128
|
-
claudeProvider.source =
|
|
1046
|
+
claudeProvider.source = 'credentials';
|
|
1129
1047
|
} else if (existsSync(claudeDir) || existsSync(replitClaudeDir)) {
|
|
1130
1048
|
claudeProvider.available = true;
|
|
1131
1049
|
claudeProvider.source = existsSync(replitClaudeDir) ? 'replit-tools' : 'credentials';
|
|
@@ -1164,15 +1082,12 @@ export async function getCapabilityManifest(cwd = process.cwd()) {
|
|
|
1164
1082
|
budget: { pressure5h: 0, pressure7d: 0 }, source: 'none' };
|
|
1165
1083
|
|
|
1166
1084
|
try {
|
|
1167
|
-
let hasSecret = false;
|
|
1168
|
-
try { const { hasSecret: hs } = await import('./replit.mjs'); hasSecret = hs('OPENAI_API_KEY'); } catch { hasSecret = !!(process.env.OPENAI_API_KEY); }
|
|
1169
|
-
|
|
1170
1085
|
let codexAvailable = false;
|
|
1171
1086
|
try { execSync('which codex', { stdio: 'pipe', timeout: 2000 }); codexAvailable = true; } catch { /* not in PATH */ }
|
|
1172
1087
|
|
|
1173
|
-
openaiProvider.available =
|
|
1174
|
-
openaiProvider.authenticated =
|
|
1175
|
-
openaiProvider.source =
|
|
1088
|
+
openaiProvider.available = codexAvailable;
|
|
1089
|
+
openaiProvider.authenticated = codexAvailable;
|
|
1090
|
+
openaiProvider.source = codexAvailable ? 'codex-cli' : 'none';
|
|
1176
1091
|
} catch { /* detection failed */ }
|
|
1177
1092
|
|
|
1178
1093
|
openaiProvider.plan = normalizePlan(orchProv.openai?.subscription ?? orchSubs.openai?.plan);
|
|
@@ -1387,7 +1302,7 @@ async function main() {
|
|
|
1387
1302
|
`head model : ${getHeadModel(profile)}`,
|
|
1388
1303
|
`providers : ${providers.map(p => p.name).join(', ') || 'none'}`,
|
|
1389
1304
|
`prefs : ${profile.preferences?.filter(p => p.enabled).length || 0} active`,
|
|
1390
|
-
`guardrail :
|
|
1305
|
+
`guardrail : off`,
|
|
1391
1306
|
'',
|
|
1392
1307
|
getOnboardingMessage(caps, profile.workStyle || profile.bias),
|
|
1393
1308
|
].forEach(l => process.stdout.write(l + '\n'));
|
|
@@ -1404,7 +1319,7 @@ export {
|
|
|
1404
1319
|
loadProfile, saveProfile, ensureProfile, runOnboarding,
|
|
1405
1320
|
rememberPreference, forgetPreference, getActivePreferences,
|
|
1406
1321
|
getAvailableProviders, isSoloBrain, getHeadModel,
|
|
1407
|
-
detectCapabilities, getOnboardingMessage,
|
|
1322
|
+
detectCapabilities, getOnboardingMessage,
|
|
1408
1323
|
syncPreferencesToMemory,
|
|
1409
1324
|
detectAuth, detectEnvironment,
|
|
1410
1325
|
autoSetup, autoRefreshToken,
|
package/src/replit.mjs
CHANGED
|
@@ -339,7 +339,7 @@ const SYSTEM_PREFIXES = [
|
|
|
339
339
|
];
|
|
340
340
|
|
|
341
341
|
const KNOWN_SECRET_NAMES = [
|
|
342
|
-
'
|
|
342
|
+
'DATABASE_URL', 'REPLIT_DB_URL',
|
|
343
343
|
'GITHUB_TOKEN', 'GITHUB_API_TOKEN', 'NPM_TOKEN', 'NPM_AUTH_TOKEN',
|
|
344
344
|
'STRIPE_SECRET_KEY', 'STRIPE_API_KEY', 'AWS_ACCESS_KEY_ID',
|
|
345
345
|
'AWS_SECRET_ACCESS_KEY', 'GOOGLE_API_KEY', 'GOOGLE_APPLICATION_CREDENTIALS',
|