@sleekcms/sync 2.3.0 → 2.4.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/dist/AGENT.md CHANGED
@@ -20,17 +20,26 @@ Three model kinds:
20
20
 
21
21
  Pages and entries are either **single** (one record) or a **collection** (many records). A collection is marked by a `+` suffix on the key, e.g. `blog+`.
22
22
 
23
- ## 2. The Edit → Sync → Verify Loop
23
+ ## 2. The Edit → Sync → Build → Verify Loop
24
24
 
25
25
  1. **Edit/create files** in the workspace under `src/`.
26
- 2. The `@sleekcms/sync` utility runs in the background and pushes every save automatically. There is no build or deploy command to run.
27
- 3. **Verify by reading `sync-errors.log`** in the workspace root after a batch of edits:
26
+ 2. The `@sleekcms/sync` utility runs in the background and pushes every save automatically — there is nothing to run for syncing.
27
+ 3. **Check `sync-errors.log`** in the workspace root after a batch of edits — this catches *per-file* save/validation problems:
28
28
  - A failed sync **adds a line** describing the error for that file.
29
29
  - When the file is fixed and re-synced, **its line is removed**.
30
- - Empty file (or no new lines) = everything synced successfully.
31
- 4. Previews are online only; the user views the result in the SleekCMS preview, not locally.
30
+ - Empty file (or no new lines) = every file saved successfully.
31
+ 4. **Build the whole site** to compile it and get a viewable URL. Once `sync-errors.log` is clean, run from the workspace root:
32
32
 
33
- Never guess at why something isn't rendering — read `sync-errors.log` first.
33
+ ```bash
34
+ node build.mjs # build + validate; prints the PREVIEW url, or build errors
35
+ node build.mjs --deploy # build + publish LIVE; prints the live url
36
+ ```
37
+
38
+ - On success it prints `✅ Build … → <url>` — **give that URL to the user** so they can view the result.
39
+ - On failure it prints `❌ Build failed:` followed by the errors (e.g. `views/pages/home.ejs:14: …`). Fix them and build again.
40
+ - This is a *whole-site* check (templates, links, references) that `sync-errors.log` does not cover. Every build is also appended to `build.log`.
41
+
42
+ Never guess at why something isn't rendering — read `sync-errors.log` first, then run `node build.mjs` and read its output.
34
43
 
35
44
  ## 3. File Naming Rules (applies to models, views, and content)
36
45
 
@@ -342,7 +351,7 @@ There is exactly **one** layout: `layouts/common.ejs`. It wraps every page autom
342
351
 
343
352
  ### Helpers — bookings
344
353
 
345
- `bookings(name, options?)` renders the appointment-booking widget mount div (see §11). `options` is `{ accent, class, style }`.
354
+ `bookings(name, options?)` renders the appointment-booking / scheduling widget mount div — use it when visitors should book or schedule a time (appointment, call, demo, consultation, class, reservation); see §11 for when it's appropriate. `options` is `{ accent, class, style }`.
346
355
 
347
356
  ### Helpers — head injection
348
357
 
@@ -397,6 +406,8 @@ The `data-sleekcms` value groups submissions by form; each input's `name` is sto
397
406
 
398
407
  ## 11. Bookings
399
408
 
409
+ Use this whenever visitors should **book or schedule** something — an appointment, a call, a demo, a consultation, a class, or a reservation ("pick a time / reserve a slot"). It fits appointment-based businesses: salon, spa, barber, dental/medical clinic, therapist, tutor, photographer, consultant, repair shop, restaurant reservations. **Only add a booking widget when the business genuinely takes appointments and the user asks for it (or it is clearly core to the business) — never add one speculatively to an arbitrary site.**
410
+
400
411
  Drop an appointment-booking widget on any page with the `bookings()` helper. Visitors pick a time slot and book right on the page; email confirmation, calendar invites, and cancel/reschedule are handled by the CMS — no backend, no JS.
401
412
 
