@curenorway/kode-cli 1.15.2 → 1.16.0
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 +28 -0
- package/dist/{chunk-RUJGSFLC.js → chunk-WWC7RIL4.js} +411 -79
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ Creates:
|
|
|
61
61
|
- `.cure-kode-scripts/` - Scripts directory
|
|
62
62
|
- `CLAUDE.md` - Reference to KODE.md (prepended, never overwrites existing content)
|
|
63
63
|
- `.mcp.json` - MCP server configuration (cure-kode, webflow, playwright)
|
|
64
|
+
- `.claude/skills/deploy/` - `/deploy` slash command for Claude Code
|
|
65
|
+
- `.claude/skills/webflow-patterns/` - Webflow DOM reference (auto-loaded by Claude when relevant)
|
|
64
66
|
|
|
65
67
|
Automatically registers and injects init.js into the Webflow site's `<head>` via the Custom Code API. If injection fails, it falls back to showing the manual embed code.
|
|
66
68
|
|
|
@@ -265,6 +267,9 @@ Default: `.cure-kode-scripts/` (avoids conflicts with AI-generated `scripts/` fo
|
|
|
265
267
|
|
|
266
268
|
```
|
|
267
269
|
your-project/
|
|
270
|
+
├── .claude/skills/ # Claude Code skills
|
|
271
|
+
│ ├── deploy/ # /deploy — push and deploy workflow
|
|
272
|
+
│ └── webflow-patterns/ # Auto-loaded Webflow DOM reference
|
|
268
273
|
├── .cure-kode/
|
|
269
274
|
│ ├── config.json # Project configuration (gitignored)
|
|
270
275
|
│ ├── scripts.json # Sync state for conflict detection
|
|
@@ -279,6 +284,29 @@ your-project/
|
|
|
279
284
|
└── CLAUDE.md # Reference to KODE.md (your content preserved)
|
|
280
285
|
```
|
|
281
286
|
|
|
287
|
+
## Claude Code Skills
|
|
288
|
+
|
|
289
|
+
`kode init` generates two Claude Code skills in `.claude/skills/`:
|
|
290
|
+
|
|
291
|
+
### `/deploy`
|
|
292
|
+
|
|
293
|
+
Type `/deploy` in Claude Code to push and deploy to staging. Add `--promote` to promote to production.
|
|
294
|
+
|
|
295
|
+
Both you and Claude can use this — Claude will also invoke it when it finishes making changes and needs to publish them.
|
|
296
|
+
|
|
297
|
+
### `webflow-patterns` (auto-loaded)
|
|
298
|
+
|
|
299
|
+
Background knowledge about Webflow DOM structure, reliable selectors, component patterns, and common pitfalls. Claude loads this automatically when writing or debugging Webflow scripts — it doesn't appear in the `/` menu.
|
|
300
|
+
|
|
301
|
+
Includes:
|
|
302
|
+
- Reliable vs unreliable CSS selectors for Webflow
|
|
303
|
+
- DOM-ready patterns with Webflow's IX2 engine
|
|
304
|
+
- Component reference (Navbar, Tabs, Slider, Dropdown, CMS lists, Forms)
|
|
305
|
+
- CSS specificity rules with Webflow's stylesheet
|
|
306
|
+
- Cure Kode script loading order and `window.CK` API
|
|
307
|
+
|
|
308
|
+
The detailed reference lives in `webflow-patterns/reference.md` — Claude reads it when it needs specifics.
|
|
309
|
+
|
|
282
310
|
## Workflow Examples
|
|
283
311
|
|
|
284
312
|
### Basic Development
|
|
@@ -1201,14 +1201,14 @@ Option C: Full rollback
|
|
|
1201
1201
|
}
|
|
1202
1202
|
|
|
1203
1203
|
// src/version.ts
|
|
1204
|
-
var CLI_VERSION = "1.
|
|
1204
|
+
var CLI_VERSION = "1.16.0";
|
|
1205
1205
|
|
|
1206
1206
|
// src/commands/init.ts
|
|
1207
1207
|
import chalk from "chalk";
|
|
1208
1208
|
import ora from "ora";
|
|
1209
1209
|
import enquirer from "enquirer";
|
|
1210
|
-
import { existsSync as
|
|
1211
|
-
import { join as
|
|
1210
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, readFileSync as readFileSync5 } from "fs";
|
|
1211
|
+
import { join as join5 } from "path";
|
|
1212
1212
|
|
|
1213
1213
|
// src/lib/claude-md.ts
|
|
1214
1214
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -1379,6 +1379,10 @@ AI agents can use these tools directly:
|
|
|
1379
1379
|
\u251C\u2500\u2500 main.js
|
|
1380
1380
|
\u251C\u2500\u2500 form-handler.js
|
|
1381
1381
|
\u2514\u2500\u2500 ...
|
|
1382
|
+
|
|
1383
|
+
.claude/skills/ # Claude Code skills
|
|
1384
|
+
\u251C\u2500\u2500 deploy/ # /deploy \u2014 push and deploy workflow
|
|
1385
|
+
\u2514\u2500\u2500 webflow-patterns/ # Auto-loaded Webflow DOM reference
|
|
1382
1386
|
\`\`\`
|
|
1383
1387
|
`;
|
|
1384
1388
|
return md;
|
|
@@ -1435,6 +1439,324 @@ function pagesToInfoFormat(pages) {
|
|
|
1435
1439
|
}
|
|
1436
1440
|
var updateClaudeMd = updateKodeDocs;
|
|
1437
1441
|
|
|
1442
|
+
// src/lib/skills.ts
|
|
1443
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1444
|
+
import { join as join4 } from "path";
|
|
1445
|
+
function generateSkills(projectRoot, siteSlug) {
|
|
1446
|
+
const skillsDir = join4(projectRoot, ".claude", "skills");
|
|
1447
|
+
const created = [];
|
|
1448
|
+
const skipped = [];
|
|
1449
|
+
const deployDir = join4(skillsDir, "deploy");
|
|
1450
|
+
const deployPath = join4(deployDir, "SKILL.md");
|
|
1451
|
+
if (!existsSync4(deployPath)) {
|
|
1452
|
+
mkdirSync2(deployDir, { recursive: true });
|
|
1453
|
+
writeFileSync4(deployPath, generateDeploySkill(siteSlug));
|
|
1454
|
+
created.push("deploy");
|
|
1455
|
+
} else {
|
|
1456
|
+
skipped.push("deploy");
|
|
1457
|
+
}
|
|
1458
|
+
const webflowDir = join4(skillsDir, "webflow-patterns");
|
|
1459
|
+
const webflowSkillPath = join4(webflowDir, "SKILL.md");
|
|
1460
|
+
const webflowRefPath = join4(webflowDir, "reference.md");
|
|
1461
|
+
if (!existsSync4(webflowSkillPath)) {
|
|
1462
|
+
mkdirSync2(webflowDir, { recursive: true });
|
|
1463
|
+
writeFileSync4(webflowSkillPath, WEBFLOW_SKILL);
|
|
1464
|
+
writeFileSync4(webflowRefPath, WEBFLOW_REFERENCE);
|
|
1465
|
+
created.push("webflow-patterns");
|
|
1466
|
+
} else {
|
|
1467
|
+
skipped.push("webflow-patterns");
|
|
1468
|
+
}
|
|
1469
|
+
return { created, skipped };
|
|
1470
|
+
}
|
|
1471
|
+
function generateDeploySkill(siteSlug) {
|
|
1472
|
+
return `---
|
|
1473
|
+
name: deploy
|
|
1474
|
+
description: Push and deploy Cure Kode scripts to staging or production. Use when the user says "deploy", "ship it", "push and deploy", or when you have finished making script changes and need to publish them.
|
|
1475
|
+
argument-hint: "[--promote]"
|
|
1476
|
+
---
|
|
1477
|
+
|
|
1478
|
+
Deploy scripts for **${siteSlug}** to Cure Kode CDN.
|
|
1479
|
+
|
|
1480
|
+
## Steps
|
|
1481
|
+
|
|
1482
|
+
1. **Push** local changes to the server:
|
|
1483
|
+
\`\`\`bash
|
|
1484
|
+
kode push
|
|
1485
|
+
\`\`\`
|
|
1486
|
+
If there are no changes to push, continue to step 2.
|
|
1487
|
+
|
|
1488
|
+
2. **Deploy** to staging:
|
|
1489
|
+
\`\`\`bash
|
|
1490
|
+
kode deploy
|
|
1491
|
+
\`\`\`
|
|
1492
|
+
|
|
1493
|
+
3. **Verify** the deployment succeeded by checking the output.
|
|
1494
|
+
|
|
1495
|
+
4. If \`$ARGUMENTS\` includes \`--promote\` or the user asked for production:
|
|
1496
|
+
\`\`\`bash
|
|
1497
|
+
kode deploy --promote
|
|
1498
|
+
\`\`\`
|
|
1499
|
+
|
|
1500
|
+
## Important
|
|
1501
|
+
|
|
1502
|
+
- Always push before deploying \u2014 unpushed changes will not be included.
|
|
1503
|
+
- After deploying to staging, tell the user to test before promoting.
|
|
1504
|
+
- If deploy fails with a lock error, suggest \`kode deploy --force\` to release it.
|
|
1505
|
+
- After promoting to production, remind the user it may take a few seconds to propagate.
|
|
1506
|
+
`;
|
|
1507
|
+
}
|
|
1508
|
+
var WEBFLOW_SKILL = `---
|
|
1509
|
+
name: webflow-patterns
|
|
1510
|
+
description: Webflow DOM structure, reliable CSS selectors, component patterns, and common pitfalls for writing scripts that target Webflow sites. Use when writing or debugging JavaScript/CSS that runs on a Webflow site.
|
|
1511
|
+
user-invocable: false
|
|
1512
|
+
---
|
|
1513
|
+
|
|
1514
|
+
# Webflow Development Patterns
|
|
1515
|
+
|
|
1516
|
+
Use these patterns when writing scripts for Webflow sites via Cure Kode.
|
|
1517
|
+
|
|
1518
|
+
## Reliable selectors
|
|
1519
|
+
|
|
1520
|
+
- Use **custom classes** you add in Webflow Designer (e.g. \`.hero-section\`, \`.cta-button\`)
|
|
1521
|
+
- Use **\`data-\` attributes** added via custom attributes in Designer (e.g. \`[data-component="pricing"]\`)
|
|
1522
|
+
- Use **\`data-w-id\`** to target interaction-bound elements (stable across publishes)
|
|
1523
|
+
- Avoid \`.w-\` prefixed classes \u2014 these are Webflow internals and can change on publish
|
|
1524
|
+
|
|
1525
|
+
## DOM ready pattern
|
|
1526
|
+
|
|
1527
|
+
Cure Kode's init.js loads with \`defer\`, so the DOM is ready when your scripts execute.
|
|
1528
|
+
For code that depends on Webflow's JS (interactions, tabs, sliders):
|
|
1529
|
+
|
|
1530
|
+
\`\`\`javascript
|
|
1531
|
+
// Safe: wait for Webflow's IX2 engine
|
|
1532
|
+
window.Webflow = window.Webflow || [];
|
|
1533
|
+
window.Webflow.push(function () {
|
|
1534
|
+
// Webflow JS is fully initialized here
|
|
1535
|
+
});
|
|
1536
|
+
\`\`\`
|
|
1537
|
+
|
|
1538
|
+
Do NOT use \`DOMContentLoaded\` \u2014 it fires before Webflow's interaction engine is ready.
|
|
1539
|
+
|
|
1540
|
+
## Common Webflow components
|
|
1541
|
+
|
|
1542
|
+
| Component | Wrapper class | Key elements | Gotchas |
|
|
1543
|
+
|-----------|--------------|--------------|---------|
|
|
1544
|
+
| Navbar | \`.w-nav\` | \`.w-nav-menu\`, \`.w-nav-link\`, \`.w-nav-overlay\` | Mobile menu uses \`.w--open\` state class |
|
|
1545
|
+
| Tabs | \`.w-tabs\` | \`.w-tab-menu\`, \`.w-tab-link\`, \`.w-tab-pane\` | Don't add click handlers \u2014 Webflow manages state |
|
|
1546
|
+
| Slider | \`.w-slider\` | \`.w-slide\`, \`.w-slider-nav\` | Auto-play conflicts with custom navigation |
|
|
1547
|
+
| Dropdown | \`.w-dropdown\` | \`.w-dropdown-toggle\`, \`.w-dropdown-list\` | Opens on hover by default on desktop |
|
|
1548
|
+
| Lightbox | \`.w-lightbox\` | Created dynamically on click | No static DOM until opened |
|
|
1549
|
+
| Form | \`.w-form\` | \`.w-form-done\`, \`.w-form-fail\` | Webflow shows/hides done/fail divs |
|
|
1550
|
+
| CMS list | \`.w-dyn-items\` | \`.w-dyn-item\` | Paginated: not all items in DOM |
|
|
1551
|
+
| Rich text | \`.w-richtext\` | Nested \`h2-h6\`, \`p\`, \`img\`, \`ul/ol\` | Has opinionated default styles |
|
|
1552
|
+
|
|
1553
|
+
## Key \`data-wf-*\` attributes
|
|
1554
|
+
|
|
1555
|
+
- \`data-wf-domain\` on \`<html>\` \u2014 the Webflow staging domain
|
|
1556
|
+
- \`data-wf-page\` on \`<html>\` \u2014 unique page identifier
|
|
1557
|
+
- \`data-wf-site\` on \`<html>\` \u2014 unique site identifier
|
|
1558
|
+
|
|
1559
|
+
## For detailed patterns
|
|
1560
|
+
|
|
1561
|
+
See [reference.md](reference.md) for:
|
|
1562
|
+
- CMS collection list patterns and filtering
|
|
1563
|
+
- Form handling and validation
|
|
1564
|
+
- CSS specificity with Webflow's stylesheets
|
|
1565
|
+
- Animation and interaction scripting
|
|
1566
|
+
- Cure Kode script loading and execution order
|
|
1567
|
+
`;
|
|
1568
|
+
var WEBFLOW_REFERENCE = `# Webflow Patterns \u2014 Detailed Reference
|
|
1569
|
+
|
|
1570
|
+
## CMS Collection Lists
|
|
1571
|
+
|
|
1572
|
+
Webflow CMS items render inside \`.w-dyn-items > .w-dyn-item\`. Key patterns:
|
|
1573
|
+
|
|
1574
|
+
\`\`\`javascript
|
|
1575
|
+
// Wait for CMS items to render
|
|
1576
|
+
function onCmsReady(listSelector, callback) {
|
|
1577
|
+
const list = document.querySelector(listSelector);
|
|
1578
|
+
if (!list) return;
|
|
1579
|
+
|
|
1580
|
+
const items = list.querySelectorAll('.w-dyn-item');
|
|
1581
|
+
if (items.length > 0) {
|
|
1582
|
+
callback(items);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// Fallback: observe for dynamic loading
|
|
1587
|
+
const observer = new MutationObserver((mutations, obs) => {
|
|
1588
|
+
const items = list.querySelectorAll('.w-dyn-item');
|
|
1589
|
+
if (items.length > 0) {
|
|
1590
|
+
obs.disconnect();
|
|
1591
|
+
callback(items);
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
observer.observe(list, { childList: true, subtree: true });
|
|
1595
|
+
}
|
|
1596
|
+
\`\`\`
|
|
1597
|
+
|
|
1598
|
+
### CMS filtering and sorting
|
|
1599
|
+
|
|
1600
|
+
Webflow has native filtering (paid plans). For custom filtering:
|
|
1601
|
+
|
|
1602
|
+
\`\`\`javascript
|
|
1603
|
+
// Use data attributes on CMS items for filtering
|
|
1604
|
+
// In Webflow: add custom attribute data-category="{Category Field}"
|
|
1605
|
+
document.querySelectorAll('[data-filter-btn]').forEach(btn => {
|
|
1606
|
+
btn.addEventListener('click', () => {
|
|
1607
|
+
const filter = btn.dataset.filterBtn;
|
|
1608
|
+
document.querySelectorAll('.w-dyn-item').forEach(item => {
|
|
1609
|
+
const category = item.querySelector('[data-category]')?.dataset.category;
|
|
1610
|
+
item.style.display = (filter === 'all' || category === filter) ? '' : 'none';
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1613
|
+
});
|
|
1614
|
+
\`\`\`
|
|
1615
|
+
|
|
1616
|
+
### Pagination
|
|
1617
|
+
|
|
1618
|
+
Webflow paginates CMS lists (default 100 items). Only items on the current page exist in the DOM.
|
|
1619
|
+
If you need all items, use the Webflow CMS API or load additional pages via fetch.
|
|
1620
|
+
|
|
1621
|
+
## Form Handling
|
|
1622
|
+
|
|
1623
|
+
Webflow forms submit via AJAX by default. To intercept:
|
|
1624
|
+
|
|
1625
|
+
\`\`\`javascript
|
|
1626
|
+
// Intercept form submission
|
|
1627
|
+
const form = document.querySelector('.w-form form');
|
|
1628
|
+
if (form) {
|
|
1629
|
+
form.addEventListener('submit', (e) => {
|
|
1630
|
+
e.preventDefault();
|
|
1631
|
+
e.stopPropagation(); // Prevent Webflow's handler
|
|
1632
|
+
|
|
1633
|
+
const formData = new FormData(form);
|
|
1634
|
+
// Your custom logic here
|
|
1635
|
+
|
|
1636
|
+
// Show success state manually:
|
|
1637
|
+
form.style.display = 'none';
|
|
1638
|
+
form.closest('.w-form').querySelector('.w-form-done').style.display = 'block';
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
\`\`\`
|
|
1642
|
+
|
|
1643
|
+
### Form validation
|
|
1644
|
+
|
|
1645
|
+
Webflow uses native HTML5 validation. For custom validation:
|
|
1646
|
+
|
|
1647
|
+
\`\`\`javascript
|
|
1648
|
+
// Add custom validation before Webflow processes the form
|
|
1649
|
+
const form = document.querySelector('[data-form="contact"]');
|
|
1650
|
+
const emailField = form?.querySelector('input[type="email"]');
|
|
1651
|
+
|
|
1652
|
+
emailField?.addEventListener('invalid', (e) => {
|
|
1653
|
+
e.target.setCustomValidity('Vennligst skriv inn en gyldig e-postadresse');
|
|
1654
|
+
});
|
|
1655
|
+
emailField?.addEventListener('input', (e) => {
|
|
1656
|
+
e.target.setCustomValidity('');
|
|
1657
|
+
});
|
|
1658
|
+
\`\`\`
|
|
1659
|
+
|
|
1660
|
+
## CSS Specificity with Webflow
|
|
1661
|
+
|
|
1662
|
+
Webflow's stylesheet loads before Cure Kode scripts. Key rules:
|
|
1663
|
+
|
|
1664
|
+
1. **Your CSS has higher specificity** when injected via Cure Kode (loaded after Webflow's CSS)
|
|
1665
|
+
2. **Webflow uses \`!important\` sparingly** but does use it for:
|
|
1666
|
+
- \`.w--current\` navigation states
|
|
1667
|
+
- Visibility utilities (\`.w-condition-invisible\`)
|
|
1668
|
+
- Rich text spacing
|
|
1669
|
+
3. **Override Webflow styles safely**:
|
|
1670
|
+
|
|
1671
|
+
\`\`\`css
|
|
1672
|
+
/* Prefer class specificity over !important */
|
|
1673
|
+
.section.hero-section .heading {
|
|
1674
|
+
font-size: 4rem; /* More specific than .heading alone */
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/* If you must override !important from Webflow */
|
|
1678
|
+
.w-richtext h2 {
|
|
1679
|
+
margin-top: 2em !important; /* Only when absolutely necessary */
|
|
1680
|
+
}
|
|
1681
|
+
\`\`\`
|
|
1682
|
+
|
|
1683
|
+
4. **Webflow responsive breakpoints**:
|
|
1684
|
+
- Desktop: default (no media query)
|
|
1685
|
+
- Tablet: \`@media (max-width: 991px)\`
|
|
1686
|
+
- Mobile landscape: \`@media (max-width: 767px)\`
|
|
1687
|
+
- Mobile portrait: \`@media (max-width: 478px)\`
|
|
1688
|
+
|
|
1689
|
+
## Interactions and Animations
|
|
1690
|
+
|
|
1691
|
+
Webflow's IX2 engine manages interactions. Avoid conflicts:
|
|
1692
|
+
|
|
1693
|
+
\`\`\`javascript
|
|
1694
|
+
// DON'T: Modify styles that Webflow interactions control
|
|
1695
|
+
element.style.opacity = '1'; // Will fight with IX2
|
|
1696
|
+
|
|
1697
|
+
// DO: Use Webflow's API to trigger interactions
|
|
1698
|
+
// Or add/remove classes that don't conflict with IX2 properties
|
|
1699
|
+
element.classList.add('is-active');
|
|
1700
|
+
\`\`\`
|
|
1701
|
+
|
|
1702
|
+
### Scroll-triggered animations
|
|
1703
|
+
|
|
1704
|
+
\`\`\`javascript
|
|
1705
|
+
// Use IntersectionObserver \u2014 doesn't conflict with IX2
|
|
1706
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1707
|
+
entries.forEach(entry => {
|
|
1708
|
+
if (entry.isIntersecting) {
|
|
1709
|
+
entry.target.classList.add('is-visible');
|
|
1710
|
+
observer.unobserve(entry.target);
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
}, { threshold: 0.2 });
|
|
1714
|
+
|
|
1715
|
+
document.querySelectorAll('[data-animate]').forEach(el => observer.observe(el));
|
|
1716
|
+
\`\`\`
|
|
1717
|
+
|
|
1718
|
+
## Cure Kode Script Loading
|
|
1719
|
+
|
|
1720
|
+
### Execution order
|
|
1721
|
+
|
|
1722
|
+
1. Browser parses HTML, encounters init.js with \`defer\`
|
|
1723
|
+
2. DOM is fully parsed
|
|
1724
|
+
3. init.js executes:
|
|
1725
|
+
a. Global + autoLoad scripts are injected (in order of creation)
|
|
1726
|
+
b. Page-specific scripts are matched against current URL
|
|
1727
|
+
c. CSS scripts injected as \`<style>\` tags in \`<head>\`
|
|
1728
|
+
d. JS scripts injected as \`<script>\` tags at end of \`<body>\`
|
|
1729
|
+
4. Each script executes as it's injected
|
|
1730
|
+
|
|
1731
|
+
### The \`window.CK\` global
|
|
1732
|
+
|
|
1733
|
+
\`\`\`javascript
|
|
1734
|
+
// Available after init.js loads
|
|
1735
|
+
window.CK.loadScript('my-script'); // Manually load a script
|
|
1736
|
+
window.CK.getConfig(); // Get site configuration
|
|
1737
|
+
window.CK.version; // Current deployment version
|
|
1738
|
+
\`\`\`
|
|
1739
|
+
|
|
1740
|
+
### Script communication
|
|
1741
|
+
|
|
1742
|
+
Scripts loaded by Cure Kode can communicate via custom events:
|
|
1743
|
+
|
|
1744
|
+
\`\`\`javascript
|
|
1745
|
+
// Script A: dispatch
|
|
1746
|
+
window.dispatchEvent(new CustomEvent('cart:updated', { detail: { count: 3 } }));
|
|
1747
|
+
|
|
1748
|
+
// Script B: listen
|
|
1749
|
+
window.addEventListener('cart:updated', (e) => {
|
|
1750
|
+
document.querySelector('.cart-count').textContent = e.detail.count;
|
|
1751
|
+
});
|
|
1752
|
+
\`\`\`
|
|
1753
|
+
|
|
1754
|
+
### CSS script specificity
|
|
1755
|
+
|
|
1756
|
+
CSS scripts are injected as \`<style>\` tags in \`<head>\`, after Webflow's stylesheet.
|
|
1757
|
+
They naturally have higher cascade priority without needing \`!important\`.
|
|
1758
|
+
`;
|
|
1759
|
+
|
|
1438
1760
|
// src/commands/init.ts
|
|
1439
1761
|
var { prompt } = enquirer;
|
|
1440
1762
|
function showBanner() {
|
|
@@ -1580,22 +1902,22 @@ async function initCommand(options) {
|
|
|
1580
1902
|
};
|
|
1581
1903
|
spinner.start("Oppretter prosjektstruktur...");
|
|
1582
1904
|
saveProjectConfig(config, cwd);
|
|
1583
|
-
const scriptsPath =
|
|
1584
|
-
if (!
|
|
1585
|
-
|
|
1905
|
+
const scriptsPath = join5(cwd, DEFAULT_SCRIPTS_DIR);
|
|
1906
|
+
if (!existsSync5(scriptsPath)) {
|
|
1907
|
+
mkdirSync3(scriptsPath, { recursive: true });
|
|
1586
1908
|
}
|
|
1587
|
-
const gitignorePath =
|
|
1909
|
+
const gitignorePath = join5(cwd, ".cure-kode", ".gitignore");
|
|
1588
1910
|
const gitignoreContent = `# Hold config.json privat - inneholder API-n\xF8kkel
|
|
1589
1911
|
config.json
|
|
1590
1912
|
`;
|
|
1591
|
-
|
|
1913
|
+
writeFileSync5(gitignorePath, gitignoreContent);
|
|
1592
1914
|
spinner.succeed("Prosjektstruktur opprettet");
|
|
1593
1915
|
spinner.start("Konfigurerer MCP-servere...");
|
|
1594
|
-
const mcpConfigPath =
|
|
1916
|
+
const mcpConfigPath = join5(cwd, ".mcp.json");
|
|
1595
1917
|
let mcpConfig = {};
|
|
1596
|
-
if (
|
|
1918
|
+
if (existsSync5(mcpConfigPath)) {
|
|
1597
1919
|
try {
|
|
1598
|
-
mcpConfig = JSON.parse(
|
|
1920
|
+
mcpConfig = JSON.parse(readFileSync5(mcpConfigPath, "utf-8"));
|
|
1599
1921
|
} catch {
|
|
1600
1922
|
}
|
|
1601
1923
|
}
|
|
@@ -1644,7 +1966,7 @@ config.json
|
|
|
1644
1966
|
mcpConfig.mcpServers[server.name] = serverConfig;
|
|
1645
1967
|
}
|
|
1646
1968
|
}
|
|
1647
|
-
|
|
1969
|
+
writeFileSync5(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1648
1970
|
spinner.succeed("MCP-servere konfigurert");
|
|
1649
1971
|
if (secretWarnings.length > 0) {
|
|
1650
1972
|
console.log();
|
|
@@ -1696,8 +2018,15 @@ config.json
|
|
|
1696
2018
|
production_domain: site.production_domain
|
|
1697
2019
|
};
|
|
1698
2020
|
const contextMdContent = generateInitialContext(config, scripts, siteInfo);
|
|
1699
|
-
|
|
2021
|
+
writeFileSync5(join5(cwd, ".cure-kode", "context.md"), contextMdContent);
|
|
1700
2022
|
spinner.succeed("AI-dokumentasjon generert");
|
|
2023
|
+
spinner.start("Genererer Claude Code skills...");
|
|
2024
|
+
const skillsResult = generateSkills(cwd, config.siteSlug);
|
|
2025
|
+
if (skillsResult.created.length > 0) {
|
|
2026
|
+
spinner.succeed(`Claude Code skills generert (${skillsResult.created.join(", ")})`);
|
|
2027
|
+
} else {
|
|
2028
|
+
spinner.succeed("Claude Code skills finnes allerede");
|
|
2029
|
+
}
|
|
1701
2030
|
console.log();
|
|
1702
2031
|
console.log(chalk.green(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
1703
2032
|
console.log(chalk.green(" \u2551") + chalk.bold.green(" \u2705 Cure Kode er klar! ") + chalk.green("\u2551"));
|
|
@@ -1713,6 +2042,9 @@ config.json
|
|
|
1713
2042
|
console.log(chalk.dim(" \u251C\u2500\u2500 CLAUDE.md ") + chalk.dim("(uendret - har allerede referanse)"));
|
|
1714
2043
|
}
|
|
1715
2044
|
console.log(chalk.dim(" \u251C\u2500\u2500 .mcp.json ") + chalk.green("(MCP-servere)"));
|
|
2045
|
+
console.log(chalk.dim(" \u251C\u2500\u2500 .claude/skills/ ") + chalk.green("(Claude Code skills)"));
|
|
2046
|
+
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 deploy/ (/deploy \u2014 push og deploy)"));
|
|
2047
|
+
console.log(chalk.dim(" \u2502 \u2514\u2500\u2500 webflow-patterns/ (Webflow DOM-referanse)"));
|
|
1716
2048
|
console.log(chalk.dim(" \u251C\u2500\u2500 .cure-kode/"));
|
|
1717
2049
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 config.json (konfigurasjon)"));
|
|
1718
2050
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 KODE.md ") + chalk.green("(Kode-dokumentasjon)"));
|
|
@@ -2071,8 +2403,8 @@ function createApiClient(config) {
|
|
|
2071
2403
|
// src/commands/pull.ts
|
|
2072
2404
|
import chalk2 from "chalk";
|
|
2073
2405
|
import ora2 from "ora";
|
|
2074
|
-
import { writeFileSync as
|
|
2075
|
-
import { join as
|
|
2406
|
+
import { writeFileSync as writeFileSync6, readFileSync as readFileSync6, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
|
|
2407
|
+
import { join as join6 } from "path";
|
|
2076
2408
|
import { createHash } from "crypto";
|
|
2077
2409
|
function hashContent(content) {
|
|
2078
2410
|
return createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
@@ -2100,14 +2432,14 @@ async function pullCommand(options) {
|
|
|
2100
2432
|
}
|
|
2101
2433
|
spinner.succeed(`Fant ${scripts.length} skript`);
|
|
2102
2434
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2103
|
-
if (!
|
|
2104
|
-
|
|
2435
|
+
if (!existsSync6(scriptsDir)) {
|
|
2436
|
+
mkdirSync4(scriptsDir, { recursive: true });
|
|
2105
2437
|
}
|
|
2106
|
-
const metadataPath =
|
|
2438
|
+
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
2107
2439
|
let existingMetadata = [];
|
|
2108
|
-
if (
|
|
2440
|
+
if (existsSync6(metadataPath)) {
|
|
2109
2441
|
try {
|
|
2110
|
-
existingMetadata = JSON.parse(
|
|
2442
|
+
existingMetadata = JSON.parse(readFileSync6(metadataPath, "utf-8"));
|
|
2111
2443
|
} catch {
|
|
2112
2444
|
}
|
|
2113
2445
|
}
|
|
@@ -2128,11 +2460,11 @@ Fant ikke skript "${options.script}".`));
|
|
|
2128
2460
|
for (const script of scriptsToPull) {
|
|
2129
2461
|
const ext = script.type === "javascript" ? "js" : "css";
|
|
2130
2462
|
const fileName = `${script.slug}.${ext}`;
|
|
2131
|
-
const filePath =
|
|
2463
|
+
const filePath = join6(scriptsDir, fileName);
|
|
2132
2464
|
const existingMeta = existingMetadata.find((m) => m.slug === script.slug);
|
|
2133
2465
|
const lastPulledVersion = existingMeta?.lastPulledVersion || 0;
|
|
2134
|
-
if (
|
|
2135
|
-
const localContent =
|
|
2466
|
+
if (existsSync6(filePath) && !options.force) {
|
|
2467
|
+
const localContent = readFileSync6(filePath, "utf-8");
|
|
2136
2468
|
const localHash = hashContent(localContent);
|
|
2137
2469
|
const hasLocalChanges = existingMeta && localHash !== existingMeta.contentHash;
|
|
2138
2470
|
if (hasLocalChanges) {
|
|
@@ -2155,7 +2487,7 @@ Fant ikke skript "${options.script}".`));
|
|
|
2155
2487
|
continue;
|
|
2156
2488
|
}
|
|
2157
2489
|
}
|
|
2158
|
-
|
|
2490
|
+
writeFileSync6(filePath, script.content);
|
|
2159
2491
|
const scopeTag = script.scope === "global" ? chalk2.blue("[G]") : chalk2.magenta("[P]");
|
|
2160
2492
|
const loadTag = script.auto_load ? chalk2.green("\u26A1") : chalk2.dim("\u25CB");
|
|
2161
2493
|
if (lastPulledVersion > 0 && script.current_version > lastPulledVersion) {
|
|
@@ -2187,8 +2519,8 @@ Fant ikke skript "${options.script}".`));
|
|
|
2187
2519
|
}
|
|
2188
2520
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2189
2521
|
const metadata = scripts.map((s) => {
|
|
2190
|
-
const filePath =
|
|
2191
|
-
const localContent =
|
|
2522
|
+
const filePath = join6(scriptsDir, `${s.slug}.${s.type === "javascript" ? "js" : "css"}`);
|
|
2523
|
+
const localContent = existsSync6(filePath) ? readFileSync6(filePath, "utf-8") : s.content;
|
|
2192
2524
|
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
2193
2525
|
const page = pages.find((pg) => pg.id === p.page_id);
|
|
2194
2526
|
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
@@ -2208,7 +2540,7 @@ Fant ikke skript "${options.script}".`));
|
|
|
2208
2540
|
contentHash: hashContent(localContent)
|
|
2209
2541
|
};
|
|
2210
2542
|
});
|
|
2211
|
-
|
|
2543
|
+
writeFileSync6(metadataPath, JSON.stringify(metadata, null, 2));
|
|
2212
2544
|
try {
|
|
2213
2545
|
const result = updateClaudeMd(
|
|
2214
2546
|
projectRoot,
|
|
@@ -2239,8 +2571,8 @@ Fant ikke skript "${options.script}".`));
|
|
|
2239
2571
|
// src/commands/push.ts
|
|
2240
2572
|
import chalk3 from "chalk";
|
|
2241
2573
|
import ora3 from "ora";
|
|
2242
|
-
import { readFileSync as
|
|
2243
|
-
import { join as
|
|
2574
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync7, readdirSync } from "fs";
|
|
2575
|
+
import { join as join7, basename, extname } from "path";
|
|
2244
2576
|
import { createHash as createHash2 } from "crypto";
|
|
2245
2577
|
function hashContent2(content) {
|
|
2246
2578
|
return createHash2("sha256").update(content).digest("hex").substring(0, 16);
|
|
@@ -2268,17 +2600,17 @@ async function pushCommand(options) {
|
|
|
2268
2600
|
return;
|
|
2269
2601
|
}
|
|
2270
2602
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2271
|
-
if (!
|
|
2603
|
+
if (!existsSync7(scriptsDir)) {
|
|
2272
2604
|
console.log(chalk3.yellow("Skriptmappen finnes ikke."));
|
|
2273
2605
|
console.log(chalk3.dim(`Forventet: ${scriptsDir}`));
|
|
2274
2606
|
console.log(chalk3.dim('Kj\xF8r "kode pull" f\xF8rst eller opprett skript manuelt.'));
|
|
2275
2607
|
return;
|
|
2276
2608
|
}
|
|
2277
|
-
const metadataPath =
|
|
2609
|
+
const metadataPath = join7(projectRoot, ".cure-kode", "scripts.json");
|
|
2278
2610
|
let metadata = [];
|
|
2279
|
-
if (
|
|
2611
|
+
if (existsSync7(metadataPath)) {
|
|
2280
2612
|
try {
|
|
2281
|
-
metadata = JSON.parse(
|
|
2613
|
+
metadata = JSON.parse(readFileSync7(metadataPath, "utf-8"));
|
|
2282
2614
|
} catch {
|
|
2283
2615
|
}
|
|
2284
2616
|
}
|
|
@@ -2313,8 +2645,8 @@ async function pushCommand(options) {
|
|
|
2313
2645
|
console.log();
|
|
2314
2646
|
let emptyScriptCount = 0;
|
|
2315
2647
|
for (const file of filesToPush) {
|
|
2316
|
-
const filePath =
|
|
2317
|
-
const content =
|
|
2648
|
+
const filePath = join7(scriptsDir, file);
|
|
2649
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
2318
2650
|
const slug = basename(file, extname(file));
|
|
2319
2651
|
const type = extname(file) === ".js" ? "javascript" : "css";
|
|
2320
2652
|
if (content.trim().length === 0) {
|
|
@@ -2397,8 +2729,8 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2397
2729
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2398
2730
|
const updatedMetadata = updatedScripts.map((s) => {
|
|
2399
2731
|
const ext = s.type === "javascript" ? "js" : "css";
|
|
2400
|
-
const filePath =
|
|
2401
|
-
const localContent =
|
|
2732
|
+
const filePath = join7(scriptsDir, `${s.slug}.${ext}`);
|
|
2733
|
+
const localContent = existsSync7(filePath) ? readFileSync7(filePath, "utf-8") : s.content;
|
|
2402
2734
|
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
2403
2735
|
const page = pages.find((pg) => pg.id === p.page_id);
|
|
2404
2736
|
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
@@ -2418,7 +2750,7 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2418
2750
|
contentHash: hashContent2(localContent)
|
|
2419
2751
|
};
|
|
2420
2752
|
});
|
|
2421
|
-
|
|
2753
|
+
writeFileSync7(metadataPath, JSON.stringify(updatedMetadata, null, 2));
|
|
2422
2754
|
try {
|
|
2423
2755
|
const result = updateClaudeMd(
|
|
2424
2756
|
projectRoot,
|
|
@@ -2448,8 +2780,8 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2448
2780
|
// src/commands/watch.ts
|
|
2449
2781
|
import chalk4 from "chalk";
|
|
2450
2782
|
import chokidar from "chokidar";
|
|
2451
|
-
import { readFileSync as
|
|
2452
|
-
import { join as
|
|
2783
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
2784
|
+
import { join as join8, basename as basename2, extname as extname2 } from "path";
|
|
2453
2785
|
async function watchCommand(options) {
|
|
2454
2786
|
const projectRoot = findProjectRoot();
|
|
2455
2787
|
if (!projectRoot) {
|
|
@@ -2463,7 +2795,7 @@ async function watchCommand(options) {
|
|
|
2463
2795
|
return;
|
|
2464
2796
|
}
|
|
2465
2797
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2466
|
-
if (!
|
|
2798
|
+
if (!existsSync8(scriptsDir)) {
|
|
2467
2799
|
console.log(chalk4.yellow("\u26A0\uFE0F Scripts directory not found."));
|
|
2468
2800
|
console.log(chalk4.dim(` Expected: ${scriptsDir}`));
|
|
2469
2801
|
console.log(chalk4.dim(' Run "kode pull" first.'));
|
|
@@ -2479,10 +2811,10 @@ async function watchCommand(options) {
|
|
|
2479
2811
|
console.log();
|
|
2480
2812
|
console.log(chalk4.dim("Press Ctrl+C to stop.\n"));
|
|
2481
2813
|
const client = createApiClient(config);
|
|
2482
|
-
const metadataPath =
|
|
2814
|
+
const metadataPath = join8(projectRoot, ".cure-kode", "scripts.json");
|
|
2483
2815
|
let metadata = [];
|
|
2484
|
-
if (
|
|
2485
|
-
metadata = JSON.parse(
|
|
2816
|
+
if (existsSync8(metadataPath)) {
|
|
2817
|
+
metadata = JSON.parse(readFileSync8(metadataPath, "utf-8"));
|
|
2486
2818
|
}
|
|
2487
2819
|
let remoteScripts = [];
|
|
2488
2820
|
try {
|
|
@@ -2514,7 +2846,7 @@ async function watchCommand(options) {
|
|
|
2514
2846
|
};
|
|
2515
2847
|
const retryFailedSyncs = async () => {
|
|
2516
2848
|
for (const [filePath, failed] of failedSyncs.entries()) {
|
|
2517
|
-
if (!
|
|
2849
|
+
if (!existsSync8(filePath)) {
|
|
2518
2850
|
failedSyncs.delete(filePath);
|
|
2519
2851
|
continue;
|
|
2520
2852
|
}
|
|
@@ -2548,7 +2880,7 @@ async function watchCommand(options) {
|
|
|
2548
2880
|
}
|
|
2549
2881
|
const syncFile = async () => {
|
|
2550
2882
|
try {
|
|
2551
|
-
const content =
|
|
2883
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
2552
2884
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
2553
2885
|
const localMeta = metadata.find((m) => m.slug === slug);
|
|
2554
2886
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
@@ -2662,7 +2994,7 @@ async function watchCommand(options) {
|
|
|
2662
2994
|
}, DEBOUNCE_MS);
|
|
2663
2995
|
pendingChanges.set(filePath, timeout);
|
|
2664
2996
|
};
|
|
2665
|
-
const watcher = chokidar.watch(
|
|
2997
|
+
const watcher = chokidar.watch(join8(scriptsDir, "**/*.{js,css}"), {
|
|
2666
2998
|
persistent: true,
|
|
2667
2999
|
ignoreInitial: true,
|
|
2668
3000
|
awaitWriteFinish: {
|
|
@@ -2703,8 +3035,8 @@ async function watchCommand(options) {
|
|
|
2703
3035
|
// src/commands/deploy.ts
|
|
2704
3036
|
import chalk5 from "chalk";
|
|
2705
3037
|
import ora4 from "ora";
|
|
2706
|
-
import { readFileSync as
|
|
2707
|
-
import { join as
|
|
3038
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9, readdirSync as readdirSync2 } from "fs";
|
|
3039
|
+
import { join as join9, basename as basename3, extname as extname3 } from "path";
|
|
2708
3040
|
function formatBytes(bytes) {
|
|
2709
3041
|
if (bytes < 1024) return `${bytes} B`;
|
|
2710
3042
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -2741,19 +3073,19 @@ async function showDeploymentPreview(config, projectRoot) {
|
|
|
2741
3073
|
}
|
|
2742
3074
|
console.log();
|
|
2743
3075
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2744
|
-
const localFiles =
|
|
2745
|
-
const metadataPath =
|
|
3076
|
+
const localFiles = existsSync9(scriptsDir) ? readdirSync2(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
|
|
3077
|
+
const metadataPath = join9(projectRoot, ".cure-kode", "scripts.json");
|
|
2746
3078
|
let metadata = [];
|
|
2747
|
-
if (
|
|
3079
|
+
if (existsSync9(metadataPath)) {
|
|
2748
3080
|
try {
|
|
2749
|
-
metadata = JSON.parse(
|
|
3081
|
+
metadata = JSON.parse(readFileSync9(metadataPath, "utf-8"));
|
|
2750
3082
|
} catch {
|
|
2751
3083
|
}
|
|
2752
3084
|
}
|
|
2753
3085
|
const localBySlug = /* @__PURE__ */ new Map();
|
|
2754
3086
|
for (const file of localFiles) {
|
|
2755
3087
|
const slug = basename3(file, extname3(file));
|
|
2756
|
-
const content =
|
|
3088
|
+
const content = readFileSync9(join9(scriptsDir, file), "utf-8");
|
|
2757
3089
|
localBySlug.set(slug, { content, size: Buffer.byteLength(content, "utf-8") });
|
|
2758
3090
|
}
|
|
2759
3091
|
const remoteBySlug = /* @__PURE__ */ new Map();
|
|
@@ -2985,8 +3317,8 @@ import chalk6 from "chalk";
|
|
|
2985
3317
|
import ora5 from "ora";
|
|
2986
3318
|
|
|
2987
3319
|
// src/lib/page-cache.ts
|
|
2988
|
-
import { existsSync as
|
|
2989
|
-
import { join as
|
|
3320
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync8, unlinkSync } from "fs";
|
|
3321
|
+
import { join as join10, basename as basename4 } from "path";
|
|
2990
3322
|
var PROJECT_CONFIG_DIR3 = ".cure-kode";
|
|
2991
3323
|
var PAGES_DIR = "pages";
|
|
2992
3324
|
function urlToSlug(url) {
|
|
@@ -3000,32 +3332,32 @@ function urlToSlug(url) {
|
|
|
3000
3332
|
}
|
|
3001
3333
|
}
|
|
3002
3334
|
function getPagesDir(projectRoot) {
|
|
3003
|
-
return
|
|
3335
|
+
return join10(projectRoot, PROJECT_CONFIG_DIR3, PAGES_DIR);
|
|
3004
3336
|
}
|
|
3005
3337
|
function getPageCachePath(projectRoot, urlOrSlug) {
|
|
3006
3338
|
const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
|
|
3007
|
-
return
|
|
3339
|
+
return join10(getPagesDir(projectRoot), `${slug}.json`);
|
|
3008
3340
|
}
|
|
3009
3341
|
function ensurePagesDir(projectRoot) {
|
|
3010
3342
|
const pagesDir = getPagesDir(projectRoot);
|
|
3011
|
-
if (!
|
|
3012
|
-
|
|
3343
|
+
if (!existsSync10(pagesDir)) {
|
|
3344
|
+
mkdirSync5(pagesDir, { recursive: true });
|
|
3013
3345
|
}
|
|
3014
3346
|
}
|
|
3015
3347
|
function savePageContext(projectRoot, context) {
|
|
3016
3348
|
ensurePagesDir(projectRoot);
|
|
3017
3349
|
const slug = urlToSlug(context.url);
|
|
3018
3350
|
const cachePath = getPageCachePath(projectRoot, slug);
|
|
3019
|
-
|
|
3351
|
+
writeFileSync8(cachePath, JSON.stringify(context, null, 2), "utf-8");
|
|
3020
3352
|
return slug;
|
|
3021
3353
|
}
|
|
3022
3354
|
function readPageContext(projectRoot, urlOrSlug) {
|
|
3023
3355
|
const cachePath = getPageCachePath(projectRoot, urlOrSlug);
|
|
3024
|
-
if (!
|
|
3356
|
+
if (!existsSync10(cachePath)) {
|
|
3025
3357
|
return null;
|
|
3026
3358
|
}
|
|
3027
3359
|
try {
|
|
3028
|
-
const content =
|
|
3360
|
+
const content = readFileSync10(cachePath, "utf-8");
|
|
3029
3361
|
return JSON.parse(content);
|
|
3030
3362
|
} catch {
|
|
3031
3363
|
return null;
|
|
@@ -3033,7 +3365,7 @@ function readPageContext(projectRoot, urlOrSlug) {
|
|
|
3033
3365
|
}
|
|
3034
3366
|
function deletePageContext(projectRoot, urlOrSlug) {
|
|
3035
3367
|
const cachePath = getPageCachePath(projectRoot, urlOrSlug);
|
|
3036
|
-
if (
|
|
3368
|
+
if (existsSync10(cachePath)) {
|
|
3037
3369
|
unlinkSync(cachePath);
|
|
3038
3370
|
return true;
|
|
3039
3371
|
}
|
|
@@ -3041,7 +3373,7 @@ function deletePageContext(projectRoot, urlOrSlug) {
|
|
|
3041
3373
|
}
|
|
3042
3374
|
function listCachedPages(projectRoot) {
|
|
3043
3375
|
const pagesDir = getPagesDir(projectRoot);
|
|
3044
|
-
if (!
|
|
3376
|
+
if (!existsSync10(pagesDir)) {
|
|
3045
3377
|
return [];
|
|
3046
3378
|
}
|
|
3047
3379
|
const files = readdirSync3(pagesDir).filter((f) => f.endsWith(".json"));
|
|
@@ -3324,8 +3656,8 @@ function printPageContext(context) {
|
|
|
3324
3656
|
// src/commands/status.ts
|
|
3325
3657
|
import chalk7 from "chalk";
|
|
3326
3658
|
import ora6 from "ora";
|
|
3327
|
-
import { readFileSync as
|
|
3328
|
-
import { join as
|
|
3659
|
+
import { readFileSync as readFileSync11, existsSync as existsSync11, readdirSync as readdirSync4, statSync } from "fs";
|
|
3660
|
+
import { join as join11, basename as basename5, extname as extname4 } from "path";
|
|
3329
3661
|
import { createHash as createHash3 } from "crypto";
|
|
3330
3662
|
function hashContent3(content) {
|
|
3331
3663
|
return createHash3("sha256").update(content).digest("hex").substring(0, 16);
|
|
@@ -3372,11 +3704,11 @@ async function statusCommand(options) {
|
|
|
3372
3704
|
console.log(chalk7.red("\u274C Could not read project configuration."));
|
|
3373
3705
|
return;
|
|
3374
3706
|
}
|
|
3375
|
-
const metadataPath =
|
|
3707
|
+
const metadataPath = join11(projectRoot, ".cure-kode", "scripts.json");
|
|
3376
3708
|
let syncMetadata = [];
|
|
3377
|
-
if (
|
|
3709
|
+
if (existsSync11(metadataPath)) {
|
|
3378
3710
|
try {
|
|
3379
|
-
syncMetadata = JSON.parse(
|
|
3711
|
+
syncMetadata = JSON.parse(readFileSync11(metadataPath, "utf-8"));
|
|
3380
3712
|
} catch {
|
|
3381
3713
|
}
|
|
3382
3714
|
}
|
|
@@ -3457,12 +3789,12 @@ async function statusCommand(options) {
|
|
|
3457
3789
|
}
|
|
3458
3790
|
console.log();
|
|
3459
3791
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
3460
|
-
const localFiles =
|
|
3792
|
+
const localFiles = existsSync11(scriptsDir) ? readdirSync4(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
|
|
3461
3793
|
const localBySlug = /* @__PURE__ */ new Map();
|
|
3462
3794
|
for (const file of localFiles) {
|
|
3463
3795
|
const slug = basename5(file, extname4(file));
|
|
3464
|
-
const filePath =
|
|
3465
|
-
const content =
|
|
3796
|
+
const filePath = join11(scriptsDir, file);
|
|
3797
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
3466
3798
|
const stats = statSync(filePath);
|
|
3467
3799
|
localBySlug.set(slug, { file, content, modified: stats.mtime });
|
|
3468
3800
|
}
|
|
@@ -3738,8 +4070,8 @@ async function contextCommand(options) {
|
|
|
3738
4070
|
// src/commands/sync-config.ts
|
|
3739
4071
|
import chalk9 from "chalk";
|
|
3740
4072
|
import ora8 from "ora";
|
|
3741
|
-
import { existsSync as
|
|
3742
|
-
import { join as
|
|
4073
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
4074
|
+
import { join as join12 } from "path";
|
|
3743
4075
|
async function syncConfigCommand() {
|
|
3744
4076
|
const cwd = process.cwd();
|
|
3745
4077
|
const projectRoot = findProjectRoot(cwd);
|
|
@@ -3770,11 +4102,11 @@ async function syncConfigCommand() {
|
|
|
3770
4102
|
}
|
|
3771
4103
|
const projectCtx = await response.json();
|
|
3772
4104
|
spinner.succeed("Prosjektkonfigurasjon hentet");
|
|
3773
|
-
const mcpConfigPath =
|
|
4105
|
+
const mcpConfigPath = join12(projectRoot, ".mcp.json");
|
|
3774
4106
|
let mcpConfig = {};
|
|
3775
|
-
if (
|
|
4107
|
+
if (existsSync12(mcpConfigPath)) {
|
|
3776
4108
|
try {
|
|
3777
|
-
mcpConfig = JSON.parse(
|
|
4109
|
+
mcpConfig = JSON.parse(readFileSync12(mcpConfigPath, "utf-8"));
|
|
3778
4110
|
} catch {
|
|
3779
4111
|
}
|
|
3780
4112
|
}
|
|
@@ -3818,7 +4150,7 @@ async function syncConfigCommand() {
|
|
|
3818
4150
|
added.push(server.name);
|
|
3819
4151
|
}
|
|
3820
4152
|
}
|
|
3821
|
-
|
|
4153
|
+
writeFileSync9(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
3822
4154
|
spinner.start("Oppdaterer dokumentasjon...");
|
|
3823
4155
|
let scripts = [];
|
|
3824
4156
|
let pages = [];
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED