claudekit-cli 1.4.1 → 1.5.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 (47) hide show
  1. package/bin/ck-darwin-arm64 +0 -0
  2. package/bin/ck-darwin-x64 +0 -0
  3. package/bin/ck-linux-x64 +0 -0
  4. package/bin/ck-win32-x64.exe +0 -0
  5. package/bin/ck.js +62 -0
  6. package/package.json +6 -2
  7. package/.github/workflows/ci.yml +0 -45
  8. package/.github/workflows/claude-code-review.yml +0 -57
  9. package/.github/workflows/claude.yml +0 -50
  10. package/.github/workflows/release.yml +0 -102
  11. package/.releaserc.json +0 -17
  12. package/.repomixignore +0 -15
  13. package/AGENTS.md +0 -217
  14. package/CHANGELOG.md +0 -95
  15. package/CLAUDE.md +0 -34
  16. package/biome.json +0 -28
  17. package/bun.lock +0 -863
  18. package/dist/index.js +0 -22511
  19. package/src/commands/new.ts +0 -185
  20. package/src/commands/update.ts +0 -174
  21. package/src/commands/version.ts +0 -135
  22. package/src/index.ts +0 -102
  23. package/src/lib/auth.ts +0 -157
  24. package/src/lib/download.ts +0 -689
  25. package/src/lib/github.ts +0 -230
  26. package/src/lib/merge.ts +0 -119
  27. package/src/lib/prompts.ts +0 -114
  28. package/src/types.ts +0 -178
  29. package/src/utils/config.ts +0 -87
  30. package/src/utils/file-scanner.ts +0 -134
  31. package/src/utils/logger.ts +0 -124
  32. package/src/utils/safe-prompts.ts +0 -44
  33. package/src/utils/safe-spinner.ts +0 -38
  34. package/src/version.json +0 -3
  35. package/tests/commands/version.test.ts +0 -297
  36. package/tests/integration/cli.test.ts +0 -252
  37. package/tests/lib/auth.test.ts +0 -116
  38. package/tests/lib/download.test.ts +0 -292
  39. package/tests/lib/github-download-priority.test.ts +0 -432
  40. package/tests/lib/github.test.ts +0 -52
  41. package/tests/lib/merge.test.ts +0 -267
  42. package/tests/lib/prompts.test.ts +0 -66
  43. package/tests/types.test.ts +0 -337
  44. package/tests/utils/config.test.ts +0 -263
  45. package/tests/utils/file-scanner.test.ts +0 -202
  46. package/tests/utils/logger.test.ts +0 -239
  47. package/tsconfig.json +0 -30
