refira-cli 0.1.1 → 0.1.3

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 (3) hide show
  1. package/README.md +8 -5
  2. package/dist/index.js +126 -31
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -102,17 +102,19 @@ Options:
102
102
 
103
103
  ---
104
104
 
105
- ### 5. Live Preview & Harness Validation
105
+ ### 5. Live Preview, Push & Harness Validation
106
106
 
107
- #### `refira preview`
107
+ #### `refira push` (or `refira preview`)
108
108
  Inspects an HTML markup file using the Refira Agent Harness. If violations are detected, execution halts with Exit Code 1 and actionable remediation instructions. If valid, the markup is streamed directly to the Refira Canvas.
109
109
 
110
110
  ```bash
111
+ refira push checkout.html --page checkout
112
+ # Alternatively:
111
113
  refira preview checkout.html --page checkout
112
114
  ```
113
115
 
114
116
  Arguments:
115
- - `<file>`: Path to the HTML file to inspect and preview.
117
+ - `<file>`: Path to the HTML file to inspect and push/preview.
116
118
 
117
119
  Options:
118
120
  - `--page <slug>`: Target page slug.
@@ -136,15 +138,16 @@ refira skill install --global
136
138
 
137
139
  ## Agent Harness Invariants
138
140
 
139
- All markup submitted via `refira preview` must conform to these strict architectural rules:
141
+ All markup submitted via `refira push` / `refira preview` must conform to these strict architectural rules:
140
142
 
141
143
  | Rule | Status | Description |
142
144
  |---|---|---|
143
145
  | **Standalone HTML5 + Tailwind** | Required | Markup must be valid native HTML5 with Tailwind CSS utility classes. |
144
146
  | **No UI Frameworks** | Prohibited | React, Vue, Svelte, Angular, Solid, and JSX syntax (`className=`, `onClick={...}`, `<Component />`) are rejected. |
145
- | **No External / Inter-Page Navigation** | Prohibited | Prototypes run inside sandboxed iframes. Anchors (`<a href="...">`) pointing to external URLs or relative pages (e.g. `/checkout`) are rejected. Only in-page anchors (`href="#section"`) or placeholders (`href="#"`) are allowed. |
147
+ | **No External / Inter-Page Navigation** | Prohibited | Prototypes run inside sandboxed iframes. Anchors (`<a href="...">`) pointing to external URLs, relative pages (e.g. `/checkout`), empty `href=""`, or `javascript:` navigation are rejected. Only in-page anchors (`href="#section"`) or placeholders (`href="#"`) are allowed. Target attributes (`target="_blank"`, `target="_top"`) and `<form action="...">` are prohibited. For interactive triggers, use `<button type="button">`. |
146
148
  | **No Raw Emojis in Markup** | Prohibited | Raw unicode emojis (e.g. rocket, fire, sparkles) in HTML text nodes or attributes are rejected to maintain professional aesthetic quality. Use Lucide Icons or Heroicons SVG elements instead. |
147
149
  | **Graphics & Animation Libraries** | Permitted | Standalone CDN scripts such as Three.js, GSAP, Spline Viewer, and Lucide Icons are explicitly allowed in document `<head>` or `<script>`. |
150
+ | **Mandatory Push Execution** | Required | AI agents are strictly forbidden from stopping after writing a local file. The task is only complete when `refira push` succeeds with exit code 0. |
148
151
 
149
152
  ---
150
153
 
package/dist/index.js CHANGED
@@ -1892,6 +1892,23 @@ var {
1892
1892
  } = exports_commander;
1893
1893
 
1894
1894
  // src/api-client.ts
1895
+ function extractFontFamily(typography, fallback = "Inter") {
1896
+ if (!typography)
1897
+ return fallback;
1898
+ if (typeof typography.font_family === "string" && typography.font_family.trim().length > 0) {
1899
+ return typography.font_family;
1900
+ }
1901
+ if (typography.primary && typeof typography.primary.font_family === "string" && typography.primary.font_family.trim().length > 0) {
1902
+ return typography.primary.font_family;
1903
+ }
1904
+ for (const item of Object.values(typography)) {
1905
+ if (item && typeof item === "object" && typeof item.font_family === "string") {
1906
+ return item.font_family;
1907
+ }
1908
+ }
1909
+ return fallback;
1910
+ }
1911
+
1895
1912
  class CliApiClient {
1896
1913
  apiUrl;
1897
1914
  apiKey;
@@ -1947,6 +1964,19 @@ class CliApiClient {
1947
1964
  const body = await res.json();
1948
1965
  return body.data;
1949
1966
  }
1967
+ async startGenerating(projectId, pageIdentifier) {
1968
+ const url = `${this.apiUrl}/api/cli/projects/${projectId}/pages/${pageIdentifier}/start-generating`;
1969
+ const res = await fetch(url, {
1970
+ method: "POST",
1971
+ headers: this.getHeaders()
1972
+ });
1973
+ if (!res.ok) {
1974
+ const err = await res.text();
1975
+ throw new Error(`Failed to signal generation start (${res.status}): ${err}`);
1976
+ }
1977
+ const body = await res.json();
1978
+ return body.data;
1979
+ }
1950
1980
  }
