@slats/claude-assets-sync 0.2.0 → 0.3.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.
Files changed (158) hide show
  1. package/README.md +47 -63
  2. package/bin/inject-claude-settings.mjs +4 -0
  3. package/dist/claude-hashes.json +9 -9
  4. package/dist/commands/index.d.ts +1 -1
  5. package/dist/commands/runCli/index.d.ts +1 -1
  6. package/dist/commands/runCli/runCli.d.ts +10 -6
  7. package/dist/commands/runCli/runCli.mjs +33 -6
  8. package/dist/commands/runCli/type.d.ts +4 -12
  9. package/dist/commands/runCli/utils/classifyTarget.d.ts +19 -0
  10. package/dist/commands/runCli/utils/classifyTarget.mjs +46 -0
  11. package/dist/commands/runCli/utils/renderOrFallback.d.ts +6 -0
  12. package/dist/commands/runCli/utils/renderOrFallback.mjs +12 -0
  13. package/dist/commands/runCli/utils/renderPlain.d.ts +11 -0
  14. package/dist/commands/runCli/utils/renderPlain.mjs +89 -0
  15. package/dist/commands/runCli/utils/resolvePackage.d.ts +16 -0
  16. package/dist/commands/runCli/utils/resolvePackage.mjs +74 -0
  17. package/dist/commands/runCli/utils/resolveScopeAlias.d.ts +2 -0
  18. package/dist/commands/runCli/utils/resolveScopeAlias.mjs +67 -0
  19. package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +9 -1
  20. package/dist/commands/runCli/utils/resolveScopeFlag.mjs +5 -11
  21. package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
  22. package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
  23. package/dist/commands/runCli/utils/toConsumerPackages.d.ts +9 -0
  24. package/dist/commands/runCli/utils/toConsumerPackages.mjs +26 -0
  25. package/dist/core/index.d.ts +2 -2
  26. package/dist/core/injectDocs/index.d.ts +3 -2
  27. package/dist/core/injectDocs/type.d.ts +0 -19
  28. package/dist/core/injectDocs/utils/applyAction.mjs +1 -1
  29. package/dist/core/scope/index.d.ts +1 -1
  30. package/dist/core/scope/scope.d.ts +0 -1
  31. package/dist/core/scope/scope.mjs +1 -4
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.mjs +2 -2
  34. package/dist/ui/InjectApp/InjectApp.d.ts +2 -0
  35. package/dist/ui/InjectApp/InjectApp.mjs +82 -0
  36. package/dist/ui/InjectApp/index.d.ts +2 -0
  37. package/dist/ui/InjectApp/utils/eventSelectors.d.ts +5 -0
  38. package/dist/ui/InjectApp/utils/eventSelectors.mjs +24 -0
  39. package/dist/ui/InjectApp/utils/phaseReducer.d.ts +2 -0
  40. package/dist/ui/InjectApp/utils/phaseReducer.mjs +130 -0
  41. package/dist/ui/InjectApp/utils/renderInjectApp.d.ts +2 -0
  42. package/dist/ui/InjectApp/utils/renderInjectApp.mjs +19 -0
  43. package/dist/ui/InjectApp/utils/type.d.ts +5 -0
  44. package/dist/ui/components/ActionRow.d.ts +7 -0
  45. package/dist/ui/components/ActionRow.mjs +45 -0
  46. package/dist/ui/components/Banner.d.ts +7 -0
  47. package/dist/ui/components/Banner.mjs +9 -0
  48. package/dist/ui/components/ConfirmForce.d.ts +8 -0
  49. package/dist/ui/components/ConfirmForce.mjs +35 -0
  50. package/dist/ui/components/ErrorPanel.d.ts +6 -0
  51. package/dist/ui/components/ErrorPanel.mjs +14 -0
  52. package/dist/ui/components/Footer.d.ts +8 -0
  53. package/dist/ui/components/Footer.mjs +27 -0
  54. package/dist/ui/components/PlanTable.d.ts +8 -0
  55. package/dist/ui/components/PlanTable.mjs +15 -0
  56. package/dist/ui/components/ProgressBar.d.ts +10 -0
  57. package/dist/ui/components/ProgressBar.mjs +28 -0
  58. package/dist/ui/components/ScopePicker.d.ts +7 -0
  59. package/dist/ui/components/ScopePicker.mjs +26 -0
  60. package/dist/ui/components/Spinner.d.ts +8 -0
  61. package/dist/ui/components/Spinner.mjs +10 -0
  62. package/dist/ui/components/StatusBadge.d.ts +8 -0
  63. package/dist/ui/components/StepTracker.d.ts +9 -0
  64. package/dist/ui/components/StepTracker.mjs +43 -0
  65. package/dist/ui/components/Summary.d.ts +9 -0
  66. package/dist/ui/components/Summary.mjs +30 -0
  67. package/dist/ui/components/TargetCard.d.ts +11 -0
  68. package/dist/ui/components/TargetCard.mjs +29 -0
  69. package/dist/ui/hooks/useApplyStep.d.ts +12 -0
  70. package/dist/ui/hooks/useApplyStep.mjs +30 -0
  71. package/dist/ui/hooks/useExitApp.d.ts +8 -0
  72. package/dist/ui/hooks/useExitApp.mjs +19 -0
  73. package/dist/ui/hooks/useForceConfirmStep.d.ts +9 -0
  74. package/dist/ui/hooks/useForceConfirmStep.mjs +24 -0
  75. package/dist/ui/hooks/useInjectSession.d.ts +10 -0
  76. package/dist/ui/hooks/useInjectSession.mjs +63 -0
  77. package/dist/ui/hooks/useInterval.d.ts +1 -0
  78. package/dist/ui/hooks/usePhase.d.ts +2 -0
  79. package/dist/ui/hooks/usePhase.mjs +9 -0
  80. package/dist/ui/hooks/usePlanStep.d.ts +13 -0
  81. package/dist/ui/hooks/usePlanStep.mjs +94 -0
  82. package/dist/ui/hooks/useResolveStep.d.ts +18 -0
  83. package/dist/ui/hooks/useResolveStep.mjs +21 -0
  84. package/dist/ui/hooks/useTerminalWidth.d.ts +1 -0
  85. package/dist/ui/index.d.ts +2 -0
  86. package/dist/ui/index.mjs +16 -0
  87. package/dist/ui/theme/colors.d.ts +12 -0
  88. package/dist/ui/theme/colors.mjs +9 -0
  89. package/dist/ui/theme/icons.d.ts +29 -0
  90. package/dist/ui/theme/icons.mjs +17 -0
  91. package/dist/ui/theme/layout.d.ts +20 -0
  92. package/dist/ui/theme/layout.mjs +9 -0
  93. package/dist/ui/types/event.d.ts +45 -0
  94. package/dist/ui/types/index.d.ts +4 -0
  95. package/dist/ui/types/phase.d.ts +44 -0
  96. package/dist/ui/types/render.d.ts +6 -0
  97. package/dist/ui/types/target.d.ts +25 -0
  98. package/dist/utils/version.d.ts +1 -1
  99. package/dist/utils/version.mjs +1 -1
  100. package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
  101. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +78 -0
  102. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
  103. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +125 -0
  104. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +150 -0
  105. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
  106. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
  107. package/docs/consumer-integration.md +43 -101
  108. package/package.json +13 -8
  109. package/scripts/dev-ui-fixtures.ts +288 -0
  110. package/scripts/dev-ui.tsx +289 -0
  111. package/bin/claude-sync.mjs +0 -24
  112. package/dist/commands/runCli/runCli.cjs +0 -31
  113. package/dist/commands/runCli/utils/injectOne.cjs +0 -48
  114. package/dist/commands/runCli/utils/injectOne.d.ts +0 -3
  115. package/dist/commands/runCli/utils/injectOne.mjs +0 -46
  116. package/dist/commands/runCli/utils/resolveScopeFlag.cjs +0 -28
  117. package/dist/commands/runCli/utils/runInject.cjs +0 -36
  118. package/dist/commands/runCli/utils/runInject.d.ts +0 -2
  119. package/dist/commands/runCli/utils/runInject.mjs +0 -34
  120. package/dist/core/buildPlan/buildPlan.cjs +0 -42
  121. package/dist/core/buildPlan/utils/toPosix.cjs +0 -9
  122. package/dist/core/buildPlan/utils/walkFiles.cjs +0 -25
  123. package/dist/core/hash/hash.cjs +0 -30
  124. package/dist/core/hashManifest/hashManifest.cjs +0 -27
  125. package/dist/core/injectDocs/injectDocs.cjs +0 -43
  126. package/dist/core/injectDocs/injectDocs.d.ts +0 -2
  127. package/dist/core/injectDocs/injectDocs.mjs +0 -41
  128. package/dist/core/injectDocs/utils/applyAction.cjs +0 -21
  129. package/dist/core/injectDocs/utils/emitCiForceList.cjs +0 -10
  130. package/dist/core/injectDocs/utils/emitCiForceList.d.ts +0 -2
  131. package/dist/core/injectDocs/utils/emitCiForceList.mjs +0 -8
  132. package/dist/core/injectDocs/utils/printPlan.cjs +0 -20
  133. package/dist/core/injectDocs/utils/printPlan.d.ts +0 -2
  134. package/dist/core/injectDocs/utils/printPlan.mjs +0 -18
  135. package/dist/core/injectDocs/utils/summarize.cjs +0 -27
  136. package/dist/core/scope/scope.cjs +0 -46
  137. package/dist/core/scope/utils/isDirectory.cjs +0 -14
  138. package/dist/index.cjs +0 -20
  139. package/dist/prompts/confirmForce.cjs +0 -27
  140. package/dist/prompts/confirmForce.d.ts +0 -1
  141. package/dist/prompts/confirmForce.mjs +0 -25
  142. package/dist/prompts/index.d.ts +0 -2
  143. package/dist/prompts/selectScope.cjs +0 -30
  144. package/dist/prompts/selectScope.d.ts +0 -2
  145. package/dist/prompts/selectScope.mjs +0 -28
  146. package/dist/utils/asyncPool.cjs +0 -26
  147. package/dist/utils/heartbeat.cjs +0 -25
  148. package/dist/utils/heartbeat.d.ts +0 -16
  149. package/dist/utils/heartbeat.mjs +0 -23
  150. package/dist/utils/logger.cjs +0 -74
  151. package/dist/utils/version.cjs +0 -5
  152. package/docs/claude/skills/claude-sync-applier/SKILL.md +0 -195
  153. package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +0 -77
  154. package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +0 -126
  155. package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +0 -139
  156. package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +0 -130
  157. package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +0 -120
  158. package/docs/claude/skills/claude-sync-applier/knowledge/smoke-tests.md +0 -102
