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.
- package/CHANGELOG.md +250 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/icopilot.js +6 -0
- package/dist/acp/router.js +123 -0
- package/dist/acp/schema.js +53 -0
- package/dist/agents/aggregator.js +187 -0
- package/dist/agents/custom-agents.js +97 -0
- package/dist/agents/goal-driven.js +411 -0
- package/dist/agents/multi-repo.js +350 -0
- package/dist/agents/parallel-runner.js +181 -0
- package/dist/agents/router.js +144 -0
- package/dist/agents/self-heal.js +481 -0
- package/dist/agents/tdd-agent.js +278 -0
- package/dist/api/github-models.js +158 -0
- package/dist/bridge/ide-bridge.js +479 -0
- package/dist/cloud/routine-executor.js +34 -0
- package/dist/cloud/routine-scheduler.js +67 -0
- package/dist/cloud/routine-storage.js +297 -0
- package/dist/commands/acp-cmd.js +143 -0
- package/dist/commands/actions-cmd.js +624 -0
- package/dist/commands/agent-cmd.js +144 -0
- package/dist/commands/alias-cmd.js +132 -0
- package/dist/commands/bookmark-cmd.js +77 -0
- package/dist/commands/changelog-cmd.js +99 -0
- package/dist/commands/changes-cmd.js +120 -0
- package/dist/commands/clipboard-cmd.js +217 -0
- package/dist/commands/cloud-routine-cmd.js +265 -0
- package/dist/commands/codegen-cmd.js +544 -0
- package/dist/commands/compare-cmd.js +116 -0
- package/dist/commands/context-cmd.js +247 -0
- package/dist/commands/context-viz-cmd.js +43 -0
- package/dist/commands/conventions-cmd.js +116 -0
- package/dist/commands/cost-cmd.js +51 -0
- package/dist/commands/deps-cmd.js +294 -0
- package/dist/commands/diagram-cmd.js +658 -0
- package/dist/commands/diff-review-cmd.js +92 -0
- package/dist/commands/doc-cmd.js +412 -0
- package/dist/commands/doctor-cmd.js +152 -0
- package/dist/commands/editor-cmd.js +49 -0
- package/dist/commands/env-cmd.js +86 -0
- package/dist/commands/explain-cmd.js +78 -0
- package/dist/commands/explain-shell-cmd.js +22 -0
- package/dist/commands/explore-cmd.js +231 -0
- package/dist/commands/feedback-cmd.js +98 -0
- package/dist/commands/fix-cmd.js +17 -0
- package/dist/commands/generate-cmd.js +38 -0
- package/dist/commands/git-extra.js +197 -0
- package/dist/commands/git-log-cmd.js +98 -0
- package/dist/commands/git-undo-cmd.js +137 -0
- package/dist/commands/git.js +155 -0
- package/dist/commands/history-cmd.js +122 -0
- package/dist/commands/index-cmd.js +65 -0
- package/dist/commands/init-cmd.js +73 -0
- package/dist/commands/lint-cmd.js +133 -0
- package/dist/commands/memory-cmd.js +98 -0
- package/dist/commands/metrics-cmd.js +97 -0
- package/dist/commands/mode-prefix.js +30 -0
- package/dist/commands/multi-cmd.js +44 -0
- package/dist/commands/notify-cmd.js +204 -0
- package/dist/commands/profile-cmd.js +101 -0
- package/dist/commands/prompts.js +17 -0
- package/dist/commands/rag-cmd.js +60 -0
- package/dist/commands/readme-cmd.js +564 -0
- package/dist/commands/reasoning-cmd.js +34 -0
- package/dist/commands/refactor-cmd.js +96 -0
- package/dist/commands/release-cmd.js +450 -0
- package/dist/commands/repo-cmd.js +195 -0
- package/dist/commands/route-cmd.js +21 -0
- package/dist/commands/schedule-cmd.js +109 -0
- package/dist/commands/search-cmd.js +47 -0
- package/dist/commands/security-cmd.js +156 -0
- package/dist/commands/settings-cmd.js +238 -0
- package/dist/commands/skill-cmd.js +338 -0
- package/dist/commands/slash.js +2721 -0
- package/dist/commands/snippets-cmd.js +83 -0
- package/dist/commands/space-cmd.js +92 -0
- package/dist/commands/stash-cmd.js +156 -0
- package/dist/commands/stats-cmd.js +36 -0
- package/dist/commands/style-cmd.js +85 -0
- package/dist/commands/suggest-cmd.js +40 -0
- package/dist/commands/summary-cmd.js +138 -0
- package/dist/commands/task-cmd.js +58 -0
- package/dist/commands/team-memory-cmd.js +97 -0
- package/dist/commands/template-cmd.js +475 -0
- package/dist/commands/test-cmd.js +146 -0
- package/dist/commands/todo-cmd.js +172 -0
- package/dist/commands/tokens-cmd.js +277 -0
- package/dist/commands/trigger-cmd.js +147 -0
- package/dist/commands/undo-cmd.js +18 -0
- package/dist/commands/voice-cmd.js +89 -0
- package/dist/commands/watch-cmd.js +110 -0
- package/dist/commands/web-cmd.js +183 -0
- package/dist/commands/worktree-cmd.js +119 -0
- package/dist/config-profile.js +66 -0
- package/dist/config.js +288 -0
- package/dist/context/compactor.js +53 -0
- package/dist/context/dep-context.js +329 -0
- package/dist/context/file-refs.js +54 -0
- package/dist/context/git-context.js +229 -0
- package/dist/context/image-input.js +66 -0
- package/dist/context/memory.js +55 -0
- package/dist/context/persistent-memory.js +104 -0
- package/dist/context/pinned.js +96 -0
- package/dist/context/priority.js +150 -0
- package/dist/context/read-only.js +48 -0
- package/dist/context/smart-files.js +286 -0
- package/dist/context/team-memory.js +156 -0
- package/dist/extensions/loader.js +149 -0
- package/dist/extensions/marketplace.js +49 -0
- package/dist/extensions/slack-provider.js +181 -0
- package/dist/extensions/team.js +56 -0
- package/dist/extensions/teams-provider.js +222 -0
- package/dist/extensions/voice.js +18 -0
- package/dist/hooks/lifecycle.js +215 -0
- package/dist/hooks/precommit.js +463 -0
- package/dist/index/embeddings.js +23 -0
- package/dist/index/indexer.js +86 -0
- package/dist/index/retrieve.js +20 -0
- package/dist/index/store.js +95 -0
- package/dist/index.js +286 -0
- package/dist/intelligence/dead-code.js +457 -0
- package/dist/intelligence/error-watch.js +263 -0
- package/dist/intelligence/navigation.js +141 -0
- package/dist/intelligence/stack-trace.js +210 -0
- package/dist/intelligence/symbol-index.js +410 -0
- package/dist/knowledge/auto-memory.js +412 -0
- package/dist/knowledge/conventions.js +475 -0
- package/dist/knowledge/corrections.js +213 -0
- package/dist/knowledge/rag.js +450 -0
- package/dist/knowledge/style-learner.js +324 -0
- package/dist/logger.js +35 -0
- package/dist/mcp/client.js +144 -0
- package/dist/mcp/config.js +24 -0
- package/dist/mcp/index.js +89 -0
- package/dist/modes/auto-compact.js +20 -0
- package/dist/modes/autopilot.js +157 -0
- package/dist/modes/background.js +82 -0
- package/dist/modes/interactive.js +187 -0
- package/dist/modes/oneshot.js +36 -0
- package/dist/modes/tui.js +265 -0
- package/dist/modes/turn.js +342 -0
- package/dist/notifications/manager.js +107 -0
- package/dist/plugins/marketplace.js +244 -0
- package/dist/providers/custom-provider.js +298 -0
- package/dist/providers/local-model.js +121 -0
- package/dist/routing/profiles.js +44 -0
- package/dist/routing/router.js +18 -0
- package/dist/sandbox/container.js +151 -0
- package/dist/security/audit.js +237 -0
- package/dist/security/content-filter.js +449 -0
- package/dist/security/proxy.js +301 -0
- package/dist/security/retention.js +281 -0
- package/dist/security/roles.js +252 -0
- package/dist/server/api-server.js +679 -0
- package/dist/session/bookmarks.js +72 -0
- package/dist/session/cloud-session.js +291 -0
- package/dist/session/handoff.js +405 -0
- package/dist/session/manager.js +35 -0
- package/dist/session/session.js +296 -0
- package/dist/session/share.js +313 -0
- package/dist/session/undo-journal.js +91 -0
- package/dist/snippets/store.js +60 -0
- package/dist/spaces/space-config.js +156 -0
- package/dist/spaces/space.js +220 -0
- package/dist/stats/store.js +101 -0
- package/dist/tools/apply-patch.js +134 -0
- package/dist/tools/auto-check.js +218 -0
- package/dist/tools/diff-edit.js +150 -0
- package/dist/tools/diff-prompt.js +36 -0
- package/dist/tools/edit-file.js +66 -0
- package/dist/tools/file-ops.js +205 -0
- package/dist/tools/glob.js +17 -0
- package/dist/tools/grep.js +56 -0
- package/dist/tools/image.js +194 -0
- package/dist/tools/list-directory.js +228 -0
- package/dist/tools/memory.js +17 -0
- package/dist/tools/multi-edit.js +299 -0
- package/dist/tools/policy.js +95 -0
- package/dist/tools/registry.js +484 -0
- package/dist/tools/retry.js +74 -0
- package/dist/tools/run-in-terminal.js +162 -0
- package/dist/tools/safety.js +64 -0
- package/dist/tools/sandbox.js +15 -0
- package/dist/tools/search-symbols.js +212 -0
- package/dist/tools/shell.js +118 -0
- package/dist/tools/web.js +167 -0
- package/dist/ui/prompt.js +37 -0
- package/dist/ui/render.js +96 -0
- package/dist/ui/screen.js +13 -0
- package/dist/ui/theme.js +56 -0
- package/dist/util/browser.js +34 -0
- package/dist/util/completion.js +350 -0
- package/dist/util/cost.js +28 -0
- package/dist/util/keybindings.js +113 -0
- package/dist/util/lazy.js +26 -0
- package/dist/util/perf.js +25 -0
- package/dist/util/token-worker.js +11 -0
- package/dist/util/tokens.js +50 -0
- package/dist/workflows/builtins.js +128 -0
- package/dist/workflows/engine.js +496 -0
- package/dist/workflows/file-trigger.js +197 -0
- 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
|
+
}
|