selftune 0.2.13 → 0.2.15

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.
Files changed (60) hide show
  1. package/apps/local-dashboard/dist/assets/index-BMIS6uUh.css +2 -0
  2. package/apps/local-dashboard/dist/assets/index-DOu3iLD9.js +16 -0
  3. package/apps/local-dashboard/dist/assets/vendor-ui-DIwlrGlb.js +12 -0
  4. package/apps/local-dashboard/dist/index.html +3 -3
  5. package/cli/selftune/activation-rules.ts +24 -48
  6. package/cli/selftune/analytics.ts +13 -11
  7. package/cli/selftune/badge/badge.ts +13 -9
  8. package/cli/selftune/canonical-export.ts +6 -6
  9. package/cli/selftune/constants.ts +7 -0
  10. package/cli/selftune/contribute/bundle.ts +9 -44
  11. package/cli/selftune/contribute/contribute.ts +2 -1
  12. package/cli/selftune/cron/setup.ts +3 -1
  13. package/cli/selftune/dashboard-contract.ts +22 -0
  14. package/cli/selftune/dashboard.ts +10 -5
  15. package/cli/selftune/eval/baseline.ts +20 -30
  16. package/cli/selftune/eval/hooks-to-evals.ts +27 -34
  17. package/cli/selftune/eval/import-skillsbench.ts +21 -8
  18. package/cli/selftune/eval/unit-test-cli.ts +22 -11
  19. package/cli/selftune/evolution/description-quality.ts +224 -0
  20. package/cli/selftune/evolution/evolve-body.ts +17 -10
  21. package/cli/selftune/evolution/evolve.ts +70 -57
  22. package/cli/selftune/evolution/rollback.ts +7 -6
  23. package/cli/selftune/grading/auto-grade.ts +27 -35
  24. package/cli/selftune/grading/grade-session.ts +24 -30
  25. package/cli/selftune/hooks/auto-activate.ts +12 -3
  26. package/cli/selftune/hooks/evolution-guard.ts +14 -24
  27. package/cli/selftune/hooks/prompt-log.ts +7 -9
  28. package/cli/selftune/hooks/session-stop.ts +0 -8
  29. package/cli/selftune/index.ts +66 -69
  30. package/cli/selftune/ingestors/claude-replay.ts +29 -14
  31. package/cli/selftune/ingestors/codex-rollout.ts +15 -5
  32. package/cli/selftune/ingestors/codex-wrapper.ts +15 -13
  33. package/cli/selftune/ingestors/openclaw-ingest.ts +24 -5
  34. package/cli/selftune/ingestors/opencode-ingest.ts +9 -4
  35. package/cli/selftune/init.ts +14 -9
  36. package/cli/selftune/localdb/queries.ts +57 -0
  37. package/cli/selftune/monitoring/watch.ts +39 -38
  38. package/cli/selftune/normalization.ts +2 -23
  39. package/cli/selftune/orchestrate.ts +224 -24
  40. package/cli/selftune/routes/skill-report.ts +17 -0
  41. package/cli/selftune/schedule.ts +74 -14
  42. package/cli/selftune/sync.ts +7 -3
  43. package/cli/selftune/types.ts +44 -10
  44. package/cli/selftune/utils/cli-error.ts +102 -0
  45. package/cli/selftune/utils/jsonl.ts +2 -0
  46. package/cli/selftune/workflows/workflows.ts +23 -17
  47. package/package.json +3 -1
  48. package/packages/ui/src/components/RecentActivityFeed.tsx +86 -0
  49. package/packages/ui/src/components/index.ts +1 -0
  50. package/packages/ui/src/components/section-cards.tsx +13 -0
  51. package/skill/SKILL.md +1 -1
  52. package/skill/Workflows/Evolve.md +4 -0
  53. package/skill/Workflows/Initialize.md +8 -8
  54. package/skill/Workflows/Orchestrate.md +11 -7
  55. package/skill/Workflows/Schedule.md +11 -0
  56. package/skill/references/logs.md +22 -21
  57. package/skill/settings_snippet.json +29 -6
  58. package/apps/local-dashboard/dist/assets/index-4_dAY17K.js +0 -16
  59. package/apps/local-dashboard/dist/assets/index-BxV5WZHc.css +0 -2
  60. package/apps/local-dashboard/dist/assets/vendor-ui-7xD7fNEU.js +0 -12
