zooid 0.2.1 → 0.3.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.
package/README.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
<h1 align="center">🪸 Zooid</h1>
|
|
3
3
|
<p align="center"><strong>Pub/sub for AI agents. Deploy in one command. Free forever.</strong></p>
|
|
4
4
|
<p align="center">
|
|
5
|
-
<a href="https://directory.zooid.dev/api/discover">Browse Servers</a> ·
|
|
6
5
|
<a href="#quickstart">Quickstart</a> ·
|
|
6
|
+
<a href="https://zooid.dev/docs">Docs</a> ·
|
|
7
|
+
<a href="https://directory.zooid.dev/api/discover">Browse Servers</a> ·
|
|
7
8
|
<a href="#why-zooid">Why Zooid</a> ·
|
|
8
9
|
<a href="https://dsc.gg/zooid">Discord</a>
|
|
9
10
|
</p>
|
|
@@ -120,6 +121,8 @@ That's the whole flow. You publish on your server, others subscribe from theirs.
|
|
|
120
121
|
|
|
121
122
|
A Zooid server is just a URL — send it anywhere (email, Discord, Twitter), and anyone can subscribe directly.
|
|
122
123
|
|
|
124
|
+
For the full reference — channels, webhooks, SDK, CLI flags — see the [docs](https://zooid.dev/docs).
|
|
125
|
+
|
|
123
126
|
---
|
|
124
127
|
|
|
125
128
|
## Why Zooid?
|
|
@@ -17,6 +17,13 @@ function createClient(token) {
|
|
|
17
17
|
}
|
|
18
18
|
return new ZooidClient({ server, token: token ?? config.admin_token });
|
|
19
19
|
}
|
|
20
|
+
function getChannelToken(channelTokens, tokenType) {
|
|
21
|
+
if (!channelTokens) return void 0;
|
|
22
|
+
if (channelTokens.token) return channelTokens.token;
|
|
23
|
+
if (tokenType === "publish") return channelTokens.publish_token;
|
|
24
|
+
if (tokenType === "subscribe") return channelTokens.subscribe_token;
|
|
25
|
+
return channelTokens.publish_token ?? channelTokens.subscribe_token;
|
|
26
|
+
}
|
|
20
27
|
function createChannelClient(channelId, tokenType) {
|
|
21
28
|
const config = loadConfig();
|
|
22
29
|
const server = config.server;
|
|
@@ -25,8 +32,7 @@ function createChannelClient(channelId, tokenType) {
|
|
|
25
32
|
"No server configured. Run: npx zooid config set server <url>"
|
|
26
33
|
);
|
|
27
34
|
}
|
|
28
|
-
const
|
|
29
|
-
const channelToken = config.channels?.[channelId]?.[tokenKey];
|
|
35
|
+
const channelToken = getChannelToken(config.channels?.[channelId], tokenType);
|
|
30
36
|
return new ZooidClient({ server, token: channelToken ?? config.admin_token });
|
|
31
37
|
}
|
|
32
38
|
var createPublishClient = (channelId) => createChannelClient(channelId, "publish");
|
|
@@ -75,9 +81,8 @@ function resolveChannel(channel, opts) {
|
|
|
75
81
|
const { server: server2, channelId } = parsed;
|
|
76
82
|
let token2 = opts?.token;
|
|
77
83
|
let tokenSaved2 = false;
|
|
78
|
-
if (token2
|
|
79
|
-
|
|
80
|
-
saveConfig({ channels: { [channelId]: { [tokenKey]: token2 } } }, server2, {
|
|
84
|
+
if (token2) {
|
|
85
|
+
saveConfig({ channels: { [channelId]: { token: token2 } } }, server2, {
|
|
81
86
|
setCurrent: false
|
|
82
87
|
});
|
|
83
88
|
tokenSaved2 = true;
|
|
@@ -85,11 +90,7 @@ function resolveChannel(channel, opts) {
|
|
|
85
90
|
if (!token2) {
|
|
86
91
|
const file = loadConfigFile();
|
|
87
92
|
const channelTokens = file.servers?.[server2]?.channels?.[channelId];
|
|
88
|
-
|
|
89
|
-
token2 = channelTokens?.publish_token;
|
|
90
|
-
} else {
|
|
91
|
-
token2 = channelTokens?.subscribe_token;
|
|
92
|
-
}
|
|
93
|
+
token2 = getChannelToken(channelTokens, opts?.tokenType);
|
|
93
94
|
}
|
|
94
95
|
return {
|
|
95
96
|
client: new ZooidClient({ server: server2, token: token2 }),
|
|
@@ -107,20 +108,16 @@ function resolveChannel(channel, opts) {
|
|
|
107
108
|
}
|
|
108
109
|
let token = opts?.token;
|
|
109
110
|
let tokenSaved = false;
|
|
110
|
-
if (token
|
|
111
|
-
|
|
112
|
-
saveConfig({ channels: { [channel]: { [tokenKey]: token } } });
|
|
111
|
+
if (token) {
|
|
112
|
+
saveConfig({ channels: { [channel]: { token } } });
|
|
113
113
|
tokenSaved = true;
|
|
114
114
|
}
|
|
115
115
|
if (!token) {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
} else {
|
|
122
|
-
token = config.admin_token;
|
|
123
|
-
}
|
|
116
|
+
const channelToken = getChannelToken(
|
|
117
|
+
config.channels?.[channel],
|
|
118
|
+
opts?.tokenType
|
|
119
|
+
);
|
|
120
|
+
token = channelToken ?? config.admin_token;
|
|
124
121
|
}
|
|
125
122
|
return {
|
|
126
123
|
client: new ZooidClient({ server, token }),
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createSubscribeClient,
|
|
6
6
|
normalizeServerUrl,
|
|
7
7
|
resolveChannel
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VBGU2NST.js";
|
|
9
9
|
import {
|
|
10
10
|
getConfigDir,
|
|
11
11
|
getStatePath,
|
|
@@ -216,10 +216,7 @@ async function runChannelCreate(id, options, client) {
|
|
|
216
216
|
if (!client) {
|
|
217
217
|
const config = loadConfig();
|
|
218
218
|
const channels = config.channels ?? {};
|
|
219
|
-
channels[id] = {
|
|
220
|
-
publish_token: result.publish_token,
|
|
221
|
-
subscribe_token: result.subscribe_token
|
|
222
|
-
};
|
|
219
|
+
channels[id] = { token: result.token };
|
|
223
220
|
saveConfig({ channels });
|
|
224
221
|
}
|
|
225
222
|
return result;
|
|
@@ -693,10 +690,9 @@ async function runServerSet(fields, client) {
|
|
|
693
690
|
}
|
|
694
691
|
|
|
695
692
|
// src/commands/token.ts
|
|
696
|
-
async function runTokenMint(
|
|
693
|
+
async function runTokenMint(scopes, options) {
|
|
697
694
|
const client = createClient();
|
|
698
|
-
const body = {
|
|
699
|
-
if (options.channels?.length) body.channels = options.channels;
|
|
695
|
+
const body = { scopes };
|
|
700
696
|
if (options.sub) body.sub = options.sub;
|
|
701
697
|
if (options.name) body.name = options.name;
|
|
702
698
|
if (options.expiresIn) body.expires_in = options.expiresIn;
|
|
@@ -1245,29 +1241,21 @@ async function runDeploy() {
|
|
|
1245
1241
|
);
|
|
1246
1242
|
printSuccess("Schema up to date");
|
|
1247
1243
|
}
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1244
|
+
const migrationsDir = path4.join(stagingDir, "src/db/migrations");
|
|
1245
|
+
if (fs5.existsSync(migrationsDir)) {
|
|
1246
|
+
const migrationFiles = fs5.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort();
|
|
1247
|
+
for (const file of migrationFiles) {
|
|
1248
|
+
const migrationPath = path4.join(migrationsDir, file);
|
|
1249
|
+
try {
|
|
1250
|
+
wrangler(
|
|
1251
|
+
`d1 execute ${dbName} --remote --file=${migrationPath}`,
|
|
1252
|
+
stagingDir,
|
|
1253
|
+
creds
|
|
1254
|
+
);
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1260
1257
|
}
|
|
1261
1258
|
}
|
|
1262
|
-
try {
|
|
1263
|
-
const dataMigrationSql = `UPDATE channels SET config = json_object('types', (SELECT json_group_object(key, json_object('schema', json_each.value)) FROM json_each(schema))) WHERE schema IS NOT NULL AND config IS NULL`;
|
|
1264
|
-
wrangler(
|
|
1265
|
-
`d1 execute ${dbName} --remote --command="${dataMigrationSql}"`,
|
|
1266
|
-
stagingDir,
|
|
1267
|
-
creds
|
|
1268
|
-
);
|
|
1269
|
-
} catch {
|
|
1270
|
-
}
|
|
1271
1259
|
try {
|
|
1272
1260
|
const keysOutput = wrangler(
|
|
1273
1261
|
`d1 execute ${dbName} --remote --json --command="SELECT kid FROM trusted_keys WHERE issuer = 'local' LIMIT 1"`,
|
|
@@ -1438,13 +1426,13 @@ async function resolveAndRecord(channel, opts) {
|
|
|
1438
1426
|
return result;
|
|
1439
1427
|
}
|
|
1440
1428
|
var program = new Command();
|
|
1441
|
-
program.name("zooid").description("\u{1FAB8} Pub/sub for AI agents").version("0.2.
|
|
1429
|
+
program.name("zooid").description("\u{1FAB8} Pub/sub for AI agents").version("0.2.1");
|
|
1442
1430
|
var telemetryCtx = { startTime: 0 };
|
|
1443
1431
|
function setTelemetryChannel(channelId) {
|
|
1444
1432
|
telemetryCtx.channelId = channelId;
|
|
1445
1433
|
const config = loadConfig();
|
|
1446
1434
|
const channelTokens = config.channels?.[channelId];
|
|
1447
|
-
const hasChannelToken = !!(channelTokens?.publish_token || channelTokens?.subscribe_token);
|
|
1435
|
+
const hasChannelToken = !!(channelTokens?.token || channelTokens?.publish_token || channelTokens?.subscribe_token);
|
|
1448
1436
|
telemetryCtx.usedToken = hasChannelToken || !!config.admin_token;
|
|
1449
1437
|
}
|
|
1450
1438
|
function getCommandPath(cmd) {
|
|
@@ -1565,8 +1553,7 @@ channelCmd.command("create <id>").description("Create a new channel").option("--
|
|
|
1565
1553
|
config
|
|
1566
1554
|
});
|
|
1567
1555
|
printSuccess(`Created channel: ${id}`);
|
|
1568
|
-
printInfo("
|
|
1569
|
-
printInfo("Subscribe token", result.subscribe_token);
|
|
1556
|
+
printInfo("Token", result.token);
|
|
1570
1557
|
} catch (err) {
|
|
1571
1558
|
handleError("channel create", err);
|
|
1572
1559
|
}
|
|
@@ -1680,7 +1667,7 @@ program.command("tail <channel>").description("Fetch latest events, or stream li
|
|
|
1680
1667
|
let unseenSince;
|
|
1681
1668
|
if (opts.unseen) {
|
|
1682
1669
|
const file = loadConfigFile();
|
|
1683
|
-
const { parseChannelUrl } = await import("./client-
|
|
1670
|
+
const { parseChannelUrl } = await import("./client-4VMFEFDX.js");
|
|
1684
1671
|
const { resolveServer: resolveServer2 } = await import("./config-2KK5GX42.js");
|
|
1685
1672
|
const parsed = parseChannelUrl(channel);
|
|
1686
1673
|
const channelId2 = parsed?.channelId ?? channel;
|
|
@@ -1827,22 +1814,25 @@ serverCmd.command("set").description("Update server metadata").option("--name <n
|
|
|
1827
1814
|
handleError("server set", err);
|
|
1828
1815
|
}
|
|
1829
1816
|
});
|
|
1830
|
-
program.command("token
|
|
1817
|
+
program.command("token").description(
|
|
1818
|
+
"Mint a new token. Scopes: admin, pub:<channel>, sub:<channel>. Wildcards: pub:*, sub:prefix-*"
|
|
1819
|
+
).argument(
|
|
1820
|
+
"<scopes...>",
|
|
1821
|
+
"Scopes to grant (e.g. admin, pub:my-channel, sub:*)"
|
|
1822
|
+
).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 (scopes, opts) => {
|
|
1831
1823
|
try {
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
const result = await runTokenMint(
|
|
1838
|
-
scope,
|
|
1839
|
-
{
|
|
1840
|
-
channels: channels.length > 0 ? channels : void 0,
|
|
1841
|
-
sub: opts.sub,
|
|
1842
|
-
name: opts.name,
|
|
1843
|
-
expiresIn: opts.expiresIn
|
|
1824
|
+
for (const s of scopes) {
|
|
1825
|
+
if (s !== "admin" && !s.startsWith("pub:") && !s.startsWith("sub:")) {
|
|
1826
|
+
throw new Error(
|
|
1827
|
+
`Invalid scope "${s}". Must be "admin", "pub:<channel>", or "sub:<channel>"`
|
|
1828
|
+
);
|
|
1844
1829
|
}
|
|
1845
|
-
|
|
1830
|
+
}
|
|
1831
|
+
const result = await runTokenMint(scopes, {
|
|
1832
|
+
sub: opts.sub,
|
|
1833
|
+
name: opts.name,
|
|
1834
|
+
expiresIn: opts.expiresIn
|
|
1835
|
+
});
|
|
1846
1836
|
console.log(result.token);
|
|
1847
1837
|
} catch (err) {
|
|
1848
1838
|
handleError("token", err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zooid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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/
|
|
28
|
-
"@zooid/
|
|
29
|
-
"@zooid/server": "^0.
|
|
27
|
+
"@zooid/types": "^0.3.0",
|
|
28
|
+
"@zooid/sdk": "^0.3.0",
|
|
29
|
+
"@zooid/server": "^0.3.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@cloudflare/vitest-pool-workers": "^0.8.34",
|