@neosh/api 0.4.0 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neosh/api",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "The neosh plugin API. Types are generated from the Rust side and drift-checked in CI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,7 @@ import type { ProjectKey } from "./ProjectKey";
32
32
  import type { ProviderEvent } from "./ProviderEvent";
33
33
  import type { QuotaSnapshot } from "./QuotaSnapshot";
34
34
  import type { Rect } from "./Rect";
35
+ import type { ScrollAmount } from "./ScrollAmount";
35
36
  import type { SelectShape } from "./SelectShape";
36
37
  import type { SessionId } from "./SessionId";
37
38
  import type { StatusSegment } from "./StatusSegment";
@@ -113,6 +114,7 @@ export type ApiCall =
113
114
  | { "call": "win_set_cursor"; win: WindowId; row: number; col: number }
114
115
  | { "call": "win_get_viewport"; win: WindowId }
115
116
  | { "call": "win_scroll_to"; win: WindowId; top_line: number | null }
117
+ | { "call": "win_scroll"; win: WindowId; amount: ScrollAmount }
116
118
  | { "call": "win_list" }
117
119
  | {
118
120
  "call": "pane_split";
@@ -139,7 +141,18 @@ export type ApiCall =
139
141
  * work somewhere is not, for the same reason `SessionNew { activate: false }` exists.
140
142
  */
141
143
  activate: boolean;
144
+ /**
145
+ * Which strip to open it on. See [`TabInfo::group`].
146
+ *
147
+ * `None` inherits the tab you are on, which is what a new tab almost always wants: opened
148
+ * while reading a conversation, it is that conversation's. Saying one is for a caller
149
+ * putting a tab somewhere *else* — and a group nothing is currently showing is a tab that
150
+ * is made and is off the bar, which is a legitimate thing to want and a surprising thing
151
+ * to do by accident.
152
+ */
153
+ group?: string | null;
142
154
  }
155
+ | { "call": "tab_group"; tab: TabId; group: string | null }
143
156
  | { "call": "tab_close"; tab: TabId }
144
157
  | { "call": "tab_select"; tab: TabId }
145
158
  | { "call": "tab_step"; view?: ViewId | null; delta: number }
@@ -478,12 +491,37 @@ export type ApiCall =
478
491
  | { "call": "git_stage"; paths: Array<string> }
479
492
  | { "call": "git_unstage"; paths: Array<string> }
480
493
  | { "call": "git_commit"; message: string }
494
+ | {
495
+ "call": "git_fetch";
496
+ /**
497
+ * The repository to fetch in. The conversation's own when absent.
498
+ */
499
+ cwd?: string | null;
500
+ }
481
501
  | {
482
502
  "call": "git_pull";
483
503
  /**
484
504
  * The repository to pull in. The conversation's own when absent.
485
505
  */
486
506
  cwd?: string | null;
507
+ /**
508
+ * Replay this branch's own commits on top of what arrived, rather than merging.
509
+ *
510
+ * A flag rather than a call of its own because it is one argument to one command, and the
511
+ * caller that needs it is the one that has just been told the branch diverged — at which
512
+ * point "rebase or merge" is the question being answered, and two entry points for the two
513
+ * answers would be two things to keep in step for no gain.
514
+ *
515
+ * It rewrites local commits, which is why nothing sets it without asking first.
516
+ */
517
+ rebase: boolean;
518
+ }
519
+ | {
520
+ "call": "forge_pulls";
521
+ /**
522
+ * The repository to ask about. The conversation's own when absent.
523
+ */
524
+ cwd?: string | null;
487
525
  }
488
526
  | {
489
527
  "call": "git_add_worktree";
@@ -507,6 +545,32 @@ export type ApiCall =
507
545
  */
508
546
  cwd?: string | null;
509
547
  }
