@leeoohoo/ui-apps-devkit 0.1.0 → 0.1.2

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 (62) hide show
  1. package/README.md +75 -60
  2. package/bin/chatos-uiapp.js +4 -4
  3. package/package.json +26 -20
  4. package/src/cli.js +53 -53
  5. package/src/commands/dev.js +14 -14
  6. package/src/commands/init.js +131 -129
  7. package/src/commands/install.js +47 -46
  8. package/src/commands/pack.js +72 -72
  9. package/src/commands/validate.js +138 -80
  10. package/src/lib/args.js +49 -49
  11. package/src/lib/config.js +29 -29
  12. package/src/lib/fs.js +78 -78
  13. package/src/lib/path-boundary.js +16 -16
  14. package/src/lib/plugin.js +45 -45
  15. package/src/lib/state-constants.js +2 -0
  16. package/src/lib/template.js +172 -168
  17. package/src/sandbox/server.js +1957 -692
  18. package/templates/basic/README.md +78 -54
  19. package/templates/basic/chatos.config.json +5 -5
  20. package/templates/basic/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  21. package/templates/basic/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  22. package/templates/basic/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  23. package/templates/basic/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  24. package/templates/basic/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  25. package/templates/basic/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  26. package/templates/basic/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  27. package/templates/basic/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  28. package/templates/basic/plugin/apps/app/compact.mjs +41 -0
  29. package/templates/basic/plugin/apps/app/index.mjs +287 -263
  30. package/templates/basic/plugin/apps/app/mcp-prompt.en.md +7 -7
  31. package/templates/basic/plugin/apps/app/mcp-prompt.zh.md +7 -7
  32. package/templates/basic/plugin/apps/app/mcp-server.mjs +15 -15
  33. package/templates/basic/plugin/backend/index.mjs +37 -37
  34. package/templates/basic/template.json +7 -7
  35. package/templates/notepad/README.md +55 -24
  36. package/templates/notepad/chatos.config.json +4 -4
  37. package/templates/notepad/docs/CHATOS_UI_APPS_AI_CONTRIBUTIONS.md +214 -181
  38. package/templates/notepad/docs/CHATOS_UI_APPS_BACKEND_PROTOCOL.md +75 -74
  39. package/templates/notepad/docs/CHATOS_UI_APPS_HOST_API.md +136 -123
  40. package/templates/notepad/docs/CHATOS_UI_APPS_OVERVIEW.md +112 -107
  41. package/templates/notepad/docs/CHATOS_UI_APPS_PLUGIN_MANIFEST.md +242 -227
  42. package/templates/notepad/docs/CHATOS_UI_APPS_STYLE_GUIDE.md +95 -0
  43. package/templates/notepad/docs/CHATOS_UI_APPS_TROUBLESHOOTING.md +45 -0
  44. package/templates/notepad/docs/CHATOS_UI_PROMPTS_PROTOCOL.md +392 -392
  45. package/templates/notepad/plugin/apps/app/api.mjs +30 -30
  46. package/templates/notepad/plugin/apps/app/compact.mjs +41 -0
  47. package/templates/notepad/plugin/apps/app/dom.mjs +14 -14
  48. package/templates/notepad/plugin/apps/app/ds-tree.mjs +35 -35
  49. package/templates/notepad/plugin/apps/app/index.mjs +1056 -1056
  50. package/templates/notepad/plugin/apps/app/layers.mjs +338 -338
  51. package/templates/notepad/plugin/apps/app/markdown.mjs +120 -120
  52. package/templates/notepad/plugin/apps/app/mcp-prompt.en.md +22 -22
  53. package/templates/notepad/plugin/apps/app/mcp-prompt.zh.md +22 -22
  54. package/templates/notepad/plugin/apps/app/mcp-server.mjs +206 -199
  55. package/templates/notepad/plugin/apps/app/styles.mjs +355 -355
  56. package/templates/notepad/plugin/apps/app/tags.mjs +21 -21
  57. package/templates/notepad/plugin/apps/app/ui.mjs +280 -280
  58. package/templates/notepad/plugin/backend/index.mjs +99 -99
  59. package/templates/notepad/plugin/plugin.json +23 -23
  60. package/templates/notepad/plugin/shared/notepad-paths.mjs +59 -41
  61. package/templates/notepad/plugin/shared/notepad-store.mjs +765 -765
  62. package/templates/notepad/template.json +8 -8
