liteagents 2.5.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +51 -45
  3. package/installer/cli.js +1 -1
  4. package/installer/installation-engine.js +82 -21
  5. package/installer/package-manager.js +34 -3
  6. package/package.json +3 -2
  7. package/packages/ampcode/AGENT.md +1 -0
  8. package/packages/ampcode/commands/friction/friction.js +19 -4
  9. package/packages/ampcode/commands/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  10. package/packages/ampcode/commands/live-canvas/README.md +273 -0
  11. package/packages/ampcode/commands/live-canvas/templates/.claude/settings.local.json +8 -0
  12. package/packages/ampcode/commands/live-canvas/templates/demo/post-variants.html +205 -0
  13. package/packages/ampcode/commands/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  14. package/packages/ampcode/commands/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  15. package/packages/ampcode/commands/live-canvas/templates/feedback-react/index.ts +62 -0
  16. package/packages/ampcode/commands/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  17. package/packages/ampcode/commands/live-canvas/templates/feedback-react/types.ts +118 -0
  18. package/packages/ampcode/commands/live-canvas/templates/overlay-vanilla.js +477 -0
  19. package/packages/ampcode/commands/live-canvas.md +1104 -0
  20. package/packages/claude/CLAUDE.md +1 -0
  21. package/packages/claude/commands/friction/friction.js +19 -4
  22. package/packages/claude/plugins/live-canvas-marketplace/.claude-plugin/marketplace.json +14 -0
  23. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/.claude-plugin/plugin.json +18 -0
  24. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/README.md +89 -0
  25. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/package-lock.json +1142 -0
  26. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/package.json +17 -0
  27. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/schema.json +37 -0
  28. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/server.js +179 -0
  29. package/packages/claude/plugins/live-canvas-marketplace/setup.sh +61 -0
  30. package/packages/claude/skills/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  31. package/packages/claude/skills/live-canvas/INTEGRATION_NOTES.md +210 -0
  32. package/packages/claude/skills/live-canvas/README.md +273 -0
  33. package/packages/claude/skills/live-canvas/SKILL.md +1119 -0
  34. package/packages/claude/skills/live-canvas/templates/.claude/settings.local.json +8 -0
  35. package/packages/claude/skills/live-canvas/templates/demo/post-variants.html +205 -0
  36. package/packages/claude/skills/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  37. package/packages/claude/skills/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  38. package/packages/claude/skills/live-canvas/templates/feedback-react/index.ts +62 -0
  39. package/packages/claude/skills/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  40. package/packages/claude/skills/live-canvas/templates/feedback-react/types.ts +118 -0
  41. package/packages/claude/skills/live-canvas/templates/overlay-vanilla.js +477 -0
  42. package/packages/claude/variants.json +2 -1
  43. package/packages/droid/AGENTS.md +1 -0
  44. package/packages/droid/commands/friction/friction.js +19 -4
  45. package/packages/droid/commands/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  46. package/packages/droid/commands/live-canvas/README.md +273 -0
  47. package/packages/droid/commands/live-canvas/templates/.claude/settings.local.json +8 -0
  48. package/packages/droid/commands/live-canvas/templates/demo/post-variants.html +205 -0
  49. package/packages/droid/commands/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  50. package/packages/droid/commands/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  51. package/packages/droid/commands/live-canvas/templates/feedback-react/index.ts +62 -0
  52. package/packages/droid/commands/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  53. package/packages/droid/commands/live-canvas/templates/feedback-react/types.ts +118 -0
  54. package/packages/droid/commands/live-canvas/templates/overlay-vanilla.js +477 -0
  55. package/packages/droid/commands/live-canvas.md +1104 -0
  56. package/packages/opencode/AGENTS.md +1 -0
  57. package/packages/opencode/command/friction/friction.js +19 -4
  58. package/packages/opencode/command/live-canvas/DESIGN_PRINCIPLES.md +2140 -0
  59. package/packages/opencode/command/live-canvas/README.md +273 -0
  60. package/packages/opencode/command/live-canvas/templates/.claude/settings.local.json +8 -0
  61. package/packages/opencode/command/live-canvas/templates/demo/post-variants.html +205 -0
  62. package/packages/opencode/command/live-canvas/templates/feedback-react/FeedbackOverlay.tsx +1299 -0
  63. package/packages/opencode/command/live-canvas/templates/feedback-react/format-utils.ts +283 -0
  64. package/packages/opencode/command/live-canvas/templates/feedback-react/index.ts +62 -0
  65. package/packages/opencode/command/live-canvas/templates/feedback-react/selector-utils.ts +423 -0
  66. package/packages/opencode/command/live-canvas/templates/feedback-react/types.ts +118 -0
  67. package/packages/opencode/command/live-canvas/templates/overlay-vanilla.js +477 -0
  68. package/packages/opencode/command/live-canvas.md +1104 -0
  69. package/packages/opencode/opencode.jsonc +4 -0
  70. package/packages/subagentic-manual.md +15 -12
package/CHANGELOG.md CHANGED
@@ -17,6 +17,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
17
 
18
18
  ---
19
19
 
