@sleekcms/sync 2.10.0 → 2.12.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 CHANGED
@@ -135,6 +135,8 @@ The CLI runs a local watcher after the initial fetch. Every time you save a file
135
135
 
136
136
  On first run, the CLI pulls the full site state from the server. After that, a local `.cache/` folder tracks server-known state so only real diffs are pushed — no redundant API calls.
137
137
 
138
+ Renames sync too. Rename or move a page's, entry's, or block's model, view, or content file (in your editor, with `mv`, or `git mv`) and SleekCMS renames it: its other files are moved to match and a page's URL follows (`models/pages/about.model` → `about-us.model` moves `/about` to `/about-us`). Rename a collection by its model file (`models/pages/blog+.model` → `news+.model`) and its records folder follows; renaming a record's content file changes its URL. Links typed into templates or copy keep the old URL, and the sync log lists them.
139
+
138
140
  ```
139
141
  Your editor → file save → watcher → SleekCMS API → rebuild → live site
140
142
  ```
@@ -205,7 +207,7 @@ No manual deploy. No waiting. SleekCMS rebuilds on every sync and the preview re
205
207
  │ ├── css/<name>.css # Stylesheets
206
208
  │ └── js/<name>.js # Scripts
207
209
  │
208
- └── setup.json # Site setup: default page title, Tailwind on/off, sitemap on/off
210
+ └── setup.json # Site setup: default page title, Tailwind on/off, sitemap on/off, cookie consent, analytics ids
209
211
  ```
210
212
 
211
213
  ### File naming
@@ -414,18 +416,34 @@ Content lives in `src/content/` as plain JSON. Edit any content file and save
414
416
 
415
417
  ## Site setup (`src/setup.json`)
416
418
 
417
- Site-wide decisions made once — the default page title, whether Tailwind is compiled, and whether a sitemap is published — live in `src/setup.json`, mirrored from **Settings → Website** in the CMS. The file is JSON with a comment above each key explaining it:
419
+ Site-wide decisions made once — the default page title, whether Tailwind is compiled, whether a sitemap is published, the cookie consent switch, and analytics tag ids — live in `src/setup.json`, mirrored from **Settings → Website** in the CMS. The file is JSON with a comment above each key explaining it:
418
420
 
419
421
  ```json5
420
422
  {
421
423
  title: "Acme Dental", // default <title> for pages without their own; null = the site name
422
424
  tw_enabled: true, // compile src/public/css/tailwind.css and inject it into every page
423
425
  sitemap_enabled: false, // publish sitemap.xml + robots.txt on every build
426
+ cookie_consent: false, // consent banner; holds analytics + data-sleekcms-consent scripts until accepted
427
+ analytics: { // tag ids injected into every page; include only the ones you use
428
+ ga4: "G-XXXXXXXXXX",
429
+ },
424
430
  }
425
431
  ```
426
432
 
427
433
  Edit the values and save — it syncs like any other file. Keys you leave out keep their current value. Which pages are excluded from the sitemap is managed in the CMS UI, not in this file.
428
434
 
435
+ ## Cookie consent
436
+
437
+ Set `cookie_consent: true` in `src/setup.json` and tag any script or iframe that sets cookies with `data-sleekcms-consent="<category>"`. At build time SleekCMS neutralizes the tagged elements, injects a consent banner into every page, and activates each one only after the visitor accepts its category. The analytics ids in `setup.json` are tagged `analytics` automatically.
438
+
439
+ ```ejs
440
+ <% script({ src: 'https://example.com/widget.js', 'data-sleekcms-consent': 'marketing' }) %>
441
+ <iframe data-sleekcms-consent="marketing" src="https://www.youtube.com/embed/VIDEO_ID"></iframe>
442
+ <a data-sleekcms-consent href="#">Cookie settings</a> <!-- reopens the preferences panel; put one in the footer -->
443
+ ```
444
+
445
+ Category names are yours (`analytics`, `marketing`, …); the banner offers exactly the ones found on the site. An optional empty `<div data-sleekcms-consent data-position="bottom" data-theme="auto" data-policy-url="/privacy"></div>` in the layout customizes position (`bottom`, `bottom-left`, `bottom-right`, `modal`), theme, policy link, locale and button labels, and `--sleek-consent-bg/text/accent/on-accent/radius` CSS variables restyle it. The full reference is in the `AGENT.md` that `sleekcms sync` writes into your workspace.
446
+
429
447
  ## Images
430
448
 
431
449
  Images are part of content, not a separate asset pipeline. Drop a shortcut string into any `image` field and the sync engine resolves it to a full `{ url, alt }` object on save:
package/dist/AGENT.md CHANGED
@@ -72,6 +72,8 @@ Never guess at why something isn't rendering — read `sync-errors.log` first, t
72
72
 
73
73
  For collection pages, the `<slug>` is the content file's name: `content/pages/blog+/my-post.json` → `/blog/my-post`. Renaming the file renames the URL.
74
74
 
75
+ **Renaming.** Rename a page, entry, or block by `mv`-ing only its model file — a collection too (`blog+.model`, not its folder). The sync renames it on the server and moves its view, content, and records to match: `models/pages/about.model` → `about-us.model` moves `/about` to `/about-us`. A collection record is the exception: its content file name is its slug, so to change one record's URL, rename that file within its folder — `content/pages/blog+/old-post.json` → `new-post.json` moves just that record from `/blog/old-post` to `/blog/new-post`. Keep slugs lowercase and dash-separated, and keep the file in its collection's folder. The kind and `+` can't change. Update hard-coded links and `getEntry()` calls that use the old name.
76
+
75
77
  ## 4. Folder Structure (under `src/`)
76
78
 
77
79
  ```
@@ -96,7 +98,7 @@ content/entries/<key>+.json Collection entry — ONE file holding a JSON arr
96
98
  content/images.json Site-level reusable images: handle → shortcut (§7)
97
99
  content/videos.json Site-level videos: handle → pexels:/pixabay: ref (user uploads show as embed_url) (§7)
98
100
 
99
- setup.json Site setup (JSON, comments allowed): default page title, Tailwind on/off, sitemap on/off — edit values in place, never delete
101
+ setup.json Site setup (JSON, comments allowed): default page title, Tailwind on/off, sitemap on/off, cookie consent on/off, analytics tag ids — edit values in place, never delete
100
102
  ```
101
103
 
102
104
  **Collection storage differs by kind — do not confuse them:**
@@ -115,6 +117,29 @@ setup.json Site setup (JSON, comments allowed): default pag
115
117
  | `title` | string or `null` | Default `<title>` for pages that don't set their own (via `title()` or the SEO block). `null` = the site name. |
116
118
  | `tw_enabled` | boolean | `true` compiles `css/tailwind.css` and injects it into every page — required for any site styled with Tailwind utilities. |
117
119
  | `sitemap_enabled` | boolean | `true` publishes `sitemap.xml` + `robots.txt` on every build. Turn it on for public sites and SEO work. |
120
+ | `cookie_consent` | boolean | `true` injects a consent banner into every page and holds the analytics tags, plus any script or iframe tagged `data-sleekcms-consent`, until the visitor accepts its category. A toggle only — banner text, position and styling are authored in HTML (§14). |
121
+ | `analytics` | object | Marketing/analytics tag ids injected into every published page. |
122
+
123
+ `analytics` is a flat map — include only the keys the site uses:
124
+
125
+ | Key | Id |
126
+ |---|---|
127
+ | `ga4` | Google Analytics 4 measurement id (`"G-XXXXXXXXXX"`) |
128
+ | `gtm` | Google Tag Manager container id (`"GTM-XXXXXXX"`) |
129
+ | `meta_pixel` | Meta (Facebook) Pixel id (digits) |
130
+ | `google_ads` | Google Ads conversion id (`"AW-XXXXXXXXX"`) |
131
+ | `gsc` | Google Search Console verification token |
132
+ | `posthog` | PostHog project API key (`"phc_..."`) |
133
+ | `posthog_host` | PostHog host — optional, defaults to PostHog US cloud |
134
+
135
+ ```json5
136
+ analytics: {
137
+ ga4: "G-ABCD1234",
138
+ gtm: "GTM-ABC1234",
139
+ },
140
+ ```
141
+
142
+ Omit a key (or the whole `analytics` object) to leave that integration alone; write `analytics: {}` to clear every one. Unrecognized keys and unusable values are dropped with a warning, and a malformed id is skipped at build time rather than breaking the build. Put tag ids here rather than pasting vendor snippets into a layout — the build injects the right script for each id, and with `cookie_consent: true` it gates them behind the banner automatically (§14).
118
143
 
119
144
  Edit values in place and keep the keys. Keys you omit keep their stored values; unknown keys are ignored. Which pages are excluded from the sitemap is a CMS UI setting, not a key in this file. Never delete the file.
120
145
 
@@ -413,7 +438,9 @@ Callable from **any** view (page, block, entry, layout); duplicates are removed
413
438
  | `style(css, order?)` | Add `<style>` |
414
439
  | `script(value, order?)` | Add `<script>` — `.js` URL → external, otherwise inline |
415
440
 
416
- `script()`/`link()` also accept a predefined library name — `gsap` (includes ScrollTrigger, pre-registered), `aos` (auto-`AOS.init()`, JS+CSS), `alpinejs`, `swiper`, `animejs`, `chartjs`, `threejs` (import map — pair with a `type: 'module'` script), `lottie`, `typedjs`, `splide`, `htmx`, `vanilla-tilt` — either call injects all of the library's CDN assets, deduped. Prefer these names over hand-picked CDN URLs, and only add a library the site actually uses.
441
+ `script()`/`link()` also accept a predefined library name — `gsap` (includes ScrollTrigger, pre-registered), `aos` (auto-`AOS.init()`, JS+CSS), `swiper`, `animejs`, `chartjs`, `threejs` (import map — pair with a `type: 'module'` script), `lottie`, `typedjs`, `splide`, `htmx`, `vanilla-tilt` — either call injects all of the library's CDN assets, deduped. Prefer these names over hand-picked CDN URLs, and only add a library the site actually uses.
442
+
443
+ `script()` also takes an attributes object — `script({ src: '/js/app.js', defer: '' })`, `script({ content: '...', type: 'module' })` — so a tag that needs attributes still never needs a raw `<script>`. That is how a third-party script is put behind cookie consent: `script({ src: 'https://…', 'data-sleekcms-consent': 'marketing' })` (§14).
417
444
 
418
445
  ```ejs
419
446
  <% script('aos') %> <!-- injects aos.js + aos.css + AOS.init() -->
@@ -447,7 +474,7 @@ Make every site strongly SEO- and sharing-friendly. The standard pattern is a re
447
474
 
448
475
  Add `seo: block(seo)` to page models and render it **once from the layout head**: `<% if (item.seo) { %><%- render(item.seo) %><% } %>` in `layouts/common.ejs` (as in the layout example above) — not per page view.
449
476
 
450
- Two site-level SEO switches live in `setup.json` (§4), not in views: `sitemap_enabled: true` publishes `sitemap.xml` + `robots.txt` on every build, and `title` is the site's default `<title>` for pages whose SEO block leaves the title empty (`null` = the site name). Sitemap page exclusions are chosen in the CMS UI — there is no file setting for them.
477
+ Three site-level SEO settings live in `setup.json` (§4), not in views: `sitemap_enabled: true` publishes `sitemap.xml` + `robots.txt` on every build; `title` is the site's default `<title>` for pages whose SEO block leaves the title empty (`null` = the site name); and a Google Search Console verification token goes in `analytics.gsc`. Sitemap page exclusions are chosen in the CMS UI — there is no file setting for them.
451
478
 
452
479
  ## 10. Forms
453
480
 
@@ -552,7 +579,76 @@ Snipcart powers stores. Use it when the business **sells products** and the user
552
579
  `data-item-url` is the product's own page path (`path(item)`), and that page must contain the same buy button — Snipcart re-crawls it to validate orders. Repeat the full `data-item-*` set everywhere the product can be added (listing cards too).
553
580
  4. **Header controls.** Add both the cart button (`class="snipcart-checkout"` with `snipcart-items-count`) and the customer sign-in link (`class="snipcart-customer-signin"`) to the site header.
554
581
 
555
- ## 14. Critical Rules Checklist
582
+ ## 14. Cookie Consent
583
+
584
+ Use this when the user wants a **cookie banner** — GDPR / ePrivacy consent for tracking scripts and third-party embeds. One switch turns it on; everything else is HTML. Tag each script or iframe that needs consent with `data-sleekcms-consent="<category>"`, and at build time SleekCMS ships it inert, injects a consent banner and runtime into every page, and activates it only after the visitor accepts that category. Choices are stored in a first-party cookie and honoured on every return visit. **Never add a banner speculatively** — only when the user asks, or the site clearly needs one.
585
+
586
+ **Turn it on** — `cookie_consent: true` in `setup.json` (§4), the same switch as Settings > Website > Analytics in the CMS. Off (the default): nothing is injected and tagged scripts run as normal.
587
+
588
+ **Tag scripts via `script()`** — views never contain raw `<script>` tags, so the category goes in the attributes object:
589
+
590
+ ```ejs
591
+ <% script({ src: 'https://example.com/widget.js', 'data-sleekcms-consent': 'marketing' }) %>
592
+ <% script({ content: "fbq('track', 'PageView');", 'data-sleekcms-consent': 'analytics' }) %>
593
+ ```
594
+
595
+ - Category names are yours to define (`analytics`, `marketing`, `personalization`, …): lowercase letters, digits, `-` and `_`. Reserved, never a category: `necessary`, `preferences`, `config`.
596
+ - The banner offers exactly the categories found on the site, each individually acceptable or declinable.
597
+ - A bare `data-sleekcms-consent` (no value) defaults to `analytics` with a build warning — always write the value.
598
+ - Scripts that don't need consent (strictly necessary) get no attribute.
599
+ - Analytics configured in `setup.json` (GA4, GTM, Meta Pixel, PostHog, …) is tagged `analytics` automatically. A tag snippet pasted by hand is not — tag it yourself, or better, move the id into `setup.json`.
600
+
601
+ **Gated embeds (iframes)** take the attribute directly in markup — YouTube, Google Maps, social embeds. Until consent is granted the iframe shows a "click to load" placeholder in its reserved space instead of contacting the third party:
602
+
603
+ ```html
604
+ <iframe data-sleekcms-consent="marketing" src="https://www.youtube.com/embed/VIDEO_ID" class="aspect-video w-full" allowfullscreen></iframe>
605
+ ```
606
+
607
+ **Customise the banner** — optional. One **empty** config element in the layout body; the build reads it and removes it from the output, so it never renders or affects layout:
608
+
609
+ ```html
610
+ <div data-sleekcms-consent data-position="bottom" data-theme="auto" data-policy-url="/privacy"></div>
611
+ ```
612
+
613
+ | Attribute | Values | Default |
614
+ |---|---|---|
615
+ | `data-position` | `bottom`, `bottom-left`, `bottom-right`, `modal` | `bottom` |
616
+ | `data-theme` | `light`, `dark`, `auto` | `auto` |
617
+ | `data-policy-url` | path to the privacy / cookie policy page | none (link hidden) |
618
+ | `data-locale` | banner language, e.g. `en`, `de` | auto-detect |
619
+ | `data-message` | banner text | built-in default |
620
+ | `data-accept-label` | accept button text | "Accept" |
621
+ | `data-decline-label` | decline button text | "Decline" |
622
+
623
+ No config element → all defaults. One per site: a second one is ignored with a build warning. Built-in banner languages: `en`, `de`, `fr`, `es`, `it`, `nl`, `pt`, `pl` — picked from `<html lang>`, then the browser; `data-locale` overrides.
624
+
625
+ **Styling** — the banner reads five CSS custom properties. Set them on `:root` in `css/tailwind.css`, from the site's `@theme` tokens:
626
+
627
+ ```css
628
+ @layer base {
629
+ :root {
630
+ --sleek-consent-bg: #1a1a2e;
631
+ --sleek-consent-text: #ffffff;
632
+ --sleek-consent-accent: #4f8cff;
633
+ --sleek-consent-on-accent: #ffffff; /* text on the accent button */
634
+ --sleek-consent-radius: 8px;
635
+ }
636
+ }
637
+ ```
638
+
639
+ **"Cookie settings" link** — regulation requires a way to change consent later, so put one in the footer; the runtime opens the preferences panel:
640
+
641
+ ```html
642
+ <a data-sleekcms-consent href="#">Cookie settings</a>
643
+ ```
644
+
645
+ **Behaviour** — first visit: banner shows, no gated script runs until the visitor chooses (no choice = no consent). Return visits: stored choices apply, no banner. Granting later activates without a reload; revoking reloads, since already-run scripts can't be undone. Adding or renaming a category re-prompts visitors on their next visit.
646
+
647
+ **JavaScript API** (custom integrations only — configuration belongs in HTML): `sleekcms.consent.get()` → `{ necessary: true, analytics: true, marketing: false }`, `sleekcms.consent.open()` opens the preferences panel, `sleekcms.consent.on('change', cb)` fires with the granted categories on every change. The runtime tag loads at the end of `<body>`; code that runs before it should listen for the `sleekcms:consent` window event instead — the same snapshot in `event.detail`, fired on load and on every change.
648
+
649
+ **Notes** — gating happens at build time: tagged scripts ship as `type="text/plain"` with their `src` stashed, and gated iframes ship without `src`, so nothing can fire before consent even on slow connections. The banner is a fixed overlay (or modal), so no layout shift. `<noscript>` content cannot be gated, and `document.write` inside a gated script is unsupported. SleekCMS provides the consent *mechanism*; which script belongs to which category, and the privacy policy itself, are the site owner's call — ask when unsure, and link the policy page via `data-policy-url`.
650
+
651
+ ## 15. Critical Rules Checklist
556
652
 
