tycono 0.1.96-beta.57 → 0.1.96-beta.59

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/bin/tycono.ts CHANGED
@@ -221,15 +221,22 @@ async function startServerForTui(): Promise<void> {
221
221
  const origStdoutWrite = process.stdout.write.bind(process.stdout);
222
222
  const origLog = (...args: unknown[]) => origStdoutWrite(args.join(' ') + '\n');
223
223
 
224
- // Redirect console methods to log file BEFORE importing server code
225
- // ⛔ Do NOT redirect process.stdout.write or process.stderr.write
226
- // stdout.write: Ink needs full control for rendering
227
- // stderr.write: Node.js http client uses it internally — redirect breaks HTTP
224
+ // Redirect console + stdout.write to suppress server logs
225
+ // ⛔ Do NOT redirect process.stderr.write breaks Node.js http client
228
226
  console.log = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
229
227
  console.error = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
230
228
  console.warn = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
231
229
  console.info = (...a: unknown[]) => { logStream.write(a.join(' ') + '\n'); };
232
230
 
231
+ // Intercept stdout.write — allow Ink (ANSI), redirect server text to log
232
+ const isInkOutput = (s: string) => s.includes('\x1b[') || s.includes('\x1b(');
233
+ process.stdout.write = ((chunk: any, ...args: any[]) => {
234
+ const str = typeof chunk === 'string' ? chunk : chunk.toString();
235
+ if (isInkOutput(str)) return origStdoutWrite(chunk, ...args);
236
+ logStream.write(str);
237
+ return true;
238
+ }) as any;
239
+
233
240
  const { createHttpServer } = await import('../src/api/src/create-server.js');
234
241
  const server = createHttpServer();
235
242
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tycono",
3
- "version": "0.1.96-beta.57",
3
+ "version": "0.1.96-beta.59",
4
4
  "description": "Build an AI company. Watch them work.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tui/api.ts CHANGED
@@ -208,6 +208,12 @@ export async function fetchKnowledgeDocs(): Promise<KnowledgeDoc[]> {
208
208
  return fetchJson<KnowledgeDoc[]>('/api/knowledge');
209
209
  }
210
210
 
211
+ /** Scan COMPANY_ROOT for all .md files (not API-dependent) */
212
+ export async function fetchCompanyRoot(): Promise<string> {
213
+ const health = await fetchJson<{ companyRoot: string }>('/api/health');
214
+ return health.companyRoot;
215
+ }
216
+
211
217
  /* ─── Setup API calls ─── */
212
218
 
213
219
  export interface TeamTemplate {
package/src/tui/app.tsx CHANGED
@@ -563,7 +563,7 @@ export const App: React.FC = () => {
563
563
  waveId={focusedWaveId}
564
564
  activeSessions={api.activeSessions}
565
565
  allSessions={api.sessions}
566
- knowledgeDocs={api.knowledgeDocs}
566
+ companyRoot={process.env.COMPANY_ROOT || process.cwd()}
567
567
  waves={waves}
568
568
  focusedWaveId={focusedWaveId}
569
569
  onMove={(dir) => {
@@ -19,7 +19,8 @@ import { execSync } from 'node:child_process';
19
19
  import { OrgTree } from './OrgTree';
20
20
  import { StreamView } from './StreamView';
21
21
  import type { OrgNode } from '../store';
22
- import type { SSEEvent, ActiveSessionInfo, SessionInfo, KnowledgeDoc } from '../api';
22
+ import path from 'node:path';
23
+ import type { SSEEvent, ActiveSessionInfo, SessionInfo } from '../api';
23
24
  import type { WaveInfo } from '../hooks/useCommand';
24
25
 
25
26
  type RightTab = 'stream' | 'docs' | 'info';
@@ -35,7 +36,7 @@ interface PanelModeProps {
35
36
  waveId: string | null;
36
37
  activeSessions: ActiveSessionInfo[];
37
38
  allSessions: SessionInfo[];
38
- knowledgeDocs: KnowledgeDoc[];
39
+ companyRoot: string;
39
40
  waves: WaveInfo[];
40
41
  focusedWaveId: string | null;
41
42
  onMove: (direction: 'up' | 'down') => void;
@@ -78,6 +79,31 @@ function elapsed(startedAt: string): string {
78
79
  return `${Math.floor(ms / 3600_000)}h`;
79
80
  }
80
81
 
82
+ /** Scan COMPANY_ROOT for .md files (cached) */
83
+ let mdFileCache: { root: string; files: string[] } | null = null;
84
+ function scanMdFiles(companyRoot: string): string[] {
85
+ if (mdFileCache && mdFileCache.root === companyRoot) return mdFileCache.files;
86
+ const results: string[] = [];
87
+ const skip = new Set(['.git', 'node_modules', '.tycono', '.worktrees', 'dist', '.claude']);
88
+ function walk(dir: string, depth: number) {
89
+ if (depth > 3) return; // Don't go too deep
90
+ try {
91
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
92
+ if (skip.has(entry.name)) continue;
93
+ const full = path.join(dir, entry.name);
94
+ if (entry.isDirectory()) {
95
+ walk(full, depth + 1);
96
+ } else if (entry.name.endsWith('.md')) {
97
+ results.push(full);
98
+ }
99
+ }
100
+ } catch { /* permission error etc */ }
101
+ }
102
+ walk(companyRoot, 0);
103
+ mdFileCache = { root: companyRoot, files: results };
104
+ return results;
105
+ }
106
+
81
107
  /** Extract files created/modified in this wave from SSE events */
82
108
  function extractWaveFiles(events: SSEEvent[]): string[] {
83
109
  const files = new Set<string>();
@@ -115,7 +141,7 @@ function readFilePreview(filePath: string, maxLines: number): string[] {
115
141
 
116
142
  export const PanelMode: React.FC<PanelModeProps> = ({
117
143
  tree, flatRoles, events, selectedRoleIndex, selectedRoleId,
118
- streamStatus, waveId, activeSessions, allSessions, knowledgeDocs, waves,
144
+ streamStatus, waveId, activeSessions, allSessions, companyRoot, waves,
119
145
  focusedWaveId, onMove, onSelect, onEscape, onFocusWave,
120
146
  }) => {
121
147
  const [termHeight, setTermHeight] = useState(process.stdout.rows || 30);
@@ -154,27 +180,30 @@ export const PanelMode: React.FC<PanelModeProps> = ({
154
180
  return new Set(extractWaveFiles(events));
155
181
  }, [rightTab === 'docs' || rightTab === 'info' ? events.length : 0, rightTab]);
156
182
 
157
- // Build docs list based on filter
183
+ // Build docs list from filesystem scan + wave files
158
184
  const docsList = useMemo(() => {
159
185
  if (rightTab !== 'docs') return [];
160
186
 
161
187
  interface DocEntry { path: string; title: string; isWave: boolean; }
162
188
  const entries: DocEntry[] = [];
163
189
 
164
- // KB docs from API
165
- for (const doc of knowledgeDocs) {
166
- const isWave = waveFileSet.has(doc.path);
167
- const isKb = doc.id.startsWith('knowledge/');
168
- const isProject = doc.id.startsWith('projects/');
190
+ // Scan all .md files from COMPANY_ROOT
191
+ const allMdFiles = companyRoot ? scanMdFiles(companyRoot) : [];
192
+
193
+ for (const filePath of allMdFiles) {
194
+ const rel = filePath.replace(companyRoot + '/', '');
195
+ const isWave = waveFileSet.has(filePath);
196
+ const isKb = rel.startsWith('knowledge/');
197
+ const isProject = rel.startsWith('projects/');
169
198
 
170
199
  if (docsFilter === 'wave' && !isWave) continue;
171
200
  if (docsFilter === 'kb' && !isKb) continue;
172
201
  if (docsFilter === 'projects' && !isProject) continue;
173
202
 
174
- entries.push({ path: doc.path, title: doc.title || doc.id.split('/').pop() || '', isWave });
203
+ entries.push({ path: filePath, title: rel, isWave });
175
204
  }
176
205
 
177
- // Wave-only files not in KB (e.g. code files)
206
+ // Wave-only files not already in list (e.g. code files written by agents)
178
207
  for (const f of waveFileSet) {
179
208
  if (!entries.some(e => e.path === f)) {
180
209
  if (docsFilter === 'kb' || docsFilter === 'projects') continue;
@@ -190,7 +219,7 @@ export const PanelMode: React.FC<PanelModeProps> = ({
190
219
  });
191
220
 
192
221
  return entries;
193
- }, [rightTab, docsFilter, knowledgeDocs, waveFileSet]);
222
+ }, [rightTab, docsFilter, companyRoot, waveFileSet]);
194
223
 
195
224
  const selectedDoc = docsList[docsIndex] ?? null;
196
225
  const filePreview = useMemo(() => {