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.
- package/dist/index.js +809 -146
- 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
|
|
1958
|
-
body: JSON.stringify({
|
|
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
|
|
1968
|
-
const url = `${this.apiUrl}/api/cli/projects/${projectId}/pages/${pageIdentifier}/
|
|
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
|
|
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
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
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 :
|
|
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("
|
|
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("
|
|
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(
|
|
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("
|
|
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("
|
|
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(
|
|
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("
|
|
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("
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
2118
|
-
import
|
|
2582
|
+
import fs3 from "node:fs";
|
|
2583
|
+
import path3 from "node:path";
|
|
2119
2584
|
|
|
2120
|
-
// src/templates/
|
|
2121
|
-
|
|
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. **
|
|
2133
|
-
2. **
|
|
2134
|
-
3. **
|
|
2135
|
-
4. **
|
|
2136
|
-
5. **
|
|
2137
|
-
6. **
|
|
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.
|
|
2142
|
-
|
|
2143
|
-
-
|
|
2144
|
-
|
|
2145
|
-
|
|
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.
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
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 <
|
|
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
|
-
|
|
2223
|
-
|
|
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("
|
|
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("
|
|
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
|
|
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
|
-
|
|
2256
|
-
console.log(" Created
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2268
|
-
console.log("
|
|
2269
|
-
console.log("
|
|
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(
|
|
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
|
|
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୕ୖ୪௫ఄ಄ದൡඅ櫬Āoungruent;扢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;怳Ādpuct;戏ortionĀ;aȥl;戝Āci༁༆r;쀀\uD835\uDCAB;䎨ȀUfos༑༖༛༟OT耻\"䀢r;쀀\uD835\uDD14pf;愚cr;쀀\uD835\uDCACBEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾ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ǣጓ\x00y;䐎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Ā;tbrk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯᝳ;䎲;愶een;扬r;쀀\uD835\uDD1Fgcostuvwឍឝឳេ៕៛ƀ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Ā;elƀ;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\x00aĀ;t䀬;䁀ƀ;fl戁îᅠeĀmxent»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ↄⅪ←ٖ↛ǰ↉\x00proø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\uDCC0ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀ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;䅓Ācrir;榿;쀀\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\x00m;櫳;櫽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
|
-
"
|
|
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("
|
|
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
|
-
"
|
|
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(
|
|
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
|
-
|
|
3856
|
-
|
|
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("
|
|
4462
|
+
console.error("Error: Target page is required. Specify <file> or --page <slug>.");
|
|
3859
4463
|
process.exit(1);
|
|
3860
4464
|
}
|
|
3861
|
-
|
|
3862
|
-
|
|
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 =
|
|
3866
|
-
console.log(
|
|
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,
|
|
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("
|
|
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(
|
|
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(
|
|
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
|
|
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("
|
|
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
|
|
3977
|
-
|
|
3978
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
console.log(`
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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("
|
|
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)
|
|
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();
|