@andreprado/agentkit 0.1.0-alpha.17 → 0.1.0-alpha.18

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.
@@ -20,6 +20,7 @@ export type ManagedComposioResolutionOptions = {
20
20
  type ManagedComposioToolInput = {
21
21
  toolSlug: string;
22
22
  arguments: Record<string, unknown>;
23
+ confirmed?: boolean;
23
24
  };
24
25
 
25
26
  type ManagedComposioToolOutput = {
@@ -32,6 +33,13 @@ type ManagedComposioToolOutput = {
32
33
  userId: string;
33
34
  };
34
35
 
36
+ type ComposioActionGuide = {
37
+ summary: string;
38
+ argumentNotes: string[];
39
+ dangerousDefaults?: string[];
40
+ examples?: Array<Record<string, unknown>>;
41
+ };
42
+
35
43
  export function resolveManagedComposioIntegrations(
36
44
  config: Pick<AgentConfig, "name" | "integrations">,
37
45
  options: ManagedComposioResolutionOptions = {},
@@ -74,8 +82,13 @@ export function createManagedComposioTools(
74
82
 
75
83
  const tool: AgentTool<ManagedComposioToolInput, ManagedComposioToolOutput> = {
76
84
  name: MANAGED_COMPOSIO_TOOL_NAME,
77
- description:
85
+ description: [
78
86
  "Executes one explicitly allowed AgentKit-managed Composio action for this deployed agent. Use only for configured connected apps.",
87
+ integration.confirmExternalWrites === false
88
+ ? "External write confirmation is disabled for this integration by agentkit.config.ts."
89
+ : "For write actions, pass confirmed: true only after the user confirms the exact external change.",
90
+ describeAllowedComposioActions(integration.allowedTools),
91
+ ].join("\n\n"),
79
92
  visibility: "user",
80
93
  secrets: [MANAGED_COMPOSIO_API_KEY_SECRET],
81
94
  permissions: integration.allowedTools.map((toolName) => `composio:${toolName.toLowerCase()}`),
@@ -91,7 +104,13 @@ export function createManagedComposioTools(
91
104
  arguments: {
92
105
  type: "object",
93
106
  additionalProperties: true,
94
- description: "Structured arguments for the selected Composio action.",
107
+ description:
108
+ "Structured arguments for the selected Composio action. Use the action notes in the tool description; do not rely on Composio defaults for dates, times, durations, recipients, or write behavior.",
109
+ },
110
+ confirmed: {
111
+ type: "boolean",
112
+ description:
113
+ "Set true only after explicit user confirmation for external write actions such as create, update, delete, send, patch, move, insert, clear, remove, import, or quick add.",
95
114
  },
96
115
  },
97
116
  required: ["toolSlug", "arguments"],
@@ -121,6 +140,9 @@ export function createManagedComposioTools(
121
140
  );
122
141
  }
123
142
 
143
+ validateComposioArguments(input.toolSlug, input.arguments);
144
+ validateComposioConfirmation(input.toolSlug, input.confirmed, integration);
145
+
124
146
  const result = await executeComposioTool({
125
147
  apiKey: ctx.secrets[MANAGED_COMPOSIO_API_KEY_SECRET],
126
148
  userId: integration.userId,
@@ -173,6 +195,150 @@ function allowedComposioTools(integration: ComposioManagedIntegration): string[]
173
195
  return Array.from(new Set(Object.values(integration.tools ?? {}).flat())).sort();
174
196
  }
175
197
 
198
+ function describeAllowedComposioActions(actions: string[]): string {
199
+ const lines = ["Allowed Composio actions:"];
200
+
201
+ for (const action of actions) {
202
+ const guide = COMPOSIO_ACTION_GUIDES[action];
203
+
204
+ if (!guide) {
205
+ lines.push(`- ${action}: Pass explicit arguments from the current Composio action schema. Do not invent fields or rely on hidden defaults.`);
206
+ continue;
207
+ }
208
+
209
+ lines.push(`- ${action}: ${guide.summary}`);
210
+
211
+ for (const note of guide.argumentNotes) {
212
+ lines.push(` - ${note}`);
213
+ }
214
+
215
+ for (const warning of guide.dangerousDefaults ?? []) {
216
+ lines.push(` - Dangerous default: ${warning}`);
217
+ }
218
+
219
+ for (const example of guide.examples ?? []) {
220
+ lines.push(` - Example arguments: ${JSON.stringify(example)}`);
221
+ }
222
+ }
223
+
224
+ return lines.join("\n");
225
+ }
226
+
227
+ function validateComposioArguments(toolSlug: string, args: Record<string, unknown>): void {
228
+ if (toolSlug !== "GOOGLECALENDAR_CREATE_EVENT") {
229
+ return;
230
+ }
231
+
232
+ const start = args.start_datetime;
233
+
234
+ if (typeof start !== "string" || !isUtcDateTime(start)) {
235
+ throw new AgentKitError(
236
+ "composio_argument_invalid",
237
+ "GOOGLECALENDAR_CREATE_EVENT requires start_datetime as an explicit UTC RFC3339 timestamp ending in Z or +00:00. Convert user-local times using the runtime time zone before calling Composio.",
238
+ );
239
+ }
240
+
241
+ const minutes = args.event_duration_minutes;
242
+ const hours = args.event_duration_hour;
243
+
244
+ if (!isPositiveNumber(minutes) && !isPositiveNumber(hours)) {
245
+ throw new AgentKitError(
246
+ "composio_argument_required",
247
+ "GOOGLECALENDAR_CREATE_EVENT requires explicit event_duration_minutes or event_duration_hour. Composio defaults event_duration_minutes to 30, so AgentKit blocks implicit duration.",
248
+ );
249
+ }
250
+ }
251
+
252
+ function validateComposioConfirmation(
253
+ toolSlug: string,
254
+ confirmed: boolean | undefined,
255
+ integration: ComposioManagedIntegration,
256
+ ): void {
257
+ if (integration.confirmExternalWrites === false || !isComposioWriteAction(toolSlug)) {
258
+ return;
259
+ }
260
+
261
+ if (confirmed !== true) {
262
+ throw new AgentKitError(
263
+ "composio_confirmation_required",
264
+ `Composio action ${toolSlug} changes an external system. Ask the user to confirm the exact action and call again with confirmed: true.`,
265
+ );
266
+ }
267
+ }
268
+
269
+ function isComposioWriteAction(toolSlug: string): boolean {
270
+ return /_(?:BATCH|CLEAR|CREATE|DELETE|DUPLICATE|IMPORT|INSERT|MOVE|PATCH|QUICK_ADD|REMOVE|REVOKE|SEND|UPDATE)(?:_|$)/.test(
271
+ toolSlug,
272
+ );
273
+ }
274
+
275
+ function isUtcDateTime(value: string): boolean {
276
+ return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|\+00:00)$/.test(value);
277
+ }
278
+
279
+ function isPositiveNumber(value: unknown): boolean {
280
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
281
+ }
282
+
283
+ const COMPOSIO_ACTION_GUIDES: Record<string, ComposioActionGuide> = {
284
+ GOOGLECALENDAR_EVENTS_LIST: {
285
+ summary: "List events from a calendar before answering schedule/availability questions.",
286
+ argumentNotes: [
287
+ "calendarId is required by Composio; use primary unless the user selected a different calendar.",
288
+ "Use explicit timeMin and timeMax RFC3339 bounds for the requested window.",
289
+ "Pass timeZone when the user or business timezone matters.",
290
+ ],
291
+ examples: [
292
+ {
293
+ calendarId: "primary",
294
+ timeMin: "2026-06-03T00:00:00-03:00",
295
+ timeMax: "2026-06-04T00:00:00-03:00",
296
+ singleEvents: true,
297
+ orderBy: "startTime",
298
+ timeZone: "America/Sao_Paulo",
299
+ },
300
+ ],
301
+ },
302
+ GOOGLECALENDAR_CREATE_EVENT: {
303
+ summary: "Create an event only after confirming the exact title, date, start time, duration, timezone, and attendees/location when relevant.",
304
+ argumentNotes: [
305
+ "start_datetime is required and must be UTC RFC3339, for example 2026-06-03T15:00:00Z for 12:00 in America/Sao_Paulo.",
306
+ "calendar_id defaults to primary; pass it explicitly when known.",
307
+ "event_duration_minutes or event_duration_hour must be explicit.",
308
+ "Set timezone to the user/business IANA timezone when known.",
309
+ ],
310
+ dangerousDefaults: [
311
+ "event_duration_minutes defaults to 30 if omitted.",
312
+ "calendar_id defaults to primary if omitted.",
313
+ ],
314
+ examples: [
315
+ {
316
+ calendar_id: "primary",
317
+ summary: "Client call",
318
+ start_datetime: "2026-06-03T15:00:00Z",
319
+ event_duration_minutes: 60,
320
+ timezone: "America/Sao_Paulo",
321
+ },
322
+ ],
323
+ },
324
+ GOOGLECALENDAR_UPDATE_EVENT: {
325
+ summary: "Update an existing event only after identifying the event and confirming the exact change.",
326
+ argumentNotes: [
327
+ "Read or find the event first so event_id/calendar_id are correct.",
328
+ "When changing time, use explicit UTC RFC3339 values and preserve duration unless the user changed it.",
329
+ "Do not infer cancellation/deletion from vague wording.",
330
+ ],
331
+ },
332
+ GMAIL_FETCH_EMAILS: {
333
+ summary: "Fetch emails for read/search requests.",
334
+ argumentNotes: ["Use narrow query/window arguments when available; do not fetch broad mailbox history unnecessarily."],
335
+ },
336
+ GMAIL_SEND_EMAIL: {
337
+ summary: "Send email only after confirming recipients, subject, body, and attachments/links.",
338
+ argumentNotes: ["Pass recipient fields explicitly and avoid using draft-like placeholders in production sends."],
339
+ },
340
+ };
341
+
176
342
  async function executeComposioTool(input: {
177
343
  apiKey: string | undefined;
178
344
  userId: string;
@@ -731,6 +731,22 @@ export class AgentKitConversationStore extends DurableObject {
731
731
  return this.getMessages(url.searchParams.get("conversationId"));
732
732
  }
733
733
 
734
+ if (request.method === "GET" && url.pathname === "/conversations") {
735
+ return this.listConversations();
736
+ }
737
+
738
+ const conversationTraceMatch = url.pathname.match(/^\\/conversations\\/([^/]+)\\/trace$/);
739
+
740
+ if (request.method === "GET" && conversationTraceMatch) {
741
+ return this.getConversationTrace(decodeURIComponent(conversationTraceMatch[1] || ""));
742
+ }
743
+
744
+ const conversationMatch = url.pathname.match(/^\\/conversations\\/([^/]+)$/);
745
+
746
+ if (request.method === "GET" && conversationMatch) {
747
+ return this.getConversation(decodeURIComponent(conversationMatch[1] || ""));
748
+ }
749
+
734
750
  return Response.json({ error: { code: "not_found", message: "Unknown Durable Object route." } }, { status: 404 });
735
751
  }
736
752
 
@@ -754,6 +770,128 @@ export class AgentKitConversationStore extends DurableObject {
754
770
  });
755
771
  }
756
772
 
773
+ listConversations() {
774
+ const rows = this.sql.exec(\`
775
+ SELECT conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at, COUNT(messages.id) AS message_count
776
+ FROM conversations
777
+ LEFT JOIN messages ON messages.conversation_id = conversations.id
778
+ GROUP BY conversations.id, conversations.agent_name, conversations.title, conversations.created_at, conversations.updated_at
779
+ ORDER BY conversations.updated_at DESC
780
+ \`).toArray();
781
+
782
+ return Response.json({
783
+ conversations: rows.map((row) => ({
784
+ id: String(row.id),
785
+ agentName: String(row.agent_name),
786
+ title: row.title === null || row.title === undefined ? null : String(row.title),
787
+ createdAt: String(row.created_at),
788
+ updatedAt: String(row.updated_at),
789
+ messageCount: Number(row.message_count || 0),
790
+ })),
791
+ });
792
+ }
793
+
794
+ getConversation(conversationId) {
795
+ const conversation = this.readConversation(conversationId);
796
+
797
+ if (!conversation) {
798
+ return Response.json(
799
+ { error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
800
+ { status: 404 },
801
+ );
802
+ }
803
+
804
+ return Response.json(conversation);
805
+ }
806
+
807
+ getConversationTrace(conversationId) {
808
+ const conversation = this.readConversation(conversationId);
809
+
810
+ if (!conversation) {
811
+ return Response.json(
812
+ { error: { code: "conversation_not_found", message: \`Conversation "\${conversationId}" was not found.\` } },
813
+ { status: 404 },
814
+ );
815
+ }
816
+
817
+ const runs = this.sql.exec(
818
+ "SELECT id, conversation_id, provider, model, status, error_code, error_message, created_at, completed_at FROM runs WHERE conversation_id = ? ORDER BY rowid ASC",
819
+ conversationId,
820
+ ).toArray();
821
+
822
+ return Response.json({
823
+ ...conversation,
824
+ runs: runs.map((run) => ({
825
+ id: String(run.id),
826
+ conversationId: String(run.conversation_id),
827
+ provider: String(run.provider),
828
+ model: String(run.model),
829
+ status: String(run.status),
830
+ errorCode: run.error_code === null || run.error_code === undefined ? null : String(run.error_code),
831
+ errorMessage: run.error_message === null || run.error_message === undefined ? null : String(run.error_message),
832
+ startedAt: String(run.created_at),
833
+ completedAt: run.completed_at === null || run.completed_at === undefined ? null : String(run.completed_at),
834
+ toolCalls: this.readToolCalls(String(run.id)),
835
+ })),
836
+ });
837
+ }
838
+
839
+ readConversation(conversationId) {
840
+ if (!conversationId) {
841
+ return null;
842
+ }
843
+
844
+ const row = this.sql.exec(
845
+ "SELECT id, agent_name, title, created_at, updated_at FROM conversations WHERE id = ?",
846
+ conversationId,
847
+ ).one();
848
+
849
+ if (!row) {
850
+ return null;
851
+ }
852
+
853
+ const messages = this.sql.exec(
854
+ "SELECT id, conversation_id, role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY rowid ASC",
855
+ conversationId,
856
+ ).toArray();
857
+
858
+ return {
859
+ id: String(row.id),
860
+ agentName: String(row.agent_name),
861
+ title: row.title === null || row.title === undefined ? null : String(row.title),
862
+ createdAt: String(row.created_at),
863
+ updatedAt: String(row.updated_at),
864
+ messages: messages.map((message) => ({
865
+ id: String(message.id),
866
+ conversationId: String(message.conversation_id),
867
+ role: String(message.role),
868
+ content: String(message.content),
869
+ createdAt: String(message.created_at),
870
+ })),
871
+ };
872
+ }
873
+
874
+ readToolCalls(runId) {
875
+ const rows = this.sql.exec(
876
+ "SELECT id, run_id, conversation_id, tool_name, visibility, status, input_json, output_json, error_message, created_at, completed_at FROM tool_calls WHERE run_id = ? ORDER BY rowid ASC",
877
+ runId,
878
+ ).toArray();
879
+
880
+ return rows.map((row) => ({
881
+ id: String(row.id),
882
+ runId: String(row.run_id),
883
+ conversationId: String(row.conversation_id),
884
+ name: String(row.tool_name),
885
+ visibility: String(row.visibility),
886
+ status: String(row.status),
887
+ input: parseStoredJson(row.input_json, {}),
888
+ output: row.output_json === null || row.output_json === undefined ? undefined : parseStoredJson(row.output_json, null),
889
+ errorMessage: row.error_message === null || row.error_message === undefined ? null : String(row.error_message),
890
+ createdAt: String(row.created_at),
891
+ completedAt: row.completed_at === null || row.completed_at === undefined ? null : String(row.completed_at),
892
+ }));
893
+ }
894
+
757
895
  persistMessages(input) {
758
896
  const now = new Date().toISOString();
759
897
  const conversationId = String(input.conversationId || crypto.randomUUID());
@@ -887,6 +1025,22 @@ export default {
887
1025
  return handleChat(request, env);
888
1026
  }
889
1027
 
1028
+ if (request.method === "GET" && (url.pathname === "/conversations" || url.pathname === "/v1/conversations")) {
1029
+ return handleConversationRead(request, env, "list");
1030
+ }
1031
+
1032
+ const conversationTraceMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)\\/trace$/);
1033
+
1034
+ if (request.method === "GET" && conversationTraceMatch) {
1035
+ return handleConversationRead(request, env, "trace", decodeURIComponent(conversationTraceMatch[1] || ""));
1036
+ }
1037
+
1038
+ const conversationMatch = url.pathname.match(/^\\/(?:v1\\/)?conversations\\/([^/]+)$/);
1039
+
1040
+ if (request.method === "GET" && conversationMatch) {
1041
+ return handleConversationRead(request, env, "show", decodeURIComponent(conversationMatch[1] || ""));
1042
+ }
1043
+
890
1044
  if (request.method === "POST" && url.pathname.startsWith("/tools/")) {
891
1045
  return handleTool(request, env, decodeURIComponent(url.pathname.slice("/tools/".length)));
892
1046
  }
@@ -1233,6 +1387,39 @@ async function handleChat(request, env) {
1233
1387
  });
1234
1388
  }
1235
1389
 
1390
+ async function handleConversationRead(request, env, action, conversationId) {
1391
+ const auth = await authorizeChat(request, env, {});
1392
+
1393
+ if (auth) {
1394
+ return auth;
1395
+ }
1396
+
1397
+ if (!env.AGENTKIT_CONVERSATIONS) {
1398
+ return Response.json({ error: { code: "conversations_not_configured", message: "Hosted conversation storage is not configured." } }, { status: 503 });
1399
+ }
1400
+
1401
+ if (action === "list") {
1402
+ const stub = hostedConversationStore(env);
1403
+ return stub.fetch("https://agentkit.internal/conversations");
1404
+ }
1405
+
1406
+ if (!conversationId) {
1407
+ return Response.json({ error: { code: "validation_error", message: "Missing conversation id." } }, { status: 400 });
1408
+ }
1409
+
1410
+ const stub = hostedConversationStore(env);
1411
+ const suffix = action === "trace" ? "/trace" : "";
1412
+ const path = \`/conversations/\${encodeURIComponent(conversationId)}\${suffix}\`;
1413
+ const response = await stub.fetch(\`https://agentkit.internal\${path}\`);
1414
+
1415
+ if (response.status !== 404) {
1416
+ return response;
1417
+ }
1418
+
1419
+ const legacyStub = legacyConversationStore(env, conversationId);
1420
+ return legacyStub.fetch(\`https://agentkit.internal\${path}\`);
1421
+ }
1422
+
1236
1423
  function readChatMessage(body) {
1237
1424
  if (typeof body.message === "string") {
1238
1425
  return body.message.trim();
@@ -2276,8 +2463,7 @@ async function persist(env, input) {
2276
2463
  return {};
2277
2464
  }
2278
2465
 
2279
- const id = env.AGENTKIT_CONVERSATIONS.idFromName(input.conversationId);
2280
- const stub = env.AGENTKIT_CONVERSATIONS.get(id);
2466
+ const stub = hostedConversationStore(env);
2281
2467
  const response = await stub.fetch("https://agentkit.internal/messages", {
2282
2468
  method: "POST",
2283
2469
  body: JSON.stringify(input),
@@ -2295,8 +2481,7 @@ async function loadConversationMessages(env, conversationId) {
2295
2481
  return [];
2296
2482
  }
2297
2483
 
2298
- const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
2299
- const stub = env.AGENTKIT_CONVERSATIONS.get(id);
2484
+ const stub = hostedConversationStore(env);
2300
2485
  const response = await stub.fetch(
2301
2486
  \`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
2302
2487
  );
@@ -2306,7 +2491,7 @@ async function loadConversationMessages(env, conversationId) {
2306
2491
  }
2307
2492
 
2308
2493
  const payload = await response.json();
2309
- return Array.isArray(payload.messages)
2494
+ const messages = Array.isArray(payload.messages)
2310
2495
  ? payload.messages.filter((message) =>
2311
2496
  message &&
2312
2497
  typeof message === "object" &&
@@ -2314,6 +2499,39 @@ async function loadConversationMessages(env, conversationId) {
2314
2499
  typeof message.content === "string"
2315
2500
  )
2316
2501
  : [];
2502
+
2503
+ if (messages.length > 0) {
2504
+ return messages;
2505
+ }
2506
+
2507
+ const legacyStub = legacyConversationStore(env, conversationId);
2508
+ const legacyResponse = await legacyStub.fetch(
2509
+ \`https://agentkit.internal/messages?conversationId=\${encodeURIComponent(conversationId)}\`,
2510
+ );
2511
+
2512
+ if (!legacyResponse.ok) {
2513
+ return [];
2514
+ }
2515
+
2516
+ const legacyPayload = await legacyResponse.json();
2517
+ return Array.isArray(legacyPayload.messages)
2518
+ ? legacyPayload.messages.filter((message) =>
2519
+ message &&
2520
+ typeof message === "object" &&
2521
+ (message.role === "user" || message.role === "assistant") &&
2522
+ typeof message.content === "string"
2523
+ )
2524
+ : [];
2525
+ }
2526
+
2527
+ function hostedConversationStore(env) {
2528
+ const id = env.AGENTKIT_CONVERSATIONS.idFromName("agentkit-conversations");
2529
+ return env.AGENTKIT_CONVERSATIONS.get(id);
2530
+ }
2531
+
2532
+ function legacyConversationStore(env, conversationId) {
2533
+ const id = env.AGENTKIT_CONVERSATIONS.idFromName(conversationId);
2534
+ return env.AGENTKIT_CONVERSATIONS.get(id);
2317
2535
  }
2318
2536
 
2319
2537
  function normalizeConversationId(value) {
@@ -2590,6 +2808,18 @@ function titleFromMessage(content) {
2590
2808
  const compact = content.split(/\\r?\\n/, 1)[0].replace(/\\s+/g, " ").trim();
2591
2809
  return compact.length <= 80 ? compact : \`\${compact.slice(0, 77)}...\`;
2592
2810
  }
2811
+
2812
+ function parseStoredJson(value, fallback) {
2813
+ if (typeof value !== "string") {
2814
+ return fallback;
2815
+ }
2816
+
2817
+ try {
2818
+ return JSON.parse(value);
2819
+ } catch {
2820
+ return fallback;
2821
+ }
2822
+ }
2593
2823
  `;
2594
2824
  }
2595
2825
 
@@ -26,8 +26,13 @@ export default defineAgent({
26
26
  toolkits: ["gmail", "googlecalendar"],
27
27
  tools: {
28
28
  gmail: ["GMAIL_FETCH_EMAILS", "GMAIL_SEND_EMAIL"],
29
- googlecalendar: ["GOOGLECALENDAR_CREATE_EVENT"],
29
+ googlecalendar: [
30
+ "GOOGLECALENDAR_EVENTS_LIST",
31
+ "GOOGLECALENDAR_CREATE_EVENT",
32
+ "GOOGLECALENDAR_UPDATE_EVENT",
33
+ ],
30
34
  },
35
+ confirmExternalWrites: true,
31
36
  }),
32
37
  ],
33
38
  });
@@ -35,13 +40,17 @@ export default defineAgent({
35
40
 
36
41
  Keep the action list explicit. Do not expose the whole Composio catalog by default.
37
42
 
43
+ For Google Calendar, do not configure create-only access. Include `GOOGLECALENDAR_EVENTS_LIST` so the agent can inspect availability before writing. For `GOOGLECALENDAR_CREATE_EVENT`, pass UTC `start_datetime` and explicit `event_duration_minutes` or `event_duration_hour`; AgentKit blocks Composio's implicit 30-minute duration default.
44
+
45
+ Managed Composio write actions require tool input `confirmed: true` by default. Set it only after the owner/user confirms the exact external change. Use `confirmExternalWrites: false` only when the capsule implements an equivalent confirmation guard elsewhere.
46
+
38
47
  ## Commands
39
48
 
40
49
  ```sh
41
50
  npm run agentkit -- inspect
42
51
  npm run agentkit -- deploy doctor
43
52
  npm run agentkit -- deploy
44
- npm run agentkit -- integrations status
53
+ npm run agentkit -- integrations status --toolkit googlecalendar
45
54
  npm run agentkit -- integrations connect composio --toolkit gmail
46
55
  ```
47
56