farmwork 1.0.0 โ†’ 1.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.
package/src/init.js CHANGED
@@ -1,15 +1,12 @@
1
1
  import inquirer from "inquirer";
2
- import chalk from "chalk";
3
- import ora from "ora";
4
2
  import fs from "fs-extra";
5
3
  import path from "path";
6
4
  import { fileURLToPath } from "url";
5
+ import { farmTerm, emojis } from "./terminal.js";
7
6
 
8
7
  const __filename = fileURLToPath(import.meta.url);
9
8
  const __dirname = path.dirname(__filename);
10
- const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
11
9
 
12
- // Try to detect package.json info
13
10
  function detectPackageJson() {
14
11
  try {
15
12
  const pkgPath = path.join(process.cwd(), "package.json");
@@ -32,19 +29,19 @@ const QUESTIONS = [
32
29
  {
33
30
  type: "input",
34
31
  name: "projectName",
35
- message: "Project name:",
32
+ message: "๐ŸŒฑ Project name:",
36
33
  default: path.basename(process.cwd()),
37
34
  },
38
35
  {
39
36
  type: "list",
40
37
  name: "packageManager",
41
- message: "Package manager:",
38
+ message: "๐Ÿงบ Package manager:",
42
39
  choices: ["npm", "yarn", "pnpm", "bun"],
43
40
  },
44
41
  {
45
42
  type: "input",
46
43
  name: "testCommand",
47
- message: "Test command:",
44
+ message: "๐Ÿฅ’ Test command:",
48
45
  default: (answers) => {
49
46
  const { scripts } = detectPackageJson();
50
47
  const pm = answers.packageManager;
@@ -56,7 +53,7 @@ const QUESTIONS = [
56
53
  {
57
54
  type: "input",
58
55
  name: "buildCommand",
59
- message: "Build command:",
56
+ message: "๐ŸŒฝ Build command:",
60
57
  default: (answers) => {
61
58
  const { scripts } = detectPackageJson();
62
59
  const pm = answers.packageManager;
@@ -67,7 +64,7 @@ const QUESTIONS = [
67
64
  {
68
65
  type: "input",
69
66
  name: "lintCommand",
70
- message: "Lint command:",
67
+ message: "๐Ÿฆ‰ Lint command:",
71
68
  default: (answers) => {
72
69
  const { scripts } = detectPackageJson();
73
70
  const pm = answers.packageManager;
@@ -78,13 +75,13 @@ const QUESTIONS = [
78
75
  {
79
76
  type: "confirm",
80
77
  name: "includeStorybook",
81
- message: "Include Storybook support? (React/Vue component docs)",
78
+ message: "๐Ÿ„ Include Storybook support?",
82
79
  default: () => detectPackageJson().hasStorybook,
83
80
  },
84
81
  {
85
82
  type: "confirm",
86
83
  name: "includeI18n",
87
- message: "Include i18n support? (multi-language translations)",
84
+ message: "๐ŸŒป Include i18n support?",
88
85
  default: false,
89
86
  },
90
87
  ];
@@ -93,27 +90,27 @@ const STORYBOOK_QUESTIONS = [
93
90
  {
94
91
  type: "input",
95
92
  name: "storybookUrl",
96
- message: "Storybook URL (e.g., storybook.yoursite.com):",
93
+ message: "๐ŸŒฟ Storybook URL:",
97
94
  default: "storybook.example.com",
98
95
  },
99
96
  {
100
97
  type: "input",
101
98
  name: "netlifyAuthToken",
102
- message: "Netlify Auth Token (from netlify.com/user/applications):",
99
+ message: "๐Ÿ—๏ธ Netlify Auth Token:",
103
100
  validate: (input) =>
104
101
  input.length > 0 || "Auth token is required for deployment",
105
102
  },
106
103
  {
107
104
  type: "input",
108
105
  name: "netlifySiteId",
109
- message: "Netlify Site ID (from site settings):",
106
+ message: "๐Ÿท๏ธ Netlify Site ID:",
110
107
  validate: (input) =>
111
108
  input.length > 0 || "Site ID is required for deployment",
112
109
  },
113
110
  {
114
111
  type: "confirm",
115
112
  name: "passwordProtect",
116
- message: "Password protect Storybook? (Recommended for private components)",
113
+ message: "๐Ÿ• Password protect Storybook?",
117
114
  default: true,
118
115
  },
119
116
  ];
@@ -121,78 +118,97 @@ const STORYBOOK_QUESTIONS = [
121
118
  export async function init(options) {
122
119
  const cwd = process.cwd();
123
120
 
124
- console.log(chalk.cyan("\n๐ŸŒฝ Farmwork Initialization\n"));
121
+ // Show animated logo
122
+ await farmTerm.logoAnimated();
123
+
124
+ // Check if farmwork is already installed
125
+ const claudeDir = path.join(cwd, ".claude");
126
+ const farmworkConfig = path.join(cwd, ".farmwork.json");
127
+ const claudeMd = path.join(cwd, "CLAUDE.md");
128
+
129
+ const isAlreadyInstalled = fs.existsSync(claudeDir) &&
130
+ (fs.existsSync(farmworkConfig) || fs.existsSync(claudeMd));
131
+
132
+ if (isAlreadyInstalled && !options.force) {
133
+ farmTerm.warn("Farmwork is already installed in this project!");
134
+ farmTerm.nl();
135
+
136
+ // Show what's detected
137
+ farmTerm.gray(" Detected:\n");
138
+ if (fs.existsSync(claudeDir)) farmTerm.gray(" โ€ข .claude/ directory\n");
139
+ if (fs.existsSync(claudeMd)) farmTerm.gray(" โ€ข CLAUDE.md\n");
140
+ if (fs.existsSync(farmworkConfig)) farmTerm.gray(" โ€ข .farmwork.json\n");
141
+ farmTerm.nl();
142
+
143
+ const { continueInit } = await inquirer.prompt([
144
+ {
145
+ type: "list",
146
+ name: "continueInit",
147
+ message: "What would you like to do?",
148
+ choices: [
149
+ { name: "๐Ÿด Re-initialize (will backup existing files)", value: "reinit" },
150
+ { name: "๐Ÿฎ Run doctor instead (check health)", value: "doctor" },
151
+ { name: "๐ŸŒพ Run status instead (view metrics)", value: "status" },
152
+ { name: "๐Ÿ” Exit", value: "exit" }
153
+ ]
154
+ }
155
+ ]);
156
+
157
+ if (continueInit === "exit") {
158
+ farmTerm.info("No changes made. Your farm is safe! ๐ŸŒพ\n");
159
+ return;
160
+ }
161
+
162
+ if (continueInit === "doctor") {
163
+ farmTerm.nl();
164
+ const { doctor } = await import("./doctor.js");
165
+ await doctor();
166
+ return;
167
+ }
168
+
169
+ if (continueInit === "status") {
170
+ farmTerm.nl();
171
+ const { status } = await import("./status.js");
172
+ await status();
173
+ return;
174
+ }
175
+
176
+ // continueInit === "reinit" - proceed with force
177
+ options.force = true;
178
+ farmTerm.nl();
179
+ }
180
+
181
+ farmTerm.header("FARMWORK INITIALIZATION", "primary");
182
+ farmTerm.info("Let's set up your farm! Answer a few questions to get started.\n");
125
183
 
126
184
  const answers = await inquirer.prompt(QUESTIONS);
127
185
 
128
- // Ask Storybook deployment questions if Storybook is enabled
186
+ // Storybook configuration
129
187
  if (answers.includeStorybook) {
130
- console.log(chalk.cyan("\n๐Ÿ‡ Storybook Deployment Configuration\n"));
131
- console.log(
132
- chalk.gray(
133
- "We recommend deploying Storybook to Netlify with password protection.",
134
- ),
135
- );
136
- console.log(
137
- chalk.gray(
138
- "This keeps your component documentation private while accessible to your team.\n",
139
- ),
140
- );
188
+ farmTerm.nl();
189
+ farmTerm.section("Storybook Deployment", "๐Ÿ„");
190
+ farmTerm.gray(" We recommend deploying Storybook to Netlify with password protection.\n");
191
+ farmTerm.gray(" This keeps your component docs private but accessible to your team.\n\n");
141
192
 
142
193
  const storybookAnswers = await inquirer.prompt(STORYBOOK_QUESTIONS);
143
194
  Object.assign(answers, storybookAnswers);
144
195
 
145
196
  if (answers.passwordProtect) {
146
- console.log(
147
- chalk.yellow("\n๐Ÿ‹ Remember to enable password protection in Netlify:"),
148
- );
149
- console.log(
150
- chalk.gray(" Site settings โ†’ Access control โ†’ Password protection"),
151
- );
197
+ farmTerm.nl();
198
+ farmTerm.warn("Remember to enable password protection in Netlify:");
199
+ farmTerm.gray(" Site settings โ†’ Access control โ†’ Password protection\n");
152
200
  }
153
201
  }
154
202
 
155
- // Check for existing files that would be overwritten
203
+ // Check for existing files
156
204
  const existingFiles = [];
157
205
  const filesToCheck = [
158
- {
159
- path: path.join(cwd, "CLAUDE.md"),
160
- name: "CLAUDE.md",
161
- backup: "OLD_CLAUDE.md",
162
- },
163
- {
164
- path: path.join(cwd, "justfile"),
165
- name: "justfile",
166
- backup: "OLD_justfile",
167
- },
168
- {
169
- path: path.join(cwd, ".farmwork.json"),
170
- name: ".farmwork.json",
171
- backup: null,
172
- },
173
- {
174
- path: path.join(cwd, ".claude", "settings.json"),
175
- name: ".claude/settings.json",
176
- backup: ".claude/OLD_settings.json",
177
- },
178
- {
179
- path: path.join(cwd, ".claude", "commands"),
180
- name: ".claude/commands/",
181
- backup: null,
182
- isDir: true,
183
- },
184
- {
185
- path: path.join(cwd, ".claude", "agents"),
186
- name: ".claude/agents/",
187
- backup: null,
188
- isDir: true,
189
- },
190
- {
191
- path: path.join(cwd, "_AUDIT"),
192
- name: "_AUDIT/",
193
- backup: null,
194
- isDir: true,
195
- },
206
+ { path: path.join(cwd, "CLAUDE.md"), name: "CLAUDE.md", backup: "OLD_CLAUDE.md" },
207
+ { path: path.join(cwd, "justfile"), name: "justfile", backup: "OLD_justfile" },
208
+ { path: path.join(cwd, ".farmwork.json"), name: ".farmwork.json", backup: null },
209
+ { path: path.join(cwd, ".claude", "commands"), name: ".claude/commands/", backup: null, isDir: true },
210
+ { path: path.join(cwd, ".claude", "agents"), name: ".claude/agents/", backup: null, isDir: true },
211
+ { path: path.join(cwd, "_AUDIT"), name: "_AUDIT/", backup: null, isDir: true },
196
212
  ];
197
213
 
198
214
  for (const file of filesToCheck) {
@@ -204,17 +220,23 @@ export async function init(options) {
204
220
  let didBackupClaudeMd = false;
205
221
 
206
222
  if (existingFiles.length > 0 && !options.force) {
207
- console.log(chalk.yellow("\n๐Ÿ‹ The following files/folders already exist:"));
223
+ farmTerm.nl();
224
+ farmTerm.warn("The following files/folders already exist:");
225
+ farmTerm.nl();
226
+
208
227
  for (const file of existingFiles) {
209
228
  if (file.isDir) {
210
- console.log(chalk.gray(` - ${file.name}`) + chalk.dim(" (will add new files)"));
229
+ farmTerm.gray(` ${file.name}`);
230
+ farmTerm.cyan(" (will add new files)\n");
211
231
  } else if (file.backup) {
212
- console.log(chalk.gray(` - ${file.name}`) + chalk.dim(` (will backup to ${file.backup})`));
232
+ farmTerm.gray(` ${file.name}`);
233
+ farmTerm.yellow(` โ†’ ${file.backup}\n`);
213
234
  } else {
214
- console.log(chalk.gray(` - ${file.name}`) + chalk.dim(" (will overwrite)"));
235
+ farmTerm.gray(` ${file.name}`);
236
+ farmTerm.red(" (will overwrite)\n");
215
237
  }
216
238
  }
217
- console.log("");
239
+ farmTerm.nl();
218
240
 
219
241
  const { overwriteChoice } = await inquirer.prompt([
220
242
  {
@@ -222,291 +244,197 @@ export async function init(options) {
222
244
  name: "overwriteChoice",
223
245
  message: "How would you like to proceed?",
224
246
  choices: [
225
- {
226
- name: "Continue (backup files, add to existing folders)",
227
- value: "overwrite",
228
- },
229
- { name: "Cancel installation", value: "cancel" },
247
+ { name: "๐ŸŒฑ Continue (backup files, add to existing folders)", value: "overwrite" },
248
+ { name: "๐Ÿ” Cancel installation", value: "cancel" },
230
249
  ],
231
250
  },
232
251
  ]);
233
252
 
234
253
  if (overwriteChoice === "cancel") {
235
- console.log(chalk.gray("\nInstallation cancelled."));
254
+ farmTerm.nl();
255
+ farmTerm.gray(" Installation cancelled.\n\n");
236
256
  process.exit(0);
237
257
  }
238
258
 
239
- // Backup files that have backup paths
240
- console.log("");
259
+ // Backup files
260
+ farmTerm.nl();
241
261
  for (const file of existingFiles) {
242
262
  if (file.backup) {
243
263
  const backupPath = path.join(cwd, file.backup);
244
264
  await fs.copy(file.path, backupPath);
245
- console.log(chalk.gray(` Backed up ${file.name} โ†’ ${file.backup}`));
265
+ farmTerm.status(`Backed up ${file.name} โ†’ ${file.backup}`, "pass");
246
266
  if (file.name === "CLAUDE.md") {
247
267
  didBackupClaudeMd = true;
248
268
  }
249
269
  }
250
270
  }
251
- console.log("");
252
271
  }
253
272
 
254
- // Store for use in final output
255
273
  answers._didBackupClaudeMd = didBackupClaudeMd;
256
274
 
257
- const spinner = ora("Creating Farmwork structure...").start();
275
+ // Planting animation
276
+ farmTerm.nl();
277
+ farmTerm.section("Planting Your Farm", emojis.seedling);
258
278
 
259
279
  try {
260
- // Create folder structure
261
- await fs.ensureDir(path.join(cwd, "_AUDIT"));
262
- await fs.ensureDir(path.join(cwd, "_PLANS"));
263
- await fs.ensureDir(path.join(cwd, ".claude", "commands"));
264
- await fs.ensureDir(path.join(cwd, ".claude", "agents"));
265
-
266
- spinner.text = "Creating CLAUDE.md...";
267
- await createClaudeMd(cwd, answers);
268
-
269
- spinner.text = "Creating FARMHOUSE.md...";
270
- await createFarmhouseMd(cwd, answers);
271
-
272
- spinner.text = "Creating audit documents...";
273
- await createAuditDocs(cwd, answers);
274
-
275
- spinner.text = "Creating justfile...";
276
- await createJustfile(cwd, answers);
277
-
278
- spinner.text = "Creating core agents...";
279
- await createAgents(cwd, answers);
280
-
281
- spinner.text = "Creating core commands...";
282
- await createCommands(cwd, answers);
283
-
284
- spinner.text = "Creating settings...";
285
- await createSettings(cwd, answers);
280
+ // Create folder structure with animations
281
+ const steps = [
282
+ { name: "Creating directories", fn: async () => {
283
+ await fs.ensureDir(path.join(cwd, "_AUDIT"));
284
+ await fs.ensureDir(path.join(cwd, "_PLANS"));
285
+ await fs.ensureDir(path.join(cwd, ".claude", "commands"));
286
+ await fs.ensureDir(path.join(cwd, ".claude", "agents"));
287
+ }},
288
+ { name: "Planting CLAUDE.md", fn: () => createClaudeMd(cwd, answers) },
289
+ { name: "Building FARMHOUSE.md", fn: () => createFarmhouseMd(cwd, answers) },
290
+ { name: "Creating audit documents", fn: () => createAuditDocs(cwd, answers) },
291
+ { name: "Laying out justfile", fn: () => createJustfile(cwd, answers) },
292
+ { name: "Training agents", fn: () => createAgents(cwd, answers) },
293
+ { name: "Setting up commands", fn: () => createCommands(cwd, answers) },
294
+ { name: "Configuring settings", fn: () => createSettings(cwd, answers) },
295
+ { name: "Writing .farmwork.json", fn: () => createProduceConfig(cwd, answers) },
296
+ ];
297
+
298
+ for (const step of steps) {
299
+ await farmTerm.spin(step.name, step.fn);
300
+ }
286
301
 
287
- spinner.text = "Creating .farmwork.json...";
288
- await createProduceConfig(cwd, answers);
302
+ // Install dependencies
303
+ farmTerm.nl();
304
+ farmTerm.section("Installing Tools", emojis.horse);
289
305
 
290
- // Check and install just if needed
291
- spinner.text = "Checking for just command runner...";
292
- try {
306
+ // Check and install just
307
+ await farmTerm.spin("Checking for just command runner", async () => {
293
308
  const { execSync } = await import("child_process");
294
309
  try {
295
310
  execSync("which just", { stdio: "ignore" });
296
311
  } catch {
297
- spinner.text = "Installing just...";
298
312
  try {
299
- // Try brew first (macOS), then cargo
300
313
  try {
301
- execSync("brew install just", { stdio: "inherit" });
302
- console.log(
303
- chalk.green("\n๐ŸŒฑ Just installed successfully via Homebrew"),
304
- );
314
+ execSync("brew install just", { stdio: "pipe" });
305
315
  } catch {
306
- execSync("cargo install just", { stdio: "inherit" });
307
- console.log(
308
- chalk.green("\n๐ŸŒฑ Just installed successfully via Cargo"),
309
- );
316
+ execSync("cargo install just", { stdio: "pipe" });
310
317
  }
311
318
  } catch {
312
- console.log(
313
- chalk.yellow("\n๐Ÿ‹ Could not install just automatically."),
314
- );
315
- console.log(chalk.gray(" Install manually: brew install just"));
316
- console.log(chalk.gray(" Or see: https://github.com/casey/just"));
319
+ farmTerm.warn("Could not install just automatically.");
320
+ farmTerm.gray(" Install manually: brew install just\n");
317
321
  }
318
322
  }
319
- } catch (e) {
320
- // Silently continue if check fails
321
- }
323
+ });
322
324
 
323
- spinner.text = "Setting up beads issue tracking...";
324
- try {
325
+ // Check and install beads
326
+ await farmTerm.spin("Setting up beads issue tracking", async () => {
325
327
  const { execSync } = await import("child_process");
326
-
327
- // Check if bd is installed
328
328
  try {
329
329
  execSync("which bd", { stdio: "ignore" });
330
330
  } catch {
331
- // bd not found, try to install it (npm first, then brew, then cargo)
332
- spinner.text = "Installing beads (bd)...";
333
331
  let installed = false;
334
-
335
- // Try npm first (most common)
336
332
  try {
337
- execSync("npm install -g @beads/bd", { stdio: "inherit" });
338
- console.log(chalk.green("\n๐ŸŒฑ Beads installed successfully via npm"));
333
+ execSync("npm install -g @beads/bd", { stdio: "pipe" });
339
334
  installed = true;
340
335
  } catch {
341
- // Try homebrew
342
336
  try {
343
- execSync("brew install steveyegge/beads/bd", { stdio: "inherit" });
344
- console.log(
345
- chalk.green("\n๐ŸŒฑ Beads installed successfully via Homebrew"),
346
- );
337
+ execSync("brew install steveyegge/beads/bd", { stdio: "pipe" });
347
338
  installed = true;
348
339
  } catch {
349
- // Try cargo as last resort
350
340
  try {
351
- execSync("cargo install beads", { stdio: "inherit" });
352
- console.log(
353
- chalk.green("\n๐ŸŒฑ Beads installed successfully via Cargo"),
354
- );
341
+ execSync("cargo install beads", { stdio: "pipe" });
355
342
  installed = true;
356
343
  } catch {
357
344
  // All methods failed
358
345
  }
359
346
  }
360
347
  }
361
-
362
348
  if (!installed) {
363
- console.log(
364
- chalk.yellow("\n๐Ÿ‹ Could not install beads automatically."),
365
- );
366
- console.log(
367
- chalk.gray(" Install manually: npm install -g @beads/bd"),
368
- );
369
- console.log(chalk.gray(" Or: brew install steveyegge/beads/bd"));
370
- console.log(
371
- chalk.gray(" Or see: https://github.com/steveyegge/beads"),
372
- );
349
+ farmTerm.warn("Could not install beads automatically.");
350
+ farmTerm.gray(" Install manually: npm install -g @beads/bd\n");
373
351
  }
374
352
  }
375
353
 
376
- // Initialize beads in the project
377
- spinner.text = "Initializing beads...";
354
+ // Initialize beads
378
355
  try {
379
356
  execSync("bd init", { cwd, stdio: "ignore" });
380
357
  } catch {
381
- // bd init might fail if already initialized or not installed
358
+ // bd init might fail if already initialized
382
359
  }
383
360
 
384
- // Clean up beads-generated agent files (we use CLAUDE.md instead)
385
- spinner.text = "Cleaning up beads defaults...";
361
+ // Clean up beads-generated files
386
362
  const beadsAgentFiles = ["AGENTS.md", "@AGENTS.md"];
387
363
  for (const file of beadsAgentFiles) {
388
- const filePath = path.join(cwd, file);
389
364
  try {
390
- await fs.remove(filePath);
365
+ await fs.remove(path.join(cwd, file));
391
366
  } catch {
392
- // File doesn't exist, ignore
367
+ // File doesn't exist
393
368
  }
394
369
  }
395
- } catch (e) {
396
- console.log(
397
- chalk.yellow(
398
- "\n๐Ÿ‹ Could not set up beads. Install with: cargo install beads",
399
- ),
400
- );
401
- }
402
-
403
- spinner.succeed(chalk.green("Farmwork initialized!"));
404
-
405
- console.log(chalk.cyan("\n๐ŸŒฑ Created structure:"));
406
- console.log(` ${chalk.green("๐ŸŒฑ")} _AUDIT/`);
407
- console.log(` ${chalk.green("๐ŸŒฑ")} _PLANS/`);
408
- console.log(` ${chalk.green("๐ŸŒฑ")} .claude/commands/`);
409
- console.log(` ${chalk.green("๐ŸŒฑ")} .claude/agents/`);
410
- console.log(` ${chalk.green("๐ŸŒฑ")} CLAUDE.md`);
411
- console.log(` ${chalk.green("๐ŸŒฑ")} justfile`);
412
- console.log(` ${chalk.green("๐ŸŒฑ")} .farmwork.json`);
413
-
414
- console.log(chalk.cyan("\n๐Ÿฅ• Next steps:"));
415
- console.log(
416
- ` 1. Run ${chalk.yellow("just --list")} to see available commands`,
417
- );
418
- console.log(
419
- ` 2. Say ${chalk.yellow('"till the land"')} to Claude to audit your setup`,
420
- );
421
- console.log(
422
- ` 3. Say ${chalk.yellow('"make a plan for <feature>"')} to start planning`,
423
- );
424
-
425
- console.log(chalk.cyan("\n๐ŸŒพ Now let Claude Code get comfortable!"));
426
- console.log(chalk.gray(" Copy and paste this prompt to Claude Code:\n"));
427
- console.log(
428
- chalk.white(
429
- " โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”",
430
- ),
431
- );
432
- console.log(
433
- chalk.white(" โ”‚") +
434
- chalk.yellow(
435
- " Hey Claude, I am using the Farmwork framework, please go through the ",
436
- ) +
437
- chalk.white("โ”‚"),
438
- );
439
- console.log(
440
- chalk.white(" โ”‚") +
441
- chalk.yellow(
442
- " justfile and create project-specific commands, and go through my app ",
443
- ) +
444
- chalk.white("โ”‚"),
445
- );
446
- console.log(
447
- chalk.white(" โ”‚") +
448
- chalk.yellow(
449
- " and suggest any project-specific subagents that would work well. ",
450
- ) +
451
- chalk.white("โ”‚"),
452
- );
453
- console.log(
454
- chalk.white(
455
- " โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜",
456
- ),
457
- );
370
+ });
371
+
372
+ // Success!
373
+ farmTerm.nl();
374
+ farmTerm.divider("โ•", 50);
375
+ farmTerm.success("Farmwork initialized successfully!");
376
+
377
+ // Show created structure
378
+ farmTerm.section("Created Structure", emojis.corn);
379
+ await farmTerm.planting([
380
+ "_AUDIT/",
381
+ "_PLANS/",
382
+ ".claude/commands/",
383
+ ".claude/agents/",
384
+ "CLAUDE.md",
385
+ "justfile",
386
+ ".farmwork.json",
387
+ ], "Files planted");
388
+
389
+ // Next steps
390
+ farmTerm.section("Next Steps", emojis.carrot);
391
+ farmTerm.nl();
392
+ farmTerm.white(" 1. ");
393
+ farmTerm.yellow("just --list");
394
+ farmTerm.gray(" โ†’ See available commands\n");
395
+ farmTerm.white(" 2. ");
396
+ farmTerm.yellow('"till the land"');
397
+ farmTerm.gray(" โ†’ Audit your setup\n");
398
+ farmTerm.white(" 3. ");
399
+ farmTerm.yellow('"make a plan for <feature>"');
400
+ farmTerm.gray(" โ†’ Start planning\n");
401
+
402
+ // Claude prompt box
403
+ farmTerm.nl();
404
+ farmTerm.section("Get Claude Comfortable", emojis.wheat);
405
+ farmTerm.gray(" Copy and paste this prompt to Claude Code:\n\n");
406
+
407
+ farmTerm.box("Prompt for Claude", [
408
+ "Hey Claude, I am using the Farmwork framework,",
409
+ "please go through the justfile and create",
410
+ "project-specific commands, and go through my",
411
+ "app and suggest project-specific subagents",
412
+ "that would work well.",
413
+ ], "secondary");
458
414
 
459
415
  // Show merge prompt if we backed up CLAUDE.md
460
416
  if (answers._didBackupClaudeMd) {
461
- console.log(chalk.cyan("\n๐Ÿฅฌ Merge your old instructions!"));
462
- console.log(
463
- chalk.gray(
464
- " Your old CLAUDE.md was backed up. Use this prompt to merge:\n",
465
- ),
466
- );
467
- console.log(
468
- chalk.white(
469
- " โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”",
470
- ),
471
- );
472
- console.log(
473
- chalk.white(" โ”‚") +
474
- chalk.yellow(
475
- " Hey Claude, look at my CLAUDE.md file and merge the project-specific ",
476
- ) +
477
- chalk.white("โ”‚"),
478
- );
479
- console.log(
480
- chalk.white(" โ”‚") +
481
- chalk.yellow(
482
- " instructions from OLD_CLAUDE.md into it, so I have one file with all ",
483
- ) +
484
- chalk.white("โ”‚"),
485
- );
486
- console.log(
487
- chalk.white(" โ”‚") +
488
- chalk.yellow(
489
- " the Farmwork framework instructions plus my original project setup. ",
490
- ) +
491
- chalk.white("โ”‚"),
492
- );
493
- console.log(
494
- chalk.white(" โ”‚") +
495
- chalk.yellow(
496
- " Then delete OLD_CLAUDE.md when done. Same for OLD_justfile. Thank you. ",
497
- ) +
498
- chalk.white("โ”‚"),
499
- );
500
- console.log(
501
- chalk.white(
502
- " โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜",
503
- ),
504
- );
417
+ farmTerm.nl();
418
+ farmTerm.section("Merge Your Old Instructions", "๐Ÿฅฌ");
419
+ farmTerm.gray(" Your old CLAUDE.md was backed up. Use this prompt to merge:\n\n");
420
+
421
+ farmTerm.box("Merge Prompt", [
422
+ "Hey Claude, look at my CLAUDE.md file and",
423
+ "merge the project-specific instructions from",
424
+ "OLD_CLAUDE.md into it, so I have one file",
425
+ "with all the Farmwork instructions plus my",
426
+ "original project setup. Then delete the OLD",
427
+ "files when done.",
428
+ ], "accent");
505
429
  }
506
430
 
507
- console.log("");
431
+ // Final tractor drive
432
+ farmTerm.nl();
433
+ await farmTerm.tractorAnimation("Your farm is ready!", 1500);
434
+ farmTerm.nl();
435
+
508
436
  } catch (error) {
509
- spinner.fail(chalk.red("Failed to initialize Farmwork"));
437
+ farmTerm.error("Failed to initialize Farmwork");
510
438
  console.error(error);
511
439
  process.exit(1);
512
440
  }
@@ -657,8 +585,8 @@ async function createFarmhouseMd(cwd, answers) {
657
585
 
658
586
  | Metric | Count |
659
587
  |--------|-------|
660
- | Commands | 1 |
661
- | Agents | 4 |
588
+ | Commands | 2 |
589
+ | Agents | 9 |
662
590
  | Justfile Recipes | 10 |
663
591
  | Unit Tests | 0 |
664
592
  | E2E Tests | 0 |
@@ -677,6 +605,7 @@ All Claude Code commands and agents are documented, phrase triggers are tested a
677
605
  | Command | Description |
678
606
  |---------|-------------|
679
607
  | \`/push\` | Clean, lint, test, build, commit, push |
608
+ | \`/open-the-farm\` | Full audit cycle, then ask user next steps |
680
609
 
681
610
  ---
682
611
 
@@ -688,6 +617,11 @@ All Claude Code commands and agents are documented, phrase triggers are tested a
688
617
  | \`code-reviewer\` | Quality & security code review |
689
618
  | \`security-auditor\` | OWASP vulnerability scanning |
690
619
  | \`performance-auditor\` | Performance anti-patterns |
620
+ | \`code-smell-auditor\` | DRY violations, complexity, naming |
621
+ | \`unused-code-cleaner\` | Detect and remove dead code |
622
+ | \`code-cleaner\` | Remove comments and console.logs |
623
+ | \`i18n-locale-translator\` | Translate UI text to locales |
624
+ | \`storybook-maintainer\` | Create/update Storybook stories |
691
625
 
692
626
  ---
693
627
 
@@ -745,26 +679,10 @@ async function createAuditDocs(cwd, answers) {
745
679
  const today = new Date().toISOString().split("T")[0];
746
680
 
747
681
  const audits = [
748
- {
749
- name: "SECURITY.md",
750
- title: "Security Audit",
751
- description: "Security posture and vulnerability tracking",
752
- },
753
- {
754
- name: "PERFORMANCE.md",
755
- title: "Performance Audit",
756
- description: "Performance metrics and optimization tracking",
757
- },
758
- {
759
- name: "CODE_QUALITY.md",
760
- title: "Code Quality Audit",
761
- description: "Code quality and standards tracking",
762
- },
763
- {
764
- name: "TESTS.md",
765
- title: "Test Coverage Audit",
766
- description: "Test coverage and gaps tracking",
767
- },
682
+ { name: "SECURITY.md", title: "Security Audit", description: "Security posture and vulnerability tracking" },
683
+ { name: "PERFORMANCE.md", title: "Performance Audit", description: "Performance metrics and optimization tracking" },
684
+ { name: "CODE_QUALITY.md", title: "Code Quality Audit", description: "Code quality and standards tracking" },
685
+ { name: "TESTS.md", title: "Test Coverage Audit", description: "Test coverage and gaps tracking" },
768
686
  ];
769
687
 
770
688
  for (const audit of audits) {
@@ -948,8 +866,8 @@ Reports findings with severity (CRITICAL, HIGH, MEDIUM, LOW) and remediation ste
948
866
  "security-auditor.md": `---
949
867
  name: security-auditor
950
868
  description: OWASP security vulnerability scanning
951
- tools: Read, Grep, Glob
952
- model: sonnet
869
+ tools: Read, Grep, Glob, Edit
870
+ model: haiku
953
871
  ---
954
872
 
955
873
  # Security Auditor Agent
@@ -967,8 +885,8 @@ Updates \`_AUDIT/SECURITY.md\` with results.
967
885
  "performance-auditor.md": `---
968
886
  name: performance-auditor
969
887
  description: Find memory leaks, unnecessary re-renders, and anti-patterns
970
- tools: Read, Grep, Glob
971
- model: sonnet
888
+ tools: Read, Grep, Glob, Edit
889
+ model: haiku
972
890
  ---
973
891
 
974
892
  # Performance Auditor Agent
@@ -982,6 +900,98 @@ Scans for performance anti-patterns:
982
900
 
983
901
  Reports findings with impact assessment.
984
902
  Updates \`_AUDIT/PERFORMANCE.md\` with results.
903
+ `,
904
+ "code-smell-auditor.md": `---
905
+ name: code-smell-auditor
906
+ description: Detect DRY violations, complexity issues, naming problems, and technical debt
907
+ tools: Read, Grep, Glob, Edit
908
+ model: haiku
909
+ ---
910
+
911
+ # Code Smell Auditor Agent
912
+
913
+ Scans for code quality issues:
914
+ - DRY violations (duplicated code)
915
+ - Complexity issues (functions > 50 lines, deep nesting)
916
+ - Naming issues (misleading names, abbreviations)
917
+ - Magic values (hardcoded numbers/strings)
918
+ - Technical debt (TODO, FIXME, HACK comments)
919
+
920
+ Reports code health as GOOD / FAIR / NEEDS ATTENTION.
921
+ Updates \`_AUDIT/CODE_QUALITY.md\` with results.
922
+ `,
923
+ "unused-code-cleaner.md": `---
924
+ name: unused-code-cleaner
925
+ description: Detect and remove unused code (imports, functions, variables)
926
+ tools: Read, Write, Edit, Bash, Grep, Glob
927
+ model: haiku
928
+ ---
929
+
930
+ # Unused Code Cleaner Agent
931
+
932
+ Detects and removes unused code:
933
+ - Unused imports
934
+ - Unused functions and classes
935
+ - Unused variables
936
+ - Dead code paths
937
+ - Console.log statements (optional)
938
+ - Comments (preserves JSDoc)
939
+
940
+ Use after refactoring, when removing features, or before production deployment.
941
+ `,
942
+ "code-cleaner.md": `---
943
+ name: code-cleaner
944
+ description: Fast removal of comments, console.logs, and debug code while preserving JSDoc
945
+ tools: Read, Edit, Glob, Grep
946
+ model: haiku
947
+ ---
948
+
949
+ # Code Cleaner Agent
950
+
951
+ Fast cleanup of TypeScript/JavaScript files:
952
+
953
+ ## Removes
954
+ - Line comments (\`//\`)
955
+ - Block comments (\`/* */\`)
956
+ - \`console.log\` statements
957
+
958
+ ## Preserves
959
+ - JSDoc comments (\`/** */\`)
960
+ - \`console.error\`, \`console.warn\`, \`console.info\`
961
+ `,
962
+ "i18n-locale-translator.md": `---
963
+ name: i18n-locale-translator
964
+ description: Translate UI text content into English (en) and Japanese (jp) using i18n locale system
965
+ tools: Read, Write, Edit, Glob, Grep
966
+ model: sonnet
967
+ ---
968
+
969
+ # i18n Locale Translator Agent
970
+
971
+ Handles internationalization tasks:
972
+ - Extract hardcoded text from components
973
+ - Create translation keys in locale files
974
+ - Translate content to English and Japanese
975
+ - Update components to use translation hooks
976
+
977
+ Use when adding new features or internationalizing existing hardcoded text.
978
+ `,
979
+ "storybook-maintainer.md": `---
980
+ name: storybook-maintainer
981
+ description: Create and update Storybook stories for UI components
982
+ tools: Read, Write, Edit, Glob, Grep
983
+ model: haiku
984
+ ---
985
+
986
+ # Storybook Maintainer Agent
987
+
988
+ Manages Storybook stories for UI components:
989
+ - Analyze component props and variants
990
+ - Create comprehensive story files
991
+ - Document component usage
992
+ - Add controls for interactive props
993
+
994
+ Use when adding new components or when existing components change significantly.
985
995
  `,
986
996
  };
987
997
 
@@ -993,7 +1003,6 @@ Updates \`_AUDIT/PERFORMANCE.md\` with results.
993
1003
  async function createCommands(cwd, answers) {
994
1004
  const pm = answers.packageManager || "npm";
995
1005
 
996
- // Build Storybook deployment steps if enabled
997
1006
  const storybookSteps = answers.includeStorybook
998
1007
  ? `
999
1008
 
@@ -1085,23 +1094,117 @@ ${reportContent}`;
1085
1094
  path.join(cwd, ".claude", "commands", "push.md"),
1086
1095
  pushCommand,
1087
1096
  );
1088
- }
1089
1097
 
1090
- async function createSettings(cwd, answers) {
1091
- const settings = {
1092
- permissions: {
1093
- allow: [],
1094
- deny: [],
1095
- ask: [],
1096
- },
1097
- };
1098
+ // Create open-the-farm command
1099
+ const openTheFarmCommand = `---
1100
+ description: Full audit cycle - till, inspect, dry run harvest, then ask user next steps
1101
+ allowed-tools: Bash(${pm}:*), Bash(just:*), Task
1102
+ ---
1103
+
1104
+ # Open the Farm Command
1105
+
1106
+ Complete development audit workflow. Runs all audits and quality checks without committing, then asks the user what to do next.
1107
+
1108
+ Trigger phrase: "open the farm"
1109
+
1110
+ ## Workflow
1111
+
1112
+ Execute these steps in order. Track findings from each step for the final summary.
1113
+
1114
+ ### Step 1: Till the Land
1115
+
1116
+ Update the harness documentation with current metrics.
1117
+
1118
+ Launch the \`the-farmer\` agent to audit and update \`_AUDIT/FARMHOUSE.md\`:
1119
+ - Commands and agents inventory
1120
+ - Test counts
1121
+ - Completed issues count
1122
+
1123
+ Record the Farmhouse score for the summary.
1124
+
1125
+ ### Step 2: Inspect the Farm
1126
+
1127
+ Run all inspection agents in parallel for comprehensive code quality check.
1128
+
1129
+ #### 2a. Code Review & Cleanup
1130
+ Launch these agents in parallel:
1131
+ - \`code-reviewer\` agent on recently modified files
1132
+ - \`unused-code-cleaner\` to detect dead code, unused imports
1133
+
1134
+ #### 2b. Performance Audit
1135
+ - Launch \`performance-auditor\` agent for anti-patterns
1136
+
1137
+ #### 2c. Security Audit
1138
+ - Launch \`security-auditor\` agent for OWASP Top 10 vulnerabilities
1139
+ - Note findings by severity (CRITICAL, HIGH, MEDIUM, LOW)
1140
+
1141
+ #### 2d. Code Quality Audit
1142
+ - Launch \`code-smell-auditor\` agent for DRY violations, complexity, naming issues
1143
+ - Note code health rating (GOOD / FAIR / NEEDS ATTENTION)
1144
+
1145
+ ### Step 3: Dry Run Harvest
1146
+
1147
+ Run quality gates WITHOUT committing or pushing:
1148
+
1149
+ 1. **Lint**: \`${answers.lintCommand}\`
1150
+ 2. **Tests**: \`${answers.testCommand}\`
1151
+ 3. **Build**: \`${answers.buildCommand}\`
1152
+
1153
+ Record pass/fail status for each gate.
1154
+
1155
+ ### Step 4: Summary Report
1156
+
1157
+ Present a consolidated report of all findings:
1158
+
1159
+ \`\`\`
1160
+ ## Farm Audit Complete
1161
+
1162
+ ### Harness Status
1163
+ - Farmhouse Score: X/10
1164
+ - Commands: X | Agents: X | Tests: X
1165
+
1166
+ ### Code Quality
1167
+ - Security: X issues (Y critical, Z high)
1168
+ - Performance: X issues
1169
+ - Code Smells: [GOOD/FAIR/NEEDS ATTENTION]
1170
+ - Unused Code: X items flagged
1171
+
1172
+ ### Quality Gates
1173
+ - Lint: โœ…/โŒ
1174
+ - Tests: โœ…/โŒ (X passed, Y failed)
1175
+ - Build: โœ…/โŒ
1176
+
1177
+ ### Open Items
1178
+ [List top 3-5 issues that should be addressed]
1179
+ \`\`\`
1180
+
1181
+ ### Step 5: Ask User
1182
+
1183
+ After presenting the summary, ask:
1184
+
1185
+ > "All audits complete. What would you like to do?"
1186
+ >
1187
+ > Options:
1188
+ > - **harvest crops** - Commit and push all changes
1189
+ > - **review audits** - Show detailed findings from a specific audit
1190
+ > - **fix issues first** - Address the open items before pushing
1191
+
1192
+ Wait for user response before proceeding.
1193
+
1194
+ ## Notes
1195
+
1196
+ - This command does NOT modify files or commit changes
1197
+ - All audit agents update their respective \`_AUDIT/*.md\` files
1198
+ - Use this before major releases or after significant development
1199
+ `;
1098
1200
 
1099
1201
  await fs.writeFile(
1100
- path.join(cwd, ".claude", "settings.json"),
1101
- JSON.stringify(settings, null, 2),
1202
+ path.join(cwd, ".claude", "commands", "open-the-farm.md"),
1203
+ openTheFarmCommand,
1102
1204
  );
1205
+ }
1103
1206
 
1104
- // Create local settings for sensitive data (gitignored)
1207
+ async function createSettings(cwd, answers) {
1105
1208
  if (answers.includeStorybook && answers.netlifyAuthToken) {
1106
1209
  const localSettings = {
1107
1210
  env: {
@@ -1115,7 +1218,6 @@ async function createSettings(cwd, answers) {
1115
1218
  JSON.stringify(localSettings, null, 2),
1116
1219
  );
1117
1220
 
1118
- // Ensure settings.local.json is gitignored
1119
1221
  const gitignorePath = path.join(cwd, ".gitignore");
1120
1222
  let gitignoreContent = "";
1121
1223
  try {
@@ -1158,7 +1260,6 @@ async function createProduceConfig(cwd, answers) {
1158
1260
  audits: ["FARMHOUSE", "SECURITY", "PERFORMANCE", "CODE_QUALITY", "TESTS"],
1159
1261
  };
1160
1262
 
1161
- // Add Storybook configuration if enabled
1162
1263
  if (answers.includeStorybook) {
1163
1264
  config.storybook = {
1164
1265
  url: answers.storybookUrl || null,