codeblog-app 2.3.4 → 2.3.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "codeblog-app",
4
- "version": "2.3.4",
4
+ "version": "2.3.5",
5
5
  "description": "CLI client for CodeBlog — the forum where AI writes the posts",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -58,11 +58,11 @@
58
58
  "typescript": "5.8.2"
59
59
  },
60
60
  "optionalDependencies": {
61
- "codeblog-app-darwin-arm64": "2.3.4",
62
- "codeblog-app-darwin-x64": "2.3.4",
63
- "codeblog-app-linux-arm64": "2.3.4",
64
- "codeblog-app-linux-x64": "2.3.4",
65
- "codeblog-app-windows-x64": "2.3.4"
61
+ "codeblog-app-darwin-arm64": "2.3.5",
62
+ "codeblog-app-darwin-x64": "2.3.5",
63
+ "codeblog-app-linux-arm64": "2.3.5",
64
+ "codeblog-app-linux-x64": "2.3.5",
65
+ "codeblog-app-windows-x64": "2.3.5"
66
66
  },
67
67
  "dependencies": {
68
68
  "@ai-sdk/anthropic": "^3.0.44",
@@ -73,7 +73,7 @@
73
73
  "@opentui/core": "^0.1.79",
74
74
  "@opentui/solid": "^0.1.79",
75
75
  "ai": "^6.0.86",
76
- "codeblog-mcp": "2.2.2",
76
+ "codeblog-mcp": "2.3.0",
77
77
  "drizzle-orm": "1.0.0-beta.12-a5629fb",
78
78
  "fuzzysort": "^3.1.0",
79
79
  "hono": "4.10.7",
@@ -0,0 +1,59 @@
1
+ import { performUpdate } from "./perform-update"
2
+ import { UI } from "./ui"
3
+
4
+ /**
5
+ * Checks for a newer CLI version on startup and auto-updates if available.
6
+ * Equivalent to running `codeblog update` automatically.
7
+ * On any failure, silently continues — never blocks normal usage.
8
+ */
9
+ export async function checkAndAutoUpdate(): Promise<void> {
10
+ try {
11
+ // Skip if disabled via env
12
+ if (process.env.CODEBLOG_NO_AUTO_UPDATE === "1") return
13
+
14
+ // Skip if running the `update` command (avoid double-update)
15
+ const cmd = process.argv[2]
16
+ if (cmd === "update") return
17
+
18
+ // Fetch latest version from npm registry
19
+ const controller = new AbortController()
20
+ const timeout = setTimeout(() => controller.abort(), 10_000)
21
+ let res: Response
22
+ try {
23
+ res = await fetch("https://registry.npmjs.org/codeblog-app/latest", { signal: controller.signal })
24
+ } catch (e: any) {
25
+ clearTimeout(timeout)
26
+ if (e.name === "AbortError") {
27
+ UI.error("Version check timed out (10s). Please check your network and try again.")
28
+ } else {
29
+ UI.error(`Failed to check for updates: ${e.message}`)
30
+ }
31
+ return
32
+ }
33
+ clearTimeout(timeout)
34
+ if (!res.ok) return
35
+
36
+ const data = (await res.json()) as { version: string }
37
+ const latest = data.version
38
+ const pkg = await import("../../package.json")
39
+ const current = pkg.version
40
+
41
+ if (current === latest) return
42
+
43
+ // Run the same flow as `codeblog update`
44
+ UI.info(`Current version: v${current}`)
45
+ UI.info(`Updating v${current} → v${latest}...`)
46
+
47
+ await performUpdate(latest)
48
+ UI.success(`Updated to v${latest}!`)
49
+
50
+ // Re-exec: run the updated binary with the same arguments
51
+ const proc = Bun.spawn([process.execPath, ...process.argv.slice(1)], {
52
+ stdio: ["inherit", "inherit", "inherit"],
53
+ })
54
+ const code = await proc.exited
55
+ process.exit(code)
56
+ } catch (e) {
57
+ UI.error(e instanceof Error ? e.message : String(e))
58
+ }
59
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CommandModule } from "yargs"
2
2
  import { UI } from "../ui"
3
+ import { performUpdate } from "../perform-update"
3
4
 
4
5
  export const UpdateCommand: CommandModule = {
5
6
  command: "update",
@@ -49,91 +50,12 @@ export const UpdateCommand: CommandModule = {
49
50
 
50
51
  UI.info(`Updating v${current} → v${latest}...`)
51
52
 
52
- const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux"
53
- const arch = process.arch === "arm64" ? "arm64" : "x64"
54
- const platform = `${os}-${arch}`
55
- const pkg_name = `codeblog-app-${platform}`
56
- const url = `https://registry.npmjs.org/${pkg_name}/-/${pkg_name}-${latest}.tgz`
57
-
58
- const tmpdir = (await import("os")).tmpdir()
59
- const path = await import("path")
60
- const fs = await import("fs/promises")
61
- const tmp = path.join(tmpdir, `codeblog-update-${Date.now()}`)
62
- await fs.mkdir(tmp, { recursive: true })
63
-
64
- UI.info("Downloading...")
65
- const tgz = path.join(tmp, "pkg.tgz")
66
- const dlController = new AbortController()
67
- const dlTimeout = setTimeout(() => dlController.abort(), 60_000)
68
- let dlRes: Response
69
53
  try {
70
- dlRes = await fetch(url, { signal: dlController.signal })
71
- } catch (e: any) {
72
- clearTimeout(dlTimeout)
73
- await fs.rm(tmp, { recursive: true, force: true }).catch(() => {})
74
- if (e.name === "AbortError") {
75
- UI.error("Download timed out (60s). Please check your network and try again.")
76
- } else {
77
- UI.error(`Download failed: ${e.message}`)
78
- }
54
+ await performUpdate(latest)
55
+ UI.success(`Updated to v${latest}!`)
56
+ } catch (e) {
57
+ UI.error(e instanceof Error ? e.message : String(e))
79
58
  process.exitCode = 1
80
- return
81
- }
82
- clearTimeout(dlTimeout)
83
- if (!dlRes.ok) {
84
- UI.error(`Failed to download update for ${platform} (HTTP ${dlRes.status})`)
85
- await fs.rm(tmp, { recursive: true, force: true }).catch(() => {})
86
- process.exitCode = 1
87
- return
88
- }
89
-
90
- const arrayBuf = await dlRes.arrayBuffer()
91
- await fs.writeFile(tgz, Buffer.from(arrayBuf))
92
-
93
- UI.info("Extracting...")
94
- const proc = Bun.spawn(["tar", "-xzf", tgz, "-C", tmp], { stdout: "ignore", stderr: "ignore" })
95
- await proc.exited
96
-
97
- const bin = process.execPath
98
- const ext = os === "windows" ? ".exe" : ""
99
- const src = path.join(tmp, "package", "bin", `codeblog${ext}`)
100
-
101
- UI.info("Installing...")
102
- // On macOS/Linux, remove the running binary first to avoid ETXTBSY
103
- if (os !== "windows") {
104
- try {
105
- await fs.unlink(bin)
106
- } catch {
107
- // ignore if removal fails
108
- }
109
- }
110
- await fs.copyFile(src, bin)
111
- if (os !== "windows") {
112
- await fs.chmod(bin, 0o755)
113
59
  }
114
- if (os === "darwin") {
115
- await Bun.spawn(["codesign", "--remove-signature", bin], { stdout: "ignore", stderr: "ignore" }).exited
116
- const cs = Bun.spawn(["codesign", "--sign", "-", "--force", bin], { stdout: "ignore", stderr: "ignore" })
117
- if ((await cs.exited) !== 0) {
118
- await fs.rm(tmp, { recursive: true, force: true })
119
- UI.error("Update installed but macOS code signing failed. Please reinstall with install.sh.")
120
- process.exitCode = 1
121
- return
122
- }
123
- const verify = Bun.spawn(["codesign", "--verify", "--deep", "--strict", bin], {
124
- stdout: "ignore",
125
- stderr: "ignore",
126
- })
127
- if ((await verify.exited) !== 0) {
128
- await fs.rm(tmp, { recursive: true, force: true })
129
- UI.error("Update installed but signature verification failed. Please reinstall with install.sh.")
130
- process.exitCode = 1
131
- return
132
- }
133
- }
134
-
135
- await fs.rm(tmp, { recursive: true, force: true })
136
-
137
- UI.success(`Updated to v${latest}!`)
138
60
  },
139
61
  }
@@ -0,0 +1,68 @@
1
+ import path from "path"
2
+ import { tmpdir } from "os"
3
+ import { mkdir, writeFile, rm, unlink, copyFile, chmod } from "fs/promises"
4
+
5
+ /**
6
+ * Downloads and installs a specific version of the codeblog CLI binary.
7
+ * Shared between the `update` command and auto-update logic.
8
+ *
9
+ * Throws on failure so callers can handle errors as they see fit.
10
+ */
11
+ export async function performUpdate(latest: string): Promise<void> {
12
+ const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux"
13
+ const arch = process.arch === "arm64" ? "arm64" : "x64"
14
+ const platform = `${os}-${arch}`
15
+ const pkgName = `codeblog-app-${platform}`
16
+ const url = `https://registry.npmjs.org/${pkgName}/-/${pkgName}-${latest}.tgz`
17
+
18
+ const tmp = path.join(tmpdir(), `codeblog-update-${Date.now()}`)
19
+ await mkdir(tmp, { recursive: true })
20
+
21
+ try {
22
+ // Download
23
+ const tgz = path.join(tmp, "pkg.tgz")
24
+ const dlController = new AbortController()
25
+ const dlTimeout = setTimeout(() => dlController.abort(), 60_000)
26
+ let dlRes: Response
27
+ try {
28
+ dlRes = await fetch(url, { signal: dlController.signal })
29
+ } catch (e: any) {
30
+ clearTimeout(dlTimeout)
31
+ if (e.name === "AbortError") throw new Error("Download timed out (60s)")
32
+ throw new Error(`Download failed: ${e.message}`)
33
+ }
34
+ clearTimeout(dlTimeout)
35
+ if (!dlRes.ok) throw new Error(`Failed to download update for ${platform} (HTTP ${dlRes.status})`)
36
+
37
+ const arrayBuf = await dlRes.arrayBuffer()
38
+ await writeFile(tgz, Buffer.from(arrayBuf))
39
+
40
+ // Extract
41
+ const proc = Bun.spawn(["tar", "-xzf", tgz, "-C", tmp], { stdout: "ignore", stderr: "ignore" })
42
+ await proc.exited
43
+
44
+ // Install
45
+ const bin = process.execPath
46
+ const ext = os === "windows" ? ".exe" : ""
47
+ const src = path.join(tmp, "package", "bin", `codeblog${ext}`)
48
+
49
+ if (os !== "windows") {
50
+ try { await unlink(bin) } catch {}
51
+ }
52
+ await copyFile(src, bin)
53
+ if (os !== "windows") {
54
+ await chmod(bin, 0o755)
55
+ }
56
+
57
+ // macOS codesign
58
+ if (os === "darwin") {
59
+ await Bun.spawn(["codesign", "--remove-signature", bin], { stdout: "ignore", stderr: "ignore" }).exited
60
+ const cs = Bun.spawn(["codesign", "--sign", "-", "--force", bin], { stdout: "ignore", stderr: "ignore" })
61
+ if ((await cs.exited) !== 0) throw new Error("macOS code signing failed")
62
+ const verify = Bun.spawn(["codesign", "--verify", "--deep", "--strict", bin], { stdout: "ignore", stderr: "ignore" })
63
+ if ((await verify.exited) !== 0) throw new Error("macOS signature verification failed")
64
+ }
65
+ } finally {
66
+ await rm(tmp, { recursive: true, force: true }).catch(() => {})
67
+ }
68
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { UI } from "./cli/ui"
5
5
  import { EOL } from "os"
6
6
  import { McpBridge } from "./mcp/client"
7
7
  import { Auth } from "./auth"
8
+ import { checkAndAutoUpdate } from "./cli/auto-update"
8
9
 
9
10
  // Commands
10
11
  import { SetupCommand } from "./cli/cmd/setup"
@@ -161,6 +162,11 @@ const hasSubcommand = args.length > 0 && !args[0]!.startsWith("-")
161
162
  const isHelp = args.includes("--help") || args.includes("-h")
162
163
  const isVersion = args.includes("--version") || args.includes("-v")
163
164
 
165
+ // Auto-update check on startup
166
+ if (!isHelp && !isVersion) {
167
+ await checkAndAutoUpdate()
168
+ }
169
+
164
170
  if (!hasSubcommand && !isHelp && !isVersion) {
165
171
  await Log.init({ print: false })
166
172
  Log.Default.info("codeblog", { version: VERSION, args: [] })