[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D0BX1RCRAVAVGSTFJJC09Y":3,"skill-same:01M6D0BX1RCRAVAVGSTFJJC09Y":101},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":52,"linkOnly":53,"markdownContent":54,"markdownBody":55,"frontMatter":56,"refName":80,"commitSha":81,"skillDirectory":82,"forksCount":83,"repositoryHtmlUrl":84,"repositoryHomepageUrl":85,"ownerHtmlUrl":86,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":87,"install":88,"resources":100},"01M6D0BX1RCRAVAVGSTFJJC09Y","making_pages",null,"Templates and rules for interactive pages beside the chat: plans with approvals, checklists, dashboards, comparisons, forms and small tools.","siddsachar\u002Frow-bot","siddsachar","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F50956096?v=4","01M6D0AG8BTX8VNFY5N3F2Q36T","Siddharth Sachar","01M6D0AGD3C7BZ6NAW9PWVKM3A",1686,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Fbundled_skills\u002Fmaking_pages\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000003","演示文稿","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"making pages","build","launch plan","for","presentation","make","form","step by step","plans","pages","chat","options","me","forms","bot","tracker","landing","interactive","Rules","page","slides","plan","dashboards","Tools","website","launch","web","dashboard","2026-11-10 10:12:30",false,"---\nname: making_pages\ndisplay_name: Making a page\nicon: \"?️\"\ndescription: \"Templates and rules for interactive pages beside the chat: plans with approvals, checklists, dashboards, comparisons, forms and small tools.\"\nenabled_by_default: true\nversion: \"1.0\"\ntags:\n  - pages\n  - plans\n  - dashboards\nactivation:\n  phrases:\n    - make a page\n    - plan i can approve\n    - make a checklist\n    - launch tracker\n    - build a dashboard\n    - compare the options\n    - make a form\n    - interactive page\n  keywords:\n    - checklist\n    - tracker\n    - dashboard\n    - interactive\n  negative_phrases:\n    - landing page\n    - website\n    - web page\n    - slides\n    - presentation\n  examples:\n    - Make me a launch plan I can approve step by step\nauthor: Row-Bot\n---\n\n# Making a page\n\nA page is one HTML document the person uses beside the chat. Make one with\n`page_create(title, html, data, libraries)`; keep it current with\n`page_update(page, data=…)`; see what they ticked or decided with\n`page_read(page)`.\n\n## Rules\n\n- **No network.** Nothing loads from the internet: no CDN scripts, fonts,\n  images or fetch. Inline all CSS and JS. Images only as `data:` URLs.\n- **Data in `data`, not in the HTML.** The page reads it with\n  `rowbot.data()`. Progress and fresh numbers go in with\n  `page_update(page, data=…)`, so the HTML stays the same.\n- **Use `libraries: [\"rowbot-style\"]`** so the page looks like Row-Bot in\n  light and dark. Add `\"charts\"` for Chart.js 4 (`window.Chart`).\n- **Plain words.** Short sentences, sentence case, no ids or codes on screen.\n- **Phone width.** It must work at 360 px: no fixed widths, tables in\n  `.rb-table-wrap`, buttons that wrap.\n- **One primary action per page,** at most. The person's Send to Row-Bot is\n  outside the page, under it.\n\n## The bridge: `window.rowbot`\n\n| Call | What it does |\n| --- | --- |\n| `rowbot.data()` | The JSON you attached (a copy). |\n| `rowbot.state.get()` | The page's saved object (`{}` at first). |\n| `rowbot.state.set(obj)` | Saves the whole object (up to 256 KB) with the chat, on every device. |\n| `rowbot.decide(id, choice, note?, label?)` | Records the person's choice on one item; `choice` `null` clears it. `label` is the item's name in words. |\n| `rowbot.decided()` | What the person chose, by id: `{choice, note, label, sent}`. |\n| `rowbot.onDecided(fn)` | Called when a choice changes, or once they are sent. |\n| `rowbot.theme()` | `{mode: 'light' \\| 'dark', tokens}`; tokens hold `chart-series-1…6`, `chart-grid`, `chart-axis`, `text-muted`… |\n| `rowbot.onTheme(fn)` | Called when the person switches theme. |\n\nChoices only gather: under the page the person sees how many and \"Send to\nRow-Bot\". Their click sends you one message listing them, with a JSON list.\nChoices you'll see in words: `approve` → Approved, `change` → Change,\n`skip` → Skipped, `pick` → Picked, `refresh` → Refresh, `submit` → Filled in;\nanything else as written. They never approve a tool call.\n\n## rowbot-style\n\nColours: `var(--rb-canvas)`, `--rb-surface`, `--rb-text-primary`,\n`--rb-text-secondary`, `--rb-text-muted`, `--rb-border-subtle`,\n`--rb-accent-solid`, `--rb-status-{info,success,warning,danger}-{text,background}`,\n`--rb-chart-series-1…6`. Classes: `rb-header`, `rb-stack`, `rb-row`,\n`rb-grid`, `rb-card`, `rb-stat` (`rb-stat-value`, `rb-stat-label`),\n`rb-badge` with `data-tone=\"success|info|warning|danger\"`, `rb-progress`\n(a `\u003Cspan>` inside, width in %), `rb-list`, `rb-table-wrap`, `rb-number`,\n`rb-muted`, `rb-primary` (a button), `rb-quiet`. Buttons with\n`aria-pressed=\"true\"` show as chosen.\n\n## Templates\n\n### A plan with approvals (the person approves, changes or skips each step)\n\ndata: `{\"title\", \"summary\", \"steps\": [{\"id\", \"title\", \"detail\", \"status\": \"todo\" | \"doing\" | \"done\"}]}`.\nAs you work, set each step's `status` with `page_update(page, data=…)`.\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003Cstyle>\n  .step { display: grid; gap: 8px; }\n  .step-head { display: flex; gap: 8px; align-items: flex-start; justify-content: space-between; }\n  .step h3 { margin: 0; }\n  .step p { margin: 0; color: var(--rb-text-secondary); }\n  .choices { display: flex; flex-wrap: wrap; gap: 8px; }\n  .why { display: none; flex-direction: column; gap: 8px; }\n  .step[data-editing] .why { display: flex; }\n  .step[data-status=\"done\"] h3 { color: var(--rb-text-muted); }\n  #steps { list-style: none; padding: 0; margin: 16px 0 0; }\n\u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n\u003Cheader class=\"rb-header\">\u003Ch1 id=\"title\">\u003C\u002Fh1>\u003Cspan class=\"rb-badge\" id=\"count\">\u003C\u002Fspan>\u003C\u002Fheader>\n\u003Cp id=\"summary\" class=\"rb-muted\">\u003C\u002Fp>\n\u003Cdiv class=\"rb-progress\" aria-hidden=\"true\">\u003Cspan id=\"bar\">\u003C\u002Fspan>\u003C\u002Fdiv>\n\u003Col id=\"steps\" class=\"rb-stack\">\u003C\u002Fol>\n\u003Cscript>\nconst plan = rowbot.data() || { steps: [] };\nconst STATUS = { done: ['Done', 'success'], doing: ['In progress', 'info'], todo: ['To do', ''] };\nconst SAID = { approve: 'Approved', change: 'Change', skip: 'Skipped' };\nconst editing = new Set();\nconst el = (tag, props, ...kids) => { const n = Object.assign(document.createElement(tag), props); n.append(...kids); return n; };\nfunction render() {\n  const chosen = rowbot.decided();\n  const done = plan.steps.filter((s) => s.status === 'done').length;\n  title.textContent = plan.title || '';\n  summary.textContent = plan.summary || '';\n  count.textContent = `${done} of ${plan.steps.length} done`;\n  bar.style.width = `${plan.steps.length ? (100 * done) \u002F plan.steps.length : 0}%`;\n  steps.replaceChildren(...plan.steps.map((step) => {\n    const mine = chosen[step.id];\n    const [words, tone] = STATUS[step.status] || STATUS.todo;\n    const card = el('li', { className: 'rb-card step' });\n    card.dataset.status = step.status || 'todo';\n    if (editing.has(step.id)) card.dataset.editing = '';\n    const badge = el('span', { className: 'rb-badge', textContent: words });\n    if (tone) badge.dataset.tone = tone;\n    card.append(el('div', { className: 'step-head' }, el('h3', { textContent: step.title }), badge));\n    if (step.detail) card.append(el('p', { textContent: step.detail }));\n    if (step.status === 'done') return card;\n    const choices = el('div', { className: 'choices', role: 'group' });\n    choices.setAttribute('aria-label', `Your choice on ${step.title}`);\n    for (const [choice, label] of [['approve', 'Approve'], ['change', 'Change'], ['skip', 'Skip']]) {\n      const button = el('button', { type: 'button', textContent: label, onclick: () => choose(step, choice) });\n      button.setAttribute('aria-pressed', String(mine?.choice === choice));\n      choices.append(button);\n    }\n    const why = el('textarea', { placeholder: 'What should change?', value: mine?.choice === 'change' ? mine.note : '' });\n    why.setAttribute('aria-label', `What should change in ${step.title}`);\n    const save = el('button', { type: 'button', className: 'rb-primary', textContent: 'Save change' });\n    save.onclick = () => { editing.delete(step.id); rowbot.decide(step.id, 'change', why.value.trim(), step.title); };\n    card.append(choices, el('div', { className: 'why' }, why, el('div', { className: 'choices' }, save)));\n    if (mine && !editing.has(step.id))\n      card.append(el('small', { textContent: `${SAID[mine.choice] || mine.choice}${mine.note ? `: “${mine.note}”` : ''}${mine.sent ? ' · sent' : ''}` }));\n    return card;\n  }));\n}\nfunction choose(step, choice) {\n  const current = rowbot.decided()[step.id];\n  if (choice === 'change') {\n    editing.add(step.id);\n    render();\n    steps.querySelector('[data-editing] textarea')?.focus();\n    return;\n  }\n  editing.delete(step.id);\n  if (current?.choice === choice && !current.sent) rowbot.decide(step.id, null);\n  else rowbot.decide(step.id, choice, '', step.title);\n}\nrowbot.onDecided(render);\nrender();\n\u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n### A checklist or launch tracker (remembers what's ticked, on every device)\n\ndata: `{\"title\", \"sections\": [{\"title\", \"items\": [{\"id\", \"text\"}]}]}`. Ticks live in state.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Cheader class=\"rb-header\">\u003Ch1 id=\"title\">\u003C\u002Fh1>\u003Cspan class=\"rb-badge\" id=\"count\">\u003C\u002Fspan>\u003C\u002Fheader>\n\u003Cdiv class=\"rb-progress\" aria-hidden=\"true\">\u003Cspan id=\"bar\">\u003C\u002Fspan>\u003C\u002Fdiv>\n\u003Cdiv id=\"sections\" class=\"rb-stack\" style=\"margin-top:16px\">\u003C\u002Fdiv>\n\u003Cscript>\nconst list = rowbot.data() || { sections: [] };\nconst ticked = rowbot.state.get().ticked || {};\nconst items = list.sections.flatMap((s) => s.items);\nfunction render() {\n  const done = items.filter((i) => ticked[i.id]).length;\n  title.textContent = list.title || '';\n  count.textContent = `${done} of ${items.length} done`;\n  bar.style.width = `${items.length ? (100 * done) \u002F items.length : 0}%`;\n  sections.replaceChildren(...list.sections.map((section) => {\n    const ul = document.createElement('ul'); ul.className = 'rb-list';\n    for (const item of section.items) {\n      const box = Object.assign(document.createElement('input'), { type: 'checkbox', checked: Boolean(ticked[item.id]) });\n      box.onchange = () => { ticked[item.id] = box.checked; rowbot.state.set({ ticked }); render(); };\n      const label = document.createElement('label'); label.append(box, item.text);\n      const li = document.createElement('li'); li.append(label); ul.append(li);\n    }\n    const h2 = Object.assign(document.createElement('h2'), { textContent: section.title });\n    const wrap = document.createElement('section'); wrap.append(h2, ul); return wrap;\n  }));\n}\nrender();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A dashboard (charts, a table with sort and filter, Refresh)\n\ndata: `{\"title\", \"updated\", \"metrics\": [{\"label\", \"value\"}], \"chart\": {\"labels\": [], \"series\": [{\"label\", \"values\": []}], \"stacked\": true}, \"columns\": [{\"key\", \"label\", \"number\": true}], \"rows\": [{}]}`.\n`stacked` shows parts of one total (online and shops) as one bar; leave it out to compare series side by side.\nCheck the numbers agree: the metrics, the chart and the table come from the same data.\nUse `libraries: [\"charts\", \"rowbot-style\"]`. Refresh records a choice; when\nit arrives, gather the numbers again and `page_update(page, data=…)`.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003Cstyle>.chart { position: relative; height: 240px; } th button { cursor: pointer; }\u003C\u002Fstyle>\u003C\u002Fhead>\n\u003Cbody>\n\u003Cheader class=\"rb-header\">\u003Cdiv>\u003Ch1 id=\"title\">\u003C\u002Fh1>\u003Csmall id=\"updated\">\u003C\u002Fsmall>\u003C\u002Fdiv>\n  \u003Cbutton type=\"button\" id=\"refresh\">Refresh\u003C\u002Fbutton>\u003C\u002Fheader>\n\u003Cdiv id=\"metrics\" class=\"rb-grid\">\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-card\" style=\"margin:16px 0\">\u003Cdiv class=\"chart\">\u003Ccanvas id=\"chart\" role=\"img\">\u003C\u002Fcanvas>\u003C\u002Fdiv>\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-row\" style=\"margin-bottom:8px\">\u003Cinput id=\"filter\" type=\"search\" placeholder=\"Filter\" aria-label=\"Filter rows\">\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-table-wrap\">\u003Ctable>\u003Cthead>\u003Ctr id=\"head\">\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody id=\"body\">\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cscript>\nconst d = rowbot.data();\nlet sortKey = null, sortUp = true, chart;\ntitle.textContent = d.title; updated.textContent = d.updated ? `Updated ${d.updated}` : '';\nmetrics.replaceChildren(...d.metrics.map((m) => { const c = document.createElement('div'); c.className = 'rb-card rb-stat';\n  c.innerHTML = '\u003Cspan class=\"rb-stat-value\">\u003C\u002Fspan>\u003Cspan class=\"rb-stat-label\">\u003C\u002Fspan>';\n  c.children[0].textContent = m.value; c.children[1].textContent = m.label; return c; }));\nfunction drawChart() {\n  const t = rowbot.theme().tokens;\n  chart?.destroy();\n  chart = new Chart(document.getElementById('chart'), { type: 'bar',\n    data: { labels: d.chart.labels, datasets: d.chart.series.map((s, i) => ({ label: s.label, data: s.values,\n      backgroundColor: t[`chart-series-${(i % 6) + 1}`], borderRadius: 4 })) },\n    options: { maintainAspectRatio: false, animation: false, plugins: { legend: { labels: { color: t['text-muted'] } } },\n      scales: { x: { stacked: Boolean(d.chart.stacked), ticks: { color: t['chart-axis'], autoSkip: true, maxRotation: 0 }, grid: { display: false } },\n                y: { stacked: Boolean(d.chart.stacked), ticks: { color: t['chart-axis'] }, grid: { color: t['border-subtle'] } } } } });\n  document.getElementById('chart').setAttribute('aria-label', d.chart.series.map((s) => s.label).join(', '));\n}\nfunction drawTable() {\n  head.replaceChildren(...d.columns.map((c) => { const th = document.createElement('th'); if (c.number) th.className = 'rb-number';\n    const b = Object.assign(document.createElement('button'), { type: 'button', textContent: c.label + (sortKey === c.key ? (sortUp ? ' ↑' : ' ↓') : '') });\n    b.onclick = () => { sortUp = sortKey === c.key ? !sortUp : true; sortKey = c.key; drawTable(); }; th.append(b); return th; }));\n  const q = filter.value.trim().toLowerCase();\n  const rows = d.rows.filter((r) => !q || Object.values(r).join(' ').toLowerCase().includes(q));\n  if (sortKey) rows.sort((a, b) => (a[sortKey] > b[sortKey] ? 1 : -1) * (sortUp ? 1 : -1));\n  body.replaceChildren(...rows.map((r) => { const tr = document.createElement('tr');\n    for (const c of d.columns) { const td = document.createElement('td'); const v = r[c.key];\n      td.textContent = typeof v === 'number' ? v.toLocaleString() : v; if (c.number) td.className = 'rb-number'; tr.append(td); }\n    return tr; }));\n}\nfilter.oninput = drawTable;\nrefresh.onclick = () => { rowbot.decide('refresh', 'refresh', '', `Refresh ${d.title}`); refresh.textContent = 'Refresh asked'; };\nrowbot.onTheme(drawChart);\ndrawChart(); drawTable();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A comparison (options side by side, pick one)\n\ndata: `{\"question\", \"options\": [{\"id\", \"name\", \"summary\", \"pros\": [], \"cons\": []}]}`.\nPicking records `rowbot.decide('pick', 'pick', why, option.name)`.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Ch1 id=\"question\">\u003C\u002Fh1>\n\u003Cdiv id=\"options\" class=\"rb-grid\">\u003C\u002Fdiv>\n\u003Clabel style=\"margin-top:16px;width:100%\">Anything to add?\u003Ctextarea id=\"why\">\u003C\u002Ftextarea>\u003C\u002Flabel>\n\u003Cscript>\nconst d = rowbot.data();\nquestion.textContent = d.question;\nwhy.value = rowbot.decided().pick?.note || '';\nfunction render() {\n  const picked = rowbot.decided().pick;\n  options.replaceChildren(...d.options.map((o) => {\n    const card = document.createElement('div'); card.className = 'rb-card rb-stack';\n    const h = Object.assign(document.createElement('h2'), { textContent: o.name });\n    const p = Object.assign(document.createElement('p'), { textContent: o.summary });\n    const list = (words, items) => { const ul = document.createElement('ul');\n      for (const i of items) ul.append(Object.assign(document.createElement('li'), { textContent: `${words} ${i}` })); return ul; };\n    const choose = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Choose this' });\n    choose.setAttribute('aria-pressed', String(picked?.label === o.name));\n    choose.onclick = () => rowbot.decide('pick', 'pick', why.value.trim(), o.name);\n    card.append(h, p, list('+', o.pros), list('−', o.cons), choose); return card;\n  }));\n}\nwhy.onchange = () => { const p = rowbot.decided().pick; if (p) rowbot.decide('pick', 'pick', why.value.trim(), p.label); };\nrowbot.onDecided(render); render();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A form (collect details, send them in one go)\n\ndata: `{\"title\", \"fields\": [{\"id\", \"label\", \"type\": \"text\" | \"date\" | \"number\" | \"textarea\" | \"select\", \"options\": []}]}`.\nThe draft lives in state; Done records one choice with every answer as\n`Label: value` lines.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Ch1 id=\"title\">\u003C\u002Fh1>\n\u003Cdiv id=\"fields\" class=\"rb-stack\">\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-row\" style=\"margin-top:16px\">\u003Cbutton type=\"button\" class=\"rb-primary\" id=\"done\">Done\u003C\u002Fbutton>\u003Csmall id=\"said\">\u003C\u002Fsmall>\u003C\u002Fdiv>\n\u003Cscript>\nconst d = rowbot.data();\nconst draft = rowbot.state.get().draft || {};\ntitle.textContent = d.title;\nfields.replaceChildren(...d.fields.map((f) => {\n  const input = f.type === 'textarea' ? document.createElement('textarea')\n    : f.type === 'select' ? document.createElement('select') : Object.assign(document.createElement('input'), { type: f.type || 'text' });\n  for (const o of f.options || []) input.append(Object.assign(document.createElement('option'), { textContent: o }));\n  input.value = draft[f.id] || '';\n  input.oninput = () => { draft[f.id] = input.value; rowbot.state.set({ draft }); };\n  const label = document.createElement('label'); label.append(f.label, input); return label;\n}));\ndone.onclick = () => {\n  const lines = d.fields.map((f) => `${f.label}: ${draft[f.id] || '—'}`).join('\\n');\n  rowbot.decide('form', 'submit', lines, d.title);\n  said.textContent = 'Ready to send to Row-Bot.';\n};\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A calculator or small tool (runs entirely in the page)\n\nNothing to send; keep inputs in state so they're there next time.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Ch1>Price per seat\u003C\u002Fh1>\n\u003Cdiv class=\"rb-grid\">\n  \u003Clabel>Seats\u003Cinput id=\"seats\" type=\"number\" min=\"1\">\u003C\u002Flabel>\n  \u003Clabel>Price per seat (£)\u003Cinput id=\"price\" type=\"number\" min=\"0\" step=\"0.01\">\u003C\u002Flabel>\n  \u003Clabel>Discount (%)\u003Cinput id=\"discount\" type=\"number\" min=\"0\" max=\"100\">\u003C\u002Flabel>\n\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-card rb-stat\" style=\"margin-top:16px\">\u003Cspan class=\"rb-stat-value\" id=\"total\">\u003C\u002Fspan>\u003Cspan class=\"rb-stat-label\">Total a month\u003C\u002Fspan>\u003C\u002Fdiv>\n\u003Cscript>\nconst saved = Object.assign({ seats: 10, price: 12, discount: 0 }, rowbot.state.get());\nfor (const id of ['seats', 'price', 'discount']) {\n  const input = document.getElementById(id); input.value = saved[id];\n  input.oninput = () => { saved[id] = Number(input.value) || 0; rowbot.state.set(saved); render(); };\n}\nfunction render() {\n  const total = saved.seats * saved.price * (1 - saved.discount \u002F 100);\n  document.getElementById('total').textContent = total.toLocaleString(undefined, { style: 'currency', currency: 'GBP' });\n}\nrender();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n","# Making a page\n\nA page is one HTML document the person uses beside the chat. Make one with\n`page_create(title, html, data, libraries)`; keep it current with\n`page_update(page, data=…)`; see what they ticked or decided with\n`page_read(page)`.\n\n## Rules\n\n- **No network.** Nothing loads from the internet: no CDN scripts, fonts,\n  images or fetch. Inline all CSS and JS. Images only as `data:` URLs.\n- **Data in `data`, not in the HTML.** The page reads it with\n  `rowbot.data()`. Progress and fresh numbers go in with\n  `page_update(page, data=…)`, so the HTML stays the same.\n- **Use `libraries: [\"rowbot-style\"]`** so the page looks like Row-Bot in\n  light and dark. Add `\"charts\"` for Chart.js 4 (`window.Chart`).\n- **Plain words.** Short sentences, sentence case, no ids or codes on screen.\n- **Phone width.** It must work at 360 px: no fixed widths, tables in\n  `.rb-table-wrap`, buttons that wrap.\n- **One primary action per page,** at most. The person's Send to Row-Bot is\n  outside the page, under it.\n\n## The bridge: `window.rowbot`\n\n| Call | What it does |\n| --- | --- |\n| `rowbot.data()` | The JSON you attached (a copy). |\n| `rowbot.state.get()` | The page's saved object (`{}` at first). |\n| `rowbot.state.set(obj)` | Saves the whole object (up to 256 KB) with the chat, on every device. |\n| `rowbot.decide(id, choice, note?, label?)` | Records the person's choice on one item; `choice` `null` clears it. `label` is the item's name in words. |\n| `rowbot.decided()` | What the person chose, by id: `{choice, note, label, sent}`. |\n| `rowbot.onDecided(fn)` | Called when a choice changes, or once they are sent. |\n| `rowbot.theme()` | `{mode: 'light' \\| 'dark', tokens}`; tokens hold `chart-series-1…6`, `chart-grid`, `chart-axis`, `text-muted`… |\n| `rowbot.onTheme(fn)` | Called when the person switches theme. |\n\nChoices only gather: under the page the person sees how many and \"Send to\nRow-Bot\". Their click sends you one message listing them, with a JSON list.\nChoices you'll see in words: `approve` → Approved, `change` → Change,\n`skip` → Skipped, `pick` → Picked, `refresh` → Refresh, `submit` → Filled in;\nanything else as written. They never approve a tool call.\n\n## rowbot-style\n\nColours: `var(--rb-canvas)`, `--rb-surface`, `--rb-text-primary`,\n`--rb-text-secondary`, `--rb-text-muted`, `--rb-border-subtle`,\n`--rb-accent-solid`, `--rb-status-{info,success,warning,danger}-{text,background}`,\n`--rb-chart-series-1…6`. Classes: `rb-header`, `rb-stack`, `rb-row`,\n`rb-grid`, `rb-card`, `rb-stat` (`rb-stat-value`, `rb-stat-label`),\n`rb-badge` with `data-tone=\"success|info|warning|danger\"`, `rb-progress`\n(a `\u003Cspan>` inside, width in %), `rb-list`, `rb-table-wrap`, `rb-number`,\n`rb-muted`, `rb-primary` (a button), `rb-quiet`. Buttons with\n`aria-pressed=\"true\"` show as chosen.\n\n## Templates\n\n### A plan with approvals (the person approves, changes or skips each step)\n\ndata: `{\"title\", \"summary\", \"steps\": [{\"id\", \"title\", \"detail\", \"status\": \"todo\" | \"doing\" | \"done\"}]}`.\nAs you work, set each step's `status` with `page_update(page, data=…)`.\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003Cstyle>\n  .step { display: grid; gap: 8px; }\n  .step-head { display: flex; gap: 8px; align-items: flex-start; justify-content: space-between; }\n  .step h3 { margin: 0; }\n  .step p { margin: 0; color: var(--rb-text-secondary); }\n  .choices { display: flex; flex-wrap: wrap; gap: 8px; }\n  .why { display: none; flex-direction: column; gap: 8px; }\n  .step[data-editing] .why { display: flex; }\n  .step[data-status=\"done\"] h3 { color: var(--rb-text-muted); }\n  #steps { list-style: none; padding: 0; margin: 16px 0 0; }\n\u003C\u002Fstyle>\n\u003C\u002Fhead>\n\u003Cbody>\n\u003Cheader class=\"rb-header\">\u003Ch1 id=\"title\">\u003C\u002Fh1>\u003Cspan class=\"rb-badge\" id=\"count\">\u003C\u002Fspan>\u003C\u002Fheader>\n\u003Cp id=\"summary\" class=\"rb-muted\">\u003C\u002Fp>\n\u003Cdiv class=\"rb-progress\" aria-hidden=\"true\">\u003Cspan id=\"bar\">\u003C\u002Fspan>\u003C\u002Fdiv>\n\u003Col id=\"steps\" class=\"rb-stack\">\u003C\u002Fol>\n\u003Cscript>\nconst plan = rowbot.data() || { steps: [] };\nconst STATUS = { done: ['Done', 'success'], doing: ['In progress', 'info'], todo: ['To do', ''] };\nconst SAID = { approve: 'Approved', change: 'Change', skip: 'Skipped' };\nconst editing = new Set();\nconst el = (tag, props, ...kids) => { const n = Object.assign(document.createElement(tag), props); n.append(...kids); return n; };\nfunction render() {\n  const chosen = rowbot.decided();\n  const done = plan.steps.filter((s) => s.status === 'done').length;\n  title.textContent = plan.title || '';\n  summary.textContent = plan.summary || '';\n  count.textContent = `${done} of ${plan.steps.length} done`;\n  bar.style.width = `${plan.steps.length ? (100 * done) \u002F plan.steps.length : 0}%`;\n  steps.replaceChildren(...plan.steps.map((step) => {\n    const mine = chosen[step.id];\n    const [words, tone] = STATUS[step.status] || STATUS.todo;\n    const card = el('li', { className: 'rb-card step' });\n    card.dataset.status = step.status || 'todo';\n    if (editing.has(step.id)) card.dataset.editing = '';\n    const badge = el('span', { className: 'rb-badge', textContent: words });\n    if (tone) badge.dataset.tone = tone;\n    card.append(el('div', { className: 'step-head' }, el('h3', { textContent: step.title }), badge));\n    if (step.detail) card.append(el('p', { textContent: step.detail }));\n    if (step.status === 'done') return card;\n    const choices = el('div', { className: 'choices', role: 'group' });\n    choices.setAttribute('aria-label', `Your choice on ${step.title}`);\n    for (const [choice, label] of [['approve', 'Approve'], ['change', 'Change'], ['skip', 'Skip']]) {\n      const button = el('button', { type: 'button', textContent: label, onclick: () => choose(step, choice) });\n      button.setAttribute('aria-pressed', String(mine?.choice === choice));\n      choices.append(button);\n    }\n    const why = el('textarea', { placeholder: 'What should change?', value: mine?.choice === 'change' ? mine.note : '' });\n    why.setAttribute('aria-label', `What should change in ${step.title}`);\n    const save = el('button', { type: 'button', className: 'rb-primary', textContent: 'Save change' });\n    save.onclick = () => { editing.delete(step.id); rowbot.decide(step.id, 'change', why.value.trim(), step.title); };\n    card.append(choices, el('div', { className: 'why' }, why, el('div', { className: 'choices' }, save)));\n    if (mine && !editing.has(step.id))\n      card.append(el('small', { textContent: `${SAID[mine.choice] || mine.choice}${mine.note ? `: “${mine.note}”` : ''}${mine.sent ? ' · sent' : ''}` }));\n    return card;\n  }));\n}\nfunction choose(step, choice) {\n  const current = rowbot.decided()[step.id];\n  if (choice === 'change') {\n    editing.add(step.id);\n    render();\n    steps.querySelector('[data-editing] textarea')?.focus();\n    return;\n  }\n  editing.delete(step.id);\n  if (current?.choice === choice && !current.sent) rowbot.decide(step.id, null);\n  else rowbot.decide(step.id, choice, '', step.title);\n}\nrowbot.onDecided(render);\nrender();\n\u003C\u002Fscript>\n\u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n### A checklist or launch tracker (remembers what's ticked, on every device)\n\ndata: `{\"title\", \"sections\": [{\"title\", \"items\": [{\"id\", \"text\"}]}]}`. Ticks live in state.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Cheader class=\"rb-header\">\u003Ch1 id=\"title\">\u003C\u002Fh1>\u003Cspan class=\"rb-badge\" id=\"count\">\u003C\u002Fspan>\u003C\u002Fheader>\n\u003Cdiv class=\"rb-progress\" aria-hidden=\"true\">\u003Cspan id=\"bar\">\u003C\u002Fspan>\u003C\u002Fdiv>\n\u003Cdiv id=\"sections\" class=\"rb-stack\" style=\"margin-top:16px\">\u003C\u002Fdiv>\n\u003Cscript>\nconst list = rowbot.data() || { sections: [] };\nconst ticked = rowbot.state.get().ticked || {};\nconst items = list.sections.flatMap((s) => s.items);\nfunction render() {\n  const done = items.filter((i) => ticked[i.id]).length;\n  title.textContent = list.title || '';\n  count.textContent = `${done} of ${items.length} done`;\n  bar.style.width = `${items.length ? (100 * done) \u002F items.length : 0}%`;\n  sections.replaceChildren(...list.sections.map((section) => {\n    const ul = document.createElement('ul'); ul.className = 'rb-list';\n    for (const item of section.items) {\n      const box = Object.assign(document.createElement('input'), { type: 'checkbox', checked: Boolean(ticked[item.id]) });\n      box.onchange = () => { ticked[item.id] = box.checked; rowbot.state.set({ ticked }); render(); };\n      const label = document.createElement('label'); label.append(box, item.text);\n      const li = document.createElement('li'); li.append(label); ul.append(li);\n    }\n    const h2 = Object.assign(document.createElement('h2'), { textContent: section.title });\n    const wrap = document.createElement('section'); wrap.append(h2, ul); return wrap;\n  }));\n}\nrender();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A dashboard (charts, a table with sort and filter, Refresh)\n\ndata: `{\"title\", \"updated\", \"metrics\": [{\"label\", \"value\"}], \"chart\": {\"labels\": [], \"series\": [{\"label\", \"values\": []}], \"stacked\": true}, \"columns\": [{\"key\", \"label\", \"number\": true}], \"rows\": [{}]}`.\n`stacked` shows parts of one total (online and shops) as one bar; leave it out to compare series side by side.\nCheck the numbers agree: the metrics, the chart and the table come from the same data.\nUse `libraries: [\"charts\", \"rowbot-style\"]`. Refresh records a choice; when\nit arrives, gather the numbers again and `page_update(page, data=…)`.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\u003Cstyle>.chart { position: relative; height: 240px; } th button { cursor: pointer; }\u003C\u002Fstyle>\u003C\u002Fhead>\n\u003Cbody>\n\u003Cheader class=\"rb-header\">\u003Cdiv>\u003Ch1 id=\"title\">\u003C\u002Fh1>\u003Csmall id=\"updated\">\u003C\u002Fsmall>\u003C\u002Fdiv>\n  \u003Cbutton type=\"button\" id=\"refresh\">Refresh\u003C\u002Fbutton>\u003C\u002Fheader>\n\u003Cdiv id=\"metrics\" class=\"rb-grid\">\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-card\" style=\"margin:16px 0\">\u003Cdiv class=\"chart\">\u003Ccanvas id=\"chart\" role=\"img\">\u003C\u002Fcanvas>\u003C\u002Fdiv>\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-row\" style=\"margin-bottom:8px\">\u003Cinput id=\"filter\" type=\"search\" placeholder=\"Filter\" aria-label=\"Filter rows\">\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-table-wrap\">\u003Ctable>\u003Cthead>\u003Ctr id=\"head\">\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody id=\"body\">\u003C\u002Ftbody>\u003C\u002Ftable>\u003C\u002Fdiv>\n\u003Cscript>\nconst d = rowbot.data();\nlet sortKey = null, sortUp = true, chart;\ntitle.textContent = d.title; updated.textContent = d.updated ? `Updated ${d.updated}` : '';\nmetrics.replaceChildren(...d.metrics.map((m) => { const c = document.createElement('div'); c.className = 'rb-card rb-stat';\n  c.innerHTML = '\u003Cspan class=\"rb-stat-value\">\u003C\u002Fspan>\u003Cspan class=\"rb-stat-label\">\u003C\u002Fspan>';\n  c.children[0].textContent = m.value; c.children[1].textContent = m.label; return c; }));\nfunction drawChart() {\n  const t = rowbot.theme().tokens;\n  chart?.destroy();\n  chart = new Chart(document.getElementById('chart'), { type: 'bar',\n    data: { labels: d.chart.labels, datasets: d.chart.series.map((s, i) => ({ label: s.label, data: s.values,\n      backgroundColor: t[`chart-series-${(i % 6) + 1}`], borderRadius: 4 })) },\n    options: { maintainAspectRatio: false, animation: false, plugins: { legend: { labels: { color: t['text-muted'] } } },\n      scales: { x: { stacked: Boolean(d.chart.stacked), ticks: { color: t['chart-axis'], autoSkip: true, maxRotation: 0 }, grid: { display: false } },\n                y: { stacked: Boolean(d.chart.stacked), ticks: { color: t['chart-axis'] }, grid: { color: t['border-subtle'] } } } } });\n  document.getElementById('chart').setAttribute('aria-label', d.chart.series.map((s) => s.label).join(', '));\n}\nfunction drawTable() {\n  head.replaceChildren(...d.columns.map((c) => { const th = document.createElement('th'); if (c.number) th.className = 'rb-number';\n    const b = Object.assign(document.createElement('button'), { type: 'button', textContent: c.label + (sortKey === c.key ? (sortUp ? ' ↑' : ' ↓') : '') });\n    b.onclick = () => { sortUp = sortKey === c.key ? !sortUp : true; sortKey = c.key; drawTable(); }; th.append(b); return th; }));\n  const q = filter.value.trim().toLowerCase();\n  const rows = d.rows.filter((r) => !q || Object.values(r).join(' ').toLowerCase().includes(q));\n  if (sortKey) rows.sort((a, b) => (a[sortKey] > b[sortKey] ? 1 : -1) * (sortUp ? 1 : -1));\n  body.replaceChildren(...rows.map((r) => { const tr = document.createElement('tr');\n    for (const c of d.columns) { const td = document.createElement('td'); const v = r[c.key];\n      td.textContent = typeof v === 'number' ? v.toLocaleString() : v; if (c.number) td.className = 'rb-number'; tr.append(td); }\n    return tr; }));\n}\nfilter.oninput = drawTable;\nrefresh.onclick = () => { rowbot.decide('refresh', 'refresh', '', `Refresh ${d.title}`); refresh.textContent = 'Refresh asked'; };\nrowbot.onTheme(drawChart);\ndrawChart(); drawTable();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A comparison (options side by side, pick one)\n\ndata: `{\"question\", \"options\": [{\"id\", \"name\", \"summary\", \"pros\": [], \"cons\": []}]}`.\nPicking records `rowbot.decide('pick', 'pick', why, option.name)`.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Ch1 id=\"question\">\u003C\u002Fh1>\n\u003Cdiv id=\"options\" class=\"rb-grid\">\u003C\u002Fdiv>\n\u003Clabel style=\"margin-top:16px;width:100%\">Anything to add?\u003Ctextarea id=\"why\">\u003C\u002Ftextarea>\u003C\u002Flabel>\n\u003Cscript>\nconst d = rowbot.data();\nquestion.textContent = d.question;\nwhy.value = rowbot.decided().pick?.note || '';\nfunction render() {\n  const picked = rowbot.decided().pick;\n  options.replaceChildren(...d.options.map((o) => {\n    const card = document.createElement('div'); card.className = 'rb-card rb-stack';\n    const h = Object.assign(document.createElement('h2'), { textContent: o.name });\n    const p = Object.assign(document.createElement('p'), { textContent: o.summary });\n    const list = (words, items) => { const ul = document.createElement('ul');\n      for (const i of items) ul.append(Object.assign(document.createElement('li'), { textContent: `${words} ${i}` })); return ul; };\n    const choose = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Choose this' });\n    choose.setAttribute('aria-pressed', String(picked?.label === o.name));\n    choose.onclick = () => rowbot.decide('pick', 'pick', why.value.trim(), o.name);\n    card.append(h, p, list('+', o.pros), list('−', o.cons), choose); return card;\n  }));\n}\nwhy.onchange = () => { const p = rowbot.decided().pick; if (p) rowbot.decide('pick', 'pick', why.value.trim(), p.label); };\nrowbot.onDecided(render); render();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A form (collect details, send them in one go)\n\ndata: `{\"title\", \"fields\": [{\"id\", \"label\", \"type\": \"text\" | \"date\" | \"number\" | \"textarea\" | \"select\", \"options\": []}]}`.\nThe draft lives in state; Done records one choice with every answer as\n`Label: value` lines.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Ch1 id=\"title\">\u003C\u002Fh1>\n\u003Cdiv id=\"fields\" class=\"rb-stack\">\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-row\" style=\"margin-top:16px\">\u003Cbutton type=\"button\" class=\"rb-primary\" id=\"done\">Done\u003C\u002Fbutton>\u003Csmall id=\"said\">\u003C\u002Fsmall>\u003C\u002Fdiv>\n\u003Cscript>\nconst d = rowbot.data();\nconst draft = rowbot.state.get().draft || {};\ntitle.textContent = d.title;\nfields.replaceChildren(...d.fields.map((f) => {\n  const input = f.type === 'textarea' ? document.createElement('textarea')\n    : f.type === 'select' ? document.createElement('select') : Object.assign(document.createElement('input'), { type: f.type || 'text' });\n  for (const o of f.options || []) input.append(Object.assign(document.createElement('option'), { textContent: o }));\n  input.value = draft[f.id] || '';\n  input.oninput = () => { draft[f.id] = input.value; rowbot.state.set({ draft }); };\n  const label = document.createElement('label'); label.append(f.label, input); return label;\n}));\ndone.onclick = () => {\n  const lines = d.fields.map((f) => `${f.label}: ${draft[f.id] || '—'}`).join('\\n');\n  rowbot.decide('form', 'submit', lines, d.title);\n  said.textContent = 'Ready to send to Row-Bot.';\n};\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n\n### A calculator or small tool (runs entirely in the page)\n\nNothing to send; keep inputs in state so they're there next time.\n\n```html\n\u003C!doctype html>\u003Chtml lang=\"en\">\u003Chead>\u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\u003C\u002Fhead>\n\u003Cbody>\n\u003Ch1>Price per seat\u003C\u002Fh1>\n\u003Cdiv class=\"rb-grid\">\n  \u003Clabel>Seats\u003Cinput id=\"seats\" type=\"number\" min=\"1\">\u003C\u002Flabel>\n  \u003Clabel>Price per seat (£)\u003Cinput id=\"price\" type=\"number\" min=\"0\" step=\"0.01\">\u003C\u002Flabel>\n  \u003Clabel>Discount (%)\u003Cinput id=\"discount\" type=\"number\" min=\"0\" max=\"100\">\u003C\u002Flabel>\n\u003C\u002Fdiv>\n\u003Cdiv class=\"rb-card rb-stat\" style=\"margin-top:16px\">\u003Cspan class=\"rb-stat-value\" id=\"total\">\u003C\u002Fspan>\u003Cspan class=\"rb-stat-label\">Total a month\u003C\u002Fspan>\u003C\u002Fdiv>\n\u003Cscript>\nconst saved = Object.assign({ seats: 10, price: 12, discount: 0 }, rowbot.state.get());\nfor (const id of ['seats', 'price', 'discount']) {\n  const input = document.getElementById(id); input.value = saved[id];\n  input.oninput = () => { saved[id] = Number(input.value) || 0; rowbot.state.set(saved); render(); };\n}\nfunction render() {\n  const total = saved.seats * saved.price * (1 - saved.discount \u002F 100);\n  document.getElementById('total').textContent = total.toLocaleString(undefined, { style: 'currency', currency: 'GBP' });\n}\nrender();\n\u003C\u002Fscript>\u003C\u002Fbody>\u003C\u002Fhtml>\n```\n",{"icon":57,"name":5,"tags":58,"author":59,"version":60,"activation":61,"description":7,"display_name":78,"enabled_by_default":79},"?️",[33,32,46],"Row-Bot","1.0",{"phrases":62,"examples":71,"keywords":73,"negative_phrases":75},[63,64,65,66,67,68,69,70],"make a page","plan i can approve","make a checklist","launch tracker","build a dashboard","compare the options","make a form","interactive page",[72],"Make me a launch plan I can approve step by step",[74,39,51,41],"checklist",[76,48,77,44,28],"landing page","web page","Making a page",true,"main","aea3219498f535052aba82436fc9612f548a086e","bundled_skills\u002Fmaking_pages",204,"https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot","https:\u002F\u002Frow-bot.ai","https:\u002F\u002Fgithub.com\u002Fsiddsachar","2026-11-12 03:30:19",{"source":8,"skillName":5,"commands":89,"prompt":99,"zipAvailable":79},[90,93,96],{"tool":91,"command":92},"npx","npx skills add siddsachar\u002Frow-bot --skill making_pages",{"tool":94,"command":95},"bunx","bunx skills add siddsachar\u002Frow-bot --skill making_pages",{"tool":97,"command":98},"pnpm","pnpm dlx skills add siddsachar\u002Frow-bot --skill making_pages","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Fbundled_skills\u002Fmaking_pages\u002FSKILL.md",[],[102,121,144,155,165,180],{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":52},"01M6D0EZEJNX7R2J8EXN3386BW","x_guide","Essential guidance for using X (Twitter) tools correctly — search syntax, time filtering, and engagement patterns.","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Ftool_guides\u002Fx_guide\u002FSKILL.md",[],[109,110,27,38,111,47,112,113,114,115,116,117,118,119,120],"x guide","guide","search","syntax","twitter","filtering","engagement","guidance","patterns","social","time","essential",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":130,"publishedAt":52},"01M6D0EWESTPTF4B1JZFVFQ49Y","workflow_guide","How workflows run (scheduled chats), how to write and change them, hand work between them, and check what a run did.","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Ftool_guides\u002Fworkflow_guide\u002FSKILL.md",[127],{"categoryNo":128,"categoryName":129,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[131,110,132,133,134,135,136,137,138,139,140,141,142,143],"workflow guide","workflow","write","task","how to","hand","scheduled","check","workflows","change","run","how","work",{"skillNo":145,"name":146,"nameZh":6,"description":147,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":148,"categories":149,"keywords":150,"publishedAt":52},"01M6D0ESS92MEV9V5EAHJ3N6VR","wiki_guide","Guidance for the Obsidian-compatible wiki vault.","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Ftool_guides\u002Fwiki_guide\u002FSKILL.md",[],[151,110,152,153,154,116,27],"wiki guide","wiki","obsidian","vault",{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":161,"publishedAt":52},"01M6D0EQDFABHZ27HMDKYSX6V4","weather_guide","Guidance for using weather tools effectively.","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Ftool_guides\u002Fweather_guide\u002FSKILL.md",[],[162,110,163,47,164,116,27],"weather guide","weather","weather tools",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":52},"01M6D0EMVZ8MZHAVSTMMTMBWN0","vision_guide","Guidance for using webcam, screen capture, and image analysis.","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Ftool_guides\u002Fvision_guide\u002FSKILL.md",[],[172,173,110,116,174,175,176,27,177,178,179],"vision guide","vision","analysis","screen","image analysis","capture","screen capture","image",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":184,"categories":185,"keywords":189,"publishedAt":52},"01M6D0EJF8QMGWNNKD38BK24G8","video_guide","Guidance for generating videos and animating images.","https:\u002F\u002Fgithub.com\u002Fsiddsachar\u002Frow-bot\u002Fblob\u002Fmain\u002Ftool_guides\u002Fvideo_guide\u002FSKILL.md",[186],{"categoryNo":187,"categoryName":188,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",[110,190,191,116,192,27,193],"Video","video guide","images","videos"]