Skip to content
Merged
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
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,26 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo build --release --locked
- run: ./target/release/omnid --version

publish-npm:
needs: upload-assets
if: ${{ secrets.NPM_TOKEN != '' }}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Sync npm version with release tag
run: |
VERSION="${GITHUB_REF_NAME#v}"
cd npm
npm version "$VERSION" --no-git-tag-version --allow-same-version
- run: cd npm && npm test
- run: cd npm && npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ cargo deny check # requires: cargo install cargo-deny

- **MSRV:** `rust-version = "1.78"` in `Cargo.toml` — CI enforces via dedicated job
- **Local toolchain:** stable (`rust-toolchain.toml`); develop on stable, CI guarantees 1.78
- **Automation:** set `OMNID_NONINTERACTIVE=1` to skip interactive prompts
- **Automation:** `OMNID_NONINTERACTIVE=1` skips `omnid add server` prompts when `--preset` or `--name` + `--command` are set; skips daemon confirm on `omnid init --install-daemon`
- **npm:** `npm/` wraps release binaries (`postinstall` downloads from GitHub, shim in `bin/omnid.js`). Release publish needs `NPM_TOKEN` in repo secrets; `publish-npm` job skips if unset.

## Git workflow

Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- npm package: `npx omnid` and `npm install -g omnid` download the native binary from GitHub releases
- MCP presets on `omnid add server` (`everything`, `filesystem`) with `--preset` flag
- Non-interactive `omnid add server` flags: `--name`, `--command`, `--args`, `--transport`, `--url`

### Changed

- `publish-npm` release job skips when `NPM_TOKEN` is not configured
- First `omnid` run scaffolds config, force-syncs, and shows a short welcome with one next step
- Status output uses plain labels (Tools, synced) and context-aware next-step hints
- README leads with `npx omnid`; `OMNID_NONINTERACTIVE` docs match actual behavior

## [0.1.1] - 2026-06-05

### Changed
Expand Down
49 changes: 31 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,45 @@ omnid owns `~/.config/omni/`. One source of truth → compiled, linked, and push
- [Development](#development)
- [License](#license)

## Quick start
## Start here

```bash
omnid init # first time
omnid # status + sync if drift
omnid add server # add MCP backend
npx omnid
```

No config? `omnid` alone makes `~/.config/omni/` and shows what's on your machine.
Open your coding app. Done.

CI: `OMNID_NONINTERACTIVE=1` skips prompts.
Want a tool inside your agent? Run `omnid add server`.

### Example
Install for good: `npm install -g omnid`

> **Note:** `npx omnid` / `npm install -g omnid` work after the package is published to npm (starting with the next tagged release). Until then, use [GitHub Releases](#install) or `cargo install`.

### Example (first run)

```
omnid — agent config sync
────────────────────────────────────────
Matrix ~/.config/omni/matrix.yaml valid
Agents 3 installed · in sync
cursor mcp ok rules ok skills ok hooks n/a
claude_code mcp ok rules ok skills ok hooks n/a
vscode mcp ok rules ok skills n/a hooks n/a
Backends 2 configured
omnid
─────
Made ~/.config/omni/
Synced cursor, claude_code

Next: open Cursor. omnid is ready.
(no tools yet — run: omnid add server)
```

`OMNID_NONINTERACTIVE=1` skips prompts on `omnid add server` when you pass `--preset` or `--name` + `--command`.

## Install

**npm** (easiest if you have Node.js — available after first npm publish):

```bash
npx omnid # try without installing
npm install -g omnid # install globally
```

Requires the `omnid` package on [npm](https://www.npmjs.com/package/omnid). Not live until the next release; use binaries below until then.

**Binaries** — [GitHub Releases](https://github.com/xb3sox/omnid/releases) (SHA256 checksum sidecars included)

```bash
Expand Down Expand Up @@ -162,9 +174,10 @@ Use `OMNID_MATRIX` or `--matrix` if your config lives outside the default path.

| Command | Does |
|---------|------|
| `omnid` | Status. Auto-sync on drift. |
| `omnid init` | Guided setup |
| `omnid add server` | Add MCP (interactive) |
| `omnid` | Setup on first run. Status after that. Auto-sync on drift. |
| `omnid init` | Same as first `omnid`, plus optional `--install-daemon` |
| `omnid add server` | Add a tool (pick a preset or type your own) |
| `omnid add server --preset everything --write` | Add demo tool, no prompts |
| `omnid add rule` | Append `rules/global.md` |
| `omnid add skill` | New skill scaffold |
| `omnid check` | Summary health check (`doctor --summary`) |
Expand Down
18 changes: 18 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# omnid

Sync MCP, rules, and skills across 20+ AI coding agents.

## Quick start

```bash
npx omnid
```

Or install globally:

```bash
npm install -g omnid
omnid
```

Full docs: [github.com/xb3sox/omnid](https://github.com/xb3sox/omnid)
43 changes: 43 additions & 0 deletions npm/bin/omnid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env node
"use strict";

const { spawnSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const { installBinary, cacheRoot, VERSION } = require("../install");
const { resolvePlatform } = require("../lib/platform");

function binaryPath() {
const spec = resolvePlatform(process.platform, process.arch);
if (!spec) {
console.error("omnid: unsupported platform");
process.exit(1);
}
const cached = path.join(cacheRoot(), spec.binary);
if (fs.existsSync(cached)) {
return cached;
}
return null;
}

async function main() {
let bin = binaryPath();
if (!bin) {
try {
bin = await installBinary();
} catch (err) {
console.error(`omnid: could not install v${VERSION}: ${err.message}`);
console.error("Run: npm rebuild omnid");
process.exit(1);
}
}

const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
if (result.error) {
console.error(`omnid: ${result.error.message}`);
process.exit(1);
}
process.exit(result.status ?? 1);
}

main();
127 changes: 127 additions & 0 deletions npm/install.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"use strict";

const crypto = require("crypto");
const fs = require("fs");
const https = require("https");
const path = require("path");
const { execFileSync } = require("child_process");
const { resolvePlatform } = require("./lib/platform");

const REPO = "xb3sox/omnid";
const VERSION = process.env.OMNID_VERSION || require("./package.json").version;

function cacheRoot() {
if (process.platform === "win32") {
const base = process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || ".", "AppData", "Local");
return path.join(base, "omnid", "versions", VERSION);
}
const home = process.env.HOME || process.env.USERPROFILE || ".";
return path.join(home, ".omnid", "versions", VERSION);
}

function download(url) {
return new Promise((resolve, reject) => {
const follow = (current) => {
https
.get(current, (res) => {
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
res.resume();
follow(res.headers.location);
return;
}
if (res.statusCode !== 200) {
reject(new Error(`download failed: HTTP ${res.statusCode} for ${current}`));
res.resume();
return;
}
const chunks = [];
res.on("data", (c) => chunks.push(c));
res.on("end", () => resolve(Buffer.concat(chunks)));
})
.on("error", reject);
};
follow(url);
});
}

function sha256(buf) {
return crypto.createHash("sha256").update(buf).digest("hex");
}

async function fetchChecksum(asset) {
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${asset}.sha256`;
const buf = await download(url);
const line = buf.toString("utf8").trim().split(/\s+/)[0];
if (!line) {
throw new Error(`empty checksum file for ${asset}`);
}
return line.toLowerCase();
}

function extractTarGz(archivePath, destDir) {
fs.mkdirSync(destDir, { recursive: true });
execFileSync("tar", ["-xzf", archivePath, "-C", destDir], { stdio: "inherit" });
}

function extractZip(archivePath, destDir) {
fs.mkdirSync(destDir, { recursive: true });
if (process.platform === "win32") {
const ps = `Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`;
execFileSync("powershell", ["-NoProfile", "-Command", ps], { stdio: "inherit" });
} else {
execFileSync("unzip", ["-o", archivePath, "-d", destDir], { stdio: "inherit" });
}
}

async function installBinary() {
const spec = resolvePlatform(process.platform, process.arch);
if (!spec) {
console.error(
`omnid: no binary for ${process.platform}/${process.arch}. See https://github.com/${REPO}/releases`
);
process.exit(1);
}

const destDir = cacheRoot();
const binaryPath = path.join(destDir, spec.binary);
if (fs.existsSync(binaryPath)) {
return binaryPath;
}

const base = `https://github.com/${REPO}/releases/download/v${VERSION}`;
const assetUrl = `${base}/${spec.asset}`;
const archivePath = path.join(destDir, spec.asset);

fs.mkdirSync(destDir, { recursive: true });
const data = await download(assetUrl);
const expected = await fetchChecksum(spec.asset);
const got = sha256(data);
if (got !== expected) {
throw new Error(`checksum mismatch for ${spec.asset}`);
}

fs.writeFileSync(archivePath, data);
if (spec.asset.endsWith(".tar.gz")) {
extractTarGz(archivePath, destDir);
} else {
extractZip(archivePath, destDir);
}
fs.unlinkSync(archivePath);

if (!fs.existsSync(binaryPath)) {
throw new Error(`binary not found after extract: ${binaryPath}`);
}
if (process.platform !== "win32") {
fs.chmodSync(binaryPath, 0o755);
}
return binaryPath;
}

if (require.main === module) {
installBinary().catch((err) => {
console.error(`omnid install failed: ${err.message}`);
process.exit(1);
});
}

module.exports = { installBinary, cacheRoot, sha256, REPO, VERSION };
36 changes: 36 additions & 0 deletions npm/lib/platform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use strict";

/** @returns {{ target: string, asset: string, binary: string } | null} */
function resolvePlatform(platform, arch) {
if (platform === "linux" && arch === "x64") {
return {
target: "x86_64-unknown-linux-gnu",
asset: "omnid-x86_64-unknown-linux-gnu.tar.gz",
binary: "omnid",
};
}
if (platform === "darwin" && arch === "arm64") {
return {
target: "aarch64-apple-darwin",
asset: "omnid-aarch64-apple-darwin.tar.gz",
binary: "omnid",
};
}
if (platform === "darwin" && arch === "x64") {
return {
target: "x86_64-apple-darwin",
asset: "omnid-x86_64-apple-darwin.tar.gz",
binary: "omnid",
};
}
if (platform === "win32" && arch === "x64") {
return {
target: "x86_64-pc-windows-msvc",
asset: "omnid-x86_64-pc-windows-msvc.zip",
binary: "omnid.exe",
};
}
return null;
}

module.exports = { resolvePlatform };
31 changes: 31 additions & 0 deletions npm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "omnid",
"version": "0.1.1",
"description": "Sync MCP, rules, and skills across 20+ AI coding agents",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/xb3sox/omnid.git"
},
"homepage": "https://github.com/xb3sox/omnid#readme",
"bugs": {
"url": "https://github.com/xb3sox/omnid/issues"
},
"keywords": ["mcp", "cursor", "claude", "ai", "agents"],
"bin": {
"omnid": "bin/omnid.js"
},
"scripts": {
"postinstall": "node install.js",
"test": "node --test test/install.test.js"
},
"engines": {
"node": ">=18"
},
"files": [
"bin/omnid.js",
"install.js",
"lib/platform.js",
"README.md"
]
}
Loading
Loading