Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .claude/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "lavra-local-marketplace",
"interface": {
"displayName": "Lavra Local"
},
"plugins": [
{
"name": "lavra",
"source": {
"source": "local",
"path": "./plugins/lavra"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_USE"
},
"category": "Coding"
}
]
}
6 changes: 5 additions & 1 deletion .claude/rules/conversion-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ globs: "**/scripts/**,**/opencode/**,**/gemini/**"

# Platform Conversion Scripts

OpenCode and Gemini CLI require conversion from Claude Code format:
OpenCode, Gemini CLI, Cortex Code, and Codex require conversion from Claude Code format:

- `scripts/convert-opencode.ts` - Converts to OpenCode format
- `scripts/convert-gemini.ts` - Converts to Gemini CLI format
- `scripts/convert-cortex.ts` - Converts to Cortex Code format
- `scripts/convert-codex.ts` - Converts Codex output from Cortex output
- Run automatically during platform-specific installation
- Requires Bun runtime (`bun run convert-opencode.ts`)

Generated outputs are checked in for release/install compatibility, but they must always match the canonical source in `plugins/lavra/`. The CI and pre-release gate use `scripts/check-generated-outputs.sh` to fail if the generated trees drift.

## Model Tier Mapping

Claude Code tiers map to platform-specific model IDs via `scripts/shared/model-config.json`:
Expand Down
6 changes: 4 additions & 2 deletions .claude/rules/github-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@ bash scripts/pre-release-check.sh

This replicates the CI `verify-release` job locally:
- Version consistency between `plugin.json` and `marketplace.json`
- Conversion outputs generated (OpenCode + Gemini)
- Generated outputs match source (OpenCode + Gemini + Cortex + Codex)
- Component counts (23+ commands, 30+ agents, 15+ skills)
- Source files present
- Catalog accuracy: ghost commands (in CATALOG.md, no file) and missing entries (file exists, not in catalog) both fail
- Compatibility tests pass

If the generated-output drift check fails, regenerate and commit the updated files before tagging.

**Do not proceed if any check fails.**

## 4. Run installer smoke tests (MUST PASS before tagging)
Expand Down Expand Up @@ -282,7 +284,7 @@ Do NOT delete and recreate the tag. Bump to a patch version, fix, and release th

**When modifying either file, always update the other.** Both files have a `SYNC:` comment pointing to each other as a reminder. If CI adds a new check, add it to the script. If the script adds a new check, add it to CI.

The catalog accuracy check (`=== Catalog accuracy ===`) also appears in the `test-compatibility` job, which runs on every PR to `main`. If you change the catalog check logic in pre-release-check.sh, update both CI locations.
The generated-output drift check and catalog accuracy check also appear in the `test-compatibility` job, which runs on every PR to `main`. If you change either check in `pre-release-check.sh`, update the matching CI locations.

## Key facts

Expand Down
35 changes: 22 additions & 13 deletions .github/workflows/test-installation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@ jobs:
- name: Install dependencies
run: cd scripts && bun install

- name: Generate conversion outputs
run: |
cd scripts
bun run convert-opencode.ts
bun run convert-gemini.ts
bun run convert-cortex.ts
- name: Verify generated outputs match source
run: bash scripts/check-generated-outputs.sh

- name: Run compatibility tests
run: cd scripts && bun run test-compatibility.ts
Expand All @@ -47,6 +43,19 @@ jobs:
- name: Build Go helper release artifacts
run: bash scripts/build-memory-sanitize-helper.sh

- name: Verify Go helper artifact integrity
run: bash scripts/verify-memory-sanitize-artifacts.sh

- name: Enforce Go helper version bump when source changes
run: |
SOURCE_TS=$(git log -1 --format=%ct -- plugins/lavra/hooks/memorysanitize/main.go plugins/lavra/hooks/memorysanitize/go.mod 2>/dev/null || echo 0)
VERSION_TS=$(git log -1 --format=%ct -- plugins/lavra/hooks/memorysanitize/VERSION 2>/dev/null || echo 0)
if [[ "$SOURCE_TS" -gt "$VERSION_TS" ]]; then
echo "FAIL Go helper source changed after VERSION; bump plugins/lavra/hooks/memorysanitize/VERSION"
exit 1
fi
echo "PASS Go helper version bump guard"

- name: Verify catalog accuracy
# SYNC: This step must stay in sync with the "Catalog accuracy" section
# in scripts/pre-release-check.sh. When modifying either, update the other.
Expand Down Expand Up @@ -177,13 +186,8 @@ jobs:
with:
bun-version: "1.3.6"

- name: Generate conversion outputs for verification
run: |
cd scripts
bun install --frozen-lockfile
bun run convert-opencode.ts
bun run convert-gemini.ts
bun run convert-cortex.ts
- name: Verify generated outputs match source
run: bash scripts/check-generated-outputs.sh

- name: Verify version consistency
run: |
Expand Down Expand Up @@ -238,11 +242,16 @@ jobs:
- name: Verify Go helper
run: |
test -f plugins/lavra/hooks/memorysanitize/go.mod || { echo "FAIL Go helper module missing"; exit 1; }
test -f plugins/lavra/hooks/memorysanitize/VERSION || { echo "FAIL Go helper version file missing"; exit 1; }
cd plugins/lavra/hooks/memorysanitize
go test -race ./...
go vet ./...
cd "$GITHUB_WORKSPACE"
bash scripts/build-memory-sanitize-helper.sh
bash scripts/verify-memory-sanitize-artifacts.sh
SOURCE_TS=$(git log -1 --format=%ct -- plugins/lavra/hooks/memorysanitize/main.go plugins/lavra/hooks/memorysanitize/go.mod 2>/dev/null || echo 0)
VERSION_TS=$(git log -1 --format=%ct -- plugins/lavra/hooks/memorysanitize/VERSION 2>/dev/null || echo 0)
[[ "$SOURCE_TS" -le "$VERSION_TS" ]] || { echo "FAIL Go helper source changed after VERSION; bump VERSION"; exit 1; }

- name: Verify catalog accuracy
# SYNC: This step must stay in sync with the "Catalog accuracy" section
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.DS_Store
.claude/launch.json
.claude/worktrees
.codex/
__pycache__

# bv (beads viewer) local config and caches
Expand All @@ -15,6 +16,7 @@ plans/
plugins/lavra/opencode/
plugins/lavra/gemini/
plugins/lavra/cortex/
plugins/lavra/codex/

# Dolt database files (added by bd init)
.dolt/
Expand Down
50 changes: 34 additions & 16 deletions bin/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const FLAG = {
opencode: args.includes("--opencode"),
gemini: args.includes("--gemini"),
cortex: args.includes("--cortex"),
codex: args.includes("--codex"),
global: args.includes("--global"),
local: args.includes("--local"),
uninstall: args.includes("--uninstall"),
Expand Down Expand Up @@ -77,8 +78,10 @@ function usage() {
console.log(" npx @lavralabs/lavra@latest --opencode OpenCode (local project)");
console.log(" npx @lavralabs/lavra@latest --gemini Gemini CLI (local project)");
console.log(" npx @lavralabs/lavra@latest --cortex Cortex Code (local project)");
console.log(" npx @lavralabs/lavra@latest --codex Codex (local project)");
console.log(" npx @lavralabs/lavra@latest --global Install globally (~/.claude/)");
console.log(" npx @lavralabs/lavra@latest --uninstall Uninstall from current project");
console.log(" npx @lavralabs/lavra@latest --codex --uninstall --global");
console.log(" npx @lavralabs/lavra@latest --yes Skip confirmation prompts");
console.log("");
console.log(" Flags can be combined:");
Expand Down Expand Up @@ -129,6 +132,7 @@ async function promptRuntime(rl) {
console.log(" 2. OpenCode");
console.log(" 3. Gemini CLI");
console.log(" 4. Cortex Code");
console.log(" 5. Codex");
console.log("");

while (true) {
Expand All @@ -138,8 +142,9 @@ async function promptRuntime(rl) {
case "2": return "opencode";
case "3": return "gemini";
case "4": return "cortex";
case "5": return "codex";
default:
console.log(" Please enter 1, 2, 3, or 4.");
console.log(" Please enter 1, 2, 3, 4, or 5.");
}
}
}
Expand All @@ -150,6 +155,7 @@ function globalPathForRuntime(runtime) {
case "opencode": return `${home}/.config/opencode/`;
case "gemini": return `${home}/.config/gemini/`;
case "cortex": return `${home}/.snowflake/cortex/`;
case "codex": return `${home}/.codex/`;
default: return `${home}/.claude/`;
}
}
Expand Down Expand Up @@ -221,6 +227,7 @@ function buildInstallArgs(runtime, scope) {
if (runtime === "opencode") scriptArgs.push("-opencode");
if (runtime === "gemini") scriptArgs.push("-gemini");
if (runtime === "cortex") scriptArgs.push("-cortex");
if (runtime === "codex") scriptArgs.push("-codex");
// Claude Code is the default — no flag needed

// --yes passthrough
Expand Down Expand Up @@ -252,13 +259,35 @@ async function main() {
ensureBash();
banner();

const runtimeFlags = [FLAG.claude, FLAG.opencode, FLAG.gemini, FLAG.cortex, FLAG.codex].filter(Boolean).length;
if (runtimeFlags > 1) {
die("Specify only one runtime: --claude, --opencode, --gemini, --cortex, or --codex");
}

let runtime;
if (FLAG.claude) runtime = "claude";
else if (FLAG.opencode) runtime = "opencode";
else if (FLAG.gemini) runtime = "gemini";
else if (FLAG.cortex) runtime = "cortex";
else if (FLAG.codex) runtime = "codex";

// --- Uninstall path ---
if (FLAG.uninstall) {
ensureScript(UNINSTALL_SH, "uninstall.sh");
const target = TARGET_PATH || process.cwd();
console.log(` Uninstalling lavra from ${target}...\n`);
const scriptArgs = [];
if (runtime) {
scriptArgs.push(`--${runtime}`);
}
if (FLAG.global) {
// No target path needed: platform uninstallers default to global root.
console.log(` Uninstalling lavra ${runtime ? `for ${runtime} ` : ""}globally...\n`);
} else {
const target = TARGET_PATH || process.cwd();
scriptArgs.push(target);
console.log(` Uninstalling lavra from ${target}...\n`);
}
try {
await runScript(UNINSTALL_SH, [target]);
await runScript(UNINSTALL_SH, scriptArgs);
console.log("\n Uninstall complete.\n");
} catch (err) {
die(err.message);
Expand All @@ -268,18 +297,6 @@ async function main() {

ensureScript(INSTALL_SH, "install.sh");

// Determine runtime
let runtime;
const runtimeFlags = [FLAG.claude, FLAG.opencode, FLAG.gemini, FLAG.cortex].filter(Boolean).length;
if (runtimeFlags > 1) {
die("Specify only one runtime: --claude, --opencode, --gemini, or --cortex");
}

if (FLAG.claude) runtime = "claude";
else if (FLAG.opencode) runtime = "opencode";
else if (FLAG.gemini) runtime = "gemini";
else if (FLAG.cortex) runtime = "cortex";

// Determine scope
let scope;
if (FLAG.global && FLAG.local) {
Expand Down Expand Up @@ -314,6 +331,7 @@ async function main() {
opencode: "OpenCode",
gemini: "Gemini CLI",
cortex: "Cortex Code",
codex: "Codex",
}[runtime];
const scopeLabel = scope === "global" ? "globally" : "in current project";

Expand Down
12 changes: 9 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Platforms:
-opencode, --opencode Install for OpenCode
-gemini, --gemini Install for Gemini CLI
-cortex, --cortex Install for Cortex Code
-codex, --codex Install for Codex (alias installer)

Target:
[path] Install to specific project directory
Expand All @@ -46,6 +47,7 @@ Examples:
./install.sh -opencode # Global OpenCode install
./install.sh -gemini /path/to/project # Project-specific Gemini install
./install.sh -cortex # Global Cortex Code install
./install.sh -codex # Global Codex install

EOF
exit 0
Expand All @@ -56,12 +58,12 @@ validate_platform() {
local platform="$1"

case "$platform" in
claude|opencode|gemini|cortex)
claude|opencode|gemini|cortex|codex)
return 0
;;
*)
echo "[!] Error: Invalid platform '$platform'"
echo " Allowed platforms: claude, opencode, gemini, cortex"
echo " Allowed platforms: claude, opencode, gemini, cortex, codex"
echo ""
echo "Run './install.sh --help' for usage information."
exit 1
Expand Down Expand Up @@ -91,6 +93,10 @@ while [[ $# -gt 0 ]]; do
PLATFORM="cortex"
shift
;;
-codex|--codex)
PLATFORM="codex"
shift
;;
*)
# Pass through to platform-specific installer
break
Expand Down Expand Up @@ -134,4 +140,4 @@ export BEADS_MARKETPLACE_ROOT="$SCRIPT_DIR"
echo "Installing lavra for $PLATFORM..."
echo ""

source "$INSTALLER" "$@"
source "$INSTALLER" "$@"
11 changes: 11 additions & 0 deletions installers/install-codex.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
#
# Install lavra plugin for Codex.
# Current implementation reuses Cortex installer path.
#

set -euo pipefail

# shellcheck source=install-cortex.sh
export LAVRA_RUNTIME_VARIANT="codex"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/install-cortex.sh" "$@"
Loading
Loading