@retasc/cli 1.27.0 → 1.29.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/CHANGELOG.md +23 -0
- package/dist/auth.js +55 -4
- package/dist/commands/bind.js +15 -8
- package/dist/index.js +7 -3
- package/dist/lib/launcher.js +19 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ release commits and the issues they reference.
|
|
|
6
6
|
|
|
7
7
|
Dates are the npm publish date. Each entry names the RTSC issue behind it.
|
|
8
8
|
|
|
9
|
+
## 1.29.0 (2026-08-19)
|
|
10
|
+
|
|
11
|
+
- **RTSC-672** — every command we hand out now says `npx @retasc/cli@latest`. npx caches
|
|
12
|
+
by spec, so a bare `npx @retasc/cli` can keep serving whatever version you first ran —
|
|
13
|
+
which meant the people most likely to re-run a command, the ones who hit a bug and were
|
|
14
|
+
told it was fixed, were exactly the ones liable to be served the broken build again.
|
|
15
|
+
`@latest` is a tag, not a pin: it re-resolves every time and can never go stale, where a
|
|
16
|
+
version number written into docs absolutely can.
|
|
17
|
+
- **RTSC-672** — a first-run failure names the build that produced it, so "it still
|
|
18
|
+
doesn't work" and "you're running last week's CLI" stop looking identical.
|
|
19
|
+
|
|
20
|
+
## 1.28.0 (2026-08-19)
|
|
21
|
+
|
|
22
|
+
- **RTSC-676** — signing in opens your browser. It used to print a URL and an
|
|
23
|
+
eight-character code and leave you to switch windows, navigate, and retype it. Now the
|
|
24
|
+
page opens for you, pre-filled when the provider supplies RFC 8628's complete URL. The
|
|
25
|
+
URL and code are still printed first and always: there is no browser inside a container
|
|
26
|
+
or over SSH, which is where `bind` runs most, so the browser is a convenience layered
|
|
27
|
+
on top and never the only way through. A missing `xdg-open` changes nothing.
|
|
28
|
+
`RETASC_NO_BROWSER=1` turns it off.
|
|
29
|
+
- **RTSC-676** — the finish screen's closing lines are flush left, and it ends with a
|
|
30
|
+
blank line instead of welding the shell prompt to the last thing you read.
|
|
31
|
+
|
|
9
32
|
## 1.27.0 (2026-08-19)
|
|
10
33
|
|
|
11
34
|
- **RTSC-673** — `bind` ends by telling you what to do, not by dumping config. It used to
|
package/dist/auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { ConvexHttpClient } from "convex/browser";
|
|
2
3
|
import { makeFunctionReference } from "convex/server";
|
|
3
4
|
import { loadConfig, patchConfig } from "./config.js";
|
|
@@ -28,11 +29,61 @@ async function postForm(url, body) {
|
|
|
28
29
|
return res.json();
|
|
29
30
|
}
|
|
30
31
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
31
|
-
/**
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Open a URL in the machine's browser, best effort (RTSC-676).
|
|
34
|
+
*
|
|
35
|
+
* BEST EFFORT is the whole contract. There is no browser inside a container or over
|
|
36
|
+
* SSH, which is where `bind` most often runs, so this must never be the only way
|
|
37
|
+
* through and must never turn a failure of its own into a failure of sign-in. It is
|
|
38
|
+
* spawned detached and every outcome is swallowed: the printed URL and code below are
|
|
39
|
+
* the real path, and this only saves the person who does have a browser a trip through
|
|
40
|
+
* their address bar.
|
|
41
|
+
*
|
|
42
|
+
* `xdg-open` on Linux is frequently absent even under a desktop; that is a miss, not an
|
|
43
|
+
* error. `start` needs a shell on Windows, and its first quoted argument is the window
|
|
44
|
+
* TITLE, hence the empty string.
|
|
45
|
+
*/
|
|
46
|
+
function openBrowser(url) {
|
|
47
|
+
if (process.env.RETASC_NO_BROWSER)
|
|
48
|
+
return;
|
|
49
|
+
const [cmd, args] = process.platform === "darwin"
|
|
50
|
+
? ["open", [url]]
|
|
51
|
+
: process.platform === "win32"
|
|
52
|
+
? ["start", ['""', url]]
|
|
53
|
+
: ["xdg-open", [url]];
|
|
54
|
+
try {
|
|
55
|
+
const child = spawn(cmd, args, {
|
|
56
|
+
stdio: "ignore",
|
|
57
|
+
detached: true,
|
|
58
|
+
shell: process.platform === "win32",
|
|
59
|
+
});
|
|
60
|
+
// Without this the CLI would wait on a browser that outlives it by hours.
|
|
61
|
+
child.unref();
|
|
62
|
+
// A missing `xdg-open` surfaces here rather than as a throw.
|
|
63
|
+
child.on("error", () => { });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* no browser, no shell, no matter — the URL is printed below */
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Print the two lines a human has to act on, identically for both doors, and open the
|
|
71
|
+
* browser for them if this machine has one.
|
|
72
|
+
*
|
|
73
|
+
* The lines are printed FIRST and unconditionally. Someone reading a terminal in a
|
|
74
|
+
* container has to see the URL whatever happened, and an auto-open that silently
|
|
75
|
+
* failed must leave the screen exactly as useful as it was before (RTSC-676).
|
|
76
|
+
*
|
|
77
|
+
* `complete` is RFC 8628's pre-filled URL when the provider sent one. It is opened in
|
|
78
|
+
* preference to the plain one, but never PRINTED in its place: the code stays on its own
|
|
79
|
+
* line so it can be read aloud, retyped on a phone, or pasted into a browser on another
|
|
80
|
+
* machine — none of which a link helps with.
|
|
81
|
+
*/
|
|
82
|
+
function announceCode(where, url, code, complete) {
|
|
33
83
|
console.log(`\n Open: ${url}`);
|
|
34
84
|
console.log(` Enter code: ${code}\n`);
|
|
35
85
|
console.log(` Waiting for ${where} authorization…`);
|
|
86
|
+
openBrowser(complete || url);
|
|
36
87
|
}
|
|
37
88
|
/** Run GitHub's device flow (talking to GitHub directly) and return its access token. */
|
|
38
89
|
async function githubDeviceToken() {
|
|
@@ -46,7 +97,7 @@ async function githubDeviceToken() {
|
|
|
46
97
|
if (!start.device_code) {
|
|
47
98
|
throw new Error(`GitHub device-flow start failed: ${JSON.stringify(start)}`);
|
|
48
99
|
}
|
|
49
|
-
announceCode("GitHub", start.verification_uri, start.user_code);
|
|
100
|
+
announceCode("GitHub", start.verification_uri, start.user_code, start.verification_uri_complete);
|
|
50
101
|
// Poll GitHub for the access token.
|
|
51
102
|
let intervalMs = (start.interval || 5) * 1000;
|
|
52
103
|
const deadline = Date.now() + start.expires_in * 1000;
|
|
@@ -80,7 +131,7 @@ async function githubDeviceToken() {
|
|
|
80
131
|
*/
|
|
81
132
|
async function googleDeviceToken(convex) {
|
|
82
133
|
const start = (await convex.action(googleDeviceStart, {}));
|
|
83
|
-
announceCode("Google", start.verificationUrl, start.userCode);
|
|
134
|
+
announceCode("Google", start.verificationUrl, start.userCode, start.verificationUrlComplete);
|
|
84
135
|
let intervalMs = (start.intervalSeconds || 5) * 1000;
|
|
85
136
|
const deadline = Date.now() + start.expiresInSeconds * 1000;
|
|
86
137
|
while (Date.now() < deadline) {
|
package/dist/commands/bind.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadConfig, patchConfig } from "../config.js";
|
|
|
5
5
|
import { installMarker, printMarkerBlock } from "./mcp.js";
|
|
6
6
|
import { readLocalBinding, resolveBinding } from "../lib/binding.js";
|
|
7
7
|
import { getBinding, setBinding, newWorkspaceId } from "../lib/keystore.js";
|
|
8
|
-
import { resolveLauncher, launcherNote, runsOk, selfCommand } from "../lib/launcher.js";
|
|
8
|
+
import { resolveLauncher, launcherNote, runsOk, selfCommand, versionStamp } from "../lib/launcher.js";
|
|
9
9
|
import { ask, confirm, isInteractive } from "../lib/prompt.js";
|
|
10
10
|
import { clean } from "../lib/text.js";
|
|
11
11
|
import { card, DOT } from "../lib/card.js";
|
|
@@ -446,7 +446,7 @@ export async function completeWorkspaceSetup(args) {
|
|
|
446
446
|
// AFTER the confirmation, and after everything that can throw, so it is only ever
|
|
447
447
|
// printed by a run that actually finished. Nothing below it can fail.
|
|
448
448
|
if (isInteractive())
|
|
449
|
-
console.log(`\n${NEXT_STEP}\n\n${whatNow(pfx, emptyProject)}`);
|
|
449
|
+
console.log(`\n${NEXT_STEP}\n\n${whatNow(pfx, emptyProject)}\n`);
|
|
450
450
|
}
|
|
451
451
|
/**
|
|
452
452
|
* The two sentences under `NEXT_STEP`, chosen by whether there is anything to pull yet
|
|
@@ -477,12 +477,15 @@ export function projectIsEmpty(p) {
|
|
|
477
477
|
return p.counter === 0;
|
|
478
478
|
}
|
|
479
479
|
export function whatNow(prefix, empty) {
|
|
480
|
+
// FLUSH LEFT (RTSC-676). Indented, these read as subordinate to the line above them
|
|
481
|
+
// when they are in fact continuing it — the eye takes the block for a nested detail
|
|
482
|
+
// rather than the instruction it is.
|
|
480
483
|
return empty
|
|
481
|
-
?
|
|
482
|
-
`
|
|
483
|
-
`
|
|
484
|
-
: `
|
|
485
|
-
`
|
|
484
|
+
? `${prefix} is empty, so start by talking with the agent. Tell it what you're\n` +
|
|
485
|
+
`building and it will file the work as it goes.\n\n` +
|
|
486
|
+
`Once there's a backlog, saying "next issue" is how you pull from it.`
|
|
487
|
+
: `There's already work in ${prefix}. Say "next issue" and your agent takes\n` +
|
|
488
|
+
`the top unblocked one and starts.`;
|
|
486
489
|
}
|
|
487
490
|
export async function setupFromToken(token, opts, deps = {}) {
|
|
488
491
|
const cfg = loadConfig();
|
|
@@ -560,7 +563,11 @@ export async function ensureSignedIn() {
|
|
|
560
563
|
if (loadConfig().token)
|
|
561
564
|
return;
|
|
562
565
|
if (!isInteractive()) {
|
|
563
|
-
|
|
566
|
+
// RTSC-672 — the stamp rides in the HINT, not the message. `formatError` keeps only
|
|
567
|
+
// the first line of a message (deliberately, to strip stack noise), so a second line
|
|
568
|
+
// here would be silently dropped — which is how a diagnostic aimed at silent failures
|
|
569
|
+
// would itself fail silently.
|
|
570
|
+
cliError("UNAUTHENTICATED", `Not signed in. Run \`${selfCommand(VERSION)} login\` first (no TTY here for the device flow).`, versionStamp(VERSION).trim());
|
|
564
571
|
}
|
|
565
572
|
// RTSC-508: don't name a provider here. `deviceLogin` asks which door, and
|
|
566
573
|
// announcing "GitHub" before the question would be wrong for the invited
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { VERSION } from "./version.js";
|
|
4
|
-
import { selfCommand } from "./lib/launcher.js";
|
|
4
|
+
import { selfCommand, versionStamp } from "./lib/launcher.js";
|
|
5
5
|
import { loadConfig, patchConfig, saveConfig, configPath, isLoggedIn } from "./config.js";
|
|
6
6
|
import { installMcp, normalizeScope } from "./commands/mcp.js";
|
|
7
7
|
import { installGate, resolveGatePrefix } from "./commands/gate.js";
|
|
@@ -27,9 +27,13 @@ program
|
|
|
27
27
|
function requireLogin() {
|
|
28
28
|
if (!isLoggedIn()) {
|
|
29
29
|
// RTSC-671 — `selfCommand` renders the form they actually used. The docs send a
|
|
30
|
-
// new owner through `npx @retasc/cli`, which never puts `retasc` on PATH, so naming
|
|
30
|
+
// new owner through `npx @retasc/cli@latest`, which never puts `retasc` on PATH, so naming
|
|
31
31
|
// it here answers a failure with a second `command not found`.
|
|
32
32
|
console.error(`Not signed in. Run \`${selfCommand(VERSION)} login\` first.`);
|
|
33
|
+
// RTSC-672 — say which build spoke. A stale CLI and a broken one produce the same
|
|
34
|
+
// sentence otherwise, and the people most likely to re-run this are the ones who
|
|
35
|
+
// were just told a fix shipped.
|
|
36
|
+
console.error(versionStamp(VERSION));
|
|
33
37
|
process.exit(1);
|
|
34
38
|
}
|
|
35
39
|
}
|
|
@@ -403,7 +407,7 @@ members
|
|
|
403
407
|
// them in, binds the folder and wires their agent, so this must not tell them to run
|
|
404
408
|
// `retasc login` first — nor name a `retasc` binary they don't have yet.
|
|
405
409
|
console.log(` Share it. In the folder their agent works in, they run:`);
|
|
406
|
-
console.log(` npx @retasc/cli join ${res.code}`);
|
|
410
|
+
console.log(` npx @retasc/cli@latest join ${res.code}`);
|
|
407
411
|
}
|
|
408
412
|
catch (e) {
|
|
409
413
|
fail(e);
|
package/dist/lib/launcher.js
CHANGED
|
@@ -96,6 +96,25 @@ export function portableLauncher(r, version) {
|
|
|
96
96
|
export function selfCommand(version, argv1 = process.argv[1] ?? "") {
|
|
97
97
|
return /[\\/]_npx[\\/]/.test(argv1) ? `npx -y ${PKG}@${version}` : "retasc";
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* A one-line stamp of which build just spoke (RTSC-672).
|
|
101
|
+
*
|
|
102
|
+
* A failure report is only diagnosable if it says what produced it. After 1.26.0 fixed
|
|
103
|
+
* sign-in, a container kept printing the 1.25.0 message and the only way to learn why was
|
|
104
|
+
* a round trip asking someone to run `--version` — because nothing in the output said.
|
|
105
|
+
* Worse, the population most likely to re-run a command is the population that hit a bug
|
|
106
|
+
* and was told it is fixed, which is exactly the population liable to be holding a stale
|
|
107
|
+
* build. "It still doesn't work" and "you are running last week's CLI" look identical
|
|
108
|
+
* without this.
|
|
109
|
+
*
|
|
110
|
+
* The EXACT version, not `@latest`: this names the code that emitted the message, which
|
|
111
|
+
* is a different question from how to fetch the current one. `npx` in the prefix when
|
|
112
|
+
* that is how they started, so the line doubles as a runnable command.
|
|
113
|
+
*/
|
|
114
|
+
export function versionStamp(version, argv1 = process.argv[1] ?? "") {
|
|
115
|
+
const how = /[\\/]_npx[\\/]/.test(argv1) ? `npx ${PKG}@${version}` : `${PKG} ${version}`;
|
|
116
|
+
return ` (${how})`;
|
|
117
|
+
}
|
|
99
118
|
/** Install (or upgrade to) an exact version globally. Returns null on success. */
|
|
100
119
|
function installGlobal(version) {
|
|
101
120
|
// A cold global install takes seconds with no output of its own. Silence here reads as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retasc/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"description": "Retasc CLI \u2014 the issue tracker AI agents pull work from. Sign in with GitHub or Google, create projects, mint agent API keys, and wire your agent to the Retasc MCP server in one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|