@noodleseed/agent-kit 0.34.0 → 0.36.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.
Files changed (50) hide show
  1. package/manifest.json +129 -29
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +48 -29
  4. package/skills/claude-code/examples/acme-bistro/README.md +1 -1
  5. package/skills/claude-code/examples/customer-auth/README.md +7 -0
  6. package/skills/claude-code/examples/gmail-multi-account/README.md +46 -0
  7. package/skills/claude-code/examples/gmail-multi-account/noodle.json +4 -0
  8. package/skills/claude-code/examples/gmail-multi-account/package.json +16 -0
  9. package/skills/claude-code/examples/gmail-multi-account/src/server.ts +485 -0
  10. package/skills/claude-code/examples/gmail-multi-account/test/server.test.ts +271 -0
  11. package/skills/claude-code/examples/gmail-multi-account/vitest.config.ts +28 -0
  12. package/skills/claude-code/references/app-directory-compliance.md +59 -0
  13. package/skills/claude-code/references/authoring-workflow.md +3 -1
  14. package/skills/claude-code/references/build-an-mcp-app.md +52 -0
  15. package/skills/claude-code/references/build-an-mcp-server.md +54 -0
  16. package/skills/claude-code/references/compile-errors.md +4 -0
  17. package/skills/claude-code/references/connect-an-api.md +60 -20
  18. package/skills/claude-code/references/deploy-and-ops.md +15 -79
  19. package/skills/claude-code/references/embedded-assistant.md +1 -1
  20. package/skills/claude-code/references/examples.md +1 -0
  21. package/skills/claude-code/references/experience-design.md +1 -1
  22. package/skills/claude-code/references/inspect-hosted.md +26 -0
  23. package/skills/claude-code/references/publishing.md +15 -17
  24. package/skills/claude-code/references/sdk-surface.md +6 -0
  25. package/skills/claude-code/references/verify-and-recover.md +65 -0
  26. package/skills/codex/SKILL.md +48 -29
  27. package/skills/codex/examples/acme-bistro/README.md +1 -1
  28. package/skills/codex/examples/customer-auth/README.md +7 -0
  29. package/skills/codex/examples/gmail-multi-account/README.md +46 -0
  30. package/skills/codex/examples/gmail-multi-account/noodle.json +4 -0
  31. package/skills/codex/examples/gmail-multi-account/package.json +16 -0
  32. package/skills/codex/examples/gmail-multi-account/src/server.ts +485 -0
  33. package/skills/codex/examples/gmail-multi-account/test/server.test.ts +271 -0
  34. package/skills/codex/examples/gmail-multi-account/vitest.config.ts +28 -0
  35. package/skills/codex/references/app-directory-compliance.md +59 -0
  36. package/skills/codex/references/authoring-workflow.md +3 -1
  37. package/skills/codex/references/build-an-mcp-app.md +52 -0
  38. package/skills/codex/references/build-an-mcp-server.md +54 -0
  39. package/skills/codex/references/compile-errors.md +4 -0
  40. package/skills/codex/references/connect-an-api.md +60 -20
  41. package/skills/codex/references/deploy-and-ops.md +15 -79
  42. package/skills/codex/references/embedded-assistant.md +1 -1
  43. package/skills/codex/references/examples.md +1 -0
  44. package/skills/codex/references/experience-design.md +1 -1
  45. package/skills/codex/references/inspect-hosted.md +26 -0
  46. package/skills/codex/references/publishing.md +15 -17
  47. package/skills/codex/references/sdk-surface.md +6 -0
  48. package/skills/codex/references/verify-and-recover.md +65 -0
  49. package/skills/claude-code/references/chatgpt-compliance.md +0 -63
  50. package/skills/codex/references/chatgpt-compliance.md +0 -63
