@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.
- package/README.md +37 -2
- package/dist/{chunk-NWXEBN2N.js → chunk-LYIXUQOG.js} +938 -42
- package/dist/cli.js +171 -4
- package/dist/index.d.ts +96 -1
- package/dist/index.js +25 -3
- package/package.json +1 -1
|
@@ -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://
|
|
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
|
|
61
|
-
import { existsSync as
|
|
62
|
-
import { join as
|
|
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://
|
|
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 =
|
|
146
|
-
if (!
|
|
547
|
+
const scriptsPath = join3(cwd, answers.scriptsDir);
|
|
548
|
+
if (!existsSync3(scriptsPath)) {
|
|
147
549
|
mkdirSync2(scriptsPath, { recursive: true });
|
|
148
550
|
}
|
|
149
|
-
const gitignorePath =
|
|
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
|
-
|
|
154
|
-
|
|
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
|
|
298
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
342
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
389
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
896
|
+
const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
|
|
410
897
|
let metadata = [];
|
|
411
|
-
if (
|
|
412
|
-
metadata = JSON.parse(
|
|
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 =
|
|
444
|
-
const content =
|
|
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
|
|
511
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
1028
|
+
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
542
1029
|
let metadata = [];
|
|
543
|
-
if (
|
|
544
|
-
metadata = JSON.parse(
|
|
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 =
|
|
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(
|
|
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://
|
|
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
|
|
820
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
880
|
-
const filePath =
|
|
881
|
-
const content =
|
|
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
|
};
|