@sleekcms/sync 1.4.3 → 1.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/README.md CHANGED
@@ -48,12 +48,15 @@ sleekcms --token <YOUR_AUTH_TOKEN>
48
48
  ## Usage
49
49
 
50
50
  ```bash
51
- # Basic
51
+ # Basic — auto-sync on every save
52
52
  npx @sleekcms/sync --token abc123-xxxx
53
53
 
54
+ # Manual sync — push changes only when you press [s]
55
+ npx @sleekcms/sync --token abc123-xxxx --manual
56
+
54
57
  ```
55
58
 
56
- Once running, press `r` to re-fetch all files or `x` / `Ctrl+C` to exit.
59
+ Once running, press `r` to re-fetch all files or `x` / `Ctrl+C` to exit. In `--manual` mode, press `s` to push your changes on demand.
57
60
 
58
61
  ---
59
62
 
@@ -136,12 +139,24 @@ On first run, the CLI pulls the full site state from the server. After that, a l
136
139
  Your editor → file save → watcher → SleekCMS API → rebuild → live site
137
140
  ```
138
141
 
139
- ### Watch mode commands
142
+ ### Manual sync mode (`--manual`)
143
+
144
+ Pass `--manual` (`-m`) to skip the live watcher. The CLI still does the initial fetch, but after that nothing is pushed until you press `s`. On each `s`, it scans the workspace, diffs against the cached state in `.cache/`, and pushes only the files that changed since your last fetch or sync — the same diff logic, run on demand instead of on every save.
145
+
146
+ This is useful when you want to batch a set of edits (or let an AI agent finish writing several files) and push them as one deliberate action.
147
+
148
+ ```
149
+ Your editor → edit freely → press [s] → diff vs last fetch → SleekCMS API → live site
150
+ ```
151
+
152
+ ### Session commands
140
153
 
141
154
  | Key | Action |
142
155
  |---|---|
156
+ | `s` | Push changed files now (`--manual` mode only) |
143
157
  | `r` | Re-fetch all files from server |
144
158
  | `x` or `Ctrl+C` | Exit and clean up local workspace |
159
+ | `q` | Quit, keeping local files |
145
160
 
146
161
  ---
147
162
 
package/dist/AGENT.md CHANGED
@@ -732,4 +732,4 @@ Template:
732
732
  17. Always create RSS feed for blogs and link them in meta so it is discoverable. Use "rss.xml" as the key.
733
733
  18. Make the sites extremely SEO friendly and sharing friendly.
734
734
  19. When naming files for models, use - (dash) as word separator. Don't use _ (underscore) as it is mapped to / (slash) in path
735
-
735
+ 20. Not all content details need to be modeled. If there is content which is not meant to be updated, such as button labels, links etc. you can inline that in the EJS view instead of adding fields to model and using from there. Decide what is relevant.
package/dist/cli.d.ts CHANGED
@@ -6,13 +6,16 @@ export interface CliOptions {
6
6
  token?: string;
7
7
  env?: string;
8
8
  path?: string;
9
+ manual?: boolean;
9
10
  }
10
11
  export interface KeyboardHandlers {
11
12
  onExit?: () => void | Promise<unknown>;
13
+ onQuit?: () => void | Promise<unknown>;
12
14
  onRefetch?: () => void | Promise<unknown>;
15
+ onSync?: () => void | Promise<unknown>;
13
16
  }
14
17
  export declare function parseArgs(): CliOptions;
15
18
  export declare function prompt(question: string): Promise<string>;
16
- export declare function showWatchHelp(): void;
17
- export declare function setupKeyboardInput(handlers: KeyboardHandlers): void;
18
- export declare function showEditorMenu(viewsDir: string, handlers: KeyboardHandlers): void;
19
+ export declare function showWatchHelp(manual?: boolean): void;
20
+ export declare function setupKeyboardInput(handlers: KeyboardHandlers, manual?: boolean): void;
21
+ export declare function showEditorMenu(viewsDir: string, handlers: KeyboardHandlers, manual?: boolean): void;
package/dist/cli.js CHANGED
@@ -23,11 +23,13 @@ function parseArgs() {
23
23
  .option("-t, --token <token>", "API authentication token (required)")
24
24
  .addOption(new commander_1.Option("-e, --env <env>", "Environment (localhost, development, production)").default("production").hideHelp())
25
25
  .option("-p, --path <path>", "Directory path for files (default: <token-prefix>-views)")
26
+ .option("-m, --manual", "Manual sync mode: don't auto-sync on file changes; press [s] to push diffs on demand")
26
27
  .addHelpText("after", `
