@slats/claude-assets-sync 0.3.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 (139) hide show
  1. package/README.md +9 -9
  2. package/dist/claude-hashes.json +7 -7
  3. package/dist/commands/runCli/runCli.mjs +7 -2
  4. package/dist/commands/runCli/type.d.ts +1 -0
  5. package/dist/commands/runCli/utils/renderOrFallback.d.ts +6 -0
  6. package/dist/commands/runCli/utils/renderOrFallback.mjs +12 -0
  7. package/dist/commands/runCli/utils/renderPlain.d.ts +11 -0
  8. package/dist/commands/runCli/utils/renderPlain.mjs +89 -0
  9. package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +9 -1
  10. package/dist/commands/runCli/utils/resolveScopeFlag.mjs +5 -11
  11. package/dist/commands/runCli/utils/toConsumerPackages.d.ts +9 -0
  12. package/dist/commands/runCli/utils/toConsumerPackages.mjs +26 -0
  13. package/dist/core/index.d.ts +2 -2
  14. package/dist/core/injectDocs/index.d.ts +3 -2
  15. package/dist/core/injectDocs/type.d.ts +0 -19
  16. package/dist/core/scope/index.d.ts +1 -1
  17. package/dist/core/scope/scope.d.ts +0 -1
  18. package/dist/core/scope/scope.mjs +1 -4
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.mjs +2 -2
  21. package/dist/ui/InjectApp/InjectApp.d.ts +2 -0
  22. package/dist/ui/InjectApp/InjectApp.mjs +82 -0
  23. package/dist/ui/InjectApp/index.d.ts +2 -0
  24. package/dist/ui/InjectApp/utils/eventSelectors.d.ts +5 -0
  25. package/dist/ui/InjectApp/utils/eventSelectors.mjs +24 -0
  26. package/dist/ui/InjectApp/utils/phaseReducer.d.ts +2 -0
  27. package/dist/ui/InjectApp/utils/phaseReducer.mjs +130 -0
  28. package/dist/ui/InjectApp/utils/renderInjectApp.d.ts +2 -0
  29. package/dist/ui/InjectApp/utils/renderInjectApp.mjs +19 -0
  30. package/dist/ui/InjectApp/utils/type.d.ts +5 -0
  31. package/dist/ui/components/ActionRow.d.ts +7 -0
  32. package/dist/ui/components/ActionRow.mjs +45 -0
  33. package/dist/ui/components/Banner.d.ts +7 -0
  34. package/dist/ui/components/Banner.mjs +9 -0
  35. package/dist/ui/components/ConfirmForce.d.ts +8 -0
  36. package/dist/ui/components/ConfirmForce.mjs +35 -0
  37. package/dist/ui/components/ErrorPanel.d.ts +6 -0
  38. package/dist/ui/components/ErrorPanel.mjs +14 -0
  39. package/dist/ui/components/Footer.d.ts +8 -0
  40. package/dist/ui/components/Footer.mjs +27 -0
  41. package/dist/ui/components/PlanTable.d.ts +8 -0
  42. package/dist/ui/components/PlanTable.mjs +15 -0
  43. package/dist/ui/components/ProgressBar.d.ts +10 -0
  44. package/dist/ui/components/ProgressBar.mjs +28 -0
  45. package/dist/ui/components/ScopePicker.d.ts +7 -0
  46. package/dist/ui/components/ScopePicker.mjs +26 -0
  47. package/dist/ui/components/Spinner.d.ts +8 -0
  48. package/dist/ui/components/Spinner.mjs +10 -0
  49. package/dist/ui/components/StatusBadge.d.ts +8 -0
  50. package/dist/ui/components/StepTracker.d.ts +9 -0
  51. package/dist/ui/components/StepTracker.mjs +43 -0
  52. package/dist/ui/components/Summary.d.ts +9 -0
  53. package/dist/ui/components/Summary.mjs +30 -0
  54. package/dist/ui/components/TargetCard.d.ts +11 -0
  55. package/dist/ui/components/TargetCard.mjs +29 -0
  56. package/dist/ui/hooks/useApplyStep.d.ts +12 -0
  57. package/dist/ui/hooks/useApplyStep.mjs +30 -0
  58. package/dist/ui/hooks/useExitApp.d.ts +8 -0
  59. package/dist/ui/hooks/useExitApp.mjs +19 -0
  60. package/dist/ui/hooks/useForceConfirmStep.d.ts +9 -0
  61. package/dist/ui/hooks/useForceConfirmStep.mjs +24 -0
  62. package/dist/ui/hooks/useInjectSession.d.ts +10 -0
  63. package/dist/ui/hooks/useInjectSession.mjs +63 -0
  64. package/dist/ui/hooks/useInterval.d.ts +1 -0
  65. package/dist/ui/hooks/usePhase.d.ts +2 -0
  66. package/dist/ui/hooks/usePhase.mjs +9 -0
  67. package/dist/ui/hooks/usePlanStep.d.ts +13 -0
  68. package/dist/ui/hooks/usePlanStep.mjs +94 -0
  69. package/dist/ui/hooks/useResolveStep.d.ts +18 -0
  70. package/dist/ui/hooks/useResolveStep.mjs +21 -0
  71. package/dist/ui/hooks/useTerminalWidth.d.ts +1 -0
  72. package/dist/ui/index.d.ts +2 -0
  73. package/dist/ui/index.mjs +16 -0
  74. package/dist/ui/theme/colors.d.ts +12 -0
  75. package/dist/ui/theme/colors.mjs +9 -0
  76. package/dist/ui/theme/icons.d.ts +29 -0
  77. package/dist/ui/theme/icons.mjs +17 -0
  78. package/dist/ui/theme/layout.d.ts +20 -0
  79. package/dist/ui/theme/layout.mjs +9 -0
  80. package/dist/ui/types/event.d.ts +45 -0
  81. package/dist/ui/types/index.d.ts +4 -0
  82. package/dist/ui/types/phase.d.ts +44 -0
  83. package/dist/ui/types/render.d.ts +6 -0
  84. package/dist/ui/types/target.d.ts +25 -0
  85. package/dist/utils/version.d.ts +1 -1
  86. package/dist/utils/version.mjs +1 -1
  87. package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +1 -1
  88. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +4 -12
  89. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +17 -14
  90. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +18 -13
  91. package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +4 -4
  92. package/docs/consumer-integration.md +9 -8
  93. package/package.json +12 -7
  94. package/scripts/dev-ui-fixtures.ts +288 -0
  95. package/scripts/dev-ui.tsx +289 -0
  96. package/dist/commands/runCli/runCli.cjs +0 -53
  97. package/dist/commands/runCli/utils/classifyTarget.cjs +0 -48
  98. package/dist/commands/runCli/utils/injectOne.cjs +0 -47
  99. package/dist/commands/runCli/utils/injectOne.d.ts +0 -3
  100. package/dist/commands/runCli/utils/injectOne.mjs +0 -45
  101. package/dist/commands/runCli/utils/resolvePackage.cjs +0 -77
  102. package/dist/commands/runCli/utils/resolveScopeAlias.cjs +0 -69
  103. package/dist/commands/runCli/utils/resolveScopeFlag.cjs +0 -28
  104. package/dist/commands/runCli/utils/resolveTargets.cjs +0 -40
  105. package/dist/commands/runCli/utils/runInject.cjs +0 -52
  106. package/dist/commands/runCli/utils/runInject.d.ts +0 -3
  107. package/dist/commands/runCli/utils/runInject.mjs +0 -50
  108. package/dist/core/buildPlan/buildPlan.cjs +0 -42
  109. package/dist/core/buildPlan/utils/toPosix.cjs +0 -9
  110. package/dist/core/buildPlan/utils/walkFiles.cjs +0 -25
  111. package/dist/core/hash/hash.cjs +0 -30
  112. package/dist/core/hashManifest/hashManifest.cjs +0 -27
  113. package/dist/core/injectDocs/injectDocs.cjs +0 -43
  114. package/dist/core/injectDocs/injectDocs.d.ts +0 -2
  115. package/dist/core/injectDocs/injectDocs.mjs +0 -41
  116. package/dist/core/injectDocs/utils/applyAction.cjs +0 -21
  117. package/dist/core/injectDocs/utils/emitCiForceList.cjs +0 -10
  118. package/dist/core/injectDocs/utils/emitCiForceList.d.ts +0 -2
  119. package/dist/core/injectDocs/utils/emitCiForceList.mjs +0 -8
  120. package/dist/core/injectDocs/utils/printPlan.cjs +0 -20
  121. package/dist/core/injectDocs/utils/printPlan.d.ts +0 -2
  122. package/dist/core/injectDocs/utils/printPlan.mjs +0 -18
  123. package/dist/core/injectDocs/utils/summarize.cjs +0 -27
  124. package/dist/core/scope/scope.cjs +0 -46
  125. package/dist/core/scope/utils/isDirectory.cjs +0 -14
  126. package/dist/index.cjs +0 -20
  127. package/dist/prompts/confirmForce.cjs +0 -27
  128. package/dist/prompts/confirmForce.d.ts +0 -1
  129. package/dist/prompts/confirmForce.mjs +0 -25
  130. package/dist/prompts/index.d.ts +0 -2
  131. package/dist/prompts/selectScope.cjs +0 -30
  132. package/dist/prompts/selectScope.d.ts +0 -2
  133. package/dist/prompts/selectScope.mjs +0 -28
  134. package/dist/utils/asyncPool.cjs +0 -26
  135. package/dist/utils/heartbeat.cjs +0 -25
  136. package/dist/utils/heartbeat.d.ts +0 -16
  137. package/dist/utils/heartbeat.mjs +0 -23
  138. package/dist/utils/logger.cjs +0 -74
  139. package/dist/utils/version.cjs +0 -5
