@wirl/mcp 0.2.3 → 0.2.5

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 (2) hide show
  1. package/bin.js +166 -75
  2. package/package.json +1 -1
package/bin.js CHANGED
@@ -2463,7 +2463,7 @@ var init_manifest = __esm({
2463
2463
  };
2464
2464
  CREATABLE_CONNECTION_KINDS = CONNECTION_KINDS.filter((kind) => !(kind in UNDELIVERED_KINDS));
2465
2465
  needsNoKey = (kind) => kind === "none";
2466
- ACCENTS = ["blue", "green", "sun", "purple", "red", "sky", "orange", "lime"];
2466
+ ACCENTS = ["blue", "green", "sun", "purple", "red", "sky", "orange", "lime", "mint", "butter", "blush", "lilac"];
2467
2467
  SCHEDULE_LIMITS = {
2468
2468
  perApp: 10,
2469
2469
  minTimeoutMs: 3e4,
@@ -2923,7 +2923,11 @@ var init_retention = __esm({
2923
2923
  * The counts settle a "why was my app paused" question and the claims
2924
2924
  * ration the mail; neither is worth anything two months on.
2925
2925
  */
2926
- flowDayRowsDays: 60
2926
+ flowDayRowsDays: 60,
2927
+ /** A row of the AI call ledger is kept this long, then removed. */
2928
+ aiCallDays: 90,
2929
+ /** An AI monthly total (the rows the caps read) is kept this long, 24 months. */
2930
+ aiMonthDays: 730
2927
2931
  };
2928
2932
  }
2929
2933
  });
@@ -2956,6 +2960,7 @@ own live deploy and a person's yes. Declare a host under \`connections\`, then \
2956
2960
  wirl attaches the key. An undeclared host is refused; a public API needing no key uses \`"kind": "none"\`.
2957
2961
  Declaring one deploys before anyone approves it; the deploy says per connection whether it is usable now or
2958
2962
  waiting on an admin. A vendor has 60 seconds to answer, whole, unless it streams: ask a model for a stream.
2963
+ Models: \`https://ai.wirl/<vendor>/\u2026\` (openrouter, openai, anthropic, google) with the vendor's own API and no key; OpenRouter works out of the box on wirl's allowance, an org's own key lifts it. See Models.
2959
2964
 
2960
2965
  Six headers say who is asking: \`x-wirl-user-id\`, \`x-wirl-user-email\`, \`x-wirl-user-name\`,
2961
2966
  \`x-wirl-role\`, \`x-wirl-app-id\`, \`x-wirl-org\`. Read the name with
@@ -3008,7 +3013,7 @@ var init_version = __esm({
3008
3013
  "packages/core/src/version.ts"() {
3009
3014
  "use strict";
3010
3015
  WORKSPACE_VERSION = "0.0.0";
3011
- BUILT_VERSION = true ? "0.2.3" : null;
3016
+ BUILT_VERSION = true ? "0.2.5" : null;
3012
3017
  }
3013
3018
  });
3014
3019
 
@@ -5195,11 +5200,12 @@ company email domain; in any other org, including a personal one or one
5195
5200
  made with \`wirl orgs create\`, it starts private. Deploys are one
5196
5201
  command; sharing is by email; a third-party API is declared, never pasted.`;
5197
5202
  var SKILL_DOCS_LINE = "Before you build, fetch https://docs.wirl.dev/llms-full.txt: every page of the docs as one Markdown file, so there is no need to read the site page by page.";
