@zoralabs/cli 1.4.0 → 1.4.2

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.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Zora CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,6 +34,9 @@
34
34
  "viem": "2.22.12",
35
35
  "@zoralabs/coins-sdk": "0.7.0"
36
36
  },
37
+ "optionalDependencies": {
38
+ "@xmtp/node-sdk-lowglibc": "npm:@xmtp/node-sdk@6.1.0-nightly.20260617.1e1a195"
39
+ },
37
40
  "devDependencies": {
38
41
  "@types/node": "^22.13.0",
39
42
  "@types/react": "^19.2.14",
@@ -50,8 +53,10 @@
50
53
  },
51
54
  "scripts": {
52
55
  "postinstall": "node scripts/postinstall.mjs",
53
- "build": "pnpm run patch:xmtp-binding && tsup",
54
- "build:js": "pnpm run patch:xmtp-binding && tsup",
56
+ "generate:skills": "node scripts/generate-skill-content.mjs",
57
+ "generate:skills:check": "node scripts/generate-skill-content.mjs --check",
58
+ "build": "pnpm run generate:skills && pnpm run patch:xmtp-binding && tsup",
59
+ "build:js": "pnpm run generate:skills && pnpm run patch:xmtp-binding && tsup",
55
60
  "check": "tsc --noEmit",
56
61
  "zora": "tsx src/index.tsx",
57
62
  "test": "vitest run",
@@ -61,12 +66,12 @@
61
66
  "gen:card-fonts": "python3 scripts/gen-card-fonts.py",
62
67
  "gen:card-wasm": "node scripts/gen-card-wasm.mjs",
63
68
  "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",
69
+ "build:binary": "pnpm run generate:skills && 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",
65
70
  "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",
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",
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",
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",
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"
71
+ "build:binary:mac-arm64": "pnpm run generate:skills && 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",
72
+ "build:binary:mac-x64": "pnpm run generate:skills && 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",
73
+ "build:binary:linux-x64": "pnpm run generate:skills && 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",
74
+ "build:binary:linux-arm64": "pnpm run generate:skills && 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",
75
+ "build:binary:windows-x64": "pnpm run generate:skills && 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"
71
76
  }
72
77
  }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Embeds skill content into the CLI bundle.
