@pshch/gary-the-gardener 1.3.0 → 1.4.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.
@@ -5,6 +5,14 @@ All notable changes to the Garden System will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.1] - 2026-02-16
9
+
10
+ ### Changed
11
+ - **Renamed "Gardner Gary" to "🪴 Gary The Gardener"** - Consistent naming with emoji across all files
12
+ - **Fixed syntax error** in CLI help output template literal
13
+
14
+ ---
15
+
8
16
  ## [1.1.0] - 2026-02-15
9
17
 
10
18
  ### Added
@@ -12,14 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
20
  - **Contextual "What should I do next?" suggestions** - Option 6 in help menu analyzes repo state on-demand
13
21
  - **Bootstrap detection** - Help workflow detects missing AGENTS.md and guides users to bootstrap first
14
22
  - **`/garden-bootstrap` command** - First-time setup workflow (converted from ai-bootstrapper.md)
15
- - **[BS] Bootstrap menu item** - Conditional display in Gardner Gary when AGENTS.md is missing
23
+ - **[BS] Bootstrap menu item** - Conditional display in 🪴 Gary The Gardener when AGENTS.md is missing
16
24
  - **Progressive disclosure** - Help menu offers option 7 to show all 9 commands
17
25
 
18
26
  ### Changed
19
27
  - **`/garden-garden` renamed to `/garden-maintain`** - Clearer command name (no longer self-referential)
20
28
  - **Workflow directory renamed** - `_gs-gardener/core/workflows/garden/` → `_gs-gardener/core/workflows/maintain/`
21
29
  - **Help workflow structure** - Now defers heavy I/O checks to contextual analysis (Phase 4)
22
- - **Gardner Gary menu** - Shows 9 options when AGENTS.md missing, 8 when it exists
30
+ - **🪴 Gary The Gardener menu** - Shows 9 options when AGENTS.md missing, 8 when it exists
23
31
  - **Bootstrap integration** - ai-bootstrapper.md converted to standard garden workflow
24
32
 
25
33
  ### Fixed
@@ -38,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
38
46
 
39
47
  ### Added
40
48
  - Initial release of Garden System
41
- - Gardner Gary 🪴 - Interactive Repository Garden Keeper subagent
49
+ - 🪴 Gary The Gardener 🪴 - Interactive Repository Garden Keeper subagent
42
50
  - 9 maintenance skills:
43
51
  - `/garden-sync` - Sync wrappers with AGENTS.md
44
52
  - `/garden-audit` - Audit for drift between docs and code
@@ -1 +1 @@
1
- 1.3.0
1
+ 1.4.1
@@ -3,7 +3,7 @@ name: "gardener"
3
3
  description: "Repository Garden Keeper"
4
4
  ---
5
5
 
6
- <agent id="gardener.agent.yaml" name="Gardner Gary" title="Repository Garden Keeper" icon="🪴">
6
+ <agent id="gardener.agent.yaml" name="🪴 Gary The Gardener" title="Repository Garden Keeper" icon="🪴">
7
7
 
8
8
  <activation critical="MANDATORY">
9
9
  <step n="1">Load persona from this current agent file</step>
@@ -14,7 +14,7 @@ description: "Repository Garden Keeper"
14
14
  </step>
15
15
  <step n="3">Read version from {project-root}/_gs-gardener/VERSION file, then display personalized welcome greeting with version:
