framer-export 4.3.2 → 4.3.8

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/cli/index.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "framer-export",
18
- version: "4.3.2",
18
+ version: "4.3.8",
19
19
  description: "Export any Framer, Webflow, or Wix site into a fully working local mirror. Downloads all assets, strips badges, rewrites URLs, and pretty-prints JS.",
20
20
  type: "module",
21
21
  main: "dist/cli/index.js",
@@ -82,8 +82,67 @@ var init_package = __esm({
82
82
  }
83
83
  });
84
84
 
85
- // src/cli/banner.ts
85
+ // src/cli/theme.ts
86
86
  import chalk from "chalk";
87
+ function stripAnsi(s) {
88
+ return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
89
+ }
90
+ function softGradient(text) {
91
+ const colors = [THEME.primary, THEME.primarySoft, THEME.text, THEME.primarySoft, THEME.primary];
92
+ let cursor = 0;
93
+ return text.split("").map((char) => {
94
+ if (char === " ") return char;
95
+ const color = colors[cursor % colors.length];
96
+ cursor++;
97
+ return chalk.hex(color).bold(char);
98
+ }).join("");
99
+ }
100
+ function chip(label) {
101
+ return `${ui.border("[")}${ui.primary(label)}${ui.border("]")}`;
102
+ }
103
+ function bullet(label = "\u2022") {
104
+ return ui.primary(label);
105
+ }
106
+ var THEME, ui;
107
+ var init_theme = __esm({
108
+ "src/cli/theme.ts"() {
109
+ "use strict";
110
+ THEME = {
111
+ background: "#0A0A0A",
112
+ panel: "#141414",
113
+ element: "#1E1E1E",
114
+ border: "#484848",
115
+ borderActive: "#606060",
116
+ text: "#EEEEEE",
117
+ muted: "#808080",
118
+ primary: "#FAB283",
119
+ primarySoft: "#FFC09F",
120
+ secondary: "#5C9CF5",
121
+ accent: "#9D7CD8",
122
+ success: "#7FD88F",
123
+ warning: "#F5A742",
124
+ error: "#E06C75",
125
+ info: "#56B6C2"
126
+ };
127
+ ui = {
128
+ text: chalk.hex(THEME.text),
129
+ muted: chalk.hex(THEME.muted),
130
+ primary: chalk.hex(THEME.primary),
131
+ primarySoft: chalk.hex(THEME.primarySoft),
132
+ secondary: chalk.hex(THEME.secondary),
133
+ accent: chalk.hex(THEME.accent),
134
+ success: chalk.hex(THEME.success),
135
+ warning: chalk.hex(THEME.warning),
136
+ error: chalk.hex(THEME.error),
137
+ info: chalk.hex(THEME.info),
138
+ border: chalk.hex(THEME.border),
139
+ borderActive: chalk.hex(THEME.borderActive),
140
+ panel: chalk.hex(THEME.panel)
141
+ };
142
+ }
143
+ });
144
+
145
+ // src/cli/banner.ts
87
146
  function getWidth() {
88
147
  return process.stdout.columns || 80;
89
148
  }
@@ -92,33 +151,35 @@ function showBanner() {
92
151
  const isSmall = width < 65;
93
152
  if (isSmall) {
94
153
  console.log(`
95
- ${chalk.hex("#D4A017").bold("F-EXPORT")} ${chalk.gray(`v${package_default.version}`)}`);
96
- console.log(` ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
154
+ ${ui.primary.bold("f-export")} ${ui.muted(`v${package_default.version}`)} ${chip("beta ui")}`);
155
+ console.log(` ${ui.text.bold("Framer Export")} ${ui.muted("for Framer, Webflow, and Wix")}
97
156
  `);
98
157
  return;
99
158
  }
100
- const gold = chalk.hex("#D4A017").bold;
101
- const gray = chalk.gray;
102
159
  console.log("");
103
160
  ASCII_ART.forEach((line) => {
104
- console.log(" " + gold(line));
161
+ console.log(" " + softGradient(line));
105
162
  });
106
163
  console.log("");
107
- console.log(` ${gray(`v${package_default.version}`)} ${chalk.white.bold("Framer \xB7 Webflow \xB7 Wix Exporter")}
108
- `);
164
+ console.log(
165
+ ` ${ui.muted(`v${package_default.version}`)} ${ui.text.bold("Framer Export")} ${chip("fexport")} ${ui.muted("local mirror exporter")}`
166
+ );
167
+ console.log(
168
+ ` ${ui.muted("Framer")} ${ui.border("/")} ${ui.muted("Webflow")} ${ui.border("/")} ${ui.muted("Wix")} ${ui.border("\xB7")} ${ui.primary("clean assets")} ${ui.border("\xB7")} ${ui.secondary("local serve")}
169
+ `
170
+ );
109
171
  }
110
172
  var ASCII_ART;
111
173
  var init_banner = __esm({
112
174
  "src/cli/banner.ts"() {
113
175
  "use strict";
114
176
  init_package();
177
+ init_theme();
115
178
  ASCII_ART = [
116
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
117
- "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
118
- "\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 ",
119
- "\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 ",
120
- "\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
121
- "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
179
+ " \u2584 \u2584 ",
180
+ "\u2588\u2580\u2580 \u2588\u2580\u2588 \u2584\u2580\u2588 \u2588\u2580\u2584\u2580\u2588 \u2588\u2580\u2580 \u2588\u2580\u2588 \u2588\u2580\u2580 \u2580\u2584\u2580 \u2588\u2580\u2588 \u2588\u2580\u2588 \u2588\u2580\u2588 \u2580\u2588\u2580",
181
+ "\u2588\u2580 \u2588\u2580\u2584 \u2588\u2580\u2588 \u2588 \u2580 \u2588 \u2588\u2588\u2584 \u2588\u2580\u2584 \u2588\u2588\u2584 \u2588 \u2588 \u2588\u2580\u2580 \u2588\u2584\u2588 \u2588\u2580\u2584 \u2588 ",
182
+ "\u2580 \u2580 \u2580 \u2580 \u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580 \u2580 \u2580 \u2580\u2580\u2580 \u2580 \u2580 \u2580 "
122
183
  ];
123
184
  }
124
185
  });
@@ -259,7 +320,7 @@ var init_download = __esm({
259
320
  });
260
321
 
261
322
  // src/logger/index.ts
262
- import chalk3 from "chalk";
323
+ import chalk2 from "chalk";
263
324
  function setCooking(spinner) {
264
325
  _cooking = spinner;
265
326
  }
@@ -267,41 +328,60 @@ function trunc(text, max) {
267
328
  if (text.length <= max) return text;
268
329
  return text.slice(0, max - 2) + "..";
269
330
  }
270
- function nextColor() {
271
- return MSG_COLORS[_msgIdx++ % MSG_COLORS.length];
272
- }
273
331
  function output(line) {
274
- if (_cooking) {
275
- _cooking.log(line);
276
- } else {
277
- console.log(line);
278
- }
332
+ if (_cooking) _cooking.log(line);
333
+ else console.log(line);
279
334
  }
280
- var _cooking, T, MSG_COLORS, _msgIdx, log, info, warn, success;
335
+ var _cooking, T, LOG_PALETTE, _li, log, INFO_PALETTE, _ii, info, warn, success;
281
336
  var init_logger = __esm({
282
337
  "src/logger/index.ts"() {
283
338
  "use strict";
339
+ init_theme();
284
340
  _cooking = null;
285
341
  T = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
286
- MSG_COLORS = [
287
- (s) => chalk3.hex("#e8c33a")(s),
288
- (s) => chalk3.hex("#d4a76a")(s),
289
- (s) => chalk3.hex("#c8a040")(s),
290
- (s) => chalk3.hex("#e0b040")(s),
291
- (s) => chalk3.hex("#dab660")(s),
292
- (s) => chalk3.hex("#c4953a")(s),
293
- (s) => chalk3.hex("#daa520")(s),
294
- (s) => chalk3.hex("#b8960a")(s)
342
+ LOG_PALETTE = [
343
+ (s) => chalk2.hex(THEME.primary)(s),
344
+ (s) => chalk2.hex(THEME.primarySoft)(s),
345
+ (s) => chalk2.hex(THEME.secondary)(s),
346
+ (s) => chalk2.hex(THEME.accent)(s),
347
+ (s) => chalk2.hex(THEME.info)(s),
348
+ (s) => chalk2.hex(THEME.text)(s)
349
+ ];
350
+ _li = 0;
351
+ log = (m) => {
352
+ _li++;
353
+ const c = LOG_PALETTE[_li % LOG_PALETTE.length];
354
+ output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.primary)("[log]")} ${c(trunc(m, 120))}`);
355
+ };
356
+ INFO_PALETTE = [
357
+ (s) => chalk2.hex(THEME.info)(s),
358
+ (s) => chalk2.hex(THEME.secondary)(s),
359
+ (s) => chalk2.hex(THEME.primarySoft)(s)
295
360
  ];
296
- _msgIdx = 0;
297
- log = (m) => output(`${chalk3.hex("#6d6d6d")(`[${T()}]`)} ${chalk3.hex("#D4A017")("[LOG]")} ${nextColor()(trunc(m, 120))}`);
298
- info = (m) => output(`${chalk3.hex("#757575")(`[${T()}]`)} ${chalk3.hex("#E8A317").bold("[INFO]")} ${chalk3.bold(trunc(m, 120))}`);
361
+ _ii = 0;
362
+ info = (m) => {
363
+ _ii++;
364
+ const c = INFO_PALETTE[_ii % INFO_PALETTE.length];
365
+ output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.info).bold("[info]")} ${c(trunc(m, 120))}`);
366
+ };
299
367
  warn = (m) => {
300
- const line = `${chalk3.hex("#8a8a8a")(`[${T()}]`)} ${chalk3.hex("#CC7722").bold("[WARN]")} ${chalk3.hex("#CC7722")(trunc(m, 120))}`;
368
+ const colors = [
369
+ (s) => chalk2.hex(THEME.warning)(s),
370
+ (s) => chalk2.hex(THEME.primary)(s)
371
+ ];
372
+ const c = colors[Math.floor(Math.random() * colors.length)];
373
+ const line = `${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.warning).bold("[warn]")} ${c(trunc(m, 120))}`;
301
374
  if (_cooking) _cooking.log(line);
302
375
  else console.warn(line);
303
376
  };
304
- success = (m) => output(`${chalk3.hex("#7a7a7a")(`[${T()}]`)} ${chalk3.greenBright("[OK]")} ${chalk3.green(trunc(m, 120))}`);
377
+ success = (m) => {
378
+ const colors = [
379
+ (s) => chalk2.hex(THEME.success)(s),
380
+ (s) => chalk2.hex(THEME.info)(s)
381
+ ];
382
+ const c = colors[Math.floor(Math.random() * colors.length)];
383
+ output(`${chalk2.hex(THEME.muted)(`[${T()}]`)} ${chalk2.hex(THEME.success)("[ok]")} ${c(trunc(m, 120))}`);
384
+ };
305
385
  }
