@curenorway/kode-cli 1.10.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -367,122 +367,6 @@ function upsertPage(projectRoot, page, agent = "kode html") {
367
367
  context.updatedBy = agent;
368
368
  writeContext(projectRoot, context);
369
369
  }
370
- function generateClaudeMdMinimal(siteName, siteSlug) {
371
- const slug = siteSlug || "your-site-slug";
372
- return `## Cure Kode: ${siteName}
373
-
374
- ### What is Cure Kode?
375
-
376
- Cure Kode is an **internal CDN tool by Cure Norway** for managing JavaScript/CSS on Webflow sites.
377
-
378
- **IMPORTANT**: This is NOT a public product. Do NOT search the web for documentation - all info is here.
379
-
380
- **How it works**:
381
- 1. Scripts live locally in the \`.cure-kode-scripts/\` folder
382
- 2. \`kode push\` uploads scripts to Cure's CDN at \`app.cure.no\`
383
- 3. \`kode deploy\` makes them live on staging or production
384
- 4. A single \`init.js\` script tag in Webflow loads all your scripts
385
-
386
- ### CDN URL
387
-
388
- Add this to Webflow \u2192 Project Settings \u2192 Custom Code \u2192 Body Code (before \`</body>\`):
389
-
390
- \`\`\`html
391
- <script defer src="https://app.cure.no/api/cdn/${slug}/init.js"></script>
392
- \`\`\`
393
-
394
- ### Workflow
395
-
396
- 1. \`kode pull\` - Download latest scripts from remote
397
- 2. \`kode status\` - Check what's changed, see CDN URL
398
- 3. Edit scripts locally
399
- 4. \`kode push\` - Upload changes
400
- 5. \`kode deploy --env staging\` - Deploy to staging for testing
401
- 6. \`kode deploy --env production\` - Deploy to production when ready
402
-
403
- ### Commands
404
-
405
- | Command | Description |
406
- |---------|-------------|
407
- | \`kode pull\` | Download scripts from remote |
408
- | \`kode push\` | Upload local scripts |
409
- | \`kode set <script> --scope <scope>\` | Change script to global or page-specific |
410
- | \`kode set <script> --auto-load\` | Enable/disable auto-loading |
411
- | \`kode status\` | Show sync status and CDN URL |
412
- | \`kode deploy staging\` | Deploy to staging |
413
- | \`kode deploy production\` | Deploy to production |
414
- | \`kode html <url> --save\` | Analyze and cache page HTML structure |
415
-
416
- ### Script Loading
417
-
418
- Scripts have two properties: **scope** and **autoLoad**.
419
-
420
- | Scope | Auto-Load | Behavior |
421
- |-------|-----------|----------|
422
- | global + true | **Default**. Inlined into init.js, runs on all pages |
423
- | global + false | Not loaded. Call \`CK.loadScript('slug')\` manually |
424
- | page-specific + true | Loaded when URL matches page patterns |
425
- | page-specific + false | Not loaded. Call \`CK.loadScript('slug')\` manually |
426
-
427
- **CLI flags**: \`kode push --auto-load\` or \`kode push --no-auto-load\`
428
-
429
- ### Script Metadata (Documentation)
430
-
431
- **ALWAYS provide metadata when creating or updating scripts.** This helps other AI agents and the Chrome extension understand what scripts do.
432
-
433
- | Field | Description | Example |
434
- |-------|-------------|---------|
435
- | \`purpose\` | What the script does (1-2 sentences) | "Adds smooth scroll behavior to anchor links" |
436
- | \`triggers\` | When the script runs | \`[{ event: 'domReady' }, { event: 'click', selector: '.nav-link' }]\` |
437
- | \`dependencies\` | External libraries needed | \`[{ name: 'gsap', version: '3.12' }]\` |
438
- | \`domTargets\` | CSS selectors the script affects | \`[{ selector: '.hero-title' }, { selector: '#contact-form' }]\` |
439
-
440
- **MCP Tools**:
441
- - \`kode_create_script\` / \`kode_update_script\` \u2192 include \`metadata: { purpose: "..." }\`
442
- - \`kode_analyze_script\` \u2192 auto-detect triggers, dependencies, and DOM targets from code
443
- - \`kode_get_script_metadata\` \u2192 get existing metadata and AI summary
444
-
445
- **Example**:
446
- \`\`\`json
447
- {
448
- "name": "smooth-scroll",
449
- "content": "...",
450
- "metadata": {
451
- "purpose": "Adds smooth scroll animation to all anchor links",
452
- "triggers": [{ "event": "click", "selector": "a[href^='#']" }],
453
- "domTargets": [{ "selector": "a[href^='#']" }]
454
- }
455
- }
456
- \`\`\`
457
-
458
- ### Context
459
-
460
- **Read \`.cure-kode/context.md\` before working on scripts** - it contains:
461
- - Script inventory with purposes and auto-load status
462
- - Cached page structures (sections, CTAs, forms)
463
- - Notes from previous sessions
464
-
465
- Update context.md after your session with discoveries and changes.
466
-
467
- ### MCP Tools
468
-
469
- The \`.mcp.json\` file enables AI tools:
470
-
471
- **cure-kode MCP**: Script management, HTML analysis, deployment
472
- - \`kode_update_script\`, \`kode_deploy\`, \`kode_fetch_html_smart\`, etc.
473
-
474
- **webflow MCP** (if configured): Webflow Designer API access
475
- - Modify site structure, CMS collections, pages, and more
476
- - Requires the Webflow MCP Bridge App running in Designer (press E \u2192 Apps)
477
-
478
- **playwright MCP**: Testing and runtime HTML inspection
479
- - \`browser_navigate\`, \`browser_snapshot\`, \`browser_click\`, \`browser_console_messages\`
480
- - Use to verify scripts work correctly AFTER deploying to staging
481
-
482
- Restart Claude Code after \`kode init\` to load MCP servers.
483
-
484
- `;
485
- }
486
370
  function generateClaudeMd(siteName, scriptsDir = ".cure-kode-scripts", siteSlug) {
487
371
  const slug = siteSlug || "your-site-slug";
488
372
  return `# Cure Kode Project: ${siteName}
@@ -1316,40 +1200,259 @@ Option C: Full rollback
1316
1200
  `;
1317
1201
  }
1318
1202
 
1203
+ // src/version.ts
1204
+ var CLI_VERSION = "1.12.1";
1205
+
1319
1206
  // src/commands/init.ts
1320
1207
  import chalk from "chalk";
1321
1208
  import ora from "ora";
1322
1209
  import enquirer from "enquirer";
1323
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
1210
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, readFileSync as readFileSync4 } from "fs";
1211
+ import { join as join4 } from "path";
1212
+
1213
+ // src/lib/claude-md.ts
1214
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1324
1215
  import { join as join3 } from "path";
1216
+ var KODE_REFERENCE = `## Cure Kode
1217
+
1218
+ This project uses **Cure Kode** for JS/CSS delivery. **Before modifying scripts:**
1219
+
1220
+ 1. Run \`kode pull\` to get latest from server
1221
+ 2. Make your changes in \`.cure-kode-scripts/\`
1222
+ 3. Run \`kode push\` to upload, then \`kode deploy\` to publish
1223
+
1224
+ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
1225
+
1226
+ ---
1227
+
1228
+ `;
1229
+ function hasKodeReference(content) {
1230
+ return content.includes(".cure-kode/KODE.md") || content.includes("cure-kode/KODE.md") || content.includes("KODE.md");
1231
+ }
1232
+ function generateKodeMd(siteName, siteSlug, scripts, pages) {
1233
+ let md = `# Cure Kode: ${siteName}
1234
+
1235
+ This project uses **Cure Kode** CDN for JavaScript/CSS delivery to Webflow.
1236
+
1237
+ > **This file is auto-generated.** Do not edit manually - changes will be overwritten by \`kode pull\`, \`kode push\`, and \`kode sync\`.
1238
+
1239
+ ---
1240
+
1241
+ `;
1242
+ md += `## CDN URL
1243
+
1244
+ Add this to Webflow \u2192 Project Settings \u2192 Custom Code \u2192 Head:
1245
+
1246
+ \`\`\`html
1247
+ <script defer src="https://app.cure.no/api/cdn/${siteSlug}/init.js"></script>
1248
+ \`\`\`
1249
+
1250
+ ---
1251
+
1252
+ `;
1253
+ md += `## Scripts
1254
+
1255
+ `;
1256
+ if (scripts.length > 0) {
1257
+ md += `| Script | Type | Scope | Auto | Purpose |
1258
+ |--------|------|-------|------|---------|
1259
+ `;
1260
+ for (const script of scripts) {
1261
+ const type = script.type === "javascript" ? "JS" : "CSS";
1262
+ const scope = script.scope === "global" ? "Global" : "Page";
1263
+ const auto = script.autoLoad ? "\u26A1" : "\u25CB";
1264
+ const purpose = script.purpose || script.description || "\u2014";
1265
+ const truncated = purpose.length > 50 ? purpose.slice(0, 47) + "..." : purpose;
1266
+ md += `| \`${script.slug}\` | ${type} | ${scope} | ${auto} | ${truncated} |
1267
+ `;
1268
+ }
1269
+ md += `
1270
+ > \u26A1 = auto-loads on every page, \u25CB = manual load via \`CK.loadScript('slug')\`
1271
+
1272
+ `;
1273
+ } else {
1274
+ md += `No scripts yet. Create one with \`kode push\`.
1275
+
1276
+ `;
1277
+ }
1278
+ if (pages.length > 0) {
1279
+ md += `---
1280
+
1281
+ ## Pages
1282
+
1283
+ Page-specific scripts only load when URL matches these patterns:
1284
+
1285
+ `;
1286
+ for (const page of pages) {
1287
+ md += `- **${page.name}** (\`${page.slug}\`) \u2192 \`${page.patterns.join("`, `")}\`
1288
+ `;
1289
+ }
1290
+ md += `
1291
+ `;
1292
+ }
1293
+ md += `---
1294
+
1295
+ ## Workflow
1296
+
1297
+ **Before making changes:**
1298
+ \`\`\`bash
1299
+ kode pull # Get latest scripts from server
1300
+ \`\`\`
1301
+
1302
+ **After making changes:**
1303
+ \`\`\`bash
1304
+ kode push # Upload your changes
1305
+ kode deploy # Deploy to staging
1306
+ kode deploy --promote # When ready, promote to production
1307
+ \`\`\`
1308
+
1309
+ **Periodically:**
1310
+ \`\`\`bash
1311
+ kode doctor # Check for issues and CLI updates
1312
+ \`\`\`
1313
+
1314
+ ---
1315
+
1316
+ ## CLI Commands
1317
+
1318
+ | Command | Description |
1319
+ |---------|-------------|
1320
+ | \`kode status\` | Show sync status and staleness warnings |
1321
+ | \`kode pull\` | Download scripts from server |
1322
+ | \`kode push\` | Upload local changes |
1323
+ | \`kode sync\` | Bidirectional sync with conflict detection |
1324
+ | \`kode diff <script>\` | Compare local vs remote versions |
1325
+ | \`kode deploy\` | Deploy to staging |
1326
+ | \`kode deploy --dry-run\` | Preview deployment without executing |
1327
+ | \`kode deploy --promote\` | Promote staging to production |
1328
+ | \`kode doctor\` | Check config, API, sync state, and CLI version |
1329
+ | \`kode pages\` | List CDN pages |
1330
+ | \`kode pages create <name> -p /path\` | Create a page with URL patterns |
1331
+ | \`kode pages assign <page> <script>\` | Assign script to page |
1332
+
1333
+ ## MCP Tools
1334
+
1335
+ AI agents can use these tools directly:
1336
+
1337
+ - \`kode_list_scripts\` - List all scripts
1338
+ - \`kode_push\` - Upload local files
1339
+ - \`kode_deploy\` / \`kode_promote\` - Deploy to staging/production
1340
+ - \`kode_create_page\` - Create CDN page with URL patterns
1341
+ - \`kode_assign_script_to_page\` - Assign script to page
1342
+
1343
+ ## File Structure
1344
+
1345
+ \`\`\`
1346
+ .cure-kode/
1347
+ \u251C\u2500\u2500 config.json # Site configuration (contains API key - gitignored)
1348
+ \u251C\u2500\u2500 scripts.json # Sync state for conflict detection
1349
+ \u251C\u2500\u2500 context.md # Dynamic context for AI agents
1350
+ \u2514\u2500\u2500 KODE.md # This file - auto-generated docs
1351
+
1352
+ .cure-kode-scripts/ # Your script files
1353
+ \u251C\u2500\u2500 main.js
1354
+ \u251C\u2500\u2500 form-handler.js
1355
+ \u2514\u2500\u2500 ...
1356
+ \`\`\`
1357
+ `;
1358
+ return md;
1359
+ }
1360
+ function ensureClaudeMdReference(projectRoot) {
1361
+ const claudeMdPath = join3(projectRoot, "CLAUDE.md");
1362
+ if (!existsSync3(claudeMdPath)) {
1363
+ writeFileSync3(claudeMdPath, KODE_REFERENCE);
1364
+ return { created: true, updated: false, skipped: false };
1365
+ }
1366
+ const content = readFileSync3(claudeMdPath, "utf-8");
1367
+ if (hasKodeReference(content)) {
1368
+ return { created: false, updated: false, skipped: true };
1369
+ }
1370
+ const newContent = KODE_REFERENCE + content;
1371
+ writeFileSync3(claudeMdPath, newContent);
1372
+ return { created: false, updated: true, skipped: false };
1373
+ }
1374
+ function updateKodeMd(projectRoot, siteName, siteSlug, scripts, pages) {
1375
+ const kodeMdPath = join3(projectRoot, ".cure-kode", "KODE.md");
1376
+ const existed = existsSync3(kodeMdPath);
1377
+ const content = generateKodeMd(siteName, siteSlug, scripts, pages);
1378
+ writeFileSync3(kodeMdPath, content);
1379
+ return { created: !existed, updated: existed };
1380
+ }
1381
+ function updateKodeDocs(projectRoot, siteName, siteSlug, scripts, pages) {
1382
+ const kodeMd = updateKodeMd(projectRoot, siteName, siteSlug, scripts, pages);
1383
+ const claudeMd = ensureClaudeMdReference(projectRoot);
1384
+ return { kodeMd, claudeMd };
1385
+ }
1386
+ function scriptsToDocsFormat(scripts) {
1387
+ return scripts.map((s) => ({
1388
+ slug: s.slug,
1389
+ name: s.name,
1390
+ type: s.type,
1391
+ scope: s.scope,
1392
+ autoLoad: s.auto_load,
1393
+ purpose: s.metadata?.purpose || s.description
1394
+ }));
1395
+ }
1396
+ function pagesToInfoFormat(pages) {
1397
+ return pages.filter((p) => p.is_active).map((p) => ({
1398
+ name: p.name,
1399
+ slug: p.slug,
1400
+ patterns: p.url_patterns
1401
+ }));
1402
+ }
1403
+ var updateClaudeMd = updateKodeDocs;
1404
+
1405
+ // src/commands/init.ts
1325
1406
  var { prompt } = enquirer;
