claudekit-cli 1.4.0 → 1.5.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/bin/ck-darwin-arm64 +0 -0
- package/bin/ck-darwin-x64 +0 -0
- package/bin/ck-linux-x64 +0 -0
- package/bin/ck-win32-x64.exe +0 -0
- package/package.json +8 -2
- package/scripts/postinstall.js +74 -0
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/claude-code-review.yml +0 -57
- package/.github/workflows/claude.yml +0 -50
- package/.github/workflows/release.yml +0 -102
- package/.releaserc.json +0 -17
- package/.repomixignore +0 -15
- package/AGENTS.md +0 -217
- package/CHANGELOG.md +0 -88
- package/CLAUDE.md +0 -34
- package/biome.json +0 -28
- package/bun.lock +0 -863
- package/dist/index.js +0 -22489
- package/src/commands/new.ts +0 -185
- package/src/commands/update.ts +0 -174
- package/src/commands/version.ts +0 -135
- package/src/index.ts +0 -102
- package/src/lib/auth.ts +0 -157
- package/src/lib/download.ts +0 -654
- package/src/lib/github.ts +0 -230
- package/src/lib/merge.ts +0 -116
- package/src/lib/prompts.ts +0 -114
- package/src/types.ts +0 -171
- package/src/utils/config.ts +0 -87
- package/src/utils/file-scanner.ts +0 -134
- package/src/utils/logger.ts +0 -124
- package/src/utils/safe-prompts.ts +0 -44
- package/src/utils/safe-spinner.ts +0 -38
- package/src/version.json +0 -3
- package/test-integration/demo/.mcp.json +0 -13
- package/test-integration/demo/.repomixignore +0 -15
- package/test-integration/demo/CLAUDE.md +0 -34
- package/tests/commands/version.test.ts +0 -297
- package/tests/integration/cli.test.ts +0 -252
- package/tests/lib/auth.test.ts +0 -116
- package/tests/lib/download.test.ts +0 -292
- package/tests/lib/github-download-priority.test.ts +0 -432
- package/tests/lib/github.test.ts +0 -52
- package/tests/lib/merge.test.ts +0 -215
- package/tests/lib/prompts.test.ts +0 -66
- package/tests/types.test.ts +0 -337
- package/tests/utils/config.test.ts +0 -263
- package/tests/utils/file-scanner.test.ts +0 -202
- package/tests/utils/logger.test.ts +0 -239
- 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,116 +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
|
-
*/
|
|
41
|
-
private async detectConflicts(sourceDir: string, destDir: string): Promise<string[]> {
|
|
42
|
-
const conflicts: string[] = [];
|
|
43
|
-
const files = await this.getFiles(sourceDir);
|
|
44
|
-
|
|
45
|
-
for (const file of files) {
|
|
46
|
-
const relativePath = relative(sourceDir, file);
|
|
47
|
-
|
|
48
|
-
// Skip protected files
|
|
49
|
-
if (this.ig.ignores(relativePath)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const destPath = join(destDir, relativePath);
|
|
54
|
-
if (await pathExists(destPath)) {
|
|
55
|
-
conflicts.push(relativePath);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return conflicts;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Copy files from source to destination, skipping protected patterns
|
|
64
|
-
*/
|
|
65
|
-
private async copyFiles(sourceDir: string, destDir: string): Promise<void> {
|
|
66
|
-
const files = await this.getFiles(sourceDir);
|
|
67
|
-
let copiedCount = 0;
|
|
68
|
-
let skippedCount = 0;
|
|
69
|
-
|
|
70
|
-
for (const file of files) {
|
|
71
|
-
const relativePath = relative(sourceDir, file);
|
|
72
|
-
|
|
73
|
-
// Skip protected files
|
|
74
|
-
if (this.ig.ignores(relativePath)) {
|
|
75
|
-
logger.debug(`Skipping protected file: ${relativePath}`);
|
|
76
|
-
skippedCount++;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const destPath = join(destDir, relativePath);
|
|
81
|
-
await copy(file, destPath, { overwrite: true });
|
|
82
|
-
copiedCount++;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
logger.success(`Copied ${copiedCount} file(s), skipped ${skippedCount} protected file(s)`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Recursively get all files in a directory
|
|
90
|
-
*/
|
|
91
|
-
private async getFiles(dir: string): Promise<string[]> {
|
|
92
|
-
const files: string[] = [];
|
|
93
|
-
const entries = await readdir(dir);
|
|
94
|
-
|
|
95
|
-
for (const entry of entries) {
|
|
96
|
-
const fullPath = join(dir, entry);
|
|
97
|
-
const stats = await stat(fullPath);
|
|
98
|
-
|
|
99
|
-
if (stats.isDirectory()) {
|
|
100
|
-
const subFiles = await this.getFiles(fullPath);
|
|
101
|
-
files.push(...subFiles);
|
|
102
|
-
} else {
|
|
103
|
-
files.push(fullPath);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return files;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Add custom patterns to ignore
|
|
112
|
-
*/
|
|
113
|
-
addIgnorePatterns(patterns: string[]): void {
|
|
114
|
-
this.ig.add(patterns);
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/lib/prompts.ts
DELETED
|
@@ -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,171 +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
|
-
".env",
|
|
109
|
-
".env.local",
|
|
110
|
-
".env.*.local",
|
|
111
|
-
"*.key",
|
|
112
|
-
"*.pem",
|
|
113
|
-
"*.p12",
|
|
114
|
-
"node_modules/**",
|
|
115
|
-
".git/**",
|
|
116
|
-
"dist/**",
|
|
117
|
-
"build/**",
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
// Archive types
|
|
121
|
-
export type ArchiveType = "tar.gz" | "zip";
|
|
122
|
-
|
|
123
|
-
// Download progress
|
|
124
|
-
export interface DownloadProgress {
|
|
125
|
-
total: number;
|
|
126
|
-
current: number;
|
|
127
|
-
percentage: number;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Authentication method
|
|
131
|
-
export type AuthMethod = "gh-cli" | "env-var" | "keychain" | "prompt";
|
|
132
|
-
|
|
133
|
-
// Error types
|
|
134
|
-
export class ClaudeKitError extends Error {
|
|
135
|
-
constructor(
|
|
136
|
-
message: string,
|
|
137
|
-
public code?: string,
|
|
138
|
-
public statusCode?: number,
|
|
139
|
-
) {
|
|
140
|
-
super(message);
|
|
141
|
-
this.name = "ClaudeKitError";
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export class AuthenticationError extends ClaudeKitError {
|
|
146
|
-
constructor(message: string) {
|
|
147
|
-
super(message, "AUTH_ERROR", 401);
|
|
148
|
-
this.name = "AuthenticationError";
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export class GitHubError extends ClaudeKitError {
|
|
153
|
-
constructor(message: string, statusCode?: number) {
|
|
154
|
-
super(message, "GITHUB_ERROR", statusCode);
|
|
155
|
-
this.name = "GitHubError";
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export class DownloadError extends ClaudeKitError {
|
|
160
|
-
constructor(message: string) {
|
|
161
|
-
super(message, "DOWNLOAD_ERROR");
|
|
162
|
-
this.name = "DownloadError";
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export class ExtractionError extends ClaudeKitError {
|
|
167
|
-
constructor(message: string) {
|
|
168
|
-
super(message, "EXTRACTION_ERROR");
|
|
169
|
-
this.name = "ExtractionError";
|
|
170
|
-
}
|
|
171
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { type Config, ConfigSchema } from "../types.js";
|
|
6
|
-
import { logger } from "./logger.js";
|
|
7
|
-
|
|
8
|
-
const CONFIG_DIR = join(homedir(), ".claudekit");
|
|
9
|
-
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
10
|
-
|
|
11
|
-
export class ConfigManager {
|
|
12
|
-
private static config: Config | null = null;
|
|
13
|
-
|
|
14
|
-
static async load(): Promise<Config> {
|
|
15
|
-
if (ConfigManager.config) {
|
|
16
|
-
return ConfigManager.config;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
if (existsSync(CONFIG_FILE)) {
|
|
21
|
-
const content = await readFile(CONFIG_FILE, "utf-8");
|
|
22
|
-
const data = JSON.parse(content);
|
|
23
|
-
ConfigManager.config = ConfigSchema.parse(data);
|
|
24
|
-
logger.debug(`Config loaded from ${CONFIG_FILE}`);
|
|
25
|
-
return ConfigManager.config;
|
|
26
|
-
}
|
|
27
|
-
} catch (error) {
|
|
28
|
-
logger.warning(
|
|
29
|
-
`Failed to load config: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Return default config
|
|
34
|
-
ConfigManager.config = { github: {}, defaults: {} };
|
|
35
|
-
return ConfigManager.config;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static async save(config: Config): Promise<void> {
|
|
39
|
-
try {
|
|
40
|
-
// Validate config
|
|
41
|
-
const validConfig = ConfigSchema.parse(config);
|
|
42
|
-
|
|
43
|
-
// Ensure config directory exists
|
|
44
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
45
|
-
await mkdir(CONFIG_DIR, { recursive: true });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Write config file
|
|
49
|
-
await writeFile(CONFIG_FILE, JSON.stringify(validConfig, null, 2), "utf-8");
|
|
50
|
-
ConfigManager.config = validConfig;
|
|
51
|
-
logger.debug(`Config saved to ${CONFIG_FILE}`);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Failed to save config: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
static async get(): Promise<Config> {
|
|
60
|
-
return ConfigManager.load();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static async set(key: string, value: unknown): Promise<void> {
|
|
64
|
-
const config = await ConfigManager.load();
|
|
65
|
-
const keys = key.split(".");
|
|
66
|
-
let current: any = config;
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
69
|
-
if (!(keys[i] in current)) {
|
|
70
|
-
current[keys[i]] = {};
|
|
71
|
-
}
|
|
72
|
-
current = current[keys[i]];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
current[keys[keys.length - 1]] = value;
|
|
76
|
-
await ConfigManager.save(config);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
static async getToken(): Promise<string | undefined> {
|
|
80
|
-
const config = await ConfigManager.load();
|
|
81
|
-
return config.github?.token;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
static async setToken(token: string): Promise<void> {
|
|
85
|
-
await ConfigManager.set("github.token", token);
|
|
86
|
-
}
|
|
87
|
-
}
|