@hiveku-apps/sync 0.3.6 → 0.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
@@ -6,10 +6,13 @@ Local sync CLI for a Hiveku account. Pairs with the Hiveku MCP server at
6
6
  1. **`hiveku-sync init <slug>`** — scaffold a per-account folder with `.env`,
7
7
  `.mcp.json`, `CLAUDE.md`, and the empty `memory/ rules/ skills/` tree.
8
8
  2. **`hiveku-sync pull`** — fetch the account's memory/rules/skills via the
9
- `memory_list` MCP tool and write each entry as a Markdown file with
10
- YAML frontmatter (name, type, domain, department, version, updated_at).
9
+ `memory_list` MCP tool and write each entry as a Markdown file, filed under
10
+ the agent that owns it (see "Filed by owner" below), with YAML frontmatter
11
+ (id, name, type, domain, owner, version, updated_at).
11
12
  It also writes the account memory (see "Account memory" below) to
12
- `hiveku-data/account/ACCOUNT_MEMORY.md`, read-only.
13
+ `hiveku-data/account/ACCOUNT_MEMORY.md`, read-only, and brings the folder
14
+ layout and memory rules in `CLAUDE.md` up to date (see "CLAUDE.md stays
15
+ current" below).
13
16
 
14
17
  The goal: open one folder per Hiveku tenant in VS Code with Claude Code,
15
18
  and the local `.md` files become the source-of-truth Claude reads. Tools
@@ -105,7 +108,20 @@ This writes, merge-safely (only Hiveku-managed regions are touched):
105
108
  `OLYMPUS_API_KEY` env var. Also wires `playwright` for visual checks.
106
109
  - **`.agents/skills/<name>/SKILL.md`** — the account's pulled `skills/` and
107
110
  `commands/` mirrored as repo-scoped Codex skills (Codex prompts are global-only
108
- and deprecated).
111
+ and deprecated), named for the agent that owns each one:
112
+ `hiveku-<agent>-<slug>` for a skill, `hiveku-<agent>-cmd-<slug>` for a
113
+ shortcut (`shared` for one every agent shares), in hyphen case and at most 64
114
+ characters. A mirrored skill whose source is gone, or that an older release
115
+ mirrored under another name (`hiveku-skill-<slug>`, `hiveku-cmd-<slug>`), is
116
+ removed, unless its folder holds anything besides our generated `SKILL.md`.
117
+ Your own skills (no hiveku-sync stamp) are never touched. Entries under a
118
+ `not-followed/` folder are not mirrored: no agent reads them.
119
+
120
+ `AGENTS.md` also carries a short **rules digest per agent**: who follows what,
121
+ then one line per agent with its rule count and first rule names, from the rules
122
+ the last `pull` wrote under `rules/<agent>/` (never those in
123
+ `rules/<agent>/not-followed/`). Run `hiveku-sync codex` after `pull` to refresh it
124
+ (`pull` reminds you when the folder has a Codex lane).
109
125
 
110
126
  Pass `--trust` to pre-trust the folder in `~/.codex/config.toml` (so Codex loads
111
127
  the repo config without its first-run trust prompt).
@@ -177,10 +193,32 @@ cd projects/<slug>
177
193
  hiveku-sync pull-env # secrets → .env.local (git-ignored)
178
194
  # ...edit files, run the dev server...
179
195
  hiveku-sync status # diff local vs the deploy source
180
- hiveku-sync push # save changes to the deploy source
196
+ hiveku-sync push -m "Updated the pricing section on the Home page"
197
+ # save changes, then save them as one version
181
198
  hiveku-sync push --deploy development --wait # ...then deploy and wait
