muonroi-cli 1.8.3 → 1.8.4

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 (39) hide show
  1. package/LICENSE +9 -21
  2. package/README.md +133 -122
  3. package/dist/src/cli/cost-forensics.js +12 -12
  4. package/dist/src/council/prompts.js +20 -20
  5. package/dist/src/generated/version.d.ts +1 -1
  6. package/dist/src/generated/version.js +1 -1
  7. package/dist/src/mcp/oauth-callback.js +2 -2
  8. package/dist/src/mcp/setup-guide-text.d.ts +1 -1
  9. package/dist/src/mcp/setup-guide-text.js +77 -76
  10. package/dist/src/ops/doctor.js +7 -7
  11. package/dist/src/orchestrator/prompts.js +159 -159
  12. package/dist/src/orchestrator/tool-engine.d.ts +4 -0
  13. package/dist/src/orchestrator/tool-engine.js +49 -0
  14. package/dist/src/pil/layer1-intent.js +37 -37
  15. package/dist/src/pil/layer2_5-ponytail.js +8 -8
  16. package/dist/src/product-loop/done-gate.js +3 -3
  17. package/dist/src/product-loop/loop-driver.js +18 -18
  18. package/dist/src/product-loop/progress-snapshot.js +4 -4
  19. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  20. package/dist/src/reporter/index.js +1 -1
  21. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  22. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  23. package/dist/src/scaffold/continuation-prompt.js +60 -60
  24. package/dist/src/scaffold/init-new.js +453 -453
  25. package/dist/src/self-qa/agentic-loop.js +19 -19
  26. package/dist/src/storage/interaction-log.js +5 -5
  27. package/dist/src/storage/migrations.js +125 -125
  28. package/dist/src/storage/session-experience-store.js +4 -4
  29. package/dist/src/storage/sessions.js +43 -43
  30. package/dist/src/storage/transcript.js +100 -100
  31. package/dist/src/storage/usage.js +14 -14
  32. package/dist/src/storage/workspaces.js +12 -12
  33. package/dist/src/ui/slash/council-inspect.js +4 -4
  34. package/dist/src/ui/use-app-logic.js +0 -0
  35. package/dist/src/utils/clipboard-image.js +23 -23
  36. package/dist/src/utils/install-manager.js +12 -10
  37. package/dist/src/utils/side-question.js +2 -2
  38. package/dist/src/utils/skills.js +3 -3
  39. package/package.json +2 -2
@@ -43,11 +43,11 @@ function isInternalCouncilMarker(content) {
43
43
  }
