@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/dist/cli.js CHANGED
@@ -1,115 +1,537 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CLI_VERSION,
4
+ KodeApiError,
3
5
  contextCommand,
4
6
  createApiClient,
5
7
  deletePageContext,
6
8
  deployCommand,
7
9
  findProjectRoot,
8
- generateClaudeMdMinimal,
9
10
  getProjectConfig,
11
+ getScriptsDir,
10
12
  htmlCommand,
11
13
  initCommand,
12
14
  listCachedPages,
15
+ pagesToInfoFormat,
13
16
  pullCommand,
14
17
  pushCommand,
15
18
  readPageContext,
19
+ scriptsToDocsFormat,
16
20
  statusCommand,
21
+ updateClaudeMd,
22
+ updateKodeDocs,
17
23
  watchCommand
18
- } from "./chunk-CUZJE4JZ.js";
24
+ } from "./chunk-NQ7RJJNI.js";
19
25
 
20
26
  // src/cli.ts
21
27
  import { Command } from "commander";
22
- import chalk6 from "chalk";
23
- import { createRequire } from "module";
24
28
 
25
- // src/commands/rollback.ts
29
+ // src/commands/upgrade.ts
26
30
  import chalk from "chalk";
27
31
  import ora from "ora";
32
+ import { existsSync, readFileSync, writeFileSync } from "fs";
33
+ import { join } from "path";
34
+ async function upgradeCommand() {
35
+ const projectRoot = findProjectRoot();
36
+ if (!projectRoot) {
37
+ console.log();
38
+ console.log(chalk.red(" Feil: Ikke i et Cure Kode-prosjekt."));
39
+ console.log();
40
+ console.log(chalk.dim(" Kj\xF8r ") + chalk.cyan("kode init") + chalk.dim(" for \xE5 initialisere et nytt prosjekt."));
41
+ console.log();
42
+ return;
43
+ }
44
+ const config = getProjectConfig(projectRoot);
45
+ if (!config) {
46
+ console.log();
47
+ console.log(chalk.red(" Feil: Kunne ikke lese prosjektkonfigurasjon."));
48
+ console.log();
49
+ return;
50
+ }
51
+ console.log();
52
+ console.log(chalk.bold(" Cure Kode Upgrade") + chalk.dim(` \u2192 v${CLI_VERSION}`));
53
+ console.log();
54
+ const spinner = ora("Sjekker prosjekt...").start();
55
+ try {
56
+ const client = createApiClient(config);
57
+ spinner.text = "Henter skript og sider fra server...";
58
+ const [scripts, pages] = await Promise.all([
59
+ client.listScripts(config.siteId),
60
+ client.listPages(config.siteId)
61
+ ]);
62
+ spinner.succeed(`Fant ${scripts.length} skript og ${pages.length} sider`);
63
+ spinner.start("Oppdaterer dokumentasjon...");
64
+ const result = updateClaudeMd(
65
+ projectRoot,
66
+ config.siteName,
67
+ config.siteSlug,
68
+ scriptsToDocsFormat(scripts),
69
+ pagesToInfoFormat(pages)
70
+ );
71
+ if (result.kodeMd.created) {
72
+ spinner.succeed("Opprettet .cure-kode/KODE.md");
73
+ } else {
74
+ spinner.succeed("Oppdatert .cure-kode/KODE.md");
75
+ }
76
+ if (result.claudeMd.updated) {
77
+ console.log(chalk.dim(" La til Kode-referanse i CLAUDE.md"));
78
+ }
79
+ spinner.start("Oppdaterer MCP-servere...");
80
+ const mcpConfigPath = join(projectRoot, ".mcp.json");
81
+ let mcpConfig = {};
82
+ if (existsSync(mcpConfigPath)) {
83
+ try {
84
+ mcpConfig = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
85
+ } catch {
86
+ }
87
+ }
88
+ mcpConfig.mcpServers = mcpConfig.mcpServers || {};
89
+ mcpConfig.mcpServers["cure-kode"] = {
90
+ type: "stdio",
91
+ command: "npx",
92
+ args: ["-y", "@curenorway/kode-mcp@^1.5.0"]
93
+ };
94
+ const existingWebflow = mcpConfig.mcpServers["webflow"];
95
+ if (existingWebflow && existingWebflow.env) {
96
+ mcpConfig.mcpServers["webflow"] = {
97
+ type: "stdio",
98
+ command: "npx",
99
+ args: ["-y", "webflow-mcp-server@^0.6.0"],
100
+ env: existingWebflow.env
101
+ };
102
+ }
103
+ mcpConfig.mcpServers["playwright"] = {
104
+ type: "stdio",
105
+ command: "npx",
106
+ args: ["-y", "@playwright/mcp@^0.0.21"]
107
+ };
108
+ writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
109
+ spinner.succeed("MCP-servere oppdatert");
110
+ spinner.start("Oppdaterer context.md...");
111
+ const contextPath = join(projectRoot, ".cure-kode", "context.md");
112
+ if (existsSync(contextPath)) {
113
+ const existingContext = readFileSync(contextPath, "utf-8");
114
+ const scriptsSection = scripts.length > 0 ? `## Scripts
115
+
116
+ ${scripts.map((s) => {
117
+ const ext = s.type === "javascript" ? "js" : "css";
118
+ const scope = s.scope === "global" ? "[G]" : "[P]";
119
+ const auto = s.auto_load ? "\u26A1" : "\u25CB";
120
+ return `- \`${s.slug}.${ext}\` ${scope} ${auto}`;
121
+ }).join("\n")}
122
+
123
+ [G]=Global [P]=Page-specific \u26A1=Auto-load \u25CB=Manual
124
+ ` : "## Scripts\n\nNo scripts yet. Create one with `kode push script-name.js`\n";
125
+ const scriptsPattern = /## Scripts[\s\S]*?(?=\n## |\n---\n|$)/;
126
+ let updatedContext;
127
+ if (scriptsPattern.test(existingContext)) {
128
+ updatedContext = existingContext.replace(scriptsPattern, scriptsSection);
129
+ } else {
130
+ updatedContext = existingContext + "\n" + scriptsSection;
131
+ }
132
+ writeFileSync(contextPath, updatedContext);
133
+ }
134
+ spinner.succeed("context.md oppdatert");
135
+ console.log();
136
+ console.log(chalk.green(" \u2705 Oppgradering fullf\xF8rt!"));
137
+ console.log();
138
+ console.log(chalk.dim(" Oppdatert:"));
139
+ console.log(chalk.dim(" \u2022 .cure-kode/KODE.md - Kode-dokumentasjon"));
140
+ console.log(chalk.dim(" \u2022 .mcp.json - MCP-servere til nyeste versjoner"));
141
+ console.log(chalk.dim(" \u2022 context.md - Dynamisk AI-kontekst"));
142
+ console.log();
143
+ console.log(chalk.dim(" Neste steg:"));
144
+ console.log(chalk.cyan(" kode pull") + chalk.dim(" - Synkroniser lokale skriptfiler"));
145
+ console.log();
146
+ } catch (error) {
147
+ spinner.fail("Oppgradering feilet");
148
+ console.log();
149
+ console.error(chalk.red(" Feil:"), error);
150
+ console.log();
151
+ }
152
+ }
153
+
154
+ // src/commands/rollback.ts
155
+ import chalk2 from "chalk";
156
+ import ora2 from "ora";
28
157
  async function rollbackCommand(environment = "staging") {
29
158
  const projectRoot = findProjectRoot();
30
159
  if (!projectRoot) {
31
- console.log(chalk.red("\u274C Not in a Cure Kode project."));
32
- console.log(chalk.dim(' Run "kode init" first.'));
160
+ console.log(chalk2.red("\u274C Not in a Cure Kode project."));
161
+ console.log(chalk2.dim(' Run "kode init" first.'));
33
162
  return;
34
163
  }
35
164
  const config = getProjectConfig(projectRoot);
36
165
  if (!config) {
37
- console.log(chalk.red("\u274C Could not read project configuration."));
166
+ console.log(chalk2.red("\u274C Could not read project configuration."));
38
167
  return;
39
168
  }
40
169
  if (!["staging", "production"].includes(environment)) {
41
- console.log(chalk.red(`\u274C Invalid environment: ${environment}`));
42
- console.log(chalk.dim(' Use "staging" or "production"'));
170
+ console.log(chalk2.red(`\u274C Invalid environment: ${environment}`));
171
+ console.log(chalk2.dim(' Use "staging" or "production"'));
43
172
  return;
44
173
  }
45
174
  const client = createApiClient(config);
46
- const spinner = ora(`Ruller tilbake ${environment}...`).start();
175
+ const spinner = ora2(`Ruller tilbake ${environment}...`).start();
47
176
  try {
48
177
  if (environment === "production") {
49
178
  const status = await client.getDeploymentStatus(config.siteId);
50
179
  if (!status.productionEnabled) {
51
180
  spinner.fail("Produksjon er ikke aktivert");
52
181
  console.log();
53
- console.log(chalk.yellow("\u26A0\uFE0F Produksjon er deaktivert for dette prosjektet."));
54
- console.log(chalk.dim(" Kan ikke rulle tilbake n\xE5r produksjon er deaktivert."));
182
+ console.log(chalk2.yellow("\u26A0\uFE0F Produksjon er deaktivert for dette prosjektet."));
183
+ console.log(chalk2.dim(" Kan ikke rulle tilbake n\xE5r produksjon er deaktivert."));
55
184
  return;
56
185
  }
57
186
  }
58
187
  const result = await client.rollback(config.siteId, environment);
59
188
  spinner.succeed(`Tilbakerulling fullf\xF8rt (${result.duration_ms}ms)`);
60
189
  console.log();
61
- console.log(chalk.dim("Tilbakerulling detaljer:"));
62
- console.log(chalk.dim(` Fra: ${result.rolledBackFrom.version}`));
63
- console.log(chalk.dim(` Til: ${result.rolledBackTo.version}`));
190
+ console.log(chalk2.dim("Tilbakerulling detaljer:"));
191
+ console.log(chalk2.dim(` Fra: ${result.rolledBackFrom.version}`));
192
+ console.log(chalk2.dim(` Til: ${result.rolledBackTo.version}`));
64
193
  console.log();
65
- console.log(chalk.bold("CDN URL:"));
66
- console.log(chalk.cyan(` ${result.cdn_url}`));
194
+ console.log(chalk2.bold("CDN URL:"));
195
+ console.log(chalk2.cyan(` ${result.cdn_url}`));
67
196
  console.log();
68
- console.log(chalk.green(`\u2705 ${environment.charAt(0).toUpperCase() + environment.slice(1)} er n\xE5 tilbake til forrige versjon!`));
197
+ console.log(chalk2.green(`\u2705 ${environment.charAt(0).toUpperCase() + environment.slice(1)} er n\xE5 tilbake til forrige versjon!`));
69
198
  } catch (error) {
70
199
  spinner.fail("Tilbakerulling feilet");
71
200
  if (error.statusCode === 404) {
72
201
  console.log();
73
- console.log(chalk.yellow("\u26A0\uFE0F Ingen tidligere versjon \xE5 rulle tilbake til."));
74
- console.log(chalk.dim(" Det m\xE5 v\xE6re minst 2 deployments for \xE5 kunne rulle tilbake."));
202
+ console.log(chalk2.yellow("\u26A0\uFE0F Ingen tidligere versjon \xE5 rulle tilbake til."));
203
+ console.log(chalk2.dim(" Det m\xE5 v\xE6re minst 2 deployments for \xE5 kunne rulle tilbake."));
75
204
  } else if (error.statusCode === 409) {
76
205
  console.log();
77
- console.log(chalk.yellow("\u26A0\uFE0F En annen deployment kj\xF8rer."));
78
- console.log(chalk.dim(" Vent til den er ferdig og pr\xF8v igjen."));
206
+ console.log(chalk2.yellow("\u26A0\uFE0F En annen deployment kj\xF8rer."));
207
+ console.log(chalk2.dim(" Vent til den er ferdig og pr\xF8v igjen."));
79
208
  } else {
80
- console.error(chalk.red("\nError:"), error.message || error);
209
+ console.error(chalk2.red("\nError:"), error.message || error);
81
210
  }
82
211
  }
83
212
  }
84
213
 
85
214
  // src/commands/pages.ts
86
- import chalk2 from "chalk";
87
- async function pagesCommand(options) {
215
+ import chalk3 from "chalk";
216
+ async function pagesListCommand(options) {
217
+ const projectRoot = findProjectRoot();
218
+ if (!projectRoot) {
219
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
220
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
221
+ return;
222
+ }
223
+ const config = getProjectConfig(projectRoot);
224
+ if (!config) {
225
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
226
+ return;
227
+ }
228
+ try {
229
+ const api = createApiClient(config);
230
+ const pages = await api.listPages(config.siteId);
231
+ if (options.json) {
232
+ console.log(JSON.stringify(pages, null, 2));
233
+ return;
234
+ }
235
+ if (pages.length === 0) {
236
+ console.log(chalk3.yellow("Ingen sider funnet."));
237
+ console.log(chalk3.dim('Bruk "kode pages create <name>" for \xE5 opprette en side.'));
238
+ return;
239
+ }
240
+ console.log(chalk3.bold(`CDN-sider (${pages.length})`));
241
+ console.log();
242
+ const rootPages = pages.filter((p) => !p.parent_id);
243
+ const childPages = pages.filter((p) => p.parent_id);
244
+ const printPage = (page, indent = 0) => {
245
+ const prefix = " ".repeat(indent);
246
+ const patterns = page.url_patterns.join(", ");
247
+ const patternType = chalk3.dim(`[${page.pattern_type}]`);
248
+ console.log(`${prefix}${chalk3.bold(page.name)} ${chalk3.dim(`(${page.slug})`)}`);
249
+ console.log(`${prefix} ${patterns} ${patternType}`);
250
+ const children = childPages.filter((c) => c.parent_id === page.id);
251
+ for (const child of children) {
252
+ printPage(child, indent + 1);
253
+ }
254
+ };
255
+ for (const page of rootPages) {
256
+ printPage(page);
257
+ console.log();
258
+ }
259
+ console.log(chalk3.dim('Bruk "kode pages create <name>" for \xE5 legge til sider'));
260
+ console.log(chalk3.dim('Bruk "kode pages assign <page> <script>" for \xE5 koble skript'));
261
+ } catch (error) {
262
+ const apiError = error;
263
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
264
+ }
265
+ }
266
+ async function pagesCreateCommand(name, options) {
88
267
  const projectRoot = findProjectRoot();
89
268
  if (!projectRoot) {
90
- console.log(chalk2.red("Error: Not in a Cure Kode project."));
91
- console.log(chalk2.dim('Run "kode init" first.'));
269
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
270
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
92
271
  return;
93
272
  }
94
273
  const config = getProjectConfig(projectRoot);
95
274
  if (!config) {
96
- console.log(chalk2.red("Error: Invalid project configuration."));
275
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
276
+ return;
277
+ }
278
+ let patterns = options.patterns;
279
+ if (!patterns || patterns.length === 0) {
280
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
281
+ patterns = [`/${slug}`, `/${slug}/*`];
282
+ console.log(chalk3.dim(`Bruker standard URL-m\xF8nstre: ${patterns.join(", ")}`));
283
+ }
284
+ const patternType = options.patternType || "prefix";
285
+ const priority = options.priority ? parseInt(options.priority, 10) : 0;
286
+ try {
287
+ const api = createApiClient(config);
288
+ let parentId;
289
+ if (options.parent) {
290
+ const pages = await api.listPages(config.siteId);
291
+ const parentPage = pages.find(
292
+ (p) => p.slug === options.parent || p.name === options.parent
293
+ );
294
+ if (!parentPage) {
295
+ console.log(chalk3.red(`Feil: Finner ikke foreldreside "${options.parent}"`));
296
+ return;
297
+ }
298
+ parentId = parentPage.id;
299
+ }
300
+ const page = await api.createPage(config.siteId, {
301
+ name,
302
+ urlPatterns: patterns,
303
+ patternType,
304
+ priority,
305
+ parentId
306
+ });
307
+ console.log(chalk3.green(`\u2713 Side opprettet: ${page.name} (${page.slug})`));
308
+ console.log(chalk3.dim(` URL-m\xF8nstre: ${page.url_patterns.join(", ")}`));
309
+ console.log(chalk3.dim(` M\xF8nstertype: ${page.pattern_type}`));
310
+ if (parentId) {
311
+ console.log(chalk3.dim(` Forelder: ${options.parent}`));
312
+ }
313
+ } catch (error) {
314
+ const apiError = error;
315
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
316
+ }
317
+ }
318
+ async function pagesDeleteCommand(pageSlug, options) {
319
+ const projectRoot = findProjectRoot();
320
+ if (!projectRoot) {
321
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
322
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
323
+ return;
324
+ }
325
+ const config = getProjectConfig(projectRoot);
326
+ if (!config) {
327
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
328
+ return;
329
+ }
330
+ try {
331
+ const api = createApiClient(config);
332
+ const pages = await api.listPages(config.siteId);
333
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug);
334
+ if (!page) {
335
+ console.log(chalk3.red(`Feil: Finner ikke side "${pageSlug}"`));
336
+ return;
337
+ }
338
+ if (!options.force) {
339
+ console.log(chalk3.yellow(`Vil du slette siden "${page.name}"? (--force for \xE5 bekrefte)`));
340
+ return;
341
+ }
342
+ await api.deletePage(page.id);
343
+ console.log(chalk3.green(`\u2713 Side slettet: ${page.name}`));
344
+ } catch (error) {
345
+ const apiError = error;
346
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
347
+ }
348
+ }
349
+ async function pagesUpdateCommand(pageSlug, options) {
350
+ const projectRoot = findProjectRoot();
351
+ if (!projectRoot) {
352
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
353
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
354
+ return;
355
+ }
356
+ const config = getProjectConfig(projectRoot);
357
+ if (!config) {
358
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
359
+ return;
360
+ }
361
+ try {
362
+ const api = createApiClient(config);
363
+ const pages = await api.listPages(config.siteId);
364
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug);
365
+ if (!page) {
366
+ console.log(chalk3.red(`Feil: Finner ikke side "${pageSlug}"`));
367
+ return;
368
+ }
369
+ const updates = {};
370
+ if (options.name) updates.name = options.name;
371
+ if (options.patterns && options.patterns.length > 0) updates.urlPatterns = options.patterns;
372
+ if (options.patternType)
373
+ updates.patternType = options.patternType;
374
+ if (options.priority) updates.priority = parseInt(options.priority, 10);
375
+ if (options.removeParent) {
376
+ updates.parentId = null;
377
+ } else if (options.parent) {
378
+ const parentPage = pages.find(
379
+ (p) => p.slug === options.parent || p.name === options.parent
380
+ );
381
+ if (!parentPage) {
382
+ console.log(chalk3.red(`Feil: Finner ikke foreldreside "${options.parent}"`));
383
+ return;
384
+ }
385
+ updates.parentId = parentPage.id;
386
+ }
387
+ if (Object.keys(updates).length === 0) {
388
+ console.log(chalk3.yellow("Ingen endringer angitt."));
389
+ return;
390
+ }
391
+ const updatedPage = await api.updatePage(page.id, updates);
392
+ console.log(chalk3.green(`\u2713 Side oppdatert: ${updatedPage.name}`));
393
+ } catch (error) {
394
+ const apiError = error;
395
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
396
+ }
397
+ }
398
+ async function pagesAssignCommand(pageSlug, scriptSlug, options) {
399
+ const projectRoot = findProjectRoot();
400
+ if (!projectRoot) {
401
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
402
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
403
+ return;
404
+ }
405
+ const config = getProjectConfig(projectRoot);
406
+ if (!config) {
407
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
408
+ return;
409
+ }
410
+ try {
411
+ const api = createApiClient(config);
412
+ const pages = await api.listPages(config.siteId);
413
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug);
414
+ if (!page) {
415
+ console.log(chalk3.red(`Feil: Finner ikke side "${pageSlug}"`));
416
+ return;
417
+ }
418
+ const scripts = await api.listScripts(config.siteId);
419
+ const script = scripts.find((s) => s.slug === scriptSlug || s.name === scriptSlug);
420
+ if (!script) {
421
+ console.log(chalk3.red(`Feil: Finner ikke skript "${scriptSlug}"`));
422
+ return;
423
+ }
424
+ const loadOrder = options.loadOrder ? parseInt(options.loadOrder, 10) : void 0;
425
+ await api.assignScriptToPage(page.id, script.id, loadOrder);
426
+ console.log(chalk3.green(`\u2713 Skript "${script.name}" tilordnet til side "${page.name}"`));
427
+ } catch (error) {
428
+ const apiError = error;
429
+ if (apiError.statusCode === 409) {
430
+ console.log(chalk3.yellow("Skriptet er allerede tilordnet denne siden."));
431
+ } else {
432
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
433
+ }
434
+ }
435
+ }
436
+ async function pagesUnassignCommand(pageSlug, scriptSlug) {
437
+ const projectRoot = findProjectRoot();
438
+ if (!projectRoot) {
439
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
440
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
441
+ return;
442
+ }
443
+ const config = getProjectConfig(projectRoot);
444
+ if (!config) {
445
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
446
+ return;
447
+ }
448
+ try {
449
+ const api = createApiClient(config);
450
+ const pages = await api.listPages(config.siteId);
451
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug);
452
+ if (!page) {
453
+ console.log(chalk3.red(`Feil: Finner ikke side "${pageSlug}"`));
454
+ return;
455
+ }
456
+ const scripts = await api.listScripts(config.siteId);
457
+ const script = scripts.find((s) => s.slug === scriptSlug || s.name === scriptSlug);
458
+ if (!script) {
459
+ console.log(chalk3.red(`Feil: Finner ikke skript "${scriptSlug}"`));
460
+ return;
461
+ }
462
+ await api.removeScriptFromPage(page.id, script.id);
463
+ console.log(chalk3.green(`\u2713 Skript "${script.name}" fjernet fra side "${page.name}"`));
464
+ } catch (error) {
465
+ const apiError = error;
466
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
467
+ }
468
+ }
469
+ async function pagesScriptsCommand(pageSlug) {
470
+ const projectRoot = findProjectRoot();
471
+ if (!projectRoot) {
472
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
473
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
474
+ return;
475
+ }
476
+ const config = getProjectConfig(projectRoot);
477
+ if (!config) {
478
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
479
+ return;
480
+ }
481
+ try {
482
+ const api = createApiClient(config);
483
+ const pages = await api.listPages(config.siteId);
484
+ const page = pages.find((p) => p.slug === pageSlug || p.name === pageSlug);
485
+ if (!page) {
486
+ console.log(chalk3.red(`Feil: Finner ikke side "${pageSlug}"`));
487
+ return;
488
+ }
489
+ const assignments = await api.getPageScripts(page.id);
490
+ console.log(chalk3.bold(`Skript p\xE5 "${page.name}"`));
491
+ console.log();
492
+ if (assignments.length === 0) {
493
+ console.log(chalk3.yellow("Ingen skript tilordnet denne siden."));
494
+ console.log(chalk3.dim('Bruk "kode pages assign <page> <script>" for \xE5 tilordne skript.'));
495
+ return;
496
+ }
497
+ for (const assignment of assignments) {
498
+ const script = assignment.script;
499
+ const type = script.type === "javascript" ? chalk3.yellow("JS") : chalk3.blue("CSS");
500
+ const loadOrder = assignment.load_order_override !== null ? chalk3.dim(`[ordre: ${assignment.load_order_override}]`) : "";
501
+ const enabled = assignment.is_enabled ? "" : chalk3.red(" (deaktivert)");
502
+ console.log(` ${type} ${chalk3.bold(script.name)} ${loadOrder}${enabled}`);
503
+ }
504
+ } catch (error) {
505
+ const apiError = error;
506
+ console.log(chalk3.red(`Feil: ${apiError.message}`));
507
+ }
508
+ }
509
+ async function pagesContextCommand(options) {
510
+ const projectRoot = findProjectRoot();
511
+ if (!projectRoot) {
512
+ console.log(chalk3.red("Feil: Ikke i et Cure Kode-prosjekt."));
513
+ console.log(chalk3.dim('Kj\xF8r "kode init" f\xF8rst.'));
514
+ return;
515
+ }
516
+ const config = getProjectConfig(projectRoot);
517
+ if (!config) {
518
+ console.log(chalk3.red("Feil: Ugyldig prosjektkonfigurasjon."));
97
519
  return;
98
520
  }
99
521
  if (options.delete && options.page) {
100
522
  const deleted = deletePageContext(projectRoot, options.page);
101
523
  if (deleted) {
102
- console.log(chalk2.green(`Deleted: ${options.page}`));
524
+ console.log(chalk3.green(`Slettet: ${options.page}`));
103
525
  } else {
104
- console.log(chalk2.red(`Not found: ${options.page}`));
526
+ console.log(chalk3.red(`Ikke funnet: ${options.page}`));
105
527
  }
106
528
  return;
107
529
  }
108
530
  if (options.page) {
109
531
  const context = readPageContext(projectRoot, options.page);
110
532
  if (!context) {
111
- console.log(chalk2.red(`Page not found: ${options.page}`));
112
- console.log(chalk2.dim('Use "kode pages" to list cached pages'));
533
+ console.log(chalk3.red(`Side ikke funnet: ${options.page}`));
534
+ console.log(chalk3.dim('Bruk "kode pages context" for \xE5 liste bufrede sider'));
113
535
  return;
114
536
  }
115
537
  if (options.json) {
@@ -121,113 +543,113 @@ async function pagesCommand(options) {
121
543
  }
122
544
  const pages = listCachedPages(projectRoot);
123
545
  if (pages.length === 0) {
124
- console.log(chalk2.yellow("No cached pages."));
125
- console.log(chalk2.dim('Use "kode html <url> --save" to cache page structures.'));
546
+ console.log(chalk3.yellow("Ingen bufrede sidekontekster."));
547
+ console.log(chalk3.dim('Bruk "kode html <url> --save" for \xE5 lagre sidestrukturer.'));
126
548
  return;
127
549
  }
128
550
  if (options.json) {
129
551
  console.log(JSON.stringify(pages, null, 2));
130
552
  return;
131
553
  }
132
- console.log(chalk2.bold(`Cached Pages (${pages.length})`));
554
+ console.log(chalk3.bold(`Bufrede sidekontekster (${pages.length})`));
133
555
  console.log();
134
556
  for (const page of pages) {
135
557
  const path = new URL(page.url).pathname;
136
- const date = new Date(page.extractedAt).toLocaleDateString();
558
+ const date = new Date(page.extractedAt).toLocaleDateString("nb-NO");
137
559
  const badges = [];
138
- if (page.sectionCount > 0) badges.push(`${page.sectionCount} sections`);
139
- if (page.cmsCollectionCount > 0) badges.push(chalk2.cyan(`${page.cmsCollectionCount} CMS`));
140
- console.log(` ${chalk2.bold(path)} ${chalk2.dim(`[${page.slug}]`)}`);
560
+ if (page.sectionCount > 0) badges.push(`${page.sectionCount} seksjoner`);
561
+ if (page.cmsCollectionCount > 0) badges.push(chalk3.cyan(`${page.cmsCollectionCount} CMS`));
562
+ console.log(` ${chalk3.bold(path)} ${chalk3.dim(`[${page.slug}]`)}`);
141
563
  if (page.title) {
142
- console.log(chalk2.dim(` "${page.title}"`));
564
+ console.log(chalk3.dim(` "${page.title}"`));
143
565
  }
144
- console.log(chalk2.dim(` ${badges.join(", ")} \u2022 ${date}`));
566
+ console.log(chalk3.dim(` ${badges.join(", ")} \u2022 ${date}`));
145
567
  console.log();
146
568
  }
147
- console.log(chalk2.dim(`Use "kode pages <slug>" to see details`));
148
- console.log(chalk2.dim(`Use "kode html <url> --save --force" to refresh`));
569
+ console.log(chalk3.dim('Bruk "kode pages context <slug>" for \xE5 se detaljer'));
570
+ console.log(chalk3.dim('Bruk "kode html <url> --save --force" for \xE5 oppdatere'));
149
571
  }
150
572
  function printPageDetails(context) {
151
- console.log(chalk2.bold(context.title || context.url));
152
- console.log(chalk2.dim(context.url));
153
- console.log(chalk2.dim(`Extracted: ${context.extractedAt}`));
573
+ console.log(chalk3.bold(context.title || context.url));
574
+ console.log(chalk3.dim(context.url));
575
+ console.log(chalk3.dim(`Hentet: ${context.extractedAt}`));
154
576
  console.log();
155
577
  if (context.sections.length > 0) {
156
- console.log(chalk2.bold("Sections"));
578
+ console.log(chalk3.bold("Seksjoner"));
157
579
  for (const section of context.sections) {
158
580
  const name = section.heading || section.id || section.className?.split(" ")[0] || "section";
159
581
  const badges = [];
160
- if (section.hasCms) badges.push(chalk2.cyan("CMS"));
161
- if (section.hasForm) badges.push(chalk2.yellow("Form"));
582
+ if (section.hasCms) badges.push(chalk3.cyan("CMS"));
583
+ if (section.hasForm) badges.push(chalk3.yellow("Skjema"));
162
584
  const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
163
585
  console.log(` \u2022 ${name}${badgeStr}`);
164
586
  if (section.textSample) {
165
- console.log(chalk2.dim(` "${section.textSample.slice(0, 80)}..."`));
587
+ console.log(chalk3.dim(` "${section.textSample.slice(0, 80)}..."`));
166
588
  }
167
589
  }
168
590
  console.log();
169
591
  }
170
592
  if (context.headings.length > 0) {
171
- console.log(chalk2.bold("Headings"));
593
+ console.log(chalk3.bold("Overskrifter"));
172
594
  for (const h of context.headings.slice(0, 10)) {
173
595
  const level = "H" + h.level;
174
- console.log(` ${chalk2.dim(level)} ${h.text}`);
596
+ console.log(` ${chalk3.dim(level)} ${h.text}`);
175
597
  }
176
598
  if (context.headings.length > 10) {
177
- console.log(chalk2.dim(` ... and ${context.headings.length - 10} more`));
599
+ console.log(chalk3.dim(` ... og ${context.headings.length - 10} flere`));
178
600
  }
179
601
  console.log();
180
602
  }
181
603
  if (context.ctas.length > 0) {
182
- console.log(chalk2.bold("CTAs"));
604
+ console.log(chalk3.bold("CTAs"));
183
605
  for (const cta of context.ctas) {
184
- const href = cta.href ? chalk2.dim(` \u2192 ${cta.href}`) : "";
606
+ const href = cta.href ? chalk3.dim(` \u2192 ${cta.href}`) : "";
185
607
  console.log(` \u2022 "${cta.text}"${href}`);
186
- console.log(chalk2.dim(` in ${cta.location}`));
608
+ console.log(chalk3.dim(` i ${cta.location}`));
187
609
  }
188
610
  console.log();
189
611
  }
190
612
  if (context.forms.length > 0) {
191
- console.log(chalk2.bold("Forms"));
613
+ console.log(chalk3.bold("Skjemaer"));
192
614
  for (const form of context.forms) {
193
- console.log(` ${chalk2.bold(form.name || "form")}`);
615
+ console.log(` ${chalk3.bold(form.name || "skjema")}`);
194
616
  for (const field of form.fields) {
195
- const required = field.required ? chalk2.red("*") : "";
617
+ const required = field.required ? chalk3.red("*") : "";
196
618
  const label = field.label || field.type;
197
- console.log(` \u2022 ${label}${required} ${chalk2.dim(`(${field.type})`)}`);
619
+ console.log(` \u2022 ${label}${required} ${chalk3.dim(`(${field.type})`)}`);
198
620
  }
199
621
  if (form.submitText) {
200
- console.log(` \u2192 Submit: "${form.submitText}"`);
622
+ console.log(` \u2192 Send: "${form.submitText}"`);
201
623
  }
202
624
  }
203
625
  console.log();
204
626
  }
205
627
  if (context.cmsPatterns.length > 0) {
206
- console.log(chalk2.bold("CMS Collections"));
628
+ console.log(chalk3.bold("CMS-samlinger"));
207
629
  for (const cms of context.cmsPatterns) {
208
- console.log(` ${chalk2.bold(cms.containerClass)}: ${chalk2.cyan(`${cms.itemCount} items`)}`);
630
+ console.log(` ${chalk3.bold(cms.containerClass)}: ${chalk3.cyan(`${cms.itemCount} elementer`)}`);
209
631
  if (cms.templateFields.length > 0) {
210
- console.log(chalk2.dim(` Template fields: ${cms.templateFields.join(", ")}`));
632
+ console.log(chalk3.dim(` Malfelt: ${cms.templateFields.join(", ")}`));
211
633
  }
212
634
  }
213
635
  console.log();
214
636
  }
215
637
  if (context.navigation.length > 0) {
216
- console.log(chalk2.bold("Navigation"));
638
+ console.log(chalk3.bold("Navigasjon"));
217
639
  for (const nav of context.navigation) {
218
- console.log(` ${chalk2.bold(nav.type)}:`);
640
+ console.log(` ${chalk3.bold(nav.type)}:`);
219
641
  for (const item of nav.items.slice(0, 8)) {
220
- const href = item.href ? chalk2.dim(` \u2192 ${item.href}`) : "";
642
+ const href = item.href ? chalk3.dim(` \u2192 ${item.href}`) : "";
221
643
  console.log(` \u2022 ${item.text}${href}`);
222
644
  }
223
645
  if (nav.items.length > 8) {
224
- console.log(chalk2.dim(` ... and ${nav.items.length - 8} more`));
646
+ console.log(chalk3.dim(` ... og ${nav.items.length - 8} flere`));
225
647
  }
226
648
  }
227
649
  console.log();
228
650
  }
229
651
  if (context.notes && context.notes.length > 0) {
230
- console.log(chalk2.bold("Notes"));
652
+ console.log(chalk3.bold("Notater"));
231
653
  for (const note of context.notes) {
232
654
  console.log(` \u2022 ${note}`);
233
655
  }
@@ -236,40 +658,40 @@ function printPageDetails(context) {
236
658
  }
237
659
 
238
660
  // src/commands/set.ts
239
- import chalk3 from "chalk";
240
- import ora2 from "ora";
661
+ import chalk4 from "chalk";
662
+ import ora3 from "ora";
241
663
  async function setCommand(script, options) {
242
664
  const projectRoot = findProjectRoot();
243
665
  if (!projectRoot) {
244
- console.log(chalk3.red("\u274C Not in a Cure Kode project."));
245
- console.log(chalk3.dim(' Run "kode init" first.'));
666
+ console.log(chalk4.red("\u274C Not in a Cure Kode project."));
667
+ console.log(chalk4.dim(' Run "kode init" first.'));
246
668
  return;
247
669
  }
248
670
  const config = getProjectConfig(projectRoot);
249
671
  if (!config) {
250
- console.log(chalk3.red("\u274C Could not read project configuration."));
672
+ console.log(chalk4.red("\u274C Could not read project configuration."));
251
673
  return;
252
674
  }
253
675
  if (!options.scope && options.autoLoad === void 0) {
254
- console.log(chalk3.yellow("\u26A0\uFE0F No changes specified."));
255
- console.log(chalk3.dim(" Use --scope or --auto-load/--no-auto-load"));
676
+ console.log(chalk4.yellow("\u26A0\uFE0F No changes specified."));
677
+ console.log(chalk4.dim(" Use --scope or --auto-load/--no-auto-load"));
256
678
  console.log();
257
- console.log(chalk3.dim("Examples:"));
258
- console.log(chalk3.dim(" kode set my-script --scope page-specific"));
259
- console.log(chalk3.dim(" kode set my-script --scope global --auto-load"));
260
- console.log(chalk3.dim(" kode set my-script --no-auto-load"));
679
+ console.log(chalk4.dim("Examples:"));
680
+ console.log(chalk4.dim(" kode set my-script --scope page-specific"));
681
+ console.log(chalk4.dim(" kode set my-script --scope global --auto-load"));
682
+ console.log(chalk4.dim(" kode set my-script --no-auto-load"));
261
683
  return;
262
684
  }
263
- const spinner = ora2(`Updating ${script}...`).start();
685
+ const spinner = ora3(`Updating ${script}...`).start();
264
686
  try {
265
687
  const client = createApiClient(config);
266
688
  const scripts = await client.listScripts(config.siteId);
267
689
  const targetScript = scripts.find((s) => s.slug === script || s.name === script);
268
690
  if (!targetScript) {
269
691
  spinner.fail(`Script "${script}" not found`);
270
- console.log(chalk3.dim("\nAvailable scripts:"));
692
+ console.log(chalk4.dim("\nAvailable scripts:"));
271
693
  scripts.forEach((s) => {
272
- console.log(chalk3.dim(` - ${s.slug}`));
694
+ console.log(chalk4.dim(` - ${s.slug}`));
273
695
  });
274
696
  return;
275
697
  }
@@ -289,80 +711,80 @@ async function setCommand(script, options) {
289
711
  }
290
712
  updates.changeSummary = `Updated settings: ${changes.join(", ")}`;
291
713
  const updated = await client.updateScript(targetScript.id, updates);
292
- spinner.succeed(chalk3.green(`Updated ${script}`));
714
+ spinner.succeed(chalk4.green(`Updated ${script}`));
293
715
  console.log();
294
716
  for (const change of changes) {
295
- console.log(chalk3.dim(` ${change}`));
717
+ console.log(chalk4.dim(` ${change}`));
296
718
  }
297
719
  console.log();
298
720
  if (options.scope === "page-specific") {
299
- console.log(chalk3.yellow("\u26A0\uFE0F Page-specific scripts need page assignments to load."));
300
- console.log(chalk3.dim(" Use app.cure.no \u2192 Kode to assign pages, or use MCP:"));
301
- console.log(chalk3.dim(" kode_assign_script_to_page(scriptSlug, pageSlug)"));
721
+ console.log(chalk4.yellow("\u26A0\uFE0F Page-specific scripts need page assignments to load."));
722
+ console.log(chalk4.dim(" Use app.cure.no \u2192 Kode to assign pages, or use MCP:"));
723
+ console.log(chalk4.dim(" kode_assign_script_to_page(scriptSlug, pageSlug)"));
302
724
  console.log();
303
725
  }
304
- console.log(chalk3.dim('Run "kode deploy" to make changes live.'));
726
+ console.log(chalk4.dim('Run "kode deploy" to make changes live.'));
305
727
  } catch (error) {
306
728
  spinner.fail("Failed to update script");
307
- console.error(chalk3.red("\nError:"), error);
729
+ console.error(chalk4.red("\nError:"), error);
308
730
  }
309
731
  }
310
732
 
311
733
  // src/commands/production.ts
312
- import chalk4 from "chalk";
313
- import ora3 from "ora";
734
+ import chalk5 from "chalk";
735
+ import ora4 from "ora";
314
736
  async function productionCommand(action, options) {
315
737
  if (!["enable", "disable", "status"].includes(action)) {
316
- console.log(chalk4.red("\u274C Invalid action. Use: enable, disable, or status"));
317
- console.log(chalk4.dim(" kode production enable [--domain <domain>]"));
318
- console.log(chalk4.dim(" kode production disable"));
319
- console.log(chalk4.dim(" kode production status"));
738
+ console.log(chalk5.red("\u274C Invalid action. Use: enable, disable, or status"));
739
+ console.log(chalk5.dim(" kode production enable [--domain <domain>]"));
740
+ console.log(chalk5.dim(" kode production disable"));
741
+ console.log(chalk5.dim(" kode production status"));
320
742
  return;
321
743
  }
322
744
  const projectRoot = findProjectRoot();
323
745
  if (!projectRoot) {
324
- console.log(chalk4.red("\u274C Not in a Cure Kode project."));
325
- console.log(chalk4.dim(' Run "kode init" first.'));
746
+ console.log(chalk5.red("\u274C Not in a Cure Kode project."));
747
+ console.log(chalk5.dim(' Run "kode init" first.'));
326
748
  return;
327
749
  }
328
750
  const config = getProjectConfig(projectRoot);
329
751
  if (!config) {
330
- console.log(chalk4.red("\u274C Could not read project configuration."));
752
+ console.log(chalk5.red("\u274C Could not read project configuration."));
331
753
  return;
332
754
  }
333
755
  const client = createApiClient(config);
334
756
  if (action === "status") {
335
- const spinner2 = ora3("Fetching production status...").start();
757
+ const spinner2 = ora4("Fetching production status...").start();
336
758
  try {
337
759
  const status = await client.getDeploymentStatus(config.siteId);
338
760
  spinner2.stop();
339
761
  console.log();
340
- console.log(chalk4.bold("Production Status"));
762
+ console.log(chalk5.bold("Production Status"));
341
763
  console.log();
342
764
  if (status.productionEnabled) {
343
- console.log(chalk4.green(" \u25CF Produksjon er aktivert"));
765
+ console.log(chalk5.green(" \u25CF Produksjon er aktivert"));
344
766
  if (status.productionDomain) {
345
- console.log(chalk4.dim(` Domain: ${status.productionDomain}`));
767
+ console.log(chalk5.dim(` Domain: ${status.productionDomain}`));
346
768
  }
347
769
  if (status.production.lastSuccessful) {
348
770
  console.log(
349
- chalk4.dim(
771
+ chalk5.dim(
350
772
  ` Siste deploy: v${status.production.lastSuccessful.version}`
351
773
  )
352
774
  );
353
775
  }
354
776
  } else {
355
- console.log(chalk4.gray(" \u25CB Produksjon er deaktivert"));
356
- console.log(chalk4.dim(" Kun staging er aktiv"));
777
+ console.log(chalk5.gray(" \u25CB Produksjon er deaktivert"));
778
+ console.log(chalk5.dim(" Kun staging er aktiv"));
357
779
  }
358
780
  console.log();
359
781
  } catch (error) {
360
782
  spinner2.fail("Failed to fetch status");
361
- console.error(chalk4.red("\nError:"), error.message || error);
783
+ console.error(chalk5.red("\nError:"), error.message || error);
362
784
  }
363
785
  return;
364
786
  }
365
- const spinner = ora3(
787
+ const spinner = ora4(
366
788
  action === "enable" ? "Aktiverer produksjon..." : "Deaktiverer produksjon..."
367
789
  ).start();
368
790
  try {
@@ -374,99 +796,830 @@ async function productionCommand(action, options) {
374
796
  spinner.stop();
375
797
  console.log();
376
798
  if (action === "enable") {
377
- console.log(chalk4.green("\u2713 Produksjon er n\xE5 aktivert"));
799
+ console.log(chalk5.green("\u2713 Produksjon er n\xE5 aktivert"));
378
800
  if (result.productionDomain) {
379
- console.log(chalk4.dim(` Domain: ${result.productionDomain}`));
801
+ console.log(chalk5.dim(` Domain: ${result.productionDomain}`));
380
802
  }
381
803
  console.log();
382
- console.log(chalk4.dim(" Neste steg:"));
383
- console.log(chalk4.dim(" 1. Deploy til staging: kode deploy"));
384
- console.log(chalk4.dim(" 2. Promoter til produksjon: kode deploy --promote"));
804
+ console.log(chalk5.dim(" Neste steg:"));
805
+ console.log(chalk5.dim(" 1. Deploy til staging: kode deploy"));
806
+ console.log(chalk5.dim(" 2. Promoter til produksjon: kode deploy --promote"));
385
807
  } else {
386
- console.log(chalk4.yellow("\u2713 Produksjon er n\xE5 deaktivert"));
387
- console.log(chalk4.dim(" Kun staging-milj\xF8et er aktivt."));
808
+ console.log(chalk5.yellow("\u2713 Produksjon er n\xE5 deaktivert"));
809
+ console.log(chalk5.dim(" Kun staging-milj\xF8et er aktivt."));
388
810
  console.log(
389
- chalk4.dim(" Produksjonsdomenet vil f\xE5 en tom script-respons.")
811
+ chalk5.dim(" Produksjonsdomenet vil f\xE5 en tom script-respons.")
390
812
  );
391
813
  }
392
814
  console.log();
393
815
  } catch (error) {
394
816
  spinner.fail(action === "enable" ? "Kunne ikke aktivere produksjon" : "Kunne ikke deaktivere produksjon");
395
- console.error(chalk4.red("\nError:"), error.message || error);
817
+ console.error(chalk5.red("\nError:"), error.message || error);
396
818
  }
397
819
  }
398
820
 
399
821
  // src/commands/update-claude-md.ts
400
- import chalk5 from "chalk";
401
- import { existsSync, readFileSync, writeFileSync } from "fs";
402
- import { join } from "path";
822
+ import chalk6 from "chalk";
823
+ import ora5 from "ora";
403
824
  async function updateClaudeMdCommand() {
404
825
  const projectRoot = findProjectRoot();
405
826
  if (!projectRoot) {
406
- console.log(chalk5.red("\u274C Not in a Cure Kode project."));
407
- console.log(chalk5.dim(' Run "kode init" first.'));
827
+ console.log(chalk6.red("\u274C Ikke i et Cure Kode-prosjekt."));
828
+ console.log(chalk6.dim(' Kj\xF8r "kode init" f\xF8rst.'));
408
829
  return;
409
830
  }
410
831
  const config = getProjectConfig(projectRoot);
411
832
  if (!config) {
412
- console.log(chalk5.red("\u274C Could not read project configuration."));
833
+ console.log(chalk6.red("\u274C Kunne ikke lese prosjektkonfigurasjon."));
834
+ return;
835
+ }
836
+ const spinner = ora5("Henter skript og sider...").start();
837
+ try {
838
+ const client = createApiClient(config);
839
+ const [scripts, pages] = await Promise.all([
840
+ client.listScripts(config.siteId),
841
+ client.listPages(config.siteId)
842
+ ]);
843
+ spinner.succeed(`Fant ${scripts.length} skript og ${pages.length} sider`);
844
+ const result = updateKodeDocs(
845
+ projectRoot,
846
+ config.siteName,
847
+ config.siteSlug,
848
+ scriptsToDocsFormat(scripts),
849
+ pagesToInfoFormat(pages)
850
+ );
851
+ if (result.kodeMd.created) {
852
+ console.log(chalk6.green("\u2705 Opprettet .cure-kode/KODE.md"));
853
+ } else {
854
+ console.log(chalk6.green("\u2705 Oppdatert .cure-kode/KODE.md"));
855
+ }
856
+ if (result.claudeMd.created) {
857
+ console.log(chalk6.green("\u2705 Opprettet CLAUDE.md med referanse"));
858
+ } else if (result.claudeMd.updated) {
859
+ console.log(chalk6.green("\u2705 La til Kode-referanse i CLAUDE.md"));
860
+ } else {
861
+ console.log(chalk6.dim(" CLAUDE.md har allerede referanse"));
862
+ }
863
+ console.log();
864
+ console.log(chalk6.dim("KODE.md inneholder n\xE5:"));
865
+ console.log(chalk6.dim(" \u2022 CDN URL og Webflow-oppsett"));
866
+ console.log(chalk6.dim(" \u2022 Liste over alle skript"));
867
+ if (pages.length > 0) {
868
+ console.log(chalk6.dim(" \u2022 Liste over sider og URL-m\xF8nstre"));
869
+ }
870
+ console.log(chalk6.dim(" \u2022 CLI-kommandoer og MCP-verkt\xF8y"));
871
+ } catch (error) {
872
+ spinner.fail("Kunne ikke oppdatere dokumentasjon");
873
+ console.error(chalk6.red("\nFeil:"), error.message || error);
874
+ }
875
+ }
876
+
877
+ // src/commands/sync.ts
878
+ import chalk7 from "chalk";
879
+ import ora6 from "ora";
880
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readdirSync, mkdirSync } from "fs";
881
+ import { join as join2, basename, extname } from "path";
882
+ import { createHash } from "crypto";
883
+ function hashContent(content) {
884
+ return createHash("sha256").update(content).digest("hex").substring(0, 16);
885
+ }
886
+ async function syncCommand(options) {
887
+ const projectRoot = findProjectRoot();
888
+ if (!projectRoot) {
889
+ console.log(chalk7.red("Feil: Ikke i et Cure Kode-prosjekt."));
890
+ console.log(chalk7.dim('Kj\xF8r "kode init" f\xF8rst.'));
891
+ return;
892
+ }
893
+ const config = getProjectConfig(projectRoot);
894
+ if (!config) {
895
+ console.log(chalk7.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
896
+ return;
897
+ }
898
+ const scriptsDir = getScriptsDir(projectRoot, config);
899
+ if (!existsSync2(scriptsDir)) {
900
+ mkdirSync(scriptsDir, { recursive: true });
901
+ }
902
+ const metadataPath = join2(projectRoot, ".cure-kode", "scripts.json");
903
+ let metadata = [];
904
+ if (existsSync2(metadataPath)) {
905
+ try {
906
+ metadata = JSON.parse(readFileSync2(metadataPath, "utf-8"));
907
+ } catch {
908
+ }
909
+ }
910
+ const spinner = ora6("Analyserer synkroniseringsstatus...").start();
911
+ try {
912
+ const client = createApiClient(config);
913
+ const remoteScripts = await client.listScripts(config.siteId);
914
+ const localFiles = existsSync2(scriptsDir) ? readdirSync(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
915
+ const syncStatuses = [];
916
+ for (const remote of remoteScripts) {
917
+ const ext = remote.type === "javascript" ? "js" : "css";
918
+ const fileName = `${remote.slug}.${ext}`;
919
+ const filePath = join2(scriptsDir, fileName);
920
+ const localMeta = metadata.find((m) => m.slug === remote.slug);
921
+ const lastPulledVersion = localMeta?.lastPulledVersion || 0;
922
+ if (!existsSync2(filePath)) {
923
+ syncStatuses.push({
924
+ slug: remote.slug,
925
+ fileName,
926
+ status: "remote-only",
927
+ remoteVersion: remote.current_version,
928
+ remoteContent: remote.content
929
+ });
930
+ } else {
931
+ const localContent = readFileSync2(filePath, "utf-8");
932
+ const localHash = hashContent(localContent);
933
+ const hasLocalChanges = localMeta && localHash !== localMeta.contentHash;
934
+ const hasRemoteChanges = remote.current_version > lastPulledVersion;
935
+ if (hasLocalChanges && hasRemoteChanges) {
936
+ syncStatuses.push({
937
+ slug: remote.slug,
938
+ fileName,
939
+ status: "conflict",
940
+ localVersion: lastPulledVersion,
941
+ remoteVersion: remote.current_version,
942
+ localContent,
943
+ remoteContent: remote.content
944
+ });
945
+ } else if (hasLocalChanges) {
946
+ syncStatuses.push({
947
+ slug: remote.slug,
948
+ fileName,
949
+ status: "local-changed",
950
+ localVersion: lastPulledVersion,
951
+ remoteVersion: remote.current_version,
952
+ localContent
953
+ });
954
+ } else if (hasRemoteChanges) {
955
+ syncStatuses.push({
956
+ slug: remote.slug,
957
+ fileName,
958
+ status: "remote-changed",
959
+ localVersion: lastPulledVersion,
960
+ remoteVersion: remote.current_version,
961
+ remoteContent: remote.content
962
+ });
963
+ } else if (localContent !== remote.content) {
964
+ syncStatuses.push({
965
+ slug: remote.slug,
966
+ fileName,
967
+ status: "remote-changed",
968
+ remoteVersion: remote.current_version,
969
+ remoteContent: remote.content
970
+ });
971
+ } else {
972
+ syncStatuses.push({
973
+ slug: remote.slug,
974
+ fileName,
975
+ status: "up-to-date",
976
+ localVersion: remote.current_version,
977
+ remoteVersion: remote.current_version
978
+ });
979
+ }
980
+ }
981
+ }
982
+ for (const file of localFiles) {
983
+ const slug = basename(file, extname(file));
984
+ if (!remoteScripts.find((r) => r.slug === slug)) {
985
+ syncStatuses.push({
986
+ slug,
987
+ fileName: file,
988
+ status: "local-only",
989
+ localContent: readFileSync2(join2(scriptsDir, file), "utf-8")
990
+ });
991
+ }
992
+ }
993
+ spinner.stop();
994
+ const upToDate = syncStatuses.filter((s) => s.status === "up-to-date");
995
+ const toPull = syncStatuses.filter((s) => s.status === "remote-only" || s.status === "remote-changed");
996
+ const toPush = syncStatuses.filter((s) => s.status === "local-only" || s.status === "local-changed");
997
+ const conflicts = syncStatuses.filter((s) => s.status === "conflict");
998
+ console.log();
999
+ console.log(chalk7.bold("Synkroniseringsstatus:"));
1000
+ console.log();
1001
+ if (upToDate.length > 0) {
1002
+ console.log(chalk7.green(` \u2713 ${upToDate.length} skript er oppdatert`));
1003
+ }
1004
+ if (toPull.length > 0) {
1005
+ console.log(chalk7.cyan(` \u2193 ${toPull.length} skript \xE5 hente fra server`));
1006
+ for (const s of toPull) {
1007
+ const versionInfo = s.status === "remote-changed" ? chalk7.dim(` (v${s.localVersion || "?"} \u2192 v${s.remoteVersion})`) : chalk7.dim(" (ny)");
1008
+ console.log(` ${s.fileName}${versionInfo}`);
1009
+ }
1010
+ }
1011
+ if (toPush.length > 0) {
1012
+ console.log(chalk7.yellow(` \u2191 ${toPush.length} skript \xE5 laste opp`));
1013
+ for (const s of toPush) {
1014
+ const versionInfo = s.status === "local-changed" ? chalk7.dim(` (lokale endringer)`) : chalk7.dim(" (ny)");
1015
+ console.log(` ${s.fileName}${versionInfo}`);
1016
+ }
1017
+ }
1018
+ if (conflicts.length > 0) {
1019
+ console.log(chalk7.red(` \u26A0 ${conflicts.length} konflikter`));
1020
+ for (const s of conflicts) {
1021
+ console.log(` ${s.fileName} ${chalk7.dim(`(lokal v${s.localVersion}, server v${s.remoteVersion})`)}`);
1022
+ }
1023
+ }
1024
+ if (options.dryRun) {
1025
+ console.log();
1026
+ console.log(chalk7.dim("T\xF8rrkj\xF8ring - ingen endringer gjort"));
1027
+ return;
1028
+ }
1029
+ if (toPull.length === 0 && toPush.length === 0 && conflicts.length === 0) {
1030
+ console.log();
1031
+ console.log(chalk7.green("Alt er synkronisert!"));
1032
+ return;
1033
+ }
1034
+ console.log();
1035
+ let pulled = 0;
1036
+ for (const s of toPull) {
1037
+ const filePath = join2(scriptsDir, s.fileName);
1038
+ writeFileSync2(filePath, s.remoteContent);
1039
+ console.log(chalk7.cyan(` \u2193 ${s.fileName}`));
1040
+ pulled++;
1041
+ }
1042
+ let pushed = 0;
1043
+ for (const s of toPush) {
1044
+ const filePath = join2(scriptsDir, s.fileName);
1045
+ const content = s.localContent;
1046
+ const type = extname(s.fileName) === ".js" ? "javascript" : "css";
1047
+ const remote = remoteScripts.find((r) => r.slug === s.slug);
1048
+ if (remote) {
1049
+ await client.updateScript(remote.id, {
1050
+ content,
1051
+ changeSummary: "Synkronisert via CLI"
1052
+ });
1053
+ } else {
1054
+ await client.createScript(config.siteId, {
1055
+ name: s.slug.charAt(0).toUpperCase() + s.slug.slice(1).replace(/-/g, " "),
1056
+ slug: s.slug,
1057
+ type,
1058
+ scope: "global",
1059
+ content
1060
+ });
1061
+ }
1062
+ console.log(chalk7.yellow(` \u2191 ${s.fileName}`));
1063
+ pushed++;
1064
+ }
1065
+ console.log();
1066
+ if (pulled > 0) {
1067
+ console.log(chalk7.cyan(`Hentet ${pulled} skript`));
1068
+ }
1069
+ if (pushed > 0) {
1070
+ console.log(chalk7.yellow(`Lastet opp ${pushed} skript`));
1071
+ }
1072
+ if (conflicts.length > 0) {
1073
+ console.log();
1074
+ console.log(chalk7.red(`${conflicts.length} konflikter krever manuell h\xE5ndtering:`));
1075
+ console.log(chalk7.dim(' Bruk "kode diff <script>" for \xE5 se forskjeller'));
1076
+ console.log(chalk7.dim(' Bruk "kode push --force <script>" for \xE5 overskrive server'));
1077
+ console.log(chalk7.dim(' Bruk "kode pull --force <script>" for \xE5 overskrive lokalt'));
1078
+ }
1079
+ const updatedScripts = await client.listScripts(config.siteId);
1080
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1081
+ const updatedMetadata = updatedScripts.map((s) => {
1082
+ const ext = s.type === "javascript" ? "js" : "css";
1083
+ const filePath = join2(scriptsDir, `${s.slug}.${ext}`);
1084
+ const localContent = existsSync2(filePath) ? readFileSync2(filePath, "utf-8") : s.content;
1085
+ return {
1086
+ id: s.id,
1087
+ slug: s.slug,
1088
+ name: s.name,
1089
+ type: s.type,
1090
+ scope: s.scope,
1091
+ autoLoad: s.auto_load,
1092
+ version: s.current_version,
1093
+ loadOrder: s.load_order,
1094
+ lastPulledVersion: s.current_version,
1095
+ lastPulledAt: now,
1096
+ contentHash: hashContent(localContent)
1097
+ };
1098
+ });
1099
+ writeFileSync2(metadataPath, JSON.stringify(updatedMetadata, null, 2));
1100
+ } catch (error) {
1101
+ spinner.fail("Synkronisering feilet");
1102
+ console.error(chalk7.red("\nFeil:"), error);
1103
+ }
1104
+ }
1105
+
1106
+ // src/commands/diff.ts
1107
+ import chalk8 from "chalk";
1108
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
1109
+ import { join as join3 } from "path";
1110
+ function showDiff(local, remote) {
1111
+ const localLines = local.split("\n");
1112
+ const remoteLines = remote.split("\n");
1113
+ let commonPrefix = 0;
1114
+ while (commonPrefix < localLines.length && commonPrefix < remoteLines.length && localLines[commonPrefix] === remoteLines[commonPrefix]) {
1115
+ commonPrefix++;
1116
+ }
1117
+ let commonSuffix = 0;
1118
+ while (commonSuffix < localLines.length - commonPrefix && commonSuffix < remoteLines.length - commonPrefix && localLines[localLines.length - 1 - commonSuffix] === remoteLines[remoteLines.length - 1 - commonSuffix]) {
1119
+ commonSuffix++;
1120
+ }
1121
+ const contextLines = 3;
1122
+ const startLine = Math.max(0, commonPrefix - contextLines);
1123
+ const localEndLine = localLines.length - commonSuffix;
1124
+ const remoteEndLine = remoteLines.length - commonSuffix;
1125
+ console.log();
1126
+ console.log(chalk8.bold("Forskjeller:"));
1127
+ console.log(chalk8.dim("\u2500".repeat(60)));
1128
+ if (startLine < commonPrefix) {
1129
+ for (let i = startLine; i < commonPrefix; i++) {
1130
+ console.log(chalk8.dim(` ${(i + 1).toString().padStart(4)} \u2502 ${localLines[i]}`));
1131
+ }
1132
+ }
1133
+ if (localEndLine > commonPrefix) {
1134
+ console.log(chalk8.red.bold(`
1135
+ --- Lokal (linje ${commonPrefix + 1}-${localEndLine}) ---`));
1136
+ for (let i = commonPrefix; i < localEndLine; i++) {
1137
+ console.log(chalk8.red(`- ${(i + 1).toString().padStart(4)} \u2502 ${localLines[i]}`));
1138
+ }
1139
+ }
1140
+ if (remoteEndLine > commonPrefix) {
1141
+ console.log(chalk8.green.bold(`
1142
+ +++ Server (linje ${commonPrefix + 1}-${remoteEndLine}) ---`));
1143
+ for (let i = commonPrefix; i < remoteEndLine; i++) {
1144
+ console.log(chalk8.green(`+ ${(i + 1).toString().padStart(4)} \u2502 ${remoteLines[i]}`));
1145
+ }
1146
+ }
1147
+ const suffixStart = localLines.length - commonSuffix;
1148
+ const suffixEnd = Math.min(localLines.length, suffixStart + contextLines);
1149
+ if (commonSuffix > 0 && suffixStart < suffixEnd) {
1150
+ console.log();
1151
+ for (let i = suffixStart; i < suffixEnd; i++) {
1152
+ console.log(chalk8.dim(` ${(i + 1).toString().padStart(4)} \u2502 ${localLines[i]}`));
1153
+ }
1154
+ }
1155
+ console.log(chalk8.dim("\u2500".repeat(60)));
1156
+ }
1157
+ async function diffCommand(scriptSlug) {
1158
+ const projectRoot = findProjectRoot();
1159
+ if (!projectRoot) {
1160
+ console.log(chalk8.red("Feil: Ikke i et Cure Kode-prosjekt."));
1161
+ console.log(chalk8.dim('Kj\xF8r "kode init" f\xF8rst.'));
1162
+ return;
1163
+ }
1164
+ const config = getProjectConfig(projectRoot);
1165
+ if (!config) {
1166
+ console.log(chalk8.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
413
1167
  return;
414
1168
  }
415
- const claudeMdPath = join(projectRoot, "CLAUDE.md");
416
- const newKodeSection = generateClaudeMdMinimal(config.siteName, config.siteSlug);
417
- if (!existsSync(claudeMdPath)) {
418
- writeFileSync(claudeMdPath, newKodeSection);
419
- console.log(chalk5.green("\u2705 Created CLAUDE.md with Cure Kode section"));
420
- return;
421
- }
422
- let content = readFileSync(claudeMdPath, "utf-8");
423
- let removedCount = 0;
424
- const kodeSectionPattern = /^## Cure Kode[:\s(][\s\S]*?(?=\n---\n|\n## (?!#)|$)/gm;
425
- const oldMinimalPattern = /^## Cure Kode[:\s(][^\n]*\n\nThis project uses \*\*Cure Kode\*\*[\s\S]*?(?=\n---\n|\n## (?!#)|$)/gm;
426
- const matches1 = content.match(kodeSectionPattern) || [];
427
- const matches2 = content.match(oldMinimalPattern) || [];
428
- removedCount = matches1.length + matches2.length;
429
- content = content.replace(kodeSectionPattern, "");
430
- content = content.replace(oldMinimalPattern, "");
431
- content = content.replace(/(\n---\n)+/g, "\n---\n");
432
- content = content.replace(/^\n+/, "");
433
- content = content.replace(/^(---\n)+/, "");
434
- content = content.replace(/\n{3,}/g, "\n\n");
435
- content = newKodeSection + "---\n\n" + content;
436
- writeFileSync(claudeMdPath, content);
437
- if (removedCount > 1) {
438
- console.log(chalk5.green(`\u2705 Cleaned up ${removedCount} duplicate Kode sections and added fresh one`));
439
- } else if (removedCount === 1) {
440
- console.log(chalk5.green("\u2705 Updated Cure Kode section in CLAUDE.md"));
1169
+ const scriptsDir = getScriptsDir(projectRoot, config);
1170
+ try {
1171
+ const client = createApiClient(config);
1172
+ const remoteScripts = await client.listScripts(config.siteId);
1173
+ const remote = remoteScripts.find(
1174
+ (s) => s.slug === scriptSlug || s.name === scriptSlug
1175
+ );
1176
+ let localPath = null;
1177
+ let localContent = null;
1178
+ const jsPath = join3(scriptsDir, `${scriptSlug}.js`);
1179
+ const cssPath = join3(scriptsDir, `${scriptSlug}.css`);
1180
+ if (existsSync3(jsPath)) {
1181
+ localPath = jsPath;
1182
+ localContent = readFileSync3(jsPath, "utf-8");
1183
+ } else if (existsSync3(cssPath)) {
1184
+ localPath = cssPath;
1185
+ localContent = readFileSync3(cssPath, "utf-8");
1186
+ }
1187
+ if (!remote && !localContent) {
1188
+ console.log(chalk8.red(`Fant ikke skript "${scriptSlug}".`));
1189
+ console.log(chalk8.dim("Tilgjengelige skript:"));
1190
+ remoteScripts.forEach((s) => {
1191
+ console.log(chalk8.dim(` - ${s.slug}`));
1192
+ });
1193
+ return;
1194
+ }
1195
+ if (!remote && localContent) {
1196
+ console.log(chalk8.yellow(`Skript "${scriptSlug}" finnes bare lokalt (ikke p\xE5 server).`));
1197
+ console.log(chalk8.dim(`
1198
+ Filsti: ${localPath}`));
1199
+ console.log(chalk8.dim(`Linjer: ${localContent.split("\n").length}`));
1200
+ console.log(chalk8.dim(`Tegn: ${localContent.length}`));
1201
+ console.log();
1202
+ console.log(chalk8.dim('Bruk "kode push" for \xE5 laste opp til server.'));
1203
+ return;
1204
+ }
1205
+ if (remote && !localContent) {
1206
+ console.log(chalk8.yellow(`Skript "${scriptSlug}" finnes bare p\xE5 server (ikke lokalt).`));
1207
+ console.log(chalk8.dim(`
1208
+ Versjon: v${remote.current_version}`));
1209
+ console.log(chalk8.dim(`Type: ${remote.type}`));
1210
+ console.log(chalk8.dim(`Linjer: ${remote.content.split("\n").length}`));
1211
+ console.log(chalk8.dim(`Tegn: ${remote.content.length}`));
1212
+ console.log();
1213
+ console.log(chalk8.dim('Bruk "kode pull" for \xE5 hente fra server.'));
1214
+ return;
1215
+ }
1216
+ console.log(chalk8.bold(`Sammenligner "${scriptSlug}":`));
1217
+ console.log();
1218
+ console.log(chalk8.cyan("Lokal:"));
1219
+ console.log(chalk8.dim(` Filsti: ${localPath}`));
1220
+ console.log(chalk8.dim(` Linjer: ${localContent.split("\n").length}`));
1221
+ console.log(chalk8.dim(` Tegn: ${localContent.length}`));
1222
+ console.log();
1223
+ console.log(chalk8.green("Server:"));
1224
+ console.log(chalk8.dim(` Versjon: v${remote.current_version}`));
1225
+ console.log(chalk8.dim(` Type: ${remote.type}`));
1226
+ console.log(chalk8.dim(` Linjer: ${remote.content.split("\n").length}`));
1227
+ console.log(chalk8.dim(` Tegn: ${remote.content.length}`));
1228
+ if (localContent === remote.content) {
1229
+ console.log();
1230
+ console.log(chalk8.green("\u2713 Innholdet er identisk"));
1231
+ return;
1232
+ }
1233
+ showDiff(localContent, remote.content);
1234
+ console.log();
1235
+ console.log(chalk8.dim("Handlinger:"));
1236
+ console.log(chalk8.dim(" kode push --force " + scriptSlug + " \u2192 Bruk lokal versjon"));
1237
+ console.log(chalk8.dim(" kode pull --force " + scriptSlug + " \u2192 Bruk server-versjon"));
1238
+ } catch (error) {
1239
+ console.error(chalk8.red("\nFeil:"), error);
1240
+ }
1241
+ }
1242
+
1243
+ // src/commands/doctor.ts
1244
+ import chalk9 from "chalk";
1245
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1246
+ import { join as join4 } from "path";
1247
+ function isNewerVersion(v1, v2) {
1248
+ const parts1 = v1.split(".").map(Number);
1249
+ const parts2 = v2.split(".").map(Number);
1250
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1251
+ const p1 = parts1[i] || 0;
1252
+ const p2 = parts2[i] || 0;
1253
+ if (p2 > p1) return true;
1254
+ if (p2 < p1) return false;
1255
+ }
1256
+ return false;
1257
+ }
1258
+ async function doctorCommand() {
1259
+ console.log();
1260
+ console.log(chalk9.bold("\u{1F50D} Cure Kode Doctor"));
1261
+ console.log();
1262
+ const results = [];
1263
+ const configChecks = [];
1264
+ const projectRoot = findProjectRoot();
1265
+ if (!projectRoot) {
1266
+ configChecks.push({
1267
+ status: "fail",
1268
+ message: "Konfigurasjonsfil ikke funnet",
1269
+ detail: 'Kj\xF8r "kode init" for \xE5 sette opp prosjektet'
1270
+ });
1271
+ results.push({ category: "Prosjektkonfigurasjon", checks: configChecks });
1272
+ printResults(results);
1273
+ return;
1274
+ }
1275
+ configChecks.push({
1276
+ status: "pass",
1277
+ message: "Konfigurasjonsfil funnet"
1278
+ });
1279
+ const config = getProjectConfig(projectRoot);
1280
+ if (!config) {
1281
+ configChecks.push({
1282
+ status: "fail",
1283
+ message: "Kunne ikke lese konfigurasjon",
1284
+ detail: "config.json kan v\xE6re korrupt"
1285
+ });
1286
+ results.push({ category: "Prosjektkonfigurasjon", checks: configChecks });
1287
+ printResults(results);
1288
+ return;
1289
+ }
1290
+ if (config.apiKey && config.apiKey.startsWith("ck_")) {
1291
+ const maskedKey = `${config.apiKey.substring(0, 6)}***${config.apiKey.substring(config.apiKey.length - 3)}`;
1292
+ configChecks.push({
1293
+ status: "pass",
1294
+ message: `API-n\xF8kkel satt (${maskedKey})`
1295
+ });
441
1296
  } else {
442
- console.log(chalk5.green("\u2705 Added Cure Kode section to CLAUDE.md"));
1297
+ configChecks.push({
1298
+ status: "fail",
1299
+ message: "API-n\xF8kkel mangler eller ugyldig format",
1300
+ detail: 'Kj\xF8r "kode init" p\xE5 nytt'
1301
+ });
1302
+ }
1303
+ configChecks.push({
1304
+ status: "pass",
1305
+ message: `Site: ${config.siteName}`,
1306
+ detail: config.siteSlug
1307
+ });
1308
+ results.push({ category: "Prosjektkonfigurasjon", checks: configChecks });
1309
+ const networkChecks = [];
1310
+ try {
1311
+ const client = createApiClient(config);
1312
+ const scripts = await client.listScripts(config.siteId);
1313
+ networkChecks.push({
1314
+ status: "pass",
1315
+ message: "API tilgjengelig"
1316
+ });
1317
+ networkChecks.push({
1318
+ status: "pass",
1319
+ message: `${scripts.length} skript p\xE5 server`
1320
+ });
1321
+ } catch (error) {
1322
+ if (error instanceof KodeApiError) {
1323
+ if (error.statusCode === 401) {
1324
+ networkChecks.push({
1325
+ status: "fail",
1326
+ message: "API-n\xF8kkel ugyldig",
1327
+ detail: "Sjekk at n\xF8kkelen er korrekt i config.json"
1328
+ });
1329
+ } else if (error.statusCode === 404) {
1330
+ networkChecks.push({
1331
+ status: "fail",
1332
+ message: "Site ikke funnet",
1333
+ detail: `Site ID "${config.siteId}" finnes ikke`
1334
+ });
1335
+ } else {
1336
+ networkChecks.push({
1337
+ status: "fail",
1338
+ message: `API-feil: ${error.statusCode}`,
1339
+ detail: error.message
1340
+ });
1341
+ }
1342
+ } else if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED") {
1343
+ networkChecks.push({
1344
+ status: "fail",
1345
+ message: "Kunne ikke n\xE5 API",
1346
+ detail: "Sjekk internettforbindelse"
1347
+ });
1348
+ } else {
1349
+ networkChecks.push({
1350
+ status: "fail",
1351
+ message: "Nettverksfeil",
1352
+ detail: error.message || "Ukjent feil"
1353
+ });
1354
+ }
443
1355
  }
1356
+ results.push({ category: "Nettverk & API", checks: networkChecks });
1357
+ const mcpChecks = [];
1358
+ const claudeConfigPaths = [
1359
+ join4(projectRoot, ".mcp.json"),
1360
+ join4(projectRoot, "mcp.json"),
1361
+ join4(projectRoot, ".claude", "mcp.json")
1362
+ ];
1363
+ let mcpConfigFound = false;
1364
+ let mcpConfig = null;
1365
+ let mcpConfigPath = "";
1366
+ for (const configPath of claudeConfigPaths) {
1367
+ if (existsSync4(configPath)) {
1368
+ try {
1369
+ mcpConfig = JSON.parse(readFileSync4(configPath, "utf-8"));
1370
+ mcpConfigPath = configPath;
1371
+ mcpConfigFound = true;
1372
+ break;
1373
+ } catch {
1374
+ }
1375
+ }
1376
+ }
1377
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
1378
+ const homeMcpPath = join4(homeDir, ".claude", "mcp.json");
1379
+ if (!mcpConfigFound && existsSync4(homeMcpPath)) {
1380
+ try {
1381
+ mcpConfig = JSON.parse(readFileSync4(homeMcpPath, "utf-8"));
1382
+ mcpConfigPath = homeMcpPath;
1383
+ mcpConfigFound = true;
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ if (mcpConfigFound) {
1388
+ mcpChecks.push({
1389
+ status: "pass",
1390
+ message: "MCP-konfigurasjon funnet",
1391
+ detail: mcpConfigPath.replace(homeDir, "~")
1392
+ });
1393
+ const servers = mcpConfig?.mcpServers || {};
1394
+ if (servers["cure-kode"]) {
1395
+ mcpChecks.push({
1396
+ status: "pass",
1397
+ message: "cure-kode server \u2713"
1398
+ });
1399
+ } else {
1400
+ mcpChecks.push({
1401
+ status: "warn",
1402
+ message: "cure-kode server mangler",
1403
+ detail: 'Kj\xF8r "kode init" for \xE5 legge til'
1404
+ });
1405
+ }
1406
+ if (servers["webflow"] || servers["Webflow"]) {
1407
+ mcpChecks.push({
1408
+ status: "pass",
1409
+ message: "webflow server \u2713"
1410
+ });
1411
+ } else {
1412
+ mcpChecks.push({
1413
+ status: "warn",
1414
+ message: "webflow server mangler (valgfri)",
1415
+ detail: "For AI-drevet Webflow-redigering"
1416
+ });
1417
+ }
1418
+ if (servers["playwright"]) {
1419
+ mcpChecks.push({
1420
+ status: "pass",
1421
+ message: "playwright server \u2713"
1422
+ });
1423
+ }
1424
+ } else {
1425
+ mcpChecks.push({
1426
+ status: "warn",
1427
+ message: "MCP-konfigurasjon ikke funnet",
1428
+ detail: 'Kj\xF8r "kode init" for \xE5 sette opp'
1429
+ });
1430
+ }
1431
+ const kodeMdPath = join4(projectRoot, ".cure-kode", "KODE.md");
1432
+ if (existsSync4(kodeMdPath)) {
1433
+ mcpChecks.push({
1434
+ status: "pass",
1435
+ message: "KODE.md dokumentasjon \u2713"
1436
+ });
1437
+ } else {
1438
+ mcpChecks.push({
1439
+ status: "warn",
1440
+ message: "KODE.md mangler",
1441
+ detail: 'Kj\xF8r "kode pull" eller "kode update-claude-md"'
1442
+ });
1443
+ }
1444
+ const claudeMdPath = join4(projectRoot, "CLAUDE.md");
1445
+ if (existsSync4(claudeMdPath)) {
1446
+ const claudeMd = readFileSync4(claudeMdPath, "utf-8");
1447
+ if (claudeMd.includes("KODE.md") || claudeMd.includes(".cure-kode/KODE.md")) {
1448
+ mcpChecks.push({
1449
+ status: "pass",
1450
+ message: "CLAUDE.md har Kode-referanse"
1451
+ });
1452
+ } else {
1453
+ mcpChecks.push({
1454
+ status: "warn",
1455
+ message: "CLAUDE.md mangler Kode-referanse",
1456
+ detail: 'Kj\xF8r "kode update-claude-md"'
1457
+ });
1458
+ }
1459
+ } else {
1460
+ mcpChecks.push({
1461
+ status: "warn",
1462
+ message: "CLAUDE.md ikke funnet",
1463
+ detail: 'Kj\xF8r "kode update-claude-md" for \xE5 opprette'
1464
+ });
1465
+ }
1466
+ results.push({ category: "AI Agent Setup", checks: mcpChecks });
1467
+ const securityChecks = [];
1468
+ const gitignorePath = join4(projectRoot, ".gitignore");
1469
+ if (existsSync4(gitignorePath)) {
1470
+ const gitignore = readFileSync4(gitignorePath, "utf-8");
1471
+ if (gitignore.includes(".cure-kode") || gitignore.includes(".cure-kode/")) {
1472
+ securityChecks.push({
1473
+ status: "pass",
1474
+ message: ".cure-kode er gitignore'd"
1475
+ });
1476
+ } else {
1477
+ securityChecks.push({
1478
+ status: "warn",
1479
+ message: ".cure-kode b\xF8r gitignores",
1480
+ detail: "API-n\xF8kkel kan lekke i versjonskontroll"
1481
+ });
1482
+ }
1483
+ } else {
1484
+ securityChecks.push({
1485
+ status: "warn",
1486
+ message: "Ingen .gitignore funnet",
1487
+ detail: "Legg til .cure-kode/ i .gitignore"
1488
+ });
1489
+ }
1490
+ results.push({ category: "Sikkerhet", checks: securityChecks });
1491
+ const syncChecks = [];
1492
+ const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
1493
+ if (existsSync4(metadataPath)) {
1494
+ try {
1495
+ const metadata = JSON.parse(readFileSync4(metadataPath, "utf-8"));
1496
+ if (Array.isArray(metadata) && metadata.length > 0) {
1497
+ const latestSync = metadata.reduce((latest, m) => {
1498
+ const date = new Date(m.lastPulledAt);
1499
+ return date > latest ? date : latest;
1500
+ }, /* @__PURE__ */ new Date(0));
1501
+ const ageHours = (Date.now() - latestSync.getTime()) / (1e3 * 60 * 60);
1502
+ const ageDays = Math.floor(ageHours / 24);
1503
+ if (ageHours < 1) {
1504
+ syncChecks.push({
1505
+ status: "pass",
1506
+ message: "Sist synkronisert: nylig"
1507
+ });
1508
+ } else if (ageDays >= 1) {
1509
+ syncChecks.push({
1510
+ status: ageDays > 3 ? "warn" : "pass",
1511
+ message: `Sist synkronisert: ${ageDays} dag${ageDays > 1 ? "er" : ""} siden`,
1512
+ detail: ageDays > 3 ? 'Vurder \xE5 kj\xF8re "kode sync"' : void 0
1513
+ });
1514
+ } else {
1515
+ syncChecks.push({
1516
+ status: "pass",
1517
+ message: `Sist synkronisert: ${Math.floor(ageHours)} time${ageHours >= 2 ? "r" : ""} siden`
1518
+ });
1519
+ }
1520
+ syncChecks.push({
1521
+ status: "pass",
1522
+ message: `${metadata.length} skript sporet`
1523
+ });
1524
+ } else {
1525
+ syncChecks.push({
1526
+ status: "warn",
1527
+ message: "Ingen skript sporet",
1528
+ detail: 'Kj\xF8r "kode pull" for \xE5 synkronisere'
1529
+ });
1530
+ }
1531
+ } catch {
1532
+ syncChecks.push({
1533
+ status: "fail",
1534
+ message: "Korrupt synkroniseringsdata",
1535
+ detail: 'Slett .cure-kode/scripts.json og kj\xF8r "kode pull"'
1536
+ });
1537
+ }
1538
+ } else {
1539
+ syncChecks.push({
1540
+ status: "warn",
1541
+ message: "Aldri synkronisert",
1542
+ detail: 'Kj\xF8r "kode pull" for \xE5 hente skript'
1543
+ });
1544
+ }
1545
+ results.push({ category: "Synkronisering", checks: syncChecks });
1546
+ const versionChecks = [];
1547
+ versionChecks.push({
1548
+ status: "pass",
1549
+ message: `CLI-versjon: ${CLI_VERSION}`
1550
+ });
1551
+ try {
1552
+ const response = await fetch("https://registry.npmjs.org/@curenorway/kode-cli/latest", {
1553
+ signal: AbortSignal.timeout(3e3)
1554
+ });
1555
+ if (response.ok) {
1556
+ const data = await response.json();
1557
+ if (data.version && isNewerVersion(CLI_VERSION, data.version)) {
1558
+ versionChecks.push({
1559
+ status: "warn",
1560
+ message: `Oppdatering tilgjengelig: v${data.version}`,
1561
+ detail: 'Kj\xF8r "npm install -g @curenorway/kode-cli@latest"'
1562
+ });
1563
+ }
1564
+ }
1565
+ } catch {
1566
+ }
1567
+ results.push({ category: "Versjon", checks: versionChecks });
1568
+ printResults(results);
1569
+ }
1570
+ function printResults(results) {
1571
+ let passCount = 0;
1572
+ let warnCount = 0;
1573
+ let failCount = 0;
1574
+ for (const { category, checks } of results) {
1575
+ console.log(chalk9.bold(category));
1576
+ for (const check of checks) {
1577
+ const icon = check.status === "pass" ? chalk9.green("\u2713") : check.status === "warn" ? chalk9.yellow("\u26A0") : chalk9.red("\u2717");
1578
+ const color = check.status === "pass" ? chalk9.dim : check.status === "warn" ? chalk9.yellow : chalk9.red;
1579
+ console.log(` ${icon} ${color(check.message)}`);
1580
+ if (check.detail) {
1581
+ console.log(chalk9.dim(` ${check.detail}`));
1582
+ }
1583
+ if (check.status === "pass") passCount++;
1584
+ else if (check.status === "warn") warnCount++;
1585
+ else failCount++;
1586
+ }
1587
+ console.log();
1588
+ }
1589
+ console.log(chalk9.dim("\u2500".repeat(40)));
1590
+ const parts = [];
1591
+ if (passCount > 0) parts.push(chalk9.green(`${passCount} \u2713`));
1592
+ if (warnCount > 0) parts.push(chalk9.yellow(`${warnCount} \u26A0`));
1593
+ if (failCount > 0) parts.push(chalk9.red(`${failCount} \u2717`));
1594
+ console.log(`Resultat: ${parts.join(", ")}`);
444
1595
  console.log();
445
- console.log(chalk5.dim("The Cure Kode section now includes:"));
446
- console.log(chalk5.dim(" \u2022 What is Cure Kode (internal tool explanation)"));
447
- console.log(chalk5.dim(" \u2022 CDN URL with script tag for Webflow"));
448
- console.log(chalk5.dim(" \u2022 Workflow steps"));
449
- console.log(chalk5.dim(" \u2022 Command reference"));
450
1596
  }
