isorite 0.3.1 → 0.6.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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/dist/core.js +4 -2
  3. package/dist/main.js +4290 -3960
  4. package/docs/setup-for-agents.md +7 -2
  5. package/docs/setup.md +23 -5
  6. package/docs/terminal.md +9 -1
  7. package/package.json +1 -1
  8. package/plugin/tui.tsx +3 -77
  9. package/web/_app/immutable/assets/{0.IOi79DXH.css → 0.LEIHnkPh.css} +1 -1
  10. package/web/_app/immutable/chunks/{8IM6BNk6.js → 2DHY0Dzh.js} +21 -21
  11. package/web/_app/immutable/chunks/BQRMn_tl.js +1 -0
  12. package/web/_app/immutable/chunks/C-352d8F.js +1 -0
  13. package/web/_app/immutable/chunks/{B_AYGp9o.js → CHxNLteG.js} +1 -1
  14. package/web/_app/immutable/chunks/Cf6h5DpS.js +20 -0
  15. package/web/_app/immutable/chunks/{CMp5T_-1.js → D4A7-5k0.js} +1 -1
  16. package/web/_app/immutable/chunks/Dijhflxl.js +1 -0
  17. package/web/_app/immutable/chunks/Ps0IPbdL.js +1 -0
  18. package/web/_app/immutable/chunks/{BHPhFydX.js → ZRnI3PpK.js} +1 -1
  19. package/web/_app/immutable/chunks/nhmVyQdD.js +3 -0
  20. package/web/_app/immutable/chunks/rTqzqD6j.js +1 -0
  21. package/web/_app/immutable/chunks/y24aTX4O.js +1 -0
  22. package/web/_app/immutable/entry/app.BQ0r6jSu.js +2 -0
  23. package/web/_app/immutable/entry/start.CIQiYrSU.js +1 -0
  24. package/web/_app/immutable/nodes/0.zPrOkn79.js +26 -0
  25. package/web/_app/immutable/nodes/1.DfBkOAo2.js +1 -0
  26. package/web/_app/immutable/nodes/2.fp8Y5joG.js +1 -0
  27. package/web/_app/immutable/nodes/3.Do9cwxjS.js +1 -0
  28. package/web/_app/immutable/nodes/4.CGd6gzGJ.js +2 -0
  29. package/web/_app/immutable/nodes/5.Dtac-HK8.js +5 -0
  30. package/web/_app/immutable/nodes/6._-kZkWYe.js +1 -0
  31. package/web/_app/immutable/nodes/7.BdTE6U4H.js +1 -0
  32. package/web/_app/version.json +1 -1
  33. package/web/index.html +17 -20
  34. package/dist/tutorial/barnes_hut.py +0 -46
  35. package/dist/tutorial/sim.py +0 -29
  36. package/plugin/tour.ts +0 -74
  37. package/web/_app/immutable/assets/8.CFA9_nYZ.css +0 -1
  38. package/web/_app/immutable/assets/Tutorial.DOfsM49T.css +0 -1
  39. package/web/_app/immutable/chunks/Bze-M_kr.js +0 -3
  40. package/web/_app/immutable/chunks/CFA70_0c.js +0 -20
  41. package/web/_app/immutable/chunks/CGgq5NWd.js +0 -1
  42. package/web/_app/immutable/chunks/CHlFgDNB.js +0 -1
  43. package/web/_app/immutable/chunks/CRzLMJPS.js +0 -1
  44. package/web/_app/immutable/chunks/D89YN-Dr.js +0 -1
  45. package/web/_app/immutable/chunks/DF3lgcLW.js +0 -1
  46. package/web/_app/immutable/chunks/DGVcYHaJ.js +0 -43
  47. package/web/_app/immutable/chunks/DHMLhlug.js +0 -1
  48. package/web/_app/immutable/chunks/DmgpLvxp.js +0 -1
  49. package/web/_app/immutable/chunks/DrsVvzvg.js +0 -1
  50. package/web/_app/immutable/entry/app.hlk6OrE8.js +0 -2
  51. package/web/_app/immutable/entry/start.BE1F0HZV.js +0 -1
  52. package/web/_app/immutable/nodes/0.DMGdYhkH.js +0 -15
  53. package/web/_app/immutable/nodes/1.Dn_0fgt7.js +0 -1
  54. package/web/_app/immutable/nodes/2.Y1Dd5PBI.js +0 -1
  55. package/web/_app/immutable/nodes/3.BlxuRbLf.js +0 -1
  56. package/web/_app/immutable/nodes/4.Bt_H0MbN.js +0 -2
  57. package/web/_app/immutable/nodes/5.BJ-EVWbz.js +0 -5
  58. package/web/_app/immutable/nodes/6.DDIiJnwi.js +0 -1
  59. package/web/_app/immutable/nodes/7.DmPPLDvb.js +0 -1
  60. package/web/_app/immutable/nodes/8.CwMTBog4.js +0 -486
