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 tokenKey = tokenType === "publish" ? "publish_token" : "subscribe_token";
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 && opts?.tokenType) {
79
- const tokenKey = opts.tokenType === "publish" ? "publish_token" : "subscribe_token";
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
- if (opts?.tokenType === "publish") {
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 && opts?.tokenType) {
111
- const tokenKey = opts.tokenType === "publish" ? "publish_token" : "subscribe_token";
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 channelTokens = config.channels?.[channel];
117
- if (opts?.tokenType === "publish") {
118
- token = channelTokens?.publish_token ?? config.admin_token;
119
- } else if (opts?.tokenType === "subscribe") {
120
- token = channelTokens?.subscribe_token ?? config.admin_token;
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 }),
@@ -8,7 +8,7 @@ import {
8
8
  normalizeServerUrl,
9
9
  parseChannelUrl,
10
10
  resolveChannel
11
- } from "./chunk-EEA3FCBS.js";
11
+ } from "./chunk-VBGU2NST.js";
12
12
  import "./chunk-67ZRMVHO.js";
13
13
  export {
14
14
  PRIVATE_HOST_RE,
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  createSubscribeClient,
6
6
  normalizeServerUrl,
7
7
  resolveChannel
8
- } from "./chunk-EEA3FCBS.js";
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(scope, options) {
693
+ async function runTokenMint(scopes, options) {
697
694
  const client = createClient();
698
- const body = { scope };
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 migrations = [
1249
- "ALTER TABLE events ADD COLUMN publisher_name TEXT",
1250
- "ALTER TABLE channels ADD COLUMN config TEXT"
1251
- ];
1252
- for (const sql of migrations) {
1253
- try {
1254
- wrangler(
1255
- `d1 execute ${dbName} --remote --command="${sql}"`,
1256
- stagingDir,
1257
- creds
1258
- );
1259
- } catch {
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.0");
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("Publish token", result.publish_token);
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-QPT54SNG.js");
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 <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) => {
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
- if (!["admin", "publish", "subscribe"].includes(scope)) {
1833
- throw new Error(
1834
- `Invalid scope "${scope}". Must be one of: admin, publish, subscribe`
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.2.1",
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/sdk": "^0.2.0",
28
- "@zooid/types": "^0.2.0",
29
- "@zooid/server": "^0.2.1"
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",