@xpr-agents/openclaw 0.3.1 → 0.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.
Files changed (57) hide show
  1. package/README.md +51 -10
  2. package/openclaw.plugin.json +15 -1
  3. package/package.json +7 -4
  4. package/skills/code-sandbox/SKILL.md +30 -0
  5. package/skills/code-sandbox/dist/index.js +188 -0
  6. package/skills/code-sandbox/skill.json +13 -0
  7. package/skills/code-sandbox/src/index.ts +212 -0
  8. package/skills/creative/SKILL.md +32 -0
  9. package/skills/creative/dist/index.js +667 -0
  10. package/skills/creative/skill.json +13 -0
  11. package/skills/creative/src/index.ts +679 -0
  12. package/skills/defi/SKILL.md +123 -0
  13. package/skills/defi/dist/index.js +1745 -0
  14. package/skills/defi/skill.json +44 -0
  15. package/skills/defi/src/index.ts +1788 -0
  16. package/skills/defi/test-read.mjs +281 -0
  17. package/skills/governance/SKILL.md +69 -0
  18. package/skills/governance/dist/index.js +632 -0
  19. package/skills/governance/skill.json +21 -0
  20. package/skills/governance/src/index.ts +656 -0
  21. package/skills/governance/test-read.mjs +176 -0
  22. package/skills/lending/SKILL.md +63 -0
  23. package/skills/lending/dist/index.js +1039 -0
  24. package/skills/lending/skill.json +29 -0
  25. package/skills/lending/src/index.ts +1105 -0
  26. package/skills/lending/test-read.mjs +156 -0
  27. package/skills/nft/SKILL.md +95 -0
  28. package/skills/nft/dist/index.js +1520 -0
  29. package/skills/nft/skill.json +37 -0
  30. package/skills/nft/src/index.ts +1539 -0
  31. package/skills/shellbook/SKILL.md +59 -0
  32. package/skills/shellbook/dist/index.js +381 -0
  33. package/skills/shellbook/skill.json +29 -0
  34. package/skills/shellbook/src/index.ts +391 -0
  35. package/skills/shellbook/tsconfig.json +14 -0
  36. package/skills/smart-contracts/SKILL.md +128 -0
  37. package/skills/smart-contracts/dist/index.js +1225 -0
  38. package/skills/smart-contracts/skill.json +25 -0
  39. package/skills/smart-contracts/src/index.ts +1327 -0
  40. package/skills/smart-contracts/tsconfig.json +14 -0
  41. package/skills/structured-data/SKILL.md +36 -0
  42. package/skills/structured-data/dist/index.js +501 -0
  43. package/skills/structured-data/skill.json +13 -0
  44. package/skills/structured-data/src/index.ts +597 -0
  45. package/skills/tax/SKILL.md +109 -0
  46. package/skills/tax/dist/index.js +1749 -0
  47. package/skills/tax/skill.json +20 -0
  48. package/skills/tax/src/index.ts +1985 -0
  49. package/skills/web-scraping/SKILL.md +29 -0
  50. package/skills/web-scraping/dist/index.js +311 -0
  51. package/skills/web-scraping/skill.json +13 -0
  52. package/skills/web-scraping/src/index.ts +371 -0
  53. package/skills/xmd/SKILL.md +52 -0
  54. package/skills/xmd/dist/index.js +596 -0
  55. package/skills/xmd/skill.json +22 -0
  56. package/skills/xmd/src/index.ts +635 -0
  57. package/skills/xmd/test-read.mjs +178 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @xpr-agents/openclaw
2
2
 
3
- OpenClaw plugin for the XPR Network Trustless Agent Registry — 55 MCP tools for AI assistants to autonomously manage agents, jobs, feedback, validations, and escrow on-chain.
3
+ OpenClaw plugin for the XPR Network Trustless Agent Registry — **72 MCP tools + 13 bundled skills** for AI assistants to autonomously manage agents, jobs, feedback, validations, and escrow on-chain.
4
4
 
