paseo-plugin-genui 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/OVERVIEW.md +21 -0
- package/README.md +77 -2
- package/assets/fonts/Excalifont-OFL.txt +109 -0
- package/assets/fonts/Excalifont-Regular-latin.woff2 +0 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +1 -0
- package/assets/screenshot-chat.png +0 -0
- package/assets/screenshot-gallery.png +0 -0
- package/assets/screenshot-playground.png +0 -0
- package/client/components/content.tsx +468 -0
- package/client/components/data.tsx +967 -0
- package/client/components/dev.tsx +998 -0
- package/client/components/diagram.tsx +513 -0
- package/client/components/inputs.tsx +994 -0
- package/client/components/layout.tsx +239 -0
- package/client/components/media.tsx +428 -0
- package/client/components/project.tsx +641 -0
- package/client/components/widgets.tsx +836 -0
- package/client/gallery.tsx +449 -0
- package/client/live-ui.tsx +525 -0
- package/client/loaders.ts +342 -0
- package/client/markdown.tsx +172 -0
- package/client/node-renderer.tsx +149 -0
- package/client/render.tsx +186 -0
- package/client/runtime.ts +103 -0
- package/client/settings.tsx +57 -0
- package/client/web.ts +186 -0
- package/gallery/Acceptance-baseline.ui +1 -0
- package/gallery/Acceptance-checklist.ui +14 -0
- package/gallery/Acceptance-evidence.ui +9 -0
- package/gallery/Acceptance.ui +1 -0
- package/gallery/Badge.ui +3 -0
- package/gallery/Benchmark-context.ui +8 -0
- package/gallery/Benchmark-hyperfine.ui +1 -0
- package/gallery/Benchmark-runs-list.ui +9 -0
- package/gallery/Benchmark-runs.ui +1 -0
- package/gallery/Benchmark-vitest.ui +1 -0
- package/gallery/Benchmark.ui +9 -0
- package/gallery/Board-bb-tasks.ui +1 -0
- package/gallery/Board-gh-issues.ui +1 -0
- package/gallery/Board-gh-prs.ui +1 -0
- package/gallery/Board.ui +12 -0
- package/gallery/Button-destructive.ui +4 -0
- package/gallery/Button.ui +5 -0
- package/gallery/Calendar.ui +15 -0
- package/gallery/CalendarHeatmap-short.ui +11 -0
- package/gallery/CalendarHeatmap.ui +4 -0
- package/gallery/Callout.ui +4 -0
- package/gallery/Card.ui +4 -0
- package/gallery/Carousel.ui +8 -0
- package/gallery/Changes-paths.ui +1 -0
- package/gallery/Changes-uncommitted.ui +1 -0
- package/gallery/Changes.ui +1 -0
- package/gallery/Chart-area.ui +3 -0
- package/gallery/Chart-funnel.ui +3 -0
- package/gallery/Chart-heatmap.ui +11 -0
- package/gallery/Chart-pie.ui +3 -0
- package/gallery/Chart-radar.ui +3 -0
- package/gallery/Chart-scatter.ui +4 -0
- package/gallery/Chart-stacked.ui +5 -0
- package/gallery/Chart.ui +3 -0
- package/gallery/Checklist.ui +6 -0
- package/gallery/Choice.ui +8 -0
- package/gallery/Code.ui +3 -0
- package/gallery/Commits-limit.ui +1 -0
- package/gallery/Commits.ui +1 -0
- package/gallery/Compare-conditions.ui +4 -0
- package/gallery/Compare-three.ui +7 -0
- package/gallery/Compare.ui +4 -0
- package/gallery/Coverage-go.ui +1 -0
- package/gallery/Coverage-lcov.ui +1 -0
- package/gallery/Coverage-python.ui +1 -0
- package/gallery/Coverage.ui +1 -0
- package/gallery/DateInput.ui +5 -0
- package/gallery/Decision-percent.ui +10 -0
- package/gallery/Decision.ui +12 -0
- package/gallery/Dependencies-audit.ui +1 -0
- package/gallery/Dependencies-both.ui +1 -0
- package/gallery/Dependencies-list.ui +7 -0
- package/gallery/Dependencies-pip.ui +1 -0
- package/gallery/Dependencies-pnpm.ui +1 -0
- package/gallery/Dependencies.ui +1 -0
- package/gallery/Details.ui +4 -0
- package/gallery/Diagram-flow.ui +16 -0
- package/gallery/Diagram-sequence.ui +15 -0
- package/gallery/Diagram.ui +16 -0
- package/gallery/Diff.ui +3 -0
- package/gallery/Divider.ui +5 -0
- package/gallery/EditableTable.ui +9 -0
- package/gallery/Entity-list.ui +6 -0
- package/gallery/Entity.ui +8 -0
- package/gallery/Environments.ui +1 -0
- package/gallery/Estimate-points.ui +6 -0
- package/gallery/Estimate.ui +8 -0
- package/gallery/File.ui +4 -0
- package/gallery/Findings-biome.ui +1 -0
- package/gallery/Findings-eslint.ui +1 -0
- package/gallery/Findings-oxlint.ui +1 -0
- package/gallery/Findings-review.ui +1 -0
- package/gallery/Findings-ruff.ui +1 -0
- package/gallery/Findings-sarif.ui +1 -0
- package/gallery/Findings-ship.ui +11 -0
- package/gallery/Findings-tsc.ui +1 -0
- package/gallery/Findings.ui +7 -0
- package/gallery/Flashcards.ui +3 -0
- package/gallery/FollowUps.ui +4 -0
- package/gallery/Form.ui +8 -0
- package/gallery/Gallery-links.ui +8 -0
- package/gallery/Gallery-strip.ui +14 -0
- package/gallery/Gallery.ui +10 -0
- package/gallery/Gauge.ui +7 -0
- package/gallery/Grid.ui +8 -0
- package/gallery/Hotspots-photo.ui +8 -0
- package/gallery/Hotspots.ui +11 -0
- package/gallery/Icon.ui +5 -0
- package/gallery/Image.ui +4 -0
- package/gallery/ImageSearch-aspects.ui +6 -0
- package/gallery/ImageSearch.ui +4 -0
- package/gallery/Input.ui +5 -0
- package/gallery/Json-api.ui +1 -0
- package/gallery/Json.ui +8 -0
- package/gallery/KeyValue.ui +3 -0
- package/gallery/List-dots.ui +8 -0
- package/gallery/List.ui +8 -0
- package/gallery/ListEditor.ui +5 -0
- package/gallery/Logs-jsonl.ui +1 -0
- package/gallery/Logs-lines.ui +11 -0
- package/gallery/Logs-python.ui +1 -0
- package/gallery/Logs.ui +1 -0
- package/gallery/Map-city.ui +8 -0
- package/gallery/Map.ui +8 -0
- package/gallery/Metric.ui +3 -0
- package/gallery/OptionCards-multi.ui +10 -0
- package/gallery/OptionCards.ui +10 -0
- package/gallery/Pipeline-checks.ui +1 -0
- package/gallery/Pipeline-passed.ui +1 -0
- package/gallery/Pipeline-running.ui +1 -0
- package/gallery/Pipeline-stages.ui +13 -0
- package/gallery/Pipeline.ui +1 -0
- package/gallery/Plan.ui +9 -0
- package/gallery/Progress.ui +4 -0
- package/gallery/Provenance-deploy.ui +6 -0
- package/gallery/Provenance-no-receipt.ui +1 -0
- package/gallery/Provenance.ui +1 -0
- package/gallery/PullRequest.ui +1 -0
- package/gallery/Quiz.ui +6 -0
- package/gallery/Radio.ui +5 -0
- package/gallery/Rating.ui +5 -0
- package/gallery/Roadmap-sprint.ui +8 -0
- package/gallery/Roadmap-year.ui +9 -0
- package/gallery/Roadmap.ui +12 -0
- package/gallery/Row.ui +3 -0
- package/gallery/SegmentedBar-status.ui +8 -0
- package/gallery/SegmentedBar.ui +8 -0
- package/gallery/Select.ui +5 -0
- package/gallery/Slider.ui +7 -0
- package/gallery/Snippet.ui +3 -0
- package/gallery/Sources-chips.ui +4 -0
- package/gallery/Sources.ui +8 -0
- package/gallery/Sparkline.ui +6 -0
- package/gallery/Stack.ui +7 -0
- package/gallery/StackTrace-aggregate.ui +1 -0
- package/gallery/StackTrace-browser.ui +1 -0
- package/gallery/StackTrace-go.ui +1 -0
- package/gallery/StackTrace-java.ui +1 -0
- package/gallery/StackTrace-python.ui +1 -0
- package/gallery/StackTrace-rust.ui +1 -0
- package/gallery/StackTrace.ui +1 -0
- package/gallery/Status.ui +9 -0
- package/gallery/Stepper.ui +5 -0
- package/gallery/Steps.ui +8 -0
- package/gallery/Swatches-long-names.ui +4 -0
- package/gallery/Swatches-picker.ui +5 -0
- package/gallery/Swatches.ui +10 -0
- package/gallery/Table.ui +7 -0
- package/gallery/Tabs.ui +8 -0
- package/gallery/Tests-junit.ui +1 -0
- package/gallery/Tests-vitest-junit.ui +1 -0
- package/gallery/Tests-vitest.ui +1 -0
- package/gallery/Tests.ui +9 -0
- package/gallery/Text.ui +4 -0
- package/gallery/TextArea.ui +5 -0
- package/gallery/Threads-ids.ui +1 -0
- package/gallery/Threads.ui +1 -0
- package/gallery/Timeline.ui +8 -0
- package/gallery/Timer.ui +4 -0
- package/gallery/Toggle.ui +5 -0
- package/gallery/Transcript-text.ui +1 -0
- package/gallery/Transcript-untimed.ui +1 -0
- package/gallery/Transcript-whisper.ui +1 -0
- package/gallery/Transcript.ui +1 -0
- package/gallery/Tree.ui +3 -0
- package/gallery/Treemap-du.ui +1 -0
- package/gallery/Treemap-esbuild.ui +1 -0
- package/gallery/Treemap-tree.ui +7 -0
- package/gallery/Treemap.ui +18 -0
- package/gallery/Walkthrough.ui +9 -0
- package/gallery/Waterfall-har.ui +1 -0
- package/gallery/Waterfall-trace.ui +1 -0
- package/gallery/Waterfall.ui +11 -0
- package/gallery/data/acceptance/coil/viewport-lifecycle.json +25 -0
- package/gallery/data/acceptance/coil-gate.json +153 -0
- package/gallery/data/acceptance/orbit-baseline.json +163 -0
- package/gallery/data/acceptance/shots/gate-cell-opened.png +0 -0
- package/gallery/data/acceptance/shots/gate-light.png +0 -0
- package/gallery/data/acceptance/shots/gate-phone-dark.png +0 -0
- package/gallery/data/activity.json +1 -0
- package/gallery/data/bb-tasks-orb.json +420 -0
- package/gallery/data/biome.json +229 -0
- package/gallery/data/compare/status-after.png +0 -0
- package/gallery/data/compare/status-before.png +0 -0
- package/gallery/data/compare/status-dark-2x.png +0 -0
- package/gallery/data/compare/status-rerender.png +0 -0
- package/gallery/data/coverage-py.json +1 -0
- package/gallery/data/coverage-summary.json +1 -0
- package/gallery/data/coverage.out +23 -0
- package/gallery/data/deploys.json +1 -0
- package/gallery/data/device-runs.json +285 -0
- package/gallery/data/du-node-modules.txt +359 -0
- package/gallery/data/esbuild-meta.json +292 -0
- package/gallery/data/eslint.json +266 -0
- package/gallery/data/eslint.sarif +343 -0
- package/gallery/data/findings-review.json +76 -0
- package/gallery/data/gh-issues-nextjs.json +293 -0
- package/gallery/data/gh-pr-checks.json +272 -0
- package/gallery/data/gh-prs-nextjs.json +419 -0
- package/gallery/data/gh-run-failed.json +1 -0
- package/gallery/data/gh-run-passed.json +1 -0
- package/gallery/data/gh-run-running.json +1 -0
- package/gallery/data/github-release.json +57 -0
- package/gallery/data/hyperfine-startup.json +187 -0
- package/gallery/data/lcov.info +234 -0
- package/gallery/data/logs/media-sync.log +127 -0
- package/gallery/data/logs/thumbnailer.jsonl +17 -0
- package/gallery/data/logs/vitest-run.log +89 -0
- package/gallery/data/npm-audit.json +672 -0
- package/gallery/data/npm-outdated.json +179 -0
- package/gallery/data/oxlint.json +156 -0
- package/gallery/data/pip-outdated.json +68 -0
- package/gallery/data/pnpm-outdated.json +58 -0
- package/gallery/data/provenance/devicectl-install.json +35 -0
- package/gallery/data/provenance/install-timeout.log +4 -0
- package/gallery/data/provenance/iphone-install.json +72 -0
- package/gallery/data/provenance/iphone-no-receipt.json +57 -0
- package/gallery/data/provenance/live-home.jpg +0 -0
- package/gallery/data/provenance/live-probe.txt +10 -0
- package/gallery/data/provenance/xcodebuild-2811.log +19 -0
- package/gallery/data/provenance/xcodebuild-4712.log +19 -0
- package/gallery/data/pytest-junit.xml +64 -0
- package/gallery/data/python-org-trace.json +35 -0
- package/gallery/data/python-org.har +31 -0
- package/gallery/data/retry.ts +13 -0
- package/gallery/data/ruff.json +261 -0
- package/gallery/data/src/durations.py +63 -0
- package/gallery/data/src/json-logic.ts +156 -0
- package/gallery/data/src/mapMath.ts +135 -0
- package/gallery/data/src/semver/bump.go +73 -0
- package/gallery/data/traces/browser-error.json +5 -0
- package/gallery/data/traces/go/main.go +27 -0
- package/gallery/data/traces/go-panic.txt +16 -0
- package/gallery/data/traces/java-caused-by.txt +15 -0
- package/gallery/data/traces/node-aggregate.txt +30 -0
- package/gallery/data/traces/node-cause.txt +21 -0
- package/gallery/data/traces/py/deploy.json +5 -0
- package/gallery/data/traces/py/deploy.py +17 -0
- package/gallery/data/traces/py/settings.py +19 -0
- package/gallery/data/traces/python-chain.txt +30 -0
- package/gallery/data/traces/rust/main.rs +11 -0
- package/gallery/data/traces/rust-panic.txt +16 -0
- package/gallery/data/traces/shop/cart.mjs +19 -0
- package/gallery/data/traces/shop/pricing.mjs +12 -0
- package/gallery/data/traces/shop/status.mjs +9 -0
- package/gallery/data/traces/web/app.js +11 -0
- package/gallery/data/traces/web/index.html +1 -0
- package/gallery/data/traces/web/search.js +10 -0
- package/gallery/data/transcript/call-noisy.wav +0 -0
- package/gallery/data/transcript/call.json +362 -0
- package/gallery/data/transcript/call.wav +0 -0
- package/gallery/data/transcript/mlx-whisper-tiny.en-noisy.json +1 -0
- package/gallery/data/transcript/whisper-cpp-base.en-noisy.json +658 -0
- package/gallery/data/transcript/whisper-cpp-base.en.json +658 -0
- package/gallery/data/transcript/whisper-cpp-base.en.srt +16 -0
- package/gallery/data/transcript/whisper-cpp-tiny.en-noisy.json +619 -0
- package/gallery/data/tsc.txt +4 -0
- package/gallery/data/vitest-bench.json +119 -0
- package/gallery/data/vitest-junit.xml +133 -0
- package/gallery/data/vitest-report.json +612 -0
- package/gallery/images/bell.svg +10 -0
- package/gallery/images/bike.svg +47 -0
- package/gallery/images/footer-planet.webp +0 -0
- package/gallery/images/front-light.svg +13 -0
- package/gallery/images/golden-hour.webp +0 -0
- package/gallery/images/option-flowers.webp +0 -0
- package/gallery/images/option-hilltop.webp +0 -0
- package/gallery/images/option-meadow.webp +0 -0
- package/gallery/images/option-sky.webp +0 -0
- package/gallery/images/u-lock.svg +10 -0
- package/index.client.tsx +164 -0
- package/index.server.ts +216 -0
- package/package.json +50 -4
- package/paseo-plugin.json +14 -0
- package/server/data-source.ts +407 -0
- package/server/project.ts +981 -0
- package/server/web/address.ts +57 -0
- package/server/web/cache.ts +85 -0
- package/server/web/commons.ts +126 -0
- package/server/web/geocode.ts +40 -0
- package/server/web/image.ts +35 -0
- package/server/web/lookups.ts +248 -0
- package/server/web/net.ts +210 -0
- package/shared/core/acceptance.ts +744 -0
- package/shared/core/ast.ts +76 -0
- package/shared/core/benchmark.ts +743 -0
- package/shared/core/catalog.ts +733 -0
- package/shared/core/compare.ts +225 -0
- package/shared/core/coverage.ts +456 -0
- package/shared/core/data.ts +334 -0
- package/shared/core/decisions.ts +527 -0
- package/shared/core/dependencies.ts +604 -0
- package/shared/core/diagnostics.ts +24 -0
- package/shared/core/environments.ts +212 -0
- package/shared/core/evaluate.ts +737 -0
- package/shared/core/fields.ts +66 -0
- package/shared/core/findings.ts +1172 -0
- package/shared/core/form.ts +69 -0
- package/shared/core/index.ts +44 -0
- package/shared/core/junit.ts +252 -0
- package/shared/core/lexer.ts +275 -0
- package/shared/core/logs.ts +496 -0
- package/shared/core/markdown.ts +2374 -0
- package/shared/core/parser.ts +355 -0
- package/shared/core/perf-units.ts +228 -0
- package/shared/core/pipeline.ts +679 -0
- package/shared/core/project.ts +472 -0
- package/shared/core/provenance.ts +725 -0
- package/shared/core/stack-trace.ts +718 -0
- package/shared/core/streaming.ts +136 -0
- package/shared/core/test-results.ts +562 -0
- package/shared/core/thread-activity.ts +203 -0
- package/shared/core/tracking.ts +887 -0
- package/shared/core/transcript.ts +1364 -0
- package/shared/core/treemap.ts +446 -0
- package/shared/core/types.ts +70 -0
- package/shared/core/validate.ts +574 -0
- package/shared/core/waterfall.ts +536 -0
- package/shared/diagram/flowchart.ts +205 -0
- package/shared/diagram/layout.ts +440 -0
- package/shared/diagram/markdown.ts +135 -0
- package/shared/diagram/sequence.ts +122 -0
- package/shared/diagram/zoom.ts +114 -0
- package/shared/fallback.ts +120 -0
- package/shared/genui.test.ts +122 -0
- package/shared/instructions.ts +144 -0
- package/shared/logic/calendarMath.ts +130 -0
- package/shared/logic/chartMath.ts +289 -0
- package/shared/logic/choose-logic.ts +196 -0
- package/shared/logic/compare-logic.ts +270 -0
- package/shared/logic/content-model.ts +227 -0
- package/shared/logic/environments-logic.ts +53 -0
- package/shared/logic/explain-logic.ts +315 -0
- package/shared/logic/fieldLogic.ts +138 -0
- package/shared/logic/gallery-search.ts +71 -0
- package/shared/logic/icons.ts +322 -0
- package/shared/logic/json-logic.ts +153 -0
- package/shared/logic/mapMath.ts +133 -0
- package/shared/logic/project-logic.ts +39 -0
- package/shared/logic/references-model.ts +201 -0
- package/shared/logic/sketch.ts +165 -0
- package/shared/logic/supersession.ts +77 -0
- package/shared/logic/values.ts +133 -0
- package/shared/logic/widget-logic.ts +163 -0
- package/shared/rpc.ts +252 -0
- package/shared/segment.ts +81 -0
- package/shared/settings.ts +12 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import type { PaseoApi } from "@getpaseo/client";
|
|
7
|
+
import { collectLoads, compile, parseDataFile, type LoadedData } from "../shared/core";
|
|
8
|
+
import { COMPONENTS, componentSignature, type ComponentName } from "../shared/core/catalog";
|
|
9
|
+
|
|
10
|
+
export const MAX_DATA_BYTES = 2 * 1024 * 1024;
|
|
11
|
+
export const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
12
|
+
export const MAX_AUDIO_BYTES = 25 * 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
const BB_STORAGE_PREFIX = "$BB_THREAD_STORAGE/";
|
|
15
|
+
const PASEO_STORAGE_PREFIX = "$PASEO_THREAD_STORAGE/";
|
|
16
|
+
|
|
17
|
+
const IMAGE_TYPES: Readonly<Record<string, string>> = {
|
|
18
|
+
png: "image/png",
|
|
19
|
+
jpg: "image/jpeg",
|
|
20
|
+
jpeg: "image/jpeg",
|
|
21
|
+
gif: "image/gif",
|
|
22
|
+
webp: "image/webp",
|
|
23
|
+
svg: "image/svg+xml",
|
|
24
|
+
avif: "image/avif",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const AUDIO_TYPES: Readonly<Record<string, string>> = {
|
|
28
|
+
wav: "audio/wav",
|
|
29
|
+
mp3: "audio/mpeg",
|
|
30
|
+
m4a: "audio/mp4",
|
|
31
|
+
aac: "audio/aac",
|
|
32
|
+
ogg: "audio/ogg",
|
|
33
|
+
oga: "audio/ogg",
|
|
34
|
+
opus: "audio/ogg",
|
|
35
|
+
flac: "audio/flac",
|
|
36
|
+
webm: "audio/webm",
|
|
37
|
+
caf: "audio/x-caf",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type LoadedImage =
|
|
41
|
+
| { readonly ok: true; readonly dataUrl: string }
|
|
42
|
+
| { readonly ok: false; readonly error: string; readonly off?: boolean };
|
|
43
|
+
|
|
44
|
+
type FileContent =
|
|
45
|
+
| { ok: true; content: string; encoding: "utf8" | "base64" }
|
|
46
|
+
| { ok: false; error: string };
|
|
47
|
+
|
|
48
|
+
type FileReader = (requested: string, maxBytes: number) => Promise<FileContent>;
|
|
49
|
+
|
|
50
|
+
function contains(root: string, target: string): boolean {
|
|
51
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
52
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function agentStorageDir(agentId: string): string {
|
|
56
|
+
const safeId = agentId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
57
|
+
return path.join("/tmp/opencode/genui-storage", safeId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cwdCache = new Map<string, { at: number; cwd: string | null }>();
|
|
61
|
+
const CWD_CACHE_MS = 60_000;
|
|
62
|
+
|
|
63
|
+
export async function resolveAgentCwd(paseo: PaseoApi, agentId: string): Promise<string | null> {
|
|
64
|
+
const cached = cwdCache.get(agentId);
|
|
65
|
+
if (cached !== undefined && Date.now() - cached.at < CWD_CACHE_MS) return cached.cwd;
|
|
66
|
+
try {
|
|
67
|
+
const handle = paseo.agents.ref(agentId);
|
|
68
|
+
const current = handle.current();
|
|
69
|
+
if (current?.cwd) {
|
|
70
|
+
cwdCache.set(agentId, { at: Date.now(), cwd: current.cwd });
|
|
71
|
+
return current.cwd;
|
|
72
|
+
}
|
|
73
|
+
const refreshed = await handle.refresh();
|
|
74
|
+
const cwd = refreshed?.agent.cwd ?? null;
|
|
75
|
+
cwdCache.set(agentId, { at: Date.now(), cwd });
|
|
76
|
+
return cwd;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function readLocalFile(target: string, requested: string, maxBytes: number): Promise<FileContent> {
|
|
83
|
+
try {
|
|
84
|
+
const info = await stat(target);
|
|
85
|
+
if (!info.isFile()) return { ok: false, error: "not a regular file" };
|
|
86
|
+
if (info.size > maxBytes) {
|
|
87
|
+
return { ok: false, error: `${info.size} bytes is over the ${maxBytes}-byte limit` };
|
|
88
|
+
}
|
|
89
|
+
const extension = /\.([^./\\]+)$/.exec(requested)?.[1]?.toLowerCase() ?? "";
|
|
90
|
+
const binary =
|
|
91
|
+
(Object.hasOwn(IMAGE_TYPES, extension) && extension !== "svg") ||
|
|
92
|
+
Object.hasOwn(AUDIO_TYPES, extension);
|
|
93
|
+
const content = await readFile(target);
|
|
94
|
+
return binary
|
|
95
|
+
? { ok: true, content: content.toString("base64"), encoding: "base64" }
|
|
96
|
+
: { ok: true, content: content.toString("utf8"), encoding: "utf8" };
|
|
97
|
+
} catch (cause) {
|
|
98
|
+
const code = typeof cause === "object" && cause !== null && "code" in cause ? cause.code : null;
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: code === "ENOENT" ? "file not found" : cause instanceof Error ? cause.message : String(cause),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function readAgentFile(
|
|
107
|
+
paseo: PaseoApi,
|
|
108
|
+
agentId: string,
|
|
109
|
+
requested: string,
|
|
110
|
+
maxBytes: number,
|
|
111
|
+
): Promise<FileContent> {
|
|
112
|
+
const storageRoot = agentStorageDir(agentId);
|
|
113
|
+
await mkdir(storageRoot, { recursive: true }).catch(() => undefined);
|
|
114
|
+
const workspaceCwd = await resolveAgentCwd(paseo, agentId);
|
|
115
|
+
|
|
116
|
+
const candidates: string[] = [];
|
|
117
|
+
if (requested.startsWith(BB_STORAGE_PREFIX)) {
|
|
118
|
+
const rel = requested.slice(BB_STORAGE_PREFIX.length);
|
|
119
|
+
const target = path.resolve(storageRoot, rel);
|
|
120
|
+
if (contains(storageRoot, target)) candidates.push(target);
|
|
121
|
+
const galleryTarget = path.resolve(galleryDir(), rel);
|
|
122
|
+
if (contains(galleryDir(), galleryTarget)) candidates.push(galleryTarget);
|
|
123
|
+
} else if (requested.startsWith(PASEO_STORAGE_PREFIX)) {
|
|
124
|
+
const rel = requested.slice(PASEO_STORAGE_PREFIX.length);
|
|
125
|
+
const target = path.resolve(storageRoot, rel);
|
|
126
|
+
if (contains(storageRoot, target)) candidates.push(target);
|
|
127
|
+
} else if (path.isAbsolute(requested)) {
|
|
128
|
+
const target = path.resolve(requested);
|
|
129
|
+
if (
|
|
130
|
+
contains(storageRoot, target) ||
|
|
131
|
+
(workspaceCwd !== null && contains(workspaceCwd, target)) ||
|
|
132
|
+
contains("/tmp", target) ||
|
|
133
|
+
contains("/private/tmp", target)
|
|
134
|
+
) {
|
|
135
|
+
candidates.push(target);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
if (workspaceCwd !== null) {
|
|
139
|
+
const target = path.resolve(workspaceCwd, requested);
|
|
140
|
+
if (contains(workspaceCwd, target)) candidates.push(target);
|
|
141
|
+
}
|
|
142
|
+
const storageTarget = path.resolve(storageRoot, requested);
|
|
143
|
+
if (contains(storageRoot, storageTarget)) candidates.push(storageTarget);
|
|
144
|
+
const galleryTarget = path.resolve(galleryDir(), requested);
|
|
145
|
+
if (contains(galleryDir(), galleryTarget)) candidates.push(galleryTarget);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (candidates.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
error: "only files in this agent's workspace, its thread storage or /tmp can be loaded",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let firstFailure: FileContent | undefined;
|
|
156
|
+
for (const candidate of candidates) {
|
|
157
|
+
const result = await readLocalFile(candidate, requested, maxBytes);
|
|
158
|
+
if (result.ok) return result;
|
|
159
|
+
firstFailure ??= result;
|
|
160
|
+
}
|
|
161
|
+
return firstFailure!;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function loadWith(read: FileReader, requested: string, raw: boolean): Promise<LoadedData> {
|
|
165
|
+
const file = await read(requested, MAX_DATA_BYTES);
|
|
166
|
+
if (!file.ok) return file;
|
|
167
|
+
if (file.encoding !== "utf8") return { ok: false, error: "not a text file" };
|
|
168
|
+
if (!raw) return parseDataFile(requested, file.content);
|
|
169
|
+
return { ok: true, value: file.content.startsWith("\uFEFF") ? file.content.slice(1) : file.content };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function loadMediaWith(
|
|
173
|
+
read: FileReader,
|
|
174
|
+
requested: string,
|
|
175
|
+
types: Readonly<Record<string, string>>,
|
|
176
|
+
maxBytes: number,
|
|
177
|
+
noun: string,
|
|
178
|
+
): Promise<LoadedImage> {
|
|
179
|
+
const extension = /\.([^./\\]+)$/.exec(requested)?.[1]?.toLowerCase();
|
|
180
|
+
if (extension === undefined || !Object.hasOwn(types, extension)) {
|
|
181
|
+
return { ok: false, error: `only ${Object.keys(types).join(", ")} ${noun} can be shown` };
|
|
182
|
+
}
|
|
183
|
+
const file = await read(requested, maxBytes);
|
|
184
|
+
if (!file.ok) return file;
|
|
185
|
+
const base64 =
|
|
186
|
+
file.encoding === "base64" ? file.content : Buffer.from(file.content, "utf8").toString("base64");
|
|
187
|
+
return { ok: true, dataUrl: `data:${types[extension]};base64,${base64}` };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function loadAgentFile(
|
|
191
|
+
paseo: PaseoApi,
|
|
192
|
+
agentId: string,
|
|
193
|
+
requested: string,
|
|
194
|
+
{ raw = false }: { raw?: boolean } = {},
|
|
195
|
+
): Promise<LoadedData> {
|
|
196
|
+
return loadWith((target, maxBytes) => readAgentFile(paseo, agentId, target, maxBytes), requested, raw);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function loadAgentImage(
|
|
200
|
+
paseo: PaseoApi,
|
|
201
|
+
agentId: string,
|
|
202
|
+
requested: string,
|
|
203
|
+
): Promise<LoadedImage> {
|
|
204
|
+
return loadMediaWith(
|
|
205
|
+
(target, maxBytes) => readAgentFile(paseo, agentId, target, maxBytes),
|
|
206
|
+
requested,
|
|
207
|
+
IMAGE_TYPES,
|
|
208
|
+
MAX_IMAGE_BYTES,
|
|
209
|
+
"images",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function loadAgentAudio(
|
|
214
|
+
paseo: PaseoApi,
|
|
215
|
+
agentId: string,
|
|
216
|
+
requested: string,
|
|
217
|
+
): Promise<LoadedImage> {
|
|
218
|
+
return loadMediaWith(
|
|
219
|
+
(target, maxBytes) => readAgentFile(paseo, agentId, target, maxBytes),
|
|
220
|
+
requested,
|
|
221
|
+
AUDIO_TYPES,
|
|
222
|
+
MAX_AUDIO_BYTES,
|
|
223
|
+
"recordings",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let cachedPluginRoot: string | null = null;
|
|
228
|
+
|
|
229
|
+
function resolvePluginRoot(): string {
|
|
230
|
+
if (cachedPluginRoot !== null) return cachedPluginRoot;
|
|
231
|
+
|
|
232
|
+
const hasGallery = (dir: string): boolean => {
|
|
233
|
+
try {
|
|
234
|
+
return existsSync(path.join(dir, "gallery", "Card.ui"));
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
if (typeof import.meta.url === "string" && import.meta.url.startsWith("file:")) {
|
|
242
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
243
|
+
const candidate =
|
|
244
|
+
path.basename(here) === "server" || path.basename(here) === "dist"
|
|
245
|
+
? path.dirname(here)
|
|
246
|
+
: here;
|
|
247
|
+
if (hasGallery(candidate)) {
|
|
248
|
+
cachedPluginRoot = candidate;
|
|
249
|
+
return candidate;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
// Compiled server bundle has no file:// import.meta.url
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (hasGallery(process.cwd())) {
|
|
257
|
+
cachedPluginRoot = process.cwd();
|
|
258
|
+
return cachedPluginRoot;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const paseoHome = process.env.PASEO_HOME || path.join(os.homedir(), ".paseo");
|
|
262
|
+
try {
|
|
263
|
+
const raw = readFileSync(path.join(paseoHome, "config.json"), "utf8");
|
|
264
|
+
const cfg = JSON.parse(raw) as { plugins?: Record<string, { path?: string }> };
|
|
265
|
+
for (const entry of Object.values(cfg.plugins ?? {})) {
|
|
266
|
+
if (typeof entry?.path === "string" && hasGallery(entry.path)) {
|
|
267
|
+
cachedPluginRoot = entry.path;
|
|
268
|
+
return entry.path;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
// Ignore config read failure
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const pluginsDir = path.join(paseoHome, "plugins");
|
|
277
|
+
for (const idDir of readdirSync(pluginsDir)) {
|
|
278
|
+
const idPath = path.join(pluginsDir, idDir);
|
|
279
|
+
for (const revDir of readdirSync(idPath)) {
|
|
280
|
+
const checkout = path.join(idPath, revDir, "checkout");
|
|
281
|
+
if (hasGallery(checkout)) {
|
|
282
|
+
cachedPluginRoot = checkout;
|
|
283
|
+
return checkout;
|
|
284
|
+
}
|
|
285
|
+
const nm = path.join(idPath, revDir, "node_modules");
|
|
286
|
+
if (existsSync(nm)) {
|
|
287
|
+
for (const pkg of readdirSync(nm)) {
|
|
288
|
+
const pkgPath = path.join(nm, pkg);
|
|
289
|
+
if (hasGallery(pkgPath)) {
|
|
290
|
+
cachedPluginRoot = pkgPath;
|
|
291
|
+
return pkgPath;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
// Ignore scan failure
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return process.cwd();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function packagePath(...parts: string[]): string {
|
|
305
|
+
return path.join(resolvePluginRoot(), ...parts);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function galleryDir(): string {
|
|
309
|
+
return packagePath("gallery");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let sketchFont: Promise<string> | null = null;
|
|
313
|
+
|
|
314
|
+
export function sketchFontDataUrl(): Promise<string> {
|
|
315
|
+
sketchFont ??= readFile(packagePath("assets", "fonts", "Excalifont-Regular-latin.woff2")).then(
|
|
316
|
+
(bytes) => `data:font/woff2;base64,${bytes.toString("base64")}`,
|
|
317
|
+
);
|
|
318
|
+
return sketchFont;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const GALLERY_SPEC = /^[A-Za-z0-9][A-Za-z0-9._-]*\.ui$/;
|
|
322
|
+
|
|
323
|
+
export async function listGallery(): Promise<string[]> {
|
|
324
|
+
const entries = await readdir(galleryDir(), { withFileTypes: true }).catch(() => []);
|
|
325
|
+
return entries
|
|
326
|
+
.filter((entry) => entry.isFile() && GALLERY_SPEC.test(entry.name))
|
|
327
|
+
.map((entry) => entry.name)
|
|
328
|
+
.sort((a, b) => a.localeCompare(b));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function readGallerySpec(name: string): Promise<string> {
|
|
332
|
+
const normalized = name.endsWith(".ui") ? name : `${name}.ui`;
|
|
333
|
+
if (!GALLERY_SPEC.test(normalized)) throw new Error(`not a gallery spec: ${name}`);
|
|
334
|
+
return readFile(path.join(galleryDir(), normalized), "utf8");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function readGalleryFile(requested: string, maxBytes: number): Promise<FileContent> {
|
|
338
|
+
const root = galleryDir();
|
|
339
|
+
const clean = requested.startsWith(BB_STORAGE_PREFIX)
|
|
340
|
+
? requested.slice(BB_STORAGE_PREFIX.length)
|
|
341
|
+
: requested;
|
|
342
|
+
const target = path.resolve(root, clean);
|
|
343
|
+
if (path.isAbsolute(clean) || !contains(root, target)) {
|
|
344
|
+
return { ok: false, error: "gallery specs can only load files in gallery/" };
|
|
345
|
+
}
|
|
346
|
+
return readLocalFile(target, clean, maxBytes);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function loadGalleryFile(requested: string, { raw = false }: { raw?: boolean } = {}): Promise<LoadedData> {
|
|
350
|
+
return loadWith(readGalleryFile, requested, raw);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function loadGalleryImage(requested: string): Promise<LoadedImage> {
|
|
354
|
+
return loadMediaWith(readGalleryFile, requested, IMAGE_TYPES, MAX_IMAGE_BYTES, "images");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function loadGalleryAudio(requested: string): Promise<LoadedImage> {
|
|
358
|
+
return loadMediaWith(readGalleryFile, requested, AUDIO_TYPES, MAX_AUDIO_BYTES, "recordings");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const DATA_PREVIEW_CHARS = 600;
|
|
362
|
+
|
|
363
|
+
function isComponentName(name: string): name is ComponentName {
|
|
364
|
+
return Object.hasOwn(COMPONENTS, name);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function examplesOf(names: readonly string[], component: string): string[] {
|
|
368
|
+
return names
|
|
369
|
+
.filter((name) => name === `${component}.ui` || name.startsWith(`${component}-`))
|
|
370
|
+
.sort((a, b) => a.length - b.length || a.localeCompare(b));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function describeExample(name: string): Promise<string> {
|
|
374
|
+
const source = (await readGallerySpec(name)).trimEnd();
|
|
375
|
+
const parts = [`\`\`\`ui\n${source}\n\`\`\``];
|
|
376
|
+
const program = compile(source).program;
|
|
377
|
+
for (const request of program === null ? [] : collectLoads(program)) {
|
|
378
|
+
const loaded = await loadGalleryFile(request.path, { raw: true });
|
|
379
|
+
if (!loaded.ok || typeof loaded.value !== "string") continue;
|
|
380
|
+
const text = loaded.value.trim();
|
|
381
|
+
const shown = text.length > DATA_PREVIEW_CHARS ? `${text.slice(0, DATA_PREVIEW_CHARS)}…` : text;
|
|
382
|
+
parts.push(`"${request.path}" in this example holds:\n${shown}`);
|
|
383
|
+
}
|
|
384
|
+
return parts.join("\n");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export async function examplesReport(components: readonly string[]): Promise<{ ok: boolean; text: string }> {
|
|
388
|
+
const names = await listGallery();
|
|
389
|
+
const sections: string[] = [];
|
|
390
|
+
let ok = true;
|
|
391
|
+
for (const component of components) {
|
|
392
|
+
if (!isComponentName(component)) {
|
|
393
|
+
ok = false;
|
|
394
|
+
sections.push(`${component}: not a component.`);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const examples = examplesOf(names, component).slice(0, 3);
|
|
398
|
+
const described = await Promise.all(examples.map(describeExample));
|
|
399
|
+
sections.push(
|
|
400
|
+
[
|
|
401
|
+
`## ${componentSignature(component)}`,
|
|
402
|
+
...(described.length > 0 ? described : ["No example yet."]),
|
|
403
|
+
].join("\n"),
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return { ok, text: sections.join("\n\n") };
|
|
407
|
+
}
|