@zoralabs/cli 1.1.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zoralabs/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Zora CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,16 +17,22 @@
17
17
  "@inkjs/ui": "^2.0.0",
18
18
  "@inquirer/confirm": "^6.0.8",
19
19
  "@inquirer/core": "^11.1.5",
20
+ "@inquirer/input": "^5.1.2",
20
21
  "@inquirer/password": "^5.0.8",
21
22
  "@inquirer/select": "^5.1.0",
23
+ "@resvg/resvg-wasm": "^2.6.2",
24
+ "@xmtp/content-type-reaction": "^2.0.2",
25
+ "@xmtp/content-type-read-receipt": "^2.0.2",
26
+ "@xmtp/node-sdk": "^6.0.0",
22
27
  "commander": "^13.1.0",
23
28
  "date-fns": "^4.1.0",
24
29
  "ink": "^6.8.0",
25
30
  "ink-spinner": "^5.0.0",
26
31
  "posthog-node": "^5.28.4",
27
32
  "react": "^19.2.4",
33
+ "satori": "^0.26.0",
28
34
  "viem": "2.22.12",
29
- "@zoralabs/coins-sdk": "0.6.0"
35
+ "@zoralabs/coins-sdk": "0.7.0"
30
36
  },
31
37
  "devDependencies": {
32
38
  "@types/node": "^22.13.0",
@@ -44,16 +50,21 @@
44
50
  },