548
+ | {
549
+ "call": "git_clone";
550
+ /**
551
+ * Anything `git clone` takes: an `https://` or `git@` URL, or a local path.
552
+ */
553
+ url: string;
554
+ /**
555
+ * Where the working tree lands, in full. The caller resolved the root and the name, so
556
+ * that a picker can *show* the destination on the row before anything is written.
557
+ */
558
+ path: string;
559
+ }
560
+ | {
561
+ "call": "git_move_worktree";
562
+ path: string;
563
+ /**
564
+ * Where it lands, in full. The leaf must not exist; the parent is created.
565
+ */
566
+ dest: string;
567
+ /**
568
+ * The repository it belongs to. `git worktree move` runs from a checkout, and the
569
+ * conversation the caller is in may be standing in the one being moved — the same reason
570
+ * [`Self::GitRemoveWorktree`] takes one.
571
+ */
572
+ cwd?: string | null;
573
+ }
510
574
  | {
511
575
  "call": "gen_complete";
512
576
  prompt: string;
@@ -515,6 +579,30 @@ export type ApiCall =
515
579
  * Ask for JSON and parse it host-side, tolerating the code fences models wrap it in.
516
580
  */
517
581
  json: boolean;
582
+ /**
583
+ * The one key the answer is *about*, when the whole answer is one value.
584
+ *
585
+ * A prompt that says "return `{"branch": …}`" is answered with the object most of the
586
+ * time and with a bare `fix/composer-paste` the rest of it — the model did the work and
587
+ * skipped the envelope, and a caller that only accepts the envelope throws a correct
588
+ * answer away. Measured on this workspace's own history it was two runs in twenty-two:
589
+ * twice a branch was never named, silently, and both times the name was sitting in the
590
+ * reply.
591
+ *
592
+ * So: extraction first, exactly as before, and this is what a *bare* answer means. Named
593
+ * rather than inferred, because only the caller knows which key one value belongs under
594
+ * — and absent, nothing changes, which is what every existing caller wants.
595
+ *
596
+ * Only for a one-value answer. A commit message is a subject *and* a body, and guessing
597
+ * which half a bare paragraph is would be inventing the other one.
598
+ *
599
+ * And only for a value a *wrong* answer is recognisable in. A branch name is checkable
600
+ * and one `git branch -m` from being fixed; a thread title is any short line, which is
601
+ * also what a refusal and a driver's own error message look like — there the envelope is
602
+ * the only evidence the question was answered rather than commented on, and it is worth
603
+ * the one reply in ten that arrives without it.
604
+ */
605
+ field?: string | null;
518
606
  /**
519
607
  * Defaults to `gen.model` if set, else the session's own selection.
520
608
  */
@@ -562,6 +650,16 @@ export type ApiCall =
562
650
  * Ask the network again rather than answering from the last check.
563
651
  */
564
652
  force: boolean;
653
+ /**
654
+ * Answer from what this machine already knows, and never reach the network.
655
+ *
656
+ * The two are not opposites and are never both set: `local` is checked first and wins.
657
+ * It exists because half of an update status is not about a registry at all —
658
+ * [`UpdateStatus::restart_pending`] is a `stat` of one file — and a caller that wants
659
+ * only that half should not be the reason a workspace makes an HTTP request. Which is
660
+ * what a panel polling for "has something replaced my binary" would otherwise be.
661
+ */
662
+ local: boolean;
565
663
  }
566
664
  | { "call": "update_apply" }
