@mccaskie-labs/workbench-config 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.
@@ -1,4 +1,4 @@
1
- export declare const MANIFEST_VERSION: "0a5f38e30870";
1
+ export declare const MANIFEST_VERSION: "b74fc43c59b7";
2
2
  export declare const capabilities: {
3
3
  readonly ai: {
4
4
  readonly $schema: "./schema.json";
@@ -194,10 +194,152 @@ export declare const capabilities: {
194
194
  readonly expect: "pass";
195
195
  readonly cost_hint_usd: 0.008;
196
196
  }];
197
+ readonly offer: {
198
+ readonly pattern: "feature_or_content";
199
+ readonly when: "The client wants the app to write, summarise, sort, answer questions or pull details out of text with AI, as a feature visitors use, or wants wording for the site written once.";
200
+ readonly say: "I'll do this with Workbench AI, which uses Claude through Workbench, so the app holds no AI key and spending has a monthly cap.";
201
+ readonly answers_file: "workbench/ai/<name>.json";
202
+ readonly per: "feature or piece of wording";
203
+ readonly answers_format: "For a feature: `{ \"format\": 1, \"route\": \"feature\", \"name\", \"about\" (the client's words), \"who\", \"cap_usd\", \"keep\", \"job\" }`, where `job` is the `X-Workbench-Job` the feature's calls send (`<name>` with dots, such as `quotes.estimate`), so its spending shows on its own line in Control. Write it, then build the feature with the package. For wording made once: `npx workbench-ai pick <name> <round>-<draft>` writes it from the draft the client picked, with the round's request ids and cost; never write one from nothing. Then put the wording into the strings catalogue (the workbench-strings skill), keyed for where it is shown. Drafts and their previews stay in `workbench/ai/drafts/`, which isn't committed.";
204
+ readonly questions: readonly [{
205
+ readonly id: "ai.route";
206
+ readonly sets: "route";
207
+ readonly ask: "Is this something visitors use on the site, or something made once for the site?";
208
+ readonly style: "cards";
209
+ readonly answers: readonly [{
210
+ readonly label: "Visitors use it";
211
+ readonly value: "feature";
212
+ readonly means: "A feature on the site that calls the AI each time someone uses it, such as a question box or a summary of what they paste in.";
213
+ }, {
214
+ readonly label: "Made once for the site";
215
+ readonly value: "content";
216
+ readonly means: "Wording for a page, written once and kept: an About page, a welcome, product descriptions.";
217
+ }];
218
+ readonly must_ask: true;
219
+ readonly note: "Ask it even when the client's words seem to say, as a quick check: the two routes are built and paid for differently.";
220
+ }, {
221
+ readonly id: "ai.who";
222
+ readonly sets: "feature.who";
223
+ readonly ask: "Who may use it?";
224
+ readonly style: "cards";
225
+ readonly answers: readonly [{
226
+ readonly label: "People who are signed in";
227
+ readonly value: "signed_in";
228
+ readonly means: "Anyone with an account. Each use is tied to a person, so misuse can be traced and stopped.";
229
+ }, {
230
+ readonly label: "Admins only";
231
+ readonly value: "admins";
232
+ readonly means: "Only the business's own admins, for example a tool for writing their own posts.";
233
+ }, {
234
+ readonly label: "Anyone visiting";
235
+ readonly value: "anyone";
236
+ readonly means: "No sign-in. The page checks for bots (Turnstile) and limits how often one visitor can use it.";
237
+ }];
238
+ readonly when: {
239
+ readonly id: "ai.route";
240
+ readonly is: readonly ["feature"];
241
+ };
242
+ readonly note: "Signed in means `requireUser` and admins means `app_admins`, as the workbench-sign-in skill says. For anyone, check Turnstile on every use and keep a per-visitor limit; say plainly that strangers can run up the cost. Pass `subject` on every call so the Gateway can tell people apart: `user:<id>` for a signed-in person, and for anyone a random id kept in a cookie (`visitor:<id>`), never an email address or name.";
243
+ }, {
244
+ readonly id: "ai.cap";
245
+ readonly sets: "feature.cap_usd";
246
+ readonly ask: "How much may it spend in a month, at most? When it's reached, the feature stops until next month.";
247
+ readonly style: "cards";
248
+ readonly answers: readonly [{
249
+ readonly label: "US$5 a month";
250
+ readonly value: 5;
251
+ readonly means: "A small trial, or light use.";
252
+ }, {
253
+ readonly label: "US$20 a month";
254
+ readonly value: 20;
255
+ readonly means: "Steady use by a small business's visitors.";
256
+ }, {
257
+ readonly label: "US$50 a month";
258
+ readonly value: 50;
259
+ readonly means: "Heavy use. It can be raised later.";
260
+ }];
261
+ readonly other: {
262
+ readonly kind: "money";
263
+ readonly min: 1;
264
+ readonly max: 1000;
265
+ readonly description: "US dollars a month, whole or with cents.";
266
+ };
267
+ readonly must_ask: true;
268
+ readonly when: {
269
+ readonly id: "ai.route";
270
+ readonly is: readonly ["feature"];
271
+ };
272
+ readonly note: "Say roughly what one use costs (usually a cent or two with claude-default) so the owner can judge. The cap is the project's production cap for the `ai` tool in the Gateway, shared by every feature that uses it: set it with Workbench Control's `gw_budget_set` (env production, tool ai, monthly_usd). Raising a cap waits for the owner's approval on Control's approval page. When it is reached, calls fail with `budget_exceeded`: the page says the feature is resting until next month, never an error.";
273
+ }, {
274
+ readonly id: "ai.sensitive";
275
+ readonly sets: "feature.keep";
276
+ readonly ask: "Will people type in sensitive personal details, such as their health, religion, money troubles or someone else's private information?";
277
+ readonly style: "cards";
278
+ readonly answers: readonly [{
279
+ readonly label: "No";
280
+ readonly value: "no";
281
+ readonly means: "Workbench keeps what each call sent and got back, so a problem can be traced.";
282
+ }, {
283
+ readonly label: "Yes";
284
+ readonly value: "yes";
285
+ readonly means: "This feature's calls are marked not to be kept: Workbench records only that each call happened and what it cost.";
286
+ }];
287
+ readonly must_ask: true;
288
+ readonly when: {
289
+ readonly id: "ai.route";
290
+ readonly is: readonly ["feature"];
291
+ };
292
+ readonly note: "With yes, pass `keep: false` on this feature's calls. If most of the app handles such details, suggest switching keeping off for the whole project instead (Workbench Control's `gw_keep_set`, env production, on false). Record it as `keep` (true or false) in the answers file.";
293
+ }, {
294
+ readonly id: "ai.what";
295
+ readonly sets: readonly ["content.name", "content.about"];
296
+ readonly ask: "What wording do you need?";
297
+ readonly style: "cards";
298
+ readonly answers: readonly [{
299
+ readonly label: "A page's main text";
300
+ readonly value: "page_text";
301
+ readonly means: "Such as an About page or a services page.";
302
+ }, {
303
+ readonly label: "A short blurb";
304
+ readonly value: "blurb";
305
+ readonly means: "A line or two for the top of a page or a card.";
306
+ }, {
307
+ readonly label: "Descriptions";
308
+ readonly value: "descriptions";
309
+ readonly means: "For products or services.";
310
+ }];
311
+ readonly other: {
312
+ readonly kind: "text";
313
+ readonly min: 3;
314
+ readonly max: 200;
315
+ readonly description: "What the wording is, in a few words.";
316
+ };
317
+ readonly when: {
318
+ readonly id: "ai.route";
319
+ readonly is: readonly ["content"];
320
+ };
321
+ readonly note: "Name the piece after where it goes (about_us, home_blurb): lower case and underscores. The client's own words, with any facts they give (when they started, where they are), go in --about, because the drafts never invent facts and leave [gaps] for the owner instead.";
322
+ }];
323
+ readonly not_asked: readonly [{
324
+ readonly sets: "model";
325
+ readonly why: "Features use claude-default unless the work needs more (claude-best) or speed matters more than depth (claude-fast). Choose from what the feature does; the client can't judge models.";
326
+ }, {
327
+ readonly sets: "voice";
328
+ readonly why: "The tone comes from the project's voice and spelling, set once for the whole site.";
329
+ }];
330
+ readonly drafts: {
331
+ readonly of: "wording";
332
+ readonly surface: "ai";
333
+ readonly alias: "claude-default";
334
+ readonly count: 3;
335
+ readonly max_rounds: 3;
336
+ readonly job: "ai.drafts";
337
+ readonly command: "npx workbench-ai drafts text <name> --about \"<what it is for, with the client's facts>\"";
338
+ };
339
+ };
197
340
  readonly delivery: {
198
341
  readonly choice: "built_in";
199
342
  readonly intro: "Writing, summarising and answering questions with Claude, with a monthly spending cap.";
200
- readonly when: "The client wants the app to write, summarise, sort, answer questions or pull details out of text with AI, as a feature visitors use or as something made once for the site.";
201
343
  };
202
344
  };
