@rafiqui987/markode 0.3.0 → 0.4.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 +11 -2
- package/dist/cli.js +194 -88
- package/dist/web/assets/{abnfDiagram-WEYOIA3C-uyv195L6.js → abnfDiagram-WEYOIA3C-B-3Lcktj.js} +1 -1
- package/dist/web/assets/{arc-CnHtXLAe.js → arc-B1vQQwbr.js} +1 -1
- package/dist/web/assets/{architecture-WOLXFQ4H-tPJ2P-p7.js → architecture-WOLXFQ4H-D8_qhlNG.js} +1 -1
- package/dist/web/assets/{architectureDiagram-ZHVYKT53-C-WCNTHv.js → architectureDiagram-ZHVYKT53-B4uGFnQg.js} +1 -1
- package/dist/web/assets/{blockDiagram-RJAE7QA3-DRgAHQJ9.js → blockDiagram-RJAE7QA3-B1PRI2Fk.js} +1 -1
- package/dist/web/assets/{c4Diagram-XDPPPE54-DBBsZpdC.js → c4Diagram-XDPPPE54-BOSiNgn_.js} +1 -1
- package/dist/web/assets/channel-ggr2Mduz.js +1 -0
- package/dist/web/assets/{chunk-2E7WHVMD-CQSFul1o.js → chunk-2E7WHVMD-B2TT-9T2.js} +1 -1
- package/dist/web/assets/{chunk-4EMNKR2T-DHeQ05G5.js → chunk-4EMNKR2T-BJStHb31.js} +1 -1
- package/dist/web/assets/{chunk-55XCU76K-C9qNkR8H.js → chunk-55XCU76K-CGSdzfU6.js} +1 -1
- package/dist/web/assets/{chunk-7S6XSTFO-C_2BTQ4f.js → chunk-7S6XSTFO-D7U3aXUK.js} +1 -1
- package/dist/web/assets/{chunk-AQDTSKZB-DZLt6T8h.js → chunk-AQDTSKZB-C6fkREWV.js} +1 -1
- package/dist/web/assets/{chunk-DSNLSHJZ-6EnWiUHw.js → chunk-DSNLSHJZ-C0Wri32R.js} +1 -1
- package/dist/web/assets/{chunk-HAGFOTED-C4TrjvWC.js → chunk-HAGFOTED-dS3yjqSY.js} +1 -1
- package/dist/web/assets/{chunk-HNE4Q36P-Bhz3xzgF.js → chunk-HNE4Q36P-B0HJeFYe.js} +1 -1
- package/dist/web/assets/{chunk-NQOKRVIF-fn9o0p_a.js → chunk-NQOKRVIF-DCR-ZD6p.js} +1 -1
- package/dist/web/assets/{chunk-UHW7PFBP-y_Cnbpso.js → chunk-UHW7PFBP-DvXVrsgg.js} +1 -1
- package/dist/web/assets/{chunk-VQ5W62QI-BDGHstYR.js → chunk-VQ5W62QI-mU_IBCXT.js} +1 -1
- package/dist/web/assets/{chunk-WQQL4CDK-DVgns4LS.js → chunk-WQQL4CDK-DnyUQt3W.js} +1 -1
- package/dist/web/assets/{chunk-XXDRQBXY-z-xFeaXQ.js → chunk-XXDRQBXY-DNhISORq.js} +1 -1
- package/dist/web/assets/{chunk-YZO67HFG-O3X4dDhT.js → chunk-YZO67HFG-C_oEIdWe.js} +1 -1
- package/dist/web/assets/{chunk-ZAUX2LTX-dMpe16to.js → chunk-ZAUX2LTX-D5aMvPiJ.js} +1 -1
- package/dist/web/assets/{chunk-ZCHMQSOR-DmywVAUg.js → chunk-ZCHMQSOR-B5J6sRpu.js} +1 -1
- package/dist/web/assets/{classDiagram-v2-4MYWR3UA-DNFxIeoH.js → classDiagram-v2-4MYWR3UA-D4-o3iga.js} +1 -1
- package/dist/web/assets/{cose-bilkent-JH36ORCC-C78U5xAn.js → cose-bilkent-JH36ORCC-CqNUB7G6.js} +1 -1
- package/dist/web/assets/{cynefin-EF2NZ3EQ-53hzJKH9.js → cynefin-EF2NZ3EQ-BLBtXLl2.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-WF24UDRP-D7qakSAO.js → cynefinDiagram-WF24UDRP-BlytpdKL.js} +1 -1
- package/dist/web/assets/{dagre-NK4GIZVX-JnnDdmH0.js → dagre-NK4GIZVX-DIUkOqo_.js} +1 -1
- package/dist/web/assets/{diagram-CBK3MWTC-C9dCT7nt.js → diagram-CBK3MWTC-4WJqLswq.js} +1 -1
- package/dist/web/assets/{diagram-I5XIIV2D-BODrA_Tz.js → diagram-I5XIIV2D-s1EUO6xt.js} +1 -1
- package/dist/web/assets/{diagram-ODIL2UCW-ByZtzdTi.js → diagram-ODIL2UCW-GvgDR4t8.js} +1 -1
- package/dist/web/assets/{diagram-PFBEJUXE-Dc3cgafk.js → diagram-PFBEJUXE-B6J9SedW.js} +1 -1
- package/dist/web/assets/{diagram-RJA3I4VV-BnTNuzvk.js → diagram-RJA3I4VV-CqA7qcNf.js} +1 -1
- package/dist/web/assets/{diagram-TYUNFSNR-fbw92djE.js → diagram-TYUNFSNR-Cln9POlx.js} +1 -1
- package/dist/web/assets/{dist-CC7LN-Hh.js → dist--fImHE6g.js} +1 -1
- package/dist/web/assets/{dist-BeUBXLX5.js → dist-Bg-QtHy9.js} +1 -1
- package/dist/web/assets/{dist-CrUcXP5H.js → dist-BghTt_37.js} +1 -1
- package/dist/web/assets/{dist-BfedOYsq.js → dist-CU6eXj3N.js} +1 -1
- package/dist/web/assets/{dist-tv7yScoP.js → dist-CVF0b6Yz.js} +1 -1
- package/dist/web/assets/{dist-EljmWMub.js → dist-Cd1DR3ly.js} +1 -1
- package/dist/web/assets/{dist-CfeLR_Q4.js → dist-CoioyMmF.js} +1 -1
- package/dist/web/assets/{dist-D0R-qVeY.js → dist-CykDxBaO.js} +1 -1
- package/dist/web/assets/{dist-DcGC8X1E.js → dist-DpqggjJG.js} +1 -1
- package/dist/web/assets/{dist-CAttduN3.js → dist-YudOLIZ5.js} +1 -1
- package/dist/web/assets/{dist-BPKiY16E.js → dist-xGXJDvke.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-V4P3TTPT-DPsfv1uf.js → ebnfDiagram-V4P3TTPT-BusXM5tX.js} +1 -1
- package/dist/web/assets/{elk-QONTBCTQ-DoINDZCB.js → elk-QONTBCTQ-B9oq7Tkq.js} +1 -1
- package/dist/web/assets/{erDiagram-KYQIGQAA-ChPWyTRi.js → erDiagram-KYQIGQAA-CmL9SAnh.js} +1 -1
- package/dist/web/assets/{eventmodeling-K75KTNOO-B1BhcpV6.js → eventmodeling-K75KTNOO-R6NmozzN.js} +1 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-CkUCRx3C.js +1 -0
- package/dist/web/assets/{ganttDiagram-R7GPNTJY-BpChuBNI.js → ganttDiagram-R7GPNTJY-DRa2V_Qe.js} +1 -1
- package/dist/web/assets/{gitGraph-VSP46ZUC-BhIFPres.js → gitGraph-VSP46ZUC-CdFMO2LD.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-PDYL7XHY-CHejLr2o.js → gitGraphDiagram-PDYL7XHY-C-xmUbl1.js} +1 -1
- package/dist/web/assets/{index-CafleYLD.js → index-BFpTDsCp.js} +7 -7
- package/dist/web/assets/{info-OHQRW6UA-p_XOG9kY.js → info-OHQRW6UA-CnPUMAqS.js} +1 -1
- package/dist/web/assets/{infoDiagram-MVDRKAX5-BjEI8OE0.js → infoDiagram-MVDRKAX5-DxouwrN7.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-HPEHV2GE-B_7LEH0q.js → ishikawaDiagram-HPEHV2GE-qrLpOq3_.js} +1 -1
- package/dist/web/assets/{journeyDiagram-BOSHW7O7-DprR7PC1.js → journeyDiagram-BOSHW7O7-BDTfHWHd.js} +1 -1
- package/dist/web/assets/{kanban-definition-FNSA4HC5-Dgn1OKTQ.js → kanban-definition-FNSA4HC5-CTBgSpp6.js} +1 -1
- package/dist/web/assets/{line-CsP7ezWR.js → line-D8R5RRtF.js} +1 -1
- package/dist/web/assets/{linear-DutM4-eX.js → linear-BCL193tG.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-CRHYFOhU.js → mermaid-parser.core-C1XIxySl.js} +3 -3
- package/dist/web/assets/{mermaid.core-C-ITLWC9.js → mermaid.core-C-385ykR.js} +4 -4
- package/dist/web/assets/{mindmap-definition-6OY57UG3-B3gANyDJ.js → mindmap-definition-6OY57UG3-BnCvQFLY.js} +1 -1
- package/dist/web/assets/{packet-JDAUHWVQ-0H1cA20q.js → packet-JDAUHWVQ-DVCRx-wc.js} +1 -1
- package/dist/web/assets/{pegDiagram-ECIUJNBX-BUxriBaM.js → pegDiagram-ECIUJNBX-CIDVz_ln.js} +1 -1
- package/dist/web/assets/{pie-XZMESJXO-CSOHv2zM.js → pie-XZMESJXO-BHXvmR-y.js} +1 -1
- package/dist/web/assets/{pieDiagram-GNMPY74T-B7B_ijMh.js → pieDiagram-GNMPY74T-Bb_l0cKd.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-D57K4MC3-CuDX_hzm.js → quadrantDiagram-D57K4MC3-Dqq4AGQt.js} +1 -1
- package/dist/web/assets/{radar-ABXABTNO-Bhdhmyn3.js → radar-ABXABTNO-Ba5vnnQ7.js} +1 -1
- package/dist/web/assets/{railroad-I3PHUGI6-D8J0asfg.js → railroad-I3PHUGI6-CxoMtJ5h.js} +1 -1
- package/dist/web/assets/{railroad-abnf-LNEFI6M7-DSkwXAS5.js → railroad-abnf-LNEFI6M7-BXxErwiU.js} +1 -1
- package/dist/web/assets/{railroad-ebnf-DWYD2UWJ-CUA5ZluT.js → railroad-ebnf-DWYD2UWJ-C_9H5arK.js} +1 -1
- package/dist/web/assets/{railroad-peg-JR7BVNR7-DOmtcJJ_.js → railroad-peg-JR7BVNR7-50KHJESB.js} +1 -1
- package/dist/web/assets/{railroadDiagram-Y72NRBL7-Bfa59bDk.js → railroadDiagram-Y72NRBL7-9w1amWMc.js} +1 -1
- package/dist/web/assets/{requirementDiagram-EQYSW5RA-TqWBSttg.js → requirementDiagram-EQYSW5RA-D3XygoSz.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-SY24JHO5-DXW4vZ49.js → sankeyDiagram-SY24JHO5-C68crPY9.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-BLEHNHQU-bR6akpDb.js → sequenceDiagram-BLEHNHQU-BNcJ1wfc.js} +1 -1
- package/dist/web/assets/{src-DWy4o1sh.js → src-okGi814h.js} +1 -1
- package/dist/web/assets/{stateDiagram-v2-A6IESKWZ-G-L9fY1U.js → stateDiagram-v2-A6IESKWZ-DX7rAO1j.js} +1 -1
- package/dist/web/assets/{swimlanes-B3ENRKFQ-Bvz0uaIY.js → swimlanes-B3ENRKFQ-DGSmz_ab.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-D8bRrq1P.js +8 -0
- package/dist/web/assets/{timeline-definition-JXYKGLGB-CLuowMKW.js → timeline-definition-JXYKGLGB-5COU2ouX.js} +1 -1
- package/dist/web/assets/{treeView-D4JQ5SDB-P9KKVCsV.js → treeView-D4JQ5SDB-BgrtWP9w.js} +1 -1
- package/dist/web/assets/{treemap-SAJKECNS-CJHxQeBA.js → treemap-SAJKECNS-CLZcktWl.js} +1 -1
- package/dist/web/assets/{usecaseDiagram-WPOLBNNX-Dp-yumhB.js → usecaseDiagram-WPOLBNNX-BbJ7QjYR.js} +1 -1
- package/dist/web/assets/{vennDiagram-Q3Y3IDCW-WYfAKKah.js → vennDiagram-Q3Y3IDCW-IhZTghFG.js} +1 -1
- package/dist/web/assets/{wardley-7MLQ67FV-16kL0OjA.js → wardley-7MLQ67FV-XjDz9vxn.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-IVAIDKWA-CKpO66Ll.js → wardleyDiagram-IVAIDKWA-DTcLR0T-.js} +1 -1
- package/dist/web/assets/{xychartDiagram-4URNHDU4-BAlKKoEI.js → xychartDiagram-4URNHDU4-Dja-ugvw.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/templates/proposals/SKILL.md +2 -1
- package/templates/skill/SKILL.md +18 -1
- package/dist/web/assets/channel-COq7V7WB.js +0 -1
- package/dist/web/assets/flowDiagram-IUGLLWBU-DZzMwlmj.js +0 -1
- package/dist/web/assets/swimlanesDiagram-AYQRV4AK-D8Xb8aOe.js +0 -8
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# markode
|
|
1
|
+
# <img src="docs/assets/logo.svg" alt="" width="32" height="32" align="top"> markode
|
|
2
2
|
|
|
3
3
|
[](https://github.com/rafiqui1987/markode/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
@@ -369,6 +369,8 @@ missing description, a name that doesn't match its folder…).
|
|
|
369
369
|
markode # set up the project if needed, then open the editor
|
|
370
370
|
markode serve # open the editor without the setup
|
|
371
371
|
markode serve --port 5000 --no-open
|
|
372
|
+
markode url [<doc.md>] # the editor's link (or a doc's); starts it in the background if needed
|
|
373
|
+
markode stop # stop the editor started in the background
|
|
372
374
|
markode init # run the setup again: only adds modules (your defaults, if saved)
|
|
373
375
|
markode init --profile full # no questions: viewer, agent or full
|
|
374
376
|
markode init --yes # no questions: your defaults, or viewer + AI agent
|
|
@@ -390,10 +392,11 @@ Installing it also makes the Claude Code hooks faster than running through `npx`
|
|
|
390
392
|
|
|
391
393
|
The AI agent module installs the `markode` skill where the project's agents read skills
|
|
392
394
|
(`.claude/skills/` for Claude Code, `.agents/skills/` for Codex, Copilot, Gemini and others). It tells
|
|
393
|
-
the agent how to write Markdown that markode presents well, and gives it
|
|
395
|
+
the agent how to write Markdown that markode presents well, and gives it three commands:
|
|
394
396
|
|
|
395
397
|
| Command | What the agent does | When |
|
|
396
398
|
|---|---|---|
|
|
399
|
+
| `/markode` | Starts the editor in the background if it isn't running and gives you its link (`markode url`) | When you want to open markode |
|
|
397
400
|
| `/markode sync` | Keeps the docs consistent: applies answered proposals, ticks phases from their tasks, checks the projects table and broken links (`markode check`) | Start and end of a session, after editing docs |
|
|
398
401
|
| `/markode translate` | Translates what is behind; asks before changing any source file | Only when you ask |
|
|
399
402
|
|
|
@@ -404,6 +407,11 @@ enough. In the main doc it writes only between its markers:
|
|
|
404
407
|
- `markode:workflow`: how the project plans its work (the markode method, OpenSpec or Spec Kit);
|
|
405
408
|
- `markode:proposals`: how the agent writes proposals and answers comments.
|
|
406
409
|
|
|
410
|
+
**Links to docs.** When the agent points you to a doc (a proposal it wrote, a doc it changed), it gives
|
|
411
|
+
a link you can click in the terminal: `http://localhost:4319/open?path=docs/proposals/003-auth.md`. The
|
|
412
|
+
markode tab you already have open shows that doc, and the tab the click opened closes itself, so you
|
|
413
|
+
keep a single markode tab. With no markode tab open, the link opens the editor on that doc.
|
|
414
|
+
|
|
407
415
|
When the agent writes a proposal, it runs `markode wait <proposal>` in the background: it waits
|
|
408
416
|
without using tokens until you press "Done, send my answer", then hands the agent your choices, open
|
|
409
417
|
questions and comments. If no session was open, the next one applies the answer first.
|
|
@@ -426,6 +434,7 @@ module, in projects that use Claude Code, markode adds three hooks to `.claude/s
|
|
|
426
434
|
Without a global install the command is `npx -y --prefer-offline @rafiqui987/markode`.
|
|
427
435
|
|
|
428
436
|
```bash
|
|
437
|
+
markode url [<doc.md>] # link to the editor or a doc (starts it if needed)
|
|
429
438
|
markode check [--json] # what /markode sync fixes
|
|
430
439
|
markode wait <proposal.md> # wait for the user's answer, then print it
|
|
431
440
|
markode changes [--json] # the user's edits and comments since last told
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs21 from "fs";
|
|
5
|
+
import path21 from "path";
|
|
6
6
|
|
|
7
7
|
// src/core/changes.ts
|
|
8
8
|
import crypto from "crypto";
|
|
@@ -1832,12 +1832,69 @@ function keepLanguage(root2, rel) {
|
|
|
1832
1832
|
return detected;
|
|
1833
1833
|
}
|
|
1834
1834
|
|
|
1835
|
-
// src/core/
|
|
1835
|
+
// src/core/server.ts
|
|
1836
|
+
import { spawn } from "child_process";
|
|
1836
1837
|
import fs17 from "fs";
|
|
1837
1838
|
import path17 from "path";
|
|
1839
|
+
var serverFile = (root2) => path17.join(stateDir(root2), "server.json");
|
|
1840
|
+
var isAlive = (pid) => {
|
|
1841
|
+
try {
|
|
1842
|
+
process.kill(pid, 0);
|
|
1843
|
+
return true;
|
|
1844
|
+
} catch {
|
|
1845
|
+
return false;
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
function recordServer(root2, port) {
|
|
1849
|
+
const file = serverFile(root2);
|
|
1850
|
+
fs17.mkdirSync(path17.dirname(file), { recursive: true });
|
|
1851
|
+
assertInside(file);
|
|
1852
|
+
fs17.writeFileSync(file, JSON.stringify({ port, pid: process.pid }));
|
|
1853
|
+
process.once("exit", () => {
|
|
1854
|
+
if (runningServer(root2)?.pid === process.pid) fs17.rmSync(file, { force: true });
|
|
1855
|
+
});
|
|
1856
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, () => process.exit(130));
|
|
1857
|
+
}
|
|
1858
|
+
function runningServer(root2) {
|
|
1859
|
+
try {
|
|
1860
|
+
const s = JSON.parse(fs17.readFileSync(serverFile(root2), "utf8"));
|
|
1861
|
+
return Number.isInteger(s.port) && Number.isInteger(s.pid) && isAlive(s.pid) ? { port: s.port, pid: s.pid } : null;
|
|
1862
|
+
} catch {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async function responds(port) {
|
|
1867
|
+
try {
|
|
1868
|
+
return (await fetch(`http://127.0.0.1:${port}/api/info`, { signal: AbortSignal.timeout(1e3) })).ok;
|
|
1869
|
+
} catch {
|
|
1870
|
+
return false;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
async function ensureServer(root2, cli2, port) {
|
|
1874
|
+
const running = runningServer(root2);
|
|
1875
|
+
if (running && await responds(running.port)) return running;
|
|
1876
|
+
const child = spawn(process.execPath, [...process.execArgv, cli2, "serve", "--no-open", "--port", String(port)], {
|
|
1877
|
+
cwd: root2,
|
|
1878
|
+
env: { ...process.env, MARKODE_ROOT: root2 },
|
|
1879
|
+
detached: true,
|
|
1880
|
+
stdio: "ignore"
|
|
1881
|
+
});
|
|
1882
|
+
child.unref();
|
|
1883
|
+
for (let i = 0; i < 100; i++) {
|
|
1884
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1885
|
+
const s = runningServer(root2);
|
|
1886
|
+
if (s && s.pid === child.pid && await responds(s.port)) return s;
|
|
1887
|
+
}
|
|
1888
|
+
throw new Error("The editor did not start; run `markode serve` to see why.");
|
|
1889
|
+
}
|
|
1890
|
+
var editorUrl = (port, doc) => `http://localhost:${port}/${doc ? `open?path=${encodeURIComponent(doc).replace(/%2F/g, "/")}` : ""}`;
|
|
1891
|
+
|
|
1892
|
+
// src/core/wait.ts
|
|
1893
|
+
import fs18 from "fs";
|
|
1894
|
+
import path18 from "path";
|
|
1838
1895
|
import YAML5 from "yaml";
|
|
1839
|
-
var waitsDir = (root2) =>
|
|
1840
|
-
var pidFile = (root2, rel) =>
|
|
1896
|
+
var waitsDir = (root2) => path18.join(stateDir(root2), "waits");
|
|
1897
|
+
var pidFile = (root2, rel) => path18.join(waitsDir(root2), `${encodeURIComponent(rel)}.pid`);
|
|
1841
1898
|
var frontmatter2 = (text3) => {
|
|
1842
1899
|
try {
|
|
1843
1900
|
return YAML5.parse(/^---\r?\n([\s\S]*?)\r?\n---/.exec(text3)?.[1] ?? "", { logLevel: "error" }) ?? {};
|
|
@@ -1845,7 +1902,7 @@ var frontmatter2 = (text3) => {
|
|
|
1845
1902
|
return {};
|
|
1846
1903
|
}
|
|
1847
1904
|
};
|
|
1848
|
-
var
|
|
1905
|
+
var isAlive2 = (pid) => {
|
|
1849
1906
|
try {
|
|
1850
1907
|
process.kill(pid, 0);
|
|
1851
1908
|
return true;
|
|
@@ -1856,27 +1913,27 @@ var isAlive = (pid) => {
|
|
|
1856
1913
|
function waitingOn(root2) {
|
|
1857
1914
|
let names2 = [];
|
|
1858
1915
|
try {
|
|
1859
|
-
names2 =
|
|
1916
|
+
names2 = fs18.readdirSync(waitsDir(root2)).filter((f) => f.endsWith(".pid"));
|
|
1860
1917
|
} catch {
|
|
1861
1918
|
}
|
|
1862
1919
|
return names2.flatMap((f) => {
|
|
1863
|
-
const pid = Number(
|
|
1864
|
-
return
|
|
1920
|
+
const pid = Number(fs18.readFileSync(path18.join(waitsDir(root2), f), "utf8"));
|
|
1921
|
+
return isAlive2(pid) ? [decodeURIComponent(f.slice(0, -4))] : [];
|
|
1865
1922
|
});
|
|
1866
1923
|
}
|
|
1867
1924
|
function waitForAnswer(root2, rel) {
|
|
1868
|
-
const abs =
|
|
1869
|
-
|
|
1925
|
+
const abs = path18.join(root2, rel);
|
|
1926
|
+
fs18.mkdirSync(waitsDir(root2), { recursive: true });
|
|
1870
1927
|
assertInside(pidFile(root2, rel));
|
|
1871
|
-
|
|
1872
|
-
const cleanup = () =>
|
|
1928
|
+
fs18.writeFileSync(pidFile(root2, rel), String(process.pid));
|
|
1929
|
+
const cleanup = () => fs18.rmSync(pidFile(root2, rel), { force: true });
|
|
1873
1930
|
process.once("exit", cleanup);
|
|
1874
1931
|
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, () => process.exit(130));
|
|
1875
1932
|
return new Promise((resolve) => {
|
|
1876
1933
|
const poll = () => {
|
|
1877
1934
|
let text3;
|
|
1878
1935
|
try {
|
|
1879
|
-
text3 =
|
|
1936
|
+
text3 = fs18.readFileSync(abs, "utf8");
|
|
1880
1937
|
} catch {
|
|
1881
1938
|
return resolve("gone");
|
|
1882
1939
|
}
|
|
@@ -1890,7 +1947,7 @@ function waitForAnswer(root2, rel) {
|
|
|
1890
1947
|
}
|
|
1891
1948
|
var COMMENT = /^\s*<!-- (comment|reply) by (.+?), (\d{4}-\d{2}-\d{2})(?:, on "((?:[^"\\]|\\.)*)")?:\s?([\s\S]*?)\s*-->\s*$/;
|
|
1892
1949
|
function answerSummary(root2, rel) {
|
|
1893
|
-
const text3 =
|
|
1950
|
+
const text3 = fs18.readFileSync(path18.join(root2, rel), "utf8");
|
|
1894
1951
|
const fm = frontmatter2(text3);
|
|
1895
1952
|
const body = text3.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
1896
1953
|
const lines = body.split("\n");
|
|
@@ -1964,9 +2021,10 @@ function answerSummary(root2, rel) {
|
|
|
1964
2021
|
}
|
|
1965
2022
|
|
|
1966
2023
|
// src/server/index.ts
|
|
1967
|
-
import
|
|
2024
|
+
import crypto3 from "crypto";
|
|
2025
|
+
import fs19 from "fs";
|
|
1968
2026
|
import http from "http";
|
|
1969
|
-
import
|
|
2027
|
+
import path19 from "path";
|
|
1970
2028
|
import { watch } from "chokidar";
|
|
1971
2029
|
import open from "open";
|
|
1972
2030
|
import pc from "picocolors";
|
|
@@ -2034,7 +2092,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2034
2092
|
if (typeof rel !== "string" || !rel.trim()) throw new HttpError(400, "Expected a path");
|
|
2035
2093
|
const abs = safeResolve(root2, rel);
|
|
2036
2094
|
if (abs === root2) throw new HttpError(400, "Refusing to delete the project root");
|
|
2037
|
-
if (!
|
|
2095
|
+
if (!fs19.existsSync(abs)) throw new HttpError(404, "Not found");
|
|
2038
2096
|
return abs;
|
|
2039
2097
|
};
|
|
2040
2098
|
const translationFile = (lang, rel) => {
|
|
@@ -2046,7 +2104,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2046
2104
|
"GET /api/info": () => {
|
|
2047
2105
|
const c = config();
|
|
2048
2106
|
return {
|
|
2049
|
-
name:
|
|
2107
|
+
name: path19.basename(root2),
|
|
2050
2108
|
mainDoc: c.mainDoc ?? null,
|
|
2051
2109
|
sourceLang: sourceLang(),
|
|
2052
2110
|
langs: langs().filter((l) => l !== sourceLang()),
|
|
@@ -2073,7 +2131,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2073
2131
|
}
|
|
2074
2132
|
if (isLang(body.sourceLang) && body.sourceLang !== sourceLang()) {
|
|
2075
2133
|
if (!c.mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
|
|
2076
|
-
applyRulesToFile(
|
|
2134
|
+
applyRulesToFile(path19.join(root2, c.mainDoc), void 0, body.sourceLang);
|
|
2077
2135
|
}
|
|
2078
2136
|
writeConfig(root2, c);
|
|
2079
2137
|
return { ...c, langs: c.langs.filter((l) => l !== sourceLang()) };
|
|
@@ -2094,20 +2152,20 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2094
2152
|
"GET /api/waiting": () => ({ paths: waitingOn(root2) }),
|
|
2095
2153
|
"GET /api/settings": () => readSettings(),
|
|
2096
2154
|
"PUT /api/settings": (_, body) => writeSettings(body),
|
|
2097
|
-
"GET /api/tree": (
|
|
2098
|
-
const l = viewLang(
|
|
2155
|
+
"GET /api/tree": (url3) => {
|
|
2156
|
+
const l = viewLang(url3.searchParams.get("lang"));
|
|
2099
2157
|
const sync2 = l ? Object.fromEntries(allStatuses(root2, l).map((e) => [e.path, e.status])) : {};
|
|
2100
2158
|
const foreign = featureState(root2).translations ? Object.fromEntries(foreignFiles(root2, sourceLang()).map((f) => [f.path, f.detected])) : {};
|
|
2101
2159
|
return { tree: scanTree(root2), git: gitStatuses(root2), sync: sync2, foreign };
|
|
2102
2160
|
},
|
|
2103
2161
|
"POST /api/keep-language": (_, body) => ({ lang: keepLanguage(root2, mdPath(body.path)) }),
|
|
2104
|
-
"GET /api/file": (
|
|
2105
|
-
const rel = mdPath(
|
|
2106
|
-
const l = viewLang(
|
|
2107
|
-
const source =
|
|
2162
|
+
"GET /api/file": (url3) => {
|
|
2163
|
+
const rel = mdPath(url3.searchParams.get("path"));
|
|
2164
|
+
const l = viewLang(url3.searchParams.get("lang"));
|
|
2165
|
+
const source = fs19.readFileSync(safeResolve(root2, rel), "utf8");
|
|
2108
2166
|
if (l) {
|
|
2109
2167
|
const tPath = translationFile(l, rel);
|
|
2110
|
-
if (
|
|
2168
|
+
if (fs19.existsSync(tPath)) return { path: rel, content: fs19.readFileSync(tPath, "utf8"), target: "translation", status: fileStatus(root2, l, rel) };
|
|
2111
2169
|
return { path: rel, content: source, target: "source", status: "missing" };
|
|
2112
2170
|
}
|
|
2113
2171
|
return { path: rel, content: source, target: "source", status: null };
|
|
@@ -2116,19 +2174,19 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2116
2174
|
const rel = mdPath(body.path);
|
|
2117
2175
|
const l = viewLang(body.lang);
|
|
2118
2176
|
const file = body.target === "translation" && l ? translationFile(l, rel) : safeResolve(root2, rel);
|
|
2119
|
-
|
|
2177
|
+
fs19.mkdirSync(path19.dirname(file), { recursive: true });
|
|
2120
2178
|
const content = String(body.content ?? "");
|
|
2121
|
-
if (!l && featureState(root2).agent) logSave(root2, rel,
|
|
2122
|
-
|
|
2179
|
+
if (!l && featureState(root2).agent) logSave(root2, rel, fs19.existsSync(file) ? fs19.readFileSync(file, "utf8") : "", content);
|
|
2180
|
+
fs19.writeFileSync(file, content);
|
|
2123
2181
|
return { ok: true, status: l ? fileStatus(root2, l, rel) : null };
|
|
2124
2182
|
},
|
|
2125
2183
|
"POST /api/file": (_, body) => {
|
|
2126
2184
|
const rel = mdPath(body.path);
|
|
2127
2185
|
const file = safeResolve(root2, rel);
|
|
2128
|
-
if (
|
|
2129
|
-
|
|
2130
|
-
const title =
|
|
2131
|
-
|
|
2186
|
+
if (fs19.existsSync(file)) throw new HttpError(409, "File already exists");
|
|
2187
|
+
fs19.mkdirSync(path19.dirname(file), { recursive: true });
|
|
2188
|
+
const title = path19.basename(rel, ".md").replace(/^\d+[-_]/, "").replace(/[-_]/g, " ");
|
|
2189
|
+
fs19.writeFileSync(file, `# ${title.charAt(0).toUpperCase() + title.slice(1)}
|
|
2132
2190
|
|
|
2133
2191
|
`);
|
|
2134
2192
|
return { ok: true, path: rel };
|
|
@@ -2138,20 +2196,20 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2138
2196
|
if (typeof body.path !== "string" || !body.path.trim()) throw new HttpError(400, "Expected a path");
|
|
2139
2197
|
const dir = safeResolve(root2, body.path);
|
|
2140
2198
|
if (dir === root2) throw new HttpError(400, "Expected a path");
|
|
2141
|
-
if (
|
|
2142
|
-
|
|
2143
|
-
return { ok: true, path: toPosix(
|
|
2199
|
+
if (fs19.existsSync(dir)) throw new HttpError(409, "A file or folder with that name already exists");
|
|
2200
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
2201
|
+
return { ok: true, path: toPosix(path19.relative(root2, dir)) };
|
|
2144
2202
|
},
|
|
2145
|
-
"GET /api/delete-preview": (
|
|
2146
|
-
const abs = deletable(
|
|
2147
|
-
if (!
|
|
2148
|
-
const files =
|
|
2203
|
+
"GET /api/delete-preview": (url3) => {
|
|
2204
|
+
const abs = deletable(url3.searchParams.get("path"));
|
|
2205
|
+
if (!fs19.statSync(abs).isDirectory()) return { kind: "file", files: 1, md: 1 };
|
|
2206
|
+
const files = fs19.readdirSync(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile());
|
|
2149
2207
|
return { kind: "dir", files: files.length, md: files.filter((e) => e.name.toLowerCase().endsWith(".md")).length };
|
|
2150
2208
|
},
|
|
2151
|
-
"DELETE /api/path": (
|
|
2152
|
-
const rel = toPosix(
|
|
2153
|
-
|
|
2154
|
-
for (const l of config().langs)
|
|
2209
|
+
"DELETE /api/path": (url3) => {
|
|
2210
|
+
const rel = toPosix(path19.relative(root2, deletable(url3.searchParams.get("path"))));
|
|
2211
|
+
fs19.rmSync(safeResolve(root2, rel), { recursive: true });
|
|
2212
|
+
for (const l of config().langs) fs19.rmSync(translationFile(l, rel), { recursive: true, force: true });
|
|
2155
2213
|
return { ok: true };
|
|
2156
2214
|
},
|
|
2157
2215
|
"POST /api/ignore": (_, body) => {
|
|
@@ -2160,7 +2218,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2160
2218
|
setIgnored(root2, body.path, body.kind === "dir" ? "dir" : "file", !!body.ignored);
|
|
2161
2219
|
return { ok: true };
|
|
2162
2220
|
},
|
|
2163
|
-
"GET /api/diff": (
|
|
2221
|
+
"GET /api/diff": (url3) => ({ diff: gitDiff(root2, mdPath(url3.searchParams.get("path"))) }),
|
|
2164
2222
|
"GET /api/dashboard": () => {
|
|
2165
2223
|
const sdd = summarizeSdd(root2);
|
|
2166
2224
|
const shown = sddPrefixes(sdd);
|
|
@@ -2168,12 +2226,12 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2168
2226
|
},
|
|
2169
2227
|
"GET /api/rules": () => {
|
|
2170
2228
|
const mainDoc = readConfig(root2)?.mainDoc;
|
|
2171
|
-
const text3 = mainDoc &&
|
|
2229
|
+
const text3 = mainDoc && fs19.existsSync(path19.join(root2, mainDoc)) ? fs19.readFileSync(path19.join(root2, mainDoc), "utf8") : "";
|
|
2172
2230
|
return { rules: listRules(), applied: appliedRuleIds(text3), mainDoc: mainDoc ?? null };
|
|
2173
2231
|
},
|
|
2174
2232
|
"PUT /api/rules": (_, body) => saveRule(body),
|
|
2175
|
-
"DELETE /api/rules": (
|
|
2176
|
-
deleteRule(
|
|
2233
|
+
"DELETE /api/rules": (url3) => {
|
|
2234
|
+
deleteRule(url3.searchParams.get("id") ?? "");
|
|
2177
2235
|
return { ok: true };
|
|
2178
2236
|
},
|
|
2179
2237
|
// Global skills: reads and writes are limited to SKILL.md of discovered skill folders.
|
|
@@ -2182,14 +2240,14 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2182
2240
|
if (typeof body.name !== "string" || !Array.isArray(body.tags)) throw new HttpError(400, "Expected name and tags");
|
|
2183
2241
|
return { tags: setSkillTags(body.name, body.tags.map(String)) };
|
|
2184
2242
|
},
|
|
2185
|
-
"GET /api/skills/file": (
|
|
2186
|
-
const skill = getSkill(root2,
|
|
2187
|
-
return { path: skill.key, content:
|
|
2243
|
+
"GET /api/skills/file": (url3) => {
|
|
2244
|
+
const skill = getSkill(root2, url3.searchParams.get("key"));
|
|
2245
|
+
return { path: skill.key, content: fs19.readFileSync(path19.join(skill.key, "SKILL.md"), "utf8"), target: "source", status: null };
|
|
2188
2246
|
},
|
|
2189
2247
|
"PUT /api/skills/file": (_, body) => {
|
|
2190
2248
|
const skill = getSkill(root2, body.key);
|
|
2191
2249
|
if (skill.managed) throw new HttpError(403, "This skill is managed by the app that installed it");
|
|
2192
|
-
|
|
2250
|
+
fs19.writeFileSync(path19.join(skill.key, "SKILL.md"), String(body.content ?? ""));
|
|
2193
2251
|
return { ok: true, status: null };
|
|
2194
2252
|
},
|
|
2195
2253
|
"POST /api/skills/install": (_, body) => {
|
|
@@ -2202,7 +2260,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2202
2260
|
"POST /api/rules/apply": (_, body) => {
|
|
2203
2261
|
const mainDoc = readConfig(root2)?.mainDoc;
|
|
2204
2262
|
if (!mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
|
|
2205
|
-
applyRulesToFile(
|
|
2263
|
+
applyRulesToFile(path19.join(root2, mainDoc), body.ids ?? []);
|
|
2206
2264
|
return { ok: true };
|
|
2207
2265
|
}
|
|
2208
2266
|
};
|
|
@@ -2213,11 +2271,11 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2213
2271
|
res.writeHead(403).end("Forbidden");
|
|
2214
2272
|
return;
|
|
2215
2273
|
}
|
|
2216
|
-
const
|
|
2217
|
-
const route = routes[`${req.method} ${
|
|
2274
|
+
const url3 = new URL(req.url ?? "/", "http://localhost");
|
|
2275
|
+
const route = routes[`${req.method} ${url3.pathname}`];
|
|
2218
2276
|
if (route) {
|
|
2219
2277
|
try {
|
|
2220
|
-
const result = await route(
|
|
2278
|
+
const result = await route(url3, req.method === "GET" || req.method === "DELETE" ? {} : await readBody(req));
|
|
2221
2279
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
|
|
2222
2280
|
} catch (err) {
|
|
2223
2281
|
const status = err instanceof HttpError ? err.status : err.code === "ENOENT" ? 404 : 500;
|
|
@@ -2225,8 +2283,9 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2225
2283
|
}
|
|
2226
2284
|
return;
|
|
2227
2285
|
}
|
|
2228
|
-
if (req.method === "GET" &&
|
|
2229
|
-
|
|
2286
|
+
if (req.method === "GET" && url3.pathname === "/api/asset") return serveAsset(url3.searchParams.get("path") ?? "", res);
|
|
2287
|
+
if (req.method === "GET" && url3.pathname === "/open") return openDoc(url3.searchParams.get("path") ?? "", res);
|
|
2288
|
+
serveStatic(url3.pathname, res);
|
|
2230
2289
|
});
|
|
2231
2290
|
function serveAsset(rel, res) {
|
|
2232
2291
|
let file;
|
|
@@ -2236,13 +2295,33 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2236
2295
|
res.writeHead(404).end();
|
|
2237
2296
|
return;
|
|
2238
2297
|
}
|
|
2239
|
-
const type = IMAGE_MIME[
|
|
2240
|
-
if (!type || !
|
|
2298
|
+
const type = IMAGE_MIME[path19.extname(file).toLowerCase()];
|
|
2299
|
+
if (!type || !fs19.existsSync(file) || !fs19.statSync(file).isFile()) {
|
|
2241
2300
|
res.writeHead(404).end();
|
|
2242
2301
|
return;
|
|
2243
2302
|
}
|
|
2244
2303
|
res.writeHead(200, { "content-type": type, "cache-control": "no-cache", "content-security-policy": "sandbox; frame-ancestors 'none'" });
|
|
2245
|
-
|
|
2304
|
+
fs19.createReadStream(file).pipe(res);
|
|
2305
|
+
}
|
|
2306
|
+
function openDoc(rel, res) {
|
|
2307
|
+
let doc;
|
|
2308
|
+
try {
|
|
2309
|
+
doc = mdPath(rel);
|
|
2310
|
+
} catch {
|
|
2311
|
+
res.writeHead(400).end("Expected a .md path");
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
const hash2 = `/#/${encodeURI(doc)}`;
|
|
2315
|
+
if (!wss.clients.size) {
|
|
2316
|
+
res.writeHead(302, { location: hash2 }).end();
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
broadcast({ type: "open", path: doc });
|
|
2320
|
+
const text3 = OPENED_TEXT[readSettings().uiLang] ?? OPENED_TEXT.en;
|
|
2321
|
+
const page = `<!doctype html><meta charset="utf-8"><title>markode</title><body style="font:15px system-ui;padding:2rem;color-scheme:light dark">
|
|
2322
|
+
<p>${text3.shown}</p><p><a href="${hash2}">${text3.here}</a></p><script>window.close()</script>`;
|
|
2323
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "content-security-policy": "frame-ancestors 'none'; script-src 'sha256-" + CLOSE_HASH + "'" });
|
|
2324
|
+
res.end(page);
|
|
2246
2325
|
}
|
|
2247
2326
|
const actualPort = await listen(server, port);
|
|
2248
2327
|
const wss = new WebSocketServer({ server, path: "/ws", verifyClient: ({ req }) => isLocalRequest(req) });
|
|
@@ -2256,27 +2335,34 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2256
2335
|
// Unreadable folders (e.g. caches owned by another user) are skipped instead of failing.
|
|
2257
2336
|
ignorePermissionErrors: true,
|
|
2258
2337
|
ignored: (p3, stats) => {
|
|
2259
|
-
const rel = toPosix(
|
|
2338
|
+
const rel = toPosix(path19.relative(root2, p3));
|
|
2260
2339
|
if (rel === ".markode") return false;
|
|
2261
2340
|
if (rel.startsWith(".markode/")) return !rel.startsWith(".markode/i18n") && rel !== ".markode/state.json" && rel !== ".markode/config.json";
|
|
2262
2341
|
return skips(rel, stats ? stats.isDirectory() : void 0);
|
|
2263
2342
|
}
|
|
2264
2343
|
}).on("error", (err) => console.warn(pc.yellow(` file watcher: ${err.message}`))).on("all", (event, file) => {
|
|
2265
|
-
const abs =
|
|
2344
|
+
const abs = path19.resolve(file);
|
|
2266
2345
|
if (!abs.endsWith(".md") && !abs.endsWith(".json") && !abs.endsWith(".markodeignore") && event !== "addDir" && event !== "unlinkDir") return;
|
|
2267
|
-
let rel = toPosix(
|
|
2268
|
-
const i18n = toPosix(
|
|
2346
|
+
let rel = toPosix(path19.relative(root2, abs));
|
|
2347
|
+
const i18n = toPosix(path19.relative(root2, path19.join(stateDir(root2), "i18n")));
|
|
2269
2348
|
if (rel.startsWith(i18n + "/")) rel = rel.split("/").slice(3).join("/");
|
|
2270
2349
|
broadcast({ type: "fs", event, path: rel });
|
|
2271
2350
|
});
|
|
2272
|
-
|
|
2351
|
+
recordServer(root2, actualPort);
|
|
2352
|
+
const url2 = `http://localhost:${actualPort}`;
|
|
2273
2353
|
console.log(`
|
|
2274
|
-
${pc.bold("markode")} ${pc.dim("\u2192")} ${pc.cyan(
|
|
2354
|
+
${pc.bold("markode")} ${pc.dim("\u2192")} ${pc.cyan(url2)} ${pc.dim(root2)}
|
|
2275
2355
|
`);
|
|
2276
|
-
if (!
|
|
2277
|
-
else if (openBrowser) open(
|
|
2356
|
+
if (!fs19.existsSync(WEB_DIR)) console.log(pc.yellow(" Web UI not built: run `pnpm build`, or `pnpm dev` and open http://localhost:4320\n"));
|
|
2357
|
+
else if (openBrowser) open(url2).catch(() => {
|
|
2278
2358
|
});
|
|
2279
2359
|
}
|
|
2360
|
+
var OPENED_TEXT = {
|
|
2361
|
+
en: { shown: "Opened in your markode tab. You can close this one.", here: "Open it here instead" },
|
|
2362
|
+
es: { shown: "Abierto en tu pesta\xF1a de markode. Puedes cerrar esta.", here: "Abrirlo aqu\xED" },
|
|
2363
|
+
fr: { shown: "Ouvert dans ton onglet markode. Tu peux fermer celui-ci.", here: "L\u2019ouvrir ici" }
|
|
2364
|
+
};
|
|
2365
|
+
var CLOSE_HASH = crypto3.createHash("sha256").update("window.close()").digest("base64");
|
|
2280
2366
|
function listen(server, port) {
|
|
2281
2367
|
return new Promise((resolve, reject) => {
|
|
2282
2368
|
server.once("error", (err) => {
|
|
@@ -2287,19 +2373,19 @@ function listen(server, port) {
|
|
|
2287
2373
|
});
|
|
2288
2374
|
}
|
|
2289
2375
|
function serveStatic(pathname, res) {
|
|
2290
|
-
let file =
|
|
2291
|
-
if (!file.startsWith(WEB_DIR) || !
|
|
2292
|
-
if (!
|
|
2376
|
+
let file = path19.join(WEB_DIR, path19.normalize(pathname).replace(/^(\.\.[/\\])+/, ""));
|
|
2377
|
+
if (!file.startsWith(WEB_DIR) || !fs19.existsSync(file) || fs19.statSync(file).isDirectory()) file = path19.join(WEB_DIR, "index.html");
|
|
2378
|
+
if (!fs19.existsSync(file)) {
|
|
2293
2379
|
res.writeHead(404).end("Web UI not built. Run `pnpm build`.");
|
|
2294
2380
|
return;
|
|
2295
2381
|
}
|
|
2296
|
-
res.writeHead(200, { "content-type": MIME[
|
|
2297
|
-
|
|
2382
|
+
res.writeHead(200, { "content-type": MIME[path19.extname(file)] ?? "application/octet-stream", "content-security-policy": "frame-ancestors 'none'" });
|
|
2383
|
+
fs19.createReadStream(file).pipe(res);
|
|
2298
2384
|
}
|
|
2299
2385
|
|
|
2300
2386
|
// src/cli/init.ts
|
|
2301
|
-
import
|
|
2302
|
-
import
|
|
2387
|
+
import fs20 from "fs";
|
|
2388
|
+
import path20 from "path";
|
|
2303
2389
|
import * as p from "@clack/prompts";
|
|
2304
2390
|
import pc2 from "picocolors";
|
|
2305
2391
|
function check(value) {
|
|
@@ -2310,12 +2396,12 @@ function check(value) {
|
|
|
2310
2396
|
return value;
|
|
2311
2397
|
}
|
|
2312
2398
|
async function offerClaudeLink(root2, d) {
|
|
2313
|
-
if (
|
|
2399
|
+
if (fs20.existsSync(path20.join(root2, "CLAUDE.md"))) return;
|
|
2314
2400
|
const link2 = d ? d.mainDoc === "CLAUDE.md" : check(await p.confirm({ message: "Claude Code reads CLAUDE.md, not AGENTS.md. Add a CLAUDE.md that imports AGENTS.md?", initialValue: true }));
|
|
2315
2401
|
if (link2 && linkAgentsDoc(root2)) p.log.success("Created CLAUDE.md (imports AGENTS.md)");
|
|
2316
2402
|
}
|
|
2317
2403
|
async function chooseMainDoc(root2, d) {
|
|
2318
|
-
const existing = MAIN_DOCS.filter((d2) =>
|
|
2404
|
+
const existing = MAIN_DOCS.filter((d2) => fs20.existsSync(path20.join(root2, d2)) && !isPointer(fs20.readFileSync(path20.join(root2, d2), "utf8"), d2));
|
|
2319
2405
|
if (d) {
|
|
2320
2406
|
if (existing.length === 1 && existing[0] === "AGENTS.md") await offerClaudeLink(root2, d);
|
|
2321
2407
|
if (existing.length) return existing.includes(d.mainDoc) ? d.mainDoc : existing[0];
|
|
@@ -2432,7 +2518,7 @@ async function chooseAreas(root2) {
|
|
|
2432
2518
|
return areas;
|
|
2433
2519
|
}
|
|
2434
2520
|
async function chooseKind(root2, dir) {
|
|
2435
|
-
const guess = guessKind(
|
|
2521
|
+
const guess = guessKind(path20.join(root2, dir));
|
|
2436
2522
|
if (!guess.unclear) return guess.kind;
|
|
2437
2523
|
const where = dir ? `${dir}/` : "this project";
|
|
2438
2524
|
const seen = guess.signals.length ? `found: ${guess.signals.join(", ")}` : "no code yet";
|
|
@@ -2445,7 +2531,7 @@ async function chooseKind(root2, dir) {
|
|
|
2445
2531
|
);
|
|
2446
2532
|
}
|
|
2447
2533
|
async function askProfile(root2, dir, kind) {
|
|
2448
|
-
const base = detectProfile(
|
|
2534
|
+
const base = detectProfile(path20.join(root2, dir), kind);
|
|
2449
2535
|
if (kind === "app") return base;
|
|
2450
2536
|
const where = dir ? ` (${dir}/)` : "";
|
|
2451
2537
|
if (kind === "library") return { kind, published: check(await p.confirm({ message: `Is the library${where} published (npm, PyPI\u2026)?`, initialValue: base.published })) };
|
|
@@ -2522,7 +2608,7 @@ async function chooseWorkflow(root2, mainDoc, previous, quiet) {
|
|
|
2522
2608
|
const profile = areas.length ? void 0 : await askProfile(root2, "", await chooseKind(root2, ""));
|
|
2523
2609
|
const created = scaffoldMethod(root2, mainDoc, areas);
|
|
2524
2610
|
installMethodSkills(root2);
|
|
2525
|
-
applyWorkflowToFile(
|
|
2611
|
+
applyWorkflowToFile(path20.join(root2, mainDoc), "markode", methodSection(areas, root2, { profile, contracts, existing }));
|
|
2526
2612
|
const big = !areas.length && flattenFiles(scanTree(root2)).length > 40;
|
|
2527
2613
|
const layout = areas.length ? "structured" : !big ? "general" : check(await p.select({ message: PROPOSALS_QUESTION, options: PROPOSAL_OPTIONS }));
|
|
2528
2614
|
activateProposals(root2, layout, mainDoc);
|
|
@@ -2587,10 +2673,10 @@ async function runInit(root2, plan2) {
|
|
|
2587
2673
|
let workflow = previous?.workflow ?? "none";
|
|
2588
2674
|
if (has2("agent")) {
|
|
2589
2675
|
mainDoc = await chooseMainDoc(root2, d);
|
|
2590
|
-
const mainFile =
|
|
2676
|
+
const mainFile = path20.join(root2, mainDoc);
|
|
2591
2677
|
if (has2("dashboard")) workflow = await chooseWorkflow(root2, mainDoc, previous?.workflow, d);
|
|
2592
2678
|
if (workflow === "openspec" || workflow === "speckit") applyWorkflowToFile(mainFile, workflow);
|
|
2593
|
-
const mainText =
|
|
2679
|
+
const mainText = fs20.readFileSync(mainFile, "utf8");
|
|
2594
2680
|
const sourceLang = d ? SOURCE_LANG_MARK.test(mainText) ? sourceLangOf(mainText) : detectedLang : check(await p.select({ message: SOURCE_LANG_QUESTION, options: LANG_OPTIONS, initialValue: SOURCE_LANG_MARK.test(mainText) ? sourceLangOf(mainText) : detectedLang }));
|
|
2595
2681
|
const rules = listRules();
|
|
2596
2682
|
const applied = appliedRuleIds(mainText);
|
|
@@ -2605,7 +2691,7 @@ async function runInit(root2, plan2) {
|
|
|
2605
2691
|
required: false
|
|
2606
2692
|
})
|
|
2607
2693
|
);
|
|
2608
|
-
for (const dir of targets) applyRulesToFile(contentDoc(
|
|
2694
|
+
for (const dir of targets) applyRulesToFile(contentDoc(path20.join(root2, dir)), selected, sourceLang);
|
|
2609
2695
|
}
|
|
2610
2696
|
installAgentSkill(root2);
|
|
2611
2697
|
const proposals = proposalsState(root2);
|
|
@@ -2752,6 +2838,8 @@ Usage
|
|
|
2752
2838
|
markode features list the modules and whether they are on in this project
|
|
2753
2839
|
markode features <module> on|off turn a module on or off (same as Settings \u2192 Features)
|
|
2754
2840
|
markode serve open the editor without running init
|
|
2841
|
+
markode url [<doc.md>] the editor's link (or a doc's), starting it in the background if it isn't running
|
|
2842
|
+
markode stop stop the editor running in the background for this project
|
|
2755
2843
|
markode translate choose languages to review the docs in (or: markode --translate)
|
|
2756
2844
|
markode check [--json] what /markode sync fixes: answered proposals, phases vs tasks, projects table, broken links
|
|
2757
2845
|
markode wait <proposal> wait until the user answers a proposal in markode, then print the answer
|
|
@@ -2785,9 +2873,9 @@ var option = (name) => {
|
|
|
2785
2873
|
var command = args[0] && !args[0].startsWith("-") ? args[0] : "start";
|
|
2786
2874
|
var root = process.env.MARKODE_ROOT ?? process.cwd();
|
|
2787
2875
|
async function main() {
|
|
2788
|
-
if (
|
|
2876
|
+
if (fs21.existsSync(root)) setProjectRoot(root);
|
|
2789
2877
|
if (flag("--help") || flag("-h")) return console.log(HELP);
|
|
2790
|
-
if (flag("--version") || flag("-v")) return console.log(JSON.parse(
|
|
2878
|
+
if (flag("--version") || flag("-v")) return console.log(JSON.parse(fs21.readFileSync(path21.join(PKG_ROOT, "package.json"), "utf8")).version);
|
|
2791
2879
|
const translation = flag("--status") || flag("--mark") || flag("--keep");
|
|
2792
2880
|
if ((command === "sync" || command === "translate") && translation) return sync();
|
|
2793
2881
|
if (command === "translate" || flag("--translate")) return runTranslate(root);
|
|
@@ -2795,6 +2883,8 @@ async function main() {
|
|
|
2795
2883
|
if (command === "wait") return wait();
|
|
2796
2884
|
if (command === "link") return link();
|
|
2797
2885
|
if (command === "changes") return changes();
|
|
2886
|
+
if (command === "url") return url();
|
|
2887
|
+
if (command === "stop") return stop();
|
|
2798
2888
|
if (command === "features") return features();
|
|
2799
2889
|
if (command === "setup") {
|
|
2800
2890
|
await runSetup(false, flag("--custom"));
|
|
@@ -2880,6 +2970,22 @@ function link() {
|
|
|
2880
2970
|
if (flag("--dry-run")) return console.log(missing.map((m) => `would create ${m.path} \u2192 ${m.pointsTo}`).join("\n"));
|
|
2881
2971
|
for (const p3 of linkMainDocs(root)) console.log(`created ${p3}`);
|
|
2882
2972
|
}
|
|
2973
|
+
async function url() {
|
|
2974
|
+
const doc = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
|
|
2975
|
+
if (doc && (!doc.toLowerCase().endsWith(".md") || !fs21.existsSync(path21.join(root, doc)))) {
|
|
2976
|
+
console.error(`Usage: markode url [<doc.md>] (relative to ${root})`);
|
|
2977
|
+
process.exitCode = 1;
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
const { port } = await ensureServer(root, process.argv[1], Number(option("--port") ?? 4319));
|
|
2981
|
+
console.log(editorUrl(port, doc));
|
|
2982
|
+
}
|
|
2983
|
+
function stop() {
|
|
2984
|
+
const running = runningServer(root);
|
|
2985
|
+
if (!running) return console.log("The editor is not running for this project.");
|
|
2986
|
+
process.kill(running.pid);
|
|
2987
|
+
console.log(`Stopped the editor on port ${running.port}.`);
|
|
2988
|
+
}
|
|
2883
2989
|
function changes() {
|
|
2884
2990
|
if (flag("--start")) return startSession(root);
|
|
2885
2991
|
const list = takeChanges(root);
|
|
@@ -2894,7 +3000,7 @@ function changes() {
|
|
|
2894
3000
|
}
|
|
2895
3001
|
async function wait() {
|
|
2896
3002
|
const rel = args[1];
|
|
2897
|
-
if (!rel || !
|
|
3003
|
+
if (!rel || !fs21.existsSync(path21.join(root, rel))) {
|
|
2898
3004
|
console.error(`Usage: markode wait <proposal.md> (relative to ${root})`);
|
|
2899
3005
|
process.exitCode = 1;
|
|
2900
3006
|
return;
|
package/dist/web/assets/{abnfDiagram-WEYOIA3C-uyv195L6.js → abnfDiagram-WEYOIA3C-B-3Lcktj.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-C1XIxySl.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-okGi814h.js";import"./chunk-DSNLSHJZ-C0Wri32R.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-B2TT-9T2.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-C-385ykR.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|