tycono 0.1.96-beta.34 → 0.1.96-beta.36

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.96-beta.34",
3
+ "version": "0.1.96-beta.36",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tui/app.tsx CHANGED
@@ -395,6 +395,64 @@ export const App: React.FC = () => {
395
395
  api.refresh();
396
396
  break;
397
397
  }
398
+ case 'docs': {
399
+ // Extract written/edited files from SSE events in current wave
400
+ const writtenFiles = new Set<string>();
401
+ for (const event of sse.events) {
402
+ if (event.type === 'tool:start') {
403
+ const name = (event.data.name as string) ?? '';
404
+ const input = event.data.input as Record<string, unknown> | undefined;
405
+ if (['Write', 'Edit', 'NotebookEdit'].includes(name) && input?.file_path) {
406
+ writtenFiles.add(String(input.file_path));
407
+ }
408
+ }
409
+ }
410
+ if (writtenFiles.size === 0) {
411
+ addSystemMessage('No files created/modified in this wave.', 'gray');
412
+ } else {
413
+ addSystemMessage(`Files in this wave (${writtenFiles.size}):`, 'cyan');
414
+ for (const f of writtenFiles) {
415
+ const short = f.split('/').slice(-3).join('/');
416
+ addSystemMessage(` ${short}`, 'white');
417
+ }
418
+ addSystemMessage(' /read <path> to preview | /open <path> to edit', 'gray');
419
+ }
420
+ break;
421
+ }
422
+ case 'read_file': {
423
+ const filePath = result.message;
424
+ try {
425
+ const content = await import('node:fs').then(fs =>
426
+ fs.readFileSync(filePath, 'utf-8')
427
+ );
428
+ const lines = content.split('\n');
429
+ const preview = lines.slice(0, 30);
430
+ addSystemMessage(`\u2500\u2500 ${filePath.split('/').slice(-2).join('/')} \u2500\u2500`, 'cyan');
431
+ for (const line of preview) {
432
+ addSystemMessage(line, 'white');
433
+ }
434
+ if (lines.length > 30) {
435
+ addSystemMessage(` ... +${lines.length - 30} more lines (/open to see full)`, 'gray');
436
+ }
437
+ addSystemMessage('\u2500'.repeat(40), 'gray');
438
+ } catch (err) {
439
+ addSystemMessage(`Cannot read: ${err instanceof Error ? err.message : 'unknown'}`, 'red');
440
+ }
441
+ break;
442
+ }
443
+ case 'open_file': {
444
+ const filePath = result.message;
445
+ const editor = process.env.EDITOR || process.env.VISUAL || 'less';
446
+ try {
447
+ const { execSync } = await import('node:child_process');
448
+ execSync(`${editor} "${filePath}"`, { stdio: 'inherit' });
449
+ addSystemMessage(`Opened: ${filePath}`, 'green');
450
+ } catch {
451
+ // Fallback to /read
452
+ addSystemMessage(`Cannot open with ${editor}. Use /read instead.`, 'yellow');
453
+ }
454
+ break;
455
+ }
398
456
  case 'error':
399
457
  addSystemMessage(result.message, 'red');
400
458
  break;
