@hiveku-apps/sync 0.3.5 → 0.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/README.md CHANGED
@@ -108,9 +108,52 @@ This writes, merge-safely (only Hiveku-managed regions are touched):
108
108
  and deprecated).
109
109
 
110
110
  Pass `--trust` to pre-trust the folder in `~/.codex/config.toml` (so Codex loads
111
- the repo config without its first-run trust prompt). Codex ignores repo-level
112
- approval/sandbox config, so the safe-work rules ship as instructions in
113
- `AGENTS.md`, not enforced permissions.
111
+ the repo config without its first-run trust prompt).
112
+
113
+ **Approvals.** Codex asks first only for a **fixed list** of high-risk Hiveku
114
+ tools: the 246 names in `src/codex-prompt-tools.json`, the same list as the
115
+ Hiveku Codex plugin 0.1.16. It includes `deploy_site`, `site_delete`,
116
+ `project_files_bulk_save` and every `hiveku_batch`, and it is written as
117
+ `[mcp_servers.hiveku.tools.<tool>] approval_mode = "prompt"`. **Every other tool
118
+ on the `hiveku` server runs without asking** (`default_tools_approval_mode =
119
+ "approve"`), and that includes many deletes and sends, for example
120
+ `crm_delete_contact`, `content_delete` and `survey_send`. So the safe-work rules
121
+ in `AGENTS.md` still apply to every tool: they are instructions, not enforced
122
+ permissions.
123
+
124
+ **Your own setting wins.** When `.codex/config.toml` already has your own
125
+ `[mcp_servers.hiveku.tools.<tool>]` table for a listed tool, outside the
126
+ hiveku-sync block, hiveku-sync leaves that tool out of its block (TOML does not
127
+ allow a table to be declared twice) and prints a line naming it. If your table
128
+ sets no `approval_mode`, Codex uses the server default, `approve`, so that tool
129
+ runs without asking, and the line says so. Add `approval_mode = "prompt"` to
130
+ your table to have it ask first.
131
+
132
+ hiveku-sync checks the updated file before it writes anything. It stops only
133
+ when its own block would declare a table or key that another line of the file
134
+ also declares, for example your own copy of `[mcp_servers.hiveku]`. Then it
135
+ writes nothing in the folder and names that line by its number. It reads the
136
+ file as Codex does, so TOML 1.1 syntax and a byte-order mark at the start are
137
+ fine, and it leaves the rest of the file, including any line it cannot read,
138
+ to you and Codex.
139
+
140
+ The folder has to carry the list itself: Codex uses the folder's
141
+ `[mcp_servers.hiveku]` instead of the plugin's, including for approvals, so the
142
+ plugin's prompts do not apply here.
143
+
144
+ Headless and bypass modes:
145
+
146
+ - **`codex exec` never stops to ask.** Under a `read-only` or `workspace-write`
147
+ sandbox it cancels the listed calls and runs the rest.
148
+ - **The bypass flag skips every prompt.** With
149
+ `--dangerously-bypass-approvals-and-sandbox` (`--yolo`), in `codex exec` or in
150
+ interactive `codex`, and with `danger-full-access` in `codex exec`
151
+ (`-s danger-full-access` or `sandbox_mode = "danger-full-access"`), the listed
152
+ tools run unattended too, like every other Hiveku tool.
153
+
154
+ Folders set up by a hiveku-sync without these prompts (0.3.5 and earlier)
155
+ approve every tool: run `hiveku-sync codex` in them once, with a release that has
156
+ them, to add the prompts.
114
157
 
115
158
  > **This vs. the plugin.** For a single account, you can instead install the
