flowviant 0.91.0 → 0.91.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/bin/cli.mjs +19 -25
- package/bin/lib/tty.mjs +43 -17
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -350,15 +350,14 @@ if (process.argv[2] === 'env') {
|
|
|
350
350
|
const { canPrompt, askWithTimeout, selectMenu, menuSupported } = await import('./lib/tty.mjs');
|
|
351
351
|
const interactive = canPrompt() && process.env.FLOWVIANT_REEXEC !== '1';
|
|
352
352
|
|
|
353
|
-
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
const PICK_TIMEOUT_MS = 60_000;
|
|
353
|
+
/** NO ANSWER TIME LIMIT on the start path (2026-09-20, the owner: "why is
|
|
354
|
+
* there an answer time limit, remove that"). The confirm below carried 20s
|
|
355
|
+
* and the picker 60s, each arguing that a restart nobody is watching must
|
|
356
|
+
* not sit dark on a prompt — but `interactive` already excludes exactly that
|
|
357
|
+
* case (no foreground terminal, or a self-update re-exec, and nothing is
|
|
358
|
+
* asked). What was left was a person reading a list being told "no answer
|
|
359
|
+
* in 60s — nothing started". A prompt a person can see waits for the
|
|
360
|
+
* person; tty.mjs's header carries the argument. */
|
|
362
361
|
const externalToken = process.argv.includes('--fleet') || Boolean(process.env.FLOWVIANT_FLEET);
|
|
363
362
|
|
|
364
363
|
/** Re-exec a plain `flowviant` after an inline login — the login command's own
|
|
@@ -437,15 +436,7 @@ if (!FLEET_TOKEN) {
|
|
|
437
436
|
const res = await selectMenu({
|
|
438
437
|
options,
|
|
439
438
|
defaultIndex: likely >= 0 ? likely : 0,
|
|
440
|
-
timeoutMs: PICK_TIMEOUT_MS,
|
|
441
439
|
});
|
|
442
|
-
if (res.timedOut) {
|
|
443
|
-
console.error(
|
|
444
|
-
`\nno answer in ${Math.round(PICK_TIMEOUT_MS / 1000)}s — nothing started. ` +
|
|
445
|
-
`Name one with \`--project <name|id>\`, or run \`flowviant\` here in the foreground and pick.`
|
|
446
|
-
);
|
|
447
|
-
process.exit(1);
|
|
448
|
-
}
|
|
449
440
|
if (res.cancelled) {
|
|
450
441
|
console.error('nothing chosen — nothing started.');
|
|
451
442
|
process.exit(1);
|
|
@@ -460,12 +451,13 @@ if (!FLEET_TOKEN) {
|
|
|
460
451
|
console.log(` ${choices.length + 1}. ${loginLabel}`);
|
|
461
452
|
const hint = likely >= 0 ? ` (enter for ${creds.projectLabel(choices[likely])})` : '';
|
|
462
453
|
const raw = await askWithTimeout(
|
|
463
|
-
`Which project should this daemon serve? [1-${choices.length + 1}]${hint}
|
|
464
|
-
PICK_TIMEOUT_MS
|
|
454
|
+
`Which project should this daemon serve? [1-${choices.length + 1}]${hint} `
|
|
465
455
|
);
|
|
466
456
|
if (raw === null) {
|
|
457
|
+
// The read itself failed — stdin closed, or the terminal went away
|
|
458
|
+
// mid-question. Not a timeout: there is none.
|
|
467
459
|
console.error(
|
|
468
|
-
`\
|
|
460
|
+
`\ncould not read an answer — nothing started. ` +
|
|
469
461
|
`Name one with \`--project <name|id>\`, or run \`flowviant\` here in the foreground and pick.`
|
|
470
462
|
);
|
|
471
463
|
process.exit(1);
|
|
@@ -519,7 +511,10 @@ if (!FLEET_TOKEN) {
|
|
|
519
511
|
// stop here with the machine serving nothing. A guard that only works once
|
|
520
512
|
// everyone already has it is not a guard.
|
|
521
513
|
//
|
|
522
|
-
//
|
|
514
|
+
// IF THE READ FAILS WE SERVE, AND WE DO NOT BIND. (This used to say "on
|
|
515
|
+
// timeout"; there is no timeout since 2026-09-20 — a person at the prompt
|
|
516
|
+
// is waited for. `null` now means stdin closed under the question.) Those
|
|
517
|
+
// are two decisions:
|
|
523
518
|
// · SERVE, because it is what every version before 0.55.0 did with this
|
|
524
519
|
// exact store, so the silent path is the status quo rather than a new
|
|
525
520
|
// risk — and a daemon that answers is strictly better than one that does
|
|
@@ -531,13 +526,12 @@ if (!FLEET_TOKEN) {
|
|
|
531
526
|
const creds = await import('./lib/credentials.mjs');
|
|
532
527
|
const label = creds.projectLabel(CREDENTIAL.entry);
|
|
533
528
|
const answered = await askWithTimeout(
|
|
534
|
-
`This machine's one connected project is ${label}. Serve this repo (${CREDENTIAL.repoRoot}) as ${label}? [Y/n]
|
|
535
|
-
CONFIRM_TIMEOUT_MS
|
|
529
|
+
`This machine's one connected project is ${label}. Serve this repo (${CREDENTIAL.repoRoot}) as ${label}? [Y/n] `
|
|
536
530
|
);
|
|
537
|
-
const raw = answered === null ? null : answered.toLowerCase(); // null =
|
|
531
|
+
const raw = answered === null ? null : answered.toLowerCase(); // null = the read failed
|
|
538
532
|
if (raw === null) {
|
|
539
533
|
console.log(
|
|
540
|
-
`\n
|
|
534
|
+
`\n could not read an answer — serving ${label} for this run ` +
|
|
541
535
|
`without tying it to this repo. Run \`flowviant\` here and answer to make it stick.`
|
|
542
536
|
);
|
|
543
537
|
} else if (raw === '' || raw === 'y' || raw === 'yes') {
|
package/bin/lib/tty.mjs
CHANGED
|
@@ -19,13 +19,29 @@
|
|
|
19
19
|
*
|
|
20
20
|
* 2. `askWithTimeout()` — while asking, install no-op SIGTTIN/SIGTTOU handlers.
|
|
21
21
|
* A handler (even an empty one) replaces the default STOP, so a misjudged
|
|
22
|
-
* foreground check degrades to a read that fails
|
|
23
|
-
*
|
|
24
|
-
* still running.
|
|
22
|
+
* foreground check degrades to a read that fails rather than a process
|
|
23
|
+
* that is frozen.
|
|
25
24
|
*
|
|
26
25
|
* The detection is best-effort by design and FAILS TOWARDS ASKING: an unknown
|
|
27
26
|
* platform returns true, because refusing to prompt a human who IS there is a
|
|
28
|
-
* worse failure than a prompt that
|
|
27
|
+
* worse failure than a prompt that waits.
|
|
28
|
+
*
|
|
29
|
+
* A PROMPT A PERSON IS LOOKING AT WAITS FOR THE PERSON (2026-09-20). Both
|
|
30
|
+
* helpers used to carry a mandatory timer — 20s on the binding confirm, 60s
|
|
31
|
+
* on the project picker — on the argument that "anything longer is a restart
|
|
32
|
+
* nobody is watching, and the machine must not sit dark for it". That
|
|
33
|
+
* argument was already answered one layer up: the start path never asks at
|
|
34
|
+
* all unless `canPrompt()` says a foreground terminal is attached AND the
|
|
35
|
+
* process is not a self-update re-exec, so the unattended restart the timer
|
|
36
|
+
* was defending against cannot reach a prompt. What the timer actually did
|
|
37
|
+
* was refuse a person who went to read the list, make a coffee, or answer a
|
|
38
|
+
* message — "no answer in 60s — nothing started" — on the one path where
|
|
39
|
+
* nothing has been started yet and waiting costs nothing. The owner's ruling,
|
|
40
|
+
* verbatim: "why is there an answer time limit, remove that." So `timeoutMs`
|
|
41
|
+
* is OPTIONAL now and the start path passes none; a caller that has a real
|
|
42
|
+
* reason to bound a wait (the install prompt, where silence is "no") may
|
|
43
|
+
* still pass one. The signal handlers stay — they were never about the timer,
|
|
44
|
+
* they are what keeps a backgrounded job from freezing.
|
|
29
45
|
*/
|
|
30
46
|
|
|
31
47
|
import { readFileSync } from 'node:fs';
|
|
@@ -80,11 +96,21 @@ export function canPrompt() {
|
|
|
80
96
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY) && inForeground();
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
/** A timer only when a caller asked for one. `clearTimeout(null)` is a no-op,
|
|
100
|
+
* so the finally blocks need no branch. Non-finite or non-positive means
|
|
101
|
+
* "wait for the person". */
|
|
102
|
+
export function boundedTimer(timeoutMs, fire) {
|
|
103
|
+
return Number.isFinite(timeoutMs) && timeoutMs > 0 ? setTimeout(fire, timeoutMs) : null;
|
|
104
|
+
}
|
|
105
|
+
|
|
83
106
|
/**
|
|
84
|
-
* Ask, and come back
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
107
|
+
* Ask, and come back with an answer or a refusal. Resolves the trimmed
|
|
108
|
+
* answer, or `null` when the read failed (stdin closed under us, the terminal
|
|
109
|
+
* went away) or — only when a caller passed a finite `timeoutMs` — when nobody
|
|
110
|
+
* answered in time. With no `timeoutMs` it waits for the person; see the
|
|
111
|
+
* header. The caller decides what `null` means, because it is not the same
|
|
112
|
+
* answer everywhere (the binding confirm serves unbound; the project picker
|
|
113
|
+
* refuses, exactly as it does headless).
|
|
88
114
|
*/
|
|
89
115
|
export async function askWithTimeout(query, timeoutMs) {
|
|
90
116
|
const noop = () => {};
|
|
@@ -97,7 +123,7 @@ export async function askWithTimeout(query, timeoutMs) {
|
|
|
97
123
|
output: process.stdout,
|
|
98
124
|
});
|
|
99
125
|
const ac = new AbortController();
|
|
100
|
-
const timer =
|
|
126
|
+
const timer = boundedTimer(timeoutMs, () => ac.abort());
|
|
101
127
|
try {
|
|
102
128
|
return (await rl.question(query, { signal: ac.signal })).trim();
|
|
103
129
|
} catch {
|
|
@@ -153,15 +179,15 @@ const MENU_FOOTER = '↑/↓ move · enter select · 1–9 jump · esc cancel';
|
|
|
153
179
|
* An arrow-navigable picker for the start path. Resolves one of:
|
|
154
180
|
* { index } the row taken
|
|
155
181
|
* { cancelled } Esc/q/Ctrl-C
|
|
156
|
-
* { timedOut } nobody drove it
|
|
182
|
+
* { timedOut } a caller passed `timeoutMs` and nobody drove it in time
|
|
183
|
+
* (the start path passes none — see the header)
|
|
157
184
|
* { unsupported } no raw mode — the caller uses the numeric prompt instead
|
|
158
185
|
*
|
|
159
|
-
* Held to this file's one law — no start-path prompt may
|
|
160
|
-
* same way `askWithTimeout` is: it
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* ever reaching here.
|
|
186
|
+
* Held to this file's one law — no start-path prompt may STOP the daemon — the
|
|
187
|
+
* same way `askWithTimeout` is: it installs the SIGTTIN/SIGTTOU no-ops,
|
|
188
|
+
* restores the terminal in every exit, and refuses (returns `unsupported`)
|
|
189
|
+
* rather than half-drawing when raw mode is not really there. The caller still
|
|
190
|
+
* gates on `canPrompt()` before ever reaching here.
|
|
165
191
|
*/
|
|
166
192
|
export async function selectMenu({ options, defaultIndex = 0, timeoutMs }) {
|
|
167
193
|
if (!menuSupported()) return { unsupported: true };
|
|
@@ -220,7 +246,7 @@ export async function selectMenu({ options, defaultIndex = 0, timeoutMs }) {
|
|
|
220
246
|
};
|
|
221
247
|
process.on('SIGTTIN', noop);
|
|
222
248
|
process.on('SIGTTOU', noop);
|
|
223
|
-
const timer =
|
|
249
|
+
const timer = boundedTimer(timeoutMs, () => finish({ timedOut: true }));
|
|
224
250
|
try {
|
|
225
251
|
stdin.setRawMode(true);
|
|
226
252
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.91.
|
|
3
|
+
"version": "0.91.1",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|