sanook-cli 0.5.7 → 0.5.9
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/CHANGELOG.md +42 -0
- package/README.md +392 -42
- package/README.th.md +15 -8
- package/dist/auto-maintain.js +113 -0
- package/dist/bin.js +63 -15
- package/dist/brain-final.js +8 -4
- package/dist/brain-new.js +9 -5
- package/dist/brain-repair.js +7 -4
- package/dist/brand.js +17 -0
- package/dist/commands.js +17 -6
- package/dist/config.js +11 -0
- package/dist/dashboard/api-helpers.js +112 -3
- package/dist/dashboard/server.js +85 -1
- package/dist/dashboard/static/app.js +381 -0
- package/dist/dashboard/static/styles.css +36 -0
- package/dist/dashboard/terminal.js +214 -0
- package/dist/diff.js +22 -8
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/th.js +1 -0
- package/dist/install-info.js +91 -0
- package/dist/loop.js +10 -1
- package/dist/memory.js +236 -16
- package/dist/model-picker.js +4 -1
- package/dist/persona.js +300 -0
- package/dist/project-scaffold.js +4 -2
- package/dist/providers/codex.js +75 -2
- package/dist/providers/models.js +17 -2
- package/dist/providers/registry.js +6 -13
- package/dist/self-improve-synth.js +86 -0
- package/dist/self-improve.js +203 -0
- package/dist/session-brain.js +10 -1
- package/dist/slash-completion.js +1 -0
- package/dist/ui/app.js +118 -27
- package/dist/ui/input-view.js +104 -0
- package/dist/ui/persona-wizard.js +89 -0
- package/dist/ui/render.js +78 -5
- package/dist/ui/setup.js +3 -4
- package/dist/ui/tool-activity.js +118 -0
- package/dist/ui/tool-trail.js +15 -3
- package/package.json +9 -1
package/dist/ui/setup.js
CHANGED
|
@@ -35,11 +35,10 @@ export function SetupWizard({ onComplete }) {
|
|
|
35
35
|
const providerOptions = setupProviderOptions();
|
|
36
36
|
const providerMenuLines = setupProviderMenuLines();
|
|
37
37
|
const advanceIfCodexReady = (status) => {
|
|
38
|
-
if (!status.loggedIn)
|
|
38
|
+
if (!status.loggedIn || !status.installed)
|
|
39
39
|
return;
|
|
40
40
|
setModel(`codex:${PROVIDERS.codex.models.default}`);
|
|
41
|
-
|
|
42
|
-
setStep('agent');
|
|
41
|
+
setStep('model');
|
|
43
42
|
};
|
|
44
43
|
// codex-auth: เช็ก codex CLI ติดตั้ง + login ChatGPT (re-run เมื่อกด "เช็กใหม่")
|
|
45
44
|
useEffect(() => {
|
|
@@ -220,7 +219,7 @@ export function SetupWizard({ onComplete }) {
|
|
|
220
219
|
setKeyError('');
|
|
221
220
|
}, onSubmit: submitKey }), keyError ? _jsxs(Text, { color: "red", children: [" \u2717 ", keyError] }) : null] })), step === 'model' &&
|
|
222
221
|
cfg &&
|
|
223
|
-
(loadingModels ? (_jsxs(Text, { color: "gray", children: [' ', m.modelLoading, " ", cfg.label, "\u2026"] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [m.stepModel, " \u2014 ", m.modelPick, remote.length ? _jsxs(Text, { color: "gray", children: [" (", modelOptions.length, ")"] }) : null, ":"] }), _jsx(Select, { options: modelOptions, onChange: (v) => {
|
|
222
|
+
(loadingModels ? (_jsxs(Text, { color: "gray", children: [' ', m.modelLoading, " ", cfg.label, "\u2026"] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [m.stepModel, " \u2014 ", m.modelPick, remote.length ? _jsxs(Text, { color: "gray", children: [" (", modelOptions.length, ")"] }) : null, ":"] }), provider === 'codex' ? _jsxs(Text, { color: "gray", children: [" ", m.codexModelHint] }) : null, _jsx(Select, { options: modelOptions, onChange: (v) => {
|
|
224
223
|
setModel(`${provider}:${v}`);
|
|
225
224
|
setStep('agent');
|
|
226
225
|
} })] }))), step === 'agent' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: m.stepAgent }), _jsx(Text, { color: "gray", children: m.agentTitle }), _jsx(Select, { options: [
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/ui/tool-activity.ts — turns a tool call's INPUT into a human-friendly activity:
|
|
3
|
+
// a clear one-line title ("แก้ไฟล์ src/app.tsx", "$ npm test") plus, for code edits, a
|
|
4
|
+
// colored diff (green + additions, red - deletions). Computed at tool-call time so the
|
|
5
|
+
// REPL shows in detail what the agent is doing in real time — not after the fact.
|
|
6
|
+
// ============================================================================
|
|
7
|
+
import { editDiffSegments } from '../diff.js';
|
|
8
|
+
// PER-SIDE diff cap (removed and added counted separately) — a two-sided edit can still yield up to
|
|
9
|
+
// ~2×this rows, so the overall per-row height bound is enforced by ActivityRow (MAX_ROW_DIFF_LINES).
|
|
10
|
+
// diffLines/additionLines append a correct "…(+N บรรทัด)" sentinel per side when exceeded.
|
|
11
|
+
const MAX_DIFF_LINES = 10;
|
|
12
|
+
function str(v) {
|
|
13
|
+
return typeof v === 'string' ? v : '';
|
|
14
|
+
}
|
|
15
|
+
function basenameish(p) {
|
|
16
|
+
return p.length > 52 ? `…${p.slice(-51)}` : p;
|
|
17
|
+
}
|
|
18
|
+
/** structured edit diff (old → new) with common prefix/suffix trimmed — green +, red -.
|
|
19
|
+
* Built on the shared core in src/diff.ts so the algorithm doesn't drift from renderEditDiff. */
|
|
20
|
+
export function diffLines(oldStr, newStr, max = MAX_DIFF_LINES) {
|
|
21
|
+
const seg = editDiffSegments(oldStr, newStr, max);
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const l of seg.removed)
|
|
24
|
+
out.push({ sign: '-', text: l });
|
|
25
|
+
if (seg.moreRemoved)
|
|
26
|
+
out.push({ sign: ' ', text: `…(-${seg.moreRemoved} บรรทัด)` });
|
|
27
|
+
for (const l of seg.added)
|
|
28
|
+
out.push({ sign: '+', text: l });
|
|
29
|
+
if (seg.moreAdded)
|
|
30
|
+
out.push({ sign: ' ', text: `…(+${seg.moreAdded} บรรทัด)` });
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/** whole-content as additions (new file write) — all green. Drops the trailing empty line of a
|
|
34
|
+
* file ending in \n so the green-line count matches the title's countLines() (no spurious blank). */
|
|
35
|
+
function additionLines(content, max = MAX_DIFF_LINES) {
|
|
36
|
+
if (content === '')
|
|
37
|
+
return []; // empty write → no diff body (title already shows "+0 บรรทัด")
|
|
38
|
+
const all = content.split('\n');
|
|
39
|
+
if (content.endsWith('\n'))
|
|
40
|
+
all.pop();
|
|
41
|
+
const out = all.slice(0, max).map((text) => ({ sign: '+', text }));
|
|
42
|
+
if (all.length > max)
|
|
43
|
+
out.push({ sign: ' ', text: `…(+${all.length - max} บรรทัด)` });
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function countLines(s) {
|
|
47
|
+
if (s === '')
|
|
48
|
+
return 0;
|
|
49
|
+
const lines = s.split('\n');
|
|
50
|
+
if (s.endsWith('\n'))
|
|
51
|
+
lines.pop();
|
|
52
|
+
return lines.length;
|
|
53
|
+
}
|
|
54
|
+
/** map a tool name + its input into a friendly title (+ optional colored diff) */
|
|
55
|
+
export function describeToolCall(name, input) {
|
|
56
|
+
const i = (input ?? {});
|
|
57
|
+
switch (name) {
|
|
58
|
+
case 'edit_file': {
|
|
59
|
+
const path = basenameish(str(i.path));
|
|
60
|
+
const all = i.replace_all ? ' (ทุกที่)' : '';
|
|
61
|
+
return { title: `✎ แก้ไฟล์ ${path}${all}`, diff: diffLines(str(i.old_string), str(i.new_string)) };
|
|
62
|
+
}
|
|
63
|
+
case 'write_file': {
|
|
64
|
+
const path = basenameish(str(i.path));
|
|
65
|
+
const content = str(i.content);
|
|
66
|
+
return { title: `✚ เขียนไฟล์ ${path} (+${countLines(content)} บรรทัด)`, diff: additionLines(content) };
|
|
67
|
+
}
|
|
68
|
+
case 'run_bash':
|
|
69
|
+
return { title: `$ ${str(i.cmd)}` };
|
|
70
|
+
case 'run_python':
|
|
71
|
+
return { title: i.path ? `▶ python ${str(i.path)}` : `▶ รัน python (${str(i.code).length} ตัวอักษร)` };
|
|
72
|
+
case 'run_rust':
|
|
73
|
+
return { title: i.path ? `▶ rust ${str(i.path)}` : `▶ รัน rust (${str(i.code).length} ตัวอักษร)` };
|
|
74
|
+
case 'read_file':
|
|
75
|
+
return { title: `📖 อ่านไฟล์ ${basenameish(str(i.path))}` };
|
|
76
|
+
case 'list_dir':
|
|
77
|
+
return { title: `📁 ดูโฟลเดอร์ ${basenameish(str(i.path) || '.')}` };
|
|
78
|
+
case 'glob':
|
|
79
|
+
return { title: `🔎 ค้นไฟล์ ${str(i.pattern)}` };
|
|
80
|
+
case 'grep':
|
|
81
|
+
return { title: `🔎 ค้นโค้ด "${str(i.pattern)}"` };
|
|
82
|
+
case 'git_status':
|
|
83
|
+
return { title: 'git status' };
|
|
84
|
+
case 'git_diff':
|
|
85
|
+
return { title: 'git diff' };
|
|
86
|
+
case 'git_log':
|
|
87
|
+
return { title: 'git log' };
|
|
88
|
+
case 'git_commit':
|
|
89
|
+
return { title: `⎇ git commit -m "${str(i.message).slice(0, 60)}"` };
|
|
90
|
+
case 'remember':
|
|
91
|
+
return { title: `🧠 จำ: ${str(i.fact).slice(0, 60)}` };
|
|
92
|
+
case 'recall':
|
|
93
|
+
return { title: `🧠 ค้นความจำ "${str(i.query)}"` };
|
|
94
|
+
case 'create_skill':
|
|
95
|
+
return { title: `✨ สร้าง skill ${str(i.name)}` };
|
|
96
|
+
case 'find_skills':
|
|
97
|
+
return { title: `✨ หา skill "${str(i.query)}"` };
|
|
98
|
+
case 'skill':
|
|
99
|
+
return { title: `✨ เปิด skill ${str(i.name)}` };
|
|
100
|
+
case 'web_fetch':
|
|
101
|
+
return { title: `🌐 โหลด ${str(i.url).slice(0, 60)}` };
|
|
102
|
+
case 'task':
|
|
103
|
+
return { title: `🤖 มอบงานให้ sub-agent: ${str(i.prompt || i.task).slice(0, 50)}` };
|
|
104
|
+
case 'schedule_task':
|
|
105
|
+
return { title: `⏰ ตั้งเวลา: ${str(i.when)} → ${str(i.task).slice(0, 40)}` };
|
|
106
|
+
default: {
|
|
107
|
+
const detail = pickDetail(i);
|
|
108
|
+
return { title: detail ? `${name} ${detail}` : name };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function pickDetail(i) {
|
|
113
|
+
for (const key of ['path', 'query', 'pattern', 'name', 'url', 'id']) {
|
|
114
|
+
if (typeof i[key] === 'string' && i[key])
|
|
115
|
+
return String(i[key]).slice(0, 60);
|
|
116
|
+
}
|
|
117
|
+
return '';
|
|
118
|
+
}
|
package/dist/ui/tool-trail.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
+
import { describeToolCall } from './tool-activity.js';
|
|
2
3
|
export const TOOL_TRAIL_LIMIT = 6;
|
|
3
4
|
function clip(text, width) {
|
|
4
5
|
if (width <= 0)
|
|
@@ -40,7 +41,10 @@ export function updateToolTrailOnEvent(items, event, nextId) {
|
|
|
40
41
|
if (event.type === 'tool-call') {
|
|
41
42
|
const name = event.tool?.trim() || 'tool';
|
|
42
43
|
return {
|
|
43
|
-
items: trimItems([
|
|
44
|
+
items: trimItems([
|
|
45
|
+
...items,
|
|
46
|
+
{ detail: compactToolDetail(event.detail), id: nextId, name, status: 'running', activity: describeToolCall(name, event.detail) },
|
|
47
|
+
]),
|
|
44
48
|
nextId: nextId + 1,
|
|
45
49
|
};
|
|
46
50
|
}
|
|
@@ -75,15 +79,23 @@ function statusSummary(items) {
|
|
|
75
79
|
const error = items.filter((item) => item.status === 'error').length;
|
|
76
80
|
return [`${done} done`, running ? `${running} running` : '', error ? `${error} error` : ''].filter(Boolean).join(' / ');
|
|
77
81
|
}
|
|
82
|
+
/** the 2 header lines (title + view/status meta) shared by string + rich rendering */
|
|
83
|
+
export function toolTrailHeader(items, mode) {
|
|
84
|
+
return [`Sanook tool trail (${items.length})`, `view: ${mode} | ${statusSummary(items)} | Ctrl+T / /trail`];
|
|
85
|
+
}
|
|
86
|
+
/** width budget for a rich activity row (mirrors toolTrailLines clipping) */
|
|
87
|
+
export function toolTrailWidth(columns) {
|
|
88
|
+
return Math.max(24, Math.min(Math.max(30, columns - 4), 96));
|
|
89
|
+
}
|
|
78
90
|
export function toolTrailLines(items, columns, mode = 'expanded') {
|
|
79
91
|
if (mode === 'hidden')
|
|
80
92
|
return [];
|
|
81
93
|
if (!items.length)
|
|
82
94
|
return [];
|
|
83
|
-
const width =
|
|
95
|
+
const width = toolTrailWidth(columns);
|
|
84
96
|
const nameWidth = Math.max(8, Math.min(24, Math.floor(width * 0.34)));
|
|
85
97
|
const detailWidth = Math.max(0, width - nameWidth - 18);
|
|
86
|
-
const lines =
|
|
98
|
+
const lines = toolTrailHeader(items, mode);
|
|
87
99
|
if (mode === 'compact') {
|
|
88
100
|
lines.push(`tools: ${items.map((item) => `${markerForStatus(item.status)}${item.name}`).join(' ')}`);
|
|
89
101
|
return lines.map((line) => clip(line, width));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanook-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "A terminal AI coding agent — BYOK, 9 providers, MCP, cron gateway, skills, and git awareness. Built from scratch in TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
"url": "https://github.com/Sir-chawakorn/sanook-cli/issues"
|
|
57
57
|
},
|
|
58
58
|
"homepage": "https://github.com/Sir-chawakorn/sanook-cli#readme",
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public",
|
|
61
|
+
"registry": "https://registry.npmjs.org/"
|
|
62
|
+
},
|
|
59
63
|
"dependencies": {
|
|
60
64
|
"@ai-sdk/anthropic": "^3.0.84",
|
|
61
65
|
"@ai-sdk/google": "^3.0.82",
|
|
@@ -80,5 +84,9 @@
|
|
|
80
84
|
"tsx": "^4.22.4",
|
|
81
85
|
"typescript": "^6.0.3",
|
|
82
86
|
"vitest": "^4.1.8"
|
|
87
|
+
},
|
|
88
|
+
"optionalDependencies": {
|
|
89
|
+
"node-pty": "^1.0.0",
|
|
90
|
+
"ws": "^8.18.0"
|
|
83
91
|
}
|
|
84
92
|
}
|