@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/dist/chunk-WF24B44I.js +54 -0
- package/dist/{client-M3K6L2ZM.js → client-QV2PLHGY.js} +60 -35
- package/dist/index.js +236 -156
- package/package.json +14 -9
- package/scripts/generate-skill-content.mjs +98 -0
- package/scripts/generate-skill-hashes.ts +0 -210
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoralabs/cli",
|
|
3
|
-
"version": "1.4.
|
|
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
|
-
"
|
|
54
|
-
"
|
|
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
|
-
});
|