opencode-swarm-plugin 0.35.0 → 0.36.1

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 (52) hide show
  1. package/.hive/issues.jsonl +4 -4
  2. package/.hive/memories.jsonl +274 -1
  3. package/.turbo/turbo-build.log +4 -4
  4. package/.turbo/turbo-test.log +307 -307
  5. package/CHANGELOG.md +133 -0
  6. package/bin/swarm.ts +234 -179
  7. package/dist/compaction-hook.d.ts +54 -4
  8. package/dist/compaction-hook.d.ts.map +1 -1
  9. package/dist/eval-capture.d.ts +122 -17
  10. package/dist/eval-capture.d.ts.map +1 -1
  11. package/dist/index.d.ts +1 -7
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1278 -619
  14. package/dist/planning-guardrails.d.ts +121 -0
  15. package/dist/planning-guardrails.d.ts.map +1 -1
  16. package/dist/plugin.d.ts +9 -9
  17. package/dist/plugin.d.ts.map +1 -1
  18. package/dist/plugin.js +1283 -329
  19. package/dist/schemas/task.d.ts +0 -1
  20. package/dist/schemas/task.d.ts.map +1 -1
  21. package/dist/swarm-decompose.d.ts +0 -8
  22. package/dist/swarm-decompose.d.ts.map +1 -1
  23. package/dist/swarm-orchestrate.d.ts.map +1 -1
  24. package/dist/swarm-prompts.d.ts +0 -4
  25. package/dist/swarm-prompts.d.ts.map +1 -1
  26. package/dist/swarm-review.d.ts.map +1 -1
  27. package/dist/swarm.d.ts +0 -6
  28. package/dist/swarm.d.ts.map +1 -1
  29. package/evals/README.md +38 -0
  30. package/evals/coordinator-session.eval.ts +154 -0
  31. package/evals/fixtures/coordinator-sessions.ts +328 -0
  32. package/evals/lib/data-loader.ts +69 -0
  33. package/evals/scorers/coordinator-discipline.evalite-test.ts +536 -0
  34. package/evals/scorers/coordinator-discipline.ts +315 -0
  35. package/evals/scorers/index.ts +12 -0
  36. package/examples/plugin-wrapper-template.ts +747 -34
  37. package/package.json +2 -2
  38. package/src/compaction-hook.test.ts +234 -281
  39. package/src/compaction-hook.ts +221 -63
  40. package/src/eval-capture.test.ts +390 -0
  41. package/src/eval-capture.ts +168 -10
  42. package/src/index.ts +89 -2
  43. package/src/learning.integration.test.ts +0 -2
  44. package/src/planning-guardrails.test.ts +387 -2
  45. package/src/planning-guardrails.ts +289 -0
  46. package/src/plugin.ts +10 -10
  47. package/src/schemas/task.ts +0 -1
  48. package/src/swarm-decompose.ts +21 -8
  49. package/src/swarm-orchestrate.ts +44 -0
  50. package/src/swarm-prompts.ts +20 -0
  51. package/src/swarm-review.ts +41 -0
  52. package/src/swarm.integration.test.ts +0 -40
package/bin/swarm.ts CHANGED
@@ -983,7 +983,7 @@ function getPluginWrapper(): string {
983
983
  `[swarm] Could not read plugin template from ${templatePath}, using minimal wrapper`,
984
984
  );
985
985
  return `// Minimal fallback - install opencode-swarm-plugin globally for full functionality
986
- import { SwarmPlugin } from "opencode-swarm-plugin"
986
+ import SwarmPlugin from "opencode-swarm-plugin"
987
987
  export default SwarmPlugin
988
988
  `;
989
989
  }
@@ -1709,7 +1709,7 @@ async function doctor() {
1709
1709
  if (updateInfo) showUpdateNotification(updateInfo);
1710
1710
  }
1711
1711
 
