@ncukondo/gcal-cli 0.2.2 → 0.2.3

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.
Files changed (2) hide show
  1. package/dist/index.js +148 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,25 +6,43 @@ var __getProtoOf = Object.getPrototypeOf;
6
6
  var __defProp = Object.defineProperty;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ function __accessProp(key) {
10
+ return this[key];
11
+ }
12
+ var __toESMCache_node;
13
+ var __toESMCache_esm;
9
14
  var __toESM = (mod, isNodeMode, target) => {
15
+ var canCache = mod != null && typeof mod === "object";
16
+ if (canCache) {
17
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
+ var cached = cache.get(mod);
19
+ if (cached)
20
+ return cached;
21
+ }
10
22
  target = mod != null ? __create(__getProtoOf(mod)) : {};
11
23
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
12
24
  for (let key of __getOwnPropNames(mod))
13
25
  if (!__hasOwnProp.call(to, key))
14
26
  __defProp(to, key, {
15
- get: () => mod[key],
27
+ get: __accessProp.bind(mod, key),
16
28
  enumerable: true
17
29
  });
30
+ if (canCache)
31
+ cache.set(mod, to);
18
32
  return to;
19
33
  };
20
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
35
+ var __returnValue = (v) => v;
36
+ function __exportSetter(name, newValue) {
37
+ this[name] = __returnValue.bind(null, newValue);
38
+ }
21
39
  var __export = (target, all) => {
22
40
  for (var name in all)
23
41
  __defProp(target, name, {
24
42
  get: all[name],
25
43
  enumerable: true,
26
44
  configurable: true,
27
- set: (newValue) => all[name] = () => newValue
45
+ set: __exportSetter.bind(all, name)
28
46
  });
29
47
  };
