pi-weave 0.1.23 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -24
- package/package.json +1 -1
- package/src/core/index.ts +1 -0
- package/src/core/links/repair.ts +2 -2
- package/src/core/vault.ts +46 -1
- package/src/web/client/api.ts +6 -0
- package/src/web/client/dist/app.js +107 -83
- package/src/web/client/note/Note.tsx +139 -14
- package/src/web/client/note/note.model.ts +51 -0
- package/src/web/client/shell/Columns.tsx +12 -1
- package/src/web/client/shell/Shell.tsx +56 -5
- package/src/web/client/shell/theme.ts +24 -0
- package/src/web/client/tree/Tree.tsx +11 -1
- package/src/web/server/routes.ts +18 -6
package/README.md
CHANGED
|
@@ -11,24 +11,56 @@
|
|
|
11
11
|
<a href="./LICENSE"><img alt="license: MIT" src="https://img.shields.io/npm/l/pi-weave?color=green"></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**Your notes. Your agent's notes. One searchable knowledge workspace.**
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
I've been chasing the right note-taking workflow for years. Notion, OneNote, Obsidian, and plenty of others.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
With pi-weave, I've made one that finally answers the way I want to work today — especially now that AI agents are part of that work.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The idea is simple:
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
- **Verbatim narration with structured summaries.** During dictation, Pi preserves each spoken passage in an append-only `## Raw` section while maintaining an organized summary above it.
|
|
24
|
-
- **Knowledge retrieval.** Pi searches existing notes when answering questions about previous decisions, people, projects, or meetings.
|
|
25
|
-
- **Unified visual workspace.** `/weave-view` presents notes, links, repository structure, and provenance in a live browser interface.
|
|
26
|
-
- **Optional session memory.** `/weave-scan sessions` turns changed pi transcripts into searchable notes with reusable technical takeaways.
|
|
27
|
-
- **Repository exploration.** A lightweight, git-aware index gives Pi a structural overview of the current codebase before it reads files.
|
|
22
|
+
**You and your agent should share the same knowledge.**
|
|
28
23
|
|
|
29
|
-
|
|
24
|
+
Talk naturally and let your thoughts become structured notes. Write notes yourself. Let your agent capture decisions, discoveries, and
|
|
25
|
+
useful context. Bring important knowledge forward from previous sessions.
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
Then find it again — by searching the workspace yourself, or simply asking your agent.
|
|
28
|
+
|
|
29
|
+
Everything is local. Everything is readable. Your notes are ordinary files, not knowledge trapped inside another chat.
|
|
30
|
+
|
|
31
|
+
## Think out loud
|
|
32
|
+
|
|
33
|
+
Dictation is one of my favorite ways to use AI.
|
|
34
|
+
|
|
35
|
+
Instead of stopping to organize every thought while I'm having it, I can just talk:
|
|
36
|
+
|
|
37
|
+
> Start a note about pi-weave positioning. I'm going to think out loud.
|
|
38
|
+
|
|
39
|
+
pi-weave keeps what I actually said while Pi continuously turns it into a useful, structured note.
|
|
40
|
+
|
|
41
|
+
The raw thought stays yours. The organization is handled for you.
|
|
42
|
+
|
|
43
|
+
And it doesn't end with human notes.
|
|
44
|
+
|
|
45
|
+
## Your knowledge + your agent's knowledge
|
|
46
|
+
|
|
47
|
+
pi-weave gives humans and agents one workspace.
|
|
48
|
+
|
|
49
|
+
A note you dictate, something you write manually, a useful discovery made by an agent, or knowledge recovered from an earlier session can
|
|
50
|
+
all live together.
|
|
51
|
+
|
|
52
|
+
pi-weave keeps track of where knowledge came from, while making the whole workspace searchable by both you and your agent.
|
|
53
|
+
|
|
54
|
+
**The conversation can end. The knowledge doesn't have to.**
|
|
55
|
+
|
|
56
|
+
## Built on Pi
|
|
57
|
+
|
|
58
|
+
pi-weave is built as an extension for [Pi](https://github.com/earendil-works/pi).
|
|
59
|
+
|
|
60
|
+
I've grown to love Pi precisely because it is such a lightweight agent harness. It gives the model tools and context without trying to
|
|
61
|
+
become the product itself, and its extensibility makes projects like pi-weave possible.
|
|
62
|
+
|
|
63
|
+
Install pi-weave:
|
|
32
64
|
|
|
33
65
|
```bash
|
|
34
66
|
pi install npm:pi-weave
|
|
@@ -43,6 +75,8 @@ pi install /path/to/pi-weave
|
|
|
43
75
|
|
|
44
76
|
Requires Node **20.13 or newer**.
|
|
45
77
|
|
|
78
|
+
Then just talk to Pi.
|
|
79
|
+
|
|
46
80
|
## Start taking notes
|
|
47
81
|
|
|
48
82
|
Talk to Pi normally:
|
|
@@ -71,7 +105,8 @@ For each chunk, Pi:
|
|
|
71
105
|
2. refreshes the structured summary above it;
|
|
72
106
|
3. leaves the raw record untouched.
|
|
73
107
|
|
|
74
|
-
This makes the note readable during the conversation without replacing your words with an AI reconstruction. Notes based on your dictation
|
|
108
|
+
This makes the note readable during the conversation without replacing your words with an AI reconstruction. Notes based on your dictation
|
|
109
|
+
remain marked `source: human`; notes drafted by Pi are marked `source: agent`.
|
|
75
110
|
|
|
76
111
|
Useful requests include:
|
|
77
112
|
|
|
@@ -86,9 +121,9 @@ Useful requests include:
|
|
|
86
121
|
|
|
87
122
|
## Keeping the vault connected
|
|
88
123
|
|
|
89
|
-
A wiki-link resolves to nothing when it is written as a bare title — `[[Quarterly Roadmap]]` when the note lives at
|
|
90
|
-
|
|
91
|
-
|
|
124
|
+
A wiki-link resolves to nothing when it is written as a bare title — `[[Quarterly Roadmap]]` when the note lives at `planning/roadmap-2026`
|
|
125
|
+
— or when it points at a note that was never written. Pi repairs those in one deterministic pass instead of rereading the vault and
|
|
126
|
+
guessing.
|
|
92
127
|
|
|
93
128
|
```text
|
|
94
129
|
You: Check the links in my notes.
|
|
@@ -112,13 +147,19 @@ Renaming or moving a note rewrites its inbound links automatically, so its backl
|
|
|
112
147
|
The browser workspace has four connected views:
|
|
113
148
|
|
|
114
149
|
- **Tree** — notes, folders, and repository structure, with text and provenance filters.
|
|
115
|
-
- **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, authorship, and an action to open the
|
|
150
|
+
- **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, authorship, click-to-edit, and an action to open the
|
|
151
|
+
source in `$EDITOR`.
|
|
116
152
|
- **Graph** — a navigable map of notes, links, mentions, modules, and repository relationships. Selecting something updates every view.
|
|
117
153
|
- **Context** — links, backlinks, tags, and code mentions for the current selection.
|
|
118
154
|
|
|
119
|
-
Search with `⌘K` / `Ctrl K`. Press `?` for all shortcuts. The workspace updates as notes change on disk, so a note written by Pi appears
|
|
155
|
+
Search with `⌘K` / `Ctrl K`. Press `?` for all shortcuts. The workspace updates as notes change on disk, so a note written by Pi appears
|
|
156
|
+
without a reload. It follows the system theme by default and can be switched between light and dark.
|
|
120
157
|
|
|
121
|
-
|
|
158
|
+
**Click the note text to edit it** — the prose swaps for its Markdown source; `⌘S` / `Ctrl S` saves, `Esc` or **Done** closes. Selecting
|
|
159
|
+
text to copy and clicking a `[[wikilink]]` do what they always did; neither opens the editor. A save rewrites the body and nothing
|
|
160
|
+
else — unknown front-matter fields, key order, and the append-only `## Raw` tail of a dictated note all survive untouched, so a note edited
|
|
161
|
+
here and a note edited in Obsidian stay byte-compatible. Editing with `$EDITOR`, Obsidian, or the `weave_note` tool works as before; the
|
|
162
|
+
last writer wins, and the workspace picks up outside changes within a couple of seconds.
|
|
122
163
|
|
|
123
164
|
`/weave-view tui` is the smaller, read-only terminal explorer: tree, focused neighborhood, details, and link health over the same graph.
|
|
124
165
|
|
|
@@ -129,7 +170,10 @@ The browser is read-only. Edit with `$EDITOR`, Obsidian, or the `weave_note` too
|
|
|
129
170
|
/weave-scan sessions /path/to/history # explicit history root
|
|
130
171
|
```
|
|
131
172
|
|
|
132
|
-
This opt-in scan treats a supplied file—or every bounded text file under a supplied directory—as opaque session material for the active
|
|
173
|
+
This opt-in scan treats a supplied file—or every bounded text file under a supplied directory—as opaque session material for the active
|
|
174
|
+
model to interpret, then writes generated notes under `~/.okf/notes/sessions/`. That makes it usable with Claude Code, opencode, Codex, or
|
|
175
|
+
exported history trees without requiring their schema or file extension. It skips unchanged files, captures outcomes plus reusable technical
|
|
176
|
+
takeaways, works outside Git repositories, and can be stopped with `/weave-scan-cancel`.
|
|
133
177
|
|
|
134
178
|
## Repository knowledge
|
|
135
179
|
|
|
@@ -140,9 +184,11 @@ Inside a Git repository, pi-weave detects whether `<repo>/.okf/` is missing, fre
|
|
|
140
184
|
/weave-scan deep # also summarize changed files with the active model
|
|
141
185
|
```
|
|
142
186
|
|
|
143
|
-
The light index covers languages, packages, modules, entry points, and Git state. A deep scan adds short per-file summaries and only
|
|
187
|
+
The light index covers languages, packages, modules, entry points, and Git state. A deep scan adds short per-file summaries and only
|
|
188
|
+
revisits files whose content changed.
|
|
144
189
|
|
|
145
|
-
The repository index is a cache, not a source of truth. Delete `.okf`, scan again, and nothing important is lost. pi-weave excludes it
|
|
190
|
+
The repository index is a cache, not a source of truth. Delete `.okf`, scan again, and nothing important is lost. pi-weave excludes it
|
|
191
|
+
locally from Git by default.
|
|
146
192
|
|
|
147
193
|
## Commands and tools
|
|
148
194
|
|
|
@@ -199,7 +245,9 @@ We probably want OIDC next quarter…
|
|
|
199
245
|
|
|
200
246
|
Set `PI_WEAVE_VAULT` to use a different vault location.
|
|
201
247
|
|
|
202
|
-
Reading, writing, searching, and viewing notes are local operations. Deep repository scans and session summaries send bounded input to
|
|
248
|
+
Reading, writing, searching, and viewing notes are local operations. Deep repository scans and session summaries send bounded input to
|
|
249
|
+
whichever model you configured in pi. The browser workspace binds only to loopback, uses a per-session token, and shuts down with the pi
|
|
250
|
+
session.
|
|
203
251
|
|
|
204
252
|
The vault format, repository index, and skills are intentionally harness-agnostic. `src/core` contains no pi-specific imports.
|
|
205
253
|
|
|
@@ -219,7 +267,8 @@ npm run coverage
|
|
|
219
267
|
npm run build:web
|
|
220
268
|
```
|
|
221
269
|
|
|
222
|
-
Coverage must remain at or above **95%** for lines, branches, functions, and statements. If browser source changes, rebuild and commit
|
|
270
|
+
Coverage must remain at or above **95%** for lines, branches, functions, and statements. If browser source changes, rebuild and commit
|
|
271
|
+
`src/web/client/dist/app.js`.
|
|
223
272
|
|
|
224
273
|
Read [AGENTS.md](AGENTS.md) before contributing. Work on a feature branch; do not commit directly to `main`.
|
|
225
274
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-weave",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "An agent-native knowledge workspace for your life and your code. Smart notepad + repository exploration, readable by humans and agents alike.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
package/src/core/index.ts
CHANGED
package/src/core/links/repair.ts
CHANGED
|
@@ -99,7 +99,7 @@ const RAW_HEADING_RE = new RegExp(`^${RAW_NOTES_HEADING}[ \\t]*\\r?$`, "m");
|
|
|
99
99
|
* inside a ```` block would too — and each mistake ends the protected region
|
|
100
100
|
* early, exposing example links in documentation to rewriting.
|
|
101
101
|
*/
|
|
102
|
-
function fenceRanges(body: string): [number, number][] {
|
|
102
|
+
export function fenceRanges(body: string): [number, number][] {
|
|
103
103
|
const ranges: [number, number][] = [];
|
|
104
104
|
let open: { at: number; marker: string; length: number } | null = null;
|
|
105
105
|
for (const match of body.matchAll(FENCE_RE)) {
|
|
@@ -134,7 +134,7 @@ function fenceRanges(body: string): [number, number][] {
|
|
|
134
134
|
* refuse to repair — every link below it. Matching a whole heading line also
|
|
135
135
|
* keeps `## Rawhide` from starting a tail.
|
|
136
136
|
*/
|
|
137
|
-
function rawTailStart(body: string, fences: readonly [number, number][]): number {
|
|
137
|
+
export function rawTailStart(body: string, fences: readonly [number, number][]): number {
|
|
138
138
|
let from = 0;
|
|
139
139
|
for (;;) {
|
|
140
140
|
const rest = body.slice(from);
|
package/src/core/vault.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
unquoteField,
|
|
11
11
|
upsertFrontMatterFields,
|
|
12
12
|
} from "./frontmatter";
|
|
13
|
-
import { auditLinks, rewriteLinks, RAW_NOTES_HEADING, type LinkAudit, type LinkFix } from "./links/repair";
|
|
13
|
+
import { auditLinks, fenceRanges, rawTailStart, rewriteLinks, RAW_NOTES_HEADING, type LinkAudit, type LinkFix } from "./links/repair";
|
|
14
14
|
import { withMutationQueue } from "./mutex";
|
|
15
15
|
import { NOTES_DIR, OKF_MANIFEST } from "./paths";
|
|
16
16
|
import { slugify, uniqueSlug } from "./slug";
|
|
@@ -596,6 +596,51 @@ export async function repairVaultLinks(root: string, options: { apply?: boolean
|
|
|
596
596
|
});
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Keep the on-disk `## Raw` tail whatever the body says — it is append-only.
|
|
601
|
+
* `rawTailStart`, not `extractRawTail`: fence-aware, for untrusted input.
|
|
602
|
+
*/
|
|
603
|
+
function preserveRawTail(currentBody: string, nextBody: string): string {
|
|
604
|
+
const tail = currentBody.slice(tailBoundary(currentBody)).trimEnd();
|
|
605
|
+
const head = nextBody.slice(0, tailBoundary(nextBody)).trim();
|
|
606
|
+
if (tail === "") return head;
|
|
607
|
+
// An emptied head leaves the tail alone, not a leading blank line.
|
|
608
|
+
return head === "" ? tail : `${head}\n\n${tail}`;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** A `---` rule, alone on the line, with only blank space after it. */
|
|
612
|
+
const TAIL_RULE_RE = /(?:^|\n)-{3,}[ \t]*\r?\n\s*$/;
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Where the tail's text begins — the `---` rule, not the heading
|
|
616
|
+
* `rawTailStart` returns. Splitting at the heading orphans the rule.
|
|
617
|
+
*/
|
|
618
|
+
function tailBoundary(body: string): number {
|
|
619
|
+
const heading = rawTailStart(body, fenceRanges(body));
|
|
620
|
+
if (heading === body.length) return heading;
|
|
621
|
+
const rule = TAIL_RULE_RE.exec(body.slice(0, heading));
|
|
622
|
+
if (rule === null) return heading;
|
|
623
|
+
// `index` is the newline before the rule when there is one; step over it.
|
|
624
|
+
return rule.index === 0 && !body.startsWith("\n") ? 0 : rule.index + 1;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Replace a note's Markdown body; unknown front matter rides on
|
|
629
|
+
* `note.frontMatter` and only `updated` moves. Last write wins — no
|
|
630
|
+
* `expectedRevision`; docs/weave-workspace.md §11 P5.3 argues the trade.
|
|
631
|
+
*/
|
|
632
|
+
export async function setNoteBody(root: string, slug: string, body: string, now = new Date()): Promise<VaultMutationResult> {
|
|
633
|
+
const path = resolveNotePath(root, slug);
|
|
634
|
+
if (path === null) return { ok: false, reason: "invalid" };
|
|
635
|
+
return withVaultLock(root, async () => {
|
|
636
|
+
const note = await getNote(root, slug);
|
|
637
|
+
if (note === null) return { ok: false, reason: "missing" };
|
|
638
|
+
const next = preserveRawTail(note.body, body);
|
|
639
|
+
await writeNote(path, slug, { ...note, updated: now.toISOString() }, next, note.frontMatter);
|
|
640
|
+
return { ok: true, slug };
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
599
644
|
/** Rename a note in place and keep its front-matter title in sync. */
|
|
600
645
|
export async function renameNote(root: string, slug: string, name: string, now = new Date()): Promise<VaultMutationResult> {
|
|
601
646
|
const from = resolveNotePath(root, slug);
|
package/src/web/client/api.ts
CHANGED
|
@@ -323,6 +323,12 @@ function mutation(fetchImpl: FetchLike, url: string, method: string, body?: unkn
|
|
|
323
323
|
const noteUrl = (slug: string, suffix = ""): string => `/api/note/${encodeURIComponent(slug)}${suffix}`;
|
|
324
324
|
const folderUrl = (path: string, suffix = ""): string => `/api/folder/${path.split("/").map(encodeURIComponent).join("/")}${suffix}`;
|
|
325
325
|
|
|
326
|
+
/**
|
|
327
|
+
* `POST /api/note/:slug` — save the note's Markdown body. No revision
|
|
328
|
+
* (last-write-wins, see core's `setNoteBody`), and a `MutationResult` rather
|
|
329
|
+
* than the note: the poll is what puts the new text back on screen.
|
|
330
|
+
*/
|
|
331
|
+
export const saveNote = (fetchImpl: FetchLike, slug: string, body: string) => mutation(fetchImpl, noteUrl(slug), "POST", { body });
|
|
326
332
|
export const renameNote = (fetchImpl: FetchLike, slug: string, name: string) => mutation(fetchImpl, noteUrl(slug, "/rename"), "POST", { name });
|
|
327
333
|
export const moveNote = (fetchImpl: FetchLike, slug: string, folder: string | null) => mutation(fetchImpl, noteUrl(slug, "/move"), "POST", { folder });
|
|
328
334
|
export const deleteNote = (fetchImpl: FetchLike, slug: string) => mutation(fetchImpl, noteUrl(slug), "DELETE");
|