omegon 0.6.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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { SemanticPurpose } from "../excalidraw/types.ts";
|
|
2
|
+
|
|
3
|
+
export const NATIVE_DIAGRAM_MOTIFS = ["pipeline", "fanout", "panel-split"] as const;
|
|
4
|
+
export type NativeDiagramMotif = (typeof NATIVE_DIAGRAM_MOTIFS)[number];
|
|
5
|
+
|
|
6
|
+
export const NATIVE_DIAGRAM_DIRECTIONS = ["horizontal", "vertical"] as const;
|
|
7
|
+
export type NativeDiagramDirection = (typeof NATIVE_DIAGRAM_DIRECTIONS)[number];
|
|
8
|
+
|
|
9
|
+
export const NATIVE_NODE_KINDS = ["process", "decision", "start", "end", "data"] as const;
|
|
10
|
+
export type NativeNodeKind = (typeof NATIVE_NODE_KINDS)[number];
|
|
11
|
+
|
|
12
|
+
export interface NativeDiagramPanelSpec {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface NativeDiagramNodeSpec {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
kind?: NativeNodeKind;
|
|
21
|
+
semantic?: SemanticPurpose;
|
|
22
|
+
order?: number;
|
|
23
|
+
panel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface NativeDiagramEdgeSpec {
|
|
27
|
+
from: string;
|
|
28
|
+
to: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
semantic?: SemanticPurpose;
|
|
31
|
+
dashed?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface NativeDiagramCanvasSpec {
|
|
35
|
+
padding?: number;
|
|
36
|
+
background?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface NativeDiagramSpec {
|
|
40
|
+
title?: string;
|
|
41
|
+
motif: NativeDiagramMotif;
|
|
42
|
+
direction?: NativeDiagramDirection;
|
|
43
|
+
canvas?: NativeDiagramCanvasSpec;
|
|
44
|
+
panels?: NativeDiagramPanelSpec[];
|
|
45
|
+
nodes: NativeDiagramNodeSpec[];
|
|
46
|
+
edges?: NativeDiagramEdgeSpec[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function asString(value: unknown): string | undefined {
|
|
54
|
+
return typeof value === "string" ? value : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function asNumber(value: unknown): number | undefined {
|
|
58
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function asBoolean(value: unknown): boolean | undefined {
|
|
62
|
+
return typeof value === "boolean" ? value : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function asMotif(value: unknown): NativeDiagramMotif | undefined {
|
|
66
|
+
return typeof value === "string" && NATIVE_DIAGRAM_MOTIFS.includes(value as NativeDiagramMotif)
|
|
67
|
+
? value as NativeDiagramMotif
|
|
68
|
+
: undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function asDirection(value: unknown): NativeDiagramDirection | undefined {
|
|
72
|
+
return typeof value === "string" && NATIVE_DIAGRAM_DIRECTIONS.includes(value as NativeDiagramDirection)
|
|
73
|
+
? value as NativeDiagramDirection
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function asNodeKind(value: unknown): NativeNodeKind | undefined {
|
|
78
|
+
return typeof value === "string" && NATIVE_NODE_KINDS.includes(value as NativeNodeKind)
|
|
79
|
+
? value as NativeNodeKind
|
|
80
|
+
: undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function asSemantic(value: unknown): SemanticPurpose | undefined {
|
|
84
|
+
return typeof value === "string" ? value as SemanticPurpose : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function parseNativeDiagramSpec(input: unknown): NativeDiagramSpec {
|
|
88
|
+
if (!isRecord(input)) throw new Error("Native diagram spec must be a JSON object");
|
|
89
|
+
const motif = asMotif(input.motif);
|
|
90
|
+
if (!motif) throw new Error(`Native diagram spec requires motif ∈ ${NATIVE_DIAGRAM_MOTIFS.join(", ")}`);
|
|
91
|
+
|
|
92
|
+
const nodesValue = input.nodes;
|
|
93
|
+
if (!Array.isArray(nodesValue) || nodesValue.length === 0) {
|
|
94
|
+
throw new Error("Native diagram spec requires a non-empty nodes array");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const nodes: NativeDiagramNodeSpec[] = nodesValue.map((value, index) => {
|
|
98
|
+
if (!isRecord(value)) throw new Error(`Node ${index} must be an object`);
|
|
99
|
+
const id = asString(value.id);
|
|
100
|
+
const label = asString(value.label);
|
|
101
|
+
if (!id) throw new Error(`Node ${index} requires string id`);
|
|
102
|
+
if (!label) throw new Error(`Node '${id}' requires string label`);
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
label,
|
|
106
|
+
kind: asNodeKind(value.kind),
|
|
107
|
+
semantic: asSemantic(value.semantic),
|
|
108
|
+
order: asNumber(value.order),
|
|
109
|
+
panel: asString(value.panel),
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const edges: NativeDiagramEdgeSpec[] | undefined = Array.isArray(input.edges)
|
|
114
|
+
? input.edges.map((value, index) => {
|
|
115
|
+
if (!isRecord(value)) throw new Error(`Edge ${index} must be an object`);
|
|
116
|
+
const from = asString(value.from);
|
|
117
|
+
const to = asString(value.to);
|
|
118
|
+
if (!from || !to) throw new Error(`Edge ${index} requires string from/to`);
|
|
119
|
+
return {
|
|
120
|
+
from,
|
|
121
|
+
to,
|
|
122
|
+
label: asString(value.label),
|
|
123
|
+
semantic: asSemantic(value.semantic),
|
|
124
|
+
dashed: asBoolean(value.dashed),
|
|
125
|
+
};
|
|
126
|
+
})
|
|
127
|
+
: undefined;
|
|
128
|
+
|
|
129
|
+
const panels: NativeDiagramPanelSpec[] | undefined = Array.isArray(input.panels)
|
|
130
|
+
? input.panels.map((value, index) => {
|
|
131
|
+
if (!isRecord(value)) throw new Error(`Panel ${index} must be an object`);
|
|
132
|
+
const id = asString(value.id);
|
|
133
|
+
const label = asString(value.label);
|
|
134
|
+
if (!id) throw new Error(`Panel ${index} requires string id`);
|
|
135
|
+
if (!label) throw new Error(`Panel '${id}' requires string label`);
|
|
136
|
+
return { id, label };
|
|
137
|
+
})
|
|
138
|
+
: undefined;
|
|
139
|
+
|
|
140
|
+
const canvas = isRecord(input.canvas)
|
|
141
|
+
? {
|
|
142
|
+
padding: asNumber(input.canvas.padding),
|
|
143
|
+
background: asString(input.canvas.background),
|
|
144
|
+
}
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
147
|
+
const spec: NativeDiagramSpec = {
|
|
148
|
+
title: asString(input.title),
|
|
149
|
+
motif,
|
|
150
|
+
direction: asDirection(input.direction),
|
|
151
|
+
canvas,
|
|
152
|
+
panels,
|
|
153
|
+
nodes,
|
|
154
|
+
edges,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const errors = validateNativeDiagramSpec(spec);
|
|
158
|
+
if (errors.length > 0) throw new Error(errors.join("\n"));
|
|
159
|
+
return spec;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function validateNativeDiagramSpec(spec: NativeDiagramSpec): string[] {
|
|
163
|
+
const errors: string[] = [];
|
|
164
|
+
const nodeIds = new Set<string>();
|
|
165
|
+
const panelIds = new Set<string>();
|
|
166
|
+
|
|
167
|
+
for (const node of spec.nodes) {
|
|
168
|
+
if (nodeIds.has(node.id)) errors.push(`Duplicate node id '${node.id}'`);
|
|
169
|
+
nodeIds.add(node.id);
|
|
170
|
+
if (!node.label.trim()) errors.push(`Node '${node.id}' requires a non-empty label`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const panel of spec.panels ?? []) {
|
|
174
|
+
if (panelIds.has(panel.id)) errors.push(`Duplicate panel id '${panel.id}'`);
|
|
175
|
+
panelIds.add(panel.id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (spec.motif === "fanout" && spec.nodes.length < 2) {
|
|
179
|
+
errors.push("Motif 'fanout' requires at least two nodes");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (spec.motif === "panel-split" && (!spec.panels || spec.panels.length < 2)) {
|
|
183
|
+
errors.push("Motif 'panel-split' requires at least two panels");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const node of spec.nodes) {
|
|
187
|
+
if (node.panel && !panelIds.has(node.panel)) {
|
|
188
|
+
errors.push(`Node '${node.id}' references missing panel '${node.panel}'`);
|
|
189
|
+
}
|
|
190
|
+
if (spec.motif !== "panel-split" && node.panel) {
|
|
191
|
+
errors.push(`Node '${node.id}' uses panel assignment but motif '${spec.motif}' does not support panels`);
|
|
192
|
+
}
|
|
193
|
+
if (spec.motif === "panel-split" && !node.panel) {
|
|
194
|
+
errors.push(`Node '${node.id}' must declare a panel for motif 'panel-split'`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const edge of spec.edges ?? []) {
|
|
199
|
+
if (!nodeIds.has(edge.from)) errors.push(`Edge references missing source '${edge.from}'`);
|
|
200
|
+
if (!nodeIds.has(edge.to)) errors.push(`Edge references missing target '${edge.to}'`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return errors;
|
|
204
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Scene,
|
|
3
|
+
SceneDiamond,
|
|
4
|
+
SceneEllipse,
|
|
5
|
+
SceneElement,
|
|
6
|
+
SceneLine,
|
|
7
|
+
ScenePath,
|
|
8
|
+
SceneRect,
|
|
9
|
+
SceneText,
|
|
10
|
+
} from "./scene.ts";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_FONT_FAMILY = "Cascadia Code, Cascadia Mono, Menlo, monospace";
|
|
13
|
+
const DEFAULT_STROKE = "#111827";
|
|
14
|
+
const DEFAULT_FILL = "none";
|
|
15
|
+
const DEFAULT_STROKE_WIDTH = 1;
|
|
16
|
+
const ARROW_MARKER_ID = "pi-native-arrow";
|
|
17
|
+
|
|
18
|
+
function escapeXml(text: string): string {
|
|
19
|
+
return text
|
|
20
|
+
.replace(/&/g, "&")
|
|
21
|
+
.replace(/</g, "<")
|
|
22
|
+
.replace(/>/g, ">")
|
|
23
|
+
.replace(/"/g, """)
|
|
24
|
+
.replace(/'/g, "'");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fmt(value: number): string {
|
|
28
|
+
const rounded = Math.round(value * 100) / 100;
|
|
29
|
+
if (Number.isInteger(rounded)) return String(rounded);
|
|
30
|
+
return rounded.toFixed(2).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function attr(name: string, value: string | number | undefined): string {
|
|
34
|
+
if (value === undefined) return "";
|
|
35
|
+
const serialized = typeof value === "number" ? fmt(value) : escapeXml(value);
|
|
36
|
+
return ` ${name}="${serialized}"`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function commonAttrs(element: Pick<SceneElement, "fill" | "stroke" | "strokeWidth" | "strokeDasharray" | "opacity">): string {
|
|
40
|
+
return [
|
|
41
|
+
attr("fill", element.fill ?? DEFAULT_FILL),
|
|
42
|
+
attr("stroke", element.stroke ?? DEFAULT_STROKE),
|
|
43
|
+
attr("stroke-width", element.strokeWidth ?? DEFAULT_STROKE_WIDTH),
|
|
44
|
+
element.strokeDasharray ? attr("stroke-dasharray", element.strokeDasharray) : "",
|
|
45
|
+
element.opacity !== undefined ? attr("opacity", element.opacity) : "",
|
|
46
|
+
].join("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function renderRect(element: SceneRect): string {
|
|
50
|
+
return `<rect${attr("x", element.x)}${attr("y", element.y)}${attr("width", element.width)}${attr("height", element.height)}${attr("rx", element.rx)}${attr("ry", element.ry)}${commonAttrs(element)} />`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function renderEllipse(element: SceneEllipse): string {
|
|
54
|
+
return `<ellipse${attr("cx", element.cx)}${attr("cy", element.cy)}${attr("rx", element.rx)}${attr("ry", element.ry)}${commonAttrs(element)} />`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderDiamond(element: SceneDiamond): string {
|
|
58
|
+
const midX = element.x + element.width / 2;
|
|
59
|
+
const midY = element.y + element.height / 2;
|
|
60
|
+
const d = [
|
|
61
|
+
`M ${fmt(midX)} ${fmt(element.y)}`,
|
|
62
|
+
`L ${fmt(element.x + element.width)} ${fmt(midY)}`,
|
|
63
|
+
`L ${fmt(midX)} ${fmt(element.y + element.height)}`,
|
|
64
|
+
`L ${fmt(element.x)} ${fmt(midY)}`,
|
|
65
|
+
"Z",
|
|
66
|
+
].join(" ");
|
|
67
|
+
return `<path${attr("d", d)}${commonAttrs(element)} />`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderPath(element: ScenePath): string {
|
|
71
|
+
const markerStart = element.markerStart === "arrow" ? attr("marker-start", `url(#${ARROW_MARKER_ID})`) : "";
|
|
72
|
+
const markerEnd = element.markerEnd === "arrow" ? attr("marker-end", `url(#${ARROW_MARKER_ID})`) : "";
|
|
73
|
+
return `<path${attr("d", element.d)}${commonAttrs(element)}${markerStart}${markerEnd} />`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function renderText(element: SceneText): string {
|
|
77
|
+
return `<text${attr("x", element.x)}${attr("y", element.y)}${attr("font-size", element.fontSize)}${attr("font-family", element.fontFamily ?? DEFAULT_FONT_FAMILY)}${attr("text-anchor", element.textAnchor ?? "start")}${attr("font-weight", element.fontWeight ?? "400")}${attr("fill", element.fill ?? DEFAULT_STROKE)}>${escapeXml(element.text)}</text>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderLine(element: SceneLine): string {
|
|
81
|
+
return `<line${attr("x1", element.x1)}${attr("y1", element.y1)}${attr("x2", element.x2)}${attr("y2", element.y2)}${commonAttrs(element)} />`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderElement(element: SceneElement): string {
|
|
85
|
+
switch (element.kind) {
|
|
86
|
+
case "rect":
|
|
87
|
+
return renderRect(element);
|
|
88
|
+
case "ellipse":
|
|
89
|
+
return renderEllipse(element);
|
|
90
|
+
case "diamond":
|
|
91
|
+
return renderDiamond(element);
|
|
92
|
+
case "path":
|
|
93
|
+
return renderPath(element);
|
|
94
|
+
case "text":
|
|
95
|
+
return renderText(element);
|
|
96
|
+
case "line":
|
|
97
|
+
return renderLine(element);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function serializeSceneToSvg(scene: Scene): string {
|
|
102
|
+
const width = fmt(scene.width);
|
|
103
|
+
const height = fmt(scene.height);
|
|
104
|
+
const body = scene.elements.map(renderElement).join("\n ");
|
|
105
|
+
return [
|
|
106
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img">`,
|
|
107
|
+
" <defs>",
|
|
108
|
+
` <marker id="${ARROW_MARKER_ID}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse" markerUnits="strokeWidth">`,
|
|
109
|
+
" <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#111827\" />",
|
|
110
|
+
" </marker>",
|
|
111
|
+
" </defs>",
|
|
112
|
+
` <rect x="0" y="0" width="${width}" height="${height}" fill="${escapeXml(scene.background)}" />`,
|
|
113
|
+
body ? ` ${body}` : "",
|
|
114
|
+
"</svg>",
|
|
115
|
+
].filter(Boolean).join("\n");
|
|
116
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sci-UI — shared visual primitives for Alpharius-styled tool call rendering.
|
|
3
|
+
*
|
|
4
|
+
* Design language:
|
|
5
|
+
* Call line: ◈──{ tool_name }── summary text ──────────────────────────
|
|
6
|
+
* Loading: ▶░░░░░▓▒{ tool_name }░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
7
|
+
* Result: ╰── ✓ compact summary
|
|
8
|
+
* Expanded: │ line 1
|
|
9
|
+
* │ line 2
|
|
10
|
+
* ╰── N lines
|
|
11
|
+
* Banner: ── ◈ label ──────────────────────────────────────────────
|
|
12
|
+
* content line
|
|
13
|
+
*
|
|
14
|
+
* NOTE: All classes use explicit field declarations (not constructor parameter
|
|
15
|
+
* properties) to remain compatible with Node.js strip-only TypeScript mode.
|
|
16
|
+
*/
|
|
17
|
+
import { truncateToWidth, visibleWidth } from "@cwilson613/pi-tui";
|
|
18
|
+
import type { Theme } from "@cwilson613/pi-coding-agent";
|
|
19
|
+
|
|
20
|
+
export interface SciComponent {
|
|
21
|
+
render(width: number): string[];
|
|
22
|
+
invalidate(): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Tool glyphs by name ───────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export const TOOL_GLYPHS: Record<string, string> = {
|
|
28
|
+
// Core tools
|
|
29
|
+
read: "▸",
|
|
30
|
+
edit: "▸",
|
|
31
|
+
write: "▸",
|
|
32
|
+
bash: "▸",
|
|
33
|
+
grep: "▸",
|
|
34
|
+
find: "▸",
|
|
35
|
+
ls: "▸",
|
|
36
|
+
// Extension tools
|
|
37
|
+
design_tree: "◈",
|
|
38
|
+
design_tree_update: "◈",
|
|
39
|
+
openspec_manage: "◎",
|
|
40
|
+
memory_store: "⌗",
|
|
41
|
+
memory_recall: "⌗",
|
|
42
|
+
memory_query: "⌗",
|
|
43
|
+
memory_focus: "⌗",
|
|
44
|
+
memory_release: "⌗",
|
|
45
|
+
memory_supersede: "⌗",
|
|
46
|
+
memory_archive: "⌗",
|
|
47
|
+
memory_compact: "⌗",
|
|
48
|
+
memory_connect: "⌗",
|
|
49
|
+
memory_search_archive: "⌗",
|
|
50
|
+
memory_episodes: "⌗",
|
|
51
|
+
memory_ingest_lifecycle: "⌗",
|
|
52
|
+
cleave_run: "⚡",
|
|
53
|
+
cleave_assess: "⚡",
|
|
54
|
+
whoami: "⊙",
|
|
55
|
+
chronos: "◷",
|
|
56
|
+
web_search: "⌖",
|
|
57
|
+
render_diagram: "⬡",
|
|
58
|
+
render_native_diagram: "⬡",
|
|
59
|
+
render_excalidraw: "⬡",
|
|
60
|
+
render_composition_still: "⬡",
|
|
61
|
+
render_composition_video: "⬡",
|
|
62
|
+
generate_image_local: "⬡",
|
|
63
|
+
view: "⬡",
|
|
64
|
+
// Inference / model tools
|
|
65
|
+
set_model_tier: "◆",
|
|
66
|
+
set_thinking_level: "◆",
|
|
67
|
+
ask_local_model: "◆",
|
|
68
|
+
list_local_models: "◆",
|
|
69
|
+
manage_ollama: "◆",
|
|
70
|
+
switch_to_offline_driver: "◆",
|
|
71
|
+
// Profile / misc
|
|
72
|
+
manage_tools: "⊞",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function glyphFor(toolName: string): string {
|
|
76
|
+
return TOOL_GLYPHS[toolName] ?? "▸";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── SciCallLine ──────────────────────────────────────────────────────────
|
|
80
|
+
//
|
|
81
|
+
// ◈──{ design_tree }── action:node_id ─────────────────────────────────
|
|
82
|
+
|
|
83
|
+
export class SciCallLine implements SciComponent {
|
|
84
|
+
glyph: string;
|
|
85
|
+
toolName: string;
|
|
86
|
+
summary: string;
|
|
87
|
+
theme: Theme;
|
|
88
|
+
|
|
89
|
+
constructor(glyph: string, toolName: string, summary: string, theme: Theme) {
|
|
90
|
+
this.glyph = glyph;
|
|
91
|
+
this.toolName = toolName;
|
|
92
|
+
this.summary = summary;
|
|
93
|
+
this.theme = theme;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
render(width: number): string[] {
|
|
97
|
+
const th = this.theme;
|
|
98
|
+
const g = th.fg("accent", this.glyph);
|
|
99
|
+
const dashes = th.fg("dim", "──");
|
|
100
|
+
const openBracket = th.fg("border", "{");
|
|
101
|
+
const closeBracket = th.fg("border", "}");
|
|
102
|
+
const name = th.fg("accent", this.toolName);
|
|
103
|
+
const sep = th.fg("dim", "──");
|
|
104
|
+
const sumText = this.summary
|
|
105
|
+
? " " + th.fg("muted", this.summary) + " "
|
|
106
|
+
: " ";
|
|
107
|
+
|
|
108
|
+
const core = `${g}${dashes}${openBracket}${name}${closeBracket}${sep}${sumText}`;
|
|
109
|
+
const coreVw = visibleWidth(core);
|
|
110
|
+
const fillLen = Math.max(0, width - coreVw);
|
|
111
|
+
const fill = th.fg("dim", "─".repeat(fillLen));
|
|
112
|
+
|
|
113
|
+
return [truncateToWidth(core + fill, width)];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
invalidate(): void {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── SciLoadingLine ───────────────────────────────────────────────────────
|
|
120
|
+
//
|
|
121
|
+
// ▶░░░░░▓▒{ tool_name }░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
122
|
+
//
|
|
123
|
+
// A bright block scans left→right while a tool is pending.
|
|
124
|
+
|
|
125
|
+
export class SciLoadingLine implements SciComponent {
|
|
126
|
+
toolName: string;
|
|
127
|
+
theme: Theme;
|
|
128
|
+
|
|
129
|
+
constructor(toolName: string, theme: Theme) {
|
|
130
|
+
this.toolName = toolName;
|
|
131
|
+
this.theme = theme;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
render(width: number): string[] {
|
|
135
|
+
const th = this.theme;
|
|
136
|
+
const label = `{ ${this.toolName} }`;
|
|
137
|
+
const labelVw = visibleWidth(label);
|
|
138
|
+
const barWidth = Math.max(4, width - labelVw - 2);
|
|
139
|
+
const frame = Math.floor(Date.now() / 120) % barWidth;
|
|
140
|
+
|
|
141
|
+
const bar = Array.from({ length: barWidth }, (_, i) => {
|
|
142
|
+
if (i === frame) return th.fg("accent", "▓");
|
|
143
|
+
if (i === (frame + 1) % barWidth) return th.fg("muted", "▒");
|
|
144
|
+
return th.fg("dim", "░");
|
|
145
|
+
}).join("");
|
|
146
|
+
|
|
147
|
+
const line =
|
|
148
|
+
th.fg("accent", "▶") +
|
|
149
|
+
bar +
|
|
150
|
+
th.fg("muted", "{") +
|
|
151
|
+
th.fg("accent", ` ${this.toolName} `) +
|
|
152
|
+
th.fg("muted", "}");
|
|
153
|
+
|
|
154
|
+
return [truncateToWidth(line, width)];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
invalidate(): void {}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── SciResult (compact / collapsed) ─────────────────────────────────────
|
|
161
|
+
//
|
|
162
|
+
// ╰── ✓ compact summary
|
|
163
|
+
// ╰── ✕ error text
|
|
164
|
+
// ╰── · pending
|
|
165
|
+
|
|
166
|
+
export class SciResult implements SciComponent {
|
|
167
|
+
summary: string;
|
|
168
|
+
status: "success" | "error" | "pending";
|
|
169
|
+
theme: Theme;
|
|
170
|
+
|
|
171
|
+
constructor(summary: string, status: "success" | "error" | "pending", theme: Theme) {
|
|
172
|
+
this.summary = summary;
|
|
173
|
+
this.status = status;
|
|
174
|
+
this.theme = theme;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
render(width: number): string[] {
|
|
178
|
+
const th = this.theme;
|
|
179
|
+
const cap = th.fg("dim", " ╰──");
|
|
180
|
+
const dot =
|
|
181
|
+
this.status === "success"
|
|
182
|
+
? th.fg("success", " ✓")
|
|
183
|
+
: this.status === "error"
|
|
184
|
+
? th.fg("error", " ✕")
|
|
185
|
+
: th.fg("dim", " ·");
|
|
186
|
+
const capVw = visibleWidth(cap + dot);
|
|
187
|
+
const textLen = Math.max(1, width - capVw - 1);
|
|
188
|
+
const text = " " + th.fg("muted", truncateToWidth(this.summary, textLen));
|
|
189
|
+
return [truncateToWidth(cap + dot + text, width)];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
invalidate(): void {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─── SciExpandedResult ───────────────────────────────────────────────────
|
|
196
|
+
//
|
|
197
|
+
// │ line 1
|
|
198
|
+
// │ line 2
|
|
199
|
+
// ╰── footer summary
|
|
200
|
+
|
|
201
|
+
export class SciExpandedResult implements SciComponent {
|
|
202
|
+
lines: string[];
|
|
203
|
+
footerSummary: string;
|
|
204
|
+
theme: Theme;
|
|
205
|
+
|
|
206
|
+
constructor(lines: string[], footerSummary: string, theme: Theme) {
|
|
207
|
+
this.lines = lines;
|
|
208
|
+
this.footerSummary = footerSummary;
|
|
209
|
+
this.theme = theme;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
render(width: number): string[] {
|
|
213
|
+
const th = this.theme;
|
|
214
|
+
const innerWidth = Math.max(1, width - 4);
|
|
215
|
+
const result: string[] = [];
|
|
216
|
+
for (const line of this.lines) {
|
|
217
|
+
result.push(th.fg("dim", " │") + " " + truncateToWidth(line, innerWidth));
|
|
218
|
+
}
|
|
219
|
+
result.push(
|
|
220
|
+
th.fg("dim", " ╰──") +
|
|
221
|
+
" " +
|
|
222
|
+
th.fg("muted", truncateToWidth(this.footerSummary, Math.max(1, width - 8))),
|
|
223
|
+
);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
invalidate(): void {}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── SciBanner (custom message renderer) ─────────────────────────────────
|
|
231
|
+
//
|
|
232
|
+
// ── ◈ label ──────────────────────────────────────────────────────────
|
|
233
|
+
// content line 1
|
|
234
|
+
|
|
235
|
+
export class SciBanner implements SciComponent {
|
|
236
|
+
glyph: string;
|
|
237
|
+
label: string;
|
|
238
|
+
contentLines: string[];
|
|
239
|
+
theme: Theme;
|
|
240
|
+
|
|
241
|
+
constructor(glyph: string, label: string, contentLines: string[], theme: Theme) {
|
|
242
|
+
this.glyph = glyph;
|
|
243
|
+
this.label = label;
|
|
244
|
+
this.contentLines = contentLines;
|
|
245
|
+
this.theme = theme;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
render(width: number): string[] {
|
|
249
|
+
const th = this.theme;
|
|
250
|
+
const midText = ` ${th.fg("accent", this.glyph)} ${th.fg("muted", this.label)} `;
|
|
251
|
+
const midVw = visibleWidth(midText);
|
|
252
|
+
const leftLen = 2;
|
|
253
|
+
const rightLen = Math.max(0, width - midVw - leftLen);
|
|
254
|
+
const header =
|
|
255
|
+
th.fg("dim", "──") +
|
|
256
|
+
midText +
|
|
257
|
+
th.fg("dim", "─".repeat(rightLen));
|
|
258
|
+
|
|
259
|
+
const result = [truncateToWidth(header, width)];
|
|
260
|
+
for (const line of this.contentLines) {
|
|
261
|
+
result.push(truncateToWidth(" " + line, width));
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
invalidate(): void {}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── Convenience builders ─────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
/** Build a SciCallLine from a tool name + summary string. */
|
|
272
|
+
export function sciCall(toolName: string, summary: string, theme: Theme): SciCallLine {
|
|
273
|
+
return new SciCallLine(glyphFor(toolName), toolName, summary, theme);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Build a SciLoadingLine for use during isPartial. */
|
|
277
|
+
export function sciLoading(toolName: string, theme: Theme): SciLoadingLine {
|
|
278
|
+
return new SciLoadingLine(toolName, theme);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Compact success result line. */
|
|
282
|
+
export function sciOk(summary: string, theme: Theme): SciResult {
|
|
283
|
+
return new SciResult(summary, "success", theme);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Compact error result line. */
|
|
287
|
+
export function sciErr(summary: string, theme: Theme): SciResult {
|
|
288
|
+
return new SciResult(summary, "error", theme);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Compact pending result line. */
|
|
292
|
+
export function sciPending(summary: string, theme: Theme): SciResult {
|
|
293
|
+
return new SciResult(summary, "pending", theme);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Expanded result with bordered body. */
|
|
297
|
+
export function sciExpanded(lines: string[], footer: string, theme: Theme): SciExpandedResult {
|
|
298
|
+
return new SciExpandedResult(lines, footer, theme);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Banner for message renderers. */
|
|
302
|
+
export function sciBanner(glyph: string, label: string, lines: string[], theme: Theme): SciBanner {
|
|
303
|
+
return new SciBanner(glyph, label, lines, theme);
|
|
304
|
+
}
|