codeblog-app 2.1.0 → 2.1.2

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 (67) hide show
  1. package/package.json +8 -71
  2. package/drizzle/0000_init.sql +0 -34
  3. package/drizzle/meta/_journal.json +0 -13
  4. package/drizzle.config.ts +0 -10
  5. package/src/ai/__tests__/chat.test.ts +0 -110
  6. package/src/ai/__tests__/provider.test.ts +0 -184
  7. package/src/ai/__tests__/tools.test.ts +0 -90
  8. package/src/ai/chat.ts +0 -169
  9. package/src/ai/configure.ts +0 -134
  10. package/src/ai/provider.ts +0 -238
  11. package/src/ai/tools.ts +0 -336
  12. package/src/auth/index.ts +0 -47
  13. package/src/auth/oauth.ts +0 -94
  14. package/src/cli/__tests__/commands.test.ts +0 -225
  15. package/src/cli/cmd/agent.ts +0 -102
  16. package/src/cli/cmd/chat.ts +0 -190
  17. package/src/cli/cmd/comment.ts +0 -70
  18. package/src/cli/cmd/config.ts +0 -153
  19. package/src/cli/cmd/feed.ts +0 -57
  20. package/src/cli/cmd/forum.ts +0 -123
  21. package/src/cli/cmd/login.ts +0 -45
  22. package/src/cli/cmd/logout.ts +0 -12
  23. package/src/cli/cmd/me.ts +0 -202
  24. package/src/cli/cmd/post.ts +0 -29
  25. package/src/cli/cmd/publish.ts +0 -70
  26. package/src/cli/cmd/scan.ts +0 -80
  27. package/src/cli/cmd/search.ts +0 -40
  28. package/src/cli/cmd/setup.ts +0 -273
  29. package/src/cli/cmd/tui.ts +0 -20
  30. package/src/cli/cmd/update.ts +0 -78
  31. package/src/cli/cmd/vote.ts +0 -50
  32. package/src/cli/cmd/whoami.ts +0 -21
  33. package/src/cli/ui.ts +0 -195
  34. package/src/config/index.ts +0 -54
  35. package/src/flag/index.ts +0 -23
  36. package/src/global/index.ts +0 -38
  37. package/src/id/index.ts +0 -20
  38. package/src/index.ts +0 -197
  39. package/src/mcp/__tests__/client.test.ts +0 -149
  40. package/src/mcp/__tests__/e2e.ts +0 -327
  41. package/src/mcp/__tests__/integration.ts +0 -148
  42. package/src/mcp/client.ts +0 -148
  43. package/src/server/index.ts +0 -48
  44. package/src/storage/chat.ts +0 -92
  45. package/src/storage/db.ts +0 -85
  46. package/src/storage/schema.sql.ts +0 -39
  47. package/src/storage/schema.ts +0 -1
  48. package/src/tui/app.tsx +0 -163
  49. package/src/tui/commands.ts +0 -187
  50. package/src/tui/context/exit.tsx +0 -15
  51. package/src/tui/context/helper.tsx +0 -25
  52. package/src/tui/context/route.tsx +0 -24
  53. package/src/tui/context/theme.tsx +0 -470
  54. package/src/tui/routes/home.tsx +0 -508
  55. package/src/tui/routes/model.tsx +0 -209
  56. package/src/tui/routes/notifications.tsx +0 -85
  57. package/src/tui/routes/post.tsx +0 -108
  58. package/src/tui/routes/search.tsx +0 -104
  59. package/src/tui/routes/setup.tsx +0 -255
  60. package/src/tui/routes/trending.tsx +0 -107
  61. package/src/util/__tests__/context.test.ts +0 -31
  62. package/src/util/__tests__/lazy.test.ts +0 -37
  63. package/src/util/context.ts +0 -23
  64. package/src/util/error.ts +0 -46
  65. package/src/util/lazy.ts +0 -18
  66. package/src/util/log.ts +0 -142
  67. package/tsconfig.json +0 -11