5198
- var SKILL_RULES = `1. **Never ask for or paste an API key.** Declare the connection in \`wirl.json\` and deploy. Needs no key at all (a public API)? Declare it with "kind": "none" and its hosts. If the key is missing, the deploy prints a link to the app's keys page; show the person that link. Locally, keys live in \`.wirl/keys.json\`, which git ignores. To try a vendor call locally without the key, map its connection to a server of your own in \`.wirl/mocks.json\`, like \`{ "anthropic": "http://127.0.0.1:4010" }\`: \`wirl dev\` then sends the calls to that connection's hosts there instead (\`wirl dev --help\` has the rest).
5203
+ var SKILL_RULES = `1. **Never ask for or paste an API key.** Declare the connection in \`wirl.json\` and deploy. Needs no key at all (a public API)? Declare it with "kind": "none" and its hosts. If the key is missing, the deploy prints a link to the app's keys page; show the person that link. Locally, keys live in \`.wirl/keys.json\`, which git ignores. To try a vendor call locally without the key, map its connection to a server of your own in \`.wirl/mocks.json\`, like \`{ "anthropic": "http://127.0.0.1:4010" }\`: \`wirl dev\` then sends the calls to that connection's hosts there instead (\`wirl dev --help\` has the rest). Models need no key either: call https://ai.wirl/<vendor>/\u2026 (see Models).
5199
5204
  2. **Call third-party APIs by their real URL** (\`fetch("https://api.hubspot.com/...")\`). wirl attaches the key on the way out, deployed and under \`wirl dev\` alike.
5200
- 3. If a command names several organisations and asks which, ask the person; pass \`--org\` or put \`"org"\` in \`wirl.json\`.
5201
- 4. To keep a file out of the deploy, list it in a \`.wirlignore\` at the project root (gitignore-style lines; no \`!\` negation).
5202
- 5. **A file that can lose data waits for a person.** A deploy that would apply a destructive file (one wirl.json's \`"destructive"\` list must name) to the live database stops and names the page where a person approves each one. Show the person those pages, and deploy again once they say they approved. If \`${WIRL_BIN} deploy\` asks you to type a file's name, stop it and show the page instead, or deploy with \`--json\`, which never asks. Never type the name yourself or send the confirmation: that is the person's, and no flag skips it.`;
5205
+ 3. **Asked to connect an app to Slack, or to receive events from any outside service:** follow the pattern on https://docs.wirl.dev/models (public path with "verify": "slack", a connection of kind http_bearer for the bot token and one of kind webhook_secret for the signing secret, the Slack app manifest you give the person to create the app with). Never ask for the token or secret in chat: the person pastes them on the app's Connections page, and you say which two values.
5206
+ 4. If a command names several organisations and asks which, ask the person; pass \`--org\` or put \`"org"\` in \`wirl.json\`.
5207
+ 5. To keep a file out of the deploy, list it in a \`.wirlignore\` at the project root (gitignore-style lines; no \`!\` negation).
5208
+ 6. **A file that can lose data waits for a person.** A deploy that would apply a destructive file (one wirl.json's \`"destructive"\` list must name) to the live database stops and names the page where a person approves each one. Show the person those pages, and deploy again once they say they approved. If \`${WIRL_BIN} deploy\` asks you to type a file's name, stop it and show the page instead, or deploy with \`--json\`, which never asks. Never type the name yourself or send the confirmation: that is the person's, and no flag skips it.`;
5203
5209
  var SKILL_MANIFEST_EXAMPLE = `\`\`\`json
5204
5210
  { "app": "crm-sync", "server": "server.mjs", "connections": [{ "slug": "hubspot", "hosts": ["api.hubspot.com"] }] }
5205
5211
  \`\`\``;
@@ -5250,37 +5256,59 @@ ${FLOWS_CONTRACT}
5250
5256
  init_src();
