@afromero/kin3o 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.
Files changed (45) hide show
  1. package/README.md +204 -0
  2. package/dist/brand.d.ts +39 -0
  3. package/dist/brand.js +63 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +249 -0
  6. package/dist/packager.d.ts +16 -0
  7. package/dist/packager.js +40 -0
  8. package/dist/preview.d.ts +2 -0
  9. package/dist/preview.js +30 -0
  10. package/dist/prompts/examples-interactive.d.ts +339 -0
  11. package/dist/prompts/examples-interactive.js +139 -0
  12. package/dist/prompts/examples-mascot.d.ts +765 -0
  13. package/dist/prompts/examples-mascot.js +319 -0
  14. package/dist/prompts/examples.d.ts +238 -0
  15. package/dist/prompts/examples.js +168 -0
  16. package/dist/prompts/index.d.ts +17 -0
  17. package/dist/prompts/index.js +21 -0
  18. package/dist/prompts/system-interactive.d.ts +2 -0
  19. package/dist/prompts/system-interactive.js +93 -0
  20. package/dist/prompts/system.d.ts +3 -0
  21. package/dist/prompts/system.js +94 -0
  22. package/dist/prompts/tokens.d.ts +6 -0
  23. package/dist/prompts/tokens.js +28 -0
  24. package/dist/providers/anthropic.d.ts +2 -0
  25. package/dist/providers/anthropic.js +25 -0
  26. package/dist/providers/claude.d.ts +2 -0
  27. package/dist/providers/claude.js +47 -0
  28. package/dist/providers/codex.d.ts +2 -0
  29. package/dist/providers/codex.js +60 -0
  30. package/dist/providers/registry.d.ts +18 -0
  31. package/dist/providers/registry.js +62 -0
  32. package/dist/state-machine-validator.d.ts +6 -0
  33. package/dist/state-machine-validator.js +182 -0
  34. package/dist/utils.d.ts +20 -0
  35. package/dist/utils.js +89 -0
  36. package/dist/validator.d.ts +8 -0
  37. package/dist/validator.js +195 -0
  38. package/examples/interactive-button.lottie +0 -0
  39. package/examples/mascot.json +760 -0
  40. package/examples/mascot.lottie +0 -0
  41. package/examples/pulse.json +75 -0
  42. package/examples/waveform.json +179 -0
  43. package/package.json +54 -0
  44. package/preview/template-interactive.html +223 -0
  45. package/preview/template.html +133 -0
