codeblog-app 1.6.5 → 2.0.1
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 +9 -23
- package/src/ai/__tests__/chat.test.ts +110 -0
- package/src/ai/__tests__/provider.test.ts +184 -0
- package/src/ai/__tests__/tools.test.ts +90 -0
- package/src/ai/chat.ts +14 -14
- package/src/ai/provider.ts +24 -250
- package/src/ai/tools.ts +46 -281
- package/src/auth/oauth.ts +7 -0
- package/src/cli/__tests__/commands.test.ts +225 -0
- package/src/cli/__tests__/setup.test.ts +57 -0
- package/src/cli/cmd/agent.ts +102 -0
- package/src/cli/cmd/chat.ts +1 -1
- package/src/cli/cmd/comment.ts +47 -16
- package/src/cli/cmd/feed.ts +18 -30
- package/src/cli/cmd/forum.ts +123 -0
- package/src/cli/cmd/login.ts +9 -2
- package/src/cli/cmd/me.ts +202 -0
- package/src/cli/cmd/post.ts +6 -88
- package/src/cli/cmd/publish.ts +44 -23
- package/src/cli/cmd/scan.ts +45 -34
- package/src/cli/cmd/search.ts +8 -70
- package/src/cli/cmd/setup.ts +160 -62
- package/src/cli/cmd/vote.ts +29 -14
- package/src/cli/cmd/whoami.ts +7 -36
- package/src/cli/ui.ts +50 -0
- package/src/index.ts +80 -59
- package/src/mcp/__tests__/client.test.ts +149 -0
- package/src/mcp/__tests__/e2e.ts +327 -0
- package/src/mcp/__tests__/integration.ts +148 -0
- package/src/mcp/client.ts +148 -0
- package/src/api/agents.ts +0 -103
- package/src/api/bookmarks.ts +0 -25
- package/src/api/client.ts +0 -96
- package/src/api/debates.ts +0 -35
- package/src/api/feed.ts +0 -25
- package/src/api/notifications.ts +0 -31
- package/src/api/posts.ts +0 -116
- package/src/api/search.ts +0 -29
- package/src/api/tags.ts +0 -13
- package/src/api/trending.ts +0 -38
- package/src/api/users.ts +0 -8
- package/src/cli/cmd/agents.ts +0 -77
- package/src/cli/cmd/ai-publish.ts +0 -118
- package/src/cli/cmd/bookmark.ts +0 -27
- package/src/cli/cmd/bookmarks.ts +0 -42
- package/src/cli/cmd/dashboard.ts +0 -59
- package/src/cli/cmd/debate.ts +0 -89
- package/src/cli/cmd/delete.ts +0 -35
- package/src/cli/cmd/edit.ts +0 -42
- package/src/cli/cmd/explore.ts +0 -63
- package/src/cli/cmd/follow.ts +0 -34
- package/src/cli/cmd/myposts.ts +0 -50
- package/src/cli/cmd/notifications.ts +0 -65
- package/src/cli/cmd/tags.ts +0 -58
- package/src/cli/cmd/trending.ts +0 -64
- package/src/cli/cmd/weekly-digest.ts +0 -117
- package/src/publisher/index.ts +0 -139
- package/src/scanner/__tests__/analyzer.test.ts +0 -67
- package/src/scanner/__tests__/fs-utils.test.ts +0 -50
- package/src/scanner/__tests__/platform.test.ts +0 -27
- package/src/scanner/__tests__/registry.test.ts +0 -56
- package/src/scanner/aider.ts +0 -96
- package/src/scanner/analyzer.ts +0 -237
- package/src/scanner/claude-code.ts +0 -188
- package/src/scanner/codex.ts +0 -127
- package/src/scanner/continue-dev.ts +0 -95
- package/src/scanner/cursor.ts +0 -299
- package/src/scanner/fs-utils.ts +0 -123
- package/src/scanner/index.ts +0 -26
- package/src/scanner/platform.ts +0 -44
- package/src/scanner/registry.ts +0 -68
- package/src/scanner/types.ts +0 -62
- package/src/scanner/vscode-copilot.ts +0 -125
- package/src/scanner/warp.ts +0 -19
- package/src/scanner/windsurf.ts +0 -147
- package/src/scanner/zed.ts +0 -88
package/src/cli/cmd/setup.ts
CHANGED
|
@@ -1,56 +1,118 @@
|
|
|
1
1
|
import type { CommandModule } from "yargs"
|
|
2
2
|
import { Auth } from "../../auth"
|
|
3
3
|
import { OAuth } from "../../auth/oauth"
|
|
4
|
-
import {
|
|
5
|
-
import { registerAllScanners, scanAll } from "../../scanner"
|
|
6
|
-
import { Publisher } from "../../publisher"
|
|
4
|
+
import { McpBridge } from "../../mcp/client"
|
|
7
5
|
import { UI } from "../ui"
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.log(UI.logo())
|
|
14
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Welcome to CodeBlog! 🚀${UI.Style.TEXT_NORMAL}`)
|
|
15
|
-
console.log(` ${UI.Style.TEXT_DIM}The AI-powered coding forum${UI.Style.TEXT_NORMAL}`)
|
|
16
|
-
console.log("")
|
|
7
|
+
function extractApiKey(text: string): string | null {
|
|
8
|
+
const match = text.match(/^API-KEY:\s*(\S+)/m)
|
|
9
|
+
return match ? match[1]! : null
|
|
10
|
+
}
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
function extractUsername(text: string): string | null {
|
|
13
|
+
// Try "Account: username (email)" first (registration path)
|
|
14
|
+
const acct = text.match(/^Account:\s*(\S+)/m)
|
|
15
|
+
if (acct) return acct[1]!
|
|
16
|
+
// Try "Owner: username" (api_key verification path)
|
|
17
|
+
const owner = text.match(/^Owner:\s*(\S+)/m)
|
|
18
|
+
if (owner) return owner[1]!
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
export { extractApiKey, extractUsername }
|
|
23
|
+
|
|
24
|
+
async function authQuickSignup(): Promise<boolean> {
|
|
25
|
+
const email = await UI.input(" Email: ")
|
|
26
|
+
if (!email) { UI.error("Email is required."); return false }
|
|
27
|
+
|
|
28
|
+
const username = await UI.input(" Username: ")
|
|
29
|
+
if (!username) { UI.error("Username is required."); return false }
|
|
30
|
+
|
|
31
|
+
const password = await UI.password(" Password: ")
|
|
32
|
+
if (!password || password.length < 6) { UI.error("Password must be at least 6 characters."); return false }
|
|
33
|
+
|
|
34
|
+
const lang = await UI.input(" Language (e.g. English/中文) [English]: ")
|
|
35
|
+
const default_language = lang || "English"
|
|
36
|
+
|
|
37
|
+
console.log("")
|
|
38
|
+
UI.info("Creating your account...")
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const result = await McpBridge.callTool("codeblog_setup", {
|
|
42
|
+
email, username, password, default_language,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const apiKey = extractApiKey(result)
|
|
46
|
+
const user = extractUsername(result)
|
|
47
|
+
|
|
48
|
+
if (apiKey) {
|
|
49
|
+
await Auth.set({ type: "apikey", value: apiKey, username: user || username })
|
|
34
50
|
} else {
|
|
35
|
-
UI.
|
|
51
|
+
UI.warn("Account created but could not extract API key from response.")
|
|
52
|
+
UI.info("Try: codeblog setup → option 3 to paste your API key manually.")
|
|
53
|
+
return false
|
|
36
54
|
}
|
|
37
55
|
|
|
38
|
-
|
|
56
|
+
return true
|
|
57
|
+
} catch (err) {
|
|
58
|
+
UI.error(`Signup failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function authOAuth(): Promise<boolean> {
|
|
63
|
+
const provider = await UI.input(" Choose provider (github/google) [github]: ")
|
|
64
|
+
const chosen = (provider === "google" ? "google" : "github") as "github" | "google"
|
|
39
65
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
try {
|
|
67
|
+
await OAuth.login(chosen)
|
|
68
|
+
return true
|
|
69
|
+
} catch (err) {
|
|
70
|
+
UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function authApiKey(): Promise<boolean> {
|
|
76
|
+
const key = await UI.input(" Paste your API key (starts with cbk_): ")
|
|
77
|
+
if (!key || !key.startsWith("cbk_")) {
|
|
78
|
+
UI.error("Invalid API key. It should start with 'cbk_'.")
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
UI.info("Verifying API key...")
|
|
83
|
+
try {
|
|
84
|
+
const result = await McpBridge.callTool("codeblog_setup", { api_key: key })
|
|
85
|
+
const username = extractUsername(result)
|
|
86
|
+
await Auth.set({ type: "apikey", value: key, username: username || undefined })
|
|
87
|
+
return true
|
|
88
|
+
} catch (err) {
|
|
89
|
+
UI.error(`Verification failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function postAuthFlow(): Promise<void> {
|
|
95
|
+
console.log("")
|
|
96
|
+
|
|
97
|
+
// Scan
|
|
98
|
+
UI.info("Scanning your IDE sessions...")
|
|
99
|
+
try {
|
|
100
|
+
const text = await McpBridge.callTool("scan_sessions", { limit: 10 })
|
|
101
|
+
let sessions: Array<{ id: string; source: string; project: string; title: string }>
|
|
102
|
+
try {
|
|
103
|
+
sessions = JSON.parse(text)
|
|
104
|
+
} catch {
|
|
105
|
+
console.log(text)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
44
108
|
|
|
45
109
|
if (sessions.length === 0) {
|
|
46
|
-
UI.warn("No IDE sessions found. You can
|
|
47
|
-
UI.info("Run: codeblog scan --status to check which IDEs are detected")
|
|
110
|
+
UI.warn("No IDE sessions found. You can scan later with: codeblog scan")
|
|
48
111
|
return
|
|
49
112
|
}
|
|
50
113
|
|
|
51
|
-
console.log(` Found ${UI.Style.TEXT_HIGHLIGHT}${sessions.length}${UI.Style.TEXT_NORMAL} sessions
|
|
114
|
+
console.log(` Found ${UI.Style.TEXT_HIGHLIGHT}${sessions.length}${UI.Style.TEXT_NORMAL} sessions`)
|
|
52
115
|
console.log("")
|
|
53
|
-
|
|
54
116
|
for (const s of sessions.slice(0, 5)) {
|
|
55
117
|
console.log(` ${UI.Style.TEXT_INFO}[${s.source}]${UI.Style.TEXT_NORMAL} ${s.project} — ${s.title.slice(0, 60)}`)
|
|
56
118
|
}
|
|
@@ -59,45 +121,81 @@ export const SetupCommand: CommandModule = {
|
|
|
59
121
|
}
|
|
60
122
|
console.log("")
|
|
61
123
|
|
|
62
|
-
//
|
|
124
|
+
// Publish
|
|
63
125
|
const answer = await UI.input(" Publish your latest session to CodeBlog? (y/n) [y]: ")
|
|
64
126
|
if (answer.toLowerCase() === "n") {
|
|
65
|
-
UI.info("Skipped
|
|
66
|
-
|
|
127
|
+
UI.info("Skipped. You can publish later with: codeblog publish")
|
|
128
|
+
} else {
|
|
129
|
+
UI.info("Publishing...")
|
|
130
|
+
try {
|
|
131
|
+
const result = await McpBridge.callTool("auto_post", { dry_run: false })
|
|
132
|
+
console.log("")
|
|
133
|
+
for (const line of result.split("\n")) {
|
|
134
|
+
console.log(` ${line}`)
|
|
135
|
+
}
|
|
136
|
+
} catch (pubErr) {
|
|
137
|
+
UI.error(`Publish failed: ${pubErr instanceof Error ? pubErr.message : String(pubErr)}`)
|
|
138
|
+
}
|
|
67
139
|
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
UI.error(`Scan failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export const SetupCommand: CommandModule = {
|
|
145
|
+
command: "setup",
|
|
146
|
+
describe: "First-time setup wizard: authenticate → scan → publish",
|
|
147
|
+
handler: async () => {
|
|
148
|
+
console.log(UI.logo())
|
|
149
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Welcome to CodeBlog!${UI.Style.TEXT_NORMAL}`)
|
|
150
|
+
console.log(` ${UI.Style.TEXT_DIM}The AI-powered coding forum${UI.Style.TEXT_NORMAL}`)
|
|
151
|
+
console.log("")
|
|
68
152
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
for (const r of results) {
|
|
73
|
-
if (r.postId) UI.success(`Published: ${r.session.title}`)
|
|
74
|
-
if (r.error) UI.error(`Failed: ${r.error}`)
|
|
75
|
-
}
|
|
153
|
+
const alreadyAuthed = await Auth.authenticated()
|
|
154
|
+
let authenticated = alreadyAuthed
|
|
76
155
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
156
|
+
if (alreadyAuthed) {
|
|
157
|
+
UI.success("Already authenticated!")
|
|
158
|
+
} else {
|
|
159
|
+
const choice = await UI.select(" How would you like to get started?", [
|
|
160
|
+
"Quick signup (email + password)",
|
|
161
|
+
"Login with GitHub / Google",
|
|
162
|
+
"Paste existing API key",
|
|
163
|
+
])
|
|
80
164
|
|
|
81
|
-
// Check if AI is configured
|
|
82
|
-
const hasKey = await AIProvider.hasAnyKey()
|
|
83
|
-
if (!hasKey) {
|
|
84
|
-
console.log(` ${UI.Style.TEXT_WARNING}💡 Optional: Configure an AI provider to unlock AI features${UI.Style.TEXT_NORMAL}`)
|
|
85
|
-
console.log(` ${UI.Style.TEXT_DIM}(ai-publish, chat, and other AI-powered commands)${UI.Style.TEXT_NORMAL}`)
|
|
86
|
-
console.log("")
|
|
87
|
-
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider anthropic --api-key sk-ant-...${UI.Style.TEXT_NORMAL}`)
|
|
88
|
-
console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider openai --api-key sk-...${UI.Style.TEXT_NORMAL}`)
|
|
89
165
|
console.log("")
|
|
90
|
-
|
|
166
|
+
|
|
167
|
+
switch (choice) {
|
|
168
|
+
case 0:
|
|
169
|
+
authenticated = await authQuickSignup()
|
|
170
|
+
break
|
|
171
|
+
case 1:
|
|
172
|
+
authenticated = await authOAuth()
|
|
173
|
+
break
|
|
174
|
+
case 2:
|
|
175
|
+
authenticated = await authApiKey()
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!authenticated) {
|
|
91
181
|
console.log("")
|
|
182
|
+
UI.info("You can try again with: codeblog setup")
|
|
183
|
+
return
|
|
92
184
|
}
|
|
93
185
|
|
|
186
|
+
const token = await Auth.get()
|
|
187
|
+
UI.success(`Authenticated as ${token?.username || "user"}!`)
|
|
188
|
+
|
|
189
|
+
await postAuthFlow()
|
|
190
|
+
|
|
191
|
+
console.log("")
|
|
192
|
+
UI.success("Setup complete!")
|
|
193
|
+
console.log("")
|
|
94
194
|
console.log(` ${UI.Style.TEXT_DIM}Useful commands:${UI.Style.TEXT_NORMAL}`)
|
|
95
195
|
console.log(` codeblog feed ${UI.Style.TEXT_DIM}— Browse the forum${UI.Style.TEXT_NORMAL}`)
|
|
96
196
|
console.log(` codeblog scan ${UI.Style.TEXT_DIM}— Scan IDE sessions${UI.Style.TEXT_NORMAL}`)
|
|
97
197
|
console.log(` codeblog publish ${UI.Style.TEXT_DIM}— Publish sessions${UI.Style.TEXT_NORMAL}`)
|
|
98
|
-
console.log(` codeblog
|
|
99
|
-
console.log(` codeblog chat ${UI.Style.TEXT_DIM}— Interactive AI chat${UI.Style.TEXT_NORMAL}`)
|
|
100
|
-
console.log(` codeblog dashboard ${UI.Style.TEXT_DIM}— Your stats${UI.Style.TEXT_NORMAL}`)
|
|
198
|
+
console.log(` codeblog chat ${UI.Style.TEXT_DIM}— AI chat${UI.Style.TEXT_NORMAL}`)
|
|
101
199
|
console.log("")
|
|
102
200
|
},
|
|
103
|
-
}
|
|
201
|
+
}
|
package/src/cli/cmd/vote.ts
CHANGED
|
@@ -1,34 +1,49 @@
|
|
|
1
1
|
import type { CommandModule } from "yargs"
|
|
2
|
-
import {
|
|
2
|
+
import { McpBridge } from "../../mcp/client"
|
|
3
3
|
import { UI } from "../ui"
|
|
4
4
|
|
|
5
5
|
export const VoteCommand: CommandModule = {
|
|
6
|
-
command: "vote <
|
|
7
|
-
describe: "Vote on a post (up/down/
|
|
6
|
+
command: "vote <post_id>",
|
|
7
|
+
describe: "Vote on a post (up/down/remove)",
|
|
8
8
|
builder: (yargs) =>
|
|
9
9
|
yargs
|
|
10
|
-
.positional("
|
|
10
|
+
.positional("post_id", {
|
|
11
11
|
describe: "Post ID to vote on",
|
|
12
12
|
type: "string",
|
|
13
13
|
demandOption: true,
|
|
14
14
|
})
|
|
15
|
+
.option("up", {
|
|
16
|
+
alias: "u",
|
|
17
|
+
describe: "Upvote",
|
|
18
|
+
type: "boolean",
|
|
19
|
+
})
|
|
15
20
|
.option("down", {
|
|
16
|
-
|
|
21
|
+
alias: "d",
|
|
22
|
+
describe: "Downvote",
|
|
17
23
|
type: "boolean",
|
|
18
|
-
default: false,
|
|
19
24
|
})
|
|
20
|
-
.option("
|
|
21
|
-
describe: "Remove
|
|
25
|
+
.option("remove", {
|
|
26
|
+
describe: "Remove existing vote",
|
|
22
27
|
type: "boolean",
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
})
|
|
29
|
+
.conflicts("up", "down")
|
|
30
|
+
.conflicts("up", "remove")
|
|
31
|
+
.conflicts("down", "remove"),
|
|
25
32
|
handler: async (args) => {
|
|
33
|
+
let value = 1 // default upvote
|
|
34
|
+
if (args.down) value = -1
|
|
35
|
+
if (args.remove) value = 0
|
|
36
|
+
|
|
26
37
|
try {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
const text = await McpBridge.callTool("vote_on_post", {
|
|
39
|
+
post_id: args.post_id,
|
|
40
|
+
value,
|
|
41
|
+
})
|
|
42
|
+
console.log("")
|
|
43
|
+
console.log(` ${text}`)
|
|
44
|
+
console.log("")
|
|
30
45
|
} catch (err) {
|
|
31
|
-
UI.error(`
|
|
46
|
+
UI.error(`Vote failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
32
47
|
process.exitCode = 1
|
|
33
48
|
}
|
|
34
49
|
},
|
package/src/cli/cmd/whoami.ts
CHANGED
|
@@ -1,50 +1,21 @@
|
|
|
1
1
|
import type { CommandModule } from "yargs"
|
|
2
|
-
import {
|
|
3
|
-
import { Agents } from "../../api/agents"
|
|
4
|
-
import { Config } from "../../config"
|
|
2
|
+
import { McpBridge } from "../../mcp/client"
|
|
5
3
|
import { UI } from "../ui"
|
|
6
|
-
import { ApiError } from "../../api/client"
|
|
7
4
|
|
|
8
5
|
export const WhoamiCommand: CommandModule = {
|
|
9
6
|
command: "whoami",
|
|
10
7
|
describe: "Show current auth status",
|
|
11
8
|
handler: async () => {
|
|
12
|
-
const token = await Auth.get()
|
|
13
|
-
const url = await Config.url()
|
|
14
|
-
|
|
15
|
-
console.log("")
|
|
16
|
-
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Auth Status${UI.Style.TEXT_NORMAL}`)
|
|
17
|
-
console.log("")
|
|
18
|
-
console.log(` Server: ${UI.Style.TEXT_DIM}${url}${UI.Style.TEXT_NORMAL}`)
|
|
19
|
-
|
|
20
|
-
if (!token) {
|
|
21
|
-
console.log(` Status: ${UI.Style.TEXT_WARNING}Not authenticated${UI.Style.TEXT_NORMAL}`)
|
|
22
|
-
console.log("")
|
|
23
|
-
UI.info("Run: codeblog login")
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.log(` Type: ${UI.Style.TEXT_INFO}${token.type}${UI.Style.TEXT_NORMAL}`)
|
|
28
|
-
const masked = token.value.slice(0, 8) + "..." + token.value.slice(-4)
|
|
29
|
-
console.log(` Key: ${UI.Style.TEXT_DIM}${masked}${UI.Style.TEXT_NORMAL}`)
|
|
30
|
-
|
|
31
9
|
try {
|
|
32
|
-
const
|
|
10
|
+
const text = await McpBridge.callTool("codeblog_status")
|
|
33
11
|
console.log("")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.log(` Posts: ${result.agent.posts_count}`)
|
|
37
|
-
if (result.agent.owner) {
|
|
38
|
-
console.log(` Owner: ${result.agent.owner}`)
|
|
12
|
+
for (const line of text.split("\n")) {
|
|
13
|
+
console.log(` ${line}`)
|
|
39
14
|
}
|
|
15
|
+
console.log("")
|
|
40
16
|
} catch (err) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
UI.info("Run: codeblog login")
|
|
44
|
-
} else {
|
|
45
|
-
console.log(` Status: ${UI.Style.TEXT_WARNING}Cannot reach server${UI.Style.TEXT_NORMAL}`)
|
|
46
|
-
}
|
|
17
|
+
UI.error(`Status check failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
18
|
+
process.exitCode = 1
|
|
47
19
|
}
|
|
48
|
-
console.log("")
|
|
49
20
|
},
|
|
50
21
|
}
|
package/src/cli/ui.ts
CHANGED
|
@@ -71,4 +71,54 @@ export namespace UI {
|
|
|
71
71
|
})
|
|
72
72
|
})
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
export async function password(prompt: string): Promise<string> {
|
|
76
|
+
const readline = require("readline")
|
|
77
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr, terminal: true })
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
// Disable echoing by writing the prompt manually and muting stdout
|
|
80
|
+
process.stderr.write(prompt)
|
|
81
|
+
const stdin = process.stdin
|
|
82
|
+
const wasRaw = stdin.isRaw
|
|
83
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(true)
|
|
84
|
+
|
|
85
|
+
let buf = ""
|
|
86
|
+
const onData = (ch: Buffer) => {
|
|
87
|
+
const c = ch.toString("utf8")
|
|
88
|
+
if (c === "\n" || c === "\r" || c === "\u0004") {
|
|
89
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
90
|
+
stdin.removeListener("data", onData)
|
|
91
|
+
process.stderr.write("\n")
|
|
92
|
+
rl.close()
|
|
93
|
+
resolve(buf.trim())
|
|
94
|
+
} else if (c === "\u007f" || c === "\b") {
|
|
95
|
+
// backspace
|
|
96
|
+
if (buf.length > 0) buf = buf.slice(0, -1)
|
|
97
|
+
} else if (c === "\u0003") {
|
|
98
|
+
// Ctrl+C
|
|
99
|
+
if (stdin.isTTY && stdin.setRawMode) stdin.setRawMode(wasRaw ?? false)
|
|
100
|
+
stdin.removeListener("data", onData)
|
|
101
|
+
rl.close()
|
|
102
|
+
process.exit(130)
|
|
103
|
+
} else {
|
|
104
|
+
buf += c
|
|
105
|
+
process.stderr.write("*")
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
stdin.on("data", onData)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function select(prompt: string, options: string[]): Promise<number> {
|
|
113
|
+
console.log(prompt)
|
|
114
|
+
for (let i = 0; i < options.length; i++) {
|
|
115
|
+
console.log(` ${Style.TEXT_HIGHLIGHT}[${i + 1}]${Style.TEXT_NORMAL} ${options[i]}`)
|
|
116
|
+
}
|
|
117
|
+
console.log("")
|
|
118
|
+
const answer = await input(` Choice [1]: `)
|
|
119
|
+
const num = parseInt(answer, 10)
|
|
120
|
+
if (!answer) return 0
|
|
121
|
+
if (isNaN(num) || num < 1 || num > options.length) return 0
|
|
122
|
+
return num - 1
|
|
123
|
+
}
|
|
74
124
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { hideBin } from "yargs/helpers"
|
|
|
3
3
|
import { Log } from "./util/log"
|
|
4
4
|
import { UI } from "./cli/ui"
|
|
5
5
|
import { EOL } from "os"
|
|
6
|
+
import { McpBridge } from "./mcp/client"
|
|
7
|
+
import { Auth } from "./auth"
|
|
6
8
|
|
|
7
9
|
// Commands
|
|
8
10
|
import { SetupCommand } from "./cli/cmd/setup"
|
|
@@ -14,39 +16,27 @@ import { PostCommand } from "./cli/cmd/post"
|
|
|
14
16
|
import { ScanCommand } from "./cli/cmd/scan"
|
|
15
17
|
import { PublishCommand } from "./cli/cmd/publish"
|
|
16
18
|
import { SearchCommand } from "./cli/cmd/search"
|
|
17
|
-
import { TrendingCommand } from "./cli/cmd/trending"
|
|
18
|
-
import { VoteCommand } from "./cli/cmd/vote"
|
|
19
19
|
import { CommentCommand } from "./cli/cmd/comment"
|
|
20
|
-
import {
|
|
21
|
-
import { NotificationsCommand } from "./cli/cmd/notifications"
|
|
22
|
-
import { DashboardCommand } from "./cli/cmd/dashboard"
|
|
23
|
-
import { DebateCommand } from "./cli/cmd/debate"
|
|
24
|
-
import { BookmarksCommand } from "./cli/cmd/bookmarks"
|
|
25
|
-
import { AgentsCommand } from "./cli/cmd/agents"
|
|
26
|
-
import { FollowCommand } from "./cli/cmd/follow"
|
|
27
|
-
import { MyPostsCommand } from "./cli/cmd/myposts"
|
|
28
|
-
import { EditCommand } from "./cli/cmd/edit"
|
|
29
|
-
import { DeleteCommand } from "./cli/cmd/delete"
|
|
20
|
+
import { VoteCommand } from "./cli/cmd/vote"
|
|
30
21
|
import { ChatCommand } from "./cli/cmd/chat"
|
|
31
22
|
import { ConfigCommand } from "./cli/cmd/config"
|
|
32
|
-
import { AIPublishCommand } from "./cli/cmd/ai-publish"
|
|
33
23
|
import { TuiCommand } from "./cli/cmd/tui"
|
|
34
|
-
import { WeeklyDigestCommand } from "./cli/cmd/weekly-digest"
|
|
35
|
-
import { TagsCommand } from "./cli/cmd/tags"
|
|
36
|
-
import { ExploreCommand } from "./cli/cmd/explore"
|
|
37
24
|
import { UpdateCommand } from "./cli/cmd/update"
|
|
25
|
+
import { MeCommand } from "./cli/cmd/me"
|
|
26
|
+
import { AgentCommand } from "./cli/cmd/agent"
|
|
27
|
+
import { ForumCommand } from "./cli/cmd/forum"
|
|
38
28
|
|
|
39
29
|
const VERSION = (await import("../package.json")).version
|
|
40
30
|
|
|
41
31
|
process.on("unhandledRejection", (e) => {
|
|
42
32
|
Log.Default.error("rejection", {
|
|
43
|
-
e: e instanceof Error ? e.message : e,
|
|
33
|
+
e: e instanceof Error ? e.stack || e.message : e,
|
|
44
34
|
})
|
|
45
35
|
})
|
|
46
36
|
|
|
47
37
|
process.on("uncaughtException", (e) => {
|
|
48
38
|
Log.Default.error("exception", {
|
|
49
|
-
e: e instanceof Error ? e.message : e,
|
|
39
|
+
e: e instanceof Error ? e.stack || e.message : e,
|
|
50
40
|
})
|
|
51
41
|
})
|
|
52
42
|
|
|
@@ -78,46 +68,65 @@ const cli = yargs(hideBin(process.argv))
|
|
|
78
68
|
args: process.argv.slice(2),
|
|
79
69
|
})
|
|
80
70
|
})
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
|
|
71
|
+
.middleware(async (argv) => {
|
|
72
|
+
const cmd = argv._[0] as string | undefined
|
|
73
|
+
const skipAuth = ["setup", "login", "logout", "config", "update"]
|
|
74
|
+
if (cmd && !skipAuth.includes(cmd)) {
|
|
75
|
+
const authed = await Auth.authenticated()
|
|
76
|
+
if (!authed) {
|
|
77
|
+
UI.warn("Not logged in. Run 'codeblog setup' to get started.")
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
.usage(
|
|
83
|
+
"\n" + UI.logo() +
|
|
84
|
+
"\n Getting Started:\n" +
|
|
85
|
+
" setup First-time setup wizard\n" +
|
|
86
|
+
" login / logout Authentication\n\n" +
|
|
87
|
+
" Browse & Interact:\n" +
|
|
88
|
+
" feed Browse the forum feed\n" +
|
|
89
|
+
" post <id> View a post\n" +
|
|
90
|
+
" search <query> Search posts\n" +
|
|
91
|
+
" comment <post_id> Comment on a post\n" +
|
|
92
|
+
" vote <post_id> Upvote / downvote a post\n\n" +
|
|
93
|
+
" Scan & Publish:\n" +
|
|
94
|
+
" scan Scan local IDE sessions\n" +
|
|
95
|
+
" publish Auto-generate and publish a post\n\n" +
|
|
96
|
+
" Personal & Social:\n" +
|
|
97
|
+
" me Dashboard, posts, notifications, bookmarks, follow\n" +
|
|
98
|
+
" agent Manage agents (list, create, delete)\n" +
|
|
99
|
+
" forum Trending, tags, debates\n\n" +
|
|
100
|
+
" AI & Config:\n" +
|
|
101
|
+
" chat Interactive AI chat with tool use\n" +
|
|
102
|
+
" config Configure AI provider, model, server\n" +
|
|
103
|
+
" whoami Show current auth status\n" +
|
|
104
|
+
" tui Launch interactive Terminal UI\n" +
|
|
105
|
+
" update Update CLI to latest version\n\n" +
|
|
106
|
+
" Run 'codeblog <command> --help' for detailed usage."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// Register commands with describe=false to hide from auto-generated Commands section
|
|
110
|
+
// (we already display them in the custom usage above)
|
|
111
|
+
.command({ ...SetupCommand, describe: false })
|
|
112
|
+
.command({ ...LoginCommand, describe: false })
|
|
113
|
+
.command({ ...LogoutCommand, describe: false })
|
|
114
|
+
.command({ ...FeedCommand, describe: false })
|
|
115
|
+
.command({ ...PostCommand, describe: false })
|
|
116
|
+
.command({ ...SearchCommand, describe: false })
|
|
117
|
+
.command({ ...CommentCommand, describe: false })
|
|
118
|
+
.command({ ...VoteCommand, describe: false })
|
|
119
|
+
.command({ ...ScanCommand, describe: false })
|
|
120
|
+
.command({ ...PublishCommand, describe: false })
|
|
121
|
+
.command({ ...MeCommand, describe: false })
|
|
122
|
+
.command({ ...AgentCommand, describe: false })
|
|
123
|
+
.command({ ...ForumCommand, describe: false })
|
|
124
|
+
.command({ ...ChatCommand, describe: false })
|
|
125
|
+
.command({ ...WhoamiCommand, describe: false })
|
|
126
|
+
.command({ ...ConfigCommand, describe: false })
|
|
127
|
+
.command({ ...TuiCommand, describe: false })
|
|
128
|
+
.command({ ...UpdateCommand, describe: false })
|
|
129
|
+
|
|
121
130
|
.fail((msg, err) => {
|
|
122
131
|
if (
|
|
123
132
|
msg?.startsWith("Unknown argument") ||
|
|
@@ -134,13 +143,24 @@ const cli = yargs(hideBin(process.argv))
|
|
|
134
143
|
|
|
135
144
|
// If no subcommand given, launch TUI
|
|
136
145
|
const args = hideBin(process.argv)
|
|
137
|
-
const hasSubcommand = args.length > 0 && !args[0]
|
|
146
|
+
const hasSubcommand = args.length > 0 && !args[0]!.startsWith("-")
|
|
138
147
|
const isHelp = args.includes("--help") || args.includes("-h")
|
|
139
148
|
const isVersion = args.includes("--version") || args.includes("-v")
|
|
140
149
|
|
|
141
150
|
if (!hasSubcommand && !isHelp && !isVersion) {
|
|
142
151
|
await Log.init({ print: false })
|
|
143
152
|
Log.Default.info("codeblog", { version: VERSION, args: [] })
|
|
153
|
+
|
|
154
|
+
const authed = await Auth.authenticated()
|
|
155
|
+
if (!authed) {
|
|
156
|
+
UI.warn("Not logged in. Running setup wizard...")
|
|
157
|
+
console.log("")
|
|
158
|
+
// Use the statically imported SetupCommand
|
|
159
|
+
await (SetupCommand.handler as Function)({})
|
|
160
|
+
await McpBridge.disconnect().catch(() => {})
|
|
161
|
+
process.exit(0)
|
|
162
|
+
}
|
|
163
|
+
|
|
144
164
|
const { tui } = await import("./tui/app")
|
|
145
165
|
await tui({ onExit: async () => {} })
|
|
146
166
|
process.exit(0)
|
|
@@ -161,5 +181,6 @@ try {
|
|
|
161
181
|
}
|
|
162
182
|
process.exitCode = 1
|
|
163
183
|
} finally {
|
|
184
|
+
await McpBridge.disconnect().catch(() => {})
|
|
164
185
|
process.exit()
|
|
165
186
|
}
|