203
345
  readonly analytics: {
@@ -271,10 +413,45 @@ export declare const capabilities: {
271
413
  readonly cost_hint_usd: 0;
272
414
  readonly note: "Create the site, drive page views, query them back (cf_analytics_query, later)";
273
415
  }];
416
+ readonly offer: {
417
+ readonly pattern: "switch_on";
418
+ readonly when: "The client wants to know how many people visit, which pages they open, or to count an action such as a sign-up or an order.";
419
+ readonly say: "The project already has Workbench visitor counts: page views counted by Cloudflare without cookies, and the actions you choose counted in the app's own database. I'll switch on what you want.";
420
+ readonly answers_file: "workbench/analytics.json";
421
+ readonly answers_format: "`{ \"format\": 1, \"page_views\": true, \"events\": [{ \"name\": \"order.placed\", \"when\": \"a customer sends an order\", \"details\": [\"items\", \"total_band\"] }] }`. For page views, create the Web Analytics site with Workbench Control (`cf_web_analytics_site_create`), write its beacon token onto staging and production (`cf_worker_secret_put source=web_analytics`), and put `beacon(env)` on every page customers see. For each event, call `eventsFor(db).track(name, details)` where the action happens.";
422
+ readonly questions: readonly [{
423
+ readonly id: "analytics.page-views";
424
+ readonly sets: "page_views";
425
+ readonly ask: "Should the site count its page views?";
426
+ readonly style: "cards";
427
+ readonly answers: readonly [{
428
+ readonly label: "Yes";
429
+ readonly value: true;
430
+ readonly means: "How many people visit and which pages they open, counted by Cloudflare. No cookies, and no personal details kept.";
431
+ }, {
432
+ readonly label: "No";
433
+ readonly value: false;
434
+ readonly means: "Nothing is counted on the site's pages.";
435
+ }];
436
+ readonly note: "Only pages customers see; never the owner's or admins' pages.";
437
+ }, {
438
+ readonly id: "analytics.actions";
439
+ readonly sets: "events";
440
+ readonly ask: "Which actions should be counted?";
441
+ readonly style: "table";
442
+ readonly note: "Propose the few actions that show whether the site is working for the business (a sign-up, an order, a booking, a form sent), each with a name (`thing.past_verb`, lower case: `order.placed`), when it happens in the client's words, and its details. Details are counts, choices and bands (`items`, `plan`, `total_band`), never an email address, a name, a phone number or free text someone typed. The client accepts, removes or adds; an empty list is fine.";
443
+ }];
444
+ readonly not_asked: readonly [{
445
+ readonly sets: "consent";
446
+ readonly why: "The page-view counter sets no cookies and keeps no personal details, and events hold none either, so they add nothing that a cookie notice would need to cover.";
447
+ }, {
448
+ readonly sets: "reports";
449
+ readonly why: "Page views are seen in Cloudflare's Web Analytics, and events are rows in the app's own `events` table; ask what the owner wants to see when they ask for a report.";
450
+ }];
451
+ };
274
452
  readonly delivery: {
275
453
  readonly choice: "optional";
276
454
  readonly intro: "Counting page views and the things visitors do on the site.";
277
- readonly when: "The client wants to know how many people visit, which pages they open, or to count an action such as a sign-up or an order.";
278
455
  };
