isorite 0.14.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 +757 -98
- package/docs/terminal.md +3 -1
- 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/{BWE_HS0c.js → 0B47b24t.js} +1 -1
- package/web/_app/immutable/chunks/{CzYPMYd4.js → 1j--pDqt.js} +1 -1
- package/web/_app/immutable/chunks/{e2LGvGEb.js → AnnzTlE8.js} +1 -1
- package/web/_app/immutable/chunks/{DRcTlHZP.js → B7LnLClC.js} +1 -1
- package/web/_app/immutable/chunks/{ZwM5k8r0.js → BAVnZFPa.js} +1 -1
- package/web/_app/immutable/chunks/BHORylQC.js +1 -0
- package/web/_app/immutable/chunks/{BiATTH-R.js → BLe29cGh.js} +1 -1
- package/web/_app/immutable/chunks/{C8h1oeAZ.js → BLzZIf7l.js} +1 -1
- package/web/_app/immutable/chunks/BMijTvgG.js +1 -0
- package/web/_app/immutable/chunks/{DCZYeMlH.js → BPpUSQ2Y.js} +1 -1
- package/web/_app/immutable/chunks/{kG4HctVa.js → BQeONalw.js} +1 -1
- package/web/_app/immutable/chunks/{CtkUy_15.js → BZX8cNYL.js} +1 -1
- package/web/_app/immutable/chunks/{Bb_N98K9.js → BiHyHFlW.js} +1 -1
- package/web/_app/immutable/chunks/{BGW1TKtR.js → BtYMl4bD.js} +1 -1
- package/web/_app/immutable/chunks/{DKqMzyyE.js → BtcGx-KQ.js} +1 -1
- package/web/_app/immutable/chunks/{BK31dpt6.js → Bytzbt5l.js} +1 -1
- package/web/_app/immutable/chunks/{DDAG55ce.js → C1FDDux-.js} +1 -1
- package/web/_app/immutable/chunks/C7qBBO-i.js +1 -0
- package/web/_app/immutable/chunks/{NyWzYSz1.js → C8FxuPry.js} +1 -1
- package/web/_app/immutable/chunks/{CdY1G_wk.js → C9Qdslk3.js} +1 -1
- package/web/_app/immutable/chunks/CA-rzUq8.js +524 -0
- package/web/_app/immutable/chunks/{CMIt1Ma8.js → CSKABQj2.js} +1 -1
- package/web/_app/immutable/chunks/CSTv2fL0.js +1 -0
- package/web/_app/immutable/chunks/{Dzi94aDH.js → CTuuQ66F.js} +1 -1
- package/web/_app/immutable/chunks/{PsfIQCJ1.js → CUB9vnd0.js} +1 -1
- package/web/_app/immutable/chunks/{DrVtdHUW.js → CUNetmvQ.js} +1 -1
- package/web/_app/immutable/chunks/{Djr0h_v1.js → CVqwkR8N.js} +1 -1
- package/web/_app/immutable/chunks/{CTDYUZPz.js → CWc-mNzo.js} +1 -1
- package/web/_app/immutable/chunks/{CryuQgNh.js → CZSmkKvQ.js} +1 -1
- package/web/_app/immutable/chunks/{BA78_vo5.js → CcE14TDI.js} +1 -1
- package/web/_app/immutable/chunks/{DBLoWLMb.js → CglDFp51.js} +1 -1
- package/web/_app/immutable/chunks/{DjUnonHY.js → Ch5Z7cbP.js} +1 -1
- package/web/_app/immutable/chunks/{DFuPQaNa.js → Cpjqsnqr.js} +1 -1
- package/web/_app/immutable/chunks/{Bd-WxALw.js → CwHHpacD.js} +1 -1
- package/web/_app/immutable/chunks/{UDPiRmKc.js → D7jLdCNu.js} +1 -1
- package/web/_app/immutable/chunks/{CIYFNih-.js → D9uNsgxP.js} +1 -1
- package/web/_app/immutable/chunks/{CFf_dAzX.js → DAVMwq6b.js} +1 -1
- package/web/_app/immutable/chunks/{C2npZzkn.js → DEQsS4Ez.js} +1 -1
- package/web/_app/immutable/chunks/{B-DzK7Gb.js → DFo4P1vU.js} +1 -1
- package/web/_app/immutable/chunks/{CdmC_l9F.js → DH_19--P.js} +1 -1
- package/web/_app/immutable/chunks/{l1LsK1sB.js → DJQ6favH.js} +1 -1
- package/web/_app/immutable/chunks/{BvPxdlBc.js → DJT73ARj.js} +1 -1
- package/web/_app/immutable/chunks/{BCGaqnao.js → DW5uy3Jw.js} +1 -1
- package/web/_app/immutable/chunks/{BT_btfh0.js → DWwu8a1P.js} +1 -1
- package/web/_app/immutable/chunks/{BhEqJU15.js → DY0M2GMn.js} +1 -1
- package/web/_app/immutable/chunks/{TanDZqdf.js → DbtvzL7r.js} +1 -1
- package/web/_app/immutable/chunks/DtEVmiYo.js +1 -0
- package/web/_app/immutable/chunks/{CT4_O9X_.js → DxRGLDbV.js} +1 -1
- package/web/_app/immutable/chunks/{D6sVhEz3.js → Ih6r6QR2.js} +1 -1
- package/web/_app/immutable/chunks/{BXBhaEho.js → Kem7IXj7.js} +1 -1
- package/web/_app/immutable/chunks/{Ct_EK1xu.js → MrW4zd8l.js} +1 -1
- package/web/_app/immutable/chunks/{BqipEJXw.js → Mwr1r9aw.js} +1 -1
- package/web/_app/immutable/chunks/{D1LK3xRu.js → OG9GsKke.js} +1 -1
- package/web/_app/immutable/chunks/{DymLl-qY.js → Qej0SEux.js} +1 -1
- package/web/_app/immutable/chunks/TmxIGCN3.js +1 -0
- package/web/_app/immutable/chunks/{CPSlCQsu.js → Wv-ICQRy.js} +4 -4
- package/web/_app/immutable/chunks/{B35sQTcK.js → ZTEErEOd.js} +1 -1
- package/web/_app/immutable/chunks/{Cf-5I5_6.js → e7GRa0yc.js} +1 -1
- package/web/_app/immutable/chunks/{C5Z-Y7Lu.js → f7mFVWQS.js} +1 -1
- package/web/_app/immutable/chunks/{Bj-5n5Pb.js → hjSZ041d.js} +1 -1
- package/web/_app/immutable/chunks/{DHJ8FcFD.js → mhWk5y4o.js} +1 -1
- package/web/_app/immutable/chunks/{CMikI1NP.js → zasx25UV.js} +3 -3
- package/web/_app/immutable/entry/app.B9TPZNFw.js +2 -0
- package/web/_app/immutable/entry/start.WU9HKeyT.js +1 -0
- package/web/_app/immutable/nodes/0.C9NFEqJd.js +3 -0
- package/web/_app/immutable/nodes/1.B5PJwWOG.js +1 -0
- package/web/_app/immutable/nodes/2.DRtC7zjs.js +1 -0
- package/web/_app/immutable/nodes/3.EEMisvUL.js +1 -0
- package/web/_app/immutable/nodes/4.DthmsFQC.js +19 -0
- package/web/_app/immutable/nodes/5.CEu0DlpQ.js +1 -0
- package/web/_app/immutable/nodes/6.DzvF0ZPt.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +16 -16
- package/web/_app/immutable/assets/0.BPP604gH.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/BPrp4s2r.js +0 -1
- package/web/_app/immutable/chunks/CAlIuau0.js +0 -1
- package/web/_app/immutable/chunks/Cl5gQm2e.js +0 -1
- package/web/_app/immutable/chunks/DFKYo3Wx.js +0 -1
- package/web/_app/immutable/chunks/DxVFSMVD.js +0 -523
- package/web/_app/immutable/chunks/X3aTZRe_.js +0 -1
- package/web/_app/immutable/chunks/ej_0XxR7.js +0 -1
- package/web/_app/immutable/entry/app.BzA5QYtw.js +0 -2
- package/web/_app/immutable/entry/start.BOh0Gj31.js +0 -1
- package/web/_app/immutable/nodes/0.BmaxMLzC.js +0 -2
- package/web/_app/immutable/nodes/1.IQDu8pD1.js +0 -1
- package/web/_app/immutable/nodes/2.D2Oi-ADx.js +0 -1
- package/web/_app/immutable/nodes/3.CF1W85jb.js +0 -1
- package/web/_app/immutable/nodes/4.BMYBn904.js +0 -15
- package/web/_app/immutable/nodes/5.1vm6WZdM.js +0 -1
- package/web/_app/immutable/nodes/6.C9ia8cDm.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}$/;
|
|
@@ -2865,7 +3103,7 @@ var Conflict = class extends EngineError {
|
|
|
2865
3103
|
};
|
|
2866
3104
|
//#endregion
|
|
2867
3105
|
//#region packages/engine/src/lookup.ts
|
|
2868
|
-
const str$
|
|
3106
|
+
const str$2 = { type: "string" };
|
|
2869
3107
|
const fn$1 = (name, description, properties, required) => ({
|
|
2870
3108
|
type: "function",
|
|
2871
3109
|
function: {
|
|
@@ -2881,9 +3119,9 @@ const fn$1 = (name, description, properties, required) => ({
|
|
|
2881
3119
|
});
|
|
2882
3120
|
const TOOLS = [
|
|
2883
3121
|
fn$1("search_notes", "Keyword search over the knowledge base. Returns note ids, titles, types, one-sentence descriptions and a one-line snippet. Search by the words a note would use.", {
|
|
2884
|
-
query: str$
|
|
3122
|
+
query: str$2,
|
|
2885
3123
|
under: {
|
|
2886
|
-
...str$
|
|
3124
|
+
...str$2,
|
|
2887
3125
|
description: "Only notes in this folder."
|
|
2888
3126
|
},
|
|
2889
3127
|
limit: {
|
|
@@ -2892,24 +3130,24 @@ const TOOLS = [
|
|
|
2892
3130
|
}
|
|
2893
3131
|
}, ["query"]),
|
|
2894
3132
|
fn$1("outline_note", "A note's description, its headings, the notes it links to and the notes that link to it, without its body. Use it to choose a section to read.", { id: {
|
|
2895
|
-
...str$
|
|
3133
|
+
...str$2,
|
|
2896
3134
|
description: "The note's id or path: basics/entropy or /basics/entropy.md."
|
|
2897
3135
|
} }, ["id"]),
|
|
2898
3136
|
fn$1("read_note", "Read one section of a note, by its heading, or the whole note when no section is named. Prefer a section. A picture in it is : you have its words, not the picture.", {
|
|
2899
|
-
id: str$
|
|
3137
|
+
id: str$2,
|
|
2900
3138
|
section: {
|
|
2901
|
-
...str$
|
|
3139
|
+
...str$2,
|
|
2902
3140
|
description: "A heading's text, as outline_note gives it."
|
|
2903
3141
|
}
|
|
2904
3142
|
}, ["id"])
|
|
2905
3143
|
];
|
|
2906
3144
|
const FIND_SIMILAR = fn$1("find_similar", "Find notes by meaning: notes that say something like the text you give, even in quite other words. Slower and vaguer than search_notes: use it after search_notes and the links of the notes in hand have not found what you need, or when you cannot guess the words a note would use. Give a question or a sentence, not keywords. Returns each note's nearest section, with how alike it is (0 to 1).", {
|
|
2907
3145
|
text: {
|
|
2908
|
-
...str$
|
|
3146
|
+
...str$2,
|
|
2909
3147
|
description: "A question, or a sentence saying what you are looking for."
|
|
2910
3148
|
},
|
|
2911
3149
|
under: {
|
|
2912
|
-
...str$
|
|
3150
|
+
...str$2,
|
|
2913
3151
|
description: "Only notes in this folder."
|
|
2914
3152
|
},
|
|
2915
3153
|
limit: {
|
|
@@ -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
|
|
|
@@ -3279,7 +3525,7 @@ or try; not for what an answer or a note says as well.
|
|
|
3279
3525
|
problem, fix that and write the whole file again.
|
|
3280
3526
|
4. Say in a sentence what it shows, linked: [what it shows](/folder/name.html).
|
|
3281
3527
|
Link it from the note it belongs to if they want it there.`;
|
|
3282
|
-
const str = { type: "string" };
|
|
3528
|
+
const str$1 = { type: "string" };
|
|
3283
3529
|
const fn = (name, description, properties, required) => ({
|
|
3284
3530
|
type: "function",
|
|
3285
3531
|
function: {
|
|
@@ -3295,16 +3541,16 @@ const fn = (name, description, properties, required) => ({
|
|
|
3295
3541
|
});
|
|
3296
3542
|
const ASK_QUESTION = fn("ask_question", "Ask them a question back, kept as an open question in their base, about one or more of their notes.", {
|
|
3297
3543
|
title: {
|
|
3298
|
-
...str,
|
|
3544
|
+
...str$1,
|
|
3299
3545
|
description: "The question, short, ending in a question mark."
|
|
3300
3546
|
},
|
|
3301
3547
|
text: {
|
|
3302
|
-
...str,
|
|
3548
|
+
...str$1,
|
|
3303
3549
|
description: "A line or two on what is asked, or empty."
|
|
3304
3550
|
},
|
|
3305
3551
|
about: {
|
|
3306
3552
|
type: "array",
|
|
3307
|
-
items: str,
|
|
3553
|
+
items: str$1,
|
|
3308
3554
|
description: "The ids of the notes it is about."
|
|
3309
3555
|
}
|
|
3310
3556
|
}, [
|
|
@@ -3313,7 +3559,7 @@ const ASK_QUESTION = fn("ask_question", "Ask them a question back, kept as an op
|
|
|
3313
3559
|
"about"
|
|
3314
3560
|
]);
|
|
3315
3561
|
const READ_DATA = fn("read_data", "A data file's shape: its columns with their types and ranges, the row count and the first rows. Read it before writing anything that uses the file, so the column names are real.", { path: {
|
|
3316
|
-
...str,
|
|
3562
|
+
...str$1,
|
|
3317
3563
|
description: "The file's path in the base: folder/name.csv."
|
|
3318
3564
|
} }, ["path"]);
|
|
3319
3565
|
const ARTIFACT_RECIPE = fn("artifact_recipe", "A recipe for an artifact: a complete working one to start from, the kit's reference, and the rules write_artifact checks. dashboard: numbers, charts and a table of one data file. compare: two data files side by side. simulation: a model with parameters and play. explorable: inputs, derived numbers and linked views. viewer: a 3D model.", { name: {
|
|
@@ -3328,21 +3574,21 @@ const ARTIFACT_RECIPE = fn("artifact_recipe", "A recipe for an artifact: a compl
|
|
|
3328
3574
|
} }, ["name"]);
|
|
3329
3575
|
const WRITE_ARTIFACT = fn("write_artifact", "Write an artifact: a whole HTML file on the kit. The title, description and uses given here are written into it. It is checked, and run where it can be; the result says what to fix. Nothing is written while a rule is broken.", {
|
|
3330
3576
|
path: {
|
|
3331
|
-
...str,
|
|
3577
|
+
...str$1,
|
|
3332
3578
|
description: "Where: folder/name.html, beside the notes it belongs to."
|
|
3333
3579
|
},
|
|
3334
|
-
title: str,
|
|
3580
|
+
title: str$1,
|
|
3335
3581
|
description: {
|
|
3336
|
-
...str,
|
|
3582
|
+
...str$1,
|
|
3337
3583
|
description: "One sentence: what it shows, and what can be done with it."
|
|
3338
3584
|
},
|
|
3339
3585
|
uses: {
|
|
3340
3586
|
type: "array",
|
|
3341
|
-
items: str,
|
|
3587
|
+
items: str$1,
|
|
3342
3588
|
description: "The data or model files it reads, relative to it (runs.csv). Those its markup and script name are added."
|
|
3343
3589
|
},
|
|
3344
3590
|
html: {
|
|
3345
|
-
...str,
|
|
3591
|
+
...str$1,
|
|
3346
3592
|
description: "The whole file."
|
|
3347
3593
|
}
|
|
3348
3594
|
}, [
|
|
@@ -3353,16 +3599,16 @@ const WRITE_ARTIFACT = fn("write_artifact", "Write an artifact: a whole HTML fil
|
|
|
3353
3599
|
"html"
|
|
3354
3600
|
]);
|
|
3355
3601
|
const READ_ARTIFACT = fn("read_artifact", "An artifact's whole file, to change it and write it again.", { path: {
|
|
3356
|
-
...str,
|
|
3602
|
+
...str$1,
|
|
3357
3603
|
description: "folder/name.html"
|
|
3358
3604
|
} }, ["path"]);
|
|
3359
3605
|
const OFFER_TEXT = fn("offer_text", "Offer them their own answers, lightly joined, to insert at the end of a note as their writing. Only their words.", {
|
|
3360
3606
|
id: {
|
|
3361
|
-
...str,
|
|
3607
|
+
...str$1,
|
|
3362
3608
|
description: "The note's id, or AGENTS.md for a line of their instructions for the base."
|
|
3363
3609
|
},
|
|
3364
3610
|
text: {
|
|
3365
|
-
...str,
|
|
3611
|
+
...str$1,
|
|
3366
3612
|
description: "The passage, in Markdown."
|
|
3367
3613
|
}
|
|
3368
3614
|
}, ["id", "text"]);
|
|
@@ -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 = {
|
|
@@ -4232,6 +4480,10 @@ var Engine = class Engine {
|
|
|
4232
4480
|
return null;
|
|
4233
4481
|
}
|
|
4234
4482
|
}
|
|
4483
|
+
async deleteAgentRecord(agent, session) {
|
|
4484
|
+
if (!AGENT_ID.test(agent) || !session || session.length > 200) throw new EngineError("an agent's session is named by the agent and its session's id");
|
|
4485
|
+
await this.opts.own.remove(agentRecordPath(agent, session));
|
|
4486
|
+
}
|
|
4235
4487
|
async agentRecords() {
|
|
4236
4488
|
const own = this.opts.own, all = [];
|
|
4237
4489
|
for (const a of await own.list(AGENTS_DIR).catch(() => [])) {
|
|
@@ -4303,6 +4555,25 @@ var Engine = class Engine {
|
|
|
4303
4555
|
createNote(id, text) {
|
|
4304
4556
|
return this.one(() => this.put(id, text, null));
|
|
4305
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
|
+
}
|
|
4306
4577
|
/** `who` is you unless Iso moves it, in Write mode. */
|
|
4307
4578
|
async moveNote(id, to, base, who = "you") {
|
|
4308
4579
|
to = to.trim().replace(/^\/+|\/+$/g, "").replace(/\.md$/, "");
|
|
@@ -4447,6 +4718,63 @@ var Engine = class Engine {
|
|
|
4447
4718
|
version: sha256(bytes).slice(0, 16)
|
|
4448
4719
|
};
|
|
4449
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
|
+
}
|
|
4450
4778
|
/** What was read of each picture, kept while its size and time are the same. */
|
|
4451
4779
|
pictureCache = /* @__PURE__ */ new Map();
|
|
4452
4780
|
/** Every picture in the notes (M22), found afresh: one put in the folder by hand since the base was opened is
|
|
@@ -4552,6 +4880,7 @@ var Engine = class Engine {
|
|
|
4552
4880
|
async put(id, text, base, why = "edit", extra = {}, who = "you") {
|
|
4553
4881
|
const { files, path } = this.where(id);
|
|
4554
4882
|
if (text.length > 2e6) throw new EngineError("note too large", 413);
|
|
4883
|
+
if (who !== "you" && path.endsWith(".md")) text = repairFrontmatter(text)?.text ?? text;
|
|
4555
4884
|
const now = await files.read(path);
|
|
4556
4885
|
if (base === null && now !== null) throw new EngineError(`there is already a note at ${id}`, 409);
|
|
4557
4886
|
if (base !== null && now === null) throw new Conflict(`${id} is gone`, null);
|
|
@@ -4648,6 +4977,10 @@ var Engine = class Engine {
|
|
|
4648
4977
|
turns: readTurns(text)
|
|
4649
4978
|
};
|
|
4650
4979
|
}
|
|
4980
|
+
async deleteConversation(id) {
|
|
4981
|
+
if (!TURN_ID.test(id)) throw new EngineError("not a conversation's id");
|
|
4982
|
+
await this.opts.own.remove(conversationPath(id));
|
|
4983
|
+
}
|
|
4651
4984
|
async conversations() {
|
|
4652
4985
|
const study = this.opts.own;
|
|
4653
4986
|
const names = await study.list(CONVERSATIONS_DIR).catch(() => []);
|
|
@@ -4939,6 +5272,8 @@ var MemoryFiles = class {
|
|
|
4939
5272
|
files = /* @__PURE__ */ new Map();
|
|
4940
5273
|
/** Files held as bytes (pictures), apart from text. */
|
|
4941
5274
|
bytes = /* @__PURE__ */ new Map();
|
|
5275
|
+
/** Folders made empty (M31). */
|
|
5276
|
+
folders = /* @__PURE__ */ new Set();
|
|
4942
5277
|
constructor(files = {}) {
|
|
4943
5278
|
for (const [path, text] of Object.entries(files)) this.files.set(path, text);
|
|
4944
5279
|
}
|
|
@@ -4963,10 +5298,17 @@ var MemoryFiles = class {
|
|
|
4963
5298
|
this.files.delete(path);
|
|
4964
5299
|
this.bytes.delete(path);
|
|
4965
5300
|
}
|
|
5301
|
+
async mkdir(dir) {
|
|
5302
|
+
this.folders.add(dir);
|
|
5303
|
+
}
|
|
4966
5304
|
async list(dir) {
|
|
4967
5305
|
const prefix = dir ? dir + "/" : "";
|
|
4968
5306
|
const found = /* @__PURE__ */ new Map();
|
|
4969
|
-
for (const path of [
|
|
5307
|
+
for (const path of [
|
|
5308
|
+
...this.files.keys(),
|
|
5309
|
+
...this.bytes.keys(),
|
|
5310
|
+
...[...this.folders].map((f) => f + "/")
|
|
5311
|
+
]) {
|
|
4970
5312
|
if (!path.startsWith(prefix)) continue;
|
|
4971
5313
|
const rest = path.slice(prefix.length), cut = rest.indexOf("/");
|
|
4972
5314
|
if (cut >= 0) {
|
|
@@ -5320,6 +5662,47 @@ const EMPTY = {
|
|
|
5320
5662
|
const textOf = (blocks) => (Array.isArray(blocks) ? blocks : [blocks]).map((b) => b?.type === "text" ? b.text ?? "" : b?.type === "resource_link" ? `@${b.name ?? b.uri ?? ""}` : "").join("");
|
|
5321
5663
|
const lines = (v) => v ? v.replace(/\n$/, "").split("\n") : [];
|
|
5322
5664
|
const keyOf = (agent, session) => `${agent}\u0000${session}`;
|
|
5665
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
5666
|
+
/** The agent's slash commands, from available_commands_update. */
|
|
5667
|
+
const commandsOf = (v) => (Array.isArray(v) ? v : []).filter((c) => str(c.name)).map((c) => {
|
|
5668
|
+
const hint = str(c.input?.hint);
|
|
5669
|
+
return {
|
|
5670
|
+
name: str(c.name).replace(/^\//, ""),
|
|
5671
|
+
description: str(c.description),
|
|
5672
|
+
...hint ? { hint } : {}
|
|
5673
|
+
};
|
|
5674
|
+
});
|
|
5675
|
+
/** The agent's modes, from a session's answer or a current_mode_update. */
|
|
5676
|
+
const modesOf = (v) => {
|
|
5677
|
+
const m = v;
|
|
5678
|
+
const options = (m?.availableModes ?? []).filter((o) => str(o.id)).map((o) => ({
|
|
5679
|
+
value: str(o.id),
|
|
5680
|
+
name: str(o.name) || str(o.id),
|
|
5681
|
+
...str(o.description) ? { description: str(o.description) } : {}
|
|
5682
|
+
}));
|
|
5683
|
+
return options.length ? {
|
|
5684
|
+
kind: "mode",
|
|
5685
|
+
id: "mode",
|
|
5686
|
+
name: "Mode",
|
|
5687
|
+
current: str(m?.currentModeId),
|
|
5688
|
+
options
|
|
5689
|
+
} : null;
|
|
5690
|
+
};
|
|
5691
|
+
/** The agent's options (the model, say), whose choices may be grouped. */
|
|
5692
|
+
const configsOf = (v) => (Array.isArray(v) ? v : []).filter((o) => str(o.id) && Array.isArray(o.options)).flatMap((o) => {
|
|
5693
|
+
const options = o.options.flatMap((x) => Array.isArray(x.options) ? x.options : [x]).filter((x) => str(x.value)).map((x) => ({
|
|
5694
|
+
value: str(x.value),
|
|
5695
|
+
name: str(x.name) || str(x.value),
|
|
5696
|
+
...str(x.description) ? { description: str(x.description) } : {}
|
|
5697
|
+
}));
|
|
5698
|
+
return options.length ? [{
|
|
5699
|
+
kind: "config",
|
|
5700
|
+
id: str(o.id),
|
|
5701
|
+
name: str(o.name) || str(o.id),
|
|
5702
|
+
current: str(o.currentValue),
|
|
5703
|
+
options
|
|
5704
|
+
}] : [];
|
|
5705
|
+
});
|
|
5323
5706
|
/** The sessions the wrappers carry, as the pane draws them. One is shown: the one you chose, else the last to start. */
|
|
5324
5707
|
var AcpFollower = class {
|
|
5325
5708
|
#links = /* @__PURE__ */ new Map();
|
|
@@ -5402,6 +5785,8 @@ var AcpFollower = class {
|
|
|
5402
5785
|
size: live.size,
|
|
5403
5786
|
ours: this.#ours,
|
|
5404
5787
|
sessions: this.#sessions(),
|
|
5788
|
+
commands: live.commands,
|
|
5789
|
+
settings: [...live.modes ? [live.modes] : [], ...live.configs],
|
|
5405
5790
|
...problem
|
|
5406
5791
|
};
|
|
5407
5792
|
else if (read) this.#view = {
|
|
@@ -5635,6 +6020,42 @@ var AcpFollower = class {
|
|
|
5635
6020
|
link.opening = "";
|
|
5636
6021
|
link.send({ type: "open" });
|
|
5637
6022
|
}
|
|
6023
|
+
/** Set a mode or option of the session shown (the agent's own: its model, its plan mode). Optimistic: the agent's
|
|
6024
|
+
* answer or update settles it. */
|
|
6025
|
+
set(kind, id, value) {
|
|
6026
|
+
const live = this.#need();
|
|
6027
|
+
const setting = kind === "mode" ? live.modes : live.configs.find((c) => c.id === id);
|
|
6028
|
+
if (!setting || !setting.options.some((o) => o.value === value)) throw new Error(`${live.link.name || "The agent"} offers no such choice`);
|
|
6029
|
+
if (kind === "mode") live.modes = {
|
|
6030
|
+
...setting,
|
|
6031
|
+
current: value
|
|
6032
|
+
};
|
|
6033
|
+
else live.configs = live.configs.map((c) => c.id === id ? {
|
|
6034
|
+
...c,
|
|
6035
|
+
current: value
|
|
6036
|
+
} : c);
|
|
6037
|
+
live.link.send({
|
|
6038
|
+
type: "set",
|
|
6039
|
+
session: live.session,
|
|
6040
|
+
kind,
|
|
6041
|
+
id,
|
|
6042
|
+
value
|
|
6043
|
+
});
|
|
6044
|
+
this.#tell();
|
|
6045
|
+
}
|
|
6046
|
+
/** Remove what isorite kept of a session that is not live. What the agent keeps of its own is not touched. */
|
|
6047
|
+
async forget(agent, session) {
|
|
6048
|
+
const key = keyOf(agent, session);
|
|
6049
|
+
if (this.#lives.has(key)) throw new Error("that session is live: stop it before removing what is kept of it");
|
|
6050
|
+
if (!this.#api?.deleteAgentRecord) throw new Error("nothing is kept here");
|
|
6051
|
+
await this.#api.deleteAgentRecord(agent, session);
|
|
6052
|
+
if (this.#shown === key) {
|
|
6053
|
+
this.#shown = null;
|
|
6054
|
+
this.#chosen = false;
|
|
6055
|
+
this.#reading = null;
|
|
6056
|
+
}
|
|
6057
|
+
await this.#readKept();
|
|
6058
|
+
}
|
|
5638
6059
|
/** Ask the attached agents for their sessions again: the drawer is being looked at. */
|
|
5639
6060
|
refresh() {
|
|
5640
6061
|
for (const link of this.#links.values()) if (link.can.list) link.send({ type: "list" });
|
|
@@ -5713,7 +6134,11 @@ var AcpFollower = class {
|
|
|
5713
6134
|
break;
|
|
5714
6135
|
}
|
|
5715
6136
|
case "session/new":
|
|
5716
|
-
if (typeof m.result?.sessionId === "string")
|
|
6137
|
+
if (typeof m.result?.sessionId === "string") {
|
|
6138
|
+
const opened = this.#open(link, m.result.sessionId, key.startsWith("isorite-") ? "ours" : "in", typeof params.cwd === "string" ? params.cwd : "");
|
|
6139
|
+
opened.modes = modesOf(m.result.modes);
|
|
6140
|
+
opened.configs = configsOf(m.result.configOptions);
|
|
6141
|
+
}
|
|
5717
6142
|
break;
|
|
5718
6143
|
case "session/load":
|
|
5719
6144
|
case "session/resume":
|
|
@@ -5722,9 +6147,20 @@ var AcpFollower = class {
|
|
|
5722
6147
|
this.#lives.delete(keyOf(link.agent, live.session));
|
|
5723
6148
|
break;
|
|
5724
6149
|
}
|
|
6150
|
+
if (m.result?.modes) live.modes = modesOf(m.result.modes);
|
|
6151
|
+
if (m.result?.configOptions) live.configs = configsOf(m.result.configOptions);
|
|
5725
6152
|
this.#loaded(live);
|
|
5726
6153
|
}
|
|
5727
6154
|
break;
|
|
6155
|
+
case "session/set_mode":
|
|
6156
|
+
if (live && !m.error && live.modes) live.modes = {
|
|
6157
|
+
...live.modes,
|
|
6158
|
+
current: str(params.modeId)
|
|
6159
|
+
};
|
|
6160
|
+
break;
|
|
6161
|
+
case "session/set_config_option":
|
|
6162
|
+
if (live && !m.error && m.result?.configOptions) live.configs = configsOf(m.result.configOptions);
|
|
6163
|
+
break;
|
|
5728
6164
|
case "session/prompt":
|
|
5729
6165
|
if (live) {
|
|
5730
6166
|
const stop = m.error ? "failed" : m.result?.stopReason === "cancelled" ? "stopped" : m.result?.stopReason === "end_turn" ? "done" : "failed";
|
|
@@ -5788,7 +6224,10 @@ var AcpFollower = class {
|
|
|
5788
6224
|
loading: false,
|
|
5789
6225
|
replaying: false,
|
|
5790
6226
|
pending: null,
|
|
5791
|
-
given: /* @__PURE__ */ new Set()
|
|
6227
|
+
given: /* @__PURE__ */ new Set(),
|
|
6228
|
+
commands: [],
|
|
6229
|
+
modes: null,
|
|
6230
|
+
configs: []
|
|
5792
6231
|
};
|
|
5793
6232
|
live.link = link;
|
|
5794
6233
|
if (cwd) live.cwd = cwd;
|
|
@@ -5978,7 +6417,19 @@ var AcpFollower = class {
|
|
|
5978
6417
|
if (typeof cost === "number") live.cost = cost;
|
|
5979
6418
|
break;
|
|
5980
6419
|
}
|
|
5981
|
-
case "session_info_update":
|
|
6420
|
+
case "session_info_update":
|
|
6421
|
+
if (typeof u.title === "string") live.title = u.title;
|
|
6422
|
+
break;
|
|
6423
|
+
case "available_commands_update":
|
|
6424
|
+
live.commands = commandsOf(u.availableCommands);
|
|
6425
|
+
break;
|
|
6426
|
+
case "current_mode_update":
|
|
6427
|
+
if (live.modes && typeof u.currentModeId === "string") live.modes = {
|
|
6428
|
+
...live.modes,
|
|
6429
|
+
current: u.currentModeId
|
|
6430
|
+
};
|
|
6431
|
+
break;
|
|
6432
|
+
case "config_option_update": live.configs = configsOf(u.configOptions);
|
|
5982
6433
|
}
|
|
5983
6434
|
}
|
|
5984
6435
|
/** The note a path names, if it is a Markdown file of the base (relative paths read from the session's folder). */
|
|
@@ -6038,7 +6489,7 @@ var AcpFollower = class {
|
|
|
6038
6489
|
};
|
|
6039
6490
|
//#endregion
|
|
6040
6491
|
//#region packages/server/src/version.ts
|
|
6041
|
-
const VERSION = "0.
|
|
6492
|
+
const VERSION = "0.16.0";
|
|
6042
6493
|
//#endregion
|
|
6043
6494
|
//#region packages/server/src/connect.ts
|
|
6044
6495
|
const HARNESSES = [{
|
|
@@ -6310,6 +6761,27 @@ function detectAgents(env = process.env) {
|
|
|
6310
6761
|
}];
|
|
6311
6762
|
});
|
|
6312
6763
|
}
|
|
6764
|
+
function listAgents(env = process.env) {
|
|
6765
|
+
const found = detectAgents(env).map((d) => ({
|
|
6766
|
+
...d,
|
|
6767
|
+
found: true
|
|
6768
|
+
}));
|
|
6769
|
+
const have = new Set(found.map((d) => d.id));
|
|
6770
|
+
const rest = KNOWN.filter((k) => !have.has(k.id)).map((k) => ({
|
|
6771
|
+
id: k.id,
|
|
6772
|
+
name: k.name,
|
|
6773
|
+
command: k.command,
|
|
6774
|
+
path: "",
|
|
6775
|
+
keys: k.keys,
|
|
6776
|
+
keySet: false,
|
|
6777
|
+
ready: false,
|
|
6778
|
+
found: false,
|
|
6779
|
+
note: `${k.name} isn't on this machine's PATH.`
|
|
6780
|
+
}));
|
|
6781
|
+
return [...found, ...rest];
|
|
6782
|
+
}
|
|
6783
|
+
/** The ACP command of an agent known by name (`isorite acp omp`), or null. */
|
|
6784
|
+
const knownCommand = (id) => KNOWN.find((k) => k.id === id)?.command ?? null;
|
|
6313
6785
|
/** Why an agent that stopped may have: the key it reads isn't set where isorite was started, for the command run. */
|
|
6314
6786
|
function missingKey(command, env = process.env) {
|
|
6315
6787
|
const k = KNOWN.find((x) => x.command === command);
|
|
@@ -6563,6 +7035,108 @@ var AgentRunner = class {
|
|
|
6563
7035
|
}
|
|
6564
7036
|
};
|
|
6565
7037
|
//#endregion
|
|
7038
|
+
//#region packages/server/src/own-repo.ts
|
|
7039
|
+
const q = (s) => /^[\w@%+=:,./-]+$/.test(s) ? s : `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
7040
|
+
const slashes = (p) => p.split("\\").join("/");
|
|
7041
|
+
function git(cwd, args, env) {
|
|
7042
|
+
return execFileSync("git", args, {
|
|
7043
|
+
cwd,
|
|
7044
|
+
env: plainGitEnv(env),
|
|
7045
|
+
timeout: 1e4,
|
|
7046
|
+
killSignal: "SIGKILL",
|
|
7047
|
+
encoding: "utf8",
|
|
7048
|
+
stdio: [
|
|
7049
|
+
"ignore",
|
|
7050
|
+
"pipe",
|
|
7051
|
+
"pipe"
|
|
7052
|
+
]
|
|
7053
|
+
});
|
|
7054
|
+
}
|
|
7055
|
+
/** What this machine chose for a base, from bases.toml. */
|
|
7056
|
+
function historyChoice(base, config) {
|
|
7057
|
+
const v = table(readConfig(basesPath(config))[base]).history;
|
|
7058
|
+
return v === "project" || v === "separate" ? v : null;
|
|
7059
|
+
}
|
|
7060
|
+
function setHistoryChoice(base, choice, config) {
|
|
7061
|
+
const bases = readConfig(basesPath(config));
|
|
7062
|
+
if (!bases[base]) throw new Error(`${base} is not among the bases`);
|
|
7063
|
+
bases[base] = {
|
|
7064
|
+
...table(bases[base]),
|
|
7065
|
+
history: choice
|
|
7066
|
+
};
|
|
7067
|
+
writeConfig(bases, basesPath(config));
|
|
7068
|
+
}
|
|
7069
|
+
/** Where isorite's own folder is kept, and the commands for the person to run. Runs `git ls-files`, `remote` and
|
|
7070
|
+
* `rev-parse`, which only read. */
|
|
7071
|
+
function ownRepoInfo(own, choice = null, env = process.env) {
|
|
7072
|
+
const project = repoOf(dirname(own));
|
|
7073
|
+
const base = {
|
|
7074
|
+
state: "none",
|
|
7075
|
+
path: own,
|
|
7076
|
+
project,
|
|
7077
|
+
remotes: [],
|
|
7078
|
+
choice,
|
|
7079
|
+
ask: false,
|
|
7080
|
+
commands: [],
|
|
7081
|
+
problem: ""
|
|
7082
|
+
};
|
|
7083
|
+
try {
|
|
7084
|
+
if (existsSync(join(own, ".git"))) {
|
|
7085
|
+
const remotes = git(own, ["remote"], env).split("\n").filter(Boolean);
|
|
7086
|
+
return {
|
|
7087
|
+
...base,
|
|
7088
|
+
state: "own",
|
|
7089
|
+
remotes,
|
|
7090
|
+
commands: remotes.length ? [`git -C ${q(own)} push`] : [`git -C ${q(own)} remote add origin <the address of your private repository>`, `git -C ${q(own)} push -u origin HEAD`]
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
7093
|
+
if (!project) return base;
|
|
7094
|
+
const rel = slashes(relative(project, own));
|
|
7095
|
+
const tracked = git(project, [
|
|
7096
|
+
"ls-files",
|
|
7097
|
+
"--",
|
|
7098
|
+
rel
|
|
7099
|
+
], env).trim() !== "";
|
|
7100
|
+
const path = git(project, [
|
|
7101
|
+
"rev-parse",
|
|
7102
|
+
"--git-path",
|
|
7103
|
+
"info/exclude"
|
|
7104
|
+
], env).trim();
|
|
7105
|
+
const exclude = isAbsolute(path) ? path : resolve(project, path);
|
|
7106
|
+
const apart = [
|
|
7107
|
+
`git -C ${q(own)} init`,
|
|
7108
|
+
`echo ${q(rel + "/")} >> ${q(exclude)}`,
|
|
7109
|
+
`git -C ${q(own)} add -A`,
|
|
7110
|
+
`git -C ${q(own)} commit -m "isorite: start"`
|
|
7111
|
+
];
|
|
7112
|
+
return {
|
|
7113
|
+
...base,
|
|
7114
|
+
state: tracked ? "tracked" : "shared",
|
|
7115
|
+
ask: choice === null,
|
|
7116
|
+
commands: tracked ? [
|
|
7117
|
+
`git -C ${q(project)} rm -r --cached ${q(rel)}`,
|
|
7118
|
+
...apart,
|
|
7119
|
+
`git -C ${q(project)} commit -m "stop tracking isorite's own files"`
|
|
7120
|
+
] : apart
|
|
7121
|
+
};
|
|
7122
|
+
} catch (e) {
|
|
7123
|
+
return {
|
|
7124
|
+
...base,
|
|
7125
|
+
state: "unavailable",
|
|
7126
|
+
problem: e.message.split("\n")[0] ?? "git did not answer"
|
|
7127
|
+
};
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
/** For a base just made in a git project: ask whether its own files stay with the project or apart, keep the answer
|
|
7131
|
+
* for this machine, and say the commands for apart. Asks nothing outside a project. isorite runs none of it. */
|
|
7132
|
+
async function askWhereFilesLive(notes, ask, out, config, env = process.env) {
|
|
7133
|
+
if (!repoOf(notes)) return null;
|
|
7134
|
+
const choice = (await ask(`${notes} is in a git project. isorite's own files (.isorite/: journals, kept sessions, settings) can be committed with the project, or kept apart from it, so the people sharing it don't have them.\nKeep them [p]roject / [a]part? (p) `)).trim().toLowerCase().startsWith("a") ? "separate" : "project";
|
|
7135
|
+
setHistoryChoice(rememberBase(notes, { path: config }), choice, config);
|
|
7136
|
+
if (choice === "separate") out(`\nTo keep them apart, run these (isorite runs no git itself):\n${ownRepoInfo(join(notes, ".isorite"), "separate", env).commands.map((c) => ` ${c}`).join("\n")}\n`);
|
|
7137
|
+
return choice;
|
|
7138
|
+
}
|
|
7139
|
+
//#endregion
|
|
6566
7140
|
//#region packages/server/src/http.ts
|
|
6567
7141
|
const LOOPBACK = /* @__PURE__ */ new Set([
|
|
6568
7142
|
"localhost",
|
|
@@ -6639,7 +7213,7 @@ const AgentStatus = z.object({
|
|
|
6639
7213
|
session: z.string().min(1).max(200),
|
|
6640
7214
|
busy: z.boolean()
|
|
6641
7215
|
}).strict();
|
|
6642
|
-
function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bases, base, agents }) {
|
|
7216
|
+
function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bases, base, agents, config }) {
|
|
6643
7217
|
const app = new Hono();
|
|
6644
7218
|
const allowed = new Set([...allowHosts].map((h) => h.toLowerCase()));
|
|
6645
7219
|
const reply = (c, status, value) => c.body(JSON.stringify(value), status, {
|
|
@@ -6749,6 +7323,10 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6749
7323
|
if (c.req.header("if-none-match") === etag) return c.body(null, 304, headers);
|
|
6750
7324
|
return c.body(found.bytes, 200, headers);
|
|
6751
7325
|
});
|
|
7326
|
+
app.get("/api/code", async (c) => {
|
|
7327
|
+
const href = c.req.query("href") ?? "";
|
|
7328
|
+
return answer(c, () => api.code(href));
|
|
7329
|
+
});
|
|
6752
7330
|
app.put("/api/note", async (c) => {
|
|
6753
7331
|
const refused = writeRefused(c);
|
|
6754
7332
|
if (refused) return refused;
|
|
@@ -6773,6 +7351,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6773
7351
|
app.get("/api/agents/notes", (c) => answer(c, () => api.agentNotes()));
|
|
6774
7352
|
post("/api/agents/notes", z.object({ may: z.boolean() }).strict(), (_c, b) => api.setAgentNotes(b.may));
|
|
6775
7353
|
app.get("/api/conversations", (c) => answer(c, () => api.conversations()));
|
|
7354
|
+
post("/api/conversation/delete", z.object({ id: z.string().max(64) }).strict(), (_c, b) => api.deleteConversation(b.id));
|
|
6776
7355
|
app.get("/api/conversation", (c) => answer(c, async () => await api.conversation(c.req.query("id") || void 0) ?? {
|
|
6777
7356
|
id: "",
|
|
6778
7357
|
turns: []
|
|
@@ -6785,6 +7364,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6785
7364
|
to: z.string().max(400),
|
|
6786
7365
|
base: z.string().max(64)
|
|
6787
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));
|
|
6788
7368
|
post("/api/note/delete", z.object({ base: z.string().max(64) }).strict(), (c, b) => api.deleteNote(id(c), b.base));
|
|
6789
7369
|
post("/api/note/done", z.object({
|
|
6790
7370
|
done: z.boolean(),
|
|
@@ -6851,6 +7431,16 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6851
7431
|
}).strict(), (_c, b) => api.agentReceived(b.id, b.agent));
|
|
6852
7432
|
post("/api/agent/status", AgentStatus, (_c, b) => api.agentStatus(b.agent, b.session, b.busy));
|
|
6853
7433
|
post("/api/agent/take", z.object({ id: z.string().max(64) }).strict(), (_c, b) => api.agentTake(b.id));
|
|
7434
|
+
if (notes) {
|
|
7435
|
+
const configFile = config ?? configPath();
|
|
7436
|
+
app.get("/api/own/repo", (c) => answer(c, async () => local(c) ? ownRepoInfo(join(notes, ".isorite"), base ? historyChoice(base, configFile) : null) : null));
|
|
7437
|
+
post("/api/own/choose", z.object({ choice: z.enum(["project", "separate"]) }).strict(), async (c, b) => {
|
|
7438
|
+
if (!local(c)) throw new EngineError("this is chosen only from this machine", 403);
|
|
7439
|
+
if (!base) throw new EngineError("this base has no name here", 409);
|
|
7440
|
+
setHistoryChoice(base, b.choice, configFile);
|
|
7441
|
+
return ownRepoInfo(join(notes, ".isorite"), b.choice);
|
|
7442
|
+
});
|
|
7443
|
+
}
|
|
6854
7444
|
if (notes) {
|
|
6855
7445
|
app.get("/api/connect", (c) => answer(c, async () => proposal(notes)));
|
|
6856
7446
|
post("/api/connect", z.object({ files: z.array(z.string().max(200)).max(10).optional() }).strict(), async (c, b) => {
|
|
@@ -6862,7 +7452,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6862
7452
|
app.get("/api/acp/agent", (c) => answer(c, async () => agents.state()));
|
|
6863
7453
|
app.get("/api/acp/detected", (c) => answer(c, async () => {
|
|
6864
7454
|
if (!local(c)) throw new EngineError("agents are looked for only from this machine", 403);
|
|
6865
|
-
return
|
|
7455
|
+
return listAgents();
|
|
6866
7456
|
}));
|
|
6867
7457
|
app.get("/api/acp/registry", (c) => answer(c, async () => registryAgents().catch((e) => {
|
|
6868
7458
|
throw new EngineError(e.message, 502);
|
|
@@ -6967,6 +7557,23 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6967
7557
|
}).strict(), async (_c, b) => {
|
|
6968
7558
|
await acp.follow(b.agent, b.session).catch(refused);
|
|
6969
7559
|
});
|
|
7560
|
+
post("/api/acp/forget", z.object({
|
|
7561
|
+
agent: z.string().max(40),
|
|
7562
|
+
session: z.string().min(1).max(200)
|
|
7563
|
+
}).strict(), async (_c, b) => {
|
|
7564
|
+
await acp.forget(b.agent, b.session).catch(refused);
|
|
7565
|
+
});
|
|
7566
|
+
post("/api/acp/set", z.object({
|
|
7567
|
+
kind: z.enum(["mode", "config"]),
|
|
7568
|
+
id: z.string().max(100),
|
|
7569
|
+
value: z.string().max(200)
|
|
7570
|
+
}).strict(), async (_c, b) => {
|
|
7571
|
+
try {
|
|
7572
|
+
acp.set(b.kind, b.id, b.value);
|
|
7573
|
+
} catch (e) {
|
|
7574
|
+
throw new EngineError(e.message, 409);
|
|
7575
|
+
}
|
|
7576
|
+
});
|
|
6970
7577
|
post("/api/acp/new", z.object({ agent: z.string().max(40) }).strict(), async (_c, b) => {
|
|
6971
7578
|
await acp.fresh(b.agent).catch(refused);
|
|
6972
7579
|
});
|
|
@@ -7133,6 +7740,9 @@ var NodeFiles = class {
|
|
|
7133
7740
|
throw e;
|
|
7134
7741
|
}
|
|
7135
7742
|
}
|
|
7743
|
+
async mkdir(dir) {
|
|
7744
|
+
await mkdir(this.at(dir), { recursive: true });
|
|
7745
|
+
}
|
|
7136
7746
|
async append(path, text) {
|
|
7137
7747
|
const full = this.at(path);
|
|
7138
7748
|
await mkdir(dirname(full), { recursive: true });
|
|
@@ -7457,7 +8067,8 @@ async function openServer(path, { host = "127.0.0.1", port = 4767, allowHosts =
|
|
|
7457
8067
|
acp,
|
|
7458
8068
|
bases,
|
|
7459
8069
|
base: known.name,
|
|
7460
|
-
...loopback ? { agents } : {}
|
|
8070
|
+
...loopback ? { agents } : {},
|
|
8071
|
+
config
|
|
7461
8072
|
}).fetch
|
|
7462
8073
|
};
|
|
7463
8074
|
}
|
|
@@ -7488,12 +8099,14 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
|
|
|
7488
8099
|
console.error(e.code === "EADDRINUSE" ? `port ${port} is in use (isorite serve --port <n>)` : e.message);
|
|
7489
8100
|
process.exit(1);
|
|
7490
8101
|
});
|
|
7491
|
-
console.log(`
|
|
7492
|
-
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)")}`);
|
|
7493
8105
|
const lent = bases.lent().filter((l) => l.name !== bases.first).map((l) => l.name);
|
|
7494
|
-
if (lent.length)
|
|
7495
|
-
|
|
7496
|
-
|
|
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()}`);
|
|
7497
8110
|
stop = () => {
|
|
7498
8111
|
server.close();
|
|
7499
8112
|
process.exit(0);
|
|
@@ -7512,6 +8125,12 @@ const MARK = {
|
|
|
7512
8125
|
warn: "!",
|
|
7513
8126
|
skip: "–"
|
|
7514
8127
|
};
|
|
8128
|
+
const PAINT = {
|
|
8129
|
+
ok: green,
|
|
8130
|
+
fail: red,
|
|
8131
|
+
warn: yellow,
|
|
8132
|
+
skip: dim
|
|
8133
|
+
};
|
|
7515
8134
|
/** The environment variables isorite and Node read for it; the secret ones are shown only by length. */
|
|
7516
8135
|
const READ = [
|
|
7517
8136
|
"XDG_CONFIG_HOME",
|
|
@@ -7554,7 +8173,7 @@ async function doctor(folder, o = {}) {
|
|
|
7554
8173
|
outcome,
|
|
7555
8174
|
said
|
|
7556
8175
|
});
|
|
7557
|
-
if (!o.report) out(`${MARK[outcome]} ${step}: ${said}`);
|
|
8176
|
+
if (!o.report) out(`${PAINT[outcome](MARK[outcome])} ${bold(step)}: ${outcome === "skip" ? dim(said) : said}`);
|
|
7558
8177
|
};
|
|
7559
8178
|
const path = configPath(env), execArgv = o.execArgv ?? process.execArgv;
|
|
7560
8179
|
if (!existsSync(path)) say("config", "ok", `there is no ${path} yet: it is made when a base is first opened`);
|
|
@@ -7614,6 +8233,8 @@ async function doctor(folder, o = {}) {
|
|
|
7614
8233
|
const errors = issues.filter((i) => i.level === "error");
|
|
7615
8234
|
say("links", errors.length ? "fail" : issues.length || cycles.length ? "warn" : "ok", `${bundle.concepts.size} notes; ${errors.length} errors, ${issues.length - errors.length} warnings, ${cycles.length} requires cycles${issues.length ? `: ${issues.slice(0, 3).map((i) => `${i.path}: ${i.message}`).join("; ")}${issues.length > 3 ? `; and ${issues.length - 3} more (isorite check ${notes})` : ""}` : ""}`);
|
|
7616
8235
|
}
|
|
8236
|
+
const kept = ownRepoInfo(own, null, env);
|
|
8237
|
+
if (existsSync(own)) say("history", kept.state === "own" ? "ok" : kept.state === "unavailable" ? "skip" : kept.state === "none" ? "skip" : "ok", kept.state === "own" ? `${OWN_DIR}/ is a repository of its own${kept.remotes.length ? `, remote ${kept.remotes.join(", ")}` : `; to keep it elsewhere: git -C ${own} remote add origin <address>`}` : kept.state === "tracked" ? `${OWN_DIR}/ is tracked by the project's repository, so everyone sharing it has isorite's files; to keep them apart, run:\n ${kept.commands.join("\n ")}` : kept.state === "shared" ? `${OWN_DIR}/ is in the project's folder, to be committed with it; to keep it apart, run:\n ${kept.commands.join("\n ")}` : kept.state === "unavailable" ? `${OWN_DIR}/: git did not answer (${kept.problem})` : `${OWN_DIR}/ is in no git repository`);
|
|
7617
8238
|
const repository = existsSync(settings) ? parseSettings(readFileSync(settings, "utf8")).repository : void 0;
|
|
7618
8239
|
const found = repoOf(notes);
|
|
7619
8240
|
if (!repository && found) say("repository", "ok", `${found}: code links point here`);
|
|
@@ -7660,7 +8281,7 @@ async function doctor(folder, o = {}) {
|
|
|
7660
8281
|
...checks.map((c) => `${MARK[c.outcome]} ${c.step}: ${c.said}`),
|
|
7661
8282
|
"```"
|
|
7662
8283
|
].join("\n"));
|
|
7663
|
-
} 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"));
|
|
7664
8285
|
return failed ? 1 : 0;
|
|
7665
8286
|
}
|
|
7666
8287
|
//#endregion
|
|
@@ -8244,6 +8865,14 @@ async function acp(path, { agent, port = 4767, input, output, env = process.env,
|
|
|
8244
8865
|
toAgent(m);
|
|
8245
8866
|
}
|
|
8246
8867
|
if (c.type === "list" && can.list) ask("session/list", { cwd });
|
|
8868
|
+
if (c.type === "set") ask(c.kind === "mode" ? "session/set_mode" : "session/set_config_option", c.kind === "mode" ? {
|
|
8869
|
+
sessionId: c.session,
|
|
8870
|
+
modeId: c.value
|
|
8871
|
+
} : {
|
|
8872
|
+
sessionId: c.session,
|
|
8873
|
+
configId: c.id,
|
|
8874
|
+
value: c.value
|
|
8875
|
+
});
|
|
8247
8876
|
if (c.type === "open") {
|
|
8248
8877
|
const params = {
|
|
8249
8878
|
cwd,
|
|
@@ -9242,7 +9871,7 @@ function bases(args, { config = configPath(), out = console.log } = {}) {
|
|
|
9242
9871
|
return 0;
|
|
9243
9872
|
}
|
|
9244
9873
|
const width = Math.max(...known.map((b) => b.name.length));
|
|
9245
|
-
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)")}`}`);
|
|
9246
9875
|
return 0;
|
|
9247
9876
|
}
|
|
9248
9877
|
//#endregion
|
|
@@ -9303,18 +9932,23 @@ function label(b, all) {
|
|
|
9303
9932
|
}
|
|
9304
9933
|
/** The choice `isorite` makes, and what it says. */
|
|
9305
9934
|
async function start(io) {
|
|
9935
|
+
const make = async (folder) => {
|
|
9936
|
+
const notes = io.make(folder);
|
|
9937
|
+
await io.made?.(notes);
|
|
9938
|
+
return notes;
|
|
9939
|
+
};
|
|
9306
9940
|
const found = findBase(io.cwd);
|
|
9307
9941
|
if (found.kind === "base") return io.open(found.notes);
|
|
9308
9942
|
if (found.kind === "several") {
|
|
9309
9943
|
io.out(`isorite: more than one base here:`);
|
|
9310
|
-
found.notes.forEach((n, i) => io.out(` ${i + 1} ${n}`));
|
|
9944
|
+
found.notes.forEach((n, i) => io.out(` ${dim(String(i + 1))} ${n}`));
|
|
9311
9945
|
const pick = await io.choose("Open which?", found.notes.length);
|
|
9312
9946
|
return pick === null ? 2 : io.open(found.notes[pick]);
|
|
9313
9947
|
}
|
|
9314
9948
|
if (found.kind === "repo") {
|
|
9315
9949
|
const notes = join(found.repo, "notes");
|
|
9316
9950
|
io.out(`isorite: no base in ${found.repo} yet.`);
|
|
9317
|
-
if (!existsSync(notes) && await io.confirm(`Make one at ${notes}?`)) return io.open(
|
|
9951
|
+
if (!existsSync(notes) && await io.confirm(`Make one at ${notes}?`)) return io.open(await make(notes));
|
|
9318
9952
|
if (existsSync(notes)) io.out(` ${notes} is there already: open it as it is with isorite serve ${notes}`);
|
|
9319
9953
|
}
|
|
9320
9954
|
const known = recent(io.config);
|
|
@@ -9328,11 +9962,11 @@ async function start(io) {
|
|
|
9328
9962
|
"the guide"
|
|
9329
9963
|
];
|
|
9330
9964
|
io.out(known.length ? "isorite: the bases on this machine, last used first:" : "isorite:");
|
|
9331
|
-
rows.forEach((r, i) => io.out(` ${i + 1} ${r}`));
|
|
9965
|
+
rows.forEach((r, i) => io.out(` ${dim(String(i + 1))} ${r}`));
|
|
9332
9966
|
const pick = await io.choose("Open which?", rows.length);
|
|
9333
9967
|
if (pick === null) return 2;
|
|
9334
9968
|
if (pick < known.length) return io.open(known[pick].path);
|
|
9335
|
-
if (pick === known.length) return io.open(
|
|
9969
|
+
if (pick === known.length) return io.open(await make(join(io.cwd, "notes")));
|
|
9336
9970
|
return io.guide();
|
|
9337
9971
|
}
|
|
9338
9972
|
/** The model for search by meaning, offered on the first start without one (M26): asked once, fetched on a yes,
|
|
@@ -9355,62 +9989,66 @@ init_embedder();
|
|
|
9355
9989
|
init_model();
|
|
9356
9990
|
const SHORT = `isorite: an agentic harness for understanding
|
|
9357
9991
|
|
|
9358
|
-
isorite open the base here, or choose one
|
|
9359
|
-
isorite doctor
|
|
9360
|
-
|
|
9361
|
-
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
|
|
9362
9995
|
isorite --version
|
|
9363
9996
|
|
|
9364
|
-
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.
|
|
9365
9998
|
`;
|
|
9366
9999
|
const USAGE = `isorite: an agentic harness for understanding
|
|
9367
10000
|
|
|
9368
|
-
isorite new <folder>
|
|
9369
|
-
isorite check <notes> [--unused]
|
|
9370
|
-
|
|
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
|
|
9371
10005
|
isorite serve <notes> [--port <n>] [--allow-host <name>]
|
|
9372
10006
|
open it in the browser, on this machine only
|
|
9373
|
-
isorite migrate <study base> [--merge]
|
|
9374
|
-
|
|
9375
|
-
isorite example <folder> [--geometry] write the example base (notes on an N-body simulation, three weeks
|
|
9376
|
-
into being built), into an empty folder; --geometry: notes on the
|
|
9377
|
-
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
|
|
9378
10009
|
isorite mcp [<notes>] [--port <n>] [--agent <name>]
|
|
9379
|
-
the
|
|
9380
|
-
|
|
10010
|
+
the base's tools over MCP, on stdio:
|
|
10011
|
+
claude mcp add isorite -- isorite mcp <base>
|
|
9381
10012
|
isorite acp --agent "<command>" [<notes>] [--port <n>] [--pane]
|
|
9382
|
-
an ACP agent
|
|
9383
|
-
|
|
9384
|
-
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
|
|
9385
10015
|
isorite model [--from <folder|.tgz>] [--url <url>]
|
|
9386
|
-
the model for search by meaning: which
|
|
9387
|
-
|
|
9388
|
-
Hugging Face, or --url alone; or from the .tgz fetched by hand, or a
|
|
9389
|
-
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)
|
|
9390
10018
|
isorite doctor [<notes>] [--report] [--offline]
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
isorite
|
|
9396
|
-
|
|
9397
|
-
isorite bases [lend|keep <name>] the bases this device knows, each listed as it is opened; lend one to
|
|
9398
|
-
every session, or keep it to itself
|
|
9399
|
-
isorite help setup how isorite is set up, every setting and failure: written for an AI
|
|
9400
|
-
helping you set it up (docs/setup.md is for people)
|
|
9401
|
-
isorite guide [<folder>] [--port <n>] the guide: a knowledge base about isorite, with things to do in it,
|
|
9402
|
-
written into a folder (~/isorite-guide unless named) and opened in
|
|
9403
|
-
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)
|
|
9404
10025
|
`;
|
|
9405
|
-
function check(notes, out = console.log, unused = false) {
|
|
10026
|
+
function check(notes, out = console.log, unused = false, fix = false) {
|
|
9406
10027
|
const root = resolve(notes);
|
|
9407
10028
|
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
9408
10029
|
out(`no folder at ${root}`);
|
|
9409
10030
|
return 2;
|
|
9410
10031
|
}
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
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(" -> ")}`);
|
|
9414
10052
|
let artifactErrors = 0, artifactWarnings = 0;
|
|
9415
10053
|
for (const f of [...bundle.files].filter((x) => citeKind(x) === "artifact").sort()) {
|
|
9416
10054
|
const html = readFileSync(join(root, f), "utf8");
|
|
@@ -9427,13 +10065,14 @@ function check(notes, out = console.log, unused = false) {
|
|
|
9427
10065
|
files,
|
|
9428
10066
|
columns
|
|
9429
10067
|
})) {
|
|
9430
|
-
out(`${b.level
|
|
10068
|
+
out(`${level(b.level)} ${bold(f)}: ${b.message}`);
|
|
9431
10069
|
if (b.level === "error") artifactErrors++;
|
|
9432
10070
|
else artifactWarnings++;
|
|
9433
10071
|
}
|
|
9434
10072
|
}
|
|
9435
10073
|
const errors = issues.filter((i) => i.level === "error").length + artifactErrors;
|
|
9436
|
-
|
|
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`);
|
|
9437
10076
|
if (unused) {
|
|
9438
10077
|
const cited = new Set([...bundle.concepts.values()].flatMap((c) => c.cites.map((x) => x.target)));
|
|
9439
10078
|
const idle = [...bundle.files].filter((f) => citeKind(f) === "image" && !cited.has(f)).sort();
|
|
@@ -9495,6 +10134,9 @@ async function terminalStart({ port, noOpen }) {
|
|
|
9495
10134
|
console.log(` made ${made}`);
|
|
9496
10135
|
return made;
|
|
9497
10136
|
},
|
|
10137
|
+
made: async (notes) => {
|
|
10138
|
+
await askWhereFilesLive(notes, ask, console.log, configPath());
|
|
10139
|
+
},
|
|
9498
10140
|
guide: async () => {
|
|
9499
10141
|
await model();
|
|
9500
10142
|
await guide(void 0, { port: port ?? await freePort() });
|
|
@@ -9516,10 +10158,25 @@ function main(argv) {
|
|
|
9516
10158
|
noOpen: args.includes("--no-open")
|
|
9517
10159
|
});
|
|
9518
10160
|
}
|
|
9519
|
-
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"));
|
|
9520
10162
|
if (command === "new" && rest[0]) {
|
|
9521
10163
|
const made = newBase(rest[0]);
|
|
9522
|
-
|
|
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}`;
|
|
10165
|
+
if (process.stdin.isTTY && process.stdout.isTTY && repoOf(made)) return (async () => {
|
|
10166
|
+
const { createInterface } = await import("node:readline/promises");
|
|
10167
|
+
const rl = createInterface({
|
|
10168
|
+
input: process.stdin,
|
|
10169
|
+
output: process.stdout
|
|
10170
|
+
});
|
|
10171
|
+
try {
|
|
10172
|
+
await askWhereFilesLive(made, (q) => rl.question(q), console.log, configPath());
|
|
10173
|
+
} finally {
|
|
10174
|
+
rl.close();
|
|
10175
|
+
}
|
|
10176
|
+
console.log(next);
|
|
10177
|
+
return 0;
|
|
10178
|
+
})();
|
|
10179
|
+
console.log(next);
|
|
9523
10180
|
return 0;
|
|
9524
10181
|
}
|
|
9525
10182
|
if (command === "serve" && rest[0]) {
|
|
@@ -9586,9 +10243,15 @@ function main(argv) {
|
|
|
9586
10243
|
const at = rest.indexOf(name);
|
|
9587
10244
|
return at < 0 ? void 0 : rest[at + 1];
|
|
9588
10245
|
};
|
|
9589
|
-
const
|
|
10246
|
+
const positional = rest.filter((a, i) => !a.startsWith("--") && ![
|
|
10247
|
+
"--port",
|
|
10248
|
+
"--agent",
|
|
10249
|
+
"--url"
|
|
10250
|
+
].includes(rest[i - 1] ?? ""));
|
|
10251
|
+
const named = !flag("--agent") && positional[0] && !existsSync(positional[0]) ? knownCommand(positional[0]) : null;
|
|
10252
|
+
const agent = flag("--agent") ?? named ?? void 0, port = flag("--port") === void 0 ? void 0 : Number(flag("--port"));
|
|
9590
10253
|
if (!agent) {
|
|
9591
|
-
console.error("isorite acp --agent \"<the agent's ACP command>\" [<notes>] [--port <n>] [--pane]");
|
|
10254
|
+
console.error("isorite acp <omp|opencode|claude|codex|hermes> [<notes>] [--port <n>]\nisorite acp --agent \"<the agent's ACP command>\" [<notes>] [--port <n>] [--pane]");
|
|
9592
10255
|
return 2;
|
|
9593
10256
|
}
|
|
9594
10257
|
if (port !== void 0 && !(Number.isInteger(port) && port > 0 && port < 65536)) {
|
|
@@ -9600,11 +10263,7 @@ function main(argv) {
|
|
|
9600
10263
|
console.error("--url takes a base's address on this machine");
|
|
9601
10264
|
return 2;
|
|
9602
10265
|
}
|
|
9603
|
-
return acp(
|
|
9604
|
-
"--port",
|
|
9605
|
-
"--agent",
|
|
9606
|
-
"--url"
|
|
9607
|
-
].includes(rest[i - 1] ?? "")), {
|
|
10266
|
+
return acp(named ? positional[1] : positional[0], {
|
|
9608
10267
|
agent,
|
|
9609
10268
|
pane,
|
|
9610
10269
|
...port === void 0 ? {} : { port },
|
|
@@ -9697,7 +10356,7 @@ function main(argv) {
|
|
|
9697
10356
|
}
|
|
9698
10357
|
throw e;
|
|
9699
10358
|
}
|
|
9700
|
-
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));
|
|
9701
10360
|
return command && command !== "help" && command !== "--help" ? 2 : 0;
|
|
9702
10361
|
}
|
|
9703
10362
|
/** The commands that start something, which say so at once (M21): a subprocess that stalls after this is then
|