@meistrari/remy-cli 1.5.0 → 1.6.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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/remy.js +192 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,7 +36,7 @@ The dashboard is the starting point for all interactive work. It refreshes the v
36
36
 
37
37
  ### Start work
38
38
 
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. Before creating the session, choose the agent model and reasoning effort. Remy then opens the session and streams its activity.
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. Before creating the session, choose the agent model and reasoning effort. Reasoning starts at `high` by default. Remy then opens the session and streams its activity.
40
40
 
41
41
  Use `Tab` while writing the request to complete a local file path. Remy attaches selected paths when you confirm the request. On macOS, `Ctrl+V` adds a PNG from the system clipboard to the request; Remy attaches it on confirmation. Remy uploads files before creating the session.
42
42
 
@@ -44,6 +44,8 @@ Use `Tab` while writing the request to complete a local file path. Remy attaches
44
44
 
45
45
  Use Up/Down to select a session in the dashboard, Left/Right to load the adjacent page, and `Enter` to open it. After the dashboard list or repository-review list has focus, Vim keys work too: `j`/`k` move, `h`/`l` go to the previous/next dashboard page (or clear/mark a repository), `G` goes to the last row, and `gg` goes to the first row. Press `Shift+L` in the dashboard to start a logout confirmation. In a session, type a follow-up and press `Enter` to send it.
46
46
 
47
+ 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.
48
+
47
49
  Drag to select visible text in any Remy view; Remy copies it to the local clipboard and emits OSC 52 for terminal or remote-session clipboard support, then clears the selection highlight. When the conversation has focus, press `Tab` to return to the composer.
48
50
 
49
51
  | Control | Result |
@@ -56,7 +58,7 @@ Drag to select visible text in any Remy view; Remy copies it to the local clipbo
56
58
  | `/logout` | Sign out of Remy after confirmation. |
57
59
  | `/help` | Show session controls. |
58
60
  | `/exit` | Leave Remy. |
59
- | `Ctrl+O` | Expand or collapse activity details. |
61
+ | `Ctrl+O` | Expand or collapse full plan and activity details. |
60
62
 
61
63
  After a session becomes terminal, press `Esc` to return to the dashboard or `n` to start new work. From the dashboard, press `q` to quit; in an open session, `q` is ordinary composer text. `Shift+L` in the dashboard and `/logout` in a session ask for confirmation; press `y` to sign out or `Esc` to cancel. `Ctrl+C` exits Remy from the dashboard, wizard, or session view.
62
64
 
@@ -70,7 +72,7 @@ The interactive dashboard is the recommended path. Use these commands when a scr
70
72
  remy new --repository owner/repository "Add a health check endpoint"
71
73
  ```
72
74
 
73
- Repeat `--repository` to select more repositories from the same GitHub installation. If you do not select a repository, supply `--installation <github-installation-id>`. Repeat `--attach <path>` to upload files. `remy new` creates the session from the prompt and flags, then opens it when the terminal is interactive.
75
+ Repeat `--repository` to select more repositories from the same GitHub installation. If you do not select a repository, supply `--installation <github-installation-id>`. Repeat `--attach <path>` to upload files. Direct creation uses `high` reasoning unless `--reasoning-effort` overrides it. `remy new` creates the session from the prompt and flags, then opens it when the terminal is interactive.
74
76
 
75
77
  ### Open a known session
76
78
 
package/dist/remy.js CHANGED
@@ -32222,6 +32222,37 @@ var agentWorkObservationSchema = zod_default2.discriminatedUnion("kind", [
32222
32222
  var agentWorkStateSchema = zod_default2.object({
32223
32223
  items: zod_default2.array(agentWorkItemSchema)
32224
32224
  }).strict();
32225
+ function applyAgentWorkObservations(state, observations) {
32226
+ let items = state.items.map((item) => ({ ...item }));
32227
+ for (const observation of observations) {
32228
+ switch (observation.kind) {
32229
+ case "snapshot":
32230
+ items = observation.items.map((item) => ({ ...item }));
32231
+ break;
32232
+ case "item_observed": {
32233
+ const existingIndex = items.findIndex((item) => item.id === observation.item.id);
32234
+ if (existingIndex === -1)
32235
+ items = [...items, { ...observation.item }];
32236
+ else
32237
+ items = items.map((item, index) => index === existingIndex ? { ...observation.item } : item);
32238
+ break;
32239
+ }
32240
+ case "item_renamed":
32241
+ items = items.map((item) => item.id === observation.itemId ? { ...item, title: observation.title } : item);
32242
+ break;
32243
+ case "item_status_changed":
32244
+ items = items.map((item) => item.id === observation.itemId ? { ...item, status: observation.status } : item);
32245
+ break;
32246
+ case "item_removed":
32247
+ items = items.filter((item) => item.id !== observation.itemId);
32248
+ break;
32249
+ default: {
32250
+ const exhaustiveObservation = observation;
32251
+ }
32252
+ }
32253
+ }
32254
+ return { items };
32255
+ }
32225
32256
 
32226
32257
  // ../../packages/agents-protocol/src/agent-event.ts
32227
32258
  var agentErrorSchema = zod_default2.object({
@@ -33643,6 +33674,20 @@ function formatSessionJson({
33643
33674
  `;
