@vibes.diy/api-svc 14.3.90 → 14.3.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/create-handler.d.ts +1 -0
  2. package/create-handler.js +1 -0
  3. package/create-handler.js.map +1 -1
  4. package/intern/agent-belt.d.ts +2 -3
  5. package/intern/agent-belt.js +6 -28
  6. package/intern/agent-belt.js.map +1 -1
  7. package/intern/agent-session-instruction.d.ts +3 -2
  8. package/intern/agent-session-instruction.js +3 -1
  9. package/intern/agent-session-instruction.js.map +1 -1
  10. package/intern/agent-skill-catalog.d.ts +12 -0
  11. package/intern/agent-skill-catalog.js +26 -0
  12. package/intern/agent-skill-catalog.js.map +1 -0
  13. package/intern/capi-build-event.d.ts +3 -1
  14. package/intern/capi-build-event.js +18 -1
  15. package/intern/capi-build-event.js.map +1 -1
  16. package/intern/capi-lead-event.d.ts +55 -0
  17. package/intern/capi-lead-event.js +169 -0
  18. package/intern/capi-lead-event.js.map +1 -0
  19. package/intern/capi-lead-ledger.d.ts +11 -0
  20. package/intern/capi-lead-ledger.js +75 -0
  21. package/intern/capi-lead-ledger.js.map +1 -0
  22. package/intern/design-repair.js +5 -6
  23. package/intern/design-repair.js.map +1 -1
  24. package/intern/instant-build.d.ts +66 -0
  25. package/intern/instant-build.js +458 -40
  26. package/intern/instant-build.js.map +1 -1
  27. package/intern/mercury-capability-triggers.d.ts +1 -1
  28. package/intern/mercury-capability-triggers.js +0 -81
  29. package/intern/mercury-capability-triggers.js.map +1 -1
  30. package/intern/mercury-first-build-seam.js +1 -1
  31. package/intern/mercury-first-build-seam.js.map +1 -1
  32. package/intern/mercury-first-build.d.ts +25 -15
  33. package/intern/mercury-first-build.js +114 -84
  34. package/intern/mercury-first-build.js.map +1 -1
  35. package/intern/opens-ast.d.ts +19 -0
  36. package/intern/opens-ast.js +75 -0
  37. package/intern/opens-ast.js.map +1 -0
  38. package/intern/opens-browser-globals.d.ts +1 -0
  39. package/intern/opens-browser-globals.js +217 -0
  40. package/intern/opens-browser-globals.js.map +1 -0
  41. package/intern/opens-check.d.ts +19 -1
  42. package/intern/opens-check.js +257 -279
  43. package/intern/opens-check.js.map +1 -1
  44. package/intern/opens-hooks.d.ts +7 -0
  45. package/intern/opens-hooks.js +176 -0
  46. package/intern/opens-hooks.js.map +1 -0
  47. package/intern/opens-inert.d.ts +10 -0
  48. package/intern/opens-inert.js +182 -0
  49. package/intern/opens-inert.js.map +1 -0
  50. package/intern/opens-scope.d.ts +61 -0
  51. package/intern/opens-scope.js +380 -0
  52. package/intern/opens-scope.js.map +1 -0
  53. package/intern/record-daily-visit.js +19 -1
  54. package/intern/record-daily-visit.js.map +1 -1
  55. package/intern/repair-ladder.d.ts +53 -0
  56. package/intern/repair-ladder.js +318 -0
  57. package/intern/repair-ladder.js.map +1 -0
  58. package/intern/save-build-pass.js +2 -2
  59. package/intern/save-build-pass.js.map +1 -1
  60. package/package.json +13 -13
  61. package/public/ensure-user-settings.d.ts +1 -1
  62. package/public/ensure-user-settings.js +23 -10
  63. package/public/ensure-user-settings.js.map +1 -1
  64. package/public/handle-prompt-context.js +12 -1
  65. package/public/handle-prompt-context.js.map +1 -1
  66. package/public/prompt-section-actions.js +7 -1
  67. package/public/prompt-section-actions.js.map +1 -1
  68. package/public/who-am-i.js +12 -1
  69. package/public/who-am-i.js.map +1 -1
  70. package/types.d.ts +2 -1
  71. package/types.js.map +1 -1
