clementine-agent 1.0.37 → 1.0.40

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.
@@ -1046,12 +1046,40 @@ Delegate data-heavy work (SEO, analytics, bulk API calls for 3+ entities) to sub
1046
1046
  Never spawn a sub-agent with vague instructions like "handle this brief" — tell it exactly what to read, what to change, and where to write the result.
1047
1047
  `);
1048
1048
  }
1049
- // Inject Claude Desktop integration awareness (compact — ~20 tokens per integration)
1049
+ // Inject Claude Desktop integration awareness. Two lists:
1050
+ // - Known-confirmed: integrations the agent has actually used before
1051
+ // (persisted in claude-integrations.json). High confidence.
1052
+ // - Possibly-available: common integrations the owner may have connected
1053
+ // at claude.ai level that we don't have a usage record for yet. The
1054
+ // integrations-file is written reactively — only entries with
1055
+ // successful tool uses get captured — so a freshly-connected
1056
+ // Google Drive / Gmail / Slack connector is invisible to us until we
1057
+ // try it. Hint the agent that it can try these blindly; the tool
1058
+ // call will either succeed or return an auth error, at which point
1059
+ // the record gets captured.
1050
1060
  try {
1051
1061
  const integrations = _mcpBridge?.getClaudeIntegrations() ?? [];
1062
+ const knownNames = new Set(integrations.map(ig => ig.name));
1052
1063
  if (integrations.length > 0) {
1053
1064
  const names = integrations.map(ig => ig.label).join(', ');
1054
- parts.push(`**Connected via Claude Desktop:** ${names}. Use their \`mcp__claude_ai_*\` tools for email, calendar, etc.`);
1065
+ parts.push(`**Connected via Claude Desktop:** ${names}. Use their \`mcp__claude_ai_*\` tools (e.g. \`mcp__claude_ai_Google_Drive__search_files\`).`);
1066
+ }
1067
+ // Common integrations to speculatively mention. If the owner has
1068
+ // connected any of these at claude.ai, the corresponding tool names
1069
+ // will work even if no prior record exists.
1070
+ const SPECULATIVE = [
1071
+ ['Google_Drive', 'Google Drive'], ['Gmail', 'Gmail'],
1072
+ ['Google_Calendar', 'Google Calendar'], ['Google_Workspace', 'Google Workspace'],
1073
+ ['Slack', 'Slack'], ['Notion', 'Notion'], ['GitHub', 'GitHub'],
1074
+ ['Linear', 'Linear'], ['Asana', 'Asana'], ['Jira', 'Jira'],
1075
+ ['Dropbox', 'Dropbox'], ['Salesforce', 'Salesforce'],
1076
+ ['Microsoft_365', 'Microsoft 365'],
1077
+ ];
1078
+ const maybe = SPECULATIVE.filter(([name]) => !knownNames.has(name)).map(([, label]) => label);
1079
+ if (maybe.length > 0) {
1080
+ parts.push(`**Possibly connected (try them — they'll either work or return an auth error):** ${maybe.join(', ')}. ` +
1081
+ `Tool names follow \`mcp__claude_ai_<IntegrationName>__<tool>\` — e.g. \`mcp__claude_ai_Google_Drive__search_files\`, \`mcp__claude_ai_Gmail__authenticate\`. ` +
1082
+ `Don't tell ${owner} an integration is "not available" without first attempting the tool call — a fresh connection won't be in your recorded list until after first use.`);
1055
1083
  }
1056
1084
  }
1057
1085
  catch { /* non-fatal */ }
@@ -329,7 +329,12 @@ function parseClaudeDesktopTool(toolName) {
329
329
  const match = toolName.match(/^mcp__claude_ai_([^_]+(?:_[^_]+)*)__(.+)$/);
330
330
  if (!match)
331
331
  return null;
332
- return { integration: match[1], tool: match[2] };
332
+ const raw = match[1];
333
+ // Normalize against the known canonical set (case-insensitive) so we don't
334
+ // create both `Microsoft_365` and `microsoft_365` entries when the SDK
335
+ // occasionally hands us a lowercased tool name.
336
+ const canonical = Object.keys(INTEGRATION_LABELS).find(k => k.toLowerCase() === raw.toLowerCase()) ?? raw;
337
+ return { integration: canonical, tool: match[2] };
333
338
  }
