get-claudia 1.51.6 → 1.51.8
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 +80 -9
- package/cli/commands/google-auth.js +75 -1
- package/cli/index.js +18 -1
- package/package.json +1 -1
- package/template-v2/CLAUDE.md +10 -4
package/bin/index.js
CHANGED
|
@@ -341,11 +341,67 @@ async function restartOllama() {
|
|
|
341
341
|
return startOllama();
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// ─── Self-update trampoline ──────────────────────────────────────────────
|
|
345
|
+
// npx aggressively caches packages. If the user runs `npx get-claudia .`
|
|
346
|
+
// and a newer version exists, we re-exec with the latest to avoid stale installs.
|
|
347
|
+
|
|
348
|
+
function isNewerVersion(latest, current) {
|
|
349
|
+
const a = latest.split('.').map(Number);
|
|
350
|
+
const b = current.split('.').map(Number);
|
|
351
|
+
for (let i = 0; i < 3; i++) {
|
|
352
|
+
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
353
|
+
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function checkForNewerVersion(currentVersion) {
|
|
359
|
+
// Skip if already re-execing (prevent infinite recursion)
|
|
360
|
+
if (process.env.CLAUDIA_SKIP_UPDATE_CHECK) return null;
|
|
361
|
+
// Skip for --help / --version (no need to update-check)
|
|
362
|
+
if (process.argv.includes('--help') || process.argv.includes('-h') || process.argv.includes('--version')) return null;
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
const resp = await fetch('https://registry.npmjs.org/get-claudia/latest', {
|
|
366
|
+
signal: AbortSignal.timeout(5000),
|
|
367
|
+
});
|
|
368
|
+
if (!resp.ok) return null;
|
|
369
|
+
const data = await resp.json();
|
|
370
|
+
const latest = data.version;
|
|
371
|
+
if (latest && isNewerVersion(latest, currentVersion)) return latest;
|
|
372
|
+
} catch {
|
|
373
|
+
// Network error or timeout: proceed with current version
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
344
378
|
// ─── Main ───────────────────────────────────────────────────────────────
|
|
345
379
|
|
|
346
380
|
async function main() {
|
|
347
381
|
const version = getVersion();
|
|
348
382
|
|
|
383
|
+
// Self-update trampoline: re-exec with latest if we're stale
|
|
384
|
+
const newerVersion = await checkForNewerVersion(version);
|
|
385
|
+
if (newerVersion) {
|
|
386
|
+
process.stdout.write(`\n ${colors.yellow}→${colors.reset} v${newerVersion} available (running v${version}). Updating...\n\n`);
|
|
387
|
+
const npxCmd = isWindows ? 'npx.cmd' : 'npx';
|
|
388
|
+
try {
|
|
389
|
+
const child = spawn(npxCmd, ['--yes', `get-claudia@${newerVersion}`, ...process.argv.slice(2)], {
|
|
390
|
+
stdio: 'inherit',
|
|
391
|
+
env: { ...process.env, CLAUDIA_SKIP_UPDATE_CHECK: '1' },
|
|
392
|
+
});
|
|
393
|
+
await new Promise((resolve, reject) => {
|
|
394
|
+
child.on('close', (code) => resolve(code));
|
|
395
|
+
child.on('error', reject);
|
|
396
|
+
}).then((code) => {
|
|
397
|
+
process.exit(code || 0);
|
|
398
|
+
});
|
|
399
|
+
} catch {
|
|
400
|
+
// Re-exec failed, fall through to current version
|
|
401
|
+
process.stdout.write(` ${colors.dim}Update failed, continuing with v${version}${colors.reset}\n`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
349
405
|
// Print compact banner
|
|
350
406
|
process.stdout.write(getBanner(version));
|
|
351
407
|
|
|
@@ -414,7 +470,7 @@ async function main() {
|
|
|
414
470
|
}
|
|
415
471
|
|
|
416
472
|
// Disable Gmail/Calendar MCP servers (Claudia has native CLI commands now)
|
|
417
|
-
|
|
473
|
+
disableLegacyMcpServers(targetPath);
|
|
418
474
|
|
|
419
475
|
// Write context/whats-new.md for Claudia's self-awareness (silent)
|
|
420
476
|
writeWhatsNewFile(targetPath, version);
|
|
@@ -820,11 +876,15 @@ async function main() {
|
|
|
820
876
|
}
|
|
821
877
|
|
|
822
878
|
/**
|
|
823
|
-
* Disable
|
|
824
|
-
*
|
|
825
|
-
*
|
|
879
|
+
* Disable legacy MCP servers in .mcp.json that have been replaced by native CLI commands.
|
|
880
|
+
* This runs on both fresh installs and upgrades.
|
|
881
|
+
*
|
|
882
|
+
* Replaced servers:
|
|
883
|
+
* - claudia-memory → `claudia memory` CLI (Node.js, direct SQLite)
|
|
884
|
+
* - gmail → `claudia gmail` CLI
|
|
885
|
+
* - google-calendar → `claudia calendar` CLI
|
|
826
886
|
*/
|
|
827
|
-
function
|
|
887
|
+
function disableLegacyMcpServers(targetPath) {
|
|
828
888
|
const mcpPath = join(targetPath, '.mcp.json');
|
|
829
889
|
if (!existsSync(mcpPath)) return;
|
|
830
890
|
|
|
@@ -833,20 +893,31 @@ function disableGoogleMcpServers(targetPath) {
|
|
|
833
893
|
const config = JSON.parse(raw);
|
|
834
894
|
if (!config.mcpServers) return;
|
|
835
895
|
|
|
836
|
-
|
|
896
|
+
// Map of MCP server keys to their CLI replacement description
|
|
897
|
+
const legacyServers = {
|
|
898
|
+
'claudia-memory': 'claudia memory CLI',
|
|
899
|
+
'claudia_memory': 'claudia memory CLI',
|
|
900
|
+
'gmail': 'claudia google login',
|
|
901
|
+
'google-calendar': 'claudia google login',
|
|
902
|
+
'google_calendar': 'claudia google login',
|
|
903
|
+
'googleCalendar': 'claudia google login',
|
|
904
|
+
};
|
|
905
|
+
|
|
837
906
|
let changed = false;
|
|
907
|
+
const disabled = [];
|
|
838
908
|
|
|
839
|
-
for (const key of
|
|
909
|
+
for (const [key, replacement] of Object.entries(legacyServers)) {
|
|
840
910
|
if (config.mcpServers[key] && !config.mcpServers[key]._disabled) {
|
|
841
911
|
config.mcpServers[key]._disabled = true;
|
|
842
|
-
config.mcpServers[key]._replaced_by =
|
|
912
|
+
config.mcpServers[key]._replaced_by = replacement;
|
|
843
913
|
changed = true;
|
|
914
|
+
disabled.push(key);
|
|
844
915
|
}
|
|
845
916
|
}
|
|
846
917
|
|
|
847
918
|
if (changed) {
|
|
848
919
|
writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n');
|
|
849
|
-
console.log(` ${colors.yellow}→${colors.reset} Disabled
|
|
920
|
+
console.log(` ${colors.yellow}→${colors.reset} Disabled legacy MCP: ${disabled.join(', ')} (replaced by native CLI)`);
|
|
850
921
|
}
|
|
851
922
|
} catch {
|
|
852
923
|
// Not valid JSON or can't read -- skip silently
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* claudia gmail search - Search emails
|
|
11
11
|
* claudia gmail read - Read a specific email
|
|
12
12
|
* claudia gmail send - Send an email with optional attachments
|
|
13
|
+
* claudia gmail draft - Create a draft email with optional attachments
|
|
13
14
|
* claudia gmail logout - Sign out of Gmail
|
|
14
15
|
* claudia calendar login - Sign in with Google (Calendar only)
|
|
15
16
|
* claudia calendar status - Check Calendar connection status
|
|
@@ -25,7 +26,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
25
26
|
import { authenticate, getAccessToken, isAuthenticated, revokeTokens, authStatus } from '../core/google-oauth.js';
|
|
26
27
|
import { outputJson as output } from '../core/output.js';
|
|
27
28
|
|
|
28
|
-
// ── MIME Helpers (for gmail send) ──
|
|
29
|
+
// ── MIME Helpers (for gmail send & draft) ──
|
|
29
30
|
|
|
30
31
|
const MIME_TYPES = {
|
|
31
32
|
png: 'image/png',
|
|
@@ -368,6 +369,79 @@ export async function gmailSendCommand(opts) {
|
|
|
368
369
|
});
|
|
369
370
|
}
|
|
370
371
|
|
|
372
|
+
export async function gmailDraftCommand(opts) {
|
|
373
|
+
const token = await getAccessToken('gmail');
|
|
374
|
+
if (!token) {
|
|
375
|
+
console.error('Not authenticated. Run: claudia gmail login');
|
|
376
|
+
process.exitCode = 1;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Drafts are more lenient than send: subject and body can be empty
|
|
381
|
+
const to = opts.to || [];
|
|
382
|
+
const subject = opts.subject || '';
|
|
383
|
+
const body = opts.body || '';
|
|
384
|
+
|
|
385
|
+
// Prepare attachments
|
|
386
|
+
let attachments = [];
|
|
387
|
+
if (opts.attach && opts.attach.length > 0) {
|
|
388
|
+
try {
|
|
389
|
+
attachments = prepareAttachments(opts.attach);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error(err.message);
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Build MIME message and base64url-encode it
|
|
398
|
+
const rawMessage = buildMimeMessage({
|
|
399
|
+
to,
|
|
400
|
+
subject,
|
|
401
|
+
body,
|
|
402
|
+
cc: opts.cc,
|
|
403
|
+
bcc: opts.bcc,
|
|
404
|
+
html: opts.html,
|
|
405
|
+
replyTo: opts.replyTo,
|
|
406
|
+
attachments,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const encodedMessage = Buffer.from(rawMessage, 'utf-8')
|
|
410
|
+
.toString('base64')
|
|
411
|
+
.replace(/\+/g, '-')
|
|
412
|
+
.replace(/\//g, '_')
|
|
413
|
+
.replace(/=+$/, '');
|
|
414
|
+
|
|
415
|
+
// Gmail drafts API: { message: { raw, threadId? } }
|
|
416
|
+
const message = { raw: encodedMessage };
|
|
417
|
+
if (opts.thread) {
|
|
418
|
+
message.threadId = opts.thread;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const resp = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/drafts', {
|
|
422
|
+
method: 'POST',
|
|
423
|
+
headers: {
|
|
424
|
+
Authorization: `Bearer ${token}`,
|
|
425
|
+
'Content-Type': 'application/json',
|
|
426
|
+
},
|
|
427
|
+
body: JSON.stringify({ message }),
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
if (!resp.ok) {
|
|
431
|
+
const err = await resp.text();
|
|
432
|
+
console.error(`Gmail API error (${resp.status}): ${err}`);
|
|
433
|
+
process.exitCode = 1;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const data = await resp.json();
|
|
438
|
+
output({
|
|
439
|
+
id: data.id,
|
|
440
|
+
messageId: data.message?.id,
|
|
441
|
+
threadId: data.message?.threadId,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
371
445
|
export async function gmailLogoutCommand() {
|
|
372
446
|
const removed = revokeTokens('gmail');
|
|
373
447
|
if (removed) {
|
package/cli/index.js
CHANGED
|
@@ -522,7 +522,7 @@ program
|
|
|
522
522
|
// ── Gmail subcommand group ──
|
|
523
523
|
const gmail = program
|
|
524
524
|
.command('gmail')
|
|
525
|
-
.description('Gmail integration (login, search, read, send)');
|
|
525
|
+
.description('Gmail integration (login, search, read, send, draft)');
|
|
526
526
|
|
|
527
527
|
gmail
|
|
528
528
|
.command('login')
|
|
@@ -576,6 +576,23 @@ gmail
|
|
|
576
576
|
await gmailSendCommand(opts);
|
|
577
577
|
});
|
|
578
578
|
|
|
579
|
+
gmail
|
|
580
|
+
.command('draft')
|
|
581
|
+
.description('Create a draft email (with optional attachments)')
|
|
582
|
+
.option('--to <email...>', 'Recipient email address(es)')
|
|
583
|
+
.option('--subject <text>', 'Email subject')
|
|
584
|
+
.option('--body <text>', 'Email body text')
|
|
585
|
+
.option('--cc <email...>', 'CC recipient(s)')
|
|
586
|
+
.option('--bcc <email...>', 'BCC recipient(s)')
|
|
587
|
+
.option('--attach <filepath...>', 'File(s) to attach')
|
|
588
|
+
.option('--html', 'Treat body as HTML', false)
|
|
589
|
+
.option('--thread <threadId>', 'Thread ID (for replies)')
|
|
590
|
+
.option('--reply-to <messageId>', 'Message-ID for In-Reply-To header')
|
|
591
|
+
.action(async (opts) => {
|
|
592
|
+
const { gmailDraftCommand } = await import('./commands/google-auth.js');
|
|
593
|
+
await gmailDraftCommand(opts);
|
|
594
|
+
});
|
|
595
|
+
|
|
579
596
|
gmail
|
|
580
597
|
.command('logout')
|
|
581
598
|
.description('Sign out of Gmail (remove stored tokens)')
|
package/package.json
CHANGED
package/template-v2/CLAUDE.md
CHANGED
|
@@ -214,17 +214,22 @@ What would you like to start with?
|
|
|
214
214
|
|
|
215
215
|
### 2. Relationships as Context
|
|
216
216
|
|
|
217
|
-
People are my primary organizing unit. When someone is mentioned
|
|
217
|
+
People are my primary organizing unit. **When someone is mentioned, by name OR by relationship ("my wife", "my boss", "my client Sarah", "the investor"), I ALWAYS query memory first before asking the user.** I never ask for information I might already know.
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
Lookup order:
|
|
220
|
+
1. `claudia memory recall "<name or relationship>" --project-dir "$PWD"` to resolve who they mean
|
|
221
|
+
2. `claudia memory about "<entity name>" --project-dir "$PWD"` for full context (email, role, history)
|
|
222
|
+
3. Check `people/[name].md` for additional context
|
|
223
|
+
4. Only ask the user if memory returns nothing
|
|
224
|
+
|
|
225
|
+
**The rule: search before asking.** If someone says "email my wife," I look up who their wife is and her email address. I don't ask "What's your wife's email?" when I might already have it.
|
|
222
226
|
|
|
223
227
|
What I track about people:
|
|
224
228
|
- Communication preferences and style
|
|
225
229
|
- What matters to them
|
|
226
230
|
- Your history with them
|
|
227
231
|
- Current context (projects, concerns, opportunities)
|
|
232
|
+
- Contact details (email, phone, handles)
|
|
228
233
|
- Notes from past interactions
|
|
229
234
|
|
|
230
235
|
### 3. Commitment Tracking
|
|
@@ -329,6 +334,7 @@ I adapt to whatever tools are available. When you ask me to do something that ne
|
|
|
329
334
|
| `claudia gmail search "<query>"` | Search emails (Gmail search syntax) |
|
|
330
335
|
| `claudia gmail read <messageId>` | Read a specific email |
|
|
331
336
|
| `claudia gmail send --to <email> --subject <text> --body <text>` | Send email (supports --attach, --cc, --bcc, --html, --thread, --reply-to) |
|
|
337
|
+
| `claudia gmail draft --to <email> --subject <text> --body <text>` | Create draft in Gmail (same options as send, all optional, supports --attach for images/files) |
|
|
332
338
|
| `claudia gmail logout` | Disconnect Gmail, remove tokens |
|
|
333
339
|
| `claudia calendar login` | Opens browser for Calendar-only OAuth |
|
|
334
340
|
| `claudia calendar status` | Check if Calendar is connected |
|