@martenhq/cli 0.1.0 → 0.1.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 ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ - `marten login --scope` adds to the default scopes (`martens`, `tasks`, `registry`) instead of replacing them, and
6
+ signing in again as the same person keeps what this machine's key could already do; `--reset-scopes` narrows it
7
+ back to the defaults. Another person signing in on the machine starts from the defaults.
8
+ - Signing in doesn't wait on the previous key's server: asking it the old key's scopes and revoking it give up after
9
+ 5 seconds.
10
+ - Signing in again writes the new key first, then revokes the old one; a revoke that fails is a warning, not a
11
+ failed login. When the key can't be made (`too_many_keys`), the sign-in's session is ended anyway.
12
+ - A key `marten login` stored is only sent to the server it was made on: `MARTEN_API_URL` applies with
13
+ `MARTEN_API_KEY`, or when signing in.
14
+ - The credentials file is written to a new file and renamed into place (never through a link at its path), 0600, in
15
+ a folder that is set to 0700 even if it was there before. On Windows it's written as is.
16
+ - `marten mcp` answers `null`, non-object lines and batches with Invalid Request (-32600), and a line that fails can
17
+ no longer stop the server.
18
+ - Task statuses are `todo`, `in_progress` and `done` (`needs_review` is gone).
19
+
20
+ ## 0.1.0
21
+
22
+ - First release: `login`/`logout`/`whoami`, `martens`, `ask`, `chats`, `chat read`/`send`, `task list`/`add`/
23
+ `update`, `registry list`/`show`/`install`, `mcp`, `setup claude|codex`, `--json` everywhere.
package/README.md CHANGED
@@ -22,17 +22,34 @@ marten login
22
22
 
23
23
  It asks for your Marten email and sends the same sign-in email as the apps: type the 6-digit code here, or open the
24
24
  link on any device. Then it keeps an API key named after this machine ("Nina's MacBook · CLI") in
