@nggaigc/cli 0.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 +11 -0
- package/dist/bin.js +37 -0
- package/dist/bundle.js +247 -0
- package/dist/errors.js +17 -0
- package/dist/install.js +167 -0
- package/package.json +32 -0
- package/scripts/expand-archive.ps1 +7 -0
- package/vendor/manifest.json +30 -0
- package/vendor/nggaigc-plugin-1.1.1.zip +0 -0
- package/vendor/trusted-keys.json +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @nggaigc/cli
|
|
2
|
+
|
|
3
|
+
NGGAIGC Codex Skill 的 Windows 一命令安装器。
|
|
4
|
+
|
|
5
|
+
```powershell
|
|
6
|
+
npx --yes @nggaigc/cli@latest install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
安装器会先验证内置 Plugin 的 Ed25519 签名、归档哈希和逐文件哈希,再原子安装 Skill。首次运行会打开 NGGAIGC 浏览器授权页;短期授权值只通过浏览器片段和本机回环请求返回,设备凭据只通过标准输入进入 Windows Credential Manager。安装器不接受 API Key、授权码或设备凭据参数,也不从环境变量读取这些值。
|
|
10
|
+
|
|
11
|
+
当前候选支持 Windows x64/arm64,并要求 Node.js 22.13 或更高的 22.x 版本。
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { CliError } from "./errors.js";
|
|
3
|
+
import { runInstall } from "./install.js";
|
|
4
|
+
const messages = {
|
|
5
|
+
authorizing: "正在打开浏览器,请在 NGGAIGC 页面确认连接…",
|
|
6
|
+
connected: "NGGAIGC Skill 已安装,设备连接验证成功。",
|
|
7
|
+
installing: "正在安全安装已签名的 NGGAIGC Skill…",
|
|
8
|
+
verifying: "正在验证 NGGAIGC 签名安装包…",
|
|
9
|
+
};
|
|
10
|
+
const errorMessages = {
|
|
11
|
+
ARCHIVE_EXTRACTION_FAILED: "安装包无法安全解压,请稍后重试。",
|
|
12
|
+
BROWSER_AUTHORIZATION_FAILED: "浏览器授权未完成,请重新运行安装命令。",
|
|
13
|
+
CONNECTION_VERIFICATION_FAILED: "设备连接验证失败,请重新运行安装命令。",
|
|
14
|
+
INSTALLATION_FAILED: "本地安装未完成,已有文件不会被静默覆盖。",
|
|
15
|
+
INVALID_INPUT: "命令格式无效。请仅运行:npx --yes @nggaigc/cli@latest install",
|
|
16
|
+
PACKAGE_VERIFICATION_FAILED: "安装包签名或完整性验证失败,已停止安装。",
|
|
17
|
+
RUNTIME_UNSUPPORTED: "请使用 Node.js 22.13 或更高的 22.x 版本。",
|
|
18
|
+
UNSUPPORTED_PLATFORM: "当前候选仅支持 Windows x64 或 Windows arm64。",
|
|
19
|
+
};
|
|
20
|
+
export async function main(args) {
|
|
21
|
+
if (args.length !== 1 || args[0] !== "install") {
|
|
22
|
+
process.stderr.write(`${errorMessages.INVALID_INPUT}\n`);
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await runInstall({
|
|
27
|
+
reporter: (event) => process.stdout.write(`${messages[event]}\n`),
|
|
28
|
+
});
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const code = error instanceof CliError ? error.code : "INSTALLATION_FAILED";
|
|
33
|
+
process.stderr.write(`${errorMessages[code] ?? errorMessages.INSTALLATION_FAILED}\n`);
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
process.exitCode = await main(process.argv.slice(2));
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash, createPublicKey, verify as verifySignature, } from "node:crypto";
|
|
3
|
+
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { CliError } from "./errors.js";
|
|
7
|
+
const manifestKeys = [
|
|
8
|
+
"artifactId",
|
|
9
|
+
"artifactPath",
|
|
10
|
+
"fileCount",
|
|
11
|
+
"files",
|
|
12
|
+
"platforms",
|
|
13
|
+
"schemaVersion",
|
|
14
|
+
"sha256",
|
|
15
|
+
"signature",
|
|
16
|
+
"sizeBytes",
|
|
17
|
+
"sourceCommit",
|
|
18
|
+
"version",
|
|
19
|
+
];
|
|
20
|
+
const signatureKeys = ["algorithm", "keyId", "scope", "value"];
|
|
21
|
+
const archiveName = "nggaigc-plugin-1.1.1.zip";
|
|
22
|
+
const trustedKeyId = "ngg-signing-skill-release-1";
|
|
23
|
+
const trustedKeySpkiSha256 = "3d7450a1a26785d900be03e52b4b7d0b2e84fe4e4fcb6f2e903c1371b0b96cee";
|
|
24
|
+
function record(value) {
|
|
25
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function exactKeys(value, expected) {
|
|
28
|
+
return (JSON.stringify(Object.keys(value).sort()) ===
|
|
29
|
+
JSON.stringify([...expected].sort()));
|
|
30
|
+
}
|
|
31
|
+
function safeArchivePath(value) {
|
|
32
|
+
return (value.length >= 1 &&
|
|
33
|
+
value.length <= 240 &&
|
|
34
|
+
!value.startsWith("/") &&
|
|
35
|
+
!value.includes("\\") &&
|
|
36
|
+
!value.includes("\0") &&
|
|
37
|
+
value
|
|
38
|
+
.split("/")
|
|
39
|
+
.every((part) => part !== "" && part !== "." && part !== ".."));
|
|
40
|
+
}
|
|
41
|
+
async function json(file) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new CliError("PACKAGE_VERIFICATION_FAILED");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function verifyBundle(input) {
|
|
50
|
+
try {
|
|
51
|
+
const manifestValue = await json(input.manifest);
|
|
52
|
+
const trustValue = await json(input.trust);
|
|
53
|
+
const signature = record(manifestValue)
|
|
54
|
+
? manifestValue.signature
|
|
55
|
+
: undefined;
|
|
56
|
+
if (!record(manifestValue) ||
|
|
57
|
+
!exactKeys(manifestValue, manifestKeys) ||
|
|
58
|
+
manifestValue.schemaVersion !== "nggaigc-plugin-package/v1" ||
|
|
59
|
+
manifestValue.artifactId !== "plugin.nggaigc" ||
|
|
60
|
+
manifestValue.version !== "1.1.1" ||
|
|
61
|
+
manifestValue.artifactPath !== `packages/plugins/${archiveName}` ||
|
|
62
|
+
typeof manifestValue.sourceCommit !== "string" ||
|
|
63
|
+
!/^[a-f0-9]{40}$/.test(manifestValue.sourceCommit) ||
|
|
64
|
+
typeof manifestValue.sha256 !== "string" ||
|
|
65
|
+
!/^[a-f0-9]{64}$/.test(manifestValue.sha256) ||
|
|
66
|
+
typeof manifestValue.sizeBytes !== "number" ||
|
|
67
|
+
!Number.isSafeInteger(manifestValue.sizeBytes) ||
|
|
68
|
+
manifestValue.sizeBytes < 1 ||
|
|
69
|
+
manifestValue.sizeBytes > 64 * 1024 * 1024 ||
|
|
70
|
+
typeof manifestValue.fileCount !== "number" ||
|
|
71
|
+
!Number.isSafeInteger(manifestValue.fileCount) ||
|
|
72
|
+
!Array.isArray(manifestValue.platforms) ||
|
|
73
|
+
JSON.stringify(manifestValue.platforms) !==
|
|
74
|
+
JSON.stringify(["win32-x64", "win32-arm64"]) ||
|
|
75
|
+
!record(manifestValue.files) ||
|
|
76
|
+
Object.keys(manifestValue.files).length !== manifestValue.fileCount ||
|
|
77
|
+
Object.entries(manifestValue.files).some(([name, digest]) => !safeArchivePath(name) ||
|
|
78
|
+
typeof digest !== "string" ||
|
|
79
|
+
!/^[a-f0-9]{64}$/.test(digest)) ||
|
|
80
|
+
!record(signature) ||
|
|
81
|
+
!exactKeys(signature, signatureKeys) ||
|
|
82
|
+
signature.algorithm !== "ed25519" ||
|
|
83
|
+
signature.scope !== "artifact-bytes" ||
|
|
84
|
+
signature.keyId !== trustedKeyId ||
|
|
85
|
+
typeof signature.value !== "string" ||
|
|
86
|
+
!record(trustValue) ||
|
|
87
|
+
!exactKeys(trustValue, ["keys", "schemaVersion"]) ||
|
|
88
|
+
trustValue.schemaVersion !== "ngg-trusted-package-keys/v1" ||
|
|
89
|
+
!Array.isArray(trustValue.keys))
|
|
90
|
+
throw new Error();
|
|
91
|
+
const trustKeys = trustValue.keys;
|
|
92
|
+
const trustedKey = trustKeys.find((value) => record(value) &&
|
|
93
|
+
exactKeys(value, ["keyId", "publicKeyPem"]) &&
|
|
94
|
+
value.keyId === signature.keyId &&
|
|
95
|
+
typeof value.publicKeyPem === "string");
|
|
96
|
+
if (!record(trustedKey) || typeof trustedKey.publicKeyPem !== "string")
|
|
97
|
+
throw new Error();
|
|
98
|
+
const publicKey = createPublicKey(trustedKey.publicKeyPem);
|
|
99
|
+
const publicKeyFingerprint = createHash("sha256")
|
|
100
|
+
.update(publicKey.export({ type: "spki", format: "der" }))
|
|
101
|
+
.digest("hex");
|
|
102
|
+
if (publicKeyFingerprint !== trustedKeySpkiSha256)
|
|
103
|
+
throw new Error();
|
|
104
|
+
const archiveFacts = await lstat(input.archive);
|
|
105
|
+
if (!archiveFacts.isFile() || archiveFacts.isSymbolicLink())
|
|
106
|
+
throw new Error();
|
|
107
|
+
const archive = await readFile(input.archive);
|
|
108
|
+
if (archive.length !== manifestValue.sizeBytes ||
|
|
109
|
+
createHash("sha256").update(archive).digest("hex") !==
|
|
110
|
+
manifestValue.sha256 ||
|
|
111
|
+
!verifySignature(null, archive, publicKey, Buffer.from(signature.value, "base64")))
|
|
112
|
+
throw new Error();
|
|
113
|
+
return {
|
|
114
|
+
archive: path.resolve(input.archive),
|
|
115
|
+
files: manifestValue.files,
|
|
116
|
+
version: manifestValue.version,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error instanceof CliError)
|
|
121
|
+
throw error;
|
|
122
|
+
throw new CliError("PACKAGE_VERIFICATION_FAILED");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function powershellPath() {
|
|
126
|
+
return path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
127
|
+
}
|
|
128
|
+
export async function extractBundle(archive, destination) {
|
|
129
|
+
const script = fileURLToPath(new URL("../scripts/expand-archive.ps1", import.meta.url));
|
|
130
|
+
await new Promise((resolve, reject) => {
|
|
131
|
+
const child = spawn(powershellPath(), [
|
|
132
|
+
"-NoLogo",
|
|
133
|
+
"-NoProfile",
|
|
134
|
+
"-NonInteractive",
|
|
135
|
+
"-File",
|
|
136
|
+
script,
|
|
137
|
+
"-Archive",
|
|
138
|
+
archive,
|
|
139
|
+
"-Destination",
|
|
140
|
+
destination,
|
|
141
|
+
], {
|
|
142
|
+
windowsHide: true,
|
|
143
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
144
|
+
env: {
|
|
145
|
+
SystemRoot: process.env.SystemRoot,
|
|
146
|
+
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
147
|
+
USERPROFILE: process.env.USERPROFILE,
|
|
148
|
+
PATH: process.env.PATH,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
let bytes = 0;
|
|
152
|
+
let failed = false;
|
|
153
|
+
const limit = (chunk) => {
|
|
154
|
+
bytes += chunk.length;
|
|
155
|
+
if (bytes > 64 * 1024) {
|
|
156
|
+
failed = true;
|
|
157
|
+
child.kill();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
child.stdout.on("data", limit);
|
|
161
|
+
child.stderr.on("data", limit);
|
|
162
|
+
const timer = setTimeout(() => {
|
|
163
|
+
failed = true;
|
|
164
|
+
child.kill();
|
|
165
|
+
}, 30_000);
|
|
166
|
+
timer.unref();
|
|
167
|
+
child.once("error", () => {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
reject(new CliError("ARCHIVE_EXTRACTION_FAILED"));
|
|
170
|
+
});
|
|
171
|
+
child.once("exit", (code) => {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
if (failed || code !== 0)
|
|
174
|
+
reject(new CliError("ARCHIVE_EXTRACTION_FAILED"));
|
|
175
|
+
else
|
|
176
|
+
resolve();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async function entries(directory, prefix = "") {
|
|
181
|
+
let children;
|
|
182
|
+
try {
|
|
183
|
+
children = await readdir(directory, { withFileTypes: true });
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
throw new CliError("PACKAGE_VERIFICATION_FAILED");
|
|
187
|
+
}
|
|
188
|
+
const result = [];
|
|
189
|
+
for (const child of children) {
|
|
190
|
+
const relative = prefix ? `${prefix}/${child.name}` : child.name;
|
|
191
|
+
const absolute = path.join(directory, child.name);
|
|
192
|
+
const facts = await lstat(absolute);
|
|
193
|
+
if (facts.isSymbolicLink())
|
|
194
|
+
throw new CliError("PACKAGE_VERIFICATION_FAILED");
|
|
195
|
+
if (facts.isDirectory())
|
|
196
|
+
result.push(...(await entries(absolute, relative)));
|
|
197
|
+
else if (facts.isFile())
|
|
198
|
+
result.push(relative);
|
|
199
|
+
else
|
|
200
|
+
throw new CliError("PACKAGE_VERIFICATION_FAILED");
|
|
201
|
+
}
|
|
202
|
+
return result.sort();
|
|
203
|
+
}
|
|
204
|
+
export async function verifyExtractedBundle(directory, expected) {
|
|
205
|
+
try {
|
|
206
|
+
const actual = await entries(directory);
|
|
207
|
+
const expectedNames = Object.keys(expected).sort();
|
|
208
|
+
if (JSON.stringify(actual) !== JSON.stringify(expectedNames))
|
|
209
|
+
throw new Error();
|
|
210
|
+
for (const name of expectedNames) {
|
|
211
|
+
const file = path.resolve(directory, ...name.split("/"));
|
|
212
|
+
const relative = path.relative(path.resolve(directory), file);
|
|
213
|
+
if (relative.startsWith(`..${path.sep}`) ||
|
|
214
|
+
relative === ".." ||
|
|
215
|
+
path.isAbsolute(relative))
|
|
216
|
+
throw new Error();
|
|
217
|
+
const bytes = await readFile(file);
|
|
218
|
+
if (createHash("sha256").update(bytes).digest("hex") !== expected[name])
|
|
219
|
+
throw new Error();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (error instanceof CliError)
|
|
224
|
+
throw error;
|
|
225
|
+
throw new CliError("PACKAGE_VERIFICATION_FAILED");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
export async function verifyInstalledSkill(directory, bundleFiles) {
|
|
229
|
+
const prefix = "skills/nggaigc/";
|
|
230
|
+
const expected = Object.fromEntries(Object.entries(bundleFiles)
|
|
231
|
+
.filter(([name]) => name.startsWith(prefix))
|
|
232
|
+
.map(([name, digest]) => [name.slice(prefix.length), digest]));
|
|
233
|
+
try {
|
|
234
|
+
const actual = await entries(directory);
|
|
235
|
+
const allowed = [...Object.keys(expected), ".nggaigc-install.json"].sort();
|
|
236
|
+
if (JSON.stringify(actual) !== JSON.stringify(allowed))
|
|
237
|
+
throw new Error();
|
|
238
|
+
for (const [name, digest] of Object.entries(expected)) {
|
|
239
|
+
const bytes = await readFile(path.resolve(directory, ...name.split("/")));
|
|
240
|
+
if (createHash("sha256").update(bytes).digest("hex") !== digest)
|
|
241
|
+
throw new Error();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
246
|
+
}
|
|
247
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class CliError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code) {
|
|
4
|
+
super(code);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = "CliError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function fixedErrorCode(error) {
|
|
10
|
+
if (error !== null &&
|
|
11
|
+
typeof error === "object" &&
|
|
12
|
+
"code" in error &&
|
|
13
|
+
typeof error.code === "string" &&
|
|
14
|
+
/^[A-Z0-9_]{1,64}$/.test(error.code))
|
|
15
|
+
return error.code;
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { extractBundle, verifyBundle, verifyExtractedBundle, verifyInstalledSkill, } from "./bundle.js";
|
|
7
|
+
import { CliError, fixedErrorCode } from "./errors.js";
|
|
8
|
+
function defaultBundle() {
|
|
9
|
+
const vendor = fileURLToPath(new URL("../vendor/", import.meta.url));
|
|
10
|
+
return {
|
|
11
|
+
archive: path.join(vendor, "nggaigc-plugin-1.1.1.zip"),
|
|
12
|
+
manifest: path.join(vendor, "manifest.json"),
|
|
13
|
+
trust: path.join(vendor, "trusted-keys.json"),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function validateRuntime(platform, architecture) {
|
|
17
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
18
|
+
if (major !== 22 || (minor ?? 0) < 13)
|
|
19
|
+
throw new CliError("RUNTIME_UNSUPPORTED");
|
|
20
|
+
if (platform !== "win32")
|
|
21
|
+
throw new CliError("UNSUPPORTED_PLATFORM");
|
|
22
|
+
if (architecture !== "x64" && architecture !== "arm64")
|
|
23
|
+
throw new CliError("UNSUPPORTED_PLATFORM");
|
|
24
|
+
}
|
|
25
|
+
function safeProfile(value) {
|
|
26
|
+
if (!path.isAbsolute(value) || /[\0\r\n]/.test(value))
|
|
27
|
+
throw new CliError("INVALID_INPUT");
|
|
28
|
+
return path.resolve(value);
|
|
29
|
+
}
|
|
30
|
+
async function installedVersion(target) {
|
|
31
|
+
try {
|
|
32
|
+
const marker = JSON.parse(await readFile(path.join(target, ".nggaigc-install.json"), "utf8"));
|
|
33
|
+
if (marker === null ||
|
|
34
|
+
typeof marker !== "object" ||
|
|
35
|
+
Array.isArray(marker) ||
|
|
36
|
+
JSON.stringify(Object.keys(marker).sort()) !==
|
|
37
|
+
JSON.stringify(["installedAt", "schemaVersion", "version"]) ||
|
|
38
|
+
!("schemaVersion" in marker) ||
|
|
39
|
+
marker.schemaVersion !== "nggaigc-local-install/v1" ||
|
|
40
|
+
!("installedAt" in marker) ||
|
|
41
|
+
typeof marker.installedAt !== "string" ||
|
|
42
|
+
!Number.isFinite(Date.parse(marker.installedAt)) ||
|
|
43
|
+
!("version" in marker) ||
|
|
44
|
+
typeof marker.version !== "string" ||
|
|
45
|
+
!/^\d+\.\d+\.\d+$/.test(marker.version))
|
|
46
|
+
return undefined;
|
|
47
|
+
return marker.version;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function installSkill(installer, source, profile, expectedVersion, bundleFiles) {
|
|
54
|
+
const target = path.join(profile, ".agents", "skills", "nggaigc");
|
|
55
|
+
try {
|
|
56
|
+
const result = await installer.main(["install", "--profile", profile], {
|
|
57
|
+
source,
|
|
58
|
+
});
|
|
59
|
+
if (result.status !== "installed" || result.version !== expectedVersion)
|
|
60
|
+
throw new Error();
|
|
61
|
+
await verifyInstalledSkill(target, bundleFiles);
|
|
62
|
+
return "installed";
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (fixedErrorCode(error) !== "ALREADY_INSTALLED")
|
|
66
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
67
|
+
}
|
|
68
|
+
const currentVersion = await installedVersion(target);
|
|
69
|
+
if (!currentVersion)
|
|
70
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
71
|
+
if (currentVersion === expectedVersion) {
|
|
72
|
+
await verifyInstalledSkill(target, bundleFiles);
|
|
73
|
+
return "unchanged";
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const result = await installer.main(["update", "--profile", profile], {
|
|
77
|
+
source,
|
|
78
|
+
});
|
|
79
|
+
if (result.status !== "updated" || result.version !== expectedVersion)
|
|
80
|
+
throw new Error();
|
|
81
|
+
await verifyInstalledSkill(target, bundleFiles);
|
|
82
|
+
return "updated";
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function connect(launcher, options, report, probe) {
|
|
89
|
+
let configured;
|
|
90
|
+
try {
|
|
91
|
+
if (probe)
|
|
92
|
+
configured = await probe(launcher);
|
|
93
|
+
else {
|
|
94
|
+
const status = await launcher.main(["status"]);
|
|
95
|
+
if (status === null ||
|
|
96
|
+
typeof status !== "object" ||
|
|
97
|
+
!("status" in status) ||
|
|
98
|
+
status.status !== "connected")
|
|
99
|
+
throw new Error();
|
|
100
|
+
await launcher.main(["preflight"]);
|
|
101
|
+
configured = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (!probe && fixedErrorCode(error) === "SETUP_REQUIRED")
|
|
106
|
+
configured = false;
|
|
107
|
+
else
|
|
108
|
+
throw new CliError("CONNECTION_VERIFICATION_FAILED");
|
|
109
|
+
}
|
|
110
|
+
if (!configured) {
|
|
111
|
+
report("authorizing");
|
|
112
|
+
try {
|
|
113
|
+
const result = await launcher.main(["login"], options);
|
|
114
|
+
if (result === null ||
|
|
115
|
+
typeof result !== "object" ||
|
|
116
|
+
!("status" in result) ||
|
|
117
|
+
result.status !== "connected")
|
|
118
|
+
throw new Error();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
throw new CliError("BROWSER_AUTHORIZATION_FAILED");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
report("connected");
|
|
125
|
+
}
|
|
126
|
+
function assertDisposableRoot(value, expectedParent) {
|
|
127
|
+
const relative = path.relative(path.resolve(expectedParent), path.resolve(value));
|
|
128
|
+
if (relative === "" ||
|
|
129
|
+
relative === ".." ||
|
|
130
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
131
|
+
path.isAbsolute(relative) ||
|
|
132
|
+
!path.basename(value).startsWith("nggaigc-cli-"))
|
|
133
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
134
|
+
}
|
|
135
|
+
export async function runInstall(options = {}) {
|
|
136
|
+
validateRuntime(options.platform ?? process.platform, options.architecture ?? process.arch);
|
|
137
|
+
const profile = safeProfile(options.profile ?? os.homedir());
|
|
138
|
+
const report = options.reporter ?? (() => undefined);
|
|
139
|
+
report("verifying");
|
|
140
|
+
const bundle = await verifyBundle(options.bundle ?? defaultBundle());
|
|
141
|
+
const temporaryParent = path.resolve(options.temporaryRoot ?? os.tmpdir());
|
|
142
|
+
const temporary = await mkdtemp(path.join(temporaryParent, "nggaigc-cli-"));
|
|
143
|
+
assertDisposableRoot(temporary, temporaryParent);
|
|
144
|
+
try {
|
|
145
|
+
const extracted = path.join(temporary, "plugin");
|
|
146
|
+
await extractBundle(bundle.archive, extracted);
|
|
147
|
+
await verifyExtractedBundle(extracted, bundle.files);
|
|
148
|
+
report("installing");
|
|
149
|
+
const require = createRequire(import.meta.url);
|
|
150
|
+
const installer = require(path.join(extracted, "installer", "install.cjs"));
|
|
151
|
+
if (!installer || typeof installer.main !== "function")
|
|
152
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
153
|
+
const installation = await installSkill(installer, path.join(extracted, "skills", "nggaigc"), profile, bundle.version, bundle.files);
|
|
154
|
+
const installedScripts = path.join(profile, ".agents", "skills", "nggaigc", "scripts");
|
|
155
|
+
const launcher = require(path.join(installedScripts, "launch-ngg-client.cjs"));
|
|
156
|
+
if (!launcher ||
|
|
157
|
+
typeof launcher.main !== "function" ||
|
|
158
|
+
typeof launcher.LauncherError !== "function")
|
|
159
|
+
throw new CliError("INSTALLATION_FAILED");
|
|
160
|
+
await connect(launcher, options.launcherOptionsFactory?.(launcher), report, options.connectionProbe);
|
|
161
|
+
return { status: "connected", installation, version: bundle.version };
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
assertDisposableRoot(temporary, temporaryParent);
|
|
165
|
+
await rm(temporary, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nggaigc/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Secure one-command NGGAIGC Skill installer for Codex on Windows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nggaigc": "dist/bin.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"scripts/expand-archive.ps1",
|
|
12
|
+
"vendor/"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=22.13.0 <23"
|
|
16
|
+
},
|
|
17
|
+
"os": [
|
|
18
|
+
"win32"
|
|
19
|
+
],
|
|
20
|
+
"cpu": [
|
|
21
|
+
"x64",
|
|
22
|
+
"arm64"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
|
+
"prepack": "tsc -p tsconfig.build.json && node scripts/stage-assets.mjs",
|
|
27
|
+
"test": "vitest run --exclude 'dist/**' --exclude 'src/windows-secure-store.integration.test.ts'",
|
|
28
|
+
"test:credential:os": "tsc -p tsconfig.build.json && vitest run src/windows-secure-store.integration.test.ts",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"license": "UNLICENSED"
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "nggaigc-plugin-package/v1",
|
|
3
|
+
"artifactId": "plugin.nggaigc",
|
|
4
|
+
"version": "1.1.1",
|
|
5
|
+
"sourceCommit": "6d6869feb01c935f16250beca0d269eef41a4eaa",
|
|
6
|
+
"artifactPath": "packages/plugins/nggaigc-plugin-1.1.1.zip",
|
|
7
|
+
"sha256": "f4371dcbfb58d003699ce854472d1a2cb356976c69db633f90b929bae2863bda",
|
|
8
|
+
"sizeBytes": 106658,
|
|
9
|
+
"fileCount": 11,
|
|
10
|
+
"platforms": ["win32-x64", "win32-arm64"],
|
|
11
|
+
"files": {
|
|
12
|
+
".codex-plugin/plugin.json": "705fd28fa3bd4b9ced198cad482bcf7af65144c368b8abcd6aa1504a47b2d25e",
|
|
13
|
+
"installer/install.cjs": "79b0b610965211dd2faf3f569d93d4f5f84b1055fc2bd11483bfdaf413edea10",
|
|
14
|
+
"skills/nggaigc/agents/openai.yaml": "4a71357d3904a2dc69b8b495f7c2f83f01d0a3c231558d7d0f10c46cd98aa301",
|
|
15
|
+
"skills/nggaigc/references/catalog-and-updates.md": "c0ee74bfe9b75dc8ee818f2db55d4fd7d581927ec037452b5971c9b3a9412e93",
|
|
16
|
+
"skills/nggaigc/references/source.json": "e315fb2f4fe35ce4fefb2c1bed60bb464cc764c3a7c69e3f3c70b7ca8a623051",
|
|
17
|
+
"skills/nggaigc/scripts/bootstrap-public.json": "ce699de3afa18941cbf9964a4e20b493a3a631f0edc1e0017a45668a9bee21a9",
|
|
18
|
+
"skills/nggaigc/scripts/launch-ngg-client.cjs": "ab96625cd10194172c18c225ba8eff0e27f1680d826ae6578645f63bcd252146",
|
|
19
|
+
"skills/nggaigc/scripts/ngg_client.mjs": "252d652b8f853fd0e610deb74a924f2143f7d17e4a34f9d35ee2bbcd800c7011",
|
|
20
|
+
"skills/nggaigc/scripts/ngg-session.mjs": "5cdcdbcf25b4852b66346f29dc8f77837bda7f680ce97f0ff1d33dc404dfb752",
|
|
21
|
+
"skills/nggaigc/scripts/windows-credential.ps1": "8b6e323bebd3f86f19e0c2ae5366f4dc68005d57c07d6f699aece366661be6a0",
|
|
22
|
+
"skills/nggaigc/SKILL.md": "5545ed96d9ab9facc241b0e6d2a18800e693d012190f08037134e9930d21fa8e"
|
|
23
|
+
},
|
|
24
|
+
"signature": {
|
|
25
|
+
"algorithm": "ed25519",
|
|
26
|
+
"keyId": "ngg-signing-skill-release-1",
|
|
27
|
+
"scope": "artifact-bytes",
|
|
28
|
+
"value": "yN8E8C4SfPMSZiOaBI4rDFJKb7EIU+5m2+dKEDGHNn/f/15xOLGiYjS0kCRoCGEDneG4GhpgF1gpX1sL/AeqDg=="
|
|
29
|
+
}
|
|
30
|
+
}
|
|
Binary file
|