teamclaws 2.0.6 → 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 +29 -0
- package/dist/app.js +20 -2
- package/dist/constants.js +1 -1
- package/dist/external-chat.js +23 -2
- package/dist/machine-key.js +10 -4
- package/dist/output.js +19 -0
- package/dist/startup.js +7 -0
- package/dist/storage.js +13 -3
- package/dist/update.js +3 -1
- package/dist/users.js +72 -0
- package/package.json +3 -1
- package/scripts/check-node.mjs +8 -0
- package/skills/teamclaws-chat/SKILL.md +6 -0
- package/skills/teamclaws-login/SKILL.md +3 -0
- package/skills/teamclaws-onboard/SKILL.md +3 -0
- package/skills/teamclaws-onboard/references/workspace-uses.md +3 -0
- package/skills/teamclaws-pair/SKILL.md +3 -0
- package/skills/teamclaws-project/SKILL.md +3 -0
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ with a TeamClaws workspace and stores the resulting Clawgate credential on that
|
|
|
5
5
|
computer. The command-line client and its agent skills ship together in this
|
|
6
6
|
package.
|
|
7
7
|
|
|
8
|
+
Pairing connects this computer to the workspace. It does not add a person as a
|
|
9
|
+
workspace user; user membership is a separate privilege.
|
|
10
|
+
|
|
8
11
|
## Requirements
|
|
9
12
|
|
|
10
13
|
- Node.js 22 or newer
|
|
@@ -14,6 +17,13 @@ package.
|
|
|
14
17
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
20
|
+
Check `node --version` first. If it is below 22, upgrade Node.js to version 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.
|
|
26
|
+
|
|
17
27
|
Install the published CLI:
|
|
18
28
|
|
|
19
29
|
```text
|
|
@@ -230,6 +240,19 @@ acceptance. Existing memberships and pending invitations are preserved without
|
|
|
230
240
|
promotion or resend. If a request has an uncertain network outcome, inspect the
|
|
231
241
|
invitation status before trying again; the command does not blindly retry it.
|
|
232
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
|
+
|
|
233
256
|
## Read chat history without a browser
|
|
234
257
|
|
|
235
258
|
```text
|
|
@@ -297,6 +320,12 @@ access is enforced by TeamClaws servers, not by the client license.
|
|
|
297
320
|
|
|
298
321
|
### Provider and model capabilities
|
|
299
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
|
+
|
|
300
329
|
Run `teamclaws chat capabilities <target> [--json]` with an existing saved
|
|
301
330
|
workspace pairing. It reads the workspace capabilities endpoint with a shared
|
|
302
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
package/dist/external-chat.js
CHANGED
|
@@ -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 =
|
|
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}
|
|
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/machine-key.js
CHANGED
|
@@ -73,7 +73,9 @@ export class OsMachineKeyProvider {
|
|
|
73
73
|
await assertPrivateDirectory(directory);
|
|
74
74
|
return directory;
|
|
75
75
|
}
|
|
76
|
-
catch {
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof TeamClawsError)
|
|
78
|
+
throw error;
|
|
77
79
|
throw keyUnavailable();
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -158,7 +160,9 @@ export class OsMachineKeyProvider {
|
|
|
158
160
|
dsaEncoding: "ieee-p1363",
|
|
159
161
|
}).toString("base64url");
|
|
160
162
|
}
|
|
161
|
-
catch {
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof TeamClawsError)
|
|
165
|
+
throw error;
|
|
162
166
|
throw keyUnavailable();
|
|
163
167
|
}
|
|
164
168
|
}
|
|
@@ -167,9 +171,11 @@ async function assertPrivateDirectory(path) {
|
|
|
167
171
|
const info = await lstat(path);
|
|
168
172
|
if (!info.isDirectory() ||
|
|
169
173
|
info.isSymbolicLink() ||
|
|
170
|
-
info.uid !== process.getuid?.()
|
|
171
|
-
(info.mode & 0o777) !== 0o700)
|
|
174
|
+
info.uid !== process.getuid?.())
|
|
172
175
|
throw keyUnavailable();
|
|
176
|
+
const mode = info.mode & 0o777;
|
|
177
|
+
if (mode !== 0o700)
|
|
178
|
+
throw new TeamClawsError("machine_key_unavailable", `The machine-key directory ${JSON.stringify(path)} has mode ${mode.toString(8).padStart(4, "0")}; mode 0700 is required. Run chmod 700 '${path.replaceAll("'", "'\\''")}' and retry.`, { exitCode: 9 });
|
|
173
179
|
}
|
|
174
180
|
async function assertPrivateFile(path) {
|
|
175
181
|
const info = await lstat(path);
|
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/startup.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { parseArguments, run } from "./app.js";
|
|
2
2
|
import { checkRelease, releaseNotice } from "./update.js";
|
|
3
|
+
import { TeamClawsError } from "./errors.js";
|
|
4
|
+
import { Output } from "./output.js";
|
|
3
5
|
export async function runCli(args, dependencies = {}, signal, check = checkRelease) {
|
|
6
|
+
if (Number(process.versions.node.split(".")[0]) < 22) {
|
|
7
|
+
const error = new TeamClawsError("unsupported_runtime", `TeamClaws requires Node.js 22 or newer; this runtime is ${process.versions.node}. Upgrade Node.js, then run npm install -g teamclaws@latest.`, { exitCode: 9 });
|
|
8
|
+
new Output(args.includes("--json"), dependencies.io?.stdout, dependencies.io?.stderr).error(error);
|
|
9
|
+
return error.exitCode;
|
|
10
|
+
}
|
|
4
11
|
// No persistent cache: every executable invocation checks before dispatch.
|
|
5
12
|
let releaseCheck;
|
|
6
13
|
try {
|
package/dist/storage.js
CHANGED
|
@@ -118,6 +118,13 @@ export class CredentialStore {
|
|
|
118
118
|
dirname(directory),
|
|
119
119
|
directory,
|
|
120
120
|
]) {
|
|
121
|
+
const info = await lstat(path);
|
|
122
|
+
if (this.platform !== "win32" &&
|
|
123
|
+
info.isDirectory() &&
|
|
124
|
+
!info.isSymbolicLink() &&
|
|
125
|
+
info.uid === process.getuid?.() &&
|
|
126
|
+
(info.mode & 0o077) !== 0)
|
|
127
|
+
throw conversationStorageError(`Directory ${JSON.stringify(path)} has mode ${(info.mode & 0o777).toString(8).padStart(4, "0")}; mode 0700 is required. Run chmod 700 '${path.replaceAll("'", "'\\''")}' and retry. `);
|
|
121
128
|
await this.assertPrivateDirectory(path);
|
|
122
129
|
}
|
|
123
130
|
}
|
|
@@ -150,7 +157,10 @@ export class CredentialStore {
|
|
|
150
157
|
}
|
|
151
158
|
return records.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
152
159
|
}
|
|
153
|
-
catch {
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error instanceof TeamClawsError &&
|
|
162
|
+
error.code === "chat_reference_storage_failed")
|
|
163
|
+
throw error;
|
|
154
164
|
throw conversationStorageError();
|
|
155
165
|
}
|
|
156
166
|
}
|
|
@@ -460,6 +470,6 @@ function parseConversationReference(value, credential) {
|
|
|
460
470
|
updated_at: value.updated_at,
|
|
461
471
|
};
|
|
462
472
|
}
|
|
463
|
-
function conversationStorageError() {
|
|
464
|
-
return new TeamClawsError("chat_reference_storage_failed",
|
|
473
|
+
function conversationStorageError(detail = "") {
|
|
474
|
+
return new TeamClawsError("chat_reference_storage_failed", `${detail}The local conversation references could not be read or saved safely. Retain any returned session/request identifiers; do not resend the message automatically.`, { exitCode: 9 });
|
|
465
475
|
}
|
package/dist/update.js
CHANGED
|
@@ -143,7 +143,9 @@ export async function npmRunner(nodeExecutable = process.execPath, platform = pr
|
|
|
143
143
|
return async (args, timeout, signal) => {
|
|
144
144
|
const env = { ...process.env };
|
|
145
145
|
for (const key of Object.keys(env)) {
|
|
146
|
-
if (/^npm_config_/i.test(key)
|
|
146
|
+
if ((/^npm_config_/i.test(key) &&
|
|
147
|
+
!(args[0] === "prefix" &&
|
|
148
|
+
/^npm_config_(prefix|userconfig|globalconfig)$/i.test(key))) ||
|
|
147
149
|
/^(NODE_OPTIONS|NODE_PATH)$/i.test(key))
|
|
148
150
|
delete env[key];
|
|
149
151
|
}
|
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.
|
|
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:
|
|
@@ -16,6 +16,9 @@ pairing approval come next when they are connecting a workspace. If onboarding
|
|
|
16
16
|
already explained the process, use a short transition into this step. Give the
|
|
17
17
|
approval link with that context, never as an unexplained authorization request.
|
|
18
18
|
|
|
19
|
+
Pairing connects this computer to the workspace. It does not add a person as a
|
|
20
|
+
workspace user; user membership is a separate privilege.
|
|
21
|
+
|
|
19
22
|
Use the official executable:
|
|
20
23
|
|
|
21
24
|
```text
|
|
@@ -37,6 +37,9 @@ or add another permission question for an already authorized step. Describe the
|
|
|
37
37
|
separate server environment accurately; inspect an existing workspace before
|
|
38
38
|
promising it is empty or that a particular isolation mechanism is configured.
|
|
39
39
|
|
|
40
|
+
Pairing connects this computer to the workspace. It does not add a person as a
|
|
41
|
+
workspace user; user membership is a separate privilege.
|
|
42
|
+
|
|
40
43
|
## Sign in
|
|
41
44
|
|
|
42
45
|
Run `teamclaws auth status --json`. If signed in, confirm the returned account
|
|
@@ -122,6 +122,9 @@ and the selected access behavior before providing a working URL.
|
|
|
122
122
|
|
|
123
123
|
## Share work with other people
|
|
124
124
|
|
|
125
|
+
Pairing connects this computer to the workspace. It does not add a person as a
|
|
126
|
+
workspace user; user membership is a separate privilege.
|
|
127
|
+
|
|
125
128
|
Distinguish inviting someone to a workspace from sharing a project's web
|
|
126
129
|
address. Workspace collaboration depends on the supported TeamClaws sharing
|
|
127
130
|
controls and the recipient's assigned access. A public app URL is available
|
|
@@ -11,6 +11,9 @@ For the full install-and-connect conversation, follow
|
|
|
11
11
|
Use the official `teamclaws` executable. Do not implement pairing inside this
|
|
12
12
|
skill.
|
|
13
13
|
|
|
14
|
+
Pairing connects this computer to the workspace. It does not add a person as a
|
|
15
|
+
workspace user; user membership is a separate privilege.
|
|
16
|
+
|
|
14
17
|
## Required boundary
|
|
15
18
|
|
|
16
19
|
- If workspace discovery was performed through a model context protocol client,
|
|
@@ -20,6 +20,9 @@ carry that task through instead of restarting a generic onboarding conversation.
|
|
|
20
20
|
Inspect the destination's instructions, available runtimes, resources, network
|
|
21
21
|
access, and account permissions through the paired shell before choosing a setup.
|
|
22
22
|
|
|
23
|
+
Pairing connects this computer to the workspace. It does not add a person as a
|
|
24
|
+
workspace user; user membership is a separate privilege.
|
|
25
|
+
|
|
23
26
|
## Jobs, services, and external systems
|
|
24
27
|
|
|
25
28
|
For a complete job, place its code and authorized inputs in a dedicated directory,
|