Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions .architecture/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# SynthOS / SynthTabs Architecture

## Overview

SynthOS is an AI-powered local web application builder. Users create fully functional
HTML/CSS/JS apps through conversational chat — no manual coding required. **SynthTabs**
is a fork that adds Microsoft Teams integration via the **Customizer** extensibility
layer.

Installed via `npm install -g synthos`, run with `synthos start`. Node.js v20+.

---

## Tech Stack

| Layer | Technology |
|------------- |-------------------------------------------------|
| Language | TypeScript (strict, ESNext target) |
| Server | Express.js |
| DOM Engine | cheerio (server-side HTML manipulation) |
| AI Providers | Anthropic SDK, OpenAI SDK, FireworksAI (OpenAI-compat) |
| Agent Protocols | A2A SDK, OpenClaw (WebSocket) |
| CLI | yargs |
| Tests | Mocha + ts-mocha, nyc (coverage) |

---

## Folder Structure

```
synthtabs/
├── bin/ CLI entry (synthos.js)
├── src/ TypeScript source
│ ├── index.ts Public exports
│ ├── init.ts Config creation, first-run setup
│ ├── files.ts File I/O helpers
│ ├── pages.ts Page CRUD, metadata, versioning
│ ├── scripts.ts Shell script execution
│ ├── settings.ts Settings persistence (v2)
│ ├── themes.ts Theme loading & parsing
│ ├── migrations.ts Page version upgrades (v1 → v2)
│ ├── synthos-cli.ts CLI command parser
│ ├── customizer/
│ │ ├── Customizer.ts Extensibility framework
│ │ └── index.ts Default Customizer instance
│ ├── service/
│ │ ├── server.ts Express app assembly
│ │ ├── usePageRoutes.ts GET/POST /:page (serve + transform)
│ │ ├── useApiRoutes.ts /api/pages, /api/themes, /api/settings, etc.
│ │ ├── useConnectorRoutes.ts /api/connectors (OAuth2, proxy)
│ │ ├── useAgentRoutes.ts /api/agents (A2A + OpenClaw streaming)
│ │ ├── useDataRoutes.ts /api/data/:page/:table (JSON CRUD)
│ │ ├── transformPage.ts Core page transformation pipeline
│ │ ├── createCompletePrompt.ts Provider-specific prompt routing
│ │ ├── modelInstructions.ts Per-model prompt tuning
│ │ ├── generateImage.ts DALL-E 3 image generation
│ │ ├── requiresSettings.ts Middleware: settings guard
│ │ └── debugLog.ts Colored debug logging
│ ├── models/ LLM provider implementations
│ │ ├── anthropic.ts Claude (Opus/Sonnet/Haiku)
│ │ ├── openai.ts GPT (5.2/5-mini/5-nano)
│ │ └── fireworksai.ts FireworksAI (GLM-5)
│ ├── agents/ Agent protocol implementations
│ │ ├── a2aProvider.ts A2A HTTP protocol
│ │ └── openclawProvider.ts OpenClaw WebSocket + SSH tunnels
│ └── connectors/ Connector registry loader
├── default-pages/ Starter HTML templates (copied on init)
├── default-scripts/ OS-specific shell script templates
├── default-themes/ Theme CSS + JSON (Nebula Dusk/Dawn)
├── page-scripts/ Versioned page scripts (page-v2.js, helpers-v2.js)
├── required-pages/ System pages (builder, settings, pages, scripts, apis)
├── service-connectors/ 28+ connector JSON definitions
├── migration-rules/ Markdown rules for page upgrades
├── tests/ Unit tests
├── teams-default-pages/ [TEAMS] Custom page templates
├── teams-default-scripts/ [TEAMS] Custom shell scripts
├── teams-default-themes/ [TEAMS] Custom themes
├── teams-page-scripts/ [TEAMS] Custom versioned page scripts
├── teams-required-pages/ [TEAMS] Custom system pages
├── teams-service-connectors/ [TEAMS] Custom connectors
└── .synthos/ USER DATA (created at runtime, git-ignored)
├── settings.json API keys, model config, features
├── pages/<name>/ page.html + page.json + data tables
├── themes/ Local theme copies
└── scripts/ User shell scripts
```

---

## Core Loop: Page Transformation

This is the heart of SynthOS — every page edit goes through this pipeline:

```
User types message in chat panel
POST /:page (usePageRoutes.ts)
transformPage() (transformPage.ts)
1. Assign data-node-id to every element (cheerio)
2. Build LLM prompt with:
- Annotated HTML
- Theme info (CSS variables, colors)
- Enabled connectors + hints
- Enabled agents + capabilities
- Available scripts
- Custom transform instructions (from Customizer)
3. Route to provider (Anthropic / OpenAI / FireworksAI)
4. LLM returns JSON array of change operations:
{ op: "update"|"delete"|"insert", nodeId, html, parentId }
5. Apply changes via cheerio
6. Strip data-node-id attributes
Save updated HTML → Serve to browser
```

On error (bad JSON, missing nodes), the original page is returned unchanged
with an injected `<script id="error">` block.

