@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.
Files changed (43) hide show
  1. package/README.md +39 -1
  2. package/dist/cli/commands/lint.d.ts +8 -0
  3. package/dist/cli/commands/lint.d.ts.map +1 -0
  4. package/dist/cli/commands/lint.js +24 -0
  5. package/dist/cli/commands/lint.js.map +1 -0
  6. package/dist/cli/commands/publish.d.ts +20 -0
  7. package/dist/cli/commands/publish.d.ts.map +1 -0
  8. package/dist/cli/commands/publish.js +365 -0
  9. package/dist/cli/commands/publish.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +36 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/lib/authorize.d.ts +80 -0
  15. package/dist/cli/lib/authorize.d.ts.map +1 -0
  16. package/dist/cli/lib/authorize.js +86 -0
  17. package/dist/cli/lib/authorize.js.map +1 -0
  18. package/dist/cli/lib/manifest.d.ts +30 -0
  19. package/dist/cli/lib/manifest.d.ts.map +1 -0
  20. package/dist/cli/lib/manifest.js +61 -0
  21. package/dist/cli/lib/manifest.js.map +1 -0
  22. package/dist/cli/lib/output.d.ts +66 -0
  23. package/dist/cli/lib/output.d.ts.map +1 -0
  24. package/dist/cli/lib/output.js +61 -0
  25. package/dist/cli/lib/output.js.map +1 -0
  26. package/dist/cli/lib/package-source.d.ts +63 -0
  27. package/dist/cli/lib/package-source.d.ts.map +1 -0
  28. package/dist/cli/lib/package-source.js +219 -0
  29. package/dist/cli/lib/package-source.js.map +1 -0
  30. package/dist/cli/lib/portal-client.d.ts +38 -0
  31. package/dist/cli/lib/portal-client.d.ts.map +1 -0
  32. package/dist/cli/lib/portal-client.js +87 -0
  33. package/dist/cli/lib/portal-client.js.map +1 -0
  34. package/dist/cli/lib/upload.d.ts +54 -0
  35. package/dist/cli/lib/upload.d.ts.map +1 -0
  36. package/dist/cli/lib/upload.js +75 -0
  37. package/dist/cli/lib/upload.js.map +1 -0
  38. package/dist/manifest-lint-cli.js +5 -16
  39. package/dist/manifest-lint-cli.js.map +1 -1
  40. package/package.json +3 -1
  41. package/skill/README.md +14 -0
  42. package/skill/SKILL.md +124 -0
  43. 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":"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"}
@@ -1,18 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { lintManifest } from './manifest-tools.js';
5
- const manifestPath = path.resolve(process.argv[2] || 'privos-app.json');
6
- let manifest;
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,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC;AACxE,IAAI,QAAiB,CAAC;AACtB,IAAI,CAAC;IACJ,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;AAC9D,CAAC;AAAC,MAAM,CAAC;IACR,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,kCAAkC,YAAY,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACrH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;AACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClE,IAAI,CAAC,MAAM,CAAC,KAAK;IAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC"}
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.8.4",
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
  ],
@@ -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`.