oceancode 0.1.0 → 0.1.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.
@@ -63,7 +63,14 @@ var AGENT_SYSTEM_PROMPT = `You are OceanCode, an elite, autonomous AI software e
63
63
  \u2022 \u{1F522} Numbered Steps: Use numbered lists (1., 2., 3.) for chronological execution steps, install steps, or procedures.
64
64
  \u2022 \u{1F524} Bold & Code Highlights: Bold key concepts (**keyword**, **feature**) and use inline code (\`path/file.ts\`, \`function()\`) to clearly separate technical terms from explanation.
65
65
  \u2022 \u{1F4BB} Fenced Code Blocks: Always specify syntax language for code snippets (\`\`\`typescript, \`\`\`bash, etc.).
66
- \u2022 \u2702\uFE0F Crisp Executive Summaries: Start with a brief, punchy overview, present the structured content (bullets), and finish with clear next steps.`;
66
+ \u2022 \u2702\uFE0F Crisp Executive Summaries: Start with a brief, punchy overview, present the structured content (bullets), and finish with clear next steps.
67
+ 7. CLEAN, TO THE POINT & DECORATIVE GREETINGS / RESPONSES:
68
+ - When introducing yourself, greeting the user, or providing project overviews:
69
+ \u2022 Keep it concise, punchy, decorative, and directly to the point \u2014 avoid rambling filler.
70
+ \u2022 Open with a stylish, clean greeting header (e.g. \u{1F30A} **OceanCode** \xB7 *<Project Name> Engineer*).
71
+ \u2022 Highlight capabilities with crisp emoji bullets (\u{1F680}, \u26A1, \u{1F527}, \u{1F50D}).
72
+ \u2022 Present project tech stack in a clean, compact markdown table.
73
+ \u2022 Finish with a friendly, direct prompt asking what the user wants to tackle.`;
67
74
  function setupServerConfig(oceanConfigDir) {
68
75
  const opencodeConfigDir = path.join(oceanConfigDir, "opencode");
69
76
  fs.mkdirSync(opencodeConfigDir, { recursive: true });
@@ -446,7 +453,6 @@ function renderTopBar(modelName, cwd = process.cwd()) {
446
453
  const width = Math.max(60, process.stdout.columns || 80);
447
454
  const folderStr = cwd.length > 35 ? "..." + cwd.slice(-32) : cwd;
448
455
  renderBigLogo(INDENT);
449
- console.log("");
450
456
  const leftText = folderStr;
451
457
  const rightText = modelName + " /help";
452
458
  const spaces = Math.max(2, width - (leftText.length + rightText.length + 6));
@@ -454,14 +460,16 @@ function renderTopBar(modelName, cwd = process.cwd()) {
454
460
  INDENT + chalk.dim(leftText) + " ".repeat(spaces) + chalk.hex(OCEAN_BLUE).bold(modelName) + " " + chalk.dim("/help")
455
461
  );
456
462
  console.log(INDENT + chalk.dim("\u2500".repeat(Math.max(40, width - 4))));
457
- console.log("");
458
463
  }
459
464
  function renderUserMessageCard(text, _modelName) {
460
465
  const width = Math.min(80, (process.stdout.columns || 80) - 4);
461
466
  const bar = chalk.hex(OCEAN_BLUE)("\u258C");
462
467
  const bg = chalk.bgHex("#161b22");
463
- const pad = Math.max(2, width - text.length - 3);
464
- console.log(bar + bg(" " + text + " ".repeat(pad)));
468
+ const lines = text.split("\n");
469
+ for (const l of lines) {
470
+ const pad = Math.max(2, width - l.length - 3);
471
+ console.log(bar + bg(" " + l + " ".repeat(pad)));
472
+ }
465
473
  }
466
474
  function renderFooter(modelName, durationSec, agent = "Build") {
467
475
  const icon = chalk.hex(OCEAN_BLUE)("\u25A3");
@@ -469,10 +477,7 @@ function renderFooter(modelName, durationSec, agent = "Build") {
469
477
  const dot = chalk.dim(" \xB7 ");
470
478
  const modelLabel = chalk.hex("#94a3b8")(modelName);
471
479
  const timeLabel = chalk.dim(`${durationSec.toFixed(1)}s`);
472
- console.log(`
473
-
474
- ${INDENT}${icon} ${agentLabel}${dot}${modelLabel}${dot}${timeLabel}
475
- `);
480
+ console.log(`${INDENT}${icon} ${agentLabel}${dot}${modelLabel}${dot}${timeLabel}`);
476
481
  }
477
482
 
478
483
  // src/ui/toolLabels.ts
@@ -555,7 +560,6 @@ function getFriendlyToolLabel(rawName) {
555
560
  }
556
561
 
557
562
  // src/ui/renderer.ts
558
- var BOTTOM_BAR_ROWS = 3;
559
563
  var StreamRenderer = class {
560
564
  reasoningStarted = false;
561
565
  textStarted = false;
@@ -566,42 +570,25 @@ var StreamRenderer = class {
566
570
  toolsUsed = [];
567
571
  thinkingTimer = null;
568
572
  thinkingFrame = 0;
569
- /** Injected by interactive.ts — repaints the full bottom bar in place */
573
+ eraseBarFn = null;
570
574
  pinBarFn = null;
571
- /** How many rows the bar currently occupies (updated by interactive.ts) */
572
- barRows = BOTTOM_BAR_ROWS;
573
575
  /** Dedupe tracker: OpenCode emits pending → running → completed updates per tool part */
574
576
  seenTools = /* @__PURE__ */ new Map();
575
577
  /** Per-part streamed text length — prevents duplicate/lost output across parts */
576
578
  textLens = /* @__PURE__ */ new Map();
577
- /** Throttle for bottom-bar repaints during streaming */
578
- lastRepinAt = 0;
579
- /** Call this from interactive.ts right after constructing StreamRenderer */
580
- setPinBar(fn, rows) {
581
- this.pinBarFn = fn;
582
- const origFn = fn;
583
- this.pinBarFn = () => {
584
- this.barRows = rows();
585
- origFn();
586
- };
579
+ /** Wire the interactive bar renderer and eraser */
580
+ setPinBar(pinFn, eraseFn) {
581
+ this.pinBarFn = pinFn;
582
+ this.eraseBarFn = eraseFn;
583
+ }
584
+ getThinkingFrame() {
585
+ return this.thinkingFrame;
587
586
  }
588
- // ── With scroll regions, bar rows are scroll-protected — no erasing needed ──
589
- // Output always goes inside the scroll region; bar rows are untouched.
590
587
  eraseBar() {
588
+ this.eraseBarFn?.();
591
589
  }
592
- /** Repaint the bar after a chunk of output (forced — for tool/finish boundaries) */
593
590
  repinBar() {
594
- if (!this.pinBarFn) return;
595
- this.lastRepinAt = Date.now();
596
- this.pinBarFn();
597
- }
598
- /** Throttled repaint for hot paths (per-token streaming). Max ~4 repaints/sec. */
599
- repinBarThrottled() {
600
- if (!this.pinBarFn) return;
601
- const now = Date.now();
602
- if (now - this.lastRepinAt < 250) return;
603
- this.lastRepinAt = now;
604
- this.pinBarFn();
591
+ this.pinBarFn?.();
605
592
  }
606
593
  /** Collapse a tool detail to a single short line so long commands don't scroll */
607
594
  formatDetail(detail) {
@@ -623,35 +610,21 @@ var StreamRenderer = class {
623
610
  this.toolsUsed = [];
624
611
  this.seenTools.clear();
625
612
  this.textLens.clear();
626
- this.lastRepinAt = 0;
613
+ this.thinkingFrame = 0;
614
+ this.repinBar();
627
615
  this.startThinkingAnimation();
628
616
  }
629
617
  startThinkingAnimation() {
630
618
  if (this.thinkingTimer) return;
631
- const frames = [
632
- chalk2.hex("#f59e0b").bold(" \u224B Thinking "),
633
- chalk2.hex("#fbbf24").bold(" \u224B Thinking. "),
634
- chalk2.hex("#fde68a").bold(" \u224B Thinking.. "),
635
- chalk2.hex("#fef08a").bold(" \u224B Thinking..."),
636
- chalk2.hex("#fde68a").bold(" \u224B Thinking.. "),
637
- chalk2.hex("#fbbf24").bold(" \u224B Thinking. "),
638
- chalk2.hex("#f59e0b").bold(" \u224B Thinking "),
639
- chalk2.dim(" \u224B Thinking ")
640
- ];
641
- const writeFrame = (frame) => {
642
- process.stdout.write(`\r${frame}\x1B[K`);
643
- };
644
- writeFrame(frames[0]);
645
619
  this.thinkingTimer = setInterval(() => {
646
620
  this.thinkingFrame++;
647
- writeFrame(frames[this.thinkingFrame % frames.length]);
648
- }, 180);
621
+ this.repinBar();
622
+ }, 200);
649
623
  }
650
624
  stopThinkingAnimation() {
651
625
  if (!this.thinkingTimer) return;
652
626
  clearInterval(this.thinkingTimer);
653
627
  this.thinkingTimer = null;
654
- process.stdout.write("\r\x1B[K");
655
628
  }
656
629
  // ── Public event handlers ────────────────────────────────────────────────────
657
630
  handleReasoning(fullText) {
@@ -674,22 +647,20 @@ var StreamRenderer = class {
674
647
  const action = chalk2.bold.hex("#60a5fa")(`${icon} ${label}`);
675
648
  const clean = this.formatDetail(detail);
676
649
  const info = clean ? chalk2.dim(` ${clean}`) : "";
677
- process.stdout.write(`
678
- ${INDENT}${badge} ${action}${info}
650
+ process.stdout.write(`${INDENT}${badge} ${action}${info}
679
651
  `);
680
652
  this.repinBar();
681
653
  this.startThinkingAnimation();
682
654
  }