@@ -4,7 +4,8 @@ import { type } from "arktype";
4
4
  import { lazyType } from "@vibes.diy/api-types/lazy-type";
5
5
  import { sanitizeRoomShortlist } from "@vibes.diy/prompts";
6
6
  import { keepsToThemselves as keepsToThemselvesWords } from "./keeps-to-themselves.js";
7
- import { KNOWN_VALUES_DIRECT_CONTROLS } from "./agent-session-instruction.js";
7
+ import { KNOWN_VALUES_DIRECT_CONTROLS, PHONE_GAME_CONTROLS_LINE } from "./agent-session-instruction.js";
8
+ import { AGENT_SKILLS_PROSE, agentNameableSkills, agentSkillMenu } from "./agent-skill-catalog.js";
8
9
  import { poufRoomCatalog, poufRoomSlugs } from "./pouf-room-catalog.js";
9
10
  import { vibeFontCatalog, vibeFontSlugs } from "./vibe-font-catalog.js";
10
11
  import { slugWordBag } from "./ensure-slug-binding.js";
@@ -33,49 +34,45 @@ class MercuryTransientError extends Error {
33
34
  this.retryAfterMs = retryAfterMs;
34
35
  }
35
36
  }
36
- export const MERCURY_ARCHETYPES = ["personal", "group", "publish", "intake", "open", "toy"];
37
- export const MERCURY_FLAGS = [
38
- "ai",
39
- "composer",
40
- "imageGen",
41
- "backend",
42
- "calendar",
43
- "audio",
44
- "threeD",
45
- "charts",
46
- "boardGame",
47
- "sensitive",
48
- ];
37
+ export const MERCURY_APP_SHAPES = ["personal", "playful", "group", "publish", "intake", "open"];
38
+ export function readMercuryAppShape(word) {
39
+ const w = word.toLowerCase().match(/[a-z]+/)?.[0] ?? "";
40
+ if (w === "toy")
41
+ return "playful";
42
+ return MERCURY_APP_SHAPES.find((a) => a === w);
43
+ }
44
+ export const MERCURY_FLAGS = ["composer", "sensitive"];
49
45
  const COMPOSER_WORDS = /paste|photo|picture|own words|however they|repl(y|ies)|in their words|type in|jot/;
50
46
  const PRIVATE_WORDS = /\b(private|just for me|only me|for myself)\b/;
51
47
  const MULTIPLAYER_WORDS = /\b(friends? can join|multi-?players?|same world)\b/;
52
48
  export function fixMercuryShape(shape, personWords) {
53
49
  const m = personWords.toLowerCase();
54
- const composer = shape.flags.ai && shape.flags.composer && COMPOSER_WORDS.test(m);
50
+ const composer = shape.flags.composer && COMPOSER_WORDS.test(m);
55
51
  const privateAsk = PRIVATE_WORDS.test(m);
56
52
  const keepsToThemselves = keepsToThemselvesWords(personWords);
57
- const keepsPersonal = keepsToThemselves && shape.archetype !== "toy" && shape.archetype !== "personal";
58
- const archetype = keepsPersonal || (privateAsk && (shape.archetype === "open" || shape.archetype === "publish"))
53
+ const keepsPersonal = keepsToThemselves && shape.appShape !== "playful" && shape.appShape !== "personal";
54
+ const appShape = keepsPersonal || (privateAsk && (shape.appShape === "open" || shape.appShape === "publish"))
59
55
  ? "personal"
60
- : shape.archetype === "toy" && MULTIPLAYER_WORDS.test(m)
56
+ : shape.appShape === "playful" && MULTIPLAYER_WORDS.test(m)
61
57
  ? "open"
62
- : shape.archetype;
58
+ : shape.appShape;
63
59
  return {
64
60
  ...shape,
65
- archetype,
61
+ appShape,
66
62
  recordNames: shape.recordNames
67
63
  .map((r) => r.trim())
68
64
  .filter((r) => r.length > 0)
69
65
  .slice(0, 5),
70
- flags: { ...shape.flags, composer, threeD: shape.flags.threeD || mercuryBlockWorldNamed(personWords) },
66
+ flags: { ...shape.flags, composer },
71
67
  ...(keepsToThemselves ? { keepsToThemselves: true } : {}),
72
68
  };
73
69
  }
