Skip to content
Merged
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
5 changes: 4 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
"Bash(test:*)",
"WebFetch(domain:docs.openclaw.ai)",
"Bash(curl:*)",
"Bash(npm run test:mocha:*)"
"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)"
]
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"typescript": "^4.2.3"
},
"scripts": {
"build": "tsc -b && node scripts/copy-connector-json.js",
"build": "tsc -b",
"clean": "rimraf dist tsconfig.tsbuildinfo node_modules",
"start": "node ./bin/synthos.js",
"test": "npm run build && npm run test:mocha",
Expand All @@ -61,6 +61,7 @@
"default-themes",
"page-scripts",
"required-pages",
"service-connectors",
"images"
]
}
17 changes: 0 additions & 17 deletions scripts/copy-connector-json.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { CONNECTOR_REGISTRY } from './registry';
export { loadConnectorRegistry, getConnectorRegistry } from './registry';
export type {
AuthStrategy,
ConnectorField,
Expand Down
24 changes: 16 additions & 8 deletions src/connectors/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import * as fs from 'fs';
import * as path from 'path';
import { ConnectorDefinition, ConnectorJson } from './types';

const connectorsDir = __dirname;

function loadConnectorJson(dir: string): ConnectorDefinition | null {
const file = path.join(dir, 'connector.json');
if (!fs.existsSync(file)) return null;
Expand All @@ -14,9 +12,19 @@ function loadConnectorJson(dir: string): ConnectorDefinition | null {
};
}

export const CONNECTOR_REGISTRY: ConnectorDefinition[] = fs
.readdirSync(connectorsDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => loadConnectorJson(path.join(connectorsDir, d.name)))
.filter((d): d is ConnectorDefinition => d !== null)
.sort((a, b) => a.name.localeCompare(b.name));
export function loadConnectorRegistry(connectorsDir: string): ConnectorDefinition[] {
if (!fs.existsSync(connectorsDir)) return [];
return fs.readdirSync(connectorsDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => loadConnectorJson(path.join(connectorsDir, d.name)))
.filter((d): d is ConnectorDefinition => d !== null)
.sort((a, b) => a.name.localeCompare(b.name));
}

let _registry: ConnectorDefinition[] | undefined;
export function getConnectorRegistry(connectorsDir?: string): ConnectorDefinition[] {
if (!_registry || connectorsDir) {
_registry = loadConnectorRegistry(connectorsDir ?? path.join(__dirname, '../../service-connectors'));
}
return _registry;
}
106 changes: 106 additions & 0 deletions src/customizer/Customizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Application } from 'express';
import { SynthOSConfig } from '../init';
import path from 'path';

export type RouteInstaller = (config: SynthOSConfig, app: Application) => void;

export class Customizer {
protected disabled: Set<string> = new Set();
protected extraRoutes: RouteInstaller[] = [];

/** Route hints for the LLM prompt (fork developers can add custom hints). */
protected routeHintEntries: { hints: string }[] = [];

/** Custom instructions appended to transformPage's instruction block. */
protected customTransformInstructions: string[] = [];

// --- Content folder paths ---
// Override these in a derived class to point to your fork's folders.

/** Folder containing built-in system pages (builder, settings, etc.) */
get requiredPagesFolder(): string {
return path.join(__dirname, '../../required-pages');
}

/** Folder containing starter page templates copied on init */
get defaultPagesFolder(): string {
return path.join(__dirname, '../../default-pages');
}

/** Folder containing theme CSS/JSON files */
get defaultThemesFolder(): string {
return path.join(__dirname, '../../default-themes');
}

/** Folder containing versioned page scripts (page-v2.js, etc.) */
get pageScriptsFolder(): string {
return path.join(__dirname, '../../page-scripts');
}

/** Folder containing connector JSON definitions */
get serviceConnectorsFolder(): string {
return path.join(__dirname, '../../service-connectors');
}

// --- Feature group control ---
// Built-in groups: 'pages', 'api', 'connectors', 'agents',
// 'data', 'brainstorm', 'search', 'scripts'

disable(...groups: string[]): this {
for (const g of groups) this.disabled.add(g);
return this;
}

enable(...groups: string[]): this {
for (const g of groups) this.disabled.delete(g);
return this;
}

isEnabled(group: string): boolean {
return !this.disabled.has(group);
}

// --- Custom routes ---

addRoutes(...installers: (RouteInstaller | { installer: RouteInstaller; hints: string })[]): this {
for (const entry of installers) {
if (typeof entry === 'function') {
this.extraRoutes.push(entry);
} else {
this.extraRoutes.push(entry.installer);
this.routeHintEntries.push({ hints: entry.hints });
}
}
return this;
}

getExtraRoutes(): RouteInstaller[] {
return this.extraRoutes;
}

// --- Route hints ---

/** Add route hints that will be shown to the LLM in transformPage. */
addRouteHints(...hints: string[]): this {
for (const h of hints) this.routeHintEntries.push({ hints: h });
return this;
}

/** Get all custom route hints. */
getRouteHints(): string[] {
return this.routeHintEntries.map(e => e.hints);
}

// --- Custom transform instructions ---

/** Add custom instructions for the transformPage LLM prompt. */
addTransformInstructions(...instructions: string[]): this {
this.customTransformInstructions.push(...instructions);
return this;
}

/** Get custom transform instructions. */
getTransformInstructions(): string[] {
return this.customTransformInstructions;
}
}
6 changes: 6 additions & 0 deletions src/customizer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { Customizer, RouteInstaller } from './Customizer';
import { Customizer } from './Customizer';

// Default instance — enables everything, uses base folders.
// Fork developers: replace this with your derived class instance.
export const customizer = new Customizer();
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './files';
export * from './init';
export * from './pages';
export * from './scripts';
export * from './settings';
export * from './settings';
export * from './customizer';
17 changes: 12 additions & 5 deletions src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { checkIfExists, copyFile, copyFiles, deleteFile, ensureFolderExists, lis
import { PAGE_VERSION } from "./pages";
import { DefaultSettings } from "./settings";
import { getOutdatedThemes, parseThemeFilename } from "./themes";
import { Customizer } from './customizer';

export interface SynthOSConfig {
pagesFolder: string;
Expand All @@ -12,18 +13,24 @@ export interface SynthOSConfig {
defaultScriptsFolder: string;
defaultThemesFolder: string;
pageScriptsFolder: string;
serviceConnectorsFolder: string;
debug: boolean;
debugPageUpdates: boolean;
}

export function createConfig(pagesFolder = '.synthos', options?: { debug?: boolean; debugPageUpdates?: boolean }): SynthOSConfig {
export function createConfig(
pagesFolder = '.synthos',
options?: { debug?: boolean; debugPageUpdates?: boolean },
customizer?: Customizer
): SynthOSConfig {
return {
pagesFolder: path.join(process.cwd(), pagesFolder),
requiredPagesFolder: path.join(__dirname, '../required-pages'),
defaultPagesFolder: path.join(__dirname, '../default-pages'),
requiredPagesFolder: customizer?.requiredPagesFolder ?? path.join(__dirname, '../required-pages'),
defaultPagesFolder: customizer?.defaultPagesFolder ?? path.join(__dirname, '../default-pages'),
defaultScriptsFolder: path.join(__dirname, '../default-scripts'),
defaultThemesFolder: path.join(__dirname, '../default-themes'),
pageScriptsFolder: path.join(__dirname, '../page-scripts'),
defaultThemesFolder: customizer?.defaultThemesFolder ?? path.join(__dirname, '../default-themes'),
pageScriptsFolder: customizer?.pageScriptsFolder ?? path.join(__dirname, '../page-scripts'),
serviceConnectorsFolder: customizer?.serviceConnectorsFolder ?? path.join(__dirname, '../service-connectors'),
debug: options?.debug ?? false,
debugPageUpdates: options?.debugPageUpdates ?? false
};
Expand Down
20 changes: 13 additions & 7 deletions src/service/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { useDataRoutes } from './useDataRoutes';
import { useConnectorRoutes } from './useConnectorRoutes';
import { useAgentRoutes } from './useAgentRoutes';
import { cyan, yellow, formatTime } from './debugLog';
import { customizer as defaultCustomizer, Customizer } from '../customizer';

export function server(config: SynthOSConfig): Application {
export function server(config: SynthOSConfig, customizer: Customizer = defaultCustomizer): Application {
const app = express();

// Debug request-logging middleware
Expand All @@ -29,19 +30,24 @@ export function server(config: SynthOSConfig): Application {
app.use(express.json());

// Page handling routes
usePageRoutes(config, app);
if (customizer.isEnabled('pages')) usePageRoutes(config, app, customizer);

// API routes
useApiRoutes(config, app);
if (customizer.isEnabled('api')) useApiRoutes(config, app, customizer);

// Connector routes
useConnectorRoutes(config, app);
if (customizer.isEnabled('connectors')) useConnectorRoutes(config, app);

// Agent routes
useAgentRoutes(config, app);
if (customizer.isEnabled('agents')) useAgentRoutes(config, app);

// Data routes
useDataRoutes(config, app);
if (customizer.isEnabled('data')) useDataRoutes(config, app);

// Custom routes from the Customizer
for (const installer of customizer.getExtraRoutes()) {
installer(config, app);
}

return app;
}
}
Loading