codex-plugin-doctor 1.59.0 → 1.61.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.
@@ -0,0 +1,61 @@
1
+ const sources = Object.freeze([
2
+ "https://developers.openai.com/plugins/build/plugins",
3
+ "https://developers.openai.com/plugins/deploy/submission-errors"
4
+ ]);
5
+ const portalRules = Object.freeze([
6
+ Object.freeze({ id: "plugin.submission.archive.plugin_name_mismatch", status: "unavailable", reason: "Requires the previously published plugin identity.", source: "submission-errors" }),
7
+ Object.freeze({ id: "plugin.submission.archive.plugin_version_unchanged", status: "unavailable", reason: "Requires the previously published plugin version.", source: "submission-errors" }),
8
+ Object.freeze({ id: "plugin.submission.archive.manifest_normalized", status: "unavailable", reason: "The portal owns exact manifest normalization.", source: "submission-errors" }),
9
+ Object.freeze({ id: "plugin.submission.archive.developer_name_defaulted", status: "unavailable", reason: "Requires the selected verified developer identity.", source: "submission-errors" }),
10
+ Object.freeze({ id: "plugin.submission.archive.path_length", status: "unavailable", reason: "The public reference does not publish a numeric path-length limit.", source: "submission-errors" }),
11
+ Object.freeze({ id: "plugin.submission.archive.normalization_algorithm", status: "unavailable", reason: "The portal does not publish its case and Unicode collision algorithm.", source: "submission-errors" }),
12
+ Object.freeze({ id: "plugin.submission.archive.claude_format_normalized", status: "manual", reason: "The portal owns Claude-format normalization details.", source: "submission-errors" })
13
+ ]);
14
+ export const submissionArchiveRuleset = Object.freeze({
15
+ version: "openai-directory-archive-2026-08-23",
16
+ reviewedAt: "2026-08-23",
17
+ sources,
18
+ limits: Object.freeze({
19
+ compressedBytes: 100 * 1000 * 1000,
20
+ entries: 5_000,
21
+ memberBytes: 100 * 1024 * 1024,
22
+ totalBytes: 512 * 1024 * 1024
23
+ }),
24
+ structures: Object.freeze({
25
+ eocdComments: "automatic",
26
+ zip64: "automatic",
27
+ dataDescriptors: "automatic"
28
+ }),
29
+ compression: Object.freeze({
30
+ stored: "automatic",
31
+ deflate: "automatic"
32
+ }),
33
+ collisionAlgorithm: "NFKC + toLowerCase per segment",
34
+ automaticChecks: Object.freeze([
35
+ "plugin.submission.archive.invalid_file",
36
+ "plugin.submission.archive.invalid_zip",
37
+ "plugin.submission.archive.too_large",
38
+ "plugin.submission.archive.entry_count",
39
+ "plugin.submission.archive.member_too_large",
40
+ "plugin.submission.archive.total_too_large",
41
+ "plugin.submission.archive.encrypted",
42
+ "plugin.submission.archive.header_mismatch",
43
+ "plugin.submission.archive.descriptor_invalid",
44
+ "plugin.submission.archive.range_invalid",
45
+ "plugin.submission.archive.crc_mismatch",
46
+ "plugin.submission.archive.path_invalid",
47
+ "plugin.submission.archive.path_duplicate",
48
+ "plugin.submission.archive.path_conflict",
49
+ "plugin.submission.archive.type_unsupported",
50
+ "plugin.submission.archive.normalization_collision",
51
+ "plugin.submission.archive.root_missing",
52
+ "plugin.submission.archive.root_ambiguous",
53
+ "plugin.submission.archive.root_siblings",
54
+ "plugin.submission.archive.manifest_missing",
55
+ "plugin.submission.archive.skill_missing",
56
+ "plugin.submission.archive.mcp_excluded",
57
+ "plugin.submission.archive.app_excluded",
58
+ "plugin.submission.archive.screenshot_excluded"
59
+ ]),
60
+ portalRules
61
+ });
@@ -1,6 +1,8 @@
1
- import type { DiscoveredPackage } from "../domain/types.js";
1
+ import type { DiscoveredPackage, PluginManifest } from "../domain/types.js";
2
+ import { type SubmissionPackageReader } from "./submission-package-reader.js";
2
3
  import type { SubmissionFinding } from "./submission-preflight.js";
