@tbox.cn/app-template-agent 0.1.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 (119) hide show
  1. package/.gitignore +13 -0
  2. package/.nvmrc +1 -0
  3. package/.tbox/app.json +4 -0
  4. package/AGENTS.md +101 -0
  5. package/ENV.md +77 -0
  6. package/LEGAL.md +7 -0
  7. package/LICENSE +21 -0
  8. package/README.md +62 -0
  9. package/apps/client/STYLE_GUIDE.md +81 -0
  10. package/apps/client/VISUAL_RESTORE.md +90 -0
  11. package/apps/client/assets/copy@2x.png +0 -0
  12. package/apps/client/assets/debug.png +0 -0
  13. package/apps/client/assets/dislike@2x.png +0 -0
  14. package/apps/client/assets/keyboard-circle.svg +12 -0
  15. package/apps/client/assets/like@2x.png +0 -0
  16. package/apps/client/assets/more-circle.svg +3 -0
  17. package/apps/client/assets/new-chat.svg +3 -0
  18. package/apps/client/assets/quick-command-star.svg +3 -0
  19. package/apps/client/assets/send-gradient.svg +14 -0
  20. package/apps/client/assets/speaker@2x.png +0 -0
  21. package/apps/client/assets/stop@2x.png +0 -0
  22. package/apps/client/assets/voice-circle.svg +3 -0
  23. package/apps/client/babel-plugins/code-inspect.cjs +110 -0
  24. package/apps/client/index.html +36 -0
  25. package/apps/client/package.json +34 -0
  26. package/apps/client/src/app.tsx +5 -0
  27. package/apps/client/src/business/App.tsx +11 -0
  28. package/apps/client/src/business/README.md +82 -0
  29. package/apps/client/src/cards/CardRenderer.tsx +10 -0
  30. package/apps/client/src/cards/registry.ts +22 -0
  31. package/apps/client/src/components/ActionBar/DebugButton.tsx +49 -0
  32. package/apps/client/src/components/ActionBar/index.less +120 -0
  33. package/apps/client/src/components/ActionBar/index.tsx +157 -0
  34. package/apps/client/src/components/ChatHeader/index.less +104 -0
  35. package/apps/client/src/components/ChatHeader/index.tsx +63 -0
  36. package/apps/client/src/components/ChatInput/index.less +332 -0
  37. package/apps/client/src/components/ChatInput/index.tsx +251 -0
  38. package/apps/client/src/components/ChatInput/voice/VoicePressField.tsx +34 -0
  39. package/apps/client/src/components/ChatInput/voice/VoiceRecordingOverlay.tsx +51 -0
  40. package/apps/client/src/components/ChatInput/voice/machine.ts +27 -0
  41. package/apps/client/src/components/ChatInput/voice/useVoicePress.ts +188 -0
  42. package/apps/client/src/components/ErrorBoundary.tsx +2 -0
  43. package/apps/client/src/components/MediaList/index.less +241 -0
  44. package/apps/client/src/components/MediaList/index.tsx +256 -0
  45. package/apps/client/src/components/MessageBubble/index.less +309 -0
  46. package/apps/client/src/components/MessageBubble/index.tsx +163 -0
  47. package/apps/client/src/components/PullDownIndicator/index.less +32 -0
  48. package/apps/client/src/components/PullDownIndicator/index.tsx +33 -0
  49. package/apps/client/src/components/QuickCommands/index.less +48 -0
  50. package/apps/client/src/components/QuickCommands/index.tsx +22 -0
  51. package/apps/client/src/components/ReasoningBlock/index.less +90 -0
  52. package/apps/client/src/components/ReasoningBlock/index.tsx +56 -0
  53. package/apps/client/src/components/ScrollToBottomButton/index.less +32 -0
  54. package/apps/client/src/components/ScrollToBottomButton/index.tsx +10 -0
  55. package/apps/client/src/components/ToolCallBlock/index.less +91 -0
  56. package/apps/client/src/components/ToolCallBlock/index.tsx +63 -0
  57. package/apps/client/src/examples/tourism/TourismLanding.tsx +96 -0
  58. package/apps/client/src/examples/tourism/index.less +192 -0
  59. package/apps/client/src/hooks/useMediaUpload.ts +237 -0
  60. package/apps/client/src/hooks/usePullDownHistory.ts +97 -0
  61. package/apps/client/src/hooks/useTTSAutoPlay.ts +80 -0
  62. package/apps/client/src/main.tsx +17 -0
  63. package/apps/client/src/modules.ts +30 -0
  64. package/apps/client/src/pages/index/index.less +43 -0
  65. package/apps/client/src/pages/index/index.tsx +145 -0
  66. package/apps/client/src/platform/AgentChatApp.tsx +39 -0
  67. package/apps/client/src/platform/agent-runtime.ts +15 -0
  68. package/apps/client/src/platform/state/createPersistedStore.ts +19 -0
  69. package/apps/client/src/shims/node-stub.ts +14 -0
  70. package/apps/client/src/store/chat-config.ts +29 -0
  71. package/apps/client/src/store/chat.ts +201 -0
  72. package/apps/client/src/styles/global.less +60 -0
  73. package/apps/client/src/styles/tokens.less +60 -0
  74. package/apps/client/src/types/chat.ts +29 -0
  75. package/apps/client/src/utils/copyText.ts +11 -0
  76. package/apps/client/src/utils/imagePreview.ts +10 -0
  77. package/apps/client/src/utils/session-error.ts +5 -0
  78. package/apps/client/src/utils/toast.ts +15 -0
  79. package/apps/client/src/vite-env.d.ts +3 -0
  80. package/apps/client/tsconfig.json +11 -0
  81. package/apps/client/vite.config.ts +40 -0
  82. package/apps/server/package.json +40 -0
  83. package/apps/server/src/cli/collect-registry.ts +26 -0
  84. package/apps/server/src/cli/plugin-doctor.ts +89 -0
  85. package/apps/server/src/cli/plugin-inspect.ts +49 -0
  86. package/apps/server/src/index.ts +230 -0
  87. package/apps/server/src/intent-routes.ts +23 -0
  88. package/apps/server/src/modules.ts +31 -0
  89. package/apps/server/src/tools/translate.ts +41 -0
  90. package/apps/server/tests/cards-registry.test.ts +55 -0
  91. package/apps/server/tests/tool-runtime.test.ts +49 -0
  92. package/apps/server/tsconfig.json +5 -0
  93. package/apps/server/tsup.config.ts +25 -0
  94. package/apps/server/vitest.config.ts +8 -0
  95. package/config/chat.json +8 -0
  96. package/config/llm_provider.json +3 -0
  97. package/config/plugins.json +3 -0
  98. package/eslint.config.mjs +41 -0
  99. package/package.json +51 -0
  100. package/packages/module-weather/package.json +35 -0
  101. package/packages/module-weather/src/client/cards/weather/index.less +22 -0
  102. package/packages/module-weather/src/client/cards/weather/index.tsx +14 -0
  103. package/packages/module-weather/src/client/index.ts +19 -0
  104. package/packages/module-weather/src/index.ts +2 -0
  105. package/packages/module-weather/src/server/cards/weather/meta.ts +28 -0
  106. package/packages/module-weather/src/server/index.ts +19 -0
  107. package/packages/module-weather/src/server/tool.ts +29 -0
  108. package/packages/module-weather/tbox.component.json +27 -0
  109. package/packages/module-weather/tsconfig.json +14 -0
  110. package/pnpm-workspace.template.yaml +60 -0
  111. package/scripts/build.sh +39 -0
  112. package/scripts/check-inspector.sh +91 -0
  113. package/scripts/check-workspace.mjs +164 -0
  114. package/scripts/dev.sh +15 -0
  115. package/scripts/prepare.sh +11 -0
  116. package/scripts/start.sh +11 -0
  117. package/scripts/stop.sh +26 -0
  118. package/scripts/verify-generated.mjs +142 -0
  119. package/tsconfig.base.json +20 -0
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@tbox.cn/app-template-agent",
3
+ "version": "0.1.0",
4
+ "description": "tbox AI Agent 全栈模板(多包 pnpm workspace):apps/client + apps/server + packages/module-*。客户端同时跑在浏览器与支付宝小程序 web-view 壳里。",
5
+ "engines": {
6
+ "node": ">=24.0.0 <25.0.0"
7
+ },
8
+ "files": [
9
+ "apps",
10
+ "!apps/**/dist",
11
+ "!apps/**/dist-ssr",
12
+ "!apps/**/.vite",
13
+ "!apps/**/node_modules",
14
+ "config",
15
+ "packages",
16
+ "!packages/**/node_modules",
17
+ "scripts",
18
+ ".tbox",
19
+ ".gitignore",
20
+ ".nvmrc",
21
+ "AGENTS.md",
22
+ "ENV.md",
23
+ "LEGAL.md",
24
+ "README.md",
25
+ "eslint.config.mjs",
26
+ "pnpm-workspace.template.yaml",
27
+ "tsconfig.base.json",
28
+ "LICENSE"
29
+ ],
30
+ "devDependencies": {
31
+ "eslint": "^9.0.0",
32
+ "typescript-eslint": "^8.57.2",
33
+ "eslint-plugin-react-hooks": "^5.2.0"
34
+ },
35
+ "license": "MIT",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "dev": "pnpm --filter @app/server dev",
41
+ "build": "pnpm check:workspace && pnpm --filter @app/server build && pnpm --filter @app/client build",
42
+ "typecheck": "pnpm -r --filter \"@app/*\" typecheck",
43
+ "test": "pnpm -r --filter \"@app/*\" test",
44
+ "lint": "eslint apps packages",
45
+ "verify": "pnpm typecheck && pnpm test && pnpm lint && pnpm build && pnpm verify:generated",
46
+ "verify:generated": "node scripts/verify-generated.mjs",
47
+ "check:workspace": "node scripts/check-workspace.mjs",
48
+ "plugin:inspect": "pnpm --filter @app/server exec node --import tsx src/cli/plugin-inspect.ts",
49
+ "plugin:doctor": "pnpm --filter @app/server exec node --import tsx src/cli/plugin-doctor.ts"
50
+ }
51
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@app/module-weather",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./src/index.ts",
9
+ "import": "./src/index.ts"
10
+ },
11
+ "./server": {
12
+ "types": "./src/server/index.ts",
13
+ "import": "./src/server/index.ts"
14
+ },
15
+ "./client": {
16
+ "types": "./src/client/index.ts",
17
+ "import": "./src/client/index.ts"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "dependencies": {
24
+ "@tbox.cn/app-sdk": "catalog:",
25
+ "@tbox.cn/app-contracts": "catalog:"
26
+ },
27
+ "peerDependencies": {
28
+ "zod": "catalog:",
29
+ "react": "catalog:"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "catalog:",
33
+ "typescript": "catalog:"
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ .tbox-weather {
2
+ max-width: 80%;
3
+ padding: 32px 24px;
4
+
5
+ &__city {
6
+ font-size: var(--tbox-fs-heading);
7
+ font-weight: 600;
8
+ color: var(--tbox-text-primary);
9
+ }
10
+
11
+ &__temp {
12
+ font-size: 56px;
13
+ font-weight: 700;
14
+ color: var(--tbox-primary);
15
+ margin: 8px 0;
16
+ }
17
+
18
+ &__meta {
19
+ font-size: var(--tbox-fs-caption);
20
+ color: var(--tbox-text-secondary);
21
+ }
22
+ }
@@ -0,0 +1,14 @@
1
+ import type { WeatherCardData } from '../../../server/cards/weather/meta';
2
+ import './index.less';
3
+
4
+ export function WeatherCard({ data }: { data: WeatherCardData; cardId: string; isHistory: boolean }) {
5
+ return (
6
+ <div className="tbox-weather">
7
+ <div className="tbox-weather__city">{data.city}</div>
8
+ <div className="tbox-weather__temp">{data.temp}°</div>
9
+ <div className="tbox-weather__meta">
10
+ {data.weather} · 湿度 {data.humidity}%
11
+ </div>
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,19 @@
1
+ import type { CardComponent } from '@tbox.cn/app-contracts';
2
+ import type { ClientContext, ClientModule } from '@tbox.cn/app-sdk/client';
3
+ import { WeatherCard } from './cards/weather/index';
4
+
5
+ /**
6
+ * 客户端卡片组件 map(CLI sync 读取)。
7
+ * key 约定 = cardType;渲染边界统一为 CardComponent(data: unknown),具体类型由组件内部收窄。
8
+ */
9
+ const cards = {
10
+ weather: WeatherCard as CardComponent,
11
+ } as const;
12
+
13
+ /** 模块注册入口:自注册闭环(与服务端 serverModule 对称) */
14
+ export const clientModule = {
15
+ cards,
16
+ register(ctx: ClientContext): void {
17
+ ctx.cards.registerMap(cards);
18
+ },
19
+ } satisfies ClientModule;
@@ -0,0 +1,2 @@
1
+ export * from './server';
2
+ export * from './client';
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import type { CardMeta } from '@tbox.cn/app-contracts';
3
+
4
+ export const dataSchema = z.object({
5
+ city: z.string(),
6
+ temp: z.number(),
7
+ weather: z.string(),
8
+ humidity: z.number(),
9
+ });
10
+
11
+ export type WeatherCardData = z.infer<typeof dataSchema>;
12
+
13
+ const meta: CardMeta<typeof dataSchema> = {
14
+ cardType: 'weather',
15
+ dataSchema,
16
+ allowedTools: ['getWeather'],
17
+ displayName: '天气卡片',
18
+ description: '展示某城市的天气数据,含温度、天气状况、湿度',
19
+ sampleData: {
20
+ city: '北京',
21
+ temp: 22,
22
+ weather: '晴',
23
+ humidity: 35,
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+ export { meta as weatherMeta };
@@ -0,0 +1,19 @@
1
+ import { weatherTool } from './tool';
2
+ import weatherMeta from './cards/weather/meta';
3
+ import type { ServerContext, ServerModule } from '@tbox.cn/app-sdk/server';
4
+
5
+ export { weatherTool, weatherMeta };
6
+
7
+ /** 卡片 meta 声明 map(key 约定 = meta.cardType;数据 + 类型源) */
8
+ const cards = {
9
+ weather: weatherMeta,
10
+ } as const;
11
+
12
+ /** 模块注册入口:自注册闭环(与客户端 clientModule 对称) */
13
+ export const serverModule = {
14
+ cards,
15
+ register(ctx: ServerContext): void {
16
+ ctx.tools.register(weatherTool);
17
+ ctx.cards.registerMap(cards);
18
+ },
19
+ } satisfies ServerModule;
@@ -0,0 +1,29 @@
1
+ import { createTool } from '@tbox.cn/app-sdk/server';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * 天气查询工具 — 示例
6
+ */
7
+ export const weatherTool = createTool({
8
+ id: 'getWeather',
9
+ description: '查询城市天气信息',
10
+ inputSchema: z.object({
11
+ city: z.string().describe('城市名称,如 北京、上海'),
12
+ }),
13
+ outputSchema: z.object({
14
+ city: z.string(),
15
+ temp: z.number(),
16
+ weather: z.string(),
17
+ humidity: z.number(),
18
+ }),
19
+ execute: async ({ city }) => {
20
+ const mockData: Record<string, { temp: number; weather: string; humidity: number }> = {
21
+ 北京: { temp: 22, weather: '晴', humidity: 35 },
22
+ 上海: { temp: 25, weather: '多云', humidity: 65 },
23
+ 广州: { temp: 30, weather: '阵雨', humidity: 80 },
24
+ 深圳: { temp: 29, weather: '多云', humidity: 75 },
25
+ };
26
+ const data = mockData[city] || { temp: 20, weather: '未知', humidity: 50 };
27
+ return { city, ...data };
28
+ },
29
+ });
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "name": "module-weather",
4
+ "version": "0.1.0",
5
+ "kind": "business",
6
+ "risk": {
7
+ "level": "low",
8
+ "writeBoundary": "source"
9
+ },
10
+ "distribution": {
11
+ "defaultMode": "codegen"
12
+ },
13
+ "contributes": {
14
+ "handlers": [],
15
+ "tools": ["getWeather"],
16
+ "cards": [
17
+ { "cardType": "weather", "schemaVersion": 1 }
18
+ ],
19
+ "routes": [],
20
+ "pages": [],
21
+ "tabs": []
22
+ },
23
+ "dependencies": {
24
+ "modules": []
25
+ },
26
+ "env": []
27
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "jsx": "react-jsx",
6
+ "paths": {
7
+ "@tbox.cn/app-contracts": ["../contracts/src/index.ts"],
8
+ "@tbox.cn/app-contracts/*": ["../contracts/src/*"],
9
+ "@tbox.cn/app-sdk/server": ["../app-sdk/src/server/index.ts"],
10
+ "@tbox.cn/app-sdk/client": ["../app-sdk/src/client/index.ts"]
11
+ }
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,60 @@
1
+ # 应用 workspace 模板:tbox-app create 用它生成应用的 pnpm-workspace.yaml。
2
+ # 本文件不触发 workspace root(非 pnpm-workspace.yaml 文件名)。
3
+ # 同步规则:catalog 与根 pnpm-workspace.yaml 保持并集一致(单向复制,未引用条目无害)。
4
+ packages:
5
+ - apps/*
6
+ - packages/*
7
+
8
+ onlyBuiltDependencies:
9
+ - esbuild
10
+
11
+ allowBuilds:
12
+ esbuild: true
13
+
14
+ catalog:
15
+ typescript: ^5.7.0
16
+ tsx: ^4.19.0
17
+ zod: ^3.24.0
18
+ vite: ^6.4.3
19
+ vitest: ^4.1.4
20
+ react: ^18.3.1
21
+ react-dom: ^18.3.1
22
+ "@types/react": ^18.3.12
23
+ "@types/react-dom": ^18.3.1
24
+ "@types/node": ^24.0.0
25
+ express: ^4.21.0
26
+ "@types/express": ^4.17.21
27
+ ws: ^8.18.0
28
+ "@types/ws": ^8.5.0
29
+ "@mastra/core": ^1.33.0
30
+ "@mastra/observability": 1.12.0
31
+ "@ag-ui/core": ^0.0.47
32
+ ai: ^6.0.0
33
+ "@ai-sdk/openai": ^3.0.0
34
+ "@ai-sdk/provider": ^3.0.10
35
+ "@tbox.cn/agent-plugin": 0.1.0
36
+ "@tbox.cn/app-sdk": ^0.1.0
37
+ "@tbox.cn/app-contracts": ^0.1.0
38
+ "@tbox.cn/app-contracts-mall": ^0.1.0
39
+ zustand: ^4.5.5
40
+ commander: ^12.1.0
41
+ diff: ^7.0.0
42
+ "@types/diff": ^7.0.0
43
+ eslint: ^9.0.0
44
+ typescript-eslint: ^8.57.2
45
+ eslint-plugin-react-hooks: ^5.2.0
46
+ tsup: ^8.0.0
47
+ cors: ^2.8.5
48
+ jose: ^6.2.2
49
+ "@types/cors": ^2.8.17
50
+ "@tbox-dev-js/sdk": 0.1.18
51
+ "@vitejs/plugin-react": ^4.3.3
52
+ less: ^4.2.0
53
+ "@phosphor-icons/react": ^2.1.10
54
+ antd-mobile: ^5.42.3
55
+ react-markdown: ^9.1.0
56
+ remark-gfm: ^4.0.1
57
+ "@types/supertest": ^7.2.0
58
+ supertest: ^7.2.2
59
+ happy-dom: ^20.9.0
60
+ postcss-pxtorem: ^6.1.0
@@ -0,0 +1,39 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Usage: build.sh [--skip-server]
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
8
+ cd "$PROJECT_DIR"
9
+ echo "Project directory: $PROJECT_DIR"
10
+
11
+ export NODE_ENV=production
12
+ echo "APP_API_URL: $APP_API_URL"
13
+
14
+ SKIP_SERVER=false
15
+ for arg in "$@"; do
16
+ [ "$arg" = "--skip-server" ] && SKIP_SERVER=true
17
+ done
18
+ echo "Skip server: $SKIP_SERVER"
19
+
20
+ # 工程边界守卫
21
+ node "$SCRIPT_DIR/check-workspace.mjs"
22
+
23
+ # 平台基础设施守卫
24
+ bash "$SCRIPT_DIR/check-inspector.sh"
25
+
26
+ echo "Installing dependencies..."
27
+ CI=true pnpm install
28
+
29
+ echo "Building client (H5)..."
30
+ pnpm --filter @app/client build
31
+
32
+ if [ "$SKIP_SERVER" = false ]; then
33
+ echo "Building server..."
34
+ pnpm --filter @app/server build
35
+ else
36
+ echo "Skipping server build (--skip-server)"
37
+ fi
38
+
39
+ echo "All builds completed successfully!"
@@ -0,0 +1,91 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # 平台基础设施守卫 — 校验 tbox 微调(Inspector)注入未被破坏。
4
+ # Inspector 已下沉 @tbox.cn/app-sdk/platform(003 §3.1),本脚本断言同步更新。
5
+ # 支持两种上下文:
6
+ # 1. 根 workspace 成员形态(模板子树):向上探测 pnpm-workspace.yaml 取 SDK 真源
7
+ # 2. 生成应用发布态:app-sdk 经 node_modules 解析(检查包存在 + exports 含 ./platform)
8
+ # ============================================================================
9
+ set -e
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
13
+
14
+ INDEX_FILE="$PROJECT_DIR/apps/server/src/index.ts"
15
+ AGENT_HTML="$PROJECT_DIR/apps/client/index.html"
16
+ VITE_CFG="$PROJECT_DIR/apps/client/vite.config.ts"
17
+
18
+ fail() {
19
+ echo ""
20
+ echo "❌ [check-inspector] 平台基础设施校验失败:$1"
21
+ echo ""
22
+ echo " tbox 平台微调能力(点选元素 → 写入聊天框)是平台基础设施,AI agent 不得删除。"
23
+ echo " 必须保留:"
24
+ echo " 1. @tbox.cn/app-sdk/platform 存在并导出 installInspector(RUNTIME_PATH 约定不变)"
25
+ echo " 2. apps/server/src/index.ts 从 @tbox.cn/app-sdk/platform import 并调用 installInspector(app)"
26
+ echo " 3. apps/client/index.html 含 inspector 脚本标签"
27
+ echo " 4. apps/client/vite.config.ts 接入 code-inspect plugin"
28
+ echo ""
29
+ exit 1
30
+ }
31
+
32
+ # 定位 app-sdk 源码路径:模板内 → workspace 根 → node_modules(发布态)
33
+ SDK_SRC=""
34
+ for candidate in \
35
+ "$PROJECT_DIR/packages/app-sdk" \
36
+ "$PROJECT_DIR/../packages/app-sdk" \
37
+ "$PROJECT_DIR/../../packages/app-sdk"; do
38
+ if [ -f "$candidate/src/platform/inspector.ts" ]; then
39
+ SDK_SRC="$candidate"
40
+ break
41
+ fi
42
+ done
43
+
44
+ if [ -n "$SDK_SRC" ]; then
45
+ # 1. inspector.ts 存在 + 导出 installInspector + RUNTIME_PATH(RUNTIME_PATH 约定不变)
46
+ INSPECTOR_FILE="$SDK_SRC/src/platform/inspector.ts"
47
+ grep -qE "export\s+(async\s+)?function\s+installInspector" "$INSPECTOR_FILE" \
48
+ || fail "inspector.ts 未导出 installInspector"
49
+ grep -qE "RUNTIME_PATH\s*=\s*['\"]/__vs_inspector__\.js['\"]" "$INSPECTOR_FILE" \
50
+ || fail "inspector.ts 中 RUNTIME_PATH 被改动"
51
+ else
52
+ # 发布态:@tbox.cn/app-sdk 经 node_modules 解析
53
+ SDK_PKG=""
54
+ for base in "$PROJECT_DIR" "$PROJECT_DIR/../.."; do
55
+ if [ -d "$base/node_modules/@tbox.cn/app-sdk" ]; then
56
+ SDK_PKG="$base/node_modules/@tbox.cn/app-sdk"
57
+ break
58
+ fi
59
+ done
60
+ [ -n "$SDK_PKG" ] || fail "未找到 @tbox.cn/app-sdk(源码形态或 node_modules 发布态)"
61
+ node -e "
62
+ const fs = require('fs');
63
+ const pkg = JSON.parse(fs.readFileSync('$SDK_PKG/package.json', 'utf8'));
64
+ const ex = pkg.exports || {};
65
+ if (!ex['./platform']) { console.error('platform export missing'); process.exit(1); }
66
+ " || fail "@tbox.cn/app-sdk 的 package.json 未导出 ./platform"
67
+ fi
68
+
69
+ # 2. index.ts 从 @tbox.cn/app-sdk/platform import 并调用 installInspector(app)
70
+ grep -qE "from\s+['\"]@tbox\.cn/app-sdk/platform['\"]" "$INDEX_FILE" \
71
+ || fail "index.ts 未从 @tbox.cn/app-sdk/platform import"
72
+ grep -qE "installInspector\s*\(\s*app\s*[,)]" "$INDEX_FILE" \
73
+ || fail "index.ts 未调用 installInspector(app)"
74
+
75
+ # 3. client/index.html 内置 inspector 脚本标签
76
+ [ -f "$AGENT_HTML" ] || fail "缺失文件 apps/client/index.html"
77
+ grep -qE "/__vs_inspector__\.js" "$AGENT_HTML" \
78
+ || fail "apps/client/index.html 缺少 inspector 脚本标签"
79
+
80
+ # 4. vite.config 接入 code-inspect plugin
81
+ [ -f "$VITE_CFG" ] || fail "缺失文件 apps/client/vite.config.ts"
82
+ grep -qE "codeInspectPlugin" "$VITE_CFG" \
83
+ || fail "apps/client/vite.config.ts 未接入 code-inspect plugin"
84
+
85
+ # 5. 源码标注:code-inspect babel plugin 存在
86
+ CODE_INSPECT="$PROJECT_DIR/apps/client/babel-plugins/code-inspect.cjs"
87
+ [ -f "$CODE_INSPECT" ] || fail "缺失文件 apps/client/babel-plugins/code-inspect.cjs"
88
+ grep -qE "data-code-inspect-loc-filepath" "$CODE_INSPECT" \
89
+ || fail "code-inspect.cjs 未注入 data-code-inspect-* 属性"
90
+
91
+ echo "✅ [check-inspector] 平台基础设施校验通过"
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from 'node:fs';
4
+ import { readdir, readFile } from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import process from 'node:process';
7
+
8
+ const projectDir = path.resolve(process.argv[2] ?? path.join(import.meta.dirname, '..'));
9
+ const allowedViteConfigDirs = new Set(['apps/client']);
10
+ const ignoredDirs = new Set([
11
+ '.agents',
12
+ '.claude',
13
+ '.codex',
14
+ '.git',
15
+ '.node',
16
+ '.pnpm-store',
17
+ '.tmp',
18
+ '.vite',
19
+ 'dist',
20
+ 'dist-ssr',
21
+ 'node_modules',
22
+ ]);
23
+ const nestedLockfiles = new Set([
24
+ 'bun.lock',
25
+ 'bun.lockb',
26
+ 'package-lock.json',
27
+ 'pnpm-lock.yaml',
28
+ 'yarn.lock',
29
+ ]);
30
+ const viteConfigPattern = /^vite\.config\.(?:js|mjs|cjs|ts|mts|cts)$/;
31
+
32
+ function relativeDir(filePath) {
33
+ const relative = path.relative(projectDir, path.dirname(filePath));
34
+ return relative === '' ? '.' : relative.split(path.sep).join('/');
35
+ }
36
+
37
+ async function walk(dir, findings) {
38
+ const entries = await readdir(dir, { withFileTypes: true });
39
+
40
+ for (const entry of entries) {
41
+ if (entry.isSymbolicLink()) continue;
42
+
43
+ const fullPath = path.join(dir, entry.name);
44
+ if (entry.isDirectory()) {
45
+ if (!ignoredDirs.has(entry.name)) await walk(fullPath, findings);
46
+ continue;
47
+ }
48
+ if (!entry.isFile()) continue;
49
+
50
+ const dirName = relativeDir(fullPath);
51
+ const relativePath = path.relative(projectDir, fullPath).split(path.sep).join('/');
52
+
53
+ if (nestedLockfiles.has(entry.name) && dirName !== '.') {
54
+ findings.nestedLockfiles.push(relativePath);
55
+ }
56
+
57
+ if (viteConfigPattern.test(entry.name) && !allowedViteConfigDirs.has(dirName)) {
58
+ findings.detachedViteConfigs.push(relativePath);
59
+ }
60
+ }
61
+ }
62
+
63
+ function readWorkspacePackages(source) {
64
+ const packages = [];
65
+ let inPackages = false;
66
+
67
+ for (const line of source.split(/\r?\n/)) {
68
+ if (/^packages:\s*$/.test(line)) {
69
+ inPackages = true;
70
+ continue;
71
+ }
72
+ if (inPackages && /^\S/.test(line)) break;
73
+
74
+ const match = inPackages ? line.match(/^\s+-\s+['"]?([^'"#]+?)['"]?\s*(?:#.*)?$/) : null;
75
+ if (match) packages.push(match[1].trim());
76
+ }
77
+
78
+ return packages;
79
+ }
80
+
81
+ function printFailure(findings) {
82
+ console.error('');
83
+ console.error('[check-workspace] 工程边界校验失败。');
84
+
85
+ if (findings.nestedLockfiles.length > 0) {
86
+ console.error('\n检测到嵌套 lockfile(本项目只允许根 pnpm-lock.yaml):');
87
+ for (const file of findings.nestedLockfiles) console.error(` - ${file}`);
88
+ }
89
+ if (findings.detachedViteConfigs.length > 0) {
90
+ console.error('\n检测到 apps/client/ 之外的 Vite 项目配置:');
91
+ for (const file of findings.detachedViteConfigs) console.error(` - ${file}`);
92
+ }
93
+ if (findings.missingComponentDescriptors.length > 0) {
94
+ console.error('\npackages/* 缺少 tbox.component.json(contracts / app-sdk / shared 除外):');
95
+ for (const file of findings.missingComponentDescriptors) console.error(` - ${file}`);
96
+ }
97
+
98
+ console.error('');
99
+ process.exitCode = 1;
100
+ }
101
+
102
+ /** 豁免:SDK/契约包无 tbox.component.json(002 §2.3 / 003 §4.1) */
103
+ function isExemptPackage(dirName) {
104
+ return dirName === 'app-sdk' || dirName === 'shared' || dirName.startsWith('contracts');
105
+ }
106
+
107
+ /** 校验 packages/* 每个业务包必须有 tbox.component.json(002 §2.3 / 003 §4.1) */
108
+ async function checkComponentDescriptors(projectDir, findings) {
109
+ const packagesDir = path.join(projectDir, 'packages');
110
+ if (!existsSync(packagesDir)) return;
111
+ const entries = await readdir(packagesDir, { withFileTypes: true });
112
+ for (const entry of entries) {
113
+ if (!entry.isDirectory()) continue;
114
+ if (isExemptPackage(entry.name)) continue;
115
+ const pkgDir = path.join(packagesDir, entry.name);
116
+ if (!existsSync(path.join(pkgDir, 'package.json'))) continue;
117
+ if (!existsSync(path.join(pkgDir, 'tbox.component.json'))) {
118
+ findings.missingComponentDescriptors.push(`packages/${entry.name}/`);
119
+ }
120
+ }
121
+ }
122
+
123
+ /** 向上查找最近的 pnpm-workspace.yaml(模板目录并入根 workspace 后本目录可无 workspace 文件,属父 workspace 成员) */
124
+ function findWorkspaceFile(dir) {
125
+ let cur = path.resolve(dir);
126
+ for (;;) {
127
+ const f = path.join(cur, 'pnpm-workspace.yaml');
128
+ if (existsSync(f)) return f;
129
+ const parent = path.dirname(cur);
130
+ if (parent === cur) return null;
131
+ cur = parent;
132
+ }
133
+ }
134
+
135
+ async function main() {
136
+ const workspaceFile = findWorkspaceFile(projectDir);
137
+ if (!workspaceFile) {
138
+ console.error('[check-workspace] 缺少 pnpm-workspace.yaml(本目录及父级均未找到)。');
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+
143
+ const workspacePackages = readWorkspacePackages(await readFile(workspaceFile, 'utf8'));
144
+ const findings = {
145
+ nestedLockfiles: [],
146
+ detachedViteConfigs: [],
147
+ missingComponentDescriptors: [],
148
+ };
149
+
150
+ await walk(projectDir, findings);
151
+ await checkComponentDescriptors(projectDir, findings);
152
+
153
+ if (Object.values(findings).some((items) => items.length > 0)) {
154
+ printFailure(findings);
155
+ return;
156
+ }
157
+
158
+ console.log('[check-workspace] 工程边界校验通过(apps/* + packages/* workspace)');
159
+ }
160
+
161
+ main().catch((error) => {
162
+ console.error('[check-workspace] 校验执行失败:', error);
163
+ process.exitCode = 1;
164
+ });
package/scripts/dev.sh ADDED
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6
+ cd "$PROJECT_DIR"
7
+ echo "Project directory: $PROJECT_DIR"
8
+
9
+ echo "Installing dependencies..."
10
+ pnpm install
11
+
12
+ echo "Starting development server..."
13
+ echo " - http://localhost:8000 (API + WS + 页面 + HMR)"
14
+
15
+ pnpm --filter @app/server dev
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6
+ cd "$PROJECT_DIR"
7
+
8
+ echo "Installing dependencies..."
9
+ CI=true pnpm install
10
+
11
+ echo "Prepare complete!"
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
6
+ cd "$PROJECT_DIR"
7
+
8
+ echo "Starting production server..."
9
+ echo "NODE_ENV: $NODE_ENV"
10
+
11
+ NODE_ENV=production pnpm --filter @app/server start