25
- `~/.config/marten/credentials` (readable only by you). `marten logout` revokes it; signing in again replaces it.
25
+ `~/.config/marten/credentials` (readable only by you; on Windows, by your user profile's permissions). `marten
26
+ logout` revokes it; signing in again replaces it and revokes the old one.
26
27
 
27
- A key has scopes. By default: `martens` (your Martens and their 1:1s with you), `tasks` and `registry`. For your
28
- groups, ask for more:
28
+ A key has scopes:
29
+
30
+ | Scope | Lets it |
31
+ |---|---|
32
+ | `martens` (default) | list your Martens, read and post in their 1:1s with you, `ask` them |
33
+ | `tasks` (default) | tasks in your Martens' 1:1s (with `chats:read`, groups' tasks too) |
34
+ | `registry` (default) | browse the registry and install Martens for you |
35
+ | `chats:read` | read your groups and their tasks |
36
+ | `chats:write` | post as you in your groups, add or change tasks there, add Martens to them |
37
+
38
+ `--scope` adds to the defaults, and signing in again as the same person keeps what this machine's key could already
39
+ do. `--reset-scopes` starts from the defaults (plus any `--scope`), to narrow a key:
29
40
 
30
41
  ```sh
31
- marten login --scope martens --scope tasks --scope registry --scope chats:read --scope chats:write
42
+ marten login --scope chats:read --scope chats:write
43
+ marten login --reset-scopes
32
44
  ```
33
45
 
34
- In CI or for another agent, set `MARTEN_API_KEY` instead (and `MARTEN_API_URL` for a server other than
35
- https://api.marten.sh).
46
+ Scopes limit what the key does, not what your Martens do: a key with `martens` talks to your own Martens, and they act
47
+ for you as they do when you ask them in the apps (they can read and post in groups you share with them, and use the
48
+ apps you've connected for them).
49
+
50
+ In CI or for another agent, set `MARTEN_API_KEY` instead, with `MARTEN_API_URL` for a server other than
51
+ https://api.marten.sh. A key `marten login` stored is only ever sent to the server it was made on (`MARTEN_API_URL`
52
+ applies to it only when signing in).
36
53
 
37
54
  ## Commands
38
55
 
package/dist/marten.js CHANGED
@@ -80,16 +80,20 @@ Usage:
80
80
  a Marten assignee is woken then to remind them
81
81
  --repeat <rule> "every weekday at 9", "every 2 weeks on friday", daily, or an RRULE (at most hourly)
82
82
  --tz <zone> IANA time zone for --due and --repeat (default UTC)
83
+ --label <name> tag it (repeat for several): a short word people filter and group tasks by,
84
+ like "travel", "bills" or the app it came from ("Gmail")
83
85
  marten task list [options] List the chat's tasks
84
86
  --assignee <who> me (you, this Marten), a person or Marten here by name or id,
85
87
  or none (unassigned)
86
88
  --for-owner assigned to your owner (a person's Marten only)
87
- --status <s> todo, in_progress, needs_review, done or open (not done)
89
+ --status <s> todo, in_progress, done or open (not done)
88
90
  --due <d> today, overdue, upcoming or none
91
+ --label <name> tagged with it
89
92
  marten task show <id> A task, with its activity
90
93
  marten task update <id> [options] Change a task: --title, --description, --status,
91
94
  --assignee (none to clear), --due (none to clear),
92
- --remind, --repeat (none to stop), --tz
95
+ --remind, --repeat (none to stop), --tz,
96
+ --label <name> to tag it, --unlabel <name> to untag it
93
97
  marten task start|done|reopen <id> Start, finish or reopen a task
94
98
  marten task comment <id> <text> Log progress or a note in the task's activity
95
99
  marten task delete <id> [--series] Delete a task (--series: stop its repeat too)
@@ -189,7 +193,8 @@ var OPTIONS = {
189
193
  series: { type: "boolean" },
190
194
  app: { type: "string" },
191
195
  title: { type: "string" },
192
- label: { type: "string" },
196
+ label: { type: "string", multiple: true },
197
+ unlabel: { type: "string", multiple: true },
193
198
  message: { type: "string" },
194
199
  version: { type: "string" },
195
200
  width: { type: "string" },
@@ -282,7 +287,11 @@ function parseCommand(argv, now = new Date) {
282
287
  throw new UsageError(`Unknown command: ${first}`);
283
288
  }
284
289
  }
285
- var STATUSES = ["todo", "in_progress", "needs_review", "done"];
290
+ var STATUSES = ["todo", "in_progress", "done"];
291
+ function labelsOf(given) {
292
+ const labels = given?.map((l) => l.trim()).filter(Boolean);
293
+ return labels?.length ? labels : undefined;
294
+ }
286
295
  var DUE_FILTERS = ["today", "overdue", "upcoming", "none"];
287
296
  function oneOf(value, allowed, option) {
288
297
  if (value === undefined)
@@ -309,16 +318,26 @@ function parseTask(sub, rest, values, group) {
309
318
  return rest[0];
310
319
  };
311
320
  switch (sub) {
312
- case "add":
313
- return { name: "task-add", title: words(rest, "task title"), ...taskFields(values), group };
321
+ case "add": {
322
+ const labels = labelsOf(values.label);
323
+ return { name: "task-add", title: words(rest, "task title"), ...taskFields(values), ...labels ? { labels } : {}, group };
324
+ }
314
325
  case "update": {
315
326
  const taskId = id("marten task update <id> [options]");
316
327
  if (values.title !== undefined && !values.title.trim())
317
328
  throw new UsageError("--title can't be empty");
318
329
  const status = oneOf(values.status, STATUSES, "--status");
319
- const changes = { ...taskFields(values), ...values.title ? { title: values.title.trim() } : {}, ...status ? { status } : {} };
330
+ const addLabels = labelsOf(values.label);
331
+ const removeLabels = labelsOf(values.unlabel);
332
+ const changes = {
333
+ ...taskFields(values),
334
+ ...values.title ? { title: values.title.trim() } : {},
335
+ ...status ? { status } : {},
336
+ ...addLabels ? { addLabels } : {},
337
+ ...removeLabels ? { removeLabels } : {}
338
+ };
320
339
  if (!Object.keys(changes).length)
321
- throw new UsageError("Nothing to change: pass --title, --description, --status, --assignee, --due, --remind or --repeat");
340
+ throw new UsageError("Nothing to change: pass --title, --description, --status, --assignee, --due, --remind, --repeat, --label or --unlabel");
322
341
  return { name: "task-update", id: taskId, ...changes, group };
323
342
  }
324
343
  case "list": {
@@ -326,7 +345,16 @@ function parseTask(sub, rest, values, group) {
326
345
  const forOwner = values["for-owner"] === true;
327
346
  if (assignee && forOwner)
328
347
  throw new UsageError("Pass --assignee or --for-owner, not both");
329
- return { name: "task-list", assignee, forOwner, status: oneOf(values.status, [...STATUSES, "open"], "--status"), due: oneOf(values.due, DUE_FILTERS, "--due"), group };
348
+ const label = labelsOf(values.label)?.at(-1);
349
+ return {
350
+ name: "task-list",
351
+ assignee,
352
+ forOwner,
353
+ status: oneOf(values.status, [...STATUSES, "open"], "--status"),
354
+ due: oneOf(values.due, DUE_FILTERS, "--due"),
355
+ ...label ? { label } : {},
356
+ group
357
+ };
330
358
  }
331
359
  case "show":
332
360
  return { name: "task-show", id: id("marten task show <id>"), group };
@@ -354,7 +382,7 @@ function parseApp(sub, rest, values, group) {
354
382
  throw new UsageError("Missing what to publish: marten sketch publish <file.jsx|file.html|folder>");
355
383
  if (values.title !== undefined && !values.title.trim())
356
384
  throw new UsageError("--title can't be empty");
357
- return { name: "app-publish", path: rest[0], app: values.app, title: values.title?.trim(), label: values.label?.trim(), group };
385
+ return { name: "app-publish", path: rest[0], app: values.app, title: values.title?.trim(), label: values.label?.at(-1)?.trim(), group };
358
386
  }
359
387
  case "list":
360
388
  return { name: "app-list", group };
@@ -809,10 +837,11 @@ function withQuery(path, group, params = {}) {
809
837
  const text = query.toString();
810
838
  return text ? `${path}?${text}` : path;
811
839
  }
812
- var STATUS_MARK = { todo: "[ ]", in_progress: "[~]", needs_review: "[?]", done: "[x]" };
840
+ var STATUS_MARK = { todo: "[ ]", in_progress: "[~]", done: "[x]" };
813
841
  function formatTask(t) {
814
842
  const time = t.scheduledAt && `${t.remind ? "remind" : "at"} ${when(t.scheduledAt)} UTC`;
815
- const details = [t.due && `due ${t.due}`, time, t.repeat && t.repeat.toLowerCase(), t.assignee && `@${t.assignee}`].filter(Boolean);
843
+ const labels = (t.labels ?? []).map((l) => `#${l}`);
844
+ const details = [t.due && `due ${t.due}`, time, t.repeat && t.repeat.toLowerCase(), t.assignee && `@${t.assignee}`, ...labels].filter(Boolean);
816
845
  return `${t.id} ${STATUS_MARK[t.status]} ${t.title}${details.length ? ` (${details.join(", ")})` : ""}`;
817
846
  }
