@mx-space/cli 0.1.1 → 0.2.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/README.md +101 -28
- package/ROADMAP.md +4 -3
- package/dist/{auth-OeKZZ_2x.mjs → auth-Duj8jJn9.mjs} +3 -3
- package/dist/{auth-OeKZZ_2x.mjs.map → auth-Duj8jJn9.mjs.map} +1 -1
- package/dist/bin/mxs.d.mts +2 -1
- package/dist/bin/mxs.mjs +250 -48
- package/dist/bin/mxs.mjs.map +1 -1
- package/dist/{config-store-WL7dNMS6.mjs → config-store-DmwX2iZU.mjs} +15 -76
- package/dist/config-store-DmwX2iZU.mjs.map +1 -0
- package/dist/{create-Cxnqt_Wt.mjs → create-Cqt1rrSZ.mjs} +5 -5
- package/dist/{create-Cxnqt_Wt.mjs.map → create-Cqt1rrSZ.mjs.map} +1 -1
- package/dist/{create-BWA2WGrW.mjs → create-D7cR9xcN.mjs} +3 -3
- package/dist/{create-BWA2WGrW.mjs.map → create-D7cR9xcN.mjs.map} +1 -1
- package/dist/{create-C85MSrtt.mjs → create-DAt3FXLf.mjs} +5 -5
- package/dist/{create-C85MSrtt.mjs.map → create-DAt3FXLf.mjs.map} +1 -1
- package/dist/{create-DUOSAqBm.mjs → create-_H8YO7ze.mjs} +3 -3
- package/dist/{create-DUOSAqBm.mjs.map → create-_H8YO7ze.mjs.map} +1 -1
- package/dist/{create-BVHnXtIH.mjs → create-kYlrs4d6.mjs} +4 -4
- package/dist/{create-BVHnXtIH.mjs.map → create-kYlrs4d6.mjs.map} +1 -1
- package/dist/{delete-Cjxehwj7.mjs → delete-DVNebWqO.mjs} +4 -4
- package/dist/{delete-Cjxehwj7.mjs.map → delete-DVNebWqO.mjs.map} +1 -1
- package/dist/{delete-CGy-cV6k.mjs → delete-D_NRteuq.mjs} +4 -4
- package/dist/{delete-CGy-cV6k.mjs.map → delete-D_NRteuq.mjs.map} +1 -1
- package/dist/{delete-CKqlAUT8.mjs → delete-DmKPzzsQ.mjs} +4 -4
- package/dist/{delete-CKqlAUT8.mjs.map → delete-DmKPzzsQ.mjs.map} +1 -1
- package/dist/{delete-CcyrLNZv.mjs → delete-ZIjkquZj.mjs} +4 -4
- package/dist/{delete-CcyrLNZv.mjs.map → delete-ZIjkquZj.mjs.map} +1 -1
- package/dist/{delete-BEXOOFAJ.mjs → delete-aCVSWuKi.mjs} +4 -4
- package/dist/{delete-BEXOOFAJ.mjs.map → delete-aCVSWuKi.mjs.map} +1 -1
- package/dist/{onboarding-C1t1yoay.mjs → dist-DJF1Ntdn.mjs} +95 -173
- package/dist/dist-DJF1Ntdn.mjs.map +1 -0
- package/dist/{document-output-C5PWbz2p.mjs → document-output-BtY8T7eY.mjs} +3 -3
- package/dist/{document-output-C5PWbz2p.mjs.map → document-output-BtY8T7eY.mjs.map} +1 -1
- package/dist/{edit-t108N11Q.mjs → edit-BWX7X4sy.mjs} +8 -8
- package/dist/{edit-t108N11Q.mjs.map → edit-BWX7X4sy.mjs.map} +1 -1
- package/dist/{edit-BRaZ6GGg.mjs → edit-CHCuoiWT.mjs} +9 -9
- package/dist/{edit-BRaZ6GGg.mjs.map → edit-CHCuoiWT.mjs.map} +1 -1
- package/dist/{edit-BuKMUNVC.mjs → edit-DjRE8Gk-.mjs} +4 -4
- package/dist/{edit-BuKMUNVC.mjs.map → edit-DjRE8Gk-.mjs.map} +1 -1
- package/dist/{edit-DFnOnk4i.mjs → edit-R6H5RtWd.mjs} +8 -8
- package/dist/{edit-DFnOnk4i.mjs.map → edit-R6H5RtWd.mjs.map} +1 -1
- package/dist/{editor-xrF-j3UE.mjs → editor-Dywlo4-8.mjs} +2 -2
- package/dist/{editor-xrF-j3UE.mjs.map → editor-Dywlo4-8.mjs.map} +1 -1
- package/dist/{envelope-overlays-DPAlMNW1.mjs → envelope-overlays-D1gmGVe5.mjs} +2 -2
- package/dist/{envelope-overlays-DPAlMNW1.mjs.map → envelope-overlays-D1gmGVe5.mjs.map} +1 -1
- package/dist/{get-GdRz82RW.mjs → get-CiLnUGHw.mjs} +4 -4
- package/dist/{get-GdRz82RW.mjs.map → get-CiLnUGHw.mjs.map} +1 -1
- package/dist/{get-Doi-NKAh.mjs → get-CkBqVyGi.mjs} +5 -5
- package/dist/{get-Doi-NKAh.mjs.map → get-CkBqVyGi.mjs.map} +1 -1
- package/dist/{get-DxTU7Nom.mjs → get-DNdde54z.mjs} +3 -3
- package/dist/{get-DxTU7Nom.mjs.map → get-DNdde54z.mjs.map} +1 -1
- package/dist/{get-DnDvLa9S.mjs → get-DbH58EkF.mjs} +4 -4
- package/dist/{get-DnDvLa9S.mjs.map → get-DbH58EkF.mjs.map} +1 -1
- package/dist/{get-CwcNFb39.mjs → get-IQQv4_rP.mjs} +3 -3
- package/dist/{get-CwcNFb39.mjs.map → get-IQQv4_rP.mjs.map} +1 -1
- package/dist/{get-BYoAlJwB.mjs → get-xtByxURm.mjs} +4 -4
- package/dist/{get-BYoAlJwB.mjs.map → get-xtByxURm.mjs.map} +1 -1
- package/dist/index.d.mts +19 -2
- package/dist/index.mjs +9 -9
- package/dist/{list-DVunNsXr.mjs → list-Bnlg9SfR.mjs} +3 -3
- package/dist/{list-DVunNsXr.mjs.map → list-Bnlg9SfR.mjs.map} +1 -1
- package/dist/{list-CtME3aDw.mjs → list-C6z1iw4a.mjs} +3 -3
- package/dist/{list-CtME3aDw.mjs.map → list-C6z1iw4a.mjs.map} +1 -1
- package/dist/{list-CLAcZYZ4.mjs → list-CD9j9did.mjs} +3 -3
- package/dist/{list-CLAcZYZ4.mjs.map → list-CD9j9did.mjs.map} +1 -1
- package/dist/{list-DbNCVZbz.mjs → list-DVMtBAHG.mjs} +3 -3
- package/dist/{list-DbNCVZbz.mjs.map → list-DVMtBAHG.mjs.map} +1 -1
- package/dist/{list-DHG-MzOM.mjs → list-DW5TcGxS.mjs} +3 -3
- package/dist/{list-DHG-MzOM.mjs.map → list-DW5TcGxS.mjs.map} +1 -1
- package/dist/{list-DN81zVDA.mjs → list-lmpHwOQx.mjs} +3 -3
- package/dist/{list-DN81zVDA.mjs.map → list-lmpHwOQx.mjs.map} +1 -1
- package/dist/{litexml-codec-CDoRloiR.mjs → litexml-codec-D-1oggXL.mjs} +3 -3
- package/dist/{litexml-codec-CDoRloiR.mjs.map → litexml-codec-D-1oggXL.mjs.map} +1 -1
- package/dist/{login-mjliJn8U.mjs → login-D-SoeV01.mjs} +20 -9
- package/dist/{login-mjliJn8U.mjs.map → login-D-SoeV01.mjs.map} +1 -1
- package/dist/logout-D63ZU-1Q.mjs +17 -0
- package/dist/logout-D63ZU-1Q.mjs.map +1 -0
- package/dist/ls-Bz3BCcwU.mjs +32 -0
- package/dist/ls-Bz3BCcwU.mjs.map +1 -0
- package/dist/mark-N-IpKK17.mjs +28 -0
- package/dist/mark-N-IpKK17.mjs.map +1 -0
- package/dist/onboarding-FPMcuqn8.mjs +212 -0
- package/dist/onboarding-FPMcuqn8.mjs.map +1 -0
- package/dist/{output-CTnO1lYt.d.mts → output-DQMhacTo.d.mts} +1 -1
- package/dist/output-Dg8PISJM.mjs +299 -0
- package/dist/output-Dg8PISJM.mjs.map +1 -0
- package/dist/{payload-CN11pUto.mjs → payload-CjlZ9CeZ.mjs} +3 -3
- package/dist/{payload-CN11pUto.mjs.map → payload-CjlZ9CeZ.mjs.map} +1 -1
- package/dist/{publish-CN3NTJ_r.mjs → publish-C9O5obZ6.mjs} +4 -4
- package/dist/{publish-CN3NTJ_r.mjs.map → publish-C9O5obZ6.mjs.map} +1 -1
- package/dist/{publish-akQWYsnL.mjs → publish-yuM6YGSb.mjs} +4 -4
- package/dist/{publish-akQWYsnL.mjs.map → publish-yuM6YGSb.mjs.map} +1 -1
- package/dist/{resolve-Bp1AfBmM.mjs → resolve-B4-fMQ6I.mjs} +3 -3
- package/dist/{resolve-Bp1AfBmM.mjs.map → resolve-B4-fMQ6I.mjs.map} +1 -1
- package/dist/{resolve-BogJOR7R.mjs → resolve-DIu9N2sD.mjs} +2 -2
- package/dist/{resolve-BogJOR7R.mjs.map → resolve-DIu9N2sD.mjs.map} +1 -1
- package/dist/{resolve-CyjMlaoJ.mjs → resolve-TBYXBS4i.mjs} +3 -3
- package/dist/{resolve-CyjMlaoJ.mjs.map → resolve-TBYXBS4i.mjs.map} +1 -1
- package/dist/{resolve-Bw5Po9yK.mjs → resolve-U6RJ3AnQ.mjs} +3 -3
- package/dist/{resolve-Bw5Po9yK.mjs.map → resolve-U6RJ3AnQ.mjs.map} +1 -1
- package/dist/{resolve-helpers-CahPoJ0Y.mjs → resolve-helpers-C7PIVziv.mjs} +2 -2
- package/dist/{resolve-helpers-CahPoJ0Y.mjs.map → resolve-helpers-C7PIVziv.mjs.map} +1 -1
- package/dist/rm-D9GvLhhF.mjs +32 -0
- package/dist/rm-D9GvLhhF.mjs.map +1 -0
- package/dist/{set-ClbrGFGv.mjs → set-jD79hpuF.mjs} +3 -3
- package/dist/{set-ClbrGFGv.mjs.map → set-jD79hpuF.mjs.map} +1 -1
- package/dist/{shared-C2EAn5h_.mjs → shared-SryY2QPY.mjs} +12 -8
- package/dist/shared-SryY2QPY.mjs.map +1 -0
- package/dist/show-C5Zk0tcK.mjs +38 -0
- package/dist/show-C5Zk0tcK.mjs.map +1 -0
- package/dist/status-evZpPR3N.mjs +22 -0
- package/dist/status-evZpPR3N.mjs.map +1 -0
- package/dist/{update-CHmckeo8.mjs → update--7NPzL_w.mjs} +4 -4
- package/dist/{update-CHmckeo8.mjs.map → update--7NPzL_w.mjs.map} +1 -1
- package/dist/{update-qfiC-9Og.mjs → update-0q5hxJFh.mjs} +5 -5
- package/dist/{update-qfiC-9Og.mjs.map → update-0q5hxJFh.mjs.map} +1 -1
- package/dist/{update-Duh-iVOZ.mjs → update-CPJXcOg_.mjs} +6 -6
- package/dist/{update-Duh-iVOZ.mjs.map → update-CPJXcOg_.mjs.map} +1 -1
- package/dist/{update-CJjwg09n.mjs → update-DN78pXGZ.mjs} +4 -4
- package/dist/{update-CJjwg09n.mjs.map → update-DN78pXGZ.mjs.map} +1 -1
- package/dist/{update-dc-72pUN.mjs → update-hhAcAz_M.mjs} +6 -6
- package/dist/{update-dc-72pUN.mjs.map → update-hhAcAz_M.mjs.map} +1 -1
- package/dist/use-CHDCEe38.mjs +28 -0
- package/dist/use-CHDCEe38.mjs.map +1 -0
- package/dist/{whoami-UwcdRchK.mjs → whoami-BHDYyY5D.mjs} +4 -5
- package/dist/whoami-BHDYyY5D.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-hT5z_Zn9.mjs +0 -35
- package/dist/config-store-WL7dNMS6.mjs.map +0 -1
- package/dist/logout-Bslt_ZJ4.mjs +0 -11
- package/dist/logout-Bslt_ZJ4.mjs.map +0 -1
- package/dist/onboarding-C1t1yoay.mjs.map +0 -1
- package/dist/output-BM7aBFwt.mjs +0 -120
- package/dist/output-BM7aBFwt.mjs.map +0 -1
- package/dist/shared-C2EAn5h_.mjs.map +0 -1
- package/dist/status-Cvyrrx-J.mjs +0 -22
- package/dist/status-Cvyrrx-J.mjs.map +0 -1
- package/dist/whoami-UwcdRchK.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -24,17 +24,18 @@ When the CLI cannot resolve an API URL, it starts an interactive onboarding prom
|
|
|
24
24
|
|
|
25
25
|
## Global Flags
|
|
26
26
|
|
|
27
|
-
| Flag
|
|
28
|
-
|
|
|
29
|
-
| `--json`
|
|
30
|
-
| `--output <mode>`
|
|
31
|
-
| `--api-url <url>`
|
|
32
|
-
| `--token <token>`
|
|
33
|
-
| `--api-key <key>`
|
|
34
|
-
| `--lang <code>`
|
|
35
|
-
| `--quiet`, `-q`
|
|
36
|
-
| `--verbose`
|
|
37
|
-
| `--dry-run`
|
|
27
|
+
| Flag | Effect |
|
|
28
|
+
| ------------------ | ----------------------------------------------------------------------------- |
|
|
29
|
+
| `--json` | Emit `{ ok: true, data }` on stdout. Takes precedence over `--output`. |
|
|
30
|
+
| `--output <mode>` | Output mode. Supported: `pretty-json`, `json`, `readable`, `llm`, `envelope`. |
|
|
31
|
+
| `--api-url <url>` | Override the configured mx-core API origin. |
|
|
32
|
+
| `--token <token>` | Override the stored access token. |
|
|
33
|
+
| `--api-key <key>` | Authenticate with an API key through the `x-api-key` header. |
|
|
34
|
+
| `--lang <code>` | Request translated data for read commands, such as `ja` or `en`. |
|
|
35
|
+
| `--quiet`, `-q` | Suppress non-error stderr messages. |
|
|
36
|
+
| `--verbose` | Print HTTP method, URL, status, and duration to stderr. |
|
|
37
|
+
| `--dry-run` | Resolve payloads without mutating the server where supported. |
|
|
38
|
+
| `--profile <name>` | Profile to use (overrides `MXS_PROFILE` and the active pointer). |
|
|
38
39
|
|
|
39
40
|
`readable`, `llm`, and `envelope` are document output modes for `post get`, `note get`, and `page get`. `post list` additionally supports `readable` and `llm` for concise list summaries. Other commands keep their existing JSON-oriented output.
|
|
40
41
|
|
|
@@ -92,6 +93,73 @@ Better Auth session token instead; for that path the CLI calls Better Auth
|
|
|
92
93
|
`/get-session`, accepts the refreshed `set-auth-token` header, and updates the
|
|
93
94
|
local expiry from the refreshed session.
|
|
94
95
|
|
|
96
|
+
### Auth and profiles
|
|
97
|
+
|
|
98
|
+
`mxs auth login [--profile <name>] [--production]` writes credentials to the named profile. If `--profile` is omitted and a current profile is active, it refreshes that profile. If no current profile exists (fresh install), it creates and selects the `default` profile. Passing `--production` sets the production flag on the target profile after login succeeds.
|
|
99
|
+
|
|
100
|
+
`mxs auth logout [--profile <name>]` clears credentials for the target profile; the profile directory and the `current` pointer are preserved.
|
|
101
|
+
|
|
102
|
+
`mxs auth whoami` and `mxs auth status` scope to the active profile as resolved by the standard chain (see §Profiles below).
|
|
103
|
+
|
|
104
|
+
## Profiles
|
|
105
|
+
|
|
106
|
+
A profile is a named bundle of `(api_url, credentials)` stored under `~/.config/mxs/profiles/<name>/`. Switching profile switches both the URL and the credentials atomically, so a production token can never accidentally reach a development backend and vice versa.
|
|
107
|
+
|
|
108
|
+
### Active profile
|
|
109
|
+
|
|
110
|
+
The file `~/.config/mxs/current` contains the name of the default profile. This is the profile used when no override is present. You can override it per-invocation with `--profile <name>` or by setting `MXS_PROFILE=<name>` in the environment. The full resolution chain is: `--profile` flag → `MXS_PROFILE` env → `current` file. If none of these resolves to a valid profile and the command is not `profile`, `auth login --profile <name>`, or `--help`, the CLI exits with a `profile.none_active` error.
|
|
111
|
+
|
|
112
|
+
### Profile commands
|
|
113
|
+
|
|
114
|
+
| Command | Description |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `mxs profile ls` | List profiles; current is marked with `*`; shows the production flag. |
|
|
117
|
+
| `mxs profile show [<name>]` | Show api_url, authenticated user, production flag, and token expiry for the named profile (or the active one). Never prints the token. |
|
|
118
|
+
| `mxs profile use <name>` | Set the active profile by writing `<name>` to `~/.config/mxs/current`. |
|
|
119
|
+
| `mxs profile mark <name> --production` / `--no-production` | Toggle the production flag on a profile. |
|
|
120
|
+
| `mxs profile rm <name>` | Delete a profile directory. Prompts for confirmation in TTY contexts; requires `--force` in non-TTY. |
|
|
121
|
+
|
|
122
|
+
### Production-profile safety
|
|
123
|
+
|
|
124
|
+
When a profile has `production: true`, the CLI prevents silent writes that occur only because the profile was inherited through the `current` pointer. A write command (`POST`, `PUT`, `PATCH`, `DELETE`) is blocked when: (1) the resolved profile is marked production, (2) the profile was selected only via the `current` file — not via `--profile` or `MXS_PROFILE`, and (3) the URL was not overridden with `--api-url` or `MXS_API_URL`. In that case the CLI refuses the request and emits:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{ "ok": false, "error": "profile.write_requires_explicit",
|
|
128
|
+
"profile": "prod", "api_url": "https://blog.example.com",
|
|
129
|
+
"hint": "active profile 'prod' is production; retry with --profile prod or MXS_PROFILE=prod" }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Exit code is `4`. To proceed, supply an explicit signal of intent:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Any of these bypasses the gate:
|
|
136
|
+
mxs --profile prod post publish my-slug
|
|
137
|
+
MXS_PROFILE=prod mxs post publish my-slug
|
|
138
|
+
mxs --api-url https://blog.example.com post publish my-slug
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Running `mxs profile use prod` and then issuing a write is **not** sufficient — the gate measures per-invocation explicitness, not session state.
|
|
142
|
+
|
|
143
|
+
`mxs auth login` and `mxs auth logout` are exempt from the gate; they are not content writes.
|
|
144
|
+
|
|
145
|
+
### Active-profile banner
|
|
146
|
+
|
|
147
|
+
When the resolved profile is marked `production: true`, the CLI emits a single line to stderr before executing the command:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
mxs: profile=prod (production) → https://blog.example.com
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This banner is suppressed by `--quiet` / `-q` and never appears on stdout.
|
|
154
|
+
|
|
155
|
+
### Profile name rules
|
|
156
|
+
|
|
157
|
+
Profile names must match `^[a-z0-9_-]+$` and be 1–32 characters long. The name `current` is reserved and cannot be used.
|
|
158
|
+
|
|
159
|
+
### Migration from the single-profile layout
|
|
160
|
+
|
|
161
|
+
Existing installations that use the legacy flat files (`~/.config/mxs/config.json` and `~/.config/mxs/credentials.json`) are automatically migrated on the first run of the new CLI version. The legacy files are moved into `~/.config/mxs/profiles/default/` and `default` is written to `~/.config/mxs/current`. In TTY contexts, the CLI prompts once: `Is "<api_url>" a production environment? [y/N]` — answering yes sets `production: true` on the migrated profile. Non-TTY contexts skip the prompt and leave the flag unset. Migration runs at most once; subsequent invocations skip the check.
|
|
162
|
+
|
|
95
163
|
## Posts
|
|
96
164
|
|
|
97
165
|
| Command | Description |
|
|
@@ -322,14 +390,15 @@ Use `--file -` to read an envelope from stdin.
|
|
|
322
390
|
|
|
323
391
|
## Configuration Files
|
|
324
392
|
|
|
325
|
-
| File
|
|
326
|
-
|
|
|
327
|
-
| `~/.config/mxs/
|
|
328
|
-
| `~/.config/mxs/
|
|
393
|
+
| File | Mode | Purpose |
|
|
394
|
+
| ------------------------------------------ | ------ | -------------------------------------------------------------------------- |
|
|
395
|
+
| `~/.config/mxs/current` | `0644` | Active profile name (single line). |
|
|
396
|
+
| `~/.config/mxs/profiles/<name>/config.json` | `0644` | API URL, API base, auth base, API version, client id, and production flag. |
|
|
397
|
+
| `~/.config/mxs/profiles/<name>/credentials.json` | `0600` | Access token, refresh token, expiry, and optional user profile. |
|
|
329
398
|
|
|
330
|
-
`XDG_CONFIG_HOME` changes the base directory. Credentials with wider permissions are automatically changed to `0600`.
|
|
399
|
+
`XDG_CONFIG_HOME` changes the base directory. Profile directories are created with mode `0700`. Credentials files with wider permissions are automatically changed to `0600`.
|
|
331
400
|
|
|
332
|
-
Example config:
|
|
401
|
+
Example profile config:
|
|
333
402
|
|
|
334
403
|
```json
|
|
335
404
|
{
|
|
@@ -337,7 +406,8 @@ Example config:
|
|
|
337
406
|
"api_base": "https://blog.example.com/api/v2",
|
|
338
407
|
"auth_base": "https://blog.example.com/api/v2/auth",
|
|
339
408
|
"api_version": 2,
|
|
340
|
-
"client_id": "mxs-cli"
|
|
409
|
+
"client_id": "mxs-cli",
|
|
410
|
+
"production": true
|
|
341
411
|
}
|
|
342
412
|
```
|
|
343
413
|
|
|
@@ -348,22 +418,23 @@ Example config:
|
|
|
348
418
|
| `MXS_API_URL` | API origin override. |
|
|
349
419
|
| `MXS_TOKEN` | Better Auth access token override; sent as `Authorization: Bearer`. |
|
|
350
420
|
| `MXS_API_KEY` | API key override; sent as `x-api-key`. |
|
|
421
|
+
| `MXS_PROFILE` | Profile to use; overrides the `current` pointer. |
|
|
351
422
|
| `MXS_DEBUG=1` | Enables verbose HTTP diagnostics in auth helpers. |
|
|
352
423
|
| `EDITOR` | Editor used by `post edit`, `note edit`, `page edit`, and `config edit`. |
|
|
353
424
|
| `XDG_CONFIG_HOME` | Base directory for `mxs` config files. |
|
|
354
425
|
|
|
355
426
|
## Exit Codes
|
|
356
427
|
|
|
357
|
-
| Code | Meaning
|
|
358
|
-
| ---- |
|
|
359
|
-
| `0` | Success
|
|
360
|
-
| `1` | Generic failure
|
|
361
|
-
| `2` | Argument parsing failure
|
|
362
|
-
| `3` | Authentication or authorization failure
|
|
363
|
-
| `4` | Network failure
|
|
364
|
-
| `5` | Validation or configuration failure
|
|
365
|
-
| `6` | Server 5xx failure
|
|
366
|
-
| `7` | Resource not found
|
|
428
|
+
| Code | Meaning |
|
|
429
|
+
| ---- | ---------------------------------------------------------------------------- |
|
|
430
|
+
| `0` | Success |
|
|
431
|
+
| `1` | Generic failure |
|
|
432
|
+
| `2` | Argument parsing failure |
|
|
433
|
+
| `3` | Authentication or authorization failure |
|
|
434
|
+
| `4` | Network failure; also profile write gate (`profile.write_requires_explicit`) and no active profile (`profile.none_active`) |
|
|
435
|
+
| `5` | Validation or configuration failure |
|
|
436
|
+
| `6` | Server 5xx failure |
|
|
437
|
+
| `7` | Resource not found |
|
|
367
438
|
|
|
368
439
|
## Troubleshooting
|
|
369
440
|
|
|
@@ -373,6 +444,8 @@ Example config:
|
|
|
373
444
|
| `API URL is not configured` | Set `MXS_API_URL`, pass `--api-url`, or run `mxs auth login` in an interactive terminal. |
|
|
374
445
|
| `EDITOR is not set` | Set `EDITOR`, for example `EDITOR=vim`. |
|
|
375
446
|
| API key no longer works in `Authorization: Bearer` | Use `--api-key` or `MXS_API_KEY` for API keys. Bearer auth is reserved for Better Auth session/OIDC access tokens. |
|
|
447
|
+
| `profile.write_requires_explicit` on a write command | The active profile is marked production. Retry with `--profile <name>` or `MXS_PROFILE=<name>` to confirm intent. |
|
|
448
|
+
| `profile.none_active` | No active profile set. Run `mxs auth login` to create one, or `mxs profile use <name>` to activate an existing profile. |
|
|
376
449
|
|
|
377
450
|
## License
|
|
378
451
|
|
package/ROADMAP.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# `@mx-space/cli` Roadmap
|
|
2
2
|
|
|
3
|
+
## Shipped in v0.2
|
|
4
|
+
|
|
5
|
+
- Multi-environment profile system: named `(api_url, credentials)` bundles under `~/.config/mxs/profiles/<name>/`, `--profile` global flag, `MXS_PROFILE` env var, `mxs profile {ls,show,use,mark,rm}` subcommands, production write gate, and automatic migration from the legacy single-profile layout.
|
|
6
|
+
|
|
3
7
|
## v2
|
|
4
8
|
|
|
5
9
|
- AI module commands
|
|
@@ -32,9 +36,6 @@
|
|
|
32
36
|
- Export / import (content as files)
|
|
33
37
|
- `mxs export <dir> [--type=post,note,page] [--format=markdown|litexml]`
|
|
34
38
|
- `mxs import <dir> [--type=post] [--update-existing]`
|
|
35
|
-
- Multi-profile
|
|
36
|
-
- `mxs use <name>`
|
|
37
|
-
- `~/.config/mxs/profiles.json`
|
|
38
39
|
- Observability
|
|
39
40
|
- `mxs logs tail`
|
|
40
41
|
- `mxs metrics`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { C as MxsError } from "./output-Dg8PISJM.mjs";
|
|
2
|
+
import { f as writeCredentials, l as readCredentials } from "./config-store-DmwX2iZU.mjs";
|
|
3
3
|
//#region src/core/auth.ts
|
|
4
4
|
const SUPPORTED_API_VERSIONS = [2];
|
|
5
5
|
function defaultHttp() {
|
|
@@ -208,4 +208,4 @@ async function ensureFreshToken(authBase, clientId, http = defaultHttp()) {
|
|
|
208
208
|
//#endregion
|
|
209
209
|
export { loadCredentialsOrThrow as a, refreshAccessToken as c, isExpiringSoon as i, requestDeviceCode as l, defaultHttp as n, pollDeviceToken as o, ensureFreshToken as r, probeAuthEndpoint as s, SUPPORTED_API_VERSIONS as t, toCredentials as u };
|
|
210
210
|
|
|
211
|
-
//# sourceMappingURL=auth-
|
|
211
|
+
//# sourceMappingURL=auth-Duj8jJn9.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-OeKZZ_2x.mjs","names":[],"sources":["../src/core/auth.ts"],"sourcesContent":["import {\n type CredentialsShape,\n readCredentials,\n writeCredentials,\n} from './config-store'\nimport { MxsError } from './errors'\n\nexport const SUPPORTED_API_VERSIONS = [2] as const\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n verification_uri_complete?: string\n expires_in: number\n interval: number\n}\n\nexport interface DeviceTokenResponse {\n access_token: string\n token_type: string\n expires_in: number\n refresh_token?: string\n scope?: string\n user?: {\n id?: string\n email?: string\n name?: string\n }\n}\n\nexport interface ProbeResult {\n apiUrl: string\n apiBase: string\n authBase: string\n apiVersion: number\n}\n\nexport interface AuthHttp {\n fetch: typeof fetch\n}\n\nexport function defaultHttp(): AuthHttp {\n return { fetch: globalThis.fetch.bind(globalThis) }\n}\n\nexport async function probeAuthEndpoint(\n apiUrl: string,\n http: AuthHttp = defaultHttp(),\n): Promise<ProbeResult> {\n const candidates: { prefix: string; version: number | null }[] = [\n ...SUPPORTED_API_VERSIONS.map((v) => ({\n prefix: `/api/v${v}/auth`,\n version: v,\n })),\n { prefix: '/auth', version: null },\n ]\n for (const cand of candidates) {\n const probeUrl = `${apiUrl}${cand.prefix}/ok`\n let res: Response\n try {\n res = await http.fetch(probeUrl, { method: 'GET' })\n } catch {\n continue\n }\n if (res.ok) {\n const apiVersion = cand.version ?? SUPPORTED_API_VERSIONS[0]\n const apiBase =\n cand.prefix === '/auth' ? apiUrl : `${apiUrl}/api/v${apiVersion}`\n const authBase = `${apiUrl}${cand.prefix}`\n return { apiUrl, apiBase, authBase, apiVersion }\n }\n }\n throw new MxsError({\n code: 'auth.probe',\n message: 'cannot detect auth endpoint',\n hint: 'verify the URL points at a running mx-core server',\n })\n}\n\nexport async function requestDeviceCode(\n authBase: string,\n clientId: string,\n scope = 'openid profile email',\n http: AuthHttp = defaultHttp(),\n): Promise<DeviceCodeResponse> {\n const res = await http.fetch(`${authBase}/device/code`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ client_id: clientId, scope }),\n })\n if (!res.ok) {\n let body: unknown\n try {\n body = await res.json()\n } catch {\n body = await res.text()\n }\n throw new MxsError({\n code: 'auth.denied',\n message: `device code request failed (${res.status})`,\n details: body,\n })\n }\n return (await res.json()) as DeviceCodeResponse\n}\n\nexport interface PollOptions {\n intervalSec: number\n expiresInSec: number\n signal?: AbortSignal\n onTick?: (state: 'pending' | 'slow_down') => void\n http?: AuthHttp\n}\n\nexport async function pollDeviceToken(\n authBase: string,\n clientId: string,\n deviceCode: string,\n opts: PollOptions,\n): Promise<DeviceTokenResponse> {\n const http = opts.http ?? defaultHttp()\n const deadline = Date.now() + opts.expiresInSec * 1000\n let intervalMs = Math.max(1, opts.intervalSec) * 1000\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) {\n throw new MxsError({ code: 'auth.denied', message: 'aborted by user' })\n }\n await sleep(intervalMs, opts.signal)\n const res = await http.fetch(`${authBase}/device/token`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: clientId,\n }),\n })\n let body: any\n try {\n body = await res.json()\n } catch {\n body = null\n }\n if (res.ok && body?.access_token) {\n return body as DeviceTokenResponse\n }\n const err = body?.error as string | undefined\n if (err === 'authorization_pending') {\n opts.onTick?.('pending')\n continue\n }\n if (err === 'slow_down') {\n intervalMs += 5000\n opts.onTick?.('slow_down')\n continue\n }\n if (err === 'access_denied') {\n throw new MxsError({\n code: 'auth.denied',\n message: 'user denied the authorization request',\n })\n }\n if (err === 'expired_token') {\n throw new MxsError({\n code: 'auth.expired',\n message: 'device code expired before authorization completed',\n hint: 'run `mxs auth login` again',\n })\n }\n throw new MxsError({\n code: 'auth.denied',\n message: `device token request failed (${res.status})`,\n details: body,\n })\n }\n throw new MxsError({\n code: 'auth.expired',\n message: 'device authorization timed out',\n hint: 'run `mxs auth login` again',\n })\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(new Error('aborted'))\n const timer = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timer)\n reject(new Error('aborted'))\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport function toCredentials(resp: DeviceTokenResponse): CredentialsShape {\n return {\n access_token: resp.access_token,\n refresh_token: resp.refresh_token,\n expires_at: Date.now() + resp.expires_in * 1000,\n user: resp.user,\n }\n}\n\nexport function isExpiringSoon(\n cred: CredentialsShape,\n bufferMs = 60_000,\n): boolean {\n return cred.expires_at < Date.now() + bufferMs\n}\n\nexport async function refreshAccessToken(\n authBase: string,\n clientId: string,\n cred: CredentialsShape,\n http: AuthHttp = defaultHttp(),\n): Promise<CredentialsShape | null> {\n if (!cred.refresh_token) {\n return refreshSessionToken(authBase, cred, http)\n }\n const res = await http.fetch(`${authBase}/token`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n refresh_token: cred.refresh_token,\n client_id: clientId,\n }),\n })\n if (!res.ok) return null\n const body = (await res.json()) as DeviceTokenResponse\n if (!body?.access_token) return null\n return {\n access_token: body.access_token,\n refresh_token: body.refresh_token ?? cred.refresh_token,\n expires_at: Date.now() + body.expires_in * 1000,\n user: body.user ?? cred.user,\n }\n}\n\nasync function refreshSessionToken(\n authBase: string,\n cred: CredentialsShape,\n http: AuthHttp,\n): Promise<CredentialsShape | null> {\n const res = await http.fetch(`${authBase}/get-session`, {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${cred.access_token}`,\n },\n })\n if (!res.ok) return null\n const body = (await res.json().catch(() => null)) as\n | {\n session?: { expiresAt?: string | Date | number }\n user?: CredentialsShape['user']\n }\n | null\n const expiresAt = normalizeExpiresAt(body?.session?.expiresAt)\n if (!expiresAt || expiresAt <= cred.expires_at) return null\n return {\n access_token: res.headers.get('set-auth-token') ?? cred.access_token,\n refresh_token: cred.refresh_token,\n expires_at: expiresAt,\n user: body?.user ?? cred.user,\n }\n}\n\nfunction normalizeExpiresAt(value: string | Date | number | undefined) {\n if (value === undefined) return null\n const time =\n value instanceof Date\n ? value.getTime()\n : typeof value === 'number'\n ? value\n : new Date(value).getTime()\n return Number.isFinite(time) ? time : null\n}\n\nexport async function loadCredentialsOrThrow(): Promise<CredentialsShape> {\n const cred = await readCredentials()\n if (!cred) {\n throw new MxsError({\n code: 'auth.missing',\n message: 'not authenticated',\n hint: 'run `mxs auth login`',\n })\n }\n return cred\n}\n\nexport async function ensureFreshToken(\n authBase: string,\n clientId: string,\n http: AuthHttp = defaultHttp(),\n): Promise<CredentialsShape> {\n const cred = await loadCredentialsOrThrow()\n if (!isExpiringSoon(cred)) return cred\n const refreshed = await refreshAccessToken(authBase, clientId, cred, http)\n if (!refreshed) return cred\n await writeCredentials(refreshed)\n return refreshed\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth-Duj8jJn9.mjs","names":[],"sources":["../src/core/auth.ts"],"sourcesContent":["import {\n type CredentialsShape,\n readCredentials,\n writeCredentials,\n} from './config-store'\nimport { MxsError } from './errors'\n\nexport const SUPPORTED_API_VERSIONS = [2] as const\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n verification_uri_complete?: string\n expires_in: number\n interval: number\n}\n\nexport interface DeviceTokenResponse {\n access_token: string\n token_type: string\n expires_in: number\n refresh_token?: string\n scope?: string\n user?: {\n id?: string\n email?: string\n name?: string\n }\n}\n\nexport interface ProbeResult {\n apiUrl: string\n apiBase: string\n authBase: string\n apiVersion: number\n}\n\nexport interface AuthHttp {\n fetch: typeof fetch\n}\n\nexport function defaultHttp(): AuthHttp {\n return { fetch: globalThis.fetch.bind(globalThis) }\n}\n\nexport async function probeAuthEndpoint(\n apiUrl: string,\n http: AuthHttp = defaultHttp(),\n): Promise<ProbeResult> {\n const candidates: { prefix: string; version: number | null }[] = [\n ...SUPPORTED_API_VERSIONS.map((v) => ({\n prefix: `/api/v${v}/auth`,\n version: v,\n })),\n { prefix: '/auth', version: null },\n ]\n for (const cand of candidates) {\n const probeUrl = `${apiUrl}${cand.prefix}/ok`\n let res: Response\n try {\n res = await http.fetch(probeUrl, { method: 'GET' })\n } catch {\n continue\n }\n if (res.ok) {\n const apiVersion = cand.version ?? SUPPORTED_API_VERSIONS[0]\n const apiBase =\n cand.prefix === '/auth' ? apiUrl : `${apiUrl}/api/v${apiVersion}`\n const authBase = `${apiUrl}${cand.prefix}`\n return { apiUrl, apiBase, authBase, apiVersion }\n }\n }\n throw new MxsError({\n code: 'auth.probe',\n message: 'cannot detect auth endpoint',\n hint: 'verify the URL points at a running mx-core server',\n })\n}\n\nexport async function requestDeviceCode(\n authBase: string,\n clientId: string,\n scope = 'openid profile email',\n http: AuthHttp = defaultHttp(),\n): Promise<DeviceCodeResponse> {\n const res = await http.fetch(`${authBase}/device/code`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ client_id: clientId, scope }),\n })\n if (!res.ok) {\n let body: unknown\n try {\n body = await res.json()\n } catch {\n body = await res.text()\n }\n throw new MxsError({\n code: 'auth.denied',\n message: `device code request failed (${res.status})`,\n details: body,\n })\n }\n return (await res.json()) as DeviceCodeResponse\n}\n\nexport interface PollOptions {\n intervalSec: number\n expiresInSec: number\n signal?: AbortSignal\n onTick?: (state: 'pending' | 'slow_down') => void\n http?: AuthHttp\n}\n\nexport async function pollDeviceToken(\n authBase: string,\n clientId: string,\n deviceCode: string,\n opts: PollOptions,\n): Promise<DeviceTokenResponse> {\n const http = opts.http ?? defaultHttp()\n const deadline = Date.now() + opts.expiresInSec * 1000\n let intervalMs = Math.max(1, opts.intervalSec) * 1000\n while (Date.now() < deadline) {\n if (opts.signal?.aborted) {\n throw new MxsError({ code: 'auth.denied', message: 'aborted by user' })\n }\n await sleep(intervalMs, opts.signal)\n const res = await http.fetch(`${authBase}/device/token`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: clientId,\n }),\n })\n let body: any\n try {\n body = await res.json()\n } catch {\n body = null\n }\n if (res.ok && body?.access_token) {\n return body as DeviceTokenResponse\n }\n const err = body?.error as string | undefined\n if (err === 'authorization_pending') {\n opts.onTick?.('pending')\n continue\n }\n if (err === 'slow_down') {\n intervalMs += 5000\n opts.onTick?.('slow_down')\n continue\n }\n if (err === 'access_denied') {\n throw new MxsError({\n code: 'auth.denied',\n message: 'user denied the authorization request',\n })\n }\n if (err === 'expired_token') {\n throw new MxsError({\n code: 'auth.expired',\n message: 'device code expired before authorization completed',\n hint: 'run `mxs auth login` again',\n })\n }\n throw new MxsError({\n code: 'auth.denied',\n message: `device token request failed (${res.status})`,\n details: body,\n })\n }\n throw new MxsError({\n code: 'auth.expired',\n message: 'device authorization timed out',\n hint: 'run `mxs auth login` again',\n })\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(new Error('aborted'))\n const timer = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timer)\n reject(new Error('aborted'))\n }\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport function toCredentials(resp: DeviceTokenResponse): CredentialsShape {\n return {\n access_token: resp.access_token,\n refresh_token: resp.refresh_token,\n expires_at: Date.now() + resp.expires_in * 1000,\n user: resp.user,\n }\n}\n\nexport function isExpiringSoon(\n cred: CredentialsShape,\n bufferMs = 60_000,\n): boolean {\n return cred.expires_at < Date.now() + bufferMs\n}\n\nexport async function refreshAccessToken(\n authBase: string,\n clientId: string,\n cred: CredentialsShape,\n http: AuthHttp = defaultHttp(),\n): Promise<CredentialsShape | null> {\n if (!cred.refresh_token) {\n return refreshSessionToken(authBase, cred, http)\n }\n const res = await http.fetch(`${authBase}/token`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n refresh_token: cred.refresh_token,\n client_id: clientId,\n }),\n })\n if (!res.ok) return null\n const body = (await res.json()) as DeviceTokenResponse\n if (!body?.access_token) return null\n return {\n access_token: body.access_token,\n refresh_token: body.refresh_token ?? cred.refresh_token,\n expires_at: Date.now() + body.expires_in * 1000,\n user: body.user ?? cred.user,\n }\n}\n\nasync function refreshSessionToken(\n authBase: string,\n cred: CredentialsShape,\n http: AuthHttp,\n): Promise<CredentialsShape | null> {\n const res = await http.fetch(`${authBase}/get-session`, {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${cred.access_token}`,\n },\n })\n if (!res.ok) return null\n const body = (await res.json().catch(() => null)) as\n | {\n session?: { expiresAt?: string | Date | number }\n user?: CredentialsShape['user']\n }\n | null\n const expiresAt = normalizeExpiresAt(body?.session?.expiresAt)\n if (!expiresAt || expiresAt <= cred.expires_at) return null\n return {\n access_token: res.headers.get('set-auth-token') ?? cred.access_token,\n refresh_token: cred.refresh_token,\n expires_at: expiresAt,\n user: body?.user ?? cred.user,\n }\n}\n\nfunction normalizeExpiresAt(value: string | Date | number | undefined) {\n if (value === undefined) return null\n const time =\n value instanceof Date\n ? value.getTime()\n : typeof value === 'number'\n ? value\n : new Date(value).getTime()\n return Number.isFinite(time) ? time : null\n}\n\nexport async function loadCredentialsOrThrow(): Promise<CredentialsShape> {\n const cred = await readCredentials()\n if (!cred) {\n throw new MxsError({\n code: 'auth.missing',\n message: 'not authenticated',\n hint: 'run `mxs auth login`',\n })\n }\n return cred\n}\n\nexport async function ensureFreshToken(\n authBase: string,\n clientId: string,\n http: AuthHttp = defaultHttp(),\n): Promise<CredentialsShape> {\n const cred = await loadCredentialsOrThrow()\n if (!isExpiringSoon(cred)) return cred\n const refreshed = await refreshAccessToken(authBase, clientId, cred, http)\n if (!refreshed) return cred\n await writeCredentials(refreshed)\n return refreshed\n}\n"],"mappings":"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"}
|
package/dist/bin/mxs.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as OutputOptions } from "../output-
|
|
1
|
+
import { t as OutputOptions } from "../output-DQMhacTo.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/typings/index.d.ts
|
|
4
4
|
// Type definitions for commander
|
|
@@ -920,6 +920,7 @@ interface GlobalOptions {
|
|
|
920
920
|
quiet?: boolean;
|
|
921
921
|
verbose?: boolean;
|
|
922
922
|
dryRun?: boolean;
|
|
923
|
+
profile?: string;
|
|
923
924
|
}
|
|
924
925
|
declare function readGlobalOptions(cmd: Command): OutputOptions;
|
|
925
926
|
declare function readGlobalFlags(cmd: Command): GlobalOptions;
|