codeblog-app 2.2.6 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +9 -7
  2. package/src/ai/__tests__/chat.test.ts +11 -2
  3. package/src/ai/__tests__/compat.test.ts +46 -0
  4. package/src/ai/__tests__/home.ai-stream.integration.test.ts +77 -0
  5. package/src/ai/__tests__/provider-registry.test.ts +61 -0
  6. package/src/ai/__tests__/provider.test.ts +58 -18
  7. package/src/ai/__tests__/stream-events.test.ts +152 -0
  8. package/src/ai/chat.ts +200 -88
  9. package/src/ai/configure.ts +13 -4
  10. package/src/ai/models.ts +26 -0
  11. package/src/ai/provider-registry.ts +150 -0
  12. package/src/ai/provider.ts +99 -137
  13. package/src/ai/stream-events.ts +64 -0
  14. package/src/ai/tools.ts +10 -6
  15. package/src/ai/types.ts +105 -0
  16. package/src/auth/index.ts +3 -1
  17. package/src/auth/oauth.ts +17 -2
  18. package/src/cli/__tests__/commands.test.ts +6 -2
  19. package/src/cli/cmd/ai.ts +10 -0
  20. package/src/cli/cmd/setup.ts +275 -5
  21. package/src/cli/ui.ts +131 -24
  22. package/src/config/index.ts +38 -1
  23. package/src/index.ts +4 -1
  24. package/src/mcp/__tests__/client.test.ts +2 -2
  25. package/src/mcp/__tests__/e2e.ts +10 -6
  26. package/src/mcp/client.ts +33 -63
  27. package/src/storage/chat.ts +3 -1
  28. package/src/tui/__tests__/input-intent.test.ts +27 -0
  29. package/src/tui/__tests__/stream-assembler.test.ts +33 -0
  30. package/src/tui/ai-stream.ts +28 -0
  31. package/src/tui/app.tsx +27 -1
  32. package/src/tui/commands.ts +41 -7
  33. package/src/tui/context/theme.tsx +2 -1
  34. package/src/tui/input-intent.ts +26 -0
  35. package/src/tui/routes/home.tsx +590 -190
  36. package/src/tui/routes/setup.tsx +20 -8
  37. package/src/tui/stream-assembler.ts +49 -0
  38. package/src/util/log.ts +3 -1
  39. package/tsconfig.json +1 -1
@@ -10,12 +10,22 @@ const HC = {
10
10
  dim: "#999999",
11
11
  }
12
12
 
