@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.
- package/README.md +47 -63
- package/bin/inject-claude-settings.mjs +4 -0
- package/dist/claude-hashes.json +9 -9
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/runCli/index.d.ts +1 -1
- package/dist/commands/runCli/runCli.d.ts +10 -6
- package/dist/commands/runCli/runCli.mjs +33 -6
- package/dist/commands/runCli/type.d.ts +4 -12
- package/dist/commands/runCli/utils/classifyTarget.d.ts +19 -0
- package/dist/commands/runCli/utils/classifyTarget.mjs +46 -0
- package/dist/commands/runCli/utils/renderOrFallback.d.ts +6 -0
- package/dist/commands/runCli/utils/renderOrFallback.mjs +12 -0
- package/dist/commands/runCli/utils/renderPlain.d.ts +11 -0
- package/dist/commands/runCli/utils/renderPlain.mjs +89 -0
- package/dist/commands/runCli/utils/resolvePackage.d.ts +16 -0
- package/dist/commands/runCli/utils/resolvePackage.mjs +74 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.d.ts +2 -0
- package/dist/commands/runCli/utils/resolveScopeAlias.mjs +67 -0
- package/dist/commands/runCli/utils/resolveScopeFlag.d.ts +9 -1
- package/dist/commands/runCli/utils/resolveScopeFlag.mjs +5 -11
- package/dist/commands/runCli/utils/resolveTargets.d.ts +15 -0
- package/dist/commands/runCli/utils/resolveTargets.mjs +38 -0
- package/dist/commands/runCli/utils/toConsumerPackages.d.ts +9 -0
- package/dist/commands/runCli/utils/toConsumerPackages.mjs +26 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/injectDocs/index.d.ts +3 -2
- package/dist/core/injectDocs/type.d.ts +0 -19
- package/dist/core/injectDocs/utils/applyAction.mjs +1 -1
- package/dist/core/scope/index.d.ts +1 -1
- package/dist/core/scope/scope.d.ts +0 -1
- package/dist/core/scope/scope.mjs +1 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/ui/InjectApp/InjectApp.d.ts +2 -0
- package/dist/ui/InjectApp/InjectApp.mjs +82 -0
- package/dist/ui/InjectApp/index.d.ts +2 -0
- package/dist/ui/InjectApp/utils/eventSelectors.d.ts +5 -0
- package/dist/ui/InjectApp/utils/eventSelectors.mjs +24 -0
- package/dist/ui/InjectApp/utils/phaseReducer.d.ts +2 -0
- package/dist/ui/InjectApp/utils/phaseReducer.mjs +130 -0
- package/dist/ui/InjectApp/utils/renderInjectApp.d.ts +2 -0
- package/dist/ui/InjectApp/utils/renderInjectApp.mjs +19 -0
- package/dist/ui/InjectApp/utils/type.d.ts +5 -0
- package/dist/ui/components/ActionRow.d.ts +7 -0
- package/dist/ui/components/ActionRow.mjs +45 -0
- package/dist/ui/components/Banner.d.ts +7 -0
- package/dist/ui/components/Banner.mjs +9 -0
- package/dist/ui/components/ConfirmForce.d.ts +8 -0
- package/dist/ui/components/ConfirmForce.mjs +35 -0
- package/dist/ui/components/ErrorPanel.d.ts +6 -0
- package/dist/ui/components/ErrorPanel.mjs +14 -0
- package/dist/ui/components/Footer.d.ts +8 -0
- package/dist/ui/components/Footer.mjs +27 -0
- package/dist/ui/components/PlanTable.d.ts +8 -0
- package/dist/ui/components/PlanTable.mjs +15 -0
- package/dist/ui/components/ProgressBar.d.ts +10 -0
- package/dist/ui/components/ProgressBar.mjs +28 -0
- package/dist/ui/components/ScopePicker.d.ts +7 -0
- package/dist/ui/components/ScopePicker.mjs +26 -0
- package/dist/ui/components/Spinner.d.ts +8 -0
- package/dist/ui/components/Spinner.mjs +10 -0
- package/dist/ui/components/StatusBadge.d.ts +8 -0
- package/dist/ui/components/StepTracker.d.ts +9 -0
- package/dist/ui/components/StepTracker.mjs +43 -0
- package/dist/ui/components/Summary.d.ts +9 -0
- package/dist/ui/components/Summary.mjs +30 -0
- package/dist/ui/components/TargetCard.d.ts +11 -0
- package/dist/ui/components/TargetCard.mjs +29 -0
- package/dist/ui/hooks/useApplyStep.d.ts +12 -0
- package/dist/ui/hooks/useApplyStep.mjs +30 -0
- package/dist/ui/hooks/useExitApp.d.ts +8 -0
- package/dist/ui/hooks/useExitApp.mjs +19 -0
- package/dist/ui/hooks/useForceConfirmStep.d.ts +9 -0
- package/dist/ui/hooks/useForceConfirmStep.mjs +24 -0
- package/dist/ui/hooks/useInjectSession.d.ts +10 -0
- package/dist/ui/hooks/useInjectSession.mjs +63 -0
- package/dist/ui/hooks/useInterval.d.ts +1 -0
- package/dist/ui/hooks/usePhase.d.ts +2 -0
- package/dist/ui/hooks/usePhase.mjs +9 -0
- package/dist/ui/hooks/usePlanStep.d.ts +13 -0
- package/dist/ui/hooks/usePlanStep.mjs +94 -0
- package/dist/ui/hooks/useResolveStep.d.ts +18 -0
- package/dist/ui/hooks/useResolveStep.mjs +21 -0
- package/dist/ui/hooks/useTerminalWidth.d.ts +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.mjs +16 -0
- package/dist/ui/theme/colors.d.ts +12 -0
- package/dist/ui/theme/colors.mjs +9 -0
- package/dist/ui/theme/icons.d.ts +29 -0
- package/dist/ui/theme/icons.mjs +17 -0
- package/dist/ui/theme/layout.d.ts +20 -0
- package/dist/ui/theme/layout.mjs +9 -0
- package/dist/ui/types/event.d.ts +45 -0
- package/dist/ui/types/index.d.ts +4 -0
- package/dist/ui/types/phase.d.ts +44 -0
- package/dist/ui/types/render.d.ts +6 -0
- package/dist/ui/types/target.d.ts +25 -0
- package/dist/utils/version.d.ts +1 -1
- package/dist/utils/version.mjs +1 -1
- package/docs/claude/skills/claude-docs-asset-wiring/SKILL.md +159 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/claude-md-template.md +78 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md +54 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/gotchas.md +125 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/package-json-patches.md +150 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/reference-files.md +37 -0
- package/docs/claude/skills/claude-docs-asset-wiring/knowledge/smoke-tests.md +111 -0
- package/docs/consumer-integration.md +43 -101
- package/package.json +13 -8
- package/scripts/dev-ui-fixtures.ts +288 -0
- package/scripts/dev-ui.tsx +289 -0
- package/bin/claude-sync.mjs +0 -24
- package/dist/commands/runCli/runCli.cjs +0 -31
- package/dist/commands/runCli/utils/injectOne.cjs +0 -48
- package/dist/commands/runCli/utils/injectOne.d.ts +0 -3
- package/dist/commands/runCli/utils/injectOne.mjs +0 -46
- package/dist/commands/runCli/utils/resolveScopeFlag.cjs +0 -28
- package/dist/commands/runCli/utils/runInject.cjs +0 -36
- package/dist/commands/runCli/utils/runInject.d.ts +0 -2
- package/dist/commands/runCli/utils/runInject.mjs +0 -34
- package/dist/core/buildPlan/buildPlan.cjs +0 -42
- package/dist/core/buildPlan/utils/toPosix.cjs +0 -9
- package/dist/core/buildPlan/utils/walkFiles.cjs +0 -25
- package/dist/core/hash/hash.cjs +0 -30
- package/dist/core/hashManifest/hashManifest.cjs +0 -27
- package/dist/core/injectDocs/injectDocs.cjs +0 -43
- package/dist/core/injectDocs/injectDocs.d.ts +0 -2
- package/dist/core/injectDocs/injectDocs.mjs +0 -41
- package/dist/core/injectDocs/utils/applyAction.cjs +0 -21
- package/dist/core/injectDocs/utils/emitCiForceList.cjs +0 -10
- package/dist/core/injectDocs/utils/emitCiForceList.d.ts +0 -2
- package/dist/core/injectDocs/utils/emitCiForceList.mjs +0 -8
- package/dist/core/injectDocs/utils/printPlan.cjs +0 -20
- package/dist/core/injectDocs/utils/printPlan.d.ts +0 -2
- package/dist/core/injectDocs/utils/printPlan.mjs +0 -18
- package/dist/core/injectDocs/utils/summarize.cjs +0 -27
- package/dist/core/scope/scope.cjs +0 -46
- package/dist/core/scope/utils/isDirectory.cjs +0 -14
- package/dist/index.cjs +0 -20
- package/dist/prompts/confirmForce.cjs +0 -27
- package/dist/prompts/confirmForce.d.ts +0 -1
- package/dist/prompts/confirmForce.mjs +0 -25
- package/dist/prompts/index.d.ts +0 -2
- package/dist/prompts/selectScope.cjs +0 -30
- package/dist/prompts/selectScope.d.ts +0 -2
- package/dist/prompts/selectScope.mjs +0 -28
- package/dist/utils/asyncPool.cjs +0 -26
- package/dist/utils/heartbeat.cjs +0 -25
- package/dist/utils/heartbeat.d.ts +0 -16
- package/dist/utils/heartbeat.mjs +0 -23
- package/dist/utils/logger.cjs +0 -74
- package/dist/utils/version.cjs +0 -5
- package/docs/claude/skills/claude-sync-applier/SKILL.md +0 -195
- package/docs/claude/skills/claude-sync-applier/knowledge/claude-md-template.md +0 -77
- package/docs/claude/skills/claude-sync-applier/knowledge/dependency-cruiser.md +0 -126
- package/docs/claude/skills/claude-sync-applier/knowledge/gotchas.md +0 -139
- package/docs/claude/skills/claude-sync-applier/knowledge/package-json-patches.md +0 -130
- package/docs/claude/skills/claude-sync-applier/knowledge/reference-files.md +0 -120
- package/docs/claude/skills/claude-sync-applier/knowledge/smoke-tests.md +0 -102
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# @slats/claude-assets-sync
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Engine + dispatcher CLI that lets any npm package ship its own Claude Code docs (skills, rules, commands) and inject them into a user's `.claude/` directory. Consumers declare `claude.assetPath` in `package.json` and the engine's `inject-claude-settings` bin handles the rest.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
A consumer package
|
|
7
|
+
A consumer package declares `claude.assetPath` in `package.json` and runs `claude-build-hashes` during build to emit `dist/claude-hashes.json`. End users run `npx -p @slats/claude-assets-sync inject-claude-settings --package=<name>` and this engine resolves that single package's metadata via `createRequire`, compares the hash manifest against the target `.claude/`, and copies only what is out of date.
|
|
8
|
+
|
|
9
|
+
The library operates on exactly one consumer per invocation — the one named in `--package`. It never walks `node_modules` for siblings; it never enumerates workspaces.
|
|
8
10
|
|
|
9
11
|
No GitHub fetch, no `.sync-meta.json`, no migrations — the consumer's `dist/claude-hashes.json` is the single source of truth.
|
|
10
12
|
|
|
@@ -19,96 +21,72 @@ yarn add -D @slats/claude-assets-sync
|
|
|
19
21
|
## CLI Surface
|
|
20
22
|
|
|
21
23
|
```
|
|
22
|
-
claude-
|
|
24
|
+
inject-claude-settings --package=<name> [--scope=user|project] [--dry-run] [--force] [--root=<cwd>]
|
|
25
|
+
claude-build-hashes
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
### End-user invocation
|
|
29
|
+
|
|
30
|
+
The engine is not shipped as a runtime dependency of consumers. Always invoke via `npx -p @slats/claude-assets-sync ...`; the package manager fetches and caches the engine on demand.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -p @slats/claude-assets-sync inject-claude-settings --package=@canard/schema-form --scope=user
|
|
34
|
+
```
|
|
26
35
|
|
|
27
36
|
| Flag | Meaning |
|
|
28
37
|
|---|---|
|
|
29
|
-
| `--
|
|
30
|
-
| `--scope=
|
|
31
|
-
| `--
|
|
32
|
-
| `--
|
|
38
|
+
| `--package <name>` | **Required.** Scoped npm name of a consumer that declares `claude.assetPath`. |
|
|
39
|
+
| `--scope=user` | `~/.claude` (applies globally). |
|
|
40
|
+
| `--scope=project` | Nearest ancestor `.claude` directory, or `<cwd>/.claude` if none found. |
|
|
41
|
+
| `--dry-run` | Print the copy / skip / warn plan, no writes. |
|
|
42
|
+
| `--force` | Overwrite diverged files & delete orphans (interactive confirm on TTY). |
|
|
43
|
+
| `--root <path>` | Override scope-resolution cwd. |
|
|
33
44
|
|
|
34
|
-
**Exit codes**: `0` success / up-to-date / dry-run, `1` runtime error, `2` user / configuration error (
|
|
45
|
+
**Exit codes**: `0` success / up-to-date / dry-run, `1` runtime error, `2` user / configuration error (missing `--package`, missing `--scope` in non-TTY, unresolvable package, missing `claude.assetPath`).
|
|
35
46
|
|
|
36
47
|
For `--scope=project` the target `.claude` directory is resolved by walking up from `process.cwd()` to the nearest existing `.claude` ancestor; the CLI logs `(auto-located)` when this happens.
|
|
37
48
|
|
|
38
|
-
## Consumer Integration (
|
|
49
|
+
## Consumer Integration (2 steps)
|
|
39
50
|
|
|
40
51
|
### 1. `package.json`
|
|
41
52
|
|
|
42
53
|
```jsonc
|
|
43
54
|
{
|
|
44
55
|
"name": "@your-scope/your-package",
|
|
45
|
-
"bin": { "claude-sync": "./bin/claude-sync.mjs" },
|
|
46
|
-
"files": ["dist", "docs", "dist/claude-hashes.json", "bin", "README.md"],
|
|
47
56
|
"scripts": {
|
|
48
57
|
"build": "… && yarn build:hashes",
|
|
49
|
-
"build:hashes": "
|
|
58
|
+
"build:hashes": "claude-build-hashes"
|
|
50
59
|
},
|
|
51
|
-
"
|
|
60
|
+
"devDependencies": {
|
|
52
61
|
"@slats/claude-assets-sync": "workspace:^"
|
|
53
62
|
},
|
|
63
|
+
"files": ["dist", "docs", "README.md"],
|
|
54
64
|
"claude": { "assetPath": "docs/claude" }
|
|
55
65
|
}
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
#!/usr/bin/env node
|
|
64
|
-
import { runCli } from '@slats/claude-assets-sync';
|
|
65
|
-
import { readFile } from 'node:fs/promises';
|
|
66
|
-
import { dirname, resolve } from 'node:path';
|
|
67
|
-
import { fileURLToPath } from 'node:url';
|
|
68
|
-
|
|
69
|
-
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
70
|
-
const pkg = JSON.parse(
|
|
71
|
-
await readFile(resolve(packageRoot, 'package.json'), 'utf-8'),
|
|
72
|
-
);
|
|
68
|
+
- `@slats/claude-assets-sync` MUST be in `devDependencies` — the engine is a CLI-only tool and must not leak into end-user production installs. See Rationale below.
|
|
69
|
+
- Do **not** add any `bin` field. The engine is the sole CLI surface; per-consumer bins would collide under `node_modules/.bin/`.
|
|
70
|
+
- Do **not** expose `./bin/*` or `./docs/*` in `exports`. That would let consumer bundlers pull CLI code or the asset tree into app bundles.
|
|
71
|
+
- Do **not** create a `bin/` or `scripts/` directory in the consumer.
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
process.stderr.write(
|
|
76
|
-
`[claude-sync] missing or invalid "claude.assetPath" in ${resolve(packageRoot, 'package.json')}\n`,
|
|
77
|
-
);
|
|
78
|
-
process.exit(2);
|
|
79
|
-
}
|
|
73
|
+
### 2. Build
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
assetPath: pkg.claude.assetPath,
|
|
86
|
-
}).catch((err) => {
|
|
87
|
-
process.stderr.write(
|
|
88
|
-
`[${pkg.name}] claude-sync failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
89
|
-
);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
});
|
|
75
|
+
```bash
|
|
76
|
+
yarn build
|
|
77
|
+
# rolls up the library, emits types, then `claude-build-hashes` hashes every
|
|
78
|
+
# file under `claude.assetPath` and writes dist/claude-hashes.json
|
|
92
79
|
```
|
|
93
80
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
### 3. `scripts/build-hashes.mjs`
|
|
97
|
-
|
|
98
|
-
```javascript
|
|
99
|
-
#!/usr/bin/env node
|
|
100
|
-
import { buildHashes } from '@slats/claude-assets-sync/buildHashes';
|
|
81
|
+
Ship the resulting `dist/` (including `claude-hashes.json`) alongside `docs/` when you publish.
|
|
101
82
|
|
|
102
|
-
|
|
103
|
-
const { outPath, fileCount } = await buildHashes();
|
|
104
|
-
console.log(`✓ claude-hashes.json written: ${fileCount} file(s) → ${outPath}`);
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error('❌ buildHashes failed:', err?.message ?? err);
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
```
|
|
83
|
+
### Rationale: `devDependencies`, not `dependencies`
|
|
110
84
|
|
|
111
|
-
|
|
85
|
+
- The engine is used at two moments only: (1) the consumer's own build, where `claude-build-hashes` produces `dist/claude-hashes.json`, and (2) the end user's one-off `inject-claude-settings` invocation. Neither is runtime behaviour of the consumer library.
|
|
86
|
+
- Putting the engine in `dependencies` would force every downstream installer of the consumer to pull `commander`, `@inquirer/prompts`, and their transitive trees into their production `node_modules` — dead weight for anyone who never sets up Claude Code assets.
|
|
87
|
+
- The workspace build chain still resolves `.bin/claude-build-hashes` from `devDependencies` at `yarn install` time; yarn workspaces link devDeps and deps identically for workspace-local builds.
|
|
88
|
+
- End users never rely on a hoisted `inject-claude-settings` bin. The canonical invocation is `npx -p @slats/claude-assets-sync inject-claude-settings --package=<THIS>`, which fetches the engine on demand and caches it.
|
|
89
|
+
- Bundle isolation is enforced by the import graph (`src/**` in the consumer never references the engine), not by dependency-type.
|
|
112
90
|
|
|
113
91
|
## Authoring `docs/claude/`
|
|
114
92
|
|
|
@@ -138,6 +116,12 @@ Every file under the asset root is hashed and tracked in `dist/claude-hashes.jso
|
|
|
138
116
|
- `--force` on TTY opens an interactive confirm via `@inquirer/prompts.confirm`, listing up to 3 diverged/orphan paths.
|
|
139
117
|
- `--force` on non-TTY prints the divergent list to stderr and proceeds.
|
|
140
118
|
|
|
119
|
+
## Architectural Invariants
|
|
120
|
+
|
|
121
|
+
- `src/core/**` never reads `package.json` or walks the filesystem. Only the `bin/` layer (and `src/commands/runCli/utils/resolvePackage.ts`, invoked from that dispatcher) is allowed to resolve a single explicitly-named target via `createRequire().resolve('${name}/package.json')`. Cross-package discovery (`--all`, workspace scan) is not supported.
|
|
122
|
+
- Prompts go through `@inquirer/prompts` only. No ink, no React.
|
|
123
|
+
- The engine assumes one consumer per invocation. That is the stable contract — extensions require explicit re-architecture.
|
|
124
|
+
|
|
141
125
|
## Programmatic API
|
|
142
126
|
|
|
143
127
|
```ts
|
|
@@ -156,7 +140,7 @@ See `src/index.ts` and `src/DETAIL.md` for the full export surface.
|
|
|
156
140
|
|
|
157
141
|
## Additional Docs
|
|
158
142
|
|
|
159
|
-
- `docs/consumer-integration.md` — complete consumer checklist (
|
|
143
|
+
- `docs/consumer-integration.md` — complete consumer checklist (package.json patches, verification steps, end-user install topologies)
|
|
160
144
|
- `docs/bundle-size-decision.md` — why `@inquirer/prompts` over ink
|
|
161
145
|
|
|
162
146
|
## License
|
package/dist/claude-hashes.json
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "@slats/claude-assets-sync",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.1"
|
|
6
6
|
},
|
|
7
|
-
"generatedAt": "2026-04-
|
|
7
|
+
"generatedAt": "2026-04-24T16:37:59.339Z",
|
|
8
8
|
"algorithm": "sha256",
|
|
9
9
|
"assetRoot": "docs/claude",
|
|
10
10
|
"files": {
|
|
11
|
-
"skills/claude-
|
|
12
|
-
"skills/claude-
|
|
13
|
-
"skills/claude-
|
|
14
|
-
"skills/claude-
|
|
15
|
-
"skills/claude-
|
|
16
|
-
"skills/claude-
|
|
17
|
-
"skills/claude-
|
|
11
|
+
"skills/claude-docs-asset-wiring/SKILL.md": "4864a5a1aba0813fb64ad19111f3db4369df6cc025e485d03d17cdc9d245bce5",
|
|
12
|
+
"skills/claude-docs-asset-wiring/knowledge/claude-md-template.md": "b0274c5a37076a7e80bd7beccd739b10cc91ca5395fe425be25d9129206c684a",
|
|
13
|
+
"skills/claude-docs-asset-wiring/knowledge/dependency-cruiser.md": "18e994bd16242f72a4dc6cfa508493f4b63853a7a16c4910c7b7abd0368bd103",
|
|
14
|
+
"skills/claude-docs-asset-wiring/knowledge/gotchas.md": "70a6c75ea09816ec457615c060e6a87197bc5ed332a377452b3cacd974b44eee",
|
|
15
|
+
"skills/claude-docs-asset-wiring/knowledge/package-json-patches.md": "063469932191843a2f7f9156a513717947bcad584c4084a0c8300897683885e7",
|
|
16
|
+
"skills/claude-docs-asset-wiring/knowledge/reference-files.md": "1b5f76d262f47835c4d0150a48fa25dfc644609fd4ecad260273a4cab57e41a2",
|
|
17
|
+
"skills/claude-docs-asset-wiring/knowledge/smoke-tests.md": "c90fc82d46bf4b55d794c2ce463b4c1e199d13d6b1161028486fb4bbbd6a60fe"
|
|
18
18
|
},
|
|
19
19
|
"previousVersions": {}
|
|
20
20
|
}
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { runCli, type DefaultFlags
|
|
1
|
+
export { runCli, type DefaultFlags } from './runCli/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { runCli } from './runCli.js';
|
|
2
|
-
export type { DefaultFlags
|
|
2
|
+
export type { DefaultFlags } from './type.js';
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { RunCliOptions } from './type.js';
|
|
2
1
|
/**
|
|
3
2
|
* CLI entry for `@slats/claude-assets-sync`.
|
|
4
3
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The `inject-claude-settings` dispatcher parses `--package <name...>`
|
|
5
|
+
* from argv and classifies each value:
|
|
6
|
+
* - `@<scope>` — enumerate every workspace package under that scope
|
|
7
|
+
* - `@<scope>/<name>` — one scoped package
|
|
8
|
+
* - `<name>` — one unscoped package
|
|
9
|
+
*
|
|
10
|
+
* Targets are resolved via Node module resolution (`resolvePackage`)
|
|
11
|
+
* except for scope aliases, which are the only path allowed to walk
|
|
12
|
+
* the monorepo — that exception is isolated to `resolveScopeAlias.ts`.
|
|
9
13
|
*/
|
|
10
|
-
export declare function runCli(argv
|
|
14
|
+
export declare function runCli(argv?: readonly string[]): Promise<void>;
|
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { logger } from '../../utils/logger.mjs';
|
|
3
3
|
import { VERSION } from '../../utils/version.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { renderOrFallback } from './utils/renderOrFallback.mjs';
|
|
5
|
+
import { resolveTargets } from './utils/resolveTargets.mjs';
|
|
6
|
+
import { toConsumerPackages } from './utils/toConsumerPackages.mjs';
|
|
5
7
|
|
|
6
|
-
async function runCli(argv = process.argv
|
|
8
|
+
async function runCli(argv = process.argv) {
|
|
7
9
|
const cmd = new Command();
|
|
8
10
|
cmd
|
|
9
|
-
.name('claude-
|
|
10
|
-
.description("Inject
|
|
11
|
-
.version(
|
|
11
|
+
.name('inject-claude-settings')
|
|
12
|
+
.description("Inject target consumer(s)' Claude assets into the selected .claude directory")
|
|
13
|
+
.version(VERSION)
|
|
14
|
+
.option('--package <name...>', 'Target(s). "@<scope>" = whole npm scope; "@<scope>/<name>" or "<name>" = one package. Repeat the flag or comma-separate values.', collectPackageValues, [])
|
|
12
15
|
.option('--scope <scope>', 'Target scope: user (~/.claude) | project (nearest ancestor .claude or <cwd>/.claude)')
|
|
13
16
|
.option('--dry-run', 'Preview without writing', false)
|
|
14
17
|
.option('--force', 'Overwrite user modifications', false)
|
|
15
18
|
.option('--root <path>', 'Override scope resolution cwd (default: cwd)')
|
|
19
|
+
.option('--json', 'Emit structured JSON output (forces non-interactive legacy logger path)', false)
|
|
16
20
|
.action(async (flags) => {
|
|
17
|
-
|
|
21
|
+
const targets = flags.package ?? [];
|
|
22
|
+
if (targets.length === 0) {
|
|
23
|
+
logger.error('missing required flag: --package <name> (e.g. --package=@canard/schema-form or --package=@canard)');
|
|
24
|
+
process.exit(2);
|
|
25
|
+
}
|
|
26
|
+
const originCwd = flags.root ?? process.cwd();
|
|
27
|
+
const metadataList = await resolveTargets(targets, originCwd);
|
|
28
|
+
if (metadataList.length === 0) {
|
|
29
|
+
logger.warn(`no packages resolved from --package target(s): ${targets.join(', ')}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const consumerPackages = await toConsumerPackages(metadataList);
|
|
33
|
+
const exitCode = await renderOrFallback(consumerPackages, flags, originCwd);
|
|
34
|
+
if (exitCode !== 0)
|
|
35
|
+
process.exit(exitCode);
|
|
18
36
|
});
|
|
19
37
|
try {
|
|
20
38
|
await cmd.parseAsync([...argv]);
|
|
@@ -25,5 +43,14 @@ async function runCli(argv = process.argv, options) {
|
|
|
25
43
|
process.exit(1);
|
|
26
44
|
}
|
|
27
45
|
}
|
|
46
|
+
function collectPackageValues(value, previous = []) {
|
|
47
|
+
return [
|
|
48
|
+
...previous,
|
|
49
|
+
...value
|
|
50
|
+
.split(',')
|
|
51
|
+
.map((s) => s.trim())
|
|
52
|
+
.filter(Boolean),
|
|
53
|
+
];
|
|
54
|
+
}
|
|
28
55
|
|
|
29
56
|
export { runCli };
|
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
export interface RunCliOptions {
|
|
2
|
-
version?: string;
|
|
3
|
-
/** Absolute filesystem path to the consumer package root. */
|
|
4
|
-
packageRoot: string;
|
|
5
|
-
/** Consumer package name used in logs and error messages. */
|
|
6
|
-
packageName: string;
|
|
7
|
-
/** Consumer package version used in logs. */
|
|
8
|
-
packageVersion: string;
|
|
9
|
-
/** Asset directory path relative to `packageRoot`. */
|
|
10
|
-
assetPath: string;
|
|
11
|
-
}
|
|
12
1
|
export interface DefaultFlags {
|
|
13
2
|
scope?: string;
|
|
14
3
|
dryRun?: boolean;
|
|
15
4
|
force?: boolean;
|
|
16
5
|
root?: string;
|
|
6
|
+
package?: string[];
|
|
7
|
+
json?: boolean;
|
|
17
8
|
}
|
|
18
9
|
/**
|
|
19
10
|
* Resolved consumer metadata passed to the injection pipeline.
|
|
20
|
-
* The
|
|
11
|
+
* The dispatcher bin populates this by resolving a single explicitly-named
|
|
12
|
+
* target package — `core/**` still never reads `package.json` itself.
|
|
21
13
|
*/
|
|
22
14
|
export interface ConsumerPackage {
|
|
23
15
|
name: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ClassifiedTarget = {
|
|
2
|
+
kind: 'scope';
|
|
3
|
+
scope: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'package';
|
|
6
|
+
name: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'invalid';
|
|
9
|
+
reason: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Classify a `--package` value as a scope alias, a package name, or invalid.
|
|
13
|
+
*
|
|
14
|
+
* - `@<scope>` (no slash) — all packages under that npm scope
|
|
15
|
+
* - `@<scope>/<name>` — one scoped package
|
|
16
|
+
* - `<name>` (no `@`, no slash) — one unscoped package
|
|
17
|
+
* - anything else → invalid
|
|
18
|
+
*/
|
|
19
|
+
export declare function classifyTarget(target: string): ClassifiedTarget;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const NPM_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
2
|
+
function classifyTarget(target) {
|
|
3
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
4
|
+
return { kind: 'invalid', reason: 'empty --package value' };
|
|
5
|
+
}
|
|
6
|
+
if (target.startsWith('@')) {
|
|
7
|
+
const body = target.slice(1);
|
|
8
|
+
const slashIndex = body.indexOf('/');
|
|
9
|
+
if (slashIndex === -1) {
|
|
10
|
+
if (!NPM_NAME_PATTERN.test(body)) {
|
|
11
|
+
return {
|
|
12
|
+
kind: 'invalid',
|
|
13
|
+
reason: `invalid scope alias "${target}" — expected "@<scope>" with lowercase alphanumerics, ".", "-", or "_"`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return { kind: 'scope', scope: body };
|
|
17
|
+
}
|
|
18
|
+
const scopePart = body.slice(0, slashIndex);
|
|
19
|
+
const namePart = body.slice(slashIndex + 1);
|
|
20
|
+
if (!NPM_NAME_PATTERN.test(scopePart) ||
|
|
21
|
+
namePart.length === 0 ||
|
|
22
|
+
namePart.includes('/') ||
|
|
23
|
+
!NPM_NAME_PATTERN.test(namePart)) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'invalid',
|
|
26
|
+
reason: `invalid scoped package "${target}" — expected "@<scope>/<name>"`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { kind: 'package', name: target };
|
|
30
|
+
}
|
|
31
|
+
if (target.includes('/')) {
|
|
32
|
+
return {
|
|
33
|
+
kind: 'invalid',
|
|
34
|
+
reason: `invalid target "${target}" — unscoped package names cannot contain "/"`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!NPM_NAME_PATTERN.test(target)) {
|
|
38
|
+
return {
|
|
39
|
+
kind: 'invalid',
|
|
40
|
+
reason: `invalid package name "${target}" — expected lowercase alphanumerics, ".", "-", or "_"`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { kind: 'package', name: target };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { classifyTarget };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConsumerPackage, DefaultFlags } from '../type.js';
|
|
2
|
+
interface RenderEnv {
|
|
3
|
+
readonly isTTY?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function renderOrFallback(targets: readonly ConsumerPackage[], flags: DefaultFlags, originCwd: string, env?: RenderEnv): Promise<number>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { renderPlain } from './renderPlain.mjs';
|
|
2
|
+
|
|
3
|
+
async function renderOrFallback(targets, flags, originCwd, env = {}) {
|
|
4
|
+
const isTTY = env.isTTY ?? Boolean(process.stdout.isTTY);
|
|
5
|
+
if (flags.json || !isTTY) {
|
|
6
|
+
return renderPlain(targets, flags, originCwd);
|
|
7
|
+
}
|
|
8
|
+
const ui = (await import('../../../ui/index.mjs'));
|
|
9
|
+
return ui.renderInjectApp({ targets, flags, originCwd });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { renderOrFallback };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConsumerPackage, DefaultFlags } from '../type.js';
|
|
2
|
+
/**
|
|
3
|
+
* Plain (picocolors) renderer for non-TTY / `--json` invocations.
|
|
4
|
+
*
|
|
5
|
+
* Composes the same `core/**` primitives that the Ink `useInjectSession`
|
|
6
|
+
* pipeline uses — no legacy `injectDocs` orchestrator in between.
|
|
7
|
+
* Missing `--scope` on non-TTY exits via `resolveScopeFlag` with code 2.
|
|
8
|
+
* Divergent/orphan files still respect `--force`: absence of `--force`
|
|
9
|
+
* returns 2; presence logs the list to stderr and proceeds.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderPlain(targets: readonly ConsumerPackage[], flags: DefaultFlags, originCwd: string): Promise<number>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import 'node:crypto';
|
|
2
|
+
import 'node:fs/promises';
|
|
3
|
+
import { readHashManifest, computeNamespacePrefixes } from '../../../core/hashManifest/hashManifest.mjs';
|
|
4
|
+
import { applyAction } from '../../../core/injectDocs/utils/applyAction.mjs';
|
|
5
|
+
import { summarize } from '../../../core/injectDocs/utils/summarize.mjs';
|
|
6
|
+
import { buildPlan } from '../../../core/buildPlan/buildPlan.mjs';
|
|
7
|
+
import { resolveScope } from '../../../core/scope/scope.mjs';
|
|
8
|
+
import { asyncPool } from '../../../utils/asyncPool.mjs';
|
|
9
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
10
|
+
import { resolveScopeFlag } from './resolveScopeFlag.mjs';
|
|
11
|
+
|
|
12
|
+
async function renderPlain(targets, flags, originCwd) {
|
|
13
|
+
if (targets.length === 0)
|
|
14
|
+
return 0;
|
|
15
|
+
const scope = resolveScopeFlag(flags.scope);
|
|
16
|
+
const fatalOnError = targets.length === 1;
|
|
17
|
+
let failureCount = 0;
|
|
18
|
+
for (const target of targets) {
|
|
19
|
+
if (!target.hashesPresent) {
|
|
20
|
+
logger.warn(`${target.name}: dist/claude-hashes.json missing — build the package (e.g. yarn build) to regenerate the hash manifest first.`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
logger.heading(`${target.name}@${target.version}`);
|
|
24
|
+
let exitCode;
|
|
25
|
+
try {
|
|
26
|
+
exitCode = await renderOneTarget(target, scope, flags, originCwd);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
30
|
+
logger.error(`${target.name}: ${msg}`);
|
|
31
|
+
exitCode = 1;
|
|
32
|
+
}
|
|
33
|
+
if (exitCode !== 0) {
|
|
34
|
+
if (fatalOnError)
|
|
35
|
+
return exitCode;
|
|
36
|
+
failureCount += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return failureCount > 0 ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
async function renderOneTarget(target, scope, flags, originCwd) {
|
|
42
|
+
const manifest = await readHashManifest(target.packageRoot);
|
|
43
|
+
const resolution = resolveScope(scope, originCwd);
|
|
44
|
+
const plan = await buildPlan({
|
|
45
|
+
sourceHashes: manifest.files,
|
|
46
|
+
targetRoot: resolution.targetRoot,
|
|
47
|
+
namespacePrefixes: computeNamespacePrefixes(manifest),
|
|
48
|
+
force: flags.force ?? false,
|
|
49
|
+
});
|
|
50
|
+
logger.info(`${target.name}@${target.version} → ${resolution.description}`);
|
|
51
|
+
printPlan(plan);
|
|
52
|
+
if (plan.requiresForce && !flags.force) {
|
|
53
|
+
logger.error('Re-run with --force to proceed, or inspect with --dry-run.');
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
if (flags.force &&
|
|
57
|
+
plan.actions.some((a) => a.kind === 'warn-diverged' || a.kind === 'warn-orphan')) {
|
|
58
|
+
emitForceList(plan);
|
|
59
|
+
}
|
|
60
|
+
if (flags.dryRun) {
|
|
61
|
+
logger.warn('[DRY RUN] No files will be created, overwritten, or deleted.');
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
await asyncPool(8, plan.actions, (action) => applyAction(action, target.assetRoot));
|
|
65
|
+
const report = summarize(plan, 0);
|
|
66
|
+
return report.exitCode;
|
|
67
|
+
}
|
|
68
|
+
function printPlan(plan) {
|
|
69
|
+
for (const action of plan.actions) {
|
|
70
|
+
if (action.kind === 'copy')
|
|
71
|
+
logger.file('create', action.relPath);
|
|
72
|
+
else if (action.kind === 'skip-uptodate')
|
|
73
|
+
logger.file('skip', `${action.relPath} (up-to-date)`);
|
|
74
|
+
else if (action.kind === 'warn-diverged')
|
|
75
|
+
logger.warn(`${action.relPath} — local differs from source (user edit or version change)`);
|
|
76
|
+
else if (action.kind === 'warn-orphan')
|
|
77
|
+
logger.warn(`${action.relPath} — present locally, absent in source`);
|
|
78
|
+
else if (action.kind === 'delete')
|
|
79
|
+
logger.file('update', `${action.relPath} (deleting)`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function emitForceList(plan) {
|
|
83
|
+
const divergent = plan.actions.filter((action) => action.kind === 'warn-diverged' || action.kind === 'warn-orphan');
|
|
84
|
+
process.stderr.write(`[claude-assets-sync] --force overwriting ${divergent.length} file(s) in non-TTY mode:\n`);
|
|
85
|
+
for (const action of divergent)
|
|
86
|
+
process.stderr.write(` ${action.relPath}\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { renderPlain };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ResolvedMetadata {
|
|
2
|
+
packageRoot: string;
|
|
3
|
+
packageName: string;
|
|
4
|
+
packageVersion: string;
|
|
5
|
+
assetPath: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvePackageOptions {
|
|
8
|
+
/**
|
|
9
|
+
* When `true`, a package without `claude.assetPath` is warned and the
|
|
10
|
+
* function returns `null` instead of calling `process.exit`. Default
|
|
11
|
+
* `false` preserves the v0.3.0 strict behavior for single-target
|
|
12
|
+
* dispatcher calls.
|
|
13
|
+
*/
|
|
14
|
+
skipMissingAsset?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function resolvePackage(name: string, options?: ResolvePackageOptions): Promise<ResolvedMetadata | null>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { logger } from '../../../utils/logger.mjs';
|
|
6
|
+
|
|
7
|
+
async function resolvePackage(name, options = {}) {
|
|
8
|
+
const pkgJsonPath = resolvePackageJsonPath(name);
|
|
9
|
+
if (!pkgJsonPath) {
|
|
10
|
+
logger.error(`cannot resolve package "${name}". Install it in the current project or pass the correct name.`);
|
|
11
|
+
process.exit(2);
|
|
12
|
+
}
|
|
13
|
+
const packageRoot = dirname(pkgJsonPath);
|
|
14
|
+
const raw = await readFile(pkgJsonPath, 'utf-8');
|
|
15
|
+
const pkg = JSON.parse(raw);
|
|
16
|
+
if (typeof pkg.name !== 'string' || typeof pkg.version !== 'string') {
|
|
17
|
+
if (options.skipMissingAsset) {
|
|
18
|
+
logger.warn(`"${name}" package.json is missing a string "name" or "version" — skipping.`);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
logger.error(`${pkgJsonPath} must define string "name" and "version".`);
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
const assetPath = pkg.claude?.assetPath;
|
|
25
|
+
if (typeof assetPath !== 'string' || assetPath.length === 0) {
|
|
26
|
+
if (options.skipMissingAsset) {
|
|
27
|
+
logger.warn(`"${name}" is missing "claude.assetPath" — skipping (the package does not ship Claude assets).`);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
logger.error(`"${name}" is missing "claude.assetPath" in its package.json — the package does not ship Claude assets.`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
packageRoot,
|
|
35
|
+
packageName: pkg.name,
|
|
36
|
+
packageVersion: pkg.version,
|
|
37
|
+
assetPath,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function resolvePackageJsonPath(name) {
|
|
41
|
+
const require = createRequire(import.meta.url);
|
|
42
|
+
try {
|
|
43
|
+
return require.resolve(`${name}/package.json`);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const code = err?.code;
|
|
47
|
+
if (code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED')
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
let mainEntry;
|
|
51
|
+
try {
|
|
52
|
+
mainEntry = require.resolve(name);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let dir = dirname(mainEntry);
|
|
58
|
+
while (dir && dir !== dirname(dir)) {
|
|
59
|
+
const candidate = resolve(dir, 'package.json');
|
|
60
|
+
if (existsSync(candidate)) {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
63
|
+
if (pkg.name === name)
|
|
64
|
+
return candidate;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
dir = dirname(dir);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { resolvePackage };
|