5251
5257
  var SUGGESTIONS = [
5252
5258
  {
5253
- title: "Rename it to something we'd actually use",
5254
- prompt: `Rename this noticeboard to something my team would actually use. Ask me what we would use it for, suggest three names, and when I pick one, change the page's title and heading and the description in wirl.json to match. Keep "app" in wirl.json as it is: a new name there would make a second app at a new address. Then deploy it with wirl deploy.`
5259
+ number: 1,
5260
+ title: "Summarise the week",
5261
+ capability: "A model comes with every app. Your agent just uses it; nothing to sign up for or paste.",
5262
+ prompt: `Add a "Summarise this week" button above the notes. When pressed, send this week's notes to a language model and show a three-line summary at the top of the page. Try it with wirl dev, then deploy it with wirl deploy. Keep the latest summary so everyone who opens the page sees it.`,
5263
+ youGet: "a summary card above the notes."
5255
5264
  },
5256
5265
  {
5257
- title: "Try a new look in a preview first",
5258
- prompt: "Give the noticeboard a warmer look: pinned notes as coloured cards, and tags as small coloured labels. Put it up with wirl preview first and give me the preview's address to check. Deploy it with wirl deploy only after I say it looks right."
5266
+ number: 2,
5267
+ title: "Give notes a due date",
5268
+ capability: "The database is yours. Changing its shape is one migration file and one deploy; wirl applies it before the new version goes live, and the Data page on the dashboard shows the result.",
5269
+ prompt: "Add an optional due date to notes. Add the column in a new migration file with the next number, and never edit a migration that has already been applied. Let people set the date when they post or edit a note, show it on each note, and put notes due soonest at the top. Try it with wirl dev, then deploy it with wirl deploy.",
5270
+ youGet: "a date on each note, the soonest first, and a new column on the Data page."
5259
5271
  },
5260
5272
  {
5261
- title: "Add a due date to notes",
5262
- prompt: "Add an optional due date to notes on the noticeboard. Add the column in a new file, migrations/0002_due_date.sql, and never edit 0001. Let people set the date when they post or edit a note, show it on each note, and put notes due soonest at the top. Try it with wirl dev, then deploy it with wirl deploy."
5273
+ number: 3,
5274
+ title: "Pull in something from outside",
5275
+ capability: "Every call out goes through wirl. The agent declares the host as a connection; a public API needs no key, and one that does gets its key on the dashboard, never in the code.",
5276
+ prompt: `Show today's weather under the notes for our city (ask me which city if you don't know), from a public weather API that needs no key. Declare the API's host as a connection in wirl.json with "kind": "none". Try it with wirl dev, then deploy it with wirl deploy. If the deploy says the connection is waiting on an admin, show me the link it prints.`,
5277
+ youGet: "a weather line under the notes, and the host listed on the app's Connections page."
5263
5278
  },
5264
5279
  {
5265
- title: "Show the weather from a public API",
5266
- prompt: `Show today's weather for our office at the top of the noticeboard, from Open-Meteo, a public weather API that needs no key. Ask me which city our office is in. Add this entry to "connections" in wirl.json: {"slug": "open-meteo", "kind": "none", "hosts": ["api.open-meteo.com"]}. Fetch Open-Meteo's own URL from server.mjs, and deploy it with wirl deploy. If the deploy says open-meteo is waiting, the connection needs approving, which your sign-in cannot do: tell me to approve it on the app's Connections page on app.wirl.dev, or to ask an admin of our organisation to. The weather shows once it is approved.`
5280
+ number: 4,
5281
+ title: "Try a new look before anyone sees it",
5282
+ // The mock says "Previews are free."; the starter carries no price words (its test forbids "free").
5283
+ capability: "Previews are a safe place to try. A preview gets its own address and its own database, so you can look at a change with a colleague before it goes live.",
5284
+ prompt: "Give the noticeboard a calmer look: more space between notes, a softer background, the newest note set apart at the top. Put it up as a preview called new-look with wirl preview and give me the link. Don't deploy it yet. Keep the change on a branch and don't run wirl deploy until I say deploy it.",
5285
+ youGet: 'a second address to open next to this one. Say "deploy it" when you like it.'
5267
5286
  },
5268
5287
  {
5269
- title: "Change when the tidy-up runs",
5270
- prompt: "Change the noticeboard's nightly tidy-up: archive notes after 14 days instead of 30, and run it at 6pm on weekdays in my time zone (ask me which). Change the schedule in wirl.json and the number in server.mjs, deploy it with wirl deploy, then run it once with wirl run tidy and tell me what it said."
5271
- },
5272
- {
5273
- title: "Find out what happened from the logs",
5274
- prompt: "Read this app's logs with wirl logs and tell me, in a few lines, what people did on the noticeboard today and whether anything failed."
5288
+ number: 5,
5289
+ title: "Share it with a colleague",
5290
+ capability: "Company sign-in is already on. Anyone you name opens the link with their work account; the Access page shows who can, and you can take it back any time.",
5291
+ prompt: "Help me share this app with a colleague: tell me how to give them access and the link they open.",
5292
+ youGet: "their name on the Access page, and a link they open with their work account."
5275
5293
  },
5276
5294
  {
5277
- title: "Share it with a colleague",
5278
- prompt: `Help me share this noticeboard with a colleague. Sharing is done by me, not by a deploy: tell me to share it from the app's Access page on app.wirl.dev (the "Who can open it" row on the noticeboard links to it), where I add their email address. Then tell me what they will see when they open the address.`
5295
+ number: 6,
5296
+ title: "Connect it to Slack",
5297
+ capability: "Apps can reach the tools your company already uses. Your agent knows wirl's Slack pattern: it gives you a Slack app to create in one click, you paste two values on the app's Connections page, and the noticeboard starts a DM with you. Ask it about the notes, or tell it to add one, from Slack.",
5298
+ prompt: "Connect this noticeboard to Slack so it DMs me this week's summary and I can chat with it there: ask it about the notes, or tell it to add or change one. Give me the Slack app manifest to create the app with, and tell me which two values to paste on the app's Connections page.",
5299
+ youGet: "a DM from the noticeboard in Slack. Everything above works without it.",
5300
+ optional: true
5279
5301
  }
5280
5302
  ];
