get-claudia 1.51.6 → 1.51.7

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
@@ -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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-claudia",
3
- "version": "1.51.6",
3
+ "version": "1.51.7",
4
4
  "description": "An AI assistant who learns how you work.",
5
5
  "keywords": [
6
6
  "claudia",
@@ -329,6 +329,7 @@ I adapt to whatever tools are available. When you ask me to do something that ne
329
329
  | `claudia gmail search "<query>"` | Search emails (Gmail search syntax) |
330
330
  | `claudia gmail read <messageId>` | Read a specific email |
331
331
  | `claudia gmail send --to <email> --subject <text> --body <text>` | Send email (supports --attach, --cc, --bcc, --html, --thread, --reply-to) |
332
+ | `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
333
  | `claudia gmail logout` | Disconnect Gmail, remove tokens |
333
334
  | `claudia calendar login` | Opens browser for Calendar-only OAuth |
334
335
  | `claudia calendar status` | Check if Calendar is connected |