@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 +1 -1
- package/src/generated/ApiCall.ts +98 -0
- package/src/generated/ApiOk.ts +2 -0
- package/src/generated/ChecksState.ts +14 -0
- package/src/generated/FloatConfig.ts +33 -0
- package/src/generated/ModelInfo.ts +16 -0
- package/src/generated/PluginEvent.ts +1 -0
- package/src/generated/PullRequest.ts +30 -0
- package/src/generated/PullState.ts +11 -0
- package/src/generated/ScrollAmount.ts +16 -0
- package/src/generated/TabInfo.ts +16 -0
- package/src/generated/UiEvent.ts +18 -1
- package/src/generated/UpdateStatus.ts +21 -1
- package/src/index.ts +314 -10
- package/src/ui.ts +276 -55
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type { CredentialSource } from "./generated/CredentialSource";
|
|
|
36
36
|
import type { CursorMotion } from "./generated/CursorMotion";
|
|
37
37
|
import type { CursorShape } from "./generated/CursorShape";
|
|
38
38
|
import type { SelectShape } from "./generated/SelectShape";
|
|
39
|
+
import type { ScrollAmount } from "./generated/ScrollAmount";
|
|
39
40
|
import type { DiffTarget } from "./generated/DiffTarget";
|
|
40
41
|
import type { Dock } from "./generated/Dock";
|
|
41
42
|
import type { Gravity } from "./generated/Gravity";
|
|
@@ -136,6 +137,9 @@ import type { Viewport } from "./generated/Viewport";
|
|
|
136
137
|
import type { WindowId } from "./generated/WindowId";
|
|
137
138
|
import type { WindowInfo } from "./generated/WindowInfo";
|
|
138
139
|
import type { WindowLayout } from "./generated/WindowLayout";
|
|
140
|
+
import type { ChecksState } from "./generated/ChecksState";
|
|
141
|
+
import type { PullRequest } from "./generated/PullRequest";
|
|
142
|
+
import type { PullState } from "./generated/PullState";
|
|
139
143
|
import type { WorktreeInfo } from "./generated/WorktreeInfo";
|
|
140
144
|
|
|
141
145
|
export type {
|
|
@@ -153,12 +157,15 @@ export type {
|
|
|
153
157
|
CostBasis, QuotaCredits, QuotaSample, QuotaSeverity, QuotaSnapshot, QuotaSource, QuotaWindow,
|
|
154
158
|
InstallMethod, UpdateOutcome, UpdateStatus,
|
|
155
159
|
UsageBucket, UsageHistory, UsageResolution, UsageScanSource,
|
|
156
|
-
Rect, RepoInfo, RepoStatus, SelectShape, SessionId, SessionInfo, StatusAlign, StatusSegment, StopReason,
|
|
160
|
+
Rect, RepoInfo, RepoStatus, ScrollAmount, SelectShape, SessionId, SessionInfo, StatusAlign, StatusSegment, StopReason,
|
|
157
161
|
SurfaceCell, SurfaceId, TextEdit, ToolCall, ToolDef, ToolResult, TurnRequest, Usage,
|
|
158
162
|
NodeCapabilities, NodeId, NodeInfo, ProjectKey, RemoteProject, StreamEvent,
|
|
159
163
|
SwarmAgent, SwarmNode, SwarmStranger,
|
|
160
164
|
VarScope, ViewId, ViewInfo, Viewport,
|
|
161
165
|
WindowId, WindowInfo, WindowLayout,
|
|
166
|
+
ChecksState,
|
|
167
|
+
PullRequest,
|
|
168
|
+
PullState,
|
|
162
169
|
WorktreeInfo,
|
|
163
170
|
};
|
|
164
171
|
|
|
@@ -368,6 +375,16 @@ export interface FloatOptions {
|
|
|
368
375
|
border?: FloatConfig["border"];
|
|
369
376
|
borderHl?: string;
|
|
370
377
|
title?: string;
|
|
378
|
+
/**
|
|
379
|
+
* A strip on the bottom border: what the keys here do.
|
|
380
|
+
*
|
|
381
|
+
* On the border rather than in the buffer, because a key strip written as a row of content is one
|
|
382
|
+
* that scrolls away exactly when it is wanted, and is the first thing clipped on a terminal too
|
|
383
|
+
* short for the panel. It costs no content row and cannot be scrolled off.
|
|
384
|
+
*
|
|
385
|
+
* Clipped to the border's width like the title, so it is for a legend and not for prose.
|
|
386
|
+
*/
|
|
387
|
+
footer?: string;
|
|
371
388
|
closeOnBlur?: boolean;
|
|
372
389
|
focusable?: boolean;
|
|
373
390
|
/**
|
|
@@ -384,6 +401,24 @@ export interface FloatOptions {
|
|
|
384
401
|
* control sheet. Not for a hint or a hover card.
|
|
385
402
|
*/
|
|
386
403
|
modal?: boolean;
|
|
404
|
+
/**
|
|
405
|
+
* This panel is a place you can move in when it does not fit.
|
|
406
|
+
*
|
|
407
|
+
* You ask for a height and get whatever there is: `{ kind: "max", n: 30 }` on a twenty-row
|
|
408
|
+
* terminal is ten rows of panel and twenty rows of content drawn nowhere, with no key pointed at
|
|
409
|
+
* them and nothing on screen to say they exist. Set this and the workspace's scroll keys resolve
|
|
410
|
+
* here — `j`/`k`, `^D`/`^U`, `^F`/`^B`, `gg`/`G`, the arrows and the paging keys — along with the
|
|
411
|
+
* mouse wheel, and a bar appears down the right border while anything is hidden.
|
|
412
|
+
*
|
|
413
|
+
* They are ordinary bindings on `{ kind: "buf_kind", name: "neosh.scroll" }`, so `^Z` lists them
|
|
414
|
+
* and `init.ts` moves them. That scope sits *below* your buffer's own kind, so a key you bind on
|
|
415
|
+
* your panel is still yours — and above `global`, so it works under `modal` too.
|
|
416
|
+
*
|
|
417
|
+
* For a panel of rows you read: a key sheet, a diff, a status, a help screen. **Not** for one with
|
|
418
|
+
* a cursor of its own — a picker, a list — which already scrolls to keep the cursor on screen and
|
|
419
|
+
* whose filter would lose `j` and `G` to this.
|
|
420
|
+
*/
|
|
421
|
+
scroll?: boolean;
|
|
387
422
|
}
|
|
388
423
|
|
|
389
424
|
export interface Neosh {
|
|
@@ -415,6 +450,8 @@ export interface Neosh {
|
|
|
415
450
|
readonly opt: OptionApi;
|
|
416
451
|
readonly state: StateApi;
|
|
417
452
|
readonly vars: VarApi;
|
|
453
|
+
/** The directories somebody works in. See {@link ProjectApi}. */
|
|
454
|
+
readonly project: ProjectApi;
|
|
418
455
|
readonly ext: ExtensionApi;
|
|
419
456
|
readonly event: EventApi;
|
|
420
457
|
readonly swarm: SwarmApi;
|
|
@@ -625,21 +662,63 @@ export interface PaneApi {
|
|
|
625
662
|
equalize(view?: ViewId): Promise<void>;
|
|
626
663
|
}
|
|
627
664
|
|
|
628
|
-
/**
|
|
665
|
+
/**
|
|
666
|
+
* The tabs of one terminal. Each is a title, a tree of panes, and the conversation it belongs to.
|
|
667
|
+
*
|
|
668
|
+
* **A tab belongs to a conversation, and the bar shows one conversation's tabs** — that is
|
|
669
|
+
* {@link TabInfo.group}, and it is why a shell opened while reading one conversation is not still
|
|
670
|
+
* on the bar after you have switched to another. The tabs of the conversations you are not in are
|
|
671
|
+
* not closed and nothing in them stops; they are off the strip until you go back.
|
|
672
|
+
*
|
|
673
|
+
* Which splits these calls in two. {@link TabApi.list} answers with *every* tab of a terminal,
|
|
674
|
+
* whatever bar it is on, because "what is open here" is a real question — and {@link TabApi.step},
|
|
675
|
+
* {@link TabApi.move} and the numbers on the strip all count the tabs of one conversation, because
|
|
676
|
+
* a key about where you are looking must not change what you are working on.
|
|
677
|
+
*/
|
|
629
678
|
export interface TabApi {
|
|
630
|
-
/**
|
|
679
|
+
/**
|
|
680
|
+
* Every tab of a terminal, its panes, and which one is on screen.
|
|
681
|
+
*
|
|
682
|
+
* All of them, including the ones on other conversations' bars: filter by {@link TabInfo.group}
|
|
683
|
+
* for what is drawn, which is the tabs sharing the active tab's group.
|
|
684
|
+
*/
|
|
631
685
|
list(view?: ViewId): Promise<{ tabs: TabInfo[]; active: TabId }>;
|
|
632
686
|
/**
|
|
633
687
|
* Open a tab with one empty pane. `activate` goes to it — `false` is for putting work somewhere
|
|
634
688
|
* without moving the screen out from under whoever is reading it.
|
|
689
|
+
*
|
|
690
|
+
* On the bar you are looking at unless `group` says otherwise: a tab opened while reading a
|
|
691
|
+
* conversation is that conversation's, which is what a new tab almost always wants.
|
|
692
|
+
*/
|
|
693
|
+
create(opts?: {
|
|
694
|
+
title?: string;
|
|
695
|
+
activate?: boolean;
|
|
696
|
+
view?: ViewId;
|
|
697
|
+
group?: string;
|
|
698
|
+
}): Promise<TabId>;
|
|
699
|
+
/**
|
|
700
|
+
* Move a tab to another conversation's bar, or to nobody's with `null`. See
|
|
701
|
+
* {@link TabInfo.group}.
|
|
702
|
+
*
|
|
703
|
+
* It keeps its panes, its windows and anything running in them — this says which bar it is on
|
|
704
|
+
* and nothing else, so moving the tab you are *in* changes which other tabs are beside it.
|
|
635
705
|
*/
|
|
636
|
-
|
|
637
|
-
/** Close a tab and every pane in it. Rejects on a
|
|
706
|
+
group(tab: TabId, group: string | null): Promise<void>;
|
|
707
|
+
/** Close a tab and every pane in it. Rejects on a conversation's last tab. */
|
|
638
708
|
close(tab: TabId): Promise<void>;
|
|
639
709
|
select(tab: TabId): Promise<void>;
|
|
640
|
-
/**
|
|
710
|
+
/**
|
|
711
|
+
* Go `delta` tabs along, wrapping — a tab bar is a list you can see all of. Along *this
|
|
712
|
+
* conversation's* tabs: stepping into another one's would be a key that changes what you are
|
|
713
|
+
* working on.
|
|
714
|
+
*/
|
|
641
715
|
step(delta: number, view?: ViewId): Promise<void>;
|
|
642
|
-
/**
|
|
716
|
+
/**
|
|
717
|
+
* Move a tab along the bar, clamped at the ends. This is a drag; a drag that teleports is a bug.
|
|
718
|
+
*
|
|
719
|
+
* `to` counts the tabs on the bar, which is the number printed on the strip — the tabs of other
|
|
720
|
+
* conversations sit between these and are not places a drag can land.
|
|
721
|
+
*/
|
|
643
722
|
move(tab: TabId, to: number): Promise<void>;
|
|
644
723
|
/** Name a tab, or pass `null` to give it back its derived name. */
|
|
645
724
|
rename(tab: TabId, title: string | null): Promise<void>;
|
|
@@ -689,6 +768,19 @@ export interface WindowApi {
|
|
|
689
768
|
* its first row at `0`. Anything else shows the same thing either way.
|
|
690
769
|
*/
|
|
691
770
|
scrollTo(win: WindowId, topLine: number | null): Promise<void>;
|
|
771
|
+
/**
|
|
772
|
+
* Move a window's scroll by a line, a half screen, a screen, or all the way to an end.
|
|
773
|
+
*
|
|
774
|
+
* What {@link scrollTo} cannot express, because the arithmetic is not yours to do: "half a screen"
|
|
775
|
+
* is counted in the buffer rows the frontend actually drew — which on a wrapping window is fewer
|
|
776
|
+
* than its height — and the floor is the last screenful rather than the last line, so the bottom
|
|
777
|
+
* is a full panel and not one row above an empty one. Both numbers are a frame old by the time
|
|
778
|
+
* {@link viewport} hands them to you; this reads them where they live.
|
|
779
|
+
*
|
|
780
|
+
* The keys bound on `neosh.scroll` run exactly this. Call it directly for a scroll a key did not
|
|
781
|
+
* ask for — following output, or a button on a panel of your own.
|
|
782
|
+
*/
|
|
783
|
+
scroll(win: WindowId, amount: ScrollAmount): Promise<void>;
|
|
692
784
|
/**
|
|
693
785
|
* How big this window actually is, in cells.
|
|
694
786
|
*
|
|
@@ -1152,12 +1244,52 @@ export interface GitApi {
|
|
|
1152
1244
|
stage(paths?: string[]): Promise<void>;
|
|
1153
1245
|
unstage(paths?: string[]): Promise<void>;
|
|
1154
1246
|
commit(message: string): Promise<CommitInfo>;
|
|
1247
|
+
/**
|
|
1248
|
+
* `git fetch --prune`, answering with where the tree stands *after* it.
|
|
1249
|
+
*
|
|
1250
|
+
* **The one call that makes `ahead` and `behind` mean anything.** `status()` reads them off
|
|
1251
|
+
* `git status --branch`, which compares HEAD with the remote-tracking ref sitting on this disk —
|
|
1252
|
+
* so a panel drawing `↓0` from it is reporting the state of the world as of whenever this
|
|
1253
|
+
* checkout last spoke to a remote. Call this first and the number is news; do not, and it is
|
|
1254
|
+
* archaeology.
|
|
1255
|
+
*
|
|
1256
|
+
* It answers with the fresh {@link RepoStatus} rather than nothing, so "fetch and see where I am"
|
|
1257
|
+
* is one round trip. Prunes, because a picker offering six branches that were deleted when their
|
|
1258
|
+
* pull requests merged is worse than one that is a fetch behind.
|
|
1259
|
+
*
|
|
1260
|
+
* A write, and the reason is the network rather than the working tree, which does not move:
|
|
1261
|
+
* this contacts a remote and writes refs. It fails rather than hangs when there are no
|
|
1262
|
+
* credentials — stdin is closed and every askpass unset — which is what makes it safe to put on
|
|
1263
|
+
* a timer. Expect it to reject routinely: no remote, no network, a key the host will not take.
|
|
1264
|
+
*/
|
|
1265
|
+
fetch(opts?: { cwd?: string }): Promise<RepoStatus>;
|
|
1266
|
+
/**
|
|
1267
|
+
* Every pull request the forge knows about for this repository, open and closed.
|
|
1268
|
+
*
|
|
1269
|
+
* **One call answers for every checkout.** They come back keyed by
|
|
1270
|
+
* {@link PullRequest.branch}, so a repository with six worktrees is one request and six lookups
|
|
1271
|
+
* — which is the difference between a panel that costs a round trip every few minutes and one
|
|
1272
|
+
* that costs six.
|
|
1273
|
+
*
|
|
1274
|
+
* Answered by `gh`, the CLI the user has already signed into, so tokens, enterprise hosts, SSO
|
|
1275
|
+
* and rate limits are already handled and the answer is the one they get in their own terminal.
|
|
1276
|
+
*
|
|
1277
|
+
* **Rejects rather than answering empty** when it cannot know — no `gh`, not signed in, no
|
|
1278
|
+
* remote on a forge. An empty list is the claim "this repository has no pull requests", and
|
|
1279
|
+
* three of the four ways this fails would be making it falsely. Catch it, say it once, and do
|
|
1280
|
+
* not say it again every tick.
|
|
1281
|
+
*/
|
|
1282
|
+
pulls(opts?: { cwd?: string }): Promise<PullRequest[]>;
|
|
1155
1283
|
/**
|
|
1156
1284
|
* `git pull`, answering with git's own summary — "Already up to date.", the fast-forward range —
|
|
1157
1285
|
* because those are different answers and a caller showing neither is a caller nobody trusts.
|
|
1158
1286
|
* `cwd` picks the repository, as everywhere; absent means the conversation's own.
|
|
1287
|
+
*
|
|
1288
|
+
* `rebase` replays this branch's commits on top of what arrived instead of merging them. It is
|
|
1289
|
+
* the answer to a diverged branch and it rewrites local commits, so ask before you set it — the
|
|
1290
|
+
* caller this exists for is a panel that has just said "diverged" and offered the choice.
|
|
1159
1291
|
*/
|
|
1160
|
-
pull(opts?: { cwd?: string }): Promise<string>;
|
|
1292
|
+
pull(opts?: { cwd?: string; rebase?: boolean }): Promise<string>;
|
|
1161
1293
|
addWorktree(
|
|
1162
1294
|
path: string,
|
|
1163
1295
|
branch: string,
|
|
@@ -1169,6 +1301,60 @@ export interface GitApi {
|
|
|
1169
1301
|
* exactly that one.
|
|
1170
1302
|
*/
|
|
1171
1303
|
removeWorktree(path: string, opts?: { force?: boolean; cwd?: string }): Promise<void>;
|
|
1304
|
+
/**
|
|
1305
|
+
* Move a worktree to `dest` — `git worktree move`, and everything that has to follow it.
|
|
1306
|
+
*
|
|
1307
|
+
* A worktree's path is an identity here, not a coordinate: it is the key of the project facts
|
|
1308
|
+
* every list draws, of the conversations living in it, of the project vars holding a pin and a
|
|
1309
|
+
* fold state, and of the working directory each vendor CLI was started in. This call moves all
|
|
1310
|
+
* of them, which is why it is a call rather than a `git worktree move` you could have run
|
|
1311
|
+
* yourself — the git part is the part that was never hard.
|
|
1312
|
+
*
|
|
1313
|
+
* `dest` is the full path it lands at. Its parent is created; the leaf must not exist.
|
|
1314
|
+
*
|
|
1315
|
+
* Refused while a turn is running anywhere in the tree, because a CLI holds its working
|
|
1316
|
+
* directory from the moment it is spawned and would otherwise write the file it is editing into
|
|
1317
|
+
* the place the tree used to be. Interrupt it and ask again.
|
|
1318
|
+
*
|
|
1319
|
+
* `cwd` names the repository, as everywhere.
|
|
1320
|
+
*/
|
|
1321
|
+
moveWorktree(path: string, dest: string, opts?: { cwd?: string }): Promise<void>;
|
|
1322
|
+
/**
|
|
1323
|
+
* Clone `url` into `path`, resolving to the path once it is there.
|
|
1324
|
+
*
|
|
1325
|
+
* The one call here with no `cwd`: everything else asks a repository a question, and this one
|
|
1326
|
+
* arrives before there is a repository to ask. `path` is absolute and its parent need not
|
|
1327
|
+
* exist — cloning into a location you have just invented is the ordinary case.
|
|
1328
|
+
*
|
|
1329
|
+
* **It reports progress while it runs**, on the bus as {@link CLONE_EVENT}, so a caller that
|
|
1330
|
+
* wants to draw a clone rather than block on one subscribes before awaiting this. Keyed by
|
|
1331
|
+
* `path`, because a workspace may be cloning two things at once.
|
|
1332
|
+
*
|
|
1333
|
+
* Needs `vcs_write` in the manifest, like every other call here that changes a disk.
|
|
1334
|
+
*/
|
|
1335
|
+
clone(url: string, path: string): Promise<string>;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* What {@link GitApi.clone} says about itself while it runs, on {@link EventsApi.on}.
|
|
1340
|
+
*
|
|
1341
|
+
* `phase` is git's own word for what it is doing — `Receiving objects`, `Resolving deltas` — and
|
|
1342
|
+
* `percent` is absent for the phases that have no total, which draw as a spinner rather than as a
|
|
1343
|
+
* bar. `done` arrives exactly once per clone, on success and on failure alike, because a panel
|
|
1344
|
+
* drawing itself from these has no other way to learn it may stop.
|
|
1345
|
+
*/
|
|
1346
|
+
export const CLONE_EVENT = "neosh.git.clone";
|
|
1347
|
+
|
|
1348
|
+
/** One {@link CLONE_EVENT} payload. */
|
|
1349
|
+
export interface CloneProgress {
|
|
1350
|
+
/** Which clone this is about. The key, since two may be running. */
|
|
1351
|
+
path: string;
|
|
1352
|
+
url: string;
|
|
1353
|
+
phase: string;
|
|
1354
|
+
percent?: number | null;
|
|
1355
|
+
done?: boolean;
|
|
1356
|
+
/** Present, with git's own last word in it, only when the clone failed. */
|
|
1357
|
+
error?: string;
|
|
1172
1358
|
}
|
|
1173
1359
|
|
|
1174
1360
|
/**
|
|
@@ -1190,6 +1376,32 @@ export interface GenApi {
|
|
|
1190
1376
|
* do not each reimplement that. Rejects if there is no JSON in the response at all.
|
|
1191
1377
|
*/
|
|
1192
1378
|
json<T = unknown>(prompt: string, opts?: { system?: string; selection?: ModelSelection }): Promise<T>;
|
|
1379
|
+
/**
|
|
1380
|
+
* One value, asked for as JSON and accepted however it comes back.
|
|
1381
|
+
*
|
|
1382
|
+
* The shape almost every generating plugin actually wants: a branch name, a thread title, a PR
|
|
1383
|
+
* subject — one string, asked for as `{"branch": …}` because that is how you pin a model down,
|
|
1384
|
+
* and answered as a bare `fix/composer-paste` often enough to matter. Both are the same answer,
|
|
1385
|
+
* and {@link GenApi.json} rejects the second one — which is a correct name thrown away, with
|
|
1386
|
+
* nothing on screen to say so.
|
|
1387
|
+
*
|
|
1388
|
+
* So the key is passed down and a bare reply is read as its value. Everything
|
|
1389
|
+
* {@link GenApi.json} tolerates is tolerated first and unchanged; this is only what happens when
|
|
1390
|
+
* there is no JSON at all. Rejects on an empty answer, or on prose it will not guess at.
|
|
1391
|
+
*
|
|
1392
|
+
* For one value only. A commit message is a subject and a body, and there is no answering the
|
|
1393
|
+
* question of which one a lone paragraph is — that stays {@link GenApi.json}.
|
|
1394
|
+
*
|
|
1395
|
+
* And only for a value you would know was wrong on sight. A branch name is that, and a wrong one
|
|
1396
|
+
* is one rename away; a thread title is any short line, and so is a refusal or a driver's own
|
|
1397
|
+
* error message — where nothing distinguishes an answer from a remark, the envelope is the
|
|
1398
|
+
* evidence, and {@link GenApi.json} is the call.
|
|
1399
|
+
*/
|
|
1400
|
+
field(
|
|
1401
|
+
prompt: string,
|
|
1402
|
+
key: string,
|
|
1403
|
+
opts?: { system?: string; selection?: ModelSelection },
|
|
1404
|
+
): Promise<string>;
|
|
1193
1405
|
}
|
|
1194
1406
|
|
|
1195
1407
|
/**
|
|
@@ -1449,6 +1661,27 @@ export interface VarApi {
|
|
|
1449
1661
|
): Disposable;
|
|
1450
1662
|
}
|
|
1451
1663
|
|
|
1664
|
+
/**
|
|
1665
|
+
* The directories somebody works in, and the one thing that can happen to one.
|
|
1666
|
+
*
|
|
1667
|
+
* A project *is* its path everywhere else in this API — `projectScope` keys vars by it, a panel's
|
|
1668
|
+
* list is a list of them, `SessionInfo.cwd` names one. Which is exactly why a path that changes
|
|
1669
|
+
* needs saying out loud rather than inferring: from the outside, a worktree that moved and a
|
|
1670
|
+
* project that was deleted while another was added are the same two facts in the same order.
|
|
1671
|
+
*/
|
|
1672
|
+
export interface ProjectApi {
|
|
1673
|
+
/**
|
|
1674
|
+
* A project's directory is now somewhere else — a worktree that was relocated.
|
|
1675
|
+
*
|
|
1676
|
+
* The host has already moved everything it owns by the time this arrives: the conversations, the
|
|
1677
|
+
* project vars, the names and branches every list draws. What is left is whatever *you* keyed by
|
|
1678
|
+
* the old path — a list of directories, a cache, a decoration target — and the point of getting
|
|
1679
|
+
* both ends in one event is that you can re-key in place instead of dropping a row and gaining a
|
|
1680
|
+
* stranger.
|
|
1681
|
+
*/
|
|
1682
|
+
onMove(cb: (e: { from: string; to: string }) => void): Disposable;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1452
1685
|
/** Sugar for the two scopes anything with a panel spends its time in. */
|
|
1453
1686
|
export function projectScope(cwd: string): VarScope {
|
|
1454
1687
|
return { scope: "project", cwd };
|
|
@@ -1600,6 +1833,17 @@ export interface UpdateApi {
|
|
|
1600
1833
|
* {@link UpdateStatus.error} when it is there.
|
|
1601
1834
|
*/
|
|
1602
1835
|
check(force?: boolean): Promise<UpdateStatus>;
|
|
1836
|
+
/**
|
|
1837
|
+
* The same answer, from this machine alone, never touching the network.
|
|
1838
|
+
*
|
|
1839
|
+
* Which is enough for the half that matters most often. {@link UpdateStatus.restart_pending} is a
|
|
1840
|
+
* `stat` of one file — *is the binary on disk the binary this workspace is running* — and it goes
|
|
1841
|
+
* true when somebody runs `brew upgrade neosh` in another terminal, which is how most installs
|
|
1842
|
+
* are actually updated and the moment nothing used to say anything. Cheap enough to poll on a
|
|
1843
|
+
* tick; {@link check} is not, and a panel that used it for this would be a workspace making an
|
|
1844
|
+
* HTTP request every thirty seconds.
|
|
1845
|
+
*/
|
|
1846
|
+
local(): Promise<UpdateStatus>;
|
|
1603
1847
|
/**
|
|
1604
1848
|
* Update, by whichever route this install takes.
|
|
1605
1849
|
*
|
|
@@ -1876,6 +2120,7 @@ interface Registered {
|
|
|
1876
2120
|
composerListeners: Array<(e: { text: string }) => void>;
|
|
1877
2121
|
activityListeners: Array<(e: { session: SessionId; turn: string; activity: Activity }) => void>;
|
|
1878
2122
|
varListeners: Array<(e: { scope: VarScope; key: string; value: unknown }) => void>;
|
|
2123
|
+
projectMovedListeners: Array<(e: { from: string; to: string }) => void>;
|
|
1879
2124
|
swarmListeners: Array<() => void>;
|
|
1880
2125
|
quotaListeners: Array<(snapshot: QuotaSnapshot) => void>;
|
|
1881
2126
|
swarmStreamListeners: Array<
|
|
@@ -1922,6 +2167,7 @@ function reg(plugin: string): Registered {
|
|
|
1922
2167
|
composerListeners: [],
|
|
1923
2168
|
activityListeners: [],
|
|
1924
2169
|
varListeners: [],
|
|
2170
|
+
projectMovedListeners: [],
|
|
1925
2171
|
swarmListeners: [],
|
|
1926
2172
|
quotaListeners: [],
|
|
1927
2173
|
swarmStreamListeners: [],
|
|
@@ -1973,9 +2219,11 @@ function floatConfig(o: FloatOptions = {}): FloatConfig {
|
|
|
1973
2219
|
border: o.border ?? "rounded",
|
|
1974
2220
|
border_hl: o.borderHl ?? null,
|
|
1975
2221
|
title: o.title ?? null,
|
|
2222
|
+
footer: o.footer ?? null,
|
|
1976
2223
|
close_on_blur: o.closeOnBlur ?? false,
|
|
1977
2224
|
focusable: o.focusable ?? true,
|
|
1978
2225
|
modal: o.modal ?? false,
|
|
2226
|
+
scroll: o.scroll ?? false,
|
|
1979
2227
|
};
|
|
1980
2228
|
}
|
|
1981
2229
|
|
|
@@ -2134,6 +2382,11 @@ function build(
|
|
|
2134
2382
|
return listener(r.varListeners, cb);
|
|
2135
2383
|
},
|
|
2136
2384
|
},
|
|
2385
|
+
project: {
|
|
2386
|
+
onMove(cb) {
|
|
2387
|
+
return listener(r.projectMovedListeners, cb);
|
|
2388
|
+
},
|
|
2389
|
+
},
|
|
2137
2390
|
ext: {
|
|
2138
2391
|
async contribute(point, id, item, opts) {
|
|
2139
2392
|
await c({ call: "ext_contribute", point, id, item, priority: opts?.priority ?? 0 });
|
|
@@ -2225,7 +2478,16 @@ function build(
|
|
|
2225
2478
|
},
|
|
2226
2479
|
update: {
|
|
2227
2480
|
async check(force) {
|
|
2228
|
-
return expect(
|
|
2481
|
+
return expect(
|
|
2482
|
+
await c({ call: "update_check", force: force ?? false, local: false }),
|
|
2483
|
+
"update",
|
|
2484
|
+
).update;
|
|
2485
|
+
},
|
|
2486
|
+
async local() {
|
|
2487
|
+
return expect(
|
|
2488
|
+
await c({ call: "update_check", force: false, local: true }),
|
|
2489
|
+
"update",
|
|
2490
|
+
).update;
|
|
2229
2491
|
},
|
|
2230
2492
|
async apply() {
|
|
2231
2493
|
return expect(await c({ call: "update_apply" }), "update_applied").outcome;
|
|
@@ -2448,10 +2710,14 @@ function build(
|
|
|
2448
2710
|
view: opts?.view,
|
|
2449
2711
|
title: opts?.title,
|
|
2450
2712
|
activate: opts?.activate ?? true,
|
|
2713
|
+
group: opts?.group,
|
|
2451
2714
|
}),
|
|
2452
2715
|
"tab",
|
|
2453
2716
|
).tab;
|
|
2454
2717
|
},
|
|
2718
|
+
async group(tab, group) {
|
|
2719
|
+
await c({ call: "tab_group", tab, group });
|
|
2720
|
+
},
|
|
2455
2721
|
async close(tab) {
|
|
2456
2722
|
await c({ call: "tab_close", tab });
|
|
2457
2723
|
},
|
|
@@ -2499,6 +2765,9 @@ function build(
|
|
|
2499
2765
|
async scrollTo(win, topLine) {
|
|
2500
2766
|
await c({ call: "win_scroll_to", win, top_line: topLine });
|
|
2501
2767
|
},
|
|
2768
|
+
async scroll(win, amount) {
|
|
2769
|
+
await c({ call: "win_scroll", win, amount });
|
|
2770
|
+
},
|
|
2502
2771
|
async viewport(win) {
|
|
2503
2772
|
return expect(await c({ call: "win_get_viewport", win }), "viewport").viewport ?? null;
|
|
2504
2773
|
},
|
|
@@ -2817,8 +3086,16 @@ function build(
|
|
|
2817
3086
|
cwd: opts?.cwd ?? null,
|
|
2818
3087
|
});
|
|
2819
3088
|
},
|
|
3089
|
+
async fetch(opts) {
|
|
3090
|
+
const v = await c({ call: "git_fetch", cwd: opts?.cwd ?? null });
|
|
3091
|
+
return expect(v, "status").status;
|
|
3092
|
+
},
|
|
3093
|
+
async pulls(opts) {
|
|
3094
|
+
const v = await c({ call: "forge_pulls", cwd: opts?.cwd ?? null });
|
|
3095
|
+
return expect(v, "pulls").pulls;
|
|
3096
|
+
},
|
|
2820
3097
|
async pull(opts) {
|
|
2821
|
-
const v = await c({ call: "git_pull", cwd: opts?.cwd ?? null });
|
|
3098
|
+
const v = await c({ call: "git_pull", cwd: opts?.cwd ?? null, rebase: opts?.rebase ?? false });
|
|
2822
3099
|
return expect(v, "text").text;
|
|
2823
3100
|
},
|
|
2824
3101
|
async removeWorktree(path, opts) {
|
|
@@ -2829,6 +3106,13 @@ function build(
|
|
|
2829
3106
|
cwd: opts?.cwd ?? null,
|
|
2830
3107
|
});
|
|
2831
3108
|
},
|
|
3109
|
+
async moveWorktree(path, dest, opts) {
|
|
3110
|
+
await c({ call: "git_move_worktree", path, dest, cwd: opts?.cwd ?? null });
|
|
3111
|
+
},
|
|
3112
|
+
async clone(url, path) {
|
|
3113
|
+
const v = await c({ call: "git_clone", url, path });
|
|
3114
|
+
return expect(v, "text").text;
|
|
3115
|
+
},
|
|
2832
3116
|
},
|
|
2833
3117
|
gen: {
|
|
2834
3118
|
async complete(prompt, opts) {
|
|
@@ -2851,6 +3135,23 @@ function build(
|
|
|
2851
3135
|
});
|
|
2852
3136
|
return expect(v, "json").value as never;
|
|
2853
3137
|
},
|
|
3138
|
+
async field(prompt, key, opts) {
|
|
3139
|
+
const v = await c({
|
|
3140
|
+
call: "gen_complete",
|
|
3141
|
+
prompt,
|
|
3142
|
+
system: opts?.system ?? null,
|
|
3143
|
+
json: true,
|
|
3144
|
+
field: key,
|
|
3145
|
+
selection: opts?.selection ?? null,
|
|
3146
|
+
});
|
|
3147
|
+
const value = (expect(v, "json").value as Record<string, unknown>)?.[key];
|
|
3148
|
+
// A key that came back as something other than a non-empty string is an answer to a
|
|
3149
|
+
// different question, and the caller is about to name a branch after it.
|
|
3150
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
3151
|
+
throw new Error(`the model returned no ${key}`);
|
|
3152
|
+
}
|
|
3153
|
+
return value.trim();
|
|
3154
|
+
},
|
|
2854
3155
|
},
|
|
2855
3156
|
view: {
|
|
2856
3157
|
async list() {
|
|
@@ -3166,6 +3467,9 @@ async function dispatchEvent(
|
|
|
3166
3467
|
for (const cb of r.varListeners)
|
|
3167
3468
|
cb({ scope: ev.scope, key: ev.key, value: ev.value });
|
|
3168
3469
|
break;
|
|
3470
|
+
case "project_moved":
|
|
3471
|
+
for (const cb of r.projectMovedListeners) cb({ from: ev.from, to: ev.to });
|
|
3472
|
+
break;
|
|
3169
3473
|
case "quota":
|
|
3170
3474
|
for (const cb of [...r.quotaListeners]) cb(ev.snapshot);
|
|
3171
3475
|
break;
|