16
16
  ```
17
- 🪴 Hello! I'm Gardner Gary v{version}, your Repository Garden Keeper.
17
+ 🪴 Hello! I'm Gary The Gardener v{version}, your Repository Garden Keeper.
18
18
 
19
19
  I'm here to help you maintain your AI configuration "garden" - keeping your
20
20
  documentation healthy, pruned, and thriving across your repository.
@@ -83,7 +83,7 @@ description: "Repository Garden Keeper"
83
83
  </activation>
84
84
 
85
85
  <persona>
86
- <name>Gardner Gary</name>
86
+ <name>🪴 Gary The Gardener</name>
87
87
  <role>Repository Garden Keeper</role>
88
88
  <identity>
89
89
  Gary is a meticulous gardener who tends to AI configuration "gardens" across repositories.
@@ -1,5 +1,5 @@
1
1
  # Garden System Configuration
2
- # Version: 1.3.0
2
+ # Version: 1.4.1
3
3
 
4
4
  project_name: ai-bootstrap
5
5
  user_name: User
@@ -22,4 +22,4 @@ references_directory: "{project-root}/docs/references"
22
22
  agents_max_lines: 150
23
23
 
24
24
  # Garden System Version
25
- version: "1.3.0"
25
+ version: "1.4.1"
@@ -240,7 +240,7 @@ Do NOT create files with fabricated content. Stubs with clear "how to populate"
240
240
 
241
241
  ### 2.5 Garden System (Maintenance System)
242
242
 
243
- Copy the garden system to enable ongoing maintenance via Gardner Gary 🪴:
243
+ Copy the garden system to enable ongoing maintenance via 🪴 Gary The Gardener 🪴:
244
244
 
245
245
  **Option A: Direct Copy (Recommended)**
246
246
  ```bash