306
386
  });
307
387
 
@@ -1154,7 +1234,7 @@ var init_output = __esm({
1154
1234
  });
1155
1235
 
1156
1236
  // src/cli/box.ts
1157
- import chalk4 from "chalk";
1237
+ import chalk3 from "chalk";
1158
1238
  function maxWidth() {
1159
1239
  return Math.min(process.stdout.columns || 80, 62);
1160
1240
  }
@@ -1163,51 +1243,45 @@ function padRight(text, w) {
1163
1243
  if (visible >= w) return text;
1164
1244
  return text + " ".repeat(w - visible);
1165
1245
  }
1166
- function stripAnsi(s) {
1167
- return s.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
1168
- }
1169
1246
  function boxTop(w) {
1170
1247
  const inner = w - 4;
1171
- return " " + B("\u2554\u2550") + B("\u2550".repeat(inner)) + B("\u2550\u2557");
1248
+ return " " + ui.border("\u256D\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256E");
1172
1249
  }
1173
1250
  function boxBot(w) {
1174
1251
  const inner = w - 4;
1175
- return " " + B("\u255A\u2550") + B("\u2550".repeat(inner)) + B("\u2550\u255D");
1252
+ return " " + ui.border("\u2570\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u256F");
1176
1253
  }
1177
1254
  function boxLine(w, text) {
1178
1255
  const inner = w - 4;
1179
1256
  const padded = padRight(text, inner);
1180
- return " " + B("\u2551 ") + padded + B(" \u2551");
1257
+ return " " + ui.border("\u2502 ") + padded + ui.border(" \u2502");
1181
1258
  }
1182
1259
  function boxSep(w) {
1183
1260
  const inner = w - 4;
1184
- return " " + B("\u2560\u2550") + B("\u2550".repeat(inner)) + B("\u2550\u2563");
1261
+ return " " + ui.border("\u251C\u2500") + ui.border("\u2500".repeat(inner)) + ui.border("\u2500\u2524");
1185
1262
  }