20
+ ## [2.6.0] - 2026-04-19
21
+
22
+ ### Added
23
+ - **live-canvas skill** — Design interview, generates 5 UI variations, collects click-to-annotate feedback from the browser, produces a final implementation plan. Available as a skill in Claude Code and as a command in Droid/Ampcode/Opencode.
24
+ - **Vanilla overlay** (`overlay-vanilla.js`, ~400 lines, zero deps) — framework-agnostic click-to-annotate HUD with pin placement, selector inference (`data-testid` > `id` > class chain, CSS-in-JS hashes filtered), variant detection via `data-variant` attribute, and a v1.0 schema wire-compatible with the upstream React template.
25
+ - **React overlay** (`FeedbackOverlay.tsx`) kept for React/Next/Vite projects.
26
+ - **Demo** (`templates/demo/post-variants.html`) — standalone 5-variant post card playground for review without starting a dev server.
27
+ - **Handholding activation flow** — Phase 0 probes the channel on every invocation and offers `AskUserQuestion` choices: first-time users get the full 4-step setup; returning users who forgot the dev flag get the restart command; channel-up sessions proceed silently.
28
+ - **live-canvas-channel plugin** (Claude Code only) — MCP channel server that bridges the overlay's HTTP POSTs into the live session as `notifications/claude/channel` events. Packaged as a Claude Code marketplace under `packages/claude/plugins/live-canvas-marketplace/`.
29
+ - HTTP listener on `127.0.0.1:8788` with `/health` probe and `/feedback` POST endpoint
30
+ - MCP server using `@modelcontextprotocol/sdk` with `experimental: {'claude/channel': {}}` capability
31
+ - `instructions` field added to tell Claude how to act on incoming `<channel source="live-canvas">` tags: acknowledge in chat → locate variant file → edit → confirm
32
+ - `setup.sh` helper that runs `npm install` and prints the 3 remaining manual steps
33
+ - **Installer: plugins as first-class category** — `packages/<tool>/plugins/` now discovered, selected per variant, and copied to `<target>/plugins/`, parallel to skills. `node_modules/` excluded during copy and size computation. Manifest generation includes a `plugins` component count, installed-files list, and path entry.
34
+ - **Friction report: project attribution in cluster output** — `antigen_review.md` now shows which projects each cluster spans (new "Projects" column in the summary table; new `**Projects:**` line per cluster). Data was always in `session_id`; the previous version dropped it during clustering.
35
+
36
+ ### Changed
37
+ - **README restructured for Hot Memory visibility** — new top-level `🧠 Hot Memory` section between Quick Start and What's Included, with pipeline diagram and sample friction output. Manual Skills/Commands list regrouped into named sub-sections (Hot Memory Pipeline, Design, Workflow & analysis). Old duplicated Hot Memory section removed.
38
+ - **Command/skill count 22 → 23** per tool across README, subagentic-manual, and per-package `AGENTS.md` / `AGENT.md` / `CLAUDE.md`.
39
+ - **opencode.jsonc** — added `live-canvas` entry under `"command"` block.
40
+
41
+ ### Fixed
42
+ - **Friction clustering dropped project names** — cluster object converted `{sessionId: true}` dict to a count before rendering, so `antigen_review.md` never surfaced which repos contributed to each pattern. Fix preserves `session_ids[]` and `projects[]` on every cluster.
43
+ - **Live Canvas overlay counter flicker in live mode** — counter went 0→1→0 during successful push roundtrip. Rewrote so successful live pushes never enter the pending-batch state; counter stays at 0 unless a push actually fails.
44
+
45
+ ### Notes
46
+ - The Claude Code channel plugin is subject to Claude Code's research preview: custom channels require `--dangerously-load-development-channels` at session start, and steps 2-4 of setup (`/plugin marketplace add`, `/plugin install`, session restart) cannot be automated. The skill prints copyable commands and an alias suggestion.
47
+ - Droid, Ampcode, and Opencode run live-canvas in batch mode only — channels are Claude-Code-specific.
48
+
49
+ ---
50
+
20
51
  ## [2.5.2] - 2026-02-11
21
52
 
