teamclaws 2.0.7 → 2.0.8

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/README.md CHANGED
@@ -18,10 +18,11 @@ workspace user; user membership is a separate privilege.
18
18
  ## Installation
19
19
 
20
20
  Check `node --version` first. If it is below 22, upgrade Node.js to version 22
21
- or newer before installing. npm normally treats the package engine requirement
22
- as a warning; `npm install -g teamclaws@latest --engine-strict` rejects an
23
- unsupported Node.js version during installation. The CLI also refuses to run on
24
- Node.js below 22.
21
+ or newer before installing. The install script rejects Node.js below 22 during
22
+ an ordinary npm install. npm's `--ignore-scripts` or an installer policy that
23
+ disables lifecycle scripts bypasses this install check; the CLI still refuses
24
+ to run on Node.js below 22. `--engine-strict` also enforces the declared engine
25
+ requirement without changing global npm settings.
25
26
 
26
27
  Install the published CLI:
27
28
 
@@ -239,6 +240,19 @@ acceptance. Existing memberships and pending invitations are preserved without
239
240
  promotion or resend. If a request has an uncertain network outcome, inspect the
240
241
  invitation status before trying again; the command does not blindly retry it.
241
242
 
243
+ ### Inspect users and invitation status
244
+
245
+ ```text
246
+ teamclaws users list <target> --json
247
+ ```
248
+
249
+ This read requires the current machine owner's active paired authorization.
250
+ It lists current members separately from pending and accepted invitation
251
+ records. An accepted invitation is historical evidence of acceptance; current
252
+ membership determines whether the person still has access. An expired pending
253
+ invitation is marked expired. Listing sends no invitations and changes no roles,
254
+ memberships, or invitation state. It requires a workspace with the users-list API.
255
+
242
256
  ## Read chat history without a browser
243
257
 