1951
1981
 
1952
1982
  // src/config.ts
@@ -2047,18 +2077,22 @@ async function contextCommand(opts) {
2047
2077
  const client = new CliApiClient(config.apiUrl, config.apiKey);
2048
2078
  try {
2049
2079
  const data = await client.getProjectContext(projectId);
2080
+ const projectName = data.context?.project_name ?? data.context?.project?.name ?? "Refira Project";
2081
+ const resolvedProjectId = data.context?.project_id ?? data.context?.project?.id ?? projectId;
2050
2082
  console.log(`
2051
2083
  ======================================================================`);
2052
- console.log(`\uD83D\uDCE6 REFIRA DESIGN CONTEXT: ${data.context.project.name} (${data.context.project.id})`);
2084
+ console.log(`\uD83D\uDCE6 REFIRA DESIGN CONTEXT: ${projectName} (${resolvedProjectId})`);
2053
2085
  console.log("======================================================================");
2054
- const font = data.context.tokens?.typography?.font_family || data.context.project.fontFamily || "Inter";
2086
+ const font = extractFontFamily(data.context?.tokens?.typography, data.context?.project?.fontFamily ?? "Inter");
2055
2087
  console.log(`
2056
2088
  \uD83D\uDD24 Primary Typography: Google Fonts "${font}"`);
2057
- if (data.context.tokens?.color) {
2089
+ const colorTokens = data.context?.tokens?.colors ?? data.context?.tokens?.color;
2090
+ if (colorTokens) {
2058
2091
  console.log(`
2059
2092
  \uD83C\uDFA8 Design Color Tokens:`);
2060
- for (const [tokenName, tokenVal] of Object.entries(data.context.tokens.color)) {
2061
- console.log(` --${tokenName}: ${tokenVal}`);
2093
+ for (const [tokenName, tokenVal] of Object.entries(colorTokens)) {
2094
+ const displayVal = typeof tokenVal === "string" ? tokenVal : tokenVal?.hex ?? tokenVal?.oklch ?? JSON.stringify(tokenVal);
2095
+ console.log(` --${tokenName}: ${displayVal}`);
2062
2096
  }
2063
2097
  }
2064
2098
  console.log(`
@@ -2098,7 +2132,7 @@ function generateAgentsGuide(opts) {
2098
2132
  1. **Output Format:** Standalone HTML5 + Tailwind CSS CDN only.
2099
2133
  2. **Typography:** Load and use Google Fonts "${opts.fontFamily}".
2100
2134
  3. **No UI Frameworks:** Prohibited from using React, Vue, Svelte, Angular, Solid, or JSX syntax (\`className=\`, \`onClick={...}\`, \`<Component />\`).
2101
- 4. **No Inter-Page Navigation:** Prohibited from using \`<a href="/path">\` or external links. Use in-page section hashes (e.g., \`href="#pricing"\`) or \`href="#"\`.
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">\`.
2102
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).
2103
2137
  6. **Permitted Graphics:** Three.js, GSAP, Spline Viewer, and Lucide Icons via CDN are explicitly allowed.
2104
2138
 
@@ -2106,18 +2140,26 @@ function generateAgentsGuide(opts) {
2106
2140
 
2107
2141
  ## 2. Refira CLI Command Surface
2108
2142
  - \`refira context\`: Inspect project design tokens, colors, typography, and existing pages.
2109
- - \`refira scaffold --page <slug>\`: Create a clean HTML5 starter template with tokens pre-configured.
2110
- - \`refira preview <file.html> --page <slug>\`: Run the deterministic harness check and stream updates to Refira Canvas.
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.
2111
2145
  - \`refira skill install [-g]\`: Re-install the Refira design craftsmanship skill.
2112
2146
 
2113
2147
  ---
2114
2148
 
2115
2149
  ## 3. Mandatory Execution Workflow
2116
2150
  1. Run \`refira context\` to understand the project theme and existing pages.
2117
- 2. Run \`refira scaffold --page <page-name>\` to generate the file.
2151
+ 2. Run \`refira scaffold --page <page-name>\` to register the page and activate live canvas pulse.
2118
2152
  3. Write your UI layout within the specified body slot using Tailwind utility classes.
2119
- 4. Execute \`refira preview <page-name>.html --page <page-name>\`.
2153
+ 4. Execute \`refira push <page-name>.html --page <page-name>\`.
2120
2154
  5. If the CLI exits with code 1, address every listed violation and retry until exit code 0.
2155
+
2156
+ ---
2157
+
2158
+ ## 4. Mandatory Delivery Invariant (Push to Canvas)
2159
+ - **Local HTML files are NOT the final deliverable.** Refira Canvas is the live source of truth.
2160
+ - **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.
2162
+ - **Success Criteria:** A task is ONLY complete when \`refira push\` succeeds with exit code 0 and streams to Refira Canvas.
2121
2163
  `;
2122
2164
  }
2123
2165
 
@@ -2177,9 +2219,10 @@ When presenting features, data, or product capabilities:
2177
2219
  ## 5. Prototype Workflow in Refira
2178
2220
 
2179
2221
  1. Check current tokens: \`refira context\`
2180
- 2. Generate base page: \`refira scaffold --page <slug>\`
2222
+ 2. Generate base page & trigger canvas pulse: \`refira scaffold --page <slug>\`
2181
2223
  3. Fill layout using Tailwind classes applying the aesthetic principles above.
2182
- 4. Preview on canvas: \`refira preview <slug>.html --page <slug>\`
2224
+ 4. Stream to Refira Canvas: \`refira push <slug>.html --page <slug>\`
2225
+ 5. Mandatory: Never stop after writing local files. Always execute \`refira push\` to complete the task.
2183
2226
  `;
2184
2227
  }
2185
2228
 
@@ -2201,8 +2244,8 @@ async function initCommand(opts) {
2201
2244
  const client = new CliApiClient(apiUrl, apiKey);
2202
2245
  try {
2203
2246
  const data = await client.getProjectContext(projectId);
2204
- const projectName = data.context.project.name || "Refira Project";
2205
- const fontFamily = data.context.tokens?.typography?.font_family || data.context.project.fontFamily || "Inter";
2247
+ const projectName = data.context?.project_name ?? data.context?.project?.name ?? "Refira Project";
2248
+ const fontFamily = extractFontFamily(data.context?.tokens?.typography, data.context?.project?.fontFamily ?? "Inter");
2206
2249
  const agentsGuide = generateAgentsGuide({
2207
2250
  projectName,
2208
2251
  projectId,
@@ -3612,12 +3655,14 @@ function checkTagName(name, violations) {
3612
3655
  }
3613
3656
  function checkAttributes(name, attribs, violations) {
3614
3657
  for (const [attrKey, attrVal] of Object.entries(attribs)) {
3615
- if (attrKey === "className") {
3658
+ const lowerAttr = attrKey.toLowerCase();
3659
+ const lowerTag = name.toLowerCase();
3660
+ if (lowerAttr === "classname") {
3616
3661
  violations.push({
3617
3662
  type: "JSX_ATTRIBUTE",
3618
- target: "className",
3619
- message: `Prohibited JSX attribute 'className' detected on <${name}>.`,
3620
- remediation: `Change 'className' to the standard HTML 'class' attribute.`
3663
+ target: attrKey,
3664
+ message: `Prohibited JSX attribute '${attrKey}' detected on <${name}>.`,
3665
+ remediation: `Change '${attrKey}' to the standard HTML 'class' attribute.`
3621
3666
  });
3622
3667
  }
3623
3668
  if (/^on[A-Z]/.test(attrKey) || attrKey.startsWith("@") || attrKey.startsWith("v-") || attrKey.startsWith("*")) {
@@ -3628,15 +3673,52 @@ function checkAttributes(name, attribs, violations) {
3628
3673
  remediation: "Remove framework event bindings. Use vanilla HTML and minimal vanilla JavaScript if needed."
3629
3674
  });
3630
3675
  }
3631
- if (attrKey === "href" && name.toLowerCase() === "a") {
3676
+ if (lowerAttr.startsWith("on") && (attrVal.includes("location") || attrVal.includes("window.open") || attrVal.includes("history."))) {
3677
+ violations.push({
3678
+ type: "NAVIGATION_PROHIBITED",
3679
+ target: `${attrKey}="${attrVal}"`,
3680
+ message: `Inline navigation script '${attrVal}' detected on <${name}>.`,
3681
+ remediation: "Remove inline navigation scripts. Prototypes run in a sandboxed canvas and cannot navigate across pages."
3682
+ });
3683
+ }
3684
+ if (lowerTag === "a") {
3685
+ if (lowerAttr === "href") {
3686
+ const trimmed = attrVal.trim();
3687
+ const isSafeHash = trimmed.startsWith("#");
3688
+ const isSafeVoid = trimmed === "javascript:void(0)" || trimmed === "javascript:;";
3689
+ if (!isSafeHash && !isSafeVoid) {
3690
+ violations.push({
3691
+ type: "NAVIGATION_PROHIBITED",
3692
+ target: `${attrKey}="${attrVal}"`,
3693
+ message: `Cross-page or external navigation '<a ${attrKey}="${attrVal}">' is strictly prohibited.`,
3694
+ remediation: 'Refira prototypes run in a sandboxed iframe. Anchor links must point to in-page section hashes (e.g. href="#features") or placeholder (href="#"). If you need action triggers, use <button type="button">.'
3695
+ });
3696
+ } else if (trimmed.includes("location") || trimmed.includes("window.open")) {
3697
+ violations.push({
3698
+ type: "NAVIGATION_PROHIBITED",
3699
+ target: `${attrKey}="${attrVal}"`,
3700
+ message: `Navigation statement detected inside href: '${attrVal}'.`,
3701
+ remediation: "Remove navigation statements from href."
3702
+ });
3703
+ }
3704
+ }
3705
+ if (lowerAttr === "target" && attrVal.trim().length > 0) {
3706
+ violations.push({
3707
+ type: "NAVIGATION_PROHIBITED",
3708
+ target: `${attrKey}="${attrVal}"`,
3709
+ message: `Target attribute '${attrKey}="${attrVal}"' on <a> is prohibited.`,
3710
+ remediation: "Remove the target attribute. Prototypes must remain within the Refira Canvas viewport."
3711
+ });
3712
+ }
3713
+ }
3714
+ if (lowerTag === "form" && lowerAttr === "action") {
3632
3715
  const trimmed = attrVal.trim();
3633
- const isInternal = trimmed.startsWith("#") || trimmed.startsWith("javascript:");
3634
- if (!isInternal && trimmed.length > 0) {
3716
+ if (trimmed.length > 0 && !trimmed.startsWith("#") && !trimmed.startsWith("javascript:void")) {
3635
3717
  violations.push({
3636
3718
  type: "NAVIGATION_PROHIBITED",
3637
- target: `href="${attrVal}"`,
3638
- message: `Cross-page or external navigation '<a href="${attrVal}">' is prohibited.`,
3639
- remediation: `Refira prototypes run in a sandboxed iframe. Replace with in-page anchor (e.g. href="#features") or placeholder (href="#").`
3719
+ target: `${attrKey}="${attrVal}"`,
3720
+ message: `Form action '${attrVal}' is prohibited in standalone prototypes.`,
3721
+ remediation: 'Remove or replace form action with action="#" or handle submission client-side without page reload.'
3640
3722
  });
3641
3723
  }
3642
3724
  }
@@ -3898,9 +3980,20 @@ async function scaffoldCommand(opts) {
3898
3980
  try {
3899
3981
  const client = new CliApiClient(config.apiUrl, config.apiKey);
3900
3982
  const data = await client.getProjectContext(config.projectId);
3901
- projectName = data.context.project.name || projectName;
3902
- fontFamily = data.context.tokens?.typography?.font_family || data.context.project.fontFamily || fontFamily;
3903
- colorTokens = data.context.tokens?.color;
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 {}
3904
3997
  } catch {}
3905
3998
  }
3906
3999
  const pageName = pageSlug.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
@@ -3916,8 +4009,10 @@ async function scaffoldCommand(opts) {
3916
4009
  console.log(` Font: ${fontFamily}`);
3917
4010
  console.log(" Slot: <!-- [REFIRA CANVAS SLOT: START] -->");
3918
4011
  console.log(`
3919
- Next step: Edit '${targetPath}', then execute:`);
3920
- console.log(` refira preview ${targetPath} --page ${pageSlug}`);
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!");
3921
4016
  }
3922
4017
 
3923
4018
  // src/commands/skill.ts
@@ -3939,14 +4034,14 @@ AI coding agents will automatically recognize this skill for high-aesthetic prot
3939
4034
 
3940
4035
  // src/index.ts
3941
4036
  var program2 = new Command2;
3942
- program2.name("refira").description("Refira CLI tool and AI Agent Harness for deterministic prototype generation").version("0.1.0");
4037
+ program2.name("refira").description("Refira CLI tool and AI Agent Harness for deterministic prototype generation").version("0.1.3");
3943
4038
  var auth = program2.command("auth").description("Manage Refira API authentication and sessions");
3944
4039
  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);
3945
4040
  auth.command("status").description("Verify and display current Refira authentication session").action(statusCommand);
3946
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);
3947
4042
  program2.command("context").description("Inspect design tokens, color palette, typography, and page roster").option("--project-id <id>", "Project UUID").action(contextCommand);
3948
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);
3949
- program2.command("preview").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);
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);
3950
4045
  var skill = program2.command("skill").description("Manage Refira Agent Skills");
3951
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);
3952
4047
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refira-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Refira CLI tool and AI Agent Harness for deterministic prototype generation",
5
5
  "type": "module",
6
6
  "bin": {