@mulmoclaude/core 0.20.2 → 0.22.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/assets/helps/error-recovery.md +29 -16
- package/dist/google/apiClient.d.ts +18 -0
- package/dist/google/broker.d.ts +15 -0
- package/dist/google/calendar.d.ts +2 -2
- package/dist/google/clientSecret.d.ts +4 -3
- package/dist/google/driveFile.d.ts +31 -0
- package/dist/google/index.cjs +410 -59
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +6 -2
- package/dist/google/index.js +390 -60
- package/dist/google/index.js.map +1 -1
- package/dist/google/tasks.d.ts +35 -0
- package/dist/google/tokenStore.d.ts +11 -3
- package/package.json +1 -1
|
@@ -304,28 +304,41 @@ shipped builders produce, and asserts `handlePermission` comes back over the MCP
|
|
|
304
304
|
### Symptoms
|
|
305
305
|
|
|
306
306
|
- The `google` tool (or a `google.calendar.*` remote command) fails with **"Google account not linked on this host"**.
|
|
307
|
-
-
|
|
307
|
+
- **"Google sign-in service unreachable"** or **"Google sign-in service returned HTTP …"**.
|
|
308
|
+
- **"multiple client_secret_*.json files found"**.
|
|
308
309
|
- **"Google Calendar API: HTTP 403"** with a hint about enabling the API.
|
|
309
310
|
- **"could not obtain a Google access token"** (grant revoked).
|
|
310
311
|
|
|
311
312
|
### Cause
|
|
312
313
|
|
|
313
314
|
The `google` tool runs against a Google account linked **locally on this machine** — a refresh
|
|
314
|
-
token stored at `~/.config/mulmo/google-token.json` (mode 600), obtained through a
|
|
315
|
-
|
|
316
|
-
|
|
315
|
+
token stored at `~/.config/mulmo/google-token.json` (mode 600), obtained through a browser
|
|
316
|
+
consent (loopback + PKCE). This is independent of claude.ai Google connectors.
|
|
317
|
+
|
|
318
|
+
Two ways the link can be minted, and the tool picks automatically:
|
|
319
|
+
|
|
320
|
+
- **Default**: the user just clicks link and consents. The mulmoserver broker applies the OAuth
|
|
321
|
+
client secret for the token exchange / renewal (Google requires one); it stores nothing —
|
|
322
|
+
the tokens live only on this machine. **The user needs no Google Cloud setup.**
|
|
323
|
+
- **Own client** (advanced / self-hosters): if `~/.secrets/client_secret_*.json` exists, the
|
|
324
|
+
whole flow stays on this machine and the broker is never contacted.
|
|
317
325
|
|
|
318
326
|
### Fix
|
|
319
327
|
|
|
320
|
-
- **Not linked / grant revoked** — ask the user to link (or re-link) the account
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
- **
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
- **Multiple client secrets** —
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
- **HTTP 403 from a Google API** — the API is not enabled for the
|
|
330
|
-
|
|
331
|
-
|
|
328
|
+
- **Not linked / grant revoked** — ask the user to link (or re-link) the account from this app's
|
|
329
|
+
settings, then retry. Do NOT tell them to create a Google Cloud project, and do NOT try to
|
|
330
|
+
create or edit the token file yourself.
|
|
331
|
+
- **Sign-in service unreachable / HTTP error** — the broker is down or the network is blocked.
|
|
332
|
+
It is only needed to link and to renew an expired access token; ask the user to retry shortly.
|
|
333
|
+
(A user with their own `~/.secrets/client_secret_*.json` never depends on it.)
|
|
334
|
+
- **Multiple client secrets** — the user has several `client_secret_*.json` in `~/.secrets/`;
|
|
335
|
+
a stored refresh token pairs with exactly one OAuth client, so the choice is refused rather
|
|
336
|
+
than guessed at. Ask them to keep one — or remove all of them to use the default flow.
|
|
337
|
+
- **HTTP 403 from a Google API** — the API is not enabled for the Cloud project behind the
|
|
338
|
+
client in use. With their own client, ask them to enable that API in the Cloud Console
|
|
339
|
+
(APIs & Services → Library — the error names it: "Google Calendar API", "Google Tasks API",
|
|
340
|
+
or "Google Drive API"), then retry — no re-link needed. On the default (broker) flow this
|
|
341
|
+
should not happen; report it rather than sending the user to the Console.
|
|
342
|
+
- **Drive shows nothing / "I can't find the user's file"** — not an error. The app holds the
|
|
343
|
+
`drive.file` scope, so it can only ever see files IT created; the user's wider Drive is
|
|
344
|
+
invisible by design. Say so plainly instead of implying an empty Drive.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const GOOGLE_API_TIMEOUT_MS: number;
|
|
2
|
+
export declare const DEFAULT_LIST_MAX_RESULTS = 10;
|
|
3
|
+
export declare const MAX_LIST_RESULTS = 50;
|
|
4
|
+
/** 403 usually means the API is not enabled for the user's Cloud project —
|
|
5
|
+
* name the API so the agent's recovery guidance can be specific. */
|
|
6
|
+
export declare const googleApiError: (apiLabel: string, status: number, body: string) => Error;
|
|
7
|
+
export interface GoogleRequestInit {
|
|
8
|
+
method?: string;
|
|
9
|
+
body?: string;
|
|
10
|
+
/** Overrides the default JSON content type (multipart upload, …). */
|
|
11
|
+
contentType?: string;
|
|
12
|
+
/** Response is not JSON (Drive media download) — return the raw text. */
|
|
13
|
+
expectText?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function googleRequest(apiLabel: string, accessToken: string, url: string, init?: GoogleRequestInit): Promise<unknown>;
|
|
16
|
+
export declare const stringField: (record: Record<string, unknown>, key: string) => string;
|
|
17
|
+
export declare const asRecord: (value: unknown) => Record<string, unknown>;
|
|
18
|
+
export declare const itemsOf: (value: unknown) => unknown[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Credentials } from 'google-auth-library';
|
|
2
|
+
/** `MULMO_GOOGLE_BROKER_URL` lets a fork / staging deploy point elsewhere
|
|
3
|
+
* without a code change; unset means the shipped broker. */
|
|
4
|
+
export declare const brokerBaseUrl: (override?: string | undefined) => string;
|
|
5
|
+
export interface BrokerStartResponse {
|
|
6
|
+
authUrl: string;
|
|
7
|
+
state: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function brokerStart(port: number, codeChallenge: string, baseUrl?: string): Promise<BrokerStartResponse>;
|
|
10
|
+
export declare function brokerExchange(input: {
|
|
11
|
+
code: string;
|
|
12
|
+
state: string;
|
|
13
|
+
codeVerifier: string;
|
|
14
|
+
}, baseUrl?: string): Promise<Credentials>;
|
|
15
|
+
export declare function brokerRefresh(refreshToken: string, baseUrl?: string): Promise<Credentials>;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export declare const DEFAULT_LIST_MAX_RESULTS = 10;
|
|
2
|
-
export declare const MAX_LIST_RESULTS = 50;
|
|
3
1
|
export interface CalendarEventInput {
|
|
4
2
|
summary: string;
|
|
5
3
|
startDateTime: string;
|
|
@@ -19,6 +17,8 @@ export interface CalendarEventSummary {
|
|
|
19
17
|
status: string;
|
|
20
18
|
}
|
|
21
19
|
export declare const toEventSummary: (value: unknown) => CalendarEventSummary;
|
|
20
|
+
/** Kept as a named export for the existing unit tests / callers; the shared
|
|
21
|
+
* helper now carries the wording. */
|
|
22
22
|
export declare const calendarApiError: (status: number, body: string) => Error;
|
|
23
23
|
export declare function createCalendarEvent(accessToken: string, input: CalendarEventInput): Promise<CalendarEventSummary>;
|
|
24
24
|
export declare function listCalendarEvents(accessToken: string, input?: ListEventsInput): Promise<CalendarEventSummary[]>;
|
|
@@ -2,9 +2,10 @@ export interface InstalledClientSecret {
|
|
|
2
2
|
client_id: string;
|
|
3
3
|
client_secret: string;
|
|
4
4
|
}
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
/** `missing` is the ordinary case — the broker supplies the client, so no user
|
|
6
|
+
* action is needed. `ambiguous` (2+ desktop clients) still needs a human:
|
|
7
|
+
* a stored refresh token pairs with exactly one client_id, so picking for
|
|
8
|
+
* them could silently break the link. */
|
|
8
9
|
export type ClientSecretPresence = "found" | "missing" | "ambiguous";
|
|
9
10
|
export declare function clientSecretPresence(home?: string): Promise<ClientSecretPresence>;
|
|
10
11
|
export declare function findClientSecretPath(home?: string): Promise<string>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface DriveFileSummary {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
webViewLink: string;
|
|
6
|
+
modifiedTime: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ListDriveFilesInput {
|
|
9
|
+
maxResults?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CreateDriveFileInput {
|
|
12
|
+
name: string;
|
|
13
|
+
content: string;
|
|
14
|
+
mimeType?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ReadDriveFileInput {
|
|
17
|
+
fileId: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const toDriveFileSummary: (value: unknown) => DriveFileSummary;
|
|
20
|
+
export declare const isTextMimeType: (mimeType: string) => boolean;
|
|
21
|
+
export declare function listDriveFiles(accessToken: string, input?: ListDriveFilesInput): Promise<DriveFileSummary[]>;
|
|
22
|
+
export declare const assertSafeMimeType: (mimeType: string) => string;
|
|
23
|
+
export declare const buildMultipartBody: (metadata: Record<string, string>, content: string, mimeType: string, boundary: string) => string;
|
|
24
|
+
/** A boundary that appears nowhere in the parts it delimits. */
|
|
25
|
+
export declare const pickBoundary: (parts: string[], generate?: () => string) => string;
|
|
26
|
+
export declare function createDriveFile(accessToken: string, input: CreateDriveFileInput): Promise<DriveFileSummary>;
|
|
27
|
+
export declare function readDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<{
|
|
28
|
+
file: DriveFileSummary;
|
|
29
|
+
content: string;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function deleteDriveFile(accessToken: string, input: ReadDriveFileInput): Promise<void>;
|