studiograph 1.0.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 +18 -0
- package/dist/agent/orchestrator.d.ts +69 -0
- package/dist/agent/orchestrator.js +211 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/tools/graph-tools.d.ts +30 -0
- package/dist/agent/tools/graph-tools.js +536 -0
- package/dist/agent/tools/graph-tools.js.map +1 -0
- package/dist/auth/github.d.ts +53 -0
- package/dist/auth/github.js +180 -0
- package/dist/auth/github.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.js +63 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +299 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/join.d.ts +14 -0
- package/dist/cli/commands/join.js +230 -0
- package/dist/cli/commands/join.js.map +1 -0
- package/dist/cli/commands/members.d.ts +11 -0
- package/dist/cli/commands/members.js +230 -0
- package/dist/cli/commands/members.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +17 -0
- package/dist/cli/commands/serve.js +90 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/start.d.ts +7 -0
- package/dist/cli/commands/start.js +381 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +10 -0
- package/dist/cli/commands/sync.js +121 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/graph.d.ts +169 -0
- package/dist/core/graph.js +558 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/types.d.ts +216 -0
- package/dist/core/types.js +71 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/user-config.d.ts +31 -0
- package/dist/core/user-config.js +50 -0
- package/dist/core/user-config.js.map +1 -0
- package/dist/core/validation.d.ts +2371 -0
- package/dist/core/validation.js +432 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/workspace-manager.d.ts +104 -0
- package/dist/core/workspace-manager.js +432 -0
- package/dist/core/workspace-manager.js.map +1 -0
- package/dist/core/workspace.d.ts +103 -0
- package/dist/core/workspace.js +306 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +84 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/plugin-loader.d.ts +31 -0
- package/dist/server/plugin-loader.js +81 -0
- package/dist/server/plugin-loader.js.map +1 -0
- package/dist/server/routes/chat.d.ts +11 -0
- package/dist/server/routes/chat.js +66 -0
- package/dist/server/routes/chat.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +9 -0
- package/dist/server/routes/graph-api.js +72 -0
- package/dist/server/routes/graph-api.js.map +1 -0
- package/dist/server/routes/webhook.d.ts +14 -0
- package/dist/server/routes/webhook.js +69 -0
- package/dist/server/routes/webhook.js.map +1 -0
- package/dist/services/assets/base.d.ts +69 -0
- package/dist/services/assets/base.js +113 -0
- package/dist/services/assets/base.js.map +1 -0
- package/dist/services/assets/index.d.ts +36 -0
- package/dist/services/assets/index.js +89 -0
- package/dist/services/assets/index.js.map +1 -0
- package/dist/services/assets/local.d.ts +42 -0
- package/dist/services/assets/local.js +161 -0
- package/dist/services/assets/local.js.map +1 -0
- package/dist/services/assets/r2.d.ts +36 -0
- package/dist/services/assets/r2.js +182 -0
- package/dist/services/assets/r2.js.map +1 -0
- package/dist/services/csv-service.d.ts +36 -0
- package/dist/services/csv-service.js +143 -0
- package/dist/services/csv-service.js.map +1 -0
- package/dist/services/git.d.ts +99 -0
- package/dist/services/git.js +306 -0
- package/dist/services/git.js.map +1 -0
- package/dist/services/github-provisioner.d.ts +30 -0
- package/dist/services/github-provisioner.js +89 -0
- package/dist/services/github-provisioner.js.map +1 -0
- package/dist/services/markdown.d.ts +82 -0
- package/dist/services/markdown.js +338 -0
- package/dist/services/markdown.js.map +1 -0
- package/dist/services/memory-service.d.ts +74 -0
- package/dist/services/memory-service.js +183 -0
- package/dist/services/memory-service.js.map +1 -0
- package/dist/utils/git.d.ts +28 -0
- package/dist/utils/git.js +55 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/preflight.d.ts +44 -0
- package/dist/utils/preflight.js +95 -0
- package/dist/utils/preflight.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace management for Studiograph
|
|
3
|
+
*
|
|
4
|
+
* A workspace is a directory with .studiograph/ folder
|
|
5
|
+
* Similar to how Git works with .git/
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import * as yaml from 'js-yaml';
|
|
10
|
+
import { RepoType } from './types.js';
|
|
11
|
+
import { setUserModelConfig } from './user-config.js';
|
|
12
|
+
export class Workspace {
|
|
13
|
+
workspacePath;
|
|
14
|
+
configPath;
|
|
15
|
+
jsonConfigPath;
|
|
16
|
+
membersPath;
|
|
17
|
+
config = null;
|
|
18
|
+
constructor(workspacePath = process.cwd()) {
|
|
19
|
+
this.workspacePath = workspacePath;
|
|
20
|
+
this.configPath = join(workspacePath, '.studiograph', 'config.yml'); // Legacy YAML format
|
|
21
|
+
this.jsonConfigPath = join(workspacePath, '.studiograph', 'workspace.json'); // New JSON format
|
|
22
|
+
this.membersPath = join(workspacePath, '.studiograph', 'team', 'members.json');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if current directory is a Studiograph workspace
|
|
26
|
+
* Supports both workspace.json (new) and config.yml (legacy)
|
|
27
|
+
*/
|
|
28
|
+
isWorkspace() {
|
|
29
|
+
return existsSync(this.jsonConfigPath) || existsSync(this.configPath);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize new workspace in current directory
|
|
33
|
+
*/
|
|
34
|
+
init(teamName) {
|
|
35
|
+
if (this.isWorkspace()) {
|
|
36
|
+
throw new Error('Already a Studiograph workspace');
|
|
37
|
+
}
|
|
38
|
+
// Create .studiograph directory and team subdirectory
|
|
39
|
+
const studiographDir = join(this.workspacePath, '.studiograph');
|
|
40
|
+
mkdirSync(join(studiographDir, 'team'), { recursive: true });
|
|
41
|
+
// Create initial config
|
|
42
|
+
const config = {
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
team_name: teamName,
|
|
45
|
+
created_at: new Date().toISOString(),
|
|
46
|
+
repos: [],
|
|
47
|
+
};
|
|
48
|
+
// Write config as workspace.json
|
|
49
|
+
this.writeConfig(config);
|
|
50
|
+
this.config = config;
|
|
51
|
+
// Create initial empty team members file
|
|
52
|
+
const initialMembers = { members: [] };
|
|
53
|
+
writeFileSync(this.membersPath, JSON.stringify(initialMembers, null, 2), 'utf-8');
|
|
54
|
+
// Create .gitignore for .studiograph
|
|
55
|
+
const gitignorePath = join(studiographDir, '.gitignore');
|
|
56
|
+
writeFileSync(gitignorePath, '# Ignore sensitive local config\n*.local\n');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load workspace config
|
|
60
|
+
* Reads workspace.json (new format) or falls back to config.yml (legacy)
|
|
61
|
+
*/
|
|
62
|
+
loadConfig() {
|
|
63
|
+
if (!this.isWorkspace()) {
|
|
64
|
+
throw new Error('Not a Studiograph workspace. Run `studiograph init` first.');
|
|
65
|
+
}
|
|
66
|
+
if (this.config) {
|
|
67
|
+
return this.config;
|
|
68
|
+
}
|
|
69
|
+
if (existsSync(this.jsonConfigPath)) {
|
|
70
|
+
const configJson = readFileSync(this.jsonConfigPath, 'utf-8');
|
|
71
|
+
this.config = JSON.parse(configJson);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Legacy YAML fallback
|
|
75
|
+
const configYaml = readFileSync(this.configPath, 'utf-8');
|
|
76
|
+
this.config = yaml.load(configYaml);
|
|
77
|
+
}
|
|
78
|
+
return this.config;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Write workspace config as workspace.json
|
|
82
|
+
*/
|
|
83
|
+
writeConfig(config) {
|
|
84
|
+
writeFileSync(this.jsonConfigPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Load team members from team/members.json
|
|
88
|
+
*/
|
|
89
|
+
loadMembers() {
|
|
90
|
+
if (!existsSync(this.membersPath)) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const membersJson = readFileSync(this.membersPath, 'utf-8');
|
|
94
|
+
const data = JSON.parse(membersJson);
|
|
95
|
+
return data.members;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Write team members to team/members.json
|
|
99
|
+
*/
|
|
100
|
+
writeMembers(members) {
|
|
101
|
+
const membersDir = join(this.workspacePath, '.studiograph', 'team');
|
|
102
|
+
mkdirSync(membersDir, { recursive: true });
|
|
103
|
+
writeFileSync(this.membersPath, JSON.stringify({ members }, null, 2), 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the GitHub org stored in workspace.json
|
|
107
|
+
*/
|
|
108
|
+
getGitHubOrg() {
|
|
109
|
+
const config = this.loadConfig();
|
|
110
|
+
return config.github?.org;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Set the GitHub org in workspace.json
|
|
114
|
+
*/
|
|
115
|
+
setGitHubOrg(org) {
|
|
116
|
+
const config = this.loadConfig();
|
|
117
|
+
config.github = { ...config.github, org };
|
|
118
|
+
this.writeConfig(config);
|
|
119
|
+
this.config = config;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Update model configuration.
|
|
123
|
+
*
|
|
124
|
+
* Provider and model ID are written to user config (~/.studiograph/user.json)
|
|
125
|
+
* so they are personal and never committed to the shared workspace repo.
|
|
126
|
+
* The API key is also written only to user config — it is never stored in
|
|
127
|
+
* workspace.json.
|
|
128
|
+
*/
|
|
129
|
+
setModelConfig(provider, modelId, apiKey) {
|
|
130
|
+
setUserModelConfig(provider, modelId, apiKey);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Generate README content for a repo
|
|
134
|
+
*/
|
|
135
|
+
generateRepoReadme(repo) {
|
|
136
|
+
const repoTypeDescriptions = {
|
|
137
|
+
project: 'Project Repository',
|
|
138
|
+
function: 'Function Repository',
|
|
139
|
+
'shared-resource': 'Shared Resource Repository',
|
|
140
|
+
};
|
|
141
|
+
const repoTypeName = repoTypeDescriptions[repo.type] || 'Repository';
|
|
142
|
+
return `# ${repo.name}
|
|
143
|
+
|
|
144
|
+
**Type:** ${repoTypeName}
|
|
145
|
+
**Access:** ${repo.access}
|
|
146
|
+
|
|
147
|
+
${repo.description || 'No description provided.'}
|
|
148
|
+
|
|
149
|
+
## About This Repository
|
|
150
|
+
|
|
151
|
+
${this.getRepoTypeGuidance(repo.type)}
|
|
152
|
+
|
|
153
|
+
## Getting Started
|
|
154
|
+
|
|
155
|
+
This repository is part of a Studiograph workspace. Entities are stored as markdown files with YAML frontmatter.
|
|
156
|
+
|
|
157
|
+
### File Structure
|
|
158
|
+
|
|
159
|
+
Organize your files however makes sense for your team. Common patterns:
|
|
160
|
+
- By entity type (e.g., \`clients/\`, \`projects/\`, \`meetings/\`)
|
|
161
|
+
- By date (e.g., \`2026/02/\`)
|
|
162
|
+
- By project or topic
|
|
163
|
+
- Flat structure (all files in root)
|
|
164
|
+
|
|
165
|
+
### Creating Entities
|
|
166
|
+
|
|
167
|
+
Each entity is a markdown file with YAML frontmatter:
|
|
168
|
+
|
|
169
|
+
\`\`\`markdown
|
|
170
|
+
---
|
|
171
|
+
entity_type: example
|
|
172
|
+
entity_id: my-entity
|
|
173
|
+
created_at: 2026-02-15T00:00:00Z
|
|
174
|
+
updated_at: 2026-02-15T00:00:00Z
|
|
175
|
+
created_by: username
|
|
176
|
+
updated_by: username
|
|
177
|
+
tags: [tag1, tag2]
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
# Entity content goes here
|
|
181
|
+
|
|
182
|
+
Use [[wikilinks]] to reference other entities.
|
|
183
|
+
\`\`\`
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get guidance text for a repo type
|
|
188
|
+
*/
|
|
189
|
+
getRepoTypeGuidance(type) {
|
|
190
|
+
switch (type) {
|
|
191
|
+
case RepoType.PROJECT:
|
|
192
|
+
return `This is a **Project Repository** - typically one per client or major initiative.
|
|
193
|
+
|
|
194
|
+
Store project-specific entities like:
|
|
195
|
+
- Project overview and status
|
|
196
|
+
- Client information
|
|
197
|
+
- Meeting notes
|
|
198
|
+
- Decisions and rationale
|
|
199
|
+
- Deliverables and artifacts
|
|
200
|
+
- Project-specific resources`;
|
|
201
|
+
case RepoType.FUNCTION:
|
|
202
|
+
return `This is a **Function Repository** - for business operations and internal functions.
|
|
203
|
+
|
|
204
|
+
Store operational entities like:
|
|
205
|
+
- Processes and workflows
|
|
206
|
+
- Policies and guidelines
|
|
207
|
+
- Team documentation
|
|
208
|
+
- Internal meetings and decisions
|
|
209
|
+
- Function-specific resources`;
|
|
210
|
+
case RepoType.SHARED_RESOURCE:
|
|
211
|
+
return `This is a **Shared Resource Repository** - for team-wide knowledge and resources.
|
|
212
|
+
|
|
213
|
+
Store shared entities like:
|
|
214
|
+
- Templates and boilerplates
|
|
215
|
+
- Best practices and standards
|
|
216
|
+
- Research and references
|
|
217
|
+
- Techniques and methods
|
|
218
|
+
- Frameworks and methodologies
|
|
219
|
+
- External examples and inspiration`;
|
|
220
|
+
default:
|
|
221
|
+
return 'This repository can store any type of entity relevant to your team.';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Register a new repo in the workspace
|
|
226
|
+
*/
|
|
227
|
+
registerRepo(repo) {
|
|
228
|
+
const config = this.loadConfig();
|
|
229
|
+
// Check if repo already exists
|
|
230
|
+
if (config.repos.find(r => r.name === repo.name)) {
|
|
231
|
+
throw new Error(`Repo '${repo.name}' already registered`);
|
|
232
|
+
}
|
|
233
|
+
// Create repo config with path and default access
|
|
234
|
+
const repoConfig = {
|
|
235
|
+
...repo,
|
|
236
|
+
path: repo.name, // Path relative to workspace root
|
|
237
|
+
access: repo.access ?? 'team', // Default to 'team' if not specified
|
|
238
|
+
};
|
|
239
|
+
// Add to config
|
|
240
|
+
config.repos.push(repoConfig);
|
|
241
|
+
this.writeConfig(config);
|
|
242
|
+
this.config = config;
|
|
243
|
+
// Create repo directory
|
|
244
|
+
const repoPath = join(this.workspacePath, repo.name);
|
|
245
|
+
mkdirSync(repoPath, { recursive: true });
|
|
246
|
+
// Create .studiograph marker in repo
|
|
247
|
+
const repoMarkerPath = join(repoPath, '.studiograph');
|
|
248
|
+
writeFileSync(repoMarkerPath, yaml.dump({ repo_type: repo.type, repo_name: repo.name }), 'utf-8');
|
|
249
|
+
// Create README.md with repo description
|
|
250
|
+
const readmePath = join(repoPath, 'README.md');
|
|
251
|
+
const readmeContent = this.generateRepoReadme(repoConfig);
|
|
252
|
+
writeFileSync(readmePath, readmeContent, 'utf-8');
|
|
253
|
+
return repoConfig;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get all registered repos
|
|
257
|
+
*/
|
|
258
|
+
getRepos() {
|
|
259
|
+
const config = this.loadConfig();
|
|
260
|
+
return config.repos;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get repo by name
|
|
264
|
+
*/
|
|
265
|
+
getRepo(name) {
|
|
266
|
+
const config = this.loadConfig();
|
|
267
|
+
return config.repos.find(r => r.name === name);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get absolute path to repo
|
|
271
|
+
*/
|
|
272
|
+
getRepoPath(repoName) {
|
|
273
|
+
const repo = this.getRepo(repoName);
|
|
274
|
+
if (!repo) {
|
|
275
|
+
throw new Error(`Repo '${repoName}' not found`);
|
|
276
|
+
}
|
|
277
|
+
return join(this.workspacePath, repo.path);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get workspace root path
|
|
281
|
+
*/
|
|
282
|
+
getWorkspacePath() {
|
|
283
|
+
return this.workspacePath;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Find workspace root by walking up directory tree
|
|
287
|
+
* (like git does with .git/)
|
|
288
|
+
*/
|
|
289
|
+
static findWorkspaceRoot(startPath = process.cwd()) {
|
|
290
|
+
let currentPath = startPath;
|
|
291
|
+
while (currentPath !== '/') {
|
|
292
|
+
const jsonConfigPath = join(currentPath, '.studiograph', 'workspace.json');
|
|
293
|
+
const yamlConfigPath = join(currentPath, '.studiograph', 'config.yml');
|
|
294
|
+
if (existsSync(jsonConfigPath) || existsSync(yamlConfigPath)) {
|
|
295
|
+
return currentPath;
|
|
296
|
+
}
|
|
297
|
+
// Go up one directory
|
|
298
|
+
const parentPath = join(currentPath, '..');
|
|
299
|
+
if (parentPath === currentPath)
|
|
300
|
+
break; // Reached root
|
|
301
|
+
currentPath = parentPath;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../src/core/workspace.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studiograph HTTP Server
|
|
3
|
+
*
|
|
4
|
+
* Hosts:
|
|
5
|
+
* - Graph API (read-only REST over WorkspaceManager)
|
|
6
|
+
* - Agent chat via POST /api/chat and SSE GET /api/chat/stream
|
|
7
|
+
* - GitHub webhook for git pull on push (Config 2 deployments)
|
|
8
|
+
* - App plugins mounted via PluginLoader
|
|
9
|
+
*/
|
|
10
|
+
import { type FastifyInstance } from 'fastify';
|
|
11
|
+
import { WorkspaceManager } from '../core/workspace-manager.js';
|
|
12
|
+
import { GitUser } from '../services/git.js';
|
|
13
|
+
import { WorkspaceConfig } from '../core/types.js';
|
|
14
|
+
export interface ServerConfig {
|
|
15
|
+
workspaceManager: WorkspaceManager;
|
|
16
|
+
workspacePath: string;
|
|
17
|
+
workspaceConfig: WorkspaceConfig;
|
|
18
|
+
gitUser: GitUser;
|
|
19
|
+
appsDir: string;
|
|
20
|
+
port: number;
|
|
21
|
+
host: string;
|
|
22
|
+
apiKeys: string[];
|
|
23
|
+
webhookSecret?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function createServer(config: ServerConfig): Promise<FastifyInstance>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studiograph HTTP Server
|
|
3
|
+
*
|
|
4
|
+
* Hosts:
|
|
5
|
+
* - Graph API (read-only REST over WorkspaceManager)
|
|
6
|
+
* - Agent chat via POST /api/chat and SSE GET /api/chat/stream
|
|
7
|
+
* - GitHub webhook for git pull on push (Config 2 deployments)
|
|
8
|
+
* - App plugins mounted via PluginLoader
|
|
9
|
+
*/
|
|
10
|
+
import Fastify from 'fastify';
|
|
11
|
+
import cors from '@fastify/cors';
|
|
12
|
+
import { AgentOrchestrator } from '../agent/orchestrator.js';
|
|
13
|
+
import { PluginLoader } from './plugin-loader.js';
|
|
14
|
+
import { registerGraphApiRoutes } from './routes/graph-api.js';
|
|
15
|
+
import { registerChatRoutes } from './routes/chat.js';
|
|
16
|
+
import { registerWebhookRoutes } from './routes/webhook.js';
|
|
17
|
+
/**
|
|
18
|
+
* Auth middleware — validates Authorization: Bearer <key> header.
|
|
19
|
+
* Applied to all write-adjacent or sensitive routes.
|
|
20
|
+
*/
|
|
21
|
+
function requireApiKey(apiKeys) {
|
|
22
|
+
return async (req, reply) => {
|
|
23
|
+
if (apiKeys.length === 0)
|
|
24
|
+
return; // no keys configured — open (dev mode)
|
|
25
|
+
const auth = req.headers['authorization'];
|
|
26
|
+
const token = auth?.startsWith('Bearer ') ? auth.slice(7) : undefined;
|
|
27
|
+
if (!token || !apiKeys.includes(token)) {
|
|
28
|
+
return reply.status(401).send({ error: 'Unauthorized' });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function createServer(config) {
|
|
33
|
+
const fastify = Fastify({ logger: true });
|
|
34
|
+
// CORS
|
|
35
|
+
await fastify.register(cors, {
|
|
36
|
+
origin: true,
|
|
37
|
+
});
|
|
38
|
+
// Parse raw body for webhook HMAC validation
|
|
39
|
+
fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => {
|
|
40
|
+
try {
|
|
41
|
+
req.rawBody = body;
|
|
42
|
+
done(null, JSON.parse(body.toString()));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
done(err, undefined);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// Auth hook — protect chat and webhook routes
|
|
49
|
+
const authHook = requireApiKey(config.apiKeys);
|
|
50
|
+
fastify.addHook('preHandler', async (req, reply) => {
|
|
51
|
+
const protectedPaths = ['/api/chat', '/webhooks/'];
|
|
52
|
+
if (protectedPaths.some(p => req.url.startsWith(p))) {
|
|
53
|
+
await authHook(req, reply);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Register graph API routes (unauthenticated read-only)
|
|
57
|
+
await registerGraphApiRoutes(fastify, config.workspaceManager);
|
|
58
|
+
// Initialize agent for chat routes
|
|
59
|
+
const agent = new AgentOrchestrator({
|
|
60
|
+
workspacePath: config.workspacePath,
|
|
61
|
+
workspaceConfig: config.workspaceConfig,
|
|
62
|
+
gitUser: config.gitUser,
|
|
63
|
+
});
|
|
64
|
+
await registerChatRoutes(fastify, agent);
|
|
65
|
+
// GitHub webhook route
|
|
66
|
+
await registerWebhookRoutes(fastify, config.workspaceManager, config.webhookSecret);
|
|
67
|
+
// Load and mount app plugins
|
|
68
|
+
const loader = new PluginLoader();
|
|
69
|
+
const manifests = loader.loadApps(config.appsDir);
|
|
70
|
+
for (const manifest of manifests) {
|
|
71
|
+
try {
|
|
72
|
+
await loader.mountPlugin(fastify, manifest, config.appsDir, {
|
|
73
|
+
workspaceManager: config.workspaceManager,
|
|
74
|
+
gitUser: config.gitUser,
|
|
75
|
+
});
|
|
76
|
+
fastify.log.info(`Mounted app "${manifest.name}" at ${manifest.mountPath}`);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
fastify.log.error(err, `Failed to mount app "${manifest.name}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return fastify;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Loader
|
|
3
|
+
*
|
|
4
|
+
* Scans .studiograph/apps/ for installed apps, parses their manifests,
|
|
5
|
+
* and mounts their Fastify plugins under the declared mountPath.
|
|
6
|
+
*/
|
|
7
|
+
import type { FastifyInstance } from 'fastify';
|
|
8
|
+
import { WorkspaceManager } from '../core/workspace-manager.js';
|
|
9
|
+
import { GitUser } from '../services/git.js';
|
|
10
|
+
export interface AppManifest {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
plugin: string;
|
|
15
|
+
entityTypes: string;
|
|
16
|
+
mountPath: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class PluginLoader {
|
|
19
|
+
/**
|
|
20
|
+
* Scan appsDir for installed apps and return their manifests.
|
|
21
|
+
* Returns empty array if the directory does not exist.
|
|
22
|
+
*/
|
|
23
|
+
loadApps(appsDir: string): AppManifest[];
|
|
24
|
+
/**
|
|
25
|
+
* Dynamically import and register a plugin under its mountPath.
|
|
26
|
+
*/
|
|
27
|
+
mountPlugin(fastify: FastifyInstance, manifest: AppManifest, appDir: string, opts: {
|
|
28
|
+
workspaceManager: WorkspaceManager;
|
|
29
|
+
gitUser: GitUser;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Loader
|
|
3
|
+
*
|
|
4
|
+
* Scans .studiograph/apps/ for installed apps, parses their manifests,
|
|
5
|
+
* and mounts their Fastify plugins under the declared mountPath.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
export class PluginLoader {
|
|
10
|
+
/**
|
|
11
|
+
* Scan appsDir for installed apps and return their manifests.
|
|
12
|
+
* Returns empty array if the directory does not exist.
|
|
13
|
+
*/
|
|
14
|
+
loadApps(appsDir) {
|
|
15
|
+
if (!existsSync(appsDir)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const manifests = [];
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = readdirSync(appsDir);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const manifestPath = join(appsDir, entry, 'app.json');
|
|
28
|
+
if (!existsSync(manifestPath)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const raw = readFileSync(manifestPath, 'utf-8');
|
|
33
|
+
const manifest = JSON.parse(raw);
|
|
34
|
+
if (!manifest.plugin) {
|
|
35
|
+
throw new Error(`app.json in "${entry}" is missing required field: plugin`);
|
|
36
|
+
}
|
|
37
|
+
if (!manifest.mountPath) {
|
|
38
|
+
throw new Error(`app.json in "${entry}" is missing required field: mountPath`);
|
|
39
|
+
}
|
|
40
|
+
manifests.push({
|
|
41
|
+
name: manifest.name || entry,
|
|
42
|
+
version: manifest.version || '0.0.0',
|
|
43
|
+
description: manifest.description,
|
|
44
|
+
plugin: manifest.plugin,
|
|
45
|
+
entityTypes: manifest.entityTypes || './entity-types.json',
|
|
46
|
+
mountPath: manifest.mountPath,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// Re-throw validation errors; skip JSON parse errors gracefully
|
|
51
|
+
if (error instanceof Error && error.message.includes('missing required field')) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
console.warn(`Failed to load app manifest from ${manifestPath}:`, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return manifests;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Dynamically import and register a plugin under its mountPath.
|
|
61
|
+
*/
|
|
62
|
+
async mountPlugin(fastify, manifest, appDir, opts) {
|
|
63
|
+
const pluginPath = join(appDir, manifest.name, manifest.plugin);
|
|
64
|
+
if (!existsSync(pluginPath)) {
|
|
65
|
+
throw new Error(`Plugin file not found: ${pluginPath}`);
|
|
66
|
+
}
|
|
67
|
+
// Dynamic import — plugin must export a default Fastify plugin function
|
|
68
|
+
const mod = await import(pluginPath);
|
|
69
|
+
const pluginFn = mod.default ?? mod;
|
|
70
|
+
if (typeof pluginFn !== 'function') {
|
|
71
|
+
throw new Error(`Plugin at ${pluginPath} does not export a function`);
|
|
72
|
+
}
|
|
73
|
+
await fastify.register(pluginFn, {
|
|
74
|
+
prefix: manifest.mountPath,
|
|
75
|
+
workspaceManager: opts.workspaceManager,
|
|
76
|
+
gitUser: opts.gitUser,
|
|
77
|
+
appsDir: appDir,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../src/server/plugin-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat / Agent SSE routes
|
|
3
|
+
*
|
|
4
|
+
* POST /api/chat — send a message, await full response
|
|
5
|
+
* GET /api/chat/stream — SSE stream of agent events (future)
|
|
6
|
+
*
|
|
7
|
+
* Single agent instance per server process (single-user v1).
|
|
8
|
+
*/
|
|
9
|
+
import type { FastifyInstance } from 'fastify';
|
|
10
|
+
import { AgentOrchestrator } from '../../agent/orchestrator.js';
|
|
11
|
+
export declare function registerChatRoutes(fastify: FastifyInstance, agent: AgentOrchestrator): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat / Agent SSE routes
|
|
3
|
+
*
|
|
4
|
+
* POST /api/chat — send a message, await full response
|
|
5
|
+
* GET /api/chat/stream — SSE stream of agent events (future)
|
|
6
|
+
*
|
|
7
|
+
* Single agent instance per server process (single-user v1).
|
|
8
|
+
*/
|
|
9
|
+
export async function registerChatRoutes(fastify, agent) {
|
|
10
|
+
// POST /api/chat — send message, get full response
|
|
11
|
+
fastify.post('/api/chat', async (req, reply) => {
|
|
12
|
+
const { message } = req.body;
|
|
13
|
+
if (!message || typeof message !== 'string' || !message.trim()) {
|
|
14
|
+
return reply.status(400).send({ error: 'message is required' });
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const response = await agent.chat(message);
|
|
18
|
+
return reply.send(response);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
fastify.log.error(err, 'Agent chat error');
|
|
22
|
+
return reply.status(500).send({ error: err.message || 'Agent error' });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
// GET /api/chat/stream — SSE stream of agent events
|
|
26
|
+
fastify.get('/api/chat/stream', async (req, reply) => {
|
|
27
|
+
const { message } = req.query;
|
|
28
|
+
if (!message || !message.trim()) {
|
|
29
|
+
return reply.status(400).send({ error: 'message query param is required' });
|
|
30
|
+
}
|
|
31
|
+
// Set SSE headers
|
|
32
|
+
reply.raw.setHeader('Content-Type', 'text/event-stream');
|
|
33
|
+
reply.raw.setHeader('Cache-Control', 'no-cache');
|
|
34
|
+
reply.raw.setHeader('Connection', 'keep-alive');
|
|
35
|
+
reply.raw.flushHeaders();
|
|
36
|
+
const sendEvent = (data) => {
|
|
37
|
+
reply.raw.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
// Subscribe to agent events before sending message
|
|
41
|
+
const unsubscribe = agent.agent?.subscribe?.((event) => {
|
|
42
|
+
if (event.type === 'message_update') {
|
|
43
|
+
const msgEvent = event.assistantMessageEvent;
|
|
44
|
+
if (msgEvent?.type === 'text_delta') {
|
|
45
|
+
sendEvent({ type: 'token', text: msgEvent.delta });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (event.type === 'tool_start') {
|
|
49
|
+
sendEvent({ type: 'tool_start', name: event.name });
|
|
50
|
+
}
|
|
51
|
+
else if (event.type === 'tool_end') {
|
|
52
|
+
sendEvent({ type: 'tool_end', name: event.name });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const response = await agent.chat(message);
|
|
56
|
+
if (unsubscribe)
|
|
57
|
+
unsubscribe();
|
|
58
|
+
sendEvent({ type: 'done', message: response.content });
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
sendEvent({ type: 'error', message: err.message || 'Agent error' });
|
|
62
|
+
}
|
|
63
|
+
reply.raw.end();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../../src/server/routes/chat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph API routes
|
|
3
|
+
*
|
|
4
|
+
* Read-only REST façade over WorkspaceManager.
|
|
5
|
+
* All writes go through the agent (/api/chat).
|
|
6
|
+
*/
|
|
7
|
+
import type { FastifyInstance } from 'fastify';
|
|
8
|
+
import { WorkspaceManager } from '../../core/workspace-manager.js';
|
|
9
|
+
export declare function registerGraphApiRoutes(fastify: FastifyInstance, workspaceManager: WorkspaceManager): Promise<void>;
|