@hiveku-apps/sync 0.3.0 → 0.3.2

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
@@ -112,7 +112,8 @@ approval/sandbox config, so the safe-work rules ship as instructions in
112
112
 
113
113
  > **This vs. the plugin.** For a single account, you can instead install the
114
114
  > published [Hiveku Codex plugin](https://github.com/locus2012/hiveku-codex-plugin)
115
- > once (`codex plugin marketplace add locus2012/hiveku-codex-plugin`) and set
115
+ > once (`codex plugin marketplace add locus2012/hiveku-codex-plugin` then
116
+ > `codex plugin add hiveku@hiveku`) and set
116
117
  > `HIVEKU_TOKEN`. `hiveku-sync --codex` is the self-contained, per-account route —
117
118
  > better for multi-account / CI / power-user setups, and it also brings the
118
119
  > account's own knowledge. Use one route per folder, not both.
@@ -84,7 +84,8 @@ Memory/rules/skills:
84
84
  the latest knowledge. Complements the published
85
85
  Hiveku Codex plugin (single-account users can
86
86
  instead run: codex plugin marketplace add
87
- locus2012/hiveku-codex-plugin).
87
+ locus2012/hiveku-codex-plugin, then:
88
+ codex plugin add hiveku@hiveku).
88
89
 
89
90
  Coder projects:
90
91
  projects List the account's coder projects.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiveku-apps/sync",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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",
package/src/codex.mjs CHANGED
@@ -33,7 +33,7 @@ import { existsSync } from 'node:fs';
33
33
  import { homedir } from 'node:os';
34
34
  import { join, resolve, dirname } from 'node:path';
35
35
 
36
- import { loadDotenv } from './util.mjs';
36
+ import { loadDotenv, safeSlug } from './util.mjs';
37
37
 
38
38
  const AGENTS_BEGIN = '<!-- hiveku:begin -->';
39
39
  const AGENTS_END = '<!-- hiveku:end -->';
@@ -100,10 +100,15 @@ function codexTomlRegion({ baseUrl, profile, apiKey }) {
100
100
  const auth = apiKey
101
101
  ? `http_headers = { "Authorization" = "Bearer ${apiKey}" }`
102
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.)
103
107
  return `${TOML_BEGIN}
104
108
  [mcp_servers.hiveku]
105
109
  url = "${url}"
106
110
  ${auth}
111
+ default_tools_approval_mode = "approve"
107
112
 
108
113
  [mcp_servers.playwright]
109
114
  command = "npx"
@@ -138,21 +143,26 @@ export async function mirrorKnowledgeToSkills(folder) {
138
143
  for (const srcDir of ['skills', 'commands']) {
139
144
  const root = join(folder, srcDir);
140
145
  if (!existsSync(root)) continue;
141
- // pull writes <type>/<department>/<slug>.md — walk one level of subdirs + top-level files.
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.
142
149
  const files = [];
143
150
  for (const entry of await readdir(root, { withFileTypes: true })) {
144
151
  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));
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));
147
154
  }
148
- } else if (entry.name.endsWith('.md')) {
155
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
149
156
  files.push(join(root, entry.name));
150
157
  }
151
158
  }
152
159
  for (const filePath of files) {
153
160
  const src = await readFile(filePath, 'utf8');
154
161
  const base = filePath.split('/').pop().replace(/\.md$/, '');
155
- const name = `hiveku-${srcDir === 'commands' ? 'cmd' : 'skill'}-${base}`.slice(0, 100);
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);
156
166
  let description = `Hiveku ${srcDir === 'commands' ? 'command' : 'skill'}: ${base}`;
157
167
  let body = src;
158
168
  const fm = src.match(/^---\n([\s\S]*?)\n---\n?/);
@@ -186,10 +196,14 @@ export async function mirrorKnowledgeToSkills(folder) {
186
196
  * MCP auth when possible.
187
197
  */
188
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.
189
203
  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 : '');
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 || '');
193
207
 
194
208
  // AGENTS.md (marker-managed)
195
209
  const agentsPath = join(folder, 'AGENTS.md');
@@ -212,7 +226,14 @@ export async function writeCodexScaffold({ folder, slug, baseUrl, profile }) {
212
226
  await mkdir(dirname(tomlPath), { recursive: true });
213
227
  await writeFile(tomlPath, upsertMarkedRegion(toml, TOML_BEGIN, TOML_END, codexTomlRegion({ baseUrl, profile, apiKey })), 'utf8');
214
228
 
215
- await appendGitignoreLines(folder, ['.codex/config.toml']);
229
+ // Gitignore EVERY credential-bearing file this lane depends on — not just the
230
+ // one we write. The retrofit reads .env for OLYMPUS_API_KEY to inline it, so a
231
+ // folder that had no .gitignore would otherwise end up with one that ignores
232
+ // .codex/config.toml while leaving the key-bearing .env tracked. (The breach
233
+ // entry point was a leaked credential; a scaffolder must never leave one
234
+ // git-trackable.) runInit already covers .env for fresh folders; this makes
235
+ // the retrofit path equally safe.
236
+ await appendGitignoreLines(folder, ['.codex/config.toml', '.env', '.env.local', '.hiveku/']);
216
237
 
217
238
  const skills = await mirrorKnowledgeToSkills(folder);
218
239
  return { inlinedKey: !!apiKey, skillsMirrored: skills };