@@ -29,6 +29,11 @@
29
29
  * selftune hook <name> — Run a hook by name (prompt-log, session-stop, etc.)
30
30
  */
31
31
 
32
+ import { CLIError, handleCLIError } from "./utils/cli-error.js";
33
+
34
+ process.on("uncaughtException", handleCLIError);
35
+ process.on("unhandledRejection", handleCLIError);
36
+
32
37
  const command = process.argv[2];
33
38
 
34
39
  if (command === "--help" || command === "-h") {
@@ -84,6 +89,7 @@ if (!command) {
84
89
  // Show status by default — same as `selftune status`
85
90
  const { cliMain: statusMain } = await import("./status.js");
86
91
  statusMain();
92
+ process.exit(0);
87
93
  }
88
94
 
89
95
  // Route to the appropriate subcommand module.
@@ -142,10 +148,11 @@ Run 'selftune ingest <agent> --help' for agent-specific options.`);
142
148
  break;
143
149
  }
144
150
  default:
145
- console.error(
146
- `Unknown ingest agent: ${sub}\nRun 'selftune ingest --help' for available agents.`,
151
+ throw new CLIError(
152
+ `Unknown ingest agent: ${sub}`,
153
+ "UNKNOWN_COMMAND",
154
+ "selftune ingest --help",
147
155
  );
148
- process.exit(1);
149
156
  }
150
157
  break;
151
158
  }
@@ -182,10 +189,11 @@ Run 'selftune grade <subcommand> --help' for subcommand-specific options.`);
182
189
  break;
183
190
  }
184
191
  default:
185
- console.error(
186
- `Unknown grade mode: ${sub}\nRun 'selftune grade --help' for available modes.`,
192
+ throw new CLIError(
193
+ `Unknown grade mode: ${sub}`,
194
+ "UNKNOWN_COMMAND",
195
+ "selftune grade --help",
187
196
  );
188
- process.exit(1);
189
197
  }
190
198
  }
191
199
  break;
@@ -223,10 +231,11 @@ Run 'selftune evolve <subcommand> --help' for subcommand-specific options.`);
223
231
  break;
224
232
  }
225
233
  default:
226
- console.error(
227
- `Unknown evolve target: ${sub}\nRun 'selftune evolve --help' for available targets.`,
234
+ throw new CLIError(
235
+ `Unknown evolve target: ${sub}`,
236
+ "UNKNOWN_COMMAND",
237
+ "selftune evolve --help",
228
238
  );
229
- process.exit(1);
230
239
  }
231
240
  }
232
241
  break;
@@ -289,13 +298,18 @@ Run 'selftune eval <action> --help' for action-specific options.`);
289
298
  }));
290
299
  } catch (error) {
291
300
  const message = error instanceof Error ? error.message : String(error);
292
- console.error(`Invalid arguments: ${message}`);
293
- console.error("Run 'selftune eval composability --help' for usage.");
294
- process.exit(1);
301
+ throw new CLIError(
302
+ `Invalid arguments: ${message}`,
303
+ "INVALID_FLAG",
304
+ "selftune eval composability --help",
305
+ );
295
306
  }
296
307
  if (!values.skill) {
297
- console.error("[ERROR] --skill <name> is required.");
298
- process.exit(1);
308
+ throw new CLIError(
309
+ "--skill <name> is required.",
310
+ "MISSING_FLAG",
311
+ "selftune eval composability --skill <name>",
312
+ );
299
313
  }
300
314
  const logPath = values["telemetry-log"] ?? TELEMETRY_LOG;
301
315
  let telemetry: unknown[];
