@nggaigc/cli 0.1.45 → 0.1.47

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
@@ -1,6 +1,6 @@
1
1
  # @nggaigc/cli
2
2
 
3
- NGGAIGC Skill 的受信安装器,默认安装目标仍为 Codex。
3
+ NGGAIGC Skill 的受信安装器。Codex 与 WorkBuddy 使用同一条安装命令,自动识别当前宿主。
4
4
 
5
5
  ```powershell
6
6
  npx --yes @nggaigc/cli@latest install
@@ -12,7 +12,15 @@ CLI 保留 macOS npm symlink 入口与 packed-TGZ launcher 检查,并验证随
12
12
 
13
13
  macOS arm64 辅助程序沿用已签名 Plugin 中通过完整性校验的字节;Intel Mac 不在支持范围内。实际公开版本和 `@latest` 状态以 npm registry 为准,Apple Silicon 真机验收需单独进行。
14
14
 
15
- ## 跨 Agent 目标(0.1.45 起)
15
+ ## 统一安装入口(0.1.46 起)
16
+
17
+ 不带目录参数时,`install`、`start-skill`、`client` 使用同一套宿主识别:Codex 的当前会话标记,或 WorkBuddy 的会话标记/实际工作会话与专属应用标记组合。配置目录、已安装应用和通用终端变量本身不代表当前宿主;环境标记只是便利提示,不用于认证或授权。
18
+
19
+ 无法识别或同时命中两个宿主时,有交互终端可选择 Codex 或 WorkBuddy;非交互调用立即返回 `status: selection-required`、两项 `choices` 和退出码 3,不安装、不打开授权网页。当前 Agent 应让用户选择一次,再把 `--agent codex` 或 `--agent workbuddy` 加到原命令继续;`client` 的目标选项放在 `--` 分隔符之前,业务参数保持原样。安装结果始终返回真实 `skillsDirectory` 和 `skillPath`,连接失败也保留该位置,后续继续使用同一位置。
20
+
21
+ 本次仅 Codex 与 WorkBuddy 为自动识别支持范围;旧 `universal` 与自定义目录参数仍兼容,但不代表其他 Agent 已获验收。
22
+
23
+ ## 显式目标(0.1.45 起)
16
24
 
17
25
  下列目标参数与 `client` 桥要求 CLI 0.1.45 或更高版本。旧版本不支持新参数;`@latest` 的实际可用版本以 npm registry 与对应签名制品为准。
18
26
 
@@ -24,7 +32,7 @@ npx --yes @nggaigc/cli@latest start-skill skill.<id> [version] --skills-dir "/ab
24
32
  npx --yes @nggaigc/cli@latest client --skills-dir "/absolute/project/.codebuddy/skills" -- <原command> <原args>
25
33
  ```
26
34
 
27
- `install`、`start-skill` 和 `client` 支持互斥的 `--agent codex|workbuddy|universal` 或 `--skills-dir <绝对目录>`。未指定目标时仍安装至 Codex 的 `CODEX_HOME/skills`,未设置 `CODEX_HOME` 时为 `~/.codex/skills`。WorkBuddy 使用官方兼容配置目录 `~/.codebuddy/skills`;universal 使用 `~/.agents/skills`,不保证所有 Agent 自动识别。明确目录可用于宿主项目级技能路径。`--no-login` 仅跳过安装后的登录,不绕过签名、哈希、路径检查或后续鉴权。
35
+ `install`、`start-skill` 和 `client` 支持互斥的 `--agent codex|workbuddy|universal` 或 `--skills-dir <绝对目录>`。识别或选择 Codex 后使用 `CODEX_HOME/skills`,未设置时为 `~/.codex/skills`。WorkBuddy 优先使用实际进程提供的 `WORKBUDDY_CONFIG_DIR/skills`(其次 `CODEBUDDY_CONFIG_DIR/skills`),否则使用 `~/.workbuddy/skills`;在宿主外升级时,若现代目录没有根 Skill 而旧 `~/.codebuddy/skills` 已安装,则继续使用旧位置。不会删除或迁移旧安装。`--skills-dir` 始终精确指定项目级或其他现有安装。universal 使用 `~/.agents/skills`,不保证所有 Agent 自动识别。`--no-login` 仅跳过安装后的登录,不绕过签名、哈希、路径检查或后续鉴权。
28
36
 
29
37
  根 Skill 按实际读取的 `SKILL.md` 定位父目录及其上一级 `skillsDirectory`;后续升级与 `start-skill` 显式使用同一目录。准备回执增加 `skillsDirectory`、`rootLauncherPath`,保留 `preparedSkills`;宿主应显式读取返回的真实路径。独立包的原客户端调用使用回执的受信根启动器;`client` 是同目录根包验签桥,保留原命令/参数,不增加 API 专用业务分支。安装新包不证明当前宿主热加载,必要时开启新会话。
