ticktick-cli 1.0.1 → 1.0.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.
package/README.md CHANGED
@@ -5,7 +5,7 @@ A TypeScript CLI wrapper for the TickTick Open API documented at:
5
5
  - https://developer.ticktick.com/
6
6
  - https://developer.ticktick.com/docs#/openapi
7
7
 
8
- `v1.0.0` covers the documented OAuth flow plus every documented task and project endpoint.
8
+ `v1.0.2` covers the documented OAuth flow plus every documented task and project endpoint.
9
9
 
10
10
  The CLI is available as both `ticktick` and the short alias `tt`.
11
11
 
@@ -100,6 +100,8 @@ Interactive login:
100
100
  ticktick auth login
101
101
  ```
102
102
 
103
+ Successful auth stores the token in your local config file and masks secrets in the terminal output by default.
104
+
103
105
  If you already have an authorization code:
104
106
 
105
107
  ```bash
@@ -118,6 +120,8 @@ Check current auth state:
118
120
  ticktick auth status
119
121
  ```
120
122
 
123
+ If you need the raw token values in the terminal, add `--show-secrets` to `auth login`, `auth exchange`, or `auth status`.
124
+
121
125
  Clear the stored access token:
122
126
 
123
127
  ```bash
@@ -205,20 +209,12 @@ ticktick request POST https://httpbin.org/post --no-auth --json '{"hello":"world
205
209
 
206
210
  ## Development
207
211
 
208
- Run the normal test suite:
209
-
210
- ```bash
211
- npm test
212
- ```
213
-
214
- Run the enforced coverage check:
212
+ Build the CLI:
215
213
 
216
214
  ```bash
217
- npm run coverage
215
+ npm run build
218
216
  ```
219
217
 
220
- `npm run coverage` currently enforces `100%` line, branch, and function coverage on the published runtime files.
221
-
222
218
  ## Notes
223
219
 
224
220
  - The docs currently show `api.ticktick.com` for most endpoints, but `api.dida365.com` in the examples for `task/move`, `task/completed`, and `task/filter`. This CLI defaults to the selected service profile and lets you override base URLs explicitly if your account needs something different.
package/dist/auth.js CHANGED
@@ -118,7 +118,7 @@ export async function openBrowserWith(url, options = {}) {
118
118
  const stderr = options.stderr ?? ((text) => process.stderr.write(text));
119
119
  try {
120
120
  if (platform === "win32") {
121
- await run("cmd", ["/c", "start", "", url]);
121
+ await run("rundll32", ["url.dll,FileProtocolHandler", url]);
122
122
  return;
123
123
  }
124
124
  if (platform === "darwin") {
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Command } from "commander";
2
+ import { pathToFileURL } from "node:url";
2
3
  import { buildAuthorizationUrl, exchangeAuthorizationCode, isLoopbackRedirect, openBrowser, waitForOAuthCode, } from "./auth.js";
3
4
  import { TickTickClient } from "./client.js";
4
5
  import { loadStoredConfig, resolveRuntimeConfig, saveStoredConfig, validateService, } from "./config.js";
@@ -82,6 +83,7 @@ function buildAuthCommands(root, dependencies) {
82
83
  .command("login")
83
84
  .description("Open the OAuth flow, exchange the code, and store the access token")
84
85
  .option("--timeout-ms <number>", "Timeout while waiting for the callback", parseInteger, 120000)
86
+ .option("--show-secrets", "Include access token and refresh token in the output")
85
87
  .action(async (...args) => {
86
88
  const command = args.at(-1);
87
89
  const options = command.optsWithGlobals();
@@ -96,24 +98,27 @@ function buildAuthCommands(root, dependencies) {
96
98
  });
97
99
  return;
98
100
  }
101
+ dependencies.stderr(`Waiting for OAuth callback on ${config.redirectUri}...\n`);
99
102
  const codePromise = dependencies.waitForOAuthCode(config.redirectUri, state, options.timeoutMs);
100
103
  await dependencies.openBrowser(url);
101
104
  const code = await codePromise;
102
105
  const token = await dependencies.exchangeAuthorizationCode(config, code);
103
106
  await persistConfig(config, token, dependencies);
104
- dependencies.printJson(token);
107
+ dependencies.printJson(formatAuthSuccess(config, token, options.showSecrets, dependencies));
105
108
  });
106
109
  auth
107
110
  .command("exchange <code>")
108
111
  .description("Exchange an authorization code for an access token")
112
+ .option("--show-secrets", "Include access token and refresh token in the output")
109
113
  .action(async (...args) => {
110
114
  const command = args.at(-1);
111
115
  const [code] = args;
112
- const config = await dependencies.resolveRuntimeConfig(runtimeOverrides(command.optsWithGlobals(), dependencies));
116
+ const options = command.optsWithGlobals();
117
+ const config = await dependencies.resolveRuntimeConfig(runtimeOverrides(options, dependencies));
113
118
  requireClientCredentials(config);
114
119
  const token = await dependencies.exchangeAuthorizationCode(config, code);
115
120
  await persistConfig(config, token, dependencies);
116
- dependencies.printJson(token);
121
+ dependencies.printJson(formatAuthSuccess(config, token, options.showSecrets, dependencies));
117
122
  });
118
123
  auth
119
124
  .command("status")
@@ -508,6 +513,26 @@ async function persistConfig(runtime, token, dependencies) {
508
513
  };
509
514
  await dependencies.saveStoredConfig(runtime.configFile, next);
510
515
  }
