@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.
- package/README.md +51 -11
- package/dist/{chunk-CUZJE4JZ.js → chunk-NQ7RJJNI.js} +893 -480
- package/dist/cli.js +1342 -168
- package/dist/index.d.ts +45 -11
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
1332
|
-
);
|
|
1333
|
-
console.log(
|
|
1334
|
-
console.log(chalk.dim("
|
|
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("
|
|
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
|
|
1444
|
+
message: "API-n\xF8kkel:",
|
|
1343
1445
|
initial: options.apiKey,
|
|
1344
1446
|
validate: (value) => {
|
|
1345
|
-
if (value.length === 0) return "API
|
|
1346
|
-
if (!value.startsWith("ck_")) return "API
|
|
1347
|
-
if (value.length < 30) return "API
|
|
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
|
-
|
|
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("
|
|
1361
|
-
console.log(
|
|
1362
|
-
console.log(chalk.
|
|
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("
|
|
1368
|
-
console.log(
|
|
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
|
|
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.
|
|
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("
|
|
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 =
|
|
1393
|
-
if (!
|
|
1523
|
+
const scriptsPath = join4(cwd, DEFAULT_SCRIPTS_DIR);
|
|
1524
|
+
if (!existsSync4(scriptsPath)) {
|
|
1394
1525
|
mkdirSync2(scriptsPath, { recursive: true });
|
|
1395
1526
|
}
|
|
1396
|
-
const gitignorePath =
|
|
1397
|
-
const gitignoreContent = `#
|
|
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
|
-
|
|
1401
|
-
|
|
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 (
|
|
1536
|
+
if (existsSync4(mcpConfigPath)) {
|
|
1404
1537
|
try {
|
|
1405
|
-
mcpConfig = JSON.parse(
|
|
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.
|
|
1546
|
+
args: ["-y", "@curenorway/kode-mcp@^1.5.0"]
|
|
1414
1547
|
};
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
-
|
|
1481
|
-
|
|
1482
|
-
spinner.
|
|
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
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
1580
|
-
spinner.succeed("AI
|
|
1581
|
-
console.log(
|
|
1582
|
-
console.log(chalk.
|
|
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 (
|
|
1585
|
-
console.log(chalk.dim(
|
|
1586
|
-
} else if (
|
|
1587
|
-
console.log(chalk.dim(
|
|
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(
|
|
1595
|
-
}
|
|
1596
|
-
console.log(chalk.dim(
|
|
1597
|
-
console.log(chalk.dim(
|
|
1598
|
-
console.log(chalk.dim(
|
|
1599
|
-
console.log(chalk.dim(
|
|
1600
|
-
console.log(chalk.dim(
|
|
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(
|
|
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.
|
|
1614
|
-
console.log(chalk.dim("
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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("
|
|
1645
|
-
console.
|
|
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
|
|
1892
|
-
import { join as
|
|
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("
|
|
1897
|
-
console.log(chalk2.dim('
|
|
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("
|
|
1943
|
+
console.log(chalk2.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
1903
1944
|
return;
|
|
1904
1945
|
}
|
|
1905
|
-
const spinner = ora2("
|
|
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("
|
|
1911
|
-
console.log(chalk2.dim('\
|
|
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(`
|
|
1955
|
+
spinner.succeed(`Fant ${scripts.length} skript`);
|
|
1915
1956
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
1916
|
-
if (!
|
|
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
|
-
|
|
1923
|
-
console.log(chalk2.dim("
|
|
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 =
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
);
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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("
|
|
1978
|
-
console.error(chalk2.red("\
|
|
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
|
|
1986
|
-
import { join as
|
|
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("
|
|
1991
|
-
console.log(chalk3.dim('
|
|
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("
|
|
2112
|
+
console.log(chalk3.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
1997
2113
|
return;
|
|
1998
2114
|
}
|
|
1999
2115
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2000
|
-
if (!
|
|
2001
|
-
console.log(chalk3.yellow("
|
|
2002
|
-
console.log(chalk3.dim(`
|
|
2003
|
-
console.log(chalk3.dim('
|
|
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 =
|
|
2122
|
+
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
2007
2123
|
let metadata = [];
|
|
2008
|
-
if (
|
|
2009
|
-
|
|
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("
|
|
2016
|
-
console.log(chalk3.dim(`
|
|
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(
|
|
2024
|
-
console.log(chalk3.dim("
|
|
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("
|
|
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 =
|
|
2042
|
-
const content =
|
|
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(" (
|
|
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(`
|
|
2186
|
+
console.log(chalk3.dim(` \u25CB ${file} (ingen endringer)`));
|
|
2054
2187
|
skipped++;
|
|
2055
2188
|
continue;
|
|
2056
2189
|
}
|
|
2057
|
-
const updateSpinner = ora3(`
|
|
2190
|
+
const updateSpinner = ora3(`Oppdaterer ${file}...`).start();
|
|
2058
2191
|
await client.updateScript(remoteScript.id, {
|
|
2059
2192
|
content,
|
|
2060
|
-
changeSummary: options.message || `
|
|
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(`
|
|
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-
|
|
2210
|
+
const autoLoadInfo = newScript.auto_load ? chalk3.green("auto-last") : chalk3.dim("manuell");
|
|
2079
2211
|
createSpinner.succeed(
|
|
2080
|
-
chalk3.green(` \u2713 ${file}`) + chalk3.cyan(" (
|
|
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(
|
|
2219
|
+
console.log(chalk3.green(`Oppdatert ${pushed} skript`));
|
|
2088
2220
|
}
|
|
2089
2221
|
if (created > 0) {
|
|
2090
|
-
console.log(chalk3.cyan(
|
|
2222
|
+
console.log(chalk3.cyan(`Opprettet ${created} nye skript`));
|
|
2091
2223
|
}
|
|
2092
2224
|
if (skipped > 0) {
|
|
2093
|
-
console.log(chalk3.dim(`
|
|
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
|
-
|
|
2098
|
-
console.log(chalk3.dim("
|
|
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
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
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("
|
|
2115
|
-
console.error(chalk3.red("\
|
|
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
|
|
2123
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
2318
|
+
const metadataPath = join7(projectRoot, ".cure-kode", "scripts.json");
|
|
2154
2319
|
let metadata = [];
|
|
2155
|
-
if (
|
|
2156
|
-
metadata = JSON.parse(
|
|
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 (!
|
|
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 =
|
|
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(
|
|
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
|
|
2502
|
-
import { join as
|
|
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
|
|
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
|
|
2843
|
+
return join9(getPagesDir(projectRoot), `${slug}.json`);
|
|
2521
2844
|
}
|
|
2522
2845
|
function ensurePagesDir(projectRoot) {
|
|
2523
2846
|
const pagesDir = getPagesDir(projectRoot);
|
|
2524
|
-
if (!
|
|
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
|
-
|
|
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 (!
|
|
2860
|
+
if (!existsSync9(cachePath)) {
|
|
2538
2861
|
return null;
|
|
2539
2862
|
}
|
|
2540
2863
|
try {
|
|
2541
|
-
const content =
|
|
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 (
|
|
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 (!
|
|
2880
|
+
if (!existsSync9(pagesDir)) {
|
|
2558
2881
|
return [];
|
|
2559
2882
|
}
|
|
2560
|
-
const files =
|
|
2883
|
+
const files = readdirSync3(pagesDir).filter((f) => f.endsWith(".json"));
|
|
2561
2884
|
const pages = [];
|
|
2562
2885
|
for (const file of files) {
|
|
2563
|
-
const slug =
|
|
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
|
|
2841
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
2920
|
-
const filePath =
|
|
2921
|
-
const content =
|
|
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
|
-
|
|
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(" (
|
|
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(" (
|
|
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(" (
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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}
|
|
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}
|
|
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}
|
|
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,
|