@@ -19,7 +19,7 @@ turn off certificate checking (`NODE_TLS_REJECT_UNAUTHORIZED=0`).
19
19
  | `isorite serve <notes> [--port <n>]` | The browser app at `http://localhost:4767/`, on 127.0.0.1 only. |
20
20
  | `isorite term [<notes>] [--port <n>]` | OpenCode 2 in this terminal, in the current folder (the code), with the notes beside it. Starts the isorite server if none answers on the port, and stops it on exit. |
21
21
  | `isorite check <notes>` | Lints the notes: frontmatter, links, cycles. |
22
- | `isorite --tutorial` | A tour on an example base with a scripted model: no key or network needed. Good to check the install alone. |
22
+ | `isorite guide [<folder>]` | The guide, a knowledge base about isorite, written into `~/isorite-guide` (or the folder named) and served. With no provider set up, Iso is the guide's finder: no key or network needed. Good to check the install alone. `isorite --tutorial` is another name for it. |
23
23
  | `isorite model [--from <folder>]` | Optional: the model for search by meaning. Says which is installed and where, else fetches it (53 MB) from npm, a GitHub release, then Hugging Face, saying why each failed. `--from`: granite's files, or any compatible model (below). |
24
24
  | `isorite help setup` | Prints this. |
25
25
 
@@ -30,6 +30,7 @@ turn off certificate checking (`NODE_TLS_REJECT_UNAUTHORIZED=0`).
30
30
  | `~/.config/isorite/config.toml` (or `$XDG_CONFIG_HOME/isorite/`) | isorite's settings for this machine: `device`, `[provider]`, `[tiers]`, `[prices]`. Mode 600. Holds the key. |
31
31
  | `~/.config/isorite/opencode/` | OpenCode's settings as isorite writes them at each `isorite term` start (`opencode.json`, theme, plugin link). Rewritten; don't edit. |
32
32
  | `~/.config/isorite/opencode/local.json` | The person's own OpenCode settings, laid over isorite's. Never rewritten. |
33
+ | `<notes>/AGENTS.md` | The base's own instructions (notation, level, how notes are named), sent to Iso with every turn and named in OpenCode's `instructions`. Not a note: not on the Atlas, not linted. |
33
34
  | `<notes>/.isorite/` | isorite's own folder in the notes: journals of every change, `settings.toml` (`repository = "<path>"`, where code links point). Made on first open; commit it with the notes. |
34
35
  | `~/.local/share/isorite/models/` (or `$XDG_DATA_HOME/isorite/models/`) | The models for search by meaning, a folder each: `granite-embedding-small-english-r2/`, and any other installed with `--from`. `use` names the one used, if not granite. |
35
36
  | `~/.cache/isorite/vectors-<hash>.json` | Each base's vectors, made by the model; made again when the model changes. |
@@ -40,11 +41,15 @@ isorite never reads or changes the person's own OpenCode settings (`~/.config/op
40
41
 
41
42
  ## config.toml
42
43
 
