@meistrari/remy-cli 1.16.0 → 1.17.1
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 +13 -2
- package/dist/remy.js +110 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ The dashboard is the starting point for all interactive work. It refreshes the v
|
|
|
38
38
|
|
|
39
39
|
Press `n` in the dashboard. The new-session flow asks what you want done, selects a GitHub installation when needed, suggests repositories based on your request, and lets you review those suggestions. Remy analyzes the request for existing work: a Remy session ID or number, a PR URL or number (`PR 267`, `#267`, or `owner/repo#267`), or an exact or approximate branch name. It verifies matches against the selected repositories and offers up to five branches per repository. In the branch step, use arrows to move, Space to select, and Enter to select the current option and review your choices. Each repository can continue one branch or start a new session branch. A lookup failure stays on a retry screen; press `r` to retry, `n` to explicitly choose new branches, or Esc to edit the request. Before creating the session, choose the agent model and reasoning effort. The wizard starts with `gpt-6-astra` and `medium` reasoning by default. Remy then opens the session and streams its activity.
|
|
40
40
|
|
|
41
|
-
Use `Tab` while writing the request to complete a local file or directory path. Remy attaches selected paths when you confirm the request. Each file may be at most 20 MiB. A directory may contain at most 20 MiB across its regular files before compression; Remy uploads it as a temporary `<directory>.tar.gz` archive with the selected directory preserved as its root, so the remote agent can extract and use its files. Remy rejects oversized attachments with guidance to choose a smaller file or directory, and rejects directory attachments that contain symbolic links or other non-regular entries. On macOS, `Ctrl+V`
|
|
41
|
+
Use `Tab` while writing the request to complete a local file or directory path. Remy attaches selected paths when you confirm the request. You can also paste an absolute path directly; quote paths containing spaces (for example, `"/Users/me/Desktop/Screen Shot.png"`) or use shell-style backslash escapes. Autocomplete selection is not required. Each file may be at most 20 MiB. A directory may contain at most 20 MiB across its regular files before compression; Remy uploads it as a temporary `<directory>.tar.gz` archive with the selected directory preserved as its root, so the remote agent can extract and use its files. Remy rejects oversized attachments with guidance to choose a smaller file or directory, and rejects directory attachments that contain symbolic links or other non-regular entries. On macOS, `Ctrl+V` saves a clipboard image as a temporary PNG and adds its path to the request; Remy attaches it on confirmation. Remy uploads attachments before creating the session and shows their filenames beneath your message.
|
|
42
42
|
|
|
43
43
|
### Resume or follow up
|
|
44
44
|
|
|
@@ -102,6 +102,14 @@ The branch-suggestion flow belongs to the interactive new-session wizard (`remy`
|
|
|
102
102
|
remy --session <session-id>
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
To submit a follow-up immediately, add one quoted positional `prompt`:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
remy --session <session-id> "Add regression coverage for that fix"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Remy sends the prompt as **Steer**, then opens the conversation. Omitting the prompt only attaches. An explicitly empty prompt is rejected. Use `--` before a prompt beginning with `--` to treat it as text.
|
|
112
|
+
|
|
105
113
|
### Automate a session
|
|
106
114
|
|
|
107
115
|
`remy new` and `remy --session` use JSON Lines output automatically when standard input or output is not a terminal. Pass `--no-tui` to choose that mode explicitly; `--json` also selects it.
|
|
@@ -111,8 +119,11 @@ Opaque session and message metadata returned by the API is accepted for compatib
|
|
|
111
119
|
```bash
|
|
112
120
|
remy new --no-tui --repository owner/repository "Add a health check endpoint"
|
|
113
121
|
remy --session <session-id> --json
|
|
122
|
+
remy --session <session-id> --no-tui "Add regression coverage for that fix"
|
|
114
123
|
```
|
|
115
124
|
|
|
125
|
+
With a follow-up prompt, Remy submits it once and streams JSON Lines until that message reaches a terminal outcome, even if a previous message is cached locally. It exits with status `0` for a completed turn or `1` for another terminal outcome or an API failure. A follow-up does not emit a `created` record because the session already exists. If the local session cache becomes unavailable after admission, Remy warns once and continues observing the accepted follow-up in memory. Do not resubmit the prompt because of that warning; local resume metadata may remain stale. These commands require prior sign-in and can be used by scripts or other agents.
|
|
126
|
+
|
|
116
127
|
For a newly created session, Remy writes a `created` record, event-name records as they arrive, then a `terminal` record when the submitted turn settles. A completed turn exits with status `0`; another terminal outcome exits with status `1`.
|
|
117
128
|
|
|
118
129
|
```json
|
|
@@ -154,7 +165,7 @@ remy logout [--api-url <url>]
|
|
|
154
165
|
remy whoami
|
|
155
166
|
remy dashboard
|
|
156
167
|
remy new [--repository <owner/name> ... --installation <id> --model <name> --reasoning-effort <low|medium|high|xhigh> --attach <path> ... --no-tui --json] <prompt>
|
|
157
|
-
remy --session <session-id> [--no-tui] [--json]
|
|
168
|
+
remy --session <session-id> [--no-tui] [--json] [prompt]
|
|
158
169
|
```
|
|
159
170
|
|
|
160
171
|
Run `remy <command> --help` for flags and command-specific usage. `remy whoami` prints the saved signed-in identity and organization.
|
package/dist/remy.js
CHANGED
|
@@ -35315,6 +35315,7 @@ function createRemoteSessionController(dependencies) {
|
|
|
35315
35315
|
let stopped = false;
|
|
35316
35316
|
let state;
|
|
35317
35317
|
let cachedLastRetainedEventId;
|
|
35318
|
+
let cacheUnavailable = false;
|
|
35318
35319
|
let reasoningPreviewState = initialReasoningPreviewState();
|
|
35319
35320
|
let reasoningConnectionGeneration = 0;
|
|
35320
35321
|
let reasoningHistorySequenceFloor = -1;
|
|
@@ -35323,7 +35324,10 @@ function createRemoteSessionController(dependencies) {
|
|
|
35323
35324
|
ready = new Promise((resolve) => {
|
|
35324
35325
|
resolveReady = resolve;
|
|
35325
35326
|
});
|
|
35326
|
-
const cache = await readSessionCache(cachePath)
|
|
35327
|
+
const cache = await readSessionCache(cachePath).catch((error93) => {
|
|
35328
|
+
handleCacheError(error93);
|
|
35329
|
+
return null;
|
|
35330
|
+
});
|
|
35327
35331
|
cachedLastRetainedEventId = input.mode === "live" ? input.lastRetainedEventId ?? cache?.lastRetainedEventId : undefined;
|
|
35328
35332
|
state = createSessionViewState({
|
|
35329
35333
|
detail: input.detail,
|
|
@@ -35550,6 +35554,8 @@ function createRemoteSessionController(dependencies) {
|
|
|
35550
35554
|
};
|
|
35551
35555
|
}
|
|
35552
35556
|
async function writeCache() {
|
|
35557
|
+
if (cacheUnavailable)
|
|
35558
|
+
return;
|
|
35553
35559
|
const currentState = getState();
|
|
35554
35560
|
const cache = {
|
|
35555
35561
|
version: 1,
|
|
@@ -35559,7 +35565,13 @@ function createRemoteSessionController(dependencies) {
|
|
|
35559
35565
|
...currentState.activeMessageId ? { activeMessageId: currentState.activeMessageId } : {},
|
|
35560
35566
|
updatedAt: new Date().toISOString()
|
|
35561
35567
|
};
|
|
35562
|
-
await writeSessionCache({ path: cachePath, cache });
|
|
35568
|
+
await writeSessionCache({ path: cachePath, cache }).catch(handleCacheError);
|
|
35569
|
+
}
|
|
35570
|
+
function handleCacheError(error93) {
|
|
35571
|
+
if (!dependencies.onCacheError)
|
|
35572
|
+
throw error93;
|
|
35573
|
+
cacheUnavailable = true;
|
|
35574
|
+
dependencies.onCacheError(error93);
|
|
35563
35575
|
}
|
|
35564
35576
|
function publishState(frame) {
|
|
35565
35577
|
const update = { state: getState(), ...frame ? { frame } : {} };
|
|
@@ -39350,13 +39362,35 @@ var directoryArchiveMediaType = "application/gzip";
|
|
|
39350
39362
|
var directoryArchiveSuffix = ".tar.gz";
|
|
39351
39363
|
var maxPortableFilenameBytes = 255;
|
|
39352
39364
|
var attachmentSizeLimitLabel = `${fileUploadMaxBytes / (1024 * 1024)} MiB`;
|
|
39365
|
+
var clipboardImageScript = `
|
|
39366
|
+
ObjC.import('AppKit');
|
|
39367
|
+
const pasteboard = $.NSPasteboard.generalPasteboard;
|
|
39368
|
+
let png = pasteboard.dataForType($.NSPasteboardTypePNG);
|
|
39369
|
+
if (!png || png.isNil()) {
|
|
39370
|
+
const tiff = pasteboard.dataForType($.NSPasteboardTypeTIFF);
|
|
39371
|
+
if (!tiff || tiff.isNil()) throw new Error('Clipboard does not contain an image.');
|
|
39372
|
+
const bitmap = $.NSBitmapImageRep.imageRepWithData(tiff);
|
|
39373
|
+
if (!bitmap || bitmap.isNil()) throw new Error('Clipboard image could not be decoded.');
|
|
39374
|
+
png = bitmap.representationUsingTypeProperties($.NSPNGFileType, $.NSDictionary.dictionary);
|
|
39375
|
+
}
|
|
39376
|
+
if (!png || png.isNil()) throw new Error('Clipboard image could not be converted to PNG.');
|
|
39377
|
+
if (png.length > ${fileUploadMaxBytes}) throw new Error('Clipboard image is larger than ${attachmentSizeLimitLabel}.');
|
|
39378
|
+
ObjC.unwrap(png.base64EncodedStringWithOptions(0));
|
|
39379
|
+
`;
|
|
39353
39380
|
async function readClipboardImage() {
|
|
39354
39381
|
if (process.platform !== "darwin")
|
|
39355
39382
|
throw new Error("Clipboard image paste is supported on macOS only.");
|
|
39356
|
-
const clipboard = Bun.spawn(["
|
|
39357
|
-
const
|
|
39358
|
-
|
|
39359
|
-
|
|
39383
|
+
const clipboard = Bun.spawn(["/usr/bin/osascript", "-l", "JavaScript", "-e", clipboardImageScript], { stdout: "pipe", stderr: "pipe" });
|
|
39384
|
+
const [encoded, error93, exitCode] = await Promise.all([
|
|
39385
|
+
new Response(clipboard.stdout).text(),
|
|
39386
|
+
new Response(clipboard.stderr).text(),
|
|
39387
|
+
clipboard.exited
|
|
39388
|
+
]);
|
|
39389
|
+
if (exitCode !== 0)
|
|
39390
|
+
throw new Error(`Could not read clipboard image: ${error93.trim() || "macOS clipboard reader failed."}`);
|
|
39391
|
+
const bytes = new Uint8Array(Buffer2.from(encoded.trim(), "base64"));
|
|
39392
|
+
if (!isPng(bytes))
|
|
39393
|
+
throw new Error("Clipboard image could not be decoded as PNG.");
|
|
39360
39394
|
return { bytes, mediaType: "image/png" };
|
|
39361
39395
|
}
|
|
39362
39396
|
async function writePastedImage({
|
|
@@ -39546,8 +39580,11 @@ function promptFilePaths({ text, cwd, selectedPaths }) {
|
|
|
39546
39580
|
const paths = new Map;
|
|
39547
39581
|
for (const selectedPath of selectedPaths)
|
|
39548
39582
|
paths.set(isAbsolute2(selectedPath) ? selectedPath : resolve2(cwd, selectedPath), true);
|
|
39549
|
-
|
|
39550
|
-
|
|
39583
|
+
const tokens = text.match(/[([{]*@?(?:"[^"]*"|'[^']*'|`[^`]*`)|(?:\\.|[^\s\\])+/g) ?? [];
|
|
39584
|
+
for (const quotedToken of tokens) {
|
|
39585
|
+
const wrappedToken = quotedToken.replace(/^[([{]+/, "").replace(/[),.;:!?\]}]+$/, "");
|
|
39586
|
+
const quotedPath = /^(@?)(["'`])([\s\S]*)\2$/.exec(wrappedToken);
|
|
39587
|
+
const token = quotedPath ? `${quotedPath[1]}${quotedPath[3]}` : wrappedToken.replace(/\\(.)/g, "$1");
|
|
39551
39588
|
const pathText = token.startsWith("@") ? token.slice(1) : token;
|
|
39552
39589
|
if (!pathText)
|
|
39553
39590
|
continue;
|
|
@@ -39596,7 +39633,7 @@ var compactMarkRows = 9;
|
|
|
39596
39633
|
var compactMinWidth = 48;
|
|
39597
39634
|
var compactMinHeight = 20;
|
|
39598
39635
|
var markBrightnessGain = 4.2;
|
|
39599
|
-
var remyCliVersion = "1.
|
|
39636
|
+
var remyCliVersion = "1.17.1";
|
|
39600
39637
|
async function showRemySplash({
|
|
39601
39638
|
createRenderer = createRemyRenderer,
|
|
39602
39639
|
durationMs = splashDurationMs,
|
|
@@ -39969,7 +40006,7 @@ async function dispatchCliCommandWithShutdown({
|
|
|
39969
40006
|
});
|
|
39970
40007
|
}
|
|
39971
40008
|
if (command.name === "session")
|
|
39972
|
-
return await attachSession({ dependencies, sessionId: command.sessionId, noTui: command.noTui, json: command.json });
|
|
40009
|
+
return await attachSession({ dependencies, sessionId: command.sessionId, noTui: command.noTui, json: command.json, prompt: command.prompt });
|
|
39973
40010
|
return await createNewSession({ dependencies, command });
|
|
39974
40011
|
}
|
|
39975
40012
|
function createCliShutdown({ abortSignal }) {
|
|
@@ -40876,12 +40913,30 @@ async function attachSession({
|
|
|
40876
40913
|
dependencies,
|
|
40877
40914
|
sessionId,
|
|
40878
40915
|
noTui,
|
|
40879
|
-
json: json3
|
|
40916
|
+
json: json3,
|
|
40917
|
+
prompt
|
|
40880
40918
|
}) {
|
|
40881
40919
|
const operations = await createSessionOperations(dependencies);
|
|
40882
40920
|
const detail = await operations.getSession({ client: operations.client, sessionId });
|
|
40883
40921
|
const repositories = detail.repositories.map((repository) => ({ id: repository.id, fullName: repository.full_name }));
|
|
40884
40922
|
const cache = await (dependencies.readSessionCache ?? readSessionCache)(resolveSessionCachePathForCommand({ dependencies, sessionId }));
|
|
40923
|
+
let activeMessageId = cache?.activeMessageId;
|
|
40924
|
+
let cacheWarningReported = false;
|
|
40925
|
+
const onCacheError = prompt === undefined ? undefined : () => {
|
|
40926
|
+
if (cacheWarningReported)
|
|
40927
|
+
return;
|
|
40928
|
+
cacheWarningReported = true;
|
|
40929
|
+
dependencies.output.writeStderr(`Local session cache is unavailable; continuing to observe the accepted follow-up. Do not resubmit the prompt.
|
|
40930
|
+
`);
|
|
40931
|
+
};
|
|
40932
|
+
if (prompt !== undefined) {
|
|
40933
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
40934
|
+
const appended = await operations.appendSessionMessage({
|
|
40935
|
+
client: operations.client,
|
|
40936
|
+
input: { sessionId, text: prompt, fileIds: [], mode: "steer", idempotencyKey: randomUUID5() }
|
|
40937
|
+
});
|
|
40938
|
+
activeMessageId = appended.message.id;
|
|
40939
|
+
}
|
|
40885
40940
|
await (dependencies.writeSessionCache ?? writeSessionCache)({
|
|
40886
40941
|
path: resolveSessionCachePathForCommand({ dependencies, sessionId }),
|
|
40887
40942
|
cache: {
|
|
@@ -40889,20 +40944,25 @@ async function attachSession({
|
|
|
40889
40944
|
sessionId,
|
|
40890
40945
|
repositories,
|
|
40891
40946
|
...cache?.lastRetainedEventId ? { lastRetainedEventId: cache.lastRetainedEventId } : {},
|
|
40892
|
-
...
|
|
40947
|
+
...activeMessageId ? { activeMessageId } : {},
|
|
40893
40948
|
updatedAt: new Date().toISOString()
|
|
40894
40949
|
}
|
|
40950
|
+
}).catch((error93) => {
|
|
40951
|
+
if (!onCacheError)
|
|
40952
|
+
throw error93;
|
|
40953
|
+
onCacheError();
|
|
40895
40954
|
});
|
|
40896
40955
|
return await runAttachedSession({
|
|
40897
40956
|
dependencies,
|
|
40898
40957
|
operations,
|
|
40899
40958
|
sessionId,
|
|
40900
40959
|
repositories,
|
|
40901
|
-
activeMessageId
|
|
40960
|
+
activeMessageId,
|
|
40902
40961
|
start: { mode: "cold-resume", detail },
|
|
40903
40962
|
noTui,
|
|
40904
40963
|
json: json3,
|
|
40905
|
-
emitCreated: false
|
|
40964
|
+
emitCreated: false,
|
|
40965
|
+
onCacheError
|
|
40906
40966
|
});
|
|
40907
40967
|
}
|
|
40908
40968
|
async function runAttachedSession({
|
|
@@ -40914,7 +40974,8 @@ async function runAttachedSession({
|
|
|
40914
40974
|
start,
|
|
40915
40975
|
noTui,
|
|
40916
40976
|
json: json3,
|
|
40917
|
-
emitCreated
|
|
40977
|
+
emitCreated,
|
|
40978
|
+
onCacheError
|
|
40918
40979
|
}) {
|
|
40919
40980
|
if (dependencies.abortSignal?.aborted)
|
|
40920
40981
|
throw dependencies.abortSignal.reason ?? new Error("interrupted");
|
|
@@ -40924,6 +40985,7 @@ async function runAttachedSession({
|
|
|
40924
40985
|
repositories,
|
|
40925
40986
|
...activeMessageId ? { activeMessageId } : {},
|
|
40926
40987
|
environment: dependencies.environment,
|
|
40988
|
+
onCacheError,
|
|
40927
40989
|
getSession: async ({ sessionId: id }) => await operations.getSession({ client: operations.client, sessionId: id }),
|
|
40928
40990
|
listSessionEvents: async ({ sessionId: id, limit, after, signal }) => await operations.listSessionEvents({ client: operations.client, sessionId: id, limit, after, signal }),
|
|
40929
40991
|
openEventStream: ({ sessionId: id, lastRetainedEventId, signal, onSynchronized }) => operations.streamSessionEvents({ client: operations.client, sessionId: id, lastRetainedEventId, signal, onSynchronized })
|
|
@@ -41306,10 +41368,15 @@ Options:
|
|
|
41306
41368
|
`;
|
|
41307
41369
|
}
|
|
41308
41370
|
if (topic === "session") {
|
|
41309
|
-
return `Usage: remy --session <session-id> [options]
|
|
41371
|
+
return `Usage: remy --session <session-id> [options] [prompt]
|
|
41310
41372
|
|
|
41311
41373
|
Attach to an existing remote session. An interactive terminal opens the session view; --no-tui streams output instead.
|
|
41312
41374
|
|
|
41375
|
+
Arguments:
|
|
41376
|
+
prompt Submit a follow-up as Steer before attaching
|
|
41377
|
+
|
|
41378
|
+
With a prompt, JSON output waits for that message's turn result. Use -- before a prompt beginning with --.
|
|
41379
|
+
|
|
41313
41380
|
Options:
|
|
41314
41381
|
--no-tui Do not open the interactive terminal view
|
|
41315
41382
|
--json Write session updates as JSON
|
|
@@ -41341,8 +41408,8 @@ Usage:
|
|
|
41341
41408
|
Commands:
|
|
41342
41409
|
remy Open interactive dashboard
|
|
41343
41410
|
remy dashboard Open interactive dashboard
|
|
41344
|
-
remy --session <session-id> [--no-tui] [--json]
|
|
41345
|
-
Attach to a session
|
|
41411
|
+
remy --session <session-id> [--no-tui] [--json] [prompt]
|
|
41412
|
+
Attach to a session, optionally submitting a follow-up
|
|
41346
41413
|
remy new [--repository <owner/name> ... --installation <id> --model <name> --reasoning-effort <low|medium|high|xhigh> --attach <path> ... --no-tui --json] <prompt>
|
|
41347
41414
|
Create a session
|
|
41348
41415
|
remy login [--env production|staging] [--api-url <url> --auth-api-url <url> --requester-application-id <uuid> --target-application-id <uuid>]
|
|
@@ -41374,14 +41441,32 @@ function parseSessionAttach(argv) {
|
|
|
41374
41441
|
const sessionId = argv[0];
|
|
41375
41442
|
if (!sessionId || sessionId.startsWith("--"))
|
|
41376
41443
|
throw new Error("--session requires a session ID.");
|
|
41377
|
-
|
|
41378
|
-
|
|
41379
|
-
|
|
41380
|
-
|
|
41381
|
-
|
|
41382
|
-
|
|
41444
|
+
let noTui = false;
|
|
41445
|
+
let json3 = false;
|
|
41446
|
+
let prompt;
|
|
41447
|
+
let positionalOnly = false;
|
|
41448
|
+
for (const token of argv.slice(1)) {
|
|
41449
|
+
if (!positionalOnly && token === "--") {
|
|
41450
|
+
positionalOnly = true;
|
|
41451
|
+
continue;
|
|
41452
|
+
}
|
|
41453
|
+
if (!positionalOnly && token === "--no-tui") {
|
|
41454
|
+
noTui = true;
|
|
41455
|
+
continue;
|
|
41456
|
+
}
|
|
41457
|
+
if (!positionalOnly && token === "--json") {
|
|
41458
|
+
json3 = true;
|
|
41459
|
+
continue;
|
|
41460
|
+
}
|
|
41461
|
+
if (!positionalOnly && token.startsWith("--"))
|
|
41462
|
+
throw new Error(`Unknown --session flag ${token}.`);
|
|
41463
|
+
if (prompt !== undefined)
|
|
41464
|
+
throw new Error("--session accepts one prompt. Quote the full prompt as a single argument.");
|
|
41465
|
+
if (!token.trim())
|
|
41466
|
+
throw new Error("A follow-up prompt must not be empty.");
|
|
41467
|
+
prompt = token;
|
|
41383
41468
|
}
|
|
41384
|
-
return { name: "session", sessionId, noTui:
|
|
41469
|
+
return { name: "session", sessionId, noTui, json: json3, ...prompt === undefined ? {} : { prompt } };
|
|
41385
41470
|
}
|
|
41386
41471
|
function parseNewCommand(argv) {
|
|
41387
41472
|
const repositories = [];
|