@@ -4,7 +4,7 @@ Reference: `packages/canard/schema-form/CLAUDE.md`.
4
4
 
5
5
  Append the section below to `${TARGET_PATH}/CLAUDE.md` if the file
6
6
  exists. Substitute the sample package name in the chosen template
7
- with `${PACKAGE_NAME}` — six occurrences. Skip the entire step if
7
+ with `${PACKAGE_NAME}` — four occurrences. Skip the entire step if
8
8
  `CLAUDE.md` does not exist (do not create one).
9
9
 
10
10
  The template is intentionally terse: CLI usage + essential isolation
@@ -18,17 +18,13 @@ do not duplicate it into every consumer's `CLAUDE.md`.
18
18
  ````markdown
19
19
  ## Claude Docs Injector
20
20
 
21
- `docs/claude/**` 자산을 사용자 `.claude/` 에 주입. 엔진: `@slats/claude-assets-sync` (bin: `inject-claude-settings`).
21
+ `docs/claude/**` 자산을 사용자 `.claude/` 에 주입. 엔진: `@slats/claude-assets-sync` (bin: `inject-claude-settings`). 엔진은 `devDependencies` 에만 있으므로 항상 `npx -p @slats/claude-assets-sync ...` 로 호출합니다.
22
22
 
23
23
  ```bash
24
- # universal — 모든 PM (pnpm strict / yarn-berry PnP 포함)
25
24
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=user
26
25
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=project
27
26
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=user --dry-run
28
27
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=user --force
29
-
30
- # 간편 — npm / yarn-classic 에서만 (transitive bin hoist 기반)
31
- npx inject-claude-settings --package=@canard/schema-form --scope=user
32
28
  ```
33
29
 
34
30
  ### Isolation Guardrails
@@ -44,17 +40,13 @@ npx inject-claude-settings --package=@canard/schema-form --scope=user
44
40
  ````markdown
45
41
  ## Claude Docs Injector
46
42
 
47
- Inject `docs/claude/**` into the user's `.claude/`. Engine: `@slats/claude-assets-sync` (bin: `inject-claude-settings`).
43
+ Inject `docs/claude/**` into the user's `.claude/`. Engine: `@slats/claude-assets-sync` (bin: `inject-claude-settings`). The engine is declared only in `devDependencies`, so always invoke via `npx -p @slats/claude-assets-sync ...`.
48
44
 
49
45
  ```bash
50
- # universal — every PM (pnpm strict / yarn-berry PnP included)
51
46
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@winglet/style-utils --scope=user
52
47
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@winglet/style-utils --scope=project
53
48
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@winglet/style-utils --scope=user --dry-run
54
49
  npx -p @slats/claude-assets-sync inject-claude-settings --package=@winglet/style-utils --scope=user --force
55
-
56
- # simple — npm / yarn-classic only (relies on transitive bin hoist)
57
- npx inject-claude-settings --package=@winglet/style-utils --scope=user
58
50
  ```
59
51
 
60
52
  ### Isolation Guardrails
