create-agdf 0.13.5 → 0.13.7
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 +7 -1
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -3
- package/generated/plugins/agdf/hooks/session-start.sh +31 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +30 -9
- package/generated/plugins/agdf/runtime/agdf-local.js +8 -1
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +30 -9
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +116 -31
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +206 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +64 -19
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +30 -9
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/cli/application.js +4 -3
- package/lib/fs-swap.js +26 -0
- package/lib/installers/local-development.js +202 -0
- package/lib/installers/local-marketplace.js +253 -46
- package/lib/installers/opencode.js +17 -2
- package/lib/installers/plugin-installers.js +88 -22
- package/lib/lifecycle/operations.js +9 -5
- package/lib/lifecycle/status.js +10 -2
- package/lib/public-plugin/builder.js +5 -4
- package/lib/runtime/local-validator.js +116 -31
- package/lib/runtime/plugin-provenance.js +206 -0
- package/package.json +6 -2
package/lib/cli/application.js
CHANGED
|
@@ -35,7 +35,7 @@ import { CliUsageError, parseArgs } from "./parse-args.js";
|
|
|
35
35
|
import { pluginDefinition } from "./runtime-context.js";
|
|
36
36
|
import { createValidationHandlers } from "./validation-handlers.js";
|
|
37
37
|
|
|
38
|
-
function createHandlers({ io, env, exec, prepare }) {
|
|
38
|
+
function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
|
|
39
39
|
const installerAdapters = {
|
|
40
40
|
...(exec ? { exec } : {}),
|
|
41
41
|
...(prepare ? { prepare } : {}),
|
|
@@ -112,7 +112,7 @@ function createHandlers({ io, env, exec, prepare }) {
|
|
|
112
112
|
["opencode", (options) => {
|
|
113
113
|
const configDir = options.dirExplicit ? options.dir : defaultOpenCodeConfigDir();
|
|
114
114
|
try {
|
|
115
|
-
const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir));
|
|
115
|
+
const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir, { packageSource: openCodePackageSource }));
|
|
116
116
|
runLifecyclePhase("global_surface", () => installOpenCodeGlobalSurface(configDir));
|
|
117
117
|
const report = runLifecyclePhase("verification", () => evaluateOpenCodeStatus(options.dir, configDir, result.transition));
|
|
118
118
|
const alignmentHealthy = ["already_matching", "aligned"].includes(result.sdk_alignment.status);
|
|
@@ -150,6 +150,7 @@ function createHandlers({ io, env, exec, prepare }) {
|
|
|
150
150
|
`experimental_hooks=${report.experimental_hooks.aggregate}`,
|
|
151
151
|
`host_sdk_version=${report.host_sdk_version.status};policy=${report.host_sdk_version.policy}`,
|
|
152
152
|
`sdk_alignment=${result.sdk_alignment.status};target=${result.sdk_alignment.target_version || "unknown"};installed=${result.sdk_alignment.installed_version || "unknown"}`,
|
|
153
|
+
`package_source=${result.package_source.kind}${result.package_source.digest ? `;digest=${result.package_source.digest}` : ""}`,
|
|
153
154
|
],
|
|
154
155
|
},
|
|
155
156
|
restart: { required: true, reason: "host_reload" },
|
|
@@ -341,7 +342,7 @@ export async function runCli(argv = process.argv.slice(2), adapters = {}) {
|
|
|
341
342
|
}
|
|
342
343
|
const command = resolveCommand(options.target);
|
|
343
344
|
if (!command) throw new Error(`No handler is registered for ${options.target}.`);
|
|
344
|
-
const handler = createHandlers({ io, env, exec: adapters.exec, prepare: adapters.prepare }).get(command.handler);
|
|
345
|
+
const handler = createHandlers({ io, env, exec: adapters.exec, prepare: adapters.prepare, openCodePackageSource: adapters.openCodePackageSource }).get(command.handler);
|
|
345
346
|
if (!handler) throw new Error(`No implementation is registered for ${command.name}.`);
|
|
346
347
|
return await handler(options);
|
|
347
348
|
}
|
package/lib/fs-swap.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { renameSync } from "node:fs";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
const RETRY_ATTEMPTS = 5;
|
|
5
|
+
const RETRY_BASE_DELAY_MS = 50;
|
|
6
|
+
|
|
7
|
+
function sleepSync(milliseconds) {
|
|
8
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function renameSyncWithRetry(source, destination, adapters = {}) {
|
|
12
|
+
const rename = adapters.rename ?? renameSync;
|
|
13
|
+
const sleep = adapters.sleep ?? sleepSync;
|
|
14
|
+
const platform = adapters.platform ?? process.platform;
|
|
15
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
16
|
+
try {
|
|
17
|
+
rename(source, destination);
|
|
18
|
+
return;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// Windows antivirus/indexer handle locks surface as transient EPERM on
|
|
21
|
+
// renames of freshly written directory trees; retry briefly before failing.
|
|
22
|
+
if (platform !== "win32" || error?.code !== "EPERM" || attempt >= RETRY_ATTEMPTS) throw error;
|
|
23
|
+
sleep(RETRY_BASE_DELAY_MS * attempt);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { join, resolve, win32 as pathWin32 } from "node:path";
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
|
|
17
|
+
const LOCAL_PACKAGE_OWNER = "create-agdf";
|
|
18
|
+
const LOCAL_PACKAGE_KIND = "opencode_local_development_package";
|
|
19
|
+
const OWNERSHIP_FILE = ".agdf-owned.json";
|
|
20
|
+
|
|
21
|
+
function sha256File(path) {
|
|
22
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function pathInside(root, candidate) {
|
|
26
|
+
const relativePath = candidate.slice(root.length);
|
|
27
|
+
return candidate === root || (candidate.startsWith(root) && ["/", "\\"].includes(relativePath[0]));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readJson(path, label) {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`${label} is invalid: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parsePackResult(output) {
|
|
39
|
+
const start = String(output).indexOf("[");
|
|
40
|
+
if (start < 0) throw new Error("AGDF local package build did not return npm pack JSON.");
|
|
41
|
+
const parsed = JSON.parse(String(output).slice(start));
|
|
42
|
+
const result = parsed?.[0];
|
|
43
|
+
const filename = result?.filename;
|
|
44
|
+
if (typeof filename !== "string" || !filename || filename.includes("/") || filename.includes("\\")) {
|
|
45
|
+
throw new Error("AGDF local package build returned an unsafe tarball name.");
|
|
46
|
+
}
|
|
47
|
+
if (!Array.isArray(result.files) || result.files.length === 0) {
|
|
48
|
+
throw new Error("AGDF local package build did not report its packed file inventory.");
|
|
49
|
+
}
|
|
50
|
+
return { filename, files: result.files };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function localNpmExecutable(platform = process.platform, execPath = process.execPath) {
|
|
54
|
+
if (platform !== "win32") return { executable: "npm", prefix: [] };
|
|
55
|
+
return {
|
|
56
|
+
executable: execPath,
|
|
57
|
+
prefix: [pathWin32.join(pathWin32.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js")],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function digestPackedFiles(packageRoot, files) {
|
|
62
|
+
const root = resolve(packageRoot);
|
|
63
|
+
const paths = files.map((entry) => {
|
|
64
|
+
if (!entry
|
|
65
|
+
|| typeof entry.path !== "string"
|
|
66
|
+
|| !entry.path
|
|
67
|
+
|| entry.path.includes("\\")
|
|
68
|
+
|| !Number.isInteger(entry.mode)) {
|
|
69
|
+
throw new Error("AGDF local package build returned an invalid packed file path.");
|
|
70
|
+
}
|
|
71
|
+
const path = resolve(root, entry.path);
|
|
72
|
+
if (!pathInside(root, path) || path === root) throw new Error(`AGDF local package build returned an unsafe packed file path: ${entry.path}`);
|
|
73
|
+
const stats = lstatSync(path);
|
|
74
|
+
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error(`AGDF local package build returned a non-file package entry: ${entry.path}`);
|
|
75
|
+
return { path, relative: entry.path.replaceAll("\\", "/"), mode: entry.mode };
|
|
76
|
+
}).sort((left, right) => left.relative.localeCompare(right.relative));
|
|
77
|
+
const hash = createHash("sha256");
|
|
78
|
+
for (const entry of paths) {
|
|
79
|
+
hash.update(entry.relative);
|
|
80
|
+
hash.update("\0");
|
|
81
|
+
hash.update(String(entry.mode));
|
|
82
|
+
hash.update("\0");
|
|
83
|
+
hash.update(readFileSync(entry.path));
|
|
84
|
+
hash.update("\0");
|
|
85
|
+
}
|
|
86
|
+
return hash.digest("hex");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateOwnedPackageDirectory(root, expected = {}) {
|
|
90
|
+
const rootStats = lstatSync(root);
|
|
91
|
+
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) throw new Error(`Refusing unsafe AGDF local package directory: ${root}`);
|
|
92
|
+
const marker = readJson(join(root, OWNERSHIP_FILE), "AGDF local package ownership marker");
|
|
93
|
+
if (marker.schema_version !== 1
|
|
94
|
+
|| marker.owner !== LOCAL_PACKAGE_OWNER
|
|
95
|
+
|| marker.kind !== LOCAL_PACKAGE_KIND
|
|
96
|
+
|| !/^[a-f0-9]{64}$/.test(marker.digest ?? "")
|
|
97
|
+
|| !/^[a-f0-9]{64}$/.test(marker.archive_digest ?? "")
|
|
98
|
+
|| typeof marker.filename !== "string"
|
|
99
|
+
|| !marker.filename) {
|
|
100
|
+
throw new Error(`Refusing unowned AGDF local package directory: ${root}`);
|
|
101
|
+
}
|
|
102
|
+
if (expected.version && marker.version !== expected.version) throw new Error(`AGDF local package version mismatch: ${root}`);
|
|
103
|
+
if (expected.digest && marker.digest !== expected.digest) throw new Error(`AGDF local package digest mismatch: ${root}`);
|
|
104
|
+
const tarball = join(root, marker.filename);
|
|
105
|
+
if (!existsSync(tarball)
|
|
106
|
+
|| !lstatSync(tarball).isFile()
|
|
107
|
+
|| lstatSync(tarball).isSymbolicLink()
|
|
108
|
+
|| sha256File(tarball) !== marker.archive_digest) {
|
|
109
|
+
throw new Error(`AGDF local package tarball is missing or tampered: ${root}`);
|
|
110
|
+
}
|
|
111
|
+
return { marker, tarball };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function validateLocalOpenCodePackageSource(source) {
|
|
115
|
+
if (!source
|
|
116
|
+
|| source.kind !== "local_checkout"
|
|
117
|
+
|| typeof source.root !== "string"
|
|
118
|
+
|| typeof source.dataRoot !== "string"
|
|
119
|
+
|| !/^[a-f0-9]{64}$/.test(source.digest ?? "")) {
|
|
120
|
+
throw new Error("Invalid AGDF OpenCode local package source.");
|
|
121
|
+
}
|
|
122
|
+
const root = resolve(source.root);
|
|
123
|
+
const dataRoot = resolve(source.dataRoot);
|
|
124
|
+
const expectedRoot = join(dataRoot, "packages", "local", source.digest);
|
|
125
|
+
if (root !== expectedRoot) throw new Error("AGDF OpenCode local package source is outside its owned data root.");
|
|
126
|
+
const { marker, tarball } = validateOwnedPackageDirectory(root, {
|
|
127
|
+
version: source.version,
|
|
128
|
+
digest: source.digest,
|
|
129
|
+
});
|
|
130
|
+
const specifier = tarball;
|
|
131
|
+
if (source.specifier && source.specifier !== specifier) throw new Error("AGDF OpenCode local package specifier does not match its owned tarball.");
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
kind: "local_checkout",
|
|
134
|
+
dataRoot,
|
|
135
|
+
root,
|
|
136
|
+
markerPath: join(root, OWNERSHIP_FILE),
|
|
137
|
+
tarball,
|
|
138
|
+
specifier,
|
|
139
|
+
version: marker.version,
|
|
140
|
+
digest: marker.digest,
|
|
141
|
+
archiveDigest: marker.archive_digest,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function prepareLocalOpenCodePackage({
|
|
146
|
+
dataRoot,
|
|
147
|
+
packageRoot,
|
|
148
|
+
expectedVersion,
|
|
149
|
+
exec = execFileSync,
|
|
150
|
+
} = {}) {
|
|
151
|
+
if (!dataRoot || !packageRoot || !expectedVersion) throw new Error("AGDF local package preparation requires dataRoot, packageRoot and expectedVersion.");
|
|
152
|
+
const localRoot = resolve(dataRoot, "packages", "local");
|
|
153
|
+
mkdirSync(localRoot, { recursive: true });
|
|
154
|
+
const stageRoot = mkdtempSync(join(localRoot, ".stage-"));
|
|
155
|
+
try {
|
|
156
|
+
const npm = localNpmExecutable();
|
|
157
|
+
const output = exec(npm.executable, [
|
|
158
|
+
...npm.prefix,
|
|
159
|
+
"pack",
|
|
160
|
+
"--ignore-scripts",
|
|
161
|
+
"--json",
|
|
162
|
+
"--pack-destination",
|
|
163
|
+
stageRoot,
|
|
164
|
+
], {
|
|
165
|
+
cwd: resolve(packageRoot),
|
|
166
|
+
encoding: "utf8",
|
|
167
|
+
stdio: "pipe",
|
|
168
|
+
});
|
|
169
|
+
const { filename, files } = parsePackResult(output);
|
|
170
|
+
const stagedTarball = join(stageRoot, filename);
|
|
171
|
+
if (!existsSync(stagedTarball) || !statSync(stagedTarball).isFile()) throw new Error("AGDF local package tarball was not created.");
|
|
172
|
+
const digest = digestPackedFiles(packageRoot, files);
|
|
173
|
+
const archiveDigest = sha256File(stagedTarball);
|
|
174
|
+
const stableRoot = join(localRoot, digest);
|
|
175
|
+
if (existsSync(stableRoot)) {
|
|
176
|
+
const existing = validateLocalOpenCodePackageSource({ kind: "local_checkout", dataRoot, root: stableRoot, version: expectedVersion, digest });
|
|
177
|
+
if (existing.archiveDigest !== archiveDigest) throw new Error(`AGDF local package archive is not deterministic for packed content: ${stableRoot}`);
|
|
178
|
+
rmSync(stageRoot, { recursive: true, force: true });
|
|
179
|
+
return existing;
|
|
180
|
+
}
|
|
181
|
+
writeFileSync(join(stageRoot, OWNERSHIP_FILE), `${JSON.stringify({
|
|
182
|
+
schema_version: 1,
|
|
183
|
+
owner: LOCAL_PACKAGE_OWNER,
|
|
184
|
+
kind: LOCAL_PACKAGE_KIND,
|
|
185
|
+
version: expectedVersion,
|
|
186
|
+
digest,
|
|
187
|
+
archive_digest: archiveDigest,
|
|
188
|
+
filename,
|
|
189
|
+
}, null, 2)}\n`, "utf8");
|
|
190
|
+
renameSync(stageRoot, stableRoot);
|
|
191
|
+
return validateLocalOpenCodePackageSource({ kind: "local_checkout", dataRoot, root: stableRoot, version: expectedVersion, digest });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (existsSync(stageRoot)) rmSync(stageRoot, { recursive: true, force: true });
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const localDevelopmentConstants = Object.freeze({
|
|
199
|
+
owner: LOCAL_PACKAGE_OWNER,
|
|
200
|
+
kind: LOCAL_PACKAGE_KIND,
|
|
201
|
+
ownershipFile: OWNERSHIP_FILE,
|
|
202
|
+
});
|