codeblog-app 1.6.4 → 2.0.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.
Files changed (76) hide show
  1. package/package.json +4 -18
  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 +81 -50
  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
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }