@hippox/codegenie 3.11.2026-alpha.1 → 3.11.2026-alpha.3

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/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
31
  env: buildLaunchEnv(resolved.packageName, resolved.platformDir, process.env),
17
32
  })
18
33
 
19
- if (result.error) {
20
- console.error(`[codegenie] Failed to start ${resolved.packageName}: ${result.error.message}`)
34
+ child.on("spawn", () => {
35
+ if (spinnerInterval) {
36
+ clearInterval(spinnerInterval)
37
+ spinnerInterval = null
38
+ }
39
+ })
40
+
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()
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
  }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@hippox/codegenie",
3
- "version": "3.11.2026-alpha.1",
3
+ "version": "3.11.2026-alpha.3",
4
4
  "private": false,
5
5
  "description": "HarmonyOS AI 开发 CLI,binary-first 发布并通过 npm 分发平台二进制",
6
6
  "license": "MIT",
@@ -26,7 +26,7 @@
26
26
  "ai"
27
27
  ],
28
28
  "optionalDependencies": {
29
- "@hippox/codegenie-win32-x64": "3.11.2026-alpha.1"
29
+ "@hippox/codegenie-win32-x64": "3.11.2026-alpha.3"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"