zooid 0.1.0 → 0.1.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/dist/index.js +169 -29
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -482,10 +482,6 @@ async function runChannelList(client) {
|
|
|
482
482
|
const c = client ?? createClient();
|
|
483
483
|
return c.listChannels();
|
|
484
484
|
}
|
|
485
|
-
async function runChannelAddPublisher(channelId, name, client) {
|
|
486
|
-
const c = client ?? createClient();
|
|
487
|
-
return c.addPublisher(channelId, name);
|
|
488
|
-
}
|
|
489
485
|
async function runChannelUpdate(channelId, options, client) {
|
|
490
486
|
const c = client ?? createClient();
|
|
491
487
|
return c.updateChannel(channelId, options);
|
|
@@ -637,6 +633,9 @@ function printError(message) {
|
|
|
637
633
|
function printInfo(label, value) {
|
|
638
634
|
console.log(` ${label}: ${value}`);
|
|
639
635
|
}
|
|
636
|
+
function printStep(message) {
|
|
637
|
+
console.log(` ${message}`);
|
|
638
|
+
}
|
|
640
639
|
function formatRelative(isoString) {
|
|
641
640
|
const diff = Date.now() - new Date(isoString).getTime();
|
|
642
641
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -947,6 +946,17 @@ async function runServerSet(fields, client) {
|
|
|
947
946
|
return c.updateServerMeta(fields);
|
|
948
947
|
}
|
|
949
948
|
|
|
949
|
+
// src/commands/token.ts
|
|
950
|
+
async function runTokenMint(scope, options) {
|
|
951
|
+
const client = createClient();
|
|
952
|
+
const body = { scope };
|
|
953
|
+
if (options.channels?.length) body.channels = options.channels;
|
|
954
|
+
if (options.sub) body.sub = options.sub;
|
|
955
|
+
if (options.name) body.name = options.name;
|
|
956
|
+
if (options.expiresIn) body.expires_in = options.expiresIn;
|
|
957
|
+
return client.mintToken(body);
|
|
958
|
+
}
|
|
959
|
+
|
|
950
960
|
// src/commands/dev.ts
|
|
951
961
|
import { execSync, spawn } from "child_process";
|
|
952
962
|
import crypto2 from "crypto";
|
|
@@ -986,6 +996,34 @@ async function createAdminToken(secret) {
|
|
|
986
996
|
const signature = base64url(Buffer.from(sig));
|
|
987
997
|
return `${data}.${signature}`;
|
|
988
998
|
}
|
|
999
|
+
async function createEdDSAAdminToken(privateKeyJwk, kid) {
|
|
1000
|
+
const header = base64url(
|
|
1001
|
+
Buffer.from(JSON.stringify({ alg: "EdDSA", typ: "JWT", kid }))
|
|
1002
|
+
);
|
|
1003
|
+
const payload = base64url(
|
|
1004
|
+
Buffer.from(
|
|
1005
|
+
JSON.stringify({
|
|
1006
|
+
scope: "admin",
|
|
1007
|
+
iat: Math.floor(Date.now() / 1e3)
|
|
1008
|
+
})
|
|
1009
|
+
)
|
|
1010
|
+
);
|
|
1011
|
+
const message = `${header}.${payload}`;
|
|
1012
|
+
const privateKey = await crypto.subtle.importKey(
|
|
1013
|
+
"jwk",
|
|
1014
|
+
privateKeyJwk,
|
|
1015
|
+
{ name: "Ed25519" },
|
|
1016
|
+
false,
|
|
1017
|
+
["sign"]
|
|
1018
|
+
);
|
|
1019
|
+
const sig = await crypto.subtle.sign(
|
|
1020
|
+
"Ed25519",
|
|
1021
|
+
privateKey,
|
|
1022
|
+
new TextEncoder().encode(message)
|
|
1023
|
+
);
|
|
1024
|
+
const signature = base64url(Buffer.from(sig));
|
|
1025
|
+
return `${message}.${signature}`;
|
|
1026
|
+
}
|
|
989
1027
|
|
|
990
1028
|
// src/commands/dev.ts
|
|
991
1029
|
function findServerDir() {
|
|
@@ -1337,7 +1375,7 @@ async function runDeploy() {
|
|
|
1337
1375
|
console.log("");
|
|
1338
1376
|
printInfo("Deploy type", "First deploy \u2014 setting up database and secrets");
|
|
1339
1377
|
console.log("");
|
|
1340
|
-
|
|
1378
|
+
printStep(`Creating D1 database (${dbName})...`);
|
|
1341
1379
|
const d1Output = wrangler(`d1 create ${dbName}`, stagingDir, creds);
|
|
1342
1380
|
const dbIdMatch = d1Output.match(/database_id\s*=\s*"([^"]+)"/);
|
|
1343
1381
|
if (!dbIdMatch) {
|
|
@@ -1370,7 +1408,7 @@ async function runDeploy() {
|
|
|
1370
1408
|
printSuccess("Configured wrangler.toml");
|
|
1371
1409
|
const schemaPath = path5.join(stagingDir, "src/db/schema.sql");
|
|
1372
1410
|
if (fs6.existsSync(schemaPath)) {
|
|
1373
|
-
|
|
1411
|
+
printStep("Running database schema migration...");
|
|
1374
1412
|
wrangler(
|
|
1375
1413
|
`d1 execute ${dbName} --remote --file=${schemaPath}`,
|
|
1376
1414
|
stagingDir,
|
|
@@ -1378,8 +1416,7 @@ async function runDeploy() {
|
|
|
1378
1416
|
);
|
|
1379
1417
|
printSuccess("Database schema initialized");
|
|
1380
1418
|
}
|
|
1381
|
-
|
|
1382
|
-
const jwtSecret = crypto3.randomBytes(32).toString("base64");
|
|
1419
|
+
printStep("Generating secrets...");
|
|
1383
1420
|
const keyPair = await crypto3.subtle.generateKey("Ed25519", true, [
|
|
1384
1421
|
"sign",
|
|
1385
1422
|
"verify"
|
|
@@ -1392,12 +1429,16 @@ async function runDeploy() {
|
|
|
1392
1429
|
"raw",
|
|
1393
1430
|
keyPair.publicKey
|
|
1394
1431
|
);
|
|
1432
|
+
const privateKeyJwk = await crypto3.subtle.exportKey(
|
|
1433
|
+
"jwk",
|
|
1434
|
+
keyPair.privateKey
|
|
1435
|
+
);
|
|
1436
|
+
const publicKeyJwk = await crypto3.subtle.exportKey(
|
|
1437
|
+
"jwk",
|
|
1438
|
+
keyPair.publicKey
|
|
1439
|
+
);
|
|
1395
1440
|
const privateKeyB64 = Buffer.from(privateKeyRaw).toString("base64");
|
|
1396
1441
|
const publicKeyB64 = Buffer.from(publicKeyRaw).toString("base64");
|
|
1397
|
-
wrangler("secret put ZOOID_JWT_SECRET", stagingDir, creds, {
|
|
1398
|
-
input: jwtSecret
|
|
1399
|
-
});
|
|
1400
|
-
printSuccess("Set ZOOID_JWT_SECRET");
|
|
1401
1442
|
wrangler("secret put ZOOID_SIGNING_KEY", stagingDir, creds, {
|
|
1402
1443
|
input: privateKeyB64
|
|
1403
1444
|
});
|
|
@@ -1406,8 +1447,17 @@ async function runDeploy() {
|
|
|
1406
1447
|
input: publicKeyB64
|
|
1407
1448
|
});
|
|
1408
1449
|
printSuccess("Set ZOOID_PUBLIC_KEY (Ed25519 public)");
|
|
1409
|
-
|
|
1410
|
-
|
|
1450
|
+
const kid = "local-1";
|
|
1451
|
+
const xValue = publicKeyJwk.x;
|
|
1452
|
+
const insertSql = `INSERT INTO trusted_keys (kid, x, issuer) VALUES ('${kid}', '${xValue}', 'local');`;
|
|
1453
|
+
wrangler(
|
|
1454
|
+
`d1 execute ${dbName} --remote --command="${insertSql}"`,
|
|
1455
|
+
stagingDir,
|
|
1456
|
+
creds
|
|
1457
|
+
);
|
|
1458
|
+
printSuccess(`Registered EdDSA public key (kid: ${kid})`);
|
|
1459
|
+
adminToken = await createEdDSAAdminToken(privateKeyJwk, kid);
|
|
1460
|
+
printSuccess("EdDSA admin token generated");
|
|
1411
1461
|
} else {
|
|
1412
1462
|
console.log("");
|
|
1413
1463
|
printInfo("Deploy type", "Redeploying existing server");
|
|
@@ -1439,8 +1489,87 @@ async function runDeploy() {
|
|
|
1439
1489
|
} catch {
|
|
1440
1490
|
}
|
|
1441
1491
|
fs6.writeFileSync(wranglerTomlPath, tomlContent);
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1492
|
+
const schemaPath = path5.join(stagingDir, "src/db/schema.sql");
|
|
1493
|
+
if (fs6.existsSync(schemaPath)) {
|
|
1494
|
+
printStep("Running schema migration...");
|
|
1495
|
+
wrangler(
|
|
1496
|
+
`d1 execute ${dbName} --remote --file=${schemaPath}`,
|
|
1497
|
+
stagingDir,
|
|
1498
|
+
creds
|
|
1499
|
+
);
|
|
1500
|
+
printSuccess("Schema up to date");
|
|
1501
|
+
}
|
|
1502
|
+
const migrations = ["ALTER TABLE events ADD COLUMN publisher_name TEXT"];
|
|
1503
|
+
for (const sql of migrations) {
|
|
1504
|
+
try {
|
|
1505
|
+
wrangler(
|
|
1506
|
+
`d1 execute ${dbName} --remote --command="${sql}"`,
|
|
1507
|
+
stagingDir,
|
|
1508
|
+
creds
|
|
1509
|
+
);
|
|
1510
|
+
} catch {
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
try {
|
|
1514
|
+
const keysOutput = wrangler(
|
|
1515
|
+
`d1 execute ${dbName} --remote --json --command="SELECT kid FROM trusted_keys WHERE issuer = 'local' LIMIT 1"`,
|
|
1516
|
+
stagingDir,
|
|
1517
|
+
creds
|
|
1518
|
+
);
|
|
1519
|
+
const keysResult = JSON.parse(keysOutput);
|
|
1520
|
+
const hasLocalKey = keysResult?.[0]?.results?.length > 0;
|
|
1521
|
+
if (!hasLocalKey) {
|
|
1522
|
+
printStep("Upgrading to EdDSA auth...");
|
|
1523
|
+
const keyPair = await crypto3.subtle.generateKey("Ed25519", true, [
|
|
1524
|
+
"sign",
|
|
1525
|
+
"verify"
|
|
1526
|
+
]);
|
|
1527
|
+
const privateKeyRaw = await crypto3.subtle.exportKey(
|
|
1528
|
+
"pkcs8",
|
|
1529
|
+
keyPair.privateKey
|
|
1530
|
+
);
|
|
1531
|
+
const publicKeyRaw = await crypto3.subtle.exportKey(
|
|
1532
|
+
"raw",
|
|
1533
|
+
keyPair.publicKey
|
|
1534
|
+
);
|
|
1535
|
+
const privateKeyJwk = await crypto3.subtle.exportKey(
|
|
1536
|
+
"jwk",
|
|
1537
|
+
keyPair.privateKey
|
|
1538
|
+
);
|
|
1539
|
+
const publicKeyJwk = await crypto3.subtle.exportKey(
|
|
1540
|
+
"jwk",
|
|
1541
|
+
keyPair.publicKey
|
|
1542
|
+
);
|
|
1543
|
+
const privateKeyB64 = Buffer.from(privateKeyRaw).toString("base64");
|
|
1544
|
+
const publicKeyB64 = Buffer.from(publicKeyRaw).toString("base64");
|
|
1545
|
+
wrangler("secret put ZOOID_SIGNING_KEY", stagingDir, creds, {
|
|
1546
|
+
input: privateKeyB64
|
|
1547
|
+
});
|
|
1548
|
+
wrangler("secret put ZOOID_PUBLIC_KEY", stagingDir, creds, {
|
|
1549
|
+
input: publicKeyB64
|
|
1550
|
+
});
|
|
1551
|
+
const kid = "local-1";
|
|
1552
|
+
const xValue = publicKeyJwk.x;
|
|
1553
|
+
const insertSql = `INSERT INTO trusted_keys (kid, x, issuer) VALUES ('${kid}', '${xValue}', 'local');`;
|
|
1554
|
+
wrangler(
|
|
1555
|
+
`d1 execute ${dbName} --remote --command="${insertSql}"`,
|
|
1556
|
+
stagingDir,
|
|
1557
|
+
creds
|
|
1558
|
+
);
|
|
1559
|
+
printSuccess("EdDSA keypair generated and registered");
|
|
1560
|
+
adminToken = await createEdDSAAdminToken(privateKeyJwk, kid);
|
|
1561
|
+
printSuccess("Upgraded to EdDSA admin token");
|
|
1562
|
+
}
|
|
1563
|
+
} catch {
|
|
1564
|
+
printInfo(
|
|
1565
|
+
"Note",
|
|
1566
|
+
"Could not check EdDSA key status, keeping existing token"
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
if (!adminToken) {
|
|
1570
|
+
const existingConfig = loadConfig();
|
|
1571
|
+
adminToken = existingConfig.admin_token;
|
|
1572
|
+
}
|
|
1444
1573
|
if (!adminToken) {
|
|
1445
1574
|
printError(
|
|
1446
1575
|
"No admin token found in ~/.zooid/config.json for this server"
|
|
@@ -1452,7 +1581,7 @@ async function runDeploy() {
|
|
|
1452
1581
|
process.exit(1);
|
|
1453
1582
|
}
|
|
1454
1583
|
}
|
|
1455
|
-
|
|
1584
|
+
printStep("Deploying worker...");
|
|
1456
1585
|
const deployOutput = wranglerVerbose("deploy", stagingDir, creds);
|
|
1457
1586
|
const { workerUrl, customDomain } = parseDeployUrls(deployOutput);
|
|
1458
1587
|
printSuccess("Worker deployed");
|
|
@@ -1730,16 +1859,6 @@ channelCmd.command("list").description("List all channels").action(async () => {
|
|
|
1730
1859
|
handleError("channel list", err);
|
|
1731
1860
|
}
|
|
1732
1861
|
});
|
|
1733
|
-
channelCmd.command("add-publisher <channel>").description("Add a publisher to a channel").requiredOption("--name <name>", "Publisher name").action(async (channel, opts) => {
|
|
1734
|
-
try {
|
|
1735
|
-
const result = await runChannelAddPublisher(channel, opts.name);
|
|
1736
|
-
printSuccess(`Added publisher: ${result.name}`);
|
|
1737
|
-
printInfo("Publisher ID", result.id);
|
|
1738
|
-
printInfo("Publish token", result.publish_token);
|
|
1739
|
-
} catch (err) {
|
|
1740
|
-
handleError("channel add-publisher", err);
|
|
1741
|
-
}
|
|
1742
|
-
});
|
|
1743
1862
|
channelCmd.command("delete <id>").description("Delete a channel and all its data").option("-y, --yes", "Skip confirmation prompt").action(async (id, opts) => {
|
|
1744
1863
|
try {
|
|
1745
1864
|
if (!opts.yes) {
|
|
@@ -1750,7 +1869,7 @@ channelCmd.command("delete <id>").description("Delete a channel and all its data
|
|
|
1750
1869
|
});
|
|
1751
1870
|
const answer = await new Promise((resolve) => {
|
|
1752
1871
|
rl.question(
|
|
1753
|
-
`Delete channel "${id}" and all its events
|
|
1872
|
+
`Delete channel "${id}" and all its events and webhooks? [y/N] `,
|
|
1754
1873
|
resolve
|
|
1755
1874
|
);
|
|
1756
1875
|
});
|
|
@@ -1907,6 +2026,27 @@ serverCmd.command("set").description("Update server metadata").option("--name <n
|
|
|
1907
2026
|
handleError("server set", err);
|
|
1908
2027
|
}
|
|
1909
2028
|
});
|
|
2029
|
+
program.command("token <scope>").description("Mint a new token (admin, publish, or subscribe)").argument("[channels...]", "Channels to scope the token to").option("--sub <sub>", "Subject identifier (e.g. publisher ID)").option("--name <name>", "Display name (used for publisher identity)").option("--expires-in <duration>", "Token expiry (e.g. 5m, 1h, 7d, 30d)").action(async (scope, channels, opts) => {
|
|
2030
|
+
try {
|
|
2031
|
+
if (!["admin", "publish", "subscribe"].includes(scope)) {
|
|
2032
|
+
throw new Error(
|
|
2033
|
+
`Invalid scope "${scope}". Must be one of: admin, publish, subscribe`
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
const result = await runTokenMint(
|
|
2037
|
+
scope,
|
|
2038
|
+
{
|
|
2039
|
+
channels: channels.length > 0 ? channels : void 0,
|
|
2040
|
+
sub: opts.sub,
|
|
2041
|
+
name: opts.name,
|
|
2042
|
+
expiresIn: opts.expiresIn
|
|
2043
|
+
}
|
|
2044
|
+
);
|
|
2045
|
+
console.log(result.token);
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
handleError("token", err);
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
1910
2050
|
program.command("status").description("Check server status").action(async () => {
|
|
1911
2051
|
try {
|
|
1912
2052
|
const { discovery, identity } = await runStatus();
|
|
@@ -1922,7 +2062,7 @@ program.command("status").description("Check server status").action(async () =>
|
|
|
1922
2062
|
handleError("status", err);
|
|
1923
2063
|
}
|
|
1924
2064
|
});
|
|
1925
|
-
program.command("history").description("Show tail/subscribe history
|
|
2065
|
+
program.command("history").description("Show tail/subscribe history").option("-n, --limit <n>", "Max entries to show", "20").option("--json", "Output as JSON").action((opts) => {
|
|
1926
2066
|
try {
|
|
1927
2067
|
const entries = runHistory();
|
|
1928
2068
|
const limit = parseInt(opts.limit, 10);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zooid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Open-source pub/sub server for AI agents. Publish signals, subscribe via webhook, WebSocket, polling, or RSS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@inquirer/checkbox": "^5.0.7",
|
|
26
26
|
"commander": "^14.0.3",
|
|
27
|
-
"@zooid/sdk": "^0.1.
|
|
28
|
-
"@zooid/types": "^0.1.
|
|
29
|
-
"@zooid/server": "^0.1.
|
|
27
|
+
"@zooid/sdk": "^0.1.1",
|
|
28
|
+
"@zooid/types": "^0.1.1",
|
|
29
|
+
"@zooid/server": "^0.1.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@cloudflare/vitest-pool-workers": "^0.8.34",
|