package/README.md CHANGED
@@ -1,44 +1,52 @@
1
- # ChatOS UI Apps DevKit
2
-
3
- 一个可通过 npm 安装的 DevKit,用来:
4
-
5
- - 生成 UI Apps 插件工程(脚手架)
6
- - 在本地沙箱里运行/调试 `module` 应用(Host API mock)
7
- - 校验 `plugin.json` 与路径边界
8
- - 打包/安装到本机 ChatOS(`~/.deepseek_cli/chatos/ui_apps/plugins`)
9
-
10
- ## 安装
11
-
12
- ```bash
13
- npm i -g @leeoohoo/ui-apps-devkit
14
- ```
15
-
16
- 或直接用 npx:
17
-
18
- ```bash
19
- npx @leeoohoo/ui-apps-devkit chatos-uiapp --help
20
- ```
21
-
22
- ## 快速开始
23
-
24
- ```bash
25
- chatos-uiapp init my-first-uiapp
26
- cd my-first-uiapp
27
- npm install
28
- npm run dev
29
- ```
30
-
31
- ## 模板
32
-
33
- ```bash
34
- chatos-uiapp init --list-templates
35
- chatos-uiapp init my-app --template basic
36
- chatos-uiapp init my-app --template notepad
37
- ```
38
-
39
- - `basic`:最小可运行骨架(含 `host.chat.*` / `ctx.llm.complete()` 示例)
40
- - `notepad`:完整示例应用(文件夹/标签/搜索/后端持久化)
41
-
1
+ # ChatOS UI Apps DevKit
2
+
3
+ 一个可通过 npm 安装的 DevKit,用来:
4
+
5
+ - 生成 UI Apps 插件工程(脚手架)
6
+ - 在本地沙箱里运行/调试 `module` 应用(Host API mock)
7
+ - 校验 `plugin.json` 与路径边界
8
+ - 打包/安装到本机 ChatOS(用户插件目录:`<stateDir>/ui_apps/plugins`;`stateDir = <stateRoot>/<hostApp>`)
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ npm i -g @leeoohoo/ui-apps-devkit
14
+ ```
15
+
16
+ 或直接用 npx:
17
+
18
+ ```bash
19
+ npx @leeoohoo/ui-apps-devkit chatos-uiapp --help
20
+ ```
21
+
22
+ ## 快速开始
23
+
24
+ ```bash
25
+ chatos-uiapp init my-first-uiapp
26
+ cd my-first-uiapp
27
+ npm install
28
+ npm run dev
29
+ ```
30
+
31
+ ## 沙箱能力
32
+
33
+ - 模拟 `module mount()` 与 `host.*` API
34
+ - 右上角 Theme 切换(light/dark/system),用于测试 `host.theme.onChange` 与样式响应
35
+ - Inspect 面板展示 `host.context` 与 `--ds-*` tokens
36
+ - 右上角 `AI Config` 可配置 `API Key / Base URL / Model ID`,用于在沙箱内调用真实模型并测试应用 MCP(需配置 `ai.mcp`)
37
+ - `AI Config` 可设置 `Workdir` 覆盖(默认 `dataDir`,支持 `$dataDir/$pluginDir/$projectRoot`)
38
+
39
+ ## 模板
40
+
41
+ ```bash
42
+ chatos-uiapp init --list-templates
43
+ chatos-uiapp init my-app --template basic
44
+ chatos-uiapp init my-app --template notepad
45
+ ```
46
+
47
+ - `basic`:最小可运行骨架(含 `host.chat.*` / `ctx.llm.complete()` 示例)
48
+ - `notepad`:完整示例应用(文件夹/标签/搜索/后端持久化)
49
+
42
50
  完成开发后:
43
51
 
44
52
  ```bash
@@ -47,24 +55,31 @@ npm run pack
47
55
  npm run install:chatos