279
456
  };
280
457
  readonly config: {
@@ -423,10 +600,137 @@ export declare const capabilities: {
423
600
  readonly cost_hint_usd: 0;
424
601
  readonly note: "Every pack's spelling is one the Gateway accepts (en-PH, en-AU, en-GB, en-US), checked against the ai manifest's spellings; no Gateway call";
425
602
  }];
603
+ readonly offer: {
604
+ readonly pattern: "project";
605
+ readonly when: "The app shows a date, a time, a number, a phone number or an amount of money to its readers, or the client says where their readers are.";
606
+ readonly say: "I'll write dates, times and money with Workbench country formats, the way your readers expect them. That needs to know where they are, once for the whole site.";
607
+ readonly answers_file: "workbench/country.json";
608
+ readonly answers_format: "`{ \"format\": 1, \"country\": \"AU\", \"time_zone\": null, \"currency\": null }`: the country code (or `ZZ`, the package's WORLDWIDE, for readers everywhere), and an IANA time zone or `{ \"code\", \"symbol\" }` currency only when they differ from the country's own (the Australian and US time questions both fill `time_zone`). Then set `READERS` in `src/lib/country.ts` to match: `audiencePack({ country, timeZone, currency })`, leaving out what is null. Every page, email, PDF and form reads `READERS`, so nothing else changes.";
609
+ readonly questions: readonly [{
610
+ readonly id: "country.where";
611
+ readonly sets: "audience.country";
612
+ readonly ask: "Where are most of the site's readers?";
613
+ readonly style: "cards";
614
+ readonly answers: readonly [{
615
+ readonly label: "Australia";
616
+ readonly value: "AU";
617
+ readonly means: "26 September 2026, A$1,400, Sydney time, Australian spelling.";
618
+ }, {
619
+ readonly label: "The Philippines";
620
+ readonly value: "PH";
621
+ readonly means: "September 26, 2026, ₱1,400, Manila time.";
622
+ }, {
623
+ readonly label: "The United Kingdom";
624
+ readonly value: "GB";
625
+ readonly means: "26 September 2026, £1,400, UK time, British spelling.";
626
+ }, {
627
+ readonly label: "The United States";
628
+ readonly value: "US";
629
+ readonly means: "September 26, 2026, US$1,400, Eastern time, American spelling.";
630
+ }];
631
+ readonly other: {
632
+ readonly kind: "text";
633
+ readonly min: 2;
634
+ readonly max: 60;
635
+ readonly description: "Another country, or readers all over the world.";
636
+ };
637
+ readonly note: "Recommend Australia unless the client's words say otherwise. A country with no pack yet, or readers everywhere, gets the neutral one (`ZZ`: 26 September 2026, 24-hour clock, UTC, US$): say so, and ask for the currency.";
638
+ }, {
639
+ readonly id: "country.au-time";
640
+ readonly sets: "audience.time_zone.au";
641
+ readonly ask: "Which Australian time should the site show?";
642
+ readonly style: "cards";
643
+ readonly answers: readonly [{
644
+ readonly label: "Sydney, Melbourne, Canberra";
645
+ readonly value: null;
646
+ readonly means: "The pack's own (Australia/Sydney), with daylight saving.";
647
+ }, {
648
+ readonly label: "Brisbane";
649
+ readonly value: "Australia/Brisbane";
650
+ readonly means: "Queensland, no daylight saving.";
651
+ }, {
652
+ readonly label: "Adelaide";
653
+ readonly value: "Australia/Adelaide";
654
+ readonly means: "South Australia, half an hour behind Sydney.";
655
+ }, {
656
+ readonly label: "Perth";
657
+ readonly value: "Australia/Perth";
658
+ readonly means: "Western Australia.";
659
+ }];
660
+ readonly other: {
661
+ readonly kind: "text";
662
+ readonly min: 5;
663
+ readonly max: 40;
664
+ readonly description: "Another Australian time zone, such as Australia/Darwin or Australia/Hobart.";
665
+ };
666
+ readonly when: {
667
+ readonly id: "country.where";
668
+ readonly is: readonly ["AU"];
669
+ };
670
+ readonly note: "The time zone of the business, where bookings and opening hours happen. Hobart follows Sydney; Darwin is Australia/Darwin.";
671
+ }, {
672
+ readonly id: "country.us-time";
673
+ readonly sets: "audience.time_zone.us";
674
+ readonly ask: "Which US time should the site show?";
675
+ readonly style: "cards";
676
+ readonly answers: readonly [{
677
+ readonly label: "Eastern";
678
+ readonly value: null;
679
+ readonly means: "The pack's own (America/New_York).";
680
+ }, {
681
+ readonly label: "Central";
682
+ readonly value: "America/Chicago";
683
+ }, {
684
+ readonly label: "Mountain";
685
+ readonly value: "America/Denver";
686
+ }, {
687
+ readonly label: "Pacific";
688
+ readonly value: "America/Los_Angeles";
689
+ }];
690
+ readonly other: {
691
+ readonly kind: "text";
692
+ readonly min: 5;
693
+ readonly max: 40;
694
+ readonly description: "Another US time zone, such as America/Phoenix or Pacific/Honolulu.";
695
+ };
696
+ readonly when: {
697
+ readonly id: "country.where";
698
+ readonly is: readonly ["US"];
699
+ };
700
+ readonly note: "The time zone of the business.";
701
+ }, {
702
+ readonly id: "country.currency";
703
+ readonly sets: "audience.currency";
704
+ readonly ask: "Which money are prices in?";
705
+ readonly style: "cards";
706
+ readonly answers: readonly [{
707
+ readonly label: "The country's own";
708
+ readonly value: null;
709
+ readonly means: "A$ in Australia, ₱ in the Philippines, £ in the UK, US$ in the US.";
710
+ }, {
711
+ readonly label: "US dollars";
712
+ readonly value: "USD";
713
+ readonly means: "Shown as US$1,400.";
714
+ }];
715
+ readonly other: {
716
+ readonly kind: "text";
717
+ readonly min: 3;
718
+ readonly max: 3;
719
+ readonly description: "Another currency's three-letter code, such as NZD or EUR.";
720
+ };
721
+ readonly note: "Write a currency other than the country's as `{ \"code\", \"symbol\" }` (NZD is NZ$). Ask it only when the client sells something; otherwise record null.";
722
+ }];
723
+ readonly not_asked: readonly [{
724
+ readonly sets: "audience.format";
725
+ readonly why: "Date order, the clock, number separators, spelling and phone number shapes all come from the country's pack, so they match what readers expect without asking.";
726
+ }, {
727
+ readonly sets: "audience.owner_pages";
728
+ readonly why: "Pages only the owner sees use workbench-ui's own times and money, not the readers'.";
729
+ }];
730
+ };
426
731
  readonly delivery: {
427
732
  readonly choice: "optional";
428
733
  readonly intro: "Dates, times, numbers and money written the way readers in your country expect.";
429
- readonly when: "The app shows a date, a time, a number, a phone number or an amount of money to its readers, or the client says where their readers are.";
430
734
  };
