@meistrari/remy-cli 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/remy.js +337 -111
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,19 +42,23 @@ Use `Tab` while writing the request to complete a local file path. Remy attaches
|
|
|
42
42
|
|
|
43
43
|
### Resume or follow up
|
|
44
44
|
|
|
45
|
-
Use
|
|
45
|
+
Use Up/Down to select a session in the dashboard, Left/Right to load the adjacent page, and `Enter` to open it. After the dashboard list or repository-review list has focus, Vim keys work too: `j`/`k` move, `h`/`l` go to the previous/next dashboard page (or clear/mark a repository), `G` goes to the last row, and `gg` goes to the first row. Press `Shift+L` in the dashboard to start a logout confirmation. In a session, type a follow-up and press `Enter` to send it.
|
|
46
|
+
|
|
47
|
+
Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. When the conversation has focus, press `Tab` to return to the composer.
|
|
46
48
|
|
|
47
49
|
| Control | Result |
|
|
48
50
|
| --- | --- |
|
|
49
51
|
| `Esc` | Interrupt the active turn; the session stays open. |
|
|
50
52
|
| `/complete` | Complete an open session after its active turn stops. |
|
|
53
|
+
| `/cancel` | Cancel the current session, including an active turn. |
|
|
51
54
|
| `/sessions` | Return to the dashboard. |
|
|
52
55
|
| `/new` | Start another new-session flow. |
|
|
56
|
+
| `/logout` | Sign out of Remy after confirmation. |
|
|
53
57
|
| `/help` | Show session controls. |
|
|
54
58
|
| `/exit` | Leave Remy. |
|
|
55
59
|
| `Ctrl+O` | Expand or collapse activity details. |
|
|
56
60
|
|
|
57
|
-
After a session becomes terminal, press `Esc` to return to the dashboard or `n` to start new work. From the dashboard, press `
|
|
61
|
+
After a session becomes terminal, press `Esc` to return to the dashboard or `n` to start new work. From the dashboard, press `q` to quit. `Shift+L` in the dashboard and `/logout` in a session ask for confirmation; press `y` to sign out or `Esc` to cancel. `Ctrl+C` exits Remy from the dashboard, wizard, or session view.
|
|
58
62
|
|
|
59
63
|
## Commands for direct or scripted use
|
|
60
64
|
|
|
@@ -112,7 +116,7 @@ remy login \
|
|
|
112
116
|
--target-application-id <target-application-uuid>
|
|
113
117
|
```
|
|
114
118
|
|
|
115
|
-
Remy stores endpoint configuration in `~/.config/remy/config.json` and tokens in `~/.local/state/remy/tokens.json`. Set `XDG_CONFIG_HOME` or `XDG_STATE_HOME` to use different base directories. `remy logout` removes tokens but retains endpoint configuration; pass `--api-url` to require that the supplied URL matches the active API host before logging out.
|
|
119
|
+
Remy stores endpoint configuration in `~/.config/remy/config.json` and tokens in `~/.local/state/remy/tokens.json`. Set `XDG_CONFIG_HOME` or `XDG_STATE_HOME` to use different base directories. It refreshes an expired access token before each Coding Agent API request, so long-running sessions and reconnects remain authenticated. `remy logout` removes tokens but retains endpoint configuration; pass `--api-url` to require that the supplied URL matches the active API host before logging out.
|
|
116
120
|
|
|
117
121
|
## Complete command reference
|
|
118
122
|
|
package/dist/remy.js
CHANGED
|
@@ -32820,6 +32820,13 @@ async function completeSession({ client, sessionId }) {
|
|
|
32820
32820
|
throw new CodingAgentProtocolError("Session completion response did not match the public API contract.", { cause: parsed.error });
|
|
32821
32821
|
return parsed.data;
|
|
32822
32822
|
}
|
|
32823
|
+
async function cancelSession({ client, sessionId }) {
|
|
32824
|
+
const response = await client.request(`/v1/sessions/${encodeURIComponent(sessionId)}/cancel`, { method: "PUT" });
|
|
32825
|
+
const parsed = sessionLifecycleResponseSchema.safeParse(await parseJson2(response, "Session cancellation response was not valid JSON."));
|
|
32826
|
+
if (!parsed.success)
|
|
32827
|
+
throw new CodingAgentProtocolError("Session cancellation response did not match the public API contract.", { cause: parsed.error });
|
|
32828
|
+
return parsed.data;
|
|
32829
|
+
}
|
|
32823
32830
|
async function listSessions({
|
|
32824
32831
|
client,
|
|
32825
32832
|
limit,
|
|
@@ -34355,7 +34362,7 @@ async function sleepMs(ms, signal) {
|
|
|
34355
34362
|
}
|
|
34356
34363
|
|
|
34357
34364
|
// src/tui/dashboard.ts
|
|
34358
|
-
import { bg, BoxRenderable, bold as bold2, CliRenderEvents, fg as fg3,
|
|
34365
|
+
import { bg, BoxRenderable, bold as bold2, CliRenderEvents as CliRenderEvents2, fg as fg3, ScrollBoxRenderable, stringToStyledText as stringToStyledText2, TextRenderable } from "@opentui/core";
|
|
34359
34366
|
|
|
34360
34367
|
// src/tui/composer-divider.ts
|
|
34361
34368
|
import { dim as dim2, fg as fg2, StyledText as StyledText2 } from "@opentui/core";
|
|
@@ -34551,18 +34558,20 @@ function renderComposerDivider({ width, tag, leadLabel }) {
|
|
|
34551
34558
|
}
|
|
34552
34559
|
|
|
34553
34560
|
// src/tui/renderer.ts
|
|
34554
|
-
import { createCliRenderer } from "@opentui/core";
|
|
34561
|
+
import { CliRenderEvents, createClipboard, createCliRenderer, createHostClipboard, createRendererClipboardAdapter } from "@opentui/core";
|
|
34555
34562
|
var rendererDestroyed = new WeakMap;
|
|
34556
34563
|
var enterAlternateScreen = "\x1B[?1049h";
|
|
34557
34564
|
var clearScreen = "\x1B[2J\x1B[H";
|
|
34558
34565
|
var exitAlternateScreen = "\x1B[?1049l";
|
|
34559
34566
|
var transitionScreenWriter;
|
|
34567
|
+
var ignoreTerminalFocus = (sequence) => sequence === "\x1B[I" || sequence === "\x1B[O";
|
|
34560
34568
|
async function createRemyRenderer({
|
|
34561
34569
|
createRenderer = createCliRenderer
|
|
34562
34570
|
} = {}) {
|
|
34563
34571
|
let resolveDestroyed = () => {
|
|
34564
34572
|
return;
|
|
34565
34573
|
};
|
|
34574
|
+
let selectionClipboard;
|
|
34566
34575
|
const destroyed = new Promise((resolve) => {
|
|
34567
34576
|
resolveDestroyed = resolve;
|
|
34568
34577
|
});
|
|
@@ -34571,9 +34580,34 @@ async function createRemyRenderer({
|
|
|
34571
34580
|
exitSignals: [],
|
|
34572
34581
|
screenMode: "alternate-screen",
|
|
34573
34582
|
useMouse: true,
|
|
34574
|
-
|
|
34583
|
+
prependInputHandlers: [ignoreTerminalFocus],
|
|
34584
|
+
onDestroy: () => {
|
|
34585
|
+
selectionClipboard?.dispose().catch(() => {
|
|
34586
|
+
return;
|
|
34587
|
+
});
|
|
34588
|
+
resolveDestroyed();
|
|
34589
|
+
}
|
|
34575
34590
|
});
|
|
34576
34591
|
transitionScreenWriter = undefined;
|
|
34592
|
+
try {
|
|
34593
|
+
selectionClipboard = createClipboard({
|
|
34594
|
+
host: createHostClipboard(),
|
|
34595
|
+
terminal: createRendererClipboardAdapter(renderer)
|
|
34596
|
+
});
|
|
34597
|
+
} catch {}
|
|
34598
|
+
renderer.on(CliRenderEvents.SELECTION, (selection) => {
|
|
34599
|
+
const text = selection.getSelectedText();
|
|
34600
|
+
if (text.length === 0)
|
|
34601
|
+
return;
|
|
34602
|
+
renderer.clearSelection();
|
|
34603
|
+
if (selectionClipboard) {
|
|
34604
|
+
selectionClipboard.writeText(text, { destination: "all-available" }).catch(() => {
|
|
34605
|
+
return;
|
|
34606
|
+
});
|
|
34607
|
+
return;
|
|
34608
|
+
}
|
|
34609
|
+
renderer.copyToClipboardOSC52(text);
|
|
34610
|
+
});
|
|
34577
34611
|
rendererDestroyed.set(renderer, destroyed);
|
|
34578
34612
|
return renderer;
|
|
34579
34613
|
}
|
|
@@ -34611,6 +34645,7 @@ async function createDashboardTui({
|
|
|
34611
34645
|
}) {
|
|
34612
34646
|
const renderer = await createRenderer();
|
|
34613
34647
|
let inputHandler;
|
|
34648
|
+
let keyHandler;
|
|
34614
34649
|
let resizeHandler;
|
|
34615
34650
|
let rendererDestroyed2 = false;
|
|
34616
34651
|
try {
|
|
@@ -34637,8 +34672,7 @@ async function createDashboardTui({
|
|
|
34637
34672
|
previewBand.visible = showPreview;
|
|
34638
34673
|
previewBand.content = showPreview ? formatSessionPreview({ session: selectedSession, width: contentWidth }) : stringToStyledText2("");
|
|
34639
34674
|
separator.content = renderComposerDivider({ width: contentWidth });
|
|
34640
|
-
footer.content =
|
|
34641
|
-
input.placeholder = logoutConfirmationOpen ? "Log out of Remy?" : "Type here to start a new session";
|
|
34675
|
+
footer.content = dashboardFooter({ loadingPage, pageError, rowsBelow, logoutConfirmationOpen });
|
|
34642
34676
|
renderer.requestRender();
|
|
34643
34677
|
}, finish = function(value) {
|
|
34644
34678
|
if (settled)
|
|
@@ -34650,6 +34684,11 @@ async function createDashboardTui({
|
|
|
34650
34684
|
return;
|
|
34651
34685
|
settled = true;
|
|
34652
34686
|
rejectDraft(error93);
|
|
34687
|
+
}, moveSelectionTo = function(index) {
|
|
34688
|
+
if (page.sessions.length === 0)
|
|
34689
|
+
return;
|
|
34690
|
+
selectedIndex = Math.max(0, Math.min(index, page.sessions.length - 1));
|
|
34691
|
+
render();
|
|
34653
34692
|
}, handleSessionKey = function(key) {
|
|
34654
34693
|
if (logoutConfirmationOpen) {
|
|
34655
34694
|
key.preventDefault();
|
|
@@ -34661,16 +34700,57 @@ async function createDashboardTui({
|
|
|
34661
34700
|
}
|
|
34662
34701
|
return;
|
|
34663
34702
|
}
|
|
34703
|
+
const listFocused = renderer.currentFocusedRenderable === listRegion;
|
|
34664
34704
|
if (key.name === "down") {
|
|
34665
34705
|
key.preventDefault();
|
|
34706
|
+
awaitingVimGoToTop = false;
|
|
34666
34707
|
moveSelection("next");
|
|
34667
34708
|
return;
|
|
34668
34709
|
}
|
|
34669
34710
|
if (key.name === "up") {
|
|
34670
34711
|
key.preventDefault();
|
|
34712
|
+
awaitingVimGoToTop = false;
|
|
34671
34713
|
moveSelection("previous");
|
|
34672
34714
|
return;
|
|
34673
34715
|
}
|
|
34716
|
+
if (key.name === "right") {
|
|
34717
|
+
key.preventDefault();
|
|
34718
|
+
awaitingVimGoToTop = false;
|
|
34719
|
+
loadAdjacentPage("next");
|
|
34720
|
+
return;
|
|
34721
|
+
}
|
|
34722
|
+
if (key.name === "left") {
|
|
34723
|
+
key.preventDefault();
|
|
34724
|
+
awaitingVimGoToTop = false;
|
|
34725
|
+
loadAdjacentPage("previous");
|
|
34726
|
+
return;
|
|
34727
|
+
}
|
|
34728
|
+
if (listFocused && (key.name === "G" || key.name === "g" && key.shift)) {
|
|
34729
|
+
key.preventDefault();
|
|
34730
|
+
awaitingVimGoToTop = false;
|
|
34731
|
+
moveSelectionTo(page.sessions.length - 1);
|
|
34732
|
+
return;
|
|
34733
|
+
}
|
|
34734
|
+
if (listFocused && key.name === "g") {
|
|
34735
|
+
key.preventDefault();
|
|
34736
|
+
if (awaitingVimGoToTop)
|
|
34737
|
+
moveSelectionTo(0);
|
|
34738
|
+
awaitingVimGoToTop = !awaitingVimGoToTop;
|
|
34739
|
+
return;
|
|
34740
|
+
}
|
|
34741
|
+
if (listFocused && (key.name === "j" || key.name === "k")) {
|
|
34742
|
+
key.preventDefault();
|
|
34743
|
+
awaitingVimGoToTop = false;
|
|
34744
|
+
moveSelection(key.name === "j" ? "next" : "previous");
|
|
34745
|
+
return;
|
|
34746
|
+
}
|
|
34747
|
+
if (listFocused && !key.shift && (key.name === "h" || key.name === "l")) {
|
|
34748
|
+
key.preventDefault();
|
|
34749
|
+
awaitingVimGoToTop = false;
|
|
34750
|
+
loadAdjacentPage(key.name === "l" ? "next" : "previous");
|
|
34751
|
+
return;
|
|
34752
|
+
}
|
|
34753
|
+
awaitingVimGoToTop = false;
|
|
34674
34754
|
if (key.name === "return" || key.name === "enter") {
|
|
34675
34755
|
key.preventDefault();
|
|
34676
34756
|
const session = page.sessions[selectedIndex];
|
|
@@ -34683,7 +34763,7 @@ async function createDashboardTui({
|
|
|
34683
34763
|
finish({ kind: "new" });
|
|
34684
34764
|
return;
|
|
34685
34765
|
}
|
|
34686
|
-
if (key.name === "l") {
|
|
34766
|
+
if (key.name === "L" || key.shift && key.name === "l") {
|
|
34687
34767
|
key.preventDefault();
|
|
34688
34768
|
logoutConfirmationOpen = true;
|
|
34689
34769
|
render();
|
|
@@ -34706,20 +34786,23 @@ async function createDashboardTui({
|
|
|
34706
34786
|
const previewBand = new TextRenderable(renderer, { content: "", flexShrink: 0 });
|
|
34707
34787
|
const separator = new TextRenderable(renderer, { content: "", flexShrink: 0 });
|
|
34708
34788
|
const footer = new TextRenderable(renderer, { content: "", flexShrink: 0 });
|
|
34709
|
-
|
|
34789
|
+
root.onMouseDown = (event) => {
|
|
34790
|
+
event.preventDefault();
|
|
34791
|
+
listRegion.focus();
|
|
34792
|
+
};
|
|
34710
34793
|
listRegion.add(content);
|
|
34711
34794
|
root.add(statusBand);
|
|
34712
34795
|
root.add(listRegion);
|
|
34713
34796
|
root.add(previewBand);
|
|
34714
34797
|
root.add(separator);
|
|
34715
34798
|
root.add(footer);
|
|
34716
|
-
root.add(input);
|
|
34717
34799
|
renderer.root.add(root);
|
|
34718
34800
|
let page = initialPage;
|
|
34719
34801
|
let pageIndex = 1;
|
|
34720
34802
|
let selectedIndex = 0;
|
|
34721
34803
|
let loadingPage = false;
|
|
34722
34804
|
let pageError;
|
|
34805
|
+
let awaitingVimGoToTop = false;
|
|
34723
34806
|
let logoutConfirmationOpen = false;
|
|
34724
34807
|
let destroyed = false;
|
|
34725
34808
|
let rendererDestroyPromise;
|
|
@@ -34734,16 +34817,10 @@ async function createDashboardTui({
|
|
|
34734
34817
|
resolveAction = resolve;
|
|
34735
34818
|
rejectDraft = reject;
|
|
34736
34819
|
});
|
|
34737
|
-
async function
|
|
34820
|
+
async function loadAdjacentPage(direction) {
|
|
34738
34821
|
if (loadingPage)
|
|
34739
34822
|
return;
|
|
34740
|
-
const isBoundary = direction === "next" ? selectedIndex === page.sessions.length - 1 : selectedIndex === 0;
|
|
34741
34823
|
const canLoad = direction === "next" ? page.hasMore : page.canGoPrevious;
|
|
34742
|
-
if (!isBoundary) {
|
|
34743
|
-
selectedIndex += direction === "next" ? 1 : -1;
|
|
34744
|
-
render();
|
|
34745
|
-
return;
|
|
34746
|
-
}
|
|
34747
34824
|
if (!canLoad)
|
|
34748
34825
|
return;
|
|
34749
34826
|
loadingPage = true;
|
|
@@ -34760,6 +34837,17 @@ async function createDashboardTui({
|
|
|
34760
34837
|
render();
|
|
34761
34838
|
}
|
|
34762
34839
|
}
|
|
34840
|
+
async function moveSelection(direction) {
|
|
34841
|
+
if (loadingPage || page.sessions.length === 0)
|
|
34842
|
+
return;
|
|
34843
|
+
const isBoundary = direction === "next" ? selectedIndex === page.sessions.length - 1 : selectedIndex === 0;
|
|
34844
|
+
if (!isBoundary) {
|
|
34845
|
+
selectedIndex += direction === "next" ? 1 : -1;
|
|
34846
|
+
render();
|
|
34847
|
+
return;
|
|
34848
|
+
}
|
|
34849
|
+
await loadAdjacentPage(direction);
|
|
34850
|
+
}
|
|
34763
34851
|
inputHandler = (sequence) => {
|
|
34764
34852
|
if (sequence === "\x03") {
|
|
34765
34853
|
finish({ kind: "interrupt" });
|
|
@@ -34767,16 +34855,17 @@ async function createDashboardTui({
|
|
|
34767
34855
|
}
|
|
34768
34856
|
return false;
|
|
34769
34857
|
};
|
|
34770
|
-
|
|
34858
|
+
keyHandler = handleSessionKey;
|
|
34771
34859
|
renderer.addInputHandler(inputHandler);
|
|
34772
|
-
renderer.on(
|
|
34860
|
+
renderer.keyInput.on("keypress", keyHandler);
|
|
34861
|
+
renderer.on(CliRenderEvents2.RENDER_ERROR, (event) => fail(event.error));
|
|
34773
34862
|
resizeHandler = () => {
|
|
34774
34863
|
if (!destroyed)
|
|
34775
34864
|
render();
|
|
34776
34865
|
};
|
|
34777
|
-
renderer.on(
|
|
34778
|
-
input.focus();
|
|
34866
|
+
renderer.on(CliRenderEvents2.RESIZE, resizeHandler);
|
|
34779
34867
|
render();
|
|
34868
|
+
listRegion.focus();
|
|
34780
34869
|
return {
|
|
34781
34870
|
waitForAction: async () => await action,
|
|
34782
34871
|
destroy() {
|
|
@@ -34785,8 +34874,10 @@ async function createDashboardTui({
|
|
|
34785
34874
|
destroyed = true;
|
|
34786
34875
|
if (inputHandler)
|
|
34787
34876
|
renderer.removeInputHandler(inputHandler);
|
|
34877
|
+
if (keyHandler)
|
|
34878
|
+
renderer.keyInput.off("keypress", keyHandler);
|
|
34788
34879
|
if (resizeHandler)
|
|
34789
|
-
renderer.off(
|
|
34880
|
+
renderer.off(CliRenderEvents2.RESIZE, resizeHandler);
|
|
34790
34881
|
rendererDestroyed2 = true;
|
|
34791
34882
|
renderer.destroy();
|
|
34792
34883
|
},
|
|
@@ -34797,8 +34888,10 @@ async function createDashboardTui({
|
|
|
34797
34888
|
} catch (error93) {
|
|
34798
34889
|
if (inputHandler)
|
|
34799
34890
|
renderer.removeInputHandler(inputHandler);
|
|
34891
|
+
if (keyHandler)
|
|
34892
|
+
renderer.keyInput.off("keypress", keyHandler);
|
|
34800
34893
|
if (resizeHandler)
|
|
34801
|
-
renderer.off(
|
|
34894
|
+
renderer.off(CliRenderEvents2.RESIZE, resizeHandler);
|
|
34802
34895
|
if (!rendererDestroyed2) {
|
|
34803
34896
|
rendererDestroyed2 = true;
|
|
34804
34897
|
renderer.destroy();
|
|
@@ -34811,22 +34904,24 @@ function formatSessionRow({ session, selected, width }) {
|
|
|
34811
34904
|
const outcome = sessionOutcome(session);
|
|
34812
34905
|
const updated = formatUpdatedAt(session.updatedAt ?? session.createdAt);
|
|
34813
34906
|
const prefix = `#${session.sessionNumber} `;
|
|
34814
|
-
const title = truncate(session.title ?? "Untitled session", Math.max(8,
|
|
34907
|
+
const title = truncate(session.title ?? "Untitled session", Math.max(8, Math.min(40, Math.floor(width * 0.24))));
|
|
34908
|
+
const attribution = `${session.creator} (${session.source})`;
|
|
34909
|
+
const metadataWidth = Math.max(0, width - prefix.length - title.length - activity.label.length - 6);
|
|
34910
|
+
const metadata = truncate(`${attribution} \xB7 ${outcome} \xB7 ${updated}`, metadataWidth);
|
|
34815
34911
|
const line = [
|
|
34816
34912
|
fg3(PALETTE.dimText)(prefix),
|
|
34817
34913
|
fg3(PALETTE.bodyText)(title),
|
|
34818
34914
|
fg3(PALETTE.dimText)(" "),
|
|
34819
34915
|
bold2(fg3(activity.color)(activity.label)),
|
|
34820
34916
|
fg3(PALETTE.dimText)(" "),
|
|
34821
|
-
fg3(PALETTE.
|
|
34822
|
-
fg3(PALETTE.dimText)(` ${updated}`)
|
|
34917
|
+
fg3(PALETTE.dimText)(metadata)
|
|
34823
34918
|
];
|
|
34824
34919
|
const lineLength = line.reduce((total, chunk) => total + chunk.text.length, 0);
|
|
34825
34920
|
const padded = selected ? [...line, fg3(PALETTE.dimText)(" ".repeat(Math.max(0, width - lineLength)))] : line;
|
|
34826
34921
|
return selected ? padded.map((chunk) => bg(PALETTE.selectionBg)(chunk)) : padded;
|
|
34827
34922
|
}
|
|
34828
34923
|
function visibleListWindow({ sessionCount, selectedIndex, height }) {
|
|
34829
|
-
const rowCount = Math.max(1, height -
|
|
34924
|
+
const rowCount = Math.max(1, height - 9);
|
|
34830
34925
|
const firstIndex = Math.min(Math.max(0, selectedIndex - Math.floor(rowCount / 2)), Math.max(0, sessionCount - rowCount));
|
|
34831
34926
|
return { firstIndex, rowCount };
|
|
34832
34927
|
}
|
|
@@ -34929,7 +35024,9 @@ function dashboardStatusBand({ page, pageIndex }) {
|
|
|
34929
35024
|
paging.push("more \u2192");
|
|
34930
35025
|
return `remy \xB7 sessions \xB7 ${openCount} open \xB7 ${paging.join(" \xB7 ")}`;
|
|
34931
35026
|
}
|
|
34932
|
-
function dashboardFooter({ loadingPage, pageError,
|
|
35027
|
+
function dashboardFooter({ loadingPage, pageError, rowsBelow, logoutConfirmationOpen }) {
|
|
35028
|
+
if (logoutConfirmationOpen)
|
|
35029
|
+
return "Log out of Remy? y confirms \xB7 Esc cancels";
|
|
34933
35030
|
const parts = [];
|
|
34934
35031
|
if (loadingPage)
|
|
34935
35032
|
parts.push("Loading sessions\u2026");
|
|
@@ -34937,9 +35034,7 @@ function dashboardFooter({ loadingPage, pageError, page, rowsBelow }) {
|
|
|
34937
35034
|
parts.push(`Could not load sessions: ${pageError}`);
|
|
34938
35035
|
if (rowsBelow > 0)
|
|
34939
35036
|
parts.push(`\u2193 ${rowsBelow} more below`);
|
|
34940
|
-
|
|
34941
|
-
parts.push("\u2191\u2193 pages at the ends of the list");
|
|
34942
|
-
parts.push("\u2191\u2193 move \xB7 \u23CE open \xB7 n new session \xB7 q quit \xB7 ctrl+c discard & quit");
|
|
35037
|
+
parts.push("\u2191\u2193 move \xB7 \u23CE open \xB7 n new session \xB7 \u21E7l log out \xB7 q quit");
|
|
34943
35038
|
return parts.join(" \xB7 ");
|
|
34944
35039
|
}
|
|
34945
35040
|
function truncate(value, width) {
|
|
@@ -34952,7 +35047,7 @@ async function createDefaultRenderer() {
|
|
|
34952
35047
|
}
|
|
34953
35048
|
|
|
34954
35049
|
// src/tui/new-session-wizard.ts
|
|
34955
|
-
import { bg as bg3, BoxRenderable as BoxRenderable3, bold as bold3, CliRenderEvents as
|
|
35050
|
+
import { bg as bg3, BoxRenderable as BoxRenderable3, bold as bold3, CliRenderEvents as CliRenderEvents3, dim as dim3, fg as fg5, ScrollBoxRenderable as ScrollBoxRenderable2, StyledText as StyledText4, stringToStyledText as stringToStyledText4, TextRenderable as TextRenderable3 } from "@opentui/core";
|
|
34956
35051
|
|
|
34957
35052
|
// src/tui/composer.ts
|
|
34958
35053
|
import { BoxRenderable as BoxRenderable2, TextRenderable as TextRenderable2, TextareaRenderable } from "@opentui/core";
|
|
@@ -35297,7 +35392,7 @@ async function createNewSessionWizard({
|
|
|
35297
35392
|
if (composerMounted)
|
|
35298
35393
|
composer.render();
|
|
35299
35394
|
if (repositoryListVisible) {
|
|
35300
|
-
renderer.once(
|
|
35395
|
+
renderer.once(CliRenderEvents3.FRAME, () => {
|
|
35301
35396
|
if (destroyed || step !== "repositories" && step !== "repositorySearch")
|
|
35302
35397
|
return;
|
|
35303
35398
|
const nextRepositoryHeaderHeight = Math.min(content.virtualLineCount, Math.floor(renderer.height * 0.45));
|
|
@@ -35526,6 +35621,24 @@ async function createNewSessionWizard({
|
|
|
35526
35621
|
if (step !== "repositories")
|
|
35527
35622
|
return;
|
|
35528
35623
|
const choices = visibleRepositories();
|
|
35624
|
+
const repositoryListFocused = renderer.currentFocusedRenderable === repositoryScrollBox;
|
|
35625
|
+
if (repositoryListFocused && (key.name === "G" || key.name === "g" && key.shift)) {
|
|
35626
|
+
key.preventDefault();
|
|
35627
|
+
awaitingVimGoToTop = false;
|
|
35628
|
+
if (choices.length > 0)
|
|
35629
|
+
repositoryIndex = choices.length - 1;
|
|
35630
|
+
render();
|
|
35631
|
+
return;
|
|
35632
|
+
}
|
|
35633
|
+
if (repositoryListFocused && key.name === "g") {
|
|
35634
|
+
key.preventDefault();
|
|
35635
|
+
if (awaitingVimGoToTop && choices.length > 0)
|
|
35636
|
+
repositoryIndex = 0;
|
|
35637
|
+
awaitingVimGoToTop = !awaitingVimGoToTop;
|
|
35638
|
+
render();
|
|
35639
|
+
return;
|
|
35640
|
+
}
|
|
35641
|
+
awaitingVimGoToTop = false;
|
|
35529
35642
|
if (key.name === "up" || key.name === "down" || key.name === "j" || key.name === "k") {
|
|
35530
35643
|
key.preventDefault();
|
|
35531
35644
|
if (choices.length > 0)
|
|
@@ -35599,6 +35712,7 @@ async function createNewSessionWizard({
|
|
|
35599
35712
|
let completionIndex = 0;
|
|
35600
35713
|
let pathCompletionMenuOpen = false;
|
|
35601
35714
|
let repositoryHeaderHeight;
|
|
35715
|
+
let awaitingVimGoToTop = false;
|
|
35602
35716
|
const manualSelections = new Map;
|
|
35603
35717
|
let searchSelections;
|
|
35604
35718
|
let suggestedIds = new Set;
|
|
@@ -35822,7 +35936,7 @@ async function createNewSessionWizard({
|
|
|
35822
35936
|
};
|
|
35823
35937
|
renderer.addInputHandler(inputHandler);
|
|
35824
35938
|
renderer.keyInput.on("keypress", keyHandler);
|
|
35825
|
-
renderer.on(
|
|
35939
|
+
renderer.on(CliRenderEvents3.RENDER_ERROR, (event) => fail(event.error));
|
|
35826
35940
|
resizeHandler = () => {
|
|
35827
35941
|
if (destroyed)
|
|
35828
35942
|
return;
|
|
@@ -35831,7 +35945,7 @@ async function createNewSessionWizard({
|
|
|
35831
35945
|
repositoryIndex = Math.min(repositoryIndex, choices.length - 1);
|
|
35832
35946
|
render();
|
|
35833
35947
|
};
|
|
35834
|
-
renderer.on(
|
|
35948
|
+
renderer.on(CliRenderEvents3.RESIZE, resizeHandler);
|
|
35835
35949
|
render();
|
|
35836
35950
|
if (!Array.isArray(repositories))
|
|
35837
35951
|
requestRepositoryLoad({ retry: false });
|
|
@@ -35846,7 +35960,7 @@ async function createNewSessionWizard({
|
|
|
35846
35960
|
if (keyHandler)
|
|
35847
35961
|
renderer.keyInput.off("keypress", keyHandler);
|
|
35848
35962
|
if (resizeHandler)
|
|
35849
|
-
renderer.off(
|
|
35963
|
+
renderer.off(CliRenderEvents3.RESIZE, resizeHandler);
|
|
35850
35964
|
if (suggestionSpinnerTimer) {
|
|
35851
35965
|
clearInterval(suggestionSpinnerTimer);
|
|
35852
35966
|
suggestionSpinnerTimer = undefined;
|
|
@@ -35867,7 +35981,7 @@ async function createNewSessionWizard({
|
|
|
35867
35981
|
if (keyHandler)
|
|
35868
35982
|
renderer.keyInput.off("keypress", keyHandler);
|
|
35869
35983
|
if (resizeHandler)
|
|
35870
|
-
renderer.off(
|
|
35984
|
+
renderer.off(CliRenderEvents3.RESIZE, resizeHandler);
|
|
35871
35985
|
if (!rendererDestroyed2) {
|
|
35872
35986
|
rendererDestroyed2 = true;
|
|
35873
35987
|
renderer.destroy();
|
|
@@ -35950,11 +36064,13 @@ async function createDefaultRenderer2() {
|
|
|
35950
36064
|
}
|
|
35951
36065
|
|
|
35952
36066
|
// src/tui/session-view.ts
|
|
35953
|
-
import { CliRenderEvents as
|
|
36067
|
+
import { CliRenderEvents as CliRenderEvents4, BoxRenderable as BoxRenderable4, bg as bg4, bold as bold4, dim as dim4, fg as fg6, ScrollBoxRenderable as ScrollBoxRenderable3, StyledText as StyledText5, stringToStyledText as stringToStyledText5, TextRenderable as TextRenderable4 } from "@opentui/core";
|
|
35954
36068
|
var composerSlashCommands = [
|
|
35955
36069
|
{ value: "/sessions", description: "Back to the session list" },
|
|
35956
36070
|
{ value: "/complete", description: "Finish this session" },
|
|
36071
|
+
{ value: "/cancel", description: "Cancel this session" },
|
|
35957
36072
|
{ value: "/new", description: "Start a new session" },
|
|
36073
|
+
{ value: "/logout", description: "Sign out of Remy" },
|
|
35958
36074
|
{ value: "/help", description: "Show what you can do here" },
|
|
35959
36075
|
{ value: "/exit", description: "Leave Remy" }
|
|
35960
36076
|
];
|
|
@@ -35965,6 +36081,7 @@ async function createSessionTui({
|
|
|
35965
36081
|
submitMessage,
|
|
35966
36082
|
requestInterrupt,
|
|
35967
36083
|
requestComplete,
|
|
36084
|
+
requestCancel,
|
|
35968
36085
|
repositoryLabel,
|
|
35969
36086
|
readClipboardImage,
|
|
35970
36087
|
savePastedImage,
|
|
@@ -36045,8 +36162,8 @@ async function createSessionTui({
|
|
|
36045
36162
|
const working = isRemyWorking(latestState);
|
|
36046
36163
|
const startedAt = workingTurnStartedAt(latestState);
|
|
36047
36164
|
const elapsedMs = startedAt === undefined ? 0 : Math.max(0, now3() - Date.parse(startedAt));
|
|
36048
|
-
statusBand.content = renderStatusBand({ state: latestState, working, elapsedMs, stopState,
|
|
36049
|
-
const liveIndicator =
|
|
36165
|
+
statusBand.content = renderStatusBand({ state: latestState, working, elapsedMs, stopState, lifecycleRequestState, repositoryLabel });
|
|
36166
|
+
const liveIndicator = lifecycleRequestState.kind === "pending" ? renderWorkingIndicator({ frame: workingSpinnerFrames[spinnerFrameIndex], elapsedMs: 0, mode: lifecycleRequestState.operation }) : working ? renderWorkingIndicator({
|
|
36050
36167
|
frame: workingSpinnerFrames[spinnerFrameIndex],
|
|
36051
36168
|
elapsedMs,
|
|
36052
36169
|
mode: stopState.kind === "stopping" ? "stopping" : "working"
|
|
@@ -36057,8 +36174,8 @@ async function createSessionTui({
|
|
|
36057
36174
|
`), joinStyled(liveContent, `
|
|
36058
36175
|
|
|
36059
36176
|
`)], "");
|
|
36060
|
-
if (
|
|
36061
|
-
composer.placeholder =
|
|
36177
|
+
if (lifecycleRequestState.kind === "pending")
|
|
36178
|
+
composer.placeholder = `${lifecycleOperationPresentParticiple(lifecycleRequestState.operation)} the session \u2014 messages are paused.`;
|
|
36062
36179
|
if (latestState.aggregateStatus === "open")
|
|
36063
36180
|
composerLayout.render();
|
|
36064
36181
|
const submissionStatus = renderComposerStatus({ admittedSubmissions });
|
|
@@ -36075,7 +36192,7 @@ async function createSessionTui({
|
|
|
36075
36192
|
|
|
36076
36193
|
`) : "";
|
|
36077
36194
|
hints.visible = renderer.height >= 10;
|
|
36078
|
-
hints.content = hints.visible ? renderActionBar({ state: latestState, working, stopState,
|
|
36195
|
+
hints.content = hints.visible ? renderActionBar({ state: latestState, working, stopState, lifecycleRequestState }) : "";
|
|
36079
36196
|
renderer.requestRender();
|
|
36080
36197
|
}, syncTerminalSessionControls = function() {
|
|
36081
36198
|
const terminal = latestState.aggregateStatus !== "open";
|
|
@@ -36107,7 +36224,7 @@ async function createSessionTui({
|
|
|
36107
36224
|
renderedAdmittedSubmissions = admittedSubmissions;
|
|
36108
36225
|
renderedWorking = working;
|
|
36109
36226
|
}, spinnerActive = function() {
|
|
36110
|
-
return isRemyWorking(latestState) ||
|
|
36227
|
+
return isRemyWorking(latestState) || lifecycleRequestState.kind === "pending";
|
|
36111
36228
|
}, syncWorkingIndicator = function() {
|
|
36112
36229
|
if (!spinnerActive()) {
|
|
36113
36230
|
if (workingSpinnerTimer) {
|
|
@@ -36136,7 +36253,7 @@ async function createSessionTui({
|
|
|
36136
36253
|
slashCompletions = [];
|
|
36137
36254
|
return;
|
|
36138
36255
|
}
|
|
36139
|
-
slashCompletions = composerSlashCommands.filter((command) => command.value.startsWith(value) && command.value !== value && (
|
|
36256
|
+
slashCompletions = composerSlashCommands.filter((command) => command.value.startsWith(value) && command.value !== value && (!["/complete", "/cancel"].includes(command.value) || latestState.aggregateStatus === "open"));
|
|
36140
36257
|
slashCompletionIndex = 0;
|
|
36141
36258
|
}, finish = function(result, error93) {
|
|
36142
36259
|
if (settled)
|
|
@@ -36178,8 +36295,9 @@ async function createSessionTui({
|
|
|
36178
36295
|
let pendingPastedImageWrites = 0;
|
|
36179
36296
|
let admittedSubmissions = [];
|
|
36180
36297
|
let stopState = { kind: "idle" };
|
|
36181
|
-
let
|
|
36298
|
+
let lifecycleRequestState = { kind: "idle" };
|
|
36182
36299
|
let helpVisible = false;
|
|
36300
|
+
let logoutConfirmationOpen = false;
|
|
36183
36301
|
let latestState = controller.getState();
|
|
36184
36302
|
let spinnerFrameIndex = 0;
|
|
36185
36303
|
let renderedTranscript;
|
|
@@ -36236,6 +36354,17 @@ async function createSessionTui({
|
|
|
36236
36354
|
return true;
|
|
36237
36355
|
};
|
|
36238
36356
|
keyHandler = (key) => {
|
|
36357
|
+
if (logoutConfirmationOpen) {
|
|
36358
|
+
key.preventDefault();
|
|
36359
|
+
if (key.name === "y") {
|
|
36360
|
+
finish({ kind: "logout" });
|
|
36361
|
+
} else if (key.name === "escape") {
|
|
36362
|
+
logoutConfirmationOpen = false;
|
|
36363
|
+
composerFeedback = undefined;
|
|
36364
|
+
render();
|
|
36365
|
+
}
|
|
36366
|
+
return;
|
|
36367
|
+
}
|
|
36239
36368
|
if (key.ctrl && key.name === "c") {
|
|
36240
36369
|
key.preventDefault();
|
|
36241
36370
|
finish({ kind: "exit", signal: "SIGINT" });
|
|
@@ -36311,6 +36440,10 @@ async function createSessionTui({
|
|
|
36311
36440
|
}
|
|
36312
36441
|
}
|
|
36313
36442
|
composer.onKeyDown = (key) => {
|
|
36443
|
+
if (logoutConfirmationOpen) {
|
|
36444
|
+
key.preventDefault();
|
|
36445
|
+
return;
|
|
36446
|
+
}
|
|
36314
36447
|
if (slashCompletions.length > 0 && (key.name === "up" || key.name === "down")) {
|
|
36315
36448
|
key.preventDefault();
|
|
36316
36449
|
slashCompletionIndex = (slashCompletionIndex + (key.name === "up" ? -1 : 1) + slashCompletions.length) % slashCompletions.length;
|
|
@@ -36400,8 +36533,8 @@ async function createSessionTui({
|
|
|
36400
36533
|
return;
|
|
36401
36534
|
if (await handleComposerCommand(value))
|
|
36402
36535
|
return;
|
|
36403
|
-
if (
|
|
36404
|
-
composerFeedback =
|
|
36536
|
+
if (lifecycleRequestState.kind === "pending") {
|
|
36537
|
+
composerFeedback = `${lifecycleOperationPresentParticiple(lifecycleRequestState.operation)} the session \u2014 messages are paused.`;
|
|
36405
36538
|
render();
|
|
36406
36539
|
return;
|
|
36407
36540
|
}
|
|
@@ -36461,7 +36594,21 @@ async function createSessionTui({
|
|
|
36461
36594
|
if (command === "/complete") {
|
|
36462
36595
|
composer.setText("");
|
|
36463
36596
|
composerDraft = { ...composerDraft, text: "" };
|
|
36464
|
-
await
|
|
36597
|
+
await handleLifecycleRequest("complete");
|
|
36598
|
+
return true;
|
|
36599
|
+
}
|
|
36600
|
+
if (command === "/cancel") {
|
|
36601
|
+
composer.setText("");
|
|
36602
|
+
composerDraft = { ...composerDraft, text: "" };
|
|
36603
|
+
await handleLifecycleRequest("cancel");
|
|
36604
|
+
return true;
|
|
36605
|
+
}
|
|
36606
|
+
if (command === "/logout") {
|
|
36607
|
+
composer.setText("");
|
|
36608
|
+
composerDraft = { ...composerDraft, text: "" };
|
|
36609
|
+
logoutConfirmationOpen = true;
|
|
36610
|
+
composerFeedback = "Log out of Remy? Press y to confirm or Escape to cancel.";
|
|
36611
|
+
render();
|
|
36465
36612
|
return true;
|
|
36466
36613
|
}
|
|
36467
36614
|
if (command === "/help") {
|
|
@@ -36537,39 +36684,39 @@ async function createSessionTui({
|
|
|
36537
36684
|
}
|
|
36538
36685
|
render();
|
|
36539
36686
|
}
|
|
36540
|
-
async function
|
|
36541
|
-
if (
|
|
36687
|
+
async function handleLifecycleRequest(operation) {
|
|
36688
|
+
if (lifecycleRequestState.kind === "pending")
|
|
36542
36689
|
return;
|
|
36543
36690
|
if (latestState.aggregateStatus !== "open") {
|
|
36544
|
-
|
|
36691
|
+
lifecycleRequestState = { kind: "idle" };
|
|
36545
36692
|
composerFeedback = "This session is already terminal.";
|
|
36546
36693
|
render();
|
|
36547
36694
|
return;
|
|
36548
36695
|
}
|
|
36549
|
-
if (isRemyWorking(latestState)) {
|
|
36696
|
+
if (operation === "complete" && isRemyWorking(latestState)) {
|
|
36550
36697
|
composerFeedback = "Stop Remy\u2019s turn before completing the session.";
|
|
36551
36698
|
render();
|
|
36552
36699
|
return;
|
|
36553
36700
|
}
|
|
36554
|
-
|
|
36555
|
-
composerFeedback =
|
|
36701
|
+
lifecycleRequestState = { kind: "pending", operation };
|
|
36702
|
+
composerFeedback = `${lifecycleOperationPresentParticiple(operation)} the session\u2026`;
|
|
36556
36703
|
render();
|
|
36557
36704
|
try {
|
|
36558
|
-
await requestComplete();
|
|
36705
|
+
await (operation === "complete" ? requestComplete() : requestCancel());
|
|
36559
36706
|
if (destroyed)
|
|
36560
36707
|
return;
|
|
36561
|
-
if (
|
|
36708
|
+
if (lifecycleRequestState.kind !== "pending" || lifecycleRequestState.operation !== operation)
|
|
36562
36709
|
return;
|
|
36563
|
-
composerFeedback =
|
|
36710
|
+
composerFeedback = lifecycleOperationSuccessFeedback(operation);
|
|
36564
36711
|
if (latestState.aggregateStatus !== "open")
|
|
36565
|
-
|
|
36712
|
+
lifecycleRequestState = { kind: "idle" };
|
|
36566
36713
|
} catch (error93) {
|
|
36567
36714
|
if (destroyed)
|
|
36568
36715
|
return;
|
|
36569
|
-
if (
|
|
36716
|
+
if (lifecycleRequestState.kind === "pending" && lifecycleRequestState.operation === operation) {
|
|
36570
36717
|
const message = error93 instanceof Error ? error93.message : String(error93);
|
|
36571
|
-
|
|
36572
|
-
composerFeedback = `Could not
|
|
36718
|
+
lifecycleRequestState = { kind: "failed", operation, message };
|
|
36719
|
+
composerFeedback = `Could not ${operation} the session: ${message}`;
|
|
36573
36720
|
}
|
|
36574
36721
|
}
|
|
36575
36722
|
render();
|
|
@@ -36581,21 +36728,22 @@ async function createSessionTui({
|
|
|
36581
36728
|
if (composerFeedback === "Stop requested.")
|
|
36582
36729
|
composerFeedback = undefined;
|
|
36583
36730
|
}
|
|
36584
|
-
if (
|
|
36585
|
-
|
|
36586
|
-
|
|
36731
|
+
if (lifecycleRequestState.kind === "pending" && latestState.aggregateStatus !== "open") {
|
|
36732
|
+
const operation = lifecycleRequestState.operation;
|
|
36733
|
+
lifecycleRequestState = { kind: "idle" };
|
|
36734
|
+
composerFeedback = lifecycleOperationSuccessFeedback(operation);
|
|
36587
36735
|
}
|
|
36588
36736
|
render();
|
|
36589
36737
|
});
|
|
36590
36738
|
renderer.addInputHandler(inputHandler);
|
|
36591
36739
|
renderer.keyInput.on("keypress", keyHandler);
|
|
36592
36740
|
composer.setText(composerDraft.text);
|
|
36593
|
-
renderer.on(
|
|
36741
|
+
renderer.on(CliRenderEvents4.RENDER_ERROR, (event) => finish(undefined, event.error));
|
|
36594
36742
|
resizeHandler = () => {
|
|
36595
36743
|
if (!destroyed)
|
|
36596
36744
|
render();
|
|
36597
36745
|
};
|
|
36598
|
-
renderer.on(
|
|
36746
|
+
renderer.on(CliRenderEvents4.RESIZE, resizeHandler);
|
|
36599
36747
|
render();
|
|
36600
36748
|
if (latestState.aggregateStatus === "open")
|
|
36601
36749
|
composer.focus();
|
|
@@ -36615,7 +36763,7 @@ async function createSessionTui({
|
|
|
36615
36763
|
if (keyHandler)
|
|
36616
36764
|
renderer.keyInput.off("keypress", keyHandler);
|
|
36617
36765
|
if (resizeHandler)
|
|
36618
|
-
renderer.off(
|
|
36766
|
+
renderer.off(CliRenderEvents4.RESIZE, resizeHandler);
|
|
36619
36767
|
composerLayout.destroy();
|
|
36620
36768
|
rendererDestroyed2 = true;
|
|
36621
36769
|
renderer.destroy();
|
|
@@ -36632,7 +36780,7 @@ async function createSessionTui({
|
|
|
36632
36780
|
if (keyHandler)
|
|
36633
36781
|
renderer.keyInput.off("keypress", keyHandler);
|
|
36634
36782
|
if (resizeHandler)
|
|
36635
|
-
renderer.off(
|
|
36783
|
+
renderer.off(CliRenderEvents4.RESIZE, resizeHandler);
|
|
36636
36784
|
if (!rendererDestroyed2) {
|
|
36637
36785
|
rendererDestroyed2 = true;
|
|
36638
36786
|
renderer.destroy();
|
|
@@ -36660,11 +36808,12 @@ var helpEntries = [
|
|
|
36660
36808
|
{ group: "command", token: "/new", description: "start a new session" },
|
|
36661
36809
|
{ group: "command", token: "/sessions", description: "back to the session list" },
|
|
36662
36810
|
{ group: "command", token: "/complete", description: "finish this session" },
|
|
36811
|
+
{ group: "command", token: "/cancel", description: "cancel this session" },
|
|
36812
|
+
{ group: "command", token: "/logout", description: "sign out of Remy" },
|
|
36663
36813
|
{ group: "command", token: "/exit", description: "leave Remy" },
|
|
36664
36814
|
{ group: "key", token: "esc", description: "stop Remy\u2019s current turn" },
|
|
36665
36815
|
{ group: "key", token: "ctrl+o", description: "expand the activity trail" },
|
|
36666
|
-
{ group: "key", token: "ctrl+r", description: "retry the oldest failed message" }
|
|
36667
|
-
{ group: "key", token: "ctrl+c", description: "quit" }
|
|
36816
|
+
{ group: "key", token: "ctrl+r", description: "retry the oldest failed message" }
|
|
36668
36817
|
];
|
|
36669
36818
|
function renderHelpPanel() {
|
|
36670
36819
|
const width = helpEntries.reduce((max, entry) => Math.max(max, entry.token.length), 0) + 2;
|
|
@@ -36685,10 +36834,10 @@ function renderHelpPanel() {
|
|
|
36685
36834
|
function hasRunningTurn(state) {
|
|
36686
36835
|
return state.aggregateStatus === "open" && Object.values(state.messageTurns).some((turn) => turn.outcome === undefined);
|
|
36687
36836
|
}
|
|
36688
|
-
function renderStatusBand({ state, working, elapsedMs, stopState,
|
|
36837
|
+
function renderStatusBand({ state, working, elapsedMs, stopState, lifecycleRequestState, repositoryLabel }) {
|
|
36689
36838
|
const stopping = stopState?.kind === "stopping";
|
|
36690
|
-
const
|
|
36691
|
-
const presentation =
|
|
36839
|
+
const pendingOperation = lifecycleRequestState?.kind === "pending" ? lifecycleRequestState.operation : undefined;
|
|
36840
|
+
const presentation = pendingOperation ? { label: lifecycleOperationPresentParticiple(pendingOperation), color: PALETTE.approvalQuestion } : stopping ? { label: "Stopping", color: PALETTE.approvalQuestion } : sessionViewStatePresentation({ aggregateStatus: state.aggregateStatus, isWorking: working });
|
|
36692
36841
|
const elapsed = (working || stopping) && elapsedMs !== undefined && elapsedMs > 0 ? ` ${formatElapsed(elapsedMs)}` : "";
|
|
36693
36842
|
const connection = state.connectionStatus === "connected" ? dim4(fg6(PALETTE.dimText)("connected")) : fg6(PALETTE.approvalQuestion)("reconnecting\u2026");
|
|
36694
36843
|
const sessionLabel = state.sessionNumber !== undefined ? `Session #${state.sessionNumber}` : `Session ${state.sessionId}`;
|
|
@@ -36722,14 +36871,14 @@ function renderTerminalSessionBand({ aggregateStatus }) {
|
|
|
36722
36871
|
], `
|
|
36723
36872
|
`);
|
|
36724
36873
|
}
|
|
36725
|
-
function renderActionBar({ state, working, stopState,
|
|
36726
|
-
if (
|
|
36727
|
-
return new StyledText5([dim4(fg6(PALETTE.dimText)(
|
|
36874
|
+
function renderActionBar({ state, working, stopState, lifecycleRequestState }) {
|
|
36875
|
+
if (lifecycleRequestState.kind === "pending")
|
|
36876
|
+
return new StyledText5([dim4(fg6(PALETTE.dimText)(`${lifecycleOperationPresentParticiple(lifecycleRequestState.operation).toLowerCase()} the session\u2026`))]);
|
|
36728
36877
|
if (state.aggregateStatus !== "open")
|
|
36729
36878
|
return new StyledText5([]);
|
|
36730
36879
|
const stopToken = working && stopState.kind === "failed" ? ["esc retry stop"] : [];
|
|
36731
|
-
const idleGroup =
|
|
36732
|
-
const tokens = working ? ["/ commands",
|
|
36880
|
+
const idleGroup = lifecycleRequestState.kind === "failed" ? `/${lifecycleRequestState.operation} retries \xB7 /new \xB7 /sessions` : "/new \xB7 /sessions \xB7 /complete \xB7 /cancel";
|
|
36881
|
+
const tokens = working ? ["/ commands", ...stopToken, "ctrl+o activity"] : ["/ commands", idleGroup, "ctrl+o activity"];
|
|
36733
36882
|
return new StyledText5([dim4(fg6(PALETTE.dimText)(tokens.join(" \xB7 ")))]);
|
|
36734
36883
|
}
|
|
36735
36884
|
function isRemyWorking(state) {
|
|
@@ -36890,10 +37039,10 @@ function renderWorkingIndicator({ frame, elapsedMs, mode }) {
|
|
|
36890
37039
|
dim4(fg6(PALETTE.dimText)(` Stopping\u2026 ${formatElapsed(elapsedMs)}`))
|
|
36891
37040
|
]);
|
|
36892
37041
|
}
|
|
36893
|
-
if (mode === "
|
|
37042
|
+
if (mode === "complete" || mode === "cancel") {
|
|
36894
37043
|
return new StyledText5([
|
|
36895
37044
|
fg6(PALETTE.approvalQuestion)(frame),
|
|
36896
|
-
dim4(fg6(PALETTE.dimText)(
|
|
37045
|
+
dim4(fg6(PALETTE.dimText)(` ${lifecycleOperationPresentParticiple(mode)} the session\u2026`))
|
|
36897
37046
|
]);
|
|
36898
37047
|
}
|
|
36899
37048
|
return new StyledText5([
|
|
@@ -36901,6 +37050,12 @@ function renderWorkingIndicator({ frame, elapsedMs, mode }) {
|
|
|
36901
37050
|
dim4(fg6(PALETTE.dimText)(` Working ${formatElapsed(elapsedMs)}`))
|
|
36902
37051
|
]);
|
|
36903
37052
|
}
|
|
37053
|
+
function lifecycleOperationPresentParticiple(operation) {
|
|
37054
|
+
return operation === "complete" ? "Completing" : "Cancelling";
|
|
37055
|
+
}
|
|
37056
|
+
function lifecycleOperationSuccessFeedback(operation) {
|
|
37057
|
+
return operation === "complete" ? "Session completed." : "Session cancelled.";
|
|
37058
|
+
}
|
|
36904
37059
|
function formatElapsed(elapsedMs) {
|
|
36905
37060
|
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
36906
37061
|
if (totalSeconds < 60)
|
|
@@ -37042,7 +37197,7 @@ var compactMarkRows = 9;
|
|
|
37042
37197
|
var compactMinWidth = 48;
|
|
37043
37198
|
var compactMinHeight = 20;
|
|
37044
37199
|
var markBrightnessGain = 4.2;
|
|
37045
|
-
var remyCliVersion = "1.
|
|
37200
|
+
var remyCliVersion = "1.3.0";
|
|
37046
37201
|
async function showRemySplash({
|
|
37047
37202
|
createRenderer = createRemyRenderer,
|
|
37048
37203
|
durationMs = splashDurationMs,
|
|
@@ -37224,7 +37379,7 @@ Verification URL: `), bold5(fg7(PALETTE.humanAccent)(state.verificationUrl))] :
|
|
|
37224
37379
|
fg7(PALETTE.bodyText)("Continue authentication in your browser."),
|
|
37225
37380
|
...url3,
|
|
37226
37381
|
fg7(PALETTE.dimText)(`
|
|
37227
|
-
${spinner} Waiting for approval \xB7 q / esc
|
|
37382
|
+
${spinner} Waiting for approval \xB7 q / esc cancel`)
|
|
37228
37383
|
]);
|
|
37229
37384
|
}
|
|
37230
37385
|
async function waitForSplashAbort(signal) {
|
|
@@ -37517,7 +37672,7 @@ ${approvalUrl}
|
|
|
37517
37672
|
`);
|
|
37518
37673
|
});
|
|
37519
37674
|
if (!splashPresented)
|
|
37520
|
-
dependencies.output.writeStderr(`Waiting for approval\u2026
|
|
37675
|
+
dependencies.output.writeStderr(`Waiting for approval\u2026
|
|
37521
37676
|
`);
|
|
37522
37677
|
});
|
|
37523
37678
|
let journalPrepared = false;
|
|
@@ -37681,7 +37836,6 @@ function parseOptionalStringFlag(value, name) {
|
|
|
37681
37836
|
}
|
|
37682
37837
|
async function createAuthenticatedClient(dependencies) {
|
|
37683
37838
|
const paths = resolveCliPaths(dependencies.environment);
|
|
37684
|
-
const candidateTokenPath = await createCandidateTokenPath(paths.tokenPath);
|
|
37685
37839
|
const snapshot = await withCredentialState({
|
|
37686
37840
|
stateDirectory: dirname6(paths.tokenPath),
|
|
37687
37841
|
action: async () => {
|
|
@@ -37693,43 +37847,21 @@ async function createAuthenticatedClient(dependencies) {
|
|
|
37693
37847
|
});
|
|
37694
37848
|
const configuration = await readCliConfiguration(paths.configPath);
|
|
37695
37849
|
const tokenGeneration = await readOptionalTokenFile(paths.tokenPath);
|
|
37696
|
-
if (tokenGeneration !== undefined)
|
|
37697
|
-
await writeFile4(candidateTokenPath, tokenGeneration, { mode: 384 });
|
|
37698
37850
|
return { configuration, tokenGeneration };
|
|
37699
37851
|
}
|
|
37700
37852
|
});
|
|
37701
|
-
const
|
|
37702
|
-
const identity = await loadStoredIdentity(
|
|
37853
|
+
const authConfiguration = toAuthConfiguration({ configuration: snapshot.configuration, tokenPath: paths.tokenPath });
|
|
37854
|
+
const identity = await loadStoredIdentity(authConfiguration);
|
|
37855
|
+
const getAccessToken = createRefreshingAccessTokenProvider({ dependencies, paths, snapshot });
|
|
37703
37856
|
try {
|
|
37704
|
-
|
|
37705
|
-
await withCredentialState({
|
|
37706
|
-
stateDirectory: dirname6(paths.tokenPath),
|
|
37707
|
-
action: async () => {
|
|
37708
|
-
await recoverCredentialPromotionWhileLocked({
|
|
37709
|
-
stateDirectory: dirname6(paths.tokenPath),
|
|
37710
|
-
configPath: paths.configPath,
|
|
37711
|
-
tokenPath: paths.tokenPath,
|
|
37712
|
-
...dependencies.credentialStateFilesystem
|
|
37713
|
-
});
|
|
37714
|
-
await assertCredentialSnapshotCurrent({ paths, snapshot });
|
|
37715
|
-
const candidateGeneration = await readFile5(candidateTokenPath, "utf8");
|
|
37716
|
-
if (candidateGeneration !== snapshot.tokenGeneration) {
|
|
37717
|
-
await replaceCredentialTokenWhileLocked({
|
|
37718
|
-
sourcePath: candidateTokenPath,
|
|
37719
|
-
tokenPath: paths.tokenPath,
|
|
37720
|
-
renameFile: dependencies.credentialStateFilesystem?.renameFile,
|
|
37721
|
-
syncDirectory: dependencies.credentialStateFilesystem?.syncDirectory
|
|
37722
|
-
});
|
|
37723
|
-
}
|
|
37724
|
-
}
|
|
37725
|
-
});
|
|
37857
|
+
await getAccessToken(dependencies.abortSignal);
|
|
37726
37858
|
const client = createCodingAgentClient({
|
|
37727
37859
|
apiUrl: snapshot.configuration.apiUrl,
|
|
37728
|
-
getAccessToken
|
|
37860
|
+
getAccessToken
|
|
37729
37861
|
});
|
|
37730
37862
|
const remoteIdentity = await materializeRemoteCurrentUser({ client, signal: dependencies.abortSignal });
|
|
37731
37863
|
return {
|
|
37732
|
-
authConfiguration
|
|
37864
|
+
authConfiguration,
|
|
37733
37865
|
client,
|
|
37734
37866
|
identity,
|
|
37735
37867
|
authenticatedEmail: remoteIdentity.email
|
|
@@ -37755,6 +37887,85 @@ async function createAuthenticatedClient(dependencies) {
|
|
|
37755
37887
|
});
|
|
37756
37888
|
}
|
|
37757
37889
|
throw error93;
|
|
37890
|
+
}
|
|
37891
|
+
}
|
|
37892
|
+
function createRefreshingAccessTokenProvider({
|
|
37893
|
+
dependencies,
|
|
37894
|
+
paths,
|
|
37895
|
+
snapshot
|
|
37896
|
+
}) {
|
|
37897
|
+
let tokenGeneration = snapshot.tokenGeneration;
|
|
37898
|
+
let pendingAccessToken;
|
|
37899
|
+
return async (signal) => {
|
|
37900
|
+
if (!pendingAccessToken) {
|
|
37901
|
+
const expectedTokenGeneration = tokenGeneration;
|
|
37902
|
+
pendingAccessToken = refreshAccessToken({ dependencies, paths, configuration: snapshot.configuration, expectedTokenGeneration, signal }).then(({ accessToken: accessToken2, tokenGeneration: refreshedTokenGeneration }) => {
|
|
37903
|
+
tokenGeneration = refreshedTokenGeneration;
|
|
37904
|
+
return accessToken2;
|
|
37905
|
+
});
|
|
37906
|
+
}
|
|
37907
|
+
const accessToken = pendingAccessToken;
|
|
37908
|
+
try {
|
|
37909
|
+
return await accessToken;
|
|
37910
|
+
} finally {
|
|
37911
|
+
if (pendingAccessToken === accessToken)
|
|
37912
|
+
pendingAccessToken = undefined;
|
|
37913
|
+
}
|
|
37914
|
+
};
|
|
37915
|
+
}
|
|
37916
|
+
async function refreshAccessToken({
|
|
37917
|
+
dependencies,
|
|
37918
|
+
paths,
|
|
37919
|
+
configuration,
|
|
37920
|
+
expectedTokenGeneration,
|
|
37921
|
+
signal
|
|
37922
|
+
}) {
|
|
37923
|
+
const candidateTokenPath = await createCandidateTokenPath(paths.tokenPath);
|
|
37924
|
+
try {
|
|
37925
|
+
await withCredentialState({
|
|
37926
|
+
stateDirectory: dirname6(paths.tokenPath),
|
|
37927
|
+
action: async () => {
|
|
37928
|
+
await recoverCredentialPromotionWhileLocked({
|
|
37929
|
+
stateDirectory: dirname6(paths.tokenPath),
|
|
37930
|
+
configPath: paths.configPath,
|
|
37931
|
+
tokenPath: paths.tokenPath,
|
|
37932
|
+
...dependencies.credentialStateFilesystem
|
|
37933
|
+
});
|
|
37934
|
+
await assertCredentialSnapshotCurrent({
|
|
37935
|
+
paths,
|
|
37936
|
+
snapshot: { configuration, tokenGeneration: expectedTokenGeneration }
|
|
37937
|
+
});
|
|
37938
|
+
if (expectedTokenGeneration !== undefined)
|
|
37939
|
+
await writeFile4(candidateTokenPath, expectedTokenGeneration, { mode: 384 });
|
|
37940
|
+
}
|
|
37941
|
+
});
|
|
37942
|
+
const candidateAuthConfiguration = toAuthConfiguration({ configuration, tokenPath: candidateTokenPath });
|
|
37943
|
+
const accessToken = await requireAccessToken(candidateAuthConfiguration, signal);
|
|
37944
|
+
const candidateTokenGeneration = await readFile5(candidateTokenPath, "utf8");
|
|
37945
|
+
await withCredentialState({
|
|
37946
|
+
stateDirectory: dirname6(paths.tokenPath),
|
|
37947
|
+
action: async () => {
|
|
37948
|
+
await recoverCredentialPromotionWhileLocked({
|
|
37949
|
+
stateDirectory: dirname6(paths.tokenPath),
|
|
37950
|
+
configPath: paths.configPath,
|
|
37951
|
+
tokenPath: paths.tokenPath,
|
|
37952
|
+
...dependencies.credentialStateFilesystem
|
|
37953
|
+
});
|
|
37954
|
+
await assertCredentialSnapshotCurrent({
|
|
37955
|
+
paths,
|
|
37956
|
+
snapshot: { configuration, tokenGeneration: expectedTokenGeneration }
|
|
37957
|
+
});
|
|
37958
|
+
if (candidateTokenGeneration !== expectedTokenGeneration) {
|
|
37959
|
+
await replaceCredentialTokenWhileLocked({
|
|
37960
|
+
sourcePath: candidateTokenPath,
|
|
37961
|
+
tokenPath: paths.tokenPath,
|
|
37962
|
+
renameFile: dependencies.credentialStateFilesystem?.renameFile,
|
|
37963
|
+
syncDirectory: dependencies.credentialStateFilesystem?.syncDirectory
|
|
37964
|
+
});
|
|
37965
|
+
}
|
|
37966
|
+
}
|
|
37967
|
+
});
|
|
37968
|
+
return { accessToken, tokenGeneration: candidateTokenGeneration };
|
|
37758
37969
|
} finally {
|
|
37759
37970
|
await removeCandidateToken({ path: candidateTokenPath, removeTokenFile: unlink3 });
|
|
37760
37971
|
}
|
|
@@ -37847,6 +38058,8 @@ async function dashboard({
|
|
|
37847
38058
|
return 0;
|
|
37848
38059
|
if (action.kind === "interrupt")
|
|
37849
38060
|
throw new CliInterruptedError("SIGINT");
|
|
38061
|
+
if (action.kind === "logout")
|
|
38062
|
+
return await logout({ dependencies, flags: {} });
|
|
37850
38063
|
holdTransitionScreen();
|
|
37851
38064
|
if (action.kind === "session")
|
|
37852
38065
|
return await attachSession({ dependencies, sessionId: action.sessionId, noTui: false, json: false });
|
|
@@ -38326,6 +38539,10 @@ async function runAttachedSession({
|
|
|
38326
38539
|
const detail = await operations.completeSession({ client: operations.client, sessionId });
|
|
38327
38540
|
controller.updateDetail(detail);
|
|
38328
38541
|
},
|
|
38542
|
+
requestCancel: async () => {
|
|
38543
|
+
const detail = await operations.cancelSession({ client: operations.client, sessionId });
|
|
38544
|
+
controller.updateDetail(detail);
|
|
38545
|
+
},
|
|
38329
38546
|
readClipboardImage,
|
|
38330
38547
|
savePastedImage: async (input) => await writePastedImage({ ...input, temporaryDirectory: tmpdir() }),
|
|
38331
38548
|
attachPromptPaths: async (input) => await uploadPromptPaths({ operations, ...input })
|
|
@@ -38354,6 +38571,13 @@ async function runAttachedSession({
|
|
|
38354
38571
|
bootstrap: await prepareDashboardBootstrap({ dependencies, operations })
|
|
38355
38572
|
});
|
|
38356
38573
|
}
|
|
38574
|
+
if (action.kind === "logout") {
|
|
38575
|
+
stop();
|
|
38576
|
+
await startPromise;
|
|
38577
|
+
if (dependencies.abortSignal?.aborted)
|
|
38578
|
+
throw dependencies.abortSignal.reason ?? new Error("interrupted");
|
|
38579
|
+
return await logout({ dependencies, flags: {} });
|
|
38580
|
+
}
|
|
38357
38581
|
holdTransitionScreen();
|
|
38358
38582
|
const availableRepositories = await collectAllRepositories({ listRepositories: operations.listRepositories });
|
|
38359
38583
|
let draft = await openTuiNewSessionWizard({ dependencies, operations, repositories: availableRepositories });
|
|
@@ -38433,6 +38657,7 @@ async function createSessionOperations(dependencies, { onAuthenticated } = {}) {
|
|
|
38433
38657
|
reserveAndUploadFile: dependencies.reserveAndUploadFile ?? reserveAndUploadFile,
|
|
38434
38658
|
createCodexSession: dependencies.createCodexSession ?? createCodexSession,
|
|
38435
38659
|
appendSessionMessage: dependencies.appendSessionMessage ?? appendSessionMessage,
|
|
38660
|
+
cancelSession: dependencies.cancelSession ?? cancelSession,
|
|
38436
38661
|
completeSession: dependencies.completeSession ?? completeSession,
|
|
38437
38662
|
interruptSession: dependencies.interruptSession ?? interruptSession,
|
|
38438
38663
|
getSession: dependencies.getSession ?? getSession,
|
|
@@ -38450,6 +38675,7 @@ async function createSessionOperations(dependencies, { onAuthenticated } = {}) {
|
|
|
38450
38675
|
reserveAndUploadFile: dependencies.reserveAndUploadFile ?? reserveAndUploadFile,
|
|
38451
38676
|
createCodexSession: dependencies.createCodexSession ?? createCodexSession,
|
|
38452
38677
|
appendSessionMessage: dependencies.appendSessionMessage ?? appendSessionMessage,
|
|
38678
|
+
cancelSession: dependencies.cancelSession ?? cancelSession,
|
|
38453
38679
|
completeSession: dependencies.completeSession ?? completeSession,
|
|
38454
38680
|
interruptSession: dependencies.interruptSession ?? interruptSession,
|
|
38455
38681
|
getSession: dependencies.getSession ?? getSession,
|