182
199
  ```
183
200
 
201
+ `pull-project` pages through the site (`project_vcs_checkout` with `limit`,
202
+ then `cursor`), so a site of any size comes down, including the ones over
203
+ 150 MB that cannot be sent in one answer. `--branch <name>` pulls that
204
+ branch's working tree instead of Your site; `.hiveku/projects.json` records
205
+ the branch, the version the pull came from (`head_commit_id`) and, on a
206
+ branch, its `working_tree_etag`. Later `push`, `status`, `commit`,
207
+ `versions` and `rollback` in that folder act on the same branch unless you
208
+ pass `--branch` (`--branch main` for Your site).
209
+
210
+ Each pull also records the files it wrote (`.hiveku/pulled/<slug>.json`). The
211
+ next pull moves the ones its tree no longer has (another branch's files, files
212
+ deleted on Hiveku) out of the project folder into
213
+ `.hiveku/set-aside/<slug>/<time>/`, so a push can never send them back; files
214
+ you created yourself are listed, never moved. The run hints go to the project
215
+ folder's `.hiveku/RUN.md`, which push never sends.
216
+
217
+ On a branch, `push` compares the branch's `working_tree_etag` with the one
218
+ recorded at pull time. If someone saved on the branch since, it lists the files
219
+ that may hold their work and changes nothing (exit 1); pull again, or pass
220
+ `--force` to overwrite them.
221
+
184
222
  **`push` writes the deploy source, lane-routed.** This is the important part.
185
223
  `deploy_site` builds from `builder_code_versions` (+ the S3/CDN asset store) —
186
224
  **not** a GitHub branch. So `push`:
@@ -195,17 +233,65 @@ hiveku-sync push --deploy development --wait # ...then deploy and wait
195
233
  - diffs correctly: code via a single `project_files_status` call (so
196
234
  `only_remote` stays reliable — chunking it produces phantom deletions), assets
197
235
  via `assets_list` (so unchanged images aren't re-uploaded every push).
236
+ - sends each batch again with only the files that did not land (a large batch
237
+ can stop at the route's time budget and name the rest in `remaining_paths`),
238
+ and lists every file that still did not save.
239
+ - with `--include-deletes`, deletes only files this folder could hold: never
240
+ dot files (`.gitignore`, `.ai/memory.md`), build folders, or files Hiveku
241
+ manages (`soul.md`, `data/marketing/...`), which a pull never brings down.
198
242
 
199
243
  Flags: `--include-deletes` (remove code paths missing locally), `--no-assets` /
200
- `--assets-all`, `--commit "msg"` (also record a Hiveku-native `main` commit),
201
- `--deploy <env> [--wait]`, `--dry-run`.
244
+ `--assets-all`, `-m "name"` (the version name; `--commit "name"` still works),
245
+ `--no-version`, `--branch <name>`, `--force`, `--deploy <env> [--wait]`,
246
+ `--dry-run`. Images, fonts and videos live in one asset store shared by Your
247
+ site and every branch, so a push to a branch says so before it uploads them
248
+ (and in `--dry-run`). `--deploy` publishes what that tier is set to publish:
249
+ Your site, unless the tier is bound to the branch.
202
250
 
203
251
  ```bash
204
252
  hiveku-sync deploy <development|staging|production> [<slug>] [--wait]
205
253
  hiveku-sync pull-env [<slug>] [--target development|staging|production]