3
4
  export interface SubmissionAssetResult {
4
5
  findings: SubmissionFinding[];
5
6
  }
7
+ export declare function validateSubmissionAssetsFromReader(manifest: PluginManifest, reader: SubmissionPackageReader): Promise<SubmissionAssetResult>;
6
8
  export declare function validateSubmissionAssets(discoveredPackage: DiscoveredPackage): Promise<SubmissionAssetResult>;
@@ -1,8 +1,7 @@
1
- import { readFile, stat } from "node:fs/promises";
2
1
  import path from "node:path";
3
2
  import { inflateSync } from "node:zlib";
4
3
  import { XMLParser, XMLValidator } from "fast-xml-parser";
5
- import { resolveSafePackagePath } from "./plugin-components.js";
4
+ import { createDirectorySubmissionPackageReader } from "./submission-package-reader.js";
6
5
  const maxAssetBytes = 5 * 1024 * 1024;
7
6
  const MAX_DECODED_PNG_BYTES = 72 * 1024 * 1024;
8
7
  const minimumDimension = 48;
@@ -360,40 +359,60 @@ function dimensionFinding(field, packagePath, format, dimensions) {
360
359
  }
361
360
  return null;
362
361
  }
363
- async function validateAsset(rootPath, field, value) {
362
+ function assetPackagePath(value) {
363
+ if (!value.startsWith("./"))
364
+ return null;
365
+ const packagePath = value.slice(2);
366
+ if (packagePath === ""
367
+ || packagePath.startsWith("/")
368
+ || /^[a-zA-Z]:/u.test(packagePath)
369
+ || packagePath.includes("\\")
370
+ || /[\u0000-\u001F\u007F]/u.test(packagePath)) {
371
+ return null;
372
+ }
373
+ const normalizedPackagePath = path.posix.normalize(packagePath);
374
+ if (normalizedPackagePath === "."
375
+ || normalizedPackagePath === ""
376
+ || normalizedPackagePath === ".."
377
+ || normalizedPackagePath.startsWith("../")
378
+ || path.posix.isAbsolute(normalizedPackagePath))
379
+ return null;
380
+ return normalizedPackagePath;
381
+ }
382
+ async function validateAsset(reader, field, value) {
364
383
  if (value === undefined) {
365
384
  return [finding("plugin.submission.asset.required", "Branding asset is required.", assetEvidence(field))];
366
385
  }
367
386
  if (typeof value !== "string") {
368
387
  return [finding("plugin.submission.asset.invalid_path", "Branding asset path is invalid.", assetEvidence(field))];
369
388
  }
370
- const resolved = await resolveSafePackagePath(rootPath, value);
371
- if (resolved === null) {
389
+ const packagePath = assetPackagePath(value);
390
+ if (packagePath === null) {
372
391
  return [finding("plugin.submission.asset.invalid_path", "Branding asset path is invalid.", assetEvidence(field))];
373
392
  }
374
- const extension = extensions.get(path.extname(resolved.packagePath).toLowerCase());
393
+ const extension = extensions.get(path.extname(packagePath).toLowerCase());
375
394
  if (extension === undefined) {
376
- return [finding("plugin.submission.asset.unsupported_format", "Branding asset format is unsupported.", assetEvidence(field, resolved.packagePath))];
395
+ return [finding("plugin.submission.asset.unsupported_format", "Branding asset format is unsupported.", assetEvidence(field, packagePath))];
377
396
  }
378
- let details;
379
- try {
380
- details = await stat(resolved.path);
397
+ const details = await reader.stat(packagePath).catch(() => null);
398
+ if (details === null) {
399
+ return [finding("plugin.submission.asset.missing", "Branding asset is missing.", assetEvidence(field, packagePath, extension))];
381
400
  }
382
- catch {
383
- return [finding("plugin.submission.asset.missing", "Branding asset is missing.", assetEvidence(field, resolved.packagePath, extension))];
401
+ if (details.safeResolution === "outside") {
402
+ return [finding("plugin.submission.asset.invalid_path", "Branding asset path is invalid.", assetEvidence(field))];
384
403
  }
385
- if (!details.isFile()) {
386
- return [finding("plugin.submission.asset.unsupported_format", "Branding asset must be a regular file.", assetEvidence(field, resolved.packagePath, extension))];
404
+ if (details.safeResolution !== "safe") {
405
+ return [finding("plugin.submission.asset.missing", "Branding asset is missing.", assetEvidence(field, packagePath, extension))];
387
406
  }
388
- if (details.size > maxAssetBytes) {
389
- return [finding("plugin.submission.asset.too_large", "Branding asset exceeds the size limit.", { ...assetEvidence(field, resolved.packagePath, extension), limit: maxAssetBytes })];
407
+ if (details.resolvedKind !== "file") {
408
+ return [finding("plugin.submission.asset.unsupported_format", "Branding asset must be a regular file.", assetEvidence(field, packagePath, extension))];
390
409
  }
391
- let content;
392
- try {
393
- content = await readFile(resolved.path);
410
+ if (details.size > maxAssetBytes) {
411
+ return [finding("plugin.submission.asset.too_large", "Branding asset exceeds the size limit.", { ...assetEvidence(field, packagePath, extension), limit: maxAssetBytes })];
394
412
  }
395
- catch {
396
- return [finding("plugin.submission.asset.missing", "Branding asset cannot be read.", assetEvidence(field, resolved.packagePath, extension))];
413
+ const content = await reader.read(packagePath, maxAssetBytes).catch(() => null);
414
+ if (content === null) {
415
+ return [finding("plugin.submission.asset.missing", "Branding asset cannot be read.", assetEvidence(field, packagePath, extension))];
397
416
  }
398
417
  if (extension === "svg") {
399
418
  let source;
@@ -401,30 +420,32 @@ async function validateAsset(rootPath, field, value) {
401
420
  source = new TextDecoder("utf-8", { fatal: true }).decode(content);
402
421
  }
403
422
  catch {
404
- return [finding("plugin.submission.asset.unsafe_svg", "SVG must be valid UTF-8.", assetEvidence(field, resolved.packagePath, extension))];
423
+ return [finding("plugin.submission.asset.unsafe_svg", "SVG must be valid UTF-8.", assetEvidence(field, packagePath, extension))];
405
424
  }
406
425
  const dimensions = svgDimensions(source);
407
426
  if (dimensions === null) {
408
- return [finding("plugin.submission.asset.unsafe_svg", "SVG is unsafe or lacks valid dimensions.", assetEvidence(field, resolved.packagePath, extension))];
427
+ return [finding("plugin.submission.asset.unsafe_svg", "SVG is unsafe or lacks valid dimensions.", assetEvidence(field, packagePath, extension))];
409
428
  }
410
- const invalidDimensions = dimensionFinding(field, resolved.packagePath, extension, dimensions);
429
+ const invalidDimensions = dimensionFinding(field, packagePath, extension, dimensions);
411
430
  return invalidDimensions === null ? [] : [invalidDimensions];
412
431
  }
413
432
  const decoded = rasterAsset(content);
414
433
  if (decoded === null) {
415
- return [finding("plugin.submission.asset.decode_failed", "Branding asset could not be decoded.", assetEvidence(field, resolved.packagePath, extension))];
434
+ return [finding("plugin.submission.asset.decode_failed", "Branding asset could not be decoded.", assetEvidence(field, packagePath, extension))];
416
435
  }
417
436
  if (decoded.format !== extension) {
418
- return [finding("plugin.submission.asset.extension_mismatch", "Branding asset extension does not match its content.", assetEvidence(field, resolved.packagePath, decoded.format, decoded.dimensions))];
437
+ return [finding("plugin.submission.asset.extension_mismatch", "Branding asset extension does not match its content.", assetEvidence(field, packagePath, decoded.format, decoded.dimensions))];
419
438
  }
420
- const invalidDimensions = dimensionFinding(field, resolved.packagePath, decoded.format, decoded.dimensions);
439
+ const invalidDimensions = dimensionFinding(field, packagePath, decoded.format, decoded.dimensions);
421
440
  return invalidDimensions === null ? [] : [invalidDimensions];
422
441
  }
423
- export async function validateSubmissionAssets(discoveredPackage) {
424
- const listing = discoveredPackage.manifest.interface;
425
- const interfaceValues = isRecord(listing) ? listing : {};
442
+ export async function validateSubmissionAssetsFromReader(manifest, reader) {
443
+ const interfaceValues = isRecord(manifest.interface) ? manifest.interface : {};
426
444
  const findings = [];
427
445
  for (const field of assetFields)
428
- findings.push(...await validateAsset(discoveredPackage.rootPath, field, interfaceValues[field]));
446
+ findings.push(...await validateAsset(reader, field, interfaceValues[field]));
429
447
  return { findings };
430
448
  }
449
+ export async function validateSubmissionAssets(discoveredPackage) {
450
+ return validateSubmissionAssetsFromReader(discoveredPackage.manifest, createDirectorySubmissionPackageReader(discoveredPackage.rootPath));
451
+ }
@@ -0,0 +1,15 @@
1
+ export type SubmissionPackageEntryKind = "file" | "directory" | "symlink" | "other";
2
+ export interface SubmissionPackageEntry {
3
+ packagePath: string;
4
+ kind: SubmissionPackageEntryKind;
5
+ resolvedKind: Exclude<SubmissionPackageEntryKind, "symlink"> | null;
6
+ resolvedPackagePath?: string;
7
+ size: number;
8
+ safeResolution: "safe" | "outside" | "unavailable";
9
+ }
10
+ export interface SubmissionPackageReader {
11
+ list(directory: string): Promise<readonly SubmissionPackageEntry[]>;
12
+ stat(packagePath: string): Promise<SubmissionPackageEntry | null>;
13
+ read(packagePath: string, maxBytes: number): Promise<Uint8Array | null>;
14
+ }
15
+ export declare function createDirectorySubmissionPackageReader(rootPath: string): SubmissionPackageReader;
@@ -0,0 +1,274 @@
1
+ import { lstat, open, readdir, realpath, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ const invalidPackagePathMessage = "Invalid package path.";
4
+ const invalidMaxBytesMessage = "maxBytes must be a nonnegative safe integer.";
5
+ function packageEntryKind(stats) {
6
+ if (stats.isFile()) {
7
+ return "file";
8
+ }
9
+ if (stats.isDirectory()) {
10
+ return "directory";
11
+ }
12
+ if (stats.isSymbolicLink()) {
13
+ return "symlink";
14
+ }
15
+ return "other";
16
+ }
17
+ function resolvedEntryKind(stats) {
18
+ const kind = packageEntryKind(stats);
19
+ return kind === "symlink" ? "other" : kind;
20
+ }
21
+ function isWithinRoot(rootPath, candidatePath) {
22
+ const relativePath = path.relative(rootPath, candidatePath);
23
+ return relativePath === "" || (relativePath !== ".."
24
+ && !relativePath.startsWith(`..${path.sep}`)
25
+ && !path.isAbsolute(relativePath));
26
+ }
27
+ function normalizePackagePath(packagePath, options) {
28
+ if (typeof packagePath !== "string") {
29
+ throw new Error(invalidPackagePathMessage);
30
+ }
31
+ if (packagePath === "") {
32
+ if (options.allowRoot) {
33
+ return "";
34
+ }
35
+ throw new Error(invalidPackagePathMessage);
36
+ }
37
+ if (/[\u0000-\u001F\u007F]/u.test(packagePath)
38
+ || packagePath.startsWith("/")
39
+ || packagePath.startsWith("\\")
40
+ || /^[a-zA-Z]:/.test(packagePath)
41
+ || packagePath.includes("\\")) {
42
+ throw new Error(invalidPackagePathMessage);
43
+ }
44
+ const pathWithoutTrailingSlash = options.allowTrailingSlash && packagePath.endsWith("/")
45
+ ? packagePath.slice(0, -1)
46
+ : packagePath;
47
+ const segments = pathWithoutTrailingSlash.split("/");
48
+ if (pathWithoutTrailingSlash === ""
49
+ || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
50
+ throw new Error(invalidPackagePathMessage);
51
+ }
52
+ return segments.join("/");
53
+ }
54
+ function nativePathFor(rootPath, packagePath) {
55
+ const candidatePath = path.resolve(rootPath, ...packagePath.split("/"));
56
+ return isWithinRoot(rootPath, candidatePath) ? candidatePath : null;
57
+ }
58
+ function validMaxBytes(maxBytes) {
59
+ return Number.isSafeInteger(maxBytes) && maxBytes >= 0;
60
+ }
61
+ export function createDirectorySubmissionPackageReader(rootPath) {
62
+ const nativeRootPath = path.resolve(rootPath);
63
+ async function canonicalRootPath() {
64
+ try {
65
+ return await realpath(nativeRootPath);
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ async function ancestorsStayWithinRoot(packagePath, rootCanonicalPath) {
72
+ const segments = packagePath.split("/");
73
+ let ancestorPath = nativeRootPath;
74
+ for (const segment of segments.slice(0, -1)) {
75
+ ancestorPath = path.join(ancestorPath, segment);
76
+ try {
77
+ const canonicalAncestorPath = await realpath(ancestorPath);
78
+ if (!isWithinRoot(rootCanonicalPath, canonicalAncestorPath)) {
79
+ return "outside";
80
+ }
81
+ }
82
+ catch {
83
+ return "unavailable";
84
+ }
85
+ }
86
+ return "safe";
87
+ }
88
+ async function entryFor(packagePath) {
89
+ const candidatePath = nativePathFor(nativeRootPath, packagePath);
90
+ if (candidatePath === null) {
91
+ return null;
92
+ }
93
+ try {
94
+ const rootCanonicalPath = await canonicalRootPath();
95
+ if (rootCanonicalPath === null) {
96
+ return null;
97
+ }
98
+ const ancestorResolution = await ancestorsStayWithinRoot(packagePath, rootCanonicalPath);
99
+ if (ancestorResolution === "outside") {
100
+ return {
101
+ packagePath,
102
+ kind: "other",
103
+ resolvedKind: null,
104
+ size: 0,
105
+ safeResolution: "outside"
106
+ };
107
+ }
108
+ if (ancestorResolution === "unavailable") {
109
+ return null;
110
+ }
111
+ const entryStats = await lstat(candidatePath);
112
+ const kind = packageEntryKind(entryStats);
113
+ try {
114
+ const canonicalCandidatePath = await realpath(candidatePath);
115
+ const safeResolution = isWithinRoot(rootCanonicalPath, canonicalCandidatePath);
116
+ if (!safeResolution) {
117
+ return {
118
+ packagePath,
119
+ kind,
120
+ resolvedKind: null,
121
+ size: entryStats.size,
122
+ safeResolution: "outside"
123
+ };
124
+ }
125
+ const targetStats = await stat(candidatePath);
126
+ const resolvedPackagePath = path.relative(rootCanonicalPath, canonicalCandidatePath).split(path.sep).join("/");
127
+ return {
128
+ packagePath,
129
+ ...(resolvedPackagePath === packagePath ? {} : { resolvedPackagePath }),
130
+ kind,
131
+ resolvedKind: resolvedEntryKind(targetStats),
132
+ size: targetStats.size,
133
+ safeResolution: "safe"
134
+ };
135
+ }
136
+ catch {
137
+ return kind === "symlink"
138
+ ? {
139
+ packagePath,
140
+ kind,
141
+ resolvedKind: null,
142
+ size: entryStats.size,
143
+ safeResolution: "unavailable"
144
+ }
145
+ : null;
146
+ }
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ return {
153
+ async list(directory) {
154
+ const normalizedDirectory = normalizePackagePath(directory, {
155
+ allowRoot: true,
156
+ allowTrailingSlash: true
157
+ });
158
+ const directoryPath = normalizedDirectory === ""
159
+ ? nativeRootPath
160
+ : nativePathFor(nativeRootPath, normalizedDirectory);
161
+ if (directoryPath === null) {
162
+ return [];
163
+ }
164
+ try {
165
+ if (normalizedDirectory !== "") {
166
+ const directoryEntry = await entryFor(normalizedDirectory);
167
+ if (directoryEntry?.resolvedKind !== "directory" || directoryEntry.safeResolution !== "safe") {
168
+ return [];
169
+ }
170
+ }
171
+ else if (await canonicalRootPath() === null) {
172
+ return [];
173
+ }
174
+ const childNames = await readdir(directoryPath);
175
+ const entries = new Array(childNames.length).fill(null);
176
+ let nextChildIndex = 0;
177
+ async function readNextChild() {
178
+ while (nextChildIndex < childNames.length) {
179
+ const childIndex = nextChildIndex;
180
+ nextChildIndex += 1;
181
+ const childName = childNames[childIndex];
182
+ const childPackagePath = normalizedDirectory === ""
183
+ ? childName
184
+ : `${normalizedDirectory}/${childName}`;
185
+ try {
186
+ const normalizedChildPath = normalizePackagePath(childPackagePath, {
187
+ allowRoot: false,
188
+ allowTrailingSlash: false
189
+ });
190
+ entries[childIndex] = await entryFor(normalizedChildPath);
191
+ }
192
+ catch {
193
+ entries[childIndex] = null;
194
+ }
195
+ }
196
+ }
197
+ const workerCount = Math.min(16, childNames.length);
198
+ const workers = Array.from({ length: workerCount }, async () => readNextChild());
199
+ await Promise.all(workers);
200
+ return entries
201
+ .filter((entry) => entry !== null)
202
+ .sort((left, right) => left.packagePath.localeCompare(right.packagePath));
203
+ }
204
+ catch {
205
+ return [];
206
+ }
207
+ },
208
+ async stat(packagePath) {
209
+ return entryFor(normalizePackagePath(packagePath, {
210
+ allowRoot: false,
211
+ allowTrailingSlash: false
212
+ }));
213
+ },
214
+ async read(packagePath, maxBytes) {
215
+ const normalizedPackagePath = normalizePackagePath(packagePath, {
216
+ allowRoot: false,
217
+ allowTrailingSlash: false
218
+ });
219
+ if (!validMaxBytes(maxBytes)) {
220
+ throw new Error(invalidMaxBytesMessage);
221
+ }
222
+ const entry = await entryFor(normalizedPackagePath);
223
+ if (entry?.resolvedKind !== "file" || entry.safeResolution !== "safe" || entry.size > maxBytes) {
224
+ return null;
225
+ }
226
+ const candidatePath = nativePathFor(nativeRootPath, normalizedPackagePath);
227
+ if (candidatePath === null) {
228
+ return null;
229
+ }
230
+ let fileHandle = null;
231
+ try {
232
+ fileHandle = await open(candidatePath, "r");
233
+ const handleStats = await fileHandle.stat();
234
+ if (!handleStats.isFile()
235
+ || !Number.isSafeInteger(handleStats.size)
236
+ || handleStats.size < 0
237
+ || handleStats.size > maxBytes) {
238
+ return null;
239
+ }
240
+ const rootCanonicalPath = await canonicalRootPath();
241
+ const canonicalCandidatePath = await realpath(candidatePath);
242
+ const pathStats = await stat(candidatePath);
243
+ if (rootCanonicalPath === null
244
+ || !isWithinRoot(rootCanonicalPath, canonicalCandidatePath)
245
+ || !pathStats.isFile()
246
+ || handleStats.dev !== pathStats.dev
247
+ || handleStats.ino !== pathStats.ino) {
248
+ return null;
249
+ }
250
+ const content = Buffer.alloc(handleStats.size);
251
+ let offset = 0;
252
+ while (offset < content.length) {
253
+ const { bytesRead } = await fileHandle.read(content, offset, content.length - offset, offset);
254
+ if (bytesRead === 0) {
255
+ return null;
256
+ }
257
+ offset += bytesRead;
258
+ }
259
+ const probe = Buffer.alloc(1);
260
+ const { bytesRead: extraBytesRead } = await fileHandle.read(probe, 0, 1, offset);
261
+ if (extraBytesRead !== 0) {
262
+ return null;
263
+ }
264
+ return Uint8Array.from(content);
265
+ }
266
+ catch {
267
+ return null;
268
+ }
269
+ finally {
270
+ await fileHandle?.close().catch(() => undefined);
271
+ }
272
+ }
273
+ };
274
+ }
@@ -31,4 +31,6 @@ export interface SubmissionPreflightReport {
31
31
  findings: SubmissionFinding[];
32
32
  manualChecklist: SubmissionManualCheck[];
33
33
  }
34
+ export type SubmissionTargetType = SubmissionPreflightReport["targetType"];
35
+ export declare function validateSubmissionListing(manifest: Record<string, unknown>, targetType: SubmissionTargetType): SubmissionFinding[];
34
36
  export declare function buildSubmissionPreflight(targetPath: string): Promise<SubmissionPreflightReport>;
@@ -106,7 +106,7 @@ function isValidHttpsUrl(value) {
106
106
  return false;
107
107
  }
108
108
  }
109
- function validateListing(manifest, targetType) {
109
+ export function validateSubmissionListing(manifest, targetType) {
110
110
  const findings = [];
111
111
  validatePackage(manifest, findings);
112
112
  const listing = manifest.interface;
@@ -253,7 +253,7 @@ export async function buildSubmissionPreflight(targetPath) {
253
253
  const targetType = manifest.mcpServers !== undefined || manifest.apps !== undefined
254
254
  ? "mcp-backed"
255
255
  : "skills-only";
256
- const listingFindings = validateListing(manifest, targetType);
256
+ const listingFindings = validateSubmissionListing(manifest, targetType);
257
257
  const componentFindings = await validateApp(discovered.rootPath, manifest.apps);
258
258
  const assetFindings = (await validateSubmissionAssets(discovered)).findings;
259
259
  const skillFindings = (await validateSubmissionSkillMetadata(discovered, targetType)).findings;
@@ -1,9 +1,11 @@
1
- import type { DiscoveredPackage } from "../domain/types.js";
2
- import type { SubmissionFinding } from "./submission-preflight.js";
3
- type TargetType = "skills-only" | "mcp-backed";
1
+ import type { DiscoveredPackage, PluginManifest } from "../domain/types.js";
2
+ import { type SubmissionPackageReader } from "./submission-package-reader.js";
3
+ import type { SubmissionFinding, SubmissionPreflightReport } from "./submission-preflight.js";
4
+ type TargetType = SubmissionPreflightReport["targetType"];
4
5
  export interface SubmissionSkillMetadataResult {
5
6
  findings: SubmissionFinding[];
6
7
  skillCount: number;
7
8
  }
9
+ export declare function validateSubmissionSkillMetadataFromReader(manifest: PluginManifest, targetType: SubmissionPreflightReport["targetType"], reader: SubmissionPackageReader): Promise<SubmissionSkillMetadataResult>;
8
10
  export declare function validateSubmissionSkillMetadata(discoveredPackage: DiscoveredPackage, targetType: TargetType): Promise<SubmissionSkillMetadataResult>;
9
11
  export {};