claudekit-cli 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/claude-code-review.yml +57 -0
- package/.github/workflows/claude.yml +50 -0
- package/CHANGELOG.md +7 -0
- package/README.md +98 -0
- package/dist/index.js +100 -11
- package/package.json +1 -1
- package/src/commands/new.ts +12 -0
- package/src/commands/update.ts +11 -0
- package/src/index.ts +12 -2
- package/src/lib/download.ts +35 -2
- package/src/types.ts +11 -0
- package/tests/types.test.ts +75 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Claude Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize]
|
|
6
|
+
# Optional: Only run on specific file changes
|
|
7
|
+
# paths:
|
|
8
|
+
# - "src/**/*.ts"
|
|
9
|
+
# - "src/**/*.tsx"
|
|
10
|
+
# - "src/**/*.js"
|
|
11
|
+
# - "src/**/*.jsx"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude-review:
|
|
15
|
+
# Optional: Filter by PR author
|
|
16
|
+
# if: |
|
|
17
|
+
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
18
|
+
# github.event.pull_request.user.login == 'new-developer' ||
|
|
19
|
+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pull-requests: read
|
|
25
|
+
issues: read
|
|
26
|
+
id-token: write
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 1
|
|
33
|
+
|
|
34
|
+
- name: Run Claude Code Review
|
|
35
|
+
id: claude-review
|
|
36
|
+
uses: anthropics/claude-code-action@v1
|
|
37
|
+
with:
|
|
38
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
+
prompt: |
|
|
40
|
+
REPO: ${{ github.repository }}
|
|
41
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
42
|
+
|
|
43
|
+
Please review this pull request and provide feedback on:
|
|
44
|
+
- Code quality and best practices
|
|
45
|
+
- Potential bugs or issues
|
|
46
|
+
- Performance considerations
|
|
47
|
+
- Security concerns
|
|
48
|
+
- Test coverage
|
|
49
|
+
|
|
50
|
+
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
|
51
|
+
|
|
52
|
+
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
|
53
|
+
|
|
54
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
55
|
+
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
|
56
|
+
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
|
57
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
issues:
|
|
9
|
+
types: [opened, assigned]
|
|
10
|
+
pull_request_review:
|
|
11
|
+
types: [submitted]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude:
|
|
15
|
+
if: |
|
|
16
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
pull-requests: read
|
|
24
|
+
issues: read
|
|
25
|
+
id-token: write
|
|
26
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout repository
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
|
|
33
|
+
- name: Run Claude Code
|
|
34
|
+
id: claude
|
|
35
|
+
uses: anthropics/claude-code-action@v1
|
|
36
|
+
with:
|
|
37
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
|
+
|
|
39
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
|
+
additional_permissions: |
|
|
41
|
+
actions: read
|
|
42
|
+
|
|
43
|
+
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
|
44
|
+
# prompt: 'Update the pull request description to include a summary of changes.'
|
|
45
|
+
|
|
46
|
+
# Optional: Add claude_args to customize behavior and configuration
|
|
47
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
48
|
+
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
|
49
|
+
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
|
50
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/mrgoonie/claudekit-cli/compare/v1.3.0...v1.4.0) (2025-10-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add --exclude flag to new and update commands ([8a0d7a0](https://github.com/mrgoonie/claudekit-cli/commit/8a0d7a00de70823d4fecac26d4c7e82c4df2ab0f))
|
|
7
|
+
|
|
1
8
|
# [1.3.0](https://github.com/mrgoonie/claudekit-cli/compare/v1.2.2...v1.3.0) (2025-10-21)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -79,6 +79,12 @@ ck new --dir my-project --kit engineer
|
|
|
79
79
|
|
|
80
80
|
# Specific version
|
|
81
81
|
ck new --kit engineer --version v1.0.0
|
|
82
|
+
|
|
83
|
+
# With exclude patterns
|
|
84
|
+
ck new --kit engineer --exclude "*.log" --exclude "temp/**"
|
|
85
|
+
|
|
86
|
+
# Multiple patterns
|
|
87
|
+
ck new --exclude "*.log" --exclude "*.tmp" --exclude "cache/**"
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
### Update Existing Project
|
|
@@ -94,6 +100,9 @@ ck update --kit engineer
|
|
|
94
100
|
|
|
95
101
|
# Specific version
|
|
96
102
|
ck update --kit engineer --version v1.0.0
|
|
103
|
+
|
|
104
|
+
# With exclude patterns
|
|
105
|
+
ck update --exclude "local-config/**" --exclude "*.local"
|
|
97
106
|
```
|
|
98
107
|
|
|
99
108
|
### List Available Versions
|
|
@@ -204,6 +213,95 @@ The following file patterns are protected and will not be overwritten during upd
|
|
|
204
213
|
- `node_modules/**`, `.git/**`
|
|
205
214
|
- `dist/**`, `build/**`
|
|
206
215
|
|
|
216
|
+
## Excluding Files
|
|
217
|
+
|
|
218
|
+
Use the `--exclude` flag to skip specific files or directories during download and extraction. This is useful for:
|
|
219
|
+
|
|
220
|
+
- Excluding temporary or cache directories
|
|
221
|
+
- Skipping log files or debug output
|
|
222
|
+
- Omitting files you want to manage manually
|
|
223
|
+
- Avoiding unnecessary large files
|
|
224
|
+
|
|
225
|
+
### Basic Usage
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Exclude log files
|
|
229
|
+
ck new --exclude "*.log"
|
|
230
|
+
|
|
231
|
+
# Exclude multiple patterns
|
|
232
|
+
ck new --exclude "*.log" --exclude "temp/**" --exclude "cache/**"
|
|
233
|
+
|
|
234
|
+
# Common exclude patterns for updates
|
|
235
|
+
ck update --exclude "node_modules/**" --exclude "dist/**" --exclude ".env.*"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Supported Glob Patterns
|
|
239
|
+
|
|
240
|
+
The `--exclude` flag accepts standard glob patterns:
|
|
241
|
+
|
|
242
|
+
- `*` - Match any characters except `/` (e.g., `*.log` matches all log files)
|
|
243
|
+
- `**` - Match any characters including `/` (e.g., `temp/**` matches all files in temp directory)
|
|
244
|
+
- `?` - Match single character (e.g., `file?.txt` matches `file1.txt`, `file2.txt`)
|
|
245
|
+
- `[abc]` - Match characters in brackets (e.g., `[Tt]emp` matches `Temp` or `temp`)
|
|
246
|
+
- `{a,b}` - Match alternatives (e.g., `*.{log,tmp}` matches `*.log` and `*.tmp`)
|
|
247
|
+
|
|
248
|
+
### Common Exclude Patterns
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Exclude all log files
|
|
252
|
+
--exclude "*.log" --exclude "**/*.log"
|
|
253
|
+
|
|
254
|
+
# Exclude temporary directories
|
|
255
|
+
--exclude "tmp/**" --exclude "temp/**" --exclude ".tmp/**"
|
|
256
|
+
|
|
257
|
+
# Exclude cache directories
|
|
258
|
+
--exclude "cache/**" --exclude ".cache/**" --exclude "**/.cache/**"
|
|
259
|
+
|
|
260
|
+
# Exclude build artifacts
|
|
261
|
+
--exclude "dist/**" --exclude "build/**" --exclude "out/**"
|
|
262
|
+
|
|
263
|
+
# Exclude local configuration
|
|
264
|
+
--exclude "*.local" --exclude "local/**" --exclude ".env.local"
|
|
265
|
+
|
|
266
|
+
# Exclude IDE/editor files
|
|
267
|
+
--exclude ".vscode/**" --exclude ".idea/**" --exclude "*.swp"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Important Notes
|
|
271
|
+
|
|
272
|
+
**Additive Behavior:**
|
|
273
|
+
- User exclude patterns are ADDED to the default protected patterns
|
|
274
|
+
- They do not replace the built-in protections
|
|
275
|
+
- All patterns work together to determine which files to skip
|
|
276
|
+
|
|
277
|
+
**Security Restrictions:**
|
|
278
|
+
- Absolute paths (starting with `/`) are not allowed
|
|
279
|
+
- Path traversal patterns (containing `..`) are not allowed
|
|
280
|
+
- Patterns must be between 1-500 characters
|
|
281
|
+
- These restrictions prevent accidental or malicious file system access
|
|
282
|
+
|
|
283
|
+
**Pattern Matching:**
|
|
284
|
+
- Patterns are case-sensitive on Linux/macOS
|
|
285
|
+
- Patterns are case-insensitive on Windows
|
|
286
|
+
- Patterns are applied during both extraction and merge phases
|
|
287
|
+
- Excluded files are never written to disk, saving time and space
|
|
288
|
+
|
|
289
|
+
**Examples of Invalid Patterns:**
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# ❌ Absolute paths not allowed
|
|
293
|
+
ck new --exclude "/etc/passwd"
|
|
294
|
+
|
|
295
|
+
# ❌ Path traversal not allowed
|
|
296
|
+
ck new --exclude "../../secret"
|
|
297
|
+
|
|
298
|
+
# ❌ Empty patterns not allowed
|
|
299
|
+
ck new --exclude ""
|
|
300
|
+
|
|
301
|
+
# ✅ Correct way to exclude root-level files
|
|
302
|
+
ck new --exclude "secret.txt" --exclude "config.local.json"
|
|
303
|
+
```
|
|
304
|
+
|
|
207
305
|
### Custom .claude Files
|
|
208
306
|
|
|
209
307
|
When updating a project, the CLI automatically preserves your custom `.claude/` files that don't exist in the new release package. This allows you to maintain:
|
package/dist/index.js
CHANGED
|
@@ -6400,6 +6400,66 @@ class CAC extends EventEmitter {
|
|
|
6400
6400
|
}
|
|
6401
6401
|
}
|
|
6402
6402
|
var cac = (name = "") => new CAC(name);
|
|
6403
|
+
// package.json
|
|
6404
|
+
var package_default = {
|
|
6405
|
+
name: "claudekit-cli",
|
|
6406
|
+
version: "1.3.0",
|
|
6407
|
+
description: "CLI tool for bootstrapping and updating ClaudeKit projects",
|
|
6408
|
+
type: "module",
|
|
6409
|
+
bin: {
|
|
6410
|
+
ck: "./dist/index.js"
|
|
6411
|
+
},
|
|
6412
|
+
scripts: {
|
|
6413
|
+
dev: "bun run src/index.ts >> logs.txt 2>&1",
|
|
6414
|
+
build: "bun build src/index.ts --outdir dist --target node --external keytar --external @octokit/rest >> logs.txt 2>&1",
|
|
6415
|
+
compile: "bun build src/index.ts --compile --outfile ck >> logs.txt 2>&1",
|
|
6416
|
+
test: "bun test >> logs.txt 2>&1",
|
|
6417
|
+
"test:watch": "bun test --watch >> logs.txt 2>&1",
|
|
6418
|
+
lint: "biome check . >> logs.txt 2>&1",
|
|
6419
|
+
format: "biome format --write . >> logs.txt 2>&1",
|
|
6420
|
+
typecheck: "tsc --noEmit >> logs.txt 2>&1"
|
|
6421
|
+
},
|
|
6422
|
+
keywords: [
|
|
6423
|
+
"cli",
|
|
6424
|
+
"claudekit",
|
|
6425
|
+
"boilerplate",
|
|
6426
|
+
"bootstrap",
|
|
6427
|
+
"template"
|
|
6428
|
+
],
|
|
6429
|
+
author: "ClaudeKit",
|
|
6430
|
+
license: "MIT",
|
|
6431
|
+
engines: {
|
|
6432
|
+
bun: ">=1.0.0"
|
|
6433
|
+
},
|
|
6434
|
+
dependencies: {
|
|
6435
|
+
"@clack/prompts": "^0.7.0",
|
|
6436
|
+
"@octokit/rest": "^22.0.0",
|
|
6437
|
+
cac: "^6.7.14",
|
|
6438
|
+
"cli-progress": "^3.12.0",
|
|
6439
|
+
"extract-zip": "^2.0.1",
|
|
6440
|
+
"fs-extra": "^11.2.0",
|
|
6441
|
+
ignore: "^5.3.2",
|
|
6442
|
+
keytar: "^7.9.0",
|
|
6443
|
+
ora: "^9.0.0",
|
|
6444
|
+
picocolors: "^1.1.1",
|
|
6445
|
+
tar: "^7.4.3",
|
|
6446
|
+
tmp: "^0.2.3",
|
|
6447
|
+
zod: "^3.23.8"
|
|
6448
|
+
},
|
|
6449
|
+
devDependencies: {
|
|
6450
|
+
"@biomejs/biome": "^1.9.4",
|
|
6451
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
6452
|
+
"@semantic-release/git": "^10.0.1",
|
|
6453
|
+
"@types/bun": "latest",
|
|
6454
|
+
"@types/cli-progress": "^3.11.6",
|
|
6455
|
+
"@types/fs-extra": "^11.0.4",
|
|
6456
|
+
"@types/node": "^22.10.1",
|
|
6457
|
+
"@types/tar": "^6.1.13",
|
|
6458
|
+
"@types/tmp": "^0.2.6",
|
|
6459
|
+
"semantic-release": "^24.2.0",
|
|
6460
|
+
typescript: "^5.7.2"
|
|
6461
|
+
}
|
|
6462
|
+
};
|
|
6403
6463
|
|
|
6404
6464
|
// src/commands/new.ts
|
|
6405
6465
|
var import_fs_extra2 = __toESM(require_lib(), 1);
|
|
@@ -10922,16 +10982,19 @@ var coerce = {
|
|
|
10922
10982
|
var NEVER = INVALID;
|
|
10923
10983
|
// src/types.ts
|
|
10924
10984
|
var KitType = exports_external.enum(["engineer", "marketing"]);
|
|
10985
|
+
var ExcludePatternSchema = exports_external.string().trim().min(1, "Exclude pattern cannot be empty").max(500, "Exclude pattern too long").refine((val) => !val.startsWith("/"), "Absolute paths not allowed in exclude patterns").refine((val) => !val.includes(".."), "Path traversal not allowed in exclude patterns");
|
|
10925
10986
|
var NewCommandOptionsSchema = exports_external.object({
|
|
10926
10987
|
dir: exports_external.string().default("."),
|
|
10927
10988
|
kit: KitType.optional(),
|
|
10928
10989
|
version: exports_external.string().optional(),
|
|
10929
|
-
force: exports_external.boolean().default(false)
|
|
10990
|
+
force: exports_external.boolean().default(false),
|
|
10991
|
+
exclude: exports_external.array(ExcludePatternSchema).optional().default([])
|
|
10930
10992
|
});
|
|
10931
10993
|
var UpdateCommandOptionsSchema = exports_external.object({
|
|
10932
10994
|
dir: exports_external.string().default("."),
|
|
10933
10995
|
kit: KitType.optional(),
|
|
10934
|
-
version: exports_external.string().optional()
|
|
10996
|
+
version: exports_external.string().optional(),
|
|
10997
|
+
exclude: exports_external.array(ExcludePatternSchema).optional().default([])
|
|
10935
10998
|
});
|
|
10936
10999
|
var VersionCommandOptionsSchema = exports_external.object({
|
|
10937
11000
|
kit: KitType.optional(),
|
|
@@ -21213,9 +21276,21 @@ class DownloadManager {
|
|
|
21213
21276
|
"*.log"
|
|
21214
21277
|
];
|
|
21215
21278
|
totalExtractedSize = 0;
|
|
21279
|
+
ig;
|
|
21280
|
+
userExcludePatterns = [];
|
|
21281
|
+
constructor() {
|
|
21282
|
+
this.ig = import_ignore.default().add(DownloadManager.EXCLUDE_PATTERNS);
|
|
21283
|
+
}
|
|
21284
|
+
setExcludePatterns(patterns) {
|
|
21285
|
+
this.userExcludePatterns = patterns;
|
|
21286
|
+
this.ig = import_ignore.default().add([...DownloadManager.EXCLUDE_PATTERNS, ...this.userExcludePatterns]);
|
|
21287
|
+
if (patterns.length > 0) {
|
|
21288
|
+
logger.info(`Added ${patterns.length} custom exclude pattern(s)`);
|
|
21289
|
+
patterns.forEach((p) => logger.debug(` - ${p}`));
|
|
21290
|
+
}
|
|
21291
|
+
}
|
|
21216
21292
|
shouldExclude(filePath) {
|
|
21217
|
-
|
|
21218
|
-
return ig.ignores(filePath);
|
|
21293
|
+
return this.ig.ignores(filePath);
|
|
21219
21294
|
}
|
|
21220
21295
|
isPathSafe(basePath, targetPath) {
|
|
21221
21296
|
const resolvedBase = resolve(basePath);
|
|
@@ -21950,6 +22025,9 @@ async function newCommand(options) {
|
|
|
21950
22025
|
logger.info(`Download source: ${downloadInfo.type}`);
|
|
21951
22026
|
logger.debug(`Download URL: ${downloadInfo.url}`);
|
|
21952
22027
|
const downloadManager = new DownloadManager;
|
|
22028
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
22029
|
+
downloadManager.setExcludePatterns(validOptions.exclude);
|
|
22030
|
+
}
|
|
21953
22031
|
const tempDir = await downloadManager.createTempDir();
|
|
21954
22032
|
const { token } = await AuthManager.getToken();
|
|
21955
22033
|
let archivePath;
|
|
@@ -21985,6 +22063,9 @@ async function newCommand(options) {
|
|
|
21985
22063
|
await downloadManager.extractArchive(archivePath, extractDir);
|
|
21986
22064
|
await downloadManager.validateExtraction(extractDir);
|
|
21987
22065
|
const merger = new FileMerger;
|
|
22066
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
22067
|
+
merger.addIgnorePatterns(validOptions.exclude);
|
|
22068
|
+
}
|
|
21988
22069
|
await merger.merge(extractDir, resolvedDir, true);
|
|
21989
22070
|
prompts.outro(`✨ Project created successfully at ${resolvedDir}`);
|
|
21990
22071
|
prompts.note(`cd ${targetDir !== "." ? targetDir : "into the directory"}
|
|
@@ -22107,6 +22188,9 @@ async function updateCommand(options) {
|
|
|
22107
22188
|
logger.info(`Download source: ${downloadInfo.type}`);
|
|
22108
22189
|
logger.debug(`Download URL: ${downloadInfo.url}`);
|
|
22109
22190
|
const downloadManager = new DownloadManager;
|
|
22191
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
22192
|
+
downloadManager.setExcludePatterns(validOptions.exclude);
|
|
22193
|
+
}
|
|
22110
22194
|
const tempDir = await downloadManager.createTempDir();
|
|
22111
22195
|
const { token } = await AuthManager.getToken();
|
|
22112
22196
|
let archivePath;
|
|
@@ -22148,6 +22232,9 @@ async function updateCommand(options) {
|
|
|
22148
22232
|
merger.addIgnorePatterns(customClaudeFiles);
|
|
22149
22233
|
logger.success(`Protected ${customClaudeFiles.length} custom .claude file(s)`);
|
|
22150
22234
|
}
|
|
22235
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
22236
|
+
merger.addIgnorePatterns(validOptions.exclude);
|
|
22237
|
+
}
|
|
22151
22238
|
await merger.merge(extractDir, resolvedDir, false);
|
|
22152
22239
|
prompts.outro(`✨ Project updated successfully at ${resolvedDir}`);
|
|
22153
22240
|
const protectedNote = customClaudeFiles.length > 0 ? `Your project has been updated with the latest version.
|
|
@@ -22354,10 +22441,6 @@ class Logger2 {
|
|
|
22354
22441
|
}
|
|
22355
22442
|
}
|
|
22356
22443
|
var logger2 = new Logger2;
|
|
22357
|
-
// src/version.json
|
|
22358
|
-
var version_default = {
|
|
22359
|
-
version: "1.2.1"
|
|
22360
|
-
};
|
|
22361
22444
|
|
|
22362
22445
|
// src/index.ts
|
|
22363
22446
|
if (process.stdout.setEncoding) {
|
|
@@ -22366,14 +22449,20 @@ if (process.stdout.setEncoding) {
|
|
|
22366
22449
|
if (process.stderr.setEncoding) {
|
|
22367
22450
|
process.stderr.setEncoding("utf8");
|
|
22368
22451
|
}
|
|
22369
|
-
var packageVersion =
|
|
22452
|
+
var packageVersion = package_default.version;
|
|
22370
22453
|
var cli = cac("ck");
|
|
22371
22454
|
cli.option("--verbose, -v", "Enable verbose logging for debugging");
|
|
22372
22455
|
cli.option("--log-file <path>", "Write logs to file");
|
|
22373
|
-
cli.command("new", "Bootstrap a new ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").option("--force", "Overwrite existing files without confirmation").action(async (options) => {
|
|
22456
|
+
cli.command("new", "Bootstrap a new ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").option("--force", "Overwrite existing files without confirmation").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").action(async (options) => {
|
|
22457
|
+
if (options.exclude && !Array.isArray(options.exclude)) {
|
|
22458
|
+
options.exclude = [options.exclude];
|
|
22459
|
+
}
|
|
22374
22460
|
await newCommand(options);
|
|
22375
22461
|
});
|
|
22376
|
-
cli.command("update", "Update existing ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").action(async (options) => {
|
|
22462
|
+
cli.command("update", "Update existing ClaudeKit project").option("--dir <dir>", "Target directory (default: .)").option("--kit <kit>", "Kit to use (engineer, marketing)").option("--version <version>", "Specific version to download (default: latest)").option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)").action(async (options) => {
|
|
22463
|
+
if (options.exclude && !Array.isArray(options.exclude)) {
|
|
22464
|
+
options.exclude = [options.exclude];
|
|
22465
|
+
}
|
|
22377
22466
|
await updateCommand(options);
|
|
22378
22467
|
});
|
|
22379
22468
|
cli.command("versions", "List available versions of ClaudeKit repositories").option("--kit <kit>", "Filter by specific kit (engineer, marketing)").option("--limit <limit>", "Number of releases to show (default: 30)").option("--all", "Show all releases including prereleases").action(async (options) => {
|
package/package.json
CHANGED
package/src/commands/new.ts
CHANGED
|
@@ -110,6 +110,12 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
|
|
|
110
110
|
|
|
111
111
|
// Download asset
|
|
112
112
|
const downloadManager = new DownloadManager();
|
|
113
|
+
|
|
114
|
+
// Apply user exclude patterns if provided
|
|
115
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
116
|
+
downloadManager.setExcludePatterns(validOptions.exclude);
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
const tempDir = await downloadManager.createTempDir();
|
|
114
120
|
|
|
115
121
|
// Get authentication token for API requests
|
|
@@ -157,6 +163,12 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
|
|
|
157
163
|
|
|
158
164
|
// Copy files to target directory
|
|
159
165
|
const merger = new FileMerger();
|
|
166
|
+
|
|
167
|
+
// Apply user exclude patterns if provided
|
|
168
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
169
|
+
merger.addIgnorePatterns(validOptions.exclude);
|
|
170
|
+
}
|
|
171
|
+
|
|
160
172
|
await merger.merge(extractDir, resolvedDir, true); // Skip confirmation for new projects
|
|
161
173
|
|
|
162
174
|
prompts.outro(`✨ Project created successfully at ${resolvedDir}`);
|
package/src/commands/update.ts
CHANGED
|
@@ -83,6 +83,12 @@ export async function updateCommand(options: UpdateCommandOptions): Promise<void
|
|
|
83
83
|
|
|
84
84
|
// Download asset
|
|
85
85
|
const downloadManager = new DownloadManager();
|
|
86
|
+
|
|
87
|
+
// Apply user exclude patterns if provided
|
|
88
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
89
|
+
downloadManager.setExcludePatterns(validOptions.exclude);
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
const tempDir = await downloadManager.createTempDir();
|
|
87
93
|
|
|
88
94
|
// Get authentication token for API requests
|
|
@@ -141,6 +147,11 @@ export async function updateCommand(options: UpdateCommandOptions): Promise<void
|
|
|
141
147
|
logger.success(`Protected ${customClaudeFiles.length} custom .claude file(s)`);
|
|
142
148
|
}
|
|
143
149
|
|
|
150
|
+
// Apply user exclude patterns if provided
|
|
151
|
+
if (validOptions.exclude && validOptions.exclude.length > 0) {
|
|
152
|
+
merger.addIgnorePatterns(validOptions.exclude);
|
|
153
|
+
}
|
|
154
|
+
|
|
144
155
|
await merger.merge(extractDir, resolvedDir, false); // Show confirmation for updates
|
|
145
156
|
|
|
146
157
|
prompts.outro(`✨ Project updated successfully at ${resolvedDir}`);
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { cac } from "cac";
|
|
4
|
+
import packageInfo from "../package.json" assert { type: "json" };
|
|
4
5
|
import { newCommand } from "./commands/new.js";
|
|
5
6
|
import { updateCommand } from "./commands/update.js";
|
|
6
7
|
import { versionCommand } from "./commands/version.js";
|
|
7
8
|
import { logger } from "./utils/logger.js";
|
|
8
|
-
import versionInfo from "./version.json" assert { type: "json" };
|
|
9
9
|
|
|
10
10
|
// Set proper output encoding to prevent unicode rendering issues
|
|
11
11
|
if (process.stdout.setEncoding) {
|
|
@@ -15,7 +15,7 @@ if (process.stderr.setEncoding) {
|
|
|
15
15
|
process.stderr.setEncoding("utf8");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const packageVersion =
|
|
18
|
+
const packageVersion = packageInfo.version;
|
|
19
19
|
|
|
20
20
|
const cli = cac("ck");
|
|
21
21
|
|
|
@@ -30,7 +30,12 @@ cli
|
|
|
30
30
|
.option("--kit <kit>", "Kit to use (engineer, marketing)")
|
|
31
31
|
.option("--version <version>", "Specific version to download (default: latest)")
|
|
32
32
|
.option("--force", "Overwrite existing files without confirmation")
|
|
33
|
+
.option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)")
|
|
33
34
|
.action(async (options) => {
|
|
35
|
+
// Normalize exclude to always be an array (CAC may pass string for single value)
|
|
36
|
+
if (options.exclude && !Array.isArray(options.exclude)) {
|
|
37
|
+
options.exclude = [options.exclude];
|
|
38
|
+
}
|
|
34
39
|
await newCommand(options);
|
|
35
40
|
});
|
|
36
41
|
|
|
@@ -40,7 +45,12 @@ cli
|
|
|
40
45
|
.option("--dir <dir>", "Target directory (default: .)")
|
|
41
46
|
.option("--kit <kit>", "Kit to use (engineer, marketing)")
|
|
42
47
|
.option("--version <version>", "Specific version to download (default: latest)")
|
|
48
|
+
.option("--exclude <pattern>", "Exclude files matching glob pattern (can be used multiple times)")
|
|
43
49
|
.action(async (options) => {
|
|
50
|
+
// Normalize exclude to always be an array (CAC may pass string for single value)
|
|
51
|
+
if (options.exclude && !Array.isArray(options.exclude)) {
|
|
52
|
+
options.exclude = [options.exclude];
|
|
53
|
+
}
|
|
44
54
|
await updateCommand(options);
|
|
45
55
|
});
|
|
46
56
|
|
package/src/lib/download.ts
CHANGED
|
@@ -41,12 +41,45 @@ export class DownloadManager {
|
|
|
41
41
|
*/
|
|
42
42
|
private totalExtractedSize = 0;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Instance-level ignore object with combined default and user patterns
|
|
46
|
+
*/
|
|
47
|
+
private ig: ReturnType<typeof ignore>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Store user-defined exclude patterns
|
|
51
|
+
*/
|
|
52
|
+
private userExcludePatterns: string[] = [];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initialize DownloadManager with default exclude patterns
|
|
56
|
+
*/
|
|
57
|
+
constructor() {
|
|
58
|
+
// Initialize ignore with default patterns
|
|
59
|
+
this.ig = ignore().add(DownloadManager.EXCLUDE_PATTERNS);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Set additional user-defined exclude patterns
|
|
64
|
+
* These are added to (not replace) the default EXCLUDE_PATTERNS
|
|
65
|
+
*/
|
|
66
|
+
setExcludePatterns(patterns: string[]): void {
|
|
67
|
+
this.userExcludePatterns = patterns;
|
|
68
|
+
// Reinitialize ignore with both default and user patterns
|
|
69
|
+
this.ig = ignore().add([...DownloadManager.EXCLUDE_PATTERNS, ...this.userExcludePatterns]);
|
|
70
|
+
|
|
71
|
+
if (patterns.length > 0) {
|
|
72
|
+
logger.info(`Added ${patterns.length} custom exclude pattern(s)`);
|
|
73
|
+
patterns.forEach((p) => logger.debug(` - ${p}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
/**
|
|
45
78
|
* Check if file path should be excluded
|
|
79
|
+
* Uses instance-level ignore with both default and user patterns
|
|
46
80
|
*/
|
|
47
81
|
private shouldExclude(filePath: string): boolean {
|
|
48
|
-
|
|
49
|
-
return ig.ignores(filePath);
|
|
82
|
+
return this.ig.ignores(filePath);
|
|
50
83
|
}
|
|
51
84
|
|
|
52
85
|
/**
|
package/src/types.ts
CHANGED
|
@@ -4,12 +4,22 @@ import { z } from "zod";
|
|
|
4
4
|
export const KitType = z.enum(["engineer", "marketing"]);
|
|
5
5
|
export type KitType = z.infer<typeof KitType>;
|
|
6
6
|
|
|
7
|
+
// Exclude pattern validation schema
|
|
8
|
+
export const ExcludePatternSchema = z
|
|
9
|
+
.string()
|
|
10
|
+
.trim()
|
|
11
|
+
.min(1, "Exclude pattern cannot be empty")
|
|
12
|
+
.max(500, "Exclude pattern too long")
|
|
13
|
+
.refine((val) => !val.startsWith("/"), "Absolute paths not allowed in exclude patterns")
|
|
14
|
+
.refine((val) => !val.includes(".."), "Path traversal not allowed in exclude patterns");
|
|
15
|
+
|
|
7
16
|
// Command options schemas
|
|
8
17
|
export const NewCommandOptionsSchema = z.object({
|
|
9
18
|
dir: z.string().default("."),
|
|
10
19
|
kit: KitType.optional(),
|
|
11
20
|
version: z.string().optional(),
|
|
12
21
|
force: z.boolean().default(false),
|
|
22
|
+
exclude: z.array(ExcludePatternSchema).optional().default([]),
|
|
13
23
|
});
|
|
14
24
|
export type NewCommandOptions = z.infer<typeof NewCommandOptionsSchema>;
|
|
15
25
|
|
|
@@ -17,6 +27,7 @@ export const UpdateCommandOptionsSchema = z.object({
|
|
|
17
27
|
dir: z.string().default("."),
|
|
18
28
|
kit: KitType.optional(),
|
|
19
29
|
version: z.string().optional(),
|
|
30
|
+
exclude: z.array(ExcludePatternSchema).optional().default([]),
|
|
20
31
|
});
|
|
21
32
|
export type UpdateCommandOptions = z.infer<typeof UpdateCommandOptionsSchema>;
|
|
22
33
|
|
package/tests/types.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ClaudeKitError,
|
|
6
6
|
ConfigSchema,
|
|
7
7
|
DownloadError,
|
|
8
|
+
ExcludePatternSchema,
|
|
8
9
|
ExtractionError,
|
|
9
10
|
GitHubError,
|
|
10
11
|
GitHubReleaseAssetSchema,
|
|
@@ -29,6 +30,44 @@ describe("Types and Schemas", () => {
|
|
|
29
30
|
});
|
|
30
31
|
});
|
|
31
32
|
|
|
33
|
+
describe("ExcludePatternSchema", () => {
|
|
34
|
+
test("should accept valid glob patterns", () => {
|
|
35
|
+
const validPatterns = ["*.log", "**/*.tmp", "temp/**", "logs/*.txt", "cache/**/*"];
|
|
36
|
+
validPatterns.forEach((pattern) => {
|
|
37
|
+
expect(() => ExcludePatternSchema.parse(pattern)).not.toThrow();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("should reject absolute paths", () => {
|
|
42
|
+
expect(() => ExcludePatternSchema.parse("/etc/passwd")).toThrow("Absolute paths not allowed");
|
|
43
|
+
expect(() => ExcludePatternSchema.parse("/var/log/**")).toThrow("Absolute paths not allowed");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("should reject path traversal", () => {
|
|
47
|
+
expect(() => ExcludePatternSchema.parse("../../etc/passwd")).toThrow(
|
|
48
|
+
"Path traversal not allowed",
|
|
49
|
+
);
|
|
50
|
+
expect(() => ExcludePatternSchema.parse("../../../secret")).toThrow(
|
|
51
|
+
"Path traversal not allowed",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("should reject empty patterns", () => {
|
|
56
|
+
expect(() => ExcludePatternSchema.parse("")).toThrow("Exclude pattern cannot be empty");
|
|
57
|
+
expect(() => ExcludePatternSchema.parse(" ")).toThrow("Exclude pattern cannot be empty");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("should reject overly long patterns", () => {
|
|
61
|
+
const longPattern = "a".repeat(501);
|
|
62
|
+
expect(() => ExcludePatternSchema.parse(longPattern)).toThrow("Exclude pattern too long");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should trim whitespace", () => {
|
|
66
|
+
const result = ExcludePatternSchema.parse(" *.log ");
|
|
67
|
+
expect(result).toBe("*.log");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
32
71
|
describe("NewCommandOptionsSchema", () => {
|
|
33
72
|
test("should validate correct options", () => {
|
|
34
73
|
const result = NewCommandOptionsSchema.parse({
|
|
@@ -46,6 +85,7 @@ describe("Types and Schemas", () => {
|
|
|
46
85
|
expect(result.dir).toBe(".");
|
|
47
86
|
expect(result.kit).toBeUndefined();
|
|
48
87
|
expect(result.version).toBeUndefined();
|
|
88
|
+
expect(result.exclude).toEqual([]);
|
|
49
89
|
});
|
|
50
90
|
|
|
51
91
|
test("should accept optional fields", () => {
|
|
@@ -53,6 +93,23 @@ describe("Types and Schemas", () => {
|
|
|
53
93
|
expect(result.dir).toBe("./custom");
|
|
54
94
|
expect(result.kit).toBeUndefined();
|
|
55
95
|
});
|
|
96
|
+
|
|
97
|
+
test("should validate exclude patterns", () => {
|
|
98
|
+
const result = NewCommandOptionsSchema.parse({
|
|
99
|
+
dir: "./test",
|
|
100
|
+
exclude: ["*.log", "temp/**"],
|
|
101
|
+
});
|
|
102
|
+
expect(result.exclude).toEqual(["*.log", "temp/**"]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("should reject invalid exclude patterns", () => {
|
|
106
|
+
expect(() =>
|
|
107
|
+
NewCommandOptionsSchema.parse({
|
|
108
|
+
dir: "./test",
|
|
109
|
+
exclude: ["/etc/passwd"],
|
|
110
|
+
}),
|
|
111
|
+
).toThrow();
|
|
112
|
+
});
|
|
56
113
|
});
|
|
57
114
|
|
|
58
115
|
describe("UpdateCommandOptionsSchema", () => {
|
|
@@ -70,6 +127,24 @@ describe("Types and Schemas", () => {
|
|
|
70
127
|
test("should use default values", () => {
|
|
71
128
|
const result = UpdateCommandOptionsSchema.parse({});
|
|
72
129
|
expect(result.dir).toBe(".");
|
|
130
|
+
expect(result.exclude).toEqual([]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("should validate exclude patterns", () => {
|
|
134
|
+
const result = UpdateCommandOptionsSchema.parse({
|
|
135
|
+
dir: "./test",
|
|
136
|
+
exclude: ["*.log", "**/*.tmp"],
|
|
137
|
+
});
|
|
138
|
+
expect(result.exclude).toEqual(["*.log", "**/*.tmp"]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("should reject invalid exclude patterns", () => {
|
|
142
|
+
expect(() =>
|
|
143
|
+
UpdateCommandOptionsSchema.parse({
|
|
144
|
+
dir: "./test",
|
|
145
|
+
exclude: ["../../../etc"],
|
|
146
|
+
}),
|
|
147
|
+
).toThrow();
|
|
73
148
|
});
|
|
74
149
|
});
|
|
75
150
|
|