451
1597
 
452
1598
  // src/cli.ts
453
- var require2 = createRequire(import.meta.url);
454
- var pkg = require2("../package.json");
455
1599
  var program = new Command();
456
- program.name("kode").description("CLI for Cure Kode - manage JS/CSS scripts for Webflow sites").version(pkg.version);
457
- program.command("init").description("Initialize Cure Kode in current directory").option("-k, --api-key <key>", "API key").option("-s, --site-slug <slug>", "Site slug").option("-f, --force", "Reinitialize even if already configured").action((options) => {
1600
+ program.name("kode").description("CLI for Cure Kode - manage JS/CSS scripts for Webflow sites").version(CLI_VERSION);
1601
+ program.command("init").description("Initialize Cure Kode in current directory").option("-k, --api-key <key>", "API key (from Cure App)").option("-f, --force", "Reinitialize even if already configured").action((options) => {
458
1602
  initCommand(options);
459
1603
  });
1604
+ program.command("upgrade").description("Upgrade existing project to latest CLI version").action(() => {
1605
+ upgradeCommand();
1606
+ });
460
1607
  program.command("pull").description("Download scripts from Cure to local files").argument("[script]", "Specific script slug to pull").option("-f, --force", "Overwrite local changes").action((script, options) => {
461
1608
  pullCommand({ script, ...options });
462
1609
  });
463
- program.command("push").description("Upload local scripts to Cure").argument("[script]", "Specific script file or slug to push").option("-m, --message <message>", "Change summary").option("-a, --all", "Push all scripts even if unchanged").option("--auto-load", "Enable auto-loading for new scripts (default for global)").option("--no-auto-load", "Disable auto-loading for new scripts").action((script, options) => {
1610
+ program.command("push").description("Upload local scripts to Cure").argument("[script]", "Specific script file or slug to push").option("-m, --message <message>", "Change summary").option("-a, --all", "Push all scripts even if unchanged").option("-f, --force", "Force push even if remote has newer version").option("--auto-load", "Enable auto-loading for new scripts (default for global)").option("--no-auto-load", "Disable auto-loading for new scripts").action((script, options) => {
464
1611
  pushCommand({ script, ...options });
465
1612
  });
1613
+ program.command("sync").description("Bidirectional sync - pull and push changes intelligently").option("-n, --dry-run", "Show what would change without making changes").action((options) => {
1614
+ syncCommand(options);
1615
+ });
1616
+ program.command("diff <script>").description("Show differences between local and remote script").action((script) => {
1617
+ diffCommand(script);
1618
+ });
466
1619
  program.command("watch").description("Watch for changes and auto-push").option("-d, --deploy", "Auto-deploy after each push").action((options) => {
467
1620
  watchCommand(options);
468
1621
  });
469
- program.command("deploy [environment]").description("Deploy to staging or production").option("-p, --promote", "Promote staging to production").option("-f, --force", "Force release stale deploy lock before deploying").action((environment, options) => {
1622
+ program.command("deploy [environment]").description("Deploy to staging or production").option("-p, --promote", "Promote staging to production").option("-f, --force", "Force release stale deploy lock before deploying").option("-n, --dry-run", "Preview deployment without executing").action((environment, options) => {
470
1623
  deployCommand(environment, options);
471
1624
  });
472
1625
  program.command("rollback [environment]").description("Rollback to previous deployment").action((environment = "staging") => {
@@ -475,8 +1628,30 @@ program.command("rollback [environment]").description("Rollback to previous depl
475
1628
  program.command("html <url>").description("Fetch and analyze HTML from a URL").option("-j, --json", "Output as JSON").option("--scripts", "Show only scripts").option("--styles", "Show only styles").option("-s, --save", "Save page structure to context").option("-f, --force", "Force refresh when using --save").action((url, options) => {
476
1629
  htmlCommand(url, options);
477
1630
  });
478
- program.command("pages").description("List cached page contexts").option("-j, --json", "Output as JSON").argument("[page]", "Show details for a specific page slug").action((page, options) => {
479
- pagesCommand({ page, ...options });
1631
+ var pagesCmd = program.command("pages").description("Manage CDN pages and page-specific scripts");
1632
+ pagesCmd.command("list", { isDefault: true }).description("List all CDN pages (remote)").option("-j, --json", "Output as JSON").action((options) => {
1633
+ pagesListCommand(options);
1634
+ });
1635
+ pagesCmd.command("create <name>").description("Create a new CDN page").option("-p, --patterns <patterns...>", "URL patterns (e.g., /about, /about/*)").option("-t, --pattern-type <type>", "Pattern type: exact, prefix, wildcard, regex", "prefix").option("--priority <n>", "Priority (higher = checked first)", "0").option("--parent <slug>", "Parent page slug for nesting").action((name, options) => {
1636
+ pagesCreateCommand(name, options);
1637
+ });
1638
+ pagesCmd.command("delete <slug>").description("Delete a CDN page").option("-f, --force", "Confirm deletion").action((slug, options) => {
1639
+ pagesDeleteCommand(slug, options);
1640
+ });
1641
+ pagesCmd.command("update <slug>").description("Update a CDN page").option("-n, --name <name>", "New name").option("-p, --patterns <patterns...>", "New URL patterns").option("-t, --pattern-type <type>", "Pattern type: exact, prefix, wildcard, regex").option("--priority <n>", "Priority").option("--parent <slug>", "Parent page slug").option("--remove-parent", "Remove parent (make root-level)").action((slug, options) => {
1642
+ pagesUpdateCommand(slug, options);
1643
+ });
1644
+ pagesCmd.command("assign <page> <script>").description("Assign a script to a page").option("-o, --load-order <n>", "Load order override").action((page, script, options) => {
1645
+ pagesAssignCommand(page, script, options);
1646
+ });
1647
+ pagesCmd.command("unassign <page> <script>").description("Remove a script from a page").action((page, script) => {
1648
+ pagesUnassignCommand(page, script);
1649
+ });
1650
+ pagesCmd.command("scripts <page>").description("Show scripts assigned to a page").action((page) => {
1651
+ pagesScriptsCommand(page);
1652
+ });
1653
+ pagesCmd.command("context").description("Manage local HTML page cache (for AI context)").option("-j, --json", "Output as JSON").option("-d, --delete", "Delete the specified page context").argument("[page]", "Page slug to view or delete").action((page, options) => {
1654
+ pagesContextCommand({ page, ...options });
480
1655
  });
481
1656
  program.command("status").description("Show current status of scripts and deployments").option("-v, --verbose", "Show more details").action((options) => {
482
1657
  statusCommand(options);
@@ -493,9 +1668,8 @@ program.command("production <action>").description("Enable or disable production
493
1668
  program.command("update-claude-md").alias("ucm").description("Add or update Cure Kode section in CLAUDE.md").action(() => {
494
1669
  updateClaudeMdCommand();
495
1670
  });
1671
+ program.command("doctor").description("Diagnose configuration and environment issues").action(() => {
1672
+ doctorCommand();
1673
+ });
496
1674
  program.showHelpAfterError();
497
- console.log();
498
- console.log(chalk6.bold(" Cure Kode CLI"));
499
- console.log(chalk6.dim(" Manage JS/CSS for Webflow sites"));
500
- console.log();
501
1675
  program.parse();