@privos_ai/app-server 0.8.4 → 0.10.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 +76 -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/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/manifest-lint-cli.js +5 -16
- package/dist/manifest-lint-cli.js.map +1 -1
- package/dist/relay/relay-client.d.ts.map +1 -1
- package/dist/relay/relay-client.js +25 -5
- package/dist/relay/relay-client.js.map +1 -1
- package/dist/runtime.d.ts +38 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +92 -5
- package/dist/runtime.js.map +1 -1
- package/dist/ui/asset-filename-rule.d.ts +18 -0
- package/dist/ui/asset-filename-rule.d.ts.map +1 -0
- package/dist/ui/asset-filename-rule.js +39 -0
- package/dist/ui/asset-filename-rule.js.map +1 -0
- package/dist/ui/assets-manifest.d.ts +19 -0
- package/dist/ui/assets-manifest.d.ts.map +1 -0
- package/dist/ui/assets-manifest.js +128 -0
- package/dist/ui/assets-manifest.js.map +1 -0
- package/dist/ui/serve-built-ui.d.ts +28 -0
- package/dist/ui/serve-built-ui.d.ts.map +1 -0
- package/dist/ui/serve-built-ui.js +131 -0
- package/dist/ui/serve-built-ui.js.map +1 -0
- package/dist/ui/shell-watchdog.d.ts +12 -0
- package/dist/ui/shell-watchdog.d.ts.map +1 -0
- package/dist/ui/shell-watchdog.js +25 -0
- package/dist/ui/shell-watchdog.js.map +1 -0
- 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,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-hashed asset filename rule shared by the SDK (build-time validation,
|
|
3
|
+
* `readAsset` lookups) and the Hub route that re-serves these files. A filename
|
|
4
|
+
* must start with an alnum, may contain `.`/`_`/`-` up to a trailing `-<hash>`
|
|
5
|
+
* of at least 8 chars, then one of the allowed extensions. No `/`, no `..`, no
|
|
6
|
+
* `.map` — content hashing itself is guaranteed by the assets manifest listing
|
|
7
|
+
* a file, not by this regex alone.
|
|
8
|
+
*/
|
|
9
|
+
export const MCP_UI_ASSET_FILENAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*-[A-Za-z0-9_-]{8,}\.(js|css|svg|json|woff|woff2|ttf|png|jpg|jpeg|gif|webp|avif|ico|wasm|gz)$/;
|
|
10
|
+
/** Extensions a split-build UI is ever allowed to publish under `assets/`. */
|
|
11
|
+
export const MCP_UI_ASSET_EXTENSIONS = [
|
|
12
|
+
'js',
|
|
13
|
+
'css',
|
|
14
|
+
'svg',
|
|
15
|
+
'json',
|
|
16
|
+
'woff',
|
|
17
|
+
'woff2',
|
|
18
|
+
'ttf',
|
|
19
|
+
'png',
|
|
20
|
+
'jpg',
|
|
21
|
+
'jpeg',
|
|
22
|
+
'gif',
|
|
23
|
+
'webp',
|
|
24
|
+
'avif',
|
|
25
|
+
'ico',
|
|
26
|
+
'wasm',
|
|
27
|
+
'gz',
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* `ui://<appSlug>/assets/` — the app-level (not per-entry-point) URI prefix
|
|
31
|
+
* every split asset is addressed under, per the wire contract.
|
|
32
|
+
*/
|
|
33
|
+
export function deriveAssetUriPrefix(appSlug) {
|
|
34
|
+
if (typeof appSlug !== 'string' || appSlug.trim().length === 0) {
|
|
35
|
+
throw new Error('deriveAssetUriPrefix requires a non-empty appSlug');
|
|
36
|
+
}
|
|
37
|
+
return `ui://${appSlug}/assets/`;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=asset-filename-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-filename-rule.js","sourceRoot":"","sources":["../../src/ui/asset-filename-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACpC,yHAAyH,CAAC;AAE3H,8EAA8E;AAC9E,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACtC,IAAI;IACJ,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,IAAI;CACK,CAAC;AAIX;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IACnD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,QAAQ,OAAO,UAAU,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface AssetManifestEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
size: number;
|
|
4
|
+
type: string;
|
|
5
|
+
}
|
|
6
|
+
export interface AssetsManifest {
|
|
7
|
+
files: AssetManifestEntry[];
|
|
8
|
+
}
|
|
9
|
+
export declare function mimeTypeForAssetFile(fileName: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Build `{ files: [{ name, size, type }] }` for `distDir/assets` — the union
|
|
12
|
+
* of Vite's `.vite/manifest.json` entries and a directory scan (lazy/CSS-only
|
|
13
|
+
* chunks and fonts never appear in the manifest). Every file actually present
|
|
14
|
+
* under `assets/` is validated: filename rule + extension allowlist, no
|
|
15
|
+
* `.map`, size ≤ 2 MB. Throws with the full list of offenders otherwise —
|
|
16
|
+
* this is boot-time validation, never a partial/best-effort manifest.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildAssetsManifest(distDir: string): AssetsManifest;
|
|
19
|
+
//# sourceMappingURL=assets-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-manifest.d.ts","sourceRoot":"","sources":["../../src/ui/assets-manifest.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC5B;AAwBD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG7D;AA4DD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA4CnE"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { MCP_UI_ASSET_FILENAME_RE } from './asset-filename-rule.js';
|
|
4
|
+
/** Matches the wire contract's per-asset cap; the SDK refuses larger at build time. */
|
|
5
|
+
const MAX_ASSET_BYTES = 2 * 1024 * 1024;
|
|
6
|
+
const MIME_BY_EXTENSION = {
|
|
7
|
+
js: 'text/javascript',
|
|
8
|
+
css: 'text/css',
|
|
9
|
+
svg: 'image/svg+xml',
|
|
10
|
+
json: 'application/json',
|
|
11
|
+
woff: 'font/woff',
|
|
12
|
+
woff2: 'font/woff2',
|
|
13
|
+
ttf: 'font/ttf',
|
|
14
|
+
png: 'image/png',
|
|
15
|
+
jpg: 'image/jpeg',
|
|
16
|
+
jpeg: 'image/jpeg',
|
|
17
|
+
gif: 'image/gif',
|
|
18
|
+
webp: 'image/webp',
|
|
19
|
+
avif: 'image/avif',
|
|
20
|
+
ico: 'image/x-icon',
|
|
21
|
+
wasm: 'application/wasm',
|
|
22
|
+
gz: 'application/gzip',
|
|
23
|
+
};
|
|
24
|
+
export function mimeTypeForAssetFile(fileName) {
|
|
25
|
+
const ext = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase();
|
|
26
|
+
return MIME_BY_EXTENSION[ext] ?? 'application/octet-stream';
|
|
27
|
+
}
|
|
28
|
+
/** Vite manifest paths are distDir-relative, e.g. `assets/index-H.js`. */
|
|
29
|
+
function assetFileNameFromManifestPath(relativePath) {
|
|
30
|
+
if (typeof relativePath !== 'string')
|
|
31
|
+
return undefined;
|
|
32
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
33
|
+
const prefix = 'assets/';
|
|
34
|
+
return normalized.startsWith(prefix) ? normalized.slice(prefix.length) : undefined;
|
|
35
|
+
}
|
|
36
|
+
function readViteManifestFileNames(distDir) {
|
|
37
|
+
const manifestPath = path.join(distDir, '.vite', 'manifest.json');
|
|
38
|
+
const names = new Set();
|
|
39
|
+
if (!fs.existsSync(manifestPath))
|
|
40
|
+
return names;
|
|
41
|
+
let raw;
|
|
42
|
+
try {
|
|
43
|
+
raw = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new Error(`serveBuiltUi: failed to parse Vite manifest at ${manifestPath}: ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
if (!raw || typeof raw !== 'object')
|
|
49
|
+
return names;
|
|
50
|
+
for (const chunk of Object.values(raw)) {
|
|
51
|
+
if (!chunk || typeof chunk !== 'object')
|
|
52
|
+
continue;
|
|
53
|
+
const file = assetFileNameFromManifestPath(chunk.file);
|
|
54
|
+
if (file)
|
|
55
|
+
names.add(file);
|
|
56
|
+
if (Array.isArray(chunk.css)) {
|
|
57
|
+
for (const entry of chunk.css) {
|
|
58
|
+
const name = assetFileNameFromManifestPath(entry);
|
|
59
|
+
if (name)
|
|
60
|
+
names.add(name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(chunk.assets)) {
|
|
64
|
+
for (const entry of chunk.assets) {
|
|
65
|
+
const name = assetFileNameFromManifestPath(entry);
|
|
66
|
+
if (name)
|
|
67
|
+
names.add(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return names;
|
|
72
|
+
}
|
|
73
|
+
function scanAssetsDirectory(assetsDir) {
|
|
74
|
+
const names = new Set();
|
|
75
|
+
if (!fs.existsSync(assetsDir))
|
|
76
|
+
return names;
|
|
77
|
+
for (const entry of fs.readdirSync(assetsDir)) {
|
|
78
|
+
if (fs.statSync(path.join(assetsDir, entry)).isFile())
|
|
79
|
+
names.add(entry);
|
|
80
|
+
}
|
|
81
|
+
return names;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build `{ files: [{ name, size, type }] }` for `distDir/assets` — the union
|
|
85
|
+
* of Vite's `.vite/manifest.json` entries and a directory scan (lazy/CSS-only
|
|
86
|
+
* chunks and fonts never appear in the manifest). Every file actually present
|
|
87
|
+
* under `assets/` is validated: filename rule + extension allowlist, no
|
|
88
|
+
* `.map`, size ≤ 2 MB. Throws with the full list of offenders otherwise —
|
|
89
|
+
* this is boot-time validation, never a partial/best-effort manifest.
|
|
90
|
+
*/
|
|
91
|
+
export function buildAssetsManifest(distDir) {
|
|
92
|
+
const assetsDir = path.join(distDir, 'assets');
|
|
93
|
+
const fileNames = new Set([
|
|
94
|
+
...readViteManifestFileNames(distDir),
|
|
95
|
+
...scanAssetsDirectory(assetsDir),
|
|
96
|
+
]);
|
|
97
|
+
const offenders = [];
|
|
98
|
+
const files = [];
|
|
99
|
+
for (const name of [...fileNames].sort()) {
|
|
100
|
+
const filePath = path.join(assetsDir, name);
|
|
101
|
+
if (name.endsWith('.map')) {
|
|
102
|
+
offenders.push(`${name}: sourcemaps must not be published under assets/`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!MCP_UI_ASSET_FILENAME_RE.test(name)) {
|
|
106
|
+
offenders.push(`${name}: does not match the content-hashed filename rule`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let stat;
|
|
110
|
+
try {
|
|
111
|
+
stat = fs.statSync(filePath);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
offenders.push(`${name}: referenced by the build but missing from ${assetsDir}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (stat.size > MAX_ASSET_BYTES) {
|
|
118
|
+
offenders.push(`${name}: ${stat.size} bytes exceeds the ${MAX_ASSET_BYTES} byte per-asset limit`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
files.push({ name, size: stat.size, type: mimeTypeForAssetFile(name) });
|
|
122
|
+
}
|
|
123
|
+
if (offenders.length > 0) {
|
|
124
|
+
throw new Error(`serveBuiltUi: invalid files under ${assetsDir}:\n${offenders.map((o) => ` - ${o}`).join('\n')}`);
|
|
125
|
+
}
|
|
126
|
+
return { files };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=assets-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets-manifest.js","sourceRoot":"","sources":["../../src/ui/assets-manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { UiAssetContent } from '../runtime.js';
|
|
2
|
+
import { type AssetsManifest } from './assets-manifest.js';
|
|
3
|
+
export interface ServeBuiltUiOptions {
|
|
4
|
+
/** Absolute path to the Vite build output directory containing `index.html`, `assets/`, `.vite/manifest.json`. */
|
|
5
|
+
distDir: string;
|
|
6
|
+
/** The app's manifest id — used to derive `ui://<appSlug>/assets/…` URIs. */
|
|
7
|
+
appSlug: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ServeBuiltUi {
|
|
10
|
+
/** Cached at construction: meta + watchdog + relative asset tags. */
|
|
11
|
+
renderHtml(): string;
|
|
12
|
+
/** `null` for an unrecognized/unlisted/traversal-unsafe URI. Cached after first successful read. */
|
|
13
|
+
readAsset(uri: string): UiAssetContent | null;
|
|
14
|
+
/** `{ files: [{ name, size, type }] }`, cached at construction. */
|
|
15
|
+
readAssetsManifest(): AssetsManifest;
|
|
16
|
+
/** `ui://<appSlug>/assets/` */
|
|
17
|
+
assetUriPrefix: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Serve a Vite-built (`base: './'`) app UI over MCP `resources/read`: the
|
|
21
|
+
* shell HTML (opt-in relay meta + inline boot watchdog) plus the hashed
|
|
22
|
+
* `assets/` files it references, split out of the HTML payload. Boot-time
|
|
23
|
+
* validation (asset filenames, extensions, size, no sourcemaps) and the shell's
|
|
24
|
+
* relative-asset-path assertion both throw at construction — a misconfigured
|
|
25
|
+
* build must fail loudly here, never serve a blank frame in production.
|
|
26
|
+
*/
|
|
27
|
+
export declare function serveBuiltUi(options: ServeBuiltUiOptions): ServeBuiltUi;
|
|
28
|
+
//# sourceMappingURL=serve-built-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-built-ui.d.ts","sourceRoot":"","sources":["../../src/ui/serve-built-ui.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAUhF,MAAM,WAAW,mBAAmB;IACnC,kHAAkH;IAClH,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,qEAAqE;IACrE,UAAU,IAAI,MAAM,CAAC;IACrB,oGAAoG;IACpG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IAC9C,mEAAmE;IACnE,kBAAkB,IAAI,cAAc,CAAC;IACrC,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAgDvE"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { deriveAssetUriPrefix } from './asset-filename-rule.js';
|
|
4
|
+
import { buildAssetsManifest } from './assets-manifest.js';
|
|
5
|
+
import { MCP_UI_SHELL_WATCHDOG_SCRIPT } from './shell-watchdog.js';
|
|
6
|
+
const TEXT_ASSET_EXTENSIONS = new Set(['js', 'css', 'svg', 'json']);
|
|
7
|
+
const PRIVOS_UI_ASSETS_META = '<meta name="privos-ui-assets" content="relay">';
|
|
8
|
+
const PRIVOS_UI_ASSETS_META_RE = /<meta\b[^>]*\bname\s*=\s*["']privos-ui-assets["'][^>]*\bcontent\s*=\s*["']relay["']|<meta\b[^>]*\bcontent\s*=\s*["']relay["'][^>]*\bname\s*=\s*["']privos-ui-assets["']/i;
|
|
9
|
+
const ASSET_TAG_RE = /<(script|link)\b[^>]*>/gi;
|
|
10
|
+
const SRC_OR_HREF_RE = /\b(?:src|href)\s*=\s*(["'])(.*?)\1/i;
|
|
11
|
+
/**
|
|
12
|
+
* Serve a Vite-built (`base: './'`) app UI over MCP `resources/read`: the
|
|
13
|
+
* shell HTML (opt-in relay meta + inline boot watchdog) plus the hashed
|
|
14
|
+
* `assets/` files it references, split out of the HTML payload. Boot-time
|
|
15
|
+
* validation (asset filenames, extensions, size, no sourcemaps) and the shell's
|
|
16
|
+
* relative-asset-path assertion both throw at construction — a misconfigured
|
|
17
|
+
* build must fail loudly here, never serve a blank frame in production.
|
|
18
|
+
*/
|
|
19
|
+
export function serveBuiltUi(options) {
|
|
20
|
+
const { distDir, appSlug } = options;
|
|
21
|
+
const assetUriPrefix = deriveAssetUriPrefix(appSlug);
|
|
22
|
+
const assetsDir = path.join(distDir, 'assets');
|
|
23
|
+
// Boot-time validation of every file under assets/ — throws with the full
|
|
24
|
+
// offender list on filename-rule / extension / size / sourcemap violations.
|
|
25
|
+
const manifest = buildAssetsManifest(distDir);
|
|
26
|
+
const manifestFiles = new Map(manifest.files.map((file) => [file.name, file]));
|
|
27
|
+
const assetsDirRealpath = fs.existsSync(assetsDir) ? fs.realpathSync(assetsDir) : undefined;
|
|
28
|
+
const html = renderShellHtml(distDir);
|
|
29
|
+
const assetCache = new Map();
|
|
30
|
+
return {
|
|
31
|
+
renderHtml() {
|
|
32
|
+
return html;
|
|
33
|
+
},
|
|
34
|
+
readAsset(uri) {
|
|
35
|
+
const cached = assetCache.get(uri);
|
|
36
|
+
if (cached)
|
|
37
|
+
return cached;
|
|
38
|
+
if (!uri.startsWith(assetUriPrefix))
|
|
39
|
+
return null;
|
|
40
|
+
const fileName = uri.slice(assetUriPrefix.length);
|
|
41
|
+
const entry = manifestFiles.get(fileName);
|
|
42
|
+
if (!entry || !assetsDirRealpath)
|
|
43
|
+
return null;
|
|
44
|
+
const filePath = path.join(assetsDir, fileName);
|
|
45
|
+
let realFilePath;
|
|
46
|
+
try {
|
|
47
|
+
realFilePath = fs.realpathSync(filePath);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!isContainedIn(assetsDirRealpath, realFilePath))
|
|
53
|
+
return null;
|
|
54
|
+
const ext = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase();
|
|
55
|
+
const resource = TEXT_ASSET_EXTENSIONS.has(ext)
|
|
56
|
+
? { uri, mimeType: entry.type, text: fs.readFileSync(realFilePath, 'utf8') }
|
|
57
|
+
: { uri, mimeType: entry.type, blob: fs.readFileSync(realFilePath).toString('base64') };
|
|
58
|
+
assetCache.set(uri, resource);
|
|
59
|
+
return resource;
|
|
60
|
+
},
|
|
61
|
+
readAssetsManifest() {
|
|
62
|
+
return manifest;
|
|
63
|
+
},
|
|
64
|
+
assetUriPrefix,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function isContainedIn(dirRealpath, fileRealpath) {
|
|
68
|
+
const relative = path.relative(dirRealpath, fileRealpath);
|
|
69
|
+
return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
70
|
+
}
|
|
71
|
+
function renderShellHtml(distDir) {
|
|
72
|
+
const indexPath = path.join(distDir, 'index.html');
|
|
73
|
+
let raw;
|
|
74
|
+
try {
|
|
75
|
+
raw = fs.readFileSync(indexPath, 'utf8');
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
throw new Error(`serveBuiltUi: cannot read ${indexPath}: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
assertRelativeAssetTags(raw, indexPath);
|
|
81
|
+
let html = raw;
|
|
82
|
+
if (!PRIVOS_UI_ASSETS_META_RE.test(html)) {
|
|
83
|
+
html = injectIntoHead(html, ` ${PRIVOS_UI_ASSETS_META}\n`, { atStart: false });
|
|
84
|
+
}
|
|
85
|
+
html = injectIntoHead(html, ` <script>${MCP_UI_SHELL_WATCHDOG_SCRIPT}</script>\n`, { atStart: true });
|
|
86
|
+
return html;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Every `<script src>` / `<link href>` in the shell must be relative
|
|
90
|
+
* (`./assets/…` or `assets/…`). An absolute or external reference means the
|
|
91
|
+
* app was built without Vite `base: './'` and would resolve against the
|
|
92
|
+
* wrong origin once the tab renders it — fail construction, not the frame.
|
|
93
|
+
*/
|
|
94
|
+
function assertRelativeAssetTags(html, indexPath) {
|
|
95
|
+
const offenders = [];
|
|
96
|
+
ASSET_TAG_RE.lastIndex = 0;
|
|
97
|
+
let tagMatch;
|
|
98
|
+
while ((tagMatch = ASSET_TAG_RE.exec(html))) {
|
|
99
|
+
const tag = tagMatch[0];
|
|
100
|
+
const refMatch = SRC_OR_HREF_RE.exec(tag);
|
|
101
|
+
if (!refMatch)
|
|
102
|
+
continue;
|
|
103
|
+
const ref = refMatch[2];
|
|
104
|
+
if (ref && (ref.startsWith('./assets/') || ref.startsWith('assets/')))
|
|
105
|
+
continue;
|
|
106
|
+
offenders.push(tag.length > 120 ? `${tag.slice(0, 117)}...` : tag);
|
|
107
|
+
}
|
|
108
|
+
if (offenders.length > 0) {
|
|
109
|
+
throw new Error(`serveBuiltUi: ${indexPath} has non-relative asset references — build with Vite base: './':\n${offenders
|
|
110
|
+
.map((o) => ` - ${o}`)
|
|
111
|
+
.join('\n')}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function injectIntoHead(html, snippet, opts) {
|
|
115
|
+
if (opts.atStart) {
|
|
116
|
+
const headOpen = /<head[^>]*>/i.exec(html);
|
|
117
|
+
if (headOpen) {
|
|
118
|
+
const insertAt = headOpen.index + headOpen[0].length;
|
|
119
|
+
return `${html.slice(0, insertAt)}\n${snippet}${html.slice(insertAt)}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const headCloseIdx = html.search(/<\/head>/i);
|
|
124
|
+
if (headCloseIdx !== -1) {
|
|
125
|
+
return html.slice(0, headCloseIdx) + snippet + html.slice(headCloseIdx);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// No <head> tag found in a well-formed Vite build — fall back to prepending.
|
|
129
|
+
return snippet + html;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=serve-built-ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve-built-ui.js","sourceRoot":"","sources":["../../src/ui/serve-built-ui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline boot watchdog injected at the top of `<head>`. No dependencies, runs
|
|
3
|
+
* before any other script/link tag so its capture-phase `error` listener is
|
|
4
|
+
* registered before those tags start loading. Two failure signals:
|
|
5
|
+
* - a script/link `error` event (asset 404/network failure), or
|
|
6
|
+
* - a 10 s timeout without `window.__privosUiBooted` being set by the app's
|
|
7
|
+
* own bootstrap once it renders.
|
|
8
|
+
* Either one replaces `#root` (or `document.body`) with a visible retry panel
|
|
9
|
+
* and notifies the host frame via `postMessage` so the Hub can log it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MCP_UI_SHELL_WATCHDOG_SCRIPT = "(function(){\nfunction fail(){\nif(window.__privosUiBooted)return;\nvar root=document.getElementById('root')||document.body;\nif(root){root.innerHTML='<div style=\"font:14px system-ui;padding:24px;text-align:center\">App assets unavailable \u2014 <button onclick=\"location.reload()\">Retry</button></div>';}\ntry{parent.postMessage({method:'ui/asset-load-failed'},'*');}catch(e){}\n}\nwindow.addEventListener('error',function(e){\nvar t=e&&e.target;\nif(!t||(t.tagName!=='SCRIPT'&&t.tagName!=='LINK'))return;\nfail();\n},true);\nsetTimeout(fail,10000);\n})();";
|
|
12
|
+
//# sourceMappingURL=shell-watchdog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-watchdog.d.ts","sourceRoot":"","sources":["../../src/ui/shell-watchdog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,qjBAanC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline boot watchdog injected at the top of `<head>`. No dependencies, runs
|
|
3
|
+
* before any other script/link tag so its capture-phase `error` listener is
|
|
4
|
+
* registered before those tags start loading. Two failure signals:
|
|
5
|
+
* - a script/link `error` event (asset 404/network failure), or
|
|
6
|
+
* - a 10 s timeout without `window.__privosUiBooted` being set by the app's
|
|
7
|
+
* own bootstrap once it renders.
|
|
8
|
+
* Either one replaces `#root` (or `document.body`) with a visible retry panel
|
|
9
|
+
* and notifies the host frame via `postMessage` so the Hub can log it.
|
|
10
|
+
*/
|
|
11
|
+
export const MCP_UI_SHELL_WATCHDOG_SCRIPT = `(function(){
|
|
12
|
+
function fail(){
|
|
13
|
+
if(window.__privosUiBooted)return;
|
|
14
|
+
var root=document.getElementById('root')||document.body;
|
|
15
|
+
if(root){root.innerHTML='<div style="font:14px system-ui;padding:24px;text-align:center">App assets unavailable — <button onclick="location.reload()">Retry</button></div>';}
|
|
16
|
+
try{parent.postMessage({method:'ui/asset-load-failed'},'*');}catch(e){}
|
|
17
|
+
}
|
|
18
|
+
window.addEventListener('error',function(e){
|
|
19
|
+
var t=e&&e.target;
|
|
20
|
+
if(!t||(t.tagName!=='SCRIPT'&&t.tagName!=='LINK'))return;
|
|
21
|
+
fail();
|
|
22
|
+
},true);
|
|
23
|
+
setTimeout(fail,10000);
|
|
24
|
+
})();`;
|
|
25
|
+
//# sourceMappingURL=shell-watchdog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-watchdog.js","sourceRoot":"","sources":["../../src/ui/shell-watchdog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG;;;;;;;;;;;;;MAatC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privos_ai/app-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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.
|