402
413
  ```ejs
package/dist/build.mjs ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ // build.mjs v2 — SleekCMS workspace build/deploy script.
3
+ /**
4
+ * Self-contained (no `npm install` needed). Run from anywhere inside a synced
5
+ * SleekCMS workspace — the one `@sleekcms/sync` created, containing `.cache/token`.
6
+ *
7
+ * node build.mjs Build + validate the whole site. Prints the PREVIEW
8
+ * URL on success, or the build errors to fix.
9
+ * node build.mjs --deploy Build + publish LIVE. Prints the live URL.
10
+ *
11
+ * It calls POST /api/mcp/deploy and polls it to a terminal state (mirrors the
12
+ * cms-agent build loop). Results go to three places:
13
+ * - stdout — for you / your AI agent to read directly,
14
+ * - ./build.log — appended history of every build,
15
+ * - ./.cache/build.json — the single source of truth the `sleekcms` dashboard watches.
16
+ *
17
+ * Exit codes: 0 = built/deployed, 1 = build errors, 2 = couldn't run / still building.
18
+ */
19
+
20
+ import fs from "node:fs";
21
+ import path from "node:path";
22
+ import { pathToFileURL } from "node:url";
23
+
24
+ // Same env → base-URL table the sync engine uses; env is the 3rd `-`-segment of
25
+ // the per-site token (see sync-engine.ts), defaulting to production.
26
+ const MCP_BASE_URLS = {
27
+ localhost: "http://app.sleekcms.test/api/mcp",
28
+ development: "https://app.sleekcms.dev/api/mcp",
29
+ production: "https://app.sleekcms.com/api/mcp",
30
+ };
31
+
32
+ const TERMINAL = new Set(["COMPLETED", "FAILED", "EXPIRED"]);
33
+ const DEFAULT_MAX_POLLS = 6;
34
+
35
+ /** Env baked into the per-site token (`<a>-<b>-<env>-…`); falls back to production. */
36
+ export function deriveEnv(token, override) {
37
+ const env = (override || (token || "").split("-")[2] || "production").toLowerCase();
38
+ return MCP_BASE_URLS[env] ? env : "production";
39
+ }
40
+
41
+ export function mcpBase(env) {
42
+ return MCP_BASE_URLS[env] || MCP_BASE_URLS.production;
43
+ }
44
+
45
+ /** Walk up from `startDir` until a `.cache/token` is found; null if none. */
46
+ export function findWorkspace(startDir) {
47
+ let dir = path.resolve(startDir);
48
+ // eslint-disable-next-line no-constant-condition
49
+ while (true) {
50
+ if (fs.existsSync(path.join(dir, ".cache", "token"))) return dir;
51
+ const parent = path.dirname(dir);
52
+ if (parent === dir) return null;
53
+ dir = parent;
54
+ }
55
+ }
56
+
57
+ /** Friendly progress label from the raw job status + how many times we've polled. */
58
+ export function stageFor(status, poll) {
59
+ if (status === "PENDING") return "queued";
60
+ if (poll >= 2) return "finalizing";
61
+ return "building";
62
+ }
63
+
64
+ /** Coerce the deploy endpoint's error entries to plain strings. */
65
+ export function toErrorStrings(errors) {
66
+ if (!Array.isArray(errors)) return [];
67
+ return errors.map((e) =>
68
+ typeof e === "string" ? e : e && e.message ? String(e.message) : JSON.stringify(e),
69
+ );
70
+ }
71
+
72
+ class DeployError extends Error {
73
+ constructor(message, httpStatus) {
74
+ super(message);
75
+ this.httpStatus = httpStatus;
76
+ }
77
+ }
78
+
79
+ async function callDeploy(base, token, deploy, force, fetchImpl) {
80
+ const res = await fetchImpl(base + "/deploy", {
81
+ method: "POST",
82
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
83
+ body: JSON.stringify({ build_only: !deploy, force }),
84
+ });
85
+ if (!res.ok) {
86
+ const text = await res.text().catch(() => "");
87
+ throw new DeployError(`/deploy → ${res.status}: ${text}`, res.status);
88
+ }
89
+ return res.json();
90
+ }
91
+
92
+ /**
93
+ * Trigger a build, then poll the SAME job to a terminal state. Mirrors
94
+ * cms-agent's build_site(): first call force=true (enqueue a fresh build),
95
+ * subsequent polls force=false (don't re-trigger). `onPoll(status, poll)` fires
96
+ * after each response so the caller can surface progress.
97
+ */
98
+ export async function buildAndPoll(opts) {
99
+ const { base, token, deploy = false, maxPolls = DEFAULT_MAX_POLLS, fetchImpl = fetch, onPoll } = opts;
100
+ let r = await callDeploy(base, token, deploy, true, fetchImpl);
101
+ let poll = 0;
102
+ if (onPoll) onPoll(r.status, poll);
103
+ while (
104
+ !TERMINAL.has(r.status) &&
105
+ !(Array.isArray(r.errors) && r.errors.length > 0) &&
106
+ poll < maxPolls
107
+ ) {
108
+ poll++;
109
+ r = await callDeploy(base, token, deploy, false, fetchImpl);
110
+ if (onPoll) onPoll(r.status, poll);
111
+ }
112
+ return r;
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // CLI
117
+ // ---------------------------------------------------------------------------
118
+
119
+ function parseFlags(argv) {
120
+ const flags = { deploy: false, env: undefined };
121
+ for (let i = 0; i < argv.length; i++) {
122
+ const a = argv[i];
123
+ if (a === "--deploy" || a === "--live") flags.deploy = true;
124
+ else if (a === "--env") flags.env = argv[++i];
125
+ else if (a.startsWith("--env=")) flags.env = a.slice("--env=".length);
126
+ }
127
+ return flags;
128
+ }
129
+
130
+ function writeStatus(ws, obj) {
131
+ const dir = path.join(ws, ".cache");
132
+ fs.mkdirSync(dir, { recursive: true });
133
+ const file = path.join(dir, "build.json");
134
+ const tmp = `${file}.tmp`;
135
+ // Write-then-rename so the dashboard never reads a half-written file.
136
+ fs.writeFileSync(tmp, JSON.stringify(obj));
137
+ fs.renameSync(tmp, file);
138
+ }
139
+
140
+ function appendLog(ws, line) {
141
+ try {
142
+ fs.appendFileSync(path.join(ws, "build.log"), `${line}\n`);
143
+ } catch {
144
+ /* logging is best-effort */
145
+ }
146
+ }
147
+
148
+ async function main() {
149
+ const flags = parseFlags(process.argv.slice(2));
150
+ const mode = flags.deploy ? "live" : "preview";
151
+
152
+ const ws = findWorkspace(process.cwd());
153
+ if (!ws) {
154
+ console.error(
155
+ "❌ Not inside a SleekCMS workspace (no .cache/token found). Run this from your synced site folder.",
156
+ );
157
+ process.exit(2);
158
+ }
159
+
160
+ let token;
161
+ try {
162
+ token = fs.readFileSync(path.join(ws, ".cache", "token"), "utf-8").trim();
163
+ } catch {
164
+ console.error("❌ Could not read .cache/token in this workspace.");
165
+ process.exit(2);
166
+ }
167
+ if (!token) {
168
+ console.error("❌ .cache/token is empty.");
169
+ process.exit(2);
170
+ }
171
+
172
+ const env = deriveEnv(token, flags.env || process.env.SLEEKCMS_ENV);
173
+ const base = mcpBase(env);
174
+ const maxPolls = Number(process.env.SLEEKCMS_MAX_BUILD_POLLS) || DEFAULT_MAX_POLLS;
175
+
176
+ console.log(`⏳ ${flags.deploy ? "Deploying" : "Building"} site…`);
177
+ writeStatus(ws, { status: "building", mode, stage: "queued", at: Date.now() });
178
+
179
+ let r;
180
+ try {
181
+ r = await buildAndPoll({
182
+ base,
183
+ token,
184
+ deploy: flags.deploy,
185
+ maxPolls,
186
+ onPoll: (status, poll) => {
187
+ if (!TERMINAL.has(status)) {
188
+ writeStatus(ws, { status: "building", mode, stage: stageFor(status, poll), at: Date.now() });
189
+ }
190
+ },
191
+ });
192
+ } catch (err) {
193
+ const msg = err && err.message ? err.message : String(err);
194
+ const hint = err && err.httpStatus === 403 ? " (the workspace token must belong to a site ADMIN)" : "";
195
+ console.error(`❌ Build request failed${hint}: ${msg}`);
196
+ writeStatus(ws, { status: "error", mode, errors: [msg], at: Date.now() });
197
+ appendLog(ws, `${new Date().toISOString()}\t${mode}\tERROR\t${msg}`);
198
+ process.exit(2);
199
+ }
200
+
201
+ const errors = toErrorStrings(r.errors);
202
+
203
+ // 1) Build failed (compile errors, or the job itself failed/expired).
204
+ if (errors.length > 0 || r.status === "FAILED" || r.status === "EXPIRED") {
205
+ const list = errors.length ? errors : [r.message || "Build failed."];
206
+ console.error("❌ Build failed:");
207
+ for (const e of list) console.error(` - ${e}`);
208
+ writeStatus(ws, { status: "error", mode, errors: list, at: Date.now() });
209
+ appendLog(ws, `${new Date().toISOString()}\t${mode}\tFAILED\n${list.map((e) => ` - ${e}`).join("\n")}`);
210
+ process.exit(1);
211
+ }
212
+
213
+ // 2) Success.
214
+ if (r.status === "COMPLETED") {
215
+ const url = r.url || "";
216
+ console.log(`✅ Build ${flags.deploy ? "deployed (live)" : "succeeded (preview)"}${url ? ` → ${url}` : ""}`);
217
+ writeStatus(ws, { status: "success", mode, url: url || undefined, at: Date.now() });
218
+ appendLog(ws, `${new Date().toISOString()}\t${mode}\tSUCCESS\t${url}`);
219
+ process.exit(0);
220
+ }
221
+
222
+ // 3) Still running after the poll budget — surface the preview URL, leave it
223
+ // as building; re-running (or the dashboard) will resolve the final state.
224
+ const url = r.url || "";
225
+ console.log(`⏳ Still building${url ? ` → ${url}` : ""}. Re-run \`node build.mjs\` to check again.`);
226
+ writeStatus(ws, { status: "building", mode, stage: "finalizing", url: url || undefined, at: Date.now() });
227
+ appendLog(ws, `${new Date().toISOString()}\t${mode}\t${r.status}\t${url}`);
228
+ process.exit(2);
229
+ }
230
+
231
+ // Run only when invoked directly (`node build.mjs`), not when imported by tests.
232
+ const isMain = import.meta.url === pathToFileURL(process.argv[1] || "").href;
233
+ if (isMain) {
234
+ main().catch((err) => {
235
+ console.error("❌", err && err.message ? err.message : String(err));
236
+ process.exit(2);
237
+ });
238
+ }
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as rn}from"node:fs";import{fileURLToPath as sn}from"node:url";import{createRequire as on}from"node:module";import ie from"node:path";import{render as an}from"ink";import{execSync as Dt,spawn as L}from"child_process";import{program as Pt,Option as ut}from"commander";function $t(){return Pt.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 ut("-v, --version","output the version number").hideHelp()).addOption(new ut("-e, --env <env>","Environment (localhost, development, production)").default("production").hideHelp()).addOption(new ut("-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 ye}from"node:fs";import{fileURLToPath as xr}from"node:url";import{createRequire as Tr}from"node:module";import Mt from"node:path";import{render as kr}from"ink";import{execSync as Wt,spawn as D}from"child_process";import{program as It,Option as ft}from"commander";function Gt(){return It.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 ft("-v, --version","output the version number").hideHelp()).addOption(new ft("-e, --env <env>","Environment (localhost, development, production)").default("production").hideHelp()).addOption(new ft("-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),Pt.opts()}function I(t){let e=process.platform==="win32"?`where ${t}`:`which ${t}`;try{return Dt(e,{stdio:"ignore"}),!0}catch{return!1}}function Rt(t){if(process.platform!=="darwin")return!1;try{return Dt(`osascript -e 'id of application "${t}"'`,{stdio:"ignore"}),!0}catch{return!1}}function fe(t,e){let n={detached:!0,stdio:"ignore",windowsHide:!0};process.platform==="win32"?L("cmd.exe",["/c",t,...e],n).unref():L(t,e,n).unref()}var Ot=[{name:"VS Code",cmd:"code",args:t=>["-n",t]},{name:"Cursor",cmd:"cursor",args:t=>["-n",t]}];function Mt(t,e){let r=(e?Ot.filter(s=>s.cmd===e):Ot).find(s=>I(s.cmd));return r?(fe(r.cmd,r.args(t)),r.name):null}function ye(t){if(process.platform==="darwin")return L("open",["-a","Terminal",t],{detached:!0,stdio:"ignore"}).unref(),!0;if(process.platform==="win32"){if(I("wt"))L("wt.exe",["-d",t],{detached:!0,stdio:"ignore"}).unref();else{let e=I("powershell")?"powershell.exe":"cmd.exe";L("cmd.exe",["/c","start","",e],{cwd:t,detached:!0,stdio:"ignore"}).unref()}return!0}return!1}function we(t){return Rt("iTerm")?(L("open",["-a","iTerm",t],{detached:!0,stdio:"ignore"}).unref(),!0):!1}function Se(t){if(process.platform!=="darwin"||!I("claude"))return!1;let e=`tell application "Terminal" to do script ("cd " & quoted form of "${t}" & " && claude")`;return L("osascript",["-e",e,"-e",'tell application "Terminal" to activate'],{detached:!0,stdio:"ignore"}).unref(),!0}function At(){let t=process.platform==="darwin",e=process.platform==="win32",n=[];return(t||e)&&n.push({key:"terminal",label:"terminal",run:ye}),Rt("iTerm")&&n.push({key:"iterm",label:"iterm",run:we}),I("code")&&n.push({key:"code",label:"vscode",run:r=>Mt(r,"code")!==null}),I("cursor")&&n.push({key:"cursor",label:"cursor",run:r=>Mt(r,"cursor")!==null}),t&&I("claude")&&n.push({key:"claude",label:"claude",run:Se}),n}import{EventEmitter as We}from"node:events";import Ht from"fs-extra";import Xt from"path";import w from"fs-extra";import be from"os";import b from"path";var Bt={localhost:"http://app.sleekcms.test/api/mcp",development:"https://app.sleekcms.dev/api/mcp",production:"https://app.sleekcms.com/api/mcp"},ve=["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"],lt=class extends Error{status;body;constructor(e,n,r){super(e),this.status=n,this.body=r}};async function dt(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 lt(`${n} ${r} \u2192 ${i.status}: ${a}`,i.status,a)}return i.json()}async function Te(t,e){e&&(await w.outputFile(b.join(t,"AGENT.md"),e),await w.outputFile(b.join(t,"CLAUDE.md"),e),await w.outputFile(b.join(t,".vscode","copilot-instructions.md"),e)),await w.outputFile(b.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 ke(t,e){let n=b.join(t,".cache","token");if(await w.pathExists(n)){if((await w.readFile(n,"utf-8")).trim()!==e)throw new Error(`Workspace at ${t} is tied to a different token. Remove ${n} or use a different workspace.`);return}await w.outputFile(n,e)}function Ee(t,e){let n=`${e.name.substr(0,20)} ${e.id}`.replace(/[\s_]+/g,"-").toLowerCase(),r=t||b.join(be.homedir(),"SleekCMS");return b.resolve(r,n)}async function ht(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(),i=Bt[s]||Bt.production,a=await dt(i,e,"GET","/get_site"),d=t.viewsDir?b.resolve(t.viewsDir):Ee(t.path,a);await w.ensureDir(d),await Promise.all(ve.map(v=>w.ensureDir(b.join(d,v)))),await ke(d,e);let h=b.join(d,".cache","state.json");t.flush&&await w.remove(h);let S=!await w.pathExists(h),f=S?{}:(await w.readJson(h)).fileMap||{},p=0,y=0;return S?({fileMap:f,pulled:y}=await Be(d,i,e,n,r),await Te(d,t.agentMd)):p=await Oe(d,f,i,e,n,r),await w.outputJson(h,{fileMap:f},{spaces:2}),{viewsDir:d,site:a,isFirstRun:S,pushed:p,pulled:y}}var Ce=new Set([".DS_Store","Thumbs.db","desktop.ini"]);async function Pe(t){let e=b.join(t,"src");if(!await w.pathExists(e))return[];let n=[];async function r(s){let i=await w.readdir(s,{withFileTypes:!0});for(let a of i){if(Ce.has(a.name))continue;let d=b.join(s,a.name);a.isDirectory()?await r(d):a.isFile()&&n.push(b.relative(t,d).replace(/\\/g,"/"))}}return await r(e),n}async function Oe(t,e,n,r,s,i){let a=[];for(let p of await Pe(t)){let y=b.join(t,p),v=e[p],O=await w.stat(y);if(v&&v.mtimeMs===O.mtimeMs)continue;let D=await w.readFile(y,"utf-8");D.trim()&&a.push({rel:p,full:y,stat:O,content:D,prior:v})}if(a.length===0)return 0;let d;try{d=await dt(n,r,"POST","/save_files",a.map(p=>({path:p.rel,content:p.content})))}catch(p){let y=p;return s(`\u274C Error saving files: ${y.body||y.message}`),0}let h=await $e(t),S=0,f=Me(d);for(let p=0;p<a.length;p++){let y=a[p],v=De(y,p,d,f);if(i?.(p+1,a.length),!v){let O="No save response returned";h[y.rel]=O,s(`\u274C Error saving ${y.rel}: ${O}`);continue}if(v.error){h[y.rel]=v.error,s(`\u274C Error saving ${y.rel}: ${v.error}`);continue}delete h[y.rel],e[y.rel]={mtimeMs:y.stat.mtimeMs},s(`\u2705 ${y.prior?"Updated":"Created"} ${y.rel}`),S++}return await Ae(t,h),S}function Me(t){let e=new Map;for(let n of t)n.path&&e.set(n.path,n);return e}function De(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 pt="sync-errors.log",gt=new Map;async function $e(t){let e=b.resolve(t),n=gt.get(e);if(n)return n;let r=await Re(e);return gt.set(e,r),r}async function Re(t){let e=b.join(t,pt);if(!await w.pathExists(e))return{};let n=await w.readFile(e,"utf-8"),r={};for(let s of n.split(`
7
- `)){let i=s.indexOf(": ");i>0&&(r[s.slice(0,i)]=s.slice(i+2))}return r}async function Ae(t,e){let n=b.join(t,pt),r=Object.entries(e);if(r.length===0){await w.remove(n);return}await w.outputFile(n,r.map(([s,i])=>`${s}: ${i}`).join(`
6
+ `).parse(process.argv),It.opts()}function j(t){let e=process.platform==="win32"?`where ${t}`:`which ${t}`;try{return Wt(e,{stdio:"ignore"}),!0}catch{return!1}}function Ut(t){if(process.platform!=="darwin")return!1;try{return Wt(`osascript -e 'id of application "${t}"'`,{stdio:"ignore"}),!0}catch{return!1}}function Me(t,e){let r={detached:!0,stdio:"ignore",windowsHide:!0};process.platform==="win32"?D("cmd.exe",["/c",t,...e],r).unref():D(t,e,r).unref()}var _t=[{name:"VS Code",cmd:"code",args:t=>["-n",t]},{name:"Cursor",cmd:"cursor",args:t=>["-n",t]}];function Nt(t,e){let n=(e?_t.filter(i=>i.cmd===e):_t).find(i=>j(i.cmd));return n?(Me(n.cmd,n.args(t)),n.name):null}function $e(t){if(process.platform==="darwin")return D("open",["-a","Terminal",t],{detached:!0,stdio:"ignore"}).unref(),!0;if(process.platform==="win32"){if(j("wt"))D("wt.exe",["-d",t],{detached:!0,stdio:"ignore"}).unref();else{let e=j("powershell")?"powershell.exe":"cmd.exe";D("cmd.exe",["/c","start","",e],{cwd:t,detached:!0,stdio:"ignore"}).unref()}return!0}return!1}function Le(t){return Ut("iTerm")?(D("open",["-a","iTerm",t],{detached:!0,stdio:"ignore"}).unref(),!0):!1}function Re(t){if(process.platform!=="darwin"||!j("claude"))return!1;let e=`tell application "Terminal" to do script ("cd " & quoted form of "${t}" & " && claude")`;return D("osascript",["-e",e,"-e",'tell application "Terminal" to activate'],{detached:!0,stdio:"ignore"}).unref(),!0}function qt(t){if(!t)return!1;try{return process.platform==="darwin"?D("open",[t],{detached:!0,stdio:"ignore"}).unref():process.platform==="win32"?D("cmd.exe",["/c","start","",t],{detached:!0,stdio:"ignore",windowsHide:!0}).unref():D("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}function Jt(){let t=process.platform==="darwin",e=process.platform==="win32",r=[];return(t||e)&&r.push({key:"terminal",label:"terminal",run:$e}),Ut("iTerm")&&r.push({key:"iterm",label:"iterm",run:Le}),j("code")&&r.push({key:"code",label:"vscode",run:n=>Nt(n,"code")!==null}),j("cursor")&&r.push({key:"cursor",label:"cursor",run:n=>Nt(n,"cursor")!==null}),t&&j("claude")&&r.push({key:"claude",label:"claude",run:Re}),r}import{EventEmitter as ir}from"node:events";import{spawn as sr}from"node:child_process";import K from"fs-extra";import Bt from"path";import or from"chokidar";import f from"fs-extra";import Ae from"os";import v from"path";var Ht={localhost:"http://app.sleekcms.test/api/mcp",development:"https://app.sleekcms.dev/api/mcp",production:"https://app.sleekcms.com/api/mcp"},je=["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"],yt=class extends Error{status;body;constructor(e,r,n){super(e),this.status=r,this.body=n}};async function bt(t,e,r,n,i){let s=await fetch(t+n,{method:r,headers:{Authorization:`Bearer ${e}`,...i!==void 0?{"Content-Type":"application/json"}:{}},body:i!==void 0?JSON.stringify(i):void 0});if(!s.ok){let c=await s.text();throw new yt(`${r} ${n} \u2192 ${s.status}: ${c}`,s.status,c)}return s.json()}async function Ie(t,e){e&&(await f.outputFile(v.join(t,"AGENT.md"),e),await f.outputFile(v.join(t,"CLAUDE.md"),e),await f.outputFile(v.join(t,".vscode","copilot-instructions.md"),e)),await f.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 _e(t,e){if(!e)return;let r=v.join(t,"build.mjs"),n=e.match(/build\.mjs v\d+/)?.[0];if(await f.pathExists(r)){let i=(await f.readFile(r,"utf-8")).match(/build\.mjs v\d+/)?.[0];if(i&&i===n)return}await f.outputFile(r,e)}async function Ne(t,e,r){let n=v.join(t,".cache","token");if(await f.pathExists(n)){if((await f.readFile(n,"utf-8")).trim()===e)return;r?.("\u267B\uFE0F Workspace was tied to a different site \u2014 recreating it."),await f.emptyDir(t)}await f.outputFile(n,e)}function We(t,e){let r=`${e.name.substr(0,20)} ${e.id}`.replace(/[\s_]+/g,"-").toLowerCase(),n=t||v.join(Ae.homedir(),"SleekCMS");return v.resolve(n,r)}async function St(t){let e=(t.token||"").trim();if(!e)throw new Error("syncSite: token is required");let r=t.log??(x=>console.log(x)),n=t.onProgress,i=(t.env||e.split("-")[2]||"production").toLowerCase(),s=Ht[i]||Ht.production,c=await bt(s,e,"GET","/get_site"),d=t.viewsDir?v.resolve(t.viewsDir):We(t.path,c);await f.ensureDir(d),await Ne(d,e,r),await Promise.all(je.map(x=>f.ensureDir(v.join(d,x)))),await _e(d,t.buildScript);let h=v.join(d,".cache","state.json");t.flush&&await f.remove(h);let S=!await f.pathExists(h),y=S?{}:(await f.readJson(h)).fileMap||{},m=0,w=0;return S?({fileMap:y,pulled:w}=await Ve(d,s,e,r,n),await Ie(d,t.agentMd)):m=await qe(d,y,s,e,r,n),await f.outputJson(h,{fileMap:y},{spaces:2}),{viewsDir:d,site:c,isFirstRun:S,pushed:m,pulled:w}}var Ge=new Set([".DS_Store","Thumbs.db","desktop.ini"]);async function Ue(t){let e=v.join(t,"src");if(!await f.pathExists(e))return[];let r=[];async function n(i){let s=await f.readdir(i,{withFileTypes:!0});for(let c of s){if(Ge.has(c.name))continue;let d=v.join(i,c.name);c.isDirectory()?await n(d):c.isFile()&&r.push(v.relative(t,d).replace(/\\/g,"/"))}}return await n(e),r}async function qe(t,e,r,n,i,s){let c=[];for(let m of await Ue(t)){let w=v.join(t,m),x=e[m],P=await f.stat(w);if(x&&x.mtimeMs===P.mtimeMs)continue;let L=await f.readFile(w,"utf-8");L.trim()&&c.push({rel:m,full:w,stat:P,content:L,prior:x})}if(c.length===0)return 0;let d;try{d=await bt(r,n,"POST","/save_files",c.map(m=>({path:m.rel,content:m.content})))}catch(m){let w=m;return i(`\u274C Error saving files: ${w.body||w.message}`),0}let h=await Xe(t),S=0,y=Je(d);for(let m=0;m<c.length;m++){let w=c[m],x=He(w,m,d,y);if(s?.(m+1,c.length),!x){let P="No save response returned";h[w.rel]=P,i(`\u274C Error saving ${w.rel}: ${P}`);continue}if(x.error){h[w.rel]=x.error,i(`\u274C Error saving ${w.rel}: ${x.error}`);continue}delete h[w.rel],e[w.rel]={mtimeMs:w.stat.mtimeMs},i(`\u2705 ${w.prior?"Updated":"Created"} ${w.rel}`),S++}return await Ke(t,h),S}function Je(t){let e=new Map;for(let r of t)r.path&&e.set(r.path,r);return e}function He(t,e,r,n){let i=n.get(t.rel);if(i)return i;let s=r[e];if(!s?.path||s.path===t.rel)return s}var vt="sync-errors.log",wt=new Map;async function Xe(t){let e=v.resolve(t),r=wt.get(e);if(r)return r;let n=await ze(e);return wt.set(e,n),n}async function ze(t){let e=v.join(t,vt);if(!await f.pathExists(e))return{};let r=await f.readFile(e,"utf-8"),n={};for(let i of r.split(`
7
+ `)){let s=i.indexOf(": ");s>0&&(n[i.slice(0,s)]=i.slice(s+2))}return n}async function Ke(t,e){let r=v.join(t,vt),n=Object.entries(e);if(n.length===0){await f.remove(r);return}await f.outputFile(r,n.map(([i,s])=>`${i}: ${s}`).join(`
8
8
  `)+`
9
- `)}async function Be(t,e,n,r,s){gt.delete(b.resolve(t)),await w.remove(b.join(t,pt)),r("\u{1F4E5} Fetching files...");let i=await dt(e,n,"GET","/get_files"),a={},d=0;for(let h of i){let S=b.join(t,h.path);await w.outputFile(S,h.content);let f=(await w.stat(S)).mtimeMs;d++,s?.(d,i.length),a[h.path]={mtimeMs:f}}return r(`\u2714\uFE0F Synced ${i.length} file(s).`),{fileMap:a,pulled:d}}import Le from"path";import Ie from"chokidar";var Fe=5e3,je=/(^|[/\\])(\.DS_Store|Thumbs\.db|desktop\.ini)$/,H=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??Fe}start(){if(this.watcher)return;let e=Le.join(this.viewsDir,"src");this.watcher=Ie.watch([e],{persistent:!0,ignoreInitial:!0,ignored:je}).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 tt from"fs-extra";import _e from"os";import It from"path";var Lt={localhost:"http://app.sleekcms.test/api",development:"https://app.sleekcms.dev/api",production:"https://app.sleekcms.com/api"};function ft(t){return Lt[t]||Lt.production}var Ne=It.join(_e.homedir(),"SleekCMS"),et=It.join(Ne,"auth.json"),mt;function Ft(t){mt=t||void 0}function yt(t){let e=[];return t&&e.push(`_auth=${t}`),mt&&e.push(`_dev=${mt}`),e.length?e.join("; "):void 0}var M=class extends Error{status;body;constructor(e,n,r=""){super(e),this.name="UserAuthError",this.status=n,this.body=r}};async function jt(){try{let t=await tt.readJson(et);return t&&t.userToken?t:null}catch{return null}}async function _t(t){await tt.outputJson(et,t,{spaces:2});try{await tt.chmod(et,384)}catch{}}async function Nt(){await tt.remove(et)}async function X(t){let e={},n=yt(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(ft(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 M(`${t.method} ${t.path} \u2192 ${r.status}: ${s}`,r.status,s)}return r.json()}async function Gt(t,e){let n={"Content-Type":"application/json"},r=yt();r&&(n.Cookie=r);let s=await fetch(ft(t)+"/auth/otp",{method:"PUT",headers:n,body:JSON.stringify({email:e})});if(!s.ok){let a=await s.text().catch(()=>"");throw new M(`Failed to send OTP \u2192 ${s.status}: ${a}`,s.status,a)}return{otp:(await s.json().catch(()=>({}))).do_not_use_in_code}}async function wt(t,e,n){let r={"Content-Type":"application/json"},s=yt();s&&(r.Cookie=s);let i=await fetch(ft(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 M(`Login failed \u2192 ${i.status}: ${h}`,i.status,h)}let a=i.headers.getSetCookie?.()??[],d=Ge(a);if(!d)throw new M("Login succeeded but no _auth cookie was returned.",500);return d}function Ge(t){for(let e of t){let n=e.match(/(?:^|;\s*)_auth=([^;]+)/);if(n&&n[1])return decodeURIComponent(n[1])}return null}async function St(t,e){return X({env:t,userToken:e,method:"GET",path:"/org"})}async function Wt(t,e,n){return X({env:t,userToken:e,method:"GET",path:"/site",subdomain:n})}async function qt(t,e,n,r){let{token:s}=await X({env:t,userToken:e,method:"POST",path:"/site_user/api_token",subdomain:n,slug:r,body:{refresh:!1}});return s}async function Jt(t,e,n){let r=await X({env:t,userToken:e,method:"POST",path:"/org",body:n?{name:n}:{}});return{id:r.id,name:r.name,subdomain:r.subdomain}}async function Ut(t,e,n,r){let s=await X({env:t,userToken:e,method:"POST",path:"/site",subdomain:n,body:{name:r}});return{id:s.id,name:s.name,slug:s.slug}}var zt=500;function qe(){return new Date().toTimeString().slice(0,8)}function nt(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 Kt(t){t&&(await Ht.remove(Xt.join(t,"src")),await Ht.remove(Xt.join(t,".cache","state.json")))}var rt=class extends We{env="production";userToken="";email;catalog=[];synced=new Map;message;messageIsError=!1;agentMd;isShuttingDown=!1;constructor(e){super(),this.agentMd=e}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:qe(),text:n}),r.logs.length>zt&&r.logs.splice(0,r.logs.length-zt),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 St(e,n);await this.buildCatalog(s)}async buildCatalog(e){let n=[];for(let r of e)try{n.push({org:r,sites:await Wt(this.env,this.userToken,r.subdomain)})}catch(s){if(s instanceof M&&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 qt(this.env,this.userToken,e.subdomain,n.slug)}catch(i){this.flash(`Failed to authorize "${n.name}": ${nt(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 ht({token:r,env:this.env,agentMd:this.agentMd,log:a=>this.appendLog(n.id,a),onProgress:(a,d)=>{s.progress={done:a,total:d},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 H({viewsDir:i.viewsDir,onSync:()=>this.runSync(n.id)}),s.watcher.start(),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 Kt(n.viewsDir),this.synced.delete(e),this.changed())}async ensureOrg(e){if(e)return e;if(this.catalog[0])return this.catalog[0].org;let n;try{n=await St(this.env,this.userToken)}catch(r){return this.flash(`Couldn't check your organizations: ${nt(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 Jt(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: ${nt(r)}`,!0),null}}async createNewSite(e,n){let r=await this.ensureOrg(n);if(!r)return;let s;try{s=await Ut(this.env,this.userToken,r.subdomain,e)}catch(a){this.flash(`Could not create "${e}": ${nt(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 ht({token:r.siteToken,env:this.env,viewsDir:r.viewsDir,agentMd:this.agentMd,flush:n.flush,log:i=>this.appendLog(e,i),onProgress:(i,a)=>{r.progress={done:i,total:a},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 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 H({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 Kt(e.viewsDir)}async exit(){this.isShuttingDown||(this.isShuttingDown=!0,await this.cleanupAll(),this.emit("exit"))}async logout(){this.isShuttingDown||(this.isShuttingDown=!0,await Nt(),await this.cleanupAll(),this.emit("logout"))}};import{useEffect as Ze,useState as ne}from"react";import{Box as tn,Text as en,useInput as nn}from"ink";import{useState as z}from"react";import{Box as K,Text as G}from"ink";import Vt from"ink-text-input";import{jsx as F,jsxs as W}from"react/jsx-runtime";function Yt(t){let e=t;return e?.body||e?.message||String(t)}function Qt({env:t,initialError:e,onAuthed:n}){let[r,s]=z("email"),[i,a]=z(""),[d,h]=z(""),[S,f]=z(e),[p,y]=z(),v=async D=>{let T=D.trim();if(T){f(void 0),s("busy");try{let{otp:E}=await Gt(t,T);if(a(T),E){let q=await wt(t,T,E);await n(q,T);return}y(`OTP sent to ${T}`),s("otp")}catch(E){f(Yt(E)),s("email")}}},O=async D=>{let T=D.trim();if(T){f(void 0),s("busy");try{let E=await wt(t,i,T);await n(E,i)}catch(E){f(Yt(E)),h(""),s("otp")}}};return W(K,{flexDirection:"column",children:[W(G,{bold:!0,children:["\u25C6 SleekCMS \u2014 sign in",t!=="production"?` (${t})`:""]}),S?W(G,{color:"red",children:["\u2717 ",S]}):null,r==="email"&&W(K,{marginTop:1,children:[F(G,{children:"Email: "}),F(Vt,{value:i,onChange:a,onSubmit:v,placeholder:"you@example.com"})]}),r==="otp"&&W(K,{flexDirection:"column",marginTop:1,children:[p?F(G,{color:"green",children:p}):null,W(K,{children:[F(G,{children:"Enter OTP: "}),F(Vt,{value:d,onChange:h,onSubmit:O})]})]}),r==="busy"&&F(K,{marginTop:1,children:F(G,{children:"Working\u2026"})})]})}import{Fragment as Ue,useEffect as bt,useMemo as He,useState as $}from"react";import{Box as m,Text as u,useInput as Xe,useStdout as ze}from"ink";import te from"ink-text-input";import Je from"node:os";function xt(t){if(!t)return"\u2014";let e=Je.homedir();return t.startsWith(e)?"~"+t.slice(e.length):t}function Zt(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 st(t){return!t||t.total<=0?null:Math.round(t.done/t.total*100)}function it(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"}}}import{Fragment as ot,jsx as o,jsxs as g}from"react/jsx-runtime";var j=(t,e)=>e<=0?0:Math.max(0,Math.min(t,e-1)),Ke=16,Ve=5;function Ye({k:t}){return o(u,{color:"black",backgroundColor:"green",children:` ${t} `})}function R({k:t,label:e,width:n=16}){return g(m,{width:n,children:[o(Ye,{k:t}),o(u,{color:"gray",children:` ${e}`})]})}function Qe({percent:t,width:e=28}){let n=Math.round(Math.max(0,Math.min(100,t))/100*e);return g(u,{children:[o(u,{color:"green",children:"\u2588".repeat(n)}),o(u,{color:"gray",children:"\u2591".repeat(Math.max(0,e-n))})]})}function ee({controller:t}){let{stdout:e}=ze(),n=He(()=>At(),[]),[,r]=$(0),[s,i]=$(()=>new Date),[a,d]=$(null),[h,S]=$(()=>t.synced.size>0||t.catalog.some(l=>l.sites.length>0)?"dashboard":t.catalog.length<=1?"newName":"newOrg"),[f,p]=$(0),[y,v]=$(""),[O,D]=$(!1),[T,E]=$(""),[q,V]=$(!1),Tt=()=>r(c=>c+1);bt(()=>(t.on("change",Tt),()=>{t.off("change",Tt)}),[t]),bt(()=>{let c=setInterval(()=>i(new Date),1e3);return()=>clearInterval(c)},[]);let A=t.catalog.flatMap(c=>c.sites.map(l=>({org:c.org,site:l,entry:t.synced.get(l.id)}))).sort((c,l)=>(c.entry?0:1)-(l.entry?0:1)),B=T.trim().toLowerCase(),C=B?A.filter(c=>c.site.name.toLowerCase().includes(B)||(c.org.name||c.org.subdomain||"").toLowerCase().includes(B)):A,_=C.findIndex(c=>c.site.id===a);_<0&&(_=0);let k=C[_],x=k?.entry,kt=C.findIndex(c=>!c.entry),N=Math.max(Ve,(e?.rows??30)-Ke),J=C.length,Y=J>N,oe=Math.floor(N/2),U=Y?Math.max(0,Math.min(_-oe,J-N)):0,ct=Math.min(J,U+N),ae=C.slice(U,ct);bt(()=>{h!=="dashboard"||O||A.length>0||(v(""),t.catalog.length<=1?S("newName"):(p(0),S("newOrg")))},[A.length,h,O,t.catalog.length]);let Q=c=>{if(C.length===0)return;let l=j(_+c,C.length);d(C[l].site.id)},ce=()=>{k&&(k.entry?t.removeSite(k.site.id):t.addSite(k.org,k.site))},ue=()=>{v(""),t.catalog.length<=1?S("newName"):(p(0),S("newOrg"))},le=c=>{if(!x){t.flash("Press \u23CE to start syncing this site first.",!0);return}let l=c.run(x.viewsDir);t.flash(l?`Opened ${c.label} at ${xt(x.viewsDir)}`:`Couldn't open ${c.label}.`,!l)},ge=()=>{if(!x){t.flash("Press \u23CE to start syncing this site first.",!0);return}t.togglePause(x.site.id)};Xe((c,l)=>{if(h==="newOrg"){l.escape?S("dashboard"):l.upArrow?p(P=>j(P-1,t.catalog.length)):l.downArrow?p(P=>j(P+1,t.catalog.length)):l.return&&S("newName");return}if(q){l.escape?(V(!1),E(""),d(null)):l.return?V(!1):l.upArrow?Q(-1):l.downArrow&&Q(1);return}if(l.escape){E(""),d(null);return}if(c==="/")return void V(!0);if(c==="n")return void ue();if(c==="q")return void t.logout();if(l.upArrow)return Q(-1);if(l.downArrow)return Q(1);if(l.return||c===" ")return ce();if(c==="p")return ge();if(c>="1"&&c<="9"){let P=n[parseInt(c,10)-1];if(P)return le(P)}},{isActive:h!=="newName"});let de=async c=>{let l=c.trim();if(!l){S("dashboard");return}let P=t.catalog[j(f,t.catalog.length)]?.org??t.catalog[0]?.org;D(!0),S("dashboard"),await t.createNewSite(l,P),D(!1)},he=Math.max(5,(e?.rows??30)-18);return g(m,{flexDirection:"column",children:[g(m,{justifyContent:"space-between",paddingX:1,children:[g(m,{children:[o(u,{color:"green",bold:!0,children:"\u25C6 SleekCMS"}),o(u,{color:"gray",children:" \u2502 "}),o(u,{children:t.email??"signed in"})]}),g(m,{children:[g(u,{color:"gray",children:[B?`${C.length}/${A.length}`:A.length," sites \xB7 "]}),g(u,{color:"green",children:[t.watchingCount()," watching"]}),o(u,{color:"gray",children:` \xB7 ${s.toTimeString().slice(0,8)}`})]})]}),g(m,{children:[g(m,{flexDirection:"column",borderStyle:"round",borderColor:"green",width:38,paddingX:1,children:[g(m,{justifyContent:"space-between",children:[o(u,{color:"green",bold:!0,children:"SITE WORKSPACES"}),A.length>N&&!q&&!B?o(u,{color:"gray",children:"/ filter"}):null]}),q?g(m,{children:[o(u,{color:"cyan",children:"/ "}),o(te,{value:T,onChange:E,onSubmit:()=>V(!1),placeholder:"type to filter\u2026"})]}):B?o(u,{color:"gray",children:`/ ${T} \xB7 ${C.length} match${C.length===1?"":"es"}`}):null,o(m,{flexDirection:"column",marginTop:1,minHeight:Y?N+3:void 0,children:C.length===0?o(u,{color:"gray",children:B?`no matches for \u201C${T}\u201D`:"no sites \u2014 press n to create one"}):g(ot,{children:[Y?o(u,{color:"gray",children:U>0?` \u2191 ${U} more`:" "}):null,ae.map((c,l)=>{let P=U+l,Et=c.entry?it(c.entry.status):{dot:"\xB7",color:"gray",label:"idle"},Ct=st(c.entry?.progress),pe=c.entry?c.entry.status==="syncing"&&Ct!=null?`${Ct}%`:c.entry.status==="paused"?"paused":"--":"",Z=P===_,me=P===kt&&kt>0;return g(Ue,{children:[me?o(u,{color:"gray",children:"\u2500".repeat(34)}):null,g(m,{justifyContent:"space-between",children:[g(m,{children:[o(u,{color:Z?"green":"gray",children:Z?"\u276F ":" "}),g(u,{color:Et.color,children:[Et.dot," "]}),o(u,{color:Z?"white":c.entry?void 0:"gray",bold:Z,children:c.site.name})]}),o(u,{color:c.entry?.status==="syncing"?"green":"gray",children:pe})]})]},c.site.id)}),Y?o(u,{color:"gray",children:ct<J?` \u2193 ${J-ct} more`:" "}):null]})}),o(m,{marginTop:1,children:o(u,{color:"gray",children:"+ new site \xB7 n"})})]}),g(m,{flexDirection:"column",flexGrow:1,marginLeft:1,children:[o(m,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,children:k?g(ot,{children:[g(u,{children:[k.org.name||k.org.subdomain?g(u,{color:"gray",children:[k.org.name||k.org.subdomain," / "]}):null,o(u,{color:"cyan",bold:!0,children:k.site.name})]}),g(m,{marginTop:1,children:[o(u,{color:"gray",children:"status "}),x?o(u,{color:it(x.status).color,children:`: ${it(x.status).label}`}):o(u,{color:"gray",children:": idle \u2014 not syncing (press \u23CE to start)"})]}),g(m,{children:[o(u,{color:"gray",children:"path "}),o(u,{children:x?.viewsDir?xt(x.viewsDir):"\u2014"})]}),g(m,{children:[o(u,{color:"gray",children:"synced "}),o(u,{children:Zt(x?.lastSyncedAt)}),o(u,{color:"gray",children:" progress "}),o(u,{color:x?.status==="syncing"?"green":"gray",children:x?.status==="syncing"?`${st(x.progress)??0}%`:"\u2014"})]}),o(m,{marginTop:1,children:o(Qe,{percent:x?x.status==="syncing"?st(x.progress)??0:x.status==="watching"?100:0:0})})]}):o(u,{color:"gray",children:"No site selected."})}),h==="dashboard"?g(m,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,marginTop:1,flexGrow:1,children:[g(u,{children:[o(u,{color:"cyan",bold:!0,children:"LOGS \xB7 "}),o(u,{color:"white",children:k?.site.name??"\u2014"})]}),o(m,{flexDirection:"column",marginTop:1,children:!x||x.logs.length===0?o(u,{color:"gray",children:"no activity yet"}):x.logs.slice(-he).map((c,l)=>g(m,{children:[g(u,{color:"gray",children:[c.time," "]}),o(u,{children:c.text})]},l))})]}):g(m,{flexDirection:"column",borderStyle:"round",borderColor:"blue",paddingX:1,marginTop:1,flexGrow:1,children:[h==="newOrg"&&g(ot,{children:[o(u,{color:"cyan",bold:!0,children:"CREATE SITE \u2014 PICK ORG"}),o(u,{color:"gray",children:"\u2191/\u2193 select \xB7 \u23CE next \xB7 esc cancel"}),o(m,{flexDirection:"column",marginTop:1,children:t.catalog.map((c,l)=>g(u,{color:l===j(f,t.catalog.length)?"blue":void 0,bold:l===j(f,t.catalog.length),children:[l===j(f,t.catalog.length)?"\u276F ":" ",c.org.name||c.org.subdomain]},c.org.id))})]}),h==="newName"&&g(ot,{children:[o(u,{color:"cyan",bold:!0,children:"CREATE SITE"}),g(m,{marginTop:1,children:[o(u,{children:"New site name: "}),o(te,{value:y,onChange:v,onSubmit:de})]}),o(u,{color:"gray",children:"\u23CE create \xB7 empty + \u23CE to cancel"})]})]})]})]}),t.message?o(m,{paddingX:1,children:g(u,{color:t.messageIsError?"red":"gray",children:[t.messageIsError?"\u2717 ":"",t.message]})}):null,g(m,{flexDirection:"column",marginTop:1,children:[g(m,{paddingX:1,flexWrap:"wrap",children:[o(R,{k:"\u2191\u2193",label:"select"}),o(R,{k:"/",label:"search"}),o(R,{k:"\u23CE",label:"start/stop"}),o(R,{k:"p",label:"pause"}),o(R,{k:"n",label:"new site"}),o(R,{k:"q",label:"logout"}),o(R,{k:"ctrl+c",label:"quit",width:14})]}),n.length>0?o(m,{paddingX:1,marginTop:1,flexWrap:"wrap",children:n.map((c,l)=>o(R,{k:String(l+1),label:c.label},c.key))}):null]})]})}import{jsx as at}from"react/jsx-runtime";function re(t){let e=t;return e?.body||e?.message||String(t)}function se({controller:t,env:e,devSecret:n,savedToken:r,savedEmail:s}){let[i,a]=ne(r?"loading":"auth"),[d,h]=ne();nn((f,p)=>{p.ctrl&&f==="c"&&t.exit()}),Ze(()=>{r&&t.init(e,r,s).then(()=>a("manage")).catch(f=>{f instanceof M&&f.status===401?h("Saved session expired \u2014 please sign in again."):h(re(f)),a("auth")})},[]);let S=async(f,p)=>{h(void 0),await _t({env:e,userToken:f,devSecret:n,email:p}),a("loading");try{await t.init(e,f,p),a("manage")}catch(y){h(re(y)),a("auth")}};return i==="auth"?at(Qt,{env:e,initialError:d,onAuthed:S}):i==="loading"?at(tn,{children:at(en,{children:"Loading your sites\u2026 "})}):at(ee,{controller:t})}import{jsx as dn}from"react/jsx-runtime";var cn=on(import.meta.url),vt=$t();if(vt.version){let t=cn("../package.json");console.log(t.version),process.exit(0)}var un=ie.dirname(sn(import.meta.url)),ln=rn(ie.join(un,"AGENT.md"),"utf-8");async function gn(){let t=new rt(ln),e=await jt(),n=(vt.env??e?.env??"production").toLowerCase(),r=vt.dev??process.env.SLEEKCMS_DEV_SECRET??e?.devSecret;Ft(r),process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[3J\x1B[H");let s=an(dn(se,{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()}gn().catch(t=>{let e=t;console.error("\u274C",e.body||e.message),process.exit(1)});
9
+ `)}async function Ve(t,e,r,n,i){wt.delete(v.resolve(t)),await f.remove(v.join(t,vt)),n("\u{1F4E5} Fetching files...");let s=await bt(e,r,"GET","/get_files"),c={},d=0;for(let h of s){let S=v.join(t,h.path);await f.outputFile(S,h.content);let y=(await f.stat(S)).mtimeMs;d++,i?.(d,s.length),c[h.path]={mtimeMs:y}}return n(`\u2714\uFE0F Synced ${s.length} file(s).`),{fileMap:c,pulled:d}}import Ye from"path";import Qe from"chokidar";var Ze=5e3,tr=/(^|[/\\])(\.DS_Store|Thumbs\.db|desktop\.ini)$/,z=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??Ze}start(){if(this.watcher)return;let e=Ye.join(this.viewsDir,"src");this.watcher=Qe.watch([e],{persistent:!0,ignoreInitial:!0,ignored:tr}).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 r=e;console.error("\u274C Sync failed:",r.body||r.message)}finally{this.syncInFlight=!1}}}};import it from"fs-extra";import er from"os";import zt from"path";var Xt={localhost:"http://app.sleekcms.test/api",development:"https://app.sleekcms.dev/api",production:"https://app.sleekcms.com/api"};function Tt(t){return Xt[t]||Xt.production}var rr=zt.join(er.homedir(),"SleekCMS"),st=zt.join(rr,"auth.json"),xt;function Kt(t){xt=t||void 0}function kt(t){let e=[];return t&&e.push(`_auth=${t}`),xt&&e.push(`_dev=${xt}`),e.length?e.join("; "):void 0}var O=class extends Error{status;body;constructor(e,r,n=""){super(e),this.name="UserAuthError",this.status=r,this.body=n}};async function Vt(){try{let t=await it.readJson(st);return t&&t.userToken?t:null}catch{return null}}async function Yt(t){await it.outputJson(st,t,{spaces:2});try{await it.chmod(st,384)}catch{}}async function Qt(){await it.remove(st)}async function G(t){let e={},r=kt(t.userToken);r&&(e.Cookie=r),t.subdomain&&(e["x-subdomain"]=t.subdomain),t.slug&&(e.site=t.slug),t.body!==void 0&&(e["Content-Type"]="application/json");let n=await fetch(Tt(t.env)+t.path,{method:t.method,headers:e,body:t.body!==void 0?JSON.stringify(t.body):void 0});if(!n.ok){let i=await n.text().catch(()=>"");throw new O(`${t.method} ${t.path} \u2192 ${n.status}: ${i}`,n.status,i)}return n.json()}async function Zt(t,e){let r={"Content-Type":"application/json"},n=kt();n&&(r.Cookie=n);let i=await fetch(Tt(t)+"/auth/otp",{method:"PUT",headers:r,body:JSON.stringify({email:e})});if(!i.ok){let c=await i.text().catch(()=>"");throw new O(`Failed to send OTP \u2192 ${i.status}: ${c}`,i.status,c)}return{otp:(await i.json().catch(()=>({}))).do_not_use_in_code}}async function Ct(t,e,r){let n={"Content-Type":"application/json"},i=kt();i&&(n.Cookie=i);let s=await fetch(Tt(t)+"/auth/login",{method:"POST",headers:n,body:JSON.stringify({email:e,otp:r})});if(!s.ok){let h=await s.text().catch(()=>"");throw new O(`Login failed \u2192 ${s.status}: ${h}`,s.status,h)}let c=s.headers.getSetCookie?.()??[],d=nr(c);if(!d)throw new O("Login succeeded but no _auth cookie was returned.",500);return d}function nr(t){for(let e of t){let r=e.match(/(?:^|;\s*)_auth=([^;]+)/);if(r&&r[1])return decodeURIComponent(r[1])}return null}async function Et(t,e){return G({env:t,userToken:e,method:"GET",path:"/org"})}async function te(t,e,r){return G({env:t,userToken:e,method:"GET",path:"/site",subdomain:r})}async function ee(t,e,r,n){let i;try{i=await G({env:t,userToken:e,method:"GET",path:"/site_build",subdomain:r,slug:n})}catch{return null}if(!Array.isArray(i)||i.length===0)return null;let s=i[0],c=i.find(h=>typeof h.url=="string"&&h.url.length>0),d=Array.isArray(s.errors)?s.errors.map(h=>String(h)):[];return d.length>0?{status:"error",mode:"preview",errors:d,url:c?.url||void 0,at:s.updated_at?Date.parse(s.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 re(t,e,r,n){let{token:i}=await G({env:t,userToken:e,method:"POST",path:"/site_user/api_token",subdomain:r,slug:n,body:{refresh:!1}});return i}async function ne(t,e,r){let n=await G({env:t,userToken:e,method:"POST",path:"/org",body:r?{name:r}:{}});return{id:n.id,name:n.name,subdomain:n.subdomain}}async function ie(t,e,r,n){let i=await G({env:t,userToken:e,method:"POST",path:"/site",subdomain:r,body:{name:n}});return{id:i.id,name:i.name,slug:i.slug}}var se=500;function ar(){return new Date().toTimeString().slice(0,8)}function ot(t){let e=t;if(e.body){try{let r=JSON.parse(e.body);if(r&&typeof r.message=="string")return r.message;if(r&&typeof r.error=="string")return r.error}catch{}return e.body}return e.message}async function oe(t){t&&(await K.remove(Bt.join(t,"src")),await K.remove(Bt.join(t,".cache","state.json")))}var at=class extends ir{env="production";userToken="";email;catalog=[];synced=new Map;message;messageIsError=!1;agentMd;buildScript;isShuttingDown=!1;constructor(e,r){super(),this.agentMd=e,this.buildScript=r}changed(){this.emit("change")}flash(e,r=!1){this.message=e,this.messageIsError=r,this.changed()}appendLog(e,r){let n=this.synced.get(e);n&&(n.logs.push({time:ar(),text:r}),n.logs.length>se&&n.logs.splice(0,n.logs.length-se),this.changed())}siteCount(){return this.synced.size}syncingCount(){let e=0;for(let r of this.synced.values())r.status==="syncing"&&e++;return e}watchingCount(){let e=0;for(let r of this.synced.values())(r.status==="watching"||r.status==="syncing")&&e++;return e}async init(e,r,n){this.env=e,this.userToken=r,n&&(this.email=n);let i=await Et(e,r);await this.buildCatalog(i)}async buildCatalog(e){let r=[];for(let n of e)try{r.push({org:n,sites:await te(this.env,this.userToken,n.subdomain)})}catch(i){if(i instanceof O&&i.status===401)throw i;r.push({org:n,sites:[]})}this.catalog=r,this.changed()}isSynced(e){return this.synced.has(e)}availableSites(){let e=[];for(let r of this.catalog)for(let n of r.sites)this.synced.has(n.id)||e.push({org:r.org,site:n});return e}async addSite(e,r){if(this.synced.has(r.id))return;let n;try{n=await re(this.env,this.userToken,e.subdomain,r.slug)}catch(s){this.flash(`Failed to authorize "${r.name}": ${ot(s)}`,!0);return}let i={org:e,site:r,siteToken:n,viewsDir:"",status:"syncing",logs:[]};this.synced.set(r.id,i),this.appendLog(r.id,"\u2795 added \u2014 pulling files"),this.changed();try{let s=await St({token:n,env:this.env,agentMd:this.agentMd,buildScript:this.buildScript,log:c=>this.appendLog(r.id,c),onProgress:(c,d)=>{i.progress={done:c,total:d},this.changed()}});i.viewsDir=s.viewsDir,i.progress=void 0,i.status="watching",i.lastSyncedAt=Date.now(),i.lastMsg=`pulled ${s.pulled} file(s)`,i.watcher=new z({viewsDir:s.viewsDir,onSync:()=>this.runSync(r.id)}),i.watcher.start(),this.watchBuildStatus(r.id),this.seedLastBuild(r.id),this.appendLog(r.id,"\u{1F440} watching for changes")}catch(s){let c=s;i.status="error",i.progress=void 0,i.lastMsg=c.body||c.message,this.appendLog(r.id,`\u274C ${c.body||c.message}`)}this.changed()}async removeSite(e){let r=this.synced.get(e);r&&(r.watcher?.setShuttingDown(!0),await r.watcher?.stop(),await r.buildWatcher?.close(),await oe(r.viewsDir),this.synced.delete(e),this.changed())}buildSite(e,r=!1){let n=this.synced.get(e);if(!n||!n.viewsDir)return;let i=r?"live":"preview";this.writeBuildFile(n.viewsDir,{status:"building",mode:i,stage:"queued",url:n.lastBuild?.url,at:Date.now()}),sr("node",r?["build.mjs","--deploy"]:["build.mjs"],{cwd:n.viewsDir,stdio:"ignore"}).on("error",d=>{this.writeBuildFile(n.viewsDir,{status:"error",mode:i,errors:[d.message],url:n.lastBuild?.url,at:Date.now()}),this.appendLog(e,`\u274C build: ${d.message}`)}),this.appendLog(e,r?"\u{1F680} deploying\u2026":"\u{1F528} building\u2026")}buildFile(e){return Bt.join(e,".cache","build.json")}async readBuildFile(e){try{let r=await K.readJson(this.buildFile(e));return r&&r.status?r:null}catch{return null}}async writeBuildFile(e,r){let n=this.buildFile(e),i=`${n}.tmp`;try{await K.outputJson(i,r),await K.rename(i,n)}catch{}}watchBuildStatus(e){let r=this.synced.get(e);!r||!r.viewsDir||(r.buildWatcher=or.watch(this.buildFile(r.viewsDir),{ignoreInitial:!1}).on("add",()=>{this.readBuildStatus(e)}).on("change",()=>{this.readBuildStatus(e)}))}async readBuildStatus(e){let r=this.synced.get(e);if(!r)return;let n=await this.readBuildFile(r.viewsDir);if(!n)return;let i=r.lastBuild?.url;r.lastBuild={status:n.status,mode:n.mode??"preview",url:n.url??(n.status==="success"?void 0:i),errors:n.errors,stage:n.stage,at:n.at},this.changed()}async seedLastBuild(e){let r=this.synced.get(e);if(!r)return;let n=await ee(this.env,this.userToken,r.org.subdomain,r.site.slug);if(!n)return;let i=this.synced.get(e);if(!i)return;let s=await this.readBuildFile(i.viewsDir);s?.at!=null&&(n.at==null||s.at>=n.at)||await this.writeBuildFile(i.viewsDir,n)}async ensureOrg(e){if(e)return e;if(this.catalog[0])return this.catalog[0].org;let r;try{r=await Et(this.env,this.userToken)}catch(n){return this.flash(`Couldn't check your organizations: ${ot(n)}`,!0),null}if(r.length>0){try{await this.buildCatalog(r)}catch{}return r[0]}try{let n=this.email?`${this.email.split("@")[0]}'s Sites`:"My Sites";this.flash("No organization yet \u2014 creating one\u2026");let i=await ne(this.env,this.userToken,n);return this.catalog=[...this.catalog,{org:i,sites:[]}],this.changed(),i}catch(n){return this.flash(`Could not create an organization: ${ot(n)}`,!0),null}}async createNewSite(e,r){let n=await this.ensureOrg(r);if(!n)return;let i;try{i=await ie(this.env,this.userToken,n.subdomain,e)}catch(c){this.flash(`Could not create "${e}": ${ot(c)}`,!0);return}let s=this.catalog.find(c=>c.org.id===n.id);s&&!s.sites.some(c=>c.id===i.id)&&(s.sites=[...s.sites,i]),this.flash(`Created "${i.name}".`),await this.addSite(n,i)}async refetch(e){await this.runSync(e,{flush:!0})}async runSync(e,r={}){let n=this.synced.get(e);if(!(!n||!n.viewsDir||n.status==="paused")){n.status="syncing",this.changed();try{let i=await St({token:n.siteToken,env:this.env,viewsDir:n.viewsDir,agentMd:this.agentMd,buildScript:this.buildScript,flush:r.flush,log:s=>this.appendLog(e,s),onProgress:(s,c)=>{n.progress={done:s,total:c},this.changed()}});n.progress=void 0,n.status="watching",n.lastSyncedAt=Date.now(),r.flush?n.lastMsg=`re-fetched ${i.pulled} file(s)`:n.lastMsg=i.pushed>0?`pushed ${i.pushed} file(s)`:"up to date"}catch(i){let s=i;n.status="error",n.progress=void 0,n.lastMsg=s.body||s.message,this.appendLog(e,`\u274C ${s.body||s.message}`)}this.changed()}}async togglePause(e){let r=this.synced.get(e);!r||!r.viewsDir||(r.status==="paused"?(r.watcher=new z({viewsDir:r.viewsDir,onSync:()=>this.runSync(e)}),r.watcher.start(),r.status="watching",this.appendLog(e,"\u25B6 resumed \u2014 watching for changes")):(r.watcher?.setShuttingDown(!0),await r.watcher?.stop(),r.watcher=void 0,r.progress=void 0,r.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 oe(e.viewsDir)}async exit(){this.isShuttingDown||(this.isShuttingDown=!0,await this.cleanupAll(),this.emit("exit"))}async logout(){this.isShuttingDown||(this.isShuttingDown=!0,await Qt(),await this.cleanupAll(),this.emit("logout"))}};import{useEffect as wr,useState as pe}from"react";import{Box as br,Text as Sr,useInput as vr}from"ink";import{useState as V}from"react";import{Box as Y,Text as U}from"ink";import ae from"ink-text-input";import{jsx as I,jsxs as q}from"react/jsx-runtime";function ce(t){let e=t;return e?.body||e?.message||String(t)}function ue({env:t,initialError:e,onAuthed:r}){let[n,i]=V("email"),[s,c]=V(""),[d,h]=V(""),[S,y]=V(e),[m,w]=V(),x=async L=>{let T=L.trim();if(T){y(void 0),i("busy");try{let{otp:C}=await Zt(t,T);if(c(T),C){let J=await Ct(t,T,C);await r(J,T);return}w(`OTP sent to ${T}`),i("otp")}catch(C){y(ce(C)),i("email")}}},P=async L=>{let T=L.trim();if(T){y(void 0),i("busy");try{let C=await Ct(t,s,T);await r(C,s)}catch(C){y(ce(C)),h(""),i("otp")}}};return q(Y,{flexDirection:"column",children:[q(U,{bold:!0,children:["\u25C6 SleekCMS \u2014 sign in",t!=="production"?` (${t})`:""]}),S?q(U,{color:"red",children:["\u2717 ",S]}):null,n==="email"&&q(Y,{marginTop:1,children:[I(U,{children:"Email: "}),I(ae,{value:s,onChange:c,onSubmit:x,placeholder:"you@example.com"})]}),n==="otp"&&q(Y,{flexDirection:"column",marginTop:1,children:[m?I(U,{color:"green",children:m}):null,q(Y,{children:[I(U,{children:"Enter OTP: "}),I(ae,{value:d,onChange:h,onSubmit:P})]})]}),n==="busy"&&I(Y,{marginTop:1,children:I(U,{children:"Working\u2026"})})]})}import{Fragment as ur,useEffect as dt,useMemo as lr,useState as M}from"react";import{Box as p,Text as u,useInput as dr,useStdout as gr}from"ink";import ge from"ink-text-input";import cr from"node:os";function Pt(t){if(!t)return"\u2014";let e=cr.homedir();return t.startsWith(e)?"~"+t.slice(e.length):t}function Dt(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 r=Math.floor(e/60);if(r<60)return`${r}m ago`;let n=Math.floor(r/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function ut(t){return!t||t.total<=0?null:Math.round(t.done/t.total*100)}var ct=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function le(t){return ct[(t%ct.length+ct.length)%ct.length]}function lt(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 de(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 Q,jsx as o,jsxs as l}from"react/jsx-runtime";var _=(t,e)=>e<=0?0:Math.max(0,Math.min(t,e-1)),hr=16,pr=5;function mr({k:t}){return o(u,{color:"black",backgroundColor:"green",children:` ${t} `})}function $({k:t,label:e,width:r=16}){return l(p,{width:r,children:[o(mr,{k:t}),o(u,{color:"gray",children:` ${e}`})]})}function fr({percent:t,width:e=28}){let r=Math.round(Math.max(0,Math.min(100,t))/100*e);return l(u,{children:[o(u,{color:"green",children:"\u2588".repeat(r)}),o(u,{color:"gray",children:"\u2591".repeat(Math.max(0,e-r))})]})}function yr({frame:t,width:e=24,seg:r=6}){let n=e+r,i=t%n-r,s="";for(let c=0;c<e;c++)s+=c>=i&&c<i+r?"\u25AE":"\u25AF";return o(u,{color:"yellow",children:s})}function he({controller:t}){let{stdout:e}=gr(),r=lr(()=>Jt(),[]),[,n]=M(0),[i,s]=M(()=>new Date),[c,d]=M(null),[h,S]=M(()=>t.synced.size>0||t.catalog.some(g=>g.sites.length>0)?"dashboard":t.catalog.length<=1?"newName":"newOrg"),[y,m]=M(0),[w,x]=M(""),[P,L]=M(!1),[T,C]=M(""),[J,Z]=M(!1),[$t,be]=M(0),Lt=()=>n(a=>a+1);dt(()=>(t.on("change",Lt),()=>{t.off("change",Lt)}),[t]),dt(()=>{let a=setInterval(()=>s(new Date),1e3);return()=>clearInterval(a)},[]);let F=t.catalog.flatMap(a=>a.sites.map(g=>({org:a.org,site:g,entry:t.synced.get(g.id)}))).sort((a,g)=>(a.entry?0:1)-(g.entry?0:1)),A=T.trim().toLowerCase(),E=A?F.filter(a=>a.site.name.toLowerCase().includes(A)||(a.org.name||a.org.subdomain||"").toLowerCase().includes(A)):F,N=E.findIndex(a=>a.site.id===c);N<0&&(N=0);let k=E[N],b=k?.entry,R=b?.lastBuild,ht=R?.status==="building",pt=de(R),tt=ht;if(!tt){for(let a of t.synced.values())if(a.status==="watching"||a.status==="syncing"){tt=!0;break}}dt(()=>{if(!tt)return;let a=setInterval(()=>be(g=>g+1),120);return()=>clearInterval(a)},[tt]);let Rt=E.findIndex(a=>!a.entry),W=Math.max(pr,(e?.rows??30)-hr),H=E.length,et=H>W,Se=Math.floor(W/2),X=et?Math.max(0,Math.min(N-Se,H-W)):0,mt=Math.min(H,X+W),ve=E.slice(X,mt);dt(()=>{h!=="dashboard"||P||F.length>0||(x(""),t.catalog.length<=1?S("newName"):(m(0),S("newOrg")))},[F.length,h,P,t.catalog.length]);let rt=a=>{if(E.length===0)return;let g=_(N+a,E.length);d(E[g].site.id)},xe=()=>{k&&(k.entry?t.removeSite(k.site.id):t.addSite(k.org,k.site))},Te=()=>{x(""),t.catalog.length<=1?S("newName"):(m(0),S("newOrg"))},ke=a=>{if(!b){t.flash("Press \u23CE to start syncing this site first.",!0);return}let g=a.run(b.viewsDir);t.flash(g?`Opened ${a.label} at ${Pt(b.viewsDir)}`:`Couldn't open ${a.label}.`,!g)},Ce=()=>{if(!b){t.flash("Press \u23CE to start syncing this site first.",!0);return}t.togglePause(b.site.id)},Ft=a=>{if(!b){t.flash("Press \u23CE to start syncing this site first.",!0);return}t.buildSite(b.site.id,a),t.flash(a?"Deploying \u2014 building live\u2026":"Building preview\u2026")},Ee=()=>{let a=b?.lastBuild?.url;if(!a){t.flash("No build URL yet \u2014 press b to build.",!0);return}t.flash(qt(a)?`Opening ${a}`:"Couldn't open the browser.",!1)};dr((a,g)=>{if(h==="newOrg"){g.escape?S("dashboard"):g.upArrow?m(B=>_(B-1,t.catalog.length)):g.downArrow?m(B=>_(B+1,t.catalog.length)):g.return&&S("newName");return}if(J){g.escape?(Z(!1),C(""),d(null)):g.return?Z(!1):g.upArrow?rt(-1):g.downArrow&&rt(1);return}if(g.escape){C(""),d(null);return}if(a==="/")return void Z(!0);if(a==="n")return void Te();if(a==="q")return void t.logout();if(g.upArrow)return rt(-1);if(g.downArrow)return rt(1);if(g.return||a===" ")return xe();if(a==="p")return Ce();if(a==="b")return Ft(!1);if(a==="B")return Ft(!0);if(a==="o")return Ee();if(a>="1"&&a<="9"){let B=r[parseInt(a,10)-1];if(B)return ke(B)}},{isActive:h!=="newName"});let Be=async a=>{let g=a.trim();if(!g){S("dashboard");return}let B=t.catalog[_(y,t.catalog.length)]?.org??t.catalog[0]?.org;L(!0),S("dashboard"),await t.createNewSite(g,B),L(!1)},Pe=Math.max(5,(e?.rows??30)-18);return l(p,{flexDirection:"column",children:[l(p,{justifyContent:"space-between",paddingX:1,children:[l(p,{children:[o(u,{color:"green",bold:!0,children:"\u25C6 SleekCMS"}),o(u,{color:"gray",children:" \u2502 "}),o(u,{children:t.email??"signed in"})]}),l(p,{children:[l(u,{color:"gray",children:[A?`${E.length}/${F.length}`:F.length," sites \xB7 "]}),l(u,{color:"green",children:[t.watchingCount()," watching"]}),o(u,{color:"gray",children:` \xB7 ${i.toTimeString().slice(0,8)}`})]})]}),l(p,{children:[l(p,{flexDirection:"column",borderStyle:"round",borderColor:"green",width:38,paddingX:1,children:[l(p,{justifyContent:"space-between",children:[o(u,{color:"green",bold:!0,children:"SITE WORKSPACES"}),F.length>W&&!J&&!A?o(u,{color:"gray",children:"/ filter"}):null]}),J?l(p,{children:[o(u,{color:"cyan",children:"/ "}),o(ge,{value:T,onChange:C,onSubmit:()=>Z(!1),placeholder:"type to filter\u2026"})]}):A?o(u,{color:"gray",children:`/ ${T} \xB7 ${E.length} match${E.length===1?"":"es"}`}):null,o(p,{flexDirection:"column",marginTop:1,minHeight:et?W+3:void 0,children:E.length===0?o(u,{color:"gray",children:A?`no matches for \u201C${T}\u201D`:"no sites \u2014 press n to create one"}):l(Q,{children:[et?o(u,{color:"gray",children:X>0?` \u2191 ${X} more`:" "}):null,ve.map((a,g)=>{let B=X+g,At=a.entry?lt(a.entry.status):{dot:"\xB7",color:"gray",label:"idle"},jt=ut(a.entry?.progress),De=a.entry?a.entry.status==="syncing"&&jt!=null?`${jt}%`:a.entry.status==="paused"?"paused":"--":"",nt=B===N,Oe=B===Rt&&Rt>0;return l(ur,{children:[Oe?o(u,{color:"gray",children:"\u2500".repeat(34)}):null,l(p,{justifyContent:"space-between",children:[l(p,{children:[o(u,{color:nt?"green":"gray",children:nt?"\u276F ":" "}),l(u,{color:a.entry?.status==="watching"?"green":At.color,children:[a.entry?.status==="watching"?le($t):At.dot," "]}),o(u,{color:nt?"white":a.entry?void 0:"gray",bold:nt,children:a.site.name})]}),o(u,{color:a.entry?.status==="syncing"?"green":"gray",children:De})]})]},a.site.id)}),et?o(u,{color:"gray",children:mt<H?` \u2193 ${H-mt} more`:" "}):null]})}),o(p,{marginTop:1,children:o(u,{color:"gray",children:"+ new site \xB7 n"})})]}),l(p,{flexDirection:"column",flexGrow:1,marginLeft:1,children:[o(p,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,children:k?l(Q,{children:[l(u,{children:[k.org.name||k.org.subdomain?l(u,{color:"gray",children:[k.org.name||k.org.subdomain," / "]}):null,o(u,{color:"cyan",bold:!0,children:k.site.name})]}),l(p,{marginTop:1,children:[o(u,{color:"gray",children:"status "}),b?o(u,{color:lt(b.status).color,children:`: ${lt(b.status).label}`}):o(u,{color:"gray",children:": idle \u2014 not syncing (press \u23CE to start)"})]}),l(p,{children:[o(u,{color:"gray",children:"path "}),o(u,{children:b?.viewsDir?Pt(b.viewsDir):"\u2014"})]}),l(p,{children:[o(u,{color:"gray",children:"synced "}),o(u,{children:Dt(b?.lastSyncedAt)}),o(u,{color:"gray",children:" progress "}),o(u,{color:b?.status==="syncing"?"green":"gray",children:b?.status==="syncing"?`${ut(b.progress)??0}%`:"\u2014"})]}),o(p,{marginTop:1,children:o(fr,{percent:b?b.status==="syncing"?ut(b.progress)??0:b.status==="watching"?100:0:0})}),b?l(Q,{children:[l(p,{marginTop:1,children:[o(u,{color:"gray",children:"build "}),o(u,{color:pt.color,children:`${pt.dot} ${pt.label}`}),o(u,{color:"gray",children:" last "}),o(u,{children:ht?"in progress":Dt(R?.at)})]}),l(p,{children:[o(u,{color:"gray",children:"url "}),R?.url?o(u,{color:"cyan",children:R.url}):o(u,{color:"gray",children:"\u2014"})]}),R?.status==="error"&&R.errors?.length?l(p,{children:[o(u,{color:"red",children:"\u258C "}),o(u,{color:"red",children:`\u2717 ${R.errors[0]}`})]}):null,ht?l(p,{children:[o(yr,{frame:$t}),o(u,{color:"gray",children:` ${R?.stage??"building"}`})]}):null]}):null]}):o(u,{color:"gray",children:"No site selected."})}),h==="dashboard"?l(p,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,marginTop:1,flexGrow:1,children:[l(u,{children:[o(u,{color:"cyan",bold:!0,children:"LOGS \xB7 "}),o(u,{color:"white",children:k?.site.name??"\u2014"})]}),o(p,{flexDirection:"column",marginTop:1,children:!b||b.logs.length===0?o(u,{color:"gray",children:"no activity yet"}):b.logs.slice(-Pe).map((a,g)=>l(p,{children:[l(u,{color:"gray",children:[a.time," "]}),o(u,{children:a.text})]},g))})]}):l(p,{flexDirection:"column",borderStyle:"round",borderColor:"blue",paddingX:1,marginTop:1,flexGrow:1,children:[h==="newOrg"&&l(Q,{children:[o(u,{color:"cyan",bold:!0,children:"CREATE SITE \u2014 PICK ORG"}),o(u,{color:"gray",children:"\u2191/\u2193 select \xB7 \u23CE next \xB7 esc cancel"}),o(p,{flexDirection:"column",marginTop:1,children:t.catalog.map((a,g)=>l(u,{color:g===_(y,t.catalog.length)?"blue":void 0,bold:g===_(y,t.catalog.length),children:[g===_(y,t.catalog.length)?"\u276F ":" ",a.org.name||a.org.subdomain]},a.org.id))})]}),h==="newName"&&l(Q,{children:[o(u,{color:"cyan",bold:!0,children:"CREATE SITE"}),l(p,{marginTop:1,children:[o(u,{children:"New site name: "}),o(ge,{value:w,onChange:x,onSubmit:Be})]}),o(u,{color:"gray",children:"\u23CE create \xB7 empty + \u23CE to cancel"})]})]})]})]}),t.message?o(p,{paddingX:1,children:l(u,{color:t.messageIsError?"red":"gray",children:[t.messageIsError?"\u2717 ":"",t.message]})}):null,l(p,{flexDirection:"column",marginTop:1,children:[l(p,{paddingX:1,flexWrap:"wrap",children:[o($,{k:"\u2191\u2193",label:"select"}),o($,{k:"/",label:"search"}),o($,{k:"\u23CE",label:"start/stop"}),o($,{k:"p",label:"pause"}),o($,{k:"b",label:"build"}),o($,{k:"o",label:"open url"}),o($,{k:"n",label:"new site"}),o($,{k:"q",label:"logout"}),o($,{k:"ctrl+c",label:"quit",width:14})]}),r.length>0?o(p,{paddingX:1,marginTop:1,flexWrap:"wrap",children:r.map((a,g)=>o($,{k:String(g+1),label:a.label},a.key))}):null]})]})}import{jsx as gt}from"react/jsx-runtime";function me(t){let e=t;return e?.body||e?.message||String(t)}function fe({controller:t,env:e,devSecret:r,savedToken:n,savedEmail:i}){let[s,c]=pe(n?"loading":"auth"),[d,h]=pe();vr((y,m)=>{m.ctrl&&y==="c"&&t.exit()}),wr(()=>{n&&t.init(e,n,i).then(()=>c("manage")).catch(y=>{y instanceof O&&y.status===401?h("Saved session expired \u2014 please sign in again."):h(me(y)),c("auth")})},[]);let S=async(y,m)=>{h(void 0),await Yt({env:e,userToken:y,devSecret:r,email:m}),c("loading");try{await t.init(e,y,m),c("manage")}catch(w){h(me(w)),c("auth")}};return s==="auth"?gt(ue,{env:e,initialError:d,onAuthed:S}):s==="loading"?gt(br,{children:gt(Sr,{children:"Loading your sites\u2026 "})}):gt(he,{controller:t})}import{jsx as Dr}from"react/jsx-runtime";var Cr=Tr(import.meta.url),Ot=Gt();if(Ot.version){let t=Cr("../package.json");console.log(t.version),process.exit(0)}var we=Mt.dirname(xr(import.meta.url)),Er=ye(Mt.join(we,"AGENT.md"),"utf-8"),Br=ye(Mt.join(we,"build.mjs"),"utf-8");async function Pr(){let t=new at(Er,Br),e=await Vt(),r=(Ot.env??e?.env??"production").toLowerCase(),n=Ot.dev??process.env.SLEEKCMS_DEV_SECRET??e?.devSecret;Kt(n),process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[3J\x1B[H");let i=kr(Dr(fe,{controller:t,env:r,devSecret:n,savedToken:e?.userToken,savedEmail:e?.email}),{exitOnCtrlC:!1}),s=c=>{i.unmount(),console.log(c),process.exit(0)};t.once("exit",()=>s("\u2713 Stopped. Local site files were cleaned up.")),t.once("logout",()=>s("\u{1F44B} Logged out. Run `sleekcms` to sign in again.")),process.on("SIGINT",()=>{t.exit()}),process.on("SIGTERM",()=>{t.exit()}),await i.waitUntilExit()}Pr().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.3.0",
3
+ "version": "2.4.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",
@@ -28,7 +28,7 @@
28
28
  "dist"
29
29
  ],
30
30
  "scripts": {
31
- "build": "npm run clean && node esbuild.config.mjs && cp src/AGENT.md dist/ && chmod +x dist/index.js",
31
+ "build": "npm run clean && node esbuild.config.mjs && cp src/AGENT.md dist/ && cp src/build.mjs dist/ && chmod +x dist/index.js",
32
32
  "dev": "tsx src/index.tsx",
33
33
  "typecheck": "tsc --noEmit",
34
34
  "clean": "rm -rf dist",