@@ -316,8 +330,11 @@ Run 'selftune eval <action> --help' for action-specific options.`);
316
330
  }
317
331
  const rawWindow = values.window as string | undefined;
318
332
  if (rawWindow !== undefined && !/^[1-9]\d*$/.test(rawWindow)) {
319
- console.error("Invalid --window value. Use a positive integer number of days.");
320
- process.exit(1);
333
+ throw new CLIError(
334
+ "Invalid --window value. Use a positive integer number of days.",
335
+ "INVALID_FLAG",
336
+ "selftune eval composability --skill <name> --window 30",
337
+ );
321
338
  }
322
339
  const windowSize = rawWindow === undefined ? undefined : Number(rawWindow);
323
340
  const report = analyzeComposability(values.skill, telemetry, windowSize);
@@ -325,10 +342,11 @@ Run 'selftune eval <action> --help' for action-specific options.`);
325
342
  break;
326
343
  }
327
344
  default:
328
- console.error(
329
- `Unknown eval action: ${sub}\nRun 'selftune eval --help' for available actions.`,
345
+ throw new CLIError(
346
+ `Unknown eval action: ${sub}`,
347
+ "UNKNOWN_COMMAND",
348
+ "selftune eval --help",
330
349
  );
331
- process.exit(1);
332
350
  }
333
351
  break;
334
352
  }
@@ -457,10 +475,11 @@ Run 'selftune cron <subcommand> --help' for subcommand-specific options.`);
457
475
  break;
458
476
  }
459
477
  default:
460
- console.error(
461
- `Unknown cron subcommand: ${sub}\nRun 'selftune cron --help' for available subcommands.`,
478
+ throw new CLIError(
479
+ `Unknown cron subcommand: ${sub}`,
480
+ "UNKNOWN_COMMAND",
481
+ "selftune cron --help",
462
482
  );
463
- process.exit(1);
464
483
  }
465
484
  break;
466
485
  }
@@ -505,9 +524,7 @@ Run 'selftune cron <subcommand> --help' for subcommand-specific options.`);
505
524
  }));
506
525
  } catch (error) {
507
526
  const message = error instanceof Error ? error.message : String(error);
508
- console.error(`Invalid arguments: ${message}`);
509
- console.error("Run 'selftune export --help' for usage.");
510
- process.exit(1);
527
+ throw new CLIError(`Invalid arguments: ${message}`, "INVALID_FLAG", "selftune export --help");
511
528
  }
512
529
  if (values.help) {
513
530
  console.log(`selftune export — Export SQLite data to JSONL files
@@ -544,9 +561,7 @@ Options:
544
561
  }
545
562
  } catch (err: unknown) {
546
563
  const message = err instanceof Error ? err.message : String(err);
547
- console.error(`Export failed: ${message}`);
548
- console.error("Ensure the SQLite database exists. Run 'selftune sync' first if needed.");
549
- process.exit(1);
564
+ throw new CLIError(`Export failed: ${message}`, "OPERATION_FAILED", "selftune sync");
550
565
  }
551
566
  break;
552
567
  }
@@ -590,8 +605,11 @@ Run 'selftune alpha <subcommand> --help' for subcommand-specific options.`);
590
605
  }));
591
606
  } catch (error) {
592
607
  const message = error instanceof Error ? error.message : String(error);
593
- console.error(`Invalid arguments: ${message}`);
594
- process.exit(1);
608
+ throw new CLIError(
609
+ `Invalid arguments: ${message}`,
610
+ "INVALID_FLAG",
611
+ "selftune alpha upload --help",
612
+ );
595
613
  }
