@curenorway/kode-cli 1.0.0 → 1.0.2

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.
@@ -5,7 +5,7 @@ import { join, dirname } from "path";
5
5
  var globalConfig = new Conf({
6
6
  projectName: "cure-kode",
7
7
  defaults: {
8
- apiUrl: "https://cure-app-v2-production.up.railway.app"
8
+ apiUrl: "https://app.cure.no"
9
9
  }
10
10
  });
11
11
  var PROJECT_CONFIG_DIR = ".cure-kode";
@@ -54,12 +54,414 @@ function getScriptsDir(projectRoot, projectConfig) {
54
54
  return join(projectRoot, projectConfig?.scriptsDir || "scripts");
55
55
  }
56
56
 
57
+ // src/lib/context.ts
58
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
59
+ import { join as join2 } from "path";
60
+ var CONTEXT_FILE = "context.md";
61
+ var PROJECT_CONFIG_DIR2 = ".cure-kode";
62
+ function getContextPath(projectRoot) {
63
+ return join2(projectRoot, PROJECT_CONFIG_DIR2, CONTEXT_FILE);
64
+ }
65
+ function parseContext(content) {
66
+ const context = {
67
+ site: { name: "", slug: "" },
68
+ scripts: [],
69
+ pages: [],
70
+ notes: [],
71
+ sessions: [],
72
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
73
+ updatedBy: "unknown"
74
+ };
75
+ const titleMatch = content.match(/^# Context: (.+)$/m);
76
+ if (titleMatch) {
77
+ context.site.name = titleMatch[1];
78
+ }
79
+ const updatedMatch = content.match(/> Last updated: (.+)$/m);
80
+ if (updatedMatch) {
81
+ context.lastUpdated = updatedMatch[1];
82
+ }
83
+ const byMatch = content.match(/> Updated by: (.+)$/m);
84
+ if (byMatch) {
85
+ context.updatedBy = byMatch[1];
86
+ }
87
+ const domainMatch = content.match(/- \*\*Domain\*\*: (.+)$/m);
88
+ if (domainMatch) {
89
+ context.site.domain = domainMatch[1];
90
+ }
91
+ const stagingMatch = content.match(/- \*\*Staging\*\*: (.+)$/m);
92
+ if (stagingMatch) {
93
+ context.site.stagingDomain = stagingMatch[1];
94
+ }
95
+ const scriptsTableMatch = content.match(/## Scripts\n\n\|[^\n]+\n\|[-|\s]+\n((?:\|[^\n]+\n?)+)/m);
96
+ if (scriptsTableMatch) {
97
+ const rows = scriptsTableMatch[1].trim().split("\n");
98
+ for (const row of rows) {
99
+ const cols = row.split("|").map((c) => c.trim()).filter(Boolean);
100
+ if (cols.length >= 3) {
101
+ context.scripts.push({
102
+ slug: cols[0],
103
+ type: cols[1].toLowerCase() === "css" ? "css" : "javascript",
104
+ scope: cols[2].toLowerCase() === "page-specific" ? "page-specific" : "global",
105
+ purpose: cols[3] || void 0
106
+ });
107
+ }
108
+ }
109
+ }
110
+ const notesMatch = content.match(/## Notes\n\n((?:- .+\n?)+)/m);
111
+ if (notesMatch) {
112
+ context.notes = notesMatch[1].trim().split("\n").map((line) => line.replace(/^- /, "").trim()).filter(Boolean);
113
+ }
114
+ const sessionsMatch = content.match(/## Sessions\n\n([\s\S]+?)(?=\n##|$)/m);
115
+ if (sessionsMatch) {
116
+ const sessionBlocks = sessionsMatch[1].split(/\n### /).filter(Boolean);
117
+ for (const block of sessionBlocks) {
118
+ const headerMatch = block.match(/^(.+?) - (.+)$/m);
119
+ const taskMatch = block.match(/\*\*Task\*\*: (.+)$/m);
120
+ const changesMatch = block.match(/\*\*Changes\*\*:\n((?:- .+\n?)+)/m);
121
+ if (headerMatch) {
122
+ const session = {
123
+ date: headerMatch[1].replace("### ", ""),
124
+ agent: headerMatch[2],
125
+ task: taskMatch?.[1] || "",
126
+ changes: []
127
+ };
128
+ if (changesMatch) {
129
+ session.changes = changesMatch[1].trim().split("\n").map((line) => line.replace(/^- /, "").trim()).filter(Boolean);
130
+ }
131
+ context.sessions.push(session);
132
+ }
133
+ }
134
+ }
135
+ return context;
136
+ }
137
+ function serializeContext(context) {
138
+ let md = `# Context: ${context.site.name}
139
+
140
+ `;
141
+ md += `> Last updated: ${context.lastUpdated}
142
+ `;
143
+ md += `> Updated by: ${context.updatedBy}
144
+
145
+ `;
146
+ if (context.site.domain || context.site.stagingDomain) {
147
+ md += `## Site
148
+
149
+ `;
150
+ if (context.site.domain) {
151
+ md += `- **Domain**: ${context.site.domain}
152
+ `;
153
+ }
154
+ if (context.site.stagingDomain) {
155
+ md += `- **Staging**: ${context.site.stagingDomain}
156
+ `;
157
+ }
158
+ md += `
159
+ `;
160
+ }
161
+ md += `## Scripts
162
+
163
+ `;
164
+ md += `| Script | Type | Scope | Purpose |
165
+ `;
166
+ md += `|--------|------|-------|--------|
167
+ `;
168
+ for (const script of context.scripts) {
169
+ const purpose = script.purpose || "(add purpose)";
170
+ md += `| ${script.slug} | ${script.type === "css" ? "CSS" : "JS"} | ${script.scope} | ${purpose} |
171
+ `;
172
+ }
173
+ md += `
174
+ `;
175
+ if (context.pages && context.pages.length > 0) {
176
+ md += `## Pages
177
+
178
+ `;
179
+ md += `> Cached HTML structure. Use \`kode html <url> --save\` to refresh.
180
+
181
+ `;
182
+ for (const page of context.pages) {
183
+ const path = new URL(page.url).pathname;
184
+ md += `### ${path}
185
+ `;
186
+ if (page.title) {
187
+ md += `"${page.title}"
188
+ `;
189
+ }
190
+ md += `
191
+ `;
192
+ if (page.sections) {
193
+ md += `**Sections**: ${page.sections}
194
+ `;
195
+ }
196
+ if (page.ctas) {
197
+ md += `**CTAs**: ${page.ctas}
198
+ `;
199
+ }
200
+ if (page.forms) {
201
+ md += `**Forms**: ${page.forms}
202
+ `;
203
+ }
204
+ if (page.cms) {
205
+ md += `**CMS**: ${page.cms}
206
+ `;
207
+ }
208
+ if (page.notes && page.notes.length > 0) {
209
+ md += `
210
+ **Notes**:
211
+ `;
212
+ for (const note of page.notes) {
213
+ md += `- ${note}
214
+ `;
215
+ }
216
+ }
217
+ md += `
218
+ `;
219
+ }
220
+ }
221
+ md += `## Notes
222
+
223
+ `;
224
+ if (context.notes.length > 0) {
225
+ for (const note of context.notes) {
226
+ md += `- ${note}
227
+ `;
228
+ }
229
+ } else {
230
+ md += `- (HTML structure discoveries go here)
231
+ `;
232
+ md += `- (Third-party integrations detected)
233
+ `;
234
+ md += `- (Known issues/workarounds)
235
+ `;
236
+ }
237
+ md += `
238
+ `;
239
+ md += `## Sessions
240
+
241
+ `;
242
+ if (context.sessions.length > 0) {
243
+ for (const session of context.sessions) {
244
+ md += `### ${session.date} - ${session.agent}
245
+ `;
246
+ md += `**Task**: ${session.task}
247
+ `;
248
+ md += `**Changes**:
249
+ `;
250
+ for (const change of session.changes) {
251
+ md += `- ${change}
252
+ `;
253
+ }
254
+ md += `
255
+ `;
256
+ }
257
+ } else {
258
+ md += `(No sessions recorded yet)
259
+ `;
260
+ }
261
+ return md;
262
+ }
263
+ function readContext(projectRoot) {
264
+ const contextPath = getContextPath(projectRoot);
265
+ if (!existsSync2(contextPath)) {
266
+ return null;
267
+ }
268
+ try {
269
+ const content = readFileSync2(contextPath, "utf-8");
270
+ return parseContext(content);
271
+ } catch {
272
+ return null;
273
+ }
274
+ }
275
+ function writeContext(projectRoot, context) {
276
+ const contextPath = getContextPath(projectRoot);
277
+ const content = serializeContext(context);
278
+ writeFileSync2(contextPath, content, "utf-8");
279
+ }
280
+ function appendNote(projectRoot, note, agent = "CLI") {
281
+ const context = readContext(projectRoot);
282
+ if (!context) {
283
+ throw new Error('No context file found. Run "kode init" first.');
284
+ }
285
+ context.notes.push(note);
286
+ context.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
287
+ context.updatedBy = agent;
288
+ writeContext(projectRoot, context);
289
+ }
290
+ function addSession(projectRoot, session, agent = "Claude") {
291
+ const context = readContext(projectRoot);
292
+ if (!context) {
293
+ throw new Error('No context file found. Run "kode init" first.');
294
+ }
295
+ context.sessions.unshift({
296
+ ...session,
297
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
298
+ });
299
+ context.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
300
+ context.updatedBy = agent;
301
+ writeContext(projectRoot, context);
302
+ }
303
+ function updateScriptPurpose(projectRoot, slug, purpose, agent = "Claude") {
304
+ const context = readContext(projectRoot);
305
+ if (!context) {
306
+ throw new Error('No context file found. Run "kode init" first.');
307
+ }
308
+ const script = context.scripts.find((s) => s.slug === slug);
309
+ if (script) {
310
+ script.purpose = purpose;
311
+ }
312
+ context.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
313
+ context.updatedBy = agent;
314
+ writeContext(projectRoot, context);
315
+ }
316
+ function generateInitialContext(config, scripts, site) {
317
+ const context = {
318
+ site: {
319
+ name: config.siteName,
320
+ slug: config.siteSlug,
321
+ domain: site?.domain || void 0,
322
+ stagingDomain: site?.staging_domain || void 0
323
+ },
324
+ scripts: scripts.map((s) => ({
325
+ slug: s.slug,
326
+ type: s.type,
327
+ scope: s.scope
328
+ })),
329
+ pages: [],
330
+ notes: [],
331
+ sessions: [],
332
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
333
+ updatedBy: "kode init"
334
+ };
335
+ return serializeContext(context);
336
+ }
337
+ function upsertPage(projectRoot, page, agent = "kode html") {
338
+ const context = readContext(projectRoot);
339
+ if (!context) {
340
+ throw new Error('No context file found. Run "kode init" first.');
341
+ }
342
+ if (!context.pages) {
343
+ context.pages = [];
344
+ }
345
+ const existingIndex = context.pages.findIndex(
346
+ (p) => p.url === page.url || p.slug === page.slug
347
+ );
348
+ if (existingIndex >= 0) {
349
+ const existingNotes = context.pages[existingIndex].notes;
350
+ context.pages[existingIndex] = {
351
+ ...page,
352
+ notes: page.notes || existingNotes
353
+ };
354
+ } else {
355
+ context.pages.push(page);
356
+ }
357
+ context.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
358
+ context.updatedBy = agent;
359
+ writeContext(projectRoot, context);
360
+ }
361
+ function generateClaudeMd(siteName, scriptsDir = "scripts") {
362
+ return `# Cure Kode Project: ${siteName}
363
+
364
+ > AI Agent Instructions for Webflow Script Management
365
+
366
+ ## Commands
367
+
368
+ | Command | Description |
369
+ |---------|-------------|
370
+ | \`kode pull\` | Download scripts from remote |
371
+ | \`kode push\` | Upload local scripts |
372
+ | \`kode watch\` | Auto-sync on file changes |
373
+ | \`kode deploy --env staging\` | Deploy to staging |
374
+ | \`kode deploy --env production\` | Deploy to production |
375
+ | \`kode status\` | Show sync status |
376
+ | \`kode html <url>\` | Analyze page HTML |
377
+ | \`kode html <url> --save\` | Analyze and cache page structure |
378
+ | \`kode pages\` | List cached page contexts |
379
+ | \`kode context\` | View/edit project context |
380
+
381
+ ## Project Structure
382
+
383
+ \`\`\`
384
+ .cure-kode/
385
+ \u251C\u2500\u2500 config.json # Site configuration (contains API key - DO NOT COMMIT)
386
+ \u251C\u2500\u2500 context.md # Dynamic context - READ and WRITE this file
387
+ \u251C\u2500\u2500 pages/ # Cached page structure (JSON files)
388
+ \u251C\u2500\u2500 .gitignore
389
+ \u2514\u2500\u2500 scripts.json # Script metadata cache
390
+ ${scriptsDir}/
391
+ \u2514\u2500\u2500 (your scripts)
392
+ \`\`\`
393
+
394
+ ## Dynamic Context
395
+
396
+ **IMPORTANT**: Read \`.cure-kode/context.md\` before starting work.
397
+
398
+ This file contains:
399
+ - Current scripts and their purposes
400
+ - **Cached page structures** (sections, CTAs, forms, CMS patterns)
401
+ - Site-specific notes and discoveries
402
+ - Session history from previous AI work
403
+
404
+ **Update it after your work session** with:
405
+ - What you changed
406
+ - Any HTML/CSS patterns you discovered
407
+ - Notes for future sessions
408
+
409
+ ## HTML Context
410
+
411
+ When working on a specific page:
412
+ 1. Use \`kode html <url> --save\` to cache the page structure
413
+ 2. The structure is saved to \`.cure-kode/pages/\` and summarized in context.md
414
+ 3. If the user says HTML has changed, refresh with \`--force\`
415
+
416
+ Page context includes:
417
+ - **Sections**: Main content areas (hero, features, footer, etc.)
418
+ - **CTAs**: Buttons and prominent links with their text
419
+ - **Forms**: Form fields, labels, and submit buttons
420
+ - **CMS**: Collection patterns with item counts
421
+
422
+ ## Workflow
423
+
424
+ 1. \`kode pull\` to get latest scripts
425
+ 2. Read \`.cure-kode/context.md\` for project state
426
+ 3. If working on specific page: \`kode html <url> --save\`
427
+ 4. Make changes to scripts in \`${scriptsDir}/\`
428
+ 5. \`kode push\` to upload
429
+ 6. \`kode deploy --env staging\` to test
430
+ 7. Update context.md with session notes
431
+ 8. \`kode deploy --env production\` when ready
432
+
433
+ ## Best Practices
434
+
435
+ 1. **Always deploy to staging first** - Test before production
436
+ 2. **Use page-specific scripts** - Don't pollute global scope
437
+ 3. **Document your changes** - Update context.md
438
+ 4. **Cache page HTML** - Use \`kode html <url> --save\` to understand structure
439
+
440
+ ## MCP Tools
441
+
442
+ If using the Kode MCP server, these tools are available:
443
+ - \`kode_list_scripts\` - List all scripts
444
+ - \`kode_get_script\` - Get script content
445
+ - \`kode_create_script\` - Create new script
446
+ - \`kode_update_script\` - Update script content
447
+ - \`kode_deploy\` - Deploy to environment
448
+ - \`kode_status\` - Get deployment status
449
+ - \`kode_fetch_html\` - Analyze page HTML
450
+ - \`kode_read_context\` - Read context file
451
+ - \`kode_update_context\` - Update context file
452
+ - \`kode_refresh_page\` - Fetch and cache page structure
453
+ - \`kode_get_page_context\` - Get cached page structure
454
+ - \`kode_list_pages\` - List cached pages
455
+ `;
456
+ }
457
+
57
458
  // src/commands/init.ts
58
459
  import chalk from "chalk";
59
460
  import ora from "ora";
60
- import { prompt } from "enquirer";
61
- import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
62
- import { join as join2 } from "path";
461
+ import enquirer from "enquirer";
462
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
463
+ import { join as join3 } from "path";
464
+ var { prompt } = enquirer;
63
465
  async function initCommand(options) {
64
466
  const cwd = process.cwd();
65
467
  const existingRoot = findProjectRoot(cwd);
@@ -119,7 +521,7 @@ async function initCommand(options) {
119
521
  const spinner = ora("Validating configuration...").start();
120
522
  try {
121
523
  const response = await fetch(
122
- `https://cure-app-v2-production.up.railway.app/api/cdn/sites/${answers.siteId}`,
524
+ `https://app.cure.no/api/cdn/sites/${answers.siteId}`,
123
525
  {
124
526
  headers: {
125
527
  "X-API-Key": answers.apiKey
@@ -142,21 +544,102 @@ async function initCommand(options) {
142
544
  environment: answers.environment
143
545
  };
144
546
  saveProjectConfig(config, cwd);
145
- const scriptsPath = join2(cwd, answers.scriptsDir);
146
- if (!existsSync2(scriptsPath)) {
547
+ const scriptsPath = join3(cwd, answers.scriptsDir);
548
+ if (!existsSync3(scriptsPath)) {
147
549
  mkdirSync2(scriptsPath, { recursive: true });
148
550
  }
149
- const gitignorePath = join2(cwd, ".cure-kode", ".gitignore");
551
+ const gitignorePath = join3(cwd, ".cure-kode", ".gitignore");
150
552
  const gitignoreContent = `# Keep config.json private - contains API key
151
553
  config.json
152
554
  `;
153
- const fs = await import("fs");
154
- fs.writeFileSync(gitignorePath, gitignoreContent);
555
+ writeFileSync3(gitignorePath, gitignoreContent);
556
+ spinner.text = "Generating AI context files...";
557
+ spinner.start();
558
+ let scripts = [];
559
+ let siteInfo;
560
+ try {
561
+ const siteResponse = await fetch(
562
+ `https://app.cure.no/api/cdn/sites/${answers.siteId}`,
563
+ {
564
+ headers: { "X-API-Key": answers.apiKey }
565
+ }
566
+ );
567
+ if (siteResponse.ok) {
568
+ siteInfo = await siteResponse.json();
569
+ }
570
+ const scriptsResponse = await fetch(
571
+ `https://app.cure.no/api/cdn/sites/${answers.siteId}/scripts`,
572
+ {
573
+ headers: { "X-API-Key": answers.apiKey }
574
+ }
575
+ );
576
+ if (scriptsResponse.ok) {
577
+ scripts = await scriptsResponse.json();
578
+ }
579
+ } catch {
580
+ }
581
+ const claudeMdPath = join3(cwd, "CLAUDE.md");
582
+ const claudeMdContent = generateClaudeMd(config.siteName, config.scriptsDir || "scripts");
583
+ let claudeMdAction = "created";
584
+ if (existsSync3(claudeMdPath)) {
585
+ spinner.stop();
586
+ console.log(chalk.yellow("\n\u26A0\uFE0F CLAUDE.md already exists in this directory.\n"));
587
+ const { action } = await prompt([
588
+ {
589
+ type: "select",
590
+ name: "action",
591
+ message: "How would you like to handle Kode instructions?",
592
+ choices: [
593
+ {
594
+ name: "append",
595
+ message: "Append to existing CLAUDE.md (recommended)"
596
+ },
597
+ {
598
+ name: "separate",
599
+ message: "Create separate KODE.md file"
600
+ },
601
+ {
602
+ name: "skip",
603
+ message: "Skip - I'll add instructions manually"
604
+ }
605
+ ],
606
+ initial: 0
607
+ }
608
+ ]);
609
+ spinner.start("Generating AI context files...");
610
+ if (action === "append") {
611
+ const existingContent = readFileSync3(claudeMdPath, "utf-8");
612
+ const separator = "\n\n---\n\n";
613
+ writeFileSync3(claudeMdPath, existingContent + separator + claudeMdContent);
614
+ claudeMdAction = "appended";
615
+ } else if (action === "separate") {
616
+ writeFileSync3(join3(cwd, "KODE.md"), claudeMdContent);
617
+ claudeMdAction = "separate";
618
+ } else {
619
+ claudeMdAction = "skipped";
620
+ }
621
+ } else {
622
+ writeFileSync3(claudeMdPath, claudeMdContent);
623
+ }
624
+ const contextMdContent = generateInitialContext(config, scripts, siteInfo);
625
+ writeFileSync3(join3(cwd, ".cure-kode", "context.md"), contextMdContent);
626
+ spinner.succeed("AI context files generated");
155
627
  console.log(chalk.green("\n\u2705 Cure Kode initialized successfully!\n"));
156
628
  console.log(chalk.dim("Project structure:"));
157
629
  console.log(chalk.dim(` ${cwd}/`));
630
+ if (claudeMdAction === "created") {
631
+ console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (AI agent instructions)`));
632
+ } else if (claudeMdAction === "appended") {
633
+ console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (Kode instructions appended)`));
634
+ } else if (claudeMdAction === "separate") {
635
+ console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (existing - unchanged)`));
636
+ console.log(chalk.dim(` \u251C\u2500\u2500 KODE.md (Kode AI instructions)`));
637
+ } else {
638
+ console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (existing - unchanged)`));
639
+ }
158
640
  console.log(chalk.dim(` \u251C\u2500\u2500 .cure-kode/`));
159
641
  console.log(chalk.dim(` \u2502 \u251C\u2500\u2500 config.json (your configuration)`));
642
+ console.log(chalk.dim(` \u2502 \u251C\u2500\u2500 context.md (dynamic AI context)`));
160
643
  console.log(chalk.dim(` \u2502 \u2514\u2500\u2500 .gitignore (protects API key)`));
161
644
  console.log(chalk.dim(` \u2514\u2500\u2500 ${answers.scriptsDir}/`));
162
645
  console.log(chalk.dim(` \u2514\u2500\u2500 (your scripts will be here)`));
@@ -164,6 +647,10 @@ config.json
164
647
  console.log(chalk.cyan(" 1. kode pull ") + chalk.dim("Download existing scripts"));
165
648
  console.log(chalk.cyan(" 2. kode watch ") + chalk.dim("Watch for changes and auto-push"));
166
649
  console.log(chalk.cyan(" 3. kode deploy ") + chalk.dim("Deploy to staging/production"));
650
+ if (claudeMdAction === "separate") {
651
+ console.log(chalk.yellow("\n\u{1F4A1} Tip: Add this line to your CLAUDE.md to reference Kode instructions:"));
652
+ console.log(chalk.dim(" See KODE.md for Cure Kode CDN management instructions."));
653
+ }
167
654
  } catch (error) {
168
655
  spinner.fail("Initialization failed");
169
656
  console.error(chalk.red("\nError:"), error);
@@ -294,8 +781,8 @@ function createApiClient(config) {
294
781
  // src/commands/pull.ts
295
782
  import chalk2 from "chalk";
296
783
  import ora2 from "ora";
297
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
298
- import { join as join3 } from "path";
784
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
785
+ import { join as join4 } from "path";
299
786
  async function pullCommand(options) {
300
787
  const projectRoot = findProjectRoot();
301
788
  if (!projectRoot) {
@@ -319,7 +806,7 @@ async function pullCommand(options) {
319
806
  }
320
807
  spinner.succeed(`Found ${scripts.length} script(s)`);
321
808
  const scriptsDir = getScriptsDir(projectRoot, config);
322
- if (!existsSync3(scriptsDir)) {
809
+ if (!existsSync4(scriptsDir)) {
323
810
  mkdirSync3(scriptsDir, { recursive: true });
324
811
  }
325
812
  const scriptsToPull = options.script ? scripts.filter((s) => s.slug === options.script || s.name === options.script) : scripts;
@@ -338,8 +825,8 @@ async function pullCommand(options) {
338
825
  for (const script of scriptsToPull) {
339
826
  const ext = script.type === "javascript" ? "js" : "css";
340
827
  const fileName = `${script.slug}.${ext}`;
341
- const filePath = join3(scriptsDir, fileName);
342
- if (existsSync3(filePath) && !options.force) {
828
+ const filePath = join4(scriptsDir, fileName);
829
+ if (existsSync4(filePath) && !options.force) {
343
830
  const localContent = await import("fs").then(
344
831
  (fs) => fs.readFileSync(filePath, "utf-8")
345
832
  );
@@ -351,7 +838,7 @@ async function pullCommand(options) {
351
838
  continue;
352
839
  }
353
840
  }
354
- writeFileSync2(filePath, script.content);
841
+ writeFileSync4(filePath, script.content);
355
842
  const scopeTag = script.scope === "global" ? chalk2.blue("[G]") : chalk2.magenta("[P]");
356
843
  console.log(
357
844
  chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${script.current_version} ${scopeTag}`)
@@ -365,7 +852,7 @@ async function pullCommand(options) {
365
852
  if (skipped > 0) {
366
853
  console.log(chalk2.yellow(`\u26A0\uFE0F Skipped ${skipped} script(s) with local changes`));
367
854
  }
368
- const metadataPath = join3(projectRoot, ".cure-kode", "scripts.json");
855
+ const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
369
856
  const metadata = scripts.map((s) => ({
370
857
  id: s.id,
371
858
  slug: s.slug,
@@ -375,7 +862,7 @@ async function pullCommand(options) {
375
862
  version: s.current_version,
376
863
  loadOrder: s.load_order
377
864
  }));
378
- writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2));
865
+ writeFileSync4(metadataPath, JSON.stringify(metadata, null, 2));
379
866
  } catch (error) {
380
867
  spinner.fail("Failed to pull scripts");
381
868
  console.error(chalk2.red("\nError:"), error);
@@ -385,8 +872,8 @@ async function pullCommand(options) {
385
872
  // src/commands/push.ts
386
873
  import chalk3 from "chalk";
387
874
  import ora3 from "ora";
388
- import { readFileSync as readFileSync2, existsSync as existsSync4, readdirSync } from "fs";
389
- import { join as join4, basename, extname } from "path";
875
+ import { readFileSync as readFileSync4, existsSync as existsSync5, readdirSync } from "fs";
876
+ import { join as join5, basename, extname } from "path";
390
877
  async function pushCommand(options) {
391
878
  const projectRoot = findProjectRoot();
392
879
  if (!projectRoot) {
@@ -400,16 +887,16 @@ async function pushCommand(options) {
400
887
  return;
401
888
  }
402
889
  const scriptsDir = getScriptsDir(projectRoot, config);
403
- if (!existsSync4(scriptsDir)) {
890
+ if (!existsSync5(scriptsDir)) {
404
891
  console.log(chalk3.yellow("\u26A0\uFE0F Scripts directory not found."));
405
892
  console.log(chalk3.dim(` Expected: ${scriptsDir}`));
406
893
  console.log(chalk3.dim(' Run "kode pull" first or create scripts manually.'));
407
894
  return;
408
895
  }
409
- const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
896
+ const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
410
897
  let metadata = [];
411
- if (existsSync4(metadataPath)) {
412
- metadata = JSON.parse(readFileSync2(metadataPath, "utf-8"));
898
+ if (existsSync5(metadataPath)) {
899
+ metadata = JSON.parse(readFileSync4(metadataPath, "utf-8"));
413
900
  }
414
901
  const files = readdirSync(scriptsDir).filter(
415
902
  (f) => f.endsWith(".js") || f.endsWith(".css")
@@ -440,8 +927,8 @@ async function pushCommand(options) {
440
927
  spinner.stop();
441
928
  console.log();
442
929
  for (const file of filesToPush) {
443
- const filePath = join4(scriptsDir, file);
444
- const content = readFileSync2(filePath, "utf-8");
930
+ const filePath = join5(scriptsDir, file);
931
+ const content = readFileSync4(filePath, "utf-8");
445
932
  const slug = basename(file, extname(file));
446
933
  const type = extname(file) === ".js" ? "javascript" : "css";
447
934
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
@@ -507,8 +994,8 @@ async function pushCommand(options) {
507
994
  // src/commands/watch.ts
508
995
  import chalk4 from "chalk";
509
996
  import chokidar from "chokidar";
510
- import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
511
- import { join as join5, basename as basename2, extname as extname2 } from "path";
997
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
998
+ import { join as join6, basename as basename2, extname as extname2 } from "path";
512
999
  async function watchCommand(options) {
513
1000
  const projectRoot = findProjectRoot();
514
1001
  if (!projectRoot) {
@@ -522,7 +1009,7 @@ async function watchCommand(options) {
522
1009
  return;
523
1010
  }
524
1011
  const scriptsDir = getScriptsDir(projectRoot, config);
525
- if (!existsSync5(scriptsDir)) {
1012
+ if (!existsSync6(scriptsDir)) {
526
1013
  console.log(chalk4.yellow("\u26A0\uFE0F Scripts directory not found."));
527
1014
  console.log(chalk4.dim(` Expected: ${scriptsDir}`));
528
1015
  console.log(chalk4.dim(' Run "kode pull" first.'));
@@ -538,10 +1025,10 @@ async function watchCommand(options) {
538
1025
  console.log();
539
1026
  console.log(chalk4.dim("Press Ctrl+C to stop.\n"));
540
1027
  const client = createApiClient(config);
541
- const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
1028
+ const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
542
1029
  let metadata = [];
543
- if (existsSync5(metadataPath)) {
544
- metadata = JSON.parse(readFileSync3(metadataPath, "utf-8"));
1030
+ if (existsSync6(metadataPath)) {
1031
+ metadata = JSON.parse(readFileSync5(metadataPath, "utf-8"));
545
1032
  }
546
1033
  let remoteScripts = [];
547
1034
  try {
@@ -563,7 +1050,7 @@ async function watchCommand(options) {
563
1050
  const timeout = setTimeout(async () => {
564
1051
  pendingChanges.delete(filePath);
565
1052
  try {
566
- const content = readFileSync3(filePath, "utf-8");
1053
+ const content = readFileSync5(filePath, "utf-8");
567
1054
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
568
1055
  const localMeta = metadata.find((m) => m.slug === slug);
569
1056
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
@@ -625,7 +1112,7 @@ async function watchCommand(options) {
625
1112
  }, DEBOUNCE_MS);
626
1113
  pendingChanges.set(filePath, timeout);
627
1114
  };
628
- const watcher = chokidar.watch(join5(scriptsDir, "**/*.{js,css}"), {
1115
+ const watcher = chokidar.watch(join6(scriptsDir, "**/*.{js,css}"), {
629
1116
  persistent: true,
630
1117
  ignoreInitial: true,
631
1118
  awaitWriteFinish: {
@@ -708,6 +1195,126 @@ async function deployCommand(environment, options) {
708
1195
  // src/commands/html.ts
709
1196
  import chalk6 from "chalk";
710
1197
  import ora5 from "ora";
1198
+
1199
+ // src/lib/page-cache.ts
1200
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync } from "fs";
1201
+ import { join as join7, basename as basename3 } from "path";
1202
+ var PROJECT_CONFIG_DIR3 = ".cure-kode";
1203
+ var PAGES_DIR = "pages";
1204
+ function urlToSlug(url) {
1205
+ try {
1206
+ const parsed = new URL(url);
1207
+ let slug = parsed.pathname.replace(/^\//, "").replace(/\//g, "--") || "home";
1208
+ slug = slug.replace(/--$/, "");
1209
+ return slug.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
1210
+ } catch {
1211
+ return "page";
1212
+ }
1213
+ }
1214
+ function getPagesDir(projectRoot) {
1215
+ return join7(projectRoot, PROJECT_CONFIG_DIR3, PAGES_DIR);
1216
+ }
1217
+ function getPageCachePath(projectRoot, urlOrSlug) {
1218
+ const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
1219
+ return join7(getPagesDir(projectRoot), `${slug}.json`);
1220
+ }
1221
+ function ensurePagesDir(projectRoot) {
1222
+ const pagesDir = getPagesDir(projectRoot);
1223
+ if (!existsSync7(pagesDir)) {
1224
+ mkdirSync4(pagesDir, { recursive: true });
1225
+ }
1226
+ }
1227
+ function savePageContext(projectRoot, context) {
1228
+ ensurePagesDir(projectRoot);
1229
+ const slug = urlToSlug(context.url);
1230
+ const cachePath = getPageCachePath(projectRoot, slug);
1231
+ writeFileSync5(cachePath, JSON.stringify(context, null, 2), "utf-8");
1232
+ return slug;
1233
+ }
1234
+ function readPageContext(projectRoot, urlOrSlug) {
1235
+ const cachePath = getPageCachePath(projectRoot, urlOrSlug);
1236
+ if (!existsSync7(cachePath)) {
1237
+ return null;
1238
+ }
1239
+ try {
1240
+ const content = readFileSync6(cachePath, "utf-8");
1241
+ return JSON.parse(content);
1242
+ } catch {
1243
+ return null;
1244
+ }
1245
+ }
1246
+ function deletePageContext(projectRoot, urlOrSlug) {
1247
+ const cachePath = getPageCachePath(projectRoot, urlOrSlug);
1248
+ if (existsSync7(cachePath)) {
1249
+ unlinkSync(cachePath);
1250
+ return true;
1251
+ }
1252
+ return false;
1253
+ }
1254
+ function listCachedPages(projectRoot) {
1255
+ const pagesDir = getPagesDir(projectRoot);
1256
+ if (!existsSync7(pagesDir)) {
1257
+ return [];
1258
+ }
1259
+ const files = readdirSync2(pagesDir).filter((f) => f.endsWith(".json"));
1260
+ const pages = [];
1261
+ for (const file of files) {
1262
+ const slug = basename3(file, ".json");
1263
+ const context = readPageContext(projectRoot, slug);
1264
+ if (context) {
1265
+ pages.push({
1266
+ slug,
1267
+ url: context.url,
1268
+ title: context.title,
1269
+ extractedAt: context.extractedAt,
1270
+ sectionCount: context.sections.length,
1271
+ cmsCollectionCount: context.cmsPatterns.length
1272
+ });
1273
+ }
1274
+ }
1275
+ pages.sort((a, b) => new Date(b.extractedAt).getTime() - new Date(a.extractedAt).getTime());
1276
+ return pages;
1277
+ }
1278
+ function toCachedContext(structure) {
1279
+ return {
1280
+ url: structure.url,
1281
+ title: structure.title,
1282
+ sections: structure.sections.map((s) => ({
1283
+ id: s.id,
1284
+ className: s.className,
1285
+ heading: s.heading,
1286
+ textSample: s.textSample,
1287
+ hasCms: s.hasCms,
1288
+ hasForm: s.hasForm
1289
+ })),
1290
+ navigation: structure.navigation.map((n) => ({
1291
+ type: n.type,
1292
+ items: n.items.map((i) => ({ text: i.text, href: i.href }))
1293
+ })),
1294
+ ctas: structure.ctas.map((c) => ({
1295
+ text: c.text,
1296
+ href: c.href,
1297
+ location: c.location
1298
+ })),
1299
+ forms: structure.forms.map((f) => ({
1300
+ name: f.name || f.id,
1301
+ fields: f.fields.map((field) => ({
1302
+ type: field.type,
1303
+ label: field.label || field.name,
1304
+ required: field.required
1305
+ })),
1306
+ submitText: f.submitText
1307
+ })),
1308
+ cmsPatterns: structure.cmsPatterns,
1309
+ headings: structure.headings.map((h) => ({
1310
+ level: h.level,
1311
+ text: h.text
1312
+ })),
1313
+ extractedAt: structure.extractedAt
1314
+ };
1315
+ }
1316
+
1317
+ // src/commands/html.ts
711
1318
  async function htmlCommand(url, options) {
712
1319
  const projectRoot = findProjectRoot();
713
1320
  if (!projectRoot) {
@@ -727,9 +1334,54 @@ async function htmlCommand(url, options) {
727
1334
  console.log(chalk6.red("\u274C Invalid URL."));
728
1335
  return;
729
1336
  }
1337
+ if (options?.save && !options?.force) {
1338
+ const slug = urlToSlug(parsedUrl.toString());
1339
+ const cached = readPageContext(projectRoot, slug);
1340
+ if (cached) {
1341
+ console.log(chalk6.dim(`Using cached version from ${cached.extractedAt}`));
1342
+ console.log(chalk6.dim(`Use --force to refresh`));
1343
+ console.log();
1344
+ printPageContext(cached);
1345
+ return;
1346
+ }
1347
+ }
730
1348
  const spinner = ora5(`Fetching ${parsedUrl.hostname}${parsedUrl.pathname}...`).start();
731
1349
  try {
732
1350
  const client = createApiClient(config);
1351
+ if (options?.save) {
1352
+ const apiUrl = config.apiUrl || "https://app.cure.no";
1353
+ const response = await fetch(`${apiUrl}/api/cdn/page-context`, {
1354
+ method: "POST",
1355
+ headers: {
1356
+ "Content-Type": "application/json",
1357
+ "X-API-Key": config.apiKey
1358
+ },
1359
+ body: JSON.stringify({ url: parsedUrl.toString() })
1360
+ });
1361
+ if (!response.ok) {
1362
+ const error = await response.json().catch(() => ({ error: response.statusText }));
1363
+ throw new Error(error.error || `HTTP ${response.status}`);
1364
+ }
1365
+ const structure = await response.json();
1366
+ spinner.succeed("Page structure extracted");
1367
+ const cachedContext = toCachedContext(structure);
1368
+ const slug = savePageContext(projectRoot, cachedContext);
1369
+ console.log(chalk6.dim(`Saved to .cure-kode/pages/${slug}.json`));
1370
+ const contextPage = {
1371
+ slug,
1372
+ url: structure.url,
1373
+ title: structure.title,
1374
+ sections: structure.sections.slice(0, 5).map((s) => s.heading || s.id || s.className?.split(" ")[0] || "section").join(", "),
1375
+ ctas: structure.ctas.length > 0 ? structure.ctas.slice(0, 3).map((c) => `"${c.text}"`).join(", ") : void 0,
1376
+ forms: structure.forms.length > 0 ? structure.forms.map((f) => f.name || "form").join(", ") : void 0,
1377
+ cms: structure.cmsPatterns.length > 0 ? structure.cmsPatterns.map((c) => `${c.containerClass} (${c.itemCount})`).join(", ") : void 0
1378
+ };
1379
+ upsertPage(projectRoot, contextPage, "kode html --save");
1380
+ console.log(chalk6.dim(`Updated .cure-kode/context.md`));
1381
+ console.log();
1382
+ printPageContext(cachedContext);
1383
+ return;
1384
+ }
733
1385
  const result = await client.fetchHtml(parsedUrl.toString());
734
1386
  spinner.succeed("HTML fetched");
735
1387
  if (options?.json) {
@@ -746,7 +1398,7 @@ async function htmlCommand(url, options) {
746
1398
  } else {
747
1399
  console.log(chalk6.yellow("\u26A0\uFE0F Cure Kode not found"));
748
1400
  console.log(chalk6.dim(" Add this to your Webflow site:"));
749
- console.log(chalk6.cyan(` <script src="https://cure-app-v2-production.up.railway.app/api/cdn/${config.siteSlug}/init.js"></script>`));
1401
+ console.log(chalk6.cyan(` <script src="https://app.cure.no/api/cdn/${config.siteSlug}/init.js"></script>`));
750
1402
  }
751
1403
  console.log();
752
1404
  console.log(chalk6.dim("\u2500".repeat(50)));
@@ -812,12 +1464,80 @@ async function htmlCommand(url, options) {
812
1464
  console.error(chalk6.red("\nError:"), error.message || error);
813
1465
  }
814
1466
  }
1467
+ function printPageContext(context) {
1468
+ console.log(chalk6.bold(context.title || context.url));
1469
+ console.log(chalk6.dim(context.url));
1470
+ console.log();
1471
+ if (context.sections.length > 0) {
1472
+ console.log(chalk6.bold("Sections"));
1473
+ for (const section of context.sections.slice(0, 8)) {
1474
+ const name = section.heading || section.id || section.className?.split(" ")[0] || "section";
1475
+ const badges = [];
1476
+ if (section.hasCms) badges.push(chalk6.cyan("CMS"));
1477
+ if (section.hasForm) badges.push(chalk6.yellow("Form"));
1478
+ const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
1479
+ console.log(` \u2022 ${name}${badgeStr}`);
1480
+ if (section.textSample) {
1481
+ console.log(chalk6.dim(` "${section.textSample.slice(0, 60)}..."`));
1482
+ }
1483
+ }
1484
+ if (context.sections.length > 8) {
1485
+ console.log(chalk6.dim(` ... and ${context.sections.length - 8} more`));
1486
+ }
1487
+ console.log();
1488
+ }
1489
+ if (context.ctas.length > 0) {
1490
+ console.log(chalk6.bold("CTAs"));
1491
+ for (const cta of context.ctas.slice(0, 6)) {
1492
+ console.log(` \u2022 "${cta.text}" ${chalk6.dim(`(${cta.location})`)}`);
1493
+ }
1494
+ if (context.ctas.length > 6) {
1495
+ console.log(chalk6.dim(` ... and ${context.ctas.length - 6} more`));
1496
+ }
1497
+ console.log();
1498
+ }
1499
+ if (context.forms.length > 0) {
1500
+ console.log(chalk6.bold("Forms"));
1501
+ for (const form of context.forms) {
1502
+ const fields = form.fields.map((f) => f.label || f.type).slice(0, 4).join(", ");
1503
+ const more = form.fields.length > 4 ? ` +${form.fields.length - 4}` : "";
1504
+ console.log(` \u2022 ${form.name || "form"}: ${fields}${more}`);
1505
+ }
1506
+ console.log();
1507
+ }
1508
+ if (context.cmsPatterns.length > 0) {
1509
+ console.log(chalk6.bold("CMS Collections"));
1510
+ for (const cms of context.cmsPatterns) {
1511
+ console.log(` \u2022 ${cms.containerClass}: ${chalk6.cyan(`${cms.itemCount} items`)}`);
1512
+ if (cms.templateFields.length > 0) {
1513
+ console.log(chalk6.dim(` Fields: ${cms.templateFields.slice(0, 5).join(", ")}`));
1514
+ }
1515
+ }
1516
+ console.log();
1517
+ }
1518
+ if (context.navigation.length > 0) {
1519
+ console.log(chalk6.bold("Navigation"));
1520
+ for (const nav of context.navigation) {
1521
+ const items = nav.items.slice(0, 5).map((i) => i.text).join(", ");
1522
+ const more = nav.items.length > 5 ? ` +${nav.items.length - 5}` : "";
1523
+ console.log(` \u2022 ${nav.type}: ${items}${more}`);
1524
+ }
1525
+ console.log();
1526
+ }
1527
+ if (context.notes && context.notes.length > 0) {
1528
+ console.log(chalk6.bold("Notes"));
1529
+ for (const note of context.notes) {
1530
+ console.log(` \u2022 ${note}`);
1531
+ }
1532
+ console.log();
1533
+ }
1534
+ }
815
1535
 
816
1536
  // src/commands/status.ts
817
1537
  import chalk7 from "chalk";
818
1538
  import ora6 from "ora";
819
- import { readFileSync as readFileSync4, existsSync as existsSync6, readdirSync as readdirSync2, statSync } from "fs";
820
- import { join as join6, basename as basename3, extname as extname3 } from "path";
1539
+ import { readFileSync as readFileSync7, existsSync as existsSync8, readdirSync as readdirSync3, statSync } from "fs";
1540
+ import { join as join8, basename as basename4, extname as extname3 } from "path";
821
1541
  async function statusCommand(options) {
822
1542
  const projectRoot = findProjectRoot();
823
1543
  if (!projectRoot) {
@@ -873,12 +1593,12 @@ async function statusCommand(options) {
873
1593
  console.log(chalk7.bold("Scripts"));
874
1594
  console.log();
875
1595
  const scriptsDir = getScriptsDir(projectRoot, config);
876
- const localFiles = existsSync6(scriptsDir) ? readdirSync2(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
1596
+ const localFiles = existsSync8(scriptsDir) ? readdirSync3(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
877
1597
  const localBySlug = /* @__PURE__ */ new Map();
878
1598
  for (const file of localFiles) {
879
- const slug = basename3(file, extname3(file));
880
- const filePath = join6(scriptsDir, file);
881
- const content = readFileSync4(filePath, "utf-8");
1599
+ const slug = basename4(file, extname3(file));
1600
+ const filePath = join8(scriptsDir, file);
1601
+ const content = readFileSync7(filePath, "utf-8");
882
1602
  const stats = statSync(filePath);
883
1603
  localBySlug.set(slug, { file, content, modified: stats.mtime });
884
1604
  }
@@ -959,6 +1679,168 @@ function formatDate(dateStr) {
959
1679
  });
960
1680
  }
961
1681
 
1682
+ // src/commands/context.ts
1683
+ import chalk8 from "chalk";
1684
+ import ora7 from "ora";
1685
+ import { spawn } from "child_process";
1686
+ async function contextCommand(options) {
1687
+ const projectRoot = findProjectRoot();
1688
+ if (!projectRoot) {
1689
+ console.log(chalk8.red("Error: Not in a Cure Kode project."));
1690
+ console.log(chalk8.dim('Run "kode init" first.'));
1691
+ return;
1692
+ }
1693
+ const config = getProjectConfig(projectRoot);
1694
+ if (!config) {
1695
+ console.log(chalk8.red("Error: Invalid project configuration."));
1696
+ return;
1697
+ }
1698
+ const contextPath = getContextPath(projectRoot);
1699
+ const context = readContext(projectRoot);
1700
+ if (options.refresh) {
1701
+ const spinner = ora7("Refreshing context from server...").start();
1702
+ try {
1703
+ const siteResponse = await fetch(
1704
+ `https://app.cure.no/api/cdn/sites/${config.siteId}`,
1705
+ {
1706
+ headers: { "X-API-Key": config.apiKey }
1707
+ }
1708
+ );
1709
+ const scriptsResponse = await fetch(
1710
+ `https://app.cure.no/api/cdn/sites/${config.siteId}/scripts`,
1711
+ {
1712
+ headers: { "X-API-Key": config.apiKey }
1713
+ }
1714
+ );
1715
+ if (!siteResponse.ok || !scriptsResponse.ok) {
1716
+ spinner.fail("Failed to fetch from server");
1717
+ return;
1718
+ }
1719
+ const siteInfo = await siteResponse.json();
1720
+ const scripts = await scriptsResponse.json();
1721
+ const existingScripts = context?.scripts || [];
1722
+ const existingNotes = context?.notes || [];
1723
+ const existingSessions = context?.sessions || [];
1724
+ const existingPages = context?.pages || [];
1725
+ const newContext = {
1726
+ site: {
1727
+ name: config.siteName,
1728
+ slug: config.siteSlug,
1729
+ domain: siteInfo?.domain || void 0,
1730
+ stagingDomain: siteInfo?.staging_domain || void 0
1731
+ },
1732
+ scripts: scripts.map((s) => {
1733
+ const existing = existingScripts.find((e) => e.slug === s.slug);
1734
+ return {
1735
+ slug: s.slug,
1736
+ type: s.type,
1737
+ scope: s.scope,
1738
+ purpose: existing?.purpose
1739
+ };
1740
+ }),
1741
+ pages: existingPages,
1742
+ notes: existingNotes,
1743
+ sessions: existingSessions,
1744
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1745
+ updatedBy: "kode context --refresh"
1746
+ };
1747
+ writeContext(projectRoot, newContext);
1748
+ spinner.succeed("Context refreshed");
1749
+ console.log(chalk8.dim(`Updated: ${contextPath}`));
1750
+ return;
1751
+ } catch (error) {
1752
+ spinner.fail("Failed to refresh context");
1753
+ console.log(chalk8.red(error instanceof Error ? error.message : "Unknown error"));
1754
+ return;
1755
+ }
1756
+ }
1757
+ if (options.edit) {
1758
+ if (!context) {
1759
+ console.log(chalk8.red("Error: No context file found."));
1760
+ console.log(chalk8.dim('Run "kode init" or "kode context --refresh" first.'));
1761
+ return;
1762
+ }
1763
+ const editor = process.env.EDITOR || process.env.VISUAL || "vim";
1764
+ console.log(chalk8.dim(`Opening ${contextPath} in ${editor}...`));
1765
+ const child = spawn(editor, [contextPath], {
1766
+ stdio: "inherit"
1767
+ });
1768
+ child.on("error", (err) => {
1769
+ console.log(chalk8.red(`Failed to open editor: ${err.message}`));
1770
+ console.log(chalk8.dim(`You can edit the file manually: ${contextPath}`));
1771
+ });
1772
+ return;
1773
+ }
1774
+ if (options.json) {
1775
+ if (!context) {
1776
+ console.log(JSON.stringify({ error: "No context file found" }));
1777
+ return;
1778
+ }
1779
+ console.log(JSON.stringify(context, null, 2));
1780
+ return;
1781
+ }
1782
+ if (!context) {
1783
+ console.log(chalk8.yellow("No context file found."));
1784
+ console.log(chalk8.dim('Run "kode context --refresh" to create one.'));
1785
+ return;
1786
+ }
1787
+ console.log(chalk8.bold(`Context: ${context.site.name}`));
1788
+ console.log(chalk8.dim(`Last updated: ${context.lastUpdated}`));
1789
+ console.log(chalk8.dim(`Updated by: ${context.updatedBy}`));
1790
+ console.log();
1791
+ if (context.site.domain || context.site.stagingDomain) {
1792
+ if (context.site.domain) {
1793
+ console.log(chalk8.dim("Domain: ") + context.site.domain);
1794
+ }
1795
+ if (context.site.stagingDomain) {
1796
+ console.log(chalk8.dim("Staging: ") + context.site.stagingDomain);
1797
+ }
1798
+ console.log();
1799
+ }
1800
+ console.log(chalk8.bold("Scripts:"));
1801
+ if (context.scripts.length === 0) {
1802
+ console.log(chalk8.dim(" (no scripts)"));
1803
+ } else {
1804
+ for (const script of context.scripts) {
1805
+ const typeIcon = script.type === "css" ? "\u{1F3A8}" : "\u{1F4DC}";
1806
+ const scopeLabel = script.scope === "global" ? chalk8.cyan("global") : chalk8.yellow("page");
1807
+ const purpose = script.purpose ? chalk8.dim(` - ${script.purpose}`) : "";
1808
+ console.log(` ${typeIcon} ${script.slug} [${scopeLabel}]${purpose}`);
1809
+ }
1810
+ }
1811
+ console.log();
1812
+ console.log(chalk8.bold("Notes:"));
1813
+ if (context.notes.length === 0 || context.notes.length === 3 && context.notes[0].startsWith("(HTML")) {
1814
+ console.log(chalk8.dim(" (no notes yet)"));
1815
+ } else {
1816
+ for (const note of context.notes) {
1817
+ if (!note.startsWith("(")) {
1818
+ console.log(` \u2022 ${note}`);
1819
+ }
1820
+ }
1821
+ }
1822
+ console.log();
1823
+ console.log(chalk8.bold("Recent Sessions:"));
1824
+ if (context.sessions.length === 0) {
1825
+ console.log(chalk8.dim(" (no sessions recorded)"));
1826
+ } else {
1827
+ const recentSessions = context.sessions.slice(0, 3);
1828
+ for (const session of recentSessions) {
1829
+ console.log(` ${chalk8.dim(session.date)} ${session.agent}`);
1830
+ console.log(` ${session.task}`);
1831
+ if (session.changes.length > 0) {
1832
+ console.log(chalk8.dim(` ${session.changes.length} change(s)`));
1833
+ }
1834
+ }
1835
+ if (context.sessions.length > 3) {
1836
+ console.log(chalk8.dim(` ... and ${context.sessions.length - 3} more sessions`));
1837
+ }
1838
+ }
1839
+ console.log();
1840
+ console.log(chalk8.dim(`Context file: ${contextPath}`));
1841
+ console.log(chalk8.dim("Use --edit to open in editor, --refresh to update from server"));
1842
+ }
1843
+
962
1844
  export {
963
1845
  findProjectRoot,
964
1846
  getProjectConfig,
@@ -967,6 +1849,16 @@ export {
967
1849
  getApiKey,
968
1850
  setGlobalConfig,
969
1851
  getScriptsDir,
1852
+ getContextPath,
1853
+ parseContext,
1854
+ serializeContext,
1855
+ readContext,
1856
+ writeContext,
1857
+ appendNote,
1858
+ addSession,
1859
+ updateScriptPurpose,
1860
+ generateInitialContext,
1861
+ generateClaudeMd,
970
1862
  initCommand,
971
1863
  KodeApiError,
972
1864
  KodeApiClient,
@@ -975,6 +1867,10 @@ export {
975
1867
  pushCommand,
976
1868
  watchCommand,
977
1869
  deployCommand,
1870
+ readPageContext,
1871
+ deletePageContext,
1872
+ listCachedPages,
978
1873
  htmlCommand,
979
- statusCommand
1874
+ statusCommand,
1875
+ contextCommand
980
1876
  };