431
735
  };
432
736
  readonly database: {
@@ -1643,10 +1947,132 @@ export declare const capabilities: {
1643
1947
  readonly smoke: true;
1644
1948
  readonly cost_hint_usd: 0.001;
1645
1949
  }];
1950
+ readonly offer: {
1951
+ readonly pattern: "feature_or_content";
1952
+ readonly when: "The client wants to find existing pictures on the web, for the site itself or as a search visitors use.";
1953
+ readonly say: "I'll do this with Workbench picture search, which searches the web through Workbench, so the app holds no search key and spending has a monthly cap.";
1954
+ readonly answers_file: "workbench/image-search/<name>.json";
1955
+ readonly per: "feature or picture";
1956
+ readonly answers_format: "For a feature: `{ \"format\": 1, \"route\": \"feature\", \"name\", \"about\" (the client's words), \"who\", \"cap_usd\", \"job\" }`, where `job` is the `X-Workbench-Job` the feature's calls send (`<name>` with dots, such as `quotes.estimate`), so its spending shows on its own line in Control. Write it, then build the feature with the package. For a picture found once: `npx workbench-ai pick <name> <round>-<draft> --licence \"<the owner's words>\"` downloads it to `public/images/<name>.jpg` (or .png) and writes the answers file with where it came from and the licence; never write one from nothing, and never pick one without the licence answer. Show the credit the licence asks for beside the picture. Drafts and their previews stay in `workbench/image-search/drafts/`, which isn't committed.";
1957
+ readonly questions: readonly [{
1958
+ readonly id: "image-search.route";
1959
+ readonly sets: "route";
1960
+ readonly ask: "Is this something visitors use on the site, or something made once for the site?";
1961
+ readonly style: "cards";
1962
+ readonly answers: readonly [{
1963
+ readonly label: "Visitors use it";
1964
+ readonly value: "feature";
1965
+ readonly means: "A search on the site that finds pictures for whoever uses it.";
1966
+ }, {
1967
+ readonly label: "Made once for the site";
1968
+ readonly value: "content";
1969
+ readonly means: "A picture for the site, found once and kept.";
1970
+ }];
1971
+ readonly must_ask: true;
1972
+ readonly note: "Ask it even when the client's words seem to say, as a quick check: the two routes are built and paid for differently.";
1973
+ }, {
1974
+ readonly id: "image-search.who";
1975
+ readonly sets: "feature.who";
1976
+ readonly ask: "Who may use it?";
1977
+ readonly style: "cards";
1978
+ readonly answers: readonly [{
1979
+ readonly label: "People who are signed in";
1980
+ readonly value: "signed_in";
1981
+ readonly means: "Anyone with an account. Each use is tied to a person, so misuse can be traced and stopped.";
1982
+ }, {
1983
+ readonly label: "Admins only";
1984
+ readonly value: "admins";
1985
+ readonly means: "Only the business's own admins, for example a tool for writing their own posts.";
1986
+ }, {
1987
+ readonly label: "Anyone visiting";
1988
+ readonly value: "anyone";
1989
+ readonly means: "No sign-in. The page checks for bots (Turnstile) and limits how often one visitor can use it.";
1990
+ }];
1991
+ readonly when: {
1992
+ readonly id: "image-search.route";
1993
+ readonly is: readonly ["feature"];
1994
+ };
1995
+ readonly note: "Signed in means `requireUser` and admins means `app_admins`, as the workbench-sign-in skill says. For anyone, check Turnstile on every use and keep a per-visitor limit; say plainly that strangers can run up the cost. Pass `subject` on every call so the Gateway can tell people apart: `user:<id>` for a signed-in person, and for anyone a random id kept in a cookie (`visitor:<id>`), never an email address or name.";
1996
+ }, {
1997
+ readonly id: "image-search.cap";
1998
+ readonly sets: "feature.cap_usd";
1999
+ readonly ask: "How much may it spend in a month, at most? When it's reached, the feature stops until next month.";
2000
+ readonly style: "cards";
2001
+ readonly answers: readonly [{
2002
+ readonly label: "US$5 a month";
2003
+ readonly value: 5;
2004
+ readonly means: "A small trial, or light use.";
2005
+ }, {
2006
+ readonly label: "US$20 a month";
2007
+ readonly value: 20;
2008
+ readonly means: "Steady use by a small business's visitors.";
2009
+ }, {
2010
+ readonly label: "US$50 a month";
2011
+ readonly value: 50;
2012
+ readonly means: "Heavy use. It can be raised later.";
2013
+ }];
2014
+ readonly other: {
2015
+ readonly kind: "money";
2016
+ readonly min: 1;
2017
+ readonly max: 1000;
2018
+ readonly description: "US dollars a month, whole or with cents.";
2019
+ };
2020
+ readonly must_ask: true;
2021
+ readonly when: {
2022
+ readonly id: "image-search.route";
2023
+ readonly is: readonly ["feature"];
2024
+ };
2025
+ readonly note: "Say roughly what one use costs (about a tenth of a cent a search) so the owner can judge. The cap is the project's production cap for the `search` tool in the Gateway, shared by every feature that uses it: set it with Workbench Control's `gw_budget_set` (env production, tool search, monthly_usd). Raising a cap waits for the owner's approval on Control's approval page. When it is reached, calls fail with `budget_exceeded`: the page says the feature is resting until next month, never an error.";
2026
+ }, {
2027
+ readonly id: "image-search.licence";
2028
+ readonly sets: "content.licence";
2029
+ readonly ask: "Does the site have the right to use this picture?";
2030
+ readonly style: "cards";
2031
+ readonly answers: readonly [{
2032
+ readonly label: "Yes, it's ours";
2033
+ readonly value: "own";
2034
+ readonly means: "The business took it or owns it.";
2035
+ }, {
2036
+ readonly label: "Yes, it's free to use";
2037
+ readonly value: "free";
2038
+ readonly means: "Its page says so, with a licence such as CC BY. Say which.";
2039
+ }, {
2040
+ readonly label: "No, find another";
2041
+ readonly value: "none";
2042
+ readonly means: "Pick another result, or make one with AI pictures instead.";
2043
+ }];
2044
+ readonly other: {
2045
+ readonly kind: "text";
2046
+ readonly min: 3;
2047
+ readonly max: 200;
2048
+ readonly description: "What gives the site the right to use it.";
2049
+ };
2050
+ readonly must_ask: true;
2051
+ readonly when: {
2052
+ readonly id: "image-search.route";
2053
+ readonly is: readonly ["content"];
2054
+ };
2055
+ readonly note: "Ask after the client picks a result, showing its page link. A search result is a lead, not permission. Pass the client's words as --licence; with no, don't pick it.";
2056
+ }];
2057
+ readonly not_asked: readonly [{
2058
+ readonly sets: "query";
2059
+ readonly why: "The client's description is the search: pass it as --about, shortened to the words that matter.";
2060
+ }, {
2061
+ readonly sets: "filters";
2062
+ readonly why: "Results narrower than 600 pixels are dropped; anything finer (shape, site) is for a feature to do in its own code.";
2063
+ }];
2064
+ readonly drafts: {
2065
+ readonly of: "found pictures";
2066
+ readonly surface: "image-search";
2067
+ readonly count: 4;
2068
+ readonly max_rounds: 3;
2069
+ readonly job: "image-search.drafts";
2070
+ readonly command: "npx workbench-ai drafts search <name> --about \"<what to search for>\"";
2071
+ };
2072
+ };
1646
2073
  readonly delivery: {
1647
2074
  readonly choice: "built_in";
1648
2075
  readonly intro: "Finding existing pictures on the web.";
1649
- readonly when: "The client wants to find existing pictures on the web, for the site itself or as a search visitors use.";
1650
2076
  };
