@onride/agent 0.0.0-stage → 0.5.1-rc.0

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.
Files changed (64) hide show
  1. package/.env.example +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +174 -2
  4. package/dist/account_balances.js +435 -0
  5. package/dist/account_reads.js +199 -0
  6. package/dist/agent/adapter.js +115 -0
  7. package/dist/agent/analytics.js +174 -0
  8. package/dist/agent/backend.js +237 -0
  9. package/dist/agent/card_hosts.js +9 -0
  10. package/dist/agent/contracts.js +276 -0
  11. package/dist/agent/conversational.js +86 -0
  12. package/dist/agent/core_bridge.js +1155 -0
  13. package/dist/agent/deposits.js +309 -0
  14. package/dist/agent/discovery.js +205 -0
  15. package/dist/agent/domain.js +507 -0
  16. package/dist/agent/firebase.js +34 -0
  17. package/dist/agent/funding.js +230 -0
  18. package/dist/agent/http.js +40 -0
  19. package/dist/agent/human.js +298 -0
  20. package/dist/agent/human_handoffs.js +39 -0
  21. package/dist/agent/ledger.js +137 -0
  22. package/dist/agent/mcp.js +245 -0
  23. package/dist/agent/order_rules.js +61 -0
  24. package/dist/agent/perps_account.js +41 -0
  25. package/dist/agent/prediction.js +607 -0
  26. package/dist/agent/prediction_equity_history.js +910 -0
  27. package/dist/agent/prediction_history.js +336 -0
  28. package/dist/agent/preferences.js +114 -0
  29. package/dist/agent/repository.js +106 -0
  30. package/dist/agent/runner.js +683 -0
  31. package/dist/agent/service.js +1347 -0
  32. package/dist/backend.js +6 -0
  33. package/dist/bridge.js +6 -0
  34. package/dist/cli/clients.js +99 -0
  35. package/dist/cli/commands.js +332 -0
  36. package/dist/cli/onboarding.js +53 -0
  37. package/dist/cli/session.js +224 -0
  38. package/dist/cli/setup.js +256 -0
  39. package/dist/cli.js +16 -0
  40. package/dist/guide.js +92 -0
  41. package/dist/http_security.js +79 -0
  42. package/dist/mcp-cli.js +7 -0
  43. package/dist/oauth.js +1032 -0
  44. package/dist/server.js +171 -0
  45. package/dist/start.js +18 -0
  46. package/dist/version.js +25 -0
  47. package/docs/confirmation-cards.md +118 -0
  48. package/docs/conversation-v050.md +30 -0
  49. package/docs/conversational-v17.md +48 -0
  50. package/docs/deployment.md +58 -0
  51. package/docs/perps-leverage.md +43 -0
  52. package/docs/prediction-history.md +28 -0
  53. package/docs/preferences-funding-v042.md +17 -0
  54. package/docs/ride-agent-migration.md +127 -0
  55. package/docs/v17-completion-checklist.md +31 -0
  56. package/docs/v17-rollout.md +44 -0
  57. package/docs/widget-account-v043.md +17 -0
  58. package/package.json +75 -4
  59. package/public/agent.html +2313 -0
  60. package/public/confirm.html +2373 -0
  61. package/public/fund.html +563 -0
  62. package/skills/ride/SKILL.md +18 -0
  63. package/skills/ride/references/commands.md +47 -0
  64. package/skills/ride/references/workflow.md +47 -0
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { startBackend } from "./agent/backend.js";
3
+ startBackend().catch(() => {
4
+ console.error("Ride Agent startup failed. Verify backend dependency configuration.");
5
+ process.exitCode = 1;
6
+ });
package/dist/bridge.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { startCoreBridge } from "./agent/core_bridge.js";
3
+ startCoreBridge().catch(() => {
4
+ console.error("Private Ride execution adapter startup failed.");
5
+ process.exitCode = 1;
6
+ });
@@ -0,0 +1,99 @@
1
+ import { access, constants } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createInterface } from "node:readline/promises";
5
+ const clients = ["codex", "claude-code", "cursor"];
6
+ const labels = {
7
+ codex: "Codex",
8
+ "claude-code": "Claude Code",
9
+ cursor: "Cursor",
10
+ };
11
+ async function exists(path, mode = constants.F_OK) {
12
+ try {
13
+ await access(path, mode);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ export async function detectClients(options = {}) {
21
+ const home = options.home ?? homedir();
22
+ const platform = options.platform ?? process.platform;
23
+ const codexHome = options.codexHome ??
24
+ (options.home === undefined ? process.env.CODEX_HOME : undefined) ??
25
+ join(home, ".codex");
26
+ const markers = {
27
+ codex: [codexHome],
28
+ "claude-code": [join(home, ".claude"), join(home, ".claude.json")],
29
+ cursor: [join(home, ".cursor")],
30
+ };
31
+ if (platform === "darwin") {
32
+ for (const client of ["codex", "cursor"]) {
33
+ for (const base of ["/Applications", join(home, "Applications")])
34
+ markers[client].push(join(base, `${labels[client]}.app`));
35
+ }
36
+ }
37
+ const searchPath = (options.path ?? process.env.PATH ?? "")
38
+ .split(platform === "win32" ? ";" : ":")
39
+ .filter(Boolean);
40
+ const detected = [];
41
+ for (const client of clients) {
42
+ const binary = client === "claude-code" ? "claude" : client;
43
+ const suffixes = platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
44
+ const executables = searchPath.flatMap((base) => suffixes.map((suffix) => join(base, binary + suffix)));
45
+ if ((await Promise.all(markers[client].map((path) => exists(path)))).some(Boolean) ||
46
+ (await Promise.all(executables.map((path) => exists(path, platform === "win32" ? constants.F_OK : constants.X_OK)))).some(Boolean))
47
+ detected.push(client);
48
+ }
49
+ return detected;
50
+ }
51
+ export async function promptClient(detected) {
52
+ const readline = createInterface({
53
+ input: process.stdin,
54
+ output: process.stderr,
55
+ });
56
+ const abort = new AbortController();
57
+ readline.once("SIGINT", () => abort.abort());
58
+ readline.once("close", () => abort.abort());
59
+ try {
60
+ process.stderr.write("Choose your AI client:\n" +
61
+ clients
62
+ .map((client, index) => ` ${index + 1}. ${labels[client]}${detected.includes(client) ? " (detected)" : ""}\n`)
63
+ .join(""));
64
+ for (;;) {
65
+ const answer = (await readline.question("Client [1-3]: ", { signal: abort.signal }))
66
+ .trim()
67
+ .toLowerCase();
68
+ const client = clients[Number(answer) - 1] ??
69
+ clients.find((client) => client === answer);
70
+ if (client)
71
+ return client;
72
+ process.stderr.write("Enter 1, 2 or 3.\n");
73
+ }
74
+ }
75
+ catch (error) {
76
+ if (abort.signal.aborted)
77
+ throw new Error("Installation cancelled before configuring a client.");
78
+ throw error;
79
+ }
80
+ finally {
81
+ readline.close();
82
+ }
83
+ }
84
+ export async function selectClient(requested, dependencies = {}) {
85
+ if (requested && requested !== "auto") {
86
+ if (!clients.includes(requested))
87
+ throw new Error("Unsupported client");
88
+ return requested;
89
+ }
90
+ const detected = await (dependencies.detect ?? detectClients)();
91
+ if (detected.length === 1)
92
+ return detected[0];
93
+ if (!(dependencies.interactive ?? !!process.stdin.isTTY))
94
+ throw new Error("Run this command in an interactive terminal to choose your AI client, or use setup --client codex|claude-code|cursor --trade.");
95
+ const selected = await (dependencies.prompt ?? promptClient)(detected);
96
+ if (!clients.includes(selected))
97
+ throw new Error("Unsupported client");
98
+ return selected;
99
+ }
@@ -0,0 +1,332 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { inputs, mutationTools, AgentError, } from "../agent/contracts.js";
4
+ import { invokeAgent, registerAgentTools, registerAppAccountRead, } from "../agent/mcp.js";
5
+ import { accountCopiesInput } from "../account_reads.js";
6
+ import { loadSession, login, accessToken, withSessionLock, openBrowser, serverUrl, } from "./session.js";
7
+ import { onboard } from "./onboarding.js";
8
+ import { selectClient } from "./clients.js";
9
+ import { VERSION } from "../version.js";
10
+ import { fetchGuide, GUIDE_INSTRUCTIONS, registerGuide } from "../guide.js";
11
+ export const HELP = `Ride Agent — AI Copy-Trading Agent\n Run with no arguments to install Skill + MCP and start browser login.\n setup [--client codex|claude-code|cursor|auto] [--trade] [--skip-login] [--server URL] [--distribution npm|github]\n login --server https://YOUR-RIDE-HOST/mcp [--trade]\n preferences --json '{"budget_usdc":"500","risk_tolerance":"medium"}'\n recommend | traders [--sort score|drawdown|roi] [--limit 10] [--cursor CURSOR] | trader TRADER_ID | portfolio [PORTFOLIO_ID] | review [PORTFOLIO_ID]\n copy start PLAN_ID | copy update COPY_ID --json '{"leverage_cap":5}'\n copy stop COPY_ID [--mode wind_down|close_now]\n updates [--cursor CURSOR] [--limit 20] | position close POSITION_ID\n plan recalculate PLAN_ID [--json ALLOCATIONS_JSON]\n performance [PORTFOLIO_ID] [--period daily|weekly|inception]\n diagnose COPY_ID --start-at MILLISECONDS --end-at MILLISECONDS\n copies [--status open] [--limit 100] [--offset 0]\n notifications [--json SETTINGS_JSON]\n proposal PROPOSAL_ID | confirm PROPOSAL_ID\n guide [--server URL] (current workflow; no login)\n mcp [--server URL]\n deposit options | deposit address --json ROUTE | deposit prepare DEPOSIT_ID --json AMOUNT | deposit status DEPOSIT_ID\n portfolio update PORTFOLIO_ID --json CHANGES | daily [--timezone Asia/Shanghai]\nSetup installs Skill + MCP and starts browser login (default server: https://mcp.onride.me/mcp).\nJSON output is default. --pretty renders a human-readable view. Confirmation opens Ride.\n`;
12
+ export function parseCommand(argv) {
13
+ const flags = {}, positionals = [];
14
+ for (let i = 0; i < argv.length; i++) {
15
+ const x = argv[i];
16
+ if (x.startsWith("--")) {
17
+ const key = x.slice(2);
18
+ if (["pretty", "trade", "help", "skip-login"].includes(key))
19
+ flags[key] = true;
20
+ else {
21
+ const value = argv[++i];
22
+ if (!value || value.startsWith("--"))
23
+ throw new Error(`Missing --${key} value`);
24
+ flags[key] = value;
25
+ }
26
+ }
27
+ else
28
+ positionals.push(x);
29
+ }
30
+ const command = positionals[0] ?? "setup";
31
+ if (command === "setup" &&
32
+ flags.distribution !== undefined &&
33
+ !["npm", "github"].includes(String(flags.distribution))) {
34
+ throw new Error("--distribution must be npm or github.");
35
+ }
36
+ let name;
37
+ let args = {};
38
+ if (flags.json) {
39
+ const parsed = JSON.parse(flags.json);
40
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
41
+ throw new Error("--json requires an object");
42
+ args = parsed;
43
+ }
44
+ if (command === "deposit") {
45
+ if (positionals[1] === "prepare") {
46
+ name = "prepare_deposit";
47
+ args.deposit_id = positionals[2];
48
+ }
49
+ else if (positionals[1] === "options")
50
+ name = "get_deposit_options";
51
+ else if (positionals[1] === "address")
52
+ name = "get_deposit_address";
53
+ else if (positionals[1] === "status") {
54
+ name = "check_deposit_status";
55
+ args.deposit_id = positionals[2];
56
+ }
57
+ else
58
+ throw new Error('Use deposit options, address --json ROUTE, prepare DEPOSIT_ID --json \'{"amount_usdc":"10"}\', or status DEPOSIT_ID');
59
+ }
60
+ if (command === "daily") {
61
+ name = "get_daily_checkin";
62
+ if (flags.timezone)
63
+ args.timezone = flags.timezone;
64
+ }
65
+ if (command === "preferences")
66
+ name = flags.json ? "set_preferences" : "get_preferences";
67
+ if (command === "plan" && positionals[1] === "recalculate") {
68
+ name = "recalculate_plan";
69
+ args.plan_id = positionals[2];
70
+ }
71
+ if (command === "performance") {
72
+ name = "get_performance";
73
+ if (positionals[1])
74
+ args.portfolio_id = positionals[1];
75
+ if (flags.period)
76
+ args.period = flags.period;
77
+ }
78
+ if (command === "diagnose") {
79
+ name = "diagnose_copy";
80
+ args.copy_id = positionals[1];
81
+ args.start_at = Number(flags["start-at"]);
82
+ args.end_at = Number(flags["end-at"]);
83
+ }
84
+ if (command === "notifications")
85
+ name = flags.json
86
+ ? "set_notification_preferences"
87
+ : "get_notification_preferences";
88
+ if (command === "recommend")
89
+ name = "recommend_traders";
90
+ if (command === "traders") {
91
+ name = "browse_traders";
92
+ if (flags.sort)
93
+ args.sort_by = flags.sort;
94
+ if (flags.direction)
95
+ args.direction = flags.direction;
96
+ if (flags.limit)
97
+ args.limit = Number(flags.limit);
98
+ if (flags.cursor)
99
+ args.cursor = flags.cursor;
100
+ }
101
+ if (command === "trader") {
102
+ name = "get_trader_profile";
103
+ args.trader_id = positionals[1];
104
+ }
105
+ if (command === "portfolio" && positionals[1] === "update") {
106
+ name = "update_portfolio";
107
+ args.portfolio_id = positionals[2];
108
+ }
109
+ else if (command === "portfolio" || command === "review") {
110
+ name = command === "portfolio" ? "get_portfolio" : "review_portfolio";
111
+ if (positionals[1])
112
+ args.portfolio_id = positionals[1];
113
+ }
114
+ if (command === "updates") {
115
+ name = "get_updates";
116
+ if (flags.cursor)
117
+ args.cursor = flags.cursor;
118
+ if (flags.limit)
119
+ args.limit = Number(flags.limit);
120
+ }
121
+ if (command === "copy") {
122
+ const action = positionals[1];
123
+ name =
124
+ action === "start"
125
+ ? "start_copy"
126
+ : action === "update"
127
+ ? "update_copy"
128
+ : action === "stop"
129
+ ? "stop_copy"
130
+ : undefined;
131
+ if (!name)
132
+ throw new Error("Unknown copy command");
133
+ args[action === "start" ? "plan_id" : "copy_id"] = positionals[2];
134
+ if (flags.mode)
135
+ args.mode = flags.mode;
136
+ }
137
+ if (command === "position" && positionals[1] === "close") {
138
+ name = "close_position";
139
+ args.position_id = positionals[2];
140
+ }
141
+ return { command, name, args, flags, positionals };
142
+ }
143
+ export async function runCli(argv, installDependencies = {}) {
144
+ const parsed = parseCommand(argv), { command, name, args, flags, positionals } = parsed;
145
+ if (command === "help" || flags.help) {
146
+ process.stdout.write(HELP);
147
+ return;
148
+ }
149
+ if (command === "setup") {
150
+ const server = flags.server ?? process.env.RIDE_MCP_URL;
151
+ console.log(JSON.stringify(await (installDependencies.onboard ?? onboard)(await (installDependencies.selectClient ?? selectClient)(flags.client), server, {
152
+ trade: positionals.length === 0 || flags.trade === true,
153
+ skipLogin: flags["skip-login"] === true,
154
+ distribution: flags.distribution === "github" ? "github" : "npm",
155
+ })));
156
+ return;
157
+ }
158
+ if (command === "login") {
159
+ const server = flags.server ?? process.env.RIDE_MCP_URL ?? "https://mcp.onride.me/mcp";
160
+ await withSessionLock(() => login(server, flags.trade === true));
161
+ console.log(JSON.stringify({
162
+ status: "connected",
163
+ scopes: flags.trade ? ["ride:read", "ride:trade"] : ["ride:read"],
164
+ }));
165
+ return;
166
+ }
167
+ if (command === "guide") {
168
+ const url = flags.server ?? process.env.RIDE_MCP_URL ?? "https://mcp.onride.me/mcp";
169
+ console.log(JSON.stringify(await fetchGuide(String(url))));
170
+ return;
171
+ }
172
+ if (command === "mcp") {
173
+ const server = new McpServer({ name: "ride", title: "Ride Agent", version: VERSION }, { instructions: GUIDE_INSTRUCTIONS });
174
+ registerGuide(server, async () => {
175
+ const session = flags.server || process.env.RIDE_MCP_URL
176
+ ? undefined
177
+ : await loadSession();
178
+ return fetchGuide(String(flags.server ??
179
+ process.env.RIDE_MCP_URL ??
180
+ session?.server ??
181
+ "https://mcp.onride.me/mcp"));
182
+ });
183
+ registerAgentTools(server, async (tool, input) => withSessionLock(async () => {
184
+ const session = await loadSession();
185
+ if (!session)
186
+ throw new AgentError("AUTH_REQUIRED", "Run ride login first.", false, 401);
187
+ if (flags.server &&
188
+ serverUrl(flags.server) !== session.server)
189
+ throw new AgentError("AUTH_REQUIRED", "Login issuer does not match this MCP configuration.", false, 401);
190
+ return invokeAgent(session.server, await accessToken(session), tool, input);
191
+ }));
192
+ registerAppAccountRead(server, async (input) => withSessionLock(async () => {
193
+ const session = await loadSession();
194
+ if (!session ||
195
+ (flags.server && serverUrl(flags.server) !== session.server))
196
+ throw new AgentError("AUTH_REQUIRED", "Run ride login first.", false, 401);
197
+ return invokeAgent(session.server, await accessToken(session), "get_my_copies", input);
198
+ }));
199
+ await server.connect(new StdioServerTransport());
200
+ return;
201
+ }
202
+ await withSessionLock(async () => {
203
+ const session = await loadSession();
204
+ if (!session)
205
+ throw new AgentError("AUTH_REQUIRED", "Run ride login first.", false, 401);
206
+ const token = await accessToken(session);
207
+ if (command === "copies") {
208
+ const input = accountCopiesInput.parse({
209
+ ...args,
210
+ ...(flags.limit ? { limit: Number(flags.limit) } : {}),
211
+ ...(flags.offset ? { offset: Number(flags.offset) } : {}),
212
+ ...(flags.status ? { status: flags.status } : {}),
213
+ });
214
+ print(await invokeAgent(session.server, token, "get_my_copies", input), !!flags.pretty);
215
+ return;
216
+ }
217
+ if (command === "proposal" || command === "confirm") {
218
+ const value = await invokeAgent(session.server, token, "proposal", {
219
+ proposal_id: positionals[1],
220
+ });
221
+ if (command === "confirm") {
222
+ const proposal = value.data.proposal;
223
+ if (!proposal?.confirmation_url)
224
+ throw new Error("Proposal has no confirmation URL");
225
+ const confirmation = new URL(proposal.confirmation_url);
226
+ if (confirmation.origin !== new URL(session.server).origin)
227
+ throw new Error("Confirmation origin does not match Ride");
228
+ await openBrowser(confirmation.href);
229
+ }
230
+ print(value, !!flags.pretty);
231
+ return;
232
+ }
233
+ if (!name)
234
+ throw new Error("Unknown command. Run ride --help.");
235
+ const input = inputs[name].parse(args);
236
+ if (mutationTools.has(name) &&
237
+ !session.tokens?.scope?.split(" ").includes("ride:trade"))
238
+ throw new AgentError("SCOPE_REQUIRED", "Run ride login --trade to enable proposals.", false, 403);
239
+ print(await invokeAgent(session.server, token, name, input), !!flags.pretty);
240
+ });
241
+ }
242
+ function print(value, pretty) {
243
+ if (!pretty) {
244
+ console.log(JSON.stringify(value));
245
+ return;
246
+ }
247
+ console.log(`Ride Agent · ${value.status} · ${value.as_of}`);
248
+ const data = value.data;
249
+ if (Array.isArray(data.candidates)) {
250
+ console.log(`Eligible candidates: ${data.candidates.length} returned / ${data.candidates_meta?.total ?? data.candidates.length} total`);
251
+ console.table(data.candidates.map((c) => ({
252
+ trader: c.handle,
253
+ id: c.id,
254
+ score: c.score,
255
+ market: c.market,
256
+ assets: c.assets.join(", "),
257
+ eligibility: c.eligibility,
258
+ })));
259
+ }
260
+ if (data.account_balances) {
261
+ const b = data.account_balances;
262
+ console.log(`Account total: ${b.summary.account_value_usd ?? "unavailable"} USD · verified subtotal: ${b.summary.known_subtotal_usd ?? "unavailable"} USD`);
263
+ console.table(Object.entries(b.markets).map(([market, m]) => ({
264
+ market,
265
+ status: m.status,
266
+ equity_USD: m.account_value_usd ?? "unavailable",
267
+ trading_USDC: m.available_trading_usdc ?? "unavailable",
268
+ reserved_USDC: m.occupied_or_reserved_usdc ?? "unavailable",
269
+ positions_USD: m.positions_value_usd ?? "unavailable",
270
+ fetched_at: m.fetched_at,
271
+ })));
272
+ }
273
+ if (value.error) {
274
+ console.log(`${value.error.code}: ${value.error.message}`);
275
+ if (value.data.recommendation_diagnostics)
276
+ console.log(JSON.stringify(value.data.recommendation_diagnostics, null, 2));
277
+ return;
278
+ }
279
+ if (data.account?.authenticated)
280
+ console.log(`Signed in: ${data.account.display_name ?? "Ride user"}`);
281
+ const app = data.app_copies ?? (data.query_scope === "ride_app_copies" ? data : null);
282
+ if (app?.perps && app?.prediction) {
283
+ for (const [market, copies] of [
284
+ ["Perps", app.perps],
285
+ ["Prediction", app.prediction],
286
+ ]) {
287
+ console.log(`${market} App copies · ${copies.configs.length} configs · ${copies.positions.length} current positions`);
288
+ console.table(copies.configs);
289
+ console.table(copies.positions);
290
+ }
291
+ console.log(`Coverage: ${app.coverage_complete ? "complete" : "partial"}`);
292
+ }
293
+ if (data.plan && !data.saved_preview) {
294
+ console.table(data.plan.allocations.map((s) => ({
295
+ trader: s.trader.handle,
296
+ market: s.trader.market,
297
+ score: s.trader.score,
298
+ USDC: s.amount_usdc,
299
+ leverage: `≤${s.leverage_cap}x`,
300
+ style: s.trader.style,
301
+ })));
302
+ console.log(`Plan: ${data.plan.id}`);
303
+ }
304
+ else if (data.portfolio) {
305
+ const p = data.portfolio;
306
+ console.log(`Portfolio ${p.id} · ${p.state}\nAccount ${p.account_value_usdc ?? "unavailable"} USDC · Net profit ${p.net_profit_usdc ?? "unavailable"} USDC`);
307
+ console.table(p.sleeves.map((s) => ({
308
+ trader: s.trader.handle,
309
+ state: s.state,
310
+ USDC: s.amount_usdc,
311
+ net: s.net_profit_usdc ?? "unavailable",
312
+ })));
313
+ console.table(p.positions);
314
+ }
315
+ else if (data.saved_preview) {
316
+ console.log(`Saved preview · ${data.plan_status} · ${data.plan.id}`);
317
+ }
318
+ else if (app) {
319
+ // App account tables above are already complete.
320
+ }
321
+ else if (data.trader) {
322
+ console.log(`${data.trader.handle} · ${data.trader.market} · Copy Score ${data.trader.score}`);
323
+ console.table(data.trader.components);
324
+ }
325
+ else if (data.proposal) {
326
+ console.log(`${data.proposal.action} · ${data.proposal.id}\nConfirm in Ride: ${data.proposal.confirmation_url}`);
327
+ }
328
+ else
329
+ console.log(JSON.stringify(data, null, 2));
330
+ for (const warning of value.warnings)
331
+ console.log(warning);
332
+ }
@@ -0,0 +1,53 @@
1
+ import { spawn } from "node:child_process";
2
+ import { setup } from "./setup.js";
3
+ import { login, withSessionLock } from "./session.js";
4
+ function runProgram(command, args) {
5
+ return new Promise((resolve, reject) => {
6
+ const child = spawn(command, args, { stdio: "inherit", shell: false });
7
+ child.once("error", reject);
8
+ child.once("exit", (code, signal) => code === 0
9
+ ? resolve()
10
+ : reject(new Error(`${command} exited with ${signal ?? code}`)));
11
+ });
12
+ }
13
+ export async function runCodex(args, execute = runProgram) {
14
+ try {
15
+ await execute("codex", args);
16
+ }
17
+ catch (e) {
18
+ if (e.code !== "ENOENT")
19
+ throw e;
20
+ // Use the official CLI without requiring a separate global installation.
21
+ await execute("npx", ["-y", "@openai/codex@0.162.0", ...args]);
22
+ }
23
+ }
24
+ export async function onboard(client, server, options = {}, dependencies = {}) {
25
+ const installed = await setup(client, server, dependencies.home, options.distribution);
26
+ if (options.skipLogin)
27
+ return { ...installed, status: "configured", restart_required: true };
28
+ const scopes = options.trade ? ["ride:read", "ride:trade"] : ["ride:read"];
29
+ try {
30
+ if (client === "codex")
31
+ await (dependencies.codex ?? runCodex)([
32
+ "mcp",
33
+ "login",
34
+ "ride",
35
+ "--scopes",
36
+ scopes.join(","),
37
+ ]);
38
+ else
39
+ await (dependencies.cliLogin ??
40
+ ((url, trade) => withSessionLock(() => login(url, trade))))(installed.server, !!options.trade);
41
+ }
42
+ catch (e) {
43
+ const reason = e instanceof Error ? e.message : "Authentication failed";
44
+ throw new Error(`Ride Agent Skill and MCP are installed, but login is incomplete. Rerun the same setup command to retry. ${reason}`);
45
+ }
46
+ return {
47
+ ...installed,
48
+ status: "connected",
49
+ login_required: false,
50
+ scopes,
51
+ restart_required: true,
52
+ };
53
+ }