feed-the-machine 1.7.1 → 1.7.2

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/README.md CHANGED
@@ -24,11 +24,23 @@ Think of it like this: regular AI is a blank whiteboard every time you walk into
24
24
 
25
25
  ## Install
26
26
 
27
+ **Everything** (26 skills + 15 hooks):
27
28
  ```bash
28
29
  npx feed-the-machine@latest
29
30
  ```
30
31
 
31
- That's it. Takes 30 seconds. Works with any existing Claude Code setup.
32
+ **Just the skills you want:**
33
+ ```bash
34
+ npx feed-the-machine --only ftm-council-chat,ftm-mind
35
+ ```
36
+ This always includes `ftm` (the router) and `ftm-config` as base dependencies.
37
+
38
+ **See what's available:**
39
+ ```bash
40
+ npx feed-the-machine --list
41
+ ```
42
+
43
+ Works with any existing Claude Code setup. After install, restart Claude Code or start a new session.
32
44
 
33
45
  ---
34
46
 
@@ -64,6 +76,8 @@ Not sure about an architecture choice or debugging approach? This sends the prob
64
76
  ```
65
77
  An AIM-styled browser chatroom where you, Claude, Codex, and Gemini all talk in real time. You're a full participant, not just watching.
66
78
 
79
+ <img src="docs/images/council-chat.png" alt="ftm-council-chat screenshot — AIM-styled chatroom with Claude, Codex, and Gemini debating Jira token types" width="800" />
80
+
67
81
  ---
68
82
 
69
83
  ## Before / After
@@ -165,18 +179,28 @@ FTM ships with 26 skills and 15 automation hooks. You don't need to memorize the
165
179
  | **ftm-pause / resume** | Save and restore. Pick up exactly where you left off in a new conversation |
166
180
  | **ftm-upgrade** | Self-update. Stay current with one command |
167
181
 
168
- ### Hooks (15 automations)
182
+ ### Hooks (16 automations)
169
183
 
170
184
  These run automatically in the background — no slash commands needed:
171
185
 
172
- - **Auto-logging** every tool call gets logged to events.log
173
- - **Learning capture** — extracts lessons from completed tasks
174
- - **Session snapshots** saves state for crash recovery
175
- - **Secret scanning** blocks commits containing credentials
176
- - **Task loading** hydrates tasks from plans on session start
177
- - **Plan gates** enforces plan approval before execution
178
- - **Compaction handlers** preserves critical state when context compresses
179
- - And 8 more for blackboard enforcement, map auto-detection, drafts gating, etc.
186
+ | Hook | What It Does |
187
+ |------|-------------|
188
+ | **event-logger** | Logs every tool use to a JSONL file with debouncing and 30-day rotation |
189
+ | **auto-log** | Detects when you report completing work and reminds Claude to log progress |
190
+ | **learning-capture** | Captures edits and task completions into a learning database for pattern recognition |
191
+ | **session-snapshot** | Saves the last 15 exchanges as a markdown snapshot when a session ends — crash recovery |
192
+ | **session-end** | Marks the current session as completed in the blackboard |
193
+ | **blackboard-enforcer** | Nudges Claude to record meaningful work to the blackboard before a session ends undocumented |
194
+ | **plan-gate** | Prevents code edits without a plan — soft-warns at first, blocks after 3+ edits |
195
+ | **task-loader** | Loads active tasks from your task database when a session starts |
196
+ | **pre-compaction** | Monitors token usage and flushes state to disk before context hits 75% capacity |
197
+ | **post-compaction** | After context compresses, reloads critical state from disk before responding |
198
+ | **map-autodetect** | Detects unmapped projects on first use and bootstraps the code knowledge graph |
199
+ | **post-commit-trigger** | After git commits, triggers code map sync and documentation updates |
200
+ | **pending-sync-check** | Surfaces out-of-session commits that need code map syncing |
201
+ | **discovery-reminder** | Reminds Claude to run a discovery interview for tasks involving external systems or migrations |
202
+ | **drafts-gate** | Blocks Slack and Gmail sends unless a draft file exists first — no accidental messages |
203
+ | **install-hooks** | Self-installer that registers all hooks into Claude Code's settings.json |
180
204
 
181
205
  ---
182
206
 
@@ -236,7 +260,9 @@ Everything else runs on Claude Code alone.
236
260
 
237
261
  ```bash