557
653
  1. **`+` is part of the key.** A collection's `+` appears on the model, the view, and the content file/folder: `models/pages/blog+.model`, `pages/blog+.ejs`, `content/pages/blog+/<slug>.json`. A file without the `+` is a *different*, single-type key.
558
654
  2. Models first: a content field that isn't in the `.model` won't sync. Update the model, then the content.
@@ -568,4 +664,5 @@ Snipcart powers stores. Use it when the business **sells products** and the user
568
664
  12. Image shortcuts (`"iconify:..."`, `"pexels:..."`, etc.) resolve **only** in `image`/`icon`-typed fields. CMS-editable icons are declared `icon: icon`, never `icon: text` — render with `svg(item.icon)`; fixed UI icons need no field: `svg('iconify:...')` directly in the view.
569
665
  13. Use entries (`entry(key+)`) for shared content (authors, testimonials, nav, footer) — not blocks. Block models cannot contain `block(...)` or `stack` fields; use blocks only when a group of fields also benefits from a reusable view.
570
666
  14. Always create an RSS feed for blogs (`rss.xml` key) and make every site strongly SEO- and sharing-friendly (reusable `seo` block rendered once from the layout head).
571
- 15. Site-wide setup — default page title, Tailwind on/off, sitemap on/off — lives in `setup.json` (§4). Change values there rather than hard-coding a site title into the layout, and never delete the file. Sitemap page exclusions are UI-only.
667
+ 15. Site-wide setup — default page title, Tailwind on/off, sitemap on/off, cookie consent on/off, analytics tag ids — lives in `setup.json` (§4). Change values there rather than hard-coding a site title into the layout, and never delete the file. Sitemap page exclusions are UI-only.
668
+ 16. Cookie consent (§14) is a `setup.json` switch plus `data-sleekcms-consent` attributes — tag third-party scripts through `script({ …, 'data-sleekcms-consent': 'category' })`, never a raw tag, and add a footer "Cookie settings" link. Only when the user asks for a banner.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as Le}from"node:fs";import{fileURLToPath as zn}from"node:url";import{createRequire as Yn}from"node:module";import It from"node:path";import{render as Vn}from"ink";import{execSync as Zt,spawn as P}from"child_process";import{program as Yt,Option as kt}from"commander";function te(){return Yt.name("cms-sync").description("SleekCMS CLI tool to sync and edit CMS templates locally. Log in once, then add the sites you want to sync \u2014 changes are watched and pushed automatically.").addOption(new kt("-v, --version","output the version number").hideHelp()).addOption(new kt("-e, --env <env>","Environment (localhost, development, production)").default("production").hideHelp()).addOption(new kt("-d, --dev <secret>","Dev secret for non-production envs (sent as the _dev cookie; enables OTP-in-response). Falls back to SLEEKCMS_DEV_SECRET.").hideHelp()).option("-p, --path <path>","Directory path for files (default: ~/SleekCMS)").addHelpText("after",`
2
+ import{readFileSync as Ne}from"node:fs";import{fileURLToPath as ar}from"node:url";import{createRequire as cr}from"node:module";import Ut from"node:path";import{render as lr}from"ink";import{execSync as ne,spawn as M}from"child_process";import{program as Zt,Option as Ct}from"commander";function re(){return Zt.name("cms-sync").description("SleekCMS CLI tool to sync and edit CMS templates locally. Log in once, then add the sites you want to sync \u2014 changes are watched and pushed automatically.").addOption(new Ct("-v, --version","output the version number").hideHelp()).addOption(new Ct("-e, --env <env>","Environment (localhost, development, production)").default("production").hideHelp()).addOption(new Ct("-d, --dev <secret>","Dev secret for non-production envs (sent as the _dev cookie; enables OTP-in-response). Falls back to SLEEKCMS_DEV_SECRET.").hideHelp()).option("-p, --path <path>","Directory path for files (default: ~/SleekCMS)").addHelpText("after",`
3
3
  Examples:
4
4
  $ sleekcms # log in and pick sites to sync
5
5
  $ sleekcms -e development # use a non-production backend
6
- `).parse(process.argv),Yt.opts()}function D(t){let e=process.platform==="win32"?`where ${t}`:`which ${t}`;try{return Zt(e,{stdio:"ignore"}),!0}catch{return!1}}function ee(t){if(process.platform!=="darwin")return!1;try{return Zt(`osascript -e 'id of application "${t}"'`,{stdio:"ignore"}),!0}catch{return!1}}function Qe(t,e){let n={detached:!0,stdio:"ignore",windowsHide:!0};process.platform==="win32"?P("cmd.exe",["/c",t,...e],n).unref():P(t,e,n).unref()}var Vt=[{name:"VS Code",cmd:"code",args:t=>["-n",t]},{name:"Cursor",cmd:"cursor",args:t=>["-n",t]}];function Qt(t,e){let r=(e?Vt.filter(s=>s.cmd===e):Vt).find(s=>D(s.cmd));return r?(Qe(r.cmd,r.args(t)),r.name):null}function Ze(t){if(process.platform==="darwin")return P("open",["-a","Terminal",t],{detached:!0,stdio:"ignore"}).unref(),!0;if(process.platform==="win32"){if(D("wt"))P("wt.exe",["-d",t],{detached:!0,stdio:"ignore"}).unref();else{let e=D("powershell")?"powershell.exe":"cmd.exe";P("cmd.exe",["/c","start","",e],{cwd:t,detached:!0,stdio:"ignore"}).unref()}return!0}return!1}function tn(t){return ee("iTerm")?(P("open",["-a","iTerm",t],{detached:!0,stdio:"ignore"}).unref(),!0):!1}function en(t){return`'${t.replace(/'/g,"'\\''")}'`}var nn=[{cmd:"x-terminal-emulator",args:t=>["-e","bash","-c",t]},{cmd:"gnome-terminal",args:t=>["--","bash","-c",t]},{cmd:"konsole",args:t=>["-e","bash","-c",t]},{cmd:"xfce4-terminal",args:t=>["-x","bash","-c",t]},{cmd:"kitty",args:t=>["bash","-c",t]},{cmd:"alacritty",args:t=>["-e","bash","-c",t]},{cmd:"xterm",args:t=>["-e","bash","-c",t]}];function ne(t,e){if(!D(e))return!1;if(process.platform==="darwin"){let r=`tell application "Terminal" to do script ("cd " & quoted form of "${t}" & " && ${e}")`;return P("osascript",["-e",r,"-e",'tell application "Terminal" to activate'],{detached:!0,stdio:"ignore"}).unref(),!0}if(process.platform==="win32")return D("wt")?P("wt.exe",["-d",t,"cmd","/k",e],{detached:!0,stdio:"ignore"}).unref():P("cmd.exe",["/c","start","","cmd","/k",e],{cwd:t,detached:!0,stdio:"ignore"}).unref(),!0;let n=`cd ${en(t)} && ${e}; exec bash`;for(let r of nn)if(D(r.cmd))try{return P(r.cmd,r.args(n),{cwd:t,detached:!0,stdio:"ignore"}).unref(),!0}catch{}return!1}function rn(t){return ne(t,"claude")}function sn(t){return ne(t,"pi")}function re(t){if(!t)return!1;try{return process.platform==="darwin"?P("open",[t],{detached:!0,stdio:"ignore"}).unref():process.platform==="win32"?P("cmd.exe",["/c","start","",t],{detached:!0,stdio:"ignore",windowsHide:!0}).unref():P("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}function se(){let t=process.platform==="darwin",e=process.platform==="win32",n=[];return(t||e)&&n.push({key:"terminal",label:"terminal",run:Ze}),ee("iTerm")&&n.push({key:"iterm",label:"iterm",run:tn}),D("code")&&n.push({key:"code",label:"vscode",run:r=>Qt(r,"code")!==null}),D("cursor")&&n.push({key:"cursor",label:"cursor",run:r=>Qt(r,"cursor")!==null}),D("claude")&&n.push({key:"claude",label:"claude",run:rn}),D("pi")&&n.push({key:"pi",label:"pi",run:sn}),n}import{EventEmitter as Mn}from"node:events";import{spawn as On}from"node:child_process";import Q from"fs-extra";import At from"path";import Dn from"chokidar";import m from"fs-extra";import an from"os";import S from"path";var ie={localhost:"http://app.sleekcms.test/api/mcp",development:"https://app.sleekcms.dev/api/mcp",production:"https://app.sleekcms.com/api/mcp"},cn=["src/views/blocks","src/views/entries","src/views/pages","src/views/layouts","src/models/blocks","src/models/entries","src/models/pages","src/content/pages","src/content/entries","src/public/js","src/public/css"],Bt=class extends Error{status;body;constructor(e,n,r){super(e),this.status=n,this.body=r}};async function Ct(t,e,n,r,s){let o=await fetch(t+r,{method:n,headers:{Authorization:`Bearer ${e}`,...s!==void 0?{"Content-Type":"application/json"}:{}},body:s!==void 0?JSON.stringify(s):void 0});if(!o.ok){let c=await o.text();throw new Bt(`${n} ${r} \u2192 ${o.status}: ${c}`,o.status,c)}return o.json()}async function ln(t,e){e&&(await m.outputFile(S.join(t,"AGENT.md"),e),await m.outputFile(S.join(t,"CLAUDE.md"),e),await m.outputFile(S.join(t,".vscode","copilot-instructions.md"),e)),await m.outputFile(S.join(t,".vscode","settings.json"),JSON.stringify({"files.associations":{"*.model":"javascript"},"[javascript]":{"editor.defaultFormatter":"esbenp.prettier-vscode"},"js/ts.validate.enabled":!1},null,2))}async function un(t,e){if(!e)return;let n=S.join(t,"build.mjs"),r=e.match(/build\.mjs v\d+/)?.[0];if(await m.pathExists(n)){let s=(await m.readFile(n,"utf-8")).match(/build\.mjs v\d+/)?.[0];if(s&&s===r)return}await m.outputFile(n,e)}async function dn(t,e,n){let r=S.join(t,".cache","token");if(await m.pathExists(r)){if((await m.readFile(r,"utf-8")).trim()===e)return;n?.("\u267B\uFE0F Workspace was tied to a different site \u2014 recreating it."),await m.emptyDir(t)}await m.outputFile(r,e)}function gn(t,e){let n=`${e.name.substr(0,20)} ${e.id}`.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,""),r=t||S.join(an.homedir(),"SleekCMS");return S.resolve(r,n)}async function Pt(t){let e=(t.token||"").trim();if(!e)throw new Error("syncSite: token is required");let n=t.log??(v=>console.log(v)),r=t.onProgress,s=(t.env||e.split("-")[2]||"production").toLowerCase(),o=ie[s]||ie.production,c=await Ct(o,e,"GET","/get_site"),h=t.viewsDir?S.resolve(t.viewsDir):gn(t.path,c);await m.ensureDir(h),await dn(h,e,n),await Promise.all(cn.map(v=>m.ensureDir(S.join(h,v)))),await un(h,t.buildScript);let p=S.join(h,".cache","state.json");t.flush&&await m.remove(p);let x=!await m.pathExists(p),w=x?{}:(await m.readJson(p)).fileMap||{},f=0,b=0;return x?({fileMap:w,pulled:b}=await Sn(h,o,e,n,r),await ln(h,t.agentMd)):f=await fn(h,w,o,e,n,r),await m.outputJson(p,{fileMap:w},{spaces:2}),{viewsDir:h,site:c,isFirstRun:x,pushed:f,pulled:b}}var hn=new Set([".DS_Store","Thumbs.db","desktop.ini"]);async function pn(t){let e=S.join(t,"src");if(!await m.pathExists(e))return[];let n=[];async function r(s){let o=await m.readdir(s,{withFileTypes:!0});for(let c of o){if(hn.has(c.name))continue;let h=S.join(s,c.name);c.isDirectory()?await r(h):c.isFile()&&n.push(S.relative(t,h).replace(/\\/g,"/"))}}return await r(e),n}async function fn(t,e,n,r,s,o){let c=[];for(let f of await pn(t)){let b=S.join(t,f),v=e[f],O=await m.stat(b);if(v&&v.mtimeMs===O.mtimeMs)continue;let R=await m.readFile(b,"utf-8");R.trim()&&c.push({rel:f,full:b,stat:O,content:R,prior:v})}if(c.length===0)return 0;let h;try{h=await Ct(n,r,"POST","/save_files",c.map(f=>({path:f.rel,content:f.content})))}catch(f){let b=f;return s(`\u274C Error saving files: ${b.body||b.message}`),0}let p=await wn(t),x=0,w=mn(h);for(let f=0;f<c.length;f++){let b=c[f],v=yn(b,f,h,w);if(o?.(f+1,c.length),!v){let O="No save response returned";p[b.rel]=O,s(`\u274C Error saving ${b.rel}: ${O}`);continue}if(v.error){p[b.rel]=v.error,s(`\u274C Error saving ${b.rel}: ${v.error}`);continue}delete p[b.rel],e[b.rel]={mtimeMs:b.stat.mtimeMs},s(`\u2705 ${b.prior?"Updated":"Created"} ${b.rel}`),x++}return await xn(t,p),x}function mn(t){let e=new Map;for(let n of t)n.path&&e.set(n.path,n);return e}function yn(t,e,n,r){let s=r.get(t.rel);if(s)return s;let o=n[e];if(!o?.path||o.path===t.rel)return o}var $t="sync-errors.log",Et=new Map;async function wn(t){let e=S.resolve(t),n=Et.get(e);if(n)return n;let r=await bn(e);return Et.set(e,r),r}async function bn(t){let e=S.join(t,$t);if(!await m.pathExists(e))return{};let n=await m.readFile(e,"utf-8"),r={};for(let s of n.split(`
7
- `)){let o=s.indexOf(": ");o>0&&(r[s.slice(0,o)]=s.slice(o+2))}return r}async function xn(t,e){let n=S.join(t,$t),r=Object.entries(e);if(r.length===0){await m.remove(n);return}await m.outputFile(n,r.map(([s,o])=>`${s}: ${o}`).join(`
6
+ `).parse(process.argv),Zt.opts()}function j(t){let e=process.platform==="win32"?`where ${t}`:`which ${t}`;try{return ne(e,{stdio:"ignore"}),!0}catch{return!1}}function se(t){if(process.platform!=="darwin")return!1;try{return ne(`osascript -e 'id of application "${t}"'`,{stdio:"ignore"}),!0}catch{return!1}}function sn(t,e){let n={detached:!0,stdio:"ignore",windowsHide:!0};process.platform==="win32"?M("cmd.exe",["/c",t,...e],n).unref():M(t,e,n).unref()}var te=[{name:"VS Code",cmd:"code",args:t=>["-n",t]},{name:"Cursor",cmd:"cursor",args:t=>["-n",t]}];function ee(t,e){let r=(e?te.filter(s=>s.cmd===e):te).find(s=>j(s.cmd));return r?(sn(r.cmd,r.args(t)),r.name):null}function on(t){if(process.platform==="darwin")return M("open",["-a","Terminal",t],{detached:!0,stdio:"ignore"}).unref(),!0;if(process.platform==="win32"){if(j("wt"))M("wt.exe",["-d",t],{detached:!0,stdio:"ignore"}).unref();else{let e=j("powershell")?"powershell.exe":"cmd.exe";M("cmd.exe",["/c","start","",e],{cwd:t,detached:!0,stdio:"ignore"}).unref()}return!0}return!1}function an(t){return se("iTerm")?(M("open",["-a","iTerm",t],{detached:!0,stdio:"ignore"}).unref(),!0):!1}function cn(t){return`'${t.replace(/'/g,"'\\''")}'`}var ln=[{cmd:"x-terminal-emulator",args:t=>["-e","bash","-c",t]},{cmd:"gnome-terminal",args:t=>["--","bash","-c",t]},{cmd:"konsole",args:t=>["-e","bash","-c",t]},{cmd:"xfce4-terminal",args:t=>["-x","bash","-c",t]},{cmd:"kitty",args:t=>["bash","-c",t]},{cmd:"alacritty",args:t=>["-e","bash","-c",t]},{cmd:"xterm",args:t=>["-e","bash","-c",t]}];function ie(t,e){if(!j(e))return!1;if(process.platform==="darwin"){let r=`tell application "Terminal" to do script ("cd " & quoted form of "${t}" & " && ${e}")`;return M("osascript",["-e",r,"-e",'tell application "Terminal" to activate'],{detached:!0,stdio:"ignore"}).unref(),!0}if(process.platform==="win32")return j("wt")?M("wt.exe",["-d",t,"cmd","/k",e],{detached:!0,stdio:"ignore"}).unref():M("cmd.exe",["/c","start","","cmd","/k",e],{cwd:t,detached:!0,stdio:"ignore"}).unref(),!0;let n=`cd ${cn(t)} && ${e}; exec bash`;for(let r of ln)if(j(r.cmd))try{return M(r.cmd,r.args(n),{cwd:t,detached:!0,stdio:"ignore"}).unref(),!0}catch{}return!1}function un(t){return ie(t,"claude")}function dn(t){return ie(t,"pi")}function oe(t){if(!t)return!1;try{return process.platform==="darwin"?M("open",[t],{detached:!0,stdio:"ignore"}).unref():process.platform==="win32"?M("cmd.exe",["/c","start","",t],{detached:!0,stdio:"ignore",windowsHide:!0}).unref():M("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}function ae(){let t=process.platform==="darwin",e=process.platform==="win32",n=[];return(t||e)&&n.push({key:"terminal",label:"terminal",run:on}),se("iTerm")&&n.push({key:"iterm",label:"iterm",run:an}),j("code")&&n.push({key:"code",label:"vscode",run:r=>ee(r,"code")!==null}),j("cursor")&&n.push({key:"cursor",label:"cursor",run:r=>ee(r,"cursor")!==null}),j("claude")&&n.push({key:"claude",label:"claude",run:un}),j("pi")&&n.push({key:"pi",label:"pi",run:dn}),n}import{EventEmitter as Gn}from"node:events";import{spawn as Un}from"node:child_process";import nt from"fs-extra";import It from"path";import qn from"chokidar";import{createHash as mn}from"crypto";import y from"fs-extra";import hn from"os";import v from"path";var ce={localhost:"http://app.sleekcms.test/api/mcp",development:"https://app.sleekcms.dev/api/mcp",production:"https://app.sleekcms.com/api/mcp"},fn=["src/views/blocks","src/views/entries","src/views/pages","src/views/layouts","src/models/blocks","src/models/entries","src/models/pages","src/content/pages","src/content/entries","src/public/js","src/public/css"],Pt=class extends Error{status;body;constructor(e,n,r){super(e),this.status=n,this.body=r}};async function ft(t,e,n,r,s){let i=await fetch(t+r,{method:n,headers:{Authorization:`Bearer ${e}`,...s!==void 0?{"Content-Type":"application/json"}:{}},body:s!==void 0?JSON.stringify(s):void 0});if(!i.ok){let a=await i.text();throw new Pt(`${n} ${r} \u2192 ${i.status}: ${a}`,i.status,a)}return i.json()}async function pn(t,e){e&&(await y.outputFile(v.join(t,"AGENT.md"),e),await y.outputFile(v.join(t,"CLAUDE.md"),e),await y.outputFile(v.join(t,".vscode","copilot-instructions.md"),e)),await y.outputFile(v.join(t,".vscode","settings.json"),JSON.stringify({"files.associations":{"*.model":"javascript"},"[javascript]":{"editor.defaultFormatter":"esbenp.prettier-vscode"},"js/ts.validate.enabled":!1},null,2))}async function yn(t,e){if(!e)return;let n=v.join(t,"build.mjs"),r=e.match(/build\.mjs v\d+/)?.[0];if(await y.pathExists(n)){let s=(await y.readFile(n,"utf-8")).match(/build\.mjs v\d+/)?.[0];if(s&&s===r)return}await y.outputFile(n,e)}async function wn(t,e,n){let r=v.join(t,".cache","token");if(await y.pathExists(r)){if((await y.readFile(r,"utf-8")).trim()===e)return;n?.("\u267B\uFE0F Workspace was tied to a different site \u2014 recreating it."),await y.emptyDir(t)}await y.outputFile(r,e)}function bn(t,e){let n=`${e.name.substr(0,20)} ${e.id}`.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,""),r=t||v.join(hn.homedir(),"SleekCMS");return v.resolve(r,n)}async function Mt(t){let e=(t.token||"").trim();if(!e)throw new Error("syncSite: token is required");let n=t.log??(x=>console.log(x)),r=t.onProgress,s=(t.env||e.split("-")[2]||"production").toLowerCase(),i=ce[s]||ce.production,a=await ft(i,e,"GET","/get_site"),u=t.viewsDir?v.resolve(t.viewsDir):bn(t.path,a);await y.ensureDir(u),await wn(u,e,n),await Promise.all(fn.map(x=>y.ensureDir(v.join(u,x)))),await yn(u,t.buildScript);let h=v.join(u,".cache","state.json");t.flush&&await y.remove(h);let w=!await y.pathExists(h),l=w?{}:(await y.readJson(h)).fileMap||{},b=0,S=0,g=0;if(w)({fileMap:l,pulled:S}=await Dn(u,i,e,n,r)),await pn(u,t.agentMd);else{let x=await Mn(u,l,i,e,n);g=x.renamed,b=await xn(u,l,i,e,n,r,x.held)}return await y.outputJson(h,{fileMap:l},{spaces:2}),{viewsDir:u,site:a,isFirstRun:w,pushed:b,pulled:S,renamed:g}}var tt=t=>mn("sha1").update(t).digest("hex"),Sn=new Set([".DS_Store","Thumbs.db","desktop.ini"]);async function Rt(t){let e=v.join(t,"src");if(!await y.pathExists(e))return[];let n=[];async function r(s){let i=await y.readdir(s,{withFileTypes:!0});for(let a of i){if(Sn.has(a.name))continue;let u=v.join(s,a.name);a.isDirectory()?await r(u):a.isFile()&&n.push(v.relative(t,u).replace(/\\/g,"/"))}}return await r(e),n}async function xn(t,e,n,r,s,i,a=new Set){let u=[];for(let S of await Rt(t)){if(a.has(S))continue;let g=v.join(t,S),x=e[S],C=await y.stat(g);if(x&&x.mtimeMs===C.mtimeMs){x.hash||(x.hash=tt(await y.readFile(g,"utf-8")));continue}let k=await y.readFile(g,"utf-8");k.trim()&&u.push({rel:S,full:g,stat:C,content:k,prior:x})}if(u.length===0)return 0;let h;try{h=await ft(n,r,"POST","/save_files",u.map(S=>({path:S.rel,content:S.content})))}catch(S){let g=S;return s(`\u274C Error saving files: ${g.body||g.message}`),0}let w=await de(t),l=0,b=vn(h);for(let S=0;S<u.length;S++){let g=u[S],x=Tn(g,S,h,b);if(i?.(S+1,u.length),!x){let C="No save response returned";w[g.rel]=C,s(`\u274C Error saving ${g.rel}: ${C}`);continue}if(x.error){w[g.rel]=x.error,s(`\u274C Error saving ${g.rel}: ${x.error}`);continue}delete w[g.rel],e[g.rel]={mtimeMs:g.stat.mtimeMs,hash:tt(g.content)},s(`\u2705 ${g.prior?"Updated":"Created"} ${g.rel}`),l++}return await ge(t,w),l}function vn(t){let e=new Map;for(let n of t)n.path&&e.set(n.path,n);return e}function Tn(t,e,n,r){let s=r.get(t.rel);if(s)return s;let i=n[e];if(!i?.path||i.path===t.rel)return i}var le=/^src\/(?:(?:models|views)\/(?:pages|entries|blocks)|content\/(?:pages|entries))\//,kn={PAGE:"pages",ENTRY:"entries",BLOCK:"blocks"},$n=t=>t.split("/").slice(0,3).join("/");function En(t,e,n){let r=new Map,s=(i,a)=>{let u=`${$n(i)}|${a}`;return r.has(u)||r.set(u,{from:[],to:[],hash:a}),r.get(u)};for(let i of t)s(i,n[i].hash).from.push(i);for(let[i,a]of e)s(i,a).to.push(i);return[...r.values()].filter(i=>i.from.length===1&&i.to.length===1).map(i=>({from:i.from[0],to:i.to[0],hash:i.hash}))}function Bn(t,e){let n=new Map,r=(s,i,a,u,h)=>{let w=n.get(s);w?(w.pairs.push(h),i<w.rank&&Object.assign(w,{from:a,to:u,rank:i})):n.set(s,{from:a,to:u,rank:i,pairs:[h]})};for(let s of t){let[,i,a,...u]=s.from.split("/"),h=s.to.split("/").slice(3);if(i==="content"&&a==="pages"&&u.length>1){let[l,...b]=u,[S,...g]=h;if(g.length===0)continue;S===l?r(`record:${s.from}`,0,s.from,s.to,s):b.join("/")===g.join("/")&&![...e].some(x=>x.startsWith(`src/content/pages/${l}/`))&&r(`pages:${l}`,3,`src/content/pages/${l}`,`src/content/pages/${S}`,s);continue}let w=u.join("/").replace(/\.[^./]+$/,"");r(`${a}:${w}`,i==="models"?0:i==="views"?1:2,s.from,s.to,s)}return[...n.values()].map(({from:s,to:i,pairs:a})=>({from:s,to:i,pairs:a}))}function ue(t,e){if(t.slug!==void 0&&t.new_slug!==void 0){let a=`src/content/pages/${t.key}/`,u=e.startsWith(a)?e.slice(a.length).match(/^(.+)\.(json|md)$/):null;return u&&u[1].toLowerCase()===t.slug.toLowerCase()?`${a}${t.new_slug}.${u[2]}`:null}let n=kn[t.type],{key:r,new_key:s}=t;if(!n||!s)return null;if(e===`src/models/${n}/${r}.model`)return`src/models/${n}/${s}.model`;if(e===`src/views/${n}/${r}.ejs`)return`src/views/${n}/${s}.ejs`;if(n!=="blocks"&&(e===`src/content/${n}/${r}.json`||e===`src/content/${n}/${r}.md`))return`src/content/${n}/${s}${v.extname(e)}`;let i=`src/content/pages/${r}/`;return n==="pages"&&r.endsWith("+")&&e.startsWith(i)?`src/content/pages/${s}/${e.slice(i.length)}`:null}async function Cn(t,e){let n=t;for(;n.startsWith(e+v.sep);){let r=await y.readdir(n).catch(()=>null);if(!r||r.length>0)return;await y.remove(n),n=v.dirname(n)}}async function Pn(t,e,n,r){let s=l=>v.join(t,l),i=v.join(t,"src"),a=l=>v.resolve(t,l).startsWith(i+v.sep),u=async(l,b)=>{e[l]={mtimeMs:(await y.stat(s(l))).mtimeMs,hash:b}},h=l=>Cn(v.dirname(s(l)),v.join(t,...l.split("/").slice(0,3))),w=[];for(let l of r){let b=ue(n.rename,l.from)??l.to;a(b)&&(b!==l.to&&(await y.move(s(l.to),s(b),{overwrite:!0}),await h(l.to),w.push(l.to)),delete e[l.from],w.push(l.from),await u(b,l.hash))}for(let l of new Set([...await Rt(t),...Object.keys(e)])){let b=ue(n.rename,l);if(!b||!a(b))continue;let S=await y.pathExists(s(l));if(S&&await y.pathExists(s(b)))continue;let g=e[l];if(delete e[l],w.push(l),!S)continue;let x=!!g?.hash&&g.mtimeMs===(await y.stat(s(l))).mtimeMs;await y.move(s(l),s(b)),x&&await u(b,g.hash),await h(l)}for(let l of n.files||[]){let b=e[l.path];if(!b||!a(l.path))continue;let S=await y.stat(s(l.path)).catch(()=>null);!S||S.mtimeMs!==b.mtimeMs||await y.readFile(s(l.path),"utf-8")!==l.content&&(await y.outputFile(s(l.path),l.content),await u(l.path,tt(l.content)))}return w}function On(t){try{let e=JSON.parse(t.body).message;if(e)return e}catch{}return(t.body||t.message||String(t)).slice(0,300)}async function Mn(t,e,n,r,s){let i=new Set,a=await Rt(t),u=new Set(a),h=Object.keys(e).filter(g=>!u.has(g)&&e[g].hash&&le.test(g));if(h.length===0)return{renamed:0,held:i};let w=new Map;for(let g of a){if(e[g]||!le.test(g))continue;let x=await y.readFile(v.join(t,g),"utf-8");x.trim()&&w.set(g,tt(x))}let l=Bn(En(h,w,e),u);if(l.length===0)return{renamed:0,held:i};let b=await de(t),S=0;for(let g of l){let x;try{x=await ft(n,r,"POST","/rename",{from:g.from,to:g.to})}catch(D){let L=D,q=On(L);if(L.status>=400&&L.status<500)s(`\u26A0\uFE0F Couldn't rename ${g.from} \u2192 ${g.to}: ${q} Saving it as a new file.`);else{for(let z of g.pairs)i.add(z.to),b[z.to]=`rename from ${z.from} failed: ${q}`;s(`\u274C Error renaming ${g.from} \u2192 ${g.to}: ${q}`)}continue}let C=await Pn(t,e,x,g.pairs);for(let D of[...C,...g.pairs.map(L=>L.to)])delete b[D];let[k,...P]=x.message.split(`
7
+ `);s(`\u{1F500} ${k}`);for(let D of P)s(` ${D}`);S++}return await ge(t,b),{renamed:S,held:i}}var Dt="sync-errors.log",Ot=new Map;async function de(t){let e=v.resolve(t),n=Ot.get(e);if(n)return n;let r=await Rn(e);return Ot.set(e,r),r}async function Rn(t){let e=v.join(t,Dt);if(!await y.pathExists(e))return{};let n=await y.readFile(e,"utf-8"),r={};for(let s of n.split(`
8
+ `)){let i=s.indexOf(": ");i>0&&(r[s.slice(0,i)]=s.slice(i+2))}return r}async function ge(t,e){let n=v.join(t,Dt),r=Object.entries(e);if(r.length===0){await y.remove(n);return}await y.outputFile(n,r.map(([s,i])=>`${s}: ${i}`).join(`
8
9
  `)+`
