mdpockla 0.1.1 → 0.3.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.
- package/LICENSE +21 -21
- package/README.md +104 -72
- package/bin/mdpockla.mjs +7 -7
- package/package.json +47 -47
- package/src/commands/auth.mjs +80 -80
- package/src/commands/note.mjs +319 -178
- package/src/index.mjs +162 -119
- package/src/lib/args.mjs +49 -49
- package/src/lib/config.mjs +59 -59
- package/src/lib/http.mjs +75 -75
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Pockla Ltd
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pockla Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,72 +1,104 @@
|
|
|
1
|
-
## mdpockla
|
|
2
|
-
|
|
3
|
-
CLI for [md.pockla.com](https://md.pockla.com). Save and edit markdown
|
|
4
|
-
or HTML notes from your shell, CI pipelines, or agent runtimes that
|
|
5
|
-
can't speak MCP.
|
|
6
|
-
|
|
7
|
-
### Install
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
npm install -g mdpockla
|
|
11
|
-
mdpockla login
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Or without installing:
|
|
15
|
-
|
|
16
|
-
```sh
|
|
17
|
-
npx mdpockla login
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### Quick start
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
# Create a note and capture its URL.
|
|
24
|
-
URL=$(mdpockla note create ./post.md --public)
|
|
25
|
-
open "$URL"
|
|
26
|
-
|
|
27
|
-
# Edit an existing note.
|
|
28
|
-
mdpockla note edit "$URL" --content ./post.v2.md
|
|
29
|
-
|
|
30
|
-
# Pipe a note's body somewhere else.
|
|
31
|
-
mdpockla note get https://md.pockla.com/n/abc12345 | pandoc -t docx -o post.docx
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
###
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
mdpockla note
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
mdpockla note list
|
|
42
|
-
mdpockla note
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
mdpockla note
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
## mdpockla
|
|
2
|
+
|
|
3
|
+
CLI for [md.pockla.com](https://md.pockla.com). Save and edit markdown
|
|
4
|
+
or HTML notes from your shell, CI pipelines, or agent runtimes that
|
|
5
|
+
can't speak MCP.
|
|
6
|
+
|
|
7
|
+
### Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g mdpockla
|
|
11
|
+
mdpockla login
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or without installing:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npx mdpockla login
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Quick start
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
# Create a note and capture its URL.
|
|
24
|
+
URL=$(mdpockla note create ./post.md --public)
|
|
25
|
+
open "$URL"
|
|
26
|
+
|
|
27
|
+
# Edit an existing note.
|
|
28
|
+
mdpockla note edit "$URL" --content ./post.v2.md
|
|
29
|
+
|
|
30
|
+
# Pipe a note's body somewhere else.
|
|
31
|
+
mdpockla note get https://md.pockla.com/n/abc12345 | pandoc -t docx -o post.docx
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Versioning
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
# Save a snapshot before risky edits.
|
|
38
|
+
mdpockla note version save "$URL" --label "before refactor"
|
|
39
|
+
|
|
40
|
+
# Inspect history, then diff a snapshot against the current file.
|
|
41
|
+
mdpockla note version list "$URL"
|
|
42
|
+
mdpockla note version get "$URL" 3 | diff - ./post.md
|
|
43
|
+
|
|
44
|
+
# Roll back.
|
|
45
|
+
mdpockla note version restore "$URL" 3
|
|
46
|
+
|
|
47
|
+
# CI pattern: update + snapshot in one call.
|
|
48
|
+
mdpockla note edit "$URL" --content ./CHANGELOG.md --save-version --label "Release $TAG"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`--save-version` applies the edit and snapshots the result as a new
|
|
52
|
+
version in a single request. If the note has no versions yet, the
|
|
53
|
+
pre-edit content is also captured as a baseline so the original stays
|
|
54
|
+
recoverable.
|
|
55
|
+
|
|
56
|
+
### Commands
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
mdpockla login | logout | whoami
|
|
60
|
+
mdpockla note create <file> [--title T] [--public] [--tag NAME ...] [--json]
|
|
61
|
+
mdpockla note edit <id|url> [--content FILE] [--title T] [--visibility public|private] [--tag NAME ...] [--save-version [--label TEXT]]
|
|
62
|
+
mdpockla note get <id|url> [--output FILE] [--version N] [--json]
|
|
63
|
+
mdpockla note list [--mine|--shared] [--query Q] [--limit N] [--json]
|
|
64
|
+
mdpockla note delete <id|url>
|
|
65
|
+
mdpockla note share <id|url> --visibility public|private
|
|
66
|
+
mdpockla note collaborator add|remove <id|url> <email>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Versions
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
mdpockla note version save <id|url> [--label TEXT] [--json]
|
|
73
|
+
mdpockla note version list <id|url> [--json]
|
|
74
|
+
mdpockla note version get <id|url> <version> [--output FILE] [--json]
|
|
75
|
+
mdpockla note version restore <id|url> <version> [--json]
|
|
76
|
+
mdpockla note version delete <id|url> <version>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Configuration
|
|
80
|
+
|
|
81
|
+
| Env var | Purpose |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `MDPOCKLA_URL` | Override the API base URL (default `https://md.pockla.com`). |
|
|
84
|
+
| `MDPOCKLA_TOKEN` | Use a specific token instead of the saved login. Useful for CI. |
|
|
85
|
+
| `NO_COLOR` | Disable colorised output. |
|
|
86
|
+
|
|
87
|
+
Credentials are saved at `~/.mdpockla/config.json` (mode 0600).
|
|
88
|
+
|
|
89
|
+
### Headless / CI
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
export MDPOCKLA_TOKEN=mdp_pat_...
|
|
93
|
+
mdpockla note create ./CHANGELOG.md --title "Release $TAG" --public
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Issue a long-lived PAT from the dashboard at
|
|
97
|
+
[md.pockla.com/settings/agents](https://md.pockla.com/settings/agents).
|
|
98
|
+
|
|
99
|
+
### How it talks to md pockla
|
|
100
|
+
|
|
101
|
+
Every command is a thin wrapper around the HTTP API at `/api/v1/*` —
|
|
102
|
+
the same endpoints the [remote MCP server](https://mcp.md.pockla.com)
|
|
103
|
+
uses. If you outgrow this CLI, the MCP route works in Claude, Cursor,
|
|
104
|
+
Codex, and ChatGPT Custom Connectors with one-click install.
|
package/bin/mdpockla.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { run } from "../src/index.mjs";
|
|
3
|
-
|
|
4
|
-
run(process.argv.slice(2)).catch((err) => {
|
|
5
|
-
process.stderr.write(`mdpockla: ${err.message ?? err}\n`);
|
|
6
|
-
process.exit(typeof err.exitCode === "number" ? err.exitCode : 1);
|
|
7
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { run } from "../src/index.mjs";
|
|
3
|
+
|
|
4
|
+
run(process.argv.slice(2)).catch((err) => {
|
|
5
|
+
process.stderr.write(`mdpockla: ${err.message ?? err}\n`);
|
|
6
|
+
process.exit(typeof err.exitCode === "number" ? err.exitCode : 1);
|
|
7
|
+
});
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mdpockla",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for md.pockla.com - save and edit markdown notes from your shell or CI. Pairs with the md pockla MCP server for agent-native sharing.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"mdpockla": "./bin/mdpockla.mjs"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin/",
|
|
11
|
-
"src/",
|
|
12
|
-
"README.md",
|
|
13
|
-
"LICENSE"
|
|
14
|
-
],
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=20"
|
|
17
|
-
},
|
|
18
|
-
"scripts": {
|
|
19
|
-
"start": "node bin/mdpockla.mjs"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"markdown",
|
|
23
|
-
"md-pockla",
|
|
24
|
-
"mdpockla",
|
|
25
|
-
"pockla",
|
|
26
|
-
"mcp",
|
|
27
|
-
"agent",
|
|
28
|
-
"notes",
|
|
29
|
-
"share",
|
|
30
|
-
"cli"
|
|
31
|
-
],
|
|
32
|
-
"author": "Jeremy Dsouza <jeremy@pockla.com>",
|
|
33
|
-
"homepage": "https://md.pockla.com",
|
|
34
|
-
"repository": {
|
|
35
|
-
"type": "git",
|
|
36
|
-
"url": "git+https://github.com/jeremydsz/md-viewer.git",
|
|
37
|
-
"directory": "cli"
|
|
38
|
-
},
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/jeremydsz/md-viewer/issues"
|
|
41
|
-
},
|
|
42
|
-
"license": "MIT",
|
|
43
|
-
"publishConfig": {
|
|
44
|
-
"access": "public",
|
|
45
|
-
"registry": "https://registry.npmjs.org/"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mdpockla",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI for md.pockla.com - save and edit markdown notes from your shell or CI. Pairs with the md pockla MCP server for agent-native sharing.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mdpockla": "./bin/mdpockla.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"start": "node bin/mdpockla.mjs"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"markdown",
|
|
23
|
+
"md-pockla",
|
|
24
|
+
"mdpockla",
|
|
25
|
+
"pockla",
|
|
26
|
+
"mcp",
|
|
27
|
+
"agent",
|
|
28
|
+
"notes",
|
|
29
|
+
"share",
|
|
30
|
+
"cli"
|
|
31
|
+
],
|
|
32
|
+
"author": "Jeremy Dsouza <jeremy@pockla.com>",
|
|
33
|
+
"homepage": "https://md.pockla.com",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/jeremydsz/md-viewer.git",
|
|
37
|
+
"directory": "cli"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/jeremydsz/md-viewer/issues"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"registry": "https://registry.npmjs.org/"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/commands/auth.mjs
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
import { setTimeout as wait } from "node:timers/promises";
|
|
2
|
-
import { saveConfig, clearConfig } from "../lib/config.mjs";
|
|
3
|
-
import { api, rawFetch } from "../lib/http.mjs";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Device authorization flow (RFC 8628):
|
|
7
|
-
*
|
|
8
|
-
* 1. POST /api/oauth/device_authorization -> { device_code, user_code, verification_uri, interval, expires_in }
|
|
9
|
-
* 2. Print the URL + user_code to the user.
|
|
10
|
-
* 3. Poll POST /api/oauth/token with the device_code until the user
|
|
11
|
-
* approves on the website. Then store the returned PAT.
|
|
12
|
-
*/
|
|
13
|
-
export async function login() {
|
|
14
|
-
const startRes = await rawFetch("/api/oauth/device_authorization", {
|
|
15
|
-
method: "POST",
|
|
16
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
17
|
-
body: "client_id=mdpockla-cli&scope=notes:read%20notes:write",
|
|
18
|
-
});
|
|
19
|
-
if (!startRes.ok) {
|
|
20
|
-
throw exit(`Could not start login: HTTP ${startRes.status}`);
|
|
21
|
-
}
|
|
22
|
-
const start = await startRes.json();
|
|
23
|
-
const { device_code, user_code, verification_uri, interval, expires_in } = start;
|
|
24
|
-
const deadline = Date.now() + expires_in * 1000;
|
|
25
|
-
|
|
26
|
-
process.stdout.write(`Visit ${verification_uri}?code=${user_code}\n`);
|
|
27
|
-
process.stdout.write(`Or open ${verification_uri} and enter: ${user_code}\n`);
|
|
28
|
-
process.stdout.write("Waiting for approval...\n");
|
|
29
|
-
|
|
30
|
-
const pollIntervalMs = Math.max(1, interval || 3) * 1000;
|
|
31
|
-
while (Date.now() < deadline) {
|
|
32
|
-
await wait(pollIntervalMs);
|
|
33
|
-
const tokenRes = await rawFetch("/api/oauth/token", {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
36
|
-
body: new URLSearchParams({
|
|
37
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
38
|
-
device_code,
|
|
39
|
-
client_id: "mdpockla-cli",
|
|
40
|
-
}).toString(),
|
|
41
|
-
});
|
|
42
|
-
if (tokenRes.ok) {
|
|
43
|
-
const tokens = await tokenRes.json();
|
|
44
|
-
await saveConfig({ token: tokens.access_token });
|
|
45
|
-
const me = await api("GET", "/api/v1/whoami");
|
|
46
|
-
process.stdout.write(`Logged in as ${me.email}.\n`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (tokenRes.status === 400) {
|
|
50
|
-
const body = await tokenRes.json().catch(() => ({}));
|
|
51
|
-
if (body.error === "authorization_pending") continue;
|
|
52
|
-
if (body.error === "slow_down") {
|
|
53
|
-
await wait(pollIntervalMs);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (body.error === "expired_token") {
|
|
57
|
-
throw exit("Login timed out. Run `mdpockla login` again.");
|
|
58
|
-
}
|
|
59
|
-
throw exit(body?.error?.message ?? body.error ?? "Login failed");
|
|
60
|
-
}
|
|
61
|
-
throw exit(`Unexpected token response: HTTP ${tokenRes.status}`);
|
|
62
|
-
}
|
|
63
|
-
throw exit("Login timed out. Run `mdpockla login` again.");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function logout() {
|
|
67
|
-
await clearConfig();
|
|
68
|
-
process.stdout.write("Logged out.\n");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function whoami() {
|
|
72
|
-
const me = await api("GET", "/api/v1/whoami");
|
|
73
|
-
process.stdout.write(`${me.email} (${me.user_id})\n`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function exit(message, code = 1) {
|
|
77
|
-
const err = new Error(message);
|
|
78
|
-
err.exitCode = code;
|
|
79
|
-
return err;
|
|
80
|
-
}
|
|
1
|
+
import { setTimeout as wait } from "node:timers/promises";
|
|
2
|
+
import { saveConfig, clearConfig } from "../lib/config.mjs";
|
|
3
|
+
import { api, rawFetch } from "../lib/http.mjs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Device authorization flow (RFC 8628):
|
|
7
|
+
*
|
|
8
|
+
* 1. POST /api/oauth/device_authorization -> { device_code, user_code, verification_uri, interval, expires_in }
|
|
9
|
+
* 2. Print the URL + user_code to the user.
|
|
10
|
+
* 3. Poll POST /api/oauth/token with the device_code until the user
|
|
11
|
+
* approves on the website. Then store the returned PAT.
|
|
12
|
+
*/
|
|
13
|
+
export async function login() {
|
|
14
|
+
const startRes = await rawFetch("/api/oauth/device_authorization", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
17
|
+
body: "client_id=mdpockla-cli&scope=notes:read%20notes:write",
|
|
18
|
+
});
|
|
19
|
+
if (!startRes.ok) {
|
|
20
|
+
throw exit(`Could not start login: HTTP ${startRes.status}`);
|
|
21
|
+
}
|
|
22
|
+
const start = await startRes.json();
|
|
23
|
+
const { device_code, user_code, verification_uri, interval, expires_in } = start;
|
|
24
|
+
const deadline = Date.now() + expires_in * 1000;
|
|
25
|
+
|
|
26
|
+
process.stdout.write(`Visit ${verification_uri}?code=${user_code}\n`);
|
|
27
|
+
process.stdout.write(`Or open ${verification_uri} and enter: ${user_code}\n`);
|
|
28
|
+
process.stdout.write("Waiting for approval...\n");
|
|
29
|
+
|
|
30
|
+
const pollIntervalMs = Math.max(1, interval || 3) * 1000;
|
|
31
|
+
while (Date.now() < deadline) {
|
|
32
|
+
await wait(pollIntervalMs);
|
|
33
|
+
const tokenRes = await rawFetch("/api/oauth/token", {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
36
|
+
body: new URLSearchParams({
|
|
37
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
38
|
+
device_code,
|
|
39
|
+
client_id: "mdpockla-cli",
|
|
40
|
+
}).toString(),
|
|
41
|
+
});
|
|
42
|
+
if (tokenRes.ok) {
|
|
43
|
+
const tokens = await tokenRes.json();
|
|
44
|
+
await saveConfig({ token: tokens.access_token });
|
|
45
|
+
const me = await api("GET", "/api/v1/whoami");
|
|
46
|
+
process.stdout.write(`Logged in as ${me.email}.\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (tokenRes.status === 400) {
|
|
50
|
+
const body = await tokenRes.json().catch(() => ({}));
|
|
51
|
+
if (body.error === "authorization_pending") continue;
|
|
52
|
+
if (body.error === "slow_down") {
|
|
53
|
+
await wait(pollIntervalMs);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (body.error === "expired_token") {
|
|
57
|
+
throw exit("Login timed out. Run `mdpockla login` again.");
|
|
58
|
+
}
|
|
59
|
+
throw exit(body?.error?.message ?? body.error ?? "Login failed");
|
|
60
|
+
}
|
|
61
|
+
throw exit(`Unexpected token response: HTTP ${tokenRes.status}`);
|
|
62
|
+
}
|
|
63
|
+
throw exit("Login timed out. Run `mdpockla login` again.");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function logout() {
|
|
67
|
+
await clearConfig();
|
|
68
|
+
process.stdout.write("Logged out.\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function whoami() {
|
|
72
|
+
const me = await api("GET", "/api/v1/whoami");
|
|
73
|
+
process.stdout.write(`${me.email} (${me.user_id})\n`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function exit(message, code = 1) {
|
|
77
|
+
const err = new Error(message);
|
|
78
|
+
err.exitCode = code;
|
|
79
|
+
return err;
|
|
80
|
+
}
|