@@ -264,7 +264,7 @@ cp _ai-bootstrap/.claude/commands/garden-*.md .claude/commands/
264
264
  ```
265
265
 
266
266
  This enables:
267
- - `/garden-agent-gardener` - Interactive maintenance menu with Gardner Gary
267
+ - `/garden-agent-gardener` - Interactive maintenance menu with 🪴 Gary The Gardener
268
268
  - Individual skills: `/garden-sync`, `/garden-audit`, `/garden-extend`, `/garden-references`, `/garden-add-tool`, `/garden-scaffold`, `/garden-maintain`, `/garden-compact`
269
269
 
270
270
  See GARDEN-SYSTEM.md in the ai-bootstrap repository for full deployment and usage guide.
package/bin/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { parseArgs } from 'node:util';
4
4
  import { cpSync, existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'node:fs';
5
5
  import { join, resolve, basename } from 'node:path';
6
+ import { createInterface } from 'node:readline/promises';
6
7
 
7
8
  const PKG_ROOT = resolve(import.meta.dirname, '..');
8
9
  const VERSION = readFileSync(join(PKG_ROOT, '_gs-gardener', 'VERSION'), 'utf8').trim();
@@ -14,6 +15,115 @@ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
14
15
  const red = (s) => `\x1b[31m${s}\x1b[0m`;
15
16
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
16
17
 
18
+ // ── Workflow catalog (shared across all tool generators) ─────────────
19
+ const WORKFLOWS = [
20
+ { name: 'bootstrap', icon: '🌱', tag: 'Plant your first seeds' },
21
+ { name: 'audit', icon: '🔍', tag: 'Pull the weeds — find stale & missing docs' },
22
+ { name: 'compact', icon: '✂️', tag: 'Prune the overgrowth' },
23
+ { name: 'sync', icon: '💧', tag: 'Water the roots — keep wrappers in sync' },
24
+ { name: 'maintain', icon: '🌿', tag: 'Walk the rows with shears' },
25
+ { name: 'extend', icon: '🌻', tag: 'Grow new beds — add content layers' },
26
+ { name: 'references', icon: '📚', tag: 'Tend the reference shelf' },
27
+ { name: 'scaffold', icon: '🏗️', tag: 'Lay out the plots — setup docs/ structure' },
28
+ { name: 'add-tool', icon: '🔧', tag: 'Plant in new soil — add AI tool support' },
29
+ { name: 'help', icon: '❓', tag: 'Ask the gardener' },
30
+ ];
31
+
32
+ // ── Agent activation block (reused by all tool providers) ────────────
33
+ const AGENT_ACTIVATION = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
34
+
35
+ <agent-activation CRITICAL="TRUE">
36
+ 1. LOAD the FULL agent file from {project-root}/_gs-gardener/core/agents/gardener.md
37
+ 2. READ its entire contents - this contains the complete agent persona, menu, and instructions
38
+ 3. FOLLOW every step in the <activation> section precisely
39
+ 4. DISPLAY the welcome greeting and coverage status
40
+ 5. PRESENT the numbered menu
41
+ 6. WAIT for user input before proceeding
42
+ </agent-activation>`;
43
+
44
+ const AGENT_DESC = '🪴 Gary The Gardener - documentation maintenance agent';
45
+
46
+ // ── Tool definitions ─────────────────────────────────────────────────
47
+ const TOOLS = {
48
+ 'claude-code': {
49
+ label: 'Claude Code',
50
+ detect: [],
51
+ alwaysInstall: true,
52
+ // Commands are copied from package source (steps 2-3 in install)
53
+ summaryPath: '.claude/commands/',
54
+ },
55
+ cursor: {
56
+ label: 'Cursor',
57
+ detect: ['.cursor', '.cursorrules'],
58
+ dirs: ['.cursor', '.cursor/rules'],
59
+ agentFile: {
60
+ path: '.cursor/rules/garden-agent-gardener.mdc',
61
+ content: `---
62
+ description: "${AGENT_DESC}"
63
+ globs:
64
+ alwaysApply: true
65
+ ---
66
+
67
+ # gardener
68
+
69
+ ${AGENT_ACTIVATION}
70
+ `,
71
+ },
72
+ summaryPath: '.cursor/rules/garden-agent-gardener.mdc',
73
+ },
74
+ copilot: {
75
+ label: 'GitHub Copilot',
76
+ detect: ['.github/copilot-instructions.md', '.github'],
77
+ dirs: ['.github', '.github/agents'],
78
+ agentFile: {
79
+ path: '.github/agents/gardener.md',
80
+ content: `---
81
+ name: 'gardener'
82
+ description: '${AGENT_DESC}'
83
+ ---
84
+
85
+ # gardener
86
+
87
+ ${AGENT_ACTIVATION}
88
+ `,
89
+ },
90
+ summaryPath: '.github/agents/gardener.md',
91
+ },
92
+ windsurf: {
93
+ label: 'Windsurf',
94
+ detect: ['.windsurfrules', '.windsurf'],
95
+ dirs: ['.windsurf', '.windsurf/rules'],
96
+ agentFile: {
97
+ path: '.windsurf/rules/garden-agent-gardener.md',
98
+ content: `# gardener
99
+
100
+ > ${AGENT_DESC}
101
+
102
+ ${AGENT_ACTIVATION}
103
+ `,
104
+ },
105
+ summaryPath: '.windsurf/rules/garden-agent-gardener.md',
106
+ },
107
+ junie: {
108
+ label: 'JetBrains Junie',
109
+ detect: ['.junie'],
110
+ dirs: ['.junie'],
111
+ agentFile: {
112
+ path: '.junie/guidelines.md',
113
+ content: `# 🪴 Gary The Gardener
114
+
115
+ > Documentation maintenance agent for this repository.
116
+
117
+ ${AGENT_ACTIVATION}
118
+ `,
119
+ },
120
+ summaryPath: '.junie/guidelines.md',
121
+ },
122
+ };
123
+
124
+ const TOOL_SLUGS = Object.keys(TOOLS);
125
+ const OPTIONAL_SLUGS = TOOL_SLUGS.filter(s => !TOOLS[s].alwaysInstall);
126
+
17
127
  // ── Parse CLI arguments ─────────────────────────────────────────────
18
128
  const { values, positionals } = parseArgs({
19
129
  args: process.argv.slice(2),
@@ -22,6 +132,7 @@ const { values, positionals } = parseArgs({
22
132
  'dry-run': { type: 'boolean', short: 'n', default: false },
23
133
  help: { type: 'boolean', short: 'h', default: false },
24
134
  version: { type: 'boolean', short: 'v', default: false },
135
+ tools: { type: 'string', short: 't' },
25
136
  },
26
137
  allowPositionals: true,
27
138
  strict: false,
@@ -31,7 +142,7 @@ const command = positionals[0];
31
142
 
32
143
  // ── Version ─────────────────────────────────────────────────────────
33
144
  if (values.version) {
34
- console.log(`gary-the-gardener v${VERSION}`);
145
+ console.log(`🪴 Gary The Gardener v${VERSION}`);
35
146
  process.exit(0);
36
147
  }
37
148
 
@@ -47,7 +158,7 @@ const dryRun = values['dry-run'];
47
158
  if (command === 'status') {
48
159
  runStatus();
49
160
  } else if (command === 'install' || !command) {
50
- runInstall(values.force, dryRun);
161
+ await runInstall(values.force, dryRun);
51
162
  } else {
52
163
  console.error(red(`Unknown command: ${command}`));
53
164
  console.error('Run "npx @pshch/gary-the-gardener --help" for usage.');
@@ -58,13 +169,16 @@ if (command === 'status') {
58
169
  // ── Core functions ────────────────────────────────────────────────
59
170
  // ═══════════════════════════════════════════════════════════════════
60
171
 
61
- function runInstall(force, dryRun) {
172
+ async function runInstall(force, dryRun) {
62
173
  const dest = process.cwd();
63
174
 
64
- console.log(`\n🪴 ${bold('Gary the Gardener')} v${VERSION}`);
175
+ console.log(`\n🪴 ${bold('Gary The Gardener')} v${VERSION}`);
65
176
  if (dryRun) console.log(yellow(` (dry run — no files will be written)`));
66
177
  console.log('');
67
178
 
179
+ // Validate --tools flag early, before any file operations
180
+ const requestedTools = parseToolsFlag(values.tools);
181
+
68
182
  // Sanity check: don't install into the package itself
69
183
  if (existsSync(join(dest, 'bin', 'cli.js')) && existsSync(join(dest, '_gs-gardener', 'core'))) {
70
184
  const pkg = safeReadJson(join(dest, 'package.json'));
@@ -82,6 +196,7 @@ function runInstall(force, dryRun) {
82
196
  const installedVersion = readInstalledVersion(coreDest);
83
197
  const isUpgrade = installedVersion && installedVersion !== VERSION;
84
198
  const isCurrent = installedVersion === VERSION;
199
+ let freshInstall = false;
85
200
 
86
201
  if (isUpgrade) {
87
202
  console.log(` Upgrading ${dim(`v${installedVersion}`)} → ${green(`v${VERSION}`)}\n`);
@@ -93,23 +208,19 @@ function runInstall(force, dryRun) {
93
208
  if (isCurrent && !force) {
94
209
  console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/ (unchanged)')}`);
95
210
  } else {
96
- // Save user config before overwriting
97
211
  const savedConfig = isUpgrade ? safeReadFile(configPath) : null;
98
212
 
99
213
  if (!dryRun) {
100
214
  cpSync(coreSrc, coreDest, { recursive: true, force: true });
101
215
 
102
216
  if (savedConfig) {
103
- // Restore user config, only bump the version line
104
217
  const updated = savedConfig
105
218
  .replace(/^# Version: .+$/m, `# Version: ${VERSION}`)
106
219
  .replace(/^version: .+$/m, `version: "${VERSION}"`);
107
220
  writeFileSync(configPath, updated);
108
221
  console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/ (upgraded, config preserved)')}`);
109
222
  } else {
110
- // Fresh install — write default config
111
- const projectName = basename(dest);
112
- writeFileSync(configPath, defaultConfig(projectName));
223
+ freshInstall = true;
113
224
  console.log(` ${green('✓')} Core system → ${dim('_gs-gardener/')}`);
114
225
  }
115
226
  } else {
@@ -133,18 +244,18 @@ function runInstall(force, dryRun) {
133
244
  copied++;
134
245
  }
135
246
  if (isCurrent && !force) {
136
- console.log(` ${green('✓')} ${gardenCmds.length} skill commands → ${dim('.claude/commands/ (unchanged)')}`);
247
+ console.log(` ${green('✓')} ${gardenCmds.length} commands → ${dim('.claude/commands/ (unchanged)')}`);
137
248
  } else {
138
- console.log(` ${green('✓')} ${copied} skill commands → ${dim('.claude/commands/')}`);
249
+ console.log(` ${green('✓')} ${copied} commands → ${dim('.claude/commands/')}`);
139
250
  }
140
251
 
141
252
  // ── 3. CLAUDE.md ──────────────────────────────────────────────────
142
- installWrapper(dest, 'CLAUDE.md',
253
+ installFile(dest, 'CLAUDE.md',
143
254
  `# CLAUDE.md\n\nFollow all instructions in the root AGENTS.md file as the primary context for this repository.\n`,
144
255
  force, dryRun);
145
256
 
146
257
  // ── 4. .aiignore ──────────────────────────────────────────────────
147
- installWrapper(dest, '.aiignore',
258
+ installFile(dest, '.aiignore',
148
259
  `# AI Agent Ignore File
149
260
  # Prevents AI tools from reading sensitive or irrelevant files
150
261
 
@@ -175,6 +286,44 @@ __pycache__/
175
286
  `,
176
287
  force, dryRun);
177
288
 
289
+ // ── 5. Determine tool selections ──────────────────────────────────
290
+ let selectedTools;
291
+
292
+ if (requestedTools) {
293
+ selectedTools = requestedTools;
294
+ } else if (!process.stdin.isTTY) {
295
+ selectedTools = ['claude-code'];
296
+ } else {
297
+ const detected = detectTools(dest);
298
+ selectedTools = await promptToolSelection(detected);
299
+ }
300
+
301
+ // ── 6. Install tool agent files ───────────────────────────────────
302
+ const toolsToInstall = selectedTools.filter(s => s !== 'claude-code');
303
+ const toolResults = [];
304
+
305
+ for (const slug of toolsToInstall) {
306
+ const tool = TOOLS[slug];
307
+
308
+ // Create directories
309
+ if (tool.dirs && !dryRun) {
310
+ for (const d of tool.dirs) {
311
+ mkdirSync(join(dest, d), { recursive: true });
312
+ }
313
+ }
314
+
315
+ // Install agent file
316
+ if (tool.agentFile) {
317
+ const written = installFile(dest, tool.agentFile.path, tool.agentFile.content, force, dryRun);
318
+ toolResults.push({ slug, label: tool.label, path: tool.agentFile.path, written });
319
+ }
320
+ }
321
+
322
+ // ── 7. Write config.yaml ──────────────────────────────────────────
323
+ if (!dryRun && freshInstall) {
324
+ writeFileSync(configPath, defaultConfig(basename(dest)));
325
+ }
326
+
178
327
  // ── Summary ───────────────────────────────────────────────────────
179
328
  if (dryRun) {
180
329
  console.log(`\n${yellow(bold('Dry run complete'))} — nothing was written.\n`);
@@ -185,25 +334,23 @@ __pycache__/
185
334
  console.log(`\n🌱 ${bold('Installation complete!')}\n`);
186
335
  }
187
336
 
188
- console.log(`${bold('Installed:')}`);
189
- console.log(` Core system, ${gardenCmds.length} skill commands, CLAUDE.md, .aiignore\n`);
190
-
191
- console.log(`${bold('Next steps:')}`);
192
- console.log(` 1. Run ${green('claude /garden-bootstrap')} to set up AI-ready documentation`);
193
- console.log(` ${dim('Creates AGENTS.md + wrappers for your AI tools (Copilot, Cursor, etc.)')}`);
194
- console.log(` 2. Run ${green('claude /garden-audit')} to verify accuracy`);
195
- console.log(` 3. Run ${green('claude /garden-extend')} to add guardrails & principles`);
196
-
197
- console.log(`\n${dim('Gary currently runs on Claude Code. Wrappers for other tools are')}`);
198
- console.log(`${dim('created by the agent during bootstrap — run /garden-bootstrap to start.')}`);
337
+ // Tools configured
338
+ console.log(`${bold('Tools configured:')}`);
339
+ console.log(` ${green('✅')} Claude Code → ${dim(`.claude/commands/ (${gardenCmds.length} commands)`)}`);
340
+ for (const r of toolResults) {
341
+ const icon = r.written ? green('') : yellow('⚠️');
342
+ const note = r.written ? '' : dim(' (already existed)');
343
+ console.log(` ${icon} ${r.label.padEnd(15)} ${dim(r.path)}${note}`);
344
+ }
199
345
 
200
- console.log(`\n🪴 Happy gardening!\n`);
346
+ // Garden metaphor
347
+ printGardenWelcome();
201
348
  }
202
349
 
203
350
  function runStatus() {
204
351
  const dest = process.cwd();
205
352
 
206
- console.log(`\n🪴 ${bold('Garden System — Status')}\n`);
353
+ console.log(`\n🪴 ${bold('Gary The Gardener — Status')}\n`);
207
354
 
208
355
  // Core system
209
356
  const coreInstalled = existsSync(join(dest, '_gs-gardener', 'core'));
@@ -227,17 +374,21 @@ function runStatus() {
227
374
 
228
375
  // AGENTS.md
229
376
  const agentsExists = existsSync(join(dest, 'AGENTS.md'));
230
- console.log(` ${agentsExists ? green('✓') : '○'} ${('AGENTS.md').padEnd(15)} ${agentsExists ? green('present') : yellow('not yet created (run /garden-bootstrap)')}`);
377
+ console.log(` ${agentsExists ? green('✓') : '○'} ${'AGENTS.md'.padEnd(18)} ${agentsExists ? green('present') : yellow('not yet created (run /garden-bootstrap)')}`);
231
378
 
232
379
  // .aiignore
233
380
  statusLine('.aiignore', existsSync(join(dest, '.aiignore')), 'present');
234
381
 
235
- // Wrappers (produced by the agent, not the CLI)
236
- console.log(`\n ${dim('Wrappers (created by /garden-bootstrap):')}`);
237
- wrapperLine('Copilot', join(dest, '.github', 'copilot-instructions.md'));
238
- wrapperLine('Cursor', join(dest, '.cursor', 'rules', 'agents.mdc'));
239
- wrapperLine('Codex', join(dest, 'codex.md'));
240
- wrapperLine('Junie', join(dest, '.junie', 'guidelines.md'));
382
+ // Tool agents
383
+ console.log(`\n ${dim('Tool agents:')}`);
384
+ for (const [, tool] of Object.entries(TOOLS)) {
385
+ if (tool.alwaysInstall) continue;
386
+ if (!tool.agentFile) continue;
387
+ const exists = existsSync(join(dest, tool.agentFile.path));
388
+ const icon = exists ? green('✓') : dim('·');
389
+ const text = exists ? green(tool.agentFile.path) : dim('—');
390
+ console.log(` ${icon} ${tool.label.padEnd(18)} ${text}`);
391
+ }
241
392
 
242
393
  console.log('');
243
394
  }
@@ -275,9 +426,6 @@ output_folder: "{project-root}/docs"
275
426
  agents_file: "{project-root}/AGENTS.md"
276
427
  wrapper_files:
277
428
  - "{project-root}/CLAUDE.md"
278
- - "{project-root}/.github/copilot-instructions.md"
279
- - "{project-root}/.cursor/rules/agents.mdc"
280
- - "{project-root}/.junie/guidelines.md"
281
429
 
282
430
  # Content layers
283
431
  docs_directory: "{project-root}/docs"
@@ -291,7 +439,7 @@ version: "${VERSION}"
291
439
  `;
292
440
  }
293
441
 
294
- function installWrapper(dest, relPath, content, force, dryRun) {
442
+ function installFile(dest, relPath, content, force, dryRun) {
295
443
  const fullPath = join(dest, relPath);
296
444
  if (existsSync(fullPath) && !force) {
297
445
  console.log(` ${yellow('⚠')} ${relPath} already exists — skipping`);
@@ -305,14 +453,7 @@ function installWrapper(dest, relPath, content, force, dryRun) {
305
453
  function statusLine(label, installed, detail) {
306
454
  const icon = installed ? green('✓') : '✗';
307
455
  const text = installed ? green(detail || 'installed') : dim('not installed');
308
- console.log(` ${icon} ${label.padEnd(15)} ${text}`);
309
- }
310
-
311
- function wrapperLine(label, filePath) {
312
- const exists = existsSync(filePath);
313
- const icon = exists ? green('✓') : dim('·');
314
- const text = exists ? green('present') : dim('—');
315
- console.log(` ${icon} ${label.padEnd(13)} ${text}`);
456
+ console.log(` ${icon} ${label.padEnd(18)} ${text}`);
316
457
  }
317
458
 
318
459
  function safeReadJson(path) {
@@ -323,37 +464,165 @@ function safeReadJson(path) {
323
464
  }
324
465
  }
325
466
 
467
+ function parseToolsFlag(flagValue) {
468
+ if (flagValue === undefined) return null;
469
+
470
+ const slugs = flagValue.split(',').map(s => s.trim().toLowerCase());
471
+ const invalid = slugs.filter(s => !TOOLS[s]);
472
+ if (invalid.length) {
473
+ console.error(red(`Unknown tool(s): ${invalid.join(', ')}`));
474
+ console.error(`Valid tools: ${TOOL_SLUGS.join(', ')}`);
475
+ process.exit(1);
476
+ }
477
+ if (!slugs.includes('claude-code')) slugs.unshift('claude-code');
478
+ return slugs;
479
+ }
480
+
481
+ function detectTools(dest) {
482
+ const detected = [];
483
+ for (const [slug, tool] of Object.entries(TOOLS)) {
484
+ if (tool.alwaysInstall) {
485
+ detected.push(slug);
486
+ continue;
487
+ }
488
+ for (const probe of tool.detect) {
489
+ if (existsSync(join(dest, probe))) {
490
+ detected.push(slug);
491
+ break;
492
+ }
493
+ }
494
+ }
495
+ return detected;
496
+ }
497
+
498
+ async function promptToolSelection(detected) {
499
+ const entries = OPTIONAL_SLUGS.map((slug, i) => ({
500
+ num: i + 1,
501
+ slug,
502
+ tool: TOOLS[slug],
503
+ selected: detected.includes(slug),
504
+ }));
505
+
506
+ function printList() {
507
+ for (const e of entries) {
508
+ const marker = e.selected ? green('[x]') : '[ ]';
509
+ const hint = detected.includes(e.slug) ? green(' (detected)') : '';
510
+ console.log(` ${e.num}. ${marker} ${e.tool.label}${hint} ${dim(e.tool.agentFile?.path || '')}`);
511
+ }
512
+ }
513
+
514
+ console.log(`\n${bold('Install gardener agent for other AI tools?')}`);
515
+ console.log(dim('Each tool gets a gardener agent that loads from _gs-gardener/.\n'));
516
+ printList();
517
+ console.log(`\n${dim('Enter numbers to toggle (e.g. "1 3"), "all", "none", or press Enter to confirm:')}`);
518
+
519
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
520
+
521
+ try {
522
+ while (true) {
523
+ const answer = await rl.question('> ');
524
+ const trimmed = answer.trim().toLowerCase();
525
+
526
+ if (trimmed === '') break;
527
+
528
+ if (trimmed === 'all') {
529
+ for (const e of entries) e.selected = true;
530
+ } else if (trimmed === 'none' || trimmed === 'skip') {
531
+ for (const e of entries) e.selected = false;
532
+ } else {
533
+ const nums = trimmed.split(/[\s,]+/).map(Number).filter(n => n >= 1 && n <= entries.length);
534
+ for (const n of nums) {
535
+ entries[n - 1].selected = !entries[n - 1].selected;
536
+ }
537
+ }
538
+
539
+ printList();
540
+ console.log(dim('Enter numbers to toggle, or press Enter to confirm:'));
541
+ }
542
+ } finally {
543
+ rl.close();
544
+ }
545
+
546
+ const result = ['claude-code'];
547
+ for (const e of entries) {
548
+ if (e.selected) result.push(e.slug);
549
+ }
550
+ return result;
551
+ }
552
+
553
+ function printGardenWelcome() {
554
+ const cmds = WORKFLOWS.map(w =>
555
+ ` ${w.icon.padEnd(4)} /garden-${w.name.padEnd(12)} ${w.tag}`
556
+ ).join('\n');
557
+
558
+ console.log(`
559
+ Every repository is a garden.
560
+ Code grows. Docs decay. Drift creeps in like weeds.
561
+
562
+ Gary tends to what others forget —
563
+ the README nobody updated,
564
+ the changelog nobody wrote,
565
+ the API docs nobody checked.
566
+
567
+ ${cmds}
568
+
569
+ Your garden is planted. Run ${green('/garden-help')} to begin. 🌻
570
+ `);
571
+ }
572
+
326
573
  function printHelp() {
574
+ const cmds = WORKFLOWS.map(w =>
575
+ ` ${w.icon.padEnd(4)} /garden-${w.name.padEnd(12)} ${w.tag}`
576
+ ).join('\n');
577
+
327
578
  console.log(`
328
- ${bold('gary-the-gardener')} v${VERSION} — Garden System installer
579
+ 🪴 ${bold('Gary The Gardener')} v${VERSION}
580
+ ${'═'.repeat(28)}
581
+
582
+ Every repository is a garden.
583
+ Code grows. Docs decay. Drift creeps in like weeds.
584
+
585
+ Gary tends to what others forget —
586
+ the README nobody updated,
587
+ the changelog nobody wrote,
588
+ the API docs nobody checked.
589
+
590
+ ${cmds}
329
591
 
330
592
  ${bold('USAGE')}
331
593
  npx @pshch/gary-the-gardener [command] [options]
332
594
 
333
595
  ${bold('COMMANDS')}
334
- ${green('install')} Install Gary the Gardener ${dim('(default if no command given)')}
596
+ ${green('install')} Install 🪴 Gary The Gardener ${dim('(default)')}
335
597
  ${green('status')} Show what's currently installed
336
598
 
337
599
  ${bold('WHAT GETS INSTALLED')}
338
- The installer sets up Gary the Gardener agent for Claude Code:
600
+ ${dim('Always (Claude Code the agent host):')}
339
601
  • Core system ${dim('(_gs-gardener/)')}
340
602
  • Skill commands ${dim('(.claude/commands/garden-*.md)')}
341
603
  • CLAUDE.md ${dim('(points Claude to AGENTS.md)')}
342
604
  • .aiignore ${dim('(keeps secrets out of AI context)')}
343
605
 
344
- After installing, run ${green('claude /garden-bootstrap')} to generate
345
- AGENTS.md and wrappers for your other AI tools (Copilot, Cursor, etc.).
606
+ ${dim('Optional (gardener agent for other tools):')}
607
+ Cursor ${dim('(.cursor/rules/garden-agent-gardener.mdc)')}
608
+ • GitHub Copilot ${dim('(.github/agents/gardener.md)')}
609
+ • Windsurf ${dim('(.windsurf/rules/garden-agent-gardener.md)')}
610
+ • JetBrains Junie ${dim('(.junie/guidelines.md)')}
346
611
 
347
612
  ${bold('OPTIONS')}
613
+ -t, --tools Comma-separated tools to install agent for
614
+ ${dim('Valid: cursor, copilot, windsurf, junie')}
615
+ ${dim('If omitted: interactive prompt (or Claude-only if piped)')}
348
616
  -n, --dry-run Show what would be installed, without writing files
349
617
  -f, --force Overwrite existing files
350
618
  -v, --version Show version
351
619
  -h, --help Show this help
352
620
 
353
621
  ${bold('EXAMPLES')}
354
- npx @pshch/gary-the-gardener ${dim('# install the agent')}
355
- npx @pshch/gary-the-gardener install ${dim('# same as above')}
356
- npx @pshch/gary-the-gardener status ${dim('# check install state')}
357
- npx @pshch/gary-the-gardener -f ${dim('# reinstall / upgrade')}
622
+ npx @pshch/gary-the-gardener ${dim('# install with interactive tool selection')}
623
+ npx @pshch/gary-the-gardener --tools cursor ${dim('# install + Cursor agent')}
624
+ npx @pshch/gary-the-gardener -t cursor,copilot ${dim('# install + Cursor + Copilot agents')}
625
+ npx @pshch/gary-the-gardener status ${dim('# check install state')}
626
+ npx @pshch/gary-the-gardener -f ${dim('# reinstall / overwrite')}
358
627
  `);
359
628
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pshch/gary-the-gardener",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Gary the Gardener — AI agent that maintains documentation and config for Claude, Copilot, Cursor, Codex, and Junie",
5
5
  "type": "module",
6
6
  "bin": {