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/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-GGBZF72M.js";
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(os) {
94
+ function formatOs(os2) {
86
95
  const names = { darwin: "Mac", linux: "Linux", windows: "Windows" };
87
- return names[os] ?? os;
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
- try {
135
- printBanner();
136
- const hardware = komodo.getHardware();
137
- console.log(chalk.hex("#b4ffb4").dim(` ${formatOs(hardware.os)} \xB7 ${formatGpu(hardware)} \xB7 ${hardware.totalMemoryGb}GB memory`));
138
- console.log();
139
- console.log(chalk.dim(" Type anything naturally. Komodo understands what you need."));
140
- console.log(chalk.dim(` Try: "set up a venv for pytorch" or "what's installed?"`));
141
- console.log(chalk.dim(" Type ") + chalk.hex("#d2ffd2")("help") + chalk.dim(" for commands, ") + chalk.hex("#d2ffd2")("exit") + chalk.dim(" to quit."));
142
- console.log();
143
- await updateChatContext(projectPath);
144
- const rl = readline.createInterface({
145
- input: process.stdin,
146
- output: process.stdout
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
- rl.on("close", () => {
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
- process.on("unhandledRejection", (reason) => {
152
- console.error(chalk.red("\n[DEBUG] Unhandled rejection:"), reason);
153
- if (!rl.closed) {
154
- prompt();
155
- }
156
- });
157
- process.on("uncaughtException", (error) => {
158
- console.error(chalk.red("\n[DEBUG] Uncaught exception:"), error);
159
- if (!rl.closed) {
160
- prompt();
161
- }
162
- });
163
- const handleInput = async (input) => {
164
- const trimmed = input.trim();
165
- if (!trimmed) {
166
- prompt();
167
- return;
168
- }
169
- if (trimmed === "exit" || trimmed === "quit" || trimmed === "q") {
170
- console.log();
171
- console.log(chalk.hex("#a5ffa5")(" See you next time!"));
172
- console.log();
173
- rl.close();
174
- process.exit(0);
175
- }
176
- if (trimmed === "clear" || trimmed === "cls") {
177
- console.clear();
178
- printBanner();
179
- prompt();
180
- return;
181
- }
182
- if (trimmed === "help" || trimmed === "?") {
183
- console.log();
184
- console.log(chalk.hex("#a5ffa5")(" Just type what you want in plain English:"));
185
- console.log(chalk.dim(' "set up a venv to train llada 8b"'));
186
- console.log(chalk.dim(' "install pytorch and transformers"'));
187
- console.log(chalk.dim(' "what packages are installed?"'));
188
- console.log(chalk.dim(' "are there any conflicts?"'));
189
- console.log();
190
- console.log(chalk.hex("#a5ffa5")(" Shortcuts:"));
191
- console.log(` ${chalk.hex("#d2ffd2")("list")} See installed packages`);
192
- console.log(` ${chalk.hex("#d2ffd2")("check")} Check for problems`);
193
- console.log(` ${chalk.hex("#d2ffd2")("fix")} Auto-repair environment`);
194
- console.log(` ${chalk.hex("#d2ffd2")("conflicts")} Find package conflicts`);
195
- console.log(` ${chalk.hex("#d2ffd2")("doctor")} Full health audit`);
196
- console.log(` ${chalk.hex("#d2ffd2")("tree")} Dependency tree`);
197
- console.log(` ${chalk.hex("#d2ffd2")("undo")} Undo last change`);
198
- console.log(` ${chalk.hex("#d2ffd2")("clear")} Clear screen`);
199
- console.log(` ${chalk.hex("#d2ffd2")("exit")} Quit Komodo`);
200
- console.log();
201
- prompt();
202
- return;
203
- }
204
- if (trimmed === "undo") {
205
- await handleUndo(projectPath);
206
- await updateChatContext(projectPath);
207
- prompt();
208
- return;
209
- }
210
- if (trimmed === "list" || trimmed === "ls") {
211
- await handleList(projectPath);
212
- prompt();
213
- return;
214
- }
215
- if (trimmed === "check") {
216
- await handleCheck(projectPath);
217
- prompt();
218
- return;
219
- }
220
- if (trimmed === "history") {
221
- await handleHistory(projectPath);
222
- prompt();
223
- return;
224
- }
225
- if (trimmed === "doctor") {
226
- await handleDoctor(projectPath);
227
- prompt();
228
- return;
229
- }
230
- if (trimmed === "optimize") {
231
- await handleOptimize(projectPath);
232
- prompt();
233
- return;
234
- }
235
- if (trimmed === "tree") {
236
- await handleTree(projectPath);
237
- prompt();
238
- return;
239
- }
240
- if (trimmed === "templates" || trimmed === "template") {
241
- await handleTemplates();
242
- prompt();
243
- return;
244
- }
245
- if (trimmed.startsWith("template ") || trimmed.startsWith("use ")) {
246
- const templateId = trimmed.replace(/^(template|use)\s+/, "").trim();
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
- prompt();
298
- };
299
- const prompt = () => {
300
- if (rl.closed) {
301
- return;
302
- }
303
- rl.question(chalk.hex("#5aff5a")("\u276F "), (input) => {
304
- handleInput(input).catch((err) => {
305
- console.error(chalk.red("\nError:"), err instanceof Error ? err.message : String(err));
306
- console.log();
307
- prompt();
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-JOKWCY3W.js");
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: API_KEY,
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-JOKWCY3W.js");
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();