@agenvo/codex-app-server 0.1.0 → 0.2.0-rc.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/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/cli.js +116 -48
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
Connect to an independently running Codex app-server over a Unix socket or loopback WebSocket. Connector shutdown and updates leave the native service and its turns running.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm install --global @agenvo/codex-app-server@0.
|
|
8
|
+
npm install --global @agenvo/codex-app-server@0.2.0-rc.1
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
For AI agents: follow the [installation guide](https://github.com/Xuanwo/agenvo/blob/v0.
|
|
11
|
+
For AI agents: follow the [installation guide](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/installation.md) for relay deployment, device pairing, and client authorization.
|
|
12
12
|
|
|
13
13
|
Requires Node.js 24.13+. Installing a Connector does not install Herdr or Codex.
|
|
14
14
|
|
|
15
|
-
[Usage and deployment](https://github.com/Xuanwo/agenvo/blob/v0.
|
|
15
|
+
[Usage and deployment](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/usage.md)
|
package/README.zh-CN.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
通过 Unix socket 或 loopback WebSocket 连接独立运行的 Codex app-server。关闭或更新 Connector 不会停止原生服务及其轮次。
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm install --global @agenvo/codex-app-server@0.
|
|
8
|
+
npm install --global @agenvo/codex-app-server@0.2.0-rc.1
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
AI Agent 请按照[安装指南](https://github.com/Xuanwo/agenvo/blob/v0.
|
|
11
|
+
AI Agent 请按照[安装指南](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/installation.zh-CN.md)完成中继部署、设备配对和客户端授权。
|
|
12
12
|
|
|
13
13
|
需要 Node.js 24.13+。安装 Connector 不会安装 Herdr 或 Codex。
|
|
14
14
|
|
|
15
|
-
[使用与部署说明](https://github.com/Xuanwo/agenvo/blob/v0.
|
|
15
|
+
[使用与部署说明](https://github.com/Xuanwo/agenvo/blob/v0.2.0-rc.1/docs/usage.zh-CN.md)
|
package/dist/cli.js
CHANGED
|
@@ -5,15 +5,6 @@ var __export = (target, all) => {
|
|
|
5
5
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
// ../../packages/connector/src/cli/main.ts
|
|
9
|
-
import { parseArgs } from "node:util";
|
|
10
|
-
import { execFile as execFile2, spawn } from "node:child_process";
|
|
11
|
-
import { promisify as promisify2 } from "node:util";
|
|
12
|
-
import { randomBytes } from "node:crypto";
|
|
13
|
-
import { readFile as readFile3, unlink as unlink3, stat as stat2 } from "node:fs/promises";
|
|
14
|
-
import { join as join4 } from "node:path";
|
|
15
|
-
import { hostname as hostname3 } from "node:os";
|
|
16
|
-
|
|
17
8
|
// ../../node_modules/zod/v4/classic/external.js
|
|
18
9
|
var external_exports = {};
|
|
19
10
|
__export(external_exports, {
|
|
@@ -19682,6 +19673,64 @@ function date4(params) {
|
|
|
19682
19673
|
return _coercedDate(ZodDate, params);
|
|
19683
19674
|
}
|
|
19684
19675
|
|
|
19676
|
+
// ../../packages/protocol/src/address.ts
|
|
19677
|
+
var RelayAddress = class {
|
|
19678
|
+
baseUrl;
|
|
19679
|
+
origin;
|
|
19680
|
+
prefix;
|
|
19681
|
+
constructor(value) {
|
|
19682
|
+
const url2 = new URL(value);
|
|
19683
|
+
const rawPath = value.replace(/^https:\/\/[^/]+/i, "");
|
|
19684
|
+
if (!/^https:\/\//i.test(value) || /[\s\x00-\x1f\x7f]/.test(value) || url2.protocol !== "https:" || url2.username || url2.password || value.includes("?") || value.includes("#") || value.includes("\\") || /%2f|%5c/i.test(url2.pathname) || url2.pathname.includes("//") || rawPath.split("/").some((part) => /^(\.|%2e){1,2}$/i.test(part)))
|
|
19685
|
+
throw new Error(
|
|
19686
|
+
"Expected an HTTPS base URL without credentials, query, fragment or ambiguous path segments"
|
|
19687
|
+
);
|
|
19688
|
+
this.origin = url2.origin;
|
|
19689
|
+
this.prefix = url2.pathname.replace(/\/$/, "");
|
|
19690
|
+
this.baseUrl = this.origin + this.prefix;
|
|
19691
|
+
}
|
|
19692
|
+
get authorizationMetadataUrl() {
|
|
19693
|
+
return this.origin + "/.well-known/oauth-authorization-server" + this.prefix;
|
|
19694
|
+
}
|
|
19695
|
+
get resourceMetadataUrl() {
|
|
19696
|
+
return this.origin + "/.well-known/oauth-protected-resource" + this.path("/mcp");
|
|
19697
|
+
}
|
|
19698
|
+
path(route) {
|
|
19699
|
+
return this.prefix + route;
|
|
19700
|
+
}
|
|
19701
|
+
url(route) {
|
|
19702
|
+
return this.baseUrl + route;
|
|
19703
|
+
}
|
|
19704
|
+
/** Undefined means this URL does not belong to this deployment. */
|
|
19705
|
+
route(value) {
|
|
19706
|
+
const url2 = new URL(value);
|
|
19707
|
+
if (url2.origin !== this.origin) return;
|
|
19708
|
+
if (url2.pathname === this.prefix) return "/";
|
|
19709
|
+
if (!url2.pathname.startsWith(this.prefix + "/")) return;
|
|
19710
|
+
return url2.pathname.slice(this.prefix.length);
|
|
19711
|
+
}
|
|
19712
|
+
};
|
|
19713
|
+
var baseUrlSchema = external_exports.string().transform((value, ctx) => {
|
|
19714
|
+
try {
|
|
19715
|
+
return new RelayAddress(value).baseUrl;
|
|
19716
|
+
} catch {
|
|
19717
|
+
ctx.addIssue({
|
|
19718
|
+
code: "custom",
|
|
19719
|
+
message: "Expected an HTTPS base URL with an optional path prefix"
|
|
19720
|
+
});
|
|
19721
|
+
return external_exports.NEVER;
|
|
19722
|
+
}
|
|
19723
|
+
});
|
|
19724
|
+
|
|
19725
|
+
// ../../packages/connector/src/cli/main.ts
|
|
19726
|
+
import { parseArgs } from "node:util";
|
|
19727
|
+
import { execFile as execFile2, spawn } from "node:child_process";
|
|
19728
|
+
import { promisify as promisify2 } from "node:util";
|
|
19729
|
+
import { randomBytes } from "node:crypto";
|
|
19730
|
+
import { readFile as readFile3, unlink as unlink3, stat as stat2 } from "node:fs/promises";
|
|
19731
|
+
import { join as join4 } from "node:path";
|
|
19732
|
+
import { hostname as hostname3 } from "node:os";
|
|
19733
|
+
|
|
19685
19734
|
// ../../packages/connector/src/config.ts
|
|
19686
19735
|
import { homedir } from "node:os";
|
|
19687
19736
|
import { join, resolve, dirname, isAbsolute } from "node:path";
|
|
@@ -19697,9 +19746,10 @@ import {
|
|
|
19697
19746
|
unlink
|
|
19698
19747
|
} from "node:fs/promises";
|
|
19699
19748
|
import { randomUUID } from "node:crypto";
|
|
19749
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
19700
19750
|
|
|
19701
19751
|
// ../../packages/protocol/src/index.ts
|
|
19702
|
-
var VERSION = "0.
|
|
19752
|
+
var VERSION = "0.2.0-rc.1";
|
|
19703
19753
|
var PROTOCOL = 1;
|
|
19704
19754
|
var LIMITS = {
|
|
19705
19755
|
frame: 64 * 1024,
|
|
@@ -19819,10 +19869,7 @@ var commonInstanceFields = {
|
|
|
19819
19869
|
};
|
|
19820
19870
|
var configSchema = (instanceSchema2) => external_exports.strictObject({
|
|
19821
19871
|
schema: external_exports.literal(1),
|
|
19822
|
-
relay:
|
|
19823
|
-
const url2 = new URL(value);
|
|
19824
|
-
return url2.protocol === "https:" && url2.origin === value;
|
|
19825
|
-
}, "Relay must be a canonical HTTPS origin").optional(),
|
|
19872
|
+
relay: baseUrlSchema.optional(),
|
|
19826
19873
|
deviceId: external_exports.string().uuid().optional(),
|
|
19827
19874
|
name: external_exports.string().max(128),
|
|
19828
19875
|
instances: external_exports.array(instanceSchema2).max(128)
|
|
@@ -19842,7 +19889,19 @@ async function atomicJson(path, value) {
|
|
|
19842
19889
|
mode: 384,
|
|
19843
19890
|
flag: "wx"
|
|
19844
19891
|
});
|
|
19845
|
-
|
|
19892
|
+
const deadline = Date.now() + 1e3;
|
|
19893
|
+
for (let attempt = 0; ; attempt++) {
|
|
19894
|
+
try {
|
|
19895
|
+
await rename(temp, path);
|
|
19896
|
+
break;
|
|
19897
|
+
} catch (error62) {
|
|
19898
|
+
if (process.platform !== "win32" || !["EACCES", "EPERM", "EBUSY"].includes(
|
|
19899
|
+
error62.code ?? ""
|
|
19900
|
+
) || Date.now() >= deadline)
|
|
19901
|
+
throw error62;
|
|
19902
|
+
await delay(Math.min(10 * 2 ** attempt, 100, deadline - Date.now()));
|
|
19903
|
+
}
|
|
19904
|
+
}
|
|
19846
19905
|
} finally {
|
|
19847
19906
|
await unlink(temp).catch(() => {
|
|
19848
19907
|
});
|
|
@@ -20038,7 +20097,17 @@ async function run(dir, backend2) {
|
|
|
20038
20097
|
const save = () => {
|
|
20039
20098
|
status.updatedAt = Date.now();
|
|
20040
20099
|
const snapshot = structuredClone(status);
|
|
20041
|
-
saving = saving.then(() => atomicJson(join2(dir, "status.json"), snapshot)).catch(() => {
|
|
20100
|
+
saving = saving.then(() => atomicJson(join2(dir, "status.json"), snapshot)).catch((err) => {
|
|
20101
|
+
connectionLog.error(
|
|
20102
|
+
{
|
|
20103
|
+
event: "connector.status.write_failed",
|
|
20104
|
+
state: snapshot.state,
|
|
20105
|
+
code: err.code,
|
|
20106
|
+
syscall: err.syscall,
|
|
20107
|
+
err
|
|
20108
|
+
},
|
|
20109
|
+
"Failed to publish connector status"
|
|
20110
|
+
);
|
|
20042
20111
|
});
|
|
20043
20112
|
};
|
|
20044
20113
|
const send = (current, packet) => {
|
|
@@ -20099,7 +20168,7 @@ async function run(dir, backend2) {
|
|
|
20099
20168
|
}
|
|
20100
20169
|
const connect = () => {
|
|
20101
20170
|
if (stopped || terminal) return;
|
|
20102
|
-
const url2 = new URL("/connect"
|
|
20171
|
+
const url2 = new URL(target + "/connect");
|
|
20103
20172
|
url2.protocol = url2.protocol === "https:" ? "wss:" : "ws:";
|
|
20104
20173
|
const current = socket = new WebSocket(url2, {
|
|
20105
20174
|
headers: {
|
|
@@ -20438,21 +20507,22 @@ async function service(dir, action, entry) {
|
|
|
20438
20507
|
}
|
|
20439
20508
|
|
|
20440
20509
|
// ../../packages/connector/src/cli/admin-client.ts
|
|
20441
|
-
function
|
|
20442
|
-
if (!value)
|
|
20443
|
-
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
|
|
20447
|
-
|
|
20448
|
-
|
|
20510
|
+
function ownerBaseUrl(value) {
|
|
20511
|
+
if (!value)
|
|
20512
|
+
throw new Fault(
|
|
20513
|
+
"base_url_required",
|
|
20514
|
+
"Pass --base-url https://RELAY[/PREFIX]"
|
|
20515
|
+
);
|
|
20516
|
+
return baseUrlSchema.parse(value);
|
|
20517
|
+
}
|
|
20518
|
+
async function adminRequest(baseUrl, method, path, body = "") {
|
|
20449
20519
|
const secret = process.env.AGENVO_ADMIN_SECRET;
|
|
20450
20520
|
if (!secret)
|
|
20451
20521
|
throw new Fault(
|
|
20452
20522
|
"admin_secret_required",
|
|
20453
20523
|
"Set AGENVO_ADMIN_SECRET for explicit administrator automation, or use the management page."
|
|
20454
20524
|
);
|
|
20455
|
-
const response = await fetch(
|
|
20525
|
+
const response = await fetch(baseUrl + path, {
|
|
20456
20526
|
method,
|
|
20457
20527
|
redirect: "error",
|
|
20458
20528
|
signal: AbortSignal.timeout(15e3),
|
|
@@ -20472,7 +20542,7 @@ async function adminRequest(origin, method, path, body = "") {
|
|
|
20472
20542
|
|
|
20473
20543
|
// ../../packages/connector/src/cli/admin.ts
|
|
20474
20544
|
async function adminCommand(action, kind, options) {
|
|
20475
|
-
const
|
|
20545
|
+
const baseUrl = ownerBaseUrl(String(options["base-url"] ?? ""));
|
|
20476
20546
|
let path, body = "", method = "POST";
|
|
20477
20547
|
if (action === "state") {
|
|
20478
20548
|
path = "/api/admin/state";
|
|
@@ -20496,12 +20566,12 @@ async function adminCommand(action, kind, options) {
|
|
|
20496
20566
|
...options["instance-id"] ? { instanceId: options["instance-id"] } : {}
|
|
20497
20567
|
});
|
|
20498
20568
|
} else throw new Fault("invalid_arguments");
|
|
20499
|
-
return adminRequest(
|
|
20569
|
+
return adminRequest(baseUrl, method, path, body);
|
|
20500
20570
|
}
|
|
20501
20571
|
|
|
20502
20572
|
// ../../packages/connector/src/cli/pairing.ts
|
|
20503
20573
|
async function pairingCommand(action, code, options) {
|
|
20504
|
-
const
|
|
20574
|
+
const baseUrl = ownerBaseUrl(String(options["base-url"] ?? ""));
|
|
20505
20575
|
if (!["list", "approve"].includes(action))
|
|
20506
20576
|
throw new Fault("invalid_arguments");
|
|
20507
20577
|
const method = action === "list" ? "GET" : "POST";
|
|
@@ -20512,7 +20582,7 @@ async function pairingCommand(action, code, options) {
|
|
|
20512
20582
|
"Pass a pairing code and --fingerprint from the device terminal"
|
|
20513
20583
|
);
|
|
20514
20584
|
const body = action === "list" ? "" : JSON.stringify({ code, digest: options.fingerprint });
|
|
20515
|
-
return adminRequest(
|
|
20585
|
+
return adminRequest(baseUrl, method, path, body);
|
|
20516
20586
|
}
|
|
20517
20587
|
|
|
20518
20588
|
// ../../packages/connector/src/cli/main.ts
|
|
@@ -20526,7 +20596,7 @@ async function connectorCli(backend2, entry) {
|
|
|
20526
20596
|
"label",
|
|
20527
20597
|
"binary",
|
|
20528
20598
|
"cwd",
|
|
20529
|
-
"
|
|
20599
|
+
"base-url",
|
|
20530
20600
|
"fingerprint",
|
|
20531
20601
|
"device-id",
|
|
20532
20602
|
"instance-id",
|
|
@@ -20599,11 +20669,11 @@ async function connectorCli(backend2, entry) {
|
|
|
20599
20669
|
${backend2.command} instance add --id ID ${backend2.help}
|
|
20600
20670
|
${backend2.command} connect https://RELAY [--name NAME] [--approve | --no-wait --no-browser]
|
|
20601
20671
|
${backend2.command} connect --cancel
|
|
20602
|
-
${backend2.command} pairing list --
|
|
20603
|
-
${backend2.command} pairing approve CODE --fingerprint SHA256 --
|
|
20604
|
-
${backend2.command} admin state --
|
|
20605
|
-
${backend2.command} admin approve-instance --device-id ID --instance-id ID --fingerprint SHA256 --
|
|
20606
|
-
${backend2.command} admin revoke device|instance|grant --id ID [--instance-id ID] --
|
|
20672
|
+
${backend2.command} pairing list --base-url https://RELAY
|
|
20673
|
+
${backend2.command} pairing approve CODE --fingerprint SHA256 --base-url https://RELAY
|
|
20674
|
+
${backend2.command} admin state --base-url https://RELAY
|
|
20675
|
+
${backend2.command} admin approve-instance --device-id ID --instance-id ID --fingerprint SHA256 --base-url https://RELAY
|
|
20676
|
+
${backend2.command} admin revoke device|instance|grant --id ID [--instance-id ID] --base-url https://RELAY
|
|
20607
20677
|
${backend2.command} run
|
|
20608
20678
|
${backend2.command} service install|uninstall
|
|
20609
20679
|
${backend2.command} status --json
|
|
@@ -20640,7 +20710,7 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20640
20710
|
config: join4(dir, "config.json"),
|
|
20641
20711
|
scope: instance,
|
|
20642
20712
|
...c.relay ? {
|
|
20643
|
-
next: "Restart the connector, inspect " + backend2.command + " admin state --
|
|
20713
|
+
next: "Restart the connector, inspect " + backend2.command + " admin state --base-url " + c.relay + ", then use " + backend2.command + " admin approve-instance."
|
|
20644
20714
|
} : {}
|
|
20645
20715
|
});
|
|
20646
20716
|
return;
|
|
@@ -20658,9 +20728,7 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20658
20728
|
});
|
|
20659
20729
|
return output2({ cancelled: true });
|
|
20660
20730
|
}
|
|
20661
|
-
const relay =
|
|
20662
|
-
if (relay.protocol !== "https:" || relay.pathname !== "/" || relay.username || relay.password || relay.search || relay.hash)
|
|
20663
|
-
throw new Fault("https_origin_required");
|
|
20731
|
+
const relay = baseUrlSchema.parse(subcommand);
|
|
20664
20732
|
const c = await config2();
|
|
20665
20733
|
if (c.deviceId)
|
|
20666
20734
|
throw new Fault(
|
|
@@ -20670,7 +20738,7 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20670
20738
|
let p;
|
|
20671
20739
|
try {
|
|
20672
20740
|
p = JSON.parse(await readFile3(join4(dir, "pairing.json"), "utf8"));
|
|
20673
|
-
if (p.relay !== relay
|
|
20741
|
+
if (p.relay !== relay)
|
|
20674
20742
|
throw new Fault(
|
|
20675
20743
|
"pending_pairing",
|
|
20676
20744
|
"Cancel the existing pairing before choosing a different relay"
|
|
@@ -20701,14 +20769,14 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20701
20769
|
)
|
|
20702
20770
|
)
|
|
20703
20771
|
);
|
|
20704
|
-
p = await post(
|
|
20772
|
+
p = await post(relay + "/pairings", {
|
|
20705
20773
|
digest: await digest(secret),
|
|
20706
20774
|
label: String(options.name ?? c.name),
|
|
20707
20775
|
instances
|
|
20708
20776
|
});
|
|
20709
20777
|
p = {
|
|
20710
20778
|
...p,
|
|
20711
|
-
relay
|
|
20779
|
+
relay,
|
|
20712
20780
|
expires: Date.now() + p.expiresIn * 1e3,
|
|
20713
20781
|
name: String(options.name ?? c.name),
|
|
20714
20782
|
configHash: await digest(JSON.stringify(c.instances))
|
|
@@ -20723,7 +20791,7 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20723
20791
|
if (options["no-wait"]) return;
|
|
20724
20792
|
if (options.approve) {
|
|
20725
20793
|
await pairingCommand("approve", p.code, {
|
|
20726
|
-
|
|
20794
|
+
"base-url": relay,
|
|
20727
20795
|
fingerprint: p.fingerprint
|
|
20728
20796
|
});
|
|
20729
20797
|
} else {
|
|
@@ -20732,13 +20800,13 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20732
20800
|
const expires = p.expires;
|
|
20733
20801
|
while (Date.now() < expires) {
|
|
20734
20802
|
const result = await post(
|
|
20735
|
-
|
|
20803
|
+
relay + "/pairings/poll",
|
|
20736
20804
|
{ code: p.code },
|
|
20737
20805
|
p.pollSecret
|
|
20738
20806
|
);
|
|
20739
20807
|
if (result.status === "approved") {
|
|
20740
20808
|
c.deviceId = result.deviceId;
|
|
20741
|
-
c.relay = relay
|
|
20809
|
+
c.relay = relay;
|
|
20742
20810
|
c.name = p.name;
|
|
20743
20811
|
await saveConfig(c, dir, backend2.schema);
|
|
20744
20812
|
await unlink3(join4(dir, "pairing.json"));
|
|
@@ -20882,7 +20950,7 @@ After changing instances, restart this connector and approve the changed scope.`
|
|
|
20882
20950
|
cloudRevoked,
|
|
20883
20951
|
serviceUninstalled,
|
|
20884
20952
|
...cloudRevoked ? {} : {
|
|
20885
|
-
next: "When connectivity returns, run " + backend2.command + " admin revoke device --id " + revokedDeviceId + " --
|
|
20953
|
+
next: "When connectivity returns, run " + backend2.command + " admin revoke device --id " + revokedDeviceId + " --base-url " + c.relay
|
|
20886
20954
|
}
|
|
20887
20955
|
});
|
|
20888
20956
|
return;
|
|
@@ -23045,7 +23113,7 @@ var CodexAdapter = class {
|
|
|
23045
23113
|
ws.once("close", () => reject(new Fault("runtime_unavailable")));
|
|
23046
23114
|
});
|
|
23047
23115
|
const init = await this.rpc("initialize", {
|
|
23048
|
-
clientInfo: { name: "agenvo", version:
|
|
23116
|
+
clientInfo: { name: "agenvo", version: VERSION },
|
|
23049
23117
|
capabilities: { experimentalApi: true }
|
|
23050
23118
|
});
|
|
23051
23119
|
if (await realpath2(init.codexHome) !== await realpath2(this.config.home))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenvo/codex-app-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"node": ">=24.13.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@agenvo/connector": "0.
|
|
16
|
-
"@agenvo/protocol": "0.
|
|
15
|
+
"@agenvo/connector": "0.2.0-rc.1",
|
|
16
|
+
"@agenvo/protocol": "0.2.0-rc.1",
|
|
17
17
|
"zod": "4.6.5"
|
|
18
18
|
},
|
|
19
19
|
"description": "Codex app-server connector for Agenvo",
|