@compilr-dev/cli 0.7.3 → 0.7.5

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 (46) hide show
  1. package/dist/agent.d.ts +7 -2
  2. package/dist/agent.js +55 -39
  3. package/dist/commands-v2/handlers/project.js +65 -2
  4. package/dist/commands-v2/handlers/settings.js +18 -26
  5. package/dist/compilr-diff-companion.vsix +0 -0
  6. package/dist/db/schema.d.ts +1 -1
  7. package/dist/episodes/index.d.ts +13 -13
  8. package/dist/episodes/index.js +13 -14
  9. package/dist/guide/cli-guide-entries.js +54 -2
  10. package/dist/handlers/delegation-handlers.js +228 -50
  11. package/dist/handlers/interactive-flow-handlers.d.ts +26 -0
  12. package/dist/handlers/interactive-flow-handlers.js +61 -0
  13. package/dist/handlers/propose-alternatives-handlers.d.ts +36 -0
  14. package/dist/handlers/propose-alternatives-handlers.js +65 -0
  15. package/dist/index.js +13 -2
  16. package/dist/repl-v2.d.ts +66 -0
  17. package/dist/repl-v2.js +382 -53
  18. package/dist/shared-handlers.d.ts +57 -5
  19. package/dist/shared-handlers.js +50 -0
  20. package/dist/tools/consult.d.ts +14 -0
  21. package/dist/tools/consult.js +73 -0
  22. package/dist/tools/delegate.d.ts +12 -6
  23. package/dist/tools/delegate.js +35 -19
  24. package/dist/tools/interactive-flow.d.ts +13 -0
  25. package/dist/tools/interactive-flow.js +19 -0
  26. package/dist/tools/platform-adapter.d.ts +14 -2
  27. package/dist/tools/platform-adapter.js +16 -4
  28. package/dist/tools/propose-alternatives.d.ts +13 -0
  29. package/dist/tools/propose-alternatives.js +19 -0
  30. package/dist/tools.d.ts +22 -98
  31. package/dist/tools.js +27 -382
  32. package/dist/ui/markdown-renderer.js +26 -7
  33. package/dist/ui/overlay/data/tutorial-registry.js +2 -0
  34. package/dist/ui/overlay/data/tutorials/projects/interactive-flows.d.ts +9 -0
  35. package/dist/ui/overlay/data/tutorials/projects/interactive-flows.js +94 -0
  36. package/dist/ui/overlay/data/tutorials/projects/new-project.js +56 -20
  37. package/dist/ui/overlay/impl/interactive-flow-overlay-v2.d.ts +79 -0
  38. package/dist/ui/overlay/impl/interactive-flow-overlay-v2.js +580 -0
  39. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +32 -0
  40. package/dist/ui/overlay/impl/new-overlay-v2.js +305 -66
  41. package/dist/ui/overlay/impl/propose-alternatives-overlay-v2.d.ts +53 -0
  42. package/dist/ui/overlay/impl/propose-alternatives-overlay-v2.js +326 -0
  43. package/dist/ui/overlay/index.d.ts +2 -0
  44. package/dist/ui/overlay/index.js +2 -0
  45. package/dist/ui/tool-formatters.js +61 -3
  46. package/package.json +3 -3
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Propose Alternatives Overlay V2
3
+ *
4
+ * Modal overlay for picking one of 2-3 agent-proposed alternatives.
5
+ * One tab per alternative (vs ask_user's one tab per question) — tabs
6
+ * are for COMPARING different answers to the SAME single question, not
7
+ * for moving between distinct questions.
8
+ *
9
+ * Spec: project-docs/00-requirements/compilr-dev-cli/propose-alternatives-spec.md
10
+ */
11
+ import chalk from 'chalk';
12
+ import { highlight } from 'cli-highlight';
13
+ import { BaseOverlayV2, renderBorder, wrapText } from '../../base/index.js';
14
+ import { renderMarkdown } from '../../conversation.js';
15
+ // =============================================================================
16
+ // Overlay Implementation
17
+ // =============================================================================
18
+ export class ProposeAlternativesOverlayV2 extends BaseOverlayV2 {
19
+ type = 'inline';
20
+ id = 'propose-alternatives-overlay-v2';
21
+ question;
22
+ context;
23
+ alternatives;
24
+ constructor(options) {
25
+ super({
26
+ currentTab: 0,
27
+ isAddingNotes: false,
28
+ notesBuffer: '',
29
+ });
30
+ this.question = options.question;
31
+ this.context = options.context;
32
+ this.alternatives = options.alternatives;
33
+ this.minHeight = 20;
34
+ }
35
+ // ───────────────────────────────────────────────────────────────────────────
36
+ // Public API (BaseOverlayV2)
37
+ // ───────────────────────────────────────────────────────────────────────────
38
+ renderContent(context) {
39
+ const s = context.styles;
40
+ const cols = context.width;
41
+ const border = renderBorder(cols, s);
42
+ const lines = [];
43
+ lines.push(border);
44
+ // Header — single question, persistent
45
+ const headerText = `Decide: ${this.question}`;
46
+ for (const line of wrapText(headerText, cols - 2)) {
47
+ lines.push(' ' + s.primaryBold(line));
48
+ }
49
+ lines.push('');
50
+ // Context block (optional)
51
+ if (this.context && this.context.trim().length > 0) {
52
+ lines.push(' ' + s.muted('Context'));
53
+ for (const line of wrapText(this.context, cols - 4)) {
54
+ lines.push(' ' + s.muted(line));
55
+ }
56
+ lines.push('');
57
+ }
58
+ // Tab bar (alternatives)
59
+ lines.push(...this.renderTabBar(s, cols));
60
+ lines.push('');
61
+ // Body for the active tab
62
+ lines.push(...this.renderActiveAlternative(s, cols));
63
+ // Notes-input row or footer
64
+ lines.push('');
65
+ if (this.state.isAddingNotes) {
66
+ lines.push(...this.renderNotesInput(s, cols));
67
+ }
68
+ else {
69
+ lines.push(...this.renderInstructions(s));
70
+ }
71
+ lines.push(border);
72
+ return lines;
73
+ }
74
+ handleKey(key) {
75
+ // Ctrl+C → hard cancel (reject, even mid-notes)
76
+ if (key.ctrl && key.name === 'c') {
77
+ return this.close(this.makeRejected());
78
+ }
79
+ if (this.state.isAddingNotes) {
80
+ return this.handleNotesKey(key);
81
+ }
82
+ return this.handleNavigationKey(key);
83
+ }
84
+ getCloseSummary(result) {
85
+ const s = this.getStyles();
86
+ if (result.rejected) {
87
+ return s.muted('Alternative: ') + s.warning('rejected');
88
+ }
89
+ const suffix = result.notes !== undefined && result.notes.trim().length > 0 ? ' (with notes)' : '';
90
+ return s.muted('Alternative: ') + s.success(result.chosenTitle + suffix);
91
+ }
92
+ // ───────────────────────────────────────────────────────────────────────────
93
+ // Key handlers
94
+ // ───────────────────────────────────────────────────────────────────────────
95
+ handleNavigationKey(key) {
96
+ const n = this.alternatives.length;
97
+ // Tab navigation
98
+ if (key.name === 'tab' && !key.shift) {
99
+ this.state.currentTab = (this.state.currentTab + 1) % n;
100
+ return this.rerender();
101
+ }
102
+ if ((key.name === 'tab' && key.shift) || key.name === 'left') {
103
+ this.state.currentTab = (this.state.currentTab - 1 + n) % n;
104
+ return this.rerender();
105
+ }
106
+ if (key.name === 'right') {
107
+ this.state.currentTab = (this.state.currentTab + 1) % n;
108
+ return this.rerender();
109
+ }
110
+ // Number-key jumps (1..N)
111
+ if (key.char && /^[1-9]$/.test(key.char)) {
112
+ const idx = parseInt(key.char, 10) - 1;
113
+ if (idx >= 0 && idx < n) {
114
+ this.state.currentTab = idx;
115
+ return this.rerender();
116
+ }
117
+ }
118
+ // Enter → pick the current tab
119
+ if (key.name === 'return' || key.name === 'enter') {
120
+ return this.close(this.makePicked(undefined));
121
+ }
122
+ // 'n' → enter notes-input mode
123
+ if (key.char === 'n' && !key.ctrl && !key.meta) {
124
+ this.state.isAddingNotes = true;
125
+ return this.rerender();
126
+ }
127
+ // 'r' / 'q' / Esc → reject all
128
+ if ((key.char === 'r' || key.char === 'q') &&
129
+ !key.ctrl &&
130
+ !key.meta) {
131
+ return this.close(this.makeRejected());
132
+ }
133
+ if (key.name === 'escape') {
134
+ return this.close(this.makeRejected());
135
+ }
136
+ return this.noAction();
137
+ }
138
+ handleNotesKey(key) {
139
+ // Esc → cancel notes, return to navigation (don't commit a pick)
140
+ if (key.name === 'escape') {
141
+ this.state.isAddingNotes = false;
142
+ this.state.notesBuffer = '';
143
+ return this.rerender();
144
+ }
145
+ // Enter → commit pick with notes
146
+ if (key.name === 'return' || key.name === 'enter') {
147
+ return this.close(this.makePicked(this.state.notesBuffer));
148
+ }
149
+ // Backspace → delete last char
150
+ if (key.name === 'backspace') {
151
+ this.state.notesBuffer = this.state.notesBuffer.slice(0, -1);
152
+ return this.rerender();
153
+ }
154
+ // Printable → append to buffer
155
+ if (key.char && key.char.length === 1 && !key.ctrl && !key.meta) {
156
+ this.state.notesBuffer += key.char;
157
+ return this.rerender();
158
+ }
159
+ return this.noAction();
160
+ }
161
+ // ───────────────────────────────────────────────────────────────────────────
162
+ // Rendering helpers
163
+ // ───────────────────────────────────────────────────────────────────────────
164
+ renderTabBar(s, cols) {
165
+ let line = ' ';
166
+ for (let i = 0; i < this.alternatives.length; i++) {
167
+ const alt = this.alternatives[i];
168
+ const isCurrent = i === this.state.currentTab;
169
+ // Truncate per-tab title to keep the row at one line for 2-3 alts
170
+ const maxTitle = 25;
171
+ const title = alt.title.length > maxTitle ? alt.title.slice(0, maxTitle - 1) + '…' : alt.title;
172
+ const tabContent = ` ${String(i + 1)} · ${title} `;
173
+ if (isCurrent) {
174
+ line += s.selected(tabContent) + ' ';
175
+ }
176
+ else {
177
+ line += s.muted(tabContent) + ' ';
178
+ }
179
+ }
180
+ // If too wide for terminal, just truncate — the user can still see the active tab
181
+ if (visibleLength(line) > cols) {
182
+ // Crude fallback: keep the active tab visible by truncating leading tabs
183
+ // (a fancier renderer would scroll; keep it simple for v1)
184
+ line = line.slice(0, cols - 1) + '…';
185
+ }
186
+ return [line];
187
+ }
188
+ renderActiveAlternative(s, cols) {
189
+ const out = [];
190
+ const alt = this.alternatives[this.state.currentTab];
191
+ // Title (re-stated in body)
192
+ out.push(' ' + s.primaryBold(alt.title));
193
+ out.push('');
194
+ // Description
195
+ for (const line of wrapText(alt.description, cols - 4)) {
196
+ out.push(' ' + line);
197
+ }
198
+ out.push('');
199
+ // Content (markdown / code / mermaid)
200
+ if (alt.content && alt.content.trim().length > 0) {
201
+ out.push(...this.renderContentBlock(alt, s, cols));
202
+ out.push('');
203
+ }
204
+ // Pros
205
+ if (alt.pros && alt.pros.length > 0) {
206
+ out.push(' ' + s.muted('Pros'));
207
+ for (const pro of alt.pros) {
208
+ for (let i = 0; i < wrapText(pro, cols - 6).length; i++) {
209
+ const wrapped = wrapText(pro, cols - 6)[i];
210
+ const prefix = i === 0 ? ' + ' : ' ';
211
+ out.push(prefix + s.success(wrapped));
212
+ }
213
+ }
214
+ out.push('');
215
+ }
216
+ // Cons
217
+ if (alt.cons && alt.cons.length > 0) {
218
+ out.push(' ' + s.muted('Cons'));
219
+ for (const con of alt.cons) {
220
+ for (let i = 0; i < wrapText(con, cols - 6).length; i++) {
221
+ const wrapped = wrapText(con, cols - 6)[i];
222
+ const prefix = i === 0 ? ' − ' : ' ';
223
+ out.push(prefix + s.warning(wrapped));
224
+ }
225
+ }
226
+ }
227
+ return out;
228
+ }
229
+ renderContentBlock(alt, s, _cols) {
230
+ const out = [];
231
+ const content = alt.content ?? '';
232
+ const contentType = alt.contentType ?? 'markdown';
233
+ if (contentType === 'mermaid') {
234
+ out.push(' ' + s.muted('Diagram (mermaid)'));
235
+ for (const raw of content.split('\n')) {
236
+ out.push(' ' + s.muted(raw));
237
+ }
238
+ out.push(' ' + s.muted('(diagram preview unavailable in terminal — open Desktop to view rendered)'));
239
+ return out;
240
+ }
241
+ if (contentType === 'code') {
242
+ out.push(' ' + s.muted('Code' + (alt.language ? ` · ${alt.language}` : '')));
243
+ let rendered;
244
+ try {
245
+ rendered = highlight(content, {
246
+ language: alt.language,
247
+ ignoreIllegals: true,
248
+ });
249
+ }
250
+ catch {
251
+ rendered = content;
252
+ }
253
+ for (const raw of rendered.split('\n')) {
254
+ // Don't re-wrap code — preserve original alignment
255
+ out.push(' ' + raw);
256
+ }
257
+ return out;
258
+ }
259
+ // markdown (default)
260
+ let rendered;
261
+ try {
262
+ rendered = renderMarkdown(content);
263
+ }
264
+ catch {
265
+ rendered = content;
266
+ }
267
+ for (const raw of rendered.split('\n')) {
268
+ // Trim trailing whitespace but preserve the rendered indentation
269
+ out.push(' ' + raw.replace(/\s+$/, ''));
270
+ }
271
+ return out;
272
+ }
273
+ renderNotesInput(s, cols) {
274
+ const prefix = ' Notes: ';
275
+ const available = Math.max(20, cols - prefix.length - 3);
276
+ const buffer = this.state.notesBuffer;
277
+ const visible = buffer.length > available ? '…' + buffer.slice(-(available - 1)) : buffer;
278
+ return [
279
+ prefix + visible + chalk.inverse(' '),
280
+ ' ' + s.muted('[Enter] confirm · [Esc] cancel notes (no selection)'),
281
+ ];
282
+ }
283
+ renderInstructions(s) {
284
+ const parts = [
285
+ '[Tab/←→] navigate',
286
+ '[1-' + String(this.alternatives.length) + '] jump',
287
+ '[Enter] pick',
288
+ '[n] notes',
289
+ '[r] reject',
290
+ '[q] cancel',
291
+ ];
292
+ return [' ' + s.muted(parts.join(' · '))];
293
+ }
294
+ // ───────────────────────────────────────────────────────────────────────────
295
+ // Result builders
296
+ // ───────────────────────────────────────────────────────────────────────────
297
+ makePicked(notes) {
298
+ const idx = this.state.currentTab;
299
+ const alt = this.alternatives[idx];
300
+ const trimmed = notes?.trim();
301
+ return {
302
+ chosenIndex: idx,
303
+ chosenTitle: alt.title,
304
+ notes: trimmed && trimmed.length > 0 ? trimmed : undefined,
305
+ rejected: false,
306
+ };
307
+ }
308
+ makeRejected() {
309
+ // SDK contract: chosenIndex + chosenTitle must be populated even on
310
+ // rejection. The agent's prompt explicitly says these fields are
311
+ // ignored when rejected=true, but the schema requires them.
312
+ return {
313
+ chosenIndex: 0,
314
+ chosenTitle: this.alternatives[0]?.title ?? '',
315
+ rejected: true,
316
+ };
317
+ }
318
+ }
319
+ // =============================================================================
320
+ // Helpers (module-private)
321
+ // =============================================================================
322
+ /** Approximate visible length of an ANSI-coloured string. */
323
+ function visibleLength(s) {
324
+ // eslint-disable-next-line no-control-regex
325
+ return s.replace(/\x1b\[[0-9;]*m/g, '').length;
326
+ }
@@ -26,6 +26,8 @@ export { ConfigOverlayV2, type ConfigOverlayV2Options, type ConfigOverlayV2Resul
26
26
  export { AskUserSimpleOverlayV2, type AskUserSimpleOptionsV2, type AskUserSimpleResultV2 } from './impl/ask-user-simple-overlay-v2.js';
27
27
  export { GuardrailOverlayV2, type GuardrailConfirmOptionsV2, type GuardrailConfirmResultV2 } from './impl/guardrail-overlay-v2.js';
28
28
  export { AskUserOverlayV2, type AskUserOptionsV2, type AskUserResultV2 } from './impl/ask-user-overlay-v2.js';
29
+ export { ProposeAlternativesOverlayV2, type ProposeAlternativesOptionsV2, type ProposeAlternativesResultV2, } from './impl/propose-alternatives-overlay-v2.js';
30
+ export { InteractiveFlowOverlayV2, type InteractiveFlowOptionsV2, type InteractiveFlowResultV2, } from './impl/interactive-flow-overlay-v2.js';
29
31
  export { IterationLimitOverlayV2, type IterationLimitOptionsV2, type IterationLimitResultV2 } from './impl/iteration-limit-overlay-v2.js';
30
32
  export { AppModelOverlayV2, type AppModelOverlayV2Options, type AppModelOverlayV2Result } from './impl/app-model-overlay-v2.js';
31
33
  export { ModelWarningOverlayV2, type ModelWarningOptionsV2, type ModelWarningChoiceV2 } from './impl/model-warning-overlay-v2.js';
@@ -28,6 +28,8 @@ export { ConfigOverlayV2 } from './impl/config-overlay-v2.js';
28
28
  export { AskUserSimpleOverlayV2 } from './impl/ask-user-simple-overlay-v2.js';
29
29
  export { GuardrailOverlayV2 } from './impl/guardrail-overlay-v2.js';
30
30
  export { AskUserOverlayV2 } from './impl/ask-user-overlay-v2.js';
31
+ export { ProposeAlternativesOverlayV2, } from './impl/propose-alternatives-overlay-v2.js';
32
+ export { InteractiveFlowOverlayV2, } from './impl/interactive-flow-overlay-v2.js';
31
33
  export { IterationLimitOverlayV2 } from './impl/iteration-limit-overlay-v2.js';
32
34
  export { AppModelOverlayV2 } from './impl/app-model-overlay-v2.js';
33
35
  export { ModelWarningOverlayV2 } from './impl/model-warning-overlay-v2.js';
@@ -202,6 +202,12 @@ export function formatToolResult(toolName, result) {
202
202
  else if (toolLower === 'ask_user' || toolLower === 'ask_user_simple') {
203
203
  ({ content, summary } = formatAskUser(innerResult));
204
204
  }
205
+ else if (toolLower === 'propose_alternatives') {
206
+ ({ content, summary } = formatProposeAlternatives(innerResult));
207
+ }
208
+ else if (toolLower === 'build_interactive_flow') {
209
+ ({ content, summary } = formatInteractiveFlow(innerResult));
210
+ }
205
211
  else if (toolLower === 'compilr_guide') {
206
212
  ({ content, summary } = formatCompilrGuide(innerResult));
207
213
  }
@@ -1268,22 +1274,74 @@ function formatWebFetch(result) {
1268
1274
  return { content, summary: `${urlShort}${statusInfo}${truncInfo}` };
1269
1275
  }
1270
1276
  function formatAskUser(result) {
1271
- // ask_user returns { answers: Record<string, string|string[]> }
1277
+ // ask_user_simple returns { answer: string, skipped: boolean }
1278
+ // ask_user returns { answers: Record<string, string|string[]>, skipped: string[] }
1272
1279
  const answers = result.answers;
1273
1280
  const answer = result.answer;
1274
- if (answer) {
1281
+ const skipped = result.skipped;
1282
+ // ask_user_simple
1283
+ if (answer !== undefined) {
1284
+ if (skipped === true)
1285
+ return { content: '', summary: 'Skipped' };
1275
1286
  return { content: '', summary: `Answer: ${answer.slice(0, 60)}` };
1276
1287
  }
1288
+ // ask_user
1277
1289
  if (answers) {
1278
1290
  const entries = Object.entries(answers);
1291
+ // Single-question case — show the value inline as before.
1279
1292
  if (entries.length === 1) {
1280
1293
  const val = Array.isArray(entries[0][1]) ? entries[0][1].join(', ') : entries[0][1];
1281
1294
  return { content: '', summary: `Answer: ${val.slice(0, 60)}` };
1282
1295
  }
1283
- return { content: '', summary: `${String(entries.length)} answer(s)` };
1296
+ // Multi-question case list each "id: value" pair in the expanded
1297
+ // body so the user can see what they answered without the agent
1298
+ // having to echo it back. The summary keeps the count.
1299
+ const lines = entries.map(([id, value]) => {
1300
+ const val = Array.isArray(value) ? value.join(', ') : value;
1301
+ return `${id}: ${val}`;
1302
+ });
1303
+ // Tack on a skipped line if anything was skipped.
1304
+ if (Array.isArray(skipped) && skipped.length > 0) {
1305
+ lines.push(`(skipped: ${skipped.join(', ')})`);
1306
+ }
1307
+ return {
1308
+ content: lines.join('\n'),
1309
+ summary: `${String(entries.length)} answer${entries.length === 1 ? '' : 's'}`,
1310
+ };
1284
1311
  }
1285
1312
  return { content: '', summary: 'User responded' };
1286
1313
  }
1314
+ function formatProposeAlternatives(result) {
1315
+ // The SDK reshapes the handler's ProposeAlternativesResult before
1316
+ // wrapping it (see compilr-dev-sdk/src/tools/propose-alternatives-tool.ts:175).
1317
+ // The result we see here is:
1318
+ // pick: { chosen: <title>, chosenIndex: N, notes?: string, rejected: false }
1319
+ // reject: { chosen: null, rejected: true, feedback: string }
1320
+ const rejected = result.rejected === true;
1321
+ const chosen = typeof result.chosen === 'string' ? result.chosen : '';
1322
+ const notes = typeof result.notes === 'string' ? result.notes.trim() : '';
1323
+ if (rejected) {
1324
+ return { content: '', summary: 'Rejected' };
1325
+ }
1326
+ if (notes.length > 0) {
1327
+ return {
1328
+ content: `notes: ${notes}`,
1329
+ summary: `Picked: ${chosen.slice(0, 50)} (with notes)`,
1330
+ };
1331
+ }
1332
+ return { content: '', summary: `Picked: ${chosen.slice(0, 60)}` };
1333
+ }
1334
+ function formatInteractiveFlow(result) {
1335
+ // The SDK passes the handler's InteractiveFlowResult through verbatim.
1336
+ // The tool is marked silent:true in the SDK so this only runs if that
1337
+ // flag changes in the future. Keep it in sync with the spec's close-summary.
1338
+ const completed = result.completed === true;
1339
+ const path = Array.isArray(result.path) ? result.path : [];
1340
+ if (!completed) {
1341
+ return { content: '', summary: `Cancelled at step ${String(path.length)}` };
1342
+ }
1343
+ return { content: '', summary: `Completed — ${String(path.length)} steps` };
1344
+ }
1287
1345
  function formatCompilrGuide(result) {
1288
1346
  const output = result.output;
1289
1347
  const content = output || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/cli",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "AI-powered coding assistant CLI using @compilr-dev/agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,12 +55,12 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@anthropic-ai/sdk": "^0.74.0",
58
- "@compilr-dev/agents": "^0.5.8",
58
+ "@compilr-dev/agents": "^0.5.9",
59
59
  "@compilr-dev/agents-coding": "^1.0.4",
60
60
  "@compilr-dev/editor-core": "^0.0.2",
61
61
  "@compilr-dev/factory": "^0.1.30",
62
62
  "@compilr-dev/logger": "^0.1.0",
63
- "@compilr-dev/sdk": "^0.10.9",
63
+ "@compilr-dev/sdk": "^0.10.39",
64
64
  "@compilr-dev/ui-core": "^0.0.1",
65
65
  "@modelcontextprotocol/sdk": "^1.23.0",
66
66
  "ansi-escapes": "^7.3.0",