@@ -404,11 +462,13 @@ export const App: React.FC = () => {
404
462
  addSystemMessage(' /new [text] Create new wave', 'white');
405
463
  addSystemMessage(' /waves List all waves', 'white');
406
464
  addSystemMessage(' /focus <n> Switch to wave n', 'white');
465
+ addSystemMessage(' /docs Files created in this wave', 'white');
466
+ addSystemMessage(' /read <path> Preview file content', 'white');
467
+ addSystemMessage(' /open <path> Open in $EDITOR', 'white');
407
468
  addSystemMessage(' /agents Wave \u2192 Role \u2192 Session tree', 'white');
408
469
  addSystemMessage(' /sessions Sessions + ports (kill/cleanup)', 'white');
409
470
  addSystemMessage(' /kill <id> Kill a session', 'white');
410
471
  addSystemMessage(' /cleanup Remove dead sessions', 'white');
411
- addSystemMessage(' /status Current status', 'white');
412
472
  addSystemMessage(' /help This help', 'white');
413
473
  addSystemMessage(' /quit Exit', 'white');
414
474
  addSystemMessage('Keys: [Tab] team panel [1-9] wave [Esc] back [Ctrl+C] quit', 'gray');
@@ -172,20 +172,10 @@ export const PanelMode: React.FC<PanelModeProps> = ({
172
172
  {/* Left: Wave title + Org Tree + Wave tabs */}
173
173
  <Box flexDirection="column" width={leftWidth}>
174
174
  {/* Wave title */}
175
- <Box paddingX={1} marginBottom={0}>
176
- <Text color="green" bold>
177
- W{focusedWaveIndex}
178
- </Text>
179
- <Text color="gray"> </Text>
180
- <Text color="white" wrap="truncate">
181
- {focusedWave?.directive ? focusedWave.directive.slice(0, leftWidth - 6) : '(idle)'}
182
- </Text>
183
- </Box>
184
-
185
- {/* Session count */}
186
- {waveSessionCount > 0 && (
187
- <Box paddingX={1}>
188
- <Text color="gray">{waveSessionCount} sessions</Text>
175
+ {focusedWaveIndex > 0 && (
176
+ <Box paddingX={1} marginBottom={0}>
177
+ <Text color="green" bold>Wave {focusedWaveIndex}</Text>
178
+ {waveSessionCount > 0 && <Text color="gray"> ({waveSessionCount})</Text>}
189
179
  </Box>
190
180
  )}
191
181
 
@@ -198,21 +188,19 @@ export const PanelMode: React.FC<PanelModeProps> = ({
198
188
  ceoStatus={waveScopedStatuses['ceo'] ?? 'idle'}
199
189
  />
200
190
 
201
- {/* Wave tabs at bottom */}
191
+ {/* Wave tabs at bottom — compact inline */}
202
192
  {waves.length > 1 && (
203
193
  <Box paddingX={1} marginTop={1}>
194
+ <Text color="gray">W </Text>
204
195
  {waves.map((w, i) => {
205
196
  const isFocused = w.waveId === focusedWaveId;
206
197
  return (
207
- <Box key={w.waveId} marginRight={1}>
208
- <Text
209
- color={isFocused ? 'green' : 'gray'}
210
- bold={isFocused}
211
- inverse={isFocused}
212
- >
213
- {` ${i + 1} `}
214
- </Text>
215
- </Box>
198
+ <Text key={w.waveId}
199
+ color={isFocused ? 'green' : 'gray'}
200
+ bold={isFocused}
201
+ >
202
+ {isFocused ? `[${i + 1}]` : ` ${i + 1} `}
203
+ </Text>
216
204
  );
217
205
  })}
218
206
  </Box>
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { useCallback } from 'react';
19
- import { dispatchWave, sendDirective, fetchJson, killSession, cleanupSessions } from '../api';
19
+ import { dispatchWave, sendDirective, fetchJson, killSession, cleanupSessions, fetchActiveSessions } from '../api';
20
20
 
21
21
  export interface WaveInfo {
22
22
  waveId: string;
@@ -25,7 +25,7 @@ export interface WaveInfo {
25
25
  }
26
26
 
27
27
  export interface CommandResult {
28
- type: 'success' | 'error' | 'info' | 'wave_started' | 'directive_sent' | 'stopped' | 'quit' | 'help' | 'panel' | 'waves_list' | 'focus_changed' | 'agents' | 'ports' | 'sessions' | 'cleanup';
28
+ type: 'success' | 'error' | 'info' | 'wave_started' | 'directive_sent' | 'stopped' | 'quit' | 'help' | 'panel' | 'waves_list' | 'focus_changed' | 'agents' | 'ports' | 'sessions' | 'cleanup' | 'docs' | 'read_file' | 'open_file';
29
29
  message: string;
30
30
  waveId?: string;
31
31
  }
@@ -123,6 +123,19 @@ export function useCommand(options: UseCommandOptions) {
123
123
  }
124
124
  }
125
125
 
126
+ case 'docs':
127
+ return { type: 'docs', message: '__docs__' };
128
+
129
+ case 'read': {
130
+ if (!args) return { type: 'error', message: 'Usage: /read <file_path>' };
131
+ return { type: 'read_file', message: args.trim() };
132
+ }
133
+
134
+ case 'open': {
135
+ if (!args) return { type: 'error', message: 'Usage: /open <file_path>' };
136
+ return { type: 'open_file', message: args.trim() };
137
+ }
138
+
126
139
  case 'status':
127
140
  return { type: 'info', message: '__status__' };
128
141