5303
+ var splitCapability = (capability) => {
5304
+ const stop = capability.indexOf(". ");
5305
+ return [capability.slice(0, stop + 1), capability.slice(stop + 2)];
5306
+ };
5281
5307
  var SUGGESTIONS_SOURCE = [
5282
5308
  "const SUGGESTIONS = [",
5283
- ...SUGGESTIONS.map(({ title, prompt }) => ` { title: ${JSON.stringify(title)}, prompt: ${JSON.stringify(prompt)} },`),
5309
+ ...SUGGESTIONS.map(
5310
+ ({ number, title, capability, prompt, youGet, optional }) => ` { number: ${String(number)}, title: ${JSON.stringify(title)}, capability: ${JSON.stringify(capability)}, prompt: ${JSON.stringify(prompt)}, youGet: ${JSON.stringify(youGet)}${optional ? ", optional: true" : ""} },`
5311
+ ),
5284
5312
  "];"
5285
5313
  ].join("\n");
5286
5314
  var SERVER_HEAD = String.raw`/**
@@ -5288,12 +5316,13 @@ var SERVER_HEAD = String.raw`/**
5288
5316
  *
5289
5317
  * Your team posts short notes here, tags them, pins the ones everyone should
5290
5318
  * see, and edits or removes their own. Every night a schedule archives old
5291
- * notes nobody pinned. The page also explains itself: a "How this works on
5292
- * wirl" panel, one row per feature, each linking to that page of the app on
5293
- * app.wirl.dev, and a few changes to ask your coding agent for next.
5319
+ * notes nobody pinned. Below the notes, "Make it yours": six prompts to
5320
+ * paste to your coding agent, in order, each saying what wirl gives the app
5321
+ * and what you get. Under them, the app's pages on app.wirl.dev.
5294
5322
  *
5295
5323
  * The whole app is this one file:
5296
- * - The page is HTML this module returns: plain forms, no script.
5324
+ * - The page is HTML this module returns: plain forms, and one small script
5325
+ * for the Copy prompt buttons.
5297
5326
  * - Notes live in env.DB, a SQLite database that belongs to this app alone.
5298
5327
  * Its tables are in migrations/0001_init.sql, and wirl applies each new
5299
5328
  * file in migrations/ before a version goes live. Put the next change in
@@ -5458,16 +5487,30 @@ async function page(request, url, me, env) {
5458
5487
  "<style>" + STYLE + "</style>",
5459
5488
  "<main>",
5460
5489
  "<header><h1>" + TITLE + "</h1>",
5461
- "<p class='dim'>Your first app on wirl. Signed in as <b>" + escape(me.name) + "</b>" + (me.role ? " (" + escape(me.role) + ")" : "") + ".</p></header>",
5490
+ "<p class='dim'>Signed in as <b>" + escape(me.name) + "</b>" + (me.role ? " (" + escape(me.role) + ")" : "") + ".</p></header>",
5491
+ me.role === "owner" ? welcome() : "",
5462
5492
  shown,
5463
5493
  "<ul class='notes'>" + (notes.length === 0 ? "<li class='dim'>Nothing here yet.</li>" : notes.map((note) => card(note, me, archived)).join("")) + "</ul>",
5464
- panel(request, me, lastRun),
5465
- suggestions(),
5494
+ "<p class='dim'>Nightly tidy-up, which archives notes older than " + ARCHIVE_AFTER_DAYS + " days that nobody pinned: " +
5495
+ (lastRun ? "last run " + escape(lastRun.ran_at.slice(0, 16)) + " UTC: " + escape(lastRun.note) : "Not run yet.") + "</p>",
5496
+ prompts(request),
5466
5497
  "</main>",
5498
+ COPY_SCRIPT,
5467
5499
  "</html>",
5468
5500
  ].join("\n");
5469
5501
  }
5470
5502
 
5503
+ // For the app's owner only: what this is, in a box above the note form.
5504
+ function welcome() {
5505
+ return [
5506
+ "<section class='welcome'>",
5507
+ "<p class='kicker'>Your first app on wirl</p>",
5508
+ "<h2>Your coding agent just built this, and it's live.</h2>",
5509
+ "<p>A small team noticeboard: notes, a nightly tidy-up, company sign-in, its own database. It runs at this address, it's private to you until you share it, and it's yours to change. Try out the app below and then customize it.</p>",
5510
+ "</section>",
5511
+ ].join("");
5512
+ }
5513
+
5471
5514
  function form() {
5472
5515
  return [
5473
5516
  "<form class='add' method='post' action='/notes'>",
@@ -5507,50 +5550,58 @@ function card(note, me, archived) {
5507
5550
  );
5508
5551
  }
5509
5552
 
5510
- // The app's page on the dashboard, from the address it was opened at:
5553
+ // The app's pages on the dashboard, from the address it was opened at:
5511
5554
  // <org>--<app>.wirl.run, or <org>--<app>--<preview>.wirl.run for a preview.
5512
5555
  // Under wirl dev the address is 127.0.0.1, which names no app: null.
5513
5556
  function dashboard(request) {
5514
5557
  const label = new URL(request.url).hostname.split(".")[0];
5515
5558
  const parts = label.split("--");
5516
5559
  if (parts.length < 2) return null;
5517
- const org = request.headers.get("x-wirl-org") || parts[0];
5518
- return DASHBOARD + "/apps/" + encodeURIComponent(org) + "/" + encodeURIComponent(parts[1]);
5560
+ const org = encodeURIComponent(request.headers.get("x-wirl-org") || parts[0]);
5561
+ return { app: DASHBOARD + "/apps/" + org + "/" + encodeURIComponent(parts[1]), org: DASHBOARD + "/orgs/" + org };
5519
5562
  }
5520
5563
 
5521
- function panel(request, me, lastRun) {
5522
- const base = dashboard(request);
5523
- const last = lastRun ? "Last run " + lastRun.ran_at.slice(0, 16) + " UTC: " + lastRun.note : "Not run yet.";
5524
- // One row per feature: what it is, what it does in this app, and its page.
5525
- const rows = [
5526
- ["Who can open it", "wirl signed you in before this page loaded and tells the app who you are, in headers: here you are " + me.name + (me.role ? ", " + me.role : "") + ". The app has no sign-in code of its own. It started private to you; choose who else can open it on Access.", "/access", "Access"],
5527
- ["Your data", "Notes are rows in this app's own SQLite database. Its tables are in migrations/0001_init.sql, and a deploy applies each new file in migrations/ before it goes live.", "/deployments", "Deployments"],
5528
- ["A schedule", "tidy runs every night at 02:00 UTC and archives notes older than " + ARCHIVE_AFTER_DAYS + " days that nobody pinned. " + last + " Run it now from Overview.", "", "Overview"],
5529
- ["Logs", "Each request writes a line with console.log, and so does each change to a note. Read them on Logs, or ask your agent to run wirl logs.", "/logs", "Logs"],
5530
- ["Versions", "Each wirl deploy is a new version, and the earlier ones stay: going back to one takes a click.", "/deployments", "Deployments"],
5531
- ["Previews", "wirl preview puts a change up at an address of its own, with its own database, and nothing goes live until you deploy. Open previews are listed on Overview.", "", "Overview"],
5532
- ["Outside APIs", "Declare a connection in wirl.json and fetch the service's own URL: wirl adds the key on the way out, so it never reaches this code or a chat.", "/connections", "Connections"],
5533
- ["Settings", "Describe the app for your team's list of tools, hand it to someone else, or delete it.", "/settings", "Settings"],
5564
+ // The onboarding path: each prompt whole, visible and ready to select and
5565
+ // copy, with what wirl gives the app and what you get. Then what the app
5566
+ // already uses, and its pages on the dashboard.
5567
+ function prompts(request) {
5568
+ const where = dashboard(request);
5569
+ const pages = [
5570
+ ["Overview", "app", ""],
5571
+ ["Data", "app", "/data"],
5572
+ ["Access", "app", "/access"],
5573
+ ["Logs", "app", "/logs"],
5574
+ ["Deployments", "app", "/deployments"],
5575
+ ["Models", "org", "/models"],
5576
+ ["Connections", "app", "/connections"],
5577
+ ["Settings", "app", "/settings"],
5534
5578
  ];
5579
+ const link = ([label, of, path]) =>
5580
+ where === null ? "<span class='dim'>" + label + "</span>" : "<a href='" + escape(where[of] + path) + "'>" + label + "</a>";
5581
+ const step = (each) => {
5582
+ const stop = each.capability.indexOf(". ");
5583
+ return (
5584
+ "<li class='step" + (each.number === 1 ? " first" : "") + (each.optional ? " optional" : "") + "'>" +
5585
+ "<h3><span class='number'>" + each.number + "</span> " + escape(each.title) +
5586
+ (each.number === 1 ? " <span class='badge start'>Start here</span>" : "") +
5587
+ (each.optional ? " <span class='badge'>Optional. The big one.</span>" : "") + "</h3>" +
5588
+ "<p class='dim'><b>" + escape(each.capability.slice(0, stop + 1)) + "</b> " + escape(each.capability.slice(stop + 2)) + "</p>" +
5589
+ "<div class='getrow'>" +
5590
+ "<button type='button' class='copy' data-copy-for='prompt-" + each.number + "'>Copy prompt</button>" +
5591
+ "<p class='dim'>You get: " + escape(each.youGet) + "</p></div>" +
5592
+ "<details class='prompt'><summary>Show the prompt</summary>" +
5593
+ "<pre id='prompt-" + each.number + "'>" + escape(each.prompt) + "</pre></details>" +
5594
+ "</li>"
5595
+ );
5596
+ };
5535
5597
  return [
5536
- "<section><h2>How this works on wirl</h2>",
5537
- "<table>",
5538
- ...rows.map(([feature, what, path, label]) =>
5539
- "<tr><td>" + feature + "</td><td>" + escape(what) + "</td><td>" +
5540
- (base === null ? "<span class='dim'>" + label + "</span>" : "<a href='" + escape(base + path) + "'>" + label + "</a>") +
5541
- "</td></tr>",
5542
- ),
5543
- "</table>",
5544
- base === null ? "<p class='dim'>Running under wirl dev, so these pages are not linked here: they open on app.wirl.dev once it is deployed.</p>" : "",
5545
- "</section>",
5546
- ].join("\n");
5547
- }
5548
-
5549
- function suggestions() {
5550
- return [
5551
- "<section><h2>Ask your agent for a first change</h2>",
5552
- "<p class='dim'>Each of these is a whole prompt. Open one, copy it, and paste it to the coding agent that made this app.</p>",
5553
- ...SUGGESTIONS.map((each) => "<details><summary>" + escape(each.title) + "</summary><pre>" + escape(each.prompt) + "</pre></details>"),
5598
+ "<section id='prompts'><h2>Make it yours</h2>",
5599
+ "<p>This app is live, at a link your team can open. Everything below is a whole prompt. Copy one, paste it to the coding agent that made this app, and the feature appears here on the next deploy. Go in order or skip around; the last one is the big one.</p>",
5600
+ "<ol class='steps'>",
5601
+ ...SUGGESTIONS.map(step),
5602
+ "</ol>",
5603
+ "<p class='dim uses'>This app already uses a database, a nightly schedule, company sign-in and logs. Each has a page on the dashboard: " + pages.map(link).join(" · ") + "</p>",
5604
+ where === null ? "<p class='dim'>Running under wirl dev, so these pages are not linked here: they open on app.wirl.dev once it is deployed.</p>" : "",
5554
5605
  "</section>",
5555
5606
  ].join("\n");
5556
5607
  }
@@ -5567,6 +5618,7 @@ const STYLE = [
5567
5618
  "a { color: var(--accent); } .dim { color: var(--dim); }",
5568
5619
  "form.add, form.edit { display: grid; gap: 8px; margin: 16px 0 8px; }",
5569
5620
  "textarea, input, button { font: inherit; color: inherit; padding: 7px 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--card); }",
5621
+ "button.copy { min-height: 44px; margin-top: 4px; }",
5570
5622
  "button { cursor: pointer; } button.primary { justify-self: start; background: var(--accent); border-color: var(--accent); color: var(--bg); }",
5571
5623
  "ul.notes { list-style: none; padding: 0; display: grid; gap: 10px; }",
5572
5624
  ".note { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; }",
@@ -5575,13 +5627,49 @@ const STYLE = [
5575
5627
  ".meta, .actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 13px; }",
5576
5628
  ".actions { margin-top: 8px; } .actions form { margin: 0; } .actions details { flex-basis: 100%; order: 3; }",
5577
5629
  ".tag { text-decoration: none; }",
5578
- "table { width: 100%; border-collapse: collapse; font-size: 14px; }",
5579
- "td { border-top: 1px solid var(--line); padding: 8px 6px; vertical-align: top; }",
5580
- "td:first-child { font-weight: 600; }",
5581
- "pre { white-space: pre-wrap; background: var(--card); border: 1px solid var(--line); border-radius: 6px; padding: 10px; font-size: 13px; }",
5630
+ "ol.steps { list-style: none; padding: 0; display: grid; gap: 12px; }",
5631
+ ".step { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; }",
5632
+ ".step.optional { border: 2px solid var(--accent); }",
5633
+ ".step h3 { font-size: 16px; margin: 0 0 6px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }",
5634
+ ".step p { margin: 6px 0; } .step b { color: var(--accent); }",
5635
+ ".number { font-size: 12px; background: var(--ink); color: var(--bg); padding: 2px 7px; border-radius: 5px; }",
5636
+ ".optional .number { background: var(--accent); }",
5637
+ ".badge { font-size: 12px; font-weight: 600; color: var(--dim); border: 1px solid var(--line); padding: 1px 8px; border-radius: 999px; }",
5638
+ ".step.first { border: 2px solid var(--accent); }",
5639
+ ".badge.start { color: var(--accent); border-color: var(--accent); }",
5640
+ ".welcome { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 14px 16px; margin: 16px 0 8px; }",
5641
+ ".welcome h2 { font-size: 19px; margin: 4px 0 8px; } .welcome p { margin: 0; }",
5642
+ ".welcome .kicker { font-size: 12px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--accent); }",
5643
+ ".getrow { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; } .getrow p { margin: 0; }",
5644
+ "details.prompt summary { display: flex; align-items: center; min-height: 44px; color: var(--dim); font-size: 14px; cursor: pointer; }",
5645
+ ".uses { border-top: 1px solid var(--line); padding-top: 12px; font-size: 14px; }",
5646
+ "pre { white-space: pre-wrap; user-select: all; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 10px; font-size: 13px; }",
5582
5647
  "details { margin: 6px 0; } summary { cursor: pointer; }",
5583
5648
  ].join("\n");
5584
5649
 
5650
+ const COPY_SCRIPT = [
5651
+ "<script>",
5652
+ "document.addEventListener('click', (event) => {",
5653
+ " const button = event.target.closest('.copy');",
5654
+ " if (!button) return;",
5655
+ " const pre = document.getElementById(button.getAttribute('data-copy-for'));",
5656
+ " const flash = (label) => {",
5657
+ " button.textContent = label;",
5658
+ " setTimeout(() => { button.textContent = 'Copy prompt'; }, 1500);",
5659
+ " };",
5660
+ " const select = () => {",
5661
+ " const range = document.createRange();",
5662
+ " range.selectNodeContents(pre);",
5663
+ " window.getSelection().removeAllRanges();",
5664
+ " window.getSelection().addRange(range);",
5665
+ " flash('Select and copy');",
5666
+ " };",
5667
+ " if (!navigator.clipboard) return select();",
5668
+ " navigator.clipboard.writeText(pre.textContent).then(() => flash('Copied'), select);",
5669
+ "});",
5670
+ "</script>",
5671
+ ].join("\n");
5672
+
5585
5673
  const home = (to = "/") => new Response(null, { status: 303, headers: { location: to } });
5586
5674
 
5587
5675
  const text = (body, status = 200) => new Response(body, { status, headers: { "content-type": "text/plain; charset=utf-8" } });
@@ -5619,7 +5707,7 @@ var STARTER_MIGRATION = [
5619
5707
  "-- Make the next change in 0002_<what>.sql; never edit a file that has been deployed.",
5620
5708
  "create table notes (id integer primary key, body text not null, tags text not null default '', pinned integer not null default 0, author_id text not null, author_name text not null default '', created_at text not null default (datetime('now')), archived_at text)",
5621
5709
  "create table runs (id integer primary key, schedule text not null, note text not null, ran_at text not null default (datetime('now')))",
5622
- "insert into notes (body, tags, pinned, author_id, author_name) values ('Welcome to the noticeboard. Post a note below, tag it, and pin the ones everyone should see.', 'welcome', 1, '', 'wirl')",
5710
+ "insert into notes (body, tags, pinned, author_id, author_name) values ('Welcome to the noticeboard. This note is pinned so everyone sees it first; post your own below.', 'welcome', 1, '', 'wirl')",
5623
5711
  ""
5624
5712
  ].join("\n");
5625
5713
  function starterManifest(app, org) {
@@ -5640,7 +5728,7 @@ var README = [
5640
5728
  "",
5641
5729
  "Your first app on wirl: a board where your team posts short notes, tags them, pins the ones everyone should see, and edits or removes their own. Every night a schedule archives old notes nobody pinned.",
5642
5730
  "",
5643
- 'Open it at the address `wirl deploy` prints. It starts private to you. Its page has a "How this works on wirl" panel: one row per thing wirl does for this app, each linking to that page of the app on app.wirl.dev.',
5731
+ "Open it at the address `wirl deploy` prints. It starts private to you. Its page is the way in: six prompts to give your coding agent, each saying what wirl gives the app and what you get, and under them the app's pages on app.wirl.dev.",
5644
5732
  "",
5645
5733
  "## What is where",
5646
5734
  "",
@@ -5655,11 +5743,14 @@ var README = [
5655
5743
  "",
5656
5744
  "It prints the address to open, and a `curl` line that runs the nightly tidy-up now.",
5657
5745
  "",
5658
- "## Changes to ask your agent for",
5746
+ "## Make it yours",
5659
5747
  "",
5660
- "Each is a whole prompt: paste it to your coding agent as it is.",
5748
+ "The same six prompts as the page. Each is a whole prompt: paste it to your coding agent as it is. Go in order or skip around; the last one is the big one.",
5661
5749
  "",
5662
- ...SUGGESTIONS.flatMap(({ title, prompt }) => [`### ${title}`, "", `> ${prompt}`, ""])
5750
+ ...SUGGESTIONS.flatMap(({ number, title, capability, prompt, youGet, optional }) => {
5751
+ const [bold, rest] = splitCapability(capability);
5752
+ return [`### ${String(number)}. ${title}`, "", ...optional ? ["Optional. The big one.", ""] : [], `**${bold}** ${rest}`, "", `> ${prompt}`, "", `You get: ${youGet}`, ""];
5753
+ })
5663
5754
  ].join("\n");
5664
5755
  var STARTER_FILES = {
5665
5756
  "server.mjs": SERVER_MODULE,
@@ -6274,7 +6365,7 @@ async function callApiBytes(creds, path, options = {}) {
6274
6365
  body = {};
6275
6366
  }
6276
6367
  if (response.status === 401 || response.status === 403) {
6277
- throw new CliError("Not signed in, or this token cannot do that.", `Try \`${WIRL_BIN} login\`.`);
6368
+ throw new CliError("Not signed in, or this token cannot do that.", `Try \`${WIRL_BIN} login\`.`, { code: "unauthenticated" });
6278
6369
  }
6279
6370
  if (response.status === 404) throw new CliError("Not found, or you do not have access to it.");
6280
6371
  throw new CliError(body.message ?? body.error ?? `Request failed (${response.status}).`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirl/mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "MCP server for wirl: let your coding agent deploy what it just built, privately.",
5
5
  "keywords": [
6
6
  "mcp",