appostle-installer 0.0.18 → 0.0.19
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/dist/appostle.js +135 -71
- package/dist/appostle.js.map +2 -2
- package/dist/schema-templates/colors.md +12 -0
- package/dist/schema-templates/typography.md +66 -0
- package/dist/worker.js +135 -71
- package/dist/worker.js.map +2 -2
- package/package.json +1 -1
package/dist/appostle.js
CHANGED
|
@@ -3203,6 +3203,7 @@ var RoleMoveResponseSchema = z10.object({
|
|
|
3203
3203
|
var BrandScopeSchema = z10.literal("project");
|
|
3204
3204
|
var BrandVariableTypeSchema = z10.enum([
|
|
3205
3205
|
"color",
|
|
3206
|
+
"gradient",
|
|
3206
3207
|
"font",
|
|
3207
3208
|
"asset",
|
|
3208
3209
|
"text",
|
|
@@ -35111,6 +35112,7 @@ ${original}`;
|
|
|
35111
35112
|
// ../server/src/server/brand/token-generator.ts
|
|
35112
35113
|
import { z as z37 } from "zod";
|
|
35113
35114
|
var HEX6 = /^#[0-9a-f]{6}$/i;
|
|
35115
|
+
var CSS_GRADIENT = /^(linear|radial|conic)-gradient\(/i;
|
|
35114
35116
|
var TokensResponseSchema = z37.object({
|
|
35115
35117
|
tokens: z37.record(z37.string(), z37.string())
|
|
35116
35118
|
});
|
|
@@ -35186,6 +35188,31 @@ function buildPrompt2(args) {
|
|
|
35186
35188
|
}
|
|
35187
35189
|
}
|
|
35188
35190
|
const tokensToFill = args.unlockedTokens.map((v) => `- ${v.key}`).join("\n");
|
|
35191
|
+
const gradientSection = args.unlockedGradients.length > 0 ? [
|
|
35192
|
+
"",
|
|
35193
|
+
"Gradient tokens to fill (values must be valid CSS gradient strings,",
|
|
35194
|
+
"e.g. linear-gradient(135deg, #c52669 0%, #ff6b35 100%)):",
|
|
35195
|
+
...args.unlockedGradients.map((v) => `- ${v.key}`),
|
|
35196
|
+
"",
|
|
35197
|
+
"Gradient key glossary:",
|
|
35198
|
+
"- gradient.primary \u2014 the brand's signature gradient, built from the",
|
|
35199
|
+
" most prominent palette colours. Used for hero backgrounds, feature",
|
|
35200
|
+
" highlights, and primary decorative surfaces.",
|
|
35201
|
+
"- gradient.accent \u2014 a secondary gradient for interactive states,",
|
|
35202
|
+
" hover effects, or accent surfaces. Distinct from primary but",
|
|
35203
|
+
" harmonious with the palette.",
|
|
35204
|
+
"- gradient.subtle \u2014 a very soft, low-contrast gradient for subtle",
|
|
35205
|
+
" background washes \u2014 card backgrounds, section tints. Should feel",
|
|
35206
|
+
" almost invisible; same hue family as bg-base with minimal shift.",
|
|
35207
|
+
"",
|
|
35208
|
+
"Include gradient values in the same JSON under the 'tokens' key,",
|
|
35209
|
+
"alongside the hex tokens. Gradient values are CSS gradient strings."
|
|
35210
|
+
].join("\n") : "";
|
|
35211
|
+
const lockedGradientSection = args.lockedGradients.length > 0 ? [
|
|
35212
|
+
"",
|
|
35213
|
+
"Locked gradients \u2014 DO NOT include these in your output:",
|
|
35214
|
+
...args.lockedGradients.map((v) => `- ${v.key}: ${v.value}`)
|
|
35215
|
+
].join("\n") : "";
|
|
35189
35216
|
const lockedSection = args.lockedTokens.length > 0 ? [
|
|
35190
35217
|
"",
|
|
35191
35218
|
"Locked tokens \u2014 DO NOT include these in your output. The user has",
|
|
@@ -35232,13 +35259,15 @@ function buildPrompt2(args) {
|
|
|
35232
35259
|
"Tokens to fill (every value must be a #RRGGBB lowercase hex string):",
|
|
35233
35260
|
tokensToFill,
|
|
35234
35261
|
lockedSection,
|
|
35262
|
+
gradientSection,
|
|
35263
|
+
lockedGradientSection,
|
|
35235
35264
|
directionSection,
|
|
35236
35265
|
"",
|
|
35237
35266
|
"Return JSON only with the shape:",
|
|
35238
|
-
'{ "tokens": { "<token-key>": "#rrggbb", ... } }',
|
|
35267
|
+
'{ "tokens": { "<token-key>": "#rrggbb or css-gradient-string", ... } }',
|
|
35239
35268
|
"",
|
|
35240
|
-
"Output ONLY the unlocked tokens listed above. Do not include
|
|
35241
|
-
"
|
|
35269
|
+
"Output ONLY the unlocked tokens and gradients listed above. Do not include",
|
|
35270
|
+
"locked entries, do not invent new keys, do not output anything outside the JSON.",
|
|
35242
35271
|
"Never use a hex value from a colour marked OFF."
|
|
35243
35272
|
].filter((line) => line !== "").join("\n");
|
|
35244
35273
|
}
|
|
@@ -35252,9 +35281,12 @@ async function generateAndApplyBrandTokens(options) {
|
|
|
35252
35281
|
const allVars = colorsBrand.variables;
|
|
35253
35282
|
const paletteVars = allVars.filter((v) => v.key.startsWith("color."));
|
|
35254
35283
|
const tokenVars = allVars.filter((v) => v.key.startsWith("token."));
|
|
35284
|
+
const gradientVars = allVars.filter((v) => v.type === "gradient");
|
|
35255
35285
|
const unlockedTokens = tokenVars.filter((v) => !v.locked);
|
|
35256
35286
|
const lockedTokens = tokenVars.filter((v) => v.locked);
|
|
35257
|
-
|
|
35287
|
+
const unlockedGradients = gradientVars.filter((v) => !v.locked);
|
|
35288
|
+
const lockedGradients = gradientVars.filter((v) => v.locked);
|
|
35289
|
+
if (unlockedTokens.length === 0 && unlockedGradients.length === 0) {
|
|
35258
35290
|
logger.info({ brandPath }, "brand-tokens: nothing to generate (all tokens locked)");
|
|
35259
35291
|
return { generatedCount: 0 };
|
|
35260
35292
|
}
|
|
@@ -35265,6 +35297,8 @@ async function generateAndApplyBrandTokens(options) {
|
|
|
35265
35297
|
paletteVars,
|
|
35266
35298
|
unlockedTokens,
|
|
35267
35299
|
lockedTokens,
|
|
35300
|
+
unlockedGradients,
|
|
35301
|
+
lockedGradients,
|
|
35268
35302
|
mode,
|
|
35269
35303
|
userPrompt: prompt
|
|
35270
35304
|
});
|
|
@@ -35290,14 +35324,19 @@ async function generateAndApplyBrandTokens(options) {
|
|
|
35290
35324
|
}
|
|
35291
35325
|
throw error;
|
|
35292
35326
|
}
|
|
35293
|
-
const
|
|
35327
|
+
const unlockedTokenKeySet = new Set(unlockedTokens.map((v) => v.key));
|
|
35328
|
+
const unlockedGradientKeySet = new Set(unlockedGradients.map((v) => v.key));
|
|
35294
35329
|
const acceptedUpdates = /* @__PURE__ */ new Map();
|
|
35295
35330
|
for (const [key, value] of Object.entries(response.tokens)) {
|
|
35296
|
-
if (!unlockedKeySet.has(key)) continue;
|
|
35297
35331
|
if (typeof value !== "string") continue;
|
|
35298
35332
|
const trimmed = value.trim();
|
|
35299
|
-
if (
|
|
35300
|
-
|
|
35333
|
+
if (unlockedTokenKeySet.has(key)) {
|
|
35334
|
+
if (!HEX6.test(trimmed)) continue;
|
|
35335
|
+
acceptedUpdates.set(key, trimmed.toLowerCase());
|
|
35336
|
+
} else if (unlockedGradientKeySet.has(key)) {
|
|
35337
|
+
if (!CSS_GRADIENT.test(trimmed)) continue;
|
|
35338
|
+
acceptedUpdates.set(key, trimmed);
|
|
35339
|
+
}
|
|
35301
35340
|
}
|
|
35302
35341
|
if (acceptedUpdates.size === 0) {
|
|
35303
35342
|
logger.warn(
|
|
@@ -35334,6 +35373,7 @@ import path20 from "node:path";
|
|
|
35334
35373
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
35335
35374
|
import { z as z38 } from "zod";
|
|
35336
35375
|
var QA_FILENAME = "layout-qa.md";
|
|
35376
|
+
var PROMPT_FILENAME = "layout-prompt.md";
|
|
35337
35377
|
var MAX_LOOKUP_LEVELS = 10;
|
|
35338
35378
|
var ROLE_FILE_RELATIVE = ".appostle/brand/assets/role/brand-layout-role.md";
|
|
35339
35379
|
async function findFileUpward(filename) {
|
|
@@ -35492,61 +35532,90 @@ async function loadQaQuestions(logger) {
|
|
|
35492
35532
|
}
|
|
35493
35533
|
return EMBEDDED_QA_FALLBACK;
|
|
35494
35534
|
}
|
|
35495
|
-
|
|
35535
|
+
async function loadLayoutPrompt(logger) {
|
|
35536
|
+
const filePath = await findFileUpward(PROMPT_FILENAME);
|
|
35537
|
+
if (filePath) {
|
|
35538
|
+
try {
|
|
35539
|
+
const content = await fs12.readFile(filePath, "utf8");
|
|
35540
|
+
logger.debug({ filePath }, "layout-generator: loaded layout prompt from disk");
|
|
35541
|
+
return content;
|
|
35542
|
+
} catch (err) {
|
|
35543
|
+
logger.warn(
|
|
35544
|
+
{ err, filePath },
|
|
35545
|
+
"layout-generator: failed to read layout-prompt file; using embedded fallback"
|
|
35546
|
+
);
|
|
35547
|
+
}
|
|
35548
|
+
}
|
|
35549
|
+
return EMBEDDED_LAYOUT_PROMPT_FALLBACK;
|
|
35550
|
+
}
|
|
35551
|
+
function interpolateTemplate(template, vars) {
|
|
35552
|
+
let out = template;
|
|
35553
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
35554
|
+
out = out.split(`{{${key}}}`).join(value);
|
|
35555
|
+
}
|
|
35556
|
+
return out;
|
|
35557
|
+
}
|
|
35558
|
+
var EMBEDDED_LAYOUT_PROMPT_FALLBACK = `You are an elite art director refining the layout and composition rules for a brand. This is ONLY about layout \u2014 structure, zones, grid, density, rhythm, containers, image placement, CTAs. Typography, colors, motion, animations, shadows are handled by separate brand files \u2014 do not duplicate them here.
|
|
35496
35559
|
|
|
35497
|
-
|
|
35560
|
+
These rules must work across media \u2014 web pages, print, PDF, presentations. Use proportional language (fractions, ratios, percentages) as the primary system. CSS values are welcome as concrete examples but should not be the only expression of a rule.
|
|
35498
35561
|
|
|
35499
|
-
|
|
35562
|
+
Your job: update the design role document based on the user's refinement prompt. This document will be injected wholesale into an AI builder's context. If your rules are vague, the output will be generic. If your rules are specific and opinionated, the output will be distinctive.
|
|
35500
35563
|
|
|
35501
|
-
|
|
35564
|
+
The document must be a complete markdown document covering:
|
|
35565
|
+
- Enemy (a specific design this must NOT resemble, with 2-3 specific layout patterns that make it wrong \u2014 and for each, the exact counter-pattern this brand uses instead)
|
|
35566
|
+
- Signature Anomaly (the one structural layout choice that makes this unmistakably distinctive)
|
|
35567
|
+
- Compositional Philosophy (one structural law + the structural spine mechanism that enforces it across every zone)
|
|
35568
|
+
- Intensity Dials (Density 1-10, Grid Variance 1-10 \u2014 with concrete implications for THIS brand)
|
|
35569
|
+
- Opening Zone / Hero Behavior (surface coverage, bleed, content placement, containment)
|
|
35570
|
+
- Zone Inventory (ordered list of all major zones: name, layout job, dense/airy, full-bleed/contained \u2014 this is the master reference for the rhythm)
|
|
35571
|
+
- Zone Variation & Flow (how zones differ, transition strategy, full-bleed vs contained \u2014 references the Zone Inventory)
|
|
35572
|
+
- Density Philosophy (spacing values; the oscillation rhythm derived from the Zone Inventory \u2014 name which zones are dense, which are airy, in sequence)
|
|
35573
|
+
- Vertical Rhythm (three values: zone padding, element gap within zones, and the one zone that breaks the rhythm and why)
|
|
35574
|
+
- Grid System (exact column structure, asymmetry, proportions)
|
|
35575
|
+
- Content Width Strategy (max-width of the primary content area, which zones break it and why)
|
|
35576
|
+
- Container & Card Rules (borders, corners, nesting, exact border-radius)
|
|
35577
|
+
- Dividers & Graphic Structure (structural vs decorative, background strategy)
|
|
35578
|
+
- Image Treatment \u2014 Layout Only (placement, grid relationship, overlap behavior)
|
|
35579
|
+
- CTA Strategy (position in layout referencing Zone Inventory, container strategy, frequency)
|
|
35580
|
+
- Responsive Behavior (how the layout adapts across sizes \u2014 web: breakpoints and stacking; print: scale and margin strategy)
|
|
35581
|
+
- Bans (at least 20 specific layout prohibitions)
|
|
35502
35582
|
|
|
35503
|
-
##
|
|
35504
|
-
The north star. 3-5 sentences describing the fundamental spatial personality.
|
|
35583
|
+
## Critical rules
|
|
35505
35584
|
|
|
35506
|
-
|
|
35507
|
-
Exact rules for hero sections \u2014 dimensions, content placement, image treatment, scroll behavior, what's forbidden.
|
|
35585
|
+
1. This is a REFINEMENT. The user already has a role document (shown below). Their prompt refines, evolves, or redirects \u2014 it does NOT start from scratch unless they explicitly say so.
|
|
35508
35586
|
|
|
35509
|
-
|
|
35510
|
-
How consecutive sections differ. Allowed arrangements. How many types cycle. Full-bleed rules.
|
|
35587
|
+
2. SPECIFICITY IS MANDATORY. Every rule must be actionable at the structural level. Generic adjectives ("clean", "minimal", "modern", "elegant") are BANNED from the output.
|
|
35511
35588
|
|
|
35512
|
-
|
|
35513
|
-
Where dense, where airy, specific spacing ratios, padding rules.
|
|
35589
|
+
3. THE DOCUMENT MUST BE OPINIONATED, NOT HEDGED. Every sentence must prescribe or ban \u2014 never suggest. Use "Always", "Never", "Must", "Banned", "Required".
|
|
35514
35590
|
|
|
35515
|
-
|
|
35516
|
-
Section height strategy, padding patterns, oscillation rules, breathing room logic.
|
|
35591
|
+
4. STAY IN YOUR LANE. Do NOT include rules about typography, colors, motion/animation, or shadows \u2014 those belong in their own brand files.
|
|
35517
35592
|
|
|
35518
|
-
|
|
35519
|
-
Column philosophy, alignment rules, breakpoint behavior, visible vs invisible structure.
|
|
35593
|
+
5. THE BANS ARE THE MOST IMPORTANT SECTION. At least 20 bans. The 7 universal AI-layout cliches MUST each appear as an explicit named ban: centered headline over full-width image; three equal-width feature cards in a row; alternating left-right image/text rows with identical padding; uniform zone height and padding; every block wrapped in a card with shadow + radius; CTA with gradient fill; full-width "Why Choose Us" icon grid. Plus 3-5 brand-specific AI tells.
|
|
35520
35594
|
|
|
35521
|
-
|
|
35522
|
-
Borders, shadows, corners, nesting rules, elevation hierarchy.
|
|
35595
|
+
6. THE ENEMY IS MANDATORY. Name a specific real design and 2-3 exact layout patterns from it. Vague enemies fail.
|
|
35523
35596
|
|
|
35524
|
-
|
|
35525
|
-
Aspect ratios, size constraints, cropping rules, filter/overlay rules, frequency.
|
|
35597
|
+
7. THE SIGNATURE ANOMALY IS MANDATORY. One specific structural choice. Apply the removal test: if removing this single decision would make the layout indistinguishable from a generic design \u2014 it qualifies. Must appear as a constraint in at least 2 other sections.
|
|
35526
35598
|
|
|
35527
|
-
|
|
35528
|
-
Frequency per page, hierarchy, styling constraints, placement rules.
|
|
35599
|
+
8. USE THE STRUCTURAL CONTEXT. The {{structuralContext}} contains sibling brand files. Do not duplicate their rules \u2014 derive layout implications from them.
|
|
35529
35600
|
|
|
35530
|
-
##
|
|
35531
|
-
|
|
35532
|
-
|
|
35533
|
-
|
|
35534
|
-
|
|
35535
|
-
|
|
35536
|
-
|
|
35537
|
-
|
|
35538
|
-
|
|
35539
|
-
|
|
35540
|
-
|
|
35541
|
-
|
|
35542
|
-
|
|
35543
|
-
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
|
|
35547
|
-
|
|
35548
|
-
- The document should be 50-100 rules total across all sections`;
|
|
35549
|
-
function buildQaSystemPrompt(questions) {
|
|
35601
|
+
## Current role document
|
|
35602
|
+
|
|
35603
|
+
{{currentValues}}
|
|
35604
|
+
|
|
35605
|
+
## Structural context (sibling brand files)
|
|
35606
|
+
|
|
35607
|
+
{{structuralContext}}
|
|
35608
|
+
|
|
35609
|
+
## User's refinement prompt
|
|
35610
|
+
|
|
35611
|
+
{{userPrompt}}
|
|
35612
|
+
|
|
35613
|
+
## Response format
|
|
35614
|
+
|
|
35615
|
+
Return ONLY a JSON object: { "roleDocument": "# Brand Layout Role\\n\\n## Enemy\\n..." }
|
|
35616
|
+
|
|
35617
|
+
Output ONLY the JSON object. No markdown fences. No explanation.`;
|
|
35618
|
+
function buildQaSystemPrompt(questions, layoutPromptSpec) {
|
|
35550
35619
|
return `You are a chill creative director doing a quick vibe check on someone's layout taste.
|
|
35551
35620
|
|
|
35552
35621
|
CONTEXT:
|
|
@@ -35580,10 +35649,11 @@ When done=false, return:
|
|
|
35580
35649
|
{ "done": false, "question": "Short question?", "options": ["Option A", "Option B", "Option C", "Option D"] }
|
|
35581
35650
|
|
|
35582
35651
|
When done=true, return:
|
|
35583
|
-
{ "done": true, "roleDocument": "# Brand Layout Role\\n\\n##
|
|
35652
|
+
{ "done": true, "roleDocument": "# Brand Layout Role\\n\\n## Enemy\\n..." }
|
|
35653
|
+
|
|
35654
|
+
The roleDocument must be a complete markdown document following the structure and rules below. Treat the Q&A conversation as the user's intent; there is no current role document yet.
|
|
35584
35655
|
|
|
35585
|
-
|
|
35586
|
-
${ROLE_GENERATION_INSTRUCTIONS}`;
|
|
35656
|
+
${layoutPromptSpec}`;
|
|
35587
35657
|
}
|
|
35588
35658
|
async function layoutQaNext(options) {
|
|
35589
35659
|
const { agentManager, workspaceRoot, brandPath, conversation, logger } = options;
|
|
@@ -35592,7 +35662,13 @@ async function layoutQaNext(options) {
|
|
|
35592
35662
|
if (!brand) throw new Error(`Brand file not found at ${brandPath}`);
|
|
35593
35663
|
const structuralContext = buildStructuralContext(brands);
|
|
35594
35664
|
const questions = await loadQaQuestions(logger);
|
|
35595
|
-
const
|
|
35665
|
+
const layoutPromptTemplate = await loadLayoutPrompt(logger);
|
|
35666
|
+
const layoutPromptSpec = interpolateTemplate(layoutPromptTemplate, {
|
|
35667
|
+
structuralContext,
|
|
35668
|
+
currentValues: "(No existing role document \u2014 this is initial generation from a Q&A conversation.)",
|
|
35669
|
+
userPrompt: "(See Q&A conversation below for the user's intent.)"
|
|
35670
|
+
});
|
|
35671
|
+
const qaSystemPrompt = buildQaSystemPrompt(questions, layoutPromptSpec);
|
|
35596
35672
|
const convLines = conversation.map((m) => `${m.role === "assistant" ? "AI" : "User"}: ${m.content}`).join("\n\n");
|
|
35597
35673
|
const userAnswerCount = conversation.filter((m) => m.role === "user").length;
|
|
35598
35674
|
const prompt = [
|
|
@@ -35643,24 +35719,12 @@ async function generateAndApplyLayout(options) {
|
|
|
35643
35719
|
if (!brand) throw new Error(`Brand file not found at ${brandPath}`);
|
|
35644
35720
|
const structuralContext = buildStructuralContext(brands);
|
|
35645
35721
|
const existingRole = await readRoleFile(workspaceRoot);
|
|
35646
|
-
const
|
|
35647
|
-
|
|
35648
|
-
"",
|
|
35649
|
-
"## Structural context (sibling brand files)",
|
|
35722
|
+
const layoutPromptTemplate = await loadLayoutPrompt(logger);
|
|
35723
|
+
const prompt = interpolateTemplate(layoutPromptTemplate, {
|
|
35650
35724
|
structuralContext,
|
|
35651
|
-
"",
|
|
35652
|
-
|
|
35653
|
-
|
|
35654
|
-
${existingRole}` : "(No existing role document \u2014 generate from scratch.)",
|
|
35655
|
-
"",
|
|
35656
|
-
"## User's refinement prompt",
|
|
35657
|
-
userPrompt,
|
|
35658
|
-
"",
|
|
35659
|
-
"This is a REFINEMENT. The user already has a role document (shown above). Their prompt refines, evolves, or redirects \u2014 it does NOT start from scratch unless they explicitly say so.",
|
|
35660
|
-
"",
|
|
35661
|
-
'Return ONLY a JSON object: { "roleDocument": "the complete updated markdown document" }',
|
|
35662
|
-
"No markdown fences. No explanation."
|
|
35663
|
-
].join("\n");
|
|
35725
|
+
currentValues: existingRole ?? "(No existing role document \u2014 generate from scratch.)",
|
|
35726
|
+
userPrompt
|
|
35727
|
+
});
|
|
35664
35728
|
let response;
|
|
35665
35729
|
try {
|
|
35666
35730
|
response = await generateStructuredAgentResponseWithFallback({
|