claudekit-cli 1.2.0 → 1.2.2
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/CHANGELOG.md +14 -0
- package/README.md +2 -0
- package/biome.json +1 -1
- package/bun.lock +17 -7
- package/dist/index.js +12352 -9730
- package/package.json +2 -2
- package/src/commands/new.ts +5 -2
- package/src/commands/update.ts +5 -2
- package/src/lib/download.ts +165 -35
- package/src/lib/github.ts +31 -14
- package/src/types.ts +2 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils/safe-prompts.ts +21 -31
- package/src/utils/safe-spinner.ts +38 -0
- package/tests/lib/github-download-priority.test.ts +136 -5
- package/tests/types.test.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudekit-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "CLI tool for bootstrapping and updating ClaudeKit projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@octokit/rest": "^22.0.0",
|
|
34
34
|
"cac": "^6.7.14",
|
|
35
35
|
"cli-progress": "^3.12.0",
|
|
36
|
+
"extract-zip": "^2.0.1",
|
|
36
37
|
"fs-extra": "^11.2.0",
|
|
37
38
|
"ignore": "^5.3.2",
|
|
38
39
|
"keytar": "^7.9.0",
|
|
@@ -40,7 +41,6 @@
|
|
|
40
41
|
"picocolors": "^1.1.1",
|
|
41
42
|
"tar": "^7.4.3",
|
|
42
43
|
"tmp": "^0.2.3",
|
|
43
|
-
"unzipper": "^0.12.3",
|
|
44
44
|
"zod": "^3.23.8"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/commands/new.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { pathExists, readdir } from "fs-extra";
|
|
3
|
-
import ora from "ora";
|
|
4
3
|
import { AuthManager } from "../lib/auth.js";
|
|
5
4
|
import { DownloadManager } from "../lib/download.js";
|
|
6
5
|
import { GitHubClient } from "../lib/github.js";
|
|
@@ -9,6 +8,7 @@ import { PromptsManager } from "../lib/prompts.js";
|
|
|
9
8
|
import { AVAILABLE_KITS, type NewCommandOptions, NewCommandOptionsSchema } from "../types.js";
|
|
10
9
|
import { ConfigManager } from "../utils/config.js";
|
|
11
10
|
import { logger } from "../utils/logger.js";
|
|
11
|
+
import { createSpinner } from "../utils/safe-spinner.js";
|
|
12
12
|
|
|
13
13
|
export async function newCommand(options: NewCommandOptions): Promise<void> {
|
|
14
14
|
const prompts = new PromptsManager();
|
|
@@ -59,7 +59,7 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
|
|
|
59
59
|
const github = new GitHubClient();
|
|
60
60
|
|
|
61
61
|
// Check repository access
|
|
62
|
-
const spinner =
|
|
62
|
+
const spinner = createSpinner("Checking repository access...").start();
|
|
63
63
|
const hasAccess = await github.checkAccess(kitConfig);
|
|
64
64
|
if (!hasAccess) {
|
|
65
65
|
spinner.fail("Access denied to repository");
|
|
@@ -132,6 +132,9 @@ export async function newCommand(options: NewCommandOptions): Promise<void> {
|
|
|
132
132
|
const extractDir = `${tempDir}/extracted`;
|
|
133
133
|
await downloadManager.extractArchive(archivePath, extractDir);
|
|
134
134
|
|
|
135
|
+
// Validate extraction
|
|
136
|
+
await downloadManager.validateExtraction(extractDir);
|
|
137
|
+
|
|
135
138
|
// Copy files to target directory
|
|
136
139
|
const merger = new FileMerger();
|
|
137
140
|
await merger.merge(extractDir, resolvedDir, true); // Skip confirmation for new projects
|
package/src/commands/update.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { pathExists } from "fs-extra";
|
|
3
|
-
import ora from "ora";
|
|
4
3
|
import { AuthManager } from "../lib/auth.js";
|
|
5
4
|
import { DownloadManager } from "../lib/download.js";
|
|
6
5
|
import { GitHubClient } from "../lib/github.js";
|
|
@@ -10,6 +9,7 @@ import { AVAILABLE_KITS, type UpdateCommandOptions, UpdateCommandOptionsSchema }
|
|
|
10
9
|
import { ConfigManager } from "../utils/config.js";
|
|
11
10
|
import { FileScanner } from "../utils/file-scanner.js";
|
|
12
11
|
import { logger } from "../utils/logger.js";
|
|
12
|
+
import { createSpinner } from "../utils/safe-spinner.js";
|
|
13
13
|
|
|
14
14
|
export async function updateCommand(options: UpdateCommandOptions): Promise<void> {
|
|
15
15
|
const prompts = new PromptsManager();
|
|
@@ -52,7 +52,7 @@ export async function updateCommand(options: UpdateCommandOptions): Promise<void
|
|
|
52
52
|
const github = new GitHubClient();
|
|
53
53
|
|
|
54
54
|
// Check repository access
|
|
55
|
-
const spinner =
|
|
55
|
+
const spinner = createSpinner("Checking repository access...").start();
|
|
56
56
|
const hasAccess = await github.checkAccess(kitConfig);
|
|
57
57
|
if (!hasAccess) {
|
|
58
58
|
spinner.fail("Access denied to repository");
|
|
@@ -125,6 +125,9 @@ export async function updateCommand(options: UpdateCommandOptions): Promise<void
|
|
|
125
125
|
const extractDir = `${tempDir}/extracted`;
|
|
126
126
|
await downloadManager.extractArchive(archivePath, extractDir);
|
|
127
127
|
|
|
128
|
+
// Validate extraction
|
|
129
|
+
await downloadManager.validateExtraction(extractDir);
|
|
130
|
+
|
|
128
131
|
// Identify custom .claude files to preserve
|
|
129
132
|
logger.info("Scanning for custom .claude files...");
|
|
130
133
|
const customClaudeFiles = await FileScanner.findCustomFiles(resolvedDir, extractDir, ".claude");
|
package/src/lib/download.ts
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createWriteStream } from "node:fs";
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { pipeline } from "node:stream";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
5
|
import cliProgress from "cli-progress";
|
|
6
|
+
import extractZip from "extract-zip";
|
|
8
7
|
import ignore from "ignore";
|
|
9
|
-
import ora from "ora";
|
|
10
8
|
import * as tar from "tar";
|
|
11
|
-
import unzipper from "unzipper";
|
|
12
9
|
import {
|
|
13
10
|
type ArchiveType,
|
|
14
11
|
DownloadError,
|
|
@@ -16,8 +13,7 @@ import {
|
|
|
16
13
|
type GitHubReleaseAsset,
|
|
17
14
|
} from "../types.js";
|
|
18
15
|
import { logger } from "../utils/logger.js";
|
|
19
|
-
|
|
20
|
-
const streamPipeline = promisify(pipeline);
|
|
16
|
+
import { createSpinner } from "../utils/safe-spinner.js";
|
|
21
17
|
|
|
22
18
|
export class DownloadManager {
|
|
23
19
|
/**
|
|
@@ -55,11 +51,11 @@ export class DownloadManager {
|
|
|
55
51
|
|
|
56
52
|
logger.info(`Downloading ${asset.name} (${this.formatBytes(asset.size)})...`);
|
|
57
53
|
|
|
58
|
-
// Create progress bar
|
|
54
|
+
// Create progress bar with simple ASCII characters
|
|
59
55
|
const progressBar = new cliProgress.SingleBar({
|
|
60
56
|
format: "Progress |{bar}| {percentage}% | {value}/{total} MB",
|
|
61
|
-
barCompleteChar: "
|
|
62
|
-
barIncompleteChar: "
|
|
57
|
+
barCompleteChar: "=",
|
|
58
|
+
barIncompleteChar: "-",
|
|
63
59
|
hideCursor: true,
|
|
64
60
|
});
|
|
65
61
|
|
|
@@ -137,7 +133,9 @@ export class DownloadManager {
|
|
|
137
133
|
// Add authentication for GitHub API URLs
|
|
138
134
|
if (token && url.includes("api.github.com")) {
|
|
139
135
|
headers.Authorization = `Bearer ${token}`;
|
|
140
|
-
|
|
136
|
+
// Use application/octet-stream for asset downloads (not vnd.github+json)
|
|
137
|
+
headers.Accept = "application/octet-stream";
|
|
138
|
+
headers["X-GitHub-Api-Version"] = "2022-11-28";
|
|
141
139
|
} else {
|
|
142
140
|
headers.Accept = "application/octet-stream";
|
|
143
141
|
}
|
|
@@ -151,13 +149,13 @@ export class DownloadManager {
|
|
|
151
149
|
const totalSize = size || Number(response.headers.get("content-length")) || 0;
|
|
152
150
|
let downloadedSize = 0;
|
|
153
151
|
|
|
154
|
-
// Create progress bar only if we know the size
|
|
152
|
+
// Create progress bar only if we know the size (using simple ASCII characters)
|
|
155
153
|
const progressBar =
|
|
156
154
|
totalSize > 0
|
|
157
155
|
? new cliProgress.SingleBar({
|
|
158
156
|
format: "Progress |{bar}| {percentage}% | {value}/{total} MB",
|
|
159
|
-
barCompleteChar: "
|
|
160
|
-
barIncompleteChar: "
|
|
157
|
+
barCompleteChar: "=",
|
|
158
|
+
barIncompleteChar: "-",
|
|
161
159
|
hideCursor: true,
|
|
162
160
|
})
|
|
163
161
|
: null;
|
|
@@ -207,7 +205,7 @@ export class DownloadManager {
|
|
|
207
205
|
destDir: string,
|
|
208
206
|
archiveType?: ArchiveType,
|
|
209
207
|
): Promise<void> {
|
|
210
|
-
const spinner =
|
|
208
|
+
const spinner = createSpinner("Extracting files...").start();
|
|
211
209
|
|
|
212
210
|
try {
|
|
213
211
|
// Detect archive type from filename if not provided
|
|
@@ -237,19 +235,95 @@ export class DownloadManager {
|
|
|
237
235
|
* Extract tar.gz archive
|
|
238
236
|
*/
|
|
239
237
|
private async extractTarGz(archivePath: string, destDir: string): Promise<void> {
|
|
240
|
-
await
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
238
|
+
const { readdir, stat, mkdir: mkdirPromise, copyFile, rm } = await import("node:fs/promises");
|
|
239
|
+
const { join: pathJoin } = await import("node:path");
|
|
240
|
+
|
|
241
|
+
// Extract to a temporary directory first
|
|
242
|
+
const tempExtractDir = `${destDir}-temp`;
|
|
243
|
+
await mkdirPromise(tempExtractDir, { recursive: true });
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Extract without stripping first
|
|
247
|
+
await tar.extract({
|
|
248
|
+
file: archivePath,
|
|
249
|
+
cwd: tempExtractDir,
|
|
250
|
+
strip: 0, // Don't strip yet - we'll decide based on wrapper detection
|
|
251
|
+
filter: (path: string) => {
|
|
252
|
+
// Exclude unwanted files
|
|
253
|
+
const shouldInclude = !this.shouldExclude(path);
|
|
254
|
+
if (!shouldInclude) {
|
|
255
|
+
logger.debug(`Excluding: ${path}`);
|
|
256
|
+
}
|
|
257
|
+
return shouldInclude;
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
logger.debug(`Extracted TAR.GZ to temp: ${tempExtractDir}`);
|
|
262
|
+
|
|
263
|
+
// Apply same wrapper detection logic as zip
|
|
264
|
+
const entries = await readdir(tempExtractDir);
|
|
265
|
+
logger.debug(`Root entries: ${entries.join(", ")}`);
|
|
266
|
+
|
|
267
|
+
if (entries.length === 1) {
|
|
268
|
+
const rootEntry = entries[0];
|
|
269
|
+
const rootPath = pathJoin(tempExtractDir, rootEntry);
|
|
270
|
+
const rootStat = await stat(rootPath);
|
|
271
|
+
|
|
272
|
+
if (rootStat.isDirectory()) {
|
|
273
|
+
// Check contents of root directory
|
|
274
|
+
const rootContents = await readdir(rootPath);
|
|
275
|
+
logger.debug(`Root directory '${rootEntry}' contains: ${rootContents.join(", ")}`);
|
|
276
|
+
|
|
277
|
+
// Only strip if root is a version/release wrapper
|
|
278
|
+
const isWrapper = this.isWrapperDirectory(rootEntry);
|
|
279
|
+
logger.debug(`Is wrapper directory: ${isWrapper}`);
|
|
280
|
+
|
|
281
|
+
if (isWrapper) {
|
|
282
|
+
// Strip wrapper and move contents
|
|
283
|
+
logger.debug(`Stripping wrapper directory: ${rootEntry}`);
|
|
284
|
+
await this.moveDirectoryContents(rootPath, destDir);
|
|
285
|
+
} else {
|
|
286
|
+
// Keep root directory - move everything including root
|
|
287
|
+
logger.debug("Preserving complete directory structure");
|
|
288
|
+
await this.moveDirectoryContents(tempExtractDir, destDir);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
// Single file, just move it
|
|
292
|
+
await mkdirPromise(destDir, { recursive: true });
|
|
293
|
+
await copyFile(rootPath, pathJoin(destDir, rootEntry));
|
|
249
294
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
295
|
+
} else {
|
|
296
|
+
// Multiple entries at root, move them all
|
|
297
|
+
logger.debug("Multiple root entries - moving all");
|
|
298
|
+
await this.moveDirectoryContents(tempExtractDir, destDir);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
logger.debug(`Moved contents to: ${destDir}`);
|
|
302
|
+
|
|
303
|
+
// Clean up temp directory
|
|
304
|
+
await rm(tempExtractDir, { recursive: true, force: true });
|
|
305
|
+
} catch (error) {
|
|
306
|
+
// Clean up temp directory on error
|
|
307
|
+
try {
|
|
308
|
+
await rm(tempExtractDir, { recursive: true, force: true });
|
|
309
|
+
} catch {
|
|
310
|
+
// Ignore cleanup errors
|
|
311
|
+
}
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Check if directory name is a version/release wrapper
|
|
318
|
+
* Examples: claudekit-engineer-v1.0.0, claudekit-engineer-1.0.0, repo-abc1234
|
|
319
|
+
*/
|
|
320
|
+
private isWrapperDirectory(dirName: string): boolean {
|
|
321
|
+
// Match version patterns: project-v1.0.0, project-1.0.0
|
|
322
|
+
const versionPattern = /^[\w-]+-v?\d+\.\d+\.\d+/;
|
|
323
|
+
// Match commit hash patterns: project-abc1234
|
|
324
|
+
const hashPattern = /^[\w-]+-[a-f0-9]{7,}$/;
|
|
325
|
+
|
|
326
|
+
return versionPattern.test(dirName) || hashPattern.test(dirName);
|
|
253
327
|
}
|
|
254
328
|
|
|
255
329
|
/**
|
|
@@ -264,24 +338,39 @@ export class DownloadManager {
|
|
|
264
338
|
await mkdirPromise(tempExtractDir, { recursive: true });
|
|
265
339
|
|
|
266
340
|
try {
|
|
267
|
-
// Extract zip to temp directory
|
|
268
|
-
await
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
341
|
+
// Extract zip to temp directory using extract-zip
|
|
342
|
+
await extractZip(archivePath, { dir: tempExtractDir });
|
|
343
|
+
|
|
344
|
+
logger.debug(`Extracted ZIP to temp: ${tempExtractDir}`);
|
|
272
345
|
|
|
273
346
|
// Find the root directory in the zip (if any)
|
|
274
347
|
const entries = await readdir(tempExtractDir);
|
|
348
|
+
logger.debug(`Root entries: ${entries.join(", ")}`);
|
|
275
349
|
|
|
276
|
-
// If there's a single root directory,
|
|
350
|
+
// If there's a single root directory, check if it's a wrapper
|
|
277
351
|
if (entries.length === 1) {
|
|
278
352
|
const rootEntry = entries[0];
|
|
279
353
|
const rootPath = pathJoin(tempExtractDir, rootEntry);
|
|
280
354
|
const rootStat = await stat(rootPath);
|
|
281
355
|
|
|
282
356
|
if (rootStat.isDirectory()) {
|
|
283
|
-
//
|
|
284
|
-
await
|
|
357
|
+
// Check contents of root directory
|
|
358
|
+
const rootContents = await readdir(rootPath);
|
|
359
|
+
logger.debug(`Root directory '${rootEntry}' contains: ${rootContents.join(", ")}`);
|
|
360
|
+
|
|
361
|
+
// Only strip if root is a version/release wrapper
|
|
362
|
+
const isWrapper = this.isWrapperDirectory(rootEntry);
|
|
363
|
+
logger.debug(`Is wrapper directory: ${isWrapper}`);
|
|
364
|
+
|
|
365
|
+
if (isWrapper) {
|
|
366
|
+
// Strip wrapper and move contents
|
|
367
|
+
logger.debug(`Stripping wrapper directory: ${rootEntry}`);
|
|
368
|
+
await this.moveDirectoryContents(rootPath, destDir);
|
|
369
|
+
} else {
|
|
370
|
+
// Keep root directory - move everything including root
|
|
371
|
+
logger.debug("Preserving complete directory structure");
|
|
372
|
+
await this.moveDirectoryContents(tempExtractDir, destDir);
|
|
373
|
+
}
|
|
285
374
|
} else {
|
|
286
375
|
// Single file, just move it
|
|
287
376
|
await mkdirPromise(destDir, { recursive: true });
|
|
@@ -289,9 +378,12 @@ export class DownloadManager {
|
|
|
289
378
|
}
|
|
290
379
|
} else {
|
|
291
380
|
// Multiple entries at root, move them all
|
|
381
|
+
logger.debug("Multiple root entries - moving all");
|
|
292
382
|
await this.moveDirectoryContents(tempExtractDir, destDir);
|
|
293
383
|
}
|
|
294
384
|
|
|
385
|
+
logger.debug(`Moved contents to: ${destDir}`);
|
|
386
|
+
|
|
295
387
|
// Clean up temp directory
|
|
296
388
|
await rm(tempExtractDir, { recursive: true, force: true });
|
|
297
389
|
} catch (error) {
|
|
@@ -386,6 +478,44 @@ export class DownloadManager {
|
|
|
386
478
|
throw new ExtractionError(`Cannot detect archive type from filename: ${filename}`);
|
|
387
479
|
}
|
|
388
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Validate extraction results
|
|
483
|
+
*/
|
|
484
|
+
async validateExtraction(extractDir: string): Promise<boolean> {
|
|
485
|
+
const { readdir, access } = await import("node:fs/promises");
|
|
486
|
+
const { join: pathJoin } = await import("node:path");
|
|
487
|
+
const { constants } = await import("node:fs");
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
// Check if extract directory exists and is not empty
|
|
491
|
+
const entries = await readdir(extractDir);
|
|
492
|
+
logger.debug(`Extracted files: ${entries.join(", ")}`);
|
|
493
|
+
|
|
494
|
+
if (entries.length === 0) {
|
|
495
|
+
logger.warning("Extraction resulted in no files");
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Verify critical paths exist
|
|
500
|
+
const criticalPaths = [".claude", "CLAUDE.md"];
|
|
501
|
+
for (const path of criticalPaths) {
|
|
502
|
+
try {
|
|
503
|
+
await access(pathJoin(extractDir, path), constants.F_OK);
|
|
504
|
+
logger.debug(`✓ Found: ${path}`);
|
|
505
|
+
} catch {
|
|
506
|
+
logger.warning(`Expected path not found: ${path}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return true;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
logger.error(
|
|
513
|
+
`Validation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
514
|
+
);
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
389
519
|
/**
|
|
390
520
|
* Create temporary download directory
|
|
391
521
|
*/
|
package/src/lib/github.ts
CHANGED
|
@@ -159,7 +159,7 @@ export class GitHubClient {
|
|
|
159
159
|
* Get downloadable asset or source code URL from release
|
|
160
160
|
* Priority:
|
|
161
161
|
* 1. "ClaudeKit Engineer Package" or "ClaudeKit Marketing Package" zip file
|
|
162
|
-
* 2. Other custom uploaded assets (.tar.gz, .tgz, .zip)
|
|
162
|
+
* 2. Other custom uploaded assets (.tar.gz, .tgz, .zip) excluding "Source code" archives
|
|
163
163
|
* 3. GitHub's automatic tarball URL
|
|
164
164
|
*/
|
|
165
165
|
static getDownloadableAsset(release: GitHubRelease): {
|
|
@@ -168,41 +168,58 @@ export class GitHubClient {
|
|
|
168
168
|
name: string;
|
|
169
169
|
size?: number;
|
|
170
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
|
+
|
|
171
181
|
// First priority: Look for official ClaudeKit package assets
|
|
172
|
-
const packageAsset = release.assets.find(
|
|
173
|
-
(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
});
|
|
178
190
|
|
|
179
191
|
if (packageAsset) {
|
|
180
|
-
logger.debug(
|
|
192
|
+
logger.debug(`✓ Selected ClaudeKit package asset: ${packageAsset.name}`);
|
|
181
193
|
return {
|
|
182
194
|
type: "asset",
|
|
183
|
-
url: packageAsset.
|
|
195
|
+
url: packageAsset.url, // Use API endpoint for authenticated downloads
|
|
184
196
|
name: packageAsset.name,
|
|
185
197
|
size: packageAsset.size,
|
|
186
198
|
};
|
|
187
199
|
}
|
|
188
200
|
|
|
189
|
-
|
|
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)
|
|
190
204
|
const customAsset = release.assets.find(
|
|
191
|
-
(a) =>
|
|
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"),
|
|
192
209
|
);
|
|
193
210
|
|
|
194
211
|
if (customAsset) {
|
|
195
|
-
logger.debug(
|
|
212
|
+
logger.debug(`✓ Selected custom asset: ${customAsset.name}`);
|
|
196
213
|
return {
|
|
197
214
|
type: "asset",
|
|
198
|
-
url: customAsset.
|
|
215
|
+
url: customAsset.url, // Use API endpoint for authenticated downloads
|
|
199
216
|
name: customAsset.name,
|
|
200
217
|
size: customAsset.size,
|
|
201
218
|
};
|
|
202
219
|
}
|
|
203
220
|
|
|
204
221
|
// Fall back to GitHub's automatic tarball
|
|
205
|
-
logger.debug("
|
|
222
|
+
logger.debug("⚠ No custom assets found, falling back to GitHub automatic tarball");
|
|
206
223
|
return {
|
|
207
224
|
type: "tarball",
|
|
208
225
|
url: release.tarball_url,
|
package/src/types.ts
CHANGED
|
@@ -46,7 +46,8 @@ export type Config = z.infer<typeof ConfigSchema>;
|
|
|
46
46
|
export const GitHubReleaseAssetSchema = z.object({
|
|
47
47
|
id: z.number(),
|
|
48
48
|
name: z.string(),
|
|
49
|
-
|
|
49
|
+
url: z.string().url(), // API endpoint for authenticated downloads
|
|
50
|
+
browser_download_url: z.string().url(), // Direct download URL (public only)
|
|
50
51
|
size: z.number(),
|
|
51
52
|
content_type: z.string(),
|
|
52
53
|
});
|
package/src/utils/logger.ts
CHANGED
|
@@ -4,9 +4,9 @@ import pc from "picocolors";
|
|
|
4
4
|
// Use ASCII-safe symbols to avoid unicode rendering issues in certain terminals
|
|
5
5
|
const symbols = {
|
|
6
6
|
info: "[i]",
|
|
7
|
-
success: "[
|
|
7
|
+
success: "[+]",
|
|
8
8
|
warning: "[!]",
|
|
9
|
-
error: "[
|
|
9
|
+
error: "[x]",
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
interface LogContext {
|
|
@@ -1,53 +1,43 @@
|
|
|
1
|
-
import
|
|
1
|
+
import picocolors from "picocolors";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Safe wrapper around clack prompts that
|
|
5
|
-
*
|
|
4
|
+
* Safe wrapper around clack prompts that uses simple ASCII characters
|
|
5
|
+
* instead of unicode box drawing to avoid rendering issues.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
// Store original methods
|
|
9
|
-
const originalIntro = clack.intro;
|
|
10
|
-
const originalOutro = clack.outro;
|
|
11
|
-
const originalNote = clack.note;
|
|
12
|
-
|
|
13
8
|
/**
|
|
14
|
-
*
|
|
9
|
+
* Simple intro with ASCII characters
|
|
15
10
|
*/
|
|
16
11
|
export function intro(message: string): void {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// Fallback to simple console log if clack fails
|
|
21
|
-
console.log(`\n=== ${message} ===\n`);
|
|
22
|
-
}
|
|
12
|
+
console.log();
|
|
13
|
+
console.log(picocolors.cyan(`> ${message}`));
|
|
14
|
+
console.log();
|
|
23
15
|
}
|
|
24
16
|
|
|
25
17
|
/**
|
|
26
|
-
*
|
|
18
|
+
* Simple outro with ASCII characters
|
|
27
19
|
*/
|
|
28
20
|
export function outro(message: string): void {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Fallback to simple console log if clack fails
|
|
33
|
-
console.log(`\n=== ${message} ===\n`);
|
|
34
|
-
}
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(picocolors.green(`[OK] ${message}`));
|
|
23
|
+
console.log();
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
/**
|
|
38
|
-
*
|
|
27
|
+
* Simple note with ASCII box drawing
|
|
39
28
|
*/
|
|
40
29
|
export function note(message: string, title?: string): void {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Fallback to simple console log if clack fails
|
|
45
|
-
if (title) {
|
|
46
|
-
console.log(`\n--- ${title} ---`);
|
|
47
|
-
}
|
|
48
|
-
console.log(message);
|
|
30
|
+
console.log();
|
|
31
|
+
if (title) {
|
|
32
|
+
console.log(picocolors.cyan(` ${title}:`));
|
|
49
33
|
console.log();
|
|
50
34
|
}
|
|
35
|
+
// Split message into lines and indent each
|
|
36
|
+
const lines = message.split("\n");
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
console.log(` ${line}`);
|
|
39
|
+
}
|
|
40
|
+
console.log();
|
|
51
41
|
}
|
|
52
42
|
|
|
53
43
|
// Re-export other clack functions unchanged
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ora, { type Ora, type Options } from "ora";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a spinner with simple ASCII characters to avoid unicode rendering issues
|
|
5
|
+
*/
|
|
6
|
+
export function createSpinner(options: string | Options): Ora {
|
|
7
|
+
const spinnerOptions: Options = typeof options === "string" ? { text: options } : options;
|
|
8
|
+
|
|
9
|
+
const spinner = ora({
|
|
10
|
+
...spinnerOptions,
|
|
11
|
+
// Use simple ASCII spinner instead of unicode
|
|
12
|
+
spinner: "dots",
|
|
13
|
+
// Override symbols to use ASCII
|
|
14
|
+
prefixText: "",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Override succeed and fail methods to use ASCII symbols
|
|
18
|
+
spinner.succeed = (text?: string) => {
|
|
19
|
+
spinner.stopAndPersist({
|
|
20
|
+
symbol: "[+]",
|
|
21
|
+
text: text || spinner.text,
|
|
22
|
+
});
|
|
23
|
+
return spinner;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
spinner.fail = (text?: string) => {
|
|
27
|
+
spinner.stopAndPersist({
|
|
28
|
+
symbol: "[x]",
|
|
29
|
+
text: text || spinner.text,
|
|
30
|
+
});
|
|
31
|
+
return spinner;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return spinner;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Re-export Ora type for convenience
|
|
38
|
+
export type { Ora } from "ora";
|