@gaoding/cli 1.0.0-alpha.25 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ gd-cli model get <model>
35
35
 
36
36
  CLI 最多每小时检查一次 npm `latest`,发现新版本后每天最多在 stderr 提醒一次;检查失败不影响原命令,`CI=true` 时跳过。普通启动不会检查或修复 Agent Skill。`gd-cli update` 始终实时检查 `latest`,支持更新 npm、pnpm 全局安装并同步 Agent Skill。
37
37
 
38
- 发布切换必须先完成精确版本的安装验收,再执行仓库中的 `scripts/promote-latest.mjs`;不要直接用未校验的 `npm dist-tag add` 移动 `latest`。
38
+ 发布只由仓库受保护 `main` CI 执行:先发布并验证 `candidate` 与精确版本安装,再提升 `latest` 并验证默认安装。版本与 dist-tag 不由本地手工修改。
39
39
 
40
40
  ## License
41
41
 
@@ -1,7 +1,8 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { resolve } from "node:path";
2
+ import { dirname, resolve } from "node:path";
3
3
  import { loadBundledSkills } from "../src/features/skill/bundled-skills.js";
4
4
  import { syncBundledSkills } from "../src/features/skill/installer.js";
5
+ import { markWorkBuddyInstallation, readInstallationOwner } from "../src/platform/installation-owner.js";
5
6
  export function isSupportedGlobalLifecycle(env) {
6
7
  const userAgent = env.npm_config_user_agent ?? "";
7
8
  if (env.npm_config_global === "true") {
@@ -19,8 +20,31 @@ export async function runPostinstall(options = {}) {
19
20
  if (!isSupportedGlobalLifecycle(env))
20
21
  return;
21
22
  const writeError = options.writeError ?? ((text) => process.stderr.write(text));
23
+ let skills;
24
+ try {
25
+ skills = (options.loadSkills ?? loadBundledSkills)(options.startFile ?? fileURLToPath(import.meta.url));
26
+ }
27
+ catch (error) {
28
+ if (env.GD_CLI_INSTALL_OWNER === "workbuddy") {
29
+ throw new Error("无法确认当前安装的 package。");
30
+ }
31
+ writeError(`警告: ${oneLineMessage(error)}\n`);
32
+ return;
33
+ }
34
+ const primarySkill = skills.find((skill) => skill.skill === "gd-cli");
35
+ if (!primarySkill)
36
+ throw new Error("Bundled gd-cli Skill is missing.");
37
+ const packageRoot = dirname(dirname(primarySkill.sourceDirectory));
38
+ if (env.GD_CLI_INSTALL_OWNER === "workbuddy") {
39
+ markWorkBuddyInstallation(packageRoot);
40
+ return;
41
+ }
42
+ const owner = readInstallationOwner(packageRoot);
43
+ if (owner === "workbuddy")
44
+ return;
45
+ if (owner === "invalid")
46
+ throw new Error("无法确认当前安装的托管信息。");
22
47
  try {
23
- const skills = (options.loadSkills ?? loadBundledSkills)(options.startFile ?? fileURLToPath(import.meta.url));
24
48
  await (options.syncSkills ?? syncBundledSkills)(skills, {
25
49
  env,
26
50
  ...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {})
@@ -38,5 +62,8 @@ const entryPath = process.argv[1];
38
62
  const isDirectEntry = entryPath !== undefined
39
63
  && resolve(entryPath) === fileURLToPath(import.meta.url);
40
64
  if (isDirectEntry || process.env.npm_lifecycle_event === "postinstall") {
41
- void runPostinstall();
65
+ void runPostinstall().catch((error) => {
66
+ process.stderr.write(`${oneLineMessage(error)}\n`);
67
+ process.exitCode = 1;
68
+ });
42
69
  }
@@ -37,6 +37,7 @@ import { createUpdateNotificationService } from "../features/update/update-notif
37
37
  import { classifyTelemetryError } from "../cli/errors.js";
38
38
  import { createTelemetryInvocation } from "../telemetry/invocation.js";
39
39
  import { createSlsTelemetrySink } from "../telemetry/sls-sink.js";
40
+ import { readInstallationOwner } from "../platform/installation-owner.js";
40
41
  const productionEndpoints = {
41
42
  orgApi: new URL("https://gdcli.gaoding.com/api"),
42
43
  agentApi: new URL("https://gdcli.gaoding.com/api"),
@@ -51,6 +52,8 @@ export function createProductionRuntime(options = {}) {
51
52
  const primarySkill = bundledSkills.find((skill) => skill.skill === "gd-cli");
52
53
  if (!primarySkill)
53
54
  throw new Error("Bundled gd-cli Skill is missing.");
55
+ const packageRoot = dirname(dirname(primarySkill.sourceDirectory));
56
+ const installationOwner = readInstallationOwner(packageRoot);
54
57
  const endpoints = options.endpoints ?? productionEndpoints;
55
58
  const fetch = options.fetch ?? globalThis.fetch;
56
59
  const now = options.now ?? (() => new Date());
@@ -233,13 +236,13 @@ export function createProductionRuntime(options = {}) {
233
236
  });
234
237
  const update = {
235
238
  run(signal) {
236
- const packageRoot = dirname(dirname(primarySkill.sourceDirectory));
237
239
  return createUpdateService({
238
240
  currentVersion: primarySkill.version,
239
241
  packageRoot,
240
242
  bundledSkills,
241
243
  fetch,
242
244
  telemetry,
245
+ installationOwner,
243
246
  ...(process.argv[1] ? { binaryPath: process.argv[1] } : {}),
244
247
  ...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {})
245
248
  }).run(signal);
@@ -249,6 +252,7 @@ export function createProductionRuntime(options = {}) {
249
252
  currentVersion: primarySkill.version,
250
253
  fetch,
251
254
  now,
255
+ installationOwner,
252
256
  ...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {}),
253
257
  ...(options.env ? { env: options.env } : {}),
254
258
  notify: (message) => { presenter.notice(message); }
@@ -24,6 +24,8 @@ export function createUpdateNotificationService(options) {
24
24
  });
25
25
  return {
26
26
  async check(signal) {
27
+ if ((options.installationOwner ?? "user") !== "user")
28
+ return;
27
29
  if (env.CI === "true")
28
30
  return;
29
31
  let release = null;
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
4
4
  import { gt } from "semver";
5
5
  import { syncBundledSkills, verifyBundledSkills } from "../skill/installer.js";
6
6
  import { fetchLatestVersion } from "./latest-version.js";
7
+ import { readInstallationOwner } from "../../platform/installation-owner.js";
7
8
  export class UpdateError extends Error {
8
9
  code;
9
10
  nextSteps;
@@ -57,6 +58,14 @@ export function createUpdateService(options) {
57
58
  return {
58
59
  async run(signal) {
59
60
  signal.throwIfAborted();
61
+ const owner = options.installationOwner
62
+ ?? readInstallationOwner(options.packageRoot);
63
+ if (owner === "workbuddy") {
64
+ throw new UpdateError("UPDATE_FAILED", "此 gd-cli 由 WorkBuddy 连接器管理。", ["请在 WorkBuddy 中更新「稿定」连接器。"]);
65
+ }
66
+ if (owner === "invalid") {
67
+ throw new UpdateError("UPDATE_FAILED", "无法确认当前安装的托管信息。", ["请通过原安装渠道重新安装 gd-cli。"]);
68
+ }
60
69
  const latest = await options.telemetry.stage("registry", async () => {
61
70
  try {
62
71
  const result = await fetchLatestVersion({ fetch: fetchPackage, signal });
@@ -0,0 +1,81 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { closeSync, linkSync, lstatSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ const markerFilename = ".gd-cli-installation.json";
5
+ export function readInstallationOwner(packageRoot) {
6
+ const path = join(packageRoot, markerFilename);
7
+ try {
8
+ if (!lstatSync(path).isFile())
9
+ return "invalid";
10
+ const value = JSON.parse(readFileSync(path, "utf8"));
11
+ if (!isRecord(value))
12
+ return "invalid";
13
+ const keys = Object.keys(value);
14
+ return keys.length === 1 && keys[0] === "owner" && value.owner === "workbuddy"
15
+ ? "workbuddy"
16
+ : "invalid";
17
+ }
18
+ catch (error) {
19
+ return isMissing(error) ? "user" : "invalid";
20
+ }
21
+ }
22
+ export function markWorkBuddyInstallation(packageRoot) {
23
+ const packageMetadata = parsePackageMetadata(packageRoot);
24
+ if (packageMetadata.name !== "@gaoding/cli") {
25
+ throw new Error("无法确认当前安装的 package。");
26
+ }
27
+ const markerPath = join(packageRoot, markerFilename);
28
+ const current = readInstallationOwner(packageRoot);
29
+ if (current === "workbuddy")
30
+ return;
31
+ if (current === "invalid")
32
+ throw new Error("无法确认当前安装的托管信息。");
33
+ const temporaryPath = join(packageRoot, `.${markerFilename}.${randomUUID()}.tmp`);
34
+ let descriptor;
35
+ try {
36
+ descriptor = openSync(temporaryPath, "wx", 0o600);
37
+ writeFileSync(descriptor, `${JSON.stringify({ owner: "workbuddy" }, null, 2)}\n`, "utf8");
38
+ closeSync(descriptor);
39
+ descriptor = undefined;
40
+ linkSync(temporaryPath, markerPath);
41
+ unlinkSync(temporaryPath);
42
+ }
43
+ catch (error) {
44
+ if (descriptor !== undefined) {
45
+ try {
46
+ closeSync(descriptor);
47
+ }
48
+ catch { /* best effort */ }
49
+ }
50
+ try {
51
+ unlinkSync(temporaryPath);
52
+ }
53
+ catch { /* best effort */ }
54
+ if (isExists(error)) {
55
+ if (readInstallationOwner(packageRoot) === "workbuddy")
56
+ return;
57
+ throw new Error("无法确认当前安装的托管信息。");
58
+ }
59
+ throw error;
60
+ }
61
+ }
62
+ function parsePackageMetadata(packageRoot) {
63
+ try {
64
+ const value = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
65
+ if (!isRecord(value))
66
+ throw new Error("invalid package metadata");
67
+ return value;
68
+ }
69
+ catch {
70
+ throw new Error("无法确认当前安装的 package。");
71
+ }
72
+ }
73
+ function isRecord(value) {
74
+ return typeof value === "object" && value !== null && !Array.isArray(value);
75
+ }
76
+ function isMissing(error) {
77
+ return isRecord(error) && error.code === "ENOENT";
78
+ }
79
+ function isExists(error) {
80
+ return isRecord(error) && error.code === "EEXIST";
81
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@gaoding/cli",
3
- "version": "1.0.0-alpha.25",
3
+ "version": "1.1.0",
4
4
  "description": "Gaoding command-line interface for agents and people.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
8
8
  "access": "public",
9
9
  "registry": "https://registry.npmjs.org/",
10
- "tag": "alpha"
10
+ "tag": "latest"
11
11
  },
12
12
  "engines": {
13
13
  "node": ">=20.0.0"
@@ -55,8 +55,8 @@
55
55
  "vitest": "4.1.10"
56
56
  },
57
57
  "gaodingRelease": {
58
- "sourceCommit": "36580b12b2af5b16235c7c658505553d4eaa3c34",
59
- "pipelineId": "166774",
60
- "pipelineUrl": "https://git.intra.gaoding.com/gtt/gaoding-cli/-/pipelines/166774"
58
+ "sourceCommit": "24e9f75334bea21d5d5fed4797a346c81b37c0b1",
59
+ "pipelineId": "173862",
60
+ "pipelineUrl": "https://git.intra.gaoding.com/gtt/gaoding-cli/-/pipelines/173862"
61
61
  }
62
62
  }
@@ -10,4 +10,6 @@ gd-cli update
10
10
 
11
11
  正常使用 CLI 时,版本可用性最多每小时检查一次;存在新版本时,同一本地自然日最多在 stderr 提醒一次。看到提示后执行 `gd-cli update`。自动检查不会更新 CLI,也不会检查或修复 Agent Skill;`CI=true` 时跳过。
12
12
 
13
+ 如果 `gd-cli update` 提示当前安装由 WorkBuddy 连接器管理,请在 WorkBuddy 中更新「稿定」连接器。不要尝试绕过该安装渠道单独升级 CLI 或同步 Agent Skill;连接器会一起管理并验收匹配版本的 CLI 与 Skill。
14
+
13
15
  命令不接收参数或 `--json`。不支持自动更新的安装来源会给出明确的 npm、pnpm 全局安装命令。若 CLI 已更新但 Agent Skill 验收失败,保留已更新版本,按 stderr 的下一步修复后重新执行 `gd-cli update`。