moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright 2026 Egzakta Group d.o.o. · waggle-os.ai
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Full license text: https://www.apache.org/licenses/LICENSE-2.0.txt

View File

@@ -0,0 +1,73 @@
# @waggle/hive-mind-hooks-codex-desktop
Silent-capture shim that wires **OpenAI Codex Desktop** lifecycle hooks into
[hive-mind](https://github.com/marolinik/hive-mind) frames. Codex Desktop shares
the **same `~/.codex/` config root** as the Codex CLI — there is no separate
codex-desktop config directory — so this package is a **thin re-export of
[`@waggle/hive-mind-hooks-codex`](../hive-mind-hooks-codex)**. Install / uninstall
/ verify behave **identically** and write into the same `~/.codex/hooks.json` +
pointer (`~/.codex/hive-mind-install.json`).
It exists as a distinct package so the dependency graph + OSS subtree-split see
the package boundary, and so the launcher's `hookPackageFor('codex-desktop')`
resolves to this package.
## Install
```bash
npx @waggle/hive-mind-hooks-codex-desktop install
# Windows / production: pin the CLI path
npx @waggle/hive-mind-hooks-codex-desktop install --cli-path "C:\\path\\to\\hive-mind-cli\\dist\\index.js"
```
The installer additively merges four hook groups into `~/.codex/hooks.json`
(creating the file if absent), preserving any existing hooks you have, and
writes a pointer + byte-identical backup so uninstall is exact. This is the
**same file** the Codex CLI shim writes — installing one is installing the other.
> **One-time trust step.** Non-managed Codex hooks require a one-time `/hooks`
> trust before they execute. After installing, run `/hooks` in Codex once to
> trust the hive-mind hooks.
```bash
npx @waggle/hive-mind-hooks-codex-desktop verify # smoke-check + surface admin lockdown
npx @waggle/hive-mind-hooks-codex-desktop uninstall # byte-identical restore (or remove if we created it)
```
`verify` also surfaces the admin lockdown `allow_managed_hooks_only = true`
(in `~/.codex/requirements.toml`), which suppresses user hooks so install would
silently no-op.
## Capture fidelity
| Lifecycle | Codex event | Status | Notes |
|---|---|---|---|
| SessionStart (recall + inject) | `SessionStart` | full | matcher `startup\|resume\|clear\|compact`; injects recalled frames as additional context |
| UserPromptSubmit (save temporary) | `UserPromptSubmit` | full | persists the prompt as a temporary frame |
| Stop (summarize + save) | `Stop` | full | reads `last_assistant_message` in the response fallback; saves an important/critical frame |
| PreCompact (compact memory) | `PreCompact` | full | carries `trigger` (`manual\|auto`); runs `cleanup_frames` before truncation |
**Full parity** with claude-code — no degraded or absent events. The only
non-silent step is the one-time `/hooks` trust step above (an install-UX step,
not a degraded event).
> **Runtime-verification note.** The **Codex CLI definitely fires** these hooks
> (the shared `~/.codex/hooks.json` is the CLI's own hook surface). Whether the
> Codex **Desktop App** honors the same `~/.codex/hooks.json` is **doc-asserted
> and still needs runtime verification on a real App install**. If you run the
> desktop App, confirm capture lands in your hive-mind frames after a session.
## How it works
This package re-exports the codex installer surface
(`export * from '@waggle/hive-mind-hooks-codex'`); the `codex-desktop-hooks` bin
delegates to the codex `install` / `uninstall` / `verify`. The compiled hook
scripts live in the codex package's `dist/hooks/` (resolved by the codex
installer's own module URL), so codex-desktop ships no duplicate hook bodies.
Each hook is a short-lived Node subprocess. Codex pipes the event JSON to the
hook's stdin; the hook shells to `hive-mind-cli` to recall or save frames, then
exits 0. **Fail-open:** if `hive-mind-cli` is unreachable or the payload is
malformed, the hook logs to stderr and exits 0 — Codex is never blocked.
License: Apache-2.0.

View File

@@ -0,0 +1,62 @@
{
"name": "@waggle/hive-mind-hooks-codex-desktop",
"version": "0.1.0",
"description": "OpenAI Codex Desktop silent capture shim for hive-mind. A thin re-export of @waggle/hive-mind-hooks-codex — Codex Desktop shares the same ~/.codex/ config root as the Codex CLI, so install / uninstall / verify behave identically and write into the same ~/.codex/hooks.json + pointer. Exists as a distinct package so the dependency graph + subtree-split see the boundary and the launcher resolves hookPackageFor('codex-desktop').",
"license": "Apache-2.0",
"type": "module",
"main": "dist/index.js",
"bin": {
"codex-desktop-hooks": "dist/bin/codex-desktop-hooks.js"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc --build",
"build:clean": "tsc --build --clean",
"typecheck": "tsc --build && tsc --noEmit -p tsconfig.test.json",
"test": "node ../../node_modules/vitest/vitest.mjs run --root ../.. --config vitest.config.ts packages/hive-mind-hooks-codex-desktop/tests",
"test:watch": "vitest"
},
"engines": {
"node": ">=20"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@waggle/hive-mind-hooks-codex": "*"
},
"peerDependencies": {
"@waggle/hive-mind-cli": "*"
},
"peerDependenciesMeta": {
"@waggle/hive-mind-cli": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/marolinik/waggle-os.git",
"directory": "packages/hive-mind-hooks-codex-desktop"
},
"homepage": "https://github.com/marolinik/waggle-os/tree/main/packages/hive-mind-hooks-codex-desktop#readme",
"bugs": {
"url": "https://github.com/marolinik/waggle-os/issues"
},
"author": "Egzakta Group d.o.o. <hello@egzakta.com> (https://egzakta.com)",
"keywords": [
"codex-desktop",
"codex",
"openai",
"hive-mind",
"memory",
"ai",
"hook",
"silent-capture"
],
"types": "dist/index.d.ts"
}

View File

@@ -0,0 +1,185 @@
#!/usr/bin/env node
/**
* `codex-desktop-hooks` — CLI entry for the @waggle/hive-mind-hooks-codex-desktop shim.
*
* codex-desktop-hooks install Patch ~/.codex/hooks.json (additive, create-if-missing).
* codex-desktop-hooks uninstall Restore the byte-identical pre-install state (or remove
* the hooks.json we created).
* codex-desktop-hooks verify Smoke-check the install + hive-mind-cli reachability.
*
* Codex Desktop shares the same `~/.codex/` config root as the Codex CLI, so
* this delegates to the codex installer (`@waggle/hive-mind-hooks-codex`) and
* behaves IDENTICALLY to `codex-hooks` — same `~/.codex/hooks.json`, same
* pointer (`~/.codex/hive-mind-install.json`), same compiled hook scripts (the
* codex package's `dist/hooks/`, resolved by the codex installer's own module
* URL).
*/
import {
install,
uninstall,
verify,
type InstallResult,
type UninstallResult,
type VerifyResult,
} from '@waggle/hive-mind-hooks-codex';
type ParsedArgs = {
command: 'install' | 'uninstall' | 'verify' | 'help';
flags: Record<string, string | boolean>;
};
function parseArgs(argv: readonly string[]): ParsedArgs {
const [first, ...rest] = argv;
const valid = ['install', 'uninstall', 'verify'] as const;
const command = first === '-h' || first === '--help' || first === undefined
? 'help'
: valid.includes(first as typeof valid[number]) ? first as typeof valid[number] : 'help';
const flags: Record<string, string | boolean> = {};
for (let i = 0; i < rest.length; i += 1) {
const arg = rest[i];
if (!arg.startsWith('--')) continue;
const eq = arg.indexOf('=');
if (eq !== -1) {
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
} else {
const next = rest[i + 1];
if (next && !next.startsWith('--')) {
flags[arg.slice(2)] = next;
i += 1;
} else {
flags[arg.slice(2)] = true;
}
}
}
return { command: command as ParsedArgs['command'], flags };
}
function printHelp(): void {
process.stdout.write([
'Usage: codex-desktop-hooks <command> [options]',
'',
'Codex Desktop shares ~/.codex/ with the Codex CLI; this delegates to',
'the @waggle/hive-mind-hooks-codex installer and behaves identically.',
'',
'Commands:',
' install Patch ~/.codex/hooks.json (additive, create-if-missing, with backup).',
' uninstall Restore the byte-identical pre-install hooks.json (or remove it if created).',
' verify Smoke-check the install + hive-mind-cli reachability.',
'',
'Options:',
' --help, -h Show this help.',
' --hooks-dir <PATH> Override compiled hooks directory (testing).',
' --hook-timeout <S> Override per-hook timeout in seconds (default 5).',
' --cli-path <PATH> Absolute path to the hive-mind-cli binary or its',
' compiled JS entry. Required on Windows (npm bin',
' is a .cmd shim) and recommended for production',
' installs. Threaded into every hook command.',
'',
'Repo: https://github.com/marolinik/waggle-os',
'',
].join('\n'));
}
function printInstallSummary(result: InstallResult): void {
const lines: string[] = [
'hive-mind/codex-desktop-hooks: install',
` - hooks.json: ${result.paths.configPath}`,
` - backup: ${result.backupPath ?? '(none — hooks.json created by us)'}`,
` - pointer: ${result.pointerPath}`,
` - added hooks: ${result.installedHooks.join(', ')}`,
` - cli path: ${result.cliPath ?? '(default — hive-mind-cli on PATH)'}`,
'',
'Shared config root:',
' Codex Desktop honors the same ~/.codex/hooks.json as the Codex CLI.',
'',
'One-time trust step required:',
' Run `/hooks` in Codex once to trust the hive-mind hooks before they execute.',
'',
'Note: the Codex CLI definitely fires these hooks. The desktop App honoring',
'hooks is doc-asserted and still needs runtime verification on a real App',
'install.',
'',
'Done. New Codex sessions will silently capture to hive-mind.',
'Run "codex-desktop-hooks verify" to inspect, "codex-desktop-hooks uninstall" to revert.',
'',
];
process.stdout.write(lines.join('\n'));
}
function printUninstallSummary(result: UninstallResult): void {
const lines: string[] = [
'hive-mind/codex-desktop-hooks: uninstall',
` - hooks.json: ${result.paths.configPath}`,
` - restored from: ${result.restoredFrom ?? '(none — removed file we created)'}`,
` - created removed: ${result.createdRemoved ? 'yes' : 'no'}`,
` - backup removed: ${result.backupRemoved ? 'yes' : 'no (kept on disk)'}`,
` - pointer removed: ${result.pointerRemoved ? 'yes' : 'no'}`,
'',
'Done. hooks.json is byte-identical to pre-install state (or removed if we created it).',
'',
];
process.stdout.write(lines.join('\n'));
}
function printVerifySummary(result: VerifyResult): void {
const lines: string[] = ['hive-mind/codex-desktop-hooks: verify'];
for (const c of result.checks) {
const tag = c.ok ? 'PASS' : 'FAIL';
const detail = c.detail ? `${c.detail}` : '';
lines.push(` [${tag}] ${c.name}${detail}`);
}
lines.push('');
lines.push(result.ok ? 'All checks passed.' : 'One or more checks failed.');
lines.push('');
process.stdout.write(lines.join('\n'));
}
async function main(): Promise<void> {
const { command, flags } = parseArgs(process.argv.slice(2));
if (command === 'help') {
printHelp();
return;
}
const hooksDir = typeof flags['hooks-dir'] === 'string' ? flags['hooks-dir'] : undefined;
const hookTimeoutRaw = flags['hook-timeout'];
const hookTimeoutSeconds = typeof hookTimeoutRaw === 'string'
? Number.parseInt(hookTimeoutRaw, 10) || undefined
: undefined;
const cliPath = typeof flags['cli-path'] === 'string' ? flags['cli-path'] : undefined;
const baseOpts = {
...(hooksDir ? { hooksDir } : {}),
};
try {
if (command === 'install') {
const installOpts = {
...baseOpts,
...(hookTimeoutSeconds !== undefined ? { hookTimeoutSeconds } : {}),
...(cliPath !== undefined ? { cliPath } : {}),
};
const result = await install(installOpts);
printInstallSummary(result);
return;
}
if (command === 'uninstall') {
const result = await uninstall(baseOpts);
printUninstallSummary(result);
return;
}
if (command === 'verify') {
const result = await verify(baseOpts);
printVerifySummary(result);
if (!result.ok) process.exit(1);
return;
}
} catch (err) {
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\n`);
process.exit(1);
}
}
void main();

View File

@@ -0,0 +1,17 @@
/**
* @waggle/hive-mind-hooks-codex-desktop — barrel export.
*
* Codex Desktop silent-capture shim for hive-mind. Codex Desktop shares the
* SAME `~/.codex/` config root as the Codex CLI — there is no separate
* codex-desktop config directory — so this package is a THIN RE-EXPORT of
* `@waggle/hive-mind-hooks-codex`. Install / uninstall / verify behave
* identically and write into the same `~/.codex/hooks.json` + pointer
* (`~/.codex/hive-mind-install.json`).
*
* It exists as a distinct package so the dependency graph + OSS subtree-split
* see the package boundary, and so the launcher's
* `hookPackageFor('codex-desktop')` resolves to this package. Most users
* invoke the `codex-desktop-hooks` bin (which delegates to the codex installer).
*/
export * from '@waggle/hive-mind-hooks-codex';

View File

@@ -0,0 +1,167 @@
import { describe, expect, it, afterEach } from 'vitest';
import { createHash } from 'node:crypto';
import { mkdtemp, mkdir, readFile, writeFile, rm } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
// codex-desktop is a THIN RE-EXPORT — install/uninstall/verify come from the
// codex package via `export * from '@waggle/hive-mind-hooks-codex'`. Importing
// them through the desktop barrel proves the re-export is wired and that the
// SAME installer writes the SAME ~/.codex/hooks.json + pointer as codex.
import { install, uninstall } from '../src/index.js';
import { install as codexInstall } from '@waggle/hive-mind-hooks-codex';
import { HIVE_MIND_MARKER } from '@waggle/hive-mind-hooks-codex';
interface TestEnv {
home: string;
hooksDir: string;
configPath: string;
pointerPath: string;
}
/** Codex Desktop shares ~/.codex/ — same config + pointer paths as the CLI. */
async function bootstrap(initial: Record<string, unknown> | undefined): Promise<TestEnv> {
const home = await mkdtemp(join(tmpdir(), 'hmcdxd-parity-'));
const codexDir = join(home, '.codex');
await mkdir(codexDir, { recursive: true });
const configPath = join(codexDir, 'hooks.json');
if (initial !== undefined) {
await writeFile(configPath, JSON.stringify(initial, null, 2) + '\n', 'utf-8');
}
const hooksDir = resolve(home, 'fake-dist', 'hooks');
await mkdir(hooksDir, { recursive: true });
return { home, hooksDir, configPath, pointerPath: join(codexDir, 'hive-mind-install.json') };
}
function sha256(s: string): string {
return createHash('sha256').update(s, 'utf-8').digest('hex');
}
describe('parity (codex-desktop ↔ codex)', () => {
let env: TestEnv;
afterEach(async () => {
if (env) await rm(env.home, { recursive: true, force: true });
});
// ── shared ~/.codex/ surface ──────────────────────────────────────────
it('installs into the SAME ~/.codex/hooks.json the Codex CLI uses', async () => {
env = await bootstrap(undefined);
expect(existsSync(env.configPath)).toBe(false);
const result = await install({ home: env.home, hooksDir: env.hooksDir });
// The config + pointer land at the shared ~/.codex/ paths.
expect(result.paths.configPath).toBe(env.configPath);
expect(result.pointerPath).toBe(env.pointerPath);
expect(existsSync(env.configPath)).toBe(true);
expect(existsSync(env.pointerPath)).toBe(true);
});
it('writes the hive group (marker) + all four lifecycle events', async () => {
env = await bootstrap(undefined);
await install({ home: env.home, hooksDir: env.hooksDir });
const after = JSON.parse(await readFile(env.configPath, 'utf-8')) as {
hooks: Record<string, Array<{ _hiveMindShim?: string }>>;
};
expect(Object.keys(after.hooks).sort()).toEqual(
['PreCompact', 'SessionStart', 'Stop', 'UserPromptSubmit'],
);
// Every group we added carries the codex hive marker.
for (const event of Object.keys(after.hooks)) {
expect(after.hooks[event][0]._hiveMindShim).toBe(HIVE_MIND_MARKER);
}
});
it('writes a pointer carrying the codex installed_hooks list', async () => {
env = await bootstrap(undefined);
const result = await install({ home: env.home, hooksDir: env.hooksDir });
const pointer = JSON.parse(await readFile(result.pointerPath, 'utf-8')) as Record<string, unknown>;
expect(pointer['installed_hooks']).toEqual(
['session-start', 'user-prompt-submit', 'stop', 'pre-compact'],
);
expect(pointer['config_path']).toBe(env.configPath);
});
// ── byte-for-byte identical to the codex installer ────────────────────
it('produces a byte-identical hooks.json to the codex installer (same inputs)', async () => {
const initial = {
hooks: {
SessionStart: [
{ matcher: 'startup', hooks: [{ type: 'command', command: 'node /existing/ctx.js' }] },
],
},
};
const fixedTs = '2026-04-28T10:30:45.123Z';
// Desktop install into env A.
env = await bootstrap(initial);
await install({
home: env.home,
hooksDir: env.hooksDir,
now: () => new Date(fixedTs),
});
const desktopConfig = await readFile(env.configPath, 'utf-8');
// Codex install into env B with IDENTICAL inputs — same hooksDir so the
// embedded hook command strings (which carry the absolute hooksDir path)
// match. Only the HOME differs (each install writes to its own ~/.codex/),
// and HOME does not appear in the emitted config.
const envB = await bootstrap(initial);
try {
await codexInstall({
home: envB.home,
hooksDir: env.hooksDir,
now: () => new Date(fixedTs),
});
const codexConfig = await readFile(envB.configPath, 'utf-8');
expect(sha256(desktopConfig)).toBe(sha256(codexConfig));
} finally {
await rm(envB.home, { recursive: true, force: true });
}
});
// ── reversibility: uninstall restores the shared file ─────────────────
it('uninstall removes the hooks.json we created and leaves NO orphan', async () => {
env = await bootstrap(undefined);
const result = await install({ home: env.home, hooksDir: env.hooksDir });
expect(result.createdByUs).toBe(true);
expect(existsSync(env.configPath)).toBe(true);
const u = await uninstall({ home: env.home, hooksDir: env.hooksDir });
expect(u.createdRemoved).toBe(true);
expect(u.restoredFrom).toBeNull();
expect(existsSync(env.configPath)).toBe(false);
expect(existsSync(env.pointerPath)).toBe(false);
});
it('uninstall restores a pre-existing hooks.json byte-identically', async () => {
const initial = {
schemaVersion: 1,
hooks: {
SessionStart: [
{ matcher: 'startup', hooks: [{ type: 'command', command: 'node /existing/ctx.js' }] },
],
},
};
env = await bootstrap(initial);
const preInstall = await readFile(env.configPath, 'utf-8');
const preHash = sha256(preInstall);
await install({ home: env.home, hooksDir: env.hooksDir });
const afterInstall = await readFile(env.configPath, 'utf-8');
expect(sha256(afterInstall)).not.toBe(preHash);
const u = await uninstall({ home: env.home, hooksDir: env.hooksDir });
expect(u.createdRemoved).toBe(false);
const afterUninstall = await readFile(env.configPath, 'utf-8');
expect(sha256(afterUninstall)).toBe(preHash);
expect(afterUninstall).toBe(preInstall);
// backup + pointer cleaned up.
expect(existsSync(env.pointerPath)).toBe(false);
});
});

View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src",
"tsBuildInfoFile": "./dist/.tsbuildinfo"
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "tests/**", "dist/**", "node_modules/**"],
"references": [
{ "path": "../hive-mind-hooks-codex" }
]
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"composite": false,
"declaration": false,
"declarationMap": false,
"sourceMap": false
},
"include": ["src/**/*.ts", "tests/**/*.ts"],
"exclude": ["dist/**", "node_modules/**"]
}