[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7AE5BARJSFCT296SEMYDSJQ":3,"skill-same:01M7AE5BARJSFCT296SEMYDSJQ":50},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":18,"linkOnly":19,"markdownContent":20,"markdownBody":21,"frontMatter":22,"refName":23,"commitSha":24,"skillDirectory":25,"forksCount":26,"repositoryHtmlUrl":27,"repositoryHomepageUrl":6,"ownerHtmlUrl":28,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":29,"install":30,"resources":43},"01M7AE5BARJSFCT296SEMYDSJQ","sandbox-twilio",null,"Send SMS and WhatsApp messages and look up numbers from a Code node or CodeAct action, with NodeTool's Twilio request builder running on the host","hanchenhao\u002Fai-canvas","hanchenhao","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F15713703?v=4","01M7ADWCF5AE30N3MG7MDF3RV2","01M7ADWCFVJS4V8VD9KWJG22WR",1,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-twilio\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: sandbox-twilio\ndescription: Send SMS and WhatsApp messages and look up numbers from a Code node or CodeAct action, with NodeTool's Twilio request builder running on the host\n---\n\n# Twilio in the sandbox\n\nSpecifier: `@nodetool-ai\u002Fsandbox-twilio`. Declare it in the node's `packages`\nproperty and import it at the top of the body.\n\nTwilio is HTTP Basic auth over form-encoded bodies, on a URL that buries the\naccount SID in the path. None of that is hard; all of it is easy to get wrong\nonce, and the failure is a 401 that names nothing.\n\n**Nothing here sends a request.** `request` builds one; the guest's own `fetch`\nsends it, under the run's fetch cap and SSRF guard.\n\n## request\n\n```js\nimport { request } from \"@nodetool-ai\u002Fsandbox-twilio\";\n\nconst accountSid = await nodetool.secrets.get(\"TWILIO_ACCOUNT_SID\");\nconst authToken = await nodetool.secrets.get(\"TWILIO_AUTH_TOKEN\");\n\nconst req = await request({\n  accountSid, authToken,\n  path: \"Messages.json\",\n  method: \"POST\",\n  params: { To: inputs.to, From: inputs.from, Body: inputs.text }\n});\nconst res = await fetch(req.url, req);\nif (!res.ok) throw new Error(`Twilio: ${res.json.message ?? res.status}`);\nreturn { sid: res.json.sid, status: res.json.status };\n```\n\nOptions: `accountSid` and `authToken` (required), `path` (required), `method`\n(default `GET`), `params`, `host` (default `api.twilio.com`), `query`.\n\n`params` become the form body on a write and the query string on a read.\n\nA `path` **without** a leading slash is an Account resource and lands under\n`\u002F2010-04-01\u002FAccounts\u002F\u003Csid>\u002F`. One **with** a leading slash is used as written,\nwhich is how the other products are reached.\n\n## WhatsApp\n\nSame endpoint; the numbers carry a `whatsapp:` prefix.\n\n```js\nconst req = await request({\n  accountSid, authToken, path: \"Messages.json\", method: \"POST\",\n  params: {\n    To: `whatsapp:${inputs.to}`,\n    From: `whatsapp:${inputs.from}`,\n    Body: inputs.text\n  }\n});\n```\n\n## Reading messages\n\n```js\nconst req = await request({\n  accountSid, authToken, path: \"Messages.json\",\n  params: { To: inputs.to, PageSize: 50 }\n});\nconst { messages } = (await fetch(req.url, req)).json;\n```\n\n## Number lookup\n\nLookup lives on its own host, so pass an absolute path:\n\n```js\nconst req = await request({\n  accountSid, authToken,\n  host: \"lookups.twilio.com\",\n  path: `\u002Fv2\u002FPhoneNumbers\u002F${encodeURIComponent(inputs.phone)}`,\n  params: { Fields: \"line_type_intelligence\" }\n});\n```\n\n## Gotchas\n\n- **`request` is async.** A host call is a round trip.\n- **Parameter names are PascalCase.** `To`, `From`, `Body`, `MediaUrl` —\n  Twilio ignores what it does not recognize, so a lowercase `body` sends an\n  empty message rather than an error.\n- **Numbers are E.164.** `+15551234567`, country code and all.\n- **Only `*.twilio.com` hosts are accepted**, so a mistyped `host` fails here\n  rather than mailing credentials somewhere else.\n- **An error is a non-2xx with JSON.** Read `res.json.message` and\n  `res.json.code`; the code maps to Twilio's error reference.\n","# Twilio in the sandbox\n\nSpecifier: `@nodetool-ai\u002Fsandbox-twilio`. Declare it in the node's `packages`\nproperty and import it at the top of the body.\n\nTwilio is HTTP Basic auth over form-encoded bodies, on a URL that buries the\naccount SID in the path. None of that is hard; all of it is easy to get wrong\nonce, and the failure is a 401 that names nothing.\n\n**Nothing here sends a request.** `request` builds one; the guest's own `fetch`\nsends it, under the run's fetch cap and SSRF guard.\n\n## request\n\n```js\nimport { request } from \"@nodetool-ai\u002Fsandbox-twilio\";\n\nconst accountSid = await nodetool.secrets.get(\"TWILIO_ACCOUNT_SID\");\nconst authToken = await nodetool.secrets.get(\"TWILIO_AUTH_TOKEN\");\n\nconst req = await request({\n  accountSid, authToken,\n  path: \"Messages.json\",\n  method: \"POST\",\n  params: { To: inputs.to, From: inputs.from, Body: inputs.text }\n});\nconst res = await fetch(req.url, req);\nif (!res.ok) throw new Error(`Twilio: ${res.json.message ?? res.status}`);\nreturn { sid: res.json.sid, status: res.json.status };\n```\n\nOptions: `accountSid` and `authToken` (required), `path` (required), `method`\n(default `GET`), `params`, `host` (default `api.twilio.com`), `query`.\n\n`params` become the form body on a write and the query string on a read.\n\nA `path` **without** a leading slash is an Account resource and lands under\n`\u002F2010-04-01\u002FAccounts\u002F\u003Csid>\u002F`. One **with** a leading slash is used as written,\nwhich is how the other products are reached.\n\n## WhatsApp\n\nSame endpoint; the numbers carry a `whatsapp:` prefix.\n\n```js\nconst req = await request({\n  accountSid, authToken, path: \"Messages.json\", method: \"POST\",\n  params: {\n    To: `whatsapp:${inputs.to}`,\n    From: `whatsapp:${inputs.from}`,\n    Body: inputs.text\n  }\n});\n```\n\n## Reading messages\n\n```js\nconst req = await request({\n  accountSid, authToken, path: \"Messages.json\",\n  params: { To: inputs.to, PageSize: 50 }\n});\nconst { messages } = (await fetch(req.url, req)).json;\n```\n\n## Number lookup\n\nLookup lives on its own host, so pass an absolute path:\n\n```js\nconst req = await request({\n  accountSid, authToken,\n  host: \"lookups.twilio.com\",\n  path: `\u002Fv2\u002FPhoneNumbers\u002F${encodeURIComponent(inputs.phone)}`,\n  params: { Fields: \"line_type_intelligence\" }\n});\n```\n\n## Gotchas\n\n- **`request` is async.** A host call is a round trip.\n- **Parameter names are PascalCase.** `To`, `From`, `Body`, `MediaUrl` —\n  Twilio ignores what it does not recognize, so a lowercase `body` sends an\n  empty message rather than an error.\n- **Numbers are E.164.** `+15551234567`, country code and all.\n- **Only `*.twilio.com` hosts are accepted**, so a mistyped `host` fails here\n  rather than mailing credentials somewhere else.\n- **An error is a non-2xx with JSON.** Read `res.json.message` and\n  `res.json.code`; the code maps to Twilio's error reference.\n",{"name":5,"description":7},"main","4ef72a828b24c99c49109033c7ec504c34f3bd17","packages\u002Fsandbox-packs\u002Fsandbox-twilio",0,"https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas","https:\u002F\u002Fgithub.com\u002Fhanchenhao","2026-08-18 16:41:12",{"source":8,"skillName":5,"commands":31,"prompt":41,"zipAvailable":42},[32,35,38],{"tool":33,"command":34},"npx","npx skills add hanchenhao\u002Fai-canvas --skill sandbox-twilio",{"tool":36,"command":37},"bunx","bunx skills add hanchenhao\u002Fai-canvas --skill sandbox-twilio",{"tool":39,"command":40},"pnpm","pnpm dlx skills add hanchenhao\u002Fai-canvas --skill sandbox-twilio","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-twilio\u002FSKILL.md",true,[44],{"path":45,"resourceType":46,"resourceTypeDesc":47,"sizeBytes":48,"sourceHtmlUrl":49},"package.json","REFERENCE","参考文档",865,"https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-twilio\u002Fpackage.json",[51,58,65,72,79,86],{"skillNo":52,"name":53,"nameZh":6,"description":54,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":18},"01M7AE5NP5AWGV47WS3F37VPZ1","sandbox-zip","Read and write zip archives in a Code node or CodeAct action, with fflate running on the host under a 50 MB inflation cap","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-zip\u002FSKILL.md",[],[],{"skillNo":59,"name":60,"nameZh":6,"description":61,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":62,"categories":63,"keywords":64,"publishedAt":18},"01M7AE5JP0CG2S114Q246RC6MH","sandbox-yaml","Read and write YAML inside a Code node or CodeAct action, with js-yaml running in the guest","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-yaml\u002FSKILL.md",[],[],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":18},"01M7AE5GJ9AV36WM1DPW1878VM","sandbox-xml","Parse XML feeds and sitemaps in a Code node or CodeAct action, with fast-xml-parser running on the host","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-xml\u002FSKILL.md",[],[],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":18},"01M7AE5DMYDAGJW27G8KKTZ2BN","sandbox-xlsx","Read and write Excel workbooks in a Code node or CodeAct action, with exceljs running on the host","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-xlsx\u002FSKILL.md",[],[],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":85,"publishedAt":18},"01M7AE57C5APRDF96NB3G0WQMW","sandbox-tfjs","Classify and embed images, detect objects, and answer from a passage in a Code node or CodeAct action, with TensorFlow.js running on the host","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-tfjs\u002FSKILL.md",[],[],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":92,"publishedAt":18},"01M7AE559S35AWH50FJBZ7JNYR","sandbox-supabase","Read and write Supabase tables from a Code node or CodeAct action, with NodeTool's PostgREST request builder running on the host","https:\u002F\u002Fgithub.com\u002Fhanchenhao\u002Fai-canvas\u002Fblob\u002Fmain\u002Fpackages\u002Fsandbox-packs\u002Fsandbox-supabase\u002FSKILL.md",[],[]]