@meistrari/remy-cli 1.19.0-rc.1 → 1.19.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 -1
- package/dist/remy.js +501 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,10 @@ Subagent work appears as named **Subagent · <name>** activity rather than messa
|
|
|
62
62
|
|
|
63
63
|
Published files and Tela Pages appear in the timeline. File artifacts include their full Web preview URL; Tela Page rows include their title and canonical URL so you can open them directly from the terminal. Remy does not request or expose the Web-only temporary iframe capability for Tela Pages.
|
|
64
64
|
|
|
65
|
+
**Open links:** Ctrl+click an HTTP or HTTPS link in the conversation, including Markdown links and expanded activity links. Ordinary clicks and drag-to-copy keep their existing behavior. Terminals may reserve that gesture or use their own modifier for hyperlinks; `/links` or `Ctrl+L` provides a keyboard alternative. Select with arrows or `j`/`k`, press `Enter` to open the browser, or `c` to copy the complete URL. Browser opening uses the machine running Remy.
|
|
66
|
+
|
|
67
|
+
**Download files:** `/files` or `Ctrl+F` opens a session file picker, newest first, containing Remy’s published artifacts and attachments sent by everyone in that session, with author/source labels. Repeated updates to an artifact appear once, using its newest title and URL. Select with arrows or `j`/`k`; `Enter` (or `d`) downloads to `~/Downloads`, creating that directory if needed. Remy shows progress and the full saved path. Existing files are preserved: another `report.txt` becomes `report (1).txt`. Press `o` for the Web preview or `c` to copy its URL; previews use your browser sign-in. Tela Pages open their canonical URL instead of downloading. The picker also works in closed sessions. `Esc` cancels an unfinished download and returns to the conversation with your draft intact, without stopping Remy. Errors stay visible for an explicit retry, which requests fresh download authorization. Files are saved without executing or extracting them. This picker covers published files and message attachments; embedded tool-output media remains available through **View event in Web**.
|
|
68
|
+
|
|
65
69
|
System observations appear as **system notifications** with the system actor, source, and a generic JSON data view. Remy preserves unknown actor IDs and notification formats rather than treating them as user messages or commands. When a notification starts agent work, retained history can associate it with a known accepted Remy turn without adding a synthetic prompt. Delivery is at-least-once, so an uncertain retry can produce additional distinct turns and repeat model or tool effects; Remy preserves those turns rather than presenting the association as proof of exactly-once execution. The CLI has no control for creating notifications.
|
|
66
70
|
|
|
67
71
|
When Remy creates a plan, the session shows its checklist in the timeline and keeps `Plan <done>/<total>` with the current item pinned above the composer. Updates change the same checklist instead of producing repeated rows, and an unfinished plan remains visible after reconnecting or between turns. The collapsed timeline shows up to five plan items; press `Ctrl+O` for the complete checklist and activity detail.
|
|
@@ -74,7 +78,7 @@ Command and attachment feedback share progress, success, failure, and attention
|
|
|
74
78
|
|
|
75
79
|
Enter `/compact` to admit a durable main-thread context compaction control without sending a human message. Remy reports request admission immediately and shows the eventual completed, no-op, failed, or unknown provider result in retained activity. Compaction waits for a safe point and does not interrupt active work or jump ahead of an earlier Next message. While admission is pending, you can keep editing or open other commands; repeated `/compact` invocations do not send another request.
|
|
76
80
|
|
|
77
|
-
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. Published file artifacts show their filename and muted, full web preview URL; open the URL in a browser to select that artifact in its session. Tela Page rows retain their canonical Page URL.
|
|
81
|
+
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. Published file artifacts show their filename and muted, full web preview URL; open the URL in a browser to select that artifact in its session. Tela Page rows retain their canonical Page URL. Downloads are explicit through `/files`; artifact bytes are never printed in the terminal. When the conversation has focus, press `Tab` to return to the composer.
|
|
78
82
|
|
|
79
83
|
| Control | Result |
|
|
80
84
|
| --- | --- |
|
|
@@ -85,6 +89,8 @@ Drag to select visible text in any Remy view; Remy copies it to the local clipbo
|
|
|
85
89
|
| `/sessions` | Return to the dashboard. |
|
|
86
90
|
| `/new` | Start another new-session flow. |
|
|
87
91
|
| `/logout` | Sign out of Remy after confirmation. |
|
|
92
|
+
| `/files` / `Ctrl+F` | Browse and download session artifacts and everyone’s attachments. |
|
|
93
|
+
| `/links` / `Ctrl+L` | Browse, open, or copy conversation links. |
|
|
88
94
|
| `/help` | Browse the same command picker shown by `/`; arrows select, Enter/Tab inserts, then submit to run. |
|
|
89
95
|
| `/exit` | Leave Remy. |
|
|
90
96
|
| `Ctrl+O` | Expand or collapse full plan and activity details. |
|
package/dist/remy.js
CHANGED
|
@@ -18,9 +18,9 @@ var __export = (target, all) => {
|
|
|
18
18
|
// src/commands.ts
|
|
19
19
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
20
20
|
import { spawn } from "child_process";
|
|
21
|
-
import { mkdir as
|
|
22
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
23
|
-
import { dirname as dirname6, join as
|
|
21
|
+
import { mkdir as mkdir6, readFile as readFile5, readdir as readdir3, rename as rename4, unlink as unlink3, writeFile as writeFile4 } from "fs/promises";
|
|
22
|
+
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
23
|
+
import { dirname as dirname6, join as join7 } from "path";
|
|
24
24
|
|
|
25
25
|
// ../../node_modules/.bun/@meistrari+auth-cli@1.6.1+aff0271b2b55d60c/node_modules/@meistrari/auth-cli/dist/index.mjs
|
|
26
26
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
@@ -35448,9 +35448,11 @@ function publicMessageContentSegmentText(segment) {
|
|
|
35448
35448
|
return segment.subject.kind === "user" ? segment.subject.identity.name ?? "" : segment.subject.kind === "assistant" ? segment.subject.name : "";
|
|
35449
35449
|
}
|
|
35450
35450
|
if (segment.type === "link")
|
|
35451
|
-
return segment.label
|
|
35452
|
-
if (segment.type === "context_file")
|
|
35453
|
-
|
|
35451
|
+
return segment.label && segment.label !== segment.url ? `${segment.label} (${segment.url})` : segment.url;
|
|
35452
|
+
if (segment.type === "context_file") {
|
|
35453
|
+
const label = segment.label ?? segment.filename;
|
|
35454
|
+
return label === segment.url ? segment.url : `${label} (${segment.url})`;
|
|
35455
|
+
}
|
|
35454
35456
|
return "";
|
|
35455
35457
|
}
|
|
35456
35458
|
function jsonDetail(value) {
|
|
@@ -36031,6 +36033,90 @@ async function sleepMs(ms, signal) {
|
|
|
36031
36033
|
});
|
|
36032
36034
|
}
|
|
36033
36035
|
|
|
36036
|
+
// src/sessions/downloads.ts
|
|
36037
|
+
import { link, mkdir as mkdir5, mkdtemp, open as open4, rm } from "fs/promises";
|
|
36038
|
+
import { basename as basename2, extname, join as join4, resolve } from "path";
|
|
36039
|
+
var downloadGrantSchema = exports_external2.object({
|
|
36040
|
+
url: exports_external2.url(),
|
|
36041
|
+
method: exports_external2.literal("GET"),
|
|
36042
|
+
headers: exports_external2.record(exports_external2.string(), exports_external2.string()),
|
|
36043
|
+
expires_at: exports_external2.iso.datetime({ offset: true })
|
|
36044
|
+
});
|
|
36045
|
+
async function downloadSessionFile({ client, sessionId, file: file3, directory, signal, fetchClient }) {
|
|
36046
|
+
signal.throwIfAborted();
|
|
36047
|
+
const resource = file3.kind === "artifact" ? "artifacts" : "message-attachments";
|
|
36048
|
+
const response = await client.request(`/v1/sessions/${encodeURIComponent(sessionId)}/${resource}/${encodeURIComponent(file3.id)}/download`, { signal });
|
|
36049
|
+
const grant = downloadGrantSchema.parse(await response.json());
|
|
36050
|
+
const url3 = new URL(grant.url);
|
|
36051
|
+
if (url3.protocol !== "https:" && url3.protocol !== "http:" || url3.username || url3.password)
|
|
36052
|
+
throw new Error("The server returned an unsupported download URL.");
|
|
36053
|
+
const bytes = await fetchClient(grant.url, { method: grant.method, headers: grant.headers, signal }).catch(() => {
|
|
36054
|
+
signal.throwIfAborted();
|
|
36055
|
+
throw new Error("Could not fetch the file. Check your connection and retry.");
|
|
36056
|
+
});
|
|
36057
|
+
if (!bytes.ok || !bytes.body)
|
|
36058
|
+
throw new Error(`File download failed (HTTP ${bytes.status}). Try again to request a fresh download.`);
|
|
36059
|
+
const destination = resolve(directory);
|
|
36060
|
+
await mkdir5(destination, { recursive: true });
|
|
36061
|
+
const staging = await mkdtemp(join4(destination, ".remy-download-"));
|
|
36062
|
+
try {
|
|
36063
|
+
const temporaryPath = join4(staging, "content");
|
|
36064
|
+
await saveDownloadStream({ body: bytes.body, path: temporaryPath, signal });
|
|
36065
|
+
const filename = safeFilename(file3.filename);
|
|
36066
|
+
const extension = extname(filename);
|
|
36067
|
+
const stem = filename.slice(0, filename.length - extension.length);
|
|
36068
|
+
for (let suffix = 0;suffix < 1000; suffix += 1) {
|
|
36069
|
+
signal.throwIfAborted();
|
|
36070
|
+
const path = join4(destination, suffix === 0 ? filename : `${stem} (${suffix})${extension}`);
|
|
36071
|
+
try {
|
|
36072
|
+
await link(temporaryPath, path);
|
|
36073
|
+
return path;
|
|
36074
|
+
} catch (error93) {
|
|
36075
|
+
if (!(error93 instanceof Error && ("code" in error93) && error93.code === "EEXIST"))
|
|
36076
|
+
throw error93;
|
|
36077
|
+
}
|
|
36078
|
+
}
|
|
36079
|
+
throw new Error("Too many files with this name in downloads. Rename or move an existing download and retry.");
|
|
36080
|
+
} finally {
|
|
36081
|
+
await rm(staging, { recursive: true, force: true });
|
|
36082
|
+
}
|
|
36083
|
+
}
|
|
36084
|
+
async function saveDownloadStream({ body, path, signal }) {
|
|
36085
|
+
const file3 = await open4(path, "wx", 384);
|
|
36086
|
+
const reader = body.getReader();
|
|
36087
|
+
const cancel = () => {
|
|
36088
|
+
reader.cancel().catch(() => {
|
|
36089
|
+
return;
|
|
36090
|
+
});
|
|
36091
|
+
};
|
|
36092
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
36093
|
+
try {
|
|
36094
|
+
while (true) {
|
|
36095
|
+
signal.throwIfAborted();
|
|
36096
|
+
const chunk = await reader.read().catch(() => {
|
|
36097
|
+
throw new Error("Download interrupted. Check your connection and retry.");
|
|
36098
|
+
});
|
|
36099
|
+
signal.throwIfAborted();
|
|
36100
|
+
if (chunk.done)
|
|
36101
|
+
break;
|
|
36102
|
+
await file3.writeFile(chunk.value);
|
|
36103
|
+
}
|
|
36104
|
+
} finally {
|
|
36105
|
+
signal.removeEventListener("abort", cancel);
|
|
36106
|
+
await reader.cancel().catch(() => {
|
|
36107
|
+
return;
|
|
36108
|
+
});
|
|
36109
|
+
reader.releaseLock();
|
|
36110
|
+
await file3.close();
|
|
36111
|
+
}
|
|
36112
|
+
}
|
|
36113
|
+
function safeFilename(value) {
|
|
36114
|
+
const filename = basename2(terminalSafeText(value).replaceAll("\\", "/")).replace(/[<>:"|?*\r\n\t]/gu, "_").replace(/^[. ]+|[. ]+$/g, "");
|
|
36115
|
+
if (!filename)
|
|
36116
|
+
return "download";
|
|
36117
|
+
return /^(?:con|prn|aux|nul|com\d|lpt\d)(?:\.|$)/i.test(filename) ? `_${filename}` : filename;
|
|
36118
|
+
}
|
|
36119
|
+
|
|
36034
36120
|
// src/tui/dashboard.ts
|
|
36035
36121
|
import { BoxRenderable as BoxRenderable3, bold as bold3, CliRenderEvents as CliRenderEvents2, fg as fg5, stringToStyledText as stringToStyledText2, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
36036
36122
|
|
|
@@ -36415,8 +36501,8 @@ async function createRemyRenderer({
|
|
|
36415
36501
|
return;
|
|
36416
36502
|
};
|
|
36417
36503
|
let selectionClipboard;
|
|
36418
|
-
const destroyed = new Promise((
|
|
36419
|
-
resolveDestroyed =
|
|
36504
|
+
const destroyed = new Promise((resolve2) => {
|
|
36505
|
+
resolveDestroyed = resolve2;
|
|
36420
36506
|
});
|
|
36421
36507
|
const renderer = await createRenderer({
|
|
36422
36508
|
exitOnCtrlC: false,
|
|
@@ -36468,10 +36554,10 @@ function restoreRemyTransitionScreen() {
|
|
|
36468
36554
|
transitionScreenWriter = undefined;
|
|
36469
36555
|
}
|
|
36470
36556
|
async function renderRemyView({
|
|
36471
|
-
open:
|
|
36557
|
+
open: open5,
|
|
36472
36558
|
waitForAction
|
|
36473
36559
|
}) {
|
|
36474
|
-
const view = await
|
|
36560
|
+
const view = await open5();
|
|
36475
36561
|
try {
|
|
36476
36562
|
return await waitForAction(view);
|
|
36477
36563
|
} finally {
|
|
@@ -36850,8 +36936,8 @@ async function createDashboardTui({
|
|
|
36850
36936
|
return;
|
|
36851
36937
|
};
|
|
36852
36938
|
let desiredContentScrollY = 0;
|
|
36853
|
-
const action = new Promise((
|
|
36854
|
-
resolveAction =
|
|
36939
|
+
const action = new Promise((resolve2, reject) => {
|
|
36940
|
+
resolveAction = resolve2;
|
|
36855
36941
|
rejectDraft = reject;
|
|
36856
36942
|
});
|
|
36857
36943
|
let mountedSearchComposer;
|
|
@@ -37387,7 +37473,7 @@ function renderPathCompletionMenu({ completions, selectedIndex, width, maxRows }
|
|
|
37387
37473
|
// src/tui/path-completion.ts
|
|
37388
37474
|
import { readdir } from "fs/promises";
|
|
37389
37475
|
import { homedir } from "os";
|
|
37390
|
-
import { basename as
|
|
37476
|
+
import { basename as basename3, dirname as dirname5, isAbsolute, join as join5, relative, resolve as resolve2, sep } from "path";
|
|
37391
37477
|
var maximumCandidates = 50;
|
|
37392
37478
|
async function completePathToken({ text, cwd, homeDirectory = homedir() }) {
|
|
37393
37479
|
const token = activePathToken(text);
|
|
@@ -37395,10 +37481,10 @@ async function completePathToken({ text, cwd, homeDirectory = homedir() }) {
|
|
|
37395
37481
|
return [];
|
|
37396
37482
|
const pathText = token.startsWith("@") ? token.slice(1) : token;
|
|
37397
37483
|
const pathUsesTilde = pathText === "~" || pathText.startsWith("~/");
|
|
37398
|
-
const expandedPath = pathText === "~" ? homeDirectory : pathText.startsWith("~/") ?
|
|
37399
|
-
const absolutePath = isAbsolute(expandedPath) ? expandedPath :
|
|
37484
|
+
const expandedPath = pathText === "~" ? homeDirectory : pathText.startsWith("~/") ? join5(homeDirectory, pathText.slice(2)) : pathText;
|
|
37485
|
+
const absolutePath = isAbsolute(expandedPath) ? expandedPath : resolve2(cwd, expandedPath);
|
|
37400
37486
|
const directory = pathText === "" || pathText === "~" || pathText.endsWith("/") ? absolutePath : dirname5(absolutePath);
|
|
37401
|
-
const prefix = pathText === "" || pathText === "~" || pathText.endsWith("/") ? "" :
|
|
37487
|
+
const prefix = pathText === "" || pathText === "~" || pathText.endsWith("/") ? "" : basename3(absolutePath);
|
|
37402
37488
|
let entries;
|
|
37403
37489
|
try {
|
|
37404
37490
|
entries = await readdir(directory, { withFileTypes: true, encoding: "utf8" });
|
|
@@ -37406,7 +37492,7 @@ async function completePathToken({ text, cwd, homeDirectory = homedir() }) {
|
|
|
37406
37492
|
return [];
|
|
37407
37493
|
}
|
|
37408
37494
|
return entries.filter((entry) => entry.name.toLowerCase().startsWith(prefix.toLowerCase())).sort((left, right) => left.name.localeCompare(right.name)).slice(0, maximumCandidates).map((entry) => {
|
|
37409
|
-
const completedPath =
|
|
37495
|
+
const completedPath = join5(directory, entry.name);
|
|
37410
37496
|
const replacementPath = pathUsesTilde ? `~/${relative(homeDirectory, completedPath)}` : isAbsolute(pathText) ? completedPath : relative(cwd, completedPath) || entry.name;
|
|
37411
37497
|
const normalized = replacementPath.split(sep).join("/");
|
|
37412
37498
|
const suffix = entry.isDirectory() ? "/" : "";
|
|
@@ -38152,8 +38238,8 @@ async function createNewSessionWizard({
|
|
|
38152
38238
|
let rejectDraft = () => {
|
|
38153
38239
|
return;
|
|
38154
38240
|
};
|
|
38155
|
-
const draft = new Promise((
|
|
38156
|
-
resolveDraft =
|
|
38241
|
+
const draft = new Promise((resolve3, reject) => {
|
|
38242
|
+
resolveDraft = resolve3;
|
|
38157
38243
|
rejectDraft = reject;
|
|
38158
38244
|
});
|
|
38159
38245
|
let editor;
|
|
@@ -38570,7 +38656,7 @@ async function createDefaultRenderer2() {
|
|
|
38570
38656
|
}
|
|
38571
38657
|
|
|
38572
38658
|
// src/tui/session-view.ts
|
|
38573
|
-
import { CliRenderEvents as CliRenderEvents4, bg as bg3, bold as bold5, dim as dim7, fg as fg10, link, StyledText as
|
|
38659
|
+
import { CliRenderEvents as CliRenderEvents4, bg as bg3, bold as bold5, dim as dim7, fg as fg10, link as link2, StyledText as StyledText9, stringToStyledText as stringToStyledText4, TextRenderable as TextRenderable4 } from "@opentui/core";
|
|
38574
38660
|
|
|
38575
38661
|
// src/tui/tool-output.ts
|
|
38576
38662
|
var maxPreviewCharacters = 8000;
|
|
@@ -38661,6 +38747,122 @@ function formattedJson(value) {
|
|
|
38661
38747
|
return JSON.stringify(presentation(value), null, 2);
|
|
38662
38748
|
}
|
|
38663
38749
|
|
|
38750
|
+
// src/tui/links.ts
|
|
38751
|
+
import { StyledText as StyledText8, TextAttributes } from "@opentui/core";
|
|
38752
|
+
function browserLink(value) {
|
|
38753
|
+
if ([...value].some((character) => character.charCodeAt(0) <= 32 || character.charCodeAt(0) >= 127 && character.charCodeAt(0) <= 159))
|
|
38754
|
+
return;
|
|
38755
|
+
try {
|
|
38756
|
+
const url3 = new URL(value);
|
|
38757
|
+
if ((url3.protocol === "https:" || url3.protocol === "http:") && !url3.username && !url3.password)
|
|
38758
|
+
return value;
|
|
38759
|
+
} catch {}
|
|
38760
|
+
return;
|
|
38761
|
+
}
|
|
38762
|
+
function linkifyText(content) {
|
|
38763
|
+
return new StyledText8(content.chunks.flatMap((chunk) => {
|
|
38764
|
+
const text = terminalSafeText(chunk.text);
|
|
38765
|
+
if (chunk.link)
|
|
38766
|
+
return [{ ...chunk, text, link: browserLink(chunk.link.url) ? chunk.link : undefined }];
|
|
38767
|
+
const chunks = [];
|
|
38768
|
+
let offset = 0;
|
|
38769
|
+
const pattern = /https?:\/\/[^\s<>"`]+/gu;
|
|
38770
|
+
for (const match of text.matchAll(pattern)) {
|
|
38771
|
+
const target = trimUrlPunctuation(match[0]);
|
|
38772
|
+
if (!browserLink(target))
|
|
38773
|
+
continue;
|
|
38774
|
+
const prefix = text.slice(offset, match.index);
|
|
38775
|
+
const label = /\[[^\][\n]+\]\($/u.exec(prefix);
|
|
38776
|
+
const start = label ? offset + label.index : match.index;
|
|
38777
|
+
const urlEnd = match.index + target.length;
|
|
38778
|
+
const end = urlEnd + (label && text[urlEnd] === ")" ? 1 : 0);
|
|
38779
|
+
chunks.push({ ...chunk, text: text.slice(offset, start) });
|
|
38780
|
+
chunks.push({ ...chunk, text: text.slice(start, end), link: { url: target }, attributes: (chunk.attributes ?? 0) | TextAttributes.UNDERLINE });
|
|
38781
|
+
offset = end;
|
|
38782
|
+
}
|
|
38783
|
+
chunks.push({ ...chunk, text: text.slice(offset) });
|
|
38784
|
+
return chunks.filter((part) => part.text.length > 0);
|
|
38785
|
+
}));
|
|
38786
|
+
}
|
|
38787
|
+
function trimUrlPunctuation(text) {
|
|
38788
|
+
const counts = new Map;
|
|
38789
|
+
for (const character of text)
|
|
38790
|
+
counts.set(character, (counts.get(character) ?? 0) + 1);
|
|
38791
|
+
const openings = new Map([[")", "("], ["]", "["], ["}", "{"]]);
|
|
38792
|
+
let end = text.length;
|
|
38793
|
+
while (end > 0) {
|
|
38794
|
+
const last = text[end - 1];
|
|
38795
|
+
const opening = openings.get(last);
|
|
38796
|
+
if (!".,;:!?".includes(last) && !(opening && (counts.get(last) ?? 0) > (counts.get(opening) ?? 0)))
|
|
38797
|
+
break;
|
|
38798
|
+
counts.set(last, (counts.get(last) ?? 0) - 1);
|
|
38799
|
+
end -= 1;
|
|
38800
|
+
}
|
|
38801
|
+
return text.slice(0, end);
|
|
38802
|
+
}
|
|
38803
|
+
function clickedLink({ text, event }) {
|
|
38804
|
+
const row = event.y - text.y + text.scrollY;
|
|
38805
|
+
const column = event.x - text.x;
|
|
38806
|
+
const info = text.lineInfo;
|
|
38807
|
+
const start = info.lineStartCols[row];
|
|
38808
|
+
const width = info.lineWidthCols[row];
|
|
38809
|
+
if (start === undefined || width === undefined || column < 0 || column >= width)
|
|
38810
|
+
return;
|
|
38811
|
+
const position = start + column + text.scrollX;
|
|
38812
|
+
let offset = 0;
|
|
38813
|
+
for (const chunk of text.chunks) {
|
|
38814
|
+
const size = Bun.stringWidth(chunk.text.replaceAll("\t", " ").replaceAll(`
|
|
38815
|
+
`, " "));
|
|
38816
|
+
if (position >= offset && position < offset + size)
|
|
38817
|
+
return chunk.link ? browserLink(chunk.link.url) : undefined;
|
|
38818
|
+
offset += size;
|
|
38819
|
+
}
|
|
38820
|
+
return;
|
|
38821
|
+
}
|
|
38822
|
+
|
|
38823
|
+
// src/tui/session-files.ts
|
|
38824
|
+
function sessionFileChoices(state) {
|
|
38825
|
+
const files = new Map;
|
|
38826
|
+
for (const item of state.transcript.toReversed()) {
|
|
38827
|
+
if (item.kind === "artifact") {
|
|
38828
|
+
const key = `artifact:${item.artifactId}`;
|
|
38829
|
+
if (files.has(key))
|
|
38830
|
+
continue;
|
|
38831
|
+
files.set(key, {
|
|
38832
|
+
key,
|
|
38833
|
+
label: item.title,
|
|
38834
|
+
description: item.artifactKind === "file" ? "Artifact \xB7 Remy" : "Tela Page \xB7 Remy",
|
|
38835
|
+
previewUrl: item.artifactKind === "file" ? item.previewUrl : browserLink(item.url),
|
|
38836
|
+
download: item.artifactKind === "file" ? { kind: "artifact", id: item.artifactId, filename: item.title } : undefined
|
|
38837
|
+
});
|
|
38838
|
+
}
|
|
38839
|
+
if (item.kind === "message") {
|
|
38840
|
+
for (const attachment of item.attachments) {
|
|
38841
|
+
const key = `attachment:${attachment.id}`;
|
|
38842
|
+
if (files.has(key))
|
|
38843
|
+
continue;
|
|
38844
|
+
let previewUrl;
|
|
38845
|
+
if (state.webUrl && browserLink(state.webUrl)) {
|
|
38846
|
+
const url3 = new URL(state.webUrl);
|
|
38847
|
+
url3.searchParams.delete("artifact");
|
|
38848
|
+
url3.searchParams.delete("event_id");
|
|
38849
|
+
url3.searchParams.set("attachment", attachment.id);
|
|
38850
|
+
url3.hash = "";
|
|
38851
|
+
previewUrl = url3.href;
|
|
38852
|
+
}
|
|
38853
|
+
files.set(key, {
|
|
38854
|
+
key,
|
|
38855
|
+
label: attachment.filename,
|
|
38856
|
+
description: `Attachment \xB7 ${item.author.label}${item.author.source ? ` (${item.author.source})` : ""}`,
|
|
38857
|
+
previewUrl,
|
|
38858
|
+
download: { kind: "attachment", id: attachment.id, filename: attachment.filename }
|
|
38859
|
+
});
|
|
38860
|
+
}
|
|
38861
|
+
}
|
|
38862
|
+
}
|
|
38863
|
+
return [...files.values()];
|
|
38864
|
+
}
|
|
38865
|
+
|
|
38664
38866
|
// src/tui/session-view.ts
|
|
38665
38867
|
var composerSlashCommands = [
|
|
38666
38868
|
{ value: "/sessions", description: "Back to the session list" },
|
|
@@ -38669,6 +38871,8 @@ var composerSlashCommands = [
|
|
|
38669
38871
|
{ value: "/compact", description: "Compact main-thread context" },
|
|
38670
38872
|
{ value: "/new", description: "Start a new session" },
|
|
38671
38873
|
{ value: "/logout", description: "Sign out of Remy" },
|
|
38874
|
+
{ value: "/files", description: "Download artifacts and user attachments" },
|
|
38875
|
+
{ value: "/links", description: "Open conversation links" },
|
|
38672
38876
|
{ value: "/help", description: "Browse session commands" },
|
|
38673
38877
|
{ value: "/exit", description: "Leave Remy" }
|
|
38674
38878
|
];
|
|
@@ -38679,6 +38883,8 @@ async function createSessionTui({
|
|
|
38679
38883
|
requestCompact,
|
|
38680
38884
|
requestComplete,
|
|
38681
38885
|
requestCancel,
|
|
38886
|
+
openBrowser,
|
|
38887
|
+
downloadFile,
|
|
38682
38888
|
repositoryLabel,
|
|
38683
38889
|
readClipboardImage,
|
|
38684
38890
|
savePastedImage,
|
|
@@ -38765,10 +38971,10 @@ async function createSessionTui({
|
|
|
38765
38971
|
syncTerminalSessionControls();
|
|
38766
38972
|
syncWorkingIndicator();
|
|
38767
38973
|
renderRetainedTranscript();
|
|
38768
|
-
const assistantPreview = latestState.previews.assistantText ? new
|
|
38769
|
-
`)), fg10(PALETTE.remyAccent)(latestState.previews.assistantText)]) : new
|
|
38770
|
-
const reasoningPreview = latestState.previews.reasoningText ? new
|
|
38771
|
-
`)), dim7(fg10(PALETTE.dimText)(latestState.previews.reasoningText))]) : new
|
|
38974
|
+
const assistantPreview = latestState.previews.assistantText ? new StyledText9([dim7(fg10(PALETTE.dimText)(`Live assistant preview
|
|
38975
|
+
`)), fg10(PALETTE.remyAccent)(latestState.previews.assistantText)]) : new StyledText9([]);
|
|
38976
|
+
const reasoningPreview = latestState.previews.reasoningText ? new StyledText9([dim7(fg10(PALETTE.dimText)(`Live reasoning preview
|
|
38977
|
+
`)), dim7(fg10(PALETTE.dimText)(latestState.previews.reasoningText))]) : new StyledText9([]);
|
|
38772
38978
|
const working = isRemyWorking(latestState);
|
|
38773
38979
|
const startedAt = workingTurnStartedAt(latestState);
|
|
38774
38980
|
const elapsedMs = startedAt === undefined ? 0 : Math.max(0, now3() - Date.parse(startedAt));
|
|
@@ -38777,13 +38983,13 @@ async function createSessionTui({
|
|
|
38777
38983
|
frame: SPINNER_FRAMES[spinnerFrameIndex],
|
|
38778
38984
|
elapsedMs,
|
|
38779
38985
|
mode: stopState.kind === "stopping" ? "stopping" : "working"
|
|
38780
|
-
}) : new
|
|
38986
|
+
}) : new StyledText9([]);
|
|
38781
38987
|
const liveContent = [assistantPreview, reasoningPreview, liveIndicator].filter((part) => part.chunks.length > 0);
|
|
38782
|
-
liveTranscript.content = liveContent.length === 0 ? "" : joinStyled([stringToStyledText4(`
|
|
38988
|
+
liveTranscript.content = liveContent.length === 0 ? "" : linkifyText(joinStyled([stringToStyledText4(`
|
|
38783
38989
|
|
|
38784
38990
|
`), joinStyled(liveContent, `
|
|
38785
38991
|
|
|
38786
|
-
`)], "");
|
|
38992
|
+
`)], ""));
|
|
38787
38993
|
composer.placeholder = lifecycleRequestState.kind === "pending" ? `${lifecycleOperationPresentParticiple(lifecycleRequestState.operation)} the session \u2014 messages are paused.` : idleComposerPlaceholder;
|
|
38788
38994
|
if (latestState.aggregateStatus === "open")
|
|
38789
38995
|
composerLayout.render();
|
|
@@ -38804,7 +39010,72 @@ async function createSessionTui({
|
|
|
38804
39010
|
`) : "";
|
|
38805
39011
|
hints.visible = logoutConfirmationOpen || renderer.height >= TUI_LAYOUT.actionBarMinHeight;
|
|
38806
39012
|
hints.content = hints.visible ? logoutConfirmationOpen ? renderKeyHints({ hints: [{ key: "y", action: "confirm" }, { key: "Esc", action: "cancel" }], width: renderer.width - 2 }) : renderActionBar({ state: latestState, working, stopState, lifecycleRequestState, contextCompactionRequestState, hasFailedSubmission: admittedSubmissions.some((submission) => submission.status === "failed"), width: renderer.width - 2 }) : "";
|
|
39013
|
+
if (resourceMode)
|
|
39014
|
+
renderResources();
|
|
38807
39015
|
renderer.requestRender();
|
|
39016
|
+
}, resourceChoices = function() {
|
|
39017
|
+
if (resourceCache?.transcript === latestState.transcript && resourceCache.webUrl === latestState.webUrl && resourceCache.mode === resourceMode)
|
|
39018
|
+
return resourceCache.choices;
|
|
39019
|
+
const choices = resourceMode === "files" ? sessionFileChoices(latestState) : conversationLinkChoices();
|
|
39020
|
+
resourceCache = { transcript: latestState.transcript, webUrl: latestState.webUrl, mode: resourceMode, choices };
|
|
39021
|
+
return choices;
|
|
39022
|
+
}, conversationLinkChoices = function() {
|
|
39023
|
+
const content = linkifyText(renderTimeline({ state: latestState, activityExpanded: true }));
|
|
39024
|
+
const urls = new Map;
|
|
39025
|
+
for (const chunk of content.chunks) {
|
|
39026
|
+
const url3 = chunk.link?.url;
|
|
39027
|
+
if (url3 && !urls.has(url3))
|
|
39028
|
+
urls.set(url3, { key: url3, label: chunk.text.trim(), description: url3, previewUrl: url3, download: undefined });
|
|
39029
|
+
}
|
|
39030
|
+
return [...urls.values()];
|
|
39031
|
+
}, openResources = function(mode) {
|
|
39032
|
+
resourceMode = mode;
|
|
39033
|
+
resourceKey = resourceChoices()[0]?.key;
|
|
39034
|
+
resourceFeedback = undefined;
|
|
39035
|
+
pathCompletionMenuOpen = false;
|
|
39036
|
+
slashCompletions = [];
|
|
39037
|
+
helpVisible = false;
|
|
39038
|
+
composer.blur();
|
|
39039
|
+
root.visible = false;
|
|
39040
|
+
resourceRoot.visible = true;
|
|
39041
|
+
resourceBody.scrollTo(0);
|
|
39042
|
+
render();
|
|
39043
|
+
}, renderResources = function() {
|
|
39044
|
+
const choices = resourceChoices();
|
|
39045
|
+
const index = Math.max(0, choices.findIndex((choice) => choice.key === resourceKey));
|
|
39046
|
+
resourceKey = choices[index]?.key;
|
|
39047
|
+
const selected = choices[index];
|
|
39048
|
+
const width = Math.max(1, renderer.width - 2);
|
|
39049
|
+
resourceHeading.content = new StyledText9([bold5(fg10(PALETTE.remyAccent)(`${resourceMode === "files" ? "Files" : "Links"} (${choices.length})`))]);
|
|
39050
|
+
const menu = renderCompletionMenu({
|
|
39051
|
+
items: choices.map((choice) => ({ label: terminalSafeText(choice.label), description: terminalSafeText(choice.description) })),
|
|
39052
|
+
selectedIndex: index,
|
|
39053
|
+
hints: [],
|
|
39054
|
+
width,
|
|
39055
|
+
maxRows: Math.max(2, renderer.height - 7)
|
|
39056
|
+
});
|
|
39057
|
+
const detail = selected ? [
|
|
39058
|
+
truncateText(terminalSafeText(selected.description), width),
|
|
39059
|
+
truncateText(terminalSafeText(selected.previewUrl ?? "Web preview unavailable"), width)
|
|
39060
|
+
].join(`
|
|
39061
|
+
`) : resourceMode === "files" ? "No artifacts or attachments in this session yet." : "No web links in this conversation yet.";
|
|
39062
|
+
resourceContent.content = joinStyled([
|
|
39063
|
+
...resourceFeedback ? [renderFeedback({ ...resourceFeedback, message: terminalSafeText(resourceFeedback.message) })] : [],
|
|
39064
|
+
...menu ? [menu] : [],
|
|
39065
|
+
stringToStyledText4(detail),
|
|
39066
|
+
...resourceMode === "files" ? [stringToStyledText4("Downloads save to ~/Downloads")] : []
|
|
39067
|
+
], `
|
|
39068
|
+
`);
|
|
39069
|
+
resourceHints.content = renderKeyHints({
|
|
39070
|
+
hints: [
|
|
39071
|
+
{ key: "\u2191\u2193", action: "select" },
|
|
39072
|
+
{ key: "Enter", action: selected?.download ? "download" : "open" },
|
|
39073
|
+
...selected?.download ? [{ key: "o", action: "preview" }] : [],
|
|
39074
|
+
{ key: "c", action: "copy URL" },
|
|
39075
|
+
{ key: "Esc", action: resourceRequest ? "cancel" : "back" }
|
|
39076
|
+
],
|
|
39077
|
+
width
|
|
39078
|
+
});
|
|
38808
39079
|
}, syncTerminalSessionControls = function() {
|
|
38809
39080
|
const terminal = latestState.aggregateStatus !== "open";
|
|
38810
39081
|
if (terminal && composerMounted) {
|
|
@@ -38824,9 +39095,9 @@ async function createSessionTui({
|
|
|
38824
39095
|
}
|
|
38825
39096
|
const timeline = renderTimeline({ state: latestState, activityExpanded });
|
|
38826
39097
|
const submittedTimeline = renderSubmittedTimeline({ state: latestState, admittedSubmissions });
|
|
38827
|
-
transcript.content = joinStyled([timeline, submittedTimeline].filter((part) => part.chunks.length > 0), `
|
|
39098
|
+
transcript.content = linkifyText(joinStyled([timeline, submittedTimeline].filter((part) => part.chunks.length > 0), `
|
|
38828
39099
|
|
|
38829
|
-
`);
|
|
39100
|
+
`));
|
|
38830
39101
|
renderedTranscript = latestState.transcript;
|
|
38831
39102
|
renderedSessionId = latestState.sessionId;
|
|
38832
39103
|
renderedAggregateStatus = latestState.aggregateStatus;
|
|
@@ -38880,6 +39151,7 @@ async function createSessionTui({
|
|
|
38880
39151
|
if (settled)
|
|
38881
39152
|
return;
|
|
38882
39153
|
settled = true;
|
|
39154
|
+
resourceRequest?.abort();
|
|
38883
39155
|
invalidateAttachmentPreparation();
|
|
38884
39156
|
if (error93)
|
|
38885
39157
|
rejectAction(error93);
|
|
@@ -38899,6 +39171,17 @@ async function createSessionTui({
|
|
|
38899
39171
|
root.add(transcriptScrollBox);
|
|
38900
39172
|
root.add(status);
|
|
38901
39173
|
renderer.root.add(root);
|
|
39174
|
+
const resourceRoot = createScreenRoot({ renderer, gutter: TUI_LAYOUT.conversationGutter });
|
|
39175
|
+
resourceRoot.visible = false;
|
|
39176
|
+
const resourceHeading = new TextRenderable4(renderer, { content: "", flexShrink: 0 });
|
|
39177
|
+
const resourceBody = createBodyViewport({ renderer, followTail: false });
|
|
39178
|
+
const resourceContent = new TextRenderable4(renderer, { content: "" });
|
|
39179
|
+
const resourceHints = new TextRenderable4(renderer, { content: "", flexShrink: 0 });
|
|
39180
|
+
resourceBody.add(resourceContent);
|
|
39181
|
+
resourceRoot.add(resourceHeading);
|
|
39182
|
+
resourceRoot.add(resourceBody);
|
|
39183
|
+
resourceRoot.add(resourceHints);
|
|
39184
|
+
renderer.root.add(resourceRoot);
|
|
38902
39185
|
let destroyed = false;
|
|
38903
39186
|
let rendererDestroyPromise;
|
|
38904
39187
|
let settled = false;
|
|
@@ -38925,6 +39208,11 @@ async function createSessionTui({
|
|
|
38925
39208
|
let contextCompactionRequestState = { kind: "idle" };
|
|
38926
39209
|
let helpVisible = false;
|
|
38927
39210
|
let logoutConfirmationOpen = false;
|
|
39211
|
+
let resourceMode;
|
|
39212
|
+
let resourceKey;
|
|
39213
|
+
let resourceFeedback;
|
|
39214
|
+
let resourceRequest;
|
|
39215
|
+
let resourceCache;
|
|
38928
39216
|
let latestState = controller.getState();
|
|
38929
39217
|
let spinnerFrameIndex = 0;
|
|
38930
39218
|
let renderedTranscript;
|
|
@@ -38943,8 +39231,8 @@ async function createSessionTui({
|
|
|
38943
39231
|
let rejectAction = () => {
|
|
38944
39232
|
return;
|
|
38945
39233
|
};
|
|
38946
|
-
const action = new Promise((
|
|
38947
|
-
resolveAction =
|
|
39234
|
+
const action = new Promise((resolve3, reject) => {
|
|
39235
|
+
resolveAction = resolve3;
|
|
38948
39236
|
rejectAction = reject;
|
|
38949
39237
|
});
|
|
38950
39238
|
const composerTag = () => latestState.sessionNumber !== undefined ? `Session #${latestState.sessionNumber}` : latestState.sessionId;
|
|
@@ -39004,6 +39292,38 @@ async function createSessionTui({
|
|
|
39004
39292
|
}
|
|
39005
39293
|
return;
|
|
39006
39294
|
}
|
|
39295
|
+
if (resourceMode) {
|
|
39296
|
+
key.preventDefault();
|
|
39297
|
+
const choices = resourceChoices();
|
|
39298
|
+
const index = Math.max(0, choices.findIndex((choice) => choice.key === resourceKey));
|
|
39299
|
+
if (key.name === "escape") {
|
|
39300
|
+
resourceRequest?.abort();
|
|
39301
|
+
resourceRequest = undefined;
|
|
39302
|
+
resourceMode = undefined;
|
|
39303
|
+
root.visible = true;
|
|
39304
|
+
resourceRoot.visible = false;
|
|
39305
|
+
if (latestState.aggregateStatus === "open")
|
|
39306
|
+
composer.focus();
|
|
39307
|
+
} else if (key.name === "up" || key.name === "down" || key.name === "j" || key.name === "k") {
|
|
39308
|
+
const next = key.name === "up" || key.name === "k" ? index - 1 : index + 1;
|
|
39309
|
+
resourceKey = choices[Math.max(0, Math.min(choices.length - 1, next))]?.key;
|
|
39310
|
+
resourceBody.scrollTo(0);
|
|
39311
|
+
} else if (key.name === "return" || key.name === "d" || key.name === "o") {
|
|
39312
|
+
const choice = choices[index];
|
|
39313
|
+
if (choice)
|
|
39314
|
+
actOnResource(choice, key.name === "o");
|
|
39315
|
+
} else if (key.name === "c") {
|
|
39316
|
+
const url3 = choices[index]?.previewUrl;
|
|
39317
|
+
resourceFeedback = url3 && renderer.copyToClipboardOSC52(url3) ? { kind: "success", message: "URL copied." } : { kind: "failure", message: "Could not copy a URL. Clipboard support or a Web preview is unavailable." };
|
|
39318
|
+
}
|
|
39319
|
+
render();
|
|
39320
|
+
return;
|
|
39321
|
+
}
|
|
39322
|
+
if (key.ctrl && (key.name === "f" || key.name === "l")) {
|
|
39323
|
+
key.preventDefault();
|
|
39324
|
+
openResources(key.name === "f" ? "files" : "links");
|
|
39325
|
+
return;
|
|
39326
|
+
}
|
|
39007
39327
|
if (key.ctrl && key.name === "v" && latestState.aggregateStatus === "open") {
|
|
39008
39328
|
key.preventDefault();
|
|
39009
39329
|
pasteClipboardImage();
|
|
@@ -39157,6 +39477,67 @@ async function createSessionTui({
|
|
|
39157
39477
|
navigatePromptHistory(key.name);
|
|
39158
39478
|
}
|
|
39159
39479
|
};
|
|
39480
|
+
async function actOnResource(choice, preview2) {
|
|
39481
|
+
if (resourceRequest)
|
|
39482
|
+
return;
|
|
39483
|
+
const request = new AbortController;
|
|
39484
|
+
resourceRequest = request;
|
|
39485
|
+
resourceFeedback = { kind: "progress", message: `${choice.download && !preview2 ? "Downloading" : "Opening"} ${choice.label}\u2026` };
|
|
39486
|
+
render();
|
|
39487
|
+
try {
|
|
39488
|
+
if (choice.download && !preview2) {
|
|
39489
|
+
const path = await downloadFile({ file: choice.download, signal: request.signal });
|
|
39490
|
+
if (!request.signal.aborted && !destroyed && !settled)
|
|
39491
|
+
resourceFeedback = { kind: "success", message: `Saved to ${path}` };
|
|
39492
|
+
} else {
|
|
39493
|
+
if (!choice.previewUrl || !browserLink(choice.previewUrl))
|
|
39494
|
+
throw new Error("Web preview unavailable from this server. You can still download this file.");
|
|
39495
|
+
await openBrowser(choice.previewUrl);
|
|
39496
|
+
if (!request.signal.aborted && !destroyed && !settled)
|
|
39497
|
+
resourceFeedback = { kind: "success", message: "Opened in your browser." };
|
|
39498
|
+
}
|
|
39499
|
+
} catch (error93) {
|
|
39500
|
+
if (!request.signal.aborted && !destroyed && !settled)
|
|
39501
|
+
resourceFeedback = { kind: "failure", message: `${error93 instanceof Error ? error93.message : String(error93)} Press Enter to retry, or o for preview.` };
|
|
39502
|
+
} finally {
|
|
39503
|
+
if (resourceRequest === request)
|
|
39504
|
+
resourceRequest = undefined;
|
|
39505
|
+
if (!request.signal.aborted && !destroyed && !settled)
|
|
39506
|
+
render();
|
|
39507
|
+
}
|
|
39508
|
+
}
|
|
39509
|
+
for (const text of [transcript, liveTranscript]) {
|
|
39510
|
+
let pressedLink;
|
|
39511
|
+
text.onMouseDown = (event) => {
|
|
39512
|
+
pressedLink = undefined;
|
|
39513
|
+
if (event.button !== 0 || !event.modifiers.ctrl || event.isDragging)
|
|
39514
|
+
return;
|
|
39515
|
+
const url3 = clickedLink({ text, event });
|
|
39516
|
+
if (!url3)
|
|
39517
|
+
return;
|
|
39518
|
+
event.preventDefault();
|
|
39519
|
+
event.stopPropagation();
|
|
39520
|
+
pressedLink = { url: url3, x: event.x, y: event.y };
|
|
39521
|
+
};
|
|
39522
|
+
text.onMouseDrag = () => {
|
|
39523
|
+
pressedLink = undefined;
|
|
39524
|
+
};
|
|
39525
|
+
text.onMouseUp = (event) => {
|
|
39526
|
+
const pressed = pressedLink;
|
|
39527
|
+
pressedLink = undefined;
|
|
39528
|
+
if (!pressed || event.button !== 0 || !event.modifiers.ctrl || event.isDragging || event.x !== pressed.x || event.y !== pressed.y || clickedLink({ text, event }) !== pressed.url) {
|
|
39529
|
+
return;
|
|
39530
|
+
}
|
|
39531
|
+
event.preventDefault();
|
|
39532
|
+
event.stopPropagation();
|
|
39533
|
+
openBrowser(pressed.url).catch(() => {
|
|
39534
|
+
if (destroyed || settled)
|
|
39535
|
+
return;
|
|
39536
|
+
composerFeedback = { kind: "failure", message: "Could not open your browser. Use /links to select and copy the URL." };
|
|
39537
|
+
render();
|
|
39538
|
+
});
|
|
39539
|
+
};
|
|
39540
|
+
}
|
|
39160
39541
|
async function openPathCompletionMenu() {
|
|
39161
39542
|
if (destroyed)
|
|
39162
39543
|
return;
|
|
@@ -39257,6 +39638,12 @@ async function createSessionTui({
|
|
|
39257
39638
|
finish({ kind: "exit" });
|
|
39258
39639
|
return true;
|
|
39259
39640
|
}
|
|
39641
|
+
if (command === "/files" || command === "/links") {
|
|
39642
|
+
composer.setText("");
|
|
39643
|
+
composerDraft = { ...composerDraft, text: "" };
|
|
39644
|
+
openResources(command === "/files" ? "files" : "links");
|
|
39645
|
+
return true;
|
|
39646
|
+
}
|
|
39260
39647
|
if (command === "/new") {
|
|
39261
39648
|
composer.setText("");
|
|
39262
39649
|
composerDraft = { ...composerDraft, text: "" };
|
|
@@ -39479,6 +39866,7 @@ async function createSessionTui({
|
|
|
39479
39866
|
if (destroyed)
|
|
39480
39867
|
return;
|
|
39481
39868
|
destroyed = true;
|
|
39869
|
+
resourceRequest?.abort();
|
|
39482
39870
|
invalidateAttachmentPreparation();
|
|
39483
39871
|
if (workingSpinnerTimer) {
|
|
39484
39872
|
clearInterval(workingSpinnerTimer);
|
|
@@ -39532,7 +39920,7 @@ function renderStatusBand({ state, working, elapsedMs, stopState, lifecycleReque
|
|
|
39532
39920
|
const pendingOperation = lifecycleRequestState?.kind === "pending" ? lifecycleRequestState.operation : undefined;
|
|
39533
39921
|
const presentation = pendingOperation ? { label: lifecycleOperationPresentParticiple(pendingOperation), color: PALETTE.approvalQuestion } : stopping ? { label: "Stopping", color: PALETTE.approvalQuestion } : sessionViewStatePresentation({ aggregateStatus: state.aggregateStatus, isWorking: working });
|
|
39534
39922
|
const elapsed = (working || stopping) && elapsedMs !== undefined && elapsedMs > 0 ? ` ${formatElapsed(elapsedMs)}` : "";
|
|
39535
|
-
const connection = state.connectionStatus === "connected" ? new
|
|
39923
|
+
const connection = state.connectionStatus === "connected" ? new StyledText9([fg10(PALETTE.statusCompleted)("\u25CF "), dim7(fg10(PALETTE.dimText)("connected"))]) : new StyledText9([fg10(PALETTE.approvalQuestion)("\u25CF reconnecting\u2026")]);
|
|
39536
39924
|
const sessionLabel = state.sessionNumber !== undefined ? `#${state.sessionNumber}` : state.sessionId;
|
|
39537
39925
|
const pullRequest = state.pullRequest;
|
|
39538
39926
|
const contextStatus = renderContextStatus(state.context.snapshot);
|
|
@@ -39558,17 +39946,17 @@ function renderContextStatus(snapshot) {
|
|
|
39558
39946
|
function renderTerminalSessionBand({ aggregateStatus, width }) {
|
|
39559
39947
|
const status = sessionStatusLabel(aggregateStatus).toLowerCase();
|
|
39560
39948
|
return joinStyled([
|
|
39561
|
-
new
|
|
39949
|
+
new StyledText9([
|
|
39562
39950
|
bg3(PALETTE.selectionBg)(bold5(fg10(PALETTE.systemAccent)(` Session ${status} `))),
|
|
39563
39951
|
bg3(PALETTE.selectionBg)(fg10(PALETTE.bodyText)("\u2014 this conversation is closed. "))
|
|
39564
39952
|
]),
|
|
39565
|
-
renderKeyHints({ hints: [{ key: "esc", action: "return to dashboard" }, { key: "n", action: "new session" }], width })
|
|
39953
|
+
renderKeyHints({ hints: [{ key: "esc", action: "return to dashboard" }, { key: "n", action: "new session" }, { key: "ctrl+f", action: "files" }, { key: "ctrl+l", action: "links" }], width })
|
|
39566
39954
|
], `
|
|
39567
39955
|
`);
|
|
39568
39956
|
}
|
|
39569
39957
|
function renderActionBar({ state, working, stopState, lifecycleRequestState, contextCompactionRequestState, hasFailedSubmission, width }) {
|
|
39570
39958
|
if (state.aggregateStatus !== "open")
|
|
39571
|
-
return new
|
|
39959
|
+
return new StyledText9([]);
|
|
39572
39960
|
const hints = [{ key: "/", action: "commands" }];
|
|
39573
39961
|
if (working && stopState.kind === "failed")
|
|
39574
39962
|
hints.push({ key: "esc", action: "retry stop" });
|
|
@@ -39580,7 +39968,7 @@ function renderActionBar({ state, working, stopState, lifecycleRequestState, con
|
|
|
39580
39968
|
hints.push({ key: "ctrl+r", action: "retry oldest failed message" });
|
|
39581
39969
|
if (!working && lifecycleRequestState.kind !== "pending")
|
|
39582
39970
|
hints.push({ key: "/new", action: "" }, { key: "/sessions", action: "" });
|
|
39583
|
-
hints.push({ key: "ctrl+o", action: "activity" });
|
|
39971
|
+
hints.push({ key: "ctrl+o", action: "activity" }, { key: "ctrl+f", action: "files" }, { key: "ctrl+l", action: "links" });
|
|
39584
39972
|
return renderKeyHints({ hints, width });
|
|
39585
39973
|
}
|
|
39586
39974
|
function isRemyWorking(state) {
|
|
@@ -39640,20 +40028,20 @@ function renderTimelineItem(item, activityExpanded) {
|
|
|
39640
40028
|
const sourceUrl = item.source.url ? terminalSafeText(item.source.url) : null;
|
|
39641
40029
|
const notificationData = JSON.stringify(item.notifications, null, 2);
|
|
39642
40030
|
return joinStyled([
|
|
39643
|
-
new
|
|
40031
|
+
new StyledText9([
|
|
39644
40032
|
renderTimestampChunk(item.occurredAt),
|
|
39645
40033
|
renderRoleLabel({ label: actorName, role: "system" }),
|
|
39646
40034
|
dim7(fg10(PALETTE.dimText)(` \xB7 System notification (${sourceLabel})`))
|
|
39647
40035
|
]),
|
|
39648
|
-
new
|
|
39649
|
-
...sourceUrl ? [new
|
|
39650
|
-
new
|
|
39651
|
-
...item.eventUrl ? [new
|
|
40036
|
+
new StyledText9([dim7(fg10(PALETTE.dimText)(`Actor ID: ${actorId} \xB7 Source: ${sourceProvider}`))]),
|
|
40037
|
+
...sourceUrl ? [new StyledText9([dim7(fg10(PALETTE.dimText)(`Source URL: ${sourceUrl}`))])] : [],
|
|
40038
|
+
new StyledText9([fg10(PALETTE.dimText)(notificationData)]),
|
|
40039
|
+
...item.eventUrl ? [new StyledText9([dim7(fg10(PALETTE.dimText)(`View event in Web: ${item.eventUrl}`))])] : []
|
|
39652
40040
|
], `
|
|
39653
40041
|
`);
|
|
39654
40042
|
}
|
|
39655
40043
|
if (item.kind === "message") {
|
|
39656
|
-
const header = new
|
|
40044
|
+
const header = new StyledText9([
|
|
39657
40045
|
renderTimestampChunk(item.occurredAt),
|
|
39658
40046
|
renderRoleLabel({ label: item.author.label, role: item.author.role }),
|
|
39659
40047
|
...item.author.source ? [dim7(fg10(PALETTE.dimText)(` (${item.author.source})`))] : []
|
|
@@ -39669,14 +40057,14 @@ function renderTimelineItem(item, activityExpanded) {
|
|
|
39669
40057
|
if (item.kind === "plan")
|
|
39670
40058
|
return renderPlan({ item, expanded: activityExpanded });
|
|
39671
40059
|
if (item.artifactKind === "tela_page") {
|
|
39672
|
-
return new
|
|
40060
|
+
return new StyledText9([
|
|
39673
40061
|
renderTimestampChunk(item.occurredAt),
|
|
39674
40062
|
bold5(fg10(PALETTE.tool)("Tela Page: ")),
|
|
39675
40063
|
fg10(PALETTE.bodyText)(item.title),
|
|
39676
40064
|
dim7(fg10(PALETTE.dimText)(` \xB7 ${item.url}`))
|
|
39677
40065
|
]);
|
|
39678
40066
|
}
|
|
39679
|
-
return new
|
|
40067
|
+
return new StyledText9([
|
|
39680
40068
|
renderTimestampChunk(item.occurredAt),
|
|
39681
40069
|
bold5(fg10(PALETTE.tool)("Artifact: ")),
|
|
39682
40070
|
fg10(PALETTE.bodyText)(item.title),
|
|
@@ -39686,31 +40074,31 @@ function renderTimelineItem(item, activityExpanded) {
|
|
|
39686
40074
|
var collapsedPlanItemLimit = 5;
|
|
39687
40075
|
function renderPlan({ item, expanded }) {
|
|
39688
40076
|
if (item.items.length === 0)
|
|
39689
|
-
return new
|
|
40077
|
+
return new StyledText9([]);
|
|
39690
40078
|
const completed = item.items.filter((workItem) => workItem.status === "completed").length;
|
|
39691
40079
|
const visibleItems = expanded ? item.items : collapsedPlanItems(item.items);
|
|
39692
40080
|
const rows = visibleItems.map((workItem) => {
|
|
39693
40081
|
if (workItem.status === "in_progress") {
|
|
39694
|
-
return new
|
|
40082
|
+
return new StyledText9([
|
|
39695
40083
|
fg10(PALETTE.remyAccent)(" \u25B8 "),
|
|
39696
40084
|
bold5(fg10(PALETTE.bodyText)(workItem.title))
|
|
39697
40085
|
]);
|
|
39698
40086
|
}
|
|
39699
40087
|
const glyph = workItem.status === "completed" ? "\u2713" : "\u25CB";
|
|
39700
|
-
return new
|
|
40088
|
+
return new StyledText9([
|
|
39701
40089
|
fg10(PALETTE.dimText)(` ${glyph} `),
|
|
39702
40090
|
dim7(fg10(PALETTE.dimText)(workItem.title))
|
|
39703
40091
|
]);
|
|
39704
40092
|
});
|
|
39705
40093
|
const hidden = item.items.length - visibleItems.length;
|
|
39706
40094
|
return joinStyled([
|
|
39707
|
-
new
|
|
40095
|
+
new StyledText9([
|
|
39708
40096
|
renderTimestampChunk(item.occurredAt),
|
|
39709
40097
|
bold5(fg10(PALETTE.remyAccent)("Plan")),
|
|
39710
40098
|
dim7(fg10(PALETTE.dimText)(` \xB7 ${completed}/${item.items.length} done`))
|
|
39711
40099
|
]),
|
|
39712
40100
|
...rows,
|
|
39713
|
-
...hidden > 0 ? [new
|
|
40101
|
+
...hidden > 0 ? [new StyledText9([dim7(fg10(PALETTE.dimText)(` \u2026 ${hidden} more task${hidden === 1 ? "" : "s"} \xB7 Ctrl+O details`))])] : []
|
|
39714
40102
|
], `
|
|
39715
40103
|
`);
|
|
39716
40104
|
}
|
|
@@ -39757,7 +40145,7 @@ function canonicalValuesEqual(left, right) {
|
|
|
39757
40145
|
}
|
|
39758
40146
|
function renderActivityGroup({ items, activityExpanded }) {
|
|
39759
40147
|
if (items.length === 0)
|
|
39760
|
-
return new
|
|
40148
|
+
return new StyledText9([]);
|
|
39761
40149
|
const summary = renderActivitySummary({ items, includeTimestamp: activityExpanded });
|
|
39762
40150
|
const lastInFlightIndex = items.length - 1;
|
|
39763
40151
|
if (!activityExpanded) {
|
|
@@ -39766,9 +40154,9 @@ function renderActivityGroup({ items, activityExpanded }) {
|
|
|
39766
40154
|
item,
|
|
39767
40155
|
inFlight: item.card.kind === "tool" && tailStart + index === lastInFlightIndex
|
|
39768
40156
|
}));
|
|
39769
|
-
const elision = tailStart > 0 ? new
|
|
40157
|
+
const elision = tailStart > 0 ? new StyledText9([dim7(fg10(PALETTE.dimText)(` \u2026 ${tailStart} earlier step${tailStart === 1 ? "" : "s"}`))]) : undefined;
|
|
39770
40158
|
return joinStyled([
|
|
39771
|
-
new
|
|
40159
|
+
new StyledText9([renderTimestampChunk(items[0].occurredAt), dim7(fg10(PALETTE.dimText)("Worked"))]),
|
|
39772
40160
|
...elision ? [elision] : [],
|
|
39773
40161
|
...tail,
|
|
39774
40162
|
summary
|
|
@@ -39787,7 +40175,7 @@ function renderExpandedActivityHierarchy({ items, lastInFlightIndex }) {
|
|
|
39787
40175
|
const attribution = item.card.attribution;
|
|
39788
40176
|
if (attribution?.status === "resolved") {
|
|
39789
40177
|
if (activeOwnerKey !== attribution.ownerKey) {
|
|
39790
|
-
rendered.push(new
|
|
40178
|
+
rendered.push(new StyledText9([
|
|
39791
40179
|
dim7(fg10(PALETTE.dimText)(" ")),
|
|
39792
40180
|
fg10(PALETTE.bodyText)(`Subagent \xB7 ${lineagePathLabel(attribution.path)}`)
|
|
39793
40181
|
]));
|
|
@@ -39817,17 +40205,17 @@ function renderExpandedActivityStep({ item, count, inFlight, nested, eventUrls }
|
|
|
39817
40205
|
const disclosure = renderActivityDisclosure(item.card);
|
|
39818
40206
|
const title = expandedActivityTitle(item.card);
|
|
39819
40207
|
const indent = nested ? " " : " ";
|
|
39820
|
-
const step = new
|
|
40208
|
+
const step = new StyledText9([
|
|
39821
40209
|
fg10(color)(`${indent}${glyph} `),
|
|
39822
40210
|
fg10(PALETTE.bodyText)(title),
|
|
39823
40211
|
...count > 1 ? [dim7(fg10(PALETTE.dimText)(` \xD7${count}`))] : [],
|
|
39824
40212
|
...showSummary ? [dim7(fg10(PALETTE.dimText)(`
|
|
39825
40213
|
${indent} ${item.card.summary}`))] : []
|
|
39826
40214
|
]);
|
|
39827
|
-
const links = eventUrls.map((url3) => new
|
|
39828
|
-
fg10(PALETTE.tool)(
|
|
40215
|
+
const links = eventUrls.map((url3) => new StyledText9([
|
|
40216
|
+
fg10(PALETTE.tool)(link2(url3)(`${indent} View event in Web: ${url3}`))
|
|
39829
40217
|
]));
|
|
39830
|
-
const unavailable = eventUrls.length === 0 && item.card.toolOutput !== undefined ? [new
|
|
40218
|
+
const unavailable = eventUrls.length === 0 && item.card.toolOutput !== undefined ? [new StyledText9([dim7(fg10(PALETTE.dimText)(`${indent} Web event link unavailable from this server.`))])] : [];
|
|
39831
40219
|
return joinStyled([step, ...disclosure ? [disclosure] : [], ...links, ...unavailable], `
|
|
39832
40220
|
`);
|
|
39833
40221
|
}
|
|
@@ -39861,7 +40249,7 @@ ${text}`;
|
|
|
39861
40249
|
const details = [];
|
|
39862
40250
|
if (card.detail !== undefined) {
|
|
39863
40251
|
const safeDetail = terminalSafeText(card.detail);
|
|
39864
|
-
details.push(card.detailFormat === "code" ? renderCodeSnippet({ text: safeDetail, indent: " " }) : new
|
|
40252
|
+
details.push(card.detailFormat === "code" ? renderCodeSnippet({ text: safeDetail, indent: " " }) : new StyledText9([dim7(fg10(PALETTE.dimText)(` ${safeDetail}`))]));
|
|
39865
40253
|
}
|
|
39866
40254
|
const toolOutput = card.toolOutput !== undefined && card.toolCall?.mcp ? renderToolOutputDetail(card.toolOutput) : undefined;
|
|
39867
40255
|
if (toolOutput)
|
|
@@ -39881,14 +40269,14 @@ function renderToolOutputDetail(output) {
|
|
|
39881
40269
|
`);
|
|
39882
40270
|
}
|
|
39883
40271
|
function renderActivitySummary({ items, includeTimestamp }) {
|
|
39884
|
-
return new
|
|
40272
|
+
return new StyledText9([
|
|
39885
40273
|
...includeTimestamp ? [renderTimestampChunk(items[0].occurredAt)] : [],
|
|
39886
40274
|
dim7(fg10(PALETTE.dimText)(`Worked \xB7 ${items.length} step${items.length === 1 ? "" : "s"} \xB7 Ctrl+O details`))
|
|
39887
40275
|
]);
|
|
39888
40276
|
}
|
|
39889
40277
|
function renderCollapsedActivityStep({ item, inFlight }) {
|
|
39890
40278
|
const { glyph, color } = activityGlyph({ kind: item.card.kind, inFlight });
|
|
39891
|
-
return new
|
|
40279
|
+
return new StyledText9([
|
|
39892
40280
|
fg10(color)(` ${glyph} `),
|
|
39893
40281
|
dim7(fg10(PALETTE.dimText)(collapsedActivityTitle(item.card)))
|
|
39894
40282
|
]);
|
|
@@ -39908,7 +40296,7 @@ function renderSubmittedTimeline({ state, admittedSubmissions }) {
|
|
|
39908
40296
|
const submitted = admittedSubmissions.filter((submission) => submission.status === "submitted" && !state.transcript.some((item) => item.kind === "message" && item.messageId === submission.messageId));
|
|
39909
40297
|
const messages = submitted.map((submission) => {
|
|
39910
40298
|
const attachments = renderAttachmentSummary(submission.attachments);
|
|
39911
|
-
const header = new
|
|
40299
|
+
const header = new StyledText9([
|
|
39912
40300
|
renderRoleLabel({ label: "You", role: "human" }),
|
|
39913
40301
|
dim7(fg10(PALETTE.dimText)(" (CLI)"))
|
|
39914
40302
|
]);
|
|
@@ -39925,18 +40313,18 @@ function renderSubmittedTimeline({ state, admittedSubmissions }) {
|
|
|
39925
40313
|
}
|
|
39926
40314
|
function renderWorkingIndicator({ frame, elapsedMs, mode }) {
|
|
39927
40315
|
if (mode === "stopping") {
|
|
39928
|
-
return new
|
|
40316
|
+
return new StyledText9([
|
|
39929
40317
|
fg10(PALETTE.approvalQuestion)("\u23F9"),
|
|
39930
40318
|
dim7(fg10(PALETTE.dimText)(` Stopping\u2026 ${formatElapsed(elapsedMs)}`))
|
|
39931
40319
|
]);
|
|
39932
40320
|
}
|
|
39933
40321
|
if (mode === "complete" || mode === "cancel") {
|
|
39934
|
-
return new
|
|
40322
|
+
return new StyledText9([
|
|
39935
40323
|
fg10(PALETTE.progress)(frame),
|
|
39936
40324
|
dim7(fg10(PALETTE.dimText)(` ${lifecycleOperationPresentParticiple(mode)} the session\u2026`))
|
|
39937
40325
|
]);
|
|
39938
40326
|
}
|
|
39939
|
-
return new
|
|
40327
|
+
return new StyledText9([
|
|
39940
40328
|
fg10(PALETTE.remyAccent)(frame),
|
|
39941
40329
|
dim7(fg10(PALETTE.dimText)(` Working ${formatElapsed(elapsedMs)}`))
|
|
39942
40330
|
]);
|
|
@@ -39955,7 +40343,7 @@ function formatElapsed(elapsedMs) {
|
|
|
39955
40343
|
}
|
|
39956
40344
|
function renderComposerStatus({ admittedSubmissions, width }) {
|
|
39957
40345
|
if (admittedSubmissions.length === 0)
|
|
39958
|
-
return new
|
|
40346
|
+
return new StyledText9([]);
|
|
39959
40347
|
const parts = admittedSubmissions.flatMap((submission) => {
|
|
39960
40348
|
if (submission.status === "submitted")
|
|
39961
40349
|
return [];
|
|
@@ -39989,9 +40377,9 @@ async function createDefaultRenderer3() {
|
|
|
39989
40377
|
// src/tui/attachments.ts
|
|
39990
40378
|
import { Buffer as Buffer2 } from "buffer";
|
|
39991
40379
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
39992
|
-
import { mkdtemp, open as
|
|
40380
|
+
import { mkdtemp as mkdtemp2, open as open5, readdir as readdir2, rm as rm2, stat, writeFile as writeFile3 } from "fs/promises";
|
|
39993
40381
|
import { homedir as homedir2, tmpdir } from "os";
|
|
39994
|
-
import { basename as
|
|
40382
|
+
import { basename as basename4, isAbsolute as isAbsolute2, join as join6, resolve as resolve3 } from "path";
|
|
39995
40383
|
var directoryArchiveMediaType = "application/gzip";
|
|
39996
40384
|
var directoryArchiveSuffix = ".tar.gz";
|
|
39997
40385
|
var maxPortableFilenameBytes = 255;
|
|
@@ -40035,7 +40423,7 @@ async function writePastedImage({
|
|
|
40035
40423
|
const extension = extensionForMediaType(mediaType);
|
|
40036
40424
|
if (!extension)
|
|
40037
40425
|
throw new Error(`Unsupported pasted image MIME type: ${mediaType}`);
|
|
40038
|
-
const filePath =
|
|
40426
|
+
const filePath = join6(temporaryDirectory, `remy-paste-${randomUUID4()}.${extension}`);
|
|
40039
40427
|
await writeFile3(filePath, bytes, { mode: 384 });
|
|
40040
40428
|
return filePath;
|
|
40041
40429
|
}
|
|
@@ -40069,7 +40457,7 @@ async function attachPromptPaths({
|
|
|
40069
40457
|
}
|
|
40070
40458
|
if (pathsToUpload.length === 0)
|
|
40071
40459
|
return [];
|
|
40072
|
-
onUploadStart?.({ filenames: pathsToUpload.map((filePath) =>
|
|
40460
|
+
onUploadStart?.({ filenames: pathsToUpload.map((filePath) => basename4(filePath)) });
|
|
40073
40461
|
const attachments = [];
|
|
40074
40462
|
for (const filePath of pathsToUpload) {
|
|
40075
40463
|
const uploaded = await uploadLocalAttachment({ filePath, reserveAndUploadFile: reserveAndUploadFile2 });
|
|
@@ -40084,14 +40472,14 @@ async function uploadLocalAttachment({
|
|
|
40084
40472
|
filePath,
|
|
40085
40473
|
reserveAndUploadFile: reserveAndUploadFile2
|
|
40086
40474
|
}) {
|
|
40087
|
-
const sourcePath =
|
|
40475
|
+
const sourcePath = resolve3(filePath);
|
|
40088
40476
|
const source = await stat(sourcePath);
|
|
40089
40477
|
if (source.isFile()) {
|
|
40090
40478
|
if (source.size > fileUploadMaxBytes)
|
|
40091
40479
|
throw oversizedFileAttachmentError(sourcePath);
|
|
40092
40480
|
return {
|
|
40093
40481
|
fileId: await reserveAndUploadFile2({ filePath: sourcePath }),
|
|
40094
|
-
filename:
|
|
40482
|
+
filename: basename4(sourcePath),
|
|
40095
40483
|
sourcePath
|
|
40096
40484
|
};
|
|
40097
40485
|
}
|
|
@@ -40105,11 +40493,11 @@ async function uploadLocalAttachment({
|
|
|
40105
40493
|
sourcePath
|
|
40106
40494
|
};
|
|
40107
40495
|
} finally {
|
|
40108
|
-
await
|
|
40496
|
+
await rm2(archive.temporaryDirectory, { recursive: true, force: true });
|
|
40109
40497
|
}
|
|
40110
40498
|
}
|
|
40111
40499
|
async function createDirectoryArchive(directoryPath) {
|
|
40112
|
-
const rootName =
|
|
40500
|
+
const rootName = basename4(directoryPath) || "directory";
|
|
40113
40501
|
const files = Object.create(null);
|
|
40114
40502
|
await collectDirectoryFiles({
|
|
40115
40503
|
directoryPath,
|
|
@@ -40120,15 +40508,15 @@ async function createDirectoryArchive(directoryPath) {
|
|
|
40120
40508
|
});
|
|
40121
40509
|
if (Object.keys(files).length === 0)
|
|
40122
40510
|
throw new Error(`Directory attachment contains no regular files: ${directoryPath}`);
|
|
40123
|
-
const temporaryDirectory = await
|
|
40511
|
+
const temporaryDirectory = await mkdtemp2(join6(tmpdir(), "remy-directory-attachment-"));
|
|
40124
40512
|
const filename = directoryArchiveFilename(rootName);
|
|
40125
|
-
const archivePath =
|
|
40513
|
+
const archivePath = join6(temporaryDirectory, filename);
|
|
40126
40514
|
try {
|
|
40127
40515
|
await Bun.Archive.write(archivePath, files, { compress: "gzip" });
|
|
40128
40516
|
if ((await stat(archivePath)).size > fileUploadMaxBytes)
|
|
40129
40517
|
throw oversizedDirectoryArchiveError(directoryPath);
|
|
40130
40518
|
} catch (error93) {
|
|
40131
|
-
await
|
|
40519
|
+
await rm2(temporaryDirectory, { recursive: true, force: true });
|
|
40132
40520
|
throw error93;
|
|
40133
40521
|
}
|
|
40134
40522
|
return { path: archivePath, filename, temporaryDirectory };
|
|
@@ -40144,7 +40532,7 @@ async function collectDirectoryFiles({
|
|
|
40144
40532
|
entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
40145
40533
|
let byteSize = collectedByteSize;
|
|
40146
40534
|
for (const entry of entries) {
|
|
40147
|
-
const entryPath =
|
|
40535
|
+
const entryPath = join6(directoryPath, entry.name);
|
|
40148
40536
|
const entryArchivePath = `${archivePath}/${entry.name}`;
|
|
40149
40537
|
if (entry.isDirectory()) {
|
|
40150
40538
|
byteSize = await collectDirectoryFiles({
|
|
@@ -40157,7 +40545,7 @@ async function collectDirectoryFiles({
|
|
|
40157
40545
|
continue;
|
|
40158
40546
|
}
|
|
40159
40547
|
if (entry.isFile()) {
|
|
40160
|
-
const fileHandle = await
|
|
40548
|
+
const fileHandle = await open5(entryPath, "r");
|
|
40161
40549
|
try {
|
|
40162
40550
|
const entryStat = await fileHandle.stat();
|
|
40163
40551
|
if (!entryStat.isFile())
|
|
@@ -40213,7 +40601,7 @@ function truncateUtf8(value, maxBytes) {
|
|
|
40213
40601
|
function promptFilePaths({ text, cwd, selectedPaths }) {
|
|
40214
40602
|
const paths = new Map;
|
|
40215
40603
|
for (const selectedPath of selectedPaths)
|
|
40216
|
-
paths.set(isAbsolute2(selectedPath) ? selectedPath :
|
|
40604
|
+
paths.set(isAbsolute2(selectedPath) ? selectedPath : resolve3(cwd, selectedPath), true);
|
|
40217
40605
|
const tokens = text.match(/[([{]*@?(?:"[^"]*"|'[^']*'|`[^`]*`)|(?:\\.|[^\s\\])+/g) ?? [];
|
|
40218
40606
|
for (const quotedToken of tokens) {
|
|
40219
40607
|
const wrappedToken = quotedToken.replace(/^[([{]+/, "").replace(/[),.;:!?\]}]+$/, "");
|
|
@@ -40226,8 +40614,8 @@ function promptFilePaths({ text, cwd, selectedPaths }) {
|
|
|
40226
40614
|
const pathShaped = explicit || pathText.includes("/") || pathText.includes("\\") || pathText.includes(".");
|
|
40227
40615
|
if (!pathShaped)
|
|
40228
40616
|
continue;
|
|
40229
|
-
const expandedPath = pathText.startsWith("~/") ?
|
|
40230
|
-
const filePath = isAbsolute2(expandedPath) ? expandedPath :
|
|
40617
|
+
const expandedPath = pathText.startsWith("~/") ? join6(homedir2(), pathText.slice(2)) : pathText;
|
|
40618
|
+
const filePath = isAbsolute2(expandedPath) ? expandedPath : resolve3(cwd, expandedPath);
|
|
40231
40619
|
paths.set(filePath, paths.get(filePath) || explicit);
|
|
40232
40620
|
}
|
|
40233
40621
|
return [...paths].map(([filePath, explicit]) => ({ filePath, explicit }));
|
|
@@ -40248,7 +40636,7 @@ function isPng(bytes) {
|
|
|
40248
40636
|
}
|
|
40249
40637
|
|
|
40250
40638
|
// src/tui/remy-splash.ts
|
|
40251
|
-
import { bold as bold6, BoxRenderable as BoxRenderable4, fg as fg11, NativeImage, StyledText as
|
|
40639
|
+
import { bold as bold6, BoxRenderable as BoxRenderable4, fg as fg11, NativeImage, StyledText as StyledText10, TextRenderable as TextRenderable5, bg as bg4 } from "@opentui/core";
|
|
40252
40640
|
// src/tui/remy-mark.ts
|
|
40253
40641
|
var remyPixelFieldSource = new URL("../assets/remy-pixel-field.png", import.meta.url);
|
|
40254
40642
|
function shouldShowRemySplash({ width, height }) {
|
|
@@ -40266,7 +40654,7 @@ var compactMarkRows = 9;
|
|
|
40266
40654
|
var compactMinWidth = 48;
|
|
40267
40655
|
var compactMinHeight = 20;
|
|
40268
40656
|
var markBrightnessGain = 4.2;
|
|
40269
|
-
var remyCliVersion = "1.19.0
|
|
40657
|
+
var remyCliVersion = "1.19.0";
|
|
40270
40658
|
async function showRemySplash({
|
|
40271
40659
|
createRenderer = createRemyRenderer,
|
|
40272
40660
|
durationMs = splashDurationMs,
|
|
@@ -40324,7 +40712,7 @@ async function showRemySplash({
|
|
|
40324
40712
|
});
|
|
40325
40713
|
const approvalCopy = new TextRenderable5(renderer, { content: "" });
|
|
40326
40714
|
const metadata = new TextRenderable5(renderer, {
|
|
40327
|
-
content: new
|
|
40715
|
+
content: new StyledText10([fg11(PALETTE.dimText)(`v${remyCliVersion} \xB7 Tela\xAE`)])
|
|
40328
40716
|
});
|
|
40329
40717
|
const field = await loadPixelFieldUntilAbort({ signal });
|
|
40330
40718
|
if (!field || signal?.aborted)
|
|
@@ -40363,7 +40751,7 @@ async function showRemySplash({
|
|
|
40363
40751
|
if (signal)
|
|
40364
40752
|
await waitForSplashAbort(signal);
|
|
40365
40753
|
else
|
|
40366
|
-
await new Promise((
|
|
40754
|
+
await new Promise((resolve4) => setTimeout(resolve4, durationMs));
|
|
40367
40755
|
} finally {
|
|
40368
40756
|
if (shimmerTimer)
|
|
40369
40757
|
clearInterval(shimmerTimer);
|
|
@@ -40422,7 +40810,7 @@ function createSplashController({
|
|
|
40422
40810
|
};
|
|
40423
40811
|
}
|
|
40424
40812
|
function renderBootstrapActions({ state, frame }) {
|
|
40425
|
-
return new
|
|
40813
|
+
return new StyledText10([
|
|
40426
40814
|
actionText({ label: state.authenticatedEmail ? `Authenticated as ${state.authenticatedEmail}` : "Checking authentication...", status: state.authentication, frame }),
|
|
40427
40815
|
fg11(PALETTE.dimText)(`
|
|
40428
40816
|
`),
|
|
@@ -40440,11 +40828,11 @@ function actionText({ label, status, frame }) {
|
|
|
40440
40828
|
}
|
|
40441
40829
|
function renderAuthenticationBand({ state, frame }) {
|
|
40442
40830
|
if (!state.waitingForAuthentication)
|
|
40443
|
-
return new
|
|
40831
|
+
return new StyledText10([]);
|
|
40444
40832
|
const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
40445
40833
|
const url3 = state.verificationUrl ? [fg11(PALETTE.dimText)(`
|
|
40446
40834
|
Verification URL: `), bold6(fg11(PALETTE.humanAccent)(state.verificationUrl))] : [];
|
|
40447
|
-
return new
|
|
40835
|
+
return new StyledText10([
|
|
40448
40836
|
fg11(PALETTE.bodyText)("Continue authentication in your browser."),
|
|
40449
40837
|
...url3,
|
|
40450
40838
|
fg11(PALETTE.dimText)(`
|
|
@@ -40454,7 +40842,7 @@ ${spinner} Waiting for approval \xB7 q / esc cancel`)
|
|
|
40454
40842
|
async function waitForSplashAbort(signal) {
|
|
40455
40843
|
if (signal.aborted)
|
|
40456
40844
|
return;
|
|
40457
|
-
await new Promise((
|
|
40845
|
+
await new Promise((resolve4) => signal.addEventListener("abort", () => resolve4(), { once: true }));
|
|
40458
40846
|
}
|
|
40459
40847
|
async function loadPixelFieldUntilAbort({ signal }) {
|
|
40460
40848
|
const field = loadPixelField();
|
|
@@ -40469,8 +40857,8 @@ async function loadPixelFieldUntilAbort({ signal }) {
|
|
|
40469
40857
|
let resolveAbort = () => {
|
|
40470
40858
|
return;
|
|
40471
40859
|
};
|
|
40472
|
-
const aborted3 = new Promise((
|
|
40473
|
-
resolveAbort =
|
|
40860
|
+
const aborted3 = new Promise((resolve4) => {
|
|
40861
|
+
resolveAbort = resolve4;
|
|
40474
40862
|
});
|
|
40475
40863
|
const abort = () => {
|
|
40476
40864
|
field.catch(() => {
|
|
@@ -40542,7 +40930,7 @@ function renderMark(field, frame, rows = markRows) {
|
|
|
40542
40930
|
chunks.push(fg11("#000000")(`
|
|
40543
40931
|
`));
|
|
40544
40932
|
}
|
|
40545
|
-
return new
|
|
40933
|
+
return new StyledText10(chunks);
|
|
40546
40934
|
}
|
|
40547
40935
|
function shimmerBrightness(field, column, sampleRow, columns, sampleRows, center) {
|
|
40548
40936
|
const x0 = Math.floor(column / columns * field.width);
|
|
@@ -40562,7 +40950,7 @@ function grey(value) {
|
|
|
40562
40950
|
return `#${hex5}${hex5}${hex5}`;
|
|
40563
40951
|
}
|
|
40564
40952
|
function renderSplashTitle() {
|
|
40565
|
-
return new
|
|
40953
|
+
return new StyledText10([
|
|
40566
40954
|
bold6(fg11(PALETTE.bodyText)("Remy CLI"))
|
|
40567
40955
|
]);
|
|
40568
40956
|
}
|
|
@@ -40781,7 +41169,7 @@ ${approvalUrl}
|
|
|
40781
41169
|
tokenPath: paths.tokenPath,
|
|
40782
41170
|
candidatePath: candidateTokenPath,
|
|
40783
41171
|
backupPath: previousTokenPath,
|
|
40784
|
-
quarantinePath:
|
|
41172
|
+
quarantinePath: join7(dirname6(paths.tokenPath), `.tokens.quarantine.${process.pid}.${randomUUID5()}.json`)
|
|
40785
41173
|
};
|
|
40786
41174
|
await writeCredentialPromotionJournal({ stateDirectory: dirname6(paths.tokenPath), journal, ...dependencies.credentialStateFilesystem });
|
|
40787
41175
|
journalPrepared = true;
|
|
@@ -41263,8 +41651,8 @@ function createSplashPresentation({
|
|
|
41263
41651
|
let rejectReady = () => {
|
|
41264
41652
|
return;
|
|
41265
41653
|
};
|
|
41266
|
-
ready = new Promise((
|
|
41267
|
-
resolveReady =
|
|
41654
|
+
ready = new Promise((resolve4, reject) => {
|
|
41655
|
+
resolveReady = resolve4;
|
|
41268
41656
|
rejectReady = reject;
|
|
41269
41657
|
});
|
|
41270
41658
|
splash = (dependencies.showRemySplash ?? showRemySplash)({
|
|
@@ -41372,7 +41760,7 @@ async function openBareDashboard({
|
|
|
41372
41760
|
return await dashboard({ dependencies, bootstrap });
|
|
41373
41761
|
}
|
|
41374
41762
|
async function delayMilliseconds(milliseconds) {
|
|
41375
|
-
await new Promise((
|
|
41763
|
+
await new Promise((resolve4) => setTimeout(resolve4, milliseconds));
|
|
41376
41764
|
}
|
|
41377
41765
|
async function completeDashboardSplash({
|
|
41378
41766
|
dependencies,
|
|
@@ -41668,6 +42056,15 @@ async function runAttachedSession({
|
|
|
41668
42056
|
const action = await renderRemyView({
|
|
41669
42057
|
open: async () => await openSessionTui({
|
|
41670
42058
|
controller,
|
|
42059
|
+
openBrowser: dependencies.openBrowser,
|
|
42060
|
+
downloadFile: async ({ file: file3, signal }) => await downloadSessionFile({
|
|
42061
|
+
client: operations.client,
|
|
42062
|
+
sessionId,
|
|
42063
|
+
file: file3,
|
|
42064
|
+
directory: join7(dependencies.environment.HOME ?? homedir3(), "Downloads"),
|
|
42065
|
+
signal,
|
|
42066
|
+
fetchClient: globalThis.fetch
|
|
42067
|
+
}),
|
|
41671
42068
|
initialComposer: composerSnapshot,
|
|
41672
42069
|
repositoryLabel: repositories.map((repository) => repository.fullName).join(", ") || undefined,
|
|
41673
42070
|
submitMessage: async (submission) => {
|
|
@@ -41889,10 +42286,10 @@ async function collectAllUsers({
|
|
|
41889
42286
|
}
|
|
41890
42287
|
}
|
|
41891
42288
|
function resolveSessionCachePathForCommand({ dependencies, sessionId }) {
|
|
41892
|
-
const stateBase = dependencies.environment.XDG_STATE_HOME ?? (dependencies.environment.HOME ?
|
|
42289
|
+
const stateBase = dependencies.environment.XDG_STATE_HOME ?? (dependencies.environment.HOME ? join7(dependencies.environment.HOME, ".local/state") : undefined);
|
|
41893
42290
|
if (!stateBase)
|
|
41894
42291
|
throw new Error("HOME is required when XDG_STATE_HOME is not set; refusing to read Remy session cache.");
|
|
41895
|
-
return
|
|
42292
|
+
return join7(stateBase, "remy", "sessions", `${sessionId}.json`);
|
|
41896
42293
|
}
|
|
41897
42294
|
function isInteractiveTerminal(dependencies) {
|
|
41898
42295
|
return dependencies.isTerminal?.() ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
@@ -42165,8 +42562,8 @@ async function readOptionalCliConfiguration(path) {
|
|
|
42165
42562
|
}
|
|
42166
42563
|
async function createCandidateTokenPath(activeTokenPath) {
|
|
42167
42564
|
const directory = dirname6(activeTokenPath);
|
|
42168
|
-
await
|
|
42169
|
-
return
|
|
42565
|
+
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
42566
|
+
return join7(directory, `.tokens.${process.pid}.${randomUUID5()}.tmp`);
|
|
42170
42567
|
}
|
|
42171
42568
|
async function removeCandidateToken({
|
|
42172
42569
|
path,
|
|
@@ -42195,7 +42592,7 @@ async function removeCommittedTokenRecoveryArtifacts({
|
|
|
42195
42592
|
for (const entry of entries) {
|
|
42196
42593
|
if (!isCommittedTokenRecoveryArtifact(entry))
|
|
42197
42594
|
continue;
|
|
42198
|
-
await removeCandidateToken({ path:
|
|
42595
|
+
await removeCandidateToken({ path: join7(directory, entry), removeTokenFile });
|
|
42199
42596
|
}
|
|
42200
42597
|
}
|
|
42201
42598
|
function isCommittedTokenRecoveryArtifact(entry) {
|
|
@@ -42297,10 +42694,10 @@ async function runBrowserLauncher(command) {
|
|
|
42297
42694
|
const [executable, ...arguments_] = command;
|
|
42298
42695
|
if (!executable)
|
|
42299
42696
|
throw new Error("Browser launcher command is empty.");
|
|
42300
|
-
return await new Promise((
|
|
42697
|
+
return await new Promise((resolve4, reject) => {
|
|
42301
42698
|
const child = spawn(executable, arguments_, { stdio: "ignore" });
|
|
42302
42699
|
child.once("error", reject);
|
|
42303
|
-
child.once("exit", (code) =>
|
|
42700
|
+
child.once("exit", (code) => resolve4(code ?? 1));
|
|
42304
42701
|
});
|
|
42305
42702
|
}
|
|
42306
42703
|
|