116
159
  > published [Hiveku Codex plugin](https://github.com/locus2012/hiveku-codex-plugin)
@@ -134,10 +177,32 @@ cd projects/<slug>
134
177
  hiveku-sync pull-env # secrets → .env.local (git-ignored)
135
178
  # ...edit files, run the dev server...
136
179
  hiveku-sync status # diff local vs the deploy source
137
- hiveku-sync push # save changes to the deploy source
180
+ hiveku-sync push -m "Updated the pricing section on the Home page"
181
+ # save changes, then save them as one version
138
182
  hiveku-sync push --deploy development --wait # ...then deploy and wait
139
183
  ```
140
184
 
185
+ `pull-project` pages through the site (`project_vcs_checkout` with `limit`,
186
+ then `cursor`), so a site of any size comes down, including the ones over
187
+ 150 MB that cannot be sent in one answer. `--branch <name>` pulls that
188
+ branch's working tree instead of Your site; `.hiveku/projects.json` records
189
+ the branch, the version the pull came from (`head_commit_id`) and, on a
190
+ branch, its `working_tree_etag`. Later `push`, `status`, `commit`,
191
+ `versions` and `rollback` in that folder act on the same branch unless you
192
+ pass `--branch` (`--branch main` for Your site).
193
+
194
+ Each pull also records the files it wrote (`.hiveku/pulled/<slug>.json`). The
195
+ next pull moves the ones its tree no longer has (another branch's files, files
196
+ deleted on Hiveku) out of the project folder into
197
+ `.hiveku/set-aside/<slug>/<time>/`, so a push can never send them back; files
198
+ you created yourself are listed, never moved. The run hints go to the project
199
+ folder's `.hiveku/RUN.md`, which push never sends.
200
+
201
+ On a branch, `push` compares the branch's `working_tree_etag` with the one
202
+ recorded at pull time. If someone saved on the branch since, it lists the files
203
+ that may hold their work and changes nothing (exit 1); pull again, or pass
204
+ `--force` to overwrite them.
205
+
141
206
  **`push` writes the deploy source, lane-routed.** This is the important part.
142
207
  `deploy_site` builds from `builder_code_versions` (+ the S3/CDN asset store) —
143
208
  **not** a GitHub branch. So `push`:
@@ -152,17 +217,65 @@ hiveku-sync push --deploy development --wait # ...then deploy and wait
152
217
  - diffs correctly: code via a single `project_files_status` call (so
153
218
  `only_remote` stays reliable — chunking it produces phantom deletions), assets
154
219
  via `assets_list` (so unchanged images aren't re-uploaded every push).
220
+ - sends each batch again with only the files that did not land (a large batch
221
+ can stop at the route's time budget and name the rest in `remaining_paths`),
222
+ and lists every file that still did not save.
223
+ - with `--include-deletes`, deletes only files this folder could hold: never
224
+ dot files (`.gitignore`, `.ai/memory.md`), build folders, or files Hiveku
225
+ manages (`soul.md`, `data/marketing/...`), which a pull never brings down.
155
226
 
156
227
  Flags: `--include-deletes` (remove code paths missing locally), `--no-assets` /
157
- `--assets-all`, `--commit "msg"` (also record a Hiveku-native `main` commit),
158
- `--deploy <env> [--wait]`, `--dry-run`.
228
+ `--assets-all`, `-m "name"` (the version name; `--commit "name"` still works),
229
+ `--no-version`, `--branch <name>`, `--force`, `--deploy <env> [--wait]`,
230
+ `--dry-run`. Images, fonts and videos live in one asset store shared by Your
231
+ site and every branch, so a push to a branch says so before it uploads them
232
+ (and in `--dry-run`). `--deploy` publishes what that tier is set to publish:
233
+ Your site, unless the tier is bound to the branch.
159
234
 
160
235
  ```bash
161
236
  hiveku-sync deploy <development|staging|production> [<slug>] [--wait]
162
237
  hiveku-sync pull-env [<slug>] [--target development|staging|production]
163
- hiveku-sync commit -m "msg" # legacy GitHub-branch commit (GitHub-connected projects only)
164
238
  ```
165
239
 
240
+ ## Versions
241
+
242
+ "Your site" is the project's main source: what the preview shows and what a
243
+ deploy ships. A save is live there at once, but it is not a **version** until
244
+ one is saved, and a version is what you can go back to.
245
+
246
+ - **`push` saves one version** after every file has landed:
247
+ `project_vcs_commit` with no files and no deletions (source `sync_cli`),
248
+ which saves everything on Your site (or the branch) not yet in a version,
249
+ from every writer (the Hiveku AI, the editor, other tools). So the name comes
250
+ from `-m`, otherwise from everything `project_vcs_status` says is waiting
251
+ ("Added the Pricing page and edited the Home page"), or only from the pages
252
+ the push changed on a server that cannot say. The first version after a
253
+ version from before versions of Your site is named by Hiveku ("Your site up
254
+ to <date>"). `--no-version` skips it.
255
+ - **Names are read by the site owner.** Describe what changed for visitors;
256
+ never file paths, file extensions, `fix:`-style prefixes or tool names.
257
+ A typed name that breaks this is refused before anything is written.
258
+ - **Exit codes:** `0` done; `1` some files did not save, or the push was
259
+ refused (they are listed, and there is no version and no deploy); `2` every
260
+ file IS saved but the version was not (the message says why and prints the
261
+ exact `hiveku-sync commit` that tries again); `3` the files and the version
262
+ ARE saved and only the deploy failed (`hiveku-sync deploy` tries again).
263
+
264
+ ```bash
265
+ hiveku-sync commit [-m "name"] # save what is waiting as one version (sends no files)
266
+ hiveku-sync commit --github -m "msg" # the old GitHub push (GitHub-connected projects only)
267
+ hiveku-sync versions [--limit 50] # the versions, newest first, and what is waiting
268
+ hiveku-sync rollback <version id> # dry run: what going back would change
269
+ ```
270
+
271
+ `rollback` is a dry run unless you add `--apply`, and `--apply` needs the
272
+ `--confirm` id the dry run printed (it prints the whole command), so it only goes
273
+ ahead if nobody changed the site since you looked. Rolling back writes a NEW
274
+ version equal to the old one, so it can be undone, saves unsaved changes as
275
+ their own version first, and never publishes: run `hiveku-sync deploy` after
276
+ it when the live site should match. Images, fonts and videos in the shared
277
+ asset store, the database, CMS entries and secrets are not part of versions.
278
+
166
279
  `pull-env` applies the same `_DEV` / `_PROD` / `_STAGING` suffix resolution the
167
280
  preview machine uses: for `development` it strips `_DEV` to the base key and
168
281
  skips `_PROD`/`_STAGING`; `--target production` does the inverse.
@@ -202,7 +315,7 @@ suggested it and when. The two account-memory rows (`account` and
202
315
  `account-suggestions`) are never written under `memory/`, even when an older
203
316
  server still lists them.
204
317
 
205
- Nothing uploads this file: `push`, `commit` and `status` leave any
318
+ Nothing uploads this file: `push`, `commit --github` and `status` leave any
206
319
  `hiveku-data/account/` folder out, and the next `pull` replaces it. To add a
207
320
  fact, suggest it with the `account_memory_append` tool; an owner keeps or
208
321
  removes it on the dashboard.
@@ -18,6 +18,7 @@ import {
18
18
  runProjectPush,
19
19
  } from '../src/projects.mjs';
20
20
  import { runPush, runDeploy, runPullEnv } from '../src/push.mjs';
21
+ import { runRollback, runVersionCommit, runVersions } from '../src/versions.mjs';
21
22
  import { pathToFileURL } from 'node:url';
22
23
 
23
24
  export function parseFlags(argv) {
@@ -34,6 +35,9 @@ export function parseFlags(argv) {
34
35
  'branch',
35
36
  'profile',
36
37
  'parent',
38
+ 'confirm',
39
+ 'fingerprint',
40
+ 'limit',
37
41
  ]);
38
42
  for (let i = 0; i < argv.length; i++) {
39
43
  const arg = argv[i];
@@ -89,46 +93,108 @@ Memory/rules/skills:
89
93
 
90
94
  Coder projects:
91
95
  projects List the account's coder projects.
92
- pull-project <slug-or-id> Pull a project's source into ./projects/<slug>/.
93
- [--branch=<name>] Records the project mapping in .hiveku/projects.json.
96
+ pull-project <slug-or-id> Pull Your site's files (or, with --branch, that
97
+ [--branch=<name>] branch's working tree) into ./projects/<slug>/,
98
+ page by page (any size). Records the project,
99
+ branch and version in .hiveku/projects.json.
100
+ Files an earlier pull wrote that are not in
101
+ this one move to .hiveku/set-aside/<slug>/.
94
102
  status [<slug>] [--json] Diff local ./projects/<slug>/ vs the deploy source
95
- (builder_code_versions). cd in to omit <slug>.
103
+ [--branch=<name>] (builder_code_versions), or the pulled branch.
104
+ cd in to omit <slug>.
96
105
 
97
106
  Ship a project (the agent-native deploy loop):
98
107
  push [<slug>] Save local changes to the DEPLOY SOURCE, lane-routed:
99
- [--include-deletes] images/fonts/video under public/<subdir>/ → the
100
- [--no-assets] [--assets-all] S3/CDN asset store; everything else → batched,
101
- [--commit "<msg>"] verified bulk_save. This is what deploy reads.
102
- [--deploy <env>] [--wait] --commit also records a Hiveku 'main' commit.
103
- [--dry-run] [--branch=<name>] --deploy <env> [--wait] deploys after the push.
108
+ [-m "<version name>"] images/fonts/video under public/<subdir>/ → the
109
+ [--no-version] S3/CDN asset store; everything else → batched,
110
+ [--include-deletes] verified bulk_save. This is what deploy reads.
111
+ [--no-assets] [--assets-all] Then saves ONE version of Your site (or of the
112
+ [--deploy <env>] [--wait] branch) named by -m, or from the pages changed.
113
+ [--dry-run] [--branch=<name>] Names are plain language for the site owner (no
114
+ [--force] file paths, extensions or "fix:" prefixes).
115
+ --no-version skips the version. --deploy <env>
116
+ [--wait] deploys after the push. --branch writes
117
+ to that branch (default: the branch pulled);
118
+ if someone saved on it since the pull, push
119
+ refuses unless --force.
120
+ Exit 0 done; 1 some files did not save or the
121
+ push was refused (no version, no deploy); 2
122
+ files ARE saved but the version was not (it
123
+ prints the \`commit\` that retries it); 3 files
124
+ and version ARE saved, the deploy failed.
104
125
  deploy <env> [<slug>] [--wait] Deploy to development | staging | production and
105
126
  (with --wait) poll to a terminal status.
106
127
  pull-env [<slug>] Write project secrets → .env.local (git-ignored),
107
128
  [--target <env>] with the same _DEV/_PROD/_STAGING resolution the
108
129
  preview uses. --target staging|production for the
109
130
  other tiers.
110
- commit [<slug>] -m "<msg>" GitHub-branch commit via project_commit (only for
111
- [--branch=<name>] GitHub-connected projects; most sites use push).
112
- [--include-deletes]
131
+
132
+ Versions (Your site = the project's main source; rollback never publishes):
133
+ commit [<slug>] [-m "<name>"] Save what is on Hiveku and not yet in a version as
134
+ [--branch=<name>] one version (no files are sent: push first).
135
+ [--dry-run] Without -m it is named from what changed. Exit 2
136
+ when the version could not be saved.
137
+ commit --github -m "<msg>" The old GitHub push via project_commit (only for
138
+ [--branch=<name>] GitHub-connected projects).
139
+ [--include-deletes]
140
+ versions [<slug>] [--branch=<b>] The versions, newest first, and what is waiting.
141
+ [--limit N] [--json]
142
+ rollback <version id> [<slug>] Show what going back to that version would change.
143
+ [--branch=<name>] Nothing changes (a dry run). It prints the exact
144
+ [--apply --confirm <id>] --apply command, with the --confirm id it saw;
145
+ [--fingerprint <fp>] applying writes a NEW version equal to the old one
146
+ [-m "<name>"] (so it can be undone) and is refused if the site
147
+ changed since the dry run. Publish separately
148
+ with deploy.
113
149
 
114
150
  help Show this message.
115
151
 
116
152
  Profiles: full, sales, marketing, helpdesk, pm, dev
117
153
  `;
118
154
 
119
- function commitMessageFrom(flags) {
120
- // --commit "msg" | --commit=msg | -m "msg" | --message "msg". A bare --commit
121
- // (boolean true) means "commit with no message" → fall back to the default.
155
+ /**
156
+ * Plain lines naming the listed tools whose own config.toml table hiveku-sync
157
+ * left alone. A table that sets no approval_mode gets the server default
158
+ * ("approve"), so those tools are said to run without asking.
159
+ */
160
+ function userToolTablesLines({ userToolTables = [], userTablesWithoutApprovalMode = [] }) {
161
+ const lines = [];
162
+ const kept = userToolTables.filter((tool) => !userTablesWithoutApprovalMode.includes(tool));
163
+ if (kept.length) {
164
+ const them = kept.length === 1 ? 'it' : 'them';
165
+ lines.push(
166
+ `your own setting kept for ${kept.join(', ')}: .codex/config.toml sets ${them} outside the hiveku-sync block, so hiveku-sync did not add its "ask first" setting for ${them}.`,
167
+ );
168
+ }
169
+ const bare = userTablesWithoutApprovalMode;
170
+ if (bare.length) {
171
+ const one = bare.length === 1;
172
+ lines.push(
173
+ `${bare.join(', ')} ${one ? 'runs' : 'run'} without asking: your own ${one ? 'table' : 'tables'} for ${one ? 'it' : 'them'} in .codex/config.toml, outside the hiveku-sync block, ${one ? 'sets' : 'set'} no approval_mode, so Codex uses the server default ("approve"). To have ${one ? 'it' : 'them'} ask first, add approval_mode = "prompt" to ${one ? 'that table' : 'those tables'}.`,
174
+ );
175
+ }
176
+ return lines;
177
+ }
178
+
179
+ export function commitMessageFrom(flags) {
180
+ // --commit "msg" | --commit=msg | -m "msg" | --message "msg": the version
181
+ // name. A bare --commit (boolean true, from before versions were the
182
+ // default) names nothing: the version is then named from what changed.
122
183
  const c = typeof flags['commit'] === 'string' ? flags['commit'] : undefined;
123
184
  const m = typeof flags['m'] === 'string' ? flags['m'] : undefined;
124
185
  const msg = typeof flags['message'] === 'string' ? flags['message'] : undefined;
125
186
  if (c) return c;
126
187
  if (m) return m;
127
188
  if (msg) return msg;
128
- if (flags['commit'] === true) return 'hiveku-sync: push';
129
189
  return undefined;
130
190
  }
131
191
 
192
+ /** A command's exit code, from the result object it returned (0 when it says nothing). */
193
+ function setExitCode(result) {
194
+ const code = result && typeof result.exit_code === 'number' ? result.exit_code : 0;
195
+ if (code) process.exitCode = code;
196
+ }
197
+
132
198
  async function main() {
133
199
  const [cmd, ...rest] = process.argv.slice(2);
134
200
  const { positional, flags } = parseFlags(rest);
@@ -163,6 +229,7 @@ async function main() {
163
229
  ? ` MCP auth: inlined your OLYMPUS_API_KEY`
164
230
  : ` MCP auth: reads the OLYMPUS_API_KEY env var — after pasting your key into .env, run \`hiveku-sync codex\` to inline it, or \`export OLYMPUS_API_KEY=...\``,
165
231
  );
232
+ for (const line of userToolTablesLines(result.codex)) console.log(` ${line}`);
166
233
  if (result.codex.trust === 'added') console.log(` trust: pre-trusted this folder in ~/.codex/config.toml`);
167
234
  }
168
235
  console.log(` then: cd ${result.folder} && hiveku-sync pull`);
@@ -172,6 +239,7 @@ async function main() {
172
239
  const result = await runCodex({ folder: positional[0], trust: !!flags['trust'] });
173
240
  console.log(`Codex lane ready in ${result.folder} (profile=${result.profile})`);
174
241
  console.log(` AGENTS.md + .codex/config.toml written (git-ignored).`);
242
+ for (const line of userToolTablesLines(result)) console.log(` ${line}`);
175
243
  console.log(
176
244
  result.inlinedKey
177
245
  ? ` MCP auth: inlined OLYMPUS_API_KEY from .env.`
@@ -231,12 +299,13 @@ async function main() {
231
299
  slug: positional[0],
232
300
  profile: flags['profile'],
233
301
  json: !!flags['json'],
302
+ branch: flags['branch'],
234
303
  });
235
304
  break;
236
305
  }
237
306
  case 'push': {
238
307
  const deployEnv = typeof flags['deploy'] === 'string' ? flags['deploy'] : undefined;
239
- await runPush({
308
+ const result = await runPush({
240
309
  slug: positional[0],
241
310
  profile: flags['profile'],
242
311
  commit: commitMessageFrom(flags),
@@ -247,7 +316,10 @@ async function main() {
247
316
  assetsAll: !!flags['assets-all'],
248
317
  dryRun: !!flags['dry-run'],
249
318
  branch: flags['branch'],
319
+ noVersion: !!flags['no-version'],
320
+ force: !!flags['force'],
250
321
  });
322
+ setExitCode(result);
251
323
  break;
252
324
  }
253
325
  case 'deploy': {
@@ -275,21 +347,63 @@ async function main() {
275
347
  break;
276
348
  }
277
349
  case 'commit': {
278
- // The legacy GitHub-branch commit path (project_commit). Requires a repo.
279
- const message = commitMessageFrom(flags);
280
- if (!message || message === 'hiveku-sync: push') {
281
- console.error('Error: commit requires -m "<commit message>"\n');
282
- console.error(HELP);
283
- process.exit(1);
350
+ if (flags['github']) {
351
+ // The old GitHub-branch path (project_commit). Requires a connected repo.
352
+ const message = commitMessageFrom(flags);
353
+ if (!message) {
354
+ console.error('Error: commit --github requires -m "<message>"\n');
355
+ console.error(HELP);
356
+ process.exit(1);
357
+ }
358
+ await runProjectPush({
359
+ slug: positional[0],
360
+ message,
361
+ branch: flags['branch'],
362
+ profile: flags['profile'],
363
+ includeDeletes: !!flags['include-deletes'],
364
+ dryRun: !!flags['dry-run'],
365
+ });
366
+ break;
284
367
  }
285
- await runProjectPush({
368
+ // Default: save what is on Hiveku as one version (no files sent).
369
+ const result = await runVersionCommit({
286
370
  slug: positional[0],
287
- message,
371
+ name: commitMessageFrom(flags),
288
372
  branch: flags['branch'],
289
373
  profile: flags['profile'],
290
- includeDeletes: !!flags['include-deletes'],
291
374
  dryRun: !!flags['dry-run'],
292
375
  });
376
+ setExitCode(result);
377
+ break;
378
+ }
379
+ case 'versions': {
380
+ const result = await runVersions({
381
+ slug: positional[0],
382
+ branch: flags['branch'],
383
+ limit: typeof flags['limit'] === 'string' ? flags['limit'] : undefined,
384
+ json: !!flags['json'],
385
+ profile: flags['profile'],
386
+ });
387
+ setExitCode(result);
388
+ break;
389
+ }
390
+ case 'rollback': {
391
+ if (!positional[0]) {
392
+ console.error('Error: rollback requires a version id (see `hiveku-sync versions`).\n');
393
+ console.error(HELP);
394
+ process.exit(1);
395
+ }
396
+ const result = await runRollback({
397
+ versionId: positional[0],
398
+ slug: positional[1],
399
+ branch: flags['branch'],
400
+ apply: !!flags['apply'],
401
+ head: typeof flags['confirm'] === 'string' ? flags['confirm'] : undefined,
402
+ fingerprint: typeof flags['fingerprint'] === 'string' ? flags['fingerprint'] : undefined,
403
+ name: commitMessageFrom(flags),
404
+ profile: flags['profile'],
405
+ });
406
+ setExitCode(result);
293
407
  break;
294
408
  }
295
409
  case 'help':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiveku-apps/sync",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Sync a Hiveku account's memory/rules/skills + coder projects into a local folder, and ship projects (push/deploy/pull-env) from the terminal. Pairs with the Hiveku MCP at core.hiveku.com/mcp so Claude Code can drive Hiveku tools per-folder.",
5
5
  "type": "module",
6
6
  "license": "MIT",