13
+ function resolveThemeDef(name: string) {
14
+ const fallback = THEMES.codeblog ?? Object.values(THEMES).find(Boolean)
15
+ if (!fallback) {
16
+ throw new Error("No themes available")
17
+ }
18
+ return THEMES[name] ?? fallback
19
+ }
20
+
13
21
  export function ThemeSetup() {
14
22
  const theme = useTheme()
15
23
  const modes = ["dark", "light"] as const
16
24
  const [step, setStep] = createSignal<"mode" | "theme">("mode")
17
25
  const [modeIdx, setModeIdx] = createSignal(0)
18
26
  const [themeIdx, setThemeIdx] = createSignal(0)
27
+ const getThemeName = (index: number) => THEME_NAMES[index] ?? "codeblog"
28
+ const getThemeColors = (name: string) => resolveThemeDef(name)[theme.mode]
19
29
 
20
30
  useKeyboard((evt) => {
21
31
  if (step() === "mode") {
@@ -30,7 +40,7 @@ export function ThemeSetup() {
30
40
  return
31
41
  }
32
42
  if (evt.name === "return") {
33
- theme.setMode(modes[modeIdx()])
43
+ theme.setMode(modes[modeIdx()] ?? "dark")
34
44
  setStep("theme")
35
45
  evt.preventDefault()
36
46
  return
@@ -41,14 +51,14 @@ export function ThemeSetup() {
41
51
  if (evt.name === "up" || evt.name === "k") {
42
52
  const next = (themeIdx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
43
53
  setThemeIdx(next)
44
- theme.set(THEME_NAMES[next])
54
+ theme.set(getThemeName(next))
45
55
  evt.preventDefault()
46
56
  return
47
57
  }
48
58
  if (evt.name === "down" || evt.name === "j") {
49
59
  const next = (themeIdx() + 1) % THEME_NAMES.length
50
60
  setThemeIdx(next)
51
- theme.set(THEME_NAMES[next])
61
+ theme.set(getThemeName(next))
52
62
  evt.preventDefault()
53
63
  return
54
64
  }
@@ -104,7 +114,7 @@ export function ThemeSetup() {
104
114
  </text>
105
115
  <box height={1} />
106
116
  {THEME_NAMES.map((name, i) => {
107
- const c = THEMES[name][theme.mode]
117
+ const c = getThemeColors(name)
108
118
  return (
109
119
  <box flexDirection="row" paddingLeft={2}>
110
120
  <text fg={themeIdx() === i ? c.primary : theme.colors.textMuted}>
@@ -136,22 +146,24 @@ export function ThemeSetup() {
136
146
 
137
147
  export function ThemePicker(props: { onDone: () => void }) {
138
148
  const theme = useTheme()
139
- const [idx, setIdx] = createSignal(THEME_NAMES.indexOf(theme.name))
149
+ const [idx, setIdx] = createSignal(Math.max(0, THEME_NAMES.indexOf(theme.name)))
140
150
  const [tab, setTab] = createSignal<"theme" | "mode">("theme")
151
+ const getThemeName = (index: number) => THEME_NAMES[index] ?? "codeblog"
152
+ const getThemeColors = (name: string) => resolveThemeDef(name)[theme.mode]
141
153
 
142
154
  useKeyboard((evt) => {
143
155
  if (tab() === "theme") {
144
156
  if (evt.name === "up" || evt.name === "k") {
145
157
  const next = (idx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
146
158
  setIdx(next)
147
- theme.set(THEME_NAMES[next])
159
+ theme.set(getThemeName(next))
148
160
  evt.preventDefault()
149
161
  return
150
162
  }
151
163
  if (evt.name === "down" || evt.name === "j") {
152
164
  const next = (idx() + 1) % THEME_NAMES.length
153
165
  setIdx(next)
154
- theme.set(THEME_NAMES[next])
166
+ theme.set(getThemeName(next))
155
167
  evt.preventDefault()
156
168
  return
157
169
  }
@@ -204,7 +216,7 @@ export function ThemePicker(props: { onDone: () => void }) {
204
216
  </text>
205
217
  <box height={1} />
206
218
  {THEME_NAMES.map((name, i) => {
207
- const c = THEMES[name][theme.mode]
219
+ const c = getThemeColors(name)
208
220
  return (
209
221
  <box flexDirection="row">
210
222
  <text fg={idx() === i ? c.primary : theme.colors.textMuted}>
@@ -0,0 +1,49 @@
1
+ export class TuiStreamAssembler {
2
+ private text = ""
3
+ private finished = false
4
+ private lastSeq = 0
5
+
6
+ reset() {
7
+ this.text = ""
8
+ this.finished = false
9
+ this.lastSeq = 0
10
+ }
11
+
12
+ getText() {
13
+ return this.text
14
+ }
15
+
16
+ pushDelta(delta: string, seq?: number) {
17
+ if (!delta || this.finished) return this.text
18
+ if (seq !== undefined && seq <= this.lastSeq) return this.text
19
+ if (seq !== undefined) this.lastSeq = seq
20
+
21
+ this.text += delta
22
+ return this.text
23
+ }
24
+
25
+ pushFinal(finalText: string) {
26
+ if (!finalText) {
27
+ this.finished = true
28
+ return this.text
29
+ }
30
+
31
+ if (!this.text) {
32
+ this.text = finalText
33
+ this.finished = true
34
+ return this.text
35
+ }
36
+
37
+ if (finalText.length >= this.text.length && finalText.includes(this.text)) {
38
+ this.text = finalText
39
+ this.finished = true
40
+ return this.text
41
+ }
42
+
43
+ if (finalText.length > this.text.length) {
44
+ this.text = finalText
45
+ }
46
+ this.finished = true
47
+ return this.text
48
+ }
49
+ }
package/src/util/log.ts CHANGED
@@ -52,9 +52,11 @@ export namespace Log {
52
52
  if (options.level) level = options.level
53
53
  cleanup(Global.Path.log)
54
54
  if (options.print) return
55
+ const isoNow = new Date().toISOString()
56
+ const timestampBase = isoNow.split(".")[0] ?? isoNow
55
57
  logpath = path.join(
56
58
  Global.Path.log,
57
- options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
59
+ options.dev ? "dev.log" : `${timestampBase.replace(/:/g, "")}.log`,
58
60
  )
59
61
  const logfile = Bun.file(logpath)
60
62
  await fs.truncate(logpath).catch(() => {})
package/tsconfig.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "extends": "@tsconfig/bun/tsconfig.json",
4
4
  "compilerOptions": {
5
5
  "jsx": "preserve",
6
- "jsxImportSource": "solid-js",
6
+ "jsxImportSource": "@opentui/solid",
7
7
  "paths": {
8
8
  "@/*": ["./src/*"]
9
9
  }