tryassay 0.21.1 → 0.22.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/README.md +4 -4
- package/demo/.claude/.truth_last_prompt +1 -0
- package/demo/.claude/truth_status +1 -0
- package/demo/css/style.css +1181 -0
- package/demo/data/demo-events.json +103 -0
- package/demo/index.html +222 -0
- package/demo/js/chat.js +292 -0
- package/demo/js/code-panel.js +206 -0
- package/demo/js/demo-mode.js +107 -0
- package/demo/js/orb.js +634 -0
- package/demo/js/question-cards.js +207 -0
- package/demo/js/sse-client.js +473 -0
- package/demo/js/state.js +162 -0
- package/demo/js/timeline.js +394 -0
- package/demo/js/voice.js +154 -0
- package/dist/api/server.d.ts +1 -0
- package/dist/api/server.js +65 -2
- package/dist/api/server.js.map +1 -1
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/demo.d.ts +5 -0
- package/dist/commands/demo.js +107 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/runtime.d.ts +4 -0
- package/dist/commands/runtime.js +50 -3
- package/dist/commands/runtime.js.map +1 -1
- package/dist/runtime/agents/planner-agent.d.ts +5 -2
- package/dist/runtime/agents/planner-agent.js +232 -1
- package/dist/runtime/agents/planner-agent.js.map +1 -1
- package/dist/runtime/app-create-orchestrator.d.ts +4 -0
- package/dist/runtime/app-create-orchestrator.js +151 -48
- package/dist/runtime/app-create-orchestrator.js.map +1 -1
- package/dist/runtime/dashboard-sync.d.ts +25 -0
- package/dist/runtime/dashboard-sync.js +169 -0
- package/dist/runtime/dashboard-sync.js.map +1 -0
- package/dist/runtime/types.d.ts +28 -0
- package/package.json +3 -2
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// code-panel.js — Bottom streaming code display with typewriter effect
|
|
2
|
+
// Subscribes to AppState code_generated events, renders tabs + syntax-highlighted code
|
|
3
|
+
|
|
4
|
+
const files = []; // { path, code }
|
|
5
|
+
let activeIndex = -1;
|
|
6
|
+
let typewriterRaf = 0; // requestAnimationFrame ID
|
|
7
|
+
|
|
8
|
+
const KEYWORDS = new Set([
|
|
9
|
+
'const', 'let', 'var', 'function', 'return', 'import', 'export', 'from',
|
|
10
|
+
'if', 'else', 'async', 'await', 'class', 'extends', 'new', 'this',
|
|
11
|
+
'throw', 'try', 'catch', 'typeof', 'interface', 'type'
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const BUILDING_PHASES = new Set(['building_feature', 'scaffolding']);
|
|
15
|
+
|
|
16
|
+
function highlightSyntax(code) {
|
|
17
|
+
let result = '';
|
|
18
|
+
let i = 0;
|
|
19
|
+
const len = code.length;
|
|
20
|
+
|
|
21
|
+
while (i < len) {
|
|
22
|
+
// Comments: // to end of line
|
|
23
|
+
if (code[i] === '/' && code[i + 1] === '/') {
|
|
24
|
+
const end = code.indexOf('\n', i);
|
|
25
|
+
const slice = end === -1 ? code.slice(i) : code.slice(i, end);
|
|
26
|
+
result += `<span class="comment">${escapeHtml(slice)}</span>`;
|
|
27
|
+
i += slice.length;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Strings: single or double quoted
|
|
32
|
+
if (code[i] === '"' || code[i] === "'") {
|
|
33
|
+
const quote = code[i];
|
|
34
|
+
let j = i + 1;
|
|
35
|
+
while (j < len && code[j] !== quote) {
|
|
36
|
+
if (code[j] === '\\') j++; // skip escaped chars
|
|
37
|
+
j++;
|
|
38
|
+
}
|
|
39
|
+
j++; // include closing quote
|
|
40
|
+
result += `<span class="string">${escapeHtml(code.slice(i, j))}</span>`;
|
|
41
|
+
i = j;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Template literals
|
|
46
|
+
if (code[i] === '`') {
|
|
47
|
+
let j = i + 1;
|
|
48
|
+
while (j < len && code[j] !== '`') {
|
|
49
|
+
if (code[j] === '\\') j++;
|
|
50
|
+
j++;
|
|
51
|
+
}
|
|
52
|
+
j++;
|
|
53
|
+
result += `<span class="string">${escapeHtml(code.slice(i, j))}</span>`;
|
|
54
|
+
i = j;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Numbers
|
|
59
|
+
if (/\d/.test(code[i]) && (i === 0 || /[\s,;:=+\-*/([\]{}<>!&|^~?]/.test(code[i - 1]))) {
|
|
60
|
+
let j = i;
|
|
61
|
+
while (j < len && /[\d.xXa-fA-F_]/.test(code[j])) j++;
|
|
62
|
+
result += `<span class="number">${escapeHtml(code.slice(i, j))}</span>`;
|
|
63
|
+
i = j;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Words (potential keywords)
|
|
68
|
+
if (/[a-zA-Z_$]/.test(code[i])) {
|
|
69
|
+
let j = i;
|
|
70
|
+
while (j < len && /[a-zA-Z0-9_$]/.test(code[j])) j++;
|
|
71
|
+
const word = code.slice(i, j);
|
|
72
|
+
if (KEYWORDS.has(word)) {
|
|
73
|
+
result += `<span class="keyword">${word}</span>`;
|
|
74
|
+
} else {
|
|
75
|
+
result += escapeHtml(word);
|
|
76
|
+
}
|
|
77
|
+
i = j;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Everything else
|
|
82
|
+
result += escapeHtml(code[i]);
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function escapeHtml(str) {
|
|
90
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function renderTabs() {
|
|
94
|
+
const tabsEl = document.getElementById('code-tabs');
|
|
95
|
+
if (!tabsEl) return;
|
|
96
|
+
|
|
97
|
+
tabsEl.innerHTML = '';
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < files.length; i++) {
|
|
100
|
+
const btn = document.createElement('button');
|
|
101
|
+
btn.className = 'code-tab';
|
|
102
|
+
if (i === activeIndex) btn.classList.add('active');
|
|
103
|
+
// Show just the filename, not the full path
|
|
104
|
+
const name = files[i].path.split('/').pop();
|
|
105
|
+
btn.textContent = name;
|
|
106
|
+
btn.addEventListener('click', () => showFile(i));
|
|
107
|
+
tabsEl.appendChild(btn);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function showFile(index) {
|
|
112
|
+
if (index < 0 || index >= files.length) return;
|
|
113
|
+
activeIndex = index;
|
|
114
|
+
renderTabs();
|
|
115
|
+
startTypewriter(files[index].code);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function startTypewriter(code) {
|
|
119
|
+
const contentEl = document.getElementById('code-content');
|
|
120
|
+
if (!contentEl) return;
|
|
121
|
+
|
|
122
|
+
// Cancel any ongoing typewriter
|
|
123
|
+
if (typewriterRaf) cancelAnimationFrame(typewriterRaf);
|
|
124
|
+
|
|
125
|
+
const highlighted = highlightSyntax(code);
|
|
126
|
+
let charIndex = 0;
|
|
127
|
+
const charsPerFrame = 30;
|
|
128
|
+
|
|
129
|
+
contentEl.innerHTML = '';
|
|
130
|
+
contentEl.classList.add('cursor-blink');
|
|
131
|
+
|
|
132
|
+
function tick() {
|
|
133
|
+
charIndex += charsPerFrame;
|
|
134
|
+
if (charIndex >= highlighted.length) {
|
|
135
|
+
contentEl.innerHTML = highlighted;
|
|
136
|
+
contentEl.classList.remove('cursor-blink');
|
|
137
|
+
typewriterRaf = 0;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Render up to charIndex, but don't break inside HTML tags
|
|
141
|
+
let safeEnd = charIndex;
|
|
142
|
+
const openBracket = highlighted.lastIndexOf('<', safeEnd);
|
|
143
|
+
const closeBracket = highlighted.lastIndexOf('>', safeEnd);
|
|
144
|
+
if (openBracket > closeBracket) {
|
|
145
|
+
// We're inside a tag — extend to close it
|
|
146
|
+
const nextClose = highlighted.indexOf('>', safeEnd);
|
|
147
|
+
if (nextClose !== -1) safeEnd = nextClose + 1;
|
|
148
|
+
}
|
|
149
|
+
contentEl.innerHTML = highlighted.slice(0, safeEnd);
|
|
150
|
+
typewriterRaf = requestAnimationFrame(tick);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
typewriterRaf = requestAnimationFrame(tick);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function updatePanelVisibility(phase) {
|
|
157
|
+
const panel = document.getElementById('code-panel');
|
|
158
|
+
if (!panel) return;
|
|
159
|
+
if (BUILDING_PHASES.has(phase)) {
|
|
160
|
+
panel.classList.add('active');
|
|
161
|
+
} else {
|
|
162
|
+
panel.classList.remove('active');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function initCodePanel() {
|
|
167
|
+
const state = window.AppState;
|
|
168
|
+
if (!state) return;
|
|
169
|
+
|
|
170
|
+
state.on('code_generated', (file) => {
|
|
171
|
+
if (!file || !file.path) return;
|
|
172
|
+
const existing = files.findIndex(f => f.path === file.path);
|
|
173
|
+
const normalized = { path: file.path, code: file.content || file.code || '' };
|
|
174
|
+
if (existing >= 0) {
|
|
175
|
+
files[existing] = normalized;
|
|
176
|
+
if (existing === activeIndex) {
|
|
177
|
+
startTypewriter(normalized.code);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
files.push(normalized);
|
|
181
|
+
// Auto-select newly added file
|
|
182
|
+
showFile(files.length - 1);
|
|
183
|
+
}
|
|
184
|
+
renderTabs();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
state.on('phase_change', ({ to }) => {
|
|
188
|
+
updatePanelVisibility(to);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
state.on('reset', () => {
|
|
192
|
+
files.length = 0;
|
|
193
|
+
activeIndex = -1;
|
|
194
|
+
if (typewriterRaf) cancelAnimationFrame(typewriterRaf);
|
|
195
|
+
typewriterRaf = 0;
|
|
196
|
+
const tabsEl = document.getElementById('code-tabs');
|
|
197
|
+
const contentEl = document.getElementById('code-content');
|
|
198
|
+
if (tabsEl) tabsEl.innerHTML = '';
|
|
199
|
+
if (contentEl) {
|
|
200
|
+
contentEl.innerHTML = '';
|
|
201
|
+
contentEl.classList.remove('cursor-blink');
|
|
202
|
+
}
|
|
203
|
+
const panel = document.getElementById('code-panel');
|
|
204
|
+
if (panel) panel.classList.remove('active');
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// demo-mode.js — Canned event replay for offline demos
|
|
2
|
+
// Loads pre-recorded events and replays them with timing
|
|
3
|
+
|
|
4
|
+
const AppState = window.AppState;
|
|
5
|
+
|
|
6
|
+
let events = null;
|
|
7
|
+
let timers = [];
|
|
8
|
+
|
|
9
|
+
function dispatchEvent(event) {
|
|
10
|
+
const { type, data } = event;
|
|
11
|
+
|
|
12
|
+
switch (type) {
|
|
13
|
+
case 'phase_change':
|
|
14
|
+
AppState.update({ phase: data.phase });
|
|
15
|
+
break;
|
|
16
|
+
|
|
17
|
+
case 'agent_activate': {
|
|
18
|
+
const existing = AppState.get().agents.find(a => a.name === data.name);
|
|
19
|
+
if (existing) {
|
|
20
|
+
AppState.updateAgent(data.name, data);
|
|
21
|
+
} else {
|
|
22
|
+
AppState.addAgent(data);
|
|
23
|
+
}
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
case 'task_add':
|
|
28
|
+
AppState.addTask(data);
|
|
29
|
+
AppState.update({ currentTask: { name: data.title || data.name, agent: data.agent, status: data.status } });
|
|
30
|
+
break;
|
|
31
|
+
|
|
32
|
+
case 'task_update':
|
|
33
|
+
AppState.updateTask(data.id, data);
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case 'verification':
|
|
37
|
+
AppState.addVerification(data);
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case 'code_generated':
|
|
41
|
+
AppState.addCodeFile(data);
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case 'metric_update':
|
|
45
|
+
AppState.update({ metrics: data });
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clearTimers() {
|
|
51
|
+
for (const id of timers) {
|
|
52
|
+
clearTimeout(id);
|
|
53
|
+
}
|
|
54
|
+
timers = [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function initDemoMode() {
|
|
58
|
+
return fetch('data/demo-events.json')
|
|
59
|
+
.then(res => {
|
|
60
|
+
if (!res.ok) throw new Error(`Failed to load demo events: ${res.status}`);
|
|
61
|
+
return res.json();
|
|
62
|
+
})
|
|
63
|
+
.then(data => {
|
|
64
|
+
events = data;
|
|
65
|
+
})
|
|
66
|
+
.catch(err => {
|
|
67
|
+
console.error('[Demo] Failed to load events:', err);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function startDemo(prompt) {
|
|
72
|
+
if (!events || events.length === 0) {
|
|
73
|
+
console.error('[Demo] No events loaded. Call initDemoMode() first.');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Reset state for fresh demo
|
|
78
|
+
AppState.reset();
|
|
79
|
+
clearTimers();
|
|
80
|
+
|
|
81
|
+
// Disable input during demo
|
|
82
|
+
const input = document.getElementById('prompt-input');
|
|
83
|
+
const btn = document.getElementById('submit-btn');
|
|
84
|
+
if (input) input.disabled = true;
|
|
85
|
+
if (btn) btn.disabled = true;
|
|
86
|
+
|
|
87
|
+
// Log the prompt
|
|
88
|
+
AppState.addLog({ level: 'info', message: `Demo started: "${prompt}"` });
|
|
89
|
+
|
|
90
|
+
// Schedule all events by their absolute delay
|
|
91
|
+
const lastDelay = events[events.length - 1].delay;
|
|
92
|
+
|
|
93
|
+
for (const event of events) {
|
|
94
|
+
const id = setTimeout(() => {
|
|
95
|
+
dispatchEvent(event);
|
|
96
|
+
}, event.delay);
|
|
97
|
+
timers.push(id);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Re-enable input after all events complete
|
|
101
|
+
const finishId = setTimeout(() => {
|
|
102
|
+
if (input) input.disabled = false;
|
|
103
|
+
if (btn) btn.disabled = false;
|
|
104
|
+
timers = [];
|
|
105
|
+
}, lastDelay + 500);
|
|
106
|
+
timers.push(finishId);
|
|
107
|
+
}
|