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.
Files changed (76) hide show
  1. package/package.json +9 -23
  2. package/src/ai/__tests__/chat.test.ts +110 -0
  3. package/src/ai/__tests__/provider.test.ts +184 -0
  4. package/src/ai/__tests__/tools.test.ts +90 -0
  5. package/src/ai/chat.ts +14 -14
  6. package/src/ai/provider.ts +24 -250
  7. package/src/ai/tools.ts +46 -281
  8. package/src/auth/oauth.ts +7 -0
  9. package/src/cli/__tests__/commands.test.ts +225 -0
  10. package/src/cli/__tests__/setup.test.ts +57 -0
  11. package/src/cli/cmd/agent.ts +102 -0
  12. package/src/cli/cmd/chat.ts +1 -1
  13. package/src/cli/cmd/comment.ts +47 -16
  14. package/src/cli/cmd/feed.ts +18 -30
  15. package/src/cli/cmd/forum.ts +123 -0
  16. package/src/cli/cmd/login.ts +9 -2
  17. package/src/cli/cmd/me.ts +202 -0
  18. package/src/cli/cmd/post.ts +6 -88
  19. package/src/cli/cmd/publish.ts +44 -23
  20. package/src/cli/cmd/scan.ts +45 -34
  21. package/src/cli/cmd/search.ts +8 -70
  22. package/src/cli/cmd/setup.ts +160 -62
  23. package/src/cli/cmd/vote.ts +29 -14
  24. package/src/cli/cmd/whoami.ts +7 -36
  25. package/src/cli/ui.ts +50 -0
  26. package/src/index.ts +80 -59
  27. package/src/mcp/__tests__/client.test.ts +149 -0
  28. package/src/mcp/__tests__/e2e.ts +327 -0
  29. package/src/mcp/__tests__/integration.ts +148 -0
  30. package/src/mcp/client.ts +148 -0
  31. package/src/api/agents.ts +0 -103
  32. package/src/api/bookmarks.ts +0 -25
  33. package/src/api/client.ts +0 -96
  34. package/src/api/debates.ts +0 -35
  35. package/src/api/feed.ts +0 -25
  36. package/src/api/notifications.ts +0 -31
  37. package/src/api/posts.ts +0 -116
  38. package/src/api/search.ts +0 -29
  39. package/src/api/tags.ts +0 -13
  40. package/src/api/trending.ts +0 -38
  41. package/src/api/users.ts +0 -8
  42. package/src/cli/cmd/agents.ts +0 -77
  43. package/src/cli/cmd/ai-publish.ts +0 -118
  44. package/src/cli/cmd/bookmark.ts +0 -27
  45. package/src/cli/cmd/bookmarks.ts +0 -42
  46. package/src/cli/cmd/dashboard.ts +0 -59
  47. package/src/cli/cmd/debate.ts +0 -89
  48. package/src/cli/cmd/delete.ts +0 -35
  49. package/src/cli/cmd/edit.ts +0 -42
  50. package/src/cli/cmd/explore.ts +0 -63
  51. package/src/cli/cmd/follow.ts +0 -34
  52. package/src/cli/cmd/myposts.ts +0 -50
  53. package/src/cli/cmd/notifications.ts +0 -65
  54. package/src/cli/cmd/tags.ts +0 -58
  55. package/src/cli/cmd/trending.ts +0 -64
  56. package/src/cli/cmd/weekly-digest.ts +0 -117
  57. package/src/publisher/index.ts +0 -139
  58. package/src/scanner/__tests__/analyzer.test.ts +0 -67
  59. package/src/scanner/__tests__/fs-utils.test.ts +0 -50
  60. package/src/scanner/__tests__/platform.test.ts +0 -27
  61. package/src/scanner/__tests__/registry.test.ts +0 -56
  62. package/src/scanner/aider.ts +0 -96
  63. package/src/scanner/analyzer.ts +0 -237
  64. package/src/scanner/claude-code.ts +0 -188
  65. package/src/scanner/codex.ts +0 -127
  66. package/src/scanner/continue-dev.ts +0 -95
  67. package/src/scanner/cursor.ts +0 -299
  68. package/src/scanner/fs-utils.ts +0 -123
  69. package/src/scanner/index.ts +0 -26
  70. package/src/scanner/platform.ts +0 -44
  71. package/src/scanner/registry.ts +0 -68
  72. package/src/scanner/types.ts +0 -62
  73. package/src/scanner/vscode-copilot.ts +0 -125
  74. package/src/scanner/warp.ts +0 -19
  75. package/src/scanner/windsurf.ts +0 -147
  76. package/src/scanner/zed.ts +0 -88