44
+ The person can set all of this from Iso's settings in the browser (M18: on `localhost` only; checked as `doctor`
45
+ checks it, kept into use without a restart). The page writes the same fields; it drops comments, keeping the file
46
+ as it was once as `config.toml.bak`. Writing it by hand needs the server restarted.
47
+
43
48
  ```toml
44
49
  [provider]
45
50
  name = "work" # optional; what isorite calls the provider in messages
46
51
  url = "https://…/v1" # OpenAI-compatible; /chat/completions and /models are added. Absent: OpenRouter
47
- key = "…" # the key; also settable in the browser (behind the logo)
52
+ key = "…" # the key; also settable in Iso's settings in the browser
48
53
  key_header = "api-key" # send the key in this header, as it is, instead of Authorization: Bearer
49
54
  stream_usage = false # don't send stream_options (some gateways answer 400 to it)
50
55
  max_tokens = 16000 # the least output budget any call gets, for models that think at length
package/docs/setup.md CHANGED
@@ -9,23 +9,37 @@ run `isorite doctor`: it checks each step and says what to change.
9
9
  - **isorite**: `npm i -g isorite`.
10
10
  - For the terminal, **OpenCode 2**: `npm i -g @opencode/cli`.
11
11
 
12
- `isorite --tutorial` now shows you around, on an example base, with a scripted model: no key, nothing sent anywhere.
12
+ `isorite guide` now opens the guide, a knowledge base about isorite with things to do in it. Until a provider is set up, Iso there is the guide's finder: no key, nothing sent anywhere.
13
13
 
14
14
  ## 2. A model provider
15
15
 
16
16
  Iso and the terminal need an OpenAI-compatible provider. isorite's settings are in `~/.config/isorite/config.toml`
17
17
  (or `$XDG_CONFIG_HOME/isorite/config.toml`), a file only you can read.
18
18
 
19
- **OpenRouter** is the default. Open isorite in the browser, and set the key behind the logo (or sign in there with
20
- OpenRouter, which makes one). Nothing else is needed.
19
+ **OpenRouter** is the default. Open isorite in the browser, open Iso's settings (the icon at the right of Iso's
20
+ pane head), and paste a key there, or sign in with OpenRouter, which makes one. Nothing else is needed.
21
21
 
22
- **Any other provider or gateway** is named under `[provider]`, with its models under `[tiers]`:
22
+ **Any other provider or gateway** is set up in the same place: choose *a gateway of your own*, give its address and
23
+ key, and open *what else it asks for* for its key's header, headers, query values, client certificate, and whether
24
+ it reports usage. Each tier's model is picked from the gateway's own list. The pulse icon checks the setup before
25
+ it is kept, as `isorite doctor` would, and says what to change; the tick keeps it, in use at once, without a
26
+ restart. The clipboard icon copies `isorite doctor --report` for someone helping.
27
+
28
+ - The setup is changed only from this machine (`localhost`). From a phone over a tailnet it is shown and checked,
29
+ and the key can be changed, but not the address it goes to.
30
+ - Keeping a new address forgets the key held for the old one, unless a key is given with it.
31
+ - Secrets (the key, the `.pfx`'s password, a header whose name looks secret) are written and never shown again.
32
+ - The page writes `config.toml` again without its comments; the first time, the file as it was is kept beside it
33
+ as `config.toml.bak`.
34
+ - A running `isorite term` keeps the models it started with, but asks through the new address at once.
35
+
36
+ The same setup can be written by hand in `config.toml`, under `[provider]`, with the models under `[tiers]`:
23
37
 
24
38
  ```toml
25
39
  [provider]
26
40
  name = "work" # what isorite calls it
27
41
  url = "https://llm.example.com/v1" # /chat/completions is added to it
28
- key = "…" # or set it behind the logo in the browser
42
+ key = "…" # or set it in Iso's settings in the browser
29
43
 
30
44
  [tiers] # the provider's own names for its models
31
45
  low = "small-model" # quick replies
@@ -88,6 +102,10 @@ isorite serve ~/notes/my-project
88
102
  `isorite new` makes the folder, empty but for isorite's own `.isorite/`. A folder of notes you have already is
89
103
  opened as it is, with `isorite serve`.
90
104
 
105
+ How you want the base kept (your notation, the level, the language, how notes are named) goes in `AGENTS.md` at
106
+ the top of the notes, which Iso and the terminal's agent are both given. Edit it in Iso's settings, the icon at the
107
+ right of its pane's header, or in any editor. Iso may offer a line for it when you say how you want things done.
108
+
91
109
  It prints a link to click: `http://localhost:4767/`. isorite keeps what it needs in the folder's `.isorite/`.
92
110
 
93
111
  ## 4. The terminal
package/docs/terminal.md CHANGED
@@ -72,7 +72,12 @@ terminal's session:
72
72
  - **Notes the agent changes** are written at once, under its name. Turn on provenance in the Note pane's header to
73
73
  see the agent's words marked, and the timeline to put a note back.
74
74
 
75
- With both off, the pane is Iso's own again.
75
+ With both off, the pane is Iso's own again. The ≡ in its header, or a click on its gauge, opens what Iso is sent:
76
+ a row for each part, with its size. These are the notes you gave and the notes Iso read, the conversation's turns,
77
+ and what is always sent. Iso keeps a note it read in hand, and sets it aside after three turns unused, as a line it
78
+ can read again. A click switches a row on or off. At the head of the conversation, one icon sums the earlier turns
79
+ up into a briefing sent instead of them, and another starts a new conversation from that briefing. Iso sums up on
80
+ its own only when a turn would be more than the model takes.
76
81
 