244
258
  ```text
@@ -306,6 +320,12 @@ access is enforced by TeamClaws servers, not by the client license.
306
320
 
307
321
  ### Provider and model capabilities
308
322
 
323
+ If the workspace confirms `no_provider_paired`, open its `/teamclaws-admin/`
324
+ page, choose **Link AI Account**, and connect a provider. For Cursor, choose
325
+ **Cursor**, then **My Subscription**, and complete browser authorization.
326
+ An unavailable provider status or a transport failure does not prove that no
327
+ provider is paired.
328
+
309
329
  Run `teamclaws chat capabilities <target> [--json]` with an existing saved
310
330
  workspace pairing. It reads the workspace capabilities endpoint with a shared
311
331
  120-second budget, attempts lasting at most 15 seconds, and pauses of up to five
package/dist/app.js CHANGED
@@ -11,7 +11,7 @@ import { CredentialStore } from "./storage.js";
11
11
  import { TargetAuthorization } from "./target-authorization.js";
12
12
  import { installRelease } from "./update.js";
13
13
  import { normalizeUuid, parsePairingTarget } from "./validation.js";
14
- import { runUsersAdd } from "./users.js";
14
+ import { runUsersAdd, runUsersList } from "./users.js";
15
15
  export async function run(arguments_, dependencies = {}, signal) {
16
16
  let parsed;
17
17
  try {
@@ -71,6 +71,18 @@ export async function run(arguments_, dependencies = {}, signal) {
71
71
  }
72
72
  const store = dependencies.store ?? new CredentialStore();
73
73
  const targetAuthorization = new TargetAuthorization(store, dependencies.fetcher);
74
+ if (parsed.action === "users_list") {
75
+ try {
76
+ await targetAuthorization.requireGrant();
77
+ output.usersListResult(await runUsersList(store, dependencies.fetcher ?? fetch, parsed.target, targetAuthorization, signal));
78
+ return 0;
79
+ }
80
+ catch (error) {
81
+ const safe = safeError(error);
82
+ output.error(safe);
83
+ return safe.exitCode;
84
+ }
85
+ }
74
86
  if (parsed.action === "users_add") {
75
87
  try {
76
88
  await targetAuthorization.requireGrant();
@@ -231,6 +243,12 @@ export function parseArguments(arguments_) {
231
243
  }
232
244
  const json = arguments_.includes("--json");
233
245
  const args = arguments_.filter((argument) => argument !== "--json");
246
+ if (args[0] === "users" && args[1] === "list" && args.length === 3)
247
+ return {
248
+ action: "users_list",
249
+ json,
250
+ target: parsePairingTarget(requiredPositional(args, 2)),
251
+ };
234
252
  if (args[0] === "users" && args[1] === "add") {
235
253
  const target = parsePairingTarget(requiredPositional(args, 2));
236
254
  const email = requiredPositional(args, 3).trim().toLowerCase();
@@ -532,5 +550,5 @@ function usageError() {
532
550
  });
533
551
  }
534
552
  export function helpText() {
535
- return `TeamClaws ${PACKAGE_VERSION}\n\nUsage:\n teamclaws update [--json]\n teamclaws shell <target> --command <text> [--json]\n teamclaws users add <target> <email> --role <user|staff|superuser> [--json]\n teamclaws login [email] [--json]\n teamclaws auth status [--json]\n teamclaws logout [--revoke] [--json]\n teamclaws servers list [--json]\n teamclaws servers update <server-id> [--name <text>] [--description <text>] [--json]\n teamclaws pair status <server-identifier-or-canonical-workspace-address> [--json]\n teamclaws pair [server-identifier-or-canonical-workspace-address] [--no-open] [--json]\n teamclaws chat list <target> [--json]\n teamclaws chat history <target> [--json]\n teamclaws chat messages <target> --chat-id <uuid> [--after-message-id <n>] [--json]\n teamclaws chat capabilities <target> [--json]\n teamclaws chat start <target> --message <text> [selection options] [--json]\n teamclaws chat send <target> --session-id <uuid> --message <text> [--json]\n teamclaws chat watch <target> --session-id <uuid> --request-id <uuid> [resume cursors] [--json]\n\nPair status performs one bounded direct verification and stops. Pairing grants the standard host-issued credential. Chat commands use that credential directly against the selected workspace.\n`;
553
+ return `TeamClaws ${PACKAGE_VERSION}\n\nUsage:\n teamclaws update [--json]\n teamclaws shell <target> --command <text> [--json]\n teamclaws users add <target> <email> --role <user|staff|superuser> [--json]\n teamclaws users list <target> [--json]\n teamclaws login [email] [--json]\n teamclaws auth status [--json]\n teamclaws logout [--revoke] [--json]\n teamclaws servers list [--json]\n teamclaws servers update <server-id> [--name <text>] [--description <text>] [--json]\n teamclaws pair status <server-identifier-or-canonical-workspace-address> [--json]\n teamclaws pair [server-identifier-or-canonical-workspace-address] [--no-open] [--json]\n teamclaws chat list <target> [--json]\n teamclaws chat history <target> [--json]\n teamclaws chat messages <target> --chat-id <uuid> [--after-message-id <n>] [--json]\n teamclaws chat capabilities <target> [--json]\n teamclaws chat start <target> --message <text> [selection options] [--json]\n teamclaws chat send <target> --session-id <uuid> --message <text> [--json]\n teamclaws chat watch <target> --session-id <uuid> --request-id <uuid> [resume cursors] [--json]\n\nPair status performs one bounded direct verification and stops. Pairing grants the standard host-issued credential. Chat commands use that credential directly against the selected workspace.\n`;
536
554
  }
package/dist/constants.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PACKAGE_NAME = "teamclaws";
2
- export const PACKAGE_VERSION = "2.0.7";
2
+ export const PACKAGE_VERSION = "2.0.8";
3
3
  export const CLIENT_NAME = "teamclaws-cli";
4
4
  export const CONTRACT_VERSION = 1;
5
5
  export const OUTPUT_SCHEMA_VERSION = 1;
@@ -88,6 +88,11 @@ export class ExternalChatClient {
88
88
  });
89
89
  }
90
90
  const inventory = parseCapabilityInventory(response.body);
91
+ if (inventory.provider_availability?.code === "no_provider_paired") {
92
+ if (inventory.providers.length !== 0)
93
+ throw invalidChatResponse();
94
+ throw new TeamClawsError("no_provider_paired", `No AI provider is paired for this workspace. Open ${credential.origin}/teamclaws-admin/, choose Link AI Account, and connect a provider. For Cursor, choose Cursor, then My Subscription.`, { exitCode: 5 });
95
+ }
91
96
  if (this.now() >= deadline)
92
97
  break;
93
98
  if (inventory.providers.some((provider) => provider.models.length > 0)) {
@@ -99,7 +104,10 @@ export class ExternalChatClient {
99
104
  external_chat_capabilities: inventory,
100
105
  };
101
106
  }
102
- lastFailure = "The workspace returned no provider/model choices.";
107
+ lastFailure =
108
+ inventory.provider_availability?.code === "provider_unavailable"
109
+ ? "AI provider status is unavailable."
110
+ : "The workspace returned no provider/model choices.";
103
111
  }
104
112
  catch (error) {
105
113
  canceled();
@@ -121,7 +129,7 @@ export class ExternalChatClient {
121
129
  await this.wait(Math.min(5_000, remaining), signal);
122
130
  }
123
131
  canceled();
124
- throw new TeamClawsError("capabilities_unavailable", `${lastFailure} Log into TeamClaws and ensure the server is running.`, { exitCode: 5, retryable: true });
132
+ throw new TeamClawsError("capabilities_unavailable", `${lastFailure} Check workspace connectivity or the provider connections at ${credential.origin}/teamclaws-admin/ and retry.`, { exitCode: 5, retryable: true });
125
133
  }
126
134
  async start(options, signal) {
127
135
  const context = await this.context(options.target, signal, true);
@@ -506,8 +514,21 @@ export function parseCapabilityInventory(value) {
506
514
  !isObject(value.identity) ||
507
515
  !Array.isArray(value.approved_scopes))
508
516
  throw invalidChatResponse();
517
+ let availability;
518
+ if (value.provider_availability !== undefined) {
519
+ const state = value.provider_availability;
520
+ if (!isObject(state) ||
521
+ !["ready", "no_provider_paired", "provider_unavailable"].includes(String(state.code)) ||
522
+ state.setup_path !== "/teamclaws-admin/")
523
+ throw invalidChatResponse();
524
+ availability = {
525
+ code: state.code,
526
+ setup_path: "/teamclaws-admin/",
527
+ };
528
+ }
509
529
  return {
510
530
  contract_version: 1,
531
+ ...(availability ? { provider_availability: availability } : {}),
511
532
  identity: {
512
533
  alias: requireString(value.identity.alias, "external identity alias", 200),
513
534
  },
package/dist/output.js CHANGED
@@ -405,6 +405,25 @@ export class Output {
405
405
  this.stdout.write(`${messages[result.code]}\n`);
406
406
  }
407
407
  }
408
+ usersListResult(result) {
409
+ this.event({
410
+ schema_version: OUTPUT_SCHEMA_VERSION,
411
+ event: "users_list_result",
412
+ ...result,
413
+ });
414
+ if (!this.json) {
415
+ this.stdout.write("Current members:\n");
416
+ for (const member of result.members)
417
+ this.stdout.write(`${member.email}: ${member.role}${member.is_owner ? " (owner)" : ""}\n`);
418
+ if (!result.members.length)
419
+ this.stdout.write("No members returned.\n");
420
+ this.stdout.write("Invitations:\n");
421
+ for (const invite of result.invites)
422
+ this.stdout.write(`${invite.email}: ${invite.status}${invite.is_expired && invite.status === "pending" ? " (expired)" : ""} — ${invite.role}\n`);
423
+ if (!result.invites.length)
424
+ this.stdout.write("No pending or accepted invitations.\n");
425
+ }
426
+ }
408
427
  event(event) {
409
428
  if (this.json)
410
429
  this.stdout.write(`${JSON.stringify(event)}\n`);
package/dist/users.js CHANGED
@@ -19,6 +19,78 @@ const failureMessages = {
19
19
  invite_delivery_failed: "The invitation could not be delivered. Check its status before retrying.",
20
20
  outcome_unverified: "The invitation outcome could not be verified. Check its status before retrying.",
21
21
  };
22
+ export async function runUsersList(store, fetcher, target, authorization, signal) {
23
+ const credential = await store.findWorkspace(target);
24
+ if (!credential)
25
+ throw new TeamClawsError("re_pair_required", "No saved pairing was found for this workspace. Pair it first.", { exitCode: 3 });
26
+ const response = await requestTargetJson(fetcher, authorization, `${credential.origin}/teamclaws-admin/api/device-users/v1/list/`, credential, { ...(signal ? { signal } : {}) });
27
+ const body = response.body;
28
+ if (response.status !== 200 || (isObject(body) && body.ok === false)) {
29
+ const messages = {
30
+ invalid_actor_token: failureMessages.invalid_actor_token,
31
+ unauthorized: failureMessages.unauthorized,
32
+ owner_required: "Only the current machine owner can inspect users and invitations.",
33
+ forbidden: "The paired identity is not authorized to inspect users and invitations.",
34
+ actor_machine_mismatch: failureMessages.actor_machine_mismatch,
35
+ actor_authorization_unavailable: failureMessages.actor_authorization_unavailable,
36
+ authorization_unavailable: failureMessages.authorization_unavailable,
37
+ machine_auth_failed: failureMessages.machine_auth_failed,
38
+ machine_ip_rejected: failureMessages.machine_ip_rejected,
39
+ };
40
+ const rawCode = isObject(body) ? body.code : undefined;
41
+ const code = typeof rawCode === "string" && Object.hasOwn(messages, rawCode)
42
+ ? rawCode
43
+ : [404, 405].includes(response.status)
44
+ ? "users_list_unavailable"
45
+ : "users_list_failed";
46
+ throw new TeamClawsError(code, messages[code] ??
47
+ (code === "users_list_unavailable"
48
+ ? "User and invitation status inspection is unavailable on this workspace."
49
+ : "The workspace could not retrieve user and invitation status. Retry the read request."), {
50
+ exitCode: response.status === 401 ? 3 : response.status === 429 ? 5 : 1,
51
+ });
52
+ }
53
+ const invalid = () => new TeamClawsError("invalid_response", "The workspace returned invalid user or invitation status.", { exitCode: 9 });
54
+ if (!isObject(body) ||
55
+ body.ok !== true ||
56
+ body.action !== "list" ||
57
+ body.scope !== "access_group" ||
58
+ !Array.isArray(body.members) ||
59
+ !Array.isArray(body.invites))
60
+ throw invalid();
61
+ const validEmail = (value) => typeof value === "string" &&
62
+ value.length > 0 &&
63
+ value.length <= 254 &&
64
+ ![...value].some((character) => character.charCodeAt(0) < 0x20 || character.charCodeAt(0) === 0x7f);
65
+ const roles = ["user", "staff", "superuser"];
66
+ const members = body.members.map((row) => {
67
+ if (!isObject(row) ||
68
+ !validEmail(row.email) ||
69
+ !roles.includes(String(row.role)) ||
70
+ typeof row.is_owner !== "boolean")
71
+ throw invalid();
72
+ return {
73
+ email: row.email,
74
+ role: row.role,
75
+ is_owner: row.is_owner,
76
+ };
77
+ });
78
+ const invites = body.invites.map((row) => {
79
+ if (!isObject(row) ||
80
+ !validEmail(row.email) ||
81
+ !roles.includes(String(row.role)) ||
82
+ !["pending", "accepted"].includes(String(row.status)) ||
83
+ typeof row.is_expired !== "boolean")
84
+ throw invalid();
85
+ return {
86
+ email: row.email,
87
+ role: row.role,
88
+ status: row.status,
89
+ is_expired: row.is_expired,
90
+ };
91
+ });
92
+ return { ok: true, action: "list", scope: "access_group", members, invites };
93
+ }
22
94
  export async function runUsersAdd(store, fetcher, target, email, role, authorization, signal) {
23
95
  const credential = await store.findWorkspace(target);
24
96
  if (!credential)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamclaws",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Official TeamClaws command-line client and agent skills",
5
5
  "license": "PolyForm-Shield-1.0.0",
6
6
  "homepage": "https://teamclaws.com",
@@ -17,12 +17,14 @@
17
17
  "teamclaws": "./dist/cli.js"
18
18
  },
19
19
  "files": [
20
+ "scripts/check-node.mjs",
20
21
  "dist/",
21
22
  "skills/",
22
23
  "LICENSE",
23
24
  "README.md"
24
25
  ],
25
26
  "scripts": {
27
+ "preinstall": "node scripts/check-node.mjs",
26
28
  "build": "tsc -p tsconfig.build.json",
27
29
  "typecheck": "tsc -p tsconfig.json --noEmit",
28
30
  "lint": "eslint .",
@@ -0,0 +1,8 @@
1
+ import process from "node:process";
2
+
3
+ if (Number(process.versions.node.split(".")[0]) < 22) {
4
+ process.stderr.write(
5
+ `TeamClaws requires Node.js 22 or newer; this runtime is ${process.versions.node}. Upgrade Node.js before installing TeamClaws.\n`,
6
+ );
7
+ process.exitCode = 1;
8
+ }
@@ -51,6 +51,12 @@ use either as evidence that a conversation has no follow-ups. Never send a
51
51
  message, start another chat, or use the browser just to read history. An HTTP
52
52
  error or incomplete pagination is not evidence of an empty history.
53
53
 
54
+ On `no_provider_paired`, explain that no AI provider is paired for this workspace.
55
+ Open the verified workspace's `/teamclaws-admin/` page and choose **Link AI
56
+ Account**. For Cursor, choose **Cursor**, then **My Subscription**, and complete
57
+ browser authorization. Do not describe an unavailable inventory or a transport
58
+ failure as proof that no provider is paired.
59
+
54
60
  ## Start one conversation
55
61
 
56
62
  Obtain a non-empty user message, then run: