@ncukondo/gcal-cli 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/index.js +835 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -587754,7 +587754,7 @@ function errorCodeToExitCode(code) {
587754
587754
  // package.json
587755
587755
  var package_default = {
587756
587756
  name: "@ncukondo/gcal-cli",
587757
- version: "0.4.0",
587757
+ version: "0.5.0",
587758
587758
  type: "module",
587759
587759
  exports: {
587760
587760
  ".": "./dist/index.js"
@@ -588022,6 +588022,7 @@ async function getAuthenticatedClient(fs, fetchFn = globalThis.fetch) {
588022
588022
  var OAUTH_SCOPES = [
588023
588023
  "https://www.googleapis.com/auth/calendar.readonly",
588024
588024
  "https://www.googleapis.com/auth/calendar.events",
588025
+ "https://www.googleapis.com/auth/tasks",
588025
588026
  "email"
588026
588027
  ];
588027
588028
  async function startOAuthFlow(credentials, fs, fetchFn = globalThis.fetch) {
@@ -588221,6 +588222,24 @@ function createAuthCommand() {
588221
588222
  return cmd;
588222
588223
  }
588223
588224
 
588225
+ // src/lib/api-utils.ts
588226
+ var MAX_PAGES = 100;
588227
+ function isGoogleApiError(error) {
588228
+ return error instanceof Error && "code" in error && typeof error.code === "number";
588229
+ }
588230
+ function mapApiError(error) {
588231
+ if (isGoogleApiError(error)) {
588232
+ if (error.code === 401 || error.code === 403) {
588233
+ throw new ApiError("AUTH_REQUIRED", error.message);
588234
+ }
588235
+ if (error.code === 404) {
588236
+ throw new ApiError("NOT_FOUND", error.message);
588237
+ }
588238
+ throw new ApiError("API_ERROR", error.message);
588239
+ }
588240
+ throw error;
588241
+ }
588242
+
588224
588243
  // src/lib/api.ts
588225
588244
  class ApiError extends Error {
588226
588245
  code;
@@ -588230,7 +588249,6 @@ class ApiError extends Error {
588230
588249
  this.name = "ApiError";
588231
588250
  }
588232
588251
  }
588233
- var MAX_PAGES = 100;
588234
588252
  var EventStatusSchema = _enum(["confirmed", "tentative", "cancelled"]).catch("confirmed");
588235
588253
  var TransparencySchema = _enum(["opaque", "transparent"]).catch("opaque");
588236
588254
  function normalizeEvent(event, calendarId, calendarName) {
@@ -588394,24 +588412,9 @@ async function deleteEvent(api2, calendarId, eventId) {
588394
588412
  mapApiError(error);
588395
588413
  }
588396
588414
  }
588397
- function isGoogleApiError(error) {
588398
- return error instanceof Error && "code" in error && typeof error.code === "number";
588399
- }
588400
588415
  function isAuthRequiredError(error) {
588401
588416
  return (error instanceof ApiError || error instanceof AuthError) && (error.code === "AUTH_REQUIRED" || error.code === "AUTH_EXPIRED");
588402
588417
  }
588403
- function mapApiError(error) {
588404
- if (isGoogleApiError(error)) {
588405
- if (error.code === 401 || error.code === 403) {
588406
- throw new ApiError("AUTH_REQUIRED", error.message);
588407
- }
588408
- if (error.code === 404) {
588409
- throw new ApiError("NOT_FOUND", error.message);
588410
- }
588411
- throw new ApiError("API_ERROR", error.message);
588412
- }
588413
- throw error;
588414
- }
588415
588418
 
588416
588419
  // src/lib/filter.ts
588417
588420
  function filterByTransparency(events, option) {
@@ -588452,6 +588455,47 @@ var fsAdapter = {
588452
588455
  function collect(value, previous) {
588453
588456
  return [...previous, value];
588454
588457
  }
588458
+ function createGoogleTasksClient(tasks) {
588459
+ return {
588460
+ tasklists: {
588461
+ list: async (p) => {
588462
+ const res = await tasks.tasklists.list(p);
588463
+ const data = {};
588464
+ if (res.data.items)
588465
+ data.items = res.data.items;
588466
+ if (res.data.nextPageToken)
588467
+ data.nextPageToken = res.data.nextPageToken;
588468
+ return { data };
588469
+ }
588470
+ },
588471
+ tasks: {
588472
+ list: async (p) => {
588473
+ const res = await tasks.tasks.list(p);
588474
+ const data = {};
588475
+ if (res.data.items)
588476
+ data.items = res.data.items;
588477
+ if (res.data.nextPageToken)
588478
+ data.nextPageToken = res.data.nextPageToken;
588479
+ return { data };
588480
+ },
588481
+ get: async (p) => {
588482
+ const res = await tasks.tasks.get(p);
588483
+ return { data: res.data };
588484
+ },
588485
+ insert: async (p) => {
588486
+ const res = await tasks.tasks.insert(p);
588487
+ return { data: res.data };
588488
+ },
588489
+ patch: async (p) => {
588490
+ const res = await tasks.tasks.patch(p);
588491
+ return { data: res.data };
588492
+ },
588493
+ delete: async (p) => {
588494
+ await tasks.tasks.delete(p);
588495
+ }
588496
+ }
588497
+ };
588498
+ }
588455
588499
  function createGoogleCalendarApi(calendar) {
588456
588500
  return {
588457
588501
  calendarList: {
@@ -590895,11 +590939,16 @@ function skipComment(str, ptr) {
590895
590939
  }
590896
590940
  function skipVoid(str, ptr, banNewLines, banComments) {
590897
590941
  let c;
590898
- while ((c = str[ptr]) === " " || c === "\t" || !banNewLines && (c === `
590942
+ while (true) {
590943
+ while ((c = str[ptr]) === " " || c === "\t" || !banNewLines && (c === `
590899
590944
  ` || c === "\r" && str[ptr + 1] === `
590900
590945
  `))
590901
- ptr++;
590902
- return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
590946
+ ptr++;
590947
+ if (banComments || c !== "#")
590948
+ break;
590949
+ ptr = skipComment(str, ptr);
590950
+ }
590951
+ return ptr;
590903
590952
  }
590904
590953
  function skipUntil(str, ptr, sep, end, banNewLines = false) {
590905
590954
  if (!end) {
@@ -591731,6 +591780,11 @@ function parseConfig(toml) {
591731
591780
  name: String(c["name"]),
591732
591781
  enabled: Boolean(c["enabled"])
591733
591782
  })) : [];
591783
+ const task_lists = Array.isArray(raw["task_lists"]) ? raw["task_lists"].map((t) => ({
591784
+ id: String(t["id"]),
591785
+ name: String(t["name"]),
591786
+ enabled: Boolean(t["enabled"])
591787
+ })) : [];
591734
591788
  const envFormat = process.env["GCAL_CLI_FORMAT"];
591735
591789
  const envTimezone = process.env["GCAL_CLI_TIMEZONE"];
591736
591790
  const fileFormat = typeof raw["default_format"] === "string" ? validateOutputFormat(raw["default_format"], "config file") : "text";
@@ -591738,7 +591792,8 @@ function parseConfig(toml) {
591738
591792
  const timezone = envTimezone || fileTimezone;
591739
591793
  const config2 = {
591740
591794
  default_format: envFormat ? validateOutputFormat(envFormat, "GCAL_CLI_FORMAT env var") : fileFormat,
591741
- calendars
591795
+ calendars,
591796
+ task_lists
591742
591797
  };
591743
591798
  if (timezone) {
591744
591799
  config2.timezone = timezone;
@@ -591774,7 +591829,7 @@ function getDefaultConfigPath() {
591774
591829
  function escapeTomlString(value) {
591775
591830
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
591776
591831
  }
591777
- function generateConfigToml(calendars, timezone) {
591832
+ function generateConfigToml(calendars, timezone, taskLists) {
591778
591833
  const lines = [];
591779
591834
  if (timezone) {
591780
591835
  lines.push(`timezone = "${escapeTomlString(timezone)}"`);
@@ -591787,6 +591842,15 @@ function generateConfigToml(calendars, timezone) {
591787
591842
  lines.push(`enabled = ${String(cal.enabled)}`);
591788
591843
  lines.push("");
591789
591844
  }
591845
+ if (taskLists && taskLists.length > 0) {
591846
+ for (const tl of taskLists) {
591847
+ lines.push("[[task_lists]]");
591848
+ lines.push(`id = "${escapeTomlString(tl.id)}"`);
591849
+ lines.push(`name = "${escapeTomlString(tl.name)}"`);
591850
+ lines.push(`enabled = ${String(tl.enabled)}`);
591851
+ lines.push("");
591852
+ }
591853
+ }
591790
591854
  return lines.join(`
591791
591855
  `);
591792
591856
  }
@@ -592459,6 +592523,7 @@ function resolveTimezone2(cliTimezone) {
592459
592523
  }
592460
592524
  async function handleInit(opts) {
592461
592525
  const { fs, format: format4, quiet, write, force, all, local, requestAuth } = opts;
592526
+ const writeErr = quiet ? () => {} : opts.writeErr ?? (() => {});
592462
592527
  const configPath = local ? `${process.cwd()}/gcal-cli.toml` : getDefaultConfigPath();
592463
592528
  if (!force && fs.existsSync(configPath)) {
592464
592529
  const msg = `Config file already exists: ${configPath}
@@ -592503,18 +592568,32 @@ Use --force to overwrite.`;
592503
592568
  name: cal.name,
592504
592569
  enabled: all || cal.primary
592505
592570
  }));
592571
+ let configTaskLists = [];
592572
+ if (opts.listTaskLists) {
592573
+ try {
592574
+ const taskLists = await opts.listTaskLists();
592575
+ configTaskLists = taskLists.map((tl) => ({
592576
+ id: tl.id,
592577
+ name: tl.title,
592578
+ enabled: all || tl.id === "@default"
592579
+ }));
592580
+ } catch {
592581
+ writeErr("[init] Tasks API unavailable, skipping task lists");
592582
+ }
592583
+ }
592506
592584
  const timezone = resolveTimezone2(opts.timezone);
592507
- const toml = generateConfigToml(configCalendars, timezone);
592585
+ const toml = generateConfigToml(configCalendars, timezone, configTaskLists.length > 0 ? configTaskLists : undefined);
592508
592586
  const dir = path.dirname(configPath);
592509
592587
  fs.mkdirSync(dir, { recursive: true });
592510
592588
  fs.writeFileSync(configPath, toml);
592511
592589
  const enabledCalendars = configCalendars.filter((c) => c.enabled);
592590
+ const enabledTaskLists = configTaskLists.filter((t) => t.enabled);
592512
592591
  if (quiet) {
592513
592592
  write(configPath);
592514
592593
  return { exitCode: ExitCode.SUCCESS };
592515
592594
  }
592516
592595
  if (format4 === "json") {
592517
- write(formatJsonSuccess({
592596
+ const data = {
592518
592597
  path: configPath,
592519
592598
  timezone,
592520
592599
  calendars: configCalendars.map((c) => ({
@@ -592524,7 +592603,15 @@ Use --force to overwrite.`;
592524
592603
  })),
592525
592604
  enabled_count: enabledCalendars.length,
592526
592605
  total_count: configCalendars.length
592527
- }));
592606
+ };
592607
+ if (configTaskLists.length > 0) {
592608
+ data.task_lists = configTaskLists.map((t) => ({
592609
+ id: t.id,
592610
+ name: t.name,
592611
+ enabled: t.enabled
592612
+ }));
592613
+ }
592614
+ write(formatJsonSuccess(data));
592528
592615
  } else {
592529
592616
  write(`Config file created: ${configPath}`);
592530
592617
  write("");
@@ -592532,6 +592619,13 @@ Use --force to overwrite.`;
592532
592619
  for (const cal of enabledCalendars) {
592533
592620
  write(` - ${cal.name} (${cal.id})`);
592534
592621
  }
592622
+ if (enabledTaskLists.length > 0) {
592623
+ write("");
592624
+ write("Enabled task lists:");
592625
+ for (const tl of enabledTaskLists) {
592626
+ write(` - ${tl.name} (${tl.id})`);
592627
+ }
592628
+ }
592535
592629
  write("");
592536
592630
  write(`Timezone: ${timezone}`);
592537
592631
  }
@@ -592546,6 +592640,553 @@ function createInitCommand() {
592546
592640
  return cmd;
592547
592641
  }
592548
592642
 
592643
+ // src/commands/tasks/index.ts
592644
+ function createTasksCommand() {
592645
+ const tasksCmd = new Command("tasks").description("Manage Google Tasks");
592646
+ const listsCmd = new Command("lists").description("List task lists");
592647
+ tasksCmd.addCommand(listsCmd);
592648
+ const listCmd = new Command("list").description("List tasks").option("-l, --list <name-or-id>", "Task list name or ID").option("--all", "Include completed tasks").option("--completed", "Show only completed tasks").option("--due-before <date>", "Tasks due before date (YYYY-MM-DD)").option("--due-after <date>", "Tasks due after date (YYYY-MM-DD)");
592649
+ tasksCmd.addCommand(listCmd);
592650
+ const showCmd = new Command("show").description("Show task details").argument("<task-id>", "Task ID").option("-l, --list <name-or-id>", "Task list name or ID");
592651
+ tasksCmd.addCommand(showCmd);
592652
+ const addCmd = new Command("add").description("Create a new task").requiredOption("-t, --title <title>", "Task title").option("-n, --notes <text>", "Notes").option("--due <date>", "Due date (YYYY-MM-DD)").option("-l, --list <name-or-id>", "Task list name or ID").option("--parent <task-id>", "Parent task ID (create as subtask)");
592653
+ tasksCmd.addCommand(addCmd);
592654
+ const updateCmd = new Command("update").description("Update an existing task").argument("<task-id>", "Task ID to update").option("-t, --title <title>", "New title").option("-n, --notes <text>", "New notes").option("--due <date>", "New due date (YYYY-MM-DD)").option("-l, --list <name-or-id>", "Task list name or ID");
592655
+ tasksCmd.addCommand(updateCmd);
592656
+ const doneCmd = new Command("done").description("Mark a task as completed").argument("<task-id>", "Task ID to complete").option("-l, --list <name-or-id>", "Task list name or ID");
592657
+ tasksCmd.addCommand(doneCmd);
592658
+ const undoneCmd = new Command("undone").description("Mark a task as not completed").argument("<task-id>", "Task ID to reopen").option("-l, --list <name-or-id>", "Task list name or ID");
592659
+ tasksCmd.addCommand(undoneCmd);
592660
+ const deleteCmd = new Command("delete").description("Delete a task").argument("<task-id>", "Task ID to delete").option("-l, --list <name-or-id>", "Task list name or ID");
592661
+ tasksCmd.addCommand(deleteCmd);
592662
+ return { tasksCmd, listsCmd, listCmd, showCmd, addCmd, updateCmd, doneCmd, undoneCmd, deleteCmd };
592663
+ }
592664
+
592665
+ // src/lib/tasks-api.ts
592666
+ function normalizeTaskList(raw) {
592667
+ return {
592668
+ id: raw.id ?? "",
592669
+ title: raw.title ?? "",
592670
+ updated: raw.updated ?? ""
592671
+ };
592672
+ }
592673
+ var VALID_TASK_STATUSES = ["needsAction", "completed"];
592674
+ function parseTaskStatus(value) {
592675
+ if (value && VALID_TASK_STATUSES.includes(value)) {
592676
+ return value;
592677
+ }
592678
+ return "needsAction";
592679
+ }
592680
+ function parseDueDate(due) {
592681
+ if (!due)
592682
+ return null;
592683
+ return due.slice(0, 10);
592684
+ }
592685
+ function normalizeTask(raw, listId, listTitle) {
592686
+ return {
592687
+ id: raw.id ?? "",
592688
+ title: raw.title ?? "",
592689
+ notes: raw.notes ?? null,
592690
+ status: parseTaskStatus(raw.status),
592691
+ due: parseDueDate(raw.due),
592692
+ completed: raw.completed ?? null,
592693
+ list_id: listId,
592694
+ list_title: listTitle,
592695
+ parent: raw.parent ?? null,
592696
+ updated: raw.updated ?? ""
592697
+ };
592698
+ }
592699
+ async function listTaskLists(client) {
592700
+ try {
592701
+ const results = [];
592702
+ let pageToken;
592703
+ let pages = 0;
592704
+ do {
592705
+ if (pages >= MAX_PAGES) {
592706
+ throw new ApiError("API_ERROR", `Pagination limit of ${MAX_PAGES} pages exceeded`);
592707
+ }
592708
+ const response = await client.tasklists.list(pageToken ? { pageToken } : undefined);
592709
+ const items = response.data.items ?? [];
592710
+ for (const item of items) {
592711
+ results.push(normalizeTaskList(item));
592712
+ }
592713
+ pageToken = response.data.nextPageToken;
592714
+ pages++;
592715
+ } while (pageToken);
592716
+ return results;
592717
+ } catch (error) {
592718
+ mapApiError(error);
592719
+ }
592720
+ }
592721
+ async function listTasks(client, taskListId, listTitle, options) {
592722
+ try {
592723
+ const results = [];
592724
+ let pageToken;
592725
+ let pages = 0;
592726
+ do {
592727
+ if (pages >= MAX_PAGES) {
592728
+ throw new ApiError("API_ERROR", `Pagination limit of ${MAX_PAGES} pages exceeded`);
592729
+ }
592730
+ const params = {
592731
+ tasklist: taskListId,
592732
+ ...options
592733
+ };
592734
+ if (pageToken) {
592735
+ params.pageToken = pageToken;
592736
+ }
592737
+ const response = await client.tasks.list(params);
592738
+ const items = response.data.items ?? [];
592739
+ for (const item of items) {
592740
+ results.push(normalizeTask(item, taskListId, listTitle));
592741
+ }
592742
+ pageToken = response.data.nextPageToken;
592743
+ pages++;
592744
+ } while (pageToken);
592745
+ return results;
592746
+ } catch (error) {
592747
+ mapApiError(error);
592748
+ }
592749
+ }
592750
+ async function getTask(client, taskListId, listTitle, taskId) {
592751
+ try {
592752
+ const response = await client.tasks.get({ tasklist: taskListId, task: taskId });
592753
+ return normalizeTask(response.data, taskListId, listTitle);
592754
+ } catch (error) {
592755
+ mapApiError(error);
592756
+ }
592757
+ }
592758
+ async function createTask(client, taskListId, listTitle, input) {
592759
+ try {
592760
+ const requestBody = {
592761
+ title: input.title
592762
+ };
592763
+ if (input.notes !== undefined) {
592764
+ requestBody.notes = input.notes;
592765
+ }
592766
+ if (input.due !== undefined) {
592767
+ requestBody.due = input.due;
592768
+ }
592769
+ const params = { tasklist: taskListId, requestBody };
592770
+ if (input.parent !== undefined) {
592771
+ params.parent = input.parent;
592772
+ }
592773
+ const response = await client.tasks.insert(params);
592774
+ return normalizeTask(response.data, taskListId, listTitle);
592775
+ } catch (error) {
592776
+ mapApiError(error);
592777
+ }
592778
+ }
592779
+ async function updateTask(client, taskListId, listTitle, taskId, input) {
592780
+ try {
592781
+ const requestBody = {};
592782
+ if (input.title !== undefined) {
592783
+ requestBody.title = input.title;
592784
+ }
592785
+ if (input.notes !== undefined) {
592786
+ requestBody.notes = input.notes;
592787
+ }
592788
+ if (input.due !== undefined) {
592789
+ requestBody.due = input.due;
592790
+ }
592791
+ const response = await client.tasks.patch({
592792
+ tasklist: taskListId,
592793
+ task: taskId,
592794
+ requestBody
592795
+ });
592796
+ return normalizeTask(response.data, taskListId, listTitle);
592797
+ } catch (error) {
592798
+ mapApiError(error);
592799
+ }
592800
+ }
592801
+ async function deleteTask(client, taskListId, taskId) {
592802
+ try {
592803
+ await client.tasks.delete({ tasklist: taskListId, task: taskId });
592804
+ } catch (error) {
592805
+ mapApiError(error);
592806
+ }
592807
+ }
592808
+ async function completeTask(client, taskListId, listTitle, taskId) {
592809
+ try {
592810
+ const response = await client.tasks.patch({
592811
+ tasklist: taskListId,
592812
+ task: taskId,
592813
+ requestBody: { status: "completed" }
592814
+ });
592815
+ return normalizeTask(response.data, taskListId, listTitle);
592816
+ } catch (error) {
592817
+ mapApiError(error);
592818
+ }
592819
+ }
592820
+ async function uncompleteTask(client, taskListId, listTitle, taskId) {
592821
+ try {
592822
+ const response = await client.tasks.patch({
592823
+ tasklist: taskListId,
592824
+ task: taskId,
592825
+ requestBody: { status: "needsAction", completed: null }
592826
+ });
592827
+ return normalizeTask(response.data, taskListId, listTitle);
592828
+ } catch (error) {
592829
+ mapApiError(error);
592830
+ }
592831
+ }
592832
+
592833
+ // src/commands/tasks/lists.ts
592834
+ function mergeTaskListsWithConfig(apiLists, configLists) {
592835
+ const hasConfig = configLists.length > 0;
592836
+ const configMap = new Map(configLists.map((c) => [c.id, c]));
592837
+ return apiLists.map((list) => {
592838
+ const config2 = configMap.get(list.id);
592839
+ return {
592840
+ ...list,
592841
+ enabled: hasConfig ? config2 ? config2.enabled : true : true
592842
+ };
592843
+ });
592844
+ }
592845
+ function formatTaskListText(lists) {
592846
+ const lines = ["Task Lists:"];
592847
+ for (const list of lists) {
592848
+ const checkbox = list.enabled ? "[x]" : "[ ]";
592849
+ const suffix = list.enabled ? "" : " (disabled)";
592850
+ lines.push(` ${checkbox} ${list.title} (${list.id})${suffix}`);
592851
+ }
592852
+ return lines.join(`
592853
+ `);
592854
+ }
592855
+ async function handleTaskLists(opts) {
592856
+ const { client, format: format4, quiet, write, configTaskLists } = opts;
592857
+ const apiLists = await listTaskLists(client);
592858
+ const lists = mergeTaskListsWithConfig(apiLists, configTaskLists);
592859
+ if (quiet) {
592860
+ write(lists.map((l) => l.id).join(`
592861
+ `));
592862
+ return { exitCode: ExitCode.SUCCESS };
592863
+ }
592864
+ if (format4 === "json") {
592865
+ write(formatJsonSuccess({ task_lists: lists, count: lists.length }));
592866
+ } else {
592867
+ write(formatTaskListText(lists));
592868
+ }
592869
+ return { exitCode: ExitCode.SUCCESS };
592870
+ }
592871
+
592872
+ // src/commands/tasks/resolve.ts
592873
+ function resolveTaskListFromConfig(configLists, listOption) {
592874
+ if (!listOption) {
592875
+ const enabled = configLists.find((c) => c.enabled);
592876
+ if (enabled)
592877
+ return { id: enabled.id, title: enabled.name };
592878
+ return null;
592879
+ }
592880
+ const byName = configLists.find((c) => c.name === listOption);
592881
+ if (byName)
592882
+ return { id: byName.id, title: byName.name };
592883
+ const byId = configLists.find((c) => c.id === listOption);
592884
+ if (byId)
592885
+ return { id: byId.id, title: byId.name };
592886
+ return null;
592887
+ }
592888
+ async function resolveTaskList(client, configLists, listOption) {
592889
+ const fromConfig = resolveTaskListFromConfig(configLists, listOption);
592890
+ if (fromConfig)
592891
+ return fromConfig;
592892
+ if (listOption) {
592893
+ const apiLists = await listTaskLists(client);
592894
+ const byTitle = apiLists.find((l) => l.title === listOption);
592895
+ if (byTitle)
592896
+ return { id: byTitle.id, title: byTitle.title };
592897
+ return { id: listOption, title: listOption };
592898
+ }
592899
+ return { id: "@default", title: "My Tasks" };
592900
+ }
592901
+
592902
+ // src/commands/tasks/list.ts
592903
+ function isValidDateString(value) {
592904
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
592905
+ return false;
592906
+ const date3 = new Date(`${value}T00:00:00Z`);
592907
+ if (Number.isNaN(date3.getTime()))
592908
+ return false;
592909
+ return date3.toISOString().startsWith(value);
592910
+ }
592911
+ function formatDueInfo(task) {
592912
+ const parts = [];
592913
+ if (task.due) {
592914
+ const month = task.due.slice(5, 7);
592915
+ const day = task.due.slice(8, 10);
592916
+ parts.push(`due: ${month}/${day}`);
592917
+ }
592918
+ if (task.status === "completed" && task.completed) {
592919
+ const month = task.completed.slice(5, 7);
592920
+ const day = task.completed.slice(8, 10);
592921
+ parts.push(`completed: ${month}/${day}`);
592922
+ }
592923
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
592924
+ }
592925
+ function formatTaskLine(task) {
592926
+ const checkbox = task.status === "completed" ? "☑" : "□";
592927
+ return `${checkbox} ${task.title}${formatDueInfo(task)}`;
592928
+ }
592929
+ function formatTaskListText2(listTitle, tasks) {
592930
+ const lines = [`${listTitle}:`];
592931
+ for (const task of tasks) {
592932
+ lines.push(` ${formatTaskLine(task)}`);
592933
+ if (task.notes) {
592934
+ const firstLine = task.notes.split(`
592935
+ `)[0];
592936
+ lines.push(` Notes: ${firstLine}`);
592937
+ }
592938
+ }
592939
+ return lines.join(`
592940
+ `);
592941
+ }
592942
+ function formatQuietTaskList(tasks) {
592943
+ return tasks.map((task) => formatTaskLine(task)).join(`
592944
+ `);
592945
+ }
592946
+ function filterTasks(tasks, options) {
592947
+ let filtered = tasks;
592948
+ if (options.completed) {
592949
+ filtered = filtered.filter((t) => t.status === "completed");
592950
+ } else if (!options.all) {
592951
+ filtered = filtered.filter((t) => t.status === "needsAction");
592952
+ }
592953
+ if (options.dueBefore) {
592954
+ filtered = filtered.filter((t) => t.due !== null && t.due <= options.dueBefore);
592955
+ }
592956
+ if (options.dueAfter) {
592957
+ filtered = filtered.filter((t) => t.due !== null && t.due >= options.dueAfter);
592958
+ }
592959
+ return filtered;
592960
+ }
592961
+ async function handleTaskList(opts) {
592962
+ const { client, format: format4, quiet, write, configTaskLists, all, completed, dueBefore, dueAfter } = opts;
592963
+ if (dueBefore !== undefined && !isValidDateString(dueBefore)) {
592964
+ write(`Error: Invalid date for --due-before: "${dueBefore}". Expected format: YYYY-MM-DD`);
592965
+ return { exitCode: ExitCode.ARGUMENT };
592966
+ }
592967
+ if (dueAfter !== undefined && !isValidDateString(dueAfter)) {
592968
+ write(`Error: Invalid date for --due-after: "${dueAfter}". Expected format: YYYY-MM-DD`);
592969
+ return { exitCode: ExitCode.ARGUMENT };
592970
+ }
592971
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
592972
+ const apiOptions = {};
592973
+ if (all || completed) {
592974
+ apiOptions.showCompleted = true;
592975
+ apiOptions.showHidden = true;
592976
+ }
592977
+ const allTasks = await listTasks(client, resolved.id, resolved.title, apiOptions);
592978
+ const filterOpts = {
592979
+ all: all ?? false,
592980
+ completed: completed ?? false
592981
+ };
592982
+ if (dueBefore !== undefined)
592983
+ filterOpts.dueBefore = dueBefore;
592984
+ if (dueAfter !== undefined)
592985
+ filterOpts.dueAfter = dueAfter;
592986
+ const tasks = filterTasks(allTasks, filterOpts);
592987
+ if (quiet) {
592988
+ write(formatQuietTaskList(tasks));
592989
+ return { exitCode: ExitCode.SUCCESS };
592990
+ }
592991
+ if (format4 === "json") {
592992
+ write(formatJsonSuccess({
592993
+ tasks,
592994
+ count: tasks.length,
592995
+ list_id: resolved.id,
592996
+ list_title: resolved.title
592997
+ }));
592998
+ } else {
592999
+ write(formatTaskListText2(resolved.title, tasks));
593000
+ }
593001
+ return { exitCode: ExitCode.SUCCESS };
593002
+ }
593003
+
593004
+ // src/commands/tasks/show.ts
593005
+ var LABEL_WIDTH = 11;
593006
+ function detailLine2(label, value) {
593007
+ return `${label}:`.padEnd(LABEL_WIDTH) + value;
593008
+ }
593009
+ function stripMilliseconds(iso) {
593010
+ return iso.replace(/\.\d{3}Z$/, "Z");
593011
+ }
593012
+ function formatTaskDetailText(task) {
593013
+ const lines = [];
593014
+ lines.push(detailLine2("ID", task.id));
593015
+ lines.push(detailLine2("Title", task.title));
593016
+ lines.push(detailLine2("Status", task.status));
593017
+ if (task.due) {
593018
+ lines.push(detailLine2("Due", task.due));
593019
+ }
593020
+ if (task.notes) {
593021
+ lines.push(detailLine2("Notes", task.notes));
593022
+ }
593023
+ lines.push(detailLine2("List", task.list_title));
593024
+ lines.push(detailLine2("Updated", stripMilliseconds(task.updated)));
593025
+ return lines.join(`
593026
+ `);
593027
+ }
593028
+ async function handleTaskShow(opts) {
593029
+ const { client, taskId, format: format4, quiet, write, configTaskLists } = opts;
593030
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
593031
+ const task = await getTask(client, resolved.id, resolved.title, taskId);
593032
+ if (format4 === "json") {
593033
+ write(formatJsonSuccess({ task }));
593034
+ } else if (quiet) {
593035
+ write(`${task.title} ${task.status} ${task.due ?? ""}`);
593036
+ } else {
593037
+ write(formatTaskDetailText(task));
593038
+ }
593039
+ return { exitCode: ExitCode.SUCCESS };
593040
+ }
593041
+
593042
+ // src/commands/tasks/add.ts
593043
+ function isValidDateString2(value) {
593044
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
593045
+ return false;
593046
+ const date3 = new Date(`${value}T00:00:00Z`);
593047
+ if (Number.isNaN(date3.getTime()))
593048
+ return false;
593049
+ return date3.toISOString().startsWith(value);
593050
+ }
593051
+ async function handleTaskAdd(opts) {
593052
+ const { client, title, format: format4, quiet, write, configTaskLists } = opts;
593053
+ if (!title) {
593054
+ const msg = "--title is required";
593055
+ if (format4 === "json") {
593056
+ write(formatJsonError("INVALID_ARGS", msg));
593057
+ } else {
593058
+ write(`Error: ${msg}`);
593059
+ }
593060
+ return { exitCode: ExitCode.ARGUMENT };
593061
+ }
593062
+ if (opts.due !== undefined && !isValidDateString2(opts.due)) {
593063
+ const msg = `Invalid date format: ${opts.due} (expected YYYY-MM-DD)`;
593064
+ if (format4 === "json") {
593065
+ write(formatJsonError("INVALID_ARGS", msg));
593066
+ } else {
593067
+ write(`Error: ${msg}`);
593068
+ }
593069
+ return { exitCode: ExitCode.ARGUMENT };
593070
+ }
593071
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
593072
+ const input = { title };
593073
+ if (opts.notes !== undefined) {
593074
+ input.notes = opts.notes;
593075
+ }
593076
+ if (opts.due !== undefined) {
593077
+ input.due = `${opts.due}T00:00:00.000Z`;
593078
+ }
593079
+ if (opts.parent !== undefined) {
593080
+ input.parent = opts.parent;
593081
+ }
593082
+ const task = await createTask(client, resolved.id, resolved.title, input);
593083
+ if (format4 === "json") {
593084
+ write(formatJsonSuccess({ task, message: "Task created" }));
593085
+ } else if (quiet) {
593086
+ write(task.id);
593087
+ } else {
593088
+ write(`Task created: ${task.title} (${task.id})`);
593089
+ }
593090
+ return { exitCode: ExitCode.SUCCESS };
593091
+ }
593092
+
593093
+ // src/commands/tasks/update.ts
593094
+ function isValidDateString3(value) {
593095
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
593096
+ return false;
593097
+ const date3 = new Date(`${value}T00:00:00Z`);
593098
+ if (Number.isNaN(date3.getTime()))
593099
+ return false;
593100
+ return date3.toISOString().startsWith(value);
593101
+ }
593102
+ async function handleTaskUpdate(opts) {
593103
+ const { client, taskId, format: format4, quiet, write, configTaskLists } = opts;
593104
+ const hasUpdate = opts.title !== undefined || opts.notes !== undefined || opts.due !== undefined;
593105
+ if (!hasUpdate) {
593106
+ const msg = "at least one update option must be provided (--title, --notes, or --due)";
593107
+ if (format4 === "json") {
593108
+ write(formatJsonError("INVALID_ARGS", msg));
593109
+ } else {
593110
+ write(`Error: ${msg}`);
593111
+ }
593112
+ return { exitCode: ExitCode.ARGUMENT };
593113
+ }
593114
+ if (opts.due !== undefined && !isValidDateString3(opts.due)) {
593115
+ const msg = `Invalid date format: ${opts.due} (expected YYYY-MM-DD)`;
593116
+ if (format4 === "json") {
593117
+ write(formatJsonError("INVALID_ARGS", msg));
593118
+ } else {
593119
+ write(`Error: ${msg}`);
593120
+ }
593121
+ return { exitCode: ExitCode.ARGUMENT };
593122
+ }
593123
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
593124
+ const input = {};
593125
+ if (opts.title !== undefined) {
593126
+ input.title = opts.title;
593127
+ }
593128
+ if (opts.notes !== undefined) {
593129
+ input.notes = opts.notes;
593130
+ }
593131
+ if (opts.due !== undefined) {
593132
+ input.due = `${opts.due}T00:00:00.000Z`;
593133
+ }
593134
+ const task = await updateTask(client, resolved.id, resolved.title, taskId, input);
593135
+ if (format4 === "json") {
593136
+ write(formatJsonSuccess({ task, message: "Task updated" }));
593137
+ } else if (quiet) {
593138
+ write(task.id);
593139
+ } else {
593140
+ write(`Task updated: ${task.title} (${task.id})`);
593141
+ }
593142
+ return { exitCode: ExitCode.SUCCESS };
593143
+ }
593144
+
593145
+ // src/commands/tasks/done.ts
593146
+ async function handleTaskDone(opts) {
593147
+ const { client, taskId, format: format4, quiet, write, configTaskLists } = opts;
593148
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
593149
+ const task = await completeTask(client, resolved.id, resolved.title, taskId);
593150
+ if (format4 === "json") {
593151
+ write(formatJsonSuccess({ task, message: "Task completed" }));
593152
+ } else if (quiet) {
593153
+ write(task.id);
593154
+ } else {
593155
+ write(`Task completed: ${task.title} (${task.id})`);
593156
+ }
593157
+ return { exitCode: ExitCode.SUCCESS };
593158
+ }
593159
+
593160
+ // src/commands/tasks/undone.ts
593161
+ async function handleTaskUndone(opts) {
593162
+ const { client, taskId, format: format4, quiet, write, configTaskLists } = opts;
593163
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
593164
+ const task = await uncompleteTask(client, resolved.id, resolved.title, taskId);
593165
+ if (format4 === "json") {
593166
+ write(formatJsonSuccess({ task, message: "Task reopened" }));
593167
+ } else if (quiet) {
593168
+ write(task.id);
593169
+ } else {
593170
+ write(`Task reopened: ${task.title} (${task.id})`);
593171
+ }
593172
+ return { exitCode: ExitCode.SUCCESS };
593173
+ }
593174
+
593175
+ // src/commands/tasks/delete.ts
593176
+ async function handleTaskDelete(opts) {
593177
+ const { client, taskId, format: format4, quiet, write, configTaskLists } = opts;
593178
+ const resolved = await resolveTaskList(client, configTaskLists, opts.list);
593179
+ await deleteTask(client, resolved.id, taskId);
593180
+ if (!quiet) {
593181
+ if (format4 === "json") {
593182
+ write(formatJsonSuccess({ deleted_id: taskId, message: "Task deleted" }));
593183
+ } else {
593184
+ write(`Task deleted (${taskId})`);
593185
+ }
593186
+ }
593187
+ return { exitCode: ExitCode.SUCCESS };
593188
+ }
593189
+
592549
593190
  // src/cli.ts
592550
593191
  var FormatSchema2 = _enum(["text", "json"]);
592551
593192
  function resolveGlobalOptions2(program2) {
@@ -592632,6 +593273,25 @@ async function resolveEventCalendar(api2, eventId, calendars) {
592632
593273
  }
592633
593274
 
592634
593275
  // src/commands/index.ts
593276
+ async function runTaskAction(program2, handler) {
593277
+ const globalOpts = resolveGlobalOptions2(program2);
593278
+ try {
593279
+ const config2 = loadConfig(fsAdapter);
593280
+ const oauth2Client = await getAuthenticatedClient(fsAdapter);
593281
+ const client = createGoogleTasksClient(import_googleapis2.google.tasks({ version: "v1", auth: oauth2Client }));
593282
+ const result = await handler({
593283
+ client,
593284
+ format: globalOpts.format,
593285
+ quiet: globalOpts.quiet,
593286
+ write: (msg) => process.stdout.write(msg + `
593287
+ `),
593288
+ configTaskLists: config2.task_lists
593289
+ });
593290
+ process.exit(result.exitCode);
593291
+ } catch (error) {
593292
+ handleError2(error, globalOpts.format);
593293
+ }
593294
+ }
592635
593295
  function registerCommands(program2) {
592636
593296
  const authCmd = createAuthCommand();
592637
593297
  authCmd.action(async () => {
@@ -592689,6 +593349,149 @@ ${url}`);
592689
593349
  }
592690
593350
  });
592691
593351
  program2.addCommand(calendarsCmd);
593352
+ const {
593353
+ tasksCmd,
593354
+ listsCmd: tasksListsCmd,
593355
+ listCmd: tasksListCmd,
593356
+ showCmd: tasksShowCmd,
593357
+ addCmd: tasksAddCmd,
593358
+ updateCmd: tasksUpdateCmd,
593359
+ doneCmd: tasksDoneCmd,
593360
+ undoneCmd: tasksUndoneCmd,
593361
+ deleteCmd: tasksDeleteCmd
593362
+ } = createTasksCommand();
593363
+ tasksListsCmd.action(async () => {
593364
+ const globalOpts = resolveGlobalOptions2(program2);
593365
+ try {
593366
+ const config2 = loadConfig(fsAdapter);
593367
+ const oauth2Client = await getAuthenticatedClient(fsAdapter);
593368
+ const tasksClient = createGoogleTasksClient(import_googleapis2.google.tasks({ version: "v1", auth: oauth2Client }));
593369
+ const result = await handleTaskLists({
593370
+ client: tasksClient,
593371
+ format: globalOpts.format,
593372
+ quiet: globalOpts.quiet,
593373
+ write: (msg) => process.stdout.write(msg + `
593374
+ `),
593375
+ configTaskLists: config2.task_lists
593376
+ });
593377
+ process.exit(result.exitCode);
593378
+ } catch (error) {
593379
+ handleError2(error, globalOpts.format);
593380
+ }
593381
+ });
593382
+ tasksListCmd.action(async () => {
593383
+ const globalOpts = resolveGlobalOptions2(program2);
593384
+ const listOpts = tasksListCmd.opts();
593385
+ try {
593386
+ const config2 = loadConfig(fsAdapter);
593387
+ const oauth2Client = await getAuthenticatedClient(fsAdapter);
593388
+ const tasksClient = createGoogleTasksClient(import_googleapis2.google.tasks({ version: "v1", auth: oauth2Client }));
593389
+ const opts = {
593390
+ client: tasksClient,
593391
+ format: globalOpts.format,
593392
+ quiet: globalOpts.quiet,
593393
+ write: (msg) => process.stdout.write(msg + `
593394
+ `),
593395
+ configTaskLists: config2.task_lists
593396
+ };
593397
+ if (listOpts.list !== undefined)
593398
+ opts.list = listOpts.list;
593399
+ if (listOpts.all)
593400
+ opts.all = true;
593401
+ if (listOpts.completed)
593402
+ opts.completed = true;
593403
+ if (listOpts.dueBefore !== undefined)
593404
+ opts.dueBefore = listOpts.dueBefore;
593405
+ if (listOpts.dueAfter !== undefined)
593406
+ opts.dueAfter = listOpts.dueAfter;
593407
+ const result = await handleTaskList(opts);
593408
+ process.exit(result.exitCode);
593409
+ } catch (error) {
593410
+ handleError2(error, globalOpts.format);
593411
+ }
593412
+ });
593413
+ tasksShowCmd.action(async (taskId) => {
593414
+ const globalOpts = resolveGlobalOptions2(program2);
593415
+ const showOpts = tasksShowCmd.opts();
593416
+ try {
593417
+ const config2 = loadConfig(fsAdapter);
593418
+ const oauth2Client = await getAuthenticatedClient(fsAdapter);
593419
+ const tasksClient = createGoogleTasksClient(import_googleapis2.google.tasks({ version: "v1", auth: oauth2Client }));
593420
+ const opts = {
593421
+ client: tasksClient,
593422
+ taskId,
593423
+ format: globalOpts.format,
593424
+ quiet: globalOpts.quiet,
593425
+ write: (msg) => process.stdout.write(msg + `
593426
+ `),
593427
+ configTaskLists: config2.task_lists
593428
+ };
593429
+ if (showOpts.list !== undefined)
593430
+ opts.list = showOpts.list;
593431
+ const result = await handleTaskShow(opts);
593432
+ process.exit(result.exitCode);
593433
+ } catch (error) {
593434
+ handleError2(error, globalOpts.format);
593435
+ }
593436
+ });
593437
+ tasksAddCmd.action(async () => {
593438
+ const addOpts = tasksAddCmd.opts();
593439
+ await runTaskAction(program2, (deps) => {
593440
+ const opts = { ...deps, title: addOpts.title };
593441
+ if (addOpts.notes !== undefined)
593442
+ opts.notes = addOpts.notes;
593443
+ if (addOpts.due !== undefined)
593444
+ opts.due = addOpts.due;
593445
+ if (addOpts.list !== undefined)
593446
+ opts.list = addOpts.list;
593447
+ if (addOpts.parent !== undefined)
593448
+ opts.parent = addOpts.parent;
593449
+ return handleTaskAdd(opts);
593450
+ });
593451
+ });
593452
+ tasksUpdateCmd.action(async (taskId) => {
593453
+ const updateOpts = tasksUpdateCmd.opts();
593454
+ await runTaskAction(program2, (deps) => {
593455
+ const opts = { ...deps, taskId };
593456
+ if (updateOpts.title !== undefined)
593457
+ opts.title = updateOpts.title;
593458
+ if (updateOpts.notes !== undefined)
593459
+ opts.notes = updateOpts.notes;
593460
+ if (updateOpts.due !== undefined)
593461
+ opts.due = updateOpts.due;
593462
+ if (updateOpts.list !== undefined)
593463
+ opts.list = updateOpts.list;
593464
+ return handleTaskUpdate(opts);
593465
+ });
593466
+ });
593467
+ tasksDoneCmd.action(async (taskId) => {
593468
+ const doneOpts = tasksDoneCmd.opts();
593469
+ await runTaskAction(program2, (deps) => {
593470
+ const opts = { ...deps, taskId };
593471
+ if (doneOpts.list !== undefined)
593472
+ opts.list = doneOpts.list;
593473
+ return handleTaskDone(opts);
593474
+ });
593475
+ });
593476
+ tasksUndoneCmd.action(async (taskId) => {
593477
+ const undoneOpts = tasksUndoneCmd.opts();
593478
+ await runTaskAction(program2, (deps) => {
593479
+ const opts = { ...deps, taskId };
593480
+ if (undoneOpts.list !== undefined)
593481
+ opts.list = undoneOpts.list;
593482
+ return handleTaskUndone(opts);
593483
+ });
593484
+ });
593485
+ tasksDeleteCmd.action(async (taskId) => {
593486
+ const deleteOpts = tasksDeleteCmd.opts();
593487
+ await runTaskAction(program2, (deps) => {
593488
+ const opts = { ...deps, taskId };
593489
+ if (deleteOpts.list !== undefined)
593490
+ opts.list = deleteOpts.list;
593491
+ return handleTaskDelete(opts);
593492
+ });
593493
+ });
593494
+ program2.addCommand(tasksCmd);
592692
593495
  const listCmd = createListCommand();
592693
593496
  listCmd.action(async () => {
592694
593497
  const globalOpts = resolveGlobalOptions2(program2);
@@ -592882,6 +593685,11 @@ ${url}`);
592882
593685
  const api2 = await getApi();
592883
593686
  return listCalendars(api2);
592884
593687
  },
593688
+ listTaskLists: async () => {
593689
+ const oauth2Client = await getAuthenticatedClient(fsAdapter);
593690
+ const tasksClient = createGoogleTasksClient(import_googleapis2.google.tasks({ version: "v1", auth: oauth2Client }));
593691
+ return listTaskLists(tasksClient);
593692
+ },
592885
593693
  requestAuth: async () => {
592886
593694
  apiRef = null;
592887
593695
  const promptFn = createReadlinePrompt();
@@ -592901,6 +593709,8 @@ ${authUrl}`);
592901
593709
  format: globalOpts.format,
592902
593710
  quiet: globalOpts.quiet,
592903
593711
  write,
593712
+ writeErr: (msg) => process.stderr.write(msg + `
593713
+ `),
592904
593714
  force: initOpts.force ?? false,
592905
593715
  all: initOpts.all ?? false,
592906
593716
  local: initOpts.local ?? false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ncukondo/gcal-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"