@mulmoclaude/google-plugin 0.1.2 → 0.2.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/README.md CHANGED
@@ -8,8 +8,11 @@ dispatch). Server-only — no Vue View.
8
8
  the host-neutral `~/.config/mulmo/google-token.json`, Calendar v3 REST).
9
9
  The host's settings UI, remote commands, auth CLI, and this tool share one
10
10
  link state — across hosts, too.
11
- - Kinds: `status`, `calendarListEvents`, `calendarCreateEvent`. Tasks / Drive
12
- (`drive.file`) ride the same consent grant later (issue #2115).
11
+ - Kinds: `status`; Calendar (`calendarListEvents`, `calendarCreateEvent`);
12
+ Tasks (`taskListsList`, `tasksList`, `tasksCreate`, `tasksComplete`);
13
+ Drive (`driveList`, `driveCreate`, `driveRead`).
14
+ - **Drive is `drive.file`-scoped** — the app only ever sees files it created,
15
+ never the user's wider Drive. That's what keeps the scope non-sensitive.
13
16
  - Not linked yet? The tool's errors tell the LLM to guide the user to this
14
17
  app's settings — wording is host-neutral (#2128) because link flows differ
15
18
  per host (MulmoClaude: Settings → Plugins → Google or `yarn google:auth`;
package/dist/args.d.ts CHANGED
@@ -11,5 +11,33 @@ export declare const GoogleArgs: z.ZodDiscriminatedUnion<[z.ZodObject<{
11
11
  start: z.ZodString;
12
12
  end: z.ZodString;
13
13
  description: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>, z.ZodObject<{
15
+ kind: z.ZodLiteral<"taskListsList">;
16
+ }, z.core.$strip>, z.ZodObject<{
17
+ kind: z.ZodLiteral<"tasksList">;
18
+ taskListId: z.ZodOptional<z.ZodString>;
19
+ maxResults: z.ZodOptional<z.ZodNumber>;
20
+ showCompleted: z.ZodOptional<z.ZodBoolean>;
21
+ }, z.core.$strip>, z.ZodObject<{
22
+ kind: z.ZodLiteral<"tasksCreate">;
23
+ title: z.ZodString;
24
+ notes: z.ZodOptional<z.ZodString>;
25
+ due: z.ZodOptional<z.ZodString>;
26
+ taskListId: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>, z.ZodObject<{
28
+ kind: z.ZodLiteral<"tasksComplete">;
29
+ taskId: z.ZodString;
30
+ taskListId: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>, z.ZodObject<{
32
+ kind: z.ZodLiteral<"driveList">;
33
+ maxResults: z.ZodOptional<z.ZodNumber>;
34
+ }, z.core.$strip>, z.ZodObject<{
35
+ kind: z.ZodLiteral<"driveCreate">;
36
+ name: z.ZodString;
37
+ content: z.ZodString;
38
+ mimeType: z.ZodOptional<z.ZodString>;
39
+ }, z.core.$strip>, z.ZodObject<{
40
+ kind: z.ZodLiteral<"driveRead">;
41
+ fileId: z.ZodString;
14
42
  }, z.core.$strip>], "kind">;
15
43
  export type GoogleArgs = z.infer<typeof GoogleArgs>;
@@ -34,6 +34,46 @@ export declare const TOOL_DEFINITION: {
34
34
  type: string;
35
35
  description: string;
36
36
  };
37
+ taskListId: {
38
+ type: string;
39
+ description: string;
40
+ };
41
+ showCompleted: {
42
+ type: string;
43
+ description: string;
44
+ };
45
+ title: {
46
+ type: string;
47
+ description: string;
48
+ };
49
+ notes: {
50
+ type: string;
51
+ description: string;
52
+ };
53
+ due: {
54
+ type: string;
55
+ description: string;
56
+ };
57
+ taskId: {
58
+ type: string;
59
+ description: string;
60
+ };
61
+ name: {
62
+ type: string;
63
+ description: string;
64
+ };
65
+ content: {
66
+ type: string;
67
+ description: string;
68
+ };
69
+ mimeType: {
70
+ type: string;
71
+ description: string;
72
+ };
73
+ fileId: {
74
+ type: string;
75
+ description: string;
76
+ };
37
77
  };
38
78
  required: string[];
39
79
  };
package/dist/index.d.ts CHANGED
@@ -37,25 +37,50 @@ declare const _default: (runtime: import('gui-chat-protocol').PluginRuntime) =>
37
37
  type: string;
38
38
  description: string;
39
39
  };
40
+ taskListId: {
41
+ type: string;
42
+ description: string;
43
+ };
44
+ showCompleted: {
45
+ type: string;
46
+ description: string;
47
+ };
48
+ title: {
49
+ type: string;
50
+ description: string;
51
+ };
52
+ notes: {
53
+ type: string;
54
+ description: string;
55
+ };
56
+ due: {
57
+ type: string;
58
+ description: string;
59
+ };
60
+ taskId: {
61
+ type: string;
62
+ description: string;
63
+ };
64
+ name: {
65
+ type: string;
66
+ description: string;
67
+ };
68
+ content: {
69
+ type: string;
70
+ description: string;
71
+ };
72
+ mimeType: {
73
+ type: string;
74
+ description: string;
75
+ };
76
+ fileId: {
77
+ type: string;
78
+ description: string;
79
+ };
40
80
  };
41
81
  required: string[];
42
82
  };
