@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.
- package/README.md +37 -2
- package/dist/{chunk-NWXEBN2N.js → chunk-Q64DBAYJ.js} +884 -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 } 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,52 @@ 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 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
|
|
298
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
342
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
389
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
842
|
+
const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
|
|
410
843
|
let metadata = [];
|
|
411
|
-
if (
|
|
412
|
-
metadata = JSON.parse(
|
|
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 =
|
|
444
|
-
const content =
|
|
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
|
|
511
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
974
|
+
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
542
975
|
let metadata = [];
|
|
543
|
-
if (
|
|
544
|
-
metadata = JSON.parse(
|
|
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 =
|
|
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(
|
|
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://
|
|
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
|
|
820
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
880
|
-
const filePath =
|
|
881
|
-
const content =
|
|
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
|
};
|