dual-brain 0.2.5 → 0.2.6

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.
@@ -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
- const openaiPresent = replit.hasSecret('OPENAI_API_KEY');
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
 
@@ -1981,13 +1979,9 @@ function buildProviderStatusLine(profile, auth, envReport = null) {
1981
1979
  const GREEN = '\x1b[32m●\x1b[0m';
1982
1980
  const RED = '\x1b[31m●\x1b[0m';
1983
1981
 
1984
- // Use envReport secrets when available; fall back to auth detection
1985
- const claudeAvailable = envReport
1986
- ? envReport.secrets.ANTHROPIC_API_KEY || auth.claude.found
1987
- : auth.claude.found;
1988
- const openaiAvailable = envReport
1989
- ? envReport.secrets.OPENAI_API_KEY || auth.openai.found
1990
- : auth.openai.found;
1982
+ // Subscription-only detection no API key secrets
1983
+ const claudeAvailable = auth.claude.found;
1984
+ const openaiAvailable = auth.openai.found;
1991
1985
 
1992
1986
  const claudeDot = claudeAvailable ? GREEN : RED;
1993
1987
  const openaiDot = openaiAvailable ? GREEN : RED;
@@ -2308,13 +2302,9 @@ async function mainScreen(rl, ask) {
2308
2302
  envReport = scanEnvironment(cwd);
2309
2303
  } catch { /* non-fatal */ }
2310
2304
 
2311
- // ── Studio Console: resolve provider availability ────────────────────────
2312
- const claudeAvail = envReport
2313
- ? envReport.secrets?.ANTHROPIC_API_KEY || auth.claude.found
2314
- : auth.claude.found;
2315
- const openaiAvail = envReport
2316
- ? envReport.secrets?.OPENAI_API_KEY || auth.openai.found
2317
- : auth.openai.found;
2305
+ // ── Studio Console: resolve provider availability (subscription-only) ───
2306
+ const claudeAvail = auth.claude.found;
2307
+ const openaiAvail = auth.openai.found;
2318
2308
 
2319
2309
  // ── Box 2 — Workspace: gather git data ───────────────────────────────────
2320
2310
  let gitBranch = 'unknown';
@@ -4108,12 +4098,10 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4108
4098
  let claudeAuthLabel = null;
4109
4099
  let claudeAuthType = null;
4110
4100
  if (claudeReady) {
4111
- if (caps.claude.source === 'claude-code') {
4101
+ if (caps.claude.source === 'claude-code' || caps.claude.source === 'claude-dir') {
4112
4102
  claudeAuthLabel = 'CLI OAuth'; claudeAuthType = 'cli_oauth';
4113
- } else if (caps.claude.source === 'env-key') {
4114
- claudeAuthLabel = 'API key'; claudeAuthType = 'api_key';
4115
4103
  } else {
4116
- claudeAuthLabel = caps.claude.source || 'detected'; claudeAuthType = 'unknown';
4104
+ claudeAuthLabel = caps.claude.source || 'detected'; claudeAuthType = 'cli_oauth';
4117
4105
  }
4118
4106
  fx.success(`Claude CLI found · ${claudeAuthLabel}`);
4119
4107
  }
@@ -4121,10 +4109,7 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4121
4109
  // OpenAI / Codex
4122
4110
  let openaiAuthLabel = null;
4123
4111
  let openaiAuthType = null;
4124
- if (openaiReady) {
4125
- openaiAuthLabel = 'API key'; openaiAuthType = 'api_key';
4126
- fx.success('OpenAI detected · API key');
4127
- } else if (codexAvailable) {
4112
+ if (openaiReady || codexAvailable) {
4128
4113
  openaiAuthLabel = 'CLI OAuth'; openaiAuthType = 'cli_oauth';
4129
4114
  fx.success('OpenAI Codex CLI found · authenticated');
4130
4115
  }
@@ -4167,7 +4152,6 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4167
4152
  } else if (noProvChoice === 'o') {
4168
4153
  process.stdout.write('\n');
4169
4154
  dimLine('Run: codex login');
4170
- dimLine('Or add OPENAI_API_KEY to Replit Secrets if using API key auth.');
4171
4155
  dimLine('Then re-run: dual-brain init');
4172
4156
  process.stdout.write('\n');
4173
4157
  }
@@ -4187,9 +4171,7 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4187
4171
  if (claudeReady) {
4188
4172
  process.stdout.write(` ${GRAY}Claude${RST} ${claudeAuthLabel} ${GREEN}✓ authenticated${RST}\n`);
4189
4173
  }
4190
- if (openaiReady) {
4191
- process.stdout.write(` ${GRAY}OpenAI${RST} API key ${GREEN}✓ OPENAI_API_KEY${RST}\n`);
4192
- } else if (codexAvailable) {
4174
+ if (openaiReady || codexAvailable) {
4193
4175
  process.stdout.write(` ${GRAY}OpenAI${RST} CLI OAuth ${GREEN}✓ authenticated${RST}\n`);
4194
4176
  }
4195
4177
 
@@ -4214,8 +4196,8 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4214
4196
  process.stdout.write('\n');
4215
4197
  } else if (provChoice === 'a') {
4216
4198
  process.stdout.write('\n');
4217
- if (!claudeReady) dimLine('Claude: run `claude auth login` to authenticate');
4218
- if (!openaiReady && !codexAvailable) dimLine('OpenAI: set OPENAI_API_KEY or run `codex login`');
4199
+ if (!claudeReady) dimLine('Claude: run `claude login` to authenticate');
4200
+ if (!openaiReady && !codexAvailable) dimLine('OpenAI: run `codex login` to authenticate');
4219
4201
  process.stdout.write('\n');
4220
4202
  process.stdout.write(` ${GRAY}[Enter]${RST} continue with current providers\n\n`);
4221
4203
  await singleKey(['\r', 'q']);
@@ -4238,10 +4220,10 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4238
4220
  }
4239
4221
  if (finalOpenaiEnabled) {
4240
4222
  credEntries.push({
4241
- id: openaiReady ? 'openai-apikey' : 'openai-codex',
4223
+ id: 'openai-codex',
4242
4224
  provider: 'openai',
4243
- auth_type: openaiAuthType || 'api_key',
4244
- source: openaiReady ? 'env_var' : 'cli_oauth',
4225
+ auth_type: 'cli_oauth',
4226
+ source: 'cli_oauth',
4245
4227
  owner: 'user',
4246
4228
  scope: 'local',
4247
4229
  plan_hint: null,
@@ -4263,12 +4245,6 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4263
4245
  const styleMap = { '1': 'auto', '2': 'quality-first', '3': 'cost-saver', '\r': 'auto' };
4264
4246
  const chosenBias = styleMap[styleKey] || 'auto';
4265
4247
 
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
4248
  process.stdout.write('\n');
4273
4249
 
4274
4250
  // Init living docs (non-fatal)
@@ -4294,7 +4270,7 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4294
4270
 
4295
4271
  finalProfile.providers.claude = { enabled: finalClaudeEnabled };
4296
4272
  finalProfile.providers.openai = { enabled: finalOpenaiEnabled };
4297
- finalProfile.apiGuardrail = caps.openai.metered;
4273
+ finalProfile.apiGuardrail = false;
4298
4274
  finalProfile.setupComplete = true;
4299
4275
 
4300
4276
  const enabledCount = [finalClaudeEnabled, finalOpenaiEnabled].filter(Boolean).length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
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: TTL_RUNTIME, fn: () => {
568
- const has = !!process.env.OPENAI_API_KEY;
569
- return { status: has ? 'verified' : 'failed', evidence: has ? 'OPENAI_API_KEY present' : 'OPENAI_API_KEY missing', probe: 'env check' };
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: TTL_RUNTIME, fn: () => {
572
- const has = !!(process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_API_KEY);
573
- return { status: has ? 'verified' : 'failed', evidence: has ? 'API key present' : 'API key missing', probe: 'env check' };
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 ?? 5000;
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?.secrets?.ANTHROPIC_API_KEY || run.environment?.claudeCode?.isInsideClaude) availableProviders.push('anthropic');
880
- if (run.environment?.secrets?.OPENAI_API_KEY) availableProviders.push('openai');
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
- * needsApiGuardrail(caps) → true if metered API key detected
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, metered: boolean },
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 or has ANTHROPIC_API_KEY or ~/.claude dir ---
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: openaiAvailable || codexAvailable,
204
- source: openaiAvailable ? 'api-key' : codexAvailable ? 'codex-cli' : null,
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 Code');
241
- if (capabilities?.openai?.available) found.push('OpenAI API');
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 OpenAI if only Claude is available
262
- if (capabilities?.claude?.available && !capabilities?.openai?.available && !capabilities?.codex?.available) {
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 Code');
397
- if (capabilities.openai.available) foundProviders.push('OpenAI API (metered)');
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.openai.available || capabilities.codex.available;
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.openai.available && !capabilities.codex.available) {
413
- const q1 = (await ask('Which AI providers do you have access to?\n (1) Claude Code only (2) OpenAI API only (3) Both (4) Neither\n> ')).trim();
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; profile.apiGuardrail = true; }
416
- else if (q1 === '3') { profile.providers.claude.enabled = true; profile.providers.openai.enabled = true; profile.apiGuardrail = 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 — install Claude Code or set ANTHROPIC_API_KEY');
522
+ result.warnings.push('Claude not detected — run: claude login');
550
523
  }
551
524
 
552
525
  // OpenAI / Codex
553
- if (capabilities.openai.available) {
526
+ if (capabilities.codex.available) {
554
527
  profile.providers.openai.enabled = true;
555
- result.actions.push('OpenAI: API key detected (metered billing — guardrails enabled)');
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 — add OPENAI_API_KEY or install Codex CLI');
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
- // ANTHROPIC_API_KEY
953
- if (process.env.ANTHROPIC_API_KEY) {
954
- found.push({
955
- id: 'anthropic-api-key',
956
- provider: 'claude',
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-api-key',
916
+ id: 'openai-codex-cli',
973
917
  provider: 'openai',
974
- auth_type: 'api_key',
975
- source: 'env',
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: 'healthy',
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 CLAUDE_CODE env or replit-tools claude dir
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 || process.env.ANTHROPIC_API_KEY) {
1044
+ if (process.env.CLAUDE_CODE) {
1127
1045
  claudeProvider.available = true;
1128
- claudeProvider.source = process.env.ANTHROPIC_API_KEY ? 'env' : 'credentials';
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 = hasSecret || codexAvailable;
1174
- openaiProvider.authenticated = hasSecret;
1175
- openaiProvider.source = hasSecret ? 'env' : codexAvailable ? 'codex-config' : 'none';
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 : ${needsApiGuardrail(caps) ? 'enabled (metered API key detected)' : 'off'}`,
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, needsApiGuardrail,
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
- 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'DATABASE_URL', 'REPLIT_DB_URL',
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',