238
262
  git clone https://github.com/kkudumu/feed-the-machine.git ~/feed-the-machine
239
- cd ~/feed-the-machine && ./install.sh
263
+ cd ~/feed-the-machine && ./install.sh # everything
264
+ # or: ./install.sh --only ftm-council-chat # just one skill
265
+ # or: ./install.sh --list # see what's available
240
266
  ```
241
267
 
242
268
  **Uninstall:** `./uninstall.sh` (removes skills, keeps your memory data)
package/bin/install.mjs CHANGED
@@ -7,9 +7,12 @@
7
7
  * Safe to re-run — idempotent.
8
8
  *
9
9
  * Flags:
10
- * --with-inbox Also install the inbox service
11
- * --no-hooks Skip hooks entirely
12
- * --skip-merge Install hook files but don't touch settings.json
10
+ * --only skill1,skill2 Install specific skills (always includes ftm + ftm-config)
11
+ * --list List available skills with descriptions
12
+ * --with-inbox Also install the inbox service
13
+ * --no-hooks Skip hooks entirely
14
+ * --with-hooks Include hooks even with --only
15
+ * --skip-merge Install hook files but don't touch settings.json
13
16
  */
14
17
 
15
18
  import { existsSync, mkdirSync, readdirSync, lstatSync, readFileSync, writeFileSync, copyFileSync, symlinkSync, unlinkSync, chmodSync, cpSync } from "fs";
@@ -31,8 +34,25 @@ const INBOX_INSTALL_DIR = join(HOME, ".claude", "ftm-inbox");
31
34
 
32
35
  const ARGS = process.argv.slice(2);
33
36
  const WITH_INBOX = ARGS.includes("--with-inbox");
34
- const NO_HOOKS = ARGS.includes("--no-hooks");
35
37
  const SKIP_MERGE = ARGS.includes("--skip-merge");
38
+ const LIST_MODE = ARGS.includes("--list");
39
+ const WITH_HOOKS_FLAG = ARGS.includes("--with-hooks");
40
+
41
+ // Parse --only (supports --only=x,y and --only x,y)
42
+ const ONLY_RAW = ARGS.find(a => a.startsWith("--only="))?.split("=")[1]
43
+ || (ARGS.includes("--only") ? ARGS[ARGS.indexOf("--only") + 1] : null);
44
+
45
+ const ONLY_SKILLS = ONLY_RAW
46
+ ? new Set(["ftm", "ftm-config", ...ONLY_RAW.split(",").map(s => s.trim())])
47
+ : null;
48
+
49
+ // When --only is used, skip hooks unless --with-hooks or explicit --no-hooks
50
+ const NO_HOOKS = ARGS.includes("--no-hooks") || (ONLY_SKILLS && !WITH_HOOKS_FLAG);
51
+
52
+ function skillWanted(name) {
53
+ if (!ONLY_SKILLS) return true;
54
+ return ONLY_SKILLS.has(name);
55
+ }
36
56
 
37
57
  let warnCount = 0;
38
58
 
@@ -268,29 +288,58 @@ function verify(skillCount, hookCount) {
268
288
  return { errors };
269
289
  }
270
290
 
291
+ // --- List Mode ---
292
+
293
+ function listSkills() {
294
+ console.log("\nAvailable FTM skills:\n");
295
+ const ymlFiles = readdirSync(REPO_DIR).filter(
296
+ (f) => f.startsWith("ftm-") && f.endsWith(".yml") && !f.includes("config.default")
297
+ );
298
+ for (const yml of ymlFiles) {
299
+ const name = yml.replace(".yml", "");
300
+ const content = readFileSync(join(REPO_DIR, yml), "utf8");
301
+ const descMatch = content.match(/^description:\s*(.+)/m);
302
+ const desc = descMatch ? descMatch[1].slice(0, 80) : "";
303
+ console.log(` ${name.padEnd(22)} ${desc}`);
304
+ }
305
+ console.log("\nInstall specific skills: npx feed-the-machine --only ftm-council-chat,ftm-mind");
306
+ console.log("Install everything: npx feed-the-machine\n");
307
+ process.exit(0);
308
+ }
309
+
271
310
  // --- Main ---
272
311
 
273
312
  function main() {
313
+ if (LIST_MODE) {
314
+ listSkills();
315
+ }
316
+
274
317
  preflight();
275
318
 
276
- console.log(`Installing ftm skills from: ${REPO_DIR}`);
319
+ if (ONLY_SKILLS) {
320
+ const requested = [...ONLY_SKILLS].filter(s => s !== "ftm" && s !== "ftm-config").join(", ");
321
+ console.log(`Installing selected FTM skills: ${requested} (+ ftm, ftm-config)`);
322
+ } else {
323
+ console.log(`Installing all FTM skills from: ${REPO_DIR}`);
324
+ }
277
325
  console.log(`Linking into: ${SKILLS_DIR}`);
278
326
  console.log("");
279
327
 
280
328
  ensureDir(SKILLS_DIR);
281
329
 
282
- // Link all ftm*.yml files
330
+ // Link ftm*.yml files (filtered by --only if set)
283
331
  const ymlFiles = readdirSync(REPO_DIR).filter(
284
332
  (f) => f.startsWith("ftm") && f.endsWith(".yml") && !f.includes("config.default")
285
- );
333
+ ).filter((f) => skillWanted(f.replace(".yml", "")));
286
334
  for (const yml of ymlFiles) {
287
335
  safeSymlink(join(REPO_DIR, yml), join(SKILLS_DIR, yml));
288
336
  }
289
337
 
290
- // Link all ftm* directories (skills with SKILL.md)
338
+ // Link ftm* directories (filtered by --only if set)
291
339
  const dirs = readdirSync(REPO_DIR).filter((f) => {
292
340
  if (!f.startsWith("ftm")) return false;
293
341
  if (f === "ftm-state") return false;
342
+ if (!skillWanted(f)) return false;
294
343
  const fullPath = join(REPO_DIR, f);
295
344
  try {
296
345
  return lstatSync(fullPath).isDirectory();
Binary file
@@ -108,3 +108,22 @@ If `experiences/index.json` has no usable matches:
108
108
  - continue normally
109
109
  - lean harder on current repo state and direct inspection
110
110
  - record the resulting experience aggressively after completion
111
+
112
+ ## Recording Code Patterns and API Gotchas
113
+
114
+ When writing an experience after task completion, actively check for these:
115
+
116
+ **code_patterns** — If during the task you wrote code that interacts with an API, library, or module, save the **final working version** (not the failed attempts). Include imports, setup, and the actual call. This is the copy-pasteable snippet future sessions will use.
117
+
118
+ **api_gotchas** — If you hit errors because an API behaved differently than expected (wrong return type, unexpected method name, None instead of Response, objects instead of dicts), record each one. Format: what the module is, what's surprising, and what you'd wrongly assume.
119
+
120
+ **playbook_ref** — If a brain.py playbook was created (via ftm-capture or auto-playbook trigger), record the path so the experience and playbook cross-reference each other.
121
+
122
+ **When to populate these fields:**
123
+ - You hit 2+ errors on the same module before getting it right → record code_patterns + api_gotchas
124
+ - You used an API/library for the first time in this project → record code_patterns
125
+ - The auto-playbook hook fired → record all three fields
126
+
127
+ **When to skip:**
128
+ - Pure file editing, config changes, or git operations — no API interaction worth capturing
129
+ - The code pattern is already in an existing experience (check by module name before duplicating)
@@ -37,11 +37,7 @@ Going ahead unless you say otherwise.
37
37
 
38
38
  **Step 0: Discovery Interview (if applicable).** Before generating the plan, check whether a Discovery Interview is needed (see Orient reference). If the task involves external systems, stakeholder coordination, or unfamiliar code, run the interview FIRST.
39
39
 
40
- **Step 1: Generate the plan.** Build a numbered list of concrete steps. Each step has:
41
- - A number
42
- - A one-line description
43
- - The files that will be touched
44
- - The verification method
40
+ **Step 1: Generate the plan.** Build a numbered checkbox list. This format is **mandatory** — no narrative steps, no prose paragraphs. Every plan MUST use: `N. [ ] One-line action → target`. See `references/protocols/PLAN-APPROVAL.md` for the full format spec, examples for code/ops/comms/infra tasks, and the list of NEVER-produce anti-patterns.
45
41
 
46
42
  **Step 2: Parse the user's response.**
47
43
 
@@ -10,11 +10,30 @@ For **medium and large** tasks, present a numbered task list and wait for the us
10
10
 
11
11
  **Step 1: Generate the plan.**
12
12
 
13
- Build a numbered list of concrete steps based on Orient synthesis. Each step must have:
13
+ Build a numbered checkbox list. This format is **mandatory** — no narrative steps, no prose paragraphs, no bullet-point summaries. Every plan, whether it's code, ops, comms, or infrastructure, MUST use this exact format:
14
+
15
+ ```
16
+ N. [ ] One-line action → target (file, channel, system, or person)
17
+ ```
18
+
19
+ Each step must have:
14
20
  - A number
21
+ - A `[ ]` checkbox (literal characters, not rendered)
15
22
  - A one-line description of what will be done
16
- - The files that will be touched
17
- - The verification method (test, lint, visual check, or "self-evident")
23
+ - An arrow `→` pointing to the target: file path for code, channel/email for comms, system name for infra, or "self-evident" for simple actions
24
+ - If applicable, a verification method after the target: `verify: test / lint / visual check / confirmation`
25
+
26
+ **This applies to ALL task types, not just code:**
27
+ - Code tasks: `3. [ ] Add OAuth validation → src/middleware/oauth.ts verify: npm test`
28
+ - Ops/comms tasks: `1. [ ] Reply to Everett requesting domain mapping → Braintrust support thread`
29
+ - Infra tasks: `2. [ ] Create Freshservice webhook trigger → freshservice admin / workflows`
30
+ - Admin tasks: `4. [ ] Close out ITWORK2-9772 → Jira`
31
+
32
+ **NEVER produce:**
33
+ - Narrative paragraphs describing steps
34
+ - Numbered steps without `[ ]` checkboxes
35
+ - Steps with sub-bullets explaining details (put that in execution, not the plan)
36
+ - Headers like "Step 1:" or "### Step 1" — use the numbered checkbox format only
18
37
 
19
38
  Present it like this:
20
39
 
@@ -36,6 +55,21 @@ Approve all? Or tell me what to change.
36
55
  - "deny" or "stop" → cancel entirely
37
56
  ```