44
44
  function loadMessageRows(sessionId) {
45
45
  const rows = getDatabase()
46
- .prepare(`
47
- SELECT session_id, seq, role, message_json, created_at
48
- FROM messages
49
- WHERE session_id = ?
50
- ORDER BY seq ASC
46
+ .prepare(`
47
+ SELECT session_id, seq, role, message_json, created_at
48
+ FROM messages
49
+ WHERE session_id = ?
50
+ ORDER BY seq ASC
51
51
  `)
52
52
  .all(sessionId);
53
53
  for (const row of rows) {
@@ -67,12 +67,12 @@ function toPersistedCompaction(row) {
67
67
  }
68
68
  export function loadLatestCompaction(sessionId) {
69
69
  const row = getDatabase()
70
- .prepare(`
71
- SELECT session_id, first_kept_seq, summary, tokens_before, created_at
72
- FROM compactions
73
- WHERE session_id = ?
74
- ORDER BY id DESC
75
- LIMIT 1
70
+ .prepare(`
71
+ SELECT session_id, first_kept_seq, summary, tokens_before, created_at
72
+ FROM compactions
73
+ WHERE session_id = ?
74
+ ORDER BY id DESC
75
+ LIMIT 1
76
76
  `)
77
77
  .get(sessionId);
78
78
  return toPersistedCompaction(row);
@@ -113,37 +113,37 @@ export function appendMessages(sessionId, messages) {
113
113
  // with `status='completed'` and the full message_json. Pre-A5 callers
114
114
  // hit this path identically: there is no pending row so the IGNORE
115
115
  // branch is unused and the INSERT wins.
116
- const insertMessage = db.prepare(`
117
- INSERT INTO messages (session_id, seq, role, message_json, created_at, status)
118
- VALUES (?, ?, ?, ?, ?, 'completed')
119
- ON CONFLICT(session_id, seq) DO UPDATE SET
120
- role = excluded.role,
121
- message_json = excluded.message_json,
122
- status = 'completed'
116
+ const insertMessage = db.prepare(`
117
+ INSERT INTO messages (session_id, seq, role, message_json, created_at, status)
118
+ VALUES (?, ?, ?, ?, ?, 'completed')
119
+ ON CONFLICT(session_id, seq) DO UPDATE SET
120
+ role = excluded.role,
121
+ message_json = excluded.message_json,
122
+ status = 'completed'
123
123
  `);
124
- const insertToolCall = db.prepare(`
125
- INSERT OR IGNORE INTO tool_calls (
126
- session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
127
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
124
+ const insertToolCall = db.prepare(`
125
+ INSERT OR IGNORE INTO tool_calls (
126
+ session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
127
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
128
128
  `);
129
- const updateToolCall = db.prepare(`
130
- UPDATE tool_calls
131
- SET tool_name = ?, args_json = ?, status = ?, completed_at = ?
132
- WHERE session_id = ? AND tool_call_id = ?
129
+ const updateToolCall = db.prepare(`
130
+ UPDATE tool_calls
131
+ SET tool_name = ?, args_json = ?, status = ?, completed_at = ?
132
+ WHERE session_id = ? AND tool_call_id = ?
133
133
  `);
134
- const selectToolCall = db.prepare(`
135
- SELECT id, tool_call_id, tool_name, args_json
136
- FROM tool_calls
137
- WHERE session_id = ? AND tool_call_id = ?
134
+ const selectToolCall = db.prepare(`
135
+ SELECT id, tool_call_id, tool_name, args_json
136
+ FROM tool_calls
137
+ WHERE session_id = ? AND tool_call_id = ?
138
138
  `);
139
- const insertToolResult = db.prepare(`
140
- INSERT INTO tool_results (tool_call_row_id, output_kind, output_json, success, created_at)
141
- VALUES (?, ?, ?, ?, ?)
139
+ const insertToolResult = db.prepare(`
140
+ INSERT INTO tool_results (tool_call_row_id, output_kind, output_json, success, created_at)
141
+ VALUES (?, ?, ?, ?, ?)
142
142
  `);
143
- const updateSession = db.prepare(`
144
- UPDATE sessions
145
- SET updated_at = ?
146
- WHERE id = ?
143
+ const updateSession = db.prepare(`
144
+ UPDATE sessions
145
+ SET updated_at = ?
146
+ WHERE id = ?
147
147
  `);
148
148
  messages.forEach((message, index) => {
149
149
  const seq = nextSeq + index;
@@ -205,10 +205,10 @@ export function persistToolCallWriteAhead(sessionId, messageSeq, toolCallId, too
205
205
  const now = new Date().toISOString();
206
206
  try {
207
207
  getDatabase()
208
- .prepare(`
209
- INSERT OR IGNORE INTO tool_calls (
210
- session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
211
- ) VALUES (?, ?, ?, ?, ?, 'pending', ?, NULL)
208
+ .prepare(`
209
+ INSERT OR IGNORE INTO tool_calls (
210
+ session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
211
+ ) VALUES (?, ?, ?, ?, ?, 'pending', ?, NULL)
212
212
  `)
213
213
  .run(sessionId, messageSeq, toolCallId, toolName, argsJson, now);
214
214
  }
@@ -254,10 +254,10 @@ export function persistMessageWriteAhead(sessionId, seq, role, messageJson) {
254
254
  const now = new Date().toISOString();
255
255
  try {
256
256
  getDatabase()
257
- .prepare(`
258
- INSERT OR IGNORE INTO messages (
259
- session_id, seq, role, message_json, created_at, status
260
- ) VALUES (?, ?, ?, ?, ?, 'pending')
257
+ .prepare(`
258
+ INSERT OR IGNORE INTO messages (
259
+ session_id, seq, role, message_json, created_at, status
260
+ ) VALUES (?, ?, ?, ?, ?, 'pending')
261
261
  `)
262
262
  .run(sessionId, seq, role, messageJson, now);
263
263
  }
@@ -275,10 +275,10 @@ export function persistMessageWriteAhead(sessionId, seq, role, messageJson) {
275
275
  export function markMessageCompleted(sessionId, seq) {
276
276
  try {
277
277
  getDatabase()
278
- .prepare(`
279
- UPDATE messages
280
- SET status = 'completed'
281
- WHERE session_id = ? AND seq = ? AND status = 'pending'
278
+ .prepare(`
279
+ UPDATE messages
280
+ SET status = 'completed'
281
+ WHERE session_id = ? AND seq = ? AND status = 'pending'
282
282
  `)
283
283
  .run(sessionId, seq);
284
284
  }
@@ -297,10 +297,10 @@ export function markMessageCompleted(sessionId, seq) {
297
297
  export function markMessageErrored(sessionId, seq) {
298
298
  try {
299
299
  getDatabase()
300
- .prepare(`
301
- UPDATE messages
302
- SET status = 'errored'
303
- WHERE session_id = ? AND seq = ? AND status = 'pending'
300
+ .prepare(`
301
+ UPDATE messages
302
+ SET status = 'errored'
303
+ WHERE session_id = ? AND seq = ? AND status = 'pending'
304
304
  `)
305
305
  .run(sessionId, seq);
306
306
  }
@@ -331,17 +331,17 @@ export function sweepStalePendingRows(staleAfterMs = 5 * 60 * 1000) {
331
331
  try {
332
332
  const db = getDatabase();
333
333
  const toolCalls = db
334
- .prepare(`
335
- UPDATE tool_calls
336
- SET status = 'aborted', completed_at = started_at
337
- WHERE status = 'pending' AND started_at < ?
334
+ .prepare(`
335
+ UPDATE tool_calls
336
+ SET status = 'aborted', completed_at = started_at
337
+ WHERE status = 'pending' AND started_at < ?
338
338
  `)
339
339
  .run(cutoff);
340
340
  const messages = db
341
- .prepare(`
342
- UPDATE messages
343
- SET status = 'aborted'
344
- WHERE status = 'pending' AND created_at < ?
341
+ .prepare(`
342
+ UPDATE messages
343
+ SET status = 'aborted'
344
+ WHERE status = 'pending' AND created_at < ?
345
345
  `)
346
346
  .run(cutoff);
347
347
  return { toolCalls: toolCalls.changes, messages: messages.changes };
@@ -355,10 +355,10 @@ export function markToolCallErrored(sessionId, toolCallId, errorMessage) {
355
355
  const now = new Date().toISOString();
356
356
  try {
357
357
  getDatabase()
358
- .prepare(`
359
- UPDATE tool_calls
360
- SET status = 'errored', completed_at = ?, args_json = COALESCE(args_json, ?)
361
- WHERE session_id = ? AND tool_call_id = ?
358
+ .prepare(`
359
+ UPDATE tool_calls
360
+ SET status = 'errored', completed_at = ?, args_json = COALESCE(args_json, ?)
361
+ WHERE session_id = ? AND tool_call_id = ?
362
362
  `)
363
363
  .run(now, JSON.stringify({ error: errorMessage.slice(0, 500) }), sessionId, toolCallId);
364
364
  }
@@ -368,28 +368,28 @@ export function markToolCallErrored(sessionId, toolCallId, errorMessage) {
368
368
  }
369
369
  export function appendCompaction(sessionId, firstKeptSeq, summary, tokensBefore) {
370
370
  withTransaction((db) => {
371
- db.prepare(`
372
- INSERT INTO compactions (session_id, first_kept_seq, summary, tokens_before, created_at)
373
- VALUES (?, ?, ?, ?, ?)
371
+ db.prepare(`
372
+ INSERT INTO compactions (session_id, first_kept_seq, summary, tokens_before, created_at)
373
+ VALUES (?, ?, ?, ?, ?)
374
374
  `).run(sessionId, firstKeptSeq, summary, tokensBefore, new Date().toISOString());
375
- db.prepare(`
376
- UPDATE sessions
377
- SET updated_at = ?
378
- WHERE id = ?
375
+ db.prepare(`
376
+ UPDATE sessions
377
+ SET updated_at = ?
378
+ WHERE id = ?
379
379
  `).run(new Date().toISOString(), sessionId);
380
380
  });
381
381
  }
382
382
  export function revertLatestCompaction(sessionId) {
383
383
  withTransaction((db) => {
384
- db.prepare(`
385
- DELETE FROM compactions
386
- WHERE session_id = ?
387
- AND id = (SELECT MAX(id) FROM compactions WHERE session_id = ?)
384
+ db.prepare(`
385
+ DELETE FROM compactions
386
+ WHERE session_id = ?
387
+ AND id = (SELECT MAX(id) FROM compactions WHERE session_id = ?)
388
388
  `).run(sessionId, sessionId);
389
- db.prepare(`
390
- UPDATE sessions
391
- SET updated_at = ?
392
- WHERE id = ?
389
+ db.prepare(`
390
+ UPDATE sessions
391
+ SET updated_at = ?
392
+ WHERE id = ?
393
393
  `).run(new Date().toISOString(), sessionId);
394
394
  });
395
395
  }
@@ -439,10 +439,10 @@ export function getSessionChain(sessionId) {
439
439
  .map(() => "?")
440
440
  .join(",");
441
441
  const sortedRows = db
442
- .prepare(`
443
- SELECT id FROM sessions
444
- WHERE id IN (${placeholders})
445
- ORDER BY created_at ASC
442
+ .prepare(`
443
+ SELECT id FROM sessions
444
+ WHERE id IN (${placeholders})
445
+ ORDER BY created_at ASC
446
446
  `)
447
447
  .all(...Array.from(allIds));
448
448
  chain = sortedRows.map((r) => r.id);
@@ -624,10 +624,10 @@ export function buildChatEntries(sessionId) {
624
624
  }
625
625
  function getNextSequence(db, sessionId) {
626
626
  const row = db
627
- .prepare(`
628
- SELECT COALESCE(MAX(seq), 0) AS max_seq
629
- FROM messages
630
- WHERE session_id = ?
627
+ .prepare(`
628
+ SELECT COALESCE(MAX(seq), 0) AS max_seq
629
+ FROM messages
630
+ WHERE session_id = ?
631
631
  `)
632
632
  .get(sessionId);
633
633
  return (row?.max_seq ?? 0) + 1;
@@ -677,12 +677,12 @@ function renderAssistantContent(content, callMap) {
677
677
  }
678
678
  function loadStoredToolResults(sessionId) {
679
679
  const rows = getDatabase()
680
- .prepare(`
681
- SELECT tc.tool_call_id, tr.output_json
682
- FROM tool_results tr
683
- JOIN tool_calls tc ON tc.id = tr.tool_call_row_id
684
- WHERE tc.session_id = ?
685
- ORDER BY tr.id ASC
680
+ .prepare(`
681
+ SELECT tc.tool_call_id, tr.output_json
682
+ FROM tool_results tr
683
+ JOIN tool_calls tc ON tc.id = tr.tool_call_row_id
684
+ WHERE tc.session_id = ?
685
+ ORDER BY tr.id ASC
686
686
  `)
687
687
  .all(sessionId);
688
688
  return new Map(rows.map((row) => [row.tool_call_id, JSON.parse(row.output_json)]));
@@ -703,12 +703,12 @@ export function getLastTodoWriteArgs(sessionId) {
703
703
  for (let i = chain.length - 1; i >= 0; i--) {
704
704
  try {
705
705
  const row = db
706
- .prepare(`
707
- SELECT args_json
708
- FROM tool_calls
709
- WHERE session_id = ? AND tool_name = 'todo_write'
710
- ORDER BY id DESC
711
- LIMIT 1
706
+ .prepare(`
707
+ SELECT args_json
708
+ FROM tool_calls
709
+ WHERE session_id = ? AND tool_name = 'todo_write'
710
+ ORDER BY id DESC
711
+ LIMIT 1
712
712
  `)
713
713
  .get(chain[i]);
714
714
  if (row?.args_json) {
@@ -14,11 +14,11 @@ export function recordUsageEvent(sessionId, source, model, usage, messageSeq, pi
14
14
  const cacheCreationTokens = usage.cacheCreationTokens ?? 0;
15
15
  const costMicros = estimateCostMicros(model, inputTokens, outputTokens, cacheReadTokens);
16
16
  getDatabase()
17
- .prepare(`
18
- INSERT INTO usage_events (
19
- session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at,
20
- pil_active, enrichment_delta, cache_read_tokens, cache_creation_tokens, provider_options_shape
21
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
17
+ .prepare(`
18
+ INSERT INTO usage_events (
19
+ session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at,
20
+ pil_active, enrichment_delta, cache_read_tokens, cache_creation_tokens, provider_options_shape
21
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
22
22
  `)
23
23
  .run(sessionId, messageSeq ?? null, source, model, inputTokens, outputTokens, totalTokens, costMicros, new Date().toISOString(), pilActive ? 1 : 0, enrichmentDelta, cacheReadTokens, cacheCreationTokens, providerOptionsShape);
24
24
  }
@@ -26,10 +26,10 @@ export function getSessionTotalTokens(sessionId) {
26
26
  const chain = getSessionChain(sessionId);
27
27
  const placeholders = chain.map(() => "?").join(",");
28
28
  const row = getDatabase()
29
- .prepare(`
30
- SELECT COALESCE(SUM(total_tokens), 0) AS total_tokens
31
- FROM usage_events
32
- WHERE session_id IN (${placeholders})
29
+ .prepare(`
30
+ SELECT COALESCE(SUM(total_tokens), 0) AS total_tokens
31
+ FROM usage_events
32
+ WHERE session_id IN (${placeholders})
33
33
  `)
34
34
  .get(...chain);
35
35
  const total = row?.total_tokens ?? 0;
@@ -44,11 +44,11 @@ export function listSessionUsage(sessionId) {
44
44
  const chain = getSessionChain(sessionId);
45
45
  const placeholders = chain.map(() => "?").join(",");
46
46
  const rows = getDatabase()
47
- .prepare(`
48
- SELECT id, session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at, cache_read_tokens, cache_creation_tokens
49
- FROM usage_events
50
- WHERE session_id IN (${placeholders})
51
- ORDER BY id ASC
47
+ .prepare(`
48
+ SELECT id, session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at, cache_read_tokens, cache_creation_tokens
49
+ FROM usage_events
50
+ WHERE session_id IN (${placeholders})
51
+ ORDER BY id ASC
52
52
  `)
53
53
  .all(...chain);
54
54
  logger.debug("storage", "Retrieved session usage events from chain", {
@@ -8,14 +8,14 @@ export function ensureWorkspace(cwd) {
8
8
  const now = new Date().toISOString();
9
9
  const id = createHash("sha1").update(resolved.scopeKey).digest("hex").slice(0, 16);
10
10
  const db = getDatabase();
11
- db.prepare(`
12
- INSERT INTO workspaces (id, scope_key, canonical_path, git_root, display_name, last_seen_at)
13
- VALUES (@id, @scope_key, @canonical_path, @git_root, @display_name, @last_seen_at)
14
- ON CONFLICT(scope_key) DO UPDATE SET
15
- canonical_path = excluded.canonical_path,
16
- git_root = excluded.git_root,
17
- display_name = excluded.display_name,
18
- last_seen_at = excluded.last_seen_at
11
+ db.prepare(`
12
+ INSERT INTO workspaces (id, scope_key, canonical_path, git_root, display_name, last_seen_at)
13
+ VALUES (@id, @scope_key, @canonical_path, @git_root, @display_name, @last_seen_at)
14
+ ON CONFLICT(scope_key) DO UPDATE SET
15
+ canonical_path = excluded.canonical_path,
16
+ git_root = excluded.git_root,
17
+ display_name = excluded.display_name,
18
+ last_seen_at = excluded.last_seen_at
19
19
  `).run({
20
20
  id,
21
21
  scope_key: resolved.scopeKey,
@@ -25,10 +25,10 @@ export function ensureWorkspace(cwd) {
25
25
  last_seen_at: now,
26
26
  });
27
27
  const row = db
28
- .prepare(`
29
- SELECT id, scope_key, canonical_path, git_root, display_name, last_seen_at
30
- FROM workspaces
31
- WHERE scope_key = ?
28
+ .prepare(`
29
+ SELECT id, scope_key, canonical_path, git_root, display_name, last_seen_at
30
+ FROM workspaces
31
+ WHERE scope_key = ?
32
32
  `)
33
33
  .get(resolved.scopeKey);
34
34
  if (!row) {
@@ -37,10 +37,10 @@ export const handleCouncilInspectSlash = async (args) => {
37
37
  }
38
38
  // Load all system messages for this session — parameterized query prevents SQL injection (T-17-04)
39
39
  const rows = db
40
- .prepare(`SELECT role, message_json, seq, created_at
41
- FROM messages
42
- WHERE session_id = ?
43
- AND role = 'system'
40
+ .prepare(`SELECT role, message_json, seq, created_at
41
+ FROM messages
42
+ WHERE session_id = ?
43
+ AND role = 'system'
44
44
  ORDER BY seq ASC`)
45
45
  .all(sessionId);
46
46
  if (rows.length === 0) {
Binary file
@@ -25,24 +25,24 @@ function readWin32() {
25
25
  try {
26
26
  const escaped = tmpFile.replace(/\\/g, "\\\\");
27
27
  // Run in STA thread with retry — clipboard can be locked by other processes
28
- const ps = `
29
- Add-Type -AssemblyName System.Windows.Forms
30
- Add-Type -AssemblyName System.Drawing
31
- $maxRetries = 3
32
- $img = $null
33
- for ($i = 0; $i -lt $maxRetries; $i++) {
34
- try {
35
- $img = [System.Windows.Forms.Clipboard]::GetImage()
36
- if ($img -ne $null) { break }
37
- } catch {
38
- # clipboard locked — wait and retry
39
- }
40
- Start-Sleep -Milliseconds 100
41
- }
42
- if ($img -ne $null) {
43
- $img.Save('${escaped}', [System.Drawing.Imaging.ImageFormat]::Png)
44
- $img.Dispose()
45
- }
28
+ const ps = `
29
+ Add-Type -AssemblyName System.Windows.Forms
30
+ Add-Type -AssemblyName System.Drawing
31
+ $maxRetries = 3
32
+ $img = $null
33
+ for ($i = 0; $i -lt $maxRetries; $i++) {
34
+ try {
35
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
36
+ if ($img -ne $null) { break }
37
+ } catch {
38
+ # clipboard locked — wait and retry
39
+ }
40
+ Start-Sleep -Milliseconds 100
41
+ }
42
+ if ($img -ne $null) {
43
+ $img.Save('${escaped}', [System.Drawing.Imaging.ImageFormat]::Png)
44
+ $img.Dispose()
45
+ }
46
46
  `;
47
47
  const result = spawnSync("powershell", ["-NoProfile", "-STA", "-Command", ps], { timeout: 8000 });
48
48
  if (result.error || result.status !== 0)
@@ -81,11 +81,11 @@ function readDarwin() {
81
81
  const pngpaste = spawnSync("pngpaste", [tmpFile], { timeout: 5000 });
82
82
  if (pngpaste.status !== 0) {
83
83
  spawnSync("screencapture", ["-c", "-x"]);
84
- const osascript = `
85
- set imgData to the clipboard as «class PNGf»
86
- set f to open for access POSIX file "${tmpFile}" with write permission
87
- write imgData to f
88
- close access f
84
+ const osascript = `
85
+ set imgData to the clipboard as «class PNGf»
86
+ set f to open for access POSIX file "${tmpFile}" with write permission
87
+ write imgData to f
88
+ close access f
89
89
  `;
90
90
  spawnSync("osascript", ["-e", osascript], { timeout: 5000 });
91
91
  }
@@ -248,21 +248,23 @@ export async function runManagedUpdate(currentVersion) {
248
248
  if (latestVersion && normalizedCurrent) {
249
249
  hasUpdate = semverGt(latestVersion, normalizedCurrent);
250
250
  if (hasUpdate) {
251
- statusHeader = `A new version of muonroi-cli is available!\n Current version: v${normalizedCurrent}\n Latest version: v${latestVersion}\n\n`;
251
+ statusHeader = `### 🔄 Update Available\n* **Current Version:** \`v${normalizedCurrent}\`\n* **Latest Version:** \`v${latestVersion}\`\n* **Status:** A new version of \`muonroi-cli\` is available!\n\n`;
252
+ }
253
+ else if (semverGt(normalizedCurrent, latestVersion)) {
254
+ statusHeader = `### 🚀 Ahead of Latest Release\n* **Current Version:** \`v${normalizedCurrent}\`\n* **Latest Version:** \`v${latestVersion}\`\n* **Status:** Your local installation is newer than the remote release tag.\n\n`;
252
255
  }
253
256
  else {
254
- statusHeader = `You are already up to date!\n Current version: v${normalizedCurrent}\n Latest version: v${latestVersion}\n\n`;
257
+ statusHeader = `### Up to Date\n* **Current Version:** \`v${normalizedCurrent}\`\n* **Latest Version:** \`v${latestVersion}\`\n* **Status:** You are already up to date!\n\n`;
255
258
  }
256
259
  }
257
260
  else if (normalizedCurrent) {
258
- statusHeader = `Current version: v${normalizedCurrent}\nUnable to check the latest version from GitHub.\n\n`;
261
+ statusHeader = `### ⚠️ Update Status\n* **Current Version:** \`v${normalizedCurrent}\`\n* **Status:** Unable to check the latest version from GitHub or NPM.\n\n`;
259
262
  }
260
263
  const cmd = getUpdateCommandForMethod(method);
261
264
  if (cmd) {
262
- const pm = method === "bun-global" ? "bun" : "npm";
263
265
  const instruction = hasUpdate
264
- ? `To update, run this in a fresh terminal:\n\n ${cmd}\n\nThen restart muonroi-cli.`
265
- : `If you want to reinstall, run this in a fresh terminal:\n\n ${cmd}`;
266
+ ? `To update, run this in a fresh terminal:\n\`\`\`bash\n${cmd}\n\`\`\`\nThen restart \`muonroi-cli\`.`
267
+ : `If you want to reinstall, run this in a fresh terminal:\n\`\`\`bash\n${cmd}\n\`\`\``;
266
268
  return {
267
269
  success: true,
268
270
  output: `${statusHeader}${instruction}`,
@@ -272,8 +274,8 @@ export async function runManagedUpdate(currentVersion) {
272
274
  const target = getReleaseTargetForPlatform();
273
275
  const asset = target?.assetName ?? "the release asset for your platform";
274
276
  const instruction = hasUpdate
275
- ? `Download the latest ${asset} from https://github.com/${GITHUB_REPO}/releases/latest and replace the current binary, or rebuild from source.`
276
- : `If you want to reinstall, download the latest ${asset} from https://github.com/${GITHUB_REPO}/releases/latest and replace the current binary.`;
277
+ ? `Download the latest \`${asset}\` from [GitHub Releases](https://github.com/${GITHUB_REPO}/releases/latest) and replace the current binary, or rebuild from source.`
278
+ : `If you want to reinstall, download the latest \`${asset}\` from [GitHub Releases](https://github.com/${GITHUB_REPO}/releases/latest) and replace the current binary.`;
277
279
  return {
278
280
  success: true,
279
281
  output: `${statusHeader}${instruction}`,
@@ -282,8 +284,8 @@ export async function runManagedUpdate(currentVersion) {
282
284
  if (method === "dev-link") {
283
285
  const target = root ?? "the muonroi-cli checkout";
284
286
  const instruction = hasUpdate
285
- ? `To update, pull the latest changes and rebuild:\n\n git -C "${target}" pull && bun install && bun run build\n\nThen restart muonroi-cli. (If you also use the compiled muonroi-cli-dev binary, rebuild that separately.)`
286
- : `To rebuild your local installation:\n\n git -C "${target}" pull && bun install && bun run build\n\nThen restart muonroi-cli.`;
287
+ ? `To update, pull the latest changes and rebuild:\n\`\`\`bash\ngit -C "${target}" pull && bun install && bun run build\n\`\`\`\nThen restart \`muonroi-cli\`. (If you also use the compiled muonroi-cli-dev binary, rebuild that separately.)`
288
+ : `To rebuild your local installation:\n\`\`\`bash\ngit -C "${target}" pull && bun install && bun run build\n\`\`\`\nThen restart \`muonroi-cli\`.`;
287
289
  return {
288
290
  success: true,
289
291
  output: `${statusHeader}${instruction}`,
@@ -1,7 +1,7 @@
1
1
  import { generateText } from "ai";
2
2
  import { resolveModelRuntime } from "../providers/runtime.js";
3
- const SIDE_QUESTION_SYSTEM = `You are a helpful coding assistant answering a quick side question. The user is in the middle of a coding session and needs a fast, concise answer. Keep your response short and focused — this is a side question, not the main task.
4
-
3
+ const SIDE_QUESTION_SYSTEM = `You are a helpful coding assistant answering a quick side question. The user is in the middle of a coding session and needs a fast, concise answer. Keep your response short and focused — this is a side question, not the main task.
4
+
5
5
  If conversation context is provided below, use it to give a more relevant answer.`;
6
6
  export async function runSideQuestion(question, provider, modelId, conversationContext, signal) {
7
7
  const runtime = resolveModelRuntime(provider, modelId);
@@ -163,9 +163,9 @@ export function discoverSkills(projectRoot) {
163
163
  _skillsCache = { skills, cachedAt: now, cwd: projectRoot };
164
164
  return skills;
165
165
  }
166
- const SKILLS_INSTRUCTIONS = `AGENT SKILLS (optional):
167
- The following <available_skills> list specialized workflows. Use them when they might help the user's request — not only on exact keyword matches.
168
- If a skill's description fits the task or could improve consistency, read that skill's instructions first using read_file with the path from <location>, then follow the SKILL.md body.
166
+ const SKILLS_INSTRUCTIONS = `AGENT SKILLS (optional):
167
+ The following <available_skills> list specialized workflows. Use them when they might help the user's request — not only on exact keyword matches.
168
+ If a skill's description fits the task or could improve consistency, read that skill's instructions first using read_file with the path from <location>, then follow the SKILL.md body.
169
169
  Paths inside a skill (scripts/, references/, assets/) are relative to the skill directory (the folder containing SKILL.md); prefer absolute paths in tool calls.`;
170
170
  /** OpenCode-style XML catalog plus activation instructions for read_file. Returns null if no skills. */
171
171
  export function formatSkillsForPrompt(skills) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "1.8.3",
6
+ "version": "1.8.4",
7
7
  "description": "BYOK AI coding agent with multi-model council debate, role-based routing, and auto-compact.",
8
8
  "repository": {
9
9
  "type": "git",
@@ -77,7 +77,7 @@
77
77
  "opentui"
78
78
  ],
79
79
  "author": "muonroi",
80
- "license": "MIT",
80
+ "license": "UNLICENSED",
81
81
  "dependencies": {
82
82
  "@ai-sdk/anthropic": "3.0.72",
83
83
  "@ai-sdk/google": "3.0.65",