@retasc/cli 1.12.0 → 1.13.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 +522 -0
- package/README.md +7 -2
- package/dist/index.js +23 -10
- package/dist/lib/format.js +157 -0
- package/package.json +6 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@retasc/cli` are recorded here, newest first. Every version
|
|
4
|
+
published to npm has an entry; the ones predating this file were reconstructed from the
|
|
5
|
+
release commits and the issues they reference.
|
|
6
|
+
|
|
7
|
+
Dates are the npm publish date. Each entry names the RTSC issue behind it.
|
|
8
|
+
|
|
9
|
+
## 1.13.1 (2026-08-02)
|
|
10
|
+
|
|
11
|
+
- **RTSC-522** — the changelog is public, and five releases that were never written down now
|
|
12
|
+
are.
|
|
13
|
+
|
|
14
|
+
It used to be unreachable by anyone who installs the CLI: it did not ship in the tarball,
|
|
15
|
+
the README never mentioned it, and the source repo is private so the npm repository link
|
|
16
|
+
404s for the public. It is now at
|
|
17
|
+
[docs.retasc.com/changelog](https://docs.retasc.com/changelog), and ships in the package.
|
|
18
|
+
|
|
19
|
+
1.1.0, 1.1.1, 1.1.3, 1.2.1 and 1.2.2 were published and never described. They are
|
|
20
|
+
reconstructed here from their release commits and the issues they reference, so every
|
|
21
|
+
version on npm now has an entry. Each one carries its publish date.
|
|
22
|
+
|
|
23
|
+
Also fixed two npm-facing details: the package description still said "sign in with
|
|
24
|
+
GitHub", which stopped being the whole truth at 1.11.0, and the bug-report link pointed at
|
|
25
|
+
a private repository that 404s for everyone npm showed it to.
|
|
26
|
+
|
|
27
|
+
## 1.13.0 (2026-08-02)
|
|
28
|
+
|
|
29
|
+
- **RTSC-521** — five commands stopped answering you with a JSON dump.
|
|
30
|
+
|
|
31
|
+
`whoami`, `org create`, `project create`, `key list` and `members list` printed the raw
|
|
32
|
+
payload. `whoami` is the one people actually noticed, because it is the command you run to
|
|
33
|
+
find out who you are:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
{
|
|
37
|
+
"orgs": [],
|
|
38
|
+
"user": {
|
|
39
|
+
"email": "you@example.com",
|
|
40
|
+
"id": "mh7edhnpet55dqr9m48yked29s8bqr9j",
|
|
41
|
+
"name": "Your Name"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The one fact you wanted was a bracket to parse, next to a document id that means nothing.
|
|
47
|
+
Now:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Signed in Your Name <you@example.com>
|
|
51
|
+
Orgs none
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`key list` and `members list` are aligned tables, and state reads as words: a revoked key
|
|
55
|
+
says `revoked` rather than an epoch, and a spent invite shows no expiry date because the
|
|
56
|
+
date stopped meaning anything. Auto-minted session keys are folded into a count instead of
|
|
57
|
+
drowning the keys you can actually manage.
|
|
58
|
+
|
|
59
|
+
Ids follow one rule: shown when a command takes them, dropped when nothing does. So
|
|
60
|
+
`org create` gives you the org id and names the command that wants it, and never mentions
|
|
61
|
+
`ownerMemberId`.
|
|
62
|
+
|
|
63
|
+
**`--json` on all five, emitting exactly what they printed before.** Anything scripted
|
|
64
|
+
against the old output keeps working by adding the flag.
|
|
65
|
+
|
|
66
|
+
`whoami`'s binding block is unchanged. It was already the useful half.
|
|
67
|
+
|
|
68
|
+
## 1.12.0 (2026-08-02)
|
|
69
|
+
|
|
70
|
+
- **RTSC-519** — `retasc bind` now tells you it finished, and what to do next.
|
|
71
|
+
|
|
72
|
+
It used to end on the last thing it *did* ("This folder is bound to org Acme / project
|
|
73
|
+
ACM") and stop. So the person who set up their own org was left reading a status line and
|
|
74
|
+
guessing whether anything else was required, while the invited teammate, who arrives
|
|
75
|
+
through `retasc join`, was told. Both commands now end the same way:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Start your agent in this folder, or restart it if it's already open, and it'll pull from the queue.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The restart half matters: `.mcp.json` is read when an MCP client starts, so an agent
|
|
82
|
+
already open on that folder won't see Retasc until it restarts — and `bind` is routinely
|
|
83
|
+
run from inside a session that's already there.
|
|
84
|
+
|
|
85
|
+
The sentence lives in the tail both commands share, rather than a copy in each, so
|
|
86
|
+
neither can lose it again.
|
|
87
|
+
|
|
88
|
+
- **RTSC-507** — "none of these are me" now covers the tools you were asked about, not the
|
|
89
|
+
whole org forever.
|
|
90
|
+
|
|
91
|
+
Declining wrote one org-wide flag, checked before the list of imported people was even
|
|
92
|
+
read. So the January answer "I was never in ClickUp" — correct, and the cheapest possible
|
|
93
|
+
answer to give — meant that when the org migrated off Jira in June, where you *did* have
|
|
94
|
+
an account, you were never offered your placeholder. Not by `retasc identity`, not by
|
|
95
|
+
`retasc join`, not by the Dash, not ever. Your Jira authorship, comments and assignments
|
|
96
|
+
stayed on a placeholder permanently, and only an owner could repair it.
|
|
97
|
+
|
|
98
|
+
Claiming was already tracked per source, so someone who *claimed* their ClickUp identity
|
|
99
|
+
was correctly re-asked at every later migration. Declining being the more final of the two
|
|
100
|
+
was backwards.
|
|
101
|
+
|
|
102
|
+
The decline row now names what it covers:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
3) None of these are me (covers ClickUp and Jira only)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
and `retasc identity` says so when nothing is left:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
You've answered about every tool imported into Acme so far.
|
|
112
|
+
Run this again after the next migration, each one is asked about separately.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
That replaces wording saying the answer "covers the whole org, including later
|
|
116
|
+
migrations" and pointing at an owner. It existed only to describe this bug.
|
|
117
|
+
|
|
118
|
+
**Needs the matching backend deploy.** The copy above is a promise the server keeps, so
|
|
119
|
+
publish this only after `convex deploy` and `migrations:backfillDismissedSources` have
|
|
120
|
+
run. Against an older backend the CLI would say a later import will ask again, and it
|
|
121
|
+
would not.
|
|
122
|
+
|
|
123
|
+
## 1.11.0 (2026-08-02)
|
|
124
|
+
|
|
125
|
+
- **RTSC-508** — you can now sign in with **Google**, not only GitHub.
|
|
126
|
+
|
|
127
|
+
Google sign-in shipped for the web Dash first, which left a one-way street: an account
|
|
128
|
+
created through that door reached the Dash and nothing else, because every login-gated
|
|
129
|
+
command here sat behind a GitHub-only device flow. `retasc login` would start a GitHub
|
|
130
|
+
device flow for an account GitHub had never heard of. That blocked `bind`, `init`, `key`,
|
|
131
|
+
`members`, `billing` and `identity` outright — including the sign-in step *inside*
|
|
132
|
+
`retasc bind` and `retasc join`, so a teammate invited through the web could not finish
|
|
133
|
+
setting up their folder at all.
|
|
134
|
+
|
|
135
|
+
With no flag, `login` now asks which door you use:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
How do you sign in to Retasc?
|
|
139
|
+
1) GitHub
|
|
140
|
+
2) Google
|
|
141
|
+
Choose a number:
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
After a successful sign-in the door is remembered, so later logins offer it back and
|
|
145
|
+
Enter takes it:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
How do you sign in to Retasc?
|
|
149
|
+
1) GitHub
|
|
150
|
+
2) Google (last used)
|
|
151
|
+
Choose a number [2]:
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A default only ever appears once it is a recorded fact about you. On a first login there
|
|
155
|
+
is none, and Enter is not an answer — because the wrong door is not a mistake you correct
|
|
156
|
+
next time (see below). The remembered door is a default, not a lock: the other one is
|
|
157
|
+
still one keystroke, and a typo re-prompts rather than being read as "the usual".
|
|
158
|
+
|
|
159
|
+
`retasc login --google` / `--github` skip the question. A non-interactive run is
|
|
160
|
+
unchanged: no prompt, GitHub, exactly as before. The same question appears inside `bind`
|
|
161
|
+
and `join` when they sign you in for you.
|
|
162
|
+
|
|
163
|
+
**Signing in through the wrong door is not a typo you correct on the next run.** Google
|
|
164
|
+
and GitHub are deliberately separate identities — doors are linked only on a provider's
|
|
165
|
+
own immutable account id, never on a matching email address, because email is an
|
|
166
|
+
account-takeover vector. So a Google sign-in by someone who already has a GitHub account
|
|
167
|
+
here creates a *second* identity with its own membership, and the CLI cannot merge them
|
|
168
|
+
back. That is why the question is asked rather than guessed, and why a non-answer
|
|
169
|
+
re-prompts instead of falling through to a door.
|
|
170
|
+
|
|
171
|
+
Needs `AUTH_GOOGLE_DEVICE_ID` / `AUTH_GOOGLE_DEVICE_SECRET` on the deployment
|
|
172
|
+
(`operations/runbooks/google-device-client.md`). Where they aren't set, the CLI says so
|
|
173
|
+
in one line and names GitHub as the way in meanwhile, rather than failing opaquely.
|
|
174
|
+
|
|
175
|
+
## 1.10.0 (2026-08-01)
|
|
176
|
+
|
|
177
|
+
- **RTSC-477** — new command: `retasc identity`. It shows the people a migration carried
|
|
178
|
+
into your org and asks which one is you — the same question `retasc join` asks, on demand.
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
retasc identity # defaults to your only org
|
|
182
|
+
retasc identity --org-id … # when you're in several
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`join` asks this once, at the moment you accept an invite. Placeholders arrive with
|
|
186
|
+
*every* migration, though, and claiming is per source tool rather than per person: the
|
|
187
|
+
backend is built for "claim your ClickUp one now, your Jira one in six months". So a team
|
|
188
|
+
that onboarded by CLI a year ago had no terminal path to a later import's history at all,
|
|
189
|
+
and had to open the Dash or leave it on placeholders. Three narrower cases `join` skips on
|
|
190
|
+
purpose are covered too: `--yes`, a non-interactive run, and `--no-bind`.
|
|
191
|
+
|
|
192
|
+
It asks the question, so it needs a TTY and refuses without one rather than exiting 0 in
|
|
193
|
+
silence. There is deliberately no `--yes` and no `identity claim <name>`: linking someone
|
|
194
|
+
pulls their authorship *and* their dispatch lane onto your account, irreversibly, with no
|
|
195
|
+
CLI way back, so it is never answered on a script's behalf.
|
|
196
|
+
|
|
197
|
+
It also names the ending, which `join` does not. "Nothing waiting for you" and "you've
|
|
198
|
+
already said none of these are you" are the same empty list from the inside and mean
|
|
199
|
+
opposite things — the second is permanent and org-wide, so it still applies after a later
|
|
200
|
+
migration that really did carry you across. That case now says so, and points at the
|
|
201
|
+
owner, who is the only one who can undo it.
|
|
202
|
+
|
|
203
|
+
## 1.9.0 (2026-07-31)
|
|
204
|
+
|
|
205
|
+
**Behaviour change, read this if you script `join`:** `retasc join <code>` used to redeem an
|
|
206
|
+
invite and stop. It now also sets up the folder you run it in — mints a key, writes a
|
|
207
|
+
binding, wires the MCP marker. **`--no-bind` reproduces the old behaviour exactly**, same
|
|
208
|
+
output and same exit code. Every other command is unchanged.
|
|
209
|
+
|
|
210
|
+
`join` also no longer exits 1 telling you to run `retasc login` first. It starts the device
|
|
211
|
+
flow itself when there is no session, and refuses with the same message `bind` uses when
|
|
212
|
+
there is no TTY to run one in.
|
|
213
|
+
|
|
214
|
+
- **RTSC-492** — `retasc join` is now the whole of an invited teammate's setup, in one
|
|
215
|
+
command run from the folder their agent will work in:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
npx @retasc/cli join https://dash.retasc.com/join#rtscinv_…
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
It signs them in (device flow, only when there is no session), redeems the invite,
|
|
222
|
+
offers them any identity a migration carried across, picks the project, makes `retasc`
|
|
223
|
+
durable, mints a key, binds the folder and wires the MCP marker. It used to be three
|
|
224
|
+
commands with an ordering trap in them, and the first of those three was `retasc login`
|
|
225
|
+
— which is not something you can tell someone who has installed nothing yet.
|
|
226
|
+
|
|
227
|
+
The target case is that an invited teammate never opens the Dash at all. With no imports
|
|
228
|
+
and one project, the only thing they type is the GitHub device code: every prompt is
|
|
229
|
+
conditional on there being a real choice.
|
|
230
|
+
|
|
231
|
+
- Takes a full invite link or a bare `rtscinv_…` code. The code is what the server
|
|
232
|
+
matches, so unwrapping a paste belongs at the edge that accepts typed input, not in the
|
|
233
|
+
mutation that grants membership.
|
|
234
|
+
- A member is never offered a project they cannot create (`createProject` is owner-only),
|
|
235
|
+
and an org with no projects names what to ask an owner for instead of failing opaquely.
|
|
236
|
+
- Imported placeholders are offered once per source tool, each behind an explicit
|
|
237
|
+
confirm, in the same words the Dash uses. `--yes` skips that question and never answers
|
|
238
|
+
it: claiming another person's history is irreversible and has no CLI path back.
|
|
239
|
+
- `--no-bind` keeps the old redeem-only behaviour for anything scripted against it.
|
|
240
|
+
|
|
241
|
+
Under it, `bind` and `join` now share one implementation of the folder half — the project
|
|
242
|
+
pick, the launcher ladder, the mint, the binding and the marker — so the fix that landed
|
|
243
|
+
in 1.8.0 cannot drift back out of one of them.
|
|
244
|
+
|
|
245
|
+
- **RTSC-498** — `retasc doctor` now says which platform the CLI is actually tested
|
|
246
|
+
on. Nothing in the product stated one: the READMEs never mentioned platforms and
|
|
247
|
+
`package.json` declares only `engines.node`, so someone hitting a problem on Linux
|
|
248
|
+
or Windows had no way to tell their own mistake from a bug from a platform we never
|
|
249
|
+
targeted. macOS is the only platform we run end to end.
|
|
250
|
+
|
|
251
|
+
It's a note in `doctor` rather than a line in the README on purpose. A README is read
|
|
252
|
+
by everyone, and for the majority on macOS a standing disclaimer about platforms they
|
|
253
|
+
aren't using is noise they read before they've even installed. `doctor` already knows
|
|
254
|
+
which machine it's on, so the people the limit applies to are exactly the people who
|
|
255
|
+
see it, and on macOS nothing is printed at all.
|
|
256
|
+
|
|
257
|
+
The note claims a limit, not a blockage: much of the CLI is plain Node and should work
|
|
258
|
+
fine, and `package.json` still carries no `os` field, so installing is never refused
|
|
259
|
+
anywhere Node 18+ runs. Windows and Linux are named as platforms we intend to support
|
|
260
|
+
properly; anything else is told it's untested without being promised a roadmap it
|
|
261
|
+
isn't on.
|
|
262
|
+
|
|
263
|
+
## 1.8.0 (2026-07-31)
|
|
264
|
+
|
|
265
|
+
- **RTSC-493** — the MCP marker no longer names a command that may not exist. It
|
|
266
|
+
used to hardcode `retasc`, which is only on PATH after a global install — but the
|
|
267
|
+
Dash tells people to run `npx @retasc/cli bind`, and npx unpacks into its own cache
|
|
268
|
+
and installs nothing. So a bind printed every success line, really did bind the
|
|
269
|
+
folder, and left an agent that could not start the Retasc MCP server at all. The
|
|
270
|
+
failure surfaced later, somewhere else, as a broken server rather than a failed setup.
|
|
271
|
+
|
|
272
|
+
`bind` now resolves a launcher and **proves it by running it** before writing
|
|
273
|
+
anything: it uses an existing `retasc` if there is one, otherwise installs the CLI
|
|
274
|
+
globally, and if that install lands where PATH cannot see it, the marker names the
|
|
275
|
+
binary's absolute path instead. Only when none of that works does it fall back to a
|
|
276
|
+
version-pinned `npx`, which is slower and needs the network — and if even that will
|
|
277
|
+
not start, it says so outright rather than reporting success.
|
|
278
|
+
|
|
279
|
+
`retasc doctor` now checks the same thing, so a folder bound by an older version
|
|
280
|
+
reports why its agent cannot start and names the repair.
|
|
281
|
+
|
|
282
|
+
## 1.7.2 (2026-07-21)
|
|
283
|
+
|
|
284
|
+
- **RTSC-297** — `retasc bind` no longer strands a new org silently. It creates
|
|
285
|
+
the org server-side before the project picker runs, so aborting at the project
|
|
286
|
+
step (reachable by a typo since 1.x's picker gives up after 3 bad answers) left
|
|
287
|
+
an org behind with no project, no key, and no local binding — invisible from
|
|
288
|
+
the CLI and quietly accruing on the billing rail. Bind now names the org and
|
|
289
|
+
prints the exact resume command (`retasc bind --org-id <id>`) when it aborts
|
|
290
|
+
before the workspace is bound. Selecting a pre-existing org and then aborting
|
|
291
|
+
is untouched — nothing is reclaimed.
|
|
292
|
+
|
|
293
|
+
## 1.7.1 (2026-07-21)
|
|
294
|
+
|
|
295
|
+
- **RTSC-321** — the liveness watchdog now stops heartbeating an issue the moment
|
|
296
|
+
it moves to `review` (RTSC-257): the server releases the lease there, so the
|
|
297
|
+
proxy no longer pings a dead lease until the next heartbeat fails. It is treated
|
|
298
|
+
as a release, not a close — the branch/worktree is preserved for the review + PR
|
|
299
|
+
(never reaped). The reviewer's send-back (`review → todo`) still self-heals via
|
|
300
|
+
the failed-heartbeat path, since it can't be told apart from a `doing → todo`
|
|
301
|
+
edit by request args alone.
|
|
302
|
+
|
|
303
|
+
## 1.7.0 (2026-07-21)
|
|
304
|
+
|
|
305
|
+
- **RTSC-306** — `retasc claim` / `retasc next` now pull **lane-scoped by
|
|
306
|
+
default**: only issues assigned to your principal or left unassigned, never
|
|
307
|
+
another human's work. The old `--mine` flag is retired — that behavior is the
|
|
308
|
+
default now. The new `--all-lanes` flag opts back into the whole ready pool
|
|
309
|
+
when you need to pick up an absent teammate's work. When your lane is empty but
|
|
310
|
+
ready work is stranded in another lane, the CLI says so and points you at
|
|
311
|
+
`--all-lanes` instead of reading as "nothing to do".
|
|
312
|
+
|
|
313
|
+
## 1.6.3 (2026-07-20)
|
|
314
|
+
|
|
315
|
+
- **RTSC-266** — README now links the setup page, the docs quickstart, and the
|
|
316
|
+
parallel-agents guide instead of only the bare homepage. npm package pages are
|
|
317
|
+
a real entry point and only re-render on publish, so this ships as its own
|
|
318
|
+
patch release. No code changes.
|
|
319
|
+
|
|
320
|
+
## 1.6.2 (2026-07-20)
|
|
321
|
+
|
|
322
|
+
- **RTSC-269** — `retasc bind`'s org/project picker no longer treats a typo as
|
|
323
|
+
"create new". Answering `1)` (a fumbled `1`) used to select nothing and fall
|
|
324
|
+
straight through to **creating a new org/project**, which then hit the owner
|
|
325
|
+
gate and surfaced as a redacted Server Error. Invalid input now re-prompts;
|
|
326
|
+
only the explicit trailing option creates. The answer must be plain digits, so
|
|
327
|
+
the non-canonical forms `Number()` happily parses (`0x4`, `4e0`, `+4`, `4.0`)
|
|
328
|
+
can't reach the create branch either.
|
|
329
|
+
- **RTSC-269** — the picker gives up after 3 invalid answers instead of looping,
|
|
330
|
+
and a closed stdin (Ctrl-D, or a pipe running dry) now exits with an error
|
|
331
|
+
rather than hanging. `readline.question()` never settles at EOF, so any prompt
|
|
332
|
+
in `bind` could previously wedge the CLI with no output and no exit.
|
|
333
|
+
|
|
334
|
+
## 1.6.1 (2026-07-20)
|
|
335
|
+
|
|
336
|
+
- **RTSC-263** — owner/member permission denials now print a readable reason and the
|
|
337
|
+
next step instead of an opaque `Server Error`. Hitting an owner-only command as a
|
|
338
|
+
member reports "Owner role required." with "Ask <owner> to create a project." on its
|
|
339
|
+
own line. The backend change does the work; the CLI already read the structured
|
|
340
|
+
payload (1.5.0), so this release carries only the hardening below.
|
|
341
|
+
- **RTSC-263** — `formatError` now strips U+0085, U+2028 and U+2029 in addition to the
|
|
342
|
+
ASCII control characters. All three are mandatory line breaks, so a server message
|
|
343
|
+
interpolating a user-controlled name could previously forge extra lines in terminal
|
|
344
|
+
output.
|
|
345
|
+
|
|
346
|
+
## 1.6.0 (2026-07-20)
|
|
347
|
+
|
|
348
|
+
- **RTSC-262** — `doctor` and `whoami` no longer report "not bound" for a folder whose
|
|
349
|
+
Retasc MCP is registered in **Claude Code local scope** (the `claude mcp add` default —
|
|
350
|
+
stored in Claude's own config, not `./.mcp.json`). The binding lookup now reads both
|
|
351
|
+
legal per-folder locations, and when both exist the **local-scope entry wins**, matching
|
|
352
|
+
Claude Code's documented precedence (local > project) — doctor names the org the agent
|
|
353
|
+
*actually* uses, and warns when a differing `./.mcp.json` marker is being shadowed.
|
|
354
|
+
- **RTSC-262** — `claim`/`tidy`/`done` resolve the key through the same entry selection,
|
|
355
|
+
so the CLI can no longer say "✓ bound" in one command and "No Retasc MCP key found" in
|
|
356
|
+
another for the same folder.
|
|
357
|
+
- **RTSC-262** — the illegal-global-server check is now **presence-based**: a hand-added
|
|
358
|
+
user-scope entry is detected in every shape (including keyless), and an unreadable
|
|
359
|
+
`~/.claude.json` reports "cannot verify" instead of a false all-clear.
|
|
360
|
+
- **RTSC-262** — honest failure modes: server-unreachable (timeouts, 5xx, rate limits) is
|
|
361
|
+
no longer reported as a rejected key, `whoami`/`doctor` requests are bounded by a 10s
|
|
362
|
+
timeout, and server-derived strings are stripped of control characters before printing.
|
|
363
|
+
- **RTSC-262** — `bind` hardening: re-binding to the *same* org/project is an idempotent
|
|
364
|
+
success (no key churn for provisioning scripts); replacing a *different* binding
|
|
365
|
+
non-interactively without `--yes` now fails loudly with exit 1 instead of silently
|
|
366
|
+
succeeding as a no-op; a cloned repo's committed marker no longer triggers the
|
|
367
|
+
replace prompt (minting your own key under it is what `bind` is for).
|
|
368
|
+
- **RTSC-262** — (security) a keystore-resolved key now only ever travels to the
|
|
369
|
+
keystore's own URL — a hostile `RETASC_MCP_URL` planted next to a committed secret-free
|
|
370
|
+
marker can no longer redirect a teammate's real key.
|
|
371
|
+
|
|
372
|
+
## 1.5.0 (2026-07-20)
|
|
373
|
+
|
|
374
|
+
- **RTSC-261** — backend failures you're meant to act on now print what actually went
|
|
375
|
+
wrong instead of `✗ Server Error`. Convex masks any plain server throw in production, so
|
|
376
|
+
every precondition — a consumed invite code, an expired one, a suspended membership —
|
|
377
|
+
reached the terminal as the same opaque string. `retasc join` was the worst case: an
|
|
378
|
+
invited teammate hit an unreadable wall at the very first step.
|
|
379
|
+
|
|
380
|
+
Errors now carry a machine-readable `code`, a readable `message`, and an optional `hint`
|
|
381
|
+
printed on its own line:
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
✗ CONSUMED: This invite was already used.
|
|
385
|
+
→ ask an owner for a fresh invite link
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
This release covers invite redemption; the remaining surfaces (auth/key problems,
|
|
389
|
+
not-found lookups, billing gating, import/restore) follow. Internal invariants stay
|
|
390
|
+
masked deliberately — those are our bugs, not yours, and their messages describe server
|
|
391
|
+
state. **Upgrade before your teammates redeem invites**: an older CLI can't read the new
|
|
392
|
+
payload and prints it as raw JSON.
|
|
393
|
+
|
|
394
|
+
## 1.4.0 (2026-07-19)
|
|
395
|
+
|
|
396
|
+
- **RTSC-279** — new `retasc billing` command: the org's whole billing picture in the
|
|
397
|
+
terminal instead of only in the Dash. Prints the subscription and spending caps, what's
|
|
398
|
+
owed right now, and the charge + confirmed on-chain payment history — both **org-scoped
|
|
399
|
+
across every payment link the org has ever used**, so changing payment link no longer
|
|
400
|
+
hides prior history. Owner-only (it reuses the same owner-gated functions the Dash
|
|
401
|
+
renders; the client-side role check is UX, the server still gates). `--org-id` selects the
|
|
402
|
+
org when you belong to more than one, `--json` emits the raw payload. Payments are a live
|
|
403
|
+
read of Xenarch's authoritative records and degrade to a note rather than failing the
|
|
404
|
+
command.
|
|
405
|
+
- **RTSC-279** — (server-side, no CLI change) the MCP usage meter now reports **two**
|
|
406
|
+
clearly-named figures instead of one misleading `wouldBeBillUsd`:
|
|
407
|
+
`lifetimeMeteredEstimateUsd` (all-time activity repriced at today's rate card — a
|
|
408
|
+
diagnostic, not an amount owed) and `pendingUsd` (what is actually owed right now, the
|
|
409
|
+
same figure the Dash shows). A new `billing_summary` MCP tool exposes the full billing
|
|
410
|
+
detail to agents whose human principal owns the org.
|
|
411
|
+
|
|
412
|
+
## 1.3.2 (2026-07-17)
|
|
413
|
+
|
|
414
|
+
- **RTSC-250** — serialize `patchConfig` under a cross-process advisory lock so
|
|
415
|
+
concurrent CLI/MCP processes can't lose an update. Previously a lock-free
|
|
416
|
+
load-modify-save meant a `defaultOrgId`/`defaultProjectPrefix` write racing the
|
|
417
|
+
hourly token refresh could write back the old single-use `refreshToken`,
|
|
418
|
+
breaking the next refresh and forcing a device re-login a non-interactive
|
|
419
|
+
context can't perform. The lock re-reads the freshest config inside the
|
|
420
|
+
critical section and stamps a nonce so a stolen-from holder never deletes the
|
|
421
|
+
new owner's lock. Also sweeps orphaned `.config.json.<uuid>.tmp` files left by a
|
|
422
|
+
hard kill, age-gated so a concurrent writer's in-flight temp is never touched.
|
|
423
|
+
|
|
424
|
+
## 1.3.1 (2026-07-17)
|
|
425
|
+
|
|
426
|
+
- **RTSC-179** — atomic config write (temp file + rename) so a reader never sees
|
|
427
|
+
a truncated `config.json`; adds `RETASC_DIR` support.
|
|
428
|
+
- **RTSC-178** — token refresh now classifies transient backend errors vs a
|
|
429
|
+
genuinely expired refresh token, so a one-off backend blip no longer drags a
|
|
430
|
+
valid session through a full device re-login.
|
|
431
|
+
|
|
432
|
+
## 1.3.0 (2026-07-08)
|
|
433
|
+
|
|
434
|
+
- Auto-reap branches on MCP close: when the watchdog proxy observes a session's
|
|
435
|
+
own claim close terminally and successfully, it removes that issue's clean,
|
|
436
|
+
merged `rtsc-NN/*` worktree and branch out-of-band (`retasc tidy --prune --only
|
|
437
|
+
<id>`), instead of orphaning them until a human ran `retasc tidy`. Exposes
|
|
438
|
+
`tidy --only <RTSC-NN>`.
|
|
439
|
+
|
|
440
|
+
## 1.2.4 (2026-07-07)
|
|
441
|
+
|
|
442
|
+
- **RTSC-170** — credential-file permission hardening: `config.json`,
|
|
443
|
+
`bindings.json`, and the fallback `.mcp.json` are written `0600` (dirs `0700`)
|
|
444
|
+
with no world-readable creation window.
|
|
445
|
+
|
|
446
|
+
## 1.2.3 (2026-07-05)
|
|
447
|
+
|
|
448
|
+
- **RTSC-150** — watchdog `isClaimLost` fix: no longer drops a live lease when a
|
|
449
|
+
payload field merely contains the string `CLAIM_LOST`.
|
|
450
|
+
|
|
451
|
+
## 1.2.2 (2026-07-04)
|
|
452
|
+
|
|
453
|
+
- **RTSC-148** — `retasc claim` takes the issue as a positional argument, and never
|
|
454
|
+
silently falls through to `next_issue`.
|
|
455
|
+
|
|
456
|
+
`retasc claim 143`, `claim RTSC-143` and `claim rtsc-143` all resolve to that issue; a
|
|
457
|
+
bare number expands against the *workspace's* prefix, resolved over the workspace key
|
|
458
|
+
rather than global config. Excess arguments now error loudly instead of being dropped by
|
|
459
|
+
Commander, which was how "claim this specific issue" quietly became "claim whatever is
|
|
460
|
+
next".
|
|
461
|
+
|
|
462
|
+
## 1.2.1 (2026-07-04)
|
|
463
|
+
|
|
464
|
+
- **RTSC-143** — one tolerant tool-result parser, and a loud failure when the claim fence
|
|
465
|
+
is off.
|
|
466
|
+
|
|
467
|
+
The proxy and `claim` each had their own silent copy of the parser; they are now one
|
|
468
|
+
(`lib/toolresult.ts`), which recovers the leading JSON value when a footer trails the
|
|
469
|
+
payload, refuses to recover from an `isError` response or concatenated JSON, and warns on
|
|
470
|
+
stderr when it degrades so a shape regression is never silent.
|
|
471
|
+
|
|
472
|
+
Session-key minting retries once with a 5s timeout and checks `res.ok`. On a double
|
|
473
|
+
failure it still fails soft, but says loudly that the per-session claim fence is OFF, and
|
|
474
|
+
the proxy appends that notice to `whoami` so the agent sees the degraded state rather than
|
|
475
|
+
it sitting in an MCP log file nobody opens.
|
|
476
|
+
|
|
477
|
+
## 1.2.0 (2026-07-04)
|
|
478
|
+
|
|
479
|
+
- **RTSC-137** — org-membership commands: `retasc members invite|list|revoke` and
|
|
480
|
+
`retasc join`.
|
|
481
|
+
|
|
482
|
+
## 1.1.3 (2026-07-02)
|
|
483
|
+
|
|
484
|
+
- **RTSC-131** — `retasc --version` reads `package.json` instead of a frozen literal.
|
|
485
|
+
|
|
486
|
+
It was hardcoded to `1.1.1`, so the published 1.1.2 still reported 1.1.1. The version is
|
|
487
|
+
now read at runtime relative to the compiled file, making `package.json` the single source
|
|
488
|
+
of truth.
|
|
489
|
+
|
|
490
|
+
## 1.1.2 (2026-07-02)
|
|
491
|
+
|
|
492
|
+
- **RTSC-130** — republish the bind auto-wire fix that had changed CLI source
|
|
493
|
+
without a version bump (npm versions are immutable).
|
|
494
|
+
|
|
495
|
+
## 1.1.1 (2026-06-28)
|
|
496
|
+
|
|
497
|
+
- **RTSC-98** — `claim`, `tidy` and `done` resolve the keystore key through the same
|
|
498
|
+
resolver the proxy uses.
|
|
499
|
+
|
|
500
|
+
After 1.1.0 moved keys out of the repo, a secret-free `.mcp.json` was understood by the
|
|
501
|
+
proxy but not by those three commands, which still only knew the legacy inline-key path
|
|
502
|
+
and bailed with "No Retasc MCP key found". The two resolvers had drifted, so they are now
|
|
503
|
+
one (`resolveConn` in `lib/keystore.ts`): explicit `RETASC_MCP_KEY`, then a legacy inline
|
|
504
|
+
key, then the canonical secret-free marker. Legacy markers keep working.
|
|
505
|
+
|
|
506
|
+
## 1.1.0 (2026-06-28)
|
|
507
|
+
|
|
508
|
+
- **RTSC-92** — workspace keys move to a home keystore, and the folder marker becomes
|
|
509
|
+
secret-free.
|
|
510
|
+
|
|
511
|
+
Secrets no longer sit in a project tree. The key lives in `~/.retasc/bindings.json` (mode
|
|
512
|
+
0600), keyed by an opaque workspace id rather than a path, so nothing leaks a home
|
|
513
|
+
directory and a checkout stays portable across a team. What lands in the repo is a marker
|
|
514
|
+
carrying only that id, which is why `./.mcp.json` is safe to commit.
|
|
515
|
+
|
|
516
|
+
`doctor` reports whether a binding is keystore-backed or legacy, and flags a workspace id
|
|
517
|
+
already bound at a different folder. A cloned repo whose marker has no key on this machine
|
|
518
|
+
is reported as such rather than as broken.
|
|
519
|
+
|
|
520
|
+
## 1.0.0 (2026-06-27)
|
|
521
|
+
|
|
522
|
+
- **RTSC-89** — initial npm publish as `@retasc/cli` (bin stays `retasc`).
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Issues live in a persistent, multi-tenant store; agents reach them through one M
|
|
|
8
8
|
The `retasc` CLI signs you in, creates projects, mints agent API keys, and wires the MCP
|
|
9
9
|
server into your agent in a single command.
|
|
10
10
|
|
|
11
|
-
→ [retasc.com](https://retasc.com) · [Set up your agent](https://retasc.com/connect) · [Quickstart](https://docs.retasc.com/quickstart)
|
|
11
|
+
→ [retasc.com](https://retasc.com) · [Set up your agent](https://retasc.com/connect) · [Quickstart](https://docs.retasc.com/quickstart) · [Changelog](https://docs.retasc.com/changelog)
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -18,6 +18,10 @@ npm i -g @retasc/cli
|
|
|
18
18
|
|
|
19
19
|
This installs the `retasc` command. Requires Node.js ≥ 18.
|
|
20
20
|
|
|
21
|
+
Already installed? `retasc --version` tells you what you have, and
|
|
22
|
+
[the changelog](https://docs.retasc.com/changelog) says what changed since. Every published
|
|
23
|
+
version is listed there with a date.
|
|
24
|
+
|
|
21
25
|
## Quickstart
|
|
22
26
|
|
|
23
27
|
```sh
|
|
@@ -75,8 +79,9 @@ Run `retasc --help` or `retasc <command> --help` for the full set.
|
|
|
75
79
|
- Website — [retasc.com](https://retasc.com)
|
|
76
80
|
- Set up your agent — [retasc.com/connect](https://retasc.com/connect)
|
|
77
81
|
- Docs + quickstart — [docs.retasc.com/quickstart](https://docs.retasc.com/quickstart)
|
|
82
|
+
- Changelog — [docs.retasc.com/changelog](https://docs.retasc.com/changelog)
|
|
78
83
|
- Parallel agents guide — [retasc.com/guides/claude-code-parallel-agents](https://retasc.com/guides/claude-code-parallel-agents)
|
|
79
|
-
-
|
|
84
|
+
- Support — [support@retasc.com](mailto:support@retasc.com)
|
|
80
85
|
|
|
81
86
|
## License
|
|
82
87
|
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { identityAction } from "./commands/identity.js";
|
|
|
11
11
|
import { doctorAction } from "./commands/doctor.js";
|
|
12
12
|
import { billingAction } from "./commands/billing.js";
|
|
13
13
|
import { isNetworkError, readLocalBinding, resolveBinding } from "./lib/binding.js";
|
|
14
|
+
import { whoamiView, orgCreatedView, projectCreatedView, keyListView, inviteListView, } from "./lib/format.js";
|
|
14
15
|
import { tidyAction, doneAction } from "./commands/tidy.js";
|
|
15
16
|
import { runProxy } from "./proxy.js";
|
|
16
17
|
import { deviceLogin } from "./auth.js";
|
|
@@ -77,7 +78,8 @@ program
|
|
|
77
78
|
program
|
|
78
79
|
.command("whoami")
|
|
79
80
|
.description("Show THIS folder's org/project binding, plus the signed-in user and their orgs.")
|
|
80
|
-
.
|
|
81
|
+
.option("--json", "Emit the raw payload instead of the summary.")
|
|
82
|
+
.action(async (opts) => {
|
|
81
83
|
// RTSC-91 (§13): lead with the binding for the folder you're in — the same
|
|
82
84
|
// "you are in org X / project Y" heads-up the agent gets — so a human can
|
|
83
85
|
// confirm scope before any work. Resolved from the local key, server-enforced.
|
|
@@ -111,7 +113,9 @@ program
|
|
|
111
113
|
}
|
|
112
114
|
try {
|
|
113
115
|
const me = await api.me();
|
|
114
|
-
|
|
116
|
+
// RTSC-521: the binding block above is untouched — it was already the good half of
|
|
117
|
+
// this command. Only the payload dump becomes a summary.
|
|
118
|
+
console.log(opts.json ? JSON.stringify(me, null, 2) : whoamiView(me));
|
|
115
119
|
}
|
|
116
120
|
catch (e) {
|
|
117
121
|
fail(e);
|
|
@@ -200,11 +204,12 @@ org
|
|
|
200
204
|
.command("create")
|
|
201
205
|
.requiredOption("--name <name>")
|
|
202
206
|
.option("--slug <slug>")
|
|
207
|
+
.option("--json", "Emit the raw payload instead of the summary.")
|
|
203
208
|
.action(async (opts) => {
|
|
204
209
|
requireLogin();
|
|
205
210
|
try {
|
|
206
|
-
const res = await api.createOrg({ name: opts.name, slug: opts.slug });
|
|
207
|
-
console.log(JSON.stringify(res, null, 2));
|
|
211
|
+
const res = (await api.createOrg({ name: opts.name, slug: opts.slug }));
|
|
212
|
+
console.log(opts.json ? JSON.stringify(res, null, 2) : orgCreatedView(res, opts.name));
|
|
208
213
|
}
|
|
209
214
|
catch (e) {
|
|
210
215
|
fail(e);
|
|
@@ -216,11 +221,16 @@ project
|
|
|
216
221
|
.requiredOption("--org-id <id>")
|
|
217
222
|
.requiredOption("--name <name>")
|
|
218
223
|
.requiredOption("--prefix <PREFIX>")
|
|
224
|
+
.option("--json", "Emit the raw payload instead of the summary.")
|
|
219
225
|
.action(async (opts) => {
|
|
220
226
|
requireLogin();
|
|
221
227
|
try {
|
|
222
|
-
const res = await api.createProject({
|
|
223
|
-
|
|
228
|
+
const res = (await api.createProject({
|
|
229
|
+
orgId: opts.orgId,
|
|
230
|
+
name: opts.name,
|
|
231
|
+
prefix: opts.prefix,
|
|
232
|
+
}));
|
|
233
|
+
console.log(opts.json ? JSON.stringify(res, null, 2) : projectCreatedView(res, opts.name));
|
|
224
234
|
}
|
|
225
235
|
catch (e) {
|
|
226
236
|
fail(e);
|
|
@@ -279,12 +289,14 @@ key
|
|
|
279
289
|
});
|
|
280
290
|
key
|
|
281
291
|
.command("list")
|
|
292
|
+
.description("List an org's agent keys, newest first.")
|
|
282
293
|
.requiredOption("--org-id <id>")
|
|
294
|
+
.option("--json", "Emit the raw payload instead of the table.")
|
|
283
295
|
.action(async (opts) => {
|
|
284
296
|
requireLogin();
|
|
285
297
|
try {
|
|
286
|
-
const res = await api.listKeys({ orgId: opts.orgId });
|
|
287
|
-
console.log(JSON.stringify(res, null, 2));
|
|
298
|
+
const res = (await api.listKeys({ orgId: opts.orgId }));
|
|
299
|
+
console.log(opts.json ? JSON.stringify(res, null, 2) : keyListView(res));
|
|
288
300
|
}
|
|
289
301
|
catch (e) {
|
|
290
302
|
fail(e);
|
|
@@ -347,11 +359,12 @@ members
|
|
|
347
359
|
.command("list")
|
|
348
360
|
.description("List an org's invites and their status (owner only).")
|
|
349
361
|
.requiredOption("--org-id <id>")
|
|
362
|
+
.option("--json", "Emit the raw payload instead of the table.")
|
|
350
363
|
.action(async (opts) => {
|
|
351
364
|
requireLogin();
|
|
352
365
|
try {
|
|
353
|
-
const res = await api.listInvites({ orgId: opts.orgId });
|
|
354
|
-
console.log(JSON.stringify(res, null, 2));
|
|
366
|
+
const res = (await api.listInvites({ orgId: opts.orgId }));
|
|
367
|
+
console.log(opts.json ? JSON.stringify(res, null, 2) : inviteListView(res));
|
|
355
368
|
}
|
|
356
369
|
catch (e) {
|
|
357
370
|
fail(e);
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { clean } from "./text.js";
|
|
2
|
+
// RTSC-521 — the CLI's human-readable views.
|
|
3
|
+
//
|
|
4
|
+
// Five commands used to answer a person with `JSON.stringify(res, null, 2)`, including
|
|
5
|
+
// `whoami`, which is the command someone runs to find out who they are. The one fact they
|
|
6
|
+
// wanted was a bracket to parse, next to a Convex document id that means nothing to them.
|
|
7
|
+
//
|
|
8
|
+
// The "it's good for the agent" defence does not apply: agents never see CLI stdout. They
|
|
9
|
+
// talk to MCP directly, and `whoami` exists there as its own tool. Nothing was reading
|
|
10
|
+
// these payloads but a human.
|
|
11
|
+
//
|
|
12
|
+
// PURE, and returning strings rather than printing them, for two reasons. Tests can pin the
|
|
13
|
+
// wording without scraping stdout, and every one of these values came off the wire — so
|
|
14
|
+
// `clean()` belongs at the point of formatting, once, rather than at each interpolation
|
|
15
|
+
// where a later line can forget it.
|
|
16
|
+
//
|
|
17
|
+
// The shape follows `commands/billing.ts`, which already got this right: an aligned summary
|
|
18
|
+
// by default, `--json` for the raw payload. It is one pattern, not two.
|
|
19
|
+
/** Width of the label column. Matches billing.ts's `row()` so the CLI reads as one tool. */
|
|
20
|
+
const LABEL = 13;
|
|
21
|
+
/** One `label value` line. Multi-line values stay hung under the value column. */
|
|
22
|
+
export function labelled(rows) {
|
|
23
|
+
return rows
|
|
24
|
+
.map(([label, value]) => {
|
|
25
|
+
const [head, ...rest] = String(value).split("\n");
|
|
26
|
+
const pad = " ".repeat(LABEL);
|
|
27
|
+
return [`${label.padEnd(LABEL)}${head}`, ...rest.map((r) => `${pad}${r}`)].join("\n");
|
|
28
|
+
})
|
|
29
|
+
.join("\n");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* An aligned table, or a plain sentence when there is nothing in it.
|
|
33
|
+
*
|
|
34
|
+
* `empty` is required rather than optional: a list command that prints a bare header row
|
|
35
|
+
* and nothing else reads like a failure, and every caller has something truer to say.
|
|
36
|
+
*/
|
|
37
|
+
export function table(headers, rows, empty) {
|
|
38
|
+
if (rows.length === 0)
|
|
39
|
+
return empty;
|
|
40
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
41
|
+
// The last column is never padded — trailing spaces are invisible until someone copies
|
|
42
|
+
// a line out of their terminal and finds them.
|
|
43
|
+
const line = (cells) => cells.map((c, i) => (i === cells.length - 1 ? c : c.padEnd(widths[i]))).join(" ").trimEnd();
|
|
44
|
+
return [line(headers), ...rows.map(line)].join("\n");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* How long ago, in the coarsest unit that is still true.
|
|
48
|
+
*
|
|
49
|
+
* A raw epoch is the specific thing this issue exists to remove: a revoked key reading as
|
|
50
|
+
* `1785660358922` is a number someone skims past, and "revoked" is the fact.
|
|
51
|
+
*/
|
|
52
|
+
export function ago(ms, now = Date.now()) {
|
|
53
|
+
if (ms == null)
|
|
54
|
+
return "never";
|
|
55
|
+
const s = Math.max(0, Math.round((now - ms) / 1000));
|
|
56
|
+
if (s < 60)
|
|
57
|
+
return "just now";
|
|
58
|
+
const m = Math.round(s / 60);
|
|
59
|
+
if (m < 60)
|
|
60
|
+
return `${m}m ago`;
|
|
61
|
+
const h = Math.round(m / 60);
|
|
62
|
+
if (h < 24)
|
|
63
|
+
return `${h}h ago`;
|
|
64
|
+
return `${Math.round(h / 24)}d ago`;
|
|
65
|
+
}
|
|
66
|
+
/** A date a human can read, for a deadline rather than an elapsed time. */
|
|
67
|
+
export function day(ms) {
|
|
68
|
+
return ms == null ? "—" : new Date(ms).toISOString().slice(0, 10);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Who you are, and where you can act.
|
|
72
|
+
*
|
|
73
|
+
* The user id is dropped: nothing a person types accepts it. The org id is KEPT, because
|
|
74
|
+
* the very next command genuinely wants it (`retasc bind --org-id …`, `members invite
|
|
75
|
+
* --org-id …`) — the same reason the Dash surfaces it on the Settings page.
|
|
76
|
+
*/
|
|
77
|
+
export function whoamiView(me) {
|
|
78
|
+
const u = me.user ?? {};
|
|
79
|
+
const who = [u.name, u.email && `<${u.email}>`].filter(Boolean).map(clean).join(" ");
|
|
80
|
+
const orgs = me.orgs ?? [];
|
|
81
|
+
return labelled([
|
|
82
|
+
["Signed in", who || "unknown"],
|
|
83
|
+
[
|
|
84
|
+
"Orgs",
|
|
85
|
+
// Said in words. An empty array printed as `[]` is the exact moment this command
|
|
86
|
+
// stops answering the question it was asked.
|
|
87
|
+
orgs.length === 0
|
|
88
|
+
? "none"
|
|
89
|
+
: orgs
|
|
90
|
+
.map((o) => {
|
|
91
|
+
const bits = [clean(o.name)];
|
|
92
|
+
if (o.slug)
|
|
93
|
+
bits.push(`(${clean(o.slug)})`);
|
|
94
|
+
const tail = [o.role && clean(o.role), o.deleting && "DELETING"].filter(Boolean);
|
|
95
|
+
// `o.id` is Convex-generated and so not attacker-influenced today. Cleaned
|
|
96
|
+
// anyway: this function's job is that nothing reaches a terminal unsanitised,
|
|
97
|
+
// and one raw interpolation is how the next field added here gets missed.
|
|
98
|
+
return `${bits.join(" ")}${tail.length ? ` · ${tail.join(" · ")}` : ""}\n ${clean(o.id)}`;
|
|
99
|
+
})
|
|
100
|
+
.join("\n"),
|
|
101
|
+
],
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
export function orgCreatedView(res, name) {
|
|
105
|
+
// `ownerMemberId` is deliberately not shown. It comes back in the payload and there is no
|
|
106
|
+
// command that takes it, so printing it is noise a reader has to decide to ignore.
|
|
107
|
+
return [
|
|
108
|
+
`✓ Created org "${clean(name)}"${res.slug ? ` (${clean(res.slug)})` : ""}.`,
|
|
109
|
+
"",
|
|
110
|
+
labelled([["Org id", clean(res.orgId ?? "—")]]),
|
|
111
|
+
"",
|
|
112
|
+
`Next: retasc bind --org-id ${clean(res.orgId ?? "<id>")}`,
|
|
113
|
+
].join("\n");
|
|
114
|
+
}
|
|
115
|
+
export function projectCreatedView(res, name) {
|
|
116
|
+
return [
|
|
117
|
+
`✓ Created project "${clean(name)}"${res.prefix ? ` (${clean(res.prefix)})` : ""}.`,
|
|
118
|
+
"",
|
|
119
|
+
labelled([["Project id", clean(res.projectId ?? "—")]]),
|
|
120
|
+
"",
|
|
121
|
+
`Issues here will be numbered ${clean(res.prefix ?? "PREFIX")}-1, ${clean(res.prefix ?? "PREFIX")}-2, and so on.`,
|
|
122
|
+
].join("\n");
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The org's agent keys.
|
|
126
|
+
*
|
|
127
|
+
* Session keys (RTSC-50) are folded into a count rather than listed. They are auto-minted
|
|
128
|
+
* per session and can outnumber the real workspace keys many to one, which is why the Dash
|
|
129
|
+
* groups them too — a list where the manageable rows are lost among machine-minted children
|
|
130
|
+
* is a list nobody reads.
|
|
131
|
+
*/
|
|
132
|
+
export function keyListView(rows, now = Date.now()) {
|
|
133
|
+
const workspace = rows.filter((k) => !k.parentKeyId);
|
|
134
|
+
const sessions = rows.length - workspace.length;
|
|
135
|
+
const body = table(["KEY", "PROJECT", "AGENT", "RUNTIME", "LAST USED"], workspace.map((k) => [
|
|
136
|
+
clean(k.displayPrefix ?? k.name ?? "—"),
|
|
137
|
+
clean(k.project ?? "—"),
|
|
138
|
+
clean(k.agent ?? "—"),
|
|
139
|
+
clean(k.runtime ?? "—"),
|
|
140
|
+
// State first: a revoked key that reads as a date is one someone counts as live.
|
|
141
|
+
k.revokedAt ? "revoked" : ago(k.lastUsedAt, now),
|
|
142
|
+
]), "No keys yet. Mint one with `retasc key mint`.");
|
|
143
|
+
return sessions > 0
|
|
144
|
+
? `${body}\n\n(${sessions} session key${sessions === 1 ? "" : "s"} not shown — auto-minted per agent session.)`
|
|
145
|
+
: body;
|
|
146
|
+
}
|
|
147
|
+
export function inviteListView(rows) {
|
|
148
|
+
return table(["CODE", "ROLE", "STATUS", "INVITED BY", "EXPIRES"], rows.map((i) => [
|
|
149
|
+
clean(i.displayPrefix ?? "—"),
|
|
150
|
+
clean(i.role ?? "—"),
|
|
151
|
+
clean(i.status ?? "—"),
|
|
152
|
+
clean(i.invitedBy ?? "—"),
|
|
153
|
+
// Only a LIVE invite has a deadline worth reading. On a spent one the date is
|
|
154
|
+
// still in the payload and still means nothing.
|
|
155
|
+
i.status === "pending" ? day(i.expiresAt) : "—",
|
|
156
|
+
]), "No invites yet. Create one with `retasc members invite --org-id <id>`.");
|
|
157
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retasc/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Retasc CLI
|
|
3
|
+
"version": "1.13.1",
|
|
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": {
|
|
7
7
|
"retasc": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"CHANGELOG.md"
|
|
11
12
|
],
|
|
12
13
|
"engines": {
|
|
13
14
|
"node": ">=18"
|
|
@@ -21,7 +22,8 @@
|
|
|
21
22
|
"directory": "cli"
|
|
22
23
|
},
|
|
23
24
|
"bugs": {
|
|
24
|
-
"
|
|
25
|
+
"email": "support@retasc.com",
|
|
26
|
+
"url": "https://retasc.com"
|
|
25
27
|
},
|
|
26
28
|
"keywords": [
|
|
27
29
|
"retasc",
|