38
57
 
58
+ **Ops example:**
59
+
60
+ ```
61
+ Here's my plan for the Braintrust post-SSO setup:
62
+
63
+ 1. [ ] Reply to Everett requesting domain mapping + group mappings → Braintrust support thread
64
+ 2. [ ] Reply to Spencer with admin process answer → #proj-braintrust
65
+ 3. [ ] Request API key from Everett or Spencer → Braintrust org settings
66
+ 4. [ ] Build Freshservice webhook → Braintrust API integration → freshservice workflows + Lambda
67
+ 5. [ ] Reconcile existing users vs Okta groups → Braintrust API + Okta
68
+ 6. [ ] Close out ITWORK2-9772 → Jira
69
+
70
+ Approve all? Or tell me what to change.
71
+ ```
72
+
39
73
  **Step 2: Parse the user's response.**
40
74
 
41
75
  | User says | Action |
@@ -73,6 +73,56 @@
73
73
  "items": {
74
74
  "type": "string"
75
75
  }
76
+ },
77
+ "code_patterns": {
78
+ "type": "array",
79
+ "description": "Working code snippets discovered during this task — the final correct version, not the failed attempts. Include imports, setup, and the actual call pattern.",
80
+ "items": {
81
+ "type": "object",
82
+ "required": ["module", "code"],
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "module": {
86
+ "type": "string",
87
+ "description": "The primary module/library this pattern uses (e.g. shared_services.okta.groups)"
88
+ },
89
+ "code": {
90
+ "type": "string",
91
+ "description": "The working code snippet — complete and copy-pasteable"
92
+ },
93
+ "description": {
94
+ "type": "string",
95
+ "description": "One-line description of what this pattern does"
96
+ }
97
+ }
98
+ }
99
+ },
100
+ "api_gotchas": {
101
+ "type": "array",
102
+ "description": "Surprising API behaviors, wrong assumptions, and type mismatches discovered during this task. Things that caused errors and would cause them again without this note.",
103
+ "items": {
104
+ "type": "object",
105
+ "required": ["module", "gotcha"],
106
+ "additionalProperties": false,
107
+ "properties": {
108
+ "module": {
109
+ "type": "string",
110
+ "description": "The module/API where the gotcha was discovered"
111
+ },
112
+ "gotcha": {
113
+ "type": "string",
114
+ "description": "What's surprising or wrong — e.g. 'list_members() returns OktaUser objects, not dicts'"
115
+ },
116
+ "wrong_assumption": {
117
+ "type": "string",
118
+ "description": "What you'd naturally assume that turns out to be wrong"
119
+ }
120
+ }
121
+ }
122
+ },
123
+ "playbook_ref": {
124
+ "type": "string",
125
+ "description": "Path to the brain.py playbook entry if one was created from this experience, for cross-referencing"
76
126
  }
