noggin-cli 0.1.3 → 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.
package/noggin-api.d.mts CHANGED
@@ -1,19 +1,34 @@
1
1
  // Type declarations for noggin-api.mjs.
2
2
  //
3
3
  // Hand-written to match the JS implementation; no build step. The .mjs uses
4
- // /// <reference path="./noggin-api.d.ts" /> so editors and tsc --noEmit can
4
+ // /// <reference path="./noggin-api.d.mts" /> so editors and tsc --noEmit can
5
5
  // check usages against this contract.
6
+ //
7
+ // API stability tiers (TSDoc release tags):
8
+ // @public — Stable contract. Breaking changes require a major bump.
9
+ // @experimental — Public but the shape may still change.
10
+ // @internal — Implementation detail; do not depend on this.
11
+
12
+ // ── Core identifiers ─────────────────────────────────────────────────────────
6
13
 
7
- export type NogginFilePath = string;
14
+ /** @public Opaque, stable item identifier. Treat as a string token. */
8
15
  export type ItemKey = string;
16
+
17
+ /** @public Tree path string (e.g. `/1/2/3` or `./X`). Display coordinate only. */
9
18
  export type ItemPath = string;
19
+
20
+ /** @public ISO-8601 / RFC 3339 date-time string. */
10
21
  export type IsoTimestamp = string;
11
22
 
23
+ // ── Data model ───────────────────────────────────────────────────────────────
24
+
25
+ /** @public A single entry in an item's append-only note log. */
12
26
  export interface Note {
13
27
  timestamp: IsoTimestamp;
14
28
  text: string;
15
29
  }
16
30
 