22
53
  ### Added
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ╚══════╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
10
10
  ```
11
11
 
12
- **AI development toolkit with 11 specialized agents and 22 commands per tool**
12
+ **AI development toolkit with 11 specialized agents and 23 commands per tool**
13
13
 
14
14
  [![npm version](https://img.shields.io/npm/v/liteagents)](https://www.npmjs.com/package/liteagents)
15
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -43,10 +43,10 @@ liteagents
43
43
 
44
44
  ### Supported Tools
45
45
 
46
- - **Claude Code** - 11 subagents + 10 skills + 12 commands
47
- - **Opencode** - 11 agent references + 22 commands
48
- - **Ampcode** - 11 subagents + 22 commands
49
- - **Droid** - 11 agent references + 22 commands
46
+ - **Claude Code** - 11 subagents + 11 skills + 12 commands (+ optional live-canvas channel plugin)
47
+ - **Opencode** - 11 agent references + 23 commands
48
+ - **Ampcode** - 11 subagents + 23 commands
49
+ - **Droid** - 11 agent references + 23 commands
50
50
 
51
51
  **Key Difference:**
52
52
  - **Claude Code**: Full subagent system with orchestrator + skills (auto-triggering)
@@ -69,6 +69,39 @@ liteagents
69
69
 
70
70
  ---
71
71
 
72
+ ## 🧠 Hot Memory — project-local learning from your own sessions
73
+
74
+ Liteagents ships a three-command pipeline that turns Claude Code's session logs into project-local memory. No databases, no external services, just markdown files the assistant reads via `@MEMORY.md`.
75
+
76
+ ```
77
+ /stash → /friction → /remember
78
+ capture analyze consolidate
79
+ ```
80
+
81
+ - **`/stash`** — snapshot the current session's context before compaction or handoff
82
+ - **`/friction`** — mine JSONL session logs for frustration signals, failed flows, and abandonment patterns; cluster them into antigen candidates per project
83
+ - **`/remember`** — consolidate stashes + friction antigens into `.claude/memory/MEMORY.md`; auto-injected into `CLAUDE.md` via `@MEMORY.md` so every future session in the project benefits
84
+
85
+ What you get is a memory that *learns from your own mistakes and interventions*, grows quietly in your repo, and works anywhere Claude Code runs. The friction analyzer alone scans all your projects and gives you a per-repo reliability verdict:
86
+
87
+ ```
88
+ Per-Project:
89
+ my-app 56% BAD (40/72) median: 16.0 🔴
90
+ api-service 40% BAD (2/5) median: 0.5 🟡
91
+ web-client 0% BAD (0/1) median: 0.0 ✅
92
+
93
+ WORST: my-app/0203-1630-11eb903a peak=225 turns=127
94
+ BEST: web-client/0202-2121-8d8608e1 peak=0 turns=4
95
+
96
+ Verdict: ✓ USEFUL Intervention predictability: 93%
97
+ ```
98
+
99
+ Results land in `.claude/friction/antigen_review.md` with projects, error patterns, and offending tool sequences called out per cluster — so `/remember` can pick them up and encode them as rules the next session sees.
100
+
101
+ > This is the thing in liteagents that nothing else ships. Normal skill bundles give you instructions. The hot-memory pipeline gives you instructions the assistant wrote for itself, from your own logs.
102
+
103
+ ---
104
+
72
105
  ## 🤖 What's Included
73
106
 
74
107
  ### 11 Agents
@@ -88,14 +121,24 @@ liteagents
88
121
  - **system-architect** - System design, technology selection, API design, scalability planning
89
122
  - **ui-designer** - UI/UX design, wireframes, prototypes, accessibility, design systems
90
123
 
91
- ### 22 Commands/Skills
124
+ ### 23 Commands/Skills
92
125
 
93
126
  **Auto-Triggering Skills (3)** - Claude Code only:
94
127
  - **test-driven-development** - Write test first, watch fail, minimal passing code
95
128
  - **testing-anti-patterns** - Prevent mocking anti-patterns
96
129
  - **verification-before-completion** - Verify before claiming done
97
130
 
98
- **Manual Skills/Commands (19):**
131
+ **Manual Skills/Commands (20):**
132
+
133
+ *Hot Memory Pipeline (3)* — see the [🧠 Hot Memory](#-hot-memory--project-local-learning-from-your-own-sessions) section above for the full walkthrough:
134
+ - **stash** - Snapshot session context to `.claude/stash/` before compaction, handoff, or ending complex work
135
+ - **friction** - Analyze all JSONL sessions for frustration signals, cluster failures per project, surface antigens
136
+ - **remember** - Consolidate stashes + friction antigens into `.claude/memory/MEMORY.md`; auto-injected via `@MEMORY.md`
137
+
138
+ *Design*:
139
+ - **live-canvas** - Design UI variations with click-to-annotate feedback in the browser; ships a companion MCP channel plugin for Claude Code so Saves stream into the session in real time. Other tools use batch mode.
140
+
141
+ *Workflow & analysis*:
99
142
  - **brainstorming** - Structured brainstorming sessions
100
143
  - **code-review** - Implementation review against requirements
101
144
  - **condition-based-waiting** - Replace timeouts with condition polling
@@ -105,52 +148,15 @@ liteagents
105
148
  - **systematic-debugging** - Four-phase debugging framework
106
149
  - **debug** - Systematic issue investigation
107
150
  - **explain** - Explain code for newcomers
108
- - **friction** - Analyze session logs for failure patterns and behavioral signals
109
151
  - **git-commit** - Intelligent commit creation
110
152
  - **optimize** - Performance analysis
111
153
  - **refactor** - Safe refactoring with behavior preservation
112
- - **remember** - Consolidate stashes + friction into project memory
113
154
  - **review** - Comprehensive code review
114
155
  - **security** - Vulnerability scanning
115
156
  - **ship** - Pre-deployment checklist
116
- - **stash** - Save session context for compaction recovery or handoffs
117
157
  - **test-generate** - Generate test suites
118
158
 
119
- ### Hot Memory (3-step pipeline)
120
-
121
- Lightweight session memory that learns from your usage patterns.
122
-
123
- ```
124
- /stash → /friction → /remember
125
- ```
126
-
127
- 1. **`/stash`** - Snapshot current session context to `.claude/stash/`. Use before compaction, handoffs, or ending complex work.
128
- 2. **`/friction`** - Analyze session logs for failure patterns. Scores sessions, clusters failures, outputs actionable antigens.
129
- 3. **`/remember`** - Consolidate stashes + friction into `.claude/memory/MEMORY.md`. Extracts facts, episodes, and behavioral preferences. Injects into CLAUDE.md via `@MEMORY.md`.
130
-
131
- **Result:** Project-local memory that accumulates across sessions — no external dependencies, no databases, just markdown.
132
-
133
- ```bash
134
- # Standalone CLI — analyze all projects
135
- liteagents friction ~/.claude/projects
136
-
137
- Per-Project:
138
- my-app 56% BAD (40/72) median: 16.0 🔴
139
- api-service 40% BAD (2/5) median: 0.5 🟡
140
- web-client 0% BAD (0/1) median: 0.0 ✅
141
-
142
- Session Extremes:
143
- WORST: my-app/0203-1630-11eb903a peak=225 turns=127
144
- BEST: web-client/0202-2121-8d8608e1 peak=0 turns=4
145
-
146
- Last 2 Weeks:
147
- 2026-02-02 15 sessions 10 BAD ██████░░░░ 67%
148
- 2026-02-03 29 sessions 12 BAD ████░░░░░░ 41%
149
- 2026-02-04 6 sessions 2 BAD ███░░░░░░░ 33%
150
-
151
- Verdict: ✓ USEFUL
152
- Intervention predictability: 93%
153
- ```
159
+ > **Claude-only plugin:** `live-canvas-channel` is a bundled Claude Code MCP channel plugin that ships under `~/.claude/plugins/live-canvas-marketplace/`. One-time `/plugin install` + a session started with `--dangerously-load-development-channels` unlocks live mode. Skill probes for the channel on each invocation and handholds setup when missing. See [`packages/claude/skills/live-canvas/README.md`](packages/claude/skills/live-canvas/README.md) for the full walkthrough.
154
160
 
155
161
  ---
156
162
 
package/installer/cli.js CHANGED
@@ -456,7 +456,7 @@ ${colors.bright}${colors.cyan}██╔══██║██║ ██║█
456
456
  ${colors.bright}${colors.cyan}██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║╚██████╗ ██║ ██╗██║ ██║${colors.reset}
457
457
  ${colors.bright}${colors.cyan}╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝${colors.reset}
458
458
 
459
- ${colors.bright}v2.5.3 | 11 agents + 22 commands per tool${colors.reset}
459
+ ${colors.bright}v2.6.0 | 11 agents + 23 commands per tool${colors.reset}
460
460
  `);
461
461
  }
462
462
 
@@ -194,18 +194,23 @@ class InstallationEngine {
194
194
  }
195
195
  };
196
196
 
197
- // Helper function to recursively collect all files in a directory
198
- const collectDirectoryFiles = async (dirPath, relativePath = '') => {
197
+ // Helper function to recursively collect all files in a directory.
198
+ // Skips node_modules directories (callers may set skipNodeModules=true for plugins).
199
+ const collectDirectoryFiles = async (dirPath, relativePath = '', skipNodeModules = false) => {
199
200
  const items = await fs.promises.readdir(dirPath);
200
201
  const filesInDir = [];
201
202
 
202
203
  for (const item of items) {
204
+ if (skipNodeModules && item === 'node_modules') {
205
+ continue;
206
+ }
207
+
203
208
  const itemPath = path.join(dirPath, item);
204
209
  const itemRelativePath = path.join(relativePath, item);
205
210
  const stat = await fs.promises.stat(itemPath);
206
211
 
207
212
  if (stat.isDirectory()) {
208
- const subFiles = await collectDirectoryFiles(itemPath, itemRelativePath);
213
+ const subFiles = await collectDirectoryFiles(itemPath, itemRelativePath, skipNodeModules);
209
214
  filesInDir.push(...subFiles);
210
215
  } else {
211
216
  filesInDir.push({
@@ -310,6 +315,23 @@ class InstallationEngine {
310
315
  totalBytes += size;
311
316
  }
312
317
 
318
+ // Collect all plugin files (plugins are directories like skills; skip node_modules)
319
+ for (const pluginPath of (packageContents.plugins || [])) {
320
+ const baseRelativePath = path.relative(sourceBase, pluginPath);
321
+ const pluginFiles = await collectDirectoryFiles(pluginPath, baseRelativePath, true);
322
+
323
+ for (const file of pluginFiles) {
324
+ filesToCopy.push({
325
+ sourcePath: file.sourcePath,
326
+ relativePath: file.relativePath,
327
+ size: file.size,
328
+ type: 'plugin'
329
+ });
330
+ totalFiles++;
331
+ totalBytes += file.size;
332
+ }
333
+ }
334
+
313
335
  // Now copy all files with progress tracking
314
336
  let filesCompleted = 0;
315
337
  let bytesTransferred = 0;
@@ -364,6 +386,11 @@ class InstallationEngine {
364
386
  const items = await fs.promises.readdir(source);
365
387
 
366
388
  for (const item of items) {
389
+ // Skip node_modules - callers (plugins, backups) should never ship bundled deps
390
+ if (item === 'node_modules') {
391
+ continue;
392
+ }
393
+
367
394
  const sourcePath = path.join(source, item);
368
395
  const targetPath = path.join(target, item);
369
396
 
@@ -433,19 +460,22 @@ class InstallationEngine {
433
460
  agents: contents.agents.length,
434
461
  skills: contents.skills.length,
435
462
  resources: contents.resources.length,
436
- hooks: contents.hooks.length
463
+ hooks: contents.hooks.length,
464
+ plugins: (contents.plugins || []).length
437
465
  },
438
466
  installedFiles: {
439
467
  agents: contents.agents.map(extractAgentName),
440
468
  skills: contents.skills.map(extractSkillName),
441
469
  resources: contents.resources.map(p => path.basename(p)),
442
- hooks: contents.hooks.map(p => path.basename(p))
470
+ hooks: contents.hooks.map(p => path.basename(p)),
471
+ plugins: (contents.plugins || []).map(extractSkillName)
443
472
  },
444
473
  paths: {
445
474
  agents: path.join(targetPath, 'agents'),
446
475
  skills: path.join(targetPath, 'skills'),
447
476
  resources: path.join(targetPath, 'resources'),
448
- hooks: path.join(targetPath, 'hooks')
477
+ hooks: path.join(targetPath, 'hooks'),
478
+ plugins: path.join(targetPath, 'plugins')
449
479
  },
450
480
  files: {
451
481
  total: contents.totalFiles,
@@ -538,7 +568,7 @@ class InstallationEngine {
538
568
 
539
569
  // Remove files listed in manifest
540
570
  if (manifest.installedFiles) {
541
- for (const category of ['agents', 'skills', 'resources', 'hooks']) {
571
+ for (const category of ['agents', 'skills', 'resources', 'hooks', 'plugins']) {
542
572
  if (manifest.installedFiles[category] && manifest.paths && manifest.paths[category]) {
543
573
  const categoryPath = manifest.paths[category];
544
574
 
@@ -548,8 +578,8 @@ class InstallationEngine {
548
578
  if (category === 'agents') {
549
579
  // Agents have .md extension
550
580
  itemPath = path.join(categoryPath, `${item}.md`);
551
- } else if (category === 'skills') {
552
- // Skills are directories
581
+ } else if (category === 'skills' || category === 'plugins') {
582
+ // Skills and plugins are directories
553
583
  itemPath = path.join(categoryPath, item);
554
584
  } else {
555
585
  // Resources and hooks have their full names
@@ -650,7 +680,7 @@ class InstallationEngine {
650
680
  * @param {string} targetPath - Base installation path
651
681
  */
652
682
  async cleanupEmptyDirectories(targetPath) {
653
- const categories = ['agents', 'skills', 'resources', 'hooks'];
683
+ const categories = ['agents', 'skills', 'resources', 'hooks', 'plugins'];
654
684
 
655
685
  for (const category of categories) {
656
686
  const categoryPath = path.join(targetPath, category);
@@ -762,7 +792,8 @@ class InstallationEngine {
762
792
  agents: { expected: 0, found: 0, missing: [] },
763
793
  skills: { expected: 0, found: 0, missing: [] },
764
794
  resources: { expected: 0, found: 0, missing: [] },
765
- hooks: { expected: 0, found: 0, missing: [] }
795
+ hooks: { expected: 0, found: 0, missing: [] },
796
+ plugins: { expected: 0, found: 0, missing: [] }
766
797
  },
767
798
  timestamp: new Date().toISOString()
768
799
  };
@@ -785,8 +816,16 @@ class InstallationEngine {
785
816
  const manifest = JSON.parse(await fs.promises.readFile(manifestPath, 'utf8'));
786
817
  result.manifest = manifest;
787
818
 
788
- // Verify all required directories exist
819
+ // Verify all required directories exist (skip categories with 0 expected items)
789
820
  for (const [component, componentPath] of Object.entries(manifest.paths || {})) {
821
+ const expectedCount = (manifest.installedFiles && Array.isArray(manifest.installedFiles[component]))
822
+ ? manifest.installedFiles[component].length
823
+ : 0;
824
+
825
+ if (expectedCount === 0) {
826
+ continue;
827
+ }
828
+
790
829
  if (!fs.existsSync(componentPath)) {
791
830
  result.valid = false;
792
831
  result.issues.push({
@@ -799,7 +838,7 @@ class InstallationEngine {
799
838
  }
800
839
 
801
840
  // Verify installed files for each component category
802
- for (const category of ['agents', 'skills', 'resources', 'hooks']) {
841
+ for (const category of ['agents', 'skills', 'resources', 'hooks', 'plugins']) {
803
842
  if (manifest.installedFiles && manifest.installedFiles[category]) {
804
843
  const expectedFiles = manifest.installedFiles[category];
805
844
  result.components[category].expected = expectedFiles.length;
@@ -812,7 +851,7 @@ class InstallationEngine {
812
851
  // Construct full path based on category
813
852
  if (category === 'agents') {
814
853
  itemPath = path.join(categoryPath, `${item}.md`);
815
- } else if (category === 'skills') {
854
+ } else if (category === 'skills' || category === 'plugins') {
816
855
  itemPath = path.join(categoryPath, item);
817
856
  } else {
818
857
  itemPath = path.join(categoryPath, item);
@@ -847,7 +886,7 @@ class InstallationEngine {
847
886
 
848
887
  // Verify file counts match
849
888
  if (manifest.components) {
850
- for (const category of ['agents', 'skills', 'resources', 'hooks']) {
889
+ for (const category of ['agents', 'skills', 'resources', 'hooks', 'plugins']) {
851
890
  if (manifest.components[category] !== undefined) {
852
891
  const expectedCount = manifest.components[category];
853
892
  const foundCount = result.components[category].found;
@@ -967,7 +1006,7 @@ class InstallationEngine {
967
1006
 
968
1007
  // Collect all files from manifest
969
1008
  if (manifest.installedFiles) {
970
- for (const category of ['agents', 'skills', 'resources', 'hooks']) {
1009
+ for (const category of ['agents', 'skills', 'resources', 'hooks', 'plugins']) {
971
1010
  if (manifest.installedFiles[category] && manifest.paths && manifest.paths[category]) {
972
1011
  const categoryPath = manifest.paths[category];
973
1012
 
@@ -977,8 +1016,8 @@ class InstallationEngine {
977
1016
  if (category === 'agents') {
978
1017
  // Agents have .md extension
979
1018
  itemPath = path.join(categoryPath, `${item}.md`);
980
- } else if (category === 'skills') {
981
- // Skills are directories - need to count all files inside
1019
+ } else if (category === 'skills' || category === 'plugins') {
1020
+ // Skills and plugins are directories - need to count all files inside
982
1021
  itemPath = path.join(categoryPath, item);
983
1022
  } else {
984
1023
  // Resources and hooks have their full names
@@ -989,7 +1028,7 @@ class InstallationEngine {
989
1028
  path: itemPath,
990
1029
  category: category,
991
1030
  name: item,
992
- isDirectory: category === 'skills'
1031
+ isDirectory: category === 'skills' || category === 'plugins'
993
1032
  });
994
1033
  }
995
1034
  }
@@ -1443,6 +1482,26 @@ class InstallationEngine {
1443
1482
  }
1444
1483
  }
1445
1484
 
1485
+ // Compare plugins (directories, like skills)
1486
+ const currentPluginsList = currentContents.plugins || [];
1487
+ const newPluginsList = newContents.plugins || [];
1488
+ const currentPlugins = new Set(currentPluginsList.map(p => path.basename(p)));
1489
+ const newPlugins = new Set(newPluginsList.map(p => path.basename(p)));
1490
+
1491
+ for (const plugin of newPluginsList) {
1492
+ const basename = path.basename(plugin);
1493
+ if (!currentPlugins.has(basename)) {
1494
+ toAdd.push({ type: 'plugin', source: plugin, name: basename });
1495
+ }
1496
+ }
1497
+
1498
+ for (const plugin of currentPluginsList) {
1499
+ const basename = path.basename(plugin);
1500
+ if (!newPlugins.has(basename)) {
1501
+ toRemove.push({ type: 'plugin', name: basename, category: 'plugins' });
1502
+ }
1503
+ }
1504
+
1446
1505
  return { toAdd, toRemove };
1447
1506
  }
1448
1507
 
@@ -1456,7 +1515,8 @@ class InstallationEngine {
1456
1515
  agents: new Set(manifest.installedFiles.agents),
1457
1516
  skills: new Set(manifest.installedFiles.skills),
1458
1517
  resources: new Set(manifest.installedFiles.resources),
1459
- hooks: new Set(manifest.installedFiles.hooks)
1518
+ hooks: new Set(manifest.installedFiles.hooks),
1519
+ plugins: new Set(manifest.installedFiles.plugins || [])
1460
1520
  };
1461
1521
 
1462
1522
  for (const file of filesToRemove) {
@@ -1503,7 +1563,8 @@ class InstallationEngine {
1503
1563
  const sourcePath = file.source;
1504
1564
  const targetCategory = file.type === 'agent' ? 'agents' :
1505
1565
  file.type === 'skill' ? 'skills' :
1506
- file.type === 'resource' ? 'resources' : 'hooks';
1566
+ file.type === 'resource' ? 'resources' :
1567
+ file.type === 'plugin' ? 'plugins' : 'hooks';
1507
1568
 
1508
1569
  const targetDir = path.join(targetPath, targetCategory);
1509
1570
 
@@ -137,7 +137,8 @@ class PackageManager {
137
137
  skills: [],
138
138
  commands: [],
139
139
  resources: [],
140
- hooks: []
140
+ hooks: [],
141
+ plugins: []
141
142
  };
142
143
 
143
144
  // Helper function to process each content category
@@ -182,6 +183,7 @@ class PackageManager {
182
183
  selected.commands = selectItems('commands', variantConfig.commands, availableContent.commands || [], true);
183
184
  selected.resources = selectItems('resources', variantConfig.resources, availableContent.resources || []);
184
185
  selected.hooks = selectItems('hooks', variantConfig.hooks, availableContent.hooks || []);
186
+ selected.plugins = selectItems('plugins', variantConfig.plugins, availableContent.plugins || []);
185
187
 
186
188
  return selected;
187
189
  }
@@ -227,6 +229,7 @@ class PackageManager {
227
229
  commands: [],
228
230
  resources: [],
229
231
  hooks: [],
232
+ plugins: [],
230
233
  totalFiles: 0,
231
234
  totalSize: 0
232
235
  };
@@ -295,12 +298,24 @@ class PackageManager {
295
298
  }
296
299
  }
297
300
 
301
+ // Build directory paths for selected plugins (plugins are directories, like skills)
302
+ const pluginsDir = path.join(packageDir, 'plugins');
303
+ if (fs.existsSync(pluginsDir)) {
304
+ for (const plugin of (selectedContent.plugins || [])) {
305
+ const pluginPath = path.join(pluginsDir, plugin);
306
+ if (fs.existsSync(pluginPath)) {
307
+ contents.plugins.push(pluginPath);
308
+ }
309
+ }
310
+ }
311
+
298
312
  // Calculate total files
299
313
  contents.totalFiles = contents.agents.length +
300
314
  contents.skills.length +
301
315
  contents.commands.length +
302
316
  contents.resources.length +
303
- contents.hooks.length;
317
+ contents.hooks.length +
318
+ contents.plugins.length;
304
319
 
305
320
  return contents;
306
321
  }
@@ -349,6 +364,7 @@ class PackageManager {
349
364
  const commandsResult = await getItemsInDir(path.join(packageDir, commandsDir), true);
350
365
  const resourcesResult = await getItemsInDir(path.join(packageDir, 'resources'));
351
366
  const hooksResult = await getItemsInDir(path.join(packageDir, 'hooks'));
367
+ const pluginsResult = await getItemsInDir(path.join(packageDir, 'plugins'));
352
368
 
353
369
  return {
354
370
  agents: agentsResult.items,
@@ -357,7 +373,8 @@ class PackageManager {
357
373
  commands: commandsResult.items,
358
374
  commandsDir: commandsDir,
359
375
  resources: resourcesResult.items,
360
- hooks: hooksResult.items
376
+ hooks: hooksResult.items,
377
+ plugins: pluginsResult.items
361
378
  };
362
379
  }
363
380
 
@@ -399,6 +416,7 @@ class PackageManager {
399
416
  let totalSize = 0;
400
417
 
401
418
  // Helper function to calculate size of a file or directory recursively
419
+ // Skips node_modules directories (plugins may have local installs)
402
420
  const calculatePathSize = async (itemPath) => {
403
421
  try {
404
422
  const stat = await fs.promises.stat(itemPath);
@@ -406,6 +424,11 @@ class PackageManager {
406
424
  if (stat.isFile()) {
407
425
  return stat.size;
408
426
  } else if (stat.isDirectory()) {
427
+ // Skip node_modules to avoid bundling local dependencies
428
+ if (path.basename(itemPath) === 'node_modules') {
429
+ return 0;
430
+ }
431
+
409
432
  // Recursively calculate directory size
410
433
  let dirSize = 0;
411
434
  const items = await fs.promises.readdir(itemPath);
@@ -445,6 +468,11 @@ class PackageManager {
445
468
  totalSize += await calculatePathSize(hookPath);
446
469
  }
447
470
 
471
+ // Calculate size for all selected plugins (directories, excluding node_modules)
472
+ for (const pluginPath of (contents.plugins || [])) {
473
+ totalSize += await calculatePathSize(pluginPath);
474
+ }
475
+
448
476
  return {
449
477
  size: totalSize,
450
478
  formattedSize: this.formatBytes(totalSize)
@@ -601,6 +629,9 @@ class PackageManager {
601
629
  const commandsDirName = availableContent.commandsDir || 'commands';
602
630
  await checkContentExists('commands', selectedContent.commands || [], commandsDirName, true);
603
631
 
632
+ // Validate plugins (directories, like skills)
633
+ await checkContentExists('plugins', selectedContent.plugins || [], 'plugins', false);
634
+
604
635
  } catch (error) {
605
636
  return {
606
637
  valid: false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "liteagents",
3
- "version": "2.5.3",
4
- "description": "AI development toolkit with 11 specialized agents and 22 commands. Simple one-question installer for Claude, Opencode, Ampcode, and Droid.",
3
+ "version": "2.6.0",
4
+ "description": "AI development toolkit with 11 specialized agents and 23 commands including live-canvas UI design with click-to-annotate feedback. Simple one-question installer for Claude, Opencode, Ampcode, and Droid.",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "liteagents": "./installer/cli.js",
@@ -16,6 +16,7 @@
16
16
  "clean:git": "git clean -fdx tests/tmp/",
17
17
  "validate": "node ./scripts/validate-package.js",
18
18
  "validate-packages": "node scripts/validate-all-packages.js",
19
+ "prepack": "rm -rf packages/*/plugins/*/plugins/*/node_modules",
19
20
  "prepublishOnly": "npm run clean && npm run validate",
20
21
  "postinstall": "node postinstall.js"
21
22
  },
@@ -45,6 +45,7 @@ These subagents are available when using Ampcode CLI.
45
45
  | explain | Explain code for someone new to the codebase | /explain <code-section> |
46
46
  | friction | Analyze session logs for failure patterns and behavioral signals | /friction <sessions-path> |
47
47
  | git-commit | Analyze changes and create intelligent git commits | /git-commit |
48
+ | live-canvas | Design UI variations and collect click-to-annotate feedback from the browser (batch mode only on Amp) | /live-canvas |
48
49
  | optimize | Analyze and optimize performance issues | /optimize <target-area> |
49
50
  | refactor | Refactor code while maintaining behavior and tests | /refactor <code-section> |
50
51
  | remember | Consolidate stashes + friction into project memory | /remember |
@@ -1940,12 +1940,21 @@ function clusterCandidates(allCandidates) {
1940
1940
  const clusters = Object.values(clusterMap).map(cl => {
1941
1941
  const weight = signalWeights[cl.anchor_signal] || 1;
1942
1942
  const peaks = cl.peaks.sort((a, b) => a - b);
1943
+ // Session IDs follow "project/dateStr-shortId", so the first segment is
1944
+ // the project. Preserve both the ID list and the deduped project set so
1945
+ // downstream renderers can surface which repos produced the cluster.
1946
+ const sessionIds = Object.keys(cl.sessions);
1947
+ const projects = [...new Set(
1948
+ sessionIds.map(s => s.includes('/') ? s.split('/')[0] : 'unknown')
1949
+ )].sort();
1943
1950
  return {
1944
1951
  anchor_signal: cl.anchor_signal,
1945
1952
  tool_pattern: cl.tool_pattern,
1946
1953
  count: cl.count,
1947
1954
  score: cl.count * weight,
1948
- sessions: Object.keys(cl.sessions).length,
1955
+ sessions: sessionIds.length,
1956
+ session_ids: sessionIds,
1957
+ projects: projects,
1949
1958
  median_peak: peaks[Math.floor(peaks.length / 2)],
1950
1959
  max_peak: peaks[peaks.length - 1],
1951
1960
  contexts: cl.contexts,
@@ -2048,10 +2057,13 @@ function extractMain(sessionsDir) {
2048
2057
 
2049
2058
  // Summary table
2050
2059
  reviewLines.push('## Cluster Summary\n\n');
2051
- reviewLines.push('| # | Signal | Tool Pattern | Count | Sessions | Score | Median Peak |\n');
2052
- reviewLines.push('|---|--------|-------------|-------|----------|-------|-------------|\n');
2060
+ reviewLines.push('| # | Signal | Tool Pattern | Count | Sessions | Projects | Score | Median Peak |\n');
2061
+ reviewLines.push('|---|--------|-------------|-------|----------|----------|-------|-------------|\n');
2053
2062
  reviewClusters.forEach((cl, idx) => {
2054
- reviewLines.push(`| ${idx + 1} | ${cl.anchor_signal} | ${cl.tool_pattern} | ${cl.count} | ${cl.sessions} | ${cl.score} | ${cl.median_peak} |\n`);
2063
+ const projs = cl.projects || [];
2064
+ const projectsShort = projs.slice(0, 3).join(', ') +
2065
+ (projs.length > 3 ? `, +${projs.length - 3}` : '');
2066
+ reviewLines.push(`| ${idx + 1} | ${cl.anchor_signal} | ${cl.tool_pattern} | ${cl.count} | ${cl.sessions} | ${projectsShort || '-'} | ${cl.score} | ${cl.median_peak} |\n`);
2055
2067
  });
2056
2068
  reviewLines.push('\n---\n\n');
2057
2069
 
@@ -2059,6 +2071,9 @@ function extractMain(sessionsDir) {
2059
2071
  reviewClusters.forEach((cl, idx) => {
2060
2072
  reviewLines.push(`## Cluster ${idx + 1}: ${cl.anchor_signal} | ${cl.tool_pattern}\n\n`);
2061
2073
  reviewLines.push(`**Occurrences:** ${cl.count} across ${cl.sessions} sessions | **Score:** ${cl.score} | **Median peak:** ${cl.median_peak} | **Max peak:** ${cl.max_peak}\n\n`);
2074
+ if (cl.projects && cl.projects.length > 0) {
2075
+ reviewLines.push(`**Projects:** ${cl.projects.join(', ')}\n\n`);
2076
+ }
2062
2077
 
2063
2078
  if (cl.contexts.length > 0) {
2064
2079
  reviewLines.push('### User Context (what the user said)\n\n');