@dp-bohrium/widget-cli 0.5.0 → 0.6.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.
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "formatVersion": 3,
3
3
  "packageName": "@dp-bohrium/widget-cli",
4
- "cli": "0.5.0",
4
+ "cli": "0.6.0",
5
5
  "profile": {
6
- "id": "widget-sdk-0.7.0",
7
- "sdk": "0.7.0"
6
+ "id": "widget-sdk-0.9.0",
7
+ "sdk": "0.9.0"
8
8
  },
9
9
  "protocol": {
10
10
  "schemaVersion": 1,
11
11
  "hostApiVersion": 1,
12
- "digest": "sha256:ee3023e337559bbf83c096365fe1eb49ce6809a880a5e5d65e2a2f6fd53a7ddb"
12
+ "digest": "sha256:a4d13070cc867aa5ddb838201ed47eee8871c32d48fd1485ce6c531b7ebad2b9"
13
13
  },
14
14
  "hostAcceptance": "unknown",
15
15
  "documentation": {
@@ -20,9 +20,9 @@
20
20
  "llms": "https://dev.dp.tech/bohrium-widget-sdk/llms.txt"
21
21
  },
22
22
  "digests": {
23
- "compiler": "sha256:6b6ac1585a3f2bc0f8b2717b2475584a502a9026045b840337547528b7a063b0",
24
- "validators": "sha256:8712fb004d9decf1eeae56c1e7057092a8abc0eeae1093c29546bb56e3261922",
25
- "schemas": "sha256:ee3023e337559bbf83c096365fe1eb49ce6809a880a5e5d65e2a2f6fd53a7ddb",
26
- "templates": "sha256:60f555788f33fb38d707525f571d13b6fa78875be7d2d87cb864db6926a52ccb"
23
+ "compiler": "sha256:a45a53ac079b9249e629ecdcfc78510aca276e8c3db786bd66daded8e75d08e3",
24
+ "validators": "sha256:27340218c481b2b3b3964b598533f07d4cec820de148ef7ad9d00adc2e8162f9",
25
+ "schemas": "sha256:a4d13070cc867aa5ddb838201ed47eee8871c32d48fd1485ce6c531b7ebad2b9",
26
+ "templates": "sha256:85be81d7ede0a1631c08a464ce7857ffcce71691bb8d75717d97513c6496a8e5"
27
27
  }
28
28
  }
package/dist/cli.mjs CHANGED
@@ -1,13 +1,123 @@
1
1
  #!/usr/bin/env node
