synthos 0.10.1 → 0.11.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 +5 -5
- package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
- package/default-pages/elevenlabs_effects_studio/page.json +13 -11
- package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_voice_studio/page.html +782 -801
- package/default-pages/elevenlabs_voice_studio/page.json +13 -11
- package/default-pages/json_tools/chat-history.json +1 -0
- package/default-pages/json_tools/page.html +70 -90
- package/default-pages/json_tools/page.json +12 -10
- package/default-pages/my_notes/chat-history.json +1 -0
- package/default-pages/my_notes/page.html +115 -131
- package/default-pages/my_notes/page.json +14 -12
- package/default-pages/neon_asteroids/chat-history.json +1 -0
- package/default-pages/neon_asteroids/page.html +1777 -1803
- package/default-pages/neon_asteroids/page.json +14 -12
- package/default-pages/oregon_trail/chat-history.json +1 -0
- package/default-pages/oregon_trail/page.html +290 -307
- package/default-pages/oregon_trail/page.json +14 -12
- package/default-pages/solar_explorer/chat-history.json +1 -0
- package/default-pages/solar_explorer/page.html +1929 -1951
- package/default-pages/solar_explorer/page.json +14 -12
- package/default-pages/solar_tutorial/chat-history.json +1 -0
- package/default-pages/solar_tutorial/page.html +464 -478
- package/default-pages/solar_tutorial/page.json +12 -10
- package/default-pages/us_map/chat-history.json +1 -0
- package/default-pages/us_map/page.html +170 -193
- package/default-pages/us_map/page.json +14 -12
- package/default-pages/us_map/page.light.png +0 -0
- package/default-pages/us_map_1850/chat-history.json +1 -0
- package/default-pages/us_map_1850/page.html +302 -326
- package/default-pages/us_map_1850/page.json +14 -12
- package/default-pages/western_cities_1850/chat-history.json +1 -0
- package/default-pages/western_cities_1850/page.html +503 -527
- package/default-pages/western_cities_1850/page.json +14 -12
- package/default-themes/aurora-dawn.v3.css +15 -14
- package/default-themes/aurora-dusk.v3.css +26 -26
- package/default-themes/cosmos-dawn.v3.css +15 -14
- package/default-themes/cosmos-dusk.v3.css +26 -26
- package/default-themes/elemental-dawn.v3.css +200 -0
- package/default-themes/nebula-dawn.v3.css +15 -14
- package/default-themes/nebula-dusk.v3.css +24 -24
- package/default-themes/solar-flare-dawn.v3.css +15 -14
- package/default-themes/solar-flare-dusk.v3.css +26 -26
- package/dist/builders/anthropic.d.ts +26 -2
- package/dist/builders/anthropic.d.ts.map +1 -1
- package/dist/builders/anthropic.js +132 -31
- package/dist/builders/anthropic.js.map +1 -1
- package/dist/builders/claudecode.d.ts +13 -0
- package/dist/builders/claudecode.d.ts.map +1 -0
- package/dist/builders/claudecode.js +253 -0
- package/dist/builders/claudecode.js.map +1 -0
- package/dist/builders/index.d.ts +2 -1
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +8 -1
- package/dist/builders/index.js.map +1 -1
- package/dist/builders/openai.js +2 -1
- package/dist/builders/openai.js.map +1 -1
- package/dist/builders/types.d.ts +31 -7
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +60 -28
- package/dist/builders/types.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +13 -6
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +161 -14
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +1 -0
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +129 -29
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/chainOfThought.d.ts.map +1 -1
- package/dist/models/chainOfThought.js +32 -19
- package/dist/models/chainOfThought.js.map +1 -1
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/providers.d.ts +1 -0
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +12 -4
- package/dist/models/providers.js.map +1 -1
- package/dist/models/types.d.ts +15 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +57 -8
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +258 -45
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +5 -0
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/mediaCache.d.ts +36 -0
- package/dist/service/mediaCache.d.ts.map +1 -0
- package/dist/service/mediaCache.js +182 -0
- package/dist/service/mediaCache.js.map +1 -0
- package/dist/service/pageValidator.d.ts +25 -0
- package/dist/service/pageValidator.d.ts.map +1 -0
- package/dist/service/pageValidator.js +315 -0
- package/dist/service/pageValidator.js.map +1 -0
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +4 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/sharedTableSchema.d.ts +73 -0
- package/dist/service/sharedTableSchema.d.ts.map +1 -0
- package/dist/service/sharedTableSchema.js +206 -0
- package/dist/service/sharedTableSchema.js.map +1 -0
- package/dist/service/transformPage.d.ts +49 -11
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +354 -241
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +288 -34
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +170 -32
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +59 -2
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useExtractRoutes.d.ts +4 -0
- package/dist/service/useExtractRoutes.d.ts.map +1 -0
- package/dist/service/useExtractRoutes.js +304 -0
- package/dist/service/useExtractRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +17 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +1385 -483
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
- package/dist/service/useSharedDataRoutes.js +54 -2
- package/dist/service/useSharedDataRoutes.js.map +1 -1
- package/dist/settings.d.ts +27 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +40 -1
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +0 -5
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +3 -95
- package/dist/themes.js.map +1 -1
- package/migration-rules/v2-to-v3.md +277 -119
- package/package.json +5 -1
- package/{default-pages/application → required-pages/_shell}/page.html +56 -42
- package/required-pages/_shell/page.json +14 -0
- package/required-pages/_starters/page.html +534 -0
- package/required-pages/_starters/page.json +12 -0
- package/required-pages/builder/page.html +353 -43
- package/required-pages/builder/page.json +12 -10
- package/required-pages/pages/page.html +697 -924
- package/required-pages/pages/page.json +12 -10
- package/required-pages/settings/page.html +1879 -1753
- package/required-pages/settings/page.json +12 -10
- package/required-pages/synthos_apis/page.html +834 -845
- package/required-pages/synthos_apis/page.json +12 -10
- package/required-pages/synthos_scripts/page.html +74 -88
- package/required-pages/synthos_scripts/page.json +12 -10
- package/scripts/append-instructions.py +90 -0
- package/scripts/audit-instructions.py +76 -0
- package/scripts/cleanup-shell-markup.mjs +112 -0
- package/service-connectors/buffer/connector.json +46 -0
- package/service-connectors/canva/connector.json +67 -0
- package/service-connectors/elevenlabs/connector.json +1 -1
- package/src/builders/anthropic.ts +150 -25
- package/src/builders/claudecode.ts +310 -0
- package/src/builders/index.ts +7 -1
- package/src/builders/openai.ts +2 -1
- package/src/builders/types.ts +93 -32
- package/src/connectors/types.ts +8 -0
- package/src/init.ts +13 -7
- package/src/migrations.ts +187 -16
- package/src/models/anthropic.ts +140 -30
- package/src/models/chainOfThought.ts +33 -18
- package/src/models/index.ts +2 -2
- package/src/models/providers.ts +10 -1
- package/src/models/types.ts +21 -1
- package/src/pages.ts +271 -35
- package/src/service/createCompletePrompt.ts +6 -0
- package/src/service/mediaCache.ts +206 -0
- package/src/service/pageValidator.ts +337 -0
- package/src/service/server.ts +4 -0
- package/src/service/sharedTableSchema.ts +236 -0
- package/src/service/transformPage.ts +370 -260
- package/src/service/useApiRoutes.ts +282 -32
- package/src/service/useConnectorRoutes.ts +189 -34
- package/src/service/useDataRoutes.ts +198 -116
- package/src/service/useExtractRoutes.ts +331 -0
- package/src/service/usePageRoutes.ts +1411 -394
- package/src/service/useSharedDataRoutes.ts +184 -109
- package/src/settings.ts +65 -0
- package/src/themes.ts +78 -180
- package/starters/blank_starter/chat-history.json +1 -0
- package/starters/blank_starter/page.dark.png +0 -0
- package/starters/blank_starter/page.html +47 -0
- package/starters/blank_starter/page.json +13 -0
- package/starters/blank_starter/page.light.png +0 -0
- package/starters/calculator_starter/chat-history.json +1 -0
- package/starters/calculator_starter/page.dark.png +0 -0
- package/starters/calculator_starter/page.html +232 -0
- package/starters/calculator_starter/page.json +13 -0
- package/starters/calculator_starter/page.light.png +0 -0
- package/starters/calendar_starter/chat-history.json +1 -0
- package/starters/calendar_starter/page.dark.png +0 -0
- package/starters/calendar_starter/page.html +495 -0
- package/starters/calendar_starter/page.json +13 -0
- package/starters/calendar_starter/page.light.png +0 -0
- package/starters/chat_starter/chat-history.json +1 -0
- package/starters/chat_starter/page.dark.png +0 -0
- package/starters/chat_starter/page.html +351 -0
- package/starters/chat_starter/page.json +13 -0
- package/starters/chat_starter/page.light.png +0 -0
- package/starters/checklist_starter/chat-history.json +1 -0
- package/starters/checklist_starter/page.dark.png +0 -0
- package/starters/checklist_starter/page.html +437 -0
- package/starters/checklist_starter/page.json +13 -0
- package/starters/checklist_starter/page.light.png +0 -0
- package/starters/dashboard_starter/chat-history.json +1 -0
- package/starters/dashboard_starter/page.dark.png +0 -0
- package/starters/dashboard_starter/page.html +195 -0
- package/starters/dashboard_starter/page.json +13 -0
- package/starters/dashboard_starter/page.light.png +0 -0
- package/starters/form_starter/chat-history.json +1 -0
- package/starters/form_starter/page.dark.png +0 -0
- package/starters/form_starter/page.html +313 -0
- package/starters/form_starter/page.json +13 -0
- package/starters/form_starter/page.light.png +0 -0
- package/starters/gallery_starter/chat-history.json +1 -0
- package/starters/gallery_starter/page.dark.png +0 -0
- package/starters/gallery_starter/page.html +418 -0
- package/starters/gallery_starter/page.json +13 -0
- package/starters/gallery_starter/page.light.png +0 -0
- package/starters/generator_starter/chat-history.json +1 -0
- package/starters/generator_starter/page.dark.png +0 -0
- package/starters/generator_starter/page.html +261 -0
- package/starters/generator_starter/page.json +13 -0
- package/starters/generator_starter/page.light.png +0 -0
- package/starters/index.html +538 -0
- package/starters/kanban_starter/chat-history.json +1 -0
- package/starters/kanban_starter/page.dark.png +0 -0
- package/starters/kanban_starter/page.html +432 -0
- package/starters/kanban_starter/page.json +13 -0
- package/starters/kanban_starter/page.light.png +0 -0
- package/starters/presentation_builder/chat-history.json +1 -0
- package/starters/presentation_builder/page.dark.png +0 -0
- package/starters/presentation_builder/page.html +970 -0
- package/starters/presentation_builder/page.json +15 -0
- package/starters/presentation_builder/page.light.png +0 -0
- package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
- package/starters/pulse_starter/chat-history.json +1 -0
- package/starters/pulse_starter/page.dark.png +0 -0
- package/starters/pulse_starter/page.html +698 -0
- package/starters/pulse_starter/page.json +13 -0
- package/starters/pulse_starter/page.light.png +0 -0
- package/starters/quiz_starter/chat-history.json +1 -0
- package/starters/quiz_starter/page.dark.png +0 -0
- package/starters/quiz_starter/page.html +292 -0
- package/starters/quiz_starter/page.json +13 -0
- package/starters/quiz_starter/page.light.png +0 -0
- package/starters/reference_starter/chat-history.json +1 -0
- package/starters/reference_starter/page.dark.png +0 -0
- package/starters/reference_starter/page.html +250 -0
- package/starters/reference_starter/page.json +13 -0
- package/starters/reference_starter/page.light.png +0 -0
- package/starters/retro_game_starter/chat-history.json +1 -0
- package/starters/retro_game_starter/page.dark.png +0 -0
- package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
- package/starters/retro_game_starter/page.json +15 -0
- package/starters/retro_game_starter/page.light.png +0 -0
- package/starters/roster_starter/chat-history.json +1 -0
- package/starters/roster_starter/page.dark.png +0 -0
- package/starters/roster_starter/page.html +600 -0
- package/starters/roster_starter/page.json +13 -0
- package/starters/roster_starter/page.light.png +0 -0
- package/starters/server.js +182 -0
- package/starters/start.cmd +1 -0
- package/starters/timeline_starter/chat-history.json +1 -0
- package/starters/timeline_starter/page.dark.png +0 -0
- package/starters/timeline_starter/page.html +446 -0
- package/starters/timeline_starter/page.json +13 -0
- package/starters/timeline_starter/page.light.png +0 -0
- package/starters/tutorial_starter/chat-history.json +1 -0
- package/starters/tutorial_starter/page.dark.png +0 -0
- package/starters/tutorial_starter/page.html +283 -0
- package/starters/tutorial_starter/page.json +13 -0
- package/starters/tutorial_starter/page.light.png +0 -0
- package/static-files/agent.v3.js +122 -0
- package/static-files/connector.v3.js +48 -0
- package/static-files/extract.v3.js +188 -0
- package/static-files/helpers.v3.js +50 -6
- package/static-files/page-bridge.js +114 -0
- package/static-files/page.v3.js +1292 -1290
- package/static-files/script.v3.js +32 -0
- package/static-files/server.v3.js +89 -0
- package/static-files/shell-bridge.v3.js +174 -0
- package/static-files/shell-modals.v3.js +521 -0
- package/static-files/{shell.css → shell.v3.css} +271 -22
- package/static-files/shell.v3.js +1865 -0
- package/static-files/storage.v3.js +176 -0
- package/tests/anthropic.spec.ts +42 -7
- package/tests/builders.spec.ts +70 -2
- package/tests/pageValidator.spec.ts +548 -0
- package/tests/profiles.spec.ts +122 -0
- package/tests/sharedTableSchema.spec.ts +242 -0
- package/tests/transformPage.spec.ts +62 -81
- package/default-pages/application/page.json +0 -10
- package/default-pages/retro_game_starter/page.json +0 -12
- package/default-pages/sidebar_page/page.html +0 -51
- package/default-pages/sidebar_page/page.json +0 -10
- package/default-pages/two-panel_page/page.html +0 -68
- package/default-pages/two-panel_page/page.json +0 -10
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { ChangeOp, getTransformInstr } from '../service/transformPage';
|
|
3
|
+
import { parseBuilderResponse } from './anthropic';
|
|
4
|
+
import { Builder, BuilderResult, CHANGE_OPS_FORMAT_INSTRUCTION } from './types';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Options
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export interface ClaudeCodeBuilderOptions {
|
|
11
|
+
/** Model to pass as --model to the CLI. */
|
|
12
|
+
model?: string;
|
|
13
|
+
/** Override path to the claude binary (defaults to 'claude' on PATH). */
|
|
14
|
+
commandPath?: string;
|
|
15
|
+
/** Request timeout in milliseconds (default 120 000). */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/** Additional CLI flags passed to every invocation. */
|
|
18
|
+
extraFlags?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/** Strip ANSI escape codes from CLI output. */
|
|
26
|
+
function stripAnsi(text: string): string {
|
|
27
|
+
// eslint-disable-next-line no-control-regex
|
|
28
|
+
return text.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Spawn the Claude CLI with a prompt on stdin and capture stdout.
|
|
33
|
+
* Returns the raw text output (ANSI-stripped).
|
|
34
|
+
*/
|
|
35
|
+
function runClaude(
|
|
36
|
+
prompt: string,
|
|
37
|
+
options: ClaudeCodeBuilderOptions
|
|
38
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
39
|
+
const cmd = options.commandPath ?? 'claude';
|
|
40
|
+
const args: string[] = ['-p', '--output-format', 'text'];
|
|
41
|
+
if (options.model) {
|
|
42
|
+
args.push('--model', options.model);
|
|
43
|
+
}
|
|
44
|
+
if (options.extraFlags) {
|
|
45
|
+
args.push(...options.extraFlags);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const timeout = options.timeout ?? 120_000;
|
|
49
|
+
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const child = spawn(cmd, args, {
|
|
52
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
53
|
+
env: {
|
|
54
|
+
...process.env,
|
|
55
|
+
FORCE_COLOR: '0', // suppress ANSI in captured output
|
|
56
|
+
CLAUDECODE: undefined, // prevent recursive detection
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let stdout = '';
|
|
61
|
+
let stderr = '';
|
|
62
|
+
|
|
63
|
+
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString(); });
|
|
64
|
+
child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString(); });
|
|
65
|
+
|
|
66
|
+
// EOF protection — swallow EPIPE if the child closes stdin early
|
|
67
|
+
child.stdin.on('error', (err: NodeJS.ErrnoException) => {
|
|
68
|
+
if (err.code !== 'EPIPE' && err.code !== 'EOF') throw err;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Timeout handling: SIGTERM → 5 s → SIGKILL
|
|
72
|
+
const timer = setTimeout(() => {
|
|
73
|
+
child.kill('SIGTERM');
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
if (!child.killed) child.kill('SIGKILL');
|
|
76
|
+
}, 5_000);
|
|
77
|
+
}, timeout);
|
|
78
|
+
|
|
79
|
+
child.on('close', (code) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolve({
|
|
82
|
+
stdout: stripAnsi(stdout),
|
|
83
|
+
stderr: stripAnsi(stderr),
|
|
84
|
+
exitCode: code ?? 1,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
child.on('error', (err) => {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Pipe the prompt via stdin
|
|
94
|
+
child.stdin.write(prompt, () => {
|
|
95
|
+
child.stdin.end();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Spawn + retry wrapper
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Execute the Claude CLI and return trimmed output.
|
|
106
|
+
* Retries once on empty output with a simplified prompt prefix.
|
|
107
|
+
*/
|
|
108
|
+
async function executeWithRetry(
|
|
109
|
+
prompt: string,
|
|
110
|
+
options: ClaudeCodeBuilderOptions,
|
|
111
|
+
): Promise<string> {
|
|
112
|
+
const attempt = async (p: string): Promise<string> => {
|
|
113
|
+
const result = await runClaude(p, options);
|
|
114
|
+
|
|
115
|
+
// Handle process-level failures
|
|
116
|
+
if (result.exitCode !== 0 && !result.stdout.trim()) {
|
|
117
|
+
if (result.stderr.includes('ENOENT') || result.stderr.includes('not found') || result.stderr.includes('not recognized')) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
throw new Error(result.stderr || `Claude CLI exited with code ${result.exitCode}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result.stdout.trim();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// First attempt
|
|
129
|
+
const output = await attempt(prompt);
|
|
130
|
+
if (output) return output;
|
|
131
|
+
|
|
132
|
+
// Retry once with a nudge prepended to the prompt
|
|
133
|
+
console.warn('[ClaudeCode] Empty response — retrying once');
|
|
134
|
+
const retryOutput = await attempt(
|
|
135
|
+
'IMPORTANT: You must produce output. Do not return an empty response.\n\n' + prompt
|
|
136
|
+
);
|
|
137
|
+
if (retryOutput) return retryOutput;
|
|
138
|
+
|
|
139
|
+
throw new Error('Claude CLI returned empty output after retry');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Change-op validation
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
const VALID_OPS = new Set(['update', 'replace', 'delete', 'insert', 'style-element', 'search-replace', 'search-insert']);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate a single change operation against the expected schema.
|
|
150
|
+
* Returns true if the op is structurally valid, false otherwise.
|
|
151
|
+
*/
|
|
152
|
+
function isValidChangeOp(op: unknown): op is ChangeOp {
|
|
153
|
+
if (!op || typeof op !== 'object') return false;
|
|
154
|
+
const o = op as Record<string, unknown>;
|
|
155
|
+
if (typeof o.op !== 'string' || !VALID_OPS.has(o.op)) return false;
|
|
156
|
+
|
|
157
|
+
switch (o.op) {
|
|
158
|
+
case 'update':
|
|
159
|
+
case 'replace':
|
|
160
|
+
return typeof o.nodeId === 'string' && typeof o.html === 'string';
|
|
161
|
+
case 'delete':
|
|
162
|
+
return typeof o.nodeId === 'string';
|
|
163
|
+
case 'insert':
|
|
164
|
+
return typeof o.parentId === 'string'
|
|
165
|
+
&& typeof o.position === 'string'
|
|
166
|
+
&& typeof o.html === 'string';
|
|
167
|
+
case 'style-element':
|
|
168
|
+
return typeof o.nodeId === 'string' && typeof o.style === 'string';
|
|
169
|
+
case 'search-replace':
|
|
170
|
+
return typeof o.nodeId === 'string'
|
|
171
|
+
&& typeof o.search === 'string'
|
|
172
|
+
&& typeof o.replace === 'string';
|
|
173
|
+
case 'search-insert':
|
|
174
|
+
return typeof o.nodeId === 'string'
|
|
175
|
+
&& typeof o.after === 'string'
|
|
176
|
+
&& typeof o.content === 'string';
|
|
177
|
+
default:
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Filter a list of change ops, keeping only valid ones.
|
|
184
|
+
* Logs warnings for any skipped ops.
|
|
185
|
+
*/
|
|
186
|
+
function filterValidOps(ops: unknown[]): ChangeOp[] {
|
|
187
|
+
const valid: ChangeOp[] = [];
|
|
188
|
+
for (let i = 0; i < ops.length; i++) {
|
|
189
|
+
const op = ops[i];
|
|
190
|
+
if (isValidChangeOp(op)) {
|
|
191
|
+
valid.push(op);
|
|
192
|
+
} else {
|
|
193
|
+
console.warn(`[ClaudeCode] Skipping invalid change op at index ${i}:`, JSON.stringify(ops[i]));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return valid;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Chat mode heuristic
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Simple heuristic to detect chat/question messages.
|
|
205
|
+
* Per spec: message ends with `?` and is under 100 characters.
|
|
206
|
+
*/
|
|
207
|
+
function isChatQuestion(message: string): boolean {
|
|
208
|
+
const trimmed = message.trim();
|
|
209
|
+
return trimmed.length < 100 && trimmed.endsWith('?');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Builder factory
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
export function createClaudeCodeBuilder(
|
|
217
|
+
userInstructions?: string,
|
|
218
|
+
productName?: string,
|
|
219
|
+
options?: ClaudeCodeBuilderOptions,
|
|
220
|
+
): Builder {
|
|
221
|
+
const name = productName ?? 'SynthOS';
|
|
222
|
+
const opts: ClaudeCodeBuilderOptions = options ?? {};
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
async run(currentPage, additionalSections, userMessage, newBuild, _attachments?): Promise<BuilderResult> {
|
|
226
|
+
try {
|
|
227
|
+
const chatMode = !newBuild && isChatQuestion(userMessage);
|
|
228
|
+
|
|
229
|
+
let prompt: string;
|
|
230
|
+
|
|
231
|
+
if (chatMode) {
|
|
232
|
+
// -- Chat prompt: no change-ops, no node annotations --
|
|
233
|
+
const contextParts: string[] = [
|
|
234
|
+
`<CURRENT_PAGE>\n${currentPage.content}\n</CURRENT_PAGE>`,
|
|
235
|
+
];
|
|
236
|
+
for (const section of additionalSections) {
|
|
237
|
+
if (section.content) {
|
|
238
|
+
contextParts.push(`${section.title}\n${section.content}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
prompt = `You are ${name}, a helpful assistant. Answer the user's question about their page.\n\n`
|
|
243
|
+
+ contextParts.join('\n\n')
|
|
244
|
+
+ `\n\n<USER_MESSAGE>\n${userMessage}\n</USER_MESSAGE>`;
|
|
245
|
+
} else {
|
|
246
|
+
// -- Build/change prompt (same structure as before) --
|
|
247
|
+
const systemParts: string[] = [
|
|
248
|
+
`${currentPage.title}\n${currentPage.content}`,
|
|
249
|
+
];
|
|
250
|
+
for (const section of additionalSections) {
|
|
251
|
+
if (section.content) {
|
|
252
|
+
systemParts.push(`${section.title}\n${section.content}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const systemContent = systemParts.join('\n\n');
|
|
256
|
+
|
|
257
|
+
const instructionParts: string[] = [];
|
|
258
|
+
if (userInstructions?.trim()) {
|
|
259
|
+
instructionParts.push(userInstructions);
|
|
260
|
+
}
|
|
261
|
+
for (const section of additionalSections) {
|
|
262
|
+
if (section.instructions?.trim()) {
|
|
263
|
+
instructionParts.push(section.instructions);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
instructionParts.push(getTransformInstr(name));
|
|
267
|
+
instructionParts.push(CHANGE_OPS_FORMAT_INSTRUCTION);
|
|
268
|
+
|
|
269
|
+
const instructions = instructionParts.filter(s => s.trim() !== '').join('\n');
|
|
270
|
+
|
|
271
|
+
prompt = `${systemContent}\n\n<USER_MESSAGE>\n${userMessage}\n\n<INSTRUCTIONS>\n${instructions}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// -- Spawn claude CLI (with retry on empty output) --
|
|
275
|
+
const output = await executeWithRetry(prompt, opts);
|
|
276
|
+
|
|
277
|
+
// Chat mode always returns a reply — skip change-ops parsing
|
|
278
|
+
if (chatMode) {
|
|
279
|
+
return { kind: 'reply', text: output };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Try to parse as builder response (JSON change ops or reply)
|
|
283
|
+
const parsed = parseBuilderResponse(output);
|
|
284
|
+
|
|
285
|
+
// Validate individual change ops — skip invalid ones
|
|
286
|
+
if (parsed.kind === 'transforms') {
|
|
287
|
+
const validated = filterValidOps(parsed.changes);
|
|
288
|
+
if (validated.length === 0) {
|
|
289
|
+
// All ops were invalid — treat the raw output as a text reply
|
|
290
|
+
return { kind: 'reply', text: output };
|
|
291
|
+
}
|
|
292
|
+
return { kind: 'transforms', changes: validated };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return parsed;
|
|
296
|
+
} catch (err: unknown) {
|
|
297
|
+
// Spawn failure (e.g. ENOENT when claude is not on PATH)
|
|
298
|
+
if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
299
|
+
return {
|
|
300
|
+
kind: 'error',
|
|
301
|
+
error: new Error(
|
|
302
|
+
'Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code'
|
|
303
|
+
),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return { kind: 'error', error: err instanceof Error ? err : new Error(String(err)) };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
package/src/builders/index.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { ProviderName, completePrompt } from '../models';
|
|
|
2
2
|
import { createAnthropicBuilder, AnthropicBuilderOptions } from './anthropic';
|
|
3
3
|
import { createOpenAIBuilder } from './openai';
|
|
4
4
|
import { createFireworksAIBuilder } from './fireworksai';
|
|
5
|
+
import { createClaudeCodeBuilder } from './claudecode';
|
|
5
6
|
import { Builder } from './types';
|
|
6
7
|
|
|
7
|
-
export { ContextSection, BuilderResult, Builder, Attachment } from './types';
|
|
8
|
+
export { ContextSection, SectionMode, BuilderResult, Builder, Attachment } from './types';
|
|
8
9
|
export { createAnthropicBuilder, AnthropicBuilderOptions } from './anthropic';
|
|
9
10
|
export { createOpenAIBuilder } from './openai';
|
|
10
11
|
export { createFireworksAIBuilder } from './fireworksai';
|
|
12
|
+
export { createClaudeCodeBuilder, ClaudeCodeBuilderOptions } from './claudecode';
|
|
11
13
|
export { parseBuilderResponse } from './anthropic';
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -27,6 +29,10 @@ export function createBuilder(
|
|
|
27
29
|
return createOpenAIBuilder(complete, userInstructions, productName);
|
|
28
30
|
case 'FireworksAI':
|
|
29
31
|
return createFireworksAIBuilder(complete, userInstructions, productName);
|
|
32
|
+
case 'ClaudeCode':
|
|
33
|
+
return createClaudeCodeBuilder(userInstructions, productName, {
|
|
34
|
+
model: options?.model,
|
|
35
|
+
});
|
|
30
36
|
default:
|
|
31
37
|
throw new Error(`Unknown provider: ${provider}`);
|
|
32
38
|
}
|
package/src/builders/openai.ts
CHANGED
|
@@ -80,7 +80,8 @@ function parseOpenAIResponse(raw: string): BuilderResult {
|
|
|
80
80
|
try {
|
|
81
81
|
const parsed = JSON.parse(raw);
|
|
82
82
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray(parsed.changes)) {
|
|
83
|
-
|
|
83
|
+
const message = typeof parsed.message === 'string' ? parsed.message : undefined;
|
|
84
|
+
return { kind: 'transforms', changes: parsed.changes, message };
|
|
84
85
|
}
|
|
85
86
|
} catch {
|
|
86
87
|
// fall through to shared parser
|
package/src/builders/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChangeList } from '../service/transformPage';
|
|
2
|
+
import { ToolDefinition, ToolHandler } from '../models/types';
|
|
2
3
|
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// Change operations output format — text instruction for non-structured builders
|
|
@@ -8,28 +9,31 @@ import { ChangeList } from '../service/transformPage';
|
|
|
8
9
|
* Text instruction that tells the model to return a JSON array of change operations.
|
|
9
10
|
* Append this to <INSTRUCTIONS> for builders that don't support structured outputs.
|
|
10
11
|
*/
|
|
11
|
-
export const CHANGE_OPS_FORMAT_INSTRUCTION = `Return a JSON
|
|
12
|
+
export const CHANGE_OPS_FORMAT_INSTRUCTION = `Return a JSON object with two fields: "message" and "changes".
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
"message" is a brief (1 sentence) message to the user explaining what you changed. It is shown in the chat feed as the assistant's reply — write it directly to the user, not a description of the JSON.
|
|
15
|
+
"changes" is an array of change operations to apply to the page. Do NOT return the full HTML page.
|
|
16
|
+
|
|
17
|
+
Each change operation must be one of:
|
|
18
|
+
{ "op": "update", "nodeId": "<data-nid>", "html": "<new innerHTML>" }
|
|
15
19
|
— replaces the innerHTML of the target element
|
|
16
20
|
|
|
17
|
-
{ "op": "replace", "nodeId": "<data-
|
|
21
|
+
{ "op": "replace", "nodeId": "<data-nid>", "html": "<new outerHTML>" }
|
|
18
22
|
— replaces the entire element (outerHTML) with new markup
|
|
19
23
|
|
|
20
|
-
{ "op": "delete", "nodeId": "<data-
|
|
24
|
+
{ "op": "delete", "nodeId": "<data-nid>" }
|
|
21
25
|
— removes the element from the page
|
|
22
26
|
|
|
23
|
-
{ "op": "insert", "parentId": "<data-
|
|
27
|
+
{ "op": "insert", "parentId": "<data-nid>", "position": "prepend"|"append"|"before"|"after", "html": "<new element HTML>" }
|
|
24
28
|
— inserts new HTML relative to the parent element
|
|
25
29
|
|
|
26
|
-
{ "op": "style-element", "nodeId": "<data-
|
|
30
|
+
{ "op": "style-element", "nodeId": "<data-nid>", "style": "<css style string>" }
|
|
27
31
|
— sets the style attribute of the target element (must be unlocked)
|
|
28
32
|
|
|
29
|
-
{ "op": "search-replace", "nodeId": "<data-
|
|
33
|
+
{ "op": "search-replace", "nodeId": "<data-nid>", "search": "<exact text to find>", "replace": "<replacement text>" }
|
|
30
34
|
— finds exact text within a script/style block and replaces it. Use empty string for replace to delete.
|
|
31
35
|
|
|
32
|
-
{ "op": "search-insert", "nodeId": "<data-
|
|
36
|
+
{ "op": "search-insert", "nodeId": "<data-nid>", "after": "<exact text to find>", "content": "<new lines to insert>" }
|
|
33
37
|
— inserts new content immediately after the matched text in a script/style block.
|
|
34
38
|
|
|
35
39
|
For partial edits to large scripts/styles, use search-replace or search-insert instead of
|
|
@@ -38,21 +42,26 @@ Copy the search/after text exactly as it appears in the source.
|
|
|
38
42
|
When making multiple edits to the same block, ensure each search string targets distinct text.
|
|
39
43
|
To delete code, use search-replace with an empty replace string.
|
|
40
44
|
|
|
41
|
-
Return ONLY the JSON
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
Return ONLY the JSON object. Example:
|
|
46
|
+
{
|
|
47
|
+
"message": "Changed the heading and added a new notice.",
|
|
48
|
+
"changes": [
|
|
49
|
+
{ "op": "update", "nodeId": "5", "html": "<p>Hello world</p>" },
|
|
50
|
+
{ "op": "insert", "parentId": "3", "position": "append", "html": "<div class=\\"msg\\">New message</div>" }
|
|
51
|
+
]
|
|
52
|
+
}`;
|
|
46
53
|
|
|
47
54
|
// ---------------------------------------------------------------------------
|
|
48
55
|
// Change operations JSON schema — for structured output (constrained decoding)
|
|
49
56
|
// ---------------------------------------------------------------------------
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const CHANGE_OP_MESSAGE_SCHEMA = {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'Brief (1 sentence) message to the user explaining the change. Written directly to the user; shown in the chat feed as the assistant reply.',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Array schema for the full change-op union (ranged writes allowed). */
|
|
64
|
+
const CHANGE_OPS_ARRAY_SCHEMA: Record<string, unknown> = {
|
|
56
65
|
type: 'array',
|
|
57
66
|
items: {
|
|
58
67
|
anyOf: [
|
|
@@ -132,12 +141,8 @@ export const CHANGE_OPS_SCHEMA: Record<string, unknown> = {
|
|
|
132
141
|
},
|
|
133
142
|
};
|
|
134
143
|
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
* Used for medium/hard/re-write changes where the model should replace full blocks instead of
|
|
138
|
-
* attempting partial edits.
|
|
139
|
-
*/
|
|
140
|
-
export const CHANGE_OPS_SCHEMA_NO_RANGED: Record<string, unknown> = {
|
|
144
|
+
/** Array schema with ranged write ops omitted. */
|
|
145
|
+
const CHANGE_OPS_ARRAY_SCHEMA_NO_RANGED: Record<string, unknown> = {
|
|
141
146
|
type: 'array',
|
|
142
147
|
items: {
|
|
143
148
|
anyOf: [
|
|
@@ -195,6 +200,36 @@ export const CHANGE_OPS_SCHEMA_NO_RANGED: Record<string, unknown> = {
|
|
|
195
200
|
},
|
|
196
201
|
};
|
|
197
202
|
|
|
203
|
+
/**
|
|
204
|
+
* JSON schema for Anthropic structured outputs.
|
|
205
|
+
* Wraps the change-ops array in `{ message, changes }` so the model always
|
|
206
|
+
* emits a user-facing message alongside its page changes.
|
|
207
|
+
*/
|
|
208
|
+
export const CHANGE_OPS_SCHEMA: Record<string, unknown> = {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: {
|
|
211
|
+
message: CHANGE_OP_MESSAGE_SCHEMA,
|
|
212
|
+
changes: CHANGE_OPS_ARRAY_SCHEMA,
|
|
213
|
+
},
|
|
214
|
+
required: ['message', 'changes'],
|
|
215
|
+
additionalProperties: false,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Variant of CHANGE_OPS_SCHEMA that omits ranged write operations (search-replace, search-insert).
|
|
220
|
+
* Used for medium/hard/re-write changes where the model should replace full blocks instead of
|
|
221
|
+
* attempting partial edits.
|
|
222
|
+
*/
|
|
223
|
+
export const CHANGE_OPS_SCHEMA_NO_RANGED: Record<string, unknown> = {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
message: CHANGE_OP_MESSAGE_SCHEMA,
|
|
227
|
+
changes: CHANGE_OPS_ARRAY_SCHEMA_NO_RANGED,
|
|
228
|
+
},
|
|
229
|
+
required: ['message', 'changes'],
|
|
230
|
+
additionalProperties: false,
|
|
231
|
+
};
|
|
232
|
+
|
|
198
233
|
/**
|
|
199
234
|
* Text instruction variant for no-ranged-writes mode.
|
|
200
235
|
* Tells the model to use `update` for full innerHTML replacement instead of partial edits.
|
|
@@ -203,14 +238,15 @@ export const CHANGE_OPS_FORMAT_INSTRUCTION_NO_RANGED = `For script and style blo
|
|
|
203
238
|
|
|
204
239
|
/**
|
|
205
240
|
* OpenAI structured outputs require a root-level object.
|
|
206
|
-
*
|
|
241
|
+
* Matches the Anthropic shape: { message, changes }.
|
|
207
242
|
*/
|
|
208
243
|
export const OPENAI_CHANGE_OPS_SCHEMA: Record<string, unknown> = {
|
|
209
244
|
type: 'object',
|
|
210
245
|
properties: {
|
|
211
|
-
|
|
246
|
+
message: CHANGE_OP_MESSAGE_SCHEMA,
|
|
247
|
+
changes: CHANGE_OPS_ARRAY_SCHEMA,
|
|
212
248
|
},
|
|
213
|
-
required: ['changes'],
|
|
249
|
+
required: ['message', 'changes'],
|
|
214
250
|
additionalProperties: false,
|
|
215
251
|
};
|
|
216
252
|
|
|
@@ -218,11 +254,33 @@ export const OPENAI_CHANGE_OPS_SCHEMA: Record<string, unknown> = {
|
|
|
218
254
|
// Context sections — structured blocks passed to the builder
|
|
219
255
|
// ---------------------------------------------------------------------------
|
|
220
256
|
|
|
257
|
+
export type SectionMode =
|
|
258
|
+
/** Always rendered in full. Never sketched. Used for small/universally relevant sections. */
|
|
259
|
+
| 'always-full'
|
|
260
|
+
/** Sketched by default; rendered in full when the classifier expands it. */
|
|
261
|
+
| 'classifier-decides'
|
|
262
|
+
/** Skipped entirely when sketch === null; otherwise sketched/expanded like classifier-decides. */
|
|
263
|
+
| 'always-omit-when-empty';
|
|
264
|
+
|
|
221
265
|
export interface ContextSection {
|
|
222
|
-
/** Section title, e.g. "<CURRENT_PAGE>", "<SERVER_SCRIPTS>" */
|
|
266
|
+
/** Section title, e.g. "<CURRENT_PAGE>", "<SERVER_SCRIPTS>". Used as the classifier expand-set key. */
|
|
223
267
|
title: string;
|
|
224
|
-
/**
|
|
268
|
+
/** Full render — what the model sees when this section is expanded. */
|
|
225
269
|
content: string;
|
|
270
|
+
/**
|
|
271
|
+
* Compact render — what the classifier sees, and what the model sees when this
|
|
272
|
+
* section is NOT expanded. `null` means "omit entirely from the prompt when not
|
|
273
|
+
* expanded" (used by always-omit-when-empty sections that have nothing to say,
|
|
274
|
+
* or by always-full sections where sketch is irrelevant).
|
|
275
|
+
*/
|
|
276
|
+
sketch: string | null;
|
|
277
|
+
/** Default behavior for this section. Producer-declared. */
|
|
278
|
+
mode: SectionMode;
|
|
279
|
+
/**
|
|
280
|
+
* If true, this section is always rendered in full on the initial page build,
|
|
281
|
+
* regardless of classifier output. Defaults to false.
|
|
282
|
+
*/
|
|
283
|
+
forceFullOnInitial?: boolean;
|
|
226
284
|
/** How the model should work with this section (appended to instructions) */
|
|
227
285
|
instructions: string;
|
|
228
286
|
}
|
|
@@ -232,7 +290,7 @@ export interface ContextSection {
|
|
|
232
290
|
// ---------------------------------------------------------------------------
|
|
233
291
|
|
|
234
292
|
export type BuilderResult =
|
|
235
|
-
| { kind: 'transforms'; changes: ChangeList }
|
|
293
|
+
| { kind: 'transforms'; changes: ChangeList; message?: string }
|
|
236
294
|
| { kind: 'reply'; text: string }
|
|
237
295
|
| { kind: 'error'; error: Error };
|
|
238
296
|
|
|
@@ -256,6 +314,9 @@ export interface Builder {
|
|
|
256
314
|
additionalSections: ContextSection[],
|
|
257
315
|
userMessage: string,
|
|
258
316
|
newBuild: boolean,
|
|
259
|
-
attachments?: Attachment[]
|
|
317
|
+
attachments?: Attachment[],
|
|
318
|
+
tools?: ToolDefinition[],
|
|
319
|
+
toolHandlers?: Record<string, ToolHandler>,
|
|
320
|
+
onToolCall?: (names: string[]) => void,
|
|
260
321
|
): Promise<BuilderResult>;
|
|
261
322
|
}
|
package/src/connectors/types.ts
CHANGED
|
@@ -30,6 +30,10 @@ export interface ConnectorDefinition {
|
|
|
30
30
|
tokenUrl?: string;
|
|
31
31
|
/** OAuth2: Requested scopes. */
|
|
32
32
|
scopes?: string[];
|
|
33
|
+
/** OAuth2: Use PKCE (RFC 7636) on the authorization-code flow. Required by Buffer, Canva, etc. */
|
|
34
|
+
usePkce?: boolean;
|
|
35
|
+
/** OAuth2: Separator used to join scopes in the authorize request. Defaults to comma; RFC 6749 mandates space. */
|
|
36
|
+
scopeSeparator?: ' ' | ',';
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export interface ConnectorJson {
|
|
@@ -46,6 +50,8 @@ export interface ConnectorJson {
|
|
|
46
50
|
authorizationUrl?: string;
|
|
47
51
|
tokenUrl?: string;
|
|
48
52
|
scopes?: string[];
|
|
53
|
+
usePkce?: boolean;
|
|
54
|
+
scopeSeparator?: ' ' | ',';
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export interface ConnectorConfig {
|
|
@@ -61,6 +67,8 @@ export interface ConnectorOAuthConfig extends ConnectorConfig {
|
|
|
61
67
|
accountName?: string;
|
|
62
68
|
clientId?: string;
|
|
63
69
|
clientSecret?: string;
|
|
70
|
+
/** Stored between /authorize and /callback for PKCE-enabled connectors. Cleared after callback. */
|
|
71
|
+
codeVerifier?: string;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
export type ConnectorsConfig = Record<string, ConnectorConfig | ConnectorOAuthConfig>;
|
package/src/init.ts
CHANGED
|
@@ -38,6 +38,11 @@ export async function createConfig(
|
|
|
38
38
|
customizer?.requiredPagesFolders ?? ['default'],
|
|
39
39
|
path.join(__dirname, '../required-pages')
|
|
40
40
|
);
|
|
41
|
+
// Starter pages (category "_Starters") ship in the package's starters/
|
|
42
|
+
// folder and act as a read-only fallback for the runtime: they are
|
|
43
|
+
// discoverable by listPages, loadPageMetadata, and loadPageWithFallback
|
|
44
|
+
// without being copied into .synthos/pages/.
|
|
45
|
+
requiredPagesFolders.push(path.join(__dirname, '../starters'));
|
|
41
46
|
const requiredPages = await getRequiredPages(requiredPagesFolders);
|
|
42
47
|
return {
|
|
43
48
|
localFolder: pagesFolder,
|
|
@@ -107,10 +112,6 @@ export async function init(config: SynthOSConfig, includeDefaultPages: boolean =
|
|
|
107
112
|
|
|
108
113
|
await sp.saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`);
|
|
109
114
|
|
|
110
|
-
// Create empty themes folder — default themes are served directly from
|
|
111
|
-
// defaultThemesFolders; users can add custom themes here.
|
|
112
|
-
await sp.ensureFolderExists(path.join(config.pagesFolder, 'themes'));
|
|
113
|
-
|
|
114
115
|
// Copy pages
|
|
115
116
|
if (includeDefaultPages) {
|
|
116
117
|
console.log(`Copying default pages to ${config.localFolder} folder...`);
|
|
@@ -141,9 +142,14 @@ async function repairMissingFolders(config: SynthOSConfig): Promise<void> {
|
|
|
141
142
|
await sp.saveFile(path.join(scriptsFolder, 'example.sh'), `#!/bin/bash\n\n# This is an example script\n\n# You can run this script using the following command:\n# sh ${config.localFolder}/scripts/example.sh\n\n# This script will print "Hello, World!" to the console\n\necho "Hello, World!"\n`);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
//
|
|
145
|
-
// defaultThemesFolders
|
|
146
|
-
|
|
145
|
+
// User-local themes are no longer supported — all themes are served from
|
|
146
|
+
// defaultThemesFolders. Remove any stale .synthos/themes/ folder left over
|
|
147
|
+
// from previous versions so it can't shadow default themes.
|
|
148
|
+
const staleThemesFolder = path.join(config.pagesFolder, 'themes');
|
|
149
|
+
if (await sp.checkIfExists(staleThemesFolder)) {
|
|
150
|
+
console.log(`Removing stale ${config.localFolder}/themes folder (themes now come from default-themes)...`);
|
|
151
|
+
await sp.deleteFolder(staleThemesFolder);
|
|
152
|
+
}
|
|
147
153
|
|
|
148
154
|
// Ensure pages/ subfolder exists
|
|
149
155
|
const pagesSubdir = path.join(config.pagesFolder, 'pages');
|