@retasc/cli 1.19.0 → 1.21.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 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.21.0 (2026-08-07)
10
+
11
+ - **RTSC-527** — the re-import warning dates the last import in **your** timezone. The day
12
+ was formatted from `toISOString()`, which rolls back one east of Greenwich: at UTC+7 an
13
+ import made five hours ago read as yesterday's, which is the opposite of useful for a line
14
+ whose job is to say how long ago it was. The Dash gained the same date in the same shape
15
+ in this release, and moved onto the same durable per-source history the CLI already read,
16
+ so the two surfaces now describe one import the same way.
17
+
18
+ ## 1.20.0 (2026-08-04)
19
+
20
+ - **RTSC-561** — the org gained an **admin** role, and the CLI stopped refusing it.
21
+ `retasc billing` gated on `role !== "owner"` client-side, so it would have refused an
22
+ admin locally for a call the server allows. Owner-only copy in `bind` and the command
23
+ help now names both roles.
24
+
9
25
  ## 1.19.0 (2026-08-03)
10
26
 
11
27
  - **RTSC-495** — `retasc bind --setup <code>` sets a folder up with no sign-in and nothing
@@ -529,7 +545,7 @@ there is no TTY to run one in.
529
545
 
530
546
  - **RTSC-263** — owner/member permission denials now print a readable reason and the
531
547
  next step instead of an opaque `Server Error`. Hitting an owner-only command as a
532
- member reports "Owner role required." with "Ask <owner> to create a project." on its
548
+ member reports "Owner role required." with "Ask `<owner>` to create a project." on its
533
549
  own line. The backend change does the work; the CLI already read the structured
534
550
  payload (1.5.0), so this release carries only the hardening below.
535
551
  - **RTSC-263** — `formatError` now strips U+0085, U+2028 and U+2029 in addition to the
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # retasc
2
2
 
3
- **The issue tracker AI agents pull work from.**
3
+ **The issue tracker that hands your backlog to AI agents.**
4
4
 
5
5
  Over [MCP](https://modelcontextprotocol.io), a heterogeneous fleet of agents atomically
6
6
  claims unblocked, prioritized tasks and runs in parallel — server-enforced, no collisions.
@@ -44,8 +44,10 @@ export async function billingAction(opts) {
44
44
  const org = me.orgs.find((o) => o.id === orgId);
45
45
  orgLabel = org ? `${org.name}${org.slug ? ` (${org.slug})` : ""}` : String(orgId);
46
46
  // Fail early with the actionable message rather than a raw FORBIDDEN from the server.
47
- if (org && org.role !== "owner") {
48
- throw new Error(`Billing is owner-only your role in ${orgLabel} is "${org.role}".`);
47
+ // Owner OR admin (RTSC-561) this mirrors `requireOwnerOrAdmin`, and a stale copy of
48
+ // the rule here would refuse an admin locally for a call the server would have allowed.
49
+ if (org && org.role !== "owner" && org.role !== "admin") {
50
+ throw new Error(`Billing needs the owner or admin role — yours in ${orgLabel} is "${org.role}".`);
49
51
  }
50
52
  const [status, charges] = await Promise.all([
51
53
  api.billingStatus({ orgId: orgId }),
@@ -286,7 +286,7 @@ export async function completeWorkspaceSetup(args) {
286
286
  let prefix;
287
287
  if (!projectId && opts.project && opts.prefix) {
288
288
  if (!args.canCreateProject) {
289
- cliError("FORBIDDEN", "Only an owner can create a project.", `Ask an owner to add one, then pass --project-id <id>.`);
289
+ cliError("FORBIDDEN", "Only an owner or admin can create a project.", `Ask one of them to add one, then pass --project-id <id>.`);
290
290
  }
291
291
  const p = (await api.createProject({ orgId, name: opts.project, prefix: opts.prefix }));
292
292
  projectId = p.projectId;
@@ -301,7 +301,7 @@ export async function completeWorkspaceSetup(args) {
301
301
  // what to ask for, by name. A bare FORBIDDEN from `createProject` would be both
302
302
  // wrong (they never asked to create anything) and unactionable.
303
303
  if (!list.length) {
304
- cliError("NO_PROJECTS", `Org ${org} has no projects yet, and only an owner can create one.`, `Ask an owner to add a project, then finish this folder with: ${selfCommand(VERSION)} bind --org-id ${orgId}`);
304
+ cliError("NO_PROJECTS", `Org ${org} has no projects yet, and only an owner or admin can create one.`, `Ask one of them to add a project, then finish this folder with: ${selfCommand(VERSION)} bind --org-id ${orgId}`);
305
305
  }
306
306
  // One project is not a choice. Asking anyway would be the only question in the
307
307
  // common invite path, for an answer that was never in doubt.
@@ -18,6 +18,11 @@ const GROUP_LABEL = {
18
18
  };
19
19
  /** Section order, matching the Dash. */
20
20
  const GROUP_ORDER = ["not_started", "active", "done", "closed"];
21
+ /** `YYYY-MM-DD` in the local zone. Mirrored in `dash/src/lib/importGating.ts`. */
22
+ function localDay(d) {
23
+ const pad = (n) => String(n).padStart(2, "0");
24
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
25
+ }
21
26
  /**
22
27
  * What to say before a SECOND import into the same org (RTSC-526).
23
28
  *
@@ -36,9 +41,11 @@ export function reimportWarning(history, source, label) {
36
41
  const prior = history.find((h) => h.source === source);
37
42
  if (!prior)
38
43
  return null;
39
- const when = prior.lastImportedAt
40
- ? ` (last on ${new Date(prior.lastImportedAt).toISOString().slice(0, 10)})`
41
- : "";
44
+ // The reader's day, not UTC (RTSC-527). `toISOString().slice(0, 10)` was the original
45
+ // and it misdates by one: at UTC+7 an import made before 07:00 local prints as
46
+ // yesterday, which is the opposite of useful for a line whose whole job is "how long
47
+ // ago". The Dash's `importDay` formats identically, so one import still gets one date.
48
+ const when = prior.lastImportedAt ? ` (last on ${localDay(new Date(prior.lastImportedAt))})` : "";
42
49
  return (`\n! You've imported from ${clean(label)} into this org before${when}.\n` +
43
50
  ` Re-importing re-syncs those issues, so any edits you made in Retasc to them\n` +
44
51
  ` (status, labels, and so on) will be replaced by ${clean(label)}'s version.`);
@@ -223,8 +223,9 @@ export async function joinAction(link, opts) {
223
223
  orgLabel,
224
224
  opts,
225
225
  existing: guard.existing,
226
- // `createProject` is `requireOwner`, and an invite only ever grants `member`. Offering
227
- // "+ create new" here would be offering a refusal dressed as a choice.
226
+ // `createProject` is `requireOwnerOrAdmin`, and an invite only ever grants `member`
227
+ // (admin is granted by `setMemberRole` after joining, never by a bearer code
228
+ // RTSC-561). Offering "+ create new" here would be offering a refusal as a choice.
228
229
  canCreateProject: false,
229
230
  });
230
231
  }
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ program
211
211
  });
212
212
  program
213
213
  .command("billing")
214
- .description("Show the org's billing: subscription, what's owed now, and the charge + on-chain payment history across every payment link ever used (owner only).")
214
+ .description("Show the org's billing: subscription, what's owed now, and the charge + on-chain payment history across every payment link ever used (owner or admin).")
215
215
  .option("--org-id <id>", "Which org (defaults to your only one).")
216
216
  .option("--json", "Emit the raw payload instead of the summary.")
217
217
  .action(async (opts) => {
@@ -356,7 +356,7 @@ key
356
356
  const members = program.command("members").description("Invite people to an org and manage invites.");
357
357
  members
358
358
  .command("invite")
359
- .description("Mint a single-use invite code for an org (owner only). Shown once.")
359
+ .description("Mint a single-use invite code for an org (owner or admin). Shown once.")
360
360
  .requiredOption("--org-id <id>")
361
361
  .option("--expires-days <n>", "Days until the code expires (1–30, default 7)", (v) => parseInt(v, 10))
362
362
  .action(async (opts) => {
@@ -377,7 +377,7 @@ members
377
377
  });
378
378
  members
379
379
  .command("list")
380
- .description("List an org's invites and their status (owner only).")
380
+ .description("List an org's invites and their status (owner or admin).")
381
381
  .requiredOption("--org-id <id>")
382
382
  .option("--json", "Emit the raw payload instead of the table.")
383
383
  .action(async (opts) => {
@@ -392,7 +392,7 @@ members
392
392
  });
393
393
  members
394
394
  .command("revoke")
395
- .description("Revoke an unused invite (owner only).")
395
+ .description("Revoke an unused invite (owner or admin).")
396
396
  .requiredOption("--invite-id <id>")
397
397
  .action(async (opts) => {
398
398
  requireLogin();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retasc/cli",
3
- "version": "1.19.0",
3
+ "version": "1.21.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": {