@mccaskie-labs/workbench-config 0.2.5 → 0.3.1
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/CHANGELOG.md +12 -0
- package/capabilities/availability.json +263 -0
- package/capabilities/country.json +20 -1
- package/capabilities/database.json +32 -3
- package/capabilities/email-inbound.json +47 -3
- package/capabilities/email.json +66 -2
- package/capabilities/schema.json +4 -6
- package/capabilities/strings.json +46 -3
- package/dist/capabilities.gen.d.ts +381 -6
- package/dist/capabilities.gen.js +474 -10
- package/dist/capability-schema.d.ts +3 -8
- package/dist/capability-schema.js +2 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +4 -8
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MANIFEST_VERSION: "
|
|
1
|
+
export declare const MANIFEST_VERSION: "9aae488ad117";
|
|
2
2
|
export declare const capabilities: {
|
|
3
3
|
readonly ai: {
|
|
4
4
|
readonly $schema: "./schema.json";
|
|
@@ -454,6 +454,221 @@ export declare const capabilities: {
|
|
|
454
454
|
readonly intro: "Counting page views and the things visitors do on the site.";
|
|
455
455
|
};
|
|
456
456
|
};
|
|
457
|
+
readonly availability: {
|
|
458
|
+
readonly $schema: "./schema.json";
|
|
459
|
+
readonly format: 1;
|
|
460
|
+
readonly component: "availability";
|
|
461
|
+
readonly title: "Availability and pricing";
|
|
462
|
+
readonly summary: "workbench-availability answers \"is this date free, and what does it cost?\" for a bookable thing (a venue, a room, a photographer's day) from its calendar and its price rules, with the same answer every time and one plain line per rule applied. A store over the app's own database stops two bookings landing on one date, and a tap-to-block calendar lets the owner close dates from a phone. It runs in the app itself, with no Gateway call, no AI and no cost.";
|
|
463
|
+
readonly status: "built";
|
|
464
|
+
readonly surfaces: {
|
|
465
|
+
readonly package: readonly ["quote", "createAvailability", "availabilityMigration", "calendarAction", "checkSettings", "stayClaims", "staysClash", "todayIn", "AvailabilityError"];
|
|
466
|
+
};
|
|
467
|
+
readonly rules: readonly [{
|
|
468
|
+
readonly id: "availability.quote-is-fact";
|
|
469
|
+
readonly kind: "must";
|
|
470
|
+
readonly text: "Take every availability answer and price the app shows or sends from quote() (or createAvailability's quote), and pass its lines and explain on as facts. Never let AI work out a price or a free date, and never write a price into a page or prompt by hand.";
|
|
471
|
+
readonly enforced_by: "review";
|
|
472
|
+
readonly probe: "availability.quote";
|
|
473
|
+
}, {
|
|
474
|
+
readonly id: "availability.book-through-block";
|
|
475
|
+
readonly kind: "must";
|
|
476
|
+
readonly text: "Book, hold and block dates only through createAvailability's block(), which runs availability_block() in one transaction: never insert into availability_blocks directly, so the exclusion constraint and the hold expiry always apply.";
|
|
477
|
+
readonly enforced_by: "database";
|
|
478
|
+
readonly probe: "availability.store";
|
|
479
|
+
}, {
|
|
480
|
+
readonly id: "availability.calendar-days";
|
|
481
|
+
readonly kind: "must";
|
|
482
|
+
readonly text: "Treat dates as calendar days (YYYY-MM-DD) in the account's time zone: pass todayIn(timeZone) as today, never new Date() in UTC.";
|
|
483
|
+
readonly enforced_by: "types";
|
|
484
|
+
readonly probe: "availability.quote";
|
|
485
|
+
}, {
|
|
486
|
+
readonly id: "availability.no-names-in-notes";
|
|
487
|
+
readonly kind: "must_not";
|
|
488
|
+
readonly text: "Write a guest's name or phone number into a block's note: link the app's own booking in subject instead. The .ics feed carries neither.";
|
|
489
|
+
readonly enforced_by: "review";
|
|
490
|
+
readonly probe: "availability.store";
|
|
491
|
+
}, {
|
|
492
|
+
readonly id: "availability.public-gets-the-quote";
|
|
493
|
+
readonly kind: "must_not";
|
|
494
|
+
readonly text: "Let a public page or the anonymous key read availability_blocks: public pages get the finished quote from the server.";
|
|
495
|
+
readonly enforced_by: "database";
|
|
496
|
+
readonly probe: "availability.store";
|
|
497
|
+
}, {
|
|
498
|
+
readonly id: "availability.daily-cleanup";
|
|
499
|
+
readonly kind: "must";
|
|
500
|
+
readonly text: "Run createAvailability's cleanup() once a day from the app's cron with the service client, so lapsed holds show as expired and old blocks are deleted on time.";
|
|
501
|
+
readonly enforced_by: "review";
|
|
502
|
+
readonly probe: "availability.store";
|
|
503
|
+
}, {
|
|
504
|
+
readonly id: "availability.calendar-as-shipped";
|
|
505
|
+
readonly kind: "must";
|
|
506
|
+
readonly text: "Draw the owner's calendar with renderCalendar and CALENDAR_SCRIPT (under the page's nonce), and hand the posted body to calendarAction behind the owner's sign-in. Never hand-write the calendar's markup or script.";
|
|
507
|
+
readonly enforced_by: "review";
|
|
508
|
+
readonly probe: "availability.calendar";
|
|
509
|
+
}];
|
|
510
|
+
readonly examples: readonly [{
|
|
511
|
+
readonly id: "availability.example.quote";
|
|
512
|
+
readonly title: "Answer \"is 12 Dec free, and how much?\"";
|
|
513
|
+
readonly lang: "ts";
|
|
514
|
+
readonly code: "import { createAvailability } from '@mccaskie-labs/workbench-availability'\nimport settings from '../workbench/availability.json'\n\nconst av = createAvailability({ db: serviceClient, account: venue.id, settings, country: 'PH', timeZone: 'Asia/Manila' })\nconst q = await av.quote({ resource: 'villa-sol-pool', date: '2026-12-12', stay: 'day_use', guests: { min: 15, max: 15 }, eventType: 'birthday' })\n// q.available 'yes'; q.options[0].total { amount: '9500.00', currency: 'PHP' }; q.explain ['Weekend day use: ₱9,500 (rule r-weekend)', ...]";
|
|
515
|
+
readonly probe: "availability.quote";
|
|
516
|
+
}, {
|
|
517
|
+
readonly id: "availability.example.calendar";
|
|
518
|
+
readonly title: "The owner's tap-to-block calendar";
|
|
519
|
+
readonly lang: "ts";
|
|
520
|
+
readonly code: "import { calendarAction } from '@mccaskie-labs/workbench-availability'\nimport { CALENDAR_SCRIPT, renderCalendar } from '@mccaskie-labs/workbench-availability/calendar'\n\nconst days = await av.month('villa-sol-pool', '2026-12')\nconst html = renderCalendar(days, t, { resource: 'villa-sol-pool', month: '2026-12', endpoint: '/owner/calendar', stays: [{ id: 'day_use', label: t('ui.stay.day_use')! }] })\n// The page adds <script nonce=...>${CALENDAR_SCRIPT}</script>; POST /owner/calendar (owner signed in):\nconst r = await calendarAction(av, await request.json())\nreturn Response.json(r.body, { status: r.status })";
|
|
521
|
+
readonly probe: "availability.calendar";
|
|
522
|
+
}];
|
|
523
|
+
readonly probes: readonly [{
|
|
524
|
+
readonly id: "availability.quote";
|
|
525
|
+
readonly half: "runtime";
|
|
526
|
+
readonly expect: "pass";
|
|
527
|
+
readonly smoke: true;
|
|
528
|
+
readonly cost_hint_usd: 0;
|
|
529
|
+
readonly note: "In the Worker itself: quote() on the spec's HmPo example (Sat 12 Dec ₱9,500, Sat 19 Dec ₱10,925 with peak, 25 guests ₱10,250 over capacity, no guests a from price) and the alternatives when 19 Dec is booked; no database, no Gateway call";
|
|
530
|
+
}, {
|
|
531
|
+
readonly id: "availability.store";
|
|
532
|
+
readonly half: "runtime";
|
|
533
|
+
readonly expect: "pass";
|
|
534
|
+
readonly cost_hint_usd: 0;
|
|
535
|
+
readonly note: "Against the probe app's own database: two block(booked) calls for one night at once (one lands, one refused as clash), a lapsed hold quoted free and marked expired by cleanup(), another account's resource unreachable, then every probe block removed";
|
|
536
|
+
}, {
|
|
537
|
+
readonly id: "availability.calendar";
|
|
538
|
+
readonly half: "runtime";
|
|
539
|
+
readonly expect: "pass";
|
|
540
|
+
readonly cost_hint_usd: 0;
|
|
541
|
+
readonly note: "The probe page draws a month with renderCalendar and CALENDAR_SCRIPT: tap, Block day, Undo leaves no block; a change made offline shows \"1 change waiting\" and is sent on reconnect";
|
|
542
|
+
}];
|
|
543
|
+
readonly offer: {
|
|
544
|
+
readonly pattern: "settings";
|
|
545
|
+
readonly when: "The client takes bookings for something with dates: a venue, a room, a resort day pass, a photographer's or hire business's days, or wants prices that depend on the day, season, holiday, number of guests or kind of event, or wants a calendar to block dates.";
|
|
546
|
+
readonly say: "I'll set this up with Workbench availability, so every price and free date comes out the same way each time, explained line by line, and you get a calendar to block dates from your phone. Here's how a business like yours usually books; tell me what to change.";
|
|
547
|
+
readonly answers_file: "workbench/availability.json";
|
|
548
|
+
readonly answers_format: "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"what\": \"<the client's words>\", \"stay_types\": [{ \"id\", \"start\", \"end\", \"clashes\" }], \"event_types\": [...], \"currency\": \"PHP\", \"alternatives\": { \"count\": 3, \"within_days\": 21 }, \"hold_days\": 3, \"holidays\": \"weekend\" | \"weekday\" | \"separate\" }`. Every stay type clashes with itself; list each pair that can't share a date on both sides. Take `currency` from workbench/country.json. checkSettings() reads this file at startup and names every problem. Each account's resources, packages and price rules are data in the app's database (availability_resources, availability_packages, availability_price_rules), entered on the owner's pages, never in this file. Copy availabilityMigration into supabase/migrations with its down file, and CALENDAR_WORDS into the strings catalogue.";
|
|
549
|
+
readonly questions: readonly [{
|
|
550
|
+
readonly id: "availability.what";
|
|
551
|
+
readonly sets: "availability.what";
|
|
552
|
+
readonly ask: "What do people book?";
|
|
553
|
+
readonly style: "table";
|
|
554
|
+
readonly other: {
|
|
555
|
+
readonly kind: "text";
|
|
556
|
+
readonly min: 3;
|
|
557
|
+
readonly max: 200;
|
|
558
|
+
readonly description: "In the client's words, such as \"a resort's pool and function hall\" or \"me, as a photographer, by the half or full day\".";
|
|
559
|
+
};
|
|
560
|
+
readonly must_ask: true;
|
|
561
|
+
readonly note: "Only the client knows. Their answer names the resources (one per thing booked separately) and suggests the stay types to propose next.";
|
|
562
|
+
}, {
|
|
563
|
+
readonly id: "availability.stays";
|
|
564
|
+
readonly sets: "availability.stay_types";
|
|
565
|
+
readonly ask: "Here are the usual ways it's booked in a day, with their hours and which can't share a date. Accept them, change some, or add or remove?";
|
|
566
|
+
readonly style: "table";
|
|
567
|
+
readonly other: {
|
|
568
|
+
readonly kind: "list";
|
|
569
|
+
readonly min: 1;
|
|
570
|
+
readonly max: 10;
|
|
571
|
+
readonly description: "Each way it's booked, with its start and end time and the others it can't share a date with.";
|
|
572
|
+
};
|
|
573
|
+
readonly must_ask: true;
|
|
574
|
+
readonly note: "Propose from the client's answer to availability.what. A resort or venue: day use 08:00 to 17:00, night use 19:00 to 07:00, overnight 14:00 to 12:00 the next day (overnight clashes with both; day and night use don't clash with each other). A photographer or hire business: half day and full day, clashing with each other. One room or slot a day: a single stay type.";
|
|
575
|
+
}, {
|
|
576
|
+
readonly id: "availability.events";
|
|
577
|
+
readonly sets: "availability.event_types";
|
|
578
|
+
readonly ask: "Should prices depend on the kind of event? Here is the usual list.";
|
|
579
|
+
readonly style: "cards";
|
|
580
|
+
readonly answers: readonly [{
|
|
581
|
+
readonly label: "Yes, use this list";
|
|
582
|
+
readonly value: "list";
|
|
583
|
+
readonly means: "Birthday, debut, wedding, baptism, reunion, team building, other: a rule can add a fee for some of them.";
|
|
584
|
+
}, {
|
|
585
|
+
readonly label: "No, same price for all";
|
|
586
|
+
readonly value: "none";
|
|
587
|
+
readonly means: "No event types; the quote never asks for one.";
|
|
588
|
+
}, {
|
|
589
|
+
readonly label: "Yes, with my own list";
|
|
590
|
+
readonly value: "own";
|
|
591
|
+
readonly means: "Type the kinds of event the business prices differently.";
|
|
592
|
+
}];
|
|
593
|
+
readonly other: {
|
|
594
|
+
readonly kind: "list";
|
|
595
|
+
readonly min: 1;
|
|
596
|
+
readonly max: 20;
|
|
597
|
+
readonly description: "The kinds of event, each a short name.";
|
|
598
|
+
};
|
|
599
|
+
readonly note: "Recommend the usual list for venues and resorts; \"none\" for most other businesses. Write ids in lower case with underscores (team_building).";
|
|
600
|
+
}, {
|
|
601
|
+
readonly id: "availability.holidays";
|
|
602
|
+
readonly sets: "availability.holidays";
|
|
603
|
+
readonly ask: "Should holidays be priced like weekends?";
|
|
604
|
+
readonly style: "cards";
|
|
605
|
+
readonly answers: readonly [{
|
|
606
|
+
readonly label: "Yes, like a weekend";
|
|
607
|
+
readonly value: "weekend";
|
|
608
|
+
readonly means: "A regular holiday or special non-working day on a weekday costs the weekend price.";
|
|
609
|
+
}, {
|
|
610
|
+
readonly label: "No, by the day of the week";
|
|
611
|
+
readonly value: "weekday";
|
|
612
|
+
readonly means: "Holidays cost what their day of the week costs.";
|
|
613
|
+
}, {
|
|
614
|
+
readonly label: "Set holiday prices separately";
|
|
615
|
+
readonly value: "separate";
|
|
616
|
+
readonly means: "Holidays get their own price rules, entered on the owner's pages.";
|
|
617
|
+
}];
|
|
618
|
+
readonly other: {
|
|
619
|
+
readonly kind: "text";
|
|
620
|
+
readonly min: 3;
|
|
621
|
+
readonly max: 200;
|
|
622
|
+
readonly description: "How holidays should be priced, in the client's words.";
|
|
623
|
+
};
|
|
624
|
+
readonly note: "Holidays come from workbench-country's yearly list (the Philippines' proclamation each year); a local fiesta goes in the availability_holidays table. Special working days (the EDSA anniversary) are never priced as holidays.";
|
|
625
|
+
}, {
|
|
626
|
+
readonly id: "availability.holds";
|
|
627
|
+
readonly sets: "availability.hold_days";
|
|
628
|
+
readonly ask: "How long is a date held while waiting for a deposit?";
|
|
629
|
+
readonly style: "cards";
|
|
630
|
+
readonly answers: readonly [{
|
|
631
|
+
readonly label: "3 days";
|
|
632
|
+
readonly value: 3;
|
|
633
|
+
readonly means: "The date shows as held (H) for 3 days, then is free again unless marked booked.";
|
|
634
|
+
}, {
|
|
635
|
+
readonly label: "1 day";
|
|
636
|
+
readonly value: 1;
|
|
637
|
+
}, {
|
|
638
|
+
readonly label: "7 days";
|
|
639
|
+
readonly value: 7;
|
|
640
|
+
}, {
|
|
641
|
+
readonly label: "No holds";
|
|
642
|
+
readonly value: 0;
|
|
643
|
+
readonly means: "A date is free until it is booked; nothing is held.";
|
|
644
|
+
}];
|
|
645
|
+
readonly other: {
|
|
646
|
+
readonly kind: "whole";
|
|
647
|
+
readonly min: 0;
|
|
648
|
+
readonly max: 60;
|
|
649
|
+
readonly description: "Days, 0 to 60.";
|
|
650
|
+
};
|
|
651
|
+
}];
|
|
652
|
+
readonly not_asked: readonly [{
|
|
653
|
+
readonly sets: "availability.prices";
|
|
654
|
+
readonly why: "Each account's prices, packages and rules change often, so they are data the owner enters on their own pages (or that onboarding reads from their rate card), not project settings.";
|
|
655
|
+
}, {
|
|
656
|
+
readonly sets: "availability.alternatives";
|
|
657
|
+
readonly why: "Up to 3 nearest free dates within 21 days, same weekday first, suits every business so far; change it in the answers file if a client asks.";
|
|
658
|
+
}, {
|
|
659
|
+
readonly sets: "availability.currency";
|
|
660
|
+
readonly why: "It is the country's own, from workbench/country.json.";
|
|
661
|
+
}];
|
|
662
|
+
readonly needs: readonly ["country"];
|
|
663
|
+
};
|
|
664
|
+
readonly styles: {
|
|
665
|
+
readonly prefix: "wb-cal-";
|
|
666
|
+
};
|
|
667
|
+
readonly delivery: {
|
|
668
|
+
readonly choice: "optional";
|
|
669
|
+
readonly intro: "Which dates are free and what each costs, worked out the same way every time, with a calendar the owner taps to block dates.";
|
|
670
|
+
};
|
|
671
|
+
};
|
|
457
672
|
readonly config: {
|
|
458
673
|
readonly $schema: "./schema.json";
|
|
459
674
|
readonly format: 1;
|
|
@@ -552,7 +767,7 @@ export declare const capabilities: {
|
|
|
552
767
|
readonly summary: "workbench-country writes dates, times, numbers and money the way readers in the Philippines, Australia, the UK and the US expect, from one pack per country, and gives each pack's spelling for the Gateway. It runs in the app itself, with no Gateway call and no cost.";
|
|
553
768
|
readonly status: "built";
|
|
554
769
|
readonly surfaces: {
|
|
555
|
-
readonly package: readonly ["COUNTRY_PACKS", "countryPackFor", "audiencePack", "checkPack", "CountryPackError", "formatDate", "formatDateTime", "calendarDayIn", "formatNumber", "formatMoney", "formattingLocale", "currencySymbol", "currencySymbolFrom", "WORLDWIDE", "isWorldwide"];
|
|
770
|
+
readonly package: readonly ["COUNTRY_PACKS", "countryPackFor", "audiencePack", "checkPack", "CountryPackError", "formatDate", "formatDateTime", "calendarDayIn", "formatNumber", "formatMoney", "formattingLocale", "currencySymbol", "currencySymbolFrom", "WORLDWIDE", "isWorldwide", "HOLIDAYS", "holidaysFor", "holidayOn", "checkHolidays"];
|
|
556
771
|
};
|
|
557
772
|
readonly rules: readonly [{
|
|
558
773
|
readonly id: "country.one-formatter";
|
|
@@ -572,6 +787,12 @@ export declare const capabilities: {
|
|
|
572
787
|
readonly text: "Write an amount with a bare \"$\": Australian dollars are A$ and US dollars US$.";
|
|
573
788
|
readonly enforced_by: "types";
|
|
574
789
|
readonly probe: "country.packs";
|
|
790
|
+
}, {
|
|
791
|
+
readonly id: "country.holidays-from-proclamation";
|
|
792
|
+
readonly kind: "must";
|
|
793
|
+
readonly text: "Take public holidays from holidaysFor or holidayOn, never worked out or guessed: a year with no list returns undefined, and checkHolidays fails from 1 December until next year's list is in.";
|
|
794
|
+
readonly enforced_by: "review";
|
|
795
|
+
readonly probe: "country.holidays";
|
|
575
796
|
}, {
|
|
576
797
|
readonly id: "country.spelling-from-pack";
|
|
577
798
|
readonly kind: "must";
|
|
@@ -599,6 +820,13 @@ export declare const capabilities: {
|
|
|
599
820
|
readonly expect: "pass";
|
|
600
821
|
readonly cost_hint_usd: 0;
|
|
601
822
|
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";
|
|
823
|
+
}, {
|
|
824
|
+
readonly id: "country.holidays";
|
|
825
|
+
readonly half: "runtime";
|
|
826
|
+
readonly expect: "pass";
|
|
827
|
+
readonly smoke: true;
|
|
828
|
+
readonly cost_hint_usd: 0;
|
|
829
|
+
readonly note: "The December probe: checkHolidays for every country with a built-in list, on today in that country's time zone. Fails when this year's list is missing, and from 1 December when next year's is, so the list is added from the proclamation before January's prices need it";
|
|
602
830
|
}];
|
|
603
831
|
readonly offer: {
|
|
604
832
|
readonly pattern: "project";
|
|
@@ -909,10 +1137,33 @@ export declare const capabilities: {
|
|
|
909
1137
|
readonly cost_hint_usd: 0;
|
|
910
1138
|
readonly note: "On a project's staging: cf_turnstile_widget_create and the turnstile_sitekey and turnstile_secret sources; the sign-in page shows the check, /auth/code without a passed token sends no code, and with one it does";
|
|
911
1139
|
}];
|
|
1140
|
+
readonly offer: {
|
|
1141
|
+
readonly pattern: "settings";
|
|
1142
|
+
readonly when: "The app needs to store or read data, or the client asks for a change to what is stored: a new table, column, rule or migration.";
|
|
1143
|
+
readonly say: "The app's data goes in its own Workbench database, one for testing and one for the live site. I'll propose what to store and who can see it, and change it with a migration you can undo.";
|
|
1144
|
+
readonly answers_file: "workbench/database.json";
|
|
1145
|
+
readonly answers_format: "`{ \"format\": 1, \"status\": \"draft\", \"tables\": [{ \"name\": \"orders\", \"row\": \"one order\", \"columns\": [{ \"name\": \"total\", \"kind\": \"money\", \"required\": true }], \"read\": \"own\", \"change\": \"own\" }] }`. Then write one migration (`supabase/migrations/<14 digits>_<name>.sql`) with its down file, turning on row-level security with one policy per answer, apply it on staging with Workbench Control (`sb_migration_apply`), and refresh `supabase/staging-columns.json`.";
|
|
1146
|
+
readonly questions: readonly [{
|
|
1147
|
+
readonly id: "database.tables";
|
|
1148
|
+
readonly sets: "tables";
|
|
1149
|
+
readonly ask: "What should the app store, and who can see and change it?";
|
|
1150
|
+
readonly style: "table";
|
|
1151
|
+
readonly note: "Propose the tables the request needs, each with what one row is, its columns (name, kind such as text, number, money, date, yes/no, a link to another table; and whether it is required), and who may read and who may change its rows: `own` (the person who made the row), `admins`, `signed_in` (anyone signed in) or `public` (anyone, read only). Recommend `own` for anything about a person. Show money as whole cents, never a fraction. The client accepts, removes or adds tables and columns.";
|
|
1152
|
+
}];
|
|
1153
|
+
readonly not_asked: readonly [{
|
|
1154
|
+
readonly sets: "keys";
|
|
1155
|
+
readonly why: "The app reaches the database as the signed-in person (`dbAs`), so row-level security decides what each person sees; the template holds no secret key.";
|
|
1156
|
+
}, {
|
|
1157
|
+
readonly sets: "hosting";
|
|
1158
|
+
readonly why: "Each project has its own staging and production databases, made by Workbench Control in the region set for the install.";
|
|
1159
|
+
}, {
|
|
1160
|
+
readonly sets: "sign_in_settings";
|
|
1161
|
+
readonly why: "The database's sign-in settings follow the Workbench baseline (the workbench-sign-in skill).";
|
|
1162
|
+
}];
|
|
1163
|
+
};
|
|
912
1164
|
readonly delivery: {
|
|
913
1165
|
readonly choice: "built_in";
|
|
914
1166
|
readonly intro: "The app's own database, one for testing and one for the live site.";
|
|
915
|
-
readonly when: "The app needs to store or read data, or the client asks for a change to what is stored: a new table, column, rule or migration.";
|
|
916
1167
|
};
|
|
917
1168
|
};
|
|
918
1169
|
readonly email: {
|
|
@@ -1157,13 +1408,67 @@ export declare const capabilities: {
|
|
|
1157
1408
|
readonly cost_hint_usd: 0;
|
|
1158
1409
|
readonly note: "GET brand.json's logo.url from outside with no sign-in answers 200 with an image content type";
|
|
1159
1410
|
}];
|
|
1411
|
+
readonly offer: {
|
|
1412
|
+
readonly pattern: "settings";
|
|
1413
|
+
readonly when: "The app needs to send an email: a confirmation, receipt, notification, invitation or sign-in code.";
|
|
1414
|
+
readonly say: "I'll send these with Workbench email, which sends from the app's own address in your brand, with no email key in the app. The sign-in codes are already there; I'll add yours beside them.";
|
|
1415
|
+
readonly answers_file: "workbench/email.json";
|
|
1416
|
+
readonly answers_format: "`{ \"format\": 1, \"status\": \"draft\", \"reply_to\": null, \"emails\": [{ \"id\": \"order_confirmed\", \"when\": \"a customer sends an order\", \"to\": \"the customer\", \"parts\": [\"heading\", \"paragraph\", \"button\"], \"link_changes_something\": false }] }`. Declare each email in `src/lib/emails.ts` with `defineEmails` (stream `transactional`, shell `account`, `decidedBy` naming this answers file), put its words in the strings under `email.<id>.<key>`, and send it with `mailerFor(env)` and an `idempotencyKey` whenever a retry could repeat it.";
|
|
1417
|
+
readonly questions: readonly [{
|
|
1418
|
+
readonly id: "email.emails";
|
|
1419
|
+
readonly sets: "emails";
|
|
1420
|
+
readonly ask: "Which emails should the app send?";
|
|
1421
|
+
readonly style: "table";
|
|
1422
|
+
readonly note: "Propose the emails the request needs, each with an id (snake_case: `order_confirmed`), when it is sent, who gets it, its parts in order (heading, paragraphs, a button and its link, small print) and whether its link changes something (then the link lands on a confirmation page first). Only emails a person's own action or account causes: receipts, confirmations, notices. The client accepts, removes or adds. The wording is drafted afterwards, one email at a time.";
|
|
1423
|
+
}, {
|
|
1424
|
+
readonly id: "email.reply-to";
|
|
1425
|
+
readonly sets: "reply_to";
|
|
1426
|
+
readonly ask: "Where should a reply to these emails go?";
|
|
1427
|
+
readonly style: "cards";
|
|
1428
|
+
readonly answers: readonly [{
|
|
1429
|
+
readonly label: "Nowhere";
|
|
1430
|
+
readonly value: null;
|
|
1431
|
+
readonly means: "The emails come from an address that isn't read. Say so in the small print, with where to get help instead.";
|
|
1432
|
+
}, {
|
|
1433
|
+
readonly label: "To the app";
|
|
1434
|
+
readonly value: "app";
|
|
1435
|
+
readonly means: "Replies go to an address the app reads itself (Workbench inbound email).";
|
|
1436
|
+
}];
|
|
1437
|
+
readonly other: {
|
|
1438
|
+
readonly kind: "text";
|
|
1439
|
+
readonly min: 6;
|
|
1440
|
+
readonly max: 254;
|
|
1441
|
+
readonly description: "An email address someone at the business reads.";
|
|
1442
|
+
};
|
|
1443
|
+
readonly must_ask: true;
|
|
1444
|
+
readonly note: "A typed address goes in each send as `replyTo`; check it looks like an email address. With the app, the workbench-email-inbound skill sets up the address.";
|
|
1445
|
+
}];
|
|
1446
|
+
readonly not_asked: readonly [{
|
|
1447
|
+
readonly sets: "sender";
|
|
1448
|
+
readonly why: "Emails come from the project's own sending address (`FROM` in `src/lib/email.ts`) on its own domain, which Workbench Control sets up.";
|
|
1449
|
+
}, {
|
|
1450
|
+
readonly sets: "marketing";
|
|
1451
|
+
readonly why: "Only transactional email exists: newsletters and other bulk mail are refused until Workbench has a sender for them, so offer none.";
|
|
1452
|
+
}, {
|
|
1453
|
+
readonly sets: "look";
|
|
1454
|
+
readonly why: "Emails take the brand's name, colour and logo, and the project's `project.css` (see Restyling).";
|
|
1455
|
+
}];
|
|
1456
|
+
readonly drafts: {
|
|
1457
|
+
readonly of: "wording for one email";
|
|
1458
|
+
readonly surface: "ai";
|
|
1459
|
+
readonly alias: "claude-default";
|
|
1460
|
+
readonly count: 3;
|
|
1461
|
+
readonly max_rounds: 3;
|
|
1462
|
+
readonly job: "ai.drafts";
|
|
1463
|
+
readonly command: "npx workbench-ai drafts text email_<id> --about \"<what the email is for, who gets it and its parts>\"";
|
|
1464
|
+
};
|
|
1465
|
+
};
|
|
1160
1466
|
readonly styles: {
|
|
1161
1467
|
readonly prefix: "wb-email-";
|
|
1162
1468
|
};
|
|
1163
1469
|
readonly delivery: {
|
|
1164
1470
|
readonly choice: "built_in";
|
|
1165
1471
|
readonly intro: "The emails the app sends, such as confirmations, receipts and sign-in codes.";
|
|
1166
|
-
readonly when: "The app needs to send an email: a confirmation, receipt, notification, invitation or sign-in code.";
|
|
1167
1472
|
};
|
|
1168
1473
|
};
|
|
1169
1474
|
readonly "email-inbound": {
|
|
@@ -1266,10 +1571,45 @@ export declare const capabilities: {
|
|
|
1266
1571
|
readonly two_step: true;
|
|
1267
1572
|
readonly note: "cf_email_routing_rule_delete returns a confirmation and changes nothing until Robert approves";
|
|
1268
1573
|
}];
|
|
1574
|
+
readonly offer: {
|
|
1575
|
+
readonly pattern: "switch_on";
|
|
1576
|
+
readonly when: "The client wants the app to receive email: replies to its messages, or an address such as hello@ that the app handles.";
|
|
1577
|
+
readonly say: "The project can already receive email with Workbench inbound email: each address goes straight to the app, which decides what to do with every message. I'll switch on the addresses you want.";
|
|
1578
|
+
readonly answers_file: "workbench/email-inbound.json";
|
|
1579
|
+
readonly answers_format: "`{ \"format\": 1, \"addresses\": [{ \"name\": \"hello\", \"does\": \"saves it as a message the admins see\" }] }`. Then set `INBOUND_EMAIL` to `\"on\"` in `wrangler.jsonc`, write `handleEmail` in `src/app.ts` to do what each address `does`, and make the addresses with Workbench Control: `cf_email_routing_enable`, then `cf_email_routing_rule_create` for each address on staging first (the capabilities guide below has the steps).";
|
|
1580
|
+
readonly questions: readonly [{
|
|
1581
|
+
readonly id: "email-inbound.addresses";
|
|
1582
|
+
readonly sets: "addresses";
|
|
1583
|
+
readonly ask: "Which addresses should the app receive, and what should it do with each message?";
|
|
1584
|
+
readonly style: "table";
|
|
1585
|
+
readonly note: "Propose the addresses the request needs, each with its name (the part before @, lower case: `hello`, `bookings`) and what the app does with a message, in the client's words: save it for the admins to read, add it to a booking, answer it automatically. One address, one job. Say plainly that the app reads the mail itself: nothing is forwarded to anyone's inbox.";
|
|
1586
|
+
}, {
|
|
1587
|
+
readonly id: "email-inbound.attachments";
|
|
1588
|
+
readonly sets: "attachments";
|
|
1589
|
+
readonly ask: "What should happen to files attached to these emails?";
|
|
1590
|
+
readonly style: "cards";
|
|
1591
|
+
readonly answers: readonly [{
|
|
1592
|
+
readonly label: "Leave them out";
|
|
1593
|
+
readonly value: "drop";
|
|
1594
|
+
readonly means: "The app keeps the words of each email, not its files. Nothing extra to store or check.";
|
|
1595
|
+
}, {
|
|
1596
|
+
readonly label: "Keep them";
|
|
1597
|
+
readonly value: "keep";
|
|
1598
|
+
readonly means: "The app stores each file for the admins. Files can be large or unsafe, so keep only the types the job needs.";
|
|
1599
|
+
}];
|
|
1600
|
+
readonly note: "With keep, ask which types (photos, PDFs) and store them outside the Worker, in file storage the app already has, or ask the owner before adding some; refuse other types with `reject`.";
|
|
1601
|
+
}];
|
|
1602
|
+
readonly not_asked: readonly [{
|
|
1603
|
+
readonly sets: "domain";
|
|
1604
|
+
readonly why: "Addresses are on the project's own email domain: its staging name first, and the client's own domain once it is attached for production (`cf_zone_attach`).";
|
|
1605
|
+
}, {
|
|
1606
|
+
readonly sets: "forwarding";
|
|
1607
|
+
readonly why: "Workbench never forwards mail outside the project or catches every address: each literal address goes to the app.";
|
|
1608
|
+
}];
|
|
1609
|
+
};
|
|
1269
1610
|
readonly delivery: {
|
|
1270
1611
|
readonly choice: "optional";
|
|
1271
1612
|
readonly intro: "Receiving email at the project's own addresses, for the app to act on.";
|
|
1272
|
-
readonly when: "The client wants the app to receive email: replies to its messages, or an address such as hello@ that the app handles.";
|
|
1273
1613
|
};
|
|
1274
1614
|
};
|
|
1275
1615
|
readonly "email-sending": {
|
|
@@ -2729,10 +3069,45 @@ export declare const capabilities: {
|
|
|
2729
3069
|
readonly cost_hint_usd: 0;
|
|
2730
3070
|
readonly note: "The app's own checkStrings run passes in its tests, and npx workbench-strings check passes in CI";
|
|
2731
3071
|
}];
|
|
3072
|
+
readonly offer: {
|
|
3073
|
+
readonly pattern: "project";
|
|
3074
|
+
readonly when: "The client wants to add or change wording on a page or in an email, or asks about showing the app in another language.";
|
|
3075
|
+
readonly say: "Every word the app shows is kept in one place with Workbench strings, so changing wording never means hunting through code, and other languages can be added later.";
|
|
3076
|
+
readonly answers_file: "workbench/strings.json";
|
|
3077
|
+
readonly answers_format: "`{ \"format\": 1, \"languages\": [\"en-AU\"], \"later\": [\"fil\"] }`: the language the site is written in first, and any the client wants later. New wording goes into `strings/strings.<base>.json` with one key per sentence, and `npm run check:strings` checks it.";
|
|
3078
|
+
readonly questions: readonly [{
|
|
3079
|
+
readonly id: "strings.languages";
|
|
3080
|
+
readonly sets: "languages";
|
|
3081
|
+
readonly ask: "Does the site need to be in any language besides English?";
|
|
3082
|
+
readonly style: "cards";
|
|
3083
|
+
readonly answers: readonly [{
|
|
3084
|
+
readonly label: "English only";
|
|
3085
|
+
readonly value: "none";
|
|
3086
|
+
readonly means: "Every page and email is in English.";
|
|
3087
|
+
}, {
|
|
3088
|
+
readonly label: "Another language later";
|
|
3089
|
+
readonly value: "later";
|
|
3090
|
+
readonly means: "English now. The words are kept so a translation can be added, checked by a native speaker, without changing the code.";
|
|
3091
|
+
}];
|
|
3092
|
+
readonly other: {
|
|
3093
|
+
readonly kind: "text";
|
|
3094
|
+
readonly min: 2;
|
|
3095
|
+
readonly max: 120;
|
|
3096
|
+
readonly description: "The languages, such as Filipino or Spanish.";
|
|
3097
|
+
};
|
|
3098
|
+
readonly note: "Translations come with a Workbench tool that isn't built yet: record the languages under `later`, keep every word in the catalogue, and tell the client a translation goes live only once a named native speaker has checked it.";
|
|
3099
|
+
}];
|
|
3100
|
+
readonly not_asked: readonly [{
|
|
3101
|
+
readonly sets: "base";
|
|
3102
|
+
readonly why: "The site is written first in English, in the readers' spelling from the country answers, so it isn't asked again.";
|
|
3103
|
+
}, {
|
|
3104
|
+
readonly sets: "wording";
|
|
3105
|
+
readonly why: "Wording is content, not a setting: the client says what they want in their own words, or asks for drafts (the workbench-ai skill), and it goes into the catalogue.";
|
|
3106
|
+
}];
|
|
3107
|
+
};
|
|
2732
3108
|
readonly delivery: {
|
|
2733
3109
|
readonly choice: "built_in";
|
|
2734
3110
|
readonly intro: "The words on the app's screens and in its emails, kept in one place for each language.";
|
|
2735
|
-
readonly when: "The client wants to add or change wording on a page or in an email, or asks about showing the app in another language.";
|
|
2736
3111
|
};
|
|
2737
3112
|
};
|
|
2738
3113
|
readonly ui: {
|