zooid 0.0.1 → 0.0.3
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 +227 -68
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -37,7 +37,10 @@ function loadConfigFile() {
|
|
|
37
37
|
};
|
|
38
38
|
const dir = getConfigDir();
|
|
39
39
|
fs.mkdirSync(dir, { recursive: true });
|
|
40
|
-
fs.writeFileSync(
|
|
40
|
+
fs.writeFileSync(
|
|
41
|
+
getConfigPath(),
|
|
42
|
+
JSON.stringify(migrated, null, 2) + "\n"
|
|
43
|
+
);
|
|
41
44
|
return migrated;
|
|
42
45
|
}
|
|
43
46
|
return parsed;
|
|
@@ -59,7 +62,9 @@ function resolveServer() {
|
|
|
59
62
|
}
|
|
60
63
|
if (cwdUrl) {
|
|
61
64
|
if (file.current && file.current !== cwdUrl) {
|
|
62
|
-
console.log(
|
|
65
|
+
console.log(
|
|
66
|
+
` Note: using server from zooid.json (${cwdUrl}), current is ${file.current}`
|
|
67
|
+
);
|
|
63
68
|
}
|
|
64
69
|
return cwdUrl;
|
|
65
70
|
}
|
|
@@ -82,7 +87,9 @@ function saveConfig(partial, serverUrl) {
|
|
|
82
87
|
const file = loadConfigFile();
|
|
83
88
|
const url = serverUrl ?? resolveServer();
|
|
84
89
|
if (!url) {
|
|
85
|
-
throw new Error(
|
|
90
|
+
throw new Error(
|
|
91
|
+
"No server URL to save config for. Deploy first or set url in zooid.json."
|
|
92
|
+
);
|
|
86
93
|
}
|
|
87
94
|
if (!file.servers) file.servers = {};
|
|
88
95
|
const existing = file.servers[url] ?? {};
|
|
@@ -281,7 +288,9 @@ function runConfigSet(key, value) {
|
|
|
281
288
|
const enabled = value === "on" || value === "true" || value === "1";
|
|
282
289
|
writeTelemetryFlag(enabled);
|
|
283
290
|
} else {
|
|
284
|
-
throw new Error(
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Unknown config key: "${key}". Valid keys: ${VALID_KEYS.join(", ")}`
|
|
293
|
+
);
|
|
285
294
|
}
|
|
286
295
|
}
|
|
287
296
|
function runConfigGet(key) {
|
|
@@ -289,7 +298,9 @@ function runConfigGet(key) {
|
|
|
289
298
|
if (key === "server") return config.server;
|
|
290
299
|
if (key === "admin-token") return config.admin_token;
|
|
291
300
|
if (key === "telemetry") return isEnabled() ? "on" : "off";
|
|
292
|
-
throw new Error(
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Unknown config key: "${key}". Valid keys: ${VALID_KEYS.join(", ")}`
|
|
303
|
+
);
|
|
293
304
|
}
|
|
294
305
|
|
|
295
306
|
// src/lib/client.ts
|
|
@@ -484,7 +495,9 @@ async function deviceAuth() {
|
|
|
484
495
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
485
496
|
exec(`${cmd} "${verification_url}"`);
|
|
486
497
|
} catch {
|
|
487
|
-
console.log(
|
|
498
|
+
console.log(
|
|
499
|
+
" Could not open browser automatically. Please visit the URL above.\n"
|
|
500
|
+
);
|
|
488
501
|
}
|
|
489
502
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
490
503
|
while (Date.now() < deadline) {
|
|
@@ -503,7 +516,9 @@ async function deviceAuth() {
|
|
|
503
516
|
throw new Error("Device authorization expired. Please try again.");
|
|
504
517
|
}
|
|
505
518
|
}
|
|
506
|
-
throw new Error(
|
|
519
|
+
throw new Error(
|
|
520
|
+
"Authorization timed out. You need your human to authorize you. Run `npx zooid share` again to retry."
|
|
521
|
+
);
|
|
507
522
|
}
|
|
508
523
|
async function directoryFetch(path6, options = {}) {
|
|
509
524
|
let token = await ensureDirectoryToken();
|
|
@@ -531,7 +546,9 @@ async function runShare(channelIds, options = {}) {
|
|
|
531
546
|
const config = loadConfig();
|
|
532
547
|
const serverUrl = config.server;
|
|
533
548
|
if (!serverUrl) {
|
|
534
|
-
throw new Error(
|
|
549
|
+
throw new Error(
|
|
550
|
+
"No server configured. Run: npx zooid config set server <url>"
|
|
551
|
+
);
|
|
535
552
|
}
|
|
536
553
|
const allChannels = await client.listChannels();
|
|
537
554
|
const publicChannels = allChannels.filter((ch) => ch.is_public);
|
|
@@ -592,10 +609,19 @@ async function runShare(channelIds, options = {}) {
|
|
|
592
609
|
console.log("");
|
|
593
610
|
for (const ch of selected) {
|
|
594
611
|
const dirChannel = result.channels?.find((c) => c.channel_id === ch.id);
|
|
595
|
-
const url = dirChannel?.directory_url ?? `${
|
|
612
|
+
const url = dirChannel?.directory_url ?? `${serverUrl}/${ch.id}`;
|
|
596
613
|
console.log(` ${ch.id} \u2192 ${url}`);
|
|
597
614
|
}
|
|
598
615
|
console.log("");
|
|
616
|
+
console.log(` Any zooid can find your channel using:`);
|
|
617
|
+
console.log(` npx zooid discover --query ${selected[0].id}`);
|
|
618
|
+
const tags = [
|
|
619
|
+
...new Set(selected.flatMap((ch) => channelDetails.get(ch.id)?.tags ?? []))
|
|
620
|
+
];
|
|
621
|
+
if (tags.length > 0) {
|
|
622
|
+
console.log(` npx zooid discover --tag ${tags[0]}`);
|
|
623
|
+
}
|
|
624
|
+
console.log("");
|
|
599
625
|
}
|
|
600
626
|
async function pickChannels(channels) {
|
|
601
627
|
const { default: checkbox } = await import("@inquirer/checkbox");
|
|
@@ -636,7 +662,11 @@ async function promptChannelDetails(channels, skipPrompt) {
|
|
|
636
662
|
for (const ch of channels) {
|
|
637
663
|
console.log(` ${ch.id}:`);
|
|
638
664
|
const desc = await ask(rl, "Description", ch.description ?? "");
|
|
639
|
-
const tagsRaw = await ask(
|
|
665
|
+
const tagsRaw = await ask(
|
|
666
|
+
rl,
|
|
667
|
+
"Tags (comma-separated)",
|
|
668
|
+
ch.tags.join(", ")
|
|
669
|
+
);
|
|
640
670
|
const tags = tagsRaw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
641
671
|
result.set(ch.id, { description: desc, tags });
|
|
642
672
|
console.log("");
|
|
@@ -670,7 +700,9 @@ async function runUnshare(channelId) {
|
|
|
670
700
|
const config = loadConfig();
|
|
671
701
|
const serverUrl = config.server;
|
|
672
702
|
if (!serverUrl) {
|
|
673
|
-
throw new Error(
|
|
703
|
+
throw new Error(
|
|
704
|
+
"No server configured. Run: npx zooid config set server <url>"
|
|
705
|
+
);
|
|
674
706
|
}
|
|
675
707
|
const { claim, signature } = await client.getClaim([channelId], "delete");
|
|
676
708
|
const res = await directoryFetch("/api/servers/channels", {
|
|
@@ -752,11 +784,17 @@ function base64url(buf) {
|
|
|
752
784
|
return buf.toString("base64url");
|
|
753
785
|
}
|
|
754
786
|
async function createAdminToken(secret) {
|
|
755
|
-
const header = base64url(
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
787
|
+
const header = base64url(
|
|
788
|
+
Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" }))
|
|
789
|
+
);
|
|
790
|
+
const payload = base64url(
|
|
791
|
+
Buffer.from(
|
|
792
|
+
JSON.stringify({
|
|
793
|
+
scope: "admin",
|
|
794
|
+
iat: Math.floor(Date.now() / 1e3)
|
|
795
|
+
})
|
|
796
|
+
)
|
|
797
|
+
);
|
|
760
798
|
const data = `${header}.${payload}`;
|
|
761
799
|
const key = await crypto.subtle.importKey(
|
|
762
800
|
"raw",
|
|
@@ -765,7 +803,11 @@ async function createAdminToken(secret) {
|
|
|
765
803
|
false,
|
|
766
804
|
["sign"]
|
|
767
805
|
);
|
|
768
|
-
const sig = await crypto.subtle.sign(
|
|
806
|
+
const sig = await crypto.subtle.sign(
|
|
807
|
+
"HMAC",
|
|
808
|
+
key,
|
|
809
|
+
new TextEncoder().encode(data)
|
|
810
|
+
);
|
|
769
811
|
const signature = base64url(Buffer.from(sig));
|
|
770
812
|
return `${data}.${signature}`;
|
|
771
813
|
}
|
|
@@ -786,10 +828,13 @@ async function runDev(port = 8787) {
|
|
|
786
828
|
const schemaPath = path3.join(serverDir, "src/db/schema.sql");
|
|
787
829
|
if (fs4.existsSync(schemaPath)) {
|
|
788
830
|
try {
|
|
789
|
-
execSync(
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
831
|
+
execSync(
|
|
832
|
+
`npx wrangler d1 execute zooid-db --local --file=${schemaPath}`,
|
|
833
|
+
{
|
|
834
|
+
cwd: serverDir,
|
|
835
|
+
stdio: "pipe"
|
|
836
|
+
}
|
|
837
|
+
);
|
|
793
838
|
printSuccess("Database schema initialized");
|
|
794
839
|
} catch {
|
|
795
840
|
printSuccess("Database ready (schema already exists)");
|
|
@@ -802,11 +847,15 @@ async function runDev(port = 8787) {
|
|
|
802
847
|
console.log("");
|
|
803
848
|
console.log("Starting wrangler dev...");
|
|
804
849
|
console.log("");
|
|
805
|
-
const child = spawn(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
850
|
+
const child = spawn(
|
|
851
|
+
"npx",
|
|
852
|
+
["wrangler", "dev", "--local", "--port", String(port)],
|
|
853
|
+
{
|
|
854
|
+
cwd: serverDir,
|
|
855
|
+
stdio: "inherit",
|
|
856
|
+
shell: true
|
|
857
|
+
}
|
|
858
|
+
);
|
|
810
859
|
child.on("error", (err) => {
|
|
811
860
|
console.error(`Failed to start local server: ${err.message}`);
|
|
812
861
|
process.exit(1);
|
|
@@ -850,11 +899,19 @@ async function runInit() {
|
|
|
850
899
|
console.log(" Configure your Zooid server");
|
|
851
900
|
console.log(" Press Enter to accept defaults shown in [brackets].\n");
|
|
852
901
|
const name = await ask2(rl, "Server name", existing?.name ?? "");
|
|
853
|
-
const description = await ask2(
|
|
902
|
+
const description = await ask2(
|
|
903
|
+
rl,
|
|
904
|
+
"Description",
|
|
905
|
+
existing?.description ?? ""
|
|
906
|
+
);
|
|
854
907
|
const owner = await ask2(rl, "Owner", existing?.owner ?? "");
|
|
855
908
|
const company = await ask2(rl, "Company", existing?.company ?? "");
|
|
856
909
|
const email = await ask2(rl, "Email", existing?.email ?? "");
|
|
857
|
-
const tagsRaw = await ask2(
|
|
910
|
+
const tagsRaw = await ask2(
|
|
911
|
+
rl,
|
|
912
|
+
"Tags (comma-separated)",
|
|
913
|
+
existing?.tags?.join(", ") ?? ""
|
|
914
|
+
);
|
|
858
915
|
const url = await ask2(rl, "URL", existing?.url ?? "");
|
|
859
916
|
const tags = tagsRaw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
860
917
|
const config = {
|
|
@@ -875,7 +932,10 @@ async function runInit() {
|
|
|
875
932
|
printInfo("Owner", config.owner || "(not set)");
|
|
876
933
|
printInfo("Company", config.company || "(not set)");
|
|
877
934
|
printInfo("Email", config.email || "(not set)");
|
|
878
|
-
printInfo(
|
|
935
|
+
printInfo(
|
|
936
|
+
"Tags",
|
|
937
|
+
config.tags.length > 0 ? config.tags.join(", ") : "(none)"
|
|
938
|
+
);
|
|
879
939
|
printInfo("URL", config.url || "(not set)");
|
|
880
940
|
console.log("");
|
|
881
941
|
} finally {
|
|
@@ -918,12 +978,26 @@ function prepareStagingDir() {
|
|
|
918
978
|
let toml = fs6.readFileSync(path5.join(serverDir, "wrangler.toml"), "utf-8");
|
|
919
979
|
toml = toml.replace(/directory\s*=\s*"[^"]*"/, 'directory = "./web-dist/"');
|
|
920
980
|
fs6.writeFileSync(path5.join(tmpDir, "wrangler.toml"), toml);
|
|
921
|
-
const
|
|
922
|
-
if (
|
|
923
|
-
fs6.symlinkSync(
|
|
981
|
+
const nodeModules = findServerNodeModules(serverDir);
|
|
982
|
+
if (nodeModules) {
|
|
983
|
+
fs6.symlinkSync(nodeModules, path5.join(tmpDir, "node_modules"), "junction");
|
|
924
984
|
}
|
|
925
985
|
return tmpDir;
|
|
926
986
|
}
|
|
987
|
+
function findServerNodeModules(serverDir) {
|
|
988
|
+
const local = path5.join(serverDir, "node_modules");
|
|
989
|
+
if (fs6.existsSync(path5.join(local, "hono"))) return local;
|
|
990
|
+
const storeNodeModules = path5.resolve(serverDir, "..", "..");
|
|
991
|
+
if (fs6.existsSync(path5.join(storeNodeModules, "hono")))
|
|
992
|
+
return storeNodeModules;
|
|
993
|
+
let dir = serverDir;
|
|
994
|
+
while (dir !== path5.dirname(dir)) {
|
|
995
|
+
dir = path5.dirname(dir);
|
|
996
|
+
const nm = path5.join(dir, "node_modules");
|
|
997
|
+
if (fs6.existsSync(path5.join(nm, "hono"))) return nm;
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
927
1001
|
function copyDirSync(src, dest) {
|
|
928
1002
|
fs6.mkdirSync(dest, { recursive: true });
|
|
929
1003
|
for (const entry of fs6.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -940,11 +1014,17 @@ function base64url2(buf) {
|
|
|
940
1014
|
return buf.toString("base64url");
|
|
941
1015
|
}
|
|
942
1016
|
async function createAdminToken2(secret) {
|
|
943
|
-
const header = base64url2(
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1017
|
+
const header = base64url2(
|
|
1018
|
+
Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" }))
|
|
1019
|
+
);
|
|
1020
|
+
const payload = base64url2(
|
|
1021
|
+
Buffer.from(
|
|
1022
|
+
JSON.stringify({
|
|
1023
|
+
scope: "admin",
|
|
1024
|
+
iat: Math.floor(Date.now() / 1e3)
|
|
1025
|
+
})
|
|
1026
|
+
)
|
|
1027
|
+
);
|
|
948
1028
|
const data = `${header}.${payload}`;
|
|
949
1029
|
const key = await crypto2.subtle.importKey(
|
|
950
1030
|
"raw",
|
|
@@ -953,7 +1033,11 @@ async function createAdminToken2(secret) {
|
|
|
953
1033
|
false,
|
|
954
1034
|
["sign"]
|
|
955
1035
|
);
|
|
956
|
-
const sig = await crypto2.subtle.sign(
|
|
1036
|
+
const sig = await crypto2.subtle.sign(
|
|
1037
|
+
"HMAC",
|
|
1038
|
+
key,
|
|
1039
|
+
new TextEncoder().encode(data)
|
|
1040
|
+
);
|
|
957
1041
|
const signature = base64url2(Buffer.from(sig));
|
|
958
1042
|
return `${data}.${signature}`;
|
|
959
1043
|
}
|
|
@@ -975,7 +1059,9 @@ function wrangler(cmd, cwd, creds, opts) {
|
|
|
975
1059
|
}
|
|
976
1060
|
function parseDeployUrls(output) {
|
|
977
1061
|
const workersDev = output.match(/https:\/\/[^\s]+\.workers\.dev/);
|
|
978
|
-
const custom = output.match(
|
|
1062
|
+
const custom = output.match(
|
|
1063
|
+
/^\s+(\S+\.(?!workers\.dev)\S+)\s+\(custom domain\)/m
|
|
1064
|
+
);
|
|
979
1065
|
return {
|
|
980
1066
|
workerUrl: workersDev ? workersDev[0] : null,
|
|
981
1067
|
customDomain: custom ? `https://${custom[1]}` : null
|
|
@@ -1011,11 +1097,15 @@ async function getCfCredentials() {
|
|
|
1011
1097
|
console.log("");
|
|
1012
1098
|
console.log(" Cloudflare API token required for deployment.");
|
|
1013
1099
|
console.log(" Go to: https://dash.cloudflare.com/profile/api-tokens");
|
|
1014
|
-
console.log(
|
|
1100
|
+
console.log(
|
|
1101
|
+
' Use the "Edit Cloudflare Workers" template, then add D1 Edit permission.'
|
|
1102
|
+
);
|
|
1015
1103
|
console.log(" Tip: save credentials in .env to skip this prompt.");
|
|
1016
1104
|
console.log("");
|
|
1017
1105
|
const token = await rl.question(" API token: ");
|
|
1018
|
-
const accountId = await rl.question(
|
|
1106
|
+
const accountId = await rl.question(
|
|
1107
|
+
" Account ID (from the dashboard URL or Workers overview): "
|
|
1108
|
+
);
|
|
1019
1109
|
return {
|
|
1020
1110
|
apiToken: token.trim(),
|
|
1021
1111
|
accountId: accountId.trim() || void 0
|
|
@@ -1087,30 +1177,61 @@ async function runDeploy() {
|
|
|
1087
1177
|
printSuccess(`D1 database created (${databaseId})`);
|
|
1088
1178
|
const wranglerTomlPath = path5.join(stagingDir, "wrangler.toml");
|
|
1089
1179
|
let tomlContent = fs6.readFileSync(wranglerTomlPath, "utf-8");
|
|
1090
|
-
tomlContent = tomlContent.replace(
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1180
|
+
tomlContent = tomlContent.replace(
|
|
1181
|
+
/name = "[^"]*"/,
|
|
1182
|
+
`name = "${workerName}"`
|
|
1183
|
+
);
|
|
1184
|
+
tomlContent = tomlContent.replace(
|
|
1185
|
+
/database_name = "[^"]*"/,
|
|
1186
|
+
`database_name = "${dbName}"`
|
|
1187
|
+
);
|
|
1188
|
+
tomlContent = tomlContent.replace(
|
|
1189
|
+
/database_id = "[^"]*"/,
|
|
1190
|
+
`database_id = "${databaseId}"`
|
|
1191
|
+
);
|
|
1192
|
+
tomlContent = tomlContent.replace(
|
|
1193
|
+
/ZOOID_SERVER_ID = "[^"]*"/,
|
|
1194
|
+
`ZOOID_SERVER_ID = "${serverSlug}"`
|
|
1195
|
+
);
|
|
1094
1196
|
fs6.writeFileSync(wranglerTomlPath, tomlContent);
|
|
1095
1197
|
printSuccess("Configured wrangler.toml");
|
|
1096
1198
|
const schemaPath = path5.join(stagingDir, "src/db/schema.sql");
|
|
1097
1199
|
if (fs6.existsSync(schemaPath)) {
|
|
1098
1200
|
console.log("Running database schema migration...");
|
|
1099
|
-
wrangler(
|
|
1201
|
+
wrangler(
|
|
1202
|
+
`d1 execute ${dbName} --remote --file=${schemaPath}`,
|
|
1203
|
+
stagingDir,
|
|
1204
|
+
creds
|
|
1205
|
+
);
|
|
1100
1206
|
printSuccess("Database schema initialized");
|
|
1101
1207
|
}
|
|
1102
1208
|
console.log("Generating secrets...");
|
|
1103
1209
|
const jwtSecret = crypto2.randomBytes(32).toString("base64");
|
|
1104
|
-
const keyPair = await crypto2.subtle.generateKey("Ed25519", true, [
|
|
1105
|
-
|
|
1106
|
-
|
|
1210
|
+
const keyPair = await crypto2.subtle.generateKey("Ed25519", true, [
|
|
1211
|
+
"sign",
|
|
1212
|
+
"verify"
|
|
1213
|
+
]);
|
|
1214
|
+
const privateKeyRaw = await crypto2.subtle.exportKey(
|
|
1215
|
+
"pkcs8",
|
|
1216
|
+
keyPair.privateKey
|
|
1217
|
+
);
|
|
1218
|
+
const publicKeyRaw = await crypto2.subtle.exportKey(
|
|
1219
|
+
"raw",
|
|
1220
|
+
keyPair.publicKey
|
|
1221
|
+
);
|
|
1107
1222
|
const privateKeyB64 = Buffer.from(privateKeyRaw).toString("base64");
|
|
1108
1223
|
const publicKeyB64 = Buffer.from(publicKeyRaw).toString("base64");
|
|
1109
|
-
wrangler("secret put ZOOID_JWT_SECRET", stagingDir, creds, {
|
|
1224
|
+
wrangler("secret put ZOOID_JWT_SECRET", stagingDir, creds, {
|
|
1225
|
+
input: jwtSecret
|
|
1226
|
+
});
|
|
1110
1227
|
printSuccess("Set ZOOID_JWT_SECRET");
|
|
1111
|
-
wrangler("secret put ZOOID_SIGNING_KEY", stagingDir, creds, {
|
|
1228
|
+
wrangler("secret put ZOOID_SIGNING_KEY", stagingDir, creds, {
|
|
1229
|
+
input: privateKeyB64
|
|
1230
|
+
});
|
|
1112
1231
|
printSuccess("Set ZOOID_SIGNING_KEY (Ed25519 private)");
|
|
1113
|
-
wrangler("secret put ZOOID_PUBLIC_KEY", stagingDir, creds, {
|
|
1232
|
+
wrangler("secret put ZOOID_PUBLIC_KEY", stagingDir, creds, {
|
|
1233
|
+
input: publicKeyB64
|
|
1234
|
+
});
|
|
1114
1235
|
printSuccess("Set ZOOID_PUBLIC_KEY (Ed25519 public)");
|
|
1115
1236
|
adminToken = await createAdminToken2(jwtSecret);
|
|
1116
1237
|
printSuccess("Admin token generated");
|
|
@@ -1120,15 +1241,27 @@ async function runDeploy() {
|
|
|
1120
1241
|
console.log("");
|
|
1121
1242
|
const wranglerTomlPath = path5.join(stagingDir, "wrangler.toml");
|
|
1122
1243
|
let tomlContent = fs6.readFileSync(wranglerTomlPath, "utf-8");
|
|
1123
|
-
tomlContent = tomlContent.replace(
|
|
1124
|
-
|
|
1244
|
+
tomlContent = tomlContent.replace(
|
|
1245
|
+
/name = "[^"]*"/,
|
|
1246
|
+
`name = "${workerName}"`
|
|
1247
|
+
);
|
|
1248
|
+
tomlContent = tomlContent.replace(
|
|
1249
|
+
/ZOOID_SERVER_ID = "[^"]*"/,
|
|
1250
|
+
`ZOOID_SERVER_ID = "${serverSlug}"`
|
|
1251
|
+
);
|
|
1125
1252
|
try {
|
|
1126
1253
|
const output = wrangler("d1 list --json", stagingDir, creds);
|
|
1127
1254
|
const databases = JSON.parse(output);
|
|
1128
1255
|
const db = databases.find((d) => d.name === dbName);
|
|
1129
1256
|
if (db) {
|
|
1130
|
-
tomlContent = tomlContent.replace(
|
|
1131
|
-
|
|
1257
|
+
tomlContent = tomlContent.replace(
|
|
1258
|
+
/database_name = "[^"]*"/,
|
|
1259
|
+
`database_name = "${dbName}"`
|
|
1260
|
+
);
|
|
1261
|
+
tomlContent = tomlContent.replace(
|
|
1262
|
+
/database_id = "[^"]*"/,
|
|
1263
|
+
`database_id = "${db.uuid}"`
|
|
1264
|
+
);
|
|
1132
1265
|
}
|
|
1133
1266
|
} catch {
|
|
1134
1267
|
}
|
|
@@ -1136,8 +1269,12 @@ async function runDeploy() {
|
|
|
1136
1269
|
const existingConfig = loadConfig();
|
|
1137
1270
|
adminToken = existingConfig.admin_token;
|
|
1138
1271
|
if (!adminToken) {
|
|
1139
|
-
printError(
|
|
1140
|
-
|
|
1272
|
+
printError(
|
|
1273
|
+
"No admin token found in ~/.zooid/config.json for this server"
|
|
1274
|
+
);
|
|
1275
|
+
console.log(
|
|
1276
|
+
"If this is a first deploy, remove the D1 database and try again."
|
|
1277
|
+
);
|
|
1141
1278
|
cleanup(stagingDir);
|
|
1142
1279
|
process.exit(1);
|
|
1143
1280
|
}
|
|
@@ -1156,7 +1293,10 @@ async function runDeploy() {
|
|
|
1156
1293
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
1157
1294
|
if (canonicalUrl && adminToken) {
|
|
1158
1295
|
try {
|
|
1159
|
-
const client = new ZooidClient2({
|
|
1296
|
+
const client = new ZooidClient2({
|
|
1297
|
+
server: canonicalUrl,
|
|
1298
|
+
token: adminToken
|
|
1299
|
+
});
|
|
1160
1300
|
await client.updateServerMeta({
|
|
1161
1301
|
name: config.name || void 0,
|
|
1162
1302
|
description: config.description || void 0,
|
|
@@ -1167,7 +1307,9 @@ async function runDeploy() {
|
|
|
1167
1307
|
});
|
|
1168
1308
|
printSuccess("Server identity updated");
|
|
1169
1309
|
} catch (err) {
|
|
1170
|
-
printError(
|
|
1310
|
+
printError(
|
|
1311
|
+
`Failed to push server identity: ${err instanceof Error ? err.message : err}`
|
|
1312
|
+
);
|
|
1171
1313
|
}
|
|
1172
1314
|
}
|
|
1173
1315
|
if (!config.url && (customDomain || workerUrl)) {
|
|
@@ -1202,7 +1344,9 @@ async function runDeploy() {
|
|
|
1202
1344
|
if (isFirstDeploy) {
|
|
1203
1345
|
console.log(" Next steps:");
|
|
1204
1346
|
console.log(" npx zooid channel create my-channel");
|
|
1205
|
-
console.log(
|
|
1347
|
+
console.log(
|
|
1348
|
+
` npx zooid publish my-channel --data='{"hello": "world"}'`
|
|
1349
|
+
);
|
|
1206
1350
|
console.log("");
|
|
1207
1351
|
}
|
|
1208
1352
|
}
|
|
@@ -1340,11 +1484,15 @@ channelCmd.command("list").description("List all channels").action(async () => {
|
|
|
1340
1484
|
try {
|
|
1341
1485
|
const channels = await runChannelList();
|
|
1342
1486
|
if (channels.length === 0) {
|
|
1343
|
-
console.log(
|
|
1487
|
+
console.log(
|
|
1488
|
+
"No channels yet. Create one with: npx zooid channel create <name>"
|
|
1489
|
+
);
|
|
1344
1490
|
} else {
|
|
1345
1491
|
for (const ch of channels) {
|
|
1346
1492
|
const visibility = ch.is_public ? "public" : "private";
|
|
1347
|
-
console.log(
|
|
1493
|
+
console.log(
|
|
1494
|
+
` ${ch.id} \u2014 ${ch.name} (${visibility}, ${ch.event_count} events)`
|
|
1495
|
+
);
|
|
1348
1496
|
}
|
|
1349
1497
|
}
|
|
1350
1498
|
} catch (err) {
|
|
@@ -1373,13 +1521,19 @@ program.command("publish <channel>").description("Publish an event to a channel"
|
|
|
1373
1521
|
process.exit(1);
|
|
1374
1522
|
}
|
|
1375
1523
|
});
|
|
1376
|
-
program.command("tail <channel>").description("Fetch latest events, or stream live with -f").option("-n, --limit <n>", "Max events to return", "50").option("-f, --follow", "Follow mode \u2014 stream new events as they arrive").option("--type <type>", "Filter events by type").option("--since <iso>", "Only events after this ISO 8601 timestamp").option("--cursor <cursor>", "Resume from a previous cursor").option(
|
|
1524
|
+
program.command("tail <channel>").description("Fetch latest events, or stream live with -f").option("-n, --limit <n>", "Max events to return", "50").option("-f, --follow", "Follow mode \u2014 stream new events as they arrive").option("--type <type>", "Filter events by type").option("--since <iso>", "Only events after this ISO 8601 timestamp").option("--cursor <cursor>", "Resume from a previous cursor").option(
|
|
1525
|
+
"--mode <mode>",
|
|
1526
|
+
"Transport mode for follow: auto, ws, or poll",
|
|
1527
|
+
"auto"
|
|
1528
|
+
).option("--interval <ms>", "Poll interval in ms for follow mode", "5000").action(async (channel, opts) => {
|
|
1377
1529
|
setTelemetryChannel(channel);
|
|
1378
1530
|
try {
|
|
1379
1531
|
if (opts.follow) {
|
|
1380
1532
|
const mode = opts.mode;
|
|
1381
1533
|
const transport = mode === "auto" ? "auto (WebSocket \u2192 poll fallback)" : mode;
|
|
1382
|
-
console.log(
|
|
1534
|
+
console.log(
|
|
1535
|
+
`Tailing ${channel} [${transport}]${opts.type ? ` type=${opts.type}` : ""}...`
|
|
1536
|
+
);
|
|
1383
1537
|
console.log("Press Ctrl+C to stop.\n");
|
|
1384
1538
|
await runTail(channel, {
|
|
1385
1539
|
follow: true,
|
|
@@ -1421,7 +1575,9 @@ program.command("subscribe <channel>").description("Subscribe to a channel").opt
|
|
|
1421
1575
|
} else {
|
|
1422
1576
|
const mode = opts.mode;
|
|
1423
1577
|
const transport = mode === "auto" ? "auto (WebSocket \u2192 poll fallback)" : mode;
|
|
1424
|
-
console.log(
|
|
1578
|
+
console.log(
|
|
1579
|
+
`Subscribing to ${channel} [${transport}]${opts.type ? ` type=${opts.type}` : ""}...`
|
|
1580
|
+
);
|
|
1425
1581
|
console.log("Press Ctrl+C to stop.\n");
|
|
1426
1582
|
await runSubscribePoll(channel, {
|
|
1427
1583
|
interval: parseInt(opts.interval, 10),
|
|
@@ -1460,12 +1616,15 @@ serverCmd.command("set").description("Update server metadata").option("--name <n
|
|
|
1460
1616
|
const fields = {};
|
|
1461
1617
|
if (opts.name !== void 0) fields.name = opts.name;
|
|
1462
1618
|
if (opts.description !== void 0) fields.description = opts.description;
|
|
1463
|
-
if (opts.tags !== void 0)
|
|
1619
|
+
if (opts.tags !== void 0)
|
|
1620
|
+
fields.tags = opts.tags.split(",").map((t) => t.trim());
|
|
1464
1621
|
if (opts.owner !== void 0) fields.owner = opts.owner;
|
|
1465
1622
|
if (opts.company !== void 0) fields.company = opts.company;
|
|
1466
1623
|
if (opts.email !== void 0) fields.email = opts.email;
|
|
1467
1624
|
if (Object.keys(fields).length === 0) {
|
|
1468
|
-
printError(
|
|
1625
|
+
printError(
|
|
1626
|
+
"No fields specified. Use --name, --description, --tags, --owner, --company, or --email."
|
|
1627
|
+
);
|
|
1469
1628
|
process.exit(1);
|
|
1470
1629
|
}
|
|
1471
1630
|
const meta = await runServerSet(fields);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zooid",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ori Ben",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"@inquirer/checkbox": "^5.0.7",
|
|
20
20
|
"commander": "^14.0.3",
|
|
21
21
|
"@zooid/sdk": "0.0.1",
|
|
22
|
-
"@zooid/
|
|
23
|
-
"@zooid/
|
|
22
|
+
"@zooid/server": "0.0.1",
|
|
23
|
+
"@zooid/types": "0.0.1"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@cloudflare/vitest-pool-workers": "^0.8.34",
|