@@ -0,0 +1,485 @@
1
+ import {
2
+ annotations,
3
+ bind,
4
+ connection,
5
+ connector,
6
+ externalExchange,
7
+ gmailConnector,
8
+ server,
9
+ tool,
10
+ when,
11
+ z,
12
+ } from '@noodleseed/one';
13
+
14
+ export const PERSONAL_ACCOUNT = 'personal@example.com';
15
+ export const WORK_ACCOUNT = 'work@example.com';
16
+
17
+ const readAccounts = z.union([
18
+ z.tuple([z.literal(PERSONAL_ACCOUNT)]).and(z.array(z.literal(PERSONAL_ACCOUNT)).length(1)),
19
+ z.tuple([z.literal(WORK_ACCOUNT)]).and(z.array(z.literal(WORK_ACCOUNT)).length(1)),
20
+ z
21
+ .tuple([z.literal(PERSONAL_ACCOUNT), z.literal(WORK_ACCOUNT)])
22
+ .and(z.array(z.enum([PERSONAL_ACCOUNT, WORK_ACCOUNT])).length(2)),
23
+ ]);
24
+ const writeAccounts = z.union([
25
+ z.tuple([z.literal(PERSONAL_ACCOUNT)]).and(z.array(z.literal(PERSONAL_ACCOUNT)).length(1)),
26
+ z.tuple([z.literal(WORK_ACCOUNT)]).and(z.array(z.literal(WORK_ACCOUNT)).length(1)),
27
+ ]);
28
+ const identifier = z.string().min(1);
29
+ const rawMessage = z.string().min(1);
30
+ const labelIds = z.array(identifier).min(1).max(100);
31
+ const epochMillis = z.string().regex(/^\d{1,19}$/);
32
+ const vacationOptionalFields = {
33
+ restrict_to_contacts: z.boolean().optional(),
34
+ restrict_to_domain: z.boolean().optional(),
35
+ start_time: epochMillis.optional(),
36
+ end_time: epochMillis.optional(),
37
+ };
38
+ const output = z.object({
39
+ results: z.array(
40
+ z.object({ account: z.enum([PERSONAL_ACCOUNT, WORK_ACCOUNT]), data: z.unknown() }),
41
+ ),
42
+ });
43
+
44
+ const gmail = gmailConnector();
45
+ const personal = connection('personal_gmail', externalExchange());
46
+ const work = connection('work_gmail', externalExchange());
47
+
48
+ const merge = connector('gmail_account_results')
49
+ .version('1.0.0')
50
+ .compute('merge', {
51
+ type: 'read',
52
+ input: z.object({
53
+ personal: z.unknown().optional(),
54
+ work_first: z.unknown().optional(),
55
+ work_second: z.unknown().optional(),
56
+ }),
57
+ output,
58
+ run: (input) => {
59
+ const results: Array<{ account: string; data: unknown }> = [];
60
+ if (input.personal !== undefined) {
61
+ results.push({ account: 'personal@example.com', data: input.personal });
62
+ }
63
+ const workData = input.work_first ?? input.work_second;
64
+ if (workData !== undefined) {
65
+ results.push({ account: 'work@example.com', data: workData });
66
+ }
67
+ return { results };
68
+ },
69
+ });
70
+
71
+ const confirmedWrite = annotations.openAction({ destructive: false, confirm: true });
72
+ const confirmedTrash = annotations.openAction({ destructive: true, confirm: true });
73
+
74
+ export default server(
75
+ 'gmail_multi_account',
76
+ {
77
+ title: 'Gmail Multi-Account Automation',
78
+ version: '1.0.0',
79
+ use: {
80
+ personal_gmail: bind(gmail, { profile: 'user_oauth', connection: personal }),
81
+ work_gmail: bind(gmail, { profile: 'user_oauth', connection: work }),
82
+ gmail_results: merge,
83
+ },
84
+ },
85
+ [
86
+ tool('search_messages', {
87
+ description: 'Search one connected Gmail account or the canonical personal-and-work pair.',
88
+ input: z.object({
89
+ accounts: readAccounts,
90
+ query: z.string(),
91
+ max_results: z.number().int().min(1).max(500).optional(),
92
+ }),
93
+ output,
94
+ fulfil: ({ input, connectors }) => {
95
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
96
+ connectors.personal_gmail.search_messages({
97
+ q: input.query,
98
+ maxResults: input.max_results,
99
+ }),
100
+ );
101
+ const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
102
+ connectors.work_gmail.search_messages({
103
+ q: input.query,
104
+ maxResults: input.max_results,
105
+ }),
106
+ );
107
+ const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
108
+ connectors.work_gmail.search_messages({
109
+ q: input.query,
110
+ maxResults: input.max_results,
111
+ }),
112
+ );
113
+ const merged = connectors.gmail_results.merge({
114
+ personal: personal.data.optional(),
115
+ work_first: workFirst.data.optional(),
116
+ work_second: workSecond.data.optional(),
117
+ });
118
+ return { results: merged.results };
119
+ },
120
+ }),
121
+ tool('get_message', {
122
+ description: 'Get one Gmail message from one connected account or both canonical accounts.',
123
+ input: z.object({
124
+ accounts: readAccounts,
125
+ message_id: identifier,
126
+ format: z.enum(['minimal', 'full', 'raw', 'metadata']).optional(),
127
+ }),
128
+ output,
129
+ fulfil: ({ input, connectors }) => {
130
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
131
+ connectors.personal_gmail.get_message({
132
+ message_id: input.message_id,
133
+ format: input.format,
134
+ }),
135
+ );
136
+ const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
137
+ connectors.work_gmail.get_message({
138
+ message_id: input.message_id,
139
+ format: input.format,
140
+ }),
141
+ );
142
+ const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
143
+ connectors.work_gmail.get_message({
144
+ message_id: input.message_id,
145
+ format: input.format,
146
+ }),
147
+ );
148
+ const merged = connectors.gmail_results.merge({
149
+ personal: personal.data.optional(),
150
+ work_first: workFirst.data.optional(),
151
+ work_second: workSecond.data.optional(),
152
+ });
153
+ return { results: merged.results };
154
+ },
155
+ }),
156
+ tool('get_thread', {
157
+ description: 'Get one Gmail thread from one connected account or both canonical accounts.',
158
+ input: z.object({
159
+ accounts: readAccounts,
160
+ thread_id: identifier,
161
+ format: z.enum(['minimal', 'full', 'metadata']).optional(),
162
+ }),
163
+ output,
164
+ fulfil: ({ input, connectors }) => {
165
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
166
+ connectors.personal_gmail.get_thread({
167
+ thread_id: input.thread_id,
168
+ format: input.format,
169
+ }),
170
+ );
171
+ const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
172
+ connectors.work_gmail.get_thread({
173
+ thread_id: input.thread_id,
174
+ format: input.format,
175
+ }),
176
+ );
177
+ const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
178
+ connectors.work_gmail.get_thread({
179
+ thread_id: input.thread_id,
180
+ format: input.format,
181
+ }),
182
+ );
183
+ const merged = connectors.gmail_results.merge({
184
+ personal: personal.data.optional(),
185
+ work_first: workFirst.data.optional(),
186
+ work_second: workSecond.data.optional(),
187
+ });
188
+ return { results: merged.results };
189
+ },
190
+ }),
191
+ tool('list_drafts', {
192
+ description: 'List drafts from one connected Gmail account or both canonical accounts.',
193
+ input: z.object({
194
+ accounts: readAccounts,
195
+ query: z.string().optional(),
196
+ max_results: z.number().int().min(1).max(500).optional(),
197
+ }),
198
+ output,
199
+ fulfil: ({ input, connectors }) => {
200
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
201
+ connectors.personal_gmail.list_drafts({
202
+ q: input.query,
203
+ maxResults: input.max_results,
204
+ }),
205
+ );
206
+ const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
207
+ connectors.work_gmail.list_drafts({
208
+ q: input.query,
209
+ maxResults: input.max_results,
210
+ }),
211
+ );
212
+ const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
213
+ connectors.work_gmail.list_drafts({
214
+ q: input.query,
215
+ maxResults: input.max_results,
216
+ }),
217
+ );
218
+ const merged = connectors.gmail_results.merge({
219
+ personal: personal.data.optional(),
220
+ work_first: workFirst.data.optional(),
221
+ work_second: workSecond.data.optional(),
222
+ });
223
+ return { results: merged.results };
224
+ },
225
+ }),
226
+ tool('get_draft', {
227
+ description: 'Get one draft from one connected Gmail account or both canonical accounts.',
228
+ input: z.object({
229
+ accounts: readAccounts,
230
+ draft_id: identifier,
231
+ format: z.enum(['minimal', 'full', 'raw', 'metadata']).optional(),
232
+ }),
233
+ output,
234
+ fulfil: ({ input, connectors }) => {
235
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
236
+ connectors.personal_gmail.get_draft({
237
+ draft_id: input.draft_id,
238
+ format: input.format,
239
+ }),
240
+ );
241
+ const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
242
+ connectors.work_gmail.get_draft({
243
+ draft_id: input.draft_id,
244
+ format: input.format,
245
+ }),
246
+ );
247
+ const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
248
+ connectors.work_gmail.get_draft({
249
+ draft_id: input.draft_id,
250
+ format: input.format,
251
+ }),
252
+ );
253
+ const merged = connectors.gmail_results.merge({
254
+ personal: personal.data.optional(),
255
+ work_first: workFirst.data.optional(),
256
+ work_second: workSecond.data.optional(),
257
+ });
258
+ return { results: merged.results };
259
+ },
260
+ }),
261
+ tool('get_vacation', {
262
+ description: 'Read vacation-responder settings from one connected account or both accounts.',
263
+ input: z.object({ accounts: readAccounts }),
264
+ output,
265
+ fulfil: ({ input, connectors }) => {
266
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
267
+ connectors.personal_gmail.get_vacation({}),
268
+ );
269
+ const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
270
+ connectors.work_gmail.get_vacation({}),
271
+ );
272
+ const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
273
+ connectors.work_gmail.get_vacation({}),
274
+ );
275
+ const merged = connectors.gmail_results.merge({
276
+ personal: personal.data.optional(),
277
+ work_first: workFirst.data.optional(),
278
+ work_second: workSecond.data.optional(),
279
+ });
280
+ return { results: merged.results };
281
+ },
282
+ }),
283
+ tool('create_draft', {
284
+ description:
285
+ 'Create a Gmail draft in exactly one selected account from a base64url MIME message.',
286
+ annotations: confirmedWrite,
287
+ input: z.object({ accounts: writeAccounts, raw: rawMessage }),
288
+ output,
289
+ fulfil: ({ input, connectors }) => {
290
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
291
+ connectors.personal_gmail.create_draft({ raw: input.raw }),
292
+ );
293
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
294
+ connectors.work_gmail.create_draft({ raw: input.raw }),
295
+ );
296
+ const merged = connectors.gmail_results.merge({
297
+ personal: personal.data.optional(),
298
+ work_first: work.data.optional(),
299
+ });
300
+ return { results: merged.results };
301
+ },
302
+ }),
303
+ tool('update_draft', {
304
+ description:
305
+ 'Replace a Gmail draft in exactly one selected account with a base64url MIME message.',
306
+ annotations: confirmedWrite,
307
+ input: z.object({ accounts: writeAccounts, draft_id: identifier, raw: rawMessage }),
308
+ output,
309
+ fulfil: ({ input, connectors }) => {
310
+ const args = { draft_id: input.draft_id, raw: input.raw };
311
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
312
+ connectors.personal_gmail.update_draft(args),
313
+ );
314
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
315
+ connectors.work_gmail.update_draft(args),
316
+ );
317
+ const merged = connectors.gmail_results.merge({
318
+ personal: personal.data.optional(),
319
+ work_first: work.data.optional(),
320
+ });
321
+ return { results: merged.results };
322
+ },
323
+ }),
324
+ tool('send_draft', {
325
+ description: 'Send an existing Gmail draft from exactly one selected account.',
326
+ annotations: confirmedWrite,
327
+ input: z.object({ accounts: writeAccounts, draft_id: identifier }),
328
+ output,
329
+ fulfil: ({ input, connectors }) => {
330
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
331
+ connectors.personal_gmail.send_draft({ draft_id: input.draft_id }),
332
+ );
333
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
334
+ connectors.work_gmail.send_draft({ draft_id: input.draft_id }),
335
+ );
336
+ const merged = connectors.gmail_results.merge({
337
+ personal: personal.data.optional(),
338
+ work_first: work.data.optional(),
339
+ });
340
+ return { results: merged.results };
341
+ },
342
+ }),
343
+ tool('modify_message_labels', {
344
+ description: 'Add or remove Gmail label ids on one message in exactly one selected account.',
345
+ annotations: confirmedWrite,
346
+ input: z.object({
347
+ accounts: writeAccounts,
348
+ message_id: identifier,
349
+ add_label_ids: labelIds.optional(),
350
+ remove_label_ids: labelIds.optional(),
351
+ }),
352
+ output,
353
+ fulfil: ({ input, connectors }) => {
354
+ const args = {
355
+ message_id: input.message_id,
356
+ addLabelIds: input.add_label_ids,
357
+ removeLabelIds: input.remove_label_ids,
358
+ };
359
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
360
+ connectors.personal_gmail.modify_message_labels(args),
361
+ );
362
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
363
+ connectors.work_gmail.modify_message_labels(args),
364
+ );
365
+ const merged = connectors.gmail_results.merge({
366
+ personal: personal.data.optional(),
367
+ work_first: work.data.optional(),
368
+ });
369
+ return { results: merged.results };
370
+ },
371
+ }),
372
+ tool('archive_message', {
373
+ description: 'Archive one Gmail message in exactly one account by removing INBOX.',
374
+ annotations: confirmedWrite,
375
+ input: z.object({ accounts: writeAccounts, message_id: identifier }),
376
+ output,
377
+ fulfil: ({ input, connectors }) => {
378
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
379
+ connectors.personal_gmail.archive_message({ message_id: input.message_id }),
380
+ );
381
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
382
+ connectors.work_gmail.archive_message({ message_id: input.message_id }),
383
+ );
384
+ const merged = connectors.gmail_results.merge({
385
+ personal: personal.data.optional(),
386
+ work_first: work.data.optional(),
387
+ });
388
+ return { results: merged.results };
389
+ },
390
+ }),
391
+ tool('send_message', {
392
+ description: 'Send one base64url MIME message from exactly one selected Gmail account.',
393
+ annotations: confirmedWrite,
394
+ input: z.object({
395
+ accounts: writeAccounts,
396
+ raw: rawMessage,
397
+ thread_id: identifier.optional(),
398
+ }),
399
+ output,
400
+ fulfil: ({ input, connectors }) => {
401
+ const args = { raw: input.raw, threadId: input.thread_id };
402
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
403
+ connectors.personal_gmail.send_message(args),
404
+ );
405
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
406
+ connectors.work_gmail.send_message(args),
407
+ );
408
+ const merged = connectors.gmail_results.merge({
409
+ personal: personal.data.optional(),
410
+ work_first: work.data.optional(),
411
+ });
412
+ return { results: merged.results };
413
+ },
414
+ }),
415
+ tool('trash_message', {
416
+ description: 'Move one Gmail message to trash in exactly one account. This is reversible.',
417
+ annotations: confirmedTrash,
418
+ input: z.object({ accounts: writeAccounts, message_id: identifier }),
419
+ output,
420
+ fulfil: ({ input, connectors }) => {
421
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
422
+ connectors.personal_gmail.trash_message({ message_id: input.message_id }),
423
+ );
424
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
425
+ connectors.work_gmail.trash_message({ message_id: input.message_id }),
426
+ );
427
+ const merged = connectors.gmail_results.merge({
428
+ personal: personal.data.optional(),
429
+ work_first: work.data.optional(),
430
+ });
431
+ return { results: merged.results };
432
+ },
433
+ }),
434
+ tool('update_vacation', {
435
+ description: 'Update vacation-responder settings on exactly one selected Gmail account.',
436
+ annotations: confirmedWrite,
437
+ input: z.object({
438
+ accounts: writeAccounts,
439
+ settings: z.union([
440
+ z.object({
441
+ enable_auto_reply: z.literal(false),
442
+ response_subject: z.string().min(1).optional(),
443
+ response_body_plain_text: z.string().min(1).optional(),
444
+ ...vacationOptionalFields,
445
+ }),
446
+ z.object({
447
+ enable_auto_reply: z.literal(true),
448
+ response_subject: z.string().min(1),
449
+ response_body_plain_text: z.string().min(1).optional(),
450
+ ...vacationOptionalFields,
451
+ }),
452
+ z.object({
453
+ enable_auto_reply: z.literal(true),
454
+ response_subject: z.string().min(1).optional(),
455
+ response_body_plain_text: z.string().min(1),
456
+ ...vacationOptionalFields,
457
+ }),
458
+ ]),
459
+ }),
460
+ output,
461
+ fulfil: ({ input, connectors }) => {
462
+ const args = {
463
+ enableAutoReply: input.settings.enable_auto_reply,
464
+ responseSubject: input.settings.response_subject,
465
+ responseBodyPlainText: input.settings.response_body_plain_text,
466
+ restrictToContacts: input.settings.restrict_to_contacts,
467
+ restrictToDomain: input.settings.restrict_to_domain,
468
+ startTime: input.settings.start_time,
469
+ endTime: input.settings.end_time,
470
+ };
471
+ const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
472
+ connectors.personal_gmail.update_vacation(args),
473
+ );
474
+ const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
475
+ connectors.work_gmail.update_vacation(args),
476
+ );
477
+ const merged = connectors.gmail_results.merge({
478
+ personal: personal.data.optional(),
479
+ work_first: work.data.optional(),
480
+ });
481
+ return { results: merged.results };
482
+ },
483
+ }),
484
+ ],
485
+ );