77
82
  ## The models
78
83
 
@@ -89,6 +94,9 @@ OpenCode doesn't know a gateway's prices, so Iso's pane shows the session's toke
89
94
 
90
95
  ## OpenCode's settings
91
96
 
97
+ OpenCode is given the base's `AGENTS.md` (in its `instructions`), when `isorite term` is named the notes, and the
98
+ knowledge base's ways through `brief_base`: how a note is written and linked, and how the base is kept.
99
+
92
100
  isorite keeps OpenCode's settings in `~/.config/isorite/opencode/`, rewritten at each start, so your own OpenCode
93
101
  settings are never read or changed. Anything of your own goes in `~/.config/isorite/opencode/local.json`, which is
94
102
  never rewritten, and is laid over what isorite writes. For example, to have OpenCode ask before every command (by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isorite",
3
- "version": "0.3.1",
3
+ "version": "0.6.0",
4
4
  "description": "An agentic harness for understanding: your notes as a knowledge base, an Atlas of them, an agent that reads them, and study.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/plugin/tui.tsx CHANGED
@@ -5,7 +5,7 @@
5
5
  import { spawn } from "node:child_process";
6
6
  import { BoxRenderable, fg, ImageRenderable, RGBA, StyledText, TextAttributes, TextRenderable, type Renderable, type TextChunk } from "@opentui/core";
7
7
  import { Plugin } from "@opencode/plugin/tui";
8
- import { createEffect, createResource, createSignal, For, onCleanup, Show, untrack } from "solid-js";
8
+ import { createEffect, createResource, createSignal, For, onCleanup, Show } from "solid-js";
9
9
  import type { ScrollBoxRenderable } from "@opentui/core";
10
10
  import { ago, anchorOf, blocks, describe, momentKey, moments, reach, type Entry, type MapModel } from "../dist/core.js";
11
11
  import { drawAtlas, runsOf, type Role } from "./oneatlas.ts";
@@ -15,7 +15,6 @@ import * as Isorite from "./isorite.ts";
15
15
  import * as Logo from "./logo.ts";
16
16
  import { lapis, quartz, routeColours } from "./palette.ts";
17
17
  import { Play, type Step } from "./play.ts";
18
- import { TOUR, type TourState } from "./tour.ts";
19
18
  import { parseSources } from "./sources.ts";
20
19
  import * as Wordmark from "./wordmark.ts";
21
20
 
@@ -265,9 +264,6 @@ export default Plugin.define({
265
264
  </text>
266
265
  ),
267
266
  });
268
- // What the tour (isorite --tutorial) waits on that nothing else counts: the agent's turns ended, Iso's answers,
269
- // and notes sent from here.
270
- const [turns, setTurns] = createSignal(0), [isoAnswers, setIsoAnswers] = createSignal(0), [sent, setSent] = createSignal(0);
271
267
 
272
268
  /** [s]: a note, or a passage of it, sent to the agent from the terminal, as from the browser: under "from you". */