1651
2077
  };
1652
2078
  readonly images: {
@@ -1800,10 +2226,159 @@ export declare const capabilities: {
1800
2226
  readonly refusal: "invalid_request";
1801
2227
  readonly cost_hint_usd: 0;
1802
2228
  }];
2229
+ readonly offer: {
2230
+ readonly pattern: "feature_or_content";
2231
+ readonly when: "The client wants a picture made by AI, such as an illustration, a hero image or a product mock-up, as a feature visitors use or once for the site.";
2232
+ readonly say: "I'll make this with Workbench pictures, made by AI through Workbench, so the app holds no image key and spending has a monthly cap.";
2233
+ readonly answers_file: "workbench/images/<name>.json";
2234
+ readonly per: "feature or picture";
2235
+ readonly answers_format: "For a feature: `{ \"format\": 1, \"route\": \"feature\", \"name\", \"about\" (the client's words), \"who\", \"cap_usd\", \"job\" }`, where `job` is the `X-Workbench-Job` the feature's calls send (`<name>` with dots, such as `quotes.estimate`), so its spending shows on its own line in Control. Write it, then build the feature with the package. For a picture made once: `npx workbench-ai pick <name> <round>-<draft>` copies the picked picture to `public/images/<name>.png` (or .jpg) and writes the answers file with its prompt, model, request id and cost; never write one from nothing. Show it on the page from `/images/<name>.png`, with alt text that says what it shows. Drafts and their previews stay in `workbench/images/drafts/`, which isn't committed.";
2236
+ readonly questions: readonly [{
2237
+ readonly id: "images.route";
2238
+ readonly sets: "route";
2239
+ readonly ask: "Is this something visitors use on the site, or something made once for the site?";
2240
+ readonly style: "cards";
2241
+ readonly answers: readonly [{
2242
+ readonly label: "Visitors use it";
2243
+ readonly value: "feature";
2244
+ readonly means: "A feature that makes a new picture each time someone uses it, such as a mock-up of their own design.";
2245
+ }, {
2246
+ readonly label: "Made once for the site";
2247
+ readonly value: "content";
2248
+ readonly means: "A picture for the site, made once and kept: a hero image, an illustration for a page.";
2249
+ }];
2250
+ readonly must_ask: true;
2251
+ readonly note: "Ask it even when the client's words seem to say, as a quick check: the two routes are built and paid for differently.";
2252
+ }, {
2253
+ readonly id: "images.who";
2254
+ readonly sets: "feature.who";
2255
+ readonly ask: "Who may use it?";
2256
+ readonly style: "cards";
2257
+ readonly answers: readonly [{
2258
+ readonly label: "People who are signed in";
2259
+ readonly value: "signed_in";
2260
+ readonly means: "Anyone with an account. Each use is tied to a person, so misuse can be traced and stopped.";
2261
+ }, {
2262
+ readonly label: "Admins only";
2263
+ readonly value: "admins";
2264
+ readonly means: "Only the business's own admins, for example a tool for writing their own posts.";
2265
+ }, {
2266
+ readonly label: "Anyone visiting";
2267
+ readonly value: "anyone";
2268
+ readonly means: "No sign-in. The page checks for bots (Turnstile) and limits how often one visitor can use it.";
2269
+ }];
2270
+ readonly when: {
2271
+ readonly id: "images.route";
2272
+ readonly is: readonly ["feature"];
2273
+ };
2274
+ readonly note: "Signed in means `requireUser` and admins means `app_admins`, as the workbench-sign-in skill says. For anyone, check Turnstile on every use and keep a per-visitor limit; say plainly that strangers can run up the cost. Pass `subject` on every call so the Gateway can tell people apart: `user:<id>` for a signed-in person, and for anyone a random id kept in a cookie (`visitor:<id>`), never an email address or name.";
2275
+ }, {
2276
+ readonly id: "images.cap";
2277
+ readonly sets: "feature.cap_usd";
2278
+ readonly ask: "How much may it spend in a month, at most? When it's reached, the feature stops until next month.";
2279
+ readonly style: "cards";
2280
+ readonly answers: readonly [{
2281
+ readonly label: "US$5 a month";
2282
+ readonly value: 5;
2283
+ readonly means: "A small trial, or light use.";
2284
+ }, {
2285
+ readonly label: "US$20 a month";
2286
+ readonly value: 20;
2287
+ readonly means: "Steady use by a small business's visitors.";
2288
+ }, {
2289
+ readonly label: "US$50 a month";
2290
+ readonly value: 50;
2291
+ readonly means: "Heavy use. It can be raised later.";
2292
+ }];
2293
+ readonly other: {
2294
+ readonly kind: "money";
2295
+ readonly min: 1;
2296
+ readonly max: 1000;
2297
+ readonly description: "US dollars a month, whole or with cents.";
2298
+ };
2299
+ readonly must_ask: true;
2300
+ readonly when: {
2301
+ readonly id: "images.route";
2302
+ readonly is: readonly ["feature"];
2303
+ };
2304
+ readonly note: "Say roughly what one use costs (about US$0.07 a picture with image-default) so the owner can judge. The cap is the project's production cap for the `images` tool in the Gateway, shared by every feature that uses it: set it with Workbench Control's `gw_budget_set` (env production, tool images, monthly_usd). Raising a cap waits for the owner's approval on Control's approval page. When it is reached, calls fail with `budget_exceeded`: the page says the feature is resting until next month, never an error.";
2305
+ }, {
2306
+ readonly id: "images.what";
2307
+ readonly sets: readonly ["content.name", "content.about"];
2308
+ readonly ask: "What picture do you need?";
2309
+ readonly style: "cards";
2310
+ readonly answers: readonly [{
2311
+ readonly label: "A hero image";
2312
+ readonly value: "hero";
2313
+ readonly means: "The large picture across the top of a page.";
2314
+ }, {
2315
+ readonly label: "An illustration";
2316
+ readonly value: "illustration";
2317
+ readonly means: "A drawing for a page or a section.";
2318
+ }, {
2319
+ readonly label: "A product mock-up";
2320
+ readonly value: "mockup";
2321
+ readonly means: "A product shown in use or in a setting.";
2322
+ }];
2323
+ readonly other: {
2324
+ readonly kind: "text";
2325
+ readonly min: 3;
2326
+ readonly max: 200;
2327
+ readonly description: "What the picture is, in a few words.";
2328
+ };
2329
+ readonly when: {
2330
+ readonly id: "images.route";
2331
+ readonly is: readonly ["content"];
2332
+ };
2333
+ readonly note: "Name the picture after where it goes (home_hero): lower case and underscores. The client's own words about what it shows and the feel they want go in --about.";
2334
+ }, {
2335
+ readonly id: "images.shape";
2336
+ readonly sets: "content.aspect";
2337
+ readonly ask: "What shape should it be?";
2338
+ readonly style: "cards";
2339
+ readonly answers: readonly [{
2340
+ readonly label: "Landscape (3:2)";
2341
+ readonly value: "3:2";
2342
+ readonly means: "Wider than tall, the usual shape for a page.";
2343
+ }, {
2344
+ readonly label: "Wide banner (16:9)";
2345
+ readonly value: "16:9";
2346
+ readonly means: "For a picture across the top of a page.";
2347
+ }, {
2348
+ readonly label: "Square (1:1)";
2349
+ readonly value: "1:1";
2350
+ readonly means: "For cards and social posts.";
2351
+ }, {
2352
+ readonly label: "Portrait (2:3)";
2353
+ readonly value: "2:3";
2354
+ readonly means: "Taller than wide.";
2355
+ }];
2356
+ readonly when: {
2357
+ readonly id: "images.route";
2358
+ readonly is: readonly ["content"];
2359
+ };
2360
+ readonly note: "Recommend the banner for a hero image. Pass it to the drafts command as --aspect.";
2361
+ }];
2362
+ readonly not_asked: readonly [{
2363
+ readonly sets: "model";
2364
+ readonly why: "Drafts and features use image-default. image-pro costs about twice as much for finer detail and image-openai is cheapest; suggest one only when the client asks about quality or cost.";
2365
+ }, {
2366
+ readonly sets: "references";
2367
+ readonly why: "A reference picture (to make something like it) is only for a feature that needs one, and works with Google aliases only; offer it when the client mentions one.";
2368
+ }];
2369
+ readonly drafts: {
2370
+ readonly of: "pictures";
2371
+ readonly surface: "images";
2372
+ readonly alias: "image-default";
2373
+ readonly count: 3;
2374
+ readonly max_rounds: 3;
2375
+ readonly job: "images.drafts";
2376
+ readonly command: "npx workbench-ai drafts image <name> --about \"<what it shows, in the client's words>\" --aspect <shape>";
2377
+ };
2378
+ };
1803
2379
  readonly delivery: {
1804
2380
  readonly choice: "built_in";
1805
2381
  readonly intro: "Pictures made by AI from a description.";
1806
- readonly when: "The client wants a picture made by AI, such as an illustration, a hero image or a product mock-up, as a feature visitors use or once for the site.";
1807
2382
  };
