@plosson/agentio 0.6.0 → 0.7.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.6.0",
3
+ "version": "0.7.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/mcp/server.ts CHANGED
@@ -197,6 +197,9 @@ export async function startMcpServer(
197
197
  process.exit(1);
198
198
  }
199
199
 
200
+ // Track last-checked timestamps per service:space for automatic --since injection
201
+ const lastChecked = new Map<string, Date>();
202
+
200
203
  // Create MCP server
201
204
  const server = new Server(
202
205
  { name: 'agentio', version: '1.0.0' },
@@ -230,6 +233,8 @@ export async function startMcpServer(
230
233
  const service = tool.commandPath[0];
231
234
  const profile = profileMap.get(service);
232
235
 
236
+ const input = (args as Record<string, unknown>) || {};
237
+
233
238
  // Build a fresh program for each call to avoid state leaks
234
239
  const execProgram = buildProgram(services);
235
240
 
@@ -237,11 +242,23 @@ export async function startMcpServer(
237
242
  const output = await executeCommand(
238
243
  execProgram,
239
244
  tool,
240
- (args as Record<string, unknown>) || {},
245
+ input,
241
246
  profile
242
247
  );
248
+
249
+ // For list commands, append last-checked info and update timestamp
250
+ let result = output || '(no output)';
251
+ if (name === 'gchat_list' && input.space) {
252
+ const key = `gchat:${input.space}`;
253
+ const last = lastChecked.get(key);
254
+ if (last) {
255
+ result += `\n\nPreviously checked: ${last.toISOString()}`;
256
+ }
257
+ lastChecked.set(key, new Date());
258
+ }
259
+
243
260
  return {
244
- content: [{ type: 'text' as const, text: output || '(no output)' }],
261
+ content: [{ type: 'text' as const, text: result }],
245
262
  };
246
263
  } catch (error: unknown) {
247
264
  const message =
@@ -23,6 +23,7 @@ interface ResolvedUser {
23
23
  export class GChatClient implements ServiceClient {
24
24
  private credentials: GChatCredentials;
25
25
  private userCache = new Map<string, ResolvedUser>();
26
+ private spaceIdCache = new Map<string, string>();
26
27
 
27
28
  constructor(credentials: GChatCredentials) {
28
29
  this.credentials = credentials;
@@ -52,9 +53,11 @@ export class GChatClient implements ServiceClient {
52
53
  async send(options: GChatSendOptions & { spaceId?: string }): Promise<GChatSendResult> {
53
54
  if (this.credentials.type === 'webhook') {
54
55
  return this.sendViaWebhook(options);
55
- } else {
56
- return this.sendViaOAuth(options);
57
56
  }
57
+ if (options.spaceId) {
58
+ options.spaceId = await this.resolveSpaceId(options.spaceId);
59
+ }
60
+ return this.sendViaOAuth(options);
58
61
  }
59
62
 
60
63
  async list(options: GChatListOptions): Promise<GChatMessage[]> {
@@ -72,6 +75,7 @@ export class GChatClient implements ServiceClient {
72
75
  'Use an OAuth profile to read messages'
73
76
  );
74
77
  }
78
+ options.spaceId = await this.resolveSpaceId(options.spaceId);
75
79
  return this.listViaOAuth(options);
76
80
  }
77
81
 
@@ -90,6 +94,7 @@ export class GChatClient implements ServiceClient {
90
94
  'Use an OAuth profile to read messages'
91
95
  );
92
96
  }
97
+ options.spaceId = await this.resolveSpaceId(options.spaceId);
93
98
  return this.getViaOAuth(options);
94
99
  }
95
100
 
@@ -347,6 +352,49 @@ export class GChatClient implements ServiceClient {
347
352
  }
348
353
  }
349
354
 
355
+ /**
356
+ * Resolve a space identifier that may be an ID or a display name.
357
+ * Tries as ID first (no API call), falls back to name resolution via listSpaces.
358
+ * Results are cached for the lifetime of this client instance.
359
+ */
360
+ private async resolveSpaceId(spaceIdOrName: string): Promise<string> {
361
+ // Check cache first
362
+ const cached = this.spaceIdCache.get(spaceIdOrName);
363
+ if (cached) return cached;
364
+
365
+ const oauthCreds = this.credentials as GChatOAuthCredentials;
366
+ const auth = this.createOAuthClient(oauthCreds);
367
+ const chat = gchat({ version: 'v1', auth: auth as any });
368
+
369
+ // Try as ID first
370
+ try {
371
+ const resp = await chat.spaces.get({ name: `spaces/${spaceIdOrName}` });
372
+ if (resp.data.name) {
373
+ this.spaceIdCache.set(spaceIdOrName, spaceIdOrName);
374
+ return spaceIdOrName;
375
+ }
376
+ } catch {
377
+ // Not a valid ID, try as display name
378
+ }
379
+
380
+ // Resolve by display name
381
+ const spaces = await this.listSpacesViaOAuth();
382
+ const nameLower = spaceIdOrName.toLowerCase();
383
+ const match = spaces.find(s => s.displayName.toLowerCase() === nameLower);
384
+
385
+ if (!match) {
386
+ throw new CliError(
387
+ 'NOT_FOUND',
388
+ `Space not found: "${spaceIdOrName}"`,
389
+ 'Use "agentio gchat spaces" to list available spaces'
390
+ );
391
+ }
392
+
393
+ const id = match.name.replace('spaces/', '');
394
+ this.spaceIdCache.set(spaceIdOrName, id);
395
+ return id;
396
+ }
397
+
350
398
  private async resolveUsers(userIds: string[], auth: OAuth2Client): Promise<void> {
351
399
  const unknown = userIds.filter(id => !this.userCache.has(id));
352
400
  if (unknown.length === 0) return;