273
269
  const sendToAgent = async (note: string, passage?: string) => {
@@ -275,7 +271,7 @@ export default Plugin.define({
275
271
  const said = await ctx.ui.dialog.prompt({ title: "Send to the agent", description: passage ? "the passage the cursor is on" : `the note “${(titles().get(note) ?? note).toLowerCase()}”`, placeholder: "what you want of it (optional)" });
276
272
  if (said === undefined) return;
277
273
  await Isorite.post("/api/agent/send", { note, ...(passage ? { passage } : {}), said: said.trim() })
278
- .then(() => setSent((n) => n + 1), (e: Error) => ctx.ui.toast?.show?.({ message: e.message, variant: "error" }));
274
+ .catch((e: Error) => ctx.ui.toast?.show?.({ message: e.message, variant: "error" }));
279
275
  };
280
276
 
281
277
  const stop = new AbortController();
@@ -293,8 +289,7 @@ export default Plugin.define({
293
289
  if (sessionID) void deliver(sessionID);
294
290
  return;
295
291
  }
296
- if (type === "agent-done" && typeof data.session === "string") { reading.delete(data.session); hear((n) => n + 1); setTurns((n) => n + 1); }
297
- if (type === "iso-done") setIsoAnswers((n) => n + 1);
292
+ if (type === "agent-done" && typeof data.session === "string") { reading.delete(data.session); hear((n) => n + 1); }
298
293
  if (type === "note-changed") bumpMap((n) => n + 1);
299
294
  if (type !== "agent-step" || data.agent !== Isorite.AGENT || typeof data.session !== "string") return;
300
295
  const play = plays.get(data.session) ?? new Play();
@@ -324,75 +319,6 @@ export default Plugin.define({
324
319
  return new TextRenderable(ctx.renderer, { content: new StyledText(chunks) });
325
320
  });
326
321
 
327
- // ------------------------------------------------------------ the tour (isorite --tutorial)
328
- const TUTORIAL = process.env.ISORITE_TUTORIAL === "1";
329
- const [stepAt, setStepAt] = createSignal(TUTORIAL ? 0 : TOUR.length);
330
- const [cardShown, setCardShown] = createSignal(true);
331
- const tourState = (): TourState => {
332
- heard();
333
- const route = ctx.ui.router.current(), shown = note();
334
- return {
335
- page: route.type === "plugin" ? (route.name === SPLASH ? "splash" : route.name === NOTE ? "note" : route.name) : route.type,
336
- turns: turns(),
337
- inPlay: route.type === "session" ? plays.get(route.sessionID)?.size ?? 0 : 0,
338
- atlasOpen: ctx.ui.panel.current()?.name === ATLAS,
339
- note: shownNote(),
340
- // Not known until the note is read: no step moves on for a note still opening.
341
- waiting: shown && shown.id === shownNote() ? shown.items.filter((i) => i.type === "change").length : -1,
342
- past: moment() !== null,
343
- isoAnswers: isoAnswers(),
344
- sent: sent(),
345
- };
346
- };
347
- /** Puts the step's prompt in the prompt being typed in, once it has focus and while it is empty. */
348
- const fillPrompt = (text: string) => {
349
- let tries = 0;
350
- const timer = setInterval(() => {
351
- const editor = ctx.renderer.currentFocusedEditor;
352
- if (editor && !editor.plainText.trim()) editor.insertText(text);
353
- if (editor || ++tries > 50) clearInterval(timer);
354
- }, 100);
355
- };
356
- ctx.ui.slot({
357
- append: "app",
358
- render() {
359
- if (!TUTORIAL) return null;
360
- // A step moves on when what it waits for holds, judged against what held when it began.
361
- let at = -1, began = untrack(tourState);
362
- createEffect(() => {
363
- const i = stepAt(), now = tourState(), step = TOUR[i];
364
- if (i !== at) { at = i; began = now; if (step?.fill) fillPrompt(step.fill); return; }
365
- if (step?.until?.(now, began)) setStepAt(i + 1);
366
- });
367
- ctx.keymap.layer(() => ({
368
- mode: "global",
369
- commands: [
370
- { id: "isorite.tour.next", title: "Tour: the next step", palette: true, slash: { name: "next" }, run: () => { setStepAt((i) => Math.min(i + 1, TOUR.length)); } },
371
- { id: "isorite.tour.card", title: "Tour: hide or show the card", palette: true, slash: { name: "tour" }, run: () => { setCardShown((v) => !v); } },
372
- ],
373
- }));
374
- return (
375
- <Show when={cardShown() && TOUR[stepAt()]}>
376
- {(step) => (
377
- <box position="absolute" right={2} bottom={4} width={48} zIndex={3000} flexDirection="column" paddingLeft={1} paddingRight={1}
378
- border borderStyle="rounded" borderColor={ground().growth} backgroundColor={ground().g0} title={` tour · ${stepAt() + 1} of ${TOUR.length} `}>
379
- <text fg={ground().text} wrapMode="word"><b>{step().title}</b></text>
380
- <text fg={ground().soft} wrapMode="word">{step().text}</text>
381
- <text> </text>
382
- <box flexDirection="row">
383
- <text wrapMode="none">
384
- <For each={TOUR}>{(_, k) => <span style={{ fg: k() <= stepAt() ? ground().growth : ground().line }}>{k() <= stepAt() ? "■" : "□"}</span>}</For>
385
- </text>
386
- <box flexGrow={1} />
387
- <text fg={ground().faint} wrapMode="none">{step().until ? "/tour hides" : "/next /tour hides"}</text>
388
- </box>
389
- </box>
390
- )}
391
- </Show>
392
- );
393
- },
394
- });
395
-
396
322
  ctx.ui.slot({
397
323
  append: "sidebar.content",
398
324
  render: (input) => {