@curenorway/kode-cli 1.0.0 → 1.0.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.
@@ -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 } 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,52 @@ 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 claudeMdContent = generateClaudeMd(config.siteName, config.scriptsDir || "scripts");
582
+ writeFileSync3(join3(cwd, "CLAUDE.md"), claudeMdContent);
583
+ const contextMdContent = generateInitialContext(config, scripts, siteInfo);
584
+ writeFileSync3(join3(cwd, ".cure-kode", "context.md"), contextMdContent);
585
+ spinner.succeed("AI context files generated");
155
586
  console.log(chalk.green("\n\u2705 Cure Kode initialized successfully!\n"));
156
587
  console.log(chalk.dim("Project structure:"));
157
588
  console.log(chalk.dim(` ${cwd}/`));
589
+ console.log(chalk.dim(` \u251C\u2500\u2500 CLAUDE.md (AI agent instructions)`));
158
590
  console.log(chalk.dim(` \u251C\u2500\u2500 .cure-kode/`));
159
591
  console.log(chalk.dim(` \u2502 \u251C\u2500\u2500 config.json (your configuration)`));
592
+ console.log(chalk.dim(` \u2502 \u251C\u2500\u2500 context.md (dynamic AI context)`));
160
593
  console.log(chalk.dim(` \u2502 \u2514\u2500\u2500 .gitignore (protects API key)`));
161
594
  console.log(chalk.dim(` \u2514\u2500\u2500 ${answers.scriptsDir}/`));
162
595
  console.log(chalk.dim(` \u2514\u2500\u2500 (your scripts will be here)`));