206
- hiveku-sync commit -m "msg" # legacy GitHub-branch commit (GitHub-connected projects only)
207
254
  ```
208
255
 
256
+ ## Versions
257
+
258
+ "Your site" is the project's main source: what the preview shows and what a
259
+ deploy ships. A save is live there at once, but it is not a **version** until
260
+ one is saved, and a version is what you can go back to.
261
+
262
+ - **`push` saves one version** after every file has landed:
263
+ `project_vcs_commit` with no files and no deletions (source `sync_cli`),
264
+ which saves everything on Your site (or the branch) not yet in a version,
265
+ from every writer (the Hiveku AI, the editor, other tools). So the name comes
266
+ from `-m`, otherwise from everything `project_vcs_status` says is waiting
267
+ ("Added the Pricing page and edited the Home page"), or only from the pages
268
+ the push changed on a server that cannot say. The first version after a
269
+ version from before versions of Your site is named by Hiveku ("Your site up
270
+ to <date>"). `--no-version` skips it.
271
+ - **Names are read by the site owner.** Describe what changed for visitors;
272
+ never file paths, file extensions, `fix:`-style prefixes or tool names.
273
+ A typed name that breaks this is refused before anything is written.
274
+ - **Exit codes:** `0` done; `1` some files did not save, or the push was
275
+ refused (they are listed, and there is no version and no deploy); `2` every
276
+ file IS saved but the version was not (the message says why and prints the
277
+ exact `hiveku-sync commit` that tries again); `3` the files and the version
278
+ ARE saved and only the deploy failed (`hiveku-sync deploy` tries again).
279
+
280
+ ```bash
281
+ hiveku-sync commit [-m "name"] # save what is waiting as one version (sends no files)
282
+ hiveku-sync commit --github -m "msg" # the old GitHub push (GitHub-connected projects only)
283
+ hiveku-sync versions [--limit 50] # the versions, newest first, and what is waiting
284
+ hiveku-sync rollback <version id> # dry run: what going back would change
285
+ ```
286
+
287
+ `rollback` is a dry run unless you add `--apply`, and `--apply` needs the
288
+ `--confirm` id the dry run printed (it prints the whole command), so it only goes
289
+ ahead if nobody changed the site since you looked. Rolling back writes a NEW
290
+ version equal to the old one, so it can be undone, saves unsaved changes as
291
+ their own version first, and never publishes: run `hiveku-sync deploy` after
292
+ it when the live site should match. Images, fonts and videos in the shared
293
+ asset store, the database, CMS entries and secrets are not part of versions.
294
+
209
295
  `pull-env` applies the same `_DEV` / `_PROD` / `_STAGING` suffix resolution the
210
296
  preview machine uses: for `development` it strips `_DEV` to the base key and
211
297
  skips `_PROD`/`_STAGING`; `--target production` does the inverse.
@@ -220,6 +306,11 @@ skips `_PROD`/`_STAGING`; `--target production` does the inverse.
220
306
  - **No deletes in v1** — entries removed in Olympus are *not* deleted
221
307
  from disk; they're listed under `removed_remotely_since_last_sync` in
222
308
  the manifest. Prune manually if you want.
309
+ - **Moved, never deleted** — when an entry the last pull wrote now lands
310
+ somewhere else (its owner changed, or an older release wrote it flat as
311
+ `<type>/<name>.md`), or is no longer mirrored, the old copy is moved to
312
+ `.hiveku/set-aside/knowledge/<time>/`. Files a pull did not write are never
313
+ moved. When the listing fails, nothing is moved or reported as removed.
223
314
 
224
315
  ## How writes work
225
316
 
@@ -227,6 +318,89 @@ For **projects**, `hiveku-sync push` / `deploy` write straight back to Hiveku
227
318
  (see "Coder projects" above). For **memory/rules/skills**, `pull` is read-only —
228
319
  persist changes with the `memory_create` / `memory_update` / `memory_delete` MCP
229
320
  tools through Claude, then `hiveku-sync pull` to refresh the local copies.
321
+ `memory_update` replaces the whole entry: send
322
+ `memory_update({ memory_id, content, reason, expected_version })` after checking
323
+ `memory_log_list({ memory_id, since })` for changes made since you read it.
324
+ `CLAUDE.md` says so in the part `pull` keeps current, also in folders an older
325
+ release made (see "CLAUDE.md stays current" below), and `AGENTS.md` in the
326
+ region `hiveku-sync codex` rewrites.
327
+
328
+ ## CLAUDE.md stays current
329
+
330
+ `init` writes `CLAUDE.md` once, and the rest of the file is yours from then on,
331
+ except its "Folder layout" and "Memory writes" sections. They sit between
332
+ `<!-- hiveku:memory:begin -->` and `<!-- hiveku:memory:end -->`, and `pull` (and
333
+ `codex`) bring them up to date, so a folder an older release made stops teaching
334
+ the old layout and the old `memory_update` call:
335
+
336
+ - the markers are there: the text between them is replaced;
337
+ - no markers, and the two sections are exactly what an earlier release wrote
338
+ (0.1.0 to 0.4.0): they are replaced by the marked part, and the rest of the
339
+ file is kept byte for byte;
340
+ - you edited those sections: the marked part is added at the end, your text is
341
+ left as it is, and the marked part says it wins where the two disagree.
342
+
343
+ A `CLAUDE.md` that is read-only, a link to another file, or whose markers are not
344
+ there once each and in order is left alone (`pull` says so). `pull` never creates
345
+ a missing one.
346
+
347
+ ## Filed by owner
348
+
349
+ `pull` lists every account-level entry with one `memory_list` call and files each
350
+ one under the agent that owns it, the way the Hiveku Memory page does:
351
+
352
+ ```
353
+ rules/sales/<name>.md a rule Sales owns
354
+ rules/seo/<name>.md a rule the SEO topic owns (each Marketing topic is its own owner)
355
+ rules/marketing/<name>.md a rule the Marketing parent owns
356
+ rules/shared/<name>.md a rule shared with every agent
357
+ skills/<agent>/, memory/<agent>/, commands/<agent>/, agents/<agent>/, identity/<agent>/
358
+ ```
359
+
360
+ The owner is the `owner` field `memory_list` returns (the builder's one owner
361
+ rule). A builder that does not return it yet gets the same rule from
362
+ `src/memory-owner.mjs`: for a rule, skill, shortcut or specialist, the
363
+ `department` column when it is set and is not `marketing`; a `marketing` or empty
364
+ column with a Marketing-family marker (`<!-- department: seo -->`) belongs to
365
+ that topic (the seeded starter rows); a `marketing` column without one stays with
366
+ the Marketing parent; an empty column falls to the marker, then the front matter,
367
+ then the shape of the name, else the entry is shared with every agent.
368
+
369
+ Who follows what: an agent follows the files in its own folder and `shared/`;
370
+ every Marketing topic also follows `marketing/`; the website agent (`coder`) also
371
+ follows `marketing/` and the website topics; calls follow only the receptionist's
372
+ own call rules.
373
+
374
+ An entry filed under an owner that no agent reads goes one folder down, in
375
+ `<type>/<owner>/not-followed/`: the Memory page does not show it, and its front
376
+ matter says `followed_by: "no agent"`. That is a rule, skill or note filed for the
377
+ chief of staff outside her own `_account:*` rows (she never reads those), an owner
378
+ that is no agent (`mini`, `graphic_design`), or a note a department owns under a
379
+ name its agent never reads (Sales reads its `sales` notes, not `pipeline-notes`).
380
+ The builder's `placement` says which (hidden, for a reason other than internal
381
+ state); an older builder gets the same rule from `src/memory-owner.mjs`. The Codex
382
+ rules digest and skill mirror leave these out.
383
+
384
+ The chief of staff's own rows (`_account:*`) and Voice and pronunciation are
385
+ placed where the Memory page shows them, **read-only** (owners and admins change
386
+ them on the Memory page; the front matter carries the `edit_url`):
387
+
388
+ ```
389
+ memory/orchestrator/how-it-works.md How it works (_account:soul)
390
+ memory/orchestrator/background.md Background (_account:claude)
391
+ rules/orchestrator/, skills/orchestrator/, memory/orchestrator/<topic>.md
392
+ memory/orchestrator/moved/<topic>.md topics now kept in About your business
393
+ hiveku-data/account/voice/<name>.md Voice and pronunciation
394
+ ```
395
+
396
+ A file under `moved/` is her old copy: it opens with a line naming the About your
397
+ business heading the topic moved to and saying that the current text is in
398
+ `hiveku-data/account/ACCOUNT_MEMORY.md`.
399
+
400
+ Internal rows the page never shows (`_workspace:*`, `_digest:*`, a row that
401
+ holds only the agents' security preamble, an old `_account:*` shape) are not
402
+ written. A name that a tool would read as its own instructions (`claude`,
403
+ `agents`) or that Windows keeps for a device gets `-entry` added.
230
404
 
231
405
  ## Account memory
232
406
 
@@ -245,7 +419,7 @@ suggested it and when. The two account-memory rows (`account` and
245
419
  `account-suggestions`) are never written under `memory/`, even when an older
246
420
  server still lists them.
247
421
 
248
- Nothing uploads this file: `push`, `commit` and `status` leave any
422
+ Nothing uploads this file: `push`, `commit --github` and `status` leave any
249
423
  `hiveku-data/account/` folder out, and the next `pull` replaces it. To add a
250
424
  fact, suggest it with the `account_memory_append` tool; an owner keeps or
251
425
  removes it on the dashboard.
@@ -18,6 +18,9 @@ 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';
22
+ import { ownerOrder } from '../src/memory-owner.mjs';
23
+ import { relative } from 'node:path';
21
24
  import { pathToFileURL } from 'node:url';
22
25
 
23
26
  export function parseFlags(argv) {
@@ -34,6 +37,9 @@ export function parseFlags(argv) {
34
37
  'branch',
35
38
  'profile',
36
39
  'parent',
40
+ 'confirm',
41
+ 'fingerprint',
42
+ 'limit',
37
43
  ]);
38
44
  for (let i = 0; i < argv.length; i++) {
39
45
  const arg = argv[i];
@@ -75,8 +81,12 @@ Memory/rules/skills:
75
81
  + .codex/config.toml + skills); --trust
76
82
  pre-trusts the folder in ~/.codex/config.toml.
77
83
  pull [--profile=<name>] Pull memory/rules/skills via MCP into the
78
- current account folder. Reads OLYMPUS_API_KEY
79
- and OLYMPUS_BASE_URL from ./.env.
84
+ current account folder, filed by the agent
85
+ that owns each one (<type>/<agent>/, and
86
+ <type>/shared/ for every agent). Brings the
87
+ folder layout and memory rules in CLAUDE.md
88
+ up to date. Reads OLYMPUS_API_KEY and
89
+ OLYMPUS_BASE_URL from ./.env.
80
90
  codex [<folder>] [--trust] Retrofit an existing account folder with the
81
91
  Codex lane (AGENTS.md + .codex/config.toml +
82
92
  mirror the account's skills/commands to
@@ -89,27 +99,59 @@ Memory/rules/skills:
89
99
 
90
100
  Coder projects:
91
101
  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.
102
+ pull-project <slug-or-id> Pull Your site's files (or, with --branch, that
103
+ [--branch=<name>] branch's working tree) into ./projects/<slug>/,
104
+ page by page (any size). Records the project,
105
+ branch and version in .hiveku/projects.json.
106
+ Files an earlier pull wrote that are not in
107
+ this one move to .hiveku/set-aside/<slug>/.
94
108
  status [<slug>] [--json] Diff local ./projects/<slug>/ vs the deploy source
95
- (builder_code_versions). cd in to omit <slug>.
109
+ [--branch=<name>] (builder_code_versions), or the pulled branch.
110
+ cd in to omit <slug>.
96
111
 
97
112
  Ship a project (the agent-native deploy loop):
98
113
  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.
114
+ [-m "<version name>"] images/fonts/video under public/<subdir>/ → the
115
+ [--no-version] S3/CDN asset store; everything else → batched,
116
+ [--include-deletes] verified bulk_save. This is what deploy reads.
117
+ [--no-assets] [--assets-all] Then saves ONE version of Your site (or of the
118
+ [--deploy <env>] [--wait] branch) named by -m, or from the pages changed.
119
+ [--dry-run] [--branch=<name>] Names are plain language for the site owner (no
120
+ [--force] file paths, extensions or "fix:" prefixes).
121
+ --no-version skips the version. --deploy <env>
122
+ [--wait] deploys after the push. --branch writes
123
+ to that branch (default: the branch pulled);
124
+ if someone saved on it since the pull, push
125
+ refuses unless --force.
126
+ Exit 0 done; 1 some files did not save or the
127
+ push was refused (no version, no deploy); 2
128
+ files ARE saved but the version was not (it
129
+ prints the \`commit\` that retries it); 3 files
130
+ and version ARE saved, the deploy failed.
104
131
  deploy <env> [<slug>] [--wait] Deploy to development | staging | production and
105
132
  (with --wait) poll to a terminal status.
106
133
  pull-env [<slug>] Write project secrets → .env.local (git-ignored),
107
134
  [--target <env>] with the same _DEV/_PROD/_STAGING resolution the
108
135
  preview uses. --target staging|production for the
109
136
  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]
137
+
138
+ Versions (Your site = the project's main source; rollback never publishes):
139
+ commit [<slug>] [-m "<name>"] Save what is on Hiveku and not yet in a version as
140
+ [--branch=<name>] one version (no files are sent: push first).
141
+ [--dry-run] Without -m it is named from what changed. Exit 2
142
+ when the version could not be saved.
143
+ commit --github -m "<msg>" The old GitHub push via project_commit (only for
144
+ [--branch=<name>] GitHub-connected projects).
145
+ [--include-deletes]
146
+ versions [<slug>] [--branch=<b>] The versions, newest first, and what is waiting.
147
+ [--limit N] [--json]
148
+ rollback <version id> [<slug>] Show what going back to that version would change.
149
+ [--branch=<name>] Nothing changes (a dry run). It prints the exact
150
+ [--apply --confirm <id>] --apply command, with the --confirm id it saw;
151
+ [--fingerprint <fp>] applying writes a NEW version equal to the old one
152
+ [-m "<name>"] (so it can be undone) and is refused if the site
153
+ changed since the dry run. Publish separately
154
+ with deploy.
113
155
 
114
156
  help Show this message.
115
157
 
@@ -140,19 +182,56 @@ function userToolTablesLines({ userToolTables = [], userTablesWithoutApprovalMod
140
182
  return lines;
141
183
  }
142
184
 
143
- function commitMessageFrom(flags) {
144
- // --commit "msg" | --commit=msg | -m "msg" | --message "msg". A bare --commit
145
- // (boolean true) means "commit with no message" → fall back to the default.
185
+ /**
186
+ * What refreshing CLAUDE.md's kept-current part (folder layout and memory
187
+ * rules, claude-md.mjs) did, as lines to print. Nothing when it was already
188
+ * current or there is no CLAUDE.md.
189
+ */
190
+ export function claudeMdLines(result) {
191
+ switch (result?.status) {
192
+ case 'updated':
193
+ case 'replaced':
194
+ return [' CLAUDE.md: brought the folder layout and memory rules up to date (the rest of the file is as it was).'];
195
+ case 'appended':
196
+ return [
197
+ ' CLAUDE.md: added the current folder layout and memory rules at the end, between hiveku-sync markers.',
198
+ ' Your file had its own version of those sections, so they were left as they are: where they disagree, the new part wins.',
199
+ ];
200
+ case 'symlink':
201
+ return [' CLAUDE.md: not updated, because it is a link to another file. Its folder layout and memory rules may be out of date.'];
202
+ case 'read-only':
203
+ return [' CLAUDE.md: not updated, because the file is read-only. Its folder layout and memory rules may be out of date.'];
204
+ case 'markers':
205
+ return [
206
+ ' CLAUDE.md: not updated, because the hiveku-sync markers (<!-- hiveku:memory:begin --> and <!-- hiveku:memory:end -->) are not there exactly once each, in order.',
207
+ ' Keep one of each, then run this again.',
208
+ ];
209
+ case 'failed':
210
+ return [` CLAUDE.md: could not update the folder layout and memory rules: ${result.message}`];
211
+ default:
212
+ return [];
213
+ }
214
+ }
215
+
216
+ export function commitMessageFrom(flags) {
217
+ // --commit "msg" | --commit=msg | -m "msg" | --message "msg": the version
218
+ // name. A bare --commit (boolean true, from before versions were the
219
+ // default) names nothing: the version is then named from what changed.
146
220
  const c = typeof flags['commit'] === 'string' ? flags['commit'] : undefined;
147
221
  const m = typeof flags['m'] === 'string' ? flags['m'] : undefined;
148
222
  const msg = typeof flags['message'] === 'string' ? flags['message'] : undefined;
149
223
  if (c) return c;
150
224
  if (m) return m;
151
225
  if (msg) return msg;
152
- if (flags['commit'] === true) return 'hiveku-sync: push';
153
226
  return undefined;
154
227
  }
155
228
 
229
+ /** A command's exit code, from the result object it returned (0 when it says nothing). */
230
+ function setExitCode(result) {
231
+ const code = result && typeof result.exit_code === 'number' ? result.exit_code : 0;
232
+ if (code) process.exitCode = code;
233
+ }
234
+
156
235
  async function main() {
157
236
  const [cmd, ...rest] = process.argv.slice(2);
158
237
  const { positional, flags } = parseFlags(rest);
@@ -203,9 +282,13 @@ async function main() {
203
282
  ? ` MCP auth: inlined OLYMPUS_API_KEY from .env.`
204
283
  : ` MCP auth: reads the OLYMPUS_API_KEY env var (no key found in .env to inline) — set it, or paste it into .env and re-run.`,
205
284
  );
206
- console.log(` skills: mirrored ${result.skillsMirrored} account skill/command entr${result.skillsMirrored === 1 ? 'y' : 'ies'} into .agents/skills/.`);
285
+ console.log(` skills: mirrored ${result.skillsMirrored} account skill/shortcut entr${result.skillsMirrored === 1 ? 'y' : 'ies'} into .agents/skills/ as hiveku-<agent>-<name>.`);
286
+ if (result.skillsRemoved > 0) {
287
+ console.log(` skills: removed ${result.skillsRemoved} mirrored skill${result.skillsRemoved === 1 ? '' : 's'} with no source left (or an older name).`);
288
+ }
207
289
  if (result.trust === 'added') console.log(` trust: pre-trusted this folder in ~/.codex/config.toml.`);
208
290
  else if (result.trust === 'already') console.log(` trust: folder already trusted.`);
291
+ for (const line of claudeMdLines(result.claudeMd)) console.log(line);
209
292
  console.log(` verify: cd there, run \`codex mcp list\` (the hiveku row), then \`get_account_info\`.`);
