@andyqiu/codeforge 0.6.13 → 0.7.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/bin/codeforge.mjs CHANGED
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * codeforge — opencode 零侵入扩展包的安装入口
4
+ * ADR:cli-deadcode-cleanup
4
5
  *
5
6
  * 一行命令安装:
6
7
  * npm install -g @andyqiu/codeforge
7
8
  *
8
9
  * 子命令:
9
- * install [--dry-run] [--enable-legacy-tools]
10
+ * install [--dry-run]
10
11
  * 把 CodeForge 单 bundle 注入到 opencode(写 opencode.json plugin entry)
11
12
  * uninstall 卸载(不动 opencode 自身)
12
13
  * list 探测 opencode 是否在机器上
13
14
  * version 打印版本
14
- * rollback [--target=<path>] [--dry-run]
15
- * 恢复到上一次 backup(auto_install 失败时手动救场)
16
15
  * runtime where [<path>] 打印当前项目对应的全局运行时目录
17
16
  * adr-init [--force] [--dry-run] [--write-prepare] [--no-pre-push]
18
17
  * 把 ADR 体系(hooks + scripts + 模板)下发到当前 git 项目
@@ -25,8 +24,7 @@
25
24
  */
26
25
 
27
26
  import { spawnSync } from "node:child_process"
