@justin06lee/yagami 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/chunk-ASS6MJ7C.js +1821 -0
- package/dist/chunk-ASS6MJ7C.js.map +1 -0
- package/dist/chunk-M5UHR273.js +317 -0
- package/dist/chunk-M5UHR273.js.map +1 -0
- package/dist/cli.js +307 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine-pmCK3S7z.d.ts +381 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.js +259 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +85 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 justin06lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/yagami.png" alt="yagami" width="330" />
|
|
4
|
+
|
|
5
|
+
# yagami
|
|
6
|
+
|
|
7
|
+
**Your signed-in coding-agent CLIs as one self-hosted Anthropic-compatible API.**<br>
|
|
8
|
+
*Claude Code, Codex, OpenCode, Gemini CLI and any ACP agent — point any Anthropic client at your own subscriptions, or embed the engine as a library.*
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
yagami does the T3-Code trick, generalized: it drives the coding-agent CLIs you already installed and logged into — Claude Code through the Agent SDK (`pathToClaudeCodeExecutable`), Codex through `codex exec`, and OpenCode, Gemini CLI, Copilot, Cursor, Qwen Code, Kimi, Goose and the rest of the [ACP registry](https://agentclientprotocol.com) through the Agent Client Protocol. No API keys from any vendor, no separate auth — each spawned engine uses the same login your terminal sessions do. On top it serves `POST /v1/messages` with Anthropic request/response shapes and SSE streaming, so anything that accepts an Anthropic `baseURL` + `apiKey` can use it as a drop-in, and pick a harness per request with `model: "<provider>:<model>"`.
|
|
15
|
+
|
|
16
|
+
> **Personal use only.** This exists so *you* can point *your own tools* at *your own subscriptions*. Offering subscription-backed access to other people is against every one of these vendors' terms. Keep the endpoint private and don't share keys.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
make # bun install + build + install `yagami` onto your PATH (~/.local/bin)
|
|
22
|
+
yagami start # first run generates + saves an API key and prints it
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`make update` stops any running yagami server, rebuilds, reinstalls, and restarts it.
|
|
26
|
+
|
|
27
|
+
Or install the published package from npm:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bun add -g @justin06lee/yagami # global `yagami` CLI
|
|
31
|
+
bun add @justin06lee/yagami # or as a library (see Library mode)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
yagami v0.4.1
|
|
36
|
+
listening http://127.0.0.1:8787
|
|
37
|
+
provider claude — /Users/you/.local/bin/claude (2.1.238 (Claude Code))
|
|
38
|
+
also codex, opencode (use model "<provider>:<model>")
|
|
39
|
+
api key ygm_…
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then from any Anthropic client:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
46
|
+
|
|
47
|
+
const client = new Anthropic({
|
|
48
|
+
baseURL: "http://127.0.0.1:8787",
|
|
49
|
+
apiKey: process.env.YAGAMI_KEY, // your ygm_ key
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await client.messages.create({ model: "sonnet", max_tokens: 1024, messages: [{ role: "user", content: "hello" }] });
|
|
53
|
+
await client.messages.create({ model: "codex:gpt-5.6-sol", max_tokens: 1024, messages: [{ role: "user", content: "hello" }] });
|
|
54
|
+
await client.messages.create({ model: "opencode:anthropic/claude-sonnet-4", max_tokens: 1024, messages: [{ role: "user", content: "hello" }] });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or raw curl:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
curl http://127.0.0.1:8787/v1/messages \
|
|
61
|
+
-H "x-api-key: ygm_..." -H "content-type: application/json" \
|
|
62
|
+
-d '{"model":"codex","max_tokens":64,"messages":[{"role":"user","content":"ping"}]}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Providers
|
|
66
|
+
|
|
67
|
+
A bare model id goes to the **default provider** (Claude Code unless you change it). `"<provider>:<model>"` routes to another harness; a bare provider id (`"codex"`) means that harness's own default model. `GET /v1/models` and `yagami models` list everything that's actually installed, with ids ready to paste.
|
|
68
|
+
|
|
69
|
+
| Provider | Driven through | Resume | Images | System prompt | Thinking / effort |
|
|
70
|
+
|---|---|---|---|---|---|
|
|
71
|
+
| `claude` — Claude Code | Agent SDK → your `claude` binary | yes, forking | yes (+ documents) | native | native |
|
|
72
|
+
| `codex` — Codex CLI | `codex exec --json` (read-only sandbox) | yes | yes | emulated | effort only |
|
|
73
|
+
| `opencode`, `gemini`, `copilot`, `cursor`, `qwen`, `goose`, `kimi`, `kilo`, `cline`, `auggie`, `amp`, `grok`, `droid`, `codex-acp`, `claude-acp` | Agent Client Protocol over stdio | if the agent supports it | if the agent supports it | emulated | — |
|
|
74
|
+
|
|
75
|
+
"Emulated" means the system prompt is folded into the user turn as a `<system>` block; unsupported `thinking`/`effort` are accepted and reported in `x-yagami-ignored` rather than rejected. Without native forking, a resumed session is single-use: a sibling branch of the same conversation falls back to transcript replay instead of corrupting the shared session.
|
|
76
|
+
|
|
77
|
+
Any other ACP agent works too — add it to config with its launch command:
|
|
78
|
+
|
|
79
|
+
```jsonc
|
|
80
|
+
{
|
|
81
|
+
"defaultProvider": "claude",
|
|
82
|
+
"providers": {
|
|
83
|
+
"codex": { "sandbox": "read-only" }, // "workspace-write" if you trust the callers
|
|
84
|
+
"gemini": { "path": "/opt/homebrew/bin/gemini" },
|
|
85
|
+
"my-agent": { "command": "my-agent", "args": ["acp"], "label": "My Agent" },
|
|
86
|
+
"goose": { "enabled": false } // hide a preset even if installed
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`yagami doctor` shows every known harness, whether it's installed and signed in, its version, and — for Claude — whether the bundled Agent SDK build matches your binary. Launch commands come from the ACP registry; sign-in hints are best-effort.
|
|
92
|
+
|
|
93
|
+
### CLI
|
|
94
|
+
|
|
95
|
+
| Command | What it does |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `yagami start` | Start the server (`-p` port, `-H` host, `--provider <id>` default provider, `--claude <path>`, `--cors`). Add `--daemon` to run it in the background (`--log <file>` overrides the default log at `~/.config/yagami/yagami.log`) |
|
|
98
|
+
| `yagami stop` | Stop the running server |
|
|
99
|
+
| `yagami status` | Show whether it's running, plus uptime, request count, and cumulative would-be API cost |
|
|
100
|
+
| `yagami models` | List models across every installed provider (`--provider <id>` to filter) |
|
|
101
|
+
| `yagami keygen` | Generate another API key and save it to the config |
|
|
102
|
+
| `yagami doctor` | Check every harness CLI; `--live` sends one tiny real completion (`--provider <id>` to pick which) |
|
|
103
|
+
|
|
104
|
+
Every request is logged as one line (time, status, model, duration, cost, session) to stdout — or to the log file in `--daemon` mode.
|
|
105
|
+
|
|
106
|
+
### Config
|
|
107
|
+
|
|
108
|
+
`~/.config/yagami/config.json` (override dir with `YAGAMI_CONFIG_DIR`):
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"host": "127.0.0.1", // keep loopback unless you know what you're doing
|
|
113
|
+
"port": 8787,
|
|
114
|
+
"apiKeys": ["ygm_..."],
|
|
115
|
+
"defaultProvider": "claude", // provider for bare model ids
|
|
116
|
+
"defaultModel": "sonnet", // used when a request omits `model` (may be "provider:model")
|
|
117
|
+
"providers": { ... }, // see above; every preset is auto-detected when omitted
|
|
118
|
+
"cors": false
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Env overrides: `YAGAMI_HOST`, `YAGAMI_PORT`, `YAGAMI_API_KEY`, `YAGAMI_PROVIDER`, `YAGAMI_DEFAULT_MODEL`, `YAGAMI_CLAUDE_PATH`, `YAGAMI_CODEX_PATH`. The older `claudePath` / `claudeConfigDir` keys still work as shorthands for `providers.claude`.
|
|
123
|
+
|
|
124
|
+
## Library mode
|
|
125
|
+
|
|
126
|
+
For apps that want the engine in-process with no HTTP hop (e.g. a desktop app):
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { YagamiEngine, claudeCodeSession, ClaudeProvider, CodexProvider, AcpProvider } from "@justin06lee/yagami";
|
|
130
|
+
|
|
131
|
+
// 1. Anthropic-shaped completions across every installed harness:
|
|
132
|
+
const engine = new YagamiEngine({ defaultModel: "sonnet" });
|
|
133
|
+
const { response } = await engine.complete({ messages: [{ role: "user", content: "hello" }] });
|
|
134
|
+
const codex = await engine.complete({ model: "codex", messages: [{ role: "user", content: "hello" }] });
|
|
135
|
+
|
|
136
|
+
// streaming (Anthropic SSE event objects):
|
|
137
|
+
const { events } = engine.stream({ model: "opencode", messages: [...], stream: true });
|
|
138
|
+
for await (const ev of events) { /* ev.event, ev.data */ }
|
|
139
|
+
|
|
140
|
+
// hand-pick providers instead of auto-detecting:
|
|
141
|
+
const custom = new YagamiEngine({
|
|
142
|
+
providers: [new ClaudeProvider(), new AcpProvider({ id: "gemini", label: "Gemini", command: "gemini", args: ["--acp"] })],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 2. Full agentic Claude Code sessions — tools, permissions, the works.
|
|
146
|
+
for await (const msg of claudeCodeSession("fix the failing test", {
|
|
147
|
+
options: { cwd: "/path/to/project", permissionMode: "acceptEdits" },
|
|
148
|
+
})) {
|
|
149
|
+
// render SDK messages however you like
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Every provider implements one small `Provider` contract (`run(turn)` → normalized `session`/`text`/`thinking`/`done` events, plus `listModels()` and `version()`), so adding a harness that isn't ACP-capable is one file. Failures are typed: `AuthRequiredError` (carries the login command), `ProviderNotInstalledError` (carries the install hint), `ProviderError`.
|
|
154
|
+
|
|
155
|
+
### Building a UI on Claude Code
|
|
156
|
+
|
|
157
|
+
`YagamiEngine` is completions-only by design. To build an actual coding UI — tools, permissions, plan mode, a warm session across turns — use `AgentSession`, which wraps the full Claude Code agent with the lifecycle the interactive terminal gives you for free:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { AgentSession } from "@justin06lee/yagami";
|
|
161
|
+
|
|
162
|
+
const session = new AgentSession({
|
|
163
|
+
cwd: "/path/to/project",
|
|
164
|
+
parity: "terminal", // load your CLAUDE.md, skills, hooks, .mcp.json — like the CLI
|
|
165
|
+
appName: "my-app", // reported to Claude as the client
|
|
166
|
+
onPermission: async (req) => {
|
|
167
|
+
// Your approve/deny UI. Policy stays here; yagami owns the state machine.
|
|
168
|
+
const ok = await showDialog(req.toolName, req.input);
|
|
169
|
+
return ok ? { behavior: "allow" } : { behavior: "deny", message: "user declined" };
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
session.send("fix the failing test"); // process starts here and stays warm
|
|
174
|
+
for await (const msg of session) { // raw SDKMessages — render however you like
|
|
175
|
+
render(msg);
|
|
176
|
+
if (msg.type === "result") break;
|
|
177
|
+
}
|
|
178
|
+
session.send("now add a test for the edge case"); // next turn resumes the same session
|
|
179
|
+
await session.interrupt(); // the CLI's Esc
|
|
180
|
+
await session.setModel("opus"); // the CLI's /model
|
|
181
|
+
await session.setPermissionMode("plan"); // shift+tab
|
|
182
|
+
session.close();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This resolves the parts of embedding Claude Code that every host would otherwise reimplement identically — process lifecycle, session resume, interrupt, settings parity, and the permission state machine. What stays yours are the genuinely app-specific choices: rendering the `SDKMessage` stream, deciding what to auto-approve, and picking the working directory. `parity` is `"terminal"` (load user+project+local settings, matching your CLI), `"project"` (project+local only), or `"isolated"` (load nothing — reproducible, no personal config). The permission `fallback` defaults to `"deny"`, so a session is safe before the UI is wired up; `autoAllow`/`autoDeny` skip the handler for named tools.
|
|
186
|
+
|
|
187
|
+
For a lower-level handle, `claudeCodeSession(prompt, { options })` returns the raw Agent SDK `Query`.
|
|
188
|
+
|
|
189
|
+
The server is also embeddable: `import { startYagami } from "@justin06lee/yagami/server"`.
|
|
190
|
+
|
|
191
|
+
## How it works
|
|
192
|
+
|
|
193
|
+
- **Engine**: each `/v1/messages` request becomes one sandboxed turn on the chosen harness. Claude runs with `tools: []`, `settingSources: []` (your CLAUDE.md/skills never leak into API completions), `maxTurns: 1` and a deny-all permission callback; Codex runs in its read-only sandbox with no approvals; ACP agents are moved to a plan/read-only mode when they offer one and every permission request is refused. All of them work in a throwaway directory. The API is text-in/text-out; a leaked key can burn tokens but never edit anything on the host — though note that agents other than Claude keep their own read-only tools, so they can still *look* at that empty directory.
|
|
194
|
+
- **Multi-turn**: the Messages API is stateless but harness sessions aren't. yagami hashes each conversation prefix (per provider) and remembers which session produced it; a follow-up request resumes that session and sends only the new user message. Unmatched histories fall back to replaying the transcript in a single prompt, and if a cached session turns out to be gone, the stale mapping is dropped and the request transparently retries via replay. The cache persists across restarts at `~/.config/yagami/sessions.json`.
|
|
195
|
+
- **Streaming**: every harness's output is normalized into deltas and re-emitted as a proper Anthropic SSE sequence — `message_start` → thinking/text content blocks → `message_delta` → `message_stop`. Claude and ACP agents stream tokens; Codex streams per message part.
|
|
196
|
+
- **Models**: `GET /v1/models` asks each installed CLI what it supports (Claude via the SDK, Codex via its app-server protocol, ACP agents via their session config) — probed once per process, then cached. Failed probes are skipped and retried next time; a static fallback list is served only if nothing answers (`x-yagami-models-source` says which).
|
|
197
|
+
- **Auth**: `x-api-key` or `Authorization: Bearer`, compared in constant time. Binds to `127.0.0.1` by default and warns loudly on anything else.
|
|
198
|
+
|
|
199
|
+
Extra response headers: `x-yagami-provider`, `x-yagami-cost-usd` (what the turn would have cost at API prices, when the harness reports it), `x-yagami-session`, `x-yagami-ignored` (accepted-but-unsupported params). `/healthz` (unauthenticated) reports the default provider, installed providers, uptime, request count, and the cumulative would-be cost — `yagami status` shows the same.
|
|
200
|
+
|
|
201
|
+
## Limitations
|
|
202
|
+
|
|
203
|
+
- No `tools` / `tool_choice` (rejected with 400 — by design, see above). `tool_use`/`tool_result` content blocks are rejected too.
|
|
204
|
+
- User messages may contain `text`, `image`, and `document` blocks (documents: Claude only; images: base64 sources only outside Claude); `system` and assistant messages are text-only. Thinking blocks echoed back in assistant history are dropped, not rejected. A conversation whose *history* contains images/documents can only be continued while the server that produced it still has that session cached.
|
|
205
|
+
- Assistant prefill (a trailing `assistant` message) is emulated: the engine is instructed to continue from the prefill text, and the response carries only the continuation, like the real API. An accidentally repeated prefill is stripped from the reply, including mid-stream.
|
|
206
|
+
- `max_tokens`, `temperature`, `top_p`, `top_k`, `stop_sequences` are accepted but ignored (reported via `x-yagami-ignored`) — none of the CLI engines expose them.
|
|
207
|
+
- `thinking` and a yagami-extension `effort` ("low"…"max") are passed through where the harness supports them (see the provider table) and reported as ignored elsewhere.
|
|
208
|
+
- Cost is reported only by harnesses that price their own turns (Claude, OpenCode); Codex reports token usage without cost.
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
bun run typecheck && bun run test # unit tests (no tokens spent)
|
|
214
|
+
bun run smoke # live end-to-end through your real Claude CLI (tiny token cost)
|
|
215
|
+
bun run live:providers # live check across every installed harness (tiny token cost each)
|
|
216
|
+
make build # build dist/ only
|
|
217
|
+
```
|