@plosson/agentio 0.5.15 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plosson/agentio",
3
- "version": "0.5.15",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for LLM agents to interact with communication and tracking services",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/auth/oauth.ts CHANGED
@@ -15,6 +15,7 @@ const GCHAT_SCOPES = [
15
15
  'https://www.googleapis.com/auth/chat.messages.readonly', // read messages (get operations)
16
16
  'https://www.googleapis.com/auth/chat.spaces.readonly', // read space info and list
17
17
  'https://www.googleapis.com/auth/chat.memberships.readonly', // read space members
18
+ 'https://www.googleapis.com/auth/directory.readonly', // resolve user IDs to names/emails via People API
18
19
  'https://www.googleapis.com/auth/userinfo.email', // get user email for profile naming
19
20
  ];
20
21
 
@@ -15,8 +15,14 @@ import type {
15
15
  GChatSpace,
16
16
  } from '../../types/gchat';
17
17
 
18
+ interface ResolvedUser {
19
+ displayName: string;
20
+ email?: string;
21
+ }
22
+
18
23
  export class GChatClient implements ServiceClient {
19
24
  private credentials: GChatCredentials;
25
+ private userCache = new Map<string, ResolvedUser>();
20
26
 
21
27
  constructor(credentials: GChatCredentials) {
22
28
  this.credentials = credentials;
@@ -223,6 +229,11 @@ export class GChatClient implements ServiceClient {
223
229
  });
224
230
 
225
231
  const messages = response.data.messages || [];
232
+
233
+ // Resolve unique sender IDs to display names via People API
234
+ const senderIds = [...new Set(messages.map(m => m.sender?.name).filter(Boolean))] as string[];
235
+ await this.resolveUsers(senderIds, auth);
236
+
226
237
  return messages.map((msg: chat_v1.Schema$Message) => {
227
238
  const gchatMsg: GChatMessage = {
228
239
  name: msg.name || '',
@@ -231,12 +242,7 @@ export class GChatClient implements ServiceClient {
231
242
  updateTime: (msg as Record<string, unknown>).lastUpdateTime as string || new Date().toISOString(),
232
243
  };
233
244
  if (msg.text) gchatMsg.text = msg.text;
234
- if (msg.sender?.name) {
235
- gchatMsg.sender = {
236
- name: msg.sender.name,
237
- displayName: msg.sender.displayName || msg.sender.name,
238
- };
239
- }
245
+ gchatMsg.sender = this.enrichSender(msg);
240
246
  if (msg.thread?.name) {
241
247
  gchatMsg.thread = {
242
248
  name: msg.thread.name,
@@ -270,6 +276,12 @@ export class GChatClient implements ServiceClient {
270
276
  }
271
277
 
272
278
  const msg = response.data as chat_v1.Schema$Message;
279
+
280
+ // Resolve sender
281
+ if (msg.sender?.name) {
282
+ await this.resolveUsers([msg.sender.name], auth);
283
+ }
284
+
273
285
  const gchatMsg: GChatMessage = {
274
286
  name: msg.name || '',
275
287
  createTime: msg.createTime || new Date().toISOString(),
@@ -277,12 +289,7 @@ export class GChatClient implements ServiceClient {
277
289
  updateTime: (msg as Record<string, unknown>).lastUpdateTime as string || new Date().toISOString(),
278
290
  };
279
291
  if (msg.text) gchatMsg.text = msg.text;
280
- if (msg.sender?.name) {
281
- gchatMsg.sender = {
282
- name: msg.sender.name,
283
- displayName: msg.sender.displayName || msg.sender.name,
284
- };
285
- }
292
+ gchatMsg.sender = this.enrichSender(msg);
286
293
  if (msg.thread?.name) {
287
294
  gchatMsg.thread = {
288
295
  name: msg.thread.name,
@@ -306,15 +313,29 @@ export class GChatClient implements ServiceClient {
306
313
  const chat = gchat({ version: 'v1', auth: auth as any });
307
314
 
308
315
  try {
309
- const response = await chat.spaces.list({});
310
-
311
- const spaces = response.data.spaces || [];
312
- return spaces.map((space: chat_v1.Schema$Space) => ({
313
- name: space.name || '',
314
- displayName: space.displayName || 'Unnamed',
315
- type: (space.type as 'ROOM' | 'DM') || 'ROOM',
316
- description: space.spaceDetails?.description || undefined,
317
- }));
316
+ const allSpaces: GChatSpace[] = [];
317
+ let pageToken: string | undefined;
318
+
319
+ do {
320
+ const response = await chat.spaces.list({
321
+ pageSize: 100,
322
+ pageToken,
323
+ });
324
+
325
+ const spaces = response.data.spaces || [];
326
+ for (const space of spaces) {
327
+ allSpaces.push({
328
+ name: space.name || '',
329
+ displayName: space.displayName || 'Unnamed',
330
+ type: (space.type as 'ROOM' | 'DM') || 'ROOM',
331
+ description: space.spaceDetails?.description || undefined,
332
+ });
333
+ }
334
+
335
+ pageToken = response.data.nextPageToken || undefined;
336
+ } while (pageToken);
337
+
338
+ return allSpaces;
318
339
  } catch (err) {
319
340
  const code = this.getErrorCode(err);
320
341
  const message = this.getErrorMessage(err);
@@ -326,6 +347,45 @@ export class GChatClient implements ServiceClient {
326
347
  }
327
348
  }
328
349
 
350
+ private async resolveUsers(userIds: string[], auth: OAuth2Client): Promise<void> {
351
+ const unknown = userIds.filter(id => !this.userCache.has(id));
352
+ if (unknown.length === 0) return;
353
+
354
+ const token = await auth.getAccessToken();
355
+ if (!token.token) return;
356
+
357
+ // Resolve users in parallel via People API
358
+ await Promise.all(unknown.map(async (userId) => {
359
+ try {
360
+ // userId is like "users/123456", extract the numeric part
361
+ const personId = userId.replace('users/', '');
362
+ const res = await fetch(
363
+ `https://people.googleapis.com/v1/people/${personId}?personFields=names,emailAddresses`,
364
+ { headers: { Authorization: `Bearer ${token.token}` } }
365
+ );
366
+ if (!res.ok) return;
367
+ const data = await res.json() as Record<string, any>;
368
+ const name = data.names?.[0]?.displayName;
369
+ const email = data.emailAddresses?.[0]?.value;
370
+ if (name) {
371
+ this.userCache.set(userId, { displayName: name, email });
372
+ }
373
+ } catch {
374
+ // Silently skip unresolvable users
375
+ }
376
+ }));
377
+ }
378
+
379
+ private enrichSender(msg: chat_v1.Schema$Message): GChatMessage['sender'] {
380
+ if (!msg.sender?.name) return undefined;
381
+ const cached = this.userCache.get(msg.sender.name);
382
+ return {
383
+ name: msg.sender.name,
384
+ displayName: cached?.displayName || msg.sender.displayName || msg.sender.name,
385
+ email: cached?.email,
386
+ };
387
+ }
388
+
329
389
  private getErrorCode(err: unknown): ErrorCode {
330
390
  if (err && typeof err === 'object') {
331
391
  const error = err as Record<string, unknown>;
@@ -1,6 +1,7 @@
1
1
  export interface GChatSender {
2
2
  name: string;
3
3
  displayName: string;
4
+ email?: string;
4
5
  avatarUrl?: string;
5
6
  }
6
7
 
@@ -132,7 +132,10 @@ export function printGChatMessageList(messages: GChatMessage[]): void {
132
132
  const msg = messages[i];
133
133
  console.log(`[${i + 1}] ${msg.name}`);
134
134
  if (msg.sender) {
135
- console.log(` From: ${msg.sender.displayName || 'Unknown'}`);
135
+ const from = msg.sender.email
136
+ ? `${msg.sender.displayName} <${msg.sender.email}>`
137
+ : msg.sender.displayName || 'Unknown';
138
+ console.log(` From: ${from}`);
136
139
  }
137
140
  if (msg.text) {
138
141
  const snippet = msg.text.length > 100 ? msg.text.substring(0, 100) + '...' : msg.text;
@@ -146,7 +149,10 @@ export function printGChatMessageList(messages: GChatMessage[]): void {
146
149
  export function printGChatMessage(msg: GChatMessage): void {
147
150
  console.log(`ID: ${msg.name}`);
148
151
  if (msg.sender) {
149
- console.log(`From: ${msg.sender.displayName || 'Unknown'}`);
152
+ const from = msg.sender.email
153
+ ? `${msg.sender.displayName} <${msg.sender.email}>`
154
+ : msg.sender.displayName || 'Unknown';
155
+ console.log(`From: ${from}`);
150
156
  }
151
157
  console.log(`Date: ${msg.createTime}`);
152
158
  if (msg.thread) {