@zoralabs/cli 1.2.0 → 1.4.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
|
@@ -0,0 +1,210 @@
|
|
|
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
|
+
});
|