@hiveku-apps/sync 0.2.0 → 0.3.1
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 +40 -0
- package/bin/hiveku-sync.mjs +40 -1
- package/package.json +4 -3
- package/src/codex.mjs +256 -0
- package/src/init.mjs +36 -4
package/README.md
CHANGED
|
@@ -78,6 +78,46 @@ 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` then
|
|
116
|
+
> `codex plugin add hiveku@hiveku`) and set
|
|
117
|
+
> `HIVEKU_TOKEN`. `hiveku-sync --codex` is the self-contained, per-account route —
|
|
118
|
+
> better for multi-account / CI / power-user setups, and it also brings the
|
|
119
|
+
> account's own knowledge. Use one route per folder, not both.
|
|
120
|
+
|
|
81
121
|
## Coder projects — pull, ship, deploy
|
|
82
122
|
|
|
83
123
|
Beyond memory/rules/skills, `hiveku-sync` can drive a Hiveku **website project**
|
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,
|
|
@@ -69,10 +70,22 @@ const HELP = `hiveku-sync — sync a Hiveku account into a local Claude Code fol
|
|
|
69
70
|
|
|
70
71
|
Memory/rules/skills:
|
|
71
72
|
init <slug> [--profile=<name>] Scaffold ./<slug>/ with .env, .mcp.json,
|
|
72
|
-
|
|
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.
|
|
73
77
|
pull [--profile=<name>] Pull memory/rules/skills via MCP into the
|
|
74
78
|
current account folder. Reads OLYMPUS_API_KEY
|
|
75
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, then:
|
|
88
|
+
codex plugin add hiveku@hiveku).
|
|
76
89
|
|
|
77
90
|
Coder projects:
|
|
78
91
|
projects List the account's coder projects.
|
|
@@ -132,6 +145,8 @@ async function main() {
|
|
|
132
145
|
slug,
|
|
133
146
|
parentDir: flags['parent'],
|
|
134
147
|
profile: flags['profile'] || 'full',
|
|
148
|
+
codex: !!flags['codex'],
|
|
149
|
+
trust: !!flags['trust'],
|
|
135
150
|
});
|
|
136
151
|
console.log(`Initialized ${result.folder}`);
|
|
137
152
|
console.log(` profile: ${result.profile}`);
|
|
@@ -141,9 +156,33 @@ async function main() {
|
|
|
141
156
|
if (result.workspaceAdded) {
|
|
142
157
|
console.log(` workspace: added entry to hiveku.code-workspace`);
|
|
143
158
|
}
|
|
159
|
+
if (result.codex) {
|
|
160
|
+
console.log(` codex: wrote AGENTS.md + .codex/config.toml (git-ignored)`);
|
|
161
|
+
console.log(
|
|
162
|
+
result.codex.inlinedKey
|
|
163
|
+
? ` MCP auth: inlined your OLYMPUS_API_KEY`
|
|
164
|
+
: ` 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
|
+
);
|
|
166
|
+
if (result.codex.trust === 'added') console.log(` trust: pre-trusted this folder in ~/.codex/config.toml`);
|
|
167
|
+
}
|
|
144
168
|
console.log(` then: cd ${result.folder} && hiveku-sync pull`);
|
|
145
169
|
break;
|
|
146
170
|
}
|
|
171
|
+
case 'codex': {
|
|
172
|
+
const result = await runCodex({ folder: positional[0], trust: !!flags['trust'] });
|
|
173
|
+
console.log(`Codex lane ready in ${result.folder} (profile=${result.profile})`);
|
|
174
|
+
console.log(` AGENTS.md + .codex/config.toml written (git-ignored).`);
|
|
175
|
+
console.log(
|
|
176
|
+
result.inlinedKey
|
|
177
|
+
? ` MCP auth: inlined OLYMPUS_API_KEY from .env.`
|
|
178
|
+
: ` 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.`,
|
|
179
|
+
);
|
|
180
|
+
console.log(` skills: mirrored ${result.skillsMirrored} account skill/command entr${result.skillsMirrored === 1 ? 'y' : 'ies'} into .agents/skills/.`);
|
|
181
|
+
if (result.trust === 'added') console.log(` trust: pre-trusted this folder in ~/.codex/config.toml.`);
|
|
182
|
+
else if (result.trust === 'already') console.log(` trust: folder already trusted.`);
|
|
183
|
+
console.log(` verify: cd there, run \`codex mcp list\` (the hiveku row), then \`get_account_info\`.`);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
147
186
|
case 'pull': {
|
|
148
187
|
const summary = await runPull({ profile: flags['profile'] });
|
|
149
188
|
console.log(`Pulled ${summary.total} entries (profile=${summary.profile}, base=${summary.baseUrl}):`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiveku-apps/sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -8,7 +8,7 @@
|
|
|
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"
|
package/src/codex.mjs
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
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, safeSlug } 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
|
+
// default_tools_approval_mode = "approve": this folder exists so the agent
|
|
104
|
+
// can operate the account — per-call approval prompts would also auto-cancel
|
|
105
|
+
// every tool call in headless `codex exec` (CI/cron), this lane's core use.
|
|
106
|
+
// (Enum is approve|prompt; the docs' "auto" is not accepted by codex 0.131.)
|
|
107
|
+
return `${TOML_BEGIN}
|
|
108
|
+
[mcp_servers.hiveku]
|
|
109
|
+
url = "${url}"
|
|
110
|
+
${auth}
|
|
111
|
+
default_tools_approval_mode = "approve"
|
|
112
|
+
|
|
113
|
+
[mcp_servers.playwright]
|
|
114
|
+
command = "npx"
|
|
115
|
+
args = ["-y", "@playwright/mcp@latest"]
|
|
116
|
+
${TOML_END}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Add lines to .gitignore when missing (non-destructive). */
|
|
120
|
+
async function appendGitignoreLines(folder, lines) {
|
|
121
|
+
const giPath = join(folder, '.gitignore');
|
|
122
|
+
let gi = '';
|
|
123
|
+
try {
|
|
124
|
+
gi = await readFile(giPath, 'utf8');
|
|
125
|
+
} catch {
|
|
126
|
+
/* new file */
|
|
127
|
+
}
|
|
128
|
+
const have = new Set(gi.split('\n').map((l) => l.trim()));
|
|
129
|
+
const missing = lines.filter((l) => !have.has(l));
|
|
130
|
+
if (missing.length === 0) return;
|
|
131
|
+
const sep = gi.length && !gi.endsWith('\n') ? '\n' : '';
|
|
132
|
+
await writeFile(giPath, gi + sep + missing.join('\n') + '\n', 'utf8');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Mirror the account's pulled skill + command knowledge into repo-scoped Codex
|
|
137
|
+
* skills at .agents/skills/<name>/SKILL.md. Reads the folders `hiveku-sync pull`
|
|
138
|
+
* writes (skills/, commands/). Idempotent: a skill dir without our stamp is
|
|
139
|
+
* left alone (user-authored). Returns the count written.
|
|
140
|
+
*/
|
|
141
|
+
export async function mirrorKnowledgeToSkills(folder) {
|
|
142
|
+
let written = 0;
|
|
143
|
+
for (const srcDir of ['skills', 'commands']) {
|
|
144
|
+
const root = join(folder, srcDir);
|
|
145
|
+
if (!existsSync(root)) continue;
|
|
146
|
+
// pull writes FLAT <type>/<slug>.md (department lives in the frontmatter,
|
|
147
|
+
// not the path) — but tolerate one level of hand-made subdirs too. Nested
|
|
148
|
+
// entries are isFile-checked so a directory named `x.md` can't crash us.
|
|
149
|
+
const files = [];
|
|
150
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
151
|
+
if (entry.isDirectory()) {
|
|
152
|
+
for (const sub of await readdir(join(root, entry.name), { withFileTypes: true })) {
|
|
153
|
+
if (sub.isFile() && sub.name.endsWith('.md')) files.push(join(root, entry.name, sub.name));
|
|
154
|
+
}
|
|
155
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
156
|
+
files.push(join(root, entry.name));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const filePath of files) {
|
|
160
|
+
const src = await readFile(filePath, 'utf8');
|
|
161
|
+
const base = filePath.split('/').pop().replace(/\.md$/, '');
|
|
162
|
+
// safeSlug: pull-written names are already slugs, but a hand-authored
|
|
163
|
+
// file like "My notes: draft.md" must not produce invalid YAML or an
|
|
164
|
+
// invalid skill dir name.
|
|
165
|
+
const name = `hiveku-${srcDir === 'commands' ? 'cmd' : 'skill'}-${safeSlug(base)}`.slice(0, 100);
|
|
166
|
+
let description = `Hiveku ${srcDir === 'commands' ? 'command' : 'skill'}: ${base}`;
|
|
167
|
+
let body = src;
|
|
168
|
+
const fm = src.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
169
|
+
if (fm) {
|
|
170
|
+
const d = fm[1].match(/^description:\s*(.+)$/m);
|
|
171
|
+
if (d) description = d[1].trim().replace(/^["']|["']$/g, '');
|
|
172
|
+
body = src.slice(fm[0].length);
|
|
173
|
+
}
|
|
174
|
+
body = body.replace(/\$ARGUMENTS/g, '(the arguments the user gave after the skill name)');
|
|
175
|
+
const skillDir = join(folder, '.agents', 'skills', name);
|
|
176
|
+
const skillPath = join(skillDir, 'SKILL.md');
|
|
177
|
+
try {
|
|
178
|
+
const existing = await readFile(skillPath, 'utf8');
|
|
179
|
+
if (!existing.includes(SKILL_STAMP)) continue; // user-authored — never clobber
|
|
180
|
+
} catch {
|
|
181
|
+
/* new */
|
|
182
|
+
}
|
|
183
|
+
await mkdir(skillDir, { recursive: true });
|
|
184
|
+
const skill = `---\nname: ${name}\ndescription: ${JSON.stringify(description)}\n---\n${SKILL_STAMP}\n\n${body.trimStart()}`;
|
|
185
|
+
await writeFile(skillPath, skill, 'utf8');
|
|
186
|
+
written += 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return written;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Write/refresh the Codex scaffold for one account folder: AGENTS.md +
|
|
194
|
+
* .codex/config.toml (git-ignored) + mirror the account's knowledge to skills.
|
|
195
|
+
* Resolves OLYMPUS_API_KEY from the folder's .env / process env to inline the
|
|
196
|
+
* MCP auth when possible.
|
|
197
|
+
*/
|
|
198
|
+
export async function writeCodexScaffold({ folder, slug, baseUrl, profile }) {
|
|
199
|
+
// The FOLDER's .env wins over a process-level export: folder = tenant
|
|
200
|
+
// boundary, and a globally exported key for account A must never get
|
|
201
|
+
// inlined into account B's config during a retrofit. process.env is only a
|
|
202
|
+
// fallback for folders that keep no .env at all.
|
|
203
|
+
const dotenv = await loadDotenv(join(folder, '.env'));
|
|
204
|
+
const dotenvKey =
|
|
205
|
+
dotenv.OLYMPUS_API_KEY && dotenv.OLYMPUS_API_KEY !== 'olp_replace_me' ? dotenv.OLYMPUS_API_KEY : '';
|
|
206
|
+
const apiKey = dotenvKey || (existsSync(join(folder, '.env')) ? '' : process.env.OLYMPUS_API_KEY || '');
|
|
207
|
+
|
|
208
|
+
// AGENTS.md (marker-managed)
|
|
209
|
+
const agentsPath = join(folder, 'AGENTS.md');
|
|
210
|
+
let agents = '';
|
|
211
|
+
try {
|
|
212
|
+
agents = await readFile(agentsPath, 'utf8');
|
|
213
|
+
} catch {
|
|
214
|
+
/* new */
|
|
215
|
+
}
|
|
216
|
+
await writeFile(agentsPath, upsertMarkedRegion(agents, AGENTS_BEGIN, AGENTS_END, agentsMdRegion({ slug })), 'utf8');
|
|
217
|
+
|
|
218
|
+
// .codex/config.toml (marker-managed region; rest of user's config untouched)
|
|
219
|
+
const tomlPath = join(folder, '.codex', 'config.toml');
|
|
220
|
+
let toml = '';
|
|
221
|
+
try {
|
|
222
|
+
toml = await readFile(tomlPath, 'utf8');
|
|
223
|
+
} catch {
|
|
224
|
+
/* new */
|
|
225
|
+
}
|
|
226
|
+
await mkdir(dirname(tomlPath), { recursive: true });
|
|
227
|
+
await writeFile(tomlPath, upsertMarkedRegion(toml, TOML_BEGIN, TOML_END, codexTomlRegion({ baseUrl, profile, apiKey })), 'utf8');
|
|
228
|
+
|
|
229
|
+
await appendGitignoreLines(folder, ['.codex/config.toml']);
|
|
230
|
+
|
|
231
|
+
const skills = await mirrorKnowledgeToSkills(folder);
|
|
232
|
+
return { inlinedKey: !!apiKey, skillsMirrored: skills };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Pre-trust a folder in the USER-GLOBAL ~/.codex/config.toml so its repo-level
|
|
237
|
+
* .codex/config.toml (which carries the MCP server + key) loads without Codex's
|
|
238
|
+
* first-run trust prompt. Crosses a security boundary — the caller must have
|
|
239
|
+
* explicit user intent (the `--trust` flag).
|
|
240
|
+
*/
|
|
241
|
+
export async function preTrustFolder(folder) {
|
|
242
|
+
const cfgPath = join(homedir(), '.codex', 'config.toml');
|
|
243
|
+
let cfg = '';
|
|
244
|
+
try {
|
|
245
|
+
cfg = await readFile(cfgPath, 'utf8');
|
|
246
|
+
} catch {
|
|
247
|
+
/* none yet */
|
|
248
|
+
}
|
|
249
|
+
const abs = resolve(folder);
|
|
250
|
+
const header = `[projects."${abs}"]`;
|
|
251
|
+
if (cfg.includes(header)) return 'already';
|
|
252
|
+
await mkdir(dirname(cfgPath), { recursive: true });
|
|
253
|
+
const sep = cfg.length && !cfg.endsWith('\n') ? '\n' : '';
|
|
254
|
+
await writeFile(cfgPath, `${cfg}${sep}\n${header}\ntrust_level = "trusted"\n`, 'utf8');
|
|
255
|
+
return 'added';
|
|
256
|
+
}
|
package/src/init.mjs
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
|
-
import { dirname, join, resolve } from 'node:path';
|
|
10
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
11
11
|
|
|
12
|
-
import { writeFileAtomic, safeSlug, TYPE_TO_FOLDER } from './util.mjs';
|
|
12
|
+
import { writeFileAtomic, safeSlug, TYPE_TO_FOLDER, loadDotenv } from './util.mjs';
|
|
13
|
+
import { writeCodexScaffold, preTrustFolder } from './codex.mjs';
|
|
13
14
|
|
|
14
15
|
const VALID_PROFILES = ['full', 'sales', 'marketing', 'helpdesk', 'pm', 'dev'];
|
|
15
16
|
const DEFAULT_BASE_URL = 'https://core.hiveku.com';
|
|
@@ -148,7 +149,7 @@ async function appendToWorkspaceFile(workspaceFilePath, folderRelPath) {
|
|
|
148
149
|
return true;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
export async function runInit({ slug, parentDir, profile = 'full' }) {
|
|
152
|
+
export async function runInit({ slug, parentDir, profile = 'full', codex = false, trust = false }) {
|
|
152
153
|
if (!slug) throw new Error('slug is required, e.g. hiveku-sync init acme-corp');
|
|
153
154
|
if (!VALID_PROFILES.includes(profile)) {
|
|
154
155
|
throw new Error(
|
|
@@ -190,5 +191,36 @@ export async function runInit({ slug, parentDir, profile = 'full' }) {
|
|
|
190
191
|
`./${safe}`,
|
|
191
192
|
);
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
// Optional Codex lane: AGENTS.md + .codex/config.toml + knowledge→skills, so
|
|
195
|
+
// the same folder works from the Codex CLI / IDE / ChatGPT desktop too.
|
|
196
|
+
let codexResult = null;
|
|
197
|
+
if (codex) {
|
|
198
|
+
codexResult = await writeCodexScaffold({
|
|
199
|
+
folder,
|
|
200
|
+
slug: safe,
|
|
201
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
202
|
+
profile,
|
|
203
|
+
});
|
|
204
|
+
if (trust) codexResult.trust = await preTrustFolder(folder);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { folder, slug: safe, profile, workspaceAdded, codex: codexResult };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Retrofit an EXISTING account folder with the Codex lane (AGENTS.md +
|
|
212
|
+
* .codex/config.toml + knowledge→skills). Run from inside the folder, or pass
|
|
213
|
+
* one. Derives the account slug from the folder name and the profile from
|
|
214
|
+
* .env (HIVEKU_PROFILE), and inlines the real OLYMPUS_API_KEY when .env has it.
|
|
215
|
+
*/
|
|
216
|
+
export async function runCodex({ folder, trust = false } = {}) {
|
|
217
|
+
const target = folder ? resolve(folder) : process.cwd();
|
|
218
|
+
if (!existsSync(target)) throw new Error(`Folder not found: ${target}`);
|
|
219
|
+
const dotenv = await loadDotenv(join(target, '.env'));
|
|
220
|
+
const profile = dotenv.HIVEKU_PROFILE || 'full';
|
|
221
|
+
const baseUrl = dotenv.OLYMPUS_BASE_URL || DEFAULT_BASE_URL;
|
|
222
|
+
const slug = safeSlug(basename(target));
|
|
223
|
+
const result = await writeCodexScaffold({ folder: target, slug, baseUrl, profile });
|
|
224
|
+
if (trust) result.trust = await preTrustFolder(target);
|
|
225
|
+
return { folder: target, slug, profile, ...result };
|
|
194
226
|
}
|