1407
+ function showBanner() {
1408
+ console.clear();
1409
+ console.log();
1410
+ console.log(chalk.bold.white(" \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\u2588\u2557"));
1411
+ console.log(chalk.bold.white(" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D"));
1412
+ console.log(chalk.bold.white(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557"));
1413
+ console.log(chalk.bold.white(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D"));
1414
+ console.log(chalk.bold.white(" \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"));
1415
+ console.log(chalk.bold.white(" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1416
+ console.log();
1417
+ console.log(chalk.dim(` Kode CLI v${CLI_VERSION}`));
1418
+ console.log(chalk.dim(" JS/CSS delivery for Webflow sites"));
1419
+ console.log();
1420
+ }
1326
1421
  async function initCommand(options) {
1327
1422
  const cwd = process.cwd();
1423
+ showBanner();
1328
1424
  const existingRoot = findProjectRoot(cwd);
1329
1425
  if (existingRoot && !options.force) {
1330
- console.log(
1331
- chalk.yellow("\u26A0\uFE0F Cure Kode is already initialized in this directory tree.")
1332
- );
1333
- console.log(chalk.dim(` Config found at: ${existingRoot}/.cure-kode/config.json`));
1334
- console.log(chalk.dim(" Use --force to reinitialize."));
1426
+ console.log(chalk.yellow(" \u26A0\uFE0F Cure Kode er allerede initialisert i denne mappen."));
1427
+ console.log();
1428
+ console.log(chalk.dim(` Konfig funnet: ${existingRoot}/.cure-kode/config.json`));
1429
+ console.log();
1430
+ console.log(chalk.dim(" Bruk ") + chalk.cyan("kode init --force") + chalk.dim(" for \xE5 reinitialisere."));
1431
+ console.log(chalk.dim(" Bruk ") + chalk.cyan("kode upgrade") + chalk.dim(" for \xE5 oppgradere til nyeste versjon."));
1432
+ console.log();
1335
1433
  return;
1336
1434
  }
1337
- console.log(chalk.bold("\n\u{1F680} Cure Kode Setup\n"));
1435
+ console.log(chalk.bold(" Velkommen til Cure Kode! \u{1F680}"));
1436
+ console.log();
1437
+ console.log(chalk.dim(" For \xE5 komme i gang trenger du en API-n\xF8kkel fra Cure App."));
1438
+ console.log(chalk.dim(" Finn den i: ") + chalk.cyan("Verkt\xF8y \u2192 Kode \u2192 API-n\xF8kler"));
1439
+ console.log();
1338
1440
  const { apiKey } = await prompt([
1339
1441
  {
1340
1442
  type: "input",
1341
1443
  name: "apiKey",
1342
- message: "API Key (from Cure App \u2192 Tools \u2192 Kode \u2192 API Keys):",
1444
+ message: "API-n\xF8kkel:",
1343
1445
  initial: options.apiKey,
1344
1446
  validate: (value) => {
1345
- if (value.length === 0) return "API key is required";
1346
- if (!value.startsWith("ck_")) return "API key should start with ck_";
1347
- if (value.length < 30) return "API key looks truncated - make sure you copied the full key";
1447
+ if (value.length === 0) return "API-n\xF8kkel er p\xE5krevd";
1448
+ if (!value.startsWith("ck_")) return "API-n\xF8kkel skal starte med ck_";
1449
+ if (value.length < 30) return "API-n\xF8kkel ser avkortet ut - kopier hele n\xF8kkelen";
1348
1450
  return true;
1349
1451
  }
1350
1452
  }
1351
1453
  ]);
1352
- const spinner = ora("Validating API key...").start();
1454
+ console.log();
1455
+ const spinner = ora("Validerer API-n\xF8kkel...").start();
1353
1456
  try {
1354
1457
  const response = await fetch("https://app.cure.no/api/cdn/sites", {
1355
1458
  headers: {
@@ -1357,27 +1460,55 @@ async function initCommand(options) {
1357
1460
  }
1358
1461
  });
1359
1462
  if (!response.ok) {
1360
- spinner.fail("Invalid API key");
1361
- console.log(chalk.red("\nCould not validate your API key."));
1362
- console.log(chalk.dim("Make sure you copied the full key from Cure App \u2192 Tools \u2192 Kode \u2192 API Keys."));
1463
+ spinner.fail("Ugyldig API-n\xF8kkel");
1464
+ console.log();
1465
+ console.log(chalk.red(" Kunne ikke validere API-n\xF8kkelen din."));
1466
+ console.log(chalk.dim(" Sjekk at du kopierte hele n\xF8kkelen fra Cure App."));
1467
+ console.log();
1363
1468
  return;
1364
1469
  }
1365
1470
  const sites = await response.json();
1366
1471
  if (!sites || sites.length === 0) {
1367
- spinner.fail("No site found for this API key");
1368
- console.log(chalk.red("\nThis API key is not associated with any site."));
1472
+ spinner.fail("Ingen nettsted funnet");
1473
+ console.log();
1474
+ console.log(chalk.red(" Denne API-n\xF8kkelen er ikke tilknyttet et nettsted."));
1475
+ console.log();
1369
1476
  return;
1370
1477
  }
1371
1478
  const site = sites[0];
1372
- spinner.succeed("API key validated");
1479
+ spinner.succeed("API-n\xF8kkel validert");
1480
+ if (!site.webflow_token || !site.webflow_site_id) {
1481
+ console.log();
1482
+ console.log(chalk.red(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
1483
+ console.log(chalk.red(" \u2551") + chalk.bold.red(" \u26A0\uFE0F Webflow er ikke konfigurert for dette nettstedet ") + chalk.red("\u2551"));
1484
+ console.log(chalk.red(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1485
+ console.log();
1486
+ console.log(chalk.dim(" Cure Kode trenger Webflow-tilkobling for \xE5 fungere."));
1487
+ console.log();
1488
+ console.log(chalk.bold(" Slik setter du opp Webflow:"));
1489
+ console.log();
1490
+ console.log(chalk.dim(" 1. G\xE5 til ") + chalk.cyan("app.cure.no") + chalk.dim(" \u2192 din klient \u2192 ") + chalk.cyan("Verkt\xF8y \u2192 Kode"));
1491
+ console.log(chalk.dim(" 2. Klikk p\xE5 ") + chalk.cyan("Webflow-innstillinger"));
1492
+ console.log(chalk.dim(" 3. Legg inn Webflow Site ID og API Token"));
1493
+ console.log(chalk.dim(" 4. Kj\xF8r ") + chalk.cyan("kode init") + chalk.dim(" p\xE5 nytt"));
1494
+ console.log();
1495
+ console.log(chalk.dim(" Trenger du hjelp? Se ") + chalk.cyan("cure.no/docs/kode"));
1496
+ console.log();
1497
+ return;
1498
+ }
1373
1499
  console.log();
1374
- console.log(chalk.bold(" Connected to: ") + chalk.cyan(site.name) + chalk.dim(` (${site.slug})`));
1500
+ console.log(chalk.green(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
1501
+ console.log(chalk.green(" \u2551") + chalk.bold.green(" \u2713 Tilkoblet til Cure Kode ") + chalk.green("\u2551"));
1502
+ console.log(chalk.green(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1503
+ console.log();
1504
+ console.log(chalk.bold(" Nettsted: ") + chalk.cyan(site.name) + chalk.dim(` (${site.slug})`));
1375
1505
  if (site.production_domain || site.domain) {
1376
- console.log(chalk.dim(" Production: ") + (site.production_domain || site.domain));
1506
+ console.log(chalk.dim(" Produksjon: ") + (site.production_domain || site.domain));
1377
1507
  }
1378
1508
  if (site.staging_domain) {
1379
1509
  console.log(chalk.dim(" Staging: ") + site.staging_domain);
1380
1510
  }
1511
+ console.log(chalk.dim(" Webflow: ") + chalk.green("\u2713 Konfigurert"));
1381
1512
  console.log();
1382
1513
  const config = {
1383
1514
  siteId: site.id,
@@ -1386,23 +1517,25 @@ async function initCommand(options) {
1386
1517
  apiKey,
1387
1518
  scriptsDir: DEFAULT_SCRIPTS_DIR,
1388
1519
  environment: "staging"
1389
- // Always default to staging
1390
1520
  };
1521
+ spinner.start("Oppretter prosjektstruktur...");
1391
1522
  saveProjectConfig(config, cwd);
1392
- const scriptsPath = join3(cwd, DEFAULT_SCRIPTS_DIR);
1393
- if (!existsSync3(scriptsPath)) {
1523
+ const scriptsPath = join4(cwd, DEFAULT_SCRIPTS_DIR);
1524
+ if (!existsSync4(scriptsPath)) {
1394
1525
  mkdirSync2(scriptsPath, { recursive: true });
1395
1526
  }
1396
- const gitignorePath = join3(cwd, ".cure-kode", ".gitignore");
1397
- const gitignoreContent = `# Keep config.json private - contains API key
1527
+ const gitignorePath = join4(cwd, ".cure-kode", ".gitignore");
1528
+ const gitignoreContent = `# Hold config.json privat - inneholder API-n\xF8kkel
1398
1529
  config.json
1399
1530
  `;
1400
- writeFileSync3(gitignorePath, gitignoreContent);
1401
- const mcpConfigPath = join3(cwd, ".mcp.json");
1531
+ writeFileSync4(gitignorePath, gitignoreContent);
1532
+ spinner.succeed("Prosjektstruktur opprettet");
1533
+ spinner.start("Konfigurerer MCP-servere...");
1534
+ const mcpConfigPath = join4(cwd, ".mcp.json");
1402
1535
  let mcpConfig = {};
1403
- if (existsSync3(mcpConfigPath)) {
1536
+ if (existsSync4(mcpConfigPath)) {
1404
1537
  try {
1405
- mcpConfig = JSON.parse(readFileSync3(mcpConfigPath, "utf-8"));
1538
+ mcpConfig = JSON.parse(readFileSync4(mcpConfigPath, "utf-8"));
1406
1539
  } catch {
1407
1540
  }
1408
1541
  }
@@ -1410,239 +1543,109 @@ config.json
1410
1543
  mcpConfig.mcpServers["cure-kode"] = {
1411
1544
  type: "stdio",
1412
1545
  command: "npx",
1413
- args: ["-y", "@curenorway/kode-mcp@^1.3.0"]
1546
+ args: ["-y", "@curenorway/kode-mcp@^1.5.0"]
1414
1547
  };
1415
- let webflowToken = site.webflow_token;
1416
- let webflowMcpMethod = null;
1417
- spinner.stop();
1418
- if (webflowToken) {
1419
- console.log(chalk.green("\u2713 Webflow API token found in site settings"));
1420
- webflowMcpMethod = "token";
1421
- } else {
1422
- console.log(chalk.yellow("\n\u{1F4E6} Webflow MCP Setup"));
1423
- console.log(chalk.dim(" The Webflow MCP enables AI agents to interact with the Webflow Designer."));
1424
- console.log();
1425
- const { webflowSetup } = await prompt([
1426
- {
1427
- type: "select",
1428
- name: "webflowSetup",
1429
- message: "How would you like to set up Webflow MCP?",
1430
- choices: [
1431
- {
1432
- name: "oauth",
1433
- message: "Use OAuth (recommended) - authorize via browser when needed"
1434
- },
1435
- {
1436
- name: "token",
1437
- message: "Provide API token - use a Webflow API token"
1438
- },
1439
- {
1440
- name: "skip",
1441
- message: "Skip - set up Webflow MCP later"
1442
- }
1443
- ],
1444
- initial: 0
1445
- }
1446
- ]);
1447
- webflowMcpMethod = webflowSetup;
1448
- if (webflowSetup === "token") {
1449
- const { webflowToken: providedToken } = await prompt([
1450
- {
1451
- type: "password",
1452
- name: "webflowToken",
1453
- message: "Webflow API Token (from webflow.com/dashboard/account/integrations):",
1454
- validate: (value) => value.length > 0 ? true : "Token is required"
1455
- }
1456
- ]);
1457
- webflowToken = providedToken;
1548
+ mcpConfig.mcpServers["webflow"] = {
1549
+ type: "stdio",
1550
+ command: "npx",
1551
+ args: ["-y", "webflow-mcp-server@^0.6.0"],
1552
+ env: {
1553
+ WEBFLOW_TOKEN: site.webflow_token
1458
1554
  }
1459
- }
1460
- if (webflowMcpMethod === "token" && webflowToken) {
1461
- mcpConfig.mcpServers["webflow"] = {
1462
- type: "stdio",
1463
- command: "npx",
1464
- args: ["-y", "webflow-mcp-server@^0.6.0"],
1465
- env: {
1466
- WEBFLOW_TOKEN: webflowToken
1467
- }
1468
- };
1469
- } else if (webflowMcpMethod === "oauth") {
1470
- mcpConfig.mcpServers["webflow"] = {
1471
- type: "http",
1472
- url: "https://mcp.webflow.com/sse"
1473
- };
1474
- }
1555
+ };
1475
1556
  mcpConfig.mcpServers["playwright"] = {
1476
1557
  type: "stdio",
1477
1558
  command: "npx",
1478
1559
  args: ["-y", "@playwright/mcp@^0.0.21"]
1479
1560
  };
1480
- spinner.start("Generating AI context files...");
1481
- writeFileSync3(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
1482
- spinner.text = "Generating AI context files...";
1483
- spinner.start();
1561
+ writeFileSync4(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
1562
+ spinner.succeed("MCP-servere konfigurert");
1563
+ spinner.start("Genererer AI-dokumentasjon...");
1484
1564
  let scripts = [];
1565
+ let pages = [];
1485
1566
  try {
1486
- const scriptsResponse = await fetch(
1487
- `https://app.cure.no/api/cdn/sites/${site.id}/scripts`,
1488
- {
1567
+ const [scriptsResponse, pagesResponse] = await Promise.all([
1568
+ fetch(`https://app.cure.no/api/cdn/sites/${site.id}/scripts`, {
1489
1569
  headers: { "X-API-Key": apiKey }
1490
- }
1491
- );
1570
+ }),
1571
+ fetch(`https://app.cure.no/api/cdn/sites/${site.id}/pages`, {
1572
+ headers: { "X-API-Key": apiKey }
1573
+ })
1574
+ ]);
1492
1575
  if (scriptsResponse.ok) {
1493
1576
  scripts = await scriptsResponse.json();
1494
1577
  }
1495
- } catch {
1496
- }
1497
- const claudeMdPath = join3(cwd, "CLAUDE.md");
1498
- const claudeMdContentFull = generateClaudeMd(config.siteName, DEFAULT_SCRIPTS_DIR, config.siteSlug);
1499
- const claudeMdContentMinimal = generateClaudeMdMinimal(config.siteName, config.siteSlug);
1500
- let claudeMdAction = "created";
1501
- if (existsSync3(claudeMdPath)) {
1502
- const existingContent = readFileSync3(claudeMdPath, "utf-8");
1503
- const kodeHeaderPattern = /^## Cure Kode[:\s]/m;
1504
- const hasExistingKodeSection = kodeHeaderPattern.test(existingContent);
1505
- spinner.stop();
1506
- if (hasExistingKodeSection) {
1507
- console.log(chalk.yellow("\n\u26A0\uFE0F Found existing Cure Kode section in CLAUDE.md.\n"));
1508
- const { action } = await prompt([
1509
- {
1510
- type: "select",
1511
- name: "action",
1512
- message: "How would you like to handle Kode instructions?",
1513
- choices: [
1514
- {
1515
- name: "prepend",
1516
- message: "Update existing Cure Kode section (recommended)"
1517
- },
1518
- {
1519
- name: "skip",
1520
- message: "Skip - keep existing section"
1521
- }
1522
- ],
1523
- initial: 0
1524
- }
1525
- ]);
1526
- spinner.start("Generating AI context files...");
1527
- if (action === "prepend") {
1528
- const sectionEndPattern = /^## Cure Kode[:\s][\s\S]*?(?=\n---\n|\n## (?!#)|$)/m;
1529
- const updatedContent = existingContent.replace(sectionEndPattern, claudeMdContentMinimal.trim());
1530
- writeFileSync3(claudeMdPath, updatedContent);
1531
- claudeMdAction = "updated";
1532
- } else {
1533
- claudeMdAction = "skipped";
1534
- }
1535
- } else {
1536
- console.log(chalk.yellow("\n\u26A0\uFE0F CLAUDE.md already exists in this directory.\n"));
1537
- const { action } = await prompt([
1538
- {
1539
- type: "select",
1540
- name: "action",
1541
- message: "How would you like to handle Kode instructions?",
1542
- choices: [
1543
- {
1544
- name: "prepend",
1545
- message: "Prepend Kode section to CLAUDE.md (recommended)"
1546
- },
1547
- {
1548
- name: "separate",
1549
- message: "Create separate KODE.md file"
1550
- },
1551
- {
1552
- name: "skip",
1553
- message: "Skip - I'll add instructions manually"
1554
- }
1555
- ],
1556
- initial: 0
1557
- }
1558
- ]);
1559
- spinner.start("Generating AI context files...");
1560
- if (action === "prepend") {
1561
- writeFileSync3(claudeMdPath, claudeMdContentMinimal + "---\n\n" + existingContent);
1562
- claudeMdAction = "prepended";
1563
- } else if (action === "separate") {
1564
- writeFileSync3(join3(cwd, "KODE.md"), claudeMdContentFull);
1565
- claudeMdAction = "separate";
1566
- } else {
1567
- claudeMdAction = "skipped";
1568
- }
1578
+ if (pagesResponse.ok) {
1579
+ pages = await pagesResponse.json();
1569
1580
  }
1570
- } else {
1571
- writeFileSync3(claudeMdPath, claudeMdContentFull);
1581
+ } catch {
1572
1582
  }
1583
+ const docsResult = updateKodeDocs(
1584
+ cwd,
1585
+ config.siteName,
1586
+ config.siteSlug,
1587
+ scriptsToDocsFormat(scripts),
1588
+ pagesToInfoFormat(pages)
1589
+ );
1573
1590
  const siteInfo = {
1574
1591
  domain: site.domain,
1575
1592
  staging_domain: site.staging_domain,
1576
1593
  production_domain: site.production_domain
1577
1594
  };
1578
1595
  const contextMdContent = generateInitialContext(config, scripts, siteInfo);
1579
- writeFileSync3(join3(cwd, ".cure-kode", "context.md"), contextMdContent);
1580
- spinner.succeed("AI context files generated");
1581
- console.log(chalk.green("\n\u2705 Cure Kode initialized successfully!\n"));
1582
- console.log(chalk.dim("Project structure:"));
1596
+ writeFileSync4(join4(cwd, ".cure-kode", "context.md"), contextMdContent);
1597
+ spinner.succeed("AI-dokumentasjon generert");
1598
+ console.log();
1599
+ console.log(chalk.green(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
1600
+ console.log(chalk.green(" \u2551") + chalk.bold.green(" \u2705 Cure Kode er klar! ") + chalk.green("\u2551"));
1601
+ console.log(chalk.green(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1602
+ console.log();
1603
+ console.log(chalk.bold(" Prosjektstruktur:"));
1583
1604
  console.log(chalk.dim(` ${cwd}/`));
1584
- if (claudeMdAction === "created") {
1585
- console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (AI agent instructions)`));
1586
- } else if (claudeMdAction === "prepended") {
1587
- console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (Kode section prepended)`));
1588
- } else if (claudeMdAction === "updated") {
1589
- console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (Kode section updated)`));
1590
- } else if (claudeMdAction === "separate") {
1591
- console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (existing - unchanged)`));
1592
- console.log(chalk.dim(` \u251C\u2500\u2500 KODE.md (Kode AI instructions)`));
1605
+ if (docsResult.claudeMd.created) {
1606
+ console.log(chalk.dim(" \u251C\u2500\u2500 CLAUDE.md ") + chalk.green("(opprettet med referanse)"));
1607
+ } else if (docsResult.claudeMd.updated) {
1608
+ console.log(chalk.dim(" \u251C\u2500\u2500 CLAUDE.md ") + chalk.green("(referanse lagt til)"));
1593
1609
  } else {
1594
- console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (existing - unchanged)`));
1595
- }
1596
- console.log(chalk.dim(` \u251C\u2500\u2500 .mcp.json (MCP server config)`));
1597
- console.log(chalk.dim(` \u251C\u2500\u2500 .cure-kode/`));
1598
- console.log(chalk.dim(` \u2502 \u251C\u2500\u2500 config.json (your configuration)`));
1599
- console.log(chalk.dim(` \u2502 \u251C\u2500\u2500 context.md (dynamic AI context)`));
1600
- console.log(chalk.dim(` \u2502 \u2514\u2500\u2500 .gitignore (protects API key)`));
1610
+ console.log(chalk.dim(" \u251C\u2500\u2500 CLAUDE.md ") + chalk.dim("(uendret - har allerede referanse)"));
1611
+ }
1612
+ console.log(chalk.dim(" \u251C\u2500\u2500 .mcp.json ") + chalk.green("(MCP-servere)"));
1613
+ console.log(chalk.dim(" \u251C\u2500\u2500 .cure-kode/"));
1614
+ console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 config.json (konfigurasjon)"));
1615
+ console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 KODE.md ") + chalk.green("(Kode-dokumentasjon)"));
1616
+ console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 context.md (dynamisk AI-kontekst)"));
1617
+ console.log(chalk.dim(" \u2502 \u2514\u2500\u2500 .gitignore (beskytter API-n\xF8kkel)"));
1601
1618
  console.log(chalk.dim(` \u2514\u2500\u2500 ${DEFAULT_SCRIPTS_DIR}/`));
1602
- console.log(chalk.dim(` \u2514\u2500\u2500 (your scripts here)`));
1603
- console.log(chalk.bold("\nWebflow Setup:"));
1604
- console.log(chalk.dim(" Add this to your Webflow <head> code:"));
1605
- console.log(chalk.cyan(` <script defer src="https://app.cure.no/api/cdn/${site.slug}/init.js"></script>`));
1606
- console.log(chalk.bold("\nNext steps:"));
1607
- console.log(chalk.cyan(" 1. kode pull ") + chalk.dim("Download existing scripts"));
1608
- console.log(chalk.cyan(" 2. kode watch ") + chalk.dim("Watch for changes and auto-push"));
1609
- console.log(chalk.cyan(" 3. kode deploy ") + chalk.dim("Deploy to staging"));
1610
- console.log(chalk.bold("\n\u{1F4A1} MCP Tools:"));
1611
- console.log(chalk.dim(" Restart Claude Code, then use /mcp to approve the MCP servers."));
1619
+ console.log(chalk.dim(" \u2514\u2500\u2500 (skriptene dine her)"));
1612
1620
  console.log();
1613
- console.log(chalk.dim(" cure-kode:"));
1614
- console.log(chalk.dim(" kode_update_script, kode_fetch_html_smart, kode_deploy, etc."));
1615
- if (webflowMcpMethod === "token") {
1616
- console.log();
1617
- console.log(chalk.dim(" webflow (token mode):"));
1618
- console.log(chalk.dim(" Interact with Webflow Designer API, modify site structure, CMS, etc."));
1619
- } else if (webflowMcpMethod === "oauth") {
1620
- console.log();
1621
- console.log(chalk.dim(" webflow (OAuth mode):"));
1622
- console.log(chalk.dim(" You'll be prompted to authorize via browser on first use."));
1623
- console.log(chalk.dim(" Requires Node.js 22.3.0+ for remote MCP."));
1624
- } else {
1625
- console.log();
1626
- console.log(chalk.dim(" webflow: ") + chalk.yellow("Not configured"));
1627
- console.log(chalk.dim(" Run `kode init --force` to set up Webflow MCP later."));
1628
- }
1621
+ console.log(chalk.bold(" Webflow-oppsett:"));
1622
+ console.log(chalk.dim(" Legg til dette i Webflow <head>:"));
1623
+ console.log();
1624
+ console.log(chalk.cyan(` <script defer src="https://app.cure.no/api/cdn/${site.slug}/init.js"></script>`));
1625
+ console.log();
1626
+ console.log(chalk.bold(" Neste steg:"));
1627
+ console.log();
1628
+ console.log(chalk.cyan(" 1. kode pull ") + chalk.dim("Last ned eksisterende skript"));
1629
+ console.log(chalk.cyan(" 2.") + chalk.dim(" Start din AI-agent (Claude Code, Cursor, etc.)"));
1630
+ console.log(chalk.cyan(" 3.") + chalk.dim(" Godkjenn MCP-servere n\xE5r agenten starter"));
1631
+ console.log();
1632
+ console.log(chalk.bold(" MCP-verkt\xF8y tilgjengelig:"));
1633
+ console.log();
1634
+ console.log(chalk.dim(" cure-kode: ") + chalk.cyan("kode_list_scripts, kode_push, kode_deploy, ..."));
1635
+ console.log(chalk.dim(" webflow: ") + chalk.cyan("Interager med Webflow Designer API"));
1636
+ console.log(chalk.dim(" playwright: ") + chalk.cyan("Test skript, hent runtime HTML, ta skjermbilder"));
1637
+ console.log();
1638
+ console.log(chalk.bold(" \u{1F4A1} Tips: Webflow Designer Bridge"));
1639
+ console.log(chalk.dim(" 1. \xC5pne nettstedet i Webflow Designer"));
1640
+ console.log(chalk.dim(" 2. Trykk E for \xE5 \xE5pne Apps-panelet"));
1641
+ console.log(chalk.dim(' 3. Start "Webflow MCP Bridge App"'));
1642
+ console.log(chalk.dim(" 4. AI-agenter kan n\xE5 interagere med Designer"));
1629
1643
  console.log();
1630
- console.log(chalk.dim(" playwright:"));
1631
- console.log(chalk.dim(" Test scripts, get runtime HTML (after JS runs), take screenshots."));
1632
- if (claudeMdAction === "separate") {
1633
- console.log(chalk.yellow("\n\u{1F4A1} Tip: Add this line to your CLAUDE.md to reference Kode instructions:"));
1634
- console.log(chalk.dim(" See KODE.md for Cure Kode CDN management instructions."));
1635
- }
1636
- if (webflowMcpMethod === "token" || webflowMcpMethod === "oauth") {
1637
- console.log(chalk.bold("\n\u{1F517} Webflow Designer Integration:"));
1638
- console.log(chalk.dim(" 1. Open your site in Webflow Designer"));
1639
- console.log(chalk.dim(" 2. Press E to open Apps panel"));
1640
- console.log(chalk.dim(' 3. Launch the "Webflow MCP Bridge App"'));
1641
- console.log(chalk.dim(" 4. AI agents can now interact with the Designer"));
1642
- }
1643
1644
  } catch (error) {
1644
- spinner.fail("Initialization failed");
1645
- console.error(chalk.red("\nError:"), error);
1645
+ spinner.fail("Initialisering feilet");
1646
+ console.log();
1647
+ console.error(chalk.red(" Feil:"), error);
1648
+ console.log();
1646
1649
  }
1647
1650
  }
1648
1651
 
@@ -1820,6 +1823,40 @@ var KodeApiClient = class {
1820
1823
  async listPages(siteId) {
1821
1824
  return this.request(`/api/cdn/sites/${siteId}/pages`);
1822
1825
  }
1826
+ async getPage(pageId) {
1827
+ return this.request(`/api/cdn/pages/${pageId}`);
1828
+ }
1829
+ async createPage(siteId, data) {
1830
+ return this.request(`/api/cdn/sites/${siteId}/pages`, {
1831
+ method: "POST",
1832
+ body: JSON.stringify(data)
1833
+ });
1834
+ }
1835
+ async updatePage(pageId, data) {
1836
+ return this.request(`/api/cdn/pages/${pageId}`, {
1837
+ method: "PATCH",
1838
+ body: JSON.stringify(data)
1839
+ });
1840
+ }
1841
+ async deletePage(pageId) {
1842
+ return this.request(`/api/cdn/pages/${pageId}`, {
1843
+ method: "DELETE"
1844
+ });
1845
+ }
1846
+ async assignScriptToPage(pageId, scriptId, loadOrderOverride) {
1847
+ return this.request(`/api/cdn/pages/${pageId}/scripts`, {
1848
+ method: "POST",
1849
+ body: JSON.stringify({ scriptId, loadOrderOverride })
1850
+ });
1851
+ }
1852
+ async removeScriptFromPage(pageId, scriptId) {
1853
+ return this.request(`/api/cdn/pages/${pageId}/scripts/${scriptId}`, {
1854
+ method: "DELETE"
1855
+ });
1856
+ }
1857
+ async getPageScripts(pageId) {
1858
+ return this.request(`/api/cdn/pages/${pageId}/scripts`);
1859
+ }
1823
1860
  // Deployments
1824
1861
  async deploy(siteId, environment = "staging") {
1825
1862
  return this.request("/api/cdn/deploy", {
@@ -1888,39 +1925,51 @@ function createApiClient(config) {
1888
1925
  // src/commands/pull.ts
1889
1926
  import chalk2 from "chalk";
1890
1927
  import ora2 from "ora";
1891
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1892
- import { join as join4 } from "path";
1928
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync5, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
1929
+ import { join as join5 } from "path";
1930
+ import { createHash } from "crypto";
1931
+ function hashContent(content) {
1932
+ return createHash("sha256").update(content).digest("hex").substring(0, 16);
1933
+ }
1893
1934
  async function pullCommand(options) {
1894
1935
  const projectRoot = findProjectRoot();
1895
1936
  if (!projectRoot) {
1896
- console.log(chalk2.red("\u274C Not in a Cure Kode project."));
1897
- console.log(chalk2.dim(' Run "kode init" first.'));
1937
+ console.log(chalk2.red("Feil: Ikke i et Cure Kode-prosjekt."));
1938
+ console.log(chalk2.dim('Kj\xF8r "kode init" f\xF8rst.'));
1898
1939
  return;
1899
1940
  }
1900
1941
  const config = getProjectConfig(projectRoot);
1901
1942
  if (!config) {
1902
- console.log(chalk2.red("\u274C Could not read project configuration."));
1943
+ console.log(chalk2.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
1903
1944
  return;
1904
1945
  }
1905
- const spinner = ora2("Fetching scripts...").start();
1946
+ const spinner = ora2("Henter skript...").start();
1906
1947
  try {
1907
1948
  const client = createApiClient(config);
1908
1949
  const scripts = await client.listScripts(config.siteId);
1909
1950
  if (scripts.length === 0) {
1910
- spinner.info("No scripts found on remote.");
1911
- console.log(chalk2.dim('\nCreate scripts in Cure App or use "kode push" to upload local scripts.'));
1951
+ spinner.info("Ingen skript funnet p\xE5 server.");
1952
+ console.log(chalk2.dim('\nOpprett skript i Cure App eller bruk "kode push" for \xE5 laste opp.'));
1912
1953
  return;
1913
1954
  }
1914
- spinner.succeed(`Found ${scripts.length} script(s)`);
1955
+ spinner.succeed(`Fant ${scripts.length} skript`);
1915
1956
  const scriptsDir = getScriptsDir(projectRoot, config);
1916
- if (!existsSync4(scriptsDir)) {
1957
+ if (!existsSync5(scriptsDir)) {
1917
1958
  mkdirSync3(scriptsDir, { recursive: true });
1918
1959
  }
1960
+ const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
1961
+ let existingMetadata = [];
1962
+ if (existsSync5(metadataPath)) {
1963
+ try {
1964
+ existingMetadata = JSON.parse(readFileSync5(metadataPath, "utf-8"));
1965
+ } catch {
1966
+ }
1967
+ }
1919
1968
  const scriptsToPull = options.script ? scripts.filter((s) => s.slug === options.script || s.name === options.script) : scripts;
1920
1969
  if (options.script && scriptsToPull.length === 0) {
1921
1970
  console.log(chalk2.yellow(`
1922
- \u26A0\uFE0F Script "${options.script}" not found.`));
1923
- console.log(chalk2.dim("Available scripts:"));
1971
+ Fant ikke skript "${options.script}".`));
1972
+ console.log(chalk2.dim("Tilgjengelige skript:"));
1924
1973
  scripts.forEach((s) => {
1925
1974
  console.log(chalk2.dim(` - ${s.slug} (${s.name})`));
1926
1975
  });
@@ -1929,198 +1978,314 @@ async function pullCommand(options) {
1929
1978
  console.log();
1930
1979
  let pulled = 0;
1931
1980
  let skipped = 0;
1981
+ let updated = 0;
1932
1982
  for (const script of scriptsToPull) {
1933
1983
  const ext = script.type === "javascript" ? "js" : "css";
1934
1984
  const fileName = `${script.slug}.${ext}`;
1935
- const filePath = join4(scriptsDir, fileName);
1936
- if (existsSync4(filePath) && !options.force) {
1937
- const localContent = await import("fs").then(
1938
- (fs) => fs.readFileSync(filePath, "utf-8")
1939
- );
1940
- if (localContent !== script.content) {
1941
- console.log(
1942
- chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(" (local changes exist, use --force to overwrite)")
1943
- );
1985
+ const filePath = join5(scriptsDir, fileName);
1986
+ const existingMeta = existingMetadata.find((m) => m.slug === script.slug);
1987
+ const lastPulledVersion = existingMeta?.lastPulledVersion || 0;
1988
+ if (existsSync5(filePath) && !options.force) {
1989
+ const localContent = readFileSync5(filePath, "utf-8");
1990
+ const localHash = hashContent(localContent);
1991
+ const hasLocalChanges = existingMeta && localHash !== existingMeta.contentHash;
1992
+ if (hasLocalChanges) {
1993
+ if (script.current_version > lastPulledVersion) {
1994
+ console.log(
1995
+ chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(` (lokal v${lastPulledVersion} \u2192 server v${script.current_version}, konflikt)`)
1996
+ );
1997
+ console.log(chalk2.dim(` Bruk --force for \xE5 overskrive lokale endringer`));
1998
+ } else {
1999
+ console.log(
2000
+ chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(" (lokale endringer, bruk --force)")
2001
+ );
2002
+ }
2003
+ skipped++;
2004
+ continue;
2005
+ }
2006
+ if (script.content === localContent) {
2007
+ console.log(chalk2.dim(` \u25CB ${fileName} (ingen endringer)`));
1944
2008
  skipped++;
1945
2009
  continue;
1946
2010
  }
1947
2011
  }
1948
- writeFileSync4(filePath, script.content);
2012
+ writeFileSync5(filePath, script.content);
1949
2013
  const scopeTag = script.scope === "global" ? chalk2.blue("[G]") : chalk2.magenta("[P]");
1950
2014
  const loadTag = script.auto_load ? chalk2.green("\u26A1") : chalk2.dim("\u25CB");
1951
- console.log(
1952
- chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
1953
- );
1954
- pulled++;
2015
+ if (lastPulledVersion > 0 && script.current_version > lastPulledVersion) {
2016
+ console.log(
2017
+ chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${lastPulledVersion} \u2192 v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
2018
+ );
2019
+ updated++;
2020
+ } else {
2021
+ console.log(
2022
+ chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
2023
+ );
2024
+ pulled++;
2025
+ }
1955
2026
  }
1956
2027
  console.log();
1957
2028
  if (pulled > 0) {
1958
- console.log(chalk2.green(`\u2705 Pulled ${pulled} script(s) to ${config.scriptsDir}/`));
2029
+ console.log(chalk2.green(`Hentet ${pulled} skript til ${config.scriptsDir}/`));
2030
+ }
2031
+ if (updated > 0) {
2032
+ console.log(chalk2.cyan(`Oppdatert ${updated} skript med nye versjoner`));
1959
2033
  }
1960
2034
  if (skipped > 0) {
1961
- console.log(chalk2.yellow(`\u26A0\uFE0F Skipped ${skipped} script(s) with local changes`));
2035
+ console.log(chalk2.dim(`Hoppet over ${skipped} skript`));
2036
+ }
2037
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2038
+ const metadata = scripts.map((s) => {
2039
+ const filePath = join5(scriptsDir, `${s.slug}.${s.type === "javascript" ? "js" : "css"}`);
2040
+ const localContent = existsSync5(filePath) ? readFileSync5(filePath, "utf-8") : s.content;
2041
+ return {
2042
+ id: s.id,
2043
+ slug: s.slug,
2044
+ name: s.name,
2045
+ type: s.type,
2046
+ scope: s.scope,
2047
+ autoLoad: s.auto_load,
2048
+ version: s.current_version,
2049
+ loadOrder: s.load_order,
2050
+ lastPulledVersion: s.current_version,
2051
+ lastPulledAt: now,
2052
+ contentHash: hashContent(localContent)
2053
+ };
2054
+ });
2055
+ writeFileSync5(metadataPath, JSON.stringify(metadata, null, 2));
2056
+ try {
2057
+ const pages = await client.listPages(config.siteId);
2058
+ const result = updateClaudeMd(
2059
+ projectRoot,
2060
+ config.siteName,
2061
+ config.siteSlug,
2062
+ scriptsToDocsFormat(scripts),
2063
+ pagesToInfoFormat(pages)
2064
+ );
2065
+ if (result.kodeMd.created) {
2066
+ console.log(chalk2.green("Opprettet .cure-kode/KODE.md"));
2067
+ } else if (result.kodeMd.updated) {
2068
+ console.log(chalk2.dim("Oppdatert .cure-kode/KODE.md"));
2069
+ }
2070
+ if (result.claudeMd.created) {
2071
+ console.log(chalk2.green("Opprettet CLAUDE.md med referanse"));
2072
+ } else if (result.claudeMd.updated) {
2073
+ console.log(chalk2.dim("La til Kode-referanse i CLAUDE.md"));
2074
+ }
2075
+ } catch {
1962
2076
  }
1963
- const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
1964
- const metadata = scripts.map((s) => ({
1965
- id: s.id,
1966
- slug: s.slug,
1967
- name: s.name,
1968
- type: s.type,
1969
- scope: s.scope,
1970
- autoLoad: s.auto_load,
1971
- version: s.current_version,
1972
- loadOrder: s.load_order
1973
- }));
1974
- writeFileSync4(metadataPath, JSON.stringify(metadata, null, 2));
1975
- console.log(chalk2.dim("\nLegend: [G]=Global [P]=Page-specific \u26A1=Auto-load \u25CB=Manual load"));
2077
+ console.log(chalk2.dim("\n[G]=Global [P]=Sidespesifikk \u26A1=Auto-last \u25CB=Manuell"));
1976
2078
  } catch (error) {
1977
- spinner.fail("Failed to pull scripts");
1978
- console.error(chalk2.red("\nError:"), error);
2079
+ spinner.fail("Kunne ikke hente skript");
2080
+ console.error(chalk2.red("\nFeil:"), error);
1979
2081
  }
1980
2082
  }
1981
2083
 
1982
2084
  // src/commands/push.ts
1983
2085
  import chalk3 from "chalk";
1984
2086
  import ora3 from "ora";
1985
- import { readFileSync as readFileSync4, existsSync as existsSync5, readdirSync } from "fs";
1986
- import { join as join5, basename, extname } from "path";
2087
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync6, readdirSync } from "fs";
2088
+ import { join as join6, basename, extname } from "path";
2089
+ import { createHash as createHash2 } from "crypto";
2090
+ function hashContent2(content) {
2091
+ return createHash2("sha256").update(content).digest("hex").substring(0, 16);
2092
+ }
2093
+ function formatTimeDiff(date) {
2094
+ const diff = Date.now() - new Date(date).getTime();
2095
+ const minutes = Math.floor(diff / 6e4);
2096
+ const hours = Math.floor(minutes / 60);
2097
+ const days = Math.floor(hours / 24);
2098
+ if (days > 0) return `${days}d siden`;
2099
+ if (hours > 0) return `${hours}t siden`;
2100
+ if (minutes > 0) return `${minutes}m siden`;
2101
+ return "nylig";
2102
+ }
1987
2103
  async function pushCommand(options) {
1988
2104
  const projectRoot = findProjectRoot();
1989
2105
  if (!projectRoot) {
1990
- console.log(chalk3.red("\u274C Not in a Cure Kode project."));
1991
- console.log(chalk3.dim(' Run "kode init" first.'));
2106
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
2107
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
1992
2108
  return;
1993
2109
  }
1994
2110
  const config = getProjectConfig(projectRoot);
1995
2111
  if (!config) {
1996
- console.log(chalk3.red("\u274C Could not read project configuration."));
2112
+ console.log(chalk3.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
1997
2113
  return;
1998
2114
  }
1999
2115
  const scriptsDir = getScriptsDir(projectRoot, config);
2000
- if (!existsSync5(scriptsDir)) {
2001
- console.log(chalk3.yellow("\u26A0\uFE0F Scripts directory not found."));
2002
- console.log(chalk3.dim(` Expected: ${scriptsDir}`));
2003
- console.log(chalk3.dim(' Run "kode pull" first or create scripts manually.'));
2116
+ if (!existsSync6(scriptsDir)) {
2117
+ console.log(chalk3.yellow("Skriptmappen finnes ikke."));
2118
+ console.log(chalk3.dim(`Forventet: ${scriptsDir}`));
2119
+ console.log(chalk3.dim('Kj\xF8r "kode pull" f\xF8rst eller opprett skript manuelt.'));
2004
2120
  return;
2005
2121
  }
2006
- const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
2122
+ const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
2007
2123
  let metadata = [];
2008
- if (existsSync5(metadataPath)) {
2009
- metadata = JSON.parse(readFileSync4(metadataPath, "utf-8"));
2124
+ if (existsSync6(metadataPath)) {
2125
+ try {
2126
+ metadata = JSON.parse(readFileSync6(metadataPath, "utf-8"));
2127
+ } catch {
2128
+ }
2010
2129
  }
2011
2130
  const files = readdirSync(scriptsDir).filter(
2012
2131
  (f) => f.endsWith(".js") || f.endsWith(".css")
2013
2132
  );
2014
2133
  if (files.length === 0) {
2015
- console.log(chalk3.yellow("\u26A0\uFE0F No script files found."));
2016
- console.log(chalk3.dim(` Add .js or .css files to ${scriptsDir}/`));
2134
+ console.log(chalk3.yellow("Ingen skriptfiler funnet."));
2135
+ console.log(chalk3.dim(`Legg til .js eller .css filer i ${scriptsDir}/`));
2017
2136
  return;
2018
2137
  }
2019
2138
  const filesToPush = options.script ? files.filter(
2020
2139
  (f) => basename(f, extname(f)) === options.script || f === options.script || f === `${options.script}.js` || f === `${options.script}.css`
2021
2140
  ) : files;
2022
2141
  if (options.script && filesToPush.length === 0) {
2023
- console.log(chalk3.yellow(`\u26A0\uFE0F Script "${options.script}" not found locally.`));
2024
- console.log(chalk3.dim("Available local scripts:"));
2142
+ console.log(chalk3.yellow(`Fant ikke skript "${options.script}" lokalt.`));
2143
+ console.log(chalk3.dim("Tilgjengelige lokale skript:"));
2025
2144
  files.forEach((f) => {
2026
2145
  console.log(chalk3.dim(` - ${f}`));
2027
2146
  });
2028
2147
  return;
2029
2148
  }
2030
- const spinner = ora3("Pushing scripts...").start();
2149
+ const spinner = ora3("Laster opp skript...").start();
2031
2150
  try {
2032
2151
  const client = createApiClient(config);
2033
2152
  const remoteScripts = await client.listScripts(config.siteId);
2034
2153
  let pushed = 0;
2035
2154
  let created = 0;
2036
2155
  let skipped = 0;
2156
+ let conflicts = 0;
2037
2157
  spinner.stop();
2038
2158
  console.log();
2039
2159
  let emptyScriptCount = 0;
2040
2160
  for (const file of filesToPush) {
2041
- const filePath = join5(scriptsDir, file);
2042
- const content = readFileSync4(filePath, "utf-8");
2161
+ const filePath = join6(scriptsDir, file);
2162
+ const content = readFileSync6(filePath, "utf-8");
2043
2163
  const slug = basename(file, extname(file));
2044
2164
  const type = extname(file) === ".js" ? "javascript" : "css";
2045
2165
  if (content.trim().length === 0) {
2046
- console.log(chalk3.yellow(` \u26A0 ${file}`) + chalk3.dim(" (empty file)"));
2166
+ console.log(chalk3.yellow(` \u26A0 ${file}`) + chalk3.dim(" (tom fil)"));
2047
2167
  emptyScriptCount++;
2048
2168
  }
2049
2169
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
2050
2170
  const localMeta = metadata.find((m) => m.slug === slug);
2051
2171
  if (remoteScript) {
2172
+ const lastPulledVersion = localMeta?.lastPulledVersion || 0;
2173
+ if (remoteScript.current_version > lastPulledVersion && !options.force) {
2174
+ console.log();
2175
+ console.log(chalk3.yellow(` \u26A0\uFE0F Advarsel: Server har nyere versjon!`));
2176
+ console.log(chalk3.dim(` ${file}:`));
2177
+ console.log(chalk3.dim(` Lokal: v${lastPulledVersion} (fra ${localMeta?.lastPulledAt ? formatTimeDiff(localMeta.lastPulledAt) : "ukjent"})`));
2178
+ console.log(chalk3.dim(` Server: v${remoteScript.current_version}`));
2179
+ console.log();
2180
+ console.log(chalk3.dim(` Bruk --force for \xE5 overskrive, eller kj\xF8r "kode pull" f\xF8rst.`));
2181
+ console.log();
2182
+ conflicts++;
2183
+ continue;
2184
+ }
2052
2185
  if (remoteScript.content === content && !options.all) {
2053
- console.log(chalk3.dim(` - ${file} (no changes)`));
2186
+ console.log(chalk3.dim(` \u25CB ${file} (ingen endringer)`));
2054
2187
  skipped++;
2055
2188
  continue;
2056
2189
  }
2057
- const updateSpinner = ora3(`Updating ${file}...`).start();
2190
+ const updateSpinner = ora3(`Oppdaterer ${file}...`).start();
2058
2191
  await client.updateScript(remoteScript.id, {
2059
2192
  content,
2060
- changeSummary: options.message || `Updated via CLI`
2193
+ changeSummary: options.message || `Oppdatert via CLI`
2061
2194
  });
2062
2195
  updateSpinner.succeed(
2063
2196
  chalk3.green(` \u2713 ${file}`) + chalk3.dim(` \u2192 v${remoteScript.current_version + 1}`)
2064
2197
  );
2065
2198
  pushed++;
2066
2199
  } else {
2067
- const createSpinner = ora3(`Creating ${file}...`).start();
2200
+ const createSpinner = ora3(`Oppretter ${file}...`).start();
2068
2201
  const scriptScope = localMeta?.scope || "global";
2069
2202
  const newScript = await client.createScript(config.siteId, {
2070
2203
  name: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, " "),
2071
2204
  slug,
2072
2205
  type,
2073
2206
  scope: scriptScope,
2074
- // autoLoad: if explicitly set via CLI, use that; otherwise let API default based on scope
2075
2207
  autoLoad: options.autoLoad,
2076
2208
  content
2077
2209
  });
2078
- const autoLoadInfo = newScript.auto_load ? chalk3.green("auto-load") : chalk3.dim("manual");
2210
+ const autoLoadInfo = newScript.auto_load ? chalk3.green("auto-last") : chalk3.dim("manuell");
2079
2211
  createSpinner.succeed(
2080
- chalk3.green(` \u2713 ${file}`) + chalk3.cyan(" (new)") + ` [${autoLoadInfo}]`
2212
+ chalk3.green(` \u2713 ${file}`) + chalk3.cyan(" (ny)") + ` [${autoLoadInfo}]`
2081
2213
  );
2082
2214
  created++;
2083
2215
  }
2084
2216
  }
2085
2217
  console.log();
2086
2218
  if (pushed > 0) {
2087
- console.log(chalk3.green(`\u2705 Updated ${pushed} script(s)`));
2219
+ console.log(chalk3.green(`Oppdatert ${pushed} skript`));
2088
2220
  }
2089
2221
  if (created > 0) {
2090
- console.log(chalk3.cyan(`\u2705 Created ${created} new script(s)`));
2222
+ console.log(chalk3.cyan(`Opprettet ${created} nye skript`));
2091
2223
  }
2092
2224
  if (skipped > 0) {
2093
- console.log(chalk3.dim(` Skipped ${skipped} unchanged script(s)`));
2225
+ console.log(chalk3.dim(`Hoppet over ${skipped} uendrede skript`));
2226
+ }
2227
+ if (conflicts > 0) {
2228
+ console.log(chalk3.yellow(`
2229
+ ${conflicts} skript med konflikter (bruk --force for \xE5 overskrive)`));
2094
2230
  }
2095
2231
  if (emptyScriptCount > 0) {
2096
2232
  console.log(chalk3.yellow(`
2097
- \u26A0\uFE0F ${emptyScriptCount} empty script(s) pushed`));
2098
- console.log(chalk3.dim(" Empty scripts will have no effect when deployed."));
2233
+ ${emptyScriptCount} tomme skript lastet opp`));
2234
+ console.log(chalk3.dim("Tomme skript har ingen effekt ved deploy."));
2099
2235
  }
2100
2236
  const updatedScripts = await client.listScripts(config.siteId);
2101
- const updatedMetadata = updatedScripts.map((s) => ({
2102
- id: s.id,
2103
- slug: s.slug,
2104
- name: s.name,
2105
- type: s.type,
2106
- scope: s.scope,
2107
- autoLoad: s.auto_load,
2108
- version: s.current_version,
2109
- loadOrder: s.load_order
2110
- }));
2111
- const fs = await import("fs");
2112
- fs.writeFileSync(metadataPath, JSON.stringify(updatedMetadata, null, 2));
2237
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2238
+ const updatedMetadata = updatedScripts.map((s) => {
2239
+ const ext = s.type === "javascript" ? "js" : "css";
2240
+ const filePath = join6(scriptsDir, `${s.slug}.${ext}`);
2241
+ const localContent = existsSync6(filePath) ? readFileSync6(filePath, "utf-8") : s.content;
2242
+ return {
2243
+ id: s.id,
2244
+ slug: s.slug,
2245
+ name: s.name,
2246
+ type: s.type,
2247
+ scope: s.scope,
2248
+ autoLoad: s.auto_load,
2249
+ version: s.current_version,
2250
+ loadOrder: s.load_order,
2251
+ lastPulledVersion: s.current_version,
2252
+ lastPulledAt: now,
2253
+ contentHash: hashContent2(localContent)
2254
+ };
2255
+ });
2256
+ writeFileSync6(metadataPath, JSON.stringify(updatedMetadata, null, 2));
2257
+ try {
2258
+ const pages = await client.listPages(config.siteId);
2259
+ const result = updateClaudeMd(
2260
+ projectRoot,
2261
+ config.siteName,
2262
+ config.siteSlug,
2263
+ scriptsToDocsFormat(updatedScripts),
2264
+ pagesToInfoFormat(pages)
2265
+ );
2266
+ if (result.kodeMd.created) {
2267
+ console.log(chalk3.green("Opprettet .cure-kode/KODE.md"));
2268
+ } else if (result.kodeMd.updated) {
2269
+ console.log(chalk3.dim("Oppdatert .cure-kode/KODE.md"));
2270
+ }
2271
+ if (result.claudeMd.created) {
2272
+ console.log(chalk3.green("Opprettet CLAUDE.md med referanse"));
2273
+ } else if (result.claudeMd.updated) {
2274
+ console.log(chalk3.dim("La til Kode-referanse i CLAUDE.md"));
2275
+ }
2276
+ } catch {
2277
+ }
2113
2278
  } catch (error) {
2114
- spinner.fail("Failed to push scripts");
2115
- console.error(chalk3.red("\nError:"), error);
2279
+ spinner.fail("Kunne ikke laste opp skript");
2280
+ console.error(chalk3.red("\nFeil:"), error);
2116
2281
  }
2117
2282
  }
2118
2283
 
2119
2284
  // src/commands/watch.ts
2120
2285
  import chalk4 from "chalk";
2121
2286
  import chokidar from "chokidar";
2122
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
2123
- import { join as join6, basename as basename2, extname as extname2 } from "path";
2287
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
2288
+ import { join as join7, basename as basename2, extname as extname2 } from "path";
2124
2289
  async function watchCommand(options) {
2125
2290
  const projectRoot = findProjectRoot();
2126
2291
  if (!projectRoot) {
@@ -2134,7 +2299,7 @@ async function watchCommand(options) {
2134
2299
  return;
2135
2300
  }
2136
2301
  const scriptsDir = getScriptsDir(projectRoot, config);
2137
- if (!existsSync6(scriptsDir)) {
2302
+ if (!existsSync7(scriptsDir)) {
2138
2303
  console.log(chalk4.yellow("\u26A0\uFE0F Scripts directory not found."));
2139
2304
  console.log(chalk4.dim(` Expected: ${scriptsDir}`));
2140
2305
  console.log(chalk4.dim(' Run "kode pull" first.'));
@@ -2150,10 +2315,10 @@ async function watchCommand(options) {
2150
2315
  console.log();
2151
2316
  console.log(chalk4.dim("Press Ctrl+C to stop.\n"));
2152
2317
  const client = createApiClient(config);
2153
- const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
2318
+ const metadataPath = join7(projectRoot, ".cure-kode", "scripts.json");
2154
2319
  let metadata = [];
2155
- if (existsSync6(metadataPath)) {
2156
- metadata = JSON.parse(readFileSync5(metadataPath, "utf-8"));
2320
+ if (existsSync7(metadataPath)) {
2321
+ metadata = JSON.parse(readFileSync7(metadataPath, "utf-8"));
2157
2322
  }
2158
2323
  let remoteScripts = [];
2159
2324
  try {
@@ -2185,7 +2350,7 @@ async function watchCommand(options) {
2185
2350
  };
2186
2351
  const retryFailedSyncs = async () => {
2187
2352
  for (const [filePath, failed] of failedSyncs.entries()) {
2188
- if (!existsSync6(filePath)) {
2353
+ if (!existsSync7(filePath)) {
2189
2354
  failedSyncs.delete(filePath);
2190
2355
  continue;
2191
2356
  }
@@ -2219,7 +2384,7 @@ async function watchCommand(options) {
2219
2384
  }
2220
2385
  const syncFile = async () => {
2221
2386
  try {
2222
- const content = readFileSync5(filePath, "utf-8");
2387
+ const content = readFileSync7(filePath, "utf-8");
2223
2388
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
2224
2389
  const localMeta = metadata.find((m) => m.slug === slug);
2225
2390
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
@@ -2333,7 +2498,7 @@ async function watchCommand(options) {
2333
2498
  }, DEBOUNCE_MS);
2334
2499
  pendingChanges.set(filePath, timeout);
2335
2500
  };
2336
- const watcher = chokidar.watch(join6(scriptsDir, "**/*.{js,css}"), {
2501
+ const watcher = chokidar.watch(join7(scriptsDir, "**/*.{js,css}"), {
2337
2502
  persistent: true,
2338
2503
  ignoreInitial: true,
2339
2504
  awaitWriteFinish: {
@@ -2374,6 +2539,160 @@ async function watchCommand(options) {
2374
2539
  // src/commands/deploy.ts
2375
2540
  import chalk5 from "chalk";
2376
2541
  import ora4 from "ora";
2542
+ import { readFileSync as readFileSync8, existsSync as existsSync8, readdirSync as readdirSync2 } from "fs";
2543
+ import { join as join8, basename as basename3, extname as extname3 } from "path";
2544
+ function formatBytes(bytes) {
2545
+ if (bytes < 1024) return `${bytes} B`;
2546
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2547
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2548
+ }
2549
+ async function showDeploymentPreview(config, projectRoot) {
2550
+ if (!config) return;
2551
+ const client = createApiClient(config);
2552
+ const spinner = ora4("Analyserer deployment...").start();
2553
+ try {
2554
+ const [remoteScripts, deployStatus] = await Promise.all([
2555
+ client.listScripts(config.siteId),
2556
+ client.getDeploymentStatus(config.siteId)
2557
+ ]);
2558
+ spinner.stop();
2559
+ console.log();
2560
+ console.log(chalk5.bold("\u{1F50D} Deployment Preview (t\xF8rrkj\xF8ring)"));
2561
+ console.log(chalk5.dim(` M\xE5l: staging`));
2562
+ console.log();
2563
+ const stagingVersion = deployStatus.staging.lastSuccessful?.version || null;
2564
+ const productionVersion = deployStatus.production.lastSuccessful?.version || null;
2565
+ console.log(chalk5.bold("N\xE5v\xE6rende status"));
2566
+ if (stagingVersion) {
2567
+ console.log(chalk5.dim(` Staging: v${stagingVersion}`));
2568
+ } else {
2569
+ console.log(chalk5.dim(` Staging: ingen deployments`));
2570
+ }
2571
+ if (deployStatus.productionEnabled) {
2572
+ if (productionVersion) {
2573
+ console.log(chalk5.dim(` Production: v${productionVersion}`));
2574
+ } else {
2575
+ console.log(chalk5.dim(` Production: aktivert, ingen deployments`));
2576
+ }
2577
+ }
2578
+ console.log();
2579
+ const scriptsDir = getScriptsDir(projectRoot, config);
2580
+ const localFiles = existsSync8(scriptsDir) ? readdirSync2(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
2581
+ const metadataPath = join8(projectRoot, ".cure-kode", "scripts.json");
2582
+ let metadata = [];
2583
+ if (existsSync8(metadataPath)) {
2584
+ try {
2585
+ metadata = JSON.parse(readFileSync8(metadataPath, "utf-8"));
2586
+ } catch {
2587
+ }
2588
+ }
2589
+ const localBySlug = /* @__PURE__ */ new Map();
2590
+ for (const file of localFiles) {
2591
+ const slug = basename3(file, extname3(file));
2592
+ const content = readFileSync8(join8(scriptsDir, file), "utf-8");
2593
+ localBySlug.set(slug, { content, size: Buffer.byteLength(content, "utf-8") });
2594
+ }
2595
+ const remoteBySlug = /* @__PURE__ */ new Map();
2596
+ for (const script of remoteScripts) {
2597
+ remoteBySlug.set(script.slug, script);
2598
+ }
2599
+ const changes = [];
2600
+ let totalBeforeSize = 0;
2601
+ let totalAfterSize = 0;
2602
+ const warnings = [];
2603
+ for (const [slug, local] of localBySlug) {
2604
+ const remote = remoteBySlug.get(slug);
2605
+ const meta = metadata.find((m) => m.slug === slug);
2606
+ totalAfterSize += local.size;
2607
+ if (!remote) {
2608
+ const lines = local.content.split("\n").length;
2609
+ changes.push({
2610
+ slug,
2611
+ fileName: `${slug}.${local.content.includes("{") ? "js" : "css"}`,
2612
+ beforeVersion: null,
2613
+ afterVersion: 1,
2614
+ linesDiff: { added: lines, removed: 0 },
2615
+ sizeDiff: local.size,
2616
+ isNew: true
2617
+ });
2618
+ warnings.push(`${slug} er ny - test f\xF8r produksjon`);
2619
+ } else {
2620
+ totalBeforeSize += Buffer.byteLength(remote.content, "utf-8");
2621
+ if (local.content !== remote.content) {
2622
+ const localLines = local.content.split("\n");
2623
+ const remoteLines = remote.content.split("\n");
2624
+ const added = localLines.filter((l) => !remoteLines.includes(l)).length;
2625
+ const removed = remoteLines.filter((l) => !localLines.includes(l)).length;
2626
+ const ext = remote.type === "javascript" ? "js" : "css";
2627
+ changes.push({
2628
+ slug,
2629
+ fileName: `${slug}.${ext}`,
2630
+ beforeVersion: remote.current_version,
2631
+ afterVersion: remote.current_version + 1,
2632
+ linesDiff: { added, removed },
2633
+ sizeDiff: local.size - Buffer.byteLength(remote.content, "utf-8"),
2634
+ isNew: false
2635
+ });
2636
+ } else {
2637
+ totalAfterSize = totalAfterSize - local.size + Buffer.byteLength(remote.content, "utf-8");
2638
+ }
2639
+ }
2640
+ }
2641
+ for (const [slug, remote] of remoteBySlug) {
2642
+ if (!localBySlug.has(slug)) {
2643
+ const size = Buffer.byteLength(remote.content, "utf-8");
2644
+ totalBeforeSize += size;
2645
+ totalAfterSize += size;
2646
+ }
2647
+ }
2648
+ if (changes.length > 0) {
2649
+ console.log(chalk5.bold("Endringer"));
2650
+ console.log();
2651
+ console.log(
2652
+ chalk5.dim(" ") + chalk5.dim("Skript".padEnd(25)) + chalk5.dim("F\xF8r".padStart(8)) + chalk5.dim("Etter".padStart(8)) + chalk5.dim("Endring".padStart(15))
2653
+ );
2654
+ console.log(chalk5.dim(" " + "\u2500".repeat(56)));
2655
+ for (const change of changes) {
2656
+ const beforeStr = change.beforeVersion ? `v${change.beforeVersion}` : "-";
2657
+ const afterStr = `v${change.afterVersion}`;
2658
+ let diffStr;
2659
+ if (change.isNew) {
2660
+ diffStr = chalk5.cyan(`+${change.linesDiff.added} linjer`);
2661
+ } else {
2662
+ const parts = [];
2663
+ if (change.linesDiff.added > 0) parts.push(chalk5.green(`+${change.linesDiff.added}`));
2664
+ if (change.linesDiff.removed > 0) parts.push(chalk5.red(`-${change.linesDiff.removed}`));
2665
+ diffStr = parts.join(" ") + " linjer";
2666
+ }
2667
+ const nameColor = change.isNew ? chalk5.cyan : chalk5.white;
2668
+ console.log(
2669
+ ` ${nameColor(change.fileName.padEnd(25))}${chalk5.dim(beforeStr.padStart(8))}${afterStr.padStart(8)}${diffStr.padStart(15)}`
2670
+ );
2671
+ }
2672
+ console.log();
2673
+ const sizeDiff = totalAfterSize - totalBeforeSize;
2674
+ const sizeDiffStr = sizeDiff > 0 ? chalk5.yellow(`+${formatBytes(sizeDiff)}`) : sizeDiff < 0 ? chalk5.green(`-${formatBytes(Math.abs(sizeDiff))}`) : chalk5.dim("uendret");
2675
+ console.log(
2676
+ chalk5.dim(` Total: ${formatBytes(totalBeforeSize)} \u2192 ${formatBytes(totalAfterSize)} (${sizeDiffStr})`)
2677
+ );
2678
+ } else {
2679
+ console.log(chalk5.dim(" Ingen endringer \xE5 deploye"));
2680
+ }
2681
+ if (warnings.length > 0) {
2682
+ console.log();
2683
+ console.log(chalk5.bold("Advarsler"));
2684
+ for (const warning of warnings) {
2685
+ console.log(chalk5.yellow(` \u26A0 ${warning}`));
2686
+ }
2687
+ }
2688
+ console.log();
2689
+ console.log(chalk5.dim('Dette er en t\xF8rrkj\xF8ring. Kj\xF8r "kode deploy" for \xE5 deploye.'));
2690
+ console.log();
2691
+ } catch (error) {
2692
+ spinner.fail("Kunne ikke analysere deployment");
2693
+ console.error(chalk5.red("\nFeil:"), error.message || error);
2694
+ }
2695
+ }
2377
2696
  async function deployCommand(environment, options) {
2378
2697
  const projectRoot = findProjectRoot();
2379
2698
  if (!projectRoot) {
@@ -2386,6 +2705,10 @@ async function deployCommand(environment, options) {
2386
2705
  console.log(chalk5.red("\u274C Could not read project configuration."));
2387
2706
  return;
2388
2707
  }
2708
+ if (options?.dryRun) {
2709
+ await showDeploymentPreview(config, projectRoot);
2710
+ return;
2711
+ }
2389
2712
  const shouldPromote = options?.promote || environment === "production";
2390
2713
  if (shouldPromote) {
2391
2714
  const client2 = createApiClient(config);
@@ -2498,8 +2821,8 @@ import chalk6 from "chalk";
2498
2821
  import ora5 from "ora";
2499
2822
 
2500
2823
  // src/lib/page-cache.ts
2501
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync } from "fs";
2502
- import { join as join7, basename as basename3 } from "path";
2824
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync7, unlinkSync } from "fs";
2825
+ import { join as join9, basename as basename4 } from "path";
2503
2826
  var PROJECT_CONFIG_DIR3 = ".cure-kode";
2504
2827
  var PAGES_DIR = "pages";
2505
2828
  function urlToSlug(url) {
@@ -2513,15 +2836,15 @@ function urlToSlug(url) {
2513
2836
  }
2514
2837
  }
2515
2838
  function getPagesDir(projectRoot) {
2516
- return join7(projectRoot, PROJECT_CONFIG_DIR3, PAGES_DIR);
2839
+ return join9(projectRoot, PROJECT_CONFIG_DIR3, PAGES_DIR);
2517
2840
  }
2518
2841
  function getPageCachePath(projectRoot, urlOrSlug) {
2519
2842
  const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
2520
- return join7(getPagesDir(projectRoot), `${slug}.json`);
2843
+ return join9(getPagesDir(projectRoot), `${slug}.json`);
2521
2844
  }
2522
2845
  function ensurePagesDir(projectRoot) {
2523
2846
  const pagesDir = getPagesDir(projectRoot);
2524
- if (!existsSync7(pagesDir)) {
2847
+ if (!existsSync9(pagesDir)) {
2525
2848
  mkdirSync4(pagesDir, { recursive: true });
2526
2849
  }
2527
2850
  }
@@ -2529,16 +2852,16 @@ function savePageContext(projectRoot, context) {
2529
2852
  ensurePagesDir(projectRoot);
2530
2853
  const slug = urlToSlug(context.url);
2531
2854
  const cachePath = getPageCachePath(projectRoot, slug);
2532
- writeFileSync5(cachePath, JSON.stringify(context, null, 2), "utf-8");
2855
+ writeFileSync7(cachePath, JSON.stringify(context, null, 2), "utf-8");
2533
2856
  return slug;
2534
2857
  }
2535
2858
  function readPageContext(projectRoot, urlOrSlug) {
2536
2859
  const cachePath = getPageCachePath(projectRoot, urlOrSlug);
2537
- if (!existsSync7(cachePath)) {
2860
+ if (!existsSync9(cachePath)) {
2538
2861
  return null;
2539
2862
  }
2540
2863
  try {
2541
- const content = readFileSync6(cachePath, "utf-8");
2864
+ const content = readFileSync9(cachePath, "utf-8");
2542
2865
  return JSON.parse(content);
2543
2866
  } catch {
2544
2867
  return null;
@@ -2546,7 +2869,7 @@ function readPageContext(projectRoot, urlOrSlug) {
2546
2869
  }
2547
2870
  function deletePageContext(projectRoot, urlOrSlug) {
2548
2871
  const cachePath = getPageCachePath(projectRoot, urlOrSlug);
2549
- if (existsSync7(cachePath)) {
2872
+ if (existsSync9(cachePath)) {
2550
2873
  unlinkSync(cachePath);
2551
2874
  return true;
2552
2875
  }
@@ -2554,13 +2877,13 @@ function deletePageContext(projectRoot, urlOrSlug) {
2554
2877
  }
2555
2878
  function listCachedPages(projectRoot) {
2556
2879
  const pagesDir = getPagesDir(projectRoot);
2557
- if (!existsSync7(pagesDir)) {
2880
+ if (!existsSync9(pagesDir)) {
2558
2881
  return [];
2559
2882
  }
2560
- const files = readdirSync2(pagesDir).filter((f) => f.endsWith(".json"));
2883
+ const files = readdirSync3(pagesDir).filter((f) => f.endsWith(".json"));
2561
2884
  const pages = [];
2562
2885
  for (const file of files) {
2563
- const slug = basename3(file, ".json");
2886
+ const slug = basename4(file, ".json");
2564
2887
  const context = readPageContext(projectRoot, slug);
2565
2888
  if (context) {
2566
2889
  pages.push({
@@ -2837,8 +3160,42 @@ function printPageContext(context) {
2837
3160
  // src/commands/status.ts
2838
3161
  import chalk7 from "chalk";
2839
3162
  import ora6 from "ora";
2840
- import { readFileSync as readFileSync7, existsSync as existsSync8, readdirSync as readdirSync3, statSync } from "fs";
2841
- import { join as join8, basename as basename4, extname as extname3 } from "path";
3163
+ import { readFileSync as readFileSync10, existsSync as existsSync10, readdirSync as readdirSync4, statSync } from "fs";
3164
+ import { join as join10, basename as basename5, extname as extname4 } from "path";
3165
+ import { createHash as createHash3 } from "crypto";
3166
+ function hashContent3(content) {
3167
+ return createHash3("sha256").update(content).digest("hex").substring(0, 16);
3168
+ }
3169
+ function formatRelativeTime(dateStr) {
3170
+ const date = new Date(dateStr);
3171
+ const now = /* @__PURE__ */ new Date();
3172
+ const diffMs = now.getTime() - date.getTime();
3173
+ const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
3174
+ const diffDays = Math.floor(diffHours / 24);
3175
+ if (diffDays > 0) {
3176
+ return `${diffDays} dag${diffDays > 1 ? "er" : ""} siden`;
3177
+ } else if (diffHours > 0) {
3178
+ return `${diffHours} time${diffHours > 1 ? "r" : ""} siden`;
3179
+ } else {
3180
+ const diffMins = Math.floor(diffMs / (1e3 * 60));
3181
+ return diffMins > 1 ? `${diffMins} minutter siden` : "nettopp";
3182
+ }
3183
+ }
3184
+ function isSyncStale(metadata) {
3185
+ if (metadata.length === 0) {
3186
+ return { isStale: true, lastSyncedAt: null, ageHours: Infinity };
3187
+ }
3188
+ const latestSync = metadata.reduce((latest, m) => {
3189
+ const date = new Date(m.lastPulledAt);
3190
+ return date > latest ? date : latest;
3191
+ }, /* @__PURE__ */ new Date(0));
3192
+ const ageHours = (Date.now() - latestSync.getTime()) / (1e3 * 60 * 60);
3193
+ return {
3194
+ isStale: ageHours > 24,
3195
+ lastSyncedAt: latestSync.toISOString(),
3196
+ ageHours
3197
+ };
3198
+ }
2842
3199
  async function statusCommand(options) {
2843
3200
  const projectRoot = findProjectRoot();
2844
3201
  if (!projectRoot) {
@@ -2851,6 +3208,14 @@ async function statusCommand(options) {
2851
3208
  console.log(chalk7.red("\u274C Could not read project configuration."));
2852
3209
  return;
2853
3210
  }
3211
+ const metadataPath = join10(projectRoot, ".cure-kode", "scripts.json");
3212
+ let syncMetadata = [];
3213
+ if (existsSync10(metadataPath)) {
3214
+ try {
3215
+ syncMetadata = JSON.parse(readFileSync10(metadataPath, "utf-8"));
3216
+ } catch {
3217
+ }
3218
+ }
2854
3219
  console.log();
2855
3220
  console.log(chalk7.bold(`\u{1F4E6} ${config.siteName}`));
2856
3221
  console.log(chalk7.dim(` Slug: ${config.siteSlug}`));
@@ -2911,14 +3276,29 @@ async function statusCommand(options) {
2911
3276
  }
2912
3277
  console.log();
2913
3278
  console.log(chalk7.bold("Scripts"));
3279
+ const syncStatus = isSyncStale(syncMetadata);
3280
+ if (syncStatus.isStale && syncStatus.lastSyncedAt) {
3281
+ const syncDate = new Date(syncStatus.lastSyncedAt);
3282
+ const dateStr = syncDate.toLocaleString("nb-NO", {
3283
+ day: "2-digit",
3284
+ month: "2-digit",
3285
+ hour: "2-digit",
3286
+ minute: "2-digit"
3287
+ });
3288
+ console.log(
3289
+ chalk7.yellow(` \u26A0 Sist synkronisert: ${formatRelativeTime(syncStatus.lastSyncedAt)}`) + chalk7.dim(` (${dateStr})`)
3290
+ );
3291
+ } else if (!syncStatus.lastSyncedAt && syncMetadata.length === 0) {
3292
+ console.log(chalk7.yellow(` \u26A0 Aldri synkronisert - kj\xF8r "kode pull" f\xF8rst`));
3293
+ }
2914
3294
  console.log();
2915
3295
  const scriptsDir = getScriptsDir(projectRoot, config);
2916
- const localFiles = existsSync8(scriptsDir) ? readdirSync3(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
3296
+ const localFiles = existsSync10(scriptsDir) ? readdirSync4(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
2917
3297
  const localBySlug = /* @__PURE__ */ new Map();
2918
3298
  for (const file of localFiles) {
2919
- const slug = basename4(file, extname3(file));
2920
- const filePath = join8(scriptsDir, file);
2921
- const content = readFileSync7(filePath, "utf-8");
3299
+ const slug = basename5(file, extname4(file));
3300
+ const filePath = join10(scriptsDir, file);
3301
+ const content = readFileSync10(filePath, "utf-8");
2922
3302
  const stats = statSync(filePath);
2923
3303
  localBySlug.set(slug, { file, content, modified: stats.mtime });
2924
3304
  }
@@ -2926,16 +3306,39 @@ async function statusCommand(options) {
2926
3306
  for (const script of remoteScripts) {
2927
3307
  remoteBySlug.set(script.slug, script);
2928
3308
  }
3309
+ const metadataBySlug = /* @__PURE__ */ new Map();
3310
+ for (const meta of syncMetadata) {
3311
+ metadataBySlug.set(meta.slug, meta);
3312
+ }
2929
3313
  const allSlugs = /* @__PURE__ */ new Set([...localBySlug.keys(), ...remoteBySlug.keys()]);
3314
+ let outdatedCount = 0;
2930
3315
  for (const slug of allSlugs) {
2931
3316
  const local = localBySlug.get(slug);
2932
3317
  const remote = remoteBySlug.get(slug);
3318
+ const meta = metadataBySlug.get(slug);
2933
3319
  if (local && remote) {
2934
- const isChanged = local.content !== remote.content;
2935
3320
  const scopeTag = remote.scope === "global" ? chalk7.blue("[G]") : chalk7.magenta("[P]");
2936
- if (isChanged) {
3321
+ const localHash = hashContent3(local.content);
3322
+ const hasLocalChanges = meta && localHash !== meta.contentHash;
3323
+ const lastPulledVersion = meta?.lastPulledVersion || 0;
3324
+ const hasRemoteChanges = remote.current_version > lastPulledVersion && lastPulledVersion > 0;
3325
+ if (hasLocalChanges && hasRemoteChanges) {
3326
+ console.log(
3327
+ chalk7.red(" \u26A0 ") + `${local.file}` + chalk7.dim(` v${lastPulledVersion}`) + chalk7.red(` \u2192 v${remote.current_version}`) + ` ${scopeTag}` + chalk7.red(" (konflikt)")
3328
+ );
3329
+ outdatedCount++;
3330
+ } else if (hasLocalChanges) {
2937
3331
  console.log(
2938
- chalk7.yellow(" M ") + `${local.file}` + chalk7.dim(` v${remote.current_version} ${scopeTag}`) + chalk7.yellow(" (modified)")
3332
+ chalk7.yellow(" M ") + `${local.file}` + chalk7.dim(` v${remote.current_version} ${scopeTag}`) + chalk7.yellow(" (lokalt endret)")
3333
+ );
3334
+ } else if (hasRemoteChanges) {
3335
+ console.log(
3336
+ chalk7.cyan(" \u26A0 ") + `${local.file}` + chalk7.dim(` v${lastPulledVersion}`) + chalk7.cyan(` \u2192 v${remote.current_version}`) + ` ${scopeTag}` + chalk7.cyan(" (server oppdatert)")
3337
+ );
3338
+ outdatedCount++;
3339
+ } else if (local.content !== remote.content) {
3340
+ console.log(
3341
+ chalk7.yellow(" M ") + `${local.file}` + chalk7.dim(` v${remote.current_version} ${scopeTag}`) + chalk7.yellow(" (lokalt endret)")
2939
3342
  );
2940
3343
  } else {
2941
3344
  console.log(
@@ -2944,24 +3347,27 @@ async function statusCommand(options) {
2944
3347
  }
2945
3348
  } else if (local && !remote) {
2946
3349
  console.log(
2947
- chalk7.cyan(" + ") + `${local.file}` + chalk7.cyan(" (new, not pushed)")
3350
+ chalk7.cyan(" + ") + `${local.file}` + chalk7.cyan(" (ny, ikke pushet)")
2948
3351
  );
2949
3352
  } else if (!local && remote) {
2950
3353
  const ext = remote.type === "javascript" ? "js" : "css";
2951
3354
  const fileName = `${slug}.${ext}`;
2952
3355
  console.log(
2953
- chalk7.red(" - ") + chalk7.dim(`${fileName}`) + chalk7.red(" (remote only, not pulled)")
3356
+ chalk7.red(" - ") + chalk7.dim(`${fileName}`) + chalk7.red(" (kun p\xE5 server, ikke pullet)")
2954
3357
  );
2955
3358
  }
2956
3359
  }
2957
3360
  if (allSlugs.size === 0) {
2958
- console.log(chalk7.dim(" No scripts yet."));
3361
+ console.log(chalk7.dim(" Ingen skript enda."));
2959
3362
  }
2960
3363
  console.log();
2961
3364
  const modified = [...allSlugs].filter((slug) => {
2962
3365
  const local = localBySlug.get(slug);
2963
3366
  const remote = remoteBySlug.get(slug);
2964
- return local && remote && local.content !== remote.content;
3367
+ if (!local || !remote) return false;
3368
+ const meta = metadataBySlug.get(slug);
3369
+ const localHash = hashContent3(local.content);
3370
+ return meta ? localHash !== meta.contentHash : local.content !== remote.content;
2965
3371
  }).length;
2966
3372
  const newLocal = [...allSlugs].filter(
2967
3373
  (slug) => localBySlug.has(slug) && !remoteBySlug.has(slug)
@@ -2969,17 +3375,20 @@ async function statusCommand(options) {
2969
3375
  const remoteOnly = [...allSlugs].filter(
2970
3376
  (slug) => !localBySlug.has(slug) && remoteBySlug.has(slug)
2971
3377
  ).length;
2972
- if (modified > 0 || newLocal > 0) {
2973
- console.log(chalk7.bold("Actions"));
3378
+ if (modified > 0 || newLocal > 0 || remoteOnly > 0 || outdatedCount > 0) {
3379
+ console.log(chalk7.bold("Handlinger"));
2974
3380
  console.log();
2975
3381
  if (modified > 0) {
2976
- console.log(chalk7.yellow(` ${modified} modified`) + chalk7.dim(' \u2192 Run "kode push" to upload'));
3382
+ console.log(chalk7.yellow(` ${modified} endret lokalt`) + chalk7.dim(' \u2192 "kode push"'));
2977
3383
  }
2978
3384
  if (newLocal > 0) {
2979
- console.log(chalk7.cyan(` ${newLocal} new local`) + chalk7.dim(' \u2192 Run "kode push" to create'));
3385
+ console.log(chalk7.cyan(` ${newLocal} nye lokale`) + chalk7.dim(' \u2192 "kode push"'));
3386
+ }
3387
+ if (outdatedCount > 0) {
3388
+ console.log(chalk7.cyan(` ${outdatedCount} utdaterte`) + chalk7.dim(' \u2192 "kode pull"'));
2980
3389
  }
2981
3390
  if (remoteOnly > 0) {
2982
- console.log(chalk7.red(` ${remoteOnly} remote only`) + chalk7.dim(' \u2192 Run "kode pull" to download'));
3391
+ console.log(chalk7.red(` ${remoteOnly} kun p\xE5 server`) + chalk7.dim(' \u2192 "kode pull"'));
2983
3392
  }
2984
3393
  console.log();
2985
3394
  }
@@ -3179,8 +3588,12 @@ export {
3179
3588
  addSession,
3180
3589
  updateScriptPurpose,
3181
3590
  generateInitialContext,
3182
- generateClaudeMdMinimal,
3183
3591
  generateClaudeMd,
3592
+ updateKodeDocs,
3593
+ scriptsToDocsFormat,
3594
+ pagesToInfoFormat,
3595
+ updateClaudeMd,
3596
+ CLI_VERSION,
3184
3597
  initCommand,
3185
3598
  KodeApiError,
3186
3599
  KodeApiClient,