818
847
  var SHOWN_OPTIONS = 25;
@@ -892,7 +921,7 @@ async function runCommand(command, api, stdin = readStdin, agentDir = AGENT_DIR)
892
921
  return `${command.verb === "start" ? "Started" : command.verb === "done" ? "Done" : "Reopened"} ${formatTask(res.task)}`;
893
922
  }
894
923
  case "task-list": {
895
- const query = { assignee: command.assignee, forOwner: command.forOwner ? "1" : undefined, status: command.status, due: command.due };
924
+ const query = { assignee: command.assignee, forOwner: command.forOwner ? "1" : undefined, status: command.status, due: command.due, label: command.label };
896
925
  const { tasks } = await api("GET", withQuery("/cli/tasks", command.group, query));
897
926
  return tasks.map(formatTask).join(`
898
927
  `) || "No tasks.";
@@ -1275,7 +1304,8 @@ function pick(list, wanted, name, what) {
1275
1304
  throw new MartenError(404, "not_found", `No ${what} called "${wanted}"${list.length ? ` (yours: ${list.map(name).join(", ")})` : ""}`);
1276
1305
  }
1277
1306
  // src/credentials/credentials.ts
1278
- import { chmod, mkdir as mkdir5, readFile as readFile4, rm, writeFile as writeFile5 } from "node:fs/promises";
1307
+ import { randomBytes } from "node:crypto";
1308
+ import { chmod, mkdir as mkdir5, readFile as readFile4, rename, rm, writeFile as writeFile5 } from "node:fs/promises";
1279
1309
  import { dirname as dirname4, join as join5 } from "node:path";
1280
1310
  var DEFAULT_API_URL = "https://api.marten.sh";
1281
1311
  function credentialsPath(env2, home) {
@@ -1292,20 +1322,32 @@ async function readCredentials(path) {
1292
1322
  return null;
1293
1323
  }
1294
1324
  }
1295
- async function writeCredentials(path, credentials) {
1296
- await mkdir5(dirname4(path), { recursive: true, mode: 448 });
1297
- await writeFile5(path, `${JSON.stringify(credentials, null, 2)}
1298
- `, { mode: 384 });
1299
- await chmod(path, 384);
1325
+ async function writeCredentials(path, credentials, platform = process.platform) {
1326
+ const dir = dirname4(path);
1327
+ const text = `${JSON.stringify(credentials, null, 2)}
1328
+ `;
1329
+ await mkdir5(dir, { recursive: true, mode: 448 });
1330
+ if (platform === "win32") {
1331
+ await writeFile5(path, text);
1332
+ return;
1333
+ }
1334
+ await chmod(dir, 448);
1335
+ const temp = join5(dir, `.credentials-${randomBytes(6).toString("hex")}.tmp`);
1336
+ try {
1337
+ await writeFile5(temp, text, { mode: 384, flag: "wx" });
1338
+ await rename(temp, path);
1339
+ } catch (err) {
1340
+ await rm(temp, { force: true });
1341
+ throw err;
1342
+ }
1300
1343
  }
1301
1344
  async function removeCredentials(path) {
1302
1345
  await rm(path, { force: true });
1303
1346
  }
1304
1347
  function resolveAuth(env2, stored) {
1305
- const apiKey = env2.MARTEN_API_KEY || stored?.apiKey;
1306
- if (!apiKey)
1307
- return null;
1308
- return { apiKey, apiUrl: env2.MARTEN_API_URL || stored?.apiUrl || DEFAULT_API_URL };
1348
+ if (env2.MARTEN_API_KEY)
1349
+ return { apiKey: env2.MARTEN_API_KEY, apiUrl: env2.MARTEN_API_URL || DEFAULT_API_URL };
1350
+ return stored ? { apiKey: stored.apiKey, apiUrl: stored.apiUrl } : null;
1309
1351
  }
1310
1352
 
1311
1353
  // src/login/login.ts
@@ -1377,12 +1419,17 @@ async function login(opts) {
1377
1419
  stop.abort();
1378
1420
  }
1379
1421
  const keyName = `${opts.machine} · CLI`;
1380
- const made = await call(doFetch, `${base}/api-keys`, { name: keyName, ...opts.scopes?.length ? { scopes: opts.scopes } : {} }, session.sessionToken);
1422
+ let made;
1423
+ try {
1424
+ const scopes = typeof opts.scopes === "function" ? opts.scopes(session.user.email) : opts.scopes;
1425
+ made = await call(doFetch, `${base}/api-keys`, { name: keyName, ...scopes?.length ? { scopes } : {} }, session.sessionToken);
1426
+ } finally {
1427
+ await call(doFetch, `${base}/auth/logout`, undefined, session.sessionToken).catch(() => {
1428
+ return;
1429
+ });
1430
+ }
1381
1431
  if (made.status !== 201)
1382
1432
  throw refused(made.status, made.data);
1383
- await call(doFetch, `${base}/auth/logout`, undefined, session.sessionToken).catch(() => {
1384
- return;
1385
- });
1386
1433
  return { apiUrl: base, apiKey: made.data.token, keyId: made.data.key.id, keyName: made.data.key.name, email: session.user.email };
1387
1434
  }
1388
1435
 
@@ -1434,7 +1481,7 @@ var TOOLS = [
1434
1481
  {
1435
1482
  name: "marten_list_tasks",
1436
1483
  description: "List tasks in the person's chats (open ones and those done in the last 30 days).",
1437
- inputSchema: { type: "object", properties: { chat: { type: "string" }, status: { type: "string", enum: ["todo", "in_progress", "needs_review", "done", "open"] } } },
1484
+ inputSchema: { type: "object", properties: { chat: { type: "string" }, status: { type: "string", enum: ["todo", "in_progress", "done", "open"] } } },
1438
1485
  command: (a) => ({ name: "task-list", ...defined({ chat: str(a, "chat"), status: str(a, "status") }) })
1439
1486
  },
1440
1487
  {
@@ -1446,7 +1493,7 @@ var TOOLS = [
1446
1493
  {
1447
1494
  name: "marten_update_task",
1448
1495
  description: "Change a task: its title, status, assignee, time or repeat.",
1449
- inputSchema: { type: "object", properties: { id: { type: "string" }, title: { type: "string" }, status: { type: "string", enum: ["todo", "in_progress", "needs_review", "done"] }, ...taskProps }, required: ["id"] },
1496
+ inputSchema: { type: "object", properties: { id: { type: "string" }, title: { type: "string" }, status: { type: "string", enum: ["todo", "in_progress", "done"] }, ...taskProps }, required: ["id"] },
1450
1497
  command: (a) => ({ name: "task-update", id: need(a, "id"), ...defined({ title: str(a, "title"), status: str(a, "status") }), ...taskOptions(a) })
1451
1498
  },
1452
1499
  { name: "marten_registry_list", description: "List the Martens anyone can install from the registry.", inputSchema: { type: "object", properties: {} }, command: () => ({ name: "registry-list" }) },
@@ -1466,12 +1513,17 @@ var TOOLS = [
1466
1513
  var reply = (id, result) => ({ jsonrpc: "2.0", id: id ?? null, result });
1467
1514
  var fail = (id, code, message) => ({ jsonrpc: "2.0", id: id ?? null, error: { code, message } });
1468
1515
  async function handleMcp(raw, run, serverVersion) {
1469
- let request;
1516
+ let parsed;
1470
1517
  try {
1471
- request = JSON.parse(raw);
1518
+ parsed = JSON.parse(raw);
1472
1519
  } catch {
1473
1520
  return fail(null, -32700, "Parse error");
1474
1521
  }
1522
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.method !== "string") {
1523
+ const id = parsed?.id;
1524
+ return fail(typeof id === "string" || typeof id === "number" ? id : null, -32600, "Invalid Request");
1525
+ }
1526
+ const request = parsed;
1475
1527
  const isNotification = request.id === undefined;
1476
1528
  switch (request.method) {
1477
1529
  case "initialize":
@@ -1505,9 +1557,11 @@ async function serveMcp(lines, write, run, serverVersion) {
1505
1557
  for await (const line of lines) {
1506
1558
  if (!line.trim())
1507
1559
  continue;
1508
- const answering = handleMcp(line, run, serverVersion).then((answer) => {
1560
+ const answering = handleMcp(line, run, serverVersion).catch((err) => fail(null, -32603, `Internal error: ${err?.message ?? String(err)}`)).then((answer) => {
1509
1561
  if (answer)
1510
1562
  write(JSON.stringify(answer));
1563
+ }).catch(() => {
1564
+ return;
1511
1565
  });
1512
1566
  inFlight.add(answering);
1513
1567
  answering.finally(() => inFlight.delete(answering));
@@ -1519,7 +1573,7 @@ async function serveMcp(lines, write, run, serverVersion) {
1519
1573
  import { parseArgs as parseArgs2 } from "node:util";
1520
1574
  var SCOPES = ["martens", "chats:read", "chats:write", "tasks", "registry"];
1521
1575
  var TASK_USAGE = ` marten task list [--chat <chat>] [--status <s>] [--tz <zone>]
1522
- Your chats' tasks (--status todo, in_progress, needs_review, done or open);
1576
+ Your chats' tasks (--status todo, in_progress, done or open);
1523
1577
  times in --tz (default: this machine's zone)
1524
1578
  marten task add <title> [--chat <chat>] [options]
1525
1579
  Add a task (--chat may be left out when you have one 1:1)
@@ -1534,7 +1588,7 @@ var REGISTRY_USAGE = ` marten registry list Martens anyone c
1534
1588
  var OUTSIDE_HELP = `marten — talk to your Martens from your terminal and your other agents
1535
1589
 
1536
1590
  Usage:
1537
- marten login [--email <you@…>] [--scope <scope>…]
1591
+ marten login [--email <you@…>] [--scope <scope>…] [--reset-scopes]
1538
1592
  Sign in (an email with a link and a 6-digit code); keeps an API key for
1539
1593
  this machine in ~/.config/marten/credentials. Scopes: martens, tasks and
1540
1594
  registry by default; add chats:read and chats:write for your groups
@@ -1562,6 +1616,7 @@ var OPTIONS2 = {
1562
1616
  version: { type: "boolean", short: "v" },
1563
1617
  email: { type: "string" },
1564
1618
  scope: { type: "string", multiple: true },
1619
+ "reset-scopes": { type: "boolean" },
1565
1620
  timeout: { type: "string" },
1566
1621
  limit: { type: "string" },
1567
1622
  chat: { type: "string" },
@@ -1575,7 +1630,7 @@ var OPTIONS2 = {
1575
1630
  description: { type: "string" },
1576
1631
  remind: { type: "boolean" }
1577
1632
  };
1578
- var STATUSES2 = ["todo", "in_progress", "needs_review", "done"];
1633
+ var STATUSES2 = ["todo", "in_progress", "done"];
1579
1634
  var MAX_ASK_SECONDS = 120;
1580
1635
  function argsProblem(err) {
1581
1636
  const message = err.message;
@@ -1626,12 +1681,17 @@ function parseOutside(argv) {
1626
1681
  return done({ name: "help" });
1627
1682
  switch (first) {
1628
1683
  case "login": {
1629
- none([second], "marten login [--email <address>] [--scope <scope>…]");
1684
+ none([second], "marten login [--email <address>] [--scope <scope>…] [--reset-scopes]");
1630
1685
  const scopes = values.scope?.map((s) => s.trim()).filter(Boolean);
1631
1686
  const unknown = scopes?.find((s) => !SCOPES.includes(s));
1632
1687
  if (unknown)
1633
1688
  throw new UsageError(`Unknown scope ${unknown}: use ${SCOPES.join(", ")}`);
1634
- return done({ name: "login", ...values.email?.trim() ? { email: values.email.trim() } : {}, ...scopes?.length ? { scopes } : {} });
1689
+ return done({
1690
+ name: "login",
1691
+ ...values.email?.trim() ? { email: values.email.trim() } : {},
1692
+ ...scopes?.length ? { scopes } : {},
1693
+ ...values["reset-scopes"] ? { resetScopes: true } : {}
1694
+ });
1635
1695
  }
1636
1696
  case "logout":
1637
1697
  case "whoami":
@@ -1997,10 +2057,13 @@ ${content}${next}`, data: { path: wrote, content } };
1997
2057
  case "login": {
1998
2058
  const previous = await readCredentials(path);
1999
2059
  const apiUrl = io.env.MARTEN_API_URL || previous?.apiUrl || DEFAULT_API_URL;
2000
- const credentials = await login({ apiUrl, email: command.email, scopes: command.scopes, machine: io.machine, io, fetch: io.fetch });
2001
- if (previous && previous.apiKey !== credentials.apiKey)
2002
- await revoke(io, previous);
2060
+ const had = previous && !command.resetScopes ? await scopesOf(io, previous) : null;
2061
+ const scopes = (email) => [...new Set([...DEFAULT_SCOPES, ...had && had.email.toLowerCase() === email.toLowerCase() ? had.scopes : [], ...command.scopes ?? []])];
2062
+ const credentials = await login({ apiUrl, email: command.email, scopes, machine: io.machine, io, fetch: io.fetch });
2003
2063
  await writeCredentials(path, credentials);
2064
+ if (previous && previous.apiKey !== credentials.apiKey) {
2065
+ await revoke(io, previous).catch((err) => io.stderr(`marten: signed in, but couldn't revoke the previous key "${previous.keyName}" (${err.message}); revoke it from another signed-in machine with marten logout, or ask support`));
2066
+ }
2004
2067
  return { text: `Signed in as ${credentials.email}. This machine's key, "${credentials.keyName}", is in ${path}.`, data: { email: credentials.email, keyId: credentials.keyId, keyName: credentials.keyName, path } };
2005
2068
  }
2006
2069
  case "logout": {
@@ -2021,8 +2084,19 @@ ${content}${next}`, data: { path: wrote, content } };
2021
2084
  return runApi(command, { marten: client(io, await readCredentials(path)), readStdin: io.readStdin, timeZone: io.timeZone });
2022
2085
  }
2023
2086
  }
2087
+ var DEFAULT_SCOPES = ["martens", "tasks", "registry"];
2088
+ var PREVIOUS_KEY_TIMEOUT_MS = 5000;
2089
+ var previousClient = (io, stored) => new Marten({
2090
+ apiKey: stored.apiKey,
2091
+ baseUrl: stored.apiUrl,
2092
+ fetch: (url, init) => (io.fetch ?? fetch)(url, { ...init, signal: AbortSignal.timeout(PREVIOUS_KEY_TIMEOUT_MS) })
2093
+ });
2094
+ async function scopesOf(io, stored) {
2095
+ const me = await previousClient(io, stored).me().catch(() => null);
2096
+ return me ? { email: me.user.email, scopes: me.key.scopes } : null;
2097
+ }
2024
2098
  async function revoke(io, stored) {
2025
- await new Marten({ apiKey: stored.apiKey, baseUrl: stored.apiUrl, fetch: io.fetch }).revokeKey().catch((err) => {
2099
+ await previousClient(io, stored).revokeKey().catch((err) => {
2026
2100
  if (!(err instanceof MartenError && err.status === 401))
2027
2101
  throw err;
2028
2102
  });
@@ -2050,7 +2124,7 @@ function client(io, stored) {
2050
2124
  // package.json
2051
2125
  var package_default = {
2052
2126
  name: "@martenhq/cli",
2053
- version: "0.1.0",
2127
+ version: "0.1.1",
2054
2128
  description: "marten: talk to your Martens from your terminal, scripts and other agents (Claude Code, Codex), and the CLI Martens use inside their boxes.",
2055
2129
  license: "MIT",
2056
2130
  type: "module",
@@ -2059,7 +2133,7 @@ var package_default = {
2059
2133
  keywords: ["marten", "cli", "agents", "mcp"],
2060
2134
  engines: { node: ">=20" },
2061
2135
  bin: { marten: "dist/marten.js" },
2062
- files: ["dist/marten.js", "README.md", "LICENSE"],
2136
+ files: ["dist/marten.js", "README.md", "CHANGELOG.md", "LICENSE"],
2063
2137
  publishConfig: { access: "public" },
2064
2138
  scripts: {
2065
2139
  build: "bun build.ts",
package/package.json CHANGED
@@ -1,33 +1,23 @@
1
1
  {
2
2
  "name": "@martenhq/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "marten: talk to your Martens from your terminal, scripts and other agents (Claude Code, Codex), and the CLI Martens use inside their boxes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "homepage": "https://marten.sh",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/RedPlanetHQ/marten-app.git",
11
- "directory": "packages/cli"
12
- },
13
- "keywords": ["marten", "cli", "agents", "mcp"],
14
- "engines": {
15
- "node": ">=20"
16
- },
17
- "bin": {
18
- "marten": "dist/marten.js"
19
- },
20
- "files": ["dist/marten.js", "README.md", "LICENSE"],
21
- "publishConfig": {
22
- "access": "public"
23
- },
24
- "scripts": {
25
- "build": "bun build.ts",
26
- "test": "bun test",
27
- "typecheck": "tsc -p tsconfig.json"
28
- },
29
- "devDependencies": {
30
- "@martenhq/sdk": "0.1.0",
31
- "@types/bun": "^1.4.2"
32
- }
8
+ "repository": { "type": "git", "url": "git+https://github.com/RedPlanetHQ/marten-app.git", "directory": "packages/cli" },
9
+ "keywords": ["marten", "cli", "agents", "mcp"],
10
+ "engines": { "node": ">=20" },
11
+ "bin": { "marten": "dist/marten.js" },
12
+ "files": ["dist/marten.js", "README.md", "CHANGELOG.md", "LICENSE"],
13
+ "publishConfig": { "access": "public" },
14
+ "scripts": {
15
+ "build": "bun build.ts",
16
+ "test": "bun test",
17
+ "typecheck": "tsc -p tsconfig.json"
18
+ },
19
+ "devDependencies": {
20
+ "@martenhq/sdk": "workspace:*",
21
+ "@types/bun": "^1.4.2"
22
+ }
33
23
  }