43
83
  };
44
- google(rawArgs: unknown): Promise<{
45
- guidance?: string | undefined;
46
- ok: boolean;
47
- linked: boolean;
48
- clientSecret: import('@mulmoclaude/core/google').ClientSecretPresence;
49
- events?: undefined;
50
- event?: undefined;
51
- } | {
52
- ok: boolean;
53
- events: import('@mulmoclaude/core/google').CalendarEventSummary[];
54
- event?: undefined;
55
- } | {
56
- ok: boolean;
57
- event: import('@mulmoclaude/core/google').CalendarEventSummary;
58
- events?: undefined;
59
- }>;
84
+ google(rawArgs: unknown): Promise<unknown>;
60
85
  };
61
86
  export default _default;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_LIST_MAX_RESULTS, MAX_LIST_RESULTS, clientSecretPresence, createCalendarEvent, getGoogleAccessToken, isIsoDateTimeWithOffset, listCalendarEvents, loadGoogleTokens } from "@mulmoclaude/core/google";
1
+ import { DEFAULT_LIST_MAX_RESULTS, MAX_LIST_RESULTS, clientSecretPresence, completeTask, createCalendarEvent, createDriveFile, createTask, getGoogleAccessToken, isIsoDateTimeWithOffset, listCalendarEvents, listDriveFiles, listTaskLists, listTasks, loadGoogleTokens, readDriveFile } from "@mulmoclaude/core/google";
2
2
  //#region ../../../node_modules/gui-chat-protocol/dist/index.js
3
3
  function definePlugin(setup) {
4
4
  return setup;
@@ -612,6 +612,7 @@ var string$1 = (params) => {
612
612
  };
613
613
  var integer = /^-?\d+$/;
614
614
  var number$1 = /^-?\d+(?:\.\d+)?$/;
615
+ var boolean$1 = /^(?:true|false)$/i;
615
616
  var lowercase = /^[^A-Z]*$/;
616
617
  var uppercase = /^[^a-z]*$/;
617
618
  //#endregion
@@ -1406,6 +1407,24 @@ var $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, def
1406
1407
  $ZodCheckNumberFormat.init(inst, def);
1407
1408
  $ZodNumber.init(inst, def);
1408
1409
  });
