@cfbender/cesium 0.5.0 → 0.5.2
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/CHANGELOG.md +86 -1
- package/README.md +8 -8
- package/package.json +19 -18
- package/src/cli/commands/serve.ts +16 -4
- package/src/index.ts +4 -1
- package/src/prompt/field-reference.ts +2 -2
- package/src/prompt/system-fragment.md +46 -16
- package/src/render/blocks/catalog.ts +2 -0
- package/src/render/blocks/diff/myers.ts +221 -0
- package/src/render/blocks/diff/parse-unified.ts +101 -0
- package/src/render/blocks/highlight.ts +185 -0
- package/src/render/blocks/markdown.ts +28 -7
- package/src/render/blocks/render.ts +16 -5
- package/src/render/blocks/renderers/code.ts +5 -5
- package/src/render/blocks/renderers/compare-table.ts +3 -4
- package/src/render/blocks/renderers/diagram.ts +2 -5
- package/src/render/blocks/renderers/diff.ts +378 -0
- package/src/render/blocks/renderers/prose.ts +1 -2
- package/src/render/blocks/renderers/section.ts +4 -2
- package/src/render/blocks/renderers/timeline.ts +2 -1
- package/src/render/blocks/themes/claret-dark.ts +201 -0
- package/src/render/blocks/themes/claret-light.ts +222 -0
- package/src/render/blocks/types.ts +13 -1
- package/src/render/blocks/validate-block.ts +19 -9
- package/src/render/theme.ts +131 -0
- package/src/render/validate.ts +53 -9
- package/src/server/lifecycle.ts +188 -3
- package/src/storage/index-gen.ts +2 -3
- package/src/tools/ask.ts +2 -2
- package/src/tools/publish.ts +6 -6
- package/src/tools/styleguide.ts +25 -20
package/src/server/lifecycle.ts
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { readFileSync, unlinkSync } from "node:fs";
|
|
5
|
-
import { unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdir, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
6
9
|
import { startServer, type ServerHandle } from "./http.ts";
|
|
7
10
|
import { acquireLock } from "../storage/lock.ts";
|
|
8
11
|
import { createApiHandler } from "./api.ts";
|
|
@@ -202,8 +205,19 @@ export async function stopRunning(stateDir: string): Promise<void> {
|
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
|
|
206
|
-
|
|
208
|
+
// ─── In-process (foreground) server start ────────────────────────────────────
|
|
209
|
+
// Used by `cesium serve` CLI. Runs Bun.serve() in-process; killing the process
|
|
210
|
+
// IS stopping the server, which is the user's intent for a foreground invocation.
|
|
211
|
+
|
|
212
|
+
export async function runServerForeground(cfg: LifecycleConfig): Promise<RunningInfo> {
|
|
213
|
+
const {
|
|
214
|
+
stateDir,
|
|
215
|
+
port,
|
|
216
|
+
portMax,
|
|
217
|
+
idleTimeoutMs,
|
|
218
|
+
hostname = "127.0.0.1",
|
|
219
|
+
theme = defaultTheme(),
|
|
220
|
+
} = cfg;
|
|
207
221
|
const pidFilePath = join(stateDir, ".server.pid");
|
|
208
222
|
const lockPath = join(stateDir, ".server-start.lock");
|
|
209
223
|
|
|
@@ -310,6 +324,177 @@ export async function ensureRunning(cfg: LifecycleConfig): Promise<RunningInfo>
|
|
|
310
324
|
}
|
|
311
325
|
}
|
|
312
326
|
|
|
327
|
+
// ─── Detached (lazy) server start ────────────────────────────────────────────
|
|
328
|
+
// Used by plugin callers (publish, ask). Spawns `cesium serve` as a detached
|
|
329
|
+
// subprocess so the subprocess PID is what ends up in the PID file. Sending a
|
|
330
|
+
// signal to that PID kills only the server child, never the plugin host.
|
|
331
|
+
|
|
332
|
+
// Locate CLI entry relative to this file: src/server/lifecycle.ts → src/cli/index.ts
|
|
333
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
334
|
+
const CLI_ENTRY = join(HERE, "..", "cli", "index.ts");
|
|
335
|
+
|
|
336
|
+
// Readiness poll backoff schedule (ms between attempts)
|
|
337
|
+
const POLL_SCHEDULE = [50, 100, 200, 500, 1000, 1000, 1000, 1000, 1000, 1000];
|
|
338
|
+
|
|
339
|
+
async function sleep(ms: number): Promise<void> {
|
|
340
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function httpProbe(url: string): Promise<boolean> {
|
|
344
|
+
try {
|
|
345
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(1000) });
|
|
346
|
+
// Any HTTP response (even 404) means the server is up
|
|
347
|
+
return res.status < 600;
|
|
348
|
+
} catch {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export async function ensureServerRunning(cfg: LifecycleConfig): Promise<RunningInfo> {
|
|
354
|
+
const { stateDir, port, idleTimeoutMs } = cfg;
|
|
355
|
+
const pidFilePath = join(stateDir, ".server.pid");
|
|
356
|
+
// Use a separate lock from runServerForeground's ".server-start.lock" to avoid
|
|
357
|
+
// deadlock: the child process runs runServerForeground which acquires that lock,
|
|
358
|
+
// so the parent must not hold it while waiting for the child.
|
|
359
|
+
const spawnLockPath = join(stateDir, ".server-spawn.lock");
|
|
360
|
+
|
|
361
|
+
// Fast path: read existing PID file and probe liveness
|
|
362
|
+
const existing = readPidFile(pidFilePath);
|
|
363
|
+
if (existing !== null && isAlive(existing.pid)) {
|
|
364
|
+
const probeUrl = `http://${existing.hostname}:${existing.port}/`;
|
|
365
|
+
const alive = await httpProbe(probeUrl);
|
|
366
|
+
if (alive) {
|
|
367
|
+
return {
|
|
368
|
+
port: existing.port,
|
|
369
|
+
url: `http://${existing.hostname}:${existing.port}`,
|
|
370
|
+
pid: existing.pid,
|
|
371
|
+
startedAt: existing.startedAt,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// Process alive but not responding — fall through to spawn fresh
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Ensure state dir exists before trying to acquire lock or write files
|
|
378
|
+
await mkdir(stateDir, { recursive: true });
|
|
379
|
+
|
|
380
|
+
// Use a spawn-only lock to prevent concurrent spawns. Release it immediately
|
|
381
|
+
// after spawning so the child can acquire its own (.server-start.lock) lock.
|
|
382
|
+
const spawnLock = await acquireLock({
|
|
383
|
+
lockPath: spawnLockPath,
|
|
384
|
+
timeoutMs: 15_000,
|
|
385
|
+
staleMs: 30_000,
|
|
386
|
+
});
|
|
387
|
+
try {
|
|
388
|
+
// Re-check after acquiring lock
|
|
389
|
+
const existingAfterLock = readPidFile(pidFilePath);
|
|
390
|
+
if (existingAfterLock !== null && isAlive(existingAfterLock.pid)) {
|
|
391
|
+
const probeUrl = `http://${existingAfterLock.hostname}:${existingAfterLock.port}/`;
|
|
392
|
+
const alive = await httpProbe(probeUrl);
|
|
393
|
+
if (alive) {
|
|
394
|
+
return {
|
|
395
|
+
port: existingAfterLock.port,
|
|
396
|
+
url: `http://${existingAfterLock.hostname}:${existingAfterLock.port}`,
|
|
397
|
+
pid: existingAfterLock.pid,
|
|
398
|
+
startedAt: existingAfterLock.startedAt,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Clean up stale PID file if present
|
|
404
|
+
try {
|
|
405
|
+
await unlink(pidFilePath);
|
|
406
|
+
} catch {
|
|
407
|
+
// ENOENT is fine
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Build spawn args — rely on env vars for config; CLI flags as defense in depth.
|
|
411
|
+
// portMax is not a serve flag; the child will scan ports starting from `port`.
|
|
412
|
+
// Port 0 means "auto-assign" — the CLI flag rejects 0, so rely on CESIUM_PORT=0 env var.
|
|
413
|
+
const spawnArgs: string[] = ["run", CLI_ENTRY, "serve", "--state-dir", stateDir];
|
|
414
|
+
if (port > 0) {
|
|
415
|
+
spawnArgs.push("--port", String(port));
|
|
416
|
+
}
|
|
417
|
+
// Pass idle timeout so the detached child self-terminates on inactivity.
|
|
418
|
+
// Serve command defaults to 0 (never) for foreground use; we override for daemon mode.
|
|
419
|
+
if (idleTimeoutMs > 0) {
|
|
420
|
+
spawnArgs.push("--idle-timeout", String(idleTimeoutMs));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const child = spawn("bun", spawnArgs, {
|
|
424
|
+
detached: true,
|
|
425
|
+
stdio: "ignore",
|
|
426
|
+
env: {
|
|
427
|
+
...process.env,
|
|
428
|
+
CESIUM_STATE_DIR: stateDir,
|
|
429
|
+
CESIUM_PORT: String(port),
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Unref so the parent can exit without waiting for the child
|
|
434
|
+
child.unref();
|
|
435
|
+
|
|
436
|
+
if (child.pid === undefined) {
|
|
437
|
+
throw new Error("cesium: failed to spawn server subprocess (no PID assigned)");
|
|
438
|
+
}
|
|
439
|
+
} finally {
|
|
440
|
+
// Release spawn lock immediately — the child needs to acquire its own lock
|
|
441
|
+
// (.server-start.lock via runServerForeground). Holding the spawn lock any
|
|
442
|
+
// longer would deadlock the child.
|
|
443
|
+
await spawnLock.release();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Wait for the child to write its PID file and respond to HTTP.
|
|
447
|
+
// This polling happens OUTSIDE the spawn lock so the child can run freely.
|
|
448
|
+
const deadline = Date.now() + 10_000;
|
|
449
|
+
let lastError = "timeout";
|
|
450
|
+
let scheduleIdx = 0;
|
|
451
|
+
|
|
452
|
+
while (Date.now() < deadline) {
|
|
453
|
+
const waitMs = POLL_SCHEDULE[scheduleIdx] ?? 1000;
|
|
454
|
+
scheduleIdx = Math.min(scheduleIdx + 1, POLL_SCHEDULE.length - 1);
|
|
455
|
+
await sleep(waitMs);
|
|
456
|
+
|
|
457
|
+
const pidContent = readPidFile(pidFilePath);
|
|
458
|
+
if (pidContent !== null && isAlive(pidContent.pid)) {
|
|
459
|
+
const probeUrl = `http://${pidContent.hostname}:${pidContent.port}/`;
|
|
460
|
+
const alive = await httpProbe(probeUrl);
|
|
461
|
+
if (alive) {
|
|
462
|
+
return {
|
|
463
|
+
port: pidContent.port,
|
|
464
|
+
url: `http://${pidContent.hostname}:${pidContent.port}`,
|
|
465
|
+
pid: pidContent.pid,
|
|
466
|
+
startedAt: pidContent.startedAt,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
lastError = `pid ${pidContent.pid} alive but not yet responding on port ${pidContent.port}`;
|
|
470
|
+
} else if (pidContent !== null) {
|
|
471
|
+
lastError = `pid ${pidContent.pid} in PID file is not alive`;
|
|
472
|
+
} else {
|
|
473
|
+
lastError = "PID file not yet written";
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Timeout — try to clean up the spawned process
|
|
478
|
+
const staleContent = readPidFile(pidFilePath);
|
|
479
|
+
if (staleContent !== null && isAlive(staleContent.pid)) {
|
|
480
|
+
try {
|
|
481
|
+
process.kill(staleContent.pid, "SIGTERM");
|
|
482
|
+
} catch {
|
|
483
|
+
// best-effort
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
throw new Error(
|
|
488
|
+
`cesium: timed out waiting for server to start in ${stateDir} (last: ${lastError})`,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ─── Backward-compat alias ────────────────────────────────────────────────────
|
|
493
|
+
// Internal callers have been updated to use runServerForeground or ensureServerRunning.
|
|
494
|
+
// Keep ensureRunning exported for any external consumers that haven't migrated.
|
|
495
|
+
|
|
496
|
+
export { runServerForeground as ensureRunning };
|
|
497
|
+
|
|
313
498
|
// ─── Test reset hook ──────────────────────────────────────────────────────────
|
|
314
499
|
// This function is intended for test use only. It clears module-level singleton
|
|
315
500
|
// state, stops any running server, and removes signal/exit listeners.
|
package/src/storage/index-gen.ts
CHANGED
|
@@ -236,9 +236,8 @@ function indexJs(): string {
|
|
|
236
236
|
function renderEntryCard(entry: IndexEntry): string {
|
|
237
237
|
const isSuperseded = entry.supersededBy !== null ? "1" : "0";
|
|
238
238
|
const kindPill = `<span class="pill">${esc(entry.kind)}</span>`;
|
|
239
|
-
const inputModeBadge =
|
|
240
|
-
? ` <span class="tag">${esc(entry.inputMode)}</span>`
|
|
241
|
-
: "";
|
|
239
|
+
const inputModeBadge =
|
|
240
|
+
entry.inputMode !== undefined ? ` <span class="tag">${esc(entry.inputMode)}</span>` : "";
|
|
242
241
|
const dateStr = `<span class="card-date">${esc(formatDate(entry.createdAt))}</span>`;
|
|
243
242
|
const supersededBadge =
|
|
244
243
|
entry.supersedes !== null
|
package/src/tools/ask.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { withLock } from "../storage/lock.ts";
|
|
|
20
20
|
import { renderProjectIndex, renderGlobalIndex } from "../storage/index-gen.ts";
|
|
21
21
|
import { buildProjectSummaries } from "../storage/project-summaries.ts";
|
|
22
22
|
import {
|
|
23
|
-
|
|
23
|
+
ensureServerRunning as defaultEnsureServerRunning,
|
|
24
24
|
type RunningInfo,
|
|
25
25
|
type LifecycleConfig,
|
|
26
26
|
} from "../server/lifecycle.ts";
|
|
@@ -58,7 +58,7 @@ export function createAskTool(
|
|
|
58
58
|
const resolveConfig = overrides?.loadConfig ?? loadConfig;
|
|
59
59
|
const now = overrides?.now ?? (() => new Date());
|
|
60
60
|
const genId = overrides?.nanoid ?? defaultNanoid;
|
|
61
|
-
const runEnsureRunning = overrides?.ensureRunning ??
|
|
61
|
+
const runEnsureRunning = overrides?.ensureRunning ?? defaultEnsureServerRunning;
|
|
62
62
|
|
|
63
63
|
return tool({
|
|
64
64
|
description: TOOL_DESCRIPTION,
|
package/src/tools/publish.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { extractTextContent } from "../render/extract.ts";
|
|
|
11
11
|
import { themeFromPreset, mergeTheme } from "../render/theme.ts";
|
|
12
12
|
import { validatePublishInput, htmlBodyWarnings, PUBLISH_KINDS } from "../render/validate.ts";
|
|
13
13
|
import { renderBlocks } from "../render/blocks/render.ts";
|
|
14
|
+
import { resolveHighlightTheme } from "../render/blocks/highlight.ts";
|
|
14
15
|
import { wrapDocument, type ArtifactMeta } from "../render/wrap.ts";
|
|
15
16
|
import { deriveProjectIdentity, artifactFilename, pathsFor } from "../storage/paths.ts";
|
|
16
17
|
import { atomicWrite, patchEmbeddedMetadata } from "../storage/write.ts";
|
|
@@ -27,7 +28,7 @@ import { withLock } from "../storage/lock.ts";
|
|
|
27
28
|
import { renderProjectIndex, renderGlobalIndex } from "../storage/index-gen.ts";
|
|
28
29
|
import { buildProjectSummaries } from "../storage/project-summaries.ts";
|
|
29
30
|
import {
|
|
30
|
-
|
|
31
|
+
ensureServerRunning as defaultEnsureServerRunning,
|
|
31
32
|
type RunningInfo,
|
|
32
33
|
type LifecycleConfig,
|
|
33
34
|
} from "../server/lifecycle.ts";
|
|
@@ -106,7 +107,7 @@ export function createPublishTool(
|
|
|
106
107
|
const resolveConfig = overrides?.loadConfig ?? loadConfig;
|
|
107
108
|
const now = overrides?.now ?? (() => new Date());
|
|
108
109
|
const genId = overrides?.nanoid ?? defaultNanoid;
|
|
109
|
-
const runEnsureRunning = overrides?.ensureRunning ??
|
|
110
|
+
const runEnsureRunning = overrides?.ensureRunning ?? defaultEnsureServerRunning;
|
|
110
111
|
|
|
111
112
|
return tool({
|
|
112
113
|
description: TOOL_DESCRIPTION,
|
|
@@ -116,9 +117,7 @@ export function createPublishTool(
|
|
|
116
117
|
html: tool.schema
|
|
117
118
|
.string()
|
|
118
119
|
.optional()
|
|
119
|
-
.describe(
|
|
120
|
-
"Body HTML — escape valve / legacy mode. Provide exactly one of html or blocks.",
|
|
121
|
-
),
|
|
120
|
+
.describe("Body HTML — escape valve / legacy mode. Provide exactly one of html or blocks."),
|
|
122
121
|
blocks: tool.schema
|
|
123
122
|
.array(tool.schema.any())
|
|
124
123
|
.optional()
|
|
@@ -193,7 +192,8 @@ export function createPublishTool(
|
|
|
193
192
|
|
|
194
193
|
if (input.blocks !== undefined) {
|
|
195
194
|
// Blocks path: render structured blocks → trusted HTML
|
|
196
|
-
|
|
195
|
+
const highlightTheme = resolveHighlightTheme(config.themePreset);
|
|
196
|
+
bodyHtml = await renderBlocks(input.blocks, { highlightTheme });
|
|
197
197
|
} else {
|
|
198
198
|
// HTML path: scrub agent-supplied HTML
|
|
199
199
|
const scrubbed = scrub(input.html);
|
package/src/tools/styleguide.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { RenderCtx, SectionCounter } from "../render/blocks/render.ts";
|
|
|
8
8
|
|
|
9
9
|
function makeCtx(): RenderCtx {
|
|
10
10
|
const counter: SectionCounter = { value: 1 };
|
|
11
|
-
return { sectionCounter: counter, depth: 0, path: "blocks[0]" };
|
|
11
|
+
return { sectionCounter: counter, depth: 0, path: "blocks[0]", highlightTheme: "claret-dark" };
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/** Escape a string for safe insertion inside a markdown fenced code block. */
|
|
@@ -18,7 +18,7 @@ function escapeForCodeFence(s: string): string {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** Generate the full markdown reference from the catalog. Deterministic — same catalog → same output. */
|
|
21
|
-
export function generateStyleguideMarkdown(): string {
|
|
21
|
+
export async function generateStyleguideMarkdown(): Promise<string> {
|
|
22
22
|
const lines: string[] = [];
|
|
23
23
|
|
|
24
24
|
lines.push("# Cesium publishing reference");
|
|
@@ -40,8 +40,26 @@ export function generateStyleguideMarkdown(): string {
|
|
|
40
40
|
lines.push("## Block reference");
|
|
41
41
|
lines.push("");
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
// Pre-render all examples in parallel (order preserved via index)
|
|
44
|
+
const renderedExamples = await Promise.all(
|
|
45
|
+
blockTypes.map(async (blockType) => {
|
|
46
|
+
const entry = blockCatalog[blockType];
|
|
47
|
+
if (entry.renderedExample !== undefined) {
|
|
48
|
+
return entry.renderedExample;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return await renderBlock(entry.example, makeCtx());
|
|
52
|
+
} catch {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < blockTypes.length; i++) {
|
|
59
|
+
const blockType = blockTypes[i];
|
|
60
|
+
if (blockType === undefined) continue;
|
|
44
61
|
const entry = blockCatalog[blockType];
|
|
62
|
+
const rendered = renderedExamples[i] ?? "";
|
|
45
63
|
|
|
46
64
|
lines.push(`### \`${entry.type}\``);
|
|
47
65
|
lines.push("");
|
|
@@ -62,15 +80,6 @@ export function generateStyleguideMarkdown(): string {
|
|
|
62
80
|
lines.push("```");
|
|
63
81
|
lines.push("");
|
|
64
82
|
|
|
65
|
-
// Rendered HTML
|
|
66
|
-
const rendered = entry.renderedExample ?? (() => {
|
|
67
|
-
try {
|
|
68
|
-
return renderBlock(entry.example, makeCtx());
|
|
69
|
-
} catch {
|
|
70
|
-
return "";
|
|
71
|
-
}
|
|
72
|
-
})();
|
|
73
|
-
|
|
74
83
|
if (rendered !== "") {
|
|
75
84
|
lines.push("Renders to:");
|
|
76
85
|
lines.push("");
|
|
@@ -92,21 +101,17 @@ export function generateStyleguideMarkdown(): string {
|
|
|
92
101
|
"- Inline: `**bold**`, `*italic*`, `` `code` ``, `[text](href)` (relative or anchor only).",
|
|
93
102
|
);
|
|
94
103
|
lines.push(
|
|
95
|
-
|
|
104
|
+
'- HTML safelist: `<kbd>`, `<span class="pill">`, `<span class="tag">`. Anything else is escaped.',
|
|
96
105
|
);
|
|
97
106
|
lines.push("");
|
|
98
107
|
lines.push("## When to reach for raw_html / diagram");
|
|
99
108
|
lines.push("");
|
|
100
|
-
lines.push(
|
|
101
|
-
"- `diagram` — inline SVG visualizations or bespoke composed HTML diagrams.",
|
|
102
|
-
);
|
|
109
|
+
lines.push("- `diagram` — inline SVG visualizations or bespoke composed HTML diagrams.");
|
|
103
110
|
lines.push(
|
|
104
111
|
"- `raw_html` — anything genuinely creative that doesn't fit a known block type." +
|
|
105
112
|
" Include a `purpose` string describing what you're building.",
|
|
106
113
|
);
|
|
107
|
-
lines.push(
|
|
108
|
-
"- Critique flags raw_html overuse (>2 blocks or >30% of body characters).",
|
|
109
|
-
);
|
|
114
|
+
lines.push("- Critique flags raw_html overuse (>2 blocks or >30% of body characters).");
|
|
110
115
|
|
|
111
116
|
return lines.join("\n");
|
|
112
117
|
}
|
|
@@ -117,7 +122,7 @@ export function createStyleguideTool(_ctx: PluginInput): ReturnType<typeof tool>
|
|
|
117
122
|
"Returns the cesium HTML design system reference page (CSS classes with example usage). Call this once at the start of writing a complex artifact to internalize the available components.",
|
|
118
123
|
args: {},
|
|
119
124
|
async execute(_args, _context) {
|
|
120
|
-
return generateStyleguideMarkdown();
|
|
125
|
+
return await generateStyleguideMarkdown();
|
|
121
126
|
},
|
|
122
127
|
});
|
|
123
128
|
}
|