codeblog-app 2.3.1 → 2.3.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/package.json +8 -73
- package/drizzle/0000_init.sql +0 -34
- package/drizzle/meta/_journal.json +0 -13
- package/drizzle.config.ts +0 -10
- package/src/ai/__tests__/chat.test.ts +0 -188
- package/src/ai/__tests__/compat.test.ts +0 -46
- package/src/ai/__tests__/home.ai-stream.integration.test.ts +0 -77
- package/src/ai/__tests__/provider-registry.test.ts +0 -61
- package/src/ai/__tests__/provider.test.ts +0 -238
- package/src/ai/__tests__/stream-events.test.ts +0 -152
- package/src/ai/__tests__/tools.test.ts +0 -93
- package/src/ai/chat.ts +0 -336
- package/src/ai/configure.ts +0 -143
- package/src/ai/models.ts +0 -26
- package/src/ai/provider-registry.ts +0 -150
- package/src/ai/provider.ts +0 -264
- package/src/ai/stream-events.ts +0 -64
- package/src/ai/tools.ts +0 -118
- package/src/ai/types.ts +0 -105
- package/src/auth/index.ts +0 -49
- package/src/auth/oauth.ts +0 -123
- package/src/cli/__tests__/commands.test.ts +0 -229
- package/src/cli/cmd/agent.ts +0 -97
- package/src/cli/cmd/ai.ts +0 -10
- package/src/cli/cmd/chat.ts +0 -190
- package/src/cli/cmd/comment.ts +0 -67
- package/src/cli/cmd/config.ts +0 -153
- package/src/cli/cmd/feed.ts +0 -53
- package/src/cli/cmd/forum.ts +0 -106
- package/src/cli/cmd/login.ts +0 -45
- package/src/cli/cmd/logout.ts +0 -12
- package/src/cli/cmd/me.ts +0 -188
- package/src/cli/cmd/post.ts +0 -25
- package/src/cli/cmd/publish.ts +0 -64
- package/src/cli/cmd/scan.ts +0 -78
- package/src/cli/cmd/search.ts +0 -35
- package/src/cli/cmd/setup.ts +0 -622
- package/src/cli/cmd/tui.ts +0 -20
- package/src/cli/cmd/uninstall.ts +0 -281
- package/src/cli/cmd/update.ts +0 -123
- package/src/cli/cmd/vote.ts +0 -50
- package/src/cli/cmd/whoami.ts +0 -18
- package/src/cli/mcp-print.ts +0 -6
- package/src/cli/ui.ts +0 -357
- package/src/config/index.ts +0 -92
- package/src/flag/index.ts +0 -23
- package/src/global/index.ts +0 -38
- package/src/id/index.ts +0 -20
- package/src/index.ts +0 -203
- package/src/mcp/__tests__/client.test.ts +0 -149
- package/src/mcp/__tests__/e2e.ts +0 -331
- package/src/mcp/__tests__/integration.ts +0 -148
- package/src/mcp/client.ts +0 -118
- package/src/server/index.ts +0 -48
- package/src/storage/chat.ts +0 -73
- package/src/storage/db.ts +0 -85
- package/src/storage/schema.sql.ts +0 -39
- package/src/storage/schema.ts +0 -1
- package/src/tui/__tests__/input-intent.test.ts +0 -27
- package/src/tui/__tests__/stream-assembler.test.ts +0 -33
- package/src/tui/ai-stream.ts +0 -28
- package/src/tui/app.tsx +0 -210
- package/src/tui/commands.ts +0 -220
- package/src/tui/context/exit.tsx +0 -15
- package/src/tui/context/helper.tsx +0 -25
- package/src/tui/context/route.tsx +0 -24
- package/src/tui/context/theme.tsx +0 -471
- package/src/tui/input-intent.ts +0 -26
- package/src/tui/routes/home.tsx +0 -1060
- package/src/tui/routes/model.tsx +0 -210
- package/src/tui/routes/notifications.tsx +0 -87
- package/src/tui/routes/post.tsx +0 -102
- package/src/tui/routes/search.tsx +0 -105
- package/src/tui/routes/setup.tsx +0 -267
- package/src/tui/routes/trending.tsx +0 -107
- package/src/tui/stream-assembler.ts +0 -49
- package/src/util/__tests__/context.test.ts +0 -31
- package/src/util/__tests__/lazy.test.ts +0 -37
- package/src/util/context.ts +0 -23
- package/src/util/error.ts +0 -46
- package/src/util/lazy.ts +0 -18
- package/src/util/log.ts +0 -144
- package/tsconfig.json +0 -11
package/src/cli/ui.ts
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
import { EOL } from "os"
|
|
2
|
-
|
|
3
|
-
export namespace UI {
|
|
4
|
-
export const Style = {
|
|
5
|
-
TEXT_HIGHLIGHT: "\x1b[96m",
|
|
6
|
-
TEXT_HIGHLIGHT_BOLD: "\x1b[96m\x1b[1m",
|
|
7
|
-
TEXT_DIM: "\x1b[90m",
|
|
8
|
-
TEXT_DIM_BOLD: "\x1b[90m\x1b[1m",
|
|
9
|
-
TEXT_NORMAL: "\x1b[0m",
|
|
10
|
-
TEXT_NORMAL_BOLD: "\x1b[1m",
|
|
11
|
-
TEXT_WARNING: "\x1b[93m",
|
|
12
|
-
TEXT_WARNING_BOLD: "\x1b[93m\x1b[1m",
|
|
13
|
-
TEXT_DANGER: "\x1b[91m",
|
|
14
|
-
TEXT_DANGER_BOLD: "\x1b[91m\x1b[1m",
|
|
15
|
-
TEXT_SUCCESS: "\x1b[92m",
|
|
16
|
-
TEXT_SUCCESS_BOLD: "\x1b[92m\x1b[1m",
|
|
17
|
-
TEXT_INFO: "\x1b[94m",
|
|
18
|
-
TEXT_INFO_BOLD: "\x1b[94m\x1b[1m",
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function println(...message: string[]) {
|
|
22
|
-
print(...message)
|
|
23
|
-
Bun.stderr.write(EOL)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function print(...message: string[]) {
|
|
27
|
-
Bun.stderr.write(message.join(" "))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function logo() {
|
|
31
|
-
const orange = "\x1b[38;5;214m"
|
|
32
|
-
const cyan = "\x1b[36m"
|
|
33
|
-
const reset = "\x1b[0m"
|
|
34
|
-
return [
|
|
35
|
-
"",
|
|
36
|
-
`${orange} ██████╗ ██████╗ ██████╗ ███████╗${cyan}██████╗ ██╗ ██████╗ ██████╗ ${reset}`,
|
|
37
|
-
`${orange} ██╔════╝██╔═══██╗██╔══██╗██╔════╝${cyan}██╔══██╗██║ ██╔═══██╗██╔════╝ ${reset}`,
|
|
38
|
-
`${orange} ██║ ██║ ██║██║ ██║█████╗ ${cyan}██████╔╝██║ ██║ ██║██║ ███╗${reset}`,
|
|
39
|
-
`${orange} ██║ ██║ ██║██║ ██║██╔══╝ ${cyan}██╔══██╗██║ ██║ ██║██║ ██║${reset}`,
|
|
40
|
-
`${orange} ╚██████╗╚██████╔╝██████╔╝███████╗${cyan}██████╔╝███████╗╚██████╔╝╚██████╔╝${reset}`,
|
|
41
|
-
`${orange} ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝${cyan}╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ${reset}`,
|
|
42
|
-
"",
|
|
43
|
-
` ${Style.TEXT_DIM}The AI-powered coding forum in your terminal${Style.TEXT_NORMAL}`,
|
|
44
|
-
"",
|
|
45
|
-
].join(EOL)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function error(message: string) {
|
|
49
|
-
println(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function success(message: string) {
|
|
53
|
-
println(Style.TEXT_SUCCESS_BOLD + "✓ " + Style.TEXT_NORMAL + message)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function info(message: string) {
|
|
57
|
-
println(Style.TEXT_INFO + "ℹ " + Style.TEXT_NORMAL + message)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function warn(message: string) {
|
|
61
|
-
println(Style.TEXT_WARNING + "⚠ " + Style.TEXT_NORMAL + message)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export async function input(prompt: string): Promise<string> {
|
|
65
|
-
const readline = require("readline")
|
|
66
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
67
|
-
return new Promise((resolve) => {
|
|
68
|
-
rl.question(prompt, (answer: string) => {
|
|
69
|
-
rl.close()
|
|
70
|
-
resolve(answer.trim())
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Input with ESC support. Returns null if user presses Escape, otherwise the input string.
|
|
77
|
-
*/
|
|
78
|
-
export async function inputWithEscape(prompt: string): Promise<string | null> {
|
|
79
|
-
const stdin = process.stdin
|
|
80
|
-
process.stderr.write(prompt)
|
|
81
|
-
|
|
82
|
-
return new Promise((resolve) => {
|
|
83
|
-
const wasRaw = stdin.isRaw
|
|
84
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
|
|
85
|
-
|
|
86
|
-
let buf = ""
|
|
87
|
-
let paste = false
|
|
88
|
-
let onData: ((ch: Buffer) => void) = () => {}
|
|
89
|
-
|
|
90
|
-
const restore = () => {
|
|
91
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
92
|
-
stdin.removeListener("data", onData)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const readClipboard = () => {
|
|
96
|
-
if (process.platform !== "darwin") return ""
|
|
97
|
-
try {
|
|
98
|
-
const out = Bun.spawnSync(["pbpaste"])
|
|
99
|
-
if (out.exitCode !== 0) return ""
|
|
100
|
-
return out.stdout.toString().replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "")
|
|
101
|
-
} catch {
|
|
102
|
-
return ""
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const append = (text: string) => {
|
|
107
|
-
if (!text) return
|
|
108
|
-
buf += text
|
|
109
|
-
process.stderr.write(text)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
onData = (ch: Buffer) => {
|
|
113
|
-
const c = ch.toString("utf8")
|
|
114
|
-
if (c === "\u0003") {
|
|
115
|
-
// Ctrl+C
|
|
116
|
-
restore()
|
|
117
|
-
process.exit(130)
|
|
118
|
-
}
|
|
119
|
-
if (!paste && c === "\x1b") {
|
|
120
|
-
// Escape
|
|
121
|
-
restore()
|
|
122
|
-
process.stderr.write("\n")
|
|
123
|
-
resolve(null)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
if (c === "\x16" || c === "\x1bv") {
|
|
127
|
-
const clip = readClipboard()
|
|
128
|
-
if (clip) append(clip)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
let text = c
|
|
133
|
-
if (text.includes("\x1b[200~")) {
|
|
134
|
-
paste = true
|
|
135
|
-
text = text.replace(/\x1b\[200~/g, "")
|
|
136
|
-
}
|
|
137
|
-
if (text.includes("\x1b[201~")) {
|
|
138
|
-
paste = false
|
|
139
|
-
text = text.replace(/\x1b\[201~/g, "")
|
|
140
|
-
}
|
|
141
|
-
text = text.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b./g, "")
|
|
142
|
-
|
|
143
|
-
if (!paste && (text === "\r" || text === "\n")) {
|
|
144
|
-
// Enter
|
|
145
|
-
restore()
|
|
146
|
-
process.stderr.write("\n")
|
|
147
|
-
resolve(buf)
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
if (!paste && (text === "\u007f" || text === "\b")) {
|
|
151
|
-
// Backspace
|
|
152
|
-
if (buf.length > 0) {
|
|
153
|
-
buf = buf.slice(0, -1)
|
|
154
|
-
process.stderr.write("\b \b")
|
|
155
|
-
}
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
// Regular character
|
|
159
|
-
const clean = text.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "")
|
|
160
|
-
append(clean)
|
|
161
|
-
}
|
|
162
|
-
stdin.on("data", onData)
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export async function password(prompt: string): Promise<string> {
|
|
167
|
-
const readline = require("readline")
|
|
168
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stderr, terminal: true })
|
|
169
|
-
return new Promise((resolve) => {
|
|
170
|
-
// Disable echoing by writing the prompt manually and muting stdout
|
|
171
|
-
process.stderr.write(prompt)
|
|
172
|
-
const stdin = process.stdin
|
|
173
|
-
const wasRaw = stdin.isRaw
|
|
174
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
|
|
175
|
-
|
|
176
|
-
let buf = ""
|
|
177
|
-
const onData = (ch: Buffer) => {
|
|
178
|
-
const c = ch.toString("utf8")
|
|
179
|
-
if (c === "\n" || c === "\r" || c === "\u0004") {
|
|
180
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
181
|
-
stdin.removeListener("data", onData)
|
|
182
|
-
process.stderr.write("\n")
|
|
183
|
-
rl.close()
|
|
184
|
-
resolve(buf.trim())
|
|
185
|
-
} else if (c === "\u007f" || c === "\b") {
|
|
186
|
-
// backspace
|
|
187
|
-
if (buf.length > 0) buf = buf.slice(0, -1)
|
|
188
|
-
} else if (c === "\u0003") {
|
|
189
|
-
// Ctrl+C
|
|
190
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
191
|
-
stdin.removeListener("data", onData)
|
|
192
|
-
rl.close()
|
|
193
|
-
process.exit(130)
|
|
194
|
-
} else {
|
|
195
|
-
buf += c
|
|
196
|
-
process.stderr.write("*")
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
stdin.on("data", onData)
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export async function select(prompt: string, options: string[]): Promise<number> {
|
|
204
|
-
if (options.length === 0) return 0
|
|
205
|
-
|
|
206
|
-
const stdin = process.stdin
|
|
207
|
-
const wasRaw = stdin.isRaw
|
|
208
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
|
|
209
|
-
process.stderr.write("\x1b[?25l")
|
|
210
|
-
|
|
211
|
-
let idx = 0
|
|
212
|
-
let drawnRows = 0
|
|
213
|
-
const maxRows = 12
|
|
214
|
-
let onData: ((ch: Buffer) => void) = () => {}
|
|
215
|
-
|
|
216
|
-
const stripAnsi = (text: string) => text.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b./g, "")
|
|
217
|
-
const rowCount = (line: string) => {
|
|
218
|
-
const cols = Math.max(20, process.stderr.columns || 80)
|
|
219
|
-
const len = Array.from(stripAnsi(line)).length
|
|
220
|
-
return Math.max(1, Math.ceil((len || 1) / cols))
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const draw = () => {
|
|
224
|
-
if (drawnRows > 1) process.stderr.write(`\x1b[${drawnRows - 1}F`)
|
|
225
|
-
process.stderr.write("\x1b[J")
|
|
226
|
-
|
|
227
|
-
const start = options.length <= maxRows ? 0 : Math.max(0, Math.min(idx - 4, options.length - maxRows))
|
|
228
|
-
const items = options.slice(start, start + maxRows)
|
|
229
|
-
const lines = [
|
|
230
|
-
prompt,
|
|
231
|
-
...items.map((item, i) => {
|
|
232
|
-
const active = start + i === idx
|
|
233
|
-
const marker = active ? `${Style.TEXT_HIGHLIGHT}●${Style.TEXT_NORMAL}` : "○"
|
|
234
|
-
const text = active ? `${Style.TEXT_NORMAL_BOLD}${item}${Style.TEXT_NORMAL}` : item
|
|
235
|
-
return ` ${marker} ${text}`
|
|
236
|
-
}),
|
|
237
|
-
options.length > maxRows
|
|
238
|
-
? ` ${Style.TEXT_DIM}${start > 0 ? "↑ more " : ""}${start + maxRows < options.length ? "↓ more" : ""}${Style.TEXT_NORMAL}`
|
|
239
|
-
: ` ${Style.TEXT_DIM}${Style.TEXT_NORMAL}`,
|
|
240
|
-
` ${Style.TEXT_DIM}Use ↑/↓ then Enter (Esc to cancel)${Style.TEXT_NORMAL}`,
|
|
241
|
-
]
|
|
242
|
-
process.stderr.write(lines.map((line) => `\x1b[2K\r${line}`).join("\n"))
|
|
243
|
-
drawnRows = lines.reduce((sum, line) => sum + rowCount(line), 0)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const restore = () => {
|
|
247
|
-
process.stderr.write("\x1b[?25h")
|
|
248
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
249
|
-
stdin.removeListener("data", onData)
|
|
250
|
-
process.stderr.write("\n")
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
draw()
|
|
254
|
-
|
|
255
|
-
return new Promise((resolve) => {
|
|
256
|
-
onData = (ch: Buffer) => {
|
|
257
|
-
const c = ch.toString("utf8")
|
|
258
|
-
if (c === "\u0003") {
|
|
259
|
-
restore()
|
|
260
|
-
process.exit(130)
|
|
261
|
-
}
|
|
262
|
-
if (c === "\r" || c === "\n") {
|
|
263
|
-
restore()
|
|
264
|
-
resolve(idx)
|
|
265
|
-
return
|
|
266
|
-
}
|
|
267
|
-
if (c === "\x1b") {
|
|
268
|
-
restore()
|
|
269
|
-
resolve(-1)
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
if (c === "k" || c.includes("\x1b[A") || c.includes("\x1bOA")) {
|
|
273
|
-
idx = (idx - 1 + options.length) % options.length
|
|
274
|
-
draw()
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
if (c === "j" || c.includes("\x1b[B") || c.includes("\x1bOB")) {
|
|
278
|
-
idx = (idx + 1) % options.length
|
|
279
|
-
draw()
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
stdin.on("data", onData)
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export async function waitKey(prompt: string, keys: string[]): Promise<string> {
|
|
288
|
-
const stdin = process.stdin
|
|
289
|
-
process.stderr.write(` ${Style.TEXT_DIM}${prompt}${Style.TEXT_NORMAL}`)
|
|
290
|
-
|
|
291
|
-
return new Promise((resolve) => {
|
|
292
|
-
const wasRaw = stdin.isRaw
|
|
293
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
|
|
294
|
-
|
|
295
|
-
const onData = (ch: Buffer) => {
|
|
296
|
-
const c = ch.toString("utf8")
|
|
297
|
-
if (c === "\u0003") {
|
|
298
|
-
// Ctrl+C
|
|
299
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
300
|
-
stdin.removeListener("data", onData)
|
|
301
|
-
process.exit(130)
|
|
302
|
-
}
|
|
303
|
-
const key = (c === "\r" || c === "\n") ? "enter" : c === "\x1b" ? "escape" : c.toLowerCase()
|
|
304
|
-
if (keys.includes(key)) {
|
|
305
|
-
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
306
|
-
stdin.removeListener("data", onData)
|
|
307
|
-
process.stderr.write("\n")
|
|
308
|
-
resolve(key)
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
stdin.on("data", onData)
|
|
312
|
-
})
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Wait for Enter key (or Esc to skip). Returns "enter" or "escape".
|
|
317
|
-
*/
|
|
318
|
-
export async function waitEnter(prompt?: string): Promise<"enter" | "escape"> {
|
|
319
|
-
return waitKey(prompt || "Press Enter to continue...", ["enter", "escape"]) as Promise<"enter" | "escape">
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Streaming typewriter effect — prints text character by character to stderr.
|
|
324
|
-
*/
|
|
325
|
-
export async function typeText(text: string, opts?: { charDelay?: number; prefix?: string }) {
|
|
326
|
-
const delay = opts?.charDelay ?? 12
|
|
327
|
-
const prefix = opts?.prefix ?? " "
|
|
328
|
-
Bun.stderr.write(prefix)
|
|
329
|
-
for (const ch of text) {
|
|
330
|
-
Bun.stderr.write(ch)
|
|
331
|
-
if (delay > 0) await Bun.sleep(delay)
|
|
332
|
-
}
|
|
333
|
-
Bun.stderr.write(EOL)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Clean markdown formatting from MCP tool output for CLI display.
|
|
338
|
-
* Removes **bold**, *italic*, keeps structure readable.
|
|
339
|
-
*/
|
|
340
|
-
export function cleanMarkdown(text: string): string {
|
|
341
|
-
return text
|
|
342
|
-
.replace(/\*\*(.+?)\*\*/g, "$1") // **bold** → bold
|
|
343
|
-
.replace(/\*(.+?)\*/g, "$1") // *italic* → italic
|
|
344
|
-
.replace(/`([^`]+)`/g, "$1") // `code` → code
|
|
345
|
-
.replace(/^#{1,6}\s+/gm, "") // ### heading → heading
|
|
346
|
-
.replace(/^---+$/gm, "──────────────────────────────────") // horizontal rule
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Print a horizontal divider.
|
|
351
|
-
*/
|
|
352
|
-
export function divider() {
|
|
353
|
-
println("")
|
|
354
|
-
println(` ${Style.TEXT_DIM}──────────────────────────────────${Style.TEXT_NORMAL}`)
|
|
355
|
-
println("")
|
|
356
|
-
}
|
|
357
|
-
}
|
package/src/config/index.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import path from "path"
|
|
2
|
-
import { chmod, writeFile } from "fs/promises"
|
|
3
|
-
import { Global } from "../global"
|
|
4
|
-
|
|
5
|
-
const CONFIG_FILE = path.join(Global.Path.config, "config.json")
|
|
6
|
-
|
|
7
|
-
export namespace Config {
|
|
8
|
-
export type ModelApi = "anthropic" | "openai" | "google" | "openai-compatible"
|
|
9
|
-
export type CompatProfile = "anthropic" | "openai" | "openai-compatible" | "google"
|
|
10
|
-
|
|
11
|
-
export interface FeatureFlags {
|
|
12
|
-
ai_provider_registry_v2?: boolean
|
|
13
|
-
ai_onboarding_wizard_v2?: boolean
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ProviderConfig {
|
|
17
|
-
api_key: string
|
|
18
|
-
base_url?: string
|
|
19
|
-
api?: ModelApi
|
|
20
|
-
compat_profile?: CompatProfile
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface CodeblogConfig {
|
|
24
|
-
api_url: string
|
|
25
|
-
api_key?: string
|
|
26
|
-
token?: string
|
|
27
|
-
model?: string
|
|
28
|
-
default_provider?: string
|
|
29
|
-
default_language?: string
|
|
30
|
-
activeAgent?: string
|
|
31
|
-
providers?: Record<string, ProviderConfig>
|
|
32
|
-
feature_flags?: FeatureFlags
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const defaults: CodeblogConfig = {
|
|
36
|
-
api_url: "https://codeblog.ai",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const filepath = CONFIG_FILE
|
|
40
|
-
|
|
41
|
-
const FEATURE_FLAG_ENV: Record<keyof FeatureFlags, string> = {
|
|
42
|
-
ai_provider_registry_v2: "CODEBLOG_AI_PROVIDER_REGISTRY_V2",
|
|
43
|
-
ai_onboarding_wizard_v2: "CODEBLOG_AI_ONBOARDING_WIZARD_V2",
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function load(): Promise<CodeblogConfig> {
|
|
47
|
-
const file = Bun.file(CONFIG_FILE)
|
|
48
|
-
const data = await file.json().catch(() => ({}))
|
|
49
|
-
return { ...defaults, ...data }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function save(config: Partial<CodeblogConfig>) {
|
|
53
|
-
const current = await load()
|
|
54
|
-
const merged = { ...current, ...config }
|
|
55
|
-
await writeFile(CONFIG_FILE, JSON.stringify(merged, null, 2))
|
|
56
|
-
await chmod(CONFIG_FILE, 0o600).catch(() => {})
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function url() {
|
|
60
|
-
return process.env.CODEBLOG_URL || (await load()).api_url || "https://codeblog.ai"
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function key() {
|
|
64
|
-
return process.env.CODEBLOG_API_KEY || (await load()).api_key || ""
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function token() {
|
|
68
|
-
return process.env.CODEBLOG_TOKEN || (await load()).token || ""
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function language() {
|
|
72
|
-
return process.env.CODEBLOG_LANGUAGE || (await load()).default_language
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function parseBool(raw: string | undefined): boolean | undefined {
|
|
76
|
-
if (!raw) return undefined
|
|
77
|
-
const v = raw.trim().toLowerCase()
|
|
78
|
-
if (["1", "true", "yes", "on"].includes(v)) return true
|
|
79
|
-
if (["0", "false", "no", "off"].includes(v)) return false
|
|
80
|
-
return undefined
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function envFlagName(flag: keyof FeatureFlags): string {
|
|
84
|
-
return FEATURE_FLAG_ENV[flag]
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export async function featureEnabled(flag: keyof FeatureFlags): Promise<boolean> {
|
|
88
|
-
const env = parseBool(process.env[FEATURE_FLAG_ENV[flag]])
|
|
89
|
-
if (env !== undefined) return env
|
|
90
|
-
return !!(await load()).feature_flags?.[flag]
|
|
91
|
-
}
|
|
92
|
-
}
|
package/src/flag/index.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
function truthy(key: string) {
|
|
2
|
-
const value = process.env[key]?.toLowerCase()
|
|
3
|
-
return value === "true" || value === "1"
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export namespace Flag {
|
|
7
|
-
export const CODEBLOG_URL = process.env["CODEBLOG_URL"]
|
|
8
|
-
export const CODEBLOG_API_KEY = process.env["CODEBLOG_API_KEY"]
|
|
9
|
-
export const CODEBLOG_TOKEN = process.env["CODEBLOG_TOKEN"]
|
|
10
|
-
export const CODEBLOG_DEBUG = truthy("CODEBLOG_DEBUG")
|
|
11
|
-
export const CODEBLOG_DISABLE_AUTOUPDATE = truthy("CODEBLOG_DISABLE_AUTOUPDATE")
|
|
12
|
-
export const CODEBLOG_DISABLE_SCANNER_CACHE = truthy("CODEBLOG_DISABLE_SCANNER_CACHE")
|
|
13
|
-
export const CODEBLOG_TEST_HOME = process.env["CODEBLOG_TEST_HOME"]
|
|
14
|
-
export declare const CODEBLOG_CLIENT: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
Object.defineProperty(Flag, "CODEBLOG_CLIENT", {
|
|
18
|
-
get() {
|
|
19
|
-
return process.env["CODEBLOG_CLIENT"] ?? "cli"
|
|
20
|
-
},
|
|
21
|
-
enumerable: true,
|
|
22
|
-
configurable: false,
|
|
23
|
-
})
|
package/src/global/index.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises"
|
|
2
|
-
import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir"
|
|
3
|
-
import path from "path"
|
|
4
|
-
import os from "os"
|
|
5
|
-
|
|
6
|
-
const app = "codeblog"
|
|
7
|
-
|
|
8
|
-
const home = process.env.CODEBLOG_TEST_HOME || os.homedir()
|
|
9
|
-
const win = os.platform() === "win32"
|
|
10
|
-
const appdata = process.env.APPDATA || path.join(home, "AppData", "Roaming")
|
|
11
|
-
const localappdata = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local")
|
|
12
|
-
|
|
13
|
-
const data = win ? path.join(localappdata, app) : path.join(xdgData || path.join(home, ".local", "share"), app)
|
|
14
|
-
const cache = win ? path.join(localappdata, app, "cache") : path.join(xdgCache || path.join(home, ".cache"), app)
|
|
15
|
-
const config = win ? path.join(appdata, app) : path.join(xdgConfig || path.join(home, ".config"), app)
|
|
16
|
-
const state = win ? path.join(localappdata, app, "state") : path.join(xdgState || path.join(home, ".local", "state"), app)
|
|
17
|
-
|
|
18
|
-
export namespace Global {
|
|
19
|
-
export const Path = {
|
|
20
|
-
get home() {
|
|
21
|
-
return process.env.CODEBLOG_TEST_HOME || os.homedir()
|
|
22
|
-
},
|
|
23
|
-
data,
|
|
24
|
-
bin: path.join(data, "bin"),
|
|
25
|
-
log: path.join(data, "log"),
|
|
26
|
-
cache,
|
|
27
|
-
config,
|
|
28
|
-
state,
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
await Promise.all([
|
|
33
|
-
fs.mkdir(Global.Path.data, { recursive: true }),
|
|
34
|
-
fs.mkdir(Global.Path.config, { recursive: true }),
|
|
35
|
-
fs.mkdir(Global.Path.state, { recursive: true }),
|
|
36
|
-
fs.mkdir(Global.Path.log, { recursive: true }),
|
|
37
|
-
fs.mkdir(Global.Path.bin, { recursive: true }),
|
|
38
|
-
])
|
package/src/id/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "crypto"
|
|
2
|
-
|
|
3
|
-
export namespace ID {
|
|
4
|
-
export function generate(prefix = ""): string {
|
|
5
|
-
const bytes = randomBytes(12).toString("hex")
|
|
6
|
-
return prefix ? `${prefix}_${bytes}` : bytes
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function short(): string {
|
|
10
|
-
return randomBytes(6).toString("hex")
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function uuid(): string {
|
|
14
|
-
return crypto.randomUUID()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function timestamp(): string {
|
|
18
|
-
return `${Date.now()}-${randomBytes(4).toString("hex")}`
|
|
19
|
-
}
|
|
20
|
-
}
|