@mx-space/cli 0.1.0 → 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.
Files changed (159) hide show
  1. package/README.md +106 -29
  2. package/ROADMAP.md +4 -3
  3. package/dist/{auth-DIFZzoMm.mjs → auth-Duj8jJn9.mjs} +3 -3
  4. package/dist/{auth-DIFZzoMm.mjs.map → auth-Duj8jJn9.mjs.map} +1 -1
  5. package/dist/bin/mxs.d.mts +3 -1
  6. package/dist/bin/mxs.mjs +250 -48
  7. package/dist/bin/mxs.mjs.map +1 -1
  8. package/dist/config-store-DmwX2iZU.mjs +165 -0
  9. package/dist/config-store-DmwX2iZU.mjs.map +1 -0
  10. package/dist/{create-DjlRHPXy.mjs → create-Cqt1rrSZ.mjs} +5 -5
  11. package/dist/{create-DjlRHPXy.mjs.map → create-Cqt1rrSZ.mjs.map} +1 -1
  12. package/dist/{create-DT1l2hue.mjs → create-D7cR9xcN.mjs} +3 -3
  13. package/dist/{create-DT1l2hue.mjs.map → create-D7cR9xcN.mjs.map} +1 -1
  14. package/dist/{create-B2AOQL1e.mjs → create-DAt3FXLf.mjs} +5 -5
  15. package/dist/{create-B2AOQL1e.mjs.map → create-DAt3FXLf.mjs.map} +1 -1
  16. package/dist/{create-CwjGcv2n.mjs → create-_H8YO7ze.mjs} +3 -3
  17. package/dist/{create-CwjGcv2n.mjs.map → create-_H8YO7ze.mjs.map} +1 -1
  18. package/dist/{create-Dz6rJJSS.mjs → create-kYlrs4d6.mjs} +4 -4
  19. package/dist/{create-Dz6rJJSS.mjs.map → create-kYlrs4d6.mjs.map} +1 -1
  20. package/dist/{delete-BrY5zJ6S.mjs → delete-DVNebWqO.mjs} +4 -4
  21. package/dist/{delete-BrY5zJ6S.mjs.map → delete-DVNebWqO.mjs.map} +1 -1
  22. package/dist/{delete-B4dGrrus.mjs → delete-D_NRteuq.mjs} +4 -4
  23. package/dist/{delete-B4dGrrus.mjs.map → delete-D_NRteuq.mjs.map} +1 -1
  24. package/dist/{delete-BH6ZngTz.mjs → delete-DmKPzzsQ.mjs} +4 -4
  25. package/dist/{delete-BH6ZngTz.mjs.map → delete-DmKPzzsQ.mjs.map} +1 -1
  26. package/dist/{delete-CaG2d_xt.mjs → delete-ZIjkquZj.mjs} +4 -4
  27. package/dist/{delete-CaG2d_xt.mjs.map → delete-ZIjkquZj.mjs.map} +1 -1
  28. package/dist/{delete-YzRliRn8.mjs → delete-aCVSWuKi.mjs} +4 -4
  29. package/dist/{delete-YzRliRn8.mjs.map → delete-aCVSWuKi.mjs.map} +1 -1
  30. package/dist/{onboarding-640IldLz.mjs → dist-DJF1Ntdn.mjs} +95 -173
  31. package/dist/dist-DJF1Ntdn.mjs.map +1 -0
  32. package/dist/{document-output-BVB8emLt.mjs → document-output-BtY8T7eY.mjs} +59 -4
  33. package/dist/document-output-BtY8T7eY.mjs.map +1 -0
  34. package/dist/{edit-DLDw_ZWX.mjs → edit-BWX7X4sy.mjs} +8 -8
  35. package/dist/{edit-DLDw_ZWX.mjs.map → edit-BWX7X4sy.mjs.map} +1 -1
  36. package/dist/{edit-Bb0Ze4w6.mjs → edit-CHCuoiWT.mjs} +9 -9
  37. package/dist/{edit-Bb0Ze4w6.mjs.map → edit-CHCuoiWT.mjs.map} +1 -1
  38. package/dist/{edit-z55ofr1S.mjs → edit-DjRE8Gk-.mjs} +4 -4
  39. package/dist/{edit-z55ofr1S.mjs.map → edit-DjRE8Gk-.mjs.map} +1 -1
  40. package/dist/{edit-BeFmC3XD.mjs → edit-R6H5RtWd.mjs} +8 -8
  41. package/dist/{edit-BeFmC3XD.mjs.map → edit-R6H5RtWd.mjs.map} +1 -1
  42. package/dist/{editor-xrF-j3UE.mjs → editor-Dywlo4-8.mjs} +2 -2
  43. package/dist/{editor-xrF-j3UE.mjs.map → editor-Dywlo4-8.mjs.map} +1 -1
  44. package/dist/{envelope-overlays-DPAlMNW1.mjs → envelope-overlays-D1gmGVe5.mjs} +2 -2
  45. package/dist/{envelope-overlays-DPAlMNW1.mjs.map → envelope-overlays-D1gmGVe5.mjs.map} +1 -1
  46. package/dist/get-CiLnUGHw.mjs +14 -0
  47. package/dist/get-CiLnUGHw.mjs.map +1 -0
  48. package/dist/{get-2qjq0wgG.mjs → get-CkBqVyGi.mjs} +8 -8
  49. package/dist/get-CkBqVyGi.mjs.map +1 -0
  50. package/dist/{get-CRR13lgj.mjs → get-DNdde54z.mjs} +3 -3
  51. package/dist/{get-CRR13lgj.mjs.map → get-DNdde54z.mjs.map} +1 -1
  52. package/dist/get-DbH58EkF.mjs +14 -0
  53. package/dist/get-DbH58EkF.mjs.map +1 -0
  54. package/dist/get-IQQv4_rP.mjs +10 -0
  55. package/dist/get-IQQv4_rP.mjs.map +1 -0
  56. package/dist/get-xtByxURm.mjs +13 -0
  57. package/dist/get-xtByxURm.mjs.map +1 -0
  58. package/dist/index.d.mts +33 -4
  59. package/dist/index.mjs +10 -10
  60. package/dist/list-Bnlg9SfR.mjs +10 -0
  61. package/dist/list-Bnlg9SfR.mjs.map +1 -0
  62. package/dist/{list-DY-tPNG-.mjs → list-C6z1iw4a.mjs} +3 -3
  63. package/dist/{list-DY-tPNG-.mjs.map → list-C6z1iw4a.mjs.map} +1 -1
  64. package/dist/list-CD9j9did.mjs +10 -0
  65. package/dist/list-CD9j9did.mjs.map +1 -0
  66. package/dist/list-DVMtBAHG.mjs +15 -0
  67. package/dist/list-DVMtBAHG.mjs.map +1 -0
  68. package/dist/list-DW5TcGxS.mjs +10 -0
  69. package/dist/list-DW5TcGxS.mjs.map +1 -0
  70. package/dist/list-lmpHwOQx.mjs +14 -0
  71. package/dist/list-lmpHwOQx.mjs.map +1 -0
  72. package/dist/{litexml-codec-CDoRloiR.mjs → litexml-codec-D-1oggXL.mjs} +3 -3
  73. package/dist/{litexml-codec-CDoRloiR.mjs.map → litexml-codec-D-1oggXL.mjs.map} +1 -1
  74. package/dist/{login-CLOJ-fTJ.mjs → login-D-SoeV01.mjs} +20 -9
  75. package/dist/{login-CLOJ-fTJ.mjs.map → login-D-SoeV01.mjs.map} +1 -1
  76. package/dist/logout-D63ZU-1Q.mjs +17 -0
  77. package/dist/logout-D63ZU-1Q.mjs.map +1 -0
  78. package/dist/ls-Bz3BCcwU.mjs +32 -0
  79. package/dist/ls-Bz3BCcwU.mjs.map +1 -0
  80. package/dist/mark-N-IpKK17.mjs +28 -0
  81. package/dist/mark-N-IpKK17.mjs.map +1 -0
  82. package/dist/onboarding-FPMcuqn8.mjs +212 -0
  83. package/dist/onboarding-FPMcuqn8.mjs.map +1 -0
  84. package/dist/{output-CTnO1lYt.d.mts → output-DQMhacTo.d.mts} +1 -1
  85. package/dist/output-Dg8PISJM.mjs +299 -0
  86. package/dist/output-Dg8PISJM.mjs.map +1 -0
  87. package/dist/{payload-CN11pUto.mjs → payload-CjlZ9CeZ.mjs} +3 -3
  88. package/dist/{payload-CN11pUto.mjs.map → payload-CjlZ9CeZ.mjs.map} +1 -1
  89. package/dist/{publish-pRBfTJfe.mjs → publish-C9O5obZ6.mjs} +4 -4
  90. package/dist/{publish-pRBfTJfe.mjs.map → publish-C9O5obZ6.mjs.map} +1 -1
  91. package/dist/{publish-DkTYB-eK.mjs → publish-yuM6YGSb.mjs} +4 -4
  92. package/dist/{publish-DkTYB-eK.mjs.map → publish-yuM6YGSb.mjs.map} +1 -1
  93. package/dist/{resolve-Bp1AfBmM.mjs → resolve-B4-fMQ6I.mjs} +3 -3
  94. package/dist/{resolve-Bp1AfBmM.mjs.map → resolve-B4-fMQ6I.mjs.map} +1 -1
  95. package/dist/{resolve-BogJOR7R.mjs → resolve-DIu9N2sD.mjs} +2 -2
  96. package/dist/{resolve-BogJOR7R.mjs.map → resolve-DIu9N2sD.mjs.map} +1 -1
  97. package/dist/{resolve-CyjMlaoJ.mjs → resolve-TBYXBS4i.mjs} +3 -3
  98. package/dist/{resolve-CyjMlaoJ.mjs.map → resolve-TBYXBS4i.mjs.map} +1 -1
  99. package/dist/{resolve-Bw5Po9yK.mjs → resolve-U6RJ3AnQ.mjs} +3 -3
  100. package/dist/{resolve-Bw5Po9yK.mjs.map → resolve-U6RJ3AnQ.mjs.map} +1 -1
  101. package/dist/{resolve-helpers-CahPoJ0Y.mjs → resolve-helpers-C7PIVziv.mjs} +2 -2
  102. package/dist/{resolve-helpers-CahPoJ0Y.mjs.map → resolve-helpers-C7PIVziv.mjs.map} +1 -1
  103. package/dist/rm-D9GvLhhF.mjs +32 -0
  104. package/dist/rm-D9GvLhhF.mjs.map +1 -0
  105. package/dist/{set-BEGQz2si.mjs → set-jD79hpuF.mjs} +3 -3
  106. package/dist/{set-BEGQz2si.mjs.map → set-jD79hpuF.mjs.map} +1 -1
  107. package/dist/{shared-D0HVBDHI.mjs → shared-SryY2QPY.mjs} +20 -9
  108. package/dist/shared-SryY2QPY.mjs.map +1 -0
  109. package/dist/show-C5Zk0tcK.mjs +38 -0
  110. package/dist/show-C5Zk0tcK.mjs.map +1 -0
  111. package/dist/status-evZpPR3N.mjs +22 -0
  112. package/dist/status-evZpPR3N.mjs.map +1 -0
  113. package/dist/{update-BMZQPOvR.mjs → update--7NPzL_w.mjs} +4 -4
  114. package/dist/{update-BMZQPOvR.mjs.map → update--7NPzL_w.mjs.map} +1 -1
  115. package/dist/{update-BXz0l7Lz.mjs → update-0q5hxJFh.mjs} +5 -5
  116. package/dist/{update-BXz0l7Lz.mjs.map → update-0q5hxJFh.mjs.map} +1 -1
  117. package/dist/{update-Dx6AF8tf.mjs → update-CPJXcOg_.mjs} +6 -6
  118. package/dist/{update-Dx6AF8tf.mjs.map → update-CPJXcOg_.mjs.map} +1 -1
  119. package/dist/{update-q66TLS38.mjs → update-DN78pXGZ.mjs} +4 -4
  120. package/dist/{update-q66TLS38.mjs.map → update-DN78pXGZ.mjs.map} +1 -1
  121. package/dist/{update-B9ynFCdZ.mjs → update-hhAcAz_M.mjs} +6 -6
  122. package/dist/{update-B9ynFCdZ.mjs.map → update-hhAcAz_M.mjs.map} +1 -1
  123. package/dist/use-CHDCEe38.mjs +28 -0
  124. package/dist/use-CHDCEe38.mjs.map +1 -0
  125. package/dist/{whoami-CTkctOEd.mjs → whoami-BHDYyY5D.mjs} +4 -5
  126. package/dist/whoami-BHDYyY5D.mjs.map +1 -0
  127. package/package.json +1 -1
  128. package/dist/chunk-hT5z_Zn9.mjs +0 -35
  129. package/dist/config-store-DIu7x0ao.mjs +0 -121
  130. package/dist/config-store-DIu7x0ao.mjs.map +0 -1
  131. package/dist/document-output-BVB8emLt.mjs.map +0 -1
  132. package/dist/get-2qjq0wgG.mjs.map +0 -1
  133. package/dist/get-BGAk1Zmv.mjs +0 -14
  134. package/dist/get-BGAk1Zmv.mjs.map +0 -1
  135. package/dist/get-ClkpDBTL.mjs +0 -13
  136. package/dist/get-ClkpDBTL.mjs.map +0 -1
  137. package/dist/get-DJfUJ3sP.mjs +0 -10
  138. package/dist/get-DJfUJ3sP.mjs.map +0 -1
  139. package/dist/get-DiqaQvoS.mjs +0 -14
  140. package/dist/get-DiqaQvoS.mjs.map +0 -1
  141. package/dist/list-BYzhflDg.mjs +0 -14
  142. package/dist/list-BYzhflDg.mjs.map +0 -1
  143. package/dist/list-DlQSkbqx.mjs +0 -10
  144. package/dist/list-DlQSkbqx.mjs.map +0 -1
  145. package/dist/list-DsrA5wIz.mjs +0 -15
  146. package/dist/list-DsrA5wIz.mjs.map +0 -1
  147. package/dist/list-Fp4nKfQb.mjs +0 -10
  148. package/dist/list-Fp4nKfQb.mjs.map +0 -1
  149. package/dist/list-s_tloiEX.mjs +0 -10
  150. package/dist/list-s_tloiEX.mjs.map +0 -1
  151. package/dist/logout-v8ihxsim.mjs +0 -11
  152. package/dist/logout-v8ihxsim.mjs.map +0 -1
  153. package/dist/onboarding-640IldLz.mjs.map +0 -1
  154. package/dist/output-BM7aBFwt.mjs +0 -120
  155. package/dist/output-BM7aBFwt.mjs.map +0 -1
  156. package/dist/shared-D0HVBDHI.mjs.map +0 -1
  157. package/dist/status-LXrN9fI-.mjs +0 -22
  158. package/dist/status-LXrN9fI-.mjs.map +0 -1
  159. package/dist/whoami-CTkctOEd.mjs.map +0 -1
