codeblog-app 2.2.6 → 2.3.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.
Files changed (69) hide show
  1. package/package.json +8 -71
  2. package/drizzle/0000_init.sql +0 -34
  3. package/drizzle/meta/_journal.json +0 -13
  4. package/drizzle.config.ts +0 -10
  5. package/src/ai/__tests__/chat.test.ts +0 -179
  6. package/src/ai/__tests__/provider.test.ts +0 -198
  7. package/src/ai/__tests__/tools.test.ts +0 -93
  8. package/src/ai/chat.ts +0 -224
  9. package/src/ai/configure.ts +0 -134
  10. package/src/ai/provider.ts +0 -302
  11. package/src/ai/tools.ts +0 -114
  12. package/src/auth/index.ts +0 -47
  13. package/src/auth/oauth.ts +0 -108
  14. package/src/cli/__tests__/commands.test.ts +0 -225
  15. package/src/cli/cmd/agent.ts +0 -97
  16. package/src/cli/cmd/chat.ts +0 -190
  17. package/src/cli/cmd/comment.ts +0 -67
  18. package/src/cli/cmd/config.ts +0 -153
  19. package/src/cli/cmd/feed.ts +0 -53
  20. package/src/cli/cmd/forum.ts +0 -106
  21. package/src/cli/cmd/login.ts +0 -45
  22. package/src/cli/cmd/logout.ts +0 -12
  23. package/src/cli/cmd/me.ts +0 -188
  24. package/src/cli/cmd/post.ts +0 -25
  25. package/src/cli/cmd/publish.ts +0 -64
  26. package/src/cli/cmd/scan.ts +0 -78
  27. package/src/cli/cmd/search.ts +0 -35
  28. package/src/cli/cmd/setup.ts +0 -352
  29. package/src/cli/cmd/tui.ts +0 -20
  30. package/src/cli/cmd/uninstall.ts +0 -281
  31. package/src/cli/cmd/update.ts +0 -123
  32. package/src/cli/cmd/vote.ts +0 -50
  33. package/src/cli/cmd/whoami.ts +0 -18
  34. package/src/cli/mcp-print.ts +0 -6
  35. package/src/cli/ui.ts +0 -250
  36. package/src/config/index.ts +0 -55
  37. package/src/flag/index.ts +0 -23
  38. package/src/global/index.ts +0 -38
  39. package/src/id/index.ts +0 -20
  40. package/src/index.ts +0 -200
  41. package/src/mcp/__tests__/client.test.ts +0 -149
  42. package/src/mcp/__tests__/e2e.ts +0 -327
  43. package/src/mcp/__tests__/integration.ts +0 -148
  44. package/src/mcp/client.ts +0 -148
  45. package/src/server/index.ts +0 -48
  46. package/src/storage/chat.ts +0 -71
  47. package/src/storage/db.ts +0 -85
  48. package/src/storage/schema.sql.ts +0 -39
  49. package/src/storage/schema.ts +0 -1
  50. package/src/tui/app.tsx +0 -184
  51. package/src/tui/commands.ts +0 -186
  52. package/src/tui/context/exit.tsx +0 -15
  53. package/src/tui/context/helper.tsx +0 -25
  54. package/src/tui/context/route.tsx +0 -24
  55. package/src/tui/context/theme.tsx +0 -470
  56. package/src/tui/routes/home.tsx +0 -660
  57. package/src/tui/routes/model.tsx +0 -210
  58. package/src/tui/routes/notifications.tsx +0 -87
  59. package/src/tui/routes/post.tsx +0 -102
  60. package/src/tui/routes/search.tsx +0 -105
  61. package/src/tui/routes/setup.tsx +0 -255
  62. package/src/tui/routes/trending.tsx +0 -107
  63. package/src/util/__tests__/context.test.ts +0 -31
  64. package/src/util/__tests__/lazy.test.ts +0 -37
  65. package/src/util/context.ts +0 -23
  66. package/src/util/error.ts +0 -46
  67. package/src/util/lazy.ts +0 -18
  68. package/src/util/log.ts +0 -142
  69. package/tsconfig.json +0 -11
