diff --git a/skills/aoconnect/SKILL.md b/skills/aoconnect/SKILL.md new file mode 100644 index 0000000..b2ab96d --- /dev/null +++ b/skills/aoconnect/SKILL.md @@ -0,0 +1,469 @@ +--- +name: aoconnect +description: CLI for interacting with AO processes using @permaweb/aoconnect - spawn processes, send messages, read results, monitor messages, and dry run +compatibility: Requires Node.js 18+, @permaweb/aoconnect package +metadata: + author: rakis + version: "0.0.1" +--- + +# AOConnect Skill + +A command-line interface for interacting with AO processes using the `@permaweb/aoconnect` library. + +## Phrase Mappings + +| User Request | Command | +|--------------|---------| +| "use aoconnect to spawn" | `spawn` | +| "use aoconnect to message" | `message` | +| "use aoconnect to read result" | `result` | +| "use aoconnect to dryrun" | `dryrun` | +| "use aoconnect to monitor" | `monitor` | +| "use aoconnect to connect" | `connect` | + +## Installation + +Install the aoconnect skill and its dependency: + +```bash +npx skills add https://github.com/permaweb/skills --skill aoconnect +npm install --save @permaweb/aoconnect +``` + +Or manually: + +```bash +cd skills/aoconnect +npm install @permaweb/aoconnect +``` + +## Prerequisites + +- **Node.js 18+** +- **@permaweb/aoconnect** package installed +- **Arweave wallet** (JWK format) for signing messages + +## Available Functions + +### Spawn an AO Process + +```sh +node skills/aoconnect/index.mjs spawn \ + --wallet ./wallet.json \ + --module \ + --scheduler +``` + +**Options:** +- `--wallet ` - Path to Arweave wallet JSON +- `--module ` - The arweave TxID of the ao Module +- `--scheduler
` - The Arweave wallet address of a Scheduler Unit +- `--data ` - Optional data to include in spawn message + +**Example:** + +```sh +node skills/aoconnect/index.mjs spawn \ + --wallet ./wallet.json \ + --module "l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc" \ + --scheduler "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA" +``` + +**Output:** + +```json +{ + "messageId": "l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc", + "processId": "5SGJUlPwlenkyuG9-xWh0Rcf0azm8XEd5RBTiutgWAg", + "height": 587540, + "tags": [ + { "name": "App-Process", "value": "l3hbt..." }, + { "name": "App-Name", "value": "arweave-ao" } + ] +} +``` + +### Send a Message to an AO Process + +```sh +node skills/aoconnect/index.mjs message \ + --wallet ./wallet.json \ + --process \ + --data= \ + --tags Name=Value,Another=Tag +``` + +**Options:** +- `--wallet ` - Path to Arweave wallet JSON (required) +- `--process ` - The ao Process ID (required) +- `--data ` - Message data (optional, random string if not provided) +- `--tags ` - Message tags (optional) + +**Example:** + +```sh +node skills/aoconnect/index.mjs message \ + --wallet ./wallet.json \ + --process "5SGJUlPwlenkyuG9-xWh0Rcf0azm8XEd5RBTiutgWAg" \ + --data="Hello from AO!" \ + --tags "Action=greet", "User=Rakis" +``` + +**Output:** + +```json +{ + "messageId": "l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc", + "height": 587540, + "tags": [ + { "name": "Action", "value": "greet" }, + { "name": "User", "value": "Rakis" }, + { "name": "Original-Message", "value": "l3hbt..." } + ] +} +``` + +### Read the Result of an AO Message Evaluation + +```sh +node skills/aoconnect/index.mjs result \ + --message= \ + --process= +``` + +**Options:** +- `--message=` - The ao message ID to evaluate (required) +- `--process=` - The ao Process ID (required) + +**Example:** + +```sh +node skills/aoconnect/index.mjs result \ + --message="l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc" \ + --process="5SGJUlPwlenkyuG9-xWh0Rcf0azm8XEd5RBTiutgWAg" +``` + +**Output:** + +```json +{ + "messages": [ + { + "messageId": "l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc", + "tags": [ + { "name": "Action", "value": "greet" }, + { "name": "User", "value": "Rakis" } + ], + "data": "Hello from AO!" + } + ], + "spawns": [], + "output": { + "messages": [ + { + "from": "address", + "to": "process-id", + "target": "process-id", + "tags": [ + { "name": "Response", "value": "Hello from AO!" } + ], + "data": "", + "height": 587541 + } + ], + "size": 256 + }, + "error": null +} +``` + +### Dry Run a Message (No Memory Commit) + +```sh +node skills/aoconnect/index.mjs dryrun \ + --message= \ + --process= +``` + +**Options:** +- `--message=` - The ao message ID to dry run (required) +- `--process=` - The ao Process ID (required) + +**Example:** + +```sh +node skills/aoconnect/index.mjs dryrun \ + --message="l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc" \ + --process="5SGJUlPwlenkyuG9-xWh0Rcf0azm8XEd5RBTiutgWAg" +``` + +**Output:** + +```json +{ + "messages": [ + { + "messageId": "l3hbt-rIJ_dr9at-eQ3EVajHWMnxPNm9eBtXpzsFWZc", + "tags": [ + { "name": "Action", "value": "greet" } + ] + } + ], + "spawns": [], + "output": { + "messages": [ + { + "from": "address", + "to": "process-id", + "target": "process-id", + "tags": [ + { "name": "Response", "value": "Hello from AO!" } + ], + "height": 587541 + } + ], + "size": 256 + }, + "error": null +} +``` + +**Note:** Dry run evaluates the message but doesn't save it to memory. Perfect for testing without committing to the chain. + +### Monitor Messages + +```sh +node skills/aoconnect/index.mjs monitor \ + --process \ + --on-message "" +``` + +**Options:** +- `--process ` - The ao Process ID to monitor (required) +- `--on-message ""` - JavaScript function to call for each new message (required) + +**Example:** + +```sh +node skills/aoconnect/index.mjs monitor \ + --process "5SGJUlPwlenkyuG9-xWh0Rcf0azm8XEd5RBTiutgWAg" \ + --on-message 'return { tags: msg.tags, data: msg.data }' +``` + +**Note:** In chat interface, use this pattern: +```bash +node skills/aoconnect/index.mjs monitor --process --on-message "{console.log(msg.tags)}" +``` + +### Connect to Custom Nodes + +```sh +node skills/aoconnect/index.mjs connect --mu --cu --gateway +``` + +**Options:** +- `--mu ` - Message Unit URL (optional) +- `--cu ` - Compute Unit URL (optional) +- `--gateway ` - Arweave gateway URL (optional) + +**Example:** + +```sh +node skills/aoconnect/index.mjs connect \ + --mu "https://mu.ao-testnet.xyz" \ + --cu "https://cu.ao-testnet.xyz" \ + --gateway "https://arweave.net" +``` + +## Usage with AI Tools + +Claude Code / OpenCode will automatically invoke the aoconnect skill when you: + +```bash +use aoconnect to spawn +use aoconnect to message --data= +use aoconnect to read result --message= +use aoconnect to dryrun --message= +``` + +The skill will prompt for wallet path if not configured. + +## Common Use Cases + +### Send a Transfer Message + +```sh +node skills/aoconnect/index.mjs message \ + --wallet ./wallet.json \ + --process "ao-token-demo-AOe5Hdg4UQhOiE0ZYvRjB_8YDhROi3pA0YCEhPzb_KQ" \ + --data= \ + --tags "Action=Transfer", "Recipient=
" +``` + +### Dry Run Before Committing + +Always dry run first to verify your message works: + +```sh +node skills/aoconnect/index.mjs message \ + --wallet ./wallet.json \ + --process \ + --data="test" + +node skills/aoconnect/index.mjs dryrun \ + --message= \ + --process= +``` + +### Monitor Active Process + +```sh +node skills/aoconnect/index.mjs monitor \ + --process "" \ + --on-message "{console.log('New message:', msg.tags)}" +``` + +## Error Handling + +### Missing Wallet + +``` +Error: walletPath is required. Please provide a path to your Arweave wallet JSON file. +``` + +**Solution:** Provide a wallet path: +```sh +--wallet ./wallet.json +``` + +### Invalid Process ID + +``` +Error: Failed to send message: Invalid process ID +``` + +**Solution:** Verify the process ID is correct and valid. + +### Authentication Failures + +``` +Error: Failed to send message: Unauthorized +``` + +**Solution:** Ensure your wallet has sufficient balance and proper permissions. + +## API Reference + +### messageAo(processId, options) +Send a message to an ao Process. + +**Parameters:** +- `processId` (string): The ao Process ID +- `options` (Object): Message options + - `data` (string): Message data + - `tags` (Array): Message tags + - `walletPath` (string): Path to wallet JSON + +**Returns:** Promise - Message result + +### resultAo(options) +Read the result of an ao message evaluation. + +**Parameters:** +- `options` (Object) + - `message` (string): The message ID + - `process` (string): The Process ID + +**Returns:** Promise - Result object + +### dryrunAo(options) +Dry run a message without committing to memory. + +**Parameters:** +- `options` (Object) + - `message` (string): The message ID + - `process` (string): The Process ID + +**Returns:** Promise - Dry run result + +### spawnAo(options) +Spawn an ao Process. + +**Parameters:** +- `options` (Object) + - `module` (string): Module TxID + - `scheduler` (string): Scheduler address + - `walletPath` (string): Path to wallet JSON + - `tags` (Array): Tags for spawn message + +**Returns:** Promise - Spawn result + +### monitorAo(options) +Monitor messages from a Process. + +**Parameters:** +- `options` (Object) + - `process` (string): Process ID + - `onMessage` (Function): Callback for new messages + +**Returns:** string - Monitor ID + +### unmonitorAo(monitorId) +Stop monitoring messages. + +**Parameters:** +- `monitorId` (string): Monitor ID + +### connectAo(config) +Connect to ao nodes with custom configuration. + +**Parameters:** +- `config` (Object) + - `MU_URL` (string): Message Unit URL + - `CU_URL` (string): Compute Unit URL + - `GATEWAY_URL` (string): Arweave gateway URL + +**Returns:** Object - aoconnect functions + +### getConnection(config) +Get connected aoconnect functions. + +**Parameters:** +- `config` (Object): Connection config + +**Returns:** Object - aoconnect functions + +## Node.js vs Browser + +This skill works in both Node.js and browser environments. + +### Node.js +```javascript +import { messageAo, createDataItemSigner } from "./index.mjs"; +import { readFileSync } from "node:fs"; + +const wallet = JSON.parse(readFileSync("./wallet.json", "utf-8")); +const result = await messageAo("process-id", { + walletPath: "./wallet.json", + data: "Hello AO!", + tags: [{ name: "Action", value: "greet" }] +}); +``` + +### Browser +```javascript +import { messageAo, createDataItemSigner } from "./index.mjs"; + +const result = await messageAo("process-id", { + walletPath: "/path/to/wallet.json", // or loaded from localStorage + data: "Hello AO!", + tags: [{ name: "Action", value: "greet" }] +}); +``` + +## See Also + +- [AO Cookbook](https://cookbook_ao.arweave.net) +- [@permaweb/aoconnect Package](https://www.npmjs.com/package/@permaweb/aoconnect) +- [AR.IO Documentation](https://ar.io) diff --git a/skills/aoconnect/index.mjs b/skills/aoconnect/index.mjs new file mode 100644 index 0000000..40e176e --- /dev/null +++ b/skills/aoconnect/index.mjs @@ -0,0 +1,292 @@ +import { readFileSync } from "node:fs"; +import { message, createDataItemSigner, spawn, result, dryrun, monitor, unmonitor, connect } from "@permaweb/aoconnect"; + +/** + * Send a message to an ao Process + * @param {string} processId - The ao Process ID to send a message to + * @param {Object} options - Message options + * @param {string} options.data - Message data (optional, will generate random if not provided) + * @param {Array} options.tags - Message tags + * @param {string} options.walletPath - Path to Arweave wallet JSON (required) + * @returns {Promise} Message result + */ +export async function messageAo(processId, options = {}) { + const { data, tags, walletPath } = options; + + if (!walletPath) { + throw new Error("walletPath is required. Please provide a path to your Arweave wallet JSON file."); + } + + try { + const wallet = JSON.parse(readFileSync(walletPath, "utf-8")); + const signer = createDataItemSigner(wallet); + + const response = await message({ + process: processId, + tags: tags || [], + data: data || undefined, + signer, + }); + + return response; + } catch (error) { + throw new Error(`Failed to send message: ${error.message}`); + } +} + +/** + * Read the result of an ao Message evaluation + * @param {Object} options - Result options + * @param {string} options.message - The message to evaluate + * @param {string} options.process - The ao Process ID + * @returns {Promise} Result object + */ +export async function resultAo(options) { + const { message, process } = options; + + if (!message || !process) { + throw new Error("message and process are required"); + } + + try { + const response = await result({ + message, + process, + }); + + return response; + } catch (error) { + throw new Error(`Failed to read result: ${error.message}`); + } +} + +/** + * Dry run a message (doesn't save to memory) + * @param {Object} options - Dry run options + * @param {string} options.message - The message to dry run + * @param {string} options.process - The ao Process ID + * @returns {Promise} Dry run result + */ +export async function dryrunAo(options) { + const { message, process } = options; + + if (!message || !process) { + throw new Error("message and process are required"); + } + + try { + const response = await dryrun({ + message, + process, + }); + + return response; + } catch (error) { + throw new Error(`Failed to dry run: ${error.message}`); + } +} + +/** + * Spawn an ao Process + * @param {Object} options - Spawn options + * @param {string} options.module - The arweave TxID of the ao Module + * @param {string} options.scheduler - The Arweave wallet address of a Scheduler Unit + * @param {string} options.walletPath - Path to Arweave wallet JSON (required) + * @param {Object} options.tags - Tags for the spawn message + * @returns {Promise} Spawn result + */ +export async function spawnAo(options) { + const { module, scheduler, walletPath, tags = [] } = options; + + if (!module || !scheduler || !walletPath) { + throw new Error("module, scheduler, and walletPath are required"); + } + + try { + const wallet = JSON.parse(readFileSync(walletPath, "utf-8")); + const signer = createDataItemSigner(wallet); + + const response = await spawn({ + module, + scheduler, + tags, + signer, + }); + + return response; + } catch (error) { + throw new Error(`Failed to spawn process: ${error.message}`); + } +} + +/** + * Monitor messages + * @param {Object} options - Monitor options + * @param {string} options.process - The ao Process ID to monitor + * @param {Function} options.onMessage - Callback for new messages + * @returns {Promise} Monitor ID + */ +export async function monitorAo(options) { + const { process, onMessage } = options; + + if (!process || typeof onMessage !== "function") { + throw new Error("process and onMessage callback are required"); + } + + try { + const monitorId = monitor({ + process, + onMessage, + }); + + return monitorId; + } catch (error) { + throw new Error(`Failed to start monitoring: ${error.message}`); + } +} + +/** + * Stop monitoring messages + * @param {string} monitorId - Monitor ID returned from monitorAo + */ +export function unmonitorAo(monitorId) { + if (!monitorId) { + throw new Error("monitorId is required"); + } + + try { + unmonitor(monitorId); + } catch (error) { + throw new Error(`Failed to stop monitoring: ${error.message}`); + } +} + +/** + * Connect to ao nodes with custom configuration + * @param {Object} config - Connection config + * @param {string} config.MU_URL - Message Unit URL + * @param {string} config.CU_URL - Compute Unit URL + * @param {string} config.GATEWAY_URL - Arweave gateway URL + * @returns {Object} aoconnect functions + */ +export function connectAo(config = {}) { + try { + const ao = connect(config); + return ao; + } catch (error) { + throw new Error(`Failed to connect: ${error.message}`); + } +} + +/** + * Get connection info + * @param {Object} config - Connection config + * @returns {Object} Connected aoconnect functions + */ +export function getConnection(config = {}) { + return connect(config); +} + +// CLI mode +if (import.meta.url === `file://${process.argv[1]}`) { + const command = process.argv[2]; + const args = process.argv.slice(3); + + try { + switch (command) { + case "message": { + const walletPath = args.find((arg, i) => arg === "--wallet" && args[i + 1]); + const processId = args.find((arg, i) => arg.startsWith("--process") && !arg.includes("=")); + const data = args.find((arg, i) => arg.startsWith("--data=")); + + if (!walletPath || !processId) { + console.error("Usage: node index.mjs message --wallet --process [--data=]"); + process.exit(1); + } + + const response = await messageAo(processId, { + walletPath, + data: data?.replace("--data=", ""), + }); + + console.log(JSON.stringify(response, null, 2)); + break; + } + + case "result": { + const message = args.find((arg, i) => arg.startsWith("--message=")); + const process = args.find((arg, i) => arg.startsWith("--process=")); + + if (!message || !process) { + console.error("Usage: node index.mjs result --message= --process="); + process.exit(1); + } + + const response = await resultAo({ + message: message.replace("--message=", ""), + process: process.replace("--process=", ""), + }); + + console.log(JSON.stringify(response, null, 2)); + break; + } + + case "dryrun": { + const message = args.find((arg, i) => arg.startsWith("--message=")); + const process = args.find((arg, i) => arg.startsWith("--process=")); + + if (!message || !process) { + console.error("Usage: node index.mjs dryrun --message= --process="); + process.exit(1); + } + + const response = await dryrunAo({ + message: message.replace("--message=", ""), + process: process.replace("--process=", ""), + }); + + console.log(JSON.stringify(response, null, 2)); + break; + } + + case "spawn": { + const walletPath = args.find((arg, i) => arg === "--wallet" && args[i + 1]); + const module = args.find((arg, i) => arg.startsWith("--module=")); + const scheduler = args.find((arg, i) => arg.startsWith("--scheduler=")); + + if (!walletPath || !module || !scheduler) { + console.error( + "Usage: node index.mjs spawn --wallet --module= --scheduler=
" + ); + process.exit(1); + } + + const response = await spawnAo({ + walletPath, + module: module.replace("--module=", ""), + scheduler: scheduler.replace("--scheduler=", ""), + }); + + console.log(JSON.stringify(response, null, 2)); + break; + } + + default: + console.log("aoconnect skill CLI"); + console.log(""); + console.log("Commands:"); + console.log(" message Send a message to an ao process"); + console.log(" result Read the result of an ao message evaluation"); + console.log(" dryrun Dry run a message without saving to memory"); + console.log(" spawn Spawn an ao process"); + console.log(""); + console.log("Examples:"); + console.log(" node index.mjs message --wallet ./wallet.json --process --data="); + console.log(" node index.mjs result --message= --process="); + console.log(" node index.mjs spawn --wallet ./wallet.json --module= --scheduler=
"); + } + } catch (error) { + console.error(`Error: ${error.message}`); + process.exit(1); + } +} diff --git a/skills/aoconnect/package.json b/skills/aoconnect/package.json new file mode 100644 index 0000000..400491c --- /dev/null +++ b/skills/aoconnect/package.json @@ -0,0 +1,32 @@ +{ + "name": "aoconnect", + "version": "0.0.1", + "type": "module", + "description": "CLI for interacting with AO processes using @permaweb/aoconnect", + "main": "index.mjs", + "keywords": ["ao", "arweave", "permaweb", "blockchain"], + "author": "rakis", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "dependencies": { + "@permaweb/aoconnect": "^0.0.93" + }, + "scripts": { + "test": "node index.mjs --help", + "dev": "node index.mjs" + }, + "files": [ + "index.mjs", + "SKILL.md" + ], + "repository": { + "type": "git", + "url": "https://github.com/permaweb/skills.git" + }, + "bugs": { + "url": "https://github.com/permaweb/skills/issues" + }, + "homepage": "https://github.com/permaweb/skills#readme" +}