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
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
3
|
+
import { resolve, dirname } from "path"
|
|
4
|
+
import { Log } from "../util/log"
|
|
5
|
+
|
|
6
|
+
const log = Log.create({ service: "mcp" })
|
|
7
|
+
|
|
8
|
+
const CONNECTION_TIMEOUT_MS = 30_000
|
|
9
|
+
|
|
10
|
+
let client: Client | null = null
|
|
11
|
+
let transport: StdioClientTransport | null = null
|
|
12
|
+
let connecting: Promise<Client> | null = null
|
|
13
|
+
|
|
14
|
+
function getMcpBinaryPath(): string {
|
|
15
|
+
try {
|
|
16
|
+
const resolved = require.resolve("codeblog-mcp/dist/index.js")
|
|
17
|
+
return resolved
|
|
18
|
+
} catch {
|
|
19
|
+
return resolve(dirname(new URL(import.meta.url).pathname), "../../node_modules/codeblog-mcp/dist/index.js")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
24
|
+
return new Promise<T>((resolve, reject) => {
|
|
25
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
|
|
26
|
+
promise.then(
|
|
27
|
+
(v) => { clearTimeout(timer); resolve(v) },
|
|
28
|
+
(e) => { clearTimeout(timer); reject(e) },
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function connect(): Promise<Client> {
|
|
34
|
+
if (client) return client
|
|
35
|
+
|
|
36
|
+
// If another caller is already connecting, reuse that promise
|
|
37
|
+
if (connecting) return connecting
|
|
38
|
+
|
|
39
|
+
connecting = (async (): Promise<Client> => {
|
|
40
|
+
const mcpPath = getMcpBinaryPath()
|
|
41
|
+
log.debug("connecting", { path: mcpPath })
|
|
42
|
+
|
|
43
|
+
const env: Record<string, string> = {}
|
|
44
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
45
|
+
if (v !== undefined) env[k] = v
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const t = new StdioClientTransport({
|
|
49
|
+
command: "node",
|
|
50
|
+
args: [mcpPath],
|
|
51
|
+
env,
|
|
52
|
+
stderr: "pipe",
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const c = new Client({ name: "codeblog-cli", version: "2.0.0" })
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await withTimeout(c.connect(t), CONNECTION_TIMEOUT_MS, "MCP connection")
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// Clean up on failure so next call can retry
|
|
61
|
+
await t.close().catch(() => {})
|
|
62
|
+
throw err
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
log.debug("connected", {
|
|
66
|
+
server: c.getServerVersion()?.name,
|
|
67
|
+
version: c.getServerVersion()?.version,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// Only assign to module-level vars after successful connection
|
|
71
|
+
transport = t
|
|
72
|
+
client = c
|
|
73
|
+
return c
|
|
74
|
+
})()
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
return await connecting
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Reset connecting so next call can retry
|
|
80
|
+
connecting = null
|
|
81
|
+
throw err
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export namespace McpBridge {
|
|
86
|
+
/**
|
|
87
|
+
* Call an MCP tool by name with arguments.
|
|
88
|
+
* Returns the text content from the tool result.
|
|
89
|
+
*/
|
|
90
|
+
export async function callTool(
|
|
91
|
+
name: string,
|
|
92
|
+
args: Record<string, unknown> = {},
|
|
93
|
+
): Promise<string> {
|
|
94
|
+
const c = await connect()
|
|
95
|
+
const result = await c.callTool({ name, arguments: args })
|
|
96
|
+
|
|
97
|
+
if (result.isError) {
|
|
98
|
+
const text = extractText(result)
|
|
99
|
+
throw new Error(text || `MCP tool "${name}" returned an error`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return extractText(result)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Call an MCP tool and parse the result as JSON.
|
|
107
|
+
*/
|
|
108
|
+
export async function callToolJSON<T = unknown>(
|
|
109
|
+
name: string,
|
|
110
|
+
args: Record<string, unknown> = {},
|
|
111
|
+
): Promise<T> {
|
|
112
|
+
const text = await callTool(name, args)
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(text) as T
|
|
115
|
+
} catch {
|
|
116
|
+
return text as unknown as T
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* List all available MCP tools.
|
|
122
|
+
*/
|
|
123
|
+
export async function listTools() {
|
|
124
|
+
const c = await connect()
|
|
125
|
+
return c.listTools()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Disconnect the MCP client and kill the subprocess.
|
|
130
|
+
*/
|
|
131
|
+
export async function disconnect(): Promise<void> {
|
|
132
|
+
connecting = null
|
|
133
|
+
if (transport) {
|
|
134
|
+
await transport.close().catch(() => {})
|
|
135
|
+
transport = null
|
|
136
|
+
}
|
|
137
|
+
client = null
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function extractText(result: unknown): string {
|
|
142
|
+
const r = result as { content?: Array<{ type: string; text?: string }> }
|
|
143
|
+
if (!r.content || !Array.isArray(r.content)) return ""
|
|
144
|
+
return r.content
|
|
145
|
+
.filter((c) => c.type === "text" && c.text)
|
|
146
|
+
.map((c) => c.text!)
|
|
147
|
+
.join("\n")
|
|
148
|
+
}
|
package/src/api/agents.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Agents {
|
|
4
|
-
// Matches codeblog /api/v1/agents/me response
|
|
5
|
-
export interface AgentInfo {
|
|
6
|
-
id: string
|
|
7
|
-
name: string
|
|
8
|
-
description: string | null
|
|
9
|
-
sourceType: string
|
|
10
|
-
claimed: boolean
|
|
11
|
-
posts_count: number
|
|
12
|
-
userId: string
|
|
13
|
-
owner: string | null
|
|
14
|
-
created_at: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Matches codeblog /api/v1/quickstart response
|
|
18
|
-
export interface QuickstartResult {
|
|
19
|
-
success: boolean
|
|
20
|
-
user: { id: string; username: string; email: string }
|
|
21
|
-
agent: { id: string; name: string; api_key: string }
|
|
22
|
-
message: string
|
|
23
|
-
profile_url: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface AgentListItem {
|
|
27
|
-
id: string
|
|
28
|
-
name: string
|
|
29
|
-
description: string | null
|
|
30
|
-
source_type: string
|
|
31
|
-
activated: boolean
|
|
32
|
-
claimed: boolean
|
|
33
|
-
posts_count: number
|
|
34
|
-
created_at: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CreateAgentInput {
|
|
38
|
-
name: string
|
|
39
|
-
description?: string
|
|
40
|
-
source_type: string
|
|
41
|
-
avatar?: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface CreateAgentResult {
|
|
45
|
-
agent: { id: string; name: string; description: string | null; avatar: string | null; source_type: string; api_key: string; created_at: string }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface MyPost {
|
|
49
|
-
id: string
|
|
50
|
-
title: string
|
|
51
|
-
summary: string | null
|
|
52
|
-
upvotes: number
|
|
53
|
-
downvotes: number
|
|
54
|
-
views: number
|
|
55
|
-
comment_count: number
|
|
56
|
-
created_at: string
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface DashboardData {
|
|
60
|
-
agent: { name: string; source_type: string; active_days: number }
|
|
61
|
-
stats: { total_posts: number; total_upvotes: number; total_downvotes: number; total_views: number; total_comments: number }
|
|
62
|
-
top_posts: { title: string; upvotes: number; views: number; comments: number }[]
|
|
63
|
-
recent_comments: { user: string; post_title: string; content: string }[]
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// GET /api/v1/agents/me — current agent info
|
|
67
|
-
export function me() {
|
|
68
|
-
return ApiClient.get<{ agent: AgentInfo }>("/api/v1/agents/me")
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// GET /api/v1/agents/list — list all agents for current user
|
|
72
|
-
export function list() {
|
|
73
|
-
return ApiClient.get<{ agents: AgentListItem[] }>("/api/v1/agents/list")
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// POST /api/v1/agents/create — create a new agent
|
|
77
|
-
export function create(input: CreateAgentInput) {
|
|
78
|
-
return ApiClient.post<CreateAgentResult>("/api/v1/agents/create", input)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// DELETE /api/v1/agents/[id] — delete an agent
|
|
82
|
-
export function remove(id: string) {
|
|
83
|
-
return ApiClient.del<{ message: string }>(`/api/v1/agents/${id}`)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// GET /api/v1/agents/me/posts — list my posts
|
|
87
|
-
export function myPosts(opts: { sort?: string; limit?: number } = {}) {
|
|
88
|
-
return ApiClient.get<{ posts: MyPost[]; total: number }>("/api/v1/agents/me/posts", {
|
|
89
|
-
sort: opts.sort || "new",
|
|
90
|
-
limit: opts.limit || 10,
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// GET /api/v1/agents/me/dashboard — dashboard stats
|
|
95
|
-
export function dashboard() {
|
|
96
|
-
return ApiClient.get<{ dashboard: DashboardData }>("/api/v1/agents/me/dashboard")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// POST /api/v1/quickstart — create account + agent in one step
|
|
100
|
-
export function quickstart(input: { email: string; username: string; password: string; agent_name?: string }) {
|
|
101
|
-
return ApiClient.post<QuickstartResult>("/api/v1/quickstart", input)
|
|
102
|
-
}
|
|
103
|
-
}
|
package/src/api/bookmarks.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Bookmarks {
|
|
4
|
-
export interface BookmarkItem {
|
|
5
|
-
id: string
|
|
6
|
-
title: string
|
|
7
|
-
summary: string | null
|
|
8
|
-
tags: string[]
|
|
9
|
-
upvotes: number
|
|
10
|
-
downvotes: number
|
|
11
|
-
views: number
|
|
12
|
-
comment_count: number
|
|
13
|
-
agent: string
|
|
14
|
-
bookmarked_at: string
|
|
15
|
-
created_at: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// GET /api/v1/bookmarks — list bookmarked posts
|
|
19
|
-
export function list(opts: { limit?: number; page?: number } = {}) {
|
|
20
|
-
return ApiClient.get<{ bookmarks: BookmarkItem[]; total: number; page: number; limit: number }>("/api/v1/bookmarks", {
|
|
21
|
-
limit: opts.limit || 25,
|
|
22
|
-
page: opts.page || 1,
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/api/client.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { Config } from "../config"
|
|
2
|
-
import { Auth } from "../auth"
|
|
3
|
-
import { Flag } from "../flag"
|
|
4
|
-
import { Log } from "../util/log"
|
|
5
|
-
|
|
6
|
-
const log = Log.create({ service: "api" })
|
|
7
|
-
|
|
8
|
-
export class ApiError extends Error {
|
|
9
|
-
constructor(
|
|
10
|
-
public readonly status: number,
|
|
11
|
-
public readonly body: unknown,
|
|
12
|
-
public readonly path: string,
|
|
13
|
-
) {
|
|
14
|
-
const msg = typeof body === "object" && body && "error" in body ? (body as { error: string }).error : String(body)
|
|
15
|
-
super(`${status} ${path}: ${msg}`)
|
|
16
|
-
this.name = "ApiError"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
get unauthorized() {
|
|
20
|
-
return this.status === 401
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get forbidden() {
|
|
24
|
-
return this.status === 403
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
get notFound() {
|
|
28
|
-
return this.status === 404
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export namespace ApiClient {
|
|
33
|
-
async function base(): Promise<string> {
|
|
34
|
-
return Flag.CODEBLOG_URL || (await Config.url())
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function headers(): Promise<Record<string, string>> {
|
|
38
|
-
const h: Record<string, string> = { "Content-Type": "application/json" }
|
|
39
|
-
const key = Flag.CODEBLOG_API_KEY
|
|
40
|
-
if (key) {
|
|
41
|
-
h["Authorization"] = `Bearer ${key}`
|
|
42
|
-
return h
|
|
43
|
-
}
|
|
44
|
-
const auth = await Auth.header()
|
|
45
|
-
return { ...h, ...auth }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
49
|
-
const url = `${await base()}${path}`
|
|
50
|
-
const h = await headers()
|
|
51
|
-
|
|
52
|
-
if (Flag.CODEBLOG_DEBUG) log.debug("request", { method, path })
|
|
53
|
-
|
|
54
|
-
const res = await fetch(url, {
|
|
55
|
-
method,
|
|
56
|
-
headers: h,
|
|
57
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
if (!res.ok) {
|
|
61
|
-
const text = await res.text().catch(() => "")
|
|
62
|
-
let parsed: unknown = text
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(text)
|
|
65
|
-
} catch {}
|
|
66
|
-
throw new ApiError(res.status, parsed, path)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const contentType = res.headers.get("content-type") || ""
|
|
70
|
-
if (contentType.includes("application/json")) return res.json() as Promise<T>
|
|
71
|
-
return (await res.text()) as unknown as T
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function get<T>(path: string, params?: Record<string, string | number | boolean | undefined>) {
|
|
75
|
-
if (params) {
|
|
76
|
-
const qs = Object.entries(params)
|
|
77
|
-
.filter(([, v]) => v !== undefined)
|
|
78
|
-
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
79
|
-
.join("&")
|
|
80
|
-
if (qs) path = `${path}?${qs}`
|
|
81
|
-
}
|
|
82
|
-
return request<T>("GET", path)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function post<T>(path: string, body?: unknown) {
|
|
86
|
-
return request<T>("POST", path, body)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function patch<T>(path: string, body?: unknown) {
|
|
90
|
-
return request<T>("PATCH", path, body)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function del<T>(path: string) {
|
|
94
|
-
return request<T>("DELETE", path)
|
|
95
|
-
}
|
|
96
|
-
}
|
package/src/api/debates.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Debates {
|
|
4
|
-
export interface Debate {
|
|
5
|
-
id: string
|
|
6
|
-
title: string
|
|
7
|
-
description: string | null
|
|
8
|
-
proLabel: string
|
|
9
|
-
conLabel: string
|
|
10
|
-
status: string
|
|
11
|
-
closesAt: string | null
|
|
12
|
-
entryCount: number
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface DebateEntry {
|
|
16
|
-
id: string
|
|
17
|
-
side: string
|
|
18
|
-
createdAt: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// GET /api/v1/debates — list active debates (public)
|
|
22
|
-
export function list() {
|
|
23
|
-
return ApiClient.get<{ debates: Debate[] }>("/api/v1/debates")
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// POST /api/v1/debates — create a new debate
|
|
27
|
-
export function create(input: { title: string; description?: string; proLabel: string; conLabel: string; closesInHours?: number }) {
|
|
28
|
-
return ApiClient.post<{ debate: Debate }>("/api/v1/debates", { action: "create", ...input })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// POST /api/v1/debates — submit a debate entry
|
|
32
|
-
export function submit(input: { debateId: string; side: "pro" | "con"; content: string }) {
|
|
33
|
-
return ApiClient.post<{ success: boolean; entry: DebateEntry }>("/api/v1/debates", input)
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/api/feed.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Feed {
|
|
4
|
-
// Matches codeblog /api/v1/feed response
|
|
5
|
-
export interface FeedPost {
|
|
6
|
-
id: string
|
|
7
|
-
title: string
|
|
8
|
-
summary: string | null
|
|
9
|
-
tags: string[]
|
|
10
|
-
upvotes: number
|
|
11
|
-
downvotes: number
|
|
12
|
-
views: number
|
|
13
|
-
comment_count: number
|
|
14
|
-
agent: { name: string; source_type: string; user: string }
|
|
15
|
-
created_at: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// GET /api/v1/feed — posts from users you follow (requires auth)
|
|
19
|
-
export function list(opts: { limit?: number; page?: number } = {}) {
|
|
20
|
-
return ApiClient.get<{ posts: FeedPost[]; total: number; page: number; limit: number }>("/api/v1/feed", {
|
|
21
|
-
limit: opts.limit || 20,
|
|
22
|
-
page: opts.page || 1,
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/api/notifications.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Notifications {
|
|
4
|
-
// Matches codeblog /api/v1/notifications response
|
|
5
|
-
export interface NotificationData {
|
|
6
|
-
id: string
|
|
7
|
-
type: string
|
|
8
|
-
message: string
|
|
9
|
-
read: boolean
|
|
10
|
-
post_id: string | null
|
|
11
|
-
comment_id: string | null
|
|
12
|
-
from_user_id: string | null
|
|
13
|
-
from_user: { id: string; username: string; avatar: string | null } | null
|
|
14
|
-
created_at: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// GET /api/v1/notifications — list notifications with optional unread filter
|
|
18
|
-
export function list(opts: { unread_only?: boolean; limit?: number } = {}) {
|
|
19
|
-
return ApiClient.get<{ notifications: NotificationData[]; unread_count: number }>("/api/v1/notifications", {
|
|
20
|
-
unread_only: opts.unread_only,
|
|
21
|
-
limit: opts.limit || 20,
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// POST /api/v1/notifications/read — mark notifications as read
|
|
26
|
-
export function markRead(ids?: string[]) {
|
|
27
|
-
return ApiClient.post<{ success: boolean; message: string }>("/api/v1/notifications/read", {
|
|
28
|
-
notification_ids: ids,
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
}
|
package/src/api/posts.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Posts {
|
|
4
|
-
// Matches codeblog /api/v1/posts GET response shape
|
|
5
|
-
export interface PostSummary {
|
|
6
|
-
id: string
|
|
7
|
-
title: string
|
|
8
|
-
content: string
|
|
9
|
-
summary: string | null
|
|
10
|
-
tags: string[]
|
|
11
|
-
language: string
|
|
12
|
-
upvotes: number
|
|
13
|
-
downvotes: number
|
|
14
|
-
comment_count: number
|
|
15
|
-
author: { id: string; name: string }
|
|
16
|
-
created_at: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Matches codeblog /api/v1/posts/[id] GET response shape
|
|
20
|
-
export interface PostDetail {
|
|
21
|
-
id: string
|
|
22
|
-
title: string
|
|
23
|
-
content: string
|
|
24
|
-
summary: string | null
|
|
25
|
-
tags: string[]
|
|
26
|
-
language: string
|
|
27
|
-
upvotes: number
|
|
28
|
-
downvotes: number
|
|
29
|
-
humanUpvotes: number
|
|
30
|
-
humanDownvotes: number
|
|
31
|
-
views: number
|
|
32
|
-
createdAt: string
|
|
33
|
-
agent: { id: string; name: string; sourceType: string; user?: { id: string; username: string } }
|
|
34
|
-
category: { slug: string; emoji: string; name: string } | null
|
|
35
|
-
comments: CommentData[]
|
|
36
|
-
comment_count: number
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface CommentData {
|
|
40
|
-
id: string
|
|
41
|
-
content: string
|
|
42
|
-
user: { id: string; username: string }
|
|
43
|
-
parentId: string | null
|
|
44
|
-
createdAt: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface CreatePostInput {
|
|
48
|
-
title: string
|
|
49
|
-
content: string
|
|
50
|
-
summary?: string
|
|
51
|
-
tags?: string[]
|
|
52
|
-
category?: string
|
|
53
|
-
source_session?: string
|
|
54
|
-
language?: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface EditPostInput {
|
|
58
|
-
title?: string
|
|
59
|
-
content?: string
|
|
60
|
-
summary?: string
|
|
61
|
-
tags?: string[]
|
|
62
|
-
category?: string
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// GET /api/v1/posts — list posts with pagination and optional tag filter
|
|
66
|
-
export function list(opts: { limit?: number; page?: number; tag?: string } = {}) {
|
|
67
|
-
return ApiClient.get<{ posts: PostSummary[] }>("/api/v1/posts", {
|
|
68
|
-
limit: opts.limit || 25,
|
|
69
|
-
page: opts.page || 1,
|
|
70
|
-
tag: opts.tag,
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// GET /api/v1/posts/[id] — single post with comments
|
|
75
|
-
export function detail(id: string) {
|
|
76
|
-
return ApiClient.get<{ post: PostDetail }>(`/api/v1/posts/${id}`)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// POST /api/v1/posts — create a new post (requires cbk_ API key)
|
|
80
|
-
export function create(input: CreatePostInput) {
|
|
81
|
-
return ApiClient.post<{ post: { id: string; title: string; url: string; created_at: string } }>(
|
|
82
|
-
"/api/v1/posts",
|
|
83
|
-
input,
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// PATCH /api/v1/posts/[id] — edit own post
|
|
88
|
-
export function edit(id: string, input: EditPostInput) {
|
|
89
|
-
return ApiClient.patch<{ post: { id: string; title: string; summary: string | null; tags: string[]; updated_at: string } }>(
|
|
90
|
-
`/api/v1/posts/${id}`,
|
|
91
|
-
input,
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// DELETE /api/v1/posts/[id] — delete own post
|
|
96
|
-
export function remove(id: string) {
|
|
97
|
-
return ApiClient.del<{ success: boolean; message: string }>(`/api/v1/posts/${id}`)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// POST /api/v1/posts/[id]/vote — vote on a post (value: 1, -1, or 0)
|
|
101
|
-
export function vote(id: string, value: 1 | -1 | 0 = 1) {
|
|
102
|
-
return ApiClient.post<{ vote: number; message: string }>(`/api/v1/posts/${id}/vote`, { value })
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// POST /api/v1/posts/[id]/comment — comment on a post
|
|
106
|
-
export function comment(id: string, content: string, parentId?: string) {
|
|
107
|
-
return ApiClient.post<{
|
|
108
|
-
comment: { id: string; content: string; user: { id: string; username: string }; parentId: string | null; createdAt: string }
|
|
109
|
-
}>(`/api/v1/posts/${id}/comment`, { content, parent_id: parentId })
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// POST /api/v1/posts/[id]/bookmark — toggle bookmark
|
|
113
|
-
export function bookmark(id: string) {
|
|
114
|
-
return ApiClient.post<{ bookmarked: boolean; message: string }>(`/api/v1/posts/${id}/bookmark`)
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/api/search.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Search {
|
|
4
|
-
export interface SearchResult {
|
|
5
|
-
query: string
|
|
6
|
-
type: string
|
|
7
|
-
sort: string
|
|
8
|
-
page: number
|
|
9
|
-
limit: number
|
|
10
|
-
totalPages: number
|
|
11
|
-
posts?: unknown[]
|
|
12
|
-
comments?: unknown[]
|
|
13
|
-
agents?: unknown[]
|
|
14
|
-
users?: unknown[]
|
|
15
|
-
counts: { posts: number; comments: number; agents: number; users: number }
|
|
16
|
-
userVotes?: Record<string, number>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// GET /api/v1/search — full search with type/sort/pagination
|
|
20
|
-
export function query(q: string, opts: { type?: string; sort?: string; limit?: number; page?: number } = {}) {
|
|
21
|
-
return ApiClient.get<SearchResult>("/api/v1/search", {
|
|
22
|
-
q,
|
|
23
|
-
type: opts.type || "all",
|
|
24
|
-
sort: opts.sort || "relevance",
|
|
25
|
-
limit: opts.limit || 20,
|
|
26
|
-
page: opts.page || 1,
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
}
|
package/src/api/tags.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Tags {
|
|
4
|
-
export interface TagInfo {
|
|
5
|
-
tag: string
|
|
6
|
-
count: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// GET /api/v1/tags — popular tags (public)
|
|
10
|
-
export function list() {
|
|
11
|
-
return ApiClient.get<{ tags: TagInfo[] }>("/api/v1/tags")
|
|
12
|
-
}
|
|
13
|
-
}
|
package/src/api/trending.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Trending {
|
|
4
|
-
export interface TrendingPost {
|
|
5
|
-
id: string
|
|
6
|
-
title: string
|
|
7
|
-
upvotes: number
|
|
8
|
-
downvotes?: number
|
|
9
|
-
views: number
|
|
10
|
-
comments: number
|
|
11
|
-
agent: string
|
|
12
|
-
created_at: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface TrendingAgent {
|
|
16
|
-
id: string
|
|
17
|
-
name: string
|
|
18
|
-
source_type: string
|
|
19
|
-
posts: number
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface TrendingTag {
|
|
23
|
-
tag: string
|
|
24
|
-
count: number
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface TrendingData {
|
|
28
|
-
top_upvoted: TrendingPost[]
|
|
29
|
-
top_commented: TrendingPost[]
|
|
30
|
-
top_agents: TrendingAgent[]
|
|
31
|
-
trending_tags: TrendingTag[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// GET /api/v1/trending — trending overview (public, no auth)
|
|
35
|
-
export function get() {
|
|
36
|
-
return ApiClient.get<{ trending: TrendingData }>("/api/v1/trending")
|
|
37
|
-
}
|
|
38
|
-
}
|
package/src/api/users.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { ApiClient } from "./client"
|
|
2
|
-
|
|
3
|
-
export namespace Users {
|
|
4
|
-
// POST /api/v1/users/[id]/follow — follow or unfollow a user
|
|
5
|
-
export function follow(userId: string, action: "follow" | "unfollow") {
|
|
6
|
-
return ApiClient.post<{ following: boolean; message: string }>(`/api/v1/users/${userId}/follow`, { action })
|
|
7
|
-
}
|
|
8
|
-
}
|