get-claudia 1.54.0 → 1.54.2

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/bin/index.js CHANGED
@@ -149,6 +149,7 @@ class ProgressRenderer {
149
149
  case 'error': return `${colors.red}!${colors.reset}`;
150
150
  case 'active': return `${colors.cyan}${this.spinnerChars[this.spinnerFrame]}${colors.reset}`;
151
151
  case 'skipped': return `${colors.dim}○${colors.reset}`;
152
+ case 'cascade': return `${colors.dim}·${colors.reset}`;
152
153
  default: return `${colors.dim}░${colors.reset}`;
153
154
  }
154
155
  }
@@ -156,7 +157,7 @@ class ProgressRenderer {
156
157
  getCompletedCount() {
157
158
  return STEPS.filter(s => {
158
159
  const st = this.states[s.id].state;
159
- return st === 'done' || st === 'warn' || st === 'skipped';
160
+ return st === 'done' || st === 'warn' || st === 'skipped' || st === 'cascade';
160
161
  }).length;
161
162
  }
162
163
 
@@ -175,7 +176,7 @@ class ProgressRenderer {
175
176
  for (const step of STEPS) {
176
177
  const { state, detail } = this.states[step.id];
177
178
  const icon = this.getIcon(state);
178
- const label = state === 'skipped'
179
+ const label = (state === 'skipped' || state === 'cascade')
179
180
  ? `${colors.dim}${step.label}${colors.reset}`
180
181
  : step.label;
181
182
  const detailStr = detail
@@ -183,7 +184,7 @@ class ProgressRenderer {
183
184
  : '';
184
185
  // Pad label to 20 chars for alignment
185
186
  const paddedLabel = step.label.padEnd(20);
186
- lines.push(` ${icon} ${state === 'skipped' ? colors.dim + paddedLabel + colors.reset : paddedLabel}${detailStr}`);
187
+ lines.push(` ${icon} ${(state === 'skipped' || state === 'cascade') ? colors.dim + paddedLabel + colors.reset : paddedLabel}${detailStr}`);
187
188
  }
188
189
 
189
190
  lines.push('');
@@ -209,10 +210,11 @@ class ProgressRenderer {
209
210
  if (supportsInPlace) return; // handled by render()
210
211
  const step = STEPS.find(s => s.id === stepId);
211
212
  if (!step) return;
212
- if (state === 'done' || state === 'warn' || state === 'error' || state === 'skipped') {
213
+ if (state === 'done' || state === 'warn' || state === 'error' || state === 'skipped' || state === 'cascade') {
213
214
  const icon = state === 'done' ? '✓' :
214
215
  state === 'warn' ? '○' :
215
- state === 'error' ? '!' : '-';
216
+ state === 'error' ? '!' :
217
+ state === 'cascade' ? '·' : '-';
216
218
  console.log(` ${icon} ${step.label}${detail ? ' ' + detail : ''}`);
217
219
  }
218
220
  }
@@ -277,6 +279,77 @@ async function installOllama() {
277
279
  });
278
280
  }
279
281
 
282
+ // ─── Python helpers ─────────────────────────────────────────────────────
283
+
284
+ /** Check if Python 3.10+ is available. Returns the command name or null. */
285
+ async function isPythonInstalled() {
286
+ for (const cmd of ['python3', 'python']) {
287
+ const ver = await new Promise((resolve) => {
288
+ const proc = spawn(cmd, ['--version'], { stdio: 'pipe', timeout: 5000 });
289
+ let stdout = '';
290
+ proc.stdout.on('data', (d) => { stdout += d.toString(); });
291
+ proc.on('close', () => resolve(stdout.trim()));
292
+ proc.on('error', () => resolve(''));
293
+ });
294
+ const match = ver.match(/Python (\d+)\.(\d+)/);
295
+ if (match && (parseInt(match[1]) > 3 || (parseInt(match[1]) === 3 && parseInt(match[2]) >= 10))) {
296
+ return cmd;
297
+ }
298
+ }
299
+ return null;
300
+ }
301
+
302
+ /**
303
+ * Install Python automatically.
304
+ * macOS: uses brew if available
305
+ * Linux: tries apt, dnf, pacman
306
+ * Windows: skip (requires manual install from python.org)
307
+ */
308
+ async function installPython() {
309
+ if (isWindows) return false;
310
+
311
+ if (process.platform === 'darwin') {
312
+ const hasBrew = await new Promise((resolve) => {
313
+ const proc = spawn('which', ['brew'], { stdio: 'pipe', timeout: 5000 });
314
+ proc.on('close', (code) => resolve(code === 0));
315
+ proc.on('error', () => resolve(false));
316
+ });
317
+ if (hasBrew) {
318
+ return new Promise((resolve) => {
319
+ const proc = spawn('brew', ['install', 'python@3.12'], {
320
+ stdio: 'pipe', timeout: 300000
321
+ });
322
+ proc.on('close', (code) => resolve(code === 0));
323
+ proc.on('error', () => resolve(false));
324
+ });
325
+ }
326
+ return false;
327
+ }
328
+
329
+ // Linux: try apt, dnf, pacman
330
+ for (const [pm, args] of [
331
+ ['apt-get', ['install', '-y', 'python3', 'python3-venv']],
332
+ ['dnf', ['install', '-y', 'python3']],
333
+ ['pacman', ['-S', '--noconfirm', 'python']],
334
+ ]) {
335
+ const hasPm = await new Promise((resolve) => {
336
+ const proc = spawn('which', [pm], { stdio: 'pipe', timeout: 5000 });
337
+ proc.on('close', (code) => resolve(code === 0));
338
+ proc.on('error', () => resolve(false));
339
+ });
340
+ if (hasPm) {
341
+ return new Promise((resolve) => {
342
+ const proc = spawn('sudo', [pm, ...args], {
343
+ stdio: 'pipe', timeout: 300000
344
+ });
345
+ proc.on('close', (code) => resolve(code === 0));
346
+ proc.on('error', () => resolve(false));
347
+ });
348
+ }
349
+ }
350
+ return false;
351
+ }
352
+
280
353
  /**
281
354
  * Start the Ollama service and wait for it to respond.
282
355
  * On macOS: open the Ollama app or run `ollama serve` in background.
@@ -548,6 +621,7 @@ async function main() {
548
621
 
549
622
  // Run CLI-based setup (no Python daemon needed)
550
623
  let memoryOk = false;
624
+ let rootCause = null;
551
625
 
552
626
  try {
553
627
  // Step 1: Environment -- check Node.js version, detect/install/start Ollama
@@ -689,30 +763,19 @@ async function main() {
689
763
  ? join(daemonVenvDir, 'Scripts', 'pip')
690
764
  : join(daemonVenvDir, 'bin', 'pip');
691
765
 
692
- // Phase 1: Find Python 3.10+
693
- let pythonCmd = null;
694
- for (const cmd of ['python3', 'python']) {
695
- const ver = await new Promise((resolve) => {
696
- const proc = spawn(cmd, ['--version'], { stdio: 'pipe' });
697
- let stdout = '';
698
- proc.stdout.on('data', (d) => { stdout += d.toString(); });
699
- proc.on('close', () => resolve(stdout.trim()));
700
- proc.on('error', () => resolve(''));
701
- });
702
- const match = ver.match(/Python (\d+)\.(\d+)/);
703
- if (match) {
704
- const major = parseInt(match[1], 10);
705
- const minor = parseInt(match[2], 10);
706
- if (major > 3 || (major === 3 && minor >= 10)) {
707
- pythonCmd = cmd;
708
- break;
709
- }
710
- }
766
+ // Phase 1: Find Python 3.10+ (auto-install if missing)
767
+ let pythonCmd = await isPythonInstalled();
768
+
769
+ if (!pythonCmd) {
770
+ renderer.update('daemon', 'active', 'installing Python...');
771
+ const installed = await installPython();
772
+ if (installed) pythonCmd = await isPythonInstalled();
711
773
  }
712
774
 
713
775
  if (!pythonCmd) {
714
776
  renderer.update('daemon', 'warn', 'Python 3.10+ not found');
715
777
  if (!supportsInPlace) renderer.appendLine('daemon', 'warn', 'Python 3.10+ not found');
778
+ rootCause = { step: 'daemon', issue: 'python' };
716
779
  } else {
717
780
  // Phase 2: Create venv if it doesn't exist
718
781
  if (!existsSync(venvPython)) {
@@ -726,6 +789,7 @@ async function main() {
726
789
  if (!venvCreated) {
727
790
  renderer.update('daemon', 'warn', 'venv creation failed');
728
791
  if (!supportsInPlace) renderer.appendLine('daemon', 'warn', 'venv creation failed');
792
+ rootCause = rootCause || { step: 'daemon', issue: 'venv' };
729
793
  }
730
794
  }
731
795
 
@@ -746,6 +810,7 @@ async function main() {
746
810
  } else {
747
811
  renderer.update('daemon', 'warn', 'pip install failed');
748
812
  if (!supportsInPlace) renderer.appendLine('daemon', 'warn', 'pip install failed');
813
+ rootCause = rootCause || { step: 'daemon', issue: 'pip' };
749
814
  }
750
815
  }
751
816
 
@@ -763,6 +828,7 @@ async function main() {
763
828
  daemonOk = false;
764
829
  renderer.update('daemon', 'warn', 'daemon import failed');
765
830
  if (!supportsInPlace) renderer.appendLine('daemon', 'warn', 'import failed');
831
+ rootCause = rootCause || { step: 'daemon', issue: 'import' };
766
832
  }
767
833
  }
768
834
 
@@ -820,59 +886,71 @@ async function main() {
820
886
  }
821
887
 
822
888
  // MCP Config step: verify .mcp.json is correct and check stdio server count
823
- renderer.update('mcp', 'active', 'checking .mcp.json...');
824
- const mcpCheckResult = checkMcpConfig(targetPath);
825
- if (mcpCheckResult.hasDaemon && mcpCheckResult.stdioCount <= 1) {
826
- renderer.update('mcp', 'done', `claudia-memory configured${mcpCheckResult.stdioCount === 1 ? '' : ' (no stdio servers?)'}`);
827
- if (!supportsInPlace) renderer.appendLine('mcp', 'done', 'claudia-memory configured');
828
- } else if (mcpCheckResult.hasDaemon && mcpCheckResult.stdioCount > 1) {
829
- renderer.update('mcp', 'warn', `${mcpCheckResult.stdioCount} stdio servers (only 1 reliable)`);
830
- if (!supportsInPlace) renderer.appendLine('mcp', 'warn', `${mcpCheckResult.stdioCount} stdio servers`);
889
+ if (rootCause?.step === 'daemon') {
890
+ const cascadeMsg = rootCause.issue === 'python' ? 'needs Python first' : 'needs daemon first';
891
+ renderer.update('mcp', 'cascade', cascadeMsg);
892
+ if (!supportsInPlace) renderer.appendLine('mcp', 'cascade', cascadeMsg);
831
893
  } else {
832
- renderer.update('mcp', 'warn', 'claudia-memory not in .mcp.json');
833
- if (!supportsInPlace) renderer.appendLine('mcp', 'warn', 'daemon not configured');
894
+ renderer.update('mcp', 'active', 'checking .mcp.json...');
895
+ const mcpCheckResult = checkMcpConfig(targetPath);
896
+ if (mcpCheckResult.hasDaemon && mcpCheckResult.stdioCount <= 1) {
897
+ renderer.update('mcp', 'done', `claudia-memory configured${mcpCheckResult.stdioCount === 1 ? '' : ' (no stdio servers?)'}`);
898
+ if (!supportsInPlace) renderer.appendLine('mcp', 'done', 'claudia-memory configured');
899
+ } else if (mcpCheckResult.hasDaemon && mcpCheckResult.stdioCount > 1) {
900
+ renderer.update('mcp', 'warn', `${mcpCheckResult.stdioCount} stdio servers (only 1 reliable)`);
901
+ if (!supportsInPlace) renderer.appendLine('mcp', 'warn', `${mcpCheckResult.stdioCount} stdio servers`);
902
+ } else {
903
+ renderer.update('mcp', 'warn', 'claudia-memory not in .mcp.json');
904
+ if (!supportsInPlace) renderer.appendLine('mcp', 'warn', 'daemon not configured');
905
+ }
834
906
  }
835
907
 
836
908
  // Vault step: handled below
837
909
 
838
910
  // Health Check: check daemon health endpoint or verify daemon can import
839
- renderer.update('health', 'active', 'verifying...');
840
- let healthOk = false;
911
+ if (rootCause?.step === 'daemon') {
912
+ const cascadeMsg = rootCause.issue === 'python' ? 'needs Python first' : 'needs daemon first';
913
+ renderer.update('health', 'cascade', cascadeMsg);
914
+ if (!supportsInPlace) renderer.appendLine('health', 'cascade', cascadeMsg);
915
+ } else {
916
+ renderer.update('health', 'active', 'verifying...');
917
+ let healthOk = false;
841
918
 
842
- // Try the standalone daemon's health endpoint first (port 3848)
843
- try {
844
- const healthResp = await fetch('http://localhost:3848/status', {
845
- signal: AbortSignal.timeout(3000),
846
- });
847
- if (healthResp.ok) {
848
- const healthData = await healthResp.json();
849
- healthOk = healthData.status === 'healthy' || healthData.status === 'degraded';
919
+ // Try the standalone daemon's health endpoint first (port 3848)
920
+ try {
921
+ const healthResp = await fetch('http://localhost:3848/status', {
922
+ signal: AbortSignal.timeout(3000),
923
+ });
924
+ if (healthResp.ok) {
925
+ const healthData = await healthResp.json();
926
+ healthOk = healthData.status === 'healthy' || healthData.status === 'degraded';
927
+ }
928
+ } catch {
929
+ // Standalone daemon not running -- that's OK, check daemon importability instead
850
930
  }
851
- } catch {
852
- // Standalone daemon not running -- that's OK, check daemon importability instead
853
- }
854
931
 
855
- // Fallback: verify the daemon can at least be imported
856
- if (!healthOk && daemonOk && existsSync(venvPython)) {
857
- healthOk = await new Promise((resolve) => {
858
- const proc = spawn(venvPython, ['-c', 'from claudia_memory.database import Database; print("ok")'], {
859
- stdio: 'pipe',
860
- timeout: 10000
932
+ // Fallback: verify the daemon can at least be imported
933
+ if (!healthOk && daemonOk && existsSync(venvPython)) {
934
+ healthOk = await new Promise((resolve) => {
935
+ const proc = spawn(venvPython, ['-c', 'from claudia_memory.database import Database; print("ok")'], {
936
+ stdio: 'pipe',
937
+ timeout: 10000
938
+ });
939
+ proc.on('close', (code) => resolve(code === 0));
940
+ proc.on('error', () => resolve(false));
861
941
  });
862
- proc.on('close', (code) => resolve(code === 0));
863
- proc.on('error', () => resolve(false));
864
- });
865
- }
942
+ }
866
943
 
867
- if (healthOk) {
868
- renderer.update('health', 'done', 'system healthy');
869
- if (!supportsInPlace) renderer.appendLine('health', 'done', 'system healthy');
870
- } else if (daemonOk) {
871
- renderer.update('health', 'warn', 'daemon installed, standalone not running');
872
- if (!supportsInPlace) renderer.appendLine('health', 'warn', 'standalone not running');
873
- } else {
874
- renderer.update('health', 'warn', 'check CLAUDE.md for troubleshooting');
875
- if (!supportsInPlace) renderer.appendLine('health', 'warn', 'check manually');
944
+ if (healthOk) {
945
+ renderer.update('health', 'done', 'system healthy');
946
+ if (!supportsInPlace) renderer.appendLine('health', 'done', 'system healthy');
947
+ } else if (daemonOk) {
948
+ renderer.update('health', 'warn', 'daemon installed, standalone not running');
949
+ if (!supportsInPlace) renderer.appendLine('health', 'warn', 'standalone not running');
950
+ } else {
951
+ renderer.update('health', 'warn', 'check CLAUDE.md for troubleshooting');
952
+ if (!supportsInPlace) renderer.appendLine('health', 'warn', 'check manually');
953
+ }
876
954
  }
877
955
 
878
956
  memoryOk = daemonOk || hasExistingDb;
@@ -891,7 +969,7 @@ async function main() {
891
969
  // Vault step, then completion
892
970
  runVaultStep(renderer, () => {
893
971
  renderer.render();
894
- showCompletion(targetDir, isCurrentDir, memoryOk);
972
+ showCompletion(targetDir, isCurrentDir, memoryOk, rootCause);
895
973
  });
896
974
 
897
975
  // ── Vault step ──
@@ -951,8 +1029,8 @@ async function main() {
951
1029
  renderer.update('vault', 'done', 'configured');
952
1030
  if (!supportsInPlace) renderer.appendLine('vault', 'done', 'configured');
953
1031
  } else {
954
- renderer.update('vault', 'warn', 'Obsidian not found (optional)');
955
- if (!supportsInPlace) renderer.appendLine('vault', 'warn', 'Obsidian not found (optional)');
1032
+ renderer.update('vault', 'skipped', 'Obsidian not installed (optional)');
1033
+ if (!supportsInPlace) renderer.appendLine('vault', 'skipped', 'Obsidian not installed (optional)');
956
1034
  }
957
1035
 
958
1036
  callback(obsidianDetected);
@@ -960,42 +1038,67 @@ async function main() {
960
1038
 
961
1039
  // ── Completion block ──
962
1040
 
963
- function showCompletion(targetDir, isCurrentDir, memoryInstalled) {
964
- const cdCmd = isCurrentDir ? '' : `cd ${targetDir} && `;
1041
+ function showCompletion(targetDir, isCurrentDir, memoryInstalled, failureCause) {
1042
+ const rerunCmd = isCurrentDir ? 'npx get-claudia .' : `cd ${targetDir} && npx get-claudia .`;
1043
+ const launchCmd = isCurrentDir ? 'claude' : `cd ${targetDir} && claude`;
965
1044
 
966
1045
  console.log('');
967
1046
  console.log(`${colors.dim}${'━'.repeat(46)}${colors.reset}`);
968
1047
 
969
- // Post-install component summary
970
- const mcpCheck = checkMcpConfig(targetPath);
971
- const components = [
972
- { name: 'Personality & Skills', ok: true },
973
- { name: 'Memory Daemon', ok: memoryInstalled },
974
- { name: 'MCP Config', ok: mcpCheck.hasDaemon },
975
- ];
976
-
977
- console.log(` ${colors.bold}Components:${colors.reset}`);
978
- for (const c of components) {
979
- if (c.ok && !c.warn) {
980
- console.log(` ${colors.green}✓${colors.reset} ${c.name}`);
981
- } else if (c.warn) {
982
- console.log(` ${colors.yellow}⚠${colors.reset} ${c.name} ${colors.dim}${c.warn}${colors.reset}`);
983
- } else {
984
- console.log(` ${colors.yellow}○${colors.reset} ${c.name} ${colors.dim}(not ready)${colors.reset}`);
985
- }
1048
+ if (memoryInstalled && !failureCause) {
1049
+ // Everything worked
1050
+ console.log('');
1051
+ console.log(` ${colors.green}Ready to go!${colors.reset}`);
1052
+ console.log('');
1053
+ console.log(` ${colors.bold}Next:${colors.reset} Open Claude Code:`);
1054
+ console.log(` ${colors.cyan}${launchCmd}${colors.reset}`);
1055
+ console.log('');
1056
+ return;
986
1057
  }
987
1058
 
1059
+ // Something needs fixing
1060
+ console.log('');
1061
+ console.log(` ${colors.boldYellow}Almost there!${colors.reset} One thing to fix:`);
988
1062
  console.log('');
989
- console.log(` ${colors.bold}Next:${colors.reset} Open Claude Code:`);
990
- console.log(` ${colors.cyan}${cdCmd}claude${colors.reset}`);
991
1063
 
992
- if (!memoryInstalled) {
993
- console.log('');
994
- console.log(` ${colors.dim}Memory requires Python 3.10+, the claudia-memory daemon, and Ollama.${colors.reset}`);
995
- console.log(` ${colors.dim}Re-run setup after installing Python and Ollama.${colors.reset}`);
1064
+ if (failureCause?.issue === 'python') {
1065
+ console.log(` ${colors.bold}→ Install Python 3.10+:${colors.reset}`);
1066
+ if (process.platform === 'darwin') {
1067
+ const hasBrew = existsSync('/opt/homebrew/bin/brew') || existsSync('/usr/local/bin/brew');
1068
+ if (hasBrew) {
1069
+ console.log(` ${colors.cyan}brew install python@3.12${colors.reset}`);
1070
+ } else {
1071
+ console.log(` ${colors.dim}Install Homebrew first:${colors.reset}`);
1072
+ console.log(` ${colors.cyan}/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"${colors.reset}`);
1073
+ console.log('');
1074
+ console.log(` ${colors.dim}Then:${colors.reset}`);
1075
+ console.log(` ${colors.cyan}brew install python@3.12${colors.reset}`);
1076
+ }
1077
+ } else if (isWindows) {
1078
+ console.log(` ${colors.cyan}https://www.python.org/downloads/${colors.reset}`);
1079
+ } else {
1080
+ console.log(` ${colors.cyan}sudo apt install python3 python3-venv${colors.reset} ${colors.dim}(Debian/Ubuntu)${colors.reset}`);
1081
+ console.log(` ${colors.cyan}sudo dnf install python3${colors.reset} ${colors.dim}(Fedora/RHEL)${colors.reset}`);
1082
+ }
1083
+ } else if (failureCause?.issue === 'venv') {
1084
+ console.log(` ${colors.bold}→ Python venv creation failed.${colors.reset}`);
1085
+ console.log(` ${colors.dim}Try: python3 -m ensurepip && python3 -m venv ~/.claudia/daemon/venv${colors.reset}`);
1086
+ } else if (failureCause?.issue === 'pip') {
1087
+ console.log(` ${colors.bold}→ Daemon package install failed.${colors.reset}`);
1088
+ console.log(` ${colors.dim}Check your internet connection and try again.${colors.reset}`);
1089
+ } else if (failureCause?.issue === 'import') {
1090
+ console.log(` ${colors.bold}→ Daemon installed but won't load.${colors.reset}`);
1091
+ console.log(` ${colors.dim}Try: rm -rf ~/.claudia/daemon/venv && re-run setup.${colors.reset}`);
1092
+ } else {
1093
+ console.log(` ${colors.bold}→ Memory daemon not ready.${colors.reset}`);
996
1094
  }
997
1095
 
998
1096
  console.log('');
1097
+ console.log(` ${colors.bold}Then finish setup:${colors.reset}`);
1098
+ console.log(` ${colors.cyan}${rerunCmd}${colors.reset}`);
1099
+ console.log('');
1100
+ console.log(` ${colors.dim}Stuck? Copy this message into any AI chat and ask for help.${colors.reset}`);
1101
+ console.log('');
999
1102
  }
1000
1103
  }
1001
1104
 
@@ -1021,7 +1124,7 @@ function restoreMcpServers(targetPath) {
1021
1124
 
1022
1125
  // Path 1: Restore from _disabled_mcpServers stash (older migration format)
1023
1126
  if (config._disabled_mcpServers) {
1024
- const toRestore = ['claudia-memory', 'claudia_memory', 'gmail', 'google-calendar'];
1127
+ const toRestore = ['claudia-memory', 'claudia_memory'];
1025
1128
  for (const key of toRestore) {
1026
1129
  if (config._disabled_mcpServers[key] && !config.mcpServers[key]) {
1027
1130
  const serverConfig = { ...config._disabled_mcpServers[key] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-claudia",
3
- "version": "1.54.0",
3
+ "version": "1.54.2",
4
4
  "description": "An AI assistant who learns how you work.",
5
5
  "keywords": [
6
6
  "claudia",
@@ -6,15 +6,19 @@
6
6
  "_description": "Claudia memory system with vector search",
7
7
  "_setup": "Auto-configured by the installer (npx get-claudia). The installer creates a Python venv at ~/.claudia/daemon/venv/ and sets the correct command path in .mcp.json automatically."
8
8
  },
9
- "gmail": {
10
- "command": "npx",
11
- "args": ["-y", "@gongrzhe/server-gmail-autoauth-mcp"],
12
- "_setup": "Requires your own Google Cloud credentials. See Google Integration Setup in CLAUDE.md, then run: npx @gongrzhe/server-gmail-autoauth-mcp auth"
13
- },
14
- "google-calendar": {
15
- "command": "npx",
16
- "args": ["-y", "@gongrzhe/server-calendar-autoauth-mcp"],
17
- "_setup": "Requires your own Google Cloud credentials. See Google Integration Setup in CLAUDE.md, then run: npx @gongrzhe/server-calendar-autoauth-mcp auth"
9
+ "google_workspace": {
10
+ "command": "uvx",
11
+ "args": ["workspace-mcp", "--tool-tier", "core"],
12
+ "env": {
13
+ "GOOGLE_OAUTH_CLIENT_ID": "",
14
+ "GOOGLE_OAUTH_CLIENT_SECRET": ""
15
+ },
16
+ "_setup": "Google Workspace MCP: Gmail, Calendar, Drive, Docs, Sheets, Tasks, and more in one server. Requires Google Cloud OAuth credentials. See Google Integration Setup in CLAUDE.md. Tool tiers: core (43 tools), extended (83), complete (111). Start with core.",
17
+ "_tiers": {
18
+ "core": "Gmail, Calendar, Drive, Contacts (43 tools, recommended default)",
19
+ "extended": "Adds Docs, Sheets, Tasks, Chat (83 tools)",
20
+ "complete": "All services including Slides, Forms, Apps Script (111 tools)"
21
+ }
18
22
  },
19
23
  "rube": {
20
24
  "type": "http",
@@ -29,7 +33,7 @@
29
33
 
30
34
  "_notes": {
31
35
  "memory": "Claudia's memory is powered by the claudia-memory daemon (Python MCP server). It provides ~33 tools for semantic search, pattern detection, and relationship tracking. The installer (npx get-claudia) automatically sets up the daemon in a Python venv at ~/.claudia/daemon/venv/ and configures .mcp.json.",
32
- "gmail_and_calendar": "Gmail and Calendar are enabled by default as stdio MCP servers. Each requires Google Cloud credentials (see Google Integration Setup in CLAUDE.md). Alternatively, connect them through Rube (HTTP) for a simpler setup.",
36
+ "google_workspace": "Google Workspace uses the workspace-mcp server (taylorwilsdon/google_workspace_mcp). One server covers Gmail, Calendar, Drive, Docs, Sheets, Tasks, and more. Tool tiers control context usage. See Google Integration Setup in CLAUDE.md.",
33
37
  "rube": "Rube (by Composio) connects 500+ apps through one HTTP MCP connection. Each user creates their own free Rube account at rube.app. See the Rube section in CLAUDE.md for setup and troubleshooting.",
34
38
  "security": "Each user authenticates with their own accounts and credentials. OAuth tokens are stored locally on your machine, never shared.",
35
39
  "not_included": {
@@ -319,12 +319,10 @@ I adapt to whatever tools are available. When you ask me to do something that ne
319
319
 
320
320
  **Obsidian vault:** My memory syncs to an Obsidian vault at `~/.claudia/vault/` using a PARA-inspired structure: `Active/` for projects, `Relationships/` for people and organizations, `Reference/` for concepts and locations, `Archive/` for dormant entities. Every entity becomes a markdown note with `[[wikilinks]]`, so Obsidian's graph view acts as a relationship visualizer. My own lookup files (MOC tables, patterns, reflections, sessions) live in `Claudia's Desk/`, keeping the human-facing folders clean. The vault syncs on-demand via `claudia vault sync`. SQLite remains the source of truth; the vault is a read projection.
321
321
 
322
- **Gmail and Calendar (MCP):** Gmail and Calendar are provided by third-party MCP servers that each user authenticates with their own Google Cloud credentials. When these MCPs are connected, I have access to tools like `search_emails`, `read_email`, `send_email`, `list_events`, `search_events`, etc. I use these tools naturally when you ask me to check email, send messages, or look at your calendar.
322
+ **Google Workspace (MCP):** Google Workspace is provided by the workspace-mcp server ([taylorwilsdon/google_workspace_mcp](https://github.com/taylorwilsdon/google_workspace_mcp)). One server covers Gmail, Calendar, Drive, Docs, Sheets, Tasks, Contacts, and more. Tool tiers control how many tools are exposed: `--tool-tier core` (43 tools, default), `--tool-tier extended` (83 tools), or `--tool-tier complete` (111 tools). When connected, I have access to Gmail, Calendar, Drive, Docs, Sheets, Tasks, and more. I use these tools naturally when you ask me to check email, send messages, look at your calendar, search Drive, or work with documents.
323
323
 
324
324
  If the MCP tools aren't responding or you see authentication errors, the user needs to set up their Google Cloud credentials. See the **Google Integration Setup** section below.
325
325
 
326
- **Alternative:** CLI commands (`claudia gmail search`, `claudia calendar list`, etc.) are also available as a fallback. These require separate authentication via `claudia google login`.
327
-
328
326
  **Rube (500+ Apps):** Rube (by Composio) is an optional MCP aggregator that connects Claudia to hundreds of apps through a single server. Each user creates their own free Rube account, connects the apps they want via one-click OAuth, and Claudia gets access to all of them through one MCP connection.
329
327
 
330
328
  If a user asks about connecting apps, integrations, or any of the services listed below, proactively suggest Rube as the easiest path. Walk them through setup step by step.
@@ -348,7 +346,7 @@ If a user asks about connecting apps, integrations, or any of the services liste
348
346
  | **Calendar** | Google Calendar, Outlook Calendar, Calendly |
349
347
  | **And 500+ more** | Browse the full list at [rube.app](https://rube.app) |
350
348
 
351
- **External integrations** (Gmail, Google Calendar, Rube, Brave Search) are optional add-ons that extend what I can see and do. I work fully without them. The core value is relationships and context.
349
+ **External integrations** (Google Workspace, Rube, Brave Search) are optional add-ons that extend what I can see and do. I work fully without them. The core value is relationships and context.
352
350
 
353
351
  ### Rube Setup (Guide Users Through This)
354
352
 
@@ -403,33 +401,41 @@ The MCP tools from Rube will have names like `SLACK_SEND_MESSAGE`, `NOTION_CREAT
403
401
  | Rate limited | Rube has usage limits on the free tier. The user may need to upgrade at rube.app/pricing. |
404
402
  | Want to disconnect an app | Go to Rube dashboard and disconnect the app there. No Claudia config changes needed. |
405
403
 
406
- **Rube vs. Individual MCPs:** Rube works alongside (not instead of) Gmail and Calendar MCPs. Individual MCPs give a direct connection with no intermediary but require per-service Google Cloud setup. Rube gives one setup for everything but routes data through Composio servers. Both can coexist. If a user has both Gmail MCP and Rube's Gmail connected, prefer the direct MCP tools.
404
+ **Rube vs. workspace-mcp:** Rube works alongside (not instead of) the workspace-mcp server. Workspace-mcp gives a direct connection with no intermediary but requires Google Cloud setup. Rube gives one setup for everything but routes data through Composio servers. Both can coexist. If a user has both workspace-mcp and Rube's Google apps connected, prefer the direct workspace-mcp tools.
407
405
 
408
406
  ### Google Integration Setup
409
407
 
410
- Gmail and Calendar MCP servers require your own Google Cloud credentials. Each user sets this up once:
408
+ The workspace-mcp server requires your own Google Cloud credentials. Each user sets this up once:
411
409
 
412
410
  1. Go to [Google Cloud Console](https://console.cloud.google.com/)
413
411
  2. Create a new project (or select an existing one)
414
- 3. Enable the **Gmail API** and/or **Google Calendar API**:
412
+ 3. Enable the APIs you need:
415
413
  - Go to APIs & Services > Library
416
- - Search for "Gmail API", click Enable
417
- - Search for "Google Calendar API", click Enable
414
+ - Search for and enable: **Gmail API**, **Google Calendar API**, **Google Drive API**, **Google Docs API**, **Google Sheets API**, **Google Tasks API**, **People API** (Contacts)
415
+ - You can enable more later as needed
418
416
  4. Create OAuth credentials:
419
417
  - Go to APIs & Services > Credentials
420
418
  - Click "Create Credentials" > "OAuth client ID"
421
419
  - If prompted, configure the consent screen first (External, add your email as test user)
422
420
  - Application type: **Desktop app**
423
- - Click Create, then download the JSON file
424
- 5. Rename the downloaded file to `gcp-oauth.keys.json`
425
- 6. Authenticate each service:
426
- ```bash
427
- npx @gongrzhe/server-gmail-autoauth-mcp auth
428
- npx @gongrzhe/server-calendar-autoauth-mcp auth
429
- ```
430
- Each command opens your browser for Google sign-in. Tokens are stored locally at `~/.gmail-mcp/` and `~/.calendar-mcp/`.
431
-
432
- After setup, restart Claude Code and the MCP servers will connect automatically.
421
+ - Click Create
422
+ - Copy the **Client ID** and **Client Secret**
423
+ 5. Add credentials to `.mcp.json`:
424
+ - Open `.mcp.json` in the project root
425
+ - Find the `google_workspace` server entry
426
+ - Set the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` environment variables:
427
+ ```json
428
+ "env": {
429
+ "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
430
+ "GOOGLE_CLIENT_SECRET": "your-client-secret"
431
+ }
432
+ ```
433
+ 6. Choose your tool tier:
434
+ - The default is `--tool-tier core` (43 tools), which covers most needs
435
+ - For more capabilities, change to `--tool-tier extended` (83 tools) or `--tool-tier complete` (111 tools) in the server args
436
+ 7. Restart Claude Code. On first run, the server opens your browser for Google sign-in. Tokens are stored locally for future sessions.
437
+
438
+ **Migrating from the old setup:** If you previously used separate Gmail and Calendar MCP servers, the same GCP project works. Just enable any additional APIs (Drive, Docs, Sheets, Tasks, People) in your existing project, copy over the Client ID and Client Secret, and update `.mcp.json` to use the `google_workspace` server entry instead of the old individual entries.
433
439
 
434
440
  ---
435
441