Create an agent skill for a tool called arx (@permaweb/arx) that can:
- Upload a single file to Arweave.
- Upload a website directory (upload files + upload an Arweave manifest).
- Update an ArNS domain record to point to a new Arweave transaction ID.
The skill must prompt the user for the location of their Arweave wallet keyfile (JWK JSON) when it is not provided.
- Funding / buying turbo credits.
- Supporting non-Arweave tokens (ETH/SOL/POL) in the first version.
- Creating or purchasing new ArNS names.
- As a user, I can say: "use arweave to upload foo.md" and receive a
txIdand gateway URL. - As a user, I can say: "use arweave to upload ./mywebsite" and receive the website manifest
txIdand gateway URL. - As a user, I can say: "use arweave to attach to hello_rakis" and have the ArNS record updated.
- As a user, if no wallet is configured, the skill asks once for the wallet keyfile path.
Build a small TypeScript CLI wrapper that imports and uses:
@permaweb/arx/node(uploads)@ar.io/sdk/node(ArNS / ANT record updates)@permaweb/aoconnect(AO network connectivity)
Bundle the CLI into a single Node-runnable file placed in:
skills/arweave/index.mjs
Add the skill instruction file:
skills/arweave/SKILL.md
- Node runtime:
>=18(required by@ar.io/sdk). - Bundler URL default:
https://turbo.ardrive.io(overrideable). - ArNS TTL default:
3600seconds (overrideable). - Network default:
mainnet.
Resolve wallet path in this order:
--wallet <path>ARWEAVE_WALLET=<path>- Prompt on stdin:
Path to Arweave wallet keyfile (JWK json):
Validation:
- File exists.
- JSON parses.
- Looks like an Arweave JWK (e.g. has
kty,n).
Security:
- Never print wallet contents.
-
upload <file>- Verify file exists.
- Instantiate
NodeARxwith{ token: "arweave", url: <bundlerUrl>, key: <JWK> }andawait arx.ready(). - Call
arx.uploadFile(file). - Output:
txId+https://arweave.net/<txId>.
-
upload-site <dir> [--index index.html]- Verify directory exists.
- If
--indexomitted, auto-selectindex.htmlif present. - Call
arx.uploadFolder(dir, { indexFile }). - Output: manifest
txId+https://arweave.net/<txId>.
-
attach <txId> <name> [--ttl 3600] [--network mainnet] [--ario-process <id>] [--yes]- Name normalization:
- Strip
.ar.iosuffix if present. - If contains
_, interpret as undername:hello_rakis=> undernamehelloon baserakis. - Else treat as base name record (undername
@).
- Strip
- Network/process selection:
--network mainnet|testnet(default:mainnet)--ario-process <mainnet|testnet|processId>(overrides--network)- Process IDs:
- mainnet:
qNvAoz0TgcH7DMg8BCVn8jF32QH5L6T29VjHxhHqqGE - testnet:
agYcCFJtrMG6cqMuZfskIkFTGvUPddICmtQSBIoPdiA
- mainnet:
- Mainnet write safety:
- Prompt confirmation unless
--yes.
- Prompt confirmation unless
- Update flow:
- Configure AO connection with reliable endpoints:
const ao = connect({ CU_URL: 'https://cu.ardrive.io', MU_URL: 'https://mu.ao-testnet.xyz', MODE: 'legacy', });
- Initialize IO with configured AO:
const signer = new ArweaveSigner(jwk); const io = IO.init({ signer, process: new AOProcess({ processId: arioProcessId, ao }), });
- Lookup ArNS record:
const arns = await io.getArNSRecord({ name: baseName }) - Initialize ANT with configured AO (critical fix):
const ant = ANT.init({ signer, process: new AOProcess({ processId: arns.processId, ao }), });
- Update record:
ant.setRecord({ undername: '@', transactionId: txId, ttlSeconds })for baseant.setRecord({ undername, transactionId: txId, ttlSeconds })for undername
- Configure AO connection with reliable endpoints:
- Timeouts:
- Wrap
io.getArNSRecord()with 60s timeout - Wrap
ant.setRecord()with 90s timeout - Clear error message on timeout with network/process info
- Wrap
- Name normalization:
The skill doc will instruct the agent to:
- Map phrases to CLI invocations:
- "use arweave to upload " ->
upload - "use arweave to upload " ->
upload-site - "use arweave to attach to " ->
attach
- "use arweave to upload " ->
- Ask for wallet path when missing.
- Report back
txIdand gateway URL. - For site uploads, emphasize the returned
txIdis the manifest transaction ID.
Example invocations:
node skills/arweave/index.mjs upload "foo.md" --wallet "<path>"
node skills/arweave/index.mjs upload-site "./mywebsite" --index "index.html" --wallet "<path>"
node skills/arweave/index.mjs attach "<txId>" "hello_rakis" --ttl 3600 --network mainnet --wallet "<path>" --yes
node skills/arweave/index.mjs attach "<txId>" "hello_rakis" --ario-process testnet --wallet "<path>" --yes- Clear failures when:
- Wallet file cannot be read/parsed.
- Upload path is missing/invalid.
- ArNS name does not exist (
getArNSRecordfails). - Network/write fails.
- AO lookup/write times out (with helpful message including network info).
After implementation:
npm inpm run buildnode skills/arweave/index.mjs --help
Verification steps:
-
Mainnet lookup (no write - answer 'n' to confirmation):
node skills/arweave/index.mjs attach <txId> cyberpunk_rakis-me --network mainnet --wallet <path>
-
Mainnet write:
node skills/arweave/index.mjs attach <txId> cyberpunk_rakis-me --network mainnet --yes --wallet <path>
-
Testnet behavior (should respond quickly, may not find name):
node skills/arweave/index.mjs attach <txId> cyberpunk_rakis-me --network testnet --yes --wallet <path>
ArNS lookups timeout because:
- The
@ar.io/sdkdefaults may use slow/unreliable AO endpoints - We're not configuring the AO connection with faster CU/MU URLs
- ANT.init() creates its own AOProcess with default
connect()instead of our configured one
Based on research of permaweb/permaweb-deploy repository patterns:
- Add to
ParsedArgs:network: 'mainnet' | 'testnet'arioProcess: string | null
- Extend
parseArgs()to parse:--network mainnet|testnet(defaultmainnet)--ario-process <id|mainnet|testnet>(defaultnull)
- Precedence logic:
- If
--ario-processprovided:- If value is
mainnet/testnet, map to process id - Else treat as explicit process id
- If value is
- Else derive process id from
--network
- If
- Add dependency:
@permaweb/aoconnect - Import:
import { connect } from '@permaweb/aoconnect'; - In
handleAttach, create configured AO:const ao = connect({ CU_URL: 'https://cu.ardrive.io', MU_URL: 'https://mu.ao-testnet.xyz', MODE: 'legacy', });
- Determine
arioProcessIdfrom flags - IO init with explicit AOProcess:
const io = IO.init({ signer, process: new AOProcess({ processId: arioProcessId, ao }), });
- ANT init with explicit AOProcess (KEY FIX):
const ant = ANT.init({ signer, process: new AOProcess({ processId: arnsRecord.processId, ao }), });
- Add helper:
withTimeout(promise, ms, label) - Wrap
io.getArNSRecord()with 60s timeout - Wrap
ant.setRecord()with 90s timeout - Error message includes network, process id, and retry suggestions
- Update
showHelp()with new flags - Update
skills/arweave/SKILL.mdexamples - Update confirmation prompt: "This will update a <mainnet|testnet> ArNS record..."
- Rebuild:
npm run build - Smoke test:
node skills/arweave/index.mjs --help - Mainnet read check (answer 'n'): test lookup works
- Mainnet write: full attach with
--yes - Testnet behavior: should respond quickly