@@ -294,8 +727,8 @@ function createApiClient(config) {
294
727
  // src/commands/pull.ts
295
728
  import chalk2 from "chalk";
296
729
  import ora2 from "ora";
297
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
298
- import { join as join3 } from "path";
730
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
731
+ import { join as join4 } from "path";
299
732
  async function pullCommand(options) {
300
733
  const projectRoot = findProjectRoot();
301
734
  if (!projectRoot) {
@@ -319,7 +752,7 @@ async function pullCommand(options) {
319
752
  }
320
753
  spinner.succeed(`Found ${scripts.length} script(s)`);
321
754
  const scriptsDir = getScriptsDir(projectRoot, config);
322
- if (!existsSync3(scriptsDir)) {
755
+ if (!existsSync4(scriptsDir)) {
323
756
  mkdirSync3(scriptsDir, { recursive: true });
324
757
  }
325
758
  const scriptsToPull = options.script ? scripts.filter((s) => s.slug === options.script || s.name === options.script) : scripts;
@@ -338,8 +771,8 @@ async function pullCommand(options) {
338
771
  for (const script of scriptsToPull) {
339
772
  const ext = script.type === "javascript" ? "js" : "css";
340
773
  const fileName = `${script.slug}.${ext}`;
341
- const filePath = join3(scriptsDir, fileName);
342
- if (existsSync3(filePath) && !options.force) {
774
+ const filePath = join4(scriptsDir, fileName);
775
+ if (existsSync4(filePath) && !options.force) {
343
776
  const localContent = await import("fs").then(
344
777
  (fs) => fs.readFileSync(filePath, "utf-8")
345
778
  );
@@ -351,7 +784,7 @@ async function pullCommand(options) {
351
784
  continue;
352
785
  }
353
786
  }
354
- writeFileSync2(filePath, script.content);
787
+ writeFileSync4(filePath, script.content);
355
788
  const scopeTag = script.scope === "global" ? chalk2.blue("[G]") : chalk2.magenta("[P]");
356
789
  console.log(
357
790
  chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${script.current_version} ${scopeTag}`)
@@ -365,7 +798,7 @@ async function pullCommand(options) {
365
798
  if (skipped > 0) {
366
799
  console.log(chalk2.yellow(`\u26A0\uFE0F Skipped ${skipped} script(s) with local changes`));
367
800
  }
368
- const metadataPath = join3(projectRoot, ".cure-kode", "scripts.json");
801
+ const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
369
802
  const metadata = scripts.map((s) => ({
370
803
  id: s.id,
371
804
  slug: s.slug,
@@ -375,7 +808,7 @@ async function pullCommand(options) {
375
808
  version: s.current_version,
376
809
  loadOrder: s.load_order
377
810
  }));
378
- writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2));
811
+ writeFileSync4(metadataPath, JSON.stringify(metadata, null, 2));
379
812
  } catch (error) {
380
813
  spinner.fail("Failed to pull scripts");
381
814
  console.error(chalk2.red("\nError:"), error);
@@ -385,8 +818,8 @@ async function pullCommand(options) {
385
818
  // src/commands/push.ts
386
819
  import chalk3 from "chalk";
387
820
  import ora3 from "ora";
388
- import { readFileSync as readFileSync2, existsSync as existsSync4, readdirSync } from "fs";
389
- import { join as join4, basename, extname } from "path";
821
+ import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync } from "fs";
822
+ import { join as join5, basename, extname } from "path";
390
823
  async function pushCommand(options) {
391
824
  const projectRoot = findProjectRoot();
392
825
  if (!projectRoot) {
@@ -400,16 +833,16 @@ async function pushCommand(options) {
400
833
  return;
401
834
  }
402
835
  const scriptsDir = getScriptsDir(projectRoot, config);
403
- if (!existsSync4(scriptsDir)) {
836
+ if (!existsSync5(scriptsDir)) {
404
837
  console.log(chalk3.yellow("\u26A0\uFE0F Scripts directory not found."));
405
838
  console.log(chalk3.dim(` Expected: ${scriptsDir}`));
406
839
  console.log(chalk3.dim(' Run "kode pull" first or create scripts manually.'));
407
840
  return;
408
841
  }
409
- const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
842
+ const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
410
843
  let metadata = [];
411
- if (existsSync4(metadataPath)) {
412
- metadata = JSON.parse(readFileSync2(metadataPath, "utf-8"));
844
+ if (existsSync5(metadataPath)) {
845
+ metadata = JSON.parse(readFileSync3(metadataPath, "utf-8"));
413
846
  }
414
847
  const files = readdirSync(scriptsDir).filter(
415
848
  (f) => f.endsWith(".js") || f.endsWith(".css")
@@ -440,8 +873,8 @@ async function pushCommand(options) {
440
873
  spinner.stop();
441
874
  console.log();
442
875
  for (const file of filesToPush) {
443
- const filePath = join4(scriptsDir, file);
444
- const content = readFileSync2(filePath, "utf-8");
876
+ const filePath = join5(scriptsDir, file);
877
+ const content = readFileSync3(filePath, "utf-8");
445
878
  const slug = basename(file, extname(file));
446
879
  const type = extname(file) === ".js" ? "javascript" : "css";
447
880
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
@@ -507,8 +940,8 @@ async function pushCommand(options) {
507
940
  // src/commands/watch.ts
508
941
  import chalk4 from "chalk";
509
942
  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";
943
+ import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
944
+ import { join as join6, basename as basename2, extname as extname2 } from "path";
512
945
  async function watchCommand(options) {
513
946
  const projectRoot = findProjectRoot();
514
947
  if (!projectRoot) {
@@ -522,7 +955,7 @@ async function watchCommand(options) {
522
955
  return;
523
956
  }
524
957
  const scriptsDir = getScriptsDir(projectRoot, config);
525
- if (!existsSync5(scriptsDir)) {
958
+ if (!existsSync6(scriptsDir)) {
526
959
  console.log(chalk4.yellow("\u26A0\uFE0F Scripts directory not found."));
527
960
  console.log(chalk4.dim(` Expected: ${scriptsDir}`));
528
961
  console.log(chalk4.dim(' Run "kode pull" first.'));
@@ -538,10 +971,10 @@ async function watchCommand(options) {
538
971
  console.log();
539
972
  console.log(chalk4.dim("Press Ctrl+C to stop.\n"));
540
973
  const client = createApiClient(config);
541
- const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
974
+ const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
542
975
  let metadata = [];
543
- if (existsSync5(metadataPath)) {
544
- metadata = JSON.parse(readFileSync3(metadataPath, "utf-8"));
976
+ if (existsSync6(metadataPath)) {
977
+ metadata = JSON.parse(readFileSync4(metadataPath, "utf-8"));
545
978
  }
546
979
  let remoteScripts = [];
547
980
  try {
@@ -563,7 +996,7 @@ async function watchCommand(options) {
563
996
  const timeout = setTimeout(async () => {
564
997
  pendingChanges.delete(filePath);
565
998
  try {
566
- const content = readFileSync3(filePath, "utf-8");
999
+ const content = readFileSync4(filePath, "utf-8");
567
1000
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
568
1001
  const localMeta = metadata.find((m) => m.slug === slug);
569
1002
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
@@ -625,7 +1058,7 @@ async function watchCommand(options) {
625
1058
  }, DEBOUNCE_MS);
626
1059
  pendingChanges.set(filePath, timeout);
627
1060
  };
628
- const watcher = chokidar.watch(join5(scriptsDir, "**/*.{js,css}"), {
1061
+ const watcher = chokidar.watch(join6(scriptsDir, "**/*.{js,css}"), {
629
1062
  persistent: true,
630
1063
  ignoreInitial: true,
631
1064
  awaitWriteFinish: {
@@ -708,6 +1141,126 @@ async function deployCommand(environment, options) {
708
1141
  // src/commands/html.ts
709
1142
  import chalk6 from "chalk";
710
1143
  import ora5 from "ora";
1144
+
1145
+ // src/lib/page-cache.ts
1146
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync } from "fs";
1147
+ import { join as join7, basename as basename3 } from "path";
1148
+ var PROJECT_CONFIG_DIR3 = ".cure-kode";
1149
+ var PAGES_DIR = "pages";
1150
+ function urlToSlug(url) {
1151
+ try {
1152
+ const parsed = new URL(url);
1153
+ let slug = parsed.pathname.replace(/^\//, "").replace(/\//g, "--") || "home";
1154
+ slug = slug.replace(/--$/, "");
1155
+ return slug.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
1156
+ } catch {
1157
+ return "page";
1158
+ }
1159
+ }
1160
+ function getPagesDir(projectRoot) {
1161
+ return join7(projectRoot, PROJECT_CONFIG_DIR3, PAGES_DIR);
1162
+ }
1163
+ function getPageCachePath(projectRoot, urlOrSlug) {
1164
+ const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
1165
+ return join7(getPagesDir(projectRoot), `${slug}.json`);
1166
+ }
1167
+ function ensurePagesDir(projectRoot) {
1168
+ const pagesDir = getPagesDir(projectRoot);
1169
+ if (!existsSync7(pagesDir)) {
1170
+ mkdirSync4(pagesDir, { recursive: true });
1171
+ }
1172
+ }
1173
+ function savePageContext(projectRoot, context) {
1174
+ ensurePagesDir(projectRoot);
1175
+ const slug = urlToSlug(context.url);
1176
+ const cachePath = getPageCachePath(projectRoot, slug);
1177
+ writeFileSync5(cachePath, JSON.stringify(context, null, 2), "utf-8");
1178
+ return slug;
1179
+ }
1180
+ function readPageContext(projectRoot, urlOrSlug) {
1181
+ const cachePath = getPageCachePath(projectRoot, urlOrSlug);
1182
+ if (!existsSync7(cachePath)) {
1183
+ return null;
1184
+ }
1185
+ try {
1186
+ const content = readFileSync5(cachePath, "utf-8");
1187
+ return JSON.parse(content);
1188
+ } catch {
1189
+ return null;
1190
+ }
1191
+ }
1192
+ function deletePageContext(projectRoot, urlOrSlug) {
1193
+ const cachePath = getPageCachePath(projectRoot, urlOrSlug);
1194
+ if (existsSync7(cachePath)) {
1195
+ unlinkSync(cachePath);
1196
+ return true;
1197
+ }
1198
+ return false;
1199
+ }
1200
+ function listCachedPages(projectRoot) {
1201
+ const pagesDir = getPagesDir(projectRoot);
1202
+ if (!existsSync7(pagesDir)) {
1203
+ return [];
1204
+ }
1205
+ const files = readdirSync2(pagesDir).filter((f) => f.endsWith(".json"));
1206
+ const pages = [];
1207
+ for (const file of files) {
1208
+ const slug = basename3(file, ".json");
1209
+ const context = readPageContext(projectRoot, slug);
1210
+ if (context) {
1211
+ pages.push({
1212
+ slug,
1213
+ url: context.url,
1214
+ title: context.title,
1215
+ extractedAt: context.extractedAt,
1216
+ sectionCount: context.sections.length,
1217
+ cmsCollectionCount: context.cmsPatterns.length
1218
+ });
1219
+ }
1220
+ }
1221
+ pages.sort((a, b) => new Date(b.extractedAt).getTime() - new Date(a.extractedAt).getTime());
1222
+ return pages;
1223
+ }
1224
+ function toCachedContext(structure) {
1225
+ return {
1226
+ url: structure.url,
1227
+ title: structure.title,
1228
+ sections: structure.sections.map((s) => ({
1229
+ id: s.id,
1230
+ className: s.className,
1231
+ heading: s.heading,
1232
+ textSample: s.textSample,
1233
+ hasCms: s.hasCms,
1234
+ hasForm: s.hasForm
1235
+ })),
1236
+ navigation: structure.navigation.map((n) => ({
1237
+ type: n.type,
1238
+ items: n.items.map((i) => ({ text: i.text, href: i.href }))
1239
+ })),
1240
+ ctas: structure.ctas.map((c) => ({
1241
+ text: c.text,
1242
+ href: c.href,
1243
+ location: c.location
1244
+ })),
1245
+ forms: structure.forms.map((f) => ({
1246
+ name: f.name || f.id,
1247
+ fields: f.fields.map((field) => ({
1248
+ type: field.type,
1249
+ label: field.label || field.name,
1250
+ required: field.required
1251
+ })),
1252
+ submitText: f.submitText
1253
+ })),
1254
+ cmsPatterns: structure.cmsPatterns,
1255
+ headings: structure.headings.map((h) => ({
1256
+ level: h.level,
1257
+ text: h.text
1258
+ })),
1259
+ extractedAt: structure.extractedAt
1260
+ };
1261
+ }
1262
+
1263
+ // src/commands/html.ts
711
1264
  async function htmlCommand(url, options) {
712
1265
  const projectRoot = findProjectRoot();
713
1266
  if (!projectRoot) {
@@ -727,9 +1280,54 @@ async function htmlCommand(url, options) {
727
1280
  console.log(chalk6.red("\u274C Invalid URL."));
728
1281
  return;
729
1282
  }
1283
+ if (options?.save && !options?.force) {
1284
+ const slug = urlToSlug(parsedUrl.toString());
1285
+ const cached = readPageContext(projectRoot, slug);
1286
+ if (cached) {
1287
+ console.log(chalk6.dim(`Using cached version from ${cached.extractedAt}`));
1288
+ console.log(chalk6.dim(`Use --force to refresh`));
1289
+ console.log();
1290
+ printPageContext(cached);
1291
+ return;
1292
+ }
1293
+ }
730
1294
  const spinner = ora5(`Fetching ${parsedUrl.hostname}${parsedUrl.pathname}...`).start();
731
1295
  try {
732
1296
  const client = createApiClient(config);
1297
+ if (options?.save) {
1298
+ const apiUrl = config.apiUrl || "https://app.cure.no";
1299
+ const response = await fetch(`${apiUrl}/api/cdn/page-context`, {
1300
+ method: "POST",
1301
+ headers: {
1302
+ "Content-Type": "application/json",
1303
+ "X-API-Key": config.apiKey
1304
+ },
1305
+ body: JSON.stringify({ url: parsedUrl.toString() })
1306
+ });
1307
+ if (!response.ok) {
1308
+ const error = await response.json().catch(() => ({ error: response.statusText }));
1309
+ throw new Error(error.error || `HTTP ${response.status}`);
1310
+ }
1311
+ const structure = await response.json();
1312
+ spinner.succeed("Page structure extracted");
1313
+ const cachedContext = toCachedContext(structure);
1314
+ const slug = savePageContext(projectRoot, cachedContext);
1315
+ console.log(chalk6.dim(`Saved to .cure-kode/pages/${slug}.json`));
1316
+ const contextPage = {
1317
+ slug,
1318
+ url: structure.url,
1319
+ title: structure.title,
1320
+ sections: structure.sections.slice(0, 5).map((s) => s.heading || s.id || s.className?.split(" ")[0] || "section").join(", "),
1321
+ ctas: structure.ctas.length > 0 ? structure.ctas.slice(0, 3).map((c) => `"${c.text}"`).join(", ") : void 0,
1322
+ forms: structure.forms.length > 0 ? structure.forms.map((f) => f.name || "form").join(", ") : void 0,
1323
+ cms: structure.cmsPatterns.length > 0 ? structure.cmsPatterns.map((c) => `${c.containerClass} (${c.itemCount})`).join(", ") : void 0
1324
+ };
1325
+ upsertPage(projectRoot, contextPage, "kode html --save");
1326
+ console.log(chalk6.dim(`Updated .cure-kode/context.md`));
1327
+ console.log();
1328
+ printPageContext(cachedContext);
1329
+ return;
1330
+ }
733
1331
  const result = await client.fetchHtml(parsedUrl.toString());
734
1332
  spinner.succeed("HTML fetched");
735
1333
  if (options?.json) {
@@ -746,7 +1344,7 @@ async function htmlCommand(url, options) {
746
1344
  } else {
747
1345
  console.log(chalk6.yellow("\u26A0\uFE0F Cure Kode not found"));
748
1346
  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>`));
1347
+ console.log(chalk6.cyan(` <script src="https://app.cure.no/api/cdn/${config.siteSlug}/init.js"></script>`));
750
1348
  }
751
1349
  console.log();
752
1350
  console.log(chalk6.dim("\u2500".repeat(50)));
@@ -812,12 +1410,80 @@ async function htmlCommand(url, options) {
812
1410
  console.error(chalk6.red("\nError:"), error.message || error);
813
1411
  }
814
1412
  }
1413
+ function printPageContext(context) {
1414
+ console.log(chalk6.bold(context.title || context.url));
1415
+ console.log(chalk6.dim(context.url));
1416
+ console.log();
1417
+ if (context.sections.length > 0) {
1418
+ console.log(chalk6.bold("Sections"));
1419
+ for (const section of context.sections.slice(0, 8)) {
1420
+ const name = section.heading || section.id || section.className?.split(" ")[0] || "section";
1421
+ const badges = [];
1422
+ if (section.hasCms) badges.push(chalk6.cyan("CMS"));
1423
+ if (section.hasForm) badges.push(chalk6.yellow("Form"));
1424
+ const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
1425
+ console.log(` \u2022 ${name}${badgeStr}`);
1426
+ if (section.textSample) {
1427
+ console.log(chalk6.dim(` "${section.textSample.slice(0, 60)}..."`));
1428
+ }
1429
+ }
1430
+ if (context.sections.length > 8) {
1431
+ console.log(chalk6.dim(` ... and ${context.sections.length - 8} more`));
1432
+ }
1433
+ console.log();
1434
+ }
1435
+ if (context.ctas.length > 0) {
1436
+ console.log(chalk6.bold("CTAs"));
1437
+ for (const cta of context.ctas.slice(0, 6)) {
1438
+ console.log(` \u2022 "${cta.text}" ${chalk6.dim(`(${cta.location})`)}`);
1439
+ }
1440
+ if (context.ctas.length > 6) {
1441
+ console.log(chalk6.dim(` ... and ${context.ctas.length - 6} more`));
1442
+ }
1443
+ console.log();
1444
+ }
1445
+ if (context.forms.length > 0) {
1446
+ console.log(chalk6.bold("Forms"));
1447
+ for (const form of context.forms) {
1448
+ const fields = form.fields.map((f) => f.label || f.type).slice(0, 4).join(", ");
1449
+ const more = form.fields.length > 4 ? ` +${form.fields.length - 4}` : "";
1450
+ console.log(` \u2022 ${form.name || "form"}: ${fields}${more}`);
1451
+ }
1452
+ console.log();
1453
+ }
1454
+ if (context.cmsPatterns.length > 0) {
1455
+ console.log(chalk6.bold("CMS Collections"));
1456
+ for (const cms of context.cmsPatterns) {
1457
+ console.log(` \u2022 ${cms.containerClass}: ${chalk6.cyan(`${cms.itemCount} items`)}`);
1458
+ if (cms.templateFields.length > 0) {
1459
+ console.log(chalk6.dim(` Fields: ${cms.templateFields.slice(0, 5).join(", ")}`));
1460
+ }
1461
+ }
1462
+ console.log();
1463
+ }
1464
+ if (context.navigation.length > 0) {
1465
+ console.log(chalk6.bold("Navigation"));
1466
+ for (const nav of context.navigation) {
1467
+ const items = nav.items.slice(0, 5).map((i) => i.text).join(", ");
1468
+ const more = nav.items.length > 5 ? ` +${nav.items.length - 5}` : "";
1469
+ console.log(` \u2022 ${nav.type}: ${items}${more}`);
1470
+ }
1471
+ console.log();
1472
+ }
1473
+ if (context.notes && context.notes.length > 0) {
1474
+ console.log(chalk6.bold("Notes"));
1475
+ for (const note of context.notes) {
1476
+ console.log(` \u2022 ${note}`);
1477
+ }
1478
+ console.log();
1479
+ }
1480
+ }
815
1481
 
816
1482
  // src/commands/status.ts
817
1483
  import chalk7 from "chalk";
818
1484
  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";
1485
+ import { readFileSync as readFileSync6, existsSync as existsSync8, readdirSync as readdirSync3, statSync } from "fs";
1486
+ import { join as join8, basename as basename4, extname as extname3 } from "path";
821
1487
  async function statusCommand(options) {
822
1488
  const projectRoot = findProjectRoot();
823
1489
  if (!projectRoot) {
@@ -873,12 +1539,12 @@ async function statusCommand(options) {
873
1539
  console.log(chalk7.bold("Scripts"));
874
1540
  console.log();
875
1541
  const scriptsDir = getScriptsDir(projectRoot, config);
876
- const localFiles = existsSync6(scriptsDir) ? readdirSync2(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
1542
+ const localFiles = existsSync8(scriptsDir) ? readdirSync3(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
877
1543
  const localBySlug = /* @__PURE__ */ new Map();
878
1544
  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");
1545
+ const slug = basename4(file, extname3(file));
1546
+ const filePath = join8(scriptsDir, file);
1547
+ const content = readFileSync6(filePath, "utf-8");
882
1548
  const stats = statSync(filePath);
883
1549
  localBySlug.set(slug, { file, content, modified: stats.mtime });
884
1550
  }
@@ -959,6 +1625,168 @@ function formatDate(dateStr) {
959
1625
  });
960
1626
  }
961
1627
 
1628
+ // src/commands/context.ts
1629
+ import chalk8 from "chalk";
1630
+ import ora7 from "ora";
1631
+ import { spawn } from "child_process";
1632
+ async function contextCommand(options) {
1633
+ const projectRoot = findProjectRoot();
1634
+ if (!projectRoot) {
1635
+ console.log(chalk8.red("Error: Not in a Cure Kode project."));
1636
+ console.log(chalk8.dim('Run "kode init" first.'));
1637
+ return;
1638
+ }
1639
+ const config = getProjectConfig(projectRoot);
1640
+ if (!config) {
1641
+ console.log(chalk8.red("Error: Invalid project configuration."));
1642
+ return;
1643
+ }
1644
+ const contextPath = getContextPath(projectRoot);
1645
+ const context = readContext(projectRoot);
1646
+ if (options.refresh) {
1647
+ const spinner = ora7("Refreshing context from server...").start();
1648
+ try {
1649
+ const siteResponse = await fetch(
1650
+ `https://app.cure.no/api/cdn/sites/${config.siteId}`,
1651
+ {
1652
+ headers: { "X-API-Key": config.apiKey }
1653
+ }
1654
+ );
1655
+ const scriptsResponse = await fetch(
1656
+ `https://app.cure.no/api/cdn/sites/${config.siteId}/scripts`,
1657
+ {
1658
+ headers: { "X-API-Key": config.apiKey }
1659
+ }
1660
+ );
1661
+ if (!siteResponse.ok || !scriptsResponse.ok) {
1662
+ spinner.fail("Failed to fetch from server");
1663
+ return;
1664
+ }
1665
+ const siteInfo = await siteResponse.json();
1666
+ const scripts = await scriptsResponse.json();
1667
+ const existingScripts = context?.scripts || [];
1668
+ const existingNotes = context?.notes || [];
1669
+ const existingSessions = context?.sessions || [];
1670
+ const existingPages = context?.pages || [];
1671
+ const newContext = {
1672
+ site: {
1673
+ name: config.siteName,
1674
+ slug: config.siteSlug,
1675
+ domain: siteInfo?.domain || void 0,
1676
+ stagingDomain: siteInfo?.staging_domain || void 0
1677
+ },
1678
+ scripts: scripts.map((s) => {
1679
+ const existing = existingScripts.find((e) => e.slug === s.slug);
1680
+ return {
1681
+ slug: s.slug,
1682
+ type: s.type,
1683
+ scope: s.scope,
1684
+ purpose: existing?.purpose
1685
+ };
1686
+ }),
1687
+ pages: existingPages,
1688
+ notes: existingNotes,
1689
+ sessions: existingSessions,
1690
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1691
+ updatedBy: "kode context --refresh"
1692
+ };
1693
+ writeContext(projectRoot, newContext);
1694
+ spinner.succeed("Context refreshed");
1695
+ console.log(chalk8.dim(`Updated: ${contextPath}`));
1696
+ return;
1697
+ } catch (error) {
1698
+ spinner.fail("Failed to refresh context");
1699
+ console.log(chalk8.red(error instanceof Error ? error.message : "Unknown error"));
1700
+ return;
1701
+ }
1702
+ }
1703
+ if (options.edit) {
1704
+ if (!context) {
1705
+ console.log(chalk8.red("Error: No context file found."));
1706
+ console.log(chalk8.dim('Run "kode init" or "kode context --refresh" first.'));
1707
+ return;
1708
+ }
1709
+ const editor = process.env.EDITOR || process.env.VISUAL || "vim";
1710
+ console.log(chalk8.dim(`Opening ${contextPath} in ${editor}...`));
1711
+ const child = spawn(editor, [contextPath], {
1712
+ stdio: "inherit"
1713
+ });
1714
+ child.on("error", (err) => {
1715
+ console.log(chalk8.red(`Failed to open editor: ${err.message}`));
1716
+ console.log(chalk8.dim(`You can edit the file manually: ${contextPath}`));
1717
+ });
1718
+ return;
1719
+ }
1720
+ if (options.json) {
1721
+ if (!context) {
1722
+ console.log(JSON.stringify({ error: "No context file found" }));
1723
+ return;
1724
+ }
1725
+ console.log(JSON.stringify(context, null, 2));
1726
+ return;
1727
+ }
1728
+ if (!context) {
1729
+ console.log(chalk8.yellow("No context file found."));
1730
+ console.log(chalk8.dim('Run "kode context --refresh" to create one.'));
1731
+ return;
1732
+ }
1733
+ console.log(chalk8.bold(`Context: ${context.site.name}`));
1734
+ console.log(chalk8.dim(`Last updated: ${context.lastUpdated}`));
1735
+ console.log(chalk8.dim(`Updated by: ${context.updatedBy}`));
1736
+ console.log();
1737
+ if (context.site.domain || context.site.stagingDomain) {
1738
+ if (context.site.domain) {
1739
+ console.log(chalk8.dim("Domain: ") + context.site.domain);
1740
+ }
1741
+ if (context.site.stagingDomain) {
1742
+ console.log(chalk8.dim("Staging: ") + context.site.stagingDomain);
1743
+ }
1744
+ console.log();
1745
+ }
1746
+ console.log(chalk8.bold("Scripts:"));
1747
+ if (context.scripts.length === 0) {
1748
+ console.log(chalk8.dim(" (no scripts)"));
1749
+ } else {
1750
+ for (const script of context.scripts) {
1751
+ const typeIcon = script.type === "css" ? "\u{1F3A8}" : "\u{1F4DC}";
1752
+ const scopeLabel = script.scope === "global" ? chalk8.cyan("global") : chalk8.yellow("page");
1753
+ const purpose = script.purpose ? chalk8.dim(` - ${script.purpose}`) : "";
1754
+ console.log(` ${typeIcon} ${script.slug} [${scopeLabel}]${purpose}`);
1755
+ }
1756
+ }
1757
+ console.log();
1758
+ console.log(chalk8.bold("Notes:"));
1759
+ if (context.notes.length === 0 || context.notes.length === 3 && context.notes[0].startsWith("(HTML")) {
1760
+ console.log(chalk8.dim(" (no notes yet)"));
1761
+ } else {
1762
+ for (const note of context.notes) {
1763
+ if (!note.startsWith("(")) {
1764
+ console.log(` \u2022 ${note}`);
1765
+ }
1766
+ }
1767
+ }
1768
+ console.log();
1769
+ console.log(chalk8.bold("Recent Sessions:"));
1770
+ if (context.sessions.length === 0) {
1771
+ console.log(chalk8.dim(" (no sessions recorded)"));
1772
+ } else {
1773
+ const recentSessions = context.sessions.slice(0, 3);
1774
+ for (const session of recentSessions) {
1775
+ console.log(` ${chalk8.dim(session.date)} ${session.agent}`);
1776
+ console.log(` ${session.task}`);
1777
+ if (session.changes.length > 0) {
1778
+ console.log(chalk8.dim(` ${session.changes.length} change(s)`));
1779
+ }
1780
+ }
1781
+ if (context.sessions.length > 3) {
1782
+ console.log(chalk8.dim(` ... and ${context.sessions.length - 3} more sessions`));
1783
+ }
1784
+ }
1785
+ console.log();
1786
+ console.log(chalk8.dim(`Context file: ${contextPath}`));
1787
+ console.log(chalk8.dim("Use --edit to open in editor, --refresh to update from server"));
1788
+ }
1789
+
962
1790
  export {
963
1791
  findProjectRoot,
964
1792
  getProjectConfig,
@@ -967,6 +1795,16 @@ export {
967
1795
  getApiKey,
968
1796
  setGlobalConfig,
969
1797
  getScriptsDir,
1798
+ getContextPath,
1799
+ parseContext,
1800
+ serializeContext,
1801
+ readContext,
1802
+ writeContext,
1803
+ appendNote,
1804
+ addSession,
1805
+ updateScriptPurpose,
1806
+ generateInitialContext,
1807
+ generateClaudeMd,
970
1808
  initCommand,
971
1809
  KodeApiError,
972
1810
  KodeApiClient,
@@ -975,6 +1813,10 @@ export {
975
1813
  pushCommand,
976
1814
  watchCommand,
977
1815
  deployCommand,
1816
+ readPageContext,
1817
+ deletePageContext,
1818
+ listCachedPages,
978
1819
  htmlCommand,
979
- statusCommand
1820
+ statusCommand,
1821
+ contextCommand
980
1822
  };