@rk0429/agentic-relay 0.12.0 → 0.12.2
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/dist/relay.mjs +182 -37
- package/package.json +1 -1
package/dist/relay.mjs
CHANGED
|
@@ -46,6 +46,129 @@ var init_logger = __esm({
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
// src/mcp-server/deferred-cleanup-task-store.ts
|
|
50
|
+
import { isTerminal } from "@modelcontextprotocol/sdk/experimental/tasks/interfaces.js";
|
|
51
|
+
import { randomBytes } from "crypto";
|
|
52
|
+
var DeferredCleanupTaskStore;
|
|
53
|
+
var init_deferred_cleanup_task_store = __esm({
|
|
54
|
+
"src/mcp-server/deferred-cleanup-task-store.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
DeferredCleanupTaskStore = class {
|
|
57
|
+
tasks = /* @__PURE__ */ new Map();
|
|
58
|
+
cleanupTimers = /* @__PURE__ */ new Map();
|
|
59
|
+
generateTaskId() {
|
|
60
|
+
return randomBytes(16).toString("hex");
|
|
61
|
+
}
|
|
62
|
+
async createTask(taskParams, requestId, request, _sessionId) {
|
|
63
|
+
const taskId = this.generateTaskId();
|
|
64
|
+
if (this.tasks.has(taskId)) {
|
|
65
|
+
throw new Error(`Task with ID ${taskId} already exists`);
|
|
66
|
+
}
|
|
67
|
+
const actualTtl = taskParams.ttl ?? null;
|
|
68
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
69
|
+
const task = {
|
|
70
|
+
taskId,
|
|
71
|
+
status: "working",
|
|
72
|
+
ttl: actualTtl,
|
|
73
|
+
createdAt,
|
|
74
|
+
lastUpdatedAt: createdAt,
|
|
75
|
+
pollInterval: taskParams.pollInterval ?? 1e3
|
|
76
|
+
};
|
|
77
|
+
this.tasks.set(taskId, { task, request, requestId });
|
|
78
|
+
return { ...task };
|
|
79
|
+
}
|
|
80
|
+
async getTask(taskId, _sessionId) {
|
|
81
|
+
const stored = this.tasks.get(taskId);
|
|
82
|
+
return stored ? { ...stored.task } : null;
|
|
83
|
+
}
|
|
84
|
+
async storeTaskResult(taskId, status, result, _sessionId) {
|
|
85
|
+
const stored = this.tasks.get(taskId);
|
|
86
|
+
if (!stored) {
|
|
87
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
88
|
+
}
|
|
89
|
+
if (isTerminal(stored.task.status)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Cannot store result for task ${taskId} in terminal status '${stored.task.status}'. Task results can only be stored once.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
stored.result = result;
|
|
95
|
+
stored.task.status = status;
|
|
96
|
+
stored.task.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
97
|
+
this.scheduleCleanup(taskId, stored.task.ttl);
|
|
98
|
+
}
|
|
99
|
+
async getTaskResult(taskId, _sessionId) {
|
|
100
|
+
const stored = this.tasks.get(taskId);
|
|
101
|
+
if (!stored) {
|
|
102
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
103
|
+
}
|
|
104
|
+
if (!stored.result) {
|
|
105
|
+
throw new Error(`Task ${taskId} has no result stored`);
|
|
106
|
+
}
|
|
107
|
+
return stored.result;
|
|
108
|
+
}
|
|
109
|
+
async updateTaskStatus(taskId, status, statusMessage, _sessionId) {
|
|
110
|
+
const stored = this.tasks.get(taskId);
|
|
111
|
+
if (!stored) {
|
|
112
|
+
throw new Error(`Task with ID ${taskId} not found`);
|
|
113
|
+
}
|
|
114
|
+
if (isTerminal(stored.task.status)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Cannot update task ${taskId} from terminal status '${stored.task.status}'.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
stored.task.status = status;
|
|
120
|
+
if (statusMessage) {
|
|
121
|
+
stored.task.statusMessage = statusMessage;
|
|
122
|
+
}
|
|
123
|
+
stored.task.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
124
|
+
if (isTerminal(status)) {
|
|
125
|
+
this.scheduleCleanup(taskId, stored.task.ttl);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async listTasks(cursor, _sessionId) {
|
|
129
|
+
const PAGE_SIZE = 10;
|
|
130
|
+
const allTaskIds = Array.from(this.tasks.keys());
|
|
131
|
+
let startIndex = 0;
|
|
132
|
+
if (cursor) {
|
|
133
|
+
const cursorIndex = allTaskIds.indexOf(cursor);
|
|
134
|
+
if (cursorIndex >= 0) {
|
|
135
|
+
startIndex = cursorIndex + 1;
|
|
136
|
+
} else {
|
|
137
|
+
throw new Error(`Invalid cursor: ${cursor}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const pageTaskIds = allTaskIds.slice(startIndex, startIndex + PAGE_SIZE);
|
|
141
|
+
const tasks = pageTaskIds.map((id) => {
|
|
142
|
+
const stored = this.tasks.get(id);
|
|
143
|
+
return { ...stored.task };
|
|
144
|
+
});
|
|
145
|
+
const nextCursor = startIndex + PAGE_SIZE < allTaskIds.length ? pageTaskIds[pageTaskIds.length - 1] : void 0;
|
|
146
|
+
return { tasks, nextCursor };
|
|
147
|
+
}
|
|
148
|
+
/** Cleanup all timers (for testing or graceful shutdown) */
|
|
149
|
+
cleanup() {
|
|
150
|
+
for (const timer of this.cleanupTimers.values()) {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
this.cleanupTimers.clear();
|
|
154
|
+
this.tasks.clear();
|
|
155
|
+
}
|
|
156
|
+
scheduleCleanup(taskId, ttl) {
|
|
157
|
+
if (!ttl) return;
|
|
158
|
+
const existingTimer = this.cleanupTimers.get(taskId);
|
|
159
|
+
if (existingTimer) {
|
|
160
|
+
clearTimeout(existingTimer);
|
|
161
|
+
}
|
|
162
|
+
const timer = setTimeout(() => {
|
|
163
|
+
this.tasks.delete(taskId);
|
|
164
|
+
this.cleanupTimers.delete(taskId);
|
|
165
|
+
}, ttl);
|
|
166
|
+
this.cleanupTimers.set(taskId, timer);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
49
172
|
// src/mcp-server/recursion-guard.ts
|
|
50
173
|
import { createHash } from "crypto";
|
|
51
174
|
var RecursionGuard;
|
|
@@ -658,27 +781,45 @@ var init_spawn_agent = __esm({
|
|
|
658
781
|
init_recursion_guard();
|
|
659
782
|
init_logger();
|
|
660
783
|
spawnAgentInputSchema = z2.object({
|
|
661
|
-
backend: z2.enum(["claude", "codex", "gemini"])
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
784
|
+
backend: z2.enum(["claude", "codex", "gemini"]).describe(
|
|
785
|
+
"Required fallback backend. Overridden by automatic selection when BackendSelector is active (priority: preferredBackend > agentType mapping > taskType mapping > default)."
|
|
786
|
+
),
|
|
787
|
+
prompt: z2.string().describe(
|
|
788
|
+
"The task instruction for the sub-agent. Be specific and include all necessary context for autonomous execution."
|
|
789
|
+
),
|
|
790
|
+
agent: z2.string().optional().describe(
|
|
791
|
+
"Agent type identifier (e.g., 'coder', 'researcher', 'documenter', 'software-engineer'). Affects backend auto-selection: coder/researcher/software-engineer/devops-engineer\u2192codex, documenter\u2192claude. Also used to resolve agentDefinition files from .agents/agents/<agent>.md."
|
|
792
|
+
),
|
|
793
|
+
systemPrompt: z2.string().optional().describe(
|
|
794
|
+
"Custom system prompt prepended to the agent's context. Use agentDefinition instead when a .md definition file exists."
|
|
795
|
+
),
|
|
796
|
+
resumeSessionId: z2.string().optional().describe(
|
|
797
|
+
"Relay session ID to resume. Enables multi-turn continuation of a previous agent execution."
|
|
798
|
+
),
|
|
799
|
+
model: z2.string().optional().describe(
|
|
800
|
+
"Model to use (e.g., 'opus', 'sonnet', 'o3'). Passed to the backend CLI. Model availability varies by backend."
|
|
801
|
+
),
|
|
802
|
+
maxTurns: z2.number().optional().describe(
|
|
803
|
+
"Maximum agentic turns (tool-use cycles). Limits execution depth to prevent runaway agents."
|
|
804
|
+
),
|
|
668
805
|
skillContext: z2.object({
|
|
669
806
|
skillPath: z2.string().describe("Path to the skill directory (e.g., '.agents/skills/software-engineer/')"),
|
|
670
|
-
subskill: z2.string().optional().describe("Specific subskill to activate")
|
|
671
|
-
}).optional().describe("Skill context to inject into the
|
|
807
|
+
subskill: z2.string().optional().describe("Specific subskill to activate within the skill")
|
|
808
|
+
}).optional().describe("Skill context to inject into the sub-agent's system prompt. Loads SKILL.md/SUBSKILL.md content."),
|
|
672
809
|
agentDefinition: z2.object({
|
|
673
|
-
definitionPath: z2.string().describe("Path to the agent definition file (e.g., '.
|
|
674
|
-
}).optional().describe("Agent definition file to inject into the sub-agent's system prompt"),
|
|
675
|
-
preferredBackend: z2.enum(["claude", "codex", "gemini"]).optional().describe(
|
|
676
|
-
|
|
810
|
+
definitionPath: z2.string().describe("Path to the agent definition .md file (e.g., '.agents/agents/coder.md')")
|
|
811
|
+
}).optional().describe("Agent definition file to inject into the sub-agent's system prompt as <agent-definition> context."),
|
|
812
|
+
preferredBackend: z2.enum(["claude", "codex", "gemini"]).optional().describe(
|
|
813
|
+
"Preferred backend override. Takes highest priority in backend selection. Use when the automatic agentType/taskType mapping does not match your needs."
|
|
814
|
+
),
|
|
815
|
+
taskType: z2.enum(["code-writing", "code-review", "document-writing", "document-review", "research", "mixed"]).optional().describe(
|
|
816
|
+
"Task type hint for automatic backend selection (priority 3, after preferredBackend and agentType). Mapping: code-writing\u2192codex, code-review\u2192claude, document-writing\u2192claude, document-review\u2192codex, research\u2192codex, mixed\u2192claude."
|
|
817
|
+
),
|
|
677
818
|
timeoutMs: z2.number().optional().describe("Timeout in milliseconds for agent execution. Default: no timeout."),
|
|
678
819
|
taskInstructionPath: z2.string().optional().describe(
|
|
679
820
|
"Path to a file containing task instructions. Content is prepended to the prompt. Path is resolved relative to the project root and validated against path traversal."
|
|
680
821
|
),
|
|
681
|
-
label: z2.string().optional().describe("Human-readable label for identifying this agent in parallel results")
|
|
822
|
+
label: z2.string().optional().describe("Human-readable label for identifying this agent in parallel results and logs.")
|
|
682
823
|
});
|
|
683
824
|
}
|
|
684
825
|
});
|
|
@@ -994,7 +1135,10 @@ var init_backend_selector = __esm({
|
|
|
994
1135
|
"skill-developer": "codex",
|
|
995
1136
|
"security-auditor": "codex",
|
|
996
1137
|
"analytics-engineer": "codex",
|
|
997
|
-
"payments-billing": "codex"
|
|
1138
|
+
"payments-billing": "codex",
|
|
1139
|
+
"coder": "codex",
|
|
1140
|
+
"researcher": "codex",
|
|
1141
|
+
"documenter": "claude"
|
|
998
1142
|
};
|
|
999
1143
|
TASK_TYPE_TO_BACKEND_MAP = {
|
|
1000
1144
|
"code-writing": "codex",
|
|
@@ -1137,13 +1281,12 @@ __export(server_exports, {
|
|
|
1137
1281
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1138
1282
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1139
1283
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
1140
|
-
import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
|
|
1141
1284
|
import { InMemoryTaskMessageQueue } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
|
|
1142
1285
|
import { createServer } from "http";
|
|
1143
1286
|
import { randomUUID } from "crypto";
|
|
1144
1287
|
import { z as z5 } from "zod";
|
|
1145
1288
|
function createMcpServerOptions() {
|
|
1146
|
-
const taskStore = new
|
|
1289
|
+
const taskStore = new DeferredCleanupTaskStore();
|
|
1147
1290
|
return {
|
|
1148
1291
|
capabilities: { tasks: { requests: { tools: { call: {} } } } },
|
|
1149
1292
|
taskStore,
|
|
@@ -1155,6 +1298,7 @@ var spawnAgentsParallelInputShape, MAX_CHILD_HTTP_SESSIONS, RelayMCPServer;
|
|
|
1155
1298
|
var init_server = __esm({
|
|
1156
1299
|
"src/mcp-server/server.ts"() {
|
|
1157
1300
|
"use strict";
|
|
1301
|
+
init_deferred_cleanup_task_store();
|
|
1158
1302
|
init_recursion_guard();
|
|
1159
1303
|
init_spawn_agent();
|
|
1160
1304
|
init_spawn_agents_parallel();
|
|
@@ -1180,7 +1324,7 @@ var init_server = __esm({
|
|
|
1180
1324
|
this.guard = new RecursionGuard(guardConfig);
|
|
1181
1325
|
this.backendSelector = new BackendSelector();
|
|
1182
1326
|
this.server = new McpServer(
|
|
1183
|
-
{ name: "agentic-relay", version: "0.12.
|
|
1327
|
+
{ name: "agentic-relay", version: "0.12.2" },
|
|
1184
1328
|
createMcpServerOptions()
|
|
1185
1329
|
);
|
|
1186
1330
|
this.registerTools(this.server);
|
|
@@ -1296,7 +1440,7 @@ var init_server = __esm({
|
|
|
1296
1440
|
server.experimental.tasks.registerToolTask(
|
|
1297
1441
|
"spawn_agent",
|
|
1298
1442
|
{
|
|
1299
|
-
description: "Spawn a sub-agent on
|
|
1443
|
+
description: "Spawn a sub-agent on a backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Backend is auto-selected by priority: preferredBackend > agentType mapping (coder/researcher\u2192codex, documenter\u2192claude) > taskType mapping > default (claude). Use 'agentDefinition' to inject an agent .md file, 'skillContext' for a SKILL.md, or 'systemPrompt' for custom instructions.",
|
|
1300
1444
|
inputSchema: spawnAgentInputSchema.shape,
|
|
1301
1445
|
execution: { taskSupport: "optional" }
|
|
1302
1446
|
},
|
|
@@ -1310,18 +1454,19 @@ var init_server = __esm({
|
|
|
1310
1454
|
return { task };
|
|
1311
1455
|
},
|
|
1312
1456
|
getTask: async (_params, extra) => {
|
|
1313
|
-
|
|
1314
|
-
return { task: task ?? void 0 };
|
|
1457
|
+
return await extra.taskStore.getTask(extra.taskId);
|
|
1315
1458
|
},
|
|
1316
1459
|
getTaskResult: async (_params, extra) => {
|
|
1317
|
-
return await extra.taskStore.getTaskResult(
|
|
1460
|
+
return await extra.taskStore.getTaskResult(
|
|
1461
|
+
extra.taskId
|
|
1462
|
+
);
|
|
1318
1463
|
}
|
|
1319
1464
|
}
|
|
1320
1465
|
);
|
|
1321
1466
|
server.experimental.tasks.registerToolTask(
|
|
1322
1467
|
"spawn_agents_parallel",
|
|
1323
1468
|
{
|
|
1324
|
-
description: "Spawn multiple sub-agents in parallel across available backends. Each agent entry accepts the same parameters as spawn_agent.
|
|
1469
|
+
description: "Spawn multiple sub-agents in parallel across available backends. Use this when you have 2+ independent tasks that can execute concurrently. Each agent entry accepts the same parameters as spawn_agent. Results are returned as an array with per-agent status (success/failure). RecursionGuard batch pre-validation ensures the batch fits within call limits. Failed results include 'originalInput' for retry via retry_failed_agents.",
|
|
1325
1470
|
inputSchema: spawnAgentsParallelInputShape,
|
|
1326
1471
|
execution: { taskSupport: "optional" }
|
|
1327
1472
|
},
|
|
@@ -1335,11 +1480,12 @@ var init_server = __esm({
|
|
|
1335
1480
|
return { task };
|
|
1336
1481
|
},
|
|
1337
1482
|
getTask: async (_params, extra) => {
|
|
1338
|
-
|
|
1339
|
-
return { task: task ?? void 0 };
|
|
1483
|
+
return await extra.taskStore.getTask(extra.taskId);
|
|
1340
1484
|
},
|
|
1341
1485
|
getTaskResult: async (_params, extra) => {
|
|
1342
|
-
return await extra.taskStore.getTaskResult(
|
|
1486
|
+
return await extra.taskStore.getTaskResult(
|
|
1487
|
+
extra.taskId
|
|
1488
|
+
);
|
|
1343
1489
|
}
|
|
1344
1490
|
}
|
|
1345
1491
|
);
|
|
@@ -1396,8 +1542,8 @@ var init_server = __esm({
|
|
|
1396
1542
|
"list_sessions",
|
|
1397
1543
|
"List relay sessions, optionally filtered by backend.",
|
|
1398
1544
|
{
|
|
1399
|
-
backend: z5.enum(["claude", "codex", "gemini"]).optional(),
|
|
1400
|
-
limit: z5.number().optional()
|
|
1545
|
+
backend: z5.enum(["claude", "codex", "gemini"]).optional().describe("Filter sessions by backend type."),
|
|
1546
|
+
limit: z5.number().optional().describe("Maximum number of sessions to return. Default: 10.")
|
|
1401
1547
|
},
|
|
1402
1548
|
async (params) => {
|
|
1403
1549
|
try {
|
|
@@ -1426,7 +1572,7 @@ var init_server = __esm({
|
|
|
1426
1572
|
"get_context_status",
|
|
1427
1573
|
"Get the context usage status of a relay session. Returns usage data from ContextMonitor when available, otherwise estimated values.",
|
|
1428
1574
|
{
|
|
1429
|
-
sessionId: z5.string()
|
|
1575
|
+
sessionId: z5.string().describe("Relay session ID to query context usage for.")
|
|
1430
1576
|
},
|
|
1431
1577
|
async (params) => {
|
|
1432
1578
|
try {
|
|
@@ -1542,7 +1688,7 @@ var init_server = __esm({
|
|
|
1542
1688
|
sessionIdGenerator: () => randomUUID()
|
|
1543
1689
|
});
|
|
1544
1690
|
const server = new McpServer(
|
|
1545
|
-
{ name: "agentic-relay", version: "0.12.
|
|
1691
|
+
{ name: "agentic-relay", version: "0.12.2" },
|
|
1546
1692
|
createMcpServerOptions()
|
|
1547
1693
|
);
|
|
1548
1694
|
this.registerTools(server);
|
|
@@ -3414,7 +3560,8 @@ var HooksEngine = class _HooksEngine {
|
|
|
3414
3560
|
const message = error instanceof Error ? error.message : String(error);
|
|
3415
3561
|
if (strategy === "abort") {
|
|
3416
3562
|
throw new Error(
|
|
3417
|
-
`Hook "${def.command}" failed (abort): ${message}
|
|
3563
|
+
`Hook "${def.command}" failed (abort): ${message}`,
|
|
3564
|
+
{ cause: error }
|
|
3418
3565
|
);
|
|
3419
3566
|
}
|
|
3420
3567
|
if (strategy === "warn") {
|
|
@@ -3461,7 +3608,8 @@ var HooksEngine = class _HooksEngine {
|
|
|
3461
3608
|
const message = error instanceof Error ? error.message : String(error);
|
|
3462
3609
|
if (strategy === "abort") {
|
|
3463
3610
|
throw new Error(
|
|
3464
|
-
`Hook "${def.command}" failed (abort): ${message}
|
|
3611
|
+
`Hook "${def.command}" failed (abort): ${message}`,
|
|
3612
|
+
{ cause: error }
|
|
3465
3613
|
);
|
|
3466
3614
|
}
|
|
3467
3615
|
if (strategy === "warn") {
|
|
@@ -3853,7 +4001,6 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
|
|
|
3853
4001
|
try {
|
|
3854
4002
|
if (flags.prompt) {
|
|
3855
4003
|
logger.debug(`Executing prompt on ${backendId}`);
|
|
3856
|
-
let nativeSessionId;
|
|
3857
4004
|
if (adapter.executeStreaming) {
|
|
3858
4005
|
for await (const event of adapter.executeStreaming(flags)) {
|
|
3859
4006
|
switch (event.type) {
|
|
@@ -3894,7 +4041,6 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
|
|
|
3894
4041
|
}
|
|
3895
4042
|
case "done":
|
|
3896
4043
|
process.exitCode = event.result.exitCode;
|
|
3897
|
-
nativeSessionId = event.nativeSessionId;
|
|
3898
4044
|
if (event.nativeSessionId && sessionManager2 && relaySessionId) {
|
|
3899
4045
|
try {
|
|
3900
4046
|
await sessionManager2.update(relaySessionId, {
|
|
@@ -3911,7 +4057,6 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
|
|
|
3911
4057
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
3912
4058
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
3913
4059
|
process.exitCode = result.exitCode;
|
|
3914
|
-
nativeSessionId = result.nativeSessionId;
|
|
3915
4060
|
if (result.nativeSessionId && sessionManager2 && relaySessionId) {
|
|
3916
4061
|
try {
|
|
3917
4062
|
await sessionManager2.update(relaySessionId, {
|
|
@@ -4495,7 +4640,7 @@ function createVersionCommand(registry2) {
|
|
|
4495
4640
|
description: "Show relay and backend versions"
|
|
4496
4641
|
},
|
|
4497
4642
|
async run() {
|
|
4498
|
-
const relayVersion = "0.12.
|
|
4643
|
+
const relayVersion = "0.12.2";
|
|
4499
4644
|
console.log(`agentic-relay v${relayVersion}`);
|
|
4500
4645
|
console.log("");
|
|
4501
4646
|
console.log("Backends:");
|
|
@@ -4845,7 +4990,7 @@ void configManager.getConfig().then((config) => {
|
|
|
4845
4990
|
var main = defineCommand10({
|
|
4846
4991
|
meta: {
|
|
4847
4992
|
name: "relay",
|
|
4848
|
-
version: "0.12.
|
|
4993
|
+
version: "0.12.2",
|
|
4849
4994
|
description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
|
|
4850
4995
|
},
|
|
4851
4996
|
subCommands: {
|
package/package.json
CHANGED