48
56
  ```
49
57
 
50
- ## 生成项目结构(约定)
51
-
52
- 生成的工程里,**可安装产物**固定在 `plugin/` 目录:
53
-
54
- ```
55
- my-first-uiapp/
56
- docs/ # 协议文档(随工程分发)
57
- chatos.config.json # DevKit 配置(pluginDir/appId)
58
- plugin/ # 直接导入/安装到 ChatOS 的插件目录
59
- plugin.json
60
- backend/ # (可选) Electron main 进程后端
61
- apps/<appId>/ # module 前端 + AI 贡献(MCP/Prompt)
62
- ```
58
+ ## MCP 依赖(必读)
63
59
 
64
- ## CLI
60
+ - ChatOS 导入插件时会排除 `node_modules/`,MCP server 运行时无法读取随包依赖。
61
+ - MCP server 只要引入第三方依赖(如 `@modelcontextprotocol/sdk`、`zod`),就必须在 build 阶段 **bundle 成单文件**,并在 `plugin.json` 的 `ai.mcp.entry` 指向 bundle 产物。
62
+ - 如果 MCP 启动报错 `Cannot find package '@modelcontextprotocol/sdk'`,说明依赖未被 bundle 或 vendoring 到插件目录。
63
+ - 或者完全使用 Node 内置模块,或把依赖源码 vendoring 到插件目录内。
65
64
 
66
- - `chatos-uiapp init <dir>`:生成工程
67
- - `chatos-uiapp dev`:启动本地运行沙箱(支持文件变更自动重载)
68
- - `chatos-uiapp validate`:校验 manifest 与路径边界
69
- - `chatos-uiapp pack`:打包 `.zip`(用于 ChatOS 导入)
70
- - `chatos-uiapp install`:复制到本机 ChatOS 用户插件目录
65
+ ## 生成项目结构(约定)
66
+
67
+ 生成的工程里,**可安装产物**固定在 `plugin/` 目录:
68
+
69
+ ```
70
+ my-first-uiapp/
71
+ docs/ # 协议文档(随工程分发)
72
+ chatos.config.json # DevKit 配置(pluginDir/appId)
73
+ plugin/ # 直接导入/安装到 ChatOS 的插件目录
74
+ plugin.json
75
+ backend/ # (可选) Electron main 进程后端
76
+ apps/<appId>/ # module 前端 + AI 贡献(MCP/Prompt)
77
+ ```
78
+
79
+ ## CLI
80
+
81
+ - `chatos-uiapp init <dir>`:生成工程
82
+ - `chatos-uiapp dev`:启动本地运行沙箱(支持文件变更自动重载)
83
+ - `chatos-uiapp validate`:校验 manifest 与路径边界(遇到 symlink 会给出警告)
84
+ - `chatos-uiapp pack`:打包 `.zip`(用于 ChatOS 导入)
85
+ - `chatos-uiapp install`:复制到本机 ChatOS 用户插件目录
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from '../src/cli.js';
3
-
4
- await runCli(process.argv);
5
-
2
+ import { runCli } from '../src/cli.js';
3
+
4
+ await runCli(process.argv);
5
+
package/package.json CHANGED
@@ -1,22 +1,28 @@
1
- {
2
- "name": "@leeoohoo/ui-apps-devkit",
3
- "version": "0.1.0",
4
- "description": "ChatOS UI Apps DevKit (CLI + templates + sandbox) for building installable ChatOS UI Apps plugins.",
5
- "license": "MIT",
1
+ {
2
+ "name": "@leeoohoo/ui-apps-devkit",
3
+ "version": "0.1.2",
4
+ "description": "ChatOS UI Apps DevKit (CLI + templates + sandbox) for building installable ChatOS UI Apps plugins.",
5
+ "license": "MIT",
6
6
  "type": "module",
7
- "bin": {
8
- "chatos-uiapp": "bin/chatos-uiapp.js"
7
+ "dependencies": {
8
+ "@modelcontextprotocol/sdk": "^1.25.2"
9
9
  },
10
- "files": [
11
- "bin/**",
12
- "src/**",
13
- "templates/**",
14
- "README.md"
15
- ],
16
- "engines": {
17
- "node": ">=18"
18
- },
19
- "publishConfig": {
20
- "access": "public"
21
- }
22
- }
10
+ "scripts": {
11
+ "test": "node --test"
12
+ },
13
+ "bin": {
14
+ "chatos-uiapp": "bin/chatos-uiapp.js"
15
+ },
16
+ "files": [
17
+ "bin/**",
18
+ "src/**",
19
+ "templates/**",
20
+ "README.md"
21
+ ],
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ }
28
+ }
package/src/cli.js CHANGED
@@ -1,53 +1,53 @@
1
- import { cmdInit } from './commands/init.js';
2
- import { cmdValidate } from './commands/validate.js';
3
- import { cmdPack } from './commands/pack.js';
4
- import { cmdInstall } from './commands/install.js';
5
- import { cmdDev } from './commands/dev.js';
6
-
7
- import { parseArgs } from './lib/args.js';
8
-
9
- function printHelp() {
10
- // Keep it short; detailed docs live in README and generated project.
11
- // eslint-disable-next-line no-console
12
- console.log(`chatos-uiapp
13
-
14
- Usage:
15
- chatos-uiapp init <dir> [--template <name>] [--force] [--plugin-id <id>] [--name <name>] [--app-id <appId>] [--version <semver>]
16
- chatos-uiapp init --list-templates
17
- chatos-uiapp dev [--port 4399] [--app <appId>] [--plugin-dir <path>]
18
- chatos-uiapp validate [--plugin-dir <path>]
19
- chatos-uiapp pack [--out <zipPath>] [--plugin-dir <path>]
20
- chatos-uiapp install [--host-app chatos] [--state-dir <path>] [--plugin-dir <path>]
21
-
22
- Examples:
23
- chatos-uiapp init my-app
24
- chatos-uiapp init my-app --template notepad
25
- chatos-uiapp init --list-templates
26
- chatos-uiapp dev --port 4399
27
- chatos-uiapp install --host-app chatos
28
- `);
29
- }
30
-
31
- export async function runCli(argv) {
32
- const { positionals, flags } = parseArgs(argv);
33
- const cmd = String(positionals[0] || '').trim();
34
-
35
- if (!cmd || cmd === 'help' || cmd === '--help' || flags.help) {
36
- printHelp();
37
- return;
38
- }
39
-
40
- try {
41
- if (cmd === 'init') return await cmdInit({ positionals: positionals.slice(1), flags });
42
- if (cmd === 'dev') return await cmdDev({ positionals: positionals.slice(1), flags });
43
- if (cmd === 'validate') return await cmdValidate({ positionals: positionals.slice(1), flags });
44
- if (cmd === 'pack') return await cmdPack({ positionals: positionals.slice(1), flags });
45
- if (cmd === 'install') return await cmdInstall({ positionals: positionals.slice(1), flags });
46
-
47
- throw new Error(`Unknown command: ${cmd}`);
48
- } catch (err) {
49
- // eslint-disable-next-line no-console
50
- console.error(`[chatos-uiapp] ${err?.message || String(err)}`);
51
- process.exitCode = 1;
52
- }
53
- }
1
+ import { cmdInit } from './commands/init.js';
2
+ import { cmdValidate } from './commands/validate.js';
3
+ import { cmdPack } from './commands/pack.js';
4
+ import { cmdInstall } from './commands/install.js';
5
+ import { cmdDev } from './commands/dev.js';
6
+
7
+ import { parseArgs } from './lib/args.js';
8
+
9
+ function printHelp() {
10
+ // Keep it short; detailed docs live in README and generated project.
11
+ // eslint-disable-next-line no-console
12
+ console.log(`chatos-uiapp
13
+
14
+ Usage:
15
+ chatos-uiapp init <dir> [--template <name>] [--force] [--plugin-id <id>] [--name <name>] [--app-id <appId>] [--version <semver>]
16
+ chatos-uiapp init --list-templates
17
+ chatos-uiapp dev [--port 4399] [--app <appId>] [--plugin-dir <path>]
18
+ chatos-uiapp validate [--plugin-dir <path>]
19
+ chatos-uiapp pack [--out <zipPath>] [--plugin-dir <path>]
20
+ chatos-uiapp install [--host-app chatos] [--state-dir <path>] [--plugin-dir <path>]
21
+
22
+ Examples:
23
+ chatos-uiapp init my-app
24
+ chatos-uiapp init my-app --template notepad
25
+ chatos-uiapp init --list-templates
26
+ chatos-uiapp dev --port 4399
27
+ chatos-uiapp install --host-app chatos
28
+ `);
29
+ }
30
+
31
+ export async function runCli(argv) {
32
+ const { positionals, flags } = parseArgs(argv);
33
+ const cmd = String(positionals[0] || '').trim();
34
+
35
+ if (!cmd || cmd === 'help' || cmd === '--help' || flags.help) {
36
+ printHelp();
37
+ return;
38
+ }
39
+
40
+ try {
41
+ if (cmd === 'init') return await cmdInit({ positionals: positionals.slice(1), flags });
42
+ if (cmd === 'dev') return await cmdDev({ positionals: positionals.slice(1), flags });
43
+ if (cmd === 'validate') return await cmdValidate({ positionals: positionals.slice(1), flags });
44
+ if (cmd === 'pack') return await cmdPack({ positionals: positionals.slice(1), flags });
45
+ if (cmd === 'install') return await cmdInstall({ positionals: positionals.slice(1), flags });
46
+
47
+ throw new Error(`Unknown command: ${cmd}`);
48
+ } catch (err) {
49
+ // eslint-disable-next-line no-console
50
+ console.error(`[chatos-uiapp] ${err?.message || String(err)}`);
51
+ process.exitCode = 1;
52
+ }
53
+ }
@@ -1,14 +1,14 @@
1
- import { loadDevkitConfig } from '../lib/config.js';
2
- import { findPluginDir } from '../lib/plugin.js';
3
- import { startSandboxServer } from '../sandbox/server.js';
4
-
5
- export async function cmdDev({ flags }) {
6
- const { config } = loadDevkitConfig(process.cwd());
7
- const pluginDir = findPluginDir(process.cwd(), flags['plugin-dir'] || flags.pluginDir || config?.pluginDir);
8
- const portRaw = String(flags.port || flags.p || '').trim();
9
- const port = portRaw ? Number(portRaw) : 4399;
10
- const appId = String(flags.app || flags['app-id'] || flags.appId || config?.appId || '').trim();
11
-
12
- await startSandboxServer({ pluginDir, port, appId });
13
- }
14
-
1
+ import { loadDevkitConfig } from '../lib/config.js';
2
+ import { findPluginDir } from '../lib/plugin.js';
3
+ import { startSandboxServer } from '../sandbox/server.js';
4
+
5
+ export async function cmdDev({ flags }) {
6
+ const { config } = loadDevkitConfig(process.cwd());
7
+ const pluginDir = findPluginDir(process.cwd(), flags['plugin-dir'] || flags.pluginDir || config?.pluginDir);
8
+ const portRaw = String(flags.port || flags.p || '').trim();
9
+ const port = portRaw ? Number(portRaw) : 4399;
10
+ const appId = String(flags.app || flags['app-id'] || flags.appId || config?.appId || '').trim();
11
+
12
+ await startSandboxServer({ pluginDir, port, appId });
13
+ }
14
+
@@ -1,7 +1,7 @@
1
- import path from 'path';
2
- import readline from 'readline';
3
-
4
- import { ensureDir, isDirectory, isFile, rmForce, writeText } from '../lib/fs.js';
1
+ import path from 'path';
2
+ import readline from 'readline';
3
+
4
+ import { ensureDir, isDirectory, isFile, rmForce, writeText } from '../lib/fs.js';
5
5
  import {
6
6
  copyTemplate,
7
7
  listTemplates,
@@ -11,131 +11,133 @@ import {
11
11
  writeScaffoldManifest,
12
12
  writeScaffoldPackageJson,
13
13
  } from '../lib/template.js';
14
-
15
- function canPrompt() {
16
- return Boolean(process.stdin.isTTY && process.stdout.isTTY);
17
- }
18
-
19
- async function promptLine(question, { defaultValue = '' } = {}) {
20
- if (!canPrompt()) throw new Error(`Missing required value: ${question}`);
21
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
22
- try {
23
- const answer = await new Promise((resolve) => rl.question(question, resolve));
24
- const v = String(answer || '').trim();
25
- return v || defaultValue;
26
- } finally {
27
- rl.close();
28
- }
29
- }
30
-
31
- export async function cmdInit({ positionals, flags }) {
32
- const list = Boolean(flags['list-templates'] || flags.listTemplates);
33
- if (list) {
34
- const templates = listTemplates();
35
- if (templates.length === 0) {
36
- // eslint-disable-next-line no-console
37
- console.log('No templates found.');
38
- return;
39
- }
40
- const lines = templates.map((t) => `- ${t.name}${t.description ? `: ${t.description}` : ''}`).join('\n');
41
- // eslint-disable-next-line no-console
42
- console.log(`Templates:\n${lines}\n\nUse:\n chatos-uiapp init my-app --template <name>\n`);
43
- return;
44
- }
45
-
46
- const dirArg = String(positionals[0] || '').trim();
47
- if (!dirArg) throw new Error('init requires <dir>');
48
-
49
- const templateName = String(flags.template || flags.t || '').trim() || 'basic';
50
- const templateMeta = readTemplateMeta(templateName);
51
-
52
- const destDir = path.resolve(process.cwd(), dirArg);
53
- const force = Boolean(flags.force);
54
-
55
- if (isDirectory(destDir)) {
56
- const entries = await (async () => {
57
- try {
58
- return (await import('fs')).default.readdirSync(destDir);
59
- } catch {
60
- return [];
61
- }
62
- })();
63
- if (entries.length > 0 && !force) {
64
- throw new Error(`Target directory is not empty: ${destDir} (use --force to overwrite)`);
65
- }
66
- if (force) rmForce(destDir);
67
- }
68
-
69
- ensureDir(destDir);
70
- copyTemplate({ templateName, destDir });
71
-
72
- const pluginId =
73
- String(flags['plugin-id'] || flags.pluginId || '').trim() || (await promptLine('pluginId (e.g. com.example.myapp): '));
74
- const pluginName =
75
- String(flags.name || '').trim() ||
76
- (await promptLine('plugin name (display): ', { defaultValue: String(templateMeta?.defaults?.pluginName || '').trim() || pluginId }));
77
-
78
- const defaultAppId = String(templateMeta?.defaults?.appId || '').trim() || 'app';
79
- const appId =
80
- String(flags['app-id'] || flags.appId || '').trim() ||
81
- (await promptLine('appId (e.g. manager): ', { defaultValue: defaultAppId }));
82
-
83
- const version = String(flags.version || '').trim() || String(templateMeta?.defaults?.version || '').trim() || '0.1.0';
84
-
85
- const pluginDir = path.join(destDir, 'plugin');
86
- ensureDir(pluginDir);
87
-
88
- if (!isFile(path.join(pluginDir, 'plugin.json'))) {
89
- const withBackend = templateMeta?.defaults?.withBackend !== false;
90
- writeScaffoldManifest({ destPluginDir: pluginDir, pluginId, pluginName, version, appId, withBackend });
91
- }
92
- writeScaffoldPackageJson({ destDir, projectName: path.basename(destDir) });
93
- writeScaffoldConfig({ destDir, pluginDir: 'plugin', appId });
94
-
95
- // rename template app folder "app" -> actual appId
96
- const srcAppDir = path.join(pluginDir, 'apps', 'app');
97
- const dstAppDir = path.join(pluginDir, 'apps', appId);
98
- try {
99
- const fs = (await import('fs')).default;
100
- if (fs.existsSync(srcAppDir) && fs.statSync(srcAppDir).isDirectory()) {
101
- ensureDir(path.dirname(dstAppDir));
102
- fs.renameSync(srcAppDir, dstAppDir);
103
- }
104
- } catch {
105
- // ignore
106
- }
107
-
108
- // Token replacements inside README/template code.
109
- const replacements = {
110
- __PLUGIN_ID__: pluginId,
111
- __PLUGIN_NAME__: pluginName,
112
- __APP_ID__: appId,
113
- __VERSION__: version,
114
- };
115
-
116
- maybeReplaceTokensInFile(path.join(destDir, 'README.md'), replacements);
117
- maybeReplaceTokensInFile(path.join(destDir, 'chatos.config.json'), replacements);
118
- if (isFile(path.join(pluginDir, 'plugin.json'))) {
119
- maybeReplaceTokensInFile(path.join(pluginDir, 'plugin.json'), replacements);
120
- }
121
- maybeReplaceTokensInFile(path.join(pluginDir, 'backend', 'index.mjs'), replacements);
122
- maybeReplaceTokensInFile(path.join(dstAppDir, 'index.mjs'), replacements);
123
- maybeReplaceTokensInFile(path.join(dstAppDir, 'mcp-server.mjs'), replacements);
124
- maybeReplaceTokensInFile(path.join(dstAppDir, 'mcp-prompt.zh.md'), replacements);
125
- maybeReplaceTokensInFile(path.join(dstAppDir, 'mcp-prompt.en.md'), replacements);
126
-
127
- // Ensure a helpful note exists even if template is edited later.
14
+ import { COMPAT_STATE_ROOT_DIRNAME, STATE_ROOT_DIRNAME } from '../lib/state-constants.js';
15
+
16
+ function canPrompt() {
17
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
18
+ }
19
+
20
+ async function promptLine(question, { defaultValue = '' } = {}) {
21
+ if (!canPrompt()) throw new Error(`Missing required value: ${question}`);
22
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
23
+ try {
24
+ const answer = await new Promise((resolve) => rl.question(question, resolve));
25
+ const v = String(answer || '').trim();
26
+ return v || defaultValue;
27
+ } finally {
28
+ rl.close();
29
+ }
30
+ }
31
+
32
+ export async function cmdInit({ positionals, flags }) {
33
+ const list = Boolean(flags['list-templates'] || flags.listTemplates);
34
+ if (list) {
35
+ const templates = listTemplates();
36
+ if (templates.length === 0) {
37
+ // eslint-disable-next-line no-console
38
+ console.log('No templates found.');
39
+ return;
40
+ }
41
+ const lines = templates.map((t) => `- ${t.name}${t.description ? `: ${t.description}` : ''}`).join('\n');
42
+ // eslint-disable-next-line no-console
43
+ console.log(`Templates:\n${lines}\n\nUse:\n chatos-uiapp init my-app --template <name>\n`);
44
+ return;
45
+ }
46
+
47
+ const dirArg = String(positionals[0] || '').trim();
48
+ if (!dirArg) throw new Error('init requires <dir>');
49
+
50
+ const templateName = String(flags.template || flags.t || '').trim() || 'basic';
51
+ const templateMeta = readTemplateMeta(templateName);
52
+
53
+ const destDir = path.resolve(process.cwd(), dirArg);
54
+ const force = Boolean(flags.force);
55
+
56
+ if (isDirectory(destDir)) {
57
+ const entries = await (async () => {
58
+ try {
59
+ return (await import('fs')).default.readdirSync(destDir);
60
+ } catch {
61
+ return [];
62
+ }
63
+ })();
64
+ if (entries.length > 0 && !force) {
65
+ throw new Error(`Target directory is not empty: ${destDir} (use --force to overwrite)`);
66
+ }
67
+ if (force) rmForce(destDir);
68
+ }
69
+
70
+ ensureDir(destDir);
71
+ copyTemplate({ templateName, destDir });
72
+
73
+ const pluginId =
74
+ String(flags['plugin-id'] || flags.pluginId || '').trim() || (await promptLine('pluginId (e.g. com.example.myapp): '));
75
+ const pluginName =
76
+ String(flags.name || '').trim() ||
77
+ (await promptLine('plugin name (display): ', { defaultValue: String(templateMeta?.defaults?.pluginName || '').trim() || pluginId }));
78
+
79
+ const defaultAppId = String(templateMeta?.defaults?.appId || '').trim() || 'app';
80
+ const appId =
81
+ String(flags['app-id'] || flags.appId || '').trim() ||
82
+ (await promptLine('appId (e.g. manager): ', { defaultValue: defaultAppId }));
83
+
84
+ const version = String(flags.version || '').trim() || String(templateMeta?.defaults?.version || '').trim() || '0.1.0';
85
+
86
+ const pluginDir = path.join(destDir, 'plugin');
87
+ ensureDir(pluginDir);
88
+
89
+ if (!isFile(path.join(pluginDir, 'plugin.json'))) {
90
+ const withBackend = templateMeta?.defaults?.withBackend !== false;
91
+ writeScaffoldManifest({ destPluginDir: pluginDir, pluginId, pluginName, version, appId, withBackend });
92
+ }
93
+ writeScaffoldPackageJson({ destDir, projectName: path.basename(destDir) });
94
+ writeScaffoldConfig({ destDir, pluginDir: 'plugin', appId });
95
+
96
+ // rename template app folder "app" -> actual appId
97
+ const srcAppDir = path.join(pluginDir, 'apps', 'app');
98
+ const dstAppDir = path.join(pluginDir, 'apps', appId);
99
+ try {
100
+ const fs = (await import('fs')).default;
101
+ if (fs.existsSync(srcAppDir) && fs.statSync(srcAppDir).isDirectory()) {
102
+ ensureDir(path.dirname(dstAppDir));
103
+ fs.renameSync(srcAppDir, dstAppDir);
104
+ }
105
+ } catch {
106
+ // ignore
107
+ }
108
+
109
+ // Token replacements inside README/template code.
110
+ const replacements = {
111
+ __PLUGIN_ID__: pluginId,
112
+ __PLUGIN_NAME__: pluginName,
113
+ __APP_ID__: appId,
114
+ __VERSION__: version,
115
+ };
116
+
117
+ maybeReplaceTokensInFile(path.join(destDir, 'README.md'), replacements);
118
+ maybeReplaceTokensInFile(path.join(destDir, 'chatos.config.json'), replacements);
119
+ if (isFile(path.join(pluginDir, 'plugin.json'))) {
120
+ maybeReplaceTokensInFile(path.join(pluginDir, 'plugin.json'), replacements);
121
+ }
122
+ maybeReplaceTokensInFile(path.join(pluginDir, 'backend', 'index.mjs'), replacements);
123
+ maybeReplaceTokensInFile(path.join(dstAppDir, 'index.mjs'), replacements);
124
+ maybeReplaceTokensInFile(path.join(dstAppDir, 'compact.mjs'), replacements);
125
+ maybeReplaceTokensInFile(path.join(dstAppDir, 'mcp-server.mjs'), replacements);
126
+ maybeReplaceTokensInFile(path.join(dstAppDir, 'mcp-prompt.zh.md'), replacements);
127
+ maybeReplaceTokensInFile(path.join(dstAppDir, 'mcp-prompt.en.md'), replacements);
128
+
129
+ // Ensure a helpful note exists even if template is edited later.
128
130
  writeText(
129
131
  path.join(destDir, '.gitignore'),
130
- `node_modules/\n.DS_Store\n.chatos/\n*.log\n\n# build outputs (if you add bundling later)\ndist/\n`
132
+ `node_modules/\n.DS_Store\n${STATE_ROOT_DIRNAME}/\n${COMPAT_STATE_ROOT_DIRNAME}/\n*.log\n\n# build outputs (if you add bundling later)\ndist/\n`
131
133
  );
132
-
133
- // eslint-disable-next-line no-console
134
- console.log(`Created: ${destDir}
135
-
136
- Next:
137
- cd ${dirArg}
138
- npm install
139
- npm run dev
140
- `);
141
- }
134
+
135
+ // eslint-disable-next-line no-console
136
+ console.log(`Created: ${destDir}
137
+
138
+ Next:
139
+ cd ${dirArg}
140
+ npm install
141
+ npm run dev
142
+ `);
143
+ }