@@ -1,6 +1,6 @@
1
1
  # Consumer Integration Template
2
2
 
3
- How to make a package "claude-sync aware" so `claude-sync` discovers and injects its `docs/claude/` tree into end-user `.claude` directories.
3
+ How to make a package ship Claude Code assets through the `inject-claude-settings` dispatcher. Two fields in `package.json`; no stub code.
4
4
 
5
5
  ## 1. `package.json` additions
6
6
 
@@ -8,146 +8,88 @@ How to make a package "claude-sync aware" so `claude-sync` discovers and injects
8
8
  {
9
9
  "name": "@your-scope/your-package",
10
10
  "version": "…",
11
- "bin": {
12
- "claude-sync": "./bin/claude-sync.mjs"
13
- },
14
- "files": [
15
- "dist",
16
- "docs",
17
- "dist/claude-hashes.json",
18
- "bin",
19
- "README.md"
20
- ],
21
11
  "scripts": {
22
12
  "build": "… && yarn build:hashes",
23
- "build:hashes": "node scripts/build-hashes.mjs"
13
+ "build:hashes": "claude-build-hashes"
24
14
  },
25
- "dependencies": {
26
- "@slats/claude-assets-sync": "workspace:^",
15
+ "devDependencies": {
16
+ "@slats/claude-assets-sync": "workspace:^"
27
17
  },
18
+ "files": ["dist", "docs", "README.md"],
28
19
  "claude": {
29
20
  "assetPath": "docs/claude"
30
21
  }
31
22
  }
32
23
  ```
33
24
 
34
- Do **not** expose `./bin/*` in `exports`. That would let consumer bundlers accidentally pull CLI code into app bundles.
35
-
36
- ## 2. `bin/claude-sync.mjs` (3-line re-export stub)
37
-
38
- ```javascript
39
- #!/usr/bin/env node
40
- import { runCli } from '@slats/claude-assets-sync';
41
-
42
- runCli(process.argv, { invokedFromBin: import.meta.url }).catch((err) => {
43
- process.stderr.write(
44
- `[@your-scope/your-package] claude-sync failed: ${err instanceof Error ? err.message : String(err)}\n`,
45
- );
46
- process.exit(1);
47
- });
48
- ```
49
-
50
- `runCli` determines the implicit `--package` target in this priority order:
25
+ - `@slats/claude-assets-sync` MUST be in `devDependencies`. The engine is a CLI-only tool; declaring it in `dependencies` would pull `commander`, `@inquirer/prompts`, and their transitive trees into every end-user's production install even though the consumer's runtime never imports the engine.
26
+ - Do **not** add any `bin` field. Bin names collide across consumers under `node_modules/.bin/` and the engine is the sole CLI surface.
27
+ - Do **not** expose `./bin/*` or `./docs/*` in `exports`. Exposing them would let bundlers pull CLI code or the docs tree into app bundles.
28
+ - Do **not** create a `bin/` or `scripts/` directory in the consumer. The engine's `claude-build-hashes` bin (resolved via `node_modules/.bin/` from `devDependencies` at workspace install time) handles build-time hashing.
51
29
 
52
- 1. `--all` or `--package=<name>` (explicit)
53
- 2. The consumer that owns `process.cwd()` (i.e. the terminal you launched from). When you `cd` into a consumer's package directory and run `yarn claude-sync`, that consumer is picked automatically.
54
- 3. The consumer that owns `invokedFromBin` (fallback). This keeps bare `npx <pkg> claude-sync` working from arbitrary cwds — including from inside another consumer's directory where option 2 would have picked a different package.
55
- 4. The sole discovered consumer, if exactly one exists.
56
- 5. Otherwise an error asking for `--package=<name>` or `--all`.
30
+ ## 2. `docs/claude/` authoring
57
31
 
58
- Passing `invokedFromBin: import.meta.url` remains the mechanism for the fallback case. Omit it in slats's own global bin so it behaves as a cross-consumer dispatcher.
59
-
60
- Remember `chmod +x bin/claude-sync.mjs` (or rely on `files` entry to ship executable bit via npm).
61
-
62
- ## 3. `scripts/build-hashes.mjs` (one line import)
32
+ Any file tree works, but the recommended layout is:
63
33
 
64
- ```javascript
65
- #!/usr/bin/env node
66
- import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
67
-
68
- try {
69
- const { outPath, fileCount } = await buildHashes();
70
- console.log(`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`);
71
- } catch (err) {
72
- console.error('❌ buildHashes failed:', err?.message ?? err);
73
- process.exit(1);
74
- }
34
+ ```
35
+ docs/claude/
36
+ ├── skills/
37
+ │ └── <skill-name>/
38
+ │ ├── SKILL.md
39
+ │ └── knowledge/...
40
+ ├── rules/...
41
+ └── commands/...
75
42
  ```
76
43
 
77
- This reads the current `package.json` + its `claude.assetPath`, hashes every file under the asset root (ignoring `.omc/**`, `*.log`, `.DS_Store`), and writes `dist/claude-hashes.json`.
44
+ The build step (`yarn build:hashes` `claude-build-hashes`) hashes every file under `docs/claude/` relative to the asset root and writes `dist/claude-hashes.json`. On inject, the CLI copies `skills`/`rules`/`commands` into the matching subtree under `.claude/`.
78
45
 
79
- ## 4. Isolation guardrails (optional but recommended)
46
+ ## 3. Isolation guardrails (optional but recommended)
80
47
 
81
48
  In a `.dependency-cruiser.cjs`:
82
49
 
83
50
  ```javascript
84
- {
85
- name: 'src-no-bin',
86
- severity: 'error',
87
- from: { path: '^src/' },
88
- to: { path: '^bin/' },
89
- },
90
51
  {
91
52
  name: 'src-no-docs',
92
53
  severity: 'error',
54
+ comment:
55
+ 'src/ must not import from docs/. docs/claude/** contains pure markdown ' +
56
+ 'assets meant only for the engine dispatcher, not for the library runtime.',
93
57
  from: { path: '^src/' },
94
58
  to: { path: '^docs/' },
95
59
  },
96
- {
97
- name: 'src-no-claude-assets-sync',
98
- severity: 'error',
99
- from: { path: '^src/' },
100
- to: { path: 'node_modules/@slats/claude-assets-sync' },
101
- },
102
60
  ```
103
61
 
104
- Plus `"sideEffects": false` in `package.json`. The guardrails ensure the CLI engine never leaks into consumer runtime bundles.
62
+ Plus `"sideEffects": false` in `package.json`. These ensure the docs tree never leaks into consumer runtime bundles.
63
+
64
+ The legacy `src-no-bin` and `src-no-claude-assets-sync` rules are no longer load-bearing — the consumer owns no `bin/`, and the engine isn't referenced from `src/` anyway. Do not reintroduce them.
105
65
 
106
- ## 5. End-user invocations
66
+ ## 4. End-user invocations
107
67
 
108
- | Install topology | Working invocations |
68
+ The engine is not shipped as a runtime dependency of any consumer, so end users never get a hoisted `inject-claude-settings` bin. Always invoke via `npx -p @slats/claude-assets-sync ...`; the package manager fetches and caches the engine on demand.
69
+
70
+ | Scenario | Invocation |
109
71
  |---|---|
110
- | Consumer is a **direct dep** of the user's project | `npx claude-sync --scope=user` *(bare)* |
111
- | Always works (preferred in docs) | `npx -p @your-scope/your-package claude-sync --scope=user` |
112
- | Consumer is a **transitive dep** | `npx -p @your-scope/your-package claude-sync --scope=user` |
113
- | User has no consumer installed | `npx @slats/claude-assets-sync --package=@your-scope/your-package --scope=user` |
114
- | Multiple consumers discovered | `npx claude-sync --package=@your-scope/your-package` *or* `npx claude-sync --all` |
72
+ | Single consumer target | `npx -p @slats/claude-assets-sync inject-claude-settings --package=@your-scope/your-package --scope=user` |
73
+ | All packages under one npm scope | `npx -p @slats/claude-assets-sync inject-claude-settings --package=@your-scope --scope=user` (scope alias — no slash) |
74
+ | Multiple specific targets | Repeat `--package` or comma-separate: `--package=@scope-a --package=@scope-b/pkg`. There is no `--all`. |
115
75
 
116
76
  ### Scope resolution (project)
117
77
 
118
- For `--scope=project`, the target `.claude` directory is resolved by walking up from `process.cwd()` and reusing the first existing `.claude` directory found. Only if no ancestor owns a `.claude` does the CLI fall back to `process.cwd()/.claude`.
78
+ For `--scope=project`, the target `.claude` directory is resolved by walking up from `process.cwd()` and reusing the first existing `.claude` directory found. Only if no ancestor owns a `.claude` does the CLI fall back to `process.cwd()/.claude`. The CLI logs `(auto-located)` when this happens.
119
79
 
120
80
  ```
121
81
  workspace/
122
- .claude/ ← reused target (auto-located)
82
+ .claude/ ← reused target (auto-located)
123
83
  packages/
124
- @your-scope/your-package/ ← cd here and run claude-sync
125
- bin/claude-sync.mjs
126
- ```
127
-
128
- Running `yarn claude-sync --scope=project` from `packages/@your-scope/your-package/` injects into `workspace/.claude`, not `packages/@your-scope/your-package/.claude`. The CLI logs `(auto-located)` in its resolution line when this happens.
129
-
130
- ## 6. Authoring `docs/claude/`
131
-
132
- Any file tree works, but the recommended layout is:
133
-
134
- ```
135
- docs/claude/
136
- ├── skills/
137
- │ └── <skill-name>/
138
- │ ├── SKILL.md
139
- │ └── knowledge/...
140
- ├── rules/...
141
- └── commands/...
84
+ @your-scope/your-package/ ← cd here and run inject-claude-settings
142
85
  ```
143
86
 
144
- The hash manifest tracks every file under `docs/claude/` relative to the asset root. On inject, the CLI copies skills/rules/commands into the matching subtree under `.claude/`.
87
+ Running `inject-claude-settings --package=@your-scope/your-package --scope=project` from `packages/@your-scope/your-package/` injects into `workspace/.claude`, not `packages/@your-scope/your-package/.claude`.
145
88
 
146
- ## 7. Verification checklist
89
+ ## 5. Verification checklist
147
90
 
148
91
  - [ ] `yarn build` succeeds and emits `dist/claude-hashes.json` alongside the rest of `dist/`.
149
- - [ ] `node bin/claude-sync.mjs --help` prints the `claude-sync` subcommand tree.
150
- - [ ] `node bin/claude-sync.mjs list --json` emits an entry for your package with `hashesPresent: true`.
151
- - [ ] `node bin/claude-sync.mjs --scope=project --dry-run --package=@your-scope/your-package` emits a copy plan.
152
- - [ ] `yarn depcheck` (or whatever your dep-cruiser invocation is named) reports zero new violations.
153
- - [ ] Consumer bundler tree-shakes away CLI code (verify by greping the built bundle: should contain zero references to `@slats/claude-assets-sync`).
92
+ - [ ] `node packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs --help` prints the dispatcher usage.
93
+ - [ ] `node packages/slats/claude-assets-sync/bin/inject-claude-settings.mjs --package=@your-scope/your-package --scope=project --dry-run` emits a copy plan when run from `/tmp/...`.
94
+ - [ ] If you enabled Section 3's depcheck, `yarn depcheck` reports zero violations.
95
+ - [ ] Consumer bundler tree-shakes away docs — grep the built bundle for `@slats/claude-assets-sync`, `inject-claude-settings`, and `docs/claude`; all three should be absent.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slats/claude-assets-sync",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Shared CLI engine that lets consumer packages inject their Claude docs (skills, rules, commands) into a user's .claude directory via a thin bin/inject-docs wrapper.",
5
5
  "keywords": [
6
6
  "claude",
@@ -27,19 +27,17 @@
27
27
  ".": {
28
28
  "types": "./dist/index.d.ts",
29
29
  "source": "./src/index.ts",
30
- "import": "./dist/index.mjs",
31
- "require": "./dist/index.cjs"
30
+ "import": "./dist/index.mjs"
32
31
  },
33
32
  "./buildHashes": {
34
33
  "import": "./scripts/buildHashes.mjs"
35
34
  }
36
35
  },
37
- "main": "dist/index.cjs",
38
36
  "module": "dist/index.mjs",
39
37
  "types": "dist/index.d.ts",
40
38
  "bin": {
41
39
  "claude-build-hashes": "./scripts/claude-build-hashes.mjs",
42
- "claude-sync": "./bin/claude-sync.mjs"
40
+ "inject-claude-settings": "./bin/inject-claude-settings.mjs"
43
41
  },
44
42
  "files": [
45
43
  "dist",
@@ -54,6 +52,8 @@
54
52
  "build:publish:npm": "yarn build && yarn publish:npm",
55
53
  "build:types": "node ../../aileron/script/build/buildTypes.mjs",
56
54
  "dev": "node scripts/inject-version.js && tsx src/main.ts",
55
+ "dev:ui": "tsx scripts/dev-ui.tsx",
56
+ "dev:cli": "tsx bin/inject-claude-settings.mjs",
57
57
  "format": "prettier --write \"src/**/*.ts\"",
58
58
  "lint": "eslint \"src/**/*.ts\"",
59
59
  "prepublishOnly": "yarn build",
@@ -64,12 +64,17 @@
64
64
  "version:patch": "yarn version patch"
65
65
  },
66
66
  "dependencies": {
67
- "@inquirer/prompts": "^8.4.2",
68
67
  "commander": "^12.1.0",
69
- "picocolors": "^1.1.1"
68
+ "ink": "^7.0.0",
69
+ "ink-gradient": "^4.0.0",
70
+ "ink-select-input": "^6.2.0",
71
+ "ink-spinner": "^5.0.0",
72
+ "picocolors": "^1.1.1",
73
+ "react": "^19.2.0"
70
74
  },
71
75
  "devDependencies": {
72
- "@inquirer/testing": "^3.3.5"
76
+ "@types/react": "^19.2.0",
77
+ "ink-testing-library": "^4.0.0"
73
78
  },
74
79
  "claude": {
75
80
  "assetPath": "docs/claude"
@@ -0,0 +1,288 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+
4
+ import type { ConsumerPackage } from '../src/commands/runCli/type.js';
5
+ import type { InjectPlan, Action } from '../src/core/buildPlan/index.js';
6
+ import type { InjectReport, ScopeResolution } from '../src/core/index.js';
7
+ import type {
8
+ ApplyProgress,
9
+ InjectEvent,
10
+ Phase,
11
+ PlanStepState,
12
+ TargetPlan,
13
+ Warning,
14
+ } from '../src/ui/types/index.js';
15
+
16
+ export const PHASES = [
17
+ 'resolving',
18
+ 'scope-select',
19
+ 'planning',
20
+ 'diff-review',
21
+ 'force-confirm',
22
+ 'applying',
23
+ 'summary',
24
+ 'summary-dry',
25
+ 'error',
26
+ ] as const;
27
+
28
+ export type PhaseKey = (typeof PHASES)[number];
29
+
30
+ const MOCK_TARGETS: ConsumerPackage[] = [
31
+ {
32
+ name: '@canard/schema-form',
33
+ version: '0.12.1',
34
+ packageRoot: '/workspace/packages/canard/schema-form',
35
+ assetRoot: '/workspace/packages/canard/schema-form/docs/claude',
36
+ hashesPresent: true,
37
+ },
38
+ {
39
+ name: '@canard/schema-form-antd-plugin',
40
+ version: '0.12.1',
41
+ packageRoot: '/workspace/packages/canard/schema-form-antd-plugin',
42
+ assetRoot: '/workspace/packages/canard/schema-form-antd-plugin/docs/claude',
43
+ hashesPresent: true,
44
+ },
45
+ {
46
+ name: '@winglet/common-utils',
47
+ version: '0.12.1',
48
+ packageRoot: '/workspace/packages/winglet/common-utils',
49
+ assetRoot: '/workspace/packages/winglet/common-utils/docs/claude',
50
+ hashesPresent: true,
51
+ },
52
+ ];
53
+
54
+ const scopeFixture: ScopeResolution = {
55
+ scope: 'user',
56
+ targetRoot: join(homedir(), '.claude'),
57
+ description: '~/.claude (user)',
58
+ };
59
+
60
+ function makeActions(
61
+ baseDstRoot: string,
62
+ options: {
63
+ copy?: number;
64
+ skip?: number;
65
+ diverged?: number;
66
+ orphan?: number;
67
+ del?: number;
68
+ },
69
+ ): Action[] {
70
+ const actions: Action[] = [];
71
+ let fileIdx = 0;
72
+ for (let i = 0; i < (options.copy ?? 0); i += 1) {
73
+ fileIdx += 1;
74
+ actions.push({
75
+ kind: 'copy',
76
+ relPath: `skills/expert/doc-${fileIdx}.md`,
77
+ dstAbs: `${baseDstRoot}/skills/expert/doc-${fileIdx}.md`,
78
+ });
79
+ }
80
+ for (let i = 0; i < (options.skip ?? 0); i += 1) {
81
+ fileIdx += 1;
82
+ actions.push({
83
+ kind: 'skip-uptodate',
84
+ relPath: `skills/expert/existing-${fileIdx}.md`,
85
+ dstAbs: `${baseDstRoot}/skills/expert/existing-${fileIdx}.md`,
86
+ });
87
+ }
88
+ for (let i = 0; i < (options.diverged ?? 0); i += 1) {
89
+ fileIdx += 1;
90
+ actions.push({
91
+ kind: 'warn-diverged',
92
+ relPath: `skills/expert/edited-${fileIdx}.md`,
93
+ dstAbs: `${baseDstRoot}/skills/expert/edited-${fileIdx}.md`,
94
+ });
95
+ }
96
+ for (let i = 0; i < (options.orphan ?? 0); i += 1) {
97
+ fileIdx += 1;
98
+ actions.push({
99
+ kind: 'warn-orphan',
100
+ relPath: `skills/expert/ghost-${fileIdx}.md`,
101
+ dstAbs: `${baseDstRoot}/skills/expert/ghost-${fileIdx}.md`,
102
+ });
103
+ }
104
+ for (let i = 0; i < (options.del ?? 0); i += 1) {
105
+ fileIdx += 1;
106
+ actions.push({
107
+ kind: 'delete',
108
+ relPath: `skills/expert/stale-${fileIdx}.md`,
109
+ dstAbs: `${baseDstRoot}/skills/expert/stale-${fileIdx}.md`,
110
+ });
111
+ }
112
+ return actions;
113
+ }
114
+
115
+ function makeTargetPlan(
116
+ target: ConsumerPackage,
117
+ actions: Action[],
118
+ requiresForce = false,
119
+ ): TargetPlan {
120
+ const plan: InjectPlan = { actions, requiresForce };
121
+ return { target, scope: scopeFixture, plan };
122
+ }
123
+
124
+ const TP_CLEAN = makeTargetPlan(
125
+ MOCK_TARGETS[0],
126
+ makeActions(scopeFixture.targetRoot, { copy: 4, skip: 2 }),
127
+ false,
128
+ );
129
+
130
+ const TP_WARN = makeTargetPlan(
131
+ MOCK_TARGETS[1],
132
+ makeActions(scopeFixture.targetRoot, {
133
+ copy: 2,
134
+ skip: 1,
135
+ diverged: 1,
136
+ orphan: 1,
137
+ }),
138
+ true,
139
+ );
140
+
141
+ const TP_THIRD = makeTargetPlan(
142
+ MOCK_TARGETS[2],
143
+ makeActions(scopeFixture.targetRoot, { copy: 3, skip: 5 }),
144
+ false,
145
+ );
146
+
147
+ const PLAN_SET: readonly TargetPlan[] = [TP_CLEAN, TP_WARN, TP_THIRD];
148
+
149
+ const MOCK_WARNINGS: Warning[] = [
150
+ {
151
+ packageName: MOCK_TARGETS[1].name,
152
+ kind: 'warn-diverged',
153
+ relPath: 'skills/expert/edited-4.md',
154
+ description: 'local differs from source',
155
+ },
156
+ {
157
+ packageName: MOCK_TARGETS[1].name,
158
+ kind: 'warn-orphan',
159
+ relPath: 'skills/expert/ghost-5.md',
160
+ description: 'exists locally but not in manifest',
161
+ },
162
+ ];
163
+
164
+ const MOCK_REPORTS: InjectReport[] = PLAN_SET.map((tp) => {
165
+ const report: InjectReport = {
166
+ created: [],
167
+ updated: [],
168
+ skipped: [],
169
+ warnings: [],
170
+ deleted: [],
171
+ exitCode: 0,
172
+ };
173
+ for (const action of tp.plan.actions) {
174
+ if (action.kind === 'copy') report.created.push(action.relPath);
175
+ else if (action.kind === 'skip-uptodate') report.skipped.push(action.relPath);
176
+ else if (action.kind === 'warn-diverged')
177
+ report.warnings.push({ relPath: action.relPath, reason: 'diverged' });
178
+ else if (action.kind === 'warn-orphan')
179
+ report.warnings.push({ relPath: action.relPath, reason: 'orphan' });
180
+ else if (action.kind === 'delete') report.deleted.push(action.relPath);
181
+ }
182
+ return report;
183
+ });
184
+
185
+ const MOCK_APPLY_PROGRESS: ApplyProgress = {
186
+ total: 18,
187
+ done: 7,
188
+ current: 'skills/expert/doc-8.md',
189
+ startedAt: Date.now() - 2500,
190
+ };
191
+
192
+ function makePlanningProgress(): ReadonlyMap<string, PlanStepState> {
193
+ return new Map([
194
+ [
195
+ MOCK_TARGETS[0].name,
196
+ { packageName: MOCK_TARGETS[0].name, status: 'done' },
197
+ ],
198
+ [
199
+ MOCK_TARGETS[1].name,
200
+ { packageName: MOCK_TARGETS[1].name, status: 'running' },
201
+ ],
202
+ [
203
+ MOCK_TARGETS[2].name,
204
+ { packageName: MOCK_TARGETS[2].name, status: 'pending' },
205
+ ],
206
+ ]);
207
+ }
208
+
209
+ export function buildPhase(kind: PhaseKey): Phase {
210
+ switch (kind) {
211
+ case 'resolving':
212
+ return { kind: 'resolving', targets: MOCK_TARGETS };
213
+ case 'scope-select':
214
+ return {
215
+ kind: 'scope-select',
216
+ targets: MOCK_TARGETS,
217
+ pending: () => {
218
+ /* noop */
219
+ },
220
+ };
221
+ case 'planning':
222
+ return {
223
+ kind: 'planning',
224
+ targets: MOCK_TARGETS,
225
+ scope: 'user',
226
+ progress: makePlanningProgress(),
227
+ };
228
+ case 'diff-review':
229
+ return {
230
+ kind: 'diff-review',
231
+ plans: PLAN_SET,
232
+ focusedIndex: 0,
233
+ scope: 'user',
234
+ };
235
+ case 'force-confirm':
236
+ return {
237
+ kind: 'force-confirm',
238
+ plans: PLAN_SET,
239
+ warnings: MOCK_WARNINGS,
240
+ pending: () => {
241
+ /* noop */
242
+ },
243
+ scope: 'user',
244
+ };
245
+ case 'applying':
246
+ return {
247
+ kind: 'applying',
248
+ plans: PLAN_SET,
249
+ progress: MOCK_APPLY_PROGRESS,
250
+ scope: 'user',
251
+ };
252
+ case 'summary':
253
+ return {
254
+ kind: 'summary',
255
+ reports: MOCK_REPORTS,
256
+ plans: PLAN_SET,
257
+ exitCode: 0,
258
+ scope: 'user',
259
+ dryRun: false,
260
+ };
261
+ case 'summary-dry':
262
+ return {
263
+ kind: 'summary',
264
+ reports: MOCK_REPORTS,
265
+ plans: PLAN_SET,
266
+ exitCode: 0,
267
+ scope: 'user',
268
+ dryRun: true,
269
+ };
270
+ case 'error':
271
+ return {
272
+ kind: 'error',
273
+ error: new Error(
274
+ 'Sample fatal: dist/claude-hashes.json missing at /workspace/packages/canard/schema-form',
275
+ ),
276
+ };
277
+ default: {
278
+ const _exhaustive: never = kind;
279
+ void _exhaustive;
280
+ throw new Error('unknown phase');
281
+ }
282
+ }
283
+ }
284
+
285
+ export function fixtureEvents(kind: PhaseKey): InjectEvent[] {
286
+ void kind;
287
+ return [];
288
+ }