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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { appHomePath, BRAND, usageLedgerEnabled } from './brand.js';
|
|
5
|
+
const USAGE_DIR_NAME = 'usage';
|
|
6
|
+
export function usageDirPath() {
|
|
7
|
+
return appHomePath(USAGE_DIR_NAME);
|
|
8
|
+
}
|
|
9
|
+
export function usageEventsPath() {
|
|
10
|
+
return join(usageDirPath(), 'events.jsonl');
|
|
11
|
+
}
|
|
12
|
+
function localDate(iso) {
|
|
13
|
+
const d = new Date(iso);
|
|
14
|
+
if (!Number.isFinite(d.getTime()))
|
|
15
|
+
return iso.slice(0, 10);
|
|
16
|
+
const y = d.getFullYear();
|
|
17
|
+
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
18
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
19
|
+
return `${y}-${m}-${day}`;
|
|
20
|
+
}
|
|
21
|
+
function num(value) {
|
|
22
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
23
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
|
|
24
|
+
}
|
|
25
|
+
/** Parse Codex JSONL turn.completed usage payloads into AI SDK Usage shape. */
|
|
26
|
+
export function usageFromCodexPayload(raw) {
|
|
27
|
+
if (!raw || typeof raw !== 'object')
|
|
28
|
+
return null;
|
|
29
|
+
const u = raw;
|
|
30
|
+
const input = num(u.input_tokens ?? u.inputTokens ?? u.prompt_tokens ?? u.promptTokens);
|
|
31
|
+
const output = num(u.output_tokens ?? u.outputTokens ?? u.completion_tokens ?? u.completionTokens);
|
|
32
|
+
const cacheRead = num(u.cache_read_input_tokens ?? u.cached_input_tokens ?? u.cacheReadInputTokens ?? u.cachedInputTokens);
|
|
33
|
+
if (!input && !output && !cacheRead)
|
|
34
|
+
return null;
|
|
35
|
+
return { inputTokens: input, outputTokens: output, cachedInputTokens: cacheRead };
|
|
36
|
+
}
|
|
37
|
+
export async function appendUsageEvent(event) {
|
|
38
|
+
if (!usageLedgerEnabled())
|
|
39
|
+
return;
|
|
40
|
+
await mkdir(usageDirPath(), { recursive: true });
|
|
41
|
+
await appendFile(usageEventsPath(), `${JSON.stringify(event)}\n`, { mode: 0o600 });
|
|
42
|
+
}
|
|
43
|
+
export function recordAgentUsage(options) {
|
|
44
|
+
if (!usageLedgerEnabled())
|
|
45
|
+
return;
|
|
46
|
+
const snap = options.cost.snapshot();
|
|
47
|
+
const ts = new Date().toISOString();
|
|
48
|
+
const event = {
|
|
49
|
+
id: randomUUID(),
|
|
50
|
+
ts,
|
|
51
|
+
date: localDate(ts),
|
|
52
|
+
sessionId: options.sessionId,
|
|
53
|
+
source: options.source ?? 'headless',
|
|
54
|
+
model: options.model,
|
|
55
|
+
cwd: options.cwd,
|
|
56
|
+
inputTokens: snap.inputTokens,
|
|
57
|
+
outputTokens: snap.outputTokens,
|
|
58
|
+
cacheReadTokens: snap.cacheReadTokens,
|
|
59
|
+
cacheWriteTokens: snap.cacheWriteTokens,
|
|
60
|
+
totalTokens: snap.totalTokens,
|
|
61
|
+
costUsd: snap.hasPricing ? snap.costUsd : null,
|
|
62
|
+
priced: snap.hasPricing,
|
|
63
|
+
};
|
|
64
|
+
void appendUsageEvent(event).catch(() => { });
|
|
65
|
+
}
|
|
66
|
+
export async function loadUsageEvents(options = {}) {
|
|
67
|
+
let raw = '';
|
|
68
|
+
try {
|
|
69
|
+
raw = await readFile(usageEventsPath(), 'utf8');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const since = options.since;
|
|
75
|
+
const until = options.until;
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const line of raw.split('\n')) {
|
|
78
|
+
const t = line.trim();
|
|
79
|
+
if (!t)
|
|
80
|
+
continue;
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(t);
|
|
83
|
+
if (!parsed?.ts || typeof parsed.model !== 'string')
|
|
84
|
+
continue;
|
|
85
|
+
const date = parsed.date || localDate(parsed.ts);
|
|
86
|
+
if (since && date < since)
|
|
87
|
+
continue;
|
|
88
|
+
if (until && date > until)
|
|
89
|
+
continue;
|
|
90
|
+
out.push({ ...parsed, date });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// skip malformed line
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function mergeModels(map, model) {
|
|
99
|
+
const label = model.includes(':') ? model.split(':').slice(1).join(':') : model;
|
|
100
|
+
map.set(label, (map.get(label) ?? 0) + 1);
|
|
101
|
+
}
|
|
102
|
+
function topModels(map) {
|
|
103
|
+
return [...map.entries()]
|
|
104
|
+
.sort((a, b) => b[1] - a[1])
|
|
105
|
+
.slice(0, 3)
|
|
106
|
+
.map(([model]) => `• ${model}`);
|
|
107
|
+
}
|
|
108
|
+
function weekKey(date) {
|
|
109
|
+
const d = new Date(`${date}T12:00:00`);
|
|
110
|
+
const day = d.getDay();
|
|
111
|
+
const diff = day === 0 ? -6 : 1 - day;
|
|
112
|
+
d.setDate(d.getDate() + diff);
|
|
113
|
+
return localDate(d.toISOString());
|
|
114
|
+
}
|
|
115
|
+
function monthKey(date) {
|
|
116
|
+
return date.slice(0, 7);
|
|
117
|
+
}
|
|
118
|
+
export function aggregateUsageEvents(events, mode) {
|
|
119
|
+
const groups = new Map();
|
|
120
|
+
for (const event of events) {
|
|
121
|
+
const key = mode === 'daily'
|
|
122
|
+
? event.date
|
|
123
|
+
: mode === 'weekly'
|
|
124
|
+
? weekKey(event.date)
|
|
125
|
+
: mode === 'monthly'
|
|
126
|
+
? monthKey(event.date)
|
|
127
|
+
: event.sessionId ?? `turn:${event.id}`;
|
|
128
|
+
const bucket = groups.get(key) ?? { models: new Map(), events: [] };
|
|
129
|
+
mergeModels(bucket.models, event.model);
|
|
130
|
+
bucket.events.push(event);
|
|
131
|
+
groups.set(key, bucket);
|
|
132
|
+
}
|
|
133
|
+
return [...groups.entries()]
|
|
134
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
135
|
+
.map(([key, bucket]) => {
|
|
136
|
+
let inputTokens = 0;
|
|
137
|
+
let outputTokens = 0;
|
|
138
|
+
let cacheReadTokens = 0;
|
|
139
|
+
let cacheWriteTokens = 0;
|
|
140
|
+
let costUsd = 0;
|
|
141
|
+
for (const event of bucket.events) {
|
|
142
|
+
inputTokens += event.inputTokens;
|
|
143
|
+
outputTokens += event.outputTokens;
|
|
144
|
+
cacheReadTokens += event.cacheReadTokens;
|
|
145
|
+
cacheWriteTokens += event.cacheWriteTokens;
|
|
146
|
+
costUsd += event.costUsd ?? 0;
|
|
147
|
+
}
|
|
148
|
+
const label = mode === 'session'
|
|
149
|
+
? key.startsWith('turn:')
|
|
150
|
+
? `${key.slice(5, 18)}…`
|
|
151
|
+
: key.slice(0, 24)
|
|
152
|
+
: key;
|
|
153
|
+
return {
|
|
154
|
+
key,
|
|
155
|
+
label,
|
|
156
|
+
models: topModels(bucket.models),
|
|
157
|
+
turns: bucket.events.length,
|
|
158
|
+
inputTokens,
|
|
159
|
+
outputTokens,
|
|
160
|
+
cacheReadTokens,
|
|
161
|
+
cacheWriteTokens,
|
|
162
|
+
totalTokens: inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
|
|
163
|
+
costUsd,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
export function formatUsageLedgerHint() {
|
|
168
|
+
return `ดูประวัติ token ทั้งหมด: ${BRAND.cliName} usage daily`;
|
|
169
|
+
}
|