@bobfrankston/gcal 0.1.48 → 0.1.50
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/README.md +8 -0
- package/gcal.js +174 -2
- package/gcal.js.map +1 -1
- package/gcal.ts +191 -5
- package/glib/aihelper.d.ts +6 -0
- package/glib/aihelper.d.ts.map +1 -1
- package/glib/aihelper.js +77 -0
- package/glib/aihelper.js.map +1 -1
- package/glib/aihelper.ts +88 -0
- package/gtask.js +89 -17
- package/gtask.js.map +1 -1
- package/gtask.ts +86 -16
- package/package.json +1 -1
package/gcal.ts
CHANGED
|
@@ -107,6 +107,66 @@ async function patchEvent(
|
|
|
107
107
|
return await res.json() as GoogleEvent;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/** Print warnings for events that overlap or fall within 1 hour of [start, end]. */
|
|
111
|
+
async function checkProximity(
|
|
112
|
+
accessToken: string,
|
|
113
|
+
calendarId: string,
|
|
114
|
+
start: Date,
|
|
115
|
+
end: Date,
|
|
116
|
+
excludeBaseId?: string
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const HOUR = 60 * 60_000;
|
|
119
|
+
const windowMin = new Date(start.getTime() - HOUR).toISOString();
|
|
120
|
+
const windowMax = new Date(end.getTime() + HOUR).toISOString();
|
|
121
|
+
let nearby: GoogleEvent[];
|
|
122
|
+
try {
|
|
123
|
+
nearby = await listEvents(accessToken, calendarId, 50, windowMin, windowMax);
|
|
124
|
+
} catch {
|
|
125
|
+
return; // Non-fatal — skip warning on fetch failure
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const sMs = start.getTime();
|
|
129
|
+
const eMs = end.getTime();
|
|
130
|
+
const warnings: string[] = [];
|
|
131
|
+
|
|
132
|
+
for (const e of nearby) {
|
|
133
|
+
if (e.eventType === 'birthday') continue;
|
|
134
|
+
const baseId = (e.id || '').split('_')[0];
|
|
135
|
+
if (excludeBaseId && baseId === excludeBaseId) continue;
|
|
136
|
+
if (!e.start) continue;
|
|
137
|
+
|
|
138
|
+
let evStart: number;
|
|
139
|
+
let evEnd: number;
|
|
140
|
+
if (e.start.dateTime && e.end?.dateTime) {
|
|
141
|
+
evStart = new Date(e.start.dateTime).getTime();
|
|
142
|
+
evEnd = new Date(e.end.dateTime).getTime();
|
|
143
|
+
} else if (e.start.date && e.end?.date) {
|
|
144
|
+
evStart = parseAllDay(e.start.date).getTime();
|
|
145
|
+
evEnd = parseAllDay(e.end.date).getTime();
|
|
146
|
+
} else {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const summary = e.summary || '(no title)';
|
|
151
|
+
const when = formatDateTime(e.start);
|
|
152
|
+
|
|
153
|
+
if (evStart < eMs && evEnd > sMs) {
|
|
154
|
+
warnings.push(` OVERLAPS: ${when} ${summary}`);
|
|
155
|
+
} else if (evEnd <= sMs && sMs - evEnd <= HOUR) {
|
|
156
|
+
const mins = Math.round((sMs - evEnd) / 60_000);
|
|
157
|
+
warnings.push(` ${String(mins).padStart(2)}m before: ${when} ${summary}`);
|
|
158
|
+
} else if (evStart >= eMs && evStart - eMs <= HOUR) {
|
|
159
|
+
const mins = Math.round((evStart - eMs) / 60_000);
|
|
160
|
+
warnings.push(` ${String(mins).padStart(2)}m after: ${when} ${summary}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (warnings.length > 0) {
|
|
165
|
+
console.log(`\nWarning: nearby events:`);
|
|
166
|
+
for (const w of warnings) console.log(w);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
110
170
|
async function importIcsFile(
|
|
111
171
|
filePath: string,
|
|
112
172
|
accessToken: string,
|
|
@@ -195,6 +255,7 @@ Usage: gcal <file.ics> Import ICS file (file association)
|
|
|
195
255
|
|
|
196
256
|
Commands:
|
|
197
257
|
list List upcoming events
|
|
258
|
+
show Show full details for an event (-json for JSON)
|
|
198
259
|
add Add event (explicit, AI, or interactive)
|
|
199
260
|
del | delete Delete event(s) by ID
|
|
200
261
|
remind Add reminder(s) to existing event
|
|
@@ -226,6 +287,15 @@ const USAGE: Record<string, string> = {
|
|
|
226
287
|
gcal list -since "10 days ago"
|
|
227
288
|
gcal list -since "april 1" -till "may 1"
|
|
228
289
|
gcal list -since "april 1" -n 50
|
|
290
|
+
`,
|
|
291
|
+
show: `gcal show <id> [-json]
|
|
292
|
+
Show full details for an event (by ID prefix).
|
|
293
|
+
-json Output raw event JSON instead of human-readable text.
|
|
294
|
+
Searches up to 30 days back; widen with -since.
|
|
295
|
+
|
|
296
|
+
Examples:
|
|
297
|
+
gcal show abc12345
|
|
298
|
+
gcal show abc12345 -json
|
|
229
299
|
`,
|
|
230
300
|
add: `gcal add <title> <when> [duration] Explicit
|
|
231
301
|
gcal add "<free text>" AI-parsed single arg
|
|
@@ -314,6 +384,7 @@ interface ParsedArgs {
|
|
|
314
384
|
birthdays: boolean;
|
|
315
385
|
clip: boolean;
|
|
316
386
|
all: boolean;
|
|
387
|
+
json: boolean;
|
|
317
388
|
reminders: number[];
|
|
318
389
|
since?: Date;
|
|
319
390
|
till?: Date;
|
|
@@ -333,6 +404,7 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
333
404
|
birthdays: false,
|
|
334
405
|
clip: false,
|
|
335
406
|
all: false,
|
|
407
|
+
json: false,
|
|
336
408
|
reminders: [],
|
|
337
409
|
helpCmd: ''
|
|
338
410
|
};
|
|
@@ -373,6 +445,10 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
373
445
|
case '--all':
|
|
374
446
|
result.all = true;
|
|
375
447
|
break;
|
|
448
|
+
case '-json':
|
|
449
|
+
case '--json':
|
|
450
|
+
result.json = true;
|
|
451
|
+
break;
|
|
376
452
|
case '-r':
|
|
377
453
|
case '-reminder':
|
|
378
454
|
case '--reminder': {
|
|
@@ -682,6 +758,7 @@ async function main(): Promise<void> {
|
|
|
682
758
|
};
|
|
683
759
|
|
|
684
760
|
const token = await getAccessToken(user, true);
|
|
761
|
+
await checkProximity(token, parsed.calendar, startTime, endTime);
|
|
685
762
|
const created = await createEvent(token, event, parsed.calendar);
|
|
686
763
|
console.log(`\nEvent created: ${created.summary}`);
|
|
687
764
|
console.log(` When: ${formatDateTime(created.start)} - ${formatDateTime(created.end)}`);
|
|
@@ -725,6 +802,7 @@ async function main(): Promise<void> {
|
|
|
725
802
|
}
|
|
726
803
|
|
|
727
804
|
const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
805
|
+
const token = await getAccessToken(user, true);
|
|
728
806
|
const events: GoogleEvent[] = [];
|
|
729
807
|
for (const extracted of extractedEvents) {
|
|
730
808
|
const tz = extracted.timeZone || localTz;
|
|
@@ -752,6 +830,8 @@ async function main(): Promise<void> {
|
|
|
752
830
|
console.log(` When: ${formatDateTime(event.start)} - ${formatDateTime(event.end)} (${extracted.duration || '1h'})${tz !== localTz ? ` [${tz}]` : ''}`);
|
|
753
831
|
if (extracted.location) console.log(` Where: ${extracted.location}`);
|
|
754
832
|
if (extracted.description) console.log(` Note: ${extracted.description}`);
|
|
833
|
+
|
|
834
|
+
await checkProximity(token, parsed.calendar, new Date(startDt), endDate);
|
|
755
835
|
}
|
|
756
836
|
|
|
757
837
|
if (events.length === 0) {
|
|
@@ -776,7 +856,6 @@ async function main(): Promise<void> {
|
|
|
776
856
|
break;
|
|
777
857
|
}
|
|
778
858
|
|
|
779
|
-
const token = await getAccessToken(user, true);
|
|
780
859
|
for (const event of events) {
|
|
781
860
|
const created = await createEvent(token, event, parsed.calendar);
|
|
782
861
|
console.log(`\nEvent created: ${created.summary}`);
|
|
@@ -1004,6 +1083,17 @@ async function main(): Promise<void> {
|
|
|
1004
1083
|
newEndDisplay = patch.end!;
|
|
1005
1084
|
}
|
|
1006
1085
|
|
|
1086
|
+
// Proximity check for timed events (skip all-day)
|
|
1087
|
+
if (!origIsAllDay && patch.start?.dateTime && patch.end?.dateTime) {
|
|
1088
|
+
await checkProximity(
|
|
1089
|
+
token,
|
|
1090
|
+
parsed.calendar,
|
|
1091
|
+
new Date(patch.start.dateTime),
|
|
1092
|
+
new Date(patch.end.dateTime),
|
|
1093
|
+
(event.id || '').split('_')[0]
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1007
1097
|
const updated = await patchEvent(token, event.id!, patch, parsed.calendar);
|
|
1008
1098
|
console.log(`Rescheduled: ${updated.summary}`);
|
|
1009
1099
|
console.log(` From: ${formatDateTime(event.start!)} - ${formatDateTime(event.end!)}`);
|
|
@@ -1011,6 +1101,100 @@ async function main(): Promise<void> {
|
|
|
1011
1101
|
break;
|
|
1012
1102
|
}
|
|
1013
1103
|
|
|
1104
|
+
case 'show': {
|
|
1105
|
+
if (parsed.args.length < 1) {
|
|
1106
|
+
console.error('Usage: gcal show <id> [-json]');
|
|
1107
|
+
console.error('Use "gcal list" to see event IDs');
|
|
1108
|
+
process.exit(1);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const idPrefix = parsed.args[0];
|
|
1112
|
+
const lookback = parsed.since
|
|
1113
|
+
? parsed.since.toISOString()
|
|
1114
|
+
: new Date(Date.now() - 30 * 86400_000).toISOString();
|
|
1115
|
+
const timeMax = parsed.till ? parsed.till.toISOString() : undefined;
|
|
1116
|
+
|
|
1117
|
+
const token = await getAccessToken(user, false);
|
|
1118
|
+
const events = await listEvents(token, parsed.calendar, 250, lookback, timeMax);
|
|
1119
|
+
|
|
1120
|
+
let matches = events.filter(e => e.id?.startsWith(idPrefix));
|
|
1121
|
+
if (!parsed.birthdays) {
|
|
1122
|
+
matches = matches.filter(e => e.eventType !== 'birthday');
|
|
1123
|
+
}
|
|
1124
|
+
const unique = [...new Map(matches.map(e => [(e.id || '').split('_')[0], e])).values()];
|
|
1125
|
+
|
|
1126
|
+
if (unique.length === 0) {
|
|
1127
|
+
console.error(`${idPrefix}: not found (searched from ${lookback.slice(0, 10)})`);
|
|
1128
|
+
process.exit(1);
|
|
1129
|
+
}
|
|
1130
|
+
if (unique.length > 1) {
|
|
1131
|
+
console.error(`${idPrefix}: ambiguous (${unique.length} matches)`);
|
|
1132
|
+
for (const e of unique) {
|
|
1133
|
+
console.error(` ${e.id?.slice(0, 8)} - ${e.summary}`);
|
|
1134
|
+
}
|
|
1135
|
+
process.exit(1);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const event = unique[0];
|
|
1139
|
+
if (parsed.json) {
|
|
1140
|
+
console.log(JSON.stringify(event, null, 2));
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
console.log(`\n${event.summary || '(no title)'}`);
|
|
1145
|
+
const duration = (event.start && event.end) ? formatDuration(event.start, event.end) : '';
|
|
1146
|
+
const whenLine = event.start && event.end
|
|
1147
|
+
? `${formatDateTime(event.start)} - ${formatDateTime(event.end)}${duration ? ` (${duration})` : ''}`
|
|
1148
|
+
: '?';
|
|
1149
|
+
console.log(` When: ${whenLine}`);
|
|
1150
|
+
if (event.start?.timeZone) {
|
|
1151
|
+
console.log(` TZ: ${event.start.timeZone}`);
|
|
1152
|
+
}
|
|
1153
|
+
if (event.location) console.log(` Where: ${event.location}`);
|
|
1154
|
+
if (event.description) {
|
|
1155
|
+
const indented = event.description.split('\n').map((l, i) => i === 0 ? l : ` ${l}`).join('\n');
|
|
1156
|
+
console.log(` Notes: ${indented}`);
|
|
1157
|
+
}
|
|
1158
|
+
if (event.recurrence?.length) {
|
|
1159
|
+
console.log(` Repeat: ${event.recurrence.join('; ')}`);
|
|
1160
|
+
}
|
|
1161
|
+
if (event.recurringEventId) {
|
|
1162
|
+
console.log(` Series ID: ${event.recurringEventId}`);
|
|
1163
|
+
}
|
|
1164
|
+
if (event.attendees?.length) {
|
|
1165
|
+
console.log(` Attendees:`);
|
|
1166
|
+
for (const a of event.attendees) {
|
|
1167
|
+
const name = a.displayName ? `${a.displayName} <${a.email}>` : (a.email || '?');
|
|
1168
|
+
const status = a.responseStatus ? ` [${a.responseStatus}]` : '';
|
|
1169
|
+
const role = a.organizer ? ' (organizer)' : a.optional ? ' (optional)' : '';
|
|
1170
|
+
console.log(` ${name}${status}${role}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
if (event.reminders?.overrides?.length) {
|
|
1174
|
+
console.log(` Reminders:`);
|
|
1175
|
+
for (const r of event.reminders.overrides) {
|
|
1176
|
+
const m = r.minutes || 0;
|
|
1177
|
+
const dur = m >= 60 && m % 60 === 0 ? `${m / 60}h` : `${m}m`;
|
|
1178
|
+
console.log(` ${dur} (${r.method || 'popup'})`);
|
|
1179
|
+
}
|
|
1180
|
+
} else if (event.reminders?.useDefault) {
|
|
1181
|
+
console.log(` Reminders: (calendar default)`);
|
|
1182
|
+
}
|
|
1183
|
+
if (event.creator?.email) {
|
|
1184
|
+
console.log(` Creator: ${event.creator.displayName ? `${event.creator.displayName} <${event.creator.email}>` : event.creator.email}`);
|
|
1185
|
+
}
|
|
1186
|
+
if (event.organizer?.email && event.organizer.email !== event.creator?.email) {
|
|
1187
|
+
console.log(` Organizer: ${event.organizer.displayName ? `${event.organizer.displayName} <${event.organizer.email}>` : event.organizer.email}`);
|
|
1188
|
+
}
|
|
1189
|
+
if (event.hangoutLink) console.log(` Meet: ${event.hangoutLink}`);
|
|
1190
|
+
if (event.htmlLink) console.log(` Link: ${event.htmlLink}`);
|
|
1191
|
+
console.log(` Status: ${event.status || 'confirmed'}`);
|
|
1192
|
+
if (event.created) console.log(` Created: ${formatDateTime({ dateTime: event.created })}`);
|
|
1193
|
+
if (event.updated) console.log(` Updated: ${formatDateTime({ dateTime: event.updated })}`);
|
|
1194
|
+
console.log(` ID: ${event.id}`);
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1014
1198
|
default:
|
|
1015
1199
|
console.error(`Unknown command: ${parsed.command}`);
|
|
1016
1200
|
showUsage();
|
|
@@ -1019,8 +1203,10 @@ async function main(): Promise<void> {
|
|
|
1019
1203
|
}
|
|
1020
1204
|
|
|
1021
1205
|
if (import.meta.main) {
|
|
1022
|
-
main()
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1206
|
+
main()
|
|
1207
|
+
.then(() => process.exit(0))
|
|
1208
|
+
.catch(e => {
|
|
1209
|
+
console.error(`Error: ${e.message}`);
|
|
1210
|
+
process.exit(1);
|
|
1211
|
+
});
|
|
1026
1212
|
}
|
package/glib/aihelper.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ export interface ExtractedEvent {
|
|
|
12
12
|
description?: string;
|
|
13
13
|
}
|
|
14
14
|
export declare function extractEventsFromText(text: string): Promise<ExtractedEvent[]>;
|
|
15
|
+
export interface ExtractedTask {
|
|
16
|
+
title: string;
|
|
17
|
+
due?: string; /** YYYY-MM-DD, optional */
|
|
18
|
+
notes?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function extractTasksFromText(text: string): Promise<ExtractedTask[]>;
|
|
15
21
|
/** Read clipboard text (cross-platform via clipboardy) */
|
|
16
22
|
export declare function readClipboard(): string;
|
|
17
23
|
//# sourceMappingURL=aihelper.d.ts.map
|
package/glib/aihelper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aihelper.d.ts","sourceRoot":"","sources":["aihelper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AA6BD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAoEnF;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,IAAI,MAAM,CAMtC"}
|
|
1
|
+
{"version":3,"file":"aihelper.d.ts","sourceRoot":"","sources":["aihelper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkCH,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AA6BD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAoEnF;AAED,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,CAAO,2BAA2B;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAwBD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA0DjF;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,IAAI,MAAM,CAMtC"}
|
package/glib/aihelper.js
CHANGED
|
@@ -121,6 +121,83 @@ export async function extractEventsFromText(text) {
|
|
|
121
121
|
return [];
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
const TASK_EXTRACTION_PROMPT = `Extract to-do tasks from the user's text and return ONLY valid JSON.
|
|
125
|
+
|
|
126
|
+
Today's date is {{TODAY}}.
|
|
127
|
+
|
|
128
|
+
The text may describe one or multiple tasks. Always return a JSON array of task objects.
|
|
129
|
+
|
|
130
|
+
Output format:
|
|
131
|
+
[
|
|
132
|
+
{
|
|
133
|
+
"title": "Task title",
|
|
134
|
+
"due": "YYYY-MM-DD",
|
|
135
|
+
"notes": "optional details"
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
Rules:
|
|
140
|
+
- title: concise task title (imperative if natural, e.g. "Call plumber")
|
|
141
|
+
- due: date-only (YYYY-MM-DD). Resolve relative dates ("tomorrow", "next Friday") using today's date. Omit if no date is implied.
|
|
142
|
+
- notes: include supporting details the title doesn't already capture. Omit if none.
|
|
143
|
+
- Google Tasks ignores time-of-day, so do not include hours/minutes.
|
|
144
|
+
- Return ONLY the JSON array, no markdown, no explanation`;
|
|
145
|
+
export async function extractTasksFromText(text) {
|
|
146
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
147
|
+
if (!apiKey) {
|
|
148
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
149
|
+
const appData = process.env.APPDATA || path.join(home, '.config');
|
|
150
|
+
const keysPath = path.join(appData, 'gcards', 'keys.env');
|
|
151
|
+
console.error(`\nANTHROPIC_API_KEY not set.`);
|
|
152
|
+
console.error(`Add it to: ${keysPath}`);
|
|
153
|
+
console.error(`Format: ANTHROPIC_API_KEY=sk-ant-...\n`);
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
const today = new Date().toISOString().split('T')[0];
|
|
157
|
+
const dayName = new Date().toLocaleDateString('en-US', { weekday: 'long' });
|
|
158
|
+
const systemPrompt = TASK_EXTRACTION_PROMPT
|
|
159
|
+
.replace('{{TODAY}}', `${today} (${dayName})`);
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
'x-api-key': apiKey,
|
|
166
|
+
'anthropic-version': '2023-06-01'
|
|
167
|
+
},
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
model: 'claude-haiku-4-5-20251001',
|
|
170
|
+
max_tokens: 512,
|
|
171
|
+
system: systemPrompt,
|
|
172
|
+
messages: [{ role: 'user', content: text }]
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const errorText = await response.text();
|
|
177
|
+
console.error(`Claude API error: ${response.status} ${errorText}`);
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
const data = await response.json();
|
|
181
|
+
const content = data.content?.[0]?.text;
|
|
182
|
+
if (!content)
|
|
183
|
+
return [];
|
|
184
|
+
const jsonMatch = content.match(/\[[\s\S]*\]/);
|
|
185
|
+
if (!jsonMatch) {
|
|
186
|
+
const objMatch = content.match(/\{[\s\S]*\}/);
|
|
187
|
+
if (!objMatch) {
|
|
188
|
+
console.error('No JSON found in AI response');
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
return [JSON.parse(objMatch[0])];
|
|
192
|
+
}
|
|
193
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
194
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error(`Error extracting tasks: ${error}`);
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
124
201
|
/** Read clipboard text (cross-platform via clipboardy) */
|
|
125
202
|
export function readClipboard() {
|
|
126
203
|
try {
|
package/glib/aihelper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aihelper.js","sourceRoot":"","sources":["aihelper.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aihelper.js","sourceRoot":"","sources":["aihelper.ts"],"names":[],"mappings":"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"}
|
package/glib/aihelper.ts
CHANGED
|
@@ -142,6 +142,94 @@ export async function extractEventsFromText(text: string): Promise<ExtractedEven
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
export interface ExtractedTask {
|
|
146
|
+
title: string;
|
|
147
|
+
due?: string; /** YYYY-MM-DD, optional */
|
|
148
|
+
notes?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const TASK_EXTRACTION_PROMPT = `Extract to-do tasks from the user's text and return ONLY valid JSON.
|
|
152
|
+
|
|
153
|
+
Today's date is {{TODAY}}.
|
|
154
|
+
|
|
155
|
+
The text may describe one or multiple tasks. Always return a JSON array of task objects.
|
|
156
|
+
|
|
157
|
+
Output format:
|
|
158
|
+
[
|
|
159
|
+
{
|
|
160
|
+
"title": "Task title",
|
|
161
|
+
"due": "YYYY-MM-DD",
|
|
162
|
+
"notes": "optional details"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
Rules:
|
|
167
|
+
- title: concise task title (imperative if natural, e.g. "Call plumber")
|
|
168
|
+
- due: date-only (YYYY-MM-DD). Resolve relative dates ("tomorrow", "next Friday") using today's date. Omit if no date is implied.
|
|
169
|
+
- notes: include supporting details the title doesn't already capture. Omit if none.
|
|
170
|
+
- Google Tasks ignores time-of-day, so do not include hours/minutes.
|
|
171
|
+
- Return ONLY the JSON array, no markdown, no explanation`;
|
|
172
|
+
|
|
173
|
+
export async function extractTasksFromText(text: string): Promise<ExtractedTask[]> {
|
|
174
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
175
|
+
if (!apiKey) {
|
|
176
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
177
|
+
const appData = process.env.APPDATA || path.join(home, '.config');
|
|
178
|
+
const keysPath = path.join(appData, 'gcards', 'keys.env');
|
|
179
|
+
console.error(`\nANTHROPIC_API_KEY not set.`);
|
|
180
|
+
console.error(`Add it to: ${keysPath}`);
|
|
181
|
+
console.error(`Format: ANTHROPIC_API_KEY=sk-ant-...\n`);
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const today = new Date().toISOString().split('T')[0];
|
|
186
|
+
const dayName = new Date().toLocaleDateString('en-US', { weekday: 'long' });
|
|
187
|
+
const systemPrompt = TASK_EXTRACTION_PROMPT
|
|
188
|
+
.replace('{{TODAY}}', `${today} (${dayName})`);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: {
|
|
194
|
+
'Content-Type': 'application/json',
|
|
195
|
+
'x-api-key': apiKey,
|
|
196
|
+
'anthropic-version': '2023-06-01'
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
model: 'claude-haiku-4-5-20251001',
|
|
200
|
+
max_tokens: 512,
|
|
201
|
+
system: systemPrompt,
|
|
202
|
+
messages: [{ role: 'user', content: text }]
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
const errorText = await response.text();
|
|
208
|
+
console.error(`Claude API error: ${response.status} ${errorText}`);
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const data = await response.json() as any;
|
|
213
|
+
const content = data.content?.[0]?.text;
|
|
214
|
+
if (!content) return [];
|
|
215
|
+
|
|
216
|
+
const jsonMatch = content.match(/\[[\s\S]*\]/);
|
|
217
|
+
if (!jsonMatch) {
|
|
218
|
+
const objMatch = content.match(/\{[\s\S]*\}/);
|
|
219
|
+
if (!objMatch) {
|
|
220
|
+
console.error('No JSON found in AI response');
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
return [JSON.parse(objMatch[0]) as ExtractedTask];
|
|
224
|
+
}
|
|
225
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
226
|
+
return Array.isArray(parsed) ? parsed as ExtractedTask[] : [parsed as ExtractedTask];
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(`Error extracting tasks: ${error}`);
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
145
233
|
/** Read clipboard text (cross-platform via clipboardy) */
|
|
146
234
|
export function readClipboard(): string {
|
|
147
235
|
try {
|
package/gtask.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createInterface } from 'readline/promises';
|
|
|
7
7
|
import { loadConfig, saveConfig, parseDateTime, formatYMD, normalizeUser } from './glib/gutils.js';
|
|
8
8
|
import { setupAbortHandler, getAccessToken } from './glib/goauth.js';
|
|
9
9
|
import { listTaskLists, listTasks, createTask, patchTask, deleteTask, moveTask, clearCompleted, resolveTaskList } from './glib/tasksapi.js';
|
|
10
|
+
import { extractTasksFromText, readClipboard } from './glib/aihelper.js';
|
|
10
11
|
import pkg from './package.json' with { type: 'json' };
|
|
11
12
|
const VERSION = pkg.version;
|
|
12
13
|
const USAGE_SUMMARY = `gtask v${VERSION} - Google Tasks CLI
|
|
@@ -16,6 +17,8 @@ Usage: gtask <command> [options]
|
|
|
16
17
|
|
|
17
18
|
Commands:
|
|
18
19
|
add <title> [when] Add a task (optional due date)
|
|
20
|
+
add -clip AI-parse task(s) from clipboard
|
|
21
|
+
add "<freeform text>" AI-parse task(s) from a single argument
|
|
19
22
|
list List open tasks
|
|
20
23
|
lists List all tasklists
|
|
21
24
|
done <id> Mark task completed (id prefix)
|
|
@@ -32,14 +35,23 @@ Global options:
|
|
|
32
35
|
`;
|
|
33
36
|
const USAGE = {
|
|
34
37
|
add: `gtask add <title> [when] [-l <list>] [-n <notes>]
|
|
35
|
-
|
|
38
|
+
gtask add -clip [-l <list>]
|
|
39
|
+
gtask add "<freeform text>" [-l <list>]
|
|
40
|
+
|
|
41
|
+
Explicit mode: supply title and optional <when> (date-only; time is ignored
|
|
36
42
|
by Google Tasks). Wrap multi-word titles in quotes.
|
|
37
43
|
|
|
44
|
+
AI mode: with -clip the clipboard is parsed into one or more tasks via
|
|
45
|
+
Claude. A single quoted argument is parsed the same way. With no args
|
|
46
|
+
and no -clip, you'll be prompted to type the description.
|
|
47
|
+
|
|
38
48
|
Examples:
|
|
39
49
|
gtask add "Write report"
|
|
40
50
|
gtask add "Write report" friday
|
|
41
51
|
gtask add "Pay bills" "april 30" -n "rent + utilities"
|
|
42
52
|
gtask add "Call plumber" tomorrow -l Errands
|
|
53
|
+
gtask add -clip
|
|
54
|
+
gtask add "call dentist tomorrow, pick up rx friday"
|
|
43
55
|
`,
|
|
44
56
|
list: `gtask list [-l <list>] [-a] [-since <date>] [-till <date>]
|
|
45
57
|
List open tasks in a tasklist.
|
|
@@ -108,6 +120,7 @@ function parseArgs(argv) {
|
|
|
108
120
|
title: '',
|
|
109
121
|
when: '',
|
|
110
122
|
showAll: false,
|
|
123
|
+
clip: false,
|
|
111
124
|
help: false,
|
|
112
125
|
helpCmd: ''
|
|
113
126
|
};
|
|
@@ -145,6 +158,10 @@ function parseArgs(argv) {
|
|
|
145
158
|
case '--all':
|
|
146
159
|
result.showAll = true;
|
|
147
160
|
break;
|
|
161
|
+
case '-clip':
|
|
162
|
+
case '--clip':
|
|
163
|
+
result.clip = true;
|
|
164
|
+
break;
|
|
148
165
|
case '-h':
|
|
149
166
|
case '-help':
|
|
150
167
|
case '--help':
|
|
@@ -282,25 +299,78 @@ async function main() {
|
|
|
282
299
|
break;
|
|
283
300
|
}
|
|
284
301
|
case 'add': {
|
|
285
|
-
|
|
286
|
-
|
|
302
|
+
// Explicit mode: gtask add "title" [when]
|
|
303
|
+
if (parsed.args.length >= 2 && !parsed.clip) {
|
|
304
|
+
const [title, when] = parsed.args;
|
|
305
|
+
const task = { title };
|
|
306
|
+
if (when)
|
|
307
|
+
task.due = buildDue(when);
|
|
308
|
+
if (parsed.notes)
|
|
309
|
+
task.notes = parsed.notes;
|
|
310
|
+
const token = await getAccessToken(user, true);
|
|
311
|
+
const tl = await resolveTaskList(token, parsed.list);
|
|
312
|
+
const created = await createTask(token, task, tl.id);
|
|
313
|
+
console.log(`\nTask created in ${tl.title}: ${created.title}`);
|
|
314
|
+
if (created.due)
|
|
315
|
+
console.log(` Due: ${dueToYMD(created.due)}`);
|
|
316
|
+
if (created.notes)
|
|
317
|
+
console.log(` Notes: ${created.notes}`);
|
|
318
|
+
console.log(` ID: ${(created.id || '').slice(0, 8)}`);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
// AI mode: freeform text from clipboard, single arg, or prompt
|
|
322
|
+
let inputText;
|
|
323
|
+
if (parsed.clip) {
|
|
324
|
+
console.log('Reading from clipboard...');
|
|
325
|
+
inputText = readClipboard();
|
|
326
|
+
if (!inputText) {
|
|
327
|
+
console.error('Clipboard is empty');
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
console.log(`Clipboard: ${inputText.substring(0, 200)}${inputText.length > 200 ? '...' : ''}`);
|
|
331
|
+
}
|
|
332
|
+
else if (parsed.args.length === 1) {
|
|
333
|
+
inputText = parsed.args[0].trim();
|
|
334
|
+
if (!inputText) {
|
|
335
|
+
console.error('Task description is empty');
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
341
|
+
inputText = (await rl.question('Describe the task(s): ')).trim();
|
|
342
|
+
rl.close();
|
|
343
|
+
if (!inputText) {
|
|
344
|
+
console.error('No input provided');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
console.log('Extracting task details...');
|
|
349
|
+
const extracted = await extractTasksFromText(inputText);
|
|
350
|
+
if (extracted.length === 0) {
|
|
351
|
+
console.error('Failed to extract task details from text');
|
|
287
352
|
process.exit(1);
|
|
288
353
|
}
|
|
289
|
-
const [title, when] = parsed.args;
|
|
290
|
-
const task = { title };
|
|
291
|
-
if (when)
|
|
292
|
-
task.due = buildDue(when);
|
|
293
|
-
if (parsed.notes)
|
|
294
|
-
task.notes = parsed.notes;
|
|
295
354
|
const token = await getAccessToken(user, true);
|
|
296
355
|
const tl = await resolveTaskList(token, parsed.list);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
356
|
+
console.log(`\nCreating ${extracted.length} task(s) in ${tl.title}:\n`);
|
|
357
|
+
for (const e of extracted) {
|
|
358
|
+
if (!e.title) {
|
|
359
|
+
console.error(' AI returned task with no title — skipping');
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const task = { title: e.title };
|
|
363
|
+
if (e.due)
|
|
364
|
+
task.due = `${e.due}T00:00:00.000Z`;
|
|
365
|
+
if (e.notes)
|
|
366
|
+
task.notes = e.notes;
|
|
367
|
+
if (parsed.notes && !task.notes)
|
|
368
|
+
task.notes = parsed.notes;
|
|
369
|
+
const created = await createTask(token, task, tl.id);
|
|
370
|
+
console.log(` ${(created.id || '').slice(0, 8)} ${created.title}${created.due ? ` (due ${dueToYMD(created.due)})` : ''}`);
|
|
371
|
+
if (created.notes)
|
|
372
|
+
console.log(` Notes: ${created.notes}`);
|
|
373
|
+
}
|
|
304
374
|
break;
|
|
305
375
|
}
|
|
306
376
|
case 'done': {
|
|
@@ -421,7 +491,9 @@ async function main() {
|
|
|
421
491
|
}
|
|
422
492
|
}
|
|
423
493
|
if (import.meta.main) {
|
|
424
|
-
main()
|
|
494
|
+
main()
|
|
495
|
+
.then(() => process.exit(0))
|
|
496
|
+
.catch(e => {
|
|
425
497
|
console.error(`Error: ${e.message}`);
|
|
426
498
|
process.exit(1);
|
|
427
499
|
});
|