@retasc/cli 1.38.1 → 1.38.2
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 +16 -0
- package/dist/commands/bind.js +92 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ 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.38.2 (2026-08-30)
|
|
10
|
+
|
|
11
|
+
- **RTSC-789** — `bind` keeps this folder's key instead of minting a new one on every run.
|
|
12
|
+
The guard meant to prevent that (RTSC-262) read the `--org-id`/`--project-id` flags, so it
|
|
13
|
+
only ever fired for a provisioning script. The canonical `npx @retasc/cli@latest bind`
|
|
14
|
+
leaves both undefined and could never reach it: pick the same org and the same project
|
|
15
|
+
from the menu and it minted anyway, leaving the old key live. The check now runs where
|
|
16
|
+
the target is known, after the pick, so how you got there stops mattering. A different
|
|
17
|
+
org or project still mints, as it must.
|
|
18
|
+
A kept key is resolved against the server before it is kept. `bind` clears a binding only
|
|
19
|
+
when the server answers `UNAUTHORIZED`, so a folder can reach the reuse branch holding a
|
|
20
|
+
key whose health is unknown: a 5xx, a proxy sign-in page, a refusal whose wording drifts.
|
|
21
|
+
Keeping one of those would have stranded the folder for good, since `doctor` sends that
|
|
22
|
+
exact state back to `bind`. It falls back to minting instead, which is the self-healing
|
|
23
|
+
the old mint-every-run was providing by accident.
|
|
24
|
+
|
|
9
25
|
## 1.38.1 (2026-08-28)
|
|
10
26
|
|
|
11
27
|
- **RTSC-780 follow-up** — `retasc init` now sets a folder up the same way every other
|
package/dist/commands/bind.js
CHANGED
|
@@ -99,6 +99,55 @@ export function strandRecoveryHint(args) {
|
|
|
99
99
|
`but has no project or key yet — re-run to finish:\n` +
|
|
100
100
|
` retasc bind --org-id ${orgId}`);
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The key this folder should KEEP rather than replace (RTSC-789).
|
|
104
|
+
*
|
|
105
|
+
* `bind` used to mint on every run. The guard that was supposed to stop that
|
|
106
|
+
* (RTSC-262) sat in `preflight` and read `args.orgId && args.projectId`, so it
|
|
107
|
+
* only fired for `retasc bind --org-id X --project-id Y` — the provisioning-script
|
|
108
|
+
* path it was written for. The canonical command is a bare `bind`, which leaves
|
|
109
|
+
* both undefined, so the branch was unreachable for almost every real run: pick
|
|
110
|
+
* the same org and the same project from the menu and it minted anyway. Fourteen
|
|
111
|
+
* keys in one org, three of them the same agent on the same project, and the old
|
|
112
|
+
* ones stay live — two were carrying 89 and 283 sessions.
|
|
113
|
+
*
|
|
114
|
+
* So the check moves to where the target is actually KNOWN, after the interactive
|
|
115
|
+
* pick, and asks the only question that matters: does this folder already hold a
|
|
116
|
+
* key for this exact (org, project)? Flags are irrelevant to that.
|
|
117
|
+
*
|
|
118
|
+
* A DIFFERENT org or project still mints.
|
|
119
|
+
*
|
|
120
|
+
* This names a CANDIDATE, not a decision. It reads the keystore and nothing else,
|
|
121
|
+
* so it cannot know whether the key still works — and "`preflight` already cleared
|
|
122
|
+
* anything the server refused" is not true enough to lean on: that clearing fires
|
|
123
|
+
* only on a literal UNAUTHORIZED, and every other failure walks straight past it.
|
|
124
|
+
* The caller resolves the candidate before keeping it.
|
|
125
|
+
*/
|
|
126
|
+
export function reusableKey(existing, target, read = getBinding) {
|
|
127
|
+
if (!existing?.workspaceId)
|
|
128
|
+
return null;
|
|
129
|
+
const cur = read(existing.workspaceId);
|
|
130
|
+
if (!cur?.key)
|
|
131
|
+
return null;
|
|
132
|
+
return cur.orgId === target.orgId && cur.projectId === target.projectId ? cur.key : null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The binding as the AGENT will see it, or null when the server won't confirm it.
|
|
136
|
+
*
|
|
137
|
+
* One place, because two callers need the same answer for different reasons: the
|
|
138
|
+
* receipt prints it, and the reuse decision above depends on it. Every failure is
|
|
139
|
+
* the same answer here — "the server did not confirm this key" — so the caller
|
|
140
|
+
* decides what that means rather than parsing an error a second time.
|
|
141
|
+
*/
|
|
142
|
+
async function describeBinding(url, key) {
|
|
143
|
+
try {
|
|
144
|
+
const b = await resolveBinding(url, key);
|
|
145
|
+
return { org: clean(b.org.name), prefix: clean(b.project.prefix), name: b.project.name };
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
102
151
|
/**
|
|
103
152
|
* "This folder is already bound — replace it?" (RTSC-262/RTSC-91)
|
|
104
153
|
*
|
|
@@ -423,14 +472,37 @@ export async function completeWorkspaceSetup(args) {
|
|
|
423
472
|
const note = launcherNote(launcher);
|
|
424
473
|
if (note)
|
|
425
474
|
console.log(note);
|
|
426
|
-
// ---
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
475
|
+
// --- keep this folder's key if it already names this (org, project), else mint
|
|
476
|
+
//
|
|
477
|
+
// A key is only kept once the SERVER agrees it works. `preflight` clears a binding
|
|
478
|
+
// only on a literal UNAUTHORIZED, so a folder reaches here holding a key of unknown
|
|
479
|
+
// health whenever that check failed some other way — a 5xx, a proxy page, a refusal
|
|
480
|
+
// whose wording drifts off the regex. Keeping such a key silently would strand the
|
|
481
|
+
// folder permanently: `doctor` tells people in exactly that state to re-run `bind`,
|
|
482
|
+
// and `bind` is this path, so it would answer "✓ bound" forever and never heal. The
|
|
483
|
+
// mint-on-every-run this issue removes was doing that healing by accident; it stays,
|
|
484
|
+
// on purpose, as the fallback.
|
|
485
|
+
//
|
|
486
|
+
// The check is not extra work — it IS the receipt's confirmation call, hoisted, and
|
|
487
|
+
// `bound` carries the answer down to the card. The mint path resolves once below,
|
|
488
|
+
// exactly as before.
|
|
489
|
+
const candidate = reusableKey(existing, { orgId, projectId: projectId });
|
|
490
|
+
let bound = candidate ? await describeBinding(cfg.mcpUrl, candidate) : null;
|
|
491
|
+
// A key that answers for a DIFFERENT project is not this folder's key, whatever the
|
|
492
|
+
// keystore says its ids were. Compared only when the target's prefix is known (a
|
|
493
|
+
// bare --project-id never listed one), because a guess would reject good keys.
|
|
494
|
+
if (bound && prefix && bound.prefix !== clean(prefix))
|
|
495
|
+
bound = null;
|
|
496
|
+
const reused = bound !== null;
|
|
497
|
+
const minted = reused
|
|
498
|
+
? { key: candidate }
|
|
499
|
+
: (await api.mintKey({
|
|
500
|
+
orgId,
|
|
501
|
+
projectId: projectId,
|
|
502
|
+
agentName: opts.agent,
|
|
503
|
+
runtime: opts.runtime ?? "claude-code",
|
|
504
|
+
keyName: prefix ? `${prefix} key` : undefined,
|
|
505
|
+
}));
|
|
434
506
|
// The key is named in the receipt card below, not here — one mention, in the place
|
|
435
507
|
// that says where it went (RTSC-673).
|
|
436
508
|
// RTSC-92: the secret stays OUT of the repo. Store it in the home keystore
|
|
@@ -460,21 +532,24 @@ export async function completeWorkspaceSetup(args) {
|
|
|
460
532
|
// (RTSC-673). Resolved rather than echoed from what we just sent: this is the last
|
|
461
533
|
// chance to notice a folder bound to something other than what was asked for, and a
|
|
462
534
|
// card built from our own inputs could never show that.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
535
|
+
//
|
|
536
|
+
// A reused key was already resolved above, and that answer is the one that decided to
|
|
537
|
+
// keep it — so only a freshly minted key is left to confirm. Still best-effort here:
|
|
538
|
+
// the binding is written and a key we just minted is live by construction, so a
|
|
539
|
+
// network fault at this point is about the confirmation, not about the key.
|
|
540
|
+
if (!bound)
|
|
541
|
+
bound = await describeBinding(cfg.mcpUrl, minted.key);
|
|
471
542
|
const orgLabel = bound?.org ?? clean(args.orgLabel ?? "");
|
|
472
543
|
const pfx = bound?.prefix ?? clean(prefix ?? "");
|
|
473
544
|
console.log("\n" +
|
|
474
545
|
card(`✓ This folder is bound to ${orgLabel} / ${pfx}`, [
|
|
475
546
|
{ label: "Org", value: orgLabel },
|
|
476
547
|
{ label: "Project", value: bound?.name ? `${pfx} ${DOT} ${bound.name}` : pfx },
|
|
477
|
-
{
|
|
548
|
+
{
|
|
549
|
+
label: "Agent key",
|
|
550
|
+
value: `${minted.key.slice(0, 14)}…`,
|
|
551
|
+
note: reused ? "kept, not replaced" : "never leaves ~/.retasc",
|
|
552
|
+
},
|
|
478
553
|
{ label: "MCP wired", value: marker.where, note: "secret-free, safe to commit" },
|
|
479
554
|
...(wired.wired.length
|
|
480
555
|
? [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retasc/cli",
|
|
3
|
-
"version": "1.38.
|
|
3
|
+
"version": "1.38.2",
|
|
4
4
|
"description": "Retasc CLI — 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": {
|