isorite 0.13.0 → 0.15.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 +643 -48
- package/docs/setup.md +5 -4
- package/docs/terminal.md +4 -2
- package/guide/your-own-base/starting-a-base.md +4 -4
- package/package.json +1 -1
- package/web/_app/immutable/assets/{0.BEjn1Bfl.css → 0.CtWz9d2A.css} +1 -1
- 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/B7Bf3hc7.js +1 -0
- package/web/_app/immutable/chunks/{DRcTlHZP.js → B7LnLClC.js} +1 -1
- package/web/_app/immutable/chunks/B9TKPEJD.js +1 -0
- package/web/_app/immutable/chunks/{ZwM5k8r0.js → BAVnZFPa.js} +1 -1
- package/web/_app/immutable/chunks/{O98JkczU.js → BDJlNL-I.js} +3 -3
- 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/{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/{C8nK3Jvr.js → B_x6_Y3m.js} +1 -1
- package/web/_app/immutable/chunks/{Bb_N98K9.js → BiHyHFlW.js} +1 -1
- package/web/_app/immutable/chunks/{B6uHgLdT.js → BsY4iMci.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/{CdY1G_wk.js → C9Qdslk3.js} +1 -1
- 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/{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/{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/{s_hW4oA1.js → D1MhxXyn.js} +1 -1
- package/web/_app/immutable/chunks/{Bt-fHApV.js → D4EpFhYZ.js} +1 -1
- package/web/_app/immutable/chunks/{UDPiRmKc.js → D7jLdCNu.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/{D9JwUycj.js → Djo-ijeo.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/{Cd_SqIWq.js → Dx_7xlS4.js} +36 -36
- 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/{WLe0MT1c.js → LbnMg2Kc.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/{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/{3fCuxe18.js → wD7RSG2d.js} +1 -1
- package/web/_app/immutable/chunks/{CMikI1NP.js → zasx25UV.js} +3 -3
- package/web/_app/immutable/entry/app.Av5W3V8v.js +2 -0
- package/web/_app/immutable/entry/start.CbpxCvWi.js +1 -0
- package/web/_app/immutable/nodes/0.BTVYyqUm.js +3 -0
- package/web/_app/immutable/nodes/1.DR-b7PMP.js +1 -0
- package/web/_app/immutable/nodes/2.BePyTqoa.js +1 -0
- package/web/_app/immutable/nodes/3.D2rfIXQP.js +1 -0
- package/web/_app/immutable/nodes/4.7tuUcODS.js +15 -0
- package/web/_app/immutable/nodes/5.Cs9hcGU9.js +1 -0
- package/web/_app/immutable/nodes/6.DzvF0ZPt.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +15 -15
- package/web/_app/immutable/chunks/BCyLp7pS.js +0 -1
- package/web/_app/immutable/chunks/BOxykeIc.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/ej_0XxR7.js +0 -1
- package/web/_app/immutable/entry/app.DM_AWBgH.js +0 -2
- package/web/_app/immutable/entry/start.BLhIKDcR.js +0 -1
- package/web/_app/immutable/nodes/0.xSbkmYPn.js +0 -2
- package/web/_app/immutable/nodes/1.BD2wJdoH.js +0 -1
- package/web/_app/immutable/nodes/2.shPqwtS_.js +0 -1
- package/web/_app/immutable/nodes/3.e_5kWaf_.js +0 -1
- package/web/_app/immutable/nodes/4.BEMEU6Jz.js +0 -15
- package/web/_app/immutable/nodes/5.DxaO-e4O.js +0 -1
- package/web/_app/immutable/nodes/6.C9ia8cDm.js +0 -1
package/dist/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { chmodSync, closeSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { Document, Scalar, isMap, isScalar, isSeq, parseDocument, stringify } from "yaml";
|
|
6
6
|
import MarkdownIt from "markdown-it";
|
|
@@ -18,6 +18,7 @@ import { z } from "zod";
|
|
|
18
18
|
import { appendFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
19
19
|
import { createInterface } from "node:readline";
|
|
20
20
|
import { PassThrough } from "node:stream";
|
|
21
|
+
import { createServer } from "node:net";
|
|
21
22
|
//#region \0rolldown/runtime.js
|
|
22
23
|
var __defProp = Object.defineProperty;
|
|
23
24
|
var __esmMin = (fn, res, err) => () => {
|
|
@@ -2276,7 +2277,8 @@ function knownBases(config = configPath()) {
|
|
|
2276
2277
|
return BASE_NAME.test(name) && typeof row.path === "string" && row.path ? [{
|
|
2277
2278
|
name,
|
|
2278
2279
|
path: expand(row.path),
|
|
2279
|
-
lend: row.lend === true
|
|
2280
|
+
lend: row.lend === true,
|
|
2281
|
+
...typeof row.used === "string" ? { used: row.used } : {}
|
|
2280
2282
|
}] : [];
|
|
2281
2283
|
});
|
|
2282
2284
|
}
|
|
@@ -2286,6 +2288,16 @@ function baseName(folder) {
|
|
|
2286
2288
|
}
|
|
2287
2289
|
/** The base at `folder` by its name in the configuration, listed first if it is not: under its folder's name, or
|
|
2288
2290
|
* that name with -2, -3… where another folder has it. `as` names it, and `lend` lends it. */
|
|
2291
|
+
/** A base opened now: when it was last used is kept (`used`), for `isorite` to list the bases last used first. */
|
|
2292
|
+
function touchBase(folder, { path = configPath(), now = /* @__PURE__ */ new Date() } = {}) {
|
|
2293
|
+
const at = resolve(folder), bases = readConfig(basesPath(path)), name = knownBases(path).find((b) => b.path === at)?.name;
|
|
2294
|
+
if (!name) return;
|
|
2295
|
+
bases[name] = {
|
|
2296
|
+
...table(bases[name]),
|
|
2297
|
+
used: now.toISOString()
|
|
2298
|
+
};
|
|
2299
|
+
writeConfig(bases, basesPath(path));
|
|
2300
|
+
}
|
|
2289
2301
|
function rememberBase(folder, { path = configPath(), as, lend } = {}) {
|
|
2290
2302
|
const at = resolve(folder), had = knownBases(path).find((b) => b.path === at);
|
|
2291
2303
|
if (had && lend === void 0) return had.name;
|
|
@@ -2853,7 +2865,7 @@ var Conflict = class extends EngineError {
|
|
|
2853
2865
|
};
|
|
2854
2866
|
//#endregion
|
|
2855
2867
|
//#region packages/engine/src/lookup.ts
|
|
2856
|
-
const str$
|
|
2868
|
+
const str$2 = { type: "string" };
|
|
2857
2869
|
const fn$1 = (name, description, properties, required) => ({
|
|
2858
2870
|
type: "function",
|
|
2859
2871
|
function: {
|
|
@@ -2869,9 +2881,9 @@ const fn$1 = (name, description, properties, required) => ({
|
|
|
2869
2881
|
});
|
|
2870
2882
|
const TOOLS = [
|
|
2871
2883
|
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.", {
|
|
2872
|
-
query: str$
|
|
2884
|
+
query: str$2,
|
|
2873
2885
|
under: {
|
|
2874
|
-
...str$
|
|
2886
|
+
...str$2,
|
|
2875
2887
|
description: "Only notes in this folder."
|
|
2876
2888
|
},
|
|
2877
2889
|
limit: {
|
|
@@ -2880,24 +2892,24 @@ const TOOLS = [
|
|
|
2880
2892
|
}
|
|
2881
2893
|
}, ["query"]),
|
|
2882
2894
|
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: {
|
|
2883
|
-
...str$
|
|
2895
|
+
...str$2,
|
|
2884
2896
|
description: "The note's id or path: basics/entropy or /basics/entropy.md."
|
|
2885
2897
|
} }, ["id"]),
|
|
2886
2898
|
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.", {
|
|
2887
|
-
id: str$
|
|
2899
|
+
id: str$2,
|
|
2888
2900
|
section: {
|
|
2889
|
-
...str$
|
|
2901
|
+
...str$2,
|
|
2890
2902
|
description: "A heading's text, as outline_note gives it."
|
|
2891
2903
|
}
|
|
2892
2904
|
}, ["id"])
|
|
2893
2905
|
];
|
|
2894
2906
|
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).", {
|
|
2895
2907
|
text: {
|
|
2896
|
-
...str$
|
|
2908
|
+
...str$2,
|
|
2897
2909
|
description: "A question, or a sentence saying what you are looking for."
|
|
2898
2910
|
},
|
|
2899
2911
|
under: {
|
|
2900
|
-
...str$
|
|
2912
|
+
...str$2,
|
|
2901
2913
|
description: "Only notes in this folder."
|
|
2902
2914
|
},
|
|
2903
2915
|
limit: {
|
|
@@ -3267,7 +3279,7 @@ or try; not for what an answer or a note says as well.
|
|
|
3267
3279
|
problem, fix that and write the whole file again.
|
|
3268
3280
|
4. Say in a sentence what it shows, linked: [what it shows](/folder/name.html).
|
|
3269
3281
|
Link it from the note it belongs to if they want it there.`;
|
|
3270
|
-
const str = { type: "string" };
|
|
3282
|
+
const str$1 = { type: "string" };
|
|
3271
3283
|
const fn = (name, description, properties, required) => ({
|
|
3272
3284
|
type: "function",
|
|
3273
3285
|
function: {
|
|
@@ -3283,16 +3295,16 @@ const fn = (name, description, properties, required) => ({
|
|
|
3283
3295
|
});
|
|
3284
3296
|
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.", {
|
|
3285
3297
|
title: {
|
|
3286
|
-
...str,
|
|
3298
|
+
...str$1,
|
|
3287
3299
|
description: "The question, short, ending in a question mark."
|
|
3288
3300
|
},
|
|
3289
3301
|
text: {
|
|
3290
|
-
...str,
|
|
3302
|
+
...str$1,
|
|
3291
3303
|
description: "A line or two on what is asked, or empty."
|
|
3292
3304
|
},
|
|
3293
3305
|
about: {
|
|
3294
3306
|
type: "array",
|
|
3295
|
-
items: str,
|
|
3307
|
+
items: str$1,
|
|
3296
3308
|
description: "The ids of the notes it is about."
|
|
3297
3309
|
}
|
|
3298
3310
|
}, [
|
|
@@ -3301,7 +3313,7 @@ const ASK_QUESTION = fn("ask_question", "Ask them a question back, kept as an op
|
|
|
3301
3313
|
"about"
|
|
3302
3314
|
]);
|
|
3303
3315
|
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: {
|
|
3304
|
-
...str,
|
|
3316
|
+
...str$1,
|
|
3305
3317
|
description: "The file's path in the base: folder/name.csv."
|
|
3306
3318
|
} }, ["path"]);
|
|
3307
3319
|
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: {
|
|
@@ -3316,21 +3328,21 @@ const ARTIFACT_RECIPE = fn("artifact_recipe", "A recipe for an artifact: a compl
|
|
|
3316
3328
|
} }, ["name"]);
|
|
3317
3329
|
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.", {
|
|
3318
3330
|
path: {
|
|
3319
|
-
...str,
|
|
3331
|
+
...str$1,
|
|
3320
3332
|
description: "Where: folder/name.html, beside the notes it belongs to."
|
|
3321
3333
|
},
|
|
3322
|
-
title: str,
|
|
3334
|
+
title: str$1,
|
|
3323
3335
|
description: {
|
|
3324
|
-
...str,
|
|
3336
|
+
...str$1,
|
|
3325
3337
|
description: "One sentence: what it shows, and what can be done with it."
|
|
3326
3338
|
},
|
|
3327
3339
|
uses: {
|
|
3328
3340
|
type: "array",
|
|
3329
|
-
items: str,
|
|
3341
|
+
items: str$1,
|
|
3330
3342
|
description: "The data or model files it reads, relative to it (runs.csv). Those its markup and script name are added."
|
|
3331
3343
|
},
|
|
3332
3344
|
html: {
|
|
3333
|
-
...str,
|
|
3345
|
+
...str$1,
|
|
3334
3346
|
description: "The whole file."
|
|
3335
3347
|
}
|
|
3336
3348
|
}, [
|
|
@@ -3341,16 +3353,16 @@ const WRITE_ARTIFACT = fn("write_artifact", "Write an artifact: a whole HTML fil
|
|
|
3341
3353
|
"html"
|
|
3342
3354
|
]);
|
|
3343
3355
|
const READ_ARTIFACT = fn("read_artifact", "An artifact's whole file, to change it and write it again.", { path: {
|
|
3344
|
-
...str,
|
|
3356
|
+
...str$1,
|
|
3345
3357
|
description: "folder/name.html"
|
|
3346
3358
|
} }, ["path"]);
|
|
3347
3359
|
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.", {
|
|
3348
3360
|
id: {
|
|
3349
|
-
...str,
|
|
3361
|
+
...str$1,
|
|
3350
3362
|
description: "The note's id, or AGENTS.md for a line of their instructions for the base."
|
|
3351
3363
|
},
|
|
3352
3364
|
text: {
|
|
3353
|
-
...str,
|
|
3365
|
+
...str$1,
|
|
3354
3366
|
description: "The passage, in Markdown."
|
|
3355
3367
|
}
|
|
3356
3368
|
}, ["id", "text"]);
|
|
@@ -4220,6 +4232,10 @@ var Engine = class Engine {
|
|
|
4220
4232
|
return null;
|
|
4221
4233
|
}
|
|
4222
4234
|
}
|
|
4235
|
+
async deleteAgentRecord(agent, session) {
|
|
4236
|
+
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");
|
|
4237
|
+
await this.opts.own.remove(agentRecordPath(agent, session));
|
|
4238
|
+
}
|
|
4223
4239
|
async agentRecords() {
|
|
4224
4240
|
const own = this.opts.own, all = [];
|
|
4225
4241
|
for (const a of await own.list(AGENTS_DIR).catch(() => [])) {
|
|
@@ -4636,6 +4652,10 @@ var Engine = class Engine {
|
|
|
4636
4652
|
turns: readTurns(text)
|
|
4637
4653
|
};
|
|
4638
4654
|
}
|
|
4655
|
+
async deleteConversation(id) {
|
|
4656
|
+
if (!TURN_ID.test(id)) throw new EngineError("not a conversation's id");
|
|
4657
|
+
await this.opts.own.remove(conversationPath(id));
|
|
4658
|
+
}
|
|
4639
4659
|
async conversations() {
|
|
4640
4660
|
const study = this.opts.own;
|
|
4641
4661
|
const names = await study.list(CONVERSATIONS_DIR).catch(() => []);
|
|
@@ -5102,7 +5122,7 @@ var Bases = class {
|
|
|
5102
5122
|
}
|
|
5103
5123
|
};
|
|
5104
5124
|
/** The revisions answered, newest first: a client asking for one of these gets it. */
|
|
5105
|
-
const KNOWN = [
|
|
5125
|
+
const KNOWN$1 = [
|
|
5106
5126
|
"2026-07-28",
|
|
5107
5127
|
"2025-11-25",
|
|
5108
5128
|
"2025-06-18",
|
|
@@ -5160,13 +5180,13 @@ var McpEndpoint = class {
|
|
|
5160
5180
|
const p = rpc.params ?? {};
|
|
5161
5181
|
switch (rpc.method) {
|
|
5162
5182
|
case "initialize": return {
|
|
5163
|
-
protocolVersion: typeof p.protocolVersion === "string" && KNOWN.includes(p.protocolVersion) ? p.protocolVersion : KNOWN[1],
|
|
5183
|
+
protocolVersion: typeof p.protocolVersion === "string" && KNOWN$1.includes(p.protocolVersion) ? p.protocolVersion : KNOWN$1[1],
|
|
5164
5184
|
capabilities: this.#capabilities(),
|
|
5165
5185
|
serverInfo: this.#info(),
|
|
5166
5186
|
instructions: INSTRUCTIONS
|
|
5167
5187
|
};
|
|
5168
5188
|
case "server/discover": return {
|
|
5169
|
-
supportedVersions: KNOWN,
|
|
5189
|
+
supportedVersions: KNOWN$1,
|
|
5170
5190
|
capabilities: this.#capabilities(),
|
|
5171
5191
|
serverInfo: this.#info(),
|
|
5172
5192
|
instructions: INSTRUCTIONS
|
|
@@ -5308,6 +5328,47 @@ const EMPTY = {
|
|
|
5308
5328
|
const textOf = (blocks) => (Array.isArray(blocks) ? blocks : [blocks]).map((b) => b?.type === "text" ? b.text ?? "" : b?.type === "resource_link" ? `@${b.name ?? b.uri ?? ""}` : "").join("");
|
|
5309
5329
|
const lines = (v) => v ? v.replace(/\n$/, "").split("\n") : [];
|
|
5310
5330
|
const keyOf = (agent, session) => `${agent}\u0000${session}`;
|
|
5331
|
+
const str = (v) => typeof v === "string" ? v : "";
|
|
5332
|
+
/** The agent's slash commands, from available_commands_update. */
|
|
5333
|
+
const commandsOf = (v) => (Array.isArray(v) ? v : []).filter((c) => str(c.name)).map((c) => {
|
|
5334
|
+
const hint = str(c.input?.hint);
|
|
5335
|
+
return {
|
|
5336
|
+
name: str(c.name).replace(/^\//, ""),
|
|
5337
|
+
description: str(c.description),
|
|
5338
|
+
...hint ? { hint } : {}
|
|
5339
|
+
};
|
|
5340
|
+
});
|
|
5341
|
+
/** The agent's modes, from a session's answer or a current_mode_update. */
|
|
5342
|
+
const modesOf = (v) => {
|
|
5343
|
+
const m = v;
|
|
5344
|
+
const options = (m?.availableModes ?? []).filter((o) => str(o.id)).map((o) => ({
|
|
5345
|
+
value: str(o.id),
|
|
5346
|
+
name: str(o.name) || str(o.id),
|
|
5347
|
+
...str(o.description) ? { description: str(o.description) } : {}
|
|
5348
|
+
}));
|
|
5349
|
+
return options.length ? {
|
|
5350
|
+
kind: "mode",
|
|
5351
|
+
id: "mode",
|
|
5352
|
+
name: "Mode",
|
|
5353
|
+
current: str(m?.currentModeId),
|
|
5354
|
+
options
|
|
5355
|
+
} : null;
|
|
5356
|
+
};
|
|
5357
|
+
/** The agent's options (the model, say), whose choices may be grouped. */
|
|
5358
|
+
const configsOf = (v) => (Array.isArray(v) ? v : []).filter((o) => str(o.id) && Array.isArray(o.options)).flatMap((o) => {
|
|
5359
|
+
const options = o.options.flatMap((x) => Array.isArray(x.options) ? x.options : [x]).filter((x) => str(x.value)).map((x) => ({
|
|
5360
|
+
value: str(x.value),
|
|
5361
|
+
name: str(x.name) || str(x.value),
|
|
5362
|
+
...str(x.description) ? { description: str(x.description) } : {}
|
|
5363
|
+
}));
|
|
5364
|
+
return options.length ? [{
|
|
5365
|
+
kind: "config",
|
|
5366
|
+
id: str(o.id),
|
|
5367
|
+
name: str(o.name) || str(o.id),
|
|
5368
|
+
current: str(o.currentValue),
|
|
5369
|
+
options
|
|
5370
|
+
}] : [];
|
|
5371
|
+
});
|
|
5311
5372
|
/** The sessions the wrappers carry, as the pane draws them. One is shown: the one you chose, else the last to start. */
|
|
5312
5373
|
var AcpFollower = class {
|
|
5313
5374
|
#links = /* @__PURE__ */ new Map();
|
|
@@ -5390,6 +5451,8 @@ var AcpFollower = class {
|
|
|
5390
5451
|
size: live.size,
|
|
5391
5452
|
ours: this.#ours,
|
|
5392
5453
|
sessions: this.#sessions(),
|
|
5454
|
+
commands: live.commands,
|
|
5455
|
+
settings: [...live.modes ? [live.modes] : [], ...live.configs],
|
|
5393
5456
|
...problem
|
|
5394
5457
|
};
|
|
5395
5458
|
else if (read) this.#view = {
|
|
@@ -5623,6 +5686,42 @@ var AcpFollower = class {
|
|
|
5623
5686
|
link.opening = "";
|
|
5624
5687
|
link.send({ type: "open" });
|
|
5625
5688
|
}
|
|
5689
|
+
/** Set a mode or option of the session shown (the agent's own: its model, its plan mode). Optimistic: the agent's
|
|
5690
|
+
* answer or update settles it. */
|
|
5691
|
+
set(kind, id, value) {
|
|
5692
|
+
const live = this.#need();
|
|
5693
|
+
const setting = kind === "mode" ? live.modes : live.configs.find((c) => c.id === id);
|
|
5694
|
+
if (!setting || !setting.options.some((o) => o.value === value)) throw new Error(`${live.link.name || "The agent"} offers no such choice`);
|
|
5695
|
+
if (kind === "mode") live.modes = {
|
|
5696
|
+
...setting,
|
|
5697
|
+
current: value
|
|
5698
|
+
};
|
|
5699
|
+
else live.configs = live.configs.map((c) => c.id === id ? {
|
|
5700
|
+
...c,
|
|
5701
|
+
current: value
|
|
5702
|
+
} : c);
|
|
5703
|
+
live.link.send({
|
|
5704
|
+
type: "set",
|
|
5705
|
+
session: live.session,
|
|
5706
|
+
kind,
|
|
5707
|
+
id,
|
|
5708
|
+
value
|
|
5709
|
+
});
|
|
5710
|
+
this.#tell();
|
|
5711
|
+
}
|
|
5712
|
+
/** Remove what isorite kept of a session that is not live. What the agent keeps of its own is not touched. */
|
|
5713
|
+
async forget(agent, session) {
|
|
5714
|
+
const key = keyOf(agent, session);
|
|
5715
|
+
if (this.#lives.has(key)) throw new Error("that session is live: stop it before removing what is kept of it");
|
|
5716
|
+
if (!this.#api?.deleteAgentRecord) throw new Error("nothing is kept here");
|
|
5717
|
+
await this.#api.deleteAgentRecord(agent, session);
|
|
5718
|
+
if (this.#shown === key) {
|
|
5719
|
+
this.#shown = null;
|
|
5720
|
+
this.#chosen = false;
|
|
5721
|
+
this.#reading = null;
|
|
5722
|
+
}
|
|
5723
|
+
await this.#readKept();
|
|
5724
|
+
}
|
|
5626
5725
|
/** Ask the attached agents for their sessions again: the drawer is being looked at. */
|
|
5627
5726
|
refresh() {
|
|
5628
5727
|
for (const link of this.#links.values()) if (link.can.list) link.send({ type: "list" });
|
|
@@ -5701,7 +5800,11 @@ var AcpFollower = class {
|
|
|
5701
5800
|
break;
|
|
5702
5801
|
}
|
|
5703
5802
|
case "session/new":
|
|
5704
|
-
if (typeof m.result?.sessionId === "string")
|
|
5803
|
+
if (typeof m.result?.sessionId === "string") {
|
|
5804
|
+
const opened = this.#open(link, m.result.sessionId, key.startsWith("isorite-") ? "ours" : "in", typeof params.cwd === "string" ? params.cwd : "");
|
|
5805
|
+
opened.modes = modesOf(m.result.modes);
|
|
5806
|
+
opened.configs = configsOf(m.result.configOptions);
|
|
5807
|
+
}
|
|
5705
5808
|
break;
|
|
5706
5809
|
case "session/load":
|
|
5707
5810
|
case "session/resume":
|
|
@@ -5710,9 +5813,20 @@ var AcpFollower = class {
|
|
|
5710
5813
|
this.#lives.delete(keyOf(link.agent, live.session));
|
|
5711
5814
|
break;
|
|
5712
5815
|
}
|
|
5816
|
+
if (m.result?.modes) live.modes = modesOf(m.result.modes);
|
|
5817
|
+
if (m.result?.configOptions) live.configs = configsOf(m.result.configOptions);
|
|
5713
5818
|
this.#loaded(live);
|
|
5714
5819
|
}
|
|
5715
5820
|
break;
|
|
5821
|
+
case "session/set_mode":
|
|
5822
|
+
if (live && !m.error && live.modes) live.modes = {
|
|
5823
|
+
...live.modes,
|
|
5824
|
+
current: str(params.modeId)
|
|
5825
|
+
};
|
|
5826
|
+
break;
|
|
5827
|
+
case "session/set_config_option":
|
|
5828
|
+
if (live && !m.error && m.result?.configOptions) live.configs = configsOf(m.result.configOptions);
|
|
5829
|
+
break;
|
|
5716
5830
|
case "session/prompt":
|
|
5717
5831
|
if (live) {
|
|
5718
5832
|
const stop = m.error ? "failed" : m.result?.stopReason === "cancelled" ? "stopped" : m.result?.stopReason === "end_turn" ? "done" : "failed";
|
|
@@ -5776,7 +5890,10 @@ var AcpFollower = class {
|
|
|
5776
5890
|
loading: false,
|
|
5777
5891
|
replaying: false,
|
|
5778
5892
|
pending: null,
|
|
5779
|
-
given: /* @__PURE__ */ new Set()
|
|
5893
|
+
given: /* @__PURE__ */ new Set(),
|
|
5894
|
+
commands: [],
|
|
5895
|
+
modes: null,
|
|
5896
|
+
configs: []
|
|
5780
5897
|
};
|
|
5781
5898
|
live.link = link;
|
|
5782
5899
|
if (cwd) live.cwd = cwd;
|
|
@@ -5966,7 +6083,19 @@ var AcpFollower = class {
|
|
|
5966
6083
|
if (typeof cost === "number") live.cost = cost;
|
|
5967
6084
|
break;
|
|
5968
6085
|
}
|
|
5969
|
-
case "session_info_update":
|
|
6086
|
+
case "session_info_update":
|
|
6087
|
+
if (typeof u.title === "string") live.title = u.title;
|
|
6088
|
+
break;
|
|
6089
|
+
case "available_commands_update":
|
|
6090
|
+
live.commands = commandsOf(u.availableCommands);
|
|
6091
|
+
break;
|
|
6092
|
+
case "current_mode_update":
|
|
6093
|
+
if (live.modes && typeof u.currentModeId === "string") live.modes = {
|
|
6094
|
+
...live.modes,
|
|
6095
|
+
current: u.currentModeId
|
|
6096
|
+
};
|
|
6097
|
+
break;
|
|
6098
|
+
case "config_option_update": live.configs = configsOf(u.configOptions);
|
|
5970
6099
|
}
|
|
5971
6100
|
}
|
|
5972
6101
|
/** The note a path names, if it is a Markdown file of the base (relative paths read from the session's folder). */
|
|
@@ -6026,7 +6155,7 @@ var AcpFollower = class {
|
|
|
6026
6155
|
};
|
|
6027
6156
|
//#endregion
|
|
6028
6157
|
//#region packages/server/src/version.ts
|
|
6029
|
-
const VERSION = "0.
|
|
6158
|
+
const VERSION = "0.15.0";
|
|
6030
6159
|
//#endregion
|
|
6031
6160
|
//#region packages/server/src/connect.ts
|
|
6032
6161
|
const HARNESSES = [{
|
|
@@ -6224,6 +6353,108 @@ async function tokenOf(url) {
|
|
|
6224
6353
|
return (await (await fetch(`${url}/api/session`)).json()).token;
|
|
6225
6354
|
}
|
|
6226
6355
|
//#endregion
|
|
6356
|
+
//#region packages/server/src/detect.ts
|
|
6357
|
+
const KNOWN = [
|
|
6358
|
+
{
|
|
6359
|
+
id: "claude",
|
|
6360
|
+
name: "Claude Code",
|
|
6361
|
+
bin: "claude",
|
|
6362
|
+
command: "npx -y @agentclientprotocol/claude-agent-acp",
|
|
6363
|
+
needs: "npx",
|
|
6364
|
+
keys: ["ANTHROPIC_API_KEY"],
|
|
6365
|
+
signedIn: true
|
|
6366
|
+
},
|
|
6367
|
+
{
|
|
6368
|
+
id: "codex",
|
|
6369
|
+
name: "Codex",
|
|
6370
|
+
bin: "codex",
|
|
6371
|
+
command: "npx -y @agentclientprotocol/codex-acp",
|
|
6372
|
+
needs: "npx",
|
|
6373
|
+
keys: ["CODEX_API_KEY", "OPENAI_API_KEY"]
|
|
6374
|
+
},
|
|
6375
|
+
{
|
|
6376
|
+
id: "opencode",
|
|
6377
|
+
name: "OpenCode",
|
|
6378
|
+
bin: "opencode",
|
|
6379
|
+
command: "opencode acp",
|
|
6380
|
+
keys: []
|
|
6381
|
+
},
|
|
6382
|
+
{
|
|
6383
|
+
id: "omp",
|
|
6384
|
+
name: "omp",
|
|
6385
|
+
bin: "omp",
|
|
6386
|
+
command: "omp acp",
|
|
6387
|
+
keys: []
|
|
6388
|
+
},
|
|
6389
|
+
{
|
|
6390
|
+
id: "hermes",
|
|
6391
|
+
name: "Hermes",
|
|
6392
|
+
bin: "hermes",
|
|
6393
|
+
command: "hermes acp",
|
|
6394
|
+
keys: []
|
|
6395
|
+
}
|
|
6396
|
+
];
|
|
6397
|
+
/** The first file called `name` on PATH (with the extensions Windows tries), or null. */
|
|
6398
|
+
function onPath(name, env = process.env) {
|
|
6399
|
+
const exts = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
|
|
6400
|
+
for (const dir of (env.PATH ?? "").split(delimiter).filter(Boolean)) for (const ext of exts) {
|
|
6401
|
+
const file = join(dir, name + ext);
|
|
6402
|
+
try {
|
|
6403
|
+
if (existsSync(file) && statSync(file).isFile()) return file;
|
|
6404
|
+
} catch {}
|
|
6405
|
+
}
|
|
6406
|
+
return null;
|
|
6407
|
+
}
|
|
6408
|
+
function detectAgents(env = process.env) {
|
|
6409
|
+
return KNOWN.flatMap((k) => {
|
|
6410
|
+
const path = onPath(k.bin, env);
|
|
6411
|
+
if (!path) return [];
|
|
6412
|
+
const keySet = k.keys.some((v) => Boolean(env[v]));
|
|
6413
|
+
const ready = !k.needs || onPath(k.needs, env) !== null;
|
|
6414
|
+
const parts = [`${k.name} is installed`];
|
|
6415
|
+
if (!ready) parts.push(`but it needs ${k.needs}, which isn't on PATH`);
|
|
6416
|
+
else if (k.keys.length && keySet) parts.push(`and ${k.keys.find((v) => env[v])} is set here`);
|
|
6417
|
+
else if (k.keys.length) parts.push(k.signedIn ? `; ${k.keys[0]} isn't set here, which is fine if you've signed in with it` : `, but ${k.keys.join(" or ")} isn't set in the terminal isorite was started from`);
|
|
6418
|
+
return [{
|
|
6419
|
+
id: k.id,
|
|
6420
|
+
name: k.name,
|
|
6421
|
+
command: k.command,
|
|
6422
|
+
path,
|
|
6423
|
+
keys: k.keys,
|
|
6424
|
+
keySet,
|
|
6425
|
+
ready,
|
|
6426
|
+
note: parts.join(" ").replace(" ;", ";").replace(" ,", ",") + "."
|
|
6427
|
+
}];
|
|
6428
|
+
});
|
|
6429
|
+
}
|
|
6430
|
+
function listAgents(env = process.env) {
|
|
6431
|
+
const found = detectAgents(env).map((d) => ({
|
|
6432
|
+
...d,
|
|
6433
|
+
found: true
|
|
6434
|
+
}));
|
|
6435
|
+
const have = new Set(found.map((d) => d.id));
|
|
6436
|
+
const rest = KNOWN.filter((k) => !have.has(k.id)).map((k) => ({
|
|
6437
|
+
id: k.id,
|
|
6438
|
+
name: k.name,
|
|
6439
|
+
command: k.command,
|
|
6440
|
+
path: "",
|
|
6441
|
+
keys: k.keys,
|
|
6442
|
+
keySet: false,
|
|
6443
|
+
ready: false,
|
|
6444
|
+
found: false,
|
|
6445
|
+
note: `${k.name} isn't on this machine's PATH.`
|
|
6446
|
+
}));
|
|
6447
|
+
return [...found, ...rest];
|
|
6448
|
+
}
|
|
6449
|
+
/** The ACP command of an agent known by name (`isorite acp omp`), or null. */
|
|
6450
|
+
const knownCommand = (id) => KNOWN.find((k) => k.id === id)?.command ?? null;
|
|
6451
|
+
/** Why an agent that stopped may have: the key it reads isn't set where isorite was started, for the command run. */
|
|
6452
|
+
function missingKey(command, env = process.env) {
|
|
6453
|
+
const k = KNOWN.find((x) => x.command === command);
|
|
6454
|
+
if (!k || !k.keys.length || k.keys.some((v) => env[v])) return "";
|
|
6455
|
+
return `${k.keys.join(" or ")} is not set in the terminal isorite was started from. Set it there (export ${k.keys[0]}=…) and start isorite again${k.signedIn ? ", or sign in with `claude` first" : ""}.`;
|
|
6456
|
+
}
|
|
6457
|
+
//#endregion
|
|
6227
6458
|
//#region packages/server/src/agents.ts
|
|
6228
6459
|
init_self();
|
|
6229
6460
|
const FRESH = 36e5;
|
|
@@ -6394,12 +6625,14 @@ var AgentRunner = class {
|
|
|
6394
6625
|
process.once("exit", () => this.#child?.kill());
|
|
6395
6626
|
}
|
|
6396
6627
|
state() {
|
|
6628
|
+
const hint = this.#problem && this.#command ? missingKey(this.#command, this.#env) : "";
|
|
6397
6629
|
return {
|
|
6398
6630
|
choice: chosenAgent(this.#base, this.#config),
|
|
6399
6631
|
running: this.#child !== null,
|
|
6400
6632
|
command: this.#command,
|
|
6401
6633
|
problem: this.#problem,
|
|
6402
|
-
said: this.#said
|
|
6634
|
+
said: this.#said,
|
|
6635
|
+
hint
|
|
6403
6636
|
};
|
|
6404
6637
|
}
|
|
6405
6638
|
/** Kept, and started in place of the one running; with none, the one running is stopped. */
|
|
@@ -6468,6 +6701,108 @@ var AgentRunner = class {
|
|
|
6468
6701
|
}
|
|
6469
6702
|
};
|
|
6470
6703
|
//#endregion
|
|
6704
|
+
//#region packages/server/src/own-repo.ts
|
|
6705
|
+
const q = (s) => /^[\w@%+=:,./-]+$/.test(s) ? s : `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
6706
|
+
const slashes = (p) => p.split("\\").join("/");
|
|
6707
|
+
function git(cwd, args, env) {
|
|
6708
|
+
return execFileSync("git", args, {
|
|
6709
|
+
cwd,
|
|
6710
|
+
env: plainGitEnv(env),
|
|
6711
|
+
timeout: 1e4,
|
|
6712
|
+
killSignal: "SIGKILL",
|
|
6713
|
+
encoding: "utf8",
|
|
6714
|
+
stdio: [
|
|
6715
|
+
"ignore",
|
|
6716
|
+
"pipe",
|
|
6717
|
+
"pipe"
|
|
6718
|
+
]
|
|
6719
|
+
});
|
|
6720
|
+
}
|
|
6721
|
+
/** What this machine chose for a base, from bases.toml. */
|
|
6722
|
+
function historyChoice(base, config) {
|
|
6723
|
+
const v = table(readConfig(basesPath(config))[base]).history;
|
|
6724
|
+
return v === "project" || v === "separate" ? v : null;
|
|
6725
|
+
}
|
|
6726
|
+
function setHistoryChoice(base, choice, config) {
|
|
6727
|
+
const bases = readConfig(basesPath(config));
|
|
6728
|
+
if (!bases[base]) throw new Error(`${base} is not among the bases`);
|
|
6729
|
+
bases[base] = {
|
|
6730
|
+
...table(bases[base]),
|
|
6731
|
+
history: choice
|
|
6732
|
+
};
|
|
6733
|
+
writeConfig(bases, basesPath(config));
|
|
6734
|
+
}
|
|
6735
|
+
/** Where isorite's own folder is kept, and the commands for the person to run. Runs `git ls-files`, `remote` and
|
|
6736
|
+
* `rev-parse`, which only read. */
|
|
6737
|
+
function ownRepoInfo(own, choice = null, env = process.env) {
|
|
6738
|
+
const project = repoOf(dirname(own));
|
|
6739
|
+
const base = {
|
|
6740
|
+
state: "none",
|
|
6741
|
+
path: own,
|
|
6742
|
+
project,
|
|
6743
|
+
remotes: [],
|
|
6744
|
+
choice,
|
|
6745
|
+
ask: false,
|
|
6746
|
+
commands: [],
|
|
6747
|
+
problem: ""
|
|
6748
|
+
};
|
|
6749
|
+
try {
|
|
6750
|
+
if (existsSync(join(own, ".git"))) {
|
|
6751
|
+
const remotes = git(own, ["remote"], env).split("\n").filter(Boolean);
|
|
6752
|
+
return {
|
|
6753
|
+
...base,
|
|
6754
|
+
state: "own",
|
|
6755
|
+
remotes,
|
|
6756
|
+
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`]
|
|
6757
|
+
};
|
|
6758
|
+
}
|
|
6759
|
+
if (!project) return base;
|
|
6760
|
+
const rel = slashes(relative(project, own));
|
|
6761
|
+
const tracked = git(project, [
|
|
6762
|
+
"ls-files",
|
|
6763
|
+
"--",
|
|
6764
|
+
rel
|
|
6765
|
+
], env).trim() !== "";
|
|
6766
|
+
const path = git(project, [
|
|
6767
|
+
"rev-parse",
|
|
6768
|
+
"--git-path",
|
|
6769
|
+
"info/exclude"
|
|
6770
|
+
], env).trim();
|
|
6771
|
+
const exclude = isAbsolute(path) ? path : resolve(project, path);
|
|
6772
|
+
const apart = [
|
|
6773
|
+
`git -C ${q(own)} init`,
|
|
6774
|
+
`echo ${q(rel + "/")} >> ${q(exclude)}`,
|
|
6775
|
+
`git -C ${q(own)} add -A`,
|
|
6776
|
+
`git -C ${q(own)} commit -m "isorite: start"`
|
|
6777
|
+
];
|
|
6778
|
+
return {
|
|
6779
|
+
...base,
|
|
6780
|
+
state: tracked ? "tracked" : "shared",
|
|
6781
|
+
ask: choice === null,
|
|
6782
|
+
commands: tracked ? [
|
|
6783
|
+
`git -C ${q(project)} rm -r --cached ${q(rel)}`,
|
|
6784
|
+
...apart,
|
|
6785
|
+
`git -C ${q(project)} commit -m "stop tracking isorite's own files"`
|
|
6786
|
+
] : apart
|
|
6787
|
+
};
|
|
6788
|
+
} catch (e) {
|
|
6789
|
+
return {
|
|
6790
|
+
...base,
|
|
6791
|
+
state: "unavailable",
|
|
6792
|
+
problem: e.message.split("\n")[0] ?? "git did not answer"
|
|
6793
|
+
};
|
|
6794
|
+
}
|
|
6795
|
+
}
|
|
6796
|
+
/** For a base just made in a git project: ask whether its own files stay with the project or apart, keep the answer
|
|
6797
|
+
* for this machine, and say the commands for apart. Asks nothing outside a project. isorite runs none of it. */
|
|
6798
|
+
async function askWhereFilesLive(notes, ask, out, config, env = process.env) {
|
|
6799
|
+
if (!repoOf(notes)) return null;
|
|
6800
|
+
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";
|
|
6801
|
+
setHistoryChoice(rememberBase(notes, { path: config }), choice, config);
|
|
6802
|
+
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`);
|
|
6803
|
+
return choice;
|
|
6804
|
+
}
|
|
6805
|
+
//#endregion
|
|
6471
6806
|
//#region packages/server/src/http.ts
|
|
6472
6807
|
const LOOPBACK = /* @__PURE__ */ new Set([
|
|
6473
6808
|
"localhost",
|
|
@@ -6544,7 +6879,7 @@ const AgentStatus = z.object({
|
|
|
6544
6879
|
session: z.string().min(1).max(200),
|
|
6545
6880
|
busy: z.boolean()
|
|
6546
6881
|
}).strict();
|
|
6547
|
-
function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bases, base, agents }) {
|
|
6882
|
+
function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bases, base, agents, config }) {
|
|
6548
6883
|
const app = new Hono();
|
|
6549
6884
|
const allowed = new Set([...allowHosts].map((h) => h.toLowerCase()));
|
|
6550
6885
|
const reply = (c, status, value) => c.body(JSON.stringify(value), status, {
|
|
@@ -6678,6 +7013,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6678
7013
|
app.get("/api/agents/notes", (c) => answer(c, () => api.agentNotes()));
|
|
6679
7014
|
post("/api/agents/notes", z.object({ may: z.boolean() }).strict(), (_c, b) => api.setAgentNotes(b.may));
|
|
6680
7015
|
app.get("/api/conversations", (c) => answer(c, () => api.conversations()));
|
|
7016
|
+
post("/api/conversation/delete", z.object({ id: z.string().max(64) }).strict(), (_c, b) => api.deleteConversation(b.id));
|
|
6681
7017
|
app.get("/api/conversation", (c) => answer(c, async () => await api.conversation(c.req.query("id") || void 0) ?? {
|
|
6682
7018
|
id: "",
|
|
6683
7019
|
turns: []
|
|
@@ -6756,6 +7092,16 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6756
7092
|
}).strict(), (_c, b) => api.agentReceived(b.id, b.agent));
|
|
6757
7093
|
post("/api/agent/status", AgentStatus, (_c, b) => api.agentStatus(b.agent, b.session, b.busy));
|
|
6758
7094
|
post("/api/agent/take", z.object({ id: z.string().max(64) }).strict(), (_c, b) => api.agentTake(b.id));
|
|
7095
|
+
if (notes) {
|
|
7096
|
+
const configFile = config ?? configPath();
|
|
7097
|
+
app.get("/api/own/repo", (c) => answer(c, async () => local(c) ? ownRepoInfo(join(notes, ".isorite"), base ? historyChoice(base, configFile) : null) : null));
|
|
7098
|
+
post("/api/own/choose", z.object({ choice: z.enum(["project", "separate"]) }).strict(), async (c, b) => {
|
|
7099
|
+
if (!local(c)) throw new EngineError("this is chosen only from this machine", 403);
|
|
7100
|
+
if (!base) throw new EngineError("this base has no name here", 409);
|
|
7101
|
+
setHistoryChoice(base, b.choice, configFile);
|
|
7102
|
+
return ownRepoInfo(join(notes, ".isorite"), b.choice);
|
|
7103
|
+
});
|
|
7104
|
+
}
|
|
6759
7105
|
if (notes) {
|
|
6760
7106
|
app.get("/api/connect", (c) => answer(c, async () => proposal(notes)));
|
|
6761
7107
|
post("/api/connect", z.object({ files: z.array(z.string().max(200)).max(10).optional() }).strict(), async (c, b) => {
|
|
@@ -6765,6 +7111,10 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6765
7111
|
}
|
|
6766
7112
|
if (agents) {
|
|
6767
7113
|
app.get("/api/acp/agent", (c) => answer(c, async () => agents.state()));
|
|
7114
|
+
app.get("/api/acp/detected", (c) => answer(c, async () => {
|
|
7115
|
+
if (!local(c)) throw new EngineError("agents are looked for only from this machine", 403);
|
|
7116
|
+
return listAgents();
|
|
7117
|
+
}));
|
|
6768
7118
|
app.get("/api/acp/registry", (c) => answer(c, async () => registryAgents().catch((e) => {
|
|
6769
7119
|
throw new EngineError(e.message, 502);
|
|
6770
7120
|
})));
|
|
@@ -6868,6 +7218,23 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, acp, bas
|
|
|
6868
7218
|
}).strict(), async (_c, b) => {
|
|
6869
7219
|
await acp.follow(b.agent, b.session).catch(refused);
|
|
6870
7220
|
});
|
|
7221
|
+
post("/api/acp/forget", z.object({
|
|
7222
|
+
agent: z.string().max(40),
|
|
7223
|
+
session: z.string().min(1).max(200)
|
|
7224
|
+
}).strict(), async (_c, b) => {
|
|
7225
|
+
await acp.forget(b.agent, b.session).catch(refused);
|
|
7226
|
+
});
|
|
7227
|
+
post("/api/acp/set", z.object({
|
|
7228
|
+
kind: z.enum(["mode", "config"]),
|
|
7229
|
+
id: z.string().max(100),
|
|
7230
|
+
value: z.string().max(200)
|
|
7231
|
+
}).strict(), async (_c, b) => {
|
|
7232
|
+
try {
|
|
7233
|
+
acp.set(b.kind, b.id, b.value);
|
|
7234
|
+
} catch (e) {
|
|
7235
|
+
throw new EngineError(e.message, 409);
|
|
7236
|
+
}
|
|
7237
|
+
});
|
|
6871
7238
|
post("/api/acp/new", z.object({ agent: z.string().max(40) }).strict(), async (_c, b) => {
|
|
6872
7239
|
await acp.fresh(b.agent).catch(refused);
|
|
6873
7240
|
});
|
|
@@ -7321,6 +7688,7 @@ async function openServer(path, { host = "127.0.0.1", port = 4767, allowHosts =
|
|
|
7321
7688
|
const opened = locate(path);
|
|
7322
7689
|
const device = deviceId(config);
|
|
7323
7690
|
const first = rememberBase(opened.notes, { path: config });
|
|
7691
|
+
touchBase(opened.notes, { path: config });
|
|
7324
7692
|
const embedder = model ? nodeEmbedder(model) : null;
|
|
7325
7693
|
const token = randomBytes(24).toString("base64url"), loopback = LOOPBACK.has(host);
|
|
7326
7694
|
const web = existsSync(join(WEB_DIR, "index.html")) ? WEB_DIR : null;
|
|
@@ -7357,7 +7725,8 @@ async function openServer(path, { host = "127.0.0.1", port = 4767, allowHosts =
|
|
|
7357
7725
|
acp,
|
|
7358
7726
|
bases,
|
|
7359
7727
|
base: known.name,
|
|
7360
|
-
...loopback ? { agents } : {}
|
|
7728
|
+
...loopback ? { agents } : {},
|
|
7729
|
+
config
|
|
7361
7730
|
}).fetch
|
|
7362
7731
|
};
|
|
7363
7732
|
}
|
|
@@ -7389,7 +7758,7 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
|
|
|
7389
7758
|
process.exit(1);
|
|
7390
7759
|
});
|
|
7391
7760
|
console.log(`isorite: ${notes}`);
|
|
7392
|
-
console.log(embedder ? " search by meaning: on" : " search by meaning: off (isorite model fetches its model)");
|
|
7761
|
+
console.log(embedder ? " search by meaning: on" : " search by meaning: off (isorite model fetches its model, 53 MB)");
|
|
7393
7762
|
const lent = bases.lent().filter((l) => l.name !== bases.first).map((l) => l.name);
|
|
7394
7763
|
if (lent.length) console.log(` lent to every session: ${lent.join(", ")} (isorite bases)`);
|
|
7395
7764
|
console.log(existsSync(join(WEB_DIR, "index.html")) ? ` ${link(`http://localhost:${port}/`)}` : ` the app is not built (npm run build); the engine alone is at http://localhost:${port}/api/`);
|
|
@@ -7509,6 +7878,13 @@ async function doctor(folder, o = {}) {
|
|
|
7509
7878
|
const { notes } = locate(folder);
|
|
7510
7879
|
const own = join(notes, OWN_DIR), settings = join(own, SETTINGS_FILE);
|
|
7511
7880
|
say("notes", "ok", existsSync(own) ? `${notes}, with its ${OWN_DIR}/` : `${notes}: its ${OWN_DIR}/ is made the first time it is opened`);
|
|
7881
|
+
if (existsSync(notes) && statSync(notes).isDirectory()) {
|
|
7882
|
+
const bundle = loadBundle(notes), issues = bundle.lint(), cycles = bundle.requiresCycles();
|
|
7883
|
+
const errors = issues.filter((i) => i.level === "error");
|
|
7884
|
+
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})` : ""}` : ""}`);
|
|
7885
|
+
}
|
|
7886
|
+
const kept = ownRepoInfo(own, null, env);
|
|
7887
|
+
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`);
|
|
7512
7888
|
const repository = existsSync(settings) ? parseSettings(readFileSync(settings, "utf8")).repository : void 0;
|
|
7513
7889
|
const found = repoOf(notes);
|
|
7514
7890
|
if (!repository && found) say("repository", "ok", `${found}: code links point here`);
|
|
@@ -7749,7 +8125,7 @@ const OPENS = /* @__PURE__ */ new Set([
|
|
|
7749
8125
|
"session/resume",
|
|
7750
8126
|
"session/fork"
|
|
7751
8127
|
]);
|
|
7752
|
-
const SKIP = /* @__PURE__ */ new Set([
|
|
8128
|
+
const SKIP$1 = /* @__PURE__ */ new Set([
|
|
7753
8129
|
"node_modules",
|
|
7754
8130
|
".git",
|
|
7755
8131
|
OWN_DIR,
|
|
@@ -7760,7 +8136,7 @@ const SKIP = /* @__PURE__ */ new Set([
|
|
|
7760
8136
|
]);
|
|
7761
8137
|
/** The base for a session started in `cwd`: the folder itself or one above it holding .isorite/, else the first
|
|
7762
8138
|
* below it, three levels down at most (a base inside the repository, as notes/); or none. */
|
|
7763
|
-
function findBase(cwd) {
|
|
8139
|
+
function findBase$1(cwd) {
|
|
7764
8140
|
for (let at = cwd;; at = dirname(at)) {
|
|
7765
8141
|
if (existsSync(join(at, ".isorite"))) return at;
|
|
7766
8142
|
if (dirname(at) === at) break;
|
|
@@ -7771,7 +8147,7 @@ function findBase(cwd) {
|
|
|
7771
8147
|
for (const dir of level) {
|
|
7772
8148
|
let names = [];
|
|
7773
8149
|
try {
|
|
7774
|
-
names = readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory() && !SKIP.has(d.name) && !d.name.startsWith(".")).map((d) => d.name).sort();
|
|
8150
|
+
names = readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory() && !SKIP$1.has(d.name) && !d.name.startsWith(".")).map((d) => d.name).sort();
|
|
7775
8151
|
} catch {
|
|
7776
8152
|
continue;
|
|
7777
8153
|
}
|
|
@@ -7793,7 +8169,7 @@ async function tokenWhenUp(url, wait = 6e4) {
|
|
|
7793
8169
|
}
|
|
7794
8170
|
}
|
|
7795
8171
|
async function acp(path, { agent, port = 4767, input, output, env = process.env, pane = false, url: at }) {
|
|
7796
|
-
if (!at) path ??= findBase(process.cwd()) ?? void 0;
|
|
8172
|
+
if (!at) path ??= findBase$1(process.cwd()) ?? void 0;
|
|
7797
8173
|
const server = at ? {
|
|
7798
8174
|
url: at,
|
|
7799
8175
|
release: () => void 0
|
|
@@ -8139,6 +8515,14 @@ async function acp(path, { agent, port = 4767, input, output, env = process.env,
|
|
|
8139
8515
|
toAgent(m);
|
|
8140
8516
|
}
|
|
8141
8517
|
if (c.type === "list" && can.list) ask("session/list", { cwd });
|
|
8518
|
+
if (c.type === "set") ask(c.kind === "mode" ? "session/set_mode" : "session/set_config_option", c.kind === "mode" ? {
|
|
8519
|
+
sessionId: c.session,
|
|
8520
|
+
modeId: c.value
|
|
8521
|
+
} : {
|
|
8522
|
+
sessionId: c.session,
|
|
8523
|
+
configId: c.id,
|
|
8524
|
+
value: c.value
|
|
8525
|
+
});
|
|
8142
8526
|
if (c.type === "open") {
|
|
8143
8527
|
const params = {
|
|
8144
8528
|
cwd,
|
|
@@ -9141,10 +9525,128 @@ function bases(args, { config = configPath(), out = console.log } = {}) {
|
|
|
9141
9525
|
return 0;
|
|
9142
9526
|
}
|
|
9143
9527
|
//#endregion
|
|
9528
|
+
//#region packages/server/src/start.ts
|
|
9529
|
+
const SKIP = /* @__PURE__ */ new Set([
|
|
9530
|
+
"node_modules",
|
|
9531
|
+
"dist",
|
|
9532
|
+
"build",
|
|
9533
|
+
"target",
|
|
9534
|
+
"vendor"
|
|
9535
|
+
]);
|
|
9536
|
+
const isBase = (dir) => existsSync(join(dir, ".isorite")) && statSync(join(dir, ".isorite")).isDirectory();
|
|
9537
|
+
/** The base `from` is in or beside: a folder holding .isorite/ on the way up to the repository's top (or `from`
|
|
9538
|
+
* alone, outside a repository), else the one folder just inside that top that holds one. */
|
|
9539
|
+
function findBase(from) {
|
|
9540
|
+
const start = resolve(from), repo = repoOf(start), top = repo ?? start;
|
|
9541
|
+
for (let at = start;; at = dirname(at)) {
|
|
9542
|
+
if (isBase(at)) return {
|
|
9543
|
+
kind: "base",
|
|
9544
|
+
notes: at
|
|
9545
|
+
};
|
|
9546
|
+
if (at === top || dirname(at) === at) break;
|
|
9547
|
+
}
|
|
9548
|
+
let inside = [];
|
|
9549
|
+
try {
|
|
9550
|
+
inside = readdirSync(top, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && !SKIP.has(e.name) && isBase(join(top, e.name))).map((e) => join(top, e.name)).sort();
|
|
9551
|
+
} catch {}
|
|
9552
|
+
if (inside.length === 1) return {
|
|
9553
|
+
kind: "base",
|
|
9554
|
+
notes: inside[0]
|
|
9555
|
+
};
|
|
9556
|
+
if (inside.length > 1) return {
|
|
9557
|
+
kind: "several",
|
|
9558
|
+
notes: inside
|
|
9559
|
+
};
|
|
9560
|
+
return repo ? {
|
|
9561
|
+
kind: "repo",
|
|
9562
|
+
repo
|
|
9563
|
+
} : { kind: "none" };
|
|
9564
|
+
}
|
|
9565
|
+
/** The first port from `from` that nothing is listening on, so a second `isorite` starts beside the first. */
|
|
9566
|
+
async function freePort(from = 4767, host = "127.0.0.1") {
|
|
9567
|
+
for (let port = from; port < from + 20; port++) if (await new Promise((done) => {
|
|
9568
|
+
const probe = createServer().once("error", () => done(false)).once("listening", () => probe.close(() => done(true)));
|
|
9569
|
+
probe.listen(port, host);
|
|
9570
|
+
})) return port;
|
|
9571
|
+
return from;
|
|
9572
|
+
}
|
|
9573
|
+
/** The bases this machine knows that are still there, the last used first (those never used by name). */
|
|
9574
|
+
function recent(config = configPath()) {
|
|
9575
|
+
return knownBases(config).filter((b) => existsSync(b.path)).sort((a, b) => (b.used ?? "").localeCompare(a.used ?? "") || a.name.localeCompare(b.name));
|
|
9576
|
+
}
|
|
9577
|
+
/** A base as the list shows it: its name with its project's, where two share a name. */
|
|
9578
|
+
function label(b, all) {
|
|
9579
|
+
const same = all.filter((o) => o.name.replace(/-\d+$/, "") === b.name.replace(/-\d+$/, "")).length > 1;
|
|
9580
|
+
const project = basename(repoOf(b.path) ?? dirname(b.path));
|
|
9581
|
+
return `${basename(b.path)}${same || basename(b.path) !== project ? ` (${project})` : ""}`;
|
|
9582
|
+
}
|
|
9583
|
+
/** The choice `isorite` makes, and what it says. */
|
|
9584
|
+
async function start(io) {
|
|
9585
|
+
const make = async (folder) => {
|
|
9586
|
+
const notes = io.make(folder);
|
|
9587
|
+
await io.made?.(notes);
|
|
9588
|
+
return notes;
|
|
9589
|
+
};
|
|
9590
|
+
const found = findBase(io.cwd);
|
|
9591
|
+
if (found.kind === "base") return io.open(found.notes);
|
|
9592
|
+
if (found.kind === "several") {
|
|
9593
|
+
io.out(`isorite: more than one base here:`);
|
|
9594
|
+
found.notes.forEach((n, i) => io.out(` ${i + 1} ${n}`));
|
|
9595
|
+
const pick = await io.choose("Open which?", found.notes.length);
|
|
9596
|
+
return pick === null ? 2 : io.open(found.notes[pick]);
|
|
9597
|
+
}
|
|
9598
|
+
if (found.kind === "repo") {
|
|
9599
|
+
const notes = join(found.repo, "notes");
|
|
9600
|
+
io.out(`isorite: no base in ${found.repo} yet.`);
|
|
9601
|
+
if (!existsSync(notes) && await io.confirm(`Make one at ${notes}?`)) return io.open(await make(notes));
|
|
9602
|
+
if (existsSync(notes)) io.out(` ${notes} is there already: open it as it is with isorite serve ${notes}`);
|
|
9603
|
+
}
|
|
9604
|
+
const known = recent(io.config);
|
|
9605
|
+
if (!known.length && found.kind === "none") {
|
|
9606
|
+
io.out("isorite: no bases yet on this machine.");
|
|
9607
|
+
return await io.confirm("Open the guide, a small base about isorite?") ? io.guide() : 0;
|
|
9608
|
+
}
|
|
9609
|
+
const rows = [
|
|
9610
|
+
...known.map((b) => label(b, known)),
|
|
9611
|
+
`a new base here (${join(io.cwd, "notes")})`,
|
|
9612
|
+
"the guide"
|
|
9613
|
+
];
|
|
9614
|
+
io.out(known.length ? "isorite: the bases on this machine, last used first:" : "isorite:");
|
|
9615
|
+
rows.forEach((r, i) => io.out(` ${i + 1} ${r}`));
|
|
9616
|
+
const pick = await io.choose("Open which?", rows.length);
|
|
9617
|
+
if (pick === null) return 2;
|
|
9618
|
+
if (pick < known.length) return io.open(known[pick].path);
|
|
9619
|
+
if (pick === known.length) return io.open(await make(join(io.cwd, "notes")));
|
|
9620
|
+
return io.guide();
|
|
9621
|
+
}
|
|
9622
|
+
/** The model for search by meaning, offered on the first start without one (M26): asked once, fetched on a yes,
|
|
9623
|
+
* and a no remembered so it isn't asked again (`isorite model` fetches it whenever wanted). A failed fetch is said
|
|
9624
|
+
* and the start goes on: search by words works without it. */
|
|
9625
|
+
async function offerModel(o) {
|
|
9626
|
+
if (o.installed || o.off || o.declined) return;
|
|
9627
|
+
if (!await o.confirm("Fetch the 53 MB model for search by meaning, once? Without it search is by words.")) {
|
|
9628
|
+
o.decline();
|
|
9629
|
+
o.out(" not fetched; isorite model fetches it whenever you want it");
|
|
9630
|
+
return;
|
|
9631
|
+
}
|
|
9632
|
+
if (await o.fetch()) o.out(" search by words, for now: isorite doctor says what stopped the fetch");
|
|
9633
|
+
}
|
|
9634
|
+
//#endregion
|
|
9144
9635
|
//#region packages/server/src/main.ts
|
|
9145
9636
|
init_network();
|
|
9146
9637
|
init_self();
|
|
9638
|
+
init_embedder();
|
|
9147
9639
|
init_model();
|
|
9640
|
+
const SHORT = `isorite: an agentic harness for understanding
|
|
9641
|
+
|
|
9642
|
+
isorite open the base here, or choose one (a terminal; --no-open, --port <n>)
|
|
9643
|
+
isorite doctor whether isorite can work here, and what to change if not (--report to paste to
|
|
9644
|
+
someone helping; run in a project it checks that project's base)
|
|
9645
|
+
isorite help this; isorite help all lists every command
|
|
9646
|
+
isorite --version
|
|
9647
|
+
|
|
9648
|
+
Notes are plain Markdown in a folder of your project; your own agent reads and writes them with you.
|
|
9649
|
+
`;
|
|
9148
9650
|
const USAGE = `isorite: an agentic harness for understanding
|
|
9149
9651
|
|
|
9150
9652
|
isorite new <folder> start a knowledge base: the folder, empty, with isorite's .isorite/
|
|
@@ -9224,13 +9726,102 @@ function check(notes, out = console.log, unused = false) {
|
|
|
9224
9726
|
}
|
|
9225
9727
|
return errors ? 1 : 0;
|
|
9226
9728
|
}
|
|
9729
|
+
/** `isorite` at a terminal: asks on stdin, starts a server on the first free port (4767 up) and opens the page. */
|
|
9730
|
+
async function terminalStart({ port, noOpen }) {
|
|
9731
|
+
const { createInterface } = await import("node:readline/promises");
|
|
9732
|
+
const ask = async (question) => {
|
|
9733
|
+
const rl = createInterface({
|
|
9734
|
+
input: process.stdin,
|
|
9735
|
+
output: process.stdout
|
|
9736
|
+
});
|
|
9737
|
+
try {
|
|
9738
|
+
return (await rl.question(question)).trim();
|
|
9739
|
+
} finally {
|
|
9740
|
+
rl.close();
|
|
9741
|
+
}
|
|
9742
|
+
};
|
|
9743
|
+
const browser = !noOpen && !process.env.SSH_CONNECTION && process.env.ISORITE_NO_BROWSER !== "1";
|
|
9744
|
+
const confirm = async (q) => /^y(es)?$/i.test(await ask(`${q} [y/N] `));
|
|
9745
|
+
const declined = join(cacheDir(), "model-declined");
|
|
9746
|
+
const model = () => offerModel({
|
|
9747
|
+
installed: modelDir() !== null,
|
|
9748
|
+
off: process.env.ISORITE_EMBED === "off",
|
|
9749
|
+
declined: existsSync(declined),
|
|
9750
|
+
confirm,
|
|
9751
|
+
fetch: () => modelCommand(void 0, console.log),
|
|
9752
|
+
out: console.log,
|
|
9753
|
+
decline: () => {
|
|
9754
|
+
try {
|
|
9755
|
+
mkdirSync(dirname(declined), { recursive: true });
|
|
9756
|
+
writeFileSync(declined, "");
|
|
9757
|
+
} catch {}
|
|
9758
|
+
}
|
|
9759
|
+
});
|
|
9760
|
+
const serving = async (notes, at = "") => {
|
|
9761
|
+
await model();
|
|
9762
|
+
const use = port ?? await freePort();
|
|
9763
|
+
await serve$1(notes, { port: use });
|
|
9764
|
+
if (browser) openBrowser(`http://localhost:${use}/${at}`);
|
|
9765
|
+
return new Promise(() => void 0);
|
|
9766
|
+
};
|
|
9767
|
+
return start({
|
|
9768
|
+
cwd: process.cwd(),
|
|
9769
|
+
config: configPath(),
|
|
9770
|
+
out: console.log,
|
|
9771
|
+
confirm,
|
|
9772
|
+
choose: async (q, count) => {
|
|
9773
|
+
const n = Number(await ask(`${q} [1-${count}] `));
|
|
9774
|
+
return Number.isInteger(n) && n >= 1 && n <= count ? n - 1 : null;
|
|
9775
|
+
},
|
|
9776
|
+
open: (notes) => serving(notes),
|
|
9777
|
+
make: (folder) => {
|
|
9778
|
+
const made = newBase(folder);
|
|
9779
|
+
console.log(` made ${made}`);
|
|
9780
|
+
return made;
|
|
9781
|
+
},
|
|
9782
|
+
made: async (notes) => {
|
|
9783
|
+
await askWhereFilesLive(notes, ask, console.log, configPath());
|
|
9784
|
+
},
|
|
9785
|
+
guide: async () => {
|
|
9786
|
+
await model();
|
|
9787
|
+
await guide(void 0, { port: port ?? await freePort() });
|
|
9788
|
+
return new Promise(() => void 0);
|
|
9789
|
+
}
|
|
9790
|
+
});
|
|
9791
|
+
}
|
|
9227
9792
|
function main(argv) {
|
|
9228
9793
|
const [command, ...rest] = argv;
|
|
9229
9794
|
try {
|
|
9795
|
+
if ((!command || command === "--no-open" || command === "--port") && process.stdin.isTTY && process.stdout.isTTY) {
|
|
9796
|
+
const args = command ? [command, ...rest] : rest, at = args.indexOf("--port"), asked = at < 0 ? void 0 : Number(args[at + 1]);
|
|
9797
|
+
if (asked !== void 0 && !(Number.isInteger(asked) && asked > 0 && asked < 65536)) {
|
|
9798
|
+
console.error("--port takes a number");
|
|
9799
|
+
return 2;
|
|
9800
|
+
}
|
|
9801
|
+
return terminalStart({
|
|
9802
|
+
port: asked,
|
|
9803
|
+
noOpen: args.includes("--no-open")
|
|
9804
|
+
});
|
|
9805
|
+
}
|
|
9230
9806
|
if (command === "check" && rest.find((r) => !r.startsWith("--"))) return check(rest.find((r) => !r.startsWith("--")), console.log, rest.includes("--unused"));
|
|
9231
9807
|
if (command === "new" && rest[0]) {
|
|
9232
9808
|
const made = newBase(rest[0]);
|
|
9233
|
-
|
|
9809
|
+
const next = `${made}: a knowledge base, empty. Open it in the browser, and attach your agent from Iso's pane:\n isorite serve ${made}`;
|
|
9810
|
+
if (process.stdin.isTTY && process.stdout.isTTY && repoOf(made)) return (async () => {
|
|
9811
|
+
const { createInterface } = await import("node:readline/promises");
|
|
9812
|
+
const rl = createInterface({
|
|
9813
|
+
input: process.stdin,
|
|
9814
|
+
output: process.stdout
|
|
9815
|
+
});
|
|
9816
|
+
try {
|
|
9817
|
+
await askWhereFilesLive(made, (q) => rl.question(q), console.log, configPath());
|
|
9818
|
+
} finally {
|
|
9819
|
+
rl.close();
|
|
9820
|
+
}
|
|
9821
|
+
console.log(next);
|
|
9822
|
+
return 0;
|
|
9823
|
+
})();
|
|
9824
|
+
console.log(next);
|
|
9234
9825
|
return 0;
|
|
9235
9826
|
}
|
|
9236
9827
|
if (command === "serve" && rest[0]) {
|
|
@@ -9297,9 +9888,15 @@ function main(argv) {
|
|
|
9297
9888
|
const at = rest.indexOf(name);
|
|
9298
9889
|
return at < 0 ? void 0 : rest[at + 1];
|
|
9299
9890
|
};
|
|
9300
|
-
const
|
|
9891
|
+
const positional = rest.filter((a, i) => !a.startsWith("--") && ![
|
|
9892
|
+
"--port",
|
|
9893
|
+
"--agent",
|
|
9894
|
+
"--url"
|
|
9895
|
+
].includes(rest[i - 1] ?? ""));
|
|
9896
|
+
const named = !flag("--agent") && positional[0] && !existsSync(positional[0]) ? knownCommand(positional[0]) : null;
|
|
9897
|
+
const agent = flag("--agent") ?? named ?? void 0, port = flag("--port") === void 0 ? void 0 : Number(flag("--port"));
|
|
9301
9898
|
if (!agent) {
|
|
9302
|
-
console.error("isorite acp --agent \"<the agent's ACP command>\" [<notes>] [--port <n>] [--pane]");
|
|
9899
|
+
console.error("isorite acp <omp|opencode|claude|codex|hermes> [<notes>] [--port <n>]\nisorite acp --agent \"<the agent's ACP command>\" [<notes>] [--port <n>] [--pane]");
|
|
9303
9900
|
return 2;
|
|
9304
9901
|
}
|
|
9305
9902
|
if (port !== void 0 && !(Number.isInteger(port) && port > 0 && port < 65536)) {
|
|
@@ -9311,11 +9908,7 @@ function main(argv) {
|
|
|
9311
9908
|
console.error("--url takes a base's address on this machine");
|
|
9312
9909
|
return 2;
|
|
9313
9910
|
}
|
|
9314
|
-
return acp(
|
|
9315
|
-
"--port",
|
|
9316
|
-
"--agent",
|
|
9317
|
-
"--url"
|
|
9318
|
-
].includes(rest[i - 1] ?? "")), {
|
|
9911
|
+
return acp(named ? positional[1] : positional[0], {
|
|
9319
9912
|
agent,
|
|
9320
9913
|
pane,
|
|
9321
9914
|
...port === void 0 ? {} : { port },
|
|
@@ -9358,7 +9951,9 @@ function main(argv) {
|
|
|
9358
9951
|
console.error("--port takes a number");
|
|
9359
9952
|
return 2;
|
|
9360
9953
|
}
|
|
9361
|
-
const
|
|
9954
|
+
const named = rest.find((a, i) => !a.startsWith("--") && rest[i - 1] !== "--port");
|
|
9955
|
+
const here = named ? void 0 : findBase(process.cwd());
|
|
9956
|
+
const folder = named ?? (here?.kind === "base" ? here.notes : void 0);
|
|
9362
9957
|
const ask = process.stdin.isTTY && process.stdout.isTTY ? async (question) => {
|
|
9363
9958
|
const { createInterface } = await import("node:readline/promises");
|
|
9364
9959
|
const rl = createInterface({
|
|
@@ -9406,7 +10001,7 @@ function main(argv) {
|
|
|
9406
10001
|
}
|
|
9407
10002
|
throw e;
|
|
9408
10003
|
}
|
|
9409
|
-
process.stdout.write(USAGE);
|
|
10004
|
+
process.stdout.write(command === "help" && rest[0] === "all" ? USAGE : command && command !== "help" && command !== "--help" ? USAGE : SHORT);
|
|
9410
10005
|
return command && command !== "help" && command !== "--help" ? 2 : 0;
|
|
9411
10006
|
}
|
|
9412
10007
|
/** The commands that start something, which say so at once (M21): a subprocess that stalls after this is then
|