@hippox/codegenie 3.11.2026-rc.3 → 3.13.2026-alpha.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/README.md CHANGED
@@ -8,6 +8,13 @@
8
8
  npm install -g @hippox/codegenie
9
9
  ```
10
10
 
11
+ 预发布渠道:
12
+
13
+ ```bash
14
+ npm install -g @hippox/codegenie@beta
15
+ npm install -g @hippox/codegenie@alpha
16
+ ```
17
+
11
18
  安装完成后即可直接执行:
12
19
 
13
20
  ```bash
@@ -17,10 +24,11 @@ codegenie --version
17
24
  说明:
18
25
 
19
26
  - npm 安装会直接创建全局 `codegenie` 命令
27
+ - 如果 npm 因 optionalDependencies 配置跳过了平台包,安装脚本会自动补装当前平台包
20
28
  - npm 安装阶段会预热 runtime assets;Windows 还会额外隐藏预热一次默认启动路径,因此安装时间会略长,但首次可见启动会更快
21
29
  - 首次运行通常不再需要解包和安装依赖;首次进入某个工作区时,仍可能有少量项目级初始化
22
30
  - 首次运行不会再把二进制复制到 `~/.codegenie/bin`
23
- - npm 安装/升级会清理旧的 standalone 二进制和临时更新残留
31
+ - npm 安装/升级会清理旧安装残留和临时更新残留
24
32
  - `upgrade` 在 npm 安装场景下会提示或委托执行 npm 升级命令,不直接覆写 npm 全局目录
25
33
 
26
34
  ## 卸载
@@ -40,9 +48,9 @@ npm uninstall -g @hippox/codegenie
40
48
 
41
49
  ## 平台说明
42
50
 
43
- 当前 staged 平台包:`@hippox/codegenie-win32-x64`
51
+ 当前 staged 平台包:`@hippox/codegenie-darwin-arm64`, `@hippox/codegenie-darwin-x64`, `@hippox/codegenie-win32-x64`
44
52
 
45
- - `baseline` 兼容构建不会通过 npm 分发,老 CPU 请改用独立二进制发布件
53
+ - `baseline` 兼容构建不进入 npm 正式发布范围
46
54
 
47
55
  ## 发布说明
48
56
 
package/bin/codegenie.js CHANGED
@@ -1,27 +1,68 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { spawnSync } = require("child_process")
3
+ const { spawn } = require("child_process")
4
4
  const { buildLaunchEnv } = require("../lib/launch.js")
5
5
  const { resolveBinary } = require("../lib/resolve-binary.js")
6
6
  const { shouldPrintStartupHint, startupHintText } = require("../lib/startup-hint.js")
7
7
 
8
+ const SPINNER = ["\u2807", "\u280B", "\u2819", "\u2838", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
9
+
8
10
  function main() {
9
- if (shouldPrintStartupHint(process.argv.slice(2))) {
10
- console.error(startupHintText())
11
+ const argv = process.argv.slice(2)
12
+ const showHint = shouldPrintStartupHint(argv)
13
+
14
+ let spinnerInterval = null
15
+ let spinnerIdx = 0
16
+ if (showHint && process.stderr.isTTY) {
17
+ process.stderr.write("\x1b[?25l")
18
+ const draw = () => {
19
+ process.stderr.write(`\r \x1b[36m\x1b[1mCodeGenie\x1b[0m \x1b[2m${SPINNER[spinnerIdx++ % SPINNER.length]} Loading...\x1b[0m`)
20
+ }
21
+ draw()
22
+ spinnerInterval = setInterval(draw, 100)
23
+ } else if (showHint) {
24
+ process.stderr.write(startupHintText() + "\n")
11
25
  }
12
26
 
13
27
  const resolved = resolveBinary()
14
- const result = spawnSync(resolved.binaryPath, process.argv.slice(2), {
28
+
29
+ const child = spawn(resolved.binaryPath, argv, {
15
30
  stdio: "inherit",
16
- env: buildLaunchEnv(resolved.packageName, process.env),
31
+ env: buildLaunchEnv(resolved.packageName, resolved.platformDir, process.env),
32
+ })
33
+
34
+ child.on("spawn", () => {
35
+ if (spinnerInterval) {
36
+ clearInterval(spinnerInterval)
37
+ spinnerInterval = null
38
+ }
17
39
  })
18
40
 
19
- if (result.error) {
20
- console.error(`[codegenie] Failed to start ${resolved.packageName}: ${result.error.message}`)
41
+ child.on("error", (err) => {
42
+ if (spinnerInterval) {
43
+ clearInterval(spinnerInterval)
44
+ }
45
+ process.stderr.write("\r\x1b[K\x1b[?25h")
46
+ console.error(`[codegenie] Failed to start ${resolved.packageName}: ${err.message}`)
21
47
  process.exit(1)
22
- }
48
+ })
23
49
 
24
- process.exit(result.status ?? 1)
50
+ child.on("exit", (code, signal) => {
51
+ if (spinnerInterval) {
52
+ clearInterval(spinnerInterval)
53
+ }
54
+ if (signal) {
55
+ process.kill(process.pid, signal)
56
+ } else {
57
+ process.exit(code ?? 1)
58
+ }
59
+ })
60
+
61
+ function forwardSignal(sig) {
62
+ if (child.pid) child.kill(sig)
63
+ }
64
+ process.on("SIGINT", forwardSignal)
65
+ process.on("SIGTERM", forwardSignal)
25
66
  }
26
67
 
27
68
  main()
@@ -1,14 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { resolveBinary } = require("../lib/resolve-binary.js")
3
+ const { execFileSync } = require("child_process")
4
+ const { resolveBinary, resolvePackageName } = require("../lib/resolve-binary.js")
4
5
  const { prewarmBinary, warmStartupBinary } = require("../lib/prewarm.js")
6
+ const packageJson = require("../package.json")
7
+
8
+ function tryInstallPlatformPackage() {
9
+ let platformPackageName
10
+ try {
11
+ platformPackageName = resolvePackageName()
12
+ } catch {
13
+ return false
14
+ }
15
+
16
+ const platformVersion = packageJson.optionalDependencies && packageJson.optionalDependencies[platformPackageName]
17
+ const installSpec = platformVersion ? `${platformPackageName}@${platformVersion}` : platformPackageName
18
+ const npmExecutable = process.platform === "win32" ? "npm.cmd" : "npm"
19
+
20
+ console.log(`[codegenie] Platform package ${platformPackageName} not found, attempting to install...`)
21
+ try {
22
+ execFileSync(npmExecutable, ["install", "-g", installSpec], {
23
+ stdio: "inherit",
24
+ timeout: 120000,
25
+ })
26
+ return true
27
+ } catch (err) {
28
+ console.warn(`[codegenie] Failed to auto-install ${installSpec}: ${err && err.message ? err.message : String(err)}`)
29
+ return false
30
+ }
31
+ }
32
+
33
+ function runPostInstall() {
34
+ let resolved
35
+ try {
36
+ resolved = resolveBinary()
37
+ } catch (firstError) {
38
+ const attempted = tryInstallPlatformPackage()
39
+ if (!attempted) {
40
+ const message = firstError && firstError.message ? firstError.message : String(firstError)
41
+ console.warn("[codegenie] npm install completed, but platform binary verification will be deferred to first launch.")
42
+ console.warn(message)
43
+ return
44
+ }
45
+ try {
46
+ resolved = resolveBinary()
47
+ } catch (retryError) {
48
+ const message = retryError && retryError.message ? retryError.message : String(retryError)
49
+ console.warn("[codegenie] npm install completed, but platform binary verification will be deferred to first launch.")
50
+ console.warn(message)
51
+ return
52
+ }
53
+ }
5
54
 
6
- try {
7
- const resolved = resolveBinary()
8
55
  console.log("[codegenie] npm install complete. Global command \"codegenie\" is ready.")
9
56
  console.log(`[codegenie] Using platform package ${resolved.packageName}.`)
10
57
  console.log("[codegenie] Prewarming runtime assets during npm install...")
11
- const result = prewarmBinary(resolved.binaryPath)
58
+ let result = prewarmBinary(resolved.binaryPath, resolved.platformDir)
59
+ if (result.error || (typeof result.status === "number" && result.status !== 0)) {
60
+ const reason = result.error ? result.error.message : `exit code ${result.status}`
61
+ console.warn(`[codegenie] Runtime prewarm attempt 1 failed (${reason}), retrying...`)
62
+ result = prewarmBinary(resolved.binaryPath, resolved.platformDir)
63
+ }
12
64
  if (result.error) {
13
65
  console.warn(`[codegenie] Runtime prewarm failed: ${result.error.message}`)
14
66
  } else if (typeof result.status === "number" && result.status !== 0) {
@@ -17,17 +69,15 @@ try {
17
69
  console.log("[codegenie] Runtime assets are ready. First launch should be faster.")
18
70
  }
19
71
 
20
- if (process.platform === "win32") {
72
+ if (process.platform === "win32" && process.env.CODEGENIE_SKIP_WARM_STARTUP !== "true") {
21
73
  console.log("[codegenie] Finalizing hidden first-launch warmup...")
22
- const startupWarm = warmStartupBinary(resolved.binaryPath)
74
+ const startupWarm = warmStartupBinary(resolved.binaryPath, resolved.platformDir)
23
75
  if (startupWarm.error) {
24
76
  console.warn(`[codegenie] Hidden startup warmup failed: ${startupWarm.error.message}`)
25
77
  } else if (typeof startupWarm.status === "number" && startupWarm.status !== 0) {
26
78
  console.warn(`[codegenie] Hidden startup warmup exited with code ${startupWarm.status}. First visible launch may still be slower.`)
27
79
  }
28
80
  }
29
- } catch (error) {
30
- const message = error && error.message ? error.message : String(error)
31
- console.warn("[codegenie] npm install completed, but platform binary verification will be deferred to first launch.")
32
- console.warn(message)
33
81
  }
82
+
83
+ runPostInstall()
package/bin/uninstall.js CHANGED
@@ -6,12 +6,13 @@ try {
6
6
  const result = cleanupAfterNpmUninstall()
7
7
  if (result.removed) {
8
8
  console.log(`[codegenie] Removed runtime directory: ${result.rootDir}`)
9
- } else if (result.reason === "standalone-active") {
10
- console.log("[codegenie] Skip runtime cleanup because standalone installation is still active.")
11
9
  } else if (result.reason === "package-mismatch") {
12
- console.log("[codegenie] Skip runtime cleanup because install context belongs to another npm package.")
13
- } else if (result.reason === "standalone-binary-present") {
14
- console.log("[codegenie] Skip runtime cleanup because standalone binary is still present.")
10
+ console.log(`[codegenie] Skip runtime cleanup because install context belongs to another npm package.`)
11
+ } else if (result.reason === "missing") {
12
+ console.log("[codegenie] Runtime directory already removed.")
13
+ } else if (result.reason === "remove-failed") {
14
+ const hint = result.error && result.error.message ? ` (${result.error.message})` : ""
15
+ console.warn(`[codegenie] Failed to remove runtime directory${hint}. Remove manually: ${result.rootDir}`)
15
16
  }
16
17
  } catch (error) {
17
18
  const message = error && error.message ? error.message : String(error)
package/lib/cleanup.js CHANGED
@@ -36,6 +36,25 @@ function readInstallContext(homeDir = os.homedir()) {
36
36
  }
37
37
  }
38
38
 
39
+ function removeWithRetry(targetPath, maxRetries = 2) {
40
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
41
+ try {
42
+ fs.rmSync(targetPath, { recursive: true, force: true })
43
+ return { success: true, error: null }
44
+ } catch (err) {
45
+ const code = err && err.code
46
+ if ((code === "EBUSY" || code === "EPERM") && attempt < maxRetries) {
47
+ const delay = (attempt + 1) * 500
48
+ const end = Date.now() + delay
49
+ while (Date.now() < end) { /* busy wait */ }
50
+ continue
51
+ }
52
+ return { success: false, error: err }
53
+ }
54
+ }
55
+ return { success: false, error: null }
56
+ }
57
+
39
58
  function cleanupAfterNpmUninstall(options = {}) {
40
59
  const homeDir = options.homeDir || os.homedir()
41
60
  const expectedPackageName =
@@ -43,28 +62,29 @@ function cleanupAfterNpmUninstall(options = {}) {
43
62
  const paths = runtimePaths(homeDir)
44
63
  const context = readInstallContext(homeDir)
45
64
 
46
- if (context && context.installSource === "standalone") {
47
- return { removed: false, reason: "standalone-active", rootDir: paths.rootDir }
48
- }
49
-
50
65
  if (
51
66
  context &&
52
67
  expectedPackageName &&
53
68
  context.npmPackageName &&
69
+ context.installSource === "npm" &&
54
70
  !belongsToExpectedPackageFamily(context.npmPackageName, expectedPackageName)
55
71
  ) {
56
72
  return { removed: false, reason: "package-mismatch", rootDir: paths.rootDir }
57
73
  }
58
74
 
59
- if (!context && fs.existsSync(paths.binaryPath)) {
60
- return { removed: false, reason: "standalone-binary-present", rootDir: paths.rootDir }
61
- }
62
-
63
75
  if (!fs.existsSync(paths.rootDir)) {
64
76
  return { removed: false, reason: "missing", rootDir: paths.rootDir }
65
77
  }
66
78
 
67
- fs.rmSync(paths.rootDir, { recursive: true, force: true })
79
+ const result = removeWithRetry(paths.rootDir)
80
+ if (!result.success) {
81
+ return {
82
+ removed: false,
83
+ reason: "remove-failed",
84
+ rootDir: paths.rootDir,
85
+ error: result.error,
86
+ }
87
+ }
68
88
  return { removed: true, reason: "removed", rootDir: paths.rootDir }
69
89
  }
70
90
 
@@ -72,5 +92,6 @@ module.exports = {
72
92
  belongsToExpectedPackageFamily,
73
93
  cleanupAfterNpmUninstall,
74
94
  readInstallContext,
95
+ removeWithRetry,
75
96
  runtimePaths,
76
97
  }
package/lib/launch.js CHANGED
@@ -1,12 +1,16 @@
1
1
  const packageJson = require("../package.json")
2
2
 
3
- function buildLaunchEnv(resolvedPackageName, baseEnv = process.env) {
4
- return {
3
+ function buildLaunchEnv(resolvedPackageName, platformDir, baseEnv = process.env) {
4
+ const env = {
5
5
  ...baseEnv,
6
6
  CODEGENIE_INSTALL_SOURCE: "npm",
7
7
  CODEGENIE_NPM_PACKAGE: packageJson.name,
8
8
  CODEGENIE_NPM_PLATFORM_PACKAGE: resolvedPackageName,
9
9
  }
10
+ if (platformDir) {
11
+ env.CODEGENIE_NPM_PLATFORM_DIR = platformDir
12
+ }
13
+ return env
10
14
  }
11
15
 
12
16
  module.exports = {
package/lib/prewarm.js CHANGED
@@ -2,26 +2,30 @@ const fs = require("fs")
2
2
  const os = require("os")
3
3
  const { spawnSync } = require("child_process")
4
4
 
5
- function buildPrewarmEnv(baseEnv = process.env) {
6
- return {
5
+ function buildPrewarmEnv(platformDir, baseEnv = process.env) {
6
+ const env = {
7
7
  ...baseEnv,
8
8
  CODEGENIE_PREPARE_ONLY: "true",
9
9
  CODEGENIE_INSTALL_SOURCE: "npm",
10
10
  CODEGENIE_NPM_PACKAGE:
11
11
  baseEnv.CODEGENIE_NPM_PACKAGE || baseEnv.npm_package_name || "codegenie",
12
12
  }
13
+ if (platformDir) {
14
+ env.CODEGENIE_NPM_PLATFORM_DIR = platformDir
15
+ }
16
+ return env
13
17
  }
14
18
 
15
- function prewarmBinary(binaryPath, spawnImpl = spawnSync, baseEnv = process.env) {
19
+ function prewarmBinary(binaryPath, platformDir, spawnImpl = spawnSync, baseEnv = process.env) {
16
20
  return spawnImpl(binaryPath, [], {
17
21
  stdio: "ignore",
18
22
  timeout: 120000,
19
- env: buildPrewarmEnv(baseEnv),
23
+ env: buildPrewarmEnv(platformDir, baseEnv),
20
24
  })
21
25
  }
22
26
 
23
- function buildStartupWarmEnv(baseEnv = process.env) {
24
- return {
27
+ function buildStartupWarmEnv(platformDir, baseEnv = process.env) {
28
+ const env = {
25
29
  ...baseEnv,
26
30
  CODEGENIE_WARM_STARTUP: "true",
27
31
  CODEGENIE_WARM_STARTUP_TIMEOUT_MS: baseEnv.CODEGENIE_WARM_STARTUP_TIMEOUT_MS || "10000",
@@ -30,6 +34,10 @@ function buildStartupWarmEnv(baseEnv = process.env) {
30
34
  baseEnv.CODEGENIE_NPM_PACKAGE || baseEnv.npm_package_name || "codegenie",
31
35
  OPENCODE_DISABLE_AUTOUPDATE: "true",
32
36
  }
37
+ if (platformDir) {
38
+ env.CODEGENIE_NPM_PLATFORM_DIR = platformDir
39
+ }
40
+ return env
33
41
  }
34
42
 
35
43
  function resolveWarmupCwd(baseEnv = process.env, existsImpl = fs.existsSync) {
@@ -51,13 +59,13 @@ function resolveWarmupCwd(baseEnv = process.env, existsImpl = fs.existsSync) {
51
59
  return os.homedir()
52
60
  }
53
61
 
54
- function warmStartupBinary(binaryPath, spawnImpl = spawnSync, baseEnv = process.env, existsImpl = fs.existsSync) {
62
+ function warmStartupBinary(binaryPath, platformDir, spawnImpl = spawnSync, baseEnv = process.env, existsImpl = fs.existsSync) {
55
63
  return spawnImpl(binaryPath, [], {
56
64
  stdio: "ignore",
57
65
  timeout: 20000,
58
66
  windowsHide: true,
59
67
  cwd: resolveWarmupCwd(baseEnv, existsImpl),
60
- env: buildStartupWarmEnv(baseEnv),
68
+ env: buildStartupWarmEnv(platformDir, baseEnv),
61
69
  })
62
70
  }
63
71
 
@@ -38,17 +38,25 @@ function resolvePackageName() {
38
38
  throw new Error(`Unsupported npm platform: ${process.platform}/${process.arch}${libcSuffix}`)
39
39
  }
40
40
 
41
+ function resolvePlatformPackageDir(packageName) {
42
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
43
+ return path.dirname(packageJsonPath)
44
+ }
45
+
41
46
  function resolveBinary() {
42
47
  const packageName = resolvePackageName()
43
- let packageJsonPath = ""
48
+ let platformDir = ""
44
49
  try {
45
- packageJsonPath = require.resolve(`${packageName}/package.json`)
50
+ platformDir = resolvePlatformPackageDir(packageName)
46
51
  } catch (error) {
47
- const metaPackageName = process.env.npm_package_name || "codegenie"
52
+ const metaPackageName = (() => {
53
+ try { return require("../package.json").name } catch { return null }
54
+ })() || process.env.npm_package_name || "@hippox/codegenie"
48
55
  const hint = [
49
56
  `[codegenie] Failed to locate installed binary package for ${process.platform}/${process.arch}.`,
50
57
  `[codegenie] Expected package: ${packageName}`,
51
- `[codegenie] Reinstall with \`npm install -g ${metaPackageName}\` or use the standalone binary for baseline / unsupported CPUs.`,
58
+ `[codegenie] Reinstall with \`npm install -g ${metaPackageName}\`.`,
59
+ `[codegenie] If your npm disables optionalDependencies, install \`${packageName}\` manually and retry.`,
52
60
  ].join("\n")
53
61
  const wrapped = new Error(hint)
54
62
  wrapped.cause = error
@@ -56,7 +64,7 @@ function resolveBinary() {
56
64
  }
57
65
 
58
66
  const binaryPath = path.join(
59
- path.dirname(packageJsonPath),
67
+ platformDir,
60
68
  "bin",
61
69
  process.platform === "win32" ? "codegenie.exe" : "codegenie",
62
70
  )
@@ -67,11 +75,13 @@ function resolveBinary() {
67
75
  return {
68
76
  packageName,
69
77
  binaryPath,
78
+ platformDir,
70
79
  }
71
80
  }
72
81
 
73
82
  module.exports = {
74
83
  detectLibc,
75
84
  resolveBinary,
85
+ resolvePlatformPackageDir,
76
86
  resolvePackageName,
77
87
  }
@@ -1,19 +1,31 @@
1
+ const NON_INTERACTIVE_ARGS = new Set([
2
+ "--version", "-v", "--help", "-h",
3
+ "check-update", "upgrade", "rollback", "uninstall",
4
+ "login", "logout",
5
+ ])
6
+
7
+ function isInteractiveLaunch(argv = []) {
8
+ if (argv.length === 0) return true
9
+ if (argv[0] === "run" || argv[0] === "--") return true
10
+ return false
11
+ }
12
+
1
13
  function shouldPrintStartupHint(argv = [], options = {}) {
2
14
  const env = options.env || process.env
3
- const platform = options.platform || process.platform
4
15
  const isTTY = options.isTTY ?? process.stderr.isTTY
5
16
 
6
17
  if (!isTTY) return false
7
- if (platform !== "win32") return false
8
18
  if (env.CODEGENIE_PREPARE_ONLY === "true" || env.CODEGENIE_WARM_STARTUP === "true") return false
9
- return argv.length === 0
19
+ if (argv.length > 0 && NON_INTERACTIVE_ARGS.has(argv[0])) return false
20
+ return isInteractiveLaunch(argv)
10
21
  }
11
22
 
12
23
  function startupHintText() {
13
- return "[codegenie] Starting interface..."
24
+ return "\x1b[36m\x1b[1m CodeGenie\x1b[0m\x1b[2m Loading...\x1b[0m"
14
25
  }
15
26
 
16
27
  module.exports = {
28
+ isInteractiveLaunch,
17
29
  shouldPrintStartupHint,
18
30
  startupHintText,
19
31
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@hippox/codegenie",
3
- "version": "3.11.2026-rc.3",
3
+ "version": "3.13.2026-alpha.1",
4
4
  "private": false,
5
- "description": "HarmonyOS AI 开发 CLI,binary-first 发布并通过 npm 分发平台二进制",
5
+ "description": "HarmonyOS AI 开发 CLI,通过 npm 分发平台二进制",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
8
  "postinstall": "node ./bin/postinstall.js",
@@ -26,7 +26,9 @@
26
26
  "ai"
27
27
  ],
28
28
  "optionalDependencies": {
29
- "@hippox/codegenie-win32-x64": "3.11.2026-rc.3"
29
+ "@hippox/codegenie-darwin-arm64": "3.13.2026-alpha.1",
30
+ "@hippox/codegenie-darwin-x64": "3.13.2026-alpha.1",
31
+ "@hippox/codegenie-win32-x64": "3.13.2026-alpha.1"
30
32
  },
31
33
  "publishConfig": {
32
34
  "access": "public"
package/platform-map.json CHANGED
@@ -1,3 +1,5 @@
1
1
  {
2
+ "darwin-arm64": "@hippox/codegenie-darwin-arm64",
3
+ "darwin-x64": "@hippox/codegenie-darwin-x64",
2
4
  "win32-x64": "@hippox/codegenie-win32-x64"
3
5
  }