icopilot 2.2.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 (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,97 @@
1
+ import { TeamMemory, } from '../context/team-memory.js';
2
+ import { theme } from '../ui/theme.js';
3
+ const CATEGORIES = ['convention', 'decision', 'tip', 'warning'];
4
+ export function teamMemoryCommand(args, cwd) {
5
+ const memory = new TeamMemory();
6
+ const entries = memory.load(cwd);
7
+ const [rawSubcommand = 'list', ...rest] = args;
8
+ const subcommand = rawSubcommand.toLowerCase();
9
+ if (subcommand === 'list') {
10
+ return formatEntries(entries, 'Team memory');
11
+ }
12
+ if (subcommand === 'add') {
13
+ const category = parseCategory(rest[0]);
14
+ const content = rest.slice(1).join(' ').trim();
15
+ if (!category || !content)
16
+ return usage();
17
+ const entry = {
18
+ id: createEntryId(category, content, entries),
19
+ category,
20
+ content,
21
+ date: new Date().toISOString().slice(0, 10),
22
+ };
23
+ memory.add(entry);
24
+ return `${theme.ok('Added')} ${theme.hl(entry.id)} ${theme.dim(`[${entry.category}]`)} ${entry.content}\n`;
25
+ }
26
+ if (subcommand === 'remove') {
27
+ const id = rest.join(' ').trim();
28
+ if (!id)
29
+ return usage();
30
+ const match = findByIdPrefix(entries, id);
31
+ if (match.kind === 'missing')
32
+ return `${theme.warn(`No team memory entry matches "${id}".`)}\n`;
33
+ if (match.kind === 'ambiguous') {
34
+ return `${theme.warn(`Multiple team memory entries match "${id}": ${match.entries.map((entry) => entry.id).join(', ')}`)}\n`;
35
+ }
36
+ memory.remove(match.entry.id);
37
+ return `${theme.ok('Removed')} ${theme.hl(match.entry.id)}\n`;
38
+ }
39
+ if (subcommand === 'search') {
40
+ const query = rest.join(' ').trim();
41
+ if (!query)
42
+ return usage();
43
+ return formatEntries(memory.search(query), `Team memory ${theme.dim(`(search: ${query})`)}`);
44
+ }
45
+ return usage();
46
+ }
47
+ function formatEntries(entries, header) {
48
+ if (entries.length === 0)
49
+ return `${theme.brand(header)}\n ${theme.dim('No shared team memory entries.')}\n`;
50
+ const lines = entries.map((entry) => {
51
+ const metadata = [entry.category, entry.author, entry.date].filter(Boolean).join(', ');
52
+ return ` ${theme.hl(entry.id)} ${entry.content} ${theme.dim(`(${metadata})`)}`;
53
+ });
54
+ return `${theme.brand(header)}\n${lines.join('\n')}\n`;
55
+ }
56
+ function usage() {
57
+ return `Usage: /team-memory [list] | /team-memory add <${CATEGORIES.join('|')}> <content> | /team-memory remove <id> | /team-memory search <query>\n`;
58
+ }
59
+ function parseCategory(value) {
60
+ if (value === 'convention' || value === 'decision' || value === 'tip' || value === 'warning') {
61
+ return value;
62
+ }
63
+ return null;
64
+ }
65
+ function createEntryId(category, content, entries) {
66
+ const slug = slugify(content) || 'entry';
67
+ const base = `${category}-${slug}`;
68
+ let candidate = base;
69
+ let suffix = 2;
70
+ while (entries.some((entry) => entry.id === candidate)) {
71
+ candidate = `${base}-${suffix}`;
72
+ suffix += 1;
73
+ }
74
+ return candidate;
75
+ }
76
+ function slugify(value) {
77
+ return value
78
+ .toLowerCase()
79
+ .replace(/[^a-z0-9]+/g, '-')
80
+ .replace(/^-+|-+$/g, '')
81
+ .split('-')
82
+ .filter(Boolean)
83
+ .slice(0, 8)
84
+ .join('-');
85
+ }
86
+ function findByIdPrefix(entries, prefix) {
87
+ const normalized = prefix.trim();
88
+ const exact = entries.find((entry) => entry.id === normalized);
89
+ if (exact)
90
+ return { kind: 'match', entry: exact };
91
+ const matches = entries.filter((entry) => entry.id.startsWith(normalized));
92
+ if (matches.length === 0)
93
+ return { kind: 'missing' };
94
+ if (matches.length > 1)
95
+ return { kind: 'ambiguous', entries: matches };
96
+ return { kind: 'match', entry: matches[0] };
97
+ }
@@ -0,0 +1,475 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { config } from '../config.js';
5
+ import { theme } from '../ui/theme.js';
6
+ export const BUILT_IN_TEMPLATES = [
7
+ {
8
+ name: 'node-ts',
9
+ description: 'Basic Node.js + TypeScript project scaffold.',
10
+ files: [
11
+ {
12
+ path: 'package.json',
13
+ content: `${JSON.stringify({
14
+ name: 'node-ts-app',
15
+ version: '1.3.0',
16
+ private: true,
17
+ type: 'module',
18
+ scripts: {
19
+ build: 'tsc -p .',
20
+ dev: 'tsx src/index.ts',
21
+ start: 'node dist/index.js',
22
+ },
23
+ devDependencies: {
24
+ '@types/node': '^22.0.0',
25
+ tsx: '^4.0.0',
26
+ typescript: '^5.0.0',
27
+ },
28
+ }, null, 2)}\n`,
29
+ },
30
+ {
31
+ path: 'tsconfig.json',
32
+ content: `${JSON.stringify({
33
+ compilerOptions: {
34
+ target: 'ES2022',
35
+ module: 'ES2022',
36
+ moduleResolution: 'Bundler',
37
+ outDir: 'dist',
38
+ rootDir: 'src',
39
+ strict: true,
40
+ esModuleInterop: true,
41
+ skipLibCheck: true,
42
+ },
43
+ include: ['src/**/*.ts'],
44
+ }, null, 2)}\n`,
45
+ },
46
+ {
47
+ path: 'src/index.ts',
48
+ content: `export function main(): void {
49
+ console.log('Hello from node-ts');
50
+ }
51
+
52
+ main();
53
+ `,
54
+ },
55
+ {
56
+ path: '.gitignore',
57
+ content: `node_modules
58
+ dist
59
+ .env
60
+ `,
61
+ },
62
+ ],
63
+ },
64
+ {
65
+ name: 'express-api',
66
+ description: 'Express REST API with route and middleware skeletons.',
67
+ files: [
68
+ {
69
+ path: 'package.json',
70
+ content: `${JSON.stringify({
71
+ name: 'express-api',
72
+ version: '1.3.0',
73
+ private: true,
74
+ type: 'module',
75
+ scripts: {
76
+ build: 'tsc -p .',
77
+ dev: 'tsx watch src/index.ts',
78
+ start: 'node dist/index.js',
79
+ },
80
+ dependencies: {
81
+ express: '^4.19.2',
82
+ },
83
+ devDependencies: {
84
+ '@types/express': '^5.0.0',
85
+ '@types/node': '^22.0.0',
86
+ tsx: '^4.0.0',
87
+ typescript: '^5.0.0',
88
+ },
89
+ }, null, 2)}\n`,
90
+ },
91
+ {
92
+ path: 'tsconfig.json',
93
+ content: `${JSON.stringify({
94
+ compilerOptions: {
95
+ target: 'ES2022',
96
+ module: 'ES2022',
97
+ moduleResolution: 'Bundler',
98
+ outDir: 'dist',
99
+ rootDir: 'src',
100
+ strict: true,
101
+ esModuleInterop: true,
102
+ skipLibCheck: true,
103
+ },
104
+ include: ['src/**/*.ts'],
105
+ }, null, 2)}\n`,
106
+ },
107
+ {
108
+ path: 'src/index.ts',
109
+ content: `import express from 'express';
110
+ import { apiRouter } from './routes/index.js';
111
+ import { errorHandler } from './middleware/error.js';
112
+
113
+ const app = express();
114
+
115
+ app.use(express.json());
116
+ app.use('/api', apiRouter);
117
+ app.use(errorHandler);
118
+
119
+ const port = Number(process.env.PORT ?? 3000);
120
+ app.listen(port, () => {
121
+ console.log(\`API listening on http://localhost:\${port}\`);
122
+ });
123
+ `,
124
+ },
125
+ {
126
+ path: 'src/routes/index.ts',
127
+ content: `import { Router } from 'express';
128
+
129
+ export const apiRouter = Router();
130
+
131
+ apiRouter.get('/health', (_req, res) => {
132
+ res.json({ ok: true });
133
+ });
134
+ `,
135
+ },
136
+ {
137
+ path: 'src/middleware/error.ts',
138
+ content: `import type { NextFunction, Request, Response } from 'express';
139
+
140
+ export function errorHandler(
141
+ error: unknown,
142
+ _req: Request,
143
+ res: Response,
144
+ _next: NextFunction,
145
+ ): void {
146
+ console.error(error);
147
+ res.status(500).json({ error: 'Internal Server Error' });
148
+ }
149
+ `,
150
+ },
151
+ {
152
+ path: '.gitignore',
153
+ content: `node_modules
154
+ dist
155
+ .env
156
+ `,
157
+ },
158
+ ],
159
+ },
160
+ {
161
+ name: 'cli-tool',
162
+ description: 'Commander-powered CLI tool scaffold.',
163
+ files: [
164
+ {
165
+ path: 'package.json',
166
+ content: `${JSON.stringify({
167
+ name: 'cli-tool',
168
+ version: '1.3.0',
169
+ private: true,
170
+ type: 'module',
171
+ bin: {
172
+ 'cli-tool': './bin/cli.js',
173
+ },
174
+ scripts: {
175
+ build: 'tsc -p .',
176
+ dev: 'tsx src/cli.ts',
177
+ start: 'node bin/cli.js',
178
+ },
179
+ dependencies: {
180
+ commander: '^12.1.0',
181
+ },
182
+ devDependencies: {
183
+ '@types/node': '^22.0.0',
184
+ tsx: '^4.0.0',
185
+ typescript: '^5.0.0',
186
+ },
187
+ }, null, 2)}\n`,
188
+ },
189
+ {
190
+ path: 'tsconfig.json',
191
+ content: `${JSON.stringify({
192
+ compilerOptions: {
193
+ target: 'ES2022',
194
+ module: 'ES2022',
195
+ moduleResolution: 'Bundler',
196
+ outDir: 'dist',
197
+ rootDir: 'src',
198
+ strict: true,
199
+ esModuleInterop: true,
200
+ skipLibCheck: true,
201
+ },
202
+ include: ['src/**/*.ts'],
203
+ }, null, 2)}\n`,
204
+ },
205
+ {
206
+ path: 'bin/cli.js',
207
+ content: `#!/usr/bin/env node
208
+ import '../dist/cli.js';
209
+ `,
210
+ },
211
+ {
212
+ path: 'src/cli.ts',
213
+ content: `import { Command } from 'commander';
214
+
215
+ const program = new Command();
216
+
217
+ program
218
+ .name('cli-tool')
219
+ .description('A starter CLI built with commander')
220
+ .version('1.3.0');
221
+
222
+ program
223
+ .command('hello')
224
+ .description('Print a greeting')
225
+ .action(() => {
226
+ console.log('Hello from cli-tool');
227
+ });
228
+
229
+ program.parse();
230
+ `,
231
+ },
232
+ {
233
+ path: '.gitignore',
234
+ content: `node_modules
235
+ dist
236
+ `,
237
+ },
238
+ ],
239
+ },
240
+ {
241
+ name: 'react-vite',
242
+ description: 'React + Vite starter scaffold.',
243
+ files: [
244
+ {
245
+ path: 'package.json',
246
+ content: `${JSON.stringify({
247
+ name: 'react-vite-app',
248
+ version: '1.3.0',
249
+ private: true,
250
+ type: 'module',
251
+ scripts: {
252
+ dev: 'vite',
253
+ build: 'tsc -p . && vite build',
254
+ preview: 'vite preview',
255
+ },
256
+ dependencies: {
257
+ react: '^18.3.1',
258
+ 'react-dom': '^18.3.1',
259
+ },
260
+ devDependencies: {
261
+ '@types/react': '^18.3.3',
262
+ '@types/react-dom': '^18.3.0',
263
+ '@vitejs/plugin-react': '^4.3.1',
264
+ typescript: '^5.5.4',
265
+ vite: '^5.4.0',
266
+ },
267
+ }, null, 2)}\n`,
268
+ },
269
+ {
270
+ path: 'tsconfig.json',
271
+ content: `${JSON.stringify({
272
+ compilerOptions: {
273
+ target: 'ES2020',
274
+ module: 'ESNext',
275
+ moduleResolution: 'Bundler',
276
+ jsx: 'react-jsx',
277
+ strict: true,
278
+ skipLibCheck: true,
279
+ noEmit: true,
280
+ },
281
+ include: ['src'],
282
+ }, null, 2)}\n`,
283
+ },
284
+ {
285
+ path: 'vite.config.ts',
286
+ content: `import { defineConfig } from 'vite';
287
+ import react from '@vitejs/plugin-react';
288
+
289
+ export default defineConfig({
290
+ plugins: [react()],
291
+ });
292
+ `,
293
+ },
294
+ {
295
+ path: 'index.html',
296
+ content: `<!doctype html>
297
+ <html lang="en">
298
+ <head>
299
+ <meta charset="UTF-8" />
300
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
301
+ <title>React + Vite</title>
302
+ </head>
303
+ <body>
304
+ <div id="root"></div>
305
+ <script type="module" src="/src/main.tsx"></script>
306
+ </body>
307
+ </html>
308
+ `,
309
+ },
310
+ {
311
+ path: 'src/main.tsx',
312
+ content: `import React from 'react';
313
+ import ReactDOM from 'react-dom/client';
314
+ import App from './App.js';
315
+
316
+ ReactDOM.createRoot(document.getElementById('root')!).render(
317
+ <React.StrictMode>
318
+ <App />
319
+ </React.StrictMode>,
320
+ );
321
+ `,
322
+ },
323
+ {
324
+ path: 'src/App.tsx',
325
+ content: `export default function App(): JSX.Element {
326
+ return (
327
+ <main>
328
+ <h1>React + Vite</h1>
329
+ <p>Start building your app.</p>
330
+ </main>
331
+ );
332
+ }
333
+ `,
334
+ },
335
+ {
336
+ path: '.gitignore',
337
+ content: `node_modules
338
+ dist
339
+ `,
340
+ },
341
+ ],
342
+ },
343
+ {
344
+ name: 'python-fastapi',
345
+ description: 'FastAPI starter scaffold for Python services.',
346
+ files: [
347
+ {
348
+ path: 'main.py',
349
+ content: `from fastapi import FastAPI
350
+
351
+ app = FastAPI()
352
+
353
+
354
+ @app.get("/health")
355
+ def health() -> dict[str, bool]:
356
+ return {"ok": True}
357
+ `,
358
+ },
359
+ {
360
+ path: 'requirements.txt',
361
+ content: `fastapi==0.115.0
362
+ uvicorn[standard]==0.30.6
363
+ `,
364
+ },
365
+ {
366
+ path: '.gitignore',
367
+ content: `__pycache__/
368
+ .venv/
369
+ *.pyc
370
+ `,
371
+ },
372
+ ],
373
+ },
374
+ ];
375
+ export function listTemplates() {
376
+ const lines = BUILT_IN_TEMPLATES.map((template) => ` ${theme.ok(template.name)} ${theme.dim(`- ${template.description}`)}`);
377
+ return `${theme.brand('Available templates')}\n${lines.join('\n')}\n`;
378
+ }
379
+ export function previewTemplate(name) {
380
+ const template = findTemplate(name);
381
+ if (!template) {
382
+ return `${theme.warn(`Unknown template: ${name}`)}\n${theme.dim(`Available templates: ${BUILT_IN_TEMPLATES.map((entry) => entry.name).join(', ')}`)}\n`;
383
+ }
384
+ return `${theme.brand(`Template: ${template.name}`)}\n${theme.dim(template.description)}\n\n${renderFileTree(template.files)}\n`;
385
+ }
386
+ export function templateCommand(args) {
387
+ const name = args.find((arg) => !arg.startsWith('--'));
388
+ const apply = args.includes('--apply');
389
+ if (!name) {
390
+ return listTemplates();
391
+ }
392
+ const template = findTemplate(name);
393
+ if (!template) {
394
+ return previewTemplate(name);
395
+ }
396
+ if (!apply) {
397
+ return `${previewTemplate(name)}\n${theme.dim(`Use /template ${name} --apply to create these files in ${resolveTargetDir()}.`)}\n`;
398
+ }
399
+ const targetDir = resolveTargetDir();
400
+ if (!confirmTemplateApply(template, targetDir)) {
401
+ return `${theme.warn('Template apply cancelled.')}\n`;
402
+ }
403
+ const writtenFiles = applyTemplate(template, targetDir);
404
+ const summary = writtenFiles
405
+ .map((filePath) => ` ${theme.ok('created')} ${theme.hl(filePath)}`)
406
+ .join('\n');
407
+ return `${theme.ok(`Created ${writtenFiles.length} file(s) from ${template.name}.`)}\n${summary}\n`;
408
+ }
409
+ function findTemplate(name) {
410
+ return BUILT_IN_TEMPLATES.find((template) => template.name === name);
411
+ }
412
+ function resolveTargetDir() {
413
+ return path.resolve(config.cwd || process.cwd());
414
+ }
415
+ function confirmTemplateApply(template, targetDir) {
416
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
417
+ return false;
418
+ }
419
+ const existingCount = template.files.filter((file) => fs.existsSync(path.join(targetDir, ...file.path.split('/')))).length;
420
+ const message = `Apply template "${template.name}" in ${targetDir}? This will write ${template.files.length} file(s)${existingCount > 0 ? ` and overwrite ${existingCount} existing file(s)` : ''}. [y/N] `;
421
+ const script = [
422
+ "const readline = require('node:readline');",
423
+ 'const rl = readline.createInterface({ input: process.stdin, output: process.stdout });',
424
+ `rl.question(${JSON.stringify(message)}, (answer) => {`,
425
+ ' rl.close();',
426
+ ' process.exit(/^(y|yes)$/i.test(String(answer).trim()) ? 0 : 1);',
427
+ '});',
428
+ ].join('\n');
429
+ const result = spawnSync(process.execPath, ['-e', script], { stdio: 'inherit' });
430
+ return result.status === 0;
431
+ }
432
+ function applyTemplate(template, targetDir) {
433
+ const writtenFiles = [];
434
+ for (const file of template.files) {
435
+ const filePath = path.join(targetDir, ...file.path.split('/'));
436
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
437
+ fs.writeFileSync(filePath, file.content, 'utf8');
438
+ writtenFiles.push(path.relative(targetDir, filePath) || file.path);
439
+ }
440
+ return writtenFiles;
441
+ }
442
+ function renderFileTree(files) {
443
+ const root = createTreeNode('');
444
+ const sortedPaths = [...files]
445
+ .map((file) => file.path)
446
+ .sort((left, right) => left.localeCompare(right));
447
+ for (const filePath of sortedPaths) {
448
+ let node = root;
449
+ for (const segment of filePath.split('/')) {
450
+ let child = node.children.get(segment);
451
+ if (!child) {
452
+ child = createTreeNode(segment);
453
+ node.children.set(segment, child);
454
+ }
455
+ node = child;
456
+ }
457
+ }
458
+ return renderTreeChildren(root, '');
459
+ }
460
+ function createTreeNode(name) {
461
+ return { name, children: new Map() };
462
+ }
463
+ function renderTreeChildren(node, prefix) {
464
+ const children = [...node.children.values()].sort((left, right) => left.name.localeCompare(right.name));
465
+ return children
466
+ .map((child, index) => {
467
+ const isLast = index === children.length - 1;
468
+ const branch = `${prefix}${isLast ? '└─ ' : '├─ '}`;
469
+ const nextPrefix = `${prefix}${isLast ? ' ' : '│ '}`;
470
+ const label = child.children.size > 0 ? `${child.name}/` : child.name;
471
+ const nested = child.children.size > 0 ? `\n${renderTreeChildren(child, nextPrefix)}` : '';
472
+ return ` ${theme.hl(branch + label)}${nested}`;
473
+ })
474
+ .join('\n');
475
+ }