@openlife/cli 1.7.5 → 1.7.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.
@@ -3,12 +3,15 @@
3
3
  // Uses CannedAnswerProvider so no real stdin/tty is required.
4
4
  //
5
5
  // Question order in wizard.run() (when no pre-existing install):
6
- // 1) profile (choice: 0=framework, 1=autonomous)
6
+ // 1) profile (choice: 0=framework, 1=autonomous, 2=both)
7
7
  // 2) host (choice: 0=claude-code, 1=gemini-cli, 2=codex)
8
- // 3) model order (text: blank or comma list)
9
- // 4) telegram? (yesNo, autonomous only)
10
- // 5) skip doctor? (yesNo)
11
- // 6) confirm preview (yesNo)
8
+ // 3) wantsApiKeys (yesNo)
9
+ // 3a-d) [if Y] OPENAI / ANTHROPIC / GEMINI / OPENROUTER pastes (text)
10
+ // 4) wantsOAuth (yesNo)
11
+ // 5) model order (text: blank or comma list)
12
+ // 6) telegram? (yesNo, autonomous only)
13
+ // 7) skip doctor? (yesNo)
14
+ // 8) confirm preview (yesNo)
12
15
  //
13
16
  // When pre-existing install detected, an extra choice prompt fires FIRST:
14
17
  // 0) abort 1) reinstall 2) repair