30
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
@@ -587718,7 +587736,7 @@ function errorCodeToExitCode(code) {
587718
587736
  // package.json
587719
587737
  var package_default = {
587720
587738
  name: "@ncukondo/gcal-cli",
587721
- version: "0.2.2",
587739
+ version: "0.2.3",
587722
587740
  type: "module",
587723
587741
  exports: {
587724
587742
  ".": "./dist/index.js"
@@ -588406,7 +588424,6 @@ function applyFilters(events, options) {
588406
588424
  const afterTransparency = filterByTransparency(events, options.transparency);
588407
588425
  return filterByStatus(afterTransparency, options);
588408
588426
  }
588409
-
588410
588427
  // node_modules/date-fns/locale/en-US/_lib/formatDistance.mjs
588411
588428
  var formatDistanceLocale = {
588412
588429
  lessThanXSeconds: {
@@ -590579,7 +590596,7 @@ function parseDateTimeInZone(dateStr, timezone) {
590579
590596
  // src/commands/search.ts
590580
590597
  var DEFAULT_SEARCH_DAYS = 30;
590581
590598
  async function handleSearch(opts) {
590582
- const { api: api2, query, format: format3, calendars, timezone, write } = opts;
590599
+ const { api: api2, query, format: format4, calendars, timezone, write } = opts;
590583
590600
  const writeErr = opts.writeErr ?? (() => {});
590584
590601
  const now = new Date;
590585
590602
  const days = opts.days ?? DEFAULT_SEARCH_DAYS;
@@ -590625,7 +590642,7 @@ async function handleSearch(opts) {
590625
590642
  if (opts.includeTentative !== undefined)
590626
590643
  filterOpts.includeTentative = opts.includeTentative;
590627
590644
  const filtered = applyFilters(allEvents, filterOpts);
590628
- if (format3 === "json") {
590645
+ if (format4 === "json") {
590629
590646
  write(formatJsonSuccess({
590630
590647
  query,
590631
590648
  events: filtered,
@@ -590656,10 +590673,10 @@ function createSearchCommand() {
590656
590673
 
590657
590674
  // src/commands/show.ts
590658
590675
  async function handleShow(opts) {
590659
- const { api: api2, eventId, calendarId, calendarName, format: format3, timezone, write } = opts;
590676
+ const { api: api2, eventId, calendarId, calendarName, format: format4, timezone, write } = opts;
590660
590677
  try {
590661
590678
  const event = await getEvent(api2, calendarId, calendarName, eventId, timezone);
590662
- if (format3 === "json") {
590679
+ if (format4 === "json") {
590663
590680
  write(formatJsonSuccess({ event }));
590664
590681
  } else {
590665
590682
  write(formatEventDetailText(event));
@@ -590667,7 +590684,7 @@ async function handleShow(opts) {
590667
590684
  return { exitCode: ExitCode.SUCCESS };
590668
590685
  } catch (error) {
590669
590686
  if (error instanceof ApiError) {
590670
- if (format3 === "json") {
590687
+ if (format4 === "json") {
590671
590688
  write(formatJsonError(error.code, error.message));
590672
590689
  } else {
590673
590690
  write(`Error: ${error.message}`);
@@ -591733,19 +591750,19 @@ function resolveDateRange(input, timezone, now = () => new Date) {
591733
591750
  }
591734
591751
  if (input.from) {
591735
591752
  const fromDate = parseDateTimeInZone(input.from, timezone);
591736
- const toDate3 = input.to ? addDays(parseDateTimeInZone(input.to, timezone), 1) : addDays(fromDate, 7);
591753
+ const toDate4 = input.to ? addDays(parseDateTimeInZone(input.to, timezone), 1) : addDays(fromDate, 7);
591737
591754
  return {
591738
591755
  timeMin: formatDateTimeInZone(fromDate, timezone),
591739
- timeMax: formatDateTimeInZone(toDate3, timezone)
591756
+ timeMax: formatDateTimeInZone(toDate4, timezone)
591740
591757
  };
591741
591758
  }
591742
591759
  if (input.to) {
591743
591760
  const todayStr2 = todayInZone(now(), timezone);
591744
591761
  const fromDate = parseDateTimeInZone(todayStr2, timezone);
591745
- const toDate3 = addDays(parseDateTimeInZone(input.to, timezone), 1);
591762
+ const toDate4 = addDays(parseDateTimeInZone(input.to, timezone), 1);
591746
591763
  return {
591747
591764
  timeMin: formatDateTimeInZone(fromDate, timezone),
591748
- timeMax: formatDateTimeInZone(toDate3, timezone),
591765
+ timeMax: formatDateTimeInZone(toDate4, timezone),
591749
591766
  warning: "--from not specified, defaulting to today"
591750
591767
  };
591751
591768
  }
@@ -591858,7 +591875,7 @@ function createListCommand() {
591858
591875
 
591859
591876
  // src/commands/update.ts
591860
591877
  async function handleUpdate(opts) {
591861
- const { api: api2, eventId, calendarId, calendarName, format: format3, timezone, write } = opts;
591878
+ const { api: api2, eventId, calendarId, calendarName, format: format4, timezone, write } = opts;
591862
591879
  const hasUpdate = opts.title !== undefined || opts.start !== undefined || opts.end !== undefined || opts.description !== undefined || opts.busy !== undefined || opts.free !== undefined;
591863
591880
  if (!hasUpdate) {
591864
591881
  throw new ApiError("INVALID_ARGS", "at least one update option must be provided");
@@ -591902,7 +591919,7 @@ async function handleUpdate(opts) {
591902
591919
  changes.start = withTime.start;
591903
591920
  if (withTime.end !== undefined)
591904
591921
  changes.end = withTime.end;
591905
- if (format3 === "json") {
591922
+ if (format4 === "json") {
591906
591923
  write(formatJsonSuccess({
591907
591924
  dry_run: true,
591908
591925
  action: "update",
@@ -591927,7 +591944,7 @@ async function handleUpdate(opts) {
591927
591944
  return { exitCode: ExitCode.SUCCESS };
591928
591945
  }
591929
591946
  const updated = await updateEvent(api2, calendarId, calendarName, eventId, input);
591930
- if (format3 === "json") {
591947
+ if (format4 === "json") {
591931
591948
  write(formatJsonSuccess({ event: updated }));
591932
591949
  } else {
591933
591950
  write(formatEventDetailText(updated));
@@ -591951,6 +591968,38 @@ function createUpdateCommand() {
591951
591968
  return cmd;
591952
591969
  }
591953
591970
 
591971
+ // src/lib/date-utils.ts
591972
+ var DATE_ONLY_RE2 = /^\d{4}-\d{2}-\d{2}$/;
591973
+ function isDateOnly(input) {
591974
+ if (!DATE_ONLY_RE2.test(input))
591975
+ return false;
591976
+ const [y, m, d] = input.split("-").map(Number);
591977
+ const date3 = new Date(Date.UTC(y, m - 1, d));
591978
+ return date3.getUTCFullYear() === y && date3.getUTCMonth() === m - 1 && date3.getUTCDate() === d;
591979
+ }
591980
+ function addDaysToDateString(dateStr, days) {
591981
+ const [y, m, d] = dateStr.split("-").map(Number);
591982
+ const date3 = new Date(Date.UTC(y, m - 1, d + days));
591983
+ return date3.toISOString().slice(0, 10);
591984
+ }
591985
+
591986
+ // src/lib/duration.ts
591987
+ var DURATION_RE = /^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?$/;
591988
+ function parseDuration(input) {
591989
+ const match2 = DURATION_RE.exec(input);
591990
+ if (!match2 || input === "") {
591991
+ throw new Error(`Invalid duration: "${input}". Use formats like 30m, 1h, 2d, 1h30m.`);
591992
+ }
591993
+ const days = Number(match2[1] || 0);
591994
+ const hours = Number(match2[2] || 0);
591995
+ const minutes = Number(match2[3] || 0);
591996
+ const ms = ((days * 24 + hours) * 60 + minutes) * 60 * 1000;
591997
+ if (ms === 0) {
591998
+ throw new Error("Duration must be greater than zero.");
591999
+ }
592000
+ return ms;
592001
+ }
592002
+
591954
592003
  // src/commands/add.ts
591955
592004
  async function handleAdd(options, deps) {
591956
592005
  if (!options.title) {
@@ -591961,10 +592010,18 @@ async function handleAdd(options, deps) {
591961
592010
  deps.write(formatJsonError("INVALID_ARGS", "--start is required"));
591962
592011
  return { exitCode: ExitCode.ARGUMENT };
591963
592012
  }
591964
- if (!options.end) {
591965
- deps.write(formatJsonError("INVALID_ARGS", "--end is required"));
592013
+ if (options.end && options.duration) {
592014
+ deps.write(formatJsonError("INVALID_ARGS", "--end and --duration cannot be used together"));
591966
592015
  return { exitCode: ExitCode.ARGUMENT };
591967
592016
  }
592017
+ const allDay = isDateOnly(options.start);
592018
+ if (options.end) {
592019
+ const endIsDateOnly = isDateOnly(options.end);
592020
+ if (allDay !== endIsDateOnly) {
592021
+ deps.write(formatJsonError("INVALID_ARGS", "--start and --end must be the same type (both date-only or both datetime)"));
592022
+ return { exitCode: ExitCode.ARGUMENT };
592023
+ }
592024
+ }
591968
592025
  const config2 = deps.loadConfig();
591969
592026
  const timezone = resolveTimezone(options.timezone, config2.timezone);
591970
592027
  const calendars = selectCalendars(options.calendar ? [options.calendar] : undefined, config2);
@@ -591976,20 +592033,54 @@ async function handleAdd(options, deps) {
591976
592033
  transparency = "transparent";
591977
592034
  let start;
591978
592035
  let end;
591979
- if (options.allDay) {
591980
- start = options.start.slice(0, 10);
591981
- end = options.end.slice(0, 10);
592036
+ if (allDay) {
592037
+ start = options.start;
592038
+ if (options.end) {
592039
+ end = addDaysToDateString(options.end, 1);
592040
+ } else if (options.duration) {
592041
+ let durationMs;
592042
+ try {
592043
+ durationMs = parseDuration(options.duration);
592044
+ } catch {
592045
+ deps.write(formatJsonError("INVALID_ARGS", `Invalid duration: "${options.duration}". Use formats like 30m, 1h, 2d, 1h30m.`));
592046
+ return { exitCode: ExitCode.ARGUMENT };
592047
+ }
592048
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
592049
+ if (durationMs % MS_PER_DAY !== 0) {
592050
+ deps.write(formatJsonError("INVALID_ARGS", "All-day events require day-unit duration (e.g. 1d, 2d). Sub-day durations like hours or minutes are not allowed."));
592051
+ return { exitCode: ExitCode.ARGUMENT };
592052
+ }
592053
+ const days = durationMs / MS_PER_DAY;
592054
+ end = addDaysToDateString(options.start, days);
592055
+ } else {
592056
+ end = addDaysToDateString(options.start, 1);
592057
+ }
591982
592058
  } else {
591983
592059
  const startDate = parseDateTimeInZone(options.start, timezone);
591984
- const endDate = parseDateTimeInZone(options.end, timezone);
591985
592060
  start = formatDateTimeInZone(startDate, timezone);
591986
- end = formatDateTimeInZone(endDate, timezone);
592061
+ if (options.end) {
592062
+ const endDate = parseDateTimeInZone(options.end, timezone);
592063
+ end = formatDateTimeInZone(endDate, timezone);
592064
+ } else if (options.duration) {
592065
+ let durationMs;
592066
+ try {
592067
+ durationMs = parseDuration(options.duration);
592068
+ } catch {
592069
+ deps.write(formatJsonError("INVALID_ARGS", `Invalid duration: "${options.duration}". Use formats like 30m, 1h, 2d, 1h30m.`));
592070
+ return { exitCode: ExitCode.ARGUMENT };
592071
+ }
592072
+ const endDate = new Date(startDate.getTime() + durationMs);
592073
+ end = formatDateTimeInZone(endDate, timezone);
592074
+ } else {
592075
+ const endDate = new Date(startDate.getTime() + 60 * 60 * 1000);
592076
+ end = formatDateTimeInZone(endDate, timezone);
592077
+ }
591987
592078
  }
591988
592079
  const input = {
591989
592080
  title: options.title,
591990
592081
  start,
591991
592082
  end,
591992
- allDay: options.allDay ?? false,
592083
+ allDay,
591993
592084
  timeZone: timezone,
591994
592085
  transparency
591995
592086
  };
@@ -592009,29 +592100,43 @@ ${detail}`);
592009
592100
  }
592010
592101
  function createAddCommand() {
592011
592102
  const cmd = new Command("add").description("Create a new event");
592012
- cmd.option("-t, --title <title>", "Event title");
592013
- cmd.option("-s, --start <datetime>", "Start datetime (ISO 8601)");
592014
- cmd.option("-e, --end <datetime>", "End datetime (ISO 8601)");
592015
- cmd.option("--all-day", "Create all-day event (use date only)");
592103
+ cmd.requiredOption("-t, --title <title>", "Event title");
592104
+ cmd.requiredOption("-s, --start <datetime>", "Start date or datetime. Date-only (YYYY-MM-DD) creates all-day event. Datetime (YYYY-MM-DDTHH:MM) creates timed event.");
592105
+ cmd.option("-e, --end <datetime>", "End date or datetime. Optional. Default: same day (all-day) or +1h (timed). All-day end is inclusive.");
592106
+ cmd.option("--duration <duration>", "Duration instead of --end (e.g. 30m, 1h, 2d, 1h30m). Mutually exclusive with --end.");
592016
592107
  cmd.option("-d, --description <text>", "Event description");
592017
592108
  cmd.option("--busy", "Mark as busy (default)");
592018
592109
  cmd.option("--free", "Mark as free (transparent)");
592110
+ const endOpt = cmd.options.find((o) => o.long === "--end");
592111
+ const durationOpt = cmd.options.find((o) => o.long === "--duration");
592112
+ endOpt.conflicts(["duration"]);
592113
+ durationOpt.conflicts(["end"]);
592019
592114
  const busyOpt = cmd.options.find((o) => o.long === "--busy");
592020
592115
  const freeOpt = cmd.options.find((o) => o.long === "--free");
592021
592116
  busyOpt.conflicts(["free"]);
592022
592117
  freeOpt.conflicts(["busy"]);
592118
+ cmd.addHelpText("after", `
592119
+ Examples:
592120
+ gcal add -t "Holiday" -s "2026-01-24" # All-day, 1 day
592121
+ gcal add -t "Vacation" -s "2026-01-24" -e "2026-01-26" # All-day, 3 days (inclusive)
592122
+ gcal add -t "Camp" -s "2026-01-24" --duration 2d # All-day, 2 days
592123
+ gcal add -t "Meeting" -s "2026-01-24T10:00" # Timed, 1h default
592124
+ gcal add -t "Meeting" -s "2026-01-24T10:00" -e "2026-01-24T11:30" # Timed, explicit end
592125
+ gcal add -t "Standup" -s "2026-01-24T10:00" --duration 30m # Timed, 30 min
592126
+ gcal add -t "Focus" -s "2026-01-24T09:00" --duration 2h --free # Timed, free
592127
+ `);
592023
592128
  return cmd;
592024
592129
  }
592025
592130
 
592026
592131
  // src/commands/delete.ts
592027
592132
  async function handleDelete(opts) {
592028
- const { api: api2, eventId, calendarId, format: format3, quiet, dryRun = false, write } = opts;
592133
+ const { api: api2, eventId, calendarId, format: format4, quiet, dryRun = false, write } = opts;
592029
592134
  if (!eventId) {
592030
592135
  write(formatJsonError("INVALID_ARGS", "event-id is required"));
592031
592136
  return { exitCode: ExitCode.ARGUMENT };
592032
592137
  }
592033
592138
  if (dryRun) {
592034
- if (format3 === "json") {
592139
+ if (format4 === "json") {
592035
592140
  write(formatJsonSuccess({
592036
592141
  dry_run: true,
592037
592142
  action: "delete",
@@ -592046,7 +592151,7 @@ async function handleDelete(opts) {
592046
592151
  try {
592047
592152
  await deleteEvent(api2, calendarId, eventId);
592048
592153
  if (!quiet) {
592049
- if (format3 === "json") {
592154
+ if (format4 === "json") {
592050
592155
  write(formatJsonSuccess({ deleted_id: eventId, message: "Event deleted" }));
592051
592156
  } else {
592052
592157
  write("Event deleted");
@@ -592055,7 +592160,7 @@ async function handleDelete(opts) {
592055
592160
  return { exitCode: ExitCode.SUCCESS };
592056
592161
  } catch (error) {
592057
592162
  if (error instanceof ApiError) {
592058
- if (format3 === "json") {
592163
+ if (format4 === "json") {
592059
592164
  write(formatJsonError(error.code, error.message));
592060
592165
  } else {
592061
592166
  write(`Error: ${error.message}`);
@@ -592081,13 +592186,13 @@ function mergeCalendarsWithConfig(apiCalendars, configCalendars) {
592081
592186
  });
592082
592187
  }
592083
592188
  async function handleCalendars(opts) {
592084
- const { api: api2, format: format3, quiet, write, configCalendars } = opts;
592189
+ const { api: api2, format: format4, quiet, write, configCalendars } = opts;
592085
592190
  let apiCalendars;
592086
592191
  try {
592087
592192
  apiCalendars = await listCalendars(api2);
592088
592193
  } catch (error) {
592089
592194
  if (error instanceof ApiError) {
592090
- if (format3 === "json") {
592195
+ if (format4 === "json") {
592091
592196
  write(formatJsonError(error.code, error.message));
592092
592197
  } else {
592093
592198
  write(error.message);
@@ -592102,7 +592207,7 @@ async function handleCalendars(opts) {
592102
592207
  `));
592103
592208
  return { exitCode: ExitCode.SUCCESS };
592104
592209
  }
592105
- if (format3 === "json") {
592210
+ if (format4 === "json") {
592106
592211
  write(formatJsonSuccess({ calendars }));
592107
592212
  } else {
592108
592213
  write(formatCalendarListText(calendars));
@@ -592119,12 +592224,12 @@ function resolveTimezone2(cliTimezone) {
592119
592224
  return cliTimezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
592120
592225
  }
592121
592226
  async function handleInit(opts) {
592122
- const { fs, format: format3, quiet, write, force, all, local, requestAuth } = opts;
592227
+ const { fs, format: format4, quiet, write, force, all, local, requestAuth } = opts;
592123
592228
  const configPath = local ? `${process.cwd()}/gcal-cli.toml` : getDefaultConfigPath();
592124
592229
  if (!force && fs.existsSync(configPath)) {
592125
592230
  const msg = `Config file already exists: ${configPath}
592126
592231
  Use --force to overwrite.`;
592127
- if (format3 === "json") {
592232
+ if (format4 === "json") {
592128
592233
  write(formatJsonError("CONFIG_ERROR", msg));
592129
592234
  } else {
592130
592235
  write(msg);
@@ -592140,7 +592245,7 @@ Use --force to overwrite.`;
592140
592245
  calendars = await opts.listCalendars();
592141
592246
  } else if (isAuthRequiredError(error)) {
592142
592247
  const msg = "Not authenticated. Run `gcal auth` to authenticate.";
592143
- if (format3 === "json") {
592248
+ if (format4 === "json") {
592144
592249
  write(formatJsonError("AUTH_REQUIRED", msg));
592145
592250
  } else {
592146
592251
  write(msg);
@@ -592152,7 +592257,7 @@ Use --force to overwrite.`;
592152
592257
  }
592153
592258
  if (calendars.length === 0) {
592154
592259
  const msg = "No calendars found in Google Calendar.";
592155
- if (format3 === "json") {
592260
+ if (format4 === "json") {
592156
592261
  write(formatJsonError("API_ERROR", msg));
592157
592262
  } else {
592158
592263
  write(msg);
@@ -592174,7 +592279,7 @@ Use --force to overwrite.`;
592174
592279
  write(configPath);
592175
592280
  return { exitCode: ExitCode.SUCCESS };
592176
592281
  }
592177
- if (format3 === "json") {
592282
+ if (format4 === "json") {
592178
592283
  write(formatJsonSuccess({
592179
592284
  path: configPath,
592180
592285
  timezone,
@@ -592293,10 +592398,10 @@ function getErrorCode2(error) {
592293
592398
  }
592294
592399
  return "API_ERROR";
592295
592400
  }
592296
- function handleError2(error, format3) {
592401
+ function handleError2(error, format4) {
592297
592402
  const errorCode = getErrorCode2(error);
592298
592403
  const message2 = error instanceof Error ? error.message : String(error);
592299
- if (format3 === "json") {
592404
+ if (format4 === "json") {
592300
592405
  process.stderr.write(formatJsonError(errorCode, message2));
592301
592406
  } else {
592302
592407
  process.stderr.write(`Error: ${message2}
@@ -592551,7 +592656,7 @@ ${url}`);
592551
592656
  title: addOpts.title,
592552
592657
  start: addOpts.start,
592553
592658
  end: addOpts.end,
592554
- allDay: addOpts.allDay,
592659
+ duration: addOpts.duration,
592555
592660
  description: addOpts.description,
592556
592661
  busy: addOpts.busy,
592557
592662
  free: addOpts.free,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ncukondo/gcal-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"