@openlife/cli 1.7.10 → 1.7.12

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.
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const assert = __importStar(require("assert"));
37
+ const Gateway_1 = require("./orchestrator/Gateway");
38
+ async function main() {
39
+ process.env.OPENLIFE_TELEGRAM_ALLOWED_USER_ID = '';
40
+ process.env.OPENLIFE_ENABLE_TTS = 'false';
41
+ const gateway = new Gateway_1.Gateway();
42
+ gateway.classifier = {
43
+ classify: async (_text) => ({ intent: 'KNOWLEDGE_RETRIEVAL', budget: 0.1 })
44
+ };
45
+ gateway.gatekeeper = {
46
+ routeTask: async (_task, _text, _userId) => {
47
+ await new Promise(resolve => setTimeout(resolve, 1200));
48
+ return 'FINAL_AGENT_RESPONSE_FROM_GPT55_PATH';
49
+ }
50
+ };
51
+ const started = Date.now();
52
+ const result = await gateway.processTextForTestDetailed('1344110010', 'ola');
53
+ const totalMs = Date.now() - started;
54
+ assert.ok(result.ackMs !== null, 'ACK_NOT_SENT');
55
+ assert.ok(result.ackMs < 500, `ACK_TOO_SLOW_${result.ackMs}`);
56
+ assert.ok(result.events.length >= 2, `EXPECTED_ACK_AND_FINAL_EVENTS_${JSON.stringify(result.events)}`);
57
+ assert.strictEqual(result.events[0].kind, 'reply', 'FIRST_EVENT_SHOULD_BE_ACK_REPLY');
58
+ assert.ok(result.events[0].text.includes('processando'), `ACK_TEXT_UNEXPECTED_${result.events[0].text}`);
59
+ assert.strictEqual(result.finalText, 'FINAL_AGENT_RESPONSE_FROM_GPT55_PATH', `FINAL_TEXT_UNEXPECTED_${result.finalText}`);
60
+ assert.ok(totalMs >= 1100, 'TEST_STUB_DID_NOT_SIMULATE_SLOW_PATH');
61
+ console.log('TEST_GATEWAY_FAST_ACK_OK');
62
+ }
63
+ main().catch((err) => {
64
+ console.error('TEST_GATEWAY_FAST_ACK_FAIL:', err?.message || err);
65
+ process.exit(1);
66
+ });
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ // src/test_matrix_rain.ts
3
+ // Smoke tests for the Matrix theme + rain renderer.
4
+ // - banner renders deterministic plain-text shape under stripAnsi
5
+ // - inventory panel borders align (top/bottom widths match)
6
+ // - rain.start() in non-TTY returns no-op handle (no interval leak)
7
+ // - sampleFrame() produces requested dimensions
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const ChatBanner_1 = require("./cli/ChatBanner");
10
+ const MatrixRain_1 = require("./cli/MatrixRain");
11
+ const MatrixTheme_1 = require("./cli/MatrixTheme");
12
+ let failed = 0;
13
+ function check(label, condition, detail) {
14
+ if (condition) {
15
+ console.log(`✅ ${label}`);
16
+ }
17
+ else {
18
+ console.error(`❌ ${label}${detail ? ` — ${detail}` : ''}`);
19
+ failed++;
20
+ }
21
+ }
22
+ // 1. Banner contains the OPENLIFE letters
23
+ {
24
+ const plain = (0, MatrixTheme_1.stripAnsi)((0, ChatBanner_1.openlifeBanner)());
25
+ check('banner contains OPENLIFE letters (5 rows of █)', plain.includes('██████ ██████ ███████ ███ ██ ██ ██ ███████ ███████'));
26
+ check('banner has horizon line', plain.includes('▁'));
27
+ }
28
+ // 2. Inventory panel borders align
29
+ {
30
+ const stats = { version: 'v1.7.12', model: 'openai-cli/gpt-5.5', sessionId: 'TEST_SESSION', cwd: '/tmp', agents: 1, squads: 2, skills: 3, mcps: 4 };
31
+ const plain = (0, MatrixTheme_1.stripAnsi)((0, ChatBanner_1.inventoryPanel)(stats));
32
+ const lines = plain.split('\n').filter(Boolean);
33
+ const top = lines[0];
34
+ const bot = lines[lines.length - 1];
35
+ check('panel top and bottom widths match', top.length === bot.length, `top=${top.length} bot=${bot.length}`);
36
+ check('panel shows model', plain.includes('openai-cli/gpt-5.5'));
37
+ check('panel shows catalog counts', plain.includes('agents 1') && plain.includes('skills 3'));
38
+ }
39
+ // 3. bootOutput composes banner + panel + welcome
40
+ {
41
+ const stats = { version: 'v1.7.12', model: 'm', sessionId: 's', cwd: '/c', agents: 0, squads: 0, skills: 0, mcps: 0 };
42
+ const plain = (0, MatrixTheme_1.stripAnsi)((0, ChatBanner_1.bootOutput)(stats));
43
+ check('bootOutput contains banner OPENLIFE marker', plain.includes('██████'));
44
+ check('bootOutput contains welcome line', plain.includes('Welcome to OpenLife'));
45
+ check('bootOutput contains divider line', plain.includes('━'));
46
+ }
47
+ // 4. Rain in non-TTY env returns no-op handle
48
+ {
49
+ // We're running under node test runner — stdout.isTTY is false.
50
+ const handle = (0, MatrixRain_1.startRain)({ row: 1, col: 1, width: 10, height: 4 });
51
+ check('rain.isRunning() returns false in non-TTY', handle.isRunning() === false);
52
+ // stop() must be safe to call without state
53
+ handle.stop();
54
+ check('rain.stop() in non-TTY is a no-op (no throw)', true);
55
+ }
56
+ // 5. sampleFrame produces requested dimensions
57
+ {
58
+ const frame = (0, MatrixRain_1.sampleFrame)(10, 5);
59
+ const rows = frame.split('\n');
60
+ check('sampleFrame produces correct number of rows', rows.length === 5);
61
+ // Each row is at most 10 visible chars (after stripAnsi)
62
+ const plainRowLengths = rows.map(r => (0, MatrixTheme_1.stripAnsi)(r).length);
63
+ check('sampleFrame rows are at most width chars', plainRowLengths.every(n => n <= 10), `lengths=${plainRowLengths.join(',')}`);
64
+ }
65
+ if (failed > 0) {
66
+ console.error(`\n${failed} check(s) failed.`);
67
+ process.exit(1);
68
+ }
69
+ console.log('\nTEST_MATRIX_RAIN_OK');
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * Regression: OpenLife chat surface must not return canned/automatic replies.
4
+ * The main ask/Gatekeeper path should invoke the configured model executor even
5
+ * for greetings; deterministic status commands remain explicit operational paths.
6
+ */
7
+ function assertTrue(cond, label) {
8
+ if (!cond)
9
+ throw new Error(`ASSERT_FAILED[${label}]`);
10
+ }
11
+ function installBrainMock(mock) {
12
+ const brainPath = require.resolve('./orchestrator/Brain');
13
+ const orig = require.cache[brainPath];
14
+ require.cache[brainPath] = {
15
+ ...orig,
16
+ exports: {
17
+ Brain: class {
18
+ isAnyProviderAvailable() { return true; }
19
+ async thinkFast(_systemPrompt, userMessage) {
20
+ mock.invocations += 1;
21
+ mock.lastUserMessage = userMessage;
22
+ return mock.reply;
23
+ }
24
+ async think(_systemPrompt, userMessage) {
25
+ mock.invocations += 1;
26
+ mock.lastUserMessage = userMessage;
27
+ return mock.reply;
28
+ }
29
+ },
30
+ },
31
+ };
32
+ return () => {
33
+ if (orig)
34
+ require.cache[brainPath] = orig;
35
+ else
36
+ delete require.cache[brainPath];
37
+ };
38
+ }
39
+ async function main() {
40
+ process.env.OPENLIFE_OUTCOME_SIMULATION = 'off';
41
+ const mock = { invocations: 0, reply: 'MODEL_REPLY_FROM_GPT55_PATH' };
42
+ const restore = installBrainMock(mock);
43
+ try {
44
+ const { IntentClassifier } = require('./orchestrator/IntentClassifier');
45
+ const { Gatekeeper } = require('./orchestrator/Gatekeeper');
46
+ const classifier = new IntentClassifier();
47
+ const gatekeeper = new Gatekeeper();
48
+ const greeting = 'ola';
49
+ const task = await classifier.classify(greeting);
50
+ const response = await gatekeeper.routeTask(task, greeting, 'no-auto-test');
51
+ assertTrue(response === 'MODEL_REPLY_FROM_GPT55_PATH', 'greeting response comes from Brain/model path');
52
+ assertTrue(mock.invocations === 1, `Brain invoked exactly once for greeting (got ${mock.invocations})`);
53
+ assertTrue(mock.lastUserMessage === greeting, 'original greeting passed to model');
54
+ assertTrue(!/OpenLife online|Como posso ajudar|OPENLIFE ONLINE|Intenção desconhecida/.test(response), 'no canned chat text returned');
55
+ }
56
+ finally {
57
+ restore();
58
+ }
59
+ console.log('TEST_NO_AUTOMATIC_MESSAGES_OK');
60
+ }
61
+ main().catch((err) => {
62
+ console.error(err);
63
+ process.exit(1);
64
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openlife/cli",
3
- "version": "1.7.10",
3
+ "version": "1.7.12",
4
4
  "description": "OPEN-LIFE Córtex Orquestrador Dual-Core",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -159,13 +159,19 @@
159
159
  "test:status-command": "npm run build && node dist/test_status_command.js",
160
160
  "test:logs-command": "npm run build && node dist/test_logs_command.js",
161
161
  "test:agent-creator": "npm run build && node dist/test_agent_creator.js",
162
+ "test:matrix-rain": "npm run build && node dist/test_matrix_rain.js",
163
+ "test:chat-tui": "npm run build && node dist/test_chat_tui.js",
164
+ "test:config-tui": "npm run build && node dist/test_config_tui.js",
162
165
  "pretest:all": "node scripts/clean-test-pollution.js",
163
- "test:all": "npm run build && node dist/test_distribution_installability.js && node dist/test_orchestration_assets_lifecycle.js && node dist/test_openlife_runtime_source_truth.js && node dist/test_openlife_evolution_surface.js && node dist/test_openlife_routing_surface.js && node dist/test_openlife_auto_creator_routing.js && node dist/test_openlife_gatekeeper_routing.js && node dist/test_enterprise_agentic_core.js && node dist/test_admin_teams_networks.js && node dist/test_agent_team_skill_network.js && node dist/test_benchmark_engine.js && node dist/test_cli_service_commands.js && node dist/test_consequence_forecaster.js && node dist/test_conversation_memory.js && node dist/test_create_entities.js && node dist/test_designmd_import_registry.js && node dist/test_designmd_mode.js && node dist/test_designmd_mode_workspace.js && node dist/test_dream_organizer.js && node dist/test_dual_mode.js && node dist/test_governance.js && node dist/test_governance_advanced.js && node dist/test_install_flow.js && node dist/test_job_lifecycle.js && node dist/test_memory_orchestrator.js && node dist/test_memory_promotion.js && node dist/test_memory_retention.js && node dist/test_operating_system.js && node dist/test_optimization_loop.js && node dist/test_outcome_simulator.js && node dist/test_performance_scorecard.js && node dist/test_phase6_board.js && node dist/test_phase6_cadence.js && node dist/test_phase6_ops.js && node dist/test_release_gate.js && node dist/test_reversa_contracts_e2e.js && node dist/test_reversa_export_and_strict.js && node dist/test_reversa_full_execution.js && node dist/test_reversa_lite.js && node dist/test_runtime_policy.js && node dist/test_runtime_probe.js && node dist/test_runtime_registry.js && node dist/test_security_download_guard.js && node dist/test_service_command_surface.js && node dist/test_service_completion_policy.js && node dist/test_service_guardrails_delete.js && node dist/test_sources_import_ref.js && node dist/test_sources_scaffold.js && node dist/test_teammate_learning.js && node dist/test_telegram_delete_guardrail.js && node dist/test_daemon_sigterm.js && node dist/test_ask_exit.js && node dist/test_brain_error_diagnostics.js && node dist/test_cli_doc_parity.js && node dist/test_trigger_basic_auth.js && node dist/test_brain_fallback_chain.js && node dist/test_cli_help_surface.js && node dist/test_cli_diagnostics.js && node dist/test_cli_crud_roundtrip.js && node dist/test_subsystems_routing_governance.js && node dist/test_subsystems_org_state.js && node dist/test_subsystems_promotion_memory_assets.js && node dist/test_phase1_check_exit.js && node dist/test_install_flow_host_validation.js && node dist/test_dist_templates_layout.js && node dist/test_host_installer.js && node dist/test_host_uninstaller.js && node dist/test_install_wizard.js && node dist/test_multi_host_docs_parity.js && node dist/test_host_install_e2e.js && node dist/test_runtime_profile_oauth_only.js && node dist/test_atomic_writer.js && node dist/test_mission_checkpoint.js && node dist/test_workflow_parser.js && node dist/test_workflow_engine.js && node dist/test_workflow_e2e.js && node dist/test_distributed_lock.js && node dist/test_watchdog_heartbeat.js && node dist/test_runtime_health_backoff.js && node dist/test_queue_scheduler.js && node dist/test_squad_skill_creator.js && node dist/test_aiobuilder_cli_parity.js && node dist/test_catalog_quality.js && node dist/test_royal_stack_golden.js && node dist/test_capability_pack_schema.js && node dist/test_capability_genesis_engine.js && node dist/test_workflow_schema_backward_compat.js && node dist/test_service_mode_explicit_only.js && node dist/test_deep_research_capability.js && node dist/test_guided_creator_cli.js && node dist/test_governance_v13_policies.js && node dist/test_gateway_telegram_guardrails.js && node dist/test_cron_manager.js && node dist/test_profile_toolset_mcp.js && node dist/test_squad_skill_design_llm.js && node dist/test_workflow_condition_parser.js && node dist/test_security_download_and_scan.js && node dist/test_host_installers_gemini_codex.js && node dist/test_toolset_enforcement.js && node dist/test_creator_placeholders_completed.js && node dist/test_performance_latency.js && node dist/test_post_mission_evaluation.js && node dist/test_governance_scope_ledger.js && node dist/test_consequence_forecast_brain.js && node dist/test_remote_publish.js && node dist/test_process_sandbox.js && node dist/test_v15_e2e_integration.js && node dist/test_doctor_sandbox_check.js && node dist/test_task_executor_sandbox_optin.js && node dist/test_forecast_brain_wiring.js && node dist/test_status_command.js && node dist/test_logs_command.js && node dist/test_agent_creator.js",
164
- "prepublishOnly": "npm run test:all"
166
+ "test:all": "npm run build && node dist/test_distribution_installability.js && node dist/test_orchestration_assets_lifecycle.js && node dist/test_openlife_runtime_source_truth.js && node dist/test_openlife_evolution_surface.js && node dist/test_openlife_routing_surface.js && node dist/test_openlife_auto_creator_routing.js && node dist/test_openlife_gatekeeper_routing.js && node dist/test_enterprise_agentic_core.js && node dist/test_admin_teams_networks.js && node dist/test_agent_team_skill_network.js && node dist/test_benchmark_engine.js && node dist/test_cli_service_commands.js && node dist/test_consequence_forecaster.js && node dist/test_conversation_memory.js && node dist/test_create_entities.js && node dist/test_designmd_import_registry.js && node dist/test_designmd_mode.js && node dist/test_designmd_mode_workspace.js && node dist/test_dream_organizer.js && node dist/test_dual_mode.js && node dist/test_governance.js && node dist/test_governance_advanced.js && node dist/test_install_flow.js && node dist/test_job_lifecycle.js && node dist/test_memory_orchestrator.js && node dist/test_memory_promotion.js && node dist/test_memory_retention.js && node dist/test_operating_system.js && node dist/test_optimization_loop.js && node dist/test_outcome_simulator.js && node dist/test_performance_scorecard.js && node dist/test_phase6_board.js && node dist/test_phase6_cadence.js && node dist/test_phase6_ops.js && node dist/test_release_gate.js && node dist/test_reversa_contracts_e2e.js && node dist/test_reversa_export_and_strict.js && node dist/test_reversa_full_execution.js && node dist/test_reversa_lite.js && node dist/test_runtime_policy.js && node dist/test_runtime_probe.js && node dist/test_runtime_registry.js && node dist/test_security_download_guard.js && node dist/test_service_command_surface.js && node dist/test_service_completion_policy.js && node dist/test_service_guardrails_delete.js && node dist/test_sources_import_ref.js && node dist/test_sources_scaffold.js && node dist/test_teammate_learning.js && node dist/test_telegram_delete_guardrail.js && node dist/test_daemon_sigterm.js && node dist/test_ask_exit.js && node dist/test_brain_error_diagnostics.js && node dist/test_cli_doc_parity.js && node dist/test_trigger_basic_auth.js && node dist/test_brain_fallback_chain.js && node dist/test_cli_help_surface.js && node dist/test_cli_diagnostics.js && node dist/test_cli_crud_roundtrip.js && node dist/test_subsystems_routing_governance.js && node dist/test_subsystems_org_state.js && node dist/test_subsystems_promotion_memory_assets.js && node dist/test_phase1_check_exit.js && node dist/test_install_flow_host_validation.js && node dist/test_dist_templates_layout.js && node dist/test_host_installer.js && node dist/test_host_uninstaller.js && node dist/test_install_wizard.js && node dist/test_multi_host_docs_parity.js && node dist/test_host_install_e2e.js && node dist/test_runtime_profile_oauth_only.js && node dist/test_atomic_writer.js && node dist/test_mission_checkpoint.js && node dist/test_workflow_parser.js && node dist/test_workflow_engine.js && node dist/test_workflow_e2e.js && node dist/test_distributed_lock.js && node dist/test_watchdog_heartbeat.js && node dist/test_runtime_health_backoff.js && node dist/test_queue_scheduler.js && node dist/test_squad_skill_creator.js && node dist/test_aiobuilder_cli_parity.js && node dist/test_catalog_quality.js && node dist/test_royal_stack_golden.js && node dist/test_capability_pack_schema.js && node dist/test_capability_genesis_engine.js && node dist/test_workflow_schema_backward_compat.js && node dist/test_service_mode_explicit_only.js && node dist/test_deep_research_capability.js && node dist/test_guided_creator_cli.js && node dist/test_governance_v13_policies.js && node dist/test_gateway_telegram_guardrails.js && node dist/test_cron_manager.js && node dist/test_profile_toolset_mcp.js && node dist/test_squad_skill_design_llm.js && node dist/test_workflow_condition_parser.js && node dist/test_security_download_and_scan.js && node dist/test_host_installers_gemini_codex.js && node dist/test_toolset_enforcement.js && node dist/test_creator_placeholders_completed.js && node dist/test_performance_latency.js && node dist/test_post_mission_evaluation.js && node dist/test_governance_scope_ledger.js && node dist/test_consequence_forecast_brain.js && node dist/test_remote_publish.js && node dist/test_process_sandbox.js && node dist/test_v15_e2e_integration.js && node dist/test_doctor_sandbox_check.js && node dist/test_task_executor_sandbox_optin.js && node dist/test_forecast_brain_wiring.js && node dist/test_status_command.js && node dist/test_logs_command.js && node dist/test_agent_creator.js && node dist/test_matrix_rain.js && node dist/test_chat_tui.js && node dist/test_config_tui.js",
167
+ "prepublishOnly": "npm run test:all",
168
+ "test:no-automatic-messages": "npm run build && node dist/test_no_automatic_messages.js",
169
+ "test:gateway-fast-ack": "npm run build && node dist/test_gateway_fast_ack.js"
165
170
  },
166
171
  "dependencies": {
167
172
  "@anthropic-ai/sdk": "^0.86.1",
168
173
  "@google/generative-ai": "^0.24.1",
174
+ "@openai/codex": "^0.130.0",
169
175
  "axios": "^1.15.0",
170
176
  "commander": "^11.1.0",
171
177
  "dotenv": "^16.3.1",
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/reauth-providers.sh
3
+ # Guided re-authentication for OpenLife LLM providers: Codex (gpt-5.5), Gemini, Anthropic.
4
+ # Idempotent. Per-provider menu (reauth/skip/edit-key). Validates each via real API call.
5
+ # Restarts daemon if running. Final status table.
6
+
7
+ set -u
8
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9
+ cd "$ROOT"
10
+
11
+ # Matrix-green ANSI palette (matches the new TUI theme)
12
+ GREEN=$'\033[38;5;46m'
13
+ GREEN_HI=$'\033[38;5;82m\033[1m'
14
+ GREEN_DIM=$'\033[38;5;22m'
15
+ RED=$'\033[38;5;196m'
16
+ YELLOW=$'\033[38;5;226m'
17
+ RESET=$'\033[0m'
18
+
19
+ say() { printf '%s%s%s\n' "$GREEN" "$*" "$RESET"; }
20
+ hi() { printf '%s%s%s\n' "$GREEN_HI" "$*" "$RESET"; }
21
+ warn() { printf '%s%s%s\n' "$YELLOW" "$*" "$RESET"; }
22
+ err() { printf '%s%s%s\n' "$RED" "$*" "$RESET" 1>&2; }
23
+ hr() { printf '%s' "$GREEN_DIM"; printf '─%.0s' $(seq 1 60); printf '%s\n' "$RESET"; }
24
+
25
+ # ─────────────────────────────────────────────────────────────
26
+ # Probe — current credential state
27
+ # ─────────────────────────────────────────────────────────────
28
+ probe_codex() {
29
+ command -v codex >/dev/null 2>&1 || { echo "missing"; return; }
30
+ if codex login status 2>/dev/null | grep -qi 'logged in'; then
31
+ # Cached status doesn't guarantee the refresh token is valid. Do a 8s probe.
32
+ if timeout 8 codex exec --model gpt-5.5 --skip-git-repo-check 'OK' >/dev/null 2>&1; then
33
+ echo "ok"
34
+ else
35
+ echo "stale"
36
+ fi
37
+ else
38
+ echo "logged-out"
39
+ fi
40
+ }
41
+
42
+ probe_gemini() {
43
+ command -v gemini >/dev/null 2>&1 || { echo "missing"; return; }
44
+ if gemini login status 2>/dev/null | grep -qi 'logged in'; then
45
+ echo "ok"
46
+ else
47
+ echo "logged-out"
48
+ fi
49
+ }
50
+
51
+ probe_anthropic() {
52
+ local key
53
+ key="$(grep -E '^ANTHROPIC_API_KEY=' "$ROOT/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' | tr -d "'")"
54
+ if [[ -z "$key" ]]; then echo "missing"; return; fi
55
+ local code
56
+ code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 8 \
57
+ -H "x-api-key: $key" -H "anthropic-version: 2023-06-01" \
58
+ -H "content-type: application/json" \
59
+ -d '{"model":"claude-haiku-4-5","max_tokens":4,"messages":[{"role":"user","content":"ok"}]}' \
60
+ https://api.anthropic.com/v1/messages 2>/dev/null || echo 000)"
61
+ case "$code" in
62
+ 200) echo "ok" ;;
63
+ 401|403) echo "auth-bad" ;;
64
+ 429) echo "rate-limited" ;;
65
+ *) echo "http-$code" ;;
66
+ esac
67
+ }
68
+
69
+ print_status_table() {
70
+ hr
71
+ printf '%s%-12s %-12s %s%s\n' "$GREEN_HI" "PROVIDER" "STATUS" "DETAIL" "$RESET"
72
+ hr
73
+ printf '%-12s %-12s %s\n' "codex" "$1" "(gpt-5.5 via Codex OAuth)"
74
+ printf '%-12s %-12s %s\n' "gemini" "$2" "(google-genai OAuth)"
75
+ printf '%-12s %-12s %s\n' "anthropic" "$3" "(ANTHROPIC_API_KEY in .env)"
76
+ hr
77
+ }
78
+
79
+ # ─────────────────────────────────────────────────────────────
80
+ # Per-provider menus
81
+ # ─────────────────────────────────────────────────────────────
82
+ menu_for() {
83
+ local name="$1" status="$2"
84
+ echo
85
+ hi "── $name ──"
86
+ echo " current status: $status"
87
+ echo " 1) reauth 2) skip 3) edit key (anthropic only)"
88
+ read -r -p " choose [1/2/3]: " choice
89
+ echo "$choice"
90
+ }
91
+
92
+ reauth_codex() {
93
+ say "Logging out of Codex…"
94
+ codex logout 2>/dev/null || true
95
+ hi "Opening Codex login (browser). Complete the flow, then press ENTER here."
96
+ codex login || { err "codex login failed"; return 1; }
97
+ if timeout 12 codex exec --model gpt-5.5 --skip-git-repo-check 'OK' >/dev/null 2>&1; then
98
+ hi "✓ codex gpt-5.5 responding"
99
+ return 0
100
+ fi
101
+ err "codex still failing after login. Inspect: codex login status"
102
+ return 1
103
+ }
104
+
105
+ reauth_gemini() {
106
+ say "Logging out of Gemini…"
107
+ gemini logout 2>/dev/null || true
108
+ hi "Opening Gemini login (device-auth). Follow on-screen URL, then return."
109
+ gemini login --device-auth || { err "gemini login failed"; return 1; }
110
+ hi "✓ gemini login completed (quota state checked on next call)"
111
+ return 0
112
+ }
113
+
114
+ reauth_anthropic_edit_key() {
115
+ echo
116
+ warn "Paste your ANTHROPIC_API_KEY (sk-ant-…). Input is hidden."
117
+ read -r -s -p " key: " newkey
118
+ echo
119
+ [[ -z "$newkey" ]] && { warn "(empty — keeping current key)"; return 0; }
120
+ # Idempotent .env edit: remove existing line then append.
121
+ local envfile="$ROOT/.env"
122
+ touch "$envfile"
123
+ grep -v -E '^ANTHROPIC_API_KEY=' "$envfile" > "$envfile.tmp" 2>/dev/null || true
124
+ mv "$envfile.tmp" "$envfile"
125
+ printf 'ANTHROPIC_API_KEY=%s\n' "$newkey" >> "$envfile"
126
+ say "✓ .env updated"
127
+ }
128
+
129
+ # ─────────────────────────────────────────────────────────────
130
+ # Daemon restart helper
131
+ # ─────────────────────────────────────────────────────────────
132
+ maybe_restart_daemon() {
133
+ local snapshot
134
+ snapshot="$(node bin/openlife.js status 2>/dev/null || echo '{}')"
135
+ if echo "$snapshot" | grep -q '"fresh":true'; then
136
+ say "Daemon is running — restarting to pick up new credentials…"
137
+ node bin/openlife.js restart >/dev/null 2>&1 || true
138
+ nohup node bin/openlife.js start --daemon >/dev/null 2>&1 &
139
+ disown 2>/dev/null || true
140
+ # Give it 3 seconds to boot + revalidate executors
141
+ sleep 3
142
+ say "✓ daemon restarted"
143
+ else
144
+ say "Daemon not running — skipping restart"
145
+ fi
146
+ }
147
+
148
+ # ─────────────────────────────────────────────────────────────
149
+ # Main
150
+ # ─────────────────────────────────────────────────────────────
151
+ hi "╔══════════════════════════════════════════════════════════╗"
152
+ hi "║ OPENLIFE · provider reauth ║"
153
+ hi "╚══════════════════════════════════════════════════════════╝"
154
+ say "Probing current credentials…"
155
+
156
+ CODEX="$(probe_codex)"
157
+ GEMINI="$(probe_gemini)"
158
+ ANTHROPIC="$(probe_anthropic)"
159
+ print_status_table "$CODEX" "$GEMINI" "$ANTHROPIC"
160
+
161
+ # Codex
162
+ case "$CODEX" in ok) say "codex already healthy — skipping menu" ;; *)
163
+ case "$(menu_for codex "$CODEX")" in
164
+ 1) reauth_codex && CODEX=ok || CODEX=failed ;;
165
+ 2) say "(skipped)" ;;
166
+ *) say "(skipped)" ;;
167
+ esac
168
+ ;; esac
169
+
170
+ # Gemini
171
+ case "$GEMINI" in ok) say "gemini already healthy — skipping menu" ;; *)
172
+ case "$(menu_for gemini "$GEMINI")" in
173
+ 1) reauth_gemini && GEMINI=ok || GEMINI=failed ;;
174
+ 2) say "(skipped)" ;;
175
+ *) say "(skipped)" ;;
176
+ esac
177
+ ;; esac
178
+
179
+ # Anthropic
180
+ case "$ANTHROPIC" in ok) say "anthropic already healthy — skipping menu" ;; *)
181
+ case "$(menu_for anthropic "$ANTHROPIC")" in
182
+ 1|3) reauth_anthropic_edit_key
183
+ ANTHROPIC="$(probe_anthropic)" ;;
184
+ 2) say "(skipped)" ;;
185
+ *) say "(skipped)" ;;
186
+ esac
187
+ ;; esac
188
+
189
+ # Final state
190
+ echo
191
+ hi "── final state ──"
192
+ print_status_table "$CODEX" "$GEMINI" "$ANTHROPIC"
193
+
194
+ # Restart daemon if running so executors revalidate
195
+ maybe_restart_daemon
196
+
197
+ # Show status snapshot
198
+ echo
199
+ hi "── openlife status ──"
200
+ node bin/openlife.js status 2>/dev/null \
201
+ | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{const j=JSON.parse(d);console.log("heartbeat fresh:",j.heartbeat&&j.heartbeat.fresh);(j.executors||[]).forEach(e=>console.log(` ${e.executor.padEnd(8)} avail=${e.available} reason=${e.reason}`));}catch(e){console.error("status parse failed");}});' \
202
+ 2>/dev/null || warn "status command unavailable"
203
+
204
+ # Exit code: 0 if at least one provider is ok
205
+ [[ "$CODEX" == "ok" || "$GEMINI" == "ok" || "$ANTHROPIC" == "ok" ]] && exit 0 || exit 1