2
- import { a as normalizeArchivePath, c as compileAuthorManifest, i as prepareWidgetPackage, n as scaffoldDshWidget, o as checkWidgetProject, s as resolveWidgetProjects, t as auditDshPlugin } from "./dsh-BkPMTfZ_.mjs";
2
+ import { a as normalizeArchivePath, c as loadContractValidators, i as prepareWidgetPackage, l as compileAuthorManifest, n as scaffoldDshWidget, o as checkWidgetProject, s as resolveWidgetProjects, t as auditDshPlugin } from "./dsh-BL6cMLrg.mjs";
3
3
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
4
- import { constants, existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
4
+ import { constants, existsSync, fstatSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
5
5
  import { chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
6
6
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { spawn, spawnSync } from "node:child_process";
9
9
  import { createServer } from "node:http";
10
10
  import { isDeepStrictEqual } from "node:util";
11
+ //#region ../../scripts/widget/widget-development-observations.mjs
12
+ const JOURNAL_CAPACITY = 32;
13
+ const ATTEMPT_CAPACITY = 32;
14
+ const ID_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/u;
15
+ const exact = (value, keys) => value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
16
+ /** 每个 SDK 开发会话独占;只有受保护的 Host control 能写入。 */
17
+ async function createDevelopmentObservations() {
18
+ const { validateWidgetDevelopmentDiagnostic, createWidgetRecoveryPlan, isWidgetRenderResult } = await loadContractValidators();
19
+ let cursor = 0;
20
+ let current = {
21
+ state: "unknown",
22
+ cursor
23
+ };
24
+ const attempts = /* @__PURE__ */ new Map();
25
+ const entries = [];
26
+ let previousFailure;
27
+ return {
28
+ snapshot(includeEntries = true) {
29
+ return structuredClone({
30
+ ...current,
31
+ cursor,
32
+ oldestCursor: entries[0]?.cursor ?? cursor,
33
+ ...includeEntries ? { entries } : {}
34
+ });
35
+ },
36
+ reset(generation) {
37
+ attempts.clear();
38
+ current = {
39
+ state: "unknown",
40
+ generation,
41
+ cursor
42
+ };
43
+ },
44
+ accept(input, generation) {
45
+ if (!exact(input, [
46
+ "generation",
47
+ "attemptId",
48
+ "instanceId",
49
+ "sequence",
50
+ "event"
51
+ ]) || input.generation !== generation || typeof input.attemptId !== "string" || !ID_PATTERN.test(input.attemptId) || !(typeof input.instanceId === "string" && /^[a-zA-Z0-9_.:%-]{1,1024}$/u.test(input.instanceId)) || !Number.isSafeInteger(input.sequence) || input.sequence < 1) throw new Error("invalid observation identity");
52
+ const event = input.event;
53
+ const connected = exact(event, ["type", "supported"]) && event.type === "connected" && typeof event.supported === "boolean";
54
+ const diagnostic = exact(event, ["type", "diagnostic"]) && event.type === "diagnostic" && validateWidgetDevelopmentDiagnostic(event.diagnostic).ok;
55
+ const result = exact(event, ["type", "result"]) && event.type === "render-result" && isWidgetRenderResult(event.result);
56
+ if (!connected && !diagnostic && !result) throw new Error("invalid observation contract");
57
+ const known = attempts.get(input.attemptId);
58
+ if (known && (known.instanceId !== input.instanceId || input.sequence <= known.sequence)) throw new Error("stale observation");
59
+ if (!connected && (current.attemptId !== input.attemptId || !known)) throw new Error("inactive observation attempt");
60
+ if (!connected && !known.supported && (result || event.diagnostic.source === "sdk")) throw new Error("development extension was not negotiated");
61
+ if (connected && (known || attempts.size >= ATTEMPT_CAPACITY)) throw new Error("reused or excessive observation attempt");
62
+ attempts.set(input.attemptId, {
63
+ instanceId: input.instanceId,
64
+ sequence: input.sequence,
65
+ supported: connected ? event.supported : known.supported
66
+ });
67
+ if (connected) current = {
68
+ state: event.supported ? "connected" : "unsupported",
69
+ generation,
70
+ attemptId: input.attemptId,
71
+ instanceId: input.instanceId
72
+ };
73
+ const failure = diagnostic ? event.diagnostic : result && event.result.outcome === "failed" ? event.result.diagnostic : void 0;
74
+ if (failure) {
75
+ const fingerprint = JSON.stringify([
76
+ failure.source,
77
+ failure.code,
78
+ failure.phase,
79
+ failure.operation,
80
+ failure.diagnostics
81
+ ]);
82
+ const repeated = previousFailure?.fingerprint === fingerprint && previousFailure.generation === generation;
83
+ const recovery = createWidgetRecoveryPlan(failure, {
84
+ executionState: "finished",
85
+ mayHaveExecuted: true,
86
+ progress: {
87
+ repeated,
88
+ hasNewEvidence: !repeated
89
+ }
90
+ });
91
+ previousFailure = {
92
+ fingerprint,
93
+ generation
94
+ };
95
+ current = {
96
+ ...current,
97
+ state: "failed",
98
+ diagnostic: failure,
99
+ recovery,
100
+ renderResult: result ? event.result : void 0
101
+ };
102
+ } else if (result && event.result.outcome === "handled") {
103
+ const { diagnostic: _diagnostic, recovery: _recovery, ...identity } = current;
104
+ current = {
105
+ ...identity,
106
+ state: "handled",
107
+ renderResult: event.result
108
+ };
109
+ }
110
+ const entry = {
111
+ cursor: ++cursor,
112
+ ...input
113
+ };
114
+ entries.push(entry);
115
+ if (entries.length > JOURNAL_CAPACITY) entries.shift();
116
+ return this.snapshot(false);
117
+ }
118
+ };
119
+ }
120
+ //#endregion
11
121
  //#region ../../scripts/widget/widget-dev.mjs
12
122
  const repoRoot = resolve(fileURLToPath(new URL("../..", import.meta.url)));
13
123
  const DEFAULT_PORT = 4300;
@@ -105,6 +215,7 @@ function usage$1() {
105
215
  --watch 监听 src、manifest.json 和 package.json,变化后重新 build
106
216
  --no-build 使用现有的 manifest.widgetConfig.entry,不执行 build
107
217
  --port <port> 固定 Profile 服务端口(默认 ${DEFAULT_PORT})
218
+ --listen-fd <fd> 接管宿主已监听的 IPv4 loopback TCP socket;仅与 --no-build 一起使用
108
219
  --help 显示帮助
109
220
 
110
221
  开发流程:
@@ -116,6 +227,7 @@ function usage$1() {
116
227
  }
117
228
  function parseArgs(argv) {
118
229
  const positional = [];
230
+ let portSpecified = false;
119
231
  const options = {
120
232
  open: false,
121
233
  watch: false,
@@ -160,10 +272,17 @@ function parseArgs(argv) {
160
272
  const value = Number(argv[++index]);
161
273
  if (!Number.isInteger(value) || value < 1024 || value > 65535) throw new Error("--port 必须是 1024 到 65535 之间的整数");
162
274
  options.port = value;
275
+ portSpecified = true;
276
+ } else if (argument === "--listen-fd") {
277
+ const value = argv[++index];
278
+ const fd = Number(value);
279
+ if (!/^\d+$/u.test(value) || fd < 3 || fd > 2 ** 31 - 1) throw new Error("--listen-fd 需要宿主传入的文件描述符(fd ≥ 3)");
280
+ options.listenFd = fd;
163
281
  } else if (argument.startsWith("-")) throw new Error(`未知选项:${argument}`);
164
282
  else positional.push(argument);
165
283
  }
166
284
  if (positional.length !== 1) throw new Error("需要一个 Widget 工程目录或 manifest.json");
285
+ if (options.listenFd !== void 0 && (portSpecified || options.build || options.watch)) throw new Error("--listen-fd 必须与 --no-build 一起使用,不能与 --port 或 --watch 同用");
167
286
  return {
168
287
  help: false,
169
288
  project: positional[0],
@@ -721,6 +840,28 @@ function runBuild(projectRoot, { capture = false } = {}) {
721
840
  });
722
841
  });
723
842
  }
843
+ async function listenForDevelopment(server, { port, listenFd }) {
844
+ if (listenFd !== void 0 && !fstatSync(listenFd).isSocket()) throw new Error("--listen-fd 需要宿主传入已监听的 127.0.0.1 TCP socket");
845
+ await new Promise((resolveListen, rejectListen) => {
846
+ server.once("error", rejectListen);
847
+ const onListen = () => {
848
+ server.off("error", rejectListen);
849
+ resolveListen();
850
+ };
851
+ if (listenFd === void 0) server.listen(port, "127.0.0.1", onListen);
852
+ else server.listen({
853
+ fd: listenFd,
854
+ exclusive: true
855
+ }, onListen);
856
+ });
857
+ const address = server.address();
858
+ if (address?.family !== "IPv4" || address.address !== "127.0.0.1" || !address.port) {
859
+ server.close();
860
+ server.closeAllConnections();
861
+ throw new Error("--listen-fd 需要宿主传入已监听的 127.0.0.1 TCP socket");
862
+ }
863
+ return `http://${address.address}:${address.port}`;
864
+ }
724
865
  async function startWidgetDevelopment(projectRoot, options) {
725
866
  const buildProject = options.buildProject ?? runBuild;
726
867
  const emit = options.emit ?? ((line, error = false) => error ? console.error(line) : console.log(line));
@@ -761,6 +902,8 @@ async function startWidgetDevelopment(projectRoot, options) {
761
902
  },
762
903
  lastBuildError: null
763
904
  };
905
+ const observations = await createDevelopmentObservations();
906
+ observations.reset(state.generation);
764
907
  const eventClients = /* @__PURE__ */ new Map();
765
908
  let eventId = 0;
766
909
  let session;
@@ -783,7 +926,7 @@ async function startWidgetDevelopment(projectRoot, options) {
783
926
  }
784
927
  });
