Workspace API
The Workspace API provides discovery of project directories on the filesystem, bulk import into ainative-business with automatic environment scanning, and runtime context about the current workspace. The import endpoint uses SSE to stream progress as each project is created and scanned.
Quick Start
Discover projects in a parent directory, then import the new ones with streamed progress:
// 1. Discover project directories under ~/Developer
interface DiscoveredProject {
path: string;
name: string;
markers: string[];
alreadyImported: boolean;
}
const { projects }: { projects: DiscoveredProject[] } = await fetch('/api/workspace/discover', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
parentDir: '~/Developer',
maxDepth: 2,
markers: ['package.json', 'Cargo.toml', 'go.mod'],
}),
}).then((r: Response) => r.json());
// 2. Filter to projects not yet imported
const newProjects: DiscoveredProject[] = projects.filter((p) => !p.alreadyImported);
console.log(`Found ${projects.length} projects, ${newProjects.length} new`);
// 3. Import new projects with streamed progress (SSE)
const res: Response = await fetch('/api/workspace/import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projects: newProjects.map((p) => ({ path: p.path, name: p.name })),
}),
});
const reader: ReadableStreamDefaultReader<Uint8Array> = res.body!.getReader();
const decoder: TextDecoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text: string = decoder.decode(value);
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
const event: { type: string; name?: string; status?: string; created?: number; failed?: number } =
JSON.parse(line.slice(6));
if (event.type === 'progress') {
console.log(`${event.name}: ${event.status}`);
} else if (event.type === 'complete') {
console.log(`Done! ${event.created} imported, ${event.failed} failed`);
}
}
}
} Base URL
/api/workspace
Endpoints
Get Workspace Context
/api/workspace/context Return the current workspace context including launch directory, platform info, and runtime state. Cached for 60 seconds.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| cwd | string | * | Current working directory at launch |
| platform | string | * | Operating system platform |
| arch | string | * | CPU architecture |
| nodeVersion | string | * | Node.js version |
| homedir | string | * | User home directory |
Get runtime context — useful for displaying workspace info in the UI or setting default paths:
// Get workspace context for the UI
interface WorkspaceContext {
cwd: string;
platform: string;
arch: string;
nodeVersion: string;
homedir: string;
}
const context: WorkspaceContext = await fetch('/api/workspace/context')
.then((r: Response) => r.json());
console.log(`CWD: ${context.cwd}`);
console.log(`Platform: ${context.platform} (${context.arch})`);
console.log(`Node: ${context.nodeVersion}`);
console.log(`Home: ${context.homedir}`); Example response:
{
"cwd": "/Users/team/Developer",
"platform": "darwin",
"arch": "arm64",
"nodeVersion": "v22.4.0",
"homedir": "/Users/team"
} Discover Projects
/api/workspace/discover Scan a parent directory for project directories using configurable marker files (package.json, Cargo.toml, etc.). Flags projects that are already imported into `ainative-business`.
Request Body
| Field | Type | Req | Description |
|---|---|---|---|
| parentDir | string | * | Parent directory to scan (supports ~ expansion) |
| maxDepth | number | — | Maximum directory depth to search |
| markers | string[] | — | Marker filenames to detect projects (e.g., package.json, .git) |
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| projects | object[] | * | Discovered project directories |
| projects[].path | string | * | Absolute path to the project |
| projects[].name | string | * | Directory name |
| projects[].markers | string[] | * | Which marker files were found |
| projects[].alreadyImported | boolean | * | Whether this project already exists in `ainative-business` |
Errors: 400 — Validation failure or directory not found
Scan a developer directory to find all project directories — already-imported projects are flagged so you can skip them:
// Discover project directories
interface DiscoveredProject {
path: string;
name: string;
markers: string[];
alreadyImported: boolean;
}
const { projects }: { projects: DiscoveredProject[] } = await fetch('/api/workspace/discover', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
parentDir: '~/Developer',
maxDepth: 2,
}),
}).then((r: Response) => r.json());
// Show discovered projects, highlighting new ones
projects.forEach((p: DiscoveredProject) => {
const status: string = p.alreadyImported ? 'imported' : 'NEW';
console.log(`[${status}] ${p.name} — ${p.path} (${p.markers.join(', ')})`);
});
const newProjects: DiscoveredProject[] = projects.filter((p) => !p.alreadyImported);
console.log(`${newProjects.length} new projects ready to import`); Example response:
{
"projects": [
{
"path": "/Users/team/Developer/my-app",
"name": "my-app",
"markers": ["package.json", ".git"],
"alreadyImported": false
},
{
"path": "/Users/team/Developer/api-server",
"name": "api-server",
"markers": ["package.json", ".git"],
"alreadyImported": true
},
{
"path": "/Users/team/Developer/rust-cli",
"name": "rust-cli",
"markers": ["Cargo.toml", ".git"],
"alreadyImported": false
}
]
} Fix Data Directory
/api/workspace/fix-data-dir Fix a data-directory mismatch for domain-cloned `ainative-business` instances. Derives the correct AINATIVE_DATA_DIR from the folder name, writes it to .env.local alongside AINATIVE_CLOUD_DISABLED=true, and bootstraps a fresh SQLite database at that path. A server restart is required to take effect.
Response Body
| Field | Type | Req | Description |
|---|---|---|---|
| success | boolean | * | Whether the fix was applied |
| dataDir | string | * | The derived data directory path (e.g. ~/.ainative-wealth) |
| envLocalPath | string | * | Absolute path to the .env.local file that was updated |
| needsRestart | boolean | * | Always true — the server must be restarted to load the new database path |
Errors:
400— The main dev repo does not need a fix, orAINATIVE_DATA_DIRis already set to a non-default path
Use this endpoint once after cloning ainative-business to a new folder name (e.g. ainative-wealth) to isolate its database from the main instance:
// Apply data-dir isolation for a domain clone — run once after cloning
const res: Response = await fetch('http://localhost:3000/api/workspace/fix-data-dir', {
method: 'POST',
});
if (res.ok) {
const result: { success: boolean; dataDir: string; envLocalPath: string; needsRestart: boolean } =
await res.json();
console.log(`Data dir set to: ${result.dataDir}`);
console.log(`Updated: ${result.envLocalPath}`);
if (result.needsRestart) {
console.log('Restart the dev server to activate the new database path');
}
} else {
const { error }: { error: string } = await res.json();
console.error(`Fix not needed: ${error}`);
} Example response:
{
"success": true,
"dataDir": "~/.ainative-wealth",
"envLocalPath": "/Users/team/Developer/ainative-wealth/.env.local",
"needsRestart": true
} Import Projects (SSE)
/api/workspace/import Bulk-import discovered projects into `ainative-business`. Each project is created with an automatic environment scan. Progress is streamed via Server-Sent Events so the UI can update in real time.
Request Body (POST)
| Field | Type | Req | Description |
|---|---|---|---|
| projects | object[] | * | Array of projects to import |
| projects[].path | string | * | Absolute path to the project directory |
| projects[].name | string | * | Project name |
SSE Event Types:
| Event type | Fields | Description |
|---|---|---|
progress | index, name, status: "creating" | Import started for a project |
progress | index, name, status: "done", projectId, artifactCount | Project created and scanned |
progress | index, name, status: "failed", error | Import failed for a project |
complete | created, failed | All imports finished |
Errors: 400 — Validation failure
Import multiple projects at once and stream progress to the UI — each project gets an environment scan automatically:
// Import projects with streamed progress
const res: Response = await fetch('/api/workspace/import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projects: [
{ path: '/Users/team/Developer/my-app', name: 'My App' },
{ path: '/Users/team/Developer/rust-cli', name: 'Rust CLI' },
],
}),
});
// Read the SSE stream
const reader: ReadableStreamDefaultReader<Uint8Array> = res.body!.getReader();
const decoder: TextDecoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text: string = decoder.decode(value);
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
const event: Record<string, unknown> = JSON.parse(line.slice(6));
if (event.type === 'progress') {
if (event.status === 'creating') {
console.log(`Importing ${event.name}...`);
} else if (event.status === 'done') {
console.log(` Done: ${event.projectId} (${event.artifactCount} artifacts)`);
} else if (event.status === 'failed') {
console.error(` Failed: ${event.error}`);
}
} else if (event.type === 'complete') {
console.log(`Import complete: ${event.created} created, ${event.failed} failed`);
}
}
}
} Example stream events:
data: {"type":"progress","index":0,"name":"My App","status":"creating"}
data: {"type":"progress","index":0,"name":"My App","status":"done","projectId":"proj-1a2b-3c4d","artifactCount":24}
data: {"type":"progress","index":1,"name":"Rust CLI","status":"creating"}
data: {"type":"progress","index":1,"name":"Rust CLI","status":"done","projectId":"proj-5e6f-7g8h","artifactCount":12}
data: {"type":"complete","created":2,"failed":0}