1712
- async function setup() {
1712
+ async function setup(forceReinstall = false, nonInteractive = false) {
1713
1713
  console.clear();
1714
1714
  console.log(yellow(BANNER));
1715
1715
  console.log(getDecoratedBee());
@@ -1783,7 +1783,7 @@ async function setup() {
1783
1783
  legacyWorkerPath,
1784
1784
  ].filter((f) => existsSync(f));
1785
1785
 
1786
- if (existingFiles.length > 0) {
1786
+ if (existingFiles.length > 0 && !forceReinstall) {
1787
1787
  p.log.success("Swarm is already configured!");
1788
1788
  p.log.message(dim(" Found " + existingFiles.length + "/5 config files"));
1789
1789
 
@@ -1903,7 +1903,8 @@ async function setup() {
1903
1903
  p.log.step("Missing " + requiredMissing.length + " required dependencies");
1904
1904
 
1905
1905
  for (const { dep } of requiredMissing) {
1906
- const shouldInstall = await p.confirm({
1906
+ // In non-interactive mode, auto-install required deps
1907
+ const shouldInstall = nonInteractive ? true : await p.confirm({
1907
1908
  message: "Install " + dep.name + "? (" + dep.description + ")",
1908
1909
  initialValue: true,
1909
1910
  });
@@ -1931,8 +1932,8 @@ async function setup() {
1931
1932
  }
1932
1933
  }
1933
1934
 
1934
- // Only prompt for optional deps if there are missing ones
1935
- if (optionalMissing.length > 0) {
1935
+ // Only prompt for optional deps if there are missing ones (skip in non-interactive mode)
1936
+ if (optionalMissing.length > 0 && !nonInteractive) {
1936
1937
  const installable = optionalMissing.filter(
1937
1938
  (r) => r.dep.installType !== "manual",
1938
1939
  );
@@ -2099,135 +2100,157 @@ async function setup() {
2099
2100
  p.log.message(dim(' No OpenCode config found (skipping MCP check)'));
2100
2101
  }
2101
2102
 
2102
- // Model selection
2103
- p.log.step("Configuring swarm agents...");
2104
- p.log.message(dim(" Coordinator handles orchestration, worker executes tasks"));
2103
+ // Model defaults: opus for coordinator, sonnet for worker, haiku for lite
2104
+ const DEFAULT_COORDINATOR = "anthropic/claude-opus-4-5";
2105
+ const DEFAULT_WORKER = "anthropic/claude-sonnet-4-5";
2106
+ const DEFAULT_LITE = "anthropic/claude-haiku-4-5";
2107
+
2108
+ // Model selection (skip if non-interactive)
2109
+ let coordinatorModel: string;
2110
+ let workerModel: string;
2111
+ let liteModel: string;
2112
+
2113
+ if (nonInteractive) {
2114
+ coordinatorModel = DEFAULT_COORDINATOR;
2115
+ workerModel = DEFAULT_WORKER;
2116
+ liteModel = DEFAULT_LITE;
2117
+ p.log.step("Using default models:");
2118
+ p.log.message(dim(` Coordinator: ${coordinatorModel}`));
2119
+ p.log.message(dim(` Worker: ${workerModel}`));
2120
+ p.log.message(dim(` Lite: ${liteModel}`));
2121
+ } else {
2122
+ p.log.step("Configuring swarm agents...");
2123
+ p.log.message(dim(" Coordinator handles orchestration, worker executes tasks"));
2105
2124
 
2106
- const coordinatorModel = await p.select({
2107
- message: "Select coordinator model (for orchestration/planning):",
2108
- options: [
2109
- {
2110
- value: "anthropic/claude-sonnet-4-5",
2111
- label: "Claude Sonnet 4.5",
2112
- hint: "Best balance of speed and capability (recommended)",
2113
- },
2114
- {
2115
- value: "anthropic/claude-haiku-4-5",
2116
- label: "Claude Haiku 4.5",
2117
- hint: "Fast and cost-effective",
2118
- },
2119
- {
2120
- value: "anthropic/claude-opus-4-5",
2121
- label: "Claude Opus 4.5",
2122
- hint: "Most capable, slower",
2123
- },
2124
- {
2125
- value: "openai/gpt-4o",
2126
- label: "GPT-4o",
2127
- hint: "Fast, good for most tasks",
2128
- },
2129
- {
2130
- value: "openai/gpt-4-turbo",
2131
- label: "GPT-4 Turbo",
2132
- hint: "Powerful, more expensive",
2133
- },
2134
- {
2135
- value: "google/gemini-2.0-flash",
2136
- label: "Gemini 2.0 Flash",
2137
- hint: "Fast and capable",
2138
- },
2139
- {
2140
- value: "google/gemini-1.5-pro",
2141
- label: "Gemini 1.5 Pro",
2142
- hint: "More capable",
2143
- },
2144
- ],
2145
- initialValue: "anthropic/claude-sonnet-4-5",
2146
- });
2125
+ const selectedCoordinator = await p.select({
2126
+ message: "Select coordinator model (for orchestration/planning):",
2127
+ options: [
2128
+ {
2129
+ value: "anthropic/claude-opus-4-5",
2130
+ label: "Claude Opus 4.5",
2131
+ hint: "Most capable, best for complex orchestration (recommended)",
2132
+ },
2133
+ {
2134
+ value: "anthropic/claude-sonnet-4-5",
2135
+ label: "Claude Sonnet 4.5",
2136
+ hint: "Good balance of speed and capability",
2137
+ },
2138
+ {
2139
+ value: "anthropic/claude-haiku-4-5",
2140
+ label: "Claude Haiku 4.5",
2141
+ hint: "Fast and cost-effective",
2142
+ },
2143
+ {
2144
+ value: "openai/gpt-4o",
2145
+ label: "GPT-4o",
2146
+ hint: "Fast, good for most tasks",
2147
+ },
2148
+ {
2149
+ value: "openai/gpt-4-turbo",
2150
+ label: "GPT-4 Turbo",
2151
+ hint: "Powerful, more expensive",
2152
+ },
2153
+ {
2154
+ value: "google/gemini-2.0-flash",
2155
+ label: "Gemini 2.0 Flash",
2156
+ hint: "Fast and capable",
2157
+ },
2158
+ {
2159
+ value: "google/gemini-1.5-pro",
2160
+ label: "Gemini 1.5 Pro",
2161
+ hint: "More capable",
2162
+ },
2163
+ ],
2164
+ initialValue: DEFAULT_COORDINATOR,
2165
+ });
2147
2166
 
2148
- if (p.isCancel(coordinatorModel)) {
2149
- p.cancel("Setup cancelled");
2150
- process.exit(0);
2151
- }
2167
+ if (p.isCancel(selectedCoordinator)) {
2168
+ p.cancel("Setup cancelled");
2169
+ process.exit(0);
2170
+ }
2171
+ coordinatorModel = selectedCoordinator;
2152
2172
 
2153
- const workerModel = await p.select({
2154
- message: "Select worker model (for task execution):",
2155
- options: [
2156
- {
2157
- value: "anthropic/claude-haiku-4-5",
2158
- label: "Claude Haiku 4.5",
2159
- hint: "Fast and cost-effective (recommended)",
2160
- },
2161
- {
2162
- value: "anthropic/claude-sonnet-4-5",
2163
- label: "Claude Sonnet 4.5",
2164
- hint: "Best balance of speed and capability",
2165
- },
2166
- {
2167
- value: "anthropic/claude-opus-4-5",
2168
- label: "Claude Opus 4.5",
2169
- hint: "Most capable, slower",
2170
- },
2171
- {
2172
- value: "openai/gpt-4o",
2173
- label: "GPT-4o",
2174
- hint: "Fast, good for most tasks",
2175
- },
2176
- {
2177
- value: "openai/gpt-4-turbo",
2178
- label: "GPT-4 Turbo",
2179
- hint: "Powerful, more expensive",
2180
- },
2181
- {
2182
- value: "google/gemini-2.0-flash",
2183
- label: "Gemini 2.0 Flash",
2184
- hint: "Fast and capable",
2185
- },
2186
- {
2187
- value: "google/gemini-1.5-pro",
2188
- label: "Gemini 1.5 Pro",
2189
- hint: "More capable",
2190
- },
2191
- ],
2192
- initialValue: "anthropic/claude-haiku-4-5",
2193
- });
2173
+ const selectedWorker = await p.select({
2174
+ message: "Select worker model (for task execution):",
2175
+ options: [
2176
+ {
2177
+ value: "anthropic/claude-sonnet-4-5",
2178
+ label: "Claude Sonnet 4.5",
2179
+ hint: "Best balance of speed and capability (recommended)",
2180
+ },
2181
+ {
2182
+ value: "anthropic/claude-haiku-4-5",
2183
+ label: "Claude Haiku 4.5",
2184
+ hint: "Fast and cost-effective",
2185
+ },
2186
+ {
2187
+ value: "anthropic/claude-opus-4-5",
2188
+ label: "Claude Opus 4.5",
2189
+ hint: "Most capable, slower",
2190
+ },
2191
+ {
2192
+ value: "openai/gpt-4o",
2193
+ label: "GPT-4o",
2194
+ hint: "Fast, good for most tasks",
2195
+ },
2196
+ {
2197
+ value: "openai/gpt-4-turbo",
2198
+ label: "GPT-4 Turbo",
2199
+ hint: "Powerful, more expensive",
2200
+ },
2201
+ {
2202
+ value: "google/gemini-2.0-flash",
2203
+ label: "Gemini 2.0 Flash",
2204
+ hint: "Fast and capable",
2205
+ },
2206
+ {
2207
+ value: "google/gemini-1.5-pro",
2208
+ label: "Gemini 1.5 Pro",
2209
+ hint: "More capable",
2210
+ },
2211
+ ],
2212
+ initialValue: DEFAULT_WORKER,
2213
+ });
2194
2214
 
2195
- if (p.isCancel(workerModel)) {
2196
- p.cancel("Setup cancelled");
2197
- process.exit(0);
2198
- }
2215
+ if (p.isCancel(selectedWorker)) {
2216
+ p.cancel("Setup cancelled");
2217
+ process.exit(0);
2218
+ }
2219
+ workerModel = selectedWorker;
2199
2220
 
2200
- // Lite model selection for simple tasks (docs, tests)
2201
- const liteModel = await p.select({
2202
- message: "Select lite model (for docs, tests, simple edits):",
2203
- options: [
2204
- {
2205
- value: "anthropic/claude-haiku-4-5",
2206
- label: "Claude Haiku 4.5",
2207
- hint: "Fast and cost-effective (recommended)",
2208
- },
2209
- {
2210
- value: "anthropic/claude-sonnet-4-5",
2211
- label: "Claude Sonnet 4.5",
2212
- hint: "More capable, slower",
2213
- },
2214
- {
2215
- value: "openai/gpt-4o-mini",
2216
- label: "GPT-4o Mini",
2217
- hint: "Fast and cheap",
2218
- },
2219
- {
2220
- value: "google/gemini-2.0-flash",
2221
- label: "Gemini 2.0 Flash",
2222
- hint: "Fast and capable",
2223
- },
2224
- ],
2225
- initialValue: "anthropic/claude-haiku-4-5",
2226
- });
2221
+ // Lite model selection for simple tasks (docs, tests)
2222
+ const selectedLite = await p.select({
2223
+ message: "Select lite model (for docs, tests, simple edits):",
2224
+ options: [
2225
+ {
2226
+ value: "anthropic/claude-haiku-4-5",
2227
+ label: "Claude Haiku 4.5",
2228
+ hint: "Fast and cost-effective (recommended)",
2229
+ },
2230
+ {
2231
+ value: "anthropic/claude-sonnet-4-5",
2232
+ label: "Claude Sonnet 4.5",
2233
+ hint: "More capable, slower",
2234
+ },
2235
+ {
2236
+ value: "openai/gpt-4o-mini",
2237
+ label: "GPT-4o Mini",
2238
+ hint: "Fast and cheap",
2239
+ },
2240
+ {
2241
+ value: "google/gemini-2.0-flash",
2242
+ label: "Gemini 2.0 Flash",
2243
+ hint: "Fast and capable",
2244
+ },
2245
+ ],
2246
+ initialValue: DEFAULT_LITE,
2247
+ });
2227
2248
 
2228
- if (p.isCancel(liteModel)) {
2229
- p.cancel("Setup cancelled");
2230
- process.exit(0);
2249
+ if (p.isCancel(selectedLite)) {
2250
+ p.cancel("Setup cancelled");
2251
+ process.exit(0);
2252
+ }
2253
+ liteModel = selectedLite;
2231
2254
  }
2232
2255
 
2233
2256
  p.log.success("Selected models:");
@@ -2249,7 +2272,8 @@ async function setup() {
2249
2272
 
2250
2273
  // Write plugin and command files
2251
2274
  p.log.step("Writing configuration files...");
2252
- stats[writeFileWithStatus(pluginPath, getPluginWrapper(), "Plugin")]++;
2275
+ const pluginContent = getPluginWrapper().replace(/__SWARM_LITE_MODEL__/g, liteModel);
2276
+ stats[writeFileWithStatus(pluginPath, pluginContent, "Plugin")]++;
2253
2277
  stats[writeFileWithStatus(commandPath, SWARM_COMMAND, "Command")]++;
2254
2278
 
2255
2279
  // Write nested agent files (swarm/planner.md, swarm/worker.md, swarm/researcher.md)
@@ -2288,21 +2312,8 @@ async function setup() {
2288
2312
  );
2289
2313
 
2290
2314
  if (missingBundled.length > 0 || managedBundled.length > 0) {
2291
- const shouldSync = await p.confirm({
2292
- message:
2293
- "Sync bundled skills into your global skills directory? " +
2294
- (missingBundled.length > 0
2295
- ? `(${missingBundled.length} missing)`
2296
- : "(update managed skills)"),
2297
- initialValue: isReinstall || missingBundled.length > 0,
2298
- });
2299
-
2300
- if (p.isCancel(shouldSync)) {
2301
- p.cancel("Setup cancelled");
2302
- process.exit(0);
2303
- }
2304
-
2305
- if (shouldSync) {
2315
+ // Always sync bundled skills - no prompt needed
2316
+ {
2306
2317
  const syncSpinner = p.spinner();
2307
2318
  syncSpinner.start("Syncing bundled skills...");
2308
2319
  try {
@@ -2339,15 +2350,10 @@ async function setup() {
2339
2350
  }
2340
2351
  }
2341
2352
 
2342
- // Offer to update AGENTS.md with skill awareness
2353
+ // Always update AGENTS.md with skill awareness - no prompt needed
2343
2354
  const agentsPath = join(configDir, "AGENTS.md");
2344
2355
  if (existsSync(agentsPath)) {
2345
- const updateAgents = await p.confirm({
2346
- message: "Update AGENTS.md with skill awareness?",
2347
- initialValue: true,
2348
- });
2349
-
2350
- if (!p.isCancel(updateAgents) && updateAgents) {
2356
+ {
2351
2357
  const s = p.spinner();
2352
2358
  s.start("Updating AGENTS.md...");
2353
2359
 
@@ -2708,8 +2714,10 @@ async function help() {
2708
2714
  console.log(magenta(" " + getRandomMessage()));
2709
2715
  console.log(`
2710
2716
  ${cyan("Commands:")}
2711
- swarm setup Interactive installer - checks and installs dependencies
2712
- swarm doctor Health check - shows status of all dependencies
2717
+ swarm setup Interactive installer - checks and installs dependencies
2718
+ --reinstall, -r Skip prompt, go straight to reinstall
2719
+ --yes, -y Non-interactive with defaults (opus/sonnet/haiku)
2720
+ swarm doctor Health check - shows status of all dependencies
2713
2721
  swarm init Initialize beads in current project
2714
2722
  swarm config Show paths to generated config files
2715
2723
  swarm agents Update AGENTS.md with skill awareness
@@ -3101,17 +3109,43 @@ interface LogLine {
3101
3109
  time: string;
3102
3110
  module: string;
3103
3111
  msg: string;
3112
+ data?: Record<string, unknown>; // Extra structured data
3104
3113
  }
3105
3114
 
3106
- function parseLogLine(line: string): LogLine | null {
3115
+ function parseLogLine(line: string, sourceFile?: string): LogLine | null {
3107
3116
  try {
3108
3117
  const parsed = JSON.parse(line);
3109
- if (typeof parsed.level === "number" && parsed.time && parsed.msg) {
3118
+ if (parsed.time && parsed.msg) {
3119
+ // Handle both pino format (level: number) and plugin wrapper format (level: string)
3120
+ let level: number;
3121
+ if (typeof parsed.level === "number") {
3122
+ level = parsed.level;
3123
+ } else if (typeof parsed.level === "string") {
3124
+ level = levelNameToNumber(parsed.level);
3125
+ } else {
3126
+ level = 30; // default to info
3127
+ }
3128
+
3129
+ // Derive module from: explicit field, or source filename (e.g., "compaction.log" -> "compaction")
3130
+ let module = parsed.module;
3131
+ if (!module && sourceFile) {
3132
+ // Extract module from filename: "compaction.log" -> "compaction", "swarm.1log" -> "swarm"
3133
+ const match = sourceFile.match(/([^/]+?)(?:\.\d+)?\.?log$/);
3134
+ if (match) {
3135
+ module = match[1];
3136
+ }
3137
+ }
3138
+
3139
+ // Extract extra data (everything except core fields)
3140
+ const { level: _l, time: _t, module: _m, msg: _msg, ...extraData } = parsed;
3141
+ const hasExtraData = Object.keys(extraData).length > 0;
3142
+
3110
3143
  return {
3111
- level: parsed.level,
3144
+ level,
3112
3145
  time: parsed.time,
3113
- module: parsed.module || "unknown",
3146
+ module: module || "unknown",
3114
3147
  msg: parsed.msg,
3148
+ data: hasExtraData ? extraData : undefined,
3115
3149
  };
3116
3150
  }
3117
3151
  } catch {
@@ -3164,36 +3198,51 @@ function parseDuration(duration: string): number | null {
3164
3198
  return value * multipliers[unit];
3165
3199
  }
3166
3200
 
3167
- function formatLogLine(log: LogLine, useColor = true): string {
3201
+ function formatLogLine(log: LogLine, useColor = true, verbose = false): string {
3168
3202
  const timestamp = new Date(log.time).toLocaleTimeString();
3169
3203
  const levelName = levelToName(log.level);
3170
3204
  const module = log.module.padEnd(12);
3171
3205
  const levelStr = useColor ? levelToColor(log.level)(levelName) : levelName;
3172
3206
 
3173
- return `${timestamp} ${levelStr} ${module} ${log.msg}`;
3207
+ let output = `${timestamp} ${levelStr} ${module} ${log.msg}`;
3208
+
3209
+ // In verbose mode, pretty print the structured data
3210
+ if (verbose && log.data) {
3211
+ output += `\n${dim(JSON.stringify(log.data, null, 2))}`;
3212
+ }
3213
+
3214
+ return output;
3215
+ }
3216
+
3217
+ interface LogEntry {
3218
+ line: string;
3219
+ file: string;
3174
3220
  }
3175
3221
 
3176
- function readLogFiles(dir: string): string[] {
3222
+ function readLogFiles(dir: string): LogEntry[] {
3177
3223
  if (!existsSync(dir)) return [];
3178
3224
 
3179
3225
  const allFiles = readdirSync(dir);
3226
+ // Match both pino-roll format (*.1log, *.2log) AND plain *.log files
3180
3227
  const logFiles = allFiles
3181
- .filter((f: string) => /\.\d+log$/.test(f))
3228
+ .filter((f: string) => /\.\d+log$/.test(f) || /\.log$/.test(f))
3182
3229
  .sort()
3183
3230
  .map((f: string) => join(dir, f));
3184
3231
 
3185
- const lines: string[] = [];
3232
+ const entries: LogEntry[] = [];
3186
3233
  for (const file of logFiles) {
3187
3234
  try {
3188
3235
  const content = readFileSync(file, "utf-8");
3189
3236
  const fileLines = content.split("\n").filter((line: string) => line.trim());
3190
- lines.push(...fileLines);
3237
+ for (const line of fileLines) {
3238
+ entries.push({ line, file });
3239
+ }
3191
3240
  } catch {
3192
3241
  // Skip unreadable files
3193
3242
  }
3194
3243
  }
3195
3244
 
3196
- return lines;
3245
+ return entries;
3197
3246
  }
3198
3247
 
3199
3248
  async function logs() {
@@ -3207,6 +3256,7 @@ async function logs() {
3207
3256
  let limit = 50;
3208
3257
  let watchMode = false;
3209
3258
  let pollInterval = 1000; // 1 second default
3259
+ let verbose = false;
3210
3260
 
3211
3261
  for (let i = 0; i < args.length; i++) {
3212
3262
  const arg = args[i];
@@ -3231,6 +3281,8 @@ async function logs() {
3231
3281
  }
3232
3282
  } else if (arg === "--watch" || arg === "-w") {
3233
3283
  watchMode = true;
3284
+ } else if (arg === "--verbose" || arg === "-v") {
3285
+ verbose = true;
3234
3286
  } else if (arg === "--interval" && i + 1 < args.length) {
3235
3287
  pollInterval = parseInt(args[++i], 10);
3236
3288
  if (isNaN(pollInterval) || pollInterval < 100) {
@@ -3290,7 +3342,7 @@ async function logs() {
3290
3342
  // Initialize positions from current file sizes
3291
3343
  const initializePositions = () => {
3292
3344
  if (!existsSync(logsDir)) return;
3293
- const files = readdirSync(logsDir).filter((f: string) => /\.\d+log$/.test(f));
3345
+ const files = readdirSync(logsDir).filter((f: string) => /\.\d+log$/.test(f) || /\.log$/.test(f));
3294
3346
  for (const file of files) {
3295
3347
  const filePath = join(logsDir, file);
3296
3348
  try {
@@ -3327,14 +3379,14 @@ async function logs() {
3327
3379
  };
3328
3380
 
3329
3381
  // Print initial logs (last N lines)
3330
- const rawLines = readLogFiles(logsDir);
3331
- let logs: LogLine[] = rawLines
3332
- .map(parseLogLine)
3382
+ const rawEntries = readLogFiles(logsDir);
3383
+ let logs: LogLine[] = rawEntries
3384
+ .map(entry => parseLogLine(entry.line, entry.file))
3333
3385
  .filter((log): log is LogLine => log !== null);
3334
3386
  logs = filterLogs(logs).slice(-limit);
3335
3387
 
3336
3388
  for (const log of logs) {
3337
- console.log(formatLogLine(log));
3389
+ console.log(formatLogLine(log, true, verbose));
3338
3390
  }
3339
3391
 
3340
3392
  // Initialize positions after printing initial logs
@@ -3344,18 +3396,18 @@ async function logs() {
3344
3396
  const pollForNewLogs = () => {
3345
3397
  if (!existsSync(logsDir)) return;
3346
3398
 
3347
- const files = readdirSync(logsDir).filter((f: string) => /\.\d+log$/.test(f));
3399
+ const files = readdirSync(logsDir).filter((f: string) => /\.\d+log$/.test(f) || /\.log$/.test(f));
3348
3400
 
3349
3401
  for (const file of files) {
3350
3402
  const filePath = join(logsDir, file);
3351
3403
  const newLines = readNewLines(filePath);
3352
3404
 
3353
3405
  for (const line of newLines) {
3354
- const parsed = parseLogLine(line);
3406
+ const parsed = parseLogLine(line, filePath);
3355
3407
  if (parsed) {
3356
3408
  const filtered = filterLogs([parsed]);
3357
3409
  if (filtered.length > 0) {
3358
- console.log(formatLogLine(filtered[0]));
3410
+ console.log(formatLogLine(filtered[0], true, verbose));
3359
3411
  }
3360
3412
  }
3361
3413
  }
@@ -3381,11 +3433,11 @@ async function logs() {
3381
3433
  }
3382
3434
 
3383
3435
  // Non-watch mode - one-shot output
3384
- const rawLines = readLogFiles(logsDir);
3436
+ const rawEntries = readLogFiles(logsDir);
3385
3437
 
3386
3438
  // Parse and filter
3387
- let logs: LogLine[] = rawLines
3388
- .map(parseLogLine)
3439
+ let logs: LogLine[] = rawEntries
3440
+ .map(entry => parseLogLine(entry.line, entry.file))
3389
3441
  .filter((log): log is LogLine => log !== null);
3390
3442
 
3391
3443
  logs = filterLogs(logs);
@@ -3410,7 +3462,7 @@ async function logs() {
3410
3462
  console.log();
3411
3463
 
3412
3464
  for (const log of logs) {
3413
- console.log(formatLogLine(log));
3465
+ console.log(formatLogLine(log, true, verbose));
3414
3466
  }
3415
3467
  console.log();
3416
3468
  }
@@ -3522,9 +3574,12 @@ async function db() {
3522
3574
  const command = process.argv[2];
3523
3575
 
3524
3576
  switch (command) {
3525
- case "setup":
3526
- await setup();
3577
+ case "setup": {
3578
+ const reinstallFlag = process.argv.includes("--reinstall") || process.argv.includes("-r");
3579
+ const yesFlag = process.argv.includes("--yes") || process.argv.includes("-y");
3580
+ await setup(reinstallFlag || yesFlag, yesFlag);
3527
3581
  break;
3582
+ }
3528
3583
  case "doctor":
3529
3584
  await doctor();
3530
3585
  break;