appostle-installer 0.0.18 → 0.0.20

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 CHANGED
@@ -1922,6 +1922,10 @@ function toAgentPayload(agent, options) {
1922
1922
  title: options?.title ?? null,
1923
1923
  labels: agent.labels,
1924
1924
  internal: agent.internal,
1925
+ // Forward archivedAt on the live broadcast so paired clients (e.g. mobile)
1926
+ // see the archive land — without this, only the device that ran the
1927
+ // archive mutation knows about it.
1928
+ archivedAt: agent.archivedAt ?? null,
1925
1929
  // Surface ownership so the client can render an owner badge / detect
1926
1930
  // "shared with me" agents. `sharedWithUserIds` deliberately stays off
1927
1931
  // the snapshot — only owners read the full ACL, via the dedicated
@@ -3203,6 +3207,7 @@ var RoleMoveResponseSchema = z10.object({
3203
3207
  var BrandScopeSchema = z10.literal("project");
3204
3208
  var BrandVariableTypeSchema = z10.enum([
3205
3209
  "color",
3210
+ "gradient",
3206
3211
  "font",
3207
3212
  "asset",
3208
3213
  "text",
@@ -19463,11 +19468,53 @@ var ClaudeAgentSession = class {
19463
19468
  // sub-agents stop announcing them as suspected prompt injections. See
19464
19469
  // getSystemReminderGuidance for the full rationale.
19465
19470
  getSystemReminderGuidance(),
19466
- "Default response shape: open with a short, scannable plain-English read. 2\u20134 short sentences, one idea each, no comma-chained clauses or em-dash pile-ups. When the read covers 3+ discrete points, use bullets instead of prose. Then the technical detail in dense form (paths, line refs, code) without narration. Don't explain what well-named code already explains. Skip the shape for trivial questions.",
19467
- "For multi-step work with independent chunks, spawn Task subagents instead of doing every tool call yourself. Run them in parallel when chunks don't depend on each other. Keeps your main context lean.",
19471
+ //
19472
+ //
19473
+ //
19474
+ //
19475
+ // ╔══════════════════════════════════════════════════════════════════╗
19476
+ // ║ CUSTOM INSTRUCTIONS — add new system prompt lines below here ║
19477
+ // ║ Each line is a quoted string: "your instruction here", ║
19478
+ // ╚══════════════════════════════════════════════════════════════════╝
19479
+ //
19480
+ //
19481
+ //
19482
+ //
19483
+ "Default response shape: open with a short, scannable plain-English read. 2\u20134 short sentences, one idea each, no comma-chained clauses or em-dash pile-ups. When the read covers 3+ discrete points, use bullets instead of prose. Then the technical detail in dense form (paths, line refs, code) without narration. Don\u2019t explain what well-named code already explains. Skip the shape for trivial questions.",
19484
+ //
19468
19485
  "When the user sends `>learn`, re-explain your previous message in plain English only \u2014 1 short paragraph that helps them build the mental model. No code, no file references, no technical repeat.",
19486
+ //
19487
+ //
19488
+ //
19489
+ //
19490
+ // ╔══════════════════════════════════════════════════════════════════╗
19491
+ // ║ END CUSTOM INSTRUCTIONS — don't edit below this line ║
19492
+ // ╚══════════════════════════════════════════════════════════════════╝
19493
+ //
19494
+ //
19495
+ //
19496
+ //
19469
19497
  this.config.systemPrompt?.trim()
19470
19498
  ].filter((entry) => typeof entry === "string" && entry.length > 0).join("\n\n");
19499
+ const appostleAgents = {
19500
+ researcher: {
19501
+ description: "Use this agent when you need to explore unfamiliar code, trace data flow across modules, search broadly across the codebase, or read more than 2 files you haven't seen yet. Delegate investigation work here to keep the main conversation context lean.",
19502
+ prompt: "You are a codebase researcher. Your job is to investigate, trace, and report findings \u2014 never edit files. Read code, grep for patterns, follow imports, and build a clear picture. Report back a concise summary of what you found: key files, relevant code paths, and your conclusion. Keep your report under 300 words unless the investigation is complex.",
19503
+ model: "sonnet"
19504
+ },
19505
+ refactorer: {
19506
+ description: "Use this agent for refactors that touch more than 2 files \u2014 renames, migrations, pattern replacements, moving code between modules. Delegate multi-file changes here to isolate the blast radius.",
19507
+ prompt: "You are a refactoring specialist. Make the requested changes across all affected files. Be thorough \u2014 update imports, references, types, and tests. Run typecheck after changes if available. Report what you changed and any issues found."
19508
+ },
19509
+ reviewer: {
19510
+ description: "Use this agent to review code for bugs, security issues, performance problems, or style violations. Use it before committing large changes or when the user asks for a review.",
19511
+ prompt: "You are a code reviewer. Analyze the code for correctness, security vulnerabilities (OWASP top 10), performance issues, and adherence to the project's coding standards. Be specific \u2014 cite file paths and line numbers. Flag severity: critical, warning, or nitpick. Keep the review focused and actionable."
19512
+ },
19513
+ debugger: {
19514
+ description: "Use this agent to investigate bugs \u2014 read logs, trace error paths, check database state, reproduce issues. Delegate debugging here when the root cause isn't obvious from a quick look.",
19515
+ prompt: "You are a debugger. Your job is to find the root cause, not patch symptoms. Check real data first \u2014 logs, network requests, database state. Trace the full lifecycle of the bug. Present your findings: root cause, evidence, and a proposed minimal fix. Do not edit files unless explicitly asked."
19516
+ }
19517
+ };
19471
19518
  const claudeBinary = await findExecutable("claude");
19472
19519
  this.logger.debug(
19473
19520
  {
@@ -19485,7 +19532,7 @@ var ClaudeAgentSession = class {
19485
19532
  // bypass launch capability available so later setPermissionMode("bypassPermissions")
19486
19533
  // calls do not fail after a model/thinking/rewind-driven restart.
19487
19534
  allowDangerouslySkipPermissions: true,
19488
- agents: this.defaults?.agents,
19535
+ agents: { ...appostleAgents, ...this.defaults?.agents },
19489
19536
  canUseTool: this.handlePermissionRequest,
19490
19537
  ...claudeBinary ? { pathToClaudeCodeExecutable: claudeBinary } : {},
19491
19538
  // Use Claude Code preset system prompt and load CLAUDE.md files
@@ -35111,6 +35158,7 @@ ${original}`;
35111
35158
  // ../server/src/server/brand/token-generator.ts
35112
35159
  import { z as z37 } from "zod";
35113
35160
  var HEX6 = /^#[0-9a-f]{6}$/i;
35161
+ var CSS_GRADIENT = /^(linear|radial|conic)-gradient\(/i;
35114
35162
  var TokensResponseSchema = z37.object({
35115
35163
  tokens: z37.record(z37.string(), z37.string())
35116
35164
  });
@@ -35186,6 +35234,31 @@ function buildPrompt2(args) {
35186
35234
  }
35187
35235
  }
35188
35236
  const tokensToFill = args.unlockedTokens.map((v) => `- ${v.key}`).join("\n");
35237
+ const gradientSection = args.unlockedGradients.length > 0 ? [
35238
+ "",
35239
+ "Gradient tokens to fill (values must be valid CSS gradient strings,",
35240
+ "e.g. linear-gradient(135deg, #c52669 0%, #ff6b35 100%)):",
35241
+ ...args.unlockedGradients.map((v) => `- ${v.key}`),
35242
+ "",
35243
+ "Gradient key glossary:",
35244
+ "- gradient.primary \u2014 the brand's signature gradient, built from the",
35245
+ " most prominent palette colours. Used for hero backgrounds, feature",
35246
+ " highlights, and primary decorative surfaces.",
35247
+ "- gradient.accent \u2014 a secondary gradient for interactive states,",
35248
+ " hover effects, or accent surfaces. Distinct from primary but",
35249
+ " harmonious with the palette.",
35250
+ "- gradient.subtle \u2014 a very soft, low-contrast gradient for subtle",
35251
+ " background washes \u2014 card backgrounds, section tints. Should feel",
35252
+ " almost invisible; same hue family as bg-base with minimal shift.",
35253
+ "",
35254
+ "Include gradient values in the same JSON under the 'tokens' key,",
35255
+ "alongside the hex tokens. Gradient values are CSS gradient strings."
35256
+ ].join("\n") : "";
35257
+ const lockedGradientSection = args.lockedGradients.length > 0 ? [
35258
+ "",
35259
+ "Locked gradients \u2014 DO NOT include these in your output:",
35260
+ ...args.lockedGradients.map((v) => `- ${v.key}: ${v.value}`)
35261
+ ].join("\n") : "";
35189
35262
  const lockedSection = args.lockedTokens.length > 0 ? [
35190
35263
  "",
35191
35264
  "Locked tokens \u2014 DO NOT include these in your output. The user has",
@@ -35232,13 +35305,15 @@ function buildPrompt2(args) {
35232
35305
  "Tokens to fill (every value must be a #RRGGBB lowercase hex string):",
35233
35306
  tokensToFill,
35234
35307
  lockedSection,
35308
+ gradientSection,
35309
+ lockedGradientSection,
35235
35310
  directionSection,
35236
35311
  "",
35237
35312
  "Return JSON only with the shape:",
35238
- '{ "tokens": { "<token-key>": "#rrggbb", ... } }',
35313
+ '{ "tokens": { "<token-key>": "#rrggbb or css-gradient-string", ... } }',
35239
35314
  "",
35240
- "Output ONLY the unlocked tokens listed above. Do not include the locked",
35241
- "tokens, do not invent new keys, do not output anything outside the JSON.",
35315
+ "Output ONLY the unlocked tokens and gradients listed above. Do not include",
35316
+ "locked entries, do not invent new keys, do not output anything outside the JSON.",
35242
35317
  "Never use a hex value from a colour marked OFF."
35243
35318
  ].filter((line) => line !== "").join("\n");
35244
35319
  }
@@ -35252,9 +35327,12 @@ async function generateAndApplyBrandTokens(options) {
35252
35327
  const allVars = colorsBrand.variables;
35253
35328
  const paletteVars = allVars.filter((v) => v.key.startsWith("color."));
35254
35329
  const tokenVars = allVars.filter((v) => v.key.startsWith("token."));
35330
+ const gradientVars = allVars.filter((v) => v.type === "gradient");
35255
35331
  const unlockedTokens = tokenVars.filter((v) => !v.locked);
35256
35332
  const lockedTokens = tokenVars.filter((v) => v.locked);
35257
- if (unlockedTokens.length === 0) {
35333
+ const unlockedGradients = gradientVars.filter((v) => !v.locked);
35334
+ const lockedGradients = gradientVars.filter((v) => v.locked);
35335
+ if (unlockedTokens.length === 0 && unlockedGradients.length === 0) {
35258
35336
  logger.info({ brandPath }, "brand-tokens: nothing to generate (all tokens locked)");
35259
35337
  return { generatedCount: 0 };
35260
35338
  }
@@ -35265,6 +35343,8 @@ async function generateAndApplyBrandTokens(options) {
35265
35343
  paletteVars,
35266
35344
  unlockedTokens,
35267
35345
  lockedTokens,
35346
+ unlockedGradients,
35347
+ lockedGradients,
35268
35348
  mode,
35269
35349
  userPrompt: prompt
35270
35350
  });
@@ -35290,14 +35370,19 @@ async function generateAndApplyBrandTokens(options) {
35290
35370
  }
35291
35371
  throw error;
35292
35372
  }
35293
- const unlockedKeySet = new Set(unlockedTokens.map((v) => v.key));
35373
+ const unlockedTokenKeySet = new Set(unlockedTokens.map((v) => v.key));
35374
+ const unlockedGradientKeySet = new Set(unlockedGradients.map((v) => v.key));
35294
35375
  const acceptedUpdates = /* @__PURE__ */ new Map();
35295
35376
  for (const [key, value] of Object.entries(response.tokens)) {
35296
- if (!unlockedKeySet.has(key)) continue;
35297
35377
  if (typeof value !== "string") continue;
35298
35378
  const trimmed = value.trim();
35299
- if (!HEX6.test(trimmed)) continue;
35300
- acceptedUpdates.set(key, trimmed.toLowerCase());
35379
+ if (unlockedTokenKeySet.has(key)) {
35380
+ if (!HEX6.test(trimmed)) continue;
35381
+ acceptedUpdates.set(key, trimmed.toLowerCase());
35382
+ } else if (unlockedGradientKeySet.has(key)) {
35383
+ if (!CSS_GRADIENT.test(trimmed)) continue;
35384
+ acceptedUpdates.set(key, trimmed);
35385
+ }
35301
35386
  }
35302
35387
  if (acceptedUpdates.size === 0) {
35303
35388
  logger.warn(
@@ -35334,6 +35419,7 @@ import path20 from "node:path";
35334
35419
  import { fileURLToPath as fileURLToPath2 } from "node:url";
35335
35420
  import { z as z38 } from "zod";
35336
35421
  var QA_FILENAME = "layout-qa.md";
35422
+ var PROMPT_FILENAME = "layout-prompt.md";
35337
35423
  var MAX_LOOKUP_LEVELS = 10;
35338
35424
  var ROLE_FILE_RELATIVE = ".appostle/brand/assets/role/brand-layout-role.md";
35339
35425
  async function findFileUpward(filename) {
@@ -35442,111 +35528,37 @@ function buildStructuralContext(allBrands) {
35442
35528
  return lines.length > 0 ? lines.join("\n") : "(No structural context established yet.)";
35443
35529
  }
35444
35530
  var TARGET_QUESTIONS = 7;
35445
- var EMBEDDED_QA_FALLBACK = `## Compositional Philosophy
35446
- - What's the single-sentence design philosophy that should govern every layout decision?
35447
- - What's the ratio of whitespace to content density you're after?
35448
-
35449
- ## Hero Behavior
35450
- - How should the hero section behave? Full viewport takeover, contained module, asymmetric split?
35451
- - Should the hero have scroll-triggered behavior or stay static?
35452
-
35453
- ## Section Variation & Flow
35454
- - How many distinct section types should the page cycle through?
35455
- - Should any sections break out of the main container (full-bleed moments)?
35456
-
35457
- ## Density & Spacing
35458
- - How tight should content be packed within sections?
35459
- - How much vertical breathing room between major sections?
35460
-
35461
- ## Grid System
35462
- - What grid philosophy \u2014 strict 12-column, asymmetric, modular, or freeform?
35463
-
35464
- ## Containers & Cards
35465
- - What's your card philosophy \u2014 flat, elevated, outlined, or glassmorphic?
35466
-
35467
- ## Image Treatment
35468
- - What role do images play \u2014 hero-level, supporting, or minimal?
35469
-
35470
- ## CTA Strategy
35471
- - How many CTAs per page and what's the hierarchy?
35472
-
35473
- ## Mobile Behavior
35474
- - How should the desktop layout transform on mobile?
35475
-
35476
- ## Prohibitions & Bans
35477
- - What design patterns are absolutely forbidden?
35478
- - Any CSS properties or techniques that are banned?`;
35531
+ async function loadSpecFile(filename, logger) {
35532
+ const filePath = await findFileUpward(filename);
35533
+ if (!filePath) {
35534
+ throw new Error(
35535
+ `layout-generator: ${filename} not found by walking up from ${fileURLToPath2(import.meta.url)}. This file is the canonical spec and must exist at the appostle repo root.`
35536
+ );
35537
+ }
35538
+ try {
35539
+ const content = await fs12.readFile(filePath, "utf8");
35540
+ logger.debug({ filePath }, `layout-generator: loaded ${filename} from disk`);
35541
+ return content;
35542
+ } catch (err) {
35543
+ throw new Error(
35544
+ `layout-generator: failed to read ${filename} at ${filePath}: ${err.message}`
35545
+ );
35546
+ }
35547
+ }
35479
35548
  async function loadQaQuestions(logger) {
35480
- const filePath = await findFileUpward(QA_FILENAME);
35481
- if (filePath) {
35482
- try {
35483
- const content = await fs12.readFile(filePath, "utf8");
35484
- logger.debug({ filePath }, "layout-generator: loaded Q&A questions from disk");
35485
- return content;
35486
- } catch (err) {
35487
- logger.warn(
35488
- { err, filePath },
35489
- "layout-generator: failed to read Q&A file; using embedded fallback"
35490
- );
35491
- }
35549
+ return loadSpecFile(QA_FILENAME, logger);
35550
+ }
35551
+ async function loadLayoutPrompt(logger) {
35552
+ return loadSpecFile(PROMPT_FILENAME, logger);
35553
+ }
35554
+ function interpolateTemplate(template, vars) {
35555
+ let out = template;
35556
+ for (const [key, value] of Object.entries(vars)) {
35557
+ out = out.split(`{{${key}}}`).join(value);
35492
35558
  }
35493
- return EMBEDDED_QA_FALLBACK;
35559
+ return out;
35494
35560
  }
35495
- var ROLE_GENERATION_INSTRUCTIONS = `You are an elite art director generating a complete design role document. This document will be injected wholesale into an AI agent's context to give it a dense, opinionated compositional voice \u2014 the way lordesign-brutalist or lordesign-soft gives a builder an entire design philosophy.
35496
-
35497
- The output must be a SINGLE cohesive markdown document (NOT JSON, NOT key-value pairs). It reads like a design manifesto \u2014 dense, opinionated, specific enough that two different builders reading it would produce nearly identical structural decisions.
35498
-
35499
- ## Required sections (use these exact headings)
35500
-
35501
- # Brand Layout Role
35502
-
35503
- ## Compositional Philosophy
35504
- The north star. 3-5 sentences describing the fundamental spatial personality.
35505
-
35506
- ## Hero Behavior
35507
- Exact rules for hero sections \u2014 dimensions, content placement, image treatment, scroll behavior, what's forbidden.
35508
-
35509
- ## Section Variation & Flow
35510
- How consecutive sections differ. Allowed arrangements. How many types cycle. Full-bleed rules.
35511
-
35512
- ## Density Philosophy
35513
- Where dense, where airy, specific spacing ratios, padding rules.
35514
-
35515
- ## Vertical Rhythm
35516
- Section height strategy, padding patterns, oscillation rules, breathing room logic.
35517
-
35518
- ## Grid System
35519
- Column philosophy, alignment rules, breakpoint behavior, visible vs invisible structure.
35520
-
35521
- ## Container & Card Rules
35522
- Borders, shadows, corners, nesting rules, elevation hierarchy.
35523
-
35524
- ## Image Treatment
35525
- Aspect ratios, size constraints, cropping rules, filter/overlay rules, frequency.
35526
-
35527
- ## CTA Strategy
35528
- Frequency per page, hierarchy, styling constraints, placement rules.
35529
-
35530
- ## Mobile Behavior
35531
- Breakpoint strategy, what collapses vs stacks, density changes, navigation transformation.
35532
-
35533
- ## Bans
35534
- The most powerful section. At least 20 specific prohibitions at EVERY level:
35535
- - CSS-level (specific properties, values, patterns)
35536
- - Component-level (specific UI patterns forbidden)
35537
- - Layout-level (spatial patterns forbidden)
35538
- - Content-level (content patterns forbidden)
35539
- - Interaction-level (motion/animation patterns forbidden)
35540
-
35541
- Each ban on its own line starting with "\u2022". Be ruthlessly specific \u2014 "no gradients" is vague, "no linear-gradient except single-stop overlays on hero images" is useful.
35542
-
35543
- ## Critical quality bar
35544
- - Every rule must be specific enough to resolve an ambiguous decision
35545
- - No generic advice ("keep it clean") \u2014 only actionable constraints
35546
- - The bans section alone should have 20+ items
35547
- - Rules should reference specific CSS properties, pixel values, viewport units where applicable
35548
- - The document should be 50-100 rules total across all sections`;
35549
- function buildQaSystemPrompt(questions) {
35561
+ function buildQaSystemPrompt(questions, layoutPromptSpec) {
35550
35562
  return `You are a chill creative director doing a quick vibe check on someone's layout taste.
35551
35563
 
35552
35564
  CONTEXT:
@@ -35580,10 +35592,11 @@ When done=false, return:
35580
35592
  { "done": false, "question": "Short question?", "options": ["Option A", "Option B", "Option C", "Option D"] }
35581
35593
 
35582
35594
  When done=true, return:
35583
- { "done": true, "roleDocument": "# Brand Layout Role\\n\\n## Compositional Philosophy\\n..." }
35595
+ { "done": true, "roleDocument": "# Brand Layout Role\\n\\n## Enemy\\n..." }
35584
35596
 
35585
- The roleDocument must be a complete markdown document following this structure:
35586
- ${ROLE_GENERATION_INSTRUCTIONS}`;
35597
+ 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.
35598
+
35599
+ ${layoutPromptSpec}`;
35587
35600
  }
35588
35601
  async function layoutQaNext(options) {
35589
35602
  const { agentManager, workspaceRoot, brandPath, conversation, logger } = options;
@@ -35592,7 +35605,13 @@ async function layoutQaNext(options) {
35592
35605
  if (!brand) throw new Error(`Brand file not found at ${brandPath}`);
35593
35606
  const structuralContext = buildStructuralContext(brands);
35594
35607
  const questions = await loadQaQuestions(logger);
35595
- const qaSystemPrompt = buildQaSystemPrompt(questions);
35608
+ const layoutPromptTemplate = await loadLayoutPrompt(logger);
35609
+ const layoutPromptSpec = interpolateTemplate(layoutPromptTemplate, {
35610
+ structuralContext,
35611
+ currentValues: "(No existing role document \u2014 this is initial generation from a Q&A conversation.)",
35612
+ userPrompt: "(See Q&A conversation below for the user's intent.)"
35613
+ });
35614
+ const qaSystemPrompt = buildQaSystemPrompt(questions, layoutPromptSpec);
35596
35615
  const convLines = conversation.map((m) => `${m.role === "assistant" ? "AI" : "User"}: ${m.content}`).join("\n\n");
35597
35616
  const userAnswerCount = conversation.filter((m) => m.role === "user").length;
35598
35617
  const prompt = [
@@ -35643,24 +35662,12 @@ async function generateAndApplyLayout(options) {
35643
35662
  if (!brand) throw new Error(`Brand file not found at ${brandPath}`);
35644
35663
  const structuralContext = buildStructuralContext(brands);
35645
35664
  const existingRole = await readRoleFile(workspaceRoot);
35646
- const prompt = [
35647
- ROLE_GENERATION_INSTRUCTIONS,
35648
- "",
35649
- "## Structural context (sibling brand files)",
35665
+ const layoutPromptTemplate = await loadLayoutPrompt(logger);
35666
+ const prompt = interpolateTemplate(layoutPromptTemplate, {
35650
35667
  structuralContext,
35651
- "",
35652
- existingRole ? `## Current role document
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");
35668
+ currentValues: existingRole ?? "(No existing role document \u2014 generate from scratch.)",
35669
+ userPrompt
35670
+ });
35664
35671
  let response;
35665
35672
  try {
35666
35673
  response = await generateStructuredAgentResponseWithFallback({