210
293
  break;
211
294
  }
@@ -215,6 +298,23 @@ async function main() {
215
298
  for (const [type, count] of Object.entries(summary.by_type)) {
216
299
  console.log(` ${type}: ${count}`);
217
300
  }
301
+ const owners = Object.entries(summary.by_owner).sort(([a], [b]) => ownerOrder(a) - ownerOrder(b) || (a < b ? -1 : 1));
302
+ if (owners.length > 0) {
303
+ console.log(` filed by agent (<type>/<agent>/): ${owners.map(([owner, n]) => `${owner} ${n}`).join(', ')}`);
304
+ }
305
+ if (summary.not_followed > 0) {
306
+ const n = summary.not_followed;
307
+ console.log(
308
+ ` not followed: ${n} entr${n === 1 ? 'y' : 'ies'} that no agent reads, in <type>/<agent>/not-followed/ (the Memory page does not show ${n === 1 ? 'it' : 'them'}).`,
309
+ );
310
+ }
311
+ if (summary.set_aside.length > 0) {
312
+ const n = summary.set_aside.length;
313
+ console.log(
314
+ ` moved ${n} older cop${n === 1 ? 'y' : 'ies'} an earlier pull wrote out of the way (now filed by agent, or no longer mirrored): ` +
315
+ `${relative(process.cwd(), summary.set_aside_dir) || summary.set_aside_dir}/`,
316
+ );
317
+ }
218
318
  if (summary.account_memory) {
219
319
  const am = summary.account_memory;
220
320
  console.log(
@@ -227,6 +327,10 @@ async function main() {
227
327
  for (const f of summary.removed_remotely) console.log(` ${f}`);
228
328
  console.log('They were left on disk. Delete them manually if no longer needed.');
229
329
  }
330
+ for (const line of claudeMdLines(summary.claude_md)) console.log(line);
331
+ if (summary.codex_lane) {
332
+ console.log('\nCodex: run `hiveku-sync codex` to refresh the rules list in AGENTS.md and the skills in .agents/skills/.');
333
+ }
230
334
  if (summary.errors.length > 0) {
231
335
  console.error('\nErrors:');
232
336
  for (const e of summary.errors) console.error(` ${e.type}: ${e.message}`);
@@ -257,12 +361,13 @@ async function main() {
257
361
  slug: positional[0],
258
362
  profile: flags['profile'],
259
363
  json: !!flags['json'],
364
+ branch: flags['branch'],
260
365
  });
261
366
  break;
262
367
  }
263
368
  case 'push': {
264
369
  const deployEnv = typeof flags['deploy'] === 'string' ? flags['deploy'] : undefined;
265
- await runPush({
370
+ const result = await runPush({
266
371
  slug: positional[0],
267
372
  profile: flags['profile'],
268
373
  commit: commitMessageFrom(flags),
@@ -273,7 +378,10 @@ async function main() {
273
378
  assetsAll: !!flags['assets-all'],
274
379
  dryRun: !!flags['dry-run'],
275
380
  branch: flags['branch'],
381
+ noVersion: !!flags['no-version'],
382
+ force: !!flags['force'],
276
383
  });
384
+ setExitCode(result);
277
385
  break;
278
386
  }
279
387
  case 'deploy': {
@@ -301,21 +409,63 @@ async function main() {
301
409
  break;
302
410
  }
303
411
  case 'commit': {
304
- // The legacy GitHub-branch commit path (project_commit). Requires a repo.
305
- const message = commitMessageFrom(flags);
306
- if (!message || message === 'hiveku-sync: push') {
307
- console.error('Error: commit requires -m "<commit message>"\n');
308
- console.error(HELP);
309
- process.exit(1);
412
+ if (flags['github']) {
413
+ // The old GitHub-branch path (project_commit). Requires a connected repo.
414
+ const message = commitMessageFrom(flags);
415
+ if (!message) {
416
+ console.error('Error: commit --github requires -m "<message>"\n');
417
+ console.error(HELP);
418
+ process.exit(1);
419
+ }
420
+ await runProjectPush({
421
+ slug: positional[0],
422
+ message,
423
+ branch: flags['branch'],
424
+ profile: flags['profile'],
425
+ includeDeletes: !!flags['include-deletes'],
426
+ dryRun: !!flags['dry-run'],
427
+ });
428
+ break;
310
429
  }
311
- await runProjectPush({
430
+ // Default: save what is on Hiveku as one version (no files sent).
431
+ const result = await runVersionCommit({
312
432
  slug: positional[0],
313
- message,
433
+ name: commitMessageFrom(flags),
314
434
  branch: flags['branch'],
315
435
  profile: flags['profile'],
316
- includeDeletes: !!flags['include-deletes'],
317
436
  dryRun: !!flags['dry-run'],
318
437
  });
438
+ setExitCode(result);
439
+ break;
440
+ }
441
+ case 'versions': {
442
+ const result = await runVersions({
443
+ slug: positional[0],
444
+ branch: flags['branch'],
445
+ limit: typeof flags['limit'] === 'string' ? flags['limit'] : undefined,
446
+ json: !!flags['json'],
447
+ profile: flags['profile'],
448
+ });
449
+ setExitCode(result);
450
+ break;
451
+ }
452
+ case 'rollback': {
453
+ if (!positional[0]) {
454
+ console.error('Error: rollback requires a version id (see `hiveku-sync versions`).\n');
455
+ console.error(HELP);
456
+ process.exit(1);
457
+ }
458
+ const result = await runRollback({
459
+ versionId: positional[0],
460
+ slug: positional[1],
461
+ branch: flags['branch'],
462
+ apply: !!flags['apply'],
463
+ head: typeof flags['confirm'] === 'string' ? flags['confirm'] : undefined,
464
+ fingerprint: typeof flags['fingerprint'] === 'string' ? flags['fingerprint'] : undefined,
465
+ name: commitMessageFrom(flags),
466
+ profile: flags['profile'],
467
+ });
468
+ setExitCode(result);
319
469
  break;
320
470
  }
321
471
  case 'help':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiveku-apps/sync",
3
- "version": "0.3.6",
3
+ "version": "0.5.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",