@promptframe/cli 0.1.64 → 0.1.67

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 CHANGED
@@ -1,10 +1,14 @@
1
1
  # @promptframe/cli
2
2
 
3
+ `package` 和目录形式的 `upload` 使用所选组件目录及其子目录中的 `.gitignore`,支持嵌套覆盖、`!` 和目录规则;不读取组件目录外的 Git 配置。`node_modules`、`dist`、`.git`、`.promptframe`、`.component-packages` 仍固定排除。将本地出图和测试产物放在明确忽略的目录中。
4
+
5
+ 必需文件、manifest 声明的源码/props schema 和 `public/` 资源不能被静默排除;规则命中时返回 `package.required_file.ignored`,应修正规则后再打包。保留文件仍接受原安全扫描,原始 lockfile 不入包,脱敏 lockfile evidence 保留。
6
+
3
7
  PromptFrame component authoring CLI.
4
8
 
5
9
  Use it to inspect the public component standard, check component folders, validate manifests, inspect the local preview envelope, package source archives, upload components, check build status, rebuild evidence indexes, and rerun layout/security probes.
6
10
 
7
- Current source baseline is `@promptframe/cli@0.1.64`, `@promptframe/contracts@0.1.32`, `@promptframe/component-kit@0.1.26`, and `create-promptframe-component@0.1.54`. The published CLI consumes the contracts AST-aware public security policy evaluator, reports `securityPolicyDigest` / `securityEvaluatorMode` in JSON output, rejects red-team browser capability vectors such as Image/Audio beacons, Worker/SharedWorker, `window.open`, `postMessage`, dynamic script/iframe creation, browser fingerprinting, storage/cookie access, and dangerous HTML, reports `publicResources` for component-level public assets, keeps native `<img>` / `<video>` source checks case-sensitive so Remotion `<Img>` / `<Video>` remain valid, validates preview props against statically declared `propsSchema` fields and requires readable public prop descriptions, enforces strict Slot-first layout/source diagnostics, supports endpoint discovery, project list/current diagnostics, self-service CI token create/list/revoke, single-component and workspace GitHub workflow setup with `promptframe-workflow-version: 2`, `setup-ci --upgrade` stale-template repair, dynamic workspace matrix discovery, `RUNNER_LABELS` and pnpm 10 workflow support, pnpm workspace root lockfile evidence without component symlinks, `promptframe-workspace.json` validation, workspace root `check .` / `upload .` auto-detection, explicit `workspace:*` shared-package diagnostics, workspace upload source metadata headers, packages sanitized lockfile evidence for platform admission, release notes forwarding to upload, stderr NDJSON upload progress for `--json`, human-readable token kind/expiry summaries for `login`, `whoami`, and authenticated `upload`, server version auto-bump notes, and supports `status --fail-on-build-failed` for CI build-status gating.
11
+ Current source baseline is `@promptframe/cli@0.1.67`, `@promptframe/contracts@0.1.35`, `@promptframe/component-kit@0.1.29`, and `create-promptframe-component@0.1.57`. The published CLI consumes the contracts AST-aware public security policy evaluator, reports `securityPolicyDigest` / `securityEvaluatorMode` in JSON output, rejects red-team browser capability vectors such as Image/Audio beacons, Worker/SharedWorker, `window.open`, `postMessage`, dynamic script/iframe creation, browser fingerprinting, storage/cookie access, and dangerous HTML, reports `publicResources` for component-level public assets, keeps native `<img>` / `<video>` source checks case-sensitive so Remotion `<Img>` / `<Video>` remain valid, validates preview props against statically declared `propsSchema` fields and requires readable public prop descriptions, enforces strict Slot-first layout/source diagnostics, supports endpoint discovery, project list/current diagnostics, self-service CI token create/list/revoke, single-component and workspace GitHub workflow setup with `promptframe-workflow-version: 2`, `setup-ci --upgrade` stale-template repair, dynamic workspace matrix discovery, `RUNNER_LABELS` and pnpm 10 workflow support, pnpm workspace root lockfile evidence without component symlinks, `promptframe-workspace.json` validation, workspace root `check .` / `upload .` auto-detection, explicit `workspace:*` shared-package diagnostics, workspace upload source metadata headers, packages sanitized lockfile evidence for platform admission, release notes forwarding to upload, stderr NDJSON upload progress for `--json`, human-readable token kind/expiry summaries for `login`, `whoami`, and authenticated `upload`, server version auto-bump notes, and supports `status --fail-on-build-failed` for CI build-status gating.
8
12
 
