@tjboudreaux/gtcli 0.1.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 (38) hide show
  1. package/README.md +158 -0
  2. package/dist/account-storage.d.ts +21 -0
  3. package/dist/account-storage.d.ts.map +1 -0
  4. package/dist/account-storage.js +96 -0
  5. package/dist/account-storage.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +405 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +5 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/oauth-flow.d.ts +27 -0
  15. package/dist/oauth-flow.d.ts.map +1 -0
  16. package/dist/oauth-flow.js +156 -0
  17. package/dist/oauth-flow.js.map +1 -0
  18. package/dist/services/base-service.d.ts +13 -0
  19. package/dist/services/base-service.d.ts.map +1 -0
  20. package/dist/services/base-service.js +40 -0
  21. package/dist/services/base-service.js.map +1 -0
  22. package/dist/services/index.d.ts +4 -0
  23. package/dist/services/index.d.ts.map +1 -0
  24. package/dist/services/index.js +4 -0
  25. package/dist/services/index.js.map +1 -0
  26. package/dist/services/task-service.d.ts +19 -0
  27. package/dist/services/task-service.d.ts.map +1 -0
  28. package/dist/services/task-service.js +152 -0
  29. package/dist/services/task-service.js.map +1 -0
  30. package/dist/services/tasklist-service.d.ts +15 -0
  31. package/dist/services/tasklist-service.d.ts.map +1 -0
  32. package/dist/services/tasklist-service.js +76 -0
  33. package/dist/services/tasklist-service.js.map +1 -0
  34. package/dist/types.d.ts +88 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # gtcli - Google Tasks CLI