package/src/lib/github.ts DELETED
@@ -1,230 +0,0 @@
1
- import { Octokit } from "@octokit/rest";
2
- import { GitHubError, type GitHubRelease, GitHubReleaseSchema, type KitConfig } from "../types.js";
3
- import { logger } from "../utils/logger.js";
4
- import { AuthManager } from "./auth.js";
5
-
6
- export class GitHubClient {
7
- private octokit: Octokit | null = null;
8
-
9
- /**
10
- * Initialize Octokit client with authentication
11
- */
12
- private async getClient(): Promise<Octokit> {
13
- if (this.octokit) {
14
- return this.octokit;
15
- }
16
-
17
- const { token } = await AuthManager.getToken();
18
-
19
- this.octokit = new Octokit({
20
- auth: token,
21
- userAgent: "claudekit-cli",
22
- request: {
23
- timeout: 30000, // 30 seconds
24
- },
25
- });
26
-
27
- return this.octokit;
28
- }
29
-
30
- /**
31
- * Get latest release for a kit
32
- */
33
- async getLatestRelease(kit: KitConfig): Promise<GitHubRelease> {
34
- try {
35
- const client = await this.getClient();
36
-
37
- logger.debug(`Fetching latest release for ${kit.owner}/${kit.repo}`);
38
-
39
- const { data } = await client.repos.getLatestRelease({
40
- owner: kit.owner,
41
- repo: kit.repo,
42
- });
43
-
44
- return GitHubReleaseSchema.parse(data);
45
- } catch (error: any) {
46
- if (error?.status === 404) {
47
- throw new GitHubError(`No releases found for ${kit.name}`, 404);
48
- }
49
- if (error?.status === 401) {
50
- throw new GitHubError("Authentication failed. Please check your GitHub token.", 401);
51
- }
52
- if (error?.status === 403) {
53
- throw new GitHubError(
54
- "Access denied. Make sure your token has access to private repositories.",
55
- 403,
56
- );
57
- }
58
- throw new GitHubError(
59
- `Failed to fetch release: ${error?.message || "Unknown error"}`,
60
- error?.status,
61
- );
62
- }
63
- }
64
-
65
- /**
66
- * Get specific release by version tag
67
- */
68
- async getReleaseByTag(kit: KitConfig, tag: string): Promise<GitHubRelease> {
69
- try {
70
- const client = await this.getClient();
71
-
72
- logger.debug(`Fetching release ${tag} for ${kit.owner}/${kit.repo}`);
73
-
74
- const { data } = await client.repos.getReleaseByTag({
75
- owner: kit.owner,
76
- repo: kit.repo,
77
- tag,
78
- });
79
-
80
- return GitHubReleaseSchema.parse(data);
81
- } catch (error: any) {
82
- if (error?.status === 404) {
83
- throw new GitHubError(`Release ${tag} not found for ${kit.name}`, 404);
84
- }
85
- if (error?.status === 401) {
86
- throw new GitHubError("Authentication failed. Please check your GitHub token.", 401);
87
- }
88
- if (error?.status === 403) {
89
- throw new GitHubError(
90
- "Access denied. Make sure your token has access to private repositories.",
91
- 403,
92
- );
93
- }
94
- throw new GitHubError(
95
- `Failed to fetch release: ${error?.message || "Unknown error"}`,
96
- error?.status,
97
- );
98
- }
99
- }
100
-
101
- /**
102
- * List all releases for a kit
103
- */
104
- async listReleases(kit: KitConfig, limit = 10): Promise<GitHubRelease[]> {
105
- try {
106
- const client = await this.getClient();
107
-
108
- logger.debug(`Listing releases for ${kit.owner}/${kit.repo}`);
109
-
110
- const { data } = await client.repos.listReleases({
111
- owner: kit.owner,
112
- repo: kit.repo,
113
- per_page: limit,
114
- });
115
-
116
- return data.map((release) => GitHubReleaseSchema.parse(release));
117
- } catch (error: any) {
118
- if (error?.status === 401) {
119
- throw new GitHubError("Authentication failed. Please check your GitHub token.", 401);
120
- }
121
- if (error?.status === 403) {
122
- throw new GitHubError(
123
- "Access denied. Make sure your token has access to private repositories.",
124
- 403,
125
- );
126
- }
127
- throw new GitHubError(
128
- `Failed to list releases: ${error?.message || "Unknown error"}`,
129
- error?.status,
130
- );
131
- }
132
- }
133
-
134
- /**
135
- * Check if user has access to repository
136
- */
137
- async checkAccess(kit: KitConfig): Promise<boolean> {
138
- try {
139
- const client = await this.getClient();
140
-
141
- await client.repos.get({
142
- owner: kit.owner,
143
- repo: kit.repo,
144
- });
145
-
146
- return true;
147
- } catch (error: any) {
148
- if (error?.status === 404 || error?.status === 403) {
149
- return false;
150
- }
151
- throw new GitHubError(
152
- `Failed to check repository access: ${error?.message || "Unknown error"}`,
153
- error?.status,
154
- );
155
- }
156
- }
157
-
158
- /**
159
- * Get downloadable asset or source code URL from release
160
- * Priority:
161
- * 1. "ClaudeKit Engineer Package" or "ClaudeKit Marketing Package" zip file
162
- * 2. Other custom uploaded assets (.tar.gz, .tgz, .zip) excluding "Source code" archives
163
- * 3. GitHub's automatic tarball URL
164
- */
165
- static getDownloadableAsset(release: GitHubRelease): {
166
- type: "asset" | "tarball" | "zipball";
167
- url: string;
168
- name: string;
169
- size?: number;
170
- } {
171
- // Log all available assets for debugging
172
- logger.debug(`Available assets for ${release.tag_name}:`);
173
- if (release.assets.length === 0) {
174
- logger.debug(" No custom assets found");
175
- } else {
176
- release.assets.forEach((asset, index) => {
177
- logger.debug(` ${index + 1}. ${asset.name} (${(asset.size / 1024 / 1024).toFixed(2)} MB)`);
178
- });
179
- }
180
-
181
- // First priority: Look for official ClaudeKit package assets
182
- const packageAsset = release.assets.find((a) => {
183
- const nameLower = a.name.toLowerCase();
184
- return (
185
- nameLower.includes("claudekit") &&
186
- nameLower.includes("package") &&
187
- nameLower.endsWith(".zip")
188
- );
189
- });
190
-
191
- if (packageAsset) {
192
- logger.debug(`✓ Selected ClaudeKit package asset: ${packageAsset.name}`);
193
- return {
194
- type: "asset",
195
- url: packageAsset.url, // Use API endpoint for authenticated downloads
196
- name: packageAsset.name,
197
- size: packageAsset.size,
198
- };
199
- }
200
-
201
- logger.debug("⚠ No ClaudeKit package asset found, checking for other custom assets...");
202
-
203
- // Second priority: Look for any custom uploaded assets (excluding GitHub's automatic source code archives)
204
- const customAsset = release.assets.find(
205
- (a) =>
206
- (a.name.endsWith(".tar.gz") || a.name.endsWith(".tgz") || a.name.endsWith(".zip")) &&
207
- !a.name.toLowerCase().startsWith("source") &&
208
- !a.name.toLowerCase().includes("source code"),
209
- );
210
-
211
- if (customAsset) {
212
- logger.debug(`✓ Selected custom asset: ${customAsset.name}`);
213
- return {
214
- type: "asset",
215
- url: customAsset.url, // Use API endpoint for authenticated downloads
216
- name: customAsset.name,
217
- size: customAsset.size,
218
- };
219
- }
220
-
221
- // Fall back to GitHub's automatic tarball
222
- logger.debug("⚠ No custom assets found, falling back to GitHub automatic tarball");
223
- return {
224
- type: "tarball",
225
- url: release.tarball_url,
226
- name: `${release.tag_name}.tar.gz`,
227
- size: undefined, // Size unknown for automatic tarballs
228
- };
229
- }
230
- }
package/src/lib/merge.ts DELETED
@@ -1,119 +0,0 @@
1
- import { join, relative } from "node:path";
2
- import * as clack from "@clack/prompts";
3
- import { copy, pathExists, readdir, stat } from "fs-extra";
4
- import ignore from "ignore";
5
- import { PROTECTED_PATTERNS } from "../types.js";
6
- import { logger } from "../utils/logger.js";
7
-
8
- export class FileMerger {
9
- private ig = ignore().add(PROTECTED_PATTERNS);
10
-
11
- /**
12
- * Merge files from source to destination with conflict detection
13
- */
14
- async merge(sourceDir: string, destDir: string, skipConfirmation = false): Promise<void> {
15
- // Get list of files that will be affected
16
- const conflicts = await this.detectConflicts(sourceDir, destDir);
17
-
18
- if (conflicts.length > 0 && !skipConfirmation) {
19
- logger.warning(`Found ${conflicts.length} file(s) that will be overwritten:`);
20
- conflicts.slice(0, 10).forEach((file) => logger.info(` - ${file}`));
21
- if (conflicts.length > 10) {
22
- logger.info(` ... and ${conflicts.length - 10} more`);
23
- }
24
-
25
- const confirm = await clack.confirm({
26
- message: "Do you want to continue?",
27
- });
28
-
29
- if (clack.isCancel(confirm) || !confirm) {
30
- throw new Error("Merge cancelled by user");
31
- }
32
- }
33
-
34
- // Copy files
35
- await this.copyFiles(sourceDir, destDir);
36
- }
37
-
38
- /**
39
- * Detect files that will be overwritten
40
- * Protected files that exist in destination are not considered conflicts (they won't be overwritten)
41
- */
42
- private async detectConflicts(sourceDir: string, destDir: string): Promise<string[]> {
43
- const conflicts: string[] = [];
44
- const files = await this.getFiles(sourceDir);
45
-
46
- for (const file of files) {
47
- const relativePath = relative(sourceDir, file);
48
- const destPath = join(destDir, relativePath);
49
-
50
- // Check if file exists in destination
51
- if (await pathExists(destPath)) {
52
- // Protected files won't be overwritten, so they're not conflicts
53
- if (this.ig.ignores(relativePath)) {
54
- logger.debug(`Protected file exists but won't be overwritten: ${relativePath}`);
55
- continue;
56
- }
57
- conflicts.push(relativePath);
58
- }
59
- }
60
-
61
- return conflicts;
62
- }
63
-
64
- /**
65
- * Copy files from source to destination, skipping protected patterns
66
- */
67
- private async copyFiles(sourceDir: string, destDir: string): Promise<void> {
68
- const files = await this.getFiles(sourceDir);
69
- let copiedCount = 0;
70
- let skippedCount = 0;
71
-
72
- for (const file of files) {
73
- const relativePath = relative(sourceDir, file);
74
- const destPath = join(destDir, relativePath);
75
-
76
- // Skip protected files ONLY if they already exist in destination
77
- // This allows new protected files to be added, but prevents overwriting existing ones
78
- if (this.ig.ignores(relativePath) && (await pathExists(destPath))) {
79
- logger.debug(`Skipping protected file (exists in destination): ${relativePath}`);
80
- skippedCount++;
81
- continue;
82
- }
83
-
84
- await copy(file, destPath, { overwrite: true });
85
- copiedCount++;
86
- }
87
-
88
- logger.success(`Copied ${copiedCount} file(s), skipped ${skippedCount} protected file(s)`);
89
- }
90
-
91
- /**
92
- * Recursively get all files in a directory
93
- */
94
- private async getFiles(dir: string): Promise<string[]> {
95
- const files: string[] = [];
96
- const entries = await readdir(dir, { encoding: "utf8" });
97
-
98
- for (const entry of entries) {
99
- const fullPath = join(dir, entry);
100
- const stats = await stat(fullPath);
101
-
102
- if (stats.isDirectory()) {
103
- const subFiles = await this.getFiles(fullPath);
104
- files.push(...subFiles);
105
- } else {
106
- files.push(fullPath);
107
- }
108
- }
109
-
110
- return files;
111
- }
112
-
113
- /**
114
- * Add custom patterns to ignore
115
- */
116
- addIgnorePatterns(patterns: string[]): void {
117
- this.ig.add(patterns);
118
- }
119
- }
@@ -1,114 +0,0 @@
1
- import * as clack from "@clack/prompts";
2
- import { AVAILABLE_KITS, type KitType } from "../types.js";
3
- import { intro, note, outro } from "../utils/safe-prompts.js";
4
-
5
- export class PromptsManager {
6
- /**
7
- * Prompt user to select a kit
8
- */
9
- async selectKit(defaultKit?: KitType): Promise<KitType> {
10
- const kit = await clack.select({
11
- message: "Select a ClaudeKit:",
12
- options: Object.entries(AVAILABLE_KITS).map(([key, config]) => ({
13
- value: key as KitType,
14
- label: config.name,
15
- hint: config.description,
16
- })),
17
- initialValue: defaultKit,
18
- });
19
-
20
- if (clack.isCancel(kit)) {
21
- throw new Error("Kit selection cancelled");
22
- }
23
-
24
- return kit as KitType;
25
- }
26
-
27
- /**
28
- * Prompt user to select a version
29
- */
30
- async selectVersion(versions: string[], defaultVersion?: string): Promise<string> {
31
- if (versions.length === 0) {
32
- throw new Error("No versions available");
33
- }
34
-
35
- // If only one version or default is latest, return first version
36
- if (versions.length === 1 || !defaultVersion) {
37
- return versions[0];
38
- }
39
-
40
- const version = await clack.select({
41
- message: "Select a version:",
42
- options: versions.map((v) => ({
43
- value: v,
44
- label: v,
45
- })),
46
- initialValue: defaultVersion,
47
- });
48
-
49
- if (clack.isCancel(version)) {
50
- throw new Error("Version selection cancelled");
51
- }
52
-
53
- return version as string;
54
- }
55
-
56
- /**
57
- * Prompt user for target directory
58
- */
59
- async getDirectory(defaultDir = "."): Promise<string> {
60
- const dir = await clack.text({
61
- message: "Enter target directory:",
62
- placeholder: defaultDir,
63
- defaultValue: defaultDir,
64
- validate: (value) => {
65
- if (!value || value.trim().length === 0) {
66
- return "Directory path is required";
67
- }
68
- return;
69
- },
70
- });
71
-
72
- if (clack.isCancel(dir)) {
73
- throw new Error("Directory input cancelled");
74
- }
75
-
76
- return dir.trim();
77
- }
78
-
79
- /**
80
- * Confirm action
81
- */
82
- async confirm(message: string): Promise<boolean> {
83
- const result = await clack.confirm({
84
- message,
85
- });
86
-
87
- if (clack.isCancel(result)) {
88
- return false;
89
- }
90
-
91
- return result;
92
- }
93
-
94
- /**
95
- * Show intro message
96
- */
97
- intro(message: string): void {
98
- intro(message);
99
- }
100
-
101
- /**
102
- * Show outro message
103
- */
104
- outro(message: string): void {
105
- outro(message);
106
- }
107
-
108
- /**
109
- * Show note
110
- */
111
- note(message: string, title?: string): void {
112
- note(message, title);
113
- }
114
- }
package/src/types.ts DELETED
@@ -1,178 +0,0 @@
1
- import { z } from "zod";
2
-
3
- // Kit types
4
- export const KitType = z.enum(["engineer", "marketing"]);
5
- export type KitType = z.infer<typeof KitType>;
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
-
16
- // Command options schemas
17
- export const NewCommandOptionsSchema = z.object({
18
- dir: z.string().default("."),
19
- kit: KitType.optional(),
20
- version: z.string().optional(),
21
- force: z.boolean().default(false),
22
- exclude: z.array(ExcludePatternSchema).optional().default([]),
23
- });
24
- export type NewCommandOptions = z.infer<typeof NewCommandOptionsSchema>;
25
-
26
- export const UpdateCommandOptionsSchema = z.object({
27
- dir: z.string().default("."),
28
- kit: KitType.optional(),
29
- version: z.string().optional(),
30
- exclude: z.array(ExcludePatternSchema).optional().default([]),
31
- });
32
- export type UpdateCommandOptions = z.infer<typeof UpdateCommandOptionsSchema>;
33
-
34
- export const VersionCommandOptionsSchema = z.object({
35
- kit: KitType.optional(),
36
- limit: z.number().optional(),
37
- all: z.boolean().optional(),
38
- });
39
- export type VersionCommandOptions = z.infer<typeof VersionCommandOptionsSchema>;
40
-
41
- // Config schemas
42
- export const ConfigSchema = z.object({
43
- github: z
44
- .object({
45
- token: z.string().optional(),
46
- })
47
- .optional(),
48
- defaults: z
49
- .object({
50
- kit: KitType.optional(),
51
- dir: z.string().optional(),
52
- })
53
- .optional(),
54
- });
55
- export type Config = z.infer<typeof ConfigSchema>;
56
-
57
- // GitHub schemas
58
- export const GitHubReleaseAssetSchema = z.object({
59
- id: z.number(),
60
- name: z.string(),
61
- url: z.string().url(), // API endpoint for authenticated downloads
62
- browser_download_url: z.string().url(), // Direct download URL (public only)
63
- size: z.number(),
64
- content_type: z.string(),
65
- });
66
- export type GitHubReleaseAsset = z.infer<typeof GitHubReleaseAssetSchema>;
67
-
68
- export const GitHubReleaseSchema = z.object({
69
- id: z.number(),
70
- tag_name: z.string(),
71
- name: z.string(),
72
- draft: z.boolean(),
73
- prerelease: z.boolean(),
74
- assets: z.array(GitHubReleaseAssetSchema),
75
- published_at: z.string().optional(),
76
- tarball_url: z.string().url(),
77
- zipball_url: z.string().url(),
78
- });
79
- export type GitHubRelease = z.infer<typeof GitHubReleaseSchema>;
80
-
81
- // Kit configuration
82
- export const KitConfigSchema = z.object({
83
- name: z.string(),
84
- repo: z.string(),
85
- owner: z.string(),
86
- description: z.string(),
87
- });
88
- export type KitConfig = z.infer<typeof KitConfigSchema>;
89
-
90
- // Available kits
91
- export const AVAILABLE_KITS: Record<KitType, KitConfig> = {
92
- engineer: {
93
- name: "ClaudeKit Engineer",
94
- repo: "claudekit-engineer",
95
- owner: "claudekit",
96
- description: "Engineering toolkit for building with Claude",
97
- },
98
- marketing: {
99
- name: "ClaudeKit Marketing",
100
- repo: "claudekit-marketing",
101
- owner: "claudekit",
102
- description: "[Coming Soon] Marketing toolkit",
103
- },
104
- };
105
-
106
- // Protected file patterns (files to skip during update)
107
- export const PROTECTED_PATTERNS = [
108
- // Environment and secrets
109
- ".env",
110
- ".env.local",
111
- ".env.*.local",
112
- "*.key",
113
- "*.pem",
114
- "*.p12",
115
- // User configuration files (only skip if they exist)
116
- ".gitignore",
117
- ".repomixignore",
118
- ".mcp.json",
119
- "CLAUDE.md",
120
- // Dependencies and build artifacts
121
- "node_modules/**",
122
- ".git/**",
123
- "dist/**",
124
- "build/**",
125
- ];
126
-
127
- // Archive types
128
- export type ArchiveType = "tar.gz" | "zip";
129
-
130
- // Download progress
131
- export interface DownloadProgress {
132
- total: number;
133
- current: number;
134
- percentage: number;
135
- }
136
-
137
- // Authentication method
138
- export type AuthMethod = "gh-cli" | "env-var" | "keychain" | "prompt";
139
-
140
- // Error types
141
- export class ClaudeKitError extends Error {
142
- constructor(
143
- message: string,
144
- public code?: string,
145
- public statusCode?: number,
146
- ) {
147
- super(message);
148
- this.name = "ClaudeKitError";
149
- }
150
- }
151
-
152
- export class AuthenticationError extends ClaudeKitError {
153
- constructor(message: string) {
154
- super(message, "AUTH_ERROR", 401);
155
- this.name = "AuthenticationError";
156
- }
157
- }
158
-
159
- export class GitHubError extends ClaudeKitError {
160
- constructor(message: string, statusCode?: number) {
161
- super(message, "GITHUB_ERROR", statusCode);
162
- this.name = "GitHubError";
163
- }
164
- }
165
-
166
- export class DownloadError extends ClaudeKitError {
167
- constructor(message: string) {
168
- super(message, "DOWNLOAD_ERROR");
169
- this.name = "DownloadError";
170
- }
171
- }
172
-
173
- export class ExtractionError extends ClaudeKitError {
174
- constructor(message: string) {
175
- super(message, "EXTRACTION_ERROR");
176
- this.name = "ExtractionError";
177
- }
178
- }