@lark-codem/codem-cli 0.1.226-dev-trace-task.100102444781.0.ga44791f → 0.1.226-dev-p1-mac.100102447463.0.g9e29c9f
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/LICENSE +6 -6
- package/bin/codem-service.js +132 -132
- package/bin/codem.js +237 -237
- package/bin/platform-child-process.js +178 -178
- package/package.json +3 -8
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
© Lark Technologies Pte. Ltd. All rights reserved. Use is subject to the Legal Agreements outlined here:
|
|
2
|
-
|
|
3
|
-
Feishu User Terms of Service [https://www.feishu.cn/terms]
|
|
4
|
-
Feishu Privacy Policy [https://www.feishu.cn/privacy]
|
|
5
|
-
|
|
6
|
-
Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
1
|
+
© Lark Technologies Pte. Ltd. All rights reserved. Use is subject to the Legal Agreements outlined here:
|
|
2
|
+
|
|
3
|
+
Feishu User Terms of Service [https://www.feishu.cn/terms]
|
|
4
|
+
Feishu Privacy Policy [https://www.feishu.cn/privacy]
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
package/bin/codem-service.js
CHANGED
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
-
|
|
8
|
-
// Inlined from src/runtime-install-form.js#isRuntimeManagedInstall(保持逐字节
|
|
9
|
-
// 同语义,二者需同步改):主包不再发布 dist/,本入口必须自包含。
|
|
10
|
-
function canonicalPath(filePath) {
|
|
11
|
-
const resolved = path.resolve(filePath);
|
|
12
|
-
const realpath = realpathSync.native ?? realpathSync;
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
return realpath(resolved);
|
|
16
|
-
} catch {
|
|
17
|
-
return resolved;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isRuntimeManagedInstall({ cliRoot, codemHome }) {
|
|
22
|
-
const versionsRoot = canonicalPath(path.join(codemHome, "runtime", "versions"));
|
|
23
|
-
const actualCliRoot = canonicalPath(cliRoot);
|
|
24
|
-
const relative = path.relative(versionsRoot, actualCliRoot);
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
relative !== "" &&
|
|
28
|
-
relative !== ".." &&
|
|
29
|
-
!relative.startsWith(`..${path.sep}`) &&
|
|
30
|
-
!path.isAbsolute(relative)
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const CODEM_HOME = process.env.CODEM_HOME || path.join(os.homedir(), ".codem");
|
|
35
|
-
const RUNTIME_ROOT = path.join(CODEM_HOME, "runtime");
|
|
36
|
-
const CURRENT_POINTER_PATH = path.join(RUNTIME_ROOT, "current.json");
|
|
37
|
-
// Must match the store layout written by RuntimeManager (runtime-manager.js
|
|
38
|
-
// RUNTIME_CLI_PACKAGE_NAME) and resolved by the stable service shim in
|
|
39
|
-
// service-manager.js. 存量兼容:scope 改名(@byted-meego → @lark-codem)前
|
|
40
|
-
// 落盘的版本目录还在旧 scope 下,探测新名优先、旧名兜底。
|
|
41
|
-
const PACKAGE_NAME = "@lark-codem/codem-cli";
|
|
42
|
-
const LEGACY_PACKAGE_NAME = "@byted-meego/codem-cli";
|
|
43
|
-
|
|
44
|
-
async function pathExists(filePath) {
|
|
45
|
-
try {
|
|
46
|
-
await fs.access(filePath);
|
|
47
|
-
return true;
|
|
48
|
-
} catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Same install-form predicate as service-manager.js#isRuntimeManaged: this entry
|
|
54
|
-
// belongs to the managed architecture only when the file itself lives inside
|
|
55
|
-
// ~/.codem/runtime/versions/. A legacy (global npm) install must always run its
|
|
56
|
-
// own copy and never follow current.json — otherwise a runtime store left behind
|
|
57
|
-
// on a legacy host would silently capture the daemon while the on-PATH CLI keeps
|
|
58
|
-
// updating in place (the two copies diverge).
|
|
59
|
-
function isRuntimeManagedHere() {
|
|
60
|
-
const cliRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
61
|
-
return isRuntimeManagedInstall({ cliRoot, codemHome: CODEM_HOME });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function readCurrentRuntimeVersion() {
|
|
65
|
-
try {
|
|
66
|
-
const pointer = JSON.parse(await fs.readFile(CURRENT_POINTER_PATH, "utf8"));
|
|
67
|
-
return typeof pointer.version === "string" && pointer.version.trim()
|
|
68
|
-
? pointer.version.trim()
|
|
69
|
-
: null;
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function resolveRuntimeEntry(version) {
|
|
76
|
-
if (!version) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const versionDir = path.join(RUNTIME_ROOT, "versions", version);
|
|
81
|
-
const directEntry = path.join(versionDir, "bin", "codem.js");
|
|
82
|
-
|
|
83
|
-
if (await pathExists(directEntry)) {
|
|
84
|
-
return directEntry;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const packageName of [PACKAGE_NAME, LEGACY_PACKAGE_NAME]) {
|
|
88
|
-
const scopedEntry = path.join(versionDir, "node_modules", ...packageName.split("/"), "bin", "codem.js");
|
|
89
|
-
|
|
90
|
-
if (await pathExists(scopedEntry)) {
|
|
91
|
-
return scopedEntry;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 服务 env 快照侧车(与 service-manager.js 的 SERVICE_ENTRY_SOURCE 同语义,
|
|
99
|
-
// 二者需同步改):Windows 计划任务 XML 无法携带环境变量,codem 专属服务 env
|
|
100
|
-
// (泳道快照等)统一从 runtime/service-env.json 加载。缺文件 = 无服务 env;
|
|
101
|
-
// 解析失败上报 stderr,不静默。
|
|
102
|
-
// Dedicated Host inputs and App ownership are already bound to its immutable
|
|
103
|
-
// config/shim. A mutable legacy snapshot must not override that installation.
|
|
104
|
-
if (process.env.CODEM_DAEMON_HOSTED_WINDOWS !== "1") {
|
|
105
|
-
try {
|
|
106
|
-
const entries = JSON.parse(await fs.readFile(path.join(RUNTIME_ROOT, "service-env.json"), "utf8"));
|
|
107
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
108
|
-
if (typeof value === "string") process.env[key] = value;
|
|
109
|
-
}
|
|
110
|
-
} catch (err) {
|
|
111
|
-
if (err?.code !== "ENOENT") {
|
|
112
|
-
console.error("[codem-service] 忽略无法解析的 service-env.json:", err?.message ?? err);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 开发态逃生门:跳过 runtime 指针,直接加载本仓源码。否则自动更新会把
|
|
118
|
-
// 指针自愈回发布版 runtime,本地源码改动永远不会被 daemon 加载。
|
|
119
|
-
const forceSource = process.env.CODEM_DAEMON_DEV_SOURCE === "1";
|
|
120
|
-
const runtimeEntry =
|
|
121
|
-
forceSource || !isRuntimeManagedHere()
|
|
122
|
-
? null
|
|
123
|
-
: await resolveRuntimeEntry(await readCurrentRuntimeVersion());
|
|
124
|
-
|
|
125
|
-
if (runtimeEntry) {
|
|
126
|
-
await import(pathToFileURL(runtimeEntry));
|
|
127
|
-
} else {
|
|
128
|
-
if (forceSource) {
|
|
129
|
-
console.log("[codem-service] CODEM_DAEMON_DEV_SOURCE=1,加载本地 CLI 入口 ./codem.js");
|
|
130
|
-
}
|
|
131
|
-
await import("./codem.js");
|
|
132
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
// Inlined from src/runtime-install-form.js#isRuntimeManagedInstall(保持逐字节
|
|
9
|
+
// 同语义,二者需同步改):主包不再发布 dist/,本入口必须自包含。
|
|
10
|
+
function canonicalPath(filePath) {
|
|
11
|
+
const resolved = path.resolve(filePath);
|
|
12
|
+
const realpath = realpathSync.native ?? realpathSync;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return realpath(resolved);
|
|
16
|
+
} catch {
|
|
17
|
+
return resolved;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isRuntimeManagedInstall({ cliRoot, codemHome }) {
|
|
22
|
+
const versionsRoot = canonicalPath(path.join(codemHome, "runtime", "versions"));
|
|
23
|
+
const actualCliRoot = canonicalPath(cliRoot);
|
|
24
|
+
const relative = path.relative(versionsRoot, actualCliRoot);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
relative !== "" &&
|
|
28
|
+
relative !== ".." &&
|
|
29
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
30
|
+
!path.isAbsolute(relative)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const CODEM_HOME = process.env.CODEM_HOME || path.join(os.homedir(), ".codem");
|
|
35
|
+
const RUNTIME_ROOT = path.join(CODEM_HOME, "runtime");
|
|
36
|
+
const CURRENT_POINTER_PATH = path.join(RUNTIME_ROOT, "current.json");
|
|
37
|
+
// Must match the store layout written by RuntimeManager (runtime-manager.js
|
|
38
|
+
// RUNTIME_CLI_PACKAGE_NAME) and resolved by the stable service shim in
|
|
39
|
+
// service-manager.js. 存量兼容:scope 改名(@byted-meego → @lark-codem)前
|
|
40
|
+
// 落盘的版本目录还在旧 scope 下,探测新名优先、旧名兜底。
|
|
41
|
+
const PACKAGE_NAME = "@lark-codem/codem-cli";
|
|
42
|
+
const LEGACY_PACKAGE_NAME = "@byted-meego/codem-cli";
|
|
43
|
+
|
|
44
|
+
async function pathExists(filePath) {
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(filePath);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Same install-form predicate as service-manager.js#isRuntimeManaged: this entry
|
|
54
|
+
// belongs to the managed architecture only when the file itself lives inside
|
|
55
|
+
// ~/.codem/runtime/versions/. A legacy (global npm) install must always run its
|
|
56
|
+
// own copy and never follow current.json — otherwise a runtime store left behind
|
|
57
|
+
// on a legacy host would silently capture the daemon while the on-PATH CLI keeps
|
|
58
|
+
// updating in place (the two copies diverge).
|
|
59
|
+
function isRuntimeManagedHere() {
|
|
60
|
+
const cliRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
61
|
+
return isRuntimeManagedInstall({ cliRoot, codemHome: CODEM_HOME });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function readCurrentRuntimeVersion() {
|
|
65
|
+
try {
|
|
66
|
+
const pointer = JSON.parse(await fs.readFile(CURRENT_POINTER_PATH, "utf8"));
|
|
67
|
+
return typeof pointer.version === "string" && pointer.version.trim()
|
|
68
|
+
? pointer.version.trim()
|
|
69
|
+
: null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function resolveRuntimeEntry(version) {
|
|
76
|
+
if (!version) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const versionDir = path.join(RUNTIME_ROOT, "versions", version);
|
|
81
|
+
const directEntry = path.join(versionDir, "bin", "codem.js");
|
|
82
|
+
|
|
83
|
+
if (await pathExists(directEntry)) {
|
|
84
|
+
return directEntry;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const packageName of [PACKAGE_NAME, LEGACY_PACKAGE_NAME]) {
|
|
88
|
+
const scopedEntry = path.join(versionDir, "node_modules", ...packageName.split("/"), "bin", "codem.js");
|
|
89
|
+
|
|
90
|
+
if (await pathExists(scopedEntry)) {
|
|
91
|
+
return scopedEntry;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 服务 env 快照侧车(与 service-manager.js 的 SERVICE_ENTRY_SOURCE 同语义,
|
|
99
|
+
// 二者需同步改):Windows 计划任务 XML 无法携带环境变量,codem 专属服务 env
|
|
100
|
+
// (泳道快照等)统一从 runtime/service-env.json 加载。缺文件 = 无服务 env;
|
|
101
|
+
// 解析失败上报 stderr,不静默。
|
|
102
|
+
// Dedicated Host inputs and App ownership are already bound to its immutable
|
|
103
|
+
// config/shim. A mutable legacy snapshot must not override that installation.
|
|
104
|
+
if (process.env.CODEM_DAEMON_HOSTED_WINDOWS !== "1") {
|
|
105
|
+
try {
|
|
106
|
+
const entries = JSON.parse(await fs.readFile(path.join(RUNTIME_ROOT, "service-env.json"), "utf8"));
|
|
107
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
108
|
+
if (typeof value === "string") process.env[key] = value;
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err?.code !== "ENOENT") {
|
|
112
|
+
console.error("[codem-service] 忽略无法解析的 service-env.json:", err?.message ?? err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 开发态逃生门:跳过 runtime 指针,直接加载本仓源码。否则自动更新会把
|
|
118
|
+
// 指针自愈回发布版 runtime,本地源码改动永远不会被 daemon 加载。
|
|
119
|
+
const forceSource = process.env.CODEM_DAEMON_DEV_SOURCE === "1";
|
|
120
|
+
const runtimeEntry =
|
|
121
|
+
forceSource || !isRuntimeManagedHere()
|
|
122
|
+
? null
|
|
123
|
+
: await resolveRuntimeEntry(await readCurrentRuntimeVersion());
|
|
124
|
+
|
|
125
|
+
if (runtimeEntry) {
|
|
126
|
+
await import(pathToFileURL(runtimeEntry));
|
|
127
|
+
} else {
|
|
128
|
+
if (forceSource) {
|
|
129
|
+
console.log("[codem-service] CODEM_DAEMON_DEV_SOURCE=1,加载本地 CLI 入口 ./codem.js");
|
|
130
|
+
}
|
|
131
|
+
await import("./codem.js");
|
|
132
|
+
}
|
package/bin/codem.js
CHANGED
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Thin launcher for the compiled CodeM CLI binary.
|
|
3
|
-
//
|
|
4
|
-
// 主包 @lark-codem/codem-cli 不再携带可运行的 JS 源码:真正的 CLI 是
|
|
5
|
-
// bun --compile 产物,按平台拆包发布在 optionalDependencies
|
|
6
|
-
// `@lark-codem/codem-cli-<os>-<arch>` 里(npm 依据平台包 package.json 的
|
|
7
|
-
// os/cpu 字段自动只装匹配的那个)。本文件是保留给所有历史消费方的稳定入口
|
|
8
|
-
// (npm bin 链接、daemon-service.cjs、codem-app 的 CodemCLIResolver、
|
|
9
|
-
// codem-shim 等都指向 bin/codem.js):解析平台包里的二进制,spawn 并透传
|
|
10
|
-
// stdio / 信号 / 退出码。
|
|
11
|
-
//
|
|
12
|
-
// 开发态兜底:repo checkout 里没有平台包,若 ../dist/src/index.js 存在
|
|
13
|
-
// (npm run build 产物),直接 import 执行——`node bin/codem.js` 在仓内可用。
|
|
14
|
-
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
16
|
-
import fs from "node:fs";
|
|
17
|
-
import { createRequire } from "node:module";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
applyHostedWindowsChildOptions,
|
|
23
|
-
watchWindowsDaemonHostMigration
|
|
24
|
-
} from "./platform-child-process.js";
|
|
25
|
-
|
|
26
|
-
const require = createRequire(import.meta.url);
|
|
27
|
-
const LAUNCHER_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
-
const PACKAGE_ROOT = path.resolve(LAUNCHER_DIR, "..");
|
|
29
|
-
// scope 从自己的安装落点推导(…/node_modules/<scope>/codem-cli/bin/codem.js),
|
|
30
|
-
// 不能写死单一 scope:历史 @byted-meego(bnpm)与公网 @lark-codem 两套包树
|
|
31
|
-
// 都用这同一份 launcher,平台包 scope 跟随主包。非 npm 布局(scope 目录名
|
|
32
|
-
// 不带 @)回退发布 scope。
|
|
33
|
-
const SCOPE_DIR_NAME = path.basename(path.dirname(PACKAGE_ROOT));
|
|
34
|
-
const PACKAGE_SCOPE = SCOPE_DIR_NAME.startsWith("@") ? SCOPE_DIR_NAME : "@lark-codem";
|
|
35
|
-
const PLATFORM_PACKAGE_NAME = `${PACKAGE_SCOPE}/codem-cli-${process.platform}-${process.arch}`;
|
|
36
|
-
const BINARY_NAME = process.platform === "win32" ? "codem.exe" : "codem";
|
|
37
|
-
|
|
38
|
-
function resolvePlatformBinary() {
|
|
39
|
-
// 常规路径:npm 已把平台包装进依赖树,走标准解析。
|
|
40
|
-
try {
|
|
41
|
-
const manifestPath = require.resolve(`${PLATFORM_PACKAGE_NAME}/package.json`);
|
|
42
|
-
return path.join(path.dirname(manifestPath), "bin", BINARY_NAME);
|
|
43
|
-
} catch {
|
|
44
|
-
// fall through to explicit probes
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// 兜底探测:主包自己的 node_modules 与同 scope 兄弟目录(npm 提升安装)。
|
|
48
|
-
const [scope, name] = PLATFORM_PACKAGE_NAME.split("/");
|
|
49
|
-
const candidates = [
|
|
50
|
-
path.join(PACKAGE_ROOT, "node_modules", scope, name, "bin", BINARY_NAME),
|
|
51
|
-
path.join(path.dirname(PACKAGE_ROOT), name, "bin", BINARY_NAME)
|
|
52
|
-
];
|
|
53
|
-
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// spawn 的失败有两条互斥出口:只有 EACCES / EAGAIN / EMFILE / ENFILE / ENOENT
|
|
57
|
-
// 会被 Node 降级成异步 `error` 事件,其余 errno(ENOEXEC / ENOTDIR / EPERM,以及
|
|
58
|
-
// Windows 上杀软 / EDR 拦截 CreateProcessW 时 libuv 报的 UNKNOWN=-4094)在
|
|
59
|
-
// `child_process.spawn` 里直接 `throw new ErrnoException(err, 'spawn')`。
|
|
60
|
-
// stderr 同步写。本文件里每一条诊断后面都紧跟 process.exit,而 process.exit()
|
|
61
|
-
// 按 Node 官方契约会**放弃尚未完成的 stdout/stderr I/O**("a note on process I/O")。
|
|
62
|
-
// stderr 是否同步取决于它接的是什么:
|
|
63
|
-
// Files —— Windows / POSIX 均同步
|
|
64
|
-
// Pipes —— Windows / POSIX 均同步
|
|
65
|
-
// TTY —— POSIX 同步,**Windows 异步**
|
|
66
|
-
// 最需要这些诊断的现场恰恰落在唯一异步的那格:用户在 PowerShell 里交互式敲
|
|
67
|
-
// codem,stderr 就是 Windows TTY。走 console.error 时诊断可能被截断甚至整条丢失,
|
|
68
|
-
// 用户继续看到「零输出」——正是本次修复要消灭的现象,等于修了个寂寞。
|
|
69
|
-
// fs.writeSync 绕过流缓冲直接写 fd,返回即已落地,不受 process.exit 影响。
|
|
70
|
-
function writeStderrSync(message) {
|
|
71
|
-
const line = `${message}\n`;
|
|
72
|
-
try {
|
|
73
|
-
fs.writeSync(process.stderr.fd, line);
|
|
74
|
-
} catch {
|
|
75
|
-
// fd 已关闭 / EPIPE 等场景下不能再抛:诊断本身不该变成新的失败源。
|
|
76
|
-
// 退回流式写尽力而为——此时能不能送达已不由本进程决定。
|
|
77
|
-
try {
|
|
78
|
-
process.stderr.write(line);
|
|
79
|
-
} catch {
|
|
80
|
-
// 到这里已无可用错误通道,只能放弃。
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 两条出口必须汇到同一个 reporter:只挂 child.on("error") 的话,后一类失败会以
|
|
86
|
-
// 一段指向 node:internal/child_process 的裸栈抛给用户——codem-cli 的平台二进制是
|
|
87
|
-
// bun --compile 出来的未签名可执行文件,Windows 上被拦截是真实可达路径。
|
|
88
|
-
function reportSpawnFailure(targetPath, error) {
|
|
89
|
-
const code = error?.code ?? "UNKNOWN";
|
|
90
|
-
const errno = error?.errno ?? "unknown";
|
|
91
|
-
const detail = error?.message ?? String(error);
|
|
92
|
-
const lines = [
|
|
93
|
-
`codem: 无法启动 CLI 二进制(${targetPath}):${detail}`,
|
|
94
|
-
`codem: 失败细节 code=${code} errno=${errno} platform=${process.platform}`
|
|
95
|
-
];
|
|
96
|
-
if (process.platform === "win32") {
|
|
97
|
-
lines.push(
|
|
98
|
-
"codem: Windows 上最常见的成因是杀毒软件 / EDR 拦截了进程创建(此时 libuv 只能报 UNKNOWN/-4094)。",
|
|
99
|
-
`codem: 请把该路径加入杀软白名单后重新安装 CodeM CLI;或先在 PowerShell 里直接裸跑 & "${targetPath}" --version,`,
|
|
100
|
-
"codem: 操作系统给出的错误会比这里的 spawn 报错具体得多。"
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
writeStderrSync(lines.join("\n"));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Windows NTSTATUS 翻译。0xC0000000 以上是内核致命错误码:进程在 main 之前就被
|
|
108
|
-
// 杀掉,stdio 里不会有任何字节。不翻译的话用户侧现象是「敲 codem 完全没反应」,
|
|
109
|
-
// 连一条能拿去搜的错误串都没有——实测一例 AVX2 缺失,PATH / launcher / 指针 /
|
|
110
|
-
// 运行时四层全部正常,三层把同一个退出码原样透传上来,用户完全无从判断断在哪。
|
|
111
|
-
// 这里只往 stderr 补一条说明,退出码仍原样透传,不改变对上游的语义。
|
|
112
|
-
//
|
|
113
|
-
// code 的符号表示两可:libuv 从 GetExitCodeProcess 拿到的是 DWORD,Node 侧多为
|
|
114
|
-
// 正数(0xC000001D → 3221225501),而 shell 常按 int32 显示(-1073741795)。
|
|
115
|
-
// `>>> 0` 把两种表示归一到同一个无符号值,避免只判其中一种而漏掉。
|
|
116
|
-
function explainWindowsFatalStatus(code) {
|
|
117
|
-
if (process.platform !== "win32" || typeof code !== "number") {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
const status = code >>> 0;
|
|
121
|
-
if (status < 0xc0000000) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
const detail = {
|
|
125
|
-
0xc000001d: [
|
|
126
|
-
"STATUS_ILLEGAL_INSTRUCTION:CPU 不支持二进制中用到的指令集,最常见是缺少 AVX2。",
|
|
127
|
-
"注意「年份新」不代表有 AVX2:Intel 在 Pentium Gold / Celeron 整条产品线上熔断了",
|
|
128
|
-
"AVX/AVX2(如 2020 年的 Pentium Gold G6400);Atom / Gemini Lake、Sandy/Ivy Bridge",
|
|
129
|
-
"以及屏蔽 AVX2 的虚拟化环境同理。请升级到使用 baseline 产线的 CodeM 版本。",
|
|
130
|
-
"自查(PowerShell,返回 False 即无 AVX2):",
|
|
131
|
-
" Add-Type -Namespace W -Name K -MemberDefinition '[DllImport(\"kernel32.dll\")] public static extern bool IsProcessorFeaturePresent(uint f);'",
|
|
132
|
-
" [W.K]::IsProcessorFeaturePresent(40)"
|
|
133
|
-
],
|
|
134
|
-
0xc0000005: ["STATUS_ACCESS_VIOLATION:二进制崩溃(段错误)。请重装 CodeM CLI,仍复现请反馈。"],
|
|
135
|
-
0xc0000022: ["STATUS_ACCESS_DENIED:进程被拒绝执行,通常是杀毒软件 / EDR 拦截。请把 CodeM 安装目录加入白名单。"],
|
|
136
|
-
0xc000007b: ["STATUS_INVALID_IMAGE_FORMAT:可执行文件架构与系统不匹配(x64 / ARM64 错配)。请重装 CodeM CLI。"],
|
|
137
|
-
0xc0000135: ["STATUS_DLL_NOT_FOUND:缺少运行时 DLL,通常需要安装 Microsoft Visual C++ 运行库。"],
|
|
138
|
-
0xc0000142: ["STATUS_DLL_INIT_FAILED:运行时 DLL 初始化失败,常见于被安全软件注入的进程。"],
|
|
139
|
-
0xc0000409: ["STATUS_STACK_BUFFER_OVERRUN:进程被安全检查主动终止。请重装 CodeM CLI,仍复现请反馈。"]
|
|
140
|
-
}[status];
|
|
141
|
-
const hex = `0x${status.toString(16).toUpperCase().padStart(8, "0")}`;
|
|
142
|
-
return [
|
|
143
|
-
`codem: CLI 二进制被系统终止,NTSTATUS=${hex}(进程在启动阶段被杀,因此没有任何输出)。`,
|
|
144
|
-
...(detail ?? ["未知的 Windows 致命错误码,请带上该 NTSTATUS 反馈。"]).map((line) => `codem: ${line}`)
|
|
145
|
-
].join("\n");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const binaryPath = resolvePlatformBinary();
|
|
149
|
-
|
|
150
|
-
if (binaryPath) {
|
|
151
|
-
let child;
|
|
152
|
-
let stopWindowsHostMigrationWatch = () => {};
|
|
153
|
-
try {
|
|
154
|
-
child = spawn(
|
|
155
|
-
binaryPath,
|
|
156
|
-
process.argv.slice(2),
|
|
157
|
-
applyHostedWindowsChildOptions({
|
|
158
|
-
stdio: "inherit",
|
|
159
|
-
env: {
|
|
160
|
-
...process.env,
|
|
161
|
-
// The compiled binary cannot infer a Node executable from its own
|
|
162
|
-
// process image. The npm launcher is the authority for this install
|
|
163
|
-
// form and hands off its exact interpreter as the canonical launcher.
|
|
164
|
-
CODEM_PLUGIN_NODE: process.execPath
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
);
|
|
168
|
-
stopWindowsHostMigrationWatch = watchWindowsDaemonHostMigration({
|
|
169
|
-
binaryPath,
|
|
170
|
-
daemonPid: child.pid,
|
|
171
|
-
launcherPid: process.pid
|
|
172
|
-
});
|
|
173
|
-
} catch (error) {
|
|
174
|
-
reportSpawnFailure(binaryPath, error);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 终端会把 SIGINT/SIGHUP 广播给整个前台进程组,但 execFile timeout、
|
|
178
|
-
// child.kill()、launchd/systemd 等调用方也会只命中 launcher PID。三类终止
|
|
179
|
-
// 信号都显式转发,保证后一种场景不会留下 Bun 子进程;child.kill 只命中
|
|
180
|
-
// 直接子进程,不会把同一信号再次广播给它下面的 linco 进程组。
|
|
181
|
-
const signalHandlers = new Map();
|
|
182
|
-
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
183
|
-
const handler = () => {
|
|
184
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
child.kill(signal);
|
|
189
|
-
} catch {
|
|
190
|
-
// child already gone
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
signalHandlers.set(signal, handler);
|
|
194
|
-
process.on(signal, handler);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
child.on("error", (error) => {
|
|
198
|
-
reportSpawnFailure(binaryPath, error);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
child.on("exit", (code, signal) => {
|
|
202
|
-
stopWindowsHostMigrationWatch();
|
|
203
|
-
for (const [registeredSignal, handler] of signalHandlers) {
|
|
204
|
-
process.removeListener(registeredSignal, handler);
|
|
205
|
-
}
|
|
206
|
-
if (signal) {
|
|
207
|
-
// 让退出信号对上游可见:自己的 handler 已摘掉,可以安全补发同名信号。
|
|
208
|
-
process.kill(process.pid, signal);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
const fatal = explainWindowsFatalStatus(code);
|
|
212
|
-
if (fatal) {
|
|
213
|
-
writeStderrSync(fatal);
|
|
214
|
-
}
|
|
215
|
-
process.exit(code ?? 0);
|
|
216
|
-
});
|
|
217
|
-
} else {
|
|
218
|
-
// 两个候选覆盖两种落点:仓根 bin/(→ dist/src)与构建产物 dist/bin/(→ ../src)。
|
|
219
|
-
const devEntry = [
|
|
220
|
-
path.join(PACKAGE_ROOT, "dist", "src", "index.js"),
|
|
221
|
-
path.join(PACKAGE_ROOT, "src", "index.js")
|
|
222
|
-
].find((candidate) => fs.existsSync(candidate));
|
|
223
|
-
if (devEntry) {
|
|
224
|
-
const { main } = await import(pathToFileURL(devEntry));
|
|
225
|
-
main().catch((error) => {
|
|
226
|
-
writeStderrSync(error.message);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
});
|
|
229
|
-
} else {
|
|
230
|
-
writeStderrSync(
|
|
231
|
-
`codem: 未找到平台 CLI 包 ${PLATFORM_PACKAGE_NAME}。` +
|
|
232
|
-
"通常是安装时 optional 依赖被跳过(npm install --no-optional)或当前平台暂不支持;" +
|
|
233
|
-
"请重新安装 CodeM CLI。"
|
|
234
|
-
);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Thin launcher for the compiled CodeM CLI binary.
|
|
3
|
+
//
|
|
4
|
+
// 主包 @lark-codem/codem-cli 不再携带可运行的 JS 源码:真正的 CLI 是
|
|
5
|
+
// bun --compile 产物,按平台拆包发布在 optionalDependencies
|
|
6
|
+
// `@lark-codem/codem-cli-<os>-<arch>` 里(npm 依据平台包 package.json 的
|
|
7
|
+
// os/cpu 字段自动只装匹配的那个)。本文件是保留给所有历史消费方的稳定入口
|
|
8
|
+
// (npm bin 链接、daemon-service.cjs、codem-app 的 CodemCLIResolver、
|
|
9
|
+
// codem-shim 等都指向 bin/codem.js):解析平台包里的二进制,spawn 并透传
|
|
10
|
+
// stdio / 信号 / 退出码。
|
|
11
|
+
//
|
|
12
|
+
// 开发态兜底:repo checkout 里没有平台包,若 ../dist/src/index.js 存在
|
|
13
|
+
// (npm run build 产物),直接 import 执行——`node bin/codem.js` 在仓内可用。
|
|
14
|
+
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
applyHostedWindowsChildOptions,
|
|
23
|
+
watchWindowsDaemonHostMigration
|
|
24
|
+
} from "./platform-child-process.js";
|
|
25
|
+
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
const LAUNCHER_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const PACKAGE_ROOT = path.resolve(LAUNCHER_DIR, "..");
|
|
29
|
+
// scope 从自己的安装落点推导(…/node_modules/<scope>/codem-cli/bin/codem.js),
|
|
30
|
+
// 不能写死单一 scope:历史 @byted-meego(bnpm)与公网 @lark-codem 两套包树
|
|
31
|
+
// 都用这同一份 launcher,平台包 scope 跟随主包。非 npm 布局(scope 目录名
|
|
32
|
+
// 不带 @)回退发布 scope。
|
|
33
|
+
const SCOPE_DIR_NAME = path.basename(path.dirname(PACKAGE_ROOT));
|
|
34
|
+
const PACKAGE_SCOPE = SCOPE_DIR_NAME.startsWith("@") ? SCOPE_DIR_NAME : "@lark-codem";
|
|
35
|
+
const PLATFORM_PACKAGE_NAME = `${PACKAGE_SCOPE}/codem-cli-${process.platform}-${process.arch}`;
|
|
36
|
+
const BINARY_NAME = process.platform === "win32" ? "codem.exe" : "codem";
|
|
37
|
+
|
|
38
|
+
function resolvePlatformBinary() {
|
|
39
|
+
// 常规路径:npm 已把平台包装进依赖树,走标准解析。
|
|
40
|
+
try {
|
|
41
|
+
const manifestPath = require.resolve(`${PLATFORM_PACKAGE_NAME}/package.json`);
|
|
42
|
+
return path.join(path.dirname(manifestPath), "bin", BINARY_NAME);
|
|
43
|
+
} catch {
|
|
44
|
+
// fall through to explicit probes
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 兜底探测:主包自己的 node_modules 与同 scope 兄弟目录(npm 提升安装)。
|
|
48
|
+
const [scope, name] = PLATFORM_PACKAGE_NAME.split("/");
|
|
49
|
+
const candidates = [
|
|
50
|
+
path.join(PACKAGE_ROOT, "node_modules", scope, name, "bin", BINARY_NAME),
|
|
51
|
+
path.join(path.dirname(PACKAGE_ROOT), name, "bin", BINARY_NAME)
|
|
52
|
+
];
|
|
53
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// spawn 的失败有两条互斥出口:只有 EACCES / EAGAIN / EMFILE / ENFILE / ENOENT
|
|
57
|
+
// 会被 Node 降级成异步 `error` 事件,其余 errno(ENOEXEC / ENOTDIR / EPERM,以及
|
|
58
|
+
// Windows 上杀软 / EDR 拦截 CreateProcessW 时 libuv 报的 UNKNOWN=-4094)在
|
|
59
|
+
// `child_process.spawn` 里直接 `throw new ErrnoException(err, 'spawn')`。
|
|
60
|
+
// stderr 同步写。本文件里每一条诊断后面都紧跟 process.exit,而 process.exit()
|
|
61
|
+
// 按 Node 官方契约会**放弃尚未完成的 stdout/stderr I/O**("a note on process I/O")。
|
|
62
|
+
// stderr 是否同步取决于它接的是什么:
|
|
63
|
+
// Files —— Windows / POSIX 均同步
|
|
64
|
+
// Pipes —— Windows / POSIX 均同步
|
|
65
|
+
// TTY —— POSIX 同步,**Windows 异步**
|
|
66
|
+
// 最需要这些诊断的现场恰恰落在唯一异步的那格:用户在 PowerShell 里交互式敲
|
|
67
|
+
// codem,stderr 就是 Windows TTY。走 console.error 时诊断可能被截断甚至整条丢失,
|
|
68
|
+
// 用户继续看到「零输出」——正是本次修复要消灭的现象,等于修了个寂寞。
|
|
69
|
+
// fs.writeSync 绕过流缓冲直接写 fd,返回即已落地,不受 process.exit 影响。
|
|
70
|
+
function writeStderrSync(message) {
|
|
71
|
+
const line = `${message}\n`;
|
|
72
|
+
try {
|
|
73
|
+
fs.writeSync(process.stderr.fd, line);
|
|
74
|
+
} catch {
|
|
75
|
+
// fd 已关闭 / EPIPE 等场景下不能再抛:诊断本身不该变成新的失败源。
|
|
76
|
+
// 退回流式写尽力而为——此时能不能送达已不由本进程决定。
|
|
77
|
+
try {
|
|
78
|
+
process.stderr.write(line);
|
|
79
|
+
} catch {
|
|
80
|
+
// 到这里已无可用错误通道,只能放弃。
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 两条出口必须汇到同一个 reporter:只挂 child.on("error") 的话,后一类失败会以
|
|
86
|
+
// 一段指向 node:internal/child_process 的裸栈抛给用户——codem-cli 的平台二进制是
|
|
87
|
+
// bun --compile 出来的未签名可执行文件,Windows 上被拦截是真实可达路径。
|
|
88
|
+
function reportSpawnFailure(targetPath, error) {
|
|
89
|
+
const code = error?.code ?? "UNKNOWN";
|
|
90
|
+
const errno = error?.errno ?? "unknown";
|
|
91
|
+
const detail = error?.message ?? String(error);
|
|
92
|
+
const lines = [
|
|
93
|
+
`codem: 无法启动 CLI 二进制(${targetPath}):${detail}`,
|
|
94
|
+
`codem: 失败细节 code=${code} errno=${errno} platform=${process.platform}`
|
|
95
|
+
];
|
|
96
|
+
if (process.platform === "win32") {
|
|
97
|
+
lines.push(
|
|
98
|
+
"codem: Windows 上最常见的成因是杀毒软件 / EDR 拦截了进程创建(此时 libuv 只能报 UNKNOWN/-4094)。",
|
|
99
|
+
`codem: 请把该路径加入杀软白名单后重新安装 CodeM CLI;或先在 PowerShell 里直接裸跑 & "${targetPath}" --version,`,
|
|
100
|
+
"codem: 操作系统给出的错误会比这里的 spawn 报错具体得多。"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
writeStderrSync(lines.join("\n"));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Windows NTSTATUS 翻译。0xC0000000 以上是内核致命错误码:进程在 main 之前就被
|
|
108
|
+
// 杀掉,stdio 里不会有任何字节。不翻译的话用户侧现象是「敲 codem 完全没反应」,
|
|
109
|
+
// 连一条能拿去搜的错误串都没有——实测一例 AVX2 缺失,PATH / launcher / 指针 /
|
|
110
|
+
// 运行时四层全部正常,三层把同一个退出码原样透传上来,用户完全无从判断断在哪。
|
|
111
|
+
// 这里只往 stderr 补一条说明,退出码仍原样透传,不改变对上游的语义。
|
|
112
|
+
//
|
|
113
|
+
// code 的符号表示两可:libuv 从 GetExitCodeProcess 拿到的是 DWORD,Node 侧多为
|
|
114
|
+
// 正数(0xC000001D → 3221225501),而 shell 常按 int32 显示(-1073741795)。
|
|
115
|
+
// `>>> 0` 把两种表示归一到同一个无符号值,避免只判其中一种而漏掉。
|
|
116
|
+
function explainWindowsFatalStatus(code) {
|
|
117
|
+
if (process.platform !== "win32" || typeof code !== "number") {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const status = code >>> 0;
|
|
121
|
+
if (status < 0xc0000000) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const detail = {
|
|
125
|
+
0xc000001d: [
|
|
126
|
+
"STATUS_ILLEGAL_INSTRUCTION:CPU 不支持二进制中用到的指令集,最常见是缺少 AVX2。",
|
|
127
|
+
"注意「年份新」不代表有 AVX2:Intel 在 Pentium Gold / Celeron 整条产品线上熔断了",
|
|
128
|
+
"AVX/AVX2(如 2020 年的 Pentium Gold G6400);Atom / Gemini Lake、Sandy/Ivy Bridge",
|
|
129
|
+
"以及屏蔽 AVX2 的虚拟化环境同理。请升级到使用 baseline 产线的 CodeM 版本。",
|
|
130
|
+
"自查(PowerShell,返回 False 即无 AVX2):",
|
|
131
|
+
" Add-Type -Namespace W -Name K -MemberDefinition '[DllImport(\"kernel32.dll\")] public static extern bool IsProcessorFeaturePresent(uint f);'",
|
|
132
|
+
" [W.K]::IsProcessorFeaturePresent(40)"
|
|
133
|
+
],
|
|
134
|
+
0xc0000005: ["STATUS_ACCESS_VIOLATION:二进制崩溃(段错误)。请重装 CodeM CLI,仍复现请反馈。"],
|
|
135
|
+
0xc0000022: ["STATUS_ACCESS_DENIED:进程被拒绝执行,通常是杀毒软件 / EDR 拦截。请把 CodeM 安装目录加入白名单。"],
|
|
136
|
+
0xc000007b: ["STATUS_INVALID_IMAGE_FORMAT:可执行文件架构与系统不匹配(x64 / ARM64 错配)。请重装 CodeM CLI。"],
|
|
137
|
+
0xc0000135: ["STATUS_DLL_NOT_FOUND:缺少运行时 DLL,通常需要安装 Microsoft Visual C++ 运行库。"],
|
|
138
|
+
0xc0000142: ["STATUS_DLL_INIT_FAILED:运行时 DLL 初始化失败,常见于被安全软件注入的进程。"],
|
|
139
|
+
0xc0000409: ["STATUS_STACK_BUFFER_OVERRUN:进程被安全检查主动终止。请重装 CodeM CLI,仍复现请反馈。"]
|
|
140
|
+
}[status];
|
|
141
|
+
const hex = `0x${status.toString(16).toUpperCase().padStart(8, "0")}`;
|
|
142
|
+
return [
|
|
143
|
+
`codem: CLI 二进制被系统终止,NTSTATUS=${hex}(进程在启动阶段被杀,因此没有任何输出)。`,
|
|
144
|
+
...(detail ?? ["未知的 Windows 致命错误码,请带上该 NTSTATUS 反馈。"]).map((line) => `codem: ${line}`)
|
|
145
|
+
].join("\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const binaryPath = resolvePlatformBinary();
|
|
149
|
+
|
|
150
|
+
if (binaryPath) {
|
|
151
|
+
let child;
|
|
152
|
+
let stopWindowsHostMigrationWatch = () => {};
|
|
153
|
+
try {
|
|
154
|
+
child = spawn(
|
|
155
|
+
binaryPath,
|
|
156
|
+
process.argv.slice(2),
|
|
157
|
+
applyHostedWindowsChildOptions({
|
|
158
|
+
stdio: "inherit",
|
|
159
|
+
env: {
|
|
160
|
+
...process.env,
|
|
161
|
+
// The compiled binary cannot infer a Node executable from its own
|
|
162
|
+
// process image. The npm launcher is the authority for this install
|
|
163
|
+
// form and hands off its exact interpreter as the canonical launcher.
|
|
164
|
+
CODEM_PLUGIN_NODE: process.execPath
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
stopWindowsHostMigrationWatch = watchWindowsDaemonHostMigration({
|
|
169
|
+
binaryPath,
|
|
170
|
+
daemonPid: child.pid,
|
|
171
|
+
launcherPid: process.pid
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
reportSpawnFailure(binaryPath, error);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 终端会把 SIGINT/SIGHUP 广播给整个前台进程组,但 execFile timeout、
|
|
178
|
+
// child.kill()、launchd/systemd 等调用方也会只命中 launcher PID。三类终止
|
|
179
|
+
// 信号都显式转发,保证后一种场景不会留下 Bun 子进程;child.kill 只命中
|
|
180
|
+
// 直接子进程,不会把同一信号再次广播给它下面的 linco 进程组。
|
|
181
|
+
const signalHandlers = new Map();
|
|
182
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
183
|
+
const handler = () => {
|
|
184
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
child.kill(signal);
|
|
189
|
+
} catch {
|
|
190
|
+
// child already gone
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
signalHandlers.set(signal, handler);
|
|
194
|
+
process.on(signal, handler);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
child.on("error", (error) => {
|
|
198
|
+
reportSpawnFailure(binaryPath, error);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
child.on("exit", (code, signal) => {
|
|
202
|
+
stopWindowsHostMigrationWatch();
|
|
203
|
+
for (const [registeredSignal, handler] of signalHandlers) {
|
|
204
|
+
process.removeListener(registeredSignal, handler);
|
|
205
|
+
}
|
|
206
|
+
if (signal) {
|
|
207
|
+
// 让退出信号对上游可见:自己的 handler 已摘掉,可以安全补发同名信号。
|
|
208
|
+
process.kill(process.pid, signal);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const fatal = explainWindowsFatalStatus(code);
|
|
212
|
+
if (fatal) {
|
|
213
|
+
writeStderrSync(fatal);
|
|
214
|
+
}
|
|
215
|
+
process.exit(code ?? 0);
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
// 两个候选覆盖两种落点:仓根 bin/(→ dist/src)与构建产物 dist/bin/(→ ../src)。
|
|
219
|
+
const devEntry = [
|
|
220
|
+
path.join(PACKAGE_ROOT, "dist", "src", "index.js"),
|
|
221
|
+
path.join(PACKAGE_ROOT, "src", "index.js")
|
|
222
|
+
].find((candidate) => fs.existsSync(candidate));
|
|
223
|
+
if (devEntry) {
|
|
224
|
+
const { main } = await import(pathToFileURL(devEntry));
|
|
225
|
+
main().catch((error) => {
|
|
226
|
+
writeStderrSync(error.message);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
writeStderrSync(
|
|
231
|
+
`codem: 未找到平台 CLI 包 ${PLATFORM_PACKAGE_NAME}。` +
|
|
232
|
+
"通常是安装时 optional 依赖被跳过(npm install --no-optional)或当前平台暂不支持;" +
|
|
233
|
+
"请重新安装 CodeM CLI。"
|
|
234
|
+
);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
const WINDOWS_LEGACY_DAEMON_CAPABILITY = "shutdown_for_service_operation";
|
|
7
|
-
const WINDOWS_LEGACY_DAEMON_CAPABILITY_RELATIVE_PATH = path.join(
|
|
8
|
-
"runtime",
|
|
9
|
-
"legacy-daemon-control",
|
|
10
|
-
"capability.json"
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
// The dedicated Windows daemon Host owns the top-level console policy. Every
|
|
14
|
-
// child it can reach must stay hidden, and a local spawn option must not be able
|
|
15
|
-
// to undo that invariant. Interactive and non-Windows launches keep their
|
|
16
|
-
// options object untouched.
|
|
17
|
-
export function applyHostedWindowsChildOptions(
|
|
18
|
-
options = {},
|
|
19
|
-
{
|
|
20
|
-
// Hosted mode belongs to the current daemon process, not to the child env
|
|
21
|
-
// object supplied by an individual caller. Otherwise a caller could omit
|
|
22
|
-
// or replace `options.env` and accidentally bypass the console invariant.
|
|
23
|
-
env = process.env,
|
|
24
|
-
platform = process.platform
|
|
25
|
-
} = {}
|
|
26
|
-
) {
|
|
27
|
-
if (platform !== "win32" || env?.CODEM_DAEMON_HOSTED_WINDOWS !== "1") {
|
|
28
|
-
return options;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
...options,
|
|
33
|
-
windowsHide: true
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function shouldWatchWindowsDaemonHostMigration({
|
|
38
|
-
platform = process.platform,
|
|
39
|
-
argv = process.argv.slice(2),
|
|
40
|
-
env = process.env
|
|
41
|
-
} = {}) {
|
|
42
|
-
return platform === "win32" &&
|
|
43
|
-
argv[0] === "daemon" &&
|
|
44
|
-
env?.CODEM_DAEMON_HOSTED_WINDOWS !== "1" &&
|
|
45
|
-
typeof env?.CODEM_DAEMON_RUNTIME_CLIENT_INSTANCE_ID === "string" &&
|
|
46
|
-
env.CODEM_DAEMON_RUNTIME_CLIENT_INSTANCE_ID.trim() !== "";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function isCurrentWindowsLegacyDaemonCapability(
|
|
50
|
-
value,
|
|
51
|
-
{ daemonPid, startedAtMs } = {}
|
|
52
|
-
) {
|
|
53
|
-
return value?.schemaVersion === 1 &&
|
|
54
|
-
value?.protocolVersion === 1 &&
|
|
55
|
-
value?.capability === WINDOWS_LEGACY_DAEMON_CAPABILITY &&
|
|
56
|
-
Number(value?.rootIdentity?.pid) === daemonPid &&
|
|
57
|
-
Number(value?.publishedAtUnixMs) >= startedAtMs;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function launchWindowsDaemonHostMigration({
|
|
61
|
-
binaryPath,
|
|
62
|
-
launcherPid = process.pid,
|
|
63
|
-
env = process.env,
|
|
64
|
-
spawnImpl = spawn
|
|
65
|
-
} = {}) {
|
|
66
|
-
if (String(binaryPath).includes("'")) {
|
|
67
|
-
throw new Error("CodeM runtime path cannot be represented in the Windows migration handoff");
|
|
68
|
-
}
|
|
69
|
-
const workingDirectory = path.win32.dirname(binaryPath);
|
|
70
|
-
if (workingDirectory.includes("'")) {
|
|
71
|
-
throw new Error("CodeM working directory cannot be represented in the Windows migration handoff");
|
|
72
|
-
}
|
|
73
|
-
const commandLine = `"${binaryPath}" service start --windows-migration-launcher-pid ${launcherPid}`;
|
|
74
|
-
const script = [
|
|
75
|
-
"$startup = ([wmiclass]'Win32_ProcessStartup').CreateInstance()",
|
|
76
|
-
"$startup.ShowWindow = 0",
|
|
77
|
-
`$result = ([wmiclass]'Win32_Process').Create('${commandLine}', '${workingDirectory}', $startup)`,
|
|
78
|
-
"if ($null -eq $result -or [int]$result.ReturnValue -ne 0) { exit 1 }"
|
|
79
|
-
].join("\r\n");
|
|
80
|
-
const powershellPath = path.win32.join(
|
|
81
|
-
env?.SystemRoot || String.raw`C:\Windows`,
|
|
82
|
-
"System32",
|
|
83
|
-
"WindowsPowerShell",
|
|
84
|
-
"v1.0",
|
|
85
|
-
"powershell.exe"
|
|
86
|
-
);
|
|
87
|
-
const bridge = spawnImpl(powershellPath, [
|
|
88
|
-
"-NoLogo",
|
|
89
|
-
"-NoProfile",
|
|
90
|
-
"-NonInteractive",
|
|
91
|
-
"-WindowStyle",
|
|
92
|
-
"Hidden",
|
|
93
|
-
"-EncodedCommand",
|
|
94
|
-
Buffer.from(script, "utf16le").toString("base64")
|
|
95
|
-
], {
|
|
96
|
-
env,
|
|
97
|
-
stdio: "ignore",
|
|
98
|
-
windowsHide: true
|
|
99
|
-
});
|
|
100
|
-
// Task Scheduler may place the launcher in a Job. CREATE_NEW_PROCESS_GROUP
|
|
101
|
-
// (`detached` in Node) does not escape that Job. Windows PowerShell delegates
|
|
102
|
-
// the exact worker creation to Win32_Process.Create, whose documented
|
|
103
|
-
// contract creates it outside the caller's Job; the short-lived bridge can
|
|
104
|
-
// then remain inside the Task without owning migration lifetime.
|
|
105
|
-
bridge.once?.("error", () => {});
|
|
106
|
-
bridge.unref?.();
|
|
107
|
-
return bridge;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function watchWindowsDaemonHostMigration({
|
|
111
|
-
binaryPath,
|
|
112
|
-
daemonPid,
|
|
113
|
-
launcherPid = process.pid,
|
|
114
|
-
argv = process.argv.slice(2),
|
|
115
|
-
env = process.env,
|
|
116
|
-
platform = process.platform,
|
|
117
|
-
codemHome = env?.CODEM_HOME || path.join(env?.USERPROFILE || os.homedir(), ".codem"),
|
|
118
|
-
fsImpl = fs,
|
|
119
|
-
spawnImpl = spawn,
|
|
120
|
-
pollIntervalMs = 250,
|
|
121
|
-
timeoutMs = 60_000,
|
|
122
|
-
nowImpl = Date.now,
|
|
123
|
-
setTimeoutImpl = setTimeout,
|
|
124
|
-
clearTimeoutImpl = clearTimeout
|
|
125
|
-
} = {}) {
|
|
126
|
-
if (!shouldWatchWindowsDaemonHostMigration({ platform, argv, env })) {
|
|
127
|
-
return () => {};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const capabilityPath = path.join(codemHome, WINDOWS_LEGACY_DAEMON_CAPABILITY_RELATIVE_PATH);
|
|
131
|
-
const startedAtMs = nowImpl();
|
|
132
|
-
let stopped = false;
|
|
133
|
-
let timer = null;
|
|
134
|
-
|
|
135
|
-
const stop = () => {
|
|
136
|
-
stopped = true;
|
|
137
|
-
if (timer !== null) {
|
|
138
|
-
clearTimeoutImpl(timer);
|
|
139
|
-
timer = null;
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
const schedule = () => {
|
|
143
|
-
if (stopped) return;
|
|
144
|
-
timer = setTimeoutImpl(() => {
|
|
145
|
-
timer = null;
|
|
146
|
-
void probe();
|
|
147
|
-
}, pollIntervalMs);
|
|
148
|
-
timer?.unref?.();
|
|
149
|
-
};
|
|
150
|
-
const probe = async () => {
|
|
151
|
-
if (stopped) return;
|
|
152
|
-
try {
|
|
153
|
-
const value = JSON.parse(await fsImpl.readFile(capabilityPath, "utf8"));
|
|
154
|
-
if (isCurrentWindowsLegacyDaemonCapability(value, { daemonPid, startedAtMs })) {
|
|
155
|
-
stop();
|
|
156
|
-
launchWindowsDaemonHostMigration({
|
|
157
|
-
binaryPath,
|
|
158
|
-
launcherPid,
|
|
159
|
-
env,
|
|
160
|
-
spawnImpl
|
|
161
|
-
});
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
// Capability publication is atomic and expected to be absent while the
|
|
166
|
-
// daemon starts. A malformed/unreadable file is never accepted; keep the
|
|
167
|
-
// bounded watcher alive so a later valid replacement can win.
|
|
168
|
-
}
|
|
169
|
-
if (nowImpl() - startedAtMs >= timeoutMs) {
|
|
170
|
-
stop();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
schedule();
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
void probe();
|
|
177
|
-
return stop;
|
|
178
|
-
}
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const WINDOWS_LEGACY_DAEMON_CAPABILITY = "shutdown_for_service_operation";
|
|
7
|
+
const WINDOWS_LEGACY_DAEMON_CAPABILITY_RELATIVE_PATH = path.join(
|
|
8
|
+
"runtime",
|
|
9
|
+
"legacy-daemon-control",
|
|
10
|
+
"capability.json"
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
// The dedicated Windows daemon Host owns the top-level console policy. Every
|
|
14
|
+
// child it can reach must stay hidden, and a local spawn option must not be able
|
|
15
|
+
// to undo that invariant. Interactive and non-Windows launches keep their
|
|
16
|
+
// options object untouched.
|
|
17
|
+
export function applyHostedWindowsChildOptions(
|
|
18
|
+
options = {},
|
|
19
|
+
{
|
|
20
|
+
// Hosted mode belongs to the current daemon process, not to the child env
|
|
21
|
+
// object supplied by an individual caller. Otherwise a caller could omit
|
|
22
|
+
// or replace `options.env` and accidentally bypass the console invariant.
|
|
23
|
+
env = process.env,
|
|
24
|
+
platform = process.platform
|
|
25
|
+
} = {}
|
|
26
|
+
) {
|
|
27
|
+
if (platform !== "win32" || env?.CODEM_DAEMON_HOSTED_WINDOWS !== "1") {
|
|
28
|
+
return options;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
...options,
|
|
33
|
+
windowsHide: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function shouldWatchWindowsDaemonHostMigration({
|
|
38
|
+
platform = process.platform,
|
|
39
|
+
argv = process.argv.slice(2),
|
|
40
|
+
env = process.env
|
|
41
|
+
} = {}) {
|
|
42
|
+
return platform === "win32" &&
|
|
43
|
+
argv[0] === "daemon" &&
|
|
44
|
+
env?.CODEM_DAEMON_HOSTED_WINDOWS !== "1" &&
|
|
45
|
+
typeof env?.CODEM_DAEMON_RUNTIME_CLIENT_INSTANCE_ID === "string" &&
|
|
46
|
+
env.CODEM_DAEMON_RUNTIME_CLIENT_INSTANCE_ID.trim() !== "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isCurrentWindowsLegacyDaemonCapability(
|
|
50
|
+
value,
|
|
51
|
+
{ daemonPid, startedAtMs } = {}
|
|
52
|
+
) {
|
|
53
|
+
return value?.schemaVersion === 1 &&
|
|
54
|
+
value?.protocolVersion === 1 &&
|
|
55
|
+
value?.capability === WINDOWS_LEGACY_DAEMON_CAPABILITY &&
|
|
56
|
+
Number(value?.rootIdentity?.pid) === daemonPid &&
|
|
57
|
+
Number(value?.publishedAtUnixMs) >= startedAtMs;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function launchWindowsDaemonHostMigration({
|
|
61
|
+
binaryPath,
|
|
62
|
+
launcherPid = process.pid,
|
|
63
|
+
env = process.env,
|
|
64
|
+
spawnImpl = spawn
|
|
65
|
+
} = {}) {
|
|
66
|
+
if (String(binaryPath).includes("'")) {
|
|
67
|
+
throw new Error("CodeM runtime path cannot be represented in the Windows migration handoff");
|
|
68
|
+
}
|
|
69
|
+
const workingDirectory = path.win32.dirname(binaryPath);
|
|
70
|
+
if (workingDirectory.includes("'")) {
|
|
71
|
+
throw new Error("CodeM working directory cannot be represented in the Windows migration handoff");
|
|
72
|
+
}
|
|
73
|
+
const commandLine = `"${binaryPath}" service start --windows-migration-launcher-pid ${launcherPid}`;
|
|
74
|
+
const script = [
|
|
75
|
+
"$startup = ([wmiclass]'Win32_ProcessStartup').CreateInstance()",
|
|
76
|
+
"$startup.ShowWindow = 0",
|
|
77
|
+
`$result = ([wmiclass]'Win32_Process').Create('${commandLine}', '${workingDirectory}', $startup)`,
|
|
78
|
+
"if ($null -eq $result -or [int]$result.ReturnValue -ne 0) { exit 1 }"
|
|
79
|
+
].join("\r\n");
|
|
80
|
+
const powershellPath = path.win32.join(
|
|
81
|
+
env?.SystemRoot || String.raw`C:\Windows`,
|
|
82
|
+
"System32",
|
|
83
|
+
"WindowsPowerShell",
|
|
84
|
+
"v1.0",
|
|
85
|
+
"powershell.exe"
|
|
86
|
+
);
|
|
87
|
+
const bridge = spawnImpl(powershellPath, [
|
|
88
|
+
"-NoLogo",
|
|
89
|
+
"-NoProfile",
|
|
90
|
+
"-NonInteractive",
|
|
91
|
+
"-WindowStyle",
|
|
92
|
+
"Hidden",
|
|
93
|
+
"-EncodedCommand",
|
|
94
|
+
Buffer.from(script, "utf16le").toString("base64")
|
|
95
|
+
], {
|
|
96
|
+
env,
|
|
97
|
+
stdio: "ignore",
|
|
98
|
+
windowsHide: true
|
|
99
|
+
});
|
|
100
|
+
// Task Scheduler may place the launcher in a Job. CREATE_NEW_PROCESS_GROUP
|
|
101
|
+
// (`detached` in Node) does not escape that Job. Windows PowerShell delegates
|
|
102
|
+
// the exact worker creation to Win32_Process.Create, whose documented
|
|
103
|
+
// contract creates it outside the caller's Job; the short-lived bridge can
|
|
104
|
+
// then remain inside the Task without owning migration lifetime.
|
|
105
|
+
bridge.once?.("error", () => {});
|
|
106
|
+
bridge.unref?.();
|
|
107
|
+
return bridge;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function watchWindowsDaemonHostMigration({
|
|
111
|
+
binaryPath,
|
|
112
|
+
daemonPid,
|
|
113
|
+
launcherPid = process.pid,
|
|
114
|
+
argv = process.argv.slice(2),
|
|
115
|
+
env = process.env,
|
|
116
|
+
platform = process.platform,
|
|
117
|
+
codemHome = env?.CODEM_HOME || path.join(env?.USERPROFILE || os.homedir(), ".codem"),
|
|
118
|
+
fsImpl = fs,
|
|
119
|
+
spawnImpl = spawn,
|
|
120
|
+
pollIntervalMs = 250,
|
|
121
|
+
timeoutMs = 60_000,
|
|
122
|
+
nowImpl = Date.now,
|
|
123
|
+
setTimeoutImpl = setTimeout,
|
|
124
|
+
clearTimeoutImpl = clearTimeout
|
|
125
|
+
} = {}) {
|
|
126
|
+
if (!shouldWatchWindowsDaemonHostMigration({ platform, argv, env })) {
|
|
127
|
+
return () => {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const capabilityPath = path.join(codemHome, WINDOWS_LEGACY_DAEMON_CAPABILITY_RELATIVE_PATH);
|
|
131
|
+
const startedAtMs = nowImpl();
|
|
132
|
+
let stopped = false;
|
|
133
|
+
let timer = null;
|
|
134
|
+
|
|
135
|
+
const stop = () => {
|
|
136
|
+
stopped = true;
|
|
137
|
+
if (timer !== null) {
|
|
138
|
+
clearTimeoutImpl(timer);
|
|
139
|
+
timer = null;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const schedule = () => {
|
|
143
|
+
if (stopped) return;
|
|
144
|
+
timer = setTimeoutImpl(() => {
|
|
145
|
+
timer = null;
|
|
146
|
+
void probe();
|
|
147
|
+
}, pollIntervalMs);
|
|
148
|
+
timer?.unref?.();
|
|
149
|
+
};
|
|
150
|
+
const probe = async () => {
|
|
151
|
+
if (stopped) return;
|
|
152
|
+
try {
|
|
153
|
+
const value = JSON.parse(await fsImpl.readFile(capabilityPath, "utf8"));
|
|
154
|
+
if (isCurrentWindowsLegacyDaemonCapability(value, { daemonPid, startedAtMs })) {
|
|
155
|
+
stop();
|
|
156
|
+
launchWindowsDaemonHostMigration({
|
|
157
|
+
binaryPath,
|
|
158
|
+
launcherPid,
|
|
159
|
+
env,
|
|
160
|
+
spawnImpl
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Capability publication is atomic and expected to be absent while the
|
|
166
|
+
// daemon starts. A malformed/unreadable file is never accepted; keep the
|
|
167
|
+
// bounded watcher alive so a later valid replacement can win.
|
|
168
|
+
}
|
|
169
|
+
if (nowImpl() - startedAtMs >= timeoutMs) {
|
|
170
|
+
stop();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
schedule();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
void probe();
|
|
177
|
+
return stop;
|
|
178
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-codem/codem-cli",
|
|
3
|
-
"version": "0.1.226-dev-
|
|
3
|
+
"version": "0.1.226-dev-p1-mac.100102447463.0.g9e29c9f",
|
|
4
4
|
"codemServiceManagementProtocol": 3,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "CodeM local CLI for Feishu-driven coding sessions",
|
|
@@ -27,15 +27,10 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@lark-codem/codem-core": "0.8.
|
|
30
|
+
"@lark-codem/codem-core": "0.8.58"
|
|
31
31
|
},
|
|
32
32
|
"optionalDependencies": {
|
|
33
|
-
"@lark-codem/codem-cli-darwin-arm64": "0.1.226-dev-
|
|
34
|
-
"@lark-codem/codem-cli-darwin-x64": "0.1.226-dev-trace-task.100102444781.0.ga44791f",
|
|
35
|
-
"@lark-codem/codem-cli-linux-x64": "0.1.226-dev-trace-task.100102444781.0.ga44791f",
|
|
36
|
-
"@lark-codem/codem-cli-linux-arm64": "0.1.226-dev-trace-task.100102444781.0.ga44791f",
|
|
37
|
-
"@lark-codem/codem-cli-win32-arm64": "0.1.226-dev-trace-task.100102444781.0.ga44791f",
|
|
38
|
-
"@lark-codem/codem-cli-win32-x64": "0.1.226-dev-trace-task.100102444781.0.ga44791f"
|
|
33
|
+
"@lark-codem/codem-cli-darwin-arm64": "0.1.226-dev-p1-mac.100102447463.0.g9e29c9f"
|
|
39
34
|
},
|
|
40
35
|
"engines": {
|
|
41
36
|
"node": ">=18"
|