superpowers-manager 0.5.0 → 0.5.2
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 +39 -9
- package/dist/artifact-tree.js +2 -1
- package/dist/cli.js +3 -3
- package/dist/commands/install.js +18 -8
- package/dist/commands/prepare.js +5 -5
- package/dist/commands/probe.js +4 -4
- package/dist/commands/uninstall.js +3 -3
- package/dist/commands/update.js +1 -1
- package/dist/domain/refs.js +4 -0
- package/dist/{adapter.js → harnesses/codex/adapter.js} +156 -27
- package/dist/{codex-compatibility.js → harnesses/codex/compatibility.js} +7 -7
- package/dist/{codex-conflicts.js → harnesses/codex/conflicts.js} +1 -1
- package/dist/{generated-plugin.js → harnesses/codex/generated-plugin.js} +8 -74
- package/dist/{codex-harness.js → harnesses/codex/harness.js} +39 -21
- package/dist/{hooks.js → harnesses/codex/hooks.js} +7 -7
- package/dist/{codex-json.js → harnesses/codex/json.js} +8 -4
- package/dist/{lifecycle.js → harnesses/codex/lifecycle.js} +2 -2
- package/dist/{manifest-overlay.js → harnesses/codex/manifest-overlay.js} +4 -4
- package/dist/harnesses/codex/marketplace.js +148 -0
- package/dist/harnesses/codex/paths.js +97 -0
- package/dist/{codex-prepare.js → harnesses/codex/prepare.js} +41 -9
- package/dist/{codex-presentation.js → harnesses/codex/presentation.js} +7 -6
- package/dist/harnesses/codex/publication.js +538 -0
- package/dist/harnesses/codex/recovery.js +338 -0
- package/dist/harnesses/codex/state.js +209 -0
- package/dist/harnesses/codex/stored-state.js +249 -0
- package/dist/{pi-compatibility.js → harnesses/pi/compatibility.js} +3 -3
- package/dist/{pi-harness.js → harnesses/pi/harness.js} +7 -7
- package/dist/{pi-install.js → harnesses/pi/install.js} +8 -8
- package/dist/{pi-native.js → harnesses/pi/native.js} +4 -4
- package/dist/{pi-package.js → harnesses/pi/package.js} +6 -6
- package/dist/{pi-prepare.js → harnesses/pi/prepare.js} +6 -6
- package/dist/{pi-settings.js → harnesses/pi/settings.js} +3 -3
- package/dist/{pi-shared-skills.js → harnesses/pi/shared-skills.js} +1 -1
- package/dist/{pi-state.js → harnesses/pi/state.js} +8 -8
- package/dist/provenance.js +3 -3
- package/dist/python-text.js +16 -0
- package/dist/skill-validation.js +52 -0
- package/dist/validate-generated-plugin-cli.js +1 -1
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +13 -4
- package/dist/codex-state.js +0 -78
- /package/dist/{pi-paths.js → harnesses/pi/paths.js} +0 -0
- /package/dist/{pi-presentation.js → harnesses/pi/presentation.js} +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open, readdir } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { parse, TomlDate } from "smol-toml";
|
|
5
|
+
import { SafetyError } from "../../safety-error.js";
|
|
6
|
+
import { codexHome } from "./paths.js";
|
|
7
|
+
const CONFIG_LIMIT = 1024 * 1024;
|
|
8
|
+
const CONFIG_DEPTH_LIMIT = 64;
|
|
9
|
+
const MANAGER_MARKETPLACE = "superpowers-manager";
|
|
10
|
+
const LEGACY_MARKETPLACE = "superpowers-wrapper";
|
|
11
|
+
const MANAGER_PLUGIN = `superpowers@${MANAGER_MARKETPLACE}`;
|
|
12
|
+
const LEGACY_PLUGIN = `superpowers@${LEGACY_MARKETPLACE}`;
|
|
13
|
+
const VERSION_SEGMENT = /^[A-Za-z0-9._+-]+$/u;
|
|
14
|
+
const MARKETPLACE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
|
|
15
|
+
function storedError(message, cause) {
|
|
16
|
+
return new SafetyError("codex-stored-state", message, { cause });
|
|
17
|
+
}
|
|
18
|
+
function isErrno(cause, code) {
|
|
19
|
+
return (cause !== null &&
|
|
20
|
+
typeof cause === "object" &&
|
|
21
|
+
"code" in cause &&
|
|
22
|
+
cause.code === code);
|
|
23
|
+
}
|
|
24
|
+
function table(value, name) {
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return {};
|
|
27
|
+
if (value === null ||
|
|
28
|
+
typeof value !== "object" ||
|
|
29
|
+
Array.isArray(value) ||
|
|
30
|
+
value instanceof TomlDate ||
|
|
31
|
+
Object.getPrototypeOf(value) !== Object.prototype) {
|
|
32
|
+
throw storedError(`Codex configuration ${name} must be a table`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
async function readConfig(path) {
|
|
37
|
+
let handle;
|
|
38
|
+
try {
|
|
39
|
+
const initial = await lstat(path);
|
|
40
|
+
if (!initial.isFile() || initial.isSymbolicLink()) {
|
|
41
|
+
throw storedError(`Codex configuration must be a regular file: ${path}`);
|
|
42
|
+
}
|
|
43
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
44
|
+
const before = await handle.stat();
|
|
45
|
+
if (!before.isFile() || before.size > CONFIG_LIMIT) {
|
|
46
|
+
throw storedError(`Codex configuration must be a bounded regular file: ${path}`);
|
|
47
|
+
}
|
|
48
|
+
const buffer = Buffer.alloc(CONFIG_LIMIT + 1);
|
|
49
|
+
let length = 0;
|
|
50
|
+
while (length < buffer.length) {
|
|
51
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
|
|
52
|
+
if (bytesRead === 0)
|
|
53
|
+
break;
|
|
54
|
+
length += bytesRead;
|
|
55
|
+
}
|
|
56
|
+
const bytes = buffer.subarray(0, length);
|
|
57
|
+
const after = await handle.stat();
|
|
58
|
+
const pathAfter = await lstat(path);
|
|
59
|
+
if (bytes.length > CONFIG_LIMIT ||
|
|
60
|
+
bytes.length !== before.size ||
|
|
61
|
+
before.dev !== after.dev ||
|
|
62
|
+
before.ino !== after.ino ||
|
|
63
|
+
before.size !== after.size ||
|
|
64
|
+
before.mtimeMs !== after.mtimeMs ||
|
|
65
|
+
before.ctimeMs !== after.ctimeMs ||
|
|
66
|
+
before.dev !== pathAfter.dev ||
|
|
67
|
+
before.ino !== pathAfter.ino ||
|
|
68
|
+
!pathAfter.isFile() ||
|
|
69
|
+
pathAfter.isSymbolicLink()) {
|
|
70
|
+
throw storedError(`Codex configuration changed while being read: ${path}`);
|
|
71
|
+
}
|
|
72
|
+
let text;
|
|
73
|
+
try {
|
|
74
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
throw storedError(`Codex configuration is not valid UTF-8: ${path}`, cause);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
return parse(text, {
|
|
81
|
+
maxDepth: CONFIG_DEPTH_LIMIT,
|
|
82
|
+
integersAsBigInt: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
throw storedError(`Codex configuration TOML is invalid: ${path}`, cause);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (cause) {
|
|
90
|
+
if (cause instanceof SafetyError && cause.module === "codex-stored-state") {
|
|
91
|
+
throw cause;
|
|
92
|
+
}
|
|
93
|
+
throw storedError(`cannot safely read Codex configuration: ${path}`, cause);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await handle?.close().catch(() => { });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function marketplaceRoot(marketplaces, name) {
|
|
100
|
+
const value = marketplaces[name];
|
|
101
|
+
if (value === undefined)
|
|
102
|
+
return null;
|
|
103
|
+
const entry = table(value, `marketplaces.${name}`);
|
|
104
|
+
if (entry.source_type !== "local" ||
|
|
105
|
+
typeof entry.source !== "string" ||
|
|
106
|
+
!isAbsolute(entry.source)) {
|
|
107
|
+
throw storedError(`Codex marketplace ${name} must name an absolute local source`);
|
|
108
|
+
}
|
|
109
|
+
return entry.source;
|
|
110
|
+
}
|
|
111
|
+
async function requireMissingManagerSource(path) {
|
|
112
|
+
try {
|
|
113
|
+
await lstat(path);
|
|
114
|
+
}
|
|
115
|
+
catch (cause) {
|
|
116
|
+
if (isErrno(cause, "ENOENT"))
|
|
117
|
+
return;
|
|
118
|
+
throw storedError(`cannot inspect Codex marketplace source: ${path}`, cause);
|
|
119
|
+
}
|
|
120
|
+
throw storedError(`Codex marketplace source is not missing: ${path}`);
|
|
121
|
+
}
|
|
122
|
+
function validVersionSegment(value) {
|
|
123
|
+
return value !== "." && value !== ".." && VERSION_SEGMENT.test(value);
|
|
124
|
+
}
|
|
125
|
+
async function cachedVersionPresent(searchRoot, marketplace) {
|
|
126
|
+
if (!MARKETPLACE_SEGMENT.test(marketplace))
|
|
127
|
+
return null;
|
|
128
|
+
let base = searchRoot;
|
|
129
|
+
for (const segment of ["plugins", "cache", marketplace, "superpowers"]) {
|
|
130
|
+
let details;
|
|
131
|
+
try {
|
|
132
|
+
details = await lstat(base);
|
|
133
|
+
}
|
|
134
|
+
catch (cause) {
|
|
135
|
+
if (isErrno(cause, "ENOENT"))
|
|
136
|
+
return false;
|
|
137
|
+
throw storedError(`cannot inspect Codex plugin cache: ${base}`, cause);
|
|
138
|
+
}
|
|
139
|
+
if (!details.isDirectory() || details.isSymbolicLink()) {
|
|
140
|
+
throw storedError(`Codex plugin cache must be a directory: ${base}`);
|
|
141
|
+
}
|
|
142
|
+
base = join(base, segment);
|
|
143
|
+
}
|
|
144
|
+
let before;
|
|
145
|
+
try {
|
|
146
|
+
before = await lstat(base);
|
|
147
|
+
}
|
|
148
|
+
catch (cause) {
|
|
149
|
+
if (isErrno(cause, "ENOENT"))
|
|
150
|
+
return false;
|
|
151
|
+
throw storedError(`cannot inspect Codex plugin cache: ${base}`, cause);
|
|
152
|
+
}
|
|
153
|
+
if (!before.isDirectory() || before.isSymbolicLink()) {
|
|
154
|
+
throw storedError(`Codex plugin cache must be a directory: ${base}`);
|
|
155
|
+
}
|
|
156
|
+
let names;
|
|
157
|
+
try {
|
|
158
|
+
names = await readdir(base);
|
|
159
|
+
}
|
|
160
|
+
catch (cause) {
|
|
161
|
+
throw storedError(`cannot inspect Codex plugin cache: ${base}`, cause);
|
|
162
|
+
}
|
|
163
|
+
let present = false;
|
|
164
|
+
for (const name of names) {
|
|
165
|
+
if (!validVersionSegment(name))
|
|
166
|
+
continue;
|
|
167
|
+
const candidate = join(base, name);
|
|
168
|
+
try {
|
|
169
|
+
const details = await lstat(candidate);
|
|
170
|
+
if (details.isSymbolicLink()) {
|
|
171
|
+
throw storedError(`Codex plugin cache entry must not be a symlink: ${candidate}`);
|
|
172
|
+
}
|
|
173
|
+
if (details.isDirectory() && !details.isSymbolicLink())
|
|
174
|
+
present = true;
|
|
175
|
+
}
|
|
176
|
+
catch (cause) {
|
|
177
|
+
if (!isErrno(cause, "ENOENT")) {
|
|
178
|
+
throw storedError(`cannot inspect Codex plugin cache entry: ${candidate}`, cause);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let after;
|
|
183
|
+
try {
|
|
184
|
+
after = await lstat(base);
|
|
185
|
+
}
|
|
186
|
+
catch (cause) {
|
|
187
|
+
throw storedError(`cannot verify Codex plugin cache: ${base}`, cause);
|
|
188
|
+
}
|
|
189
|
+
if (!after.isDirectory() ||
|
|
190
|
+
after.isSymbolicLink() ||
|
|
191
|
+
before.dev !== after.dev ||
|
|
192
|
+
before.ino !== after.ino ||
|
|
193
|
+
before.mtimeMs !== after.mtimeMs) {
|
|
194
|
+
throw storedError(`Codex plugin cache changed while being inspected: ${base}`);
|
|
195
|
+
}
|
|
196
|
+
return present;
|
|
197
|
+
}
|
|
198
|
+
export async function readCodexStoredState(env, cwd) {
|
|
199
|
+
const root = codexHome(env, cwd);
|
|
200
|
+
try {
|
|
201
|
+
const details = await lstat(root);
|
|
202
|
+
if (!details.isDirectory() || details.isSymbolicLink()) {
|
|
203
|
+
throw storedError(`Codex home must be a directory: ${root}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (cause) {
|
|
207
|
+
if (cause instanceof SafetyError && cause.module === "codex-stored-state") {
|
|
208
|
+
throw cause;
|
|
209
|
+
}
|
|
210
|
+
throw storedError(`cannot inspect Codex home: ${root}`, cause);
|
|
211
|
+
}
|
|
212
|
+
const config = await readConfig(join(root, "config.toml"));
|
|
213
|
+
const marketplaces = table(config.marketplaces, "marketplaces");
|
|
214
|
+
const managerMarketplaceRoot = marketplaceRoot(marketplaces, MANAGER_MARKETPLACE);
|
|
215
|
+
if (managerMarketplaceRoot === null) {
|
|
216
|
+
throw storedError("Codex manager marketplace is not configured");
|
|
217
|
+
}
|
|
218
|
+
await requireMissingManagerSource(managerMarketplaceRoot);
|
|
219
|
+
const legacyMarketplaceRoot = marketplaceRoot(marketplaces, LEGACY_MARKETPLACE);
|
|
220
|
+
const plugins = table(config.plugins, "plugins");
|
|
221
|
+
const searchRoot = env.SUPERPOWERS_INSTALLED_SEARCH_ROOT || root;
|
|
222
|
+
const installed = [];
|
|
223
|
+
for (const [pluginId, rawEntry] of Object.entries(plugins)) {
|
|
224
|
+
const entry = table(rawEntry, `plugins.${pluginId}`);
|
|
225
|
+
if (typeof entry.enabled !== "boolean") {
|
|
226
|
+
throw storedError(`Codex plugin ${pluginId} has invalid enabled state`);
|
|
227
|
+
}
|
|
228
|
+
if (!pluginId.startsWith("superpowers@"))
|
|
229
|
+
continue;
|
|
230
|
+
const marketplace = pluginId.slice("superpowers@".length);
|
|
231
|
+
installed.push({
|
|
232
|
+
pluginId,
|
|
233
|
+
installed: await cachedVersionPresent(searchRoot, marketplace),
|
|
234
|
+
enabled: entry.enabled === true,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const byId = (id) => installed.find((plugin) => plugin.pluginId === id);
|
|
238
|
+
const manager = byId(MANAGER_PLUGIN);
|
|
239
|
+
const legacy = byId(LEGACY_PLUGIN);
|
|
240
|
+
return {
|
|
241
|
+
managerMarketplaceRoot,
|
|
242
|
+
legacyMarketplaceRoot,
|
|
243
|
+
managerPluginPresent: manager?.installed === true,
|
|
244
|
+
managerPluginEnabled: manager?.enabled === true,
|
|
245
|
+
legacyPluginPresent: legacy?.installed === true,
|
|
246
|
+
legacyPluginEnabled: legacy?.enabled === true,
|
|
247
|
+
installedListingJson: JSON.stringify({ installed }),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { readArtifactFile, readArtifactObject, validateNativeSkill, } from "
|
|
5
|
-
import { SEMVER_RE } from "
|
|
6
|
-
import { validateSource } from "
|
|
4
|
+
import { readArtifactFile, readArtifactObject, validateNativeSkill, } from "../../artifact-tree.js";
|
|
5
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
6
|
+
import { validateSource } from "../../selection.js";
|
|
7
7
|
const OFFICIAL_SOURCES = new Set([
|
|
8
8
|
"https://github.com/obra/superpowers",
|
|
9
9
|
"https://github.com/obra/superpowers.git",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { failureResult, successResult } from "
|
|
2
|
-
import { classifyPathNoFollow } from "
|
|
3
|
-
import { installPi, removePi } from "./
|
|
4
|
-
import { piPaths } from "./
|
|
5
|
-
import { inspectPiPrepared, piPreparationLocation, preparePiCandidate, readPiPrepared, } from "./
|
|
6
|
-
import { piPresentation } from "./
|
|
7
|
-
import { inspectPiControl, inspectPiInstalled, inspectPiOwnership, } from "./
|
|
1
|
+
import { failureResult, successResult } from "../../adapter-result.js";
|
|
2
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
3
|
+
import { installPi, removePi } from "./install.js";
|
|
4
|
+
import { piPaths } from "./paths.js";
|
|
5
|
+
import { inspectPiPrepared, piPreparationLocation, preparePiCandidate, readPiPrepared, } from "./prepare.js";
|
|
6
|
+
import { piPresentation } from "./presentation.js";
|
|
7
|
+
import { inspectPiControl, inspectPiInstalled, inspectPiOwnership, } from "./state.js";
|
|
8
8
|
export const piHarness = {
|
|
9
9
|
preparationLocation: piPreparationLocation,
|
|
10
10
|
mutationRoots: async (ctx) => [
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { cp, lstat, mkdir, open, readFile, readdir, rm, rmdir, unlink, } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
-
import { failureResult, successResult, } from "
|
|
5
|
-
import { atomicWriteFile, beginDirectoryPublication, } from "
|
|
6
|
-
import { normalizePiRuntimeVersion, runPi } from "./
|
|
7
|
-
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./
|
|
8
|
-
import { piPaths } from "./
|
|
9
|
-
import { readPiSettings, resolveCanonicalPiLocalSource, } from "./
|
|
10
|
-
import { inspectPiControl, inspectPiOwnership, } from "./
|
|
11
|
-
import { assertNoFollowType, canonicalizeProspectivePath, classifyPathNoFollow, } from "
|
|
4
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
5
|
+
import { atomicWriteFile, beginDirectoryPublication, } from "../../atomic.js";
|
|
6
|
+
import { normalizePiRuntimeVersion, runPi } from "./native.js";
|
|
7
|
+
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./package.js";
|
|
8
|
+
import { piPaths } from "./paths.js";
|
|
9
|
+
import { readPiSettings, resolveCanonicalPiLocalSource, } from "./settings.js";
|
|
10
|
+
import { inspectPiControl, inspectPiOwnership, } from "./state.js";
|
|
11
|
+
import { assertNoFollowType, canonicalizeProspectivePath, classifyPathNoFollow, } from "../../safe-path.js";
|
|
12
12
|
const DEFAULTS = {
|
|
13
13
|
run: runPi,
|
|
14
14
|
beginPublication: beginDirectoryPublication,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { isAbsolute, resolve, sep } from "node:path";
|
|
3
|
-
import { AdapterMessageLog, failureResult, successResult, } from "
|
|
4
|
-
import { SEMVER_RE } from "
|
|
5
|
-
import { BOUNDED_EXECUTABLE, runValidator as runBoundedCommand, } from "
|
|
6
|
-
import { withWorkspace } from "
|
|
3
|
+
import { AdapterMessageLog, failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
5
|
+
import { BOUNDED_EXECUTABLE, runValidator as runBoundedCommand, } from "../../validator.js";
|
|
6
|
+
import { withWorkspace } from "../../workspace.js";
|
|
7
7
|
function selectedExecutable(env, invocationCwd) {
|
|
8
8
|
const configured = env.SUPERPOWERS_PI || "pi";
|
|
9
9
|
return configured.includes(sep) && !isAbsolute(configured)
|
|
@@ -3,12 +3,12 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, addArtifactHashField, digestArtifactTree, readArtifactObject, } from "
|
|
7
|
-
import { COMMIT_RE } from "
|
|
8
|
-
import { assertNoFollowType, assertSymlinkTargetContained, } from "
|
|
9
|
-
import { SafetyError } from "
|
|
10
|
-
import { validateSource } from "
|
|
11
|
-
import { assessPiCompatibility } from "./
|
|
6
|
+
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, addArtifactHashField, digestArtifactTree, readArtifactObject, } from "../../artifact-tree.js";
|
|
7
|
+
import { COMMIT_RE } from "../../domain/refs.js";
|
|
8
|
+
import { assertNoFollowType, assertSymlinkTargetContained, } from "../../safe-path.js";
|
|
9
|
+
import { SafetyError } from "../../safety-error.js";
|
|
10
|
+
import { validateSource } from "../../selection.js";
|
|
11
|
+
import { assessPiCompatibility } from "./compatibility.js";
|
|
12
12
|
const exec = promisify(execFile);
|
|
13
13
|
const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
14
14
|
// A consistency checksum, not authentication. A coherent manual rewrite of
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { failureResult, successResult, } from "
|
|
4
|
-
import { ARTIFACT_RECEIPT } from "
|
|
5
|
-
import { piPaths } from "./
|
|
6
|
-
import { assessPiCompatibility, samePiSource } from "./
|
|
7
|
-
import { digestPiTree, materializePiTree, readPiPackageAssessment, piReceiptBinding, } from "./
|
|
8
|
-
import { classifyPathNoFollow } from "
|
|
3
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { ARTIFACT_RECEIPT } from "../../artifact-tree.js";
|
|
5
|
+
import { piPaths } from "./paths.js";
|
|
6
|
+
import { assessPiCompatibility, samePiSource } from "./compatibility.js";
|
|
7
|
+
import { digestPiTree, materializePiTree, readPiPackageAssessment, piReceiptBinding, } from "./package.js";
|
|
8
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
9
9
|
export function piPreparationLocation(ctx) {
|
|
10
10
|
return {
|
|
11
11
|
destinationRoot: piPaths(ctx.env ?? {}, process.cwd()).preparedRoot,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { canonicalizeProspectivePath } from "
|
|
5
|
-
import { SafetyError } from "
|
|
6
|
-
import { parseStrictJson, } from "
|
|
4
|
+
import { canonicalizeProspectivePath } from "../../safe-path.js";
|
|
5
|
+
import { SafetyError } from "../../safety-error.js";
|
|
6
|
+
import { parseStrictJson, } from "../../strict-json.js";
|
|
7
7
|
const SETTINGS_PROFILE = {
|
|
8
8
|
duplicateKeys: "last-wins",
|
|
9
9
|
nonStandardConstants: "reject",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, readlink, realpath } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { classifyPathNoFollow } from "
|
|
4
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
5
5
|
const PI_IGNORE_FILES = [".gitignore", ".ignore", ".fdignore"];
|
|
6
6
|
function toPosixPath(path) {
|
|
7
7
|
return path.split(sep).join("/");
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { sharedPiSkillsActivity } from "./
|
|
3
|
-
import { failureResult, successResult, } from "
|
|
4
|
-
import { readArtifactObject } from "
|
|
5
|
-
import { samePiSource } from "./
|
|
6
|
-
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./
|
|
7
|
-
import { piPaths } from "./
|
|
8
|
-
import { readPiSettings, resolveCanonicalPiLocalSource, resolvePiLocalSource, } from "./
|
|
9
|
-
import { classifyPathNoFollow } from "
|
|
2
|
+
import { sharedPiSkillsActivity } from "./shared-skills.js";
|
|
3
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { readArtifactObject } from "../../artifact-tree.js";
|
|
5
|
+
import { samePiSource } from "./compatibility.js";
|
|
6
|
+
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./package.js";
|
|
7
|
+
import { piPaths } from "./paths.js";
|
|
8
|
+
import { readPiSettings, resolveCanonicalPiLocalSource, resolvePiLocalSource, } from "./settings.js";
|
|
9
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
10
10
|
// Verified remote spellings only: Pi 0.85.1 accepts http:// as Git. Unprefixed
|
|
11
11
|
// github: and git+ forms stay local; git:-wrapped aliases and explicit transports
|
|
12
12
|
// normalize only an exact GitHub authority and repository path (native source
|
package/dist/provenance.js
CHANGED
|
@@ -4,16 +4,16 @@ import { COMMIT_INPUT_RE } from "./domain/refs.js";
|
|
|
4
4
|
import { escapePythonJsonString } from "./python-json.js";
|
|
5
5
|
import { SafetyError } from "./safety-error.js";
|
|
6
6
|
import { parseStrictJson, } from "./strict-json.js";
|
|
7
|
-
|
|
7
|
+
const PROVENANCE_STRICT_PROFILE = {
|
|
8
8
|
duplicateKeys: "last-wins",
|
|
9
9
|
nonStandardConstants: "reject",
|
|
10
10
|
maxDepth: 256,
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
const PROVENANCE_LENIENT_PROFILE = {
|
|
13
13
|
duplicateKeys: "last-wins",
|
|
14
14
|
nonStandardConstants: "reject",
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
const PROVENANCE_CODEX_SOURCE_PROFILE = {
|
|
17
17
|
duplicateKeys: "last-wins",
|
|
18
18
|
nonStandardConstants: "accept",
|
|
19
19
|
};
|
package/dist/python-text.js
CHANGED
|
@@ -72,3 +72,19 @@ export function pythonSplitlines(value) {
|
|
|
72
72
|
lines.push(value.slice(start));
|
|
73
73
|
return lines;
|
|
74
74
|
}
|
|
75
|
+
/** Python `sorted()` orders by code point; JavaScript's default sort does not. */
|
|
76
|
+
export function compareByCodePoint(left, right) {
|
|
77
|
+
// `Array.from` splits by code point, exactly as spreading would; oxlint's
|
|
78
|
+
// `no-misused-spread` rejects the spread form, and grapheme segmentation is
|
|
79
|
+
// the wrong unit here — Python compares code points.
|
|
80
|
+
const leftPoints = Array.from(left);
|
|
81
|
+
const rightPoints = Array.from(right);
|
|
82
|
+
const shared = Math.min(leftPoints.length, rightPoints.length);
|
|
83
|
+
for (let index = 0; index < shared; index += 1) {
|
|
84
|
+
const a = leftPoints[index].codePointAt(0);
|
|
85
|
+
const b = rightPoints[index].codePointAt(0);
|
|
86
|
+
if (a !== b)
|
|
87
|
+
return a - b;
|
|
88
|
+
}
|
|
89
|
+
return leftPoints.length - rightPoints.length;
|
|
90
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { lstat, readdir, readFile, readlink, stat } from "node:fs/promises";
|
|
2
|
+
import { pythonSplitlines, pythonStrip } from "./python-text.js";
|
|
3
|
+
export const DEFAULT_FS_DEPS = {
|
|
4
|
+
lstat,
|
|
5
|
+
stat,
|
|
6
|
+
readdir,
|
|
7
|
+
readlink,
|
|
8
|
+
readFile,
|
|
9
|
+
};
|
|
10
|
+
const STRICT_DECODER = new TextDecoder("utf-8", {
|
|
11
|
+
fatal: true,
|
|
12
|
+
ignoreBOM: true,
|
|
13
|
+
});
|
|
14
|
+
export async function validateSkillFrontmatter(skillMd, skillName, errors, deps) {
|
|
15
|
+
let contents;
|
|
16
|
+
try {
|
|
17
|
+
contents = STRICT_DECODER.decode(await deps.readFile(skillMd));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
errors.push(`skill \`${skillName}\` has unreadable UTF-8 \`SKILL.md\``);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (contents === "") {
|
|
24
|
+
errors.push(`skill \`${skillName}\` has empty \`SKILL.md\``);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const lines = pythonSplitlines(contents);
|
|
28
|
+
if (lines.length === 0 || lines[0] !== "---") {
|
|
29
|
+
errors.push(`skill \`${skillName}\` must start with \`---\``);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const closingIndex = lines.indexOf("---", 1);
|
|
33
|
+
if (closingIndex === -1) {
|
|
34
|
+
errors.push(`skill \`${skillName}\` frontmatter is not closed`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const frontmatter = lines.slice(1, closingIndex);
|
|
38
|
+
for (const key of ["name", "description"]) {
|
|
39
|
+
const matches = frontmatter.filter((line) => line.startsWith(`${key}:`));
|
|
40
|
+
if (matches.length !== 1) {
|
|
41
|
+
errors.push(`skill \`${skillName}\` frontmatter must contain exactly one top-level \`${key}:\``);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const value = pythonStrip(matches[0].slice(matches[0].indexOf(":") + 1));
|
|
45
|
+
if (value === "" ||
|
|
46
|
+
value === "''" ||
|
|
47
|
+
value === '""' ||
|
|
48
|
+
value.startsWith("#")) {
|
|
49
|
+
errors.push(`skill \`${skillName}\` frontmatter field \`${key}\` must be non-empty`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
2
|
import { oneLine, parseFlags, UsageError } from "./cli-arguments.js";
|
|
3
|
-
import { validateGeneratedPlugin, } from "./generated-plugin.js";
|
|
3
|
+
import { validateGeneratedPlugin, } from "./harnesses/codex/generated-plugin.js";
|
|
4
4
|
const FLAGS = [
|
|
5
5
|
"plugin-root",
|
|
6
6
|
"source",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
7
|
+
list of conditions and the following disclaimer.
|
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer in the
|
|
10
|
+
documentation and/or other materials provided with the distribution.
|
|
11
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
12
|
+
may be used to endorse or promote products derived from this software without
|
|
13
|
+
specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|