@hiveku-apps/sync 0.1.0 → 0.3.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 +90 -4
- package/bin/hiveku-sync.mjs +151 -23
- package/package.json +7 -5
- package/src/codex.mjs +242 -0
- package/src/init.mjs +36 -4
- package/src/projects.mjs +77 -42
- package/src/push.mjs +618 -0
package/README.md
CHANGED
|
@@ -78,6 +78,92 @@ narrowed tool list. Available: `full`, `sales`, `marketing`, `helpdesk`,
|
|
|
78
78
|
`pm`, `dev`. Change `HIVEKU_PROFILE` in `.env` and reload the MCP server
|
|
79
79
|
in your editor to switch.
|
|
80
80
|
|
|
81
|
+
## Codex (OpenAI) support
|
|
82
|
+
|
|
83
|
+
By default `init` scaffolds a Claude Code folder (`.mcp.json` + `CLAUDE.md`). Add
|
|
84
|
+
`--codex` to also write the Codex-native equivalents so the same folder works
|
|
85
|
+
from the **Codex CLI / IDE extension / ChatGPT desktop**:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Scaffold with the Codex lane too:
|
|
89
|
+
hiveku-sync init acme-corp --codex
|
|
90
|
+
|
|
91
|
+
# Or retrofit an existing account folder (run after `pull` to include the
|
|
92
|
+
# account's latest skills/commands):
|
|
93
|
+
cd acme-corp && hiveku-sync codex
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This writes, merge-safely (only Hiveku-managed regions are touched):
|
|
97
|
+
|
|
98
|
+
- **`AGENTS.md`** — Codex's `CLAUDE.md` equivalent: a lean pointer to `CLAUDE.md`
|
|
99
|
+
plus the load-bearing rules (Codex caps the instruction chain at ~32 KiB).
|
|
100
|
+
- **`.codex/config.toml`** — the same `core.hiveku.com/mcp` server via
|
|
101
|
+
`[mcp_servers.hiveku]`, git-ignored. Your `OLYMPUS_API_KEY` is **inlined** when
|
|
102
|
+
`.env` has it (self-contained, no env setup); otherwise it's read from the
|
|
103
|
+
`OLYMPUS_API_KEY` env var. Also wires `playwright` for visual checks.
|
|
104
|
+
- **`.agents/skills/<name>/SKILL.md`** — the account's pulled `skills/` and
|
|
105
|
+
`commands/` mirrored as repo-scoped Codex skills (Codex prompts are global-only
|
|
106
|
+
and deprecated).
|
|
107
|
+
|
|
108
|
+
Pass `--trust` to pre-trust the folder in `~/.codex/config.toml` (so Codex loads
|
|
109
|
+
the repo config without its first-run trust prompt). Codex ignores repo-level
|
|
110
|
+
approval/sandbox config, so the safe-work rules ship as instructions in
|
|
111
|
+
`AGENTS.md`, not enforced permissions.
|
|
112
|
+
|
|
113
|
+
> **This vs. the plugin.** For a single account, you can instead install the
|
|
114
|
+
> published [Hiveku Codex plugin](https://github.com/locus2012/hiveku-codex-plugin)
|
|
115
|
+
> once (`codex plugin marketplace add locus2012/hiveku-codex-plugin`) and set
|
|
116
|
+
> `HIVEKU_TOKEN`. `hiveku-sync --codex` is the self-contained, per-account route —
|
|
117
|
+
> better for multi-account / CI / power-user setups, and it also brings the
|
|
118
|
+
> account's own knowledge. Use one route per folder, not both.
|
|
119
|
+
|
|
120
|
+
## Coder projects — pull, ship, deploy
|
|
121
|
+
|
|
122
|
+
Beyond memory/rules/skills, `hiveku-sync` can drive a Hiveku **website project**
|
|
123
|
+
end-to-end from the terminal — the same push/deploy loop the VS Code extension's
|
|
124
|
+
Source Control gives you, but headless (Claude Code in a terminal, CI, a cron).
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
hiveku-sync projects # list the account's coder projects
|
|
128
|
+
hiveku-sync pull-project <slug-or-id> # → ./projects/<slug>/ (+ .hiveku/projects.json)
|
|
129
|
+
|
|
130
|
+
cd projects/<slug>
|
|
131
|
+
hiveku-sync pull-env # secrets → .env.local (git-ignored)
|
|
132
|
+
# ...edit files, run the dev server...
|
|
133
|
+
hiveku-sync status # diff local vs the deploy source
|
|
134
|
+
hiveku-sync push # save changes to the deploy source
|
|
135
|
+
hiveku-sync push --deploy development --wait # ...then deploy and wait
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**`push` writes the deploy source, lane-routed.** This is the important part.
|
|
139
|
+
`deploy_site` builds from `builder_code_versions` (+ the S3/CDN asset store) —
|
|
140
|
+
**not** a GitHub branch. So `push`:
|
|
141
|
+
|
|
142
|
+
- routes **CDN-servable binary** (images/fonts/video under `public/<subdir>/`)
|
|
143
|
+
to `assets_upload` (S3 → CloudFront). A binary put in `builder_code_versions`
|
|
144
|
+
renders in the Fly preview but **vanishes on deploy** (the build excludes it) —
|
|
145
|
+
`push` never makes that mistake.
|
|
146
|
+
- routes everything else (code, config, text-under-public) to
|
|
147
|
+
`project_files_bulk_save` in small, per-batch-verified batches (transport-safe
|
|
148
|
+
vs one giant call).
|
|
149
|
+
- diffs correctly: code via a single `project_files_status` call (so
|
|
150
|
+
`only_remote` stays reliable — chunking it produces phantom deletions), assets
|
|
151
|
+
via `assets_list` (so unchanged images aren't re-uploaded every push).
|
|
152
|
+
|
|
153
|
+
Flags: `--include-deletes` (remove code paths missing locally), `--no-assets` /
|
|
154
|
+
`--assets-all`, `--commit "msg"` (also record a Hiveku-native `main` commit),
|
|
155
|
+
`--deploy <env> [--wait]`, `--dry-run`.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
hiveku-sync deploy <development|staging|production> [<slug>] [--wait]
|
|
159
|
+
hiveku-sync pull-env [<slug>] [--target development|staging|production]
|
|
160
|
+
hiveku-sync commit -m "msg" # legacy GitHub-branch commit (GitHub-connected projects only)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`pull-env` applies the same `_DEV` / `_PROD` / `_STAGING` suffix resolution the
|
|
164
|
+
preview machine uses: for `development` it strips `_DEV` to the base key and
|
|
165
|
+
skips `_PROD`/`_STAGING`; `--target production` does the inverse.
|
|
166
|
+
|
|
81
167
|
## Safety
|
|
82
168
|
|
|
83
169
|
- **Manifest pin** — `.hiveku/manifest.json` records the API key preview.
|
|
@@ -91,7 +177,7 @@ in your editor to switch.
|
|
|
91
177
|
|
|
92
178
|
## How writes work
|
|
93
179
|
|
|
94
|
-
`hiveku-sync`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
refresh the local copies.
|
|
180
|
+
For **projects**, `hiveku-sync push` / `deploy` write straight back to Hiveku
|
|
181
|
+
(see "Coder projects" above). For **memory/rules/skills**, `pull` is read-only —
|
|
182
|
+
persist changes with the `memory_create` / `memory_update` / `memory_delete` MCP
|
|
183
|
+
tools through Claude, then `hiveku-sync pull` to refresh the local copies.
|
package/bin/hiveku-sync.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { runInit } from '../src/init.mjs';
|
|
12
|
+
import { runCodex } from '../src/init.mjs';
|
|
12
13
|
import { runPull } from '../src/pull.mjs';
|
|
13
14
|
import {
|
|
14
15
|
runProjectsList,
|
|
@@ -16,20 +17,39 @@ import {
|
|
|
16
17
|
runProjectStatus,
|
|
17
18
|
runProjectPush,
|
|
18
19
|
} from '../src/projects.mjs';
|
|
20
|
+
import { runPush, runDeploy, runPullEnv } from '../src/push.mjs';
|
|
21
|
+
import { pathToFileURL } from 'node:url';
|
|
19
22
|
|
|
20
|
-
function parseFlags(argv) {
|
|
23
|
+
export function parseFlags(argv) {
|
|
21
24
|
const positional = [];
|
|
22
25
|
const flags = {};
|
|
23
|
-
// Short flags that take
|
|
26
|
+
// Short flags that take the NEXT arg as their value.
|
|
24
27
|
const VALUE_SHORT = new Set(['m']);
|
|
28
|
+
// Long flags that take the next arg as their value when not given with `=`.
|
|
29
|
+
const VALUE_LONG = new Set([
|
|
30
|
+
'message',
|
|
31
|
+
'commit',
|
|
32
|
+
'deploy',
|
|
33
|
+
'target',
|
|
34
|
+
'branch',
|
|
35
|
+
'profile',
|
|
36
|
+
'parent',
|
|
37
|
+
]);
|
|
25
38
|
for (let i = 0; i < argv.length; i++) {
|
|
26
39
|
const arg = argv[i];
|
|
27
40
|
if (arg.startsWith('--')) {
|
|
28
41
|
const eq = arg.indexOf('=');
|
|
29
|
-
if (eq
|
|
30
|
-
flags[arg.slice(2)] = true;
|
|
31
|
-
} else {
|
|
42
|
+
if (eq !== -1) {
|
|
32
43
|
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
44
|
+
} else {
|
|
45
|
+
const key = arg.slice(2);
|
|
46
|
+
const nextIsValue = i + 1 < argv.length && !argv[i + 1].startsWith('-');
|
|
47
|
+
if (VALUE_LONG.has(key) && nextIsValue) {
|
|
48
|
+
flags[key] = argv[i + 1];
|
|
49
|
+
i += 1;
|
|
50
|
+
} else {
|
|
51
|
+
flags[key] = true;
|
|
52
|
+
}
|
|
33
53
|
}
|
|
34
54
|
} else if (arg.startsWith('-') && arg.length > 1) {
|
|
35
55
|
const key = arg.slice(1);
|
|
@@ -50,27 +70,64 @@ const HELP = `hiveku-sync — sync a Hiveku account into a local Claude Code fol
|
|
|
50
70
|
|
|
51
71
|
Memory/rules/skills:
|
|
52
72
|
init <slug> [--profile=<name>] Scaffold ./<slug>/ with .env, .mcp.json,
|
|
53
|
-
|
|
73
|
+
[--codex] [--trust] CLAUDE.md, and the memory/rules/skills tree.
|
|
74
|
+
--codex also writes the Codex lane (AGENTS.md
|
|
75
|
+
+ .codex/config.toml + skills); --trust
|
|
76
|
+
pre-trusts the folder in ~/.codex/config.toml.
|
|
54
77
|
pull [--profile=<name>] Pull memory/rules/skills via MCP into the
|
|
55
78
|
current account folder. Reads OLYMPUS_API_KEY
|
|
56
79
|
and OLYMPUS_BASE_URL from ./.env.
|
|
80
|
+
codex [<folder>] [--trust] Retrofit an existing account folder with the
|
|
81
|
+
Codex lane (AGENTS.md + .codex/config.toml +
|
|
82
|
+
mirror the account's skills/commands to
|
|
83
|
+
.agents/skills/). Run after 'pull' to include
|
|
84
|
+
the latest knowledge. Complements the published
|
|
85
|
+
Hiveku Codex plugin (single-account users can
|
|
86
|
+
instead run: codex plugin marketplace add
|
|
87
|
+
locus2012/hiveku-codex-plugin).
|
|
57
88
|
|
|
58
89
|
Coder projects:
|
|
59
90
|
projects List the account's coder projects.
|
|
60
91
|
pull-project <slug-or-id> Pull a project's source into ./projects/<slug>/.
|
|
61
92
|
[--branch=<name>] Records the project mapping in .hiveku/projects.json.
|
|
62
|
-
status [<slug>] [--json] Diff local ./projects/<slug>/ vs
|
|
63
|
-
cd
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
status [<slug>] [--json] Diff local ./projects/<slug>/ vs the deploy source
|
|
94
|
+
(builder_code_versions). cd in to omit <slug>.
|
|
95
|
+
|
|
96
|
+
Ship a project (the agent-native deploy loop):
|
|
97
|
+
push [<slug>] Save local changes to the DEPLOY SOURCE, lane-routed:
|
|
98
|
+
[--include-deletes] images/fonts/video under public/<subdir>/ → the
|
|
99
|
+
[--no-assets] [--assets-all] S3/CDN asset store; everything else → batched,
|
|
100
|
+
[--commit "<msg>"] verified bulk_save. This is what deploy reads.
|
|
101
|
+
[--deploy <env>] [--wait] --commit also records a Hiveku 'main' commit.
|
|
102
|
+
[--dry-run] [--branch=<name>] --deploy <env> [--wait] deploys after the push.
|
|
103
|
+
deploy <env> [<slug>] [--wait] Deploy to development | staging | production and
|
|
104
|
+
(with --wait) poll to a terminal status.
|
|
105
|
+
pull-env [<slug>] Write project secrets → .env.local (git-ignored),
|
|
106
|
+
[--target <env>] with the same _DEV/_PROD/_STAGING resolution the
|
|
107
|
+
preview uses. --target staging|production for the
|
|
108
|
+
other tiers.
|
|
109
|
+
commit [<slug>] -m "<msg>" GitHub-branch commit via project_commit (only for
|
|
110
|
+
[--branch=<name>] GitHub-connected projects; most sites use push).
|
|
111
|
+
[--include-deletes]
|
|
68
112
|
|
|
69
113
|
help Show this message.
|
|
70
114
|
|
|
71
115
|
Profiles: full, sales, marketing, helpdesk, pm, dev
|
|
72
116
|
`;
|
|
73
117
|
|
|
118
|
+
function commitMessageFrom(flags) {
|
|
119
|
+
// --commit "msg" | --commit=msg | -m "msg" | --message "msg". A bare --commit
|
|
120
|
+
// (boolean true) means "commit with no message" → fall back to the default.
|
|
121
|
+
const c = typeof flags['commit'] === 'string' ? flags['commit'] : undefined;
|
|
122
|
+
const m = typeof flags['m'] === 'string' ? flags['m'] : undefined;
|
|
123
|
+
const msg = typeof flags['message'] === 'string' ? flags['message'] : undefined;
|
|
124
|
+
if (c) return c;
|
|
125
|
+
if (m) return m;
|
|
126
|
+
if (msg) return msg;
|
|
127
|
+
if (flags['commit'] === true) return 'hiveku-sync: push';
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
74
131
|
async function main() {
|
|
75
132
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
76
133
|
const { positional, flags } = parseFlags(rest);
|
|
@@ -87,6 +144,8 @@ async function main() {
|
|
|
87
144
|
slug,
|
|
88
145
|
parentDir: flags['parent'],
|
|
89
146
|
profile: flags['profile'] || 'full',
|
|
147
|
+
codex: !!flags['codex'],
|
|
148
|
+
trust: !!flags['trust'],
|
|
90
149
|
});
|
|
91
150
|
console.log(`Initialized ${result.folder}`);
|
|
92
151
|
console.log(` profile: ${result.profile}`);
|
|
@@ -96,9 +155,33 @@ async function main() {
|
|
|
96
155
|
if (result.workspaceAdded) {
|
|
97
156
|
console.log(` workspace: added entry to hiveku.code-workspace`);
|
|
98
157
|
}
|
|
158
|
+
if (result.codex) {
|
|
159
|
+
console.log(` codex: wrote AGENTS.md + .codex/config.toml (git-ignored)`);
|
|
160
|
+
console.log(
|
|
161
|
+
result.codex.inlinedKey
|
|
162
|
+
? ` MCP auth: inlined your OLYMPUS_API_KEY`
|
|
163
|
+
: ` 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=...\``,
|
|
164
|
+
);
|
|
165
|
+
if (result.codex.trust === 'added') console.log(` trust: pre-trusted this folder in ~/.codex/config.toml`);
|
|
166
|
+
}
|
|
99
167
|
console.log(` then: cd ${result.folder} && hiveku-sync pull`);
|
|
100
168
|
break;
|
|
101
169
|
}
|
|
170
|
+
case 'codex': {
|
|
171
|
+
const result = await runCodex({ folder: positional[0], trust: !!flags['trust'] });
|
|
172
|
+
console.log(`Codex lane ready in ${result.folder} (profile=${result.profile})`);
|
|
173
|
+
console.log(` AGENTS.md + .codex/config.toml written (git-ignored).`);
|
|
174
|
+
console.log(
|
|
175
|
+
result.inlinedKey
|
|
176
|
+
? ` MCP auth: inlined OLYMPUS_API_KEY from .env.`
|
|
177
|
+
: ` 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.`,
|
|
178
|
+
);
|
|
179
|
+
console.log(` skills: mirrored ${result.skillsMirrored} account skill/command entr${result.skillsMirrored === 1 ? 'y' : 'ies'} into .agents/skills/.`);
|
|
180
|
+
if (result.trust === 'added') console.log(` trust: pre-trusted this folder in ~/.codex/config.toml.`);
|
|
181
|
+
else if (result.trust === 'already') console.log(` trust: folder already trusted.`);
|
|
182
|
+
console.log(` verify: cd there, run \`codex mcp list\` (the hiveku row), then \`get_account_info\`.`);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
102
185
|
case 'pull': {
|
|
103
186
|
const summary = await runPull({ profile: flags['profile'] });
|
|
104
187
|
console.log(`Pulled ${summary.total} entries (profile=${summary.profile}, base=${summary.baseUrl}):`);
|
|
@@ -144,16 +227,55 @@ async function main() {
|
|
|
144
227
|
break;
|
|
145
228
|
}
|
|
146
229
|
case 'push': {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
230
|
+
const deployEnv = typeof flags['deploy'] === 'string' ? flags['deploy'] : undefined;
|
|
231
|
+
await runPush({
|
|
232
|
+
slug: positional[0],
|
|
233
|
+
profile: flags['profile'],
|
|
234
|
+
commit: commitMessageFrom(flags),
|
|
235
|
+
deploy: deployEnv,
|
|
236
|
+
wait: !!flags['wait'],
|
|
237
|
+
includeDeletes: !!flags['include-deletes'],
|
|
238
|
+
noAssets: !!flags['no-assets'],
|
|
239
|
+
assetsAll: !!flags['assets-all'],
|
|
240
|
+
dryRun: !!flags['dry-run'],
|
|
241
|
+
branch: flags['branch'],
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case 'deploy': {
|
|
246
|
+
// `deploy <env> [<slug>]` — env is the first positional.
|
|
247
|
+
const environment = positional[0];
|
|
248
|
+
if (!environment) {
|
|
249
|
+
console.error('Error: deploy requires an environment: development | staging | production\n');
|
|
250
|
+
console.error(HELP);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
await runDeploy({
|
|
254
|
+
environment,
|
|
255
|
+
slugOrId: positional[1],
|
|
256
|
+
wait: !!flags['wait'],
|
|
257
|
+
profile: flags['profile'],
|
|
258
|
+
});
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'pull-env': {
|
|
262
|
+
await runPullEnv({
|
|
263
|
+
slug: positional[0],
|
|
264
|
+
profile: flags['profile'],
|
|
265
|
+
target: typeof flags['target'] === 'string' ? flags['target'] : 'development',
|
|
266
|
+
});
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
case 'commit': {
|
|
270
|
+
// The legacy GitHub-branch commit path (project_commit). Requires a repo.
|
|
271
|
+
const message = commitMessageFrom(flags);
|
|
272
|
+
if (!message || message === 'hiveku-sync: push') {
|
|
273
|
+
console.error('Error: commit requires -m "<commit message>"\n');
|
|
152
274
|
console.error(HELP);
|
|
153
275
|
process.exit(1);
|
|
154
276
|
}
|
|
155
277
|
await runProjectPush({
|
|
156
|
-
slug,
|
|
278
|
+
slug: positional[0],
|
|
157
279
|
message,
|
|
158
280
|
branch: flags['branch'],
|
|
159
281
|
profile: flags['profile'],
|
|
@@ -175,8 +297,14 @@ async function main() {
|
|
|
175
297
|
}
|
|
176
298
|
}
|
|
177
299
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
process.
|
|
182
|
-
|
|
300
|
+
// Only run the CLI when invoked directly — importing this module (e.g. from
|
|
301
|
+
// tests to reach parseFlags) must not execute main().
|
|
302
|
+
const isEntrypoint =
|
|
303
|
+
process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
304
|
+
if (isEntrypoint) {
|
|
305
|
+
main().catch((err) => {
|
|
306
|
+
console.error(`hiveku-sync: ${err.message}`);
|
|
307
|
+
if (process.env.HIVEKU_SYNC_DEBUG) console.error(err.stack);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
});
|
|
310
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiveku-apps/sync",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sync a Hiveku account's memory/rules/skills + coder projects into a local folder. Pairs with the Hiveku MCP at core.hiveku.com/mcp so Claude Code can drive Hiveku tools per-folder.",
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
7
7
|
"author": "Hiveku",
|
|
8
8
|
"homepage": "https://app.hiveku.com",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/locus2012/hiveku-sync.git"
|
|
11
|
+
"url": "git+https://github.com/locus2012/hiveku-sync.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/locus2012/hiveku-sync/issues"
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"claude",
|
|
20
20
|
"claude-code",
|
|
21
21
|
"cli",
|
|
22
|
-
"olympus"
|
|
22
|
+
"olympus",
|
|
23
|
+
"codex"
|
|
23
24
|
],
|
|
24
25
|
"bin": {
|
|
25
26
|
"hiveku-sync": "bin/hiveku-sync.mjs"
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
|
-
"start": "node bin/hiveku-sync.mjs"
|
|
40
|
+
"start": "node bin/hiveku-sync.mjs",
|
|
41
|
+
"test": "node --test 'test/*.test.mjs'"
|
|
40
42
|
}
|
|
41
43
|
}
|
package/src/codex.mjs
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex (OpenAI) support for hiveku-sync — the Codex-native equivalents of the
|
|
3
|
+
* Claude Code artifacts, so a Hiveku account folder works identically from the
|
|
4
|
+
* Codex CLI, the Codex IDE extension, and the ChatGPT desktop app (all three
|
|
5
|
+
* read the same files). Opt in with `--codex` on `init`, or run
|
|
6
|
+
* `hiveku-sync codex` in an existing folder.
|
|
7
|
+
*
|
|
8
|
+
* Writes, merge-safely (only our marked regions are rewritten; user content is
|
|
9
|
+
* preserved):
|
|
10
|
+
* CLAUDE.md → AGENTS.md (lean pointer + load-bearing rules; Codex
|
|
11
|
+
* concatenates AGENTS.md with a ~32KiB cap, so the
|
|
12
|
+
* full guide stays in CLAUDE.md and AGENTS.md says to
|
|
13
|
+
* read it)
|
|
14
|
+
* .mcp.json → .codex/config.toml [mcp_servers.*] (the SAME
|
|
15
|
+
* core.hiveku.com/mcp server + the SAME OLYMPUS_API_KEY;
|
|
16
|
+
* inlined when we can resolve the key, else read from
|
|
17
|
+
* the OLYMPUS_API_KEY env var). git-ignored.
|
|
18
|
+
* skill/command knowledge → .agents/skills/<name>/SKILL.md (repo-scoped; Codex
|
|
19
|
+
* prompts are global-only + deprecated)
|
|
20
|
+
* permissions → NO per-repo equivalent (Codex ignores approval/
|
|
21
|
+
* sandbox keys in repo config) — stated in AGENTS.md.
|
|
22
|
+
*
|
|
23
|
+
* This complements the published Hiveku Codex plugin
|
|
24
|
+
* (github.com/locus2012/hiveku-codex-plugin): the plugin ships the generic
|
|
25
|
+
* `hiveku` MCP server + doctrine skills for single-account users who install it
|
|
26
|
+
* once and set HIVEKU_TOKEN; this writes a SELF-CONTAINED per-account folder
|
|
27
|
+
* (its own MCP config + that account's own knowledge) for multi-account / CI /
|
|
28
|
+
* power-user setups. Use one route or the other per folder, not both.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
|
|
32
|
+
import { existsSync } from 'node:fs';
|
|
33
|
+
import { homedir } from 'node:os';
|
|
34
|
+
import { join, resolve, dirname } from 'node:path';
|
|
35
|
+
|
|
36
|
+
import { loadDotenv } from './util.mjs';
|
|
37
|
+
|
|
38
|
+
const AGENTS_BEGIN = '<!-- hiveku:begin -->';
|
|
39
|
+
const AGENTS_END = '<!-- hiveku:end -->';
|
|
40
|
+
const TOML_BEGIN = '# hiveku:begin (managed by hiveku-sync — do not edit inside)';
|
|
41
|
+
const TOML_END = '# hiveku:end';
|
|
42
|
+
const SKILL_STAMP = '<!-- generated-by: hiveku-sync (mirrored from account knowledge) -->';
|
|
43
|
+
|
|
44
|
+
/** Replace (or append) the marked region inside existing content. */
|
|
45
|
+
function upsertMarkedRegion(existing, begin, end, region) {
|
|
46
|
+
const b = existing.indexOf(begin);
|
|
47
|
+
const e = existing.indexOf(end);
|
|
48
|
+
if (b !== -1 && e !== -1 && e > b) {
|
|
49
|
+
return existing.slice(0, b) + region + existing.slice(e + end.length);
|
|
50
|
+
}
|
|
51
|
+
const sep = existing.length && !existing.endsWith('\n\n') ? (existing.endsWith('\n') ? '\n' : '\n\n') : '';
|
|
52
|
+
return existing + sep + region + '\n';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** AGENTS.md — lean on purpose (Codex caps the instruction chain at ~32KiB). */
|
|
56
|
+
function agentsMdRegion({ slug }) {
|
|
57
|
+
return `${AGENTS_BEGIN}
|
|
58
|
+
# Hiveku — ${slug}
|
|
59
|
+
|
|
60
|
+
This folder is ONE Hiveku account (${slug}). Its MCP server \`hiveku\` (configured in
|
|
61
|
+
\`.codex/config.toml\`) carries this account's API key — never operate a different account from here.
|
|
62
|
+
|
|
63
|
+
**FIRST ACTION every session: read \`CLAUDE.md\` in this folder.** It is the full operating guide
|
|
64
|
+
(written for Claude Code, applies to you identically): account tools, department data, project
|
|
65
|
+
push/deploy flows, and the work-tracking rules. Ignore only its Claude-specific file paths
|
|
66
|
+
(\`.claude/*\`) — your equivalents are \`.agents/skills/*\` (the account's mirrored skills/commands,
|
|
67
|
+
invoked by name) and this file.
|
|
68
|
+
|
|
69
|
+
Non-negotiables (also in CLAUDE.md, restated because they are load-bearing):
|
|
70
|
+
- **Verify identity before ANY write:** \`get_account_info\` must return THIS account.
|
|
71
|
+
- **You are NOT the only writer.** Others push to these same projects while you work. Check what is
|
|
72
|
+
current BEFORE you start (\`project_version_log\`) and AGAIN before you push (\`project_files_status\`
|
|
73
|
+
— \`changed\` = they edited it, \`only_remote\` = they added files you lack). Before any tree-replace
|
|
74
|
+
(\`delete_missing: true\`), \`dry_run\` first and READ the would-delete list — a file you did not send
|
|
75
|
+
may be someone else's NEW work. Never blind-overwrite.
|
|
76
|
+
- **Keep ALL scratch work in \`.hiveku/tmp/\`** — never \`/tmp\` (shared across accounts), your home
|
|
77
|
+
dir, or the repo root.
|
|
78
|
+
- **Never ingest local agent config into a project push.** \`.codex/config.toml\`, \`.mcp.json\`, and
|
|
79
|
+
\`.env*\` carry this account's key/secrets — exclude them from every tar/bulk-save. Secrets belong in
|
|
80
|
+
\`project_secrets_*\`, never in project code. Never read or print \`.env.local\`.
|
|
81
|
+
- **PM tasks are required** — create one when you start, comment as you go, complete it when done,
|
|
82
|
+
attributed to the authenticated user (\`crm_list_users\`).
|
|
83
|
+
- **Every completed task ends with an "Owner update"** — 2–4 calm, plain-language sentences a busy owner
|
|
84
|
+
can skim: benefit first, no alarm vocabulary, accurate.
|
|
85
|
+
- Video generation is paid + capped — \`marketing_generate_video\` with \`dry_run: true\` first.
|
|
86
|
+
|
|
87
|
+
Discover tools with \`hiveku_docs_search\` / \`hiveku_docs_get\` and \`hiveku_playbooks_list\` /
|
|
88
|
+
\`hiveku_playbook_get\`. Codex ignores repo-level approval/sandbox config, so the rules above are
|
|
89
|
+
instructions, not enforced permissions — follow them.
|
|
90
|
+
${AGENTS_END}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The [mcp_servers.*] region for <folder>/.codex/config.toml. */
|
|
94
|
+
function codexTomlRegion({ baseUrl, profile, apiKey }) {
|
|
95
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
96
|
+
const url = profile === 'full' ? `${base}/mcp` : `${base}/mcp?profile=${encodeURIComponent(profile)}`;
|
|
97
|
+
// Prefer an inlined key (self-contained, no env setup needed; the file is
|
|
98
|
+
// git-ignored). Fall back to the OLYMPUS_API_KEY env var when we can't
|
|
99
|
+
// resolve a real key at scaffold time (e.g. `init` before the user pastes it).
|
|
100
|
+
const auth = apiKey
|
|
101
|
+
? `http_headers = { "Authorization" = "Bearer ${apiKey}" }`
|
|
102
|
+
: `bearer_token_env_var = "OLYMPUS_API_KEY"`;
|
|
103
|
+
return `${TOML_BEGIN}
|
|
104
|
+
[mcp_servers.hiveku]
|
|
105
|
+
url = "${url}"
|
|
106
|
+
${auth}
|
|
107
|
+
|
|
108
|
+
[mcp_servers.playwright]
|
|
109
|
+
command = "npx"
|
|
110
|
+
args = ["-y", "@playwright/mcp@latest"]
|
|
111
|
+
${TOML_END}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Add lines to .gitignore when missing (non-destructive). */
|
|
115
|
+
async function appendGitignoreLines(folder, lines) {
|
|
116
|
+
const giPath = join(folder, '.gitignore');
|
|
117
|
+
let gi = '';
|
|
118
|
+
try {
|
|
119
|
+
gi = await readFile(giPath, 'utf8');
|
|
120
|
+
} catch {
|
|
121
|
+
/* new file */
|
|
122
|
+
}
|
|
123
|
+
const have = new Set(gi.split('\n').map((l) => l.trim()));
|
|
124
|
+
const missing = lines.filter((l) => !have.has(l));
|
|
125
|
+
if (missing.length === 0) return;
|
|
126
|
+
const sep = gi.length && !gi.endsWith('\n') ? '\n' : '';
|
|
127
|
+
await writeFile(giPath, gi + sep + missing.join('\n') + '\n', 'utf8');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Mirror the account's pulled skill + command knowledge into repo-scoped Codex
|
|
132
|
+
* skills at .agents/skills/<name>/SKILL.md. Reads the folders `hiveku-sync pull`
|
|
133
|
+
* writes (skills/, commands/). Idempotent: a skill dir without our stamp is
|
|
134
|
+
* left alone (user-authored). Returns the count written.
|
|
135
|
+
*/
|
|
136
|
+
export async function mirrorKnowledgeToSkills(folder) {
|
|
137
|
+
let written = 0;
|
|
138
|
+
for (const srcDir of ['skills', 'commands']) {
|
|
139
|
+
const root = join(folder, srcDir);
|
|
140
|
+
if (!existsSync(root)) continue;
|
|
141
|
+
// pull writes <type>/<department>/<slug>.md — walk one level of subdirs + top-level files.
|
|
142
|
+
const files = [];
|
|
143
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
144
|
+
if (entry.isDirectory()) {
|
|
145
|
+
for (const f of await readdir(join(root, entry.name))) {
|
|
146
|
+
if (f.endsWith('.md')) files.push(join(root, entry.name, f));
|
|
147
|
+
}
|
|
148
|
+
} else if (entry.name.endsWith('.md')) {
|
|
149
|
+
files.push(join(root, entry.name));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const filePath of files) {
|
|
153
|
+
const src = await readFile(filePath, 'utf8');
|
|
154
|
+
const base = filePath.split('/').pop().replace(/\.md$/, '');
|
|
155
|
+
const name = `hiveku-${srcDir === 'commands' ? 'cmd' : 'skill'}-${base}`.slice(0, 100);
|
|
156
|
+
let description = `Hiveku ${srcDir === 'commands' ? 'command' : 'skill'}: ${base}`;
|
|
157
|
+
let body = src;
|
|
158
|
+
const fm = src.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
159
|
+
if (fm) {
|
|
160
|
+
const d = fm[1].match(/^description:\s*(.+)$/m);
|
|
161
|
+
if (d) description = d[1].trim().replace(/^["']|["']$/g, '');
|
|
162
|
+
body = src.slice(fm[0].length);
|
|
163
|
+
}
|
|
164
|
+
body = body.replace(/\$ARGUMENTS/g, '(the arguments the user gave after the skill name)');
|
|
165
|
+
const skillDir = join(folder, '.agents', 'skills', name);
|
|
166
|
+
const skillPath = join(skillDir, 'SKILL.md');
|
|
167
|
+
try {
|
|
168
|
+
const existing = await readFile(skillPath, 'utf8');
|
|
169
|
+
if (!existing.includes(SKILL_STAMP)) continue; // user-authored — never clobber
|
|
170
|
+
} catch {
|
|
171
|
+
/* new */
|
|
172
|
+
}
|
|
173
|
+
await mkdir(skillDir, { recursive: true });
|
|
174
|
+
const skill = `---\nname: ${name}\ndescription: ${JSON.stringify(description)}\n---\n${SKILL_STAMP}\n\n${body.trimStart()}`;
|
|
175
|
+
await writeFile(skillPath, skill, 'utf8');
|
|
176
|
+
written += 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return written;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Write/refresh the Codex scaffold for one account folder: AGENTS.md +
|
|
184
|
+
* .codex/config.toml (git-ignored) + mirror the account's knowledge to skills.
|
|
185
|
+
* Resolves OLYMPUS_API_KEY from the folder's .env / process env to inline the
|
|
186
|
+
* MCP auth when possible.
|
|
187
|
+
*/
|
|
188
|
+
export async function writeCodexScaffold({ folder, slug, baseUrl, profile }) {
|
|
189
|
+
const dotenv = await loadDotenv(join(folder, '.env'));
|
|
190
|
+
const apiKey =
|
|
191
|
+
process.env.OLYMPUS_API_KEY ||
|
|
192
|
+
(dotenv.OLYMPUS_API_KEY && dotenv.OLYMPUS_API_KEY !== 'olp_replace_me' ? dotenv.OLYMPUS_API_KEY : '');
|
|
193
|
+
|
|
194
|
+
// AGENTS.md (marker-managed)
|
|
195
|
+
const agentsPath = join(folder, 'AGENTS.md');
|
|
196
|
+
let agents = '';
|
|
197
|
+
try {
|
|
198
|
+
agents = await readFile(agentsPath, 'utf8');
|
|
199
|
+
} catch {
|
|
200
|
+
/* new */
|
|
201
|
+
}
|
|
202
|
+
await writeFile(agentsPath, upsertMarkedRegion(agents, AGENTS_BEGIN, AGENTS_END, agentsMdRegion({ slug })), 'utf8');
|
|
203
|
+
|
|
204
|
+
// .codex/config.toml (marker-managed region; rest of user's config untouched)
|
|
205
|
+
const tomlPath = join(folder, '.codex', 'config.toml');
|
|
206
|
+
let toml = '';
|
|
207
|
+
try {
|
|
208
|
+
toml = await readFile(tomlPath, 'utf8');
|
|
209
|
+
} catch {
|
|
210
|
+
/* new */
|
|
211
|
+
}
|
|
212
|
+
await mkdir(dirname(tomlPath), { recursive: true });
|
|
213
|
+
await writeFile(tomlPath, upsertMarkedRegion(toml, TOML_BEGIN, TOML_END, codexTomlRegion({ baseUrl, profile, apiKey })), 'utf8');
|
|
214
|
+
|
|
215
|
+
await appendGitignoreLines(folder, ['.codex/config.toml']);
|
|
216
|
+
|
|
217
|
+
const skills = await mirrorKnowledgeToSkills(folder);
|
|
218
|
+
return { inlinedKey: !!apiKey, skillsMirrored: skills };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Pre-trust a folder in the USER-GLOBAL ~/.codex/config.toml so its repo-level
|
|
223
|
+
* .codex/config.toml (which carries the MCP server + key) loads without Codex's
|
|
224
|
+
* first-run trust prompt. Crosses a security boundary — the caller must have
|
|
225
|
+
* explicit user intent (the `--trust` flag).
|
|
226
|
+
*/
|
|
227
|
+
export async function preTrustFolder(folder) {
|
|
228
|
+
const cfgPath = join(homedir(), '.codex', 'config.toml');
|
|
229
|
+
let cfg = '';
|
|
230
|
+
try {
|
|
231
|
+
cfg = await readFile(cfgPath, 'utf8');
|
|
232
|
+
} catch {
|
|
233
|
+
/* none yet */
|
|
234
|
+
}
|
|
235
|
+
const abs = resolve(folder);
|
|
236
|
+
const header = `[projects."${abs}"]`;
|
|
237
|
+
if (cfg.includes(header)) return 'already';
|
|
238
|
+
await mkdir(dirname(cfgPath), { recursive: true });
|
|
239
|
+
const sep = cfg.length && !cfg.endsWith('\n') ? '\n' : '';
|
|
240
|
+
await writeFile(cfgPath, `${cfg}${sep}\n${header}\ntrust_level = "trusted"\n`, 'utf8');
|
|
241
|
+
return 'added';
|
|
242
|
+
}
|