1410
+ var $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
1411
+ $ZodType.init(inst, def);
1412
+ inst._zod.pattern = boolean$1;
1413
+ inst._zod.parse = (payload, _ctx) => {
1414
+ if (def.coerce) try {
1415
+ payload.value = Boolean(payload.value);
1416
+ } catch (_) {}
1417
+ const input = payload.value;
1418
+ if (typeof input === "boolean") return payload;
1419
+ payload.issues.push({
1420
+ expected: "boolean",
1421
+ code: "invalid_type",
1422
+ input,
1423
+ inst
1424
+ });
1425
+ return payload;
1426
+ };
1427
+ });
1409
1428
  var $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => {
1410
1429
  $ZodType.init(inst, def);
1411
1430
  inst._zod.parse = (payload) => payload;
@@ -2512,6 +2531,13 @@ function _int(Class, params) {
2512
2531
  });
2513
2532
  }
2514
2533
  // @__NO_SIDE_EFFECTS__
2534
+ function _boolean(Class, params) {
2535
+ return new Class({
2536
+ type: "boolean",
2537
+ ...normalizeParams(params)
2538
+ });
2539
+ }
2540
+ // @__NO_SIDE_EFFECTS__
2515
2541
  function _unknown(Class) {
2516
2542
  return new Class({ type: "unknown" });
2517
2543
  }
@@ -3057,6 +3083,9 @@ var numberProcessor = (schema, ctx, _json, _params) => {
3057
3083
  else if (typeof maximum === "number") json.maximum = maximum;
3058
3084
  if (typeof multipleOf === "number") json.multipleOf = multipleOf;
3059
3085
  };
3086
+ var booleanProcessor = (_schema, _ctx, json, _params) => {
3087
+ json.type = "boolean";
3088
+ };
3060
3089
  var neverProcessor = (_schema, _ctx, json, _params) => {
3061
3090
  json.not = {};
3062
3091
  };
@@ -3706,6 +3735,14 @@ var ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def)
3706
3735
  function int(params) {
3707
3736
  return /* @__PURE__ */ _int(ZodNumberFormat, params);
3708
3737
  }
3738
+ var ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
3739
+ $ZodBoolean.init(inst, def);
3740
+ ZodType.init(inst, def);
3741
+ inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
3742
+ });
3743
+ function boolean(params) {
3744
+ return /* @__PURE__ */ _boolean(ZodBoolean, params);
3745
+ }
3709
3746
  var ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
3710
3747
  $ZodUnknown.init(inst, def);
3711
3748
  ZodType.init(inst, def);