9
13
  ```bash
10
14
  npm install -D @promptframe/cli
@@ -202,3 +206,34 @@ promptframe dev stop .
202
206
  关闭开发面板不停止连接;切换账号或作品、关闭页面会断开。刷新页面后重新粘贴连接JSON。停止或替换仅操作本工作区的实际实例;无法确认实例退出时命令报错,不自动强制终止进程。
203
207
 
204
208
  上传组件后,用准确的正式 `componentRef` 替换作品节点引用并移除该节点的 `componentSource`。仅上传成功不会自动改变作品的选源。确认作品已使用正式引用且不再需要本地预览后,可执行 `promptframe dev stop .`。
209
+
210
+ ## 素材引用槽与本地私有预览
211
+
212
+ 在 schema 中用 `promptFrameResourceSchema` 声明素材参数,在组件中用 `promptFrameResource` 消费。参数保存 public 路径或 `asset-proxy://` 引用;组件不读取凭据,也不调用平台解析接口。
213
+
214
+ ```ts
215
+ import { z } from 'zod';
216
+ import { promptFrameResourceSchema } from '@promptframe/contracts';
217
+ export const propsSchema = z.object({
218
+ illustration: promptFrameResourceSchema({
219
+ kinds: ['image'], sources: ['public', 'asset'],
220
+ accept: ['image/png', 'image/jpeg'], maxFileBytes: 16 * 1024 * 1024,
221
+ }).describe('展示图片').default('/example.png'),
222
+ });
223
+ ```
224
+
225
+ 组件使用 `<Img src={promptFrameResource(props, props.illustration)} />`。本地、Studio 预览和正式渲染使用同一写法;解析由宿主完成。正式渲染缺失、错误类型或越权引用会报错,不自动替换作品内容。
226
+
227
+ 新模板已启用 Vite 适配器。自定义旧项目在 `vite.config.ts` 的 plugins 中加入 `promptFramePreviewResources()`(从 `@promptframe/cli/preview` 导入),再运行 `promptframe sync . --apply` 更新预览壳;已有 React 插件等设置继续保留。适配器仅在本机 loopback 开发服务中运行,不导入组件业务代码。
228
+
229
+ ```bash
230
+ promptframe resource set . --prop /illustration --file ./my-selected.png
231
+ promptframe resource status .
232
+ promptframe resource clear .
233
+ ```
234
+
235
+ 本人明确选择的 PNG、JPEG、WebP 文件最大 16MiB,并受槽声明的 MIME/大小限制。CLI 记录原文件身份和 SHA,不复制原件;每次读取重新核对。选择存入 ignored 的 `.promptframe/local-previews/resources.json`,不会改公开 `preview-props.json`,打包固定排除此目录。选择最多 16 项,24 小时后不再提供读取;`resource clear` 只移除受管选择记录,不删除原始文件。没有后台清理进程。
236
+
237
+ 平台素材使用同一选择命令的 `--asset-ref` 和原作品 `--session-id`,不得与 `--file` 同用。项目来自正常 `promptframe init` 写入的无凭据上下文。需要先沿正常 `promptframe login --endpoint <平台API> --with-assets` 授予 `asset.read`;旧组件上传凭据不会自动扩大权限。开发服务复用现有 CLI 凭据,逐次请求当前用户、项目与作品范围内的短时只读表示,核字节 SHA 后向浏览器提供本机地址。凭据、签名 URL 和私有文件路径不会发送给组件。断网、撤权、文件变化或选择过期时,预览明确提示不可用并标明正在展示包内示例;恢复条件后刷新。
238
+
239
+ 市场预览的素材选择读取访问者当前作品中的就绪素材,不改变组件包或上传新素材。正式使用时将合法素材引用冻结在 Spec,渲染入口沿原授权、素材存储和预下载合同准备本地表示;签名 URL 不保存到 Spec。
@@ -0,0 +1,13 @@
1
+ export declare class CliCredentialError extends Error {
2
+ readonly code: 'cli.auth.token_expired';
3
+ constructor(code: 'cli.auth.token_expired');
4
+ }
5
+ export declare function readCliConfiguration(path: string): Record<string, unknown>;
6
+ /** Shared read-only access to the existing credential; never logs or writes it. */
7
+ export declare function cliBearerToken(input: {
8
+ endpoint: string;
9
+ explicit?: string;
10
+ configuration: Record<string, unknown>;
11
+ now?: () => number;
12
+ }): string | undefined;
13
+ //# sourceMappingURL=configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.d.ts","sourceRoot":"","sources":["../src/configuration.ts"],"names":[],"mappings":"AAEA,qBAAa,kBAAmB,SAAQ,KAAK;IAC/B,QAAQ,CAAC,IAAI,EAAE,wBAAwB;gBAA9B,IAAI,EAAE,wBAAwB;CACpD;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAM1E;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACjG,GAAG,MAAM,GAAG,SAAS,CAWrB"}
@@ -0,0 +1,36 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ export class CliCredentialError extends Error {
3
+ code;
4
+ constructor(code) {
5
+ super(code);
6
+ this.code = code;
7
+ }
8
+ }
9
+ export function readCliConfiguration(path) {
10
+ if (!existsSync(path))
11
+ return {};
12
+ try {
13
+ const value = JSON.parse(readFileSync(path, 'utf8'));
14
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ /** Shared read-only access to the existing credential; never logs or writes it. */
21
+ export function cliBearerToken(input) {
22
+ if (input.explicit)
23
+ return input.explicit;
24
+ const credential = input.configuration.credential;
25
+ if (!credential || typeof credential !== 'object' || Array.isArray(credential))
26
+ return undefined;
27
+ const row = credential;
28
+ const normalize = (value) => value.replace(/\/+$/, '');
29
+ if (typeof row.endpoint !== 'string' || normalize(row.endpoint) !== normalize(input.endpoint))
30
+ return undefined;
31
+ if (typeof row.expiresAt === 'string' && Date.parse(row.expiresAt) <= (input.now ?? Date.now)()) {
32
+ throw new CliCredentialError('cli.auth.token_expired');
33
+ }
34
+ return typeof row.tokenSecret === 'string' ? row.tokenSecret : undefined;
35
+ }
36
+ //# sourceMappingURL=configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.js","sourceRoot":"","sources":["../src/configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACtB;IAArB,YAAqB,IAA8B;QAAI,KAAK,CAAC,IAAI,CAAC,CAAC;QAA9C,SAAI,GAAJ,IAAI,CAA0B;IAAiB,CAAC;CACtE;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9D,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7G,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,KAE9B;IACC,IAAI,KAAK,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC,QAAQ,CAAC;IAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC;IAClD,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAC;IACjG,MAAM,GAAG,GAAG,UAAqC,CAAC;IAClD,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/D,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IAChH,IAAI,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QAChG,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC"}
package/dist/index.js CHANGED
@@ -6,12 +6,15 @@ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync,
6
6
  import os from 'node:os';
7
7
  import { basename, dirname, extname, join, relative, resolve } from 'node:path';
8
8
  import { inflateRawSync } from 'node:zlib';
9
- import { COMPONENT_MANIFEST_SCHEMA_VERSION, COMPONENT_PUBLIC_RESOURCES_CONTRACT_VERSION, COMPONENT_REF_VERSION, COMPONENT_STANDARD_SOURCE_HASH, COMPONENT_STANDARD_VERSION, PROMPTFRAME_AUTHORING_STANDARD_RELEASE, PROMPTFRAME_PUBLIC_RESOURCE_LIMITS, PROMPTFRAME_PUBLIC_RESOURCE_POLICY, PROMPTFRAME_PUBLIC_SECURITY_POLICY, PROMPTFRAME_PUBLIC_SECURITY_POLICY_DIGEST, PROMPTFRAME_PUBLIC_STANDARD_POLICY, PROMPTFRAME_LAYOUT_POLICY_RULE_IDS, PROMPTFRAME_CONTRACTS_VERSION, PROMPTFRAME_PUBLIC_DEPENDENCY_POLICY, detectPromptFrameUnknownCustomStyleProps, evaluatePromptFrameLayoutPolicy, evaluatePromptFrameDependencyPolicy, authoringUploadTargetSchema, parseComponentManifest, } from '@promptframe/contracts';
9
+ import { cliBearerToken, readCliConfiguration } from './configuration.js';
10
+ import ignore from 'ignore';
11
+ import { COMPONENT_MANIFEST_SCHEMA_VERSION, COMPONENT_PUBLIC_RESOURCES_CONTRACT_VERSION, COMPONENT_REF_VERSION, COMPONENT_STANDARD_SOURCE_HASH, COMPONENT_STANDARD_VERSION, PROMPTFRAME_AUTHORING_STANDARD_RELEASE, PROMPTFRAME_PUBLIC_RESOURCE_LIMITS, PROMPTFRAME_PUBLIC_RESOURCE_POLICY, PROMPTFRAME_PUBLIC_SECURITY_POLICY, PROMPTFRAME_PUBLIC_SECURITY_POLICY_DIGEST, PROMPTFRAME_PUBLIC_STANDARD_POLICY, PROMPTFRAME_LAYOUT_POLICY_RULE_IDS, PROMPTFRAME_CONTRACTS_VERSION, PROMPTFRAME_PUBLIC_DEPENDENCY_POLICY, detectPromptFrameUnknownCustomStyleProps, evaluatePromptFrameLayoutPolicy, evaluatePromptFrameDependencyPolicy, collectPromptFrameResourceReferences, authoringUploadTargetSchema, parseComponentManifest, } from '@promptframe/contracts';
10
12
  import { evaluatePromptFrameSecurityPolicySource, } from '@promptframe/contracts/security-evaluator';
11
13
  import { evaluatePromptFrameSchemaSource, } from '@promptframe/contracts/schema-evaluator';
12
14
  import { applyPackageChanges, buildFreshnessDecision, computePackageChanges, computePackageFreshnessDiagnostics, computeScaffoldFreshnessDiagnostics, resolveLocalPreviewScript, } from './lifecycle.js';
13
15
  import { evaluateLocalReusability, reusabilityDiagnostics, } from './reusability.js';
14
16
  import { assertPreviewEnvelope, writeLocalPreviewReport, } from './preview-report.js';
17
+ import { capturePreviewResourceFile, clearPreviewResourceDraft, previewResourcePropPath, readPreviewResourceDraft, writePreviewResourceSelection } from './preview-resource-draft.js';
15
18
  const command = process.argv[2] ?? 'help';
16
19
  const args = process.argv.slice(3);
17
20
  const REQUIRED_COMPONENT_FILES = PROMPTFRAME_PUBLIC_STANDARD_POLICY.requiredFiles;
@@ -96,6 +99,9 @@ async function run(name, argv) {
96
99
  case 'preview':
97
100
  preview(argv);
98
101
  break;
102
+ case 'resource':
103
+ resourceCommand(argv);
104
+ break;
99
105
  case 'dev':
100
106
  await dev(argv);
101
107
  break;
@@ -383,7 +389,7 @@ function sync(argv) {
383
389
  const nextFiles = [
384
390
  {
385
391
  path: GENERATED_PREVIEW_ROOT_PATH,
386
- contents: renderPromptFrameThinPreviewRoot(),
392
+ contents: renderPromptFrameThinPreviewRoot(hasResourcePreviewAdapter(dir)),
387
393
  },
388
394
  {
389
395
  path: DEV_PUBLIC_RESOURCES_MODULE_PATH,
@@ -541,6 +547,59 @@ async function dev(argv) {
541
547
  return;
542
548
  await runDevServer(dir, devCommand);
543
549
  }
550
+ function resourceCommand(argv) {
551
+ const [action, directory] = positionalArgs(argv), root = resolve(directory ?? '.');
552
+ if (action === 'clear') {
553
+ clearPreviewResourceDraft(root);
554
+ printJson({ command: 'resource.clear', status: 'cleared', originalFilesDeleted: 0 });
555
+ return;
556
+ }
557
+ if (action === 'status') {
558
+ const draft = readPreviewResourceDraft(root);
559
+ printJson({ command: 'resource.status', selectedCount: draft?.selections.length ?? 0,
560
+ updatedAt: draft?.updatedAt, credentialsIncluded: false });
561
+ return;
562
+ }
563
+ if (action !== 'set')
564
+ fail('Use resource set|status|clear <component-directory>.', 'preview.resource.command_invalid', 2);
565
+ const prop = valueAfter(argv, '--prop'), file = valueAfter(argv, '--file'), reference = valueAfter(argv, '--asset-ref');
566
+ if (!prop || !!file === !!reference)
567
+ fail('Select --prop /field with exactly one --file or --asset-ref.', 'preview.resource.selection_invalid', 2);
568
+ const manifest = validateComponentDirectory(root), envelope = readPreviewProps(root);
569
+ const facts = evaluatePromptFrameSchemaSource(readFileSync(join(root, manifest.entry.propsSchemaPath), 'utf8'));
570
+ const props = structuredClone(envelope.props ?? {}), keys = previewResourcePropPath(prop);
571
+ let object = props;
572
+ for (const key of keys.slice(0, -1)) {
573
+ if (!object || typeof object !== 'object' || !Object.hasOwn(object, key))
574
+ fail('Resource field is missing.', 'preview.resource.prop_missing');
575
+ object = object[key];
576
+ }
577
+ if (!object || typeof object !== 'object')
578
+ fail('Resource field is missing.', 'preview.resource.prop_missing');
579
+ object[keys.at(-1)] = reference ?? '/local-preview.png';
580
+ const slot = collectPromptFrameResourceReferences({ type: 'object', properties: facts.properties }, props)
581
+ .find(row => JSON.stringify(row.path) === JSON.stringify(keys));
582
+ if (facts.status !== 'resolved' || !slot)
583
+ fail('Use promptFrameResourceSchema for this field.', 'preview.resource.slot_missing');
584
+ const selectedFile = file ? capturePreviewResourceFile(file) : undefined;
585
+ if (selectedFile && (!slot.slot.sources.includes('public') || !slot.slot.kinds.includes('image')
586
+ || slot.slot.maxFileBytes !== undefined && selectedFile.size > slot.slot.maxFileBytes
587
+ || slot.slot.accept?.length && !slot.slot.accept.some(type => type === '*/*' || type === selectedFile.contentType
588
+ || type.endsWith('/*') && selectedFile.contentType.startsWith(type.slice(0, -1)))))
589
+ fail('Selected file does not match the declared slot.', 'preview.resource.file_rejected');
590
+ const sessionId = valueAfter(argv, '--session-id');
591
+ if (reference && !sessionId)
592
+ fail('A platform asset needs its original --session-id.', 'preview.resource.scope_required', 2);
593
+ const ignored = join(root, '.gitignore'), oldIgnore = existsSync(ignored) ? readFileSync(ignored, 'utf8') : '';
594
+ if (!ignore().add(oldIgnore).ignores('.promptframe/local-previews/resources.json'))
595
+ writeFileSync(ignored, oldIgnore.replace(/\n?$/, '\n') + '.promptframe/\n');
596
+ writePreviewResourceSelection(root, { prop, ...(selectedFile ? { file: selectedFile } : { reference, sessionId }) });
597
+ const config = existsSync(join(root, 'vite.config.ts')) ? readFileSync(join(root, 'vite.config.ts'), 'utf8') : '';
598
+ printJson({ command: 'resource.set', status: 'selected', prop, source: selectedFile ? 'local_file' : 'platform_asset',
599
+ draftOnly: true, sourcePreviewPropsChanged: false, platformWrites: 0,
600
+ previewAdapter: config.includes('@promptframe/cli/preview') ? 'configured' : 'setup_required',
601
+ nextAction: 'Refresh the local preview. Existing custom Vite shells need promptFramePreviewResources().' });
602
+ }
544
603
  async function devWorkspace(root, argv) {
545
604
  const studioOrigin = valueAfter(argv, '--studio-origin')
546
605
  ?? fail('Workspace dev requires --studio-origin <exact-origin>.', 'dev.workspace.origin_required', 2);
@@ -1685,6 +1744,7 @@ function buildVersionNotesHeaders(versionNotes) {
1685
1744
  return versionNotes ? { 'x-promptframe-version-notes': versionNotes } : {};
1686
1745
  }
1687
1746
  const POSITIONAL_VALUE_FLAGS = new Set([
1747
+ '--prop', '--file', '--asset-ref',
1688
1748
  '--allowed-upload-target',
1689
1749
  '--auth-permissions',
1690
1750
  '--auth-roles',
@@ -1874,7 +1934,8 @@ async function loginWithDeviceCode(endpoint, argv) {
1874
1934
  contractVersion: CLI_AUTH_CONTRACT_VERSION,
1875
1935
  endpoint,
1876
1936
  clientName: `PromptFrame CLI on ${os.hostname()}`,
1877
- requestedScopes: DEFAULT_CLI_LOGIN_SCOPES,
1937
+ requestedScopes: [...new Set([...(valuesAfter(argv, '--scope').length ? valuesAfter(argv, '--scope') : DEFAULT_CLI_LOGIN_SCOPES),
1938
+ ...(hasFlag(argv, '--with-assets') ? ['asset.read'] : [])])],
1878
1939
  }),
1879
1940
  }, 'login.device_start.failed');
1880
1941
  const deviceCode = requiredPayloadString(startPayload, 'deviceCode', 'login.device_start.invalid');
@@ -2015,6 +2076,7 @@ async function initProjectContext(argv) {
2015
2076
  projectId,
2016
2077
  projectNamespace: valueAfter(argv, '--project-namespace')
2017
2078
  ?? stringValue(project.projectNamespace)
2079
+ ?? stringValue(project.namespace)
2018
2080
  ?? normalizeProjectNamespace(projectId),
2019
2081
  defaultUploadTarget: valueAfter(argv, '--target')
2020
2082
  ?? valueAfter(argv, '--upload-target')
@@ -3018,18 +3080,12 @@ function resolveBearerToken(endpoint, argv) {
3018
3080
  ?? process.env.PROMPTFRAME_CI_TOKEN
3019
3081
  ?? process.env.PROMPTFRAME_CLI_TOKEN
3020
3082
  ?? process.env.PROMPTFRAME_AUTH_TOKEN;
3021
- if (explicit)
3022
- return explicit;
3023
- const credential = asRecord(readConfig(argv).credential);
3024
- if (!credential)
3025
- return undefined;
3026
- if (normalizeEndpoint(stringValue(credential.endpoint) ?? '') !== normalizeEndpoint(endpoint))
3027
- return undefined;
3028
- const expiresAt = stringValue(credential.expiresAt);
3029
- if (expiresAt && Date.parse(expiresAt) <= Date.now()) {
3030
- fail('Stored PromptFrame CLI credential is expired. Run promptframe login again.', 'cli.auth.token_expired', 2);
3083
+ try {
3084
+ return cliBearerToken({ endpoint, explicit, configuration: readConfig(argv) });
3085
+ }
3086
+ catch {
3087
+ return fail('Stored PromptFrame CLI credential is expired. Run promptframe login again.', 'cli.auth.token_expired', 2);
3031
3088
  }
3032
- return stringValue(credential.tokenSecret);
3033
3089
  }
3034
3090
  function describeAuthForEndpoint(endpoint, argv) {
3035
3091
  const credential = asRecord(readConfig(argv).credential);
@@ -3168,15 +3224,7 @@ function fileCredentialWarning() {
3168
3224
  };
3169
3225
  }
3170
3226
  function readConfig(argv) {
3171
- const path = resolveConfigPath(argv);
3172
- if (!existsSync(path))
3173
- return {};
3174
- try {
3175
- return JSON.parse(readFileSync(path, 'utf8'));
3176
- }
3177
- catch {
3178
- return {};
3179
- }
3227
+ return readCliConfiguration(resolveConfigPath(argv));
3180
3228
  }
3181
3229
  function writeConfig(argv, config) {
3182
3230
  const path = resolveConfigPath(argv);
@@ -3315,6 +3363,15 @@ function validatePreviewProps(dir, manifest) {
3315
3363
  assertStaticSchemaResolved(schemaFacts, manifest.entry.propsSchemaPath);
3316
3364
  assertPreviewPropsMatchStaticSchema(manifest, preview, schemaFacts);
3317
3365
  assertStaticPropsHaveDescriptions(schemaFacts, manifest.entry.propsSchemaPath);
3366
+ let references;
3367
+ try {
3368
+ references = collectPromptFrameResourceReferences({ type: 'object', properties: schemaFacts.properties }, preview.props ?? {});
3369
+ }
3370
+ catch {
3371
+ fail('Public preview resource fields must use valid example paths.', 'component_standard.preview.resource_invalid');
3372
+ }
3373
+ if (references.some(row => row.source !== 'public'))
3374
+ fail('Private resource selections belong in resource set drafts; public preview props must use package examples.', 'component_standard.preview.private_resource_reference');
3318
3375
  }
3319
3376
  function assertPreviewDurationMatchesManifest(preview, manifest) {
3320
3377
  const durationFrames = Number(preview.durationFrames);
@@ -3729,8 +3786,8 @@ function classifyPreviewShell(source) {
3729
3786
  }
3730
3787
  return 'custom_or_unknown';
3731
3788
  }
3732
- function renderPromptFrameThinPreviewRoot() {
3733
- return `import { Player } from '@remotion/player';
3789
+ function renderPromptFrameThinPreviewRoot(localResources = false) {
3790
+ const source = `import { Player } from '@remotion/player';
3734
3791
  import {
3735
3792
  PromptFramePreviewApp,
3736
3793
  resolvePromptFramePreviewLocale,
@@ -3799,6 +3856,17 @@ createRoot(root).render(
3799
3856
  </StrictMode>,
3800
3857
  );
3801
3858
  `;
3859
+ if (!localResources)
3860
+ return source;
3861
+ return source
3862
+ .replace("import { propsSchema, type ComponentProps } from './schema';", "import { propsSchema, type ComponentProps } from './schema';\nimport { promptFrameLocalPreview } from 'virtual:promptframe-local-preview';")
3863
+ .replace('const initialPropsParse = propsSchema.safeParse(preview.props ?? {});', 'async function mountPreview() {\nconst localPreview = await promptFrameLocalPreview;\nconst initialPropsParse = propsSchema.safeParse(localPreview.props ?? preview.props ?? {});')
3864
+ .replace(' <StrictMode>\n <PromptFramePreviewApp', ' <StrictMode>\n {localPreview.issues.length > 0 && <p role="alert" style={{ padding: 12 }}>\n 本地预览素材不可用,当前显示包内示例。请核对所选文件、作品与 CLI 素材读取授权后刷新。\n </p>}\n <PromptFramePreviewApp')
3865
+ + '\n}\nvoid mountPreview();\n';
3866
+ }
3867
+ function hasResourcePreviewAdapter(dir) {
3868
+ const path = join(dir, 'vite.config.ts');
3869
+ return existsSync(path) && readFileSync(path, 'utf8').includes('@promptframe/cli/preview');
3802
3870
  }
3803
3871
  function assertPublicResourcesAccepted(report) {
3804
3872
  const firstError = report.diagnostics.find((item) => item.severity === 'error');
@@ -3962,13 +4030,43 @@ function decodeZipEntry(data, method) {
3962
4030
  }
3963
4031
  function collectPackageFiles(root) {
3964
4032
  const files = [];
3965
- function walk(dir) {
4033
+ const manifest = parseComponentManifest(normalizeLegacyManifest(JSON.parse(readFileSync(join(root, 'manifest.json'), 'utf8'))));
4034
+ const requiredPaths = new Set([
4035
+ ...REQUIRED_COMPONENT_FILES,
4036
+ manifest.entry.sourcePath,
4037
+ manifest.entry.propsSchemaPath,
4038
+ ]);
4039
+ function walk(dir, inherited) {
4040
+ const rules = [...inherited];
4041
+ const ignorePath = join(dir, '.gitignore');
4042
+ if (existsSync(ignorePath)) {
4043
+ if (!lstatSync(ignorePath).isFile()) {
4044
+ fail('.gitignore must be an ordinary file inside the component directory.', 'package.gitignore.invalid');
4045
+ }
4046
+ rules.push({ root: dir, matcher: ignore({ ignorecase: false }).add(readFileSync(ignorePath, 'utf8')) });
4047
+ }
3966
4048
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3967
4049
  if (['node_modules', 'dist', '.git', '.component-packages', '.promptframe'].includes(entry.name))
3968
4050
  continue;
3969
4051
  const full = join(dir, entry.name);
4052
+ const name = relative(root, full).replace(/\\/g, '/');
4053
+ let ignored = false;
4054
+ for (const rule of rules) {
4055
+ const result = rule.matcher.test(`${relative(rule.root, full).replace(/\\/g, '/')}${entry.isDirectory() ? '/' : ''}`);
4056
+ if (result.ignored)
4057
+ ignored = true;
4058
+ else if (result.unignored)
4059
+ ignored = false;
4060
+ }
4061
+ if (ignored) {
4062
+ const containsRequiredPath = [...requiredPaths].some((requiredPath) => requiredPath === name || (entry.isDirectory() && requiredPath.startsWith(`${name}/`)));
4063
+ if (containsRequiredPath || name === 'public' || name.startsWith('public/')) {
4064
+ fail(`.gitignore excludes required component content: ${name}. Remove that rule or explicitly include the required source/resource.`, 'package.required_file.ignored');
4065
+ }
4066
+ continue;
4067
+ }
3970
4068
  if (entry.isDirectory()) {
3971
- walk(full);
4069
+ walk(full, rules);
3972
4070
  }
3973
4071
  else if (entry.isFile()) {
3974
4072
  if (entry.name === LOCKFILE_EVIDENCE_FILE_NAME)
@@ -3976,13 +4074,13 @@ function collectPackageFiles(root) {
3976
4074
  if (isPackageManagerLockfile(entry.name))
3977
4075
  continue;
3978
4076
  files.push({
3979
- name: relative(root, full).replace(/\\/g, '/'),
4077
+ name,
3980
4078
  data: readFileSync(full),
3981
4079
  });
3982
4080
  }
3983
4081
  }
3984
4082
  }
3985
- walk(root);
4083
+ walk(root, []);
3986
4084
  const evidence = buildLockfileEvidenceFile(root);
3987
4085
  if (evidence)
3988
4086
  files.push(evidence);
@@ -4329,6 +4427,8 @@ Commands:
4329
4427
  sync <dir> Refresh PromptFrame-generated authoring shell files (--dry-run by default; use --apply)
4330
4428
  preview <dir> Validate and print local Remotion preview envelope
4331
4429
  --write-local-report Write .promptframe/local-previews/preview-report.json
4430
+ resource set <dir> Select a private local preview resource (--prop /field, --file path OR --asset-ref ref --session-id id)
4431
+ resource status|clear <dir> Inspect or clear private preview selections; original files are retained
4332
4432
  dev <dir> Start a local preview or workspace checked-module connection
4333
4433
  --studio-origin <origin> Required for a multi-component workspace
4334
4434
  --idle-timeout <minutes> Workspace idle exit (default 30; 0 disables)
@@ -4345,6 +4445,7 @@ Commands:
4345
4445
  probe <buildId> --level <level> Rerun component layout/security probe
4346
4446
  login --endpoint <url> Start browser login code flow and store a CLI token
4347
4447
  --token <token> Verify and store an already issued CLI/CI token
4448
+ --with-assets Explicitly request asset.read in the normal browser authorization
4348
4449
  whoami Show the current PromptFrame CLI identity
4349
4450
  logout Revoke the current CLI token and clear local config
4350
4451
  discovery Fetch platform endpoint and self-service capabilities