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
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ subscription.
**You need:** Node 20.11+ and one of these AI CLIs, installed and signed in:

- [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) (`claude login`)
- [Codex CLI](https://developers.openai.com/codex/cli) (`codex login`)
- [Antigravity CLI](https://antigravity.google/docs/cli/reference) (run `agy` once and follow the prompts)

> [!NOTE]
> **Coming soon:** Codex, OpenCode, Pi, Grok, and more AI CLIs.
> **Coming soon:** OpenCode, Pi, Grok, and more AI CLIs.

The first run downloads a one-time embedded MongoDB binary, so it takes a little longer.
Run `npx muktiai --help` for all options, or see [Local Mode](docs/local-mode.md) for details.
Expand Down Expand Up @@ -85,33 +86,34 @@ Mukti responds with a compact sequence:

## Ways to run Mukti

| I want to… | Use | Needs |
| ----------------------------- | -------------------------------------------------------- | ---------------------------------- |
| Try Mukti | [`npx muktiai`](#try-it-in-30-seconds) | Node 20.11+, `claude` or `agy` CLI |
| Run the full hosted stack | [`docker compose up -d`](#full-stack-docker) | Docker, an OpenRouter key |
| Hack on the code (hot reload) | [`bun run dev`](#development) | Bun, Docker (for MongoDB + Redis) |
| Run from source, no Docker | [`bun run start:local`](#run-from-source-without-docker) | Bun, `claude` or `agy` CLI |
| I want to… | Use | Needs |
| ----------------------------- | -------------------------------------------------------- | --------------------------------------------- |
| Try Mukti | [`npx muktiai`](#try-it-in-30-seconds) | Node 20.11+, a `claude`, `codex` or `agy` CLI |
| Run the full hosted stack | [`docker compose up -d`](#full-stack-docker) | Docker, an OpenRouter key |
| Hack on the code (hot reload) | [`bun run dev`](#development) | Bun, Docker (for MongoDB + Redis) |
| Run from source, no Docker | [`bun run start:local`](#run-from-source-without-docker) | Bun, a `claude`, `codex` or `agy` CLI |

### Choosing the AI CLI

With more than one CLI ready, Mukti asks which to use on the first run and offers to remember it.
To override:

```bash
npx muktiai --choose # ask again
npx muktiai --provider agy --save # set the default, no prompt
npx muktiai --choose # ask again
npx muktiai --provider codex --save # set the default, no prompt
```

| | Claude Code | Antigravity |
| -------------- | ----------------------------- | ------------------------------------------------------------- |
| Time per reply | about 13–20 seconds | about 30–60 seconds |
| Tokens | Mukti's prompt + conversation | about 15–25k per reply (includes agy's own agent prompt) |
| Side effects | — | Keeps a ~1 MB record per reply in `~/.gemini/antigravity-cli` |
| | Claude Code | Codex | Antigravity |
| -------------- | ----------------------------- | ------------------------------------------------ | ------------------------------------------------------------- |
| Time per reply | about 13–20 seconds | about 8–12 seconds | about 30–60 seconds |
| Tokens | Mukti's prompt + conversation | about 2.5–4.5k per reply, depending on the model | about 15–25k per reply (includes agy's own agent prompt) |
| Side effects | — | — (runs keep no Codex session history) | Keeps a ~1 MB record per reply in `~/.gemini/antigravity-cli` |

> [!WARNING]
> With Antigravity, your global agy rules apply to Mukti's replies, and each prompt is passed as a
> command-line argument (visible to other local processes via `ps`). Read the
> [full comparison and privacy notes](docs/local-mode.md#claude-code-vs-antigravity) before choosing it.
> With Codex, your global `~/.codex/AGENTS.md` still applies to Mukti's replies. With Antigravity,
> your global agy rules apply, and each prompt is passed as a command-line argument (visible to other
> local processes via `ps`). Read the [full comparison and privacy notes](docs/local-mode.md#comparing-the-ai-clis)
> before choosing either.

Your data lives in `~/.mukti/` (change it with `--data-dir` or `MUKTI_HOME`).

Expand Down Expand Up @@ -192,7 +194,7 @@ TypeScript support). Every other workspace script assumes Bun. Data is stored in
## Troubleshooting

- **First run is slow** — a one-time embedded MongoDB download. Later starts are fast.
- **AI CLI not detected** — make sure `claude` or `agy` is on your `PATH` and signed in.
- **AI CLI not detected** — make sure `claude`, `codex` or `agy` is on your `PATH` and signed in.
Mukti never picks a CLI you haven't signed in to.
- **Port already in use** — `npx muktiai --port 4001 --api-port 4000`.
- **More** — see [Local Mode → Troubleshooting](docs/local-mode.md#troubleshooting).
Expand Down
63 changes: 43 additions & 20 deletions docs/local-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ Both share the same launcher, preflight checks, and saved settings.
| CLI | Provider id | Status |
| ------------------------------------------------------------------------------ | ----------- | ----------- |
| [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) (`claude`) | `claude` | Supported |
| [Codex](https://developers.openai.com/codex/cli) (`codex`) | `codex` | Supported |
| [Antigravity](https://antigravity.google/docs/cli/reference) (`agy`) | `agy` | Supported |
| Codex, OpenCode, Pi, Grok, and more | — | Coming soon |
| OpenCode, Pi, Grok, and more | — | Coming soon |

Each provider is named after the command it runs, so `--provider claude` and `--provider agy` are
all you have to remember. The older names `claude-code` and `antigravity` still work everywhere a
provider can be named.
Each provider is named after the command it runs, so `--provider claude`, `--provider codex` and
`--provider agy` are all you have to remember. The older names `claude-code` and `antigravity` still
work everywhere a provider can be named.

Sign in first: `claude login`, or run `agy` once and follow the prompts.
Sign in first: `claude login`, `codex login`, or run `agy` once and follow the prompts.

## Choosing the AI CLI

Expand All @@ -34,6 +35,7 @@ remember your answer:
```
◇ Which AI CLI should Mukti use?
│ ● Claude CLI ready · about 13-20 seconds per reply
│ ○ Codex CLI ready · about 8-12 seconds per reply
│ ○ Antigravity CLI ready · about 30-60 seconds and 15-25k tokens per reply
│
◇ Use Claude CLI by default from now on? Yes
Expand All @@ -45,30 +47,48 @@ so. To change it, or to decide without being asked:

```bash
npx muktiai --choose # ask again, and offer to remember
npx muktiai --provider agy # just this once
npx muktiai --provider agy --save # set the default, no prompt
npx muktiai --provider codex # just this once
npx muktiai --provider codex --save # set the default, no prompt
```

From a checkout, pass the same flags after `--`: `bun run start:local -- --choose`.

The launcher decides in this order: `--provider`, then the `AI_PROVIDER` environment variable,
then your saved choice, then whichever CLIs are ready — installed _and_ signed in. A CLI you have
not signed in to is never chosen for you. It always reports which provider it used and why.
not signed in to is never chosen for you. When several are ready and Mukti cannot ask, it takes the
first in the order Claude Code, Codex, Antigravity. It always reports which provider it used and
why.

Your choice is saved in `~/.mukti/config.json` (or under `--data-dir`/`MUKTI_HOME`), and the same
choice is used whether you run from a checkout or through `npx`. Delete that file to forget it.
Without a terminal — piped output, or CI — Mukti never asks: it uses the first ready CLI and tells
you how to choose another.

## Claude Code vs. Antigravity
## Comparing the AI CLIs

The two are not equivalent, and the difference is worth knowing before you choose:
They are not equivalent, and the difference is worth knowing before you choose:

| | Claude Code (`claude`) | Antigravity (`agy`) |
| ------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Time per reply (measured) | about 13–20 seconds | about 30–60 seconds |
| Prompt size per reply | Mukti's own prompt and your conversation | about 15–25k input tokens: the same, plus agy's own ~14k-token coding-agent prompt, which cannot be replaced |
| Stored outside Mukti | whatever Claude Code itself records for a `claude -p` run | a conversation record (about 1 MB) per reply in your Antigravity history (`~/.gemini/antigravity-cli`), filed under a `mukti-socratic` project. Mukti never deletes these |
| | Claude Code (`claude`) | Codex (`codex`) | Antigravity (`agy`) |
| ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Time per reply (measured) | about 13–20 seconds | about 8–12 seconds | about 30–60 seconds |
| Prompt size per reply | Mukti's own prompt and your conversation | about 2.5–4.5k input tokens: Mukti's own prompt replaces Codex's, plus up to ~2k tokens of Codex context on some models | about 15–25k input tokens: the same, plus agy's own ~14k-token coding-agent prompt, which cannot be replaced |
| Stored outside Mukti | whatever Claude Code itself records for a `claude -p` run | nothing: runs are ephemeral and leave no Codex session history | a conversation record (about 1 MB) per reply in your Antigravity history (`~/.gemini/antigravity-cli`), filed under a `mukti-socratic` project. Mukti never deletes these |

### Codex: privacy and behavior notes

- Codex runs **read-only, with its tools switched off**: no shell, no file edits, no web search,
no images, no sub-agents. Asked to read a file outside its folder, it cannot.
- It runs from `~/.mukti/mukti-socratic`, never from your projects, so project `AGENTS.md` files
do not apply. Your Codex settings (`~/.codex/config.toml`, MCP servers, hooks, rules) do not
apply either, except your default model, which Mukti offers first.
- Your **global** `~/.codex/AGENTS.md` does apply: Codex has no way to leave it out, and a rule in
it can change what a reply says.
- Mukti's prompt, which can include your conversation, is written to a private file in
`~/.mukti/mukti-socratic/.mukti-codex/` for the length of one reply and then deleted. It never
appears on the command line.
- On startup, Mukti checks that your Codex version still accepts every setting it uses, without
running a model. If a Codex update renamed one, Mukti refuses to start and names it, rather than
running Codex with a tool switched back on.

### Antigravity: privacy and behavior notes

Expand All @@ -83,8 +103,9 @@ The two are not equivalent, and the difference is worth knowing before you choos
## Models

Pick a model from the model picker in the app. The list comes from the CLI you are running —
Claude Code's Sonnet / Opus / Haiku aliases, or whatever `agy models` reports — and the selection
is passed to that CLI's `--model`.
Claude Code's Sonnet / Opus / Haiku aliases, the models Codex lists for your account (your Codex
default first), or whatever `agy models` reports — and the selection is passed to that CLI's model
flag.

## What local mode does

Expand Down Expand Up @@ -112,7 +133,7 @@ npx muktiai [options]
--port, --web-port <n> Port for the web app (default 3001)
--api-port <n> Port for the API (default 3000)
--data-dir <path> Where to keep the database and logs (default ~/.mukti, or $MUKTI_HOME)
--provider <name> AI CLI to run on: claude or agy
--provider <name> AI CLI to run on: claude, codex, agy
--save Save --provider as your default for later runs
--choose Ask which AI CLI to use, and offer to save it
-h, --help Show help
Expand All @@ -122,8 +143,10 @@ npx muktiai [options]

- **First run is slow.** Mukti downloads a one-time embedded MongoDB binary (via
`mongodb-memory-server`). Later starts are fast.
- **"CLI not signed in".** Run `claude login`, or run `agy` once and finish its prompts, then
start Mukti again. Mukti never picks a CLI that is not signed in.
- **"CLI not signed in".** Run `claude login` or `codex login`, or run `agy` once and finish its
prompts, then start Mukti again. Mukti never picks a CLI that is not signed in.
- **"The installed Codex CLI rejected Mukti's settings".** Your Codex version no longer accepts a
setting Mukti uses to switch off its tools. Update Mukti, or use another CLI until it is updated.
- **Port 3000 or 3001 is busy.** `npx muktiai` moves to the next free port on its own.
To pick one yourself: `npx muktiai --port 4001 --api-port 4000`.
- **Start over.** Delete the data directory (`~/.mukti/local-db/` for npx, `.mukti/local-db/` in a
Expand Down
2 changes: 1 addition & 1 deletion packages/mukti-api/src/modules/ai/ai.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
* Resolves the active chat-client factory from the provider registry.
*
* @remarks
* Local-CLI providers (`claude-code`, `antigravity`) share
* Local-CLI providers (`claude-code`, `antigravity`, `codex`) share
* {@link LocalCliClientFactory} and differ only by adapter, so a new CLI is an
* adapter plus a registry entry rather than another branch here. Anything else
* is served by OpenRouter over HTTP.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { join } from 'path';

import { AntigravityCliAdapter } from '../adapters/antigravity-cli.adapter';
import { ClaudeCliAdapter } from '../adapters/claude-cli.adapter';
import { CodexCliAdapter } from '../adapters/codex-cli.adapter';
import { AiProviderRegistry } from '../ai-provider.registry';

/**
Expand Down Expand Up @@ -60,6 +61,14 @@ describe('AiProviderRegistry', () => {
expect(r.isLocalCliProvider()).toBe(true);
});

it('resolves codex to the Codex adapter', () => {
const r = registry({ AI_PROVIDER: 'codex' });

expect(r.getProviderId()).toBe('codex');
expect(r.getActiveLocalCliAdapter()).toBeInstanceOf(CodexCliAdapter);
expect(r.isLocalCliProvider()).toBe(true);
});

// The launcher asks for these CLIs by the command they run and passes that
// name straight through, so both spellings have to reach the same adapter.
it.each([
Expand All @@ -82,11 +91,36 @@ describe('AiProviderRegistry', () => {
expect(adapter.spawnOptions?.().cwd).toBe(realpathSync(workspace));
});

it.each(['antigravity', 'codex'])(
'runs %s from the workspace named by MUKTI_CLI_WORKSPACE, which wins over the older name',
(provider) => {
const workspace = join(root, 'shared-workspace');
const adapter = registry({
AI_PROVIDER: provider,
MUKTI_AGY_WORKSPACE: join(root, 'older-name'),
MUKTI_CLI_WORKSPACE: workspace,
}).getActiveLocalCliAdapter()!;

expect(adapter.spawnOptions?.().cwd).toBe(realpathSync(workspace));
},
);

it('runs codex from MUKTI_AGY_WORKSPACE when only the older name is set', () => {
const workspace = join(root, 'older-name');
const adapter = registry({
AI_PROVIDER: 'codex',
MUKTI_AGY_WORKSPACE: workspace,
MUKTI_CLI_WORKSPACE: ' ',
}).getActiveLocalCliAdapter()!;

expect(adapter.spawnOptions?.().cwd).toBe(realpathSync(workspace));
});

it('fails startup on an unknown provider, naming every supported value, without falling back', async () => {
const r = registry({ AI_PROVIDER: 'gemini-cli' });

await expect(r.onModuleInit()).rejects.toThrow(
'Unsupported AI_PROVIDER "gemini-cli". Supported values: openrouter, claude-code, antigravity, agy, claude.',
'Unsupported AI_PROVIDER "gemini-cli". Supported values: openrouter, claude-code, antigravity, codex, agy, claude.',
);
expect(r.isLocalCliProvider()).toBe(false);
});
Expand All @@ -95,12 +129,31 @@ describe('AiProviderRegistry', () => {
const warm = jest
.spyOn(AntigravityCliAdapter.prototype, 'warm')
.mockResolvedValue();
const warmCodex = jest
.spyOn(CodexCliAdapter.prototype, 'warm')
.mockResolvedValue();

await registry({ AI_PROVIDER: 'claude-code' }).onModuleInit();
expect(warm).not.toHaveBeenCalled();
expect(warmCodex).not.toHaveBeenCalled();

await registry({ AI_PROVIDER: 'antigravity' }).onModuleInit();
expect(warm).toHaveBeenCalledTimes(1);
expect(warmCodex).not.toHaveBeenCalled();

await registry({ AI_PROVIDER: 'codex' }).onModuleInit();
expect(warmCodex).toHaveBeenCalledTimes(1);
expect(warm).toHaveBeenCalledTimes(1);
});

it('fails startup when the Codex adapter rejects its own settings', async () => {
jest
.spyOn(CodexCliAdapter.prototype, 'warm')
.mockRejectedValue(new Error('unknown configuration field `x`'));

await expect(
registry({ AI_PROVIDER: 'codex' }).onModuleInit(),
).rejects.toThrow('unknown configuration field `x`');
});

// Preflight already checked sign-in; a failure here means the environment
Expand Down
Loading
Loading