@sns-myagent/cli 0.3.5 → 0.3.7
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.
- package/README.md +51 -1
- package/package.json +1 -1
- package/scripts/smoke-tui.ts +18 -22
- package/src/tui/chat-blocks.ts +85 -179
- package/src/tui/code-cell.ts +9 -9
- package/src/tui/command-palette.ts +56 -174
- package/src/tui/index.ts +1 -1
- package/src/tui/splash.ts +67 -111
- package/src/ui/error-display.ts +60 -103
- package/src/ui/gradient.ts +15 -95
- package/src/ui/index.ts +3 -9
- package/src/ui/memory-toast.ts +38 -77
- package/src/ui/status-bar.ts +24 -47
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
| Feature | Source |
|
|
61
61
|
|---------|--------|
|
|
62
62
|
| **30 built-in tools** | `src/tools/builtin-names.ts` |
|
|
63
|
-
| **
|
|
63
|
+
| **157 built-in slash commands** | `src/slash-commands/builtin-registry.ts` |
|
|
64
64
|
| **Multi-provider LLM** | OpenAI, Anthropic, Ollama, custom endpoints via `@oh-my-pi/pi-ai` |
|
|
65
65
|
| **7 memory backends** | mnemopi (default), hindsight, mnemosyne, mem0, lcm, local, off — `src/memory-backend/resolve.ts` |
|
|
66
66
|
| **MCP integration** | 22 source files in `src/mcp/` |
|
|
@@ -597,6 +597,56 @@ MIT — see [LICENSE](LICENSE).
|
|
|
597
597
|
|
|
598
598
|
---
|
|
599
599
|
|
|
600
|
+
## Implementation Status (v0.3.7)
|
|
601
|
+
|
|
602
|
+
What is actually wired and working in the source tree, verified 2026-06-30:
|
|
603
|
+
|
|
604
|
+
### Tools (30 / 30) — `src/tools/builtin-names.ts`
|
|
605
|
+
|
|
606
|
+
All 30 tools are real implementations, not stubs.
|
|
607
|
+
|
|
608
|
+
### Slash Commands (157) — `src/slash-commands/builtin-registry.ts`
|
|
609
|
+
|
|
610
|
+
157 commands registered. All callable via `/<name>` in interactive mode.
|
|
611
|
+
|
|
612
|
+
### Memory Backends (7 / 7) — `src/memory-backend/resolve.ts`
|
|
613
|
+
|
|
614
|
+
| Backend | Status | Requires config |
|
|
615
|
+
|---------|--------|-----------------|
|
|
616
|
+
| `mnemopi` (default) | ✅ Works out of box | None — local SQLite + vector + graph |
|
|
617
|
+
| `local` | ✅ Works out of box | None — rollout summary pipeline |
|
|
618
|
+
| `off` | ✅ Works out of box | None — no-op |
|
|
619
|
+
| `mnemosyne` | ✅ Wired | `mnemosyne` Python daemon running locally |
|
|
620
|
+
| `mem0` | ✅ Wired | `MEM0_API_KEY` (or self-hosted endpoint) |
|
|
621
|
+
| `lcm` | ✅ Wired | `LCM_HOST` (default `127.0.0.1:8500`) |
|
|
622
|
+
| `hindsight` | ✅ Wired | `HINDSIGHT_API_KEY` or remote endpoint |
|
|
623
|
+
|
|
624
|
+
Set via `memory.backend` in `~/.sns-myagent/config.yaml`. Default is `mnemopi`.
|
|
625
|
+
|
|
626
|
+
### Telegram Adapter — `src/adapters/telegram/`
|
|
627
|
+
|
|
628
|
+
5 files (bot, handler, format, index, bridge). 10 slash commands wired: `/start /help /chat /reset /status /memory /cron /model /code /review`. File upload/download supported. Auto-boot when `SNS_TELEGRAM_BOT_TOKEN` is set.
|
|
629
|
+
|
|
630
|
+
### Multi-Agent Orchestration — `src/agents/`
|
|
631
|
+
|
|
632
|
+
7 files. `agents.yaml` config, 3 ensemble strategies (consensus / critic / best-of-N), resilience (retry + circuit-breaker + timeout), 21 tests pass. CLI `orchestrate <prompt>` is wired but executor is stubbed (returns clear "not wired yet" message).
|
|
633
|
+
|
|
634
|
+
### Terminal UI — `src/tui/` + `src/ui/`
|
|
635
|
+
|
|
636
|
+
8 files. `MY snsagent` splash, `●` prefix chat blocks, flat status bar, command palette, error display, memory toast. No gradient, no rounded boxes — flat list style. v0.3.6+ rebrand.
|
|
637
|
+
|
|
638
|
+
### CI / CD
|
|
639
|
+
|
|
640
|
+
GitHub Actions runs 7-stage pipeline: typecheck, lint, build, test, diagnose, smoke, all-checks. 5 platform binaries (linux x64/arm64, macos x64/arm64, windows x64) auto-built on tag push. Docker multi-arch image auto-built and pushed to `ghcr.io/reihantt6/sns-myagent`.
|
|
641
|
+
|
|
642
|
+
### Published
|
|
643
|
+
|
|
644
|
+
- npm: `npm install -g @sns-myagent/cli` → v0.3.7
|
|
645
|
+
- GitHub releases: 5 binaries per version
|
|
646
|
+
- Docker: `ghcr.io/reihantt6/sns-myagent:v0.3.7`
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
600
650
|
## Credits
|
|
601
651
|
|
|
602
652
|
Forked from [Pi Agent / oh-my-pi](https://github.com/can1357/oh-my-pi) by can1357. Stripped to a focused, single-user terminal agent with conversational configuration.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sns-myagent/cli",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.7",
|
|
5
5
|
"description": "SNS-MyAgent \u2014 BYOK coding agent CLI. Multi-agent + Telegram + self-learning memory. ~120MB binary, zero forced cost.",
|
|
6
6
|
"homepage": "https://github.com/Reihantt6/sns-myagent",
|
|
7
7
|
"author": "Reihantt6",
|
package/scripts/smoke-tui.ts
CHANGED
|
@@ -11,15 +11,8 @@ import { renderChatBlock, renderSessionHeader } from "../src/tui/chat-blocks.js"
|
|
|
11
11
|
import { CHAT_COMMANDS, renderCommandPalette } from "../src/tui/command-palette.js";
|
|
12
12
|
import { renderErrorDisplay } from "../src/ui/error-display.js";
|
|
13
13
|
import { renderMemoryToast, renderMemoryRecall } from "../src/ui/memory-toast.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
accentGrad,
|
|
17
|
-
subtle,
|
|
18
|
-
gradientLine,
|
|
19
|
-
labeledGradientLine,
|
|
20
|
-
roleGradient,
|
|
21
|
-
statusGradient,
|
|
22
|
-
} from "../src/ui/gradient.js";
|
|
14
|
+
import { renderStatusBar } from "../src/ui/status-bar.js";
|
|
15
|
+
import { accent, brand, subtle, inline } from "../src/ui/gradient.js";
|
|
23
16
|
|
|
24
17
|
// Stub stdout.columns so width-dependent renderers behave deterministically.
|
|
25
18
|
Object.defineProperty(process.stdout, "columns", { value: 100, configurable: true });
|
|
@@ -69,7 +62,7 @@ console.log(
|
|
|
69
62
|
);
|
|
70
63
|
|
|
71
64
|
section("3. SESSION HEADER");
|
|
72
|
-
console.log(renderSessionHeader("openai/gpt-4o-mini", "0.3.
|
|
65
|
+
console.log(renderSessionHeader("openai/gpt-4o-mini", "0.3.5"));
|
|
73
66
|
|
|
74
67
|
section("4. COMMAND PALETTE");
|
|
75
68
|
console.log(
|
|
@@ -99,16 +92,19 @@ console.log(
|
|
|
99
92
|
renderMemoryRecall("dark theme", "user prefers dark theme for terminal UI", 0.92),
|
|
100
93
|
);
|
|
101
94
|
|
|
102
|
-
section("
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(
|
|
110
|
-
console.log("statusGradient(warning):", statusGradient("warning")("⚠ warning"));
|
|
111
|
-
console.log("statusGradient(error): ", statusGradient("error")("✗ failed"));
|
|
112
|
-
console.log("statusGradient(info): ", statusGradient("info")("ℹ info"));
|
|
95
|
+
section("8. STATUS BAR");
|
|
96
|
+
renderStatusBar({
|
|
97
|
+
model: "gpt-4o-mini",
|
|
98
|
+
tokensUsed: 12345,
|
|
99
|
+
sessionStarted: Date.now() - 10_000,
|
|
100
|
+
memoryHits: 3,
|
|
101
|
+
});
|
|
102
|
+
console.log(); // newline after status bar (renderStatusBar writes to stdout directly)
|
|
113
103
|
|
|
114
|
-
|
|
104
|
+
section("9. GRADIENT HELPERS (minimal)");
|
|
105
|
+
console.log("brand: ", brand("SnsAgent — coding agent CLI"));
|
|
106
|
+
console.log("accent: ", accent("accent cyan"));
|
|
107
|
+
console.log("subtle: ", subtle("dim text"));
|
|
108
|
+
console.log("inline: ", inline("file.ts:42"));
|
|
109
|
+
|
|
110
|
+
console.log("\n\x1b[1m\x1b[32m✓ All 7 TUI components rendered without crashing.\x1b[0m");
|
package/src/tui/chat-blocks.ts
CHANGED
|
@@ -1,205 +1,111 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Chat
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Chat blocks — flat `●` prefix style.
|
|
3
|
+
* No rounded boxes, no per-role color. One accent (cyan) for the bullet.
|
|
4
|
+
* Role distinction via the bullet label only.
|
|
5
5
|
*/
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import gradient from "gradient-string";
|
|
8
7
|
import { visibleWidth } from "@oh-my-pi/pi-tui";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
// ── Color themes per role ──
|
|
24
|
-
const ROLE_COLORS = {
|
|
25
|
-
user: { border: chalk.cyan, label: chalk.cyan.bold, gradient: [ROLE_HEX.user, "#0099cc"] },
|
|
26
|
-
assistant: { border: chalk.magenta, label: chalk.magenta.bold, gradient: [ROLE_HEX.assistant, "#9b59b6"] },
|
|
27
|
-
tool: { border: chalk.yellow, label: chalk.yellow, gradient: [ROLE_HEX.tool, "#e6a700"] },
|
|
28
|
-
system: { border: chalk.dim, label: chalk.dim, gradient: [ROLE_HEX.system, "#555555"] },
|
|
29
|
-
error: { border: chalk.red, label: chalk.red.bold, gradient: [ROLE_HEX.error, "#cc0000"] },
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
|
-
export type MessageRole = keyof typeof ROLE_COLORS;
|
|
8
|
+
|
|
9
|
+
export type MessageRole = "user" | "assistant" | "tool" | "system" | "error";
|
|
10
|
+
|
|
11
|
+
const ROLE_LABEL: Record<MessageRole, string> = {
|
|
12
|
+
user: "you",
|
|
13
|
+
assistant: "snsagent",
|
|
14
|
+
tool: "tool",
|
|
15
|
+
system: "system",
|
|
16
|
+
error: "error",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const BULLET = chalk.cyan("●");
|
|
33
20
|
|
|
34
21
|
export interface ChatBlockOptions {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
role: MessageRole;
|
|
23
|
+
label?: string;
|
|
24
|
+
content: string;
|
|
25
|
+
width?: number;
|
|
26
|
+
meta?: string;
|
|
27
|
+
streaming?: boolean;
|
|
41
28
|
}
|
|
42
29
|
|
|
43
|
-
/**
|
|
44
|
-
* Word-wrap text respecting ANSI escape sequences.
|
|
45
|
-
* Returns array of lines fitting within `maxWidth` visible columns.
|
|
46
|
-
*/
|
|
47
30
|
function wrapAnsi(text: string, maxWidth: number): string[] {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return lines;
|
|
31
|
+
const lines: string[] = [];
|
|
32
|
+
for (const rawLine of text.split("\n")) {
|
|
33
|
+
if (visibleWidth(rawLine) <= maxWidth) {
|
|
34
|
+
lines.push(rawLine);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let current = "";
|
|
38
|
+
let currentWidth = 0;
|
|
39
|
+
const words = rawLine.split(/(\s+)/);
|
|
40
|
+
for (const word of words) {
|
|
41
|
+
const wWidth = visibleWidth(word);
|
|
42
|
+
if (currentWidth + wWidth > maxWidth && current.length > 0) {
|
|
43
|
+
lines.push(current);
|
|
44
|
+
current = word.trimStart();
|
|
45
|
+
currentWidth = visibleWidth(current);
|
|
46
|
+
} else {
|
|
47
|
+
current += word;
|
|
48
|
+
currentWidth += wWidth;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (current.length > 0) lines.push(current);
|
|
52
|
+
}
|
|
53
|
+
return lines;
|
|
72
54
|
}
|
|
73
55
|
|
|
74
|
-
/**
|
|
75
|
-
* Render a single chat message as a bordered block with gradient border.
|
|
76
|
-
*/
|
|
77
56
|
export function renderChatBlock(opts: ChatBlockOptions): string {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// ── Content lines with gradient side borders ──
|
|
102
|
-
const contentLines = wrapAnsi(opts.content, innerWidth);
|
|
103
|
-
const padStr = " ".repeat(pad);
|
|
104
|
-
for (const line of contentLines) {
|
|
105
|
-
const vis = visibleWidth(line);
|
|
106
|
-
const fill = " ".repeat(Math.max(0, innerWidth - vis));
|
|
107
|
-
lines.push(
|
|
108
|
-
gradBorder(BOX.vertical) + padStr + line + fill + padStr + gradBorder(BOX.vertical)
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ── Meta line (timestamp, tokens, etc.) ──
|
|
113
|
-
if (opts.meta) {
|
|
114
|
-
const metaText = chalk.dim(opts.meta);
|
|
115
|
-
const metaFill = " ".repeat(Math.max(0, innerWidth - visibleWidth(opts.meta)));
|
|
116
|
-
lines.push(
|
|
117
|
-
gradBorder(BOX.vertical) + padStr + metaText + metaFill + padStr + gradBorder(BOX.vertical)
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── Streaming indicator (animated) ──
|
|
122
|
-
if (opts.streaming) {
|
|
123
|
-
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
124
|
-
const frameIdx = Date.now() % (frames.length * 80);
|
|
125
|
-
const frame = frames[Math.floor(frameIdx / 80)];
|
|
126
|
-
const spinnerText = `${chalk.cyan(frame)} ${chalk.dim("thinking...")}`;
|
|
127
|
-
const spinFill = " ".repeat(Math.max(0, innerWidth - visibleWidth(spinnerText)));
|
|
128
|
-
lines.push(
|
|
129
|
-
gradBorder(BOX.vertical) + padStr + spinnerText + spinFill + padStr + gradBorder(BOX.vertical)
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ── Bottom bar ──
|
|
134
|
-
lines.push(
|
|
135
|
-
gradBorder(BOX.bottomLeft + BOX.horizontal.repeat(width - 2) + BOX.bottomRight)
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
return lines.join("\n");
|
|
57
|
+
const cols = process.stdout.columns ?? 80;
|
|
58
|
+
const width = opts.width ?? Math.min(cols - 4, 80);
|
|
59
|
+
const innerWidth = width - 6; // " ● label "
|
|
60
|
+
|
|
61
|
+
const label = opts.label ?? ROLE_LABEL[opts.role];
|
|
62
|
+
const headerRight = opts.meta ? chalk.dim(opts.meta) : "";
|
|
63
|
+
|
|
64
|
+
// Header: `● label meta`
|
|
65
|
+
const headerFillLen = Math.max(0, innerWidth - visibleWidth(label) - visibleWidth(headerRight));
|
|
66
|
+
const headerFill = " ".repeat(headerFillLen);
|
|
67
|
+
const header = ` ${BULLET} ${chalk.bold(label)}${headerFill}${headerRight ? " " + headerRight : ""}`;
|
|
68
|
+
|
|
69
|
+
// Content: indent continuation lines with `│` for visual grouping
|
|
70
|
+
const contentLines = wrapAnsi(opts.content, innerWidth);
|
|
71
|
+
const indented = contentLines.map((l) => ` ${chalk.dim("│")} ${l}`);
|
|
72
|
+
|
|
73
|
+
const parts: string[] = [header, ...indented];
|
|
74
|
+
|
|
75
|
+
if (opts.streaming) {
|
|
76
|
+
parts.push(` ${chalk.dim("│")} ${chalk.cyan("…")} ${chalk.dim("thinking")}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return parts.join("\n");
|
|
139
80
|
}
|
|
140
81
|
|
|
141
|
-
/**
|
|
142
|
-
* Render a compact inline message (no box, just colored prefix).
|
|
143
|
-
*/
|
|
82
|
+
/** Compact inline message — no box. */
|
|
144
83
|
export function renderInline(role: MessageRole, text: string): string {
|
|
145
|
-
|
|
146
|
-
const prefix = colors.label(`[${role}]`);
|
|
147
|
-
return `${prefix} ${text}`;
|
|
84
|
+
return `${BULLET} ${chalk.bold(ROLE_LABEL[role])} ${text}`;
|
|
148
85
|
}
|
|
149
86
|
|
|
150
|
-
/**
|
|
151
|
-
* Render a separator/divider line with gradient accent.
|
|
152
|
-
*/
|
|
87
|
+
/** Separator line — single dim rule. */
|
|
153
88
|
export function renderDivider(label?: string): string {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const labelText = ` ${label} `;
|
|
160
|
-
const fill = BOX.horizontal.repeat(Math.max(0, cols - 4 - visibleWidth(labelText)));
|
|
161
|
-
return grad(`${fill.slice(0, Math.floor(fill.length / 2))}`) +
|
|
162
|
-
chalk.dim(labelText) +
|
|
163
|
-
grad(`${fill.slice(Math.floor(fill.length / 2))}`);
|
|
89
|
+
const cols = process.stdout.columns ?? 80;
|
|
90
|
+
if (!label) return chalk.dim("─".repeat(cols - 2));
|
|
91
|
+
const labelText = ` ${label} `;
|
|
92
|
+
const fill = "─".repeat(Math.max(0, cols - 2 - visibleWidth(labelText)));
|
|
93
|
+
return chalk.dim(fill) + chalk.dim(labelText) + chalk.dim(fill);
|
|
164
94
|
}
|
|
165
95
|
|
|
166
|
-
/**
|
|
167
|
-
* Render a tool-call status block (compact, with gradient border).
|
|
168
|
-
*/
|
|
96
|
+
/** Tool-call status line. */
|
|
169
97
|
export function renderToolBlock(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
98
|
+
toolName: string,
|
|
99
|
+
status: "running" | "done" | "error",
|
|
100
|
+
detail?: string,
|
|
173
101
|
): string {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
: status === "running"
|
|
179
|
-
? ["#ffd700", "#e6a700"]
|
|
180
|
-
: ["#00d2ff", "#7b2ff7"];
|
|
181
|
-
const grad = gradient(gradColors as [string, string]);
|
|
182
|
-
|
|
183
|
-
const icon = status === "running" ? chalk.yellow("⚙") : status === "done" ? chalk.green("✓") : chalk.red("✗");
|
|
184
|
-
const label = ` ${icon} tool:${toolName} `;
|
|
185
|
-
const detailText = detail ? chalk.dim(` ${detail}`) : "";
|
|
186
|
-
const fill = BOX.horizontal.repeat(
|
|
187
|
-
Math.max(0, width - 2 - visibleWidth(` ⚙ tool:${toolName} `) - visibleWidth(detail ?? ""))
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
return grad(BOX.topLeft + BOX.horizontal) + label + detailText + grad(fill + BOX.topRight);
|
|
102
|
+
const icon = status === "running" ? chalk.cyan("●") : status === "done" ? chalk.green("●") : chalk.red("●");
|
|
103
|
+
const label = ` ${icon} tool:${toolName} `;
|
|
104
|
+
const detailText = detail ? chalk.dim(` ${detail}`) : "";
|
|
105
|
+
return label + detailText;
|
|
191
106
|
}
|
|
192
107
|
|
|
193
|
-
/**
|
|
194
|
-
* Build a gradient header line for the top of the session.
|
|
195
|
-
*/
|
|
108
|
+
/** Session header — single line. */
|
|
196
109
|
export function renderSessionHeader(model: string, version: string): string {
|
|
197
|
-
|
|
198
|
-
const left = gradient(["#00d2ff", "#7b2ff7"])(" SnsAgent");
|
|
199
|
-
const ver = chalk.dim(` v${version}`);
|
|
200
|
-
const modelStr = chalk.cyan(` ${model}`);
|
|
201
|
-
const sep = chalk.dim(" │ ");
|
|
202
|
-
const line = `${left}${ver}${sep}${modelStr}`;
|
|
203
|
-
const fill = chalk.dim("─".repeat(Math.max(0, cols - visibleWidth(line) - 2)));
|
|
204
|
-
return `\n${line}\n${fill}\n`;
|
|
110
|
+
return ` ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${chalk.dim(`v${version}`)} ${chalk.dim("·")} ${chalk.cyan(model)}\n`;
|
|
205
111
|
}
|
package/src/tui/code-cell.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Markdown, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import gradient from "gradient-string";
|
|
7
6
|
import { getMarkdownTheme, highlightCode, type Theme } from "../modes/theme/theme";
|
|
8
7
|
import {
|
|
9
8
|
formatDuration,
|
|
@@ -241,7 +240,6 @@ export function renderCollapsibleOutput(
|
|
|
241
240
|
theme: Theme,
|
|
242
241
|
maxLines: number = 8,
|
|
243
242
|
): string[] {
|
|
244
|
-
const grad = theme.fg;
|
|
245
243
|
const lines: string[] = [];
|
|
246
244
|
const inner = Math.max(20, width - 4);
|
|
247
245
|
|
|
@@ -249,20 +247,22 @@ export function renderCollapsibleOutput(
|
|
|
249
247
|
// Collapsed: just header + hint
|
|
250
248
|
const lineCount = content.split("\n").length;
|
|
251
249
|
const hint = chalk.dim(` (${lineCount} lines, click to expand)`);
|
|
252
|
-
lines.push(` ${
|
|
250
|
+
lines.push(` ${chalk.cyan("▸")} ${chalk.bold(label)}${hint}`);
|
|
253
251
|
return lines;
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
// Expanded: bordered block
|
|
257
|
-
const
|
|
258
|
-
|
|
255
|
+
const accent = chalk.cyan;
|
|
256
|
+
|
|
257
|
+
// Expanded: bordered block
|
|
258
|
+
const accentFn = (s: string) => chalk.cyan(s);
|
|
259
259
|
|
|
260
260
|
// Header
|
|
261
261
|
const headerText = ` ${label} `;
|
|
262
262
|
const headerFill = COLLAPSE_BORDER.horizontal.repeat(
|
|
263
263
|
Math.max(0, inner - headerText.length - 2),
|
|
264
264
|
);
|
|
265
|
-
lines.push(
|
|
265
|
+
lines.push(accent(COLLAPSE_BORDER.topLeft + COLLAPSE_BORDER.horizontal) + chalk.bold(headerText) + accent(headerFill + COLLAPSE_BORDER.topRight));
|
|
266
266
|
|
|
267
267
|
// Content
|
|
268
268
|
const rawLines = content.split("\n");
|
|
@@ -270,7 +270,7 @@ export function renderCollapsibleOutput(
|
|
|
270
270
|
for (const line of visibleLines) {
|
|
271
271
|
const visLen = visibleWidth(line);
|
|
272
272
|
const fill = " ".repeat(Math.max(0, inner - visLen));
|
|
273
|
-
lines.push(
|
|
273
|
+
lines.push(accent(COLLAPSE_BORDER.vertical) + " " + line + fill + " " + accent(COLLAPSE_BORDER.vertical));
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
// Hidden lines hint
|
|
@@ -278,11 +278,11 @@ export function renderCollapsibleOutput(
|
|
|
278
278
|
if (remaining > 0) {
|
|
279
279
|
const moreText = ` ... +${remaining} more lines`;
|
|
280
280
|
const moreFill = " ".repeat(Math.max(0, inner - moreText.length));
|
|
281
|
-
lines.push(
|
|
281
|
+
lines.push(accent(COLLAPSE_BORDER.vertical) + chalk.dim(moreText) + moreFill + accent(COLLAPSE_BORDER.vertical));
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Footer
|
|
285
|
-
lines.push(
|
|
285
|
+
lines.push(accent(COLLAPSE_BORDER.bottomLeft + COLLAPSE_BORDER.horizontal.repeat(inner) + COLLAPSE_BORDER.bottomRight));
|
|
286
286
|
|
|
287
287
|
return lines;
|
|
288
288
|
}
|