45
51
  "scripts": {
46
52
  "postinstall": "node scripts/postinstall.mjs",
47
- "build": "tsup",
48
- "build:js": "tsup",
53
+ "build": "pnpm run patch:xmtp-binding && tsup",
54
+ "build:js": "pnpm run patch:xmtp-binding && tsup",
55
+ "check": "tsc --noEmit",
49
56
  "zora": "tsx src/index.tsx",
50
57
  "test": "vitest run",
51
58
  "test:js": "pnpm run test",
52
59
  "test:coverage": "vitest run --coverage",
53
- "build:binary": "V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora",
60
+ "patch:xmtp-binding": "node scripts/patch-xmtp-binding.mjs",
61
+ "gen:card-fonts": "python3 scripts/gen-card-fonts.py",
62
+ "gen:card-wasm": "node scripts/gen-card-wasm.mjs",
63
+ "gen:card-assets": "pnpm run gen:card-fonts && pnpm run gen:card-wasm",
64
+ "build:binary": "pnpm run patch:xmtp-binding && V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora",
54
65
  "build:binary:all": "pnpm run build:binary:mac-arm64 && pnpm run build:binary:mac-x64 && pnpm run build:binary:linux-x64 && pnpm run build:binary:linux-arm64 && pnpm run build:binary:windows-x64",
55
- "build:binary:mac-arm64": "V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-darwin-arm64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-darwin-arm64",
56
- "build:binary:mac-x64": "V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-darwin-x64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-darwin-x64",
66
+ "build:binary:mac-arm64": "pnpm run patch:xmtp-binding && V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-darwin-arm64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-darwin-arm64",
67
+ "build:binary:mac-x64": "pnpm run patch:xmtp-binding && V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-darwin-x64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-darwin-x64",
57
68
  "build:binary:linux-x64": "V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-linux-x64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-linux-x64",
58
69
  "build:binary:linux-arm64": "V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-linux-arm64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-linux-arm64",
59
70
  "build:binary:windows-x64": "V=$(node -p \"require('./package.json').version\") && bun build ./src/index.tsx --compile --target=bun-windows-x64 --define \"PKG_VERSION=\\\"$V\\\"\" --outfile ./bin/zora-windows-x64.exe"
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+ """Generate the bundled, base64-embedded card fonts for the agent first post.
3
+
4
+ Instantiates static Regular (400) and Medium (500) weights from the ABC Monument
5
+ Grotesk *variable* font and writes them, base64-encoded, into a generated
6
+ TypeScript module. The renderer (Satori) needs static TTF/OTF — it can't read
7
+ woff2 and is unreliable with variable fonts — so we pin the two weights the
8
+ brand template uses and embed them inline (same approach as the greeting cards,
9
+ so the compiled bun binary and the published Node CLI both ship the bytes with
10
+ no external asset files).
11
+
12
+ Run from the package root: python3 scripts/gen-card-fonts.py
13
+ Source font: cli-docs/docs/public/fonts/ABCMonumentGroteskVariableVF.woff
14
+ """
15
+
16
+ import base64
17
+ import io
18
+ import os
19
+
20
+ from fontTools import ttLib
21
+ from fontTools.varLib.instancer import instantiateVariableFont
22
+
23
+ HERE = os.path.dirname(os.path.abspath(__file__))
24
+ PKG_ROOT = os.path.dirname(HERE)
25
+ REPO_ROOT = os.path.dirname(os.path.dirname(PKG_ROOT))
26
+ SRC_FONT = os.path.join(
27
+ REPO_ROOT, "cli-docs", "docs", "public", "fonts", "ABCMonumentGroteskVariableVF.woff"
28
+ )
29
+ OUT_TS = os.path.join(PKG_ROOT, "src", "lib", "agent", "card-fonts.ts")
30
+
31
+ # The two weights the brand meme template uses: caption (Medium) + handle (Regular).
32
+ WEIGHTS = {"regular": 400, "medium": 500}
33
+
34
+
35
+ def instantiate_ttf_base64(weight: int) -> str:
36
+ font = ttLib.TTFont(SRC_FONT)
37
+ # Pin the variable axes to a single static instance (upright, target weight).
38
+ instantiateVariableFont(font, {"wght": weight, "slnt": 0}, inplace=True)
39
+ font.flavor = None # emit a bare TTF (drop woff compression)
40
+ buf = io.BytesIO()
41
+ font.save(buf)
42
+ return base64.b64encode(buf.getvalue()).decode("ascii")
43
+
44
+
45
+ def main() -> None:
46
+ fonts = {name: instantiate_ttf_base64(wght) for name, wght in WEIGHTS.items()}
47
+
48
+ lines = [
49
+ "// AUTO-GENERATED by scripts/gen-card-fonts.py — do not edit by hand.",
50
+ "// Static Regular (400) + Medium (500) instances of ABC Monument Grotesk,",
51
+ "// instantiated from the variable font and inlined as base64 TTF so the",
52
+ "// card renderer ships the bytes in both the bun binary and the Node CLI.",
53
+ "/* eslint-disable */",
54
+ "",
55
+ "import { Buffer } from \"node:buffer\";",
56
+ "",
57
+ ]
58
+ for name in WEIGHTS:
59
+ lines.append(f'const {name}Base64 =')
60
+ lines.append(f' "{fonts[name]}";')
61
+ lines.append("")
62
+ lines.append("/** ABC Monument Grotesk Regular (400) as raw TTF bytes. */")
63
+ lines.append('export const MONUMENT_REGULAR = Buffer.from(regularBase64, "base64");')
64
+ lines.append("/** ABC Monument Grotesk Medium (500) as raw TTF bytes. */")
65
+ lines.append('export const MONUMENT_MEDIUM = Buffer.from(mediumBase64, "base64");')
66
+ lines.append("")
67
+
68
+ with open(OUT_TS, "w") as f:
69
+ f.write("\n".join(lines))
70
+
71
+ sizes = ", ".join(f"{n}={len(b) * 3 // 4 // 1024}KB" for n, b in fonts.items())
72
+ print(f"Wrote {OUT_TS} ({sizes})")
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // Generate the bundled, base64-embedded WASM blobs the card renderer needs:
3
+ // - Satori's Yoga layout engine (satori/yoga.wasm)
4
+ // - resvg's SVG rasterizer (@resvg/resvg-wasm/index_bg.wasm)
5
+ //
6
+ // We inline them as base64 (decoded at runtime and handed to satori's `init()`
7
+ // and resvg's `initWasm()`) rather than loading the .wasm files from disk. The
8
+ // default `satori` entry resolves yoga.wasm relative to import.meta.url, which
9
+ // works under Node but not inside a `bun build --compile` single-file binary;
10
+ // embedding the bytes makes the renderer work identically in the published Node
11
+ // CLI and the compiled binary. Same rationale as the inlined fonts/cards.
12
+ //
13
+ // Run from the package root: node scripts/gen-card-wasm.mjs
14
+
15
+ import { readFileSync, writeFileSync } from "node:fs";
16
+ import { createRequire } from "node:module";
17
+ import { dirname, join } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ const require = createRequire(import.meta.url);
21
+ const here = dirname(fileURLToPath(import.meta.url));
22
+ const outPath = join(here, "..", "src", "lib", "agent", "card-wasm.ts");
23
+
24
+ // Resolve the .wasm files via the package entry points, then sit them next to
25
+ // the resolved module so this keeps working regardless of the hoist layout.
26
+ const yogaWasm = readFileSync(
27
+ join(dirname(require.resolve("satori/package.json")), "yoga.wasm"),
28
+ );
29
+ const resvgWasm = readFileSync(
30
+ require.resolve("@resvg/resvg-wasm/index_bg.wasm"),
31
+ );
32
+
33
+ const ts = `// AUTO-GENERATED by scripts/gen-card-wasm.mjs — do not edit by hand.
34
+ // Inlined WASM for the first-post card renderer: Satori's Yoga layout engine and
35
+ // resvg's SVG rasterizer, base64-encoded so they ship in both the bun binary and
36
+ // the published Node CLI without external asset files.
37
+ /* eslint-disable */
38
+
39
+ import { Buffer } from "node:buffer";
40
+
41
+ const yogaBase64 =
42
+ "${yogaWasm.toString("base64")}";
43
+ const resvgBase64 =
44
+ "${resvgWasm.toString("base64")}";
45
+
46
+ /** Satori's Yoga layout engine (yoga.wasm) as raw bytes. */
47
+ export const YOGA_WASM = Buffer.from(yogaBase64, "base64");
48
+ /** resvg's SVG-to-PNG rasterizer (index_bg.wasm) as raw bytes. */
49
+ export const RESVG_WASM = Buffer.from(resvgBase64, "base64");
50
+ `;
51
+
52
+ writeFileSync(outPath, ts);
53
+ console.log(
54
+ `Wrote ${outPath} (yoga=${Math.round(yogaWasm.length / 1024)}KB, resvg=${Math.round(
55
+ resvgWasm.length / 1024,
56
+ )}KB)`,
57
+ );
@@ -0,0 +1,118 @@
1
+ // Work around an upstream packaging bug in @xmtp/node-bindings (present in 1.10.0,
2
+ // the version pulled in by @xmtp/node-sdk@6): the prebuilt macOS .node files link
3
+ // libiconv from a Nix store path (e.g. /nix/store/<hash>-libiconv-*/lib/libiconv.2.dylib)
4
+ // instead of the system one. On a stock Mac dyld falls back to /usr/lib and it
5
+ // loads, but environments that override DYLD_FALLBACK_LIBRARY_PATH (Nix shells,
6
+ // some sandboxes) can't fall back, so `Client.create` fails with a dlopen error.
7
+ //
8
+ // This repoints that load command to /usr/lib/libiconv.2.dylib in the installed
9
+ // .node so the binding loads on any Mac. It runs from `build`/`build:js` (fixes
10
+ // the node_modules install for source/dev runs), `postinstall` (npm i -g), and
11
+ // before `bun build --compile` for the `build:binary:mac-*` targets.
12
+ //
13
+ // Safe by design — exits 0 (no-op) when:
14
+ // • not running on macOS (the only place install_name_tool/codesign exist),
15
+ // • @xmtp/node-bindings isn't installed,
16
+ // • no macOS .node files are present,
17
+ // • the binding is already correctly linked (e.g. after XMTP ships a fix).
18
+ // Because it keys off the build *host* (process.platform), the macOS targets must
19
+ // be built on a macOS runner for the fix to apply (which signed releases need anyway).
20
+
21
+ import { execFileSync } from "node:child_process";
22
+ import { createRequire } from "node:module";
23
+ import { dirname, join } from "node:path";
24
+ import { existsSync, copyFileSync, renameSync } from "node:fs";
25
+
26
+ const SYSTEM_LIBICONV = "/usr/lib/libiconv.2.dylib";
27
+ const log = (msg) => console.log(`[patch-xmtp-binding] ${msg}`);
28
+
29
+ if (process.platform !== "darwin") {
30
+ log(`skipping on ${process.platform} — macOS-only fix`);
31
+ process.exit(0);
32
+ }
33
+
34
+ let distDir;
35
+ try {
36
+ // @xmtp/node-bindings is a transitive dep (via @xmtp/node-sdk), so under pnpm's
37
+ // strict node_modules it can't be resolved directly from here. Resolve node-sdk
38
+ // (a direct dep) first, then node-bindings from there. Its `exports` only expose
39
+ // ".", so resolve the entry (./dist/index.js) and take its directory — that's
40
+ // where the platform .node files live.
41
+ const require = createRequire(import.meta.url);
42
+ const sdkRequire = createRequire(require.resolve("@xmtp/node-sdk"));
43
+ distDir = dirname(sdkRequire.resolve("@xmtp/node-bindings"));
44
+ } catch (err) {
45
+ log(`@xmtp/node-bindings not found (${err.message}) — nothing to patch`);
46
+ process.exit(0);
47
+ }
48
+
49
+ const targets = [
50
+ "bindings_node.darwin-arm64.node",
51
+ "bindings_node.darwin-x64.node",
52
+ ]
53
+ .map((f) => join(distDir, f))
54
+ .filter(existsSync);
55
+
56
+ if (targets.length === 0) {
57
+ log("no macOS .node files present — nothing to patch");
58
+ process.exit(0);
59
+ }
60
+
61
+ // Resolve Apple's toolchain by absolute path so the patch still applies when run
62
+ // inside a Nix shell (or anything else that shadows these tools on PATH) — the
63
+ // case that left the binding broken for some contributors. `/usr/bin/{otool,
64
+ // install_name_tool,codesign}` are Apple stubs that dispatch through the active
65
+ // Xcode toolchain regardless of PATH. Falls back to a bare PATH lookup if the
66
+ // system copy isn't where we expect.
67
+ const systemTool = (name) =>
68
+ existsSync(`/usr/bin/${name}`) ? `/usr/bin/${name}` : name;
69
+ const OTOOL = systemTool("otool");
70
+ const INSTALL_NAME_TOOL = systemTool("install_name_tool");
71
+ const CODESIGN = systemTool("codesign");
72
+
73
+ const run = (cmd, args) => execFileSync(cmd, args, { encoding: "utf8" });
74
+
75
+ let patched = 0;
76
+ for (const file of targets) {
77
+ let deps;
78
+ try {
79
+ deps = run(OTOOL, ["-L", file]);
80
+ } catch (err) {
81
+ // Xcode command line tools unavailable — warn but don't fail the build.
82
+ log(
83
+ `otool unavailable (${err.message}); skipping — install Xcode CLT to apply the fix`,
84
+ );
85
+ process.exit(0);
86
+ }
87
+
88
+ // The load command for libiconv that isn't already the system path.
89
+ const bad = deps
90
+ .split("\n")
91
+ .map((line) => line.trim().split(/\s+/)[0])
92
+ .find((p) => /\/libiconv\.2\.dylib$/.test(p) && p !== SYSTEM_LIBICONV);
93
+
94
+ if (!bad) {
95
+ log(`already OK: ${file}`);
96
+ continue;
97
+ }
98
+
99
+ try {
100
+ log(`repointing libiconv: ${bad} -> ${SYSTEM_LIBICONV}`);
101
+ // Break the hard link into the pnpm store so we edit a private copy in
102
+ // node_modules, never the shared store object other projects link to.
103
+ const tmp = `${file}.patching`;
104
+ copyFileSync(file, tmp);
105
+ renameSync(tmp, file);
106
+ run(INSTALL_NAME_TOOL, ["-change", bad, SYSTEM_LIBICONV, file]);
107
+ // Editing load commands invalidates the signature; re-sign ad-hoc so the
108
+ // embedded binary still loads under macOS code-signing checks.
109
+ run(CODESIGN, ["-f", "-s", "-", file]);
110
+ patched += 1;
111
+ } catch (err) {
112
+ // Best-effort: never fail the caller (build / postinstall). On a stock Mac
113
+ // the dyld /usr/lib fallback still loads the binding without this patch.
114
+ log(`could not patch ${file} (${err.message}); leaving as-is`);
115
+ }
116
+ }
117
+
118
+ log(`done — ${patched} file(s) patched`);
@@ -2,6 +2,20 @@
2
2
 
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
+ import { execFileSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ // Best-effort: fix the XMTP macOS native binding's libiconv link so `zora`'s DM
9
+ // commands load without a DYLD_FALLBACK_LIBRARY_PATH workaround. No-op on
10
+ // non-macOS, missing Xcode toolchain, or when already correct. Never fails install.
11
+ try {
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ execFileSync(process.execPath, [path.join(here, "patch-xmtp-binding.mjs")], {
14
+ stdio: "inherit",
15
+ });
16
+ } catch {
17
+ // ignore — see patch-xmtp-binding.mjs
18
+ }
5
19
 
6
20
  try {
7
21
  if (process.env.npm_config_global !== "true") {