@shardflux/cli 0.19.0 → 0.19.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/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 0.19.0 (not yet published)
3
+ ## 0.19.1 (not yet published)
4
+
5
+ - A first prompt is never lost to a slow or unusual start. `--prompt` / `--prompt-file` types the prompt only into the
6
+ agent's input box, never into one of its dialogs ("❯ 1. Yes"). If the terminal never shows the input box and
7
+ nothing was typed, the prompt goes to the conversation's own inbox, which delivers it once the agent reads input.
8
+ `--json` then reports `"initial_prompt": {"submitted": false, "queued": true}` instead of failing with
9
+ `initial_prompt_failed`.
10
+ - Brings the Shardflux app for Ubuntu 0.1.3: Try it once never waits silently. A demo agent still waiting with nothing
11
+ said 45 s after its start gets its task once more through its conversation, and one line says so; still nothing
12
+ 45 s later, one line says it hasn't started. The optional dependency on `@shardflux/desktop-linux-x64` / `-arm64` is
13
+ exactly 0.1.3.
14
+
15
+ ## 0.19.0 (2026-10-11)
4
16
 
5
17
  - A new conversation on an account without a saved machine gets its first prompt in seconds, whatever is in
6
18
  `~/.claude` and `~/.codex`. The prompt is typed first, and this computer's Claude and Codex config is copied into
@@ -28,6 +28,7 @@ import { openInFor } from "./t3-mode.js";
28
28
  import { openClaudeDesktopSession, openDesktopWorkspace, queueT3Prewarm } from "./agent-target-commands.js";
29
29
  import { submitInitialPrompt, prepareInitialCodexProject } from "./initial-prompt.js";
30
30
  import { conversationWorkspace, conversationsUnavailable, getConversation, listConversations, resumeConversation } from "./conversations.js";
31
+ import { conversationClient } from "./conversation-commands.js";
31
32
  import { conversationTerminal, reopenConversation } from "./conversation-terminal.js";
32
33
  import { CHOICE_PENDING, choiceReport, hasChoice, launchChoice, modelChoice, runFlags } from "./agent-models.js";
33
34
  const active = (session) => session.state === 'running' || session.state === 'starting';
@@ -145,8 +146,12 @@ export async function terminal(ctx, ws, app, agent, project, values, setupHash,
145
146
  throw new Error(`project setup could not be queued: ${queued.stderr.trim() || `exit ${queued.exitCode}`}`);
146
147
  });
147
148
  store.save(project, app);
149
+ let delivery;
148
150
  if (typeof values.initialPrompt === 'string') {
149
- await submitInitialPrompt(signal => cellConnector(cell, session.session_id)(0, signal), values.initialPrompt, ws.key, ctx.signal, app, bytes => cell.pty.input(session.session_id, bytes));
151
+ const text = values.initialPrompt;
152
+ // A conversation's guest delivers the prompt itself when this terminal never showed its input box (0.19.1+).
153
+ const queue = conversation ? () => conversationClient(ctx).conversations.send(conversation.id, text, { signal: ctx.signal }) : undefined;
154
+ delivery = await submitInitialPrompt(signal => cellConnector(cell, session.session_id)(0, signal), text, ws.key, ctx.signal, app, bytes => cell.pty.input(session.session_id, bytes), queue);
150
155
  }
151
156
  // Work that must never delay the first prompt (the laptop's agent config, 0.19.0+) starts once the agent has it.
152
157
  const after = conversation?.afterStart?.();