596
614
  if (values.help) {
597
615
  console.log(`selftune alpha upload — Run a manual alpha data upload cycle
@@ -619,44 +637,20 @@ Output:
619
637
  const identity = readAlphaIdentity(SELFTUNE_CONFIG_PATH);
620
638
  if (!identity?.enrolled) {
621
639
  const guidance = getAlphaGuidance(identity);
622
- console.log(
623
- JSON.stringify(
624
- {
625
- enrolled: false,
626
- prepared: 0,
627
- sent: 0,
628
- failed: 0,
629
- skipped: 0,
630
- guidance,
631
- },
632
- null,
633
- 2,
634
- ),
640
+ throw new CLIError(
641
+ `[alpha upload] ${guidance.message}`,
642
+ "OPERATION_FAILED",
643
+ guidance.next_command,
635
644
  );
636
- console.error(`[alpha upload] ${guidance.message}`);
637
- console.error(`[alpha upload] Next: ${guidance.next_command}`);
638
- process.exit(1);
639
645
  }
640
646
 
641
647
  if (!identity.user_id?.trim() || !identity.api_key?.trim()) {
642
648
  const guidance = getAlphaGuidance(identity);
643
- console.log(
644
- JSON.stringify(
645
- {
646
- enrolled: true,
647
- prepared: 0,
648
- sent: 0,
649
- failed: 0,
650
- skipped: 0,
651
- guidance,
652
- },
653
- null,
654
- 2,
655
- ),
649
+ throw new CLIError(
650
+ `[alpha upload] ${guidance.message}`,
651
+ "OPERATION_FAILED",
652
+ guidance.next_command,
656
653
  );
657
- console.error(`[alpha upload] ${guidance.message}`);
658
- console.error(`[alpha upload] Next: ${guidance.next_command}`);
659
- process.exit(1);
660
654
  }
661
655
 
662
656
  const db = getDb();
@@ -742,10 +736,11 @@ Output:
742
736
  break;
743
737
  }
744
738
  default:
745
- console.error(
746
- `Unknown alpha subcommand: ${sub}\nRun 'selftune alpha --help' for available subcommands.`,
739
+ throw new CLIError(
740
+ `Unknown alpha subcommand: ${sub}`,
741
+ "UNKNOWN_COMMAND",
742
+ "selftune alpha --help",
747
743
  );
748
- process.exit(1);
749
744
  }
750
745
  break;
751
746
  }
@@ -767,8 +762,11 @@ Output:
767
762
  };
768
763
  if (!hookName || !HOOK_MAP[hookName]) {
769
764
  const available = Object.keys(HOOK_MAP).join(", ");
770
- console.error(`Unknown hook: ${hookName ?? "(none)"}\nAvailable hooks: ${available}`);
771
- process.exit(1);
765
+ throw new CLIError(
766
+ `Unknown hook: ${hookName ?? "(none)"}. Available: ${available}`,
767
+ "UNKNOWN_COMMAND",
768
+ "selftune hook prompt-log",
769
+ );
772
770
  }
773
771
  const { resolve, dirname } = await import("node:path");
774
772
  const { fileURLToPath } = await import("node:url");
@@ -782,6 +780,5 @@ Output:
782
780
  break;
783
781
  }
784
782
  default:
785
- console.error(`Unknown command: ${command}\nRun 'selftune --help' for available commands.`);
786
- process.exit(1);
783
+ throw new CLIError(`Unknown command: ${command}`, "UNKNOWN_COMMAND", "selftune --help");
787
784
  }
@@ -55,6 +55,7 @@ import type {
55
55
  SessionTelemetryRecord,
56
56
  TranscriptMetrics,
57
57
  } from "../types.js";
58
+ import { CLIError, handleCLIError } from "../utils/cli-error.js";
58
59
  import { loadMarker, saveMarker } from "../utils/jsonl.js";
59
60
  import { isActionableQueryText } from "../utils/query-filter.js";
60
61
  import {
@@ -326,26 +327,36 @@ export function buildCanonicalRecordsFromReplay(session: ParsedSession): Canonic
326
327
 
327
328
  // --- CLI main ---
328
329
  export function cliMain(): void {
329
- const { values } = parseArgs({
330
- options: {
331
- "projects-dir": { type: "string", default: CLAUDE_CODE_PROJECTS_DIR },
332
- since: { type: "string" },
333
- "dry-run": { type: "boolean", default: false },
334
- force: { type: "boolean", default: false },
335
- verbose: { type: "boolean", short: "v", default: false },
336
- },
337
- strict: true,
338
- });
330
+ let values: Record<string, string | boolean | undefined>;
331
+ try {
332
+ ({ values } = parseArgs({
333
+ options: {
334
+ "projects-dir": { type: "string", default: CLAUDE_CODE_PROJECTS_DIR },
335
+ since: { type: "string" },
336
+ "dry-run": { type: "boolean", default: false },
337
+ force: { type: "boolean", default: false },
338
+ verbose: { type: "boolean", short: "v", default: false },
339
+ },
340
+ strict: true,
341
+ }));
342
+ } catch (err) {
343
+ throw new CLIError(
344
+ err instanceof Error ? err.message : String(err),
345
+ "INVALID_FLAG",
346
+ "selftune ingest claude --since 2026-01-01",
347
+ );
348
+ }
339
349
 
340
350
  const projectsDir = values["projects-dir"] ?? CLAUDE_CODE_PROJECTS_DIR;
341
351
  let since: Date | undefined;
342
352
  if (values.since) {
343
353
  since = new Date(values.since);
344
354
  if (Number.isNaN(since.getTime())) {
345
- console.error(
346
- `Error: Invalid --since date: "${values.since}". Use a valid date format (e.g., 2026-01-01).`,
355
+ throw new CLIError(
356
+ `Invalid --since date: "${values.since}"`,
357
+ "INVALID_FLAG",
358
+ "selftune ingest claude --since 2026-01-01",
347
359
  );
348
- process.exit(1);
349
360
  }
350
361
  }
351
362
 
@@ -401,5 +412,9 @@ export function cliMain(): void {
401
412
  }
402
413
 
403
414
  if (import.meta.main) {
404
- cliMain();
415
+ try {
416
+ cliMain();
417
+ } catch (err) {
418
+ handleCLIError(err);
419
+ }
405
420
  }
@@ -27,6 +27,11 @@ import { basename, join } from "node:path";
27
27
  import { parseArgs } from "node:util";
28
28
 
29
29
  import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
30
+ import {
31
+ writeQueryToDb,
32
+ writeSessionTelemetryToDb,
33
+ writeSkillUsageToDb,
34
+ } from "../localdb/direct-write.js";
30
35
  import {
31
36
  appendCanonicalRecords,
32
37
  buildCanonicalExecutionFact,
@@ -44,7 +49,8 @@ import type {
44
49
  SessionTelemetryRecord,
45
50
  SkillUsageRecord,
46
51
  } from "../types.js";
47
- import { appendJsonl, loadMarker, saveMarker } from "../utils/jsonl.js";
52
+ import { handleCLIError } from "../utils/cli-error.js";
53
+ import { loadMarker, saveMarker } from "../utils/jsonl.js";
48
54
  import { extractActionableQueryText } from "../utils/query-filter.js";
49
55
  import {
50
56
  classifySkillPath,
@@ -490,7 +496,7 @@ export function ingestFile(
490
496
  query: prompt,
491
497
  source: "codex_rollout",
492
498
  };
493
- appendJsonl(queryLogPath, queryRecord, "all_queries");
499
+ writeQueryToDb(queryRecord);
494
500
  }
495
501
 
496
502
  // Write telemetry — explicitly select SessionTelemetryRecord fields
@@ -513,7 +519,7 @@ export function ingestFile(
513
519
  output_tokens: parsed.output_tokens,
514
520
  rollout_path: parsed.rollout_path,
515
521
  };
516
- appendJsonl(telemetryLogPath, telemetry, "session_telemetry");
522
+ writeSessionTelemetryToDb(telemetry);
517
523
 
518
524
  // Write skill triggers
519
525
  for (const skillName of skills) {
@@ -537,7 +543,7 @@ export function ingestFile(
537
543
  triggered: true,
538
544
  source: isExplicit ? "codex_rollout_explicit" : "codex_rollout",
539
545
  };
540
- appendJsonl(skillLogPath, skillRecord, "skill_usage");
546
+ writeSkillUsageToDb(skillRecord);
541
547
  }
542
548
 
543
549
  // --- Canonical normalization records (additive) ---
@@ -712,5 +718,9 @@ export function cliMain(): void {
712
718
  }
713
719
 
714
720
  if (import.meta.main) {
715
- cliMain();
721
+ try {
722
+ cliMain();
723
+ } catch (err) {
724
+ handleCLIError(err);
725
+ }
716
726
  }
@@ -11,16 +11,19 @@
11
11
  * The wrapper:
12
12
  * 1. Runs `codex exec --json <your args>` as a subprocess
13
13
  * 2. Streams stdout (JSONL events) to your terminal in real time
14
- * 3. Parses events and writes to:
15
- * ~/.claude/all_queries_log.jsonl
16
- * ~/.claude/session_telemetry_log.jsonl
17
- * ~/.claude/skill_usage_log.jsonl
14
+ * 3. Writes to SQLite via writeQueryToDb, writeSessionTelemetryToDb,
15
+ * writeSkillUsageToDb (Phase 3: JSONL writes removed)
18
16
  */
19
17
 
20
18
  import { homedir } from "node:os";
21
19
  import { join } from "node:path";
22
20
 
23
21
  import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
22
+ import {
23
+ writeQueryToDb,
24
+ writeSessionTelemetryToDb,
25
+ writeSkillUsageToDb,
26
+ } from "../localdb/direct-write.js";
24
27
  import {
25
28
  appendCanonicalRecords,
26
29
  buildCanonicalExecutionFact,
@@ -38,7 +41,6 @@ import type {
38
41
  SessionTelemetryRecord,
39
42
  SkillUsageRecord,
40
43
  } from "../types.js";
41
- import { appendJsonl } from "../utils/jsonl.js";
42
44
  import {
43
45
  classifySkillPath,
44
46
  containsWholeSkillMention,
@@ -240,8 +242,8 @@ export function parseJsonlStream(lines: string[], skillNames: Set<string>): Pars
240
242
  };
241
243
  }
242
244
 
243
- /** Append the user prompt to all_queries_log.jsonl. */
244
- export function logQuery(prompt: string, sessionId: string, logPath: string = QUERY_LOG): void {
245
+ /** Write the user prompt to SQLite. */
246
+ export function logQuery(prompt: string, sessionId: string, _logPath: string = QUERY_LOG): void {
245
247
  if (!prompt || prompt.length < 4) return;
246
248
  const record: QueryLogRecord = {
247
249
  timestamp: new Date().toISOString(),
@@ -249,16 +251,16 @@ export function logQuery(prompt: string, sessionId: string, logPath: string = QU
249
251
  query: prompt,
250
252
  source: "codex",
251
253
  };
252
- appendJsonl(logPath, record);
254
+ writeQueryToDb(record);
253
255
  }
254
256
 
255
- /** Append session metrics to session_telemetry_log.jsonl. */
257
+ /** Write session metrics to SQLite. */
256
258
  export function logTelemetry(
257
259
  metrics: Omit<ParsedCodexStream, "thread_id">,
258
260
  prompt: string,
259
261
  sessionId: string,
260
262
  cwd: string,
261
- logPath: string = TELEMETRY_LOG,
263
+ _logPath: string = TELEMETRY_LOG,
262
264
  ): void {
263
265
  const record: SessionTelemetryRecord = {
264
266
  timestamp: new Date().toISOString(),
@@ -269,10 +271,10 @@ export function logTelemetry(
269
271
  source: "codex",
270
272
  ...metrics,
271
273
  };
272
- appendJsonl(logPath, record);
274
+ writeSessionTelemetryToDb(record);
273
275
  }
274
276
 
275
- /** Append a skill trigger to skill_usage_log.jsonl. */
277
+ /** Write a skill trigger to SQLite. */
276
278
  export function logSkillTrigger(
277
279
  skillName: string,
278
280
  prompt: string,
@@ -300,7 +302,7 @@ export function logSkillTrigger(
300
302
  triggered: true,
301
303
  source: "codex",
302
304
  };
303
- appendJsonl(logPath, record);
305
+ writeSkillUsageToDb(record);
304
306
  }
305
307
 
306
308
  /** Build canonical records from a wrapper session. */
@@ -34,6 +34,11 @@ import {
34
34
  SKILL_LOG,
35
35
  TELEMETRY_LOG,
36
36
  } from "../constants.js";
37
+ import {
38
+ writeQueryToDb,
39
+ writeSessionTelemetryToDb,
40
+ writeSkillUsageToDb,
41
+ } from "../localdb/direct-write.js";
37
42
  import {
38
43
  appendCanonicalRecords,
39
44
  buildCanonicalExecutionFact,
@@ -46,7 +51,7 @@ import {
46
51
  deriveSkillInvocationId,
47
52
  } from "../normalization.js";
48
53
  import type { CanonicalRecord, QueryLogRecord, SkillUsageRecord } from "../types.js";
49
- import { appendJsonl, loadMarker, saveMarker } from "../utils/jsonl.js";
54
+ import { loadMarker, saveMarker } from "../utils/jsonl.js";
50
55
 
51
56
  export interface SessionFile {
52
57
  agentId: string;
@@ -389,11 +394,25 @@ export function writeSession(
389
394
  query: prompt,
390
395
  source: session.source,
391
396
  };
392
- appendJsonl(queryLogPath, queryRecord, "all_queries");
397
+ writeQueryToDb(queryRecord);
393
398
  }
394
399
 
395
- const { query: _q, ...telemetry } = session;
396
- appendJsonl(telemetryLogPath, telemetry, "session_telemetry");
400
+ // Build a SessionTelemetryRecord-shaped object for SQLite
401
+ writeSessionTelemetryToDb({
402
+ timestamp: session.timestamp,
403
+ session_id: session.session_id,
404
+ cwd: session.cwd,
405
+ transcript_path: session.transcript_path,
406
+ tool_calls: session.tool_calls,
407
+ total_tool_calls: session.total_tool_calls,
408
+ bash_commands: session.bash_commands,
409
+ skills_triggered: session.skills_triggered,
410
+ assistant_turns: session.assistant_turns,
411
+ errors_encountered: session.errors_encountered,
412
+ transcript_chars: session.transcript_chars,
413
+ last_user_query: session.last_user_query,
414
+ source: session.source,
415
+ });
397
416
 
398
417
  for (const skillName of skills) {
399
418
  const skillRecord: SkillUsageRecord = {
@@ -405,7 +424,7 @@ export function writeSession(
405
424
  triggered: true,
406
425
  source: session.source,
407
426
  };
408
- appendJsonl(skillLogPath, skillRecord, "skill_usage");
427
+ writeSkillUsageToDb(skillRecord);
409
428
  }
410
429
 
411
430
  // --- Canonical normalization records (additive) ---
@@ -27,6 +27,11 @@ import { basename, join } from "node:path";
27
27
  import { parseArgs } from "node:util";
28
28
 
29
29
  import { CANONICAL_LOG, QUERY_LOG, SKILL_LOG, TELEMETRY_LOG } from "../constants.js";
30
+ import {
31
+ writeQueryToDb,
32
+ writeSessionTelemetryToDb,
33
+ writeSkillUsageToDb,
34
+ } from "../localdb/direct-write.js";
30
35
  import {
31
36
  appendCanonicalRecords,
32
37
  buildCanonicalExecutionFact,
@@ -44,7 +49,7 @@ import type {
44
49
  SessionTelemetryRecord,
45
50
  SkillUsageRecord,
46
51
  } from "../types.js";
47
- import { appendJsonl, loadMarker, saveMarker } from "../utils/jsonl.js";
52
+ import { loadMarker, saveMarker } from "../utils/jsonl.js";
48
53
 
49
54
  const XDG_DATA_HOME = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
50
55
  const DEFAULT_DATA_DIR = join(XDG_DATA_HOME, "opencode");
@@ -528,7 +533,7 @@ export function writeSession(
528
533
  query: prompt,
529
534
  source: session.source,
530
535
  };
531
- appendJsonl(queryLogPath, queryRecord, "all_queries");
536
+ writeQueryToDb(queryRecord);
532
537
  }
533
538
 
534
539
  const telemetry: SessionTelemetryRecord = {
@@ -546,7 +551,7 @@ export function writeSession(
546
551
  last_user_query: session.last_user_query,
547
552
  source: session.source,
548
553
  };
549
- appendJsonl(telemetryLogPath, telemetry, "session_telemetry");
554
+ writeSessionTelemetryToDb(telemetry);
550
555
 
551
556
  for (const skillName of skills) {
552
557
  const skillRecord: SkillUsageRecord = {
@@ -558,7 +563,7 @@ export function writeSession(
558
563
  triggered: true,
559
564
  source: session.source,
560
565
  };
561
- appendJsonl(skillLogPath, skillRecord, "skill_usage");
566
+ writeSkillUsageToDb(skillRecord);
562
567
  }
563
568
 
564
569
  // --- Canonical normalization records (additive) ---
@@ -44,6 +44,7 @@ import {
44
44
  import { installAgentFiles } from "./claude-agents.js";
45
45
  import { CLAUDE_CODE_HOOK_KEYS, SELFTUNE_CONFIG_DIR, SELFTUNE_CONFIG_PATH } from "./constants.js";
46
46
  import type { AgentCommandGuidance, AlphaIdentity, SelftuneConfig } from "./types.js";
47
+ import { CLIError, handleCLIError } from "./utils/cli-error.js";
47
48
  import { hookKeyHasSelftuneEntry } from "./utils/hooks.js";
48
49
  import { detectAgent } from "./utils/llm-call.js";
49
50
 
@@ -692,8 +693,11 @@ export async function cliMain(): Promise<void> {
692
693
  try {
693
694
  validateAlphaMetadataFlags(values.alpha, values["alpha-email"], values["alpha-name"]);
694
695
  } catch (error) {
695
- console.error(error instanceof Error ? error.message : String(error));
696
- process.exit(1);
696
+ throw new CLIError(
697
+ error instanceof Error ? error.message : String(error),
698
+ "INVALID_FLAG",
699
+ "Pass --alpha along with --alpha-email and --alpha-name.",
700
+ );
697
701
  }
698
702
 
699
703
  // Check for existing config without force
@@ -890,10 +894,11 @@ export async function cliMain(): Promise<void> {
890
894
  });
891
895
 
892
896
  if (!scheduleResult.activated) {
893
- console.error(
894
- "Failed to activate the autonomous scheduler. Re-run with --schedule-format or use `selftune schedule --install --dry-run` to inspect the generated artifacts first.",
897
+ throw new CLIError(
898
+ "Failed to activate the autonomous scheduler.",
899
+ "OPERATION_FAILED",
900
+ "Re-run with --schedule-format or use `selftune schedule --install --dry-run` to inspect the generated artifacts first.",
895
901
  );
896
- process.exit(1);
897
902
  }
898
903
 
899
904
  console.log(
@@ -906,10 +911,11 @@ export async function cliMain(): Promise<void> {
906
911
  }),
907
912
  );
908
913
  } catch (err) {
909
- console.error(
914
+ throw new CLIError(
910
915
  `Failed to enable autonomy: ${err instanceof Error ? err.message : String(err)}`,
916
+ "OPERATION_FAILED",
917
+ "Re-run with --schedule-format or use `selftune schedule --install --dry-run` to inspect artifacts.",
911
918
  );
912
- process.exit(1);
913
919
  }
914
920
  }
915
921
  }
@@ -947,7 +953,6 @@ if (isMain) {
947
953
  console.error(JSON.stringify(err.payload));
948
954
  process.exit(1);
949
955
  }
950
- console.error(`[FATAL] ${err}`);
951
- process.exit(1);
956
+ handleCLIError(err);
952
957
  });
953
958
  }