27
28
  Examples:
28
29
  $ cms-sync --token abc123-xxxx
29
30
  $ cms-sync -t abc123-xxxx -e development
30
31
  $ cms-sync -t abc123-xxxx -p ./my-templates
32
+ $ cms-sync -t abc123-xxxx -m # manual sync — push only when you press [s]
31
33
  `)
32
34
  .parse(process.argv);
33
35
  return commander_1.program.opts();
@@ -66,10 +68,11 @@ function commandExists(cmd) {
66
68
  return false;
67
69
  }
68
70
  }
69
- function showWatchHelp() {
70
- console.log("📋 Commands: [r] Re-fetch all files [x] Exit\n");
71
+ function showWatchHelp(manual = false) {
72
+ const sync = manual ? "[s] Sync changes " : "";
73
+ console.log(`📋 Commands: ${sync}[r] Re-fetch all files [x] Exit (cleanup) [q] Quit (keep files)\n`);
71
74
  }
72
- function setupKeyboardInput(handlers) {
75
+ function setupKeyboardInput(handlers, manual = false) {
73
76
  if (process.stdin.isTTY) {
74
77
  process.stdin.setRawMode(true);
75
78
  rawModeEnabled = true;
@@ -84,41 +87,55 @@ function setupKeyboardInput(handlers) {
84
87
  return;
85
88
  }
86
89
  const cmd = key.toLowerCase();
87
- if (cmd === "r" && handlers.onRefetch) {
90
+ if (cmd === "s" && handlers.onSync) {
91
+ await handlers.onSync();
92
+ showWatchHelp(manual);
93
+ }
94
+ else if (cmd === "r" && handlers.onRefetch) {
88
95
  console.log("\n🔄 Re-fetching all files...");
89
96
  await handlers.onRefetch();
90
- console.log("👀 Watching for changes...");
91
- showWatchHelp();
97
+ if (!manual)
98
+ console.log("👀 Watching for changes...");
99
+ showWatchHelp(manual);
92
100
  }
93
101
  else if (cmd === "x" && handlers.onExit) {
94
102
  await handlers.onExit();
95
103
  }
104
+ else if (cmd === "q" && handlers.onQuit) {
105
+ await handlers.onQuit();
106
+ }
96
107
  });
97
108
  }
98
109
  const EDITOR_CANDIDATES = [
99
110
  { name: "VS Code", cmd: "code", args: (dir) => ["-n", dir] },
100
111
  { name: "Cursor", cmd: "cursor", args: (dir) => ["-n", dir] },
101
112
  ];
102
- function showEditorMenu(viewsDir, handlers) {
113
+ function statusLine(manual, suffix = "") {
114
+ const base = manual ? "✋ Manual sync mode — press [s] to push changes." : "👀 Watching for changes...";
115
+ console.log(`${base}${suffix}`);
116
+ }
117
+ function showEditorMenu(viewsDir, handlers, manual = false) {
103
118
  const editors = EDITOR_CANDIDATES
104
119
  .filter(e => commandExists(e.cmd))
105
120
  .map((e, i) => ({ ...e, key: String(i + 1) }));
106
121
  if (editors.length === 0) {
107
- console.log("\n👀 Watching for changes...");
108
- showWatchHelp();
109
- setupKeyboardInput(handlers);
122
+ console.log("");
123
+ statusLine(manual);
124
+ showWatchHelp(manual);
125
+ setupKeyboardInput(handlers, manual);
110
126
  return;
111
127
  }
112
128
  console.log("\n📂 Open in editor:");
113
129
  editors.forEach(e => console.log(` [${e.key}] ${e.name}`));
114
130
  console.log(" [Enter] Skip");
115
- console.log(" [x] Quit\n");
131
+ console.log(" [x] Exit (cleanup)");
132
+ console.log(" [q] Quit (keep files)\n");
116
133
  const rl = readline_1.default.createInterface({
117
134
  input: process.stdin,
118
135
  output: process.stdout,
119
136
  });
120
- // Count lines to clear (menu header + editors + skip + quit + empty + prompt)
121
- const linesToClear = editors.length + 5;
137
+ // Count lines to clear (menu header + editors + skip + exit + quit + empty + prompt)
138
+ const linesToClear = editors.length + 6;
122
139
  rl.question("Select editor: ", async (answer) => {
123
140
  rl.close();
124
141
  // Clear the menu lines
@@ -127,14 +144,20 @@ function showEditorMenu(viewsDir, handlers) {
127
144
  process.stdout.write("\x1b[2K\n"); // Clear each line
128
145
  }
129
146
  process.stdout.write(`\x1b[${linesToClear}A`); // Move back up
130
- if (answer.trim().toLowerCase() === "x") {
147
+ const choice = answer.trim().toLowerCase();
148
+ if (choice === "x") {
131
149
  if (handlers.onExit)
132
150
  await handlers.onExit();
133
151
  return;
134
152
  }
153
+ if (choice === "q") {
154
+ if (handlers.onQuit)
155
+ await handlers.onQuit();
156
+ return;
157
+ }
135
158
  const selected = editors.find(e => e.key === answer.trim());
136
159
  if (selected) {
137
- console.log(`👀 Watching for changes... (opened ${selected.name})`);
160
+ statusLine(manual, ` (opened ${selected.name})`);
138
161
  (0, child_process_1.spawn)(selected.cmd, selected.args(viewsDir), {
139
162
  detached: true,
140
163
  stdio: "ignore",
@@ -142,9 +165,9 @@ function showEditorMenu(viewsDir, handlers) {
142
165
  }).unref();
143
166
  }
144
167
  else {
145
- console.log("👀 Watching for changes...");
168
+ statusLine(manual);
146
169
  }
147
- showWatchHelp();
148
- setupKeyboardInput(handlers);
170
+ showWatchHelp(manual);
171
+ setupKeyboardInput(handlers, manual);
149
172
  });
150
173
  }
package/dist/index.js CHANGED
@@ -116,6 +116,32 @@ async function handleExit() {
116
116
  await cleanupFiles(VIEWS_DIR);
117
117
  process.exit(0);
118
118
  }
119
+ async function handleQuit() {
120
+ if (isShuttingDown)
121
+ return;
122
+ isShuttingDown = true;
123
+ watcher.setShuttingDown(true);
124
+ console.log("\n⚠️ Quitting (keeping files)...");
125
+ await watcher.stopWatching();
126
+ if (VIEWS_DIR)
127
+ console.log(`📁 Workspace left at: ${VIEWS_DIR}`);
128
+ process.exit(0);
129
+ }
130
+ /**
131
+ * Manual sync trigger ([s] key). Scans the workspace on demand, diffs against
132
+ * the cached state in .cache/state.json, and pushes only changed files.
133
+ */
134
+ async function handleManualSync() {
135
+ console.log("\n🔄 Syncing changes...");
136
+ try {
137
+ const { pushed } = await runSync();
138
+ console.log(pushed > 0 ? `✅ Synced ${pushed} change(s).` : "✔️ No changes to sync.");
139
+ }
140
+ catch (err) {
141
+ const e = err;
142
+ console.error("❌ Sync failed:", e.body || e.message);
143
+ }
144
+ }
119
145
  async function main() {
120
146
  await initConfig();
121
147
  try {
@@ -126,17 +152,24 @@ async function main() {
126
152
  console.error("❌ Sync failed:", e.body || e.message);
127
153
  process.exit(1);
128
154
  }
129
- watcher.init({ viewsDir: VIEWS_DIR, onSync: runSync, onIdle: handleExit });
130
- watcher.monitorFiles();
155
+ const manual = options.manual ?? false;
156
+ if (!manual) {
157
+ watcher.init({ viewsDir: VIEWS_DIR, onSync: runSync, onIdle: handleExit });
158
+ watcher.monitorFiles();
159
+ }
131
160
  console.log(`\n✅ Ready! Editing session started for site - ${site.name}.`);
132
161
  console.log(`\n📁 Workspace created at: ${VIEWS_DIR}`);
133
162
  if (ENV !== "production")
134
163
  console.log(`🌐 Environment: ${ENV}`);
164
+ if (manual)
165
+ console.log(`\n✋ Manual sync mode — changes are pushed only when you press [s].`);
135
166
  console.log(`\n⚠️ Files will be cleaned up on exit (Ctrl+C).`);
136
167
  cli.showEditorMenu(VIEWS_DIR, {
137
168
  onExit: handleExit,
169
+ onQuit: handleQuit,
138
170
  onRefetch: () => runSync({ flush: true }),
139
- });
171
+ ...(manual ? { onSync: handleManualSync } : {}),
172
+ }, manual);
140
173
  process.on("SIGINT", async () => {
141
174
  console.log("\n🛑 Caught interrupt signal (Ctrl+C)");
142
175
  await handleExit();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleekcms/sync",
3
- "version": "1.4.3",
3
+ "version": "1.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",