zubo 0.1.11 → 0.1.14
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 +1 -1
- package/src/channels/webchat.ts +1 -0
- package/src/tools/builtin/google-oauth.ts +39 -75
- package/src/tools/builtin/secrets.ts +32 -1
- package/src/tools/builtin-integrations/google/gmail/handler.ts +1 -3
- package/src/tools/builtin-integrations/google/google_calendar/handler.ts +1 -3
- package/src/tools/builtin-integrations/google/google_docs/handler.ts +1 -3
- package/src/tools/builtin-integrations/google/google_drive/handler.ts +1 -3
- package/src/tools/builtin-integrations/google/google_sheets/handler.ts +1 -3
- package/src/tools/permissions.ts +2 -1
package/package.json
CHANGED
package/src/channels/webchat.ts
CHANGED
|
@@ -1978,6 +1978,7 @@ export function createWebChatAdapter(
|
|
|
1978
1978
|
server = Bun.serve({
|
|
1979
1979
|
port,
|
|
1980
1980
|
hostname: "127.0.0.1", // Bind to localhost only — prevents network exposure
|
|
1981
|
+
idleTimeout: 120,
|
|
1981
1982
|
async fetch(req) {
|
|
1982
1983
|
const response = await handleRequest(req, router, sessionKey, chatLimiter, uploadLimiter, server, port);
|
|
1983
1984
|
return addSecurityHeaders(response);
|
|
@@ -16,9 +16,9 @@ export function registerGoogleOAuthTool(): void {
|
|
|
16
16
|
name: "google_oauth",
|
|
17
17
|
description:
|
|
18
18
|
"Manage the Google OAuth 2.0 connection used by Gmail, Calendar, Sheets, Docs, and Drive. " +
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
19
|
+
"Use action 'start' to begin or re-start the OAuth flow. If credentials are already stored in secrets, " +
|
|
20
|
+
"you can call 'start' with NO parameters — it will automatically use the saved google_client_id and google_client_secret. " +
|
|
21
|
+
"Only ask the user for client_id and client_secret if this tool returns an error saying they are missing. " +
|
|
22
22
|
"Use 'complete' to finish the flow by providing the authorization code the user copied from the browser. " +
|
|
23
23
|
"Use 'status' to check connection state. Use 'disconnect' to remove all stored Google credentials.",
|
|
24
24
|
input_schema: {
|
|
@@ -32,12 +32,12 @@ export function registerGoogleOAuthTool(): void {
|
|
|
32
32
|
client_id: {
|
|
33
33
|
type: "string",
|
|
34
34
|
description:
|
|
35
|
-
"Google OAuth client ID
|
|
35
|
+
"Google OAuth client ID. Optional — if omitted, uses the value already saved in secrets.",
|
|
36
36
|
},
|
|
37
37
|
client_secret: {
|
|
38
38
|
type: "string",
|
|
39
39
|
description:
|
|
40
|
-
"Google OAuth client secret
|
|
40
|
+
"Google OAuth client secret. Optional — if omitted, uses the value already saved in secrets.",
|
|
41
41
|
},
|
|
42
42
|
code: {
|
|
43
43
|
type: "string",
|
|
@@ -124,19 +124,11 @@ async function handleStart(
|
|
|
124
124
|
setSecret("google_client_secret", clientSecret, "google");
|
|
125
125
|
|
|
126
126
|
// Start a temporary local HTTP server to receive the OAuth callback
|
|
127
|
-
let resolveCallback: (code: string) => void;
|
|
128
|
-
let rejectCallback: (reason: Error) => void;
|
|
129
|
-
|
|
130
|
-
const codePromise = new Promise<string>((resolve, reject) => {
|
|
131
|
-
resolveCallback = resolve;
|
|
132
|
-
rejectCallback = reject;
|
|
133
|
-
});
|
|
134
|
-
|
|
135
127
|
let server: ReturnType<typeof Bun.serve>;
|
|
136
128
|
try {
|
|
137
129
|
server = Bun.serve({
|
|
138
130
|
port: 0,
|
|
139
|
-
idleTimeout: 120,
|
|
131
|
+
idleTimeout: 120,
|
|
140
132
|
fetch(req) {
|
|
141
133
|
const url = new URL(req.url);
|
|
142
134
|
if (url.pathname === "/oauth/callback") {
|
|
@@ -144,7 +136,7 @@ async function handleStart(
|
|
|
144
136
|
const error = url.searchParams.get("error");
|
|
145
137
|
|
|
146
138
|
if (error) {
|
|
147
|
-
|
|
139
|
+
logger.error(`Google OAuth error: ${error}`);
|
|
148
140
|
return new Response(
|
|
149
141
|
"<html><body><h2>Authorization failed</h2><p>You can close this window.</p></body></html>",
|
|
150
142
|
{ headers: { "Content-Type": "text/html" } }
|
|
@@ -152,7 +144,19 @@ async function handleStart(
|
|
|
152
144
|
}
|
|
153
145
|
|
|
154
146
|
if (code) {
|
|
155
|
-
|
|
147
|
+
// Exchange code in the background — don't block the callback response
|
|
148
|
+
const storedRedirectUri = getSecret("google_redirect_uri") || `http://localhost:${server.port}/oauth/callback`;
|
|
149
|
+
exchangeGoogleCode(code, storedRedirectUri)
|
|
150
|
+
.then(() => {
|
|
151
|
+
logger.info("[OAuth] Google connected successfully via auto-callback");
|
|
152
|
+
// Auto-shutdown the callback server after success
|
|
153
|
+
setTimeout(() => server.stop(), 1000);
|
|
154
|
+
})
|
|
155
|
+
.catch((err) => {
|
|
156
|
+
logger.error("[OAuth] Auto-callback code exchange failed", { error: err.message });
|
|
157
|
+
setTimeout(() => server.stop(), 1000);
|
|
158
|
+
});
|
|
159
|
+
|
|
156
160
|
return new Response(
|
|
157
161
|
"<html><body><h2>Google connected successfully!</h2><p>You can close this window and return to Zubo.</p></body></html>",
|
|
158
162
|
{ headers: { "Content-Type": "text/html" } }
|
|
@@ -166,14 +170,12 @@ async function handleStart(
|
|
|
166
170
|
},
|
|
167
171
|
});
|
|
168
172
|
} catch (err: any) {
|
|
169
|
-
// If local server fails (e.g. running in a container), fall back to manual flow
|
|
170
173
|
logger.warn("Could not start local OAuth callback server", { error: err.message });
|
|
171
174
|
return handleStartManual();
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
const port = server.port;
|
|
175
178
|
const redirectUri = `http://localhost:${port}/oauth/callback`;
|
|
176
|
-
// Store redirect URI so 'complete' action can use it if auto-callback fails
|
|
177
179
|
setSecret("google_redirect_uri", redirectUri, "google");
|
|
178
180
|
|
|
179
181
|
let authUrl: string;
|
|
@@ -185,7 +187,6 @@ async function handleStart(
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
// Try to open the authorization URL in the user's default browser
|
|
188
|
-
let browserOpened = false;
|
|
189
190
|
try {
|
|
190
191
|
const cmd =
|
|
191
192
|
process.platform === "darwin"
|
|
@@ -194,66 +195,29 @@ async function handleStart(
|
|
|
194
195
|
? ["cmd", "/c", "start", authUrl]
|
|
195
196
|
: ["xdg-open", authUrl];
|
|
196
197
|
Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] });
|
|
197
|
-
browserOpened = true;
|
|
198
198
|
} catch (err: any) {
|
|
199
|
-
logger.warn("Failed to open browser for Google OAuth", {
|
|
200
|
-
error: err.message,
|
|
201
|
-
});
|
|
199
|
+
logger.warn("Failed to open browser for Google OAuth", { error: err.message });
|
|
202
200
|
}
|
|
203
201
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
timeoutId = setTimeout(() => {
|
|
209
|
-
reject(
|
|
210
|
-
new Error("TIMEOUT")
|
|
211
|
-
);
|
|
212
|
-
}, TIMEOUT_MS);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const code = await Promise.race([codePromise, timeoutPromise]);
|
|
217
|
-
clearTimeout(timeoutId!);
|
|
218
|
-
|
|
219
|
-
await exchangeGoogleCode(code, redirectUri);
|
|
220
|
-
server.stop();
|
|
221
|
-
|
|
222
|
-
return JSON.stringify({
|
|
223
|
-
success: true,
|
|
224
|
-
message:
|
|
225
|
-
"Google connected successfully! The following services are now available: Gmail, Google Calendar, Google Sheets, Google Docs, Google Drive.",
|
|
226
|
-
services: [
|
|
227
|
-
"gmail",
|
|
228
|
-
"google_calendar",
|
|
229
|
-
"google_sheets",
|
|
230
|
-
"google_docs",
|
|
231
|
-
"google_drive",
|
|
232
|
-
],
|
|
233
|
-
});
|
|
234
|
-
} catch (err: any) {
|
|
235
|
-
clearTimeout(timeoutId!);
|
|
236
|
-
server.stop();
|
|
202
|
+
// Auto-shutdown the callback server after 3 minutes if no callback received
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
try { server.stop(); } catch {}
|
|
205
|
+
}, 180_000);
|
|
237
206
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
logger.error("Google OAuth flow failed", { error: err.message });
|
|
255
|
-
return JSON.stringify({ error: err.message });
|
|
256
|
-
}
|
|
207
|
+
// Return immediately — don't block waiting for the callback.
|
|
208
|
+
// If the user is local, the browser will handle it automatically in the background.
|
|
209
|
+
// If the user is remote (Telegram, Discord), they can open the link manually.
|
|
210
|
+
return JSON.stringify({
|
|
211
|
+
auth_url: authUrl,
|
|
212
|
+
instructions:
|
|
213
|
+
"Send this authorization link to the user. Tell them to open it in their browser and authorize Google. " +
|
|
214
|
+
"If they are on this machine, a browser window should have opened automatically. " +
|
|
215
|
+
"After authorizing, the connection will be set up automatically. " +
|
|
216
|
+
"If the automatic callback fails (e.g. remote user), ask the user to copy the URL from their browser " +
|
|
217
|
+
"after authorizing (it will show a localhost URL), extract the 'code' parameter, and call google_oauth " +
|
|
218
|
+
"with action 'complete' and that code.",
|
|
219
|
+
message: "A browser window should open shortly. If not, use the link above to authorize Google.",
|
|
220
|
+
});
|
|
257
221
|
}
|
|
258
222
|
|
|
259
223
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerTool } from "../registry";
|
|
2
|
-
import { setSecret, listSecrets, deleteSecret } from "../../secrets/store";
|
|
2
|
+
import { setSecret, getSecret, listSecrets, deleteSecret } from "../../secrets/store";
|
|
3
3
|
|
|
4
4
|
export function registerSecretTools() {
|
|
5
5
|
registerTool({
|
|
@@ -52,6 +52,37 @@ export function registerSecretTools() {
|
|
|
52
52
|
},
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
registerTool({
|
|
56
|
+
definition: {
|
|
57
|
+
name: "secret_get",
|
|
58
|
+
description:
|
|
59
|
+
"Retrieve the value of a stored secret. Use this to access API keys, tokens, and credentials needed for tool calls and integrations.",
|
|
60
|
+
input_schema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
name: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "The name of the secret to retrieve (e.g., 'github_token', 'google_client_id')",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ["name"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
execute: async (input) => {
|
|
72
|
+
const { name } = input as { name: string };
|
|
73
|
+
if (!name) {
|
|
74
|
+
return JSON.stringify({ error: "Secret name is required." });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const value = getSecret(name);
|
|
78
|
+
if (value === null) {
|
|
79
|
+
return JSON.stringify({ error: `No secret found with name "${name}".` });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return JSON.stringify({ name, value });
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
55
86
|
registerTool({
|
|
56
87
|
definition: {
|
|
57
88
|
name: "secret_list",
|
|
@@ -41,9 +41,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
41
41
|
} catch (err: any) {
|
|
42
42
|
return JSON.stringify({
|
|
43
43
|
error: err.message,
|
|
44
|
-
action_required: "Google is not connected.
|
|
45
|
-
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
46
|
-
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
44
|
+
action_required: "Google is not connected. Call google_oauth with action 'start' (no other parameters needed — it will use stored credentials automatically). Only ask the user for credentials if google_oauth returns an error saying they are missing.",
|
|
47
45
|
});
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -24,9 +24,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
24
24
|
} catch (err: any) {
|
|
25
25
|
return JSON.stringify({
|
|
26
26
|
error: err.message,
|
|
27
|
-
action_required: "Google is not connected.
|
|
28
|
-
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
29
|
-
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
27
|
+
action_required: "Google is not connected. Call google_oauth with action 'start' (no other parameters needed — it will use stored credentials automatically). Only ask the user for credentials if google_oauth returns an error saying they are missing.",
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
|
|
@@ -24,9 +24,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
24
24
|
} catch (err: any) {
|
|
25
25
|
return JSON.stringify({
|
|
26
26
|
error: err.message,
|
|
27
|
-
action_required: "Google is not connected.
|
|
28
|
-
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
29
|
-
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
27
|
+
action_required: "Google is not connected. Call google_oauth with action 'start' (no other parameters needed — it will use stored credentials automatically). Only ask the user for credentials if google_oauth returns an error saying they are missing.",
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
|
|
@@ -24,9 +24,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
24
24
|
} catch (err: any) {
|
|
25
25
|
return JSON.stringify({
|
|
26
26
|
error: err.message,
|
|
27
|
-
action_required: "Google is not connected.
|
|
28
|
-
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
29
|
-
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
27
|
+
action_required: "Google is not connected. Call google_oauth with action 'start' (no other parameters needed — it will use stored credentials automatically). Only ask the user for credentials if google_oauth returns an error saying they are missing.",
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
|
|
@@ -24,9 +24,7 @@ export default async function (input: Record<string, unknown>): Promise<string>
|
|
|
24
24
|
} catch (err: any) {
|
|
25
25
|
return JSON.stringify({
|
|
26
26
|
error: err.message,
|
|
27
|
-
action_required: "Google is not connected.
|
|
28
|
-
"Ask them for their Google OAuth client_id (ends with .apps.googleusercontent.com) " +
|
|
29
|
-
"and client_secret (starts with GOCSPX-), then use google_oauth tool with action 'start'.",
|
|
27
|
+
action_required: "Google is not connected. Call google_oauth with action 'start' (no other parameters needed — it will use stored credentials automatically). Only ask the user for credentials if google_oauth returns an error saying they are missing.",
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
|
package/src/tools/permissions.ts
CHANGED
|
@@ -9,8 +9,9 @@ const DEFAULT_PERMISSIONS: Record<string, ToolPermission> = {
|
|
|
9
9
|
reminder_set: "auto",
|
|
10
10
|
diagnose: "auto",
|
|
11
11
|
|
|
12
|
-
// Secrets — set/list are safe, delete requires confirmation
|
|
12
|
+
// Secrets — set/list/get are safe, delete requires confirmation
|
|
13
13
|
secret_set: "auto",
|
|
14
|
+
secret_get: "auto",
|
|
14
15
|
secret_list: "auto",
|
|
15
16
|
secret_delete: "confirm",
|
|
16
17
|
|