package/README.md CHANGED
@@ -24,18 +24,20 @@ When the CLI cannot resolve an API URL, it starts an interactive onboarding prom
24
24
 
25
25
  ## Global Flags
26
26
 
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
- | `--quiet`, `-q` | Suppress non-error stderr messages. |
35
- | `--verbose` | Print HTTP method, URL, status, and duration to stderr. |
36
- | `--dry-run` | Resolve payloads without mutating the server where supported. |
37
-
38
- `readable`, `llm`, and `envelope` are currently document output modes for `post get`, `note get`, and `page get`. Other commands keep their existing JSON-oriented output.
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). |
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.
39
41
 
40
42
  ## Output Modes
41
43
 
@@ -53,6 +55,7 @@ Example:
53
55
 
54
56
  ```bash
55
57
  mxs post get my-slug --output llm
58
+ mxs --lang ja post list --output llm
56
59
  ```
57
60
 
58
61
  ```text
@@ -90,6 +93,73 @@ Better Auth session token instead; for that path the CLI calls Better Auth
90
93
  `/get-session`, accepts the refreshed `set-auth-token` header, and updates the
91
94
  local expiry from the refreshed session.
92
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
+
93
163
  ## Posts
94
164
 
95
165
  | Command | Description |
@@ -112,6 +182,8 @@ local expiry from the refreshed session.
112
182
  | `--state <state>` | Publication filter, such as `draft` or `publish`. |
113
183
  | `--sort <field>` | Sort field passed as `sortBy`, such as `created` or `modified`. |
114
184
 
185
+ `mxs post list --output llm` emits one compact metadata block per post and omits full article bodies.
186
+
115
187
  ### Post Write Flags
116
188
 
117
189
  | Flag | Field |
@@ -318,14 +390,15 @@ Use `--file -` to read an envelope from stdin.
318
390
 
319
391
  ## Configuration Files
320
392
 
321
- | File | Mode | Purpose |
322
- | -------------------------------- | ------ | --------------------------------------------------------------- |
323
- | `~/.config/mxs/config.json` | `0644` | API URL, API base, auth base, API version, and client id. |
324
- | `~/.config/mxs/credentials.json` | `0600` | Access token, refresh token, expiry, and optional user profile. |
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. |
325
398
 
326
- `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`.
327
400
 
328
- Example config:
401
+ Example profile config:
329
402
 
330
403
  ```json