1186
1263
  function boxRow(w, label, value) {
1187
1264
  const inner = w - 4;
1188
- const labelPlain = stripAnsi(chalk4.bold(label));
1265
+ const labelPlain = stripAnsi(chalk3.bold(label));
1189
1266
  const visible = labelPlain.length + 1 + value.length;
1190
1267
  if (visible > inner) {
1191
1268
  const avail = inner - labelPlain.length - 2;
1192
1269
  const truncated = value.length > avail ? value.slice(0, avail - 1) + ".." : value;
1193
- return " " + G("\u2551 ") + chalk4.bold(label) + ": " + chalk4.yellow(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + G(" \u2551");
1270
+ return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(truncated) + " ".repeat(Math.max(0, inner - labelPlain.length - 1 - truncated.length)) + ui.border(" \u2502");
1194
1271
  }
1195
1272
  const right = inner - labelPlain.length - 1 - value.length;
1196
- return " " + G("\u2551 ") + chalk4.bold(label) + ": " + chalk4.yellow(value) + " ".repeat(right) + G(" \u2551");
1273
+ return " " + ui.border("\u2502 ") + chalk3.bold(label) + ": " + ui.primary(value) + " ".repeat(right) + ui.border(" \u2502");
1197
1274
  }
1198
- var B, G;
1199
1275
  var init_box = __esm({
1200
1276
  "src/cli/box.ts"() {
1201
1277
  "use strict";
1202
- B = chalk4.hex("#8B6914");
1203
- G = chalk4.hex("#C4953A");
1278
+ init_theme();
1204
1279
  }
1205
1280
  });
1206
1281
 
1207
1282
  // src/exporter/summary.ts
1208
1283
  import fs3 from "fs/promises";
1209
1284
  import path4 from "path";
1210
- import chalk5 from "chalk";
1211
1285
  async function printSummary(exporter) {
1212
1286
  const w = maxWidth();
1213
1287
  const isSmall = w < 50;
@@ -1229,17 +1303,17 @@ async function printSummary(exporter) {
1229
1303
  count("data"),
1230
1304
  count("subpages")
1231
1305
  ]);
1232
- const G2 = chalk5.hex("#D4A017");
1233
- const G22 = chalk5.hex("#DAA520");
1234
- const C2 = chalk5.hex("#E0B040");
1235
- const Y = chalk5.yellow;
1236
- const O = chalk5.hex("#CC7722");
1237
- const Br = chalk5.hex("#B8860B");
1238
- const Gr = chalk5.gray;
1239
- const Gn = chalk5.green;
1306
+ const G = ui.primary;
1307
+ const G2 = ui.primarySoft;
1308
+ const C2 = ui.secondary;
1309
+ const Y = ui.accent;
1310
+ const O = ui.warning;
1311
+ const Br = ui.info;
1312
+ const Gr = ui.muted;
1313
+ const Gn = ui.success;
1240
1314
  const entries = [
1241
- ["styles/", styles, "CSS", G2],
1242
- ["scripts/vendor/", vendor, "JS vendor", G22],
1315
+ ["styles/", styles, "CSS", G],
1316
+ ["scripts/vendor/", vendor, "JS vendor", G2],
1243
1317
  ["scripts/modules/", scripts, "JS modules", C2],
1244
1318
  ["assets/images/", imgs, "images", Y],
1245
1319
  ["assets/videos/", videos, "videos", O],
@@ -1251,23 +1325,23 @@ async function printSummary(exporter) {
1251
1325
  console.log("");
1252
1326
  if (!isSmall) {
1253
1327
  console.log(boxTop(w));
1254
- console.log(boxLine(w, chalk5.bold.white(" Export Summary")));
1328
+ console.log(boxLine(w, `${ui.text.bold(" Export Summary")} ${chip("done")}`));
1255
1329
  console.log(boxSep(w));
1256
1330
  } else {
1257
- console.log(chalk5.bold.white(" Export Summary:"));
1331
+ console.log(ui.text.bold(" Export Summary:"));
1258
1332
  }
1259
1333
  for (const [label, cnt, type, color] of entries) {
1260
1334
  if (cnt === 0) continue;
1261
1335
  const inner = w - 4;
1262
1336
  const l = label.padEnd(16);
1263
1337
  const c = String(cnt).padStart(3);
1264
- const rowText = `${color(l)}${chalk5.white(c)} ${chalk5.gray(type)}`;
1338
+ const rowText = `${color(l)}${ui.text(c)} ${ui.muted(type)}`;
1265
1339
  const visible = 16 + 3 + 2 + type.length;
1266
1340
  const pad = Math.max(0, inner - visible);
1267
1341
  if (isSmall) {
1268
- console.log(` ${color(label)} ${chalk5.white(String(cnt))} ${chalk5.gray(type)}`);
1342
+ console.log(` ${color(label)} ${ui.text(String(cnt))} ${ui.muted(type)}`);
1269
1343
  } else {
1270
- console.log(" " + chalk5.hex("#C4953A")("\u2551 ") + rowText + " ".repeat(pad) + chalk5.hex("#C4953A")(" \u2551"));
1344
+ console.log(" " + ui.border("\u2502 ") + rowText + " ".repeat(pad) + ui.border(" \u2502"));
1271
1345
  }
1272
1346
  }
1273
1347
  if (!isSmall) {
@@ -1277,74 +1351,601 @@ async function printSummary(exporter) {
1277
1351
  const cdCmd = "cd " + path4.basename(exporter.outDir) + " && node serve.cjs";
1278
1352
  if (!isSmall) {
1279
1353
  console.log(boxTop(w));
1280
- console.log(boxLine(w, chalk5.bold.white(" To serve locally")));
1354
+ console.log(boxLine(w, ui.text.bold(" To serve locally")));
1281
1355
  console.log(boxSep(w));
1282
1356
  const inner = w - 6;
1283
1357
  const cmdLen = cdCmd.length;
1284
1358
  const pad = Math.max(0, inner - cmdLen);
1285
- console.log(" " + chalk5.hex("#C4953A")("\u2551 ") + G2(cdCmd) + " ".repeat(pad) + chalk5.hex("#9e9e9e")(" \u{1F5D0}") + chalk5.hex("#C4953A")(" \u2551"));
1359
+ console.log(" " + ui.border("\u2502 ") + G(cdCmd) + " ".repeat(pad) + ui.muted(" copy") + ui.border(" \u2502"));
1286
1360
  console.log(boxBot(w));
1287
1361
  } else {
1288
- console.log(chalk5.bold.white(" To serve locally:"));
1289
- console.log(` ${G2(cdCmd)}`);
1362
+ console.log(ui.text.bold(" To serve locally:"));
1363
+ console.log(` ${G(cdCmd)}`);
1290
1364
  }
1291
1365
  console.log("");
1292
- console.log(chalk5.gray(" NOTE: Must be served via HTTP for JS modules to work."));
1366
+ console.log(ui.muted(" note: must be served via HTTP for JS modules to work."));
1293
1367
  console.log("");
1294
1368
  }
1295
1369
  var init_summary = __esm({
1296
1370
  "src/exporter/summary.ts"() {
1297
1371
  "use strict";
1298
1372
  init_box();
1373
+ init_theme();
1374
+ }
1375
+ });
1376
+
1377
+ // src/cli/select.ts
1378
+ import readline from "readline";
1379
+ import { stdin, stdout } from "process";
1380
+ async function select(question, options, defaultIndex = 0) {
1381
+ const isTTY = stdin.isTTY && stdout.isTTY;
1382
+ if (!isTTY) {
1383
+ return fallbackPrompt(question, options, defaultIndex);
1384
+ }
1385
+ return arrowSelect(question, options, defaultIndex);
1386
+ }
1387
+ async function arrowSelect(question, options, defaultIndex) {
1388
+ return new Promise((resolve) => {
1389
+ const firstEnabled = options.findIndex((option) => !option.disabled);
1390
+ let selected = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
1391
+ if (selected < 0) selected = 0;
1392
+ const move = (direction) => {
1393
+ let next = selected + direction;
1394
+ while (next >= 0 && next < options.length) {
1395
+ if (!options[next].disabled) {
1396
+ selected = next;
1397
+ return;
1398
+ }
1399
+ next += direction;
1400
+ }
1401
+ };
1402
+ const render = (initial = false) => {
1403
+ if (!initial) {
1404
+ stdout.write(`\x1B[${options.length + 1}A`);
1405
+ stdout.write("\x1B[J");
1406
+ }
1407
+ console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}`);
1408
+ for (let i = 0; i < options.length; i++) {
1409
+ if (options[i].disabled) {
1410
+ console.log(` ${ui.muted(stripAnsi(options[i].label))}`);
1411
+ } else if (i === selected) {
1412
+ console.log(` ${ui.primary(">")} ${ui.text.bold(options[i].label)}`);
1413
+ } else {
1414
+ console.log(` ${ui.muted(options[i].label)}`);
1415
+ }
1416
+ }
1417
+ };
1418
+ render(true);
1419
+ readline.emitKeypressEvents(stdin);
1420
+ stdin.setRawMode(true);
1421
+ const onKeypress = (_str, key) => {
1422
+ if (!key) return;
1423
+ if (key.name === "up" && selected > 0) {
1424
+ move(-1);
1425
+ render();
1426
+ } else if (key.name === "down" && selected < options.length - 1) {
1427
+ move(1);
1428
+ render();
1429
+ } else if (key.name === "return") {
1430
+ stdin.setRawMode(false);
1431
+ stdin.removeListener("keypress", onKeypress);
1432
+ stdin.pause();
1433
+ stdout.write(`\x1B[${options.length + 1}A`);
1434
+ stdout.write("\x1B[J");
1435
+ console.log(
1436
+ ` ${ui.success("\u2713")} ${ui.text.bold(question)} ${ui.primary(stripAnsi(options[selected].label))}
1437
+ `
1438
+ );
1439
+ resolve(options[selected].value);
1440
+ } else if (key.ctrl && key.name === "c" || key.name === "escape") {
1441
+ stdin.setRawMode(false);
1442
+ stdin.removeListener("keypress", onKeypress);
1443
+ process.exit(0);
1444
+ }
1445
+ };
1446
+ stdin.resume();
1447
+ stdin.on("keypress", onKeypress);
1448
+ });
1449
+ }
1450
+ async function fallbackPrompt(question, options, defaultIndex) {
1451
+ return new Promise((resolve) => {
1452
+ const rl = readline.createInterface({ input: stdin, output: stdout });
1453
+ const firstEnabled = options.findIndex((option) => !option.disabled);
1454
+ const enabledDefault = options[defaultIndex]?.disabled ? firstEnabled : defaultIndex;
1455
+ console.log(` ${ui.primary("\u25CF")} ${ui.text.bold(question)}
1456
+ `);
1457
+ for (let i = 0; i < options.length; i++) {
1458
+ const marker = i === enabledDefault ? ui.success(" \u25C6") : " ";
1459
+ const label = options[i].disabled ? ui.muted(stripAnsi(options[i].label)) : ui.text(options[i].label);
1460
+ console.log(` ${ui.muted(`[${i + 1}]`)}${marker} ${label}`);
1461
+ }
1462
+ console.log("");
1463
+ const def = String(enabledDefault + 1);
1464
+ const ask = () => {
1465
+ rl.question(
1466
+ ` ${ui.primary(">")} ${ui.muted(`Choose [1-${options.length}] (${def})`)}: `,
1467
+ (answer) => {
1468
+ const trimmed = answer.trim();
1469
+ if (!trimmed) {
1470
+ rl.close();
1471
+ const label = stripAnsi(options[enabledDefault].label);
1472
+ console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
1473
+ `);
1474
+ resolve(options[enabledDefault].value);
1475
+ return;
1476
+ }
1477
+ const idx = parseInt(trimmed, 10);
1478
+ if (idx >= 1 && idx <= options.length && !options[idx - 1].disabled) {
1479
+ rl.close();
1480
+ const label = stripAnsi(options[idx - 1].label);
1481
+ console.log(` ${ui.success("\u2713")} ${ui.primary(label)}
1482
+ `);
1483
+ resolve(options[idx - 1].value);
1484
+ } else if (idx >= 1 && idx <= options.length && options[idx - 1].disabled) {
1485
+ console.log(` ${ui.error("\u2717")} ${ui.warning("Option unavailable for now")}
1486
+ `);
1487
+ ask();
1488
+ } else {
1489
+ console.log(` ${ui.error("\u2717")} ${ui.warning(`Enter 1-${options.length}`)}
1490
+ `);
1491
+ ask();
1492
+ }
1493
+ }
1494
+ );
1495
+ };
1496
+ ask();
1497
+ });
1498
+ }
1499
+ var init_select = __esm({
1500
+ "src/cli/select.ts"() {
1501
+ "use strict";
1502
+ init_theme();
1503
+ }
1504
+ });
1505
+
1506
+ // src/ai/prompt-assistant.ts
1507
+ import fs4 from "fs/promises";
1508
+ import path5 from "path";
1509
+ import { stdin as stdin2, stdout as stdout2 } from "process";
1510
+ import { spawn } from "child_process";
1511
+ import chalk4 from "chalk";
1512
+ async function runAiPromptAssistant(exporter) {
1513
+ if (!stdin2.isTTY || !stdout2.isTTY) return;
1514
+ const targetOptions = [
1515
+ ...TARGETS.map((target2) => ({ label: target2.label, value: target2.id })),
1516
+ { label: "Customize with AI - BETA in development", value: "custom-ai", disabled: true },
1517
+ { label: "Skip AI prompt", value: "skip" }
1518
+ ];
1519
+ console.log("");
1520
+ const targetId = await select("AI Prompt Assistant BETA: choose target stack", targetOptions, 0);
1521
+ if (targetId === "skip") return;
1522
+ const aiToolId = await select(
1523
+ "Choose the AI coding tool",
1524
+ AI_TOOLS.map((tool) => ({ label: tool.label, value: tool.id })),
1525
+ 0
1526
+ );
1527
+ const goalId = await select(
1528
+ "Choose the conversion situation",
1529
+ GOALS.map((goal2) => ({ label: goal2.label, value: goal2.id })),
1530
+ 0
1531
+ );
1532
+ const target = TARGETS.find((item) => item.id === targetId) || TARGETS[0];
1533
+ const aiTool = AI_TOOLS.find((item) => item.id === aiToolId) || AI_TOOLS[0];
1534
+ const goal = GOALS.find((item) => item.id === goalId) || GOALS[0];
1535
+ const facts = await collectExportFacts(exporter);
1536
+ const prompt = buildConversionPrompt(target, aiTool, goal, facts);
1537
+ const aiDir = path5.join(exporter.outDir, "ai");
1538
+ const promptPath = path5.join(aiDir, `${aiTool.id}-${target.id}-${goal.id}-prompt.md`);
1539
+ await fs4.mkdir(aiDir, { recursive: true });
1540
+ await fs4.writeFile(promptPath, prompt, "utf-8");
1541
+ printPromptResult(promptPath, target, aiTool, goal);
1542
+ const copyChoice = await select(
1543
+ "Copy prompt to clipboard?",
1544
+ [
1545
+ { label: "Copy prompt", value: "yes" },
1546
+ { label: "No, keep file only", value: "no" }
1547
+ ],
1548
+ 0
1549
+ );
1550
+ if (copyChoice === "yes") {
1551
+ try {
1552
+ await copyToClipboard(prompt);
1553
+ console.log(` ${chalk4.green("\u2713")} ${chalk4.white.bold("Prompt copied to clipboard")}
1554
+ `);
1555
+ } catch (error) {
1556
+ console.log(
1557
+ ` ${chalk4.yellow("!")} ${chalk4.yellow("Clipboard copy unavailable:")} ${chalk4.gray(error.message)}
1558
+ `
1559
+ );
1560
+ }
1561
+ }
1562
+ }
1563
+ async function collectExportFacts(exporter) {
1564
+ const counts = {};
1565
+ const rootEntries = await safeReadDir(exporter.outDir);
1566
+ for (const item of IMPORTANT_DIRS) {
1567
+ counts[item] = item === "index.html" ? await exists(path5.join(exporter.outDir, item)) ? 1 : 0 : await countEntries(path5.join(exporter.outDir, item));
1568
+ }
1569
+ return {
1570
+ sourceUrl: exporter.siteUrl,
1571
+ outputDir: exporter.outDir,
1572
+ platformName: exporter.platform.displayName,
1573
+ rootEntries,
1574
+ counts
1575
+ };
1576
+ }
1577
+ async function exists(filePath) {
1578
+ try {
1579
+ await fs4.access(filePath);
1580
+ return true;
1581
+ } catch {
1582
+ return false;
1583
+ }
1584
+ }
1585
+ async function safeReadDir(dir) {
1586
+ try {
1587
+ return (await fs4.readdir(dir)).sort();
1588
+ } catch {
1589
+ return [];
1590
+ }
1591
+ }
1592
+ async function countEntries(dir) {
1593
+ try {
1594
+ return (await fs4.readdir(dir)).length;
1595
+ } catch {
1596
+ return 0;
1597
+ }
1598
+ }
1599
+ function buildConversionPrompt(target, aiTool, goal, facts) {
1600
+ const rootEntries = facts.rootEntries.length ? facts.rootEntries.join(", ") : "No root entries detected";
1601
+ const exportDir = quoteForPrompt(facts.outputDir);
1602
+ const projectDir = quoteForPrompt(path5.join(facts.outputDir, target.projectDir));
1603
+ const scaffoldCommand = `cd ${exportDir}; ${target.scaffold}`;
1604
+ const sourceAssets = quoteForPrompt(path5.join(facts.outputDir, "assets", "*"));
1605
+ const destinationAssets = quoteForPrompt(
1606
+ path5.join(facts.outputDir, target.projectDir, target.staticDir, "assets")
1607
+ );
1608
+ const windowsAssetCopyCommand = `New-Item -ItemType Directory -Force -Path ${destinationAssets}; Copy-Item -Path ${sourceAssets} -Destination ${destinationAssets} -Recurse -Force`;
1609
+ const unixSourceAssets = quoteForPrompt(toPosixPath(path5.join(facts.outputDir, "assets")));
1610
+ const unixDestinationParent = quoteForPrompt(
1611
+ toPosixPath(path5.join(facts.outputDir, target.projectDir, target.staticDir))
1612
+ );
1613
+ const unixAssetCopyCommand = `mkdir -p ${unixDestinationParent} && cp -R ${unixSourceAssets} ${unixDestinationParent}/assets`;
1614
+ const directoryLines = IMPORTANT_DIRS.map(
1615
+ (dir) => `Inspect ${dir}: ${facts.counts[dir]} item(s) detected in the export.`
1616
+ );
1617
+ const promptLines = [
1618
+ `You are ${aiTool.agentName} working inside a local export created by Cooksite / Framer Export.`,
1619
+ "This brief was generated by the Cooksite AI Prompt Assistant BETA.",
1620
+ "Treat this beta prompt as a strict conversion checklist, not as permission to skip inspection.",
1621
+ "Your mission is to convert this exported static mirror into a clean production project.",
1622
+ `Target stack: ${target.stack}.`,
1623
+ `Selected AI coding tool: ${aiTool.displayName}.`,
1624
+ `Selected conversion situation: ${goal.label}.`,
1625
+ `Primary instruction for this situation: ${goal.instruction}`,
1626
+ `Main priority: ${goal.priority}`,
1627
+ `Source URL from the real export: ${facts.sourceUrl}`,
1628
+ `Detected platform from the real export: ${facts.platformName}`,
1629
+ `Export folder to inspect first: ${facts.outputDir}`,
1630
+ `Root entries actually present: ${rootEntries}`,
1631
+ `Create the converted project in: ${projectDir}`,
1632
+ `Recommended scaffold command: ${scaffoldCommand}`,
1633
+ `Expected important target files: ${target.entryFiles}`,
1634
+ `Routing guidance: ${target.routing}`,
1635
+ `Static assets destination for this stack: ${target.staticDir}/assets inside the converted project.`,
1636
+ `PowerShell command to copy exported assets after scaffolding: ${windowsAssetCopyCommand}`,
1637
+ `macOS/Linux command to copy exported assets after scaffolding: ${unixAssetCopyCommand}`,
1638
+ "Run the asset copy command; do not recreate, redownload, rename randomly, or replace real exported assets with placeholders.",
1639
+ "After copying assets, update every image, video, font, CSS url(), and script reference to the new local asset path.",
1640
+ "Do not rush. Take time to inspect the export before writing the final implementation.",
1641
+ "Do not invent brand names, copy, images, links, animations, colors, or sections.",
1642
+ "Use only real information found in index.html, CSS files, JavaScript files, data files, and assets.",
1643
+ "If a detail is missing, inspect more files instead of guessing.",
1644
+ "If a vendor script is minified or hard to understand, identify what behavior it provides before replacing it.",
1645
+ "Keep the final result professional, clean, responsive, and maintainable.",
1646
+ "Do not simplify the site because a section, animation, page, or layout is difficult.",
1647
+ "If something is hard, break it into smaller components and keep working until the result matches closely.",
1648
+ "For every exported page, aim for the closest possible pixel-perfect result: spacing, typography, images, viewport behavior, and motion.",
1649
+ "Do not merge multiple pages into one generic page unless the export proves they are duplicate routes.",
1650
+ "Do not replace complex exported sections with summaries, cards, screenshots, or placeholder blocks.",
1651
+ "If a page has visual depth, overlapping layers, sticky sections, galleries, or scroll effects, recreate those behaviors instead of flattening them.",
1652
+ "Start by listing the files and directories that matter for the conversion.",
1653
+ ...directoryLines,
1654
+ "Read index.html fully enough to understand page structure, metadata, linked assets, and scripts.",
1655
+ "Read the CSS files that define layout, typography, responsive rules, and visual details.",
1656
+ "Inspect scripts/vendor only to understand required interactions; do not blindly copy huge vendor bundles.",
1657
+ "Inspect scripts/modules for page-specific logic, animations, sliders, menus, and dynamic behavior.",
1658
+ "Inspect data files for CMS-like content, page data, configuration, or serialized props.",
1659
+ "Inspect assets/images and preserve the real image files that are actually used.",
1660
+ "Inspect assets/fonts and preserve font loading if the design depends on custom fonts.",
1661
+ "Inspect subpages if it contains exported pages; map them to routes only when they represent real pages.",
1662
+ "If subpages contains pages, convert each meaningful page with its own route and page component.",
1663
+ "For each converted page, compare against the original exported HTML route and adjust until it is visually close.",
1664
+ "Create a clean project structure instead of dumping everything into one component.",
1665
+ "Separate global layout, page sections, shared components, data helpers, and styles.",
1666
+ "Use semantic HTML for headings, navigation, buttons, forms, sections, and footer content.",
1667
+ "Preserve the original hierarchy of visible content unless there is a clear bug to fix.",
1668
+ "Preserve real URLs and links, but convert local asset paths to the new project structure.",
1669
+ "Move static assets into the target framework public/static asset location when appropriate.",
1670
+ "Do not keep broken CDN references if a local exported asset already exists.",
1671
+ "Do not hardcode absolute machine paths into source files; use framework-relative public asset paths after copying.",
1672
+ "Keep original filenames when possible so CSS and content references remain traceable.",
1673
+ "Do not leave unused analytics, editor badges, platform badges, or export-only scripts in the new app.",
1674
+ "Replace platform-specific runtime code with native framework components when possible.",
1675
+ "Keep interactions that users can see: menus, hover states, forms, sliders, animations, and scroll effects.",
1676
+ "If an interaction is too complex, implement a clean equivalent and document the difference briefly.",
1677
+ "Keep responsive behavior for desktop, tablet, and mobile.",
1678
+ "Check layout at small widths and avoid fixed desktop-only dimensions unless the original requires them.",
1679
+ "Use CSS variables or a clear theme file for colors, spacing, radius, shadows, and typography.",
1680
+ "Name components after their role: Hero, Header, FeatureGrid, Gallery, Pricing, Footer, and similar real sections.",
1681
+ "Avoid generic placeholder components if the exported site has specific section meaning.",
1682
+ "Use TypeScript types where they make the content or component props clearer.",
1683
+ "Do not add unnecessary libraries unless they replace a real exported behavior cleanly.",
1684
+ "If you add a library, explain why it is needed and where it is used.",
1685
+ "Keep package.json scripts standard: dev, build, preview, lint when available.",
1686
+ "Keep the build reproducible from a fresh install.",
1687
+ "After implementing, run the install/build/typecheck commands available for the target project.",
1688
+ "Fix any build errors instead of leaving TODOs.",
1689
+ "Open the generated app locally if possible and compare against the exported index.html visually.",
1690
+ "When there are subpages, compare every generated route against its matching exported file.",
1691
+ "Verify that every visible image loads from the new project.",
1692
+ "Verify that font rendering is close to the export.",
1693
+ "Verify that navigation and internal links work.",
1694
+ "Verify that responsive breakpoints do not overlap or hide important content.",
1695
+ "Verify that there are no console errors caused by missing assets or copied platform scripts.",
1696
+ "Keep accessibility basics: alt text when inferable, keyboard-reachable controls, visible focus states.",
1697
+ "Keep SEO basics: title, meta description if present, canonical only if it is correct, Open Graph when present.",
1698
+ "Do not fabricate SEO copy; reuse existing metadata or ask for missing copy if necessary.",
1699
+ "Commit to a small number of high-quality files rather than many noisy fragments.",
1700
+ "If a section repeats, extract a reusable component and data array.",
1701
+ "If a section is unique, keep it simple and local to the page.",
1702
+ "Document important migration decisions in a short README inside the converted project.",
1703
+ "The README must mention the source export folder, target stack, setup command, and known limitations.",
1704
+ "Do not delete the original export folder.",
1705
+ "Do not modify unrelated files outside the new converted project unless required for setup.",
1706
+ "If the worktree has existing changes, avoid reverting or overwriting them.",
1707
+ "Before large edits, inspect first and explain the conversion plan briefly.",
1708
+ "Then implement the conversion step by step until the app builds.",
1709
+ "Final answer must summarize what was converted, where the new project lives, and which checks passed.",
1710
+ "If something could not be converted, state the exact file or behavior and the reason.",
1711
+ "Quality bar: the result should feel like a real hand-built production app, not an automated scrape or simplified demo."
1712
+ ];
1713
+ return [
1714
+ `# ${aiTool.displayName} Conversion Prompt - ${target.label}`,
1715
+ "",
1716
+ "Status: BETA assistant output. Review the export carefully and follow the real files.",
1717
+ `Generated from real export: ${facts.outputDir}`,
1718
+ `AI tool: ${aiTool.displayName}`,
1719
+ `Target: ${target.label}`,
1720
+ `Situation: ${goal.label}`,
1721
+ "",
1722
+ promptLines.map((line, index) => `${String(index + 1).padStart(2, "0")}. ${line}`).join("\n"),
1723
+ ""
1724
+ ].join("\n");
1725
+ }
1726
+ function quoteForPrompt(value) {
1727
+ return `"${value.replace(/"/g, '\\"')}"`;
1728
+ }
1729
+ function toPosixPath(value) {
1730
+ return value.replace(/\\/g, "/");
1731
+ }
1732
+ async function copyToClipboard(text) {
1733
+ const commands = process.platform === "win32" ? [{ command: "clip", args: [] }] : process.platform === "darwin" ? [{ command: "pbcopy", args: [] }] : [
1734
+ { command: "wl-copy", args: [] },
1735
+ { command: "xclip", args: ["-selection", "clipboard"] },
1736
+ { command: "xsel", args: ["--clipboard", "--input"] }
1737
+ ];
1738
+ let lastError = null;
1739
+ for (const item of commands) {
1740
+ try {
1741
+ await pipeToCommand(item.command, item.args, text);
1742
+ return;
1743
+ } catch (error) {
1744
+ lastError = error;
1745
+ }
1746
+ }
1747
+ throw lastError || new Error("No clipboard command found");
1748
+ }
1749
+ function pipeToCommand(command, args, input) {
1750
+ return new Promise((resolve, reject) => {
1751
+ const child = spawn(command, args, { stdio: ["pipe", "ignore", "pipe"] });
1752
+ let stderr = "";
1753
+ let settled = false;
1754
+ const finish = (error) => {
1755
+ if (settled) return;
1756
+ settled = true;
1757
+ if (error) reject(error);
1758
+ else resolve();
1759
+ };
1760
+ child.stderr?.on("data", (chunk) => {
1761
+ stderr += chunk.toString();
1762
+ });
1763
+ child.on("error", finish);
1764
+ child.on("close", (code) => {
1765
+ if (code === 0) {
1766
+ finish();
1767
+ } else {
1768
+ finish(new Error(`${command} exited with ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
1769
+ }
1770
+ });
1771
+ child.stdin?.end(input);
1772
+ });
1773
+ }
1774
+ function printPromptResult(promptPath, target, aiTool, goal) {
1775
+ const w = maxWidth();
1776
+ const isSmall = w < 50;
1777
+ const relPath = path5.relative(process.cwd(), promptPath) || promptPath;
1778
+ console.log("");
1779
+ if (!isSmall) {
1780
+ console.log(boxTop(w));
1781
+ console.log(boxLine(w, chalk4.bold.white(" AI Prompt Ready - BETA")));
1782
+ console.log(boxSep(w));
1783
+ console.log(boxLine(w, ` Tool: ${aiTool.displayName}`));
1784
+ console.log(boxLine(w, ` Stack: ${target.label}`));
1785
+ console.log(boxLine(w, ` Situation: ${goal.label}`));
1786
+ console.log(boxLine(w, ` File: ${relPath}`));
1787
+ console.log(boxBot(w));
1788
+ } else {
1789
+ console.log(chalk4.bold.white(" AI Prompt Ready - BETA"));
1790
+ console.log(` Tool: ${chalk4.hex("#D4A017")(aiTool.displayName)}`);
1791
+ console.log(` Stack: ${chalk4.hex("#D4A017")(target.label)}`);
1792
+ console.log(` Situation: ${chalk4.hex("#D4A017")(goal.label)}`);
1793
+ console.log(` File: ${chalk4.hex("#D4A017")(relPath)}`);
1794
+ }
1795
+ console.log("");
1796
+ }
1797
+ var IMPORTANT_DIRS, AI_TOOLS, TARGETS, GOALS;
1798
+ var init_prompt_assistant = __esm({
1799
+ "src/ai/prompt-assistant.ts"() {
1800
+ "use strict";
1801
+ init_select();
1802
+ init_box();
1803
+ IMPORTANT_DIRS = [
1804
+ "index.html",
1805
+ "styles",
1806
+ "scripts/vendor",
1807
+ "scripts/modules",
1808
+ "assets/images",
1809
+ "assets/videos",
1810
+ "assets/fonts",
1811
+ "assets/misc",
1812
+ "data",
1813
+ "subpages"
1814
+ ];
1815
+ AI_TOOLS = [
1816
+ {
1817
+ id: "claude-code",
1818
+ label: "Claude Code",
1819
+ displayName: "Claude Code",
1820
+ agentName: "Claude Code"
1821
+ },
1822
+ {
1823
+ id: "codex",
1824
+ label: "Codex",
1825
+ displayName: "Codex",
1826
+ agentName: "Codex coding agent"
1827
+ },
1828
+ {
1829
+ id: "opencode",
1830
+ label: "OpenCode",
1831
+ displayName: "OpenCode",
1832
+ agentName: "OpenCode"
1833
+ },
1834
+ {
1835
+ id: "other-ai",
1836
+ label: "Other AI coding agent",
1837
+ displayName: "Other AI",
1838
+ agentName: "AI coding agent"
1839
+ }
1840
+ ];
1841
+ TARGETS = [
1842
+ {
1843
+ id: "react-vite",
1844
+ label: "React + Vite + TypeScript",
1845
+ stack: "React 18/19, TypeScript, Vite, CSS modules or plain CSS",
1846
+ projectDir: "converted-react-vite",
1847
+ staticDir: "public",
1848
+ scaffold: "npm create vite@latest converted-react-vite -- --template react-ts",
1849
+ entryFiles: "src/main.tsx, src/App.tsx, src/components/*, src/styles/*",
1850
+ routing: "Use React Router only if multiple exported pages exist in subpages/."
1851
+ },
1852
+ {
1853
+ id: "nextjs-app-router",
1854
+ label: "Next.js App Router",
1855
+ stack: "Next.js App Router, TypeScript, React Server Components where useful",
1856
+ projectDir: "converted-nextjs",
1857
+ staticDir: "public",
1858
+ scaffold: "npx create-next-app@latest converted-nextjs --ts --app --eslint",
1859
+ entryFiles: "app/page.tsx, app/layout.tsx, components/*, public/*",
1860
+ routing: "Map exported subpages to app routes and keep shared layout code reusable."
1861
+ },
1862
+ {
1863
+ id: "vue-vite",
1864
+ label: "Vue + Vite + TypeScript",
1865
+ stack: "Vue 3, TypeScript, Vite, single-file components",
1866
+ projectDir: "converted-vue-vite",
1867
+ staticDir: "public",
1868
+ scaffold: "npm create vite@latest converted-vue-vite -- --template vue-ts",
1869
+ entryFiles: "src/main.ts, src/App.vue, src/components/*.vue, src/styles/*",
1870
+ routing: "Use Vue Router only if multiple exported pages exist in subpages/."
1871
+ },
1872
+ {
1873
+ id: "sveltekit",
1874
+ label: "SvelteKit",
1875
+ stack: "SvelteKit, TypeScript, componentized routes and assets",
1876
+ projectDir: "converted-sveltekit",
1877
+ staticDir: "static",
1878
+ scaffold: "npm create svelte@latest converted-sveltekit",
1879
+ entryFiles: "src/routes/+page.svelte, src/lib/components/*, static/*",
1880
+ routing: "Create SvelteKit routes for meaningful exported pages when subpages/ exists."
1881
+ },
1882
+ {
1883
+ id: "astro",
1884
+ label: "Astro",
1885
+ stack: "Astro, TypeScript, island components only where interactivity is needed",
1886
+ projectDir: "converted-astro",
1887
+ staticDir: "public",
1888
+ scaffold: "npm create astro@latest converted-astro",
1889
+ entryFiles: "src/pages/index.astro, src/components/*, public/*",
1890
+ routing: "Use Astro pages for exported subpages and avoid unnecessary client JavaScript."
1891
+ }
1892
+ ];
1893
+ GOALS = [
1894
+ {
1895
+ id: "clean-rebuild",
1896
+ label: "Clean professional rebuild",
1897
+ instruction: "Rebuild the export as clean production code, not as a one-file HTML clone, while preserving the full page experience.",
1898
+ priority: "Readable structure, maintainability, complete pages, and faithful visual result."
1899
+ },
1900
+ {
1901
+ id: "pixel-perfect",
1902
+ label: "Pixel-perfect visual migration",
1903
+ instruction: "Prioritize pixel-perfect fidelity before refactoring anything aggressively, even when the layout is difficult.",
1904
+ priority: "Spacing, typography, responsive behavior, colors, media, animations, and page-by-page fidelity."
1905
+ },
1906
+ {
1907
+ id: "component-system",
1908
+ label: "Reusable component system",
1909
+ instruction: "Extract repeated UI blocks into reusable components with clean props without simplifying the original pages.",
1910
+ priority: "Components, layout primitives, naming, future editability, and complete section coverage."
1911
+ },
1912
+ {
1913
+ id: "performance-seo",
1914
+ label: "Performance and SEO rebuild",
1915
+ instruction: "Rebuild the site while reducing unused vendor code and improving SEO basics without losing visual fidelity.",
1916
+ priority: "Fast loading, semantic markup, metadata, accessibility, asset hygiene, and faithful pages."
1917
+ }
1918
+ ];
1299
1919
  }
1300
1920
  });
1301
1921
 
1302
1922
  // src/cli/cooking.ts
1303
- import chalk6 from "chalk";
1304
- var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, PALETTE, CookingSpinner;
1923
+ import chalk5 from "chalk";
1924
+ var SHINE_WIDTH, FRAME_INTERVAL, SPINNER_FRAMES, CookingSpinner;
1305
1925
  var init_cooking = __esm({
1306
1926
  "src/cli/cooking.ts"() {
1307
1927
  "use strict";
1928
+ init_theme();
1308
1929
  SHINE_WIDTH = 10;
1309
- FRAME_INTERVAL = 140;
1930
+ FRAME_INTERVAL = 80;
1310
1931
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1311
- PALETTE = [
1312
- (s) => chalk6.hex("#D4A017")(s),
1313
- (s) => chalk6.hex("#E8A317")(s),
1314
- (s) => chalk6.hex("#DAA520")(s),
1315
- (s) => chalk6.hex("#F0C040")(s),
1316
- (s) => chalk6.hex("#C4953A")(s),
1317
- (s) => chalk6.hex("#E6B83C")(s),
1318
- (s) => chalk6.hex("#C8A951")(s),
1319
- (s) => chalk6.hex("#D9B44D")(s)
1320
- ];
1321
1932
  CookingSpinner = class {
1322
1933
  interval = null;
1323
- bgInterval = null;
1324
1934
  frame = 0;
1325
1935
  phase = "";
1326
1936
  active = false;
1327
- bar = 0;
1328
- barDir = 1;
1329
1937
  start(phase = "") {
1330
1938
  this.phase = phase;
1331
1939
  this.frame = 0;
1332
- this.bar = 0;
1333
- this.barDir = 1;
1334
1940
  this.active = true;
1335
1941
  this.draw();
1336
1942
  this.interval = setInterval(() => {
1337
1943
  this.frame++;
1338
1944
  this.draw();
1339
1945
  }, FRAME_INTERVAL);
1340
- this.bgInterval = setInterval(() => {
1341
- this.bar++;
1342
- this.draw();
1343
- }, 800);
1344
1946
  }
1345
1947
  update(phase) {
1346
1948
  this.phase = phase;
1347
- if (!this.active) this.draw();
1348
1949
  }
1349
1950
  log(message) {
1350
1951
  if (this.active) {
@@ -1361,27 +1962,15 @@ var init_cooking = __esm({
1361
1962
  clearInterval(this.interval);
1362
1963
  this.interval = null;
1363
1964
  }
1364
- if (this.bgInterval) {
1365
- clearInterval(this.bgInterval);
1366
- this.bgInterval = null;
1367
- }
1368
1965
  process.stdout.write("\r\x1B[2K");
1369
1966
  }
1370
1967
  draw() {
1371
1968
  if (!this.active) return;
1372
1969
  const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
1373
- const paletteFn = PALETTE[this.frame % PALETTE.length];
1374
- const dotPhase = Math.floor(this.frame / 3) % 4;
1375
- const dotStr = dotPhase === 0 ? " " : dotPhase === 1 ? ". " : dotPhase === 2 ? ".. " : "...";
1376
- const shimmer = this.renderShimmer("Cooking");
1377
- const spinnerChar = chalk6.hex("#F0C040")(spinner);
1378
- const barLen = 12;
1379
- const barPos = this.bar % (barLen * 2);
1380
- const barStr = this.renderBar(barLen, barPos);
1381
- const phaseStr = this.phase ? ` ${chalk6.gray(this.limitLen(this.phase, 38))}` : "";
1382
- process.stdout.write(
1383
- `\r\x1B[2K ${spinnerChar} ${shimmer} ${paletteFn(dotStr)} ${barStr}${phaseStr}`
1384
- );
1970
+ const shimmer = this.renderShimmer("f-export");
1971
+ const frameStr = ui.primary(spinner);
1972
+ const phaseStr = this.phase ? ` ${ui.muted(this.limitLen(this.phase, 52))}` : "";
1973
+ process.stdout.write(`\r\x1B[2K ${frameStr} ${shimmer}${phaseStr}`);
1385
1974
  }
1386
1975
  renderShimmer(text) {
1387
1976
  let result = "";
@@ -1391,34 +1980,16 @@ var init_cooking = __esm({
1391
1980
  if (dist < SHINE_WIDTH) {
1392
1981
  const t = 1 - dist / SHINE_WIDTH;
1393
1982
  const g = Math.floor(160 + t * 95);
1394
- result += chalk6.rgb(g, g, g)(text[i]);
1983
+ result += chalk5.rgb(250, Math.min(255, g), Math.min(255, Math.floor(g * 0.75)))(text[i]);
1395
1984
  } else {
1396
- result += chalk6.rgb(160, 160, 160)(text[i]);
1985
+ result += ui.muted(text[i]);
1397
1986
  }
1398
1987
  }
1399
1988
  return result;
1400
1989
  }
1401
- renderBar(len, pos) {
1402
- let s = "";
1403
- for (let i = 0; i < len; i++) {
1404
- const dist = Math.abs(i - pos);
1405
- if (dist < 3) {
1406
- const t = 1 - dist / 3;
1407
- const r = Math.floor(200 + t * 55);
1408
- const g = Math.floor(160 + t * 50);
1409
- const b = Math.floor(20 + t * 30);
1410
- s += chalk6.rgb(r, g, b)("\u258C");
1411
- } else if (dist < 6) {
1412
- s += chalk6.rgb(140, 100, 20)("\u258C");
1413
- } else {
1414
- s += chalk6.rgb(50, 35, 10)(" ");
1415
- }
1416
- }
1417
- return `[${s}]`;
1418
- }
1419
1990
  limitLen(s, max) {
1420
1991
  if (s.length <= max) return s;
1421
- return s.slice(0, max - 3) + "\u2026";
1992
+ return s.slice(0, max - 1) + "\u2026";
1422
1993
  }
1423
1994
  };
1424
1995
  }
@@ -1430,9 +2001,9 @@ __export(exporter_exports, {
1430
2001
  FramerExporter: () => FramerExporter,
1431
2002
  deriveOutputName: () => deriveOutputName
1432
2003
  });
1433
- import fs4 from "fs/promises";
1434
- import path5 from "path";
1435
- import chalk7 from "chalk";
2004
+ import fs5 from "fs/promises";
2005
+ import path6 from "path";
2006
+ import chalk6 from "chalk";
1436
2007
  function deriveOutputName(url, platformName) {
1437
2008
  try {
1438
2009
  const parsed = new URL(url);
@@ -1464,8 +2035,10 @@ var init_exporter = __esm({
1464
2035
  init_download2();
1465
2036
  init_output();
1466
2037
  init_summary();
2038
+ init_prompt_assistant();
1467
2039
  init_platforms();
1468
2040
  init_cooking();
2041
+ init_theme();
1469
2042
  FramerExporter = class {
1470
2043
  siteUrl;
1471
2044
  outDir;
@@ -1491,12 +2064,14 @@ var init_exporter = __esm({
1491
2064
  }
1492
2065
  }
1493
2066
  async run(includeSubpages = false) {
1494
- console.log(chalk7.bold.hex("#D4A017")("\n Cooksite v4 - Full Mirror & Clean Export\n"));
1495
- info("Source : " + chalk7.underline(this.siteUrl));
1496
- info("Output : " + chalk7.yellow(this.outDir));
1497
- info("Platform : " + chalk7.hex("#D4A017")(this.platform.displayName));
2067
+ console.log(`
2068
+ ${ui.text.bold("Framer Export")} ${chip("mirror")} ${ui.muted("v4 pipeline")}
2069
+ `);
2070
+ info("Source : " + chalk6.underline(this.siteUrl));
2071
+ info("Output : " + ui.primary(this.outDir));
2072
+ info("Platform : " + ui.primary(this.platform.displayName));
1498
2073
  if (includeSubpages) {
1499
- info("Subpages : " + chalk7.green("enabled"));
2074
+ info("Subpages : " + ui.success("enabled"));
1500
2075
  }
1501
2076
  console.log("");
1502
2077
  this.cooking = new CookingSpinner();
@@ -1514,7 +2089,7 @@ var init_exporter = __esm({
1514
2089
  "data",
1515
2090
  "subpages"
1516
2091
  ]) {
1517
- await fs4.mkdir(path5.join(this.outDir, d), { recursive: true });
2092
+ await fs5.mkdir(path6.join(this.outDir, d), { recursive: true });
1518
2093
  }
1519
2094
  log("Output directory structure created");
1520
2095
  this.cooking.update("Fetching SSR HTML...");
@@ -1524,12 +2099,14 @@ var init_exporter = __esm({
1524
2099
  this.ssrHTML = buf.toString("utf-8");
1525
2100
  success("SSR HTML fetched (" + (this.ssrHTML.length / 1024).toFixed(1) + " KB)");
1526
2101
  } catch (e) {
1527
- log(chalk7.red("Could not fetch SSR HTML: " + e.message));
2102
+ log(chalk6.red("Could not fetch SSR HTML: " + e.message));
1528
2103
  }
1529
2104
  const htmlDetected = detectPlatform(this.siteUrl, this.ssrHTML);
1530
2105
  if (htmlDetected.name !== this.platform.name) {
1531
2106
  this.platform = htmlDetected;
1532
- log("Platform refined: " + chalk7.hex("#D4A017")(this.platform.displayName) + " (from HTML analysis)");
2107
+ log(
2108
+ "Platform refined: " + ui.primary(this.platform.displayName) + " (from HTML analysis)"
2109
+ );
1533
2110
  }
1534
2111
  await launchAndCapture(this);
1535
2112
  if (includeSubpages && this.page) {
@@ -1545,6 +2122,7 @@ var init_exporter = __esm({
1545
2122
  console.log("");
1546
2123
  success("Export complete!");
1547
2124
  await printSummary(this);
2125
+ await runAiPromptAssistant(this);
1548
2126
  }
1549
2127
  async crawlSubpages() {
1550
2128
  this.cooking?.update("Discovering sub-pages...");
@@ -1557,7 +2135,8 @@ var init_exporter = __esm({
1557
2135
  const hrefs = /* @__PURE__ */ new Set();
1558
2136
  for (const a of anchors) {
1559
2137
  const href = a.href;
1560
- if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#")) continue;
2138
+ if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("#"))
2139
+ continue;
1561
2140
  try {
1562
2141
  const u = new URL(href);
1563
2142
  const h = u.hostname.replace(/^www\./, "");
@@ -1585,8 +2164,8 @@ var init_exporter = __esm({
1585
2164
  });
1586
2165
  const slug = this.deriveSlug(link, baseUrl);
1587
2166
  const filename = slug + ".html";
1588
- const filepath = path5.join(this.outDir, "subpages", filename);
1589
- await fs4.writeFile(filepath, html, "utf-8");
2167
+ const filepath = path6.join(this.outDir, "subpages", filename);
2168
+ await fs5.writeFile(filepath, html, "utf-8");
1590
2169
  log(" Saved: subpages/" + filename);
1591
2170
  } catch (e) {
1592
2171
  log(" Skipped " + link + ": " + e.message);
@@ -1608,94 +2187,37 @@ var init_exporter = __esm({
1608
2187
  }
1609
2188
  });
1610
2189
 
1611
- // src/cli/select.ts
1612
- import readline from "readline";
1613
- import { stdin, stdout } from "process";
1614
- import chalk8 from "chalk";
1615
- async function select(question, options, defaultIndex = 0) {
1616
- return new Promise((resolve) => {
1617
- let selected = defaultIndex;
1618
- const render = (initial = false) => {
1619
- if (!initial) {
1620
- stdout.write(`\x1B[${options.length + 1}A`);
1621
- stdout.write("\x1B[J");
1622
- }
1623
- console.log(` ${chalk8.hex("#D4A017")("?")} ${chalk8.white.bold(question)}`);
1624
- for (let i = 0; i < options.length; i++) {
1625
- if (i === selected) {
1626
- console.log(` ${chalk8.hex("#D4A017")(">")} ${chalk8.white.bold(options[i].label)}`);
1627
- } else {
1628
- console.log(` ${chalk8.gray(options[i].label)}`);
1629
- }
1630
- }
1631
- };
1632
- render(true);
1633
- readline.emitKeypressEvents(stdin);
1634
- const wasTTY = stdin.isTTY || false;
1635
- if (wasTTY) stdin.setRawMode(true);
1636
- const onKeypress = (_str, key) => {
1637
- if (!key) return;
1638
- if (key.name === "up" && selected > 0) {
1639
- selected--;
1640
- render();
1641
- } else if (key.name === "down" && selected < options.length - 1) {
1642
- selected++;
1643
- render();
1644
- } else if (key.name === "return") {
1645
- if (wasTTY) stdin.setRawMode(false);
1646
- stdin.removeListener("keypress", onKeypress);
1647
- stdin.pause();
1648
- stdout.write(`\x1B[${options.length + 1}A`);
1649
- stdout.write("\x1B[J");
1650
- console.log(` ${chalk8.green("\u2713")} ${chalk8.white.bold(question)} ${chalk8.hex("#D4A017")(options[selected].label)}
1651
- `);
1652
- resolve(options[selected].value);
1653
- } else if (key.ctrl && key.name === "c" || key.name === "escape") {
1654
- if (wasTTY) stdin.setRawMode(false);
1655
- stdin.removeListener("keypress", onKeypress);
1656
- process.exit(0);
1657
- }
1658
- };
1659
- stdin.resume();
1660
- stdin.on("keypress", onKeypress);
1661
- });
1662
- }
1663
- var init_select = __esm({
1664
- "src/cli/select.ts"() {
1665
- "use strict";
1666
- }
1667
- });
1668
-
1669
2190
  // src/cli/setup.ts
1670
2191
  var setup_exports = {};
1671
2192
  __export(setup_exports, {
1672
2193
  runSetup: () => runSetup
1673
2194
  });
1674
2195
  import readline2 from "readline/promises";
1675
- import { stdin as stdin2, stdout as stdout2 } from "process";
1676
- import path6 from "path";
2196
+ import { stdin as stdin3, stdout as stdout3 } from "process";
2197
+ import path7 from "path";
1677
2198
  import { URL as URL4 } from "url";
1678
- import chalk9 from "chalk";
2199
+ import chalk7 from "chalk";
1679
2200
  function drawHeader(title) {
1680
2201
  const w = maxWidth();
1681
2202
  if (w < 50) {
1682
2203
  console.log(`
1683
- ${chalk9.hex("#D4A017")("\u25CF")} ${chalk9.bold.white(title)}`);
2204
+ ${bullet("\u25CF")} ${ui.text.bold(title)}`);
1684
2205
  return;
1685
2206
  }
1686
2207
  console.log(boxTop(w));
1687
- console.log(boxLine(w, chalk9.bold.white(" " + title)));
2208
+ console.log(boxLine(w, ui.text.bold(" " + title)));
1688
2209
  console.log(boxBot(w));
1689
2210
  console.log("");
1690
2211
  }
1691
2212
  async function runSetup(legacyMode = false) {
1692
2213
  showBanner();
1693
- console.log(chalk9.bold.white(" Welcome to Framer Export setup.\n"));
1694
- console.log(chalk9.gray(" Supports Framer, Webflow, and Wix sites.\n"));
1695
- const rl = readline2.createInterface({ input: stdin2, output: stdout2 });
2214
+ console.log(` ${ui.text.bold("Framer Export setup")} ${chip("interactive")}`);
2215
+ console.log(` ${ui.muted("Export Framer, Webflow, and Wix sites into a clean local mirror.")}
2216
+ `);
2217
+ const rl = readline2.createInterface({ input: stdin3, output: stdout3 });
1696
2218
  const ask = async (question, defaultVal) => {
1697
- const suffix = defaultVal ? chalk9.gray(` (${defaultVal})`) : "";
1698
- const prompt = ` ${chalk9.hex("#D4A017")("?")} ${chalk9.white.bold(question)}${suffix} ${chalk9.gray(">")} `;
2219
+ const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
2220
+ const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
1699
2221
  const answer = await rl.question(prompt);
1700
2222
  return answer.trim() || defaultVal || "";
1701
2223
  };
@@ -1707,42 +2229,42 @@ async function runSetup(legacyMode = false) {
1707
2229
  new URL4(input);
1708
2230
  siteUrl = input;
1709
2231
  } catch {
1710
- console.log(` ${chalk9.red("\u2717")} ${chalk9.red("Invalid URL. Enter a valid URL (https://...)")}
2232
+ console.log(` ${ui.error("\u2717")} ${ui.error("Invalid URL. Enter a valid URL (https://...)")}
1711
2233
  `);
1712
2234
  }
1713
2235
  }
1714
- console.log(` ${chalk9.green("\u2713")} ${chalk9.green("URL:")} ${chalk9.underline(siteUrl)}
2236
+ console.log(` ${ui.success("\u2713")} ${ui.success("URL:")} ${chalk7.underline(siteUrl)}
1715
2237
  `);
1716
2238
  drawHeader("Step 2 : Platform");
1717
2239
  const detected = detectPlatform(siteUrl);
1718
2240
  let platformName;
1719
2241
  if (legacyMode) {
1720
- console.log(` ${chalk9.blue("i")} Auto-detected: ${chalk9.hex("#D4A017")(detected.displayName)}`);
2242
+ console.log(` ${ui.info("i")} Auto-detected: ${ui.primary(detected.displayName)}`);
1721
2243
  const platformInput = await ask("Platform (framer/webflow/wix)", detected.name);
1722
2244
  platformName = ["framer", "webflow", "wix"].includes(platformInput) ? platformInput : detected.name;
1723
- console.log(` ${chalk9.green("\u2713")} ${chalk9.green("Platform:")} ${chalk9.hex("#D4A017")(platformName)}
2245
+ console.log(` ${ui.success("\u2713")} ${ui.success("Platform:")} ${ui.primary(platformName)}
1724
2246
  `);
1725
2247
  } else {
1726
2248
  rl.close();
1727
2249
  const platforms = [
1728
- { label: `Framer${detected.name === "framer" ? chalk9.gray(" (detected)") : ""}`, value: "framer" },
1729
- { label: `Webflow${detected.name === "webflow" ? chalk9.gray(" (detected)") : ""}`, value: "webflow" },
1730
- { label: `Wix${detected.name === "wix" ? chalk9.gray(" (detected)") : ""}`, value: "wix" }
2250
+ { label: `Framer${detected.name === "framer" ? chalk7.gray(" (detected)") : ""}`, value: "framer" },
2251
+ { label: `Webflow${detected.name === "webflow" ? chalk7.gray(" (detected)") : ""}`, value: "webflow" },
2252
+ { label: `Wix${detected.name === "wix" ? chalk7.gray(" (detected)") : ""}`, value: "wix" }
1731
2253
  ];
1732
2254
  const defaultIdx = ["framer", "webflow", "wix"].indexOf(detected.name);
1733
2255
  platformName = await select("Select platform", platforms, Math.max(0, defaultIdx));
1734
2256
  }
1735
- const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin2, output: stdout2 });
2257
+ const rl2 = legacyMode ? rl : readline2.createInterface({ input: stdin3, output: stdout3 });
1736
2258
  const ask2 = async (question, defaultVal) => {
1737
- const suffix = defaultVal ? chalk9.gray(` (${defaultVal})`) : "";
1738
- const prompt = ` ${chalk9.hex("#D4A017")("?")} ${chalk9.white.bold(question)}${suffix} ${chalk9.gray(">")} `;
2259
+ const suffix = defaultVal ? chalk7.gray(` (${defaultVal})`) : "";
2260
+ const prompt = ` ${ui.primary("\u25CF")} ${ui.text.bold(question)}${suffix} ${ui.muted(">")} `;
1739
2261
  const answer = await rl2.question(prompt);
1740
2262
  return answer.trim() || defaultVal || "";
1741
2263
  };
1742
2264
  const derivedName = deriveOutputName(siteUrl, platformName);
1743
2265
  drawHeader("Step 3 : Output Directory");
1744
2266
  const outDir = await ask2("Output directory", "./" + derivedName);
1745
- console.log(` ${chalk9.green("\u2713")} ${chalk9.green("Output:")} ${chalk9.yellow(outDir)}
2267
+ console.log(` ${ui.success("\u2713")} ${ui.success("Output:")} ${ui.primary(outDir)}
1746
2268
  `);
1747
2269
  drawHeader("Step 4 : Options");
1748
2270
  let prettyPrint;
@@ -1751,15 +2273,15 @@ async function runSetup(legacyMode = false) {
1751
2273
  if (legacyMode) {
1752
2274
  const prettyAnswer = await ask2("Pretty-print JS files? (y/n)", "y");
1753
2275
  prettyPrint = prettyAnswer.toLowerCase().startsWith("y");
1754
- console.log(` ${chalk9.green("\u2713")} Pretty-print: ${prettyPrint ? chalk9.green("yes") : chalk9.red("no")}
2276
+ console.log(` ${ui.success("\u2713")} Pretty-print: ${prettyPrint ? ui.success("yes") : ui.error("no")}
1755
2277
  `);
1756
2278
  const subpagesAnswer = await ask2("Export sub-pages? (y/n)", "n");
1757
2279
  includeSubpages = subpagesAnswer.toLowerCase().startsWith("y");
1758
- console.log(` ${chalk9.green("\u2713")} Sub-pages: ${includeSubpages ? chalk9.green("yes") : chalk9.red("no")}
2280
+ console.log(` ${ui.success("\u2713")} Sub-pages: ${includeSubpages ? ui.success("yes") : ui.error("no")}
1759
2281
  `);
1760
2282
  const concurrencyAnswer = await ask2("Download concurrency", "12");
1761
2283
  concurrency = parseInt(concurrencyAnswer, 10) || 12;
1762
- console.log(` ${chalk9.green("\u2713")} Concurrency: ${chalk9.yellow(String(concurrency))}
2284
+ console.log(` ${ui.success("\u2713")} Concurrency: ${ui.primary(String(concurrency))}
1763
2285
  `);
1764
2286
  } else {
1765
2287
  rl2.close();
@@ -1782,19 +2304,18 @@ async function runSetup(legacyMode = false) {
1782
2304
  }
1783
2305
  const w = maxWidth();
1784
2306
  const isSmall = w < 50;
1785
- const G2 = chalk9.hex("#C4953A");
1786
- const B2 = chalk9.hex("#8B6914");
1787
2307
  console.log("");
1788
2308
  if (!isSmall) {
1789
2309
  console.log(boxTop(w));
1790
- console.log(boxLine(w, chalk9.bold.white(" Summary")));
2310
+ console.log(boxLine(w, ui.text.bold(" Summary")));
2311
+ console.log(boxSep(w));
1791
2312
  } else {
1792
- console.log(chalk9.bold.white(" Summary:"));
2313
+ console.log(ui.text.bold(" Summary:"));
1793
2314
  }
1794
2315
  for (const [label, value] of [
1795
2316
  ["URL", siteUrl],
1796
2317
  ["Platform", platformName],
1797
- ["Output", path6.resolve(outDir)],
2318
+ ["Output", path7.resolve(outDir)],
1798
2319
  ["Pretty-print", prettyPrint ? "yes" : "no"],
1799
2320
  ["Sub-pages", includeSubpages ? "yes" : "no"],
1800
2321
  ["Concurrency", String(concurrency)]
@@ -1819,14 +2340,14 @@ async function runSetup(legacyMode = false) {
1819
2340
  }
1820
2341
  if (!startExport) {
1821
2342
  console.log(`
1822
- ${chalk9.yellow("Export cancelled.")}
2343
+ ${ui.warning("Export cancelled.")}
1823
2344
  `);
1824
2345
  return;
1825
2346
  }
1826
2347
  console.log("");
1827
2348
  const { CFG: CFG2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1828
2349
  CFG2.concurrency = concurrency;
1829
- const exporter = new FramerExporter(siteUrl, path6.resolve(outDir), platformName);
2350
+ const exporter = new FramerExporter(siteUrl, path7.resolve(outDir), platformName);
1830
2351
  exporter.prettyPrint = prettyPrint;
1831
2352
  await exporter.run(includeSubpages);
1832
2353
  }
@@ -1838,24 +2359,26 @@ var init_setup = __esm({
1838
2359
  init_platforms();
1839
2360
  init_select();
1840
2361
  init_box();
2362
+ init_theme();
1841
2363
  }
1842
2364
  });
1843
2365
 
1844
2366
  // src/cli/index.ts
1845
2367
  init_package();
1846
- import path7 from "path";
2368
+ import path8 from "path";
1847
2369
  import { URL as URL5 } from "url";
1848
2370
 
1849
2371
  // src/cli/help.ts
1850
2372
  init_banner();
1851
- import chalk2 from "chalk";
2373
+ init_theme();
1852
2374
  function showHelp() {
1853
2375
  showBanner();
1854
- console.log(chalk2.white.bold(" USAGE\n"));
1855
- console.log(` ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
1856
- console.log(` ${chalk2.hex("#D4A017")("fexport")} ${chalk2.yellow("<url>")} ${chalk2.gray("[output-dir]")}`);
2376
+ console.log(`${ui.text.bold(" USAGE")} ${chip("cli")}
2377
+ `);
2378
+ console.log(` ${ui.primary("framer-export")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`);
2379
+ console.log(` ${ui.primary("fexport")} ${ui.warning("<url>")} ${ui.muted("[output-dir]")}`);
1857
2380
  console.log("");
1858
- console.log(chalk2.white.bold(" OPTIONS\n"));
2381
+ console.log(ui.text.bold(" OPTIONS\n"));
1859
2382
  const opts = [
1860
2383
  ["--setup", "Launch the interactive setup assistant"],
1861
2384
  ["--platform <p>", "Force platform: framer, webflow, wix"],
@@ -1864,27 +2387,27 @@ function showHelp() {
1864
2387
  ["--help, -h", "Show this help message"]
1865
2388
  ];
1866
2389
  for (const [flag, desc] of opts) {
1867
- console.log(` ${chalk2.green(flag.padEnd(18))} ${chalk2.white(desc)}`);
2390
+ console.log(` ${ui.success(flag.padEnd(18))} ${ui.text(desc)}`);
1868
2391
  }
1869
2392
  console.log("");
1870
- console.log(chalk2.white.bold(" SUPPORTED PLATFORMS\n"));
2393
+ console.log(ui.text.bold(" SUPPORTED PLATFORMS\n"));
1871
2394
  const platforms = [
1872
2395
  ["Framer", "Auto-detected via .framer.app / .framer.website URLs"],
1873
2396
  ["Webflow", "Auto-detected via .webflow.io URLs"],
1874
2397
  ["Wix", "Auto-detected via .wixsite.com URLs + HTML analysis"]
1875
2398
  ];
1876
2399
  for (const [name, desc] of platforms) {
1877
- console.log(` ${chalk2.hex("#D4A017")(name.padEnd(12))} ${chalk2.gray(desc)}`);
2400
+ console.log(` ${ui.primary(name.padEnd(12))} ${ui.muted(desc)}`);
1878
2401
  }
1879
2402
  console.log("");
1880
- console.log(chalk2.white.bold(" EXAMPLES\n"));
1881
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("https://mysite.framer.app")}`);
1882
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("https://mysite.webflow.io")}`);
1883
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.yellow("https://user.wixsite.com/my-site")}`);
1884
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--platform webflow")} ${chalk2.yellow("https://custom.com")}`);
1885
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--subpages")} ${chalk2.yellow("https://mysite.com")}`);
1886
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--setup")}`);
1887
- console.log(` ${chalk2.gray("$")} ${chalk2.hex("#D4A017")("framer-export")} ${chalk2.green("--setup --legacy-mode")}`);
2403
+ console.log(ui.text.bold(" EXAMPLES\n"));
2404
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://mysite.framer.app")}`);
2405
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://mysite.webflow.io")}`);
2406
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.warning("https://user.wixsite.com/my-site")}`);
2407
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--platform webflow")} ${ui.warning("https://custom.com")}`);
2408
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--subpages")} ${ui.warning("https://mysite.com")}`);
2409
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--setup")}`);
2410
+ console.log(` ${ui.muted("$")} ${ui.primary("framer-export")} ${ui.success("--setup --legacy-mode")}`);
1888
2411
  console.log("");
1889
2412
  }
1890
2413
 
@@ -1923,6 +2446,7 @@ async function checkForUpdates(currentVersion) {
1923
2446
  }
1924
2447
 
1925
2448
  // src/cli/index.ts
2449
+ init_theme();
1926
2450
  var VERSION = package_default.version;
1927
2451
  function extractFlag(args, flag) {
1928
2452
  const idx = args.indexOf(flag);
@@ -1944,30 +2468,26 @@ async function main() {
1944
2468
  process.exit(0);
1945
2469
  }
1946
2470
  if (args.includes("--about")) {
1947
- const chalk10 = (await import("chalk")).default;
2471
+ const chalk8 = (await import("chalk")).default;
1948
2472
  showBanner();
1949
- console.log(` ${chalk10.white.bold("Framer Export")} ${chalk10.gray(`v${package_default.version}`)}`);
1950
- console.log(` ${chalk10.white(package_default.description)}
2473
+ console.log(` ${ui.text.bold("Framer Export")} ${ui.muted(`v${package_default.version}`)}`);
2474
+ console.log(` ${ui.text(package_default.description)}
1951
2475
  `);
1952
- console.log(` ${chalk10.white.bold("Author")} ${chalk10.hex("#D4A017")("Dany (danbenba)")}`);
1953
- console.log(` ${chalk10.white.bold("Portfolio")} ${chalk10.underline.hex("#D4A017")("https://github.com/danbenba")}`);
1954
- console.log(` ${chalk10.white.bold("GitHub")} ${chalk10.underline.hex("#D4A017")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`);
1955
- console.log(` ${chalk10.white.bold("npm")} ${chalk10.underline.hex("#D4A017")(`https://www.npmjs.com/package/${package_default.name}`)}`);
1956
- console.log(` ${chalk10.white.bold("License")} ${chalk10.green(package_default.license)}`);
1957
- console.log(` ${chalk10.white.bold("Node")} ${chalk10.gray(`>=${package_default.engines.node}`)}`);
2476
+ console.log(` ${ui.text.bold("Author")} ${ui.primary("Dany (danbenba)")}`);
2477
+ console.log(` ${ui.text.bold("Portfolio")} ${chalk8.underline.hex("#FAB283")("https://github.com/danbenba")}`);
2478
+ console.log(` ${ui.text.bold("GitHub")} ${chalk8.underline.hex("#FAB283")(package_default.repository.url.replace("git+", "").replace(".git", ""))}`);
2479
+ console.log(` ${ui.text.bold("npm")} ${chalk8.underline.hex("#FAB283")(`https://www.npmjs.com/package/${package_default.name}`)}`);
2480
+ console.log(` ${ui.text.bold("License")} ${ui.success(package_default.license)}`);
2481
+ console.log(` ${ui.text.bold("Node")} ${ui.muted(`>=${package_default.engines.node}`)}`);
1958
2482
  console.log("");
1959
2483
  process.exit(0);
1960
2484
  }
1961
2485
  checkForUpdates(VERSION).then((latest) => {
1962
2486
  if (!latest) return;
1963
- import("chalk").then((chalk10) => {
1964
- console.log("");
1965
- console.log(
1966
- ` ${chalk10.default.yellow("\u21B3")} Update available: ${chalk10.default.gray(VERSION)} \u2192 ${chalk10.default.green(latest)}`
1967
- );
1968
- console.log(` ${chalk10.default.hex("#D4A017")(" Run:")} ${chalk10.default.hex("#B8860B")("npm i -g framer-export@latest")}`);
1969
- console.log("");
1970
- });
2487
+ console.log("");
2488
+ console.log(` ${ui.warning("\u21B3")} Update available: ${ui.muted(VERSION)} -> ${ui.success(latest)}`);
2489
+ console.log(` ${ui.primary(" Run:")} ${ui.primarySoft("npm i -g framer-export@latest")}`);
2490
+ console.log("");
1971
2491
  });
1972
2492
  if (args.includes("--help") || args.includes("-h")) {
1973
2493
  showHelp();
@@ -1992,9 +2512,8 @@ async function main() {
1992
2512
  try {
1993
2513
  new URL5(url);
1994
2514
  } catch {
1995
- const chalk10 = (await import("chalk")).default;
1996
- console.log(` ${chalk10.red("\u2717")} ${chalk10.red.bold("Invalid URL:")} ${chalk10.white(url)}`);
1997
- console.log(` ${chalk10.gray("Expected: https://yoursite.framer.app")}
2515
+ console.log(` ${ui.error("\u2717")} ${ui.error.bold("Invalid URL:")} ${ui.text(url)}`);
2516
+ console.log(` ${ui.muted("Expected: https://yoursite.framer.app")}
1998
2517
  `);
1999
2518
  process.exit(1);
2000
2519
  }
@@ -2004,12 +2523,12 @@ async function main() {
2004
2523
  const defaultDir = deriveOutputName2(url, detected);
2005
2524
  const out = args[1] || `./${defaultDir}`;
2006
2525
  try {
2007
- await new FramerExporter2(url, path7.resolve(out), platformOverride || void 0).run(includeSubpages);
2526
+ await new FramerExporter2(url, path8.resolve(out), platformOverride || void 0).run(includeSubpages);
2008
2527
  } catch (e) {
2009
- const chalk10 = (await import("chalk")).default;
2528
+ const chalk8 = (await import("chalk")).default;
2010
2529
  console.log(`
2011
- ${chalk10.red("\u2717")} ${chalk10.red.bold("FAILED:")} ${chalk10.white(e.message)}`);
2012
- console.log(chalk10.gray(e.stack || ""));
2530
+ ${ui.error("\u2717")} ${ui.error.bold("FAILED:")} ${ui.text(e.message)}`);
2531
+ console.log(chalk8.gray(e.stack || ""));
2013
2532
  process.exit(1);
2014
2533
  }
2015
2534
  }