785
928
  const developmentSnapshot = () => {
786
- const origin = `http://127.0.0.1:${server.address().port}`;
929
+ const { origin } = session.descriptor;
787
930
  const development = panelDevelopmentDescriptor(successfulGeneration, `${origin}${V1_PROFILE_PATH}`, `${origin}${V1_STATUS_PATH}`);
788
931
  return {
789
932
  protocolVersion: 1,
@@ -798,7 +941,8 @@ async function startWidgetDevelopment(projectRoot, options) {
798
941
  assets: successfulGeneration.assets,
799
942
  watch: state.watch,
800
943
  lastBuild: state.lastBuild,
801
- lastBuildError: state.lastBuildError
944
+ lastBuildError: state.lastBuildError,
945
+ runtime: observations.snapshot()
802
946
  };
803
947
  };
804
948
  const generationReadyData = () => {
@@ -813,6 +957,10 @@ async function startWidgetDevelopment(projectRoot, options) {
813
957
  };
814
958
  };
815
959
  const server = createServer((request, response) => {
960
+ if (!session) {
961
+ response.destroy();
962
+ return;
963
+ }
816
964
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
817
965
  if (request.method === "OPTIONS") {
818
966
  response.writeHead(405, secureHeaders("text/plain; charset=utf-8")).end();
@@ -877,8 +1025,30 @@ async function startWidgetDevelopment(projectRoot, options) {
877
1025
  request.on("end", () => {
878
1026
  try {
879
1027
  const control = JSON.parse(body || "{}");
880
- if (control.protocolVersion !== 1 || !["reload", "disconnect"].includes(control.action)) throw new Error("invalid control action");
881
- if (control.action === "disconnect") {
1028
+ if (control.protocolVersion !== 1 || ![
1029
+ "reload",
1030
+ "disconnect",
1031
+ "report-runtime",
1032
+ "refresh-built-output"
1033
+ ].includes(control.action)) throw new Error("invalid control action");
1034
+ if (control.action === "report-runtime") {
1035
+ json$1(response, 200, {
1036
+ ok: true,
1037
+ protocolVersion: 1,
1038
+ runtime: observations.accept(control.observation, state.generation)
1039
+ });
1040
+ return;
1041
+ }
1042
+ if (control.action === "refresh-built-output") {
1043
+ const candidate = createDevelopmentGeneration(projectRoot, successfulGeneration.number + 1, GENERATION_LIMITS, options.manifestOptions);
1044
+ successfulGeneration = candidate;
1045
+ retainedGenerations.set(candidate.guestCapability, candidate);
1046
+ while (retainedGenerations.size > RETAINED_GENERATIONS) retainedGenerations.delete(retainedGenerations.keys().next().value);
1047
+ state.generation = candidate.number;
1048
+ observations.reset(candidate.number);
1049
+ state.lastBuildError = null;
1050
+ sendEvent("generation-ready", generationReadyData());
1051
+ } else if (control.action === "disconnect") {
882
1052
  sendEvent("disconnected", { reason: "host-request" });
883
1053
  session.disconnect();
884
1054
  } else sendEvent("reload-requested", { generation: state.generation });
@@ -915,7 +1085,7 @@ async function startWidgetDevelopment(projectRoot, options) {
915
1085
  });
916
1086
  return;
917
1087
  }
918
- const origin = `http://127.0.0.1:${server.address().port}`;
1088
+ const { origin } = session.descriptor;
919
1089
  const launchUrl = `${origin}${guestGenerationRoot(successfulGeneration)}/launch.html`;
920
1090
  json$1(response, 200, createDevelopmentConfig({
921
1091
  manifest: successfulGeneration.manifest,
@@ -928,7 +1098,7 @@ async function startWidgetDevelopment(projectRoot, options) {
928
1098
  }
929
1099
  if (url.pathname === PROFILE_PATH) {
930
1100
  const runId = url.searchParams.get("runId") || "widget-dev-run";
931
- const origin = `http://127.0.0.1:${server.address().port}`;
1101
+ const { origin } = session.descriptor;
932
1102
  json$1(response, 200, createDevelopmentConfig({
933
1103
  manifest: successfulGeneration.manifest,
934
1104
  runId,
@@ -987,11 +1157,7 @@ async function startWidgetDevelopment(projectRoot, options) {
987
1157
  }
988
1158
  serveGenerationAsset(successfulGeneration, assetPath, response);
989
1159
  });
990
- await new Promise((resolveListen, rejectListen) => {
991
- server.once("error", rejectListen);
992
- server.listen(options.port, "127.0.0.1", resolveListen);
993
- });
994
- const origin = `http://127.0.0.1:${server.address().port}`;
1160
+ const origin = await listenForDevelopment(server, options);
995
1161
  session = createWidgetDevelopmentSession({ origin });
996
1162
  const profileUrl = `${origin}${PROFILE_PATH}`;
997
1163
  const descriptorUrl = `${origin}${DESCRIPTOR_PATH}`;
@@ -1054,6 +1220,7 @@ async function startWidgetDevelopment(projectRoot, options) {
1054
1220
  retainedGenerations.set(candidate.guestCapability, candidate);
1055
1221
  while (retainedGenerations.size > RETAINED_GENERATIONS) retainedGenerations.delete(retainedGenerations.keys().next().value);
1056
1222
  state.generation = candidate.number;
1223
+ observations.reset(candidate.number);
1057
1224
  state.lastBuild = {
1058
1225
  ok: true,
1059
1226
  digest: candidate.digest,
@@ -1269,13 +1436,14 @@ function commandTopics(context) {
1269
1436
  options: [
1270
1437
  "--template <模板> panel 或 file-preview(默认 panel)",
1271
1438
  "--ui <模式> vanilla 或 react(默认 vanilla)",
1272
- "--panel-id <id> 仅用于 panel;默认取规范化后的工程名",
1439
+ "--panel-id <id> 自定义面板标识,仅用于 panel;默认取规范化后的工程名",
1273
1440
  "--extensions <列表> 仅用于 file-preview;逗号分隔扩展名(默认 txt)",
1274
1441
  "--tool <名称> 按指定工具成功结果激活,不注册工具卡片",
1275
1442
  HOST_OPTION,
1276
1443
  JSON_OPTION
1277
1444
  ],
1278
1445
  examples: [
1446
+ "bohr-widget create my-widget --ui react --panel-id my-widget",
1279
1447
  "bohr-widget create workspace-browser --ui react --panel-id workspace-files",
1280
1448
  "bohr-widget create text-preview --template file-preview --extensions txt,csv --ui vanilla --json",
1281
1449
  "bohr-widget create search-panel --tool ScholarSearch --json"
@@ -1283,8 +1451,9 @@ function commandTopics(context) {
1283
1451
  notes: [
1284
1452
  `默认组合为 panel + vanilla;生成工程的构建环境要求 Node.js ${context.generatedProjectNode}。`,
1285
1453
  "不会自动安装依赖;创建后进入目录,运行 pnpm install 或 npm install。",
1286
- "workspace-files 会补齐 files.list、files.metadata、files.read 和目录引用样例;文件树逻辑与夹具需自行实现。",
1287
- "其他 Panel id 需要 Host 预先注册和绑定;更改 id 不会自动接管宿主界面。",
1454
+ "Panel ID 可自定义,用于工作台识别面板;打包后按目标工作台的安装流程加载。",
1455
+ "workspace-files 是可选的文件浏览面板预设,会补齐 files.list、files.metadata、files.read 和目录引用样例;文件树界面与逻辑需自行实现。",
1456
+ "生成 README.md 和 AGENTS.md;先读取项目指引与公开文档,再安装依赖和执行工程命令。",
1288
1457
  "将 widget-toolchain.json 与包管理器 lockfile 一起保存。"
1289
1458
  ],
1290
1459
  quickStart: true
@@ -1313,6 +1482,7 @@ function commandTopics(context) {
1313
1482
  "--open 连接或唤起 Widget 开发宿主(默认关闭)",
1314
1483
  "--no-build 跳过首次构建,使用现有入口(默认先构建)",
1315
1484
  "--port <端口> 本地服务端口,1024–65535(默认 4300)",
1485
+ "--listen-fd <fd> 接管宿主的 IPv4 回环监听;须与 --no-build 同用,排除 --port 和 --watch",
1316
1486
  "--scheme <scheme> 宿主 deep link scheme(默认 widget-host)",
1317
1487
  "--bridge <url> 指定 loopback HTTP(S) bridge;默认探测标准端口",
1318
1488
  HOST_OPTION,
@@ -1473,7 +1643,8 @@ function documentationLines(documentation, topic = {}) {
1473
1643
  ...topic.agentDocs ? [
1474
1644
  `文档站:${documentation.url}`,
1475
1645
  `Agent 索引:${documentation.llms}`,
1476
- `完整文档:${documentation.llmsFull}`
1646
+ `完整文档:${documentation.llmsFull}`,
1647
+ "工程内的作者指引见 node_modules/@dp-bohrium/widget-sdk/llms.txt。"
1477
1648
  ] : []
1478
1649
  ];
1479
1650
  }
@@ -1519,7 +1690,7 @@ SDK ${sdk}(协议与执行工具链均由 SDK 内置)
1519
1690
  CLI 要求 Node.js >=20;默认生成工程要求 Node.js ${generatedProjectNode}。
1520
1691
 
1521
1692
  典型开发流程:
1522
- pnpm dlx ${packageName} create my-widget --ui react --panel-id workspace-files
1693
+ pnpm dlx ${packageName} create my-widget --template panel --ui react
1523
1694
  cd my-widget
1524
1695
  pnpm install
1525
1696
  pnpm exec bohr-widget build .
@@ -1554,7 +1725,7 @@ function renderHelp(path = [], context) {
1554
1725
  /**
1555
1726
  * 作者清单(VS Code 风格 `contributes` 清单)的本地静态检查。
1556
1727
  *
1557
- * `@dp-bohrium/widget-contracts` 的 host schema 只在 `compileAuthorManifest` 之后生效,
1728
+ * SDK 的 host schema 只在 `compileAuthorManifest` 之后生效,
1558
1729
  * 而编译只保留宿主运行时需要的字段:作者的 `version`、`displayName` 和未知顶层键会被
1559
1730
  * 静默丢弃,于是"清单写错但 check 全绿"。这里只补下游永远看不到的作者侧约束,报错文案
1560
1731
  * 直接指向作者填的字段名。
@@ -1812,7 +1983,7 @@ async function validateProjectToolchain(projectRoot, toolchain, { requireInstall
1812
1983
  ]) for (const name of ["@dp-bohrium/widget-contracts", "@dp-bohrium/widget-cli"]) if (pkg[section]?.[name] !== void 0) throw failure("PROJECT_SDK_MISMATCH", "作者工程仍直接声明 contracts 或 CLI,需统一使用 SDK 入口", {
1813
1984
  projectRoot: root,
1814
1985
  package: name
1815
- }, ["作者工程统一依赖 SDK;独立 CLI 请在工具目录或通过 npx 使用,Contracts 独立包供 Host 等消费者使用。保留作者源码,在独立候选中移除重复依赖、重新安装和构建。"]);
1986
+ }, ["作者工程统一依赖 SDK;协议从 SDK 的 /contracts 与 /schemas 出口读取。保留作者源码,在独立候选中移除重复依赖、重新安装和构建。"]);
1816
1987
  const declared = {};
1817
1988
  const installed = {};
1818
1989
  for (const [key, name] of Object.entries(PACKAGES)) {
@@ -2413,11 +2584,35 @@ function projectPackage({ name, ui, sdkVersion }) {
2413
2584
  }
2414
2585
  };
2415
2586
  }
2416
- function projectReadme({ name, template, ui, sourceFile, hostFile }) {
2587
+ function projectAgentGuide({ sdkVersion, sourceFile, hostFile, documentation, agentSetupUrl }) {
2588
+ return `# 项目开发指引
2589
+
2590
+ 创建时 SDK:\`@dp-bohrium/widget-sdk@${sdkVersion}\`。
2591
+
2592
+ 以下路径均相对于本工程根目录:
2593
+
2594
+ - \`${sourceFile}\`:本工程的 Widget 界面与业务逻辑,通常在这里修改功能。
2595
+ - \`${hostFile}\`:本工程的启动文件,调用 SDK 的 \`runWidgetHost\` 启动 Widget。
2596
+ - \`manifest.json\`:本工程的 Widget 声明。
2597
+ - \`samples/default.json\`:本工程的样例输入。
2598
+
2599
+ 本工程的依赖版本与工具链记录在 \`package.json\`、\`widget-toolchain.json\` 和包管理器 lockfile 中。
2600
+
2601
+ 阅读入口:
2602
+
2603
+ - [README.md](./README.md):本工程的环境、安装、命令和验收说明。
2604
+ - [开发 Agent 引导](${agentSetupUrl}):通用的能力选型与开发阅读路径。
2605
+ - [文档索引](${documentation.llms}):按当前任务查找指南、API 和示例。
2606
+ `;
2607
+ }
2608
+ function projectReadme({ name, template, ui, sourceFile, hostFile, sdkVersion, documentation, agentSetupUrl }) {
2417
2609
  return `# ${name}
2418
2610
 
2419
2611
  Bohrium Widget ${template} 插件工程(${ui})。
2420
2612
 
2613
+ 创建时 SDK:\`@dp-bohrium/widget-sdk@${sdkVersion}\`。项目上下文与 Agent 阅读入口见 [AGENTS.md](./AGENTS.md);安装依赖前即可读取[开发 Agent 引导](${agentSetupUrl})、[文档索引](${documentation.llms})和[快速开始](${documentation.quickStart})。
2614
+ 文档独立更新;接口以本工程固定 SDK 和目标 Host 实际支持为准。
2615
+
2421
2616
  默认构建器要求 Node.js \`${GENERATED_PROJECT_NODE}\`,高于执行器自身的 Node.js 20 要求。
2422
2617
  工程已经精确依赖提供命令的 \`${PROJECT_EXECUTOR_PACKAGE}\`;在本目录安装后使用本地命令,无需全局安装或源码仓库。
2423
2618
 
@@ -2431,8 +2626,9 @@ pnpm run widget:pack --json
2431
2626
  使用 npm 时,安装后可执行 \`npm run build\`、\`npm run check\`、\`npm run widget:pack\`;需要构建 receipt 时执行 \`npx --no-install bohr-widget build .\`。
2432
2627
  Widget ZIP 使用 \`pnpm run widget:pack\` 或 \`npm run widget:pack\`;\`pnpm pack\` 是制作 npm \`.tgz\` 包的内置命令。
2433
2628
 
2434
- - 源码入口:${sourceFile},由 ${hostFile} 调用 SDK 的 runWidgetHost 启动。
2629
+ - Widget 界面与业务逻辑在 ${sourceFile} 中;本工程的 ${hostFile} 调用 SDK 的 runWidgetHost 启动 Widget。
2435
2630
  - 高度链、布局与滚动策略直接写在 ${sourceFile} 中,样式随 activate 安装并在释放时清理;JSON 展示元素单独处理换行。
2631
+ - 在 [SDK 本地索引](./node_modules/@dp-bohrium/widget-sdk/llms.txt) 查看「Widget 尺寸与滚动」。
2436
2632
  - manifest.json 是作者清单,打包时由 CLI 编译为宿主清单。
2437
2633
  - 提交 widget-toolchain.json 与依赖 lockfile;dist/widget.js 与生成的 ZIP 不提交。
2438
2634
  - check 只验证结构、声明和样例,不执行 JavaScript;pack 成功不代表首帧渲染、真实授权或业务验收成功。
@@ -2983,6 +3179,17 @@ async function commandCreate(directory, options) {
2983
3179
  await writeFile(join(root, "samples/default.json"), `${JSON.stringify(sample, null, 2)}\n`);
2984
3180
  const sourceFile = options.ui === "react" ? "src/widget.tsx" : "src/widget.ts";
2985
3181
  const hostFile = "src/widget-host.ts";
3182
+ const documentation = getDocumentation();
3183
+ const agentSetupUrl = new URL("agent-setup/prompt.md", documentation.url).href;
3184
+ const projectGuidance = {
3185
+ sdkVersion: sdkPackage.version,
3186
+ sourceFile,
3187
+ hostFile,
3188
+ documentation,
3189
+ agentSetupUrl
3190
+ };
3191
+ const readmePath = join(root, "README.md");
3192
+ const agentGuidePath = join(root, "AGENTS.md");
2986
3193
  const source = options.ui === "react" && template.reactSource ? template.reactSource({ name }) : typeof template.source === "function" ? template.source({
2987
3194
  ui: options.ui,
2988
3195
  name
@@ -2991,13 +3198,13 @@ async function commandCreate(directory, options) {
2991
3198
  await writeFile(join(root, hostFile), template.hostSource({ sourceFile }));
2992
3199
  await writeFile(join(root, "scripts/rename-iife.mjs"), `import { rename } from "node:fs/promises";\n\nawait rename("dist/widget.iife.js", "dist/widget.js");\n`);
2993
3200
  await writeFile(join(root, ".gitignore"), "node_modules/\ndist/\n.artifacts/\n.widget-build-receipt.json\n*.zip\n");
2994
- await writeFile(join(root, "README.md"), projectReadme({
3201
+ await writeFile(readmePath, projectReadme({
3202
+ ...projectGuidance,
2995
3203
  name,
2996
3204
  template: options.template,
2997
- ui: options.ui,
2998
- sourceFile,
2999
- hostFile
3205
+ ui: options.ui
3000
3206
  }));
3207
+ await writeFile(agentGuidePath, projectAgentGuide(projectGuidance));
3001
3208
  await writeFile(join(root, "tsconfig.json"), `${JSON.stringify({
3002
3209
  compilerOptions: {
3003
3210
  target: "ES2022",
@@ -3021,7 +3228,60 @@ async function commandCreate(directory, options) {
3021
3228
  const warnings = [];
3022
3229
  await writeProjectToolchain(root, toolchain);
3023
3230
  if (name !== directoryName) warnings.push(`目录名 "${directoryName}" 不是合法标识符,manifest 的 name 已取 "${name}";需要其他取值请直接编辑 manifest.json。`);
3024
- if (options.template === "panel" && panelId !== "workspace-files") warnings.push(`Panel id "${panelId}" 需要 Host 预先注册并绑定;工作区文件面板请使用 --panel-id workspace-files。`);
3231
+ const nextSteps = [
3232
+ {
3233
+ cwd: root,
3234
+ command: "pnpm",
3235
+ args: ["install"]
3236
+ },
3237
+ {
3238
+ cwd: root,
3239
+ command: "pnpm",
3240
+ args: [
3241
+ "exec",
3242
+ "bohr-widget",
3243
+ "integration",
3244
+ "--project",
3245
+ ".",
3246
+ "--json"
3247
+ ]
3248
+ },
3249
+ {
3250
+ cwd: root,
3251
+ command: "pnpm",
3252
+ args: [
3253
+ "exec",
3254
+ "bohr-widget",
3255
+ "build",
3256
+ ".",
3257
+ "--json"
3258
+ ]
3259
+ },
3260
+ {
3261
+ cwd: root,
3262
+ command: "pnpm",
3263
+ args: [
3264
+ "exec",
3265
+ "bohr-widget",
3266
+ "check",
3267
+ ".",
3268
+ "--json"
3269
+ ]
3270
+ },
3271
+ {
3272
+ cwd: root,
3273
+ command: "pnpm",
3274
+ args: [
3275
+ "exec",
3276
+ "bohr-widget",
3277
+ "pack",
3278
+ ".",
3279
+ "--out",
3280
+ "artifacts/my-widget.zip",
3281
+ "--json"
3282
+ ]
3283
+ }
3284
+ ];
3025
3285
  const result = {
3026
3286
  ok: true,
3027
3287
  command: "create",
@@ -3033,6 +3293,11 @@ async function commandCreate(directory, options) {
3033
3293
  ...panelId ? { panelId } : {},
3034
3294
  manifestPath: join(root, "manifest.json"),
3035
3295
  packagePath: join(root, "package.json"),
3296
+ readmePath,
3297
+ agentGuidePath,
3298
+ documentation,
3299
+ agentSetupUrl,
3300
+ nextSteps,
3036
3301
  profile: toolchain.profile,
3037
3302
  hostCompatibility,
3038
3303
  ...warnings.length === 0 ? {} : { warnings }
@@ -3040,8 +3305,14 @@ async function commandCreate(directory, options) {
3040
3305
  if (options.json) console.log(JSON.stringify(result, null, 2));
3041
3306
  else {
3042
3307
  console.log(`已创建 Widget 工程:${root}(manifest name: ${name})`);
3308
+ if (panelId) console.log(`Panel ID:${panelId}(可自定义)`);
3309
+ console.log(`创建时 SDK:@dp-bohrium/widget-sdk@${sdkPackage.version}`);
3310
+ console.log(`项目说明:${readmePath}\nAgent 指引:${agentGuidePath}`);
3311
+ console.log(`文档站:${documentation.url}\nAgent 阅读入口:${agentSetupUrl}`);
3312
+ console.log(`文档索引:${documentation.llms}`);
3043
3313
  for (const warning of warnings) console.warn(`提示:${warning}`);
3044
- console.log(`下一步:cd ${JSON.stringify(root)},执行 pnpm install,再执行 pnpm exec bohr-widget build . --json 和 pnpm exec bohr-widget check . --json。`);
3314
+ console.log(`先读取项目指引和公开文档,再进入工程目录:${root}`);
3315
+ console.log(`依次执行:\n${nextSteps.map((step) => [step.command, ...step.args].join(" ")).join("\n")}`);
3045
3316
  }
3046
3317
  return result;
3047
3318
  }