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
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
648
|
-
},
|
|
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
|
-
|
|
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 (
|
|
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-
|
|
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-
|
|
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.
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
446
|
+
lines.push(isSelected ? peachBg(padded) : chalk.white(row));
|
|
384
447
|
});
|
|
385
448
|
}
|
|
386
449
|
}
|
|
387
450
|
const friendlyAgent = currentAgent === "build" ? "Build" : "Plan";
|
|
388
|
-
|
|
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
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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(
|
|
418
|
-
() =>
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
813
|
+
printToContent(chalk.green("\u2714 Started a new coding session."));
|
|
732
814
|
} catch (err) {
|
|
733
|
-
|
|
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
|
-
|
|
825
|
+
printToContent(chalk.green("\u2714 Reverted the last AI action and restored previous file states."));
|
|
747
826
|
} else {
|
|
748
|
-
|
|
827
|
+
printToContent(chalk.yellow("Could not revert the last action."));
|
|
749
828
|
}
|
|
750
829
|
} catch (err) {
|
|
751
|
-
|
|
752
|
-
Revert failed: ${err.message}
|
|
753
|
-
`));
|
|
830
|
+
printToContent(chalk.red(`Revert failed: ${err.message}`));
|
|
754
831
|
}
|
|
755
832
|
} else {
|
|
756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
774
|
-
console.log(
|
|
849
|
+
printToContent(
|
|
775
850
|
chalk.yellow(
|
|
776
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
903
|
+
printToContent(
|
|
835
904
|
chalk.yellow(
|
|
836
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
924
|
+
printToContent(
|
|
858
925
|
chalk.dim(
|
|
859
|
-
"
|
|
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
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
908
|
-
|
|
909
|
-
console.log(diffSummary + "\n");
|
|
970
|
+
printToContent(chalk.bold(`Modified Files (${diffs.length}):
|
|
971
|
+
`) + diffSummary);
|
|
910
972
|
}
|
|
911
973
|
} catch (err) {
|
|
912
|
-
|
|
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
|
-
|
|
982
|
+
printToContent(chalk.green("\u2713 Session context memory compacted and summarized successfully."));
|
|
924
983
|
} catch (err) {
|
|
925
|
-
|
|
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
|
-
|
|
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
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
965
|
-
|
|
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
|
-
|
|
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
|
-
|
|
994
|
-
|
|
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.
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
-
|
|
1027
|
-
|
|
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)} \
|
|
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
|
-
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oceancode",
|
|
3
|
-
"version": "0.1.
|
|
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": "
|
|
8
|
-
"ocean": "
|
|
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
|
+
}
|