isorite 0.15.0 → 0.16.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/dist/main.js +416 -61
- package/guide/out-of-the-base/web-and-code-links.md +3 -1
- package/package.json +1 -1
- package/web/_app/immutable/assets/0.BEFtmzIh.css +1 -0
- package/web/_app/immutable/assets/4.LR47AJKA.css +1 -0
- package/web/_app/immutable/assets/NoteList.C2GYAaLW.css +1 -0
- package/web/_app/immutable/chunks/BHORylQC.js +1 -0
- package/web/_app/immutable/chunks/{LbnMg2Kc.js → BMijTvgG.js} +1 -1
- package/web/_app/immutable/chunks/{Djo-ijeo.js → BtYMl4bD.js} +1 -1
- package/web/_app/immutable/chunks/{BsY4iMci.js → C8FxuPry.js} +1 -1
- package/web/_app/immutable/chunks/CA-rzUq8.js +524 -0
- package/web/_app/immutable/chunks/{D1MhxXyn.js → CTuuQ66F.js} +1 -1
- package/web/_app/immutable/chunks/{D4EpFhYZ.js → CglDFp51.js} +1 -1
- package/web/_app/immutable/chunks/{B_x6_Y3m.js → D9uNsgxP.js} +1 -1
- package/web/_app/immutable/chunks/{wD7RSG2d.js → Qej0SEux.js} +1 -1
- package/web/_app/immutable/chunks/{B9TKPEJD.js → TmxIGCN3.js} +1 -1
- package/web/_app/immutable/chunks/{BDJlNL-I.js → Wv-ICQRy.js} +3 -3
- package/web/_app/immutable/entry/{app.Av5W3V8v.js → app.B9TPZNFw.js} +2 -2
- package/web/_app/immutable/entry/start.WU9HKeyT.js +1 -0
- package/web/_app/immutable/nodes/{0.BTVYyqUm.js → 0.C9NFEqJd.js} +2 -2
- package/web/_app/immutable/nodes/{1.DR-b7PMP.js → 1.B5PJwWOG.js} +1 -1
- package/web/_app/immutable/nodes/{2.BePyTqoa.js → 2.DRtC7zjs.js} +1 -1
- package/web/_app/immutable/nodes/{3.D2rfIXQP.js → 3.EEMisvUL.js} +1 -1
- package/web/_app/immutable/nodes/4.DthmsFQC.js +19 -0
- package/web/_app/immutable/nodes/5.CEu0DlpQ.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +14 -14
- package/web/_app/immutable/assets/0.CtWz9d2A.css +0 -1
- package/web/_app/immutable/assets/4.fOHsAKBF.css +0 -1
- package/web/_app/immutable/assets/NoteList.E6jC-AtZ.css +0 -1
- package/web/_app/immutable/chunks/B7Bf3hc7.js +0 -1
- package/web/_app/immutable/chunks/Dx_7xlS4.js +0 -523
- package/web/_app/immutable/entry/start.CbpxCvWi.js +0 -1
- package/web/_app/immutable/nodes/4.7tuUcODS.js +0 -15
- package/web/_app/immutable/nodes/5.Cs9hcGU9.js +0 -1
package/dist/main.js
CHANGED
|
@@ -619,6 +619,39 @@ function splitFrontmatter(source) {
|
|
|
619
619
|
if (typeof meta !== "object" || Array.isArray(meta)) throw new FrontmatterError("frontmatter is not a mapping");
|
|
620
620
|
return [meta, body.replace(/^\n+/, "")];
|
|
621
621
|
}
|
|
622
|
+
/** A frontmatter line `key: value` at the top level whose value the YAML parser refused, such as an unquoted
|
|
623
|
+
* colon (`title: Foo: bar`) or a value starting with a backtick, `%` or `@`: agents write these often. The value
|
|
624
|
+
* is put in double quotes, for those lines alone, and the text kept as it was otherwise. Null when the
|
|
625
|
+
* frontmatter reads already, or the quotes would not make it read (an unclosed list, say): the note is left as written. */
|
|
626
|
+
function repairFrontmatter(source) {
|
|
627
|
+
try {
|
|
628
|
+
if (frontmatterText(source)[0] === null) return null;
|
|
629
|
+
} catch {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
const bom = source.startsWith(BOM$1) ? BOM$1 : "", lines = source.slice(bom.length).split("\n");
|
|
633
|
+
let close = 1;
|
|
634
|
+
while (rstrip(lines[close]) !== "---") close++;
|
|
635
|
+
const raw = lines.slice(1, close).join("\n") + "\n";
|
|
636
|
+
const bad = /* @__PURE__ */ new Set();
|
|
637
|
+
for (const e of parseYaml(raw).errors) {
|
|
638
|
+
const at = e.linePos?.[0]?.line;
|
|
639
|
+
if (at) bad.add(at);
|
|
640
|
+
}
|
|
641
|
+
const fixed = [];
|
|
642
|
+
for (const n of bad) {
|
|
643
|
+
const m = /^([A-Za-z_][\w .-]*):[ \t]+(\S.*?)[ \t]*(\r?)$/.exec(lines[n] ?? "");
|
|
644
|
+
if (!m || /^["'\[{|>&*!#]/.test(m[2])) continue;
|
|
645
|
+
lines[n] = `${m[1]}: ${JSON.stringify(m[2])}${m[3]}`;
|
|
646
|
+
fixed.push(m[1]);
|
|
647
|
+
}
|
|
648
|
+
if (!fixed.length) return null;
|
|
649
|
+
if (parseYaml(lines.slice(1, close).join("\n") + "\n").errors.length) return null;
|
|
650
|
+
return {
|
|
651
|
+
text: bom + lines.join("\n"),
|
|
652
|
+
fixed
|
|
653
|
+
};
|
|
654
|
+
}
|
|
622
655
|
/** Serialise frontmatter, keeping key order. */
|
|
623
656
|
function dumpFrontmatter(meta) {
|
|
624
657
|
return stringify(meta, {
|
|
@@ -885,16 +918,19 @@ function embedFor(id, ids) {
|
|
|
885
918
|
for (const other of ids) if (other.split("/").at(-1) === name) shared++;
|
|
886
919
|
return `[[!${shared > 1 ? id : name}]]`;
|
|
887
920
|
}
|
|
888
|
-
/** A code link's file and
|
|
921
|
+
/** A code link's file and, after `#`, its lines (`L40-L52`) or the symbol it names (`leapfrog`, `Body.step`: a
|
|
922
|
+
* function, method or class, found by its name wherever it has moved to, M29). Null when it is not one, or names a
|
|
923
|
+
* path outside the repository. */
|
|
889
924
|
function codeTarget(href) {
|
|
890
|
-
const m = /^code:([^#?]+)(?:#L(\d+)(?:-L?(\d+))
|
|
925
|
+
const m = /^code:([^#?]+)(?:#(?:L(\d+)(?:-L?(\d+))?|([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)))?$/.exec(href.trim());
|
|
891
926
|
if (!m) return null;
|
|
892
927
|
const path = decodeURIComponent(m[1]).replace(/^\.?\/+/, "");
|
|
893
928
|
if (!path || path.split("/").some((p) => p === ".." || !p)) return null;
|
|
894
929
|
return {
|
|
895
930
|
path,
|
|
896
931
|
...m[2] ? { line: Number(m[2]) } : {},
|
|
897
|
-
...m[3] ? { end: Number(m[3]) } : {}
|
|
932
|
+
...m[3] ? { end: Number(m[3]) } : {},
|
|
933
|
+
...m[4] ? { symbol: m[4] } : {}
|
|
898
934
|
};
|
|
899
935
|
}
|
|
900
936
|
/** A note's links out of the base, in order, each once. */
|
|
@@ -922,14 +958,20 @@ function outwardLinks(body) {
|
|
|
922
958
|
for (const { href, embed, alt } of citeRefs(body)) {
|
|
923
959
|
if (!embed || seen.has(href)) continue;
|
|
924
960
|
const c = codeTarget(href);
|
|
925
|
-
if (!c
|
|
926
|
-
out.push({
|
|
961
|
+
if (!c) continue;
|
|
962
|
+
if (pictureType(c.path)) out.push({
|
|
927
963
|
kind: "code",
|
|
928
964
|
href,
|
|
929
965
|
text: alt || c.path.split("/").at(-1),
|
|
930
966
|
path: c.path,
|
|
931
967
|
picture: true
|
|
932
968
|
});
|
|
969
|
+
else out.push({
|
|
970
|
+
kind: "code",
|
|
971
|
+
href,
|
|
972
|
+
text: alt || c.symbol || c.path.split("/").at(-1),
|
|
973
|
+
...c
|
|
974
|
+
});
|
|
933
975
|
seen.add(href);
|
|
934
976
|
}
|
|
935
977
|
return out;
|
|
@@ -2197,6 +2239,176 @@ function mapModel(base) {
|
|
|
2197
2239
|
};
|
|
2198
2240
|
}
|
|
2199
2241
|
//#endregion
|
|
2242
|
+
//#region packages/core/src/symbol.ts
|
|
2243
|
+
const PY = /* @__PURE__ */ new Set(["py", "pyi"]);
|
|
2244
|
+
const BRACE = /* @__PURE__ */ new Set([
|
|
2245
|
+
"js",
|
|
2246
|
+
"jsx",
|
|
2247
|
+
"mjs",
|
|
2248
|
+
"cjs",
|
|
2249
|
+
"ts",
|
|
2250
|
+
"tsx",
|
|
2251
|
+
"mts",
|
|
2252
|
+
"cts",
|
|
2253
|
+
"go",
|
|
2254
|
+
"rs",
|
|
2255
|
+
"java",
|
|
2256
|
+
"kt",
|
|
2257
|
+
"kts",
|
|
2258
|
+
"cs",
|
|
2259
|
+
"c",
|
|
2260
|
+
"h",
|
|
2261
|
+
"cc",
|
|
2262
|
+
"cpp",
|
|
2263
|
+
"cxx",
|
|
2264
|
+
"hpp",
|
|
2265
|
+
"swift",
|
|
2266
|
+
"php",
|
|
2267
|
+
"scala",
|
|
2268
|
+
"dart",
|
|
2269
|
+
"zig",
|
|
2270
|
+
"svelte",
|
|
2271
|
+
"vue"
|
|
2272
|
+
]);
|
|
2273
|
+
/** Whether symbols can be found in a file of this name. */
|
|
2274
|
+
function hasSymbols(path) {
|
|
2275
|
+
const ext = path.split(".").at(-1).toLowerCase();
|
|
2276
|
+
return PY.has(ext) || BRACE.has(ext);
|
|
2277
|
+
}
|
|
2278
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2279
|
+
/** The lines of `path`'s symbol `name` (`Class.method` for one inside another) in `text`, or null. */
|
|
2280
|
+
function findSymbol(text, path, name) {
|
|
2281
|
+
const ext = path.split(".").at(-1).toLowerCase();
|
|
2282
|
+
const lines = text.split(/\r?\n/);
|
|
2283
|
+
const parts = name.split(".").filter(Boolean);
|
|
2284
|
+
if (!parts.length || !PY.has(ext) && !BRACE.has(ext)) return null;
|
|
2285
|
+
let from = 0, to = lines.length - 1, span = null;
|
|
2286
|
+
for (const part of parts) {
|
|
2287
|
+
const found = PY.has(ext) ? pyFind(lines, part, from, to) : braceFind(lines, part, from, to);
|
|
2288
|
+
if (!found) return null;
|
|
2289
|
+
span = found;
|
|
2290
|
+
from = found.start;
|
|
2291
|
+
to = found.end - 1;
|
|
2292
|
+
}
|
|
2293
|
+
return span;
|
|
2294
|
+
}
|
|
2295
|
+
function pyFind(lines, name, from, to) {
|
|
2296
|
+
const re = new RegExp(`^(\\s*)(?:async\\s+)?(?:def|class)\\s+${esc(name)}\\b`);
|
|
2297
|
+
const at = [];
|
|
2298
|
+
for (let i = from; i <= to; i++) {
|
|
2299
|
+
const m = re.exec(lines[i]);
|
|
2300
|
+
if (m) at.push({
|
|
2301
|
+
i,
|
|
2302
|
+
indent: m[1].length
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
const shallow = Math.min(...at.map((a) => a.indent), Infinity);
|
|
2306
|
+
const top = from === 0 && to === lines.length - 1;
|
|
2307
|
+
const here = at.filter((a) => a.indent === shallow && (!top || shallow === 0));
|
|
2308
|
+
let first = null, count = 0;
|
|
2309
|
+
for (const { i, indent } of here) {
|
|
2310
|
+
count++;
|
|
2311
|
+
if (first) continue;
|
|
2312
|
+
let open = i;
|
|
2313
|
+
while (open < lines.length - 1 && !/:\s*(#.*)?$/.test(lines[open])) open++;
|
|
2314
|
+
let end = open;
|
|
2315
|
+
for (let j = open + 1; j < lines.length; j++) {
|
|
2316
|
+
const l = lines[j];
|
|
2317
|
+
if (!l.trim()) continue;
|
|
2318
|
+
if (l.length - l.trimStart().length <= indent) break;
|
|
2319
|
+
end = j;
|
|
2320
|
+
}
|
|
2321
|
+
let start = i;
|
|
2322
|
+
while (start > 0 && new RegExp(`^\\s{${indent}}@`).test(lines[start - 1])) start--;
|
|
2323
|
+
first = {
|
|
2324
|
+
start: start + 1,
|
|
2325
|
+
end: Math.min(end, to) + 1,
|
|
2326
|
+
count: 1
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
return first ? {
|
|
2330
|
+
...first,
|
|
2331
|
+
count
|
|
2332
|
+
} : null;
|
|
2333
|
+
}
|
|
2334
|
+
const DECL = (n) => [
|
|
2335
|
+
new RegExp(`\\b(?:function\\*?|class|interface|enum|struct|trait|union|impl|fn|func|def|type|record|namespace|object|module)\\s+(?:<[^>]*>\\s*)?(?:\\([^)]*\\)\\s*)?${n}\\b`),
|
|
2336
|
+
new RegExp(`\\b(?:const|let|var|val)\\s+${n}\\s*(?::[^=]+)?=\\s*(?:async\\s*)?(?:function\\b|\\([^)]*\\)\\s*(?::[^=]+)?=>|\\w+\\s*=>)`),
|
|
2337
|
+
new RegExp(`^\\s*(?:(?:public|private|protected|internal|static|async|override|final|abstract|export|default|get|set|pub|unsafe|virtual|readonly|suspend|inline|open)\\s+)*(?:[\\w<>\\[\\],.?*&:]+\\s+)?\\*?${n}\\s*(?:<[^>]*>)?\\(`)
|
|
2338
|
+
];
|
|
2339
|
+
function braceFind(lines, name, from, to) {
|
|
2340
|
+
const res = DECL(esc(name));
|
|
2341
|
+
let first = null, count = 0;
|
|
2342
|
+
let i = from;
|
|
2343
|
+
while (i <= to) {
|
|
2344
|
+
const line = lines[i];
|
|
2345
|
+
if (/^\s*(\/\/|\*|\/\*|#)/.test(line) || !res.some((re) => re.test(line))) {
|
|
2346
|
+
i++;
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
const body = openBrace(lines, i, to);
|
|
2350
|
+
if (!body) {
|
|
2351
|
+
i++;
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
count++;
|
|
2355
|
+
const end = closeBrace(lines, body.line, body.col);
|
|
2356
|
+
if (!first) {
|
|
2357
|
+
let start = i;
|
|
2358
|
+
while (start > 0 && /^\s*(@|#\[|\/\/|\/\*|\*)/.test(lines[start - 1])) start--;
|
|
2359
|
+
first = {
|
|
2360
|
+
start: start + 1,
|
|
2361
|
+
end: Math.min(end, to) + 1,
|
|
2362
|
+
count: 1
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
i = end + 1;
|
|
2366
|
+
}
|
|
2367
|
+
return first ? {
|
|
2368
|
+
...first,
|
|
2369
|
+
count
|
|
2370
|
+
} : null;
|
|
2371
|
+
}
|
|
2372
|
+
/** The first `{` from line `i`, if it comes before a `;` (within a few lines, the signature). */
|
|
2373
|
+
function openBrace(lines, i, to) {
|
|
2374
|
+
for (let j = i; j <= Math.min(to, i + 8); j++) {
|
|
2375
|
+
const l = stripStrings(lines[j]);
|
|
2376
|
+
const b = l.indexOf("{"), s = l.indexOf(";");
|
|
2377
|
+
if (b >= 0 && (s < 0 || b < s)) return {
|
|
2378
|
+
line: j,
|
|
2379
|
+
col: b
|
|
2380
|
+
};
|
|
2381
|
+
if (s >= 0) return null;
|
|
2382
|
+
}
|
|
2383
|
+
return null;
|
|
2384
|
+
}
|
|
2385
|
+
/** Blank out what is inside quotes and after `//`, so braces there are not counted. */
|
|
2386
|
+
function stripStrings(l) {
|
|
2387
|
+
return l.replace(/\/\/.*$/, (m) => " ".repeat(m.length)).replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, (m) => " ".repeat(m.length));
|
|
2388
|
+
}
|
|
2389
|
+
/** The line holding the `}` that closes the `{` at (line, col). The end of the file when it does not. */
|
|
2390
|
+
function closeBrace(lines, line, col) {
|
|
2391
|
+
let depth = 0, block = false;
|
|
2392
|
+
for (let j = line; j < lines.length; j++) {
|
|
2393
|
+
let l = stripStrings(lines[j]);
|
|
2394
|
+
if (block) {
|
|
2395
|
+
const e = l.indexOf("*/");
|
|
2396
|
+
if (e < 0) continue;
|
|
2397
|
+
l = " ".repeat(e + 2) + l.slice(e + 2);
|
|
2398
|
+
block = false;
|
|
2399
|
+
}
|
|
2400
|
+
l = l.replace(/\/\*.*?\*\//g, (m) => " ".repeat(m.length));
|
|
2401
|
+
const o = l.indexOf("/*");
|
|
2402
|
+
if (o >= 0) {
|
|
2403
|
+
block = true;
|
|
2404
|
+
l = l.slice(0, o);
|
|
2405
|
+
}
|
|
2406
|
+
for (let k = j === line ? col : 0; k < l.length; k++) if (l[k] === "{") depth++;
|
|
2407
|
+
else if (l[k] === "}" && --depth === 0) return j;
|
|
2408
|
+
}
|
|
2409
|
+
return lines.length - 1;
|
|
2410
|
+
}
|
|
2411
|
+
//#endregion
|
|
2200
2412
|
//#region packages/core/src/node.ts
|
|
2201
2413
|
/** Every file and folder under `root`, Markdown with its text. */
|
|
2202
2414
|
function readFolder(root) {
|
|
@@ -2231,6 +2443,32 @@ function loadBundle(root) {
|
|
|
2231
2443
|
return Bundle.fromFiles(readFolder(root));
|
|
2232
2444
|
}
|
|
2233
2445
|
//#endregion
|
|
2446
|
+
//#region packages/server/src/term.ts
|
|
2447
|
+
const wrap = (open, close) => (text) => colour() ? `\x1b[${open}m${text}\x1b[${close}m` : text;
|
|
2448
|
+
/** Whether to paint: asked each time, so a test or a caller can change the environment. */
|
|
2449
|
+
function colour(env = process.env, tty = Boolean(process.stdout.isTTY)) {
|
|
2450
|
+
if (env.NO_COLOR) return false;
|
|
2451
|
+
if (env.FORCE_COLOR !== void 0) return env.FORCE_COLOR !== "0" && env.FORCE_COLOR !== "false";
|
|
2452
|
+
return tty && env.TERM !== "dumb";
|
|
2453
|
+
}
|
|
2454
|
+
const bold = wrap(1, 22);
|
|
2455
|
+
const dim = wrap(2, 22);
|
|
2456
|
+
const red = wrap(31, 39);
|
|
2457
|
+
const green = wrap(32, 39);
|
|
2458
|
+
const yellow = wrap(33, 39);
|
|
2459
|
+
/** A help text with its commands in bold and what they say dimmed: a line ` isorite word … said` and its continuations. */
|
|
2460
|
+
function helpText(text) {
|
|
2461
|
+
if (!colour()) return text;
|
|
2462
|
+
return text.split("\n").map((line, i) => {
|
|
2463
|
+
if (i === 0) return bold(line);
|
|
2464
|
+
if (/^ {20,}\S/.test(line)) return dim(line);
|
|
2465
|
+
const m = /^( isorite(?: [a-z-]+)?)( .*?)(\s{2,})(\S.*)$/.exec(line);
|
|
2466
|
+
if (m) return `${bold(m[1])}${m[2]}${m[3]}${dim(m[4])}`;
|
|
2467
|
+
const c = /^( isorite(?: [a-z-]+)?)(.*)$/.exec(line);
|
|
2468
|
+
return c ? `${bold(c[1])}${c[2]}` : line;
|
|
2469
|
+
}).join("\n");
|
|
2470
|
+
}
|
|
2471
|
+
//#endregion
|
|
2234
2472
|
//#region packages/server/src/config.ts
|
|
2235
2473
|
const configPath = (env = process.env) => join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "isorite", "config.toml");
|
|
2236
2474
|
const DEVICE$1 = /^[a-z0-9]{4,16}$/;
|
|
@@ -3233,6 +3471,14 @@ const ISORITE = `# Their knowledge base
|
|
|
3233
3471
|
together. A problem has no steps inside it.
|
|
3234
3472
|
- Files beside the notes that are not notes (data, documents, pictures,
|
|
3235
3473
|
artifacts) are linked as any note is: [runs](/simulation/runs.csv).
|
|
3474
|
+
- Code in their repository is linked as [the step](code:sim/step.py#leapfrog):
|
|
3475
|
+
the path from the repository's root, then a name (\`#leapfrog\`, or
|
|
3476
|
+
\`#Class.method\`) or lines (\`#L40-L52\`). Prefer a name: it still finds the
|
|
3477
|
+
code when it moves. Look in the file for the name before you write it; a name
|
|
3478
|
+
that is not there marks the link broken. \`\`
|
|
3479
|
+
shows that code in the note, where it is read.
|
|
3480
|
+
- Frontmatter is YAML: put a value in quotes when it holds a colon, as
|
|
3481
|
+
\`title: "Gauss: Bonnet"\` and \`description: "Why: the proof"\`.
|
|
3236
3482
|
- Every change you make is marked as yours, in the note's history, and they
|
|
3237
3483
|
can undo it.
|
|
3238
3484
|
|
|
@@ -3931,7 +4177,9 @@ var Engine = class Engine {
|
|
|
3931
4177
|
for (const c of b.concepts.values()) for (const o of c.outward) {
|
|
3932
4178
|
if (o.kind !== "code") continue;
|
|
3933
4179
|
const k = o.path.lastIndexOf("/");
|
|
3934
|
-
|
|
4180
|
+
let gone = !(await names(k < 0 ? "" : o.path.slice(0, k))).has(o.path.slice(k + 1));
|
|
4181
|
+
if (!gone && o.symbol) gone = (await this.code(o.href)).state === "no-symbol";
|
|
4182
|
+
if (gone) broken.set(c.id, [...broken.get(c.id) ?? [], o.href]);
|
|
3935
4183
|
}
|
|
3936
4184
|
}
|
|
3937
4185
|
this.codeCheck = {
|
|
@@ -4307,6 +4555,25 @@ var Engine = class Engine {
|
|
|
4307
4555
|
createNote(id, text) {
|
|
4308
4556
|
return this.one(() => this.put(id, text, null));
|
|
4309
4557
|
}
|
|
4558
|
+
async makeFolder(path) {
|
|
4559
|
+
path = path.trim().replace(/^\/+|\/+$/g, "");
|
|
4560
|
+
if (!inBase(path)) throw new EngineError("only a folder of the base is made here");
|
|
4561
|
+
checkNoteId(path);
|
|
4562
|
+
if (!this.opts.notes.mkdir) throw new EngineError("an empty folder can't be made here");
|
|
4563
|
+
if (this.files.has(`${path}.md`)) throw new EngineError(`there is a note at ${path} already`, 409);
|
|
4564
|
+
await this.one(async () => {
|
|
4565
|
+
await this.opts.notes.mkdir(path);
|
|
4566
|
+
const parts = path.split("/");
|
|
4567
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
4568
|
+
const p = parts.slice(0, i).join("/");
|
|
4569
|
+
if (!this.files.has(p)) this.files.set(p, {
|
|
4570
|
+
path: p,
|
|
4571
|
+
dir: true
|
|
4572
|
+
});
|
|
4573
|
+
}
|
|
4574
|
+
this.built = this.index = null;
|
|
4575
|
+
});
|
|
4576
|
+
}
|
|
4310
4577
|
/** `who` is you unless Iso moves it, in Write mode. */
|
|
4311
4578
|
async moveNote(id, to, base, who = "you") {
|
|
4312
4579
|
to = to.trim().replace(/^\/+|\/+$/g, "").replace(/\.md$/, "");
|
|
@@ -4451,6 +4718,63 @@ var Engine = class Engine {
|
|
|
4451
4718
|
version: sha256(bytes).slice(0, 16)
|
|
4452
4719
|
};
|
|
4453
4720
|
}
|
|
4721
|
+
/** The code a code link names (M29): its symbol's lines, or the lines it gives, or the file's start. A symbol is
|
|
4722
|
+
* found by its name in the file as it is now, so the link holds where the code has moved. */
|
|
4723
|
+
async code(href) {
|
|
4724
|
+
const target = codeTarget(href);
|
|
4725
|
+
if (!target) throw new EngineError("not a code link", 400);
|
|
4726
|
+
const { path, symbol } = target, where = await this.repositoryOfBase();
|
|
4727
|
+
const files = where ? this.opts.repository?.(where) ?? null : null;
|
|
4728
|
+
if (!files) return {
|
|
4729
|
+
state: "no-repository",
|
|
4730
|
+
path,
|
|
4731
|
+
...symbol ? { symbol } : {}
|
|
4732
|
+
};
|
|
4733
|
+
const bytes = await files.readBytes(path);
|
|
4734
|
+
if (!bytes) return {
|
|
4735
|
+
state: "no-file",
|
|
4736
|
+
path,
|
|
4737
|
+
...symbol ? { symbol } : {}
|
|
4738
|
+
};
|
|
4739
|
+
if (bytes.length > 2e6) return {
|
|
4740
|
+
state: "too-large",
|
|
4741
|
+
path
|
|
4742
|
+
};
|
|
4743
|
+
const lines = new TextDecoder().decode(bytes).split(/\r?\n/);
|
|
4744
|
+
if (lines.at(-1) === "") lines.pop();
|
|
4745
|
+
let start = target.line ?? 1, end = target.end ?? target.line ?? lines.length, count;
|
|
4746
|
+
if (symbol) {
|
|
4747
|
+
if (!hasSymbols(path)) return {
|
|
4748
|
+
state: "unsupported",
|
|
4749
|
+
path,
|
|
4750
|
+
symbol
|
|
4751
|
+
};
|
|
4752
|
+
const found = findSymbol(lines.join("\n"), path, symbol);
|
|
4753
|
+
if (!found) return {
|
|
4754
|
+
state: "no-symbol",
|
|
4755
|
+
path,
|
|
4756
|
+
symbol
|
|
4757
|
+
};
|
|
4758
|
+
({start, end, count} = found);
|
|
4759
|
+
}
|
|
4760
|
+
end = Math.min(Math.max(end, start), lines.length);
|
|
4761
|
+
if (start > lines.length) return {
|
|
4762
|
+
state: "no-symbol",
|
|
4763
|
+
path
|
|
4764
|
+
};
|
|
4765
|
+
const shown = Math.min(end, start + 120 - 1);
|
|
4766
|
+
return {
|
|
4767
|
+
state: "ok",
|
|
4768
|
+
path,
|
|
4769
|
+
...symbol ? { symbol } : {},
|
|
4770
|
+
start,
|
|
4771
|
+
end,
|
|
4772
|
+
total: lines.length,
|
|
4773
|
+
text: lines.slice(start - 1, shown).join("\n"),
|
|
4774
|
+
...shown < end ? { more: true } : {},
|
|
4775
|
+
...count && count > 1 ? { count } : {}
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4454
4778
|
/** What was read of each picture, kept while its size and time are the same. */
|
|
4455
4779
|
pictureCache = /* @__PURE__ */ new Map();
|
|
4456
4780
|
/** Every picture in the notes (M22), found afresh: one put in the folder by hand since the base was opened is
|
|
@@ -4556,6 +4880,7 @@ var Engine = class Engine {
|
|
|
4556
4880
|
async put(id, text, base, why = "edit", extra = {}, who = "you") {
|
|
4557
4881
|
const { files, path } = this.where(id);
|
|
4558
4882
|
if (text.length > 2e6) throw new EngineError("note too large", 413);
|
|
4883
|
+
if (who !== "you" && path.endsWith(".md")) text = repairFrontmatter(text)?.text ?? text;
|
|
4559
4884
|
const now = await files.read(path);
|
|
4560
4885
|
if (base === null && now !== null) throw new EngineError(`there is already a note at ${id}`, 409);
|
|
4561
4886
|
if (base !== null && now === null) throw new Conflict(`${id} is gone`, null);
|
|
@@ -4947,6 +5272,8 @@ var MemoryFiles = class {
|
|
|
4947
5272
|
files = /* @__PURE__ */ new Map();
|
|
4948
5273
|
/** Files held as bytes (pictures), apart from text. */
|
|
4949
5274
|
bytes = /* @__PURE__ */ new Map();
|
|
5275
|
+
/** Folders made empty (M31). */
|
|
5276
|
+
folders = /* @__PURE__ */ new Set();
|
|
4950
5277
|
constructor(files = {}) {
|
|
4951
5278
|
for (const [path, text] of Object.entries(files)) this.files.set(path, text);
|
|
4952
5279
|
}
|
|
@@ -4971,10 +5298,17 @@ var MemoryFiles = class {
|
|
|
4971
5298
|
this.files.delete(path);
|
|
4972
5299
|
this.bytes.delete(path);
|
|
4973
5300
|
}
|
|
5301
|
+
async mkdir(dir) {
|
|
5302
|
+
this.folders.add(dir);
|
|
5303
|
+
}
|
|
4974
5304
|
async list(dir) {
|
|
4975
5305
|
const prefix = dir ? dir + "/" : "";
|
|
4976
5306
|
const found = /* @__PURE__ */ new Map();
|
|
4977
|
-
for (const path of [
|
|
5307
|
+
for (const path of [
|
|
5308
|
+
...this.files.keys(),
|
|
5309
|
+
...this.bytes.keys(),
|
|
5310
|
+
...[...this.folders].map((f) => f + "/")
|
|
5311
|
+
]) {
|
|
4978
5312
|
if (!path.startsWith(prefix)) continue;
|
|
4979
5313
|
const rest = path.slice(prefix.length), cut = rest.indexOf("/");
|
|
4980
5314
|
if (cut >= 0) {
|
|
@@ -6155,7 +6489,7 @@ var AcpFollower = class {
|
|
|
6155
6489
|
};
|
|
6156
6490
|
//#endregion
|
|
6157
6491
|
//#region packages/server/src/version.ts
|
|
6158
|
-
const VERSION = "0.
|
|
6492
|
+
const VERSION = "0.16.0";
|
|
6159
6493
|
//#endregion
|
|
6160
6494
|
//#region packages/server/src/connect.ts
|
|
6161
6495
|
const HARNESSES = [{
|
|
@@ -6989,6 +7323,10 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6989
7323
|
if (c.req.header("if-none-match") === etag) return c.body(null, 304, headers);
|
|
6990
7324
|
return c.body(found.bytes, 200, headers);
|
|
6991
7325
|
});
|
|
7326
|
+
app.get("/api/code", async (c) => {
|
|
7327
|
+
const href = c.req.query("href") ?? "";
|
|
7328
|
+
return answer(c, () => api.code(href));
|
|
7329
|
+
});
|
|
6992
7330
|
app.put("/api/note", async (c) => {
|
|
6993
7331
|
const refused = writeRefused(c);
|
|
6994
7332
|
if (refused) return refused;
|
|
@@ -7026,6 +7364,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
7026
7364
|
to: z.string().max(400),
|
|
7027
7365
|
base: z.string().max(64)
|
|
7028
7366
|
}).strict(), (c, b) => api.moveNote(id(c), b.to, b.base));
|
|
7367
|
+
post("/api/folder", z.object({ path: z.string().max(400) }).strict(), (_c, b) => api.makeFolder(b.path));
|
|
7029
7368
|
post("/api/note/delete", z.object({ base: z.string().max(64) }).strict(), (c, b) => api.deleteNote(id(c), b.base));
|
|
7030
7369
|
post("/api/note/done", z.object({
|
|
7031
7370
|
done: z.boolean(),
|
|
@@ -7401,6 +7740,9 @@ var NodeFiles = class {
|
|
|
7401
7740
|
throw e;
|
|
7402
7741
|
}
|
|
7403
7742
|
}
|
|
7743
|
+
async mkdir(dir) {
|
|
7744
|
+
await mkdir(this.at(dir), { recursive: true });
|
|
7745
|
+
}
|
|
7404
7746
|
async append(path, text) {
|
|
7405
7747
|
const full = this.at(path);
|
|
7406
7748
|
await mkdir(dirname(full), { recursive: true });
|
|
@@ -7757,12 +8099,14 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
|
|
|
7757
8099
|
console.error(e.code === "EADDRINUSE" ? `port ${port} is in use (isorite serve --port <n>)` : e.message);
|
|
7758
8100
|
process.exit(1);
|
|
7759
8101
|
});
|
|
7760
|
-
console.log(`
|
|
7761
|
-
console.log(
|
|
8102
|
+
const row = (label, text) => console.log(` ${dim(label.padEnd(8))}${text}`);
|
|
8103
|
+
console.log(`${bold("isorite")} ${notes}`);
|
|
8104
|
+
row("search", embedder ? "by meaning" : `by words ${dim("(isorite model fetches the model, 53 MB)")}`);
|
|
7762
8105
|
const lent = bases.lent().filter((l) => l.name !== bases.first).map((l) => l.name);
|
|
7763
|
-
if (lent.length)
|
|
7764
|
-
|
|
7765
|
-
|
|
8106
|
+
if (lent.length) row("lent", `${lent.join(", ")} ${dim("(isorite bases)")}`);
|
|
8107
|
+
if (existsSync(join(WEB_DIR, "index.html"))) row("open", link(`http://localhost:${port}/`));
|
|
8108
|
+
else row("open", `the app is not built (npm run build); the engine alone is at http://localhost:${port}/api/`);
|
|
8109
|
+
if (process.env.SSH_CONNECTION && LOOPBACK.has(host)) row("ssh", `opens on this machine; from your own, first: ssh -L ${port}:localhost:${port} ${hostname()}`);
|
|
7766
8110
|
stop = () => {
|
|
7767
8111
|
server.close();
|
|
7768
8112
|
process.exit(0);
|
|
@@ -7781,6 +8125,12 @@ const MARK = {
|
|
|
7781
8125
|
warn: "!",
|
|
7782
8126
|
skip: "–"
|
|
7783
8127
|
};
|
|
8128
|
+
const PAINT = {
|
|
8129
|
+
ok: green,
|
|
8130
|
+
fail: red,
|
|
8131
|
+
warn: yellow,
|
|
8132
|
+
skip: dim
|
|
8133
|
+
};
|
|
7784
8134
|
/** The environment variables isorite and Node read for it; the secret ones are shown only by length. */
|
|
7785
8135
|
const READ = [
|
|
7786
8136
|
"XDG_CONFIG_HOME",
|
|
@@ -7823,7 +8173,7 @@ async function doctor(folder, o = {}) {
|
|
|
7823
8173
|
outcome,
|
|
7824
8174
|
said
|
|
7825
8175
|
});
|
|
7826
|
-
if (!o.report) out(`${MARK[outcome]} ${step}: ${said}`);
|
|
8176
|
+
if (!o.report) out(`${PAINT[outcome](MARK[outcome])} ${bold(step)}: ${outcome === "skip" ? dim(said) : said}`);
|
|
7827
8177
|
};
|
|
7828
8178
|
const path = configPath(env), execArgv = o.execArgv ?? process.execArgv;
|
|
7829
8179
|
if (!existsSync(path)) say("config", "ok", `there is no ${path} yet: it is made when a base is first opened`);
|
|
@@ -7931,7 +8281,7 @@ async function doctor(folder, o = {}) {
|
|
|
7931
8281
|
...checks.map((c) => `${MARK[c.outcome]} ${c.step}: ${c.said}`),
|
|
7932
8282
|
"```"
|
|
7933
8283
|
].join("\n"));
|
|
7934
|
-
} else out(failed ? `${failed} failed
|
|
8284
|
+
} else out(failed ? `${red(`${failed} failed.`)} isorite doctor --report gives all this as one block, its secrets hidden, to paste to someone helping.` : green("nothing failed"));
|
|
7935
8285
|
return failed ? 1 : 0;
|
|
7936
8286
|
}
|
|
7937
8287
|
//#endregion
|
|
@@ -9521,7 +9871,7 @@ function bases(args, { config = configPath(), out = console.log } = {}) {
|
|
|
9521
9871
|
return 0;
|
|
9522
9872
|
}
|
|
9523
9873
|
const width = Math.max(...known.map((b) => b.name.length));
|
|
9524
|
-
for (const b of known) out(`${b.name.padEnd(width)} ${b.lend ? "lent " : " "} ${b.path}${existsSync(b.path) ? "" :
|
|
9874
|
+
for (const b of known) out(`${bold(b.name.padEnd(width))} ${b.lend ? "lent " : " "} ${dim(b.path)}${existsSync(b.path) ? "" : ` ${red("(not there)")}`}`);
|
|
9525
9875
|
return 0;
|
|
9526
9876
|
}
|
|
9527
9877
|
//#endregion
|
|
@@ -9591,7 +9941,7 @@ async function start(io) {
|
|
|
9591
9941
|
if (found.kind === "base") return io.open(found.notes);
|
|
9592
9942
|
if (found.kind === "several") {
|
|
9593
9943
|
io.out(`isorite: more than one base here:`);
|
|
9594
|
-
found.notes.forEach((n, i) => io.out(` ${i + 1} ${n}`));
|
|
9944
|
+
found.notes.forEach((n, i) => io.out(` ${dim(String(i + 1))} ${n}`));
|
|
9595
9945
|
const pick = await io.choose("Open which?", found.notes.length);
|
|
9596
9946
|
return pick === null ? 2 : io.open(found.notes[pick]);
|
|
9597
9947
|
}
|
|
@@ -9612,7 +9962,7 @@ async function start(io) {
|
|
|
9612
9962
|
"the guide"
|
|
9613
9963
|
];
|
|
9614
9964
|
io.out(known.length ? "isorite: the bases on this machine, last used first:" : "isorite:");
|
|
9615
|
-
rows.forEach((r, i) => io.out(` ${i + 1} ${r}`));
|
|
9965
|
+
rows.forEach((r, i) => io.out(` ${dim(String(i + 1))} ${r}`));
|
|
9616
9966
|
const pick = await io.choose("Open which?", rows.length);
|
|
9617
9967
|
if (pick === null) return 2;
|
|
9618
9968
|
if (pick < known.length) return io.open(known[pick].path);
|
|
@@ -9639,62 +9989,66 @@ init_embedder();
|
|
|
9639
9989
|
init_model();
|
|
9640
9990
|
const SHORT = `isorite: an agentic harness for understanding
|
|
9641
9991
|
|
|
9642
|
-
isorite open the base here, or choose one
|
|
9643
|
-
isorite doctor
|
|
9644
|
-
|
|
9645
|
-
isorite help this; isorite help all lists every command
|
|
9992
|
+
isorite open the base here, or choose one
|
|
9993
|
+
isorite doctor can isorite work here, and what to change if not
|
|
9994
|
+
isorite help this; help all lists every command
|
|
9646
9995
|
isorite --version
|
|
9647
9996
|
|
|
9648
|
-
Notes are plain Markdown in a folder of your project
|
|
9997
|
+
Notes are plain Markdown in a folder of your project. Your agent reads and writes them with you.
|
|
9649
9998
|
`;
|
|
9650
9999
|
const USAGE = `isorite: an agentic harness for understanding
|
|
9651
10000
|
|
|
9652
|
-
isorite new <folder>
|
|
9653
|
-
isorite check <notes> [--unused]
|
|
9654
|
-
|
|
10001
|
+
isorite new <folder> a new base: an empty folder with .isorite/
|
|
10002
|
+
isorite check <notes> [--unused] [--fix]
|
|
10003
|
+
lint: frontmatter, links, cycles; --unused: pictures no note
|
|
10004
|
+
shows; --fix: quote a colon in frontmatter
|
|
9655
10005
|
isorite serve <notes> [--port <n>] [--allow-host <name>]
|
|
9656
10006
|
open it in the browser, on this machine only
|
|
9657
|
-
isorite migrate <study base> [--merge]
|
|
9658
|
-
|
|
9659
|
-
isorite example <folder> [--geometry] write the example base (notes on an N-body simulation, three weeks
|
|
9660
|
-
into being built), into an empty folder; --geometry: notes on the
|
|
9661
|
-
differential geometry of surfaces, with their questions and problems
|
|
10007
|
+
isorite migrate <study base> [--merge] a study base (format 1) into its notes' .isorite/
|
|
10008
|
+
isorite example <folder> [--geometry] an example base, partway built, in an empty folder
|
|
9662
10009
|
isorite mcp [<notes>] [--port <n>] [--agent <name>]
|
|
9663
|
-
the
|
|
9664
|
-
|
|
10010
|
+
the base's tools over MCP, on stdio:
|
|
10011
|
+
claude mcp add isorite -- isorite mcp <base>
|
|
9665
10012
|
isorite acp --agent "<command>" [<notes>] [--port <n>] [--pane]
|
|
9666
|
-
an ACP agent
|
|
9667
|
-
|
|
9668
|
-
sessions in Iso's pane; --pane: no front end, the pane alone
|
|
10013
|
+
an ACP agent behind your ACP front end, its sessions in Iso's
|
|
10014
|
+
pane; --pane: the pane alone
|
|
9669
10015
|
isorite model [--from <folder|.tgz>] [--url <url>]
|
|
9670
|
-
the model for search by meaning: which
|
|
9671
|
-
|
|
9672
|
-
Hugging Face, or --url alone; or from the .tgz fetched by hand, or a
|
|
9673
|
-
folder: its files copied there, or any compatible model
|
|
10016
|
+
the model for search by meaning: which, and where; else fetch it
|
|
10017
|
+
(53 MB, once)
|
|
9674
10018
|
isorite doctor [<notes>] [--report] [--offline]
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
isorite
|
|
9680
|
-
|
|
9681
|
-
isorite bases [lend|keep <name>] the bases this device knows, each listed as it is opened; lend one to
|
|
9682
|
-
every session, or keep it to itself
|
|
9683
|
-
isorite help setup how isorite is set up, every setting and failure: written for an AI
|
|
9684
|
-
helping you set it up (docs/setup.md is for people)
|
|
9685
|
-
isorite guide [<folder>] [--port <n>] the guide: a knowledge base about isorite, with things to do in it,
|
|
9686
|
-
written into a folder (~/isorite-guide unless named) and opened in
|
|
9687
|
-
the browser; run again, as you left it (also: isorite --tutorial)
|
|
10019
|
+
can isorite work here, a step at a time; --report: one block to
|
|
10020
|
+
paste to someone helping
|
|
10021
|
+
isorite memory [<folder>] the agent's memory: a base of its own, lent to every session
|
|
10022
|
+
isorite bases [lend|keep <name>] the bases this device knows; lend one to every session, or keep it
|
|
10023
|
+
isorite help setup every setting and failure, written for an AI helping you set up
|
|
10024
|
+
isorite guide [<folder>] [--port <n>] the guide: a base about isorite, to do things in (--tutorial)
|
|
9688
10025
|
`;
|
|
9689
|
-
function check(notes, out = console.log, unused = false) {
|
|
10026
|
+
function check(notes, out = console.log, unused = false, fix = false) {
|
|
9690
10027
|
const root = resolve(notes);
|
|
9691
10028
|
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
9692
10029
|
out(`no folder at ${root}`);
|
|
9693
10030
|
return 2;
|
|
9694
10031
|
}
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
10032
|
+
let bundle = loadBundle(root), issues = bundle.lint();
|
|
10033
|
+
const mendable = issues.filter((i) => i.code === "frontmatter-invalid").flatMap((i) => {
|
|
10034
|
+
const m = repairFrontmatter(readFileSync(join(root, i.path), "utf8"));
|
|
10035
|
+
return m ? [{
|
|
10036
|
+
path: i.path,
|
|
10037
|
+
...m
|
|
10038
|
+
}] : [];
|
|
10039
|
+
});
|
|
10040
|
+
if (fix && mendable.length) {
|
|
10041
|
+
for (const m of mendable) {
|
|
10042
|
+
writeFileSync(join(root, m.path), m.text);
|
|
10043
|
+
out(`fixed ${m.path}: quoted ${m.fixed.join(", ")}`);
|
|
10044
|
+
}
|
|
10045
|
+
bundle = loadBundle(root);
|
|
10046
|
+
issues = bundle.lint();
|
|
10047
|
+
}
|
|
10048
|
+
const cycles = bundle.requiresCycles();
|
|
10049
|
+
const level = (l) => (l === "error" ? red(l) : yellow(l)) + " ".repeat(Math.max(0, 7 - l.length));
|
|
10050
|
+
for (const i of issues) out(`${level(i.level)} ${bold(i.path)}: ${i.message}${i.code === "frontmatter-invalid" && !fix && mendable.some((m) => m.path === i.path) ? " (isorite check --fix quotes it)" : ""}`);
|
|
10051
|
+
for (const c of cycles) out(`${level("warning")} requires cycle: ${c.join(" -> ")}`);
|
|
9698
10052
|
let artifactErrors = 0, artifactWarnings = 0;
|
|
9699
10053
|
for (const f of [...bundle.files].filter((x) => citeKind(x) === "artifact").sort()) {
|
|
9700
10054
|
const html = readFileSync(join(root, f), "utf8");
|
|
@@ -9711,13 +10065,14 @@ function check(notes, out = console.log, unused = false) {
|
|
|
9711
10065
|
files,
|
|
9712
10066
|
columns
|
|
9713
10067
|
})) {
|
|
9714
|
-
out(`${b.level
|
|
10068
|
+
out(`${level(b.level)} ${bold(f)}: ${b.message}`);
|
|
9715
10069
|
if (b.level === "error") artifactErrors++;
|
|
9716
10070
|
else artifactWarnings++;
|
|
9717
10071
|
}
|
|
9718
10072
|
}
|
|
9719
10073
|
const errors = issues.filter((i) => i.level === "error").length + artifactErrors;
|
|
9720
|
-
|
|
10074
|
+
const warnings = issues.length - issues.filter((i) => i.level === "error").length + artifactWarnings;
|
|
10075
|
+
out(`${dim(`${bundle.concepts.size} notes in ${bundle.directories.size} folders;`)} ${(errors ? red : green)(`${errors} errors`)}, ${(warnings ? yellow : green)(`${warnings} warnings`)}, ${cycles.length} requires cycles`);
|
|
9721
10076
|
if (unused) {
|
|
9722
10077
|
const cited = new Set([...bundle.concepts.values()].flatMap((c) => c.cites.map((x) => x.target)));
|
|
9723
10078
|
const idle = [...bundle.files].filter((f) => citeKind(f) === "image" && !cited.has(f)).sort();
|
|
@@ -9803,7 +10158,7 @@ function main(argv) {
|
|
|
9803
10158
|
noOpen: args.includes("--no-open")
|
|
9804
10159
|
});
|
|
9805
10160
|
}
|
|
9806
|
-
if (command === "check" && rest.find((r) => !r.startsWith("--"))) return check(rest.find((r) => !r.startsWith("--")), console.log, rest.includes("--unused"));
|
|
10161
|
+
if (command === "check" && rest.find((r) => !r.startsWith("--"))) return check(rest.find((r) => !r.startsWith("--")), console.log, rest.includes("--unused"), rest.includes("--fix"));
|
|
9807
10162
|
if (command === "new" && rest[0]) {
|
|
9808
10163
|
const made = newBase(rest[0]);
|
|
9809
10164
|
const next = `${made}: a knowledge base, empty. Open it in the browser, and attach your agent from Iso's pane:\n isorite serve ${made}`;
|
|
@@ -10001,7 +10356,7 @@ function main(argv) {
|
|
|
10001
10356
|
}
|
|
10002
10357
|
throw e;
|
|
10003
10358
|
}
|
|
10004
|
-
process.stdout.write(command === "help" && rest[0] === "all" ? USAGE : command && command !== "help" && command !== "--help" ? USAGE : SHORT);
|
|
10359
|
+
process.stdout.write(helpText(command === "help" && rest[0] === "all" ? USAGE : command && command !== "help" && command !== "--help" ? USAGE : SHORT));
|
|
10005
10360
|
return command && command !== "help" && command !== "--help" ? 2 : 0;
|
|
10006
10361
|
}
|
|
10007
10362
|
/** The commands that start something, which say so at once (M21): a subprocess that stalls after this is then
|