@bobfrankston/gcal 0.1.46 → 0.1.47
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 +150 -96
- package/gcal.js +115 -115
- package/gcal.js.map +1 -1
- package/gcal.ts +124 -130
- package/glib/goauth.d.ts +13 -0
- package/glib/goauth.d.ts.map +1 -0
- package/glib/goauth.js +70 -0
- package/glib/goauth.js.map +1 -0
- package/glib/goauth.ts +93 -0
- package/glib/tasksapi.d.ts +23 -0
- package/glib/tasksapi.d.ts.map +1 -0
- package/glib/tasksapi.js +113 -0
- package/glib/tasksapi.js.map +1 -0
- package/glib/tasksapi.ts +155 -0
- package/glib/tasktypes.d.ts +47 -0
- package/glib/tasktypes.d.ts.map +1 -0
- package/glib/tasktypes.js +6 -0
- package/glib/tasktypes.js.map +1 -0
- package/glib/tasktypes.ts +51 -0
- package/gtask.d.ts +7 -0
- package/gtask.d.ts.map +1 -0
- package/gtask.js +429 -0
- package/gtask.js.map +1 -0
- package/gtask.ts +459 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,96 +1,150 @@
|
|
|
1
|
-
# @bobfrankston/gcal
|
|
2
|
-
|
|
3
|
-
Google Calendar CLI
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g @bobfrankston/gcal
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
|
28
|
-
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
| `-
|
|
48
|
-
| `-
|
|
49
|
-
| `-
|
|
50
|
-
| `-
|
|
51
|
-
| `-
|
|
52
|
-
| `-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
gcal
|
|
62
|
-
gcal
|
|
63
|
-
gcal
|
|
64
|
-
gcal
|
|
65
|
-
gcal add "
|
|
66
|
-
gcal add "
|
|
67
|
-
gcal add
|
|
68
|
-
gcal add
|
|
69
|
-
gcal
|
|
70
|
-
gcal
|
|
71
|
-
gcal resched abc12345
|
|
72
|
-
gcal snooze abc12345
|
|
73
|
-
gcal
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1
|
+
# @bobfrankston/gcal
|
|
2
|
+
|
|
3
|
+
Google Calendar **and** Google Tasks CLI tools. Two binaries (`gcal`, `gtask`) shipped together — they share OAuth credentials, scopes, and token files, so you authenticate once.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @bobfrankston/gcal
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Provides:
|
|
12
|
+
- `gcal` — Google Calendar
|
|
13
|
+
- `gtask` — Google Tasks
|
|
14
|
+
|
|
15
|
+
Both share OAuth client credentials with `gcards` (`%APPDATA%\gcards\credentials.json`).
|
|
16
|
+
|
|
17
|
+
## gcal — Google Calendar
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gcal <file.ics> # Import ICS file (file association)
|
|
21
|
+
gcal <command> [options]
|
|
22
|
+
gcal help <command> # Detailed help for one command
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---------|-------------|
|
|
29
|
+
| `list [n]` | List upcoming n events (default: 10) |
|
|
30
|
+
| `add <title> <when> [duration]` | Add event (explicit) |
|
|
31
|
+
| `add "<free text>"` | Add event (AI-parsed) |
|
|
32
|
+
| `add -clip` | Add event from clipboard (AI-parsed) |
|
|
33
|
+
| `add` | Add event interactively |
|
|
34
|
+
| `del \| delete <id> [id2...]` | Delete event(s) by ID prefix |
|
|
35
|
+
| `remind <id> <dur> [dur2...]` | Add reminder(s) |
|
|
36
|
+
| `resched <id> <when> [duration]` | Reschedule (preserves duration) |
|
|
37
|
+
| `snooze <id> [when]` | Snooze (default `+1d`) |
|
|
38
|
+
| `import <file.ics>` | Import events from ICS |
|
|
39
|
+
| `calendars` | List available calendars |
|
|
40
|
+
| `assoc` | Set up `.ics` file association (Windows) |
|
|
41
|
+
| `help [command]` | Show help |
|
|
42
|
+
|
|
43
|
+
### Options
|
|
44
|
+
|
|
45
|
+
| Flag | Description |
|
|
46
|
+
|------|-------------|
|
|
47
|
+
| `-u`, `-user <email>` | Set / use default Google account |
|
|
48
|
+
| `-c`, `-calendar <id>` | Calendar ID (default: primary) |
|
|
49
|
+
| `-n <count>` | Number of events to list |
|
|
50
|
+
| `-v`, `-verbose` | Show event IDs and links |
|
|
51
|
+
| `-b`, `-birthdays` | Include birthday events |
|
|
52
|
+
| `-clip` | Read from clipboard (for `add`) |
|
|
53
|
+
| `-r`, `-reminder <dur>` | Add popup reminder (e.g. `30m`, `1h`); repeatable |
|
|
54
|
+
| `-since <date>` | Start listing from `<date>` |
|
|
55
|
+
| `-till <date>` | End listing at `<date>` |
|
|
56
|
+
| `-all` | Delete all instances of recurring event |
|
|
57
|
+
|
|
58
|
+
### Examples
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gcal meeting.ics
|
|
62
|
+
gcal list
|
|
63
|
+
gcal list -since "10 days ago"
|
|
64
|
+
gcal list -since "april 1" -till "may 1"
|
|
65
|
+
gcal add "Dentist" "Friday 3pm" "1h"
|
|
66
|
+
gcal add "Lunch" "1/14/2026 12:00" "1h"
|
|
67
|
+
gcal add "Dentist appointment Friday 3pm for 1 hour"
|
|
68
|
+
gcal add -clip
|
|
69
|
+
gcal add "Dentist" "Friday 3pm" -r 30m
|
|
70
|
+
gcal remind abc12345 30m
|
|
71
|
+
gcal resched abc12345 "next friday 3pm"
|
|
72
|
+
gcal snooze abc12345 +1w
|
|
73
|
+
gcal -u bob@gmail.com
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Reschedule / snooze notes
|
|
77
|
+
|
|
78
|
+
`resched` and `snooze` find events up to 30 days in the past by default (so stale reminders remain findable). Widen with `-since <date>`. For timed events, if `<when>` lacks a time-of-day (e.g. `tomorrow`), the original time is preserved. All-day events stay all-day. Relative offsets `+1d` / `+1w` / `+1h` / `+1m` advance from the event's current start.
|
|
79
|
+
|
|
80
|
+
### Windows file association
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
gcal assoc # Sets up .ics → gcal
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
On first run, gcal offers to set this up. Run `gcal assoc` anytime to (re)configure.
|
|
87
|
+
|
|
88
|
+
## gtask — Google Tasks
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
gtask <command> [options]
|
|
92
|
+
gtask help <command>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Commands
|
|
96
|
+
|
|
97
|
+
| Command | Description |
|
|
98
|
+
|---------|-------------|
|
|
99
|
+
| `add <title> [when]` | Add a task (optional due date — date-only) |
|
|
100
|
+
| `list` | List open tasks |
|
|
101
|
+
| `lists` | List all tasklists |
|
|
102
|
+
| `done <id>` | Mark task completed |
|
|
103
|
+
| `undone <id>` | Reopen a completed task |
|
|
104
|
+
| `del <id>` | Delete a task |
|
|
105
|
+
| `edit <id> [-t title] [-when date] [-n notes]` | Update fields |
|
|
106
|
+
| `clear` | Remove all completed tasks from list |
|
|
107
|
+
| `move <id> -l <list>` | Move task to another tasklist |
|
|
108
|
+
| `help [command]` | Show help |
|
|
109
|
+
|
|
110
|
+
### Options
|
|
111
|
+
|
|
112
|
+
| Flag | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `-u`, `-user <email>` | Google account |
|
|
115
|
+
| `-l`, `-list <name\|id>` | Tasklist (default: primary) |
|
|
116
|
+
| `-n`, `-notes <text>` | Notes for `add` / `edit` |
|
|
117
|
+
| `-t`, `-title <text>` | New title for `edit` |
|
|
118
|
+
| `-when <date>` | New due date for `edit` |
|
|
119
|
+
| `-a`, `-all` | Include completed tasks in `list` |
|
|
120
|
+
|
|
121
|
+
### Examples
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
gtask add "Write report"
|
|
125
|
+
gtask add "Write report" friday
|
|
126
|
+
gtask add "Pay bills" "april 30" -n "rent + utilities"
|
|
127
|
+
gtask add "Call plumber" tomorrow -l Errands
|
|
128
|
+
gtask list
|
|
129
|
+
gtask list -l Errands
|
|
130
|
+
gtask list -a
|
|
131
|
+
gtask done abc12345
|
|
132
|
+
gtask edit abc12345 -when "next monday"
|
|
133
|
+
gtask move abc12345 -l Personal
|
|
134
|
+
gtask clear -l Errands
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Notes on Google Tasks
|
|
138
|
+
|
|
139
|
+
- **Due dates are date-only.** The Tasks API stores RFC3339 timestamps, but the Google UI ignores time-of-day. `gtask` writes midnight UTC.
|
|
140
|
+
- **No reminders.** Tasks have no notification mechanism in the API. Reminders only appear if you create the task through the Calendar UI's task-with-time feature.
|
|
141
|
+
- **No recurrence.** Recurring tasks aren't exposed via the API.
|
|
142
|
+
- **Hierarchy is flat.** One level of subtasks via `move?parent=`. Not currently surfaced by `gtask`.
|
|
143
|
+
|
|
144
|
+
## Shared OAuth
|
|
145
|
+
|
|
146
|
+
Both tools request the combined scope set `calendar + tasks` (read or write depending on operation). The first time you run either tool after upgrading, Google prompts once for the new combined consent; afterward both share `token.json` / `token-write.json`.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
package/gcal.js
CHANGED
|
@@ -11,65 +11,12 @@ import fs from 'fs';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { execSync } from 'child_process';
|
|
13
13
|
import { createInterface } from 'readline/promises';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { loadConfig, saveConfig, formatDateTime, formatDuration, parseDuration, parseDateTime, hasTimeComponent, parseAllDay, formatYMD, normalizeUser } from './glib/gutils.js';
|
|
15
|
+
import { setupAbortHandler, getAccessToken, apiFetch } from './glib/goauth.js';
|
|
16
16
|
import { extractEventsFromText, readClipboard } from './glib/aihelper.js';
|
|
17
17
|
import pkg from './package.json' with { type: 'json' };
|
|
18
18
|
const VERSION = pkg.version;
|
|
19
19
|
const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3';
|
|
20
|
-
const CALENDAR_SCOPE_READ = 'https://www.googleapis.com/auth/calendar.readonly';
|
|
21
|
-
const CALENDAR_SCOPE_WRITE = 'https://www.googleapis.com/auth/calendar';
|
|
22
|
-
let abortController = null;
|
|
23
|
-
function setupAbortHandler() {
|
|
24
|
-
abortController = new AbortController();
|
|
25
|
-
let ctrlCCount = 0;
|
|
26
|
-
process.on('SIGINT', () => {
|
|
27
|
-
ctrlCCount++;
|
|
28
|
-
abortController?.abort();
|
|
29
|
-
if (ctrlCCount >= 2) {
|
|
30
|
-
console.log('\n\nForce exit.');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
console.log('\n\nCtrl+C pressed - aborting... (press again to force exit)');
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
async function getAccessToken(user, writeAccess = false, forceRefresh = false) {
|
|
37
|
-
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
38
|
-
console.error(`\nCredentials file not found: ${CREDENTIALS_FILE}\n`);
|
|
39
|
-
console.error(`gcal uses the same credentials as gcards.`);
|
|
40
|
-
console.error(`Make sure gcards is set up with OAuth credentials first.`);
|
|
41
|
-
console.error(`See: https://github.com/BobFrankston/oauthsupport/blob/master/SETUP-GOOGLE-OAUTH.md`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
const paths = getUserPaths(user);
|
|
45
|
-
ensureUserDir(user);
|
|
46
|
-
const scope = writeAccess ? CALENDAR_SCOPE_WRITE : CALENDAR_SCOPE_READ;
|
|
47
|
-
const tokenFileName = writeAccess ? 'token-write.json' : 'token.json';
|
|
48
|
-
const tokenFilePath = path.join(paths.userDir, tokenFileName);
|
|
49
|
-
if (forceRefresh && fs.existsSync(tokenFilePath)) {
|
|
50
|
-
fs.unlinkSync(tokenFilePath);
|
|
51
|
-
console.log(`${ts()} Token expired, refreshing...`);
|
|
52
|
-
}
|
|
53
|
-
const token = await authenticateOAuth(CREDENTIALS_FILE, {
|
|
54
|
-
scope,
|
|
55
|
-
tokenDirectory: paths.userDir,
|
|
56
|
-
tokenFileName,
|
|
57
|
-
credentialsKey: 'installed',
|
|
58
|
-
signal: abortController?.signal
|
|
59
|
-
});
|
|
60
|
-
if (!token) {
|
|
61
|
-
throw new Error('OAuth authentication failed');
|
|
62
|
-
}
|
|
63
|
-
return token.access_token;
|
|
64
|
-
}
|
|
65
|
-
async function apiFetch(url, accessToken, options = {}) {
|
|
66
|
-
const headers = {
|
|
67
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
...options.headers
|
|
70
|
-
};
|
|
71
|
-
return fetch(url, { ...options, headers });
|
|
72
|
-
}
|
|
73
20
|
async function listCalendars(accessToken) {
|
|
74
21
|
const url = `${CALENDAR_API_BASE}/users/me/calendarList`;
|
|
75
22
|
const res = await apiFetch(url, accessToken);
|
|
@@ -197,68 +144,117 @@ async function importIcsFile(filePath, accessToken, calendarId = 'primary') {
|
|
|
197
144
|
}
|
|
198
145
|
return result;
|
|
199
146
|
}
|
|
200
|
-
|
|
201
|
-
console.log(`
|
|
202
|
-
gcal v${VERSION} - Google Calendar CLI
|
|
147
|
+
const USAGE_SUMMARY = `gcal v${VERSION} - Google Calendar CLI
|
|
203
148
|
|
|
204
|
-
Usage:
|
|
205
|
-
|
|
206
|
-
|
|
149
|
+
Usage: gcal <file.ics> Import ICS file (file association)
|
|
150
|
+
gcal <command> [options] Run command
|
|
151
|
+
gcal help <command> Detailed help for a command
|
|
207
152
|
|
|
208
153
|
Commands:
|
|
209
|
-
list
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
154
|
+
list List upcoming events
|
|
155
|
+
add Add event (explicit, AI, or interactive)
|
|
156
|
+
del | delete Delete event(s) by ID
|
|
157
|
+
remind Add reminder(s) to existing event
|
|
158
|
+
resched Reschedule event
|
|
159
|
+
snooze Snooze event (default: +1d)
|
|
160
|
+
import Import events from ICS file
|
|
161
|
+
calendars List available calendars
|
|
162
|
+
assoc Set up .ics file association (Windows)
|
|
163
|
+
help [command] Show help
|
|
164
|
+
|
|
165
|
+
Global options:
|
|
166
|
+
-u, -user <email> Set / use default Google account
|
|
167
|
+
-c, -calendar <id> Calendar ID (default: primary)
|
|
168
|
+
|
|
169
|
+
Companion tool: gtask (Google Tasks - shares OAuth with gcal)
|
|
170
|
+
`;
|
|
171
|
+
const USAGE = {
|
|
172
|
+
list: `gcal list [n] [-since <date>] [-till <date>] [-c <calendar>] [-b] [-v]
|
|
173
|
+
List upcoming events. Default n=10.
|
|
174
|
+
-since <date> Start from date (past dates allowed)
|
|
175
|
+
-till <date> End at date
|
|
176
|
+
-b Include birthday events (hidden by default)
|
|
177
|
+
-v Verbose (show full IDs and links)
|
|
178
|
+
|
|
179
|
+
Examples:
|
|
180
|
+
gcal list
|
|
181
|
+
gcal list 20
|
|
182
|
+
gcal list -since "10 days ago"
|
|
183
|
+
gcal list -since "april 1" -till "may 1"
|
|
184
|
+
gcal list -since "april 1" -n 50
|
|
185
|
+
`,
|
|
186
|
+
add: `gcal add <title> <when> [duration] Explicit
|
|
187
|
+
gcal add "<free text>" AI-parsed single arg
|
|
188
|
+
gcal add -clip AI-parsed from clipboard
|
|
189
|
+
gcal add Interactive (type description)
|
|
190
|
+
Add a calendar event. Default duration 1h. Use -r <dur> to add reminder(s).
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
gcal add "Dentist" "Friday 3pm" "1h"
|
|
194
|
+
gcal add "Lunch" "1/14/2026 12:00" "1h"
|
|
195
|
+
gcal add "Meeting" "tomorrow 10:00"
|
|
196
|
+
gcal add "Appointment" "jan 15 2pm"
|
|
197
|
+
gcal add "Dentist appointment Friday 3pm for 1 hour"
|
|
198
|
+
gcal add -clip
|
|
199
|
+
gcal add "Dentist" "Friday 3pm" -r 30m
|
|
200
|
+
`,
|
|
201
|
+
del: `gcal del <id> [id2...] [-all] [-b]
|
|
202
|
+
gcal delete <id> [id2...]
|
|
203
|
+
Delete event(s) by ID prefix.
|
|
204
|
+
-all Delete entire recurring series (not just instance)
|
|
205
|
+
-b Allow deletion of birthday events
|
|
206
|
+
`,
|
|
207
|
+
delete: `gcal delete <id> [id2...] [-all]
|
|
208
|
+
Alias for "del".
|
|
209
|
+
`,
|
|
210
|
+
remind: `gcal remind <id> <duration> [duration2...]
|
|
211
|
+
Add popup reminder(s) to an existing event.
|
|
225
212
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
-till <date> End listing at <date>
|
|
236
|
-
-all Delete all instances of recurring event
|
|
213
|
+
Examples:
|
|
214
|
+
gcal remind abc12345 30m
|
|
215
|
+
gcal remind abc12345 30m 1h
|
|
216
|
+
`,
|
|
217
|
+
resched: `gcal resched <id> <when> [duration]
|
|
218
|
+
Reschedule an event. Preserves duration unless [duration] given.
|
|
219
|
+
If <when> lacks a time-of-day, the original time is preserved.
|
|
220
|
+
All-day events stay all-day. Searches up to 30 days back by default
|
|
221
|
+
(widen with -since).
|
|
237
222
|
|
|
238
|
-
Examples:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
gcal add "Lunch" "1/14/2026 12:00" "1h"
|
|
246
|
-
gcal add "Meeting" "tomorrow 10:00"
|
|
247
|
-
gcal add "Appointment" "jan 15 2pm"
|
|
248
|
-
gcal add "Dentist appointment Friday 3pm for 1 hour"
|
|
249
|
-
gcal add -clip Add from clipboard text
|
|
250
|
-
gcal add "Dentist" "Friday 3pm" -r 30m Add with 30-min reminder
|
|
251
|
-
gcal remind abc12345 30m Add 30-min reminder to event
|
|
252
|
-
gcal resched abc12345 "next friday 3pm" Reschedule to new date/time
|
|
253
|
-
gcal resched abc12345 tomorrow Move to tomorrow (preserve time-of-day)
|
|
254
|
-
gcal snooze abc12345 Snooze event +1 day
|
|
255
|
-
gcal snooze abc12345 +1w Snooze event +1 week
|
|
256
|
-
gcal add Type event description (one or multiple)
|
|
257
|
-
gcal -u bob@gmail.com Set default user
|
|
223
|
+
Examples:
|
|
224
|
+
gcal resched abc12345 "next friday 3pm"
|
|
225
|
+
gcal resched abc12345 tomorrow
|
|
226
|
+
gcal resched abc12345 +1w
|
|
227
|
+
`,
|
|
228
|
+
snooze: `gcal snooze <id> [when]
|
|
229
|
+
Like resched, but defaults to +1d if no <when> given.
|
|
258
230
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
231
|
+
Examples:
|
|
232
|
+
gcal snooze abc12345
|
|
233
|
+
gcal snooze abc12345 +1w
|
|
234
|
+
`,
|
|
235
|
+
import: `gcal import <file.ics>
|
|
236
|
+
gcal <file.ics> (via file association)
|
|
237
|
+
Import events from an iCalendar file.
|
|
238
|
+
`,
|
|
239
|
+
calendars: `gcal calendars
|
|
240
|
+
List available calendars (id, name, access role).
|
|
241
|
+
`,
|
|
242
|
+
assoc: `gcal assoc
|
|
243
|
+
Set up Windows .ics file association so double-clicking imports to gcal.
|
|
244
|
+
`,
|
|
245
|
+
help: `gcal help [command]
|
|
246
|
+
Show summary, or detailed help for a single command.
|
|
247
|
+
`
|
|
248
|
+
};
|
|
249
|
+
function showUsage(cmd) {
|
|
250
|
+
if (cmd && USAGE[cmd]) {
|
|
251
|
+
console.log(USAGE[cmd]);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (cmd) {
|
|
255
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
256
|
+
}
|
|
257
|
+
console.log(USAGE_SUMMARY);
|
|
262
258
|
}
|
|
263
259
|
function parseArgs(argv) {
|
|
264
260
|
const result = {
|
|
@@ -273,7 +269,8 @@ function parseArgs(argv) {
|
|
|
273
269
|
birthdays: false,
|
|
274
270
|
clip: false,
|
|
275
271
|
all: false,
|
|
276
|
-
reminders: []
|
|
272
|
+
reminders: [],
|
|
273
|
+
helpCmd: ''
|
|
277
274
|
};
|
|
278
275
|
const unknown = [];
|
|
279
276
|
let i = 0;
|
|
@@ -346,7 +343,6 @@ function parseArgs(argv) {
|
|
|
346
343
|
case '-h':
|
|
347
344
|
case '-help':
|
|
348
345
|
case '--help':
|
|
349
|
-
case 'help':
|
|
350
346
|
result.help = true;
|
|
351
347
|
break;
|
|
352
348
|
case '-V':
|
|
@@ -378,6 +374,10 @@ function parseArgs(argv) {
|
|
|
378
374
|
console.error(`Unknown options: ${unknown.join(', ')}`);
|
|
379
375
|
process.exit(1);
|
|
380
376
|
}
|
|
377
|
+
if (result.command === 'help') {
|
|
378
|
+
result.help = true;
|
|
379
|
+
result.helpCmd = result.args[0] || '';
|
|
380
|
+
}
|
|
381
381
|
return result;
|
|
382
382
|
}
|
|
383
383
|
function buildReminders(minutes) {
|
|
@@ -444,8 +444,8 @@ async function main() {
|
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
446
|
if (parsed.help) {
|
|
447
|
-
showUsage();
|
|
448
|
-
if (process.platform === 'win32' && !checkIcsAssoc()) {
|
|
447
|
+
showUsage(parsed.helpCmd);
|
|
448
|
+
if (!parsed.helpCmd && process.platform === 'win32' && !checkIcsAssoc()) {
|
|
449
449
|
console.log('Note: .ics file association not set. Run "gcal assoc" to set it up.');
|
|
450
450
|
}
|
|
451
451
|
process.exit(0);
|