@takisvc/code-canvas 0.1.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.
Files changed (2) hide show
  1. package/dist/code-canvas.js +282 -0
  2. package/package.json +38 -0
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+
3
+ // client.ts
4
+ import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
5
+ import { formatSIWEMessage } from "@worldcoin/agentkit";
6
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
7
+ import { execSync } from "child_process";
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ import ora from "ora";
11
+ import chalk from "chalk";
12
+ function ensureWallet() {
13
+ const dir = join(homedir(), ".code-canvas");
14
+ const keyPath = join(dir, "key");
15
+ if (existsSync(keyPath)) {
16
+ const key2 = readFileSync(keyPath, "utf8").trim();
17
+ return privateKeyToAccount(key2);
18
+ }
19
+ mkdirSync(dir, { recursive: true });
20
+ const key = generatePrivateKey();
21
+ writeFileSync(keyPath, key, { mode: 384 });
22
+ const account2 = privateKeyToAccount(key);
23
+ console.log(chalk.green("Generated new agent wallet"));
24
+ console.log(` Address: ${chalk.bold(account2.address)}`);
25
+ console.log(` Key file: ${keyPath}`);
26
+ console.log();
27
+ return account2;
28
+ }
29
+ var SPINNER_MESSAGES = [
30
+ "Conjuring pixels...",
31
+ "Asking the AI gods...",
32
+ "Brewing creativity...",
33
+ "Painting with algorithms...",
34
+ "Summoning the muse...",
35
+ "Mixing digital paint...",
36
+ "Consulting the pixel oracle...",
37
+ "Channeling pure imagination..."
38
+ ];
39
+ function randomSpinnerMessage() {
40
+ return SPINNER_MESSAGES[Math.floor(Math.random() * SPINNER_MESSAGES.length)];
41
+ }
42
+ function supportsInlineImages() {
43
+ if (process.env.TERM_PROGRAM === "iTerm.app" || process.env.TERM_PROGRAM === "WezTerm") {
44
+ return "iterm";
45
+ }
46
+ if (process.env.KITTY_WINDOW_ID) {
47
+ return "kitty";
48
+ }
49
+ return false;
50
+ }
51
+ function displayInlineImage(base64) {
52
+ const protocol = supportsInlineImages();
53
+ if (protocol === "iterm") {
54
+ process.stdout.write(
55
+ `\x1B]1337;File=inline=1;preserveAspectRatio=1:${base64}\x07
56
+ `
57
+ );
58
+ return true;
59
+ }
60
+ if (protocol === "kitty") {
61
+ const chunkSize = 4096;
62
+ for (let i = 0; i < base64.length; i += chunkSize) {
63
+ const chunk = base64.slice(i, i + chunkSize);
64
+ const more = i + chunkSize < base64.length ? 1 : 0;
65
+ if (i === 0) {
66
+ process.stdout.write(
67
+ `\x1B_Gf=100,a=T,m=${more};${chunk}\x1B\\`
68
+ );
69
+ } else {
70
+ process.stdout.write(`\x1B_Gm=${more};${chunk}\x1B\\`);
71
+ }
72
+ }
73
+ process.stdout.write("\n");
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ function openFile(filepath) {
79
+ try {
80
+ if (process.platform === "darwin") {
81
+ execSync(`open "${filepath}"`, { stdio: "ignore" });
82
+ } else if (process.platform === "linux") {
83
+ execSync(`xdg-open "${filepath}"`, { stdio: "ignore" });
84
+ } else if (process.platform === "win32") {
85
+ execSync(`start "" "${filepath}"`, { stdio: "ignore" });
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ function saveImage(base64, prompt2) {
91
+ const filename = `generated-${Date.now()}.png`;
92
+ const buffer = Buffer.from(base64, "base64");
93
+ writeFileSync(filename, buffer);
94
+ const shown = displayInlineImage(base64);
95
+ const tweetText = encodeURIComponent(
96
+ `My AI agent just roasted my coding session \u{1F525}
97
+
98
+ Prompt: "${prompt2}"
99
+
100
+ Powered by @worldcoin AgentKit`
101
+ );
102
+ console.log(` Saved: ${chalk.bold(filename)}`);
103
+ console.log(
104
+ ` Share on X: https://twitter.com/intent/tweet?text=${tweetText}`
105
+ );
106
+ if (!shown) {
107
+ openFile(filename);
108
+ }
109
+ }
110
+ function showUsage(usage) {
111
+ if (!usage) return;
112
+ const remaining = Math.max(0, usage.total - usage.used);
113
+ if (remaining > 0) {
114
+ console.log(
115
+ chalk.dim(` Free generations: ${remaining} of ${usage.total} remaining`)
116
+ );
117
+ } else {
118
+ console.log(
119
+ chalk.yellow(
120
+ ` Free trial exhausted. Register with World ID for unlimited access or pay $1.20 USDC.`
121
+ )
122
+ );
123
+ }
124
+ }
125
+ function extractAgentkitChallenge(body) {
126
+ return body?.extensions?.agentkit ?? body?.accepts?.[0]?.extensions?.agentkit ?? body?.paymentRequirements?.[0]?.extensions?.agentkit ?? body?.challenge;
127
+ }
128
+ function runRegistration(address) {
129
+ console.log();
130
+ console.log(
131
+ chalk.yellow("Launching World ID registration \u2014 scan the QR code with your World App:")
132
+ );
133
+ console.log();
134
+ try {
135
+ execSync(
136
+ `npx @worldcoin/agentkit-cli register ${address} --network world`,
137
+ { stdio: "inherit" }
138
+ );
139
+ console.log();
140
+ console.log(chalk.green("Registration successful!"));
141
+ return true;
142
+ } catch {
143
+ console.log();
144
+ console.log(chalk.red("Registration failed or was cancelled."));
145
+ return false;
146
+ }
147
+ }
148
+ async function generateImage(endpoint2, prompt2, account2) {
149
+ const spinner = ora(randomSpinnerMessage()).start();
150
+ const interval = setInterval(() => {
151
+ spinner.text = randomSpinnerMessage();
152
+ }, 3e3);
153
+ try {
154
+ const firstResponse = await fetch(endpoint2, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({ prompt: prompt2 })
158
+ });
159
+ if (firstResponse.status === 200) {
160
+ const body = await firstResponse.json();
161
+ spinner.succeed(chalk.green("Image generated!"));
162
+ saveImage(body.image, prompt2);
163
+ showUsage(body.usage);
164
+ return true;
165
+ }
166
+ if (firstResponse.status !== 402) {
167
+ spinner.fail("Request failed");
168
+ const body = await firstResponse.json().catch(() => ({}));
169
+ console.error(chalk.red(body.error ?? `HTTP ${firstResponse.status}`));
170
+ return false;
171
+ }
172
+ const paymentRequiredHeader = firstResponse.headers.get("payment-required");
173
+ let paymentData = null;
174
+ if (paymentRequiredHeader) {
175
+ try {
176
+ paymentData = JSON.parse(
177
+ Buffer.from(paymentRequiredHeader, "base64").toString()
178
+ );
179
+ } catch {
180
+ }
181
+ }
182
+ const challenge = extractAgentkitChallenge(paymentData);
183
+ if (!challenge?.info || !challenge?.supportedChains?.length) {
184
+ spinner.fail("Server did not return an agentkit challenge");
185
+ if (paymentData) {
186
+ console.error(
187
+ "402 payment data:",
188
+ JSON.stringify(paymentData, null, 2)
189
+ );
190
+ } else {
191
+ console.error("No payment-required header found");
192
+ }
193
+ return false;
194
+ }
195
+ spinner.text = "Signing with wallet...";
196
+ const { info, supportedChains } = challenge;
197
+ const chain = supportedChains[0];
198
+ const message = formatSIWEMessage(
199
+ { ...info, chainId: chain.chainId },
200
+ account2.address
201
+ );
202
+ const signature = await account2.signMessage({ message });
203
+ const payload = {
204
+ ...info,
205
+ address: account2.address,
206
+ chainId: chain.chainId,
207
+ type: chain.type,
208
+ signature
209
+ };
210
+ const encoded = Buffer.from(JSON.stringify(payload)).toString("base64");
211
+ spinner.text = "Verifying World ID...";
212
+ const secondResponse = await fetch(endpoint2, {
213
+ method: "POST",
214
+ headers: {
215
+ "Content-Type": "application/json",
216
+ agentkit: encoded
217
+ },
218
+ body: JSON.stringify({ prompt: prompt2 })
219
+ });
220
+ if (secondResponse.status === 200) {
221
+ const body = await secondResponse.json();
222
+ spinner.succeed(
223
+ chalk.green("Image generated (free \u2014 World ID verified!)")
224
+ );
225
+ saveImage(body.image, prompt2);
226
+ showUsage(body.usage);
227
+ return true;
228
+ }
229
+ const errorBody = await secondResponse.json().catch(() => ({}));
230
+ if (secondResponse.status !== 402) {
231
+ spinner.fail(`Server error (${secondResponse.status})`);
232
+ console.error(chalk.red(errorBody?.error ?? "Unknown error"));
233
+ return false;
234
+ }
235
+ spinner.stop();
236
+ console.log();
237
+ console.log(chalk.yellow("Your agent is not registered with World ID."));
238
+ const registered = runRegistration(account2.address);
239
+ if (registered) {
240
+ console.log();
241
+ console.log(chalk.cyan("Retrying image generation..."));
242
+ return false;
243
+ }
244
+ console.log();
245
+ console.log(
246
+ ` ${chalk.bold("Option 1:")} Try registration again manually:`
247
+ );
248
+ console.log(
249
+ ` npx @worldcoin/agentkit-cli register ${account2.address} --network world`
250
+ );
251
+ console.log();
252
+ console.log(
253
+ ` ${chalk.bold("Option 2:")} Pay ${chalk.cyan("$1.20 USDC")} per image:`
254
+ );
255
+ console.log(` Fund your wallet with USDC on World Chain and re-run.`);
256
+ console.log(` Wallet: ${account2.address}`);
257
+ if (errorBody?.error) {
258
+ console.log();
259
+ console.log(chalk.dim(`Server: ${errorBody.error}`));
260
+ }
261
+ return false;
262
+ } finally {
263
+ clearInterval(interval);
264
+ }
265
+ }
266
+ var SERVER_URL = process.env.SERVER_URL ?? "https://takis.dev";
267
+ var prompt = process.argv[2];
268
+ if (!prompt) {
269
+ console.log(`Usage: ${chalk.bold('code-canvas "<prompt>"')}`);
270
+ console.log();
271
+ console.log("Examples:");
272
+ console.log(' code-canvas "a cat riding a skateboard"');
273
+ console.log(' code-canvas "cyberpunk city at sunset"');
274
+ process.exit(1);
275
+ }
276
+ var account = ensureWallet();
277
+ var endpoint = `${SERVER_URL}/code-canvas/generate-image`;
278
+ var success = await generateImage(endpoint, prompt, account);
279
+ if (!success && !process.exitCode) {
280
+ success = await generateImage(endpoint, prompt, account);
281
+ }
282
+ process.exit(success ? 0 : 1);
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@takisvc/code-canvas",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "code-canvas": "./dist/code-canvas.js"
7
+ },
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "dev": "tsx dev.ts",
13
+ "server": "tsx dev.ts",
14
+ "deploy": "wrangler deploy",
15
+ "build": "tsup",
16
+ "prepublishOnly": "npm run build",
17
+ "generate": "tsx client.ts"
18
+ },
19
+ "dependencies": {
20
+ "@hono/node-server": "^1.13.0",
21
+ "@openrouter/sdk": "^0.9.11",
22
+ "@worldcoin/agentkit": "^0.1.5",
23
+ "@x402/core": "^2.6.0",
24
+ "@x402/evm": "^2.6.0",
25
+ "@x402/hono": "^2.6.0",
26
+ "chalk": "^5.4.0",
27
+ "dotenv": "^17.3.1",
28
+ "hono": "^4.7.0",
29
+ "ora": "^8.2.0",
30
+ "viem": "^2.46.0"
31
+ },
32
+ "devDependencies": {
33
+ "tsup": "^8.4.0",
34
+ "tsx": "^4.7.0",
35
+ "typescript": "^5.3.0",
36
+ "wrangler": "^4.0.0"
37
+ }
38
+ }