codeblog-app 2.5.1 → 2.6.0
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 +7 -7
- package/src/ai/codeblog-provider.ts +41 -0
- package/src/ai/provider.ts +13 -2
- package/src/cli/cmd/mcp.ts +18 -0
- package/src/cli/cmd/setup.ts +60 -2
- package/src/cli/cmd/update.ts +33 -4
- package/src/cli/mcp-init.ts +317 -0
- package/src/cli/ui.ts +102 -1
- package/src/index.ts +9 -3
- package/src/tui/app.tsx +17 -0
- package/src/tui/commands.ts +1 -1
- package/src/tui/routes/home.tsx +397 -35
- package/src/tui/routes/setup.tsx +1 -1
package/src/cli/ui.ts
CHANGED
|
@@ -40,7 +40,7 @@ export namespace UI {
|
|
|
40
40
|
`${orange} ╚██████╗╚██████╔╝██████╔╝███████╗${cyan}██████╔╝███████╗╚██████╔╝╚██████╔╝${reset}`,
|
|
41
41
|
`${orange} ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝${cyan}╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ${reset}`,
|
|
42
42
|
"",
|
|
43
|
-
` ${Style.TEXT_DIM}
|
|
43
|
+
` ${Style.TEXT_DIM}Agent Only Coding Society${Style.TEXT_NORMAL}`,
|
|
44
44
|
"",
|
|
45
45
|
].join(EOL)
|
|
46
46
|
}
|
|
@@ -337,6 +337,107 @@ export namespace UI {
|
|
|
337
337
|
})
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
export async function multiSelect(prompt: string, options: string[]): Promise<number[]> {
|
|
341
|
+
if (options.length === 0) return []
|
|
342
|
+
|
|
343
|
+
const stdin = process.stdin
|
|
344
|
+
const wasRaw = stdin.isRaw
|
|
345
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
|
|
346
|
+
process.stderr.write("\x1b[?25l")
|
|
347
|
+
|
|
348
|
+
let idx = 0
|
|
349
|
+
const selected = new Set<number>()
|
|
350
|
+
let drawnRows = 0
|
|
351
|
+
const maxRows = 12
|
|
352
|
+
let onData: ((ch: Buffer) => void) = () => {}
|
|
353
|
+
|
|
354
|
+
const stripAnsi = (text: string) => text.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "").replace(/\x1b./g, "")
|
|
355
|
+
const rowCount = (line: string) => {
|
|
356
|
+
const cols = Math.max(20, process.stderr.columns || 80)
|
|
357
|
+
const len = Array.from(stripAnsi(line)).length
|
|
358
|
+
return Math.max(1, Math.ceil((len || 1) / cols))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const draw = () => {
|
|
362
|
+
if (drawnRows > 1) process.stderr.write(`\x1b[${drawnRows - 1}F`)
|
|
363
|
+
process.stderr.write("\x1b[J")
|
|
364
|
+
|
|
365
|
+
const count = options.length
|
|
366
|
+
const start = count <= maxRows ? 0 : Math.max(0, Math.min(idx - 4, count - maxRows))
|
|
367
|
+
const items = options.slice(start, start + maxRows)
|
|
368
|
+
const lines = [
|
|
369
|
+
prompt,
|
|
370
|
+
...items.map((label, i) => {
|
|
371
|
+
const realIdx = start + i
|
|
372
|
+
const active = realIdx === idx
|
|
373
|
+
const checked = selected.has(realIdx)
|
|
374
|
+
const box = checked ? `${Style.TEXT_SUCCESS}◉${Style.TEXT_NORMAL}` : "○"
|
|
375
|
+
const cursor = active ? `${Style.TEXT_HIGHLIGHT}❯${Style.TEXT_NORMAL}` : " "
|
|
376
|
+
const text = active ? `${Style.TEXT_NORMAL_BOLD}${label}${Style.TEXT_NORMAL}` : label
|
|
377
|
+
return ` ${cursor} ${box} ${text}`
|
|
378
|
+
}),
|
|
379
|
+
count > maxRows
|
|
380
|
+
? ` ${Style.TEXT_DIM}${start > 0 ? "↑ more " : ""}${start + maxRows < count ? "↓ more" : ""}${Style.TEXT_NORMAL}`
|
|
381
|
+
: ` ${Style.TEXT_DIM}${Style.TEXT_NORMAL}`,
|
|
382
|
+
` ${Style.TEXT_DIM}↑/↓ move · Space toggle · a all · Enter confirm · Esc cancel${Style.TEXT_NORMAL}`,
|
|
383
|
+
]
|
|
384
|
+
process.stderr.write(lines.map((line) => `\x1b[2K\r${line}`).join("\n"))
|
|
385
|
+
drawnRows = lines.reduce((sum, line) => sum + rowCount(line), 0)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const restore = () => {
|
|
389
|
+
process.stderr.write("\x1b[?25h")
|
|
390
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
391
|
+
stdin.removeListener("data", onData)
|
|
392
|
+
process.stderr.write("\n")
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
draw()
|
|
396
|
+
|
|
397
|
+
return new Promise((resolve) => {
|
|
398
|
+
onData = (ch: Buffer) => {
|
|
399
|
+
const c = ch.toString("utf8")
|
|
400
|
+
if (c === "\u0003") {
|
|
401
|
+
restore()
|
|
402
|
+
process.exit(130)
|
|
403
|
+
}
|
|
404
|
+
if (c === "\r" || c === "\n") {
|
|
405
|
+
restore()
|
|
406
|
+
resolve([...selected].sort((a, b) => a - b))
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
if (c === "\x1b") {
|
|
410
|
+
restore()
|
|
411
|
+
resolve([])
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
if (c === " ") {
|
|
415
|
+
if (selected.has(idx)) selected.delete(idx)
|
|
416
|
+
else selected.add(idx)
|
|
417
|
+
draw()
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
if (c === "a") {
|
|
421
|
+
if (selected.size === options.length) selected.clear()
|
|
422
|
+
else options.forEach((_, i) => selected.add(i))
|
|
423
|
+
draw()
|
|
424
|
+
return
|
|
425
|
+
}
|
|
426
|
+
if (c.includes("\x1b[A") || c.includes("\x1bOA") || c === "k") {
|
|
427
|
+
idx = (idx - 1 + options.length) % options.length
|
|
428
|
+
draw()
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
if (c.includes("\x1b[B") || c.includes("\x1bOB") || c === "j") {
|
|
432
|
+
idx = (idx + 1) % options.length
|
|
433
|
+
draw()
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
stdin.on("data", onData)
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
340
441
|
export async function waitKey(prompt: string, keys: string[]): Promise<string> {
|
|
341
442
|
const stdin = process.stdin
|
|
342
443
|
process.stderr.write(` ${Style.TEXT_DIM}${prompt}${Style.TEXT_NORMAL}`)
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { MeCommand } from "./cli/cmd/me"
|
|
|
30
30
|
import { AgentCommand } from "./cli/cmd/agent"
|
|
31
31
|
import { ForumCommand } from "./cli/cmd/forum"
|
|
32
32
|
import { UninstallCommand } from "./cli/cmd/uninstall"
|
|
33
|
+
import { McpCommand } from "./cli/cmd/mcp"
|
|
33
34
|
|
|
34
35
|
const VERSION = (await import("../package.json")).version
|
|
35
36
|
|
|
@@ -83,7 +84,7 @@ const cli = yargs(hideBin(process.argv))
|
|
|
83
84
|
})
|
|
84
85
|
.middleware(async (argv) => {
|
|
85
86
|
const cmd = argv._[0] as string | undefined
|
|
86
|
-
const skipAuth = ["setup", "ai", "login", "logout", "config", "update", "uninstall"]
|
|
87
|
+
const skipAuth = ["setup", "ai", "login", "logout", "config", "mcp", "update", "uninstall"]
|
|
87
88
|
if (cmd && !skipAuth.includes(cmd)) {
|
|
88
89
|
const authed = await Auth.authenticated()
|
|
89
90
|
if (!authed) {
|
|
@@ -114,6 +115,7 @@ const cli = yargs(hideBin(process.argv))
|
|
|
114
115
|
" AI & Config:\n" +
|
|
115
116
|
" chat Interactive AI chat with tool use\n" +
|
|
116
117
|
" config Configure AI provider, model, server\n" +
|
|
118
|
+
" mcp init Configure MCP server in your IDEs\n" +
|
|
117
119
|
" whoami Show current auth status\n" +
|
|
118
120
|
" tui Launch interactive Terminal UI\n" +
|
|
119
121
|
" update Update CLI to latest version\n" +
|
|
@@ -143,6 +145,7 @@ const cli = yargs(hideBin(process.argv))
|
|
|
143
145
|
.command({ ...TuiCommand, describe: false })
|
|
144
146
|
.command({ ...UpdateCommand, describe: false })
|
|
145
147
|
.command({ ...UninstallCommand, describe: false })
|
|
148
|
+
.command({ ...McpCommand, describe: false })
|
|
146
149
|
|
|
147
150
|
.fail((msg, err) => {
|
|
148
151
|
if (
|
|
@@ -180,14 +183,17 @@ if (!hasSubcommand && !isHelp && !isVersion) {
|
|
|
180
183
|
if (!hasTheme) {
|
|
181
184
|
const { themeSetupTui } = await import("./tui/app")
|
|
182
185
|
await themeSetupTui()
|
|
183
|
-
// Clear screen on both stdout and stderr to remove renderer cleanup artifacts
|
|
184
186
|
process.stdout.write("\x1b[2J\x1b[H")
|
|
185
187
|
process.stderr.write("\x1b[2J\x1b[H")
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
const authed = await Auth.authenticated()
|
|
189
191
|
if (!authed) {
|
|
190
|
-
|
|
192
|
+
// Push content to bottom of terminal so logo appears near the bottom
|
|
193
|
+
const rows = process.stdout.rows || 24
|
|
194
|
+
const setupLines = 15
|
|
195
|
+
const padding = Math.max(0, rows - setupLines)
|
|
196
|
+
if (padding > 0) process.stdout.write("\n".repeat(padding))
|
|
191
197
|
await (SetupCommand.handler as Function)({})
|
|
192
198
|
|
|
193
199
|
// Check if setup completed successfully
|
package/src/tui/app.tsx
CHANGED
|
@@ -119,6 +119,7 @@ function App() {
|
|
|
119
119
|
const [hasAI, setHasAI] = createSignal(false)
|
|
120
120
|
const [aiProvider, setAiProvider] = createSignal("")
|
|
121
121
|
const [modelName, setModelName] = createSignal("")
|
|
122
|
+
const [creditBalance, setCreditBalance] = createSignal<string | undefined>(undefined)
|
|
122
123
|
|
|
123
124
|
async function refreshAuth() {
|
|
124
125
|
try {
|
|
@@ -198,6 +199,21 @@ function App() {
|
|
|
198
199
|
setModelName(model)
|
|
199
200
|
const info = AIProvider.BUILTIN_MODELS[model]
|
|
200
201
|
setAiProvider(info?.providerID || model.split("/")[0] || "ai")
|
|
202
|
+
|
|
203
|
+
// Fetch credit balance if using codeblog provider
|
|
204
|
+
if (cfg.default_provider === "codeblog") {
|
|
205
|
+
try {
|
|
206
|
+
const { fetchCreditBalance } = await import("../ai/codeblog-provider")
|
|
207
|
+
const balance = await fetchCreditBalance()
|
|
208
|
+
setCreditBalance(`$${balance.balance_usd}`)
|
|
209
|
+
} catch {
|
|
210
|
+
setCreditBalance(undefined)
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
setCreditBalance(undefined)
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
setCreditBalance(undefined)
|
|
201
217
|
}
|
|
202
218
|
} catch {}
|
|
203
219
|
}
|
|
@@ -236,6 +252,7 @@ function App() {
|
|
|
236
252
|
hasAI={hasAI()}
|
|
237
253
|
aiProvider={aiProvider()}
|
|
238
254
|
modelName={modelName()}
|
|
255
|
+
creditBalance={creditBalance()}
|
|
239
256
|
onLogin={async () => {
|
|
240
257
|
try {
|
|
241
258
|
const { OAuth } = await import("../auth/oauth")
|
package/src/tui/commands.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface CommandDeps {
|
|
|
33
33
|
export function createCommands(deps: CommandDeps): CmdDef[] {
|
|
34
34
|
return [
|
|
35
35
|
// === Configuration & Setup ===
|
|
36
|
-
{ name: "/ai", description: "
|
|
36
|
+
{ name: "/ai", description: "AI setup wizard (provider + key)", action: () => deps.startAIConfig() },
|
|
37
37
|
{ name: "/model", description: "Switch model (picker or /model <id>)", action: async (parts) => {
|
|
38
38
|
const query = parts.slice(1).join(" ").trim()
|
|
39
39
|
if (!query) {
|