@@ -68,7 +60,7 @@ npx inject-claude-settings --package=@winglet/style-utils --scope=user
68
60
  ## Substitution Rules
69
61
 
70
62
  - Replace the sample package name in the chosen template with
71
- `${PACKAGE_NAME}` — six occurrences.
63
+ `${PACKAGE_NAME}` — four occurrences.
72
64
  - Preserve the Isolation Guardrails bullets verbatim — these are
73
65
  the sharp invariants that must stay consistent across consumers.
74
66
 
@@ -27,22 +27,25 @@ case — it would break silently-disabled packages.
27
27
 
28
28
  ---
29
29
 
30
- ## `@slats/claude-assets-sync` must be in `dependencies`
31
-
32
- Not `devDependencies`, not `peerDependencies`. Reasons:
33
-
34
- 1. Monorepo build chain needs `.bin/claude-build-hashes` resolved,
35
- which requires the engine as a direct dep of each consumer.
36
- 2. For end users on npm / yarn-classic, listing the engine in
37
- `dependencies` makes `inject-claude-settings` transitively
38
- hoisted into `node_modules/.bin/`, enabling the short
39
- invocation `npx inject-claude-settings --package=<THIS>`.
40
- 3. Bundle isolation is enforced by the import graph (`src/**`
30
+ ## `@slats/claude-assets-sync` must be in `devDependencies`
31
+
32
+ Not `dependencies`, not `peerDependencies`. Reasons:
33
+
34
+ 1. The engine is CLI-only. Declaring it in `dependencies` would
35
+ pull `commander`, `@inquirer/prompts`, and their transitive
36
+ trees into every end-user's production install even though the
37
+ consumer's runtime never imports the engine.
38
+ 2. The monorepo build chain still resolves `.bin/claude-build-hashes`
39
+ from `devDependencies` at `yarn install` time — yarn workspaces
40
+ link devDeps and deps identically for workspace-local builds.
41
+ 3. End users never rely on a hoisted `inject-claude-settings` bin.
42
+ The canonical invocation is `npx -p @slats/claude-assets-sync
43
+ inject-claude-settings --package=<THIS>`, which fetches the
44
+ engine on demand and caches it.
45
+ 4. Bundle isolation is enforced by the import graph (`src/**`
41
46
  never references the engine), not by dependency-type.
42
47
 
43
- Pnpm strict users do not get the transitive hoist and must use the
44
- universal form `npx -p @slats/claude-assets-sync inject-claude-settings
45
- --package=<THIS>`. Every consumer's CLAUDE.md documents both paths.
48
+ Every consumer's CLAUDE.md documents the single `npx -p` path.
46
49
 
47
50
  ---
48
51
 
@@ -53,8 +53,9 @@ Point to the engine's bin (NOT a local stub):
53
53
 
54
54
  `claude-build-hashes` reads `process.cwd()/package.json` and picks
55
55
  up `claude.assetPath`. Works because `@slats/claude-assets-sync` is
56
- in `dependencies`, so `node_modules/.bin/claude-build-hashes` is
57
- linked.
56
+ in `devDependencies`, so `node_modules/.bin/claude-build-hashes` is
57
+ linked at workspace install time (yarn workspaces link devDeps and
58
+ deps identically for workspace-local builds).
58
59
 
59
60
  If a different `build:hashes` script exists, ask.
60
61
 
@@ -74,30 +75,34 @@ If the target already has a `prepublishOnly` that calls `yarn build`
74
75
 
75
76
  ---
76
77
 
77
- ## 5. `dependencies."@slats/claude-assets-sync"`
78
+ ## 5. `devDependencies."@slats/claude-assets-sync"`
78
79
 
79
- **Must be in `dependencies`, never `devDependencies` or
80
+ **Must be in `devDependencies`, never `dependencies` or
80
81
  `peerDependencies`.**
81
82
 
82
83
  ```json
83
- "dependencies": {
84
+ "devDependencies": {
84
85
  "@slats/claude-assets-sync": "workspace:^"
85
86
  }
86
87
  ```
87
88
 
88
89
  Reasons:
89
90
 
90
- - Monorepo build chain needs `.bin/claude-build-hashes` resolved,
91
- which requires the engine as a direct dep.
92
- - On npm / yarn-classic, listing the engine in `dependencies` makes
93
- `inject-claude-settings` transitively hoisted into
94
- `node_modules/.bin/` for end users, enabling the short
95
- invocation `npx inject-claude-settings --package=<THIS>`.
91
+ - The engine is CLI-only. Declaring it in `dependencies` would pull
92
+ `commander`, `@inquirer/prompts`, and their transitive trees into
93
+ every end-user's production install even though the consumer's
94
+ runtime never imports the engine.
95
+ - The monorepo build chain still resolves `.bin/claude-build-hashes`
96
+ from `devDependencies` at `yarn install` time — yarn workspaces
97
+ link devDeps and deps identically for workspace-local builds.
98
+ - End users invoke the engine via `npx -p @slats/claude-assets-sync
99
+ inject-claude-settings --package=<THIS>`, which fetches the engine
100
+ on demand and caches it. No transitive bin hoist is required.
96
101
  - Bundle isolation is enforced by the import graph (`src/**` never
97
102
  references the engine), not by dependency-type.
98
103
 
99
- If the target already has it in `devDependencies` or
100
- `peerDependencies`, move it to `dependencies`. Do not duplicate.
104
+ If the target already has it in `dependencies` or
105
+ `peerDependencies`, move it to `devDependencies`. Do not duplicate.
101
106
 
102
107
  ---
103
108
 
@@ -24,10 +24,10 @@ Reference consumer: `packages/canard/schema-form`.
24
24
  ## What the engine provides
25
25
 
26
26
  - `inject-claude-settings` bin — dispatcher. Invoked as
27
- `npx -p @slats/claude-assets-sync inject-claude-settings --package=<name> --scope=<scope>`,
28
- or (on npm / yarn-classic) `npx inject-claude-settings --package=<name> --scope=<scope>`
29
- once the engine is installed as a transitive dependency of the
30
- consumer.
27
+ `npx -p @slats/claude-assets-sync inject-claude-settings --package=<name> --scope=<scope>`.
28
+ The engine is a consumer-side `devDependency` only, so end users
29
+ never get a hoisted bin; the `npx -p` form pulls the engine on
30
+ demand and caches it.
31
31
  - `claude-build-hashes` bin — reads `process.cwd()/package.json`,
32
32
  picks up `claude.assetPath`, hashes every file beneath it, and
33
33
  writes `dist/claude-hashes.json`. Run via `yarn build:hashes` in
@@ -12,7 +12,7 @@ How to make a package ship Claude Code assets through the `inject-claude-setting
12
12
  "build": "… && yarn build:hashes",
13
13
  "build:hashes": "claude-build-hashes"
14
14
  },
15
- "dependencies": {
15
+ "devDependencies": {
16
16
  "@slats/claude-assets-sync": "workspace:^"
17
17
  },
18
18
  "files": ["dist", "docs", "README.md"],
@@ -22,10 +22,10 @@ How to make a package ship Claude Code assets through the `inject-claude-setting
22
22
  }