1808
2383
  };
1809
2384
  readonly pdf: {
@@ -2223,13 +2798,33 @@ export declare const capabilities: {
2223
2798
  readonly cost_hint_usd: 0;
2224
2799
  readonly note: "In the Worker itself: compares money (US$0.0042, US$12.50, and an en dash for nothing), duration (640 ms, 2.35 s, 3 min 5 s) and timeLeft against a fixed now (42 minutes left) with the expected text, and checks when and shortWhen write one fixed moment as its Manila day and hour with UTC beside it";
2225
2800
  }];
2801
+ readonly offer: {
2802
+ readonly pattern: "switch_on";
2803
+ readonly when: "The client wants a page only the owner or the app's admins see: a dashboard, a list, a settings page or a status page.";
2804
+ readonly say: "I'll build this with the Workbench owner-page look, the same top bar, cards and pills as the other admin pages, so it's quick to build and consistent. Your customers never see it.";
2805
+ readonly answers_file: "workbench/ui.json";
2806
+ readonly answers_format: "`{ \"format\": 1, \"pages\": [{ \"path\": \"/admin/orders\", \"what\": \"every order this month, newest first\", \"who\": \"admins\" }] }`. Build each page with `shell`, `topBar`, `card` and `pill`. An `admins` page lives under `/admin/` and checks `requireUser` and `app_admins` (the workbench-sign-in skill); an `owner` page lives under `/owner/` and is behind Access in production.";
2807
+ readonly questions: readonly [{
2808
+ readonly id: "ui.pages";
2809
+ readonly sets: "pages";
2810
+ readonly ask: "Which admin pages should the app have?";
2811
+ readonly style: "table";
2812
+ readonly note: "Propose the pages the request needs, each with its path, what it shows or lets them change in the client's words, and who sees it: `admins` (the business's people, who sign in to the app) or `owner` (whoever runs the app's Workbench, through Access). Recommend `admins` unless the page is about running the app itself, such as a status page. The client accepts, removes or adds pages.";
2813
+ }];
2814
+ readonly not_asked: readonly [{
2815
+ readonly sets: "look";
2816
+ readonly why: "Every admin page has the same Workbench look, so the business's people learn it once. Restyle it in project.css (see Restyling) if the client wants their colours.";
2817
+ }, {
2818
+ readonly sets: "formats";
2819
+ readonly why: "Times, amounts and durations on these pages come from workbench-ui's `when`, `money` and `duration`. What customers see uses the readers' formats (the workbench-country skill).";
2820
+ }];
2821
+ };
2226
2822
  readonly styles: {
2227
2823
  readonly prefix: "wb-ui-";
2228
2824
  };
2229
2825
  readonly delivery: {
2230
2826
  readonly choice: "optional";
2231
2827
  readonly intro: "A ready-made look for the pages only you and your admins see.";
2232
- readonly when: "The client wants a page only the owner or the app's admins see: a dashboard, a list, a settings page or a status page.";
2233
2828
  };
2234
2829
  };
2235
2830
  };