create-agentlink 0.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/.claude/settings.local.json +19 -0
- package/README.md +42 -0
- package/dist/index.js +397 -0
- package/package.json +25 -0
- package/src/banner.ts +14 -0
- package/src/index.ts +29 -0
- package/src/scaffold.ts +195 -0
- package/src/theme.ts +9 -0
- package/src/utils.ts +90 -0
- package/src/wizard.ts +121 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebSearch",
|
|
5
|
+
"Bash(npm install:*)",
|
|
6
|
+
"Bash(npm run:*)",
|
|
7
|
+
"Bash(node:*)",
|
|
8
|
+
"Bash(npm view:*)",
|
|
9
|
+
"Bash(npx skills:*)",
|
|
10
|
+
"Bash(npx supabase:*)",
|
|
11
|
+
"Bash(npx create-next-app@latest:*)",
|
|
12
|
+
"Bash(npm search:*)",
|
|
13
|
+
"Bash(npm uninstall:*)",
|
|
14
|
+
"Bash(git:*)",
|
|
15
|
+
"Bash(supabase status:*)",
|
|
16
|
+
"Bash(claude plugin:*)"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# agentlink
|
|
2
|
+
|
|
3
|
+
CLI for scaffolding Supabase apps with AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g agentlink
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Run the interactive wizard:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
agentlink init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or pass options directly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
agentlink init --name my-app
|
|
23
|
+
agentlink init --name my-app --skip-skills
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
1. Creates a new project directory
|
|
29
|
+
2. Installs the Supabase CLI (if not already installed)
|
|
30
|
+
3. Installs the Agent Link plugin
|
|
31
|
+
4. Configures Claude Code with MCP server and settings
|
|
32
|
+
5. Installs companion skills (Supabase best practices, frontend design, Vercel/Next.js)
|
|
33
|
+
6. Initializes a git repository
|
|
34
|
+
7. Launches Claude Code with your prompt
|
|
35
|
+
|
|
36
|
+
## Development
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install
|
|
40
|
+
npm run build
|
|
41
|
+
npm run dev # watch mode
|
|
42
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import path3 from "path";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/theme.ts
|
|
8
|
+
var rgb = (r, g, b) => (s) => `\x1B[38;2;${r};${g};${b}m${s}\x1B[0m`;
|
|
9
|
+
var blue = rgb(92, 184, 228);
|
|
10
|
+
var amber = rgb(232, 168, 56);
|
|
11
|
+
var red = rgb(239, 68, 68);
|
|
12
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
13
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
14
|
+
|
|
15
|
+
// src/utils.ts
|
|
16
|
+
import { execSync, spawn } from "child_process";
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
var LOG_FILE = "agentlink-debug.log";
|
|
20
|
+
function appendLog(msg) {
|
|
21
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
22
|
+
fs.appendFileSync(LOG_FILE, `[${timestamp}] ${msg}
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
function initLog() {
|
|
26
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
27
|
+
}
|
|
28
|
+
function runCommand(cmd, cwd) {
|
|
29
|
+
appendLog(`$ ${cmd}${cwd ? ` (in ${cwd})` : ""}`);
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const child = spawn("sh", ["-c", cmd], {
|
|
32
|
+
cwd,
|
|
33
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
34
|
+
});
|
|
35
|
+
const stdout = [];
|
|
36
|
+
const stderr = [];
|
|
37
|
+
child.stdout.on("data", (data) => stdout.push(data.toString()));
|
|
38
|
+
child.stderr.on("data", (data) => stderr.push(data.toString()));
|
|
39
|
+
child.on("close", (code) => {
|
|
40
|
+
const out = stdout.join("").trim();
|
|
41
|
+
const errOut = stderr.join("").trim();
|
|
42
|
+
if (out) appendLog(out);
|
|
43
|
+
if (code !== 0) {
|
|
44
|
+
appendLog(`EXIT CODE ${code}`);
|
|
45
|
+
if (errOut) appendLog(`STDERR: ${errOut}`);
|
|
46
|
+
reject(
|
|
47
|
+
new Error(
|
|
48
|
+
`Command failed: ${cmd}
|
|
49
|
+
See ${path.resolve(LOG_FILE)} for details`
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
resolve(out);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
child.on("error", (err) => {
|
|
57
|
+
appendLog(`SPAWN ERROR: ${err.message}`);
|
|
58
|
+
reject(
|
|
59
|
+
new Error(
|
|
60
|
+
`Command failed: ${cmd}
|
|
61
|
+
See ${path.resolve(LOG_FILE)} for details`
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function delay(ms) {
|
|
68
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
69
|
+
}
|
|
70
|
+
function checkCommand(cmd) {
|
|
71
|
+
try {
|
|
72
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function skillDisplayName(skill) {
|
|
79
|
+
if (skill.includes("@")) return skill.split("@").pop();
|
|
80
|
+
const flag = skill.match(/--skill\s+(\S+)/);
|
|
81
|
+
if (flag) return flag[1];
|
|
82
|
+
return skill.split("/").pop();
|
|
83
|
+
}
|
|
84
|
+
function validateProjectName(name) {
|
|
85
|
+
if (!name) return "Project name is required.";
|
|
86
|
+
if (/\s/.test(name)) return "Project name cannot contain spaces.";
|
|
87
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name))
|
|
88
|
+
return "Project name can only contain letters, numbers, hyphens, dots, and underscores.";
|
|
89
|
+
if (fs.existsSync(name))
|
|
90
|
+
return `Directory "${name}" already exists.`;
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/wizard.ts
|
|
95
|
+
import { execSync as execSync2 } from "child_process";
|
|
96
|
+
import { input, select } from "@inquirer/prompts";
|
|
97
|
+
|
|
98
|
+
// src/banner.ts
|
|
99
|
+
var TITLE = [
|
|
100
|
+
" \u2584\u2580\u2588 \u2588\u2580\u2580 \u2588\u2580\u2580 \u2588\u2584 \u2588 \u2580\u2588\u2580 \u2588 \u2588 \u2588\u2584 \u2588 \u2588\u2584\u2580",
|
|
101
|
+
" \u2588\u2580\u2588 \u2588\u2584\u2588 \u2588\u2588\u2584 \u2588 \u2580\u2588 \u2588 \u2588\u2584\u2584 \u2588 \u2588 \u2580\u2588 \u2588 \u2588"
|
|
102
|
+
].join("\n");
|
|
103
|
+
function printBanner() {
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(bold(blue(TITLE)));
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(dim(" Build Supabase apps with AI agents"));
|
|
108
|
+
console.log();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/scaffold.ts
|
|
112
|
+
import fs2 from "fs";
|
|
113
|
+
import path2 from "path";
|
|
114
|
+
import { Listr } from "listr2";
|
|
115
|
+
var RENDERER_OPTIONS = {
|
|
116
|
+
icon: {
|
|
117
|
+
COMPLETED: blue("\u2714"),
|
|
118
|
+
STARTED: amber("\u25FC"),
|
|
119
|
+
FAILED: red("\u2716"),
|
|
120
|
+
SKIPPED_WITH_COLLAPSE: dim("\u25CB"),
|
|
121
|
+
SKIPPED_WITHOUT_COLLAPSE: dim("\u25CB"),
|
|
122
|
+
WAITING: dim("\u25FB"),
|
|
123
|
+
COMPLETED_WITH_FAILED_SUBTASKS: amber("\u2714"),
|
|
124
|
+
COMPLETED_WITH_SISTER_TASKS_FAILED: blue("\u2714"),
|
|
125
|
+
RETRY: amber("\u21BB"),
|
|
126
|
+
ROLLING_BACK: red("\u21BA"),
|
|
127
|
+
PAUSED: amber("\u2016")
|
|
128
|
+
},
|
|
129
|
+
color: {
|
|
130
|
+
COMPLETED: blue,
|
|
131
|
+
STARTED: amber,
|
|
132
|
+
FAILED: red,
|
|
133
|
+
SKIPPED_WITH_COLLAPSE: dim,
|
|
134
|
+
SKIPPED_WITHOUT_COLLAPSE: dim,
|
|
135
|
+
WAITING: dim,
|
|
136
|
+
COMPLETED_WITH_FAILED_SUBTASKS: amber,
|
|
137
|
+
COMPLETED_WITH_SISTER_TASKS_FAILED: blue,
|
|
138
|
+
RETRY: amber,
|
|
139
|
+
ROLLING_BACK: red,
|
|
140
|
+
PAUSED: amber
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var CLAUDE_SETTINGS = {
|
|
144
|
+
permissions: {
|
|
145
|
+
allow: [
|
|
146
|
+
"Bash(supabase --version)",
|
|
147
|
+
"Bash(supabase status:*)",
|
|
148
|
+
"Bash(supabase init:*)",
|
|
149
|
+
"Bash(supabase start:*)",
|
|
150
|
+
"Bash(supabase stop:*)",
|
|
151
|
+
"Bash(supabase functions:*)",
|
|
152
|
+
"Bash(npx create-next-app@latest:*)",
|
|
153
|
+
"Bash(npx create-vite@latest:*)",
|
|
154
|
+
"Bash(npm install:*)"
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
extraKnownMarketplaces: {
|
|
158
|
+
agentlink: {
|
|
159
|
+
source: {
|
|
160
|
+
source: "github",
|
|
161
|
+
repo: "agentlinksh/agent"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
mcpServers: {
|
|
166
|
+
supabase: {
|
|
167
|
+
type: "http",
|
|
168
|
+
url: "http://127.0.0.1:54321/mcp"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var REQUIRED_SKILL = "supabase/agent-skills@supabase-postgres-best-practices";
|
|
173
|
+
var COMPANION_SKILLS = [
|
|
174
|
+
REQUIRED_SKILL,
|
|
175
|
+
"anthropics/skills@frontend-design",
|
|
176
|
+
"vercel-labs/agent-skills@vercel-react-best-practices",
|
|
177
|
+
"vercel-labs/next-skills --skill next-best-practices",
|
|
178
|
+
"resend/react-email",
|
|
179
|
+
"resend/email-best-practices",
|
|
180
|
+
"resend/resend-skills"
|
|
181
|
+
];
|
|
182
|
+
async function getVersion(cmd) {
|
|
183
|
+
try {
|
|
184
|
+
const out = await runCommand(cmd);
|
|
185
|
+
return out.replace(/^v/, "").split("\n")[0].trim();
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function scaffold(options) {
|
|
191
|
+
const { name, skills } = options;
|
|
192
|
+
const projectDir = path2.resolve(process.cwd(), name);
|
|
193
|
+
const installedSkills = [];
|
|
194
|
+
fs2.mkdirSync(projectDir, { recursive: true });
|
|
195
|
+
const tasks = new Listr(
|
|
196
|
+
[
|
|
197
|
+
{
|
|
198
|
+
title: "Installing Supabase CLI",
|
|
199
|
+
skip: () => checkCommand("supabase") && "Supabase CLI already installed",
|
|
200
|
+
task: () => runCommand("npm install -g supabase")
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
title: "Initializing Supabase",
|
|
204
|
+
task: async () => {
|
|
205
|
+
await runCommand("supabase init", projectDir);
|
|
206
|
+
await delay(500);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
title: "Stopping existing Supabase instances",
|
|
211
|
+
task: async () => {
|
|
212
|
+
await runCommand("supabase stop --all").catch(() => {
|
|
213
|
+
});
|
|
214
|
+
await delay(500);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
title: "Starting Supabase",
|
|
219
|
+
task: () => runCommand("supabase start", projectDir)
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
title: "Configuring Claude Code",
|
|
223
|
+
task: async () => {
|
|
224
|
+
const claudeDir = path2.join(projectDir, ".claude");
|
|
225
|
+
fs2.mkdirSync(claudeDir, { recursive: true });
|
|
226
|
+
fs2.writeFileSync(
|
|
227
|
+
path2.join(claudeDir, "settings.local.json"),
|
|
228
|
+
JSON.stringify(CLAUDE_SETTINGS, null, 2) + "\n"
|
|
229
|
+
);
|
|
230
|
+
await delay(500);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
title: "Installing Agent Link plugin",
|
|
235
|
+
task: () => runCommand("claude plugin install link@agentlink --scope project", projectDir)
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
title: "Installing companion skills",
|
|
239
|
+
skip: () => skills.length === 0 && "No skills selected",
|
|
240
|
+
task: (_, task) => task.newListr(
|
|
241
|
+
skills.map((skill) => {
|
|
242
|
+
const displayName = skillDisplayName(skill);
|
|
243
|
+
return {
|
|
244
|
+
title: displayName,
|
|
245
|
+
task: async () => {
|
|
246
|
+
await runCommand(
|
|
247
|
+
`npx skills add ${skill} -a "claude-code" -y`,
|
|
248
|
+
projectDir
|
|
249
|
+
);
|
|
250
|
+
installedSkills.push(displayName);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}),
|
|
254
|
+
{ concurrent: true }
|
|
255
|
+
)
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
title: "Initializing git",
|
|
259
|
+
skip: () => fs2.existsSync(path2.join(projectDir, ".git")) && "Already initialized",
|
|
260
|
+
task: async () => {
|
|
261
|
+
await runCommand("git init", projectDir);
|
|
262
|
+
await runCommand("git add supabase .claude/settings.json", projectDir);
|
|
263
|
+
await runCommand('git commit -m "Initial commit from agentlink"', projectDir);
|
|
264
|
+
await delay(500);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
{ concurrent: false, rendererOptions: RENDERER_OPTIONS }
|
|
269
|
+
);
|
|
270
|
+
await tasks.run();
|
|
271
|
+
const claudeCodeVersion = await getVersion("claude --version");
|
|
272
|
+
const supabaseVersion = await getVersion("supabase --version");
|
|
273
|
+
return {
|
|
274
|
+
projectDir,
|
|
275
|
+
claudeCodeVersion,
|
|
276
|
+
supabaseVersion,
|
|
277
|
+
skills: installedSkills
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/wizard.ts
|
|
282
|
+
var theme = {
|
|
283
|
+
prefix: { idle: blue("?"), done: blue("\u2714") },
|
|
284
|
+
style: {
|
|
285
|
+
answer: (text) => amber(text),
|
|
286
|
+
message: (text) => bold(text),
|
|
287
|
+
error: (text) => red(`> ${text}`),
|
|
288
|
+
help: (text) => dim(text),
|
|
289
|
+
highlight: (text) => blue(text),
|
|
290
|
+
key: (text) => blue(bold(`<${text}>`))
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
function printSummary(summary) {
|
|
294
|
+
const line = (label, value) => ` ${dim(label)} ${value}`;
|
|
295
|
+
console.log();
|
|
296
|
+
console.log(bold(" Summary"));
|
|
297
|
+
console.log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
298
|
+
console.log(line("Project ", summary.projectDir));
|
|
299
|
+
if (summary.claudeCodeVersion)
|
|
300
|
+
console.log(line("Claude Code", `v${summary.claudeCodeVersion}`));
|
|
301
|
+
if (summary.supabaseVersion)
|
|
302
|
+
console.log(line("Supabase ", `v${summary.supabaseVersion}`));
|
|
303
|
+
if (summary.skills.length > 0)
|
|
304
|
+
console.log(line("Skills ", summary.skills.join(", ")));
|
|
305
|
+
console.log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
306
|
+
console.log();
|
|
307
|
+
}
|
|
308
|
+
async function wizard(initialName, initialPrompt) {
|
|
309
|
+
printBanner();
|
|
310
|
+
if (!checkCommand("claude")) {
|
|
311
|
+
console.log(amber(" Installing Claude Code..."));
|
|
312
|
+
await runCommand("npm install -g @anthropic-ai/claude-code");
|
|
313
|
+
console.log(blue(" \u2714 Claude Code installed."));
|
|
314
|
+
}
|
|
315
|
+
const authStatus = await runCommand("claude auth status").catch(() => "");
|
|
316
|
+
const isLoggedIn = authStatus.includes('"loggedIn": true') || authStatus.includes('"loggedIn":true');
|
|
317
|
+
if (!isLoggedIn) {
|
|
318
|
+
console.log(amber(" Claude Code requires authentication. Opening login..."));
|
|
319
|
+
execSync2("claude auth login", { stdio: "inherit" });
|
|
320
|
+
}
|
|
321
|
+
let name;
|
|
322
|
+
if (initialName) {
|
|
323
|
+
name = initialName;
|
|
324
|
+
} else {
|
|
325
|
+
name = await input({
|
|
326
|
+
message: "Project name",
|
|
327
|
+
default: "my-app",
|
|
328
|
+
theme,
|
|
329
|
+
validate: (val) => validateProjectName(val) ?? true
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
let skills;
|
|
333
|
+
if (initialName && initialPrompt) {
|
|
334
|
+
skills = COMPANION_SKILLS;
|
|
335
|
+
} else {
|
|
336
|
+
const skillChoice = await select({
|
|
337
|
+
message: "Install companion skills?",
|
|
338
|
+
theme,
|
|
339
|
+
choices: [
|
|
340
|
+
{
|
|
341
|
+
name: "All recommended",
|
|
342
|
+
value: "all",
|
|
343
|
+
description: COMPANION_SKILLS.map(skillDisplayName).join(", ")
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "Required only",
|
|
347
|
+
value: "required",
|
|
348
|
+
description: skillDisplayName(REQUIRED_SKILL)
|
|
349
|
+
},
|
|
350
|
+
{ name: "Skip", value: "skip" }
|
|
351
|
+
]
|
|
352
|
+
});
|
|
353
|
+
skills = [];
|
|
354
|
+
if (skillChoice === "all") {
|
|
355
|
+
skills = COMPANION_SKILLS;
|
|
356
|
+
} else if (skillChoice === "required") {
|
|
357
|
+
skills = [REQUIRED_SKILL];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
let prompt;
|
|
361
|
+
if (initialPrompt) {
|
|
362
|
+
prompt = initialPrompt;
|
|
363
|
+
} else {
|
|
364
|
+
prompt = await input({
|
|
365
|
+
message: "What do you want to build?",
|
|
366
|
+
default: "A todo app with auth and real-time updates",
|
|
367
|
+
theme
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const summary = await scaffold({ name, skills });
|
|
371
|
+
printSummary(summary);
|
|
372
|
+
console.log(blue(" Launching Claude Code..."));
|
|
373
|
+
console.log();
|
|
374
|
+
execSync2(`claude ${JSON.stringify(prompt)}`, {
|
|
375
|
+
cwd: summary.projectDir,
|
|
376
|
+
stdio: "inherit"
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/index.ts
|
|
381
|
+
var LOG_FILE2 = "agentlink-debug.log";
|
|
382
|
+
var program = new Command();
|
|
383
|
+
program.name("agentlink").description("CLI for scaffolding Supabase apps with AI agents").version("0.1.0").argument("[name]", "Project name").argument("[prompt]", "What do you want to build?").action(async (name, prompt) => {
|
|
384
|
+
initLog();
|
|
385
|
+
try {
|
|
386
|
+
await wizard(name, prompt);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error(
|
|
389
|
+
`
|
|
390
|
+
${red("Error:")} ${err.message}
|
|
391
|
+
|
|
392
|
+
Debug log: ${dim(path3.resolve(LOG_FILE2))}`
|
|
393
|
+
);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-agentlink",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI for scaffolding Supabase apps with AI agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"agentlink": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsup --watch"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@inquirer/prompts": "^8.3.0",
|
|
15
|
+
"commander": "^13",
|
|
16
|
+
"listr2": "^10.1.1",
|
|
17
|
+
"skills": "^1.4.3",
|
|
18
|
+
"supabase": "^2.76.15"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22",
|
|
22
|
+
"tsup": "^8",
|
|
23
|
+
"typescript": "^5"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/banner.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { blue, bold, dim } from "./theme.js";
|
|
2
|
+
|
|
3
|
+
const TITLE = [
|
|
4
|
+
" ▄▀█ █▀▀ █▀▀ █▄ █ ▀█▀ █ █ █▄ █ █▄▀",
|
|
5
|
+
" █▀█ █▄█ ██▄ █ ▀█ █ █▄▄ █ █ ▀█ █ █",
|
|
6
|
+
].join("\n");
|
|
7
|
+
|
|
8
|
+
export function printBanner() {
|
|
9
|
+
console.log();
|
|
10
|
+
console.log(bold(blue(TITLE)));
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(dim(" Build Supabase apps with AI agents"));
|
|
13
|
+
console.log();
|
|
14
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { dim, red } from "./theme.js";
|
|
4
|
+
import { initLog } from "./utils.js";
|
|
5
|
+
import { wizard } from "./wizard.js";
|
|
6
|
+
|
|
7
|
+
const LOG_FILE = "agentlink-debug.log";
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name("agentlink")
|
|
13
|
+
.description("CLI for scaffolding Supabase apps with AI agents")
|
|
14
|
+
.version("0.1.0")
|
|
15
|
+
.argument("[name]", "Project name")
|
|
16
|
+
.argument("[prompt]", "What do you want to build?")
|
|
17
|
+
.action(async (name?: string, prompt?: string) => {
|
|
18
|
+
initLog();
|
|
19
|
+
try {
|
|
20
|
+
await wizard(name, prompt);
|
|
21
|
+
} catch (err: any) {
|
|
22
|
+
console.error(
|
|
23
|
+
`\n${red("Error:")} ${err.message}\n\nDebug log: ${dim(path.resolve(LOG_FILE))}`,
|
|
24
|
+
);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program.parse();
|
package/src/scaffold.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Listr } from "listr2";
|
|
4
|
+
import { amber, blue, dim, red } from "./theme.js";
|
|
5
|
+
import { checkCommand, delay, runCommand, skillDisplayName } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
const RENDERER_OPTIONS = {
|
|
8
|
+
icon: {
|
|
9
|
+
COMPLETED: blue("✔"),
|
|
10
|
+
STARTED: amber("◼"),
|
|
11
|
+
FAILED: red("✖"),
|
|
12
|
+
SKIPPED_WITH_COLLAPSE: dim("○"),
|
|
13
|
+
SKIPPED_WITHOUT_COLLAPSE: dim("○"),
|
|
14
|
+
WAITING: dim("◻"),
|
|
15
|
+
COMPLETED_WITH_FAILED_SUBTASKS: amber("✔"),
|
|
16
|
+
COMPLETED_WITH_SISTER_TASKS_FAILED: blue("✔"),
|
|
17
|
+
RETRY: amber("↻"),
|
|
18
|
+
ROLLING_BACK: red("↺"),
|
|
19
|
+
PAUSED: amber("‖"),
|
|
20
|
+
},
|
|
21
|
+
color: {
|
|
22
|
+
COMPLETED: blue,
|
|
23
|
+
STARTED: amber,
|
|
24
|
+
FAILED: red,
|
|
25
|
+
SKIPPED_WITH_COLLAPSE: dim,
|
|
26
|
+
SKIPPED_WITHOUT_COLLAPSE: dim,
|
|
27
|
+
WAITING: dim,
|
|
28
|
+
COMPLETED_WITH_FAILED_SUBTASKS: amber,
|
|
29
|
+
COMPLETED_WITH_SISTER_TASKS_FAILED: blue,
|
|
30
|
+
RETRY: amber,
|
|
31
|
+
ROLLING_BACK: red,
|
|
32
|
+
PAUSED: amber,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const CLAUDE_SETTINGS = {
|
|
37
|
+
permissions: {
|
|
38
|
+
allow: [
|
|
39
|
+
"Bash(supabase --version)",
|
|
40
|
+
"Bash(supabase status:*)",
|
|
41
|
+
"Bash(supabase init:*)",
|
|
42
|
+
"Bash(supabase start:*)",
|
|
43
|
+
"Bash(supabase stop:*)",
|
|
44
|
+
"Bash(supabase functions:*)",
|
|
45
|
+
"Bash(npx create-next-app@latest:*)",
|
|
46
|
+
"Bash(npx create-vite@latest:*)",
|
|
47
|
+
"Bash(npm install:*)",
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
extraKnownMarketplaces: {
|
|
51
|
+
agentlink: {
|
|
52
|
+
source: {
|
|
53
|
+
source: "github",
|
|
54
|
+
repo: "agentlinksh/agent",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
mcpServers: {
|
|
59
|
+
supabase: {
|
|
60
|
+
type: "http",
|
|
61
|
+
url: "http://127.0.0.1:54321/mcp",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const REQUIRED_SKILL =
|
|
67
|
+
"supabase/agent-skills@supabase-postgres-best-practices";
|
|
68
|
+
|
|
69
|
+
export const COMPANION_SKILLS = [
|
|
70
|
+
REQUIRED_SKILL,
|
|
71
|
+
"anthropics/skills@frontend-design",
|
|
72
|
+
"vercel-labs/agent-skills@vercel-react-best-practices",
|
|
73
|
+
"vercel-labs/next-skills --skill next-best-practices",
|
|
74
|
+
"resend/react-email",
|
|
75
|
+
"resend/email-best-practices",
|
|
76
|
+
"resend/resend-skills",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
export interface ScaffoldOptions {
|
|
80
|
+
name: string;
|
|
81
|
+
skills: string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ScaffoldSummary {
|
|
85
|
+
projectDir: string;
|
|
86
|
+
claudeCodeVersion: string | null;
|
|
87
|
+
supabaseVersion: string | null;
|
|
88
|
+
skills: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function getVersion(cmd: string): Promise<string | null> {
|
|
92
|
+
try {
|
|
93
|
+
const out = await runCommand(cmd);
|
|
94
|
+
return out.replace(/^v/, "").split("\n")[0].trim();
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function scaffold(options: ScaffoldOptions): Promise<ScaffoldSummary> {
|
|
101
|
+
const { name, skills } = options;
|
|
102
|
+
const projectDir = path.resolve(process.cwd(), name);
|
|
103
|
+
const installedSkills: string[] = [];
|
|
104
|
+
|
|
105
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
106
|
+
|
|
107
|
+
const tasks = new Listr(
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
title: "Installing Supabase CLI",
|
|
111
|
+
skip: () => checkCommand("supabase") && "Supabase CLI already installed",
|
|
112
|
+
task: () => runCommand("npm install -g supabase"),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
title: "Initializing Supabase",
|
|
116
|
+
task: async () => {
|
|
117
|
+
await runCommand("supabase init", projectDir);
|
|
118
|
+
await delay(500);
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
title: "Stopping existing Supabase instances",
|
|
123
|
+
task: async () => {
|
|
124
|
+
await runCommand("supabase stop --all").catch(() => {});
|
|
125
|
+
await delay(500);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: "Starting Supabase",
|
|
130
|
+
task: () => runCommand("supabase start", projectDir),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: "Configuring Claude Code",
|
|
134
|
+
task: async () => {
|
|
135
|
+
const claudeDir = path.join(projectDir, ".claude");
|
|
136
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
137
|
+
fs.writeFileSync(
|
|
138
|
+
path.join(claudeDir, "settings.local.json"),
|
|
139
|
+
JSON.stringify(CLAUDE_SETTINGS, null, 2) + "\n",
|
|
140
|
+
);
|
|
141
|
+
await delay(500);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
title: "Installing Agent Link plugin",
|
|
146
|
+
task: () =>
|
|
147
|
+
runCommand("claude plugin install link@agentlink --scope project", projectDir),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
title: "Installing companion skills",
|
|
151
|
+
skip: () => skills.length === 0 && "No skills selected",
|
|
152
|
+
task: (_, task) =>
|
|
153
|
+
task.newListr(
|
|
154
|
+
skills.map((skill) => {
|
|
155
|
+
const displayName = skillDisplayName(skill);
|
|
156
|
+
return {
|
|
157
|
+
title: displayName,
|
|
158
|
+
task: async () => {
|
|
159
|
+
await runCommand(
|
|
160
|
+
`npx skills add ${skill} -a "claude-code" -y`,
|
|
161
|
+
projectDir,
|
|
162
|
+
);
|
|
163
|
+
installedSkills.push(displayName);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}),
|
|
167
|
+
{ concurrent: true },
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Initializing git",
|
|
172
|
+
skip: () => fs.existsSync(path.join(projectDir, ".git")) && "Already initialized",
|
|
173
|
+
task: async () => {
|
|
174
|
+
await runCommand("git init", projectDir);
|
|
175
|
+
await runCommand("git add supabase .claude/settings.json", projectDir);
|
|
176
|
+
await runCommand('git commit -m "Initial commit from agentlink"', projectDir);
|
|
177
|
+
await delay(500);
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
{ concurrent: false, rendererOptions: RENDERER_OPTIONS },
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await tasks.run();
|
|
185
|
+
|
|
186
|
+
const claudeCodeVersion = await getVersion("claude --version");
|
|
187
|
+
const supabaseVersion = await getVersion("supabase --version");
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
projectDir,
|
|
191
|
+
claudeCodeVersion,
|
|
192
|
+
supabaseVersion,
|
|
193
|
+
skills: installedSkills,
|
|
194
|
+
};
|
|
195
|
+
}
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Brand colors via truecolor ANSI escapes
|
|
2
|
+
const rgb = (r: number, g: number, b: number) => (s: string) =>
|
|
3
|
+
`\x1b[38;2;${r};${g};${b}m${s}\x1b[0m`;
|
|
4
|
+
|
|
5
|
+
export const blue = rgb(92, 184, 228); // #5cb8e4 — Sheikah Blue
|
|
6
|
+
export const amber = rgb(232, 168, 56); // #e8a838 — Accent
|
|
7
|
+
export const red = rgb(239, 68, 68); // #ef4444 — Destructive
|
|
8
|
+
export const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
|
|
9
|
+
export const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const LOG_FILE = "agentlink-debug.log";
|
|
6
|
+
|
|
7
|
+
function appendLog(msg: string) {
|
|
8
|
+
const timestamp = new Date().toISOString();
|
|
9
|
+
fs.appendFileSync(LOG_FILE, `[${timestamp}] ${msg}\n`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function initLog() {
|
|
13
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function runCommand(cmd: string, cwd?: string): Promise<string> {
|
|
17
|
+
appendLog(`$ ${cmd}${cwd ? ` (in ${cwd})` : ""}`);
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const child = spawn("sh", ["-c", cmd], {
|
|
20
|
+
cwd,
|
|
21
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const stdout: string[] = [];
|
|
25
|
+
const stderr: string[] = [];
|
|
26
|
+
|
|
27
|
+
child.stdout.on("data", (data) => stdout.push(data.toString()));
|
|
28
|
+
child.stderr.on("data", (data) => stderr.push(data.toString()));
|
|
29
|
+
|
|
30
|
+
child.on("close", (code) => {
|
|
31
|
+
const out = stdout.join("").trim();
|
|
32
|
+
const errOut = stderr.join("").trim();
|
|
33
|
+
if (out) appendLog(out);
|
|
34
|
+
|
|
35
|
+
if (code !== 0) {
|
|
36
|
+
appendLog(`EXIT CODE ${code}`);
|
|
37
|
+
if (errOut) appendLog(`STDERR: ${errOut}`);
|
|
38
|
+
reject(
|
|
39
|
+
new Error(
|
|
40
|
+
`Command failed: ${cmd}\nSee ${path.resolve(LOG_FILE)} for details`,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
resolve(out);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
child.on("error", (err) => {
|
|
49
|
+
appendLog(`SPAWN ERROR: ${err.message}`);
|
|
50
|
+
reject(
|
|
51
|
+
new Error(
|
|
52
|
+
`Command failed: ${cmd}\nSee ${path.resolve(LOG_FILE)} for details`,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function delay(ms: number): Promise<void> {
|
|
60
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function checkCommand(cmd: string): boolean {
|
|
64
|
+
try {
|
|
65
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
66
|
+
return true;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function skillDisplayName(skill: string): string {
|
|
73
|
+
// "org/repo@skill-name" → "skill-name"
|
|
74
|
+
if (skill.includes("@")) return skill.split("@").pop()!;
|
|
75
|
+
// "org/repo --skill skill-name" → "skill-name"
|
|
76
|
+
const flag = skill.match(/--skill\s+(\S+)/);
|
|
77
|
+
if (flag) return flag[1];
|
|
78
|
+
// fallback: "org/repo" → "repo"
|
|
79
|
+
return skill.split("/").pop()!;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function validateProjectName(name: string): string | undefined {
|
|
83
|
+
if (!name) return "Project name is required.";
|
|
84
|
+
if (/\s/.test(name)) return "Project name cannot contain spaces.";
|
|
85
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name))
|
|
86
|
+
return "Project name can only contain letters, numbers, hyphens, dots, and underscores.";
|
|
87
|
+
if (fs.existsSync(name))
|
|
88
|
+
return `Directory "${name}" already exists.`;
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
package/src/wizard.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { input, select } from "@inquirer/prompts";
|
|
3
|
+
import { printBanner } from "./banner.js";
|
|
4
|
+
import { COMPANION_SKILLS, REQUIRED_SKILL, scaffold, ScaffoldSummary } from "./scaffold.js";
|
|
5
|
+
import { amber, blue, bold, dim, red } from "./theme.js";
|
|
6
|
+
import { checkCommand, runCommand, skillDisplayName, validateProjectName } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
const theme = {
|
|
9
|
+
prefix: { idle: blue("?"), done: blue("✔") },
|
|
10
|
+
style: {
|
|
11
|
+
answer: (text: string) => amber(text),
|
|
12
|
+
message: (text: string) => bold(text),
|
|
13
|
+
error: (text: string) => red(`> ${text}`),
|
|
14
|
+
help: (text: string) => dim(text),
|
|
15
|
+
highlight: (text: string) => blue(text),
|
|
16
|
+
key: (text: string) => blue(bold(`<${text}>`)),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function printSummary(summary: ScaffoldSummary) {
|
|
21
|
+
const line = (label: string, value: string) =>
|
|
22
|
+
` ${dim(label)} ${value}`;
|
|
23
|
+
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(bold(" Summary"));
|
|
26
|
+
console.log(dim(" ───────────────────────────────────"));
|
|
27
|
+
console.log(line("Project ", summary.projectDir));
|
|
28
|
+
if (summary.claudeCodeVersion)
|
|
29
|
+
console.log(line("Claude Code", `v${summary.claudeCodeVersion}`));
|
|
30
|
+
if (summary.supabaseVersion)
|
|
31
|
+
console.log(line("Supabase ", `v${summary.supabaseVersion}`));
|
|
32
|
+
if (summary.skills.length > 0)
|
|
33
|
+
console.log(line("Skills ", summary.skills.join(", ")));
|
|
34
|
+
console.log(dim(" ───────────────────────────────────"));
|
|
35
|
+
console.log();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function wizard(initialName?: string, initialPrompt?: string) {
|
|
39
|
+
printBanner();
|
|
40
|
+
|
|
41
|
+
// Ensure Claude Code is installed
|
|
42
|
+
if (!checkCommand("claude")) {
|
|
43
|
+
console.log(amber(" Installing Claude Code..."));
|
|
44
|
+
await runCommand("npm install -g @anthropic-ai/claude-code");
|
|
45
|
+
console.log(blue(" ✔ Claude Code installed."));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure Claude Code is authenticated
|
|
49
|
+
const authStatus = await runCommand("claude auth status").catch(() => "");
|
|
50
|
+
const isLoggedIn = authStatus.includes('"loggedIn": true') || authStatus.includes('"loggedIn":true');
|
|
51
|
+
if (!isLoggedIn) {
|
|
52
|
+
console.log(amber(" Claude Code requires authentication. Opening login..."));
|
|
53
|
+
execSync("claude auth login", { stdio: "inherit" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let name: string;
|
|
57
|
+
if (initialName) {
|
|
58
|
+
name = initialName;
|
|
59
|
+
} else {
|
|
60
|
+
name = await input({
|
|
61
|
+
message: "Project name",
|
|
62
|
+
default: "my-app",
|
|
63
|
+
theme,
|
|
64
|
+
validate: (val) => validateProjectName(val) ?? true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let skills: string[];
|
|
69
|
+
if (initialName && initialPrompt) {
|
|
70
|
+
// Non-interactive: use all recommended skills
|
|
71
|
+
skills = COMPANION_SKILLS;
|
|
72
|
+
} else {
|
|
73
|
+
const skillChoice = await select({
|
|
74
|
+
message: "Install companion skills?",
|
|
75
|
+
theme,
|
|
76
|
+
choices: [
|
|
77
|
+
{
|
|
78
|
+
name: "All recommended",
|
|
79
|
+
value: "all" as const,
|
|
80
|
+
description: COMPANION_SKILLS.map(skillDisplayName).join(", "),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "Required only",
|
|
84
|
+
value: "required" as const,
|
|
85
|
+
description: skillDisplayName(REQUIRED_SKILL),
|
|
86
|
+
},
|
|
87
|
+
{ name: "Skip", value: "skip" as const },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
skills = [];
|
|
92
|
+
if (skillChoice === "all") {
|
|
93
|
+
skills = COMPANION_SKILLS;
|
|
94
|
+
} else if (skillChoice === "required") {
|
|
95
|
+
skills = [REQUIRED_SKILL];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let prompt: string;
|
|
100
|
+
if (initialPrompt) {
|
|
101
|
+
prompt = initialPrompt;
|
|
102
|
+
} else {
|
|
103
|
+
prompt = await input({
|
|
104
|
+
message: "What do you want to build?",
|
|
105
|
+
default: "A todo app with auth and real-time updates",
|
|
106
|
+
theme,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const summary = await scaffold({ name, skills });
|
|
111
|
+
|
|
112
|
+
printSummary(summary);
|
|
113
|
+
console.log(blue(" Launching Claude Code..."));
|
|
114
|
+
console.log();
|
|
115
|
+
|
|
116
|
+
// Hand off to Claude Code in the project directory
|
|
117
|
+
execSync(`claude ${JSON.stringify(prompt)}`, {
|
|
118
|
+
cwd: summary.projectDir,
|
|
119
|
+
stdio: "inherit",
|
|
120
|
+
});
|
|
121
|
+
}
|
package/tsconfig.json
ADDED