331
404
  {
@@ -333,7 +406,8 @@ Example config:
333
406
  "api_base": "https://blog.example.com/api/v2",
334
407
  "auth_base": "https://blog.example.com/api/v2/auth",
335
408
  "api_version": 2,
336
- "client_id": "mxs-cli"
409
+ "client_id": "mxs-cli",
410
+ "production": true
337
411
  }
338
412
  ```
339
413
 
@@ -344,22 +418,23 @@ Example config:
344
418
  | `MXS_API_URL` | API origin override. |
345
419
  | `MXS_TOKEN` | Better Auth access token override; sent as `Authorization: Bearer`. |
346
420
  | `MXS_API_KEY` | API key override; sent as `x-api-key`. |
421
+ | `MXS_PROFILE` | Profile to use; overrides the `current` pointer. |
347
422
  | `MXS_DEBUG=1` | Enables verbose HTTP diagnostics in auth helpers. |
348
423
  | `EDITOR` | Editor used by `post edit`, `note edit`, `page edit`, and `config edit`. |
349
424
  | `XDG_CONFIG_HOME` | Base directory for `mxs` config files. |
350
425
 
351
426
  ## Exit Codes
352
427
 
353
- | Code | Meaning |
354
- | ---- | --------------------------------------- |
355
- | `0` | Success |
356
- | `1` | Generic failure |
357
- | `2` | Argument parsing failure |
358
- | `3` | Authentication or authorization failure |
359
- | `4` | Network failure |
360
- | `5` | Validation or configuration failure |
361
- | `6` | Server 5xx failure |
362
- | `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 |
363
438
 
