@kkelly-offical/kkcode 0.1.2
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/LICENSE +674 -0
- package/README.md +445 -0
- package/package.json +46 -0
- package/src/agent/agent.mjs +170 -0
- package/src/agent/custom-agent-loader.mjs +158 -0
- package/src/agent/generator.mjs +115 -0
- package/src/agent/prompt/architect.txt +36 -0
- package/src/agent/prompt/build-fixer.txt +71 -0
- package/src/agent/prompt/build.txt +101 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +29 -0
- package/src/agent/prompt/guide.txt +40 -0
- package/src/agent/prompt/longagent.txt +178 -0
- package/src/agent/prompt/plan.txt +50 -0
- package/src/agent/prompt/researcher.txt +23 -0
- package/src/agent/prompt/reviewer.txt +44 -0
- package/src/agent/prompt/security-reviewer.txt +62 -0
- package/src/agent/prompt/tdd-guide.txt +84 -0
- package/src/agent/prompt/title.txt +8 -0
- package/src/command/custom-commands.mjs +57 -0
- package/src/commands/agent.mjs +71 -0
- package/src/commands/audit.mjs +77 -0
- package/src/commands/background.mjs +86 -0
- package/src/commands/chat.mjs +114 -0
- package/src/commands/command.mjs +41 -0
- package/src/commands/config.mjs +44 -0
- package/src/commands/doctor.mjs +148 -0
- package/src/commands/hook.mjs +29 -0
- package/src/commands/init.mjs +141 -0
- package/src/commands/longagent.mjs +100 -0
- package/src/commands/mcp.mjs +89 -0
- package/src/commands/permission.mjs +36 -0
- package/src/commands/prompt.mjs +42 -0
- package/src/commands/review.mjs +266 -0
- package/src/commands/rule.mjs +34 -0
- package/src/commands/session.mjs +235 -0
- package/src/commands/theme.mjs +98 -0
- package/src/commands/usage.mjs +91 -0
- package/src/config/defaults.mjs +195 -0
- package/src/config/import-config.mjs +76 -0
- package/src/config/load-config.mjs +76 -0
- package/src/config/schema.mjs +509 -0
- package/src/context.mjs +40 -0
- package/src/core/constants.mjs +46 -0
- package/src/core/errors.mjs +57 -0
- package/src/core/events.mjs +29 -0
- package/src/core/types.mjs +57 -0
- package/src/github/api.mjs +78 -0
- package/src/github/auth.mjs +286 -0
- package/src/github/flow.mjs +298 -0
- package/src/github/workspace.mjs +212 -0
- package/src/index.mjs +82 -0
- package/src/knowledge/api-design.txt +9 -0
- package/src/knowledge/cpp.txt +10 -0
- package/src/knowledge/docker.txt +10 -0
- package/src/knowledge/dotnet.txt +9 -0
- package/src/knowledge/electron.txt +10 -0
- package/src/knowledge/flutter.txt +10 -0
- package/src/knowledge/go.txt +9 -0
- package/src/knowledge/graphql.txt +10 -0
- package/src/knowledge/java.txt +9 -0
- package/src/knowledge/kotlin.txt +10 -0
- package/src/knowledge/loader.mjs +125 -0
- package/src/knowledge/next.txt +8 -0
- package/src/knowledge/node.txt +8 -0
- package/src/knowledge/nuxt.txt +9 -0
- package/src/knowledge/php.txt +10 -0
- package/src/knowledge/python.txt +10 -0
- package/src/knowledge/react-native.txt +10 -0
- package/src/knowledge/react.txt +9 -0
- package/src/knowledge/ruby.txt +11 -0
- package/src/knowledge/rust.txt +9 -0
- package/src/knowledge/svelte.txt +9 -0
- package/src/knowledge/swift.txt +10 -0
- package/src/knowledge/tailwind.txt +10 -0
- package/src/knowledge/testing.txt +8 -0
- package/src/knowledge/typescript.txt +8 -0
- package/src/knowledge/vue.txt +9 -0
- package/src/mcp/client-http.mjs +157 -0
- package/src/mcp/client-sse.mjs +286 -0
- package/src/mcp/client-stdio.mjs +451 -0
- package/src/mcp/registry.mjs +394 -0
- package/src/mcp/stdio-framing.mjs +127 -0
- package/src/orchestration/background-manager.mjs +358 -0
- package/src/orchestration/background-worker.mjs +245 -0
- package/src/orchestration/longagent-manager.mjs +116 -0
- package/src/orchestration/stage-scheduler.mjs +489 -0
- package/src/orchestration/subagent-router.mjs +62 -0
- package/src/orchestration/task-scheduler.mjs +74 -0
- package/src/permission/engine.mjs +92 -0
- package/src/permission/exec-policy.mjs +372 -0
- package/src/permission/prompt.mjs +39 -0
- package/src/permission/rules.mjs +120 -0
- package/src/permission/workspace-trust.mjs +44 -0
- package/src/plugin/builtin-hooks/console-warn.mjs +41 -0
- package/src/plugin/builtin-hooks/extract-patterns.mjs +75 -0
- package/src/plugin/builtin-hooks/post-edit-format.mjs +57 -0
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +61 -0
- package/src/plugin/builtin-hooks/strategic-compaction.mjs +38 -0
- package/src/plugin/hook-bus.mjs +154 -0
- package/src/provider/anthropic.mjs +389 -0
- package/src/provider/ollama.mjs +236 -0
- package/src/provider/openai-compatible.mjs +1 -0
- package/src/provider/openai.mjs +339 -0
- package/src/provider/retry-policy.mjs +68 -0
- package/src/provider/router.mjs +228 -0
- package/src/provider/sse.mjs +91 -0
- package/src/repl.mjs +2929 -0
- package/src/review/diff-parser.mjs +36 -0
- package/src/review/rejection-queue.mjs +62 -0
- package/src/review/review-store.mjs +21 -0
- package/src/review/risk-score.mjs +61 -0
- package/src/rules/load-rules.mjs +64 -0
- package/src/runtime.mjs +1 -0
- package/src/session/checkpoint.mjs +239 -0
- package/src/session/compaction.mjs +276 -0
- package/src/session/engine.mjs +225 -0
- package/src/session/instinct-manager.mjs +172 -0
- package/src/session/instruction-loader.mjs +25 -0
- package/src/session/longagent-plan.mjs +329 -0
- package/src/session/longagent-scaffold.mjs +100 -0
- package/src/session/longagent.mjs +1462 -0
- package/src/session/loop.mjs +905 -0
- package/src/session/memory-loader.mjs +75 -0
- package/src/session/project-context.mjs +367 -0
- package/src/session/prompt/anthropic.txt +151 -0
- package/src/session/prompt/beast.txt +37 -0
- package/src/session/prompt/max-steps.txt +6 -0
- package/src/session/prompt/plan.txt +9 -0
- package/src/session/prompt/qwen.txt +46 -0
- package/src/session/prompt-loader.mjs +18 -0
- package/src/session/recovery.mjs +52 -0
- package/src/session/store.mjs +503 -0
- package/src/session/system-prompt.mjs +260 -0
- package/src/session/task-validator.mjs +266 -0
- package/src/session/usability-gates.mjs +379 -0
- package/src/skill/builtin/backend-patterns.mjs +123 -0
- package/src/skill/builtin/commit.mjs +64 -0
- package/src/skill/builtin/debug.mjs +45 -0
- package/src/skill/builtin/frontend-patterns.mjs +120 -0
- package/src/skill/builtin/frontend.mjs +188 -0
- package/src/skill/builtin/init.mjs +220 -0
- package/src/skill/builtin/review.mjs +49 -0
- package/src/skill/builtin/security-checklist.mjs +80 -0
- package/src/skill/builtin/tdd.mjs +54 -0
- package/src/skill/generator.mjs +113 -0
- package/src/skill/registry.mjs +336 -0
- package/src/storage/audit-store.mjs +83 -0
- package/src/storage/event-log.mjs +82 -0
- package/src/storage/ghost-commit-store.mjs +235 -0
- package/src/storage/json-store.mjs +53 -0
- package/src/storage/paths.mjs +148 -0
- package/src/theme/color.mjs +64 -0
- package/src/theme/default-theme.mjs +29 -0
- package/src/theme/load-theme.mjs +71 -0
- package/src/theme/markdown.mjs +135 -0
- package/src/theme/schema.mjs +45 -0
- package/src/theme/status-bar.mjs +158 -0
- package/src/tool/audit-wrapper.mjs +38 -0
- package/src/tool/edit-transaction.mjs +126 -0
- package/src/tool/executor.mjs +109 -0
- package/src/tool/file-lock-manager.mjs +85 -0
- package/src/tool/git-auto.mjs +545 -0
- package/src/tool/git-full-auto.mjs +478 -0
- package/src/tool/image-util.mjs +276 -0
- package/src/tool/prompt/background_cancel.txt +1 -0
- package/src/tool/prompt/background_output.txt +1 -0
- package/src/tool/prompt/bash.txt +71 -0
- package/src/tool/prompt/codesearch.txt +18 -0
- package/src/tool/prompt/edit.txt +27 -0
- package/src/tool/prompt/enter_plan.txt +74 -0
- package/src/tool/prompt/exit_plan.txt +62 -0
- package/src/tool/prompt/glob.txt +33 -0
- package/src/tool/prompt/grep.txt +43 -0
- package/src/tool/prompt/list.txt +8 -0
- package/src/tool/prompt/multiedit.txt +20 -0
- package/src/tool/prompt/notebookedit.txt +21 -0
- package/src/tool/prompt/patch.txt +24 -0
- package/src/tool/prompt/question.txt +44 -0
- package/src/tool/prompt/read.txt +40 -0
- package/src/tool/prompt/task.txt +83 -0
- package/src/tool/prompt/todowrite.txt +117 -0
- package/src/tool/prompt/webfetch.txt +38 -0
- package/src/tool/prompt/websearch.txt +43 -0
- package/src/tool/prompt/write.txt +38 -0
- package/src/tool/prompt-loader.mjs +18 -0
- package/src/tool/question-prompt.mjs +86 -0
- package/src/tool/registry.mjs +1309 -0
- package/src/tool/task-tool.mjs +28 -0
- package/src/ui/activity-renderer.mjs +410 -0
- package/src/ui/repl-dashboard.mjs +357 -0
- package/src/usage/pricing.mjs +121 -0
- package/src/usage/usage-meter.mjs +113 -0
- package/src/util/git.mjs +496 -0
- package/src/util/template.mjs +10 -0
- package/src/util/yaml.mjs +100 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { homedir } from "node:os"
|
|
2
|
+
import { paint } from "../theme/color.mjs"
|
|
3
|
+
|
|
4
|
+
function stripAnsi(text) {
|
|
5
|
+
return String(text || "").replace(/\x1B\[[0-9;]*m/g, "")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isFullWidthCodePoint(code) {
|
|
9
|
+
if (Number.isNaN(code)) return false
|
|
10
|
+
if (
|
|
11
|
+
code >= 0x1100 && (
|
|
12
|
+
code <= 0x115f ||
|
|
13
|
+
code === 0x2329 || code === 0x232a ||
|
|
14
|
+
(code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
|
|
15
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
16
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
17
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
18
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
19
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
20
|
+
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
21
|
+
(code >= 0x1f300 && code <= 0x1f64f) ||
|
|
22
|
+
(code >= 0x1f900 && code <= 0x1f9ff) ||
|
|
23
|
+
(code >= 0x20000 && code <= 0x3fffd)
|
|
24
|
+
)
|
|
25
|
+
) return true
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function visibleWidth(text) {
|
|
30
|
+
let width = 0
|
|
31
|
+
for (const ch of stripAnsi(text)) {
|
|
32
|
+
width += isFullWidthCodePoint(ch.codePointAt(0)) ? 2 : 1
|
|
33
|
+
}
|
|
34
|
+
return width
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function clipPlainByWidth(text, maxWidth) {
|
|
38
|
+
if (maxWidth <= 0) return ""
|
|
39
|
+
let out = ""
|
|
40
|
+
let used = 0
|
|
41
|
+
for (const ch of String(text || "")) {
|
|
42
|
+
const w = isFullWidthCodePoint(ch.codePointAt(0)) ? 2 : 1
|
|
43
|
+
if (used + w > maxWidth) break
|
|
44
|
+
out += ch
|
|
45
|
+
used += w
|
|
46
|
+
}
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function padCell(text, width) {
|
|
51
|
+
const raw = stripAnsi(text)
|
|
52
|
+
const w = visibleWidth(raw)
|
|
53
|
+
if (w === width) return text
|
|
54
|
+
if (w < width) return text + " ".repeat(width - w)
|
|
55
|
+
if (width <= 1) return clipPlainByWidth(raw, width)
|
|
56
|
+
return clipPlainByWidth(raw, Math.max(1, width - 1)) + "…"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function wrapPlain(text, width) {
|
|
60
|
+
if (width <= 4) return [clipPlainByWidth(String(text || ""), Math.max(width, 1))]
|
|
61
|
+
const words = String(text || "").split(/\s+/).filter(Boolean)
|
|
62
|
+
if (!words.length) return [""]
|
|
63
|
+
const out = []
|
|
64
|
+
let line = ""
|
|
65
|
+
for (const word of words) {
|
|
66
|
+
const candidate = line ? `${line} ${word}` : word
|
|
67
|
+
if (visibleWidth(candidate) <= width) {
|
|
68
|
+
line = candidate
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
if (line) out.push(line)
|
|
72
|
+
if (visibleWidth(word) <= width) {
|
|
73
|
+
line = word
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
let rest = word
|
|
77
|
+
while (visibleWidth(rest) > width) {
|
|
78
|
+
const part = clipPlainByWidth(rest, width)
|
|
79
|
+
out.push(part)
|
|
80
|
+
rest = rest.slice(part.length)
|
|
81
|
+
}
|
|
82
|
+
line = rest || ""
|
|
83
|
+
}
|
|
84
|
+
if (line) out.push(line)
|
|
85
|
+
return out.length ? out : [""]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function terminalWidth() {
|
|
89
|
+
const cols = Number(process.stdout.columns || 120)
|
|
90
|
+
if (!Number.isFinite(cols) || cols <= 0) return 120
|
|
91
|
+
return Math.max(60, Math.min(cols, 220))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function centerLine(text, width) {
|
|
95
|
+
const rawWidth = visibleWidth(text)
|
|
96
|
+
if (rawWidth >= width) return text
|
|
97
|
+
const pad = Math.floor((width - rawWidth) / 2)
|
|
98
|
+
return `${" ".repeat(Math.max(0, pad))}${text}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function shortenPath(path) {
|
|
102
|
+
const home = homedir()
|
|
103
|
+
if (!path) return ""
|
|
104
|
+
const replaced = path.startsWith(home) ? `~${path.slice(home.length)}` : path
|
|
105
|
+
if (replaced.length <= 72) return replaced
|
|
106
|
+
return `...${replaced.slice(-69)}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderTag(theme, label, fg = "#0b0b0b", bg = theme.base.accent) {
|
|
110
|
+
return paint(` ${label} `, fg, { bg, bold: true })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function ageLabel(ms) {
|
|
114
|
+
const mins = Math.round(ms / 60000)
|
|
115
|
+
if (mins < 1) return "just now"
|
|
116
|
+
if (mins < 60) return `${mins}m ago`
|
|
117
|
+
const hours = Math.round(mins / 60)
|
|
118
|
+
if (hours < 24) return `${hours}h ago`
|
|
119
|
+
return `${Math.round(hours / 24)}d ago`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function flattenSections(sections, width) {
|
|
123
|
+
const lines = []
|
|
124
|
+
for (const [index, section] of sections.entries()) {
|
|
125
|
+
lines.push(paint(section.title, section.color, { bold: true }))
|
|
126
|
+
for (const item of section.items) {
|
|
127
|
+
if (!item) {
|
|
128
|
+
lines.push("")
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
const wrapped = wrapPlain(item, width)
|
|
132
|
+
for (const line of wrapped) lines.push(line)
|
|
133
|
+
}
|
|
134
|
+
if (index !== sections.length - 1) lines.push("")
|
|
135
|
+
}
|
|
136
|
+
return lines
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function frameLine(content, width, borderColor) {
|
|
140
|
+
const inner = width - 4
|
|
141
|
+
const padded = padCell(content, inner)
|
|
142
|
+
return paint(`| ${padded} |`, borderColor)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function drawSingleColumn({ width, theme, sections }) {
|
|
146
|
+
const border = paint(`+${"-".repeat(width - 2)}+`, theme.base.border)
|
|
147
|
+
const out = [border]
|
|
148
|
+
const cellWidth = width - 4
|
|
149
|
+
const lines = flattenSections(sections, cellWidth)
|
|
150
|
+
for (const line of lines) out.push(frameLine(line, width, theme.base.border))
|
|
151
|
+
out.push(border)
|
|
152
|
+
return out
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function drawDoubleColumn({ width, theme, leftSections, rightSections }) {
|
|
156
|
+
const border = paint(`+${"-".repeat(width - 2)}+`, theme.base.border)
|
|
157
|
+
const out = [border]
|
|
158
|
+
const inner = width - 4
|
|
159
|
+
const gap = 3
|
|
160
|
+
const leftWidth = Math.floor((inner - gap) * 0.5)
|
|
161
|
+
const rightWidth = inner - gap - leftWidth
|
|
162
|
+
|
|
163
|
+
const leftLines = flattenSections(leftSections, leftWidth)
|
|
164
|
+
const rightLines = flattenSections(rightSections, rightWidth)
|
|
165
|
+
const rows = Math.max(leftLines.length, rightLines.length)
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < rows; i++) {
|
|
168
|
+
const left = padCell(leftLines[i] || "", leftWidth)
|
|
169
|
+
const right = padCell(rightLines[i] || "", rightWidth)
|
|
170
|
+
out.push(paint(`| ${left} | ${right} |`, theme.base.border))
|
|
171
|
+
}
|
|
172
|
+
out.push(border)
|
|
173
|
+
return out
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function renderReplDashboard({
|
|
177
|
+
theme,
|
|
178
|
+
state,
|
|
179
|
+
providers,
|
|
180
|
+
recentSessions,
|
|
181
|
+
customCommandCount,
|
|
182
|
+
cwd,
|
|
183
|
+
columns = null
|
|
184
|
+
}) {
|
|
185
|
+
const width = Number.isFinite(columns) ? Math.max(60, Math.min(Number(columns), 220)) : terminalWidth()
|
|
186
|
+
const title = `${renderTag(theme, "KKCODE", "#111111", theme.semantic.info)} ${paint("Interactive Coding CLI", theme.base.fg, { bold: true })}`
|
|
187
|
+
const subtitle = paint("Adaptive dashboard + richer command palette", theme.base.muted)
|
|
188
|
+
|
|
189
|
+
const recentLines = recentSessions.length
|
|
190
|
+
? recentSessions.slice(0, 6).map((s) => `${s.id.slice(0, 12)} ${s.mode} ${ageLabel(Date.now() - s.updatedAt)}`)
|
|
191
|
+
: ["(no session yet)"]
|
|
192
|
+
|
|
193
|
+
const leftSections = [
|
|
194
|
+
{
|
|
195
|
+
title: "Workspace",
|
|
196
|
+
color: theme.semantic.info,
|
|
197
|
+
items: [shortenPath(cwd)]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
title: "Runtime",
|
|
201
|
+
color: theme.semantic.success,
|
|
202
|
+
items: [
|
|
203
|
+
`Session: ${state.sessionId}`,
|
|
204
|
+
`Mode: ${state.mode}`,
|
|
205
|
+
`Provider: ${state.providerType}`,
|
|
206
|
+
`Model: ${state.model}`,
|
|
207
|
+
`Custom commands: ${customCommandCount}`
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
title: "Quick Aliases",
|
|
212
|
+
color: theme.modes.agent,
|
|
213
|
+
items: [
|
|
214
|
+
"/h /? help",
|
|
215
|
+
"/n new session",
|
|
216
|
+
"/r resume latest",
|
|
217
|
+
"/m switch mode",
|
|
218
|
+
"/p switch provider",
|
|
219
|
+
"/k shortcuts"
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
const rightSections = [
|
|
225
|
+
{
|
|
226
|
+
title: "Tips",
|
|
227
|
+
color: theme.semantic.warn,
|
|
228
|
+
items: [
|
|
229
|
+
"Use /dash to redraw this panel",
|
|
230
|
+
"Use /clear to clear screen",
|
|
231
|
+
"Use /model <id> to override model",
|
|
232
|
+
"Use \"\"\" for multi-line prompts"
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
title: "Recent Activity",
|
|
237
|
+
color: theme.modes.plan,
|
|
238
|
+
items: recentLines
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
title: "Providers",
|
|
242
|
+
color: theme.modes.ask,
|
|
243
|
+
items: [providers.length ? providers.join(" | ") : "(none configured)"]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
title: "Useful Commands",
|
|
247
|
+
color: theme.modes.longagent,
|
|
248
|
+
items: [
|
|
249
|
+
"/history /resume /commands /reload",
|
|
250
|
+
"/ask /plan /agent /longagent"
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
const lines = [
|
|
256
|
+
title,
|
|
257
|
+
subtitle,
|
|
258
|
+
""
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
const useSingle = width < 110
|
|
262
|
+
const panel = useSingle
|
|
263
|
+
? drawSingleColumn({ width, theme, sections: [...leftSections, ...rightSections] })
|
|
264
|
+
: drawDoubleColumn({ width, theme, leftSections, rightSections })
|
|
265
|
+
|
|
266
|
+
lines.push(...panel)
|
|
267
|
+
return lines.join("\n")
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function renderReplLogo({ theme, columns = null }) {
|
|
271
|
+
const width = Number.isFinite(columns) ? Math.max(60, Math.min(Number(columns), 220)) : terminalWidth()
|
|
272
|
+
const rawLogo = [
|
|
273
|
+
"██╗ ██╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗",
|
|
274
|
+
"██║ ██╔╝ ██║ ██╔╝ ██╔════╝ ██╔═══██╗ ██╔══██╗ ██╔════╝",
|
|
275
|
+
"█████╔╝ █████╔╝ ██║ ██║ ██║ ██║ ██║ █████╗ ",
|
|
276
|
+
"██╔═██╗ ██╔═██╗ ██║ ██║ ██║ ██║ ██║ ██╔══╝ ",
|
|
277
|
+
"██║ ██╗ ██║ ██╗ ╚██████╗ ╚██████╔╝ ██████╔╝ ███████╗",
|
|
278
|
+
"╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝"
|
|
279
|
+
]
|
|
280
|
+
const wave = [
|
|
281
|
+
"#4af5f0", "#3de8f5", "#30dbfa", "#38c8ff", "#40b5ff",
|
|
282
|
+
"#58a0ff", "#708bff", "#8876ff", "#a061ff", "#b84cff",
|
|
283
|
+
"#d037ff", "#e828f0", "#f034d0", "#f040b0", "#f04c90",
|
|
284
|
+
"#f040b0", "#f034d0", "#e828f0", "#d037ff", "#b84cff",
|
|
285
|
+
"#a061ff", "#8876ff", "#708bff", "#58a0ff", "#40b5ff",
|
|
286
|
+
"#38c8ff", "#30dbfa", "#3de8f5"
|
|
287
|
+
]
|
|
288
|
+
const coreLines = rawLogo.map((line, row) => {
|
|
289
|
+
let out = ""
|
|
290
|
+
for (let col = 0; col < line.length; col++) {
|
|
291
|
+
const ch = line[col]
|
|
292
|
+
if (ch === " ") { out += " "; continue }
|
|
293
|
+
const waveIdx = (col + row * 3) % wave.length
|
|
294
|
+
out += paint(ch, wave[waveIdx], { bold: true })
|
|
295
|
+
}
|
|
296
|
+
return out
|
|
297
|
+
})
|
|
298
|
+
coreLines.push(paint("AI Coding Agent", theme.base.fg, { bold: true }))
|
|
299
|
+
coreLines.push(paint("Type /status to open Workspace & Runtime panel", theme.base.muted))
|
|
300
|
+
|
|
301
|
+
const mascotRaw = [
|
|
302
|
+
" /\\ ",
|
|
303
|
+
" /__\\ ",
|
|
304
|
+
" /|[]|\\ ",
|
|
305
|
+
" /_|__|_\\ ",
|
|
306
|
+
" /||\\ ",
|
|
307
|
+
" /_||_\\ ",
|
|
308
|
+
" /\\ "
|
|
309
|
+
]
|
|
310
|
+
const mascotPalette = [
|
|
311
|
+
"#6ec1ff",
|
|
312
|
+
"#52b7ff",
|
|
313
|
+
"#36d8d3",
|
|
314
|
+
"#3fd487",
|
|
315
|
+
"#f1c55b",
|
|
316
|
+
"#f39b52",
|
|
317
|
+
"#ff7f6e"
|
|
318
|
+
]
|
|
319
|
+
const mascotLines = mascotRaw.map((line, idx) => paint(line, mascotPalette[idx % mascotPalette.length], { bold: true }))
|
|
320
|
+
|
|
321
|
+
// Narrow terminals: keep pure centered logo, avoid cramped side art.
|
|
322
|
+
if (width < 96) {
|
|
323
|
+
const lines = coreLines.map((line) => centerLine(line, width))
|
|
324
|
+
return lines.join("\n")
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Three-column layout keeps KKCODE visually centered in the full terminal:
|
|
328
|
+
// [mascot] [center logo block] [symmetric spacer]
|
|
329
|
+
const mascotWidth = Math.max(...mascotRaw.map((line) => visibleWidth(line)))
|
|
330
|
+
const sideWidth = mascotWidth + 2
|
|
331
|
+
const centerWidth = Math.max(24, width - (sideWidth * 2))
|
|
332
|
+
|
|
333
|
+
const rows = Math.max(coreLines.length, mascotLines.length)
|
|
334
|
+
const mascotTopPad = Math.max(0, Math.floor((rows - mascotLines.length) / 2))
|
|
335
|
+
const logoTopPad = Math.max(0, Math.floor((rows - coreLines.length) / 2))
|
|
336
|
+
const lines = []
|
|
337
|
+
|
|
338
|
+
for (let i = 0; i < rows; i++) {
|
|
339
|
+
const mascotIdx = i - mascotTopPad
|
|
340
|
+
const logoIdx = i - logoTopPad
|
|
341
|
+
const left = mascotIdx >= 0 && mascotIdx < mascotLines.length ? mascotLines[mascotIdx] : ""
|
|
342
|
+
const mid = logoIdx >= 0 && logoIdx < coreLines.length ? centerLine(coreLines[logoIdx], centerWidth) : ""
|
|
343
|
+
const leftCell = padCell(left, sideWidth)
|
|
344
|
+
const midCell = padCell(mid, centerWidth)
|
|
345
|
+
const rightCell = " ".repeat(sideWidth)
|
|
346
|
+
lines.push(`${leftCell}${midCell}${rightCell}`)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return lines.join("\n")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function renderStartupHint(recentSessions = []) {
|
|
353
|
+
if (!recentSessions.length) return ""
|
|
354
|
+
const last = recentSessions[0]
|
|
355
|
+
const age = ageLabel(Date.now() - last.updatedAt)
|
|
356
|
+
return `last session: ${last.id} (${last.mode}, ${age})\n quick resume: /r ${last.id.slice(0, 12)}`
|
|
357
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { access, readFile } from "node:fs/promises"
|
|
3
|
+
import YAML from "yaml"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PRICING = {
|
|
6
|
+
currency: "USD",
|
|
7
|
+
per_tokens: 1000000,
|
|
8
|
+
models: {
|
|
9
|
+
"claude-opus-4-6": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
|
|
10
|
+
"claude-opus-4-5": { input: 5, output: 25, cache_read: 0.5, cache_write: 6.25 },
|
|
11
|
+
"claude-opus-4-1": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
12
|
+
"claude-opus-4": { input: 15, output: 75, cache_read: 1.5, cache_write: 18.75 },
|
|
13
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
14
|
+
"claude-sonnet-4-5": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
15
|
+
"claude-sonnet-4": { input: 3, output: 15, cache_read: 0.3, cache_write: 3.75 },
|
|
16
|
+
"claude-haiku-4-5": { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 },
|
|
17
|
+
"claude-haiku-3-5": { input: 0.8, output: 4, cache_read: 0.08, cache_write: 1 },
|
|
18
|
+
"gpt-5.3-codex": { input: 15, output: 60, cache_read: 7.5, cache_write: 15 },
|
|
19
|
+
"gpt-4o": { input: 2.5, output: 10, cache_read: 1.25, cache_write: 2.5 },
|
|
20
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6, cache_read: 0.075, cache_write: 0.15 },
|
|
21
|
+
"deepseek-chat": { input: 0.27, output: 1.1, cache_read: 0.07, cache_write: 0.27 },
|
|
22
|
+
"deepseek-coder": { input: 0.27, output: 1.1, cache_read: 0.07, cache_write: 0.27 },
|
|
23
|
+
"deepseek-v3.1-terminus": { input: 0.55, output: 1.65, cache_read: 0.07, cache_write: 0.55 },
|
|
24
|
+
"deepseek-v3.2": { input: 0.28, output: 0.41, cache_read: 0.03, cache_write: 0.28 },
|
|
25
|
+
"kimi-k2.5": { input: 0.55, output: 2.9, cache_read: 0, cache_write: 0 },
|
|
26
|
+
"kimi-k2-0905": { input: 0.55, output: 2.2, cache_read: 0.14, cache_write: 0 },
|
|
27
|
+
"qwen-plus": { input: 0.11, output: 1.11, cache_read: 0.02, cache_write: 0 },
|
|
28
|
+
"qwen-max": { input: 0.33, output: 1.33, cache_read: 0.07, cache_write: 0 },
|
|
29
|
+
"qwen-turbo": { input: 0.04, output: 0.08, cache_read: 0.01, cache_write: 0 },
|
|
30
|
+
"qwen3-coder-plus": { input: 0.55, output: 2.2, cache_read: 0.11, cache_write: 0 },
|
|
31
|
+
"qwen3-coder-flash": { input: 0.14, output: 0.55, cache_read: 0.03, cache_write: 0 },
|
|
32
|
+
"qwen3-coder-480b-a35b": { input: 0.83, output: 3.3, cache_read: 0.17, cache_write: 0 },
|
|
33
|
+
"doubao-seed-2.0-code": { input: 0.44, output: 2.2, cache_read: 0, cache_write: 0 },
|
|
34
|
+
"doubao-seed-2.0-pro": { input: 0.44, output: 2.2, cache_read: 0, cache_write: 0 },
|
|
35
|
+
"doubao-seed-1.8": { input: 0.11, output: 1.1, cache_read: 0, cache_write: 0 },
|
|
36
|
+
"doubao-seed-code": { input: 0.17, output: 1.1, cache_read: 0, cache_write: 0 },
|
|
37
|
+
"minimax-m2.5": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
38
|
+
"minimax-m2.5-highspeed": { input: 0.58, output: 2.32, cache_read: 0.03, cache_write: 0 },
|
|
39
|
+
"minimax-m2.1": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
40
|
+
"minimax-m2": { input: 0.29, output: 1.16, cache_read: 0.03, cache_write: 0 },
|
|
41
|
+
"glm-5": { input: 0.55, output: 3.05, cache_read: 0, cache_write: 0 },
|
|
42
|
+
"glm-4.7": { input: 0.41, output: 1.93, cache_read: 0, cache_write: 0 },
|
|
43
|
+
"glm-4.6": { input: 0.28, output: 1.1, cache_read: 0, cache_write: 0 }
|
|
44
|
+
},
|
|
45
|
+
default: {
|
|
46
|
+
input: 3,
|
|
47
|
+
output: 15,
|
|
48
|
+
cache_read: 0.3,
|
|
49
|
+
cache_write: 3.75
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function exists(file) {
|
|
54
|
+
try {
|
|
55
|
+
await access(file)
|
|
56
|
+
return true
|
|
57
|
+
} catch {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parse(file, raw) {
|
|
63
|
+
if (file.endsWith(".json")) return JSON.parse(raw)
|
|
64
|
+
return YAML.parse(raw)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolvePricingPath(configState) {
|
|
68
|
+
const projectPath = configState.source.projectRaw?.usage?.pricing_file
|
|
69
|
+
if (typeof projectPath === "string" && projectPath.trim()) {
|
|
70
|
+
return path.resolve(configState.source.projectDir ?? process.cwd(), projectPath)
|
|
71
|
+
}
|
|
72
|
+
const userPath = configState.source.userRaw?.usage?.pricing_file
|
|
73
|
+
if (typeof userPath === "string" && userPath.trim()) {
|
|
74
|
+
return path.resolve(configState.source.userDir ?? process.cwd(), userPath)
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function loadPricing(configState) {
|
|
80
|
+
const file = resolvePricingPath(configState)
|
|
81
|
+
if (!file || !(await exists(file))) {
|
|
82
|
+
return { pricing: DEFAULT_PRICING, source: "default", errors: [] }
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const raw = await readFile(file, "utf8")
|
|
86
|
+
const parsed = parse(file, raw)
|
|
87
|
+
const pricing = {
|
|
88
|
+
...DEFAULT_PRICING,
|
|
89
|
+
...parsed,
|
|
90
|
+
default: { ...DEFAULT_PRICING.default, ...(parsed.default ?? {}) }
|
|
91
|
+
}
|
|
92
|
+
return { pricing, source: file, errors: [] }
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return { pricing: DEFAULT_PRICING, source: "default", errors: [`${file}: ${error.message}`] }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findPricingEntry(models, model) {
|
|
99
|
+
if (models[model]) return models[model]
|
|
100
|
+
// Fuzzy: try prefix match (e.g. "claude-opus-4-6-20250601" → "claude-opus-4-6")
|
|
101
|
+
const m = String(model).toLowerCase()
|
|
102
|
+
for (const key of Object.keys(models)) {
|
|
103
|
+
if (m.startsWith(key)) return models[key]
|
|
104
|
+
}
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function calculateCost(pricing, model, usage) {
|
|
109
|
+
const entry = findPricingEntry(pricing.models, model) ?? pricing.default
|
|
110
|
+
const per = pricing.per_tokens || 1000000
|
|
111
|
+
// All providers normalize input to non-cached tokens only (see provider/*.mjs)
|
|
112
|
+
const amount =
|
|
113
|
+
((usage.input || 0) * (entry.input || 0) +
|
|
114
|
+
(usage.output || 0) * (entry.output || 0) +
|
|
115
|
+
(usage.cacheRead || 0) * (entry.cache_read || 0) +
|
|
116
|
+
(usage.cacheWrite || 0) * (entry.cache_write || 0)) /
|
|
117
|
+
per
|
|
118
|
+
const savings = ((usage.cacheRead || 0) * ((entry.input || 0) - (entry.cache_read || 0))) / per
|
|
119
|
+
const unknown = !findPricingEntry(pricing.models, model)
|
|
120
|
+
return { amount, savings, unknown, currency: pricing.currency }
|
|
121
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ensureUserRoot, usageStorePath } from "../storage/paths.mjs"
|
|
2
|
+
import { readJson, writeJson } from "../storage/json-store.mjs"
|
|
3
|
+
|
|
4
|
+
export function emptyUsage() {
|
|
5
|
+
return {
|
|
6
|
+
input: 0,
|
|
7
|
+
output: 0,
|
|
8
|
+
cacheRead: 0,
|
|
9
|
+
cacheWrite: 0,
|
|
10
|
+
cost: 0,
|
|
11
|
+
turns: 0
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function addUsage(target, delta, cost) {
|
|
16
|
+
target.input += delta.input || 0
|
|
17
|
+
target.output += delta.output || 0
|
|
18
|
+
target.cacheRead += delta.cacheRead || 0
|
|
19
|
+
target.cacheWrite += delta.cacheWrite || 0
|
|
20
|
+
target.cost += cost || 0
|
|
21
|
+
target.turns += 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function todayKey() {
|
|
25
|
+
return new Date().toISOString().slice(0, 10) // "YYYY-MM-DD"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function defaultStore() {
|
|
29
|
+
return {
|
|
30
|
+
updatedAt: Date.now(),
|
|
31
|
+
globalDay: todayKey(),
|
|
32
|
+
global: emptyUsage(),
|
|
33
|
+
sessions: {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function maybeRotateGlobal(store) {
|
|
38
|
+
const today = todayKey()
|
|
39
|
+
if (store.globalDay && store.globalDay !== today) {
|
|
40
|
+
store.global = emptyUsage()
|
|
41
|
+
store.globalDay = today
|
|
42
|
+
}
|
|
43
|
+
if (!store.globalDay) store.globalDay = today
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function readUsageStore() {
|
|
47
|
+
await ensureUserRoot()
|
|
48
|
+
return readJson(usageStorePath(), defaultStore())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function persist(store) {
|
|
52
|
+
store.updatedAt = Date.now()
|
|
53
|
+
await writeJson(usageStorePath(), store)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function recordTurn({ sessionId, usage, cost }) {
|
|
57
|
+
const store = await readUsageStore()
|
|
58
|
+
maybeRotateGlobal(store)
|
|
59
|
+
if (!store.sessions[sessionId]) store.sessions[sessionId] = emptyUsage()
|
|
60
|
+
addUsage(store.sessions[sessionId], usage, cost)
|
|
61
|
+
addUsage(store.global, usage, cost)
|
|
62
|
+
await persist(store)
|
|
63
|
+
return {
|
|
64
|
+
turn: {
|
|
65
|
+
input: usage.input || 0,
|
|
66
|
+
output: usage.output || 0,
|
|
67
|
+
cacheRead: usage.cacheRead || 0,
|
|
68
|
+
cacheWrite: usage.cacheWrite || 0,
|
|
69
|
+
cost: cost || 0,
|
|
70
|
+
turns: 1
|
|
71
|
+
},
|
|
72
|
+
session: store.sessions[sessionId],
|
|
73
|
+
global: store.global
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function resetUsage(sessionId = null) {
|
|
78
|
+
if (!sessionId) {
|
|
79
|
+
await persist(defaultStore())
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
const store = await readUsageStore()
|
|
83
|
+
delete store.sessions[sessionId]
|
|
84
|
+
store.global = emptyUsage()
|
|
85
|
+
for (const session of Object.values(store.sessions)) {
|
|
86
|
+
store.global.input += session.input
|
|
87
|
+
store.global.output += session.output
|
|
88
|
+
store.global.cacheRead += session.cacheRead
|
|
89
|
+
store.global.cacheWrite += session.cacheWrite
|
|
90
|
+
store.global.cost += session.cost
|
|
91
|
+
store.global.turns += session.turns
|
|
92
|
+
}
|
|
93
|
+
await persist(store)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function exportUsageCsv() {
|
|
97
|
+
const store = await readUsageStore()
|
|
98
|
+
const rows = [["scope", "sessionId", "input", "output", "cacheRead", "cacheWrite", "cost", "turns"]]
|
|
99
|
+
rows.push([
|
|
100
|
+
"global",
|
|
101
|
+
"",
|
|
102
|
+
store.global.input,
|
|
103
|
+
store.global.output,
|
|
104
|
+
store.global.cacheRead,
|
|
105
|
+
store.global.cacheWrite,
|
|
106
|
+
store.global.cost,
|
|
107
|
+
store.global.turns
|
|
108
|
+
])
|
|
109
|
+
for (const [sessionId, usage] of Object.entries(store.sessions)) {
|
|
110
|
+
rows.push(["session", sessionId, usage.input, usage.output, usage.cacheRead, usage.cacheWrite, usage.cost, usage.turns])
|
|
111
|
+
}
|
|
112
|
+
return rows.map((row) => row.join(",")).join("\n") + "\n"
|
|
113
|
+
}
|