9
- `)}async function Sn(t,e,n,r,s){Et.delete(S.resolve(t)),await m.remove(S.join(t,$t)),r("\u{1F4E5} Fetching files...");let o=await Ct(e,n,"GET","/get_files"),c={},h=0;for(let p of o){let x=S.join(t,p.path);await m.outputFile(x,p.content);let w=(await m.stat(x)).mtimeMs;h++,s?.(h,o.length),c[p.path]={mtimeMs:w}}return r(`\u2714\uFE0F Synced ${o.length} file(s).`),{fileMap:c,pulled:h}}import vn from"path";import Tn from"chokidar";var kn=5e3,Bn=/(^|[/\\])(\.DS_Store|Thumbs\.db|desktop\.ini)$/,V=class{viewsDir;onSync;debounceMs;watcher=null;debounceTimer=null;isShuttingDown=!1;dirty=!1;syncInFlight=!1;constructor(e){this.viewsDir=e.viewsDir,this.onSync=e.onSync,this.debounceMs=e.debounceMs??kn}start(){if(this.watcher)return;let e=vn.join(this.viewsDir,"src");this.watcher=Tn.watch([e],{persistent:!0,ignoreInitial:!0,ignored:Bn}).on("change",()=>this.scheduleSync()).on("add",()=>this.scheduleSync()).on("unlink",()=>this.scheduleSync())}setShuttingDown(e){this.isShuttingDown=e}async stop(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.watcher&&(await this.watcher.close(),this.watcher=null)}scheduleSync(){this.isShuttingDown||(this.dirty=!0,this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.flush(),this.debounceMs))}async flush(){if(this.debounceTimer=null,!(!this.dirty||this.isShuttingDown)){if(this.syncInFlight){this.debounceTimer=setTimeout(()=>this.flush(),this.debounceMs);return}this.dirty=!1,this.syncInFlight=!0;try{await this.onSync()}catch(e){let n=e;console.error("\u274C Sync failed:",n.body||n.message)}finally{this.syncInFlight=!1}}}};import gt from"fs-extra";import En from"os";import ae from"path";var oe={localhost:"http://app.sleekcms.test/api",development:"https://app.sleekcms.dev/api",production:"https://app.sleekcms.com/api"};function Ot(t){return oe[t]||oe.production}var Cn=ae.join(En.homedir(),"SleekCMS"),ht=ae.join(Cn,"auth.json"),Mt;function ce(t){Mt=t||void 0}function Dt(t){let e=[];return t&&e.push(`_auth=${t}`),Mt&&e.push(`_dev=${Mt}`),e.length?e.join("; "):void 0}var L=class extends Error{status;body;constructor(e,n,r=""){super(e),this.name="UserAuthError",this.status=n,this.body=r}};async function le(){try{let t=await gt.readJson(ht);return t&&t.userToken?t:null}catch{return null}}async function ue(t){await gt.outputJson(ht,t,{spaces:2});try{await gt.chmod(ht,384)}catch{}}async function de(){await gt.remove(ht)}async function q(t){let e={},n=Dt(t.userToken);n&&(e.Cookie=n),t.subdomain&&(e["x-subdomain"]=t.subdomain),t.slug&&(e.site=t.slug),t.body!==void 0&&(e["Content-Type"]="application/json");let r=await fetch(Ot(t.env)+t.path,{method:t.method,headers:e,body:t.body!==void 0?JSON.stringify(t.body):void 0});if(!r.ok){let s=await r.text().catch(()=>"");throw new L(`${t.method} ${t.path} \u2192 ${r.status}: ${s}`,r.status,s)}return r.json()}async function ge(t,e){let n={"Content-Type":"application/json"},r=Dt();r&&(n.Cookie=r);let s=await fetch(Ot(t)+"/auth/otp",{method:"PUT",headers:n,body:JSON.stringify({email:e})});if(!s.ok){let c=await s.text().catch(()=>"");throw new L(`Failed to send OTP \u2192 ${s.status}: ${c}`,s.status,c)}return{otp:(await s.json().catch(()=>({}))).do_not_use_in_code}}async function Lt(t,e,n){let r={"Content-Type":"application/json"},s=Dt();s&&(r.Cookie=s);let o=await fetch(Ot(t)+"/auth/login",{method:"POST",headers:r,body:JSON.stringify({email:e,otp:n})});if(!o.ok){let p=await o.text().catch(()=>"");throw new L(`Login failed \u2192 ${o.status}: ${p}`,o.status,p)}let c=o.headers.getSetCookie?.()??[],h=Pn(c);if(!h)throw new L("Login succeeded but no _auth cookie was returned.",500);return h}function Pn(t){for(let e of t){let n=e.match(/(?:^|;\s*)_auth=([^;]+)/);if(n&&n[1])return decodeURIComponent(n[1])}return null}async function Rt(t,e){return q({env:t,userToken:e,method:"GET",path:"/org"})}async function he(t,e,n){return q({env:t,userToken:e,method:"GET",path:"/site",subdomain:n})}function $n(t){if(t==null)return"(unknown error)";if(typeof t=="string")return t;if(typeof t!="object")return String(t);let e=t,n=typeof e.file_path=="string"?e.file_path:"",r=typeof e.path=="string"?e.path:typeof e.filename=="string"?e.filename:"",s=n||r,o=e.line!=null?`:${e.line}`:"",c=e.column!=null?`:${e.column}`:"",h=typeof e.message=="string"&&e.message?e.message:typeof e.name=="string"&&e.name?e.name:"",p=n&&r&&r!==n?` (on page ${r})`:"";if(s)return`${s}${o}${c}${p}: ${h||"(no message)"}`;if(h)return h;try{return JSON.stringify(t)}catch{return"(unknown error)"}}async function pe(t,e,n,r){let s;try{s=await q({env:t,userToken:e,method:"GET",path:"/site_build",subdomain:n,slug:r})}catch{return null}if(!Array.isArray(s)||s.length===0)return null;let o=s[0],c=s.find(p=>typeof p.url=="string"&&p.url.length>0),h=Array.isArray(o.errors)?o.errors.map($n):[];return h.length>0?{status:"error",mode:"preview",errors:h,url:c?.url||void 0,at:o.updated_at?Date.parse(o.updated_at):void 0}:c?{status:"success",mode:"preview",url:c.url||void 0,at:c.updated_at?Date.parse(c.updated_at):void 0}:null}async function fe(t,e,n,r){let{token:s}=await q({env:t,userToken:e,method:"POST",path:"/site_user/api_token",subdomain:n,slug:r,body:{refresh:!1}});return s}async function me(t,e,n){let r=await q({env:t,userToken:e,method:"POST",path:"/org",body:n?{name:n}:{}});return{id:r.id,name:r.name,subdomain:r.subdomain}}async function ye(t,e,n,r){let s=await q({env:t,userToken:e,method:"POST",path:"/site",subdomain:n,body:{name:r}});return{id:s.id,name:s.name,slug:s.slug}}var we=500;function Ln(){return new Date().toTimeString().slice(0,8)}function pt(t){let e=t;if(e.body){try{let n=JSON.parse(e.body);if(n&&typeof n.message=="string")return n.message;if(n&&typeof n.error=="string")return n.error}catch{}return e.body}return e.message}async function be(t){t&&(await Q.remove(At.join(t,"src")),await Q.remove(At.join(t,".cache","state.json")))}var ft=class extends Mn{env="production";userToken="";email;catalog=[];synced=new Map;message;messageIsError=!1;agentMd;buildScript;isShuttingDown=!1;constructor(e,n){super(),this.agentMd=e,this.buildScript=n}changed(){this.emit("change")}flash(e,n=!1){this.message=e,this.messageIsError=n,this.changed()}appendLog(e,n){let r=this.synced.get(e);r&&(r.logs.push({time:Ln(),text:n}),r.logs.length>we&&r.logs.splice(0,r.logs.length-we),this.changed())}siteCount(){return this.synced.size}syncingCount(){let e=0;for(let n of this.synced.values())n.status==="syncing"&&e++;return e}watchingCount(){let e=0;for(let n of this.synced.values())(n.status==="watching"||n.status==="syncing")&&e++;return e}async init(e,n,r){this.env=e,this.userToken=n,r&&(this.email=r);let s=await Rt(e,n);await this.buildCatalog(s)}async buildCatalog(e){let n=[];for(let r of e)try{n.push({org:r,sites:await he(this.env,this.userToken,r.subdomain)})}catch(s){if(s instanceof L&&s.status===401)throw s;n.push({org:r,sites:[]})}this.catalog=n,this.changed()}isSynced(e){return this.synced.has(e)}availableSites(){let e=[];for(let n of this.catalog)for(let r of n.sites)this.synced.has(r.id)||e.push({org:n.org,site:r});return e}async addSite(e,n){if(this.synced.has(n.id))return;let r;try{r=await fe(this.env,this.userToken,e.subdomain,n.slug)}catch(o){this.flash(`Failed to authorize "${n.name}": ${pt(o)}`,!0);return}let s={org:e,site:n,siteToken:r,viewsDir:"",status:"syncing",logs:[]};this.synced.set(n.id,s),this.appendLog(n.id,"\u2795 added \u2014 pulling files"),this.changed();try{let o=await Pt({token:r,env:this.env,agentMd:this.agentMd,buildScript:this.buildScript,log:c=>this.appendLog(n.id,c),onProgress:(c,h)=>{s.progress={done:c,total:h},this.changed()}});s.viewsDir=o.viewsDir,s.progress=void 0,s.status="watching",s.lastSyncedAt=Date.now(),s.lastMsg=`pulled ${o.pulled} file(s)`,s.watcher=new V({viewsDir:o.viewsDir,onSync:()=>this.runSync(n.id)}),s.watcher.start(),this.watchBuildStatus(n.id),this.seedLastBuild(n.id),this.appendLog(n.id,"\u{1F440} watching for changes")}catch(o){let c=o;s.status="error",s.progress=void 0,s.lastMsg=c.body||c.message,this.appendLog(n.id,`\u274C ${c.body||c.message}`)}this.changed()}async removeSite(e){let n=this.synced.get(e);n&&(n.watcher?.setShuttingDown(!0),await n.watcher?.stop(),await n.buildWatcher?.close(),await be(n.viewsDir),this.synced.delete(e),this.changed())}buildSite(e,n=!1){let r=this.synced.get(e);if(!r||!r.viewsDir)return;let s=n?"live":"preview";this.writeBuildFile(r.viewsDir,{status:"building",mode:s,stage:"queued",url:r.lastBuild?.url,at:Date.now()}),On("node",n?["build.mjs","--publish"]:["build.mjs"],{cwd:r.viewsDir,stdio:"ignore"}).on("error",h=>{this.writeBuildFile(r.viewsDir,{status:"error",mode:s,errors:[h.message],url:r.lastBuild?.url,at:Date.now()}),this.appendLog(e,`\u274C build: ${h.message}`)}),this.appendLog(e,n?"\u{1F680} publishing\u2026":"\u{1F528} building\u2026")}buildFile(e){return At.join(e,".cache","build.json")}async readBuildFile(e){try{let n=await Q.readJson(this.buildFile(e));return n&&n.status?n:null}catch{return null}}async writeBuildFile(e,n){let r=this.buildFile(e),s=`${r}.tmp`;try{await Q.outputJson(s,n),await Q.rename(s,r)}catch{}}watchBuildStatus(e){let n=this.synced.get(e);!n||!n.viewsDir||(n.buildWatcher=Dn.watch(this.buildFile(n.viewsDir),{ignoreInitial:!1}).on("add",()=>{this.readBuildStatus(e)}).on("change",()=>{this.readBuildStatus(e)}))}async readBuildStatus(e){let n=this.synced.get(e);if(!n)return;let r=await this.readBuildFile(n.viewsDir);if(!r)return;let s=n.lastBuild?.url;n.lastBuild={status:r.status,mode:r.mode??"preview",url:r.url??(r.status==="success"?void 0:s),errors:r.errors,stage:r.stage,at:r.at},this.changed()}async seedLastBuild(e){let n=this.synced.get(e);if(!n)return;let r=await pe(this.env,this.userToken,n.org.subdomain,n.site.slug);if(!r)return;let s=this.synced.get(e);if(!s)return;let o=await this.readBuildFile(s.viewsDir);o?.at!=null&&(r.at==null||o.at>=r.at)||await this.writeBuildFile(s.viewsDir,r)}async ensureOrg(e){if(e)return e;if(this.catalog[0])return this.catalog[0].org;let n;try{n=await Rt(this.env,this.userToken)}catch(r){return this.flash(`Couldn't check your organizations: ${pt(r)}`,!0),null}if(n.length>0){try{await this.buildCatalog(n)}catch{}return n[0]}try{let r=this.email?`${this.email.split("@")[0]}'s Sites`:"My Sites";this.flash("No organization yet \u2014 creating one\u2026");let s=await me(this.env,this.userToken,r);return this.catalog=[...this.catalog,{org:s,sites:[]}],this.changed(),s}catch(r){return this.flash(`Could not create an organization: ${pt(r)}`,!0),null}}async createNewSite(e,n){let r=await this.ensureOrg(n);if(!r)return;let s;try{s=await ye(this.env,this.userToken,r.subdomain,e)}catch(c){this.flash(`Could not create "${e}": ${pt(c)}`,!0);return}let o=this.catalog.find(c=>c.org.id===r.id);o&&!o.sites.some(c=>c.id===s.id)&&(o.sites=[...o.sites,s]),this.flash(`Created "${s.name}".`),await this.addSite(r,s)}async refetch(e){await this.runSync(e,{flush:!0})}async runSync(e,n={}){let r=this.synced.get(e);if(!(!r||!r.viewsDir||r.status==="paused")){r.status="syncing",this.changed();try{let s=await Pt({token:r.siteToken,env:this.env,viewsDir:r.viewsDir,agentMd:this.agentMd,buildScript:this.buildScript,flush:n.flush,log:o=>this.appendLog(e,o),onProgress:(o,c)=>{r.progress={done:o,total:c},this.changed()}});r.progress=void 0,r.status="watching",r.lastSyncedAt=Date.now(),n.flush?r.lastMsg=`re-fetched ${s.pulled} file(s)`:r.lastMsg=s.pushed>0?`pushed ${s.pushed} file(s)`:"up to date"}catch(s){let o=s;r.status="error",r.progress=void 0,r.lastMsg=o.body||o.message,this.appendLog(e,`\u274C ${o.body||o.message}`)}this.changed()}}async togglePause(e){let n=this.synced.get(e);!n||!n.viewsDir||(n.status==="paused"?(n.watcher=new V({viewsDir:n.viewsDir,onSync:()=>this.runSync(e)}),n.watcher.start(),n.status="watching",this.appendLog(e,"\u25B6 resumed \u2014 watching for changes")):(n.watcher?.setShuttingDown(!0),await n.watcher?.stop(),n.watcher=void 0,n.progress=void 0,n.status="paused",this.appendLog(e,"\u23F8 paused")),this.changed())}async cleanupAll(){for(let e of this.synced.values())e.watcher?.setShuttingDown(!0),await e.watcher?.stop(),await e.buildWatcher?.close(),await be(e.viewsDir)}async exit(){this.isShuttingDown||(this.isShuttingDown=!0,await this.cleanupAll(),this.emit("exit"))}async logout(){this.isShuttingDown||(this.isShuttingDown=!0,await de(),await this.cleanupAll(),this.emit("logout"))}};import{useEffect as Hn,useState as Me}from"react";import{Box as Jn,Text as Xn,useInput as Kn}from"ink";import{useState as Z}from"react";import{Box as tt,Text as H}from"ink";import xe from"ink-text-input";import{jsx as N,jsxs as J}from"react/jsx-runtime";function Se(t){let e=t;return e?.body||e?.message||String(t)}function ve({env:t,initialError:e,onAuthed:n}){let[r,s]=Z("email"),[o,c]=Z(""),[h,p]=Z(""),[x,w]=Z(e),[f,b]=Z(),v=async R=>{let E=R.trim();if(E){w(void 0),s("busy");try{let{otp:$}=await ge(t,E);if(c(E),$){let X=await Lt(t,E,$);await n(X,E);return}b(`OTP sent to ${E}`),s("otp")}catch($){w(Se($)),s("email")}}},O=async R=>{let E=R.trim();if(E){w(void 0),s("busy");try{let $=await Lt(t,o,E);await n($,o)}catch($){w(Se($)),p(""),s("otp")}}};return J(tt,{flexDirection:"column",children:[J(H,{bold:!0,children:["\u25C6 SleekCMS \u2014 sign in",t!=="production"?` (${t})`:""]}),x?J(H,{color:"red",children:["\u2717 ",x]}):null,r==="email"&&J(tt,{marginTop:1,children:[N(H,{children:"Email: "}),N(xe,{value:o,onChange:c,onSubmit:v,placeholder:"you@example.com"})]}),r==="otp"&&J(tt,{flexDirection:"column",marginTop:1,children:[f?N(H,{color:"green",children:f}):null,J(tt,{children:[N(H,{children:"Enter OTP: "}),N(xe,{value:h,onChange:p,onSubmit:O})]})]}),r==="busy"&&N(tt,{marginTop:1,children:N(H,{children:"Working\u2026"})})]})}import{Fragment as An,useEffect as et,useMemo as Fn,useState as C}from"react";import{Box as g,Text as l,useInput as jn,useStdout as _n}from"ink";import Be from"ink-text-input";import Rn from"node:os";function Ft(t){if(!t)return"\u2014";let e=Rn.homedir();return t.startsWith(e)?"~"+t.slice(e.length):t}function jt(t){if(!t)return"\u2014";let e=Math.floor((Date.now()-t)/1e3);if(e<5)return"just now";if(e<60)return`${e}s ago`;let n=Math.floor(e/60);if(n<60)return`${n}m ago`;let r=Math.floor(n/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function yt(t){return!t||t.total<=0?null:Math.round(t.done/t.total*100)}var mt=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Te(t){return mt[(t%mt.length+mt.length)%mt.length]}function wt(t){switch(t){case"syncing":return{dot:"\u25CF",color:"green",label:"syncing"};case"watching":return{dot:"\u25CB",color:"gray",label:"watching"};case"paused":return{dot:"\u2016",color:"yellow",label:"paused"};case"error":return{dot:"\u2717",color:"red",label:"error"}}}function ke(t){if(!t)return{dot:"\xB7",color:"gray",label:"\u2014"};switch(t.status){case"building":return{dot:"\u2234",color:"yellow",label:"building"};case"error":return{dot:"\u2717",color:"red",label:"failed"};case"success":return{dot:"\u2713",color:"green",label:t.mode==="live"?"live":"preview"}}}import{Fragment as nt,jsx as i,jsxs as u}from"react/jsx-runtime";var U=(t,e)=>e<=0?0:Math.max(0,Math.min(t,e-1)),In=16,Nn=5,Un="greenBright",Ee="blueBright",Ce="magentaBright";function Pe({k:t,bg:e=Un}){return i(l,{color:"black",backgroundColor:e,children:` ${t} `})}function j({k:t,label:e,width:n=16,bg:r}){return u(g,{width:n,children:[i(Pe,{k:t,bg:r}),i(l,{color:"gray",children:` ${e}`})]})}function B({k:t,label:e,bg:n}){return u(g,{marginBottom:1,children:[i(g,{width:9,children:i(Pe,{k:t,bg:n})}),i(l,{color:"gray",children:e})]})}function Wn({launchers:t}){return u(g,{flexDirection:"column",borderStyle:"round",borderColor:"green",paddingX:2,paddingY:1,width:66,children:[u(g,{justifyContent:"space-between",children:[i(l,{color:"green",bold:!0,children:"KEYBOARD SHORTCUTS"}),i(l,{color:"gray",children:"? or esc to close"})]}),u(g,{marginTop:1,children:[u(g,{flexDirection:"column",width:34,children:[i(l,{color:"cyan",bold:!0,children:"Navigate"}),i(B,{k:"\u2191\u2193",label:"move selection"}),i(B,{k:"tab",label:"focus logs \xB7 \u2191\u2193 scroll"}),i(B,{k:"esc",label:"clear filter / back"}),i(g,{marginTop:1,children:i(l,{color:"cyan",bold:!0,children:"Global"})}),i(B,{k:"/",label:"search sites"}),i(B,{k:"n",label:"new site"}),i(B,{k:"?",label:"toggle this help",bg:Ee}),i(B,{k:"q",label:"logout"}),i(B,{k:"ctrl+c",label:"quit"})]}),u(g,{flexDirection:"column",flexGrow:1,children:[i(l,{color:"cyan",bold:!0,children:"Site actions"}),i(B,{k:"s",label:"start sync"}),i(B,{k:"d",label:"disconnect"}),i(B,{k:"p",label:"pause / resume sync"}),i(B,{k:"b",label:"build preview"}),i(B,{k:"B",label:"publish live"}),i(B,{k:"o",label:"open preview url"}),t.map((e,n)=>i(B,{k:String(n+1),label:`open in ${e.label}`,bg:Ce},e.key))]})]})]})}function Gn({percent:t,width:e=28}){let n=Math.round(Math.max(0,Math.min(100,t))/100*e);return u(l,{children:[i(l,{color:"green",children:"\u2588".repeat(n)}),i(l,{color:"gray",children:"\u2591".repeat(Math.max(0,e-n))})]})}function qn({frame:t,width:e=24,seg:n=6}){let r=e+n,s=t%r-n,o="";for(let c=0;c<e;c++)o+=c>=s&&c<s+n?"\u25AE":"\u25AF";return i(l,{color:"yellow",children:o})}function $e({controller:t}){let{stdout:e}=_n(),n=Fn(()=>se(),[]),[,r]=C(0),[s,o]=C(()=>new Date),[c,h]=C(null),[p,x]=C(()=>t.synced.size>0||t.catalog.some(d=>d.sites.length>0)?"dashboard":t.catalog.length<=1?"newName":"newOrg"),[w,f]=C(0),[b,v]=C(""),[O,R]=C(!1),[E,$]=C(""),[X,rt]=C(!1),[Nt,Ae]=C(0),[Ut,Wt]=C(!1),[Fe,je]=C(!1),[st,it]=C("list"),[_e,K]=C(0),Gt=()=>r(a=>a+1);et(()=>(t.on("change",Gt),()=>{t.off("change",Gt)}),[t]),et(()=>{let a,d=()=>{o(new Date),a=setTimeout(d,6e4-Date.now()%6e4)};return a=setTimeout(d,6e4-Date.now()%6e4),()=>clearTimeout(a)},[]);let F=t.catalog.flatMap(a=>a.sites.map(d=>({org:a.org,site:d,entry:t.synced.get(d.id)}))).sort((a,d)=>(a.entry?0:1)-(d.entry?0:1)),_=E.trim().toLowerCase(),M=_?F.filter(a=>a.site.name.toLowerCase().includes(_)||(a.org.name||a.org.subdomain||"").toLowerCase().includes(_)):F,W=M.findIndex(a=>a.site.id===c);W<0&&(W=0);let T=M[W],y=T?.entry,ot=Math.max(3,(e?.rows??30)-26),I=y?.logs??[],xt=Math.max(0,I.length-ot),Ie=Math.min(_e,xt),at=I.length-Ie,qt=I.slice(Math.max(0,at-ot),at),Ne=T?.site.id;et(()=>K(0),[Ne]);let A=y?.lastBuild,St=A?.status==="building",vt=ke(A),ct=St;if(!ct){for(let a of t.synced.values())if(a.status==="syncing"){ct=!0;break}}et(()=>{if(!ct)return;let a=setInterval(()=>Ae(d=>d+1),120);return()=>clearInterval(a)},[ct]);let Ht=M.findIndex(a=>!a.entry),G=Math.max(Nn,(e?.rows??30)-In),z=M.length,lt=z>G,Ue=Math.floor(G/2),Y=lt?Math.max(0,Math.min(W-Ue,z-G)):0,Tt=Math.min(z,Y+G),We=M.slice(Y,Tt);et(()=>{p!=="dashboard"||O||F.length>0||(v(""),t.catalog.length<=1?x("newName"):(f(0),x("newOrg")))},[F.length,p,O,t.catalog.length]);let ut=a=>{if(M.length===0)return;let d=U(W+a,M.length);h(M[d].site.id)},Ge=()=>{if(T){if(T.entry){t.flash("Already syncing \u2014 press d to disconnect.");return}t.addSite(T.org,T.site)}},qe=()=>{T?.entry&&t.removeSite(T.site.id)},He=()=>{v(""),t.catalog.length<=1?x("newName"):(f(0),x("newOrg"))},Je=a=>{if(!y){t.flash("Press s to start syncing this site first.",!0);return}je(!0);let d=a.run(y.viewsDir);t.flash(d?`Opened ${a.label} at ${Ft(y.viewsDir)}`:`Couldn't open ${a.label}.`,!d)},Xe=()=>{if(!y){t.flash("Press s to start syncing this site first.",!0);return}t.togglePause(y.site.id)},Jt=a=>{if(!y){t.flash("Press s to start syncing this site first.",!0);return}t.buildSite(y.site.id,a),t.flash(a?"Publishing \u2014 making this live\u2026":"Building preview\u2026")},Ke=()=>{let a=y?.lastBuild?.url;if(!a){t.flash("No build URL yet \u2014 press b to build.",!0);return}t.flash(re(a)?`Opening ${a}`:"Couldn't open the browser.",!1)};jn((a,d)=>{if(Ut){(a==="?"||d.escape)&&Wt(!1);return}if(p==="newOrg"){d.escape?x("dashboard"):d.upArrow?f(k=>U(k-1,t.catalog.length)):d.downArrow?f(k=>U(k+1,t.catalog.length)):d.return&&x("newName");return}if(X){d.escape?(rt(!1),$(""),h(null)):d.return?rt(!1):d.upArrow?ut(-1):d.downArrow&&ut(1);return}if(d.tab){st==="logs"?it("list"):y&&it("logs"),K(0);return}if(st==="logs"){if(d.escape){it("list"),K(0);return}if(d.upArrow)return void K(k=>Math.min(k+1,xt));if(d.downArrow)return void K(k=>Math.max(0,k-1))}if(d.escape){$(""),h(null);return}if(a==="?")return void Wt(!0);if(a==="/")return it("list"),void rt(!0);if(a==="n")return void He();if(a==="q")return void t.logout();if(d.upArrow)return ut(-1);if(d.downArrow)return ut(1);if(a==="s")return Ge();if(a==="d")return qe();if(a==="p")return Xe();if(a==="b")return Jt(!1);if(a==="B")return Jt(!0);if(a==="o")return Ke();if(a>="1"&&a<="9"){let k=n[parseInt(a,10)-1];if(k)return Je(k)}},{isActive:p!=="newName"});let ze=async a=>{let d=a.trim();if(!d){x("dashboard");return}let k=t.catalog[U(w,t.catalog.length)]?.org??t.catalog[0]?.org;R(!0),x("dashboard"),await t.createNewSite(d,k),R(!1)},Xt=u(g,{justifyContent:"space-between",paddingX:1,children:[u(g,{children:[i(l,{color:"green",bold:!0,children:"\u25C6 SleekCMS"}),i(l,{color:"gray",children:" \u2502 "}),i(l,{children:t.email??"signed in"})]}),u(g,{children:[u(l,{color:"gray",children:[_?`${M.length}/${F.length}`:F.length," sites \xB7 "]}),u(l,{color:"green",children:[t.watchingCount()," watching"]}),i(l,{color:"gray",children:` \xB7 ${s.toTimeString().slice(0,5)}`})]})]});return Ut?u(g,{flexDirection:"column",children:[Xt,i(g,{marginTop:1,justifyContent:"center",children:i(Wn,{launchers:n})})]}):u(g,{flexDirection:"column",children:[Xt,u(g,{children:[u(g,{flexDirection:"column",borderStyle:"round",borderColor:"green",width:38,paddingX:1,children:[u(g,{justifyContent:"space-between",children:[i(l,{color:"green",bold:!0,children:"SITE WORKSPACES"}),F.length>G&&!X&&!_?i(l,{color:"gray",children:"/ filter"}):null]}),X?u(g,{children:[i(l,{color:"cyan",children:"/ "}),i(Be,{value:E,onChange:$,onSubmit:()=>rt(!1),placeholder:"type to filter\u2026"})]}):_?i(l,{color:"gray",children:`/ ${E} \xB7 ${M.length} match${M.length===1?"":"es"}`}):null,i(g,{flexDirection:"column",marginTop:1,minHeight:lt?G+3:void 0,children:M.length===0?i(l,{color:"gray",children:_?`no matches for \u201C${E}\u201D`:"no sites \u2014 press n to create one"}):u(nt,{children:[lt?i(l,{color:"gray",children:Y>0?` \u2191 ${Y} more`:" "}):null,We.map((a,d)=>{let k=Y+d,Kt=a.entry?wt(a.entry.status):{dot:"\xB7",color:"gray",label:"idle"},zt=yt(a.entry?.progress),Ye=a.entry?a.entry.status==="syncing"&&zt!=null?`${zt}%`:a.entry.status==="paused"?"paused":"--":"",dt=k===W,Ve=k===Ht&&Ht>0;return u(An,{children:[Ve?i(l,{color:"gray",children:"\u2500".repeat(34)}):null,u(g,{justifyContent:"space-between",children:[u(g,{children:[i(l,{color:dt?"green":"gray",children:dt?"\u276F ":" "}),u(l,{color:a.entry?.status==="watching"||a.entry?.status==="syncing"?"green":Kt.color,children:[a.entry?.status==="syncing"?Te(Nt):a.entry?.status==="watching"?"\u25CF":Kt.dot," "]}),i(l,{color:dt?"white":a.entry?void 0:"gray",bold:dt,children:a.site.name})]}),i(l,{color:a.entry?.status==="syncing"?"green":"gray",children:Ye})]})]},a.site.id)}),lt?i(l,{color:"gray",children:Tt<z?` \u2193 ${z-Tt} more`:" "}):null]})}),i(g,{marginTop:1,children:i(l,{color:"gray",children:"+ new site \xB7 n"})})]}),u(g,{flexDirection:"column",flexGrow:1,marginLeft:1,children:[i(g,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,children:T?u(nt,{children:[u(l,{children:[T.org.name||T.org.subdomain?u(l,{color:"gray",children:[T.org.name||T.org.subdomain," / "]}):null,i(l,{color:"cyan",bold:!0,children:T.site.name})]}),u(g,{marginTop:1,children:[i(l,{color:"gray",children:"status "}),y?i(l,{color:wt(y.status).color,children:`: ${wt(y.status).label}`}):i(l,{color:"gray",children:": idle \u2014 not syncing (press s to start)"})]}),u(g,{children:[i(l,{color:"gray",children:"path "}),i(l,{wrap:"truncate-end",children:y?.viewsDir?Ft(y.viewsDir):"\u2014"})]}),u(g,{children:[i(l,{color:"gray",children:"synced "}),i(l,{children:jt(y?.lastSyncedAt)}),i(l,{color:"gray",children:" progress "}),i(l,{color:y?.status==="syncing"?"green":"gray",children:y?.status==="syncing"?`${yt(y.progress)??0}%`:"\u2014"})]}),i(g,{marginTop:1,children:i(Gn,{percent:y?y.status==="syncing"?yt(y.progress)??0:y.status==="watching"?100:0:0})}),y?u(nt,{children:[u(g,{marginTop:1,children:[i(l,{color:"gray",children:"build "}),i(l,{color:vt.color,children:`${vt.dot} ${vt.label}`}),i(l,{color:"gray",children:" last "}),i(l,{children:St?"in progress":jt(A?.at)})]}),u(g,{children:[i(l,{color:"gray",children:"url "}),A?.url?i(l,{color:"cyan",wrap:"truncate-end",children:A.url}):i(l,{color:"gray",children:"\u2014"})]}),A?.status==="error"&&A.errors?.length?u(g,{children:[i(l,{color:"red",children:"\u258C "}),i(l,{color:"red",wrap:"truncate-end",children:`\u2717 ${A.errors[0]}`})]}):null,St?u(g,{children:[i(qn,{frame:Nt}),i(l,{color:"gray",children:` ${A?.stage??"building"}`})]}):null]}):null]}):i(l,{color:"gray",children:"No site selected."})}),p==="dashboard"?u(g,{flexDirection:"column",borderStyle:"round",borderColor:st==="logs"?"green":"gray",paddingX:1,marginTop:1,height:ot+4,flexShrink:0,overflowY:"hidden",children:[u(g,{justifyContent:"space-between",children:[u(l,{children:[i(l,{color:"cyan",bold:!0,children:"LOGS \xB7 "}),i(l,{color:"white",children:T?.site.name??"\u2014"})]}),y&&I.length>0?st==="logs"?i(l,{color:"green",children:`\u2191\u2193 scroll \xB7 esc back${xt>0?` \xB7 ${at}/${I.length}`:""}`}):I.length>ot?i(l,{color:"gray",children:"tab to scroll"}):null:null]}),i(g,{flexDirection:"column",marginTop:1,children:!y||I.length===0?i(l,{color:"gray",children:"no activity yet"}):qt.map((a,d)=>u(l,{wrap:"truncate-end",children:[u(l,{color:"gray",children:[a.time," "]}),a.text]},at-qt.length+d))})]}):u(g,{flexDirection:"column",borderStyle:"round",borderColor:"blue",paddingX:1,marginTop:1,flexGrow:1,children:[p==="newOrg"&&u(nt,{children:[i(l,{color:"cyan",bold:!0,children:"CREATE SITE \u2014 PICK ORG"}),i(l,{color:"gray",children:"\u2191/\u2193 select \xB7 \u23CE next \xB7 esc cancel"}),i(g,{flexDirection:"column",marginTop:1,children:t.catalog.map((a,d)=>u(l,{color:d===U(w,t.catalog.length)?"blue":void 0,bold:d===U(w,t.catalog.length),children:[d===U(w,t.catalog.length)?"\u276F ":" ",a.org.name||a.org.subdomain]},a.org.id))})]}),p==="newName"&&u(nt,{children:[i(l,{color:"cyan",bold:!0,children:"CREATE SITE"}),u(g,{marginTop:1,children:[i(l,{children:"New site name: "}),i(Be,{value:b,onChange:v,onSubmit:ze})]}),i(l,{color:"gray",children:"\u23CE create \xB7 empty + \u23CE to cancel"})]})]})]})]}),t.message?i(g,{paddingX:1,children:u(l,{color:t.messageIsError?"red":"gray",wrap:"truncate-end",children:[t.messageIsError?"\u2717 ":"",t.message]})}):null,u(g,{flexDirection:"column",marginTop:1,children:[u(g,{paddingX:1,justifyContent:"space-between",children:[u(g,{flexWrap:"wrap",children:[i(j,{k:"\u2191\u2193",label:"select"}),i(j,{k:"/",label:"search"}),i(j,{k:"s",label:"start sync"}),i(j,{k:"d",label:"disconnect"}),F.length===0?i(j,{k:"n",label:"new site"}):null,i(j,{k:"ctrl+c",label:"quit",width:14})]}),i(j,{k:"?",label:"all keys",width:12,bg:Ee})]}),!Fe&&n.length>0?i(g,{paddingX:1,marginTop:1,flexWrap:"wrap",children:n.map((a,d)=>i(j,{k:String(d+1),label:a.label,bg:Ce},a.key))}):null]})]})}import{jsx as bt}from"react/jsx-runtime";function Oe(t){let e=t;return e?.body||e?.message||String(t)}function De({controller:t,env:e,devSecret:n,savedToken:r,savedEmail:s}){let[o,c]=Me(r?"loading":"auth"),[h,p]=Me();Kn((w,f)=>{f.ctrl&&w==="c"&&t.exit()}),Hn(()=>{r&&t.init(e,r,s).then(()=>c("manage")).catch(w=>{w instanceof L&&w.status===401?p("Saved session expired \u2014 please sign in again."):p(Oe(w)),c("auth")})},[]);let x=async(w,f)=>{p(void 0),await ue({env:e,userToken:w,devSecret:n,email:f}),c("loading");try{await t.init(e,w,f),c("manage")}catch(b){p(Oe(b)),c("auth")}};return o==="auth"?bt(ve,{env:e,initialError:h,onAuthed:x}):o==="loading"?bt(Jn,{children:bt(Xn,{children:"Loading your sites\u2026 "})}):bt($e,{controller:t})}import{jsx as nr}from"react/jsx-runtime";var Qn=Yn(import.meta.url),_t=te();if(_t.version){let t=Qn("../package.json");console.log(t.version),process.exit(0)}var Re=It.dirname(zn(import.meta.url)),Zn=Le(It.join(Re,"AGENT.md"),"utf-8"),tr=Le(It.join(Re,"build.mjs"),"utf-8");async function er(){let t=new ft(Zn,tr),e=await le(),n=(_t.env??e?.env??"production").toLowerCase(),r=_t.dev??process.env.SLEEKCMS_DEV_SECRET??e?.devSecret;ce(r),process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[3J\x1B[H");let s=Vn(nr(De,{controller:t,env:n,devSecret:r,savedToken:e?.userToken,savedEmail:e?.email}),{exitOnCtrlC:!1}),o=c=>{s.unmount(),console.log(c),process.exit(0)};t.once("exit",()=>o("\u2713 Stopped. Local site files were cleaned up.")),t.once("logout",()=>o("\u{1F44B} Logged out. Run `sleekcms` to sign in again.")),process.on("SIGINT",()=>{t.exit()}),process.on("SIGTERM",()=>{t.exit()}),await s.waitUntilExit()}er().catch(t=>{let e=t;console.error("\u274C",e.body||e.message),process.exit(1)});
10
+ `)}async function Dn(t,e,n,r,s){Ot.delete(v.resolve(t)),await y.remove(v.join(t,Dt)),r("\u{1F4E5} Fetching files...");let i=await ft(e,n,"GET","/get_files"),a={},u=0;for(let h of i){let w=v.join(t,h.path);await y.outputFile(w,h.content);let l=(await y.stat(w)).mtimeMs;u++,s?.(u,i.length),a[h.path]={mtimeMs:l,hash:tt(h.content)}}return r(`\u2714\uFE0F Synced ${i.length} file(s).`),{fileMap:a,pulled:u}}import Ln from"path";import jn from"chokidar";var Fn=5e3,An=/(^|[/\\])(\.DS_Store|Thumbs\.db|desktop\.ini)$/,et=class{viewsDir;onSync;debounceMs;watcher=null;debounceTimer=null;isShuttingDown=!1;dirty=!1;syncInFlight=!1;constructor(e){this.viewsDir=e.viewsDir,this.onSync=e.onSync,this.debounceMs=e.debounceMs??Fn}start(){if(this.watcher)return;let e=Ln.join(this.viewsDir,"src");this.watcher=jn.watch([e],{persistent:!0,ignoreInitial:!0,ignored:An}).on("change",()=>this.scheduleSync()).on("add",()=>this.scheduleSync()).on("unlink",()=>this.scheduleSync()).on("addDir",()=>this.scheduleSync()).on("unlinkDir",()=>this.scheduleSync())}setShuttingDown(e){this.isShuttingDown=e}async stop(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.watcher&&(await this.watcher.close(),this.watcher=null)}scheduleSync(){this.isShuttingDown||(this.dirty=!0,this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.flush(),this.debounceMs))}async flush(){if(this.debounceTimer=null,!(!this.dirty||this.isShuttingDown)){if(this.syncInFlight){this.debounceTimer=setTimeout(()=>this.flush(),this.debounceMs);return}this.dirty=!1,this.syncInFlight=!0;try{await this.onSync()}catch(e){let n=e;console.error("\u274C Sync failed:",n.body||n.message)}finally{this.syncInFlight=!1}}}};import pt from"fs-extra";import _n from"os";import he from"path";var me={localhost:"http://app.sleekcms.test/api",development:"https://app.sleekcms.dev/api",production:"https://app.sleekcms.com/api"};function jt(t){return me[t]||me.production}var In=he.join(_n.homedir(),"SleekCMS"),yt=he.join(In,"auth.json"),Lt;function fe(t){Lt=t||void 0}function Ft(t){let e=[];return t&&e.push(`_auth=${t}`),Lt&&e.push(`_dev=${Lt}`),e.length?e.join("; "):void 0}var F=class extends Error{status;body;constructor(e,n,r=""){super(e),this.name="UserAuthError",this.status=n,this.body=r}};async function pe(){try{let t=await pt.readJson(yt);return t&&t.userToken?t:null}catch{return null}}async function ye(t){await pt.outputJson(yt,t,{spaces:2});try{await pt.chmod(yt,384)}catch{}}async function we(){await pt.remove(yt)}async function K(t){let e={},n=Ft(t.userToken);n&&(e.Cookie=n),t.subdomain&&(e["x-subdomain"]=t.subdomain),t.slug&&(e.site=t.slug),t.body!==void 0&&(e["Content-Type"]="application/json");let r=await fetch(jt(t.env)+t.path,{method:t.method,headers:e,body:t.body!==void 0?JSON.stringify(t.body):void 0});if(!r.ok){let s=await r.text().catch(()=>"");throw new F(`${t.method} ${t.path} \u2192 ${r.status}: ${s}`,r.status,s)}return r.json()}async function be(t,e){let n={"Content-Type":"application/json"},r=Ft();r&&(n.Cookie=r);let s=await fetch(jt(t)+"/auth/otp",{method:"PUT",headers:n,body:JSON.stringify({email:e})});if(!s.ok){let a=await s.text().catch(()=>"");throw new F(`Failed to send OTP \u2192 ${s.status}: ${a}`,s.status,a)}return{otp:(await s.json().catch(()=>({}))).do_not_use_in_code}}async function At(t,e,n){let r={"Content-Type":"application/json"},s=Ft();s&&(r.Cookie=s);let i=await fetch(jt(t)+"/auth/login",{method:"POST",headers:r,body:JSON.stringify({email:e,otp:n})});if(!i.ok){let h=await i.text().catch(()=>"");throw new F(`Login failed \u2192 ${i.status}: ${h}`,i.status,h)}let a=i.headers.getSetCookie?.()??[],u=Nn(a);if(!u)throw new F("Login succeeded but no _auth cookie was returned.",500);return u}function Nn(t){for(let e of t){let n=e.match(/(?:^|;\s*)_auth=([^;]+)/);if(n&&n[1])return decodeURIComponent(n[1])}return null}async function _t(t,e){return K({env:t,userToken:e,method:"GET",path:"/org"})}async function Se(t,e,n){return K({env:t,userToken:e,method:"GET",path:"/site",subdomain:n})}function Wn(t){if(t==null)return"(unknown error)";if(typeof t=="string")return t;if(typeof t!="object")return String(t);let e=t,n=typeof e.file_path=="string"?e.file_path:"",r=typeof e.path=="string"?e.path:typeof e.filename=="string"?e.filename:"",s=n||r,i=e.line!=null?`:${e.line}`:"",a=e.column!=null?`:${e.column}`:"",u=typeof e.message=="string"&&e.message?e.message:typeof e.name=="string"&&e.name?e.name:"",h=n&&r&&r!==n?` (on page ${r})`:"";if(s)return`${s}${i}${a}${h}: ${u||"(no message)"}`;if(u)return u;try{return JSON.stringify(t)}catch{return"(unknown error)"}}async function xe(t,e,n,r){let s;try{s=await K({env:t,userToken:e,method:"GET",path:"/site_build",subdomain:n,slug:r})}catch{return null}if(!Array.isArray(s)||s.length===0)return null;let i=s[0],a=s.find(h=>typeof h.url=="string"&&h.url.length>0),u=Array.isArray(i.errors)?i.errors.map(Wn):[];return u.length>0?{status:"error",mode:"preview",errors:u,url:a?.url||void 0,at:i.updated_at?Date.parse(i.updated_at):void 0}:a?{status:"success",mode:"preview",url:a.url||void 0,at:a.updated_at?Date.parse(a.updated_at):void 0}:null}async function ve(t,e,n,r){let{token:s}=await K({env:t,userToken:e,method:"POST",path:"/site_user/api_token",subdomain:n,slug:r,body:{refresh:!1}});return s}async function Te(t,e,n){let r=await K({env:t,userToken:e,method:"POST",path:"/org",body:n?{name:n}:{}});return{id:r.id,name:r.name,subdomain:r.subdomain}}async function ke(t,e,n,r){let s=await K({env:t,userToken:e,method:"POST",path:"/site",subdomain:n,body:{name:r}});return{id:s.id,name:s.name,slug:s.slug}}var $e=500;function Hn(){return new Date().toTimeString().slice(0,8)}function wt(t){let e=t;if(e.body){try{let n=JSON.parse(e.body);if(n&&typeof n.message=="string")return n.message;if(n&&typeof n.error=="string")return n.error}catch{}return e.body}return e.message}async function Ee(t){t&&(await nt.remove(It.join(t,"src")),await nt.remove(It.join(t,".cache","state.json")))}var bt=class extends Gn{env="production";userToken="";email;catalog=[];synced=new Map;message;messageIsError=!1;agentMd;buildScript;isShuttingDown=!1;constructor(e,n){super(),this.agentMd=e,this.buildScript=n}changed(){this.emit("change")}flash(e,n=!1){this.message=e,this.messageIsError=n,this.changed()}appendLog(e,n){let r=this.synced.get(e);r&&(r.logs.push({time:Hn(),text:n}),r.logs.length>$e&&r.logs.splice(0,r.logs.length-$e),this.changed())}siteCount(){return this.synced.size}syncingCount(){let e=0;for(let n of this.synced.values())n.status==="syncing"&&e++;return e}watchingCount(){let e=0;for(let n of this.synced.values())(n.status==="watching"||n.status==="syncing")&&e++;return e}async init(e,n,r){this.env=e,this.userToken=n,r&&(this.email=r);let s=await _t(e,n);await this.buildCatalog(s)}async buildCatalog(e){let n=[];for(let r of e)try{n.push({org:r,sites:await Se(this.env,this.userToken,r.subdomain)})}catch(s){if(s instanceof F&&s.status===401)throw s;n.push({org:r,sites:[]})}this.catalog=n,this.changed()}isSynced(e){return this.synced.has(e)}availableSites(){let e=[];for(let n of this.catalog)for(let r of n.sites)this.synced.has(r.id)||e.push({org:n.org,site:r});return e}async addSite(e,n){if(this.synced.has(n.id))return;let r;try{r=await ve(this.env,this.userToken,e.subdomain,n.slug)}catch(i){this.flash(`Failed to authorize "${n.name}": ${wt(i)}`,!0);return}let s={org:e,site:n,siteToken:r,viewsDir:"",status:"syncing",logs:[]};this.synced.set(n.id,s),this.appendLog(n.id,"\u2795 added \u2014 pulling files"),this.changed();try{let i=await Mt({token:r,env:this.env,agentMd:this.agentMd,buildScript:this.buildScript,log:a=>this.appendLog(n.id,a),onProgress:(a,u)=>{s.progress={done:a,total:u},this.changed()}});s.viewsDir=i.viewsDir,s.progress=void 0,s.status="watching",s.lastSyncedAt=Date.now(),s.lastMsg=`pulled ${i.pulled} file(s)`,s.watcher=new et({viewsDir:i.viewsDir,onSync:()=>this.runSync(n.id)}),s.watcher.start(),this.watchBuildStatus(n.id),this.seedLastBuild(n.id),this.appendLog(n.id,"\u{1F440} watching for changes")}catch(i){let a=i;s.status="error",s.progress=void 0,s.lastMsg=a.body||a.message,this.appendLog(n.id,`\u274C ${a.body||a.message}`)}this.changed()}async removeSite(e){let n=this.synced.get(e);n&&(n.watcher?.setShuttingDown(!0),await n.watcher?.stop(),await n.buildWatcher?.close(),await Ee(n.viewsDir),this.synced.delete(e),this.changed())}buildSite(e,n=!1){let r=this.synced.get(e);if(!r||!r.viewsDir)return;let s=n?"live":"preview";this.writeBuildFile(r.viewsDir,{status:"building",mode:s,stage:"queued",url:r.lastBuild?.url,at:Date.now()}),Un("node",n?["build.mjs","--publish"]:["build.mjs"],{cwd:r.viewsDir,stdio:"ignore"}).on("error",u=>{this.writeBuildFile(r.viewsDir,{status:"error",mode:s,errors:[u.message],url:r.lastBuild?.url,at:Date.now()}),this.appendLog(e,`\u274C build: ${u.message}`)}),this.appendLog(e,n?"\u{1F680} publishing\u2026":"\u{1F528} building\u2026")}buildFile(e){return It.join(e,".cache","build.json")}async readBuildFile(e){try{let n=await nt.readJson(this.buildFile(e));return n&&n.status?n:null}catch{return null}}async writeBuildFile(e,n){let r=this.buildFile(e),s=`${r}.tmp`;try{await nt.outputJson(s,n),await nt.rename(s,r)}catch{}}watchBuildStatus(e){let n=this.synced.get(e);!n||!n.viewsDir||(n.buildWatcher=qn.watch(this.buildFile(n.viewsDir),{ignoreInitial:!1}).on("add",()=>{this.readBuildStatus(e)}).on("change",()=>{this.readBuildStatus(e)}))}async readBuildStatus(e){let n=this.synced.get(e);if(!n)return;let r=await this.readBuildFile(n.viewsDir);if(!r)return;let s=n.lastBuild?.url;n.lastBuild={status:r.status,mode:r.mode??"preview",url:r.url??(r.status==="success"?void 0:s),errors:r.errors,stage:r.stage,at:r.at},this.changed()}async seedLastBuild(e){let n=this.synced.get(e);if(!n)return;let r=await xe(this.env,this.userToken,n.org.subdomain,n.site.slug);if(!r)return;let s=this.synced.get(e);if(!s)return;let i=await this.readBuildFile(s.viewsDir);i?.at!=null&&(r.at==null||i.at>=r.at)||await this.writeBuildFile(s.viewsDir,r)}async ensureOrg(e){if(e)return e;if(this.catalog[0])return this.catalog[0].org;let n;try{n=await _t(this.env,this.userToken)}catch(r){return this.flash(`Couldn't check your organizations: ${wt(r)}`,!0),null}if(n.length>0){try{await this.buildCatalog(n)}catch{}return n[0]}try{let r=this.email?`${this.email.split("@")[0]}'s Sites`:"My Sites";this.flash("No organization yet \u2014 creating one\u2026");let s=await Te(this.env,this.userToken,r);return this.catalog=[...this.catalog,{org:s,sites:[]}],this.changed(),s}catch(r){return this.flash(`Could not create an organization: ${wt(r)}`,!0),null}}async createNewSite(e,n){let r=await this.ensureOrg(n);if(!r)return;let s;try{s=await ke(this.env,this.userToken,r.subdomain,e)}catch(a){this.flash(`Could not create "${e}": ${wt(a)}`,!0);return}let i=this.catalog.find(a=>a.org.id===r.id);i&&!i.sites.some(a=>a.id===s.id)&&(i.sites=[...i.sites,s]),this.flash(`Created "${s.name}".`),await this.addSite(r,s)}async refetch(e){await this.runSync(e,{flush:!0})}async runSync(e,n={}){let r=this.synced.get(e);if(!(!r||!r.viewsDir||r.status==="paused")){r.status="syncing",this.changed();try{let s=await Mt({token:r.siteToken,env:this.env,viewsDir:r.viewsDir,agentMd:this.agentMd,buildScript:this.buildScript,flush:n.flush,log:i=>this.appendLog(e,i),onProgress:(i,a)=>{r.progress={done:i,total:a},this.changed()}});if(r.progress=void 0,r.status="watching",r.lastSyncedAt=Date.now(),n.flush)r.lastMsg=`re-fetched ${s.pulled} file(s)`;else{let i=[s.renamed>0?`renamed ${s.renamed}`:"",s.pushed>0?`pushed ${s.pushed} file(s)`:""].filter(Boolean);r.lastMsg=i.length>0?i.join(", "):"up to date"}}catch(s){let i=s;r.status="error",r.progress=void 0,r.lastMsg=i.body||i.message,this.appendLog(e,`\u274C ${i.body||i.message}`)}this.changed()}}async togglePause(e){let n=this.synced.get(e);!n||!n.viewsDir||(n.status==="paused"?(n.watcher=new et({viewsDir:n.viewsDir,onSync:()=>this.runSync(e)}),n.watcher.start(),n.status="watching",this.appendLog(e,"\u25B6 resumed \u2014 watching for changes")):(n.watcher?.setShuttingDown(!0),await n.watcher?.stop(),n.watcher=void 0,n.progress=void 0,n.status="paused",this.appendLog(e,"\u23F8 paused")),this.changed())}async cleanupAll(){for(let e of this.synced.values())e.watcher?.setShuttingDown(!0),await e.watcher?.stop(),await e.buildWatcher?.close(),await Ee(e.viewsDir)}async exit(){this.isShuttingDown||(this.isShuttingDown=!0,await this.cleanupAll(),this.emit("exit"))}async logout(){this.isShuttingDown||(this.isShuttingDown=!0,await we(),await this.cleanupAll(),this.emit("logout"))}};import{useEffect as rr,useState as Ae}from"react";import{Box as sr,Text as ir,useInput as or}from"ink";import{useState as rt}from"react";import{Box as st,Text as Y}from"ink";import Be from"ink-text-input";import{jsx as G,jsxs as X}from"react/jsx-runtime";function Ce(t){let e=t;return e?.body||e?.message||String(t)}function Pe({env:t,initialError:e,onAuthed:n}){let[r,s]=rt("email"),[i,a]=rt(""),[u,h]=rt(""),[w,l]=rt(e),[b,S]=rt(),g=async C=>{let k=C.trim();if(k){l(void 0),s("busy");try{let{otp:P}=await be(t,k);if(a(k),P){let D=await At(t,k,P);await n(D,k);return}S(`OTP sent to ${k}`),s("otp")}catch(P){l(Ce(P)),s("email")}}},x=async C=>{let k=C.trim();if(k){l(void 0),s("busy");try{let P=await At(t,i,k);await n(P,i)}catch(P){l(Ce(P)),h(""),s("otp")}}};return X(st,{flexDirection:"column",children:[X(Y,{bold:!0,children:["\u25C6 SleekCMS \u2014 sign in",t!=="production"?` (${t})`:""]}),w?X(Y,{color:"red",children:["\u2717 ",w]}):null,r==="email"&&X(st,{marginTop:1,children:[G(Y,{children:"Email: "}),G(Be,{value:i,onChange:a,onSubmit:g,placeholder:"you@example.com"})]}),r==="otp"&&X(st,{flexDirection:"column",marginTop:1,children:[b?G(Y,{color:"green",children:b}):null,X(st,{children:[G(Y,{children:"Enter OTP: "}),G(Be,{value:u,onChange:h,onSubmit:x})]})]}),r==="busy"&&G(st,{marginTop:1,children:G(Y,{children:"Working\u2026"})})]})}import{Fragment as Kn,useEffect as it,useMemo as Yn,useState as O}from"react";import{Box as p,Text as d,useInput as Xn,useStdout as zn}from"ink";import Re from"ink-text-input";import Jn from"node:os";function Nt(t){if(!t)return"\u2014";let e=Jn.homedir();return t.startsWith(e)?"~"+t.slice(e.length):t}function Wt(t){if(!t)return"\u2014";let e=Math.floor((Date.now()-t)/1e3);if(e<5)return"just now";if(e<60)return`${e}s ago`;let n=Math.floor(e/60);if(n<60)return`${n}m ago`;let r=Math.floor(n/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function xt(t){return!t||t.total<=0?null:Math.round(t.done/t.total*100)}var St=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Oe(t){return St[(t%St.length+St.length)%St.length]}function vt(t){switch(t){case"syncing":return{dot:"\u25CF",color:"green",label:"syncing"};case"watching":return{dot:"\u25CB",color:"gray",label:"watching"};case"paused":return{dot:"\u2016",color:"yellow",label:"paused"};case"error":return{dot:"\u2717",color:"red",label:"error"}}}function Me(t){if(!t)return{dot:"\xB7",color:"gray",label:"\u2014"};switch(t.status){case"building":return{dot:"\u2234",color:"yellow",label:"building"};case"error":return{dot:"\u2717",color:"red",label:"failed"};case"success":return{dot:"\u2713",color:"green",label:t.mode==="live"?"live":"preview"}}}import{Fragment as ot,jsx as o,jsxs as m}from"react/jsx-runtime";var U=(t,e)=>e<=0?0:Math.max(0,Math.min(t,e-1)),Vn=16,Qn=5,Zn="greenBright",De="blueBright",Le="magentaBright";function je({k:t,bg:e=Zn}){return o(d,{color:"black",backgroundColor:e,children:` ${t} `})}function I({k:t,label:e,width:n=16,bg:r}){return m(p,{width:n,children:[o(je,{k:t,bg:r}),o(d,{color:"gray",children:` ${e}`})]})}function B({k:t,label:e,bg:n}){return m(p,{marginBottom:1,children:[o(p,{width:9,children:o(je,{k:t,bg:n})}),o(d,{color:"gray",children:e})]})}function tr({launchers:t}){return m(p,{flexDirection:"column",borderStyle:"round",borderColor:"green",paddingX:2,paddingY:1,width:66,children:[m(p,{justifyContent:"space-between",children:[o(d,{color:"green",bold:!0,children:"KEYBOARD SHORTCUTS"}),o(d,{color:"gray",children:"? or esc to close"})]}),m(p,{marginTop:1,children:[m(p,{flexDirection:"column",width:34,children:[o(d,{color:"cyan",bold:!0,children:"Navigate"}),o(B,{k:"\u2191\u2193",label:"move selection"}),o(B,{k:"tab",label:"focus logs \xB7 \u2191\u2193 scroll"}),o(B,{k:"esc",label:"clear filter / back"}),o(p,{marginTop:1,children:o(d,{color:"cyan",bold:!0,children:"Global"})}),o(B,{k:"/",label:"search sites"}),o(B,{k:"n",label:"new site"}),o(B,{k:"?",label:"toggle this help",bg:De}),o(B,{k:"q",label:"logout"}),o(B,{k:"ctrl+c",label:"quit"})]}),m(p,{flexDirection:"column",flexGrow:1,children:[o(d,{color:"cyan",bold:!0,children:"Site actions"}),o(B,{k:"s",label:"start sync"}),o(B,{k:"d",label:"disconnect"}),o(B,{k:"p",label:"pause / resume sync"}),o(B,{k:"b",label:"build preview"}),o(B,{k:"B",label:"publish live"}),o(B,{k:"o",label:"open preview url"}),t.map((e,n)=>o(B,{k:String(n+1),label:`open in ${e.label}`,bg:Le},e.key))]})]})]})}function er({percent:t,width:e=28}){let n=Math.round(Math.max(0,Math.min(100,t))/100*e);return m(d,{children:[o(d,{color:"green",children:"\u2588".repeat(n)}),o(d,{color:"gray",children:"\u2591".repeat(Math.max(0,e-n))})]})}function nr({frame:t,width:e=24,seg:n=6}){let r=e+n,s=t%r-n,i="";for(let a=0;a<e;a++)i+=a>=s&&a<s+n?"\u25AE":"\u25AF";return o(d,{color:"yellow",children:i})}function Fe({controller:t}){let{stdout:e}=zn(),n=Yn(()=>ae(),[]),[,r]=O(0),[s,i]=O(()=>new Date),[a,u]=O(null),[h,w]=O(()=>t.synced.size>0||t.catalog.some(f=>f.sites.length>0)?"dashboard":t.catalog.length<=1?"newName":"newOrg"),[l,b]=O(0),[S,g]=O(""),[x,C]=O(!1),[k,P]=O(""),[D,L]=O(!1),[q,z]=O(0),[qt,Ht]=O(!1),[Ge,Ue]=O(!1),[at,ct]=O("list"),[qe,V]=O(0),Jt=()=>r(c=>c+1);it(()=>(t.on("change",Jt),()=>{t.off("change",Jt)}),[t]),it(()=>{let c,f=()=>{i(new Date),c=setTimeout(f,6e4-Date.now()%6e4)};return c=setTimeout(f,6e4-Date.now()%6e4),()=>clearTimeout(c)},[]);let _=t.catalog.flatMap(c=>c.sites.map(f=>({org:c.org,site:f,entry:t.synced.get(f.id)}))).sort((c,f)=>(c.entry?0:1)-(f.entry?0:1)),N=k.trim().toLowerCase(),R=N?_.filter(c=>c.site.name.toLowerCase().includes(N)||(c.org.name||c.org.subdomain||"").toLowerCase().includes(N)):_,H=R.findIndex(c=>c.site.id===a);H<0&&(H=0);let $=R[H],T=$?.entry,lt=Math.max(3,(e?.rows??30)-26),W=T?.logs??[],kt=Math.max(0,W.length-lt),He=Math.min(qe,kt),ut=W.length-He,Kt=W.slice(Math.max(0,ut-lt),ut),Je=$?.site.id;it(()=>V(0),[Je]);let A=T?.lastBuild,$t=A?.status==="building",Et=Me(A),dt=$t;if(!dt){for(let c of t.synced.values())if(c.status==="syncing"){dt=!0;break}}it(()=>{if(!dt)return;let c=setInterval(()=>z(f=>f+1),120);return()=>clearInterval(c)},[dt]);let Yt=R.findIndex(c=>!c.entry),J=Math.max(Qn,(e?.rows??30)-Vn),Q=R.length,gt=Q>J,Ke=Math.floor(J/2),Z=gt?Math.max(0,Math.min(H-Ke,Q-J)):0,Bt=Math.min(Q,Z+J),Ye=R.slice(Z,Bt);it(()=>{h!=="dashboard"||x||_.length>0||(g(""),t.catalog.length<=1?w("newName"):(b(0),w("newOrg")))},[_.length,h,x,t.catalog.length]);let mt=c=>{if(R.length===0)return;let f=U(H+c,R.length);u(R[f].site.id)},Xe=()=>{if($){if($.entry){t.flash("Already syncing \u2014 press d to disconnect.");return}t.addSite($.org,$.site)}},ze=()=>{$?.entry&&t.removeSite($.site.id)},Ve=()=>{g(""),t.catalog.length<=1?w("newName"):(b(0),w("newOrg"))},Qe=c=>{if(!T){t.flash("Press s to start syncing this site first.",!0);return}Ue(!0);let f=c.run(T.viewsDir);t.flash(f?`Opened ${c.label} at ${Nt(T.viewsDir)}`:`Couldn't open ${c.label}.`,!f)},Ze=()=>{if(!T){t.flash("Press s to start syncing this site first.",!0);return}t.togglePause(T.site.id)},Xt=c=>{if(!T){t.flash("Press s to start syncing this site first.",!0);return}t.buildSite(T.site.id,c),t.flash(c?"Publishing \u2014 making this live\u2026":"Building preview\u2026")},tn=()=>{let c=T?.lastBuild?.url;if(!c){t.flash("No build URL yet \u2014 press b to build.",!0);return}t.flash(oe(c)?`Opening ${c}`:"Couldn't open the browser.",!1)};Xn((c,f)=>{if(qt){(c==="?"||f.escape)&&Ht(!1);return}if(h==="newOrg"){f.escape?w("dashboard"):f.upArrow?b(E=>U(E-1,t.catalog.length)):f.downArrow?b(E=>U(E+1,t.catalog.length)):f.return&&w("newName");return}if(D){f.escape?(L(!1),P(""),u(null)):f.return?L(!1):f.upArrow?mt(-1):f.downArrow&&mt(1);return}if(f.tab){at==="logs"?ct("list"):T&&ct("logs"),V(0);return}if(at==="logs"){if(f.escape){ct("list"),V(0);return}if(f.upArrow)return void V(E=>Math.min(E+1,kt));if(f.downArrow)return void V(E=>Math.max(0,E-1))}if(f.escape){P(""),u(null);return}if(c==="?")return void Ht(!0);if(c==="/")return ct("list"),void L(!0);if(c==="n")return void Ve();if(c==="q")return void t.logout();if(f.upArrow)return mt(-1);if(f.downArrow)return mt(1);if(c==="s")return Xe();if(c==="d")return ze();if(c==="p")return Ze();if(c==="b")return Xt(!1);if(c==="B")return Xt(!0);if(c==="o")return tn();if(c>="1"&&c<="9"){let E=n[parseInt(c,10)-1];if(E)return Qe(E)}},{isActive:h!=="newName"});let en=async c=>{let f=c.trim();if(!f){w("dashboard");return}let E=t.catalog[U(l,t.catalog.length)]?.org??t.catalog[0]?.org;C(!0),w("dashboard"),await t.createNewSite(f,E),C(!1)},zt=m(p,{justifyContent:"space-between",paddingX:1,children:[m(p,{children:[o(d,{color:"green",bold:!0,children:"\u25C6 SleekCMS"}),o(d,{color:"gray",children:" \u2502 "}),o(d,{children:t.email??"signed in"})]}),m(p,{children:[m(d,{color:"gray",children:[N?`${R.length}/${_.length}`:_.length," sites \xB7 "]}),m(d,{color:"green",children:[t.watchingCount()," watching"]}),o(d,{color:"gray",children:` \xB7 ${s.toTimeString().slice(0,5)}`})]})]});return qt?m(p,{flexDirection:"column",children:[zt,o(p,{marginTop:1,justifyContent:"center",children:o(tr,{launchers:n})})]}):m(p,{flexDirection:"column",children:[zt,m(p,{children:[m(p,{flexDirection:"column",borderStyle:"round",borderColor:"green",width:38,paddingX:1,children:[m(p,{justifyContent:"space-between",children:[o(d,{color:"green",bold:!0,children:"SITE WORKSPACES"}),_.length>J&&!D&&!N?o(d,{color:"gray",children:"/ filter"}):null]}),D?m(p,{children:[o(d,{color:"cyan",children:"/ "}),o(Re,{value:k,onChange:P,onSubmit:()=>L(!1),placeholder:"type to filter\u2026"})]}):N?o(d,{color:"gray",children:`/ ${k} \xB7 ${R.length} match${R.length===1?"":"es"}`}):null,o(p,{flexDirection:"column",marginTop:1,minHeight:gt?J+3:void 0,children:R.length===0?o(d,{color:"gray",children:N?`no matches for \u201C${k}\u201D`:"no sites \u2014 press n to create one"}):m(ot,{children:[gt?o(d,{color:"gray",children:Z>0?` \u2191 ${Z} more`:" "}):null,Ye.map((c,f)=>{let E=Z+f,Vt=c.entry?vt(c.entry.status):{dot:"\xB7",color:"gray",label:"idle"},Qt=xt(c.entry?.progress),nn=c.entry?c.entry.status==="syncing"&&Qt!=null?`${Qt}%`:c.entry.status==="paused"?"paused":"--":"",ht=E===H,rn=E===Yt&&Yt>0;return m(Kn,{children:[rn?o(d,{color:"gray",children:"\u2500".repeat(34)}):null,m(p,{justifyContent:"space-between",children:[m(p,{children:[o(d,{color:ht?"green":"gray",children:ht?"\u276F ":" "}),m(d,{color:c.entry?.status==="watching"||c.entry?.status==="syncing"?"green":Vt.color,children:[c.entry?.status==="syncing"?Oe(q):c.entry?.status==="watching"?"\u25CF":Vt.dot," "]}),o(d,{color:ht?"white":c.entry?void 0:"gray",bold:ht,children:c.site.name})]}),o(d,{color:c.entry?.status==="syncing"?"green":"gray",children:nn})]})]},c.site.id)}),gt?o(d,{color:"gray",children:Bt<Q?` \u2193 ${Q-Bt} more`:" "}):null]})}),o(p,{marginTop:1,children:o(d,{color:"gray",children:"+ new site \xB7 n"})})]}),m(p,{flexDirection:"column",flexGrow:1,marginLeft:1,children:[o(p,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,children:$?m(ot,{children:[m(d,{children:[$.org.name||$.org.subdomain?m(d,{color:"gray",children:[$.org.name||$.org.subdomain," / "]}):null,o(d,{color:"cyan",bold:!0,children:$.site.name})]}),m(p,{marginTop:1,children:[o(d,{color:"gray",children:"status "}),T?o(d,{color:vt(T.status).color,children:`: ${vt(T.status).label}`}):o(d,{color:"gray",children:": idle \u2014 not syncing (press s to start)"})]}),m(p,{children:[o(d,{color:"gray",children:"path "}),o(d,{wrap:"truncate-end",children:T?.viewsDir?Nt(T.viewsDir):"\u2014"})]}),m(p,{children:[o(d,{color:"gray",children:"synced "}),o(d,{children:Wt(T?.lastSyncedAt)}),o(d,{color:"gray",children:" progress "}),o(d,{color:T?.status==="syncing"?"green":"gray",children:T?.status==="syncing"?`${xt(T.progress)??0}%`:"\u2014"})]}),o(p,{marginTop:1,children:o(er,{percent:T?T.status==="syncing"?xt(T.progress)??0:T.status==="watching"?100:0:0})}),T?m(ot,{children:[m(p,{marginTop:1,children:[o(d,{color:"gray",children:"build "}),o(d,{color:Et.color,children:`${Et.dot} ${Et.label}`}),o(d,{color:"gray",children:" last "}),o(d,{children:$t?"in progress":Wt(A?.at)})]}),m(p,{children:[o(d,{color:"gray",children:"url "}),A?.url?o(d,{color:"cyan",wrap:"truncate-end",children:A.url}):o(d,{color:"gray",children:"\u2014"})]}),A?.status==="error"&&A.errors?.length?m(p,{children:[o(d,{color:"red",children:"\u258C "}),o(d,{color:"red",wrap:"truncate-end",children:`\u2717 ${A.errors[0]}`})]}):null,$t?m(p,{children:[o(nr,{frame:q}),o(d,{color:"gray",children:` ${A?.stage??"building"}`})]}):null]}):null]}):o(d,{color:"gray",children:"No site selected."})}),h==="dashboard"?m(p,{flexDirection:"column",borderStyle:"round",borderColor:at==="logs"?"green":"gray",paddingX:1,marginTop:1,height:lt+4,flexShrink:0,overflowY:"hidden",children:[m(p,{justifyContent:"space-between",children:[m(d,{children:[o(d,{color:"cyan",bold:!0,children:"LOGS \xB7 "}),o(d,{color:"white",children:$?.site.name??"\u2014"})]}),T&&W.length>0?at==="logs"?o(d,{color:"green",children:`\u2191\u2193 scroll \xB7 esc back${kt>0?` \xB7 ${ut}/${W.length}`:""}`}):W.length>lt?o(d,{color:"gray",children:"tab to scroll"}):null:null]}),o(p,{flexDirection:"column",marginTop:1,children:!T||W.length===0?o(d,{color:"gray",children:"no activity yet"}):Kt.map((c,f)=>m(d,{wrap:"truncate-end",children:[m(d,{color:"gray",children:[c.time," "]}),c.text]},ut-Kt.length+f))})]}):m(p,{flexDirection:"column",borderStyle:"round",borderColor:"blue",paddingX:1,marginTop:1,flexGrow:1,children:[h==="newOrg"&&m(ot,{children:[o(d,{color:"cyan",bold:!0,children:"CREATE SITE \u2014 PICK ORG"}),o(d,{color:"gray",children:"\u2191/\u2193 select \xB7 \u23CE next \xB7 esc cancel"}),o(p,{flexDirection:"column",marginTop:1,children:t.catalog.map((c,f)=>m(d,{color:f===U(l,t.catalog.length)?"blue":void 0,bold:f===U(l,t.catalog.length),children:[f===U(l,t.catalog.length)?"\u276F ":" ",c.org.name||c.org.subdomain]},c.org.id))})]}),h==="newName"&&m(ot,{children:[o(d,{color:"cyan",bold:!0,children:"CREATE SITE"}),m(p,{marginTop:1,children:[o(d,{children:"New site name: "}),o(Re,{value:S,onChange:g,onSubmit:en})]}),o(d,{color:"gray",children:"\u23CE create \xB7 empty + \u23CE to cancel"})]})]})]})]}),t.message?o(p,{paddingX:1,children:m(d,{color:t.messageIsError?"red":"gray",wrap:"truncate-end",children:[t.messageIsError?"\u2717 ":"",t.message]})}):null,m(p,{flexDirection:"column",marginTop:1,children:[m(p,{paddingX:1,justifyContent:"space-between",children:[m(p,{flexWrap:"wrap",children:[o(I,{k:"\u2191\u2193",label:"select"}),o(I,{k:"/",label:"search"}),o(I,{k:"s",label:"start sync"}),o(I,{k:"d",label:"disconnect"}),_.length===0?o(I,{k:"n",label:"new site"}):null,o(I,{k:"ctrl+c",label:"quit",width:14})]}),o(I,{k:"?",label:"all keys",width:12,bg:De})]}),!Ge&&n.length>0?o(p,{paddingX:1,marginTop:1,flexWrap:"wrap",children:n.map((c,f)=>o(I,{k:String(f+1),label:c.label,bg:Le},c.key))}):null]})]})}import{jsx as Tt}from"react/jsx-runtime";function _e(t){let e=t;return e?.body||e?.message||String(t)}function Ie({controller:t,env:e,devSecret:n,savedToken:r,savedEmail:s}){let[i,a]=Ae(r?"loading":"auth"),[u,h]=Ae();or((l,b)=>{b.ctrl&&l==="c"&&t.exit()}),rr(()=>{r&&t.init(e,r,s).then(()=>a("manage")).catch(l=>{l instanceof F&&l.status===401?h("Saved session expired \u2014 please sign in again."):h(_e(l)),a("auth")})},[]);let w=async(l,b)=>{h(void 0),await ye({env:e,userToken:l,devSecret:n,email:b}),a("loading");try{await t.init(e,l,b),a("manage")}catch(S){h(_e(S)),a("auth")}};return i==="auth"?Tt(Pe,{env:e,initialError:u,onAuthed:w}):i==="loading"?Tt(sr,{children:Tt(ir,{children:"Loading your sites\u2026 "})}):Tt(Fe,{controller:t})}import{jsx as hr}from"react/jsx-runtime";var ur=cr(import.meta.url),Gt=re();if(Gt.version){let t=ur("../package.json");console.log(t.version),process.exit(0)}var We=Ut.dirname(ar(import.meta.url)),dr=Ne(Ut.join(We,"AGENT.md"),"utf-8"),gr=Ne(Ut.join(We,"build.mjs"),"utf-8");async function mr(){let t=new bt(dr,gr),e=await pe(),n=(Gt.env??e?.env??"production").toLowerCase(),r=Gt.dev??process.env.SLEEKCMS_DEV_SECRET??e?.devSecret;fe(r),process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[3J\x1B[H");let s=lr(hr(Ie,{controller:t,env:n,devSecret:r,savedToken:e?.userToken,savedEmail:e?.email}),{exitOnCtrlC:!1}),i=a=>{s.unmount(),console.log(a),process.exit(0)};t.once("exit",()=>i("\u2713 Stopped. Local site files were cleaned up.")),t.once("logout",()=>i("\u{1F44B} Logged out. Run `sleekcms` to sign in again.")),process.on("SIGINT",()=>{t.exit()}),process.on("SIGTERM",()=>{t.exit()}),await s.waitUntilExit()}mr().catch(t=>{let e=t;console.error("\u274C",e.body||e.message),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleekcms/sync",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Edit SleekCMS sites locally — models, content, templates, images — with live two-way sync and AI agent support.",
5
5
  "keywords": [
6
6
  "sleekcms",