@dyyz1993/create-agent 1.9.0 → 2.0.1

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 (121) hide show
  1. package/package.json +23 -23
  2. package/src/lib/copy.ts +260 -245
  3. package/templates/agent/eslint.config.mjs +14 -0
  4. package/templates/agent/package.json +8 -1
  5. package/templates/agent/src/__tests__/hybrid-mode.test.ts +126 -0
  6. package/templates/agent/src/__tests__/server-config-security.test.ts +55 -0
  7. package/templates/agent/src/bun/index.ts +96 -41
  8. package/templates/agent/src/bun/three.d.ts +1 -0
  9. package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +118 -0
  10. package/templates/agent/src/gateway/http-routes.ts +1 -1
  11. package/templates/agent/src/gateway/ws-handler.ts +84 -56
  12. package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
  13. package/templates/agent/src/mainview/__tests__/setup.ts +32 -29
  14. package/templates/agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
  15. package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +1 -58
  16. package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
  17. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +1 -0
  18. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +232 -210
  19. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +3 -1
  20. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +149 -122
  21. package/templates/agent/src/mainview/lib/api-client.ts +182 -172
  22. package/templates/agent/src/server-config.ts +34 -32
  23. package/templates/agent/src/server.ts +43 -54
  24. package/templates/agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  25. package/templates/agent/src/shared/handlers/__tests__/handler-isolation.test.ts +244 -0
  26. package/templates/agent/src/shared/handlers/bash.ts +65 -65
  27. package/templates/agent/src/shared/handlers/chat.ts +189 -155
  28. package/templates/agent/src/shared/handlers/debug.ts +8 -0
  29. package/templates/agent/src/shared/handlers/feed.ts +31 -32
  30. package/templates/agent/src/shared/handlers/index.ts +1 -0
  31. package/templates/agent/src/shared/handlers/rules.ts +31 -31
  32. package/templates/agent/src/shared/handlers/todo.ts +31 -31
  33. package/templates/agent/src/shared/http-routes.ts +250 -0
  34. package/templates/agent/src/shared/lib/__tests__/web-server.test.ts +190 -0
  35. package/templates/agent/src/shared/lib/logger.ts +117 -95
  36. package/templates/agent/src/shared/lib/web-server.ts +128 -0
  37. package/templates/agent/src/shared/modules/debug.ts +8 -0
  38. package/templates/agent/src/shared/rpc-schema.ts +16 -3
  39. package/templates/agent/test-upload.txt +0 -0
  40. package/templates/agent/tsconfig.ipc.json +10 -0
  41. package/templates/agent/tsconfig.json +10 -2
  42. package/templates/chat/eslint.config.mjs +14 -0
  43. package/templates/chat/package.json +8 -1
  44. package/templates/chat/src/__tests__/hybrid-mode.test.ts +125 -0
  45. package/templates/chat/src/__tests__/server-config-security.test.ts +55 -0
  46. package/templates/chat/src/__tests__/server-config.test.ts +46 -44
  47. package/templates/chat/src/bun/index.ts +94 -44
  48. package/templates/chat/src/bun/three.d.ts +1 -0
  49. package/templates/chat/src/gateway/http-routes.ts +1 -1
  50. package/templates/chat/src/gateway/ws-handler.ts +84 -56
  51. package/templates/chat/src/mainview/App.tsx +6 -0
  52. package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
  53. package/templates/chat/src/mainview/__tests__/theme-variables.test.ts +36 -0
  54. package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
  55. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +1 -0
  56. package/templates/chat/src/mainview/lib/api-client.ts +183 -156
  57. package/templates/chat/src/mainview/lib/rpc-cache.ts +84 -0
  58. package/templates/chat/src/mainview/main.tsx +4 -3
  59. package/templates/chat/src/server-config.ts +33 -31
  60. package/templates/chat/src/server.ts +43 -60
  61. package/templates/chat/src/shared/handlers/chat.ts +196 -162
  62. package/templates/chat/src/shared/handlers/debug.ts +8 -0
  63. package/templates/chat/src/shared/handlers/index.ts +1 -0
  64. package/templates/chat/src/shared/http-routes.ts +250 -0
  65. package/templates/chat/src/shared/lib/__tests__/logger.test.ts +92 -0
  66. package/templates/chat/src/shared/lib/logger.ts +108 -95
  67. package/templates/chat/src/shared/lib/web-server.ts +128 -0
  68. package/templates/chat/src/shared/modules/debug.ts +8 -0
  69. package/templates/chat/src/shared/rpc-schema.ts +3 -2
  70. package/templates/chat/test-upload.txt +0 -0
  71. package/templates/chat/tsconfig.ipc.json +10 -0
  72. package/templates/general/eslint.config.mjs +14 -0
  73. package/templates/general/package.json +8 -1
  74. package/templates/general/src/__tests__/hybrid-mode.test.ts +125 -0
  75. package/templates/general/src/__tests__/server-config-security.test.ts +55 -0
  76. package/templates/general/src/__tests__/server-config.test.ts +46 -44
  77. package/templates/general/src/bun/index.ts +94 -44
  78. package/templates/general/src/bun/three.d.ts +1 -0
  79. package/templates/general/src/gateway/http-routes.ts +1 -1
  80. package/templates/general/src/gateway/ws-handler.ts +84 -56
  81. package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
  82. package/templates/general/src/mainview/__tests__/theme-variables.test.ts +36 -0
  83. package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
  84. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +1 -0
  85. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +241 -219
  86. package/templates/general/src/mainview/components/layout/AppLayout.tsx +6 -0
  87. package/templates/general/src/mainview/lib/api-client.ts +183 -156
  88. package/templates/general/src/mainview/lib/rpc-cache.ts +84 -0
  89. package/templates/general/src/mainview/main.tsx +4 -3
  90. package/templates/general/src/server-config.ts +33 -31
  91. package/templates/general/src/server.ts +43 -60
  92. package/templates/general/src/shared/handlers/__tests__/chat.test.ts +165 -160
  93. package/templates/general/src/shared/handlers/chat.ts +189 -155
  94. package/templates/general/src/shared/handlers/debug.ts +8 -0
  95. package/templates/general/src/shared/handlers/feed.ts +38 -40
  96. package/templates/general/src/shared/handlers/index.ts +1 -0
  97. package/templates/general/src/shared/http-routes.ts +250 -0
  98. package/templates/general/src/shared/lib/__tests__/logger.test.ts +92 -0
  99. package/templates/general/src/shared/lib/logger.ts +117 -95
  100. package/templates/general/src/shared/lib/web-server.ts +128 -0
  101. package/templates/general/src/shared/modules/debug.ts +8 -0
  102. package/templates/general/src/shared/rpc-schema.ts +12 -2
  103. package/templates/general/test-upload.txt +0 -0
  104. package/templates/general/tsconfig.ipc.json +10 -0
  105. package/templates/shared/__tests__/cors-security.test.ts +165 -0
  106. package/templates/shared/__tests__/file-path-security.test.ts +260 -0
  107. package/templates/shared/__tests__/http-routes.test.ts +174 -0
  108. package/templates/shared/__tests__/logger.test.ts +186 -0
  109. package/templates/shared/components/ErrorBoundary.tsx +58 -0
  110. package/templates/shared/env.d.ts +11 -0
  111. package/templates/shared/http-routes.ts +230 -0
  112. package/templates/shared/logger.ts +129 -0
  113. package/templates/shared/test-upload.txt +0 -0
  114. package/templates/shared/vite-base.config.ts +92 -0
  115. package/templates/shared/vitest-base.config.ts +42 -0
  116. package/templates/agent/bun.lock +0 -1279
  117. package/templates/agent/package-lock.json +0 -3670
  118. package/templates/chat/bun.lock +0 -1203
  119. package/templates/chat/package-lock.json +0 -3670
  120. package/templates/general/bun.lock +0 -1266
  121. package/templates/general/package-lock.json +0 -3670
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
- "name": "@dyyz1993/create-agent",
3
- "version": "1.9.0",
4
- "description": "Create Agent project scaffolding CLI",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/dyyz1993/pi-agent-template"
8
- },
9
- "bin": {
10
- "create-agent": "./src/cli.ts"
11
- },
12
- "files": [
13
- "src/",
14
- "templates/"
15
- ],
16
- "scripts": {
17
- "sync-templates": "rm -rf templates/general templates/chat templates/agent && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/general/ templates/general/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/chat/ templates/chat/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/agent/ templates/agent/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/agent/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/chat/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/general/eslint-plugin-rpc/ && node scripts/post-sync-templates.mjs",
18
- "test": "bun test src/__tests__/",
19
- "prepublishOnly": "test -d templates/general/src && test -d templates/chat/src && test -d templates/agent/src"
20
- },
21
- "engines": {
22
- "bun": ">=1.0.0"
23
- },
24
- "license": "MIT"
2
+ "name": "@dyyz1993/create-agent",
3
+ "version": "2.0.1",
4
+ "description": "Create Agent project scaffolding CLI",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/dyyz1993/pi-agent-template"
8
+ },
9
+ "bin": {
10
+ "create-agent": "./src/cli.ts"
11
+ },
12
+ "files": [
13
+ "src/",
14
+ "templates/"
15
+ ],
16
+ "scripts": {
17
+ "sync-templates": "rm -rf templates/general templates/chat templates/agent && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/general/ templates/general/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/chat/ templates/chat/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/agent/ templates/agent/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/agent/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/chat/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/general/eslint-plugin-rpc/ && node scripts/post-sync-templates.mjs",
18
+ "test": "bun test src/__tests__/",
19
+ "prepublishOnly": "test -d templates/general/src && test -d templates/chat/src && test -d templates/agent/src"
20
+ },
21
+ "engines": {
22
+ "bun": ">=1.0.0"
23
+ },
24
+ "license": "MIT"
25
25
  }
package/src/lib/copy.ts CHANGED
@@ -1,285 +1,300 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'fs';
2
- import { join, resolve } from 'path';
3
- import { execSync } from 'child_process';
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } from "fs";
2
+ import { join, resolve } from "path";
3
+ import { execSync } from "child_process";
4
4
 
5
- const SKIP_DIRS = new Set(['node_modules', 'build', 'dist', '.git', '.husky', '.trae']);
6
- const SKIP_FILES = new Set(['bun.lock', 'rpc-browser.js']);
5
+ const SKIP_DIRS = new Set(["node_modules", "build", "dist", ".git", ".husky", ".trae"]);
6
+ const SKIP_FILES = new Set(["bun.lock", "rpc-browser.js"]);
7
7
 
8
8
  export interface CopyOptions {
9
- projectName: string;
10
- templateDir: string;
11
- targetDir: string;
9
+ projectName: string;
10
+ templateDir: string;
11
+ targetDir: string;
12
12
  }
13
13
 
14
14
  export function deriveNames(projectName: string) {
15
- const pascalName = projectName.replace(/(^|-)([a-z])/g, (_, _sep, c) => c.toUpperCase());
16
- const identifier = `com.${projectName.replace(/-/g, '')}.app`;
17
- const shortId = `com.${projectName.replace(/-/g, '')}`;
18
- return { pascalName, identifier, shortId };
15
+ const pascalName = projectName.replace(/(^|-)([a-z])/g, (_, _sep, c) => c.toUpperCase());
16
+ const identifier = `com.${projectName.replace(/-/g, "")}.app`;
17
+ const shortId = `com.${projectName.replace(/-/g, "")}`;
18
+ return { pascalName, identifier, shortId };
19
19
  }
20
20
 
21
21
  export function copyAndReplace(srcDir: string, destDir: string, projectName: string): void {
22
- const { pascalName, identifier, shortId } = deriveNames(projectName);
22
+ const { pascalName, identifier, shortId } = deriveNames(projectName);
23
23
 
24
- mkdirSync(destDir, { recursive: true });
24
+ mkdirSync(destDir, { recursive: true });
25
25
 
26
- const entries = readdirSync(srcDir, { withFileTypes: true });
26
+ const entries = readdirSync(srcDir, { withFileTypes: true });
27
27
 
28
- for (const entry of entries) {
29
- const srcPath = join(srcDir, entry.name);
30
- const destPath = join(destDir, entry.name);
28
+ for (const entry of entries) {
29
+ const srcPath = join(srcDir, entry.name);
30
+ const destPath = join(destDir, entry.name);
31
31
 
32
- if (entry.isDirectory()) {
33
- if (SKIP_DIRS.has(entry.name)) continue;
34
- copyAndReplace(srcPath, destPath, projectName);
35
- } else {
36
- if (SKIP_FILES.has(entry.name)) continue;
32
+ if (entry.isDirectory()) {
33
+ if (SKIP_DIRS.has(entry.name)) continue;
34
+ copyAndReplace(srcPath, destPath, projectName);
35
+ } else {
36
+ if (SKIP_FILES.has(entry.name)) continue;
37
37
 
38
- let content = readFileSync(srcPath, 'utf-8');
38
+ let content = readFileSync(srcPath, "utf-8");
39
39
 
40
- content = content.replace(/pi-agent-template/g, projectName);
41
- content = content.replace(/Pi Agent Template/g, pascalName);
42
- content = content.replace(/Pi Agent/g, pascalName);
43
- content = content.replace(/com\.piagent\.template/g, identifier);
44
- content = content.replace(/com\.piagent/g, shortId);
45
- content = content.replace(/@pi-agent\//g, `@${projectName}/`);
40
+ content = content.replace(/pi-agent-template/g, projectName);
41
+ content = content.replace(/Pi Agent Template/g, pascalName);
42
+ content = content.replace(/Pi Agent/g, pascalName);
43
+ content = content.replace(/com\.piagent\.template/g, identifier);
44
+ content = content.replace(/com\.piagent/g, shortId);
45
+ content = content.replace(/@pi-agent\//g, `@${projectName}/`);
46
46
 
47
- writeFileSync(destPath, content);
48
- }
49
- }
47
+ writeFileSync(destPath, content);
48
+ }
49
+ }
50
50
  }
51
51
 
52
52
  function copyTraeRules(monorepoRoot: string, targetDir: string, projectName: string): void {
53
- const traeRulesDir = resolve(monorepoRoot, '.trae', 'rules');
54
- if (!existsSync(traeRulesDir)) return;
55
-
56
- const destRulesDir = join(targetDir, '.trae', 'rules');
57
- mkdirSync(destRulesDir, { recursive: true });
58
-
59
- const files = readdirSync(traeRulesDir).filter((f) => f !== 'memory.md');
60
- for (const file of files) {
61
- const src = join(traeRulesDir, file);
62
- const dest = join(destRulesDir, file);
63
- let content = readFileSync(src, 'utf-8');
64
- content = content.replace(/pi-agent-template/g, projectName);
65
- writeFileSync(dest, content);
66
- }
53
+ const traeRulesDir = resolve(monorepoRoot, ".trae", "rules");
54
+ if (!existsSync(traeRulesDir)) return;
55
+
56
+ const destRulesDir = join(targetDir, ".trae", "rules");
57
+ mkdirSync(destRulesDir, { recursive: true });
58
+
59
+ const files = readdirSync(traeRulesDir).filter((f) => f !== "memory.md");
60
+ for (const file of files) {
61
+ const src = join(traeRulesDir, file);
62
+ const dest = join(destRulesDir, file);
63
+ let content = readFileSync(src, "utf-8");
64
+ content = content.replace(/pi-agent-template/g, projectName);
65
+ writeFileSync(dest, content);
66
+ }
67
67
  }
68
68
 
69
69
  function copySharedModules(monorepoRoot: string, targetDir: string, projectName: string): void {
70
- const sharedDir = resolve(monorepoRoot, 'templates', 'shared');
71
- if (!existsSync(sharedDir)) return;
70
+ const sharedDir = resolve(monorepoRoot, "templates", "shared");
71
+ if (!existsSync(sharedDir)) return;
72
+
73
+ const destSharedDir = join(targetDir, "shared");
74
+ copyAndReplace(sharedDir, destSharedDir, projectName);
75
+
76
+ for (const f of ["http-routes.ts", "logger.ts"]) {
77
+ const p = join(destSharedDir, f);
78
+ if (existsSync(p)) unlinkSync(p);
79
+ }
80
+
81
+ const tsconfigPath = join(targetDir, "tsconfig.json");
82
+ if (existsSync(tsconfigPath)) {
83
+ let tsconfig = readFileSync(tsconfigPath, "utf-8");
84
+ tsconfig = tsconfig.replace(/"\.\.\/shared\/\*"/g, '"./shared/*"');
85
+ tsconfig = tsconfig.replace(/"\.\.\/shared"/g, '"./shared"');
86
+ writeFileSync(tsconfigPath, tsconfig);
87
+ }
88
+
89
+ cleanSharedViteConfig(destSharedDir);
90
+ }
72
91
 
73
- const destSharedDir = join(targetDir, 'shared');
74
- copyAndReplace(sharedDir, destSharedDir, projectName);
92
+ function cleanViteConfig(targetDir: string): void {
93
+ const viteConfigPath = join(targetDir, "vite.config.ts");
94
+ const vitestConfigPath = join(targetDir, "vitest.config.ts");
75
95
 
76
- const tsconfigPath = join(targetDir, 'tsconfig.json');
77
- if (existsSync(tsconfigPath)) {
78
- let tsconfig = readFileSync(tsconfigPath, 'utf-8');
79
- tsconfig = tsconfig.replace(/"\.\.\/shared\/\*"/g, '"./shared/*"');
80
- tsconfig = tsconfig.replace(/"\.\.\/shared"/g, '"./shared"');
81
- writeFileSync(tsconfigPath, tsconfig);
82
- }
96
+ for (const configPath of [viteConfigPath, vitestConfigPath]) {
97
+ if (!existsSync(configPath)) continue;
83
98
 
84
- cleanSharedViteConfig(destSharedDir);
85
- }
99
+ let config = readFileSync(configPath, "utf-8");
86
100
 
87
- function cleanViteConfig(targetDir: string): void {
88
- const viteConfigPath = join(targetDir, "vite.config.ts");
89
- const vitestConfigPath = join(targetDir, "vitest.config.ts");
90
-
91
- for (const configPath of [viteConfigPath, vitestConfigPath]) {
92
- if (!existsSync(configPath)) continue;
93
-
94
- let config = readFileSync(configPath, "utf-8");
95
-
96
- config = config.replace(
97
- /,\n\s+resolve:\s*\{\n\s+alias:\s*\{[^}]*\}[,\s]*\}[,;]?\n/g,
98
- ""
99
- );
100
-
101
- if (!config.includes('from "path"') && config.includes("resolve(")) {
102
- config = 'import { resolve } from "path";\n' + config;
103
- }
104
-
105
- config = config.replace(/from ["']\.\.\/shared\/vite-base\.config["']/g, 'from "./shared/vite-base.config"');
106
- config = config.replace(/from ["']\.\.\/shared\/vitest-base\.config["']/g, 'from "./shared/vitest-base.config"');
107
-
108
- writeFileSync(configPath, config);
109
- }
101
+ config = config.replace(/,\n\s+resolve:\s*\{\n\s+alias:\s*\{[^}]*\}[,\s]*\}[,;]?\n/g, "");
102
+
103
+ if (!config.includes('from "path"') && config.includes("resolve(")) {
104
+ config = 'import { resolve } from "path";\n' + config;
105
+ }
106
+
107
+ config = config.replace(
108
+ /from ["']\.\.\/shared\/vite-base\.config["']/g,
109
+ 'from "./shared/vite-base.config"'
110
+ );
111
+ config = config.replace(
112
+ /from ["']\.\.\/shared\/vitest-base\.config["']/g,
113
+ 'from "./shared/vitest-base.config"'
114
+ );
115
+
116
+ writeFileSync(configPath, config);
117
+ }
110
118
  }
111
119
 
112
120
  function cleanSharedViteConfig(sharedDir: string): void {
113
- const viteBaseConfigPath = join(sharedDir, "vite-base.config.ts");
114
- const vitestBaseConfigPath = join(sharedDir, "vitest-base.config.ts");
115
-
116
- for (const configPath of [viteBaseConfigPath, vitestBaseConfigPath]) {
117
- if (!existsSync(configPath)) continue;
118
-
119
- let config = readFileSync(configPath, "utf-8");
120
- config = config.replace(
121
- /,\n\s+resolve:\s*\{\n\s+alias:\s*\{[^}]*\}[,\s]*\}[,;]?\n/g,
122
- ""
123
- );
124
- config = config.replace(
125
- /,\n\s+alias:\s*\{[^}]*\}[,\s]*[,;]?\n/g,
126
- ""
127
- );
128
- if (!config.includes("resolve(")) {
129
- config = config.replace(/import\s*\{\s*resolve\s*\}\s*from\s*["']path["'];?\n?/g, "");
130
- config = config.replace(
131
- /export function create(Vite|Vitest)Config\(dirname: string\)/g,
132
- "export function create$1Config()"
133
- );
134
- }
135
- writeFileSync(configPath, config);
136
- }
121
+ const viteBaseConfigPath = join(sharedDir, "vite-base.config.ts");
122
+ const vitestBaseConfigPath = join(sharedDir, "vitest-base.config.ts");
123
+
124
+ for (const configPath of [viteBaseConfigPath, vitestBaseConfigPath]) {
125
+ if (!existsSync(configPath)) continue;
126
+
127
+ let config = readFileSync(configPath, "utf-8");
128
+ config = config.replace(
129
+ /,?\s*\n?\s*"@dyyz1993\/rpc-core":\s*resolve\(\s*\n?\s*dirname,\s*\n?\s*"\.\.",\s*\n?\s*"\.\.",\s*\n?\s*"packages",\s*\n?\s*"rpc-core",\s*\n?\s*"src",\s*\n?\s*"index\.ts"\s*\n?\s*\),?\s*\n?/g,
130
+ ""
131
+ );
132
+ config = config.replace(
133
+ /resolve\(\s*\n?\s*dirname,\s*\n?\s*"\.\.",\s*\n?\s*"shared"\s*\n?\s*\)/g,
134
+ 'resolve(dirname, "shared")'
135
+ );
136
+ writeFileSync(configPath, config);
137
+ }
137
138
  }
138
139
 
139
140
  function resolvePackageVersion(packageName: string): string {
140
- try {
141
- return execSync(`npm view ${packageName} version`, { encoding: 'utf-8' }).trim();
142
- } catch {
143
- return '1.0.0';
144
- }
141
+ try {
142
+ return execSync(`npm view ${packageName} version`, { encoding: "utf-8" }).trim();
143
+ } catch {
144
+ return "1.0.0";
145
+ }
145
146
  }
146
147
 
147
- const WORKSPACE_PACKAGES = [
148
- '@dyyz1993/rpc-core',
149
- '@dyyz1993/eslint-plugin-rpc',
150
- ];
148
+ const WORKSPACE_PACKAGES = ["@dyyz1993/rpc-core", "@dyyz1993/eslint-plugin-rpc"];
151
149
 
152
150
  function updatePackageJson(targetDir: string, _projectName: string): void {
153
- const rootPkgPath = join(targetDir, 'package.json');
154
- if (!existsSync(rootPkgPath)) return;
155
-
156
- const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
157
-
158
- delete rootPkg.workspaces;
159
-
160
- for (const depKey of ['dependencies', 'devDependencies'] as const) {
161
- if (!rootPkg[depKey]) continue;
162
- for (const pkgName of WORKSPACE_PACKAGES) {
163
- if (!rootPkg[depKey][pkgName]) continue;
164
- if (pkgName === '@dyyz1993/rpc-core') {
165
- const version = resolvePackageVersion(pkgName);
166
- rootPkg[depKey][pkgName] = `^${version}`;
167
- } else {
168
- delete rootPkg[depKey][pkgName];
169
- }
170
- }
171
- }
172
-
173
- writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, '\t') + '\n');
174
-
175
- const eslintConfigPath = join(targetDir, 'eslint.config.mjs');
176
- if (existsSync(eslintConfigPath)) {
177
- const lines = readFileSync(eslintConfigPath, 'utf-8').split('\n');
178
- const filteredLines: string[] = [];
179
-
180
- let inRpcSection = false;
181
- for (let i = 0; i < lines.length; i++) {
182
- const line = lines[i];
183
-
184
- if (line.includes('import rpcPlugin') && line.includes('@dyyz1993/eslint-plugin-rpc')) {
185
- continue;
186
- }
187
-
188
- if (line.includes('RPC') && line.includes('规范规则')) {
189
- inRpcSection = true;
190
- continue;
191
- }
192
-
193
- if (inRpcSection) {
194
- if (line.trim().startsWith("'rpc/") || line.trim().startsWith('"rpc/')) {
195
- continue;
196
- }
197
- inRpcSection = false;
198
- }
199
-
200
- if (line.includes('rpc: rpcPlugin')) {
201
- continue;
202
- }
203
-
204
- filteredLines.push(line);
205
- }
206
-
207
- let content = filteredLines.join('\n');
208
- content = content.replace(/\n\s+plugins:\s*\{\s*\},?\s*\n/g, '\n');
209
-
210
- writeFileSync(eslintConfigPath, content);
211
- }
151
+ const rootPkgPath = join(targetDir, "package.json");
152
+ if (!existsSync(rootPkgPath)) return;
153
+
154
+ const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
155
+
156
+ delete rootPkg.workspaces;
157
+
158
+ for (const depKey of ["dependencies", "devDependencies"] as const) {
159
+ if (!rootPkg[depKey]) continue;
160
+ for (const pkgName of WORKSPACE_PACKAGES) {
161
+ if (!rootPkg[depKey][pkgName]) continue;
162
+ if (pkgName === "@dyyz1993/rpc-core") {
163
+ const version = resolvePackageVersion(pkgName);
164
+ rootPkg[depKey][pkgName] = `^${version}`;
165
+ } else {
166
+ delete rootPkg[depKey][pkgName];
167
+ }
168
+ }
169
+ }
170
+
171
+ writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, "\t") + "\n");
172
+
173
+ const eslintConfigPath = join(targetDir, "eslint.config.mjs");
174
+ if (existsSync(eslintConfigPath)) {
175
+ const lines = readFileSync(eslintConfigPath, "utf-8").split("\n");
176
+ const filteredLines: string[] = [];
177
+
178
+ let inRpcSection = false;
179
+ for (let i = 0; i < lines.length; i++) {
180
+ const line = lines[i];
181
+
182
+ if (line.includes("import rpcPlugin") && line.includes("@dyyz1993/eslint-plugin-rpc")) {
183
+ continue;
184
+ }
185
+
186
+ if (line.includes("RPC") && line.includes("规范规则")) {
187
+ inRpcSection = true;
188
+ continue;
189
+ }
190
+
191
+ if (inRpcSection) {
192
+ if (line.trim().startsWith("'rpc/") || line.trim().startsWith('"rpc/')) {
193
+ continue;
194
+ }
195
+ inRpcSection = false;
196
+ }
197
+
198
+ if (line.includes("rpc: rpcPlugin")) {
199
+ continue;
200
+ }
201
+
202
+ filteredLines.push(line);
203
+ }
204
+
205
+ let content = filteredLines.join("\n");
206
+ content = content.replace(/\n\s+plugins:\s*\{\s*\},?\s*\n/g, "\n");
207
+
208
+ writeFileSync(eslintConfigPath, content);
209
+ }
212
210
  }
213
211
 
214
212
  export async function copyTemplate(options: CopyOptions): Promise<void> {
215
- const { projectName, templateDir, targetDir } = options;
216
- const localRoot = resolve(import.meta.dir, '..', '..', '..', '..');
217
- const isMonorepo = existsSync(resolve(localRoot, 'templates', 'general'));
218
-
219
- if (existsSync(targetDir)) {
220
- throw new Error(`Directory "${targetDir}" already exists.`);
221
- }
222
-
223
- const { identifier } = deriveNames(projectName);
224
-
225
- console.log(`Creating project: ${projectName}`);
226
- console.log(`Target directory: ${targetDir}`);
227
- console.log(`App identifier: ${identifier}`);
228
- console.log('');
229
-
230
- copyAndReplace(templateDir, targetDir, projectName);
231
- if (isMonorepo) {
232
- copyTraeRules(localRoot, targetDir, projectName);
233
- copySharedModules(localRoot, targetDir, projectName);
234
- }
235
- cleanViteConfig(targetDir);
236
- updatePackageJson(targetDir, projectName);
237
-
238
- console.log('Initializing git...');
239
- execSync('git init', { cwd: targetDir, stdio: 'pipe' });
240
-
241
- console.log('Installing dependencies...');
242
- execSync('bun install', { cwd: targetDir, stdio: 'inherit' });
243
-
244
- console.log('Building browser bundle...');
245
- try {
246
- execSync('bun run build:browser', { cwd: targetDir, stdio: 'pipe' });
247
- } catch {
248
- console.log('(build:browser skipped - script not found)');
249
- }
250
-
251
- const huskyDir = join(targetDir, '.husky');
252
- mkdirSync(huskyDir, { recursive: true });
253
- writeFileSync(
254
- join(huskyDir, 'pre-commit'),
255
- [
256
- '#!/bin/sh',
257
- 'bun run lint',
258
- 'ERRORS=$(bunx tsc --noEmit 2>&1 | grep "error TS" | grep -v "node_modules" || true)',
259
- 'if [ -n "$ERRORS" ]; then',
260
- ' echo "$ERRORS"',
261
- ' exit 1',
262
- 'fi',
263
- ].join('\n') + '\n',
264
- );
265
-
266
- execSync('git add -A', { cwd: targetDir, stdio: 'pipe' });
267
-
268
- try {
269
- execSync(`git commit --no-verify -m "feat: init ${projectName} from pi-agent-template"`, {
270
- cwd: targetDir,
271
- stdio: 'pipe',
272
- });
273
- } catch {
274
- console.log('(git commit skipped - no files to commit)');
275
- }
276
-
277
- console.log('');
278
- console.log('Project created successfully!');
279
- console.log('');
280
- console.log('Next steps:');
281
- console.log(` cd ${targetDir}`);
282
- console.log(' bun run dev # Start desktop app (Electrobun)');
283
- console.log(' bun run dev:web # Start web mode (Vite + Gateway)');
284
- console.log('');
213
+ const { projectName, templateDir, targetDir } = options;
214
+ const localRoot = resolve(import.meta.dir, "..", "..", "..", "..");
215
+ const isMonorepo = existsSync(resolve(localRoot, "templates", "general"));
216
+
217
+ if (existsSync(targetDir)) {
218
+ throw new Error(`Directory "${targetDir}" already exists.`);
219
+ }
220
+
221
+ const { identifier } = deriveNames(projectName);
222
+
223
+ console.log(`Creating project: ${projectName}`);
224
+ console.log(`Target directory: ${targetDir}`);
225
+ console.log(`App identifier: ${identifier}`);
226
+ console.log("");
227
+
228
+ copyAndReplace(templateDir, targetDir, projectName);
229
+ if (isMonorepo) {
230
+ copyTraeRules(localRoot, targetDir, projectName);
231
+ copySharedModules(localRoot, targetDir, projectName);
232
+ } else {
233
+ const publishedShared = resolve(import.meta.dir, "..", "..", "templates", "shared");
234
+ if (existsSync(publishedShared)) {
235
+ copyAndReplace(publishedShared, join(targetDir, "shared"), projectName);
236
+ for (const f of ["http-routes.ts", "logger.ts"]) {
237
+ const p = join(targetDir, "shared", f);
238
+ if (existsSync(p)) unlinkSync(p);
239
+ }
240
+ const tsconfigPath = join(targetDir, "tsconfig.json");
241
+ if (existsSync(tsconfigPath)) {
242
+ let tsconfig = readFileSync(tsconfigPath, "utf-8");
243
+ tsconfig = tsconfig.replace(/"\.\.\/shared\/\*"/g, '"./shared/*"');
244
+ tsconfig = tsconfig.replace(/"\.\.\/shared"/g, '"./shared"');
245
+ writeFileSync(tsconfigPath, tsconfig);
246
+ }
247
+ cleanSharedViteConfig(join(targetDir, "shared"));
248
+ }
249
+ }
250
+ cleanViteConfig(targetDir);
251
+ updatePackageJson(targetDir, projectName);
252
+
253
+ console.log("Initializing git...");
254
+ execSync("git init", { cwd: targetDir, stdio: "pipe" });
255
+
256
+ console.log("Installing dependencies...");
257
+ execSync("bun install", { cwd: targetDir, stdio: "inherit" });
258
+
259
+ console.log("Building browser bundle...");
260
+ try {
261
+ execSync("bun run build:browser", { cwd: targetDir, stdio: "pipe" });
262
+ } catch {
263
+ console.log("(build:browser skipped - script not found)");
264
+ }
265
+
266
+ const huskyDir = join(targetDir, ".husky");
267
+ mkdirSync(huskyDir, { recursive: true });
268
+ writeFileSync(
269
+ join(huskyDir, "pre-commit"),
270
+ [
271
+ "#!/bin/sh",
272
+ "bun run lint",
273
+ 'ERRORS=$(bunx tsc --noEmit 2>&1 | grep "error TS" | grep -v "node_modules" || true)',
274
+ 'if [ -n "$ERRORS" ]; then',
275
+ ' echo "$ERRORS"',
276
+ " exit 1",
277
+ "fi",
278
+ ].join("\n") + "\n"
279
+ );
280
+
281
+ execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
282
+
283
+ try {
284
+ execSync(`git commit --no-verify -m "feat: init ${projectName} from pi-agent-template"`, {
285
+ cwd: targetDir,
286
+ stdio: "pipe",
287
+ });
288
+ } catch {
289
+ console.log("(git commit skipped - no files to commit)");
290
+ }
291
+
292
+ console.log("");
293
+ console.log("Project created successfully!");
294
+ console.log("");
295
+ console.log("Next steps:");
296
+ console.log(` cd ${targetDir}`);
297
+ console.log(" bun run dev # Start desktop app (Electrobun)");
298
+ console.log(" bun run dev:web # Start web mode (Vite + Gateway)");
299
+ console.log("");
285
300
  }
@@ -1,6 +1,9 @@
1
1
  import js from '@eslint/js';
2
2
  import tseslint from 'typescript-eslint';
3
3
  import rpcPlugin from '@dyyz1993/eslint-plugin-rpc';
4
+ import reactHooks from 'eslint-plugin-react-hooks';
5
+ import reactRefresh from 'eslint-plugin-react-refresh';
6
+ import jsxA11y from 'eslint-plugin-jsx-a11y';
4
7
 
5
8
  export default tseslint.config(
6
9
  js.configs.recommended,
@@ -18,8 +21,19 @@ export default tseslint.config(
18
21
  files: ['src/**/*.{ts,tsx}'],
19
22
  plugins: {
20
23
  rpc: rpcPlugin,
24
+ 'react-hooks': reactHooks,
25
+ 'react-refresh': reactRefresh,
26
+ 'jsx-a11y': jsxA11y,
21
27
  },
22
28
  rules: {
29
+ ...reactHooks.configs.recommended.rules,
30
+ 'react-hooks/rules-of-hooks': 'warn',
31
+ 'react-hooks/exhaustive-deps': 'warn',
32
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
33
+ ...jsxA11y.configs.recommended.rules,
34
+ 'jsx-a11y/click-events-have-key-events': 'warn',
35
+ 'jsx-a11y/no-static-element-interactions': 'warn',
36
+ 'jsx-a11y/no-autofocus': 'warn',
23
37
  // TS 严格规则
24
38
  '@typescript-eslint/no-explicit-any': 'error',
25
39
  '@typescript-eslint/no-empty-object-type': ['error', { allowObjectTypes: 'always', allowInterfaces: 'with-single-extends' }],