@rafiqui987/markode 0.4.0 → 0.4.1
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 +6 -0
- package/dist/cli.js +115 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -382,12 +382,18 @@ markode features # which modules are on, with what each one does
|
|
|
382
382
|
markode features live off # turn a module on or off (same as Settings → Features)
|
|
383
383
|
markode translate # choose languages to review the docs in (also: markode --translate)
|
|
384
384
|
markode link # only CLAUDE.md or only AGENTS.md? add the other one, pointing to it
|
|
385
|
+
markode update # install the latest version now (see below)
|
|
385
386
|
markode -v # version
|
|
386
387
|
```
|
|
387
388
|
|
|
388
389
|
To keep it installed: `npm install -g @rafiqui987/markode` (or `pnpm add -g @rafiqui987/markode`).
|
|
389
390
|
Installing it also makes the Claude Code hooks faster than running through `npx`.
|
|
390
391
|
|
|
392
|
+
**A new version right away.** pnpm (and npm with `min-release-age`) wait a day before installing a
|
|
393
|
+
version just published. `markode update` asks the npm registry for the latest version and installs it
|
|
394
|
+
now, with the same package manager that installed markode (`--pnpm` or `--npm` to choose);
|
|
395
|
+
`markode update --check` only says whether there is one.
|
|
396
|
+
|
|
391
397
|
## For AI agents
|
|
392
398
|
|
|
393
399
|
The AI agent module installs the `markode` skill where the project's agents read skills
|
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 fs22 from "fs";
|
|
5
|
+
import path22 from "path";
|
|
6
6
|
|
|
7
7
|
// src/core/changes.ts
|
|
8
8
|
import crypto from "crypto";
|
|
@@ -1889,12 +1889,43 @@ async function ensureServer(root2, cli2, port) {
|
|
|
1889
1889
|
}
|
|
1890
1890
|
var editorUrl = (port, doc) => `http://localhost:${port}/${doc ? `open?path=${encodeURIComponent(doc).replace(/%2F/g, "/")}` : ""}`;
|
|
1891
1891
|
|
|
1892
|
-
// src/core/
|
|
1892
|
+
// src/core/update.ts
|
|
1893
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1893
1894
|
import fs18 from "fs";
|
|
1894
1895
|
import path18 from "path";
|
|
1896
|
+
var PKG = JSON.parse(fs18.readFileSync(path18.join(PKG_ROOT, "package.json"), "utf8"));
|
|
1897
|
+
var CURRENT_VERSION = PKG.version;
|
|
1898
|
+
function installer() {
|
|
1899
|
+
const dir = PKG_ROOT.split(path18.sep).join("/");
|
|
1900
|
+
if (dir.includes("/_npx/")) return "npx";
|
|
1901
|
+
if (!dir.includes("/node_modules/")) return null;
|
|
1902
|
+
return dir.includes("/pnpm/") || dir.includes("/.pnpm/") ? "pnpm" : "npm";
|
|
1903
|
+
}
|
|
1904
|
+
async function latestVersion() {
|
|
1905
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG.name}/latest`, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15e3) });
|
|
1906
|
+
if (!res.ok) throw new Error(`The npm registry answered ${res.status}`);
|
|
1907
|
+
return (await res.json()).version;
|
|
1908
|
+
}
|
|
1909
|
+
function isNewer(a, b) {
|
|
1910
|
+
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
1911
|
+
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
1912
|
+
for (let i = 0; i < 3; i++) if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) > (pb[i] ?? 0);
|
|
1913
|
+
return false;
|
|
1914
|
+
}
|
|
1915
|
+
function installCommand2(via, version) {
|
|
1916
|
+
const spec = `${PKG.name}@${version}`;
|
|
1917
|
+
return via === "pnpm" ? ["pnpm", "add", "-g", spec, "--config.minimum-release-age=0"] : ["npm", "install", "-g", spec, "--min-release-age=0", "--prefer-online"];
|
|
1918
|
+
}
|
|
1919
|
+
function runInstall(cmd) {
|
|
1920
|
+
return spawnSync2(cmd[0], cmd.slice(1), { stdio: "inherit", shell: process.platform === "win32" }).status === 0;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// src/core/wait.ts
|
|
1924
|
+
import fs19 from "fs";
|
|
1925
|
+
import path19 from "path";
|
|
1895
1926
|
import YAML5 from "yaml";
|
|
1896
|
-
var waitsDir = (root2) =>
|
|
1897
|
-
var pidFile = (root2, rel) =>
|
|
1927
|
+
var waitsDir = (root2) => path19.join(stateDir(root2), "waits");
|
|
1928
|
+
var pidFile = (root2, rel) => path19.join(waitsDir(root2), `${encodeURIComponent(rel)}.pid`);
|
|
1898
1929
|
var frontmatter2 = (text3) => {
|
|
1899
1930
|
try {
|
|
1900
1931
|
return YAML5.parse(/^---\r?\n([\s\S]*?)\r?\n---/.exec(text3)?.[1] ?? "", { logLevel: "error" }) ?? {};
|
|
@@ -1913,27 +1944,27 @@ var isAlive2 = (pid) => {
|
|
|
1913
1944
|
function waitingOn(root2) {
|
|
1914
1945
|
let names2 = [];
|
|
1915
1946
|
try {
|
|
1916
|
-
names2 =
|
|
1947
|
+
names2 = fs19.readdirSync(waitsDir(root2)).filter((f) => f.endsWith(".pid"));
|
|
1917
1948
|
} catch {
|
|
1918
1949
|
}
|
|
1919
1950
|
return names2.flatMap((f) => {
|
|
1920
|
-
const pid = Number(
|
|
1951
|
+
const pid = Number(fs19.readFileSync(path19.join(waitsDir(root2), f), "utf8"));
|
|
1921
1952
|
return isAlive2(pid) ? [decodeURIComponent(f.slice(0, -4))] : [];
|
|
1922
1953
|
});
|
|
1923
1954
|
}
|
|
1924
1955
|
function waitForAnswer(root2, rel) {
|
|
1925
|
-
const abs =
|
|
1926
|
-
|
|
1956
|
+
const abs = path19.join(root2, rel);
|
|
1957
|
+
fs19.mkdirSync(waitsDir(root2), { recursive: true });
|
|
1927
1958
|
assertInside(pidFile(root2, rel));
|
|
1928
|
-
|
|
1929
|
-
const cleanup = () =>
|
|
1959
|
+
fs19.writeFileSync(pidFile(root2, rel), String(process.pid));
|
|
1960
|
+
const cleanup = () => fs19.rmSync(pidFile(root2, rel), { force: true });
|
|
1930
1961
|
process.once("exit", cleanup);
|
|
1931
1962
|
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, () => process.exit(130));
|
|
1932
1963
|
return new Promise((resolve) => {
|
|
1933
1964
|
const poll = () => {
|
|
1934
1965
|
let text3;
|
|
1935
1966
|
try {
|
|
1936
|
-
text3 =
|
|
1967
|
+
text3 = fs19.readFileSync(abs, "utf8");
|
|
1937
1968
|
} catch {
|
|
1938
1969
|
return resolve("gone");
|
|
1939
1970
|
}
|
|
@@ -1947,7 +1978,7 @@ function waitForAnswer(root2, rel) {
|
|
|
1947
1978
|
}
|
|
1948
1979
|
var COMMENT = /^\s*<!-- (comment|reply) by (.+?), (\d{4}-\d{2}-\d{2})(?:, on "((?:[^"\\]|\\.)*)")?:\s?([\s\S]*?)\s*-->\s*$/;
|
|
1949
1980
|
function answerSummary(root2, rel) {
|
|
1950
|
-
const text3 =
|
|
1981
|
+
const text3 = fs19.readFileSync(path19.join(root2, rel), "utf8");
|
|
1951
1982
|
const fm = frontmatter2(text3);
|
|
1952
1983
|
const body = text3.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
1953
1984
|
const lines = body.split("\n");
|
|
@@ -2022,9 +2053,9 @@ function answerSummary(root2, rel) {
|
|
|
2022
2053
|
|
|
2023
2054
|
// src/server/index.ts
|
|
2024
2055
|
import crypto3 from "crypto";
|
|
2025
|
-
import
|
|
2056
|
+
import fs20 from "fs";
|
|
2026
2057
|
import http from "http";
|
|
2027
|
-
import
|
|
2058
|
+
import path20 from "path";
|
|
2028
2059
|
import { watch } from "chokidar";
|
|
2029
2060
|
import open from "open";
|
|
2030
2061
|
import pc from "picocolors";
|
|
@@ -2092,7 +2123,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2092
2123
|
if (typeof rel !== "string" || !rel.trim()) throw new HttpError(400, "Expected a path");
|
|
2093
2124
|
const abs = safeResolve(root2, rel);
|
|
2094
2125
|
if (abs === root2) throw new HttpError(400, "Refusing to delete the project root");
|
|
2095
|
-
if (!
|
|
2126
|
+
if (!fs20.existsSync(abs)) throw new HttpError(404, "Not found");
|
|
2096
2127
|
return abs;
|
|
2097
2128
|
};
|
|
2098
2129
|
const translationFile = (lang, rel) => {
|
|
@@ -2104,7 +2135,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2104
2135
|
"GET /api/info": () => {
|
|
2105
2136
|
const c = config();
|
|
2106
2137
|
return {
|
|
2107
|
-
name:
|
|
2138
|
+
name: path20.basename(root2),
|
|
2108
2139
|
mainDoc: c.mainDoc ?? null,
|
|
2109
2140
|
sourceLang: sourceLang(),
|
|
2110
2141
|
langs: langs().filter((l) => l !== sourceLang()),
|
|
@@ -2131,7 +2162,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2131
2162
|
}
|
|
2132
2163
|
if (isLang(body.sourceLang) && body.sourceLang !== sourceLang()) {
|
|
2133
2164
|
if (!c.mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
|
|
2134
|
-
applyRulesToFile(
|
|
2165
|
+
applyRulesToFile(path20.join(root2, c.mainDoc), void 0, body.sourceLang);
|
|
2135
2166
|
}
|
|
2136
2167
|
writeConfig(root2, c);
|
|
2137
2168
|
return { ...c, langs: c.langs.filter((l) => l !== sourceLang()) };
|
|
@@ -2162,10 +2193,10 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2162
2193
|
"GET /api/file": (url3) => {
|
|
2163
2194
|
const rel = mdPath(url3.searchParams.get("path"));
|
|
2164
2195
|
const l = viewLang(url3.searchParams.get("lang"));
|
|
2165
|
-
const source =
|
|
2196
|
+
const source = fs20.readFileSync(safeResolve(root2, rel), "utf8");
|
|
2166
2197
|
if (l) {
|
|
2167
2198
|
const tPath = translationFile(l, rel);
|
|
2168
|
-
if (
|
|
2199
|
+
if (fs20.existsSync(tPath)) return { path: rel, content: fs20.readFileSync(tPath, "utf8"), target: "translation", status: fileStatus(root2, l, rel) };
|
|
2169
2200
|
return { path: rel, content: source, target: "source", status: "missing" };
|
|
2170
2201
|
}
|
|
2171
2202
|
return { path: rel, content: source, target: "source", status: null };
|
|
@@ -2174,19 +2205,19 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2174
2205
|
const rel = mdPath(body.path);
|
|
2175
2206
|
const l = viewLang(body.lang);
|
|
2176
2207
|
const file = body.target === "translation" && l ? translationFile(l, rel) : safeResolve(root2, rel);
|
|
2177
|
-
|
|
2208
|
+
fs20.mkdirSync(path20.dirname(file), { recursive: true });
|
|
2178
2209
|
const content = String(body.content ?? "");
|
|
2179
|
-
if (!l && featureState(root2).agent) logSave(root2, rel,
|
|
2180
|
-
|
|
2210
|
+
if (!l && featureState(root2).agent) logSave(root2, rel, fs20.existsSync(file) ? fs20.readFileSync(file, "utf8") : "", content);
|
|
2211
|
+
fs20.writeFileSync(file, content);
|
|
2181
2212
|
return { ok: true, status: l ? fileStatus(root2, l, rel) : null };
|
|
2182
2213
|
},
|
|
2183
2214
|
"POST /api/file": (_, body) => {
|
|
2184
2215
|
const rel = mdPath(body.path);
|
|
2185
2216
|
const file = safeResolve(root2, rel);
|
|
2186
|
-
if (
|
|
2187
|
-
|
|
2188
|
-
const title =
|
|
2189
|
-
|
|
2217
|
+
if (fs20.existsSync(file)) throw new HttpError(409, "File already exists");
|
|
2218
|
+
fs20.mkdirSync(path20.dirname(file), { recursive: true });
|
|
2219
|
+
const title = path20.basename(rel, ".md").replace(/^\d+[-_]/, "").replace(/[-_]/g, " ");
|
|
2220
|
+
fs20.writeFileSync(file, `# ${title.charAt(0).toUpperCase() + title.slice(1)}
|
|
2190
2221
|
|
|
2191
2222
|
`);
|
|
2192
2223
|
return { ok: true, path: rel };
|
|
@@ -2196,20 +2227,20 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2196
2227
|
if (typeof body.path !== "string" || !body.path.trim()) throw new HttpError(400, "Expected a path");
|
|
2197
2228
|
const dir = safeResolve(root2, body.path);
|
|
2198
2229
|
if (dir === root2) throw new HttpError(400, "Expected a path");
|
|
2199
|
-
if (
|
|
2200
|
-
|
|
2201
|
-
return { ok: true, path: toPosix(
|
|
2230
|
+
if (fs20.existsSync(dir)) throw new HttpError(409, "A file or folder with that name already exists");
|
|
2231
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
2232
|
+
return { ok: true, path: toPosix(path20.relative(root2, dir)) };
|
|
2202
2233
|
},
|
|
2203
2234
|
"GET /api/delete-preview": (url3) => {
|
|
2204
2235
|
const abs = deletable(url3.searchParams.get("path"));
|
|
2205
|
-
if (!
|
|
2206
|
-
const files =
|
|
2236
|
+
if (!fs20.statSync(abs).isDirectory()) return { kind: "file", files: 1, md: 1 };
|
|
2237
|
+
const files = fs20.readdirSync(abs, { recursive: true, withFileTypes: true }).filter((e) => e.isFile());
|
|
2207
2238
|
return { kind: "dir", files: files.length, md: files.filter((e) => e.name.toLowerCase().endsWith(".md")).length };
|
|
2208
2239
|
},
|
|
2209
2240
|
"DELETE /api/path": (url3) => {
|
|
2210
|
-
const rel = toPosix(
|
|
2211
|
-
|
|
2212
|
-
for (const l of config().langs)
|
|
2241
|
+
const rel = toPosix(path20.relative(root2, deletable(url3.searchParams.get("path"))));
|
|
2242
|
+
fs20.rmSync(safeResolve(root2, rel), { recursive: true });
|
|
2243
|
+
for (const l of config().langs) fs20.rmSync(translationFile(l, rel), { recursive: true, force: true });
|
|
2213
2244
|
return { ok: true };
|
|
2214
2245
|
},
|
|
2215
2246
|
"POST /api/ignore": (_, body) => {
|
|
@@ -2226,7 +2257,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2226
2257
|
},
|
|
2227
2258
|
"GET /api/rules": () => {
|
|
2228
2259
|
const mainDoc = readConfig(root2)?.mainDoc;
|
|
2229
|
-
const text3 = mainDoc &&
|
|
2260
|
+
const text3 = mainDoc && fs20.existsSync(path20.join(root2, mainDoc)) ? fs20.readFileSync(path20.join(root2, mainDoc), "utf8") : "";
|
|
2230
2261
|
return { rules: listRules(), applied: appliedRuleIds(text3), mainDoc: mainDoc ?? null };
|
|
2231
2262
|
},
|
|
2232
2263
|
"PUT /api/rules": (_, body) => saveRule(body),
|
|
@@ -2242,12 +2273,12 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2242
2273
|
},
|
|
2243
2274
|
"GET /api/skills/file": (url3) => {
|
|
2244
2275
|
const skill = getSkill(root2, url3.searchParams.get("key"));
|
|
2245
|
-
return { path: skill.key, content:
|
|
2276
|
+
return { path: skill.key, content: fs20.readFileSync(path20.join(skill.key, "SKILL.md"), "utf8"), target: "source", status: null };
|
|
2246
2277
|
},
|
|
2247
2278
|
"PUT /api/skills/file": (_, body) => {
|
|
2248
2279
|
const skill = getSkill(root2, body.key);
|
|
2249
2280
|
if (skill.managed) throw new HttpError(403, "This skill is managed by the app that installed it");
|
|
2250
|
-
|
|
2281
|
+
fs20.writeFileSync(path20.join(skill.key, "SKILL.md"), String(body.content ?? ""));
|
|
2251
2282
|
return { ok: true, status: null };
|
|
2252
2283
|
},
|
|
2253
2284
|
"POST /api/skills/install": (_, body) => {
|
|
@@ -2260,7 +2291,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2260
2291
|
"POST /api/rules/apply": (_, body) => {
|
|
2261
2292
|
const mainDoc = readConfig(root2)?.mainDoc;
|
|
2262
2293
|
if (!mainDoc) throw new HttpError(400, "No main document configured; run `markode init`");
|
|
2263
|
-
applyRulesToFile(
|
|
2294
|
+
applyRulesToFile(path20.join(root2, mainDoc), body.ids ?? []);
|
|
2264
2295
|
return { ok: true };
|
|
2265
2296
|
}
|
|
2266
2297
|
};
|
|
@@ -2295,13 +2326,13 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2295
2326
|
res.writeHead(404).end();
|
|
2296
2327
|
return;
|
|
2297
2328
|
}
|
|
2298
|
-
const type = IMAGE_MIME[
|
|
2299
|
-
if (!type || !
|
|
2329
|
+
const type = IMAGE_MIME[path20.extname(file).toLowerCase()];
|
|
2330
|
+
if (!type || !fs20.existsSync(file) || !fs20.statSync(file).isFile()) {
|
|
2300
2331
|
res.writeHead(404).end();
|
|
2301
2332
|
return;
|
|
2302
2333
|
}
|
|
2303
2334
|
res.writeHead(200, { "content-type": type, "cache-control": "no-cache", "content-security-policy": "sandbox; frame-ancestors 'none'" });
|
|
2304
|
-
|
|
2335
|
+
fs20.createReadStream(file).pipe(res);
|
|
2305
2336
|
}
|
|
2306
2337
|
function openDoc(rel, res) {
|
|
2307
2338
|
let doc;
|
|
@@ -2335,16 +2366,16 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2335
2366
|
// Unreadable folders (e.g. caches owned by another user) are skipped instead of failing.
|
|
2336
2367
|
ignorePermissionErrors: true,
|
|
2337
2368
|
ignored: (p3, stats) => {
|
|
2338
|
-
const rel = toPosix(
|
|
2369
|
+
const rel = toPosix(path20.relative(root2, p3));
|
|
2339
2370
|
if (rel === ".markode") return false;
|
|
2340
2371
|
if (rel.startsWith(".markode/")) return !rel.startsWith(".markode/i18n") && rel !== ".markode/state.json" && rel !== ".markode/config.json";
|
|
2341
2372
|
return skips(rel, stats ? stats.isDirectory() : void 0);
|
|
2342
2373
|
}
|
|
2343
2374
|
}).on("error", (err) => console.warn(pc.yellow(` file watcher: ${err.message}`))).on("all", (event, file) => {
|
|
2344
|
-
const abs =
|
|
2375
|
+
const abs = path20.resolve(file);
|
|
2345
2376
|
if (!abs.endsWith(".md") && !abs.endsWith(".json") && !abs.endsWith(".markodeignore") && event !== "addDir" && event !== "unlinkDir") return;
|
|
2346
|
-
let rel = toPosix(
|
|
2347
|
-
const i18n = toPosix(
|
|
2377
|
+
let rel = toPosix(path20.relative(root2, abs));
|
|
2378
|
+
const i18n = toPosix(path20.relative(root2, path20.join(stateDir(root2), "i18n")));
|
|
2348
2379
|
if (rel.startsWith(i18n + "/")) rel = rel.split("/").slice(3).join("/");
|
|
2349
2380
|
broadcast({ type: "fs", event, path: rel });
|
|
2350
2381
|
});
|
|
@@ -2353,7 +2384,7 @@ async function startServer({ root: root2, port, open: openBrowser }) {
|
|
|
2353
2384
|
console.log(`
|
|
2354
2385
|
${pc.bold("markode")} ${pc.dim("\u2192")} ${pc.cyan(url2)} ${pc.dim(root2)}
|
|
2355
2386
|
`);
|
|
2356
|
-
if (!
|
|
2387
|
+
if (!fs20.existsSync(WEB_DIR)) console.log(pc.yellow(" Web UI not built: run `pnpm build`, or `pnpm dev` and open http://localhost:4320\n"));
|
|
2357
2388
|
else if (openBrowser) open(url2).catch(() => {
|
|
2358
2389
|
});
|
|
2359
2390
|
}
|
|
@@ -2373,19 +2404,19 @@ function listen(server, port) {
|
|
|
2373
2404
|
});
|
|
2374
2405
|
}
|
|
2375
2406
|
function serveStatic(pathname, res) {
|
|
2376
|
-
let file =
|
|
2377
|
-
if (!file.startsWith(WEB_DIR) || !
|
|
2378
|
-
if (!
|
|
2407
|
+
let file = path20.join(WEB_DIR, path20.normalize(pathname).replace(/^(\.\.[/\\])+/, ""));
|
|
2408
|
+
if (!file.startsWith(WEB_DIR) || !fs20.existsSync(file) || fs20.statSync(file).isDirectory()) file = path20.join(WEB_DIR, "index.html");
|
|
2409
|
+
if (!fs20.existsSync(file)) {
|
|
2379
2410
|
res.writeHead(404).end("Web UI not built. Run `pnpm build`.");
|
|
2380
2411
|
return;
|
|
2381
2412
|
}
|
|
2382
|
-
res.writeHead(200, { "content-type": MIME[
|
|
2383
|
-
|
|
2413
|
+
res.writeHead(200, { "content-type": MIME[path20.extname(file)] ?? "application/octet-stream", "content-security-policy": "frame-ancestors 'none'" });
|
|
2414
|
+
fs20.createReadStream(file).pipe(res);
|
|
2384
2415
|
}
|
|
2385
2416
|
|
|
2386
2417
|
// src/cli/init.ts
|
|
2387
|
-
import
|
|
2388
|
-
import
|
|
2418
|
+
import fs21 from "fs";
|
|
2419
|
+
import path21 from "path";
|
|
2389
2420
|
import * as p from "@clack/prompts";
|
|
2390
2421
|
import pc2 from "picocolors";
|
|
2391
2422
|
function check(value) {
|
|
@@ -2396,12 +2427,12 @@ function check(value) {
|
|
|
2396
2427
|
return value;
|
|
2397
2428
|
}
|
|
2398
2429
|
async function offerClaudeLink(root2, d) {
|
|
2399
|
-
if (
|
|
2430
|
+
if (fs21.existsSync(path21.join(root2, "CLAUDE.md"))) return;
|
|
2400
2431
|
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 }));
|
|
2401
2432
|
if (link2 && linkAgentsDoc(root2)) p.log.success("Created CLAUDE.md (imports AGENTS.md)");
|
|
2402
2433
|
}
|
|
2403
2434
|
async function chooseMainDoc(root2, d) {
|
|
2404
|
-
const existing = MAIN_DOCS.filter((d2) =>
|
|
2435
|
+
const existing = MAIN_DOCS.filter((d2) => fs21.existsSync(path21.join(root2, d2)) && !isPointer(fs21.readFileSync(path21.join(root2, d2), "utf8"), d2));
|
|
2405
2436
|
if (d) {
|
|
2406
2437
|
if (existing.length === 1 && existing[0] === "AGENTS.md") await offerClaudeLink(root2, d);
|
|
2407
2438
|
if (existing.length) return existing.includes(d.mainDoc) ? d.mainDoc : existing[0];
|
|
@@ -2518,7 +2549,7 @@ async function chooseAreas(root2) {
|
|
|
2518
2549
|
return areas;
|
|
2519
2550
|
}
|
|
2520
2551
|
async function chooseKind(root2, dir) {
|
|
2521
|
-
const guess = guessKind(
|
|
2552
|
+
const guess = guessKind(path21.join(root2, dir));
|
|
2522
2553
|
if (!guess.unclear) return guess.kind;
|
|
2523
2554
|
const where = dir ? `${dir}/` : "this project";
|
|
2524
2555
|
const seen = guess.signals.length ? `found: ${guess.signals.join(", ")}` : "no code yet";
|
|
@@ -2531,7 +2562,7 @@ async function chooseKind(root2, dir) {
|
|
|
2531
2562
|
);
|
|
2532
2563
|
}
|
|
2533
2564
|
async function askProfile(root2, dir, kind) {
|
|
2534
|
-
const base = detectProfile(
|
|
2565
|
+
const base = detectProfile(path21.join(root2, dir), kind);
|
|
2535
2566
|
if (kind === "app") return base;
|
|
2536
2567
|
const where = dir ? ` (${dir}/)` : "";
|
|
2537
2568
|
if (kind === "library") return { kind, published: check(await p.confirm({ message: `Is the library${where} published (npm, PyPI\u2026)?`, initialValue: base.published })) };
|
|
@@ -2608,7 +2639,7 @@ async function chooseWorkflow(root2, mainDoc, previous, quiet) {
|
|
|
2608
2639
|
const profile = areas.length ? void 0 : await askProfile(root2, "", await chooseKind(root2, ""));
|
|
2609
2640
|
const created = scaffoldMethod(root2, mainDoc, areas);
|
|
2610
2641
|
installMethodSkills(root2);
|
|
2611
|
-
applyWorkflowToFile(
|
|
2642
|
+
applyWorkflowToFile(path21.join(root2, mainDoc), "markode", methodSection(areas, root2, { profile, contracts, existing }));
|
|
2612
2643
|
const big = !areas.length && flattenFiles(scanTree(root2)).length > 40;
|
|
2613
2644
|
const layout = areas.length ? "structured" : !big ? "general" : check(await p.select({ message: PROPOSALS_QUESTION, options: PROPOSAL_OPTIONS }));
|
|
2614
2645
|
activateProposals(root2, layout, mainDoc);
|
|
@@ -2673,10 +2704,10 @@ async function runInit(root2, plan2) {
|
|
|
2673
2704
|
let workflow = previous?.workflow ?? "none";
|
|
2674
2705
|
if (has2("agent")) {
|
|
2675
2706
|
mainDoc = await chooseMainDoc(root2, d);
|
|
2676
|
-
const mainFile =
|
|
2707
|
+
const mainFile = path21.join(root2, mainDoc);
|
|
2677
2708
|
if (has2("dashboard")) workflow = await chooseWorkflow(root2, mainDoc, previous?.workflow, d);
|
|
2678
2709
|
if (workflow === "openspec" || workflow === "speckit") applyWorkflowToFile(mainFile, workflow);
|
|
2679
|
-
const mainText =
|
|
2710
|
+
const mainText = fs21.readFileSync(mainFile, "utf8");
|
|
2680
2711
|
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 }));
|
|
2681
2712
|
const rules = listRules();
|
|
2682
2713
|
const applied = appliedRuleIds(mainText);
|
|
@@ -2691,7 +2722,7 @@ async function runInit(root2, plan2) {
|
|
|
2691
2722
|
required: false
|
|
2692
2723
|
})
|
|
2693
2724
|
);
|
|
2694
|
-
for (const dir of targets) applyRulesToFile(contentDoc(
|
|
2725
|
+
for (const dir of targets) applyRulesToFile(contentDoc(path21.join(root2, dir)), selected, sourceLang);
|
|
2695
2726
|
}
|
|
2696
2727
|
installAgentSkill(root2);
|
|
2697
2728
|
const proposals = proposalsState(root2);
|
|
@@ -2848,6 +2879,8 @@ Usage
|
|
|
2848
2879
|
markode changes [--json] what the user edited or commented in markode since the agent was last told
|
|
2849
2880
|
markode changes --start a new agent session: what happened before is old news
|
|
2850
2881
|
markode changes --hook <event> the same, as Claude Code hook output (silent when nothing changed)
|
|
2882
|
+
markode update [--check] install the latest version now, without the day npm/pnpm wait for new releases
|
|
2883
|
+
markode update --pnpm|--npm the same, choosing the package manager to install it with
|
|
2851
2884
|
|
|
2852
2885
|
For the agent's /markode translate:
|
|
2853
2886
|
markode translate --status [--json] translations + files not in the source language
|
|
@@ -2873,18 +2906,20 @@ var option = (name) => {
|
|
|
2873
2906
|
var command = args[0] && !args[0].startsWith("-") ? args[0] : "start";
|
|
2874
2907
|
var root = process.env.MARKODE_ROOT ?? process.cwd();
|
|
2875
2908
|
async function main() {
|
|
2876
|
-
if (
|
|
2909
|
+
if (fs22.existsSync(root)) setProjectRoot(root);
|
|
2877
2910
|
if (flag("--help") || flag("-h")) return console.log(HELP);
|
|
2878
|
-
if (flag("--version") || flag("-v")) return console.log(JSON.parse(
|
|
2911
|
+
if (flag("--version") || flag("-v")) return console.log(JSON.parse(fs22.readFileSync(path22.join(PKG_ROOT, "package.json"), "utf8")).version);
|
|
2879
2912
|
const translation = flag("--status") || flag("--mark") || flag("--keep");
|
|
2880
2913
|
if ((command === "sync" || command === "translate") && translation) return sync();
|
|
2881
2914
|
if (command === "translate" || flag("--translate")) return runTranslate(root);
|
|
2915
|
+
if (["check", "url", "link"].includes(command) || command === "changes" && flag("--start")) refreshInstalled(root);
|
|
2882
2916
|
if (command === "check") return check3();
|
|
2883
2917
|
if (command === "wait") return wait();
|
|
2884
2918
|
if (command === "link") return link();
|
|
2885
2919
|
if (command === "changes") return changes();
|
|
2886
2920
|
if (command === "url") return url();
|
|
2887
2921
|
if (command === "stop") return stop();
|
|
2922
|
+
if (command === "update") return update();
|
|
2888
2923
|
if (command === "features") return features();
|
|
2889
2924
|
if (command === "setup") {
|
|
2890
2925
|
await runSetup(false, flag("--custom"));
|
|
@@ -2972,7 +3007,7 @@ function link() {
|
|
|
2972
3007
|
}
|
|
2973
3008
|
async function url() {
|
|
2974
3009
|
const doc = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
|
|
2975
|
-
if (doc && (!doc.toLowerCase().endsWith(".md") || !
|
|
3010
|
+
if (doc && (!doc.toLowerCase().endsWith(".md") || !fs22.existsSync(path22.join(root, doc)))) {
|
|
2976
3011
|
console.error(`Usage: markode url [<doc.md>] (relative to ${root})`);
|
|
2977
3012
|
process.exitCode = 1;
|
|
2978
3013
|
return;
|
|
@@ -2986,6 +3021,20 @@ function stop() {
|
|
|
2986
3021
|
process.kill(running.pid);
|
|
2987
3022
|
console.log(`Stopped the editor on port ${running.port}.`);
|
|
2988
3023
|
}
|
|
3024
|
+
async function update() {
|
|
3025
|
+
const latest = await latestVersion();
|
|
3026
|
+
if (!isNewer(latest, CURRENT_VERSION)) return console.log(`markode ${CURRENT_VERSION} is the latest version.`);
|
|
3027
|
+
console.log(`markode ${latest} is out (you have ${CURRENT_VERSION}).`);
|
|
3028
|
+
if (flag("--check")) return;
|
|
3029
|
+
const via = flag("--pnpm") ? "pnpm" : flag("--npm") ? "npm" : installer();
|
|
3030
|
+
if (via === "npx") return console.log(`Running through npx: use \`npx -y @rafiqui987/markode@${latest}\` once to fetch it, or install it with \`markode update --pnpm\` (or --npm).`);
|
|
3031
|
+
if (!via) return console.log("This copy runs from a clone of the repository: pull and build it, or install it with `markode update --pnpm` (or --npm).");
|
|
3032
|
+
const cmd = installCommand2(via, latest);
|
|
3033
|
+
console.log(`$ ${cmd.join(" ")}`);
|
|
3034
|
+
if (runInstall(cmd)) return console.log(`Updated to ${latest}. A running editor keeps the old version until \`markode stop\`.`);
|
|
3035
|
+
console.error("The install failed; the output above says why.");
|
|
3036
|
+
process.exitCode = 1;
|
|
3037
|
+
}
|
|
2989
3038
|
function changes() {
|
|
2990
3039
|
if (flag("--start")) return startSession(root);
|
|
2991
3040
|
const list = takeChanges(root);
|
|
@@ -3000,7 +3049,7 @@ function changes() {
|
|
|
3000
3049
|
}
|
|
3001
3050
|
async function wait() {
|
|
3002
3051
|
const rel = args[1];
|
|
3003
|
-
if (!rel || !
|
|
3052
|
+
if (!rel || !fs22.existsSync(path22.join(root, rel))) {
|
|
3004
3053
|
console.error(`Usage: markode wait <proposal.md> (relative to ${root})`);
|
|
3005
3054
|
process.exitCode = 1;
|
|
3006
3055
|
return;
|
package/package.json
CHANGED