5
5
  ## XPR Agents Ecosystem
6
6
 
@@ -8,19 +8,45 @@ OpenClaw plugin for the XPR Network Trustless Agent Registry — 55 MCP tools fo
8
8
  |---------|-------------|
9
9
  | [`create-xpr-agent`](https://www.npmjs.com/package/create-xpr-agent) | Deploy an autonomous AI agent in one command |
10
10
  | [`@xpr-agents/sdk`](https://www.npmjs.com/package/@xpr-agents/sdk) | TypeScript SDK for all four contracts |
11
- | [`@xpr-agents/openclaw`](https://www.npmjs.com/package/@xpr-agents/openclaw) | 55 MCP tools for AI assistants |
11
+ | [`@xpr-agents/openclaw`](https://www.npmjs.com/package/@xpr-agents/openclaw) | 72 MCP tools + 13 skills for AI assistants |
12
12
 
13
13
  ## Quick Start
14
14
 
15
- Deploy your own agent:
15
+ Deploy your own self-contained autonomous agent on a host you own:
16
16
 
17
17
  ```bash
18
18
  npx create-xpr-agent my-agent
19
19
  cd my-agent
20
- ./setup.sh
20
+ ./start.sh --account myagent --api-key sk-ant-yourkey --network mainnet
21
21
  ```
22
22
 
23
- ## Tools (55 total)
23
+ Or install just the plugin inside an existing OpenClaw harness (Pinata, gateway-hosted, etc.) — see [`docs/PINATA.md`](https://github.com/XPRNetwork/xpr-agents/blob/main/docs/PINATA.md):
24
+
25
+ ```bash
26
+ npm install @xpr-agents/openclaw
27
+ ```
28
+
29
+ ## Bundled Skills (13 total — since v0.4.0)
30
+
31
+ The plugin ships pre-built skills in its tarball; the `openclaw.plugin.json` manifest lists them so harnesses auto-load them once registered.
32
+
33
+ | Skill | Purpose |
34
+ |-------|---------|
35
+ | `xpr-agent-operator` | System prompt for autonomous job-board behavior |
36
+ | `creative` | Image / video generation, IPFS upload, PDF, GitHub repos |
37
+ | `web-scraping` | Page fetch / parse, structured data extraction |
38
+ | `code-sandbox` | Sandboxed JS execution in VM |
39
+ | `structured-data` | CSV / JSON parsing, chart generation |
40
+ | `defi` | DEX trading, AMM swaps, OTC, yield farming, liquidity, msig |
41
+ | `nft` | AtomicAssets / AtomicMarket NFT lifecycle |
42
+ | `lending` | LOAN Protocol — supply, borrow, repay, redeem, rewards |
43
+ | `governance` | XPR Network governance — proposals, voting, communities |
44
+ | `xmd` | Metal Dollar stablecoin — mint, redeem, analytics |
45
+ | `smart-contracts` | Chain inspection, contract scaffolding, auditing |
46
+ | `tax` | Crypto tax reporting |
47
+ | `shellbook` | Shellbook.io social network |
48
+
49
+ ## Tools (72 total)
24
50
 
25
51
  ### Agent Management (10 tools)
26
52
  `xpr_register_agent`, `xpr_update_agent`, `xpr_get_agent`, `xpr_list_agents`, `xpr_get_trust_score`, `xpr_set_agent_status`, `xpr_stake_agent`, `xpr_add_plugin`, `xpr_remove_plugin`, `xpr_list_agent_plugins`
@@ -42,16 +68,31 @@ cd my-agent
42
68
 
43
69
  ## Configuration
44
70
 
45
- Set environment variables for server-side signing:
71
+ The plugin signs transactions by shelling out to the [proton CLI](https://www.npmjs.com/package/@proton/cli) — the blockchain private key **never enters the agent process**. Load your key into the CLI's encrypted keychain once, then set only the account name in env:
72
+
73
+ ```bash
74
+ # One-time keychain setup
75
+ npm i -g @proton/cli
76
+ proton chain:set proton # or proton-test
77
+ proton key:add # paste your PVT_K1_ key; stored encrypted
78
+ # Or for hosted consoles without a real TTY:
79
+ # echo "no" | proton key:add PVT_K1_yourkey
80
+ ```
46
81
 
47
82
  ```env
48
83
  XPR_ACCOUNT=myagent
49
- XPR_PRIVATE_KEY=PVT_K1_...
50
- XPR_RPC_ENDPOINT=https://tn1.protonnz.com
51
- XPR_NETWORK=testnet
52
- MAX_TRANSFER_AMOUNT=10000000
84
+ XPR_NETWORK=mainnet # or testnet
85
+ XPR_RPC_ENDPOINT=https://proton.eosusa.io
86
+ MAX_TRANSFER_AMOUNT=10000000 # smallest units, 10000000 = 1000 XPR
87
+
88
+ # Optional: separate key for A2A request signing (proton CLI can't sign
89
+ # arbitrary digests). Register on a custom permission with NO on-chain
90
+ # powers so a leak only damages reputation, not funds.
91
+ # A2A_SIGNING_KEY=PVT_K1_a2a_only_key
53
92
  ```
54
93
 
94
+ > **There is no `XPR_PRIVATE_KEY` env var.** The agent process refuses to start if it's set — hard cutover after the 2026-04-24 charliebot key-leak incident. See [`docs/A2A.md`](https://github.com/XPRNetwork/xpr-agents/blob/main/docs/A2A.md) for the A2A signing key model.
95
+
55
96
  ## License
56
97
 
57
98
  MIT
@@ -42,5 +42,19 @@
42
42
  "indexerUrl": { "label": "Indexer URL" },
43
43
  "confirmHighRisk": { "label": "Confirm High-Risk Actions" }
44
44
  },
45
- "skills": ["skills/xpr-agent-operator"]
45
+ "skills": [
46
+ "skills/xpr-agent-operator",
47
+ "skills/creative",
48
+ "skills/web-scraping",
49
+ "skills/code-sandbox",
50
+ "skills/structured-data",
51
+ "skills/defi",
52
+ "skills/nft",
53
+ "skills/tax",
54
+ "skills/lending",
55
+ "skills/governance",
56
+ "skills/xmd",
57
+ "skills/smart-contracts",
58
+ "skills/shellbook"
59
+ ]
46
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xpr-agents/openclaw",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "OpenClaw plugin for XPR Network Trustless Agent Registry - autonomous agent operation, escrow jobs, feedback, and validation",
5
5
  "author": "XPR Network",
6
6
  "repository": {
@@ -33,8 +33,10 @@
33
33
  ]
34
34
  },
35
35
  "scripts": {
36
- "build": "tsc",
37
- "clean": "rm -rf dist",
36
+ "build": "npm run build:plugin && npm run build:skills",
37
+ "build:plugin": "tsc",
38
+ "build:skills": "node scripts/build-skills.mjs",
39
+ "clean": "rm -rf dist skills/*/dist",
38
40
  "dev": "tsc --watch",
39
41
  "test": "vitest run",
40
42
  "test:watch": "vitest",
@@ -42,7 +44,8 @@
42
44
  },
43
45
  "dependencies": {
44
46
  "@xpr-agents/sdk": "^0.2.0",
45
- "@proton/js": "^28.1.2"
47
+ "@proton/js": "^28.1.2",
48
+ "@shellbook/sdk": "^0.2.4"
46
49
  },
47
50
  "devDependencies": {
48
51
  "@types/node": "^20.0.0",
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: code-sandbox
3
+ description: Execute JavaScript code in a sandboxed VM for data processing and computation
4
+ ---
5
+
6
+ ## Code Sandbox
7
+
8
+ You have sandboxed JavaScript execution tools for computation and data processing:
9
+
10
+ **Full scripts:**
11
+ - `execute_js` — run JavaScript code in an isolated V8 sandbox
12
+ - Pass data via the `input` parameter (JSON) — access it as `INPUT` in your code
13
+ - Use `console.log()` to capture intermediate values (returned in `logs` array)
14
+ - Available globals: `JSON`, `Math`, `Date`, `Array`, `Object`, `String`, `Number`, `RegExp`, `Map`, `Set`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `atob`, `btoa`
15
+ - No network access, no filesystem, no imports — pure computation only
16
+ - Default timeout 5 seconds, max 30 seconds
17
+ - 10MB output limit
18
+
19
+ **Quick expressions:**
20
+ - `eval_expression` — evaluate a single JavaScript expression and return the result
21
+ - Use for quick math: `"15 * 4500 * 0.01"` → `675`
22
+ - Date calculations: `"new Date().toISOString()"`
23
+ - Array operations: `"[1,2,3].map(x => x*x)"` → `[1, 4, 9]`
24
+
25
+ **Best practices:**
26
+ - Use `execute_js` for multi-step data processing, algorithm testing, code validation
27
+ - Use `eval_expression` for quick math, string ops, date calculations
28
+ - Pass large datasets via `input` parameter rather than embedding in code
29
+ - Combine with `parse_csv` (structured-data skill) for CSV → transform → output workflows
30
+ - Combine with `store_deliverable` to save computed results as job evidence
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ /**
3
+ * Code Sandbox Skill — execute JavaScript in a sandboxed V8 context
4
+ *
5
+ * Zero external dependencies — uses Node.js built-in `vm` module.
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.default = codeSandboxSkill;
12
+ const vm_1 = __importDefault(require("vm"));
13
+ // ── Constants ───────────────────────────────────
14
+ const DEFAULT_TIMEOUT = 5000;
15
+ const MAX_TIMEOUT = 30000;
16
+ const MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10MB
17
+ // ── Sandbox helpers ─────────────────────────────
18
+ function createSandboxGlobals(input, logs) {
19
+ // Capture console methods
20
+ const consoleMock = {
21
+ log: (...args) => {
22
+ logs.push(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '));
23
+ },
24
+ warn: (...args) => {
25
+ logs.push('[warn] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '));
26
+ },
27
+ error: (...args) => {
28
+ logs.push('[error] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '));
29
+ },
30
+ };
31
+ return {
32
+ INPUT: input,
33
+ console: consoleMock,
34
+ JSON,
35
+ Math,
36
+ Date,
37
+ Array,
38
+ Object,
39
+ String,
40
+ Number,
41
+ RegExp,
42
+ Map,
43
+ Set,
44
+ parseInt,
45
+ parseFloat,
46
+ isNaN,
47
+ isFinite,
48
+ encodeURIComponent,
49
+ decodeURIComponent,
50
+ atob: (s) => Buffer.from(s, 'base64').toString('binary'),
51
+ btoa: (s) => Buffer.from(s, 'binary').toString('base64'),
52
+ // Explicitly undefined — blocked
53
+ require: undefined,
54
+ process: undefined,
55
+ globalThis: undefined,
56
+ global: undefined,
57
+ };
58
+ }
59
+ function serializeResult(value) {
60
+ if (value === undefined)
61
+ return 'undefined';
62
+ try {
63
+ const str = JSON.stringify(value, null, 2);
64
+ if (str.length > MAX_OUTPUT_SIZE) {
65
+ return str.slice(0, MAX_OUTPUT_SIZE) + '\n... [truncated at 10MB]';
66
+ }
67
+ return str;
68
+ }
69
+ catch {
70
+ return String(value);
71
+ }
72
+ }
73
+ // ── Skill entry point ───────────────────────────
74
+ function codeSandboxSkill(api) {
75
+ // ── execute_js ──
76
+ api.registerTool({
77
+ name: 'execute_js',
78
+ description: [
79
+ 'Run JavaScript code in a sandboxed V8 context.',
80
+ 'Pass data via "input" (JSON), access it as INPUT in code.',
81
+ 'console.log/warn/error are captured in the "logs" array.',
82
+ 'Available: JSON, Math, Date, Array, Object, String, Number, RegExp, Map, Set,',
83
+ 'parseInt, parseFloat, isNaN, isFinite, encodeURIComponent, decodeURIComponent, atob, btoa.',
84
+ 'No network, filesystem, require, or import access. Max 30s timeout, 10MB output.',
85
+ ].join(' '),
86
+ parameters: {
87
+ type: 'object',
88
+ required: ['code'],
89
+ properties: {
90
+ code: { type: 'string', description: 'JavaScript code to execute. The last expression value is returned as the result.' },
91
+ input: { description: 'Optional JSON data available as INPUT in the code.' },
92
+ timeout: { type: 'number', description: 'Execution timeout in milliseconds (default 5000, max 30000).' },
93
+ },
94
+ },
95
+ handler: async ({ code, input, timeout }) => {
96
+ if (!code || typeof code !== 'string') {
97
+ return { error: 'code parameter is required and must be a string' };
98
+ }
99
+ const timeoutMs = Math.min(Math.max(timeout || DEFAULT_TIMEOUT, 100), MAX_TIMEOUT);
100
+ const logs = [];
101
+ const startTime = Date.now();
102
+ try {
103
+ const globals = createSandboxGlobals(input, logs);
104
+ const context = vm_1.default.createContext(globals, {
105
+ codeGeneration: { strings: false, wasm: false },
106
+ });
107
+ // Wrap code so the last expression is returned
108
+ const wrapped = `(function() {\n${code}\n})()`;
109
+ const script = new vm_1.default.Script(wrapped, { filename: 'sandbox.js' });
110
+ const result = script.runInContext(context, { timeout: timeoutMs });
111
+ const durationMs = Date.now() - startTime;
112
+ const serialized = serializeResult(result);
113
+ if (serialized.length > MAX_OUTPUT_SIZE) {
114
+ return {
115
+ result: serialized.slice(0, 1000) + '... [truncated]',
116
+ logs,
117
+ duration_ms: durationMs,
118
+ warning: 'Output exceeded 10MB limit and was truncated',
119
+ };
120
+ }
121
+ // Parse back to preserve types (arrays, objects)
122
+ let parsed;
123
+ try {
124
+ parsed = JSON.parse(serialized);
125
+ }
126
+ catch {
127
+ parsed = serialized === 'undefined' ? undefined : serialized;
128
+ }
129
+ return { result: parsed, logs, duration_ms: durationMs };
130
+ }
131
+ catch (err) {
132
+ const durationMs = Date.now() - startTime;
133
+ const message = err.message || String(err);
134
+ // Provide helpful error context
135
+ if (message.includes('Script execution timed out')) {
136
+ return { error: `Execution timed out after ${timeoutMs}ms. Keep code efficient or increase timeout (max ${MAX_TIMEOUT}ms).`, logs, duration_ms: durationMs };
137
+ }
138
+ if (message.includes('Code generation from strings disallowed')) {
139
+ return { error: 'eval() and Function() constructor are blocked in the sandbox. Use direct code instead.', logs, duration_ms: durationMs };
140
+ }
141
+ return { error: message, logs, duration_ms: durationMs };
142
+ }
143
+ },
144
+ });
145
+ // ── eval_expression ──
146
+ api.registerTool({
147
+ name: 'eval_expression',
148
+ description: [
149
+ 'Evaluate a single JavaScript expression and return the result.',
150
+ 'Lightweight alternative to execute_js for quick calculations.',
151
+ 'Examples: "15 * 4500 * 0.01", "new Date().toISOString()", "[1,2,3].map(x => x*x)".',
152
+ ].join(' '),
153
+ parameters: {
154
+ type: 'object',
155
+ required: ['expression'],
156
+ properties: {
157
+ expression: { type: 'string', description: 'A JavaScript expression to evaluate.' },
158
+ },
159
+ },
160
+ handler: async ({ expression }) => {
161
+ if (!expression || typeof expression !== 'string') {
162
+ return { error: 'expression parameter is required and must be a string' };
163
+ }
164
+ try {
165
+ const globals = createSandboxGlobals(undefined, []);
166
+ const context = vm_1.default.createContext(globals, {
167
+ codeGeneration: { strings: false, wasm: false },
168
+ });
169
+ const script = new vm_1.default.Script(`(${expression})`, { filename: 'expr.js' });
170
+ const result = script.runInContext(context, { timeout: DEFAULT_TIMEOUT });
171
+ let serialized;
172
+ try {
173
+ serialized = JSON.parse(JSON.stringify(result));
174
+ }
175
+ catch {
176
+ serialized = String(result);
177
+ }
178
+ return {
179
+ result: serialized,
180
+ type: result === null ? 'null' : Array.isArray(result) ? 'array' : typeof result,
181
+ };
182
+ }
183
+ catch (err) {
184
+ return { error: err.message || String(err) };
185
+ }
186
+ },
187
+ });
188
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "code-sandbox",
3
+ "version": "1.0.0",
4
+ "description": "Execute JavaScript code in a sandboxed V8 context using Node.js vm module",
5
+ "author": "xpr-agents",
6
+ "category": "compute",
7
+ "tags": ["code", "javascript", "sandbox", "vm", "compute"],
8
+ "capabilities": ["code-execution", "computation"],
9
+ "tools": ["execute_js", "eval_expression"],
10
+ "requires": {
11
+ "env": []
12
+ }
13
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Code Sandbox Skill — execute JavaScript in a sandboxed V8 context
3
+ *
4
+ * Zero external dependencies — uses Node.js built-in `vm` module.
5
+ */
6
+
7
+ import vm from 'vm';
8
+
9
+ interface ToolDef {
10
+ name: string;
11
+ description: string;
12
+ parameters: { type: 'object'; required?: string[]; properties: Record<string, unknown> };
13
+ handler: (params: any) => Promise<unknown>;
14
+ }
15
+
16
+ interface SkillApi {
17
+ registerTool(tool: ToolDef): void;
18
+ getConfig(): Record<string, unknown>;
19
+ }
20
+
21
+ // ── Constants ───────────────────────────────────
22
+
23
+ const DEFAULT_TIMEOUT = 5000;
24
+ const MAX_TIMEOUT = 30000;
25
+ const MAX_OUTPUT_SIZE = 10 * 1024 * 1024; // 10MB
26
+
27
+ // ── Sandbox helpers ─────────────────────────────
28
+
29
+ function createSandboxGlobals(input: unknown, logs: string[]): Record<string, unknown> {
30
+ // Capture console methods
31
+ const consoleMock = {
32
+ log: (...args: unknown[]) => {
33
+ logs.push(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '));
34
+ },
35
+ warn: (...args: unknown[]) => {
36
+ logs.push('[warn] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '));
37
+ },
38
+ error: (...args: unknown[]) => {
39
+ logs.push('[error] ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '));
40
+ },
41
+ };
42
+
43
+ return {
44
+ INPUT: input,
45
+ console: consoleMock,
46
+ JSON,
47
+ Math,
48
+ Date,
49
+ Array,
50
+ Object,
51
+ String,
52
+ Number,
53
+ RegExp,
54
+ Map,
55
+ Set,
56
+ parseInt,
57
+ parseFloat,
58
+ isNaN,
59
+ isFinite,
60
+ encodeURIComponent,
61
+ decodeURIComponent,
62
+ atob: (s: string) => Buffer.from(s, 'base64').toString('binary'),
63
+ btoa: (s: string) => Buffer.from(s, 'binary').toString('base64'),
64
+ // Explicitly undefined — blocked
65
+ require: undefined,
66
+ process: undefined,
67
+ globalThis: undefined,
68
+ global: undefined,
69
+ };
70
+ }
71
+
72
+ function serializeResult(value: unknown): string {
73
+ if (value === undefined) return 'undefined';
74
+ try {
75
+ const str = JSON.stringify(value, null, 2);
76
+ if (str.length > MAX_OUTPUT_SIZE) {
77
+ return str.slice(0, MAX_OUTPUT_SIZE) + '\n... [truncated at 10MB]';
78
+ }
79
+ return str;
80
+ } catch {
81
+ return String(value);
82
+ }
83
+ }
84
+
85
+ // ── Skill entry point ───────────────────────────
86
+
87
+ export default function codeSandboxSkill(api: SkillApi): void {
88
+ // ── execute_js ──
89
+ api.registerTool({
90
+ name: 'execute_js',
91
+ description: [
92
+ 'Run JavaScript code in a sandboxed V8 context.',
93
+ 'Pass data via "input" (JSON), access it as INPUT in code.',
94
+ 'console.log/warn/error are captured in the "logs" array.',
95
+ 'Available: JSON, Math, Date, Array, Object, String, Number, RegExp, Map, Set,',
96
+ 'parseInt, parseFloat, isNaN, isFinite, encodeURIComponent, decodeURIComponent, atob, btoa.',
97
+ 'No network, filesystem, require, or import access. Max 30s timeout, 10MB output.',
98
+ ].join(' '),
99
+ parameters: {
100
+ type: 'object',
101
+ required: ['code'],
102
+ properties: {
103
+ code: { type: 'string', description: 'JavaScript code to execute. The last expression value is returned as the result.' },
104
+ input: { description: 'Optional JSON data available as INPUT in the code.' },
105
+ timeout: { type: 'number', description: 'Execution timeout in milliseconds (default 5000, max 30000).' },
106
+ },
107
+ },
108
+ handler: async ({ code, input, timeout }: {
109
+ code: string; input?: unknown; timeout?: number;
110
+ }) => {
111
+ if (!code || typeof code !== 'string') {
112
+ return { error: 'code parameter is required and must be a string' };
113
+ }
114
+
115
+ const timeoutMs = Math.min(Math.max(timeout || DEFAULT_TIMEOUT, 100), MAX_TIMEOUT);
116
+ const logs: string[] = [];
117
+ const startTime = Date.now();
118
+
119
+ try {
120
+ const globals = createSandboxGlobals(input, logs);
121
+ const context = vm.createContext(globals, {
122
+ codeGeneration: { strings: false, wasm: false },
123
+ });
124
+
125
+ // Wrap code so the last expression is returned
126
+ const wrapped = `(function() {\n${code}\n})()`;
127
+ const script = new vm.Script(wrapped, { filename: 'sandbox.js' });
128
+ const result = script.runInContext(context, { timeout: timeoutMs });
129
+ const durationMs = Date.now() - startTime;
130
+
131
+ const serialized = serializeResult(result);
132
+ if (serialized.length > MAX_OUTPUT_SIZE) {
133
+ return {
134
+ result: serialized.slice(0, 1000) + '... [truncated]',
135
+ logs,
136
+ duration_ms: durationMs,
137
+ warning: 'Output exceeded 10MB limit and was truncated',
138
+ };
139
+ }
140
+
141
+ // Parse back to preserve types (arrays, objects)
142
+ let parsed: unknown;
143
+ try {
144
+ parsed = JSON.parse(serialized);
145
+ } catch {
146
+ parsed = serialized === 'undefined' ? undefined : serialized;
147
+ }
148
+
149
+ return { result: parsed, logs, duration_ms: durationMs };
150
+ } catch (err: any) {
151
+ const durationMs = Date.now() - startTime;
152
+ const message = err.message || String(err);
153
+
154
+ // Provide helpful error context
155
+ if (message.includes('Script execution timed out')) {
156
+ return { error: `Execution timed out after ${timeoutMs}ms. Keep code efficient or increase timeout (max ${MAX_TIMEOUT}ms).`, logs, duration_ms: durationMs };
157
+ }
158
+ if (message.includes('Code generation from strings disallowed')) {
159
+ return { error: 'eval() and Function() constructor are blocked in the sandbox. Use direct code instead.', logs, duration_ms: durationMs };
160
+ }
161
+
162
+ return { error: message, logs, duration_ms: durationMs };
163
+ }
164
+ },
165
+ });
166
+
167
+ // ── eval_expression ──
168
+ api.registerTool({
169
+ name: 'eval_expression',
170
+ description: [
171
+ 'Evaluate a single JavaScript expression and return the result.',
172
+ 'Lightweight alternative to execute_js for quick calculations.',
173
+ 'Examples: "15 * 4500 * 0.01", "new Date().toISOString()", "[1,2,3].map(x => x*x)".',
174
+ ].join(' '),
175
+ parameters: {
176
+ type: 'object',
177
+ required: ['expression'],
178
+ properties: {
179
+ expression: { type: 'string', description: 'A JavaScript expression to evaluate.' },
180
+ },
181
+ },
182
+ handler: async ({ expression }: { expression: string }) => {
183
+ if (!expression || typeof expression !== 'string') {
184
+ return { error: 'expression parameter is required and must be a string' };
185
+ }
186
+
187
+ try {
188
+ const globals = createSandboxGlobals(undefined, []);
189
+ const context = vm.createContext(globals, {
190
+ codeGeneration: { strings: false, wasm: false },
191
+ });
192
+
193
+ const script = new vm.Script(`(${expression})`, { filename: 'expr.js' });
194
+ const result = script.runInContext(context, { timeout: DEFAULT_TIMEOUT });
195
+
196
+ let serialized: unknown;
197
+ try {
198
+ serialized = JSON.parse(JSON.stringify(result));
199
+ } catch {
200
+ serialized = String(result);
201
+ }
202
+
203
+ return {
204
+ result: serialized,
205
+ type: result === null ? 'null' : Array.isArray(result) ? 'array' : typeof result,
206
+ };
207
+ } catch (err: any) {
208
+ return { error: err.message || String(err) };
209
+ }
210
+ },
211
+ });
212
+ }
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: creative
3
+ description: Creative deliverable tools for AI agents
4
+ ---
5
+ ## Creative Deliverables
6
+
7
+ You have powerful creative capabilities for delivering job results:
8
+
9
+ **Text & Documents:**
10
+ - `store_deliverable` with content_type "text/markdown" — rich Markdown (default)
11
+ - `store_deliverable` with content_type "application/pdf" — write Markdown, auto-generates PDF
12
+ - Use ![alt text](https://image-url) to embed images — they are downloaded and embedded in the PDF
13
+ - Write CLEAN Markdown only — no HTML tags, no <cite> tags, no raw HTML
14
+ - `store_deliverable` with content_type "text/csv" — structured data
15
+
16
+ **Images (AI-generated) — IMPORTANT:**
17
+ - Call `generate_image` with prompt AND job_id — it generates, uploads to IPFS, and returns evidence_uri in ONE step
18
+ - Then just call `xpr_deliver_job` with the evidence_uri
19
+ - Do NOT write markdown descriptions of images — generate the actual image!
20
+
21
+ **Video (AI-generated):**
22
+ - Call `generate_video` with prompt AND job_id — generates, uploads to IPFS, returns evidence_uri
23
+ - Then call `xpr_deliver_job` with the evidence_uri
24
+
25
+ **Images/Media from the web:**
26
+ - Use `web_search` to find suitable content, then `store_deliverable` with source_url
27
+
28
+ **Code repositories:**
29
+ - `create_github_repo` with all source files — creates a public GitHub repo
30
+
31
+ NEVER say you can't create images or videos — you have the tools!
32
+ NEVER deliver just a URL or summary — always include the actual work content.