3
+ *
4
+ * Skills are authored as Markdown in this package (the core skill at SKILL.md,
5
+ * strategy skills under skills/<name>/SKILL.md). The CLI installs them by writing
6
+ * the content to the agent's skills directory. Rather than fetch that content from
7
+ * a remote server at install time (unverifiable, and it drifts from the published
8
+ * CLI), we embed it into the bundle as string literals. This way the content is
9
+ * identical to the reviewed source at the commit the CLI was built from, ships
10
+ * inside both the npm tarball and the compiled standalone binary, and needs no
11
+ * network access or integrity check to install.
12
+ *
13
+ * The generated module is committed so dev (`tsx`), tests, and review all see
14
+ * exactly what ships. CI runs this with --check to fail if it is out of date.
15
+ *
16
+ * Usage:
17
+ * node scripts/generate-skill-content.mjs # write src/generated/skill-content.ts
18
+ * node scripts/generate-skill-content.mjs --check # exit non-zero if out of date (CI)
19
+ */
20
+
21
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync } from "node:fs";
22
+ import { dirname, join } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const PKG_ROOT = join(__dirname, "..");
27
+ const CORE_SKILL_PATH = join(PKG_ROOT, "SKILL.md");
28
+ const SKILLS_DIR = join(PKG_ROOT, "skills");
29
+ const OUT_PATH = join(PKG_ROOT, "src", "generated", "skill-content.ts");
30
+
31
+ // The core skill (the `cli` entry in SKILLS) lives at the package root; every
32
+ // other skill is a folder under skills/ containing a SKILL.md.
33
+ const CORE_SKILL_NAME = "cli";
34
+
35
+ function collectContent() {
36
+ const content = {};
37
+ content[CORE_SKILL_NAME] = readFileSync(CORE_SKILL_PATH, "utf8");
38
+
39
+ const dirs = readdirSync(SKILLS_DIR, { withFileTypes: true })
40
+ .filter((e) => e.isDirectory())
41
+ .map((e) => e.name)
42
+ .sort();
43
+
44
+ for (const name of dirs) {
45
+ content[name] = readFileSync(join(SKILLS_DIR, name, "SKILL.md"), "utf8");
46
+ }
47
+ return content;
48
+ }
49
+
50
+ function render(content) {
51
+ const entries = Object.keys(content)
52
+ .map(
53
+ (name) => ` ${JSON.stringify(name)}: ${JSON.stringify(content[name])},`,
54
+ )
55
+ .join("\n");
56
+
57
+ return (
58
+ "// AUTO-GENERATED by scripts/generate-skill-content.mjs. Do not edit by hand.\n" +
59
+ "// Run `pnpm --filter @zoralabs/cli generate:skills` after editing any SKILL.md.\n" +
60
+ "// Source of truth: SKILL.md (the `cli` skill) and skills/<name>/SKILL.md.\n" +
61
+ "\n" +
62
+ "export const SKILL_CONTENT: Record<string, string> = {\n" +
63
+ entries +
64
+ "\n};\n"
65
+ );
66
+ }
67
+
68
+ function main() {
69
+ const check = process.argv.includes("--check");
70
+ const generated = render(collectContent());
71
+
72
+ if (check) {
73
+ let current = "";
74
+ try {
75
+ current = readFileSync(OUT_PATH, "utf8");
76
+ } catch {
77
+ console.error(
78
+ "skill-content.ts is missing. Run `pnpm --filter @zoralabs/cli generate:skills`.",
79
+ );
80
+ process.exit(1);
81
+ }
82
+ if (current !== generated) {
83
+ console.error(
84
+ "skill-content.ts is out of date with the SKILL.md sources.\n" +
85
+ "Run `pnpm --filter @zoralabs/cli generate:skills` and commit the result.",
86
+ );
87
+ process.exit(1);
88
+ }
89
+ console.log("skill-content.ts is up to date.");
90
+ return;
91
+ }
92
+
93
+ mkdirSync(dirname(OUT_PATH), { recursive: true });
94
+ writeFileSync(OUT_PATH, generated);
95
+ console.log(`Wrote ${OUT_PATH}`);
96
+ }
97
+
98
+ main();
@@ -1,210 +0,0 @@
1
- /**
2
- * Generates SHA-256 integrity hashes for all skills.
3
- *
4
- * Usage:
5
- * npx tsx scripts/generate-skill-hashes.ts # Print hashes to console
6
- * npx tsx scripts/generate-skill-hashes.ts --write # Update skills.ts directly
7
- * npx tsx scripts/generate-skill-hashes.ts --check # Check if hashes are up-to-date (CI)
8
- */
9
-
10
- import { createHash } from "node:crypto";
11
- import { readFileSync, writeFileSync } from "node:fs";
12
- import { dirname, join } from "node:path";
13
- import { fileURLToPath } from "node:url";
14
-
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- const SKILLS_TS_PATH = join(__dirname, "../src/commands/skills.ts");
17
-
18
- const SKILLS_URL = "https://agents.zora.com/skill";
19
-
20
- const computeIntegrity = (content: string): string => {
21
- const hash = createHash("sha256").update(content, "utf8").digest("base64");
22
- return `sha256-${hash}`;
23
- };
24
-
25
- /**
26
- * Parse skill names from skills.ts to avoid import dependency chain.
27
- * Single source of truth - names are extracted from the SKILLS array.
28
- */
29
- function parseSkillNamesFromFile(): string[] {
30
- const content = readFileSync(SKILLS_TS_PATH, "utf8");
31
- const names: string[] = [];
32
-
33
- // Match all name: "skillname" patterns within the SKILLS array
34
- const namePattern = /name:\s*"([^"]+)"/g;
35
- let match;
36
- while ((match = namePattern.exec(content)) !== null) {
37
- names.push(match[1]);
38
- }
39
-
40
- if (names.length === 0) {
41
- throw new Error("No skill names found in skills.ts");
42
- }
43
-
44
- return names;
45
- }
46
-
47
- /**
48
- * Parse current hashes from skills.ts
49
- */
50
- function getCurrentHashes(): Map<string, string> {
51
- const content = readFileSync(SKILLS_TS_PATH, "utf8");
52
- const hashes = new Map<string, string>();
53
- const skillNames = parseSkillNamesFromFile();
54
-
55
- for (const name of skillNames) {
56
- // Use lazy matching to find the integrity field for each skill
57
- const pattern = new RegExp(
58
- `name:\\s*"${name}"[\\s\\S]*?integrity:\\s*"([^"]*)"`,
59
- );
60
- const match = content.match(pattern);
61
- if (match) {
62
- hashes.set(name, match[1]);
63
- }
64
- }
65
-
66
- return hashes;
67
- }
68
-
69
- async function fetchAllHashes(): Promise<Map<string, string>> {
70
- const hashes = new Map<string, string>();
71
- const skillNames = parseSkillNamesFromFile();
72
-
73
- for (const name of skillNames) {
74
- const url = `${SKILLS_URL}/${name}.md`;
75
- const response = await fetch(url);
76
- if (!response.ok) {
77
- throw new Error(`Failed to fetch ${name}: HTTP ${response.status}`);
78
- }
79
- const content = await response.text();
80
- hashes.set(name, computeIntegrity(content));
81
- }
82
-
83
- return hashes;
84
- }
85
-
86
- function updateSkillsFile(hashes: Map<string, string>): boolean {
87
- const content = readFileSync(SKILLS_TS_PATH, "utf8");
88
- let updated = content;
89
- let changed = false;
90
- const errors: string[] = [];
91
-
92
- for (const [name, hash] of hashes) {
93
- // First, verify this skill has an integrity field by checking
94
- // that we can find it within its own object block (before the next skill)
95
- // Find the position of this skill's name
96
- const namePattern = new RegExp(`name:\\s*"${name}"`);
97
- const nameMatch = namePattern.exec(updated);
98
- if (!nameMatch) {
99
- errors.push(`Skill "${name}" not found in skills.ts`);
100
- continue;
101
- }
102
-
103
- const namePos = nameMatch.index;
104
-
105
- // Find the next skill's name (if any) to bound our search
106
- const remainingContent = updated.slice(namePos + nameMatch[0].length);
107
- const nextSkillMatch = /name:\s*"[^"]+"/.exec(remainingContent);
108
- const searchBound = nextSkillMatch
109
- ? namePos + nameMatch[0].length + nextSkillMatch.index
110
- : updated.length;
111
-
112
- // Extract the bounded region for this skill
113
- const skillRegion = updated.slice(namePos, searchBound);
114
-
115
- // Check if integrity field exists in this skill's region
116
- const integrityInRegion = /integrity:\s*"[^"]*"/.exec(skillRegion);
117
- if (!integrityInRegion) {
118
- errors.push(
119
- `Skill "${name}" is missing integrity field - add 'integrity: "sha256-PLACEHOLDER"' to the skill definition`,
120
- );
121
- continue;
122
- }
123
-
124
- // Now safely replace the integrity value within the bounded region
125
- const updatedRegion = skillRegion.replace(
126
- /(integrity:\s*)"[^"]*"/,
127
- `$1"${hash}"`,
128
- );
129
-
130
- if (updatedRegion !== skillRegion) {
131
- changed = true;
132
- updated = updated.slice(0, namePos) + updatedRegion + updated.slice(searchBound);
133
- }
134
- }
135
-
136
- if (errors.length > 0) {
137
- console.error("\nErrors found:");
138
- for (const err of errors) {
139
- console.error(` - ${err}`);
140
- }
141
- process.exit(1);
142
- }
143
-
144
- if (changed) {
145
- writeFileSync(SKILLS_TS_PATH, updated);
146
- }
147
-
148
- return changed;
149
- }
150
-
151
- async function main() {
152
- const args = process.argv.slice(2);
153
- const writeMode = args.includes("--write");
154
- const checkMode = args.includes("--check");
155
-
156
- console.log("Fetching skills from production...\n");
157
-
158
- let remoteHashes: Map<string, string>;
159
- try {
160
- remoteHashes = await fetchAllHashes();
161
- } catch (err) {
162
- console.error("Failed to fetch skills:", err);
163
- process.exit(1);
164
- }
165
-
166
- if (checkMode) {
167
- const currentHashes = getCurrentHashes();
168
- let hasChanges = false;
169
-
170
- for (const [name, remoteHash] of remoteHashes) {
171
- const currentHash = currentHashes.get(name);
172
- if (currentHash !== remoteHash) {
173
- console.log(`${name}: CHANGED`);
174
- console.log(` Current: ${currentHash}`);
175
- console.log(` Expected: ${remoteHash}\n`);
176
- hasChanges = true;
177
- }
178
- }
179
-
180
- if (hasChanges) {
181
- console.log("Skill hashes are out of date. Run with --write to update.");
182
- process.exit(1);
183
- } else {
184
- console.log("All skill hashes are up to date.");
185
- process.exit(0);
186
- }
187
- }
188
-
189
- if (writeMode) {
190
- const changed = updateSkillsFile(remoteHashes);
191
- if (changed) {
192
- console.log("Updated skills.ts with new hashes:");
193
- for (const [name, hash] of remoteHashes) {
194
- console.log(` ${name}: ${hash}`);
195
- }
196
- } else {
197
- console.log("No changes needed - hashes are already up to date.");
198
- }
199
- } else {
200
- console.log("Generated hashes (run with --write to update skills.ts):\n");
201
- for (const [name, hash] of remoteHashes) {
202
- console.log(` ${name}: ${hash}`);
203
- }
204
- }
205
- }
206
-
207
- main().catch((err) => {
208
- console.error("Fatal error:", err);
209
- process.exit(1);
210
- });