74
70
  const briefShapeParsed = lazyType(() => type({
75
- archetype: type.enumerated(...MERCURY_ARCHETYPES),
71
+ appShape: type.enumerated(...MERCURY_APP_SHAPES),
76
72
  coreAction: "string",
77
73
  recordNames: "string[]",
78
74
  capabilities: type.enumerated(...MERCURY_FLAGS).array(),
75
+ "skills?": "string[]",
79
76
  }));
80
77
  export function parseMercuryBriefShape(raw, personWords) {
81
78
  const p = briefShapeParsed(raw);
@@ -85,7 +82,7 @@ export function parseMercuryBriefShape(raw, personWords) {
85
82
  return Result.Err("mercury classification: empty coreAction");
86
83
  const caps = new Set(p.capabilities);
87
84
  const flags = Object.fromEntries(MERCURY_FLAGS.map((f) => [f, caps.has(f)]));
88
- return Result.Ok(fixMercuryShape({ archetype: p.archetype, coreAction: p.coreAction.trim(), recordNames: p.recordNames, flags }, personWords));
85
+ return Result.Ok(fixMercuryShape({ appShape: p.appShape, coreAction: p.coreAction.trim(), recordNames: p.recordNames, flags, skills: p.skills ?? [] }, personWords));
89
86
  }
90
87
  function setFlags(flags) {
91
88
  return MERCURY_FLAGS.filter((f) => flags[f]);
@@ -142,36 +139,28 @@ export function mercurySlugWordsSentence(words) {
142
139
  }
143
140
  export const MERCURY_ROOMS_HINT = "an array of 2 or 3 room slugs from the room list";
144
141
  export const MERCURY_FONT_HINT = "one font slug from the font list";
145
- const MERCURY_FIRST_PERSON_TRIGGER = " - a first-person walk around a place";
146
- export const MERCURY_BLOCK_WORLD_TRIGGER = " - a Minecraft-style block world to build and break blocks in";
147
- const MERCURY_BLOCK_WORLD_WORDS = /\b(minecraft|voxels?|block[- ]world|blocky|sandbox game)\b|\b(?:build|place|break|mine|dig)(?:s|ing)?(?:\s+(?:and|or|&)\s+(?:build|place|break|mine|dig)(?:s|ing)?)?\s+(?:the\s+|some\s+)?blocks\b/i;
148
- export function mercuryBlockWorldNamed(personWords) {
149
- return MERCURY_BLOCK_WORLD_WORDS.test(personWords);
150
- }
151
- const MERCURY_REAL_TIME_GAME_WORDS = /\b(arcade|runner|racer|racing game|shooter|shoot[- ]?'?em[- ]?up|platformer|snake game|asteroids|pinball|slingshot|catapult|flappy|endless run|dodge|joystick|swipe to)\b/i;
152
- export function mercuryRealTimeGameNamed(personWords) {
153
- return MERCURY_REAL_TIME_GAME_WORDS.test(personWords);
154
- }
155
- const MERCURY_LEAN_ARCHETYPES = `- personal: one person's own tool ("for me", "my", "private", a tracker, a log, a list, a journal) with no other people named.
142
+ const MERCURY_LEAN_APP_SHAPES = `Decide playful first: would people happily show off what gets made here?
143
+ - playful: a game, music or sound toy, drawing or pixel art, image or meme generator, quiz, bingo, or anything made for fun. What people make is shared with everyone who opens the app, even when the person says "my": "save my beats", "my drawings" and "my favorites" are playful.
144
+ Otherwise, the one that fits who takes part:
145
+ - personal: things private by nature, kept by one person about themselves: a journal, mood, health, money, habits, notes to self.
156
146
  - group: the person names other people who use it with them (we, our, friends, family, team, club, kids, housemates, partner, members, a foursome).
157
147
  - publish: the person writes and other people only read (a blog, menu, gallery, portfolio, schedule, zine, "everyone can read").
158
148
  - intake: strangers or customers submit something without signing up and the person sees the submissions (orders, bookings, requests, sign-ups, replies, reports from the public).
159
- - open: anyone with the link joins in and adds their own bit (a shared wall, canvas, feed, leaderboard game, community board, swap board, "anyone can post").
160
- - toy: a game, simulator, visual, sound or generator that keeps little or nothing (scores at most).`;
161
- const MERCURY_LEAN_CAPABILITIES = MERCURY_CAPABILITY_TRIGGERS.replace(/^A trigger is an example, not a password: [^\n]*?composer is set only together with ai\./, "composer goes with ai.");
149
+ - open: anyone with the link joins in and adds their own bit (a shared wall, canvas, feed, leaderboard game, community board, swap board, "anyone can post").`;
150
+ const MERCURY_LEAN_CAPABILITIES = MERCURY_CAPABILITY_TRIGGERS.replace(/^A trigger is an example, not a password: [^\n]*?composer is set only together with ai\./, "composer goes with callai.");
162
151
  function kebab(s) {
163
152
  return s
164
153
  .toLowerCase()
165
154
  .replace(/[^a-z0-9]+/g, "-")
166
155
  .replace(/^-+|-+$/g, "");
167
156
  }
168
- const LEADING_TAG = /^\s*\[(?:personal|group|publish|intake|open|toy)\]\s*/i;
169
- export function stripMercuryArchetypeTag(text) {
157
+ const LEADING_TAG = /^\s*\[(?:personal|playful|group|publish|intake|open|toy)\]\s*/i;
158
+ export function stripMercuryShapeTag(text) {
170
159
  return LEADING_TAG.test(text) ? { text: text.replace(LEADING_TAG, ""), repaired: true } : { text, repaired: false };
171
160
  }
172
161
  export function gateMercuryAddress(o) {
173
162
  const raw = o.title.trim();
174
- const cut = stripMercuryArchetypeTag(raw);
163
+ const cut = stripMercuryShapeTag(raw);
175
164
  const title = cut.text.trim().replace(/^["*]+|["*]+$/g, "");
176
165
  const own = kebab(title);
177
166
  if (title.length === 0 || own.length === 0)
@@ -194,30 +183,41 @@ export function mercurySlugsWithWord(pairs, words) {
194
183
  const ws = words.map((w) => kebab(w)).filter((w) => w.length > 0);
195
184
  return pairs.filter((p) => ws.some((w) => p.slug.includes(w))).length;
196
185
  }
197
- const FLAG_SKILLS = [
198
- ["ai", "callai"],
199
- ["imageGen", "image-gen"],
200
- ["backend", "backend"],
201
- ["calendar", "calendar"],
202
- ["audio", "web-audio"],
203
- ["threeD", "three-js"],
204
- ["charts", "d3"],
205
- ["boardGame", "board-games"],
206
- ];
207
- export function mercurySkills(shape, opts = {}) {
208
- const words = opts.personWords ?? "";
209
- const base = ["access", "fireproof", ...FLAG_SKILLS.filter(([f]) => shape.flags[f]).map(([, skill]) => skill)];
210
- const skills = mercuryBlockWorldNamed(words) ? [...base.filter((k) => k !== "three-js"), "voxel", "three-js"] : base;
211
- const realTime = mercuryBlockWorldNamed(words) || mercuryRealTimeGameNamed(words) || (shape.archetype === "toy" && shape.flags.threeD);
212
- return realTime ? [...skills, "touch-games"] : skills;
186
+ export function mercurySkills(shape) {
187
+ const out = [];
188
+ for (const k of ["access", "fireproof", ...shape.skills, ...(shape.flags.composer ? ["callai"] : [])])
189
+ if (!out.includes(k))
190
+ out.push(k);
191
+ return out;
192
+ }
193
+ export function gateMercurySkills(raw, opts) {
194
+ const nameable = agentNameableSkills(opts);
195
+ const skills = [];
196
+ const unknown = [];
197
+ for (const t of tailList(raw)) {
198
+ const name = t
199
+ .replace(/^[-*\u2022\s]+/, "")
200
+ .replace(/^["'`]+|["'`]+$/g, "")
201
+ .trim()
202
+ .toLowerCase();
203
+ if (name.length === 0 || /^(none|n\/a|nothing|-)$/.test(name))
204
+ continue;
205
+ if (!nameable.has(name)) {
206
+ if (!unknown.includes(name))
207
+ unknown.push(name);
208
+ }
209
+ else if (!skills.includes(name))
210
+ skills.push(name);
211
+ }
212
+ return { skills, unknown };
213
213
  }
214
214
  export function mercuryNature(shape, personWords) {
215
215
  if (shape.keepsToThemselves === true)
216
216
  return "sensitive";
217
217
  if (PRIVATE_WORDS.test(personWords.toLowerCase()))
218
218
  return "sensitive";
219
- const a = shape.archetype;
220
- if (a === "open" || a === "toy" || a === "publish")
219
+ const a = shape.appShape;
220
+ if (a === "open" || a === "playful" || a === "publish")
221
221
  return "open";
222
222
  return shape.flags.sensitive && (a === "personal" || a === "intake") ? "sensitive" : "collaborative";
223
223
  }
@@ -623,7 +623,7 @@ function tailList(v) {
623
623
  .trim())
624
624
  .filter((x) => x.length > 0 && /^(none|n\/a|nothing|-)$/i.test(x) === false);
625
625
  }
626
- export const MERCURY_CARDS_DEFAULT_ARCHETYPE = MERCURY_ARCHETYPES[0];
626
+ export const MERCURY_CARDS_DEFAULT_APP_SHAPE = MERCURY_APP_SHAPES[0];
627
627
  export const MERCURY_TESTER_REPLY_INTRO = "A fast tester's first read:";
628
628
  export const MERCURY_TESTER_REPLY_RULE = "Guesses only; the creator's words win.";
629
629
  export const MERCURY_SHARING_LINE = {
@@ -632,7 +632,7 @@ export const MERCURY_SHARING_LINE = {
632
632
  publish: "The owner writes; everything they publish is readable by anyone with the link, signed in or not. Shaped so a co-owner could be let in later.",
633
633
  intake: "Every visitor adds submissions to the shared list, signed in or not; a signed-in visitor edits only their own (a signed-out submission is create-only — the rules have no identity to check it against), and the signed-in owner reads and manages all of them. Shaped so staff could be let in later.",
634
634
  open: "Everyone with the link reads the shared records — posts, votes, entries — signed in or not; every signed-in visitor adds their own and edits only their own, and the owner can remove anything.",
635
- toy: "Anything players save — beats, levels, scores, creations — lands on one always-on public channel, readable by anyone with the link, signed in or not; each signed-in player edits only their own, and the owner can remove anything.",
635
+ playful: "Anything players save — beats, levels, scores, creations — lands on one always-on public channel, readable by anyone with the link, signed in or not; each signed-in player edits only their own, and the owner can remove anything.",
636
636
  };
637
637
  export const MERCURY_SHARING_KEEPS_TO_THEMSELVES = "Each person's records are theirs alone, and a one-tap publish shares a chosen one with everyone who can open the app.";
638
638
  export const MERCURY_SHARING_SENSITIVE = 'Each person\'s records are private to them from the first one, and sharing is theirs to turn on: a "Shared with followers" checkbox that starts unchecked, an add-buddies picker that grants one chosen handle, and a one-tap publish for a single finished item — never an always-on public channel for the records.';
@@ -642,30 +642,47 @@ export function mercurySharingLine(shape, personWords) {
642
642
  ? MERCURY_SHARING_KEEPS_TO_THEMSELVES
643
643
  : mercuryNature(shape, personWords) === "sensitive"
644
644
  ? MERCURY_SHARING_SENSITIVE
645
- : MERCURY_SHARING_LINE[shape.archetype];
645
+ : MERCURY_SHARING_LINE[shape.appShape];
646
646
  return `${MERCURY_SHARING_PREFIX} ${line}`;
647
647
  }
648
- export function mercuryTesterReplyEnrichedPrompt(reply, personWords, shape) {
648
+ export function mercuryReplyForBuilder(reply) {
649
+ const lines = reply.split("\n");
650
+ const at = lines.findIndex((l) => APP_MARKER_LINE.test(l));
651
+ if (at < 0)
652
+ return reply;
653
+ return [...lines.slice(0, at + 1), ...lines.slice(at + 1).filter((l) => !isShapeTailLine(l))].join("\n");
654
+ }
655
+ function isShapeTailLine(line) {
656
+ const k = TAIL_KEY_LINE.exec(line);
657
+ return k !== null && SHAPE_TAIL_KEYS.has(k[1].toLowerCase());
658
+ }
659
+ const SHAPE_TAIL_KEYS = new Set(["shape", "archetype"]);
660
+ export function mercuryTesterReplyEnrichedPrompt(reply, personWords, shape, skills = mercurySkills(shape)) {
649
661
  return [
650
662
  MERCURY_TESTER_REPLY_INTRO,
651
- `<tester-reply>\n${reply}\n</tester-reply>`,
663
+ `<tester-reply>\n${mercuryReplyForBuilder(reply)}\n</tester-reply>`,
652
664
  MERCURY_TESTER_REPLY_RULE,
653
665
  mercurySharingLine(shape, personWords),
654
- ...mercuryCardsBuilderLines(shape),
666
+ ...mercuryCardsBuilderLines(shape, skills),
655
667
  `The app creator's initial prompt: "${personWords}"`,
656
668
  ].join("\n\n");
657
669
  }
658
670
  export const MERCURY_COMPOSER_LINE = "The records come in through a sentence composer (Add and Ask) as the front door.";
659
- export function mercuryCardsBuilderLines(shape) {
660
- return [shape.flags.composer ? MERCURY_COMPOSER_LINE : "", KNOWN_VALUES_DIRECT_CONTROLS].filter((l) => l.length > 0);
671
+ export function mercuryCardsBuilderLines(shape, skills = []) {
672
+ return [
673
+ shape.flags.composer ? MERCURY_COMPOSER_LINE : "",
674
+ KNOWN_VALUES_DIRECT_CONTROLS,
675
+ skills.includes("touch-games") ? PHONE_GAME_CONTROLS_LINE : "",
676
+ ].filter((l) => l.length > 0);
661
677
  }
662
678
  export const MERCURY_CARDS_MARKER = "--- CARDS ---";
663
679
  export const MERCURY_CARDS_APP_MARKER = "--- APP ---";
664
680
  export const MERCURY_CARDS_TAIL_HINTS = {
665
681
  title: "the app's name, two or three words in its own vocabulary",
666
682
  slugs: "three short kebab-case addresses for the app, comma-separated",
667
- archetype: "one of personal, group, publish, intake, open, toy: the ownership shape that fits the person's words",
683
+ shape: "one of playful, personal, group, publish, intake, open: playful first when people would happily show off what gets made",
668
684
  capabilities: "the capabilities this request needs, from the capability list, comma-separated; none when it needs none",
685
+ skills: "the skills this build loads, by name from the skill list, comma-separated; none when it needs none",
669
686
  };
670
687
  export const MERCURY_CARDS_PREAMBLE_RULE = `Start with a short paragraph to the app creator, in plain warm words: what you're making for them and its most important workflow, walked through the way they'll do it. Say "probably" where you're guessing.`;
671
688
  export const MERCURY_CARDS_SHAPE = `As a <role>, I <action on a named record>, so that <outcome>.
@@ -681,7 +698,9 @@ export const MERCURY_CARDS_RULES = `Rules:
681
698
  - A line that rests on your assumption and not on the person's words ends with (guess). A line the person's words settle carries no label.
682
699
  - ${MERCURY_ASK_WORDS_RULE}
683
700
  - ${MERCURY_TYPED_DATA_RULE}`;
684
- const MERCURY_CARDS_CAPABILITIES = MERCURY_LEAN_CAPABILITIES.replace(MERCURY_FIRST_PERSON_TRIGGER, `${MERCURY_FIRST_PERSON_TRIGGER}\n${MERCURY_BLOCK_WORLD_TRIGGER}`);
701
+ export function mercuryCardsSkillList(o) {
702
+ return `${AGENT_SKILLS_PROSE}${agentSkillMenu({ poufBlocks: o.poufBlocks })}`;
703
+ }
685
704
  export function mercuryCardsSystem(o = {}) {
686
705
  const keys = {
687
706
  ...MERCURY_CARDS_TAIL_HINTS,
@@ -702,11 +721,14 @@ ${MERCURY_CARDS_RULES}
702
721
  Then a line reading exactly "${MERCURY_CARDS_APP_MARKER}", then one line per key, as "key: value":
703
722
  ${tailLines}
704
723
 
705
- archetype, exactly one:
706
- ${MERCURY_LEAN_ARCHETYPES}
724
+ shape, exactly one:
725
+ ${MERCURY_LEAN_APP_SHAPES}
707
726
 
708
727
  capabilities: each capability the request itself needs, from this list; none when it needs none:
709
- ${MERCURY_CARDS_CAPABILITIES}
728
+ ${MERCURY_LEAN_CAPABILITIES}
729
+
730
+ skills: each skill this build loads, by name from this list; none when it needs none:
731
+ ${mercuryCardsSkillList({ poufBlocks: o.poufBlocks === true })}
710
732
 
711
733
  ${MERCURY_ADDRESS.rule}`;
712
734
  }
@@ -720,11 +742,11 @@ export function mercuryCardsMessages(o) {
720
742
  ...(o.words.length > 0 ? ["", mercurySlugWordsSentence(o.words)] : []),
721
743
  ].join("\n");
722
744
  return [
723
- { role: "system", content: [{ type: "text", text: mercuryCardsSystem({ rooms, font }) }] },
745
+ { role: "system", content: [{ type: "text", text: mercuryCardsSystem({ rooms, font, poufBlocks: o.poufBlocks === true }) }] },
724
746
  { role: "user", content: [{ type: "text", text: user }] },
725
747
  ];
726
748
  }
727
- const CARDS_TAIL_KEYS = new Set([...Object.keys(MERCURY_CARDS_TAIL_HINTS), "rooms", "font"]);
749
+ const CARDS_TAIL_KEYS = new Set([...Object.keys(MERCURY_CARDS_TAIL_HINTS), "rooms", "font", "archetype"]);
728
750
  const CARDS_MARKER_LINE = /^[ \t]*[*_#>-]*[ \t]*-{2,}[ \t]*CARDS[ \t]*-{2,}[ \t]*[*_]*[ \t]*$/i;
729
751
  const APP_MARKER_LINE = /^[ \t]*[*_#>-]*[ \t]*-{2,}[ \t]*APP[ \t]*-{2,}[ \t]*[*_]*[ \t]*$/i;
730
752
  const CARD_LINE = /^\s*(?:[-*•]|\d+[.)])?\s*(?:\*\*)?As an? \S/i;
@@ -807,17 +829,18 @@ export function gateMercuryCards(raw, o) {
807
829
  else if (!caps.includes(flag))
808
830
  caps.push(flag);
809
831
  }
810
- const named = tailScalar(p.keys.archetype)
811
- .toLowerCase()
812
- .match(/[a-z]+/)?.[0] ?? "";
813
- const known = MERCURY_ARCHETYPES.find((a) => a === named);
832
+ const named = gateMercurySkills(p.keys.skills, { poufBlocks: o.poufBlocks === true });
833
+ for (const u of named.unknown)
834
+ miss("skills", `unknown:${u.slice(0, 40)}`);
835
+ const known = readMercuryAppShape(tailScalar(p.keys.shape ?? p.keys.archetype));
814
836
  if (known === undefined)
815
- miss("archetype", "default");
837
+ miss("shape", "default");
816
838
  const rShape = parseMercuryBriefShape({
817
- archetype: known ?? MERCURY_CARDS_DEFAULT_ARCHETYPE,
839
+ appShape: known ?? MERCURY_CARDS_DEFAULT_APP_SHAPE,
818
840
  coreAction: o.personWords.trim(),
819
841
  recordNames: p.dataRecords.slice(0, 5),
820
842
  capabilities: caps,
843
+ skills: [...named.skills],
821
844
  }, o.personWords);
822
845
  if (rShape.isErr())
823
846
  return Result.Err(`classification:${rShape.Err().message}`);
@@ -1060,10 +1083,11 @@ export function assembleMercuryCardsCreateVibe(o) {
1060
1083
  const s = o.brief.shape;
1061
1084
  const nature = mercuryNature(s, o.personWords);
1062
1085
  const rooms = sanitizeRoomShortlist("pouf", o.brief.rooms?.rooms);
1086
+ const skills = mercurySkills(s);
1063
1087
  return {
1064
1088
  prompt: o.personWords,
1065
- enrichedPrompt: mercuryTesterReplyEnrichedPrompt(o.reply, o.personWords, s),
1066
- skills: mercurySkills(s, { personWords: o.personWords }),
1089
+ enrichedPrompt: mercuryTesterReplyEnrichedPrompt(o.reply, o.personWords, s, skills),
1090
+ skills,
1067
1091
  ...(o.brief.address.pairs.length === 0 ? {} : { pairs: o.brief.address.pairs.map((p) => ({ title: p.title, slug: p.slug })) }),
1068
1092
  nature,
1069
1093
  ...(nature === "sensitive" ? { privacySensitive: true } : {}),
@@ -1085,7 +1109,7 @@ function titleFromSlug(slug) {
1085
1109
  .join(" ");
1086
1110
  }
1087
1111
  function looseMercuryAddress(title, slugs) {
1088
- const cut = stripMercuryArchetypeTag((title ?? "").trim());
1112
+ const cut = stripMercuryShapeTag((title ?? "").trim());
1089
1113
  const own = cut.text.trim().replace(/^["*]+|["*]+$/g, "");
1090
1114
  const kept = [...new Set(slugs.map(kebab).filter((x) => x.length > 0))].slice(0, 3);
1091
1115
  const name = own.length > 0 && kebab(own).length > 0 ? own : kept.length > 0 ? titleFromSlug(kept[0]) : "";
@@ -1119,9 +1143,15 @@ async function mercuryCardsBriefCall(vctx, o) {
1119
1143
  aborted: false,
1120
1144
  }));
1121
1145
  const request = (words) => () => ({
1122
- messages: mercuryCardsMessages({ personWords: o.personWords, words, rooms: o.askRooms, font: o.askRooms }),
1146
+ messages: mercuryCardsMessages({
1147
+ personWords: o.personWords,
1148
+ words,
1149
+ rooms: o.askRooms,
1150
+ font: o.askRooms,
1151
+ poufBlocks: o.askRooms,
1152
+ }),
1123
1153
  });
1124
- const gate = (raw) => gateMercuryCards(raw, { personWords: o.personWords, askRooms: o.askRooms });
1154
+ const gate = (raw) => gateMercuryCards(raw, { personWords: o.personWords, askRooms: o.askRooms, poufBlocks: o.askRooms });
1125
1155
  const attempt = {
1126
1156
  call: "brief",
1127
1157
  deadlineMs: o.deadlineMs,