30
38
 
@@ -0,0 +1,91 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { resolveSkillLocation, } from "./skill-location.js";
3
+ export const agentChoices = [
4
+ { agent: "codex", label: "Codex" },
5
+ { agent: "workbuddy", label: "WorkBuddy" },
6
+ ];
7
+ export class AgentSelectionRequired extends Error {
8
+ reason;
9
+ code = "AGENT_SELECTION_REQUIRED";
10
+ constructor(reason) {
11
+ super("AGENT_SELECTION_REQUIRED");
12
+ this.reason = reason;
13
+ }
14
+ }
15
+ /** Configuration paths and installed applications are not caller identity. */
16
+ export function detectCallingAgent(environment) {
17
+ const present = (value) => typeof value === "string" && value.trim().length > 0;
18
+ const codex = present(environment.CODEX_THREAD_ID);
19
+ // WorkBuddy's prewarmed runtime omits WORKBUDDY_CONVERSATION_ID. Its shell
20
+ // still has a CodeBuddy session AND WorkBuddy-specific application markers.
21
+ // A standalone CodeBuddy session is deliberately not treated as WorkBuddy.
22
+ const workbuddy = present(environment.WORKBUDDY_CONVERSATION_ID) ||
23
+ (present(environment.CODEBUDDY_SESSION_ID) &&
24
+ present(environment.WORKBUDDY_APP_VERSION) &&
25
+ present(environment.WORKBUDDY_APP_PATH));
26
+ if (codex && workbuddy)
27
+ return { reason: "conflicting" };
28
+ if (codex)
29
+ return { agent: "codex" };
30
+ if (workbuddy)
31
+ return { agent: "workbuddy" };
32
+ return { reason: "unknown" };
33
+ }
34
+ async function chooseInTerminal() {
35
+ const terminal = createInterface({
36
+ input: process.stdin,
37
+ output: process.stderr,
38
+ });
39
+ const controller = new AbortController();
40
+ const cancel = () => controller.abort();
41
+ terminal.once("SIGINT", cancel);
42
+ terminal.once("close", cancel);
43
+ try {
44
+ const answer = (await terminal.question("请选择使用 NGGAIGC 的 AI 助手:1. Codex 2. WorkBuddy\n输入 1 或 2:", { signal: controller.signal }))
45
+ .trim()
46
+ .toLowerCase();
47
+ if (answer === "1" || answer === "codex")
48
+ return "codex";
49
+ if (answer === "2" || answer === "workbuddy")
50
+ return "workbuddy";
51
+ return undefined;
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ finally {
57
+ terminal.off("SIGINT", cancel);
58
+ terminal.off("close", cancel);
59
+ terminal.close();
60
+ }
61
+ }
62
+ /** Resolve before any installation, browser authorization or client request. */
63
+ export async function selectCommandLocation(options, runtime = {}) {
64
+ const environment = runtime.environment ?? process.env;
65
+ if (options.agent !== undefined ||
66
+ options.skillsDirectory !== undefined ||
67
+ options.codexHome !== undefined) {
68
+ resolveSkillLocation(options, environment);
69
+ return options;
70
+ }
71
+ const detected = detectCallingAgent(environment);
72
+ if ("agent" in detected)
73
+ return { ...options, agent: detected.agent };
74
+ const choose = runtime.choose ??
75
+ (process.stdin.isTTY && process.stderr.isTTY
76
+ ? chooseInTerminal
77
+ : undefined);
78
+ const agent = await choose?.();
79
+ if (agent === "codex" || agent === "workbuddy")
80
+ return { ...options, agent };
81
+ throw new AgentSelectionRequired(detected.reason);
82
+ }
83
+ export function selectionRequiredResult(error) {
84
+ return {
85
+ status: "selection-required",
86
+ error: { code: error.code, reason: error.reason },
87
+ message: "请选择当前使用的 AI 助手,然后在原命令中添加 --agent codex 或 --agent workbuddy 继续;client 命令的选项须放在 -- 分隔符之前。",
88
+ choices: agentChoices,
89
+ nextAction: "select-agent-and-retry",
90
+ };
91
+ }
package/dist/bundle.js CHANGED
@@ -47,7 +47,7 @@ export async function verifyBundle(input) {
47
47
  !exactKeys(manifestValue, manifestKeys) ||
48
48
  manifestValue.schemaVersion !== "nggaigc-plugin-package/v1" ||
49
49
  manifestValue.artifactId !== "plugin.nggaigc" ||
50
- manifestValue.version !== "1.1.44" ||
50
+ manifestValue.version !== "1.1.45" ||
51
51
  manifestValue.artifactPath !==
52
52
  `packages/plugins/nggaigc-plugin-${manifestValue.version}.zip` ||
53
53
  typeof manifestValue.sourceCommit !== "string" ||
package/dist/errors.js CHANGED
@@ -1,9 +1,57 @@
1
+ const localCauseCodes = new Set([
2
+ "EPERM",
3
+ "EACCES",
4
+ "ENOENT",
5
+ "EAGAIN",
6
+ "EMFILE",
7
+ "ENFILE",
8
+ "ENOMEM",
9
+ "EINVAL",
10
+ "ERR_ACCESS_DENIED",
11
+ "ERR_MODULE_NOT_FOUND",
12
+ "SETUP_REQUIRED",
13
+ "CREDENTIAL_FRAME_INVALID",
14
+ "CREDENTIAL_STORE_FAILED",
15
+ "INVALID_INPUT",
16
+ "CREDENTIAL_CHANGED",
17
+ "ALREADY_CONFIGURED",
18
+ "CREDENTIAL_STORE_UNAVAILABLE",
19
+ "CREDENTIAL_HELPER_START_FAILED",
20
+ "CREDENTIAL_HELPER_FAILED",
21
+ "DISTRIBUTION_INVALID",
22
+ "LOCAL_SESSION_FAILED",
23
+ "LOCAL_SESSION_TIMEOUT",
24
+ "POWERSHELL_EXECUTION_POLICY_BLOCKED",
25
+ "RUNTIME_UNSUPPORTED",
26
+ "UNKNOWN",
27
+ ]);
28
+ export function safeConnectionDiagnostics(value) {
29
+ if (value === null || typeof value !== "object" || !("stage" in value))
30
+ return undefined;
31
+ const stage = value.stage;
32
+ if (stage !== "helper_start" && stage !== "credential_verification")
33
+ return undefined;
34
+ const cause = "causeCode" in value ? value.causeCode : undefined;
35
+ return {
36
+ stage,
37
+ causeCode: typeof cause === "string" && localCauseCodes.has(cause)
38
+ ? cause
39
+ : "UNKNOWN",
40
+ };
41
+ }
1
42
  export class CliError extends Error {
2
43
  code;
3
- constructor(code) {
44
+ stage;
45
+ causeCode;
46
+ constructor(code, diagnostics) {
4
47
  super(code);
5
48
  this.code = code;
6
49
  this.name = "CliError";
50
+ const safe = safeConnectionDiagnostics(diagnostics);
51
+ if (safe) {
52
+ this.stage = safe.stage;
53
+ this.causeCode = safe.causeCode;
54
+ }
7
55
  }
8
56
  }
9
57
  export function fixedErrorCode(error) {
package/dist/install.js CHANGED
@@ -4,12 +4,12 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { extractBundle, verifyBundle, verifyExtractedBundle, verifyInstalledSkill, } from "./bundle.js";
7
- import { CliError, fixedErrorCode } from "./errors.js";
7
+ import { CliError, fixedErrorCode, safeConnectionDiagnostics, } from "./errors.js";
8
8
  import { resolveSkillLocation, } from "./skill-location.js";
9
9
  function defaultBundle() {
10
10
  const vendor = fileURLToPath(new URL("../vendor/", import.meta.url));
11
11
  return {
12
- archive: path.join(vendor, "nggaigc-plugin-1.1.44.zip"),
12
+ archive: path.join(vendor, "nggaigc-plugin-1.1.45.zip"),
13
13
  manifest: path.join(vendor, "manifest.json"),
14
14
  trust: path.join(vendor, "trusted-keys.json"),
15
15
  };
@@ -37,6 +37,47 @@ function installationError(error) {
37
37
  return new CliError("SKILL_PAIR_RECOVERY_REQUIRED");
38
38
  return new CliError("INSTALLATION_FAILED");
39
39
  }
40
+ function connectionError(error, fallback) {
41
+ const code = fixedErrorCode(error);
42
+ const localCodes = [
43
+ "CREDENTIAL_HELPER_START_FAILED",
44
+ "CREDENTIAL_VERIFICATION_FAILED",
45
+ "CREDENTIAL_STORE_FAILED",
46
+ "CREDENTIAL_STORE_UNAVAILABLE",
47
+ "CREDENTIAL_FRAME_INVALID",
48
+ "CREDENTIAL_HELPER_FAILED",
49
+ "DISTRIBUTION_INVALID",
50
+ "LOCAL_SESSION_FAILED",
51
+ "LOCAL_SESSION_TIMEOUT",
52
+ "POWERSHELL_EXECUTION_POLICY_BLOCKED",
53
+ "RUNTIME_UNSUPPORTED",
54
+ "UNSUPPORTED_PLATFORM",
55
+ "BROWSER_OPEN_FAILED",
56
+ ];
57
+ if (localCodes.includes(code))
58
+ return new CliError(code, error);
59
+ // Older signed launchers can throw a synchronous OS spawn error before
60
+ // they can return a protected error envelope. Keep only this closed cause.
61
+ if (error !== null &&
62
+ typeof error === "object" &&
63
+ "syscall" in error &&
64
+ typeof error.syscall === "string" &&
65
+ /^spawn(?: |$)/.test(error.syscall)) {
66
+ return new CliError("CREDENTIAL_HELPER_START_FAILED", {
67
+ stage: "helper_start",
68
+ causeCode: code,
69
+ });
70
+ }
71
+ if (fallback === "BROWSER_AUTHORIZATION_FAILED" &&
72
+ [
73
+ "AUTHORIZATION_REJECTED",
74
+ "AUTHORIZATION_CANCELLED",
75
+ "ACTIVATION_REJECTED",
76
+ "ACTIVATION_INPUT_INVALID",
77
+ ].includes(code ?? ""))
78
+ return new CliError(fallback);
79
+ return new CliError("CONNECTION_VERIFICATION_FAILED");
80
+ }
40
81
  async function installedVersion(target) {
41
82
  try {
42
83
  const marker = JSON.parse(await readFile(path.join(target, ".nggaigc-install.json"), "utf8"));
@@ -116,10 +157,8 @@ async function connect(launcher, options, report, probe) {
116
157
  catch (error) {
117
158
  if (!probe && fixedErrorCode(error) === "SETUP_REQUIRED")
118
159
  configured = false;
119
- else if (fixedErrorCode(error) === "POWERSHELL_EXECUTION_POLICY_BLOCKED")
120
- throw new CliError("POWERSHELL_EXECUTION_POLICY_BLOCKED");
121
160
  else
122
- throw new CliError("CONNECTION_VERIFICATION_FAILED");
161
+ throw connectionError(error, "CONNECTION_VERIFICATION_FAILED");
123
162
  }
124
163
  if (!configured) {
125
164
  report("launching");
@@ -157,11 +196,7 @@ async function connect(launcher, options, report, probe) {
157
196
  throw new Error();
158
197
  }
159
198
  catch (error) {
160
- if (fixedErrorCode(error) === "BROWSER_OPEN_FAILED")
161
- throw new CliError("BROWSER_OPEN_FAILED");
162
- if (fixedErrorCode(error) === "POWERSHELL_EXECUTION_POLICY_BLOCKED")
163
- throw new CliError("POWERSHELL_EXECUTION_POLICY_BLOCKED");
164
- throw new CliError("BROWSER_AUTHORIZATION_FAILED");
199
+ throw connectionError(error, "BROWSER_AUTHORIZATION_FAILED");
165
200
  }
166
201
  finally {
167
202
  process.off("SIGINT", interrupt);
@@ -215,11 +250,13 @@ export async function runInstall(options = {}) {
215
250
  catch (error) {
216
251
  if (!(error instanceof CliError))
217
252
  throw error;
253
+ const diagnostics = safeConnectionDiagnostics(error);
218
254
  return {
219
255
  status: "installed-unconnected",
220
256
  installation,
221
257
  version: bundle.version,
222
258
  connectionError: error.code,
259
+ ...(diagnostics ? { connectionDiagnostics: diagnostics } : {}),
223
260
  };
224
261
  }
225
262
  }
package/dist/main.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import path from "node:path";
2
- import { CliError, fixedErrorCode } from "./errors.js";
2
+ import { CliError, fixedErrorCode, safeConnectionDiagnostics, } from "./errors.js";
3
3
  import { runInstall } from "./install.js";
4
4
  import { runSkillStartCommand, runClientCommand, } from "./skill-command.js";
5
5
  import { parseSkillLocation, resolveSkillLocation } from "./skill-location.js";
6
+ import { AgentSelectionRequired, selectCommandLocation, selectionRequiredResult, } from "./agent-selection.js";
6
7
  const messages = {
7
8
  authorizing: "浏览器已成功启动,请在 NGGAIGC 页面确认连接…",
8
9
  connected: "NGGAIGC Skill 已安装,设备连接验证成功。",
@@ -16,6 +17,15 @@ const errorMessages = {
16
17
  BROWSER_AUTHORIZATION_FAILED: "网页授权未完成;请在当前 Agent 中调用 NGGAIGC 继续连接,无需重装。",
17
18
  BROWSER_OPEN_FAILED: "系统未能打开默认浏览器,也无法提供手动授权入口;请在当前 Agent 中调用 NGGAIGC 重试连接,无需重装。",
18
19
  CONNECTION_VERIFICATION_FAILED: "设备连接验证未完成;请在当前 Agent 中检查连接状态,无需重装。",
20
+ CREDENTIAL_HELPER_START_FAILED: "本地凭据助手未能启动;请先处理当前运行错误,不要重复网页授权或重装。安装器没有更改系统保护。",
21
+ CREDENTIAL_VERIFICATION_FAILED: "凭据写入后的本地验证未完成,已尝试安全清理本次凭据;请先处理原本地错误,不要重复网页授权或重装。",
22
+ CREDENTIAL_STORE_FAILED: "本地凭据保存未完成;请先检查本地存储错误,不要重复网页授权。",
23
+ CREDENTIAL_STORE_UNAVAILABLE: "本地凭据存储暂不可用;请先检查本地运行条件,不要重复网页授权。",
24
+ CREDENTIAL_FRAME_INVALID: "本地凭据数据未能通过校验;请先处理当前本地错误,不要重复网页授权。",
25
+ CREDENTIAL_HELPER_FAILED: "本地凭据助手未能完成操作;请先处理当前本地错误,不要重复网页授权。",
26
+ DISTRIBUTION_INVALID: "本地受信客户端文件无法通过校验;请先检查当前安装,不要重复网页授权或手工修改签名文件。",
27
+ LOCAL_SESSION_FAILED: "本地凭据会话未能完成;请先处理本地错误,不要重复网页授权。",
28
+ LOCAL_SESSION_TIMEOUT: "本地凭据会话超时;请先检查当前本地进程,不要重复网页授权。",
19
29
  POWERSHELL_EXECUTION_POLICY_BLOCKED: "Windows 脚本执行受到系统或组织策略限制,设备连接未完成。安装器没有更改执行策略。",
20
30
  INSTALLATION_FAILED: "本地安装未完成,请检查现有文件状态后重试。",
21
31
  SKILL_PAIR_UPGRADE_REQUIRED: "检测到无法安全配套迁移的本地 Seedream Skill,已停止升级。请保留旧 Skill 和任务文件并联系支持。",
@@ -34,7 +44,8 @@ export async function main(args, runtime = {}) {
34
44
  "install [--agent codex|workbuddy|universal | --skills-dir <absolute-directory>] [--no-login]",
35
45
  "start-skill skill.<id> [version] [--agent codex|workbuddy|universal | --skills-dir <absolute-directory>]",
36
46
  "client [--agent codex|workbuddy|universal | --skills-dir <absolute-directory>] -- <command> [arguments]",
37
- "Default: Codex (existing CODEX_HOME is preserved). WorkBuddy: ~/.codebuddy/skills; universal: ~/.agents/skills.",
47
+ "Default: automatically detect Codex or WorkBuddy. If unknown or conflicting, select one; non-interactive calls return selection-required (exit 3).",
48
+ "Codex: existing CODEX_HOME is preserved, otherwise ~/.codex/skills. WorkBuddy: its runtime config directory/skills, otherwise ~/.workbuddy/skills; existing legacy ~/.codebuddy installs are reused. Explicit legacy universal/custom targets remain supported.",
38
49
  "Use the same target for install and subsequent commands. --no-login installs verified files without opening a browser.",
39
50
  "client examples: -- status, -- login, -- account-status, -- workflows, -- history, -- disconnect",
40
51
  "",
@@ -52,7 +63,8 @@ export async function main(args, runtime = {}) {
52
63
  if (positional.length)
53
64
  throw new CliError("INVALID_INPUT");
54
65
  process.once("SIGINT", interrupt);
55
- const result = await (runtime.client ?? runClientCommand)(args.slice(separator + 1), location, {
66
+ const selectedLocation = await selectCommandLocation(location, runtime.agentSelection);
67
+ const result = await (runtime.client ?? runClientCommand)(args.slice(separator + 1), selectedLocation, {
56
68
  signal: controller.signal,
57
69
  browserManualLink: (url) => errorOutput(`如果没有看到授权网页,请在本机浏览器打开此一次性链接;不要转发或保存:\n${url}\n`),
58
70
  browserFallback: (url) => errorOutput(`浏览器未能自动打开,请在本机手动打开此一次性链接;不要转发或保存:\n${url}\n`),
@@ -61,7 +73,11 @@ export async function main(args, runtime = {}) {
61
73
  return 0;
62
74
  }
63
75
  catch (error) {
64
- errorOutput(`${JSON.stringify({ error: { code: fixedErrorCode(error) ?? "CLIENT_UNAVAILABLE" } })}\n`);
76
+ if (error instanceof AgentSelectionRequired) {
77
+ output(`${JSON.stringify(selectionRequiredResult(error))}\n`);
78
+ return 3;
79
+ }
80
+ errorOutput(`${JSON.stringify({ error: { code: fixedErrorCode(error) ?? "CLIENT_UNAVAILABLE", ...safeConnectionDiagnostics(error) } })}\n`);
65
81
  return 1;
66
82
  }
67
83
  finally {
@@ -80,11 +96,16 @@ export async function main(args, runtime = {}) {
80
96
  if (args[0] === "start-skill" &&
81
97
  (parameters.length === 1 || parameters.length === 2)) {
82
98
  try {
83
- const result = await (runtime.startSkill ?? runSkillStartCommand)(parameters[0], parameters[1], { ...runtime.skillOptions, ...parsed.location });
99
+ const location = await selectCommandLocation({ ...runtime.skillOptions, ...parsed.location }, runtime.agentSelection);
100
+ const result = await (runtime.startSkill ?? runSkillStartCommand)(parameters[0], parameters[1], location);
84
101
  output(`${JSON.stringify({ status: "ready", ...result })}\n`);
85
102
  return 0;
86
103
  }
87
104
  catch (error) {
105
+ if (error instanceof AgentSelectionRequired) {
106
+ output(`${JSON.stringify(selectionRequiredResult(error))}\n`);
107
+ return 3;
108
+ }
88
109
  const code = fixedErrorCode(error) ?? "SKILL_START_UNAVAILABLE";
89
110
  errorOutput(`${JSON.stringify({ error: { code } })}\n`);
90
111
  return 1;
@@ -97,8 +118,10 @@ export async function main(args, runtime = {}) {
97
118
  return 1;
98
119
  }
99
120
  try {
121
+ const location = await selectCommandLocation(parsed.location, runtime.agentSelection);
122
+ const { skillsDirectory } = resolveSkillLocation(location);
100
123
  const result = await (runtime.install ?? runInstall)({
101
- ...parsed.location,
124
+ ...location,
102
125
  ...(parameters[0] === "--no-login" ? { connect: false } : {}),
103
126
  reporter: (event) => output(`${messages[event]}\n`),
104
127
  launcherOptionsFactory: () => ({
@@ -112,18 +135,24 @@ export async function main(args, runtime = {}) {
112
135
  },
113
136
  }),
114
137
  });
138
+ output(`${JSON.stringify({
139
+ ...result,
140
+ ...(location.agent ? { agent: location.agent } : {}),
141
+ skillsDirectory,
142
+ skillPath: path.join(skillsDirectory, "nggaigc", "SKILL.md"),
143
+ guidance: "在当前 AI 助手中调用 NGGAIGC;若未发现新 Skill,请新开一个对话。后续安装、更新和工作流加载沿用此 skillsDirectory。",
144
+ })}\n`);
115
145
  if (result.status === "installed-unconnected") {
116
146
  errorOutput(`NGGAIGC Skill ${result.version} 文件已${result.installation === "unchanged" ? "存在" : "安装"},但设备尚未连接。${errorMessages[result.connectionError] ?? "请在当前 Agent 中检查连接状态。"}\n`);
117
147
  return 2;
118
148
  }
119
- if (result.status === "installed" ||
120
- Object.keys(parsed.location).length > 0) {
121
- const { skillsDirectory } = resolveSkillLocation(parsed.location);
122
- output(`${JSON.stringify({ ...result, skillsDirectory, skillPath: path.join(skillsDirectory, "nggaigc", "SKILL.md") })}\n`);
123
- }
124
149
  return 0;
125
150
  }
126
151
  catch (error) {
152
+ if (error instanceof AgentSelectionRequired) {
153
+ output(`${JSON.stringify(selectionRequiredResult(error))}\n`);
154
+ return 3;
155
+ }
127
156
  const code = error instanceof CliError ? error.code : "INSTALLATION_FAILED";
128
157
  errorOutput(`${errorMessages[code] ?? errorMessages.INSTALLATION_FAILED}\n`);
129
158
  return 1;
@@ -15,7 +15,7 @@ function record(value) {
15
15
  export async function productionDependencies(skillsDirectory) {
16
16
  const vendor = fileURLToPath(new URL("../vendor/", import.meta.url));
17
17
  const bundle = await verifyBundle({
18
- archive: path.join(vendor, "nggaigc-plugin-1.1.44.zip"),
18
+ archive: path.join(vendor, "nggaigc-plugin-1.1.45.zip"),
19
19
  manifest: path.join(vendor, "manifest.json"),
20
20
  trust: path.join(vendor, "trusted-keys.json"),
21
21
  });
@@ -1,5 +1,6 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
+ import { lstatSync } from "node:fs";
3
4
  import { CliError } from "./errors.js";
4
5
  export function absoluteDirectory(value) {
5
6
  if (!path.isAbsolute(value) || /[\0\r\n]/u.test(value))
@@ -16,6 +17,30 @@ export function stateDirectoryForSkills(skillsDirectory) {
16
17
  ? path.join(path.dirname(skillsDirectory), ".nggaigc-managed")
17
18
  : path.join(skillsDirectory, ".nggaigc-managed", "custom-target");
18
19
  }
20
+ function hasInstalledRoot(home) {
21
+ try {
22
+ lstatSync(path.join(home, "skills", "nggaigc"));
23
+ return true;
24
+ }
25
+ catch (error) {
26
+ if (error.code === "ENOENT")
27
+ return false;
28
+ throw new CliError("INSTALLATION_FAILED");
29
+ }
30
+ }
31
+ function workbuddyHome(profile, environment) {
32
+ const configured = environment.WORKBUDDY_CONFIG_DIR ?? environment.CODEBUDDY_CONFIG_DIR;
33
+ if (configured !== undefined)
34
+ return absoluteDirectory(configured);
35
+ const current = path.join(profile, ".workbuddy");
36
+ const legacy = path.join(profile, ".codebuddy");
37
+ // Upgrade an existing legacy global installation in place when outside the
38
+ // host. Inside WorkBuddy, its actual runtime configuration takes precedence.
39
+ // The installer still verifies ownership/signatures before changing files.
40
+ return !hasInstalledRoot(current) && hasInstalledRoot(legacy)
41
+ ? legacy
42
+ : current;
43
+ }
19
44
  /** One explicit location for root installation and every dependent Skill. */
20
45
  export function resolveSkillLocation(options = {}, environment = process.env) {
21
46
  if ([options.agent, options.skillsDirectory, options.codexHome].filter((value) => value !== undefined).length > 1)
@@ -36,7 +61,9 @@ export function resolveSkillLocation(options = {}, environment = process.env) {
36
61
  ? ((options.profile === undefined
37
62
  ? environment.CODEX_HOME
38
63
  : undefined) ?? path.join(profile, ".codex"))
39
- : path.join(profile, agent === "workbuddy" ? ".codebuddy" : ".agents")));
64
+ : agent === "workbuddy"
65
+ ? workbuddyHome(profile, options.profile === undefined ? environment : {})
66
+ : path.join(profile, ".agents")));
40
67
  return {
41
68
  skillsDirectory: path.join(home, "skills"),
42
69
  // Explicit and named-host calls for the same directory share one journal.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nggaigc/cli",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "Secure NGGAIGC Skill installer for Codex, WorkBuddy and compatible agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": "nggaigc-plugin-package/v1",
3
3
  "artifactId": "plugin.nggaigc",
4
- "version": "1.1.44",
5
- "sourceCommit": "daa6585bd3c9c57b77a92ac452a866566efe546c",
6
- "artifactPath": "packages/plugins/nggaigc-plugin-1.1.44.zip",
7
- "sha256": "d3e9c98a65df60f9ffb6426e60341a3c621a62e1779a172f6d7939023be7fb16",
8
- "sizeBytes": 498924,
4
+ "version": "1.1.45",
5
+ "sourceCommit": "241601b7d034ce36442e7738438cbc6c6b3b867d",
6
+ "artifactPath": "packages/plugins/nggaigc-plugin-1.1.45.zip",
7
+ "sha256": "e23b7781579124a1b90ece1558d58d418dfef6a6557152f1581cbb81145281da",
8
+ "sizeBytes": 502168,
9
9
  "fileCount": 24,
10
10
  "platforms": [
11
11
  "win32-x64",
@@ -13,7 +13,7 @@
13
13
  "darwin-arm64"
14
14
  ],
15
15
  "files": {
16
- ".codex-plugin/plugin.json": "44ffcd18f569942856d9b955dacca52dd7559972ba7e42ca5c875084b7c36bb8",
16
+ ".codex-plugin/plugin.json": "89478e096e5cbd46ad156cb06abee9a59973f530e08be815283bf6d4ddc3903f",
17
17
  "installer/install.cjs": "76f4ed73233fd39900fb0bedbc434dea39e52e6b6b4622f3fd388a5ca09c6f62",
18
18
  "installer/legacy-root-digests.json": "922552646a5aa6cff8616c372912a37b1d5f263351206b85785ed9b2eae31641",
19
19
  "installer/signed-pair-1.1.16.json": "ccb22a90302cc5c9c585067ebc211a27b0c9d8a177c2740a5678a58a3d70bfe9",
@@ -24,24 +24,24 @@
24
24
  "migration/seedream-1.1.3/scripts/seedream.mjs": "dcc6af4d945790d9dfd7fac21c60934759d7ac2576a14507d0299f4833942a96",
25
25
  "migration/seedream-1.1.3/SKILL.md": "5c5cf3c2d99376f246425e873c5566436ed528256372ac3208abb60364c4517c",
26
26
  "skills/nggaigc/agents/openai.yaml": "8936d97b9821e687e224e9349cc42be2ee5fc20988562efffbcb70080aedf87c",
27
- "skills/nggaigc/references/catalog-and-updates.md": "990abe5d4e6d34283ad957a372e1892fda2dc1620aa2fb6c5adca28ad3727d89",
28
- "skills/nggaigc/references/source.json": "49025cea97197ff8644448fe35d54addc2f27154f8dfeb7791099badcba28ee8",
27
+ "skills/nggaigc/references/catalog-and-updates.md": "a43bee94a480aa2bf0b1e7038cee6f27fb86d2bd8fa88a28a63678963d7c1530",
28
+ "skills/nggaigc/references/source.json": "26838dd1c3df2347abb97ac47da61148c7099ce9f12c935b788c6fe2e9e26f0c",
29
29
  "skills/nggaigc/scripts/api_client.mjs": "e16bf750a297ef7e88e4843d2fba9c33b412a66516bfe76822d7deec6c153069",
30
30
  "skills/nggaigc/scripts/api_package.mjs": "6beee22aed770cbf501abe6d1d9d252f7a53893eeb9b6399bd26c4ef68470d5a",
31
- "skills/nggaigc/scripts/bootstrap-public.json": "67bc46ee22771782b2186613ecb5ef04ec813e2ce68d5365eb42012703edad94",
32
- "skills/nggaigc/scripts/launch-ngg-client.cjs": "c9295fa3838015d0614cb663a1419e3965ed673b87bba42856828ef8bcf1ade9",
31
+ "skills/nggaigc/scripts/bootstrap-public.json": "4b1aba50683bc50d3ac9c2ec10479366fa10e7452874b67f2cd6b93e3e23952d",
32
+ "skills/nggaigc/scripts/launch-ngg-client.cjs": "5953b9211b31e72b9f7d22a92f954e9b526387f6d86ac4387e0c810f3e01c0da",
33
33
  "skills/nggaigc/scripts/macos-keychain": "b0bdf29d14219d38ca18c7e5c599deb0daa8c8ac70b0babbc7d4fb23673079ec",
34
34
  "skills/nggaigc/scripts/macos-keychain.swift": "1b57e74fde66c824fe970547dd8ca0883a3334c34c065e85a8a2a90d4556a7f5",
35
- "skills/nggaigc/scripts/ngg_client.mjs": "9a8bcb2557dbcb8d8d97c6a930c22ca6833bbfbdbffa742ab6dd0bf00799ee4c",
36
- "skills/nggaigc/scripts/ngg-session.mjs": "4ab35bae06c02a5825d36a8d860273ed9c512f9ee40debd7f8cf32c50fc9d94f",
35
+ "skills/nggaigc/scripts/ngg_client.mjs": "2b4903ad8afe76b0dc970b5519a50d04dcc0e12dfa8f4267fb85d878187c3789",
36
+ "skills/nggaigc/scripts/ngg-session.mjs": "21df7cd76e729a2109e2f8ec53d5a5998e801ab9259ce8d20b9c62f9926b1135",
37
37
  "skills/nggaigc/scripts/supported-platforms.json": "ec8b0754c489e04534a1d9880dd6664ba9c5bcd07789e06d138e0923f23575ff",
38
38
  "skills/nggaigc/scripts/windows-credential.ps1": "80d470fb0c9473d26bd599cafa9f1c2ad69056d4a835888ddcbb5c1548f9d059",
39
- "skills/nggaigc/SKILL.md": "7985e5c6543d56bfb16502f050b2e24bd59172346f6cba3e159b1d1e8a923e52"
39
+ "skills/nggaigc/SKILL.md": "8cb3eb8a03cf144ce15fa01d7d342495c2b11ba0f6a0cf61a493e1e43c83a11f"
40
40
  },
41
41
  "signature": {
42
42
  "algorithm": "ed25519",
43
43
  "keyId": "ngg-signing-skill-release-1",
44
44
  "scope": "artifact-bytes",
45
- "value": "zWO1/CE7wHcX6+KPdluqCzsmbqUp8xy4piqj/Znj7ZpnfZJCMJnfqxn5Nc9G/mjDW1Yqznigg39bs78LrED5BA=="
45
+ "value": "OlUz+AuTVQN/u8Kp+d91twCKkU6VtRPUrzez8iGYLRmLck5lXvG6KUbVabPMIkEirKD/fOCcwJ47SKSsS8x+BQ=="
46
46
  }
47
47
  }
index b07c405..bc17d2a 100644
Binary file