@@ -154,7 +159,7 @@ export async function terminal(ctx, ws, app, agent, project, values, setupHash,
154
159
  await preparation;
155
160
  // conversation (0.16.0+): which conversation this session is, also when its computer hosts several threads.
156
161
  // model_choice (0.17.0+): the requested model and effort, and whether this thread started with them.
157
- ctx.io.stdout.write(json({ workspace: ws.data, session, ...(conversation ? { conversation: { id: conversation.id } } : {}), ...(typeof values.initialPrompt === 'string' ? { initial_prompt: { submitted: true } } : {}),
162
+ ctx.io.stdout.write(json({ workspace: ws.data, session, ...(conversation ? { conversation: { id: conversation.id } } : {}), ...(delivery ? { initial_prompt: delivery === 'submitted' ? { submitted: true } : { submitted: false, queued: true } } : {}),
158
163
  ...(hasChoice(explicit) ? { model_choice: choiceReport(explicit, applied) } : {}) }));
159
164
  await after;
160
165
  return 0;
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const CLI_VERSION = "0.19.0";
1
+ export declare const CLI_VERSION = "0.19.1";
2
2
  export interface OptionSpec {
3
3
  type: 'string' | 'boolean';
4
4
  short?: string;
package/dist/args.js CHANGED
@@ -13,7 +13,7 @@ import { parseArgs } from 'node:util';
13
13
  import { ACCOUNT_COMMANDS } from "./account-args.js";
14
14
  import { UsageError } from "./errors.js";
15
15
  import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
16
- export const CLI_VERSION = '0.19.0';
16
+ export const CLI_VERSION = '0.19.1';
17
17
  export const GLOBAL_OPTIONS = {
18
18
  'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
19
19
  json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
@@ -22,5 +22,18 @@ export declare function readInitialImages(ctx: Ctx, values: Values, prompt: stri
22
22
  export declare function promptWithImages(prompt: string, images: readonly InitialImage[]): string;
23
23
  /** Writes the images into the conversation VM before the first message refers to them. */
24
24
  export declare function uploadInitialImages(cell: Pick<CellClient, 'files'>, images: readonly InitialImage[], signal?: AbortSignal): Promise<void>;
25
- /** Wait for the native terminal to enable paste before submitting one first turn. No blind timing or input replay. */
26
- export declare function submitInitialPrompt(connect: (signal: AbortSignal) => Promise<TerminalConnection>, prompt: string, workspaceKey: string, signal: AbortSignal, app?: 'claude' | 'codex', write?: (bytes: Uint8Array) => Promise<unknown>): Promise<void>;
25
+ /** The conversation inbox takes a message of at most this many characters (the API's text limit). */
26
+ export declare const INBOX_MAX_CHARS = 65536;
27
+ /**
28
+ * The harness's input box is the last thing that drew its prompt glyph (0.19.1+). Claude Code and Codex draw the same
29
+ * glyph as the selection cursor of their dialogs ("❯ 1. Yes, I trust this folder"); a prompt pasted there would pick
30
+ * options. The guest's own first-message delivery reads the screen the same way (coding 39+).
31
+ */
32
+ export declare function inputBoxReady(tail: string, glyph: string): boolean;
33
+ /**
34
+ * Wait for the native terminal to enable paste before submitting one first turn. No blind timing or input replay.
35
+ * When nothing was typed yet and the terminal never became ready, `queue` hands the prompt to the conversation's guest
36
+ * (its inbox, which waits for the harness and retries Enter) and the result is `queued` (0.19.1+): a first prompt is
37
+ * never lost to a slow start.
38
+ */
39
+ export declare function submitInitialPrompt(connect: (signal: AbortSignal) => Promise<TerminalConnection>, prompt: string, workspaceKey: string, signal: AbortSignal, app?: 'claude' | 'codex', write?: (bytes: Uint8Array) => Promise<unknown>, queue?: () => Promise<unknown>): Promise<'submitted' | 'queued'>;
@@ -137,8 +137,26 @@ export async function uploadInitialImages(cell, images, signal) {
137
137
  await cell.files.write(image.path, image.data, { mode: '0600', createParents: true });
138
138
  }
139
139
  }
140
- /** Wait for the native terminal to enable paste before submitting one first turn. No blind timing or input replay. */
141
- export async function submitInitialPrompt(connect, prompt, workspaceKey, signal, app = 'claude', write) {
140
+ /** The conversation inbox takes a message of at most this many characters (the API's text limit). */
141
+ export const INBOX_MAX_CHARS = 65536;
142
+ /**
143
+ * The harness's input box is the last thing that drew its prompt glyph (0.19.1+). Claude Code and Codex draw the same
144
+ * glyph as the selection cursor of their dialogs ("❯ 1. Yes, I trust this folder"); a prompt pasted there would pick
145
+ * options. The guest's own first-message delivery reads the screen the same way (coding 39+).
146
+ */
147
+ export function inputBoxReady(tail, glyph) {
148
+ // eslint-disable-next-line no-control-regex -- Native terminal escapes are removed before reading the screen text.
149
+ const plain = tail.replace(/\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, ' ');
150
+ const at = plain.lastIndexOf(glyph);
151
+ return at >= 0 && !/^[\s\u00a0]*\d+\./.test(plain.slice(at + glyph.length));
152
+ }
153
+ /**
154
+ * Wait for the native terminal to enable paste before submitting one first turn. No blind timing or input replay.
155
+ * When nothing was typed yet and the terminal never became ready, `queue` hands the prompt to the conversation's guest
156
+ * (its inbox, which waits for the harness and retries Enter) and the result is `queued` (0.19.1+): a first prompt is
157
+ * never lost to a slow start.
158
+ */
159
+ export async function submitInitialPrompt(connect, prompt, workspaceKey, signal, app = 'claude', write, queue) {
142
160
  const bounded = AbortSignal.any([signal, AbortSignal.timeout(20000)]);
143
161
  let connection;
144
162
  let sent = false, pasted = false;
@@ -159,7 +177,7 @@ export async function submitInitialPrompt(connect, prompt, workspaceKey, signal,
159
177
  // eslint-disable-next-line no-control-regex -- Strip native terminal ANSI before matching prompt echo.
160
178
  const plain = tail.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '').replace(/\s/g, '');
161
179
  if (sent && bytes.length)
162
- return;
180
+ return 'submitted';
163
181
  if (pasted) {
164
182
  // A long prompt overflows the input box: a 24-row terminal shows only its end, so either end confirms the paste.
165
183
  const compact = prompt.replace(/\s/g, '');
@@ -174,7 +192,7 @@ export async function submitInitialPrompt(connect, prompt, workspaceKey, signal,
174
192
  tail = '';
175
193
  continue;
176
194
  }
177
- const ready = app === 'claude' ? tail.includes('❯') : tail.includes('›');
195
+ const ready = inputBoxReady(tail, app === 'claude' ? '❯' : '›');
178
196
  if (!ready || !pasteEnabled)
179
197
  continue;
180
198
  const paste = Buffer.from(`\x1b[200~${prompt}\x1b[201~`);
@@ -188,6 +206,13 @@ export async function submitInitialPrompt(connect, prompt, workspaceKey, signal,
188
206
  throw new Error('The agent terminal closed before accepting a prompt.');
189
207
  }
190
208
  catch {
209
+ if (!pasted && queue && !signal.aborted && prompt.length <= INBOX_MAX_CHARS) {
210
+ try {
211
+ await queue();
212
+ return 'queued';
213
+ }
214
+ catch { /* reported as not submitted below */ }
215
+ }
191
216
  throw new CodedError('initial_prompt_failed', `Created ${workspaceKey}, but the initial prompt ${sent ? 'could not be confirmed' : 'was not submitted'}. Open that conversation before starting another.`, undefined, { workspace_key: workspaceKey, submitted: sent });
192
217
  }
193
218
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/cli",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "type": "module",
5
5
  "description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork serverless agent workspaces.",
6
6
  "license": "Apache-2.0",
@@ -58,9 +58,9 @@
58
58
  "typescript-eslint": "8.70.1"
59
59
  },
60
60
  "optionalDependencies": {
61
- "@shardflux/desktop-linux-x64": "0.1.2",
62
- "@shardflux/desktop-linux-arm64": "0.1.2",
63
- "@shardflux/menubar-darwin": "^0.7.0"
61
+ "@shardflux/desktop-linux-x64": "0.1.3",
62
+ "@shardflux/desktop-linux-arm64": "0.1.3",
63
+ "@shardflux/menubar-darwin": "^0.7.1"
64
64
  },
65
65
  "scripts": {
66
66
  "build": "node scripts/build.mjs",