@oxbit/cli 0.11.3 → 0.11.5
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/agent/dist/assets/{index-DU-a4mnM.js → index-Crr8fCGQ.js} +18 -6
- package/agent/dist/assets/{memory-Bdy8hvTl.js → memory-C3ixKimB.js} +11 -7
- package/agent/dist/index.js +302 -36
- package/package.json +1 -1
- package/runtime/dist/index.js +5 -3
- package/runtime/package.json +1 -1
- package/web/dist/assets/{index-CMDbkm04.css → index-BTkGIkXb.css} +1 -1
- package/web/dist/assets/{index-C8DIIrnZ.js → index-BxpGWZaW.js} +225 -225
- package/web/dist/assets/{typescript-Z_qrZEol.js → typescript-BpDj41Lu.js} +2 -2
- package/web/dist/index.html +2 -2
- package/web/dist/preview-shell.html +12 -0
- package/web/dist/schemas/settings.v1.schema.json +1 -0
- package/web/dist/sdk/index.js +1 -0
package/agent/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
import { parseArgs } from "node:util";
|
|
3
4
|
import { createInterface } from "node:readline";
|
|
4
5
|
import { mkdir, readFile as readFile$1, writeFile as writeFile$1, chmod, rename, stat, rm, readdir, realpath, unlink, open, access, cp } from "node:fs/promises";
|
|
5
6
|
import { join, dirname, isAbsolute, sep, basename, resolve } from "node:path";
|
|
6
7
|
import { randomBytes, createHash, randomUUID } from "node:crypto";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
10
|
async function* lines(chunks) {
|
|
11
11
|
let buffer = "";
|
|
@@ -1124,6 +1124,73 @@ Additional focus: ${focus}` : "");
|
|
|
1124
1124
|
}
|
|
1125
1125
|
}
|
|
1126
1126
|
}
|
|
1127
|
+
const OXBIT_CLI_PROVIDER = "oxbit-cli";
|
|
1128
|
+
const ACP_PROVIDERS = [
|
|
1129
|
+
{
|
|
1130
|
+
id: OXBIT_CLI_PROVIDER,
|
|
1131
|
+
name: "Oxbit Agent CLI",
|
|
1132
|
+
command: "oxbit-agent",
|
|
1133
|
+
args: ["acp"],
|
|
1134
|
+
setup: "Runs the oxbit-agent CLI that ships with Oxbit. It reads providers, keys, MCP servers, skills and hooks from ~/.oxbit/agent. Elsewhere, install @oxbit/agent from npm.",
|
|
1135
|
+
url: "https://github.com/yannelli/oxbit/tree/main/apps/agent",
|
|
1136
|
+
registryId: "oxbit-agent"
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
id: "codex",
|
|
1140
|
+
name: "Codex ACP",
|
|
1141
|
+
command: "npx",
|
|
1142
|
+
args: ["-y", "@agentclientprotocol/codex-acp@2.2.2"],
|
|
1143
|
+
setup: "Uses the Codex ACP adapter. Sign in through an advertised authentication method, or use your existing Codex credentials.",
|
|
1144
|
+
url: "https://github.com/agentclientprotocol/codex-acp",
|
|
1145
|
+
registryId: "codex-acp"
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
id: "claude",
|
|
1149
|
+
name: "Claude Agent",
|
|
1150
|
+
command: "npx",
|
|
1151
|
+
args: ["-y", "@agentclientprotocol/claude-agent-acp@0.89.1"],
|
|
1152
|
+
setup: "Uses the Claude Agent ACP adapter. Sign in through an advertised authentication method, or use your existing Claude Code credentials.",
|
|
1153
|
+
url: "https://github.com/agentclientprotocol/claude-agent-acp",
|
|
1154
|
+
registryId: "claude-acp"
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
id: "gemini",
|
|
1158
|
+
name: "Gemini CLI",
|
|
1159
|
+
command: "npx",
|
|
1160
|
+
args: ["-y", "@google/gemini-cli@0.63.0", "--acp"],
|
|
1161
|
+
setup: "Runs Gemini CLI in ACP mode. Sign in through an advertised authentication method, or set GEMINI_API_KEY in the runtime environment.",
|
|
1162
|
+
url: "https://github.com/google-gemini/gemini-cli",
|
|
1163
|
+
registryId: "gemini"
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
id: "copilot",
|
|
1167
|
+
name: "GitHub Copilot",
|
|
1168
|
+
command: "npx",
|
|
1169
|
+
args: ["-y", "@github/copilot@1.0.95", "--acp"],
|
|
1170
|
+
setup: "Runs GitHub Copilot CLI in ACP mode. Sign in through an advertised authentication method, or run copilot and use /login first.",
|
|
1171
|
+
url: "https://github.com/github/copilot-cli",
|
|
1172
|
+
registryId: "github-copilot-cli"
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
id: "cursor",
|
|
1176
|
+
name: "Cursor ACP",
|
|
1177
|
+
command: "agent",
|
|
1178
|
+
args: ["acp"],
|
|
1179
|
+
setup: "Install Cursor CLI and run agent login first. If your executable is cursor-agent, change the command below.",
|
|
1180
|
+
url: "https://cursor.com/docs/cli/acp",
|
|
1181
|
+
registryId: "cursor"
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
id: "amp",
|
|
1185
|
+
name: "Amp Agent ACP",
|
|
1186
|
+
command: "npx",
|
|
1187
|
+
args: ["-y", "amp-acp@0.10.0"],
|
|
1188
|
+
setup: "Uses the community Amp ACP adapter. Install Amp CLI and run amp login first. Set AMP_CLI_PATH in the runtime environment if needed.",
|
|
1189
|
+
url: "https://github.com/tao12345666333/amp-acp",
|
|
1190
|
+
registryId: "amp-acp"
|
|
1191
|
+
}
|
|
1192
|
+
];
|
|
1193
|
+
const acpPreset = (id2) => ACP_PROVIDERS.find((provider2) => provider2.id === id2);
|
|
1127
1194
|
const OXBIT_SUBAGENT_TOOL = "task";
|
|
1128
1195
|
const FORWARDED_HOOKS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse", "PostToolUseFailure", "PreCompact", "PostCompact"]);
|
|
1129
1196
|
const childCallId = (spawnId, id2) => `${spawnId}/${id2}`;
|
|
@@ -4796,19 +4863,18 @@ class FileCatalogCache {
|
|
|
4796
4863
|
}
|
|
4797
4864
|
}
|
|
4798
4865
|
function agentVersion() {
|
|
4799
|
-
return "0.11.
|
|
4866
|
+
return "0.11.5";
|
|
4800
4867
|
}
|
|
4801
4868
|
const clone = (message2) => JSON.parse(JSON.stringify(message2));
|
|
4802
|
-
class
|
|
4803
|
-
server;
|
|
4869
|
+
class AcpClient {
|
|
4804
4870
|
client;
|
|
4805
4871
|
listeners = /* @__PURE__ */ new Set();
|
|
4806
4872
|
permission;
|
|
4807
4873
|
waiting = /* @__PURE__ */ new Map();
|
|
4808
4874
|
/** The initialize result. */
|
|
4809
4875
|
initialized = {};
|
|
4810
|
-
constructor(
|
|
4811
|
-
this.client = new JsonRpcPeer(
|
|
4876
|
+
constructor(send) {
|
|
4877
|
+
this.client = new JsonRpcPeer(send, {
|
|
4812
4878
|
request: (method, params) => this.serve(method, params),
|
|
4813
4879
|
notification: (method, params) => {
|
|
4814
4880
|
if (method !== "session/update") return;
|
|
@@ -4820,16 +4886,12 @@ class MemoryConnection {
|
|
|
4820
4886
|
}
|
|
4821
4887
|
}
|
|
4822
4888
|
});
|
|
4823
|
-
this.server = new AcpServer((message2) => this.client.receive(clone(message2)), factory);
|
|
4824
4889
|
}
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
const connection = new MemoryConnection(factory);
|
|
4828
|
-
connection.initialized = await connection.request(
|
|
4890
|
+
async initialize(capabilities) {
|
|
4891
|
+
this.initialized = await this.request(
|
|
4829
4892
|
"initialize",
|
|
4830
4893
|
{ protocolVersion: 1, clientCapabilities: capabilities, clientInfo: { name: "oxbit-agent", version: agentVersion() } }
|
|
4831
4894
|
);
|
|
4832
|
-
return connection;
|
|
4833
4895
|
}
|
|
4834
4896
|
request(method, params = {}) {
|
|
4835
4897
|
return this.client.request(method, params);
|
|
@@ -4856,8 +4918,14 @@ class MemoryConnection {
|
|
|
4856
4918
|
setMode(sessionId, modeId) {
|
|
4857
4919
|
return this.request("session/set_mode", { sessionId, modeId });
|
|
4858
4920
|
}
|
|
4859
|
-
|
|
4860
|
-
|
|
4921
|
+
/** session/set_model, else the `model` config option for agents without that method. */
|
|
4922
|
+
async setModel(sessionId, modelId) {
|
|
4923
|
+
try {
|
|
4924
|
+
return await this.request("session/set_model", { sessionId, modelId });
|
|
4925
|
+
} catch (error) {
|
|
4926
|
+
if (error.code !== -32601) throw error;
|
|
4927
|
+
return this.setConfigOption(sessionId, "model", modelId);
|
|
4928
|
+
}
|
|
4861
4929
|
}
|
|
4862
4930
|
setConfigOption(sessionId, configId, value2) {
|
|
4863
4931
|
return this.request("session/set_config_option", { sessionId, configId, value: value2 });
|
|
@@ -4873,9 +4941,6 @@ class MemoryConnection {
|
|
|
4873
4941
|
if (this.permission === handler2) this.permission = void 0;
|
|
4874
4942
|
};
|
|
4875
4943
|
}
|
|
4876
|
-
close() {
|
|
4877
|
-
return this.server.close("The connection was closed");
|
|
4878
|
-
}
|
|
4879
4944
|
async serve(method, params) {
|
|
4880
4945
|
if (method !== "session/request_permission") throw Object.assign(new Error(`Method not found: ${method}`), { code: -32601 });
|
|
4881
4946
|
const handler2 = this.permission;
|
|
@@ -4892,6 +4957,115 @@ class MemoryConnection {
|
|
|
4892
4957
|
}
|
|
4893
4958
|
}
|
|
4894
4959
|
}
|
|
4960
|
+
class MemoryConnection extends AcpClient {
|
|
4961
|
+
server;
|
|
4962
|
+
constructor(factory) {
|
|
4963
|
+
super((message2) => this.server.receive(clone(message2)));
|
|
4964
|
+
this.server = new AcpServer((message2) => this.client.receive(clone(message2)), factory);
|
|
4965
|
+
}
|
|
4966
|
+
/** Connects and sends initialize. Without fs or terminal capabilities the server serves them locally. */
|
|
4967
|
+
static async open(factory, capabilities = {}) {
|
|
4968
|
+
const connection = new MemoryConnection(factory);
|
|
4969
|
+
await connection.initialize(capabilities);
|
|
4970
|
+
return connection;
|
|
4971
|
+
}
|
|
4972
|
+
close() {
|
|
4973
|
+
return this.server.close("The connection was closed");
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
class ProcessConnection extends AcpClient {
|
|
4977
|
+
constructor(child, rewrite) {
|
|
4978
|
+
super((message2) => void (child.stdin.writable && child.stdin.write(`${JSON.stringify(message2)}
|
|
4979
|
+
`)));
|
|
4980
|
+
this.child = child;
|
|
4981
|
+
this.rewrite = rewrite;
|
|
4982
|
+
child.stdin.on("error", () => {
|
|
4983
|
+
});
|
|
4984
|
+
child.stderr.on("data", (chunk) => void (this.stderr = (this.stderr + chunk).slice(-4e3)));
|
|
4985
|
+
createInterface({ input: child.stdout, crlfDelay: Infinity }).on("line", (line) => {
|
|
4986
|
+
if (!line.trim()) return;
|
|
4987
|
+
let message2;
|
|
4988
|
+
try {
|
|
4989
|
+
message2 = JSON.parse(line);
|
|
4990
|
+
} catch {
|
|
4991
|
+
return;
|
|
4992
|
+
}
|
|
4993
|
+
this.client.receive(message2);
|
|
4994
|
+
});
|
|
4995
|
+
this.exited = new Promise((resolve2) => child.once("close", (code, signal) => {
|
|
4996
|
+
this.client.close(`The agent exited (${signal ?? `code ${code}`})${this.stderr.trim() ? `: ${this.stderr.trim().split("\n").at(-1)}` : ""}`);
|
|
4997
|
+
resolve2();
|
|
4998
|
+
}));
|
|
4999
|
+
}
|
|
5000
|
+
child;
|
|
5001
|
+
rewrite;
|
|
5002
|
+
static CLOSE_GRACE_MS = 2e3;
|
|
5003
|
+
stderr = "";
|
|
5004
|
+
exited;
|
|
5005
|
+
/** Starts `agent` in `cwd` and sends initialize. `rewrite` replaces error replies, such as an auth_required error with sign-in steps. */
|
|
5006
|
+
static async open(agent, cwd, options = {}) {
|
|
5007
|
+
const child = spawn(agent.command, agent.args, { cwd, env: agent.env ?? process.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
5008
|
+
const failed2 = new Promise((_, reject) => child.once("error", (error) => reject(new Error(`Could not start ${agent.command}: ${error.message}`))));
|
|
5009
|
+
const connection = new ProcessConnection(child, options.rewrite);
|
|
5010
|
+
try {
|
|
5011
|
+
await Promise.race([connection.initialize(options.capabilities ?? {}), failed2]);
|
|
5012
|
+
} catch (error) {
|
|
5013
|
+
await connection.close();
|
|
5014
|
+
throw error;
|
|
5015
|
+
}
|
|
5016
|
+
return connection;
|
|
5017
|
+
}
|
|
5018
|
+
async request(method, params = {}) {
|
|
5019
|
+
try {
|
|
5020
|
+
return await super.request(method, params);
|
|
5021
|
+
} catch (error) {
|
|
5022
|
+
throw this.rewrite?.(error) ?? error;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
async close() {
|
|
5026
|
+
if (this.child.pid === void 0 || this.child.exitCode !== null || this.child.signalCode !== null) return;
|
|
5027
|
+
this.child.stdin.end();
|
|
5028
|
+
const timer = setTimeout(() => this.child.kill(), ProcessConnection.CLOSE_GRACE_MS);
|
|
5029
|
+
await this.exited;
|
|
5030
|
+
clearTimeout(timer);
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
const CLAUDE_PLAN = { id: "claude", name: "Claude plan", inUse: "Using Claude plan" };
|
|
5034
|
+
const CLAUDE_SIGN_IN = "Sign in with your Claude plan first: oxbit-agent login claude";
|
|
5035
|
+
const INSTALL = "Install Claude Code first: https://code.claude.com/docs/en/setup";
|
|
5036
|
+
const CLAUDE_MODES = { ask: "default", "accept-edits": "acceptEdits", "full-auto": "bypassPermissions" };
|
|
5037
|
+
function claudeAgent(env) {
|
|
5038
|
+
const preset = acpPreset("claude");
|
|
5039
|
+
const { ANTHROPIC_API_KEY: _key, ANTHROPIC_AUTH_TOKEN: _token, ...rest } = env;
|
|
5040
|
+
return { command: preset.command, args: [...preset.args], env: rest };
|
|
5041
|
+
}
|
|
5042
|
+
const openClaude = (agent, cwd) => ProcessConnection.open(agent, cwd, {
|
|
5043
|
+
rewrite: (error) => error.code === -32e3 && /auth/i.test(error.message) ? Object.assign(new Error(CLAUDE_SIGN_IN), { code: error.code }) : error
|
|
5044
|
+
});
|
|
5045
|
+
const run$1 = (args, env, stdio) => new Promise((resolve2, reject) => {
|
|
5046
|
+
const child = spawn("claude", args, { env, stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "ignore"] });
|
|
5047
|
+
let stdout = "";
|
|
5048
|
+
child.stdout?.on("data", (chunk) => void (stdout += chunk));
|
|
5049
|
+
child.once("error", (error) => reject(error.code === "ENOENT" ? Object.assign(new Error(INSTALL), { missing: true }) : error));
|
|
5050
|
+
child.once("close", (code) => resolve2({ code, stdout }));
|
|
5051
|
+
});
|
|
5052
|
+
async function claudeStatus(env) {
|
|
5053
|
+
try {
|
|
5054
|
+
const { stdout } = await run$1(["auth", "status", "--json"], claudeAgent(env).env, "pipe");
|
|
5055
|
+
const status2 = JSON.parse(stdout);
|
|
5056
|
+
return {
|
|
5057
|
+
installed: true,
|
|
5058
|
+
loggedIn: status2?.loggedIn === true,
|
|
5059
|
+
...status2?.authMethod === "claude.ai" && typeof status2.subscriptionType === "string" ? { plan: status2.subscriptionType } : {}
|
|
5060
|
+
};
|
|
5061
|
+
} catch (error) {
|
|
5062
|
+
return { installed: !error.missing, loggedIn: false };
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
const planText = (status2) => !status2.installed ? "Claude Code not installed" : !status2.loggedIn ? "not signed in" : status2.plan ? `signed in with Claude ${status2.plan[0].toUpperCase()}${status2.plan.slice(1)}` : "signed in";
|
|
5066
|
+
async function claudeAuth(command, env) {
|
|
5067
|
+
return (await run$1(["auth", command], claudeAgent(env).env, "inherit")).code ?? 1;
|
|
5068
|
+
}
|
|
4895
5069
|
function globSource(glob2) {
|
|
4896
5070
|
let source = "";
|
|
4897
5071
|
for (let index = 0; index < glob2.length; index++) {
|
|
@@ -5194,10 +5368,11 @@ function nodeMcp(config, log) {
|
|
|
5194
5368
|
clientInfo: { name: "oxbit-agent", version: agentVersion() }
|
|
5195
5369
|
});
|
|
5196
5370
|
}
|
|
5197
|
-
const PROVIDER_IDS = PROVIDERS.map((spec) => spec.id);
|
|
5371
|
+
const PROVIDER_IDS = [...PROVIDERS.map((spec) => spec.id), CLAUDE_PLAN.id];
|
|
5198
5372
|
const LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
5199
5373
|
const OLLAMA$1 = "http://127.0.0.1:11434";
|
|
5200
5374
|
const DETECTED = ["anthropic", "openai", "openrouter"];
|
|
5375
|
+
const isClaude = (choice) => choice.id === CLAUDE_PLAN.id;
|
|
5201
5376
|
function normalizeBase(input2) {
|
|
5202
5377
|
let url;
|
|
5203
5378
|
try {
|
|
@@ -5237,6 +5412,11 @@ async function methodFor(spec, input2, signedIn, baseUrl) {
|
|
|
5237
5412
|
if (ids.includes("api-key") && (ids.length === 1 || await keyFor(spec, input2, baseUrl))) return "api-key";
|
|
5238
5413
|
return ids.includes("none") ? "none" : ids.find((id2) => id2 !== "chatgpt") ?? ids[0];
|
|
5239
5414
|
}
|
|
5415
|
+
async function activeProvider(input2) {
|
|
5416
|
+
if ((input2.provider || input2.settings.provider) !== CLAUDE_PLAN.id) return selectProvider(input2);
|
|
5417
|
+
const model = input2.model || input2.settings.providers?.[CLAUDE_PLAN.id]?.model;
|
|
5418
|
+
return { id: CLAUDE_PLAN.id, ...model ? { model } : {} };
|
|
5419
|
+
}
|
|
5240
5420
|
async function selectProvider(input2) {
|
|
5241
5421
|
const { settings, env } = input2;
|
|
5242
5422
|
const signedIn = await chatgptSignedIn(input2.auth);
|
|
@@ -5252,6 +5432,7 @@ async function selectProvider(input2) {
|
|
|
5252
5432
|
}
|
|
5253
5433
|
const name = input2.provider || settings.provider || detected || "ollama";
|
|
5254
5434
|
const spec = providerSpec(name);
|
|
5435
|
+
if (name === CLAUDE_PLAN.id) throw new Error(`${CLAUDE_PLAN.name} runs Claude Agent and has no model provider settings`);
|
|
5255
5436
|
if (!spec) throw new Error(`Unknown provider ${name}. Use one of: ${PROVIDER_IDS.join(", ")}`);
|
|
5256
5437
|
const baseUrl = baseFor(spec, settings, env);
|
|
5257
5438
|
const method = await methodFor(spec, input2, signedIn, baseUrl);
|
|
@@ -6159,13 +6340,19 @@ function methodList() {
|
|
|
6159
6340
|
lines2.push(` ${spec.name.padEnd(18)} ${method.label.padEnd(24)} ${command}`);
|
|
6160
6341
|
if (method.detail) lines2.push(` ${"".padEnd(18)} ${method.detail}`);
|
|
6161
6342
|
}
|
|
6343
|
+
lines2.push(
|
|
6344
|
+
` ${CLAUDE_PLAN.name.padEnd(18)} ${"Claude Code sign-in".padEnd(24)} oxbit-agent login claude`,
|
|
6345
|
+
` ${"".padEnd(18)} Runs Claude Agent, which uses Claude Code's own sign-in.`
|
|
6346
|
+
);
|
|
6162
6347
|
return `${lines2.join("\n")}
|
|
6163
6348
|
`;
|
|
6164
6349
|
}
|
|
6165
6350
|
async function login({ config, auth, io, input: input2 }, rest) {
|
|
6166
6351
|
if (rest[0] === "chatgpt" && rest.length === 1) return loginChatGPT({ config, auth, io });
|
|
6352
|
+
if (rest[0] === CLAUDE_PLAN.id && rest.length === 1) return loginClaude({ config, io });
|
|
6167
6353
|
if (rest.length) throw new UsageError(`Unknown login method ${rest.join(" ")}. Run oxbit-agent login to list the methods`);
|
|
6168
6354
|
if (!input2.provider) return void io.stdout.write(methodList()), 0;
|
|
6355
|
+
if (input2.provider === CLAUDE_PLAN.id) return loginClaude({ config, io });
|
|
6169
6356
|
const spec = providerSpec(input2.provider);
|
|
6170
6357
|
if (!spec) throw new UsageError(`Unknown provider ${input2.provider}`);
|
|
6171
6358
|
const choice = await apiKeyChoice(input2, spec.id);
|
|
@@ -6208,9 +6395,20 @@ ${CHATGPT_LABELS.manage}: ${CHATGPT.usageUrl}
|
|
|
6208
6395
|
`);
|
|
6209
6396
|
return 0;
|
|
6210
6397
|
}
|
|
6398
|
+
async function loginClaude({ config, io }) {
|
|
6399
|
+
io.stderr.write("Signing in to Claude Code with `claude auth login`\n");
|
|
6400
|
+
const code = await claudeAuth("login", io.env);
|
|
6401
|
+
if (code !== 0) return code;
|
|
6402
|
+
if (!config.settings.provider) await config.saveSettings({ provider: CLAUDE_PLAN.id });
|
|
6403
|
+
else if (config.settings.provider !== CLAUDE_PLAN.id)
|
|
6404
|
+
io.stdout.write(`settings.json selects ${config.settings.provider}; run with --provider claude to use your Claude plan.
|
|
6405
|
+
`);
|
|
6406
|
+
return 0;
|
|
6407
|
+
}
|
|
6211
6408
|
async function logout({ auth, io, input: input2 }, rest) {
|
|
6212
6409
|
if (rest.length > 1) throw new UsageError(`Unexpected argument ${rest[1]}`);
|
|
6213
6410
|
const target = rest[0] ?? input2.provider;
|
|
6411
|
+
if (target === CLAUDE_PLAN.id) return claudeAuth("logout", io.env);
|
|
6214
6412
|
const removed = target === "chatgpt" ? [] : await removeApiKeys(auth, input2, target);
|
|
6215
6413
|
if (!target || target === "chatgpt") {
|
|
6216
6414
|
const revoked = await signOutChatGPT(auth, fetchOr(io.fetch));
|
|
@@ -6221,7 +6419,9 @@ async function logout({ auth, io, input: input2 }, rest) {
|
|
|
6221
6419
|
return 0;
|
|
6222
6420
|
}
|
|
6223
6421
|
async function models({ io, input: input2 }) {
|
|
6224
|
-
const
|
|
6422
|
+
const active = await activeProvider(input2);
|
|
6423
|
+
if (isClaude(active)) return claudeModels(io, active);
|
|
6424
|
+
const choice = active;
|
|
6225
6425
|
if (!choice.ready) throw missingKey(choice);
|
|
6226
6426
|
const list2 = await choice.runtime.catalog();
|
|
6227
6427
|
io.stderr.write(`${choice.runtime.label}
|
|
@@ -6234,6 +6434,23 @@ async function models({ io, input: input2 }) {
|
|
|
6234
6434
|
}
|
|
6235
6435
|
return 0;
|
|
6236
6436
|
}
|
|
6437
|
+
async function claudeModels(io, choice) {
|
|
6438
|
+
const connection = await openClaude(io.claudeAgent ?? claudeAgent(io.env), io.cwd);
|
|
6439
|
+
try {
|
|
6440
|
+
const state = await connection.newSession({ cwd: io.cwd });
|
|
6441
|
+
const option = state.configOptions?.find((item) => item.id === "model");
|
|
6442
|
+
const list2 = Array.isArray(option?.options) ? option.options : [];
|
|
6443
|
+
const current = choice.model ?? option?.currentValue;
|
|
6444
|
+
io.stderr.write(`${CLAUDE_PLAN.inUse}
|
|
6445
|
+
`);
|
|
6446
|
+
const width = Math.max(0, ...list2.map((model) => model.value.length));
|
|
6447
|
+
for (const model of list2) io.stdout.write(`${model.value === current ? "*" : " "} ${model.value.padEnd(width)} ${model.name}
|
|
6448
|
+
`);
|
|
6449
|
+
return 0;
|
|
6450
|
+
} finally {
|
|
6451
|
+
await connection.close();
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6237
6454
|
const OLLAMA = "http://127.0.0.1:11434";
|
|
6238
6455
|
const input = (deps) => ({ settings: deps.config.settings, env: deps.env, auth: deps.auth, fetch: deps.fetch });
|
|
6239
6456
|
const has = (spec, method) => spec.methods.some((item) => item.id === method);
|
|
@@ -6249,7 +6466,7 @@ function baseOf(spec, deps) {
|
|
|
6249
6466
|
async function status(spec, deps, signedIn) {
|
|
6250
6467
|
const parts = [];
|
|
6251
6468
|
const current = deps.provider();
|
|
6252
|
-
if (current.id === spec.id) parts.push(current.runtime.method === "chatgpt" ? "in use with your ChatGPT plan" : "in use");
|
|
6469
|
+
if (current.id === spec.id) parts.push(!isClaude(current) && current.runtime.method === "chatgpt" ? "in use with your ChatGPT plan" : "in use");
|
|
6253
6470
|
if (spec.id === "openai" && signedIn) parts.push("signed in with ChatGPT");
|
|
6254
6471
|
const base = baseOf(spec, deps);
|
|
6255
6472
|
let saved = false;
|
|
@@ -6273,13 +6490,20 @@ async function apply(deps, id2, message2) {
|
|
|
6273
6490
|
}
|
|
6274
6491
|
async function use(deps, spec) {
|
|
6275
6492
|
const { config } = deps;
|
|
6276
|
-
await selectProvider({ ...input(deps), provider: spec.id });
|
|
6493
|
+
if (spec.id !== CLAUDE_PLAN.id) await selectProvider({ ...input(deps), provider: spec.id });
|
|
6277
6494
|
const previous = config.settings.provider ?? deps.provider().id;
|
|
6278
6495
|
const providers = { ...config.settings.providers };
|
|
6279
6496
|
if (config.settings.model && !providers[previous]?.model) providers[previous] = { ...providers[previous], model: config.settings.model };
|
|
6280
6497
|
await config.saveSettings({ provider: spec.id, model: void 0, providers });
|
|
6281
6498
|
return apply(deps, spec.id, `Using ${spec.name}`);
|
|
6282
6499
|
}
|
|
6500
|
+
async function claudeRow(deps) {
|
|
6501
|
+
const inUse = deps.provider().id === CLAUDE_PLAN.id;
|
|
6502
|
+
const status2 = await claudeStatus(deps.env);
|
|
6503
|
+
const steps = status2.installed && !status2.loggedIn ? "run `oxbit-agent login claude` in a terminal" : void 0;
|
|
6504
|
+
const detail = [inUse && "in use", planText(status2), steps, "Claude Agent with Claude Code's own sign-in"].filter(Boolean).join(", ");
|
|
6505
|
+
return { id: CLAUDE_PLAN.id, section: "Providers", label: CLAUDE_PLAN.name, detail, actions: inUse ? [] : [{ id: "use", label: "Use this provider", reloads: true }] };
|
|
6506
|
+
}
|
|
6283
6507
|
async function signIn(deps, context) {
|
|
6284
6508
|
const signal = AbortSignal.any([context.signal, AbortSignal.timeout(SIGN_IN_TIMEOUT_MS)]);
|
|
6285
6509
|
const { account, enabled } = await signInChatGPT({ config: deps.config, auth: deps.auth, fetch: fetchOr(deps.fetch), signal, open: async (url) => {
|
|
@@ -6309,6 +6533,7 @@ const providersPanel = {
|
|
|
6309
6533
|
});
|
|
6310
6534
|
return { id: spec.id, section: "Providers", label: spec.name, detail: text, enabled: !disabled.includes(spec.id), actions };
|
|
6311
6535
|
}));
|
|
6536
|
+
rows.push(await claudeRow(deps));
|
|
6312
6537
|
return { id: "providers", title: "Providers", rows, footer: "Unchecked providers are skipped when settings name no provider; --provider and Use still pick them." };
|
|
6313
6538
|
},
|
|
6314
6539
|
async toggle({ config }, rowId, enabled) {
|
|
@@ -6318,6 +6543,7 @@ const providersPanel = {
|
|
|
6318
6543
|
return { message: `${spec.name} is ${enabled ? "on" : "off"} for automatic selection` };
|
|
6319
6544
|
},
|
|
6320
6545
|
async act(deps, rowId, actionId, inputs, context) {
|
|
6546
|
+
if (rowId === CLAUDE_PLAN.id && actionId === "use") return use(deps, CLAUDE_PLAN);
|
|
6321
6547
|
const spec = providerSpec(rowId);
|
|
6322
6548
|
if (!spec) throw unknownRow(rowId);
|
|
6323
6549
|
const inUse = deps.provider().id === spec.id;
|
|
@@ -6751,7 +6977,7 @@ async function runPrint(options) {
|
|
|
6751
6977
|
const note = (text) => stderr.write(`${text}
|
|
6752
6978
|
`);
|
|
6753
6979
|
try {
|
|
6754
|
-
const state = options.sessionId ? await connection.loadSession(options.sessionId) : await connection.newSession(options.cwd ? { cwd: options.cwd } : {});
|
|
6980
|
+
const state = options.sessionId ? await connection.loadSession(options.sessionId, options.cwd ? { cwd: options.cwd } : {}) : await connection.newSession(options.cwd ? { cwd: options.cwd } : {});
|
|
6755
6981
|
const { sessionId } = state;
|
|
6756
6982
|
if (options.mode) await connection.setMode(sessionId, options.mode);
|
|
6757
6983
|
if (options.model && options.model !== state.models?.currentModelId) await connection.setModel(sessionId, options.model);
|
|
@@ -6802,8 +7028,9 @@ const USAGE = `Usage: oxbit-agent [options] [prompt] Terminal UI
|
|
|
6802
7028
|
oxbit-agent models List the provider's models
|
|
6803
7029
|
oxbit-agent login List the sign-in methods
|
|
6804
7030
|
oxbit-agent login chatgpt Continue with ChatGPT to use your ChatGPT plan
|
|
7031
|
+
oxbit-agent login claude Sign in to Claude Code to use your Claude plan
|
|
6805
7032
|
oxbit-agent login --provider <id> Save an API key read from stdin
|
|
6806
|
-
oxbit-agent logout [provider|chatgpt] Remove saved keys, or sign out of ChatGPT
|
|
7033
|
+
oxbit-agent logout [provider|chatgpt|claude] Remove saved keys, or sign out of ChatGPT or Claude Code
|
|
6807
7034
|
|
|
6808
7035
|
Options:
|
|
6809
7036
|
-p, --print <prompt> Run one prompt; "-" reads it from stdin
|
|
@@ -6869,49 +7096,69 @@ ${USAGE}`);
|
|
|
6869
7096
|
case "logout":
|
|
6870
7097
|
return await logout(commandContext, rest);
|
|
6871
7098
|
}
|
|
6872
|
-
let provider2 = await
|
|
7099
|
+
let provider2 = await activeProvider(input2);
|
|
6873
7100
|
const deps = { config, auth, env: io.env, fetch: io.fetch, open: io.open, provider: () => provider2 };
|
|
6874
7101
|
const settings = createSettingsManager(deps);
|
|
6875
|
-
const
|
|
7102
|
+
const claude = () => io.claudeAgent ?? claudeAgent(io.env);
|
|
7103
|
+
const startClaude = () => {
|
|
7104
|
+
io.stderr.write("Starting Claude Agent with npx; the first run downloads it.\n");
|
|
7105
|
+
return openClaude(claude(), io.cwd);
|
|
7106
|
+
};
|
|
7107
|
+
const open2 = (choice, generateTitles, quiet = false) => isClaude(choice) ? startClaude() : MemoryConnection.open(agentFactory({ root: io.cwd, config, generateTitles, log: quiet ? void 0 : log, provider: choice, settings }));
|
|
7108
|
+
const mode = values.mode ?? config.settings.mode;
|
|
7109
|
+
const session = (connection) => isClaude(provider2) ? claudeResume(connection, values, io.cwd) : resumeId(config, values, io.cwd);
|
|
6876
7110
|
if (command === "acp") {
|
|
7111
|
+
if (isClaude(provider2)) return await relay(claude(), io);
|
|
6877
7112
|
const rejected = (error) => log(`Unhandled rejection: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
6878
7113
|
process.on("unhandledRejection", rejected);
|
|
6879
7114
|
try {
|
|
6880
|
-
await serveStdio({ input: io.stdin, output: io.stdout, factory: agentFactory(
|
|
7115
|
+
await serveStdio({ input: io.stdin, output: io.stdout, factory: agentFactory({ root: io.cwd, config, generateTitles: true, log, provider: provider2, settings }), log });
|
|
6881
7116
|
} finally {
|
|
6882
7117
|
process.off("unhandledRejection", rejected);
|
|
6883
7118
|
}
|
|
6884
7119
|
return 0;
|
|
6885
7120
|
}
|
|
6886
|
-
const sessionId = await resumeId(config, values, io.cwd);
|
|
6887
|
-
const mode = values.mode ?? config.settings.mode;
|
|
6888
7121
|
if (values.print !== void 0) {
|
|
6889
7122
|
if (positionals.length) throw new UsageError(`Unexpected argument ${positionals[0]}`);
|
|
6890
7123
|
const prompt = values.print === "-" ? (await readAll(io.stdin)).trim() : values.print;
|
|
6891
7124
|
if (!prompt) throw new UsageError("The prompt is empty");
|
|
6892
|
-
const connection = await
|
|
7125
|
+
const connection = await open2(provider2, false);
|
|
6893
7126
|
const abort = new AbortController();
|
|
6894
7127
|
const interrupt = () => abort.abort();
|
|
6895
7128
|
process.once("SIGINT", interrupt);
|
|
6896
7129
|
try {
|
|
6897
|
-
|
|
7130
|
+
const sessionId = await session(connection);
|
|
7131
|
+
return await runPrint({
|
|
7132
|
+
connection,
|
|
7133
|
+
prompt,
|
|
7134
|
+
sessionId,
|
|
7135
|
+
cwd: io.cwd,
|
|
7136
|
+
mode: mode && isClaude(provider2) ? CLAUDE_MODES[mode] : mode,
|
|
7137
|
+
model: isClaude(provider2) ? provider2.model : values.model,
|
|
7138
|
+
stdout: io.stdout,
|
|
7139
|
+
stderr: io.stderr,
|
|
7140
|
+
signal: abort.signal
|
|
7141
|
+
});
|
|
6898
7142
|
} finally {
|
|
6899
7143
|
process.off("SIGINT", interrupt);
|
|
6900
7144
|
await connection.close();
|
|
6901
7145
|
}
|
|
6902
7146
|
}
|
|
6903
7147
|
if (!io.stdout.isTTY) throw new UsageError("stdout is not a terminal. Use -p <prompt> for one reply or `oxbit-agent acp` for ACP");
|
|
6904
|
-
const [tui, memory] = await Promise.all([import("./assets/index-
|
|
6905
|
-
const label = () => ({ provider: provider2.runtime.label, usingChatGPTPlan: provider2.runtime.method === "chatgpt" }
|
|
7148
|
+
const [tui, memory] = await Promise.all([import("./assets/index-Crr8fCGQ.js"), import("./assets/memory-C3ixKimB.js")]);
|
|
7149
|
+
const label = () => isClaude(provider2) ? { provider: CLAUDE_PLAN.inUse } : { provider: provider2.runtime.label, usingChatGPTPlan: provider2.runtime.method === "chatgpt" };
|
|
7150
|
+
const claudeOptions = () => isClaude(provider2) ? { modes: CLAUDE_MODES, model: provider2.model } : {};
|
|
6906
7151
|
const panels = createSettingsManager({ ...deps, rebuild: async (id2) => {
|
|
6907
7152
|
await config.reload();
|
|
6908
|
-
const next = await
|
|
6909
|
-
const connection = await
|
|
7153
|
+
const next = await activeProvider({ ...input2, settings: config.settings, provider: id2, model: id2 === provider2.id ? provider2.model : void 0 });
|
|
7154
|
+
const connection = await open2(next, true, true);
|
|
6910
7155
|
provider2 = next;
|
|
6911
|
-
await adapter.replace(connection, label());
|
|
7156
|
+
await adapter.replace(connection, label(), claudeOptions());
|
|
6912
7157
|
} });
|
|
6913
|
-
const
|
|
7158
|
+
const first = await open2(provider2, true, true);
|
|
7159
|
+
const adapter = memory.memoryTuiConnection(first, { ...label(), ...claudeOptions(), mode, settings: panels });
|
|
6914
7160
|
try {
|
|
7161
|
+
const sessionId = await session(first);
|
|
6915
7162
|
const { sessionId: last } = await tui.run(adapter, {
|
|
6916
7163
|
cwd: io.cwd,
|
|
6917
7164
|
sessionId,
|
|
@@ -6932,6 +7179,25 @@ ${USAGE}`);
|
|
|
6932
7179
|
return error instanceof UsageError ? 2 : 1;
|
|
6933
7180
|
}
|
|
6934
7181
|
}
|
|
7182
|
+
async function claudeResume(connection, values, cwd) {
|
|
7183
|
+
if (values.continue && values.resume) throw new UsageError("Use --continue or --resume, not both");
|
|
7184
|
+
if (!values.continue) return values.resume;
|
|
7185
|
+
const [latest] = (await connection.listSessions(cwd)).sessions.filter((item) => item.cwd === cwd).sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
|
|
7186
|
+
if (!latest) throw new Error(`No saved session for ${cwd}`);
|
|
7187
|
+
return latest.sessionId;
|
|
7188
|
+
}
|
|
7189
|
+
function relay(agent, io) {
|
|
7190
|
+
return new Promise((resolve2, reject) => {
|
|
7191
|
+
const child = spawn(agent.command, agent.args, { cwd: io.cwd, env: agent.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
7192
|
+
child.once("error", (error) => reject(new Error(`Could not start ${agent.command}: ${error.message}`)));
|
|
7193
|
+
child.stdin.on("error", () => {
|
|
7194
|
+
});
|
|
7195
|
+
io.stdin.pipe(child.stdin);
|
|
7196
|
+
child.stdout.on("data", (chunk) => io.stdout.write(chunk));
|
|
7197
|
+
child.stderr.on("data", (chunk) => io.stderr.write(chunk));
|
|
7198
|
+
child.once("close", (code) => resolve2(code ?? 1));
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
6935
7201
|
async function resumeId(config, values, cwd) {
|
|
6936
7202
|
if (values.continue && values.resume) throw new UsageError("Use --continue or --resume, not both");
|
|
6937
7203
|
const store = new JsonlSessionStore(config.paths.sessions);
|