@sleekcms/sync 2.3.0 → 2.5.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 +18 -7
- package/dist/build.mjs +238 -0
- package/dist/index.js +4 -4
- package/package.json +2 -2
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
|
|
27
|
-
3. **
|
|
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) =
|
|
31
|
-
4.
|
|
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
|
-
|
|
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
|
|
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
|
|
2
|
+
import{readFileSync as Be}from"node:fs";import{fileURLToPath as Ar}from"node:url";import{createRequire as jr}from"node:module";import $t from"node:path";import{render as Ir}from"ink";import{execSync as Ht,spawn as B}from"child_process";import{program as Ut,Option as yt}from"commander";function Jt(){return Ut.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 yt("-v, --version","output the version number").hideHelp()).addOption(new yt("-e, --env <env>","Environment (localhost, development, production)").default("production").hideHelp()).addOption(new yt("-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),
|
|
7
|
-
`)){let i
|
|
6
|
+
`).parse(process.argv),Ut.opts()}function $(t){let e=process.platform==="win32"?`where ${t}`:`which ${t}`;try{return Ht(e,{stdio:"ignore"}),!0}catch{return!1}}function Xt(t){if(process.platform!=="darwin")return!1;try{return Ht(`osascript -e 'id of application "${t}"'`,{stdio:"ignore"}),!0}catch{return!1}}function Ge(t,e){let r={detached:!0,stdio:"ignore",windowsHide:!0};process.platform==="win32"?B("cmd.exe",["/c",t,...e],r).unref():B(t,e,r).unref()}var Wt=[{name:"VS Code",cmd:"code",args:t=>["-n",t]},{name:"Cursor",cmd:"cursor",args:t=>["-n",t]}];function qt(t,e){let n=(e?Wt.filter(i=>i.cmd===e):Wt).find(i=>$(i.cmd));return n?(Ge(n.cmd,n.args(t)),n.name):null}function Ue(t){if(process.platform==="darwin")return B("open",["-a","Terminal",t],{detached:!0,stdio:"ignore"}).unref(),!0;if(process.platform==="win32"){if($("wt"))B("wt.exe",["-d",t],{detached:!0,stdio:"ignore"}).unref();else{let e=$("powershell")?"powershell.exe":"cmd.exe";B("cmd.exe",["/c","start","",e],{cwd:t,detached:!0,stdio:"ignore"}).unref()}return!0}return!1}function We(t){return Xt("iTerm")?(B("open",["-a","iTerm",t],{detached:!0,stdio:"ignore"}).unref(),!0):!1}function qe(t){return`'${t.replace(/'/g,"'\\''")}'`}var He=[{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 Kt(t,e){if(!$(e))return!1;if(process.platform==="darwin"){let n=`tell application "Terminal" to do script ("cd " & quoted form of "${t}" & " && ${e}")`;return B("osascript",["-e",n,"-e",'tell application "Terminal" to activate'],{detached:!0,stdio:"ignore"}).unref(),!0}if(process.platform==="win32")return $("wt")?B("wt.exe",["-d",t,"cmd","/k",e],{detached:!0,stdio:"ignore"}).unref():B("cmd.exe",["/c","start","","cmd","/k",e],{cwd:t,detached:!0,stdio:"ignore"}).unref(),!0;let r=`cd ${qe(t)} && ${e}; exec bash`;for(let n of He)if($(n.cmd))try{return B(n.cmd,n.args(r),{cwd:t,detached:!0,stdio:"ignore"}).unref(),!0}catch{}return!1}function Je(t){return Kt(t,"claude")}function Xe(t){return Kt(t,"pi")}function zt(t){if(!t)return!1;try{return process.platform==="darwin"?B("open",[t],{detached:!0,stdio:"ignore"}).unref():process.platform==="win32"?B("cmd.exe",["/c","start","",t],{detached:!0,stdio:"ignore",windowsHide:!0}).unref():B("xdg-open",[t],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}function Yt(){let t=process.platform==="darwin",e=process.platform==="win32",r=[];return(t||e)&&r.push({key:"terminal",label:"terminal",run:Ue}),Xt("iTerm")&&r.push({key:"iterm",label:"iterm",run:We}),$("code")&&r.push({key:"code",label:"vscode",run:n=>qt(n,"code")!==null}),$("cursor")&&r.push({key:"cursor",label:"cursor",run:n=>qt(n,"cursor")!==null}),$("claude")&&r.push({key:"claude",label:"claude",run:Je}),$("pi")&&r.push({key:"pi",label:"pi",run:Xe}),r}import{EventEmitter as yr}from"node:events";import{spawn as wr}from"node:child_process";import Y from"fs-extra";import Pt from"path";import br from"chokidar";import f from"fs-extra";import ze from"os";import S from"path";var Vt={localhost:"http://app.sleekcms.test/api/mcp",development:"https://app.sleekcms.dev/api/mcp",production:"https://app.sleekcms.com/api/mcp"},Ye=["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"],wt=class extends Error{status;body;constructor(e,r,n){super(e),this.status=r,this.body=n}};async function xt(t,e,r,n,i){let o=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(!o.ok){let c=await o.text();throw new wt(`${r} ${n} \u2192 ${o.status}: ${c}`,o.status,c)}return o.json()}async function Ve(t,e){e&&(await f.outputFile(S.join(t,"AGENT.md"),e),await f.outputFile(S.join(t,"CLAUDE.md"),e),await f.outputFile(S.join(t,".vscode","copilot-instructions.md"),e)),await f.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 Qe(t,e){if(!e)return;let r=S.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 Ze(t,e,r){let n=S.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 tr(t,e){let r=`${e.name.substr(0,20)} ${e.id}`.replace(/[\s_]+/g,"-").toLowerCase(),n=t||S.join(ze.homedir(),"SleekCMS");return S.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??(v=>console.log(v)),n=t.onProgress,i=(t.env||e.split("-")[2]||"production").toLowerCase(),o=Vt[i]||Vt.production,c=await xt(o,e,"GET","/get_site"),h=t.viewsDir?S.resolve(t.viewsDir):tr(t.path,c);await f.ensureDir(h),await Ze(h,e,r),await Promise.all(Ye.map(v=>f.ensureDir(S.join(h,v)))),await Qe(h,t.buildScript);let p=S.join(h,".cache","state.json");t.flush&&await f.remove(p);let x=!await f.pathExists(p),y=x?{}:(await f.readJson(p)).fileMap||{},m=0,w=0;return x?({fileMap:y,pulled:w}=await lr(h,o,e,r,n),await Ve(h,t.agentMd)):m=await nr(h,y,o,e,r,n),await f.outputJson(p,{fileMap:y},{spaces:2}),{viewsDir:h,site:c,isFirstRun:x,pushed:m,pulled:w}}var er=new Set([".DS_Store","Thumbs.db","desktop.ini"]);async function rr(t){let e=S.join(t,"src");if(!await f.pathExists(e))return[];let r=[];async function n(i){let o=await f.readdir(i,{withFileTypes:!0});for(let c of o){if(er.has(c.name))continue;let h=S.join(i,c.name);c.isDirectory()?await n(h):c.isFile()&&r.push(S.relative(t,h).replace(/\\/g,"/"))}}return await n(e),r}async function nr(t,e,r,n,i,o){let c=[];for(let m of await rr(t)){let w=S.join(t,m),v=e[m],M=await f.stat(w);if(v&&v.mtimeMs===M.mtimeMs)continue;let R=await f.readFile(w,"utf-8");R.trim()&&c.push({rel:m,full:w,stat:M,content:R,prior:v})}if(c.length===0)return 0;let h;try{h=await xt(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 p=await or(t),x=0,y=ir(h);for(let m=0;m<c.length;m++){let w=c[m],v=sr(w,m,h,y);if(o?.(m+1,c.length),!v){let M="No save response returned";p[w.rel]=M,i(`\u274C Error saving ${w.rel}: ${M}`);continue}if(v.error){p[w.rel]=v.error,i(`\u274C Error saving ${w.rel}: ${v.error}`);continue}delete p[w.rel],e[w.rel]={mtimeMs:w.stat.mtimeMs},i(`\u2705 ${w.prior?"Updated":"Created"} ${w.rel}`),x++}return await cr(t,p),x}function ir(t){let e=new Map;for(let r of t)r.path&&e.set(r.path,r);return e}function sr(t,e,r,n){let i=n.get(t.rel);if(i)return i;let o=r[e];if(!o?.path||o.path===t.rel)return o}var vt="sync-errors.log",bt=new Map;async function or(t){let e=S.resolve(t),r=bt.get(e);if(r)return r;let n=await ar(e);return bt.set(e,n),n}async function ar(t){let e=S.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 o=i.indexOf(": ");o>0&&(n[i.slice(0,o)]=i.slice(o+2))}return n}async function cr(t,e){let r=S.join(t,vt),n=Object.entries(e);if(n.length===0){await f.remove(r);return}await f.outputFile(r,n.map(([i,o])=>`${i}: ${o}`).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 lr(t,e,r,n,i){bt.delete(S.resolve(t)),await f.remove(S.join(t,vt)),n("\u{1F4E5} Fetching files...");let o=await xt(e,r,"GET","/get_files"),c={},h=0;for(let p of o){let x=S.join(t,p.path);await f.outputFile(x,p.content);let y=(await f.stat(x)).mtimeMs;h++,i?.(h,o.length),c[p.path]={mtimeMs:y}}return n(`\u2714\uFE0F Synced ${o.length} file(s).`),{fileMap:c,pulled:h}}import ur from"path";import dr from"chokidar";var gr=5e3,hr=/(^|[/\\])(\.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??gr}start(){if(this.watcher)return;let e=ur.join(this.viewsDir,"src");this.watcher=dr.watch([e],{persistent:!0,ignoreInitial:!0,ignored:hr}).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 st from"fs-extra";import pr from"os";import Zt from"path";var Qt={localhost:"http://app.sleekcms.test/api",development:"https://app.sleekcms.dev/api",production:"https://app.sleekcms.com/api"};function kt(t){return Qt[t]||Qt.production}var mr=Zt.join(pr.homedir(),"SleekCMS"),ot=Zt.join(mr,"auth.json"),Tt;function te(t){Tt=t||void 0}function Bt(t){let e=[];return t&&e.push(`_auth=${t}`),Tt&&e.push(`_dev=${Tt}`),e.length?e.join("; "):void 0}var L=class extends Error{status;body;constructor(e,r,n=""){super(e),this.name="UserAuthError",this.status=r,this.body=n}};async function ee(){try{let t=await st.readJson(ot);return t&&t.userToken?t:null}catch{return null}}async function re(t){await st.outputJson(ot,t,{spaces:2});try{await st.chmod(ot,384)}catch{}}async function ne(){await st.remove(ot)}async function W(t){let e={},r=Bt(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(kt(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 L(`${t.method} ${t.path} \u2192 ${n.status}: ${i}`,n.status,i)}return n.json()}async function ie(t,e){let r={"Content-Type":"application/json"},n=Bt();n&&(r.Cookie=n);let i=await fetch(kt(t)+"/auth/otp",{method:"PUT",headers:r,body:JSON.stringify({email:e})});if(!i.ok){let c=await i.text().catch(()=>"");throw new L(`Failed to send OTP \u2192 ${i.status}: ${c}`,i.status,c)}return{otp:(await i.json().catch(()=>({}))).do_not_use_in_code}}async function Et(t,e,r){let n={"Content-Type":"application/json"},i=Bt();i&&(n.Cookie=i);let o=await fetch(kt(t)+"/auth/login",{method:"POST",headers:n,body:JSON.stringify({email:e,otp:r})});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=fr(c);if(!h)throw new L("Login succeeded but no _auth cookie was returned.",500);return h}function fr(t){for(let e of t){let r=e.match(/(?:^|;\s*)_auth=([^;]+)/);if(r&&r[1])return decodeURIComponent(r[1])}return null}async function Ct(t,e){return W({env:t,userToken:e,method:"GET",path:"/org"})}async function se(t,e,r){return W({env:t,userToken:e,method:"GET",path:"/site",subdomain:r})}async function oe(t,e,r,n){let i;try{i=await W({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 o=i[0],c=i.find(p=>typeof p.url=="string"&&p.url.length>0),h=Array.isArray(o.errors)?o.errors.map(p=>String(p)):[];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 ae(t,e,r,n){let{token:i}=await W({env:t,userToken:e,method:"POST",path:"/site_user/api_token",subdomain:r,slug:n,body:{refresh:!1}});return i}async function ce(t,e,r){let n=await W({env:t,userToken:e,method:"POST",path:"/org",body:r?{name:r}:{}});return{id:n.id,name:n.name,subdomain:n.subdomain}}async function le(t,e,r,n){let i=await W({env:t,userToken:e,method:"POST",path:"/site",subdomain:r,body:{name:n}});return{id:i.id,name:i.name,slug:i.slug}}var ue=500;function xr(){return new Date().toTimeString().slice(0,8)}function at(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 de(t){t&&(await Y.remove(Pt.join(t,"src")),await Y.remove(Pt.join(t,".cache","state.json")))}var ct=class extends yr{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:xr(),text:r}),n.logs.length>ue&&n.logs.splice(0,n.logs.length-ue),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 Ct(e,r);await this.buildCatalog(i)}async buildCatalog(e){let r=[];for(let n of e)try{r.push({org:n,sites:await se(this.env,this.userToken,n.subdomain)})}catch(i){if(i instanceof L&&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 ae(this.env,this.userToken,e.subdomain,r.slug)}catch(o){this.flash(`Failed to authorize "${r.name}": ${at(o)}`,!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 o=await St({token:n,env:this.env,agentMd:this.agentMd,buildScript:this.buildScript,log:c=>this.appendLog(r.id,c),onProgress:(c,h)=>{i.progress={done:c,total:h},this.changed()}});i.viewsDir=o.viewsDir,i.progress=void 0,i.status="watching",i.lastSyncedAt=Date.now(),i.lastMsg=`pulled ${o.pulled} file(s)`,i.watcher=new z({viewsDir:o.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(o){let c=o;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 de(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()}),wr("node",r?["build.mjs","--deploy"]:["build.mjs"],{cwd:n.viewsDir,stdio:"ignore"}).on("error",h=>{this.writeBuildFile(n.viewsDir,{status:"error",mode:i,errors:[h.message],url:n.lastBuild?.url,at:Date.now()}),this.appendLog(e,`\u274C build: ${h.message}`)}),this.appendLog(e,r?"\u{1F680} deploying\u2026":"\u{1F528} building\u2026")}buildFile(e){return Pt.join(e,".cache","build.json")}async readBuildFile(e){try{let r=await Y.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 Y.outputJson(i,r),await Y.rename(i,n)}catch{}}watchBuildStatus(e){let r=this.synced.get(e);!r||!r.viewsDir||(r.buildWatcher=br.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 oe(this.env,this.userToken,r.org.subdomain,r.site.slug);if(!n)return;let i=this.synced.get(e);if(!i)return;let o=await this.readBuildFile(i.viewsDir);o?.at!=null&&(n.at==null||o.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 Ct(this.env,this.userToken)}catch(n){return this.flash(`Couldn't check your organizations: ${at(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 ce(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: ${at(n)}`,!0),null}}async createNewSite(e,r){let n=await this.ensureOrg(r);if(!n)return;let i;try{i=await le(this.env,this.userToken,n.subdomain,e)}catch(c){this.flash(`Could not create "${e}": ${at(c)}`,!0);return}let o=this.catalog.find(c=>c.org.id===n.id);o&&!o.sites.some(c=>c.id===i.id)&&(o.sites=[...o.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:o=>this.appendLog(e,o),onProgress:(o,c)=>{n.progress={done:o,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 o=i;n.status="error",n.progress=void 0,n.lastMsg=o.body||o.message,this.appendLog(e,`\u274C ${o.body||o.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 de(e.viewsDir)}async exit(){this.isShuttingDown||(this.isShuttingDown=!0,await this.cleanupAll(),this.emit("exit"))}async logout(){this.isShuttingDown||(this.isShuttingDown=!0,await ne(),await this.cleanupAll(),this.emit("logout"))}};import{useEffect as $r,useState as ve}from"react";import{Box as Lr,Text as Rr,useInput as Fr}from"ink";import{useState as V}from"react";import{Box as Q,Text as q}from"ink";import ge from"ink-text-input";import{jsx as _,jsxs as H}from"react/jsx-runtime";function he(t){let e=t;return e?.body||e?.message||String(t)}function pe({env:t,initialError:e,onAuthed:r}){let[n,i]=V("email"),[o,c]=V(""),[h,p]=V(""),[x,y]=V(e),[m,w]=V(),v=async R=>{let T=R.trim();if(T){y(void 0),i("busy");try{let{otp:C}=await ie(t,T);if(c(T),C){let J=await Et(t,T,C);await r(J,T);return}w(`OTP sent to ${T}`),i("otp")}catch(C){y(he(C)),i("email")}}},M=async R=>{let T=R.trim();if(T){y(void 0),i("busy");try{let C=await Et(t,o,T);await r(C,o)}catch(C){y(he(C)),p(""),i("otp")}}};return H(Q,{flexDirection:"column",children:[H(q,{bold:!0,children:["\u25C6 SleekCMS \u2014 sign in",t!=="production"?` (${t})`:""]}),x?H(q,{color:"red",children:["\u2717 ",x]}):null,n==="email"&&H(Q,{marginTop:1,children:[_(q,{children:"Email: "}),_(ge,{value:o,onChange:c,onSubmit:v,placeholder:"you@example.com"})]}),n==="otp"&&H(Q,{flexDirection:"column",marginTop:1,children:[m?_(q,{color:"green",children:m}):null,H(Q,{children:[_(q,{children:"Enter OTP: "}),_(ge,{value:h,onChange:p,onSubmit:M})]})]}),n==="busy"&&_(Q,{marginTop:1,children:_(q,{children:"Working\u2026"})})]})}import{Fragment as vr,useEffect as gt,useMemo as Tr,useState as O}from"react";import{Box as d,Text as l,useInput as kr,useStdout as Br}from"ink";import ye from"ink-text-input";import Sr from"node:os";function Ot(t){if(!t)return"\u2014";let e=Sr.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 lt=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function me(t){return lt[(t%lt.length+lt.length)%lt.length]}function dt(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 fe(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 Z,jsx as s,jsxs as u}from"react/jsx-runtime";var N=(t,e)=>e<=0?0:Math.max(0,Math.min(t,e-1)),Er=16,Cr=5,Pr="greenBright",we="blueBright",be="magentaBright";function xe({k:t,bg:e=Pr}){return s(l,{color:"black",backgroundColor:e,children:` ${t} `})}function j({k:t,label:e,width:r=16,bg:n}){return u(d,{width:r,children:[s(xe,{k:t,bg:n}),s(l,{color:"gray",children:` ${e}`})]})}function E({k:t,label:e,bg:r}){return u(d,{marginBottom:1,children:[s(d,{width:9,children:s(xe,{k:t,bg:r})}),s(l,{color:"gray",children:e})]})}function Or({launchers:t}){return u(d,{flexDirection:"column",borderStyle:"round",borderColor:"green",paddingX:2,paddingY:1,width:66,children:[u(d,{justifyContent:"space-between",children:[s(l,{color:"green",bold:!0,children:"KEYBOARD SHORTCUTS"}),s(l,{color:"gray",children:"? or esc to close"})]}),u(d,{marginTop:1,children:[u(d,{flexDirection:"column",width:34,children:[s(l,{color:"cyan",bold:!0,children:"Navigate"}),s(E,{k:"\u2191\u2193",label:"move selection"}),s(E,{k:"\u23CE",label:"start / stop sync"}),s(E,{k:"esc",label:"clear filter / back"}),s(d,{marginTop:1,children:s(l,{color:"cyan",bold:!0,children:"Global"})}),s(E,{k:"/",label:"search sites"}),s(E,{k:"n",label:"new site"}),s(E,{k:"?",label:"toggle this help",bg:we}),s(E,{k:"q",label:"logout"}),s(E,{k:"ctrl+c",label:"quit"})]}),u(d,{flexDirection:"column",flexGrow:1,children:[s(l,{color:"cyan",bold:!0,children:"Site actions"}),s(E,{k:"p",label:"pause / resume sync"}),s(E,{k:"b",label:"build preview"}),s(E,{k:"B",label:"deploy live"}),s(E,{k:"o",label:"open preview url"}),t.map((e,r)=>s(E,{k:String(r+1),label:`open in ${e.label}`,bg:be},e.key))]})]})]})}function Dr({percent:t,width:e=28}){let r=Math.round(Math.max(0,Math.min(100,t))/100*e);return u(l,{children:[s(l,{color:"green",children:"\u2588".repeat(r)}),s(l,{color:"gray",children:"\u2591".repeat(Math.max(0,e-r))})]})}function Mr({frame:t,width:e=24,seg:r=6}){let n=e+r,i=t%n-r,o="";for(let c=0;c<e;c++)o+=c>=i&&c<i+r?"\u25AE":"\u25AF";return s(l,{color:"yellow",children:o})}function Se({controller:t}){let{stdout:e}=Br(),r=Tr(()=>Yt(),[]),[,n]=O(0),[i,o]=O(()=>new Date),[c,h]=O(null),[p,x]=O(()=>t.synced.size>0||t.catalog.some(g=>g.sites.length>0)?"dashboard":t.catalog.length<=1?"newName":"newOrg"),[y,m]=O(0),[w,v]=O(""),[M,R]=O(!1),[T,C]=O(""),[J,tt]=O(!1),[Lt,Ce]=O(0),[Rt,Ft]=O(!1),[Pe,Oe]=O(!1),At=()=>n(a=>a+1);gt(()=>(t.on("change",At),()=>{t.off("change",At)}),[t]),gt(()=>{let a=setInterval(()=>o(new Date),1e3);return()=>clearInterval(a)},[]);let A=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)),I=T.trim().toLowerCase(),P=I?A.filter(a=>a.site.name.toLowerCase().includes(I)||(a.org.name||a.org.subdomain||"").toLowerCase().includes(I)):A,G=P.findIndex(a=>a.site.id===c);G<0&&(G=0);let k=P[G],b=k?.entry,F=b?.lastBuild,pt=F?.status==="building",mt=fe(F),et=pt;if(!et){for(let a of t.synced.values())if(a.status==="watching"||a.status==="syncing"){et=!0;break}}gt(()=>{if(!et)return;let a=setInterval(()=>Ce(g=>g+1),120);return()=>clearInterval(a)},[et]);let jt=P.findIndex(a=>!a.entry),U=Math.max(Cr,(e?.rows??30)-Er),X=P.length,rt=X>U,De=Math.floor(U/2),K=rt?Math.max(0,Math.min(G-De,X-U)):0,ft=Math.min(X,K+U),Me=P.slice(K,ft);gt(()=>{p!=="dashboard"||M||A.length>0||(v(""),t.catalog.length<=1?x("newName"):(m(0),x("newOrg")))},[A.length,p,M,t.catalog.length]);let nt=a=>{if(P.length===0)return;let g=N(G+a,P.length);h(P[g].site.id)},$e=()=>{k&&(k.entry?t.removeSite(k.site.id):t.addSite(k.org,k.site))},Le=()=>{v(""),t.catalog.length<=1?x("newName"):(m(0),x("newOrg"))},Re=a=>{if(!b){t.flash("Press \u23CE to start syncing this site first.",!0);return}Oe(!0);let g=a.run(b.viewsDir);t.flash(g?`Opened ${a.label} at ${Ot(b.viewsDir)}`:`Couldn't open ${a.label}.`,!g)},Fe=()=>{if(!b){t.flash("Press \u23CE to start syncing this site first.",!0);return}t.togglePause(b.site.id)},It=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")},Ae=()=>{let a=b?.lastBuild?.url;if(!a){t.flash("No build URL yet \u2014 press b to build.",!0);return}t.flash(zt(a)?`Opening ${a}`:"Couldn't open the browser.",!1)};kr((a,g)=>{if(Rt){(a==="?"||g.escape)&&Ft(!1);return}if(p==="newOrg"){g.escape?x("dashboard"):g.upArrow?m(D=>N(D-1,t.catalog.length)):g.downArrow?m(D=>N(D+1,t.catalog.length)):g.return&&x("newName");return}if(J){g.escape?(tt(!1),C(""),h(null)):g.return?tt(!1):g.upArrow?nt(-1):g.downArrow&&nt(1);return}if(g.escape){C(""),h(null);return}if(a==="?")return void Ft(!0);if(a==="/")return void tt(!0);if(a==="n")return void Le();if(a==="q")return void t.logout();if(g.upArrow)return nt(-1);if(g.downArrow)return nt(1);if(g.return||a===" ")return $e();if(a==="p")return Fe();if(a==="b")return It(!1);if(a==="B")return It(!0);if(a==="o")return Ae();if(a>="1"&&a<="9"){let D=r[parseInt(a,10)-1];if(D)return Re(D)}},{isActive:p!=="newName"});let je=async a=>{let g=a.trim();if(!g){x("dashboard");return}let D=t.catalog[N(y,t.catalog.length)]?.org??t.catalog[0]?.org;R(!0),x("dashboard"),await t.createNewSite(g,D),R(!1)},Ie=Math.max(5,(e?.rows??30)-18),_t=u(d,{justifyContent:"space-between",paddingX:1,children:[u(d,{children:[s(l,{color:"green",bold:!0,children:"\u25C6 SleekCMS"}),s(l,{color:"gray",children:" \u2502 "}),s(l,{children:t.email??"signed in"})]}),u(d,{children:[u(l,{color:"gray",children:[I?`${P.length}/${A.length}`:A.length," sites \xB7 "]}),u(l,{color:"green",children:[t.watchingCount()," watching"]}),s(l,{color:"gray",children:` \xB7 ${i.toTimeString().slice(0,8)}`})]})]});return Rt?u(d,{flexDirection:"column",children:[_t,s(d,{marginTop:1,justifyContent:"center",children:s(Or,{launchers:r})})]}):u(d,{flexDirection:"column",children:[_t,u(d,{children:[u(d,{flexDirection:"column",borderStyle:"round",borderColor:"green",width:38,paddingX:1,children:[u(d,{justifyContent:"space-between",children:[s(l,{color:"green",bold:!0,children:"SITE WORKSPACES"}),A.length>U&&!J&&!I?s(l,{color:"gray",children:"/ filter"}):null]}),J?u(d,{children:[s(l,{color:"cyan",children:"/ "}),s(ye,{value:T,onChange:C,onSubmit:()=>tt(!1),placeholder:"type to filter\u2026"})]}):I?s(l,{color:"gray",children:`/ ${T} \xB7 ${P.length} match${P.length===1?"":"es"}`}):null,s(d,{flexDirection:"column",marginTop:1,minHeight:rt?U+3:void 0,children:P.length===0?s(l,{color:"gray",children:I?`no matches for \u201C${T}\u201D`:"no sites \u2014 press n to create one"}):u(Z,{children:[rt?s(l,{color:"gray",children:K>0?` \u2191 ${K} more`:" "}):null,Me.map((a,g)=>{let D=K+g,Nt=a.entry?dt(a.entry.status):{dot:"\xB7",color:"gray",label:"idle"},Gt=ut(a.entry?.progress),_e=a.entry?a.entry.status==="syncing"&&Gt!=null?`${Gt}%`:a.entry.status==="paused"?"paused":"--":"",it=D===G,Ne=D===jt&&jt>0;return u(vr,{children:[Ne?s(l,{color:"gray",children:"\u2500".repeat(34)}):null,u(d,{justifyContent:"space-between",children:[u(d,{children:[s(l,{color:it?"green":"gray",children:it?"\u276F ":" "}),u(l,{color:a.entry?.status==="watching"?"green":Nt.color,children:[a.entry?.status==="watching"?me(Lt):Nt.dot," "]}),s(l,{color:it?"white":a.entry?void 0:"gray",bold:it,children:a.site.name})]}),s(l,{color:a.entry?.status==="syncing"?"green":"gray",children:_e})]})]},a.site.id)}),rt?s(l,{color:"gray",children:ft<X?` \u2193 ${X-ft} more`:" "}):null]})}),s(d,{marginTop:1,children:s(l,{color:"gray",children:"+ new site \xB7 n"})})]}),u(d,{flexDirection:"column",flexGrow:1,marginLeft:1,children:[s(d,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,children:k?u(Z,{children:[u(l,{children:[k.org.name||k.org.subdomain?u(l,{color:"gray",children:[k.org.name||k.org.subdomain," / "]}):null,s(l,{color:"cyan",bold:!0,children:k.site.name})]}),u(d,{marginTop:1,children:[s(l,{color:"gray",children:"status "}),b?s(l,{color:dt(b.status).color,children:`: ${dt(b.status).label}`}):s(l,{color:"gray",children:": idle \u2014 not syncing (press \u23CE to start)"})]}),u(d,{children:[s(l,{color:"gray",children:"path "}),s(l,{children:b?.viewsDir?Ot(b.viewsDir):"\u2014"})]}),u(d,{children:[s(l,{color:"gray",children:"synced "}),s(l,{children:Dt(b?.lastSyncedAt)}),s(l,{color:"gray",children:" progress "}),s(l,{color:b?.status==="syncing"?"green":"gray",children:b?.status==="syncing"?`${ut(b.progress)??0}%`:"\u2014"})]}),s(d,{marginTop:1,children:s(Dr,{percent:b?b.status==="syncing"?ut(b.progress)??0:b.status==="watching"?100:0:0})}),b?u(Z,{children:[u(d,{marginTop:1,children:[s(l,{color:"gray",children:"build "}),s(l,{color:mt.color,children:`${mt.dot} ${mt.label}`}),s(l,{color:"gray",children:" last "}),s(l,{children:pt?"in progress":Dt(F?.at)})]}),u(d,{children:[s(l,{color:"gray",children:"url "}),F?.url?s(l,{color:"cyan",children:F.url}):s(l,{color:"gray",children:"\u2014"})]}),F?.status==="error"&&F.errors?.length?u(d,{children:[s(l,{color:"red",children:"\u258C "}),s(l,{color:"red",children:`\u2717 ${F.errors[0]}`})]}):null,pt?u(d,{children:[s(Mr,{frame:Lt}),s(l,{color:"gray",children:` ${F?.stage??"building"}`})]}):null]}):null]}):s(l,{color:"gray",children:"No site selected."})}),p==="dashboard"?u(d,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,marginTop:1,flexGrow:1,children:[u(l,{children:[s(l,{color:"cyan",bold:!0,children:"LOGS \xB7 "}),s(l,{color:"white",children:k?.site.name??"\u2014"})]}),s(d,{flexDirection:"column",marginTop:1,children:!b||b.logs.length===0?s(l,{color:"gray",children:"no activity yet"}):b.logs.slice(-Ie).map((a,g)=>u(d,{children:[u(l,{color:"gray",children:[a.time," "]}),s(l,{children:a.text})]},g))})]}):u(d,{flexDirection:"column",borderStyle:"round",borderColor:"blue",paddingX:1,marginTop:1,flexGrow:1,children:[p==="newOrg"&&u(Z,{children:[s(l,{color:"cyan",bold:!0,children:"CREATE SITE \u2014 PICK ORG"}),s(l,{color:"gray",children:"\u2191/\u2193 select \xB7 \u23CE next \xB7 esc cancel"}),s(d,{flexDirection:"column",marginTop:1,children:t.catalog.map((a,g)=>u(l,{color:g===N(y,t.catalog.length)?"blue":void 0,bold:g===N(y,t.catalog.length),children:[g===N(y,t.catalog.length)?"\u276F ":" ",a.org.name||a.org.subdomain]},a.org.id))})]}),p==="newName"&&u(Z,{children:[s(l,{color:"cyan",bold:!0,children:"CREATE SITE"}),u(d,{marginTop:1,children:[s(l,{children:"New site name: "}),s(ye,{value:w,onChange:v,onSubmit:je})]}),s(l,{color:"gray",children:"\u23CE create \xB7 empty + \u23CE to cancel"})]})]})]})]}),t.message?s(d,{paddingX:1,children:u(l,{color:t.messageIsError?"red":"gray",children:[t.messageIsError?"\u2717 ":"",t.message]})}):null,u(d,{flexDirection:"column",marginTop:1,children:[u(d,{paddingX:1,justifyContent:"space-between",children:[u(d,{flexWrap:"wrap",children:[s(j,{k:"\u2191\u2193",label:"select"}),s(j,{k:"/",label:"search"}),s(j,{k:"\u23CE",label:"start/stop"}),s(j,{k:"p",label:"pause"}),A.length===0?s(j,{k:"n",label:"new site"}):null,s(j,{k:"ctrl+c",label:"quit",width:14})]}),s(j,{k:"?",label:"all keys",width:12,bg:we})]}),!Pe&&r.length>0?s(d,{paddingX:1,marginTop:1,flexWrap:"wrap",children:r.map((a,g)=>s(j,{k:String(g+1),label:a.label,bg:be},a.key))}):null]})]})}import{jsx as ht}from"react/jsx-runtime";function Te(t){let e=t;return e?.body||e?.message||String(t)}function ke({controller:t,env:e,devSecret:r,savedToken:n,savedEmail:i}){let[o,c]=ve(n?"loading":"auth"),[h,p]=ve();Fr((y,m)=>{m.ctrl&&y==="c"&&t.exit()}),$r(()=>{n&&t.init(e,n,i).then(()=>c("manage")).catch(y=>{y instanceof L&&y.status===401?p("Saved session expired \u2014 please sign in again."):p(Te(y)),c("auth")})},[]);let x=async(y,m)=>{p(void 0),await re({env:e,userToken:y,devSecret:r,email:m}),c("loading");try{await t.init(e,y,m),c("manage")}catch(w){p(Te(w)),c("auth")}};return o==="auth"?ht(pe,{env:e,initialError:h,onAuthed:x}):o==="loading"?ht(Lr,{children:ht(Rr,{children:"Loading your sites\u2026 "})}):ht(Se,{controller:t})}import{jsx as Wr}from"react/jsx-runtime";var _r=jr(import.meta.url),Mt=Jt();if(Mt.version){let t=_r("../package.json");console.log(t.version),process.exit(0)}var Ee=$t.dirname(Ar(import.meta.url)),Nr=Be($t.join(Ee,"AGENT.md"),"utf-8"),Gr=Be($t.join(Ee,"build.mjs"),"utf-8");async function Ur(){let t=new ct(Nr,Gr),e=await ee(),r=(Mt.env??e?.env??"production").toLowerCase(),n=Mt.dev??process.env.SLEEKCMS_DEV_SECRET??e?.devSecret;te(n),process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[3J\x1B[H");let i=Ir(Wr(ke,{controller:t,env:r,devSecret:n,savedToken:e?.userToken,savedEmail:e?.email}),{exitOnCtrlC:!1}),o=c=>{i.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 i.waitUntilExit()}Ur().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
|
+
"version": "2.5.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",
|