2
+
3
+ [![CI](https://github.com/tjboudreaux/gtcli/actions/workflows/ci.yml/badge.svg)](https://github.com/tjboudreaux/gtcli/actions/workflows/ci.yml)
4
+ [![npm version](https://badge.fury.io/js/%40tjboudreaux%2Fgtcli.svg)](https://www.npmjs.com/package/@tjboudreaux/gtcli)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A fast, minimal command-line interface for Google Tasks. Manage your task lists and tasks directly from the terminal with simple, intuitive commands.
8
+
9
+ ## Features
10
+
11
+ - **Multi-account support** - Manage tasks across multiple Google accounts
12
+ - **Full CRUD operations** - Create, read, update, delete tasks and task lists
13
+ - **Subtasks** - Create hierarchical task structures with parent-child relationships
14
+ - **Tab-separated output** - Easy to parse with standard Unix tools
15
+ - **Secure OAuth2** - Browser-based or manual authentication flow
16
+ - **Cross-platform** - Works on macOS, Linux, and Windows
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g @tjboudreaux/gtcli
22
+ ```
23
+
24
+ ## Setup
25
+
26
+ Before using gtcli, you need OAuth2 credentials from Google Cloud Console:
27
+
28
+ 1. [Create a new project](https://console.cloud.google.com/projectcreate) (or select existing)
29
+ 2. Enable the [Google Tasks API](https://console.cloud.google.com/apis/api/tasks.googleapis.com)
30
+ 3. [Set app name](https://console.cloud.google.com/auth/branding) in OAuth branding
31
+ 4. [Add test users](https://console.cloud.google.com/auth/audience) (all Google accounts you want to use)
32
+ 5. [Create OAuth client](https://console.cloud.google.com/auth/clients):
33
+ - Click "Create Client"
34
+ - Application type: "Desktop app"
35
+ - Download the JSON file
36
+
37
+ Then configure gtcli:
38
+
39
+ ```bash
40
+ gtcli accounts credentials ~/path/to/credentials.json
41
+ gtcli accounts add you@gmail.com
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```
47
+ gtcli accounts <action> Account management
48
+ gtcli <email> lists [command] [options] Task list operations
49
+ gtcli <email> tasks <listId> [command] Task operations
50
+ ```
51
+
52
+ ## Commands
53
+
54
+ ### accounts
55
+
56
+ ```bash
57
+ gtcli accounts credentials <file.json> # Set OAuth credentials (once)
58
+ gtcli accounts list # List configured accounts
59
+ gtcli accounts add <email> # Add account (opens browser)
60
+ gtcli accounts add <email> --manual # Add account (browserless, paste redirect URL)
61
+ gtcli accounts remove <email> # Remove account
62
+ ```
63
+
64
+ ### lists
65
+
66
+ ```bash
67
+ # List all task lists
68
+ gtcli <email> lists [--max N] [--page TOKEN]
69
+
70
+ # Task list operations
71
+ gtcli <email> lists get <listId>
72
+ gtcli <email> lists create <title>
73
+ gtcli <email> lists update <listId> --title <title>
74
+ gtcli <email> lists delete <listId>
75
+ gtcli <email> lists url <listIds...>
76
+ ```
77
+
78
+ ### tasks
79
+
80
+ ```bash
81
+ # List tasks in a task list
82
+ gtcli <email> tasks <listId> [--completed] [--hidden] [--max N]
83
+
84
+ # Task operations
85
+ gtcli <email> tasks <listId> get <taskId>
86
+ gtcli <email> tasks <listId> create <title> [--notes N] [--due DATE] [--parent ID]
87
+ gtcli <email> tasks <listId> update <taskId> [--title T] [--notes N] [--due DATE] [--status STATUS]
88
+ gtcli <email> tasks <listId> complete <taskId>
89
+ gtcli <email> tasks <listId> uncomplete <taskId>
90
+ gtcli <email> tasks <listId> delete <taskId>
91
+ gtcli <email> tasks <listId> move <taskId> [--parent ID] [--previous ID]
92
+ gtcli <email> tasks <listId> clear
93
+ gtcli <email> tasks <listId> url <taskIds...>
94
+ ```
95
+
96
+ **Status options:** `needsAction`, `completed`
97
+
98
+ **Due date format:** RFC 3339 timestamp (e.g., `2024-12-31T00:00:00Z`)
99
+
100
+ ## Examples
101
+
102
+ ```bash
103
+ # List all task lists
104
+ gtcli you@gmail.com lists
105
+
106
+ # Create a new task list
107
+ gtcli you@gmail.com lists create "Shopping List"
108
+
109
+ # List tasks in default task list (use list ID from 'lists' command)
110
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5
111
+
112
+ # Create a task with due date
113
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 create "Buy groceries" --due "2024-12-31T00:00:00Z"
114
+
115
+ # Create a task with notes
116
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 create "Call mom" --notes "Ask about dinner plans"
117
+
118
+ # Mark task as completed
119
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 complete TASK_ID
120
+
121
+ # List completed tasks
122
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 --completed
123
+
124
+ # Clear all completed tasks
125
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 clear
126
+
127
+ # Create a subtask
128
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 create "Buy milk" --parent PARENT_TASK_ID
129
+
130
+ # Move task to different position
131
+ gtcli you@gmail.com tasks MTIzNDU2Nzg5 move TASK_ID --previous SIBLING_TASK_ID
132
+ ```
133
+
134
+ ## Data Storage
135
+
136
+ All data is stored in `~/.gtcli/`:
137
+
138
+ - `credentials.json` - OAuth client credentials
139
+ - `accounts.json` - Account tokens
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ npm install
145
+ npm run build
146
+ npm run check
147
+ npm test
148
+ ```
149
+
150
+ ## Acknowledgements
151
+
152
+ Inspired by:
153
+ - [gmcli](https://github.com/badlogic/gmcli) - Gmail CLI by badlogic
154
+ - [gdcli](https://github.com/tjboudreaux/gdcli) - Google Drive CLI
155
+
156
+ ## License
157
+
158
+ MIT
@@ -0,0 +1,21 @@
1
+ import type { Account, StoredCredentials } from "./types.js";
2
+ export declare class AccountStorage {
3
+ private accounts;
4
+ private configDir;
5
+ private accountsFile;
6
+ private credentialsFile;
7
+ constructor(configDir?: string);
8
+ private ensureConfigDir;
9
+ private loadAccounts;
10
+ private isValidAccount;
11
+ private saveAccounts;
12
+ addAccount(account: Account): void;
13
+ getAccount(email: string): Account | undefined;
14
+ getAllAccounts(): Account[];
15
+ deleteAccount(email: string): boolean;
16
+ hasAccount(email: string): boolean;
17
+ setCredentials(clientId: string, clientSecret: string): void;
18
+ getCredentials(): StoredCredentials | null;
19
+ getConfigDir(): string;
20
+ }
21
+ //# sourceMappingURL=account-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-storage.d.ts","sourceRoot":"","sources":["../src/account-storage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE7D,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAAS;gBAEpB,SAAS,CAAC,EAAE,MAAM;IAQ9B,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,YAAY;IAIpB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKlC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI9C,cAAc,IAAI,OAAO,EAAE;IAI3B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAQrC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIlC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAI5D,cAAc,IAAI,iBAAiB,GAAG,IAAI;IAe1C,YAAY,IAAI,MAAM;CAGtB"}
@@ -0,0 +1,96 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ export class AccountStorage {
5
+ accounts = new Map();
6
+ configDir;
7
+ accountsFile;
8
+ credentialsFile;
9
+ constructor(configDir) {
10
+ this.configDir = configDir ?? path.join(os.homedir(), ".gtcli");
11
+ this.accountsFile = path.join(this.configDir, "accounts.json");
12
+ this.credentialsFile = path.join(this.configDir, "credentials.json");
13
+ this.ensureConfigDir();
14
+ this.loadAccounts();
15
+ }
16
+ ensureConfigDir() {
17
+ if (!fs.existsSync(this.configDir)) {
18
+ fs.mkdirSync(this.configDir, { recursive: true });
19
+ }
20
+ }
21
+ loadAccounts() {
22
+ if (fs.existsSync(this.accountsFile)) {
23
+ try {
24
+ const data = JSON.parse(fs.readFileSync(this.accountsFile, "utf8"));
25
+ if (Array.isArray(data)) {
26
+ for (const account of data) {
27
+ if (this.isValidAccount(account)) {
28
+ this.accounts.set(account.email, account);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ catch {
34
+ // Ignore invalid JSON
35
+ }
36
+ }
37
+ }
38
+ isValidAccount(account) {
39
+ if (typeof account !== "object" || account === null)
40
+ return false;
41
+ const acc = account;
42
+ if (typeof acc.email !== "string")
43
+ return false;
44
+ if (typeof acc.oauth2 !== "object" || acc.oauth2 === null)
45
+ return false;
46
+ const oauth2 = acc.oauth2;
47
+ return (typeof oauth2.clientId === "string" &&
48
+ typeof oauth2.clientSecret === "string" &&
49
+ typeof oauth2.refreshToken === "string");
50
+ }
51
+ saveAccounts() {
52
+ fs.writeFileSync(this.accountsFile, JSON.stringify(Array.from(this.accounts.values()), null, 2));
53
+ }
54
+ addAccount(account) {
55
+ this.accounts.set(account.email, account);
56
+ this.saveAccounts();
57
+ }
58
+ getAccount(email) {
59
+ return this.accounts.get(email);
60
+ }
61
+ getAllAccounts() {
62
+ return Array.from(this.accounts.values());
63
+ }
64
+ deleteAccount(email) {
65
+ const deleted = this.accounts.delete(email);
66
+ if (deleted) {
67
+ this.saveAccounts();
68
+ }
69
+ return deleted;
70
+ }
71
+ hasAccount(email) {
72
+ return this.accounts.has(email);
73
+ }
74
+ setCredentials(clientId, clientSecret) {
75
+ fs.writeFileSync(this.credentialsFile, JSON.stringify({ clientId, clientSecret }, null, 2));
76
+ }
77
+ getCredentials() {
78
+ if (!fs.existsSync(this.credentialsFile)) {
79
+ return null;
80
+ }
81
+ try {
82
+ const data = JSON.parse(fs.readFileSync(this.credentialsFile, "utf8"));
83
+ if (typeof data.clientId === "string" && typeof data.clientSecret === "string") {
84
+ return data;
85
+ }
86
+ return null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ getConfigDir() {
93
+ return this.configDir;
94
+ }
95
+ }
96
+ //# sourceMappingURL=account-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-storage.js","sourceRoot":"","sources":["../src/account-storage.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}