516
+ function formatAuthSuccess(runtime, token, showSecrets, dependencies) {
517
+ return {
518
+ ok: true,
519
+ message: "Authorization complete.",
520
+ service: runtime.service,
521
+ configFile: runtime.configFile,
522
+ redirectUri: runtime.redirectUri,
523
+ scope: token.scope ?? runtime.scopes,
524
+ tokenType: token.token_type,
525
+ expiresIn: token.expires_in,
526
+ accessToken: showSecrets
527
+ ? token.access_token
528
+ : dependencies.maskSecret(token.access_token),
529
+ refreshToken: token.refresh_token
530
+ ? showSecrets
531
+ ? token.refresh_token
532
+ : dependencies.maskSecret(token.refresh_token)
533
+ : undefined,
534
+ };
535
+ }
511
536
  function requireClientCredentials(config) {
512
537
  if (!config.clientId || !config.clientSecret) {
513
538
  throw new Error("Client credentials are required. Set TICKTICK_CLIENT_ID and TICKTICK_CLIENT_SECRET or use `ticktick config set`.");
@@ -524,3 +549,9 @@ function collectString(value, previous = []) {
524
549
  function collectInteger(value, previous = []) {
525
550
  return [...previous, parseInteger(value)];
526
551
  }
552
+ function isDirectExecution() {
553
+ return Boolean(process.argv[1]) && import.meta.url === pathToFileURL(process.argv[1]).href;
554
+ }
555
+ if (isDirectExecution()) {
556
+ await main();
557
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticktick-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "CLI wrapper for the TickTick Open API",
5
5
  "type": "module",
6
6
  "files": [
@@ -14,14 +14,10 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "npm run clean && tsc -p tsconfig.json",
17
- "build:coverage": "npm run clean:coverage && tsc -p tsconfig.coverage.json",
18
- "check": "npm run coverage",
17
+ "check": "npm run build",
19
18
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
20
- "clean:coverage": "node -e \"require('node:fs').rmSync('.coverage-dist',{recursive:true,force:true})\"",
21
19
  "dev": "tsx src/cli.ts",
22
- "prepack": "npm run coverage",
23
- "test": "npm run build && node --import tsx --test src/test/*.test.ts",
24
- "coverage": "npm run build:coverage && node --experimental-test-coverage --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=100 --test-coverage-include=.coverage-dist/*.js --test-coverage-exclude=.coverage-dist/bin.js --test .coverage-dist/test/*.test.js"
20
+ "prepack": "npm run build"
25
21
  },
26
22
  "keywords": [
27
23
  "ticktick",