@mx-space/cli 0.6.1 → 0.6.2
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 +14 -0
- package/ROADMAP.md +2 -1
- package/dist/bin/mxs.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{mxs-Bs_7ZKs9.mjs → mxs-BxXhmqqF.mjs} +172 -17
- package/package.json +2 -1
- package/skills/commands-preview.md +58 -0
- package/skills/overview.md +1 -0
package/README.md
CHANGED
|
@@ -485,6 +485,20 @@ Example profile config:
|
|
|
485
485
|
| `6` | Server 5xx failure |
|
|
486
486
|
| `7` | Resource not found |
|
|
487
487
|
|
|
488
|
+
## Preview
|
|
489
|
+
|
|
490
|
+
`mxs preview <file>` renders a LiteXML fragment or `<mxpost>` / `<mxnote>` envelope to HTML and opens it in your default browser. Use it to sanity-check what the article will look like before publishing.
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
mxs preview ./post.xml # open in browser
|
|
494
|
+
mxs preview - < note.xml # read stdin
|
|
495
|
+
mxs preview ./post.xml --theme dark # dark theme
|
|
496
|
+
mxs preview ./post.xml --save out.html # write HTML to file
|
|
497
|
+
mxs preview ./post.xml --print # emit HTML to stdout
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The variant (`article` / `note`) is auto-detected from the envelope root. Override with `--variant` for raw LiteXML fragments. The command does not contact `mx-core` — it's a pure local render via `@haklex/rich-litexml-cli`.
|
|
501
|
+
|
|
488
502
|
## Skill bundle
|
|
489
503
|
|
|
490
504
|
`mxs skill` exposes the bundled AI-agent documentation directly from the CLI binary. Chapters are shipped inside the published `@mx-space/cli` package; liteXML chapters are pulled live from `@haklex/rich-litexml` at runtime (requires `@haklex/rich-litexml@>=0.16.0`).
|
package/ROADMAP.md
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
- Internal rewrite onto Effect-TS (`@effect/cli` + `@effect/platform`). User-facing CLI surface is unchanged — see [`README.md`](./README.md#v030-behavior-changes) for the small list of behavioral adjustments and [`docs/architecture.md`](./docs/architecture.md) for the new internal architecture.
|
|
10
10
|
|
|
11
|
-
## Shipped in v0.
|
|
11
|
+
## Shipped in v0.6
|
|
12
12
|
|
|
13
13
|
- `mxs skill` — bundled AI-agent documentation. List, get, all, search across chapters shipped inside the CLI (`packages/cli/skills/*.md`); liteXML chapters loaded live from the installed `@haklex/rich-litexml` package. Default output is raw markdown for direct context injection.
|
|
14
|
+
- `mxs preview <file>` — local HTML preview of a LiteXML fragment or `<mxpost>` / `<mxnote>` envelope. Wraps `@haklex/rich-litexml-cli` so the output matches the editor renderer.
|
|
14
15
|
|
|
15
16
|
## Next — Comment moderation
|
|
16
17
|
|
package/dist/bin/mxs.mjs
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as defaultMessageFor, c as tagToCode, i as codeForTag, l as toErrorEnvelope, n as defaultGlobalFlags, o as exitCodeForError, r as parseGlobalFlags, s as exitCodeForTag, t as run } from "./mxs-
|
|
1
|
+
import { a as defaultMessageFor, c as tagToCode, i as codeForTag, l as toErrorEnvelope, n as defaultGlobalFlags, o as exitCodeForError, r as parseGlobalFlags, s as exitCodeForTag, t as run } from "./mxs-BxXhmqqF.mjs";
|
|
2
2
|
export { codeForTag, defaultGlobalFlags, defaultMessageFor, exitCodeForError, exitCodeForTag, parseGlobalFlags, run, tagToCode, toErrorEnvelope };
|
|
@@ -24,7 +24,7 @@ import * as ChildProcess from "node:child_process";
|
|
|
24
24
|
import childProcess, { execFile, spawn } from "node:child_process";
|
|
25
25
|
import * as readline from "node:readline";
|
|
26
26
|
import E from "node:readline";
|
|
27
|
-
import
|
|
27
|
+
import process$1, { stdin, stdout } from "node:process";
|
|
28
28
|
import "node:tty";
|
|
29
29
|
//#region ../../node_modules/.pnpm/@effect+platform@0.96.1_effect@3.21.2/node_modules/@effect/platform/dist/esm/Error.js
|
|
30
30
|
/**
|
|
@@ -9042,7 +9042,7 @@ function isInsideContainer() {
|
|
|
9042
9042
|
//#endregion
|
|
9043
9043
|
//#region ../../node_modules/.pnpm/is-wsl@3.1.1/node_modules/is-wsl/index.js
|
|
9044
9044
|
const isWsl = () => {
|
|
9045
|
-
if (
|
|
9045
|
+
if (process$1.platform !== "linux") return false;
|
|
9046
9046
|
if (os.release().toLowerCase().includes("microsoft")) {
|
|
9047
9047
|
if (isInsideContainer()) return false;
|
|
9048
9048
|
return true;
|
|
@@ -9053,11 +9053,11 @@ const isWsl = () => {
|
|
|
9053
9053
|
if (fs.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs.existsSync("/run/WSL")) return !isInsideContainer();
|
|
9054
9054
|
return false;
|
|
9055
9055
|
};
|
|
9056
|
-
var is_wsl_default =
|
|
9056
|
+
var is_wsl_default = process$1.env.__IS_WSL_TEST__ ? isWsl : isWsl();
|
|
9057
9057
|
//#endregion
|
|
9058
9058
|
//#region ../../node_modules/.pnpm/powershell-utils@0.1.0/node_modules/powershell-utils/index.js
|
|
9059
9059
|
const execFile$2 = promisify(childProcess.execFile);
|
|
9060
|
-
const powerShellPath$1 = () => `${
|
|
9060
|
+
const powerShellPath$1 = () => `${process$1.env.SYSTEMROOT || process$1.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
|
9061
9061
|
const executePowerShell = async (command, options = {}) => {
|
|
9062
9062
|
const { powerShellPath: psPath, ...execFileOptions } = options;
|
|
9063
9063
|
const encodedCommand = executePowerShell.encodeCommand(command);
|
|
@@ -9164,7 +9164,7 @@ function defineLazyProperty(object, propertyName, valueGetter) {
|
|
|
9164
9164
|
//#region ../../node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js
|
|
9165
9165
|
const execFileAsync$3 = promisify(execFile);
|
|
9166
9166
|
async function defaultBrowserId() {
|
|
9167
|
-
if (
|
|
9167
|
+
if (process$1.platform !== "darwin") throw new Error("macOS only");
|
|
9168
9168
|
const { stdout } = await execFileAsync$3("defaults", [
|
|
9169
9169
|
"read",
|
|
9170
9170
|
"com.apple.LaunchServices/com.apple.launchservices.secure",
|
|
@@ -9178,7 +9178,7 @@ async function defaultBrowserId() {
|
|
|
9178
9178
|
//#region ../../node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js
|
|
9179
9179
|
const execFileAsync$2 = promisify(execFile);
|
|
9180
9180
|
async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
|
|
9181
|
-
if (
|
|
9181
|
+
if (process$1.platform !== "darwin") throw new Error("macOS only");
|
|
9182
9182
|
const outputArguments = humanReadableOutput ? [] : ["-ss"];
|
|
9183
9183
|
const execOptions = {};
|
|
9184
9184
|
if (signal) execOptions.signal = signal;
|
|
@@ -9289,14 +9289,14 @@ async function defaultBrowser$1(_execFileAsync = execFileAsync$1) {
|
|
|
9289
9289
|
const execFileAsync = promisify(execFile);
|
|
9290
9290
|
const titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
|
|
9291
9291
|
async function defaultBrowser() {
|
|
9292
|
-
if (
|
|
9292
|
+
if (process$1.platform === "darwin") {
|
|
9293
9293
|
const id = await defaultBrowserId();
|
|
9294
9294
|
return {
|
|
9295
9295
|
name: await bundleName(id),
|
|
9296
9296
|
id
|
|
9297
9297
|
};
|
|
9298
9298
|
}
|
|
9299
|
-
if (
|
|
9299
|
+
if (process$1.platform === "linux") {
|
|
9300
9300
|
const { stdout } = await execFileAsync("xdg-mime", [
|
|
9301
9301
|
"query",
|
|
9302
9302
|
"default",
|
|
@@ -9308,18 +9308,18 @@ async function defaultBrowser() {
|
|
|
9308
9308
|
id
|
|
9309
9309
|
};
|
|
9310
9310
|
}
|
|
9311
|
-
if (
|
|
9311
|
+
if (process$1.platform === "win32") return defaultBrowser$1();
|
|
9312
9312
|
throw new Error("Only macOS, Linux, and Windows are supported");
|
|
9313
9313
|
}
|
|
9314
9314
|
//#endregion
|
|
9315
9315
|
//#region ../../node_modules/.pnpm/is-in-ssh@1.0.0/node_modules/is-in-ssh/index.js
|
|
9316
|
-
const isInSsh = Boolean(
|
|
9316
|
+
const isInSsh = Boolean(process$1.env.SSH_CONNECTION || process$1.env.SSH_CLIENT || process$1.env.SSH_TTY);
|
|
9317
9317
|
//#endregion
|
|
9318
9318
|
//#region ../../node_modules/.pnpm/open@11.0.0/node_modules/open/index.js
|
|
9319
9319
|
const fallbackAttemptSymbol = Symbol("fallbackAttempt");
|
|
9320
9320
|
const __dirname = import.meta.url ? path.dirname(fileURLToPath(import.meta.url)) : "";
|
|
9321
9321
|
const localXdgOpenPath = path.join(__dirname, "xdg-open");
|
|
9322
|
-
const { platform: platform$1, arch: arch$1 } =
|
|
9322
|
+
const { platform: platform$1, arch: arch$1 } = process$1;
|
|
9323
9323
|
const tryEachApp = async (apps, opener) => {
|
|
9324
9324
|
if (apps.length === 0) return;
|
|
9325
9325
|
const errors = [];
|
|
@@ -9432,7 +9432,7 @@ const baseOpen = async (options) => {
|
|
|
9432
9432
|
await fs$1.access(localXdgOpenPath, constants.X_OK);
|
|
9433
9433
|
exeLocalXdgOpen = true;
|
|
9434
9434
|
} catch {}
|
|
9435
|
-
command =
|
|
9435
|
+
command = process$1.versions.electron ?? (platform$1 === "android" || isBundled || !exeLocalXdgOpen) ? "xdg-open" : localXdgOpenPath;
|
|
9436
9436
|
}
|
|
9437
9437
|
if (appArguments.length > 0) cliArguments.push(...appArguments);
|
|
9438
9438
|
if (!options.wait) {
|
|
@@ -10889,7 +10889,7 @@ var ht$1 = class extends m {
|
|
|
10889
10889
|
//#endregion
|
|
10890
10890
|
//#region ../../node_modules/.pnpm/@clack+prompts@1.4.0/node_modules/@clack/prompts/dist/index.mjs
|
|
10891
10891
|
function ee() {
|
|
10892
|
-
return
|
|
10892
|
+
return process$1.platform !== "win32" ? process$1.env.TERM !== "linux" : !!process$1.env.CI || !!process$1.env.WT_SESSION || !!process$1.env.TERMINUS_SUBLIME || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
10893
10893
|
}
|
|
10894
10894
|
const tt = ee(), w = (t, i) => tt ? t : i, Tt = w("◆", "*"), at = w("■", "x"), ut = w("▲", "x"), H = w("◇", "o");
|
|
10895
10895
|
const $ = w("│", "|"), x = w("└", "—");
|
|
@@ -27683,14 +27683,14 @@ const decideWriteGate = (resolved, method) => {
|
|
|
27683
27683
|
};
|
|
27684
27684
|
//#endregion
|
|
27685
27685
|
//#region src/domain/version.ts
|
|
27686
|
-
const requireFrom$
|
|
27686
|
+
const requireFrom$4 = createRequire(import.meta.url);
|
|
27687
27687
|
const resolveCliVersion$2 = () => {
|
|
27688
27688
|
for (const candidate of [
|
|
27689
27689
|
"../package.json",
|
|
27690
27690
|
"../../package.json",
|
|
27691
27691
|
"../../../package.json"
|
|
27692
27692
|
]) try {
|
|
27693
|
-
const pkg = requireFrom$
|
|
27693
|
+
const pkg = requireFrom$4(candidate);
|
|
27694
27694
|
if (pkg.name === "@mx-space/cli" && typeof pkg.version === "string") return pkg.version;
|
|
27695
27695
|
} catch {}
|
|
27696
27696
|
return "0.0.0-unknown";
|
|
@@ -31747,6 +31747,150 @@ const postCmd = make$13("post").pipe(withDescription(help$3.description), withSu
|
|
|
31747
31747
|
unpublish
|
|
31748
31748
|
]));
|
|
31749
31749
|
//#endregion
|
|
31750
|
+
//#region src/cli/preview/index.ts
|
|
31751
|
+
registerCommandHelp({
|
|
31752
|
+
name: "preview",
|
|
31753
|
+
description: "render a LiteXML fragment or <mxpost>/<mxnote> envelope to HTML and open it in a browser",
|
|
31754
|
+
isLeaf: true,
|
|
31755
|
+
leafOptions: [
|
|
31756
|
+
{
|
|
31757
|
+
flag: "--theme <light|dark>",
|
|
31758
|
+
description: "HTML theme; default: light"
|
|
31759
|
+
},
|
|
31760
|
+
{
|
|
31761
|
+
flag: "--variant <article|note|comment>",
|
|
31762
|
+
description: "HTML variant; auto-detected from envelope root, default: article"
|
|
31763
|
+
},
|
|
31764
|
+
{
|
|
31765
|
+
flag: "--save <path>",
|
|
31766
|
+
description: "write HTML to <path> instead of opening a browser"
|
|
31767
|
+
},
|
|
31768
|
+
{
|
|
31769
|
+
flag: "--print",
|
|
31770
|
+
description: "emit HTML to stdout instead of opening a browser"
|
|
31771
|
+
}
|
|
31772
|
+
]
|
|
31773
|
+
});
|
|
31774
|
+
const fileArg = text$3({ name: "file" }).pipe(optional$4);
|
|
31775
|
+
const themeOpt = choice("theme", ["light", "dark"]).pipe(optional$2);
|
|
31776
|
+
const variantOpt = choice("variant", [
|
|
31777
|
+
"article",
|
|
31778
|
+
"note",
|
|
31779
|
+
"comment"
|
|
31780
|
+
]).pipe(optional$2);
|
|
31781
|
+
const saveOpt = text$1("save").pipe(optional$2);
|
|
31782
|
+
const printOpt = boolean("print").pipe(optional$2);
|
|
31783
|
+
const requireFrom$3 = createRequire(import.meta.url);
|
|
31784
|
+
const resolveLitexmlBin = () => {
|
|
31785
|
+
try {
|
|
31786
|
+
return requireFrom$3.resolve("@haklex/rich-litexml-cli/dist/cli.mjs");
|
|
31787
|
+
} catch {}
|
|
31788
|
+
const here = import.meta.url;
|
|
31789
|
+
const filePath = here.startsWith("file://") ? here.slice(7) : here;
|
|
31790
|
+
let dir = filePath.slice(0, Math.max(0, filePath.lastIndexOf("/")));
|
|
31791
|
+
for (let i = 0; i < 8; i++) {
|
|
31792
|
+
const candidate = `${dir}/node_modules/@haklex/rich-litexml-cli/dist/cli.mjs`;
|
|
31793
|
+
try {
|
|
31794
|
+
requireFrom$3(`${candidate}`);
|
|
31795
|
+
return candidate;
|
|
31796
|
+
} catch {}
|
|
31797
|
+
const next = dir.slice(0, Math.max(0, dir.lastIndexOf("/")));
|
|
31798
|
+
if (next === dir) break;
|
|
31799
|
+
dir = next;
|
|
31800
|
+
}
|
|
31801
|
+
throw new Generic({
|
|
31802
|
+
message: "cannot resolve @haklex/rich-litexml-cli binary",
|
|
31803
|
+
hint: "reinstall mxs or run `pnpm install` so the dependency is present"
|
|
31804
|
+
});
|
|
31805
|
+
};
|
|
31806
|
+
const detectVariant = (body) => {
|
|
31807
|
+
const trimmed = body.trimStart();
|
|
31808
|
+
if (trimmed.startsWith("<mxpost")) return {
|
|
31809
|
+
contentXml: parseEnvelope(trimmed, "post").contentXml,
|
|
31810
|
+
variant: "article"
|
|
31811
|
+
};
|
|
31812
|
+
if (trimmed.startsWith("<mxnote")) return {
|
|
31813
|
+
contentXml: parseEnvelope(trimmed, "note").contentXml,
|
|
31814
|
+
variant: "note"
|
|
31815
|
+
};
|
|
31816
|
+
return {
|
|
31817
|
+
contentXml: body,
|
|
31818
|
+
variant: "article"
|
|
31819
|
+
};
|
|
31820
|
+
};
|
|
31821
|
+
const spawnLitexml = (bin, input, args, capture) => tryPromise({
|
|
31822
|
+
try: () => new Promise((resolve, reject) => {
|
|
31823
|
+
const child = spawn(process.execPath, [
|
|
31824
|
+
bin,
|
|
31825
|
+
"-",
|
|
31826
|
+
...args
|
|
31827
|
+
], { stdio: capture ? [
|
|
31828
|
+
"pipe",
|
|
31829
|
+
"pipe",
|
|
31830
|
+
"inherit"
|
|
31831
|
+
] : [
|
|
31832
|
+
"pipe",
|
|
31833
|
+
"inherit",
|
|
31834
|
+
"inherit"
|
|
31835
|
+
] });
|
|
31836
|
+
const out = [];
|
|
31837
|
+
if (capture && child.stdout) child.stdout.on("data", (chunk) => out.push(chunk));
|
|
31838
|
+
child.on("error", reject);
|
|
31839
|
+
child.on("exit", (code) => {
|
|
31840
|
+
if (code === 0) resolve(capture ? Buffer.concat(out).toString("utf8") : "");
|
|
31841
|
+
else reject(/* @__PURE__ */ new Error(`litexml exited with code ${code}`));
|
|
31842
|
+
});
|
|
31843
|
+
if (!child.stdin) {
|
|
31844
|
+
reject(/* @__PURE__ */ new Error("failed to open litexml stdin"));
|
|
31845
|
+
return;
|
|
31846
|
+
}
|
|
31847
|
+
child.stdin.write(input);
|
|
31848
|
+
child.stdin.end();
|
|
31849
|
+
}),
|
|
31850
|
+
catch: (err) => new Generic({
|
|
31851
|
+
message: err instanceof Error ? err.message : String(err),
|
|
31852
|
+
cause: err
|
|
31853
|
+
})
|
|
31854
|
+
});
|
|
31855
|
+
const previewCmd = make$13("preview", {
|
|
31856
|
+
file: fileArg,
|
|
31857
|
+
theme: themeOpt,
|
|
31858
|
+
variant: variantOpt,
|
|
31859
|
+
save: saveOpt,
|
|
31860
|
+
print: printOpt
|
|
31861
|
+
}, ({ file, theme, variant, save, print }) => gen(function* () {
|
|
31862
|
+
const editor = yield* Editor;
|
|
31863
|
+
const renderer = yield* Renderer;
|
|
31864
|
+
const filePath = getOrUndefined(file);
|
|
31865
|
+
const raw = yield* editor.readFileOrStdin(filePath);
|
|
31866
|
+
let body;
|
|
31867
|
+
let detectedVariant = "article";
|
|
31868
|
+
try {
|
|
31869
|
+
const det = detectVariant(raw);
|
|
31870
|
+
body = det.contentXml;
|
|
31871
|
+
detectedVariant = det.variant;
|
|
31872
|
+
} catch (err) {
|
|
31873
|
+
return yield* fail$2(err instanceof ValidationXml ? err : new ValidationXml({ message: err instanceof Error ? err.message : String(err) }));
|
|
31874
|
+
}
|
|
31875
|
+
const resolvedVariant = getOrElse(variant, () => detectedVariant);
|
|
31876
|
+
const resolvedTheme = getOrUndefined(theme);
|
|
31877
|
+
const savePath = getOrUndefined(save);
|
|
31878
|
+
const printMode = getOrElse(print, () => false);
|
|
31879
|
+
const args = [
|
|
31880
|
+
"--format",
|
|
31881
|
+
"html",
|
|
31882
|
+
"--variant",
|
|
31883
|
+
resolvedVariant
|
|
31884
|
+
];
|
|
31885
|
+
if (resolvedTheme) args.push("--theme", resolvedTheme);
|
|
31886
|
+
if (savePath) args.push("-o", savePath);
|
|
31887
|
+
else if (!printMode) args.push("--open");
|
|
31888
|
+
const html = yield* spawnLitexml(resolveLitexmlBin(), body, args, printMode);
|
|
31889
|
+
if (printMode) process.stdout.write(html.endsWith("\n") ? html : `${html}\n`);
|
|
31890
|
+
else if (savePath) yield* renderer.emitInfo(`wrote HTML preview to ${savePath}`);
|
|
31891
|
+
else yield* renderer.emitInfo(`opened HTML preview (variant=${resolvedVariant})`);
|
|
31892
|
+
}));
|
|
31893
|
+
//#endregion
|
|
31750
31894
|
//#region src/cli/profile/view.ts
|
|
31751
31895
|
const profileListView = {
|
|
31752
31896
|
kind: "profile-list",
|
|
@@ -34500,6 +34644,7 @@ const GROUP_NAMES = [
|
|
|
34500
34644
|
"comment",
|
|
34501
34645
|
"config",
|
|
34502
34646
|
"skill",
|
|
34647
|
+
"preview",
|
|
34503
34648
|
"update"
|
|
34504
34649
|
];
|
|
34505
34650
|
const isGroupName = (s) => GROUP_NAMES.includes(s);
|
|
@@ -34703,7 +34848,11 @@ const PREFLIGHT_EXEMPT_COMMANDS = [{
|
|
|
34703
34848
|
* Top-level command names that are exempt from the profile.none_active guard.
|
|
34704
34849
|
* Used for commands that should work without a configured server (e.g. `mxs update`).
|
|
34705
34850
|
*/
|
|
34706
|
-
const PREFLIGHT_EXEMPT_TOPLEVEL = new Set([
|
|
34851
|
+
const PREFLIGHT_EXEMPT_TOPLEVEL = new Set([
|
|
34852
|
+
"update",
|
|
34853
|
+
"skill",
|
|
34854
|
+
"preview"
|
|
34855
|
+
]);
|
|
34707
34856
|
/** Returns true if the command is exempt from the profile.none_active guard. */
|
|
34708
34857
|
function isPreflightExempt(input) {
|
|
34709
34858
|
return PREFLIGHT_EXEMPT_PARENTS.has(input.parentName) || input.commandName === "profile" || PREFLIGHT_EXEMPT_TOPLEVEL.has(input.commandName) || PREFLIGHT_EXEMPT_COMMANDS.some((c) => c.parent === input.parentName && c.name === input.commandName);
|
|
@@ -34730,6 +34879,7 @@ const TOPLEVEL_COMMANDS = new Set([
|
|
|
34730
34879
|
"topic",
|
|
34731
34880
|
"config",
|
|
34732
34881
|
"skill",
|
|
34882
|
+
"preview",
|
|
34733
34883
|
"update"
|
|
34734
34884
|
]);
|
|
34735
34885
|
/**
|
|
@@ -35105,6 +35255,7 @@ const rootCmd = make$13("mxs", {}, () => flatMap$1(Renderer, (r) => r.emitInfo("
|
|
|
35105
35255
|
commentCmd,
|
|
35106
35256
|
configCmd,
|
|
35107
35257
|
skillCmd,
|
|
35258
|
+
previewCmd,
|
|
35108
35259
|
updateCmd
|
|
35109
35260
|
]));
|
|
35110
35261
|
const preflight = (flags) => gen(function* () {
|
|
@@ -35150,7 +35301,11 @@ const preflight = (flags) => gen(function* () {
|
|
|
35150
35301
|
parentName: invoked.parentName
|
|
35151
35302
|
}));
|
|
35152
35303
|
});
|
|
35153
|
-
const LEAF_COMMANDS = new Set([
|
|
35304
|
+
const LEAF_COMMANDS = new Set([
|
|
35305
|
+
"update",
|
|
35306
|
+
"skill",
|
|
35307
|
+
"preview"
|
|
35308
|
+
]);
|
|
35154
35309
|
const detectHelpTarget = (rest) => {
|
|
35155
35310
|
const args = rest.slice(2);
|
|
35156
35311
|
if (args.length === 0) return { kind: "root" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mx-space/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Command line interface for mx-space (mx-core) — auth, content, configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mx-space",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@effect/platform-node": "0.106.0",
|
|
55
55
|
"@haklex/rich-headless": "0.13.0",
|
|
56
56
|
"@haklex/rich-litexml": "0.15.2",
|
|
57
|
+
"@haklex/rich-litexml-cli": "0.15.2",
|
|
57
58
|
"@lexical/headless": "^0.44.0",
|
|
58
59
|
"better-auth": "^1.6.9",
|
|
59
60
|
"effect": "3.21.2",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
slug: commands-preview
|
|
3
|
+
title: Preview command
|
|
4
|
+
description: render LiteXML / envelope to HTML and open it in a browser
|
|
5
|
+
order: 39
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Preview command
|
|
9
|
+
|
|
10
|
+
`mxs preview` renders a LiteXML fragment or `<mxpost>` / `<mxnote>` envelope to HTML and (by default) opens it in the system browser. It is a thin wrapper around the `@haklex/rich-litexml-cli` `litexml --format html` pipeline, so the output matches what the editor would render once published.
|
|
11
|
+
|
|
12
|
+
| Command | Behavior |
|
|
13
|
+
| ---------------------------------------- | --------------------------------------------------------------------------- |
|
|
14
|
+
| `mxs preview <file>` | Render the file and open the preview in a browser. |
|
|
15
|
+
| `mxs preview -` | Read LiteXML or envelope from stdin and open the preview. |
|
|
16
|
+
| `mxs preview <file> --print` | Emit HTML to stdout instead of opening a browser. |
|
|
17
|
+
| `mxs preview <file> --save <out.html>` | Write HTML to `<out.html>` instead of opening a browser. |
|
|
18
|
+
|
|
19
|
+
## Flags
|
|
20
|
+
|
|
21
|
+
| Flag | Meaning |
|
|
22
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------ |
|
|
23
|
+
| `--theme <light\|dark>` | HTML theme. Default `light`. |
|
|
24
|
+
| `--variant <article\|note\|comment>` | HTML variant. Auto-detected from the envelope root (`<mxpost>` → `article`, `<mxnote>` → `note`). |
|
|
25
|
+
| `--save <path>` | Write HTML to a file instead of opening the browser. Mutually exclusive with `--print`. |
|
|
26
|
+
| `--print` | Emit HTML to stdout. Mutually exclusive with `--save`. |
|
|
27
|
+
|
|
28
|
+
## Behavior notes
|
|
29
|
+
|
|
30
|
+
- Input format is auto-detected. If the input starts with `<mxpost>` or `<mxnote>`, the envelope is parsed and only the `<content>` body is rendered (envelope `<meta>` such as title, category, tags is **not** reflected in the HTML preview — this command is for visualising the article body, not the listing card).
|
|
31
|
+
- For raw LiteXML fragments (no envelope wrapper), the entire input is treated as the article body.
|
|
32
|
+
- Variant detection only fires when an envelope is supplied. Override with `--variant` when previewing a raw fragment that should render as a note or comment.
|
|
33
|
+
- The command does not contact the `mx-core` server and does not require an active profile.
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Open envelope file in browser
|
|
39
|
+
mxs preview ./post.xml
|
|
40
|
+
|
|
41
|
+
# Pipe from another tool
|
|
42
|
+
some-generator | mxs preview -
|
|
43
|
+
|
|
44
|
+
# Dark theme, save to file
|
|
45
|
+
mxs preview ./note.xml --theme dark --save ./preview.html
|
|
46
|
+
|
|
47
|
+
# Emit HTML to stdout (e.g. for CI)
|
|
48
|
+
mxs preview ./post.xml --print > preview.html
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Failure modes
|
|
52
|
+
|
|
53
|
+
| Symptom | Likely cause |
|
|
54
|
+
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
55
|
+
| `cannot resolve @haklex/rich-litexml-cli binary` | Installed dep is broken; reinstall `@mx-space/cli` or run `pnpm install`. |
|
|
56
|
+
| `Cannot resolve @haklex/rich-compose asset "style.css"` | Outdated `@haklex/rich-compose` (< 0.15.2). Upgrade or reinstall. |
|
|
57
|
+
| `expected root <mxpost>` / `expected root <mxnote>` | Input begins with the wrong envelope root, or the envelope is malformed. Check the root tag. |
|
|
58
|
+
| Browser does not open | `--open` shells out to the system `open`/`xdg-open`/`start`. Use `--save` or `--print` instead. |
|
package/skills/overview.md
CHANGED
|
@@ -26,6 +26,7 @@ The audience is **AI agents**. Pass `--output llm` for raw markdown suitable for
|
|
|
26
26
|
| Server-side options | `commands-config` |
|
|
27
27
|
| Authentication | `commands-auth` |
|
|
28
28
|
| Local profile management | `commands-profile` |
|
|
29
|
+
| Browser preview of LiteXML / envelopes | `commands-preview` |
|
|
29
30
|
| Profiles, env vars, target selection | `auth-config` |
|
|
30
31
|
| Output modes (`--json`, `--output llm`, …) | `output-modes` |
|
|
31
32
|
| Mutation safety, verification, exit codes | `safety` |
|