364
439
  ## Troubleshooting
365
440
 
@@ -369,6 +444,8 @@ Example config:
369
444
  | `API URL is not configured` | Set `MXS_API_URL`, pass `--api-url`, or run `mxs auth login` in an interactive terminal. |
370
445
  | `EDITOR is not set` | Set `EDITOR`, for example `EDITOR=vim`. |
371
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. |
372
449
 
373
450
  ## License
374
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 { s as MxsError } from "./output-BM7aBFwt.mjs";
2
- import { c as readCredentials, d as writeCredentials } from "./config-store-DIu7x0ao.mjs";
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-DIFZzoMm.mjs.map
211
+ //# sourceMappingURL=auth-Duj8jJn9.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-DIFZzoMm.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":";;;AAOA,MAAa,yBAAyB,CAAC,CAAC;AAmCxC,SAAgB,cAAwB;CACtC,OAAO,EAAE,OAAO,WAAW,MAAM,KAAK,UAAU,EAAE;AACpD;AAEA,eAAsB,kBACpB,QACA,OAAiB,YAAY,GACP;CACtB,MAAM,aAA2D,CAC/D,GAAG,uBAAuB,KAAK,OAAO;EACpC,QAAQ,SAAS,EAAE;EACnB,SAAS;CACX,EAAE,GACF;EAAE,QAAQ;EAAS,SAAS;CAAK,CACnC;CACA,KAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,WAAW,GAAG,SAAS,KAAK,OAAO;EACzC,IAAI;EACJ,IAAI;GACF,MAAM,MAAM,KAAK,MAAM,UAAU,EAAE,QAAQ,MAAM,CAAC;EACpD,QAAQ;GACN;EACF;EACA,IAAI,IAAI,IAAI;GACV,MAAM,aAAa,KAAK,WAAW,uBAAuB;GAI1D,OAAO;IAAE;IAAQ,SAFf,KAAK,WAAW,UAAU,SAAS,GAAG,OAAO,QAAQ;IAE7B,UAAA,GADN,SAAS,KAAK;IACE;GAAW;EACjD;CACF;CACA,MAAM,IAAI,SAAS;EACjB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;AACH;AAEA,eAAsB,kBACpB,UACA,UACA,QAAQ,wBACR,OAAiB,YAAY,GACA;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,eAAe;EACtD,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GAAE,WAAW;GAAU;EAAM,CAAC;CACrD,CAAC;CACD,IAAI,CAAC,IAAI,IAAI;EACX,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,IAAI,KAAK;EACxB,QAAQ;GACN,OAAO,MAAM,IAAI,KAAK;EACxB;EACA,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS,+BAA+B,IAAI,OAAO;GACnD,SAAS;EACX,CAAC;CACH;CACA,OAAQ,MAAM,IAAI,KAAK;AACzB;AAUA,eAAsB,gBACpB,UACA,UACA,YACA,MAC8B;CAC9B,MAAM,OAAO,KAAK,QAAQ,YAAY;CACtC,MAAM,WAAW,KAAK,IAAI,IAAI,KAAK,eAAe;CAClD,IAAI,aAAa,KAAK,IAAI,GAAG,KAAK,WAAW,IAAI;CACjD,OAAO,KAAK,IAAI,IAAI,UAAU;EAC5B,IAAI,KAAK,QAAQ,SACf,MAAM,IAAI,SAAS;GAAE,MAAM;GAAe,SAAS;EAAkB,CAAC;EAExE,MAAM,MAAM,YAAY,KAAK,MAAM;EACnC,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,gBAAgB;GACvD,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU;IACnB,YAAY;IACZ,aAAa;IACb,WAAW;GACb,CAAC;EACH,CAAC;EACD,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,IAAI,KAAK;EACxB,QAAQ;GACN,OAAO;EACT;EACA,IAAI,IAAI,MAAM,MAAM,cAClB,OAAO;EAET,MAAM,MAAM,MAAM;EAClB,IAAI,QAAQ,yBAAyB;GACnC,KAAK,SAAS,SAAS;GACvB;EACF;EACA,IAAI,QAAQ,aAAa;GACvB,cAAc;GACd,KAAK,SAAS,WAAW;GACzB;EACF;EACA,IAAI,QAAQ,iBACV,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS;EACX,CAAC;EAEH,IAAI,QAAQ,iBACV,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS;GACT,MAAM;EACR,CAAC;EAEH,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS,gCAAgC,IAAI,OAAO;GACpD,SAAS;EACX,CAAC;CACH;CACA,MAAM,IAAI,SAAS;EACjB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;AACH;AAEA,SAAS,MAAM,IAAY,QAAqC;CAC9D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ,SAAS,OAAO,uBAAO,IAAI,MAAM,SAAS,CAAC;EACvD,MAAM,QAAQ,iBAAiB;GAC7B,IAAI,QAAQ,OAAO,oBAAoB,SAAS,OAAO;GACvD,QAAQ;EACV,GAAG,EAAE;EACL,MAAM,gBAAgB;GACpB,aAAa,KAAK;GAClB,uBAAO,IAAI,MAAM,SAAS,CAAC;EAC7B;EACA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC3D,CAAC;AACH;AAEA,SAAgB,cAAc,MAA6C;CACzE,OAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK;EACpB,YAAY,KAAK,IAAI,IAAI,KAAK,aAAa;EAC3C,MAAM,KAAK;CACb;AACF;AAEA,SAAgB,eACd,MACA,WAAW,KACF;CACT,OAAO,KAAK,aAAa,KAAK,IAAI,IAAI;AACxC;AAEA,eAAsB,mBACpB,UACA,UACA,MACA,OAAiB,YAAY,GACK;CAClC,IAAI,CAAC,KAAK,eACR,OAAO,oBAAoB,UAAU,MAAM,IAAI;CAEjD,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,SAAS;EAChD,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GACnB,YAAY;GACZ,eAAe,KAAK;GACpB,WAAW;EACb,CAAC;CACH,CAAC;CACD,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,MAAM,OAAQ,MAAM,IAAI,KAAK;CAC7B,IAAI,CAAC,MAAM,cAAc,OAAO;CAChC,OAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK,iBAAiB,KAAK;EAC1C,YAAY,KAAK,IAAI,IAAI,KAAK,aAAa;EAC3C,MAAM,KAAK,QAAQ,KAAK;CAC1B;AACF;AAEA,eAAe,oBACb,UACA,MACA,MACkC;CAClC,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,eAAe;EACtD,QAAQ;EACR,SAAS;GACP,QAAQ;GACR,eAAe,UAAU,KAAK;EAChC;CACF,CAAC;CACD,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,MAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,YAAY,IAAI;CAM/C,MAAM,YAAY,mBAAmB,MAAM,SAAS,SAAS;CAC7D,IAAI,CAAC,aAAa,aAAa,KAAK,YAAY,OAAO;CACvD,OAAO;EACL,cAAc,IAAI,QAAQ,IAAI,gBAAgB,KAAK,KAAK;EACxD,eAAe,KAAK;EACpB,YAAY;EACZ,MAAM,MAAM,QAAQ,KAAK;CAC3B;AACF;AAEA,SAAS,mBAAmB,OAA2C;CACrE,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,MAAM,OACJ,iBAAiB,OACb,MAAM,QAAQ,IACd,OAAO,UAAU,WACf,QACA,IAAI,KAAK,KAAK,EAAE,QAAQ;CAChC,OAAO,OAAO,SAAS,IAAI,IAAI,OAAO;AACxC;AAEA,eAAsB,yBAAoD;CACxE,MAAM,OAAO,MAAM,gBAAgB;CACnC,IAAI,CAAC,MACH,MAAM,IAAI,SAAS;EACjB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;CAEH,OAAO;AACT;AAEA,eAAsB,iBACpB,UACA,UACA,OAAiB,YAAY,GACF;CAC3B,MAAM,OAAO,MAAM,uBAAuB;CAC1C,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,YAAY,MAAM,mBAAmB,UAAU,UAAU,MAAM,IAAI;CACzE,IAAI,CAAC,WAAW,OAAO;CACvB,MAAM,iBAAiB,SAAS;CAChC,OAAO;AACT"}
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":";;;AAOA,MAAa,yBAAyB,CAAC,CAAC;AAmCxC,SAAgB,cAAwB;CACtC,OAAO,EAAE,OAAO,WAAW,MAAM,KAAK,UAAU,EAAE;AACpD;AAEA,eAAsB,kBACpB,QACA,OAAiB,YAAY,GACP;CACtB,MAAM,aAA2D,CAC/D,GAAG,uBAAuB,KAAK,OAAO;EACpC,QAAQ,SAAS,EAAE;EACnB,SAAS;CACX,EAAE,GACF;EAAE,QAAQ;EAAS,SAAS;CAAK,CACnC;CACA,KAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,WAAW,GAAG,SAAS,KAAK,OAAO;EACzC,IAAI;EACJ,IAAI;GACF,MAAM,MAAM,KAAK,MAAM,UAAU,EAAE,QAAQ,MAAM,CAAC;EACpD,QAAQ;GACN;EACF;EACA,IAAI,IAAI,IAAI;GACV,MAAM,aAAa,KAAK,WAAW,uBAAuB;GAI1D,OAAO;IAAE;IAAQ,SAFf,KAAK,WAAW,UAAU,SAAS,GAAG,OAAO,QAAQ;IAE7B,UAAA,GADN,SAAS,KAAK;IACE;GAAW;EACjD;CACF;CACA,MAAM,IAAI,SAAS;EACjB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;AACH;AAEA,eAAsB,kBACpB,UACA,UACA,QAAQ,wBACR,OAAiB,YAAY,GACA;CAC7B,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,eAAe;EACtD,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GAAE,WAAW;GAAU;EAAM,CAAC;CACrD,CAAC;CACD,IAAI,CAAC,IAAI,IAAI;EACX,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,IAAI,KAAK;EACxB,QAAQ;GACN,OAAO,MAAM,IAAI,KAAK;EACxB;EACA,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS,+BAA+B,IAAI,OAAO;GACnD,SAAS;EACX,CAAC;CACH;CACA,OAAQ,MAAM,IAAI,KAAK;AACzB;AAUA,eAAsB,gBACpB,UACA,UACA,YACA,MAC8B;CAC9B,MAAM,OAAO,KAAK,QAAQ,YAAY;CACtC,MAAM,WAAW,KAAK,IAAI,IAAI,KAAK,eAAe;CAClD,IAAI,aAAa,KAAK,IAAI,GAAG,KAAK,WAAW,IAAI;CACjD,OAAO,KAAK,IAAI,IAAI,UAAU;EAC5B,IAAI,KAAK,QAAQ,SACf,MAAM,IAAI,SAAS;GAAE,MAAM;GAAe,SAAS;EAAkB,CAAC;EAExE,MAAM,MAAM,YAAY,KAAK,MAAM;EACnC,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,gBAAgB;GACvD,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU;IACnB,YAAY;IACZ,aAAa;IACb,WAAW;GACb,CAAC;EACH,CAAC;EACD,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,IAAI,KAAK;EACxB,QAAQ;GACN,OAAO;EACT;EACA,IAAI,IAAI,MAAM,MAAM,cAClB,OAAO;EAET,MAAM,MAAM,MAAM;EAClB,IAAI,QAAQ,yBAAyB;GACnC,KAAK,SAAS,SAAS;GACvB;EACF;EACA,IAAI,QAAQ,aAAa;GACvB,cAAc;GACd,KAAK,SAAS,WAAW;GACzB;EACF;EACA,IAAI,QAAQ,iBACV,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS;EACX,CAAC;EAEH,IAAI,QAAQ,iBACV,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS;GACT,MAAM;EACR,CAAC;EAEH,MAAM,IAAI,SAAS;GACjB,MAAM;GACN,SAAS,gCAAgC,IAAI,OAAO;GACpD,SAAS;EACX,CAAC;CACH;CACA,MAAM,IAAI,SAAS;EACjB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;AACH;AAEA,SAAS,MAAM,IAAY,QAAqC;CAC9D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,QAAQ,SAAS,OAAO,uBAAO,IAAI,MAAM,SAAS,CAAC;EACvD,MAAM,QAAQ,iBAAiB;GAC7B,IAAI,QAAQ,OAAO,oBAAoB,SAAS,OAAO;GACvD,QAAQ;EACV,GAAG,EAAE;EACL,MAAM,gBAAgB;GACpB,aAAa,KAAK;GAClB,uBAAO,IAAI,MAAM,SAAS,CAAC;EAC7B;EACA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC3D,CAAC;AACH;AAEA,SAAgB,cAAc,MAA6C;CACzE,OAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK;EACpB,YAAY,KAAK,IAAI,IAAI,KAAK,aAAa;EAC3C,MAAM,KAAK;CACb;AACF;AAEA,SAAgB,eACd,MACA,WAAW,KACF;CACT,OAAO,KAAK,aAAa,KAAK,IAAI,IAAI;AACxC;AAEA,eAAsB,mBACpB,UACA,UACA,MACA,OAAiB,YAAY,GACK;CAClC,IAAI,CAAC,KAAK,eACR,OAAO,oBAAoB,UAAU,MAAM,IAAI;CAEjD,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,SAAS;EAChD,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,KAAK,UAAU;GACnB,YAAY;GACZ,eAAe,KAAK;GACpB,WAAW;EACb,CAAC;CACH,CAAC;CACD,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,MAAM,OAAQ,MAAM,IAAI,KAAK;CAC7B,IAAI,CAAC,MAAM,cAAc,OAAO;CAChC,OAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK,iBAAiB,KAAK;EAC1C,YAAY,KAAK,IAAI,IAAI,KAAK,aAAa;EAC3C,MAAM,KAAK,QAAQ,KAAK;CAC1B;AACF;AAEA,eAAe,oBACb,UACA,MACA,MACkC;CAClC,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS,eAAe;EACtD,QAAQ;EACR,SAAS;GACP,QAAQ;GACR,eAAe,UAAU,KAAK;EAChC;CACF,CAAC;CACD,IAAI,CAAC,IAAI,IAAI,OAAO;CACpB,MAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,YAAY,IAAI;CAM/C,MAAM,YAAY,mBAAmB,MAAM,SAAS,SAAS;CAC7D,IAAI,CAAC,aAAa,aAAa,KAAK,YAAY,OAAO;CACvD,OAAO;EACL,cAAc,IAAI,QAAQ,IAAI,gBAAgB,KAAK,KAAK;EACxD,eAAe,KAAK;EACpB,YAAY;EACZ,MAAM,MAAM,QAAQ,KAAK;CAC3B;AACF;AAEA,SAAS,mBAAmB,OAA2C;CACrE,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,MAAM,OACJ,iBAAiB,OACb,MAAM,QAAQ,IACd,OAAO,UAAU,WACf,QACA,IAAI,KAAK,KAAK,EAAE,QAAQ;CAChC,OAAO,OAAO,SAAS,IAAI,IAAI,OAAO;AACxC;AAEA,eAAsB,yBAAoD;CACxE,MAAM,OAAO,MAAM,gBAAgB;CACnC,IAAI,CAAC,MACH,MAAM,IAAI,SAAS;EACjB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;CAEH,OAAO;AACT;AAEA,eAAsB,iBACpB,UACA,UACA,OAAiB,YAAY,GACF;CAC3B,MAAM,OAAO,MAAM,uBAAuB;CAC1C,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,YAAY,MAAM,mBAAmB,UAAU,UAAU,MAAM,IAAI;CACzE,IAAI,CAAC,WAAW,OAAO;CACvB,MAAM,iBAAiB,SAAS;CAChC,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { t as OutputOptions } from "../output-CTnO1lYt.mjs";
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
@@ -916,9 +916,11 @@ interface GlobalOptions {
916
916
  apiUrl?: string;
917
917
  token?: string;
918
918
  apiKey?: string;
919
+ lang?: string;
919
920
  quiet?: boolean;
920
921
  verbose?: boolean;
921
922
  dryRun?: boolean;
923
+ profile?: string;
922
924
  }
923
925
  declare function readGlobalOptions(cmd: Command): OutputOptions;
924
926
  declare function readGlobalFlags(cmd: Command): GlobalOptions;