@nggaigc/cli 0.1.16 → 0.1.18

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.
@@ -1,264 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { existsSync } from "node:fs";
3
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import { afterEach, describe, expect, it } from "vitest";
7
- import { verifyBundle } from "./bundle.js";
8
- import { CliError } from "./errors.js";
9
- import { runInstall } from "./install.js";
10
- const workspace = path.resolve(import.meta.dirname, "../../..");
11
- const bundle = {
12
- archive: path.join(workspace, "packages/plugins/nggaigc-plugin-1.1.18.zip"),
13
- manifest: path.join(workspace, "release/plugin-candidates/nggaigc-1.1.18/manifest.json"),
14
- trust: path.join(workspace, "release/trust/skill-package-keys.v1.json"),
15
- };
16
- const supportedWindowsRuntime = {
17
- platform: "win32",
18
- architecture: "x64",
19
- };
20
- const signedCandidatePresent = existsSync(bundle.archive) && existsSync(bundle.manifest);
21
- const temporary = [];
22
- async function disposable(prefix) {
23
- const value = await mkdtemp(path.join(os.tmpdir(), prefix));
24
- temporary.push(value);
25
- return value;
26
- }
27
- afterEach(async () => {
28
- while (temporary.length) {
29
- const value = temporary.pop();
30
- if (value)
31
- await rm(value, { recursive: true, force: true });
32
- }
33
- });
34
- describe("platform boundary", () => {
35
- it("does not claim untested Intel Mac support", async () => {
36
- await expect(runInstall({ platform: "darwin", architecture: "x64", bundle })).rejects.toEqual(new CliError("UNSUPPORTED_PLATFORM"));
37
- });
38
- });
39
- describe.skipIf(!signedCandidatePresent)("signed bundle", () => {
40
- it("rejects relabelling signed metadata with unsupported Intel Mac support", async () => {
41
- const root = await disposable("nggaigc-cli-manifest-");
42
- const value = JSON.parse(await readFile(bundle.manifest, "utf8"));
43
- value.platforms = [
44
- "win32-x64",
45
- "win32-arm64",
46
- "darwin-x64",
47
- "darwin-arm64",
48
- ];
49
- const manifest = path.join(root, "manifest.json");
50
- await writeFile(manifest, JSON.stringify(value));
51
- await expect(verifyBundle({ ...bundle, manifest })).rejects.toEqual(new CliError("PACKAGE_VERIFICATION_FAILED"));
52
- });
53
- it("verifies the accepted Plugin candidate", async () => {
54
- await expect(verifyBundle(bundle)).resolves.toMatchObject({
55
- version: "1.1.18",
56
- });
57
- });
58
- it("fails closed when an archive byte changes", async () => {
59
- const root = await disposable("nggaigc-cli-tamper-");
60
- const changed = path.join(root, "plugin.zip");
61
- const bytes = Buffer.from(await readFile(bundle.archive));
62
- bytes[bytes.length - 1] = (bytes[bytes.length - 1] ?? 0) ^ 1;
63
- await writeFile(changed, bytes);
64
- await expect(verifyBundle({ ...bundle, archive: changed })).rejects.toEqual(new CliError("PACKAGE_VERIFICATION_FAILED"));
65
- });
66
- });
67
- describe.skipIf(!signedCandidatePresent)("one-command installation", () => {
68
- it("installs, completes the polling browser flow, and verifies connection", async () => {
69
- const root = await disposable("nggaigc-cli-install-");
70
- const profile = path.join(root, "profile");
71
- const challenge = `ngg_chal_${randomBytes(32).toString("base64url")}`;
72
- const pollingCredential = `ngg_poll_${randomBytes(32).toString("base64url")}`;
73
- const credential = `ngg_v1_${randomBytes(32).toString("base64url")}`;
74
- const epoch = randomBytes(16).toString("hex");
75
- const events = [];
76
- const observed = [];
77
- let configured = false;
78
- let exchanges = 0;
79
- let browserOpens = 0;
80
- const options = {
81
- ...supportedWindowsRuntime,
82
- bundle,
83
- profile,
84
- connectionProbe: () => Promise.resolve(configured),
85
- reporter: (event) => events.push(event),
86
- launcherOptionsFactory: (launcher) => ({
87
- browserAuthorizationTimeoutMs: 5_000,
88
- helperRunner: (_directory, operation, input) => {
89
- observed.push(operation);
90
- if (operation === "status" && !configured)
91
- throw new launcher.LauncherError("SETUP_REQUIRED");
92
- if (operation === "setup") {
93
- expect(Buffer.from(input ?? []).includes(Buffer.from(credential))).toBe(true);
94
- configured = true;
95
- return { status: "credential-stored", credentialEpoch: epoch };
96
- }
97
- return { status: operation === "status" ? "connected" : "ready" };
98
- },
99
- fetchImplementation: (url, request) => {
100
- exchanges += 1;
101
- expect(typeof request.body).toBe("string");
102
- const body = typeof request.body === "string" ? request.body : "{}";
103
- if (url.endsWith("/codex-device-authorization-challenges")) {
104
- expect(JSON.parse(body)).toMatchObject({
105
- displayName: "Codex on Windows",
106
- });
107
- return Response.json({
108
- schemaVersion: "nggaigc-device-authorization-poll/v1",
109
- authorizationId: "12345678-1234-4123-8123-123456789abc",
110
- challengeToken: challenge,
111
- pollingCredential,
112
- expiresAt: new Date(Date.now() + 60_000).toISOString(),
113
- intervalSeconds: 5,
114
- });
115
- }
116
- expect(url).toBe("https://api.nggaigc.com/v1/codex-device-authorization-results/poll");
117
- expect(JSON.parse(body)).toEqual({ pollingCredential });
118
- return Response.json({
119
- schemaVersion: "nggaigc-device-authorization-poll/v1",
120
- status: "connected",
121
- credential,
122
- device: {
123
- id: "12345678-1234-4123-8123-123456789abc",
124
- displayName: "Codex on Windows",
125
- credentialSuffix: credential.slice(-4),
126
- status: "active",
127
- createdAt: "2026-09-14T00:00:00.000Z",
128
- revokedAt: null,
129
- lastUsedAt: null,
130
- },
131
- });
132
- },
133
- browserOpener: (value) => {
134
- browserOpens += 1;
135
- const authorization = new URL(value);
136
- expect(authorization.origin).toBe("https://api.nggaigc.com");
137
- expect(authorization.pathname).toBe("/connect-codex");
138
- expect(authorization.searchParams.get("flow")).toBe("poll-v1");
139
- expect(authorization.searchParams.get("challenge")).toBe(challenge);
140
- expect(value).not.toContain("127.0.0.1");
141
- expect(value).not.toContain(pollingCredential);
142
- expect(value).not.toContain(credential);
143
- return Promise.resolve();
144
- },
145
- }),
146
- };
147
- await expect(runInstall(options)).resolves.toEqual({
148
- status: "connected",
149
- installation: "installed",
150
- version: "1.1.18",
151
- });
152
- expect(events).toEqual([
153
- "verifying",
154
- "installing",
155
- "launching",
156
- "authorizing",
157
- "connected",
158
- ]);
159
- expect(exchanges).toBe(2);
160
- expect(browserOpens).toBe(1);
161
- expect(observed).toContain("session");
162
- events.length = 0;
163
- await expect(runInstall(options)).resolves.toEqual({
164
- status: "connected",
165
- installation: "unchanged",
166
- version: "1.1.18",
167
- });
168
- expect(events).toEqual(["verifying", "installing", "connected"]);
169
- expect(exchanges).toBe(2);
170
- expect(browserOpens).toBe(1);
171
- const installed = path.join(profile, ".codex", "skills", "nggaigc");
172
- expect(await readFile(path.join(installed, "SKILL.md"), "utf8")).toContain("name: nggaigc");
173
- const output = JSON.stringify({ events, observed });
174
- expect(output).not.toContain(challenge);
175
- expect(output).not.toContain(pollingCredential);
176
- expect(output).not.toContain(credential);
177
- expect(process.argv.join("\n")).not.toContain(challenge);
178
- expect(JSON.stringify(process.env)).not.toContain(challenge);
179
- expect(JSON.stringify(process.env)).not.toContain(pollingCredential);
180
- expect(JSON.stringify(process.env)).not.toContain(credential);
181
- }, 60_000);
182
- it("reports browser authorization only after Windows accepts the launch", async () => {
183
- const root = await disposable("nggaigc-cli-browser-failure-");
184
- const events = [];
185
- const challenge = `ngg_chal_${randomBytes(32).toString("base64url")}`;
186
- const pollingCredential = `ngg_poll_${randomBytes(32).toString("base64url")}`;
187
- await expect(runInstall({
188
- ...supportedWindowsRuntime,
189
- bundle,
190
- profile: path.join(root, "profile"),
191
- connectionProbe: () => Promise.resolve(false),
192
- reporter: (event) => events.push(event),
193
- launcherOptionsFactory: (launcher) => ({
194
- helperRunner: (_directory, operation) => {
195
- if (operation === "status")
196
- throw new launcher.LauncherError("SETUP_REQUIRED");
197
- return { status: "ready" };
198
- },
199
- fetchImplementation: (url) => url.endsWith("/codex-device-authorization-challenges")
200
- ? Response.json({
201
- schemaVersion: "nggaigc-device-authorization-poll/v1",
202
- authorizationId: "12345678-1234-4123-8123-123456789abc",
203
- challengeToken: challenge,
204
- pollingCredential,
205
- expiresAt: new Date(Date.now() + 60_000).toISOString(),
206
- intervalSeconds: 5,
207
- })
208
- : new Response(null, { status: 204 }),
209
- browserOpener: () => {
210
- throw new launcher.LauncherError("BROWSER_OPEN_FAILED");
211
- },
212
- }),
213
- })).resolves.toEqual({
214
- status: "installed-unconnected",
215
- installation: "installed",
216
- version: "1.1.18",
217
- connectionError: "BROWSER_OPEN_FAILED",
218
- });
219
- expect(events).toEqual(["verifying", "installing", "launching"]);
220
- }, 60_000);
221
- it("keeps verified files installed when Windows policy blocks connection", async () => {
222
- const root = await disposable("nggaigc-cli-policy-blocked-");
223
- const profile = path.join(root, "profile");
224
- const events = [];
225
- await expect(runInstall({
226
- ...supportedWindowsRuntime,
227
- bundle,
228
- profile,
229
- reporter: (event) => events.push(event),
230
- connectionProbe: (launcher) => Promise.reject(new launcher.LauncherError("POWERSHELL_EXECUTION_POLICY_BLOCKED")),
231
- })).resolves.toEqual({
232
- status: "installed-unconnected",
233
- installation: "installed",
234
- version: "1.1.18",
235
- connectionError: "POWERSHELL_EXECUTION_POLICY_BLOCKED",
236
- });
237
- expect(events).toEqual(["verifying", "installing"]);
238
- expect(await readFile(path.join(profile, ".codex", "skills", "nggaigc", "SKILL.md"), "utf8")).toContain("name: nggaigc");
239
- }, 60_000);
240
- it("does not overwrite a modified managed installation", async () => {
241
- const root = await disposable("nggaigc-cli-modified-");
242
- const profile = path.join(root, "profile");
243
- let configured = true;
244
- const options = {
245
- ...supportedWindowsRuntime,
246
- bundle,
247
- profile,
248
- connectionProbe: () => Promise.resolve(configured),
249
- launcherOptionsFactory: () => ({
250
- helperRunner: (_directory, operation) => {
251
- if (operation === "status" && configured)
252
- return { status: "connected" };
253
- throw new Error("unexpected");
254
- },
255
- }),
256
- };
257
- await runInstall(options);
258
- const skill = path.join(profile, ".codex", "skills", "nggaigc", "SKILL.md");
259
- await writeFile(skill, "user-modified\n");
260
- await expect(runInstall(options)).rejects.toEqual(new CliError("INSTALLATION_FAILED"));
261
- expect(await readFile(skill, "utf8")).toBe("user-modified\n");
262
- configured = false;
263
- }, 60_000);
264
- });
@@ -1,53 +0,0 @@
1
- import { createHash, generateKeyPairSync, sign, verify } from "node:crypto";
2
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { expect, it } from "vitest";
6
- import { trustedKeyId, verifyBundle } from "./bundle.js";
7
- it("the official fixed plugin verifier rejects an injected synthetic trust key", async () => {
8
- const directory = await mkdtemp(path.join(os.tmpdir(), "nggaigc-production-trust-test-"));
9
- try {
10
- const pair = generateKeyPairSync("ed25519");
11
- const key = pair.publicKey.export({
12
- type: "spki",
13
- format: "pem",
14
- });
15
- const archive = Buffer.from("synthetic-plugin-test-archive");
16
- const manifest = {
17
- schemaVersion: "nggaigc-plugin-package/v1",
18
- artifactId: "plugin.nggaigc",
19
- version: "1.1.16",
20
- sourceCommit: "0".repeat(40),
21
- artifactPath: "packages/plugins/nggaigc-plugin-1.1.16.zip",
22
- sha256: createHash("sha256").update(archive).digest("hex"),
23
- sizeBytes: archive.length,
24
- fileCount: 1,
25
- platforms: ["win32-x64", "win32-arm64", "darwin-arm64"],
26
- files: { "skills/nggaigc/SKILL.md": "0".repeat(64) },
27
- signature: {
28
- algorithm: "ed25519",
29
- keyId: trustedKeyId,
30
- scope: "artifact-bytes",
31
- value: sign(null, archive, pair.privateKey).toString("base64"),
32
- },
33
- };
34
- expect(verify(null, archive, pair.publicKey, Buffer.from(manifest.signature.value, "base64"))).toBe(true);
35
- const manifestPath = path.join(directory, "manifest.json");
36
- const trustPath = path.join(directory, "trusted-keys.json");
37
- const archivePath = path.join(directory, "nggaigc-plugin-1.1.16.zip");
38
- await writeFile(manifestPath, JSON.stringify(manifest));
39
- await writeFile(archivePath, archive);
40
- await writeFile(trustPath, JSON.stringify({
41
- schemaVersion: "ngg-trusted-package-keys/v1",
42
- keys: [{ keyId: trustedKeyId, publicKeyPem: key.toString() }],
43
- }));
44
- await expect(verifyBundle({
45
- archive: archivePath,
46
- manifest: manifestPath,
47
- trust: trustPath,
48
- })).rejects.toMatchObject({ code: "PACKAGE_VERIFICATION_FAILED" });
49
- }
50
- finally {
51
- await rm(directory, { recursive: true, force: true });
52
- }
53
- });
@@ -1,53 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { runInstall } from "./install.js";
3
- describe("CLI runtime admission before installation or connection", () => {
4
- for (const [platform, architecture] of [
5
- ["win32", "x64"],
6
- ["win32", "arm64"],
7
- ["darwin", "arm64"],
8
- ]) {
9
- it(`admits ${platform}/${architecture} before bundle verification`, async () => {
10
- await expect(runInstall({ platform, architecture, profile: "relative" })).rejects.toMatchObject({ code: "INVALID_INPUT" });
11
- });
12
- }
13
- for (const [platform, architecture] of [
14
- ["linux", "x64"],
15
- ["darwin", "x64"],
16
- ["darwin", "ia32"],
17
- ]) {
18
- it(`rejects ${platform}/${architecture}`, async () => {
19
- await expect(runInstall({ platform, architecture, profile: "relative" })).rejects.toMatchObject({ code: "UNSUPPORTED_PLATFORM" });
20
- });
21
- }
22
- for (const [version, supported] of [
23
- ["20.19.0", false],
24
- ["22.0.0", false],
25
- ["22.12.9", false],
26
- ["22.13.0", true],
27
- ["22.22.2", true],
28
- ["23.0.0", false],
29
- ["24.0.0", true],
30
- ["24.16.0", true],
31
- ["25.0.0", false],
32
- ["26.0.0", false],
33
- ]) {
34
- it(`${supported ? "accepts" : "rejects"} Node ${version}`, async () => {
35
- const original = Object.getOwnPropertyDescriptor(process.versions, "node");
36
- Object.defineProperty(process.versions, "node", { value: version });
37
- try {
38
- // Invalid profile is checked immediately after runtime admission.
39
- // No bundle extraction, filesystem writes, browser, or credential access.
40
- await expect(runInstall({
41
- platform: "win32",
42
- architecture: "x64",
43
- profile: "relative",
44
- })).rejects.toMatchObject({
45
- code: supported ? "INVALID_INPUT" : "RUNTIME_UNSUPPORTED",
46
- });
47
- }
48
- finally {
49
- Object.defineProperty(process.versions, "node", original);
50
- }
51
- });
52
- }
53
- });
@@ -1,158 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return -- The signed historical .mjs client and loopback fixture are intentionally loaded dynamically. */
2
- import assert from "node:assert/strict";
3
- import { createHash } from "node:crypto";
4
- import { mkdtemp, readFile, rm } from "node:fs/promises";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import { pathToFileURL } from "node:url";
8
- import { afterEach, test } from "vitest";
9
- import { decodeArchive, writeArchive } from "./archive.js";
10
- const repo = path.resolve(import.meta.dirname, "../../..");
11
- const temporary = [];
12
- afterEach(async () => {
13
- await Promise.all(temporary.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
14
- });
15
- test("the new Seedream/root pair resumes an old pair's accepted task without a second charge", async () => {
16
- const manifest = JSON.parse(await readFile(path.join(repo, "release/plugin-candidates/nggaigc-1.1.10/manifest.json"), "utf8"));
17
- const zip = await readFile(path.join(repo, manifest.artifactPath));
18
- assert.equal(createHash("sha256").update(zip).digest("hex"), manifest.sha256);
19
- const entries = decodeArchive(zip);
20
- const files = Object.fromEntries(entries
21
- .filter((entry) => !entry.directory)
22
- .map((entry) => [
23
- entry.name,
24
- createHash("sha256").update(entry.bytes).digest("hex"),
25
- ]));
26
- assert.deepEqual(files, manifest.files);
27
- const oldFiles = entries
28
- .filter((entry) => !entry.directory && entry.name.startsWith("skills/nggaigc/"))
29
- .map((entry) => ({
30
- ...entry,
31
- name: entry.name.slice("skills/nggaigc/".length),
32
- }));
33
- const tmp = await mkdtemp(path.join(os.tmpdir(), "ngg-seedream-root-compat-"));
34
- temporary.push(tmp);
35
- const oldRoot = path.join(tmp, "old-root");
36
- await writeArchive(oldFiles, oldRoot);
37
- const oldWrapper = path.join(repo, "tests/skill-sources/fixtures/seedream-1.1.0");
38
- assert.equal(createHash("sha256")
39
- .update(await readFile(path.join(oldWrapper, "scripts/seedream.mjs")))
40
- .digest("hex"), "27e0056bbe9fae35233b10247f13cc679747bb8fe1682b39d74e7ffa77a5a1fb");
41
- assert.equal(createHash("sha256")
42
- .update(await readFile(path.join(oldWrapper, "references/source.json")))
43
- .digest("hex"), "78d1a03451b83dd31d83d5e9af88341a64a5b290f9a2e0c46abf368a6a7a5650");
44
- const currentRoot = path.join(repo, "skill-sources/nggaigc");
45
- const oldClient = await import(pathToFileURL(path.join(oldRoot, "scripts/ngg_client.mjs")).href);
46
- const currentClient = await import(pathToFileURL(path.join(currentRoot, "scripts/ngg_client.mjs")).href);
47
- for (const client of [oldClient, currentClient]) {
48
- assert.equal(client.CLIENT_PROTOCOL, 1);
49
- assert.deepEqual(client.IMAGE, {
50
- id: "skill.seedream-5-lite.image-generation",
51
- version: "1.1.0",
52
- capability: "capability.seedream-5-lite.image-generation",
53
- });
54
- }
55
- const fakeApiPath = path.join(repo, "tests/skill-sources/fake-api.mjs");
56
- const wrapperPath = path.join(repo, "skill-sources/ngg-seedream-image/scripts/seedream.mjs");
57
- const { createFakeApi } = await import(pathToFileURL(fakeApiPath).href);
58
- const { runSeedream } = await import(pathToFileURL(wrapperPath).href);
59
- const { runSeedream: runOldSeedream } = await import(pathToFileURL(path.join(oldWrapper, "scripts/seedream.mjs")).href);
60
- const api = await createFakeApi({
61
- clientRootVersion: "1.1.10",
62
- clientSkillVersion: "1.1.0",
63
- dropPosts: 1,
64
- });
65
- try {
66
- const options = {
67
- sessionProvider: api.sessionProvider,
68
- stateDirectory: path.join(tmp, "state"),
69
- allowLoopbackTest: true,
70
- retryDelayMs: 0,
71
- timeoutMs: 1000,
72
- maxPolls: 8,
73
- };
74
- const old = (args) => runOldSeedream(args, { ...options, trustedRootDirectory: oldRoot });
75
- const next = (args) => runSeedream(args, { ...options, trustedRootDirectory: currentRoot });
76
- await assert.rejects(runSeedream(["preflight"], { trustedRootDirectory: oldRoot }), /SETUP_REQUIRED/);
77
- assert.equal((await old(["preflight"])).clientProtocol, 1);
78
- const quote = await old(["quote", "A paper landscape", "1"]);
79
- assert.equal(quote.requiresPaidConsent, true);
80
- assert.equal(api.events.filter((event) => event.method === "POST")
81
- .length, 0);
82
- await assert.rejects(old(["submit", quote.quoteId]), {
83
- code: "PAID_CONSENT_REQUIRED",
84
- });
85
- const accepted = await old(["submit", quote.quoteId, "--confirm"]);
86
- assert.ok(accepted.runId);
87
- const posts = api.events.filter((event) => event.method === "POST");
88
- assert.equal(posts.length, 2);
89
- assert.equal(new Set(posts.map((event) => event.key)).size, 1);
90
- assert.equal(api.runs.size, 1);
91
- api.options.dropPosts = 4;
92
- const uncertainQuote = await old([
93
- "quote",
94
- "Uncertain paper landscape",
95
- "1",
96
- ]);
97
- await assert.rejects(old(["submit", uncertainQuote.quoteId, "--confirm"]), {
98
- code: "NETWORK_UNCERTAIN",
99
- });
100
- const uncertainPosts = api.events.filter((event) => event.method === "POST" && event.key === uncertainQuote.quoteId);
101
- assert.equal(uncertainPosts.length, 3);
102
- assert.equal(api.runs.size, 2);
103
- api.setClientRootVersion("1.1.18");
104
- api.setClientSkillVersion("1.1.3");
105
- await assert.rejects(runOldSeedream(["preflight"], { trustedRootDirectory: currentRoot }), /SETUP_REQUIRED/);
106
- await assert.rejects(currentClient.runCommand(["quote", "unsafe mix", "1"], {
107
- ...options,
108
- imageSkillDirectory: oldWrapper,
109
- }), { code: "NO_LOCAL_IMPLEMENTATION" });
110
- assert.equal((await next(["preflight"])).clientProtocol, 1);
111
- const resumed = await next(["resume", quote.quoteId]);
112
- assert.equal(resumed.runId, accepted.runId);
113
- const recovered = await next(["resume", uncertainQuote.quoteId]);
114
- assert.ok(recovered.runId);
115
- assert.notEqual(recovered.runId, accepted.runId);
116
- assert.equal(api.events.filter((event) => event.method === "POST" && event.key === uncertainQuote.quoteId).length, 4);
117
- const completed = await next(["poll", accepted.runId]);
118
- assert.equal(completed.status, "succeeded");
119
- assert.equal(completed.usage.chargedCredits, 40);
120
- const saved = await next([
121
- "save",
122
- accepted.runId,
123
- path.join(tmp, "images"),
124
- ]);
125
- assert.equal(saved.files.length, 1);
126
- assert.deepEqual(await readFile(saved.files[0].path), api.png);
127
- assert.equal(api.runs.size, 2);
128
- const newQuote = await next(["quote", "Another paper landscape", "1"]);
129
- const newAccepted = await next(["submit", newQuote.quoteId, "--confirm"]);
130
- assert.notEqual(newAccepted.runId, accepted.runId);
131
- assert.equal((await next(["poll", newAccepted.runId])).status, "succeeded");
132
- const newSaved = await next([
133
- "save",
134
- newAccepted.runId,
135
- path.join(tmp, "new-images"),
136
- ]);
137
- assert.equal(newSaved.files.length, 1);
138
- assert.deepEqual(await readFile(newSaved.files[0].path), api.png);
139
- assert.equal(api.runs.size, 3);
140
- // Production may remove this Skill from the active catalog while a
141
- // previously accepted historical run remains queued. Reads must survive.
142
- api.options.missingSkill = true;
143
- const postCount = api.events.filter((event) => event.method === "POST").length;
144
- assert.equal((await next(["resume", quote.quoteId])).runId, accepted.runId);
145
- assert.equal((await next(["poll", accepted.runId])).status, "succeeded");
146
- assert.equal((await next(["save", accepted.runId, path.join(tmp, "images")])).files
147
- .length, 1);
148
- await assert.rejects(next(["quote", "No new catalog entry", "1"]), {
149
- code: "NO_LOCAL_IMPLEMENTATION",
150
- });
151
- assert.equal(api.events.filter((event) => event.method === "POST")
152
- .length, postCount);
153
- assert.deepEqual(api.failures, []);
154
- }
155
- finally {
156
- await api.close();
157
- }
158
- }, 30_000);
@@ -1,122 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { randomBytes } from "node:crypto";
3
- import { existsSync } from "node:fs";
4
- import { mkdtemp, rm } from "node:fs/promises";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import { expect, it } from "vitest";
8
- import { extractBundle, verifyBundle, verifyExtractedBundle, } from "./bundle.js";
9
- const workspace = path.resolve(import.meta.dirname, "../../..");
10
- const bundleFiles = {
11
- archive: path.join(workspace, "packages/plugins/nggaigc-plugin-1.1.16.zip"),
12
- manifest: path.join(workspace, "release/plugin-candidates/nggaigc-1.1.16/manifest.json"),
13
- trust: path.join(workspace, "release/trust/skill-package-keys.v1.json"),
14
- };
15
- const signedCandidatePresent = Object.values(bundleFiles).every((file) => existsSync(file));
16
- function frame(value) {
17
- const payload = Buffer.from(JSON.stringify(value));
18
- const result = Buffer.allocUnsafe(payload.length + 4);
19
- result.writeUInt32BE(payload.length, 0);
20
- payload.copy(result, 4);
21
- payload.fill(0);
22
- return result;
23
- }
24
- async function runHelper(distribution, target, operation, input, expectedEpoch) {
25
- const executable = path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
26
- const args = [
27
- "-NoLogo",
28
- "-NoProfile",
29
- "-NonInteractive",
30
- "-File",
31
- path.join(distribution, "windows-credential.ps1"),
32
- "-Distribution",
33
- distribution,
34
- "-Operation",
35
- operation,
36
- "-NodePath",
37
- process.execPath,
38
- "-Target",
39
- target,
40
- ];
41
- if (expectedEpoch)
42
- args.push("-ExpectedEpoch", expectedEpoch);
43
- const child = spawn(executable, args, {
44
- windowsHide: true,
45
- stdio: ["pipe", "pipe", "pipe"],
46
- env: {
47
- SystemRoot: process.env.SystemRoot,
48
- LOCALAPPDATA: process.env.LOCALAPPDATA,
49
- USERPROFILE: process.env.USERPROFILE,
50
- PATH: process.env.PATH,
51
- },
52
- });
53
- const stdout = [];
54
- const stderr = [];
55
- child.stdout.on("data", (value) => stdout.push(Buffer.from(value)));
56
- child.stderr.on("data", (value) => stderr.push(Buffer.from(value)));
57
- child.stdin.end(input);
58
- const code = await new Promise((resolve, reject) => {
59
- child.once("error", reject);
60
- child.once("exit", (value) => resolve(value ?? 1));
61
- });
62
- return {
63
- code,
64
- stdout: Buffer.concat(stdout).toString("utf8"),
65
- stderr: Buffer.concat(stderr).toString("utf8"),
66
- spawnargs: child.spawnargs,
67
- };
68
- }
69
- it.skipIf(process.platform !== "win32" || !signedCandidatePresent)("stores a synthetic credential in Windows Credential Manager without leakage", async () => {
70
- const root = await mkdtemp(path.join(os.tmpdir(), "nggaigc-cli-os-"));
71
- const target = `NGGAIGC/Codex/test-${randomBytes(16).toString("hex")}`;
72
- const credential = `ngg_v1_${randomBytes(32).toString("base64url")}`;
73
- const observed = [];
74
- let epoch;
75
- try {
76
- const verified = await verifyBundle(bundleFiles);
77
- const extracted = path.join(root, "plugin");
78
- await extractBundle(verified.archive, extracted, verified.sha256);
79
- await verifyExtractedBundle(extracted, verified.files);
80
- const distribution = path.join(extracted, "skills", "nggaigc", "scripts");
81
- const preflight = await runHelper(distribution, target, "preflight");
82
- observed.push(preflight);
83
- expect(preflight.code).toBe(0);
84
- const credentialFrame = frame({
85
- schemaVersion: "ngg-credential-input/v1",
86
- origin: "https://api.nggaigc.com",
87
- token: credential,
88
- });
89
- const setup = await runHelper(distribution, target, "setup", credentialFrame);
90
- credentialFrame.fill(0);
91
- observed.push(setup);
92
- expect(setup.code).toBe(0);
93
- epoch = /"credentialEpoch":"([a-f0-9]{32})"/.exec(setup.stdout)?.[1];
94
- expect(epoch).toMatch(/^[a-f0-9]{32}$/);
95
- const status = await runHelper(distribution, target, "status");
96
- observed.push(status);
97
- expect(status.code).toBe(0);
98
- expect(status.stdout).toContain(`"credentialEpoch":"${epoch}"`);
99
- const commandFrame = frame({
100
- schemaVersion: "nggaigc-command/v1",
101
- args: ["preflight"],
102
- });
103
- const session = await runHelper(distribution, target, "session", commandFrame);
104
- commandFrame.fill(0);
105
- observed.push(session);
106
- expect(session.code).toBe(0);
107
- expect(session.stdout).toContain('"clientProtocol":1');
108
- }
109
- finally {
110
- if (epoch) {
111
- const distribution = path.join(root, "plugin", "skills", "nggaigc", "scripts");
112
- observed.push(await runHelper(distribution, target, "delete-exact", undefined, epoch));
113
- }
114
- await rm(root, { recursive: true, force: true });
115
- }
116
- const combined = observed
117
- .flatMap((result) => [result.stdout, result.stderr, ...result.spawnargs])
118
- .join("\n");
119
- expect(combined).not.toContain(credential);
120
- expect(JSON.stringify(process.env)).not.toContain(credential);
121
- expect(observed.at(-1)?.code).toBe(0);
122
- }, 120_000);