package/src/ai/tools.ts DELETED
@@ -1,114 +0,0 @@
1
- import { tool, jsonSchema } from "ai"
2
- import { McpBridge } from "../mcp/client"
3
- import { Log } from "../util/log"
4
-
5
- const log = Log.create({ service: "ai-tools" })
6
-
7
- // ---------------------------------------------------------------------------
8
- // Tool display labels for the TUI streaming indicator.
9
- // Kept as a static fallback — new tools added to MCP will show their name
10
- // as-is if not listed here, which is acceptable.
11
- // ---------------------------------------------------------------------------
12
- export const TOOL_LABELS: Record<string, string> = {
13
- scan_sessions: "Scanning IDE sessions...",
14
- read_session: "Reading session...",
15
- analyze_session: "Analyzing session...",
16
- post_to_codeblog: "Publishing post...",
17
- auto_post: "Auto-posting...",
18
- weekly_digest: "Generating weekly digest...",
19
- browse_posts: "Browsing posts...",
20
- search_posts: "Searching posts...",
21
- read_post: "Reading post...",
22
- comment_on_post: "Posting comment...",
23
- vote_on_post: "Voting...",
24
- edit_post: "Editing post...",
25
- delete_post: "Deleting post...",
26
- bookmark_post: "Bookmarking...",
27
- browse_by_tag: "Browsing tags...",
28
- trending_topics: "Loading trending...",
29
- explore_and_engage: "Exploring posts...",
30
- join_debate: "Loading debates...",
31
- my_notifications: "Checking notifications...",
32
- manage_agents: "Managing agents...",
33
- my_posts: "Loading your posts...",
34
- my_dashboard: "Loading dashboard...",
35
- follow_user: "Processing follow...",
36
- codeblog_setup: "Configuring CodeBlog...",
37
- codeblog_status: "Checking status...",
38
- }
39
-
40
- // ---------------------------------------------------------------------------
41
- // Helper: call MCP tool and return result
42
- // ---------------------------------------------------------------------------
43
- async function mcp(name: string, args: Record<string, unknown> = {}): Promise<any> {
44
- return McpBridge.callToolJSON(name, args)
45
- }
46
-
47
- // Strip undefined/null values from args before sending to MCP
48
- function clean(obj: Record<string, unknown>): Record<string, unknown> {
49
- const result: Record<string, unknown> = {}
50
- for (const [k, v] of Object.entries(obj)) {
51
- if (v !== undefined && v !== null) result[k] = v
52
- }
53
- return result
54
- }
55
-
56
- // ---------------------------------------------------------------------------
57
- // Schema normalization: ensure all JSON schemas are valid tool input schemas.
58
- // Some MCP tools have empty inputSchema ({}) which produces schemas without
59
- // "type": "object", causing providers like DeepSeek/Qwen to reject them.
60
- // ---------------------------------------------------------------------------
61
- function normalizeToolSchema(schema: Record<string, unknown>): Record<string, unknown> {
62
- const normalized = { ...schema }
63
- if (!normalized.type) normalized.type = "object"
64
- if (normalized.type === "object" && !normalized.properties) normalized.properties = {}
65
- return normalized
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // Dynamic tool discovery from MCP server
70
- // ---------------------------------------------------------------------------
71
- let _cached: Record<string, any> | null = null
72
-
73
- /**
74
- * Build AI SDK tools dynamically from the MCP server's listTools() response.
75
- * Results are cached after the first successful call.
76
- */
77
- export async function getChatTools(): Promise<Record<string, any>> {
78
- if (_cached) return _cached
79
-
80
- const { tools: mcpTools } = await McpBridge.listTools()
81
- log.info("discovered MCP tools", { count: mcpTools.length, names: mcpTools.map((t) => t.name) })
82
-
83
- const tools: Record<string, any> = {}
84
-
85
- for (const t of mcpTools) {
86
- const name = t.name
87
- const rawSchema = (t.inputSchema ?? {}) as Record<string, unknown>
88
-
89
- tools[name] = tool({
90
- description: t.description || name,
91
- inputSchema: jsonSchema(normalizeToolSchema(rawSchema)),
92
- execute: async (args: any) => {
93
- log.info("execute tool", { name, args })
94
- const result = await mcp(name, clean(args))
95
- const resultStr = typeof result === "string" ? result : JSON.stringify(result)
96
- log.info("execute tool result", { name, resultType: typeof result, resultLength: resultStr.length, resultPreview: resultStr.slice(0, 300) })
97
- // Truncate very large tool results to avoid overwhelming the LLM context
98
- if (resultStr.length > 8000) {
99
- log.info("truncating large tool result", { name, originalLength: resultStr.length })
100
- return resultStr.slice(0, 8000) + "\n...(truncated)"
101
- }
102
- return resultStr
103
- },
104
- })
105
- }
106
-
107
- _cached = tools
108
- return tools
109
- }
110
-
111
- /** Clear the cached tools (useful for testing or reconnection). */
112
- export function clearChatToolsCache(): void {
113
- _cached = null
114
- }
package/src/auth/index.ts DELETED
@@ -1,47 +0,0 @@
1
- import path from "path"
2
- import { Global } from "../global"
3
- import z from "zod"
4
-
5
- export namespace Auth {
6
- export const Token = z
7
- .object({
8
- type: z.enum(["jwt", "apikey"]),
9
- value: z.string(),
10
- expires: z.number().optional(),
11
- username: z.string().optional(),
12
- })
13
- .meta({ ref: "AuthToken" })
14
- export type Token = z.infer<typeof Token>
15
-
16
- const filepath = path.join(Global.Path.data, "auth.json")
17
-
18
- export async function get(): Promise<Token | null> {
19
- const file = Bun.file(filepath)
20
- const data = await file.json().catch(() => null)
21
- if (!data) return null
22
- const parsed = Token.safeParse(data)
23
- if (!parsed.success) return null
24
- return parsed.data
25
- }
26
-
27
- export async function set(token: Token) {
28
- await Bun.write(Bun.file(filepath, { mode: 0o600 }), JSON.stringify(token, null, 2))
29
- }
30
-
31
- export async function remove() {
32
- const fs = await import("fs/promises")
33
- await fs.unlink(filepath).catch(() => {})
34
- }
35
-
36
- export async function header(): Promise<Record<string, string>> {
37
- const token = await get()
38
- if (!token) return {}
39
- if (token.type === "apikey") return { Authorization: `Bearer ${token.value}` }
40
- return { Authorization: `Bearer ${token.value}` }
41
- }
42
-
43
- export async function authenticated(): Promise<boolean> {
44
- const token = await get()
45
- return token !== null
46
- }
47
- }
package/src/auth/oauth.ts DELETED
@@ -1,108 +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
- Server.start(wrapped, port)
95
-
96
- const authUrl = `${base}/auth/cli?port=${port}`
97
- log.info("opening browser", { url: authUrl })
98
- if (options?.onUrl) options.onUrl(authUrl)
99
- open(authUrl)
100
-
101
- // Timeout after 5 minutes
102
- setTimeout(() => {
103
- Server.stop()
104
- reject(new Error("OAuth login timed out"))
105
- }, 5 * 60 * 1000)
106
- })
107
- }
108
- }
@@ -1,225 +0,0 @@
1
- import { describe, test, expect, mock, beforeEach } 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 = undefined as any
57
- })
58
-
59
- // ---------------------------------------------------------------------------
60
- // ScanCommand
61
- // ---------------------------------------------------------------------------
62
- describe("ScanCommand", () => {
63
- test("has correct command name and describe", () => {
64
- expect(ScanCommand.command).toBe("scan")
65
- expect(ScanCommand.describe).toBeTruthy()
66
- })
67
-
68
- test("handler calls scan_sessions MCP tool", async () => {
69
- mockCallTool.mockImplementationOnce(() => Promise.resolve("[]"))
70
- await (ScanCommand.handler as any)({ limit: 10 })
71
- expect(mockCallTool).toHaveBeenCalledWith("scan_sessions", { limit: 10 })
72
- })
73
-
74
- test("handler calls codeblog_status when --status flag", async () => {
75
- mockCallTool.mockImplementationOnce(() => Promise.resolve("Status: OK"))
76
- await (ScanCommand.handler as any)({ status: true, limit: 20 })
77
- expect(mockCallTool).toHaveBeenCalledWith("codeblog_status", {})
78
- })
79
-
80
- test("handler passes source when provided", async () => {
81
- mockCallTool.mockImplementationOnce(() => Promise.resolve("[]"))
82
- await (ScanCommand.handler as any)({ limit: 5, source: "cursor" })
83
- expect(mockCallTool).toHaveBeenCalledWith("scan_sessions", { limit: 5, source: "cursor" })
84
- })
85
-
86
- test("handler sets exitCode on error", async () => {
87
- mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("fail")))
88
- await (ScanCommand.handler as any)({ limit: 10 })
89
- expect(process.exitCode).toBe(1)
90
- expect(mockError).toHaveBeenCalled()
91
- })
92
- })
93
-
94
- // ---------------------------------------------------------------------------
95
- // FeedCommand
96
- // ---------------------------------------------------------------------------
97
- describe("FeedCommand", () => {
98
- test("has correct command name", () => {
99
- expect(FeedCommand.command).toBe("feed")
100
- })
101
-
102
- test("handler calls browse_posts MCP tool", async () => {
103
- mockCallTool.mockImplementationOnce(() => Promise.resolve("post1\npost2"))
104
- await (FeedCommand.handler as any)({ limit: 15, page: 1, sort: "new" })
105
- expect(mockCallTool).toHaveBeenCalledWith("browse_posts", {
106
- limit: 15,
107
- page: 1,
108
- sort: "new",
109
- })
110
- })
111
-
112
- test("handler includes tag filter when provided", async () => {
113
- mockCallTool.mockImplementationOnce(() => Promise.resolve("post1"))
114
- await (FeedCommand.handler as any)({ limit: 10, page: 1, sort: "new", tag: "react" })
115
- expect(mockCallTool).toHaveBeenCalledWith("browse_posts", {
116
- limit: 10,
117
- page: 1,
118
- sort: "new",
119
- tag: "react",
120
- })
121
- })
122
-
123
- test("handler sets exitCode on error", async () => {
124
- mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("network")))
125
- await (FeedCommand.handler as any)({ limit: 10, page: 1, sort: "new" })
126
- expect(process.exitCode).toBe(1)
127
- })
128
- })
129
-
130
- // ---------------------------------------------------------------------------
131
- // SearchCommand
132
- // ---------------------------------------------------------------------------
133
- describe("SearchCommand", () => {
134
- test("has correct command format", () => {
135
- expect(SearchCommand.command).toBe("search <query>")
136
- })
137
-
138
- test("handler calls search_posts MCP tool", async () => {
139
- mockCallTool.mockImplementationOnce(() => Promise.resolve("result1"))
140
- await (SearchCommand.handler as any)({ query: "typescript", limit: 20 })
141
- expect(mockCallTool).toHaveBeenCalledWith("search_posts", {
142
- query: "typescript",
143
- limit: 20,
144
- })
145
- })
146
-
147
- test("handler sets exitCode on error", async () => {
148
- mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("search failed")))
149
- await (SearchCommand.handler as any)({ query: "test", limit: 10 })
150
- expect(process.exitCode).toBe(1)
151
- })
152
- })
153
-
154
- // ---------------------------------------------------------------------------
155
- // PublishCommand
156
- // ---------------------------------------------------------------------------
157
- describe("PublishCommand", () => {
158
- test("has correct command name", () => {
159
- expect(PublishCommand.command).toBe("publish")
160
- })
161
-
162
- test("handler calls auto_post for normal publish", async () => {
163
- mockCallTool.mockImplementationOnce(() => Promise.resolve("Published!"))
164
- await (PublishCommand.handler as any)({ dryRun: false, weekly: false })
165
- expect(mockCallTool).toHaveBeenCalledWith("auto_post", {
166
- dry_run: false,
167
- })
168
- })
169
-
170
- test("handler passes dry_run correctly when true", async () => {
171
- mockCallTool.mockImplementationOnce(() => Promise.resolve("Preview"))
172
- await (PublishCommand.handler as any)({ dryRun: true, weekly: false })
173
- expect(mockCallTool).toHaveBeenCalledWith("auto_post", {
174
- dry_run: true,
175
- })
176
- })
177
-
178
- test("handler calls weekly_digest for --weekly", async () => {
179
- mockCallTool.mockImplementationOnce(() => Promise.resolve("Digest"))
180
- await (PublishCommand.handler as any)({ dryRun: false, weekly: true })
181
- expect(mockCallTool).toHaveBeenCalledWith("weekly_digest", {
182
- dry_run: false,
183
- post: true,
184
- })
185
- })
186
-
187
- test("weekly with dry-run sets dry_run true", async () => {
188
- mockCallTool.mockImplementationOnce(() => Promise.resolve("Digest preview"))
189
- await (PublishCommand.handler as any)({ dryRun: true, weekly: true })
190
- expect(mockCallTool).toHaveBeenCalledWith("weekly_digest", {
191
- dry_run: true,
192
- })
193
- })
194
-
195
- test("handler passes source and style options", async () => {
196
- mockCallTool.mockImplementationOnce(() => Promise.resolve("OK"))
197
- await (PublishCommand.handler as any)({
198
- dryRun: false,
199
- weekly: false,
200
- source: "cursor",
201
- style: "bug-story",
202
- })
203
- expect(mockCallTool).toHaveBeenCalledWith("auto_post", {
204
- dry_run: false,
205
- source: "cursor",
206
- style: "bug-story",
207
- })
208
- })
209
-
210
- test("handler sets exitCode on error", async () => {
211
- mockCallTool.mockImplementationOnce(() => Promise.reject(new Error("publish failed")))
212
- await (PublishCommand.handler as any)({ dryRun: false, weekly: false })
213
- expect(process.exitCode).toBe(1)
214
- })
215
-
216
- // Regression test: dry_run should NOT always be true
217
- test("REGRESSION: publish --weekly does NOT always set dry_run=true", async () => {
218
- mockCallTool.mockImplementationOnce(() => Promise.resolve("Posted"))
219
- await (PublishCommand.handler as any)({ dryRun: false, weekly: true })
220
- const callArgs = mockCallTool.mock.calls[0]
221
- expect(callArgs![1]).toHaveProperty("dry_run", false)
222
- expect(callArgs![1]).toHaveProperty("post", true)
223
- })
224
- })
225
- })
@@ -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
- }