@@ -0,0 +1,168 @@
1
+ /** Hand-crafted, validated Lottie JSON examples for few-shot prompting */
2
+ export const PULSING_CIRCLE = {
3
+ v: '5.5.2',
4
+ fr: 60,
5
+ ip: 0,
6
+ op: 120,
7
+ w: 512,
8
+ h: 512,
9
+ ddd: 0,
10
+ assets: [],
11
+ layers: [
12
+ {
13
+ ty: 4,
14
+ ind: 0,
15
+ nm: 'Pulsing Circle',
16
+ ip: 0,
17
+ op: 120,
18
+ st: 0,
19
+ ddd: 0,
20
+ ks: {
21
+ a: { a: 0, k: [0, 0] },
22
+ p: { a: 0, k: [256, 256] },
23
+ s: {
24
+ a: 1,
25
+ k: [
26
+ {
27
+ t: 0,
28
+ s: [80, 80],
29
+ o: { x: [0.42], y: [0] },
30
+ i: { x: [0.58], y: [1] },
31
+ },
32
+ {
33
+ t: 60,
34
+ s: [120, 120],
35
+ o: { x: [0.42], y: [0] },
36
+ i: { x: [0.58], y: [1] },
37
+ },
38
+ { t: 120, s: [80, 80] },
39
+ ],
40
+ },
41
+ r: { a: 0, k: 0 },
42
+ o: { a: 0, k: 100 },
43
+ },
44
+ shapes: [
45
+ {
46
+ ty: 'gr',
47
+ nm: 'Circle Group',
48
+ it: [
49
+ {
50
+ ty: 'el',
51
+ nm: 'Ellipse',
52
+ p: { a: 0, k: [0, 0] },
53
+ s: { a: 0, k: [200, 200] },
54
+ },
55
+ {
56
+ ty: 'fl',
57
+ nm: 'Fill',
58
+ c: { a: 0, k: [0.851, 0.467, 0.024, 1] },
59
+ o: { a: 0, k: 100 },
60
+ r: 1,
61
+ },
62
+ {
63
+ ty: 'tr',
64
+ p: { a: 0, k: [0, 0] },
65
+ a: { a: 0, k: [0, 0] },
66
+ s: { a: 0, k: [100, 100] },
67
+ r: { a: 0, k: 0 },
68
+ o: { a: 0, k: 100 },
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ bm: 0,
74
+ },
75
+ ],
76
+ };
77
+ export const WAVEFORM_BARS = {
78
+ v: '5.5.2',
79
+ fr: 60,
80
+ ip: 0,
81
+ op: 120,
82
+ w: 512,
83
+ h: 512,
84
+ ddd: 0,
85
+ assets: [],
86
+ layers: [
87
+ ...[0, 1, 2].map((i) => {
88
+ const x = 206 + i * 50;
89
+ const delay = i * 15;
90
+ return {
91
+ ty: 4,
92
+ ind: i,
93
+ nm: `Bar ${i + 1}`,
94
+ ip: 0,
95
+ op: 120,
96
+ st: 0,
97
+ ddd: 0,
98
+ ks: {
99
+ a: { a: 0, k: [0, 0] },
100
+ p: { a: 0, k: [x, 256] },
101
+ s: { a: 0, k: [100, 100] },
102
+ r: { a: 0, k: 0 },
103
+ o: { a: 0, k: 100 },
104
+ },
105
+ shapes: [
106
+ {
107
+ ty: 'gr',
108
+ nm: `Bar Group ${i + 1}`,
109
+ it: [
110
+ {
111
+ ty: 'rc',
112
+ nm: 'Rectangle',
113
+ p: { a: 0, k: [0, 0] },
114
+ s: {
115
+ a: 1,
116
+ k: [
117
+ {
118
+ t: delay,
119
+ s: [30, 40],
120
+ o: { x: [0.42], y: [0] },
121
+ i: { x: [0.58], y: [1] },
122
+ },
123
+ {
124
+ t: 30 + delay,
125
+ s: [30, 160],
126
+ o: { x: [0.42], y: [0] },
127
+ i: { x: [0.58], y: [1] },
128
+ },
129
+ {
130
+ t: 60 + delay,
131
+ s: [30, 40],
132
+ o: { x: [0.42], y: [0] },
133
+ i: { x: [0.58], y: [1] },
134
+ },
135
+ {
136
+ t: 90 + delay,
137
+ s: [30, 120],
138
+ o: { x: [0.42], y: [0] },
139
+ i: { x: [0.58], y: [1] },
140
+ },
141
+ { t: 120, s: [30, 40] },
142
+ ],
143
+ },
144
+ r: { a: 0, k: 4 },
145
+ },
146
+ {
147
+ ty: 'fl',
148
+ nm: 'Fill',
149
+ c: { a: 0, k: [0.851, 0.467, 0.024, 1] },
150
+ o: { a: 0, k: 100 },
151
+ r: 1,
152
+ },
153
+ {
154
+ ty: 'tr',
155
+ p: { a: 0, k: [0, 0] },
156
+ a: { a: 0, k: [0, 0] },
157
+ s: { a: 0, k: [100, 100] },
158
+ r: { a: 0, k: 0 },
159
+ o: { a: 0, k: 100 },
160
+ },
161
+ ],
162
+ },
163
+ ],
164
+ bm: 0,
165
+ };
166
+ }),
167
+ ],
168
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Prompt Registry — single entry point for all system prompts.
3
+ *
4
+ * Static mode: buildSystemPrompt() → single Lottie JSON animation
5
+ * Interactive: buildInteractiveSystemPrompt() → multi-animation envelope + state machine
6
+ *
7
+ * Both share LOTTIE_FORMAT_REFERENCE for consistent Lottie spec knowledge.
8
+ * Each mode has its own few-shot examples tuned for that output format.
9
+ */
10
+ export { LOTTIE_FORMAT_REFERENCE } from './system.js';
11
+ export { buildSystemPrompt } from './system.js';
12
+ export { PULSING_CIRCLE, WAVEFORM_BARS } from './examples.js';
13
+ export { buildInteractiveSystemPrompt } from './system-interactive.js';
14
+ export { INTERACTIVE_BUTTON } from './examples-interactive.js';
15
+ export { MASCOT_STATIC, MASCOT_INTERACTIVE } from './examples-mascot.js';
16
+ export { loadDesignTokens, SOTTO_TOKENS } from './tokens.js';
17
+ export type { DesignTokens } from './tokens.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Prompt Registry — single entry point for all system prompts.
3
+ *
4
+ * Static mode: buildSystemPrompt() → single Lottie JSON animation
5
+ * Interactive: buildInteractiveSystemPrompt() → multi-animation envelope + state machine
6
+ *
7
+ * Both share LOTTIE_FORMAT_REFERENCE for consistent Lottie spec knowledge.
8
+ * Each mode has its own few-shot examples tuned for that output format.
9
+ */
10
+ // ── Shared ──
11
+ export { LOTTIE_FORMAT_REFERENCE } from './system.js';
12
+ // ── Static mode ──
13
+ export { buildSystemPrompt } from './system.js';
14
+ export { PULSING_CIRCLE, WAVEFORM_BARS } from './examples.js';
15
+ // ── Interactive mode ──
16
+ export { buildInteractiveSystemPrompt } from './system-interactive.js';
17
+ export { INTERACTIVE_BUTTON } from './examples-interactive.js';
18
+ // ── Mascot ──
19
+ export { MASCOT_STATIC, MASCOT_INTERACTIVE } from './examples-mascot.js';
20
+ // ── Design tokens ──
21
+ export { loadDesignTokens, SOTTO_TOKENS } from './tokens.js';
@@ -0,0 +1,2 @@
1
+ import type { DesignTokens } from './tokens.js';
2
+ export declare function buildInteractiveSystemPrompt(tokens?: DesignTokens): string;
@@ -0,0 +1,93 @@
1
+ import { LOTTIE_FORMAT_REFERENCE } from './system.js';
2
+ import { INTERACTIVE_BUTTON } from './examples-interactive.js';
3
+ export function buildInteractiveSystemPrompt(tokens) {
4
+ const sections = [];
5
+ // 1. Role + output rules
6
+ sections.push(`You are an interactive Lottie animation generator. You create multiple Lottie animations plus a dotLottie state machine that wires them together with user interactions (hover, click, etc.).
7
+
8
+ Output ONLY valid JSON matching the envelope format below. No markdown fences, no explanation, no commentary. Your output must be directly parseable by JSON.parse().`);
9
+ // 2. Shared Lottie format reference
10
+ sections.push(LOTTIE_FORMAT_REFERENCE);
11
+ // 3. dotLottie state machine spec
12
+ sections.push(`
13
+ DOTLOTTIE STATE MACHINE FORMAT:
14
+
15
+ The state machine controls transitions between animations based on user interactions.
16
+
17
+ Output envelope format:
18
+ {
19
+ "animations": {
20
+ "<id>": { <complete Lottie JSON> },
21
+ "<id>": { <complete Lottie JSON> }
22
+ },
23
+ "stateMachine": {
24
+ "initial": "<state name>",
25
+ "states": [ ... ],
26
+ "interactions": [ ... ],
27
+ "inputs": [ ... ]
28
+ }
29
+ }
30
+
31
+ State types:
32
+ - "PlaybackState": plays an animation. Fields: "name", "type", "animation" (animation ID), "transitions" (array)
33
+ - "GlobalState": listens globally. Fields: "name", "type", "transitions" (array)
34
+
35
+ Transition format:
36
+ { "type": "Transition", "toState": "<target state name>", "guards": [ ... ] }
37
+
38
+ Guard format (conditional transition):
39
+ - Boolean: { "type": "Boolean", "inputName": "<name>", "conditionType": "Equal", "compareTo": true/false }
40
+ - Numeric: { "type": "Numeric", "inputName": "<name>", "conditionType": "Equal"|"GreaterThan"|"LessThan", "compareTo": <number> }
41
+ - String: { "type": "String", "inputName": "<name>", "conditionType": "Equal", "compareTo": "<value>" }
42
+
43
+ Interaction types (user events that set inputs):
44
+ - "PointerDown": mouse click / tap
45
+ - "PointerUp": mouse release
46
+ - "PointerEnter": mouse hover enter
47
+ - "PointerExit": mouse hover leave
48
+ - "OnComplete": animation playback completes (requires "stateName" field)
49
+ - "OnLoopComplete": animation loop completes (requires "stateName" field)
50
+
51
+ Interaction format:
52
+ { "type": "<event type>", "actions": [{ "type": "<action type>", "inputName": "<input name>", "value": <value> }] }
53
+ For OnComplete/OnLoopComplete add "stateName": "<which state this applies to>"
54
+
55
+ Action types:
56
+ - "SetBoolean": set a boolean input { "type": "SetBoolean", "inputName": "...", "value": true/false }
57
+ - "Toggle": toggle a boolean input { "type": "Toggle", "inputName": "..." }
58
+ - "Increment": increment a numeric input { "type": "Increment", "inputName": "...", "value": 1 }
59
+ - "Decrement": decrement a numeric input { "type": "Decrement", "inputName": "...", "value": 1 }
60
+ - "SetString": set a string input { "type": "SetString", "inputName": "...", "value": "..." }
61
+ - "SetNumeric": set a numeric input { "type": "SetNumeric", "inputName": "...", "value": 0 }
62
+
63
+ Input types:
64
+ - "Boolean": { "name": "<name>", "type": "Boolean", "value": <default bool> }
65
+ - "Numeric": { "name": "<name>", "type": "Numeric", "value": <default number> }
66
+ - "String": { "name": "<name>", "type": "String", "value": "<default string>" }`);
67
+ // 4. Design rules
68
+ sections.push(`
69
+ INTERACTIVE DESIGN RULES:
70
+ - Keep individual animations SHORT: 60-120 frames (1-2 seconds at 60fps)
71
+ - Maximum 4 states per state machine
72
+ - Each animation: 512x512 canvas, 60fps, shape layers only
73
+ - Animations should be distinct but visually cohesive (same style, similar colors)
74
+ - Use ease-in-out easing by default
75
+ - Make loopable animations where appropriate (idle states)
76
+ - Always include "ddd": 0 and "assets": []
77
+ - Colors: 0-1 floats (NOT 0-255)
78
+ - Groups MUST end with "tr" transform`);
79
+ // 5. Design tokens
80
+ if (tokens) {
81
+ const colorLines = Object.entries(tokens.colors)
82
+ .map(([name, rgba]) => ` ${name}: [${rgba.join(', ')}]`)
83
+ .join('\n');
84
+ sections.push(`
85
+ DESIGN TOKENS — use these colors when appropriate:
86
+ ${colorLines}`);
87
+ }
88
+ // 6. Few-shot example
89
+ sections.push(`
90
+ EXAMPLE — Interactive button (idle/hover/pressed states):
91
+ ${JSON.stringify(INTERACTIVE_BUTTON)}`);
92
+ return sections.join('\n');
93
+ }
@@ -0,0 +1,3 @@
1
+ import type { DesignTokens } from './tokens.js';
2
+ export declare const LOTTIE_FORMAT_REFERENCE = "\nLOTTIE FORMAT REFERENCE:\n\nTop-level required fields:\n- \"v\": string (use \"5.5.2\")\n- \"fr\": number (use 60 for 60fps)\n- \"ip\": 0 (in-point, always 0)\n- \"op\": number (out-point, end frame \u2014 e.g. 120 for 2s at 60fps)\n- \"w\": number (width in pixels)\n- \"h\": number (height in pixels)\n- \"ddd\": 0 (no 3D)\n- \"assets\": [] (empty array)\n- \"layers\": array of layer objects\n\nLayer (shape layer, ty=4):\n- \"ty\": 4 (shape layer)\n- \"ind\": number (unique index)\n- \"nm\": string (layer name)\n- \"ip\": 0, \"op\": same as top-level op\n- \"st\": 0 (start time)\n- \"ddd\": 0\n- \"ks\": transform object\n- \"shapes\": array of shape objects\n- \"bm\": 0 (blend mode, normal)\n\nTransform \"ks\" object:\n- \"a\": anchor point (VECTOR property)\n- \"p\": position (VECTOR property)\n- \"s\": scale (VECTOR property, [100,100] = 100%)\n- \"r\": rotation (SCALAR property, degrees)\n- \"o\": opacity (SCALAR property, 0-100)\n\nPROPERTY TYPES \u2014 this distinction is critical:\n- VECTOR property (position, scale, anchor, size): {\"a\":0,\"k\":[x,y]} or animated {\"a\":1,\"k\":[keyframes]}\n- SCALAR property (rotation, opacity, roundness, stroke width): {\"a\":0,\"k\":0} or animated {\"a\":1,\"k\":[keyframes]}\n- COLOR property: {\"a\":0,\"k\":[r,g,b,1]} with 0-1 floats (NOT 0-255)\n\nKeyframe format:\n{\"t\":frame,\"s\":[values],\"o\":{\"x\":[n],\"y\":[n]},\"i\":{\"x\":[n],\"y\":[n]}}\n- \"t\": frame number\n- \"s\": start values (array for vector, array with single value for scalar)\n- \"o\": out-tangent (ease out), \"i\": in-tangent (ease in)\n- Last keyframe needs only \"t\" and \"s\" (no tangents)\n\nEasing (ease-in-out): \"o\":{\"x\":[0.42],\"y\":[0]}, \"i\":{\"x\":[0.58],\"y\":[1]}\n\nShape types:\n- \"el\": ellipse \u2014 \"p\" (center, vector), \"s\" (size, vector)\n- \"rc\": rectangle \u2014 \"p\" (center, vector), \"s\" (size, vector), \"r\" (roundness, scalar)\n- \"sh\": path \u2014 \"ks\" with bezier {\"c\":bool, \"v\":[[x,y],...], \"i\":[[dx,dy],...], \"o\":[[dx,dy],...]}\n- \"fl\": fill \u2014 \"c\" (color), \"o\" (opacity, scalar), \"r\" (fill rule, 1=nonzero)\n- \"st\": stroke \u2014 \"c\" (color), \"o\" (opacity), \"w\" (width, scalar), \"lc\" (line cap, 2=round), \"lj\" (line join, 2=round)\n- \"gr\": group \u2014 \"it\" array of shapes + MUST end with \"tr\" (group transform)\n- \"tr\": group transform \u2014 same fields as layer transform \"ks\"\n- \"tm\": trim path \u2014 \"s\" (start%, scalar), \"e\" (end%, scalar), \"o\" (offset, scalar)\n\nCRITICAL: Groups MUST have \"tr\" (transform) as the LAST item in their \"it\" array.";
3
+ export declare function buildSystemPrompt(tokens?: DesignTokens): string;
@@ -0,0 +1,94 @@
1
+ import { PULSING_CIRCLE, WAVEFORM_BARS } from './examples.js';
2
+ export const LOTTIE_FORMAT_REFERENCE = `
3
+ LOTTIE FORMAT REFERENCE:
4
+
5
+ Top-level required fields:
6
+ - "v": string (use "5.5.2")
7
+ - "fr": number (use 60 for 60fps)
8
+ - "ip": 0 (in-point, always 0)
9
+ - "op": number (out-point, end frame — e.g. 120 for 2s at 60fps)
10
+ - "w": number (width in pixels)
11
+ - "h": number (height in pixels)
12
+ - "ddd": 0 (no 3D)
13
+ - "assets": [] (empty array)
14
+ - "layers": array of layer objects
15
+
16
+ Layer (shape layer, ty=4):
17
+ - "ty": 4 (shape layer)
18
+ - "ind": number (unique index)
19
+ - "nm": string (layer name)
20
+ - "ip": 0, "op": same as top-level op
21
+ - "st": 0 (start time)
22
+ - "ddd": 0
23
+ - "ks": transform object
24
+ - "shapes": array of shape objects
25
+ - "bm": 0 (blend mode, normal)
26
+
27
+ Transform "ks" object:
28
+ - "a": anchor point (VECTOR property)
29
+ - "p": position (VECTOR property)
30
+ - "s": scale (VECTOR property, [100,100] = 100%)
31
+ - "r": rotation (SCALAR property, degrees)
32
+ - "o": opacity (SCALAR property, 0-100)
33
+
34
+ PROPERTY TYPES — this distinction is critical:
35
+ - VECTOR property (position, scale, anchor, size): {"a":0,"k":[x,y]} or animated {"a":1,"k":[keyframes]}
36
+ - SCALAR property (rotation, opacity, roundness, stroke width): {"a":0,"k":0} or animated {"a":1,"k":[keyframes]}
37
+ - COLOR property: {"a":0,"k":[r,g,b,1]} with 0-1 floats (NOT 0-255)
38
+
39
+ Keyframe format:
40
+ {"t":frame,"s":[values],"o":{"x":[n],"y":[n]},"i":{"x":[n],"y":[n]}}
41
+ - "t": frame number
42
+ - "s": start values (array for vector, array with single value for scalar)
43
+ - "o": out-tangent (ease out), "i": in-tangent (ease in)
44
+ - Last keyframe needs only "t" and "s" (no tangents)
45
+
46
+ Easing (ease-in-out): "o":{"x":[0.42],"y":[0]}, "i":{"x":[0.58],"y":[1]}
47
+
48
+ Shape types:
49
+ - "el": ellipse — "p" (center, vector), "s" (size, vector)
50
+ - "rc": rectangle — "p" (center, vector), "s" (size, vector), "r" (roundness, scalar)
51
+ - "sh": path — "ks" with bezier {"c":bool, "v":[[x,y],...], "i":[[dx,dy],...], "o":[[dx,dy],...]}
52
+ - "fl": fill — "c" (color), "o" (opacity, scalar), "r" (fill rule, 1=nonzero)
53
+ - "st": stroke — "c" (color), "o" (opacity), "w" (width, scalar), "lc" (line cap, 2=round), "lj" (line join, 2=round)
54
+ - "gr": group — "it" array of shapes + MUST end with "tr" (group transform)
55
+ - "tr": group transform — same fields as layer transform "ks"
56
+ - "tm": trim path — "s" (start%, scalar), "e" (end%, scalar), "o" (offset, scalar)
57
+
58
+ CRITICAL: Groups MUST have "tr" (transform) as the LAST item in their "it" array.`;
59
+ export function buildSystemPrompt(tokens) {
60
+ const sections = [];
61
+ // 1. Role + output rules
62
+ sections.push(`You are a Lottie animation generator. Output ONLY valid JSON. No markdown fences, no explanation, no commentary. Your output must be directly parseable by JSON.parse().`);
63
+ // 2. Lottie format spec
64
+ sections.push(LOTTIE_FORMAT_REFERENCE);
65
+ // 3. Design rules
66
+ sections.push(`
67
+ DESIGN RULES:
68
+ - Frame rate: 60fps
69
+ - Duration: 120-180 frames (2-3 seconds)
70
+ - Canvas: 512x512 pixels, center at [256, 256]
71
+ - Loopable: last keyframe values must match first keyframe values
72
+ - Use ease-in-out easing by default
73
+ - Shape layers only (ty=4)
74
+ - Always include "ddd": 0 and "assets": []
75
+ - Keep animations smooth and proportional
76
+ - Use distinct colors for different elements`);
77
+ // 4. Design tokens (conditional)
78
+ if (tokens) {
79
+ const colorLines = Object.entries(tokens.colors)
80
+ .map(([name, rgba]) => ` ${name}: [${rgba.join(', ')}]`)
81
+ .join('\n');
82
+ sections.push(`
83
+ DESIGN TOKENS — use these colors when appropriate:
84
+ ${colorLines}`);
85
+ }
86
+ // 5. Few-shot examples
87
+ sections.push(`
88
+ EXAMPLE 1 — Pulsing circle (scale animation):
89
+ ${JSON.stringify(PULSING_CIRCLE)}
90
+
91
+ EXAMPLE 2 — 3-bar waveform (staggered position animation):
92
+ ${JSON.stringify(WAVEFORM_BARS)}`);
93
+ return sections.join('\n');
94
+ }
@@ -0,0 +1,6 @@
1
+ export interface DesignTokens {
2
+ colors: Record<string, [number, number, number, number]>;
3
+ }
4
+ export declare const SOTTO_TOKENS: DesignTokens;
5
+ /** Load design tokens from a JSON file, converting hex colors to Lottie RGBA */
6
+ export declare function loadDesignTokens(path?: string): DesignTokens | undefined;
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { hexToLottieColor } from '../utils.js';
3
+ export const SOTTO_TOKENS = {
4
+ colors: {
5
+ primary: hexToLottieColor('#D97706'),
6
+ accent: hexToLottieColor('#1E3A5F'),
7
+ background: hexToLottieColor('#FEFCF8'),
8
+ surface: hexToLottieColor('#FFFFFF'),
9
+ text: hexToLottieColor('#1A1A1A'),
10
+ muted: hexToLottieColor('#6B7280'),
11
+ },
12
+ };
13
+ /** Load design tokens from a JSON file, converting hex colors to Lottie RGBA */
14
+ export function loadDesignTokens(path) {
15
+ if (!path)
16
+ return undefined;
17
+ if (path === 'sotto')
18
+ return SOTTO_TOKENS;
19
+ const raw = readFileSync(path, 'utf-8');
20
+ const data = JSON.parse(raw);
21
+ if (!data.colors)
22
+ return undefined;
23
+ const colors = {};
24
+ for (const [name, hex] of Object.entries(data.colors)) {
25
+ colors[name] = hexToLottieColor(hex);
26
+ }
27
+ return { colors };
28
+ }
@@ -0,0 +1,2 @@
1
+ import type { GenerationResult } from './registry.js';
2
+ export declare function generateWithAnthropic(model: string, systemPrompt: string, userPrompt: string): Promise<GenerationResult>;
@@ -0,0 +1,25 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ export async function generateWithAnthropic(model, systemPrompt, userPrompt) {
3
+ const apiKey = process.env['ANTHROPIC_API_KEY'];
4
+ if (!apiKey) {
5
+ throw new Error('ANTHROPIC_API_KEY environment variable is required');
6
+ }
7
+ const start = Date.now();
8
+ const client = new Anthropic({ apiKey });
9
+ const response = await client.messages.create({
10
+ model,
11
+ max_tokens: 16384,
12
+ system: systemPrompt,
13
+ messages: [{ role: 'user', content: userPrompt }],
14
+ });
15
+ const text = response.content
16
+ .filter((block) => block.type === 'text')
17
+ .map((block) => block.text)
18
+ .join('');
19
+ return {
20
+ content: text,
21
+ provider: 'anthropic',
22
+ model,
23
+ durationMs: Date.now() - start,
24
+ };
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { GenerationResult } from './registry.js';
2
+ export declare function generateWithClaude(model: string, systemPrompt: string, userPrompt: string): Promise<GenerationResult>;
@@ -0,0 +1,47 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { filterCliStderr } from '../utils.js';
3
+ export async function generateWithClaude(model, systemPrompt, userPrompt) {
4
+ const start = Date.now();
5
+ const fullPrompt = `${systemPrompt}\n\n${userPrompt}`;
6
+ const content = await new Promise((resolve, reject) => {
7
+ const env = { ...process.env };
8
+ delete env.ANTHROPIC_API_KEY;
9
+ const proc = spawn('claude', ['--print', '--model', model], {
10
+ timeout: 240_000,
11
+ env,
12
+ });
13
+ let stdout = '';
14
+ let stderr = '';
15
+ proc.stdout.on('data', (d) => {
16
+ stdout += d.toString();
17
+ });
18
+ proc.stderr.on('data', (d) => {
19
+ stderr += d.toString();
20
+ });
21
+ proc.on('close', (code) => {
22
+ if (code !== 0) {
23
+ const filtered = filterCliStderr(stderr);
24
+ reject(new Error(`claude CLI exited ${code}: ${filtered}`));
25
+ }
26
+ else {
27
+ resolve(stdout.trim());
28
+ }
29
+ });
30
+ proc.on('error', (err) => {
31
+ if (err.code === 'ENOENT') {
32
+ reject(new Error('claude CLI not found. Install Claude Code first.'));
33
+ }
34
+ else {
35
+ reject(err);
36
+ }
37
+ });
38
+ proc.stdin.write(fullPrompt);
39
+ proc.stdin.end();
40
+ });
41
+ return {
42
+ content,
43
+ provider: 'claude-code',
44
+ model,
45
+ durationMs: Date.now() - start,
46
+ };
47
+ }
@@ -0,0 +1,2 @@
1
+ import type { GenerationResult } from './registry.js';
2
+ export declare function generateWithCodex(model: string, systemPrompt: string, userPrompt: string): Promise<GenerationResult>;
@@ -0,0 +1,60 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, unlinkSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { filterCliStderr } from '../utils.js';
6
+ export async function generateWithCodex(model, systemPrompt, userPrompt) {
7
+ const start = Date.now();
8
+ const fullPrompt = `${systemPrompt}\n\n${userPrompt}`;
9
+ const tmpFile = join(mkdtempSync(join(tmpdir(), 'codex-')), 'output.txt');
10
+ const args = [
11
+ 'exec', '-',
12
+ '--skip-git-repo-check',
13
+ '--ephemeral',
14
+ '-o', tmpFile,
15
+ '--model', model,
16
+ ];
17
+ const content = await new Promise((resolve, reject) => {
18
+ const proc = spawn('codex', args, {
19
+ timeout: 240_000,
20
+ env: { ...process.env },
21
+ });
22
+ let stderr = '';
23
+ proc.stderr.on('data', (d) => {
24
+ stderr += d.toString();
25
+ });
26
+ proc.on('close', (code) => {
27
+ const filtered = filterCliStderr(stderr);
28
+ const hint = filtered.includes('401') || filtered.includes('Unauthorized')
29
+ ? ' (ensure codex is authenticated via `codex auth`)'
30
+ : '';
31
+ try {
32
+ const output = readFileSync(tmpFile, 'utf-8').trim();
33
+ unlinkSync(tmpFile);
34
+ if (code !== 0)
35
+ reject(new Error(`codex CLI exited ${code}: ${filtered}${hint}`));
36
+ else
37
+ resolve(output);
38
+ }
39
+ catch {
40
+ reject(new Error(`codex CLI exited ${code}: ${filtered}${hint}`));
41
+ }
42
+ });
43
+ proc.on('error', (err) => {
44
+ if (err.code === 'ENOENT') {
45
+ reject(new Error('codex CLI not found. Install Codex first.'));
46
+ }
47
+ else {
48
+ reject(err);
49
+ }
50
+ });
51
+ proc.stdin.write(fullPrompt);
52
+ proc.stdin.end();
53
+ });
54
+ return {
55
+ content,
56
+ provider: 'codex',
57
+ model,
58
+ durationMs: Date.now() - start,
59
+ };
60
+ }
@@ -0,0 +1,18 @@
1
+ export interface GenerationResult {
2
+ content: string;
3
+ provider: string;
4
+ model: string;
5
+ durationMs: number;
6
+ }
7
+ export interface ProviderConfig {
8
+ displayName: string;
9
+ models: string[];
10
+ defaultModel: string;
11
+ isAvailable: () => Promise<boolean>;
12
+ generate: (model: string, systemPrompt: string, userPrompt: string) => Promise<GenerationResult>;
13
+ }
14
+ export declare const PROVIDERS: Record<string, ProviderConfig>;
15
+ /** Detect which providers are available (binary + auth) */
16
+ export declare function detectAvailableProviders(): Promise<string[]>;
17
+ /** Get default provider (first available in priority order) */
18
+ export declare function getDefaultProvider(): Promise<string | null>;