refira-cli 0.1.3 → 0.2.0

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 (2) hide show
  1. package/dist/index.js +809 -146
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1950,13 +1950,26 @@ class CliApiClient {
1950
1950
  const body = await res.json();
1951
1951
  return body.data;
1952
1952
  }
1953
- async pushPreview(projectId, pageIdentifier, html) {
1953
+ async pushPreview(projectId, pageIdentifier, html, expectedRevision) {
1954
1954
  const url = `${this.apiUrl}/api/cli/projects/${projectId}/pages/${pageIdentifier}/preview`;
1955
+ const headers = this.getHeaders();
1956
+ if (expectedRevision) {
1957
+ headers["If-Match"] = expectedRevision;
1958
+ }
1955
1959
  const res = await fetch(url, {
1956
1960
  method: "POST",
1957
- headers: this.getHeaders(),
1958
- body: JSON.stringify({ html })
1961
+ headers,
1962
+ body: JSON.stringify({
1963
+ html,
1964
+ ...expectedRevision !== undefined ? { expected_revision: expectedRevision } : {}
1965
+ })
1959
1966
  });
1967
+ if (res.status === 409) {
1968
+ const errJson = await res.json().catch(() => ({}));
1969
+ const error = new Error(errJson.message ?? "Page has been modified concurrently. Fetch latest revision before applying updates.");
1970
+ error.code = "REVISION_CONFLICT";
1971
+ throw error;
1972
+ }
1960
1973
  if (!res.ok) {
1961
1974
  const err = await res.text();
1962
1975
  throw new Error(`Failed to push preview (${res.status}): ${err}`);
@@ -1964,12 +1977,38 @@ class CliApiClient {
1964
1977
  const body = await res.json();
1965
1978
  return body.data;
1966
1979
  }
1967
- async startGenerating(projectId, pageIdentifier) {
1968
- const url = `${this.apiUrl}/api/cli/projects/${projectId}/pages/${pageIdentifier}/start-generating`;
1980
+ async getAnnotations(projectId, pageIdentifier) {
1981
+ const url = `${this.apiUrl}/api/cli/projects/${projectId}/pages/${pageIdentifier}/annotations`;
1982
+ const res = await fetch(url, {
1983
+ headers: this.getHeaders()
1984
+ });
1985
+ if (!res.ok) {
1986
+ const err = await res.text();
1987
+ throw new Error(`Failed to fetch annotations (${res.status}): ${err}`);
1988
+ }
1989
+ const body = await res.json();
1990
+ return body.data;
1991
+ }
1992
+ async cleanServer() {
1993
+ const url = `${this.apiUrl}/api/system/clean`;
1969
1994
  const res = await fetch(url, {
1970
1995
  method: "POST",
1971
1996
  headers: this.getHeaders()
1972
1997
  });
1998
+ if (!res.ok) {
1999
+ const err = await res.text();
2000
+ throw new Error(`Failed to clean server artifacts (${res.status}): ${err}`);
2001
+ }
2002
+ const body = await res.json();
2003
+ return body.data;
2004
+ }
2005
+ async startGenerating(projectId, pageIdentifier, layoutSlug) {
2006
+ const url = `${this.apiUrl}/api/cli/projects/${projectId}/pages/${pageIdentifier}/start-generating`;
2007
+ const res = await fetch(url, {
2008
+ method: "POST",
2009
+ headers: this.getHeaders(),
2010
+ ...layoutSlug ? { body: JSON.stringify({ layout: layoutSlug }) } : {}
2011
+ });
1973
2012
  if (!res.ok) {
1974
2013
  const err = await res.text();
1975
2014
  throw new Error(`Failed to signal generation start (${res.status}): ${err}`);
@@ -1977,13 +2016,29 @@ class CliApiClient {
1977
2016
  const body = await res.json();
1978
2017
  return body.data;
1979
2018
  }
2019
+ async selectActiveLayout(projectId, selection) {
2020
+ const url = `${this.apiUrl}/api/cli/projects/${projectId}/layout/select`;
2021
+ const res = await fetch(url, {
2022
+ method: "POST",
2023
+ headers: this.getHeaders(),
2024
+ body: JSON.stringify(selection)
2025
+ });
2026
+ if (!res.ok) {
2027
+ const err = await res.text();
2028
+ throw new Error(`Failed to select active layout (${res.status}): ${err}`);
2029
+ }
2030
+ const body = await res.json();
2031
+ return body.data;
2032
+ }
1980
2033
  }
1981
2034
 
1982
2035
  // src/config.ts
1983
2036
  import fs from "node:fs";
1984
2037
  import os from "node:os";
1985
2038
  import path from "node:path";
1986
- var LOCAL_CONFIG_FILE = ".refirarc";
2039
+ var REFIRA_DIR = ".refira";
2040
+ var REFIRA_CONFIG_FILE = path.join(REFIRA_DIR, ".refirarc");
2041
+ var ROOT_CONFIG_FILE = ".refirarc";
1987
2042
  var GLOBAL_CONFIG_FILE = path.join(os.homedir(), ".refirarc");
1988
2043
  function loadConfig() {
1989
2044
  let config = {
@@ -1991,23 +2046,21 @@ function loadConfig() {
1991
2046
  apiKey: process.env.REFIRA_API_KEY,
1992
2047
  projectId: process.env.REFIRA_PROJECT_ID
1993
2048
  };
1994
- if (fs.existsSync(LOCAL_CONFIG_FILE)) {
1995
- try {
1996
- const raw = fs.readFileSync(LOCAL_CONFIG_FILE, "utf-8");
1997
- const parsed = JSON.parse(raw);
1998
- config = { ...config, ...parsed };
1999
- } catch {}
2000
- } else if (fs.existsSync(GLOBAL_CONFIG_FILE)) {
2001
- try {
2002
- const raw = fs.readFileSync(GLOBAL_CONFIG_FILE, "utf-8");
2003
- const parsed = JSON.parse(raw);
2004
- config = { ...config, ...parsed };
2005
- } catch {}
2049
+ const candidateFiles = [REFIRA_CONFIG_FILE, ROOT_CONFIG_FILE, GLOBAL_CONFIG_FILE];
2050
+ for (const file of candidateFiles) {
2051
+ if (fs.existsSync(file)) {
2052
+ try {
2053
+ const raw = fs.readFileSync(file, "utf-8");
2054
+ const parsed = JSON.parse(raw);
2055
+ config = { ...config, ...parsed };
2056
+ break;
2057
+ } catch {}
2058
+ }
2006
2059
  }
2007
2060
  return config;
2008
2061
  }
2009
2062
  function saveConfig(updates, isGlobal = false) {
2010
- const targetPath = isGlobal ? GLOBAL_CONFIG_FILE : LOCAL_CONFIG_FILE;
2063
+ const targetPath = isGlobal ? GLOBAL_CONFIG_FILE : REFIRA_CONFIG_FILE;
2011
2064
  let current = {};
2012
2065
  if (fs.existsSync(targetPath)) {
2013
2066
  try {
@@ -2015,18 +2068,79 @@ function saveConfig(updates, isGlobal = false) {
2015
2068
  } catch {
2016
2069
  current = {};
2017
2070
  }
2071
+ } else if (!isGlobal && fs.existsSync(ROOT_CONFIG_FILE)) {
2072
+ try {
2073
+ current = JSON.parse(fs.readFileSync(ROOT_CONFIG_FILE, "utf-8"));
2074
+ } catch {
2075
+ current = {};
2076
+ }
2077
+ }
2078
+ if (!isGlobal) {
2079
+ const dir = path.dirname(targetPath);
2080
+ if (!fs.existsSync(dir)) {
2081
+ fs.mkdirSync(dir, { recursive: true });
2082
+ }
2018
2083
  }
2019
2084
  const merged = { ...current, ...updates };
2020
2085
  fs.writeFileSync(targetPath, JSON.stringify(merged, null, 2), "utf-8");
2021
2086
  }
2022
2087
 
2088
+ // src/commands/annotations.ts
2089
+ function formatAnnotationsMarkdown(pageSlug, annotations) {
2090
+ if (annotations.length === 0) {
2091
+ return `No open annotations found for page '${pageSlug}'.`;
2092
+ }
2093
+ const lines = [
2094
+ `# Open Annotations for page: ${pageSlug} (${annotations.length})`,
2095
+ "",
2096
+ "| ID | Element | Selector | Comment | Date |",
2097
+ "|---|---|---|---|---|"
2098
+ ];
2099
+ for (const ann of annotations) {
2100
+ const el = ann.element_id;
2101
+ const selector = ann.selector_path || "-";
2102
+ const cleanComment = ann.content.replace(/\|/g, "\\|").replace(/\n/g, " ");
2103
+ const date = ann.created_at ? new Date(ann.created_at).toISOString().split("T")[0] : "-";
2104
+ lines.push(`| \`${ann.id}\` | \`${el}\` | \`${selector}\` | ${cleanComment} | ${date} |`);
2105
+ }
2106
+ return lines.join(`
2107
+ `);
2108
+ }
2109
+ async function annotationsCommand(opts) {
2110
+ const pageSlug = opts.page?.trim().toLowerCase();
2111
+ if (!pageSlug) {
2112
+ console.error("Error: --page <slug> is required.");
2113
+ process.exit(1);
2114
+ }
2115
+ const config = loadConfig();
2116
+ if (!config.apiKey || !config.projectId) {
2117
+ console.error("Error: Authentication required. Run `refira auth login` first.");
2118
+ process.exit(1);
2119
+ }
2120
+ const client = new CliApiClient(config.apiUrl, config.apiKey);
2121
+ try {
2122
+ const res = await client.getAnnotations(config.projectId, pageSlug);
2123
+ const format = opts.format ?? "table";
2124
+ if (format === "json") {
2125
+ console.log(JSON.stringify(res, null, 2));
2126
+ return;
2127
+ }
2128
+ const output = formatAnnotationsMarkdown(pageSlug, res.annotations);
2129
+ console.log(output);
2130
+ } catch (err) {
2131
+ const msg = err instanceof Error ? err.message : String(err);
2132
+ console.error(`Failed to fetch annotations: ${msg}`);
2133
+ process.exit(1);
2134
+ }
2135
+ }
2136
+
2023
2137
  // src/commands/auth.ts
2024
2138
  async function loginCommand(opts) {
2025
2139
  const current = loadConfig();
2026
2140
  const apiUrl = opts.apiUrl ?? current.apiUrl ?? "http://localhost:3001";
2027
2141
  const apiKey = opts.apiKey ?? current.apiKey;
2028
2142
  if (!apiKey) {
2029
- console.error("Error: API key is required. Provide --api-key <rfr_...>");
2143
+ console.error("Error: API key is required. Provide --api-key <rfr_...>");
2030
2144
  process.exit(1);
2031
2145
  }
2032
2146
  console.log(`Verifying authentication with ${apiUrl}...`);
@@ -2034,44 +2148,150 @@ async function loginCommand(opts) {
2034
2148
  try {
2035
2149
  const result = await client.verifyAuth();
2036
2150
  saveConfig({ apiUrl, apiKey, projectId: result.project_id }, opts.global ?? false);
2037
- console.log("Authentication successful!");
2151
+ console.log("Authentication successful.");
2038
2152
  console.log(` Project ID: ${result.project_id}`);
2039
2153
  console.log(` User ID: ${result.user_id}`);
2040
2154
  } catch (err) {
2041
2155
  const msg = err instanceof Error ? err.message : String(err);
2042
- console.error(`❌ Authentication failed: ${msg}`);
2156
+ console.error(`Authentication failed: ${msg}`);
2043
2157
  process.exit(1);
2044
2158
  }
2045
2159
  }
2046
2160
  async function statusCommand() {
2047
2161
  const config = loadConfig();
2048
2162
  if (!config.apiKey) {
2049
- console.log("⚠️ No active Refira session found. Run `refira auth login --api-key <key>` to connect.");
2163
+ console.log("No active Refira session found. Run `refira auth login --api-key <key>` to connect.");
2050
2164
  return;
2051
2165
  }
2052
2166
  const client = new CliApiClient(config.apiUrl, config.apiKey);
2053
2167
  try {
2054
2168
  const result = await client.verifyAuth();
2055
- console.log("Active Refira Session:");
2169
+ console.log("Active Refira Session:");
2056
2170
  console.log(` API Endpoint: ${config.apiUrl}`);
2057
2171
  console.log(` Project ID: ${result.project_id}`);
2058
2172
  console.log(` User ID: ${result.user_id}`);
2059
2173
  } catch (err) {
2060
2174
  const msg = err instanceof Error ? err.message : String(err);
2061
- console.error(`❌ Session invalid or expired: ${msg}`);
2175
+ console.error(`Session invalid or expired: ${msg}`);
2176
+ }
2177
+ }
2178
+
2179
+ // src/commands/clean.ts
2180
+ import fs2 from "node:fs";
2181
+ import path2 from "node:path";
2182
+ var LOCAL_CLEAN_TARGETS = [path2.join(REFIRA_DIR, "work"), "tmp", ".cache"];
2183
+ function discoverLocalCleanFiles(cwd = process.cwd()) {
2184
+ const discovered = [];
2185
+ for (const target of LOCAL_CLEAN_TARGETS) {
2186
+ const fullPath = path2.resolve(cwd, target);
2187
+ if (fs2.existsSync(fullPath)) {
2188
+ discovered.push(fullPath);
2189
+ }
2190
+ }
2191
+ return discovered;
2192
+ }
2193
+ async function cleanCommand(opts) {
2194
+ const isDryRun = opts.dryRun === true;
2195
+ const isAll = opts.all === true;
2196
+ console.log(isDryRun ? "Refira clean (dry run mode):" : "Refira clean:");
2197
+ const targets = discoverLocalCleanFiles();
2198
+ if (targets.length === 0) {
2199
+ console.log(" No local scratch or work directories found to clean.");
2200
+ } else {
2201
+ for (const target of targets) {
2202
+ if (isDryRun) {
2203
+ console.log(` [dry-run] Would remove: ${target}`);
2204
+ } else {
2205
+ try {
2206
+ fs2.rmSync(target, { recursive: true, force: true });
2207
+ console.log(` Removed: ${target}`);
2208
+ } catch (err) {
2209
+ const msg = err instanceof Error ? err.message : String(err);
2210
+ console.warn(` Warning: Failed to remove ${target}: ${msg}`);
2211
+ }
2212
+ }
2213
+ }
2062
2214
  }
2215
+ if (isAll) {
2216
+ if (isDryRun) {
2217
+ console.log(" [dry-run] Would invoke server system clean (POST /api/system/clean)");
2218
+ return;
2219
+ }
2220
+ const config = loadConfig();
2221
+ if (!config.apiKey) {
2222
+ console.warn(" Warning: Server clean skipped because no API key is configured.");
2223
+ return;
2224
+ }
2225
+ console.log(" Invoking server system clean...");
2226
+ const client = new CliApiClient(config.apiUrl, config.apiKey);
2227
+ try {
2228
+ const serverResult = await client.cleanServer();
2229
+ console.log(` Server clean: ${serverResult.message}`);
2230
+ console.log(` Purged: ${serverResult.purged.export_files} export files, ${serverResult.purged.share_tokens} expired share tokens`);
2231
+ } catch (err) {
2232
+ const msg = err instanceof Error ? err.message : String(err);
2233
+ console.warn(` Warning: Server clean failed: ${msg}`);
2234
+ }
2235
+ }
2236
+ console.log(isDryRun ? "Dry run finished." : "Clean finished.");
2063
2237
  }
2064
2238
 
2065
2239
  // src/commands/context.ts
2240
+ function extractSupportedSlots(slug) {
2241
+ const normalized = slug.toLowerCase();
2242
+ if (normalized.includes("blank") || normalized.includes("empty")) {
2243
+ return ["{{ content }}"];
2244
+ }
2245
+ return ["{{ content }}", "{{ title }}"];
2246
+ }
2247
+ function printLayoutsSection(layouts) {
2248
+ console.log(`
2249
+ Available Shared Layouts (${layouts.length}):`);
2250
+ if (layouts.length === 0) {
2251
+ console.log(" (No shared layouts configured)");
2252
+ return;
2253
+ }
2254
+ layouts.forEach((layout, idx) => {
2255
+ const defaultTag = layout.is_default ? "yes" : "no";
2256
+ const slots = extractSupportedSlots(layout.slug).join(", ");
2257
+ console.log(` ${idx + 1}. ${layout.name} [slug: ${layout.slug}] (default: ${defaultTag})`);
2258
+ console.log(` Supported Slots: ${slots}`);
2259
+ });
2260
+ }
2261
+ function formatPropsSchema(propsSchema) {
2262
+ if (!propsSchema || Object.keys(propsSchema).length === 0) {
2263
+ return { summary: "(none)", exampleAttr: "" };
2264
+ }
2265
+ const entries = Object.entries(propsSchema);
2266
+ const summary = entries.map(([k, v]) => `${k}="${String(v)}"`).join(", ");
2267
+ const exampleAttr = ` ${entries.map(([k, v]) => `${k}="${String(v)}"`).join(" ")}`;
2268
+ return { summary, exampleAttr };
2269
+ }
2270
+ function printComponentsSection(components) {
2271
+ console.log(`
2272
+ Reusable Component Catalog (${components.length}):`);
2273
+ if (components.length === 0) {
2274
+ console.log(" (No reusable components registered yet)");
2275
+ return;
2276
+ }
2277
+ components.forEach((comp, idx) => {
2278
+ const { summary, exampleAttr } = formatPropsSchema(comp.props_schema);
2279
+ console.log(` ${idx + 1}. ${comp.name} [tag: <x-${comp.name}></x-${comp.name}> or <x-${comp.name} />]`);
2280
+ console.log(` Props: ${summary}`);
2281
+ console.log(` Example: <x-${comp.name}${exampleAttr}></x-${comp.name}>`);
2282
+ });
2283
+ console.log(" NOTE: Do NOT copy-paste repetitive markup for registered components.");
2284
+ console.log(" Always use <x-...> macro tags in your page templates.");
2285
+ }
2066
2286
  async function contextCommand(opts) {
2067
2287
  const config = loadConfig();
2068
2288
  const projectId = opts.projectId ?? config.projectId;
2069
2289
  if (!config.apiKey) {
2070
- console.error("Error: API key required. Run `refira auth login --api-key <key>`.");
2290
+ console.error("Error: API key required. Run `refira auth login --api-key <key>`.");
2071
2291
  process.exit(1);
2072
2292
  }
2073
2293
  if (!projectId) {
2074
- console.error("Error: Project ID required. Run `refira init --project-id <id>` or pass --project-id.");
2294
+ console.error("Error: Project ID required. Run `refira init --project-id <id>` or pass --project-id.");
2075
2295
  process.exit(1);
2076
2296
  }
2077
2297
  const client = new CliApiClient(config.apiUrl, config.apiKey);
@@ -2081,44 +2301,295 @@ async function contextCommand(opts) {
2081
2301
  const resolvedProjectId = data.context?.project_id ?? data.context?.project?.id ?? projectId;
2082
2302
  console.log(`
2083
2303
  ======================================================================`);
2084
- console.log(`\uD83D\uDCE6 REFIRA DESIGN CONTEXT: ${projectName} (${resolvedProjectId})`);
2304
+ console.log(`REFIRA DESIGN CONTEXT: ${projectName} (${resolvedProjectId})`);
2085
2305
  console.log("======================================================================");
2086
2306
  const font = extractFontFamily(data.context?.tokens?.typography, data.context?.project?.fontFamily ?? "Inter");
2087
2307
  console.log(`
2088
- \uD83D\uDD24 Primary Typography: Google Fonts "${font}"`);
2308
+ Primary Typography: Google Fonts "${font}"`);
2089
2309
  const colorTokens = data.context?.tokens?.colors ?? data.context?.tokens?.color;
2090
2310
  if (colorTokens) {
2091
2311
  console.log(`
2092
- \uD83C\uDFA8 Design Color Tokens:`);
2312
+ Design Color Tokens:`);
2093
2313
  for (const [tokenName, tokenVal] of Object.entries(colorTokens)) {
2094
2314
  const displayVal = typeof tokenVal === "string" ? tokenVal : tokenVal?.hex ?? tokenVal?.oklch ?? JSON.stringify(tokenVal);
2095
2315
  console.log(` --${tokenName}: ${displayVal}`);
2096
2316
  }
2097
2317
  }
2318
+ const layouts = data.layouts ?? data.context?.layouts ?? [];
2319
+ const activeLayout = data.active_layout ?? data.context?.active_layout ?? layouts.find((l) => l.is_active) ?? layouts.find((l) => l.is_default) ?? null;
2320
+ console.log(`
2321
+ Active Project Layout Focus:`);
2322
+ if (activeLayout) {
2323
+ console.log(` ${activeLayout.name} [slug: ${activeLayout.slug}] (ID: ${activeLayout.id})`);
2324
+ console.log(` Supported Slots: ${extractSupportedSlots(activeLayout.slug).join(", ")}`);
2325
+ } else {
2326
+ console.log(" (None selected - standalone fallback)");
2327
+ }
2328
+ printLayoutsSection(layouts);
2329
+ const components = data.components ?? data.context?.components ?? [];
2330
+ printComponentsSection(components);
2098
2331
  console.log(`
2099
- \uD83D\uDCC4 Existing Pages (${data.pages.length}):`);
2332
+ Existing Pages Roster & Archetypes (${data.pages.length}):`);
2100
2333
  if (data.pages.length === 0) {
2101
2334
  console.log(" (No pages generated yet)");
2102
2335
  } else {
2103
2336
  data.pages.forEach((p, idx) => {
2104
- console.log(` ${idx + 1}. ${p.name} [slug: ${p.slug}] (status: ${p.status})`);
2337
+ const pageLayout = p.layout_id ? layouts.find((l) => l.id === p.layout_id) : null;
2338
+ const archetype = pageLayout ? `${pageLayout.name} [${pageLayout.slug}]` : "Standalone";
2339
+ console.log(` ${idx + 1}. ${p.name} [slug: ${p.slug}] (status: ${p.status}, archetype: ${archetype})`);
2105
2340
  });
2106
2341
  }
2107
2342
  console.log(`======================================================================
2108
2343
  `);
2109
2344
  } catch (err) {
2110
2345
  const msg = err instanceof Error ? err.message : String(err);
2111
- console.error(`❌ Failed to retrieve design context: ${msg}`);
2346
+ console.error(`Failed to retrieve design context: ${msg}`);
2347
+ process.exit(1);
2348
+ }
2349
+ }
2350
+
2351
+ // src/commands/guide.ts
2352
+ var GUIDE_TOPICS = ["aria", "forms", "modals", "tables"];
2353
+ var GUIDES = {
2354
+ aria: `# Accessible ARIA Patterns for Refira Prototypes
2355
+
2356
+ ## 1. Principles
2357
+ - Always prefer semantic HTML elements (<button>, <nav>, <main>, <header>, <footer>) over generic <div> with ARIA roles.
2358
+ - Use \`aria-expanded="true|false"\` on disclosure buttons (accordions, dropdown menus).
2359
+ - Use \`aria-controls="<id>"\` linking the trigger to the toggled panel.
2360
+ - Use \`aria-current="page"\` on active navigation links.
2361
+ - Use \`aria-live="polite"\` for dynamic status updates (toasts, counter badges).
2362
+
2363
+ ## 2. Accessible Disclosure Pattern
2364
+ \`\`\`html
2365
+ <button
2366
+ type="button"
2367
+ id="toggle-btn"
2368
+ aria-expanded="false"
2369
+ aria-controls="panel-1"
2370
+ class="px-4 py-2 bg-gray-100 rounded text-sm font-medium focus:ring-2 focus:ring-blue-500"
2371
+ >
2372
+ Toggle Details
2373
+ </button>
2374
+
2375
+ <div
2376
+ id="panel-1"
2377
+ hidden
2378
+ class="p-4 mt-2 border rounded bg-white"
2379
+ >
2380
+ Content revealed when expanded.
2381
+ </div>
2382
+
2383
+ <script>
2384
+ const btn = document.getElementById('toggle-btn');
2385
+ const panel = document.getElementById('panel-1');
2386
+ btn.addEventListener('click', () => {
2387
+ const expanded = btn.getAttribute('aria-expanded') === 'true';
2388
+ btn.setAttribute('aria-expanded', String(!expanded));
2389
+ panel.hidden = expanded;
2390
+ });
2391
+ </script>
2392
+ \`\`\`
2393
+ `,
2394
+ forms: `# Accessible Forms & Validation Patterns for Refira Prototypes
2395
+
2396
+ ## 1. Principles
2397
+ - Every input must have an explicitly associated \`<label for="<id>">\`.
2398
+ - Required fields must specify \`required\` and \`aria-required="true"\`.
2399
+ - Invalid inputs must be marked with \`aria-invalid="true"\` and linked to error text via \`aria-describedby="<error-id>"\`.
2400
+ - Buttons inside forms must explicitly declare \`type="submit"\` or \`type="button"\`.
2401
+
2402
+ ## 2. Form Group with Error State
2403
+ \`\`\`html
2404
+ <form id="sample-form" novalidate class="space-y-4 max-w-md">
2405
+ <div>
2406
+ <label for="email-field" class="block text-sm font-medium text-gray-700 mb-1">
2407
+ Email address <span class="text-red-500" aria-hidden="true">*</span>
2408
+ </label>
2409
+ <input
2410
+ type="email"
2411
+ id="email-field"
2412
+ name="email"
2413
+ required
2414
+ aria-required="true"
2415
+ aria-describedby="email-error"
2416
+ class="w-full px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
2417
+ placeholder="you@example.com"
2418
+ />
2419
+ <p id="email-error" class="hidden text-xs text-red-600 mt-1" role="alert">
2420
+ Please enter a valid email address.
2421
+ </p>
2422
+ </div>
2423
+ <button
2424
+ type="submit"
2425
+ class="w-full bg-blue-600 text-white py-2 px-4 rounded-md font-medium text-sm hover:bg-blue-700"
2426
+ >
2427
+ Submit
2428
+ </button>
2429
+ </form>
2430
+
2431
+ <script>
2432
+ const form = document.getElementById('sample-form');
2433
+ const input = document.getElementById('email-field');
2434
+ const error = document.getElementById('email-error');
2435
+
2436
+ form.addEventListener('submit', (e) => {
2437
+ e.preventDefault();
2438
+ const isValid = input.value.includes('@') && input.value.includes('.');
2439
+ input.setAttribute('aria-invalid', String(!isValid));
2440
+ error.classList.toggle('hidden', isValid);
2441
+ });
2442
+ </script>
2443
+ \`\`\`
2444
+ `,
2445
+ modals: `# Accessible Modal Dialog Pattern for Refira Prototypes
2446
+
2447
+ ## 1. Principles
2448
+ - Use the native HTML5 \`<dialog>\` element or a container with \`role="dialog"\` and \`aria-modal="true"\`.
2449
+ - Provide \`aria-labelledby="<title-id>"\` pointing to the modal heading.
2450
+ - Close on Escape key and restore focus to the triggering element.
2451
+ - Backdrop click closes the modal dialog.
2452
+
2453
+ ## 2. Accessible Native Dialog Pattern
2454
+ \`\`\`html
2455
+ <button
2456
+ type="button"
2457
+ id="open-dialog-btn"
2458
+ class="px-4 py-2 bg-blue-600 text-white rounded text-sm font-medium hover:bg-blue-700"
2459
+ >
2460
+ Open Settings Modal
2461
+ </button>
2462
+
2463
+ <dialog
2464
+ id="settings-dialog"
2465
+ aria-labelledby="dialog-title"
2466
+ class="rounded-lg shadow-xl p-0 backdrop:bg-black/50 max-w-lg w-full"
2467
+ >
2468
+ <div class="p-6">
2469
+ <div class="flex items-center justify-between mb-4">
2470
+ <h2 id="dialog-title" class="text-lg font-semibold text-gray-900">
2471
+ Account Settings
2472
+ </h2>
2473
+ <button
2474
+ type="button"
2475
+ id="close-dialog-btn"
2476
+ aria-label="Close dialog"
2477
+ class="text-gray-400 hover:text-gray-600"
2478
+ >
2479
+
2480
+ </button>
2481
+ </div>
2482
+ <p class="text-sm text-gray-600 mb-6">
2483
+ Modify your account preferences below.
2484
+ </p>
2485
+ <div class="flex justify-end gap-2">
2486
+ <button
2487
+ type="button"
2488
+ id="cancel-dialog-btn"
2489
+ class="px-3 py-1.5 border rounded text-sm text-gray-700 hover:bg-gray-50"
2490
+ >
2491
+ Cancel
2492
+ </button>
2493
+ <button
2494
+ type="button"
2495
+ class="px-3 py-1.5 bg-blue-600 text-white rounded text-sm font-medium hover:bg-blue-700"
2496
+ >
2497
+ Save Changes
2498
+ </button>
2499
+ </div>
2500
+ </div>
2501
+ </dialog>
2502
+
2503
+ <script>
2504
+ const openBtn = document.getElementById('open-dialog-btn');
2505
+ const closeBtn = document.getElementById('close-dialog-btn');
2506
+ const cancelBtn = document.getElementById('cancel-dialog-btn');
2507
+ const dialog = document.getElementById('settings-dialog');
2508
+
2509
+ openBtn.addEventListener('click', () => dialog.showModal());
2510
+ closeBtn.addEventListener('click', () => dialog.close());
2511
+ cancelBtn.addEventListener('click', () => dialog.close());
2512
+
2513
+ dialog.addEventListener('click', (e) => {
2514
+ if (e.target === dialog) dialog.close();
2515
+ });
2516
+ </script>
2517
+ \`\`\`
2518
+ `,
2519
+ tables: `# Accessible Data Table Patterns for Refira Prototypes
2520
+
2521
+ ## 1. Principles
2522
+ - Always include a \`<caption>\` describing the table's purpose or summarizing its data.
2523
+ - Header cells must use \`<th scope="col">\` for column headers and \`<th scope="row">\` for row headers.
2524
+ - For sortable tables, use \`aria-sort="ascending|descending|none"\` on sortable \`<th>\` headers with an accessible \`<button>\`.
2525
+ - Maintain tabular layout responsiveness via horizontal overflow containers (\`overflow-x-auto\`).
2526
+
2527
+ ## 2. Accessible Sortable Table Pattern
2528
+ \`\`\`html
2529
+ <div class="overflow-x-auto rounded-lg border border-gray-200">
2530
+ <table class="min-w-full divide-y divide-gray-200 text-sm">
2531
+ <caption class="sr-only">Project pages and deployment revisions</caption>
2532
+ <thead class="bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
2533
+ <tr>
2534
+ <th scope="col" class="px-6 py-3">
2535
+ <button type="button" class="group flex items-center gap-1 font-semibold" aria-sort="ascending">
2536
+ Page Title
2537
+ <span class="text-gray-400 group-hover:text-gray-600" aria-hidden="true">▲</span>
2538
+ </button>
2539
+ </th>
2540
+ <th scope="col" class="px-6 py-3">Slug</th>
2541
+ <th scope="col" class="px-6 py-3">Status</th>
2542
+ <th scope="col" class="px-6 py-3 text-right">Actions</th>
2543
+ </tr>
2544
+ </thead>
2545
+ <tbody class="divide-y divide-gray-200 bg-white">
2546
+ <tr>
2547
+ <th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
2548
+ Overview Dashboard
2549
+ </th>
2550
+ <td class="px-6 py-4 text-gray-500">dashboard</td>
2551
+ <td class="px-6 py-4">
2552
+ <span class="inline-flex px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
2553
+ Approved
2554
+ </span>
2555
+ </td>
2556
+ <td class="px-6 py-4 text-right">
2557
+ <button type="button" class="text-blue-600 hover:text-blue-900 font-medium">Edit</button>
2558
+ </td>
2559
+ </tr>
2560
+ </tbody>
2561
+ </table>
2562
+ </div>
2563
+ \`\`\`
2564
+ `
2565
+ };
2566
+ function getGuideContent(topic) {
2567
+ const normalized = topic.trim().toLowerCase();
2568
+ return GUIDES[normalized] ?? null;
2569
+ }
2570
+ function guideCommand(topic) {
2571
+ const content = getGuideContent(topic);
2572
+ if (!content) {
2573
+ console.error(`Error: Unknown guide topic '${topic}'.`);
2574
+ console.error(`Supported topics: ${GUIDES_TOPICS_LIST}`);
2112
2575
  process.exit(1);
2113
2576
  }
2577
+ console.log(content);
2114
2578
  }
2579
+ var GUIDES_TOPICS_LIST = GUIDE_TOPICS.join(", ");
2115
2580
 
2116
2581
  // src/commands/init.ts
2117
- import fs2 from "node:fs";
2118
- import path2 from "node:path";
2582
+ import fs3 from "node:fs";
2583
+ import path3 from "node:path";
2119
2584
 
2120
- // src/templates/agents-guide-template.ts
2121
- function generateAgentsGuide(opts) {
2585
+ // src/templates/rules-template.ts
2586
+ var REFIRA_SENTINEL_START = "<!-- REFIRA:START -->";
2587
+ var REFIRA_SENTINEL_END = "<!-- REFIRA:END -->";
2588
+ var REFIRA_SENTINEL_BLOCK = `${REFIRA_SENTINEL_START}
2589
+ ### Refira UI Prototypes
2590
+ All prototype pages are located in \`.refira/pages/\`. When creating or editing prototypes, strictly adhere to [.refira/RULES.md](.refira/RULES.md) and activate skill \`refira\` (\`.agents/skills/refira/SKILL.md\`).
2591
+ ${REFIRA_SENTINEL_END}`;
2592
+ function generateRefiraRules(opts) {
2122
2593
  return `# Refira Project Instructions
2123
2594
 
2124
2595
  **Project Name:** ${opts.projectName}
@@ -2129,36 +2600,42 @@ function generateAgentsGuide(opts) {
2129
2600
  ---
2130
2601
 
2131
2602
  ## 1. Core Architecture Invariants
2132
- 1. **Output Format:** Standalone HTML5 + Tailwind CSS CDN only.
2133
- 2. **Typography:** Load and use Google Fonts "${opts.fontFamily}".
2134
- 3. **No UI Frameworks:** Prohibited from using React, Vue, Svelte, Angular, Solid, or JSX syntax (\`className=\`, \`onClick={...}\`, \`<Component />\`).
2135
- 4. **Strict Anchor & Navigation Invariant:** Prohibited from creating cross-page navigation, external links, empty \`href=""\`, or \`javascript:\` navigation in \`<a href="...">\`. Anchor links MUST point only to in-page section hashes (e.g. \`href="#pricing"\`) or dummy hash (\`href="#"\`). Target attributes (\`target="_blank"\`, \`target="_top"\`) and external \`<form action="...">\` are prohibited. For interactive triggers, use \`<button type="button">\`.
2136
- 5. **No Raw Emojis:** Do not include raw emojis (e.g. \uD83D\uDE80, \uD83D\uDCA1, \uD83D\uDD25) in HTML markup. Use SVG icon libraries (Lucide Icons or Heroicons).
2137
- 6. **Permitted Graphics:** Three.js, GSAP, Spline Viewer, and Lucide Icons via CDN are explicitly allowed.
2603
+ 1. **Workspace Location:** All prototype pages reside strictly in \`.refira/pages/<slug>.html\`.
2604
+ 2. **Output Format:** Standalone HTML5 + Tailwind CSS CDN only.
2605
+ 3. **Typography:** Load and use Google Fonts "${opts.fontFamily}".
2606
+ 4. **No UI Frameworks:** Prohibited from using React, Vue, Svelte, Angular, Solid, or JSX syntax (\`className=\`, \`onClick={...}\`, \`<Component />\`).
2607
+ 5. **Strict Anchor & Navigation Invariant:** Prohibited from creating cross-page navigation, external links, empty \`href=""\`, or \`javascript:\` navigation in \`<a href="...">\`. Anchor links MUST point only to in-page section hashes (e.g. \`href="#pricing"\`) or dummy hash (\`href="#"\`). Target attributes (\`target="_blank"\`, \`target="_top"\`) and external \`<form action="...">\` are prohibited. For interactive triggers, use \`<button type="button">\`.
2608
+ 6. **No Raw Emojis:** Do not include raw emojis (e.g. icons, smileys, unicode pictographs) in HTML markup. Use SVG icon libraries (Lucide Icons or Heroicons).
2609
+ 7. **Permitted Graphics:** Three.js, GSAP, Spline Viewer, and Lucide Icons via CDN are explicitly allowed.
2138
2610
 
2139
2611
  ---
2140
2612
 
2141
- ## 2. Refira CLI Command Surface
2142
- - \`refira context\`: Inspect project design tokens, colors, typography, and existing pages.
2143
- - \`refira scaffold --page <slug>\`: Create a clean HTML5 starter template and trigger live generating indicator on Refira Canvas.
2144
- - \`refira push <file.html> --page <slug>\` (or \`refira preview\`): Run the deterministic harness check and stream updates to Refira Canvas.
2145
- - \`refira skill install [-g]\`: Re-install the Refira design craftsmanship skill.
2613
+ ## 2. Reusable Components & Layout Macros
2614
+ 1. **Mandatory Context Check Protocol:** Sebelum membuat halaman baru di \`.refira/pages/\`, AI Agent WAJIB menjalankan \`refira context\` untuk memeriksa acuan layout aktif (\`Active Project Layout Focus\`), riwayat archetype halaman, dan katalog komponen terkini.
2615
+ 2. **Alur A Decision Protocol:** Jika maksud halaman baru terdeteksi berbeda kelompok dengan Active Layout (misal: halaman publik saat layout aktif adalah dashboard), AI Agent DILARANG mengubah layout diam-diam atau memaksakan layout yang salah. Tanyakan konfirmasi terlebih dahulu di terminal/chat kepada pengguna, lalu setelah disetujui jalankan \`refira layout select <slug>\`.
2616
+ 3. **Architectural Shell & Real Components:** Layout adalah acuan arsitektur. Anda wajib membuat komponen nyata untuk shell arsitektur tersebut (misal navbar / sidebar) dan menggunakannya di halaman-halaman selanjutnya menggunakan tag \`<x-...>\`.
2617
+ 4. **No Duplicate Markup:** DILARANG meng-copas markup navbar, sidebar, footer, atau elemen UI lain yang sudah terdaftar di katalog komponen proyek (\`refira context\`).
2618
+ 5. **Macro Usage:** Wajib gunakan tag makro \`<x-nama-komponen></x-nama-komponen>\` atau \`<x-nama-komponen />\` dengan atribut props yang didefinisikan pada props schema. Komponen akan di-expand secara otomatis oleh Refira Layout Engine saat push.
2619
+ 6. **Shared Layouts & Slots:** Halaman yang terikat pada shared layout harus fokus mengisi konten pada slot \`{{ content }}\` dan judul pada \`{{ title }}\` (jika ada), tanpa perlu mengulang boilerplate \`<head>\` atau kerangka shell yang sudah disediakan layout.
2146
2620
 
2147
2621
  ---
2148
2622
 
2149
- ## 3. Mandatory Execution Workflow
2150
- 1. Run \`refira context\` to understand the project theme and existing pages.
2151
- 2. Run \`refira scaffold --page <page-name>\` to register the page and activate live canvas pulse.
2152
- 3. Write your UI layout within the specified body slot using Tailwind utility classes.
2153
- 4. Execute \`refira push <page-name>.html --page <page-name>\`.
2154
- 5. If the CLI exits with code 1, address every listed violation and retry until exit code 0.
2623
+ ## 3. Refira CLI Command Surface
2624
+ - \`refira context\`: Inspect project design tokens, colors, typography, shared layouts, and existing pages.
2625
+ - \`refira layout select <slug>\`: Select active project layout focus by slug.
2626
+ - \`refira layout status\`: Inspect active project layout focus and available layouts.
2627
+ - \`refira scaffold --page <slug>\`: Create a clean HTML5 starter template in \`.refira/pages/<slug>.html\` and trigger live generating indicator on Refira Canvas.
2628
+ - \`refira push <slug> --expected-revision <hash>\` (or \`refira preview\`): Run deterministic harness check and stream updates to Refira Canvas.
2629
+ - \`refira annotations --page <slug>\`: Ingest open canvas reviewer comments and element selectors.
2630
+ - \`refira guide <topic>\`: Consult JIT interaction guidelines (aria, forms, modals, tables).
2631
+ - \`refira clean [--all]\`: Purge local agent scratch directories (\`.refira/work/\`).
2155
2632
 
2156
2633
  ---
2157
2634
 
2158
2635
  ## 4. Mandatory Delivery Invariant (Push to Canvas)
2159
2636
  - **Local HTML files are NOT the final deliverable.** Refira Canvas is the live source of truth.
2160
2637
  - **Strictly Prohibited:** Stopping, ending your response, or reporting completion to the user after only writing a local HTML file.
2161
- - **Autonomous Execution:** You MUST execute \`refira push <file.html> --page <slug>\` via your command-line execution tool. Do NOT ask the user to run it manually.
2638
+ - **Autonomous Execution:** You MUST execute \`refira push <slug> --expected-revision <hash>\` via your command-line execution tool. Do NOT ask the user to run it manually.
2162
2639
  - **Success Criteria:** A task is ONLY complete when \`refira push\` succeeds with exit code 0 and streams to Refira Canvas.
2163
2640
  `;
2164
2641
  }
@@ -2218,26 +2695,45 @@ When presenting features, data, or product capabilities:
2218
2695
 
2219
2696
  ## 5. Prototype Workflow in Refira
2220
2697
 
2221
- 1. Check current tokens: \`refira context\`
2222
- 2. Generate base page & trigger canvas pulse: \`refira scaffold --page <slug>\`
2223
- 3. Fill layout using Tailwind classes applying the aesthetic principles above.
2698
+ 1. Check current tokens & layout focus: \`refira context\`
2699
+ - Inspect Active Project Layout Focus and existing page archetypes.
2700
+ - If the new page requires a different architectural archetype, ask user for confirmation and run \`refira layout select <slug>\`.
2701
+ 2. Generate base page & trigger canvas pulse: \`refira scaffold --page <slug> --layout <slug>\`
2702
+ 3. Fill layout using real reusable components (\`<x-...>\`) and Tailwind classes applying the aesthetic principles above.
2224
2703
  4. Stream to Refira Canvas: \`refira push <slug>.html --page <slug>\`
2225
2704
  5. Mandatory: Never stop after writing local files. Always execute \`refira push\` to complete the task.
2226
2705
  `;
2227
2706
  }
2228
2707
 
2229
2708
  // src/commands/init.ts
2709
+ function appendSentinelIfPresent(filePath) {
2710
+ if (!fs3.existsSync(filePath)) {
2711
+ return false;
2712
+ }
2713
+ const content = fs3.readFileSync(filePath, "utf-8");
2714
+ if (!content.includes(REFIRA_SENTINEL_START)) {
2715
+ const separator = content.endsWith(`
2716
+ `) ? `
2717
+ ` : `
2718
+
2719
+ `;
2720
+ fs3.writeFileSync(filePath, `${content}${separator}${REFIRA_SENTINEL_BLOCK}
2721
+ `, "utf-8");
2722
+ return true;
2723
+ }
2724
+ return false;
2725
+ }
2230
2726
  async function initCommand(opts) {
2231
2727
  const config = loadConfig();
2232
2728
  const apiUrl = opts.apiUrl ?? config.apiUrl;
2233
2729
  const apiKey = opts.apiKey ?? config.apiKey;
2234
2730
  const projectId = opts.projectId ?? config.projectId;
2235
2731
  if (!apiKey) {
2236
- console.error("Error: API key is required. Run `refira auth login --api-key <key>` or pass --api-key.");
2732
+ console.error("Error: API key is required. Run `refira auth login --api-key <key>` or pass --api-key.");
2237
2733
  process.exit(1);
2238
2734
  }
2239
2735
  if (!projectId) {
2240
- console.error("Error: Project ID is required. Pass --project-id <uuid>");
2736
+ console.error("Error: Project ID is required. Pass --project-id <uuid>");
2241
2737
  process.exit(1);
2242
2738
  }
2243
2739
  console.log(`Initializing Refira workspace for project ${projectId}...`);
@@ -2246,36 +2742,124 @@ async function initCommand(opts) {
2246
2742
  const data = await client.getProjectContext(projectId);
2247
2743
  const projectName = data.context?.project_name ?? data.context?.project?.name ?? "Refira Project";
2248
2744
  const fontFamily = extractFontFamily(data.context?.tokens?.typography, data.context?.project?.fontFamily ?? "Inter");
2249
- const agentsGuide = generateAgentsGuide({
2745
+ const pagesDir = path3.join(REFIRA_DIR, "pages");
2746
+ const workDir = path3.join(REFIRA_DIR, "work");
2747
+ fs3.mkdirSync(pagesDir, { recursive: true });
2748
+ fs3.mkdirSync(workDir, { recursive: true });
2749
+ const rulesContent = generateRefiraRules({
2250
2750
  projectName,
2251
2751
  projectId,
2252
2752
  fontFamily,
2253
2753
  apiUrl
2254
2754
  });
2255
- fs2.writeFileSync("AGENTS.md", agentsGuide, "utf-8");
2256
- console.log(" Created AGENTS.md");
2257
- fs2.writeFileSync(".cursorrules", agentsGuide, "utf-8");
2258
- console.log(" Created .cursorrules");
2259
- const skillDir = path2.join(".agents", "skills", "refira");
2260
- fs2.mkdirSync(skillDir, { recursive: true });
2755
+ fs3.writeFileSync(path3.join(REFIRA_DIR, "RULES.md"), rulesContent, "utf-8");
2756
+ console.log(" Created .refira/RULES.md");
2757
+ const updatedAgents = appendSentinelIfPresent("AGENTS.md");
2758
+ if (updatedAgents) {
2759
+ console.log(" Appended Refira sentinel link to AGENTS.md");
2760
+ }
2761
+ const updatedClaude = appendSentinelIfPresent("CLAUDE.md");
2762
+ if (updatedClaude) {
2763
+ console.log(" Appended Refira sentinel link to CLAUDE.md");
2764
+ }
2765
+ const skillDir = path3.join(".agents", "skills", "refira");
2766
+ fs3.mkdirSync(skillDir, { recursive: true });
2261
2767
  const skillContent = generateRefiraSkill();
2262
- fs2.writeFileSync(path2.join(skillDir, "SKILL.md"), skillContent, "utf-8");
2768
+ fs3.writeFileSync(path3.join(skillDir, "SKILL.md"), skillContent, "utf-8");
2263
2769
  console.log(" Created .agents/skills/refira/SKILL.md");
2264
2770
  saveConfig({ apiUrl, apiKey, projectId }, false);
2265
- console.log(" Saved project configuration to .refirarc");
2771
+ console.log(" Saved project configuration to .refira/.refirarc");
2266
2772
  console.log(`
2267
- Refira workspace initialized successfully!`);
2268
- console.log(" Run `refira context` to inspect design tokens.");
2269
- console.log(" Run `refira scaffold --page <name>` to create your first page.");
2773
+ Refira workspace initialized successfully.`);
2774
+ console.log(" Run `refira context` to inspect design tokens and layout templates.");
2775
+ console.log(" Run `refira scaffold --page <name>` to create your first page.");
2270
2776
  } catch (err) {
2271
2777
  const msg = err instanceof Error ? err.message : String(err);
2272
- console.error(`❌ Initialization failed: ${msg}`);
2778
+ console.error(`Initialization failed: ${msg}`);
2779
+ process.exit(1);
2780
+ }
2781
+ }
2782
+
2783
+ // src/commands/layout.ts
2784
+ async function layoutSelectCommand(slug, opts) {
2785
+ const config = loadConfig();
2786
+ const projectId = opts.projectId ?? config.projectId;
2787
+ if (!config.apiKey) {
2788
+ console.error("Error: API key required. Run `refira auth login --api-key <key>`.");
2789
+ process.exit(1);
2790
+ }
2791
+ if (!projectId) {
2792
+ console.error("Error: Project ID required. Run `refira init --project-id <id>` or pass --project-id.");
2793
+ process.exit(1);
2794
+ }
2795
+ const client = new CliApiClient(config.apiUrl, config.apiKey);
2796
+ try {
2797
+ const result = await client.selectActiveLayout(projectId, { layout_slug: slug });
2798
+ console.log(`
2799
+ Active layout focus selected successfully:`);
2800
+ console.log(` Project ID: ${result.project_id}`);
2801
+ console.log(` Active Layout: ${result.layout_name} [slug: ${result.layout_slug}]`);
2802
+ console.log(` Layout ID: ${result.active_layout_id}
2803
+ `);
2804
+ } catch (err) {
2805
+ const msg = err instanceof Error ? err.message : String(err);
2806
+ console.error(`Failed to select active layout: ${msg}`);
2807
+ process.exit(1);
2808
+ }
2809
+ }
2810
+ async function layoutStatusCommand(opts) {
2811
+ const config = loadConfig();
2812
+ const projectId = opts.projectId ?? config.projectId;
2813
+ if (!config.apiKey) {
2814
+ console.error("Error: API key required. Run `refira auth login --api-key <key>`.");
2815
+ process.exit(1);
2816
+ }
2817
+ if (!projectId) {
2818
+ console.error("Error: Project ID required. Run `refira init --project-id <id>` or pass --project-id.");
2819
+ process.exit(1);
2820
+ }
2821
+ const client = new CliApiClient(config.apiUrl, config.apiKey);
2822
+ try {
2823
+ const data = await client.getProjectContext(projectId);
2824
+ const projectName = data.context?.project_name ?? data.context?.project?.name ?? "Refira Project";
2825
+ const layouts = data.layouts ?? data.context?.layouts ?? [];
2826
+ const activeLayout = data.active_layout ?? data.context?.active_layout ?? layouts.find((l) => l.is_active) ?? layouts.find((l) => l.is_default) ?? null;
2827
+ console.log(`
2828
+ ======================================================================`);
2829
+ console.log(`REFIRA PROJECT LAYOUT STATUS: ${projectName}`);
2830
+ console.log("======================================================================");
2831
+ console.log(`
2832
+ Active Project Layout Focus:`);
2833
+ if (activeLayout) {
2834
+ console.log(` Name: ${activeLayout.name}`);
2835
+ console.log(` Slug: ${activeLayout.slug}`);
2836
+ console.log(` ID: ${activeLayout.id}`);
2837
+ console.log(` Default: ${activeLayout.is_default ? "yes" : "no"}`);
2838
+ } else {
2839
+ console.log(" (None selected - using fallback or standalone)");
2840
+ }
2841
+ console.log(`
2842
+ Available Project Layouts (${layouts.length}):`);
2843
+ if (layouts.length === 0) {
2844
+ console.log(" (No layouts available)");
2845
+ } else {
2846
+ layouts.forEach((l, idx) => {
2847
+ const isActive = activeLayout?.id === l.id || l.is_active;
2848
+ console.log(` ${idx + 1}. ${l.name} [slug: ${l.slug}] (active: ${isActive ? "yes" : "no"}, default: ${l.is_default ? "yes" : "no"})`);
2849
+ });
2850
+ }
2851
+ console.log(`======================================================================
2852
+ `);
2853
+ } catch (err) {
2854
+ const msg = err instanceof Error ? err.message : String(err);
2855
+ console.error(`Failed to retrieve layout status: ${msg}`);
2273
2856
  process.exit(1);
2274
2857
  }
2275
2858
  }
2276
2859
 
2277
2860
  // src/commands/preview.ts
2278
- import fs3 from "node:fs";
2861
+ import fs4 from "node:fs";
2862
+ import path4 from "node:path";
2279
2863
 
2280
2864
  // ../../node_modules/.bun/entities@4.5.0/node_modules/entities/lib/esm/generated/decode-data-html.js
2281
2865
  var decode_data_html_default = new Uint16Array("ᵁ<Õıʊҝջאٵ۞ޢߖࠏ੊ઑඡ๭༉༦჊ረዡᐕᒝᓃᓟᔥ\x00\x00\x00\x00\x00\x00ᕫᛍᦍᰒᷝ὾⁠↰⊍⏀⏻⑂⠤⤒ⴈ⹈⿎〖㊺㘹㞬㣾㨨㩱㫠㬮ࠀEMabcfglmnoprstu\\bfms„‹•˜¦³¹ÈÏlig耻Æ䃆P耻&䀦cute耻Á䃁reve;䄂Āiyx}rc耻Â䃂;䐐r;쀀\uD835\uDD04rave耻À䃀pha;䎑acr;䄀d;橓Āgp¡on;䄄f;쀀\uD835\uDD38plyFunction;恡ing耻Å䃅Ācs¾Ãr;쀀\uD835\uDC9Cign;扔ilde耻Ã䃃ml耻Ä䃄ЀaceforsuåûþėĜĢħĪĀcrêòkslash;或Ŷöø;櫧ed;挆y;䐑ƀcrtąċĔause;戵noullis;愬a;䎒r;쀀\uD835\uDD05pf;쀀\uD835\uDD39eve;䋘còēmpeq;扎܀HOacdefhilorsuōőŖƀƞƢƵƷƺǜȕɳɸɾcy;䐧PY耻©䂩ƀcpyŝŢźute;䄆Ā;iŧŨ拒talDifferentialD;慅leys;愭ȀaeioƉƎƔƘron;䄌dil耻Ç䃇rc;䄈nint;戰ot;䄊ĀdnƧƭilla;䂸terDot;䂷òſi;䎧rcleȀDMPTLJNjǑǖot;抙inus;抖lus;投imes;抗oĀcsǢǸkwiseContourIntegral;戲eCurlyĀDQȃȏoubleQuote;思uote;怙ȀlnpuȞȨɇɕonĀ;eȥȦ户;橴ƀgitȯȶȺruent;扡nt;戯ourIntegral;戮ĀfrɌɎ;愂oduct;成nterClockwiseContourIntegral;戳oss;樯cr;쀀\uD835\uDC9EpĀ;Cʄʅ拓ap;才րDJSZacefiosʠʬʰʴʸˋ˗ˡ˦̳ҍĀ;oŹʥtrahd;椑cy;䐂cy;䐅cy;䐏ƀgrsʿ˄ˇger;怡r;憡hv;櫤Āayː˕ron;䄎;䐔lĀ;t˝˞戇a;䎔r;쀀\uD835\uDD07Āaf˫̧Ācm˰̢riticalȀADGT̖̜̀̆cute;䂴oŴ̋̍;䋙bleAcute;䋝rave;䁠ilde;䋜ond;拄ferentialD;慆Ѱ̽\x00\x00\x00͔͂\x00Ѕf;쀀\uD835\uDD3Bƀ;DE͈͉͍䂨ot;惜qual;扐blèCDLRUVͣͲ΂ϏϢϸontourIntegraìȹoɴ͹\x00\x00ͻ»͉nArrow;懓Āeo·ΤftƀARTΐΖΡrrow;懐ightArrow;懔eåˊngĀLRΫτeftĀARγιrrow;柸ightArrow;柺ightArrow;柹ightĀATϘϞrrow;懒ee;抨pɁϩ\x00\x00ϯrrow;懑ownArrow;懕erticalBar;戥ǹABLRTaВЪаўѿͼrrowƀ;BUНОТ憓ar;椓pArrow;懵reve;䌑eft˒к\x00ц\x00ѐightVector;楐eeVector;楞ectorĀ;Bљњ憽ar;楖ightǔѧ\x00ѱeeVector;楟ectorĀ;BѺѻ懁ar;楗eeĀ;A҆҇护rrow;憧ĀctҒҗr;쀀\uD835\uDC9Frok;䄐ࠀNTacdfglmopqstuxҽӀӄӋӞӢӧӮӵԡԯԶՒ՝ՠեG;䅊H耻Ð䃐cute耻É䃉ƀaiyӒӗӜron;䄚rc耻Ê䃊;䐭ot;䄖r;쀀\uD835\uDD08rave耻È䃈ement;戈ĀapӺӾcr;䄒tyɓԆ\x00\x00ԒmallSquare;旻erySmallSquare;斫ĀgpԦԪon;䄘f;쀀\uD835\uDD3Csilon;䎕uĀaiԼՉlĀ;TՂՃ橵ilde;扂librium;懌Āci՗՚r;愰m;橳a;䎗ml耻Ë䃋Āipժկsts;戃onentialE;慇ʀcfiosօֈ֍ֲ׌y;䐤r;쀀\uD835\uDD09lledɓ֗\x00\x00֣mallSquare;旼erySmallSquare;斪Ͱֺ\x00ֿ\x00\x00ׄf;쀀\uD835\uDD3DAll;戀riertrf;愱cò׋؀JTabcdfgorstר׬ׯ׺؀ؒؖ؛؝أ٬ٲcy;䐃耻>䀾mmaĀ;d׷׸䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀\uD835\uDD0A;拙pf;쀀\uD835\uDD3Eeater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀\uD835\uDCA2;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\x00ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇܎ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\x00ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀\uD835\uDD40a;䎙cr;愐ilde;䄨ǫޚ\x00ޞcy;䐆l耻Ï䃏ʀcfosuެ޷޼߂ߐĀiyޱ޵rc;䄴;䐙r;쀀\uD835\uDD0Dpf;쀀\uD835\uDD41ǣ߇\x00ߌr;쀀\uD835\uDCA5rcy;䐈kcy;䐄΀HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶߻dil;䄶;䐚r;쀀\uD835\uDD0Epf;쀀\uD835\uDD42cr;쀀\uD835\uDCA6րJTaceflmostࠥࠩࠬࡐࡣ঳সে্਷ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗ࡜ࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४Ānrࢃ࢏gleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\x00ࣃbleBracket;柦nǔࣈ\x00࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀\uD835\uDD0FĀ;eঽা拘ftarrow;懚idot;䄿ƀnpw৔ਖਛgȀLRlr৞৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀\uD835\uDD43erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼੝੠੷੼અઋ઎p;椅y;䐜Ādl੥੯iumSpace;恟lintrf;愳r;쀀\uD835\uDD10nusPlus;戓pf;쀀\uD835\uDD44cò੶;䎜ҀJacefostuણધભીଔଙඑ඗ඞcy;䐊cute;䅃ƀaey઴હાron;䅇dil;䅅;䐝ƀgswે૰଎ativeƀMTV૓૟૨ediumSpace;怋hiĀcn૦૘ë૙eryThiî૙tedĀGL૸ଆreaterGreateòٳessLesóੈLine;䀊r;쀀\uD835\uDD11ȀBnptଢନଷ଺reak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪୼஡௫ఄ౞಄ದ೘ൡඅ櫬Āou୛୤ngruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊ஛ement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater΀;EFGLSTஶஷ஽௉௓௘௥扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲௽ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ೒拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨೹setĀ;E೰ೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀\uD835\uDCA9ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂ෉෕ෛ෠෧෼ขภยา฿ไlig;䅒cute耻Ó䃓Āiy෎ීrc耻Ô䃔;䐞blac;䅐r;쀀\uD835\uDD12rave耻Ò䃒ƀaei෮ෲ෶cr;䅌ga;䎩cron;䎟pf;쀀\uD835\uDD46enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀\uD835\uDCAAash耻Ø䃘iŬื฼de耻Õ䃕es;樷ml耻Ö䃖erĀBP๋๠Āar๐๓r;怾acĀek๚๜;揞et;掴arenthesis;揜Ҁacfhilors๿ງຊຏຒດຝະ໼rtialD;戂y;䐟r;쀀\uD835\uDD13i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ໠໤檻cedesȀ;EST່້໏໚扺qual;檯lantEqual;扼ilde;找me;怳Ādp໩໮uct;戏ortionĀ;aȥ໹l;戝Āci༁༆r;쀀\uD835\uDCAB;䎨ȀUfos༑༖༛༟OT耻\"䀢r;쀀\uD835\uDD14pf;愚cr;쀀\uD835\uDCAC؀BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁࿫࿳ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL࿜࿝࿡憒ar;懥eftArrow;懄eiling;按oǵ࿹\x00စbleBracket;柧nǔည\x00နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀\uD835\uDD16ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»࿝pArrow;憑gma;䎣allCircle;战pf;쀀\uD835\uDD4Aɲᅭ\x00\x00ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀\uD835\uDCAEar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄ቉ቕ቞ቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHc቎ቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀\uD835\uDD17Āeiቻ኉Dzኀ\x00ኇefore;戴a;䎘Ācn኎ኘkSpace;쀀  Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀\uD835\uDD4BipleDot;惛Āctዖዛr;쀀\uD835\uDCAFrok;䅦ૡዷጎጚጦ\x00ጬጱ\x00\x00\x00\x00\x00ጸጽ፷ᎅ\x00᏿ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\x00጖y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀\uD835\uDD18rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻፿on;䅲f;쀀\uD835\uDD4CЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀\uD835\uDCB0ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀\uD835\uDD19pf;쀀\uD835\uDD4Dcr;쀀\uD835\uDCB1dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀\uD835\uDD1Apf;쀀\uD835\uDD4Ecr;쀀\uD835\uDCB2Ȁfiosᓋᓐᓒᓘr;쀀\uD835\uDD1B;䎞pf;쀀\uD835\uDD4Fcr;쀀\uD835\uDCB3ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀\uD835\uDD1Cpf;쀀\uD835\uDD50cr;쀀\uD835\uDCB4ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\x00ᕛoWidtè૙a;䎖r;愨pf;愤cr;쀀\uD835\uDCB5௡ᖃᖊᖐ\x00ᖰᖶᖿ\x00\x00\x00\x00ᗆᗛᗫᙟ᙭\x00ᚕ᚛ᚲᚹ\x00ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀\uD835\uDD1Erave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\x00\x00ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚΀;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀\uD835\uDD52΀;Eaeiop዁ᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;e዁ᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀\uD835\uDCB6;䀪mpĀ;e዁ᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰ᜼ᝃᝈ᝸᝽០៦ᠹᡐᜍ᤽᥈ᥰot;櫭Ācrᛶ᜞kȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e᜚᜛戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;t፜᜷brk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓ᝛ᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯ᝱ᝳ;䎲;愶een;扬r;쀀\uD835\uDD1Fg΀costuvwឍឝឳេ៕៛៞ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\x00\x00ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀako៭ᠦᠵĀcn៲ᠣkƀlst៺֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘᠝斴own;斾eft;旂ight;斸k;搣Ʊᠫ\x00ᠳƲᠯ\x00ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀\uD835\uDD53Ā;tᏋᡣom»Ꮜtie;拈؀DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬ᣿ᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教΀;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ᣷᣹᣻᣽;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ᤟;敛;敘;攘;攔΀;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģ᥂bar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀\uD835\uDCB7mi;恏mĀ;e᜚᜜lƀ;bhᥨᥩᥫ䁜;槅sub;柈Ŭᥴ᥾lĀ;e᥹᥺怢t»᥺pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\x00᧨ᨑᨕᨲ\x00ᨷᩐ\x00\x00᪴\x00\x00᫁\x00\x00ᬡᬮ᭍᭒\x00᯽\x00ᰌƀcpr᦭ᦲ᧝ute;䄇̀;abcdsᦿᧀᧄ᧊᧕᧙戩nd;橄rcup;橉Āau᧏᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\x00᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀\uD835\uDD20ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r΀;Ecefms᩟᩠ᩢᩫ᪤᪪᪮旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\x00\x00᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖᪚᪟»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇᫔᫺\x00ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ᫙\x00\x00᫢aĀ;t᫞᫟䀬;䁀ƀ;fl᫨᫩᫫戁îᅠeĀmx᫱᫶ent»᫩eóɍǧ᫾\x00ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀\uD835\uDD54oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀\uD835\uDCB8Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯΀delprvw᭠᭬᭷ᮂᮬᯔ᯹arrĀlr᭨᭪;椸;椵ɰ᭲\x00\x00᭵r;拞c;拟arrĀ;p᭿ᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\x00\x00ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰸᰻᰿ᱝᱩᱵᲊᲞᲬᲷ᳻᳿ᴍᵻᶑᶫᶻ᷆᷍rò΁ar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀\uD835\uDD21arĀlrᲳᲵ»ࣜ»သʀaegsv᳂͸᳖᳜᳠mƀ;oș᳊᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\x00\x00ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀\uD835\uDD55ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\x00\x00ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀\uD835\uDCB9;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄ὎὚ĀDoḆᴴoôᲉĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀\uD835\uDD22ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀\uD835\uDD56ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\x00\x00ỻíՈantĀglἂἆtr»ṝess»Ṻƀaeiἒ἖Ἒls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\x00ᾞ\x00ᾡᾧ\x00\x00ῆῌ\x00ΐ\x00ῦῪ \x00 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\x00\x00᾽g;耀ffig;耀ffl;쀀\uD835\uDD23lig;耀filig;쀀fjƀaltῙ῜ῡt;晭ig;耀flns;斱of;䆒ǰ΅\x00ῳf;쀀\uD835\uDD57ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao‌⁕Ācs‑⁒ႉ‸⁅⁈\x00⁐β•‥‧‪‬\x00‮耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\x00‶;慔;慖ʴ‾⁁\x00\x00⁃耻¾䂾;慗;慜5;慘ƶ⁌\x00⁎;慚;慝8;慞l;恄wn;挢cr;쀀\uD835\uDCBBࢀEabcdefgijlnorstv₂₉₟₥₰₴⃰⃵⃺⃿℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕ₝ute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽⃉ƀ;qsؾٌ⃄lanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀\uD835\uDD24Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀\uD835\uDD58Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqr׮ⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\x00↎proø₞r;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀\uD835\uDD25sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀\uD835\uDD59bar;怕ƀclt≯≴≸r;쀀\uD835\uDCBDasè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\x00⊪\x00⊸⋅⋎\x00⋕⋳\x00\x00⋸⌢⍧⍢⍿\x00⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀\uD835\uDD26rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀\uD835\uDD5Aa;䎹uest耻¿䂿Āci⎊⎏r;쀀\uD835\uDCBEnʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\x00⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀\uD835\uDD27ath;䈷pf;쀀\uD835\uDD5Bǣ⏬\x00⏱r;쀀\uD835\uDCBFrcy;䑘kcy;䑔Ѐacfghjos␋␖␢␧␭␱␵␻ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀\uD835\uDD28reen;䄸cy;䑅cy;䑜pf;쀀\uD835\uDD5Ccr;쀀\uD835\uDCC0஀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼rò৆òΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\x00⒪\x00⒱\x00\x00\x00\x00\x00⒵Ⓔ\x00ⓆⓈⓍ\x00⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonó྘quigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀\uD835\uDD29Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀\uD835\uDD5Dus;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d྘➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀\uD835\uDCC1mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ᠛旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀\uD835\uDD2Ao;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀\uD835\uDD5EĀct⣸⣽r;쀀\uD835\uDCC2pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐௏쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop඄⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roø඄urĀ;a⧓⧔普lĀ;s⧓ସdz⧟\x00⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\x00⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓΀;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨í஘istĀ;s஠டr;쀀\uD835\uDD2BȀEest௅⩦⩹⩼ƀ;qs஼⩭௡ƀ;qs஼௅⩴lanô௢ií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚΀AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs఻⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs఻⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀\uD835\uDD5F膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast୻⭕⭚⭟lleì୻l;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖ΀chimpqu⮽⯍⯙⬄୸⯤⯯Ȁ;cerല⯆ഷ⯉uå൅;쀀\uD835\uDCC3ortɭ⬅\x00\x00⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭å೸åഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñ೗Ā;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰⳴ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ⴭ\x00ⴸⵈⵠⵥ⵲ⶄᬇ\x00\x00ⶍⶫ\x00ⷈⷎ\x00ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;c᪞ⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācr⵩⵭ir;榿;쀀\uD835\uDD2Cͯ⵹\x00\x00⵼\x00ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕ⶘ⶥⶨrò᪀Āir⶝ⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀\uD835\uDD60ƀaelⷔ⷗ǒr;榷rp;榹΀;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ⹞\x00⹽\x00⺀⺝\x00⺢⺹\x00\x00⻋ຜ\x00⼓\x00\x00⼫⾼\x00⿈rȀ;astЃ⹧⹲຅脀¶;l⹭⹮䂶leìЃɩ⹸\x00\x00⹻m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀\uD835\uDD2Dƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳⻴ᤈ⻹⻽⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀\uD835\uDD61nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp໬⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t໻⾴ï໻rel;抰Āci⿀⿅r;쀀\uD835\uDCC5;䏈ncsp;怈̀fiopsu⿚⋢⿟⿥⿫⿱r;쀀\uD835\uDD2Epf;쀀\uD835\uDD62rime;恗cr;쀀\uD835\uDCC6ƀaeo⿸〉〓tĀei⿾々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔઀ABHabcdefhilmnoprstux぀けさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤΀cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstw࿜ガクシスゼゾダッデナp;極Ā;f࿠ゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes㄂㄄;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ì࿲âヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀\uD835\uDD2FĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘㇤㇮rrowĀ;t࿜ㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowó࿪arpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓rò࿪aòՑ;怏oustĀ;a㈞㈟掱che»㈟mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀\uD835\uDD63us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀\uD835\uDCC7Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\x00㍺㎤\x00\x00㏬㏰\x00㐨㑈㑚㒭㒱㓊㓱\x00㘖\x00\x00㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\x00㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦΀Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼਴t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀\uD835\uDD30Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\x00\x00㎜iäᑤaraì⹯耻­䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;q኱ኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀\uD835\uDD64aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀\uD835\uDCC8tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫ਩war;椪lig耻ß䃟௡㙑㙝㙠ዎ㙳㙹\x00㙾㛂\x00\x00\x00\x00\x00㛛㜃\x00㜉㝬\x00\x00\x00㞇ɲ㙖\x00\x00㙛get;挖;䏄rë๟ƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀\uD835\uDD31Ȁeiko㚆㚝㚵㚼Dz㚋\x00㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproø዁im»ኬsðኞĀas㚺㚮ð዁rn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀\uD835\uDD65rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈ΀adempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀\uD835\uDCC9;䑆cy;䑛rok;䅧Āio㞋㞎xô᝷headĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\x00㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀\uD835\uDD32rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\x00\x00㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀\uD835\uDD66̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\x00\x00㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀\uD835\uDCCAƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜΀eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀\uD835\uDD33tré㦮suĀbp㧯㧱»ജ»൙pf;쀀\uD835\uDD67roð໻tré㦴Ācu㨆㨋r;쀀\uD835\uDCCBĀbp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚΀cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀\uD835\uDD34pf;쀀\uD835\uDD68Ā;eᑹ㩦atèᑹcr;쀀\uD835\uDCCCૣណ㪇\x00㪋\x00㪐㪛\x00\x00㪝㪨㪫㪯\x00\x00㫃㫎\x00㫘ៜ៟tré៑r;쀀\uD835\uDD35ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀\uD835\uDD69imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀\uD835\uDCCDĀpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀\uD835\uDD36cy;䑗pf;쀀\uD835\uDD6Acr;쀀\uD835\uDCCEĀcm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀\uD835\uDD37cy;䐶grarr;懝pf;쀀\uD835\uDD6Bcr;쀀\uD835\uDCCFĀjn㮅㮇;怍j;怌".split("").map((c) => c.charCodeAt(0)));
@@ -3808,7 +4392,7 @@ function formatHarnessErrorReport(violations, filePath) {
3808
4392
  const lines = [
3809
4393
  "",
3810
4394
  "======================================================================",
3811
- "[REFIRA HARNESS] VALIDATION FAILED — EXIT CODE 1",
4395
+ "[REFIRA HARNESS] VALIDATION FAILED — EXIT CODE 1",
3812
4396
  "======================================================================",
3813
4397
  `Target File: ${filePath}`,
3814
4398
  `Total Violations Found: ${violations.length}`,
@@ -3825,7 +4409,7 @@ function formatHarnessErrorReport(violations, filePath) {
3825
4409
  lines.push("");
3826
4410
  });
3827
4411
  lines.push("======================================================================");
3828
- lines.push("\uD83D\uDC49 ACTION REQUIRED FOR AI AGENT:");
4412
+ lines.push("ACTION REQUIRED FOR AI AGENT:");
3829
4413
  lines.push("1. Review the remediation instructions above for each violation.");
3830
4414
  lines.push("2. Edit the HTML file to eliminate prohibited framework syntax, emojis, or external links.");
3831
4415
  lines.push("3. Re-run `refira preview <file.html> --page <page>` until validation succeeds.");
@@ -3838,12 +4422,12 @@ function formatHarnessSuccessReport(pageSlug, previewUrl) {
3838
4422
  const lines = [
3839
4423
  "",
3840
4424
  "======================================================================",
3841
- "[REFIRA HARNESS] VALIDATION PASSED — EXIT CODE 0",
4425
+ "[REFIRA HARNESS] VALIDATION PASSED — EXIT CODE 0",
3842
4426
  "======================================================================",
3843
4427
  `Page '${pageSlug}' markup conforms to Refira HTML5 + Tailwind invariants.`
3844
4428
  ];
3845
4429
  if (previewUrl) {
3846
- lines.push(`\uD83D\uDE80 Live Preview Updated: ${previewUrl}`);
4430
+ lines.push(`Live Preview Updated: ${previewUrl}`);
3847
4431
  }
3848
4432
  lines.push("======================================================================");
3849
4433
  lines.push("");
@@ -3852,45 +4436,73 @@ function formatHarnessSuccessReport(pageSlug, previewUrl) {
3852
4436
  }
3853
4437
 
3854
4438
  // src/commands/preview.ts
3855
- async function previewCommand(filePath, opts) {
3856
- const pageSlug = opts.page?.trim().toLowerCase();
4439
+ function resolveTargetFile(fileArg, pageSlug) {
4440
+ if (fileArg && fs4.existsSync(fileArg)) {
4441
+ return fileArg;
4442
+ }
4443
+ if (fileArg && fs4.existsSync(path4.join(REFIRA_DIR, "pages", fileArg))) {
4444
+ return path4.join(REFIRA_DIR, "pages", fileArg);
4445
+ }
4446
+ if (fileArg && fs4.existsSync(path4.join(REFIRA_DIR, "pages", `${fileArg}.html`))) {
4447
+ return path4.join(REFIRA_DIR, "pages", `${fileArg}.html`);
4448
+ }
4449
+ if (pageSlug) {
4450
+ const refiraPage = path4.join(REFIRA_DIR, "pages", `${pageSlug}.html`);
4451
+ if (fs4.existsSync(refiraPage))
4452
+ return refiraPage;
4453
+ const rootPage = `${pageSlug}.html`;
4454
+ if (fs4.existsSync(rootPage))
4455
+ return rootPage;
4456
+ }
4457
+ return null;
4458
+ }
4459
+ async function previewCommand(fileArg, opts) {
4460
+ const pageSlug = opts?.page?.trim().toLowerCase() ?? (fileArg ? path4.basename(fileArg, ".html").toLowerCase() : undefined);
3857
4461
  if (!pageSlug) {
3858
- console.error("Error: --page <slug> is required. Example: refira preview index.html --page home");
4462
+ console.error("Error: Target page is required. Specify <file> or --page <slug>.");
3859
4463
  process.exit(1);
3860
4464
  }
3861
- if (!fs3.existsSync(filePath)) {
3862
- console.error(`❌ Error: File '${filePath}' does not exist.`);
4465
+ const resolvedPath = resolveTargetFile(fileArg, pageSlug);
4466
+ if (!resolvedPath) {
4467
+ console.error(`Error: File for page '${pageSlug}' not found. Looked in '${path4.join(REFIRA_DIR, "pages", `${pageSlug}.html`)}'.`);
3863
4468
  process.exit(1);
3864
4469
  }
3865
- const content = fs3.readFileSync(filePath, "utf-8");
3866
- console.log(`\uD83D\uDD0D Inspecting '${filePath}' with Refira Agent Harness...`);
4470
+ const content = fs4.readFileSync(resolvedPath, "utf-8");
4471
+ console.log(`Inspecting '${resolvedPath}' with Refira Agent Harness...`);
3867
4472
  const validation = validateHtmlMarkup(content);
3868
4473
  if (!validation.valid) {
3869
- const errorReport = formatHarnessErrorReport(validation.violations, filePath);
4474
+ const errorReport = formatHarnessErrorReport(validation.violations, resolvedPath);
3870
4475
  console.error(errorReport);
3871
4476
  process.exit(1);
3872
4477
  }
3873
4478
  const config = loadConfig();
3874
4479
  if (!config.apiKey || !config.projectId) {
3875
4480
  console.log(formatHarnessSuccessReport(pageSlug));
3876
- console.log("⚠️ Offline mode: Markup is valid. To stream live preview to Canvas, run `refira auth login`.");
4481
+ console.log("Offline mode: Markup is valid. To stream live preview to Canvas, run `refira auth login`.");
3877
4482
  process.exit(0);
3878
4483
  }
3879
- console.log(`\uD83D\uDE80 Harness passed! Streaming '${pageSlug}' to Refira Canvas...`);
4484
+ console.log(`Harness passed! Streaming '${pageSlug}' to Refira Canvas...`);
3880
4485
  const client = new CliApiClient(config.apiUrl, config.apiKey);
3881
4486
  try {
3882
- const result = await client.pushPreview(config.projectId, pageSlug, content);
4487
+ const result = await client.pushPreview(config.projectId, pageSlug, content, opts?.expectedRevision);
3883
4488
  console.log(formatHarnessSuccessReport(pageSlug, result.preview_url));
3884
4489
  process.exit(0);
3885
4490
  } catch (err) {
4491
+ const isConflict = err?.code === "REVISION_CONFLICT" || err instanceof Error && err.message.includes("modified concurrently");
4492
+ if (isConflict) {
4493
+ console.error(`Error: Page '${pageSlug}' has been modified concurrently (REVISION_CONFLICT).`);
4494
+ console.error("Please fetch the latest revision with `refira context` and re-apply changes.");
4495
+ process.exit(1);
4496
+ }
3886
4497
  const msg = err instanceof Error ? err.message : String(err);
3887
- console.error(`❌ Failed to stream preview to Refira: ${msg}`);
4498
+ console.error(`Failed to stream preview to Refira: ${msg}`);
3888
4499
  process.exit(1);
3889
4500
  }
3890
4501
  }
3891
4502
 
3892
4503
  // src/commands/scaffold.ts
3893
- import fs4 from "node:fs";
4504
+ import fs5 from "node:fs";
4505
+ import path5 from "node:path";
3894
4506
 
3895
4507
  // src/templates/scaffold-template.ts
3896
4508
  function generateScaffoldHtml(opts) {
@@ -3964,84 +4576,135 @@ function generateScaffoldHtml(opts) {
3964
4576
  </html>
3965
4577
  `;
3966
4578
  }
4579
+ function generateLayoutScaffoldHtml(opts) {
4580
+ const macroNotice = opts.componentMacros && opts.componentMacros.length > 0 ? `
4581
+ <!-- Available component macros: ${opts.componentMacros.map((m) => `<x-${m} />`).join(", ")} -->` : "";
4582
+ return `<!-- ====================================================================== -->
4583
+ <!-- [REFIRA LAYOUT BINDING: ${opts.layoutSlug}]
4584
+ <!-- Project: ${opts.projectName} | Page: ${opts.pageName}
4585
+ <!-- AI Agent: This page is bound to shared layout '${opts.layoutSlug}'.
4586
+ <!-- Write ONLY the inner UI content for slot {{ content }}.
4587
+ <!-- Do NOT write standard document tags (doctype, html, head, body).${macroNotice}
4588
+ <!-- ====================================================================== -->
4589
+
4590
+ <!-- [REFIRA CANVAS SLOT: START] -->
4591
+ <div class="space-y-6">
4592
+ <!-- Page Header -->
4593
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 pb-6 border-b border-slate-200">
4594
+ <div>
4595
+ <h1 class="text-2xl font-bold tracking-tight text-slate-900">${opts.pageName}</h1>
4596
+ <p class="text-sm text-slate-500 mt-1">Start crafting the content for ${opts.pageName.toLowerCase()}.</p>
4597
+ </div>
4598
+ <div class="flex items-center gap-3">
4599
+ <button type="button" class="inline-flex items-center px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 transition">
4600
+ Action
4601
+ </button>
4602
+ </div>
4603
+ </div>
4604
+
4605
+ <!-- Content Section -->
4606
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
4607
+ <div class="p-6 bg-white rounded-xl border border-slate-200 shadow-sm space-y-2">
4608
+ <p class="text-sm font-medium text-slate-500">Overview</p>
4609
+ <p class="text-2xl font-bold text-slate-900">${opts.pageName}</p>
4610
+ </div>
4611
+ </div>
4612
+ </div>
4613
+ <!-- [REFIRA CANVAS SLOT: END] -->
4614
+ `;
4615
+ }
3967
4616
 
3968
4617
  // src/commands/scaffold.ts
4618
+ async function resolveProjectDetails(apiUrl, apiKey, projectId, pageSlug, layoutSlug) {
4619
+ const client = new CliApiClient(apiUrl, apiKey);
4620
+ const data = await client.getProjectContext(projectId);
4621
+ const projectName = data.context?.project_name ?? data.context?.project?.name ?? "Refira Project";
4622
+ const fontFamily = extractFontFamily(data.context?.tokens?.typography, data.context?.project?.fontFamily ?? "Inter");
4623
+ let colorTokens;
4624
+ const colors = data.context?.tokens?.colors ?? data.context?.tokens?.color;
4625
+ if (colors) {
4626
+ const parsed = {};
4627
+ for (const [k, v] of Object.entries(colors)) {
4628
+ parsed[k] = typeof v === "string" ? v : v?.hex ?? v?.oklch ?? "";
4629
+ }
4630
+ colorTokens = parsed;
4631
+ }
4632
+ const rawComponents = data.context?.components ?? data.components ?? [];
4633
+ const componentMacros = rawComponents.map((c) => c.name);
4634
+ try {
4635
+ await client.startGenerating(projectId, pageSlug, layoutSlug);
4636
+ console.log(`Activated live generation indicator on Refira Canvas for '${pageSlug}'`);
4637
+ } catch {}
4638
+ return { projectName, fontFamily, colorTokens, componentMacros };
4639
+ }
3969
4640
  async function scaffoldCommand(opts) {
3970
4641
  const pageSlug = opts.page.trim().toLowerCase();
3971
4642
  if (!pageSlug) {
3972
- console.error("Error: --page <slug> is required.");
4643
+ console.error("Error: --page <slug> is required.");
3973
4644
  process.exit(1);
3974
4645
  }
4646
+ const layoutSlug = opts.layout?.trim().toLowerCase();
3975
4647
  const config = loadConfig();
3976
- let projectName = "Refira Project";
3977
- let fontFamily = "Inter";
3978
- let colorTokens;
4648
+ let details = {
4649
+ projectName: "Refira Project",
4650
+ fontFamily: "Inter",
4651
+ colorTokens: undefined,
4652
+ componentMacros: undefined
4653
+ };
3979
4654
  if (config.apiKey && config.projectId) {
3980
4655
  try {
3981
- const client = new CliApiClient(config.apiUrl, config.apiKey);
3982
- const data = await client.getProjectContext(config.projectId);
3983
- projectName = data.context?.project_name ?? data.context?.project?.name ?? projectName;
3984
- fontFamily = extractFontFamily(data.context?.tokens?.typography, data.context?.project?.fontFamily ?? fontFamily);
3985
- const colors = data.context?.tokens?.colors ?? data.context?.tokens?.color;
3986
- if (colors) {
3987
- const parsed = {};
3988
- for (const [k, v] of Object.entries(colors)) {
3989
- parsed[k] = typeof v === "string" ? v : v?.hex ?? v?.oklch ?? "";
3990
- }
3991
- colorTokens = parsed;
3992
- }
3993
- try {
3994
- await client.startGenerating(config.projectId, pageSlug);
3995
- console.log(`✨ Activated live generation indicator on Refira Canvas for '${pageSlug}'`);
3996
- } catch {}
4656
+ details = await resolveProjectDetails(config.apiUrl, config.apiKey, config.projectId, pageSlug, layoutSlug);
3997
4657
  } catch {}
3998
4658
  }
3999
4659
  const pageName = pageSlug.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
4000
- const html = generateScaffoldHtml({
4660
+ const html = layoutSlug ? generateLayoutScaffoldHtml({
4661
+ pageName,
4662
+ projectName: details.projectName,
4663
+ layoutSlug,
4664
+ componentMacros: details.componentMacros
4665
+ }) : generateScaffoldHtml({
4001
4666
  pageName,
4002
- projectName,
4003
- fontFamily,
4004
- colorTokens
4667
+ projectName: details.projectName,
4668
+ fontFamily: details.fontFamily,
4669
+ colorTokens: details.colorTokens
4005
4670
  });
4006
- const targetPath = opts.output ?? `${pageSlug}.html`;
4007
- fs4.writeFileSync(targetPath, html, "utf-8");
4008
- console.log(`✅ Scaffolded starter page '${targetPath}' successfully!`);
4009
- console.log(` Font: ${fontFamily}`);
4010
- console.log(" Slot: <!-- [REFIRA CANVAS SLOT: START] -->");
4011
- console.log(`
4012
- \uD83D\uDEA8 [MANDATORY AGENT COMPLETION GATE]:`);
4013
- console.log(` 1. Write your UI layout within the designated slot in '${targetPath}'.`);
4014
- console.log(` 2. You MUST execute: refira push ${targetPath} --page ${pageSlug}`);
4015
- console.log(" DO NOT stop your response or declare completion until `refira push` succeeds!");
4016
- }
4017
-
4018
- // src/commands/skill.ts
4019
- import fs5 from "node:fs";
4020
- import os2 from "node:os";
4021
- import path3 from "node:path";
4022
- async function skillInstallCommand(opts) {
4023
- const baseDir = opts.global ? path3.join(os2.homedir(), ".agents", "skills", "refira") : path3.join(process.cwd(), ".agents", "skills", "refira");
4024
- fs5.mkdirSync(baseDir, { recursive: true });
4025
- const targetFile = path3.join(baseDir, "SKILL.md");
4026
- const content = generateRefiraSkill();
4027
- fs5.writeFileSync(targetFile, content, "utf-8");
4028
- console.log("✅ Refira Design Craftsmanship Skill installed successfully!");
4029
- console.log(` Location: ${targetFile}`);
4030
- console.log(` Scope: ${opts.global ? "Global (~/.agents/skills/refira)" : "Local (.agents/skills/refira)"}`);
4671
+ const defaultDir = path5.join(REFIRA_DIR, "pages");
4672
+ const targetPath = opts.output ?? path5.join(defaultDir, `${pageSlug}.html`);
4673
+ const targetDir = path5.dirname(targetPath);
4674
+ if (!fs5.existsSync(targetDir)) {
4675
+ fs5.mkdirSync(targetDir, { recursive: true });
4676
+ }
4677
+ fs5.writeFileSync(targetPath, html, "utf-8");
4678
+ console.log(`Scaffolded starter page '${targetPath}' successfully.`);
4679
+ if (layoutSlug) {
4680
+ console.log(` Layout: ${layoutSlug} (slot fragment)`);
4681
+ } else {
4682
+ console.log(` Font: ${details.fontFamily}`);
4683
+ }
4684
+ console.log(" Slot: <!-- [REFIRA CANVAS SLOT: START] -->");
4031
4685
  console.log(`
4032
- AI coding agents will automatically recognize this skill for high-aesthetic prototype design.`);
4686
+ Mandatory Agent Completion Gate:`);
4687
+ console.log(` 1. Write your UI layout within the designated slot in '${targetPath}'.`);
4688
+ console.log(` 2. You MUST execute: refira push ${pageSlug}`);
4689
+ console.log(" DO NOT declare completion until `refira push` succeeds.");
4033
4690
  }
4034
4691
 
4035
4692
  // src/index.ts
4036
4693
  var program2 = new Command2;
4037
- program2.name("refira").description("Refira CLI tool and AI Agent Harness for deterministic prototype generation").version("0.1.3");
4694
+ program2.name("refira").description("Refira CLI tool and AI Agent Harness for deterministic prototype generation").version("0.2.0");
4038
4695
  var auth = program2.command("auth").description("Manage Refira API authentication and sessions");
4039
4696
  auth.command("login").description("Login to Refira using an API key").option("--api-url <url>", "Refira backend API base URL", "http://localhost:3001").option("--api-key <key>", "Project API key (rfr_...)").option("-g, --global", "Save credentials globally in user home directory", false).action(loginCommand);
4040
4697
  auth.command("status").description("Verify and display current Refira authentication session").action(statusCommand);
4041
- program2.command("init").description("Initialize a Refira project workspace, generating AGENTS.md, .cursorrules, and skills").requiredOption("--project-id <id>", "Target project UUID").option("--api-url <url>", "Refira backend API base URL").option("--api-key <key>", "Project API key (rfr_...)").action(initCommand);
4698
+ program2.command("init").description("Initialize a Refira project workspace (.refira/RULES.md, pages/, and skills)").requiredOption("--project-id <id>", "Target project UUID").option("--api-url <url>", "Refira backend API base URL").option("--api-key <key>", "Project API key (rfr_...)").action(initCommand);
4042
4699
  program2.command("context").description("Inspect design tokens, color palette, typography, and page roster").option("--project-id <id>", "Project UUID").action(contextCommand);
4043
- program2.command("scaffold").description("Generate a clean HTML5 + Tailwind starter template for a page").requiredOption("--page <slug>", "Page slug (e.g. checkout, dashboard, landing)").option("--output <path>", "Destination file path").action(scaffoldCommand);
4044
- program2.command("preview").alias("push").description("Inspect HTML markup with Agent Harness and stream to Refira Canvas").argument("<file>", "Path to HTML file to preview").requiredOption("--page <slug>", "Target page slug").action(previewCommand);
4700
+ program2.command("scaffold").description("Generate a clean HTML5 + Tailwind starter template for a page").requiredOption("--page <slug>", "Page slug (e.g. checkout, dashboard, landing)").option("--layout <slug>", "Bind page to an existing shared layout").option("--output <path>", "Destination file path").action(scaffoldCommand);
4701
+ program2.command("preview").alias("push").description("Inspect HTML markup with Agent Harness and stream to Refira Canvas").argument("[file]", "Path to HTML file to preview").option("--page <slug>", "Target page slug").option("--expected-revision <hash>", "Expected SHA-256 revision hash for OCC concurrency guard").action((file, options) => previewCommand(file, options));
4702
+ program2.command("annotations").description("Fetch open canvas annotations to guide AI agent page revisions").requiredOption("--page <slug>", "Target page slug or ID").option("--format <format>", "Output format (table|json|md)", "table").action(annotationsCommand);
4703
+ program2.command("guide").description("Display JIT interaction pattern guidance (aria, forms, modals, tables)").argument("<topic>", "Interaction topic (aria, forms, modals, tables)").action(guideCommand);
4704
+ program2.command("clean").description("Clean up local agent work directories and scratch files").option("--all", "Also invoke server system clean to purge expired tokens and export bundles", false).option("--dry-run", "List targets to be removed without deleting", false).action(cleanCommand);
4045
4705
  var skill = program2.command("skill").description("Manage Refira Agent Skills");
4046
- skill.command("install").description("Install the Refira design craftsmanship skill (.agents/skills/refira/SKILL.md)").option("-g, --global", "Install globally into user home directory (~/.agents/skills/refira)", false).action(skillInstallCommand);
4706
+ skill.command("install").description("Install the Refira design craftsmanship skill (.agents/skills/refira/SKILL.md)").option("-g, --global", "Install globally into user home directory (~/.agents/skills/refira)", false);
4707
+ var layout = program2.command("layout").description("Inspect and select active project layout focus");
4708
+ layout.command("select").description("Set active project layout focus by slug").argument("<slug>", "Layout slug (e.g. dashboard, landing, auth)").option("--project-id <id>", "Target project UUID").action((slug, options) => layoutSelectCommand(slug, options));
4709
+ layout.command("status").description("Display current active layout focus and available project layouts").option("--project-id <id>", "Target project UUID").action(layoutStatusCommand);
4047
4710
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refira-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Refira CLI tool and AI Agent Harness for deterministic prototype generation",
5
5
  "type": "module",
6
6
  "bin": {