334
339
  /** Load persisted integrations from disk. */
335
340
  export function loadClaudeIntegrations() {
@@ -1159,6 +1159,17 @@ function writeCronFileAt(cronFile, parsed, jobs) {
1159
1159
  }
1160
1160
  // ── Express app ──────────────────────────────────────────────────────
1161
1161
  export async function cmdDashboard(opts) {
1162
+ // Ensure BASE_DIR exists before any dashboard-local files (PID file, auth
1163
+ // token) are written. Other CLI commands route through ensureDataHome() in
1164
+ // index.ts, but the dashboard command doesn't — on a truly fresh install
1165
+ // where someone runs `clementine dashboard` before `clementine launch` or
1166
+ // `config setup`, the parent-process writeFileSync(DASHBOARD_PID_FILE)
1167
+ // would ENOENT and the dashboard would die before the browser could see
1168
+ // it. Idempotent, so safe for every invocation.
1169
+ try {
1170
+ mkdirSync(BASE_DIR, { recursive: true });
1171
+ }
1172
+ catch { /* ignore */ }
1162
1173
  // Child process skips the kill step — parent already handled it
1163
1174
  if (!process.env.__CLEM_DASHBOARD_CHILD) {
1164
1175
  const killed = killExistingDashboards();
@@ -9328,7 +9339,7 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
9328
9339
  const wmHtml = a.workingMemorySnippet
9329
9340
  ? '<div style="margin-top:8px;padding:8px;background:var(--bg-input);border-radius:6px;font-size:11px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + a.workingMemorySnippet + '</div>'
9330
9341
  : '';
9331
- return '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:16px;cursor:pointer" onclick="showPage(\'team\');setTimeout(()=>selectAgent(\''+a.slug+'\'),100)">' +
9342
+ return '<div style="background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:16px;cursor:pointer" onclick="showPage(\\'team\\');setTimeout(()=>selectAgent(\\''+a.slug+'\\'),100)">' +
9332
9343
  '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
9333
9344
  '<div style="width:40px;height:40px;border-radius:50%;background:var(--clementine);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px">' + avatarLetter + '</div>' +
9334
9345
  '<div><div style="font-weight:600">' + (a.name || a.slug) + '</div>' +
@@ -16480,11 +16491,11 @@ async function applyBrokenJobFix(jobName) {
16480
16491
  return;
16481
16492
  }
16482
16493
  var diffPreview = (dryRes.diff || '(no diff)').slice(0, 1200);
16483
- var msg = 'Apply this fix to ' + jobName + '?\n\n'
16484
- + 'File: ' + (dryRes.file || 'unknown') + '\n'
16485
- + 'Operations: ' + (dryRes.appliedOps || []).length + '\n\n'
16494
+ var msg = 'Apply this fix to ' + jobName + '?\\n\\n'
16495
+ + 'File: ' + (dryRes.file || 'unknown') + '\\n'
16496
+ + 'Operations: ' + (dryRes.appliedOps || []).length + '\\n\\n'
16486
16497
  + diffPreview
16487
- + '\n\nA .bak will be written. The daemon auto-reloads; the next run will be fix-verified.';
16498
+ + '\\n\\nA .bak will be written. The daemon auto-reloads; the next run will be fix-verified.';
16488
16499
  if (!confirm(msg)) return;
16489
16500
 
16490
16501
  var res = await apiJson('POST', '/api/cron/broken-jobs/' + encodeURIComponent(jobName) + '/apply-fix', {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.0.37",
3
+ "version": "1.0.40",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",