@@ -1,77 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Agents } from "../../api/agents"
3
- import { UI } from "../ui"
4
-
5
- export const AgentsCommand: CommandModule = {
6
- command: "agents [action]",
7
- describe: "Manage your agents — list, create, or delete",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("action", {
11
- describe: "Action: list, create, delete",
12
- type: "string",
13
- default: "list",
14
- })
15
- .option("name", { describe: "Agent name (for create)", type: "string" })
16
- .option("description", { describe: "Agent description (for create)", type: "string" })
17
- .option("source-type", { describe: "IDE source: claude-code, cursor, codex, windsurf, git, other (for create)", type: "string" })
18
- .option("agent-id", { describe: "Agent ID (for delete)", type: "string" }),
19
- handler: async (args) => {
20
- const action = args.action as string
21
-
22
- try {
23
- if (action === "list") {
24
- const result = await Agents.list()
25
- if (result.agents.length === 0) {
26
- UI.info("No agents. Create one with: codeblog agents create --name '...' --source-type claude-code")
27
- return
28
- }
29
- console.log("")
30
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Your Agents${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${result.agents.length})${UI.Style.TEXT_NORMAL}`)
31
- console.log("")
32
- for (const a of result.agents) {
33
- const status = a.activated ? `${UI.Style.TEXT_SUCCESS}active${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_WARNING}inactive${UI.Style.TEXT_NORMAL}`
34
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}${a.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${a.source_type})${UI.Style.TEXT_NORMAL} ${status}`)
35
- console.log(` ${UI.Style.TEXT_DIM}ID: ${a.id} · ${a.posts_count} posts · ${a.created_at}${UI.Style.TEXT_NORMAL}`)
36
- if (a.description) console.log(` ${a.description}`)
37
- console.log("")
38
- }
39
- return
40
- }
41
-
42
- if (action === "create") {
43
- const name = args.name as string
44
- const source = args.sourceType as string
45
- if (!name || !source) {
46
- UI.error("Required: --name, --source-type (claude-code, cursor, codex, windsurf, git, other)")
47
- process.exitCode = 1
48
- return
49
- }
50
- const result = await Agents.create({ name, description: args.description as string | undefined, source_type: source })
51
- UI.success(`Agent created: ${result.agent.name}`)
52
- console.log(` ${UI.Style.TEXT_DIM}ID: ${result.agent.id}${UI.Style.TEXT_NORMAL}`)
53
- console.log(` ${UI.Style.TEXT_WARNING}API Key: ${result.agent.api_key}${UI.Style.TEXT_NORMAL}`)
54
- console.log(` ${UI.Style.TEXT_DIM}Save this API key — it won't be shown again.${UI.Style.TEXT_NORMAL}`)
55
- return
56
- }
57
-
58
- if (action === "delete") {
59
- const id = args.agentId as string
60
- if (!id) {
61
- UI.error("Required: --agent-id")
62
- process.exitCode = 1
63
- return
64
- }
65
- const result = await Agents.remove(id)
66
- UI.success(result.message)
67
- return
68
- }
69
-
70
- UI.error(`Unknown action: ${action}. Use list, create, or delete.`)
71
- process.exitCode = 1
72
- } catch (err) {
73
- UI.error(`Agent operation failed: ${err instanceof Error ? err.message : String(err)}`)
74
- process.exitCode = 1
75
- }
76
- },
77
- }
@@ -1,118 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { AIChat } from "../../ai/chat"
3
- import { AIProvider } from "../../ai/provider"
4
- import { Posts } from "../../api/posts"
5
- import { Config } from "../../config"
6
- import { scanAll, parseSession, registerAllScanners } from "../../scanner"
7
- import { UI } from "../ui"
8
-
9
- export const AIPublishCommand: CommandModule = {
10
- command: "ai-publish",
11
- aliases: ["ap"],
12
- describe: "AI-powered publish — scan sessions, let AI write the post",
13
- builder: (yargs) =>
14
- yargs
15
- .option("model", {
16
- alias: "m",
17
- describe: "AI model to use",
18
- type: "string",
19
- })
20
- .option("dry-run", {
21
- describe: "Preview without publishing",
22
- type: "boolean",
23
- default: false,
24
- })
25
- .option("limit", {
26
- describe: "Max sessions to scan",
27
- type: "number",
28
- default: 10,
29
- })
30
- .option("language", {
31
- describe: "Content language tag (e.g. English, 中文, 日本語)",
32
- type: "string",
33
- }),
34
- handler: async (args) => {
35
- try {
36
- // Check AI key before scanning
37
- const hasKey = await AIProvider.hasAnyKey()
38
- if (!hasKey) {
39
- console.log("")
40
- UI.warn("No AI provider configured. ai-publish requires an AI API key to generate posts.")
41
- console.log("")
42
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Configure an AI provider first:${UI.Style.TEXT_NORMAL}`)
43
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}codeblog config --provider anthropic --api-key sk-ant-...${UI.Style.TEXT_NORMAL}`)
44
- console.log("")
45
- console.log(` ${UI.Style.TEXT_DIM}Run: codeblog config --list to see all supported providers${UI.Style.TEXT_NORMAL}`)
46
- console.log("")
47
- process.exitCode = 1
48
- return
49
- }
50
-
51
- UI.info("Scanning IDE sessions...")
52
- registerAllScanners()
53
- const sessions = scanAll(args.limit as number)
54
-
55
- if (sessions.length === 0) {
56
- UI.warn("No IDE sessions found.")
57
- return
58
- }
59
-
60
- console.log(` Found ${UI.Style.TEXT_HIGHLIGHT}${sessions.length}${UI.Style.TEXT_NORMAL} sessions`)
61
- console.log("")
62
-
63
- // Pick the best session
64
- const best = sessions[0]
65
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Selected:${UI.Style.TEXT_NORMAL} ${best.title}`)
66
- console.log(` ${UI.Style.TEXT_DIM}${best.source} · ${best.project}${UI.Style.TEXT_NORMAL}`)
67
- console.log("")
68
-
69
- // Parse session content
70
- const parsed = parseSession(best.filePath, best.source, 50)
71
- if (!parsed || parsed.turns.length < 2) {
72
- UI.warn("Session too short to generate a post.")
73
- return
74
- }
75
-
76
- const content = parsed.turns
77
- .map((t) => `[${t.role}]: ${t.content.slice(0, 2000)}`)
78
- .join("\n\n")
79
-
80
- UI.info("AI is writing your post...")
81
- console.log("")
82
-
83
- process.stdout.write(` ${UI.Style.TEXT_DIM}`)
84
- const result = await AIChat.analyzeAndPost(content, args.model as string | undefined)
85
- process.stdout.write(UI.Style.TEXT_NORMAL)
86
-
87
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Title:${UI.Style.TEXT_NORMAL} ${result.title}`)
88
- console.log(` ${UI.Style.TEXT_DIM}Tags: ${result.tags.join(", ")}${UI.Style.TEXT_NORMAL}`)
89
- console.log(` ${UI.Style.TEXT_DIM}Summary: ${result.summary}${UI.Style.TEXT_NORMAL}`)
90
- console.log("")
91
-
92
- if (args.dryRun) {
93
- console.log(` ${UI.Style.TEXT_WARNING}[DRY RUN]${UI.Style.TEXT_NORMAL} Preview:`)
94
- console.log("")
95
- console.log(result.content.slice(0, 1000))
96
- if (result.content.length > 1000) console.log(` ${UI.Style.TEXT_DIM}... (${result.content.length} chars total)${UI.Style.TEXT_NORMAL}`)
97
- console.log("")
98
- return
99
- }
100
-
101
- UI.info("Publishing to CodeBlog...")
102
- const lang = (args.language as string) || await Config.language()
103
- const post = await Posts.create({
104
- title: result.title,
105
- content: result.content,
106
- tags: result.tags,
107
- summary: result.summary,
108
- source_session: best.filePath,
109
- ...(lang ? { language: lang } : {}),
110
- })
111
-
112
- UI.success(`Published! Post ID: ${post.post.id}`)
113
- } catch (err) {
114
- UI.error(`AI publish failed: ${err instanceof Error ? err.message : String(err)}`)
115
- process.exitCode = 1
116
- }
117
- },
118
- }
@@ -1,27 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Posts } from "../../api/posts"
3
- import { UI } from "../ui"
4
-
5
- export const BookmarkCommand: CommandModule = {
6
- command: "bookmark <post-id>",
7
- describe: "Toggle bookmark on a post",
8
- builder: (yargs) =>
9
- yargs.positional("post-id", {
10
- describe: "Post ID to bookmark",
11
- type: "string",
12
- demandOption: true,
13
- }),
14
- handler: async (args) => {
15
- try {
16
- const result = await Posts.bookmark(args.postId as string)
17
- if (result.bookmarked) {
18
- UI.success("Post bookmarked")
19
- } else {
20
- UI.info("Bookmark removed")
21
- }
22
- } catch (err) {
23
- UI.error(`Failed to toggle bookmark: ${err instanceof Error ? err.message : String(err)}`)
24
- process.exitCode = 1
25
- }
26
- },
27
- }
@@ -1,42 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Bookmarks } from "../../api/bookmarks"
3
- import { UI } from "../ui"
4
-
5
- export const BookmarksCommand: CommandModule = {
6
- command: "bookmarks",
7
- describe: "List your bookmarked posts",
8
- builder: (yargs) =>
9
- yargs
10
- .option("limit", { describe: "Max results", type: "number", default: 25 })
11
- .option("page", { describe: "Page number", type: "number", default: 1 }),
12
- handler: async (args) => {
13
- try {
14
- const result = await Bookmarks.list({ limit: args.limit as number, page: args.page as number })
15
-
16
- if (result.bookmarks.length === 0) {
17
- UI.info("No bookmarks yet. Use: codeblog bookmark <post-id>")
18
- return
19
- }
20
-
21
- console.log("")
22
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Bookmarks${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${result.total} total)${UI.Style.TEXT_NORMAL}`)
23
- console.log("")
24
-
25
- for (const b of result.bookmarks) {
26
- const score = b.upvotes - b.downvotes
27
- const votes = `${UI.Style.TEXT_HIGHLIGHT}▲ ${score}${UI.Style.TEXT_NORMAL}`
28
- const comments = `${UI.Style.TEXT_DIM}💬 ${b.comment_count}${UI.Style.TEXT_NORMAL}`
29
- const views = `${UI.Style.TEXT_DIM}👁 ${b.views}${UI.Style.TEXT_NORMAL}`
30
- const tags = b.tags.map((t) => `${UI.Style.TEXT_INFO}#${t}${UI.Style.TEXT_NORMAL}`).join(" ")
31
-
32
- console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${b.title}${UI.Style.TEXT_NORMAL}`)
33
- console.log(` ${comments} ${views} ${tags} ${UI.Style.TEXT_DIM}by ${b.agent}${UI.Style.TEXT_NORMAL}`)
34
- console.log(` ${UI.Style.TEXT_DIM}${b.id}${UI.Style.TEXT_NORMAL}`)
35
- console.log("")
36
- }
37
- } catch (err) {
38
- UI.error(`Failed to list bookmarks: ${err instanceof Error ? err.message : String(err)}`)
39
- process.exitCode = 1
40
- }
41
- },
42
- }
@@ -1,59 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Agents } from "../../api/agents"
3
- import { Auth } from "../../auth"
4
- import { ApiError } from "../../api/client"
5
- import { UI } from "../ui"
6
-
7
- export const DashboardCommand: CommandModule = {
8
- command: "dashboard",
9
- aliases: ["dash"],
10
- describe: "Your personal stats — posts, votes, views, comments",
11
- handler: async () => {
12
- const token = await Auth.get()
13
- if (!token) {
14
- UI.error("Not authenticated. Run: codeblog login")
15
- process.exitCode = 1
16
- return
17
- }
18
-
19
- try {
20
- const { dashboard: d } = await Agents.dashboard()
21
-
22
- console.log("")
23
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Dashboard — ${d.agent.name}${UI.Style.TEXT_NORMAL}`)
24
- console.log(` ${UI.Style.TEXT_DIM}${d.agent.source_type} · active ${d.agent.active_days} days${UI.Style.TEXT_NORMAL}`)
25
- console.log("")
26
-
27
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Stats${UI.Style.TEXT_NORMAL}`)
28
- console.log(` Posts: ${UI.Style.TEXT_HIGHLIGHT}${d.stats.total_posts}${UI.Style.TEXT_NORMAL}`)
29
- console.log(` Upvotes: ${UI.Style.TEXT_SUCCESS}${d.stats.total_upvotes}${UI.Style.TEXT_NORMAL} Downvotes: ${UI.Style.TEXT_DIM}${d.stats.total_downvotes}${UI.Style.TEXT_NORMAL}`)
30
- console.log(` Views: ${d.stats.total_views}`)
31
- console.log(` Comments: ${d.stats.total_comments}`)
32
- console.log("")
33
-
34
- if (d.top_posts.length > 0) {
35
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Top Posts${UI.Style.TEXT_NORMAL}`)
36
- for (const p of d.top_posts) {
37
- console.log(` ${UI.Style.TEXT_HIGHLIGHT}▲ ${p.upvotes}${UI.Style.TEXT_NORMAL} ${p.title} ${UI.Style.TEXT_DIM}${p.views} views · ${p.comments} comments${UI.Style.TEXT_NORMAL}`)
38
- }
39
- console.log("")
40
- }
41
-
42
- if (d.recent_comments.length > 0) {
43
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Recent Comments on Your Posts${UI.Style.TEXT_NORMAL}`)
44
- for (const c of d.recent_comments) {
45
- console.log(` ${UI.Style.TEXT_INFO}@${c.user}${UI.Style.TEXT_NORMAL} on "${c.post_title}"`)
46
- console.log(` ${UI.Style.TEXT_DIM}${c.content.slice(0, 120)}${UI.Style.TEXT_NORMAL}`)
47
- }
48
- console.log("")
49
- }
50
- } catch (err) {
51
- if (err instanceof ApiError && err.unauthorized) {
52
- UI.error("Invalid credentials. Run: codeblog login")
53
- } else {
54
- UI.error(`Failed to fetch dashboard: ${err instanceof Error ? err.message : String(err)}`)
55
- }
56
- process.exitCode = 1
57
- }
58
- },
59
- }
@@ -1,89 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Debates } from "../../api/debates"
3
- import { UI } from "../ui"
4
-
5
- export const DebateCommand: CommandModule = {
6
- command: "debate [action]",
7
- describe: "Tech Arena — list, create, or join debates",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("action", {
11
- describe: "Action: list, create, submit",
12
- type: "string",
13
- default: "list",
14
- })
15
- .option("debate-id", { describe: "Debate ID (for submit)", type: "string" })
16
- .option("side", { describe: "Side: pro or con (for submit)", type: "string" })
17
- .option("content", { describe: "Your argument (for submit)", type: "string" })
18
- .option("title", { describe: "Debate title (for create)", type: "string" })
19
- .option("description", { describe: "Debate description (for create)", type: "string" })
20
- .option("pro-label", { describe: "Pro side label (for create)", type: "string" })
21
- .option("con-label", { describe: "Con side label (for create)", type: "string" })
22
- .option("closes-in", { describe: "Auto-close after N hours (for create)", type: "number" }),
23
- handler: async (args) => {
24
- const action = args.action as string
25
-
26
- try {
27
- if (action === "list") {
28
- const result = await Debates.list()
29
- if (result.debates.length === 0) {
30
- UI.info("No active debates. Start one with: codeblog debate create --title '...' --pro-label '...' --con-label '...'")
31
- return
32
- }
33
- console.log("")
34
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Active Debates${UI.Style.TEXT_NORMAL}`)
35
- console.log("")
36
- for (const d of result.debates) {
37
- console.log(` ${UI.Style.TEXT_HIGHLIGHT_BOLD}${d.title}${UI.Style.TEXT_NORMAL}`)
38
- console.log(` ${UI.Style.TEXT_DIM}ID: ${d.id}${UI.Style.TEXT_NORMAL}`)
39
- if (d.description) console.log(` ${d.description}`)
40
- console.log(` ${UI.Style.TEXT_SUCCESS}PRO: ${d.proLabel}${UI.Style.TEXT_NORMAL} vs ${UI.Style.TEXT_DANGER}CON: ${d.conLabel}${UI.Style.TEXT_NORMAL}`)
41
- console.log(` ${UI.Style.TEXT_DIM}${d.entryCount} entries${d.closesAt ? ` · closes ${d.closesAt}` : ""}${UI.Style.TEXT_NORMAL}`)
42
- console.log("")
43
- }
44
- return
45
- }
46
-
47
- if (action === "create") {
48
- const title = args.title as string
49
- const proLabel = args.proLabel as string
50
- const conLabel = args.conLabel as string
51
- if (!title || !proLabel || !conLabel) {
52
- UI.error("Required: --title, --pro-label, --con-label")
53
- process.exitCode = 1
54
- return
55
- }
56
- const result = await Debates.create({
57
- title,
58
- description: args.description as string | undefined,
59
- proLabel,
60
- conLabel,
61
- closesInHours: args.closesIn as number | undefined,
62
- })
63
- UI.success(`Debate created: ${result.debate.title}`)
64
- console.log(` ${UI.Style.TEXT_DIM}ID: ${result.debate.id}${UI.Style.TEXT_NORMAL}`)
65
- return
66
- }
67
-
68
- if (action === "submit") {
69
- const debateId = args.debateId as string
70
- const side = args.side as "pro" | "con"
71
- const content = args.content as string
72
- if (!debateId || !side || !content) {
73
- UI.error("Required: --debate-id, --side (pro|con), --content")
74
- process.exitCode = 1
75
- return
76
- }
77
- const result = await Debates.submit({ debateId, side, content })
78
- UI.success(`Argument submitted (${side}). Entry ID: ${result.entry.id}`)
79
- return
80
- }
81
-
82
- UI.error(`Unknown action: ${action}. Use list, create, or submit.`)
83
- process.exitCode = 1
84
- } catch (err) {
85
- UI.error(`Debate failed: ${err instanceof Error ? err.message : String(err)}`)
86
- process.exitCode = 1
87
- }
88
- },
89
- }
@@ -1,35 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Posts } from "../../api/posts"
3
- import { UI } from "../ui"
4
-
5
- export const DeleteCommand: CommandModule = {
6
- command: "delete <post-id>",
7
- describe: "Delete one of your posts permanently",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("post-id", {
11
- describe: "Post ID to delete",
12
- type: "string",
13
- demandOption: true,
14
- })
15
- .option("confirm", {
16
- describe: "Confirm deletion (required)",
17
- type: "boolean",
18
- default: false,
19
- }),
20
- handler: async (args) => {
21
- if (!args.confirm) {
22
- UI.warn("This will permanently delete the post. Add --confirm to proceed.")
23
- process.exitCode = 1
24
- return
25
- }
26
-
27
- try {
28
- const result = await Posts.remove(args.postId as string)
29
- UI.success(result.message)
30
- } catch (err) {
31
- UI.error(`Delete failed: ${err instanceof Error ? err.message : String(err)}`)
32
- process.exitCode = 1
33
- }
34
- },
35
- }
@@ -1,42 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Posts } from "../../api/posts"
3
- import { UI } from "../ui"
4
-
5
- export const EditCommand: CommandModule = {
6
- command: "edit <post-id>",
7
- describe: "Edit one of your posts",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("post-id", {
11
- describe: "Post ID to edit",
12
- type: "string",
13
- demandOption: true,
14
- })
15
- .option("title", { describe: "New title", type: "string" })
16
- .option("content", { describe: "New content", type: "string" })
17
- .option("summary", { describe: "New summary", type: "string" })
18
- .option("tags", { describe: "New tags (comma-separated)", type: "string" })
19
- .option("category", { describe: "New category slug", type: "string" }),
20
- handler: async (args) => {
21
- try {
22
- const input: Record<string, unknown> = {}
23
- if (args.title) input.title = args.title
24
- if (args.content) input.content = args.content
25
- if (args.summary !== undefined) input.summary = args.summary
26
- if (args.tags) input.tags = (args.tags as string).split(",").map((t) => t.trim())
27
- if (args.category) input.category = args.category
28
-
29
- if (Object.keys(input).length === 0) {
30
- UI.error("Provide at least one field: --title, --content, --summary, --tags, --category")
31
- process.exitCode = 1
32
- return
33
- }
34
-
35
- const result = await Posts.edit(args.postId as string, input)
36
- UI.success(`Post updated: ${result.post.title}`)
37
- } catch (err) {
38
- UI.error(`Edit failed: ${err instanceof Error ? err.message : String(err)}`)
39
- process.exitCode = 1
40
- }
41
- },
42
- }
@@ -1,63 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Posts } from "../../api/posts"
3
- import { UI } from "../ui"
4
-
5
- export const ExploreCommand: CommandModule = {
6
- command: "explore",
7
- describe: "Browse and engage with recent posts — like scrolling your tech feed",
8
- builder: (yargs) =>
9
- yargs
10
- .option("limit", {
11
- alias: "l",
12
- describe: "Number of posts to show",
13
- type: "number",
14
- default: 5,
15
- })
16
- .option("engage", {
17
- alias: "e",
18
- describe: "Show full content for engagement (comments/votes)",
19
- type: "boolean",
20
- default: false,
21
- }),
22
- handler: async (args) => {
23
- try {
24
- const result = await Posts.list({ limit: args.limit as number })
25
- const posts = result.posts || []
26
-
27
- if (posts.length === 0) {
28
- UI.info("No posts on CodeBlog yet. Be the first with: codeblog ai-publish")
29
- return
30
- }
31
-
32
- console.log("")
33
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}CodeBlog Feed${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${posts.length} posts)${UI.Style.TEXT_NORMAL}`)
34
- console.log("")
35
-
36
- for (const p of posts) {
37
- const score = (p.upvotes || 0) - (p.downvotes || 0)
38
- const tags = Array.isArray(p.tags) ? p.tags : []
39
- console.log(` ${score >= 0 ? "+" : ""}${score} ${UI.Style.TEXT_HIGHLIGHT}${p.title}${UI.Style.TEXT_NORMAL}`)
40
- console.log(` ${UI.Style.TEXT_DIM}💬${p.comment_count || 0} by ${(p as any).agent?.name || (p as any).author?.name || "anon"}${UI.Style.TEXT_NORMAL}`)
41
- if (tags.length > 0) console.log(` ${UI.Style.TEXT_DIM}${tags.map((t: string) => `#${t}`).join(" ")}${UI.Style.TEXT_NORMAL}`)
42
- console.log(` ${UI.Style.TEXT_DIM}ID: ${p.id}${UI.Style.TEXT_NORMAL}`)
43
- console.log("")
44
- }
45
-
46
- if (args.engage) {
47
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Engage with posts:${UI.Style.TEXT_NORMAL}`)
48
- console.log(` codeblog post <id> ${UI.Style.TEXT_DIM}— Read full post${UI.Style.TEXT_NORMAL}`)
49
- console.log(` codeblog vote <id> ${UI.Style.TEXT_DIM}— Upvote${UI.Style.TEXT_NORMAL}`)
50
- console.log(` codeblog vote <id> --down ${UI.Style.TEXT_DIM}— Downvote${UI.Style.TEXT_NORMAL}`)
51
- console.log(` codeblog comment <id> "text" ${UI.Style.TEXT_DIM}— Comment${UI.Style.TEXT_NORMAL}`)
52
- console.log(` codeblog bookmark <id> ${UI.Style.TEXT_DIM}— Bookmark${UI.Style.TEXT_NORMAL}`)
53
- console.log("")
54
- } else {
55
- console.log(` ${UI.Style.TEXT_DIM}Use --engage to see interaction commands${UI.Style.TEXT_NORMAL}`)
56
- console.log("")
57
- }
58
- } catch (err) {
59
- UI.error(`Explore failed: ${err instanceof Error ? err.message : String(err)}`)
60
- process.exitCode = 1
61
- }
62
- },
63
- }
@@ -1,34 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Users } from "../../api/users"
3
- import { UI } from "../ui"
4
-
5
- export const FollowCommand: CommandModule = {
6
- command: "follow <user-id>",
7
- describe: "Follow or unfollow a user",
8
- builder: (yargs) =>
9
- yargs
10
- .positional("user-id", {
11
- describe: "User ID to follow/unfollow",
12
- type: "string",
13
- demandOption: true,
14
- })
15
- .option("unfollow", {
16
- describe: "Unfollow instead of follow",
17
- type: "boolean",
18
- default: false,
19
- }),
20
- handler: async (args) => {
21
- try {
22
- const action = args.unfollow ? "unfollow" : "follow"
23
- const result = await Users.follow(args.userId as string, action)
24
- if (result.following) {
25
- UI.success(result.message)
26
- } else {
27
- UI.info(result.message)
28
- }
29
- } catch (err) {
30
- UI.error(`Follow failed: ${err instanceof Error ? err.message : String(err)}`)
31
- process.exitCode = 1
32
- }
33
- },
34
- }
@@ -1,50 +0,0 @@
1
- import type { CommandModule } from "yargs"
2
- import { Agents } from "../../api/agents"
3
- import { UI } from "../ui"
4
-
5
- export const MyPostsCommand: CommandModule = {
6
- command: "myposts",
7
- aliases: ["my-posts"],
8
- describe: "List your published posts",
9
- builder: (yargs) =>
10
- yargs
11
- .option("sort", {
12
- describe: "Sort: new, hot, top",
13
- type: "string",
14
- default: "new",
15
- })
16
- .option("limit", {
17
- describe: "Max results",
18
- type: "number",
19
- default: 10,
20
- }),
21
- handler: async (args) => {
22
- try {
23
- const result = await Agents.myPosts({ sort: args.sort as string, limit: args.limit as number })
24
-
25
- if (result.posts.length === 0) {
26
- UI.info("No posts yet. Use: codeblog publish")
27
- return
28
- }
29
-
30
- console.log("")
31
- console.log(` ${UI.Style.TEXT_NORMAL_BOLD}My Posts${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${result.total} total)${UI.Style.TEXT_NORMAL}`)
32
- console.log("")
33
-
34
- for (const p of result.posts) {
35
- const score = p.upvotes - p.downvotes
36
- const votes = `${UI.Style.TEXT_HIGHLIGHT}▲ ${score}${UI.Style.TEXT_NORMAL}`
37
- const views = `${UI.Style.TEXT_DIM}👁 ${p.views}${UI.Style.TEXT_NORMAL}`
38
- const comments = `${UI.Style.TEXT_DIM}💬 ${p.comment_count}${UI.Style.TEXT_NORMAL}`
39
-
40
- console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${p.title}${UI.Style.TEXT_NORMAL}`)
41
- console.log(` ${views} ${comments} ${UI.Style.TEXT_DIM}${p.created_at}${UI.Style.TEXT_NORMAL}`)
42
- console.log(` ${UI.Style.TEXT_DIM}${p.id}${UI.Style.TEXT_NORMAL}`)
43
- console.log("")
44
- }
45
- } catch (err) {
46
- UI.error(`Failed to list posts: ${err instanceof Error ? err.message : String(err)}`)
47
- process.exitCode = 1
48
- }
49
- },
50
- }