@privos_ai/app-server 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -1
- package/dist/cli/commands/lint.d.ts +8 -0
- package/dist/cli/commands/lint.d.ts.map +1 -0
- package/dist/cli/commands/lint.js +24 -0
- package/dist/cli/commands/lint.js.map +1 -0
- package/dist/cli/commands/publish.d.ts +20 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +365 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +36 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lib/authorize.d.ts +80 -0
- package/dist/cli/lib/authorize.d.ts.map +1 -0
- package/dist/cli/lib/authorize.js +86 -0
- package/dist/cli/lib/authorize.js.map +1 -0
- package/dist/cli/lib/manifest.d.ts +30 -0
- package/dist/cli/lib/manifest.d.ts.map +1 -0
- package/dist/cli/lib/manifest.js +61 -0
- package/dist/cli/lib/manifest.js.map +1 -0
- package/dist/cli/lib/output.d.ts +66 -0
- package/dist/cli/lib/output.d.ts.map +1 -0
- package/dist/cli/lib/output.js +61 -0
- package/dist/cli/lib/output.js.map +1 -0
- package/dist/cli/lib/package-source.d.ts +63 -0
- package/dist/cli/lib/package-source.d.ts.map +1 -0
- package/dist/cli/lib/package-source.js +219 -0
- package/dist/cli/lib/package-source.js.map +1 -0
- package/dist/cli/lib/portal-client.d.ts +38 -0
- package/dist/cli/lib/portal-client.d.ts.map +1 -0
- package/dist/cli/lib/portal-client.js +87 -0
- package/dist/cli/lib/portal-client.js.map +1 -0
- package/dist/cli/lib/upload.d.ts +54 -0
- package/dist/cli/lib/upload.d.ts.map +1 -0
- package/dist/cli/lib/upload.js +75 -0
- package/dist/cli/lib/upload.js.map +1 -0
- package/dist/manifest-lint-cli.js +5 -16
- package/dist/manifest-lint-cli.js.map +1 -1
- package/package.json +3 -1
- package/skill/README.md +14 -0
- package/skill/SKILL.md +124 -0
- package/skill/references/errors.md +68 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin fetch wrapper for the `/api/cloud/marketplace/*` publish routes: JSON
|
|
3
|
+
* request/response, retry-after-driven backoff on transient 429/5xx, and a
|
|
4
|
+
* typed {@link PortalError} carrying the Portal's `code` for callers that
|
|
5
|
+
* branch on specific wire-contract error codes (`VERSION_SEMVER_EXISTS`,
|
|
6
|
+
* `PUBLISH_GRANT_EXPIRED`, `PUBLISH_GRANT_MISMATCH`, `LISTING_NOT_BOUND`,
|
|
7
|
+
* `LISTING_UNRESOLVED`, RFC 8628 `slow_down`, …).
|
|
8
|
+
*/
|
|
9
|
+
export declare class PortalError extends Error {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly code?: string | undefined;
|
|
12
|
+
readonly retryAfterMs?: number | undefined;
|
|
13
|
+
constructor(message: string, status: number, code?: string | undefined, retryAfterMs?: number | undefined);
|
|
14
|
+
}
|
|
15
|
+
export type PortalRequestInit = Readonly<{
|
|
16
|
+
method?: string;
|
|
17
|
+
/** Bearer token: publisher token (`pvp_…`), publish grant (`pvg_…`), or none (anonymous create). */
|
|
18
|
+
token?: string;
|
|
19
|
+
body?: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
export type PortalClientOptions = Readonly<{
|
|
22
|
+
origin: string;
|
|
23
|
+
basePath?: string;
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
maxRetries?: number;
|
|
26
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
27
|
+
}>;
|
|
28
|
+
export declare function parseRetryAfterMs(header: string | null): number | undefined;
|
|
29
|
+
export declare class PortalClient {
|
|
30
|
+
private readonly origin;
|
|
31
|
+
private readonly basePath;
|
|
32
|
+
private readonly fetchImpl;
|
|
33
|
+
private readonly maxRetries;
|
|
34
|
+
private readonly sleepImpl;
|
|
35
|
+
constructor(options: PortalClientOptions);
|
|
36
|
+
request<T>(path: string, init?: PortalRequestInit): Promise<T>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=portal-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-client.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/portal-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,qBAAa,WAAY,SAAQ,KAAK;aAGpB,MAAM,EAAE,MAAM;aACd,IAAI,CAAC,EAAE,MAAM;aACb,YAAY,CAAC,EAAE,MAAM;gBAHrC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,YAAY,CAAC,EAAE,MAAM,YAAA;CAKtC;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC,CAAC;AASH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAM3E;AAED,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE9C,OAAO,EAAE,mBAAmB;IAQlC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,CAAC,CAAC;CAoCxE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin fetch wrapper for the `/api/cloud/marketplace/*` publish routes: JSON
|
|
3
|
+
* request/response, retry-after-driven backoff on transient 429/5xx, and a
|
|
4
|
+
* typed {@link PortalError} carrying the Portal's `code` for callers that
|
|
5
|
+
* branch on specific wire-contract error codes (`VERSION_SEMVER_EXISTS`,
|
|
6
|
+
* `PUBLISH_GRANT_EXPIRED`, `PUBLISH_GRANT_MISMATCH`, `LISTING_NOT_BOUND`,
|
|
7
|
+
* `LISTING_UNRESOLVED`, RFC 8628 `slow_down`, …).
|
|
8
|
+
*/
|
|
9
|
+
export class PortalError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
code;
|
|
12
|
+
retryAfterMs;
|
|
13
|
+
constructor(message, status, code, retryAfterMs) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.retryAfterMs = retryAfterMs;
|
|
18
|
+
this.name = 'PortalError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_BASE_PATH = '/api/cloud/marketplace';
|
|
22
|
+
const DEFAULT_MAX_RETRIES = 5;
|
|
23
|
+
function defaultSleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
export function parseRetryAfterMs(header) {
|
|
27
|
+
if (!header)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (/^\d+$/.test(header))
|
|
30
|
+
return Number(header) * 1000;
|
|
31
|
+
const parsed = Date.parse(header);
|
|
32
|
+
if (Number.isNaN(parsed))
|
|
33
|
+
return undefined;
|
|
34
|
+
return Math.max(0, parsed - Date.now());
|
|
35
|
+
}
|
|
36
|
+
export class PortalClient {
|
|
37
|
+
origin;
|
|
38
|
+
basePath;
|
|
39
|
+
fetchImpl;
|
|
40
|
+
maxRetries;
|
|
41
|
+
sleepImpl;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.origin = options.origin.replace(/\/+$/, '');
|
|
44
|
+
this.basePath = options.basePath ?? DEFAULT_BASE_PATH;
|
|
45
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
46
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
47
|
+
this.sleepImpl = options.sleepImpl ?? defaultSleep;
|
|
48
|
+
}
|
|
49
|
+
async request(path, init = {}) {
|
|
50
|
+
const method = init.method ?? 'GET';
|
|
51
|
+
const url = `${this.origin}${this.basePath}${path}`;
|
|
52
|
+
let attempt = 0;
|
|
53
|
+
for (;;) {
|
|
54
|
+
const response = await this.fetchImpl(url, {
|
|
55
|
+
method,
|
|
56
|
+
headers: {
|
|
57
|
+
...(init.token ? { Authorization: `Bearer ${init.token}` } : {}),
|
|
58
|
+
...(init.body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
59
|
+
},
|
|
60
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
61
|
+
});
|
|
62
|
+
const text = await response.text();
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = text ? JSON.parse(text) : {};
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
parsed = { raw: text.slice(0, 500) };
|
|
69
|
+
}
|
|
70
|
+
if (response.ok)
|
|
71
|
+
return parsed;
|
|
72
|
+
const record = parsed;
|
|
73
|
+
const code = typeof record?.code === 'string' ? record.code : undefined;
|
|
74
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get('retry-after'));
|
|
75
|
+
const transient = response.status === 429 || (response.status >= 500 && response.status < 600);
|
|
76
|
+
if (transient && attempt < this.maxRetries) {
|
|
77
|
+
const backoffMs = retryAfterMs ?? Math.min(30_000, 1_000 * 2 ** attempt);
|
|
78
|
+
await this.sleepImpl(backoffMs);
|
|
79
|
+
attempt += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const message = typeof record?.message === 'string' ? record.message : `${method} ${path} failed (${response.status})`;
|
|
83
|
+
throw new PortalError(message, response.status, code, retryAfterMs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=portal-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-client.js","sourceRoot":"","sources":["../../../src/cli/lib/portal-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PortalClient } from './portal-client.js';
|
|
2
|
+
/** Portal's `express.json()` body limit is 10 MB; base64 inflates bytes ~1.37x, so 5 MB parts stay well under it. */
|
|
3
|
+
export declare const DEFAULT_PART_SIZE_BYTES: number;
|
|
4
|
+
export type UploadSession = Readonly<{
|
|
5
|
+
uploadId: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function createUploadSession(client: PortalClient, token: string, listingId: string, request: Readonly<{
|
|
8
|
+
fileName: string;
|
|
9
|
+
totalBytes: number;
|
|
10
|
+
paths: readonly string[];
|
|
11
|
+
}>): Promise<UploadSession>;
|
|
12
|
+
/**
|
|
13
|
+
* Uploads `buffer` as base64 parts, one at a time (sequential — never
|
|
14
|
+
* concurrent), retrying transient 5xx/429 per-part via {@link PortalClient}'s
|
|
15
|
+
* own retry-after backoff.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uploadPartsSequentially(client: PortalClient, token: string, uploadId: string, buffer: Buffer, partSizeBytes?: number, onProgress?: (part: number, totalParts: number) => void): Promise<number>;
|
|
18
|
+
export type CompleteUploadResult = Readonly<{
|
|
19
|
+
sha256: string;
|
|
20
|
+
uploadId: string;
|
|
21
|
+
manifestDigest: string;
|
|
22
|
+
manifest: Record<string, unknown>;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function completeUpload(client: PortalClient, token: string, uploadId: string): Promise<CompleteUploadResult>;
|
|
25
|
+
export type CreatedVersion = Readonly<{
|
|
26
|
+
id: string;
|
|
27
|
+
semver: string;
|
|
28
|
+
state: string;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function createVersion(client: PortalClient, token: string, listingId: string, request: Readonly<{
|
|
31
|
+
semver: string;
|
|
32
|
+
uploadId: string;
|
|
33
|
+
changelog: string;
|
|
34
|
+
}>): Promise<CreatedVersion>;
|
|
35
|
+
export declare function submitVersion(client: PortalClient, token: string, listingId: string, versionId: string): Promise<void>;
|
|
36
|
+
export type ListingVersion = Readonly<{
|
|
37
|
+
id: string;
|
|
38
|
+
semver: string;
|
|
39
|
+
state: string;
|
|
40
|
+
}> & Record<string, unknown>;
|
|
41
|
+
export type ListingDetail = Readonly<{
|
|
42
|
+
listing: Record<string, unknown>;
|
|
43
|
+
versions: readonly ListingVersion[];
|
|
44
|
+
events?: readonly Record<string, unknown>[];
|
|
45
|
+
}>;
|
|
46
|
+
export declare function getListingDetail(client: PortalClient, token: string, listingId: string): Promise<ListingDetail>;
|
|
47
|
+
export type WaitForPreflightOptions = Readonly<{
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
pollMs?: number;
|
|
50
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
51
|
+
}>;
|
|
52
|
+
/** Polls listing detail up to `timeoutMs` (default 60 s) while the version sits in `PREFLIGHT_PENDING`. */
|
|
53
|
+
export declare function waitForPreflight(client: PortalClient, token: string, listingId: string, versionId: string, options?: WaitForPreflightOptions): Promise<ListingVersion | undefined>;
|
|
54
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Portal's `express.json()` body limit is 10 MB; base64 inflates bytes ~1.37x, so 5 MB parts stay well under it. */
|
|
2
|
+
export const DEFAULT_PART_SIZE_BYTES = 5 * 1024 * 1024;
|
|
3
|
+
export async function createUploadSession(client, token, listingId, request) {
|
|
4
|
+
const response = await client.request(`/creator/listings/${listingId}/uploads`, {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
token,
|
|
7
|
+
body: {
|
|
8
|
+
fileName: request.fileName,
|
|
9
|
+
totalBytes: request.totalBytes,
|
|
10
|
+
kind: 'APP_SOURCE',
|
|
11
|
+
paths: request.paths,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
const upload = response.upload;
|
|
15
|
+
const uploadId = upload?.id;
|
|
16
|
+
if (typeof uploadId !== 'string' || !uploadId)
|
|
17
|
+
throw new Error('Portal did not return an upload session id.');
|
|
18
|
+
return { uploadId };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Uploads `buffer` as base64 parts, one at a time (sequential — never
|
|
22
|
+
* concurrent), retrying transient 5xx/429 per-part via {@link PortalClient}'s
|
|
23
|
+
* own retry-after backoff.
|
|
24
|
+
*/
|
|
25
|
+
export async function uploadPartsSequentially(client, token, uploadId, buffer, partSizeBytes = DEFAULT_PART_SIZE_BYTES, onProgress) {
|
|
26
|
+
const totalParts = Math.max(1, Math.ceil(buffer.length / partSizeBytes));
|
|
27
|
+
for (let index = 0; index < totalParts; index += 1) {
|
|
28
|
+
const start = index * partSizeBytes;
|
|
29
|
+
const end = Math.min(buffer.length, start + partSizeBytes);
|
|
30
|
+
const chunk = buffer.subarray(start, end);
|
|
31
|
+
await client.request(`/creator/uploads/${uploadId}/parts/${index + 1}`, {
|
|
32
|
+
method: 'PUT',
|
|
33
|
+
token,
|
|
34
|
+
body: { dataBase64: chunk.toString('base64') },
|
|
35
|
+
});
|
|
36
|
+
onProgress?.(index + 1, totalParts);
|
|
37
|
+
}
|
|
38
|
+
return totalParts;
|
|
39
|
+
}
|
|
40
|
+
export async function completeUpload(client, token, uploadId) {
|
|
41
|
+
const response = await client.request(`/creator/uploads/${uploadId}/complete`, { method: 'POST', token, body: {} });
|
|
42
|
+
return {
|
|
43
|
+
sha256: String(response.sha256),
|
|
44
|
+
uploadId: String(response.uploadId ?? uploadId),
|
|
45
|
+
manifestDigest: String(response.manifestDigest),
|
|
46
|
+
manifest: response.manifest ?? {},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function createVersion(client, token, listingId, request) {
|
|
50
|
+
const response = await client.request(`/creator/listings/${listingId}/versions`, { method: 'POST', token, body: request });
|
|
51
|
+
const version = response.version;
|
|
52
|
+
return { id: String(version.id), semver: String(version.semver), state: String(version.state) };
|
|
53
|
+
}
|
|
54
|
+
export async function submitVersion(client, token, listingId, versionId) {
|
|
55
|
+
await client.request(`/creator/listings/${listingId}/submit`, { method: 'POST', token, body: { versionId } });
|
|
56
|
+
}
|
|
57
|
+
export async function getListingDetail(client, token, listingId) {
|
|
58
|
+
return client.request(`/creator/listings/${listingId}`, { method: 'GET', token });
|
|
59
|
+
}
|
|
60
|
+
/** Polls listing detail up to `timeoutMs` (default 60 s) while the version sits in `PREFLIGHT_PENDING`. */
|
|
61
|
+
export async function waitForPreflight(client, token, listingId, versionId, options = {}) {
|
|
62
|
+
const timeoutMs = options.timeoutMs ?? 60_000;
|
|
63
|
+
const pollMs = options.pollMs ?? 1_000;
|
|
64
|
+
const sleepImpl = options.sleepImpl ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
65
|
+
const deadline = Date.now() + timeoutMs;
|
|
66
|
+
let detail = await getListingDetail(client, token, listingId);
|
|
67
|
+
let version = detail.versions.find((item) => item.id === versionId);
|
|
68
|
+
while (version?.state === 'PREFLIGHT_PENDING' && Date.now() < deadline) {
|
|
69
|
+
await sleepImpl(pollMs);
|
|
70
|
+
detail = await getListingDetail(client, token, listingId);
|
|
71
|
+
version = detail.versions.find((item) => item.id === versionId);
|
|
72
|
+
}
|
|
73
|
+
return version;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/cli/lib/upload.ts"],"names":[],"mappings":"AAEA,qHAAqH;AACrH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAIvD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,MAAoB,EACpB,KAAa,EACb,SAAiB,EACjB,OAAqF;IAErF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAA0B,qBAAqB,SAAS,UAAU,EAAE;QACxG,MAAM,EAAE,MAAM;QACd,KAAK;QACL,IAAI,EAAE;YACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK;SACpB;KACD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,QAAQ,CAAC,MAA6C,CAAC;IACtE,MAAM,QAAQ,GAAG,MAAM,EAAE,EAAE,CAAC;IAC5B,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC9G,OAAO,EAAE,QAAQ,EAAE,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC5C,MAAoB,EACpB,KAAa,EACb,QAAgB,EAChB,MAAc,EACd,gBAAwB,uBAAuB,EAC/C,UAAuD;IAEvD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC;IACzE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,KAAK,GAAG,aAAa,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,aAAa,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC1C,MAAM,MAAM,CAAC,OAAO,CAAC,oBAAoB,QAAQ,UAAU,KAAK,GAAG,CAAC,EAAE,EAAE;YACvE,MAAM,EAAE,KAAK;YACb,KAAK;YACL,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;SAC9C,CAAC,CAAC;QACH,UAAU,EAAE,CAAC,KAAK,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,UAAU,CAAC;AACnB,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAoB,EAAE,KAAa,EAAE,QAAgB;IACzF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAA0B,oBAAoB,QAAQ,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7I,OAAO;QACN,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC;QAC/C,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC/C,QAAQ,EAAG,QAAQ,CAAC,QAAoC,IAAI,EAAE;KAC9D,CAAC;AACH,CAAC;AAID,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,MAAoB,EACpB,KAAa,EACb,SAAiB,EACjB,OAA0E;IAE1E,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAA0B,qBAAqB,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACpJ,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAkC,CAAC;IAC5D,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;AACjG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAoB,EAAE,KAAa,EAAE,SAAiB,EAAE,SAAiB;IAC5G,MAAM,MAAM,CAAC,OAAO,CAAC,qBAAqB,SAAS,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;AAC/G,CAAC;AAKD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAoB,EAAE,KAAa,EAAE,SAAiB;IAC5F,OAAO,MAAM,CAAC,OAAO,CAAgB,qBAAqB,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC;AAQD,2GAA2G;AAC3G,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,MAAoB,EACpB,KAAa,EACb,SAAiB,EACjB,SAAiB,EACjB,UAAmC,EAAE;IAErC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACjH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IAExC,IAAI,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;IACpE,OAAO,OAAO,EAAE,KAAK,KAAK,mBAAmB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QACxE,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC1D,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC"}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
console.error(JSON.stringify({ valid: false, errors: [`Unable to read valid JSON from ${manifestPath}`] }, null, 2));
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
const result = lintManifest(manifest);
|
|
15
|
-
console.log(JSON.stringify({ manifestPath, ...result }, null, 2));
|
|
16
|
-
if (!result.valid)
|
|
17
|
-
process.exit(1);
|
|
2
|
+
// Compatibility alias for `privos-app lint`: kept as its own bin entry
|
|
3
|
+
// (`privos-app-lint`) with byte-identical output and exit code, delegating
|
|
4
|
+
// to the same implementation so the two can never drift.
|
|
5
|
+
import { runLint } from './cli/commands/lint.js';
|
|
6
|
+
process.exit(runLint(process.argv.slice(2)));
|
|
18
7
|
//# sourceMappingURL=manifest-lint-cli.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-lint-cli.js","sourceRoot":"","sources":["../src/manifest-lint-cli.ts"],"names":[],"mappings":";AACA,
|
|
1
|
+
{"version":3,"file":"manifest-lint-cli.js","sourceRoot":"","sources":["../src/manifest-lint-cli.ts"],"names":[],"mappings":";AACA,uEAAuE;AACvE,2EAA2E;AAC3E,yDAAyD;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privos_ai/app-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Business-agnostic PrivOS MCP App server runtime — Direct HTTP + Relay WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
+
"privos-app": "./dist/cli/index.js",
|
|
9
10
|
"privos-app-lint": "./dist/manifest-lint-cli.js"
|
|
10
11
|
},
|
|
11
12
|
"exports": {
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
42
43
|
"dist",
|
|
44
|
+
"skill",
|
|
43
45
|
"README.md",
|
|
44
46
|
"LICENSE"
|
|
45
47
|
],
|
package/skill/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# `privos-app-publish` Claude Skill
|
|
2
|
+
|
|
3
|
+
This directory is the packaged Claude skill for publishing PrivOS MCP apps
|
|
4
|
+
to the Marketplace with the `privos-app` CLI (shipped in this package's
|
|
5
|
+
`bin`). It is not loaded automatically — `create-privos-mcp-app` copies it
|
|
6
|
+
into a generated app's `.claude/skills/privos-app-publish/` at scaffold time.
|
|
7
|
+
|
|
8
|
+
- `SKILL.md` — when to use, preconditions, exact commands, `--json` event
|
|
9
|
+
reading, approval-URL handoff, token-mode rules, exit codes.
|
|
10
|
+
- `references/errors.md` — full error-code table and remediation.
|
|
11
|
+
|
|
12
|
+
Keep this content in sync with the actual CLI flags/behavior in
|
|
13
|
+
`src/cli/commands/publish.ts` and `src/cli/lib/*.ts` — it is generated from
|
|
14
|
+
that source, not hand-invented.
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: privos-app-publish
|
|
3
|
+
description: Publish a PrivOS MCP app to the Marketplace using the `privos-app` CLI (package, lint, upload, version, submit) with either interactive browser approval or a CI publisher token.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
when_to_use: "Invoke when the user asks to publish, release, or ship a new version of an MCP app to the PrivOS Marketplace."
|
|
6
|
+
category: publishing
|
|
7
|
+
keywords: [privos-app, marketplace, publish, mcp-app, cli]
|
|
8
|
+
license: MIT
|
|
9
|
+
argument-hint: "[app directory]"
|
|
10
|
+
metadata:
|
|
11
|
+
author: privos
|
|
12
|
+
version: "1.0.0"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# PrivOS App Publish Skill
|
|
16
|
+
|
|
17
|
+
Runs `privos-app publish` correctly from an MCP app folder. The CLI (shipped
|
|
18
|
+
in `@privos_ai/app-server`, bin `privos-app`) is the only supported publish
|
|
19
|
+
path — it is a scripted `npm publish`-shaped flow: package → lint → authorize
|
|
20
|
+
→ upload → version → submit. Do not hand-drive Portal API calls or resurrect
|
|
21
|
+
the retired `.mjs` publish scripts.
|
|
22
|
+
|
|
23
|
+
## When to Use
|
|
24
|
+
|
|
25
|
+
- The user asks to publish, release, or ship a new version of an MCP app to
|
|
26
|
+
the PrivOS Marketplace.
|
|
27
|
+
- The user asks to check why a publish failed (see `references/errors.md`).
|
|
28
|
+
|
|
29
|
+
Not for: editing listing content, pricing, screenshots, or Stripe Connect
|
|
30
|
+
(all Creator Studio, web-only) — the CLI never touches those.
|
|
31
|
+
|
|
32
|
+
## Preconditions (verify before running)
|
|
33
|
+
|
|
34
|
+
1. **Clean git tree.** `privos-app publish` refuses a dirty working tree
|
|
35
|
+
unless `--allow-dirty` is passed. Prefer committing first; only use
|
|
36
|
+
`--allow-dirty` when the user explicitly wants an uncommitted snapshot
|
|
37
|
+
packaged.
|
|
38
|
+
2. **Version bumped in BOTH files.** `privos-app.json` `version` and
|
|
39
|
+
`package.json` `version` must be equal (the CLI fast-fails otherwise,
|
|
40
|
+
`MANIFEST_IDENTITY_MISMATCH`) and must be a semver that has never been
|
|
41
|
+
published for this listing before — re-publishing an existing semver fails
|
|
42
|
+
with `VERSION_SEMVER_EXISTS`. Always bump both fields together.
|
|
43
|
+
3. **Manifest passes structural lint.** Run `privos-app lint` (or let
|
|
44
|
+
`publish` run it — it lints as step 1) before attempting a full publish.
|
|
45
|
+
4. **Catalog check.** Confirm this is genuinely a new version of an existing
|
|
46
|
+
listing or a legitimately new app — do not re-run publish blindly after a
|
|
47
|
+
failure without reading the error.
|
|
48
|
+
|
|
49
|
+
## Exact Commands
|
|
50
|
+
|
|
51
|
+
Run from the app's root directory (where `privos-app.json` lives):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Preview only — packages the archive, prints its sha256, does not authorize or upload
|
|
55
|
+
npx privos-app publish --dry-run
|
|
56
|
+
|
|
57
|
+
# Interactive (default): prints an approval URL, waits for browser approval
|
|
58
|
+
npx privos-app publish
|
|
59
|
+
|
|
60
|
+
# Non-interactive / CI: requires PRIVOS_PUBLISHER_TOKEN in the environment
|
|
61
|
+
PRIVOS_PUBLISHER_TOKEN=pvp_xxx npx privos-app publish --yes
|
|
62
|
+
|
|
63
|
+
# Machine-readable event stream (one NDJSON object per line on stdout)
|
|
64
|
+
npx privos-app publish --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Useful flags: `--listing <slug>` (when the listing can't be resolved from the
|
|
68
|
+
manifest name), `--changelog <text>` / `--changelog-file <path>`,
|
|
69
|
+
`--allow-dirty`, `--portal <origin>` (default `https://portal.privos.io`),
|
|
70
|
+
`--machine-label <text>` (shown on the approval page, never the hostname by
|
|
71
|
+
default), `--cwd <path>`, `-h/--help`.
|
|
72
|
+
|
|
73
|
+
## Reading `--json` Output
|
|
74
|
+
|
|
75
|
+
Each line is one JSON object with an `event` field, emitted in this order:
|
|
76
|
+
`lint` → `package` → (`authorization_token` | `authorization_device` →
|
|
77
|
+
`authorization_approved`) → `upload_progress` (repeated) → `upload_complete`
|
|
78
|
+
→ `version_created` → `submitted` → `status`. A run that fails emits `error`
|
|
79
|
+
with `code` and `message` instead of continuing. Never print or log a `grant`
|
|
80
|
+
or token value beyond what the CLI itself already masks — the CLI does not
|
|
81
|
+
emit raw secrets in any event.
|
|
82
|
+
|
|
83
|
+
## Handing the Approval URL to the User
|
|
84
|
+
|
|
85
|
+
**The agent cannot click the approval link.** On `authorization_device`,
|
|
86
|
+
print the `verificationUrl` and `userCode` verbatim to the user and ask them
|
|
87
|
+
to open it in their own browser and log in to `client.privos.io` — do not
|
|
88
|
+
attempt to fetch, curl, or "visit" the URL yourself. The CLI polls
|
|
89
|
+
automatically after printing it; do not re-run `publish` while it is waiting.
|
|
90
|
+
If the user says they approved but the CLI is still waiting, tell them to
|
|
91
|
+
recheck they approved the correct listing/version shown on the page —
|
|
92
|
+
approval is not automatic on click, it depends on identity match.
|
|
93
|
+
|
|
94
|
+
## Token Mode (CI / Non-Interactive)
|
|
95
|
+
|
|
96
|
+
`PRIVOS_PUBLISHER_TOKEN` must come from the environment only — never ask the
|
|
97
|
+
user to paste a `pvp_…` token into chat, never write one into a command
|
|
98
|
+
argument, file, or log. If the user wants CI mode but has no token yet, tell
|
|
99
|
+
them to generate one from Creator Studio (a step-up action: email code or
|
|
100
|
+
magic link, plus TOTP if their account has 2FA) and export it in their own
|
|
101
|
+
shell/CI secret store before re-running. Token mode only works after the
|
|
102
|
+
listing's first version was approved once interactively (see
|
|
103
|
+
`LISTING_NOT_BOUND` below).
|
|
104
|
+
|
|
105
|
+
## Exit Codes
|
|
106
|
+
|
|
107
|
+
| Code | Meaning |
|
|
108
|
+
|---|---|
|
|
109
|
+
| 0 | Submitted (or reached a benign non-blocking status; see `references/errors.md`) |
|
|
110
|
+
| 2 | Blocked by policy (manifest invalid, dirty tree, denied archive path, semver reused, preflight failed) |
|
|
111
|
+
| 3 | Authorization denied, expired, or already consumed |
|
|
112
|
+
| 4 | Network or Portal error |
|
|
113
|
+
| 5 | Usage error (bad flags, cancelled prompt, not a git repository) |
|
|
114
|
+
|
|
115
|
+
## After Submit
|
|
116
|
+
|
|
117
|
+
The CLI has no `status` or `whoami` command. Once `submitted` prints, review
|
|
118
|
+
progress (`PREFLIGHT_PENDING` → `SCAN` → `AI_REVIEW` → human `APPROVE` →
|
|
119
|
+
build) is tracked only in Creator Studio on `client.privos.io`. Tell the user
|
|
120
|
+
to check there — do not invent a polling loop or guess at review state.
|
|
121
|
+
|
|
122
|
+
See `references/errors.md` for the full error-code table and remediation,
|
|
123
|
+
including `VERSION_SEMVER_EXISTS`, `PREFLIGHT_FAILED` vs
|
|
124
|
+
`PREFLIGHT_BLOCKED_INFRA` vs incomplete listing content.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# `privos-app publish` Error Reference
|
|
2
|
+
|
|
3
|
+
Every failure the CLI surfaces carries a `code` (visible in `--json` `error`
|
|
4
|
+
events and in the human-readable message) and maps to one of the exit codes
|
|
5
|
+
in `SKILL.md`. This table is generated from the CLI's actual error sites
|
|
6
|
+
(`src/cli/commands/publish.ts`, `src/cli/lib/*.ts`) — do not hand-edit
|
|
7
|
+
without checking the source stays in sync.
|
|
8
|
+
|
|
9
|
+
## Manifest / packaging (exit 2 unless noted)
|
|
10
|
+
|
|
11
|
+
| Code | Cause | Remediation |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `MANIFEST_INVALID` | `privos-app.json` fails structural lint | Run `privos-app lint` and fix the reported fields |
|
|
14
|
+
| `MANIFEST_IDENTITY_MISMATCH` | `name`/`version` differ between `privos-app.json` and `package.json` | Make both files agree exactly |
|
|
15
|
+
| `NOT_GIT_REPOSITORY` (exit 5) | `--cwd` is not inside a git repo, or has no commits | Publish from a committed git checkout |
|
|
16
|
+
| `DIRTY_TREE` | Uncommitted changes present, `--allow-dirty` not passed | Commit the intended files, or pass `--allow-dirty` deliberately |
|
|
17
|
+
| `CREDENTIAL_FILE_FOUND` | A `.env.*`, `.pem`, `.key`, `id_rsa*`, or `*credentials*` file is in the working tree | Remove or relocate the file outside the packaged tree |
|
|
18
|
+
| `GIT_ARCHIVE_FAILED` (exit 5) | `git read-tree`/`add`/`write-tree`/`archive` failed | Check the git error text printed alongside; usually a broken index or permissions issue |
|
|
19
|
+
| `INVALID_ZIP` | Produced archive has no valid ZIP central directory | Re-run; if it repeats, file a bug — not user-fixable |
|
|
20
|
+
| `MISSING_REQUIRED_ENTRY` | Archive is missing `privos-app.json` or `Dockerfile` at its root | Ensure both files are tracked (or present with `--allow-dirty`) at the repo root |
|
|
21
|
+
| `DENIED_PATH_IN_ARCHIVE` | Archive contains `node_modules`, `dist`, `dist-source`, `.git`, `.privos/skills`, `.env*`, `..` traversal, or credential-like paths | Remove those paths from the tree/`.gitattributes export-ignore` before packaging |
|
|
22
|
+
| `ENTRY_LIMIT_EXCEEDED` | Archive has more than 20,000 entries | Reduce tracked files or exclude generated assets |
|
|
23
|
+
| `FILE_SIZE_EXCEEDED` | A single file exceeds 50 MB | Remove or externalize the oversized file |
|
|
24
|
+
| `TOTAL_SIZE_EXCEEDED` | Archive exceeds 200 MB total | Trim the packaged tree |
|
|
25
|
+
| `CANCELLED` (exit 5) | User declined the interactive confirmation prompt | Re-run and confirm, or pass `--yes` |
|
|
26
|
+
|
|
27
|
+
## Authorization (exit 3 unless noted)
|
|
28
|
+
|
|
29
|
+
| Code | Cause | Remediation |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `LISTING_NOT_BOUND` (exit 2) | A publisher token was used but this listing has never had an interactively-approved first version | Run `privos-app publish` without a token once (browser approval) to bind the listing, then use the token for subsequent versions |
|
|
32
|
+
| `LISTING_UNRESOLVED` (exit 2) | The Portal could not resolve a listing from the manifest name | Pass `--listing <slug>` |
|
|
33
|
+
| `AUTHORIZATION_DENIED` | The approver declined the request on the approval page | Confirm with the user this was intentional; re-run to request a new approval if not |
|
|
34
|
+
| `AUTHORIZATION_EXPIRED` | Nobody approved within the authorization's TTL (15 min) | Re-run `publish`; approve promptly this time |
|
|
35
|
+
| `AUTHORIZATION_CONSUMED` | The device code was already used to mint a grant | Re-run `publish` to request a fresh authorization |
|
|
36
|
+
| `PUBLISHER_TOKEN_INVALID` / `PUBLISHER_TOKEN_REVOKED` / `PUBLISHER_TOKEN_EXPIRED` (exit 3) | `PRIVOS_PUBLISHER_TOKEN` is malformed, revoked, or past its expiry | Generate a new token in Creator Studio (step-up required) and export it in the environment |
|
|
37
|
+
|
|
38
|
+
## Upload / version / submit (exit 2 unless noted)
|
|
39
|
+
|
|
40
|
+
| Code | Cause | Remediation |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `PUBLISH_GRANT_EXPIRED` (exit 3) | The 60-minute grant expired mid-upload | Re-run `publish` to request a new approval; do not resume a stale grant |
|
|
43
|
+
| `PUBLISH_GRANT_MISMATCH` | Uploaded archive sha256 or manifest name/version does not match what was approved | Re-run `publish` from a clean state — do not edit the archive after approval |
|
|
44
|
+
| `VERSION_SEMVER_EXISTS` | This exact semver was already published for the listing | **Bump `version` in both `privos-app.json` and `package.json`**, then re-run |
|
|
45
|
+
|
|
46
|
+
## Post-submit states (exit 0 — informational, not failures)
|
|
47
|
+
|
|
48
|
+
`publish` waits up to 60 s after submit for the version to leave
|
|
49
|
+
`PREFLIGHT_PENDING`, then prints one of:
|
|
50
|
+
|
|
51
|
+
- **`PREFLIGHT_FAILED`** — the automated preflight scan rejected the archive
|
|
52
|
+
itself (manifest/runtime problems found in the uploaded content). The CLI
|
|
53
|
+
prints the findings verbatim (exit 2). Fix the flagged issue, bump the
|
|
54
|
+
version, and re-run `publish`.
|
|
55
|
+
- **`PREFLIGHT_BLOCKED_INFRA`** — preflight cannot run because of
|
|
56
|
+
Portal-side build infrastructure capacity/availability, not anything wrong
|
|
57
|
+
with the submission (exit 0). Tell the user this is admin-side; no local
|
|
58
|
+
action fixes it — wait and check Creator Studio later.
|
|
59
|
+
- **listing content incomplete** — a *different* gate than preflight: if the
|
|
60
|
+
Creator Studio listing itself is missing required metadata (categories,
|
|
61
|
+
screenshots, legal URLs), the submission can be accepted by the CLI but
|
|
62
|
+
stall in human review because the listing page is incomplete. This is not
|
|
63
|
+
a CLI error code — the CLI only packages and submits the version; finishing
|
|
64
|
+
the listing's content is done in Creator Studio, not via `privos-app`.
|
|
65
|
+
- **`PREFLIGHT_PENDING`** (still pending after 60 s) or any other state —
|
|
66
|
+
printed as-is with "follow up in Creator Studio". The CLI has no `status`
|
|
67
|
+
command; all further review progress (`SCAN` → `AI_REVIEW` → human
|
|
68
|
+
`APPROVE` → build) is tracked only on `client.privos.io`.
|