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
@@ -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
- }
@@ -1,134 +0,0 @@
1
- import { join, relative, resolve } from "node:path";
2
- import { lstat, pathExists, readdir } from "fs-extra";
3
- import { logger } from "./logger.js";
4
-
5
- /**
6
- * Utility class for scanning directories and comparing file structures
7
- */
8
- export class FileScanner {
9
- /**
10
- * Get all files in a directory recursively
11
- *
12
- * @param dirPath - Directory path to scan
13
- * @param relativeTo - Base path for calculating relative paths (defaults to dirPath)
14
- * @returns Array of relative file paths
15
- *
16
- * @example
17
- * ```typescript
18
- * const files = await FileScanner.getFiles('/path/to/dir');
19
- * // Returns: ['file1.txt', 'subdir/file2.txt', ...]
20
- * ```
21
- */
22
- static async getFiles(dirPath: string, relativeTo?: string): Promise<string[]> {
23
- const basePath = relativeTo || dirPath;
24
- const files: string[] = [];
25
-
26
- // Check if directory exists
27
- if (!(await pathExists(dirPath))) {
28
- return files;
29
- }
30
-
31
- try {
32
- const entries = await readdir(dirPath, { encoding: "utf8" });
33
-
34
- for (const entry of entries) {
35
- const fullPath = join(dirPath, entry);
36
-
37
- // Security: Validate path to prevent traversal
38
- if (!FileScanner.isSafePath(basePath, fullPath)) {
39
- logger.warning(`Skipping potentially unsafe path: ${entry}`);
40
- continue;
41
- }
42
-
43
- const stats = await lstat(fullPath);
44
-
45
- // Skip symlinks for security
46
- if (stats.isSymbolicLink()) {
47
- logger.debug(`Skipping symlink: ${entry}`);
48
- continue;
49
- }
50
-
51
- if (stats.isDirectory()) {
52
- // Recursively scan subdirectories
53
- const subFiles = await FileScanner.getFiles(fullPath, basePath);
54
- files.push(...subFiles);
55
- } else if (stats.isFile()) {
56
- // Add relative path
57
- const relativePath = relative(basePath, fullPath);
58
- files.push(relativePath);
59
- }
60
- }
61
- } catch (error) {
62
- const errorMessage =
63
- error instanceof Error
64
- ? `Failed to scan directory: ${dirPath} - ${error.message}`
65
- : `Failed to scan directory: ${dirPath}`;
66
- logger.error(errorMessage);
67
- throw error;
68
- }
69
-
70
- return files;
71
- }
72
-
73
- /**
74
- * Find files in destination that don't exist in source
75
- *
76
- * @param destDir - Destination directory path
77
- * @param sourceDir - Source directory path
78
- * @param subPath - Subdirectory to compare (e.g., '.claude')
79
- * @returns Array of relative file paths that are custom (exist in dest but not in source)
80
- *
81
- * @example
82
- * ```typescript
83
- * const customFiles = await FileScanner.findCustomFiles(
84
- * '/path/to/project',
85
- * '/path/to/release',
86
- * '.claude'
87
- * );
88
- * // Returns: ['.claude/custom-command.md', '.claude/workflows/my-workflow.md']
89
- * ```
90
- */
91
- static async findCustomFiles(
92
- destDir: string,
93
- sourceDir: string,
94
- subPath: string,
95
- ): Promise<string[]> {
96
- const destSubDir = join(destDir, subPath);
97
- const sourceSubDir = join(sourceDir, subPath);
98
-
99
- // Get files from both directories
100
- const destFiles = await FileScanner.getFiles(destSubDir, destDir);
101
- const sourceFiles = await FileScanner.getFiles(sourceSubDir, sourceDir);
102
-
103
- // Create a Set of source files for O(1) lookup
104
- const sourceFileSet = new Set(sourceFiles);
105
-
106
- // Find files in destination that don't exist in source
107
- const customFiles = destFiles.filter((file) => !sourceFileSet.has(file));
108
-
109
- if (customFiles.length > 0) {
110
- logger.info(`Found ${customFiles.length} custom file(s) in ${subPath}/`);
111
- customFiles.slice(0, 5).forEach((file) => logger.debug(` - ${file}`));
112
- if (customFiles.length > 5) {
113
- logger.debug(` ... and ${customFiles.length - 5} more`);
114
- }
115
- }
116
-
117
- return customFiles;
118
- }
119
-
120
- /**
121
- * Validate path to prevent path traversal attacks
122
- *
123
- * @param basePath - Base directory path
124
- * @param targetPath - Target path to validate
125
- * @returns true if path is safe, false otherwise
126
- */
127
- private static isSafePath(basePath: string, targetPath: string): boolean {
128
- const resolvedBase = resolve(basePath);
129
- const resolvedTarget = resolve(targetPath);
130
-
131
- // Ensure target is within base
132
- return resolvedTarget.startsWith(resolvedBase);
133
- }
134
- }
@@ -1,124 +0,0 @@
1
- import { type WriteStream, createWriteStream } from "node:fs";
2
- import pc from "picocolors";
3
-
4
- // Use ASCII-safe symbols to avoid unicode rendering issues in certain terminals
5
- const symbols = {
6
- info: "[i]",
7
- success: "[+]",
8
- warning: "[!]",
9
- error: "[x]",
10
- };
11
-
12
- interface LogContext {
13
- [key: string]: any;
14
- }
15
-
16
- class Logger {
17
- private verboseEnabled = false;
18
- private logFileStream?: WriteStream;
19
-
20
- info(message: string): void {
21
- console.log(pc.blue(symbols.info), message);
22
- }
23
-
24
- success(message: string): void {
25
- console.log(pc.green(symbols.success), message);
26
- }
27
-
28
- warning(message: string): void {
29
- console.log(pc.yellow(symbols.warning), message);
30
- }
31
-
32
- error(message: string): void {
33
- console.error(pc.red(symbols.error), message);
34
- }
35
-
36
- debug(message: string): void {
37
- if (process.env.DEBUG) {
38
- console.log(pc.gray("[DEBUG]"), message);
39
- }
40
- }
41
-
42
- verbose(message: string, context?: LogContext): void {
43
- if (!this.verboseEnabled) return;
44
-
45
- const timestamp = this.getTimestamp();
46
- const sanitizedMessage = this.sanitize(message);
47
- const formattedContext = context ? this.formatContext(context) : "";
48
-
49
- const logLine = `${timestamp} ${pc.gray("[VERBOSE]")} ${sanitizedMessage}${formattedContext}`;
50
-
51
- console.error(logLine);
52
-
53
- if (this.logFileStream) {
54
- const plainLogLine = `${timestamp} [VERBOSE] ${sanitizedMessage}${formattedContext}`;
55
- this.logFileStream.write(`${plainLogLine}\n`);
56
- }
57
- }
58
-
59
- setVerbose(enabled: boolean): void {
60
- this.verboseEnabled = enabled;
61
- if (enabled) {
62
- this.verbose("Verbose logging enabled");
63
- }
64
- }
65
-
66
- isVerbose(): boolean {
67
- return this.verboseEnabled;
68
- }
69
-
70
- setLogFile(path?: string): void {
71
- if (this.logFileStream) {
72
- this.logFileStream.end();
73
- this.logFileStream = undefined;
74
- }
75
-
76
- if (path) {
77
- this.logFileStream = createWriteStream(path, {
78
- flags: "a",
79
- mode: 0o600,
80
- });
81
- this.verbose(`Logging to file: ${path}`);
82
- }
83
- }
84
-
85
- sanitize(text: string): string {
86
- return text
87
- .replace(/ghp_[a-zA-Z0-9]{36}/g, "ghp_***")
88
- .replace(/github_pat_[a-zA-Z0-9_]{82}/g, "github_pat_***")
89
- .replace(/gho_[a-zA-Z0-9]{36}/g, "gho_***")
90
- .replace(/ghu_[a-zA-Z0-9]{36}/g, "ghu_***")
91
- .replace(/ghs_[a-zA-Z0-9]{36}/g, "ghs_***")
92
- .replace(/ghr_[a-zA-Z0-9]{36}/g, "ghr_***")
93
- .replace(/Bearer [a-zA-Z0-9_-]+/g, "Bearer ***")
94
- .replace(/token=[a-zA-Z0-9_-]+/g, "token=***");
95
- }
96
-
97
- private getTimestamp(): string {
98
- return new Date().toISOString();
99
- }
100
-
101
- private formatContext(context: LogContext): string {
102
- const sanitized = Object.entries(context).reduce((acc, [key, value]) => {
103
- if (typeof value === "string") {
104
- acc[key] = this.sanitize(value);
105
- } else if (value && typeof value === "object") {
106
- // Recursively sanitize nested objects
107
- try {
108
- const stringified = JSON.stringify(value);
109
- const sanitizedStr = this.sanitize(stringified);
110
- acc[key] = JSON.parse(sanitizedStr);
111
- } catch {
112
- acc[key] = "[Object]";
113
- }
114
- } else {
115
- acc[key] = value;
116
- }
117
- return acc;
118
- }, {} as LogContext);
119
-
120
- return `\n ${JSON.stringify(sanitized, null, 2).split("\n").join("\n ")}`;
121
- }
122
- }
123
-
124
- export const logger = new Logger();
@@ -1,44 +0,0 @@
1
- import picocolors from "picocolors";
2
-
3
- /**
4
- * Safe wrapper around clack prompts that uses simple ASCII characters
5
- * instead of unicode box drawing to avoid rendering issues.
6
- */
7
-
8
- /**
9
- * Simple intro with ASCII characters
10
- */
11
- export function intro(message: string): void {
12
- console.log();
13
- console.log(picocolors.cyan(`> ${message}`));
14
- console.log();
15
- }
16
-
17
- /**
18
- * Simple outro with ASCII characters
19
- */
20
- export function outro(message: string): void {
21
- console.log();
22
- console.log(picocolors.green(`[OK] ${message}`));
23
- console.log();
24
- }
25
-
26
- /**
27
- * Simple note with ASCII box drawing
28
- */
29
- export function note(message: string, title?: string): void {
30
- console.log();
31
- if (title) {
32
- console.log(picocolors.cyan(` ${title}:`));
33
- console.log();
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();
41
- }
42
-
43
- // Re-export other clack functions unchanged
44
- export { select, confirm, text, isCancel } from "@clack/prompts";
@@ -1,38 +0,0 @@
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";
package/src/version.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "version": "1.2.1"
3
- }