683
655
  handleDelta(delta, partId = "default") {
684
656
  if (!delta) return;
685
- if (this.thinkingTimer) this.stopThinkingAnimation();
657
+ this.stopThinkingAnimation();
686
658
  if (!this.textStarted) {
687
659
  this.textStarted = true;
688
660
  this.eraseBar();
689
661
  if (this.toolsUsed.length > 0) {
690
662
  process.stdout.write(
691
663
  `${INDENT}${chalk2.green("\u2713")} ${chalk2.dim(`Used tools: ${this.toolsUsed.join(", ")}`)}
692
-
693
664
  `
694
665
  );
695
666
  }
@@ -697,16 +668,16 @@ ${INDENT}${badge} ${action}${info}
697
668
  this.currentTextLength += delta.length;
698
669
  this.textLens.set(partId, (this.textLens.get(partId) ?? 0) + delta.length);
699
670
  process.stdout.write(delta);
700
- this.repinBarThrottled();
701
671
  }
702
672
  handleText(fullText, partId = "default") {
673
+ if (!fullText) return;
674
+ this.stopThinkingAnimation();
703
675
  if (!this.textStarted) {
704
676
  this.textStarted = true;
705
677
  this.eraseBar();
706
678
  if (this.toolsUsed.length > 0) {
707
679
  process.stdout.write(
708
680
  `${INDENT}${chalk2.green("\u2713")} ${chalk2.dim(`Used tools: ${this.toolsUsed.join(", ")}`)}
709
-
710
681
  `
711
682
  );
712
683
  }
@@ -717,7 +688,6 @@ ${INDENT}${badge} ${action}${info}
717
688
  this.textLens.set(partId, fullText.length);
718
689
  this.currentTextLength += slice.length;
719
690
  process.stdout.write(slice);
720
- this.repinBarThrottled();
721
691
  }
722
692
  finish(agent = "Build") {
723
693
  this.stopThinkingAnimation();
@@ -1117,8 +1087,8 @@ async function runCommand(messages, options) {
1117
1087
  }
1118
1088
  }
1119
1089
  if (event.type === "message.part.delta") {
1120
- const partId = event.properties?.partID;
1121
- if (partId && reasoningPartIds.has(partId)) return;
1090
+ const partId = event.properties?.partID || event.properties?.partId || event.properties?.id || "default";
1091
+ if (reasoningPartIds.has(partId)) return;
1122
1092
  const delta = event.properties?.delta;
1123
1093
  const field = event.properties?.field;
1124
1094
  if (field === "text" && typeof delta === "string") {
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  runCommand,
18
18
  saveProviderKey,
19
19
  validateProviderKey
20
- } from "./chunk-EB736JHH.js";
20
+ } from "./chunk-U5PZQWEB.js";
21
21
 
22
22
  // src/index.ts
23
23
  import { Command } from "commander";
@@ -220,7 +220,7 @@ async function interactiveChatCommand(options) {
220
220
  lines.push(line);
221
221
  }
222
222
  if (lines.length > 0) {
223
- const { runCommand: runCommand2 } = await import("./run-R7NU5EID.js");
223
+ const { runCommand: runCommand2 } = await import("./run-WPMSAWQ3.js");
224
224
  await runCommand2(lines, { model: currentBackendModel });
225
225
  }
226
226
  return;
@@ -246,17 +246,78 @@ async function interactiveChatCommand(options) {
246
246
  let lastUserMsgId = null;
247
247
  let lastSubmittedPrompt = "";
248
248
  const renderer = new StreamRenderer();
249
+ let currentScrollBottom = 0;
250
+ let contentRow = 8;
251
+ let lastBarStartRow = 0;
252
+ let lastBarLinesCount = 0;
253
+ const printToContent = (text) => {
254
+ if (!process.stdout.isTTY) {
255
+ process.stdout.write(text.endsWith("\n") ? text : text + "\n");
256
+ return;
257
+ }
258
+ process.stdout.write(`\x1B[${contentRow};1H`);
259
+ const formatted = text.endsWith("\n") ? text : text + "\n";
260
+ process.stdout.write(formatted);
261
+ const lineCount = (formatted.match(/\n/g) || []).length;
262
+ contentRow = Math.min(currentScrollBottom || 20, contentRow + lineCount);
263
+ };
264
+ const getBarMetrics = (linesCount) => {
265
+ const rows = process.stdout.rows || 24;
266
+ const barRows = linesCount;
267
+ const barStartRow = Math.max(1, rows - barRows + 1);
268
+ const scrollBottom = Math.max(1, barStartRow - 1);
269
+ return { rows, barRows, barStartRow, scrollBottom };
270
+ };
271
+ const applyScrollRegion = (scrollBottom) => {
272
+ if (!process.stdout.isTTY) return;
273
+ if (currentScrollBottom !== scrollBottom) {
274
+ currentScrollBottom = scrollBottom;
275
+ process.stdout.write(`\x1B[1;${scrollBottom}r`);
276
+ }
277
+ };
278
+ const resetScrollRegion = () => {
279
+ if (!process.stdout.isTTY) return;
280
+ const rows = process.stdout.rows || 24;
281
+ process.stdout.write(`\x1B[1;${rows}r`);
282
+ currentScrollBottom = 0;
283
+ };
284
+ const onSignal = () => {
285
+ cleanup();
286
+ process.exit(0);
287
+ };
288
+ process.on("SIGINT", onSignal);
289
+ process.on("SIGTERM", onSignal);
249
290
  const cleanup = () => {
291
+ process.off("SIGINT", onSignal);
292
+ process.off("SIGTERM", onSignal);
250
293
  if (activityTimeoutTimer) clearTimeout(activityTimeoutTimer);
251
294
  if (process.stdout.isTTY) {
252
- process.stdout.write("\x1B[r");
253
- process.stdout.write("\x1B[?25h");
295
+ process.stdout.off("resize", onResize);
296
+ resetScrollRegion();
297
+ const rows = process.stdout.rows || 24;
298
+ if (lastBarStartRow > 0) {
299
+ for (let r = lastBarStartRow; r < lastBarStartRow + lastBarLinesCount; r++) {
300
+ process.stdout.write(`\x1B[${r};1H\x1B[2K`);
301
+ }
302
+ }
303
+ process.stdout.write(`\x1B[${rows};1H
304
+ \x1B[?25h`);
254
305
  try {
255
306
  process.stdin.setRawMode(false);
256
307
  } catch {
257
308
  }
258
309
  }
259
310
  };
311
+ const onResize = () => {
312
+ if (!process.stdout.isTTY) return;
313
+ const lines = buildBarLines();
314
+ const { scrollBottom } = getBarMetrics(lines.length);
315
+ applyScrollRegion(scrollBottom);
316
+ paintBar();
317
+ };
318
+ if (process.stdout.isTTY) {
319
+ process.stdout.on("resize", onResize);
320
+ }
260
321
  const resetActivityTimeout = () => {
261
322
  if (activityTimeoutTimer) clearTimeout(activityTimeoutTimer);
262
323
  activityTimeoutTimer = setTimeout(() => {
@@ -264,11 +325,9 @@ async function interactiveChatCommand(options) {
264
325
  isProcessing = false;
265
326
  renderer.finish(currentAgent === "build" ? "Build" : "Plan");
266
327
  const friendlyName = getFriendlyModelName(currentBackendModel);
267
- process.stdout.write(
328
+ printToContent(
268
329
  chalk.yellow(
269
- `
270
- \u26A0\uFE0F Operation timed out waiting for ${friendlyName}. You can retry or switch models with /models.
271
- `
330
+ `\u26A0\uFE0F Operation timed out waiting for ${friendlyName}. You can retry or switch models with /models.`
272
331
  )
273
332
  );
274
333
  render();
@@ -276,23 +335,6 @@ async function interactiveChatCommand(options) {
276
335
  }, 18e4);
277
336
  activityTimeoutTimer.unref();
278
337
  };
279
- const BAR_ROWS = 3;
280
- let currentBarRows = BAR_ROWS;
281
- const applyScrollRegion = (rows) => {
282
- const scrollBottom = Math.max(1, rows - currentBarRows);
283
- process.stdout.write(`\x1B[1;${scrollBottom}r`);
284
- process.stdout.write(`\x1B[${scrollBottom};1H`);
285
- };
286
- const paintBar = (lines) => {
287
- const rows = process.stdout.rows || 24;
288
- const barStartRow = rows - lines.length + 1;
289
- for (let i = 0; i < lines.length; i++) {
290
- process.stdout.write(`\x1B[${barStartRow + i};1H\x1B[2K`);
291
- process.stdout.write(lines[i]);
292
- }
293
- const scrollBottom = Math.max(1, rows - lines.length);
294
- process.stdout.write(`\x1B[${scrollBottom};1H`);
295
- };
296
338
  const buildBarLines = () => {
297
339
  const cols = process.stdout.columns || 80;
298
340
  const friendlyModel = getFriendlyModelName(currentBackendModel);
@@ -316,7 +358,17 @@ async function interactiveChatCommand(options) {
316
358
  if (filtered.length === 0) {
317
359
  lines.push(chalk.dim(" No matching models found."));
318
360
  } else {
319
- filtered.forEach((m, idx) => {
361
+ const MAX_MODELS = 6;
362
+ let startIdx = 0;
363
+ if (filtered.length > MAX_MODELS) {
364
+ startIdx = Math.max(0, Math.min(modelIndex - 2, filtered.length - MAX_MODELS));
365
+ }
366
+ const visibleModels = filtered.slice(startIdx, startIdx + MAX_MODELS);
367
+ if (filtered.length > MAX_MODELS) {
368
+ lines.push(chalk.dim(` Models (${modelIndex + 1}/${filtered.length}) \xB7 \u2191/\u2193 to navigate`));
369
+ }
370
+ visibleModels.forEach((m, relIdx) => {
371
+ const idx = startIdx + relIdx;
320
372
  const isCurrent = m.id === currentBackendModel;
321
373
  const prefix = isCurrent ? "\u25CF " : " ";
322
374
  const left = prefix + m.name;
@@ -377,15 +429,32 @@ async function interactiveChatCommand(options) {
377
429
  const matching = COMMANDS.filter((c) => c.name.startsWith(input));
378
430
  if (menuIndex >= matching.length) menuIndex = Math.max(0, matching.length - 1);
379
431
  if (matching.length > 0) {
380
- matching.forEach((c, idx) => {
432
+ const MAX_VISIBLE = 6;
433
+ let startIdx = 0;
434
+ if (matching.length > MAX_VISIBLE) {
435
+ startIdx = Math.max(0, Math.min(menuIndex - 2, matching.length - MAX_VISIBLE));
436
+ }
437
+ const visible = matching.slice(startIdx, startIdx + MAX_VISIBLE);
438
+ lines.push(
439
+ chalk.dim(` Commands (${menuIndex + 1}/${matching.length}) \xB7 \u2191/\u2193 navigate \xB7 Tab/Enter select \xB7 Esc cancel`)
440
+ );
441
+ visible.forEach((c, relIdx) => {
442
+ const absIdx = startIdx + relIdx;
443
+ const isSelected = absIdx === menuIndex;
381
444
  const row = " " + c.name.padEnd(12) + c.desc;
382
445
  const padded = row + " ".repeat(Math.max(2, width - row.length));
383
- lines.push(idx === menuIndex ? peachBg(padded) : chalk.white(row));
446
+ lines.push(isSelected ? peachBg(padded) : chalk.white(row));
384
447
  });
385
448
  }
386
449
  }
387
450
  const friendlyAgent = currentAgent === "build" ? "Build" : "Plan";
388
- lines.push(`${bar} ${input}\u2588`);
451
+ if (isProcessing) {
452
+ const frameIdx = renderer.getThinkingFrame() % THINKING_FRAMES.length;
453
+ const thinkingText = THINKING_FRAMES[frameIdx].trim();
454
+ lines.push(`${bar} ${thinkingText}`);
455
+ } else {
456
+ lines.push(`${bar} ${input}\u2588`);
457
+ }
389
458
  lines.push(`${bar}`);
390
459
  lines.push(
391
460
  ` ${chalk.hex(OCEAN_BLUE).bold(friendlyAgent)} ${chalk.dim("\xB7")} ${chalk.white(friendlyModel)} ${chalk.dim("\xB7 OceanCode")}`
@@ -393,29 +462,51 @@ async function interactiveChatCommand(options) {
393
462
  }
394
463
  return lines;
395
464
  };
396
- const render = () => {
397
- if (!process.stdout.isTTY) return;
398
- const lines = buildBarLines();
399
- const rows = process.stdout.rows || 24;
400
- if (lines.length !== currentBarRows) {
401
- currentBarRows = lines.length;
402
- applyScrollRegion(rows);
403
- }
404
- paintBar(lines);
405
- };
406
- const clearPromptArea = () => {
407
- if (!process.stdout.isTTY) return;
408
- const rows = process.stdout.rows || 24;
409
- const barStartRow = rows - currentBarRows + 1;
410
- for (let i = 0; i < currentBarRows; i++) {
411
- process.stdout.write(`\x1B[${barStartRow + i};1H\x1B[2K`);
465
+ const paintBar = () => {
466
+ try {
467
+ if (!process.stdout.isTTY) return;
468
+ const lines = buildBarLines();
469
+ const { rows, barStartRow, scrollBottom } = getBarMetrics(lines.length);
470
+ applyScrollRegion(scrollBottom);
471
+ if (lastBarStartRow > 0) {
472
+ if (lastBarStartRow !== barStartRow) {
473
+ for (let r = lastBarStartRow; r < lastBarStartRow + lastBarLinesCount; r++) {
474
+ process.stdout.write(`\x1B[${r};1H\x1B[2K`);
475
+ }
476
+ } else if (lastBarLinesCount > lines.length) {
477
+ for (let r = barStartRow + lines.length; r < barStartRow + lastBarLinesCount; r++) {
478
+ process.stdout.write(`\x1B[${r};1H\x1B[2K`);
479
+ }
480
+ }
481
+ }
482
+ lastBarStartRow = barStartRow;
483
+ lastBarLinesCount = lines.length;
484
+ for (let i = 0; i < lines.length; i++) {
485
+ const r = barStartRow + i;
486
+ process.stdout.write(`\x1B[${r};1H\x1B[2K${lines[i]}`);
487
+ }
488
+ if (!isProcessing && mode === "normal") {
489
+ process.stdout.write("\x1B[?25h");
490
+ const inputLineIdx = Math.max(0, lines.length - 3);
491
+ const targetRow = barStartRow + inputLineIdx;
492
+ const col = Math.min(process.stdout.columns || 80, 3 + input.length);
493
+ process.stdout.write(`\x1B[${targetRow};${col}H`);
494
+ } else if (!isProcessing && mode === "model_selector") {
495
+ process.stdout.write("\x1B[?25h");
496
+ const col = Math.min(process.stdout.columns || 80, 9 + modelSearch.length);
497
+ process.stdout.write(`\x1B[${barStartRow + 1};${col}H`);
498
+ } else {
499
+ process.stdout.write("\x1B[?25l");
500
+ process.stdout.write(`\x1B[${contentRow};1H`);
501
+ }
502
+ } catch {
412
503
  }
413
- const scrollBottom = Math.max(1, rows - currentBarRows);
414
- process.stdout.write(`\x1B[${scrollBottom};1H`);
415
504
  };
505
+ const render = () => paintBar();
416
506
  renderer.setPinBar(
417
- () => paintBar(buildBarLines()),
418
- () => currentBarRows
507
+ () => paintBar(),
508
+ () => {
509
+ }
419
510
  );
420
511
  const reasoningPartIds = /* @__PURE__ */ new Set();
421
512
  const textPartIds = /* @__PURE__ */ new Set();
@@ -454,8 +545,8 @@ async function interactiveChatCommand(options) {
454
545
  }
455
546
  }
456
547
  if (event.type === "message.part.delta") {
457
- const partId = event.properties?.partID;
458
- if (partId && reasoningPartIds.has(partId)) return;
548
+ const partId = event.properties?.partID || event.properties?.partId || event.properties?.id || "default";
549
+ if (reasoningPartIds.has(partId)) return;
459
550
  const delta = event.properties?.delta;
460
551
  const field = event.properties?.field;
461
552
  if (field === "text" && typeof delta === "string") {
@@ -477,9 +568,7 @@ async function interactiveChatCommand(options) {
477
568
  renderer.finish(currentAgent === "build" ? "Build" : "Plan");
478
569
  const errObj = event.properties?.error;
479
570
  const errMsg = errObj?.message || errObj?.data?.message || (typeof errObj === "string" ? errObj : "Model request failed or timed out.");
480
- console.error(chalk.red(`
481
- \u26A0\uFE0F Model Error (${getFriendlyModelName(currentBackendModel)}): ${errMsg}
482
- `));
571
+ printToContent(chalk.red(`\u26A0\uFE0F Model Error (${getFriendlyModelName(currentBackendModel)}): ${errMsg}`));
483
572
  render();
484
573
  }
485
574
  }, void 0, session.directory);
@@ -535,7 +624,6 @@ async function interactiveChatCommand(options) {
535
624
  if (str === "d" || str === "D") {
536
625
  const prov = SUPPORTED_BYOK_PROVIDERS[connectProviderIndex];
537
626
  removeProviderKey(prov.id);
538
- clearPromptArea();
539
627
  console.log(chalk.yellow(`
540
628
  \u2713 Disconnected API key for ${prov.name}.
541
629
  `));
@@ -569,7 +657,6 @@ async function interactiveChatCommand(options) {
569
657
  connectStep = "provider_list";
570
658
  connectKeyInput = "";
571
659
  connectErrorMsg = "";
572
- clearPromptArea();
573
660
  console.log(
574
661
  chalk.green(
575
662
  `
@@ -631,11 +718,8 @@ async function interactiveChatCommand(options) {
631
718
  mode = "normal";
632
719
  input = "";
633
720
  modelSearch = "";
634
- clearPromptArea();
635
721
  if (selectedFriendly) {
636
- console.log(chalk.green(`
637
- \u2713 Active model changed to: ${selectedFriendly} (${selectedId})
638
- `));
722
+ printToContent(chalk.green(`\u2713 Active model changed to: ${selectedFriendly} (${selectedId})`));
639
723
  }
640
724
  render();
641
725
  return;
@@ -688,7 +772,7 @@ async function interactiveChatCommand(options) {
688
772
  input = "";
689
773
  menuIndex = 0;
690
774
  if (!submitted) {
691
- render();
775
+ paintBar();
692
776
  return;
693
777
  }
694
778
  if (submitted === "/exit" || submitted === "/quit") {
@@ -700,68 +784,60 @@ async function interactiveChatCommand(options) {
700
784
  process.exit(0);
701
785
  }
702
786
  if (submitted === "/clear") {
703
- clearPromptArea();
704
787
  if (process.stdout.isTTY) {
705
788
  process.stdout.write("\x1B[2J\x1B[H");
789
+ const { scrollBottom } = getBarMetrics(3);
790
+ applyScrollRegion(scrollBottom);
791
+ process.stdout.write("\x1B[1;1H");
706
792
  }
707
793
  renderTopBar(getFriendlyModelName(currentBackendModel));
708
- render();
794
+ contentRow = 8;
795
+ lastBarStartRow = 0;
796
+ paintBar();
709
797
  return;
710
798
  }
711
799
  if (submitted === "/init") {
712
- clearPromptArea();
713
800
  try {
714
801
  const res = initializeAgentsDoc(process.cwd());
715
- console.log(chalk.green(`
716
- \u2714 ${res.summary}`));
717
- console.log(chalk.dim(` Location: ${res.filePath}
718
- `));
802
+ printToContent(`${chalk.green(`\u2714 ${res.summary}`)}
803
+ ${chalk.dim(` Location: ${res.filePath}`)}`);
719
804
  } catch (err) {
720
- console.error(chalk.red(`
721
- \u26A0\uFE0F Initialization failed: ${err.message}
722
- `));
805
+ printToContent(chalk.red(`\u26A0\uFE0F Initialization failed: ${err.message}`));
723
806
  }
724
807
  render();
725
808
  return;
726
809
  }
727
810
  if (submitted === "/new") {
728
- clearPromptArea();
729
811
  try {
730
812
  session = await client.createSession("Ocean Session", process.cwd());
731
- console.log(chalk.green("\n\u2714 Started a new coding session.\n"));
813
+ printToContent(chalk.green("\u2714 Started a new coding session."));
732
814
  } catch (err) {
733
- console.error(chalk.red(`
734
- Failed to create new session: ${err.message}
735
- `));
815
+ printToContent(chalk.red(`Failed to create new session: ${err.message}`));
736
816
  }
737
817
  render();
738
818
  return;
739
819
  }
740
820
  if (submitted === "/undo") {
741
- clearPromptArea();
742
821
  if (lastUserMsgId) {
743
822
  try {
744
823
  const success = await client.revertSession(session.id, lastUserMsgId);
745
824
  if (success) {
746
- console.log(chalk.green("\n\u2714 Reverted the last AI action and restored previous file states.\n"));
825
+ printToContent(chalk.green("\u2714 Reverted the last AI action and restored previous file states."));
747
826
  } else {
748
- console.log(chalk.yellow("\nCould not revert the last action.\n"));
827
+ printToContent(chalk.yellow("Could not revert the last action."));
749
828
  }
750
829
  } catch (err) {
751
- console.error(chalk.red(`
752
- Revert failed: ${err.message}
753
- `));
830
+ printToContent(chalk.red(`Revert failed: ${err.message}`));
754
831
  }
755
832
  } else {
756
- console.log(chalk.yellow("\nNo previous action found to undo.\n"));
833
+ printToContent(chalk.yellow("No previous action found to undo."));
757
834
  }
758
835
  render();
759
836
  return;
760
837
  }
761
838
  if (submitted === "/redo") {
762
839
  if (!lastSubmittedPrompt) {
763
- clearPromptArea();
764
- console.log(chalk.yellow("\nNo previous action to redo.\n"));
840
+ printToContent(chalk.yellow("No previous action to redo."));
765
841
  render();
766
842
  return;
767
843
  }
@@ -770,10 +846,9 @@ Revert failed: ${err.message}
770
846
  if (submitted.startsWith("/goal")) {
771
847
  const goalDesc = submitted.replace(/^\/goal\s*/, "").trim();
772
848
  if (!goalDesc) {
773
- clearPromptArea();
774
- console.log(
849
+ printToContent(
775
850
  chalk.yellow(
776
- "\nPlease provide a goal description.\nUsage: /goal <your objective or feature description>\nExample: /goal Add user authentication with JWT and refresh tokens\n"
851
+ "Please provide a goal description.\nUsage: /goal <your objective or feature description>\nExample: /goal Add user authentication with JWT and refresh tokens"
777
852
  )
778
853
  );
779
854
  render();
@@ -794,22 +869,17 @@ Please accomplish this goal systematically:
794
869
  const provArg = parts[1]?.toLowerCase();
795
870
  const keyArg = parts[2];
796
871
  if (provArg && keyArg) {
797
- clearPromptArea();
798
- console.log(chalk.cyan(`
799
- Validating API key with ${provArg.toUpperCase()}...`));
872
+ printToContent(chalk.cyan(`Validating API key with ${provArg.toUpperCase()}...`));
800
873
  const val = await validateProviderKey(provArg, keyArg);
801
874
  if (val.valid) {
802
875
  saveProviderKey(provArg, keyArg);
803
- console.log(
876
+ printToContent(
804
877
  chalk.green(
805
- `\u2714 Successfully verified and connected ${provArg.toUpperCase()}! Its models are now unlocked in /models.
806
- `
878
+ `\u2714 Successfully verified and connected ${provArg.toUpperCase()}! Its models are now unlocked in /models.`
807
879
  )
808
880
  );
809
881
  } else {
810
- console.error(chalk.red(`
811
- \u26A0\uFE0F Validation failed for ${provArg.toUpperCase()}: ${val.message}
812
- `));
882
+ printToContent(chalk.red(`\u26A0\uFE0F Validation failed for ${provArg.toUpperCase()}: ${val.message}`));
813
883
  }
814
884
  render();
815
885
  return;
@@ -824,16 +894,15 @@ Validating API key with ${provArg.toUpperCase()}...`));
824
894
  }
825
895
  }
826
896
  if (submitted.startsWith("/mcp")) {
827
- clearPromptArea();
828
897
  const parts = submitted.split(" ").filter(Boolean);
829
898
  const sub = parts[1]?.toLowerCase();
830
899
  if (sub === "add") {
831
900
  const serverName = parts[2];
832
901
  const serverCmdOrUrl = parts.slice(3).join(" ");
833
902
  if (!serverName || !serverCmdOrUrl) {
834
- console.log(
903
+ printToContent(
835
904
  chalk.yellow(
836
- "\nUsage: /mcp add <server-name> <command-or-url>\nExamples:\n /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem .\n /mcp add remote https://example.com/mcp\n"
905
+ "Usage: /mcp add <server-name> <command-or-url>\nExamples:\n /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem .\n /mcp add remote https://example.com/mcp"
837
906
  )
838
907
  );
839
908
  render();
@@ -842,10 +911,8 @@ Validating API key with ${provArg.toUpperCase()}...`));
842
911
  const isUrl = serverCmdOrUrl.startsWith("http://") || serverCmdOrUrl.startsWith("https://");
843
912
  const mcpConfig = isUrl ? { type: "remote", url: serverCmdOrUrl, enabled: true } : { type: "local", command: serverCmdOrUrl.split(" "), enabled: true };
844
913
  saveMcpServer(serverName, mcpConfig);
845
- console.log(
846
- chalk.green(`
847
- \u2713 Added MCP server "${serverName}" (${mcpConfig.type}). Configured in opencode.json.
848
- `)
914
+ printToContent(
915
+ chalk.green(`\u2713 Added MCP server "${serverName}" (${mcpConfig.type}). Configured in opencode.json.`)
849
916
  );
850
917
  render();
851
918
  return;
@@ -854,9 +921,9 @@ Validating API key with ${provArg.toUpperCase()}...`));
854
921
  const live = await fetchLiveMcpStatus(baseUrl);
855
922
  const names = Array.from(/* @__PURE__ */ new Set([...Object.keys(configured), ...Object.keys(live)]));
856
923
  if (names.length === 0) {
857
- console.log(
924
+ printToContent(
858
925
  chalk.dim(
859
- "\nNo Model Context Protocol (MCP) servers configured.\n\nTo add an MCP server, run:\n /mcp add <name> <command-or-url>\nExample:\n /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem .\n"
926
+ "No Model Context Protocol (MCP) servers configured.\n\nTo add an MCP server, run:\n /mcp add <name> <command-or-url>\nExample:\n /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem ."
860
927
  )
861
928
  );
862
929
  } else {
@@ -869,15 +936,14 @@ Validating API key with ${provArg.toUpperCase()}...`));
869
936
  return ` \u2022 ${n} (${type}): ${badge}
870
937
  ${chalk.dim(target)}`;
871
938
  }).join("\n");
872
- console.log(chalk.bold("\nModel Context Protocol (MCP) Servers:"));
873
- console.log(lines);
874
- console.log(chalk.dim("\nAdd new servers with: /mcp add <name> <command>\n"));
939
+ printToContent(
940
+ chalk.bold("Model Context Protocol (MCP) Servers:\n") + lines + chalk.dim("\n\nAdd new servers with: /mcp add <name> <command>")
941
+ );
875
942
  }
876
943
  render();
877
944
  return;
878
945
  }
879
946
  if (submitted === "/agent" || submitted.startsWith("/agent ") || submitted === "/mode" || submitted.startsWith("/mode ")) {
880
- clearPromptArea();
881
947
  if (submitted.includes(" ")) {
882
948
  const target = submitted.split(" ")[1].toLowerCase().trim();
883
949
  if (target === "plan" || target === "build") {
@@ -888,43 +954,34 @@ Validating API key with ${provArg.toUpperCase()}...`));
888
954
  } else {
889
955
  currentAgent = currentAgent === "build" ? "plan" : "build";
890
956
  }
891
- console.log(chalk.cyan(`
892
- \u2713 Switched mode to: ${currentAgent.toUpperCase()}
893
- `));
957
+ printToContent(chalk.cyan(`\u2713 Switched mode to: ${currentAgent.toUpperCase()}`));
894
958
  render();
895
959
  return;
896
960
  }
897
961
  if (submitted === "/diff") {
898
- clearPromptArea();
899
962
  try {
900
963
  const diffs = await client.getSessionDiff(session.id);
901
964
  if (!diffs || diffs.length === 0) {
902
- console.log(chalk.dim("\nNo file modifications recorded in the current session.\n"));
965
+ printToContent(chalk.dim("No file modifications recorded in the current session."));
903
966
  } else {
904
967
  const diffSummary = diffs.map(
905
968
  (d) => ` \u2022 ${d.path || d.file || "file"} (+${d.additions || 0} -${d.deletions || 0})`
906
969
  ).join("\n");
907
- console.log(chalk.bold(`
908
- Modified Files (${diffs.length}):`));
909
- console.log(diffSummary + "\n");
970
+ printToContent(chalk.bold(`Modified Files (${diffs.length}):
971
+ `) + diffSummary);
910
972
  }
911
973
  } catch (err) {
912
- console.error(chalk.red(`
913
- Failed to retrieve diff: ${err.message}
914
- `));
974
+ printToContent(chalk.red(`Failed to retrieve diff: ${err.message}`));
915
975
  }
916
976
  render();
917
977
  return;
918
978
  }
919
979
  if (submitted === "/compact") {
920
- clearPromptArea();
921
980
  try {
922
981
  await client.summarizeSession(session.id);
923
- console.log(chalk.green("\n\u2713 Session context memory compacted and summarized successfully.\n"));
982
+ printToContent(chalk.green("\u2713 Session context memory compacted and summarized successfully."));
924
983
  } catch (err) {
925
- console.error(chalk.red(`
926
- Compact failed: ${err.message}
927
- `));
984
+ printToContent(chalk.red(`Compact failed: ${err.message}`));
928
985
  }
929
986
  render();
930
987
  return;
@@ -933,13 +990,10 @@ Compact failed: ${err.message}
933
990
  const parts = submitted.split(" ");
934
991
  const targetArg = parts.slice(1).join(" ").trim();
935
992
  if (targetArg) {
936
- clearPromptArea();
937
993
  const resolvedId = getBackendModelId(targetArg);
938
994
  currentBackendModel = resolvedId;
939
995
  const friendly = getFriendlyModelName(resolvedId);
940
- console.log(chalk.green(`
941
- \u2713 Switched model to: ${friendly} (${resolvedId})
942
- `));
996
+ printToContent(chalk.green(`\u2713 Switched model to: ${friendly} (${resolvedId})`));
943
997
  render();
944
998
  return;
945
999
  } else {
@@ -951,35 +1005,34 @@ Compact failed: ${err.message}
951
1005
  }
952
1006
  }
953
1007
  if (submitted === "/help") {
954
- clearPromptArea();
955
- console.log(chalk.bold("\nAvailable Commands:"));
956
- for (const c of COMMANDS) {
957
- console.log(` ${chalk.hex(OCEAN_BLUE).bold(c.name.padEnd(12))} ${chalk.dim(c.desc)}`);
958
- }
959
- console.log("");
1008
+ const helpLines = [
1009
+ chalk.bold("Available Commands:"),
1010
+ ...COMMANDS.map((c) => ` ${chalk.hex(OCEAN_BLUE).bold(c.name.padEnd(12))} ${chalk.dim(c.desc)}`)
1011
+ ].join("\n");
1012
+ printToContent(helpLines);
960
1013
  render();
961
1014
  return;
962
1015
  }
963
1016
  if (submitted === "/info") {
964
- clearPromptArea();
965
- console.log(
966
- `
967
- Session Details:
1017
+ printToContent(
1018
+ `Session Details:
968
1019
  Model: ${getFriendlyModelName(currentBackendModel)}
969
1020
  Agent: ${currentAgent.toUpperCase()}
970
1021
  Directory: ${session.directory}
971
- Session: ${session.id}
972
- `
1022
+ Session: ${session.id}`
973
1023
  );
974
1024
  render();
975
1025
  return;
976
1026
  }
977
1027
  const friendlyName = getFriendlyModelName(currentBackendModel);
978
- clearPromptArea();
1028
+ process.stdout.write(`\x1B[${contentRow};1H`);
979
1029
  renderUserMessageCard(submitted, friendlyName);
1030
+ const promptLines = submitted.split("\n").length;
1031
+ contentRow = Math.min(currentScrollBottom || 20, contentRow + promptLines);
980
1032
  isProcessing = true;
981
1033
  userMsgId = null;
982
1034
  lastSubmittedPrompt = submitted;
1035
+ paintBar();
983
1036
  resetActivityTimeout();
984
1037
  renderer.start(friendlyName);
985
1038
  client.promptSession(session.id, submitted, {
@@ -990,10 +1043,8 @@ Session Details:
990
1043
  if (activityTimeoutTimer) clearTimeout(activityTimeoutTimer);
991
1044
  isProcessing = false;
992
1045
  renderer.finish(currentAgent === "build" ? "Build" : "Plan");
993
- console.error(chalk.red(`
994
- \u26A0\uFE0F Prompt Error: ${err.message}
995
- `));
996
- render();
1046
+ printToContent(chalk.red(`\u26A0\uFE0F Prompt Error: ${err.message}`));
1047
+ paintBar();
997
1048
  });
998
1049
  return;
999
1050
  }
@@ -1012,20 +1063,13 @@ Session Details:
1012
1063
  };
1013
1064
  process.stdin.on("keypress", onKeypress);
1014
1065
  if (process.stdout.isTTY) {
1015
- process.stdout.on("resize", () => {
1016
- const rows = process.stdout.rows || 24;
1017
- applyScrollRegion(rows);
1018
- render();
1019
- });
1020
- }
1021
- if (process.stdout.isTTY) {
1022
- const rows = process.stdout.rows || 24;
1023
- process.stdout.write("\x1B[2J");
1024
- process.stdout.write("\x1B[H");
1066
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1067
+ const { scrollBottom } = getBarMetrics(3);
1068
+ applyScrollRegion(scrollBottom);
1069
+ process.stdout.write("\x1B[1;1H");
1025
1070
  renderTopBar(getFriendlyModelName(currentBackendModel));
1026
- currentBarRows = BAR_ROWS;
1027
- applyScrollRegion(rows);
1028
- paintBar(buildBarLines());
1071
+ contentRow = 8;
1072
+ paintBar();
1029
1073
  }
1030
1074
  }
1031
1075
 
@@ -1090,7 +1134,7 @@ async function sessionCommand() {
1090
1134
  const date = new Date(s.time?.created || Date.now()).toLocaleDateString();
1091
1135
  const tokens = (s.tokens?.input || 0) + (s.tokens?.output || 0);
1092
1136
  console.log(`${indent}${chalk3.cyan(s.id.slice(0, 16))} ${chalk3.bold.white(s.title || "Untitled")}`);
1093
- console.log(`${indent} ${chalk3.dim(date)} \uFFFD ${chalk3.dim(`${tokens} tokens`)} \uFFFD ${chalk3.dim(s.directory)}`);
1137
+ console.log(`${indent} ${chalk3.dim(date)} \xB7 ${chalk3.dim(`${tokens} tokens`)} \xB7 ${chalk3.dim(s.directory)}`);
1094
1138
  console.log("");
1095
1139
  }
1096
1140
  } catch (err) {
@@ -1139,7 +1183,19 @@ program.name("oceancode").description("Ocean CLI: Next-generation AI coding assi
1139
1183
  ${getBigLogo()}
1140
1184
  `);
1141
1185
  program.option("-m, --model <model>", "Model to use (default: opencode/muse-spark-1.3-contributor-free [1M Context])").action(async (options) => {
1142
- await interactiveChatCommand({ model: options.model });
1186
+ try {
1187
+ await interactiveChatCommand({ model: options.model });
1188
+ } catch (err) {
1189
+ if (process.stdout.isTTY) {
1190
+ const rows = process.stdout.rows || 24;
1191
+ process.stdout.write(`\x1B[1;${rows}r\x1B[?25h
1192
+ `);
1193
+ }
1194
+ console.error(chalk5.red(`
1195
+ An error occurred: ${err?.message || err}
1196
+ `));
1197
+ process.exit(1);
1198
+ }
1143
1199
  });
1144
1200
  program.command("run [message...]").description("Run a prompt or instruction directly").option("-m, --model <model>", "Model to use (default: opencode/muse-spark-1.3-contributor-free)").option("-a, --agent <agent>", "Agent mode (default: build)").option("--auto", "Auto-approve tool permissions").action(async (messages, options, cmd) => {
1145
1201
  const opts = cmd?.optsWithGlobals ? cmd.optsWithGlobals() : options;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runCommand
3
- } from "./chunk-EB736JHH.js";
3
+ } from "./chunk-U5PZQWEB.js";
4
4
  export {
5
5
  runCommand
6
6
  };
package/package.json CHANGED
@@ -1,28 +1,28 @@
1
1
  {
2
2
  "name": "oceancode",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Next-generation CLI coding assistant powered by OpenCode server",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "oceancode": "./dist/index.js",
8
- "ocean": "./dist/index.js"
7
+ "oceancode": "dist/index.js",
8
+ "ocean": "dist/index.js"
9
9
  },
10
- "type": "module",
11
- "files": [
12
- "dist",
13
- "scripts/postinstall.mjs",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
- "engines": {
18
- "node": ">=18"
19
- },
20
- "scripts": {
21
- "build": "tsup",
22
- "dev": "tsx src/index.ts",
23
- "start": "node dist/index.js",
24
- "postinstall": "node ./scripts/postinstall.mjs",
25
- "prepublishOnly": "npm run build"
10
+ "type": "module",
11
+ "files": [
12
+ "dist",
13
+ "scripts/postinstall.mjs",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsx src/index.ts",
23
+ "start": "node dist/index.js",
24
+ "postinstall": "node ./scripts/postinstall.mjs",
25
+ "prepublishOnly": "npm run build"
26
26
  },
27
27
  "keywords": [
28
28
  "ai",
@@ -33,13 +33,13 @@
33
33
  ],
34
34
  "author": "",
35
35
  "license": "MIT",
36
- "dependencies": {
37
- "@inquirer/prompts": "^7.3.2",
38
- "chalk": "^5.4.1",
39
- "commander": "^13.1.0",
40
- "dotenv": "^16.4.7",
41
- "opencode-ai": "^1.18.29",
42
- "ora": "^8.2.0"
36
+ "dependencies": {
37
+ "@inquirer/prompts": "^7.3.2",
38
+ "chalk": "^5.4.1",
39
+ "commander": "^13.1.0",
40
+ "dotenv": "^16.4.7",
41
+ "opencode-ai": "^1.18.29",
42
+ "ora": "^8.2.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^22.13.5",
@@ -47,4 +47,4 @@
47
47
  "tsx": "^4.19.3",
48
48
  "typescript": "^5.7.3"
49
49
  }
50
- }
50
+ }