23
23
  ```
24
24
 
25
- - `@slats/claude-assets-sync` MUST be in `dependencies`, not `devDependencies` or `peerDependencies`.
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
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
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/`) handles build-time hashing.
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.
29
29
 
30
30
  ## 2. `docs/claude/` authoring
31
31
 
@@ -65,12 +65,13 @@ The legacy `src-no-bin` and `src-no-claude-assets-sync` rules are no longer load
65
65
 
66
66
  ## 4. End-user invocations
67
67
 
68
- | Install topology | Invocation |
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 |
69
71
  |---|---|
70
- | End user installs consumer as a **direct dep** on npm / yarn-classic | `npx inject-claude-settings --package=@your-scope/your-package --scope=user` |
71
- | End user installs consumer as a **direct dep** on pnpm strict / yarn-berry PnP | `npx -p @slats/claude-assets-sync inject-claude-settings --package=@your-scope/your-package --scope=user` |
72
- | End user has **no consumer installed** yet | `npx -p @slats/claude-assets-sync inject-claude-settings --package=@your-scope/your-package --scope=user` (resolves to whatever is on the registry under that exact name) |
73
- | End user has **multiple consumers installed** | Call `inject-claude-settings --package=<one-name>` per target. There is no `--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`. |
74
75
 
75
76
  ### Scope resolution (project)
76
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slats/claude-assets-sync",
3
- "version": "0.3.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,14 +27,12 @@
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": {
@@ -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
+ }