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.
@@ -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
@@ -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}Restart the daemon to pick up changes: clementine restart${RESET}`);
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 steps:${RESET}`);
214
- console.log(` 1. Enable Chrome remote debugging open Chrome with:`);
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
@@ -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, body.agentSlug || undefined);
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
- const capabilityContext = await buildBuilderCapabilityContext(agentSlug);
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 rawName = String(artifact.name || 'new-cron').trim();
5863
- const jobName = agentSlug && rawName.startsWith(agentSlug + ':')
5864
- ? rawName.slice(agentSlug.length + 1)
5865
- : rawName;
5866
- const cronFile = agentSlug
5867
- ? path.join(VAULT_DIR, '00-System', 'agents', agentSlug, 'CRON.md')
5868
- : path.join(VAULT_DIR, '00-System', 'CRON.md');
5869
- const { parsed, jobs } = readCronFileAt(cronFile);
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
- writeCronFileAt(cronFile, parsed, jobs);
5880
- const displayName = agentSlug ? `${agentSlug}:${jobName}` : jobName;
5881
- res.json({ ok: true, name: displayName, message: `Cron job "${displayName}" saved` });
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 = agentSlug
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
- <label for="builder-agent-scope" style="font-size:11px;color:var(--text-muted);font-weight:600">Owner</label>
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 agentSlug = getBuilderSelectedAgentSlug();
15208
- var r;
15209
- if (activeTab === 'crons') {
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 agentSlug = getBuilderSelectedAgentSlug();
15280
- var r;
15281
- if (tpl.schedule) {
15282
- r = await apiJson('POST', '/api/cron', {
15283
- name: name,
15284
- schedule: tpl.schedule,
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 ownerScope = getBuilderOwnerScope();
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 = builderOwnerLabel(w) + ' · ' + w.name + (w.schedule ? ' · ' + w.schedule : '') + (w.enabled ? '' : ' · off');
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 agentSlug = getBuilderSelectedAgentSlug();
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) + ' ' + scopeTag + sourceTag + '</div>'
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
- function setBuilderAgentScope(value) {
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
- var agentSlug = (value && value !== '__all' && value !== '__global') ? value : '';
20544
- if (hidden) hidden.value = agentSlug;
20545
- if (label) {
20546
- label.textContent = value === '__all'
20547
- ? 'All owners'
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
- var type = (document.getElementById('builder-type') || {}).value;
20553
- if (type === 'cron' || type === 'workflow') refreshBuilderCanvasPicker(type);
20554
- if (type === 'skill') refreshBuilderSkills();
20555
- apiJson('POST', '/api/builder/reset', { artifactType: type, agentSlug: agentSlug || undefined }).catch(function(){});
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, agentSlug: getBuilderSelectedAgentSlug() || undefined }).then(function(r) {
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)')