567
665
  | {
@@ -25,6 +25,7 @@ import type { PermissionDecision } from "./PermissionDecision";
25
25
  import type { PermissionMode } from "./PermissionMode";
26
26
  import type { PluginInfo } from "./PluginInfo";
27
27
  import type { PointInfo } from "./PointInfo";
28
+ import type { PullRequest } from "./PullRequest";
28
29
  import type { QuestionAnswer } from "./QuestionAnswer";
29
30
  import type { QuotaSample } from "./QuotaSample";
30
31
  import type { QuotaSnapshot } from "./QuotaSnapshot";
@@ -92,6 +93,7 @@ export type ApiOk =
92
93
  | { "ok": "status"; status: RepoStatus }
93
94
  | { "ok": "branches"; branches: Array<BranchInfo> }
94
95
  | { "ok": "worktrees"; worktrees: Array<WorktreeInfo> }
96
+ | { "ok": "pulls"; pulls: Array<PullRequest> }
95
97
  | { "ok": "commits"; commits: Array<CommitInfo> }
96
98
  | { "ok": "commit"; commit: CommitInfo }
97
99
  | { "ok": "text"; text: string }
@@ -0,0 +1,14 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * What the forge's checks say, folded to the one thing a row has room for.
5
+ *
6
+ * Folded here rather than on the wire as a list, because the question a project row answers is
7
+ * "is anything wrong" and the answer is one glyph. The counts travel beside it for whoever wants
8
+ * to say `✗2`; the full list of check names is a panel's job and a different call.
9
+ *
10
+ * `Pending` and `None` are deliberately different. Nothing has run yet is a state that resolves on
11
+ * its own in a few minutes; there are no checks configured at all is permanent, and drawing a
12
+ * spinner over it for ever is the failure this distinction avoids.
13
+ */
14
+ export type ChecksState = "none" | "pending" | "passing" | "failing";
@@ -23,6 +23,17 @@ export type FloatConfig = {
23
23
  */
24
24
  border_hl?: string | null;
25
25
  title?: string | null;
26
+ /**
27
+ * A strip on the bottom border: what the keys here do.
28
+ *
29
+ * On the border rather than in the buffer, because a key strip written as a row of content is
30
+ * one that scrolls away exactly when it is wanted, and is the first thing clipped on a terminal
31
+ * too short for the panel. Both of those happened: the picker's strip is the row that says how
32
+ * to get out, and on a sixteen-row screen it was the row that did not fit.
33
+ *
34
+ * Clipped to the border's width like the title, so it is for a legend and not for prose.
35
+ */
36
+ footer?: string | null;
26
37
  /**
27
38
  * Close automatically when focus moves elsewhere. The right default for pickers and hovers.
28
39
  */
@@ -60,4 +71,26 @@ export type FloatConfig = {
60
71
  * characters through to the composer would be typing into a field you cannot see.
61
72
  */
62
73
  modal: boolean;
74
+ /**
75
+ * This panel is a place you can move in when it does not fit.
76
+ *
77
+ * A float asks for a height and the frontend gives it what there is — so on a short terminal
78
+ * every row past the bottom edge is content that exists, is drawn nowhere, and has no key
79
+ * pointed at it. `height: Max { n: 30 }` on a twenty-row screen is not thirty rows of panel, it
80
+ * is ten rows of panel and twenty rows nobody can reach.
81
+ *
82
+ * Setting this adds one scope to the keymap chain while this float has focus —
83
+ * `BufKind { name: "neosh.scroll" }` — which is where the workspace binds the reader's motions:
84
+ * `j`/`k`, `<C-d>`/`<C-u>`, `<C-f>`/`<C-b>`, `gg`/`G`, the arrows and the paging keys. A scope
85
+ * rather than a capture, so all three of the usual rules hold: `^Z` lists the keys, `init.ts`
86
+ * moves them, and a panel that wants `j` for something of its own binds it on *its* kind, which
87
+ * is nearer and wins. It sits below the panel's own kind and above `Global`, so it is a default
88
+ * that never takes a key away from the thing it is scrolling.
89
+ *
90
+ * Off by default, and not for want of ambition: a picker whose filter takes printable
91
+ * characters through a capture would have `j` and `G` resolved out from under it by a scope, so
92
+ * typing a model's name would scroll instead of filtering. A panel that already moves in itself
93
+ * says so by staying quiet here.
94
+ */
95
+ scroll: boolean;
63
96
  };
@@ -27,4 +27,20 @@ export type ModelInfo = {
27
27
  * numbers are already columns.
28
28
  */
29
29
  tagline?: string | null;
30
+ /**
31
+ * Why this one cannot be chosen here, in a sentence, or nothing when it can.
32
+ *
33
+ * A model a driver lists but will not run — the vendor CLI on this machine is too old for it,
34
+ * most often. Said rather than filtered out, for the reason a missing provider is listed
35
+ * rather than dropped: nothing there and not allowed are the same empty list, and only one of
36
+ * them is fixed by doing something. A model that silently vanishes from the picker is a
37
+ * question with nowhere to ask it — you go looking for the release notes, or for our bug
38
+ * tracker, and the answer was one `claude update` away the whole time.
39
+ *
40
+ * The sentence carries the fix, not just the diagnosis: "needs claude 2.1.251 — run `claude
41
+ * update`" is actionable and "unsupported model" is not. Anything reading a catalogue to
42
+ * *choose* — the startup default, `model.upgrade`, `model.line` — skips these; only a picker
43
+ * draws them, and draws them disabled.
44
+ */
45
+ unavailable?: string | null;
30
46
  };
@@ -63,6 +63,7 @@ export type PluginEvent =
63
63
  | { "type": "composer_changed"; text: string }
64
64
  | { "type": "event"; name: string; data?: unknown; from: string }
65
65
  | { "type": "var_changed"; scope: VarScope; key: string; value?: unknown }
66
+ | { "type": "project_moved"; from: string; to: string }
66
67
  | { "type": "highlight_changed"; names: Array<string> }
67
68
  | { "type": "contributions_changed"; point: string }
68
69
  | { "type": "swarm_changed" }
@@ -0,0 +1,30 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { ChecksState } from "./ChecksState";
3
+ import type { PullState } from "./PullState";
4
+
5
+ /**
6
+ * One pull request, as much of it as a row can use.
7
+ *
8
+ * **Keyed by `branch`**, which is what makes one call answer for every worktree at once: a
9
+ * repository's pull requests come back together and each checkout finds its own by the branch it
10
+ * has out. Asking per branch would be one network round trip per row in the panel.
11
+ */
12
+ export type PullRequest = {
13
+ number: number;
14
+ title: string;
15
+ /**
16
+ * For opening it. The forge's own URL, so nothing here has to know how to build one.
17
+ */
18
+ url: string;
19
+ /**
20
+ * The head branch. What a checkout matches itself against.
21
+ */
22
+ branch: string;
23
+ state: PullState;
24
+ checks: ChecksState;
25
+ /**
26
+ * How many checks have failed, for a row that wants to say `✗2` rather than `✗`.
27
+ */
28
+ checks_failed: number;
29
+ checks_total: number;
30
+ };
@@ -0,0 +1,11 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * Where a pull request has got to.
5
+ *
6
+ * Four states because that is what a person means by "what happened to it": one is a review you
7
+ * are waiting for, one is a review you have not asked for yet, one is done, and one is not going
8
+ * to happen. Draft is its own state rather than a flag on `Open` because on a row it is the whole
9
+ * difference between "somebody should look at this" and "I am still writing it".
10
+ */
11
+ export type PullState = "open" | "draft" | "merged" | "closed";
@@ -0,0 +1,16 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * How far to move a window's scroll. See [`crate::ApiCall::WinScroll`].
5
+ *
6
+ * Verbs rather than a row number, for the reason [`CursorMotion`] is: only the core knows how many
7
+ * buffer rows the frontend last drew, so "half a screen" is a question the caller cannot answer and
8
+ * a caller that tried would answer it with a height. Signed, so one variant covers both directions
9
+ * and a binding for `<C-d>` is the binding for `<C-u>` with the sign flipped.
10
+ */
11
+ export type ScrollAmount =
12
+ | { "kind": "lines"; n: number }
13
+ | { "kind": "half"; n: number }
14
+ | { "kind": "page"; n: number }
15
+ | { "kind": "top" }
16
+ | { "kind": "bottom" };
@@ -24,4 +24,20 @@ export type TabInfo = {
24
24
  * rather than in whichever pane happens to be first.
25
25
  */
26
26
  active_pane: PaneId;
27
+ /**
28
+ * Which strip this tab is on — the conversation it belongs to.
29
+ *
30
+ * **A tab belongs to a conversation, and the bar shows one conversation's tabs.** A shell
31
+ * opened while reading one is *that* conversation's shell: it was started in its directory,
32
+ * about its work, and a bar that goes on showing it after you have switched is a bar about
33
+ * the terminal rather than about what you are doing. The tabs of the conversations you are
34
+ * not in are not closed, and nothing in them stops — they are off the strip until you go
35
+ * back, which is what makes leaving a build running and returning to it work.
36
+ *
37
+ * Opaque here and in the editor, which only ever asks whether two of them are equal: the host
38
+ * fills it with a [`crate::SessionId`], and a plugin that grouped its tabs by project would be
39
+ * using it exactly as intended. `None` is a group of its own — where a tab nobody has filed
40
+ * sits, and what every tab was before this existed.
41
+ */
42
+ group?: string | null;
27
43
  };
@@ -81,4 +81,21 @@ export type UiEvent =
81
81
  }
82
82
  | { "type": "clipboard"; text: string }
83
83
  | { "type": "flush" }
84
- | { "type": "shutdown" };
84
+ | {
85
+ "type": "shutdown";
86
+ /**
87
+ * Whether it is coming straight back, and this terminal should follow it.
88
+ *
89
+ * `neosh stop` and a restart are the same shutdown — conversations flushed, plugins torn
90
+ * down — and they differ entirely in what the *terminal* should do next. Stopping is
91
+ * somebody finishing; a restart is the second half of an update, and a terminal that
92
+ * exits to the shell there has turned "finish updating" into "and now type `neosh`
93
+ * again", which is the step nobody knows is owed. It cannot be worked out at the far end
94
+ * either: from a closed socket, a workspace that stopped and a workspace that is
95
+ * restarting look identical.
96
+ *
97
+ * `#[serde(default)]` so an older terminal reads it as an ordinary stop, which is the
98
+ * behaviour it already has.
99
+ */
100
+ restarting: boolean;
101
+ };
@@ -33,7 +33,27 @@ export type UpdateStatus = {
33
33
  */
34
34
  error?: string | null;
35
35
  /**
36
- * Whether an update has been downloaded and is waiting for a restart.
36
+ * Whether the binary on disk is not the one this process is running.
37
+ *
38
+ * Not "whether neosh downloaded something": most installs are updated by somebody else's
39
+ * package manager, in another terminal, and the workspace goes on executing the inode it
40
+ * started with — which is the whole failure this field exists to name. `brew upgrade neosh`
41
+ * finishes, says so, and leaves a workspace running the old code with nothing on screen to
42
+ * say why the thing you just installed is not there. So this is a `stat` of the running
43
+ * executable against the stamp taken at startup, and it is true for a self-update, a
44
+ * `brew upgrade`, an `npm install -g` and a re-run of `install.sh` alike.
45
+ *
46
+ * Never set when there is no binary left to restart onto: a promise of a restart that cannot
47
+ * come back is worse than saying nothing.
37
48
  */
38
49
  restart_pending: boolean;
50
+ /**
51
+ * What is waiting on disk, when it could be read.
52
+ *
53
+ * Asked of the binary itself (`neosh --version`) rather than assumed to be
54
+ * [`latest`](UpdateStatus::latest): somebody who ran `brew upgrade` got whatever Homebrew had,
55
+ * which is not always the newest release, and a row naming the wrong number is a row that
56
+ * teaches you not to read it. `None` means *something* changed and it would not say what.
57
+ */
58
+ restart_version?: string | null;
39
59
  };