---

## Key Interfaces

### SynthOSConfig (src/init.ts)
```typescript
interface SynthOSConfig {
pagesFolder: string; // User's .synthos/ directory
requiredPagesFolder: string; // Built-in system pages
defaultPagesFolder: string; // Starter templates
defaultScriptsFolder: string; // OS-specific scripts
defaultThemesFolder: string; // Theme CSS/JSON
pageScriptsFolder: string; // Versioned page scripts
serviceConnectorsFolder: string; // Connector definitions
debug: boolean;
debugPageUpdates: boolean;
}
```

### PageInfo (src/pages.ts)
```typescript
interface PageInfo {
name: string;
title: string;
categories: string[];
pinned: boolean;
showInAll: boolean;
createdDate: string; // ISO 8601
lastModified: string; // ISO 8601
pageVersion: number; // 0=pre, 1=legacy, 2=current
mode: 'unlocked' | 'locked';
}
```

### SettingsV2 (src/settings.ts)
```typescript
interface SettingsV2 {
version: 2;
theme: string;
models: ModelEntry[]; // builder + chat models
features: string[];
connectors?: ServicesConfig;
agents?: AgentConfig[];
}
```

---

## Customizer Architecture (src/customizer/)

The `Customizer` class is the extensibility hook for forks like SynthTabs.
It allows a derived class to:

1. **Override content folder paths** — point to `teams-*` folders instead of defaults
2. **Disable feature groups** — `'pages'`, `'api'`, `'connectors'`, `'agents'`, `'data'`, `'brainstorm'`, `'search'`, `'scripts'`
3. **Add custom Express routes** — with optional LLM routing hints
4. **Inject custom LLM instructions** — appended to transformPage prompt

### How it plugs in

- `createConfig()` in `init.ts` accepts an optional `Customizer` and uses its
folder getters (with `??` fallback to defaults)
- `server()` in `server.ts` checks `customizer.isEnabled(group)` before
mounting each route set
- `server()` installs any extra routes via `customizer.getExtraRoutes()`
- The default singleton in `customizer/index.ts` enables everything with base paths

### For SynthTabs

A `TeamsCustomizer extends Customizer` would override folder getters to return
the `teams-*` paths, disable irrelevant feature groups, and add Teams-specific
routes + LLM instructions.

---

## Route Map

| Route Group | Guard | Key Endpoints |
|-------------------|-------------------------------|---------------|
| Page routes | `isEnabled('pages')` | `GET /:page`, `POST /:page`, `/:page/reset` |
| API routes | `isEnabled('api')` | `/api/pages`, `/api/themes`, `/api/settings`, `/api/generate/*` |
| Connector routes | `isEnabled('connectors')` | `/api/connectors`, `/api/connectors/:id` |
| Agent routes | `isEnabled('agents')` | `/api/agents`, `/api/agents/:id/send` |
| Data routes | `isEnabled('data')` | `/api/data/:page/:table/*` |
| Custom routes | Always installed | Defined by `Customizer.addRoutes()` |

---

## Model Routing

Model prefix determines provider:
- `claude-*` → Anthropic (`src/models/anthropic.ts`)
- `gpt-*` → OpenAI (`src/models/openai.ts`)
- `glm-*` → FireworksAI (`src/models/fireworksai.ts`)

Each model can be assigned to a **use**: `'builder'` (page transformation) or
`'chat'` (in-app conversations). Different models can serve different roles.

---

## Data Storage

All user data lives in `.synthos/` (file-based, no database):

```
.synthos/pages/<page-name>/
├── page.html Page state (full HTML)
├── page.json Metadata (title, version, mode, dates)
└── <table-name>/ Data tables
├── <uuid>.json Individual records
└── ...
```

CRUD via `/api/data/:page/:table` endpoints. Client helper: `synthos.data.*`.

---

## Upstream Reference

The upstream SynthOS source lives at `..\synthos` relative to this repo.
The Customizer pattern means SynthTabs can diverge in content (pages, themes,
connectors) while staying in sync with core logic changes from upstream.
10 changes: 9 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@
"Bash(npm run test:mocha:*)",
"Bash(git -C \"C:/source/synthos\" log --oneline)",
"Bash(git -C \"C:/source/synthos\" log --oneline --all)",
"Bash(git -C C:/source/synthos log --oneline --all)"
"Bash(git -C C:/source/synthos log --oneline --all)",
"Bash(wc:*)",
"Bash(where.exe:*)",
"Bash(winget install:*)",
"Bash(export PATH=\"$PATH:/c/Program Files/GitHub CLI\")",
"Bash(git fetch:*)",
"Bash(git checkout:*)",
"Bash(git merge:*)",
"Bash(git commit:*)"
]
}
}
127 changes: 127 additions & 0 deletions SYNC_FORK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Syncing SynthTabs with Upstream SynthOS

## Upstream Remote

```
upstream = ../synthos (local path)
```

If the git remote `upstream` is not configured yet, add it:

```bash
git remote add upstream ../synthos
```

## Sync Procedure

1. **Fetch upstream changes**
```bash
git fetch upstream main
```