28
- import { copyFileSync, existsSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs"
29
- import { homedir } from "node:os"
27
+ import { existsSync, readFileSync } from "node:fs"
30
28
  import * as path from "node:path"
31
29
  import * as url from "node:url"
32
30
 
@@ -94,15 +92,12 @@ function parseArgs(argv) {
94
92
 
95
93
  // ────────────────────────────────────────────────────────────────────
96
94
  // opencode installer:薄壳,统一调 install.mjs(Node ESM 跨平台)
97
- // ADR:unify-install-to-node-mjs
98
- // 自举兼容:优先找 install.mjs(v0.6+),fallback install.sh(v0.5 过渡期)
99
- // 用于 upgrade 命令:旧版 CLI 调新包时,新包里只有 install.mjs
95
+ // ADR:unify-install-to-node-mjs / ADR:cli-deadcode-cleanup
96
+ // install.sh/.ps1 已合并进 install.mjs,installer 永远走 node。
100
97
  // ────────────────────────────────────────────────────────────────────
101
98
  function resolveInstallerScript() {
102
99
  const mjsScript = path.join(REPO_ROOT, "install.mjs")
103
- if (existsSync(mjsScript)) return { script: mjsScript, useNode: true }
104
- const shScript = path.join(REPO_ROOT, "install.sh")
105
- if (existsSync(shScript)) return { script: shScript, useNode: false }
100
+ if (existsSync(mjsScript)) return { script: mjsScript }
106
101
  return null
107
102
  }
108
103
 
@@ -112,24 +107,14 @@ function installOpencode({ scope, dryRun, extraArgs, quiet = false, verbose = fa
112
107
  err(`installer 脚本不存在:${path.join(REPO_ROOT, "install.mjs")}`)
113
108
  return 2
114
109
  }
115
- const { script, useNode } = resolved
116
- let cmd, args
117
- if (useNode) {
118
- cmd = process.execPath
119
- args = [script]
120
- if (scope === "global") args.push("--global")
121
- if (dryRun) args.push("--dry-run")
122
- if (verbose) args.push("--verbose")
123
- if (extraArgs) args.push(...extraArgs)
124
- } else {
125
- // fallback:旧版包里只有 install.sh(v0.5 过渡期)
126
- cmd = "bash"
127
- args = [script]
128
- if (scope === "global") args.push("--global")
129
- if (dryRun) args.push("--dry-run")
130
- if (extraArgs) args.push(...extraArgs)
131
- }
132
- if (!quiet) log(`opencode: ${useNode ? "node" : "bash"} ${args.join(" ")}`)
110
+ const { script } = resolved
111
+ const cmd = process.execPath
112
+ const args = [script]
113
+ if (scope === "global") args.push("--global")
114
+ if (dryRun) args.push("--dry-run")
115
+ if (verbose) args.push("--verbose")
116
+ if (extraArgs) args.push(...extraArgs)
117
+ if (!quiet) log(`opencode: node ${args.join(" ")}`)
133
118
  const r = spawnSync(cmd, args, { stdio: quiet ? "pipe" : "inherit", cwd: REPO_ROOT })
134
119
  if (quiet && r.status !== 0) {
135
120
  if (r.stderr) process.stderr.write(r.stderr)
@@ -144,15 +129,11 @@ function uninstallOpencode({ scope }) {
144
129
  err(`installer 脚本不存在:${path.join(REPO_ROOT, "install.mjs")}`)
145
130
  return 2
146
131
  }
147
- const { script, useNode } = resolved
148
- if (!useNode && process.platform === "win32") {
149
- err("Windows 下旧版 install.sh 不支持,请重新安装:npm install -g @andyqiu/codeforge")
150
- return 2
151
- }
152
- const cmd = useNode ? process.execPath : "bash"
132
+ const { script } = resolved
133
+ const cmd = process.execPath
153
134
  const args = [script, "--uninstall"]
154
135
  if (scope === "global") args.push("--global")
155
- log(`opencode uninstall: ${useNode ? "node" : "bash"} ${args.join(" ")}`)
136
+ log(`opencode uninstall: node ${args.join(" ")}`)
156
137
  const r = spawnSync(cmd, args, { stdio: "inherit", cwd: REPO_ROOT })
157
138
  return r.status ?? 1
158
139
  }
@@ -175,7 +156,6 @@ function cmdInstall(args) {
175
156
  const autoSkipBuild = fromNpm
176
157
  const verbose = !!args.flags.verbose
177
158
  const extraArgs = []
178
- if (args.flags["enable-legacy-tools"]) extraArgs.push("--enable-legacy-tools")
179
159
  if (args.flags["skip-build"] || autoSkipBuild) extraArgs.push("--skip-build")
180
160
 
181
161
  // npm 场景:dist/index.js 必须已经被 prepack 打好;找不到就直接报错并提示
@@ -222,80 +202,6 @@ function cmdList() {
222
202
  return 0
223
203
  }
224
204
 
225
- // ────────────────────────────────────────────────────────────────────
226
- // 子命令:rollback(ADR-0047)—— 把最近的 .bak.<ver> 文件恢复回 index.js
227
- // ────────────────────────────────────────────────────────────────────
228
- function defaultBundlePath() {
229
- const candidates = [path.join(homedir(), ".config", "opencode", "codeforge", "index.js")]
230
- if (process.platform === "win32") {
231
- if (process.env.APPDATA) candidates.push(path.join(process.env.APPDATA, "opencode", "codeforge", "index.js"))
232
- if (process.env.LOCALAPPDATA) candidates.push(path.join(process.env.LOCALAPPDATA, "opencode", "codeforge", "index.js"))
233
- }
234
- for (const c of candidates) {
235
- if (existsSync(c)) return c
236
- }
237
- return candidates[0]
238
- }
239
-
240
- function cmdRollback(args) {
241
- const target = typeof args.flags.target === "string" ? args.flags.target : defaultBundlePath()
242
- const dryRun = !!args.flags["dry-run"] || !!args.flags.dryRun
243
-
244
- log(`CodeForge rollback —— 恢复到上一次 backup`)
245
- log(` target : ${target}`)
246
- log(` dry-run : ${dryRun}`)
247
- hr()
248
-
249
- const dir = path.dirname(target)
250
- const base = path.basename(target)
251
- if (!existsSync(dir)) {
252
- err(`目标目录不存在:${dir}`)
253
- err(`提示:先跑 codeforge install 安装一次,自动更新失败时再 rollback。`)
254
- return 1
255
- }
256
-
257
- let entries
258
- try {
259
- entries = readdirSync(dir)
260
- } catch (e) {
261
- err(`读取目录失败:${e.message}`)
262
- return 1
263
- }
264
- const prefix = `${base}.bak.`
265
- const backups = entries
266
- .filter((f) => f.startsWith(prefix))
267
- .map((f) => {
268
- const full = path.join(dir, f)
269
- let mtimeMs = 0
270
- try { mtimeMs = statSync(full).mtimeMs } catch {}
271
- return { full, name: f, mtimeMs, ver: f.substring(prefix.length) }
272
- })
273
- .sort((a, b) => b.mtimeMs - a.mtimeMs)
274
-
275
- if (backups.length === 0) {
276
- err(`找不到任何 backup(pattern: ${prefix}*)`)
277
- err(`提示:自动更新尚未执行过 / backup 已被清理。可跑 npm install -g @andyqiu/codeforge 重装。`)
278
- return 1
279
- }
280
-
281
- const latest = backups[0]
282
- log(`找到 ${backups.length} 个 backup,最新:${latest.name}`)
283
- if (dryRun) {
284
- ok(`[dry-run] 会执行:cp ${latest.full} → ${target}`)
285
- return 0
286
- }
287
-
288
- try {
289
- copyFileSync(latest.full, target)
290
- } catch (e) {
291
- err(`恢复失败:${e.message}`)
292
- return 1
293
- }
294
- ok(`已恢复 ${latest.name} → ${base}(版本:${latest.ver})`)
295
- ok(`下次启动 opencode 即可生效`)
296
- return 0
297
- }
298
-
299
205
  // ────────────────────────────────────────────────────────────────────
300
206
  // 子命令:upgrade —— 升级到 npm latest 并重新 install --global
301
207
  // ────────────────────────────────────────────────────────────────────
@@ -366,17 +272,6 @@ async function cmdUpgrade(args) {
366
272
  return 1
367
273
  }
368
274
 
369
- // npm install 成功后刷新 opencode plugin 缓存,确保重启后加载新版本
370
- try {
371
- const cacheDir = path.join(homedir(), ".cache", "opencode", "packages", "@andyqiu", "teamkit@latest")
372
- if (existsSync(cacheDir)) {
373
- rmSync(cacheDir, { recursive: true, force: true })
374
- log("已清除 opencode plugin 缓存,重启 opencode 后生效")
375
- }
376
- } catch {
377
- warn("清除 opencode 缓存失败(非致命,可手动重启 opencode)")
378
- }
379
-
380
275
  const code = installOpencode({ scope: "global", dryRun: false, extraArgs: ["--skip-build"], quiet: true })
381
276
  if (code !== 0) {
382
277
  err(`install --global 失败 (exit=${code})`)
@@ -512,7 +407,6 @@ function cmdHelp() {
512
407
  codeforge uninstall
513
408
  codeforge list
514
409
  codeforge version
515
- codeforge rollback [--target=<path>] [--dry-run] # 恢复最近 backup(auto_install 失败救场)
516
410
  codeforge upgrade|update [--dry-run] # 升级到 npm latest 并重新 install --global
517
411
  codeforge doctor [--project] # 诊断 CodeForge 安装健康状态(manifest/disk/source 三方比对)
518
412
  codeforge runtime where [<path>] # 打印当前项目的全局运行时目录
@@ -525,8 +419,6 @@ function cmdHelp() {
525
419
  参数:
526
420
  --dry-run 只打印操作,不真改
527
421
  --skip-build 跳过 npm run build(已有 dist/index.js 时增量装)
528
- --enable-legacy-tools 启用旧版 file-based tools(默认禁用,避免 zod 跨实例 bug)
529
- --target rollback 子命令:指定 index.js 路径(默认自动探测)
530
422
 
531
423
  参数(adr-init 专用):
532
424
  --force 覆盖已存在文件(覆盖前自动 .bak.<ts> 备份)
@@ -573,8 +465,6 @@ async function main() {
573
465
  case "-v":
574
466
  console.log(getVersion())
575
467
  return 0
576
- case "rollback":
577
- return cmdRollback(args)
578
468
  case "upgrade":
579
469
  case "update":
580
470
  return cmdUpgrade(args)
@@ -31,7 +31,6 @@ install:
31
31
  # 哪些目录拷贝(用户可自定义)
32
32
  copy:
33
33
  - workflows
34
- - context-templates
35
34
  # 安装目标
36
35
  targets:
37
36
  project: ".opencode"
package/codeforge.json CHANGED
@@ -152,7 +152,7 @@
152
152
  },
153
153
 
154
154
  "update": {
155
- "_doc": "自动更新检查(ADR-0047)。opencode 启动时由 plugins/update-checker.ts 后台拉 npm registry latest,auto_install=true 时自动替换 ~/.config/opencode/codeforge/index.js(下次启动生效),失败/离线静默回退到 GitHub Releases。手动回滚:codeforge rollback。",
155
+ "_doc": "自动更新检查(ADR-0047)。opencode 启动时由 plugins/update-checker.ts 后台拉 npm registry latest,auto_install=true 时自动替换 ~/.config/opencode/codeforge/index.js(下次启动生效),失败/离线静默回退到 GitHub Releases。手动降级:npm install -g @andyqiu/codeforge@<旧版本>。",
156
156
  "auto_check_enabled": true,
157
157
  "interval_hours": 6,
158
158
  "package": "@andyqiu/codeforge",
@@ -40,7 +40,7 @@ codeforge 元数据(opencode 不读,由 plugins / workflow-engine 解析)
40
40
  或单行 `;` 分隔:
41
41
 
42
42
  ```
43
- /parallel 改 README; 同步 PRD; 跑 phase4-check
43
+ /parallel 改 README; 同步 PRD; 跑测试
44
44
  ```
45
45
 
46
46
  走 workflow(`parallel-explore.yaml`):
package/dist/index.js CHANGED
@@ -20166,35 +20166,21 @@ var toolPolicyServer = async (ctx) => {
20166
20166
  var handler19 = toolPolicyServer;
20167
20167
 
20168
20168
  // plugins/update-checker.ts
20169
- import { existsSync as existsSync6, rmSync } from "node:fs";
20170
- import { homedir as homedir9 } from "node:os";
20171
- import { join as join25 } from "node:path";
20169
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
20170
+ import { homedir as homedir8 } from "node:os";
20171
+ import { dirname as dirname15, join as join25 } from "node:path";
20172
20172
  import { spawnSync as spawnSync2 } from "node:child_process";
20173
20173
 
20174
20174
  // lib/update-checker-impl.ts
20175
- import { createHash as createHash4 } from "node:crypto";
20176
- import {
20177
- copyFileSync,
20178
- existsSync as existsSync5,
20179
- mkdirSync as mkdirSync3,
20180
- mkdtempSync,
20181
- readFileSync as readFileSync5,
20182
- readdirSync as readdirSync3,
20183
- renameSync,
20184
- statSync as statSync4,
20185
- unlinkSync,
20186
- writeFileSync as writeFileSync2
20187
- } from "node:fs";
20188
- import { homedir as homedir8, tmpdir } from "node:os";
20175
+ import { readFileSync as readFileSync5 } from "node:fs";
20189
20176
  import { dirname as dirname14, join as join24 } from "node:path";
20190
20177
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20191
20178
  import * as https from "node:https";
20192
- import * as zlib from "node:zlib";
20193
20179
 
20194
20180
  // lib/version-injected.ts
20195
20181
  function getInjectedVersion() {
20196
20182
  try {
20197
- const v = "0.6.13";
20183
+ const v = "0.7.1";
20198
20184
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
20199
20185
  return v;
20200
20186
  }
@@ -20205,48 +20191,6 @@ function getInjectedVersion() {
20205
20191
  }
20206
20192
 
20207
20193
  // lib/update-checker-impl.ts
20208
- async function checkUpdateOnce(opts) {
20209
- const local = opts.localVersion ?? readLocalVersion();
20210
- const cacheFile = opts.cacheFile ?? defaultCacheFile();
20211
- const now = (opts.now ?? Date.now)();
20212
- if (!opts.forceFresh) {
20213
- const cached = readCache(cacheFile);
20214
- if (cached && cached.repo === opts.repo && now - cached.checkedAt < opts.intervalMs) {
20215
- return {
20216
- hasUpdate: cmpVersion(local, cached.remote) < 0,
20217
- local,
20218
- remote: cached.remote,
20219
- fromCache: true,
20220
- cacheAgeMs: now - cached.checkedAt
20221
- };
20222
- }
20223
- }
20224
- const fetcher = opts.fetcher ?? fetchLatestTagFromGitHub;
20225
- let remote = null;
20226
- let fetchErr;
20227
- try {
20228
- remote = await fetcher(opts.repo);
20229
- } catch (e) {
20230
- fetchErr = e instanceof Error ? e.message : String(e);
20231
- }
20232
- if (remote === null) {
20233
- writeCache(cacheFile, { checkedAt: now, remote: local, repo: opts.repo });
20234
- return {
20235
- hasUpdate: false,
20236
- local,
20237
- remote: "(no release)",
20238
- fromCache: false,
20239
- error: fetchErr ?? "no_release"
20240
- };
20241
- }
20242
- writeCache(cacheFile, { checkedAt: now, remote, repo: opts.repo });
20243
- return {
20244
- hasUpdate: cmpVersion(local, remote) < 0,
20245
- local,
20246
- remote,
20247
- fromCache: false
20248
- };
20249
- }
20250
20194
  function cmpVersion(a, b) {
20251
20195
  const pa = parseSemver(a);
20252
20196
  const pb = parseSemver(b);
@@ -20290,97 +20234,6 @@ function readLocalVersion() {
20290
20234
  return "0.0.0";
20291
20235
  }
20292
20236
  }
20293
- function defaultCacheDir() {
20294
- return process.env["CODEFORGE_CACHE_DIR"] ?? join24(homedir8(), ".cache", "codeforge");
20295
- }
20296
- function defaultCacheFile() {
20297
- return join24(defaultCacheDir(), "update-check.json");
20298
- }
20299
- function readCache(file) {
20300
- try {
20301
- if (!existsSync5(file))
20302
- return null;
20303
- const raw = readFileSync5(file, "utf8");
20304
- const obj = JSON.parse(raw);
20305
- if (obj && typeof obj === "object" && typeof obj.checkedAt === "number" && typeof obj.remote === "string" && typeof obj.repo === "string") {
20306
- return obj;
20307
- }
20308
- return null;
20309
- } catch {
20310
- return null;
20311
- }
20312
- }
20313
- function writeCache(file, entry) {
20314
- try {
20315
- mkdirSync3(dirname14(file), { recursive: true });
20316
- writeFileSync2(file, JSON.stringify(entry, null, 2), "utf8");
20317
- } catch {}
20318
- }
20319
- var GITHUB_API_HOST = "api.github.com";
20320
- var MAX_REDIRECTS = 5;
20321
- function fetchLatestTagFromGitHub(repo) {
20322
- return getJsonWithRedirect(`https://${GITHUB_API_HOST}/repos/${repo}/releases/latest`, MAX_REDIRECTS).then((body) => {
20323
- if (body === null)
20324
- return null;
20325
- try {
20326
- const json = JSON.parse(body);
20327
- return typeof json.tag_name === "string" && json.tag_name.length > 0 ? json.tag_name : null;
20328
- } catch (e) {
20329
- throw e instanceof Error ? e : new Error(String(e));
20330
- }
20331
- });
20332
- }
20333
- function getJsonWithRedirect(url2, hopsLeft) {
20334
- return new Promise((resolve17, reject) => {
20335
- const u = new URL(url2);
20336
- const headers = {
20337
- "User-Agent": "codeforge-update-checker",
20338
- Accept: "application/vnd.github+json"
20339
- };
20340
- if (process.env["GITHUB_TOKEN"]) {
20341
- headers["Authorization"] = `Bearer ${process.env["GITHUB_TOKEN"]}`;
20342
- }
20343
- const req = https.request({
20344
- host: u.hostname,
20345
- path: u.pathname + (u.search ?? ""),
20346
- method: "GET",
20347
- headers,
20348
- timeout: 5000
20349
- }, (res) => {
20350
- const status = res.statusCode ?? 0;
20351
- if (status >= 300 && status < 400 && res.headers.location) {
20352
- res.resume();
20353
- if (hopsLeft <= 0) {
20354
- reject(new Error("too_many_redirects"));
20355
- return;
20356
- }
20357
- const next = new URL(res.headers.location, url2).toString();
20358
- getJsonWithRedirect(next, hopsLeft - 1).then(resolve17, reject);
20359
- return;
20360
- }
20361
- if (status === 404) {
20362
- res.resume();
20363
- resolve17(null);
20364
- return;
20365
- }
20366
- if (status >= 400) {
20367
- res.resume();
20368
- reject(new Error(`http_${status}`));
20369
- return;
20370
- }
20371
- let body = "";
20372
- res.setEncoding("utf8");
20373
- res.on("data", (chunk) => body += chunk);
20374
- res.on("end", () => resolve17(body));
20375
- });
20376
- req.on("timeout", () => {
20377
- req.destroy();
20378
- reject(new Error("timeout"));
20379
- });
20380
- req.on("error", reject);
20381
- req.end();
20382
- });
20383
- }
20384
20237
  async function fetchLatestFromNpm(opts) {
20385
20238
  const registry = (opts.registry ?? "https://registry.npmjs.org").replace(/\/+$/, "");
20386
20239
  const channel = opts.channel ?? "latest";
@@ -20449,256 +20302,75 @@ function defaultHttpFetcher(url2, timeoutMs) {
20449
20302
  req.end();
20450
20303
  });
20451
20304
  }
20452
- async function downloadAndExtractBundle(opts) {
20453
- const tmpRoot = opts.tmpDir ?? mkdtempSync(join24(tmpdir(), "codeforge-update-"));
20454
- mkdirSync3(tmpRoot, { recursive: true });
20455
- const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
20456
- const tarballBuf = await fetcher(opts.tarballUrl);
20457
- verifyIntegrity(tarballBuf, opts.expectedIntegrity);
20458
- const tarBuf = zlib.gunzipSync(tarballBuf);
20459
- extractTarToDir(tarBuf, tmpRoot);
20460
- const bundlePath = join24(tmpRoot, "package", "dist", "index.js");
20461
- if (!existsSync5(bundlePath)) {
20462
- throw new Error(`bundle_not_found: ${bundlePath}`);
20463
- }
20464
- return { bundlePath, extractDir: tmpRoot };
20465
- }
20466
- function verifyIntegrity(buf, expected) {
20467
- const m = /^([a-z0-9]+)-(.+)$/i.exec(expected.trim());
20468
- if (!m) {
20469
- throw new Error(`integrity_format_invalid: ${expected}`);
20470
- }
20471
- const algo = m[1].toLowerCase();
20472
- const expectedB64 = m[2];
20473
- if (algo !== "sha512" && algo !== "sha256" && algo !== "sha384") {
20474
- throw new Error(`integrity_algo_unsupported: ${algo}`);
20475
- }
20476
- const actualB64 = createHash4(algo).update(buf).digest("base64");
20477
- if (actualB64 !== expectedB64) {
20478
- throw new Error(`integrity_mismatch: expected ${algo}=${expectedB64.slice(0, 16)}... got ${actualB64.slice(0, 16)}...`);
20479
- }
20480
- }
20481
- function extractTarToDir(tarBuf, destRoot) {
20482
- let offset = 0;
20483
- while (offset + 512 <= tarBuf.length) {
20484
- const header = tarBuf.subarray(offset, offset + 512);
20485
- if (header.every((b) => b === 0))
20486
- break;
20487
- const nameRaw = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
20488
- if (!nameRaw) {
20489
- offset += 512;
20490
- continue;
20491
- }
20492
- const sizeOctal = header.subarray(124, 124 + 12).toString("ascii").replace(/\0.*$/, "").trim();
20493
- const size = sizeOctal ? parseInt(sizeOctal, 8) : 0;
20494
- const typeFlag = header.subarray(156, 157).toString("ascii");
20495
- const prefixRaw = header.subarray(345, 345 + 155).toString("utf8").replace(/\0.*$/, "");
20496
- const fullName = prefixRaw ? `${prefixRaw}/${nameRaw}` : nameRaw;
20497
- offset += 512;
20498
- if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
20499
- const fileBuf = tarBuf.subarray(offset, offset + size);
20500
- const dest = join24(destRoot, fullName);
20501
- mkdirSync3(dirname14(dest), { recursive: true });
20502
- writeFileSync2(dest, fileBuf);
20503
- } else if (typeFlag === "5") {
20504
- mkdirSync3(join24(destRoot, fullName), { recursive: true });
20505
- }
20506
- offset += Math.ceil(size / 512) * 512;
20507
- }
20508
- }
20509
- function defaultBinaryFetcher(url2) {
20510
- return downloadBinary(url2, 3);
20511
- }
20512
- function downloadBinary(url2, hopsLeft) {
20513
- return new Promise((resolve17, reject) => {
20514
- const u = new URL(url2);
20515
- const req = https.request({
20516
- host: u.hostname,
20517
- port: u.port || undefined,
20518
- path: u.pathname + (u.search ?? ""),
20519
- method: "GET",
20520
- headers: { "User-Agent": "codeforge-update-checker" },
20521
- timeout: 30000
20522
- }, (res) => {
20523
- const status = res.statusCode ?? 0;
20524
- if (status >= 300 && status < 400 && res.headers.location) {
20525
- res.resume();
20526
- if (hopsLeft <= 0) {
20527
- reject(new Error("too_many_redirects"));
20528
- return;
20529
- }
20530
- const next = new URL(res.headers.location, url2).toString();
20531
- downloadBinary(next, hopsLeft - 1).then(resolve17, reject);
20532
- return;
20533
- }
20534
- if (status >= 400) {
20535
- res.resume();
20536
- reject(new Error(`http_${status}`));
20537
- return;
20538
- }
20539
- const chunks = [];
20540
- res.on("data", (chunk) => chunks.push(chunk));
20541
- res.on("end", () => resolve17(Buffer.concat(chunks)));
20542
- });
20543
- req.on("timeout", () => {
20544
- req.destroy();
20545
- reject(new Error("timeout"));
20546
- });
20547
- req.on("error", reject);
20548
- req.end();
20549
- });
20305
+
20306
+ // plugins/update-checker.ts
20307
+ var PLUGIN_NAME20 = "update-checker";
20308
+ var PLUGIN_VERSION = "3.0.0";
20309
+ var _updateCheckStarted = false;
20310
+ function getCacheFile() {
20311
+ return join25(process.env["CODEFORGE_CACHE_DIR"] ?? join25(homedir8(), ".cache", "codeforge"), "update-check.json");
20550
20312
  }
20551
- function atomicReplaceBundle(opts) {
20552
- const { source, target, oldVersion } = opts;
20553
- const keep = opts.keepBackups ?? 3;
20554
- if (!existsSync5(source)) {
20555
- throw new Error(`atomic_source_missing: ${source}`);
20556
- }
20557
- mkdirSync3(dirname14(target), { recursive: true });
20558
- const newPath = `${target}.new`;
20559
- const backupPath = `${target}.bak.${oldVersion}`;
20560
- let strategy = "rename";
20313
+ function readLastInstalledVersion() {
20561
20314
  try {
20562
- copyFileSync(source, newPath);
20563
- if (existsSync5(target)) {
20564
- try {
20565
- renameSync(target, backupPath);
20566
- } catch (e) {
20567
- const code = e.code;
20568
- if (code === "EBUSY" || code === "EPERM" || code === "EACCES") {
20569
- copyFileSync(target, backupPath);
20570
- copyFileSync(newPath, target);
20571
- try {
20572
- unlinkSync(newPath);
20573
- } catch {}
20574
- strategy = "copy_fallback";
20575
- cleanupOldBackups(target, keep);
20576
- return { backupPath, strategy };
20577
- }
20578
- throw e;
20579
- }
20580
- }
20581
- try {
20582
- renameSync(newPath, target);
20583
- } catch (e) {
20584
- const code = e.code;
20585
- if (code === "EBUSY" || code === "EPERM" || code === "EACCES") {
20586
- copyFileSync(newPath, target);
20587
- try {
20588
- unlinkSync(newPath);
20589
- } catch {}
20590
- strategy = "copy_fallback";
20591
- } else {
20592
- throw e;
20593
- }
20594
- }
20595
- cleanupOldBackups(target, keep);
20596
- return { backupPath, strategy };
20597
- } catch (e) {
20598
- try {
20599
- if (existsSync5(newPath))
20600
- unlinkSync(newPath);
20601
- } catch {}
20602
- throw e;
20315
+ const f = getCacheFile();
20316
+ if (!existsSync5(f))
20317
+ return null;
20318
+ const o = JSON.parse(readFileSync6(f, "utf8"));
20319
+ return typeof o.installedVersion === "string" ? o.installedVersion : null;
20320
+ } catch {
20321
+ return null;
20603
20322
  }
20604
20323
  }
20605
- function cleanupOldBackups(target, keep) {
20606
- if (keep <= 0)
20607
- return;
20324
+ function writeLastInstalledVersion(v) {
20608
20325
  try {
20609
- const dir = dirname14(target);
20610
- const base = target.substring(dir.length + 1);
20611
- const prefix = `${base}.bak.`;
20612
- const all = readdirSync3(dir).filter((f) => f.startsWith(prefix)).map((f) => {
20613
- const full = join24(dir, f);
20614
- let mtimeMs = 0;
20615
- try {
20616
- mtimeMs = statSync4(full).mtimeMs;
20617
- } catch {}
20618
- return { full, mtimeMs };
20619
- }).sort((a, b) => b.mtimeMs - a.mtimeMs);
20620
- const toRemove = all.slice(keep);
20621
- for (const item of toRemove) {
20622
- try {
20623
- unlinkSync(item.full);
20624
- } catch {}
20625
- }
20326
+ const f = getCacheFile();
20327
+ mkdirSync3(dirname15(f), { recursive: true });
20328
+ writeFileSync2(f, JSON.stringify({ installedVersion: v }, null, 2), "utf8");
20626
20329
  } catch {}
20627
20330
  }
20628
- function loadCompatibility(opts) {
20331
+ function resolveNodeBin() {
20332
+ const w = process.platform === "win32";
20629
20333
  try {
20630
- let file = opts?.file;
20631
- if (!file) {
20632
- const root = opts?.cwd ?? inferPluginRoot();
20633
- if (!root)
20634
- return null;
20635
- file = join24(root, "compatibility.json");
20636
- }
20637
- if (!existsSync5(file))
20638
- return null;
20639
- const raw = readFileSync5(file, "utf8");
20640
- const obj = JSON.parse(raw);
20641
- if (!obj || typeof obj !== "object")
20642
- return null;
20643
- const oc = obj["opencode"];
20644
- if (!oc || typeof oc !== "object")
20645
- return null;
20646
- const o = oc;
20647
- const min_version = typeof o["min_version"] === "string" ? o["min_version"] : "";
20648
- const max_version = typeof o["max_version"] === "string" ? o["max_version"] : null;
20649
- const tested_versions = Array.isArray(o["tested_versions"]) ? o["tested_versions"].filter((v) => typeof v === "string") : [];
20650
- if (!min_version)
20651
- return null;
20652
- return { min_version, max_version, tested_versions };
20653
- } catch {
20654
- return null;
20334
+ const r = spawnSync2(w ? "where" : "which", ["node"], { encoding: "utf8", stdio: "pipe" });
20335
+ if (r.status === 0 && r.stdout.trim())
20336
+ return r.stdout.trim().split(/\r?\n/)[0].trim();
20337
+ } catch {}
20338
+ for (const c of w ? ["C:\\Program Files\\nodejs\\node.exe"] : ["/usr/local/bin/node", "/usr/bin/node", "/opt/homebrew/bin/node", "/opt/homebrew/opt/node/bin/node"]) {
20339
+ try {
20340
+ if (spawnSync2(c, ["--version"], { stdio: "pipe", timeout: 2000 }).status === 0)
20341
+ return c;
20342
+ } catch {}
20655
20343
  }
20344
+ return process.execPath;
20656
20345
  }
20657
- function inferPluginRoot() {
20346
+ function resolveNpmBin() {
20347
+ const w = process.platform === "win32";
20658
20348
  try {
20659
- const here = fileURLToPath2(import.meta.url);
20660
- return dirname14(dirname14(here));
20661
- } catch {
20662
- return null;
20349
+ const r = spawnSync2(w ? "where" : "which", ["npm"], { encoding: "utf8", stdio: "pipe" });
20350
+ if (r.status === 0 && r.stdout.trim())
20351
+ return r.stdout.trim().split(/\r?\n/)[0].trim();
20352
+ } catch {}
20353
+ for (const c of w ? ["C:\\Program Files\\nodejs\\npm.cmd"] : ["/usr/local/bin/npm", "/usr/bin/npm", "/opt/homebrew/bin/npm"]) {
20354
+ try {
20355
+ if (spawnSync2(c, ["--version"], { stdio: "pipe", timeout: 2000 }).status === 0)
20356
+ return c;
20357
+ } catch {}
20663
20358
  }
20359
+ return "npm";
20664
20360
  }
20665
- function compareOpencodeVersion(opts) {
20666
- const cur = (opts.currentOpencodeVer ?? "").trim();
20667
- const compat = opts.compat;
20668
- if (!compat) {
20669
- return { status: "unknown", message: "compatibility.json 不可用,跳过版本校验" };
20670
- }
20671
- if (!cur || cur === "unknown") {
20672
- return { status: "unknown", message: "无法检测 opencode 版本(OPENCODE_VERSION 未设)" };
20673
- }
20674
- if (compat.tested_versions.includes(cur)) {
20675
- return {
20676
- status: "supported",
20677
- message: `opencode ${cur} 在 CodeForge 已测试版本列表内`
20678
- };
20679
- }
20680
- if (cmpVersion(cur, compat.min_version) < 0) {
20681
- return {
20682
- status: "below_min",
20683
- message: `opencode ${cur} 低于 CodeForge 要求的最低版本 ${compat.min_version}`
20684
- };
20685
- }
20686
- if (compat.max_version && cmpVersion(cur, compat.max_version) > 0) {
20687
- return {
20688
- status: "above_max",
20689
- message: `opencode ${cur} 高于 CodeForge 已知兼容上限 ${compat.max_version}`
20690
- };
20691
- }
20692
- const testedDisplay = compat.tested_versions.length > 0 ? compat.tested_versions.join(", ") : "(空)";
20693
- return {
20694
- status: "untested",
20695
- message: `opencode ${cur} 未在 CodeForge 已测试版本列表中(已测:${testedDisplay})`
20696
- };
20361
+ function getNpmGlobalRoot(npmBin) {
20362
+ try {
20363
+ const r = spawnSync2(npmBin, ["root", "-g"], {
20364
+ encoding: "utf8",
20365
+ stdio: "pipe",
20366
+ timeout: 1e4,
20367
+ shell: process.platform === "win32"
20368
+ });
20369
+ if (r.status === 0 && r.stdout.trim())
20370
+ return r.stdout.trim();
20371
+ } catch {}
20372
+ return null;
20697
20373
  }
20698
-
20699
- // plugins/update-checker.ts
20700
- var PLUGIN_NAME20 = "update-checker";
20701
- var PLUGIN_VERSION = "2.0.0";
20702
20374
  logLifecycle(PLUGIN_NAME20, "import", { version: PLUGIN_VERSION });
20703
20375
  var updateCheckerServer = async (ctx) => {
20704
20376
  const yieldResult = shouldYieldToLocalPlugin({ directory: ctx.directory });
@@ -20722,45 +20394,20 @@ var updateCheckerServer = async (ctx) => {
20722
20394
  logLifecycle(PLUGIN_NAME20, "activate", {
20723
20395
  version: PLUGIN_VERSION,
20724
20396
  auto_check_enabled: u.auto_check_enabled,
20725
- interval_hours: u.interval_hours,
20726
20397
  package: u.package,
20727
20398
  registry: u.registry,
20728
20399
  channel: u.channel,
20729
- auto_install: u.auto_install,
20730
- backup_keep: u.backup_keep,
20731
- repo_fallback: u.repo,
20732
- config_source: "codeforge.json"
20733
- });
20734
- await safeAsync(PLUGIN_NAME20, "opencode_version_check", async () => {
20735
- const compat = loadCompatibility();
20736
- const opencodeVer = detectOpencodeVersion();
20737
- const verdict = compareOpencodeVersion({
20738
- currentOpencodeVer: opencodeVer,
20739
- compat
20740
- });
20741
- safeWriteLog(PLUGIN_NAME20, {
20742
- level: "info",
20743
- msg: "opencode_version_check",
20744
- opencodeVer,
20745
- status: verdict.status,
20746
- verdict_message: verdict.message
20747
- });
20748
- if (verdict.status === "untested" || verdict.status === "above_max") {
20749
- await postToast(ctx, `[CodeForge] ⚠ ${verdict.message}
20750
- 遇到问题请在 issue 中附带本提示`);
20751
- } else if (verdict.status === "below_min") {
20752
- const minVer = compat?.min_version ?? "(unknown)";
20753
- await postToast(ctx, `[CodeForge] ⚠ ${verdict.message}
20754
- 请升级 opencode 到 ${minVer}+`);
20755
- }
20400
+ auto_install: u.auto_install
20756
20401
  });
20757
20402
  if (!u.auto_check_enabled) {
20758
- safeWriteLog(PLUGIN_NAME20, {
20759
- level: "info",
20760
- msg: "auto-check disabled (codeforge.json update.auto_check_enabled=false)"
20761
- });
20403
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "auto_check_disabled" });
20404
+ return {};
20405
+ }
20406
+ if (_updateCheckStarted) {
20407
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "check_skipped_already_running" });
20762
20408
  return {};
20763
20409
  }
20410
+ _updateCheckStarted = true;
20764
20411
  setImmediate(() => {
20765
20412
  safeAsync(PLUGIN_NAME20, "checkAndMaybeUpdate", async () => {
20766
20413
  const local = readLocalVersion();
@@ -20773,224 +20420,72 @@ var updateCheckerServer = async (ctx) => {
20773
20420
  timeoutMs: 5000
20774
20421
  });
20775
20422
  } catch (e) {
20776
- safeWriteLog(PLUGIN_NAME20, {
20777
- level: "warn",
20778
- msg: "npm_fetch_failed",
20779
- error: e.message
20780
- });
20781
- await fallbackToGitHubReleases(ctx, u);
20423
+ safeWriteLog(PLUGIN_NAME20, { level: "warn", msg: "npm_fetch_failed", error: e.message });
20782
20424
  return;
20783
20425
  }
20784
- if (!npmResult) {
20785
- safeWriteLog(PLUGIN_NAME20, {
20786
- level: "info",
20787
- msg: "npm_no_release",
20788
- package: u.package,
20789
- channel: u.channel
20790
- });
20426
+ if (!npmResult)
20791
20427
  return;
20792
- }
20793
- const hasUpdate = cmpVersion(local, npmResult.version) < 0;
20794
- safeWriteLog(PLUGIN_NAME20, {
20795
- level: "info",
20796
- msg: "npm_check_result",
20797
- local,
20798
- remote: npmResult.version,
20799
- hasUpdate,
20800
- tarballUrl: npmResult.tarballUrl
20801
- });
20428
+ const remote = npmResult.version;
20429
+ const hasUpdate = cmpVersion(local, remote) < 0;
20430
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "npm_check_result", local, remote, hasUpdate });
20802
20431
  if (!hasUpdate)
20803
20432
  return;
20433
+ const lastInstalled = readLastInstalledVersion();
20434
+ if (lastInstalled === remote) {
20435
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "install_skipped_already_installed", version: remote });
20436
+ return;
20437
+ }
20804
20438
  if (!u.auto_install) {
20805
- await postToast(ctx, `[CodeForge] 有新版本 ${local} → ${npmResult.version}
20806
- 更新命令:npx ${u.package} install --global`);
20439
+ await postToast(ctx, `[codeforge] 有新版本 ${local} → ${remote}
20440
+ 运行:codeforge upgrade`);
20807
20441
  return;
20808
20442
  }
20809
- await safeAsync(PLUGIN_NAME20, "auto_install_full", async () => {
20810
- const target = getOpencodeBundlePath();
20811
- if (!target) {
20812
- safeWriteLog(PLUGIN_NAME20, {
20813
- level: "warn",
20814
- msg: "auto_install_skip",
20815
- reason: "无法定位 opencode bundle 路径"
20816
- });
20817
- await postToast(ctx, `[CodeForge] 有新版本 ${local} → ${npmResult.version}
20818
- 自动安装失败:找不到 bundle,请手动 npx ${u.package} install`);
20443
+ await safeAsync(PLUGIN_NAME20, "auto_install", async () => {
20444
+ const nodeBin = resolveNodeBin();
20445
+ const npmBin = resolveNpmBin();
20446
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "auto_install_start", local, remote, nodeBin, npmBin });
20447
+ const r1 = spawnSync2(npmBin, ["install", "-g", `${u.package}@${remote}`], {
20448
+ stdio: "pipe",
20449
+ encoding: "utf8",
20450
+ timeout: 120000,
20451
+ shell: process.platform === "win32"
20452
+ });
20453
+ if (r1.status !== 0) {
20454
+ safeWriteLog(PLUGIN_NAME20, { level: "warn", msg: "npm_install_failed", status: r1.status, stderr: (r1.stderr ?? "").slice(0, 300) });
20455
+ await postToast(ctx, `[codeforge] 自动升级失败(${local} → ${remote}),请手动运行:codeforge upgrade`);
20456
+ return;
20457
+ }
20458
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "npm_install_success", remote });
20459
+ const npmRoot = getNpmGlobalRoot(npmBin);
20460
+ const codeForgeBin = npmRoot ? join25(npmRoot, "@andyqiu", "codeforge", "bin", "codeforge.mjs") : null;
20461
+ if (!codeForgeBin || !existsSync5(codeForgeBin)) {
20462
+ safeWriteLog(PLUGIN_NAME20, { level: "warn", msg: "codeforge_bin_not_found", path: codeForgeBin ?? "null" });
20463
+ await postToast(ctx, `[codeforge] ⚠ npm 包已升级 ${local} → ${remote},但资产部署未完成。下次启动将重试,或手动运行:codeforge upgrade`);
20819
20464
  return;
20820
20465
  }
20821
- const { bundlePath, extractDir } = await downloadAndExtractBundle({
20822
- tarballUrl: npmResult.tarballUrl,
20823
- expectedIntegrity: npmResult.integrity
20466
+ const r2 = spawnSync2(nodeBin, [codeForgeBin, "install", "--global", "--skip-build"], {
20467
+ stdio: "pipe",
20468
+ encoding: "utf8",
20469
+ timeout: 60000,
20470
+ shell: false
20824
20471
  });
20825
- try {
20826
- const installMjs = join25(extractDir, "package", "install.mjs");
20827
- if (existsSync6(installMjs)) {
20828
- const nodeBin = resolveNodeBin();
20829
- const r = spawnSync2(nodeBin, [installMjs, "--global", "--skip-build"], {
20830
- cwd: join25(extractDir, "package"),
20831
- stdio: "pipe",
20832
- encoding: "utf8"
20833
- });
20834
- if (r.status === 0) {
20835
- safeWriteLog(PLUGIN_NAME20, {
20836
- level: "info",
20837
- msg: "auto_install_full_success",
20838
- local,
20839
- remote: npmResult.version,
20840
- strategy: "install.mjs"
20841
- });
20842
- await postToast(ctx, `[CodeForge] ✅ 已全量更新 ${local} → ${npmResult.version}(bundle + agents/skills/commands/workflows,重启 opencode 生效)
20843
- 回滚:npx ${u.package} rollback`);
20844
- return;
20845
- }
20846
- safeWriteLog(PLUGIN_NAME20, {
20847
- level: "warn",
20848
- msg: "install_mjs_failed",
20849
- nodeBin,
20850
- status: r.status,
20851
- stderr: r.stderr?.slice(0, 500),
20852
- stdout: r.stdout?.slice(0, 500)
20853
- });
20854
- } else {
20855
- safeWriteLog(PLUGIN_NAME20, {
20856
- level: "info",
20857
- msg: "install_mjs_absent_fallback_bundle_only",
20858
- installMjs
20859
- });
20860
- }
20861
- const { backupPath, strategy } = atomicReplaceBundle({
20862
- source: bundlePath,
20863
- target,
20864
- oldVersion: local,
20865
- keepBackups: u.backup_keep
20866
- });
20867
- safeWriteLog(PLUGIN_NAME20, {
20868
- level: "info",
20869
- msg: "auto_install_bundle_only_success",
20870
- local,
20871
- remote: npmResult.version,
20872
- target,
20873
- backupPath,
20874
- strategy
20875
- });
20876
- await postToast(ctx, `[CodeForge] ✅ 已更新 bundle ${local} → ${npmResult.version}(仅 bundle,agents/skills 未变;重启生效)
20877
- 回滚:npx ${u.package} rollback`);
20878
- } finally {
20879
- try {
20880
- rmSync(extractDir, { recursive: true, force: true });
20881
- } catch {}
20472
+ if (r2.status !== 0) {
20473
+ safeWriteLog(PLUGIN_NAME20, { level: "warn", msg: "codeforge_install_failed", status: r2.status, stderr: (r2.stderr ?? "").slice(0, 300) });
20474
+ await postToast(ctx, `[codeforge] ⚠ npm 包已升级 ${local} → ${remote},但资产部署失败。下次启动将重试,或手动运行:codeforge upgrade`);
20475
+ return;
20882
20476
  }
20477
+ writeLastInstalledVersion(remote);
20478
+ safeWriteLog(PLUGIN_NAME20, { level: "info", msg: "auto_install_full_success", local, remote, nodeBin, npmBin });
20479
+ await postToast(ctx, `[codeforge] ✅ 已升级 ${local} → ${remote}(重启 opencode 生效)
20480
+ 回滚:npm install -g ${u.package}@${local}`);
20883
20481
  });
20884
20482
  });
20885
20483
  });
20886
20484
  return {};
20887
20485
  };
20888
- function resolveNodeBin() {
20889
- const IS_WIN = process.platform === "win32";
20890
- try {
20891
- const r = spawnSync2(IS_WIN ? "where" : "which", ["node"], {
20892
- encoding: "utf8",
20893
- stdio: "pipe"
20894
- });
20895
- if (r.status === 0 && r.stdout.trim()) {
20896
- const first = r.stdout.trim().split(/\r?\n/)[0].trim();
20897
- if (first)
20898
- return first;
20899
- }
20900
- } catch {}
20901
- const candidates = IS_WIN ? [
20902
- "C:\\Program Files\\nodejs\\node.exe",
20903
- "C:\\Program Files (x86)\\nodejs\\node.exe"
20904
- ] : [
20905
- "/usr/local/bin/node",
20906
- "/usr/bin/node",
20907
- "/opt/homebrew/bin/node",
20908
- "/opt/homebrew/opt/node/bin/node"
20909
- ];
20910
- for (const c of candidates) {
20911
- try {
20912
- const t = spawnSync2(c, ["--version"], {
20913
- encoding: "utf8",
20914
- stdio: "pipe",
20915
- timeout: 2000
20916
- });
20917
- if (t.status === 0)
20918
- return c;
20919
- } catch {}
20920
- }
20921
- return process.execPath;
20922
- }
20923
- function detectOpencodeVersion() {
20924
- const env = process.env["OPENCODE_VERSION"];
20925
- if (env && env.trim().length > 0)
20926
- return env.trim();
20927
- return "unknown";
20928
- }
20929
- function getOpencodeBundlePath() {
20930
- const candidates = [];
20931
- candidates.push(join25(homedir9(), ".config", "opencode", "codeforge", "index.js"));
20932
- if (process.platform === "win32") {
20933
- const appData = process.env["APPDATA"];
20934
- if (appData)
20935
- candidates.push(join25(appData, "opencode", "codeforge", "index.js"));
20936
- const localAppData = process.env["LOCALAPPDATA"];
20937
- if (localAppData)
20938
- candidates.push(join25(localAppData, "opencode", "codeforge", "index.js"));
20939
- }
20940
- for (const c of candidates) {
20941
- if (existsSync6(c))
20942
- return c;
20943
- }
20944
- return candidates[0] ?? null;
20945
- }
20946
- async function fallbackToGitHubReleases(ctx, u) {
20947
- await safeAsync(PLUGIN_NAME20, "github_fallback", async () => {
20948
- const result = await checkUpdateOnce({
20949
- repo: u.repo,
20950
- intervalMs: u.interval_hours * 3600 * 1000
20951
- });
20952
- await reportLegacyResult(ctx, result, u.repo);
20953
- });
20954
- }
20955
- async function reportLegacyResult(ctx, result, repo) {
20956
- if (result.error && !result.fromCache) {
20957
- safeWriteLog(PLUGIN_NAME20, {
20958
- level: "warn",
20959
- msg: `update check failed: ${result.error}`,
20960
- ...result
20961
- });
20962
- return;
20963
- }
20964
- if (!result.hasUpdate) {
20965
- safeWriteLog(PLUGIN_NAME20, {
20966
- level: "info",
20967
- msg: `up-to-date (local=${result.local}, remote=${result.remote}${result.fromCache ? ", from_cache" : ""})`,
20968
- ...result
20969
- });
20970
- return;
20971
- }
20972
- const updateCmd = `bunx --bun github:${repo} install`;
20973
- const toast = `[CodeForge] 有新版本:${result.local} → ${result.remote}
20974
- 更新命令:${updateCmd}`;
20975
- safeWriteLog(PLUGIN_NAME20, {
20976
- level: "info",
20977
- msg: "new_version_available_github_fallback",
20978
- local: result.local,
20979
- remote: result.remote,
20980
- update_command: updateCmd,
20981
- fromCache: result.fromCache
20982
- });
20983
- await postToast(ctx, toast);
20984
- }
20985
20486
  async function postToast(ctx, message) {
20986
20487
  await safeAsync(PLUGIN_NAME20, "client.app.log", async () => {
20987
- await ctx.client.app.log({
20988
- body: {
20989
- service: PLUGIN_NAME20,
20990
- level: "info",
20991
- message
20992
- }
20993
- });
20488
+ await ctx.client.app.log({ body: { service: PLUGIN_NAME20, level: "info", message } });
20994
20489
  });
20995
20490
  }
20996
20491
  var handler20 = updateCheckerServer;
package/install.mjs CHANGED
@@ -103,7 +103,6 @@ function parseArgs(argv) {
103
103
  action: "install",
104
104
  dryRun: false,
105
105
  skipBuild: false,
106
- enableLegacyTools: false,
107
106
  verbose: false,
108
107
  help: false,
109
108
  global: false,
@@ -116,22 +115,11 @@ function parseArgs(argv) {
116
115
  case "--uninstall": out.action = "uninstall"; break
117
116
  case "--dry-run": out.dryRun = true; break
118
117
  case "--skip-build": out.skipBuild = true; break
119
- case "--enable-legacy-tools": out.enableLegacyTools = true; break
120
118
  case "--verbose": out.verbose = true; break
121
119
  case "-h":
122
120
  case "--help": out.help = true; break
123
121
  default:
124
- // 容忍 PS 风格 flag(来自旧 bin 调用),静默忽略未知,避免炸
125
- if (a.startsWith("-")) {
126
- // 兼容 -Global/-DryRun/-Uninstall/-SkipBuild/-EnableLegacyTools
127
- const low = a.toLowerCase()
128
- if (low === "-global") out.mode = "global"
129
- else if (low === "-uninstall") out.action = "uninstall"
130
- else if (low === "-dryrun") out.dryRun = true
131
- else if (low === "-skipbuild") out.skipBuild = true
132
- else if (low === "-enablelegacytools") out.enableLegacyTools = true
133
- else if (low === "-verbose") out.verbose = true
134
- }
122
+ // 未知参数静默忽略
135
123
  break
136
124
  }
137
125
  }
@@ -172,7 +160,7 @@ function resolvePaths({ mode }) {
172
160
  // v0.1 之前装的目录(卸载时一并清掉)
173
161
  const LEGACY_DIRS = ["agent", "command", "tool", "tools", "plugin", "plugins", "lib"]
174
162
  // v0.1+ 才有的目录
175
- const MANAGED_DIRS = ["codeforge", "agents", "commands", "workflows", "context-templates", "review-profiles", "agent-templates"]
163
+ const MANAGED_DIRS = ["codeforge", "agents", "commands", "workflows", "review-profiles", "agent-templates"]
176
164
  // 细粒度卸载:只删 CodeForge 自己的 skill
177
165
  const OWNED_SKILLS = ["ambiguity-gate", "devils-advocate", "ears-zh", "example-mapping", "success-criteria", "weighted-dimensions"]
178
166
 
@@ -188,7 +176,6 @@ const MD_MANIFEST_REL = "codeforge/installed-md-manifest.json"
188
176
  // 整目录拷贝
189
177
  const COPY_DIRS = [
190
178
  ["workflows", "workflows"],
191
- ["context-templates", "context-templates"],
192
179
  ["review-profiles", "review-profiles"],
193
180
  ["agent-templates", "agent-templates"],
194
181
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.6.13",
3
+ "version": "0.7.1",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,
@@ -48,16 +48,7 @@
48
48
  "bench:json": "node --experimental-strip-types --no-warnings ./scripts/bench-repo-map.mjs --json",
49
49
  "lint": "tsc --noEmit",
50
50
  "check:bun": "node ./scripts/check-bun.mjs",
51
- "install:local": "node ./install.mjs",
52
- "install:global": "node ./install.mjs --global",
53
- "uninstall:local": "node ./install.mjs --uninstall",
54
- "phase0:check": "node ./scripts/phase0-check.mjs",
55
51
  "phase1:check": "node ./scripts/check-dist-built.mjs && node ./scripts/check-layer-deps.mjs && node ./scripts/phase1-check.mjs",
56
- "phase15:check": "node ./scripts/phase15-check.mjs",
57
- "v13:check": "node ./scripts/v13-check.mjs",
58
- "phase2:check": "node ./scripts/phase2-check.mjs",
59
- "phase3:check": "node ./scripts/phase3-check.mjs",
60
- "phase4:check": "node ./scripts/phase4-check.mjs",
61
52
  "adr-check": "node ./scripts/adr-check.mjs",
62
53
  "adr-check:strict": "cross-env ADR_STRICT=1 node ./scripts/adr-check.mjs",
63
54
  "adr-index-sync": "node ./scripts/adr-index-sync.mjs",
@@ -65,7 +56,6 @@
65
56
  "adr-graph": "node ./scripts/adr-graph-gen.mjs",
66
57
  "adr-graph-check": "node ./scripts/adr-graph-gen.mjs --check",
67
58
  "adr-init": "node ./bin/codeforge.mjs adr-init",
68
- "phases:all": "npm run phase0:check && npm run phase1:check && npm run phase15:check && npm run phase2:check && npm run phase3:check && npm run phase4:check",
69
59
  "sync:models": "node ./scripts/sync-agent-models.mjs",
70
60
  "sync:models:check": "node ./scripts/sync-agent-models.mjs --check",
71
61
  "sync": "node ./scripts/sync-agent-models.mjs && node ./scripts/dev-sync.mjs --sync-only",
@@ -115,7 +105,6 @@
115
105
  "bin/",
116
106
  "commands/",
117
107
  "workflows/",
118
- "context-templates/",
119
108
  "skills/",
120
109
  "review-profiles/",
121
110
  "scripts/check-bun.mjs",
@@ -24,9 +24,6 @@ description: |
24
24
 
25
25
  trigger: /ship
26
26
 
27
- # 可选:会话开始注入的上下文模板(context-templates/<name>)
28
- # context_template: feature-dev
29
-
30
27
  steps:
31
28
  - name: 规划
32
29
  agent: planner