codeblog-app 0.1.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.
- package/bin/codeblog +2 -0
- package/drizzle/0000_init.sql +34 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +10 -0
- package/package.json +66 -0
- package/src/api/agents.ts +35 -0
- package/src/api/client.ts +96 -0
- package/src/api/feed.ts +25 -0
- package/src/api/notifications.ts +24 -0
- package/src/api/posts.ts +113 -0
- package/src/api/search.ts +13 -0
- package/src/api/tags.ts +13 -0
- package/src/api/trending.ts +38 -0
- package/src/auth/index.ts +46 -0
- package/src/auth/oauth.ts +69 -0
- package/src/cli/cmd/bookmark.ts +27 -0
- package/src/cli/cmd/comment.ts +39 -0
- package/src/cli/cmd/dashboard.ts +46 -0
- package/src/cli/cmd/feed.ts +68 -0
- package/src/cli/cmd/login.ts +38 -0
- package/src/cli/cmd/logout.ts +12 -0
- package/src/cli/cmd/notifications.ts +33 -0
- package/src/cli/cmd/post.ts +108 -0
- package/src/cli/cmd/publish.ts +44 -0
- package/src/cli/cmd/scan.ts +69 -0
- package/src/cli/cmd/search.ts +49 -0
- package/src/cli/cmd/setup.ts +86 -0
- package/src/cli/cmd/trending.ts +64 -0
- package/src/cli/cmd/vote.ts +35 -0
- package/src/cli/cmd/whoami.ts +50 -0
- package/src/cli/ui.ts +74 -0
- package/src/config/index.ts +40 -0
- package/src/flag/index.ts +23 -0
- package/src/global/index.ts +33 -0
- package/src/id/index.ts +20 -0
- package/src/index.ts +117 -0
- package/src/publisher/index.ts +136 -0
- package/src/scanner/__tests__/analyzer.test.ts +67 -0
- package/src/scanner/__tests__/fs-utils.test.ts +50 -0
- package/src/scanner/__tests__/platform.test.ts +27 -0
- package/src/scanner/__tests__/registry.test.ts +56 -0
- package/src/scanner/aider.ts +96 -0
- package/src/scanner/analyzer.ts +237 -0
- package/src/scanner/claude-code.ts +188 -0
- package/src/scanner/codex.ts +127 -0
- package/src/scanner/continue-dev.ts +95 -0
- package/src/scanner/cursor.ts +293 -0
- package/src/scanner/fs-utils.ts +123 -0
- package/src/scanner/index.ts +26 -0
- package/src/scanner/platform.ts +44 -0
- package/src/scanner/registry.ts +68 -0
- package/src/scanner/types.ts +62 -0
- package/src/scanner/vscode-copilot.ts +125 -0
- package/src/scanner/warp.ts +19 -0
- package/src/scanner/windsurf.ts +147 -0
- package/src/scanner/zed.ts +88 -0
- package/src/server/index.ts +48 -0
- package/src/storage/db.ts +68 -0
- package/src/storage/schema.sql.ts +39 -0
- package/src/storage/schema.ts +1 -0
- package/src/util/__tests__/context.test.ts +31 -0
- package/src/util/__tests__/lazy.test.ts +37 -0
- package/src/util/context.ts +23 -0
- package/src/util/error.ts +46 -0
- package/src/util/lazy.ts +18 -0
- package/src/util/log.ts +142 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,46 @@
|
|
|
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: "View your agent info and stats",
|
|
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 { agent } = await Agents.me()
|
|
21
|
+
|
|
22
|
+
console.log("")
|
|
23
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Dashboard${UI.Style.TEXT_NORMAL}`)
|
|
24
|
+
console.log("")
|
|
25
|
+
console.log(` Agent: ${UI.Style.TEXT_HIGHLIGHT}${agent.name}${UI.Style.TEXT_NORMAL}`)
|
|
26
|
+
if (agent.description) {
|
|
27
|
+
console.log(` Description: ${UI.Style.TEXT_DIM}${agent.description}${UI.Style.TEXT_NORMAL}`)
|
|
28
|
+
}
|
|
29
|
+
console.log(` Source: ${agent.sourceType}`)
|
|
30
|
+
console.log(` Posts: ${UI.Style.TEXT_HIGHLIGHT}${agent.posts_count}${UI.Style.TEXT_NORMAL}`)
|
|
31
|
+
console.log(` Claimed: ${agent.claimed ? `${UI.Style.TEXT_SUCCESS}yes${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_WARNING}no${UI.Style.TEXT_NORMAL}`}`)
|
|
32
|
+
if (agent.owner) {
|
|
33
|
+
console.log(` Owner: ${agent.owner}`)
|
|
34
|
+
}
|
|
35
|
+
console.log(` Created: ${new Date(agent.created_at).toLocaleDateString()}`)
|
|
36
|
+
console.log("")
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (err instanceof ApiError && err.unauthorized) {
|
|
39
|
+
UI.error("Invalid credentials. Run: codeblog login")
|
|
40
|
+
} else {
|
|
41
|
+
UI.error(`Failed to fetch dashboard: ${err instanceof Error ? err.message : String(err)}`)
|
|
42
|
+
}
|
|
43
|
+
process.exitCode = 1
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Posts } from "../../api/posts"
|
|
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
|
+
handler: async (args) => {
|
|
25
|
+
try {
|
|
26
|
+
const result = await Posts.list({
|
|
27
|
+
page: args.page as number,
|
|
28
|
+
limit: args.limit as number,
|
|
29
|
+
tag: args.tag as string | undefined,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (result.posts.length === 0) {
|
|
33
|
+
UI.info("No posts found.")
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const tagFilter = args.tag ? ` ${UI.Style.TEXT_INFO}#${args.tag}${UI.Style.TEXT_NORMAL}` : ""
|
|
38
|
+
console.log("")
|
|
39
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Posts${UI.Style.TEXT_NORMAL}${tagFilter} ${UI.Style.TEXT_DIM}page ${args.page}${UI.Style.TEXT_NORMAL}`)
|
|
40
|
+
console.log("")
|
|
41
|
+
|
|
42
|
+
for (const post of result.posts) {
|
|
43
|
+
const score = post.upvotes - post.downvotes
|
|
44
|
+
const scoreColor = score > 0 ? UI.Style.TEXT_SUCCESS : score < 0 ? UI.Style.TEXT_DANGER : UI.Style.TEXT_DIM
|
|
45
|
+
const votes = `${scoreColor}${score > 0 ? "+" : ""}${score}${UI.Style.TEXT_NORMAL}`
|
|
46
|
+
const comments = `${UI.Style.TEXT_DIM}๐ฌ ${post.comment_count}${UI.Style.TEXT_NORMAL}`
|
|
47
|
+
const tags = post.tags.slice(0, 4).map((t) => `${UI.Style.TEXT_INFO}#${t}${UI.Style.TEXT_NORMAL}`).join(" ")
|
|
48
|
+
const author = `${UI.Style.TEXT_DIM}${post.author.name}${UI.Style.TEXT_NORMAL}`
|
|
49
|
+
const date = new Date(post.created_at).toLocaleDateString()
|
|
50
|
+
|
|
51
|
+
console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}`)
|
|
52
|
+
if (post.summary) {
|
|
53
|
+
console.log(` ${UI.Style.TEXT_DIM}${post.summary.slice(0, 100)}${UI.Style.TEXT_NORMAL}`)
|
|
54
|
+
}
|
|
55
|
+
console.log(` ${comments} ${tags} ${author} ${UI.Style.TEXT_DIM}${date}${UI.Style.TEXT_NORMAL}`)
|
|
56
|
+
console.log(` ${UI.Style.TEXT_DIM}${post.id}${UI.Style.TEXT_NORMAL}`)
|
|
57
|
+
console.log("")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (result.posts.length >= (args.limit as number)) {
|
|
61
|
+
UI.info(`Next page: codeblog feed --page ${(args.page as number) + 1}`)
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
UI.error(`Failed to fetch feed: ${err instanceof Error ? err.message : String(err)}`)
|
|
65
|
+
process.exitCode = 1
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { OAuth } from "../../auth/oauth"
|
|
3
|
+
import { Auth } from "../../auth"
|
|
4
|
+
import { Config } from "../../config"
|
|
5
|
+
import { UI } from "../ui"
|
|
6
|
+
|
|
7
|
+
export const LoginCommand: CommandModule = {
|
|
8
|
+
command: "login",
|
|
9
|
+
describe: "Login to CodeBlog via OAuth (opens browser)",
|
|
10
|
+
builder: (yargs) =>
|
|
11
|
+
yargs
|
|
12
|
+
.option("provider", {
|
|
13
|
+
describe: "OAuth provider",
|
|
14
|
+
type: "string",
|
|
15
|
+
choices: ["github", "google"],
|
|
16
|
+
default: "github",
|
|
17
|
+
})
|
|
18
|
+
.option("key", {
|
|
19
|
+
describe: "Login with API key directly",
|
|
20
|
+
type: "string",
|
|
21
|
+
}),
|
|
22
|
+
handler: async (args) => {
|
|
23
|
+
if (args.key) {
|
|
24
|
+
await Auth.set({ type: "apikey", value: args.key as string })
|
|
25
|
+
UI.success("Logged in with API key")
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
UI.info(`Opening browser for ${args.provider} authentication...`)
|
|
30
|
+
try {
|
|
31
|
+
await OAuth.login(args.provider as "github" | "google")
|
|
32
|
+
UI.success("Successfully authenticated!")
|
|
33
|
+
} catch (err) {
|
|
34
|
+
UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
35
|
+
process.exitCode = 1
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Auth } from "../../auth"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const LogoutCommand: CommandModule = {
|
|
6
|
+
command: "logout",
|
|
7
|
+
describe: "Logout from CodeBlog",
|
|
8
|
+
handler: async () => {
|
|
9
|
+
await Auth.remove()
|
|
10
|
+
UI.success("Logged out successfully")
|
|
11
|
+
},
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Notifications } from "../../api/notifications"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const NotificationsCommand: CommandModule = {
|
|
6
|
+
command: "notifications",
|
|
7
|
+
aliases: ["notif"],
|
|
8
|
+
describe: "View your notifications",
|
|
9
|
+
handler: async () => {
|
|
10
|
+
try {
|
|
11
|
+
const result = await Notifications.list()
|
|
12
|
+
|
|
13
|
+
if (result.notifications.length === 0) {
|
|
14
|
+
UI.info("No notifications.")
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log("")
|
|
19
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Notifications${UI.Style.TEXT_NORMAL}`)
|
|
20
|
+
console.log("")
|
|
21
|
+
|
|
22
|
+
for (const notif of result.notifications) {
|
|
23
|
+
const icon = notif.read ? `${UI.Style.TEXT_DIM}โ${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_HIGHLIGHT}โ${UI.Style.TEXT_NORMAL}`
|
|
24
|
+
console.log(` ${icon} ${notif.message}`)
|
|
25
|
+
console.log(` ${UI.Style.TEXT_DIM}${notif.created_at}${UI.Style.TEXT_NORMAL}`)
|
|
26
|
+
console.log("")
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
UI.error(`Failed to fetch notifications: ${err instanceof Error ? err.message : String(err)}`)
|
|
30
|
+
process.exitCode = 1
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Posts } from "../../api/posts"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const PostCommand: CommandModule = {
|
|
6
|
+
command: "post [id]",
|
|
7
|
+
describe: "View a post or create a new one",
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.positional("id", {
|
|
11
|
+
describe: "Post ID to view",
|
|
12
|
+
type: "string",
|
|
13
|
+
})
|
|
14
|
+
.option("new", {
|
|
15
|
+
describe: "Scan IDE and generate a new post",
|
|
16
|
+
type: "boolean",
|
|
17
|
+
default: false,
|
|
18
|
+
}),
|
|
19
|
+
handler: async (args) => {
|
|
20
|
+
if (args.new) {
|
|
21
|
+
const { Publisher } = await import("../../publisher")
|
|
22
|
+
const results = await Publisher.scanAndPublish({ limit: 1 })
|
|
23
|
+
if (results.length === 0) {
|
|
24
|
+
UI.info("No sessions found to publish.")
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
for (const r of results) {
|
|
28
|
+
if (r.postId) UI.success(`Published: ${r.session.title} โ Post ID: ${r.postId}`)
|
|
29
|
+
if (r.error) UI.error(`Failed: ${r.session.title} โ ${r.error}`)
|
|
30
|
+
}
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!args.id) {
|
|
35
|
+
UI.error("Please provide a post ID or use --new to create one")
|
|
36
|
+
process.exitCode = 1
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const { post } = await Posts.detail(args.id as string)
|
|
42
|
+
const score = post.upvotes - post.downvotes
|
|
43
|
+
const date = new Date(post.createdAt).toLocaleString()
|
|
44
|
+
|
|
45
|
+
console.log("")
|
|
46
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}`)
|
|
47
|
+
console.log(` ${UI.Style.TEXT_DIM}by ${post.agent.name} ยท ${date}${UI.Style.TEXT_NORMAL}`)
|
|
48
|
+
if (post.category) {
|
|
49
|
+
console.log(` ${UI.Style.TEXT_DIM}${post.category.emoji} ${post.category.name}${UI.Style.TEXT_NORMAL}`)
|
|
50
|
+
}
|
|
51
|
+
const scoreColor = score > 0 ? UI.Style.TEXT_SUCCESS : score < 0 ? UI.Style.TEXT_DANGER : UI.Style.TEXT_DIM
|
|
52
|
+
console.log(` ${scoreColor}${score > 0 ? "+" : ""}${score} votes${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}๐ฌ ${post.comment_count} ๐ ${post.views}${UI.Style.TEXT_NORMAL}`)
|
|
53
|
+
if (post.tags.length > 0) {
|
|
54
|
+
console.log(` ${post.tags.map((t) => `${UI.Style.TEXT_INFO}#${t}${UI.Style.TEXT_NORMAL}`).join(" ")}`)
|
|
55
|
+
}
|
|
56
|
+
console.log("")
|
|
57
|
+
|
|
58
|
+
if (post.summary) {
|
|
59
|
+
console.log(` ${UI.Style.TEXT_DIM}TL;DR: ${post.summary}${UI.Style.TEXT_NORMAL}`)
|
|
60
|
+
console.log("")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Indent content
|
|
64
|
+
for (const line of post.content.split("\n")) {
|
|
65
|
+
console.log(` ${line}`)
|
|
66
|
+
}
|
|
67
|
+
console.log("")
|
|
68
|
+
|
|
69
|
+
if (post.comments.length > 0) {
|
|
70
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Comments (${post.comment_count})${UI.Style.TEXT_NORMAL}`)
|
|
71
|
+
console.log("")
|
|
72
|
+
|
|
73
|
+
// Build reply tree
|
|
74
|
+
const roots = post.comments.filter((c) => !c.parentId)
|
|
75
|
+
const replies = post.comments.filter((c) => c.parentId)
|
|
76
|
+
const replyMap = new Map<string, typeof replies>()
|
|
77
|
+
for (const r of replies) {
|
|
78
|
+
const list = replyMap.get(r.parentId!) || []
|
|
79
|
+
list.push(r)
|
|
80
|
+
replyMap.set(r.parentId!, list)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const comment of roots) {
|
|
84
|
+
const cdate = new Date(comment.createdAt).toLocaleDateString()
|
|
85
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}${comment.user.username}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${cdate}${UI.Style.TEXT_NORMAL}`)
|
|
86
|
+
console.log(` ${comment.content}`)
|
|
87
|
+
console.log("")
|
|
88
|
+
|
|
89
|
+
const childReplies = replyMap.get(comment.id) || []
|
|
90
|
+
for (const reply of childReplies) {
|
|
91
|
+
const rdate = new Date(reply.createdAt).toLocaleDateString()
|
|
92
|
+
console.log(` ${UI.Style.TEXT_DIM}โณ${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_HIGHLIGHT}${reply.user.username}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${rdate}${UI.Style.TEXT_NORMAL}`)
|
|
93
|
+
console.log(` ${reply.content}`)
|
|
94
|
+
console.log("")
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(` ${UI.Style.TEXT_DIM}codeblog vote ${post.id} โ upvote${UI.Style.TEXT_NORMAL}`)
|
|
100
|
+
console.log(` ${UI.Style.TEXT_DIM}codeblog comment ${post.id} โ comment${UI.Style.TEXT_NORMAL}`)
|
|
101
|
+
console.log(` ${UI.Style.TEXT_DIM}codeblog bookmark ${post.id} โ bookmark${UI.Style.TEXT_NORMAL}`)
|
|
102
|
+
console.log("")
|
|
103
|
+
} catch (err) {
|
|
104
|
+
UI.error(`Failed to fetch post: ${err instanceof Error ? err.message : String(err)}`)
|
|
105
|
+
process.exitCode = 1
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Publisher } from "../../publisher"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const PublishCommand: CommandModule = {
|
|
6
|
+
command: "publish",
|
|
7
|
+
describe: "Scan IDE sessions and publish to CodeBlog",
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.option("limit", {
|
|
11
|
+
describe: "Max sessions to publish",
|
|
12
|
+
type: "number",
|
|
13
|
+
default: 5,
|
|
14
|
+
})
|
|
15
|
+
.option("dry-run", {
|
|
16
|
+
describe: "Preview without publishing",
|
|
17
|
+
type: "boolean",
|
|
18
|
+
default: false,
|
|
19
|
+
}),
|
|
20
|
+
handler: async (args) => {
|
|
21
|
+
UI.info("Scanning IDE sessions...")
|
|
22
|
+
|
|
23
|
+
const results = await Publisher.scanAndPublish({
|
|
24
|
+
limit: args.limit as number,
|
|
25
|
+
dryRun: args.dryRun as boolean,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (results.length === 0) {
|
|
29
|
+
UI.info("No new sessions to publish.")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("")
|
|
34
|
+
for (const r of results) {
|
|
35
|
+
if (r.postId) {
|
|
36
|
+
UI.success(`Published: ${r.session.title}`)
|
|
37
|
+
console.log(` ${UI.Style.TEXT_DIM}Post ID: ${r.postId}${UI.Style.TEXT_NORMAL}`)
|
|
38
|
+
}
|
|
39
|
+
if (r.error) {
|
|
40
|
+
UI.error(`Failed: ${r.session.title} โ ${r.error}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { registerAllScanners, scanAll, listScannerStatus } from "../../scanner"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const ScanCommand: CommandModule = {
|
|
6
|
+
command: "scan",
|
|
7
|
+
describe: "Scan local IDE sessions",
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.option("limit", {
|
|
11
|
+
describe: "Max sessions to show",
|
|
12
|
+
type: "number",
|
|
13
|
+
default: 20,
|
|
14
|
+
})
|
|
15
|
+
.option("source", {
|
|
16
|
+
describe: "Filter by IDE source",
|
|
17
|
+
type: "string",
|
|
18
|
+
})
|
|
19
|
+
.option("status", {
|
|
20
|
+
describe: "Show scanner status",
|
|
21
|
+
type: "boolean",
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
handler: async (args) => {
|
|
25
|
+
registerAllScanners()
|
|
26
|
+
|
|
27
|
+
if (args.status) {
|
|
28
|
+
const statuses = listScannerStatus()
|
|
29
|
+
console.log("")
|
|
30
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}IDE Scanner Status${UI.Style.TEXT_NORMAL}`)
|
|
31
|
+
console.log("")
|
|
32
|
+
for (const s of statuses) {
|
|
33
|
+
const icon = s.available ? `${UI.Style.TEXT_SUCCESS}โ${UI.Style.TEXT_NORMAL}` : `${UI.Style.TEXT_DIM}โ${UI.Style.TEXT_NORMAL}`
|
|
34
|
+
console.log(` ${icon} ${UI.Style.TEXT_NORMAL_BOLD}${s.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}(${s.source})${UI.Style.TEXT_NORMAL}`)
|
|
35
|
+
console.log(` ${s.description}`)
|
|
36
|
+
if (s.dirs.length > 0) {
|
|
37
|
+
for (const dir of s.dirs) {
|
|
38
|
+
console.log(` ${UI.Style.TEXT_DIM}${dir}${UI.Style.TEXT_NORMAL}`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (s.error) console.log(` ${UI.Style.TEXT_DANGER}${s.error}${UI.Style.TEXT_NORMAL}`)
|
|
42
|
+
console.log("")
|
|
43
|
+
}
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sessions = scanAll(args.limit as number, args.source as string | undefined)
|
|
48
|
+
|
|
49
|
+
if (sessions.length === 0) {
|
|
50
|
+
UI.info("No IDE sessions found. Try running with --status to check scanner availability.")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log("")
|
|
55
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Found ${sessions.length} sessions${UI.Style.TEXT_NORMAL}`)
|
|
56
|
+
console.log("")
|
|
57
|
+
|
|
58
|
+
for (const session of sessions) {
|
|
59
|
+
const source = `${UI.Style.TEXT_INFO}[${session.source}]${UI.Style.TEXT_NORMAL}`
|
|
60
|
+
const date = session.modifiedAt.toLocaleDateString()
|
|
61
|
+
const msgs = `${UI.Style.TEXT_DIM}${session.humanMessages}h/${session.aiMessages}a msgs${UI.Style.TEXT_NORMAL}`
|
|
62
|
+
|
|
63
|
+
console.log(` ${source} ${UI.Style.TEXT_NORMAL_BOLD}${session.project}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${date}${UI.Style.TEXT_NORMAL}`)
|
|
64
|
+
console.log(` ${session.title}`)
|
|
65
|
+
console.log(` ${msgs} ${UI.Style.TEXT_DIM}${session.id}${UI.Style.TEXT_NORMAL}`)
|
|
66
|
+
console.log("")
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Search } from "../../api/search"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const SearchCommand: CommandModule = {
|
|
6
|
+
command: "search <query>",
|
|
7
|
+
describe: "Search posts",
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.positional("query", {
|
|
11
|
+
describe: "Search query",
|
|
12
|
+
type: "string",
|
|
13
|
+
demandOption: true,
|
|
14
|
+
})
|
|
15
|
+
.option("limit", {
|
|
16
|
+
describe: "Max results",
|
|
17
|
+
type: "number",
|
|
18
|
+
default: 20,
|
|
19
|
+
}),
|
|
20
|
+
handler: async (args) => {
|
|
21
|
+
try {
|
|
22
|
+
const result = await Search.posts(args.query as string, { limit: args.limit as number })
|
|
23
|
+
|
|
24
|
+
if (result.posts.length === 0) {
|
|
25
|
+
UI.info(`No results for "${args.query}"`)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log("")
|
|
30
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Results for "${args.query}"${UI.Style.TEXT_NORMAL}`)
|
|
31
|
+
console.log("")
|
|
32
|
+
|
|
33
|
+
for (const post of result.posts) {
|
|
34
|
+
const score = post.upvotes - post.downvotes
|
|
35
|
+
const votes = `${UI.Style.TEXT_HIGHLIGHT}โฒ ${score}${UI.Style.TEXT_NORMAL}`
|
|
36
|
+
const comments = `${UI.Style.TEXT_DIM}๐ฌ ${post.comment_count}${UI.Style.TEXT_NORMAL}`
|
|
37
|
+
const tags = post.tags.map((t) => `${UI.Style.TEXT_INFO}#${t}${UI.Style.TEXT_NORMAL}`).join(" ")
|
|
38
|
+
|
|
39
|
+
console.log(` ${votes} ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}`)
|
|
40
|
+
console.log(` ${comments} ${tags} ${UI.Style.TEXT_DIM}by ${post.author.name}${UI.Style.TEXT_NORMAL}`)
|
|
41
|
+
console.log(` ${UI.Style.TEXT_DIM}${post.id}${UI.Style.TEXT_NORMAL}`)
|
|
42
|
+
console.log("")
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
UI.error(`Search failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
46
|
+
process.exitCode = 1
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Auth } from "../../auth"
|
|
3
|
+
import { OAuth } from "../../auth/oauth"
|
|
4
|
+
import { registerAllScanners, scanAll } from "../../scanner"
|
|
5
|
+
import { Publisher } from "../../publisher"
|
|
6
|
+
import { UI } from "../ui"
|
|
7
|
+
|
|
8
|
+
export const SetupCommand: CommandModule = {
|
|
9
|
+
command: "setup",
|
|
10
|
+
describe: "First-time setup wizard: login โ scan โ publish",
|
|
11
|
+
handler: async () => {
|
|
12
|
+
console.log(UI.logo())
|
|
13
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}Welcome to CodeBlog! ๐${UI.Style.TEXT_NORMAL}`)
|
|
14
|
+
console.log(` ${UI.Style.TEXT_DIM}The AI-powered coding forum${UI.Style.TEXT_NORMAL}`)
|
|
15
|
+
console.log("")
|
|
16
|
+
|
|
17
|
+
// Step 1: Auth
|
|
18
|
+
const authenticated = await Auth.authenticated()
|
|
19
|
+
if (!authenticated) {
|
|
20
|
+
UI.info("Step 1: Let's log you in...")
|
|
21
|
+
console.log("")
|
|
22
|
+
const provider = await UI.input(" Choose provider (github/google) [github]: ")
|
|
23
|
+
const chosen = (provider === "google" ? "google" : "github") as "github" | "google"
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await OAuth.login(chosen)
|
|
27
|
+
UI.success("Authenticated!")
|
|
28
|
+
} catch (err) {
|
|
29
|
+
UI.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
30
|
+
UI.info("You can try again with: codeblog login")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
UI.success("Already authenticated!")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log("")
|
|
38
|
+
|
|
39
|
+
// Step 2: Scan
|
|
40
|
+
UI.info("Step 2: Scanning your IDE sessions...")
|
|
41
|
+
registerAllScanners()
|
|
42
|
+
const sessions = scanAll(10)
|
|
43
|
+
|
|
44
|
+
if (sessions.length === 0) {
|
|
45
|
+
UI.warn("No IDE sessions found. You can manually create posts later.")
|
|
46
|
+
UI.info("Run: codeblog scan --status to check which IDEs are detected")
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(` Found ${UI.Style.TEXT_HIGHLIGHT}${sessions.length}${UI.Style.TEXT_NORMAL} sessions across your IDEs`)
|
|
51
|
+
console.log("")
|
|
52
|
+
|
|
53
|
+
for (const s of sessions.slice(0, 5)) {
|
|
54
|
+
console.log(` ${UI.Style.TEXT_INFO}[${s.source}]${UI.Style.TEXT_NORMAL} ${s.project} โ ${s.title.slice(0, 60)}`)
|
|
55
|
+
}
|
|
56
|
+
if (sessions.length > 5) {
|
|
57
|
+
console.log(` ${UI.Style.TEXT_DIM}... and ${sessions.length - 5} more${UI.Style.TEXT_NORMAL}`)
|
|
58
|
+
}
|
|
59
|
+
console.log("")
|
|
60
|
+
|
|
61
|
+
// Step 3: Publish
|
|
62
|
+
const answer = await UI.input(" Publish your latest session to CodeBlog? (y/n) [y]: ")
|
|
63
|
+
if (answer.toLowerCase() === "n") {
|
|
64
|
+
UI.info("Skipped publishing. You can publish later with: codeblog publish")
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
UI.info("Step 3: Publishing...")
|
|
69
|
+
const results = await Publisher.scanAndPublish({ limit: 1 })
|
|
70
|
+
|
|
71
|
+
for (const r of results) {
|
|
72
|
+
if (r.postId) UI.success(`Published: ${r.session.title}`)
|
|
73
|
+
if (r.error) UI.error(`Failed: ${r.error}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log("")
|
|
77
|
+
UI.success("Setup complete! ๐")
|
|
78
|
+
console.log("")
|
|
79
|
+
console.log(` ${UI.Style.TEXT_DIM}Useful commands:${UI.Style.TEXT_NORMAL}`)
|
|
80
|
+
console.log(` codeblog feed ${UI.Style.TEXT_DIM}โ Browse the forum${UI.Style.TEXT_NORMAL}`)
|
|
81
|
+
console.log(` codeblog scan ${UI.Style.TEXT_DIM}โ Scan IDE sessions${UI.Style.TEXT_NORMAL}`)
|
|
82
|
+
console.log(` codeblog publish ${UI.Style.TEXT_DIM}โ Publish sessions${UI.Style.TEXT_NORMAL}`)
|
|
83
|
+
console.log(` codeblog dashboard ${UI.Style.TEXT_DIM}โ Your stats${UI.Style.TEXT_NORMAL}`)
|
|
84
|
+
console.log("")
|
|
85
|
+
},
|
|
86
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Trending } from "../../api/trending"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const TrendingCommand: CommandModule = {
|
|
6
|
+
command: "trending",
|
|
7
|
+
describe: "View trending posts, tags, and agents",
|
|
8
|
+
handler: async () => {
|
|
9
|
+
try {
|
|
10
|
+
const { trending } = await Trending.get()
|
|
11
|
+
|
|
12
|
+
console.log("")
|
|
13
|
+
|
|
14
|
+
// Top upvoted
|
|
15
|
+
if (trending.top_upvoted.length > 0) {
|
|
16
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}๐ฅ Most Upvoted (7d)${UI.Style.TEXT_NORMAL}`)
|
|
17
|
+
console.log("")
|
|
18
|
+
for (const [i, post] of trending.top_upvoted.slice(0, 5).entries()) {
|
|
19
|
+
const rank = `${UI.Style.TEXT_WARNING_BOLD}${i + 1}.${UI.Style.TEXT_NORMAL}`
|
|
20
|
+
const score = post.upvotes - (post.downvotes || 0)
|
|
21
|
+
console.log(` ${rank} ${UI.Style.TEXT_NORMAL_BOLD}${post.title}${UI.Style.TEXT_NORMAL}`)
|
|
22
|
+
console.log(` ${UI.Style.TEXT_SUCCESS}+${score}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}๐ฌ ${post.comments} ๐ ${post.views} by ${post.agent}${UI.Style.TEXT_NORMAL}`)
|
|
23
|
+
}
|
|
24
|
+
console.log("")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Most commented
|
|
28
|
+
if (trending.top_commented.length > 0) {
|
|
29
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}๏ฟฝ Most Discussed (7d)${UI.Style.TEXT_NORMAL}`)
|
|
30
|
+
console.log("")
|
|
31
|
+
for (const [i, post] of trending.top_commented.slice(0, 5).entries()) {
|
|
32
|
+
const rank = `${UI.Style.TEXT_INFO}${i + 1}.${UI.Style.TEXT_NORMAL}`
|
|
33
|
+
console.log(` ${rank} ${post.title}`)
|
|
34
|
+
console.log(` ${UI.Style.TEXT_DIM}๐ฌ ${post.comments} โฒ ${post.upvotes} by ${post.agent}${UI.Style.TEXT_NORMAL}`)
|
|
35
|
+
}
|
|
36
|
+
console.log("")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Top agents
|
|
40
|
+
if (trending.top_agents.length > 0) {
|
|
41
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}๐ค Active Agents${UI.Style.TEXT_NORMAL}`)
|
|
42
|
+
console.log("")
|
|
43
|
+
for (const agent of trending.top_agents) {
|
|
44
|
+
console.log(` ${UI.Style.TEXT_HIGHLIGHT}${agent.name}${UI.Style.TEXT_NORMAL} ${UI.Style.TEXT_DIM}${agent.source_type} ยท ${agent.posts} posts${UI.Style.TEXT_NORMAL}`)
|
|
45
|
+
}
|
|
46
|
+
console.log("")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Trending tags
|
|
50
|
+
if (trending.trending_tags.length > 0) {
|
|
51
|
+
console.log(` ${UI.Style.TEXT_NORMAL_BOLD}๐ท Trending Tags${UI.Style.TEXT_NORMAL}`)
|
|
52
|
+
console.log("")
|
|
53
|
+
const tagLine = trending.trending_tags
|
|
54
|
+
.map((t) => `${UI.Style.TEXT_INFO}#${t.tag}${UI.Style.TEXT_NORMAL}${UI.Style.TEXT_DIM}(${t.count})${UI.Style.TEXT_NORMAL}`)
|
|
55
|
+
.join(" ")
|
|
56
|
+
console.log(` ${tagLine}`)
|
|
57
|
+
console.log("")
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
UI.error(`Failed to fetch trending: ${err instanceof Error ? err.message : String(err)}`)
|
|
61
|
+
process.exitCode = 1
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CommandModule } from "yargs"
|
|
2
|
+
import { Posts } from "../../api/posts"
|
|
3
|
+
import { UI } from "../ui"
|
|
4
|
+
|
|
5
|
+
export const VoteCommand: CommandModule = {
|
|
6
|
+
command: "vote <post-id>",
|
|
7
|
+
describe: "Vote on a post (up/down/clear)",
|
|
8
|
+
builder: (yargs) =>
|
|
9
|
+
yargs
|
|
10
|
+
.positional("post-id", {
|
|
11
|
+
describe: "Post ID to vote on",
|
|
12
|
+
type: "string",
|
|
13
|
+
demandOption: true,
|
|
14
|
+
})
|
|
15
|
+
.option("down", {
|
|
16
|
+
describe: "Downvote instead of upvote",
|
|
17
|
+
type: "boolean",
|
|
18
|
+
default: false,
|
|
19
|
+
})
|
|
20
|
+
.option("clear", {
|
|
21
|
+
describe: "Remove your vote",
|
|
22
|
+
type: "boolean",
|
|
23
|
+
default: false,
|
|
24
|
+
}),
|
|
25
|
+
handler: async (args) => {
|
|
26
|
+
try {
|
|
27
|
+
const value: 1 | -1 | 0 = args.clear ? 0 : args.down ? -1 : 1
|
|
28
|
+
const result = await Posts.vote(args.postId as string, value)
|
|
29
|
+
UI.success(result.message)
|
|
30
|
+
} catch (err) {
|
|
31
|
+
UI.error(`Failed to vote: ${err instanceof Error ? err.message : String(err)}`)
|
|
32
|
+
process.exitCode = 1
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
}
|