clementine-agent 1.6.1 → 1.6.3
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/dist/cli/browser.d.ts +1 -0
- package/dist/cli/browser.js +171 -8
- package/dist/cli/dashboard.js +51 -193
- package/dist/cli/index.js +5 -1
- package/dist/dashboard/builder/serializer.d.ts +9 -9
- package/dist/dashboard/builder/serializer.js +40 -162
- package/dist/tools/builder-tools.js +2 -2
- package/dist/types.d.ts +0 -1
- package/package.json +1 -1
- package/vendor/browser-harness-mcp/README.md +12 -7
- package/vendor/browser-harness-mcp/__pycache__/server.cpython-314.pyc +0 -0
- package/vendor/browser-harness-mcp/server.py +288 -44
package/dist/cli/browser.d.ts
CHANGED
|
@@ -32,4 +32,5 @@ export declare function cmdBrowserEnable(): Promise<void>;
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function maybePromptBrowserHarness(): Promise<void>;
|
|
34
34
|
export declare function cmdBrowserDisable(): Promise<void>;
|
|
35
|
+
export declare function cmdBrowserConnect(): Promise<void>;
|
|
35
36
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/cli/browser.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { execSync, spawnSync } from 'node:child_process';
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import http from 'node:http';
|
|
22
23
|
import os from 'node:os';
|
|
23
24
|
import path from 'node:path';
|
|
24
25
|
import { fileURLToPath } from 'node:url';
|
|
@@ -46,6 +47,31 @@ function commandExists(cmd) {
|
|
|
46
47
|
const result = spawnSync('which', [cmd], { stdio: 'pipe' });
|
|
47
48
|
return result.status === 0;
|
|
48
49
|
}
|
|
50
|
+
/** Probe the CDP socket — returns true if Chrome is listening on :9222. */
|
|
51
|
+
function probeCdp() {
|
|
52
|
+
return new Promise(resolve => {
|
|
53
|
+
const req = http.get('http://localhost:9222/json/version', { timeout: 1500 }, res => {
|
|
54
|
+
resolve(res.statusCode === 200);
|
|
55
|
+
res.resume();
|
|
56
|
+
});
|
|
57
|
+
req.on('error', () => resolve(false));
|
|
58
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** True if a Google Chrome process is currently running. */
|
|
62
|
+
function isChromeRunning() {
|
|
63
|
+
if (process.platform === 'darwin') {
|
|
64
|
+
const r = spawnSync('pgrep', ['-x', 'Google Chrome'], { stdio: 'pipe' });
|
|
65
|
+
return r.status === 0;
|
|
66
|
+
}
|
|
67
|
+
// Linux: chrome / chromium / google-chrome
|
|
68
|
+
for (const name of ['google-chrome', 'chromium', 'chrome']) {
|
|
69
|
+
const r = spawnSync('pgrep', ['-x', name], { stdio: 'pipe' });
|
|
70
|
+
if (r.status === 0)
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
49
75
|
function pythonVersion() {
|
|
50
76
|
try {
|
|
51
77
|
const out = execSync('python3 --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
@@ -77,6 +103,7 @@ export async function cmdBrowserStatus() {
|
|
|
77
103
|
const harnessOk = existsSync(path.join(HARNESS_HOME, 'src'));
|
|
78
104
|
const servers = loadMcpServers();
|
|
79
105
|
const enabled = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
|
|
106
|
+
const cdpOk = await probeCdp();
|
|
80
107
|
console.log();
|
|
81
108
|
console.log(` ${BOLD}Browser Harness${RESET} ${DIM}(beta)${RESET}`);
|
|
82
109
|
console.log();
|
|
@@ -85,6 +112,7 @@ export async function cmdBrowserStatus() {
|
|
|
85
112
|
console.log(` ${venvOk ? GREEN + '✓' : YELLOW + '○'}${RESET} venv installed ${DIM}${VENV_DIR}${RESET}`);
|
|
86
113
|
console.log(` ${harnessOk ? GREEN + '✓' : YELLOW + '○'}${RESET} harness cloned ${DIM}${HARNESS_HOME}${RESET}`);
|
|
87
114
|
console.log(` ${enabled ? GREEN + '✓' : DIM + '○'}${RESET} MCP entry ${DIM}${enabled ? 'enabled' : 'disabled'} in mcp-servers.json${RESET}`);
|
|
115
|
+
console.log(` ${cdpOk ? GREEN + '✓' : YELLOW + '○'}${RESET} Chrome CDP ${DIM}${cdpOk ? 'connected on :9222' : 'not connected — run: clementine browser connect'}${RESET}`);
|
|
88
116
|
console.log();
|
|
89
117
|
if (!py) {
|
|
90
118
|
console.log(` ${YELLOW}Install Python 3.10+ first:${RESET}`);
|
|
@@ -99,8 +127,12 @@ export async function cmdBrowserStatus() {
|
|
|
99
127
|
console.log(` Next: ${BOLD}clementine browser enable${RESET}`);
|
|
100
128
|
console.log();
|
|
101
129
|
}
|
|
130
|
+
else if (!cdpOk) {
|
|
131
|
+
console.log(` Next: ${BOLD}clementine browser connect${RESET}`);
|
|
132
|
+
console.log();
|
|
133
|
+
}
|
|
102
134
|
else {
|
|
103
|
-
console.log(` ${GREEN}Ready.${RESET} ${DIM}
|
|
135
|
+
console.log(` ${GREEN}Ready.${RESET} ${DIM}Browser harness is fully connected.${RESET}`);
|
|
104
136
|
console.log();
|
|
105
137
|
}
|
|
106
138
|
}
|
|
@@ -210,12 +242,8 @@ export async function cmdBrowserInstall() {
|
|
|
210
242
|
if (!ok)
|
|
211
243
|
process.exit(1);
|
|
212
244
|
console.log();
|
|
213
|
-
console.log(` ${BOLD}Next
|
|
214
|
-
console.log(`
|
|
215
|
-
console.log(` ${CYAN}/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome \\${RESET}`);
|
|
216
|
-
console.log(` ${CYAN}--remote-debugging-port=9222${RESET}`);
|
|
217
|
-
console.log(` 2. Enable the MCP server: ${BOLD}clementine browser enable${RESET}`);
|
|
218
|
-
console.log(` 3. Restart the daemon: ${BOLD}clementine restart${RESET}`);
|
|
245
|
+
console.log(` ${BOLD}Next:${RESET} ${BOLD}clementine browser enable${RESET} — register the MCP server`);
|
|
246
|
+
console.log(` ${DIM}Then connect Chrome with: clementine browser connect${RESET}`);
|
|
219
247
|
console.log();
|
|
220
248
|
}
|
|
221
249
|
export async function cmdBrowserEnable() {
|
|
@@ -287,8 +315,26 @@ export async function maybePromptBrowserHarness() {
|
|
|
287
315
|
}
|
|
288
316
|
console.log();
|
|
289
317
|
console.log(` ${GREEN}✓${RESET} Browser Harness installed and enabled.`);
|
|
290
|
-
console.log(` ${DIM}Open Chrome with --remote-debugging-port=9222 to connect.${RESET}`);
|
|
291
318
|
console.log();
|
|
319
|
+
// Offer to connect Chrome right now
|
|
320
|
+
let connectNow = false;
|
|
321
|
+
try {
|
|
322
|
+
connectNow = await confirm({
|
|
323
|
+
message: 'Connect Chrome now? (relaunches Chrome with debugging — will close current windows)',
|
|
324
|
+
default: false,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Ctrl+C — bail without dismissing
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (connectNow) {
|
|
332
|
+
await runConnect({ confirmQuit: false });
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
console.log(` ${DIM}Connect later with: ${BOLD}clementine browser connect${RESET}`);
|
|
336
|
+
console.log();
|
|
337
|
+
}
|
|
292
338
|
}
|
|
293
339
|
export async function cmdBrowserDisable() {
|
|
294
340
|
const servers = loadMcpServers();
|
|
@@ -307,4 +353,121 @@ export async function cmdBrowserDisable() {
|
|
|
307
353
|
console.log(` ${DIM}Restart the daemon: clementine restart${RESET}`);
|
|
308
354
|
console.log();
|
|
309
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Core connect logic — quits any running Chrome and relaunches with
|
|
358
|
+
* --remote-debugging-port=9222 so browser-harness can connect.
|
|
359
|
+
*
|
|
360
|
+
* Returns true when CDP is reachable on :9222 at the end, false otherwise.
|
|
361
|
+
* Never calls process.exit so it's safe to call from the auto-prompt flow.
|
|
362
|
+
*/
|
|
363
|
+
async function runConnect(opts = {}) {
|
|
364
|
+
// 1. Already connected? Done.
|
|
365
|
+
if (await probeCdp()) {
|
|
366
|
+
console.log();
|
|
367
|
+
console.log(` ${GREEN}✓${RESET} Already connected — Chrome is running with remote debugging on :9222`);
|
|
368
|
+
console.log();
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
// 2. Platform check — auto-launch is currently macOS only
|
|
372
|
+
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
373
|
+
console.error();
|
|
374
|
+
console.error(` ${YELLOW}Auto-connect is only supported on macOS and Linux.${RESET}`);
|
|
375
|
+
console.error(` Launch Chrome manually with the flag: ${BOLD}--remote-debugging-port=9222${RESET}`);
|
|
376
|
+
console.error();
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
// 3. Chrome already running without the flag? Need to quit first.
|
|
380
|
+
if (isChromeRunning()) {
|
|
381
|
+
console.log();
|
|
382
|
+
console.log(` ${YELLOW}Chrome is running, but without remote debugging.${RESET}`);
|
|
383
|
+
console.log(` ${DIM}To connect, Chrome needs to be quit and relaunched with the flag.${RESET}`);
|
|
384
|
+
console.log(` ${DIM}This will close your current Chrome windows.${RESET}`);
|
|
385
|
+
console.log();
|
|
386
|
+
let confirmed = !opts.confirmQuit; // skip prompt when caller already confirmed
|
|
387
|
+
if (opts.confirmQuit) {
|
|
388
|
+
try {
|
|
389
|
+
confirmed = await confirm({
|
|
390
|
+
message: 'Quit Chrome and relaunch with debugging?',
|
|
391
|
+
default: false,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!confirmed) {
|
|
399
|
+
console.log(` ${DIM}Skipped. To do it yourself: quit Chrome (Cmd+Q), then run:${RESET}`);
|
|
400
|
+
console.log(` ${BOLD}clementine browser connect${RESET}`);
|
|
401
|
+
console.log();
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
console.log(` ${DIM}→ quitting Chrome...${RESET}`);
|
|
405
|
+
try {
|
|
406
|
+
if (process.platform === 'darwin') {
|
|
407
|
+
execSync('osascript -e \'tell application "Google Chrome" to quit\'', { stdio: 'pipe' });
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
// Linux: graceful TERM, then KILL if needed
|
|
411
|
+
try {
|
|
412
|
+
execSync('pkill -TERM -x "google-chrome|chromium|chrome"', { stdio: 'pipe' });
|
|
413
|
+
}
|
|
414
|
+
catch { /* ok */ }
|
|
415
|
+
}
|
|
416
|
+
// Wait briefly for Chrome to actually exit
|
|
417
|
+
for (let i = 0; i < 15; i++) {
|
|
418
|
+
if (!isChromeRunning())
|
|
419
|
+
break;
|
|
420
|
+
await new Promise(r => setTimeout(r, 300));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
console.error(` ${RED}Failed to quit Chrome.${RESET} Please quit it manually and re-run.`);
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// 4. Launch Chrome with the debugging flag
|
|
429
|
+
console.log(` ${DIM}→ launching Chrome with --remote-debugging-port=9222${RESET}`);
|
|
430
|
+
try {
|
|
431
|
+
if (process.platform === 'darwin') {
|
|
432
|
+
execSync('open -na "Google Chrome" --args --remote-debugging-port=9222', { stdio: 'pipe' });
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// Linux — find a chrome binary in PATH
|
|
436
|
+
const candidates = ['google-chrome', 'chromium', 'chrome'];
|
|
437
|
+
const bin = candidates.find(commandExists);
|
|
438
|
+
if (!bin) {
|
|
439
|
+
console.error(` ${RED}No Chrome / Chromium binary found in PATH.${RESET}`);
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
// Launch detached so this command returns immediately
|
|
443
|
+
execSync(`nohup ${bin} --remote-debugging-port=9222 >/dev/null 2>&1 &`, { stdio: 'pipe' });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
console.error(` ${RED}Failed to launch Chrome:${RESET} ${String(e).slice(0, 200)}`);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
// 5. Poll for CDP availability (up to ~6s)
|
|
451
|
+
for (let i = 0; i < 24; i++) {
|
|
452
|
+
await new Promise(r => setTimeout(r, 250));
|
|
453
|
+
if (await probeCdp()) {
|
|
454
|
+
console.log();
|
|
455
|
+
console.log(` ${GREEN}✓${RESET} Connected — Chrome is running with remote debugging on :9222`);
|
|
456
|
+
console.log(` ${DIM}Browser harness can now control your live session.${RESET}`);
|
|
457
|
+
console.log();
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
console.error();
|
|
462
|
+
console.error(` ${YELLOW}Chrome launched, but CDP socket isn't responding yet.${RESET}`);
|
|
463
|
+
console.error(` ${DIM}Check that Chrome started, then verify with:${RESET}`);
|
|
464
|
+
console.error(` ${CYAN}curl http://localhost:9222/json/version${RESET}`);
|
|
465
|
+
console.error();
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
export async function cmdBrowserConnect() {
|
|
469
|
+
const ok = await runConnect({ confirmQuit: true });
|
|
470
|
+
if (!ok)
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
310
473
|
//# sourceMappingURL=browser.js.map
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -3174,9 +3174,8 @@ export async function cmdDashboard(opts) {
|
|
|
3174
3174
|
maxTurns: 15,
|
|
3175
3175
|
}],
|
|
3176
3176
|
sourceFile: '',
|
|
3177
|
-
agentSlug: body.agentSlug || undefined,
|
|
3178
3177
|
};
|
|
3179
|
-
const id = workflowId(slug
|
|
3178
|
+
const id = workflowId(slug);
|
|
3180
3179
|
const result = saveWorkflow(id, wf);
|
|
3181
3180
|
if (!result.ok) {
|
|
3182
3181
|
res.status(400).json({ error: result.error });
|
|
@@ -5621,55 +5620,6 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
5621
5620
|
// ── Builder chat endpoint ──────────────────────────────────────────
|
|
5622
5621
|
// Track which builder sessions have received the full system prefix
|
|
5623
5622
|
const builderSessionInited = new Set();
|
|
5624
|
-
async function buildBuilderCapabilityContext(agentSlug) {
|
|
5625
|
-
const lines = [];
|
|
5626
|
-
try {
|
|
5627
|
-
const { discoverMcpServers, loadToolInventory } = await import('../agent/mcp-bridge.js');
|
|
5628
|
-
const servers = discoverMcpServers();
|
|
5629
|
-
const inv = loadToolInventory();
|
|
5630
|
-
const serverLines = servers.map((s) => {
|
|
5631
|
-
const tools = (inv?.tools ?? [])
|
|
5632
|
-
.filter((t) => t.startsWith(`mcp__${s.name}__`))
|
|
5633
|
-
.map((t) => t.split('__')[2])
|
|
5634
|
-
.filter(Boolean)
|
|
5635
|
-
.slice(0, 40);
|
|
5636
|
-
return `- ${s.name} [${s.enabled ? 'on' : 'off'}]: ${tools.length ? tools.join(', ') : '(no tools cached yet)'}`;
|
|
5637
|
-
});
|
|
5638
|
-
lines.push('[AVAILABLE MCP SERVERS AND TOOLS]');
|
|
5639
|
-
lines.push(serverLines.length ? serverLines.join('\n') : '- none configured');
|
|
5640
|
-
}
|
|
5641
|
-
catch {
|
|
5642
|
-
lines.push('[AVAILABLE MCP SERVERS AND TOOLS]\n- unavailable');
|
|
5643
|
-
}
|
|
5644
|
-
const readSkills = (dir, scope) => {
|
|
5645
|
-
if (!existsSync(dir))
|
|
5646
|
-
return [];
|
|
5647
|
-
return readdirSync(dir)
|
|
5648
|
-
.filter(f => f.endsWith('.md'))
|
|
5649
|
-
.slice(0, 80)
|
|
5650
|
-
.map(f => {
|
|
5651
|
-
try {
|
|
5652
|
-
const parsed = matter(readFileSync(path.join(dir, f), 'utf-8'));
|
|
5653
|
-
const name = f.replace(/\.md$/, '');
|
|
5654
|
-
const title = String(parsed.data.title ?? name);
|
|
5655
|
-
const triggers = Array.isArray(parsed.data.triggers) ? parsed.data.triggers.join(', ') : '';
|
|
5656
|
-
const tools = Array.isArray(parsed.data.toolsUsed) ? parsed.data.toolsUsed.join(', ') : '';
|
|
5657
|
-
return `- ${title} (${scope}${name !== title ? `/${name}` : ''})${triggers ? ` triggers: ${triggers}` : ''}${tools ? ` tools: ${tools}` : ''}`;
|
|
5658
|
-
}
|
|
5659
|
-
catch {
|
|
5660
|
-
return null;
|
|
5661
|
-
}
|
|
5662
|
-
})
|
|
5663
|
-
.filter((x) => Boolean(x));
|
|
5664
|
-
};
|
|
5665
|
-
const globalSkills = readSkills(path.join(VAULT_DIR, '00-System', 'skills'), 'global');
|
|
5666
|
-
const agentSkills = agentSlug
|
|
5667
|
-
? readSkills(path.join(VAULT_DIR, '00-System', 'agents', agentSlug, 'skills'), `agent:${agentSlug}`)
|
|
5668
|
-
: [];
|
|
5669
|
-
lines.push('[AVAILABLE SKILLS]');
|
|
5670
|
-
lines.push([...agentSkills, ...globalSkills].length ? [...agentSkills, ...globalSkills].join('\n') : '- none saved yet');
|
|
5671
|
-
return `\n${lines.join('\n\n')}\n`;
|
|
5672
|
-
}
|
|
5673
5623
|
app.post('/api/builder/chat', async (req, res) => {
|
|
5674
5624
|
const { message, artifactType, agentSlug, currentArtifact, attachments, linkedTools } = req.body;
|
|
5675
5625
|
if (!message || typeof message !== 'string') {
|
|
@@ -5736,8 +5686,7 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
5736
5686
|
`Workflows are defined as markdown files with YAML frontmatter. Each step has an id, prompt, optional agent, and optional dependsOn array.\n` +
|
|
5737
5687
|
`When the user says "save" or approves, output the final artifact block.]\n\n`
|
|
5738
5688
|
: `[BUILDER MODE: You are helping configure an artifact. Output structured JSON blocks as you build.]\n\n`;
|
|
5739
|
-
|
|
5740
|
-
enrichedMessage = builderPrefix + capabilityContext + fileContext + toolContext + artifactContext + message;
|
|
5689
|
+
enrichedMessage = builderPrefix + fileContext + toolContext + artifactContext + message;
|
|
5741
5690
|
builderSessionInited.add(sessionKey);
|
|
5742
5691
|
}
|
|
5743
5692
|
else {
|
|
@@ -5859,26 +5808,35 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
5859
5808
|
}
|
|
5860
5809
|
else if (artifactType === 'cron') {
|
|
5861
5810
|
// Scope cron to agent if selected
|
|
5862
|
-
const
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5811
|
+
const jobName = agentSlug && !artifact.name.startsWith(agentSlug + ':')
|
|
5812
|
+
? `${agentSlug}:${artifact.name}`
|
|
5813
|
+
: artifact.name;
|
|
5814
|
+
const { cronFile } = agentSlug
|
|
5815
|
+
? (() => {
|
|
5816
|
+
const agentCronFile = path.join(VAULT_DIR, '00-System', 'agents', agentSlug, 'CRON.md');
|
|
5817
|
+
return { cronFile: existsSync(agentCronFile) ? agentCronFile : path.join(VAULT_DIR, '00-System', 'CRON.md') };
|
|
5818
|
+
})()
|
|
5819
|
+
: { cronFile: path.join(VAULT_DIR, '00-System', 'CRON.md') };
|
|
5820
|
+
if (!existsSync(cronFile)) {
|
|
5821
|
+
res.status(500).json({ error: 'CRON.md not found' });
|
|
5822
|
+
return;
|
|
5823
|
+
}
|
|
5824
|
+
const matterMod = require('gray-matter');
|
|
5825
|
+
const parsed = matterMod(readFileSync(cronFile, 'utf-8'));
|
|
5826
|
+
const jobs = parsed.data.jobs || [];
|
|
5870
5827
|
jobs.push({
|
|
5871
5828
|
name: jobName,
|
|
5872
5829
|
schedule: artifact.schedule,
|
|
5873
5830
|
prompt: artifact.prompt,
|
|
5874
5831
|
tier: artifact.tier || 1,
|
|
5875
5832
|
enabled: artifact.enabled !== false,
|
|
5833
|
+
...(agentSlug ? { agent: agentSlug } : {}),
|
|
5876
5834
|
...(artifact.mode === 'unleashed' ? { mode: 'unleashed', max_hours: artifact.max_hours || 1 } : {}),
|
|
5877
5835
|
...(artifact.work_dir ? { work_dir: artifact.work_dir } : {}),
|
|
5878
5836
|
});
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
res.json({ ok: true, name:
|
|
5837
|
+
parsed.data.jobs = jobs;
|
|
5838
|
+
writeFileSync(cronFile, matterMod.stringify(parsed.content, parsed.data));
|
|
5839
|
+
res.json({ ok: true, name: jobName, message: `Cron job "${jobName}" saved` });
|
|
5882
5840
|
}
|
|
5883
5841
|
else if (artifactType === 'agent') {
|
|
5884
5842
|
// Create agent via the same mechanism as the manual form
|
|
@@ -5907,19 +5865,15 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
5907
5865
|
}
|
|
5908
5866
|
else if (artifactType === 'workflow') {
|
|
5909
5867
|
// Save workflow as markdown file
|
|
5910
|
-
const wfDir =
|
|
5911
|
-
? path.join(VAULT_DIR, '00-System', 'agents', agentSlug, 'workflows')
|
|
5912
|
-
: path.join(VAULT_DIR, '00-System', 'workflows');
|
|
5868
|
+
const wfDir = path.join(VAULT_DIR, '00-System', 'workflows');
|
|
5913
5869
|
if (!existsSync(wfDir))
|
|
5914
5870
|
mkdirSync(wfDir, { recursive: true });
|
|
5915
5871
|
const wfName = (artifact.name || 'new-workflow').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60);
|
|
5916
5872
|
const matterMod = require('gray-matter');
|
|
5917
5873
|
const meta = {
|
|
5918
|
-
type: 'workflow',
|
|
5919
5874
|
name: artifact.name || wfName,
|
|
5920
5875
|
description: artifact.description || '',
|
|
5921
5876
|
enabled: true,
|
|
5922
|
-
...(agentSlug ? { agentSlug } : {}),
|
|
5923
5877
|
...(artifact.schedule ? { trigger: { schedule: artifact.schedule } } : {}),
|
|
5924
5878
|
};
|
|
5925
5879
|
const content = matterMod.stringify(`\n# ${artifact.name || wfName}\n\n${artifact.description || ''}\n\n## Steps\n\n${artifact.steps || ''}\n`, meta);
|
|
@@ -12361,13 +12315,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
12361
12315
|
<option value="agent">agent</option>
|
|
12362
12316
|
<option value="workflow">workflow</option>
|
|
12363
12317
|
</select>
|
|
12364
|
-
<
|
|
12365
|
-
<select id="builder-agent-scope" onchange="setBuilderAgentScope(this.value)" style="padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-secondary);color:var(--text-primary);font-size:12px;max-width:220px">
|
|
12366
|
-
<option value="__all">All owners</option>
|
|
12367
|
-
<option value="__global">Clementine (global)</option>
|
|
12368
|
-
</select>
|
|
12369
|
-
<span id="builder-capability-summary" style="font-size:11px;color:var(--text-muted)"></span>
|
|
12370
|
-
<span id="builder-agent-label" style="display:none;padding:0;font-size:13px;color:var(--text-secondary);font-weight:500"></span>
|
|
12318
|
+
<span id="builder-agent-label" style="padding:0;font-size:13px;color:var(--text-secondary);font-weight:500"></span>
|
|
12371
12319
|
<input type="hidden" id="builder-agent" value="">
|
|
12372
12320
|
<span style="flex:1"></span>
|
|
12373
12321
|
<button class="btn-sm btn-primary" onclick="newFromBuildHeader()" title="Create a new artifact for this tab" style="padding:4px 14px;border-radius:6px;cursor:pointer;font-size:12px">New</button>
|
|
@@ -15204,24 +15152,9 @@ async function newFromBuildHeader() {
|
|
|
15204
15152
|
var name = prompt('Name your new ' + noun + ':');
|
|
15205
15153
|
if (!name || !name.trim()) return;
|
|
15206
15154
|
try {
|
|
15207
|
-
var
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
r = await apiJson('POST', '/api/cron', {
|
|
15211
|
-
name: name.trim(),
|
|
15212
|
-
schedule: '0 9 * * *',
|
|
15213
|
-
prompt: 'Describe what this scheduled automation should do.',
|
|
15214
|
-
tier: 1,
|
|
15215
|
-
enabled: true,
|
|
15216
|
-
agent: agentSlug || undefined,
|
|
15217
|
-
});
|
|
15218
|
-
if (r && !r.error) r.id = builderScopedId('cron', name.trim(), agentSlug);
|
|
15219
|
-
} else {
|
|
15220
|
-
r = await apiJson('POST', '/api/builder/workflows', {
|
|
15221
|
-
name: name.trim(),
|
|
15222
|
-
agentSlug: agentSlug || undefined,
|
|
15223
|
-
});
|
|
15224
|
-
}
|
|
15155
|
+
var body = { name: name.trim() };
|
|
15156
|
+
if (activeTab === 'crons') body.schedule = '0 9 * * *'; // sensible default; user edits in canvas
|
|
15157
|
+
var r = await apiJson('POST', '/api/builder/workflows', body);
|
|
15225
15158
|
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
15226
15159
|
if (r && r.id) {
|
|
15227
15160
|
await refreshBuilderCanvasPicker(activeTab === 'crons' ? 'cron' : 'workflow');
|
|
@@ -15276,26 +15209,12 @@ async function forkBuildTemplate(templateId) {
|
|
|
15276
15209
|
var name = prompt('Name for the new workflow:', tpl.name);
|
|
15277
15210
|
if (!name) return;
|
|
15278
15211
|
try {
|
|
15279
|
-
var
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
prompt: tpl.initialPrompt,
|
|
15286
|
-
tier: 1,
|
|
15287
|
-
enabled: true,
|
|
15288
|
-
agent: agentSlug || undefined,
|
|
15289
|
-
});
|
|
15290
|
-
if (r && !r.error) r.id = builderScopedId('cron', name, agentSlug);
|
|
15291
|
-
} else {
|
|
15292
|
-
r = await apiJson('POST', '/api/builder/workflows', {
|
|
15293
|
-
name: name,
|
|
15294
|
-
description: tpl.description,
|
|
15295
|
-
initialPrompt: tpl.initialPrompt,
|
|
15296
|
-
agentSlug: agentSlug || undefined,
|
|
15297
|
-
});
|
|
15298
|
-
}
|
|
15212
|
+
var r = await apiJson('POST', '/api/builder/workflows', {
|
|
15213
|
+
name: name,
|
|
15214
|
+
description: tpl.description,
|
|
15215
|
+
schedule: tpl.schedule,
|
|
15216
|
+
initialPrompt: tpl.initialPrompt,
|
|
15217
|
+
});
|
|
15299
15218
|
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
15300
15219
|
if (r && r.id) {
|
|
15301
15220
|
switchBuildTab(tpl.schedule ? 'crons' : 'workflows');
|
|
@@ -19432,26 +19351,6 @@ var _builderCanvasOpenId = null;
|
|
|
19432
19351
|
var _builderCanvasLastWorkflow = null;
|
|
19433
19352
|
var _builderDrawflowLoading = null;
|
|
19434
19353
|
|
|
19435
|
-
function getBuilderOwnerScope() {
|
|
19436
|
-
var sel = document.getElementById('builder-agent-scope');
|
|
19437
|
-
return (sel && sel.value) || '__all';
|
|
19438
|
-
}
|
|
19439
|
-
|
|
19440
|
-
function getBuilderSelectedAgentSlug() {
|
|
19441
|
-
var scope = getBuilderOwnerScope();
|
|
19442
|
-
return (scope && scope !== '__all' && scope !== '__global') ? scope : '';
|
|
19443
|
-
}
|
|
19444
|
-
|
|
19445
|
-
function builderScopedId(type, name, agentSlug) {
|
|
19446
|
-
var prefix = type === 'cron' ? 'cron:' : 'workflow:';
|
|
19447
|
-
return agentSlug ? (prefix + 'agent:' + agentSlug + ':' + name) : (prefix + 'global:' + name);
|
|
19448
|
-
}
|
|
19449
|
-
|
|
19450
|
-
function builderOwnerLabel(item) {
|
|
19451
|
-
if (!item || !item.agentSlug) return 'Clementine';
|
|
19452
|
-
return item.agentSlug;
|
|
19453
|
-
}
|
|
19454
|
-
|
|
19455
19354
|
function _ensureDrawflowLoaded() {
|
|
19456
19355
|
if (window.Drawflow) return Promise.resolve();
|
|
19457
19356
|
if (_builderDrawflowLoading) return _builderDrawflowLoading;
|
|
@@ -19471,17 +19370,11 @@ async function refreshBuilderCanvasPicker(type) {
|
|
|
19471
19370
|
try {
|
|
19472
19371
|
var r = await apiFetch('/api/builder/workflows');
|
|
19473
19372
|
var d = await r.json();
|
|
19474
|
-
var
|
|
19475
|
-
var items = (d.workflows || []).filter(function(w) {
|
|
19476
|
-
if (w.origin !== type) return false;
|
|
19477
|
-
if (ownerScope === '__all') return true;
|
|
19478
|
-
if (ownerScope === '__global') return !w.agentSlug;
|
|
19479
|
-
return w.agentSlug === ownerScope;
|
|
19480
|
-
});
|
|
19373
|
+
var items = (d.workflows || []).filter(function(w) { return w.origin === type; });
|
|
19481
19374
|
var opts = '<option value="">' + (items.length ? '— pick a ' + type + ' —' : '(none yet)') + '</option>';
|
|
19482
19375
|
for (var i = 0; i < items.length; i++) {
|
|
19483
19376
|
var w = items[i];
|
|
19484
|
-
var lbl =
|
|
19377
|
+
var lbl = w.name + (w.schedule ? ' · ' + w.schedule : '') + (w.enabled ? '' : ' · off');
|
|
19485
19378
|
opts += '<option value="' + esc(w.id) + '">' + esc(lbl) + '</option>';
|
|
19486
19379
|
}
|
|
19487
19380
|
picker.innerHTML = opts;
|
|
@@ -20138,8 +20031,7 @@ async function refreshBuilderSkills() {
|
|
|
20138
20031
|
var countEl = document.getElementById('builder-skills-count');
|
|
20139
20032
|
if (!container) return;
|
|
20140
20033
|
try {
|
|
20141
|
-
var
|
|
20142
|
-
var r = await apiFetch(agentSlug ? ('/api/agents/' + encodeURIComponent(agentSlug) + '/skills') : '/api/skills');
|
|
20034
|
+
var r = await apiFetch('/api/skills');
|
|
20143
20035
|
var d = await r.json();
|
|
20144
20036
|
var skills = d.skills || [];
|
|
20145
20037
|
if (countEl) countEl.textContent = skills.length + ' skill' + (skills.length !== 1 ? 's' : '');
|
|
@@ -20149,13 +20041,12 @@ async function refreshBuilderSkills() {
|
|
|
20149
20041
|
}
|
|
20150
20042
|
var html = '';
|
|
20151
20043
|
for (var s of skills) {
|
|
20152
|
-
var scopeTag = s.scope === 'agent' ? '<span style="font-size:9px;background:var(--blue);color:white;padding:1px 5px;border-radius:3px">agent</span> ' : '';
|
|
20153
20044
|
var sourceTag = s.source === 'builder' ? '<span style="font-size:9px;background:var(--accent);color:white;padding:1px 5px;border-radius:3px">built</span>'
|
|
20154
20045
|
: s.source === 'manual' ? '<span style="font-size:9px;background:var(--blue);color:white;padding:1px 5px;border-radius:3px">taught</span>'
|
|
20155
20046
|
: '<span style="font-size:9px;background:var(--bg-tertiary);color:var(--text-muted);padding:1px 5px;border-radius:3px">' + esc(s.source || 'auto') + '</span>';
|
|
20156
20047
|
html += '<div style="display:flex;align-items:center;gap:6px;padding:6px 4px;border-bottom:1px solid var(--border);font-size:12px">'
|
|
20157
20048
|
+ '<div style="flex:1;min-width:0">'
|
|
20158
|
-
+ '<div style="font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(s.title) + ' ' +
|
|
20049
|
+
+ '<div style="font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(s.title) + ' ' + sourceTag + '</div>'
|
|
20159
20050
|
+ '<div style="font-size:10px;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(s.description || '') + '</div>'
|
|
20160
20051
|
+ '</div>'
|
|
20161
20052
|
+ '<button onclick="editSkillInBuilder(\\x27' + esc(s.name) + '\\x27)" style="background:none;border:1px solid var(--border);border-radius:4px;padding:2px 8px;font-size:10px;color:var(--accent);cursor:pointer;white-space:nowrap">Edit</button>'
|
|
@@ -20203,17 +20094,6 @@ async function editSkillInBuilder(name, agentSlug) {
|
|
|
20203
20094
|
var agentLabel = document.getElementById('builder-agent-label');
|
|
20204
20095
|
if (agentHidden) agentHidden.value = agentSlug || '';
|
|
20205
20096
|
if (agentLabel) agentLabel.textContent = agentSlug ? 'Agent: ' + agentSlug : '';
|
|
20206
|
-
var ownerSel = document.getElementById('builder-agent-scope');
|
|
20207
|
-
if (ownerSel) {
|
|
20208
|
-
if (agentSlug && !Array.prototype.some.call(ownerSel.options, function(o) { return o.value === agentSlug; })) {
|
|
20209
|
-
var opt = document.createElement('option');
|
|
20210
|
-
opt.value = agentSlug;
|
|
20211
|
-
opt.textContent = agentSlug;
|
|
20212
|
-
ownerSel.appendChild(opt);
|
|
20213
|
-
}
|
|
20214
|
-
ownerSel.value = agentSlug || '__global';
|
|
20215
|
-
setBuilderAgentScope(ownerSel.value);
|
|
20216
|
-
}
|
|
20217
20097
|
|
|
20218
20098
|
// Reset session on server so next message gets full prefix
|
|
20219
20099
|
await apiJson('POST', '/api/builder/reset', { artifactType: 'skill', agentSlug: agentSlug || undefined }).catch(function(){});
|
|
@@ -20537,45 +20417,23 @@ async function saveBuilderArtifact() {
|
|
|
20537
20417
|
} catch(e) { toast('Error: ' + e, 'error'); }
|
|
20538
20418
|
}
|
|
20539
20419
|
|
|
20540
|
-
|
|
20420
|
+
// Populate agent dropdown when builder page loads
|
|
20421
|
+
function refreshBuilderAgents(preselect) {
|
|
20541
20422
|
var hidden = document.getElementById('builder-agent');
|
|
20542
20423
|
var label = document.getElementById('builder-agent-label');
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
if (
|
|
20546
|
-
label.textContent =
|
|
20547
|
-
|
|
20548
|
-
: value === '__global'
|
|
20549
|
-
? 'Clementine (global)'
|
|
20550
|
-
: agentSlug;
|
|
20424
|
+
if (!hidden || !label) return;
|
|
20425
|
+
hidden.value = preselect || '';
|
|
20426
|
+
if (!preselect) {
|
|
20427
|
+
label.textContent = 'Clementine (global)';
|
|
20428
|
+
return;
|
|
20551
20429
|
}
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
if (
|
|
20555
|
-
|
|
20556
|
-
}
|
|
20557
|
-
|
|
20558
|
-
// Populate owner dropdown when builder page loads
|
|
20559
|
-
async function refreshBuilderAgents(preselect) {
|
|
20560
|
-
var hidden = document.getElementById('builder-agent');
|
|
20561
|
-
var label = document.getElementById('builder-agent-label');
|
|
20562
|
-
var select = document.getElementById('builder-agent-scope');
|
|
20563
|
-
if (!hidden || !label || !select) return;
|
|
20564
|
-
var current = preselect ? preselect : (select.value || '__all');
|
|
20565
|
-
try {
|
|
20566
|
-
var r = await apiFetch('/api/agents');
|
|
20567
|
-
var agents = await r.json();
|
|
20568
|
-
var opts = '<option value="__all">All owners</option><option value="__global">Clementine (global)</option>';
|
|
20569
|
-
(agents || []).forEach(function(a) {
|
|
20570
|
-
opts += '<option value="' + esc(a.slug) + '">' + esc(a.name || a.slug) + '</option>';
|
|
20571
|
-
});
|
|
20572
|
-
select.innerHTML = opts;
|
|
20573
|
-
} catch(e) {
|
|
20574
|
-
select.innerHTML = '<option value="__all">All owners</option><option value="__global">Clementine (global)</option>';
|
|
20430
|
+
// Look up agent name from sidebar
|
|
20431
|
+
var teamItem = document.querySelector('.team-nav-item[data-slug="' + preselect + '"] span');
|
|
20432
|
+
if (teamItem) {
|
|
20433
|
+
label.textContent = teamItem.textContent;
|
|
20434
|
+
} else {
|
|
20435
|
+
label.textContent = preselect;
|
|
20575
20436
|
}
|
|
20576
|
-
var hasCurrent = Array.prototype.some.call(select.options, function(o) { return o.value === current; });
|
|
20577
|
-
select.value = hasCurrent ? current : '__all';
|
|
20578
|
-
setBuilderAgentScope(select.value);
|
|
20579
20437
|
}
|
|
20580
20438
|
|
|
20581
20439
|
// ── Builder Linked Tools ──────────────────
|
|
@@ -20946,7 +20804,7 @@ function openBuilderForNewWorkflow() {
|
|
|
20946
20804
|
if (typeSel) { typeSel.value = 'workflow'; updateBuilderMode(); }
|
|
20947
20805
|
var name = prompt('Name your new workflow:');
|
|
20948
20806
|
if (!name) return;
|
|
20949
|
-
apiJson('POST', '/api/builder/workflows', { name: name
|
|
20807
|
+
apiJson('POST', '/api/builder/workflows', { name: name }).then(function(r) {
|
|
20950
20808
|
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
20951
20809
|
if (r && r.id) {
|
|
20952
20810
|
// Refresh picker, then open the new workflow
|
package/dist/cli/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import { cmdCronList, cmdCronRun, cmdCronRunDue, cmdCronRuns, cmdCronAdd, cmdCro
|
|
|
24
24
|
import { cmdDashboard } from './dashboard.js';
|
|
25
25
|
import { cmdChat } from './chat.js';
|
|
26
26
|
import { cmdIngestSeed, cmdIngestRun, cmdIngestList, cmdIngestStatus } from './ingest.js';
|
|
27
|
-
import { cmdBrowserStatus, cmdBrowserInstall, cmdBrowserEnable, cmdBrowserDisable, maybePromptBrowserHarness } from './browser.js';
|
|
27
|
+
import { cmdBrowserStatus, cmdBrowserInstall, cmdBrowserEnable, cmdBrowserDisable, cmdBrowserConnect, maybePromptBrowserHarness } from './browser.js';
|
|
28
28
|
import { isSensitiveEnvKey } from '../secrets/sensitivity.js';
|
|
29
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
30
30
|
const __dirname = path.dirname(__filename);
|
|
@@ -4570,6 +4570,10 @@ browserCmd
|
|
|
4570
4570
|
.command('enable')
|
|
4571
4571
|
.description('Register the browser harness MCP server in mcp-servers.json')
|
|
4572
4572
|
.action(cmdBrowserEnable);
|
|
4573
|
+
browserCmd
|
|
4574
|
+
.command('connect')
|
|
4575
|
+
.description('Quit Chrome and relaunch it with --remote-debugging-port=9222 (one-shot)')
|
|
4576
|
+
.action(cmdBrowserConnect);
|
|
4573
4577
|
browserCmd
|
|
4574
4578
|
.command('disable')
|
|
4575
4579
|
.description('Remove the browser harness MCP entry (keeps installed files)')
|