tycono 0.1.25 → 0.1.27
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 +1 -1
- package/src/api/src/routes/knowledge.ts +13 -13
- package/src/api/src/routes/setup.ts +21 -15
- package/src/api/src/services/activity-stream.ts +8 -5
- package/src/api/src/services/activity-tracker.ts +9 -6
- package/src/api/src/services/file-reader.ts +7 -1
- package/src/api/src/services/session-store.ts +9 -6
- package/src/web/dist/assets/{index-DhqDOR_8.js → index-CgztWO_G.js} +36 -36
- package/src/web/dist/assets/{preview-app-Bmqw6mTx.js → preview-app-LDESpiII.js} +1 -1
- package/src/web/dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -13,8 +13,8 @@ import { COMPANY_ROOT } from '../services/file-reader.js';
|
|
|
13
13
|
|
|
14
14
|
export const knowledgeRouter = Router();
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
function knowledgeDir(): string { return path.join(COMPANY_ROOT, 'knowledge'); }
|
|
17
|
+
function companyRoot(): string { return COMPANY_ROOT; }
|
|
18
18
|
|
|
19
19
|
/* ─── Helpers ─────────────────────────────────────── */
|
|
20
20
|
|
|
@@ -61,13 +61,13 @@ function inferCategory(filePath: string, tags: string[]): string {
|
|
|
61
61
|
|
|
62
62
|
knowledgeRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
63
63
|
try {
|
|
64
|
-
if (!fs.existsSync(companyRoot)) {
|
|
64
|
+
if (!fs.existsSync(companyRoot())) {
|
|
65
65
|
res.json([]);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const files = glob.sync('**/*.{md,html}', {
|
|
70
|
-
cwd: companyRoot,
|
|
70
|
+
cwd: companyRoot(),
|
|
71
71
|
ignore: [
|
|
72
72
|
'node_modules/**', '.claude/**', '.obsidian/**', '.tycono/**', '.git/**',
|
|
73
73
|
'**/node_modules/**',
|
|
@@ -82,7 +82,7 @@ knowledgeRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
|
82
82
|
.sort();
|
|
83
83
|
|
|
84
84
|
const docs = files.map((f) => {
|
|
85
|
-
const absPath = path.join(companyRoot, f);
|
|
85
|
+
const absPath = path.join(companyRoot(), f);
|
|
86
86
|
let raw = '';
|
|
87
87
|
try { raw = fs.readFileSync(absPath, 'utf-8'); } catch { return null; }
|
|
88
88
|
|
|
@@ -165,9 +165,9 @@ knowledgeRouter.post('/', (req: Request, res: Response, next: NextFunction) => {
|
|
|
165
165
|
// Sanitize filename
|
|
166
166
|
const safeName = filename.replace(/[^a-zA-Z0-9가-힣_\-. ]/g, '').replace(/\s+/g, '-');
|
|
167
167
|
const fullName = safeName.endsWith('.md') ? safeName : `${safeName}.md`;
|
|
168
|
-
const absPath = path.join(knowledgeDir, fullName);
|
|
168
|
+
const absPath = path.join(knowledgeDir(), fullName);
|
|
169
169
|
|
|
170
|
-
if (!absPath.startsWith(knowledgeDir)) {
|
|
170
|
+
if (!absPath.startsWith(knowledgeDir())) {
|
|
171
171
|
res.status(403).json({ error: 'Forbidden' });
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
@@ -206,8 +206,8 @@ knowledgeRouter.put('/{*path}', (req: Request, res: Response, next: NextFunction
|
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
const absPath = path.join(companyRoot, docId);
|
|
210
|
-
if (!absPath.startsWith(companyRoot)) {
|
|
209
|
+
const absPath = path.join(companyRoot(), docId);
|
|
210
|
+
if (!absPath.startsWith(companyRoot())) {
|
|
211
211
|
res.status(403).json({ error: 'Forbidden' });
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
@@ -248,8 +248,8 @@ knowledgeRouter.delete('/{*path}', (req: Request, res: Response, next: NextFunct
|
|
|
248
248
|
return;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
const absPath = path.join(companyRoot, docId);
|
|
252
|
-
if (!absPath.startsWith(companyRoot)) {
|
|
251
|
+
const absPath = path.join(companyRoot(), docId);
|
|
252
|
+
if (!absPath.startsWith(companyRoot())) {
|
|
253
253
|
res.status(403).json({ error: 'Forbidden' });
|
|
254
254
|
return;
|
|
255
255
|
}
|
|
@@ -277,10 +277,10 @@ knowledgeRouter.get('/{*path}', (req: Request, res: Response, next: NextFunction
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
const absPath = path.join(knowledgeDir, docId);
|
|
280
|
+
const absPath = path.join(knowledgeDir(), docId);
|
|
281
281
|
|
|
282
282
|
// Security: ensure path stays within knowledgeDir
|
|
283
|
-
if (!absPath.startsWith(knowledgeDir)) {
|
|
283
|
+
if (!absPath.startsWith(knowledgeDir())) {
|
|
284
284
|
res.status(403).json({ error: 'Forbidden' });
|
|
285
285
|
return;
|
|
286
286
|
}
|
|
@@ -16,6 +16,7 @@ import { AnthropicProvider, type LLMProvider } from '../engine/llm-adapter.js';
|
|
|
16
16
|
import { jobManager } from '../services/job-manager.js';
|
|
17
17
|
import { applyConfig, readConfig, writeConfig } from '../services/company-config.js';
|
|
18
18
|
import { mergePreferences } from '../services/preferences.js';
|
|
19
|
+
import { setCompanyRoot } from '../services/file-reader.js';
|
|
19
20
|
|
|
20
21
|
export const setupRouter = Router();
|
|
21
22
|
|
|
@@ -82,28 +83,33 @@ setupRouter.post('/validate-path', (req, res) => {
|
|
|
82
83
|
* POST /api/setup/scaffold
|
|
83
84
|
*/
|
|
84
85
|
setupRouter.post('/scaffold', (req, res) => {
|
|
85
|
-
const { companyName, description, apiKey, team, existingProjectPath, knowledgePaths, codeRoot, language } = req.body;
|
|
86
|
+
const { companyName, description, apiKey, team, existingProjectPath, knowledgePaths, codeRoot, language, location } = req.body;
|
|
86
87
|
|
|
87
88
|
if (!companyName || typeof companyName !== 'string') {
|
|
88
89
|
res.status(400).json({ error: 'companyName is required' });
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
// Determine project root: explicit location from wizard > fallback to CWD with safety check
|
|
94
|
+
let projectRoot: string;
|
|
95
|
+
if (location && typeof location === 'string') {
|
|
96
|
+
projectRoot = path.resolve(location);
|
|
97
|
+
} else {
|
|
98
|
+
const baseRoot = process.env.COMPANY_ROOT || process.cwd();
|
|
99
|
+
const dangerousPaths = new Set(['/', os.homedir(), os.tmpdir()]);
|
|
100
|
+
const isDangerous = dangerousPaths.has(baseRoot) || baseRoot === '/tmp';
|
|
101
|
+
if (isDangerous) {
|
|
102
|
+
const slug = companyName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'my-company';
|
|
103
|
+
projectRoot = path.join(baseRoot, slug);
|
|
104
|
+
} else {
|
|
105
|
+
projectRoot = baseRoot;
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
if (!fs.existsSync(projectRoot)) {
|
|
110
|
+
fs.mkdirSync(projectRoot, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
107
113
|
const config: ScaffoldConfig = {
|
|
108
114
|
companyName,
|
|
109
115
|
description: description || 'An AI-powered organization',
|
|
@@ -117,7 +123,7 @@ setupRouter.post('/scaffold', (req, res) => {
|
|
|
117
123
|
try {
|
|
118
124
|
const created = scaffold(config);
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
setCompanyRoot(projectRoot);
|
|
121
127
|
// Load config.json written by scaffold and apply to process.env
|
|
122
128
|
const scaffoldConfig = applyConfig(projectRoot);
|
|
123
129
|
// Save codeRoot if provided
|
|
@@ -212,7 +218,7 @@ setupRouter.post('/connect-akb', (req, res) => {
|
|
|
212
218
|
if (match) companyName = match[1].trim();
|
|
213
219
|
} catch { /* ignore */ }
|
|
214
220
|
|
|
215
|
-
|
|
221
|
+
setCompanyRoot(resolved);
|
|
216
222
|
|
|
217
223
|
// Load existing config.json if present
|
|
218
224
|
const config = readConfig(resolved);
|
|
@@ -24,16 +24,19 @@ export interface ActivityEvent {
|
|
|
24
24
|
|
|
25
25
|
/* ─── Constants ──────────────────────────── */
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
function streamsDir(): string {
|
|
28
|
+
return path.join(COMPANY_ROOT, 'operations', 'activity-streams');
|
|
29
|
+
}
|
|
28
30
|
|
|
29
31
|
function ensureDir(): void {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const dir = streamsDir();
|
|
33
|
+
if (!fs.existsSync(dir)) {
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
function streamPath(jobId: string): string {
|
|
36
|
-
return path.join(
|
|
39
|
+
return path.join(streamsDir(), `${jobId}.jsonl`);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/* ─── Subscriber type ────────────────────── */
|
|
@@ -149,7 +152,7 @@ export class ActivityStream {
|
|
|
149
152
|
/** List all stream files (job IDs) */
|
|
150
153
|
static listAll(): string[] {
|
|
151
154
|
ensureDir();
|
|
152
|
-
return fs.readdirSync(
|
|
155
|
+
return fs.readdirSync(streamsDir())
|
|
153
156
|
.filter(f => f.endsWith('.jsonl'))
|
|
154
157
|
.map(f => f.replace('.jsonl', ''));
|
|
155
158
|
}
|
|
@@ -2,7 +2,9 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { COMPANY_ROOT } from './file-reader.js';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
function activityDir(): string {
|
|
6
|
+
return path.join(COMPANY_ROOT, 'operations', 'activity');
|
|
7
|
+
}
|
|
6
8
|
|
|
7
9
|
export interface RoleActivity {
|
|
8
10
|
roleId: string;
|
|
@@ -14,12 +16,13 @@ export interface RoleActivity {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
function activityPath(roleId: string): string {
|
|
17
|
-
return path.join(
|
|
19
|
+
return path.join(activityDir(), `${roleId}.json`);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function ensureDir(): void {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const dir = activityDir();
|
|
24
|
+
if (!fs.existsSync(dir)) {
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -71,10 +74,10 @@ export function getActivity(roleId: string): RoleActivity | null {
|
|
|
71
74
|
|
|
72
75
|
export function getAllActivities(): RoleActivity[] {
|
|
73
76
|
ensureDir();
|
|
74
|
-
const files = fs.readdirSync(
|
|
77
|
+
const files = fs.readdirSync(activityDir()).filter(f => f.endsWith('.json'));
|
|
75
78
|
return files.map(f => {
|
|
76
79
|
try {
|
|
77
|
-
return JSON.parse(fs.readFileSync(path.join(
|
|
80
|
+
return JSON.parse(fs.readFileSync(path.join(activityDir(), f), 'utf-8'));
|
|
78
81
|
} catch {
|
|
79
82
|
return null;
|
|
80
83
|
}
|
|
@@ -14,7 +14,13 @@ function findCompanyRoot(): string {
|
|
|
14
14
|
return process.cwd();
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export
|
|
17
|
+
export let COMPANY_ROOT = findCompanyRoot();
|
|
18
|
+
|
|
19
|
+
/** Update COMPANY_ROOT at runtime (e.g. after scaffold picks a new location) */
|
|
20
|
+
export function setCompanyRoot(root: string): void {
|
|
21
|
+
COMPANY_ROOT = root;
|
|
22
|
+
process.env.COMPANY_ROOT = root;
|
|
23
|
+
}
|
|
18
24
|
|
|
19
25
|
function resolve(...segments: string[]): string {
|
|
20
26
|
return path.resolve(COMPANY_ROOT, ...segments);
|
|
@@ -26,16 +26,19 @@ export interface Session {
|
|
|
26
26
|
|
|
27
27
|
/* ─── Session directory ─────────────────── */
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
function sessionsDir(): string {
|
|
30
|
+
return path.join(COMPANY_ROOT, 'operations', 'sessions');
|
|
31
|
+
}
|
|
30
32
|
|
|
31
33
|
function ensureDir(): void {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const dir = sessionsDir();
|
|
35
|
+
if (!fs.existsSync(dir)) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
function sessionPath(id: string): string {
|
|
38
|
-
return path.join(
|
|
41
|
+
return path.join(sessionsDir(), `${id}.json`);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/* ─── Debounced write ───────────────────── */
|
|
@@ -69,10 +72,10 @@ const cache = new Map<string, Session>();
|
|
|
69
72
|
|
|
70
73
|
function loadAll(): void {
|
|
71
74
|
ensureDir();
|
|
72
|
-
const files = fs.readdirSync(
|
|
75
|
+
const files = fs.readdirSync(sessionsDir()).filter((f) => f.endsWith('.json'));
|
|
73
76
|
for (const file of files) {
|
|
74
77
|
try {
|
|
75
|
-
const data = JSON.parse(fs.readFileSync(path.join(
|
|
78
|
+
const data = JSON.parse(fs.readFileSync(path.join(sessionsDir(), file), 'utf-8')) as Session;
|
|
76
79
|
cache.set(data.id, data);
|
|
77
80
|
} catch { /* skip corrupted */ }
|
|
78
81
|
}
|