noggin-cli 0.4.2 → 0.4.3

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 (2) hide show
  1. package/README.md +35 -451
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,469 +1,53 @@
1
1
  # noggin
2
2
 
3
- A small, single-user working-memory tree for in-flight work — your
4
- second brain for the stuff you can't fit in your head.
3
+ A working-memory tree for in-flight work — your second brain for
4
+ the stuff you can't fit in your head.
5
5
 
6
- Lives in `~/.noggin.yaml` by default. Override per call with `--file
7
- <path>`, or set `$NOGGIN` to point every invocation at a
8
- different file. (The VS Code extension sets `NOGGIN` in its
9
- terminals so the CLI follows whichever noggin you have open.) Driven
10
- by [`noggin.mjs`](noggin.mjs) next to this file. The YAML file is the source
11
- of truth; the CLI is the only sanctioned way to read or write it.
12
-
13
- For the agent-facing behavioral instructions, see [SKILL.md](SKILL.md).
14
- This document is the human reference: what noggin is, what the
15
- commands do, how the file is shaped.
16
-
17
- ## Mental model
18
-
19
- Items form a tree. There is at most one **active** item; the path
6
+ Items form a tree. There's at most one **active** item; the path
20
7
  from a root to the active item is your current spine. Other open
21
- items are paused work you started but stepped away from. Done
22
- items stay in the tree under their parent so you can see what got
23
- finished. Use `edit --open` if it turns out something was not
24
- really finished.
25
-
26
- An item and a "todo" are the same thing at different lifecycle stages:
27
-
28
- - **push** = create a child of active and immediately become it
29
- ("I'm going to do this now").
30
- - **add** = create a child of active without becoming it (a deferred
31
- task; same shape, just never activated).
32
-
33
- You can later `goto` an added child to make it active, or just `done`
34
- it without ever activating.
35
-
36
- ### What an item carries
37
-
38
- Just the things that are about an item *being an item* in the tree:
39
-
40
- - a **title** (one line)
41
- - a **done** flag and a `createdAt` timestamp
42
- - append-only timestamped **notes** — anything you want to remember,
43
- including a system-generated `closed` note appended whenever the item
44
- transitions from open to done (the note's timestamp records when)
45
-
46
- There is **no fixed schema** for things like "why," "where," "what's
47
- next," tags, or resolution. If it matters, drop a `note`. The CLI
48
- stays focused on tree shape and lifecycle; everything else is content
49
- the user (or an agent) writes in note text.
50
-
51
- ## Identifiers and paths
52
-
53
- - **key** — an opaque, stable internal identifier (e.g.
54
- `i-20260616-184644-f04bf5`). Used in the YAML file for parent links
55
- and the active pointer. Hidden from human output; included in JSON
56
- output when you want to inspect it.
57
- - **position** — a computed 1-based index among siblings. Shown in
58
- brackets in human output, e.g. `[2]`.
59
- - **path** — how you refer to items on the command line.
60
-
61
- ### Path syntax
62
-
63
- The leading `/` is the unambiguous marker that separates the two
64
- families of paths.
65
-
66
- **Absolute** paths start with `/` and walk from a root. This is the
67
- canonical form used everywhere the API or human output reports a
68
- path (`activePath`, `ItemView.path`, `parentPath`, error messages).
69
-
70
- ```
71
- "/1/2/3"
72
- │ │ │
73
- │ │ └── third child of "/1/2"
74
- │ └──── second child of root "/1"
75
- └────── first root item
76
- ```
77
-
78
- **Relative** paths are everything else, resolved against the active
79
- item (file-system style):
80
-
81
- | Token | Meaning |
82
- |---|---|
83
- | `.` | active item |
84
- | `..` | parent of active |
85
- | `-` | previous sibling of active |
86
- | `+` | next sibling of active |
87
- | `./X/Y` | descendant of active |
88
- | `../X` | sibling of active (child X of parent) |
89
- | `-/X/Y` | descendant under the previous sibling |
90
- | `+/X/Y` | descendant under the next sibling |
91
- | `../../X` | walk up two and then down |
92
- | `X` / `X/Y` | bare positions are short for `./X` / `./X/Y` |
93
-
94
- Relative paths require an active item. If none is set, pass an
95
- absolute path instead (e.g. `noggin show /1` rather than `noggin
96
- show 1`).
97
-
98
- Paths are coordinates into the current tree order — they are
99
- intended for immediate interactive use, not long-term bookmarks.
100
- Stable identity lives in `key` and `parentKey`.
101
-
102
- ## Command reference
103
-
104
- Every command takes:
105
-
106
- - `--file <path>` — override the file resolution (highest priority).
107
- - `--json` — emit structured JSON instead of the human tree view.
108
- - `--with-json` — human output followed by JSON.
109
-
110
- The file is resolved in this order:
111
-
112
- 1. `--file <path>`
113
- 2. `$NOGGIN` environment variable
114
- 3. `~/.noggin.yaml`
115
-
116
- Use `noggin where` at any time to print the canonical location of
117
- the noggin currently in use (a round-trippable string like
118
- `~/.noggin.yaml`, `./.noggin.yaml`, or an absolute path).
119
- why.
120
-
121
- Commands that change or inspect a target also take `--goto [path]`.
122
- With no path, `--goto` activates the command's target; with a path,
123
- the path resolves from the command target (not from the previously
124
- active item).
125
-
126
- Common flags can appear before or after the verb.
127
-
128
- | Verb | Effect |
129
- |---|---|
130
- | `push <title>` | Create a child of active and make it active. |
131
- | `add <title> [--before\|--after\|--into <path>] [--goto [path]]` | Create a child of active by default. `--before <path>` / `--after <path>` insert as a sibling of the anchor; `--into <path>` makes it the last child of the anchor. Active does **not** change unless `--goto` is present. |
132
- | `move [<path>] (--before\|--after\|--into <path>) [--goto [path]]` | Relocate an item. Default target is the active item. Exactly one of `--before` / `--after` / `--into` is required. Active is preserved by key, so the computed path may change but `📍` stays on the same item. Cycles (anchor in the moved subtree) are rejected. |
133
- | `goto <path>` | Make the item at `<path>` active. |
134
- | `done [<path>] [--force\|--close-all]` | Mark an item done, then make the target's parent active. Idempotent (no error if already done). Refuses if open descendants exist unless `--close-all` first closes them or `--force` closes just the target anyway. Root items leave no active item after completion. |
135
- | `pop [--force\|--close-all]` | Shorthand for `done` on the active item (no path). Honors `--force` and `--close-all` the same way. |
136
- | `edit [<path>] [--done\|--open] [--title T] [--force\|--close-all] [--goto [path]]` | Idempotent mutation of a single item's lifecycle state and/or title. Pass at least one of `--done` / `--open` / `--title`. Active is unchanged unless `--goto` is passed. When closing (`--done`), the same open-descendant rules apply as `done`: `--force` closes anyway, `--close-all` closes descendants first. Replaces the older `set-state` and `retitle` verbs. |
137
- | `show [<path>] [--no-children\|--with-descendants] [--with-siblings] [--with-all] [--with-notes] [--goto [path]]` | Current-position view: ancestor spine, sibling peers, current-item details, and first-level children. Default target is active. `--no-children` omits children. `--with-siblings` also includes the full sibling row at every ancestor depth (sibling subtrees stay collapsed). `--with-descendants` expands the target's subtree recursively. `--with-all` = `--with-siblings --with-descendants`. `--with-notes` appends note bodies. `--no-children` and `--with-descendants` are mutually exclusive. |
138
- | `note [<path>] <text…> [--goto [path]]` | Append a timestamped note. |
139
- | `delete <path> [--recursive]` | Remove an item. Refuses if the item has descendants unless `--recursive` is passed, in which case the whole subtree is deleted. If the active item is inside the deleted subtree, active falls back to the deleted item's parent (or becomes empty if it was a root). |
140
- | `where` | Print the canonical location string of the noggin in use. |
141
- | `copy <from> <to>` | Append every item from the `<from>` noggin into the `<to>` noggin. Whole-noggin, append-only. Source roots become new roots of dest, after any existing dest content. Keys are regenerated; notes, done state, and `createdAt` are preserved verbatim. Source is not modified. Dest's `active` is unchanged. v1 copies the entire source; subtree slicing is reserved for a later version. |
142
- | `help` | Print full help. |
143
-
144
- ### Tree output
145
-
146
- Each row is `<absolute-path> <state> title <notes>`, with three
147
- optional indicator slots:
148
-
149
- - `📍` (between path and title) — this is the active item
150
- - `✅` (between path and title) — done
151
- - `✏️` (trailing) — has notes
152
-
153
- Every row leads with the item's absolute path (`/1/3` etc.) so each
154
- row self-describes — ancestors on the spine still read clearly even
155
- though their siblings are trimmed from the view.
156
-
157
- `show` keeps note bodies collapsed by default; pass `--with-notes` to
158
- append them after the tree.
159
-
160
- ### JSON output
161
-
162
- `--json` and `--with-json` emit a stable response envelope shared with
163
- the VS Code extension's language-model tools, so a single consumer can
164
- target both surfaces.
165
-
166
- ```jsonc
167
- // success
168
- {
169
- "status": "ok",
170
- "envelopeVersion": 3, // RESPONSE_ENVELOPE_VERSION — bump on breaking changes
171
- "verb": "push", // command that produced this payload
172
- "data": { … } // verb-specific (CurrentTreeView, DeleteResult, …)
173
- }
174
-
175
- // error (written to stderr; exit code matches error.exitCode)
176
- {
177
- "status": "error",
178
- "envelopeVersion": 3,
179
- "verb": "push",
180
- "error": { "code": "title-required", "message": "…", "exitCode": 2 }
181
- }
182
- ```
183
-
184
- `envelopeVersion` versions the wrapper shape (and the per-verb
185
- payloads inside `data`), independently of the on-disk document's
186
- `schemaVersion` (see [File schema](#file-schema-v1)). The two rev
187
- on different cadences.
188
-
189
- Inside `data`, a small whitelist of fields whose value matches their
190
- declared default is **omitted** to keep payloads focused. A consumer
191
- that doesn't see one of these fields should treat it as the default:
192
-
193
- | Field | Omitted when |
194
- |---|---|
195
- | `parentKey` | `null` (item is a root) |
196
- | `done` | `false` (item is still open) |
197
- | `notes` | `[]` (no notes) |
198
- | `activePath` | `null` (no active item) |
199
- | `activeKey` | `null` (no active item) |
200
- | `descendantCount` | `0` (in `DeleteResult`) |
201
- | `view` | `null` (delete left the tree empty) |
202
-
203
- Everything else is always present, including the envelope itself
204
- (`status`, `envelopeVersion`, `verb`, `data` / `error`).
205
-
206
- `where --json` is a special case: `data` is a plain string (the
207
- canonical location of the noggin), not a structured object.
208
-
209
- `ViewNode.children` is special: it's already a tri-state encoded by
210
- presence (see `CurrentTreeView` below). Pruning doesn't touch it.
211
-
212
- #### `CurrentTreeView`
213
-
214
- Returned in `data` by every mutating verb and by `show`. Carries
215
- everything the human "current tree" view shows, so JSON consumers can
216
- reconstruct the same picture without re-reading the file.
8
+ items are paused. Done items stay in the tree under their parent
9
+ so you can see what got finished. Lives in `~/.noggin.yaml` by
10
+ default; the YAML file is the source of truth.
217
11
 
218
- ```jsonc
219
- {
220
- "activePath": "/1/2/3", // path of the active item, or null
221
- "activeKey": "i-…", // opaque key of the active item, or null
222
- "targetKey": "i-…", // opaque key of the item the verb acted on
223
- "items": [ // top of the rendered tree (see below)
224
- { …ItemView…, "children"?: [ ViewNode, … ] },
225
-
226
- ]
227
- }
228
- ```
229
-
230
- The view is a **recursive tree**. Each node (`ViewNode`) is an
231
- `ItemView` (the usual `key, parentKey, path, position, title, done,
232
- createdAt, notes` fields) plus an *optional* `children` slot:
233
-
234
- | `children` | Meaning |
235
- |---|---|
236
- | present (array, possibly `[]`) | this view renders this node's child level; the array is the rendered children |
237
- | **absent** | leaf of this view — the store may have a subtree here, but this view doesn't render it |
238
-
239
- The recursion walks the direct ancestor chain from a root down to the
240
- target. Sibling-of-ancestor items are **trimmed** — each intermediate
241
- ancestor's `children` is a single-element array. The target's parent's
242
- `children` is the full **peer row** (siblings + target itself, in
243
- tree order). The target itself carries its first-level kids in
244
- `children` (or omits the field entirely with `--no-children`).
245
-
246
- Peers and grandkids (the children listed under the target) are
247
- **leaves of the view**: no `children` field. To explore their
248
- subtrees, call `show` on them.
249
-
250
- `items` is either:
251
- - a one-element array containing the target's root ancestor, when the
252
- target is below the root level; or
253
- - the target's full peer row (the actual store roots, in tree order),
254
- when the target itself is a root.
255
-
256
- To find the target node, walk the tree and match on `targetKey`.
257
-
258
- Active is reported separately as both `activePath` and `activeKey`
259
- because the active item may not appear in this view at all (e.g.
260
- `add --into <other-branch>` returns a view of the new item, but
261
- active is unchanged on a different branch). A consumer that wants
262
- to show "📍 you're at `X`" needs the path explicitly.
263
-
264
- An `ItemView` is `{ key, parentKey, path, position, title, done,
265
- createdAt, notes }`. Notes are `{ timestamp, text }` objects.
266
-
267
- #### `DeleteResult`
268
-
269
- Returned in `data` by `delete`. Always carries the deletion record;
270
- `view` is `null` only when the tree is left with no active item.
271
-
272
- ```jsonc
273
- {
274
- "deleted": { "key": "i-…", "path": "/1/2/3", "title": "…" },
275
- "descendantCount": 2,
276
- "view": { … CurrentTreeView … } | null
277
- }
278
- ```
12
+ ---
279
13
 
280
- #### `where`
14
+ ## 📖 Full docs
281
15
 
282
- Returns the canonical location string of the noggin currently in use
283
- the same string `openNoggin()` would accept to reopen it. The
284
- string is round-trippable: `~/.noggin.yaml` stays as `~/.noggin.yaml`,
285
- `./.noggin.yaml` stays as `./.noggin.yaml`, absolute paths stay
286
- absolute. Both the human and `--json` output are this single string.
287
-
288
- ## JavaScript API
289
-
290
- For consumers embedding noggin in a Node process (the VS Code
291
- extension, custom tooling), there's a small public API beyond the
292
- CLI:
293
-
294
- ```js
295
- import { fileNoggin } from 'noggin/backends/file';
296
-
297
- const noggin = await fileNoggin('/path/to/.noggin.yaml', { watch: true });
298
- const view = await noggin.push({ title: 'spike storage layer' });
299
- console.log(noggin.active?.title);
300
- noggin.onDidChange(() => render(noggin.items));
301
- await noggin.dispose();
302
- ```
303
-
304
- ### Public surface
305
-
306
- | What | Where |
307
- |---|---|
308
- | `Noggin` class — live noggin with verb methods, accessors, events | `noggin/noggin-api.mjs` |
309
- | `fileNoggin(path, opts?): Promise<Noggin>` — open a file-backed noggin | `noggin/backends/file.mjs` |
310
- | `applyX(doc, opts, ctx?)` — pure verb functions over `NogginDocument` | `noggin/noggin-api.mjs` |
311
- | `fromYaml` / `toYaml` / `fromJson` / `toJson` — serializers | `noggin/serializers/{yaml,json}.mjs` |
312
- | `NogginError`, `NogginErrorCode` — typed errors | `noggin/noggin-api.mjs` |
313
- | `formatSuccess` / `formatError` — response envelope helpers | `noggin/noggin-api.mjs` |
314
- | `SCHEMA_VERSION`, `RESPONSE_ENVELOPE_VERSION` — constants | `noggin/noggin-api.mjs` |
315
-
316
- All `Noggin` verb methods return `Promise`. Per-instance calls are
317
- serialized (in-process queue); cross-process callers should treat
318
- the file as advisory-locked at the application layer.
319
-
320
- ### `NogginDocument` shape
321
-
322
- The serialized form (what the JSON Schema validates, what
323
- serializers convert to/from) is just:
324
-
325
- ```ts
326
- interface NogginDocument {
327
- schemaVersion: 1;
328
- active: ItemKey | null;
329
- items: Item[];
330
- }
331
- ```
16
+ This README is intentionally short. Everything else lives on the
17
+ **[noggin docs site](https://dornstein.github.io/noggin/)**:
332
18
 
333
- A live `Noggin` does not expose `schemaVersion` that's a wire
334
- concern, owned by the serializers.
19
+ - **[Install](https://dornstein.github.io/noggin/install/)** VS Code extension, agent plugin (Codex / Claude Code / GitHub Copilot CLI), bare CLI
20
+ - **[CLI reference](https://dornstein.github.io/noggin/cli/)** — every verb, every flag, generated from the binary
21
+ - **[Verb demo](https://dornstein.github.io/noggin/demo/)** — side-by-side human vs JSON output, real CLI runs
22
+ - **[MCP server](https://dornstein.github.io/noggin/mcp/)** — tools the agent sees over stdio
23
+ - **[Document schema](https://dornstein.github.io/noggin/schema/)** — the `NogginDocument` shape and invariants
24
+ - **[Response envelope](https://dornstein.github.io/noggin/envelope/)** — JSON wrapper around every CLI / MCP / LM-tool response
25
+ - **[Playground](https://dornstein.github.io/noggin/playground/)** — try noggin in your browser, no install
335
26
 
336
- ## File schema (v1)
27
+ Agent-facing instructions live in [SKILL.md](SKILL.md). The TypeScript
28
+ declarations for the in-process API are in [noggin-api.d.mts](noggin-api.d.mts).
337
29
 
338
- The CLI reads and writes a single YAML file. Writes are atomic: the
339
- CLI writes to `<file>.tmp-<pid>-<ts>` and renames over the real path,
340
- so a partial write never corrupts the user's file.
30
+ ## Quick start
341
31
 
342
- A machine-readable JSON Schema for the noggin data model is published
343
- at the repo root as [`noggin.schema.json`](../noggin.schema.json).
344
- The schema describes the shape itself, independent of any particular
345
- producer or consumer — YAML 1.2 is a JSON superset, so the same schema
346
- validates both YAML and JSON renderings. To get autocomplete and inline
347
- validation in VS Code, install the Red Hat YAML extension and add to
348
- your settings:
32
+ ```bash
33
+ # Install the CLI (and the MCP server bin) globally
34
+ npm install -g noggin-cli
349
35
 
350
- ```jsonc
351
- "yaml.schemas": {
352
- "https://dornstein.github.io/noggin/noggin.schema.json": [
353
- ".noggin.yaml",
354
- "**/.noggin/*.yaml"
355
- ]
356
- }
36
+ # Or run ad-hoc via npx
37
+ npx -y noggin-cli noggin push "ship v1"
38
+ npx -y noggin-cli noggin show
357
39
  ```
358
40
 
359
- The CLI enforces stronger invariants than JSON Schema can express
360
- (unique keys, `parentKey`/`active` referential integrity, the "done
361
- items have no open descendants" rule unless force-closed) — see
362
- [Invariants](#invariants) below.
41
+ The package ships two bins:
363
42
 
364
- ### Top-level shape
43
+ - `noggin` — the working-memory tree CLI
44
+ - `noggin-mcp` — a stdio MCP server exposing the same verbs to
45
+ agent hosts (Codex, Claude Code, GitHub Copilot CLI, VS Code MCP)
365
46
 
366
- ```yaml
367
- schemaVersion: 1
368
- active: <key> | null # the item currently being worked on
369
- items: [] # flat array; tree is implied via parentKey
370
- ```
371
-
372
- - `schemaVersion` is required and must equal `1`. Any other value
373
- causes the CLI to refuse to read the file.
374
- - `active` is the opaque `key` of the active item, or `null` when
375
- nothing is active. The active item's path is computed at runtime
376
- by walking parents.
377
- - `items` is a flat list. Tree structure comes from `parentKey`
378
- pointers. Sibling order is array order, and display positions are
379
- computed from that order.
380
-
381
- ### Item shape
382
-
383
- ```yaml
384
- - key: i-20260616-184644-f04bf5 # opaque, immortal
385
- parentKey: null # null = root item
386
- title: marketplace import path
387
- done: false # true once finished; reversible via `edit --open`
388
- createdAt: 2026-06-16T18:46:44.071Z
389
- notes:
390
- - timestamp: 2026-06-16T18:46:45.625Z
391
- text: found the storage abstraction in tableStorageService
392
- - timestamp: 2026-06-16T18:46:46.200Z
393
- text: |
394
- Resumption note
395
-
396
- Where I am
397
- - branch users/davidorn/marketplace-import
398
- ...
399
- - timestamp: 2026-06-16T18:50:11.300Z
400
- text: closed # system-generated when the item is closed
401
- ```
402
-
403
- ### Field semantics
404
-
405
- | Field | Purpose |
406
- |---|---|
407
- | `key` | Opaque, never reused. Format `i-YYYYMMDD-HHMMSS-<hex>` (display only — don't parse). Hidden from human output; included in JSON. |
408
- | `parentKey` | Opaque key of the parent item, or `null` for roots. Multiple roots are allowed. |
409
- | `title` | One-line human label. |
410
- | `done` | `false` while the work is live, `true` once finished. Reversible via `edit --open`. |
411
- | `createdAt` | ISO-8601 timestamp when the item was created. |
412
- | `notes` | Append-only list of `{ timestamp, text }` objects. Each user note is added by `noggin note`. A single system-generated note with text `closed` is appended whenever the item transitions from open to done (via `done`, `pop`, `edit --done`, or the extension UI). Reopening with `edit --open` does not add or remove notes — the historical close stays in the log. |
413
-
414
- ### Invariants
415
-
416
- The CLI validates these on every save:
417
-
418
- 1. Every item has a unique `key`.
419
- 2. Every non-null `parentKey` references an existing item.
420
- 3. `active`, if non-null, references an existing item. An active
421
- item may have `done: true` after explicit `edit --done`; use
422
- `edit --open` to revert, or `goto ..` to leave it.
423
- 4. Done items (`done: true`) remain in the tree (they are not
424
- deleted) and can be reverted via `edit --open`.
425
- 5. A done item may have open descendants only when it was closed
426
- with `--force`. The standard close paths (`done`, `pop`, `edit
427
- --done` without flags, or with `--close-all`) preserve the
428
- stronger invariant "done items have no open descendants".
429
-
430
- ## Resumption notes
431
-
432
- Resumption notes are for **cold-start rehydration** — what an LLM (or
433
- you, two days later) needs to resume work without reading the whole
434
- session. They are just notes; the schema does not enforce structure.
435
-
436
- A useful shape:
437
-
438
- ```
439
- Where I am
440
- - branch / file / cursor
441
- - last action
442
-
443
- What I believe
444
- - the model of the system that this work assumes
445
- - constraints and invariants
446
-
447
- Ruled out
448
- - approaches considered and rejected (and why)
449
-
450
- Decisions in flight
451
- - questions that aren't settled yet
452
-
453
- Resume by
454
- - the literal first thing to do on return
455
- ```
456
-
457
- Append it as a normal note:
458
-
459
- ```powershell
460
- node noggin.mjs note "Resumption note`n`nWhere I am`n - ...`nResume by`n - ..."
461
- ```
47
+ For MCP wire-up examples and host-specific config paths, see the
48
+ [MCP page](https://dornstein.github.io/noggin/mcp/) and the
49
+ [plugin distribution README](https://github.com/dornstein/noggin/blob/main/plugin/README.md).
462
50
 
463
- ## Constraints
51
+ ## License
464
52
 
465
- - Single-user, single-machine. No collaboration, no network, no
466
- remote sync.
467
- - The CLI is intentionally tiny: stdlib + `js-yaml`. Bundleable into
468
- the Agency plugin if needed (vendor `js-yaml` next to `noggin.mjs` at
469
- bundle time).
53
+ [MIT](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noggin-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "A working-memory tree CLI for in-flight work.",
5
5
  "type": "module",
6
6
  "bin": {