gws-axi 0.1.0 → 0.3.0
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 +41 -10
- package/dist/src/commands/calendar/create.d.ts +2 -0
- package/dist/src/commands/calendar/create.js +247 -0
- package/dist/src/commands/calendar/create.js.map +1 -0
- package/dist/src/commands/calendar/dateish.d.ts +42 -0
- package/dist/src/commands/calendar/dateish.js +86 -0
- package/dist/src/commands/calendar/dateish.js.map +1 -0
- package/dist/src/commands/calendar/delete.d.ts +2 -0
- package/dist/src/commands/calendar/delete.js +97 -0
- package/dist/src/commands/calendar/delete.js.map +1 -0
- package/dist/src/commands/calendar/events.js +3 -19
- package/dist/src/commands/calendar/events.js.map +1 -1
- package/dist/src/commands/calendar/freebusy.d.ts +2 -0
- package/dist/src/commands/calendar/freebusy.js +165 -0
- package/dist/src/commands/calendar/freebusy.js.map +1 -0
- package/dist/src/commands/calendar/get.js +12 -16
- package/dist/src/commands/calendar/get.js.map +1 -1
- package/dist/src/commands/calendar/respond.d.ts +2 -0
- package/dist/src/commands/calendar/respond.js +156 -0
- package/dist/src/commands/calendar/respond.js.map +1 -0
- package/dist/src/commands/calendar/search.d.ts +2 -0
- package/dist/src/commands/calendar/search.js +352 -0
- package/dist/src/commands/calendar/search.js.map +1 -0
- package/dist/src/commands/calendar/update.d.ts +2 -0
- package/dist/src/commands/calendar/update.js +279 -0
- package/dist/src/commands/calendar/update.js.map +1 -0
- package/dist/src/commands/calendar.js +12 -6
- package/dist/src/commands/calendar.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Agent-ergonomic CLI for Google Workspace — Gmail, Calendar, Docs, Drive, and S
|
|
|
4
4
|
|
|
5
5
|
Designed for use by AI agents. Every response is structured, every error names a specific fix, and write operations lock to the explicit account when multiple are authenticated — so two agents in parallel sessions can't silently touch the wrong mailbox.
|
|
6
6
|
|
|
7
|
-
> **Status
|
|
7
|
+
> **Status (v0.3):** **Calendar is complete** — all 9 subcommands (5 reads + 4 writes) are implemented. Gmail, Docs, Drive, and Slides are scaffolded stubs that exercise auth + account resolution but return `NOT_IMPLEMENTED` on their concrete operations. Auth, doctor (with live per-account API probes), and multi-account management are stable.
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
|
|
@@ -37,16 +37,46 @@ Typical flow: set project → enable APIs → create OAuth client → download J
|
|
|
37
37
|
|
|
38
38
|
Every subcommand supports `--help`. The bare `gws-axi` command prints current state, authenticated accounts, and top suggestions.
|
|
39
39
|
|
|
40
|
+
### Home / auth / health
|
|
41
|
+
|
|
40
42
|
```bash
|
|
41
|
-
gws-axi # home view
|
|
42
|
-
gws-axi calendar events # list upcoming events
|
|
43
|
-
gws-axi calendar events --query standup # full-text search
|
|
44
|
-
gws-axi calendar events --fields attendees,location
|
|
43
|
+
gws-axi # home view — current state + suggestions
|
|
45
44
|
gws-axi auth accounts # list authenticated Google accounts
|
|
46
|
-
gws-axi auth login --account <email> # add another account
|
|
47
|
-
gws-axi
|
|
45
|
+
gws-axi auth login --account <email> # add another account (prepare + --wait)
|
|
46
|
+
gws-axi auth use <email> # set the default account
|
|
47
|
+
gws-axi doctor # prerequisites + setup + live API probes
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
### Calendar reads
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
gws-axi calendar events # upcoming 7 days on primary
|
|
54
|
+
gws-axi calendar events --from 2026-04-20T00:00 --to 2026-04-27T00:00
|
|
55
|
+
gws-axi calendar events --fields attendees,location,status
|
|
56
|
+
gws-axi calendar get <event-id> # full detail + attendees
|
|
57
|
+
gws-axi calendar get <event-id> --full # don't truncate description
|
|
58
|
+
gws-axi calendar calendars # list calendars accessible to this account
|
|
59
|
+
gws-axi calendar search --query "standup" # primary calendar by default
|
|
60
|
+
gws-axi calendar search --query "chris" --include-shared # include delegated + subscribed
|
|
61
|
+
gws-axi calendar freebusy --calendars primary,team@jarv.us # cross-calendar availability
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Calendar writes
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
gws-axi calendar create \
|
|
68
|
+
--summary "Team sync" --start 2026-04-22T14:00 --duration 30m
|
|
69
|
+
|
|
70
|
+
gws-axi calendar update <event-id> \
|
|
71
|
+
--summary "Team sync (rescheduled)" --start 2026-04-22T15:00
|
|
72
|
+
|
|
73
|
+
gws-axi calendar delete <event-id> # idempotent (404/410 → noop)
|
|
74
|
+
|
|
75
|
+
gws-axi calendar respond <event-id> --response accepted
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Writes default `--send-updates` to `none` so agent-created events don't spam attendees. Pass `--send-updates all` for production-style invite behavior.
|
|
79
|
+
|
|
50
80
|
### Multi-account with write protection
|
|
51
81
|
|
|
52
82
|
gws-axi supports multiple Google accounts under one OAuth client. When two or more are authenticated, write operations require `--account <email>` explicitly — silent wrong-account mutations are impossible:
|
|
@@ -77,10 +107,11 @@ help[2]:
|
|
|
77
107
|
- [`docs/design.md`](https://github.com/JarvusInnovations/gws-axi/blob/main/docs/design.md) — architecture, auth model, doctor tiers, command surface
|
|
78
108
|
- [`docs/shared-client-future.md`](https://github.com/JarvusInnovations/gws-axi/blob/main/docs/shared-client-future.md) — why BYO-only for v1, what a shared-client public distribution would involve
|
|
79
109
|
|
|
80
|
-
## Known issues
|
|
110
|
+
## Known issues & roadmap
|
|
81
111
|
|
|
82
|
-
- **Testing-mode tokens expire every 7 days.** Google's OAuth policy. Re-run `gws-axi auth login --wait` when it happens. A future `gws-axi auth publish` helper will walk single-developer verification to eliminate this.
|
|
83
|
-
- **
|
|
112
|
+
- **Testing-mode tokens expire every 7 days.** Google's OAuth policy for unpublished apps. Re-run `gws-axi auth login --account <email>` + `auth login --wait` when it happens. A future `gws-axi auth publish` helper will walk single-developer verification to eliminate this.
|
|
113
|
+
- **Gmail, Docs, Drive, Slides**: not yet implemented. Stubs exercise auth + account resolution so write-protection errors still surface correctly; actual operations return `NOT_IMPLEMENTED`. Gmail is next.
|
|
114
|
+
- **Tests**: not yet — coming alongside Gmail.
|
|
84
115
|
|
|
85
116
|
## Contributing
|
|
86
117
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const CREATE_HELP = "usage: gws-axi calendar create --summary <text> --start <iso> [flags]\nflags[13]:\n --summary <text> REQUIRED \u2014 event title\n --start <iso> REQUIRED \u2014 event start (ISO datetime or YYYY-MM-DD for all-day)\n --end <iso> End time (default: start + 1h for timed, start + 1d for all-day)\n --duration <h|m> Alternative to --end, e.g. \"1h\", \"30m\", \"90m\", \"1h30m\"\n --all-day Treat --start as a date (not datetime); all-day event\n --description <text> Multi-line ok; pass via shell heredoc or quoted string\n --location <text> Freeform location string\n --attendees <emails> Comma-separated emails (no spaces). Invites sent per --send-updates\n --timezone <tz> IANA TZ (e.g. America/New_York) for timed events (default: calendar's tz)\n --recurrence <rrule> RFC 5545 RRULE (e.g. \"RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR\")\n --send-updates <mode> none | all | externalOnly (default: none \u2014 agent-safe)\n --calendar <id> Calendar to write to (default: primary)\n --account <email> REQUIRED when 2+ accounts are authenticated\nexamples:\n gws-axi calendar create --summary \"Team sync\" --start 2026-04-22T14:00 --duration 30m\n gws-axi calendar create --summary \"Holiday\" --start 2026-05-26 --all-day\n gws-axi calendar create --summary \"1:1\" --start 2026-04-22T10:00 --end 2026-04-22T10:30 --attendees alice@x.com,bob@x.com --send-updates all\nnotes:\n --send-updates defaults to \"none\" so agent-created events don't spam\n invited guests with notifications unless explicitly requested. Pass\n --send-updates all for production-style invite behavior.\noutput:\n Returns the newly-created event's key fields (id, summary, start, end,\n organizer, attendees count, htmlLink) plus an `action: created` line.\n";
|
|
2
|
+
export declare function calendarCreateCommand(account: string, args: string[]): Promise<string>;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { calendarClient, translateGoogleError } from "../../google/client.js";
|
|
3
|
+
import { joinBlocks, renderObject } from "../../output/index.js";
|
|
4
|
+
import { formatEventTime, parseDateishFlag } from "./dateish.js";
|
|
5
|
+
export const CREATE_HELP = `usage: gws-axi calendar create --summary <text> --start <iso> [flags]
|
|
6
|
+
flags[13]:
|
|
7
|
+
--summary <text> REQUIRED — event title
|
|
8
|
+
--start <iso> REQUIRED — event start (ISO datetime or YYYY-MM-DD for all-day)
|
|
9
|
+
--end <iso> End time (default: start + 1h for timed, start + 1d for all-day)
|
|
10
|
+
--duration <h|m> Alternative to --end, e.g. "1h", "30m", "90m", "1h30m"
|
|
11
|
+
--all-day Treat --start as a date (not datetime); all-day event
|
|
12
|
+
--description <text> Multi-line ok; pass via shell heredoc or quoted string
|
|
13
|
+
--location <text> Freeform location string
|
|
14
|
+
--attendees <emails> Comma-separated emails (no spaces). Invites sent per --send-updates
|
|
15
|
+
--timezone <tz> IANA TZ (e.g. America/New_York) for timed events (default: calendar's tz)
|
|
16
|
+
--recurrence <rrule> RFC 5545 RRULE (e.g. "RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR")
|
|
17
|
+
--send-updates <mode> none | all | externalOnly (default: none — agent-safe)
|
|
18
|
+
--calendar <id> Calendar to write to (default: primary)
|
|
19
|
+
--account <email> REQUIRED when 2+ accounts are authenticated
|
|
20
|
+
examples:
|
|
21
|
+
gws-axi calendar create --summary "Team sync" --start 2026-04-22T14:00 --duration 30m
|
|
22
|
+
gws-axi calendar create --summary "Holiday" --start 2026-05-26 --all-day
|
|
23
|
+
gws-axi calendar create --summary "1:1" --start 2026-04-22T10:00 --end 2026-04-22T10:30 --attendees alice@x.com,bob@x.com --send-updates all
|
|
24
|
+
notes:
|
|
25
|
+
--send-updates defaults to "none" so agent-created events don't spam
|
|
26
|
+
invited guests with notifications unless explicitly requested. Pass
|
|
27
|
+
--send-updates all for production-style invite behavior.
|
|
28
|
+
output:
|
|
29
|
+
Returns the newly-created event's key fields (id, summary, start, end,
|
|
30
|
+
organizer, attendees count, htmlLink) plus an \`action: created\` line.
|
|
31
|
+
`;
|
|
32
|
+
function parseDurationMs(value) {
|
|
33
|
+
// Accept forms like "90m", "1h", "1h30m", "45min"
|
|
34
|
+
const re = /^(?:(\d+)h)?(?:(\d+)(?:m|min)?)?$/i;
|
|
35
|
+
const m = value.trim().match(re);
|
|
36
|
+
if (!m || (!m[1] && !m[2])) {
|
|
37
|
+
throw new AxiError(`Cannot parse duration: ${value}`, "VALIDATION_ERROR", ["Use forms like 1h, 30m, 90m, or 1h30m"]);
|
|
38
|
+
}
|
|
39
|
+
const hours = parseInt(m[1] ?? "0", 10);
|
|
40
|
+
const minutes = parseInt(m[2] ?? "0", 10);
|
|
41
|
+
return (hours * 60 + minutes) * 60 * 1000;
|
|
42
|
+
}
|
|
43
|
+
function parseFlags(args) {
|
|
44
|
+
const flags = {
|
|
45
|
+
summary: undefined,
|
|
46
|
+
start: undefined,
|
|
47
|
+
end: undefined,
|
|
48
|
+
duration: undefined,
|
|
49
|
+
allDay: false,
|
|
50
|
+
description: undefined,
|
|
51
|
+
location: undefined,
|
|
52
|
+
attendees: [],
|
|
53
|
+
timezone: undefined,
|
|
54
|
+
recurrence: undefined,
|
|
55
|
+
sendUpdates: "none",
|
|
56
|
+
calendar: "primary",
|
|
57
|
+
};
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const arg = args[i];
|
|
60
|
+
const next = args[i + 1];
|
|
61
|
+
switch (arg) {
|
|
62
|
+
case "--summary":
|
|
63
|
+
flags.summary = next;
|
|
64
|
+
i++;
|
|
65
|
+
break;
|
|
66
|
+
case "--start":
|
|
67
|
+
flags.start = next;
|
|
68
|
+
i++;
|
|
69
|
+
break;
|
|
70
|
+
case "--end":
|
|
71
|
+
flags.end = next;
|
|
72
|
+
i++;
|
|
73
|
+
break;
|
|
74
|
+
case "--duration":
|
|
75
|
+
flags.duration = next;
|
|
76
|
+
i++;
|
|
77
|
+
break;
|
|
78
|
+
case "--all-day":
|
|
79
|
+
flags.allDay = true;
|
|
80
|
+
break;
|
|
81
|
+
case "--description":
|
|
82
|
+
flags.description = next;
|
|
83
|
+
i++;
|
|
84
|
+
break;
|
|
85
|
+
case "--location":
|
|
86
|
+
flags.location = next;
|
|
87
|
+
i++;
|
|
88
|
+
break;
|
|
89
|
+
case "--attendees":
|
|
90
|
+
flags.attendees = next.split(",").map((s) => s.trim()).filter(Boolean);
|
|
91
|
+
i++;
|
|
92
|
+
break;
|
|
93
|
+
case "--timezone":
|
|
94
|
+
flags.timezone = next;
|
|
95
|
+
i++;
|
|
96
|
+
break;
|
|
97
|
+
case "--recurrence":
|
|
98
|
+
flags.recurrence = next;
|
|
99
|
+
i++;
|
|
100
|
+
break;
|
|
101
|
+
case "--send-updates":
|
|
102
|
+
if (next === "all" || next === "externalOnly" || next === "none") {
|
|
103
|
+
flags.sendUpdates = next;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new AxiError(`Invalid --send-updates value: ${next}`, "VALIDATION_ERROR", ["Valid values: none, all, externalOnly"]);
|
|
107
|
+
}
|
|
108
|
+
i++;
|
|
109
|
+
break;
|
|
110
|
+
case "--calendar":
|
|
111
|
+
flags.calendar = next;
|
|
112
|
+
i++;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return flags;
|
|
117
|
+
}
|
|
118
|
+
/** Format a Date to `YYYY-MM-DD` in local time. */
|
|
119
|
+
function toDateOnly(d) {
|
|
120
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
121
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
122
|
+
}
|
|
123
|
+
function buildEventBody(flags) {
|
|
124
|
+
if (!flags.summary) {
|
|
125
|
+
throw new AxiError("--summary is required", "VALIDATION_ERROR", [
|
|
126
|
+
"Pass --summary 'Event title'",
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
if (!flags.start) {
|
|
130
|
+
throw new AxiError("--start is required", "VALIDATION_ERROR", [
|
|
131
|
+
"Pass --start 2026-04-22T14:00 (timed) or --start 2026-04-22 --all-day",
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
const body = {
|
|
135
|
+
summary: flags.summary,
|
|
136
|
+
};
|
|
137
|
+
if (flags.description !== undefined)
|
|
138
|
+
body.description = flags.description;
|
|
139
|
+
if (flags.location !== undefined)
|
|
140
|
+
body.location = flags.location;
|
|
141
|
+
if (flags.attendees.length > 0) {
|
|
142
|
+
body.attendees = flags.attendees.map((email) => ({ email }));
|
|
143
|
+
}
|
|
144
|
+
if (flags.recurrence) {
|
|
145
|
+
const line = flags.recurrence.startsWith("RRULE:")
|
|
146
|
+
? flags.recurrence
|
|
147
|
+
: `RRULE:${flags.recurrence}`;
|
|
148
|
+
body.recurrence = [line];
|
|
149
|
+
}
|
|
150
|
+
if (flags.allDay) {
|
|
151
|
+
// All-day events use the date field; Google expects exclusive end date,
|
|
152
|
+
// so default end = start + 1 day if neither --end nor --duration given.
|
|
153
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(flags.start)) {
|
|
154
|
+
throw new AxiError("--all-day requires --start in YYYY-MM-DD form", "VALIDATION_ERROR", [`Got: ${flags.start}`]);
|
|
155
|
+
}
|
|
156
|
+
body.start = { date: flags.start };
|
|
157
|
+
let endDate;
|
|
158
|
+
if (flags.end) {
|
|
159
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(flags.end)) {
|
|
160
|
+
throw new AxiError("--all-day requires --end in YYYY-MM-DD form", "VALIDATION_ERROR", [`Got: ${flags.end}`]);
|
|
161
|
+
}
|
|
162
|
+
endDate = flags.end;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Google's all-day end date is exclusive — default to start + 1 day
|
|
166
|
+
const d = new Date(`${flags.start}T00:00:00`);
|
|
167
|
+
d.setDate(d.getDate() + 1);
|
|
168
|
+
endDate = toDateOnly(d);
|
|
169
|
+
}
|
|
170
|
+
body.end = { date: endDate };
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Timed event: parse via parseDateishFlag (handles ISO, local, date-only)
|
|
174
|
+
const startIso = parseDateishFlag(flags.start);
|
|
175
|
+
let endIso;
|
|
176
|
+
if (flags.end) {
|
|
177
|
+
endIso = parseDateishFlag(flags.end);
|
|
178
|
+
}
|
|
179
|
+
else if (flags.duration) {
|
|
180
|
+
const ms = parseDurationMs(flags.duration);
|
|
181
|
+
endIso = new Date(new Date(startIso).getTime() + ms).toISOString();
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Default: 1 hour after start
|
|
185
|
+
endIso = new Date(new Date(startIso).getTime() + 60 * 60 * 1000).toISOString();
|
|
186
|
+
}
|
|
187
|
+
body.start = flags.timezone
|
|
188
|
+
? { dateTime: startIso, timeZone: flags.timezone }
|
|
189
|
+
: { dateTime: startIso };
|
|
190
|
+
body.end = flags.timezone
|
|
191
|
+
? { dateTime: endIso, timeZone: flags.timezone }
|
|
192
|
+
: { dateTime: endIso };
|
|
193
|
+
}
|
|
194
|
+
return body;
|
|
195
|
+
}
|
|
196
|
+
export async function calendarCreateCommand(account, args) {
|
|
197
|
+
const flags = parseFlags(args);
|
|
198
|
+
const body = buildEventBody(flags);
|
|
199
|
+
const api = await calendarClient(account);
|
|
200
|
+
let created;
|
|
201
|
+
try {
|
|
202
|
+
const res = await api.events.insert({
|
|
203
|
+
calendarId: flags.calendar,
|
|
204
|
+
requestBody: body,
|
|
205
|
+
sendUpdates: flags.sendUpdates,
|
|
206
|
+
});
|
|
207
|
+
created = res.data;
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
throw translateGoogleError(err, {
|
|
211
|
+
account,
|
|
212
|
+
operation: "calendar.events.insert",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const blocks = [];
|
|
216
|
+
blocks.push(renderObject({
|
|
217
|
+
action: "created",
|
|
218
|
+
account,
|
|
219
|
+
calendar: flags.calendar,
|
|
220
|
+
}));
|
|
221
|
+
const attendees = created.attendees ?? [];
|
|
222
|
+
const event = {
|
|
223
|
+
id: created.id ?? "",
|
|
224
|
+
summary: created.summary ?? "",
|
|
225
|
+
start: formatEventTime(created.start),
|
|
226
|
+
end: formatEventTime(created.end),
|
|
227
|
+
status: created.status ?? "",
|
|
228
|
+
organizer: created.organizer?.email ?? "",
|
|
229
|
+
attendee_count: attendees.length,
|
|
230
|
+
send_updates: flags.sendUpdates,
|
|
231
|
+
};
|
|
232
|
+
if (created.location)
|
|
233
|
+
event.location = created.location;
|
|
234
|
+
if (created.htmlLink)
|
|
235
|
+
event.html_link = created.htmlLink;
|
|
236
|
+
if (created.recurrence?.length)
|
|
237
|
+
event.recurrence = created.recurrence;
|
|
238
|
+
blocks.push(renderObject({ event }));
|
|
239
|
+
const suggestions = [];
|
|
240
|
+
suggestions.push(`Run \`gws-axi calendar get ${created.id} --calendar ${flags.calendar}\` to view full details`);
|
|
241
|
+
if (attendees.length > 0 && flags.sendUpdates === "none") {
|
|
242
|
+
suggestions.push(`Attendees were NOT notified (--send-updates none). Run \`calendar update ${created.id} --send-updates all\` to resend`);
|
|
243
|
+
}
|
|
244
|
+
blocks.push(renderObject({ help: suggestions }));
|
|
245
|
+
return joinBlocks(...blocks);
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/commands/calendar/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a date-ish flag value into an ISO 8601 string.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - Date-only (`YYYY-MM-DD`) → LOCAL midnight of that date, expressed as
|
|
6
|
+
* ISO. We deliberately do NOT use `new Date("YYYY-MM-DD")` because the
|
|
7
|
+
* JS spec parses that as UTC midnight (a well-known footgun), which
|
|
8
|
+
* contradicts our docs ("midnight local").
|
|
9
|
+
* - Datetime with offset (`YYYY-MM-DDTHH:MM:SS±HH:MM` or trailing `Z`)
|
|
10
|
+
* → parsed as-is, timezone preserved.
|
|
11
|
+
* - Datetime without offset (`YYYY-MM-DDTHH:MM`) → parsed as local time
|
|
12
|
+
* per the JS spec (which for this format does use local).
|
|
13
|
+
* - Anything else → AxiError with a clear format hint.
|
|
14
|
+
*
|
|
15
|
+
* Returns an ISO string (with timezone info) suitable for Google API
|
|
16
|
+
* `timeMin` / `timeMax` parameters.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseDateishFlag(value: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Format a Google Calendar dateTime/date field for detail-view output.
|
|
21
|
+
* Timed events: datetime + "(IANA tz)" suffix when timeZone is set.
|
|
22
|
+
* All-day events: "YYYY-MM-DD (all-day)".
|
|
23
|
+
* Missing/empty: empty string.
|
|
24
|
+
*
|
|
25
|
+
* Use for single-event detail views (get, create, update, respond).
|
|
26
|
+
* List views should stick to offset-only for compact columns.
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatEventTime(value: {
|
|
29
|
+
dateTime?: string | null;
|
|
30
|
+
date?: string | null;
|
|
31
|
+
timeZone?: string | null;
|
|
32
|
+
} | null | undefined): string;
|
|
33
|
+
/**
|
|
34
|
+
* Convert a UTC timestamp string (e.g. "2026-04-22T13:00:00Z") to an ISO
|
|
35
|
+
* string with the system's local timezone offset (e.g.
|
|
36
|
+
* "2026-04-22T09:00:00-04:00"). Useful for making freebusy output
|
|
37
|
+
* consistent with events output, which already comes back in
|
|
38
|
+
* offset-preserving form from Google.
|
|
39
|
+
*
|
|
40
|
+
* Invalid input returns the original string unchanged.
|
|
41
|
+
*/
|
|
42
|
+
export declare function toLocalOffsetISO(input: string): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a date-ish flag value into an ISO 8601 string.
|
|
4
|
+
*
|
|
5
|
+
* Rules:
|
|
6
|
+
* - Date-only (`YYYY-MM-DD`) → LOCAL midnight of that date, expressed as
|
|
7
|
+
* ISO. We deliberately do NOT use `new Date("YYYY-MM-DD")` because the
|
|
8
|
+
* JS spec parses that as UTC midnight (a well-known footgun), which
|
|
9
|
+
* contradicts our docs ("midnight local").
|
|
10
|
+
* - Datetime with offset (`YYYY-MM-DDTHH:MM:SS±HH:MM` or trailing `Z`)
|
|
11
|
+
* → parsed as-is, timezone preserved.
|
|
12
|
+
* - Datetime without offset (`YYYY-MM-DDTHH:MM`) → parsed as local time
|
|
13
|
+
* per the JS spec (which for this format does use local).
|
|
14
|
+
* - Anything else → AxiError with a clear format hint.
|
|
15
|
+
*
|
|
16
|
+
* Returns an ISO string (with timezone info) suitable for Google API
|
|
17
|
+
* `timeMin` / `timeMax` parameters.
|
|
18
|
+
*/
|
|
19
|
+
export function parseDateishFlag(value) {
|
|
20
|
+
if (!value) {
|
|
21
|
+
throw new AxiError("Missing date/time value", "VALIDATION_ERROR", ["Use ISO format: 2026-04-20T14:00 or just 2026-04-20"]);
|
|
22
|
+
}
|
|
23
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
24
|
+
// Date-only: force local midnight instead of the spec-mandated UTC
|
|
25
|
+
// midnight, matching our --help docs.
|
|
26
|
+
const [y, m, d] = value.split("-").map((part) => Number(part));
|
|
27
|
+
const local = new Date(y, m - 1, d, 0, 0, 0, 0);
|
|
28
|
+
if (Number.isNaN(local.getTime())) {
|
|
29
|
+
throw new AxiError(`Cannot parse date: ${value}`, "VALIDATION_ERROR", ["Use YYYY-MM-DD (e.g. 2026-04-20)"]);
|
|
30
|
+
}
|
|
31
|
+
return local.toISOString();
|
|
32
|
+
}
|
|
33
|
+
const parsed = new Date(value);
|
|
34
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
35
|
+
throw new AxiError(`Cannot parse date/time: ${value}`, "VALIDATION_ERROR", [
|
|
36
|
+
"Use ISO 8601 format: 2026-04-20T14:00:00-04:00",
|
|
37
|
+
"Local time: 2026-04-20T14:00 (no offset — interpreted as local)",
|
|
38
|
+
"Date-only: 2026-04-20 (midnight local)",
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
return parsed.toISOString();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Format a Google Calendar dateTime/date field for detail-view output.
|
|
45
|
+
* Timed events: datetime + "(IANA tz)" suffix when timeZone is set.
|
|
46
|
+
* All-day events: "YYYY-MM-DD (all-day)".
|
|
47
|
+
* Missing/empty: empty string.
|
|
48
|
+
*
|
|
49
|
+
* Use for single-event detail views (get, create, update, respond).
|
|
50
|
+
* List views should stick to offset-only for compact columns.
|
|
51
|
+
*/
|
|
52
|
+
export function formatEventTime(value) {
|
|
53
|
+
if (!value)
|
|
54
|
+
return "";
|
|
55
|
+
if (value.dateTime) {
|
|
56
|
+
return value.timeZone ? `${value.dateTime} (${value.timeZone})` : value.dateTime;
|
|
57
|
+
}
|
|
58
|
+
if (value.date)
|
|
59
|
+
return `${value.date} (all-day)`;
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert a UTC timestamp string (e.g. "2026-04-22T13:00:00Z") to an ISO
|
|
64
|
+
* string with the system's local timezone offset (e.g.
|
|
65
|
+
* "2026-04-22T09:00:00-04:00"). Useful for making freebusy output
|
|
66
|
+
* consistent with events output, which already comes back in
|
|
67
|
+
* offset-preserving form from Google.
|
|
68
|
+
*
|
|
69
|
+
* Invalid input returns the original string unchanged.
|
|
70
|
+
*/
|
|
71
|
+
export function toLocalOffsetISO(input) {
|
|
72
|
+
if (!input)
|
|
73
|
+
return input;
|
|
74
|
+
const d = new Date(input);
|
|
75
|
+
if (Number.isNaN(d.getTime()))
|
|
76
|
+
return input;
|
|
77
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
78
|
+
const offsetMin = -d.getTimezoneOffset();
|
|
79
|
+
const sign = offsetMin >= 0 ? "+" : "-";
|
|
80
|
+
const absMin = Math.abs(offsetMin);
|
|
81
|
+
const offsetStr = `${sign}${pad(Math.floor(absMin / 60))}:${pad(absMin % 60)}`;
|
|
82
|
+
return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
83
|
+
`T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` +
|
|
84
|
+
offsetStr);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=dateish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dateish.js","sourceRoot":"","sources":["../../../../src/commands/calendar/dateish.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const DELETE_HELP = "usage: gws-axi calendar delete <event-id> [flags]\nargs[1]:\n <event-id> Event ID to delete\nflags[4]:\n --calendar <id> Calendar containing the event (default: primary)\n --send-updates <mode> none | all | externalOnly (default: none)\n --account <email> REQUIRED when 2+ accounts are authenticated\n --yes Don't warn; just delete (no-op currently \u2014 writes\n are already explicit via --account + this command)\nexamples:\n gws-axi calendar delete abc_123\n gws-axi calendar delete abc_123 --send-updates all --calendar team@jarv.us\nnotes:\n Idempotent: deleting an already-deleted or non-existent event returns\n exit 0 with `action: noop` rather than an error. If you need a hard\n guarantee the event existed before deletion, run `calendar get` first.\n";
|
|
2
|
+
export declare function calendarDeleteCommand(account: string, args: string[]): Promise<string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { calendarClient, translateGoogleError } from "../../google/client.js";
|
|
3
|
+
import { renderObject } from "../../output/index.js";
|
|
4
|
+
export const DELETE_HELP = `usage: gws-axi calendar delete <event-id> [flags]
|
|
5
|
+
args[1]:
|
|
6
|
+
<event-id> Event ID to delete
|
|
7
|
+
flags[4]:
|
|
8
|
+
--calendar <id> Calendar containing the event (default: primary)
|
|
9
|
+
--send-updates <mode> none | all | externalOnly (default: none)
|
|
10
|
+
--account <email> REQUIRED when 2+ accounts are authenticated
|
|
11
|
+
--yes Don't warn; just delete (no-op currently — writes
|
|
12
|
+
are already explicit via --account + this command)
|
|
13
|
+
examples:
|
|
14
|
+
gws-axi calendar delete abc_123
|
|
15
|
+
gws-axi calendar delete abc_123 --send-updates all --calendar team@jarv.us
|
|
16
|
+
notes:
|
|
17
|
+
Idempotent: deleting an already-deleted or non-existent event returns
|
|
18
|
+
exit 0 with \`action: noop\` rather than an error. If you need a hard
|
|
19
|
+
guarantee the event existed before deletion, run \`calendar get\` first.
|
|
20
|
+
`;
|
|
21
|
+
function parseFlags(args) {
|
|
22
|
+
let eventId;
|
|
23
|
+
let calendar = "primary";
|
|
24
|
+
let sendUpdates = "none";
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i];
|
|
27
|
+
const next = args[i + 1];
|
|
28
|
+
switch (arg) {
|
|
29
|
+
case "--calendar":
|
|
30
|
+
calendar = next;
|
|
31
|
+
i++;
|
|
32
|
+
break;
|
|
33
|
+
case "--send-updates":
|
|
34
|
+
if (next === "all" || next === "externalOnly" || next === "none") {
|
|
35
|
+
sendUpdates = next;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new AxiError(`Invalid --send-updates value: ${next}`, "VALIDATION_ERROR", ["Valid values: none, all, externalOnly"]);
|
|
39
|
+
}
|
|
40
|
+
i++;
|
|
41
|
+
break;
|
|
42
|
+
case "--yes":
|
|
43
|
+
// no-op — reserved for future confirmation prompts
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
if (!arg.startsWith("--") && eventId === undefined) {
|
|
47
|
+
eventId = arg;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!eventId) {
|
|
52
|
+
throw new AxiError("Missing event ID argument", "VALIDATION_ERROR", [
|
|
53
|
+
"Usage: gws-axi calendar delete <event-id> [flags]",
|
|
54
|
+
"Get an ID from `gws-axi calendar events`",
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
return { eventId, calendar, sendUpdates };
|
|
58
|
+
}
|
|
59
|
+
export async function calendarDeleteCommand(account, args) {
|
|
60
|
+
const flags = parseFlags(args);
|
|
61
|
+
const api = await calendarClient(account);
|
|
62
|
+
try {
|
|
63
|
+
await api.events.delete({
|
|
64
|
+
calendarId: flags.calendar,
|
|
65
|
+
eventId: flags.eventId,
|
|
66
|
+
sendUpdates: flags.sendUpdates,
|
|
67
|
+
});
|
|
68
|
+
return renderObject({
|
|
69
|
+
action: "deleted",
|
|
70
|
+
account,
|
|
71
|
+
calendar: flags.calendar,
|
|
72
|
+
event_id: flags.eventId,
|
|
73
|
+
send_updates: flags.sendUpdates,
|
|
74
|
+
help: [
|
|
75
|
+
`Run \`gws-axi calendar events --calendar ${flags.calendar}\` to verify`,
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const translated = translateGoogleError(err, {
|
|
81
|
+
account,
|
|
82
|
+
operation: "calendar.events.delete",
|
|
83
|
+
});
|
|
84
|
+
if (translated.code === "NOT_FOUND" || translated.code === "GOOGLE_API_ERROR_410") {
|
|
85
|
+
// 404 or 410 (gone) — already deleted. Idempotent success.
|
|
86
|
+
return renderObject({
|
|
87
|
+
action: "noop",
|
|
88
|
+
account,
|
|
89
|
+
calendar: flags.calendar,
|
|
90
|
+
event_id: flags.eventId,
|
|
91
|
+
reason: "event was already deleted or never existed",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
throw translated;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../../../src/commands/calendar/delete.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
2
|
import { calendarClient, translateGoogleError } from "../../google/client.js";
|
|
3
3
|
import { field, mapEnum, pluck, renderListResponse, truncated, } from "../../output/index.js";
|
|
4
|
+
import { parseDateishFlag } from "./dateish.js";
|
|
4
5
|
export const EVENTS_HELP = `usage: gws-axi calendar events [flags]
|
|
5
6
|
flags[8]:
|
|
6
7
|
--calendar <id> Calendar to query (default: "primary")
|
|
@@ -50,11 +51,11 @@ function parseEventsFlags(args) {
|
|
|
50
51
|
i++;
|
|
51
52
|
break;
|
|
52
53
|
case "--from":
|
|
53
|
-
flags.from =
|
|
54
|
+
flags.from = parseDateishFlag(next);
|
|
54
55
|
i++;
|
|
55
56
|
break;
|
|
56
57
|
case "--to":
|
|
57
|
-
flags.to =
|
|
58
|
+
flags.to = parseDateishFlag(next);
|
|
58
59
|
i++;
|
|
59
60
|
break;
|
|
60
61
|
case "--limit":
|
|
@@ -78,23 +79,6 @@ function parseEventsFlags(args) {
|
|
|
78
79
|
}
|
|
79
80
|
return flags;
|
|
80
81
|
}
|
|
81
|
-
function parseDateish(value) {
|
|
82
|
-
if (!value) {
|
|
83
|
-
throw new AxiError("Missing date/time value", "VALIDATION_ERROR", ["Use ISO format: 2026-04-20T14:00 or just 2026-04-20"]);
|
|
84
|
-
}
|
|
85
|
-
// Accept ISO 8601 directly. If it parses, use the parsed form (normalizes
|
|
86
|
-
// time zone). If not, raise a clear error — we're deliberately not doing
|
|
87
|
-
// natural-language parsing in v1.
|
|
88
|
-
const parsed = new Date(value);
|
|
89
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
90
|
-
throw new AxiError(`Cannot parse date/time: ${value}`, "VALIDATION_ERROR", [
|
|
91
|
-
"Use ISO 8601 format: 2026-04-20T14:00:00-04:00",
|
|
92
|
-
"Or date-only: 2026-04-20",
|
|
93
|
-
"Natural-language dates (e.g. 'tomorrow 2pm') are not supported in v1",
|
|
94
|
-
]);
|
|
95
|
-
}
|
|
96
|
-
return parsed.toISOString();
|
|
97
|
-
}
|
|
98
82
|
function baseSchema() {
|
|
99
83
|
return [
|
|
100
84
|
field("id"),
|