77
127
  }
78
128
  }
@@ -100,6 +100,139 @@ except Exception:
100
100
  fi
101
101
  fi
102
102
 
103
+ # --- Auto-Playbook Trigger: detect repeated errors then success on same module ---
104
+ ERROR_TRACKER="$FTM_STATE/.error-tracker.jsonl"
105
+
106
+ if [ "$TOOL_NAME" = "Bash" ]; then
107
+ # Extract module/import and error status from the tool result
108
+ ANALYSIS=$(printf '%s' "$PAYLOAD" | python3 -c '
109
+ import json, sys
110
+ try:
111
+ d = json.load(sys.stdin)
112
+ output = d.get("tool_result", "") or ""
113
+ tool_input = d.get("tool_input", {})
114
+ command = tool_input.get("command", "") if isinstance(tool_input, dict) else ""
115
+ is_error = "Error:" in output or "Traceback" in output or "AttributeError" in output or "TypeError" in output or "ImportError" in output or "ModuleNotFoundError" in output
116
+ # Extract the primary module from import statements in the command
117
+ module = ""
118
+ for line in command.split("\n"):
119
+ line = line.strip()
120
+ if line.startswith("from ") and " import " in line:
121
+ module = line.split("from ")[1].split(" import")[0].strip()
122
+ break
123
+ elif line.startswith("import "):
124
+ module = line.split("import ")[1].split()[0].strip()
125
+ break
126
+ import json as j
127
+ j.dump({"is_error": is_error, "module": module}, sys.stdout)
128
+ except Exception:
129
+ import json as j
130
+ j.dump({"is_error": False, "module": ""}, sys.stdout)
131
+ ' 2>/dev/null)
132
+
133
+ IS_ERROR=$(printf '%s' "$ANALYSIS" | python3 -c 'import json,sys; print("1" if json.load(sys.stdin).get("is_error") else "0")' 2>/dev/null)
134
+ MODULE=$(printf '%s' "$ANALYSIS" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("module",""))' 2>/dev/null)
135
+
136
+ if [ -n "$MODULE" ]; then
137
+ TIMESTAMP=$(date +%s)
138
+ if [ "$IS_ERROR" = "1" ]; then
139
+ # Append error event
140
+ echo "{\"ts\":$TIMESTAMP,\"module\":\"$MODULE\",\"type\":\"error\"}" >> "$ERROR_TRACKER"
141
+ else
142
+ # Success — check if this module had 3+ recent errors
143
+ if [ -f "$ERROR_TRACKER" ]; then
144
+ ERROR_COUNT=$(python3 -c "
145
+ import json, sys
146
+ count = 0
147
+ cutoff = $TIMESTAMP - 600 # last 10 minutes
148
+ for line in open('$ERROR_TRACKER'):
149
+ line = line.strip()
150
+ if not line: continue
151
+ try:
152
+ ev = json.loads(line)
153
+ if ev.get('module') == '$MODULE' and ev.get('type') == 'error' and ev.get('ts', 0) >= cutoff:
154
+ count += 1
155
+ except: pass
156
+ print(count)
157
+ " 2>/dev/null)
158
+
159
+ if [ "$ERROR_COUNT" -ge 3 ]; then
160
+ # Write a blackboard experience with the module and error count
161
+ SLUG=$(echo "$MODULE" | tr '.' '-' | tr '[:upper:]' '[:lower:]')
162
+ DATE_STAMP=$(date +%Y-%m-%d)
163
+ EXP_FILE="$FTM_STATE/blackboard/experiences/${DATE_STAMP}_auto-playbook-${SLUG}.json"
164
+ python3 -c "
165
+ import json, os, datetime
166
+ exp = {
167
+ 'task_type': 'api-discovery',
168
+ 'task_description': 'Auto-captured: $ERROR_COUNT errors on $MODULE before finding the working pattern',
169
+ 'lessons_learned': [
170
+ '$MODULE required $ERROR_COUNT attempts to get right — working pattern saved as playbook',
171
+ 'Check experience code_patterns before using this module in future sessions'
172
+ ],
173
+ 'code_patterns': [],
174
+ 'api_gotchas': [],
175
+ 'playbook_ref': None,
176
+ 'timestamp': datetime.datetime.utcnow().isoformat() + 'Z',
177
+ 'confidence': 0.7,
178
+ 'tags': ['auto-playbook', '$(echo "$MODULE" | tr "." " ")']
179
+ }
180
+ os.makedirs(os.path.dirname('$EXP_FILE'), exist_ok=True)
181
+ with open('$EXP_FILE', 'w') as f:
182
+ json.dump(exp, f, indent=2)
183
+
184
+ # Update index
185
+ idx_path = '$FTM_STATE/blackboard/experiences/index.json'
186
+ try:
187
+ with open(idx_path) as f:
188
+ idx = json.load(f)
189
+ except:
190
+ idx = {'entries': [], 'metadata': {'total_count': 0, 'last_updated': None, 'max_entries': 200, 'pruning_strategy': 'remove_oldest_low_confidence'}}
191
+
192
+ idx['entries'].append({
193
+ 'id': 'auto-playbook-${SLUG}-${DATE_STAMP}',
194
+ 'file': os.path.basename('$EXP_FILE'),
195
+ 'task_type': 'api-discovery',
196
+ 'tags': ['auto-playbook', '$(echo "$MODULE" | tr "." " ")'],
197
+ 'timestamp': exp['timestamp'],
198
+ 'confidence': 0.7
199
+ })
200
+ idx['metadata']['total_count'] = len(idx['entries'])
201
+ idx['metadata']['last_updated'] = exp['timestamp']
202
+ with open(idx_path, 'w') as f:
203
+ json.dump(idx, f, indent=2)
204
+ " 2>/dev/null
205
+
206
+ echo ""
207
+ echo "[Auto-Playbook] Detected $ERROR_COUNT errors on '$MODULE' followed by a success in this session."
208
+ echo "A skeleton experience has been saved to the blackboard at: $EXP_FILE"
209
+ echo "NOW you MUST do two things:"
210
+ echo "1. Proactively invoke ftm-capture to save the working code pattern as a playbook"
211
+ echo "2. Update the experience file at $EXP_FILE — fill in the code_patterns array with the working snippet and api_gotchas with every wrong assumption you hit"
212
+ echo "Tell the user: \"That was rough — I'm saving the working pattern so next time it's one clean shot.\""
213
+ echo ""
214
+
215
+ # Clear tracked errors for this module so we don't re-trigger
216
+ python3 -c "
217
+ import json
218
+ lines = []
219
+ for line in open('$ERROR_TRACKER'):
220
+ line = line.strip()
221
+ if not line: continue
222
+ try:
223
+ ev = json.loads(line)
224
+ if ev.get('module') != '$MODULE':
225
+ lines.append(line)
226
+ except: pass
227
+ with open('$ERROR_TRACKER', 'w') as f:
228
+ f.write('\n'.join(lines) + '\n' if lines else '')
229
+ " 2>/dev/null
230
+ fi
231
+ fi
232
+ fi
233
+ fi
234
+ fi
235
+
103
236
  # --- Feed Playbook Tracer if active trace exists ---
104
237
  ACTIVE_TRACE_FILE="$FTM_STATE/.active-trace-id"
105
238
  if [ -f "$ACTIVE_TRACE_FILE" ] && [ -f "$BRAIN_PY" ]; then
package/install.sh CHANGED
@@ -7,9 +7,13 @@ set -euo pipefail
7
7
  # Safe to re-run — idempotent.
8
8
  #
9
9
  # Usage:
10
- # ./install.sh # Full install (skills + hooks + settings merge)
11
- # ./install.sh --no-hooks # Skills and state only, skip hooks entirely
12
- # ./install.sh --skip-merge # Install hook files but don't touch settings.json
10
+ # ./install.sh # Full install (all skills + hooks)
11
+ # ./install.sh --only ftm-council-chat # Install specific skill(s)
12
+ # ./install.sh --only ftm-mind,ftm-debug # Install multiple specific skills
13
+ # ./install.sh --list # List available skills
14
+ # ./install.sh --no-hooks # Skills only, skip hooks
15
+ # ./install.sh --only ftm-mind --with-hooks # Specific skills + all hooks
16
+ # ./install.sh --skip-merge # Install hook files but don't touch settings.json
13
17
 
14
18
  REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
15
19
  SKILLS_DIR="$HOME/.claude/skills"
@@ -20,21 +24,85 @@ SETTINGS_FILE="$CONFIG_DIR/settings.json"
20
24
 
21
25
  NO_HOOKS=false
22
26
  SKIP_MERGE=false
23
- for arg in "$@"; do
27
+ WITH_HOOKS=false
28
+ LIST_MODE=false
29
+ ONLY_SKILLS=""
30
+
31
+ i=1
32
+ while [ "$i" -le "$#" ]; do
33
+ arg="${!i}"
24
34
  case "$arg" in
25
35
  --no-hooks) NO_HOOKS=true ;;
26
36
  --skip-merge) SKIP_MERGE=true ;;
37
+ --with-hooks) WITH_HOOKS=true ;;
38
+ --list) LIST_MODE=true ;;
39
+ --only=*) ONLY_SKILLS="${arg#--only=}" ;;
40
+ --only)
41
+ i=$((i + 1))
42
+ ONLY_SKILLS="${!i}"
43
+ ;;
27
44
  # Keep --setup-hooks for backwards compat (now a no-op since merge is default)
28
45
  --setup-hooks) ;;
29
46
  esac
47
+ i=$((i + 1))
30
48
  done
31
49
 
50
+ # When --only is used, skip hooks by default unless --with-hooks
51
+ if [ -n "$ONLY_SKILLS" ] && [ "$WITH_HOOKS" != true ]; then
52
+ NO_HOOKS=true
53
+ fi
54
+
32
55
  WARN_COUNT=0
33
56
  warn() {
34
57
  echo " WARN: $1"
35
58
  WARN_COUNT=$((WARN_COUNT + 1))
36
59
  }
37
60
 
61
+ # --- List Mode ---
62
+
63
+ if [ "$LIST_MODE" = true ]; then
64
+ echo ""
65
+ echo "Available FTM skills:"
66
+ echo ""
67
+ for yml in "$REPO_DIR"/ftm-*.yml; do
68
+ [ -f "$yml" ] || continue
69
+ name=$(basename "$yml" .yml)
70
+ [[ "$name" == *".default"* ]] && continue
71
+ desc=$(grep '^description:' "$yml" | head -1 | sed 's/^description: *//' | cut -c1-80)
72
+ printf " %-22s %s\n" "$name" "$desc"
73
+ done
74
+ echo ""
75
+ echo "Install specific skills: ./install.sh --only ftm-council-chat,ftm-mind"
76
+ echo "Install everything: ./install.sh"
77
+ exit 0
78
+ fi
79
+
80
+ # --- Skill Filter ---
81
+
82
+ declare -a SKILL_FILTER=()
83
+ if [ -n "$ONLY_SKILLS" ]; then
84
+ # Always include base dependencies
85
+ SKILL_FILTER+=("ftm" "ftm-config")
86
+ IFS=',' read -ra REQUESTED <<< "$ONLY_SKILLS"
87
+ for s in "${REQUESTED[@]}"; do
88
+ s=$(echo "$s" | xargs) # trim whitespace
89
+ SKILL_FILTER+=("$s")
90
+ done
91
+ fi
92
+
93
+ skill_wanted() {
94
+ local name="$1"
95
+ if [ ${#SKILL_FILTER[@]} -eq 0 ]; then
96
+ return 0 # no filter = install all
97
+ fi
98
+ for wanted in "${SKILL_FILTER[@]}"; do
99
+ if [ "$name" = "$wanted" ]; then
100
+ return 0
101
+ fi
102
+ done
103
+ return 1
104
+ }
105
+
38
106
  # --- Preflight Checks ---
39
107
 
40
108
  echo "Preflight checks..."
@@ -76,7 +144,11 @@ else
76
144
  fi
77
145
 
78
146
  echo ""
79
- echo "Installing FTM skills from: $REPO_DIR"
147
+ if [ -n "$ONLY_SKILLS" ]; then
148
+ echo "Installing selected FTM skills: $ONLY_SKILLS (+ ftm, ftm-config)"
149
+ else
150
+ echo "Installing all FTM skills from: $REPO_DIR"
151
+ fi
80
152
  echo "Linking into: $SKILLS_DIR"
81
153
  echo ""
82
154
 
@@ -84,12 +156,13 @@ mkdir -p "$SKILLS_DIR"
84
156
 
85
157
  # --- Skills ---
86
158
 
87
- # Link all ftm*.yml files
159
+ # Link ftm*.yml files (filtered by --only if set)
88
160
  for yml in "$REPO_DIR"/ftm*.yml; do
89
161
  [ -f "$yml" ] || continue
90
162
  name=$(basename "$yml")
91
163
  # Skip ftm-config.default.yml — it's a template, not a skill
92
164
  [[ "$name" == *".default."* ]] && continue
165
+ skill_wanted "${name%.yml}" || continue
93
166
  target="$SKILLS_DIR/$name"
94
167
  if [ -L "$target" ]; then
95
168
  rm "$target"
@@ -101,11 +174,12 @@ for yml in "$REPO_DIR"/ftm*.yml; do
101
174
  echo " LINK $name"
102
175
  done
103
176
 
104
- # Link all ftm* directories (skills with SKILL.md)
177
+ # Link ftm* directories (filtered by --only if set)
105
178
  for dir in "$REPO_DIR"/ftm*/; do
106
179
  [ -d "$dir" ] || continue
107
180
  name=$(basename "$dir")
108
181
  [ "$name" = "ftm-state" ] && continue # state is handled separately
182
+ skill_wanted "$name" || continue
109
183
  target="$SKILLS_DIR/$name"
110
184
  if [ -L "$target" ]; then
111
185
  rm "$target"
@@ -121,6 +195,8 @@ SKILL_COUNT=0
121
195
  for _f in "$REPO_DIR"/ftm*.yml; do
122
196
  [ -e "$_f" ] || continue
123
197
  case "$_f" in *.default.*) continue ;; esac
198
+ _name=$(basename "$_f" .yml)
199
+ skill_wanted "$_name" || continue
124
200
  SKILL_COUNT=$((SKILL_COUNT + 1))
125
201
  done
126
202
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feed-the-machine",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "A brain upgrade for Claude Code — 26 skills that teach it how to think before acting, remember across conversations, debug like a war room, run plans on autopilot with agent teams, and get second opinions from GPT & Gemini. Plus 15 hooks that automate the boring stuff.",
5
5
  "license": "MIT",
6
6
  "author": "kkudumu",
@@ -26,6 +26,7 @@
26
26
  "bin/",
27
27
  "hooks/",
28
28
  "docs/HOOKS.md",
29
+ "docs/images/",
29
30
  "install.sh",
30
31
  "uninstall.sh",
31
32
  "ftm.yml",