31
+ /** @public A single work item in the noggin tree. */
17
32
  export interface Item {
18
33
  key: ItemKey;
19
34
  parentKey: ItemKey | null;
@@ -23,102 +38,76 @@ export interface Item {
23
38
  notes: Note[];
24
39
  }
25
40
 
26
- export interface Store {
41
+ /**
42
+ * @public
43
+ * The serialized form of a noggin: pure data. The JSON Schema validates
44
+ * this shape; serializers convert it to/from YAML or JSON; backends
45
+ * load and save it.
46
+ */
47
+ export interface NogginDocument {
27
48
  schemaVersion: number;
28
49
  active: ItemKey | null;
29
50
  items: Item[];
30
51
  }
31
52
 
32
- /** An Item enriched with computed path and 1-based sibling position. */
53
+ /** @public An Item enriched with computed path and 1-based sibling position. */
33
54
  export interface ItemView extends Item {
34
55
  path: ItemPath | null;
35
56
  position: number | null;
36
57
  }
37
58
 
38
- /**
39
- * A node in a CurrentTreeView's recursive tree. Carries the usual
40
- * ItemView fields plus an *optional* `children` slot:
41
- *
42
- * children present this view renders this node's child level (the
43
- * array may be empty — e.g. target with no kids)
44
- * children absent leaf of this view; the store may have a subtree
45
- * here, but this view doesn't render it
46
- *
47
- * The recursion walks the direct ancestor chain from root to target.
48
- * Each ancestor has a single-element `children`. The target's parent
49
- * has the full peer row. The target has `children` populated with its
50
- * first-level kids (or no `children` field at all with `--nokids`).
51
- * Peers and grandkids are leaves and have no `children` field.
52
- */
59
+ /** @public A node in a CurrentTreeView's recursive tree. */
53
60
  export interface ViewNode extends ItemView {
54
61
  children?: ViewNode[];
55
62
  }
56
63
 
57
- /** Shape returned by every mutating verb and by `show`/`view`. */
64
+ /** @public Shape returned by every mutating verb and by `verbs.show`. */
58
65
  export interface CurrentTreeView {
59
- /** Path of the active item, or null. May differ from the target —
60
- * active is the user's persistent cursor (📍) and is not necessarily
61
- * on the spine of this view. */
62
66
  activePath: ItemPath | null;
63
- /** Stable key of the active item, or null. */
64
67
  activeKey: ItemKey | null;
65
- /** Stable key of the item the verb acted on. To grab the full row,
66
- * walk `items` and find the node whose `key === targetKey`. */
67
68
  targetKey: ItemKey;
68
- /**
69
- * Top of the rendered tree. Contains either:
70
- * - a single root ancestor (when the target is below depth 0); or
71
- * - the target's full peer row (when the target itself is a root).
72
- * Either way, every node along the path from `items` down to the
73
- * target has a non-null `children`; leaves of the view have `null`.
74
- */
75
69
  items: ViewNode[];
76
70
  }
77
71
 
78
- /** Identifying tombstone for a deleted item — survives the delete itself. */
72
+ /** @public Identifying tombstone for a deleted item. */
79
73
  export interface DeletedItem {
80
74
  key: ItemKey;
81
75
  path: ItemPath | null;
82
76
  title: string;
83
77
  }
84
78
 
79
+ /** @public */
85
80
  export type PlacementKind = 'before' | 'after' | 'into';
86
81
 
82
+ /** @public Placement spec for `add` / `move`. */
87
83
  export interface Placement {
88
84
  kind: PlacementKind;
89
- /** Path to the anchor item. Resolved against the live store. */
85
+ /** Path to the anchor item. */
90
86
  anchor: ItemPath;
91
87
  }
92
88
 
93
- /** Optional reposition-after-write. Mirrors the CLI `--goto` flag. */
89
+ /** @public Optional reposition-after-write. Mirrors the CLI `--goto` flag. */
94
90
  export interface GotoOption {
95
- /**
96
- * Path resolved relative to the operation's target.
97
- * `true` (or omitted with bare `--goto`) means `.` (the target itself).
98
- */
91
+ /** Path resolved relative to the operation's target. `true` means `.`. */
99
92
  goto?: ItemPath | true;
100
93
  }
101
94
 
102
- export interface FileResolution {
103
- file: NogginFilePath;
104
- source: 'flag' | 'env' | 'default';
105
- exists: boolean;
106
- defaultFile: NogginFilePath;
107
- /** Value of $NOGGIN_FILE at the time of resolution, or null. */
108
- env: string | null;
109
- }
110
-
95
+ /** @public Result of `verbs.delete`. */
111
96
  export interface DeleteResult {
112
97
  deleted: DeletedItem;
113
98
  descendantCount: number;
114
- /** Null only when the resulting tree has no active item (e.g. a root was deleted). */
115
99
  view: CurrentTreeView | null;
116
100
  }
117
101
 
102
+ // ── Errors ───────────────────────────────────────────────────────────────────
103
+
104
+ /** @public Closed-ish union of error codes. New codes are non-breaking. */
118
105
  export type NogginErrorCode =
119
106
  | 'noggin-error'
120
107
  | 'no-active-item'
121
108
  | 'no-file'
109
+ | 'no-location'
110
+ | 'no-factory'
122
111
  | 'path-not-found'
123
112
  | 'path-required'
124
113
  | 'cycle'
@@ -136,10 +125,13 @@ export type NogginErrorCode =
136
125
  | 'open-descendants'
137
126
  | 'pop-no-path'
138
127
  | 'invalid-note'
139
- | 'invalid-store'
128
+ | 'invalid-op'
129
+ | 'invalid-document'
140
130
  | 'unsupported-schema'
131
+ | 'lock-timeout'
141
132
  | 'io';
142
133
 
134
+ /** @public Thrown by every engine function on usage/state errors. */
143
135
  export class NogginError extends Error {
144
136
  readonly code: NogginErrorCode | string;
145
137
  /** Mirrors the CLI exit code (1 = runtime/state, 2 = usage/parse/invalid). */
@@ -149,169 +141,280 @@ export class NogginError extends Error {
149
141
 
150
142
  // ── Constants ────────────────────────────────────────────────────────────────
151
143
 
144
+ /** @public Current `schemaVersion` written into a `NogginDocument`. */
152
145
  export const SCHEMA_VERSION: number;
153
- export const JSON_SCHEMA_VERSION: number;
154
- export const DEFAULT_FILE: NogginFilePath;
155
146
 
156
- // ── Stateless functions ──────────────────────────────────────────────────────
147
+ /** @public Current `envelopeVersion` stamped onto every response envelope. */
148
+ export const RESPONSE_ENVELOPE_VERSION: number;
157
149
 
158
- export function resolveFile(opts?: { file?: NogginFilePath; env?: Record<string, string | undefined> }): FileResolution;
150
+ /** @public @deprecated Renamed to `RESPONSE_ENVELOPE_VERSION`. */
151
+ export const JSON_SCHEMA_VERSION: number;
159
152
 
160
- export function loadStore(file: NogginFilePath): Store;
161
- export function saveStore(file: NogginFilePath, store: Store): void;
153
+ /** @public The system-generated note text appended on close. */
154
+ export const CLOSE_NOTE_TEXT: string;
162
155
 
163
- export function resolvePath(store: Store, path: ItemPath): Item;
164
- export function tryResolvePath(store: Store, path: ItemPath): Item | null;
156
+ // ── Eventing primitives ─────────────────────────────────────────────────────
165
157
 
166
- export function pathOf(store: Store, item: Item | null | undefined): ItemPath | null;
167
- export function childrenOf(store: Store, parentKey: ItemKey | null | undefined): Item[];
158
+ /** @public Disposable returned by event subscriptions. */
159
+ export interface Disposable { dispose(): void }
168
160
 
169
- export function buildView(
170
- store: Store,
171
- target: Item,
172
- opts?: { includeChildren?: boolean; withSiblings?: boolean; withDescendants?: boolean }
173
- ): CurrentTreeView;
161
+ /** @public vscode-style event subscribe function. */
162
+ export type Event<T> = (handler: (e: T) => void) => Disposable;
174
163
 
175
- // ── JSON envelope ────────────────────────────────────────────────────────────
164
+ // ── Noggin (interface) ──────────────────────────────────────────────────────
176
165
 
177
166
  /**
178
- * Canonical JSON envelope shared by the CLI `--json` output and the VS
179
- * Code extension's language-model tools. Both surfaces emit this exact
180
- * shape so a single consumer (or test) can target both.
167
+ * @public
168
+ * A live noggin. Backends implement this interface; consumers consume
169
+ * it. Read accessors are synchronous and reflect the current state.
170
+ * `apply(ops)` is the only mutator — every backend implements it; the
171
+ * `verbs` namespace composes ops and calls it.
172
+ *
173
+ * Storage-tracking contract:
174
+ * - Accessors always reflect the latest known state.
175
+ * - `onDidChange` fires after every mutation (in-process or externally
176
+ * observed). After it fires, accessors are up to date.
181
177
  */
182
- export interface SuccessEnvelope<T = unknown> {
183
- status: 'ok';
184
- schemaVersion: number;
185
- verb: string | null;
186
- file: NogginFilePath | null;
187
- data: T;
188
- }
178
+ export interface Noggin {
179
+ // accessors (sync)
180
+ readonly items: readonly Item[];
181
+ readonly active: Item | null;
182
+ readonly roots: readonly Item[];
189
183
 
190
- export interface ErrorEnvelope {
191
- status: 'error';
192
- schemaVersion: number;
193
- verb: string | null;
194
- file: NogginFilePath | null;
195
- error: {
196
- code: NogginErrorCode | string;
197
- message: string;
198
- exitCode: number;
199
- };
184
+ findByKey(k: ItemKey | null | undefined): Item | null;
185
+ childrenOf(k: ItemKey | null | undefined): readonly Item[];
186
+ pathOf(item: Item | null | undefined): ItemPath | null;
187
+ resolvePath(p: ItemPath): Item;
188
+ tryResolvePath(p: ItemPath): Item | null;
189
+
190
+ /** Atomically apply a list of `AtomicOp`s. The only write primitive. */
191
+ apply(ops: readonly AtomicOp[]): Promise<void>;
192
+
193
+ /** Release backend resources. After dispose the noggin is unusable. */
194
+ dispose(): Promise<void>;
195
+
196
+ /** Human-readable description of where this noggin lives. Not machine-parseable. */
197
+ describe(): string;
198
+
199
+ readonly onDidChange: Event<void>;
200
+ readonly onDidError: Event<NogginError>;
200
201
  }
201
202
 
202
- export type JsonEnvelope<T = unknown> = SuccessEnvelope<T> | ErrorEnvelope;
203
+ // ── Atomic ops ──────────────────────────────────────────────────────────────
204
+
205
+ /**
206
+ * @public
207
+ * Atomic state mutations. Every change to a noggin's state goes through
208
+ * one of these. Verbs compose op lists; backends execute them
209
+ * atomically via `Noggin.apply(ops)`.
210
+ *
211
+ * `position` is the 0-based index among siblings of `parentKey`, or
212
+ * the literal string `'end'` to append.
213
+ */
214
+ export type AtomicOp =
215
+ | { type: 'add'; item: Item; parentKey: ItemKey | null; position: number | 'end' }
216
+ | { type: 'remove'; keys: readonly ItemKey[] }
217
+ | { type: 'set'; key: ItemKey; patch: { title?: string; done?: boolean } }
218
+ | { type: 'note'; key: ItemKey; note: Note }
219
+ | { type: 'move'; key: ItemKey; parentKey: ItemKey | null; position: number | 'end' }
220
+ | { type: 'setActive'; key: ItemKey | null };
221
+
222
+ /**
223
+ * @public
224
+ * Apply a list of `AtomicOp`s to a `NogginDocument` in-place, then
225
+ * validate. Used by backends inside their `apply()`; also useful for
226
+ * offline document manipulation. Throws `NogginError` if any op
227
+ * references missing data or the resulting document is malformed.
228
+ */
229
+ export function applyOps(doc: NogginDocument, ops: readonly AtomicOp[]): NogginDocument;
203
230
 
204
- export function formatSuccess<T>(opts: {
205
- verb?: string;
206
- file?: NogginFilePath | null;
207
- data?: T;
208
- }): SuccessEnvelope<T>;
231
+ /**
232
+ * @public
233
+ * Validate a document's structural invariants. Throws `NogginError`
234
+ * with code `'invalid-document'` on failure.
235
+ */
236
+ export function validateDocument(doc: NogginDocument): void;
237
+
238
+ /**
239
+ * @public
240
+ * Normalize a parsed document in-place: stamp schemaVersion, normalize
241
+ * notes, strip legacy fields.
242
+ */
243
+ export function normalizeDocument(doc: NogginDocument): NogginDocument;
244
+
245
+ /** @internal Used by serializers and `applyOps`. */
246
+ export function normalizeNote(note: { timestamp?: string | null; text: string }): Note;
247
+
248
+ /**
249
+ * @public
250
+ * Structural equality between two documents. Used by backends to
251
+ * decide whether an external change actually changed anything.
252
+ */
253
+ export function documentsEqual(a: NogginDocument, b: NogginDocument): boolean;
209
254
 
210
- export function formatError(opts: {
211
- verb?: string;
212
- file?: NogginFilePath | null;
213
- error: unknown;
214
- }): ErrorEnvelope;
255
+ /**
256
+ * @public
257
+ * Deep-freeze a document so accessors can return references without
258
+ * worrying about consumer mutation.
259
+ */
260
+ export function freezeDocument(doc: NogginDocument): NogginDocument;
215
261
 
216
- // ── Verb functions (stateless) ───────────────────────────────────────────────
262
+ // ── Verbs ───────────────────────────────────────────────────────────────────
217
263
 
264
+ /** @public Optional context for verbs that stamp timestamps. */
265
+ export interface VerbContext {
266
+ /** Fixed clock for deterministic timestamps in tests. */
267
+ now?: Date;
268
+ }
269
+
270
+ /** @public */
218
271
  export interface PushOptions { title: string }
272
+ /** @public */
219
273
  export interface AddOptions extends GotoOption { title: string; placement?: Placement }
274
+ /** @public */
220
275
  export interface MoveOptions extends GotoOption { path?: ItemPath; placement: Placement }
221
- export interface GotoOptions { path?: ItemPath }
276
+ /** @public */
277
+ export interface GotoOptions { path: ItemPath }
222
278
 
223
- /** Shared shape for closing verbs (`done`, `pop`, `set --done`). */
279
+ /** @public Shared shape for closing verbs (`done`, `pop`, `edit --done`). */
224
280
  export interface CloseOptions {
225
- /** Skip the open-descendant safety check; close the target even with open kids. */
226
281
  force?: boolean;
227
- /** Close every open descendant first (each gets its own system close note). */
228
282
  closeAll?: boolean;
229
283
  }
230
284
 
285
+ /** @public */
231
286
  export interface DoneOptions extends CloseOptions { path?: ItemPath }
287
+ /** @public */
232
288
  export interface PopOptions extends CloseOptions {}
233
289
 
234
- /**
235
- * `edit` combines the old `set-state` and `retitle` verbs into one
236
- * idempotent mutation verb. Specify at least one of `done`/`title`;
237
- * each operation is a no-op when the value already matches.
238
- */
290
+ /** @public */
239
291
  export interface EditOptions extends GotoOption, CloseOptions {
240
292
  path?: ItemPath;
241
- /** true → close, false → reopen, undefined → don't touch state. */
242
293
  done?: boolean;
243
- /** New title (trimmed). Empty/whitespace is ignored, not an error. */
244
294
  title?: string;
245
295
  }
246
296
 
297
+ /** @public */
247
298
  export interface ShowOptions extends GotoOption {
248
299
  path?: ItemPath;
249
- /** Whether to expand the target's `children` field. Default true; set false for --no-children. */
250
300
  includeChildren?: boolean;
251
- /** Show note bodies in human output (no effect on JSON — notes are always present). */
252
301
  withNotes?: boolean;
253
- /** Include the full sibling row at every ancestor depth. */
254
302
  withSiblings?: boolean;
255
- /** Expand the target's subtree recursively. */
256
303
  withDescendants?: boolean;
257
304
  }
305
+ /** @public */
258
306
  export interface NoteOptions extends GotoOption { path?: ItemPath; text: string }
307
+ /** @public */
259
308
  export interface DeleteOptions { path: ItemPath; recursive?: boolean }
260
309
 
261
- export function apiPush(file: NogginFilePath, opts: PushOptions): CurrentTreeView;
262
- export function apiAdd(file: NogginFilePath, opts: AddOptions): CurrentTreeView;
263
- export function apiMove(file: NogginFilePath, opts: MoveOptions): CurrentTreeView;
264
- export function apiGoto(file: NogginFilePath, opts: GotoOptions): CurrentTreeView;
265
- export function apiDone(file: NogginFilePath, opts?: DoneOptions): CurrentTreeView;
266
- export function apiPop(file: NogginFilePath, opts?: PopOptions): CurrentTreeView;
267
- export function apiEdit(file: NogginFilePath, opts: EditOptions): CurrentTreeView;
268
- export function apiShow(file: NogginFilePath, opts?: ShowOptions): CurrentTreeView | null;
269
- export function apiNote(file: NogginFilePath, opts: NoteOptions): CurrentTreeView;
270
- export function apiDelete(file: NogginFilePath, opts: DeleteOptions): DeleteResult;
271
- export function apiWhere(opts?: { file?: NogginFilePath; env?: Record<string, string | undefined> }): FileResolution;
310
+ /**
311
+ * @public
312
+ * The single verb implementation, shared by every backend. Each verb
313
+ * reads state via the `Noggin`'s accessors, composes an `AtomicOp[]`,
314
+ * calls `noggin.apply(ops)` once, and returns the resulting view (or
315
+ * a `DeleteResult` for delete).
316
+ *
317
+ * Verb behavior contracts (push moves active, add doesn't, done
318
+ * appends a close note and surfaces to parent, etc.) live here.
319
+ * Backends do not implement verbs.
320
+ */
321
+ export const verbs: {
322
+ push(noggin: Noggin, opts: PushOptions, ctx?: VerbContext): Promise<CurrentTreeView>;
323
+ add(noggin: Noggin, opts: AddOptions, ctx?: VerbContext): Promise<CurrentTreeView>;
324
+ move(noggin: Noggin, opts: MoveOptions): Promise<CurrentTreeView>;
325
+ goto(noggin: Noggin, opts: GotoOptions): Promise<CurrentTreeView>;
326
+ done(noggin: Noggin, opts?: DoneOptions, ctx?: VerbContext): Promise<CurrentTreeView>;
327
+ pop(noggin: Noggin, opts?: PopOptions, ctx?: VerbContext): Promise<CurrentTreeView>;
328
+ edit(noggin: Noggin, opts: EditOptions, ctx?: VerbContext): Promise<CurrentTreeView>;
329
+ show(noggin: Noggin, opts?: ShowOptions): Promise<CurrentTreeView | null>;
330
+ note(noggin: Noggin, opts: NoteOptions, ctx?: VerbContext): Promise<CurrentTreeView>;
331
+ delete(noggin: Noggin, opts: DeleteOptions): Promise<DeleteResult>;
332
+ };
333
+
334
+ // ── Factory registry ───────────────────────────────────────────────────────
335
+
336
+ /** @public A backend factory: claims a scheme prefix, opens a Noggin. */
337
+ export interface NogginFactory {
338
+ readonly scheme: string;
339
+ open(location: string, opts?: object): Promise<Noggin>;
340
+ }
272
341
 
273
- // ── Noggin class ─────────────────────────────────────────────────────────────
342
+ /** @public Registry interface. The exported `factories` is the singleton. */
343
+ export interface NogginFactoryRegistry {
344
+ register(factory: NogginFactory, opts?: { default?: boolean }): void;
345
+ unregister(scheme: string): boolean;
346
+ get(scheme: string): NogginFactory | null;
347
+ getDefault(): NogginFactory | null;
348
+ list(): readonly { scheme: string; default: boolean }[];
349
+ }
274
350
 
275
- export interface Disposable { dispose(): void }
276
- export type Event<T> = (handler: (e: T) => void) => Disposable;
351
+ /** @public The process-wide noggin factory registry. */
352
+ export const factories: NogginFactoryRegistry;
277
353
 
278
- export class Noggin {
279
- constructor(file: NogginFilePath, opts?: { watch?: boolean });
354
+ /**
355
+ * @public
356
+ * Open a noggin by location. The scheme prefix (e.g. `file://`,
357
+ * `localstorage://`) selects the factory; a bare location goes to
358
+ * whichever factory was registered with `{default: true}`.
359
+ */
360
+ export function openNoggin(location: string, opts?: object): Promise<Noggin>;
280
361
 
281
- readonly file: NogginFilePath;
362
+ // ── Public utilities ────────────────────────────────────────────────────────
282
363
 
283
- readonly store: Readonly<Store>;
284
- readonly active: Item | null;
285
- readonly roots: Item[];
364
+ /**
365
+ * @public
366
+ * Resolve a path string against a doc-shaped `{items, active}` snapshot.
367
+ * Throws `NogginError('path-not-found')` if the path doesn't resolve.
368
+ */
369
+ export function resolvePath(snapshot: { items: Item[]; active: ItemKey | null }, p: ItemPath): Item;
286
370
 
287
- findByKey(key: ItemKey | null | undefined): Item | null;
288
- childrenOf(parentKey: ItemKey | null | undefined): Item[];
289
- pathOf(item: Item | null | undefined): ItemPath | null;
290
- resolvePath(path: ItemPath): Item;
291
- tryResolvePath(path: ItemPath): Item | null;
371
+ /** @public Like `resolvePath` but returns `null` instead of throwing. */
372
+ export function tryResolvePath(snapshot: { items: Item[]; active: ItemKey | null }, p: ItemPath): Item | null;
292
373
 
293
- view(
294
- target?: Item | ItemPath | null,
295
- opts?: { includeChildren?: boolean }
296
- ): CurrentTreeView | null;
374
+ /** @public Compute the absolute path string for an item. */
375
+ export function pathOf(snapshot: { items: Item[] }, item: Item | null | undefined): ItemPath | null;
297
376
 
298
- reload(): boolean;
299
- dispose(): void;
377
+ /** @public Direct children of `parentKey` (null = roots), in tree order. */
378
+ export function childrenOf(snapshot: { items: Item[] }, parentKey: ItemKey | null | undefined): Item[];
300
379
 
301
- readonly onDidChange: Event<void>;
302
- readonly onDidError: Event<NogginError>;
380
+ /**
381
+ * @public
382
+ * Build a CurrentTreeView for `target`. Pure; does not mutate.
383
+ * Accepts a noggin or a `{items, active}` doc-shape.
384
+ */
385
+ export function buildView(
386
+ snapshot: { items: readonly Item[]; active: ItemKey | null },
387
+ target: Item,
388
+ opts?: { includeChildren?: boolean; withSiblings?: boolean; withDescendants?: boolean }
389
+ ): CurrentTreeView;
303
390
 
304
- push(opts: PushOptions): CurrentTreeView;
305
- add(opts: AddOptions): CurrentTreeView;
306
- move(opts: MoveOptions): CurrentTreeView;
307
- goto(path: ItemPath): CurrentTreeView;
308
- done(opts?: DoneOptions): CurrentTreeView;
309
- pop(opts?: PopOptions): CurrentTreeView;
310
- edit(opts: EditOptions): CurrentTreeView;
311
- show(opts?: ShowOptions): CurrentTreeView | null;
312
- note(opts: NoteOptions): CurrentTreeView;
313
- delete(opts: DeleteOptions): DeleteResult;
314
- where(): FileResolution;
391
+ // ── Response envelope ───────────────────────────────────────────────────────
392
+
393
+ /** @public */
394
+ export interface SuccessEnvelope<T = unknown> {
395
+ status: 'ok';
396
+ envelopeVersion: number;
397
+ verb: string | null;
398
+ data: T;
315
399
  }
316
400
 
317
- export function openNoggin(file: NogginFilePath): Noggin;
401
+ /** @public */
402
+ export interface ErrorEnvelope {
403
+ status: 'error';
404
+ envelopeVersion: number;
405
+ verb: string | null;
406
+ error: {
407
+ code: NogginErrorCode | string;
408
+ message: string;
409
+ exitCode: number;
410
+ };
411
+ }
412
+
413
+ /** @public */
414
+ export type JsonEnvelope<T = unknown> = SuccessEnvelope<T> | ErrorEnvelope;
415
+
416
+ /** @public */
417
+ export function formatSuccess<T>(opts: { verb?: string; data?: T }): SuccessEnvelope<T>;
418
+
419
+ /** @public */
420
+ export function formatError(opts: { verb?: string; error: unknown }): ErrorEnvelope;