teamclaws 2.0.1 → 2.0.3

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
@@ -213,6 +213,30 @@ Python helper. Use `--client-request-id <uuid>` to safely repeat an uncertain
213
213
  create request, and use the watch cursors printed in JSON events to resume
214
214
  monitoring without creating another turn.
215
215
 
216
+ ## Read chat history without a browser
217
+
218
+ ```text
219
+ teamclaws chat history <target> --json
220
+ teamclaws chat messages <target> --chat-id <chat-public-uuid> --json
221
+ teamclaws chat messages <target> --chat-id <chat-public-uuid> --after-message-id <last-read-id> --json
222
+ ```
223
+
224
+ The first command lists chats from the workspace; the messages command reads
225
+ their content and later follow-ups. Both commands automatically read every page.
226
+ Use `chat_id` from the list or `chat_public_id` from a conversation URL. Save
227
+ `latest_message_id` to read only later messages next time.
228
+
229
+ This access is standard for new and existing pairings. The workspace owner's
230
+ agents can read every user's chats on the workspace. Other users' agents,
231
+ including superusers' agents, can read every chat belonging to their own user.
232
+ Archived, deleted, empty, and browser-created chats are included. There is no
233
+ optional history grant or model requirement. These commands do not create chats,
234
+ send messages, or mark conversations viewed. Clawgate must include the history
235
+ endpoints; older servers return an error rather than an empty history.
236
+
237
+ `chat list` only lists local request references. `chat watch` only reads responses
238
+ to a specific request, so neither command checks for later conversation turns.
239
+
216
240
  ## Host shell commands
217
241
 
218
242
  For authorized workspace administration, first check pairing status with
package/dist/app.js CHANGED
@@ -146,6 +146,10 @@ export async function run(arguments_, dependencies = {}, signal) {
146
146
  }
147
147
  case "chat_list":
148
148
  return await externalChat.list(parsePairingTarget(parsed.target), signal);
149
+ case "chat_history":
150
+ return await externalChat.history(parsePairingTarget(parsed.target), signal);
151
+ case "chat_messages":
152
+ return await externalChat.messages(parsePairingTarget(parsed.target), parsed.chatId, parsed.afterId, signal);
149
153
  case "chat_capabilities":
150
154
  return await externalChat.capabilities(parsePairingTarget(parsed.target), signal);
151
155
  case "chat_start":
@@ -257,6 +261,29 @@ export function parseArguments(arguments_) {
257
261
  if (args[0] === "chat" && args[1] === "list" && args.length === 3) {
258
262
  return { action: "chat_list", json, target: requiredPositional(args, 2) };
259
263
  }
264
+ if (args[0] === "chat" && args[1] === "history" && args.length === 3) {
265
+ return {
266
+ action: "chat_history",
267
+ json,
268
+ target: requiredPositional(args, 2),
269
+ };
270
+ }
271
+ if (args[0] === "chat" && args[1] === "messages") {
272
+ const target = requiredPositional(args, 2);
273
+ const options = parseNamedOptions(args.slice(3), {
274
+ values: ["--chat-id", "--after-message-id"],
275
+ booleans: [],
276
+ });
277
+ if (!options.values["--chat-id"])
278
+ throw usageError();
279
+ return {
280
+ action: "chat_messages",
281
+ json,
282
+ target,
283
+ chatId: normalizeUuid(options.values["--chat-id"]),
284
+ afterId: nonNegativeInteger(options.values["--after-message-id"]),
285
+ };
286
+ }
260
287
  if (args[0] === "chat" && args[1] === "capabilities" && args.length === 3) {
261
288
  return { action: "chat_capabilities", json, target: args[2] };
262
289
  }
@@ -471,5 +498,5 @@ function usageError() {
471
498
  });
472
499
  }
473
500
  export function helpText() {
474
- return `TeamClaws ${PACKAGE_VERSION}\n\nUsage:\n teamclaws update [--json]\n teamclaws shell <target> --command <text> [--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 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`;
501
+ return `TeamClaws ${PACKAGE_VERSION}\n\nUsage:\n teamclaws update [--json]\n teamclaws shell <target> --command <text> [--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`;
475
502
  }
package/dist/constants.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PACKAGE_NAME = "teamclaws";
2
- export const PACKAGE_VERSION = "2.0.1";
2
+ export const PACKAGE_VERSION = "2.0.3";
3
3
  export const CLIENT_NAME = "teamclaws-cli";
4
4
  export const CONTRACT_VERSION = 1;
5
5
  export const OUTPUT_SCHEMA_VERSION = 1;
@@ -172,6 +172,84 @@ export class ExternalChatClient {
172
172
  const requestId = validUuid(options.requestId);
173
173
  return this.poll(context, { requestId, sessionId }, options.knownRevision, options.responseCursor, signal);
174
174
  }
175
+ async historyCredential(target, signal) {
176
+ const credential = await this.store.findWorkspace(target);
177
+ if (!credential)
178
+ throw new TeamClawsError("re_pair_required", "No saved pairing was found for this workspace. Pair it first.", { exitCode: 3 });
179
+ const response = await this.request(`${credential.origin}/teamclaws-admin/api/device-pairings/me/`, credential, {}, [200], signal);
180
+ parseVerifiedDevice(response.body, credential.device_id);
181
+ return credential;
182
+ }
183
+ async history(target, signal) {
184
+ const credential = await this.historyCredential(target, signal);
185
+ let afterId = 0;
186
+ while (true) {
187
+ const response = await this.request(`${credential.origin}/teamclaws-admin/api/device-chat/v1/chats/?after_id=${afterId}`, credential, {}, [200], signal);
188
+ const body = response.body;
189
+ if (!isObject(body) || !Array.isArray(body.chats))
190
+ throw invalidChatResponse();
191
+ const chats = body.chats.map((chat) => {
192
+ if (!isObject(chat))
193
+ throw invalidChatResponse();
194
+ const url = requireHttpsUrl(chat.owner_conversation_url, "conversation address");
195
+ if (url.origin !== credential.origin)
196
+ throw invalidChatResponse();
197
+ return {
198
+ chat_id: validUuid(chat.chat_id),
199
+ name: historyText(chat.name),
200
+ surface: historyText(chat.surface),
201
+ created_at: historyText(chat.created_at),
202
+ updated_at: historyText(chat.updated_at),
203
+ archived_at: chat.archived_at === null ? null : historyText(chat.archived_at),
204
+ deleted_at: chat.deleted_at === null ? null : historyText(chat.deleted_at),
205
+ owner_conversation_url: url.toString(),
206
+ };
207
+ });
208
+ const next = body.next_after_id === null
209
+ ? null
210
+ : requireInteger(body.next_after_id, "history cursor", afterId + 1, Number.MAX_SAFE_INTEGER);
211
+ if (next !== null && chats.length === 0)
212
+ throw invalidChatResponse();
213
+ this.output.chatHistory(chats, next);
214
+ if (next === null)
215
+ return 0;
216
+ afterId = next;
217
+ }
218
+ }
219
+ async messages(target, chatId, afterId, signal) {
220
+ chatId = validUuid(chatId);
221
+ const credential = await this.historyCredential(target, signal);
222
+ while (true) {
223
+ const response = await this.request(`${credential.origin}/teamclaws-admin/api/device-chat/v1/chats/${chatId}/messages/?after_id=${afterId}`, credential, {}, [200], signal);
224
+ const body = response.body;
225
+ if (!isObject(body) ||
226
+ body.chat_id !== chatId ||
227
+ !Array.isArray(body.messages) ||
228
+ typeof body.has_more !== "boolean")
229
+ throw invalidChatResponse();
230
+ let previousId = afterId;
231
+ const messages = body.messages.map((message) => {
232
+ if (!isObject(message))
233
+ throw invalidChatResponse();
234
+ const id = requireInteger(message.id, "message identifier", previousId + 1, Number.MAX_SAFE_INTEGER);
235
+ previousId = id;
236
+ return {
237
+ id,
238
+ role: historyText(message.role),
239
+ body_text: historyText(message.body_text),
240
+ status: historyText(message.status),
241
+ created_at: historyText(message.created_at),
242
+ };
243
+ });
244
+ const latest = requireInteger(body.latest_message_id, "message cursor", afterId, Number.MAX_SAFE_INTEGER);
245
+ if (latest !== previousId || (body.has_more && messages.length === 0))
246
+ throw invalidChatResponse();
247
+ this.output.chatMessages(chatId, messages, latest, body.has_more);
248
+ if (!body.has_more)
249
+ return 0;
250
+ afterId = latest;
251
+ }
252
+ }
175
253
  async list(target, signal) {
176
254
  const credential = await this.store.findWorkspace(target);
177
255
  if (!credential)
@@ -568,6 +646,11 @@ function requireOwnerUrl(value, origin) {
568
646
  function validUuid(value) {
569
647
  return normalizeUuid(value, "identifier");
570
648
  }
649
+ function historyText(value) {
650
+ if (typeof value !== "string")
651
+ throw invalidChatResponse();
652
+ return value;
653
+ }
571
654
  function invalidChatResponse() {
572
655
  return new TeamClawsError("invalid_external_chat_response", "The workspace returned an invalid external chat response.", { exitCode: 9 });
573
656
  }
package/dist/output.js CHANGED
@@ -233,6 +233,34 @@ export class Output {
233
233
  ? `Snapshot generated: ${generatedAt}.\n`
234
234
  : "Generation time is not supplied by the capabilities endpoint.\n");
235
235
  }
236
+ chatHistory(chats, nextAfterId) {
237
+ this.event({
238
+ schema_version: OUTPUT_SCHEMA_VERSION,
239
+ event: "chat_history",
240
+ status: "success",
241
+ source: "workspace",
242
+ chats,
243
+ next_after_id: nextAfterId,
244
+ });
245
+ if (!this.json)
246
+ for (const chat of chats)
247
+ this.stdout.write(`${chat.chat_id} — ${chat.name} — ${chat.owner_conversation_url}\n`);
248
+ }
249
+ chatMessages(chatId, messages, latestMessageId, hasMore) {
250
+ this.event({
251
+ schema_version: OUTPUT_SCHEMA_VERSION,
252
+ event: "chat_messages",
253
+ status: "success",
254
+ source: "workspace",
255
+ chat_id: chatId,
256
+ messages,
257
+ latest_message_id: latestMessageId,
258
+ has_more: hasMore,
259
+ });
260
+ if (!this.json)
261
+ for (const message of messages)
262
+ this.stdout.write(`${message.id} ${message.role}: ${message.body_text}\n`);
263
+ }
236
264
  chatSessionsList(workspace, records) {
237
265
  const sessions = records.map((record) => ({
238
266
  session_id: record.session_id,
package/dist/pairing.js CHANGED
@@ -153,6 +153,7 @@ export class PairingClient {
153
153
  const response = await requestJson(this.fetcher, pollUrl, {
154
154
  method: "POST",
155
155
  body: { device_code: pending.device_code },
156
+ timeoutMs: Math.max(1, deadline - this.now()),
156
157
  ...(signal ? { signal } : {}),
157
158
  });
158
159
  if (response.status >= 200 && response.status < 300) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamclaws",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
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",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: teamclaws-chat
3
- description: Start, continue, or resume one external-agent TCChat through the official TeamClaws CLI after this computer is paired with the exact workspace.
3
+ description: Read workspace chat history or start, continue, and resume an external-agent TCChat through the official TeamClaws CLI after this computer is paired with the exact workspace.
4
4
  ---
5
5
 
6
6
  # TeamClaws external chat
@@ -20,6 +20,37 @@ transport, idempotency, and durable request polling.
20
20
  - Treat the newline-delimited JSON as an allowlisted protocol. Never expose raw
21
21
  HTTP responses or provider-native identifiers.
22
22
 
23
+ ## Read chats and follow-up messages without a browser
24
+
25
+ For conversation history, new messages, or follow-ups, use these read-only commands:
26
+
27
+ ```text
28
+ teamclaws chat history <target> --json
29
+ teamclaws chat messages <target> --chat-id <chat-public-uuid> --json
30
+ teamclaws chat messages <target> --chat-id <chat-public-uuid> --after-message-id <last-read-id> --json
31
+ ```
32
+
33
+ `chat history` queries the workspace and emits `chat_history` pages containing
34
+ `chats`, including browser-created, archived, empty, and deleted chats. Use the
35
+ returned `chat_id`, or the `chat_public_id` in the user's conversation URL, with
36
+ `chat messages`. It emits `chat_messages` pages containing every returned role
37
+ and message body, including owner follow-ups and subsequent agent replies.
38
+ Both commands fetch every page automatically. Save the final
39
+ `latest_message_id` to check for later messages. A successful empty messages
40
+ result means no messages after that cursor in that chat at the time of the read.
41
+
42
+ History access is standard for existing and new valid pairings. There is no
43
+ optional history permission, browser approval, model selection, or new pairing
44
+ step. The workspace owner's paired agents can read every user's chats on that
45
+ workspace. Other paired agents, including superusers' agents, can read every
46
+ chat belonging to their own user. The workspace enforces this distinction.
47
+
48
+ `chat list` is a local request-reference index, not a workspace chat inventory.
49
+ `chat watch` reads replies to one request, not later conversation turns. Do not
50
+ use either as evidence that a conversation has no follow-ups. Never send a
51
+ message, start another chat, or use the browser just to read history. An HTTP
52
+ error or incomplete pagination is not evidence of an empty history.
53
+
23
54
  ## Start one conversation
24
55
 
25
56
  Obtain a non-empty user message, then run: