Skip to content

Commit 7da25d9

Browse files
feat(install): add native OMP support
1 parent 27d5a39 commit 7da25d9

5 files changed

Lines changed: 325 additions & 6 deletions

File tree

INSTALL.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ If you want to install for one agent (or want to know exactly what command runs
4343
| **Claude Code** | `claude plugin marketplace add JuliusBrussee/caveman && claude plugin install caveman@caveman` | Yes |
4444
| **Gemini CLI** | `gemini extensions install https://github.com/JuliusBrussee/caveman` | Yes |
4545
| **opencode** | `node bin/install.js --only opencode` *(or `npx -y github:JuliusBrussee/caveman -- --only opencode`)* | Yes (plugin + AGENTS.md) |
46+
| **Oh My Pi (OMP)** | `npx -y github:JuliusBrussee/caveman -- --only omp` *(or `node bin/install.js --only omp` from a clone)* | Yes (native OMP plugin) |
4647
| **OpenClaw** | `npx -y github:JuliusBrussee/caveman -- --only openclaw` | Yes (workspace skill + SOUL.md) |
4748
| **Hermes Agent** | `npx -y github:JuliusBrussee/caveman -- --only hermes` *(or `node bin/install.js --only hermes` from a clone)* | Yes (native skills, enabled on load) |
4849
| **Codex CLI** | `npx skills add JuliusBrussee/caveman -a codex` | Per-session: `/caveman` |
@@ -125,7 +126,7 @@ Useful flags:
125126
| `--no-mcp-shrink` | Skip MCP-shrink registration. (Default.) |
126127
| `--with-hooks` / `--no-hooks` | Force-on or force-off the Claude Code hook installer. (Default: on.) |
127128
| `--skip-skills` | Don't run the npx-skills auto-detect fallback when nothing else matched. |
128-
| `--config-dir <path>` | Claude Code config dir for hook files + `settings.json`. **Does NOT scope** `claude plugin install`, `gemini extensions install`, opencode (`XDG_CONFIG_HOME`), or openclaw (`OPENCLAW_WORKSPACE`) — those use their own paths. Default: `$CLAUDE_CONFIG_DIR` or `~/.claude`. `~` is expanded. |
129+
| `--config-dir <path>` | Claude Code config dir for hook files + `settings.json`. **Does NOT scope** `claude plugin install`, `gemini extensions install`, OMP (`~/.omp/`), opencode (`XDG_CONFIG_HOME`), or openclaw (`OPENCLAW_WORKSPACE`) — those use their own paths. Default: `$CLAUDE_CONFIG_DIR` or `~/.claude`. `~` is expanded. |
129130
| `--non-interactive` | Never prompt; use defaults. (Auto when stdin is not a TTY.) |
130131
| `--no-color` | Disable ANSI colors. |
131132
| `--list` | Print full agent matrix and exit. |
@@ -186,6 +187,7 @@ What it removes:
186187
- Hook files in `$CLAUDE_CONFIG_DIR/hooks/` (`caveman-activate.js`, `caveman-mode-tracker.js`, `caveman-parse.js`, `caveman-stats.js`, `caveman-config.js`, `cavecrew-model-overrides.js`, `caveman-statusline.{sh,ps1}`, plus the dir's `package.json` marker).
187188
- The Claude Code plugin and the Gemini CLI extension (if installed).
188189
- The opencode native plugin (`~/.config/opencode/plugins/caveman/`, the `plugin` and `mcp.caveman-shrink` entries from `opencode.json`, our skill/agent/command files, the caveman block from `AGENTS.md`, and the opencode flag file).
190+
- The Oh My Pi plugin (`omp plugin uninstall caveman`) and Caveman's managed OMP plugin package at `~/.omp/caveman-plugin/`.
189191
- The OpenClaw workspace skill folder and the marker-fenced block from `~/.openclaw/workspace/SOUL.md` (when present).
190192
- Per-session state (`.caveman-active`, `.caveman-active.prev`, `.caveman-mode-log.jsonl`, `.caveman-statusline-suffix`, and `.caveman-nudge-shown`).
191193

@@ -254,9 +256,10 @@ The installer doesn't phone home. It writes to:
254256
- `$CLAUDE_CONFIG_DIR` (default `~/.claude/`) — hooks, flag file, `settings.json` merge.
255257
- Each agent's own config location — Cursor's `.cursor/rules/`, Windsurf's `.windsurf/rules/`, opencode's `~/.config/opencode/`, etc.
256258
- Your current working directory (only with `--with-init`) — repo-local rule files.
259+
- `~/.omp/caveman-plugin/` (only with `--only omp`, or auto-detect when `omp` is on `PATH`) — managed OMP plugin package installed through `omp plugin install`.
257260
- `~/.openclaw/workspace/` (only with `--only openclaw` or `--with-init` when OpenClaw is detected) — the one `--with-init` side-effect outside the cwd.
258261

259-
Installer sends no Caveman telemetry or analytics. Run from a clone or via npx, its own code copies files locally. One exception: run detached from any checkout (the rare curl-fallback path), it downloads hook files from raw.githubusercontent.com pinned to an immutable release tag and verifies each against a SHA-256 manifest before wiring anything. Network requests also happen indirectly through per-agent CLIs it shells out to — `claude plugin marketplace add`, `claude plugin install`, `gemini extensions install`, `npm view caveman-shrink`, and `npx -y skills add`. Each fetches from its own registry (Anthropic / GitHub / npm). Source: [`bin/install.js`](bin/install.js).
262+
Installer sends no Caveman telemetry or analytics. Run from a clone or via npx, its own code copies files locally. One exception: run detached from any checkout (the rare curl-fallback path), it downloads hook files from raw.githubusercontent.com pinned to an immutable release tag and verifies each against a SHA-256 manifest before wiring anything. Network requests also happen indirectly through per-agent CLIs it shells out to — `claude plugin marketplace add`, `claude plugin install`, `gemini extensions install`, `omp plugin install`, `npm view caveman-shrink`, and `npx -y skills add`. Each fetches from its own registry or local plugin manager (Anthropic / GitHub / OMP / npm). Source: [`bin/install.js`](bin/install.js).
260263

261264
After install, classic skill and output hooks stay local. CLI telemetry is off by default and sends content-free events only after explicit opt-in. Proxy, SDK, provider, authenticated sync, and managed gateway commands use network according to their configured purpose. Full data-flow statement: [SECURITY.md](./SECURITY.md#privacy--telemetry).
262265

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ npx skills add JuliusBrussee/caveman --skill '*' -a codex --yes # replace codex
8585

8686
See [INSTALL.md](./INSTALL.md) for the full 30+ agent matrix, dry run, flags, verification, and uninstall.
8787

88-
Caveman started as a skill/plugin for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Codex, Gemini, Cursor, Windsurf, Cline, Copilot, and 30+ other agents. Install once; the agent drops filler and answers in tight caveman-speak while keeping code, commands, and errors byte-for-byte exact.
88+
Caveman started as a skill/plugin for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), Oh My Pi (OMP), Codex, Gemini, Cursor, Windsurf, Cline, Copilot, and 30+ other agents. Install once; the agent drops filler and answers in tight caveman-speak while keeping code, commands, and errors byte-for-byte exact.
8989

9090
Agent mouth got smaller. Appetite did not. Tool schemas, files, logs, history, and skill bodies still crossed the provider boundary in full, often on every turn. Caveman 2 shrinks that input before the provider call. Caveman Proxy sits under your existing agent; Caveman Engine powers compression and stores every moved byte for exact recovery.
9191

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ third-party tools trustworthy.
140140

141141
Network installers fetch source from GitHub and may invoke npm or agent-specific
142142
registries. Per-agent installers can contact Anthropic/GitHub, Gemini extension,
143-
npm, or other configured registries. Detached hook installation downloads files
143+
the Oh My Pi plugin manager, npm, or other configured registries. Detached hook installation downloads files
144144
from an immutable release tag and verifies SHA-256 manifest entries. Runtime
145145
companion setup downloads a signed checksum manifest and verifies each binary's
146146
signature and SHA-256 before installation.

bin/install.js

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ const PROVIDERS = [
214214
{ id: 'claude', label: 'Claude Code', mech: 'claude plugin install', detect: 'command:claude' },
215215
{ id: 'gemini', label: 'Gemini CLI', mech: 'gemini extensions install', detect: 'command:gemini' },
216216
{ id: 'opencode', label: 'opencode', mech: 'native opencode plugin', detect: 'command:opencode' },
217+
{ id: 'omp', label: 'Oh My Pi (OMP)', mech: 'native OMP plugin', detect: 'command:omp' },
217218
{ id: 'openclaw', label: 'OpenClaw', mech: 'workspace skill + SOUL.md', detect: 'command:openclaw||dir:$HOME/.openclaw/workspace' },
218219
{ id: 'codex', label: 'Codex CLI', mech: 'npx skills add (codex)', detect: 'command:codex', profile: 'codex' },
219220

@@ -667,6 +668,185 @@ const OPENCODE_AGENTS_MD_SENTINEL = 'Respond terse like smart caveman';
667668
const OPENCODE_AGENTS_MD_BEGIN = '<!-- caveman-begin -->';
668669
const OPENCODE_AGENTS_MD_END = '<!-- caveman-end -->';
669670

671+
// ── Oh My Pi native install ────────────────────────────────────────────────
672+
// OMP local-path installs intentionally route through its plugin manager:
673+
// `omp plugin install <path>` symlinks the package into ~/.omp/plugins/
674+
// node_modules and records runtime state in omp-plugins.lock.json. We prepare
675+
// a stable managed plugin package under ~/.omp/ so the symlink never points at
676+
// an npx cache directory that may disappear after install.
677+
const OMP_PLUGIN_NAME = 'caveman';
678+
const OMP_PLUGIN_DIRNAME = 'caveman-plugin';
679+
const OMP_STAGING_PREFIX = 'caveman-plugin-staging-';
680+
const OMP_BACKUP_SUFFIX = '.previous';
681+
const OMP_EXTENSION_ENTRY = './index.js';
682+
const OMP_SKILL_DIRS = OPENCODE_SKILL_DIRS;
683+
const OMP_AGENT_FILES = OPENCODE_AGENT_FILES;
684+
const OMP_COMMAND_FILES = OPENCODE_COMMAND_FILES;
685+
const OMP_RULE_FILE = 'caveman.md';
686+
const OMP_PACKAGE_FILE = 'package.json';
687+
const OMP_INDEX_FILE = 'index.js';
688+
const OMP_PACKAGE_VERSION = '0.1.0';
689+
const OMP_PLUGIN_DESCRIPTION = 'Caveman terse communication mode for Oh My Pi';
690+
const OMP_RULE_COUNT = 1;
691+
const OMP_EXTENSION_SOURCE = `'use strict';
692+
693+
const STATUS_LABEL = 'caveman';
694+
const STATUS_TEXT = 'CAVEMAN';
695+
696+
module.exports = function cavemanPlugin(pi) {
697+
if (!pi || typeof pi.on !== 'function') return;
698+
pi.on('session_start', async (_event, ctx) => {
699+
if (ctx && ctx.ui && typeof ctx.ui.setStatus === 'function') {
700+
ctx.ui.setStatus(STATUS_LABEL, STATUS_TEXT);
701+
}
702+
});
703+
};
704+
`;
705+
706+
function ompPluginDir() {
707+
return path.join(os.homedir(), '.omp', OMP_PLUGIN_DIRNAME);
708+
}
709+
710+
function packageVersion(repoRoot) {
711+
if (!repoRoot) return OMP_PACKAGE_VERSION;
712+
try {
713+
const raw = fs.readFileSync(path.join(repoRoot, OMP_PACKAGE_FILE), 'utf8');
714+
const pkg = JSON.parse(raw);
715+
return typeof pkg.version === 'string' && pkg.version ? pkg.version : OMP_PACKAGE_VERSION;
716+
} catch (_) {
717+
return OMP_PACKAGE_VERSION;
718+
}
719+
}
720+
721+
function writeOmpPluginPackage(ctx, pluginDir) {
722+
const { repoRoot } = ctx;
723+
if (!repoRoot) throw new Error('native install requires local repo clone');
724+
725+
fs.mkdirSync(pluginDir, { recursive: true });
726+
727+
const pkg = {
728+
name: OMP_PLUGIN_NAME,
729+
version: packageVersion(repoRoot),
730+
description: OMP_PLUGIN_DESCRIPTION,
731+
omp: {
732+
description: OMP_PLUGIN_DESCRIPTION,
733+
extensions: [OMP_EXTENSION_ENTRY],
734+
},
735+
};
736+
fs.writeFileSync(path.join(pluginDir, OMP_PACKAGE_FILE), JSON.stringify(pkg, null, 2) + '\n');
737+
fs.writeFileSync(path.join(pluginDir, OMP_INDEX_FILE), OMP_EXTENSION_SOURCE);
738+
739+
const skillsRoot = path.join(pluginDir, 'skills');
740+
for (const name of OMP_SKILL_DIRS) {
741+
const src = path.join(repoRoot, 'skills', name);
742+
if (fs.existsSync(src)) fs.cpSync(src, path.join(skillsRoot, name), { recursive: true });
743+
}
744+
745+
const commandsRoot = path.join(pluginDir, 'commands');
746+
fs.mkdirSync(commandsRoot, { recursive: true });
747+
const commandSrcRoot = path.join(repoRoot, 'src', 'plugins', 'opencode', 'commands');
748+
for (const name of OMP_COMMAND_FILES) {
749+
const src = path.join(commandSrcRoot, name);
750+
if (fs.existsSync(src)) fs.copyFileSync(src, path.join(commandsRoot, name));
751+
}
752+
753+
const agentsRoot = path.join(pluginDir, 'agents');
754+
fs.mkdirSync(agentsRoot, { recursive: true });
755+
const agentSrcRoot = path.join(repoRoot, 'agents');
756+
for (const name of OMP_AGENT_FILES) {
757+
const src = path.join(agentSrcRoot, name);
758+
if (fs.existsSync(src)) {
759+
fs.writeFileSync(path.join(agentsRoot, name), stripOpencodeAgentTools(fs.readFileSync(src, 'utf8')));
760+
}
761+
}
762+
763+
const rulesRoot = path.join(pluginDir, 'rules');
764+
fs.mkdirSync(rulesRoot, { recursive: true });
765+
const ruleBody = fs.readFileSync(path.join(repoRoot, 'src', 'rules', 'caveman-activate.md'), 'utf8');
766+
fs.writeFileSync(path.join(rulesRoot, OMP_RULE_FILE), ruleBody);
767+
}
768+
769+
function prepareOmpPluginPackage(ctx, pluginDir) {
770+
const parentDir = path.dirname(pluginDir);
771+
fs.mkdirSync(parentDir, { recursive: true });
772+
const stagingDir = fs.mkdtempSync(path.join(parentDir, OMP_STAGING_PREFIX));
773+
try {
774+
writeOmpPluginPackage(ctx, stagingDir);
775+
return stagingDir;
776+
} catch (err) {
777+
fs.rmSync(stagingDir, { recursive: true, force: true });
778+
throw err;
779+
}
780+
}
781+
782+
function replaceOmpPluginPackage(stagingDir, pluginDir) {
783+
const backupDir = pluginDir + OMP_BACKUP_SUFFIX;
784+
fs.rmSync(backupDir, { recursive: true, force: true });
785+
const hadPrevious = fs.existsSync(pluginDir);
786+
if (hadPrevious) fs.renameSync(pluginDir, backupDir);
787+
try {
788+
fs.renameSync(stagingDir, pluginDir);
789+
} catch (err) {
790+
if (hadPrevious) fs.renameSync(backupDir, pluginDir);
791+
throw err;
792+
}
793+
return hadPrevious ? backupDir : null;
794+
}
795+
796+
function restoreOmpPluginPackage(pluginDir, backupDir) {
797+
fs.rmSync(pluginDir, { recursive: true, force: true });
798+
if (backupDir && fs.existsSync(backupDir)) fs.renameSync(backupDir, pluginDir);
799+
}
800+
801+
function installOmp(ctx) {
802+
const { say, note, warn, opts, repoRoot, results } = ctx;
803+
results.detected++;
804+
say('→ Oh My Pi (OMP) detected');
805+
806+
if (!repoRoot) {
807+
warn(' OMP native install requires a local clone of the caveman repo.');
808+
note(' Re-run from a clone: git clone https://github.com/' + REPO + ' && cd caveman && node bin/install.js --only omp');
809+
results.failed.push(['omp', 'native install requires local repo clone']);
810+
process.stdout.write('\n');
811+
return;
812+
}
813+
814+
const pluginDir = ompPluginDir();
815+
if (opts.dryRun) {
816+
note(` would prepare OMP plugin package at ${pluginDir}/`);
817+
note(` would copy ${OMP_SKILL_DIRS.length} skill dirs, ${OMP_COMMAND_FILES.length} commands, ${OMP_AGENT_FILES.length} agents, and ${OMP_RULE_COUNT} rule`);
818+
runSpawn('omp', ['plugin', 'install', pluginDir], null, true);
819+
results.installed.push('omp');
820+
process.stdout.write('\n');
821+
return;
822+
}
823+
824+
let stagingDir = null;
825+
let backupDir = null;
826+
let packageReplaced = false;
827+
try {
828+
stagingDir = prepareOmpPluginPackage(ctx, pluginDir);
829+
backupDir = replaceOmpPluginPackage(stagingDir, pluginDir);
830+
stagingDir = null;
831+
packageReplaced = true;
832+
process.stdout.write(` prepared: ${pluginDir}/\n`);
833+
const r = runSpawn('omp', ['plugin', 'install', pluginDir], null, false);
834+
if (spawnOk(r)) {
835+
if (backupDir) fs.rmSync(backupDir, { recursive: true, force: true });
836+
results.installed.push('omp');
837+
} else {
838+
restoreOmpPluginPackage(pluginDir, backupDir);
839+
results.failed.push(['omp', 'omp plugin install failed']);
840+
}
841+
} catch (e) {
842+
if (packageReplaced) restoreOmpPluginPackage(pluginDir, backupDir);
843+
if (stagingDir) fs.rmSync(stagingDir, { recursive: true, force: true });
844+
warn(' OMP install failed: ' + (e && e.message || e));
845+
results.failed.push(['omp', (e && e.message) || 'unknown error']);
846+
}
847+
process.stdout.write('\n');
848+
}
849+
670850
function opencodeConfigDir() {
671851
// opencode uses ~/.config/opencode on every platform (on Windows that's
672852
// %USERPROFILE%\.config\opencode via os.homedir()), NOT %APPDATA% (#376).
@@ -1302,6 +1482,17 @@ function uninstall(ctx) {
13021482
}
13031483
}
13041484

1485+
// Oh My Pi native plugin — delegate lifecycle removal to OMP, then remove
1486+
// the managed local package source we prepared for its plugin-manager link.
1487+
const ompDir = ompPluginDir();
1488+
if (hasCmd('omp')) {
1489+
runSpawn('omp', ['plugin', 'uninstall', OMP_PLUGIN_NAME], null, opts.dryRun);
1490+
}
1491+
if (fs.existsSync(ompDir)) {
1492+
if (!opts.dryRun) { try { fs.rmSync(ompDir, { recursive: true, force: true }); } catch (_) {} }
1493+
note(` removed ${ompDir}`);
1494+
}
1495+
13051496
// Hermes native install — same journal/digest contract as opencode.
13061497
const hermesRoot = path.join(hermesConfigDir(), 'productivity');
13071498
try {
@@ -1421,8 +1612,9 @@ FLAGS
14211612
--config-dir <path> Claude Code config dir for hook files + settings.json.
14221613
Default: \$CLAUDE_CONFIG_DIR or ~/.claude. Does NOT
14231614
scope \`claude plugin install\`, \`gemini extensions
1424-
install\`, opencode (XDG_CONFIG_HOME), or openclaw
1425-
(OPENCLAW_WORKSPACE) — those use their own paths.
1615+
install\`, OMP (~/.omp/), opencode (XDG_CONFIG_HOME),
1616+
or openclaw (OPENCLAW_WORKSPACE) — those use their
1617+
own paths.
14261618
--non-interactive Never prompt; use defaults. (Auto when stdin is not a TTY.)
14271619
--list Print provider matrix and exit.
14281620
--no-color Disable ANSI colors.
@@ -1495,6 +1687,7 @@ async function main() {
14951687
if (prov.id === 'claude') { await installClaude(ctx); continue; }
14961688
if (prov.id === 'gemini') { installGemini(ctx); continue; }
14971689
if (prov.id === 'opencode') { installOpencode(ctx); continue; }
1690+
if (prov.id === 'omp') { installOmp(ctx); continue; }
14981691
if (prov.id === 'openclaw') { installOpenclaw(ctx); continue; }
14991692
if (prov.id === 'hermes') { installHermes(ctx); continue; }
15001693
if (prov.profile) { installViaSkills(ctx, prov); continue; }

0 commit comments

Comments
 (0)