sanook-cli 0.5.2 → 0.5.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/CHANGELOG.md +112 -2
- package/README.md +15 -3
- package/README.th.md +8 -1
- package/dist/approval.js +7 -0
- package/dist/bin.js +637 -56
- package/dist/brain-consolidate.js +335 -0
- package/dist/brain-context.js +42 -3
- package/dist/brain-final.js +15 -9
- package/dist/brain-link.js +73 -0
- package/dist/brain-metrics.js +277 -0
- package/dist/brain-new.js +402 -0
- package/dist/brain-pack.js +210 -0
- package/dist/brain-repair.js +280 -0
- package/dist/brain.js +3 -0
- package/dist/brand.js +4 -0
- package/dist/cli-args.js +47 -9
- package/dist/cli-option-values.js +1 -1
- package/dist/clipboard.js +65 -0
- package/dist/commands.js +98 -15
- package/dist/config.js +66 -34
- package/dist/context-pack.js +145 -0
- package/dist/cost.js +20 -0
- package/dist/dashboard/api-helpers.js +87 -0
- package/dist/dashboard/server.js +179 -0
- package/dist/dashboard/static/app.js +277 -0
- package/dist/dashboard/static/index.html +39 -0
- package/dist/dashboard/static/styles.css +85 -0
- package/dist/diff.js +10 -2
- package/dist/gateway/auth.js +14 -3
- package/dist/gateway/deliver.js +45 -3
- package/dist/gateway/doctor.js +456 -0
- package/dist/gateway/email.js +30 -1
- package/dist/gateway/ledger.js +20 -1
- package/dist/gateway/session.js +34 -11
- package/dist/hotkeys.js +21 -0
- package/dist/i18n/en.js +98 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/th.js +98 -0
- package/dist/i18n/types.js +1 -0
- package/dist/insights-args.js +24 -4
- package/dist/knowledge.js +55 -29
- package/dist/loop.js +65 -9
- package/dist/mcp-hub.js +33 -0
- package/dist/mcp-registry.js +153 -9
- package/dist/mcp-risk.js +71 -0
- package/dist/mcp.js +77 -5
- package/dist/memory-log.js +90 -0
- package/dist/memory-store.js +37 -1
- package/dist/memory.js +51 -7
- package/dist/model-picker.js +58 -0
- package/dist/orchestrate.js +7 -5
- package/dist/plan-handoff.js +17 -0
- package/dist/polyglot.js +162 -0
- package/dist/process-runner.js +96 -0
- package/dist/project-init.js +91 -0
- package/dist/project-registry.js +143 -0
- package/dist/project-scaffold.js +124 -0
- package/dist/prompt-size.js +155 -0
- package/dist/providers/codex-login.js +138 -0
- package/dist/providers/codex.js +20 -8
- package/dist/providers/keys.js +21 -0
- package/dist/providers/models.js +1 -1
- package/dist/providers/registry.js +11 -1
- package/dist/search/cli.js +9 -1
- package/dist/search/embedding-config.js +22 -0
- package/dist/search/engine.js +2 -13
- package/dist/search/indexer.js +10 -10
- package/dist/session-brain.js +103 -0
- package/dist/session-distill.js +84 -0
- package/dist/session.js +1 -11
- package/dist/skill-install.js +24 -1
- package/dist/skills.js +33 -0
- package/dist/slash-completion.js +155 -0
- package/dist/support-dump.js +31 -0
- package/dist/tool-catalog.js +59 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/permission.js +82 -16
- package/dist/tools/polyglot.js +126 -0
- package/dist/tools/sandbox.js +38 -13
- package/dist/tools/search.js +9 -2
- package/dist/tools/task.js +22 -2
- package/dist/tools/timeout.js +7 -5
- package/dist/tools/web-fetch-tool.js +33 -0
- package/dist/turn-retrieval.js +83 -0
- package/dist/ui/app.js +874 -35
- package/dist/ui/banner.js +78 -4
- package/dist/ui/markdown.js +122 -0
- package/dist/ui/overlay.js +496 -0
- package/dist/ui/queue.js +23 -0
- package/dist/ui/render.js +30 -2
- package/dist/ui/session-panel.js +115 -0
- package/dist/ui/setup-providers.js +40 -0
- package/dist/ui/setup.js +163 -50
- package/dist/ui/status.js +142 -0
- package/dist/ui/thinking-panel.js +36 -0
- package/dist/ui/tool-trail.js +97 -0
- package/dist/ui/transcript.js +26 -0
- package/dist/ui/useBusyElapsed.js +19 -0
- package/dist/ui/useEditor.js +144 -5
- package/dist/ui/useGitBranch.js +57 -0
- package/dist/update.js +32 -6
- package/dist/usage-cli.js +160 -0
- package/dist/usage-ledger.js +169 -0
- package/dist/web-fetch.js +637 -0
- package/dist/web-surface.js +190 -0
- package/package.json +4 -3
- package/scripts/postinstall.mjs +4 -4
- package/second-brain/Projects/_Index.md +17 -4
- package/second-brain/Projects/sanook-cli/_Index.md +7 -3
- package/second-brain/Projects/sanook-cli/context.md +35 -0
- package/second-brain/Projects/sanook-cli/current-state.md +32 -0
- package/second-brain/Projects/sanook-cli/overview.md +41 -0
- package/second-brain/Projects/sanook-cli/repo.md +34 -0
- package/second-brain/Projects/sanook-cli/second-brain-feature-roadmap.md +52 -11
- package/second-brain/Research/2026-06-18-hermes-tui-parity-map.md +129 -0
- package/second-brain/Research/2026-06-19-hermes-python-architecture-for-sanook.md +49 -0
- package/second-brain/Research/2026-06-19-terminal-ui-brand-research.md +52 -0
- package/second-brain/Research/_Index.md +2 -0
- package/second-brain/Shared/Operating-State/current-state.md +14 -23
- package/second-brain/Shared/Tech-Standards/_Index.md +2 -0
- package/second-brain/Shared/Tech-Standards/polyglot-runtime-strategy.md +46 -0
- package/second-brain/Shared/Tech-Standards/web-search-grounding-policy.md +70 -0
- package/second-brain/Templates/project-workspace/_Index.md +31 -0
- package/second-brain/Templates/project-workspace/context.md +28 -0
- package/second-brain/Templates/project-workspace/current-state.md +29 -0
- package/second-brain/Templates/project-workspace/overview.md +39 -0
- package/second-brain/Templates/project-workspace/repo.md +33 -0
package/dist/ui/banner.js
CHANGED
|
@@ -1,15 +1,89 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
2
|
+
import { Box, Text, useStdout } from 'ink';
|
|
3
|
+
import BigText from 'ink-big-text';
|
|
3
4
|
import Gradient from 'ink-gradient';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
6
|
import { readFileSync } from 'node:fs';
|
|
6
7
|
import { BRAND } from '../brand.js';
|
|
7
8
|
// gradient ของ Sanook: เขียว → ส้ม → ฟ้า (สนุก = สดใส)
|
|
8
9
|
const SANOOK_GRADIENT = ['#22C55E', '#F97316', '#38BDF8'];
|
|
10
|
+
const BANNER_TITLE = BRAND.bannerWide.toUpperCase();
|
|
11
|
+
const COMMAND_HINTS = ['/help', '/tools', '/mcp', '/status'];
|
|
12
|
+
const BRAND_LINE = 'งานหนักให้เบาลง · ไม่เบาความรับผิดชอบ · local-first memory';
|
|
13
|
+
const WORKFLOW = ['plan', 'patch', 'prove', 'remember'];
|
|
14
|
+
const PROMISE = ['readable', 'recoverable', 'remembered'];
|
|
15
|
+
const SERVICE_ROUTES = [
|
|
16
|
+
['1', 'Code', '@file · /tools · /diff'],
|
|
17
|
+
['2', 'Brain', 'brain context · /skills · /compress'],
|
|
18
|
+
['3', 'Connect', '/mcp · serve · webhooks'],
|
|
19
|
+
['4', 'Ship', '/cost · /copy · /undo'],
|
|
20
|
+
];
|
|
21
|
+
const WIDE_WORDMARK_MIN_COLUMNS = 96;
|
|
22
|
+
const COMPACT_PANEL_COLUMNS = 76;
|
|
23
|
+
const TINY_PANEL_COLUMNS = 44;
|
|
24
|
+
const MAX_PANEL_COLUMNS = 100;
|
|
9
25
|
// version จาก package.json (single source of truth) — กัน default drift เหมือน bin.ts
|
|
10
26
|
const VERSION = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version;
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
const clip = (text, width) => {
|
|
28
|
+
if (width <= 0)
|
|
29
|
+
return '';
|
|
30
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}…` : text;
|
|
31
|
+
};
|
|
32
|
+
function signalText(signals) {
|
|
33
|
+
return signals
|
|
34
|
+
.filter((signal) => signal.label.trim() && signal.value.trim())
|
|
35
|
+
.map((signal) => {
|
|
36
|
+
const prefix = signal.tone === 'warn' ? '!' : signal.tone === 'muted' ? '-' : '+';
|
|
37
|
+
return `${prefix} ${signal.label} ${signal.value}`;
|
|
38
|
+
})
|
|
39
|
+
.join(' · ');
|
|
40
|
+
}
|
|
41
|
+
function bannerLines({ account, dir, model, mode, signals, version, }, columns) {
|
|
42
|
+
const title = `${BANNER_TITLE} v${version} · terminal AI agent · ${account}`;
|
|
43
|
+
const status = `● model ${model} · mode ${mode} · cwd ${dir}`;
|
|
44
|
+
const signalLine = signalText(signals);
|
|
45
|
+
const flow = `Flow ${WORKFLOW.join(' -> ')} · Promise ${PROMISE.join(' · ')}`;
|
|
46
|
+
const routeLine = `Routes ${SERVICE_ROUTES.map(([num, label]) => `${num} ${label}`).join(' | ')} · ${COMMAND_HINTS.join(' · ')}`;
|
|
47
|
+
if (columns < TINY_PANEL_COLUMNS) {
|
|
48
|
+
return [
|
|
49
|
+
title,
|
|
50
|
+
`● ${model} · ${mode}`,
|
|
51
|
+
...(signalLine ? [`Signals ${signalLine}`] : []),
|
|
52
|
+
'› /help · /tools · /mcp',
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
if (columns < COMPACT_PANEL_COLUMNS) {
|
|
56
|
+
return [
|
|
57
|
+
title,
|
|
58
|
+
status,
|
|
59
|
+
...(signalLine ? [`Signals ${signalLine}`] : []),
|
|
60
|
+
`◆ ${BRAND_LINE}`,
|
|
61
|
+
`Flow ${WORKFLOW.join(' -> ')}`,
|
|
62
|
+
'› routes: Code · Brain · Connect · Ship',
|
|
63
|
+
'› code: @file · /tools · /diff',
|
|
64
|
+
'› brain: context · skills · compress',
|
|
65
|
+
'› connect: /mcp · serve',
|
|
66
|
+
'› ship: /copy · /cost · /undo',
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
return [
|
|
70
|
+
title,
|
|
71
|
+
status,
|
|
72
|
+
...(signalLine ? [`Signals ${signalLine}`] : []),
|
|
73
|
+
`◆ ${BRAND_LINE}`,
|
|
74
|
+
flow,
|
|
75
|
+
routeLine,
|
|
76
|
+
...SERVICE_ROUTES.map(([num, label, hint]) => `› ${num} ${label.padEnd(7)} ${hint}`),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
/** welcome banner — Hermes-style responsive wordmark + compact Sanook launchpad. */
|
|
80
|
+
export function Banner({ model, version = VERSION, account = 'BYOK', cwd, mode = 'auto', columns, signals = [] }) {
|
|
81
|
+
const { stdout } = useStdout();
|
|
13
82
|
const dir = (cwd ?? process.cwd()).replace(homedir(), '~');
|
|
14
|
-
|
|
83
|
+
const terminalColumns = Math.max(1, Math.floor(columns ?? stdout?.columns ?? MAX_PANEL_COLUMNS));
|
|
84
|
+
const showWordmark = terminalColumns >= WIDE_WORDMARK_MIN_COLUMNS;
|
|
85
|
+
const panelWidth = Math.max(28, Math.min(terminalColumns, MAX_PANEL_COLUMNS));
|
|
86
|
+
const innerWidth = Math.max(1, panelWidth - 4);
|
|
87
|
+
const lines = bannerLines({ account, dir, model, mode, signals, version }, terminalColumns);
|
|
88
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [showWordmark ? (_jsx(Gradient, { colors: SANOOK_GRADIENT, children: _jsx(BigText, { text: BANNER_TITLE, font: "block", align: "left" }) })) : null, _jsx(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, width: panelWidth, children: lines.map((line, index) => (_jsx(Text, { color: index === 0 ? 'cyan' : undefined, dimColor: index > 0, wrap: "truncate-end", children: clip(line, innerWidth) }, `${index}-${line}`))) })] }));
|
|
15
89
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { memo, useRef } from 'react';
|
|
4
|
+
const FENCE_RE = /^\s*(`{3,}|~{3,})(.*)$/;
|
|
5
|
+
function clip(text, width) {
|
|
6
|
+
if (width <= 0)
|
|
7
|
+
return '';
|
|
8
|
+
return text.length > width ? `${text.slice(0, Math.max(0, width - 3))}...` : text;
|
|
9
|
+
}
|
|
10
|
+
function bodyWidth(columns) {
|
|
11
|
+
return Math.max(24, Math.min(Math.max(30, columns - 4), 100));
|
|
12
|
+
}
|
|
13
|
+
function lineStartsFence(line) {
|
|
14
|
+
return FENCE_RE.test(line);
|
|
15
|
+
}
|
|
16
|
+
export function findStableMarkdownBoundary(text) {
|
|
17
|
+
let inFence = false;
|
|
18
|
+
let last = -1;
|
|
19
|
+
for (let i = 0; i < text.length;) {
|
|
20
|
+
const nl = text.indexOf('\n', i);
|
|
21
|
+
const end = nl === -1 ? text.length : nl;
|
|
22
|
+
const line = text.slice(i, end);
|
|
23
|
+
if (lineStartsFence(line))
|
|
24
|
+
inFence = !inFence;
|
|
25
|
+
if (!inFence && text.slice(end, end + 2) === '\n\n')
|
|
26
|
+
last = end + 2;
|
|
27
|
+
if (nl === -1)
|
|
28
|
+
break;
|
|
29
|
+
i = nl + 1;
|
|
30
|
+
}
|
|
31
|
+
return last;
|
|
32
|
+
}
|
|
33
|
+
function inlineSegments(text) {
|
|
34
|
+
const out = [];
|
|
35
|
+
const re = /(`[^`\n]+`|\*\*[^*\n]+\*\*)/g;
|
|
36
|
+
let last = 0;
|
|
37
|
+
for (const match of text.matchAll(re)) {
|
|
38
|
+
const index = match.index ?? 0;
|
|
39
|
+
if (index > last)
|
|
40
|
+
out.push({ kind: 'text', text: text.slice(last, index) });
|
|
41
|
+
const token = match[0];
|
|
42
|
+
out.push(token.startsWith('`')
|
|
43
|
+
? { kind: 'code', text: token.slice(1, -1) }
|
|
44
|
+
: { kind: 'bold', text: token.slice(2, -2) });
|
|
45
|
+
last = index + token.length;
|
|
46
|
+
}
|
|
47
|
+
if (last < text.length)
|
|
48
|
+
out.push({ kind: 'text', text: text.slice(last) });
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function InlineMarkdown({ text }) {
|
|
52
|
+
return (_jsx(Text, { children: inlineSegments(text).map((segment, index) => {
|
|
53
|
+
if (segment.kind === 'code') {
|
|
54
|
+
return (_jsx(Text, { color: "yellow", children: segment.text }, index));
|
|
55
|
+
}
|
|
56
|
+
if (segment.kind === 'bold') {
|
|
57
|
+
return (_jsx(Text, { bold: true, children: segment.text }, index));
|
|
58
|
+
}
|
|
59
|
+
return _jsx(Text, { children: segment.text }, index);
|
|
60
|
+
}) }));
|
|
61
|
+
}
|
|
62
|
+
export function MarkdownText({ columns, text }) {
|
|
63
|
+
const width = bodyWidth(columns);
|
|
64
|
+
const lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
65
|
+
const nodes = [];
|
|
66
|
+
let inFence = false;
|
|
67
|
+
let fenceLang = '';
|
|
68
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
69
|
+
const raw = lines[index] ?? '';
|
|
70
|
+
const fence = FENCE_RE.exec(raw);
|
|
71
|
+
if (fence) {
|
|
72
|
+
inFence = !inFence;
|
|
73
|
+
fenceLang = inFence ? fence[2]?.trim() ?? '' : '';
|
|
74
|
+
nodes.push(_jsx(Text, { color: "cyan", dimColor: true, children: inFence ? `code${fenceLang ? ` ${clip(fenceLang, 24)}` : ''}` : 'end code' }, `f-${index}`));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (inFence) {
|
|
78
|
+
nodes.push(_jsxs(Text, { color: "gray", wrap: "truncate-end", children: [' ', clip(raw || ' ', width - 2)] }, `c-${index}`));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const trimmed = raw.trim();
|
|
82
|
+
if (!trimmed) {
|
|
83
|
+
nodes.push(_jsx(Text, { children: " " }, `b-${index}`));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const heading = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
87
|
+
if (heading) {
|
|
88
|
+
nodes.push(_jsx(Text, { color: "cyan", bold: true, wrap: "truncate-end", children: clip(heading[2] ?? '', width) }, `h-${index}`));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const quote = /^>\s?(.*)$/.exec(trimmed);
|
|
92
|
+
if (quote) {
|
|
93
|
+
nodes.push(_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ['> ', clip(quote[1] ?? '', width - 2)] }, `q-${index}`));
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const bullet = /^([-*])\s+(.+)$/.exec(trimmed);
|
|
97
|
+
if (bullet) {
|
|
98
|
+
nodes.push(_jsxs(Text, { wrap: "truncate-end", children: ['- ', _jsx(InlineMarkdown, { text: clip(bullet[2] ?? '', width - 2) })] }, `li-${index}`));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const ordered = /^(\d+[.)])\s+(.+)$/.exec(trimmed);
|
|
102
|
+
if (ordered) {
|
|
103
|
+
const marker = `${ordered[1]} `;
|
|
104
|
+
nodes.push(_jsxs(Text, { wrap: "truncate-end", children: [marker, _jsx(InlineMarkdown, { text: clip(ordered[2] ?? '', width - marker.length) })] }, `ol-${index}`));
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
nodes.push(_jsx(Text, { wrap: "truncate-end", children: _jsx(InlineMarkdown, { text: clip(trimmed, width) }) }, `p-${index}`));
|
|
108
|
+
}
|
|
109
|
+
return _jsx(Box, { flexDirection: "column", children: nodes });
|
|
110
|
+
}
|
|
111
|
+
const MemoMarkdownText = memo(MarkdownText);
|
|
112
|
+
export const StreamingMarkdownText = memo(function StreamingMarkdownText({ columns, text }) {
|
|
113
|
+
const stableRef = useRef('');
|
|
114
|
+
if (!text.startsWith(stableRef.current))
|
|
115
|
+
stableRef.current = '';
|
|
116
|
+
const boundary = findStableMarkdownBoundary(text);
|
|
117
|
+
if (boundary > stableRef.current.length)
|
|
118
|
+
stableRef.current = text.slice(0, boundary);
|
|
119
|
+
const stable = stableRef.current;
|
|
120
|
+
const tail = text.slice(stable.length);
|
|
121
|
+
return (_jsxs(Box, { flexDirection: "column", children: [stable ? _jsx(MemoMarkdownText, { columns: columns, text: stable }) : null, tail ? _jsx(MarkdownText, { columns: columns, text: tail }) : null] }));
|
|
122
|
+
});
|