@@ -1,209 +0,0 @@
1
- import { createSignal, createMemo, onMount, Show, For } from "solid-js"
2
- import { useKeyboard, usePaste } from "@opentui/solid"
3
- import { useTheme } from "../context/theme"
4
-
5
- interface ModelItem {
6
- id: string
7
- name: string
8
- provider: string
9
- }
10
-
11
- export function ModelPicker(props: { onDone: (model?: string) => void }) {
12
- const theme = useTheme()
13
- const [models, setModels] = createSignal<ModelItem[]>([])
14
- const [idx, setIdx] = createSignal(0)
15
- const [current, setCurrent] = createSignal("")
16
- const [loading, setLoading] = createSignal(true)
17
- const [filter, setFilter] = createSignal("")
18
- const [status, setStatus] = createSignal("")
19
-
20
- // Visible height for scrolling
21
- const maxVisible = 15
22
-
23
- const filtered = createMemo(() => {
24
- const q = filter().toLowerCase()
25
- if (!q) return models()
26
- return models().filter((m) => m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q))
27
- })
28
-
29
- // Scroll offset
30
- const scrollTop = createMemo(() => {
31
- const i = idx()
32
- const len = filtered().length
33
- if (len <= maxVisible) return 0
34
- if (i < maxVisible - 2) return 0
35
- if (i > len - 3) return Math.max(0, len - maxVisible)
36
- return i - maxVisible + 3
37
- })
38
-
39
- const visible = createMemo(() => filtered().slice(scrollTop(), scrollTop() + maxVisible))
40
-
41
- onMount(async () => {
42
- try {
43
- const { AIProvider } = await import("../../ai/provider")
44
- const { Config } = await import("../../config")
45
- const cfg = await Config.load()
46
- setCurrent(cfg.model || AIProvider.DEFAULT_MODEL)
47
-
48
- setStatus("Fetching models from API...")
49
- const dynamic = await AIProvider.fetchAllModels()
50
- if (dynamic.length > 0) {
51
- setModels(dynamic.map((m) => ({ id: m.id, name: m.name, provider: m.providerID })))
52
- const curIdx = dynamic.findIndex((m) => m.id === (cfg.model || AIProvider.DEFAULT_MODEL))
53
- if (curIdx >= 0) setIdx(curIdx)
54
- setStatus(`${dynamic.length} models loaded`)
55
- } else {
56
- // Fallback to builtin
57
- const all = await AIProvider.available()
58
- const items = all.filter((m) => m.hasKey).map((m) => ({
59
- id: m.model.id,
60
- name: m.model.name,
61
- provider: m.model.providerID,
62
- }))
63
- setModels(items)
64
- setStatus(`${items.length} models (builtin)`)
65
- }
66
- } catch (err) {
67
- setStatus(`Error: ${err instanceof Error ? err.message : String(err)}`)
68
- }
69
- setLoading(false)
70
- })
71
-
72
- usePaste((evt) => {
73
- const text = evt.text.replace(/[\n\r]/g, "").trim()
74
- if (text) {
75
- setFilter((s) => s + text)
76
- setIdx(0)
77
- evt.preventDefault()
78
- }
79
- })
80
-
81
- useKeyboard((evt) => {
82
- if (evt.name === "up") {
83
- setIdx((i) => (i - 1 + filtered().length) % filtered().length)
84
- evt.preventDefault()
85
- return
86
- }
87
- if (evt.name === "down") {
88
- setIdx((i) => (i + 1) % filtered().length)
89
- evt.preventDefault()
90
- return
91
- }
92
- if (evt.name === "return") {
93
- const m = filtered()[idx()]
94
- if (m) save(m.id)
95
- evt.preventDefault()
96
- return
97
- }
98
- if (evt.name === "escape") {
99
- if (filter()) { setFilter(""); setIdx(0) }
100
- else props.onDone()
101
- evt.preventDefault()
102
- return
103
- }
104
- if (evt.name === "backspace") {
105
- setFilter((s) => s.slice(0, -1))
106
- setIdx(0)
107
- evt.preventDefault()
108
- return
109
- }
110
- if (evt.sequence && evt.sequence.length >= 1 && !evt.ctrl && !evt.meta) {
111
- const clean = evt.sequence.replace(/[\x00-\x1f\x7f]/g, "")
112
- if (clean) {
113
- setFilter((s) => s + clean)
114
- setIdx(0)
115
- evt.preventDefault()
116
- return
117
- }
118
- }
119
- if (evt.name === "space") {
120
- setFilter((s) => s + " ")
121
- evt.preventDefault()
122
- return
123
- }
124
- })
125
-
126
- async function save(id: string) {
127
- try {
128
- const { Config } = await import("../../config")
129
- await Config.save({ model: id })
130
- props.onDone(id)
131
- } catch {
132
- props.onDone()
133
- }
134
- }
135
-
136
- return (
137
- <box flexDirection="column" flexGrow={1} paddingLeft={2} paddingRight={2} paddingTop={1}>
138
- <box flexDirection="row" flexShrink={0}>
139
- <text fg={theme.colors.primary}>
140
- <span style={{ bold: true }}>Select Model</span>
141
- </text>
142
- <box flexGrow={1} />
143
- <text fg={theme.colors.textMuted}>
144
- ↑↓ select · Enter confirm · Type to filter · Esc back
145
- </text>
146
- </box>
147
-
148
- <box flexShrink={0} flexDirection="row" paddingTop={1}>
149
- <text fg={theme.colors.textMuted}>Current: </text>
150
- <text fg={theme.colors.success}>{current()}</text>
151
- <text fg={theme.colors.textMuted}>{" · " + status()}</text>
152
- </box>
153
-
154
- {/* Filter input */}
155
- <box flexShrink={0} paddingTop={1} flexDirection="row">
156
- <text fg={theme.colors.primary}><span style={{ bold: true }}>{"❯ "}</span></text>
157
- <text fg={theme.colors.input}>{filter()}</text>
158
- <text fg={theme.colors.cursor}>{"█"}</text>
159
- <Show when={!filter()}>
160
- <text fg={theme.colors.textMuted}> type to filter...</text>
161
- </Show>
162
- </box>
163
-
164
- <box height={1} />
165
-
166
- <Show when={loading()}>
167
- <text fg={theme.colors.textMuted}>Fetching models from API...</text>
168
- </Show>
169
-
170
- <Show when={!loading()}>
171
- <Show when={filtered().length === 0}>
172
- <text fg={theme.colors.warning}>No models match "{filter()}"</text>
173
- </Show>
174
- <Show when={filtered().length > 0}>
175
- <box flexDirection="column" flexShrink={0}>
176
- <For each={visible()}>
177
- {(m, i) => {
178
- const selected = () => scrollTop() + i() === idx()
179
- return (
180
- <box flexDirection="row" backgroundColor={selected() ? theme.colors.primary : undefined}>
181
- <text fg={selected() ? "#ffffff" : theme.colors.textMuted}>
182
- {selected() ? "❯ " : " "}
183
- </text>
184
- <text fg={selected() ? "#ffffff" : theme.colors.text}>
185
- <span style={{ bold: selected() }}>
186
- {m.id.length > 40 ? m.id.slice(0, 40) + "…" : m.id.padEnd(42)}
187
- </span>
188
- </text>
189
- <text fg={selected() ? "#ffffff" : theme.colors.textMuted}>
190
- {" " + m.provider}
191
- </text>
192
- {m.id === current() ? (
193
- <text fg={selected() ? "#ffffff" : theme.colors.success}>{" ← current"}</text>
194
- ) : null}
195
- </box>
196
- )
197
- }}
198
- </For>
199
- </box>
200
- <Show when={filtered().length > maxVisible}>
201
- <text fg={theme.colors.textMuted}>
202
- {" ↑↓ " + filtered().length + " models total · showing " + (scrollTop() + 1) + "-" + Math.min(scrollTop() + maxVisible, filtered().length)}
203
- </text>
204
- </Show>
205
- </Show>
206
- </Show>
207
- </box>
208
- )
209
- }
@@ -1,85 +0,0 @@
1
- import { createSignal, onMount, For, Show } from "solid-js"
2
- import { useKeyboard } from "@opentui/solid"
3
-
4
- export function Notifications() {
5
- const [items, setItems] = createSignal<any[]>([])
6
- const [loading, setLoading] = createSignal(true)
7
- const [selected, setSelected] = createSignal(0)
8
-
9
- onMount(async () => {
10
- try {
11
- const { Notifications } = await import("../../api/notifications")
12
- const result = await Notifications.list()
13
- setItems(result.notifications || result || [])
14
- } catch {
15
- setItems([])
16
- }
17
- setLoading(false)
18
- })
19
-
20
- useKeyboard((evt) => {
21
- const n = items()
22
- if (evt.name === "up" || evt.name === "k") {
23
- setSelected((s) => Math.max(0, s - 1))
24
- evt.preventDefault()
25
- }
26
- if (evt.name === "down" || evt.name === "j") {
27
- setSelected((s) => Math.min(n.length - 1, s + 1))
28
- evt.preventDefault()
29
- }
30
- })
31
-
32
- return (
33
- <box flexDirection="column" flexGrow={1}>
34
- <box paddingLeft={2} paddingRight={2} paddingTop={1} flexShrink={0} flexDirection="row" gap={1}>
35
- <text fg="#f48225">
36
- <span style={{ bold: true }}>Notifications</span>
37
- </text>
38
- <text fg="#6a737c">{`(${items().length})`}</text>
39
- <box flexGrow={1} />
40
- <text fg="#6a737c">esc:back j/k:navigate</text>
41
- </box>
42
-
43
- <Show when={loading()}>
44
- <box paddingLeft={4} paddingTop={1}>
45
- <text fg="#6a737c">Loading notifications...</text>
46
- </box>
47
- </Show>
48
-
49
- <Show when={!loading() && items().length === 0}>
50
- <box paddingLeft={4} paddingTop={1}>
51
- <text fg="#6a737c">No notifications.</text>
52
- </box>
53
- </Show>
54
-
55
- <box flexDirection="column" paddingTop={1} flexGrow={1}>
56
- <For each={items()}>
57
- {(item: any, i) => {
58
- const isSelected = () => i() === selected()
59
- const isRead = item.read || item.is_read
60
- return (
61
- <box flexDirection="row" paddingLeft={2} paddingRight={2}>
62
- <box width={3}>
63
- <text fg={isRead ? "#6a737c" : "#0074cc"}>
64
- {isRead ? " " : "● "}
65
- </text>
66
- </box>
67
- <box flexDirection="column" flexGrow={1}>
68
- <text fg={isSelected() ? "#0074cc" : "#e7e9eb"}>
69
- <span style={{ bold: isSelected() }}>
70
- {item.message || item.content || item.type || "Notification"}
71
- </span>
72
- </text>
73
- <box flexDirection="row" gap={1}>
74
- <text fg="#838c95">{item.from_user || item.actor || ""}</text>
75
- <text fg="#6a737c">{item.created_at || ""}</text>
76
- </box>
77
- </box>
78
- </box>
79
- )
80
- }}
81
- </For>
82
- </box>
83
- </box>
84
- )
85
- }
@@ -1,108 +0,0 @@
1
- import { createSignal, onMount, For, Show } from "solid-js"
2
- import { useKeyboard } from "@opentui/solid"
3
- import { useRoute } from "../context/route"
4
-
5
- export function Post() {
6
- const route = useRoute()
7
- const postId = () => route.data.type === "post" ? route.data.postId : ""
8
- const [post, setPost] = createSignal<any>(null)
9
- const [comments, setComments] = createSignal<any[]>([])
10
- const [loading, setLoading] = createSignal(true)
11
- const [scroll, setScroll] = createSignal(0)
12
-
13
- onMount(async () => {
14
- try {
15
- const { Posts } = await import("../../api/posts")
16
- const result = await Posts.detail(postId())
17
- const p = result.post || result
18
- setPost(p)
19
- setComments(p.comments || [])
20
- } catch {
21
- setPost(null)
22
- }
23
- setLoading(false)
24
- })
25
-
26
- useKeyboard((evt) => {
27
- if (evt.name === "up" || evt.name === "k") {
28
- setScroll((s) => Math.max(0, s - 1))
29
- evt.preventDefault()
30
- }
31
- if (evt.name === "down" || evt.name === "j") {
32
- setScroll((s) => s + 1)
33
- evt.preventDefault()
34
- }
35
- })
36
-
37
- return (
38
- <box flexDirection="column" flexGrow={1}>
39
- <Show when={loading()}>
40
- <box paddingLeft={4} paddingTop={2}>
41
- <text fg="#6a737c">Loading post...</text>
42
- </box>
43
- </Show>
44
-
45
- <Show when={!loading() && !post()}>
46
- <box paddingLeft={4} paddingTop={2}>
47
- <text fg="#d73a49">Post not found.</text>
48
- </box>
49
- </Show>
50
-
51
- <Show when={!loading() && post()}>
52
- {/* Title */}
53
- <box paddingLeft={2} paddingRight={2} paddingTop={1} flexShrink={0}>
54
- <text fg="#e7e9eb">
55
- <span style={{ bold: true }}>{post()?.title}</span>
56
- </text>
57
- </box>
58
-
59
- {/* Meta */}
60
- <box paddingLeft={2} paddingTop={0} flexShrink={0} flexDirection="row" gap={2}>
61
- <text fg="#48a868">{`▲${(post()?.upvotes ?? 0) - (post()?.downvotes ?? 0)}`}</text>
62
- <text fg="#6a737c">{`💬${post()?.comment_count ?? 0} 👁${post()?.views ?? 0}`}</text>
63
- <text fg="#838c95">{`by ${post()?.agent?.name || "anon"}`}</text>
64
- </box>
65
-
66
- {/* Tags */}
67
- <Show when={(post()?.tags || []).length > 0}>
68
- <box paddingLeft={2} paddingTop={0} flexShrink={0} flexDirection="row" gap={1}>
69
- <For each={post()?.tags || []}>
70
- {(tag: string) => <text fg="#39739d">{`#${tag}`}</text>}
71
- </For>
72
- </box>
73
- </Show>
74
-
75
- {/* Content */}
76
- <box paddingLeft={2} paddingRight={2} paddingTop={1} flexGrow={1}>
77
- <text fg="#c9d1d9">{post()?.content?.slice(0, 2000) || post()?.summary || ""}</text>
78
- </box>
79
-
80
- {/* Comments */}
81
- <Show when={comments().length > 0}>
82
- <box paddingLeft={2} paddingTop={1} flexShrink={0}>
83
- <text fg="#f48225">
84
- <span style={{ bold: true }}>{`Comments (${comments().length})`}</span>
85
- </text>
86
- </box>
87
- <box flexDirection="column" paddingLeft={2} paddingRight={2}>
88
- <For each={comments()}>
89
- {(comment: any) => (
90
- <box flexDirection="column" paddingTop={1}>
91
- <box flexDirection="row" gap={1}>
92
- <text fg="#0074cc">
93
- <span style={{ bold: true }}>{comment.user?.username || comment.agent || "anon"}</span>
94
- </text>
95
- <text fg="#6a737c">{comment.createdAt || comment.created_at || ""}</text>
96
- </box>
97
- <box paddingLeft={2}>
98
- <text fg="#c9d1d9">{comment.content || comment.body || ""}</text>
99
- </box>
100
- </box>
101
- )}
102
- </For>
103
- </box>
104
- </Show>
105
- </Show>
106
- </box>
107
- )
108
- }
@@ -1,104 +0,0 @@
1
- import { createSignal, For, Show } from "solid-js"
2
- import { useKeyboard } from "@opentui/solid"
3
- import { useRoute } from "../context/route"
4
-
5
- export function Search() {
6
- const route = useRoute()
7
- const [query, setQuery] = createSignal(route.data.type === "search" ? route.data.query : "")
8
- const [results, setResults] = createSignal<any[]>([])
9
- const [loading, setLoading] = createSignal(false)
10
- const [searched, setSearched] = createSignal(false)
11
-
12
- async function doSearch(q: string) {
13
- if (!q.trim()) return
14
- setLoading(true)
15
- setSearched(true)
16
- try {
17
- const { Search } = await import("../../api/search")
18
- const result = await Search.query(q.trim())
19
- setResults(result.results || result.posts || [])
20
- } catch {
21
- setResults([])
22
- }
23
- setLoading(false)
24
- }
25
-
26
- useKeyboard((evt) => {
27
- if (evt.name === "return" && !evt.shift) {
28
- doSearch(query())
29
- evt.preventDefault()
30
- return
31
- }
32
- if (evt.name === "backspace") {
33
- setQuery((s) => s.slice(0, -1))
34
- evt.preventDefault()
35
- return
36
- }
37
- if (evt.sequence && evt.sequence.length === 1 && !evt.ctrl && !evt.meta) {
38
- setQuery((s) => s + evt.sequence)
39
- evt.preventDefault()
40
- return
41
- }
42
- if (evt.name === "space") {
43
- setQuery((s) => s + " ")
44
- evt.preventDefault()
45
- return
46
- }
47
- })
48
-
49
- return (
50
- <box flexDirection="column" flexGrow={1}>
51
- <box paddingLeft={2} paddingRight={2} paddingTop={1} flexShrink={0} flexDirection="row" gap={1}>
52
- <text fg="#f48225">
53
- <span style={{ bold: true }}>Search</span>
54
- </text>
55
- <box flexGrow={1} />
56
- <text fg="#6a737c">esc:back</text>
57
- </box>
58
-
59
- {/* Search input */}
60
- <box paddingLeft={2} paddingRight={2} paddingTop={1} flexShrink={0} flexDirection="row">
61
- <text fg="#0074cc">
62
- <span style={{ bold: true }}>{"🔍 "}</span>
63
- </text>
64
- <text fg="#e7e9eb">{query()}</text>
65
- <text fg="#6a737c">{"█"}</text>
66
- </box>
67
-
68
- <Show when={loading()}>
69
- <box paddingLeft={4} paddingTop={1}>
70
- <text fg="#6a737c">Searching...</text>
71
- </box>
72
- </Show>
73
-
74
- <Show when={!loading() && searched() && results().length === 0}>
75
- <box paddingLeft={4} paddingTop={1}>
76
- <text fg="#6a737c">No results found.</text>
77
- </box>
78
- </Show>
79
-
80
- <box flexDirection="column" paddingTop={1} flexGrow={1}>
81
- <For each={results()}>
82
- {(item: any) => (
83
- <box flexDirection="row" paddingLeft={2} paddingRight={2}>
84
- <box width={6} justifyContent="flex-end" marginRight={1}>
85
- <text fg="#48a868">{`▲${item.score ?? item.upvotes ?? 0}`}</text>
86
- </box>
87
- <box flexDirection="column" flexGrow={1}>
88
- <text fg="#e7e9eb">
89
- <span style={{ bold: true }}>{item.title}</span>
90
- </text>
91
- <box flexDirection="row" gap={1}>
92
- <text fg="#6a737c">{`💬${item.comment_count ?? 0}`}</text>
93
- <For each={(item.tags || []).slice(0, 3)}>
94
- {(tag: string) => <text fg="#39739d">{`#${tag}`}</text>}
95
- </For>
96
- </box>
97
- </box>
98
- </box>
99
- )}
100
- </For>
101
- </box>
102
- </box>
103
- )
104
- }