33644
33675
  }
33645
33676
 
33677
+ // src/session-agent-options.ts
33678
+ function newSessionModelLabel(model) {
33679
+ return codexModelPresentation[model].label;
33680
+ }
33681
+ function newSessionReasoningEffortLabel(effort) {
33682
+ return agentReasoningEffortPresentation[effort].label;
33683
+ }
33684
+ var newSessionModels = codexModelIds;
33685
+ var newSessionReasoningEfforts = selectableAgentReasoningEfforts;
33686
+ var defaultNewSessionAgent = {
33687
+ model: codexModelIds[0],
33688
+ reasoningEffort: "high"
33689
+ };
33690
+
33646
33691
  // src/sessions/session-controller.ts
33647
33692
  import { randomUUID as randomUUID3 } from "crypto";
33648
33693
  import { mkdir as mkdir4, readFile as readFile4, rename as rename3, writeFile as writeFile2 } from "fs/promises";
@@ -33852,6 +33897,8 @@ function createSessionViewState({ detail, activeMessageId }) {
33852
33897
  connectionPreviews: toConnectionPreviews(detail),
33853
33898
  previews: { assistantText: "", reasoningText: "" },
33854
33899
  commandAuthorLabels: {},
33900
+ work: { items: [] },
33901
+ workTurnBaselines: {},
33855
33902
  messageTurns: {},
33856
33903
  retainedTurnStarts: {},
33857
33904
  retainedTurnEnds: {}
@@ -34043,6 +34090,8 @@ function projectDurableAgentEvent({ state, event, occurredAt, retainedEventId })
34043
34090
  const agentEvent = parseDurableAgentEvent(event);
34044
34091
  if (agentEvent.type === "agent.message.ended")
34045
34092
  return projectAgentMessageEnded({ state, event: agentEvent, occurredAt });
34093
+ if (agentEvent.type === "agent.work.observed" && agentEvent.actor.type === "main")
34094
+ return projectAgentWorkObserved({ state, event: agentEvent, occurredAt });
34046
34095
  const stateWithTurnStart = agentEvent.type === "agent.turn.started" && agentEvent.actor.type === "main" ? {
34047
34096
  ...state,
34048
34097
  retainedTurnStarts: { ...state.retainedTurnStarts, [agentEvent.turnId]: { actorType: agentEvent.actor.type, startedAt: occurredAt } },
@@ -34051,6 +34100,77 @@ function projectDurableAgentEvent({ state, event, occurredAt, retainedEventId })
34051
34100
  const stateWithCompletedReasoning = agentEvent.type === "agent.reasoning.ended" ? { ...stateWithTurnStart, previews: { ...stateWithTurnStart.previews, reasoningText: "" } } : stateWithTurnStart;
34052
34101
  return appendActivity({ state: stateWithCompletedReasoning, retainedEventId, occurredAt, card: toAgentActivityCard(agentEvent) });
34053
34102
  }
34103
+ function projectAgentWorkObserved({
34104
+ state,
34105
+ event,
34106
+ occurredAt
34107
+ }) {
34108
+ const observations = event.payload.observations.map(sanitizeWorkObservation);
34109
+ const planIndex = state.transcript.findIndex((item) => item.kind === "plan" && item.turnId === event.turnId);
34110
+ const existingPlan = planIndex === -1 ? undefined : state.transcript[planIndex];
34111
+ const turnBaseline = state.workTurnBaselines[event.turnId] ?? state.work;
34112
+ let turnWork = { items: existingPlan?.items ?? [] };
34113
+ for (const observation of observations) {
34114
+ const itemId = patchedWorkItemId(observation);
34115
+ const priorItem = itemId === null ? undefined : turnBaseline.items.find((item) => item.id === itemId);
34116
+ if (priorItem && !turnWork.items.some((item) => item.id === itemId)) {
34117
+ turnWork = applyAgentWorkObservations(turnWork, [{
34118
+ kind: "item_observed",
34119
+ item: priorItem
34120
+ }]);
34121
+ }
34122
+ turnWork = applyAgentWorkObservations(turnWork, [observation]);
34123
+ }
34124
+ const plan = {
34125
+ kind: "plan",
34126
+ planId: `plan:${event.turnId}`,
34127
+ turnId: event.turnId,
34128
+ occurredAt: existingPlan?.occurredAt ?? occurredAt,
34129
+ items: turnWork.items
34130
+ };
34131
+ const transcript = planIndex === -1 ? [...state.transcript, plan] : state.transcript.map((item, index) => index === planIndex ? plan : item);
34132
+ return {
34133
+ ...state,
34134
+ work: applyAgentWorkObservations(state.work, observations),
34135
+ workTurnBaselines: state.workTurnBaselines[event.turnId] ? state.workTurnBaselines : { ...state.workTurnBaselines, [event.turnId]: turnBaseline },
34136
+ latestWorkTurnId: event.turnId,
34137
+ transcript
34138
+ };
34139
+ }
34140
+ function sanitizeWorkObservation(observation) {
34141
+ switch (observation.kind) {
34142
+ case "snapshot":
34143
+ return {
34144
+ ...observation,
34145
+ items: observation.items.map(sanitizeWorkItem)
34146
+ };
34147
+ case "item_observed":
34148
+ return {
34149
+ ...observation,
34150
+ item: sanitizeWorkItem(observation.item)
34151
+ };
34152
+ case "item_renamed":
34153
+ return {
34154
+ ...observation,
34155
+ title: providerText(observation.title, "Untitled task")
34156
+ };
34157
+ case "item_status_changed":
34158
+ case "item_removed":
34159
+ return observation;
34160
+ default: {
34161
+ const exhaustiveObservation = observation;
34162
+ return exhaustiveObservation;
34163
+ }
34164
+ }
34165
+ }
34166
+ function sanitizeWorkItem(item) {
34167
+ return { ...item, title: providerText(item.title, "Untitled task") };
34168
+ }
34169
+ function patchedWorkItemId(observation) {
34170
+ if (observation.kind === "item_renamed" || observation.kind === "item_status_changed")
34171
+ return observation.itemId;
34172
+ return null;
34173
+ }
34054
34174
  function projectAgentMessageEnded({ state, event, occurredAt }) {
34055
34175
  const messageId = event.payload.messageId;
34056
34176
  if (state.transcript.some((item) => item.kind === "message" && item.messageId === messageId))
@@ -34163,9 +34283,10 @@ function publicMessageContentSegmentText(segment) {
34163
34283
  function jsonDetail(value) {
34164
34284
  return JSON.stringify(value);
34165
34285
  }
34166
- var ansiEscapePattern = /\u001B\[[0-9;?]*[a-z]|\u001B\][^\u0007]*\u0007/gi;
34286
+ var ansiEscapePattern = /\u001B\[[0-9;:?]*[\u0020-\u002F]*[\u0040-\u007E]|\u009B[0-9;:?]*[\u0020-\u002F]*[\u0040-\u007E]|\u001B\][^\u0007]*?(?:\u0007|\u001B\\)|\u001B[0-9<=>\u0040-\u005A\u005C-\u005F]/g;
34287
+ var terminalControlPattern = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g;
34167
34288
  function stripAnsi(value) {
34168
- return value.replace(ansiEscapePattern, "");
34289
+ return value.replace(ansiEscapePattern, "").replace(terminalControlPattern, "");
34169
34290
  }
34170
34291
  function providerText(raw, fallback) {
34171
34292
  const cleaned = stripAnsi(raw ?? "").trim();
@@ -35341,20 +35462,6 @@ function activePathToken(text) {
35341
35462
  return match?.[1];
35342
35463
  }
35343
35464
 
35344
- // src/session-agent-options.ts
35345
- function newSessionModelLabel(model) {
35346
- return codexModelPresentation[model].label;
35347
- }
35348
- function newSessionReasoningEffortLabel(effort) {
35349
- return agentReasoningEffortPresentation[effort].label;
35350
- }
35351
- var newSessionModels = codexModelIds;
35352
- var newSessionReasoningEfforts = selectableAgentReasoningEfforts;
35353
- var defaultNewSessionAgent = {
35354
- model: codexModelIds[0],
35355
- reasoningEffort: "medium"
35356
- };
35357
-
35358
35465
  // src/tui/new-session-wizard.ts
35359
35466
  var suggestionSpinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
35360
35467
  var suggestionSpinnerIntervalMs = 80;
@@ -36260,7 +36367,19 @@ async function createSessionTui({
36260
36367
  }
36261
36368
  return count;
36262
36369
  }, turnDividerLabel = function() {
36263
- if (!isRemyWorking(latestState))
36370
+ const working = isRemyWorking(latestState);
36371
+ const planItems = pinnedPlanItems(latestState);
36372
+ if (planItems.length > 0) {
36373
+ const completed = planItems.filter((item) => item.status === "completed").length;
36374
+ const current = planItems.find((item) => item.status === "in_progress") ?? planItems.find((item) => item.status === "pending");
36375
+ if (stopState.kind === "stopping")
36376
+ return renderer.width < 76 ? `Plan ${completed}/${planItems.length} \xB7 stopping\u2026` : `Plan ${completed}/${planItems.length} \xB7 stopping the turn\u2026`;
36377
+ if (renderer.width < 84)
36378
+ return `Plan ${completed}/${planItems.length} \xB7 ctrl+o${working ? " \xB7 esc stop" : ""}`;
36379
+ const currentLabel = current ? ` \xB7 ${truncate2(current.title, 20)}` : "";
36380
+ return `Plan ${completed}/${planItems.length}${currentLabel} \xB7 ctrl+o details${working ? " \xB7 esc to stop" : ""}`;
36381
+ }
36382
+ if (!working)
36264
36383
  return;
36265
36384
  if (stopState.kind === "stopping")
36266
36385
  return renderer.width < 76 ? "stopping\u2026" : "stopping the turn\u2026";
@@ -37038,6 +37157,12 @@ function workingTurnStartedAt(state) {
37038
37157
  return activeTurn.startedAt;
37039
37158
  return Object.values(state.messageTurns).find((turn) => turn.outcome === undefined && turn.startedAt !== undefined)?.startedAt;
37040
37159
  }
37160
+ function pinnedPlanItems(state) {
37161
+ if (state.aggregateStatus !== "open" || state.work.items.length === 0 || state.latestWorkTurnId === undefined)
37162
+ return [];
37163
+ const hasIncompleteItem = state.work.items.some((item) => item.status !== "completed");
37164
+ return !hasIncompleteItem && state.retainedTurnEnds[state.latestWorkTurnId] ? [] : state.work.items;
37165
+ }
37041
37166
  var collapsedActivityTailLength = 3;
37042
37167
  function isSignalActivity(item) {
37043
37168
  return item.card.weight === "signal";
@@ -37058,12 +37183,12 @@ function renderTimeline({ state, activityExpanded }) {
37058
37183
  }
37059
37184
  groups.push({ kind: "single", item });
37060
37185
  }
37061
- const blocks = groups.map((group) => group.kind === "single" ? renderTimelineItem(group.item) : renderActivityGroup({ items: group.items, activityExpanded }));
37186
+ const blocks = groups.map((group) => group.kind === "single" ? renderTimelineItem(group.item, activityExpanded) : renderActivityGroup({ items: group.items, activityExpanded }));
37062
37187
  return joinStyled(blocks.filter((block) => block.chunks.length > 0), `
37063
37188
 
37064
37189
  `);
37065
37190
  }
37066
- function renderTimelineItem(item) {
37191
+ function renderTimelineItem(item, activityExpanded) {
37067
37192
  if (item.kind === "message") {
37068
37193
  const header = new StyledText5([
37069
37194
  renderTimestampChunk(item.occurredAt),
@@ -37073,12 +37198,53 @@ function renderTimelineItem(item) {
37073
37198
  return joinStyled([header, renderMessageBody({ text: item.text, role: item.author.role })], `
37074
37199
  `);
37075
37200
  }
37201
+ if (item.kind === "plan")
37202
+ return renderPlan({ item, expanded: activityExpanded });
37076
37203
  return new StyledText5([
37077
37204
  renderTimestampChunk(item.occurredAt),
37078
37205
  bold4(fg6(PALETTE.tool)("Artifact: ")),
37079
37206
  fg6(PALETTE.bodyText)(item.filename)
37080
37207
  ]);
37081
37208
  }
37209
+ var collapsedPlanItemLimit = 5;
37210
+ function renderPlan({ item, expanded }) {
37211
+ if (item.items.length === 0)
37212
+ return new StyledText5([]);
37213
+ const completed = item.items.filter((workItem) => workItem.status === "completed").length;
37214
+ const visibleItems = expanded ? item.items : collapsedPlanItems(item.items);
37215
+ const rows = visibleItems.map((workItem) => {
37216
+ if (workItem.status === "in_progress") {
37217
+ return new StyledText5([
37218
+ fg6(PALETTE.remyAccent)(" \u25B8 "),
37219
+ bold4(fg6(PALETTE.bodyText)(workItem.title))
37220
+ ]);
37221
+ }
37222
+ const glyph = workItem.status === "completed" ? "\u2713" : "\u25CB";
37223
+ return new StyledText5([
37224
+ fg6(PALETTE.dimText)(` ${glyph} `),
37225
+ dim4(fg6(PALETTE.dimText)(workItem.title))
37226
+ ]);
37227
+ });
37228
+ const hidden = item.items.length - visibleItems.length;
37229
+ return joinStyled([
37230
+ new StyledText5([
37231
+ renderTimestampChunk(item.occurredAt),
37232
+ bold4(fg6(PALETTE.remyAccent)("Plan")),
37233
+ dim4(fg6(PALETTE.dimText)(` \xB7 ${completed}/${item.items.length} done`))
37234
+ ]),
37235
+ ...rows,
37236
+ ...hidden > 0 ? [new StyledText5([dim4(fg6(PALETTE.dimText)(` \u2026 ${hidden} more task${hidden === 1 ? "" : "s"} \xB7 Ctrl+O details`))])] : []
37237
+ ], `
37238
+ `);
37239
+ }
37240
+ function collapsedPlanItems(items) {
37241
+ if (items.length <= collapsedPlanItemLimit)
37242
+ return items;
37243
+ const current = items.find((item) => item.status === "in_progress");
37244
+ if (!current || items.slice(0, collapsedPlanItemLimit).some((item) => item.id === current.id))
37245
+ return items.slice(0, collapsedPlanItemLimit);
37246
+ return [...items.slice(0, collapsedPlanItemLimit - 1), current];
37247
+ }
37082
37248
  var boilerplateActivitySummaries = new Set([
37083
37249
  "Tool started.",
37084
37250
  "Tool completed.",
@@ -37208,6 +37374,11 @@ function formatElapsed(elapsedMs) {
37208
37374
  return `${totalSeconds}s`;
37209
37375
  return `${Math.floor(totalSeconds / 60)}m ${totalSeconds % 60}s`;
37210
37376
  }
37377
+ function truncate2(value, width) {
37378
+ if (value.length <= width)
37379
+ return value;
37380
+ return `${value.slice(0, Math.max(0, width - 1))}\u2026`;
37381
+ }
37211
37382
  function renderComposerStatus({ admittedSubmissions }) {
37212
37383
  if (admittedSubmissions.length === 0)
37213
37384
  return new StyledText5([]);
@@ -37343,7 +37514,7 @@ var compactMarkRows = 9;
37343
37514
  var compactMinWidth = 48;
37344
37515
  var compactMinHeight = 20;
37345
37516
  var markBrightnessGain = 4.2;
37346
- var remyCliVersion = "1.5.0";
37517
+ var remyCliVersion = "1.6.0";
37347
37518
  async function showRemySplash({
37348
37519
  createRenderer = createRemyRenderer,
37349
37520
  durationMs = splashDurationMs,
@@ -38431,7 +38602,7 @@ async function createNewSession({
38431
38602
  prompt: command.prompt,
38432
38603
  fileIds,
38433
38604
  ...command.model ? { model: command.model } : {},
38434
- ...command.reasoningEffort ? { reasoningEffort: command.reasoningEffort } : {},
38605
+ reasoningEffort: command.reasoningEffort ?? defaultNewSessionAgent.reasoningEffort,
38435
38606
  idempotencyKey: randomUUID5()
38436
38607
  }
38437
38608
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/remy-cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Remy, the Coding Agent terminal client.",
5
5
  "type": "module",
6
6
  "bin": {