cskit-cli 1.0.20 → 1.0.22
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/package.json +1 -1
- package/src/commands/init.js +595 -266
- package/src/commands/python.js +170 -0
- package/src/index.js +10 -0
- package/src/lib/github.js +95 -0
- package/src/lib/python-check.js +402 -0
package/src/commands/init.js
CHANGED
|
@@ -1,299 +1,418 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Init Command
|
|
4
|
+
* Init Command - Improved Version
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Download zip instead of individual files
|
|
8
|
+
* - Timeline progress display (◇ pending → ◆ done)
|
|
9
|
+
* - Scan and show files to be overwritten
|
|
10
|
+
* - User confirmation before changes
|
|
11
|
+
* - Optional advanced packages installation
|
|
12
|
+
* - Platform-specific handling
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
15
|
const fs = require('fs');
|
|
11
16
|
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
12
18
|
const chalk = require('chalk');
|
|
13
|
-
const ora = require('ora');
|
|
14
|
-
const crypto = require('crypto');
|
|
15
19
|
const inquirer = require('inquirer');
|
|
16
|
-
const
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
const { execSync, spawnSync } = require('child_process');
|
|
17
22
|
const { getToken } = require('../lib/keychain');
|
|
18
23
|
const {
|
|
19
24
|
verifyAccess,
|
|
20
|
-
getRepoTreeFromRef,
|
|
21
|
-
downloadFileFromRef,
|
|
22
25
|
getAllReleases,
|
|
23
|
-
getLatestRelease
|
|
26
|
+
getLatestRelease,
|
|
27
|
+
downloadZipFromRef
|
|
24
28
|
} = require('../lib/github');
|
|
25
29
|
const {
|
|
26
30
|
isProtected,
|
|
27
31
|
shouldExclude,
|
|
28
|
-
determineMergeAction,
|
|
29
32
|
loadManifest,
|
|
30
33
|
saveManifest,
|
|
31
34
|
ensureDir
|
|
32
35
|
} = require('../lib/merge');
|
|
36
|
+
const {
|
|
37
|
+
detectPython,
|
|
38
|
+
checkAndReport: checkPythonEnv,
|
|
39
|
+
getInstallInstructions,
|
|
40
|
+
canAutoInstall,
|
|
41
|
+
installPython
|
|
42
|
+
} = require('../lib/python-check');
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Timeline Display
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
const SYMBOLS = {
|
|
49
|
+
pending: chalk.dim('◇'),
|
|
50
|
+
active: chalk.cyan('◈'),
|
|
51
|
+
done: chalk.green('◆'),
|
|
52
|
+
error: chalk.red('◆'),
|
|
53
|
+
skip: chalk.yellow('◇'),
|
|
54
|
+
file: chalk.dim('│ '),
|
|
55
|
+
fileNew: chalk.green('│ + '),
|
|
56
|
+
fileUpdate: chalk.blue('│ ~ '),
|
|
57
|
+
fileSkip: chalk.yellow('│ ! '),
|
|
58
|
+
fileDel: chalk.red('│ - '),
|
|
59
|
+
branch: chalk.dim('├──'),
|
|
60
|
+
branchLast: chalk.dim('└──'),
|
|
61
|
+
indent: ' ',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
class Timeline {
|
|
65
|
+
constructor() {
|
|
66
|
+
this.steps = [];
|
|
67
|
+
this.currentStep = -1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
addStep(name) {
|
|
71
|
+
this.steps.push({ name, status: 'pending', children: [] });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
start(stepIndex) {
|
|
75
|
+
this.currentStep = stepIndex;
|
|
76
|
+
this.steps[stepIndex].status = 'active';
|
|
77
|
+
this.render();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
complete(stepIndex, message = null) {
|
|
81
|
+
this.steps[stepIndex].status = 'done';
|
|
82
|
+
if (message) this.steps[stepIndex].message = message;
|
|
83
|
+
this.render();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
error(stepIndex, message) {
|
|
87
|
+
this.steps[stepIndex].status = 'error';
|
|
88
|
+
this.steps[stepIndex].message = message;
|
|
89
|
+
this.render();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
skip(stepIndex, message) {
|
|
93
|
+
this.steps[stepIndex].status = 'skip';
|
|
94
|
+
this.steps[stepIndex].message = message;
|
|
95
|
+
this.render();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
addChild(stepIndex, text, type = 'info') {
|
|
99
|
+
this.steps[stepIndex].children.push({ text, type });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
render() {
|
|
103
|
+
// Clear and redraw (simple version)
|
|
104
|
+
console.log('');
|
|
105
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
106
|
+
const step = this.steps[i];
|
|
107
|
+
const symbol = SYMBOLS[step.status];
|
|
108
|
+
const num = chalk.dim(`${i + 1}.`);
|
|
109
|
+
const name = step.status === 'active' ? chalk.cyan(step.name) : step.name;
|
|
110
|
+
const msg = step.message ? chalk.dim(` (${step.message})`) : '';
|
|
111
|
+
|
|
112
|
+
console.log(` ${symbol} ${num} ${name}${msg}`);
|
|
113
|
+
|
|
114
|
+
// Show children (files, etc.)
|
|
115
|
+
for (let j = 0; j < step.children.length; j++) {
|
|
116
|
+
const child = step.children[j];
|
|
117
|
+
const isLast = j === step.children.length - 1;
|
|
118
|
+
const branch = isLast ? SYMBOLS.branchLast : SYMBOLS.branch;
|
|
119
|
+
const prefix = child.type === 'new' ? chalk.green('+') :
|
|
120
|
+
child.type === 'update' ? chalk.blue('~') :
|
|
121
|
+
child.type === 'skip' ? chalk.yellow('!') :
|
|
122
|
+
child.type === 'delete' ? chalk.red('-') :
|
|
123
|
+
chalk.dim('•');
|
|
124
|
+
console.log(` ${SYMBOLS.indent}${branch} ${prefix} ${child.text}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log('');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// =============================================================================
|
|
132
|
+
// Utility Functions
|
|
133
|
+
// =============================================================================
|
|
33
134
|
|
|
34
|
-
/**
|
|
35
|
-
* Format date for display
|
|
36
|
-
*/
|
|
37
135
|
function formatDate(isoDate) {
|
|
38
136
|
const d = new Date(isoDate);
|
|
39
137
|
return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
|
40
138
|
}
|
|
41
139
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const filled = Math.round((current / total) * width);
|
|
48
|
-
const empty = width - filled;
|
|
49
|
-
const bar = chalk.green('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
|
|
50
|
-
return `${bar} ${percent}%`;
|
|
140
|
+
function getPlatform() {
|
|
141
|
+
const platform = os.platform();
|
|
142
|
+
if (platform === 'win32') return 'windows';
|
|
143
|
+
if (platform === 'darwin') return 'macos';
|
|
144
|
+
return 'linux';
|
|
51
145
|
}
|
|
52
146
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
147
|
+
function remapPath(filePath) {
|
|
148
|
+
const mappings = [
|
|
149
|
+
{ from: 'src/commands/', to: '.claude/commands/' },
|
|
150
|
+
{ from: 'src/skills/', to: '.claude/skills/' },
|
|
151
|
+
{ from: 'core/', to: '.claude/skills/csk/core/' },
|
|
152
|
+
{ from: 'domains/', to: '.claude/skills/csk/domains/' },
|
|
153
|
+
{ from: 'industries/', to: '.claude/skills/csk/industries/' }
|
|
154
|
+
];
|
|
59
155
|
|
|
60
|
-
|
|
61
|
-
|
|
156
|
+
for (const { from, to } of mappings) {
|
|
157
|
+
if (filePath.startsWith(from)) {
|
|
158
|
+
return filePath.replace(from, to);
|
|
159
|
+
}
|
|
62
160
|
}
|
|
161
|
+
return filePath;
|
|
162
|
+
}
|
|
63
163
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const venvPath = path.join(libPythonDir, '.venv');
|
|
164
|
+
async function extractZip(zipPath, destDir) {
|
|
165
|
+
const platform = getPlatform();
|
|
67
166
|
|
|
68
167
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
spinner.warn('Python 3 not found, skipping venv setup');
|
|
76
|
-
return { success: true, skipped: true, reason: 'python3 not found' };
|
|
168
|
+
if (platform === 'windows') {
|
|
169
|
+
// Use PowerShell on Windows
|
|
170
|
+
execSync(`powershell -command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'pipe' });
|
|
171
|
+
} else {
|
|
172
|
+
// Use unzip on Unix/macOS
|
|
173
|
+
execSync(`unzip -o -q "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
|
|
77
174
|
}
|
|
175
|
+
return { success: true };
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return { success: false, error: error.message };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
78
180
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
181
|
+
function scanDirectory(dir, basePath = '') {
|
|
182
|
+
const files = [];
|
|
183
|
+
if (!fs.existsSync(dir)) return files;
|
|
184
|
+
|
|
185
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
const fullPath = path.join(dir, entry.name);
|
|
188
|
+
const relativePath = path.join(basePath, entry.name);
|
|
88
189
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
190
|
+
if (entry.isDirectory()) {
|
|
191
|
+
files.push(...scanDirectory(fullPath, relativePath));
|
|
192
|
+
} else {
|
|
193
|
+
files.push(relativePath);
|
|
93
194
|
}
|
|
195
|
+
}
|
|
196
|
+
return files;
|
|
197
|
+
}
|
|
94
198
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.split('\n')
|
|
99
|
-
.map(line => line.split('#')[0].trim()) // Remove inline comments
|
|
100
|
-
.filter(line => line); // Remove empty lines
|
|
199
|
+
function compareFiles(sourceDir, targetDir, manifest) {
|
|
200
|
+
const sourceFiles = scanDirectory(sourceDir);
|
|
201
|
+
const changes = { new: [], update: [], skip: [], protected: [] };
|
|
101
202
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return { success: true, version: pythonVersion };
|
|
105
|
-
}
|
|
203
|
+
for (const file of sourceFiles) {
|
|
204
|
+
if (shouldExclude(file)) continue;
|
|
106
205
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
execSync(`"${pipPath}" install --upgrade pip -q`, { stdio: 'pipe', cwd: libPythonDir });
|
|
111
|
-
} catch { /* ignore pip upgrade errors */ }
|
|
206
|
+
const targetPath = remapPath(file);
|
|
207
|
+
const fullTargetPath = path.join(targetDir, targetPath);
|
|
112
208
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (toInstall.length === 0) {
|
|
130
|
-
spinner.succeed(`Python ready: ${requirements.length} packages already installed`);
|
|
131
|
-
return { success: true, version: pythonVersion };
|
|
209
|
+
if (isProtected(targetPath)) {
|
|
210
|
+
changes.protected.push({ source: file, target: targetPath });
|
|
211
|
+
} else if (!fs.existsSync(fullTargetPath)) {
|
|
212
|
+
changes.new.push({ source: file, target: targetPath });
|
|
213
|
+
} else {
|
|
214
|
+
// Check if file changed
|
|
215
|
+
const sourceContent = fs.readFileSync(path.join(sourceDir, file));
|
|
216
|
+
const sourceHash = crypto.createHash('md5').update(sourceContent).digest('hex');
|
|
217
|
+
const targetHash = manifest.files?.[targetPath];
|
|
218
|
+
|
|
219
|
+
if (sourceHash !== targetHash) {
|
|
220
|
+
changes.update.push({ source: file, target: targetPath, hash: sourceHash });
|
|
221
|
+
} else {
|
|
222
|
+
changes.skip.push({ source: file, target: targetPath });
|
|
223
|
+
}
|
|
132
224
|
}
|
|
225
|
+
}
|
|
133
226
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const skipped = requirements.length - toInstall.length;
|
|
137
|
-
console.log(chalk.dim(`\n Installing ${toInstall.length} packages (${skipped} already installed):\n`));
|
|
227
|
+
return changes;
|
|
228
|
+
}
|
|
138
229
|
|
|
139
|
-
|
|
140
|
-
|
|
230
|
+
function formatFileList(files, maxShow = 10) {
|
|
231
|
+
const lines = [];
|
|
232
|
+
const showFiles = files.slice(0, maxShow);
|
|
141
233
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const progress = `[${i + 1}/${toInstall.length}]`;
|
|
234
|
+
for (const file of showFiles) {
|
|
235
|
+
lines.push(file.target || file);
|
|
236
|
+
}
|
|
146
237
|
|
|
147
|
-
|
|
238
|
+
if (files.length > maxShow) {
|
|
239
|
+
lines.push(chalk.dim(`... and ${files.length - maxShow} more`));
|
|
240
|
+
}
|
|
148
241
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
stdio: 'pipe',
|
|
152
|
-
cwd: libPythonDir,
|
|
153
|
-
timeout: 120000 // 2 min timeout per package
|
|
154
|
-
});
|
|
155
|
-
console.log(chalk.green('✓'));
|
|
156
|
-
installed.push(pkgName);
|
|
157
|
-
} catch (err) {
|
|
158
|
-
console.log(chalk.red('✗'));
|
|
159
|
-
const errMsg = err.stderr ? err.stderr.toString().split('\n')[0] : err.message;
|
|
160
|
-
console.log(chalk.dim(` ${errMsg.slice(0, 60)}`));
|
|
161
|
-
failed.push(pkgName);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
242
|
+
return lines;
|
|
243
|
+
}
|
|
164
244
|
|
|
165
|
-
|
|
245
|
+
// =============================================================================
|
|
246
|
+
// Package Installation
|
|
247
|
+
// =============================================================================
|
|
166
248
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return { success: true, version: pythonVersion };
|
|
172
|
-
} else if (installed.length > 0 || skipped > 0) {
|
|
173
|
-
spinner.warn(`Partial: ${totalOk} ok, ${failed.length} failed`);
|
|
174
|
-
console.log(chalk.yellow(` Failed: ${failed.join(', ')}`));
|
|
175
|
-
console.log(chalk.dim(` Run manually: ${pipPath} install <package>\n`));
|
|
176
|
-
return { success: true, partial: true, version: pythonVersion, failed };
|
|
177
|
-
} else {
|
|
178
|
-
spinner.fail('All packages failed');
|
|
179
|
-
return { success: false, error: 'All packages failed to install' };
|
|
180
|
-
}
|
|
181
|
-
} catch (error) {
|
|
182
|
-
spinner.warn(`Python setup failed: ${error.message}`);
|
|
183
|
-
return { success: false, error: error.message };
|
|
249
|
+
function checkPythonPackages(libPythonDir) {
|
|
250
|
+
const requirementsFile = path.join(libPythonDir, 'requirements.txt');
|
|
251
|
+
if (!fs.existsSync(requirementsFile)) {
|
|
252
|
+
return { required: [], installed: [], toInstall: [], toUpdate: [] };
|
|
184
253
|
}
|
|
185
|
-
}
|
|
186
254
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const mappings = [
|
|
193
|
-
{ from: 'src/commands/', to: '.claude/commands/' },
|
|
194
|
-
{ from: 'core/', to: '.claude/skills/csk/core/' },
|
|
195
|
-
{ from: 'domains/', to: '.claude/skills/csk/domains/' },
|
|
196
|
-
{ from: 'industries/', to: '.claude/skills/csk/industries/' }
|
|
197
|
-
];
|
|
255
|
+
const platform = getPlatform();
|
|
256
|
+
const venvPath = path.join(libPythonDir, '.venv');
|
|
257
|
+
const pipPath = platform === 'windows'
|
|
258
|
+
? path.join(venvPath, 'Scripts', 'pip.exe')
|
|
259
|
+
: path.join(venvPath, 'bin', 'pip');
|
|
198
260
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
261
|
+
// Parse requirements
|
|
262
|
+
const required = fs.readFileSync(requirementsFile, 'utf8')
|
|
263
|
+
.split('\n')
|
|
264
|
+
.map(line => line.split('#')[0].trim())
|
|
265
|
+
.filter(line => line && !line.startsWith('#'));
|
|
266
|
+
|
|
267
|
+
// Get installed packages
|
|
268
|
+
let installed = [];
|
|
269
|
+
let installedVersions = {};
|
|
270
|
+
|
|
271
|
+
if (fs.existsSync(venvPath)) {
|
|
272
|
+
try {
|
|
273
|
+
const freeze = execSync(`"${pipPath}" freeze`, { stdio: 'pipe' }).toString();
|
|
274
|
+
freeze.split('\n').forEach(line => {
|
|
275
|
+
const match = line.match(/^([^=]+)==(.+)$/);
|
|
276
|
+
if (match) {
|
|
277
|
+
installed.push(match[1].toLowerCase());
|
|
278
|
+
installedVersions[match[1].toLowerCase()] = match[2];
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
} catch {}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Compare
|
|
285
|
+
const toInstall = [];
|
|
286
|
+
const toUpdate = [];
|
|
287
|
+
const alreadyOk = [];
|
|
288
|
+
|
|
289
|
+
for (const req of required) {
|
|
290
|
+
const pkgName = req.split(/[>=<\[]/)[0].toLowerCase();
|
|
291
|
+
const reqVersion = req.match(/[>=<]+(.+)/)?.[1];
|
|
292
|
+
|
|
293
|
+
if (!installed.includes(pkgName)) {
|
|
294
|
+
toInstall.push({ name: pkgName, required: req });
|
|
295
|
+
} else if (reqVersion && installedVersions[pkgName] !== reqVersion) {
|
|
296
|
+
toUpdate.push({
|
|
297
|
+
name: pkgName,
|
|
298
|
+
current: installedVersions[pkgName],
|
|
299
|
+
required: reqVersion
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
alreadyOk.push({ name: pkgName, version: installedVersions[pkgName] });
|
|
202
303
|
}
|
|
203
304
|
}
|
|
204
305
|
|
|
205
|
-
|
|
206
|
-
return filePath;
|
|
306
|
+
return { required, installed: alreadyOk, toInstall, toUpdate };
|
|
207
307
|
}
|
|
208
308
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
309
|
+
async function installPackages(libPythonDir, packages, timeline, stepIndex, pythonCmd = 'python3') {
|
|
310
|
+
const platform = getPlatform();
|
|
311
|
+
const venvPath = path.join(libPythonDir, '.venv');
|
|
312
|
+
const pipPath = platform === 'windows'
|
|
313
|
+
? path.join(venvPath, 'Scripts', 'pip.exe')
|
|
314
|
+
: path.join(venvPath, 'bin', 'pip');
|
|
214
315
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
316
|
+
// Create venv if not exists
|
|
317
|
+
if (!fs.existsSync(venvPath)) {
|
|
318
|
+
try {
|
|
319
|
+
execSync(`${pythonCmd} -m venv "${venvPath}"`, { stdio: 'pipe', cwd: libPythonDir });
|
|
320
|
+
timeline.addChild(stepIndex, 'Created virtual environment', 'new');
|
|
321
|
+
} catch (error) {
|
|
322
|
+
timeline.addChild(stepIndex, `Failed to create venv: ${error.message}`, 'skip');
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
218
326
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
327
|
+
// Install packages
|
|
328
|
+
let success = true;
|
|
329
|
+
for (const pkg of packages) {
|
|
330
|
+
const pkgSpec = pkg.required || pkg.name;
|
|
331
|
+
try {
|
|
332
|
+
execSync(`"${pipPath}" install "${pkgSpec}" -q`, {
|
|
333
|
+
stdio: 'pipe',
|
|
334
|
+
cwd: libPythonDir,
|
|
335
|
+
timeout: 120000
|
|
336
|
+
});
|
|
337
|
+
timeline.addChild(stepIndex, `${pkg.name}`, 'new');
|
|
338
|
+
} catch (error) {
|
|
339
|
+
timeline.addChild(stepIndex, `${pkg.name} (failed)`, 'skip');
|
|
340
|
+
success = false;
|
|
224
341
|
}
|
|
225
342
|
}
|
|
343
|
+
|
|
344
|
+
return success;
|
|
226
345
|
}
|
|
227
346
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
347
|
+
// =============================================================================
|
|
348
|
+
// Main Init Command
|
|
349
|
+
// =============================================================================
|
|
350
|
+
|
|
231
351
|
async function initCommand(options) {
|
|
232
352
|
const projectDir = process.cwd();
|
|
353
|
+
const platform = getPlatform();
|
|
233
354
|
|
|
234
355
|
console.log(chalk.cyan('\n Content Suite Kit - Init\n'));
|
|
356
|
+
console.log(chalk.dim(` Platform: ${platform}\n`));
|
|
235
357
|
|
|
236
|
-
//
|
|
237
|
-
const
|
|
358
|
+
// Initialize timeline
|
|
359
|
+
const timeline = new Timeline();
|
|
360
|
+
timeline.addStep('Authenticate');
|
|
361
|
+
timeline.addStep('Download package');
|
|
362
|
+
timeline.addStep('Extract files');
|
|
363
|
+
timeline.addStep('Scan changes');
|
|
364
|
+
timeline.addStep('Confirm changes');
|
|
365
|
+
timeline.addStep('Apply changes');
|
|
366
|
+
timeline.addStep('Setup dependencies');
|
|
367
|
+
|
|
368
|
+
// Step 1: Authenticate
|
|
369
|
+
timeline.start(0);
|
|
238
370
|
|
|
239
371
|
const token = await getToken();
|
|
240
372
|
if (!token) {
|
|
241
|
-
|
|
373
|
+
timeline.error(0, 'Not authenticated');
|
|
242
374
|
console.log(chalk.dim('\n Run `cskit auth --login` first.\n'));
|
|
243
375
|
process.exit(1);
|
|
244
376
|
}
|
|
245
377
|
|
|
246
378
|
const { valid, error } = await verifyAccess(token);
|
|
247
379
|
if (!valid) {
|
|
248
|
-
|
|
249
|
-
console.log(chalk.red(`\n ${error}`));
|
|
380
|
+
timeline.error(0, error);
|
|
250
381
|
process.exit(1);
|
|
251
382
|
}
|
|
252
383
|
|
|
253
|
-
|
|
384
|
+
timeline.complete(0, 'OK');
|
|
254
385
|
|
|
255
386
|
// Check existing installation
|
|
256
387
|
const manifest = loadManifest(projectDir);
|
|
257
388
|
const isUpdate = manifest.version !== null;
|
|
258
389
|
|
|
259
390
|
if (isUpdate) {
|
|
260
|
-
|
|
261
|
-
console.log(chalk.dim(`\n Current: v${currentVer} (${formatDate(manifest.installedAt)})\n`));
|
|
391
|
+
console.log(chalk.dim(` Current: ${manifest.version} (${formatDate(manifest.installedAt)})\n`));
|
|
262
392
|
}
|
|
263
393
|
|
|
264
|
-
//
|
|
265
|
-
spinner.start('Fetching available versions...');
|
|
266
|
-
|
|
394
|
+
// Get available versions
|
|
267
395
|
const releases = await getAllReleases(token);
|
|
268
396
|
const latest = await getLatestRelease(token);
|
|
269
397
|
|
|
270
|
-
|
|
271
|
-
spinner.info('No releases found, using main branch');
|
|
272
|
-
} else {
|
|
273
|
-
spinner.succeed(`Found ${releases.length} release(s)`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Version selection (show max 5 recent releases)
|
|
398
|
+
// Version selection
|
|
277
399
|
let selectedVersion = 'main';
|
|
278
|
-
const maxVersions = 5;
|
|
279
400
|
|
|
280
401
|
if (releases.length > 0 && !options.latest) {
|
|
281
|
-
const recentReleases = releases.slice(0,
|
|
402
|
+
const recentReleases = releases.slice(0, 5);
|
|
282
403
|
const choices = recentReleases.map((r, i) => ({
|
|
283
404
|
name: `${r.tag}${i === 0 ? chalk.green(' (latest)') : ''} - ${formatDate(r.published)}`,
|
|
284
405
|
value: r.tag
|
|
285
406
|
}));
|
|
286
|
-
|
|
287
|
-
choices.push({ name: chalk.dim('main branch (development)'), value: 'main' });
|
|
407
|
+
choices.push({ name: chalk.dim('main branch (dev)'), value: 'main' });
|
|
288
408
|
|
|
289
409
|
const answer = await inquirer.prompt([{
|
|
290
410
|
type: 'list',
|
|
291
411
|
name: 'version',
|
|
292
|
-
message: 'Select version
|
|
412
|
+
message: 'Select version:',
|
|
293
413
|
choices,
|
|
294
414
|
default: releases[0]?.tag || 'main'
|
|
295
415
|
}]);
|
|
296
|
-
|
|
297
416
|
selectedVersion = answer.version;
|
|
298
417
|
} else if (latest) {
|
|
299
418
|
selectedVersion = latest.tag;
|
|
@@ -301,110 +420,320 @@ async function initCommand(options) {
|
|
|
301
420
|
|
|
302
421
|
console.log(chalk.dim(`\n Installing: ${selectedVersion}\n`));
|
|
303
422
|
|
|
304
|
-
//
|
|
305
|
-
|
|
423
|
+
// Step 2: Download zip
|
|
424
|
+
timeline.start(1);
|
|
425
|
+
|
|
426
|
+
const tempDir = path.join(os.tmpdir(), `csk-${Date.now()}`);
|
|
427
|
+
const zipPath = path.join(tempDir, 'csk.zip');
|
|
428
|
+
|
|
429
|
+
ensureDir(tempDir);
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
const zipBuffer = await downloadZipFromRef(token, selectedVersion);
|
|
433
|
+
fs.writeFileSync(zipPath, zipBuffer);
|
|
434
|
+
const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
|
|
435
|
+
timeline.complete(1, `${sizeMB} MB`);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
timeline.error(1, error.message);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Step 3: Extract
|
|
442
|
+
timeline.start(2);
|
|
443
|
+
|
|
444
|
+
const extractDir = path.join(tempDir, 'extracted');
|
|
445
|
+
const result = await extractZip(zipPath, extractDir);
|
|
446
|
+
|
|
447
|
+
if (!result.success) {
|
|
448
|
+
timeline.error(2, result.error);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Find the extracted folder (GitHub adds repo-branch prefix)
|
|
453
|
+
const extractedFolders = fs.readdirSync(extractDir);
|
|
454
|
+
const sourceDir = path.join(extractDir, extractedFolders[0]);
|
|
455
|
+
|
|
456
|
+
timeline.complete(2, `${extractedFolders[0]}`);
|
|
306
457
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
item.type === 'blob' && !shouldExclude(item.path)
|
|
310
|
-
);
|
|
458
|
+
// Step 4: Scan changes
|
|
459
|
+
timeline.start(3);
|
|
311
460
|
|
|
312
|
-
|
|
461
|
+
const changes = compareFiles(sourceDir, projectDir, manifest);
|
|
313
462
|
|
|
314
|
-
|
|
315
|
-
|
|
463
|
+
timeline.addChild(3, `${changes.new.length} new files`, 'new');
|
|
464
|
+
timeline.addChild(3, `${changes.update.length} updates`, 'update');
|
|
465
|
+
timeline.addChild(3, `${changes.skip.length} unchanged`, 'info');
|
|
466
|
+
|
|
467
|
+
if (changes.protected.length > 0) {
|
|
468
|
+
timeline.addChild(3, `${changes.protected.length} protected (skip)`, 'skip');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
timeline.complete(3, `${changes.new.length + changes.update.length} changes`);
|
|
472
|
+
|
|
473
|
+
// Step 5: Confirm changes
|
|
474
|
+
timeline.start(4);
|
|
475
|
+
|
|
476
|
+
// Show files that will be changed
|
|
477
|
+
if (changes.update.length > 0) {
|
|
478
|
+
console.log(chalk.yellow('\n Files to be updated (will overwrite):\n'));
|
|
479
|
+
for (const file of changes.update.slice(0, 15)) {
|
|
480
|
+
console.log(chalk.yellow(` ! ${file.target}`));
|
|
481
|
+
}
|
|
482
|
+
if (changes.update.length > 15) {
|
|
483
|
+
console.log(chalk.dim(` ... and ${changes.update.length - 15} more\n`));
|
|
484
|
+
}
|
|
485
|
+
console.log('');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (changes.new.length > 0) {
|
|
489
|
+
console.log(chalk.green(' New files to be created:\n'));
|
|
490
|
+
for (const file of changes.new.slice(0, 10)) {
|
|
491
|
+
console.log(chalk.green(` + ${file.target}`));
|
|
492
|
+
}
|
|
493
|
+
if (changes.new.length > 10) {
|
|
494
|
+
console.log(chalk.dim(` ... and ${changes.new.length - 10} more\n`));
|
|
495
|
+
}
|
|
496
|
+
console.log('');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Confirm
|
|
500
|
+
if (changes.update.length > 0 && !options.force) {
|
|
501
|
+
const { confirm } = await inquirer.prompt([{
|
|
502
|
+
type: 'confirm',
|
|
503
|
+
name: 'confirm',
|
|
504
|
+
message: `Overwrite ${changes.update.length} files?`,
|
|
505
|
+
default: true
|
|
506
|
+
}]);
|
|
507
|
+
|
|
508
|
+
if (!confirm) {
|
|
509
|
+
timeline.skip(4, 'User cancelled');
|
|
510
|
+
timeline.skip(5, 'Skipped');
|
|
511
|
+
timeline.skip(6, 'Skipped');
|
|
512
|
+
|
|
513
|
+
// Cleanup
|
|
514
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
515
|
+
|
|
516
|
+
console.log(chalk.yellow('\n Installation cancelled.\n'));
|
|
517
|
+
process.exit(0);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
timeline.complete(4, 'Confirmed');
|
|
522
|
+
|
|
523
|
+
// Step 6: Apply changes
|
|
524
|
+
timeline.start(5);
|
|
316
525
|
|
|
317
|
-
const stats = { created: 0, updated: 0, skipped: 0, protected: 0, userModified: 0 };
|
|
318
526
|
const newManifest = {
|
|
319
527
|
files: {},
|
|
320
528
|
version: selectedVersion,
|
|
321
529
|
installedAt: new Date().toISOString()
|
|
322
530
|
};
|
|
323
531
|
|
|
324
|
-
|
|
325
|
-
const file = files[i];
|
|
326
|
-
const relativePath = remapPath(file.path);
|
|
327
|
-
const targetPath = path.join(projectDir, relativePath);
|
|
532
|
+
let applied = 0;
|
|
328
533
|
|
|
329
|
-
|
|
330
|
-
|
|
534
|
+
// Apply new files
|
|
535
|
+
for (const file of changes.new) {
|
|
536
|
+
const sourcePath = path.join(sourceDir, file.source);
|
|
537
|
+
const targetPath = path.join(projectDir, file.target);
|
|
331
538
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
switch (action) {
|
|
341
|
-
case 'create':
|
|
342
|
-
ensureDir(path.dirname(targetPath));
|
|
343
|
-
fs.writeFileSync(targetPath, content);
|
|
344
|
-
stats.created++;
|
|
345
|
-
newManifest.files[relativePath] = contentHash;
|
|
346
|
-
break;
|
|
347
|
-
|
|
348
|
-
case 'update':
|
|
349
|
-
ensureDir(path.dirname(targetPath));
|
|
350
|
-
fs.writeFileSync(targetPath, content);
|
|
351
|
-
stats.updated++;
|
|
352
|
-
newManifest.files[relativePath] = contentHash;
|
|
353
|
-
break;
|
|
354
|
-
|
|
355
|
-
case 'skip':
|
|
356
|
-
if (reason === 'protected') stats.protected++;
|
|
357
|
-
else if (reason === 'user-modified') stats.userModified++;
|
|
358
|
-
else stats.skipped++;
|
|
359
|
-
|
|
360
|
-
if (manifest.files[relativePath]) {
|
|
361
|
-
newManifest.files[relativePath] = manifest.files[relativePath];
|
|
362
|
-
}
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
} catch (err) {
|
|
366
|
-
// Silent fail for individual files
|
|
367
|
-
}
|
|
539
|
+
ensureDir(path.dirname(targetPath));
|
|
540
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
541
|
+
|
|
542
|
+
const content = fs.readFileSync(sourcePath);
|
|
543
|
+
const hash = crypto.createHash('md5').update(content).digest('hex');
|
|
544
|
+
newManifest.files[file.target] = hash;
|
|
545
|
+
applied++;
|
|
368
546
|
}
|
|
369
547
|
|
|
370
|
-
//
|
|
371
|
-
|
|
548
|
+
// Apply updates
|
|
549
|
+
for (const file of changes.update) {
|
|
550
|
+
const sourcePath = path.join(sourceDir, file.source);
|
|
551
|
+
const targetPath = path.join(projectDir, file.target);
|
|
552
|
+
|
|
553
|
+
ensureDir(path.dirname(targetPath));
|
|
554
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
555
|
+
|
|
556
|
+
newManifest.files[file.target] = file.hash;
|
|
557
|
+
applied++;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Keep unchanged files in manifest
|
|
561
|
+
for (const file of changes.skip) {
|
|
562
|
+
if (manifest.files?.[file.target]) {
|
|
563
|
+
newManifest.files[file.target] = manifest.files[file.target];
|
|
564
|
+
}
|
|
565
|
+
}
|
|
372
566
|
|
|
373
567
|
// Save manifest
|
|
374
568
|
saveManifest(projectDir, newManifest);
|
|
375
569
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
570
|
+
timeline.complete(5, `${applied} files`);
|
|
571
|
+
|
|
572
|
+
// Step 7: Setup dependencies
|
|
573
|
+
timeline.start(6);
|
|
574
|
+
|
|
575
|
+
const libPythonDir = path.join(projectDir, 'lib', 'python');
|
|
576
|
+
|
|
577
|
+
// Helper function to install Python packages
|
|
578
|
+
async function handlePythonPackages(pythonCmd) {
|
|
579
|
+
if (!fs.existsSync(libPythonDir)) {
|
|
580
|
+
timeline.skip(6, 'No lib/python folder');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const { installAdvanced } = await inquirer.prompt([{
|
|
585
|
+
type: 'confirm',
|
|
586
|
+
name: 'installAdvanced',
|
|
587
|
+
message: 'Install Python dependencies?',
|
|
588
|
+
default: true
|
|
589
|
+
}]);
|
|
590
|
+
|
|
591
|
+
if (!installAdvanced) {
|
|
592
|
+
timeline.skip(6, 'Skipped by user');
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const pkgStatus = checkPythonPackages(libPythonDir);
|
|
597
|
+
|
|
598
|
+
if (pkgStatus.installed.length > 0) {
|
|
599
|
+
console.log(chalk.dim('\n Already installed:'));
|
|
600
|
+
for (const pkg of pkgStatus.installed.slice(0, 5)) {
|
|
601
|
+
console.log(chalk.dim(` ✓ ${pkg.name} (${pkg.version})`));
|
|
602
|
+
}
|
|
603
|
+
if (pkgStatus.installed.length > 5) {
|
|
604
|
+
console.log(chalk.dim(` ... and ${pkgStatus.installed.length - 5} more\n`));
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const allToInstall = [...pkgStatus.toInstall, ...pkgStatus.toUpdate];
|
|
609
|
+
|
|
610
|
+
if (allToInstall.length === 0) {
|
|
611
|
+
timeline.complete(6, 'All packages ready');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
console.log(chalk.cyan('\n Packages to install/update:\n'));
|
|
616
|
+
for (const pkg of allToInstall) {
|
|
617
|
+
if (pkg.current) {
|
|
618
|
+
console.log(` ${pkg.name}: ${pkg.current} → ${pkg.required}`);
|
|
619
|
+
} else {
|
|
620
|
+
console.log(` ${pkg.name}: ${pkg.required || 'latest'}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
console.log('');
|
|
624
|
+
|
|
625
|
+
const { confirmPkgs } = await inquirer.prompt([{
|
|
626
|
+
type: 'confirm',
|
|
627
|
+
name: 'confirmPkgs',
|
|
628
|
+
message: `Install ${allToInstall.length} packages?`,
|
|
629
|
+
default: true
|
|
630
|
+
}]);
|
|
631
|
+
|
|
632
|
+
if (confirmPkgs) {
|
|
633
|
+
await installPackages(libPythonDir, allToInstall, timeline, 6, pythonCmd);
|
|
634
|
+
timeline.complete(6, `${allToInstall.length} packages`);
|
|
635
|
+
} else {
|
|
636
|
+
timeline.skip(6, 'User skipped');
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Check Python availability first
|
|
641
|
+
let pythonInfo = detectPython();
|
|
642
|
+
let pythonReady = false;
|
|
383
643
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
644
|
+
if (!pythonInfo.found) {
|
|
645
|
+
console.log('');
|
|
646
|
+
console.log(chalk.yellow(' Python not found on this system.'));
|
|
647
|
+
console.log(chalk.dim(' Python is required for:'));
|
|
648
|
+
console.log(chalk.dim(' - Data fetching and analysis'));
|
|
649
|
+
console.log(chalk.dim(' - Chart generation'));
|
|
650
|
+
console.log(chalk.dim(' - MCP server tools'));
|
|
651
|
+
console.log('');
|
|
387
652
|
|
|
388
|
-
|
|
389
|
-
|
|
653
|
+
// Check if we can auto-install
|
|
654
|
+
const autoInstall = canAutoInstall();
|
|
390
655
|
|
|
391
|
-
|
|
656
|
+
if (autoInstall.canInstall) {
|
|
657
|
+
console.log(chalk.cyan(` Auto-install available via ${autoInstall.manager}`));
|
|
658
|
+
if (autoInstall.needsSudo) {
|
|
659
|
+
console.log(chalk.dim(' (requires sudo password)'));
|
|
660
|
+
}
|
|
661
|
+
console.log(chalk.dim(` Command: ${autoInstall.command}`));
|
|
662
|
+
console.log('');
|
|
663
|
+
|
|
664
|
+
const { doInstall } = await inquirer.prompt([{
|
|
665
|
+
type: 'confirm',
|
|
666
|
+
name: 'doInstall',
|
|
667
|
+
message: `Install Python now via ${autoInstall.manager}?`,
|
|
668
|
+
default: true
|
|
669
|
+
}]);
|
|
670
|
+
|
|
671
|
+
if (doInstall) {
|
|
672
|
+
console.log('');
|
|
673
|
+
timeline.addChild(6, `Installing via ${autoInstall.manager}...`, 'update');
|
|
674
|
+
|
|
675
|
+
const result = await installPython((msg) => {
|
|
676
|
+
console.log(chalk.dim(` ${msg}`));
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (result.success) {
|
|
680
|
+
pythonInfo = detectPython();
|
|
681
|
+
timeline.addChild(6, `Python ${result.version} installed`, 'new');
|
|
682
|
+
pythonReady = true;
|
|
683
|
+
} else {
|
|
684
|
+
console.log('');
|
|
685
|
+
console.log(chalk.red(` ${result.error}`));
|
|
686
|
+
console.log(chalk.dim(' You may need to restart your terminal after installation.'));
|
|
687
|
+
console.log('');
|
|
688
|
+
timeline.skip(6, 'Installation failed');
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
timeline.skip(6, 'User declined install');
|
|
692
|
+
}
|
|
693
|
+
} else {
|
|
694
|
+
// No package manager - show manual instructions
|
|
695
|
+
const instructions = getInstallInstructions();
|
|
696
|
+
console.log(chalk.bold(` Install via ${instructions.method}:\n`));
|
|
697
|
+
for (const cmd of instructions.commands.slice(0, 4)) {
|
|
698
|
+
if (cmd.startsWith('#')) {
|
|
699
|
+
console.log(chalk.dim(` ${cmd}`));
|
|
700
|
+
} else if (cmd !== '') {
|
|
701
|
+
console.log(chalk.cyan(` ${cmd}`));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
console.log('');
|
|
705
|
+
timeline.skip(6, 'Python not installed');
|
|
706
|
+
}
|
|
707
|
+
} else if (!pythonInfo.meetsMinimum) {
|
|
708
|
+
console.log('');
|
|
709
|
+
console.log(chalk.yellow(` Python ${pythonInfo.version} found, but 3.8+ required.`));
|
|
710
|
+
console.log(chalk.dim(' Please upgrade Python to use advanced features.'));
|
|
711
|
+
console.log('');
|
|
712
|
+
timeline.skip(6, `Python ${pythonInfo.version} too old`);
|
|
713
|
+
} else {
|
|
714
|
+
pythonReady = true;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Install packages if Python is ready
|
|
718
|
+
if (pythonReady && pythonInfo.meetsMinimum) {
|
|
719
|
+
timeline.addChild(6, `Python ${pythonInfo.version} (${pythonInfo.command})`, 'update');
|
|
720
|
+
await handlePythonPackages(pythonInfo.command);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Cleanup temp files
|
|
724
|
+
try {
|
|
725
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
726
|
+
} catch {}
|
|
727
|
+
|
|
728
|
+
// Final message
|
|
392
729
|
const ver = selectedVersion.replace(/^v/, '');
|
|
393
730
|
console.log(chalk.green(`\n ✓ CSK v${ver} ${isUpdate ? 'updated' : 'installed'} successfully!\n`));
|
|
394
731
|
|
|
395
|
-
// Quick start guide
|
|
396
732
|
console.log(chalk.cyan(' Quick Start\n'));
|
|
397
|
-
console.log(chalk.dim(' 1. Open Claude Code
|
|
733
|
+
console.log(chalk.dim(' 1. Open Claude Code:'));
|
|
398
734
|
console.log(` ${chalk.white('claude')}\n`);
|
|
399
|
-
console.log(chalk.dim(' 2. Start with CSK
|
|
735
|
+
console.log(chalk.dim(' 2. Start with CSK:'));
|
|
400
736
|
console.log(` ${chalk.white('/csk')}\n`);
|
|
401
|
-
console.log(chalk.dim(' 3. Or explore available skills:'));
|
|
402
|
-
console.log(` ${chalk.white('./cli/csk list')}\n`);
|
|
403
|
-
|
|
404
|
-
if (pythonResult.skipped && pythonResult.reason) {
|
|
405
|
-
console.log(chalk.dim(` Note: ${pythonResult.reason}`));
|
|
406
|
-
console.log(chalk.dim(' Run manually: cd lib/python && python3 -m venv .venv && .venv/bin/pip install -r requirements.txt\n'));
|
|
407
|
-
}
|
|
408
737
|
}
|
|
409
738
|
|
|
410
739
|
module.exports = initCommand;
|