@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.
- package/bin.js +166 -75
- 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.
|
|
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.
|
|
5201
|
-
4.
|
|
5202
|
-
5.
|
|
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
|
-
|
|
5254
|
-
|
|
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
|
-
|
|
5258
|
-
|
|
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
|
-
|
|
5262
|
-
|
|
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
|
-
|
|
5266
|
-
|
|
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
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
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
|
-
|
|
5278
|
-
|
|
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(
|
|
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.
|
|
5292
|
-
*
|
|
5293
|
-
*
|
|
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,
|
|
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'>
|
|
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
|
-
|
|
5465
|
-
|
|
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
|
|
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/" +
|
|
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
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
const
|
|
5526
|
-
|
|
5527
|
-
["
|
|
5528
|
-
["
|
|
5529
|
-
["
|
|
5530
|
-
["
|
|
5531
|
-
["
|
|
5532
|
-
["
|
|
5533
|
-
["
|
|
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>
|
|
5537
|
-
"<
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
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
|
-
"
|
|
5579
|
-
"
|
|
5580
|
-
"
|
|
5581
|
-
"
|
|
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.
|
|
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
|
-
|
|
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
|
-
"##
|
|
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 }) =>
|
|
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}).`);
|