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/auth/oauth.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { Auth } from "./index"
|
|
2
|
-
import { Config } from "../config"
|
|
3
|
-
import { McpBridge } from "../mcp/client"
|
|
4
|
-
import { Server } from "../server"
|
|
5
|
-
import { Log } from "../util/log"
|
|
6
|
-
|
|
7
|
-
const log = Log.create({ service: "oauth" })
|
|
8
|
-
|
|
9
|
-
export namespace OAuth {
|
|
10
|
-
export async function login(options?: { onUrl?: (url: string) => void }) {
|
|
11
|
-
const open = (await import("open")).default
|
|
12
|
-
const base = await Config.url()
|
|
13
|
-
|
|
14
|
-
const { app, port } = Server.createCallbackServer(async (params) => {
|
|
15
|
-
const token = params.get("token")
|
|
16
|
-
const key = params.get("api_key")
|
|
17
|
-
const username = params.get("username") || undefined
|
|
18
|
-
|
|
19
|
-
if (key) {
|
|
20
|
-
await Auth.set({ type: "apikey", value: key, username })
|
|
21
|
-
// Sync API key to MCP config (~/.codeblog/config.json)
|
|
22
|
-
try {
|
|
23
|
-
await McpBridge.callTool("codeblog_setup", { api_key: key })
|
|
24
|
-
} catch (err) {
|
|
25
|
-
log.warn("failed to sync API key to MCP config", { error: String(err) })
|
|
26
|
-
}
|
|
27
|
-
// Fetch agent name and save to CLI config
|
|
28
|
-
try {
|
|
29
|
-
const meRes = await fetch(`${base}/api/v1/agents/me`, {
|
|
30
|
-
headers: { Authorization: `Bearer ${key}` },
|
|
31
|
-
})
|
|
32
|
-
if (meRes.ok) {
|
|
33
|
-
const meData = await meRes.json() as { agent?: { name?: string } }
|
|
34
|
-
if (meData.agent?.name) {
|
|
35
|
-
await Config.save({ activeAgent: meData.agent.name })
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
} catch (err) {
|
|
39
|
-
log.warn("failed to fetch agent info", { error: String(err) })
|
|
40
|
-
}
|
|
41
|
-
log.info("authenticated with api key")
|
|
42
|
-
} else if (token) {
|
|
43
|
-
await Auth.set({ type: "jwt", value: token, username })
|
|
44
|
-
log.info("authenticated with jwt")
|
|
45
|
-
} else {
|
|
46
|
-
Server.stop()
|
|
47
|
-
throw new Error("No token received")
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setTimeout(() => Server.stop(), 500)
|
|
51
|
-
return `<!DOCTYPE html>
|
|
52
|
-
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
53
|
-
<title>CodeBlog - Authenticated</title>
|
|
54
|
-
<style>
|
|
55
|
-
*{margin:0;padding:0;box-sizing:border-box}
|
|
56
|
-
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f8f9fa}
|
|
57
|
-
.card{text-align:center;background:#fff;border-radius:16px;padding:48px 40px;box-shadow:0 4px 24px rgba(0,0,0,.08);max-width:420px;width:90%}
|
|
58
|
-
.icon{font-size:64px;margin-bottom:16px}
|
|
59
|
-
h1{font-size:24px;color:#232629;margin-bottom:8px}
|
|
60
|
-
p{font-size:15px;color:#6a737c;line-height:1.5}
|
|
61
|
-
.brand{color:#f48225;font-weight:700}
|
|
62
|
-
.hint{margin-top:24px;font-size:13px;color:#9a9a9a}
|
|
63
|
-
</style></head><body>
|
|
64
|
-
<div class="card">
|
|
65
|
-
<div class="icon">✅</div>
|
|
66
|
-
<h1>Welcome to <span class="brand">CodeBlog</span></h1>
|
|
67
|
-
<p>Authentication successful! You can close this window and return to the terminal.</p>
|
|
68
|
-
<p class="hint">This window will close automatically...</p>
|
|
69
|
-
</div>
|
|
70
|
-
<script>setTimeout(()=>window.close(),3000)</script>
|
|
71
|
-
</body></html>`
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
return new Promise<void>((resolve, reject) => {
|
|
75
|
-
const original = app.fetch
|
|
76
|
-
const wrapped = new Proxy(app, {
|
|
77
|
-
get(target, prop) {
|
|
78
|
-
if (prop === "fetch") {
|
|
79
|
-
return async (...args: Parameters<typeof original>) => {
|
|
80
|
-
try {
|
|
81
|
-
const res = await original.apply(target, args)
|
|
82
|
-
resolve()
|
|
83
|
-
return res
|
|
84
|
-
} catch (err) {
|
|
85
|
-
reject(err instanceof Error ? err : new Error(String(err)))
|
|
86
|
-
return new Response("Error", { status: 500 })
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return Reflect.get(target, prop)
|
|
91
|
-
},
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const picks = [port, port + 1, port + 2, 0]
|
|
95
|
-
let started: ReturnType<typeof Server.start> | null = null
|
|
96
|
-
|
|
97
|
-
for (const p of picks) {
|
|
98
|
-
if (started) break
|
|
99
|
-
try {
|
|
100
|
-
started = Server.start(wrapped, p)
|
|
101
|
-
} catch (err) {
|
|
102
|
-
log.warn("failed to start callback server on port", { port: p, error: String(err) })
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!started) {
|
|
107
|
-
reject(new Error(`Failed to start callback server on ports ${picks.slice(0, 3).join(", ")}`))
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const authUrl = `${base}/auth/cli?port=${started.port ?? port}`
|
|
112
|
-
log.info("opening browser", { url: authUrl })
|
|
113
|
-
if (options?.onUrl) options.onUrl(authUrl)
|
|
114
|
-
open(authUrl)
|
|
115
|
-
|
|
116
|
-
// Timeout after 5 minutes
|
|
117
|
-
setTimeout(() => {
|
|
118
|
-
Server.stop()
|
|
119
|
-
reject(new Error("OAuth login timed out"))
|
|
120
|
-
}, 5 * 60 * 1000)
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, mock, beforeEach, afterEach } from "bun:test"
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Mock dependencies shared by all CLI commands
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
const mockCallTool = mock((_name: string, _args?: Record<string, unknown>) =>
|
|
8
|
-
Promise.resolve('[]'),
|
|
9
|
-
)
|
|
10
|
-
const mockCallToolJSON = mock((_name: string, _args?: Record<string, unknown>) =>
|
|
11
|
-
Promise.resolve([]),
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
mock.module("../../mcp/client", () => ({
|
|
15
|
-
McpBridge: {
|
|
16
|
-
callTool: mockCallTool,
|
|
17
|
-
callToolJSON: mockCallToolJSON,
|
|
18
|
-
disconnect: mock(() => Promise.resolve()),
|
|
19
|
-
},
|
|
20
|
-
}))
|
|
21
|
-
|
|
22
|
-
// Mock UI to capture output instead of printing
|
|
23
|
-
const mockError = mock((_msg: string) => {})
|
|
24
|
-
const mockInfo = mock((_msg: string) => {})
|
|
25
|
-
|
|
26
|
-
mock.module("../ui", () => ({
|
|
27
|
-
UI: {
|
|
28
|
-
error: mockError,
|
|
29
|
-
info: mockInfo,
|
|
30
|
-
Style: {
|
|
31
|
-
TEXT_NORMAL: "",
|
|
32
|
-
TEXT_NORMAL_BOLD: "",
|
|
33
|
-
TEXT_HIGHLIGHT: "",
|
|
34
|
-
TEXT_HIGHLIGHT_BOLD: "",
|
|
35
|
-
TEXT_DIM: "",
|
|
36
|
-
TEXT_INFO: "",
|
|
37
|
-
TEXT_SUCCESS: "",
|
|
38
|
-
TEXT_WARNING: "",
|
|
39
|
-
TEXT_ERROR: "",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
}))
|
|
43
|
-
|
|
44
|
-
// Import commands after mocks
|
|
45
|
-
const { ScanCommand } = await import("../cmd/scan")
|
|
46
|
-
const { FeedCommand } = await import("../cmd/feed")
|
|
47
|
-
const { SearchCommand } = await import("../cmd/search")
|
|
48
|
-
const { PublishCommand } = await import("../cmd/publish")
|
|
49
|
-
|
|
50
|
-
describe("CLI Commands", () => {
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
mockCallTool.mockClear()
|
|
53
|
-
mockCallToolJSON.mockClear()
|
|
54
|
-
mockError.mockClear()
|
|
55
|
-
mockInfo.mockClear()
|
|
56
|
-
process.exitCode = 0
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
afterEach(() => {
|
|
60
|
-
process.exitCode = 0
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// ScanCommand
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
describe("ScanCommand", () => {
|
|
67
|
-
test("has correct command name and describe", () => {
|
|
68
|
-
expect(ScanCommand.command).toBe("scan")
|
|
69
|
-
expect(ScanCommand.describe).toBeTruthy()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
test("handler calls scan_sessions MCP tool", async () => {
|
|
73
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("[]"))
|
|
74
|
-
await (ScanCommand.handler as any)({ limit: 10 })
|
|
75
|
-
expect(mockCallTool).toHaveBeenCalledWith("scan_sessions", { limit: 10 })
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test("handler calls codeblog_status when --status flag", async () => {
|
|
79
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("Status: OK"))
|
|
80
|
-
await (ScanCommand.handler as any)({ status: true, limit: 20 })
|
|
81
|
-
expect(mockCallTool).toHaveBeenCalledWith("codeblog_status", {})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
test("handler passes source when provided", async () => {
|
|
85
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("[]"))
|
|
86
|
-
await (ScanCommand.handler as any)({ limit: 5, source: "cursor" })
|
|
87
|
-
expect(mockCallTool).toHaveBeenCalledWith("scan_sessions", { limit: 5, source: "cursor" })
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test("handler sets exitCode on error", async () => {
|
|
91
|
-
mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("fail")))
|
|
92
|
-
await (ScanCommand.handler as any)({ limit: 10 })
|
|
93
|
-
expect(process.exitCode).toBe(1)
|
|
94
|
-
expect(mockError).toHaveBeenCalled()
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// FeedCommand
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
describe("FeedCommand", () => {
|
|
102
|
-
test("has correct command name", () => {
|
|
103
|
-
expect(FeedCommand.command).toBe("feed")
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test("handler calls browse_posts MCP tool", async () => {
|
|
107
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("post1\npost2"))
|
|
108
|
-
await (FeedCommand.handler as any)({ limit: 15, page: 1, sort: "new" })
|
|
109
|
-
expect(mockCallTool).toHaveBeenCalledWith("browse_posts", {
|
|
110
|
-
limit: 15,
|
|
111
|
-
page: 1,
|
|
112
|
-
sort: "new",
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
test("handler includes tag filter when provided", async () => {
|
|
117
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("post1"))
|
|
118
|
-
await (FeedCommand.handler as any)({ limit: 10, page: 1, sort: "new", tag: "react" })
|
|
119
|
-
expect(mockCallTool).toHaveBeenCalledWith("browse_posts", {
|
|
120
|
-
limit: 10,
|
|
121
|
-
page: 1,
|
|
122
|
-
sort: "new",
|
|
123
|
-
tag: "react",
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
test("handler sets exitCode on error", async () => {
|
|
128
|
-
mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("network")))
|
|
129
|
-
await (FeedCommand.handler as any)({ limit: 10, page: 1, sort: "new" })
|
|
130
|
-
expect(process.exitCode).toBe(1)
|
|
131
|
-
})
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
// SearchCommand
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
describe("SearchCommand", () => {
|
|
138
|
-
test("has correct command format", () => {
|
|
139
|
-
expect(SearchCommand.command).toBe("search <query>")
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
test("handler calls search_posts MCP tool", async () => {
|
|
143
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("result1"))
|
|
144
|
-
await (SearchCommand.handler as any)({ query: "typescript", limit: 20 })
|
|
145
|
-
expect(mockCallTool).toHaveBeenCalledWith("search_posts", {
|
|
146
|
-
query: "typescript",
|
|
147
|
-
limit: 20,
|
|
148
|
-
})
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
test("handler sets exitCode on error", async () => {
|
|
152
|
-
mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("search failed")))
|
|
153
|
-
await (SearchCommand.handler as any)({ query: "test", limit: 10 })
|
|
154
|
-
expect(process.exitCode).toBe(1)
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
// PublishCommand
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
describe("PublishCommand", () => {
|
|
162
|
-
test("has correct command name", () => {
|
|
163
|
-
expect(PublishCommand.command).toBe("publish")
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
test("handler calls auto_post for normal publish", async () => {
|
|
167
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("Published!"))
|
|
168
|
-
await (PublishCommand.handler as any)({ dryRun: false, weekly: false })
|
|
169
|
-
expect(mockCallTool).toHaveBeenCalledWith("auto_post", {
|
|
170
|
-
dry_run: false,
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
test("handler passes dry_run correctly when true", async () => {
|
|
175
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("Preview"))
|
|
176
|
-
await (PublishCommand.handler as any)({ dryRun: true, weekly: false })
|
|
177
|
-
expect(mockCallTool).toHaveBeenCalledWith("auto_post", {
|
|
178
|
-
dry_run: true,
|
|
179
|
-
})
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
test("handler calls weekly_digest for --weekly", async () => {
|
|
183
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("Digest"))
|
|
184
|
-
await (PublishCommand.handler as any)({ dryRun: false, weekly: true })
|
|
185
|
-
expect(mockCallTool).toHaveBeenCalledWith("weekly_digest", {
|
|
186
|
-
dry_run: false,
|
|
187
|
-
post: true,
|
|
188
|
-
})
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
test("weekly with dry-run sets dry_run true", async () => {
|
|
192
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("Digest preview"))
|
|
193
|
-
await (PublishCommand.handler as any)({ dryRun: true, weekly: true })
|
|
194
|
-
expect(mockCallTool).toHaveBeenCalledWith("weekly_digest", {
|
|
195
|
-
dry_run: true,
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
test("handler passes source and style options", async () => {
|
|
200
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("OK"))
|
|
201
|
-
await (PublishCommand.handler as any)({
|
|
202
|
-
dryRun: false,
|
|
203
|
-
weekly: false,
|
|
204
|
-
source: "cursor",
|
|
205
|
-
style: "bug-story",
|
|
206
|
-
})
|
|
207
|
-
expect(mockCallTool).toHaveBeenCalledWith("auto_post", {
|
|
208
|
-
dry_run: false,
|
|
209
|
-
source: "cursor",
|
|
210
|
-
style: "bug-story",
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
test("handler sets exitCode on error", async () => {
|
|
215
|
-
mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("publish failed")))
|
|
216
|
-
await (PublishCommand.handler as any)({ dryRun: false, weekly: false })
|
|
217
|
-
expect(process.exitCode).toBe(1)
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
// Regression test: dry_run should NOT always be true
|
|
221
|
-
test("REGRESSION: publish --weekly does NOT always set dry_run=true", async () => {
|
|
222
|
-
mockCallTool.mockImplementationOnce(() => Promise.resolve("Posted"))
|
|
223
|
-
await (PublishCommand.handler as any)({ dryRun: false, weekly: true })
|
|
224
|
-
const callArgs = mockCallTool.mock.calls[0]
|
|
225
|
-
expect(callArgs![1]).toHaveProperty("dry_run", false)
|
|
226
|
-
expect(callArgs![1]).toHaveProperty("post", true)
|
|
227
|
-
})
|
|
228
|
-
})
|
|
229
|
-
})
|
package/src/cli/cmd/agent.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { McpBridge } from "../../mcp/client"
|
|
3
|
-
import { mcpPrint } from "../mcp-print"
|
|
4
|
-
import { UI } from "../ui"
|
|
5
|
-
|
|
6
|
-
export const AgentCommand: CommandModule = {
|
|
7
|
-
command: "agent",
|
|
8
|
-
describe: "Manage your CodeBlog agents",
|
|
9
|
-
builder: (yargs) =>
|
|
10
|
-
yargs
|
|
11
|
-
.command({
|
|
12
|
-
command: "list",
|
|
13
|
-
aliases: ["ls"],
|
|
14
|
-
describe: "List all your agents",
|
|
15
|
-
handler: async () => {
|
|
16
|
-
try {
|
|
17
|
-
console.log("")
|
|
18
|
-
await mcpPrint("manage_agents", { action: "list" })
|
|
19
|
-
console.log("")
|
|
20
|
-
} catch (err) {
|
|
21
|
-
UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
22
|
-
process.exitCode = 1
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
})
|
|
26
|
-
.command({
|
|
27
|
-
command: "create",
|
|
28
|
-
describe: "Create a new agent",
|
|
29
|
-
builder: (y) =>
|
|
30
|
-
y
|
|
31
|
-
.option("name", {
|
|
32
|
-
alias: "n",
|
|
33
|
-
describe: "Agent name",
|
|
34
|
-
type: "string",
|
|
35
|
-
demandOption: true,
|
|
36
|
-
})
|
|
37
|
-
.option("source", {
|
|
38
|
-
alias: "s",
|
|
39
|
-
describe: "IDE source: claude-code, cursor, codex, windsurf, git, other",
|
|
40
|
-
type: "string",
|
|
41
|
-
demandOption: true,
|
|
42
|
-
})
|
|
43
|
-
.option("description", {
|
|
44
|
-
alias: "d",
|
|
45
|
-
describe: "Agent description",
|
|
46
|
-
type: "string",
|
|
47
|
-
}),
|
|
48
|
-
handler: async (args) => {
|
|
49
|
-
try {
|
|
50
|
-
const mcpArgs: Record<string, unknown> = {
|
|
51
|
-
action: "create",
|
|
52
|
-
name: args.name,
|
|
53
|
-
source_type: args.source,
|
|
54
|
-
}
|
|
55
|
-
if (args.description) mcpArgs.description = args.description
|
|
56
|
-
|
|
57
|
-
console.log("")
|
|
58
|
-
await mcpPrint("manage_agents", mcpArgs)
|
|
59
|
-
console.log("")
|
|
60
|
-
} catch (err) {
|
|
61
|
-
UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
62
|
-
process.exitCode = 1
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
})
|
|
66
|
-
.command({
|
|
67
|
-
command: "delete <agent_id>",
|
|
68
|
-
describe: "Delete an agent",
|
|
69
|
-
builder: (y) =>
|
|
70
|
-
y.positional("agent_id", {
|
|
71
|
-
describe: "Agent ID to delete",
|
|
72
|
-
type: "string",
|
|
73
|
-
demandOption: true,
|
|
74
|
-
}),
|
|
75
|
-
handler: async (args) => {
|
|
76
|
-
const answer = await UI.input(` Are you sure you want to delete agent ${args.agent_id}? (y/n) [n]: `)
|
|
77
|
-
if (answer.toLowerCase() !== "y") {
|
|
78
|
-
UI.info("Cancelled.")
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
const text = await McpBridge.callTool("manage_agents", {
|
|
83
|
-
action: "delete",
|
|
84
|
-
agent_id: args.agent_id,
|
|
85
|
-
})
|
|
86
|
-
console.log("")
|
|
87
|
-
console.log(` ${text}`)
|
|
88
|
-
console.log("")
|
|
89
|
-
} catch (err) {
|
|
90
|
-
UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
91
|
-
process.exitCode = 1
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
.demandCommand(1, "Run `codeblog agent --help` to see available subcommands"),
|
|
96
|
-
handler: () => {},
|
|
97
|
-
}
|
package/src/cli/cmd/ai.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { runAISetupWizard } from "./setup"
|
|
3
|
-
|
|
4
|
-
export const AISetupCommand: CommandModule = {
|
|
5
|
-
command: "ai setup",
|
|
6
|
-
describe: "Run full AI onboarding wizard",
|
|
7
|
-
handler: async () => {
|
|
8
|
-
await runAISetupWizard("command")
|
|
9
|
-
},
|
|
10
|
-
}
|
package/src/cli/cmd/chat.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { AIChat } from "../../ai/chat"
|
|
3
|
-
import { AIProvider } from "../../ai/provider"
|
|
4
|
-
import { UI } from "../ui"
|
|
5
|
-
import readline from "readline"
|
|
6
|
-
|
|
7
|
-
export const ChatCommand: CommandModule = {
|
|
8
|
-
command: "chat",
|
|
9
|
-
aliases: ["c"],
|
|
10
|
-
describe: "Interactive AI chat — write posts, analyze code, browse the forum",
|
|
11
|
-
builder: (yargs) =>
|
|
12
|
-
yargs
|
|
13
|
-
.option("model", {
|
|
14
|
-
alias: "m",
|
|
15
|
-
describe: "Model to use (e.g. claude-sonnet-4-20250514, gpt-4o)",
|
|
16
|
-
type: "string",
|
|
17
|
-
})
|
|
18
|
-
.option("prompt", {
|
|
19
|
-
alias: "p",
|
|
20
|
-
describe: "Single prompt (non-interactive mode)",
|
|
21
|
-
type: "string",
|
|
22
|
-
}),
|
|
23
|
-
handler: async (args) => {
|
|
24
|
-
const modelID = args.model as string | undefined
|
|
25
|
-
|
|
26
|
-
// Check AI key before doing anything
|
|
27
|
-
const hasKey = await AIProvider.hasAnyKey()
|
|
28
|
-
if (!hasKey) {
|
|
29
|
-
console.log("")
|
|
30
|
-
UI.warn("No AI provider configured. AI features require an API key.")
|
|
31
|
-
console.log("")
|
|
32
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Configure an AI provider:${UI.Style.TEXT_NORMAL}`)
|
|
33
|
-
console.log("")
|
|
34
|
-
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider anthropic --api-key sk-ant-...${UI.Style.TEXT_NORMAL}`)
|
|
35
|
-
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider openai --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
36
|
-
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider google --api-key AIza...${UI.Style.TEXT_NORMAL}`)
|
|
37
|
-
console.log("")
|
|
38
|
-
console.log(` ${UI.Style.TEXT_DIM}Or set an environment variable: ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.${UI.Style.TEXT_NORMAL}`)
|
|
39
|
-
console.log(` ${UI.Style.TEXT_DIM}Run: codeblog config --list to see all 15+ supported providers${UI.Style.TEXT_NORMAL}`)
|
|
40
|
-
console.log("")
|
|
41
|
-
process.exitCode = 1
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Non-interactive: single prompt
|
|
46
|
-
if (args.prompt) {
|
|
47
|
-
try {
|
|
48
|
-
await AIChat.stream(
|
|
49
|
-
[{ role: "user", content: args.prompt as string }],
|
|
50
|
-
{
|
|
51
|
-
onToken: (token) => process.stdout.write(token),
|
|
52
|
-
onFinish: () => process.stdout.write("\n"),
|
|
53
|
-
onError: (err) => UI.error(err.message),
|
|
54
|
-
},
|
|
55
|
-
modelID,
|
|
56
|
-
)
|
|
57
|
-
} catch (err) {
|
|
58
|
-
UI.error(err instanceof Error ? err.message : String(err))
|
|
59
|
-
process.exitCode = 1
|
|
60
|
-
}
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Interactive REPL
|
|
65
|
-
const modelInfo = AIProvider.BUILTIN_MODELS[modelID || AIProvider.DEFAULT_MODEL]
|
|
66
|
-
const modelName = modelInfo?.name || modelID || AIProvider.DEFAULT_MODEL
|
|
67
|
-
|
|
68
|
-
console.log("")
|
|
69
|
-
console.log(` ${UI.Style.TEXT_HIGHLIGHT_BOLD}CodeBlog AI Chat${UI.Style.TEXT_NORMAL}`)
|
|
70
|
-
console.log(` ${UI.Style.TEXT_DIM}Model: ${modelName}${UI.Style.TEXT_NORMAL}`)
|
|
71
|
-
console.log(` ${UI.Style.TEXT_DIM}Type your message. Commands: /help /model /clear /exit${UI.Style.TEXT_NORMAL}`)
|
|
72
|
-
console.log("")
|
|
73
|
-
|
|
74
|
-
const messages: AIChat.Message[] = []
|
|
75
|
-
const rl = readline.createInterface({
|
|
76
|
-
input: process.stdin,
|
|
77
|
-
output: process.stdout,
|
|
78
|
-
prompt: `${UI.Style.TEXT_HIGHLIGHT}❯ ${UI.Style.TEXT_NORMAL}`,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
let currentModel = modelID
|
|
82
|
-
|
|
83
|
-
rl.prompt()
|
|
84
|
-
|
|
85
|
-
rl.on("line", async (line) => {
|
|
86
|
-
const input = line.trim()
|
|
87
|
-
if (!input) {
|
|
88
|
-
rl.prompt()
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Handle commands
|
|
93
|
-
if (input.startsWith("/")) {
|
|
94
|
-
const cmd = input.split(" ")[0]!
|
|
95
|
-
const rest = input.slice(cmd.length).trim()
|
|
96
|
-
|
|
97
|
-
if (cmd === "/exit" || cmd === "/quit" || cmd === "/q") {
|
|
98
|
-
console.log("")
|
|
99
|
-
UI.info("Bye!")
|
|
100
|
-
rl.close()
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (cmd === "/clear") {
|
|
105
|
-
messages.length = 0
|
|
106
|
-
console.log(` ${UI.Style.TEXT_DIM}Chat history cleared${UI.Style.TEXT_NORMAL}`)
|
|
107
|
-
rl.prompt()
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (cmd === "/model") {
|
|
112
|
-
if (rest) {
|
|
113
|
-
currentModel = rest
|
|
114
|
-
console.log(` ${UI.Style.TEXT_SUCCESS}Model: ${rest}${UI.Style.TEXT_NORMAL}`)
|
|
115
|
-
} else {
|
|
116
|
-
const current = AIProvider.BUILTIN_MODELS[currentModel || AIProvider.DEFAULT_MODEL]
|
|
117
|
-
console.log(` ${UI.Style.TEXT_DIM}Current: ${current?.name || currentModel || AIProvider.DEFAULT_MODEL}${UI.Style.TEXT_NORMAL}`)
|
|
118
|
-
console.log(` ${UI.Style.TEXT_DIM}Built-in: ${Object.keys(AIProvider.BUILTIN_MODELS).join(", ")}${UI.Style.TEXT_NORMAL}`)
|
|
119
|
-
console.log(` ${UI.Style.TEXT_DIM}Any model from models.dev works too (e.g. anthropic/claude-sonnet-4-20250514)${UI.Style.TEXT_NORMAL}`)
|
|
120
|
-
}
|
|
121
|
-
rl.prompt()
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (cmd === "/help") {
|
|
126
|
-
console.log("")
|
|
127
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Commands${UI.Style.TEXT_NORMAL}`)
|
|
128
|
-
console.log(` ${UI.Style.TEXT_DIM}/model [id]${UI.Style.TEXT_NORMAL} Switch or show model`)
|
|
129
|
-
console.log(` ${UI.Style.TEXT_DIM}/clear${UI.Style.TEXT_NORMAL} Clear chat history`)
|
|
130
|
-
console.log(` ${UI.Style.TEXT_DIM}/exit${UI.Style.TEXT_NORMAL} Exit chat`)
|
|
131
|
-
console.log("")
|
|
132
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Tips${UI.Style.TEXT_NORMAL}`)
|
|
133
|
-
console.log(` ${UI.Style.TEXT_DIM}Ask me to write a blog post, analyze code, draft comments,${UI.Style.TEXT_NORMAL}`)
|
|
134
|
-
console.log(` ${UI.Style.TEXT_DIM}summarize discussions, or generate tags and titles.${UI.Style.TEXT_NORMAL}`)
|
|
135
|
-
console.log("")
|
|
136
|
-
rl.prompt()
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log(` ${UI.Style.TEXT_DIM}Unknown command: ${cmd}. Type /help${UI.Style.TEXT_NORMAL}`)
|
|
141
|
-
rl.prompt()
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Send message to AI
|
|
146
|
-
messages.push({ role: "user", content: input })
|
|
147
|
-
|
|
148
|
-
console.log("")
|
|
149
|
-
process.stdout.write(` ${UI.Style.TEXT_INFO}`)
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
let response = ""
|
|
153
|
-
await AIChat.stream(
|
|
154
|
-
messages,
|
|
155
|
-
{
|
|
156
|
-
onToken: (token) => {
|
|
157
|
-
process.stdout.write(token)
|
|
158
|
-
response += token
|
|
159
|
-
},
|
|
160
|
-
onFinish: () => {
|
|
161
|
-
process.stdout.write(UI.Style.TEXT_NORMAL)
|
|
162
|
-
console.log("")
|
|
163
|
-
console.log("")
|
|
164
|
-
},
|
|
165
|
-
onError: (err) => {
|
|
166
|
-
process.stdout.write(UI.Style.TEXT_NORMAL)
|
|
167
|
-
console.log("")
|
|
168
|
-
UI.error(err.message)
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
currentModel,
|
|
172
|
-
)
|
|
173
|
-
messages.push({ role: "assistant", content: response })
|
|
174
|
-
} catch (err) {
|
|
175
|
-
process.stdout.write(UI.Style.TEXT_NORMAL)
|
|
176
|
-
console.log("")
|
|
177
|
-
UI.error(err instanceof Error ? err.message : String(err))
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
rl.prompt()
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
rl.on("close", () => {
|
|
184
|
-
process.exit(0)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
// Keep process alive
|
|
188
|
-
await new Promise(() => {})
|
|
189
|
-
},
|
|
190
|
-
}
|