2. **Create a sync branch**
```bash
git checkout -b sync/upstream-YYYY-MM-DD
```

3. **Merge upstream into sync branch**
```bash
git merge upstream/main --no-commit
```

4. **Apply conflict-resolution rules** (see below), then commit and PR.

---

## Conflict-Resolution Rules

### ALWAYS KEEP OURS (never accept upstream)

| File / Pattern | Reason |
|----------------|--------|
| `README.md` | SynthTabs has its own README |
| `package.json` — `name` | Must stay `"synthtabs"` |
| `package.json` — `version` | We maintain our own version |
| `package.json` — `description` | Teams-specific description |
| `package.json` — `keywords` | Teams-specific keywords |
| `package.json` — `bugs` | Points to our repo |
| `package.json` — `repository` | Points to our repo |
| `package.json` — `bin` | Must stay `"./bin/synthtabs.js"` (our CLI) |
| `package.json` — `files` | Includes `teams-*` folders and `teams-tests` |
| `package.json` — `scripts.start` | Must use our CLI entry point |
| `package.json` — `scripts.test:mocha` | Must include both `tests/**/*.spec.ts` and `teams-tests/**/*.spec.ts` |
| `SHARED-MEMORIES.md` | We may have our own project context |
| `.github/` | Our own CI/CD config |
| `.claude/` | Our own Claude Code settings |
| `.experts/` | Our own expert knowledge base |

### ALWAYS ACCEPT UPSTREAM

| File / Pattern | Reason |
|----------------|--------|
| `src/**/*.ts` | Core logic — we extend via Customizer, not by editing source |
| `required-pages/` | System pages come from upstream |
| `default-pages/` | Starter templates come from upstream |
| `default-scripts/` | OS script templates come from upstream |
| `default-themes/` | Base themes come from upstream |
| `page-scripts/` | Versioned page scripts come from upstream |
| `service-connectors/` | Base connector definitions come from upstream |
| `migration-rules/` | Page migration rules come from upstream |
| `tests/` | Upstream test coverage — run alongside our `teams-tests/` |

### MERGE CAREFULLY (manual review required)

| File / Pattern | How to merge |
|----------------|-------------|
| `package.json` — `dependencies` | Accept new/updated deps from upstream. Keep any Teams-specific deps we added. |
| `package.json` — `devDependencies` | Same — accept upstream updates, keep our additions. |
| `package.json` — `scripts` (non-start) | Accept upstream changes to `build`, `test`, etc. Keep our custom scripts. |
| `tsconfig.json` | Accept upstream compiler changes. Keep any overrides we need. |
| `bin/` | Upstream may update `synthos.js`. Our `synthtabs.js` wrapper is separate — keep both. |
| `src/synthos-cli.ts` → `src/synthtabs-cli.ts` | When upstream changes `synthos-cli.ts`, manually migrate those changes into `synthtabs-cli.ts`. Our CLI mirrors upstream but uses our branding (`synthtabs` script name, `SynthTabs` messaging) and passes our `customizer` instance. |

### NEVER TOUCH (ours only, upstream doesn't have these)

| File / Pattern | Reason |
|----------------|--------|
| `teams-default-pages/` | Teams-specific content |
| `teams-default-scripts/` | Teams-specific content |
| `teams-default-themes/` | Teams-specific content |
| `teams-page-scripts/` | Teams-specific content |
| `teams-required-pages/` | Teams-specific content |
| `teams-service-connectors/` | Teams-specific content |
| `teams-tests/` | Our own unit and integration tests |
| `.architecture/` | Our architecture docs |
| `SYNC_FORK.md` | This file |

---

## Post-Merge Checklist

- [ ] `package.json` — name is still `"synthtabs"`, version is ours, bin points to our CLI
- [ ] `npm install` succeeds (new upstream deps are picked up)
- [ ] `npm run build` succeeds
- [ ] `npm test` passes (runs both upstream `tests/` and our `teams-tests/`)
- [ ] Our `teams-*` folders are untouched
- [ ] `README.md` is still our Teams-specific readme
- [ ] No upstream-only files leaked into our `files` list that shouldn't be there

---

## Quick Reference: Manual package.json Merge

When `package.json` conflicts, use this approach:

1. Accept the upstream version first
2. Restore our overrides:
- `name`: `"synthtabs"`
- `version`: our current version
- `description`: `"Dynamic vibe coded Tab Apps for Microsoft Teams."`
- `bin`: `"./bin/synthtabs.js"`
- `bugs.url`: our GitHub issues URL
- `repository.url`: our GitHub repo URL
- `keywords`: include `"teams"`
- `files`: include `teams-*` folders and `teams-tests`
- `scripts.start`: `"node ./bin/synthtabs.js start"`
- `scripts.test:mocha`: `"nyc ts-mocha tests/**/*.spec.ts teams-tests/**/*.spec.ts"`
3. Add back any Teams-specific dependencies we have
3 changes: 3 additions & 0 deletions bin/synthtabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node
var app = require('../dist/synthtabs-cli.js');
app.run();
Loading