@sns-myagent/cli 0.3.6 → 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/src/tui/chat-blocks.ts +25 -62
- package/src/tui/command-palette.ts +14 -19
- package/src/tui/splash.ts +26 -26
- package/src/ui/error-display.ts +18 -25
- package/src/ui/memory-toast.ts +10 -23
- package/src/ui/status-bar.ts +2 -4
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/src/tui/chat-blocks.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
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
7
|
import { visibleWidth } from "@oh-my-pi/pi-tui";
|
|
8
8
|
|
|
9
|
-
const BOX = {
|
|
10
|
-
topLeft: "╭",
|
|
11
|
-
topRight: "╮",
|
|
12
|
-
bottomLeft: "╰",
|
|
13
|
-
bottomRight: "╯",
|
|
14
|
-
horizontal: "─",
|
|
15
|
-
vertical: "│",
|
|
16
|
-
} as const;
|
|
17
|
-
|
|
18
|
-
const ACCENT = chalk.cyan;
|
|
19
|
-
|
|
20
9
|
export type MessageRole = "user" | "assistant" | "tool" | "system" | "error";
|
|
21
10
|
|
|
22
11
|
const ROLE_LABEL: Record<MessageRole, string> = {
|
|
@@ -27,6 +16,8 @@ const ROLE_LABEL: Record<MessageRole, string> = {
|
|
|
27
16
|
error: "error",
|
|
28
17
|
};
|
|
29
18
|
|
|
19
|
+
const BULLET = chalk.cyan("●");
|
|
20
|
+
|
|
30
21
|
export interface ChatBlockOptions {
|
|
31
22
|
role: MessageRole;
|
|
32
23
|
label?: string;
|
|
@@ -64,65 +55,42 @@ function wrapAnsi(text: string, maxWidth: number): string[] {
|
|
|
64
55
|
|
|
65
56
|
export function renderChatBlock(opts: ChatBlockOptions): string {
|
|
66
57
|
const cols = process.stdout.columns ?? 80;
|
|
67
|
-
const width =
|
|
68
|
-
const
|
|
69
|
-
const innerWidth = width - 2 - pad * 2;
|
|
58
|
+
const width = opts.width ?? Math.min(cols - 4, 80);
|
|
59
|
+
const innerWidth = width - 6; // " ● label "
|
|
70
60
|
|
|
71
|
-
const lines: string[] = [];
|
|
72
|
-
|
|
73
|
-
// Top bar: accent line + label
|
|
74
61
|
const label = opts.label ?? ROLE_LABEL[opts.role];
|
|
75
|
-
const
|
|
76
|
-
const topFill = BOX.horizontal.repeat(Math.max(0, innerWidth - visibleWidth(labelText) + pad * 2));
|
|
77
|
-
lines.push(ACCENT(BOX.topLeft + BOX.horizontal.repeat(2)) + chalk.bold(labelText) + ACCENT(topFill + BOX.topRight));
|
|
62
|
+
const headerRight = opts.meta ? chalk.dim(opts.meta) : "";
|
|
78
63
|
|
|
79
|
-
//
|
|
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
|
|
80
70
|
const contentLines = wrapAnsi(opts.content, innerWidth);
|
|
81
|
-
const
|
|
82
|
-
for (const line of contentLines) {
|
|
83
|
-
const vis = visibleWidth(line);
|
|
84
|
-
const fill = " ".repeat(Math.max(0, innerWidth - vis));
|
|
85
|
-
lines.push(ACCENT(BOX.vertical) + padStr + line + fill + padStr + ACCENT(BOX.vertical));
|
|
86
|
-
}
|
|
71
|
+
const indented = contentLines.map((l) => ` ${chalk.dim("│")} ${l}`);
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
if (opts.meta) {
|
|
90
|
-
const metaText = chalk.dim(opts.meta);
|
|
91
|
-
const metaFill = " ".repeat(Math.max(0, innerWidth - visibleWidth(opts.meta)));
|
|
92
|
-
lines.push(ACCENT(BOX.vertical) + padStr + metaText + metaFill + padStr + ACCENT(BOX.vertical));
|
|
93
|
-
}
|
|
73
|
+
const parts: string[] = [header, ...indented];
|
|
94
74
|
|
|
95
|
-
// Streaming
|
|
96
75
|
if (opts.streaming) {
|
|
97
|
-
|
|
98
|
-
const frameIdx = Date.now() % (frames.length * 80);
|
|
99
|
-
const frame = frames[Math.floor(frameIdx / 80)];
|
|
100
|
-
const spinnerText = `${ACCENT(frame)} ${chalk.dim("thinking...")}`;
|
|
101
|
-
const spinFill = " ".repeat(Math.max(0, innerWidth - visibleWidth(spinnerText)));
|
|
102
|
-
lines.push(ACCENT(BOX.vertical) + padStr + spinnerText + spinFill + padStr + ACCENT(BOX.vertical));
|
|
76
|
+
parts.push(` ${chalk.dim("│")} ${chalk.cyan("…")} ${chalk.dim("thinking")}`);
|
|
103
77
|
}
|
|
104
78
|
|
|
105
|
-
|
|
106
|
-
lines.push(ACCENT(BOX.bottomLeft + BOX.horizontal.repeat(width - 2) + BOX.bottomRight));
|
|
107
|
-
|
|
108
|
-
return lines.join("\n");
|
|
79
|
+
return parts.join("\n");
|
|
109
80
|
}
|
|
110
81
|
|
|
111
82
|
/** Compact inline message — no box. */
|
|
112
83
|
export function renderInline(role: MessageRole, text: string): string {
|
|
113
|
-
|
|
114
|
-
return `${label} ${text}`;
|
|
84
|
+
return `${BULLET} ${chalk.bold(ROLE_LABEL[role])} ${text}`;
|
|
115
85
|
}
|
|
116
86
|
|
|
117
|
-
/** Separator line
|
|
87
|
+
/** Separator line — single dim rule. */
|
|
118
88
|
export function renderDivider(label?: string): string {
|
|
119
89
|
const cols = process.stdout.columns ?? 80;
|
|
120
|
-
if (!label)
|
|
121
|
-
return chalk.dim("─".repeat(cols - 2));
|
|
122
|
-
}
|
|
90
|
+
if (!label) return chalk.dim("─".repeat(cols - 2));
|
|
123
91
|
const labelText = ` ${label} `;
|
|
124
92
|
const fill = "─".repeat(Math.max(0, cols - 2 - visibleWidth(labelText)));
|
|
125
|
-
return chalk.dim(fill
|
|
93
|
+
return chalk.dim(fill) + chalk.dim(labelText) + chalk.dim(fill);
|
|
126
94
|
}
|
|
127
95
|
|
|
128
96
|
/** Tool-call status line. */
|
|
@@ -131,18 +99,13 @@ export function renderToolBlock(
|
|
|
131
99
|
status: "running" | "done" | "error",
|
|
132
100
|
detail?: string,
|
|
133
101
|
): string {
|
|
134
|
-
const icon = status === "running" ? chalk.cyan("
|
|
102
|
+
const icon = status === "running" ? chalk.cyan("●") : status === "done" ? chalk.green("●") : chalk.red("●");
|
|
135
103
|
const label = ` ${icon} tool:${toolName} `;
|
|
136
104
|
const detailText = detail ? chalk.dim(` ${detail}`) : "";
|
|
137
105
|
return label + detailText;
|
|
138
106
|
}
|
|
139
107
|
|
|
140
|
-
/** Session header. */
|
|
108
|
+
/** Session header — single line. */
|
|
141
109
|
export function renderSessionHeader(model: string, version: string): string {
|
|
142
|
-
|
|
143
|
-
const left = ACCENT.bold(" snsagent");
|
|
144
|
-
const ver = chalk.dim(` v${version}`);
|
|
145
|
-
const modelStr = chalk.dim(" · ") + chalk.cyan(model);
|
|
146
|
-
const sep = chalk.dim("─".repeat(Math.max(0, cols - visibleWidth(left + ver + modelStr) - 2)));
|
|
147
|
-
return `\n${left}${ver}${modelStr}\n${sep}\n`;
|
|
110
|
+
return ` ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${chalk.dim(`v${version}`)} ${chalk.dim("·")} ${chalk.cyan(model)}\n`;
|
|
148
111
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Command palette —
|
|
3
|
-
*
|
|
2
|
+
* Command palette — flat list, no box.
|
|
3
|
+
* Cyan highlight on selected row, dim otherwise.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import chalk from "chalk";
|
|
@@ -36,7 +36,7 @@ export const CHAT_COMMANDS: PaletteCommand[] = [
|
|
|
36
36
|
];
|
|
37
37
|
|
|
38
38
|
export function renderCommandPalette(opts: CommandPaletteOptions): string {
|
|
39
|
-
const { commands, query = "", highlighted = 0
|
|
39
|
+
const { commands, query = "", highlighted = 0 } = opts;
|
|
40
40
|
const filtered = commands.filter(
|
|
41
41
|
(c) => c.enabled !== false &&
|
|
42
42
|
(c.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
@@ -44,33 +44,28 @@ export function renderCommandPalette(opts: CommandPaletteOptions): string {
|
|
|
44
44
|
c.category.toLowerCase().includes(query.toLowerCase()))
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
-
const cols = process.stdout.columns ?? 80;
|
|
48
|
-
const width2 = Math.min(opts.width ?? cols, cols) - 4;
|
|
49
|
-
|
|
50
47
|
const lines: string[] = [];
|
|
51
48
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
lines.push(chalk.dim("─".repeat(60)));
|
|
55
|
-
lines.push(` ${chalk.cyan("?")} ${query}${chalk.dim(" (↑↓ Enter Esc)")}`);
|
|
49
|
+
// Search line
|
|
50
|
+
lines.push(` ${chalk.cyan("?")} ${query}${chalk.dim(" (↑↓ navigate · Enter select · Esc cancel)")}`);
|
|
56
51
|
lines.push("");
|
|
57
52
|
|
|
58
53
|
if (filtered.length === 0) {
|
|
59
|
-
lines.push(chalk.dim("
|
|
54
|
+
lines.push(` ${chalk.dim("no commands match")}`);
|
|
60
55
|
} else {
|
|
61
56
|
filtered.forEach((cmd, idx) => {
|
|
62
57
|
const isActive = idx === highlighted;
|
|
63
|
-
const prefix = isActive ? chalk.cyan("
|
|
58
|
+
const prefix = isActive ? chalk.cyan("●") : " ";
|
|
64
59
|
const name = isActive ? chalk.cyan.bold(cmd.name) : chalk.cyan(cmd.name);
|
|
65
60
|
const desc = chalk.dim(cmd.description);
|
|
66
|
-
const cat = chalk.dim(`
|
|
67
|
-
const shortcut = cmd.shortcut ? chalk.dim(`
|
|
68
|
-
lines.push(
|
|
61
|
+
const cat = chalk.dim(` [${cmd.category}]`);
|
|
62
|
+
const shortcut = cmd.shortcut ? chalk.dim(` ${cmd.shortcut}`) : "";
|
|
63
|
+
lines.push(` ${prefix} ${name}${cat}${shortcut}`);
|
|
64
|
+
if (isActive) {
|
|
65
|
+
lines.push(` ${desc}`);
|
|
66
|
+
}
|
|
69
67
|
});
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
lines.push("");
|
|
73
|
-
lines.push(chalk.dim("─".repeat(60)));
|
|
74
|
-
|
|
75
70
|
return lines.join("\n");
|
|
76
|
-
}
|
|
71
|
+
}
|
package/src/tui/splash.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SNS-MyAgent splash —
|
|
3
|
-
* Single
|
|
2
|
+
* SNS-MyAgent splash — flat list, no boxes.
|
|
3
|
+
* Single line brand + `●` prefixed info rows. No rounded borders, no
|
|
4
|
+
* gradient, no separator boxes. Reads like a status line.
|
|
4
5
|
*/
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
import { readFileSync } from "node:fs";
|
|
7
8
|
import { dirname, resolve } from "node:path";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
|
|
10
|
-
const SUBTITLE = "snsagent — coding agent CLI";
|
|
11
|
-
|
|
12
11
|
function readVersion(): string {
|
|
13
12
|
try {
|
|
14
13
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -49,38 +48,39 @@ export interface SplashInfo {
|
|
|
49
48
|
nodeVersion?: string;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
/**
|
|
52
|
+
* One-line prefix used throughout the TUI.
|
|
53
|
+
* `●` = present, default text. No rounded boxes.
|
|
54
|
+
*/
|
|
55
|
+
const BULLET = chalk.cyan("●");
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
57
|
+
export function renderSplash(info: SplashInfo = {}): string {
|
|
58
|
+
const ver = readVersion();
|
|
59
|
+
const lines: string[] = [];
|
|
59
60
|
|
|
60
|
-
//
|
|
61
|
-
|
|
61
|
+
// Brand line: MY · snsagent · v0.3.6
|
|
62
|
+
lines.push(` ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${chalk.dim(`v${ver}`)}`);
|
|
63
|
+
lines.push(` ${chalk.dim("coding agent CLI")}`);
|
|
64
|
+
lines.push("");
|
|
62
65
|
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (info.
|
|
68
|
-
|
|
69
|
-
if (info.platform) rows.push(kv("Platform", info.platform));
|
|
70
|
-
rows.push(kv("Version", readVersion()));
|
|
71
|
-
rows.push(sep);
|
|
66
|
+
// Status lines — flat, one per line, ● prefix
|
|
67
|
+
const row = (label: string, value: string) => ` ${BULLET} ${chalk.dim(label.padEnd(13))}${value}`;
|
|
68
|
+
if (info.model) lines.push(row("model", `${info.provider ?? "unknown"}/${info.model}`));
|
|
69
|
+
if (info.cwd) lines.push(row("dir", info.cwd));
|
|
70
|
+
if (info.platform) lines.push(row("platform", info.platform));
|
|
71
|
+
lines.push(row("version", ver));
|
|
72
72
|
|
|
73
73
|
// Hints
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push(` ${chalk.dim("type to chat · /exit to quit")}`);
|
|
76
76
|
|
|
77
|
-
return
|
|
77
|
+
return lines.join("\n") + "\n";
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
export function renderInlineHeader(info: SplashInfo = {}): string {
|
|
81
|
+
const ver = readVersion();
|
|
81
82
|
const model = info.model
|
|
82
83
|
? chalk.cyan(`${info.provider ?? "?"}/${info.model}`)
|
|
83
84
|
: chalk.dim("no model");
|
|
84
|
-
|
|
85
|
-
return `\n ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${ver} ${chalk.dim("│")} ${model}\n`;
|
|
85
|
+
return ` ${chalk.cyan.bold("MY")} ${chalk.bold("snsagent")} ${chalk.dim(`v${ver}`)} ${chalk.dim("·")} ${model}\n`;
|
|
86
86
|
}
|
package/src/ui/error-display.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Error display —
|
|
3
|
-
*
|
|
4
|
-
* stays dim + default text.
|
|
2
|
+
* Error display — flat `●` prefix, severity icon.
|
|
3
|
+
* No box, no gradient, no nested borders. Just one line per item.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
import chalk from "chalk";
|
|
@@ -21,67 +20,61 @@ export interface ErrorDisplayOptions {
|
|
|
21
20
|
function getSeverityMeta(severity: ErrorSeverity) {
|
|
22
21
|
switch (severity) {
|
|
23
22
|
case "error":
|
|
24
|
-
return { icon: "
|
|
23
|
+
return { icon: chalk.red("●"), label: "ERROR" };
|
|
25
24
|
case "warning":
|
|
26
|
-
return { icon: "
|
|
25
|
+
return { icon: chalk.yellow("●"), label: "WARNING" };
|
|
27
26
|
case "info":
|
|
28
|
-
return { icon: "
|
|
27
|
+
return { icon: chalk.cyan("●"), label: "INFO" };
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export function renderErrorDisplay(opts: ErrorDisplayOptions): string {
|
|
33
32
|
const severity = opts.severity ?? "error";
|
|
34
33
|
const meta = getSeverityMeta(severity);
|
|
35
|
-
const
|
|
36
|
-
const width = Math.min(cols - 2, 72);
|
|
37
|
-
|
|
38
|
-
const border = "─".repeat(width - 2);
|
|
34
|
+
const codeStr = opts.code ? chalk.dim(` [${opts.code}]`) : "";
|
|
39
35
|
|
|
40
36
|
const lines: string[] = [];
|
|
41
|
-
lines.push(chalk.dim(border));
|
|
42
37
|
|
|
43
38
|
// Header
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
lines.push(chalk.bold(opts.title));
|
|
48
|
-
lines.push("");
|
|
39
|
+
lines.push(` ${meta.icon} ${chalk.bold(meta.label)}${codeStr}`);
|
|
40
|
+
|
|
41
|
+
// Title
|
|
42
|
+
lines.push(` ${chalk.bold(opts.title)}`);
|
|
49
43
|
|
|
50
44
|
// Message
|
|
51
45
|
for (const line of opts.message.split("\n")) {
|
|
52
|
-
lines.push(line);
|
|
46
|
+
lines.push(` ${line}`);
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
// Context
|
|
56
50
|
if (opts.context && opts.context.length > 0) {
|
|
57
51
|
lines.push("");
|
|
58
|
-
lines.push(chalk.dim("
|
|
52
|
+
lines.push(` ${chalk.dim("context:")}`);
|
|
59
53
|
for (const ctx of opts.context.slice(0, 5)) {
|
|
60
|
-
lines.push(chalk.dim(
|
|
54
|
+
lines.push(` ${chalk.dim(ctx)}`);
|
|
61
55
|
}
|
|
62
56
|
if (opts.context.length > 5) {
|
|
63
|
-
lines.push(chalk.dim(
|
|
57
|
+
lines.push(` ${chalk.dim(`... +${opts.context.length - 5} more`)}`);
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
// Suggestion
|
|
68
62
|
if (opts.suggestion) {
|
|
69
63
|
lines.push("");
|
|
70
|
-
lines.push(
|
|
71
|
-
lines.push(`
|
|
64
|
+
lines.push(` ${chalk.cyan("→")} ${chalk.bold("suggestion:")}`);
|
|
65
|
+
lines.push(` ${opts.suggestion}`);
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
// Stack preview
|
|
75
69
|
if (opts.stack) {
|
|
76
70
|
lines.push("");
|
|
77
71
|
const stackLines = opts.stack.split("\n").slice(0, 3);
|
|
78
|
-
lines.push(chalk.dim("
|
|
72
|
+
lines.push(` ${chalk.dim("stack (first 3):")}`);
|
|
79
73
|
for (const sl of stackLines) {
|
|
80
|
-
lines.push(chalk.dim(
|
|
74
|
+
lines.push(` ${chalk.dim(sl.trim())}`);
|
|
81
75
|
}
|
|
82
76
|
}
|
|
83
77
|
|
|
84
|
-
lines.push(chalk.dim(border));
|
|
85
78
|
return lines.join("\n");
|
|
86
79
|
}
|
|
87
80
|
|
package/src/ui/memory-toast.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory toast —
|
|
3
|
-
* Single accent (cyan), no gradient, no per-type color explosion.
|
|
2
|
+
* Memory toast — flat `●` prefix, single accent.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from "chalk";
|
|
@@ -22,19 +21,10 @@ const TOAST_LABEL: Record<ToastType, string> = {
|
|
|
22
21
|
info: "info",
|
|
23
22
|
};
|
|
24
23
|
|
|
25
|
-
const TOAST_ICON: Record<ToastType, string> = {
|
|
26
|
-
recall: "·",
|
|
27
|
-
save: "+",
|
|
28
|
-
forget: "-",
|
|
29
|
-
info: "i",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
24
|
export function renderMemoryToast(opts: MemoryToastOptions): string {
|
|
33
|
-
const
|
|
34
|
-
const label = chalk.cyan.bold(` ${TOAST_LABEL[opts.type]} `);
|
|
25
|
+
const label = chalk.cyan(`● ${TOAST_LABEL[opts.type]}`);
|
|
35
26
|
const msg = opts.message;
|
|
36
|
-
|
|
37
|
-
const parts = [chalk.dim(icon), label, msg];
|
|
27
|
+
const parts = [label, msg];
|
|
38
28
|
|
|
39
29
|
if (opts.memoryId) {
|
|
40
30
|
parts.push(chalk.dim(`#${opts.memoryId.slice(0, 8)}`));
|
|
@@ -42,33 +32,30 @@ export function renderMemoryToast(opts: MemoryToastOptions): string {
|
|
|
42
32
|
|
|
43
33
|
if (opts.relevance !== undefined) {
|
|
44
34
|
const score = Math.round(opts.relevance * 100);
|
|
45
|
-
|
|
46
|
-
parts.push(scoreColor(`(${score}%)`));
|
|
35
|
+
parts.push(chalk.cyan(`(${score}%)`));
|
|
47
36
|
}
|
|
48
37
|
|
|
49
|
-
return ` ${parts.join("
|
|
38
|
+
return ` ${parts.join(" ")}`;
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
export function renderMemoryRecall(query: string, snippet: string, relevance?: number): string {
|
|
53
42
|
const queryStr = chalk.cyan(`"${query}"`);
|
|
54
43
|
const scoreStr = relevance !== undefined
|
|
55
|
-
? chalk.dim(`
|
|
44
|
+
? chalk.dim(` ${Math.round(relevance * 100)}% match`)
|
|
56
45
|
: "";
|
|
57
46
|
|
|
58
47
|
const lines = [
|
|
59
|
-
|
|
60
|
-
`
|
|
48
|
+
` ${chalk.cyan("● memory")} ${queryStr}${scoreStr}`,
|
|
49
|
+
` ${chalk.dim(snippet.slice(0, 120))}${snippet.length > 120 ? chalk.dim("...") : ""}`,
|
|
61
50
|
];
|
|
62
51
|
|
|
63
52
|
return lines.join("\n");
|
|
64
53
|
}
|
|
65
54
|
|
|
66
55
|
export function renderMemorySave(content: string, scope?: string): string {
|
|
67
|
-
const
|
|
68
|
-
const scopeStr = scope ? chalk.dim(` [${scope}]`) : "";
|
|
56
|
+
const scopeStr = scope ? chalk.dim(` [${scope}]`) : "";
|
|
69
57
|
const preview = content.length > 80 ? content.slice(0, 80) + "..." : content;
|
|
70
|
-
|
|
71
|
-
return `${chalk.dim("+")} ${label}${scopeStr} ${chalk.dim(preview)}`;
|
|
58
|
+
return ` ${chalk.cyan("● saved")}${scopeStr} ${chalk.dim(preview)}`;
|
|
72
59
|
}
|
|
73
60
|
|
|
74
61
|
export function clearToastLine(): void {
|
package/src/ui/status-bar.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Status bar —
|
|
3
|
-
* Minimal: dim key + accent value. No gradient, no per-segment color.
|
|
2
|
+
* Status bar — flat single line, dim key + cyan value.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from "chalk";
|
|
@@ -27,12 +26,11 @@ export function renderStatusBar(state: StatusBarState): void {
|
|
|
27
26
|
? `${(state.tokensUsed / 1000).toFixed(1)}k`
|
|
28
27
|
: String(state.tokensUsed);
|
|
29
28
|
|
|
30
|
-
// Single dim/accent pair, no per-segment color
|
|
31
29
|
const k = (s: string) => chalk.dim(s);
|
|
32
30
|
const v = (s: string) => chalk.cyan(s);
|
|
33
31
|
const sep = chalk.dim(" · ");
|
|
34
32
|
|
|
35
|
-
const bar = ` ${k("model")} ${v(state.model)}${sep}${k("tokens")} ${v(tokens)}${sep}${k("time")} ${v(elapsed)}${sep}${k("mem")} ${v(String(state.memoryHits))}
|
|
33
|
+
const bar = ` ${k("model")} ${v(state.model)}${sep}${k("tokens")} ${v(tokens)}${sep}${k("time")} ${v(elapsed)}${sep}${k("mem")} ${v(String(state.memoryHits))}`;
|
|
36
34
|
|
|
37
35
|
process.stdout.write(`\x1b[2K\r${bar}`);
|
|
38
36
|
}
|