komodo-cli 2.8.0 → 2.10.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/README.md +26 -0
- package/dist/chunk-BK2YSCS2.js +6784 -0
- package/dist/{chunk-GGBZF72M.js → chunk-EU4PZLPQ.js} +1598 -4276
- package/dist/index.js +353 -189
- package/dist/mcp/index.js +5906 -0
- package/dist/{server-JOKWCY3W.js → server-G6XWHEZX.js} +2276 -10946
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Komodo,
|
|
4
4
|
KomodoChat,
|
|
5
5
|
TEMPLATES,
|
|
6
|
+
analyzeCodebase,
|
|
6
7
|
analyzeEnvironment,
|
|
7
8
|
analyzeHealth,
|
|
8
9
|
analyzeOptimizations,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
diffPackages,
|
|
15
16
|
executeRepair,
|
|
16
17
|
explainPackage,
|
|
18
|
+
exportEnvironment,
|
|
17
19
|
formatDiff,
|
|
18
20
|
formatDoctorReport,
|
|
19
21
|
formatExplanation,
|
|
@@ -21,17 +23,22 @@ import {
|
|
|
21
23
|
formatOptimizationReport,
|
|
22
24
|
formatRepoAnalysis,
|
|
23
25
|
formatTemplateList,
|
|
26
|
+
generateHashSnapshot,
|
|
24
27
|
generateOptimizationScript,
|
|
28
|
+
getCodebaseSummary,
|
|
25
29
|
getInstallCommandsForTemplate,
|
|
26
30
|
getInstalledPackages,
|
|
27
31
|
getTemplateById,
|
|
32
|
+
importEnvironment,
|
|
33
|
+
loadAndVerifySnapshot,
|
|
28
34
|
loadState,
|
|
29
35
|
parseGitHubUrl,
|
|
30
36
|
resolveConflicts,
|
|
31
37
|
runDoctor,
|
|
38
|
+
saveSnapshot,
|
|
32
39
|
searchTemplates,
|
|
33
40
|
visualizeTree
|
|
34
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-EU4PZLPQ.js";
|
|
35
42
|
|
|
36
43
|
// src/index.ts
|
|
37
44
|
import { Command } from "commander";
|
|
@@ -40,6 +47,8 @@ import ora from "ora";
|
|
|
40
47
|
import boxen from "boxen";
|
|
41
48
|
import * as readline from "readline";
|
|
42
49
|
import { createRequire } from "module";
|
|
50
|
+
import os from "os";
|
|
51
|
+
import { writeFile, readFile } from "fs/promises";
|
|
43
52
|
var require2 = createRequire(import.meta.url);
|
|
44
53
|
var packageJson = require2("../package.json");
|
|
45
54
|
var CEREBRAS_API_KEY = "csk-m4vcnx94p854xmvnhxx38chwmxxwtffpnymk2ewexktk3962";
|
|
@@ -82,9 +91,9 @@ function printBanner() {
|
|
|
82
91
|
console.log(chalk.hex("#78ff78").dim(" Just say what you want to build."));
|
|
83
92
|
console.log();
|
|
84
93
|
}
|
|
85
|
-
function formatOs(
|
|
94
|
+
function formatOs(os2) {
|
|
86
95
|
const names = { darwin: "Mac", linux: "Linux", windows: "Windows" };
|
|
87
|
-
return names[
|
|
96
|
+
return names[os2] ?? os2;
|
|
88
97
|
}
|
|
89
98
|
function formatGpu(hardware) {
|
|
90
99
|
if (hardware.gpu === "apple-silicon") {
|
|
@@ -96,6 +105,8 @@ function formatGpu(hardware) {
|
|
|
96
105
|
}
|
|
97
106
|
return "CPU";
|
|
98
107
|
}
|
|
108
|
+
var cachedCodebaseSummary;
|
|
109
|
+
var codebaseAnalyzed = false;
|
|
99
110
|
async function updateChatContext(projectPath) {
|
|
100
111
|
const hardware = komodo.getHardware();
|
|
101
112
|
const state = await loadState(projectPath);
|
|
@@ -115,6 +126,15 @@ async function updateChatContext(projectPath) {
|
|
|
115
126
|
healthIssues = health.issues;
|
|
116
127
|
}
|
|
117
128
|
}
|
|
129
|
+
if (!codebaseAnalyzed) {
|
|
130
|
+
try {
|
|
131
|
+
const project = await analyzeCodebase(projectPath, { maxFiles: 300 });
|
|
132
|
+
cachedCodebaseSummary = getCodebaseSummary(project);
|
|
133
|
+
codebaseAnalyzed = true;
|
|
134
|
+
} catch {
|
|
135
|
+
codebaseAnalyzed = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
118
138
|
const context = {
|
|
119
139
|
hardware,
|
|
120
140
|
runtime,
|
|
@@ -122,7 +142,8 @@ async function updateChatContext(projectPath) {
|
|
|
122
142
|
conflicts,
|
|
123
143
|
healthIssues,
|
|
124
144
|
projectPath,
|
|
125
|
-
environmentName
|
|
145
|
+
environmentName,
|
|
146
|
+
codebaseSummary: cachedCodebaseSummary
|
|
126
147
|
};
|
|
127
148
|
chat.setContext(context);
|
|
128
149
|
return { installedPackages, conflicts, healthIssues, runtime, environmentName };
|
|
@@ -131,192 +152,158 @@ function stripMarkdown(text) {
|
|
|
131
152
|
return text.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/_([^_]+)_/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^\s*[-*+]\s+/gm, " - ").replace(/^\s*\d+\.\s+/gm, (m) => m).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\n{3,}/g, "\n\n").trim();
|
|
132
153
|
}
|
|
133
154
|
async function startInteractiveMode(projectPath) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
155
|
+
printBanner();
|
|
156
|
+
const hardware = komodo.getHardware();
|
|
157
|
+
console.log(chalk.hex("#b4ffb4").dim(` ${formatOs(hardware.os)} \xB7 ${formatGpu(hardware)} \xB7 ${hardware.totalMemoryGb}GB memory`));
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(chalk.dim(" Type anything naturally. Komodo understands what you need."));
|
|
160
|
+
console.log(chalk.dim(` Try: "set up a venv for pytorch" or "what's installed?"`));
|
|
161
|
+
console.log(chalk.dim(" Type ") + chalk.hex("#d2ffd2")("help") + chalk.dim(" for commands, ") + chalk.hex("#d2ffd2")("exit") + chalk.dim(" to quit."));
|
|
162
|
+
console.log();
|
|
163
|
+
await updateChatContext(projectPath);
|
|
164
|
+
process.stdin.resume();
|
|
165
|
+
process.stdin.setEncoding("utf8");
|
|
166
|
+
const rl = readline.createInterface({
|
|
167
|
+
input: process.stdin,
|
|
168
|
+
output: process.stdout,
|
|
169
|
+
terminal: process.stdin.isTTY ?? false
|
|
170
|
+
});
|
|
171
|
+
const showPrompt = () => {
|
|
172
|
+
process.stdout.write(chalk.hex("#5aff5a")("\u276F "));
|
|
173
|
+
};
|
|
174
|
+
const askQuestion = () => {
|
|
175
|
+
rl.once("line", (input) => {
|
|
176
|
+
handleInput(input).then(() => {
|
|
177
|
+
askQuestion();
|
|
178
|
+
}).catch((err) => {
|
|
179
|
+
console.error(chalk.red("\nError:"), err instanceof Error ? err.message : String(err));
|
|
180
|
+
console.log();
|
|
181
|
+
showPrompt();
|
|
182
|
+
askQuestion();
|
|
183
|
+
});
|
|
147
184
|
});
|
|
148
|
-
|
|
185
|
+
showPrompt();
|
|
186
|
+
};
|
|
187
|
+
const handleInput = async (input) => {
|
|
188
|
+
const trimmed = input.trim();
|
|
189
|
+
if (!trimmed) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (trimmed === "exit" || trimmed === "quit" || trimmed === "q") {
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(chalk.hex("#a5ffa5")(" See you next time!"));
|
|
195
|
+
console.log();
|
|
149
196
|
process.exit(0);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
console.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
await handleUseTemplate(templateId, projectPath);
|
|
248
|
-
prompt();
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (trimmed.startsWith("explain ")) {
|
|
252
|
-
const packageName = trimmed.slice(8).trim();
|
|
253
|
-
await handleExplain(packageName);
|
|
254
|
-
prompt();
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (trimmed.startsWith("clone ") || trimmed.startsWith("setup ")) {
|
|
258
|
-
const url = trimmed.replace(/^(clone|setup)\s+/, "").trim();
|
|
259
|
-
await handleClone(url);
|
|
260
|
-
prompt();
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (trimmed === "insights" || trimmed === "analytics") {
|
|
264
|
-
await handleInsights(projectPath);
|
|
265
|
-
prompt();
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (trimmed === "ui") {
|
|
269
|
-
await handleUI(projectPath);
|
|
270
|
-
prompt();
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const spinner = ora(chalk.hex("#b4ffb4")("Thinking...")).start();
|
|
274
|
-
try {
|
|
275
|
-
await updateChatContext(projectPath);
|
|
276
|
-
const response = await chat.chat(trimmed);
|
|
277
|
-
spinner.stop();
|
|
278
|
-
const cleanMessage = stripMarkdown(response.message);
|
|
279
|
-
console.log();
|
|
280
|
-
console.log(chalk.hex("#b4ffb4")(cleanMessage));
|
|
281
|
-
console.log();
|
|
282
|
-
if (response.packages) {
|
|
283
|
-
const hasActions = response.packages.toInstall && response.packages.toInstall.length > 0 || response.packages.toRemove && response.packages.toRemove.length > 0 || response.packages.toUpdate && response.packages.toUpdate.length > 0;
|
|
284
|
-
if (hasActions) {
|
|
285
|
-
await executePackageActions(response.packages, projectPath);
|
|
286
|
-
await updateChatContext(projectPath);
|
|
287
|
-
}
|
|
197
|
+
}
|
|
198
|
+
if (trimmed === "clear" || trimmed === "cls") {
|
|
199
|
+
console.clear();
|
|
200
|
+
printBanner();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (trimmed === "help" || trimmed === "?") {
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(chalk.hex("#a5ffa5")(" Just type what you want in plain English:"));
|
|
206
|
+
console.log(chalk.dim(' "set up a venv to train llada 8b"'));
|
|
207
|
+
console.log(chalk.dim(' "install pytorch and transformers"'));
|
|
208
|
+
console.log(chalk.dim(' "what packages are installed?"'));
|
|
209
|
+
console.log(chalk.dim(' "are there any conflicts?"'));
|
|
210
|
+
console.log();
|
|
211
|
+
console.log(chalk.hex("#a5ffa5")(" Shortcuts:"));
|
|
212
|
+
console.log(` ${chalk.hex("#d2ffd2")("list")} See installed packages`);
|
|
213
|
+
console.log(` ${chalk.hex("#d2ffd2")("check")} Check for problems`);
|
|
214
|
+
console.log(` ${chalk.hex("#d2ffd2")("fix")} Auto-repair environment`);
|
|
215
|
+
console.log(` ${chalk.hex("#d2ffd2")("conflicts")} Find package conflicts`);
|
|
216
|
+
console.log(` ${chalk.hex("#d2ffd2")("doctor")} Full health audit`);
|
|
217
|
+
console.log(` ${chalk.hex("#d2ffd2")("tree")} Dependency tree`);
|
|
218
|
+
console.log(` ${chalk.hex("#d2ffd2")("undo")} Undo last change`);
|
|
219
|
+
console.log(` ${chalk.hex("#d2ffd2")("clear")} Clear screen`);
|
|
220
|
+
console.log(` ${chalk.hex("#d2ffd2")("exit")} Quit Komodo`);
|
|
221
|
+
console.log();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (trimmed === "undo") {
|
|
225
|
+
await handleUndo(projectPath);
|
|
226
|
+
await updateChatContext(projectPath);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (trimmed === "list" || trimmed === "ls") {
|
|
230
|
+
await handleList(projectPath);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (trimmed === "check") {
|
|
234
|
+
await handleCheck(projectPath);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (trimmed === "history") {
|
|
238
|
+
await handleHistory(projectPath);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (trimmed === "doctor") {
|
|
242
|
+
await handleDoctor(projectPath);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (trimmed === "optimize") {
|
|
246
|
+
await handleOptimize(projectPath);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (trimmed === "tree") {
|
|
250
|
+
await handleTree(projectPath);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (trimmed === "templates" || trimmed === "template") {
|
|
254
|
+
await handleTemplates();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (trimmed.startsWith("template ") || trimmed.startsWith("use ")) {
|
|
258
|
+
const templateId = trimmed.replace(/^(template|use)\s+/, "").trim();
|
|
259
|
+
await handleUseTemplate(templateId, projectPath);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (trimmed.startsWith("explain ")) {
|
|
263
|
+
const packageName = trimmed.slice(8).trim();
|
|
264
|
+
await handleExplain(packageName);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (trimmed.startsWith("clone ") || trimmed.startsWith("setup ")) {
|
|
268
|
+
const url = trimmed.replace(/^(clone|setup)\s+/, "").trim();
|
|
269
|
+
await handleClone(url);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (trimmed === "insights" || trimmed === "analytics") {
|
|
273
|
+
await handleInsights(projectPath);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (trimmed === "ui") {
|
|
277
|
+
await handleUI(projectPath);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
process.stdout.write(chalk.hex("#b4ffb4")(" Thinking...\r"));
|
|
281
|
+
try {
|
|
282
|
+
await updateChatContext(projectPath);
|
|
283
|
+
const response = await chat.chat(trimmed);
|
|
284
|
+
process.stdout.write(" \r");
|
|
285
|
+
const cleanMessage = stripMarkdown(response.message);
|
|
286
|
+
console.log();
|
|
287
|
+
console.log(chalk.hex("#b4ffb4")(cleanMessage));
|
|
288
|
+
console.log();
|
|
289
|
+
if (response.packages) {
|
|
290
|
+
const hasActions = response.packages.toInstall && response.packages.toInstall.length > 0 || response.packages.toRemove && response.packages.toRemove.length > 0 || response.packages.toUpdate && response.packages.toUpdate.length > 0;
|
|
291
|
+
if (hasActions) {
|
|
292
|
+
await executePackageActions(response.packages, projectPath);
|
|
293
|
+
await updateChatContext(projectPath);
|
|
288
294
|
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
spinner.fail(chalk.red("Something went wrong"));
|
|
291
|
-
const errMsg = error instanceof Error ? error.message : "Unknown error";
|
|
292
|
-
console.log();
|
|
293
|
-
console.log(chalk.dim(" " + errMsg));
|
|
294
|
-
console.log(chalk.dim(" Try again or type 'help' for options."));
|
|
295
|
-
console.log();
|
|
296
295
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}).catch(() => {
|
|
309
|
-
if (!rl.closed) {
|
|
310
|
-
prompt();
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
};
|
|
315
|
-
prompt();
|
|
316
|
-
} catch (error) {
|
|
317
|
-
console.error(chalk.red("Fatal error:"), error instanceof Error ? error.message : String(error));
|
|
318
|
-
process.exit(1);
|
|
319
|
-
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
process.stdout.write(" \r");
|
|
298
|
+
console.log(chalk.red(" Something went wrong"));
|
|
299
|
+
const errMsg = error instanceof Error ? error.message : "Unknown error";
|
|
300
|
+
console.log();
|
|
301
|
+
console.log(chalk.dim(" " + errMsg));
|
|
302
|
+
console.log(chalk.dim(" Try again or type 'help' for options."));
|
|
303
|
+
console.log();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
askQuestion();
|
|
320
307
|
}
|
|
321
308
|
async function executePackageActions(packages, projectPath) {
|
|
322
309
|
const allActions = [
|
|
@@ -669,7 +656,7 @@ async function handleUI(projectPath) {
|
|
|
669
656
|
console.log();
|
|
670
657
|
console.log(chalk.hex("#b4ffb4").dim(" Starting dashboard..."));
|
|
671
658
|
try {
|
|
672
|
-
const { startServer } = await import("./server-
|
|
659
|
+
const { startServer } = await import("./server-G6XWHEZX.js");
|
|
673
660
|
await startServer(projectPath, port);
|
|
674
661
|
console.log(chalk.hex("#5aff5a")(` \u2713 Dashboard ready at ${chalk.bold(url)}`));
|
|
675
662
|
console.log();
|
|
@@ -707,7 +694,7 @@ program.argument("[intent]", "What you want to build").option("-p, --path <path>
|
|
|
707
694
|
path: options.path,
|
|
708
695
|
dryRun: options.preview,
|
|
709
696
|
useAI: true,
|
|
710
|
-
apiKey:
|
|
697
|
+
apiKey: CEREBRAS_API_KEY,
|
|
711
698
|
onProgress: (message) => {
|
|
712
699
|
const friendly = friendlyMessage(message);
|
|
713
700
|
if (spinner.isSpinning) {
|
|
@@ -998,7 +985,7 @@ program.command("ui").description("Open the visual dashboard").option("-p, --pat
|
|
|
998
985
|
const url = `http://localhost:${port}`;
|
|
999
986
|
console.log(chalk.dim(" Starting dashboard..."));
|
|
1000
987
|
try {
|
|
1001
|
-
const { startServer } = await import("./server-
|
|
988
|
+
const { startServer } = await import("./server-G6XWHEZX.js");
|
|
1002
989
|
await startServer(options.path, port);
|
|
1003
990
|
console.log();
|
|
1004
991
|
console.log(chalk.green(` \u2713 Dashboard ready at ${chalk.bold(url)}`));
|
|
@@ -1306,4 +1293,181 @@ function formatTimeAgo(date) {
|
|
|
1306
1293
|
if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`;
|
|
1307
1294
|
return date.toLocaleDateString();
|
|
1308
1295
|
}
|
|
1296
|
+
program.command("export").description("Export environment to shareable file").option("-p, --path <path>", "Project folder", process.cwd()).option("-o, --output <file>", "Output file", ".komodo-env.json").option("--notes <notes>", "Description or notes about this environment").option("--tags <tags>", "Comma-separated tags for categorization").action(async (options) => {
|
|
1297
|
+
const spinner = ora(chalk.hex("#b4ffb4")("Exporting environment...")).start();
|
|
1298
|
+
try {
|
|
1299
|
+
const state = await loadState(options.path);
|
|
1300
|
+
if (!state.activeEnvironmentId) {
|
|
1301
|
+
spinner.fail(chalk.red("No environment to export"));
|
|
1302
|
+
console.log();
|
|
1303
|
+
console.log(chalk.dim(' Set up an environment first with: komodo "install <packages>"'));
|
|
1304
|
+
console.log();
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const activeEnv = state.environments.find((e) => e.id === state.activeEnvironmentId);
|
|
1308
|
+
if (!activeEnv) {
|
|
1309
|
+
spinner.fail(chalk.red("Environment not found"));
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const exported = await exportEnvironment(options.path, activeEnv.runtime, {
|
|
1313
|
+
includeDevDeps: true,
|
|
1314
|
+
cleanVersions: false
|
|
1315
|
+
});
|
|
1316
|
+
exported.exportedBy = os.userInfo().username;
|
|
1317
|
+
if (options.notes) exported.notes = options.notes;
|
|
1318
|
+
if (options.tags) exported.tags = options.tags.split(",").map((t) => t.trim());
|
|
1319
|
+
exported.komodoVersion = packageJson.version;
|
|
1320
|
+
await writeFile(options.output, JSON.stringify(exported, null, 2));
|
|
1321
|
+
spinner.succeed(chalk.hex("#5aff5a")("Environment exported!"));
|
|
1322
|
+
console.log();
|
|
1323
|
+
console.log(chalk.hex("#b4ffb4")(" File: ") + chalk.white(options.output));
|
|
1324
|
+
console.log(chalk.hex("#b4ffb4")(" Runtime: ") + chalk.white(activeEnv.runtime));
|
|
1325
|
+
console.log(chalk.hex("#b4ffb4")(" Packages: ") + chalk.white(`${exported.packages.length}`));
|
|
1326
|
+
console.log(chalk.hex("#b4ffb4")(" Hardware: ") + chalk.white(`${exported.hardware?.gpu || "CPU"}`));
|
|
1327
|
+
console.log();
|
|
1328
|
+
console.log(chalk.dim(" Share this file with teammates: komodo import .komodo-env.json"));
|
|
1329
|
+
console.log();
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
spinner.fail();
|
|
1332
|
+
console.log();
|
|
1333
|
+
console.log(chalk.red(" Export failed"));
|
|
1334
|
+
console.log(chalk.dim(` ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1335
|
+
console.log();
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
program.command("import").description("Import environment from file or URL").argument("<source>", "File path or URL to environment").option("-p, --path <path>", "Target project folder", process.cwd()).option("--force", "Skip hardware compatibility check").action(async (source, options) => {
|
|
1339
|
+
const spinner = ora(chalk.hex("#b4ffb4")("Importing environment...")).start();
|
|
1340
|
+
try {
|
|
1341
|
+
let data;
|
|
1342
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
1343
|
+
spinner.text = chalk.hex("#b4ffb4")("Fetching environment...");
|
|
1344
|
+
const response = await fetch(source);
|
|
1345
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1346
|
+
data = await response.json();
|
|
1347
|
+
} else {
|
|
1348
|
+
const content = await readFile(source, "utf-8");
|
|
1349
|
+
data = JSON.parse(content);
|
|
1350
|
+
}
|
|
1351
|
+
if (!data.runtime || !data.packages || !Array.isArray(data.packages)) {
|
|
1352
|
+
throw new Error("Invalid environment file format");
|
|
1353
|
+
}
|
|
1354
|
+
spinner.stop();
|
|
1355
|
+
console.log();
|
|
1356
|
+
const hardware = komodo.getHardware();
|
|
1357
|
+
if (data.hardware && !options.force) {
|
|
1358
|
+
console.log(chalk.hex("#b4ffb4")(" Hardware check:"));
|
|
1359
|
+
console.log(chalk.dim(` Your machine: ${hardware.gpu || "CPU"}, ${hardware.totalMemoryGb}GB RAM`));
|
|
1360
|
+
console.log(chalk.dim(` Environment: ${data.hardware.gpu || "CPU"}, ${data.hardware.totalMemoryGb}GB RAM`));
|
|
1361
|
+
if (data.hardware.gpu !== hardware.gpu) {
|
|
1362
|
+
console.log(chalk.yellow(` \u26A0 GPU mismatch - packages will be adapted`));
|
|
1363
|
+
}
|
|
1364
|
+
if (data.hardware.totalMemoryGb > hardware.totalMemoryGb) {
|
|
1365
|
+
console.log(chalk.yellow(` \u26A0 Less memory than original - some packages may be slow`));
|
|
1366
|
+
}
|
|
1367
|
+
console.log();
|
|
1368
|
+
}
|
|
1369
|
+
console.log(chalk.hex("#a5ffa5")(" Installing:"));
|
|
1370
|
+
data.packages.slice(0, 10).forEach((pkg) => {
|
|
1371
|
+
const plus = chalk.hex("#5aff5a")("+");
|
|
1372
|
+
console.log(` ${plus} ${friendlyPackageName(pkg.name)}@${pkg.version}`);
|
|
1373
|
+
});
|
|
1374
|
+
if (data.packages.length > 10) {
|
|
1375
|
+
console.log(chalk.dim(` ...and ${data.packages.length - 10} more`));
|
|
1376
|
+
}
|
|
1377
|
+
console.log();
|
|
1378
|
+
const installSpinner = ora(chalk.hex("#b4ffb4")("Installing packages...")).start();
|
|
1379
|
+
const result = await importEnvironment(options.path, data);
|
|
1380
|
+
if (result.success) {
|
|
1381
|
+
installSpinner.succeed(chalk.hex("#5aff5a")("Environment imported!"));
|
|
1382
|
+
console.log();
|
|
1383
|
+
console.log(chalk.hex("#5aff5a")(` \u2713 ${result.installed?.length || 0} packages installed`));
|
|
1384
|
+
if (data.exportedBy) {
|
|
1385
|
+
console.log(chalk.dim(` From: ${data.exportedBy}`));
|
|
1386
|
+
}
|
|
1387
|
+
if (data.notes) {
|
|
1388
|
+
console.log(chalk.dim(` Notes: ${data.notes}`));
|
|
1389
|
+
}
|
|
1390
|
+
console.log();
|
|
1391
|
+
} else {
|
|
1392
|
+
installSpinner.fail(chalk.red("Import failed"));
|
|
1393
|
+
console.log();
|
|
1394
|
+
console.log(chalk.dim(` ${result.error || "Could not install packages"}`));
|
|
1395
|
+
console.log();
|
|
1396
|
+
}
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
spinner.fail();
|
|
1399
|
+
console.log();
|
|
1400
|
+
console.log(chalk.red(" Import failed"));
|
|
1401
|
+
console.log(chalk.dim(` ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1402
|
+
console.log();
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
program.command("verify").description("Verify environment matches reproducible snapshot").option("-p, --path <path>", "Project folder", process.cwd()).option("--create", "Create a new reproducible snapshot").action(async (options) => {
|
|
1406
|
+
const spinner = ora(chalk.hex("#b4ffb4")("Verifying environment...")).start();
|
|
1407
|
+
try {
|
|
1408
|
+
const state = await loadState(options.path);
|
|
1409
|
+
if (!state.activeEnvironmentId) {
|
|
1410
|
+
spinner.fail(chalk.red("No environment to verify"));
|
|
1411
|
+
console.log();
|
|
1412
|
+
console.log(chalk.dim(' Set up an environment first with: komodo "install <packages>"'));
|
|
1413
|
+
console.log();
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const activeEnv = state.environments.find((e) => e.id === state.activeEnvironmentId);
|
|
1417
|
+
if (!activeEnv) {
|
|
1418
|
+
spinner.fail(chalk.red("Environment not found"));
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (options.create) {
|
|
1422
|
+
spinner.text = chalk.hex("#b4ffb4")("Creating reproducible snapshot...");
|
|
1423
|
+
const snapshot2 = await generateHashSnapshot(
|
|
1424
|
+
options.path,
|
|
1425
|
+
activeEnv.runtime,
|
|
1426
|
+
`Created at ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
1427
|
+
);
|
|
1428
|
+
await saveSnapshot(options.path, snapshot2);
|
|
1429
|
+
spinner.succeed(chalk.hex("#5aff5a")("Snapshot created!"));
|
|
1430
|
+
console.log();
|
|
1431
|
+
console.log(chalk.hex("#b4ffb4")(" Snapshot: .komodo-snapshot.json"));
|
|
1432
|
+
console.log(chalk.hex("#b4ffb4")(" Packages: ") + chalk.white(snapshot2.hashes.length.toString()));
|
|
1433
|
+
console.log(chalk.dim(" Use 'komodo verify' to check reproducibility"));
|
|
1434
|
+
console.log();
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const { snapshot, verification } = await loadAndVerifySnapshot(options.path);
|
|
1438
|
+
spinner.stop();
|
|
1439
|
+
console.log();
|
|
1440
|
+
if (verification.valid) {
|
|
1441
|
+
console.log(chalk.hex("#5aff5a")(" \u2713 Environment is reproducible!"));
|
|
1442
|
+
console.log(chalk.hex("#b4ffb4")(" Packages verified: ") + chalk.white(snapshot.hashes.length.toString()));
|
|
1443
|
+
console.log(chalk.dim(` Snapshot from: ${snapshot.timestamp.split("T")[0]}`));
|
|
1444
|
+
console.log();
|
|
1445
|
+
} else {
|
|
1446
|
+
console.log(chalk.red(` \u2717 Hash mismatches (${verification.mismatches.length}):`));
|
|
1447
|
+
verification.mismatches.slice(0, 5).forEach((m) => {
|
|
1448
|
+
console.log(chalk.dim(` \u2022 ${m}`));
|
|
1449
|
+
});
|
|
1450
|
+
if (verification.mismatches.length > 5) {
|
|
1451
|
+
console.log(chalk.dim(` ...and ${verification.mismatches.length - 5} more`));
|
|
1452
|
+
}
|
|
1453
|
+
console.log();
|
|
1454
|
+
console.log(chalk.dim(" Packages may have been upgraded or changed."));
|
|
1455
|
+
console.log(chalk.dim(" Run 'komodo verify --create' to create a new snapshot."));
|
|
1456
|
+
console.log();
|
|
1457
|
+
}
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
spinner.fail();
|
|
1460
|
+
console.log();
|
|
1461
|
+
if (error instanceof Error && error.message.includes("Snapshot not found")) {
|
|
1462
|
+
console.log(chalk.yellow(" No snapshot found"));
|
|
1463
|
+
console.log();
|
|
1464
|
+
console.log(chalk.dim(" Create one with: komodo verify --create"));
|
|
1465
|
+
console.log();
|
|
1466
|
+
} else {
|
|
1467
|
+
console.log(chalk.red(" Verification failed"));
|
|
1468
|
+
console.log(chalk.dim(` ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1469
|
+
console.log();
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1309
1473
|
program.parse();
|