@@ -76,8 +79,8 @@ function writeExistingInstall(root) {
76
79
  async function scenario1HappyPathFrameworkClaudeCode() {
77
80
  const root = tempRoot();
78
81
  try {
79
- // profile=framework(0), host=claude-code(0), models='', skipDoctor=false, confirm=true
80
- const provider = new InstallWizard_1.CannedAnswerProvider([0, 0, '', false, true]);
82
+ // profile=framework(0), host=claude-code(0), apiKeys=N, oauth=N, models='', skipDoctor=false, confirm=true
83
+ const provider = new InstallWizard_1.CannedAnswerProvider([0, 0, false, false, '', false, true]);
81
84
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
82
85
  const result = await wizard.run();
83
86
  assert(result.ok === true, 'scenario1: expected ok=true');
@@ -88,7 +91,8 @@ async function scenario1HappyPathFrameworkClaudeCode() {
88
91
  assert(result.options.skipDoctor === false, 'scenario1: skipDoctor should be false');
89
92
  assert(result.options.modelOrder === undefined, 'scenario1: modelOrder should be undefined when blank');
90
93
  assert(result.preExistingAction === undefined, 'scenario1: no pre-existing action expected');
91
- assert(!result.warnings, 'scenario1: no warnings expected for default claude-code');
94
+ // SKIPPED_API_KEYS warning is expected when wantsApiKeys=false
95
+ assert(Array.isArray(result.warnings) && result.warnings.some((w) => w.includes('SKIPPED_API_KEYS')), 'scenario1: should warn SKIPPED_API_KEYS when api-key prompt skipped');
92
96
  console.log('✅ scenario 1: happy path framework + claude-code');
93
97
  }
94
98
  finally {
@@ -98,8 +102,8 @@ async function scenario1HappyPathFrameworkClaudeCode() {
98
102
  async function scenario2HappyPathAutonomousClaudeCode() {
99
103
  const root = tempRoot();
100
104
  try {
101
- // profile=autonomous(1), host=claude-code(0), models='', telegram=true, skipDoctor=false, confirm=true
102
- const provider = new InstallWizard_1.CannedAnswerProvider([1, 0, '', true, false, true]);
105
+ // profile=autonomous(1), host=claude-code(0), apiKeys=N, oauth=N, models='', telegram=true, skipDoctor=false, confirm=true
106
+ const provider = new InstallWizard_1.CannedAnswerProvider([1, 0, false, false, '', true, false, true]);
103
107
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
104
108
  const result = await wizard.run();
105
109
  assert(result.ok === true, 'scenario2: expected ok=true');
@@ -107,7 +111,8 @@ async function scenario2HappyPathAutonomousClaudeCode() {
107
111
  return;
108
112
  assert(result.options.profile === 'autonomous', 'scenario2: profile should be autonomous');
109
113
  assert(result.options.host === 'claude-code', 'scenario2: host should be claude-code');
110
- assert(!result.warnings, 'scenario2: no warnings when telegram already configured');
114
+ // SKIPPED_API_KEYS warning is expected when wantsApiKeys=false
115
+ assert(Array.isArray(result.warnings) && result.warnings.some((w) => w.includes('SKIPPED_API_KEYS')), 'scenario2: should warn SKIPPED_API_KEYS when api-key prompt skipped');
111
116
  console.log('✅ scenario 2: happy path autonomous + claude-code');
112
117
  }
113
118
  finally {
@@ -117,8 +122,8 @@ async function scenario2HappyPathAutonomousClaudeCode() {
117
122
  async function scenario3UserAbortsOnConfirm() {
118
123
  const root = tempRoot();
119
124
  try {
120
- // framework, claude-code, blank models, skipDoctor=false, confirm=false (abort)
121
- const provider = new InstallWizard_1.CannedAnswerProvider([0, 0, '', false, false]);
125
+ // framework, claude-code, apiKeys=N, oauth=N, blank models, skipDoctor=false, confirm=false (abort)
126
+ const provider = new InstallWizard_1.CannedAnswerProvider([0, 0, false, false, '', false, false]);
122
127
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
123
128
  const result = await wizard.run();
124
129
  assert(result.ok === false, 'scenario3: expected ok=false');
@@ -156,8 +161,8 @@ async function scenario5PreExistingRepair() {
156
161
  const root = tempRoot();
157
162
  try {
158
163
  writeExistingInstall(root);
159
- // 2=repair, then full flow: framework, claude-code, '', skipDoctor=false, confirm=true
160
- const provider = new InstallWizard_1.CannedAnswerProvider([2, 0, 0, '', false, true]);
164
+ // 2=repair, then full flow: framework, claude-code, apiKeys=N, oauth=N, '', skipDoctor=false, confirm=true
165
+ const provider = new InstallWizard_1.CannedAnswerProvider([2, 0, 0, false, false, '', false, true]);
161
166
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
162
167
  const result = await wizard.run();
163
168
  assert(result.ok === true, 'scenario5: expected ok=true');
@@ -175,8 +180,8 @@ async function scenario6PreExistingReinstall() {
175
180
  const root = tempRoot();
176
181
  try {
177
182
  writeExistingInstall(root);
178
- // 1=reinstall, framework, claude-code, '', skipDoctor=false, confirm=true
179
- const provider = new InstallWizard_1.CannedAnswerProvider([1, 0, 0, '', false, true]);
183
+ // 1=reinstall, framework, claude-code, apiKeys=N, oauth=N, '', skipDoctor=false, confirm=true
184
+ const provider = new InstallWizard_1.CannedAnswerProvider([1, 0, 0, false, false, '', false, true]);
180
185
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
181
186
  const result = await wizard.run();
182
187
  assert(result.ok === true, 'scenario6: expected ok=true');
@@ -192,8 +197,8 @@ async function scenario6PreExistingReinstall() {
192
197
  async function scenario7UnsupportedHostGeminiCli() {
193
198
  const root = tempRoot();
194
199
  try {
195
- // framework, host=1 (gemini-cli, not yet supported), blank models, skipDoctor=false, confirm=true
196
- const provider = new InstallWizard_1.CannedAnswerProvider([0, 1, '', false, true]);
200
+ // framework, host=1 (gemini-cli, not yet supported), apiKeys=N, oauth=N, blank models, skipDoctor=false, confirm=true
201
+ const provider = new InstallWizard_1.CannedAnswerProvider([0, 1, false, false, '', false, true]);
197
202
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
198
203
  const result = await wizard.run();
199
204
  assert(result.ok === true, 'scenario7: expected ok=true even with unsupported host');
@@ -212,8 +217,8 @@ async function scenario8CustomModelChain() {
212
217
  const root = tempRoot();
213
218
  try {
214
219
  const models = 'gemini-api/gemini-3.1-pro-preview,openai-api/gpt-5.4-mini';
215
- // framework, claude-code, models=custom, skipDoctor=false, confirm=true
216
- const provider = new InstallWizard_1.CannedAnswerProvider([0, 0, models, false, true]);
220
+ // framework, claude-code, apiKeys=N, oauth=N, models=custom, skipDoctor=false, confirm=true
221
+ const provider = new InstallWizard_1.CannedAnswerProvider([0, 0, false, false, models, false, true]);
217
222
  const wizard = new InstallWizard_1.InstallWizard(root, provider);
218
223
  const result = await wizard.run();
219
224
  assert(result.ok === true, 'scenario8: expected ok=true');
@@ -252,6 +257,89 @@ async function scenario9OutOfAnswersThrows() {
252
257
  cleanup(root);
253
258
  }
254
259
  }
260
+ async function scenario10WithApiKeysPersists() {
261
+ const root = tempRoot();
262
+ try {
263
+ // framework, claude-code, apiKeys=Y, paste 4 keys (openai+anthropic, skip gemini+openrouter),
264
+ // oauth=N, blank models, skipDoctor=false, confirm=true
265
+ const provider = new InstallWizard_1.CannedAnswerProvider([
266
+ 0, 0,
267
+ true, // wantsApiKeys
268
+ 'openai-test-key', // openai paste (non-secret test fixture)
269
+ 'anthropic-test-key', // anthropic paste (non-secret test fixture)
270
+ '', // gemini skip
271
+ '', // openrouter skip
272
+ false, // wantsOAuth
273
+ '', // models
274
+ false, // skipDoctor
275
+ true, // confirm
276
+ ]);
277
+ const wizard = new InstallWizard_1.InstallWizard(root, provider);
278
+ const result = await wizard.run();
279
+ assert(result.ok === true, 'scenario10: expected ok=true');
280
+ if (!result.ok)
281
+ return;
282
+ // .env should contain the pasted keys
283
+ const envPath = path.join(root, '.env');
284
+ assert(fs.existsSync(envPath), 'scenario10: .env should exist after wizard');
285
+ const envContent = fs.readFileSync(envPath, 'utf-8');
286
+ assert(envContent.includes('OPENAI_API_KEY=openai-test-key'), 'scenario10: .env should have OPENAI_API_KEY');
287
+ assert(envContent.includes('ANTHROPIC_API_KEY=anthropic-test-key'), 'scenario10: .env should have ANTHROPIC_API_KEY');
288
+ assert(!envContent.includes('GEMINI_API_KEY='), 'scenario10: .env should NOT have GEMINI_API_KEY (skipped)');
289
+ // SKIPPED_API_KEYS warning should NOT fire when at least one key was provided
290
+ if (result.warnings) {
291
+ assert(!result.warnings.some((w) => w.includes('SKIPPED_API_KEYS')), 'scenario10: should NOT warn SKIPPED_API_KEYS when keys were provided');
292
+ }
293
+ console.log('✅ scenario 10: API keys collected and persisted to .env');
294
+ }
295
+ finally {
296
+ cleanup(root);
297
+ }
298
+ }
299
+ async function scenario11AbortDoesNotPersistApiKeys() {
300
+ const root = tempRoot();
301
+ try {
302
+ const provider = new InstallWizard_1.CannedAnswerProvider([
303
+ 0, 0,
304
+ true,
305
+ 'openai-abort-test-key',
306
+ '',
307
+ '',
308
+ '',
309
+ false,
310
+ '',
311
+ false,
312
+ false,
313
+ ]);
314
+ const wizard = new InstallWizard_1.InstallWizard(root, provider);
315
+ const result = await wizard.run();
316
+ assert(result.ok === false, 'scenario11: expected ok=false');
317
+ assert(!fs.existsSync(path.join(root, '.env')), 'scenario11: aborted wizard must not write .env');
318
+ console.log('✅ scenario 11: abort after API-key collection does not persist .env');
319
+ }
320
+ finally {
321
+ cleanup(root);
322
+ }
323
+ }
324
+ async function scenario12ProfileBothMapsToAutonomous() {
325
+ const root = tempRoot();
326
+ try {
327
+ // profile=both(2), claude-code, apiKeys=N, oauth=N, models='', telegram=true (autonomous path),
328
+ // skipDoctor=false, confirm=true
329
+ const provider = new InstallWizard_1.CannedAnswerProvider([2, 0, false, false, '', true, false, true]);
330
+ const wizard = new InstallWizard_1.InstallWizard(root, provider);
331
+ const result = await wizard.run();
332
+ assert(result.ok === true, 'scenario11: expected ok=true');
333
+ if (!result.ok)
334
+ return;
335
+ assert(result.options.profile === 'autonomous', 'scenario11: profile=both should map internally to autonomous');
336
+ assert(Array.isArray(result.warnings) && result.warnings.some((w) => w.includes('INSTALLING_BOTH')), 'scenario11: should emit INSTALLING_BOTH warning so caller knows both layers were intended');
337
+ console.log('✅ scenario 11: profile=both maps to autonomous with INSTALLING_BOTH warning');
338
+ }
339
+ finally {
340
+ cleanup(root);
341
+ }
342
+ }
255
343
  async function main() {
256
344
  console.log('🧪 test_install_wizard — Story 3.5 regression suite');
257
345
  await scenario1HappyPathFrameworkClaudeCode();
@@ -263,6 +351,9 @@ async function main() {
263
351
  await scenario7UnsupportedHostGeminiCli();
264
352
  await scenario8CustomModelChain();
265
353
  await scenario9OutOfAnswersThrows();
354
+ await scenario10WithApiKeysPersists();
355
+ await scenario11AbortDoesNotPersistApiKeys();
356
+ await scenario12ProfileBothMapsToAutonomous();
266
357
  console.log('');
267
358
  console.log('TEST_INSTALL_WIZARD_OK');
268
359
  }
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ // test_logs_command — verifies filter / tail / since semantics + duration
3
+ // parser of `openlife logs`.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const LogsCommand_1 = require("./cli/LogsCommand");
42
+ function assert(cond, msg) {
43
+ if (!cond) {
44
+ console.error(`❌ ASSERT FAILED: ${msg}`);
45
+ process.exit(1);
46
+ }
47
+ }
48
+ function tempRoot() {
49
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openlife-logs-'));
50
+ fs.mkdirSync(path.join(root, '.openlife'), { recursive: true });
51
+ return root;
52
+ }
53
+ function cleanup(root) {
54
+ try {
55
+ fs.rmSync(root, { recursive: true, force: true });
56
+ }
57
+ catch {
58
+ // best-effort
59
+ }
60
+ }
61
+ function writeJsonl(root, filename, entries) {
62
+ fs.writeFileSync(path.join(root, '.openlife', filename), entries.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
63
+ }
64
+ function scenarioDurationParser() {
65
+ assert((0, LogsCommand_1.parseDurationToSeconds)('30s') === 30, '30s → 30');
66
+ assert((0, LogsCommand_1.parseDurationToSeconds)('5m') === 300, '5m → 300');
67
+ assert((0, LogsCommand_1.parseDurationToSeconds)('1h') === 3600, '1h → 3600');
68
+ assert((0, LogsCommand_1.parseDurationToSeconds)('2d') === 172800, '2d → 172800');
69
+ assert((0, LogsCommand_1.parseDurationToSeconds)('bogus') === null, 'bogus → null');
70
+ assert((0, LogsCommand_1.parseDurationToSeconds)('-1m') === null, 'negative → null');
71
+ console.log('✅ scenario 1: parseDurationToSeconds covers s/m/h/d + rejects garbage');
72
+ }
73
+ function scenarioTailDefault() {
74
+ const root = tempRoot();
75
+ try {
76
+ const entries = [];
77
+ const now = Date.now();
78
+ for (let i = 0; i < 50; i++) {
79
+ entries.push({ ts: new Date(now - (50 - i) * 1000).toISOString(), summary: `entry ${i}` });
80
+ }
81
+ writeJsonl(root, 'governance-ledger.jsonl', entries);
82
+ const collected = (0, LogsCommand_1.collectLogs)({ root, tail: 5 });
83
+ assert(collected.length === 5, `expected 5 entries, got ${collected.length}`);
84
+ assert(collected[collected.length - 1].parsed.summary === 'entry 49', 'last entry should be the newest');
85
+ console.log('✅ scenario 2: tail=5 returns last 5 chronological entries');
86
+ }
87
+ finally {
88
+ cleanup(root);
89
+ }
90
+ }
91
+ function scenarioFilterBySubsystem() {
92
+ const root = tempRoot();
93
+ try {
94
+ const now = Date.now();
95
+ const govEntries = [
96
+ { ts: new Date(now).toISOString(), summary: 'gov-entry' },
97
+ ];
98
+ const mediaEntries = [
99
+ { ts: new Date(now).toISOString(), summary: 'media-entry' },
100
+ ];
101
+ writeJsonl(root, 'governance-ledger.jsonl', govEntries);
102
+ writeJsonl(root, 'media-routing.log.jsonl', mediaEntries);
103
+ const govOnly = (0, LogsCommand_1.collectLogs)({ root, filter: 'governance' });
104
+ assert(govOnly.length === 1, `expected 1 governance entry, got ${govOnly.length}`);
105
+ assert(govOnly[0].subsystem === 'governance-ledger', 'subsystem should match');
106
+ const mediaOnly = (0, LogsCommand_1.collectLogs)({ root, filter: 'media' });
107
+ assert(mediaOnly.length === 1, `expected 1 media entry, got ${mediaOnly.length}`);
108
+ assert(mediaOnly[0].subsystem === 'media-routing', 'media subsystem name');
109
+ console.log('✅ scenario 3: --filter narrows by subsystem id');
110
+ }
111
+ finally {
112
+ cleanup(root);
113
+ }
114
+ }
115
+ function scenarioSinceFiltersOldEntries() {
116
+ const root = tempRoot();
117
+ try {
118
+ const now = Date.now();
119
+ const entries = [
120
+ { ts: new Date(now - 3600_000).toISOString(), summary: 'one-hour-ago' },
121
+ { ts: new Date(now - 60_000).toISOString(), summary: 'one-minute-ago' },
122
+ { ts: new Date(now - 5_000).toISOString(), summary: 'five-seconds-ago' },
123
+ ];
124
+ writeJsonl(root, 'governance-ledger.jsonl', entries);
125
+ const recent = (0, LogsCommand_1.collectLogs)({ root, since: '5m' });
126
+ assert(recent.length === 2, `expected 2 entries within 5m, got ${recent.length}`);
127
+ assert(!recent.some((e) => e.parsed.summary === 'one-hour-ago'), 'one-hour-ago should be excluded');
128
+ console.log('✅ scenario 4: --since=5m excludes older entries');
129
+ }
130
+ finally {
131
+ cleanup(root);
132
+ }
133
+ }
134
+ function scenarioRenderHumanAndJson() {
135
+ const root = tempRoot();
136
+ try {
137
+ writeJsonl(root, 'governance-ledger.jsonl', [
138
+ { ts: new Date().toISOString(), summary: 'test summary' },
139
+ ]);
140
+ const entries = (0, LogsCommand_1.collectLogs)({ root });
141
+ const human = (0, LogsCommand_1.renderLogsHuman)(entries);
142
+ assert(human.includes('[governance-ledger]'), 'human render includes subsystem');
143
+ assert(human.includes('test summary'), 'human render includes summary');
144
+ const jsonLine = (0, LogsCommand_1.renderLogsJson)(entries);
145
+ const parsed = JSON.parse(jsonLine);
146
+ assert(parsed.subsystem === 'governance-ledger', 'json render preserves subsystem');
147
+ console.log('✅ scenario 5: human + JSON renderers both produce expected shape');
148
+ }
149
+ finally {
150
+ cleanup(root);
151
+ }
152
+ }
153
+ function scenarioNoEntriesGracefullyHandled() {
154
+ const root = tempRoot();
155
+ try {
156
+ const entries = (0, LogsCommand_1.collectLogs)({ root });
157
+ assert(entries.length === 0, 'no jsonl files → empty result');
158
+ const human = (0, LogsCommand_1.renderLogsHuman)(entries);
159
+ assert(human.includes('no entries'), 'human render reports empty');
160
+ console.log('✅ scenario 6: empty state renders helpful message');
161
+ }
162
+ finally {
163
+ cleanup(root);
164
+ }
165
+ }
166
+ function main() {
167
+ console.log('🧪 test_logs_command — jsonl viewer');
168
+ scenarioDurationParser();
169
+ scenarioTailDefault();
170
+ scenarioFilterBySubsystem();
171
+ scenarioSinceFiltersOldEntries();
172
+ scenarioRenderHumanAndJson();
173
+ scenarioNoEntriesGracefullyHandled();
174
+ console.log('');
175
+ console.log('TEST_LOGS_COMMAND_OK');
176
+ }
177
+ main();
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ // test_status_command — verifies the shape and overall-derivation logic of
3
+ // `openlife status`. Uses a temp .openlife/ scratch dir so we don't rely on
4
+ // (or pollute) the maintainer's local runtime state.
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const StatusCommand_1 = require("./cli/StatusCommand");
43
+ function assert(cond, msg) {
44
+ if (!cond) {
45
+ console.error(`❌ ASSERT FAILED: ${msg}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ function tempRoot() {
50
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openlife-status-'));
51
+ fs.mkdirSync(path.join(root, '.openlife'), { recursive: true });
52
+ return root;
53
+ }
54
+ function cleanup(root) {
55
+ try {
56
+ fs.rmSync(root, { recursive: true, force: true });
57
+ }
58
+ catch {
59
+ // best-effort
60
+ }
61
+ }
62
+ function writeJson(root, filename, payload) {
63
+ fs.writeFileSync(path.join(root, '.openlife', filename), JSON.stringify(payload, null, 2), 'utf-8');
64
+ }
65
+ function writeJsonl(root, filename, entries) {
66
+ fs.writeFileSync(path.join(root, '.openlife', filename), entries.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
67
+ }
68
+ function scenarioEmpty() {
69
+ const root = tempRoot();
70
+ try {
71
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
72
+ assert(report.overall === 'down', 'empty state should be down (no heartbeat, no executors)');
73
+ assert(report.heartbeat.present === false, 'no heartbeat in empty state');
74
+ assert(report.governance.ledgerPresent === false, 'no governance ledger in empty state');
75
+ assert(report.executors.length === 0, 'no executors in empty state');
76
+ assert(report.notes.includes('no_heartbeat'), 'should note no_heartbeat');
77
+ console.log('✅ scenario 1: empty state → overall=down with no_heartbeat note');
78
+ }
79
+ finally {
80
+ cleanup(root);
81
+ }
82
+ }
83
+ function scenarioHealthy() {
84
+ const root = tempRoot();
85
+ try {
86
+ const now = Date.now();
87
+ writeJson(root, 'heartbeat.json', { pid: 12345, host: 'test-host', ts: now, uptime_s: 100, startedAt: now - 100_000 });
88
+ writeJson(root, 'runtime-policy-status.json', {
89
+ executors: {
90
+ openai: { executor: 'openai', available: true, reason: 'ok', updatedAt: new Date(now).toISOString() },
91
+ anthropic: { executor: 'anthropic', available: true, reason: 'ok', updatedAt: new Date(now).toISOString() },
92
+ },
93
+ });
94
+ writeJsonl(root, 'governance-ledger.jsonl', [
95
+ { index: 1, ts: new Date(now).toISOString(), entryHash: 'aaa' },
96
+ { index: 2, ts: new Date(now).toISOString(), entryHash: 'bbb' },
97
+ ]);
98
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
99
+ assert(report.overall === 'healthy', `expected healthy, got ${report.overall} (notes: ${report.notes.join(',')})`);
100
+ assert(report.heartbeat.fresh === true, 'fresh heartbeat expected');
101
+ assert(report.executors.length === 2, 'two executors expected');
102
+ assert(report.executors.every((e) => e.available), 'all executors available');
103
+ assert(report.governance.entryCount === 2, 'two ledger entries expected');
104
+ assert(report.governance.lastEntryHash === 'bbb', 'last entry hash is bbb');
105
+ console.log('✅ scenario 2: healthy heartbeat + all executors + ledger → overall=healthy');
106
+ }
107
+ finally {
108
+ cleanup(root);
109
+ }
110
+ }
111
+ function scenarioDegradedOneExecutorDown() {
112
+ const root = tempRoot();
113
+ try {
114
+ const now = Date.now();
115
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: now, uptime_s: 1, startedAt: now });
116
+ writeJson(root, 'runtime-policy-status.json', {
117
+ executors: {
118
+ openai: { executor: 'openai', available: true, reason: 'ok' },
119
+ anthropic: { executor: 'anthropic', available: false, reason: 'auth expired', category: 'auth' },
120
+ },
121
+ });
122
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
123
+ assert(report.overall === 'degraded', `expected degraded, got ${report.overall}`);
124
+ assert(report.notes.some((n) => n.startsWith('executors_down:')), 'should note specific executors down');
125
+ console.log('✅ scenario 3: one executor down → overall=degraded');
126
+ }
127
+ finally {
128
+ cleanup(root);
129
+ }
130
+ }
131
+ function scenarioDownAllExecutorsDown() {
132
+ const root = tempRoot();
133
+ try {
134
+ const now = Date.now();
135
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: now, uptime_s: 1, startedAt: now });
136
+ writeJson(root, 'runtime-policy-status.json', {
137
+ executors: {
138
+ openai: { executor: 'openai', available: false, reason: 'rate limit' },
139
+ anthropic: { executor: 'anthropic', available: false, reason: 'auth expired' },
140
+ },
141
+ });
142
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
143
+ assert(report.overall === 'down', `expected down, got ${report.overall}`);
144
+ assert(report.notes.includes('all_executors_down'), 'should note all_executors_down');
145
+ console.log('✅ scenario 4: all executors down → overall=down');
146
+ }
147
+ finally {
148
+ cleanup(root);
149
+ }
150
+ }
151
+ function scenarioStaleHeartbeat() {
152
+ const root = tempRoot();
153
+ try {
154
+ const staleTs = Date.now() - (StatusCommand_1.HEARTBEAT_FRESH_S + 30) * 1000;
155
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: staleTs, uptime_s: 9999, startedAt: staleTs - 1000 });
156
+ writeJson(root, 'runtime-policy-status.json', {
157
+ executors: { openai: { executor: 'openai', available: true, reason: 'ok' } },
158
+ });
159
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
160
+ assert(report.overall === 'degraded', `expected degraded for stale beat, got ${report.overall}`);
161
+ assert(report.heartbeat.fresh === false, 'stale heartbeat is not fresh');
162
+ assert(report.notes.some((n) => n.startsWith('heartbeat_warning_')), 'should warn about stale heartbeat');
163
+ console.log('✅ scenario 5: stale heartbeat → overall=degraded with heartbeat_warning note');
164
+ }
165
+ finally {
166
+ cleanup(root);
167
+ }
168
+ }
169
+ function scenarioDeadHeartbeat() {
170
+ const root = tempRoot();
171
+ try {
172
+ const deadTs = Date.now() - (StatusCommand_1.HEARTBEAT_STALE_S + 60) * 1000;
173
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: deadTs, uptime_s: 9999, startedAt: deadTs - 1000 });
174
+ writeJson(root, 'runtime-policy-status.json', {
175
+ executors: { openai: { executor: 'openai', available: true, reason: 'ok' } },
176
+ });
177
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
178
+ assert(report.overall === 'down', `expected down for dead beat, got ${report.overall}`);
179
+ assert(report.notes.some((n) => n.startsWith('heartbeat_stale_')), 'should note heartbeat_stale_<age>s');
180
+ console.log('✅ scenario 6: dead heartbeat → overall=down');
181
+ }
182
+ finally {
183
+ cleanup(root);
184
+ }
185
+ }
186
+ function scenarioCorruptLedgerHandled() {
187
+ const root = tempRoot();
188
+ try {
189
+ const now = Date.now();
190
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: now, uptime_s: 1, startedAt: now });
191
+ writeJson(root, 'runtime-policy-status.json', {
192
+ executors: { openai: { executor: 'openai', available: true, reason: 'ok' } },
193
+ });
194
+ // Write a corrupt last line — must not throw.
195
+ fs.writeFileSync(path.join(root, '.openlife', 'governance-ledger.jsonl'), '{"index":1,"entryHash":"good","ts":"' + new Date(now).toISOString() + '"}\n{not-valid-json\n', 'utf-8');
196
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
197
+ assert(report.governance.entryCount === 2, 'should count both lines (parseability separate from count)');
198
+ // Last entry was corrupt → lastEntryHash undefined; gracefully degraded, doesn't throw.
199
+ assert(report.governance.lastEntryHash === undefined, 'corrupt last line yields no entryHash');
200
+ console.log('✅ scenario 7: corrupt ledger line handled gracefully');
201
+ }
202
+ finally {
203
+ cleanup(root);
204
+ }
205
+ }
206
+ function main() {
207
+ console.log('🧪 test_status_command — runtime state aggregator');
208
+ scenarioEmpty();
209
+ scenarioHealthy();
210
+ scenarioDegradedOneExecutorDown();
211
+ scenarioDownAllExecutorsDown();
212
+ scenarioStaleHeartbeat();
213
+ scenarioDeadHeartbeat();
214
+ scenarioCorruptLedgerHandled();
215
+ console.log('');
216
+ console.log('TEST_STATUS_COMMAND_OK');
217
+ }
218
+ main();