@@ -4078,19 +4115,54 @@ function superRefine(fn, params) {
4078
4115
  //#endregion
4079
4116
  //#region src/args.ts
4080
4117
  var IsoDateTimeWithOffset = string().refine(isIsoDateTimeWithOffset, { error: "must be an ISO 8601 date-time with a timezone offset (e.g. 2026-07-17T09:00:00+09:00)" });
4118
+ var MaxResults = number().int().min(1).max(MAX_LIST_RESULTS).optional();
4119
+ var NonEmpty = string().min(1);
4081
4120
  var GoogleArgs = discriminatedUnion("kind", [
4082
4121
  object({ kind: literal("status") }),
4083
4122
  object({
4084
4123
  kind: literal("calendarListEvents"),
4085
4124
  timeMin: IsoDateTimeWithOffset.optional(),
4086
- maxResults: number().int().min(1).max(MAX_LIST_RESULTS).optional()
4125
+ maxResults: MaxResults
4087
4126
  }),
4088
4127
  object({
4089
4128
  kind: literal("calendarCreateEvent"),
4090
- summary: string().min(1),
4129
+ summary: NonEmpty,
4091
4130
  start: IsoDateTimeWithOffset,
4092
4131
  end: IsoDateTimeWithOffset,
4093
4132
  description: string().optional()
4133
+ }),
4134
+ object({ kind: literal("taskListsList") }),
4135
+ object({
4136
+ kind: literal("tasksList"),
4137
+ taskListId: string().optional(),
4138
+ maxResults: MaxResults,
4139
+ showCompleted: boolean().optional()
4140
+ }),
4141
+ object({
4142
+ kind: literal("tasksCreate"),
4143
+ title: NonEmpty,
4144
+ notes: string().optional(),
4145
+ due: IsoDateTimeWithOffset.optional(),
4146
+ taskListId: string().optional()
4147
+ }),
4148
+ object({
4149
+ kind: literal("tasksComplete"),
4150
+ taskId: NonEmpty,
4151
+ taskListId: string().optional()
4152
+ }),
4153
+ object({
4154
+ kind: literal("driveList"),
4155
+ maxResults: MaxResults
4156
+ }),
4157
+ object({
4158
+ kind: literal("driveCreate"),
4159
+ name: NonEmpty,
4160
+ content: string(),
4161
+ mimeType: string().optional()
4162
+ }),
4163
+ object({
4164
+ kind: literal("driveRead"),
4165
+ fileId: NonEmpty
4094
4166
  })
4095
4167
  ]);
4096
4168
  //#endregion
@@ -4099,7 +4171,7 @@ var TOOL_DEFINITION = {
4099
4171
  type: "function",
4100
4172
  name: "google",
4101
4173
  prompt: "The user's Google account is linked LOCALLY on this machine — the refresh token lives in ~/.config/mulmo/ and never reaches any cloud. This is independent of claude.ai Google connectors; the tool works without them. If a call fails with 'Google account not linked', ask the user to link their Google account in this app's settings, then retry the original call.",
4102
- description: "Operate the user's Google services through the locally linked Google account. Currently Google Calendar (the primary calendar). Supported kinds:\n - `status`: report whether the Google account is linked on this machine — call this first when unsure.\n - `calendarListEvents`: list upcoming events. Optional `timeMin` (ISO 8601 date-time with timezone offset; default now) and `maxResults` (1-50, default 10).\n - `calendarCreateEvent`: create an event. Requires `summary`, `start`, `end` — ISO 8601 date-times WITH a timezone offset (e.g. 2026-07-17T09:00:00+09:00); optional `description`.",
4174
+ description: "Operate the user's Google services through the locally linked Google account: Calendar (primary calendar), Tasks, and Drive. Supported kinds:\n - `status`: report whether the Google account is linked on this machine — call this first when unsure.\n\nCalendar:\n - `calendarListEvents`: list upcoming events. Optional `timeMin` (ISO 8601 date-time with timezone offset; default now) and `maxResults` (1-50, default 10).\n - `calendarCreateEvent`: create an event. Requires `summary`, `start`, `end` — ISO 8601 date-times WITH a timezone offset (e.g. 2026-07-17T09:00:00+09:00); optional `description`.\n\nTasks:\n - `taskListsList`: list the user's task lists (`id`, `title`). Only needed when the user means a list other than their default one.\n - `tasksList`: list tasks. Optional `taskListId` (default: the user's default list), `maxResults` (1-50, default 10), `showCompleted` (default false).\n - `tasksCreate`: add a task. Requires `title`; optional `notes`, `due` (ISO 8601 with offset — Google keeps the DATE only, so do not promise a time of day), `taskListId`.\n - `tasksComplete`: mark a task done. Requires `taskId` (from `tasksList`); optional `taskListId`.\n\nDrive — IMPORTANT: this app can only see files IT created, never the user's wider Drive. Never claim you searched their whole Drive:\n - `driveList`: list files this app created. Optional `maxResults` (1-50, default 10).\n - `driveCreate`: create a text file. Requires `name` and `content`; optional `mimeType` (default text/plain).\n - `driveRead`: read one of this app's files. Requires `fileId` (from `driveList` or `driveCreate`). Text files only.",
4103
4175
  parameters: {
4104
4176
  type: "object",
4105
4177
  properties: {
@@ -4108,7 +4180,14 @@ var TOOL_DEFINITION = {
4108
4180
  enum: [
4109
4181
  "status",
4110
4182
  "calendarListEvents",
4111
- "calendarCreateEvent"
4183
+ "calendarCreateEvent",
4184
+ "taskListsList",
4185
+ "tasksList",
4186
+ "tasksCreate",
4187
+ "tasksComplete",
4188
+ "driveList",
4189
+ "driveCreate",
4190
+ "driveRead"
4112
4191
  ]
4113
4192
  },
4114
4193
  timeMin: {
@@ -4117,7 +4196,7 @@ var TOOL_DEFINITION = {
4117
4196
  },
4118
4197
  maxResults: {
4119
4198
  type: "number",
4120
- description: "calendarListEvents: max events to return, 1-50 (default 10)"
4199
+ description: "list kinds: max items to return, 1-50 (default 10)"
4121
4200
  },
4122
4201
  summary: {
4123
4202
  type: "string",
@@ -4134,6 +4213,46 @@ var TOOL_DEFINITION = {
4134
4213
  description: {
4135
4214
  type: "string",
4136
4215
  description: "calendarCreateEvent: optional event body"
4216
+ },
4217
+ taskListId: {
4218
+ type: "string",
4219
+ description: "tasks kinds: target list id (default: the user's default list)"
4220
+ },
4221
+ showCompleted: {
4222
+ type: "boolean",
4223
+ description: "tasksList: include completed tasks (default false)"
4224
+ },
4225
+ title: {
4226
+ type: "string",
4227
+ description: "tasksCreate: task title"
4228
+ },
4229
+ notes: {
4230
+ type: "string",
4231
+ description: "tasksCreate: optional task notes"
4232
+ },
4233
+ due: {
4234
+ type: "string",
4235
+ description: "tasksCreate: optional due date, ISO 8601 with offset (Google keeps the date only)"
4236
+ },
4237
+ taskId: {
4238
+ type: "string",
4239
+ description: "tasksComplete: id of the task to mark done"
4240
+ },
4241
+ name: {
4242
+ type: "string",
4243
+ description: "driveCreate: file name"
4244
+ },
4245
+ content: {
4246
+ type: "string",
4247
+ description: "driveCreate: file body"
4248
+ },
4249
+ mimeType: {
4250
+ type: "string",
4251
+ description: "driveCreate: optional MIME type (default text/plain)"
4252
+ },
4253
+ fileId: {
4254
+ type: "string",
4255
+ description: "driveRead: id of a file this app created"
4137
4256
  }
4138
4257
  },
4139
4258
  required: ["kind"]
@@ -4143,43 +4262,101 @@ var TOOL_DEFINITION = {
4143
4262
  //#region src/index.ts
4144
4263
  var LINK_GUIDANCE = "Ask the user to link their Google account in this app's settings, then retry.";
4145
4264
  var src_default = definePlugin(({ log }) => {
4146
- return {
4147
- TOOL_DEFINITION,
4148
- async google(rawArgs) {
4149
- const args = GoogleArgs.parse(rawArgs);
4150
- switch (args.kind) {
4151
- case "status": {
4152
- const [tokens, clientSecret] = await Promise.all([loadGoogleTokens(), clientSecretPresence()]);
4153
- const linked = Boolean(tokens?.refresh_token);
4154
- return {
4155
- ok: true,
4156
- linked,
4157
- clientSecret,
4158
- ...linked ? {} : { guidance: LINK_GUIDANCE }
4159
- };
4160
- }
4161
- case "calendarListEvents": return {
4265
+ const dispatch = async (args) => {
4266
+ switch (args.kind) {
4267
+ case "status": {
4268
+ const [tokens, clientSecret] = await Promise.all([loadGoogleTokens(), clientSecretPresence()]);
4269
+ const linked = Boolean(tokens?.refresh_token);
4270
+ return {
4162
4271
  ok: true,
4163
- events: await listCalendarEvents(await getGoogleAccessToken(), {
4164
- timeMin: args.timeMin,
4165
- maxResults: args.maxResults ?? DEFAULT_LIST_MAX_RESULTS
4166
- })
4272
+ linked,
4273
+ clientSecret,
4274
+ ...linked ? {} : { guidance: LINK_GUIDANCE }
4275
+ };
4276
+ }
4277
+ case "calendarListEvents": return {
4278
+ ok: true,
4279
+ events: await listCalendarEvents(await getGoogleAccessToken(), {
4280
+ timeMin: args.timeMin,
4281
+ maxResults: args.maxResults ?? DEFAULT_LIST_MAX_RESULTS
4282
+ })
4283
+ };
4284
+ case "calendarCreateEvent": {
4285
+ const event = await createCalendarEvent(await getGoogleAccessToken(), {
4286
+ summary: args.summary,
4287
+ startDateTime: args.start,
4288
+ endDateTime: args.end,
4289
+ description: args.description
4290
+ });
4291
+ log.info("calendar event created", { id: event.id });
4292
+ return {
4293
+ ok: true,
4294
+ event
4167
4295
  };
4168
- case "calendarCreateEvent": {
4169
- const event = await createCalendarEvent(await getGoogleAccessToken(), {
4170
- summary: args.summary,
4171
- startDateTime: args.start,
4172
- endDateTime: args.end,
4173
- description: args.description
4174
- });
4175
- log.info("calendar event created", { id: event.id });
4176
- return {
4177
- ok: true,
4178
- event
4179
- };
4180
- }
4181
- default: throw new Error(`unknown kind: ${JSON.stringify(args)}`);
4182
4296
  }
4297
+ case "taskListsList": return {
4298
+ ok: true,
4299
+ taskLists: await listTaskLists(await getGoogleAccessToken())
4300
+ };
4301
+ case "tasksList": return {
4302
+ ok: true,
4303
+ tasks: await listTasks(await getGoogleAccessToken(), {
4304
+ taskListId: args.taskListId,
4305
+ maxResults: args.maxResults ?? DEFAULT_LIST_MAX_RESULTS,
4306
+ showCompleted: args.showCompleted
4307
+ })
4308
+ };
4309
+ case "tasksCreate": {
4310
+ const task = await createTask(await getGoogleAccessToken(), {
4311
+ title: args.title,
4312
+ notes: args.notes,
4313
+ due: args.due,
4314
+ taskListId: args.taskListId
4315
+ });
4316
+ log.info("task created", { id: task.id });
4317
+ return {
4318
+ ok: true,
4319
+ task
4320
+ };
4321
+ }
4322
+ case "tasksComplete": return {
4323
+ ok: true,
4324
+ task: await completeTask(await getGoogleAccessToken(), {
4325
+ taskId: args.taskId,
4326
+ taskListId: args.taskListId
4327
+ })
4328
+ };
4329
+ case "driveList": return {
4330
+ ok: true,
4331
+ files: await listDriveFiles(await getGoogleAccessToken(), { maxResults: args.maxResults ?? DEFAULT_LIST_MAX_RESULTS })
4332
+ };
4333
+ case "driveCreate": {
4334
+ const file = await createDriveFile(await getGoogleAccessToken(), {
4335
+ name: args.name,
4336
+ content: args.content,
4337
+ mimeType: args.mimeType
4338
+ });
4339
+ log.info("drive file created", { id: file.id });
4340
+ return {
4341
+ ok: true,
4342
+ file
4343
+ };
4344
+ }
4345
+ case "driveRead": {
4346
+ const { file, content } = await readDriveFile(await getGoogleAccessToken(), { fileId: args.fileId });
4347
+ return {
4348
+ ok: true,
4349
+ file,
4350
+ content
4351
+ };
4352
+ }
4353
+ default: throw new Error(`unknown kind: ${JSON.stringify(args)}`);
4354
+ }
4355
+ };
4356
+ return {
4357
+ TOOL_DEFINITION,
4358
+ async google(rawArgs) {
4359
+ return await dispatch(GoogleArgs.parse(rawArgs));
4183
4360
  }
4184
4361
  };
4185
4362
  });