get-claudia 1.51.2 → 1.51.4
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 +38 -2
- package/cli/commands/google-auth.js +134 -6
- package/cli/core/google-oauth.js +115 -93
- package/cli/index.js +45 -4
- package/package.json +1 -1
- package/template-v2/.mcp.json.example +3 -23
- package/template-v2/CLAUDE.md +7 -3
package/bin/index.js
CHANGED
|
@@ -413,6 +413,9 @@ async function main() {
|
|
|
413
413
|
console.log(` ${colors.green}✓${colors.reset} Framework updated (data preserved)`);
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
// Disable Gmail/Calendar MCP servers (Claudia has native CLI commands now)
|
|
417
|
+
disableGoogleMcpServers(targetPath);
|
|
418
|
+
|
|
416
419
|
// Write context/whats-new.md for Claudia's self-awareness (silent)
|
|
417
420
|
writeWhatsNewFile(targetPath, version);
|
|
418
421
|
|
|
@@ -811,12 +814,45 @@ async function main() {
|
|
|
811
814
|
|
|
812
815
|
console.log('');
|
|
813
816
|
console.log(` ${colors.dim}Optional: connect Google services${colors.reset}`);
|
|
814
|
-
console.log(` ${colors.cyan}claudia
|
|
815
|
-
console.log(` ${colors.cyan}claudia calendar login${colors.reset} ${colors.dim}View & create events${colors.reset}`);
|
|
817
|
+
console.log(` ${colors.cyan}claudia google login${colors.reset} ${colors.dim}Gmail + Calendar in one step${colors.reset}`);
|
|
816
818
|
console.log('');
|
|
817
819
|
}
|
|
818
820
|
}
|
|
819
821
|
|
|
822
|
+
/**
|
|
823
|
+
* Disable Gmail and Google Calendar MCP servers in .mcp.json if present.
|
|
824
|
+
* Claudia now has native CLI commands for both services, so external MCP
|
|
825
|
+
* servers are no longer needed. This runs on both fresh installs and upgrades.
|
|
826
|
+
*/
|
|
827
|
+
function disableGoogleMcpServers(targetPath) {
|
|
828
|
+
const mcpPath = join(targetPath, '.mcp.json');
|
|
829
|
+
if (!existsSync(mcpPath)) return;
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
const raw = readFileSync(mcpPath, 'utf-8');
|
|
833
|
+
const config = JSON.parse(raw);
|
|
834
|
+
if (!config.mcpServers) return;
|
|
835
|
+
|
|
836
|
+
const googleKeys = ['gmail', 'google-calendar', 'google_calendar', 'googleCalendar'];
|
|
837
|
+
let changed = false;
|
|
838
|
+
|
|
839
|
+
for (const key of googleKeys) {
|
|
840
|
+
if (config.mcpServers[key] && !config.mcpServers[key]._disabled) {
|
|
841
|
+
config.mcpServers[key]._disabled = true;
|
|
842
|
+
config.mcpServers[key]._replaced_by = 'claudia google login';
|
|
843
|
+
changed = true;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (changed) {
|
|
848
|
+
writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n');
|
|
849
|
+
console.log(` ${colors.yellow}→${colors.reset} Disabled Gmail/Calendar MCP servers (replaced by ${colors.cyan}claudia google login${colors.reset})`);
|
|
850
|
+
}
|
|
851
|
+
} catch {
|
|
852
|
+
// Not valid JSON or can't read -- skip silently
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
820
856
|
function installVisualizer() {
|
|
821
857
|
const vizSrc = join(__dirname, '..', 'visualizer');
|
|
822
858
|
if (!existsSync(vizSrc)) return;
|
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
* Google integration CLI commands.
|
|
3
3
|
*
|
|
4
4
|
* Provides:
|
|
5
|
-
* claudia
|
|
6
|
-
* claudia
|
|
5
|
+
* claudia google login - Sign in once for Gmail + Calendar
|
|
6
|
+
* claudia google status - Check connection status for all services
|
|
7
|
+
* claudia google logout - Sign out of all Google services
|
|
8
|
+
* claudia gmail login - Sign in with Google (Gmail only)
|
|
9
|
+
* claudia gmail status - Check Gmail connection status
|
|
7
10
|
* claudia gmail search - Search emails
|
|
8
11
|
* claudia gmail read - Read a specific email
|
|
9
|
-
* claudia gmail logout - Sign out
|
|
10
|
-
* claudia calendar login - Sign in with Google (Calendar)
|
|
11
|
-
* claudia calendar status - Check connection status
|
|
12
|
+
* claudia gmail logout - Sign out of Gmail
|
|
13
|
+
* claudia calendar login - Sign in with Google (Calendar only)
|
|
14
|
+
* claudia calendar status - Check Calendar connection status
|
|
12
15
|
* claudia calendar list - List upcoming events
|
|
13
|
-
* claudia calendar
|
|
16
|
+
* claudia calendar search - Search events by text
|
|
17
|
+
* claudia calendar read - Read a specific event by ID
|
|
18
|
+
* claudia calendar logout - Sign out of Calendar
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
21
|
import { authenticate, getAccessToken, isAuthenticated, revokeTokens, authStatus } from '../core/google-oauth.js';
|
|
@@ -218,6 +223,103 @@ export async function calendarListCommand(opts) {
|
|
|
218
223
|
output({ events, timeRange: { from: now.toISOString(), to: timeMax.toISOString() }, total: events.length });
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
export async function calendarSearchCommand(query, opts) {
|
|
227
|
+
const token = await getAccessToken('calendar');
|
|
228
|
+
if (!token) {
|
|
229
|
+
console.error('Not authenticated. Run: claudia calendar login');
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const now = new Date();
|
|
235
|
+
const maxDays = opts.days || 90;
|
|
236
|
+
const timeMin = opts.past
|
|
237
|
+
? new Date(now.getTime() - maxDays * 24 * 60 * 60 * 1000)
|
|
238
|
+
: now;
|
|
239
|
+
const timeMax = new Date(now.getTime() + maxDays * 24 * 60 * 60 * 1000);
|
|
240
|
+
const maxResults = opts.limit || 25;
|
|
241
|
+
|
|
242
|
+
const url = new URL('https://www.googleapis.com/calendar/v3/calendars/primary/events');
|
|
243
|
+
url.searchParams.set('q', query);
|
|
244
|
+
url.searchParams.set('timeMin', timeMin.toISOString());
|
|
245
|
+
url.searchParams.set('timeMax', timeMax.toISOString());
|
|
246
|
+
url.searchParams.set('maxResults', String(maxResults));
|
|
247
|
+
url.searchParams.set('singleEvents', 'true');
|
|
248
|
+
url.searchParams.set('orderBy', 'startTime');
|
|
249
|
+
|
|
250
|
+
const resp = await fetch(url, {
|
|
251
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (!resp.ok) {
|
|
255
|
+
const err = await resp.text();
|
|
256
|
+
console.error(`Calendar API error (${resp.status}): ${err}`);
|
|
257
|
+
process.exitCode = 1;
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const data = await resp.json();
|
|
262
|
+
const events = (data.items || []).map(e => ({
|
|
263
|
+
id: e.id,
|
|
264
|
+
summary: e.summary || '(no title)',
|
|
265
|
+
start: e.start?.dateTime || e.start?.date || '',
|
|
266
|
+
end: e.end?.dateTime || e.end?.date || '',
|
|
267
|
+
location: e.location || '',
|
|
268
|
+
attendees: (e.attendees || []).map(a => a.email),
|
|
269
|
+
status: e.status,
|
|
270
|
+
htmlLink: e.htmlLink,
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
output({ events, query, total: events.length });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function calendarReadCommand(eventId) {
|
|
277
|
+
const token = await getAccessToken('calendar');
|
|
278
|
+
if (!token) {
|
|
279
|
+
console.error('Not authenticated. Run: claudia calendar login');
|
|
280
|
+
process.exitCode = 1;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const resp = await fetch(
|
|
285
|
+
`https://www.googleapis.com/calendar/v3/calendars/primary/events/${encodeURIComponent(eventId)}`,
|
|
286
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (!resp.ok) {
|
|
290
|
+
const err = await resp.text();
|
|
291
|
+
console.error(`Calendar API error (${resp.status}): ${err}`);
|
|
292
|
+
process.exitCode = 1;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const e = await resp.json();
|
|
297
|
+
output({
|
|
298
|
+
id: e.id,
|
|
299
|
+
summary: e.summary || '(no title)',
|
|
300
|
+
description: e.description || '',
|
|
301
|
+
start: e.start?.dateTime || e.start?.date || '',
|
|
302
|
+
end: e.end?.dateTime || e.end?.date || '',
|
|
303
|
+
location: e.location || '',
|
|
304
|
+
attendees: (e.attendees || []).map(a => ({
|
|
305
|
+
email: a.email,
|
|
306
|
+
displayName: a.displayName || '',
|
|
307
|
+
responseStatus: a.responseStatus || '',
|
|
308
|
+
organizer: a.organizer || false,
|
|
309
|
+
})),
|
|
310
|
+
organizer: e.organizer?.email || '',
|
|
311
|
+
status: e.status,
|
|
312
|
+
htmlLink: e.htmlLink,
|
|
313
|
+
created: e.created,
|
|
314
|
+
updated: e.updated,
|
|
315
|
+
recurringEventId: e.recurringEventId || null,
|
|
316
|
+
conferenceData: e.conferenceData?.entryPoints?.map(ep => ({
|
|
317
|
+
type: ep.entryPointType,
|
|
318
|
+
uri: ep.uri,
|
|
319
|
+
})) || [],
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
221
323
|
export async function calendarLogoutCommand() {
|
|
222
324
|
const removed = revokeTokens('calendar');
|
|
223
325
|
if (removed) {
|
|
@@ -227,6 +329,31 @@ export async function calendarLogoutCommand() {
|
|
|
227
329
|
}
|
|
228
330
|
}
|
|
229
331
|
|
|
332
|
+
// ── Unified Google Commands ──
|
|
333
|
+
|
|
334
|
+
export async function googleLoginCommand() {
|
|
335
|
+
try {
|
|
336
|
+
await authenticate('google');
|
|
337
|
+
console.log('\n\u2713 Google connected! Claudia can now access Gmail and Calendar.');
|
|
338
|
+
console.log(' Try: claudia gmail search "is:unread"');
|
|
339
|
+
console.log(' Try: claudia calendar list');
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.error(`\n\u2717 ${err.message}`);
|
|
342
|
+
process.exitCode = 1;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function googleLogoutCommand() {
|
|
347
|
+
const gmailRemoved = revokeTokens('gmail');
|
|
348
|
+
const calendarRemoved = revokeTokens('calendar');
|
|
349
|
+
if (gmailRemoved || calendarRemoved) {
|
|
350
|
+
const services = [gmailRemoved && 'Gmail', calendarRemoved && 'Calendar'].filter(Boolean).join(' and ');
|
|
351
|
+
console.log(`\u2713 Signed out of ${services}. Run "claudia google login" to reconnect.`);
|
|
352
|
+
} else {
|
|
353
|
+
console.log('Not signed in to any Google services.');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
230
357
|
// ── Shared status command ──
|
|
231
358
|
|
|
232
359
|
export async function googleStatusCommand() {
|
|
@@ -234,6 +361,7 @@ export async function googleStatusCommand() {
|
|
|
234
361
|
output({
|
|
235
362
|
gmail: { connected: status.gmail, login_command: 'claudia gmail login' },
|
|
236
363
|
calendar: { connected: status.calendar, login_command: 'claudia calendar login' },
|
|
364
|
+
unified_login: 'claudia google login',
|
|
237
365
|
tokens_dir: '~/.claudia/tokens/',
|
|
238
366
|
});
|
|
239
367
|
}
|
package/cli/core/google-oauth.js
CHANGED
|
@@ -45,6 +45,13 @@ const SCOPES = {
|
|
|
45
45
|
'https://www.googleapis.com/auth/calendar.readonly',
|
|
46
46
|
'https://www.googleapis.com/auth/calendar.events',
|
|
47
47
|
],
|
|
48
|
+
google: [
|
|
49
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
50
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
51
|
+
'https://www.googleapis.com/auth/gmail.modify',
|
|
52
|
+
'https://www.googleapis.com/auth/calendar.readonly',
|
|
53
|
+
'https://www.googleapis.com/auth/calendar.events',
|
|
54
|
+
],
|
|
48
55
|
};
|
|
49
56
|
|
|
50
57
|
// ── Credential resolution ──
|
|
@@ -75,12 +82,12 @@ function getCredentials() {
|
|
|
75
82
|
/**
|
|
76
83
|
* Run the full OAuth browser flow for a service.
|
|
77
84
|
* Opens the user's browser, waits for consent, stores tokens locally.
|
|
78
|
-
* @param {'gmail'|'calendar'} service
|
|
85
|
+
* @param {'gmail'|'calendar'|'google'} service
|
|
79
86
|
* @returns {Promise<{access_token: string, refresh_token: string}>}
|
|
80
87
|
*/
|
|
81
88
|
export async function authenticate(service) {
|
|
82
89
|
const scopes = SCOPES[service];
|
|
83
|
-
if (!scopes) throw new Error(`Unknown service: ${service}. Use "gmail" or "
|
|
90
|
+
if (!scopes) throw new Error(`Unknown service: ${service}. Use "gmail", "calendar", or "google".`);
|
|
84
91
|
|
|
85
92
|
const { clientId, clientSecret } = getCredentials();
|
|
86
93
|
|
|
@@ -101,6 +108,7 @@ export async function authenticate(service) {
|
|
|
101
108
|
|
|
102
109
|
console.log(`\nOpening your browser to sign in with Google...`);
|
|
103
110
|
console.log(`If it doesn't open, visit:\n ${authUrl.toString()}\n`);
|
|
111
|
+
console.log(`Waiting for browser authorization...`);
|
|
104
112
|
|
|
105
113
|
// 3. Start local server, open browser, wait for callback
|
|
106
114
|
const code = await new Promise((resolve, reject) => {
|
|
@@ -134,8 +142,7 @@ export async function authenticate(service) {
|
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
137
|
-
|
|
138
|
-
res.end(htmlPage('Connected!', `Claudia now has access to your ${serviceName}. Here's what she can do:`, service));
|
|
145
|
+
res.end(htmlPage('Connected', `You're all set. Claudia is now connected.`, service));
|
|
139
146
|
server.close();
|
|
140
147
|
resolve(authCode);
|
|
141
148
|
});
|
|
@@ -154,17 +161,28 @@ export async function authenticate(service) {
|
|
|
154
161
|
});
|
|
155
162
|
|
|
156
163
|
// 4. Exchange authorization code for tokens
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
console.log(`Token received, finishing setup...`);
|
|
165
|
+
|
|
166
|
+
const controller = new AbortController();
|
|
167
|
+
const fetchTimeout = setTimeout(() => controller.abort(), 10_000);
|
|
168
|
+
|
|
169
|
+
let tokenResp;
|
|
170
|
+
try {
|
|
171
|
+
tokenResp = await fetch('https://oauth2.googleapis.com/token', {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
174
|
+
body: new URLSearchParams({
|
|
175
|
+
code,
|
|
176
|
+
client_id: clientId,
|
|
177
|
+
client_secret: clientSecret,
|
|
178
|
+
redirect_uri: redirectUri,
|
|
179
|
+
grant_type: 'authorization_code',
|
|
180
|
+
}),
|
|
181
|
+
signal: controller.signal,
|
|
182
|
+
});
|
|
183
|
+
} finally {
|
|
184
|
+
clearTimeout(fetchTimeout);
|
|
185
|
+
}
|
|
168
186
|
|
|
169
187
|
if (!tokenResp.ok) {
|
|
170
188
|
const errBody = await tokenResp.text();
|
|
@@ -182,7 +200,14 @@ export async function authenticate(service) {
|
|
|
182
200
|
scopes,
|
|
183
201
|
created: new Date().toISOString(),
|
|
184
202
|
};
|
|
185
|
-
|
|
203
|
+
|
|
204
|
+
if (service === 'google') {
|
|
205
|
+
// Unified login: save tokens for both gmail and calendar
|
|
206
|
+
writeFileSync(join(TOKENS_DIR, 'gmail.json'), JSON.stringify(tokenData, null, 2));
|
|
207
|
+
writeFileSync(join(TOKENS_DIR, 'calendar.json'), JSON.stringify(tokenData, null, 2));
|
|
208
|
+
} else {
|
|
209
|
+
writeFileSync(join(TOKENS_DIR, `${service}.json`), JSON.stringify(tokenData, null, 2));
|
|
210
|
+
}
|
|
186
211
|
|
|
187
212
|
return tokens;
|
|
188
213
|
}
|
|
@@ -291,23 +316,45 @@ function openBrowser(url) {
|
|
|
291
316
|
}
|
|
292
317
|
|
|
293
318
|
function htmlPage(title, message, service) {
|
|
294
|
-
const isSuccess = title === 'Connected
|
|
295
|
-
const LOGO_BASE64 = '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';
|
|
319
|
+
const isSuccess = title === 'Connected';
|
|
296
320
|
|
|
297
321
|
const features = {
|
|
298
322
|
gmail: [
|
|
299
|
-
['Search & read emails', '
|
|
300
|
-
['Draft & send replies', '
|
|
301
|
-
['Inbox triage', '
|
|
323
|
+
['Search & read emails', 'Find emails by sender, subject, or content'],
|
|
324
|
+
['Draft & send replies', 'Compose emails with your tone and context'],
|
|
325
|
+
['Inbox triage', 'Morning brief of what needs attention'],
|
|
302
326
|
],
|
|
303
327
|
calendar: [
|
|
304
|
-
['View your schedule', '
|
|
305
|
-
['Create events', 'Schedule meetings through
|
|
306
|
-
['Time awareness', '
|
|
328
|
+
['View your schedule', 'See upcoming events for meeting prep'],
|
|
329
|
+
['Create events', 'Schedule meetings through conversation'],
|
|
330
|
+
['Time awareness', 'Know when you\'re busy or free'],
|
|
307
331
|
],
|
|
308
332
|
};
|
|
309
333
|
|
|
310
|
-
|
|
334
|
+
// For unified 'google' login, show both sets of features
|
|
335
|
+
const showGmail = service === 'gmail' || service === 'google';
|
|
336
|
+
const showCalendar = service === 'calendar' || service === 'google';
|
|
337
|
+
|
|
338
|
+
const featureCards = [];
|
|
339
|
+
if (showGmail) {
|
|
340
|
+
for (const [name, desc] of features.gmail) {
|
|
341
|
+
featureCards.push({ icon: '📩', name, desc });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (showCalendar) {
|
|
345
|
+
for (const [name, desc] of features.calendar) {
|
|
346
|
+
featureCards.push({ icon: '📅', name, desc });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const serviceName = service === 'google' ? 'Gmail & Google Calendar'
|
|
351
|
+
: service === 'calendar' ? 'Google Calendar' : 'Gmail';
|
|
352
|
+
|
|
353
|
+
const featuresHtml = featureCards.map((f, i) => `
|
|
354
|
+
<div class="feature" style="animation-delay:${0.1 + i * 0.08}s">
|
|
355
|
+
<div class="feature-icon">${f.icon}</div>
|
|
356
|
+
<div class="feature-text"><h3>${f.name}</h3><p>${f.desc}</p></div>
|
|
357
|
+
</div>`).join('');
|
|
311
358
|
|
|
312
359
|
return `<!DOCTYPE html>
|
|
313
360
|
<html>
|
|
@@ -317,95 +364,70 @@ function htmlPage(title, message, service) {
|
|
|
317
364
|
body {
|
|
318
365
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
319
366
|
display: flex; justify-content: center; align-items: center; min-height: 100vh;
|
|
320
|
-
background: #
|
|
321
|
-
background-image: radial-gradient(ellipse at 50% 0%, rgba(124,111,239,0.12) 0%, transparent 50%);
|
|
367
|
+
background: #F0F0F0; color: #1d1d1f;
|
|
322
368
|
}
|
|
323
369
|
.card {
|
|
324
|
-
text-align: center; padding: 2.5rem
|
|
325
|
-
background: #
|
|
326
|
-
box-shadow: 0
|
|
327
|
-
}
|
|
328
|
-
.logo { margin-bottom: 1.25rem; }
|
|
329
|
-
.logo img {
|
|
330
|
-
width: 64px; height: auto; image-rendering: pixelated;
|
|
331
|
-
${isSuccess ? 'animation: float 3s ease-in-out infinite;' : ''}
|
|
370
|
+
text-align: center; padding: 2.5rem 2.5rem 2rem; border-radius: 16px;
|
|
371
|
+
background: #fff; max-width: 440px; width: 100%;
|
|
372
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04);
|
|
332
373
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
.badge {
|
|
338
|
-
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
339
|
-
padding: 0.35rem 0.9rem; border-radius: 99px; font-size: 0.78rem; font-weight: 500;
|
|
340
|
-
margin-bottom: 1.25rem;
|
|
374
|
+
.checkmark {
|
|
375
|
+
width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 1.25rem;
|
|
376
|
+
display: flex; align-items: center; justify-content: center;
|
|
341
377
|
${isSuccess
|
|
342
|
-
? 'background:
|
|
343
|
-
: 'background:
|
|
344
|
-
|
|
378
|
+
? 'background: #00CED1; color: #fff;'
|
|
379
|
+
: 'background: #ef4444; color: #fff;'}
|
|
380
|
+
font-size: 1.4rem; font-weight: bold;
|
|
381
|
+
animation: popIn 0.4s cubic-bezier(0.175,0.885,0.32,1.275);
|
|
345
382
|
}
|
|
346
|
-
@keyframes
|
|
383
|
+
@keyframes popIn { 0% { transform: scale(0); } 100% { transform: scale(1); } }
|
|
347
384
|
h1 {
|
|
348
|
-
color: ${isSuccess ? '#
|
|
349
|
-
font-weight: 600; margin-bottom: 0.
|
|
385
|
+
color: ${isSuccess ? '#00CED1' : '#ef4444'}; font-size: 1.4rem;
|
|
386
|
+
font-weight: 600; margin-bottom: 0.4rem;
|
|
387
|
+
}
|
|
388
|
+
.subtitle { color: #6e6e73; line-height: 1.5; font-size: 0.9rem; margin-bottom: 1.5rem; }
|
|
389
|
+
.service-label {
|
|
390
|
+
display: inline-block; padding: 0.2rem 0.7rem; border-radius: 99px;
|
|
391
|
+
font-size: 0.75rem; font-weight: 500; margin-bottom: 1.25rem;
|
|
392
|
+
background: rgba(0,206,209,0.08); color: #00CED1;
|
|
350
393
|
}
|
|
351
|
-
.subtitle { color: #71717a; line-height: 1.6; font-size: 0.88rem; margin-bottom: 1.5rem; }
|
|
352
394
|
.features {
|
|
353
395
|
text-align: left; margin: 0 auto 1.5rem; padding: 0;
|
|
354
|
-
display: flex; flex-direction: column; gap: 0.
|
|
396
|
+
display: flex; flex-direction: column; gap: 0.5rem;
|
|
355
397
|
}
|
|
356
398
|
.feature {
|
|
357
|
-
display: flex; gap: 0.
|
|
358
|
-
padding: 0.
|
|
359
|
-
background: #
|
|
360
|
-
animation: slideIn 0.
|
|
399
|
+
display: flex; gap: 0.65rem; align-items: flex-start;
|
|
400
|
+
padding: 0.55rem 0.75rem; border-radius: 10px;
|
|
401
|
+
background: #f9f9fb; border: 1px solid #e8e8ed;
|
|
402
|
+
animation: slideIn 0.35s ease backwards;
|
|
361
403
|
}
|
|
362
|
-
|
|
363
|
-
.feature:nth-child(2) { animation-delay: 0.2s; }
|
|
364
|
-
.feature:nth-child(3) { animation-delay: 0.3s; }
|
|
365
|
-
@keyframes slideIn { 0% { opacity: 0; transform: translateX(-12px); } 100% { opacity: 1; transform: translateX(0); } }
|
|
404
|
+
@keyframes slideIn { 0% { opacity: 0; transform: translateY(8px); } 100% { opacity: 1; transform: translateY(0); } }
|
|
366
405
|
.feature-icon {
|
|
367
|
-
flex-shrink: 0; width:
|
|
368
|
-
background: rgba(
|
|
369
|
-
justify-content: center; font-size: 0.
|
|
406
|
+
flex-shrink: 0; width: 26px; height: 26px; border-radius: 6px;
|
|
407
|
+
background: rgba(0,206,209,0.08); display: flex; align-items: center;
|
|
408
|
+
justify-content: center; font-size: 0.8rem; margin-top: 1px;
|
|
370
409
|
}
|
|
371
|
-
.feature-text h3 { font-size: 0.
|
|
372
|
-
.feature-text p { font-size: 0.
|
|
373
|
-
|
|
374
|
-
.footer { color: #
|
|
375
|
-
.footer .
|
|
376
|
-
.footer .close { color: #3f3f46; }
|
|
410
|
+
.feature-text h3 { font-size: 0.8rem; font-weight: 500; color: #1d1d1f; margin-bottom: 1px; }
|
|
411
|
+
.feature-text p { font-size: 0.72rem; color: #86868b; line-height: 1.4; }
|
|
412
|
+
hr { border: none; border-top: 1px solid #e8e8ed; margin: 0 0 0.85rem; }
|
|
413
|
+
.footer { color: #86868b; font-size: 0.72rem; line-height: 1.6; }
|
|
414
|
+
.footer .close { color: #aeaeb2; margin-top: 0.25rem; }
|
|
377
415
|
</style></head>
|
|
378
416
|
<body>
|
|
379
417
|
<div class="card">
|
|
380
|
-
<div class="
|
|
381
|
-
|
|
382
|
-
</
|
|
383
|
-
<div class="badge">${isSuccess ? '✓ Connected' : '✗ Failed'}</div>
|
|
384
|
-
<h1>${title}</h1>
|
|
385
|
-
<p class="subtitle">${message}</p>
|
|
418
|
+
<div class="checkmark">${isSuccess ? '✓' : '✗'}</div>
|
|
419
|
+
<h1>${isSuccess ? serviceName + ' Connected' : title}</h1>
|
|
420
|
+
<p class="subtitle">${isSuccess ? 'You can close this tab and return to your terminal.' : message}</p>
|
|
386
421
|
${isSuccess ? `
|
|
387
|
-
<div class="
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
<div class="feature-text"><h3>${serviceFeatures[0][0]}</h3><p>${serviceFeatures[0][1]}</p></div>
|
|
391
|
-
</div>
|
|
392
|
-
<div class="feature">
|
|
393
|
-
<div class="feature-icon">${service === 'calendar' ? '⏱' : '✍'}</div>
|
|
394
|
-
<div class="feature-text"><h3>${serviceFeatures[1][0]}</h3><p>${serviceFeatures[1][1]}</p></div>
|
|
395
|
-
</div>
|
|
396
|
-
<div class="feature">
|
|
397
|
-
<div class="feature-icon">${service === 'calendar' ? '🔔' : '📋'}</div>
|
|
398
|
-
<div class="feature-text"><h3>${serviceFeatures[2][0]}</h3><p>${serviceFeatures[2][1]}</p></div>
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
<hr class="divider" />
|
|
422
|
+
<div class="service-label">What Claudia can do</div>
|
|
423
|
+
<div class="features">${featuresHtml}</div>
|
|
424
|
+
<hr />
|
|
402
425
|
<div class="footer">
|
|
403
|
-
<div
|
|
404
|
-
<div class="close">You can close this tab and return to your terminal.</div>
|
|
426
|
+
<div>🔒 Tokens stored locally. They never leave your machine.</div>
|
|
405
427
|
</div>
|
|
406
428
|
` : `
|
|
407
|
-
<div class="footer" style="margin-top:
|
|
408
|
-
<div
|
|
429
|
+
<div class="footer" style="margin-top:0.5rem;">
|
|
430
|
+
<div>Check your terminal for details, then try again.</div>
|
|
409
431
|
</div>
|
|
410
432
|
`}
|
|
411
433
|
</div>
|
package/cli/index.js
CHANGED
|
@@ -570,7 +570,7 @@ gmail
|
|
|
570
570
|
// ── Calendar subcommand group ──
|
|
571
571
|
const calendar = program
|
|
572
572
|
.command('calendar')
|
|
573
|
-
.description('Google Calendar integration (login, list
|
|
573
|
+
.description('Google Calendar integration (login, list, search, read)');
|
|
574
574
|
|
|
575
575
|
calendar
|
|
576
576
|
.command('login')
|
|
@@ -598,6 +598,27 @@ calendar
|
|
|
598
598
|
await calendarListCommand(opts);
|
|
599
599
|
});
|
|
600
600
|
|
|
601
|
+
calendar
|
|
602
|
+
.command('search')
|
|
603
|
+
.description('Search calendar events by text')
|
|
604
|
+
.argument('<query>', 'Search query, e.g. "meeting" or "lunch"')
|
|
605
|
+
.option('--days <n>', 'Days ahead to search', parseInt, 90)
|
|
606
|
+
.option('--limit <n>', 'Max results', parseInt, 25)
|
|
607
|
+
.option('--past', 'Also search past events within range')
|
|
608
|
+
.action(async (query, opts) => {
|
|
609
|
+
const { calendarSearchCommand } = await import('./commands/google-auth.js');
|
|
610
|
+
await calendarSearchCommand(query, opts);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
calendar
|
|
614
|
+
.command('read')
|
|
615
|
+
.description('Read a specific calendar event by ID')
|
|
616
|
+
.argument('<eventId>', 'Calendar event ID')
|
|
617
|
+
.action(async (eventId) => {
|
|
618
|
+
const { calendarReadCommand } = await import('./commands/google-auth.js');
|
|
619
|
+
await calendarReadCommand(eventId);
|
|
620
|
+
});
|
|
621
|
+
|
|
601
622
|
calendar
|
|
602
623
|
.command('logout')
|
|
603
624
|
.description('Sign out of Calendar (remove stored tokens)')
|
|
@@ -606,14 +627,34 @@ calendar
|
|
|
606
627
|
await calendarLogoutCommand();
|
|
607
628
|
});
|
|
608
629
|
|
|
609
|
-
// ── Google
|
|
610
|
-
program
|
|
611
|
-
.command('google
|
|
630
|
+
// ── Unified Google command group ──
|
|
631
|
+
const google = program
|
|
632
|
+
.command('google')
|
|
633
|
+
.description('Google services (login, status, logout for Gmail + Calendar)');
|
|
634
|
+
|
|
635
|
+
google
|
|
636
|
+
.command('login')
|
|
637
|
+
.description('Sign in once for both Gmail and Calendar')
|
|
638
|
+
.action(async () => {
|
|
639
|
+
const { googleLoginCommand } = await import('./commands/google-auth.js');
|
|
640
|
+
await googleLoginCommand();
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
google
|
|
644
|
+
.command('status')
|
|
612
645
|
.description('Show connection status for all Google services')
|
|
613
646
|
.action(async () => {
|
|
614
647
|
const { googleStatusCommand } = await import('./commands/google-auth.js');
|
|
615
648
|
await googleStatusCommand();
|
|
616
649
|
});
|
|
617
650
|
|
|
651
|
+
google
|
|
652
|
+
.command('logout')
|
|
653
|
+
.description('Sign out of all Google services')
|
|
654
|
+
.action(async () => {
|
|
655
|
+
const { googleLogoutCommand } = await import('./commands/google-auth.js');
|
|
656
|
+
await googleLogoutCommand();
|
|
657
|
+
});
|
|
658
|
+
|
|
618
659
|
// Parse and execute
|
|
619
660
|
program.parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"mcpServers": {
|
|
3
|
-
|
|
4
|
-
"gmail": {
|
|
5
|
-
"_disabled": true,
|
|
6
|
-
"command": "npx",
|
|
7
|
-
"args": ["-y", "@gongrzhe/server-gmail-autoauth-mcp"],
|
|
8
|
-
"_setup": "Remove _disabled, restart Claude Code, authenticate when browser opens"
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
"google-calendar": {
|
|
12
|
-
"_disabled": true,
|
|
13
|
-
"command": "npx",
|
|
14
|
-
"args": ["-y", "@modelcontextprotocol/server-google-calendar"],
|
|
15
|
-
"_setup": "Remove _disabled, restart Claude Code, authenticate when browser opens"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
3
|
},
|
|
19
4
|
|
|
20
5
|
"_notes": {
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"2. Remove _disabled from servers you want",
|
|
24
|
-
"3. Restart Claude Code",
|
|
25
|
-
"4. Gmail/Calendar will open a browser for you to authenticate with YOUR Google account"
|
|
26
|
-
],
|
|
27
|
-
"memory": "Claudia's memory system now uses the `claudia` CLI (via Bash tool), not MCP. No MCP server needed for memory.",
|
|
6
|
+
"gmail_and_calendar": "Claudia has built-in Gmail and Calendar support. Run 'claudia google login' to connect. No MCP server needed.",
|
|
7
|
+
"memory": "Claudia's memory system uses the 'claudia' CLI (via Bash tool), not MCP. No MCP server needed for memory.",
|
|
28
8
|
"security": "Each user authenticates with their own accounts. OAuth tokens are stored locally on your machine, never shared.",
|
|
29
9
|
"not_included": {
|
|
30
10
|
"filesystem": "Claude Code has native Read/Write/Edit tools",
|
|
31
11
|
"web-search": "Claude Code has native WebSearch",
|
|
32
12
|
"fetch": "Claude Code has native WebFetch"
|
|
33
13
|
},
|
|
34
|
-
"
|
|
14
|
+
"adding_servers": "Add MCP servers here if needed. Find more at mcp.so or github.com/modelcontextprotocol/servers"
|
|
35
15
|
}
|
|
36
16
|
}
|
package/template-v2/CLAUDE.md
CHANGED
|
@@ -321,16 +321,20 @@ I adapt to whatever tools are available. When you ask me to do something that ne
|
|
|
321
321
|
|
|
322
322
|
| Command | What it does |
|
|
323
323
|
|---------|-------------|
|
|
324
|
-
| `claudia
|
|
324
|
+
| `claudia google login` | Sign in once for both Gmail + Calendar |
|
|
325
|
+
| `claudia google status` | Show connection status for all services |
|
|
326
|
+
| `claudia google logout` | Disconnect all Google services |
|
|
327
|
+
| `claudia gmail login` | Opens browser for Gmail-only OAuth |
|
|
325
328
|
| `claudia gmail status` | Check if Gmail is connected |
|
|
326
329
|
| `claudia gmail search "<query>"` | Search emails (Gmail search syntax) |
|
|
327
330
|
| `claudia gmail read <messageId>` | Read a specific email |
|
|
328
331
|
| `claudia gmail logout` | Disconnect Gmail, remove tokens |
|
|
329
|
-
| `claudia calendar login` | Opens browser for
|
|
332
|
+
| `claudia calendar login` | Opens browser for Calendar-only OAuth |
|
|
330
333
|
| `claudia calendar status` | Check if Calendar is connected |
|
|
331
334
|
| `claudia calendar list` | Show upcoming events |
|
|
335
|
+
| `claudia calendar search "<query>"` | Search events by text |
|
|
336
|
+
| `claudia calendar read <eventId>` | Read a specific event by ID |
|
|
332
337
|
| `claudia calendar logout` | Disconnect Calendar, remove tokens |
|
|
333
|
-
| `claudia google-status` | Show status of all Google connections |
|
|
334
338
|
|
|
335
339
|
These are **real CLI commands**, not questions. Always run them via the Bash tool. Tokens are stored locally at `~/.claudia/tokens/`.
|
|
336
340
|
|