codeblog-app 2.0.1 → 2.0.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 -71
- 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 -110
- package/src/ai/__tests__/provider.test.ts +0 -184
- package/src/ai/__tests__/tools.test.ts +0 -90
- package/src/ai/chat.ts +0 -169
- package/src/ai/configure.ts +0 -89
- package/src/ai/provider.ts +0 -238
- package/src/ai/tools.ts +0 -321
- package/src/auth/index.ts +0 -47
- package/src/auth/oauth.ts +0 -93
- package/src/cli/__tests__/commands.test.ts +0 -225
- package/src/cli/__tests__/setup.test.ts +0 -57
- package/src/cli/cmd/agent.ts +0 -102
- package/src/cli/cmd/chat.ts +0 -190
- package/src/cli/cmd/comment.ts +0 -70
- package/src/cli/cmd/config.ts +0 -153
- package/src/cli/cmd/feed.ts +0 -57
- package/src/cli/cmd/forum.ts +0 -123
- package/src/cli/cmd/login.ts +0 -45
- package/src/cli/cmd/logout.ts +0 -12
- package/src/cli/cmd/me.ts +0 -202
- package/src/cli/cmd/post.ts +0 -29
- package/src/cli/cmd/publish.ts +0 -70
- package/src/cli/cmd/scan.ts +0 -80
- package/src/cli/cmd/search.ts +0 -40
- package/src/cli/cmd/setup.ts +0 -201
- package/src/cli/cmd/tui.ts +0 -20
- package/src/cli/cmd/update.ts +0 -78
- package/src/cli/cmd/vote.ts +0 -50
- package/src/cli/cmd/whoami.ts +0 -21
- package/src/cli/ui.ts +0 -124
- package/src/config/index.ts +0 -54
- 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 -186
- package/src/mcp/__tests__/client.test.ts +0 -149
- package/src/mcp/__tests__/e2e.ts +0 -327
- package/src/mcp/__tests__/integration.ts +0 -148
- package/src/mcp/client.ts +0 -148
- package/src/server/index.ts +0 -48
- package/src/storage/chat.ts +0 -92
- 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/app.tsx +0 -163
- package/src/tui/commands.ts +0 -177
- 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 -470
- package/src/tui/routes/home.tsx +0 -490
- package/src/tui/routes/model.tsx +0 -209
- package/src/tui/routes/notifications.tsx +0 -85
- package/src/tui/routes/post.tsx +0 -108
- package/src/tui/routes/search.tsx +0 -104
- package/src/tui/routes/setup.tsx +0 -255
- package/src/tui/routes/trending.tsx +0 -107
- 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 -142
- package/tsconfig.json +0 -11
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test"
|
|
2
|
-
|
|
3
|
-
// Import the pure extraction functions directly — no mocks needed
|
|
4
|
-
const { extractApiKey, extractUsername } = await import("../cmd/setup")
|
|
5
|
-
|
|
6
|
-
describe("Setup — extractApiKey", () => {
|
|
7
|
-
test("extracts API key from registration response", () => {
|
|
8
|
-
const text =
|
|
9
|
-
"✅ CodeBlog setup complete!\n\n" +
|
|
10
|
-
"Account: alice (alice@example.com)\nAgent: alice-agent\n" +
|
|
11
|
-
"Agent is activated and ready to post.\n\n" +
|
|
12
|
-
"API-KEY: cbk_abc123xyz\n\n" +
|
|
13
|
-
'Try: "Scan my coding sessions and post an insight to CodeBlog."'
|
|
14
|
-
expect(extractApiKey(text)).toBe("cbk_abc123xyz")
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test("extracts API key from api_key verification response", () => {
|
|
18
|
-
const text =
|
|
19
|
-
"✅ CodeBlog setup complete!\n\n" +
|
|
20
|
-
"Agent: bob-agent\nOwner: bob\nPosts: 5\n\n" +
|
|
21
|
-
"API-KEY: cbk_existing_key_999\n\n" +
|
|
22
|
-
'Try: "Scan my coding sessions and post an insight to CodeBlog."'
|
|
23
|
-
expect(extractApiKey(text)).toBe("cbk_existing_key_999")
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test("returns null when no API-KEY line present", () => {
|
|
27
|
-
const text = "✅ CodeBlog setup complete!\n\nAgent: test-agent\n"
|
|
28
|
-
expect(extractApiKey(text)).toBeNull()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test("handles API-KEY with extra whitespace", () => {
|
|
32
|
-
const text = "API-KEY: cbk_spaced_key \nsome other line"
|
|
33
|
-
expect(extractApiKey(text)).toBe("cbk_spaced_key")
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe("Setup — extractUsername", () => {
|
|
38
|
-
test("extracts username from Account line (registration)", () => {
|
|
39
|
-
const text = "Account: alice (alice@example.com)\nAgent: alice-agent\n"
|
|
40
|
-
expect(extractUsername(text)).toBe("alice")
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test("extracts username from Owner line (api_key verification)", () => {
|
|
44
|
-
const text = "Agent: bob-agent\nOwner: bob\nPosts: 5\n"
|
|
45
|
-
expect(extractUsername(text)).toBe("bob")
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test("prefers Account over Owner when both present", () => {
|
|
49
|
-
const text = "Account: alice (alice@example.com)\nOwner: bob\n"
|
|
50
|
-
expect(extractUsername(text)).toBe("alice")
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test("returns null when neither Account nor Owner present", () => {
|
|
54
|
-
const text = "✅ CodeBlog setup complete!\nAgent: test-agent\n"
|
|
55
|
-
expect(extractUsername(text)).toBeNull()
|
|
56
|
-
})
|
|
57
|
-
})
|
package/src/cli/cmd/agent.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { McpBridge } from "../../mcp/client"
|
|
3
|
-
import { UI } from "../ui"
|
|
4
|
-
|
|
5
|
-
export const AgentCommand: CommandModule = {
|
|
6
|
-
command: "agent",
|
|
7
|
-
describe: "Manage your CodeBlog agents",
|
|
8
|
-
builder: (yargs) =>
|
|
9
|
-
yargs
|
|
10
|
-
.command({
|
|
11
|
-
command: "list",
|
|
12
|
-
aliases: ["ls"],
|
|
13
|
-
describe: "List all your agents",
|
|
14
|
-
handler: async () => {
|
|
15
|
-
try {
|
|
16
|
-
const text = await McpBridge.callTool("manage_agents", { action: "list" })
|
|
17
|
-
console.log("")
|
|
18
|
-
for (const line of text.split("\n")) {
|
|
19
|
-
console.log(` ${line}`)
|
|
20
|
-
}
|
|
21
|
-
console.log("")
|
|
22
|
-
} catch (err) {
|
|
23
|
-
UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
24
|
-
process.exitCode = 1
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
})
|
|
28
|
-
.command({
|
|
29
|
-
command: "create",
|
|
30
|
-
describe: "Create a new agent",
|
|
31
|
-
builder: (y) =>
|
|
32
|
-
y
|
|
33
|
-
.option("name", {
|
|
34
|
-
alias: "n",
|
|
35
|
-
describe: "Agent name",
|
|
36
|
-
type: "string",
|
|
37
|
-
demandOption: true,
|
|
38
|
-
})
|
|
39
|
-
.option("source", {
|
|
40
|
-
alias: "s",
|
|
41
|
-
describe: "IDE source: claude-code, cursor, codex, windsurf, git, other",
|
|
42
|
-
type: "string",
|
|
43
|
-
demandOption: true,
|
|
44
|
-
})
|
|
45
|
-
.option("description", {
|
|
46
|
-
alias: "d",
|
|
47
|
-
describe: "Agent description",
|
|
48
|
-
type: "string",
|
|
49
|
-
}),
|
|
50
|
-
handler: async (args) => {
|
|
51
|
-
try {
|
|
52
|
-
const mcpArgs: Record<string, unknown> = {
|
|
53
|
-
action: "create",
|
|
54
|
-
name: args.name,
|
|
55
|
-
source_type: args.source,
|
|
56
|
-
}
|
|
57
|
-
if (args.description) mcpArgs.description = args.description
|
|
58
|
-
|
|
59
|
-
const text = await McpBridge.callTool("manage_agents", mcpArgs)
|
|
60
|
-
console.log("")
|
|
61
|
-
for (const line of text.split("\n")) {
|
|
62
|
-
console.log(` ${line}`)
|
|
63
|
-
}
|
|
64
|
-
console.log("")
|
|
65
|
-
} catch (err) {
|
|
66
|
-
UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
67
|
-
process.exitCode = 1
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
})
|
|
71
|
-
.command({
|
|
72
|
-
command: "delete <agent_id>",
|
|
73
|
-
describe: "Delete an agent",
|
|
74
|
-
builder: (y) =>
|
|
75
|
-
y.positional("agent_id", {
|
|
76
|
-
describe: "Agent ID to delete",
|
|
77
|
-
type: "string",
|
|
78
|
-
demandOption: true,
|
|
79
|
-
}),
|
|
80
|
-
handler: async (args) => {
|
|
81
|
-
const answer = await UI.input(` Are you sure you want to delete agent ${args.agent_id}? (y/n) [n]: `)
|
|
82
|
-
if (answer.toLowerCase() !== "y") {
|
|
83
|
-
UI.info("Cancelled.")
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const text = await McpBridge.callTool("manage_agents", {
|
|
88
|
-
action: "delete",
|
|
89
|
-
agent_id: args.agent_id,
|
|
90
|
-
})
|
|
91
|
-
console.log("")
|
|
92
|
-
console.log(` ${text}`)
|
|
93
|
-
console.log("")
|
|
94
|
-
} catch (err) {
|
|
95
|
-
UI.error(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
96
|
-
process.exitCode = 1
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
})
|
|
100
|
-
.demandCommand(1, "Run `codeblog agent --help` to see available subcommands"),
|
|
101
|
-
handler: () => {},
|
|
102
|
-
}
|
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
|
-
}
|
package/src/cli/cmd/comment.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { McpBridge } from "../../mcp/client"
|
|
3
|
-
import { UI } from "../ui"
|
|
4
|
-
|
|
5
|
-
export const CommentCommand: CommandModule = {
|
|
6
|
-
command: "comment <post_id>",
|
|
7
|
-
describe: "Comment on a post",
|
|
8
|
-
builder: (yargs) =>
|
|
9
|
-
yargs
|
|
10
|
-
.positional("post_id", {
|
|
11
|
-
describe: "Post ID to comment on",
|
|
12
|
-
type: "string",
|
|
13
|
-
demandOption: true,
|
|
14
|
-
})
|
|
15
|
-
.option("reply", {
|
|
16
|
-
alias: "r",
|
|
17
|
-
describe: "Reply to a specific comment by its ID",
|
|
18
|
-
type: "string",
|
|
19
|
-
}),
|
|
20
|
-
handler: async (args) => {
|
|
21
|
-
const postId = args.post_id as string
|
|
22
|
-
|
|
23
|
-
console.log("")
|
|
24
|
-
console.log(` ${UI.Style.TEXT_DIM}Write your comment (end with an empty line):${UI.Style.TEXT_NORMAL}`)
|
|
25
|
-
console.log("")
|
|
26
|
-
|
|
27
|
-
// Read multiline input
|
|
28
|
-
const lines: string[] = []
|
|
29
|
-
const readline = require("readline")
|
|
30
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
31
|
-
|
|
32
|
-
const content = await new Promise<string>((resolve) => {
|
|
33
|
-
rl.on("line", (line: string) => {
|
|
34
|
-
if (line === "" && lines.length > 0) {
|
|
35
|
-
rl.close()
|
|
36
|
-
resolve(lines.join("\n"))
|
|
37
|
-
} else {
|
|
38
|
-
lines.push(line)
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
rl.on("close", () => {
|
|
42
|
-
resolve(lines.join("\n"))
|
|
43
|
-
})
|
|
44
|
-
rl.prompt()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
if (!content.trim()) {
|
|
48
|
-
UI.warn("Empty comment, skipped.")
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const mcpArgs: Record<string, unknown> = {
|
|
54
|
-
post_id: postId,
|
|
55
|
-
content: content.trim(),
|
|
56
|
-
}
|
|
57
|
-
if (args.reply) mcpArgs.parent_id = args.reply
|
|
58
|
-
|
|
59
|
-
const text = await McpBridge.callTool("comment_on_post", mcpArgs)
|
|
60
|
-
console.log("")
|
|
61
|
-
for (const line of text.split("\n")) {
|
|
62
|
-
console.log(` ${line}`)
|
|
63
|
-
}
|
|
64
|
-
console.log("")
|
|
65
|
-
} catch (err) {
|
|
66
|
-
UI.error(`Comment failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
67
|
-
process.exitCode = 1
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
}
|
package/src/cli/cmd/config.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { Config } from "../../config"
|
|
3
|
-
import { AIProvider } from "../../ai/provider"
|
|
4
|
-
import { UI } from "../ui"
|
|
5
|
-
|
|
6
|
-
export const ConfigCommand: CommandModule = {
|
|
7
|
-
command: "config",
|
|
8
|
-
describe: "Configure AI provider, model, and server settings",
|
|
9
|
-
builder: (yargs) =>
|
|
10
|
-
yargs
|
|
11
|
-
.option("provider", {
|
|
12
|
-
describe: "AI provider: anthropic, openai, google, xai, mistral, groq, etc.",
|
|
13
|
-
type: "string",
|
|
14
|
-
})
|
|
15
|
-
.option("api-key", {
|
|
16
|
-
describe: "API key for the provider",
|
|
17
|
-
type: "string",
|
|
18
|
-
})
|
|
19
|
-
.option("model", {
|
|
20
|
-
alias: "m",
|
|
21
|
-
describe: "Set default AI model (e.g. claude-sonnet-4-20250514, gpt-4o)",
|
|
22
|
-
type: "string",
|
|
23
|
-
})
|
|
24
|
-
.option("url", {
|
|
25
|
-
describe: "Set CodeBlog server URL",
|
|
26
|
-
type: "string",
|
|
27
|
-
})
|
|
28
|
-
.option("list", {
|
|
29
|
-
alias: "l",
|
|
30
|
-
describe: "List available models and their status",
|
|
31
|
-
type: "boolean",
|
|
32
|
-
default: false,
|
|
33
|
-
})
|
|
34
|
-
.option("path", {
|
|
35
|
-
describe: "Show config file path",
|
|
36
|
-
type: "boolean",
|
|
37
|
-
default: false,
|
|
38
|
-
})
|
|
39
|
-
.option("base-url", {
|
|
40
|
-
describe: "Custom base URL for the provider (for third-party API proxies)",
|
|
41
|
-
type: "string",
|
|
42
|
-
})
|
|
43
|
-
.option("language", {
|
|
44
|
-
describe: "Default content language for posts (e.g. English, 中文, 日本語)",
|
|
45
|
-
type: "string",
|
|
46
|
-
}),
|
|
47
|
-
handler: async (args) => {
|
|
48
|
-
try {
|
|
49
|
-
if (args.path) {
|
|
50
|
-
console.log(Config.filepath)
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (args.list) {
|
|
55
|
-
const models = await AIProvider.available()
|
|
56
|
-
const providers = await AIProvider.listProviders()
|
|
57
|
-
|
|
58
|
-
console.log("")
|
|
59
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Providers${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${Object.keys(providers).length} from models.dev)${UI.Style.TEXT_NORMAL}`)
|
|
60
|
-
console.log("")
|
|
61
|
-
|
|
62
|
-
const configured = Object.entries(providers).filter(([, p]) => p.hasKey)
|
|
63
|
-
|
|
64
|
-
if (configured.length > 0) {
|
|
65
|
-
console.log(` ${UI.Style.TEXT_SUCCESS}Configured:${UI.Style.TEXT_NORMAL}`)
|
|
66
|
-
for (const [, p] of configured) {
|
|
67
|
-
console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_NORMAL_BOLD}${p.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${p.models.length} models)${UI.Style.TEXT_NORMAL}`)
|
|
68
|
-
}
|
|
69
|
-
console.log("")
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Built-in Models${UI.Style.TEXT_NORMAL}`)
|
|
73
|
-
console.log("")
|
|
74
|
-
for (const { model, hasKey } of models) {
|
|
75
|
-
const status = hasKey ? `${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_DIM}✗${UI.Style.TEXT_NORMAL}`
|
|
76
|
-
console.log(` ${status} ${UI.Style.TEXT_NORMAL_BOLD}${model.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${model.id})${UI.Style.TEXT_NORMAL}`)
|
|
77
|
-
console.log(` ${UI.Style.TEXT_DIM}${model.providerID} · ${(model.contextWindow / 1000).toFixed(0)}k context${UI.Style.TEXT_NORMAL}`)
|
|
78
|
-
}
|
|
79
|
-
console.log("")
|
|
80
|
-
console.log(` ${UI.Style.TEXT_DIM}✓ = API key configured, ✗ = needs key${UI.Style.TEXT_NORMAL}`)
|
|
81
|
-
console.log(` ${UI.Style.TEXT_DIM}Set key: codeblog config --provider anthropic --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
82
|
-
console.log(` ${UI.Style.TEXT_DIM}Any model from models.dev can be used with provider/model format${UI.Style.TEXT_NORMAL}`)
|
|
83
|
-
console.log("")
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (args.provider && (args.apiKey || args.baseUrl)) {
|
|
88
|
-
const cfg = await Config.load()
|
|
89
|
-
const providers = cfg.providers || {}
|
|
90
|
-
const existing = providers[args.provider as string] || {} as Config.ProviderConfig
|
|
91
|
-
if (args.apiKey) existing.api_key = args.apiKey as string
|
|
92
|
-
if (args.baseUrl) existing.base_url = args.baseUrl as string
|
|
93
|
-
providers[args.provider as string] = existing
|
|
94
|
-
await Config.save({ providers })
|
|
95
|
-
const parts: string[] = []
|
|
96
|
-
if (args.apiKey) parts.push("API key")
|
|
97
|
-
if (args.baseUrl) parts.push(`base URL (${args.baseUrl})`)
|
|
98
|
-
UI.success(`${args.provider} ${parts.join(" + ")} saved`)
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (args.model) {
|
|
103
|
-
await Config.save({ model: args.model as string })
|
|
104
|
-
UI.success(`Default model set to ${args.model}`)
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (args.url) {
|
|
109
|
-
await Config.save({ api_url: args.url as string })
|
|
110
|
-
UI.success(`Server URL set to ${args.url}`)
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (args.language) {
|
|
115
|
-
await Config.save({ default_language: args.language as string })
|
|
116
|
-
UI.success(`Default language set to ${args.language}`)
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Show current config
|
|
121
|
-
const cfg = await Config.load()
|
|
122
|
-
const model = cfg.model || AIProvider.DEFAULT_MODEL
|
|
123
|
-
const providers = cfg.providers || {}
|
|
124
|
-
|
|
125
|
-
console.log("")
|
|
126
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Current Config${UI.Style.TEXT_NORMAL}`)
|
|
127
|
-
console.log(` ${UI.Style.TEXT_DIM}${Config.filepath}${UI.Style.TEXT_NORMAL}`)
|
|
128
|
-
console.log("")
|
|
129
|
-
console.log(` Model: ${UI.Style.TEXT_HIGHLIGHT}${model}${UI.Style.TEXT_NORMAL}`)
|
|
130
|
-
console.log(` API URL: ${cfg.api_url || "https://codeblog.ai"}`)
|
|
131
|
-
console.log(` Language: ${cfg.default_language || `${UI.Style.TEXT_DIM}(server default)${UI.Style.TEXT_NORMAL}`}`)
|
|
132
|
-
console.log("")
|
|
133
|
-
|
|
134
|
-
if (Object.keys(providers).length > 0) {
|
|
135
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}AI Providers${UI.Style.TEXT_NORMAL}`)
|
|
136
|
-
for (const [id, p] of Object.entries(providers)) {
|
|
137
|
-
const masked = p.api_key ? p.api_key.slice(0, 8) + "..." : "not set"
|
|
138
|
-
const url = p.base_url ? ` → ${p.base_url}` : ""
|
|
139
|
-
console.log(` ${UI.Style.TEXT_SUCCESS}✓${UI.Style.TEXT_NORMAL} ${id}: ${UI.Style.TEXT_DIM}${masked}${url}${UI.Style.TEXT_NORMAL}`)
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
console.log(` ${UI.Style.TEXT_DIM}No AI providers configured.${UI.Style.TEXT_NORMAL}`)
|
|
143
|
-
console.log(` ${UI.Style.TEXT_DIM}Set one: codeblog config --provider anthropic --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
144
|
-
console.log(` ${UI.Style.TEXT_DIM}Third-party proxy: codeblog config --provider anthropic --api-key sk-... --base-url https://proxy.example.com${UI.Style.TEXT_NORMAL}`)
|
|
145
|
-
console.log(` ${UI.Style.TEXT_DIM}Or use env: ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL${UI.Style.TEXT_NORMAL}`)
|
|
146
|
-
}
|
|
147
|
-
console.log("")
|
|
148
|
-
} catch (err) {
|
|
149
|
-
UI.error(`Config failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
150
|
-
process.exitCode = 1
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
}
|
package/src/cli/cmd/feed.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs"
|
|
2
|
-
import { McpBridge } from "../../mcp/client"
|
|
3
|
-
import { UI } from "../ui"
|
|
4
|
-
|
|
5
|
-
export const FeedCommand: CommandModule = {
|
|
6
|
-
command: "feed",
|
|
7
|
-
describe: "Browse the CodeBlog feed",
|
|
8
|
-
builder: (yargs) =>
|
|
9
|
-
yargs
|
|
10
|
-
.option("page", {
|
|
11
|
-
describe: "Page number",
|
|
12
|
-
type: "number",
|
|
13
|
-
default: 1,
|
|
14
|
-
})
|
|
15
|
-
.option("limit", {
|
|
16
|
-
describe: "Posts per page",
|
|
17
|
-
type: "number",
|
|
18
|
-
default: 15,
|
|
19
|
-
})
|
|
20
|
-
.option("tag", {
|
|
21
|
-
describe: "Filter by tag",
|
|
22
|
-
type: "string",
|
|
23
|
-
})
|
|
24
|
-
.option("sort", {
|
|
25
|
-
describe: "Sort: new, hot, top",
|
|
26
|
-
type: "string",
|
|
27
|
-
default: "new",
|
|
28
|
-
}),
|
|
29
|
-
handler: async (args) => {
|
|
30
|
-
try {
|
|
31
|
-
const mcpArgs: Record<string, unknown> = {
|
|
32
|
-
limit: args.limit,
|
|
33
|
-
page: args.page,
|
|
34
|
-
sort: args.sort,
|
|
35
|
-
}
|
|
36
|
-
if (args.tag) mcpArgs.tag = args.tag
|
|
37
|
-
|
|
38
|
-
const text = await McpBridge.callTool("browse_posts", mcpArgs)
|
|
39
|
-
|
|
40
|
-
const tagFilter = args.tag ? ` ${UI.Style.TEXT_INFO}#${args.tag}${UI.Style.TEXT_NORMAL}` : ""
|
|
41
|
-
console.log("")
|
|
42
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Posts${UI.Style.TEXT_NORMAL}${tagFilter} ${UI.Style.TEXT_DIM}page ${args.page}${UI.Style.TEXT_NORMAL}`)
|
|
43
|
-
console.log("")
|
|
44
|
-
|
|
45
|
-
for (const line of text.split("\n")) {
|
|
46
|
-
console.log(` ${line}`)
|
|
47
|
-
}
|
|
48
|
-
console.log("")
|
|
49
|
-
|
|
50
|
-
console.log(` ${UI.Style.TEXT_DIM}Next page: codeblog feed --page ${(args.page as number) + 1}${UI.Style.TEXT_NORMAL}`)
|
|
51
|
-
console.log("")
|
|
52
|
-
} catch (err) {
|
|
53
|
-
UI.error(`Failed to fetch feed: ${err instanceof Error ? err.message : String(err)}`)
|
|
54
|
-
process.exitCode = 1
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
}
|