@nggaigc/cli 0.1.44 → 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 +29 -1
- package/dist/agent-selection.js +91 -0
- package/dist/bundle.js +1 -1
- package/dist/errors.js +49 -1
- package/dist/independent-skill.js +31 -9
- package/dist/install.js +57 -19
- package/dist/main.js +100 -10
- package/dist/skill-command.js +25 -16
- package/dist/skill-location.js +96 -0
- package/package.json +2 -2
- package/vendor/manifest.json +15 -15
- package/vendor/{nggaigc-plugin-1.1.43.zip → nggaigc-plugin-1.1.45.zip} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @nggaigc/cli
|
|
2
2
|
|
|
3
|
-
NGGAIGC Codex
|
|
3
|
+
NGGAIGC Skill 的受信安装器。Codex 与 WorkBuddy 使用同一条安装命令,自动识别当前宿主。
|
|
4
4
|
|
|
5
5
|
```powershell
|
|
6
6
|
npx --yes @nggaigc/cli@latest install
|
|
@@ -11,3 +11,31 @@ npx --yes @nggaigc/cli@latest install
|
|
|
11
11
|
CLI 保留 macOS npm symlink 入口与 packed-TGZ launcher 检查,并验证随包的已签名 Plugin;实际版本以包内签名清单为准。支持 Windows x64/arm64 与 Apple 芯片 Mac arm64,使用本机 Node.js 22.x(22.13 起)或 24.x,不下载或内嵌 Node.js。Windows PowerShell 5.1 默认 `Restricted` 时,安装器仅为受信凭据辅助脚本的子进程指定 `RemoteSigned`;不修改系统或用户执行策略。安装成功但连接失败会明确分开报告,无需重装;由于系统 URL 处理器退出码不能证明浏览器窗口已显示,安装器会同时给出短期一次性手动授权链接。
|
|
12
12
|
|
|
13
13
|
macOS arm64 辅助程序沿用已签名 Plugin 中通过完整性校验的字节;Intel Mac 不在支持范围内。实际公开版本和 `@latest` 状态以 npm registry 为准,Apple Silicon 真机验收需单独进行。
|
|
14
|
+
|
|
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 起)
|
|
24
|
+
|
|
25
|
+
下列目标参数与 `client` 桥要求 CLI 0.1.45 或更高版本。旧版本不支持新参数;`@latest` 的实际可用版本以 npm registry 与对应签名制品为准。
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
npx --yes @nggaigc/cli@latest install --agent workbuddy
|
|
29
|
+
npx --yes @nggaigc/cli@latest install --agent universal --no-login
|
|
30
|
+
npx --yes @nggaigc/cli@latest install --skills-dir "/absolute/project/.codebuddy/skills" --no-login
|
|
31
|
+
npx --yes @nggaigc/cli@latest start-skill skill.<id> [version] --skills-dir "/absolute/project/.codebuddy/skills"
|
|
32
|
+
npx --yes @nggaigc/cli@latest client --skills-dir "/absolute/project/.codebuddy/skills" -- <原command> <原args>
|
|
33
|
+
```
|
|
34
|
+
|
|
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` 仅跳过安装后的登录,不绕过签名、哈希、路径检查或后续鉴权。
|
|
36
|
+
|
|
37
|
+
根 Skill 按实际读取的 `SKILL.md` 定位父目录及其上一级 `skillsDirectory`;后续升级与 `start-skill` 显式使用同一目录。准备回执增加 `skillsDirectory`、`rootLauncherPath`,保留 `preparedSkills`;宿主应显式读取返回的真实路径。独立包的原客户端调用使用回执的受信根启动器;`client` 是同目录根包验签桥,保留原命令/参数,不增加 API 专用业务分支。安装新包不证明当前宿主热加载,必要时开启新会话。
|
|
38
|
+
|
|
39
|
+
本地技能包导入可能复制文件,需核导入后的实际路径和独立包同级关系。交互使用宿主可用的提问工具或当前聊天,交付使用宿主可用的媒体/附件能力;不依赖 Codex 专用工具。切换宿主继续复用当前系统用户的系统凭证库和任务历史,不迁移凭据、不要求重新粘贴密钥。资金、权限与未知结果同幂等键恢复规则不变。
|
|
40
|
+
|
|
41
|
+
此功能不增加 Linux、Intel Mac 支持,也不代表已接入或认证 WorkBuddy 官方连接器市场;现有系统、Node.js 与正式发布门禁保持适用。
|
|
@@ -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.
|
|
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
|
-
|
|
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) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, createPublicKey, randomUUID, verify } from "node:crypto";
|
|
2
|
-
import { open, lstat, mkdir, readFile, readdir, rename, rm, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { open, lstat, mkdir, readFile, readdir, realpath, rename, rm, writeFile, } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { resolveSkillLocation, stateDirectoryForSkills, } from "./skill-location.js";
|
|
4
5
|
const ID = /^skill\.[a-z0-9][a-z0-9.-]{0,79}$/;
|
|
5
6
|
const VERSION = /^\d+\.\d+\.\d+$/;
|
|
6
7
|
const DIGEST = /^[a-f0-9]{64}$/;
|
|
@@ -17,6 +18,33 @@ export class IndependentSkillError extends Error {
|
|
|
17
18
|
function fail(code) {
|
|
18
19
|
throw new IndependentSkillError(code);
|
|
19
20
|
}
|
|
21
|
+
async function independentLocation(options, create = false) {
|
|
22
|
+
// Missing programmatic arguments must not select the user's real home.
|
|
23
|
+
if (options.codexHome === undefined &&
|
|
24
|
+
options.skillsDirectory === undefined &&
|
|
25
|
+
options.agent === undefined)
|
|
26
|
+
fail("INSTALL_PATH_UNSAFE");
|
|
27
|
+
try {
|
|
28
|
+
const selected = resolveSkillLocation(options).skillsDirectory;
|
|
29
|
+
if (await hasLinkedPathComponent(selected, create))
|
|
30
|
+
fail("INSTALL_PATH_UNSAFE");
|
|
31
|
+
if (create)
|
|
32
|
+
await mkdir(selected, { recursive: true });
|
|
33
|
+
// One physical target must use one journal, including case aliases on
|
|
34
|
+
// case-insensitive disks. Check links before resolving the actual casing.
|
|
35
|
+
const skillsDirectory = await realpath(selected);
|
|
36
|
+
const legacyDirectory = await realpath(path.join(path.dirname(skillsDirectory), "skills")).catch(() => null);
|
|
37
|
+
return {
|
|
38
|
+
skillsDirectory,
|
|
39
|
+
stateDirectory: legacyDirectory === skillsDirectory
|
|
40
|
+
? path.join(path.dirname(skillsDirectory), ".nggaigc-managed")
|
|
41
|
+
: stateDirectoryForSkills(skillsDirectory),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return fail("INSTALL_PATH_UNSAFE");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
20
48
|
async function hasLinkedPathComponent(value, allowMissing = false) {
|
|
21
49
|
const absolute = path.resolve(value);
|
|
22
50
|
const root = path.parse(absolute).root;
|
|
@@ -795,10 +823,7 @@ async function acquireInstallLock(lock) {
|
|
|
795
823
|
}
|
|
796
824
|
export async function prepareIndependentSkill(options) {
|
|
797
825
|
const ordered = resolve(options.index, options.skillId, options.skillVersion, options.rootSkillVersion, options.platformVersion);
|
|
798
|
-
|
|
799
|
-
fail("INSTALL_PATH_UNSAFE");
|
|
800
|
-
const skillsRoot = path.join(options.codexHome, "skills");
|
|
801
|
-
const stateRoot = path.join(options.codexHome, ".nggaigc-managed");
|
|
826
|
+
const { skillsDirectory: skillsRoot, stateDirectory: stateRoot } = await independentLocation(options, true);
|
|
802
827
|
if (await hasLinkedPathComponent(skillsRoot, true).catch(() => true))
|
|
803
828
|
fail("INSTALL_PATH_UNSAFE");
|
|
804
829
|
if (await hasLinkedPathComponent(stateRoot, true).catch(() => true))
|
|
@@ -943,10 +968,7 @@ export async function rollbackIndependentSkill(options) {
|
|
|
943
968
|
entry.version === options.installedVersion);
|
|
944
969
|
if (!current)
|
|
945
970
|
fail("ROLLBACK_UNAVAILABLE");
|
|
946
|
-
|
|
947
|
-
fail("INSTALL_PATH_UNSAFE");
|
|
948
|
-
const skillsRoot = path.join(options.codexHome, "skills");
|
|
949
|
-
const stateRoot = path.join(options.codexHome, ".nggaigc-managed");
|
|
971
|
+
const { skillsDirectory: skillsRoot, stateDirectory: stateRoot } = await independentLocation(options);
|
|
950
972
|
if (await hasLinkedPathComponent(skillsRoot).catch(() => true))
|
|
951
973
|
fail("INSTALL_PATH_UNSAFE");
|
|
952
974
|
if (await hasLinkedPathComponent(stateRoot).catch(() => true))
|
package/dist/install.js
CHANGED
|
@@ -4,11 +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
|
+
import { resolveSkillLocation, } from "./skill-location.js";
|
|
8
9
|
function defaultBundle() {
|
|
9
10
|
const vendor = fileURLToPath(new URL("../vendor/", import.meta.url));
|
|
10
11
|
return {
|
|
11
|
-
archive: path.join(vendor, "nggaigc-plugin-1.1.
|
|
12
|
+
archive: path.join(vendor, "nggaigc-plugin-1.1.45.zip"),
|
|
12
13
|
manifest: path.join(vendor, "manifest.json"),
|
|
13
14
|
trust: path.join(vendor, "trusted-keys.json"),
|
|
14
15
|
};
|
|
@@ -36,6 +37,47 @@ function installationError(error) {
|
|
|
36
37
|
return new CliError("SKILL_PAIR_RECOVERY_REQUIRED");
|
|
37
38
|
return new CliError("INSTALLATION_FAILED");
|
|
38
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
|
+
}
|
|
39
81
|
async function installedVersion(target) {
|
|
40
82
|
try {
|
|
41
83
|
const marker = JSON.parse(await readFile(path.join(target, ".nggaigc-install.json"), "utf8"));
|
|
@@ -59,11 +101,11 @@ async function installedVersion(target) {
|
|
|
59
101
|
return undefined;
|
|
60
102
|
}
|
|
61
103
|
}
|
|
62
|
-
async function installSkill(installer, source, profile,
|
|
63
|
-
const target = path.join(
|
|
104
|
+
async function installSkill(installer, source, profile, skillsDirectory, expectedVersion, bundleFiles) {
|
|
105
|
+
const target = path.join(skillsDirectory, "nggaigc");
|
|
64
106
|
try {
|
|
65
107
|
const result = await installer.main(["install", "--profile", profile], {
|
|
66
|
-
|
|
108
|
+
skillsDirectory,
|
|
67
109
|
source,
|
|
68
110
|
});
|
|
69
111
|
if (result.status !== "installed" || result.version !== expectedVersion)
|
|
@@ -84,7 +126,7 @@ async function installSkill(installer, source, profile, codexHome, expectedVersi
|
|
|
84
126
|
}
|
|
85
127
|
try {
|
|
86
128
|
const result = await installer.main(["update", "--profile", profile], {
|
|
87
|
-
|
|
129
|
+
skillsDirectory,
|
|
88
130
|
source,
|
|
89
131
|
});
|
|
90
132
|
if (result.status !== "updated" || result.version !== expectedVersion)
|
|
@@ -115,10 +157,8 @@ async function connect(launcher, options, report, probe) {
|
|
|
115
157
|
catch (error) {
|
|
116
158
|
if (!probe && fixedErrorCode(error) === "SETUP_REQUIRED")
|
|
117
159
|
configured = false;
|
|
118
|
-
else if (fixedErrorCode(error) === "POWERSHELL_EXECUTION_POLICY_BLOCKED")
|
|
119
|
-
throw new CliError("POWERSHELL_EXECUTION_POLICY_BLOCKED");
|
|
120
160
|
else
|
|
121
|
-
throw
|
|
161
|
+
throw connectionError(error, "CONNECTION_VERIFICATION_FAILED");
|
|
122
162
|
}
|
|
123
163
|
if (!configured) {
|
|
124
164
|
report("launching");
|
|
@@ -156,11 +196,7 @@ async function connect(launcher, options, report, probe) {
|
|
|
156
196
|
throw new Error();
|
|
157
197
|
}
|
|
158
198
|
catch (error) {
|
|
159
|
-
|
|
160
|
-
throw new CliError("BROWSER_OPEN_FAILED");
|
|
161
|
-
if (fixedErrorCode(error) === "POWERSHELL_EXECUTION_POLICY_BLOCKED")
|
|
162
|
-
throw new CliError("POWERSHELL_EXECUTION_POLICY_BLOCKED");
|
|
163
|
-
throw new CliError("BROWSER_AUTHORIZATION_FAILED");
|
|
199
|
+
throw connectionError(error, "BROWSER_AUTHORIZATION_FAILED");
|
|
164
200
|
}
|
|
165
201
|
finally {
|
|
166
202
|
process.off("SIGINT", interrupt);
|
|
@@ -180,9 +216,7 @@ function assertDisposableRoot(value, expectedParent) {
|
|
|
180
216
|
export async function runInstall(options = {}) {
|
|
181
217
|
validateRuntime(options.platform ?? process.platform, options.architecture ?? process.arch);
|
|
182
218
|
const profile = safeProfile(options.profile ?? os.homedir());
|
|
183
|
-
const
|
|
184
|
-
(options.profile ? undefined : process.env.CODEX_HOME) ??
|
|
185
|
-
path.join(profile, ".codex"));
|
|
219
|
+
const { skillsDirectory } = resolveSkillLocation(options);
|
|
186
220
|
const report = options.reporter ?? (() => undefined);
|
|
187
221
|
report("verifying");
|
|
188
222
|
const bundle = await verifyBundle(options.bundle ?? defaultBundle());
|
|
@@ -200,8 +234,10 @@ export async function runInstall(options = {}) {
|
|
|
200
234
|
const installer = require(path.join(extracted, "installer", "install.cjs"));
|
|
201
235
|
if (!installer || typeof installer.main !== "function")
|
|
202
236
|
throw new CliError("INSTALLATION_FAILED");
|
|
203
|
-
const installation = await installSkill(installer, path.join(extracted, "skills", "nggaigc"), profile,
|
|
204
|
-
const installedScripts = path.join(
|
|
237
|
+
const installation = await installSkill(installer, path.join(extracted, "skills", "nggaigc"), profile, skillsDirectory, bundle.version, bundle.files);
|
|
238
|
+
const installedScripts = path.join(skillsDirectory, "nggaigc", "scripts");
|
|
239
|
+
if (options.connect === false)
|
|
240
|
+
return { status: "installed", installation, version: bundle.version };
|
|
205
241
|
const launcher = require(path.join(installedScripts, "launch-ngg-client.cjs"));
|
|
206
242
|
if (!launcher ||
|
|
207
243
|
typeof launcher.main !== "function" ||
|
|
@@ -214,11 +250,13 @@ export async function runInstall(options = {}) {
|
|
|
214
250
|
catch (error) {
|
|
215
251
|
if (!(error instanceof CliError))
|
|
216
252
|
throw error;
|
|
253
|
+
const diagnostics = safeConnectionDiagnostics(error);
|
|
217
254
|
return {
|
|
218
255
|
status: "installed-unconnected",
|
|
219
256
|
installation,
|
|
220
257
|
version: bundle.version,
|
|
221
258
|
connectionError: error.code,
|
|
259
|
+
...(diagnostics ? { connectionDiagnostics: diagnostics } : {}),
|
|
222
260
|
};
|
|
223
261
|
}
|
|
224
262
|
}
|
package/dist/main.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { CliError, fixedErrorCode, safeConnectionDiagnostics, } from "./errors.js";
|
|
2
3
|
import { runInstall } from "./install.js";
|
|
3
|
-
import { runSkillStartCommand, } from "./skill-command.js";
|
|
4
|
+
import { runSkillStartCommand, runClientCommand, } from "./skill-command.js";
|
|
5
|
+
import { parseSkillLocation, resolveSkillLocation } from "./skill-location.js";
|
|
6
|
+
import { AgentSelectionRequired, selectCommandLocation, selectionRequiredResult, } from "./agent-selection.js";
|
|
4
7
|
const messages = {
|
|
5
8
|
authorizing: "浏览器已成功启动,请在 NGGAIGC 页面确认连接…",
|
|
6
9
|
connected: "NGGAIGC Skill 已安装,设备连接验证成功。",
|
|
@@ -11,14 +14,23 @@ const messages = {
|
|
|
11
14
|
};
|
|
12
15
|
const errorMessages = {
|
|
13
16
|
ARCHIVE_EXTRACTION_FAILED: "安装包无法安全解压,请稍后重试。",
|
|
14
|
-
BROWSER_AUTHORIZATION_FAILED: "
|
|
15
|
-
BROWSER_OPEN_FAILED: "
|
|
16
|
-
CONNECTION_VERIFICATION_FAILED: "
|
|
17
|
+
BROWSER_AUTHORIZATION_FAILED: "网页授权未完成;请在当前 Agent 中调用 NGGAIGC 继续连接,无需重装。",
|
|
18
|
+
BROWSER_OPEN_FAILED: "系统未能打开默认浏览器,也无法提供手动授权入口;请在当前 Agent 中调用 NGGAIGC 重试连接,无需重装。",
|
|
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: "本地凭据会话超时;请先检查当前本地进程,不要重复网页授权。",
|
|
17
29
|
POWERSHELL_EXECUTION_POLICY_BLOCKED: "Windows 脚本执行受到系统或组织策略限制,设备连接未完成。安装器没有更改执行策略。",
|
|
18
30
|
INSTALLATION_FAILED: "本地安装未完成,请检查现有文件状态后重试。",
|
|
19
31
|
SKILL_PAIR_UPGRADE_REQUIRED: "检测到无法安全配套迁移的本地 Seedream Skill,已停止升级。请保留旧 Skill 和任务文件并联系支持。",
|
|
20
32
|
SKILL_PAIR_RECOVERY_REQUIRED: "检测到本地组合安装中断或目录变化,已停止自动操作。请保留相关目录和任务文件并联系支持。",
|
|
21
|
-
INVALID_INPUT: "
|
|
33
|
+
INVALID_INPUT: "命令格式无效。使用 --help 查看 install、start-skill 和 client 的目录选项。",
|
|
22
34
|
PACKAGE_VERIFICATION_FAILED: "安装包签名或完整性验证失败,已停止安装。",
|
|
23
35
|
RUNTIME_UNSUPPORTED: "请使用 Node.js 22.x(22.13 起)或 24.x 版本。",
|
|
24
36
|
UNSUPPORTED_PLATFORM: "当前系统或架构尚未获得此签名安装包支持,请使用已支持的平台。",
|
|
@@ -26,24 +38,91 @@ const errorMessages = {
|
|
|
26
38
|
export async function main(args, runtime = {}) {
|
|
27
39
|
const output = runtime.output ?? ((line) => process.stdout.write(line));
|
|
28
40
|
const errorOutput = runtime.error ?? ((line) => process.stderr.write(line));
|
|
29
|
-
if (args
|
|
41
|
+
if (args.length === 1 && ["--help", "-h", "help"].includes(args[0])) {
|
|
42
|
+
output([
|
|
43
|
+
"NGGAIGC CLI",
|
|
44
|
+
"install [--agent codex|workbuddy|universal | --skills-dir <absolute-directory>] [--no-login]",
|
|
45
|
+
"start-skill skill.<id> [version] [--agent codex|workbuddy|universal | --skills-dir <absolute-directory>]",
|
|
46
|
+
"client [--agent codex|workbuddy|universal | --skills-dir <absolute-directory>] -- <command> [arguments]",
|
|
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.",
|
|
49
|
+
"Use the same target for install and subsequent commands. --no-login installs verified files without opening a browser.",
|
|
50
|
+
"client examples: -- status, -- login, -- account-status, -- workflows, -- history, -- disconnect",
|
|
51
|
+
"",
|
|
52
|
+
].join("\n"));
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (args[0] === "client") {
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const interrupt = () => controller.abort();
|
|
58
|
+
try {
|
|
59
|
+
const separator = args.indexOf("--");
|
|
60
|
+
if (separator < 1 || separator === args.length - 1)
|
|
61
|
+
throw new CliError("INVALID_INPUT");
|
|
62
|
+
const { location, positional } = parseSkillLocation(args.slice(1, separator));
|
|
63
|
+
if (positional.length)
|
|
64
|
+
throw new CliError("INVALID_INPUT");
|
|
65
|
+
process.once("SIGINT", interrupt);
|
|
66
|
+
const selectedLocation = await selectCommandLocation(location, runtime.agentSelection);
|
|
67
|
+
const result = await (runtime.client ?? runClientCommand)(args.slice(separator + 1), selectedLocation, {
|
|
68
|
+
signal: controller.signal,
|
|
69
|
+
browserManualLink: (url) => errorOutput(`如果没有看到授权网页,请在本机浏览器打开此一次性链接;不要转发或保存:\n${url}\n`),
|
|
70
|
+
browserFallback: (url) => errorOutput(`浏览器未能自动打开,请在本机手动打开此一次性链接;不要转发或保存:\n${url}\n`),
|
|
71
|
+
});
|
|
72
|
+
output(`${JSON.stringify(result)}\n`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
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`);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
process.off("SIGINT", interrupt);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = parseSkillLocation(args.slice(1));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
errorOutput(`${errorMessages.INVALID_INPUT}\n`);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
const parameters = parsed.positional;
|
|
96
|
+
if (args[0] === "start-skill" &&
|
|
97
|
+
(parameters.length === 1 || parameters.length === 2)) {
|
|
30
98
|
try {
|
|
31
|
-
const
|
|
99
|
+
const location = await selectCommandLocation({ ...runtime.skillOptions, ...parsed.location }, runtime.agentSelection);
|
|
100
|
+
const result = await (runtime.startSkill ?? runSkillStartCommand)(parameters[0], parameters[1], location);
|
|
32
101
|
output(`${JSON.stringify({ status: "ready", ...result })}\n`);
|
|
33
102
|
return 0;
|
|
34
103
|
}
|
|
35
104
|
catch (error) {
|
|
105
|
+
if (error instanceof AgentSelectionRequired) {
|
|
106
|
+
output(`${JSON.stringify(selectionRequiredResult(error))}\n`);
|
|
107
|
+
return 3;
|
|
108
|
+
}
|
|
36
109
|
const code = fixedErrorCode(error) ?? "SKILL_START_UNAVAILABLE";
|
|
37
110
|
errorOutput(`${JSON.stringify({ error: { code } })}\n`);
|
|
38
111
|
return 1;
|
|
39
112
|
}
|
|
40
113
|
}
|
|
41
|
-
if (args
|
|
114
|
+
if (args[0] !== "install" ||
|
|
115
|
+
!(parameters.length === 0 ||
|
|
116
|
+
(parameters.length === 1 && parameters[0] === "--no-login"))) {
|
|
42
117
|
errorOutput(`${errorMessages.INVALID_INPUT}\n`);
|
|
43
118
|
return 1;
|
|
44
119
|
}
|
|
45
120
|
try {
|
|
121
|
+
const location = await selectCommandLocation(parsed.location, runtime.agentSelection);
|
|
122
|
+
const { skillsDirectory } = resolveSkillLocation(location);
|
|
46
123
|
const result = await (runtime.install ?? runInstall)({
|
|
124
|
+
...location,
|
|
125
|
+
...(parameters[0] === "--no-login" ? { connect: false } : {}),
|
|
47
126
|
reporter: (event) => output(`${messages[event]}\n`),
|
|
48
127
|
launcherOptionsFactory: () => ({
|
|
49
128
|
browserManualLink: (url) => {
|
|
@@ -56,13 +135,24 @@ export async function main(args, runtime = {}) {
|
|
|
56
135
|
},
|
|
57
136
|
}),
|
|
58
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`);
|
|
59
145
|
if (result.status === "installed-unconnected") {
|
|
60
|
-
errorOutput(`NGGAIGC Skill ${result.version} 文件已${result.installation === "unchanged" ? "存在" : "安装"},但设备尚未连接。${errorMessages[result.connectionError] ?? "
|
|
146
|
+
errorOutput(`NGGAIGC Skill ${result.version} 文件已${result.installation === "unchanged" ? "存在" : "安装"},但设备尚未连接。${errorMessages[result.connectionError] ?? "请在当前 Agent 中检查连接状态。"}\n`);
|
|
61
147
|
return 2;
|
|
62
148
|
}
|
|
63
149
|
return 0;
|
|
64
150
|
}
|
|
65
151
|
catch (error) {
|
|
152
|
+
if (error instanceof AgentSelectionRequired) {
|
|
153
|
+
output(`${JSON.stringify(selectionRequiredResult(error))}\n`);
|
|
154
|
+
return 3;
|
|
155
|
+
}
|
|
66
156
|
const code = error instanceof CliError ? error.code : "INSTALLATION_FAILED";
|
|
67
157
|
errorOutput(`${errorMessages[code] ?? errorMessages.INSTALLATION_FAILED}\n`);
|
|
68
158
|
return 1;
|
package/dist/skill-command.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash, createPublicKey } from "node:crypto";
|
|
2
2
|
import { readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { resolveSkillLocation, } from "./skill-location.js";
|
|
7
7
|
import { trustedKeyId, trustedKeySpkiSha256, verifyBundle, verifyInstalledSkill, } from "./bundle.js";
|
|
8
8
|
import { IndependentSkillError, projectActiveSkillCatalog, startIndependentSkill, } from "./independent-skill.js";
|
|
9
9
|
function fail(code) {
|
|
@@ -12,14 +12,14 @@ function fail(code) {
|
|
|
12
12
|
function record(value) {
|
|
13
13
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
14
14
|
}
|
|
15
|
-
async function productionDependencies(
|
|
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.
|
|
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
|
});
|
|
22
|
-
const root = path.join(
|
|
22
|
+
const root = path.join(skillsDirectory, "nggaigc");
|
|
23
23
|
await verifyInstalledSkill(root, bundle.files);
|
|
24
24
|
const source = JSON.parse(await readFile(path.join(root, "references", "source.json"), "utf8"));
|
|
25
25
|
if (!record(source) ||
|
|
@@ -58,24 +58,28 @@ async function productionDependencies(codexHome) {
|
|
|
58
58
|
rootSkillVersion: source.version,
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
/** Generic bridge only: the verified launcher owns command and API contracts. */
|
|
62
|
+
export async function runClientCommand(args, options = {}, launcherOptions = {}) {
|
|
63
|
+
if (args.length === 0)
|
|
64
|
+
fail("INVALID_INPUT");
|
|
65
|
+
const { skillsDirectory } = resolveSkillLocation(options);
|
|
66
|
+
const { launcher } = await productionDependencies(skillsDirectory);
|
|
67
|
+
return launcher.main(args, launcherOptions);
|
|
68
|
+
}
|
|
61
69
|
/** Candidate npm entry: active catalog → server admission → verified local Skill path. */
|
|
62
70
|
export async function runSkillStartCommand(skillId, skillVersion, options = {}) {
|
|
63
71
|
if (!/^skill\.[a-z0-9][a-z0-9.-]{0,79}$/.test(skillId) ||
|
|
64
72
|
(skillVersion !== undefined && !/^\d+\.\d+\.\d+$/.test(skillVersion)))
|
|
65
73
|
fail("INVALID_INPUT");
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
fail("INSTALL_PATH_UNSAFE");
|
|
71
|
-
const codexHome = process.platform === "win32"
|
|
72
|
-
? await realpath(configuredCodexHome).catch((error) => record(error) && error.code === "ENOENT"
|
|
73
|
-
? configuredCodexHome
|
|
74
|
+
const configuredDirectory = resolveSkillLocation(options).skillsDirectory;
|
|
75
|
+
const skillsDirectory = process.platform === "win32"
|
|
76
|
+
? await realpath(configuredDirectory).catch((error) => record(error) && error.code === "ENOENT"
|
|
77
|
+
? configuredDirectory
|
|
74
78
|
: fail("INSTALL_PATH_UNSAFE"))
|
|
75
|
-
:
|
|
79
|
+
: configuredDirectory;
|
|
76
80
|
const production = options.launcher && options.trust && options.rootSkillVersion
|
|
77
81
|
? null
|
|
78
|
-
: await productionDependencies(
|
|
82
|
+
: await productionDependencies(skillsDirectory);
|
|
79
83
|
const launcher = options.launcher ?? production.launcher;
|
|
80
84
|
const trust = options.trust ?? production.trust;
|
|
81
85
|
const rootSkillVersion = options.rootSkillVersion ?? production.rootSkillVersion;
|
|
@@ -96,13 +100,13 @@ export async function runSkillStartCommand(skillId, skillVersion, options = {})
|
|
|
96
100
|
if (matches.length !== 1)
|
|
97
101
|
fail("SKILL_UNAVAILABLE");
|
|
98
102
|
const selected = matches[0];
|
|
99
|
-
|
|
103
|
+
const result = await startIndependentSkill({
|
|
100
104
|
index,
|
|
101
105
|
skillId: selected.id,
|
|
102
106
|
skillVersion: selected.version,
|
|
103
107
|
rootSkillVersion,
|
|
104
108
|
platformVersion,
|
|
105
|
-
|
|
109
|
+
skillsDirectory,
|
|
106
110
|
trust,
|
|
107
111
|
fetcher: options.fetcher,
|
|
108
112
|
admit: async (identity) => {
|
|
@@ -117,4 +121,9 @@ export async function runSkillStartCommand(skillId, skillVersion, options = {})
|
|
|
117
121
|
return answer;
|
|
118
122
|
},
|
|
119
123
|
});
|
|
124
|
+
return {
|
|
125
|
+
...result,
|
|
126
|
+
skillsDirectory,
|
|
127
|
+
rootLauncherPath: path.join(skillsDirectory, "nggaigc", "scripts", "launch-ngg-client.cjs"),
|
|
128
|
+
};
|
|
120
129
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { lstatSync } from "node:fs";
|
|
4
|
+
import { CliError } from "./errors.js";
|
|
5
|
+
export function absoluteDirectory(value) {
|
|
6
|
+
if (!path.isAbsolute(value) || /[\0\r\n]/u.test(value))
|
|
7
|
+
throw new CliError("INVALID_INPUT");
|
|
8
|
+
const resolved = path.resolve(value);
|
|
9
|
+
if (resolved === path.parse(resolved).root)
|
|
10
|
+
throw new CliError("INVALID_INPUT");
|
|
11
|
+
return resolved;
|
|
12
|
+
}
|
|
13
|
+
export function stateDirectoryForSkills(skillsDirectory) {
|
|
14
|
+
// Preserve existing */skills journals. Other targets use a different leaf:
|
|
15
|
+
// /x and /x/skills must never share backups or installation locks.
|
|
16
|
+
return path.basename(skillsDirectory) === "skills"
|
|
17
|
+
? path.join(path.dirname(skillsDirectory), ".nggaigc-managed")
|
|
18
|
+
: path.join(skillsDirectory, ".nggaigc-managed", "custom-target");
|
|
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
|
+
}
|
|
44
|
+
/** One explicit location for root installation and every dependent Skill. */
|
|
45
|
+
export function resolveSkillLocation(options = {}, environment = process.env) {
|
|
46
|
+
if ([options.agent, options.skillsDirectory, options.codexHome].filter((value) => value !== undefined).length > 1)
|
|
47
|
+
throw new CliError("INVALID_INPUT");
|
|
48
|
+
if (options.skillsDirectory !== undefined) {
|
|
49
|
+
const skillsDirectory = absoluteDirectory(options.skillsDirectory);
|
|
50
|
+
return {
|
|
51
|
+
skillsDirectory,
|
|
52
|
+
stateDirectory: stateDirectoryForSkills(skillsDirectory),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const profile = absoluteDirectory(options.profile ?? os.homedir());
|
|
56
|
+
const agent = options.agent ?? "codex";
|
|
57
|
+
if (!["codex", "workbuddy", "universal"].includes(agent))
|
|
58
|
+
throw new CliError("INVALID_INPUT");
|
|
59
|
+
const home = absoluteDirectory(options.codexHome ??
|
|
60
|
+
(agent === "codex"
|
|
61
|
+
? ((options.profile === undefined
|
|
62
|
+
? environment.CODEX_HOME
|
|
63
|
+
: undefined) ?? path.join(profile, ".codex"))
|
|
64
|
+
: agent === "workbuddy"
|
|
65
|
+
? workbuddyHome(profile, options.profile === undefined ? environment : {})
|
|
66
|
+
: path.join(profile, ".agents")));
|
|
67
|
+
return {
|
|
68
|
+
skillsDirectory: path.join(home, "skills"),
|
|
69
|
+
// Explicit and named-host calls for the same directory share one journal.
|
|
70
|
+
stateDirectory: stateDirectoryForSkills(path.join(home, "skills")),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function parseSkillLocation(args) {
|
|
74
|
+
const location = {};
|
|
75
|
+
const positional = [];
|
|
76
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
77
|
+
const value = args[index];
|
|
78
|
+
if (value !== "--agent" && value !== "--skills-dir") {
|
|
79
|
+
positional.push(value);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (location.agent !== undefined || location.skillsDirectory !== undefined)
|
|
83
|
+
throw new CliError("INVALID_INPUT");
|
|
84
|
+
const argument = args[++index];
|
|
85
|
+
if (!argument || argument.startsWith("--"))
|
|
86
|
+
throw new CliError("INVALID_INPUT");
|
|
87
|
+
if (value === "--agent") {
|
|
88
|
+
if (!["codex", "workbuddy", "universal"].includes(argument))
|
|
89
|
+
throw new CliError("INVALID_INPUT");
|
|
90
|
+
location.agent = argument;
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
location.skillsDirectory = absoluteDirectory(argument);
|
|
94
|
+
}
|
|
95
|
+
return { location, positional };
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nggaigc/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Secure
|
|
3
|
+
"version": "0.1.47",
|
|
4
|
+
"description": "Secure NGGAIGC Skill installer for Codex, WorkBuddy and compatible agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nggaigc": "dist/bin.js"
|
package/vendor/manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "nggaigc-plugin-package/v1",
|
|
3
3
|
"artifactId": "plugin.nggaigc",
|
|
4
|
-
"version": "1.1.
|
|
5
|
-
"sourceCommit": "
|
|
6
|
-
"artifactPath": "packages/plugins/nggaigc-plugin-1.1.
|
|
7
|
-
"sha256": "
|
|
8
|
-
"sizeBytes":
|
|
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,8 +13,8 @@
|
|
|
13
13
|
"darwin-arm64"
|
|
14
14
|
],
|
|
15
15
|
"files": {
|
|
16
|
-
".codex-plugin/plugin.json": "
|
|
17
|
-
"installer/install.cjs": "
|
|
16
|
+
".codex-plugin/plugin.json": "89478e096e5cbd46ad156cb06abee9a59973f530e08be815283bf6d4ddc3903f",
|
|
17
|
+
"installer/install.cjs": "76f4ed73233fd39900fb0bedbc434dea39e52e6b6b4622f3fd388a5ca09c6f62",
|
|
18
18
|
"installer/legacy-root-digests.json": "922552646a5aa6cff8616c372912a37b1d5f263351206b85785ed9b2eae31641",
|
|
19
19
|
"installer/signed-pair-1.1.16.json": "ccb22a90302cc5c9c585067ebc211a27b0c9d8a177c2740a5678a58a3d70bfe9",
|
|
20
20
|
"installer/signed-pair-1.1.17.json": "a3697506c929aaa085a6d994fc3a1b9a87e522ec6e1e32f3f760e6a534effa2b",
|
|
@@ -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": "
|
|
28
|
-
"skills/nggaigc/references/source.json": "
|
|
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": "
|
|
32
|
-
"skills/nggaigc/scripts/launch-ngg-client.cjs": "
|
|
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": "
|
|
36
|
-
"skills/nggaigc/scripts/ngg-session.mjs": "
|
|
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": "
|
|
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": "
|
|
45
|
+
"value": "OlUz+AuTVQN/u8Kp+d91twCKkU6VtRPUrzez8iGYLRmLck5lXvG6KUbVabPMIkEirKD/fOCcwJ47SKSsS8x+BQ=="
|
|
46
46
|
}
|
|
47
47
|
}
|
|
index bbbb0a7..bc17d2a 100644
|
|
|
Binary file
|