@alfe.ai/openclaw-google 0.0.40 → 0.0.42

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.
@@ -0,0 +1,540 @@
1
+ import { createRequire } from "node:module";
2
+ import { execFile } from "node:child_process";
3
+ import { homedir } from "node:os";
4
+ import { Type } from "@sinclair/typebox";
5
+ import { resolveConfig } from "@alfe.ai/config";
6
+ import { AgentApiClient, installToolErrorCapture } from "@alfe.ai/agent-api-client";
7
+ import { defineTool, getActivationKey, guardedStart, publicToolError, resetActivation } from "@alfe.ai/openclaw-plugin-kit";
8
+ import { lstatSync, realpathSync, statSync } from "node:fs";
9
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
10
+ //#region src/boundary.ts
11
+ const MAX_ACCOUNTS = 128;
12
+ const MAX_EMAIL_CHARS = 320;
13
+ const MAX_DISPLAY_NAME_CHARS = 512;
14
+ const MAX_COMMAND_CHARS = 64 * 1024;
15
+ const MAX_COMMAND_ARGS = 256;
16
+ const MAX_FILE_PATH_CHARS = 8192;
17
+ const MAX_TOOL_OUTPUT_BYTES = 256 * 1024;
18
+ const EMAIL_PATTERN$1 = /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/u;
19
+ const DESTRUCTIVE_METHODS = new Set([
20
+ "batchclear",
21
+ "batchdelete",
22
+ "clear",
23
+ "delete",
24
+ "emptytrash",
25
+ "purge",
26
+ "remove",
27
+ "trash",
28
+ "wipe"
29
+ ]);
30
+ const FILE_FLAGS = new Set(["--output", "--upload"]);
31
+ const CHILD_ENV_ALLOWLIST = [
32
+ "HOME",
33
+ "HTTP_PROXY",
34
+ "HTTPS_PROXY",
35
+ "LANG",
36
+ "LC_ALL",
37
+ "LC_CTYPE",
38
+ "NO_PROXY",
39
+ "PATH",
40
+ "SSL_CERT_DIR",
41
+ "SSL_CERT_FILE",
42
+ "TZ"
43
+ ];
44
+ function normalizeGoogleAccounts(value, homeDirectory = homedir()) {
45
+ const root = requireRecord(value, "Google accounts response");
46
+ if (!Array.isArray(root.accounts) || root.accounts.length > MAX_ACCOUNTS) throw new Error(`Google accounts response must contain at most ${String(MAX_ACCOUNTS)} accounts`);
47
+ const selectors = /* @__PURE__ */ new Set();
48
+ const configDirectories = /* @__PURE__ */ new Set();
49
+ return root.accounts.map((raw, index) => {
50
+ const account = requireRecord(raw, `Google account ${String(index)}`);
51
+ const email = validateGoogleEmail(account.email, "Google account email");
52
+ const selector = email.toLowerCase();
53
+ if (selectors.has(selector)) throw new Error("Google accounts response contains a duplicate email");
54
+ selectors.add(selector);
55
+ const configDir = resolveConfigDir(email, homeDirectory);
56
+ const configKey = configDir.toLowerCase();
57
+ if (configDirectories.has(configKey)) throw new Error("Google account emails map to an ambiguous gws config directory");
58
+ configDirectories.add(configKey);
59
+ return {
60
+ email,
61
+ displayName: optionalDisplayText(account.displayName, "Google account display name"),
62
+ connectedAt: optionalConnectedAt(account.connectedAt),
63
+ configDir
64
+ };
65
+ });
66
+ }
67
+ function normalizeAccountSelector(value) {
68
+ return validateGoogleEmail(value, "Google account selector").toLowerCase();
69
+ }
70
+ function sanitizeEmail(email) {
71
+ return validateGoogleEmail(email, "Google account email").replace(/@/gu, "-").replace(/\./gu, "-");
72
+ }
73
+ function resolveConfigDir(email, homeDirectory = homedir()) {
74
+ return join(homeDirectory, ".config", `gws-${sanitizeEmail(email)}`);
75
+ }
76
+ function parseGwsCommand(value, confirmation) {
77
+ const command = requireBoundedString(value, "command", MAX_COMMAND_CHARS).trim();
78
+ if (!command) throw new Error("Command cannot be empty");
79
+ if (hasControlCharacter(command)) throw new Error("Command cannot contain control characters");
80
+ const args = tokenizeCommand(command);
81
+ if (args.length === 0) throw new Error("Command cannot be empty");
82
+ if (args.length > MAX_COMMAND_ARGS) throw new Error(`Command cannot contain more than ${String(MAX_COMMAND_ARGS)} arguments`);
83
+ if (args[0]?.toLowerCase() === "auth") throw new Error("Authentication commands are managed by the Google integration");
84
+ const positional = args.slice(0, args.findIndex((arg) => arg.startsWith("--")) === -1 ? args.length : args.findIndex((arg) => arg.startsWith("--")));
85
+ const destructive = !args.includes("--dry-run") && positional.some((arg) => DESTRUCTIVE_METHODS.has(arg.toLowerCase()));
86
+ if (destructive && confirmation !== command) throw new Error("confirmCommand must exactly match command for a destructive gws operation");
87
+ return {
88
+ command,
89
+ args,
90
+ destructive
91
+ };
92
+ }
93
+ function confineGwsFileArguments(args, workspacePath) {
94
+ let workspaceRealPath;
95
+ try {
96
+ workspaceRealPath = realpathSync(workspacePath);
97
+ } catch {
98
+ throw new Error("Configured workspace is unavailable");
99
+ }
100
+ if (!statSync(workspaceRealPath).isDirectory()) throw new Error("Configured workspace path is not a directory");
101
+ const rewritten = [...args];
102
+ for (let index = 0; index < rewritten.length; index += 1) {
103
+ const argument = rewritten.at(index);
104
+ if (argument === void 0) continue;
105
+ const equalIndex = argument.indexOf("=");
106
+ const flag = equalIndex === -1 ? argument : argument.slice(0, equalIndex);
107
+ if (!FILE_FLAGS.has(flag)) continue;
108
+ const inlineValue = equalIndex === -1 ? void 0 : argument.slice(equalIndex + 1);
109
+ const valueIndex = inlineValue === void 0 ? index + 1 : index;
110
+ const rawPath = inlineValue ?? rewritten.at(valueIndex);
111
+ if (rawPath === void 0 || rawPath.length === 0 || rawPath.startsWith("--")) throw new Error(`${flag} requires a workspace-relative file path`);
112
+ const confined = flag === "--upload" ? confineUploadPath(rawPath, workspaceRealPath) : confineOutputPath(rawPath, workspaceRealPath);
113
+ if (inlineValue === void 0) {
114
+ rewritten[valueIndex] = confined;
115
+ index = valueIndex;
116
+ } else rewritten[index] = `${flag}=${confined}`;
117
+ }
118
+ return {
119
+ args: rewritten,
120
+ workspacePath: workspaceRealPath
121
+ };
122
+ }
123
+ function buildGwsEnvironment(configDir, source = process.env) {
124
+ const environment = Object.create(null);
125
+ for (const name of CHILD_ENV_ALLOWLIST) {
126
+ const value = source[name];
127
+ if (value !== void 0 && !hasControlCharacter(value)) environment[name] = value;
128
+ }
129
+ environment.GOOGLE_WORKSPACE_CLI_CONFIG_DIR = requireBoundedString(configDir, "gws config directory", MAX_FILE_PATH_CHARS);
130
+ if (hasControlCharacter(environment.GOOGLE_WORKSPACE_CLI_CONFIG_DIR)) throw new Error("gws config directory cannot contain control characters");
131
+ return environment;
132
+ }
133
+ function boundToolOutput(value) {
134
+ const buffer = Buffer.from(typeof value === "string" ? value : "", "utf8");
135
+ if (buffer.byteLength <= 262144) return {
136
+ text: buffer.toString("utf8"),
137
+ truncated: false
138
+ };
139
+ return {
140
+ text: `${buffer.subarray(0, MAX_TOOL_OUTPUT_BYTES).toString("utf8")}\n[output truncated]`,
141
+ truncated: true
142
+ };
143
+ }
144
+ function safeGwsDiagnostic(value, secrets = []) {
145
+ let redacted = value.slice(0, 4096).replace(/\b(Bearer|Basic)\s+\S+/giu, "$1 [REDACTED]").replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password)\s*[=:]\s*\S+/giu, "$1=[REDACTED]").replace(/\balfe_[A-Za-z0-9_-]{8,}/gu, "[REDACTED]");
146
+ for (const secret of secrets) if (secret.length >= 4) redacted = redacted.split(secret).join("[REDACTED]");
147
+ let flattened = "";
148
+ for (const character of redacted) {
149
+ const codePoint = character.codePointAt(0) ?? 0;
150
+ flattened += codePoint < 32 || codePoint === 127 ? " " : character;
151
+ }
152
+ return flattened.trim();
153
+ }
154
+ function tokenizeCommand(command) {
155
+ const args = [];
156
+ let current = "";
157
+ let tokenStarted = false;
158
+ let quote = null;
159
+ let escaped = false;
160
+ for (const character of command) {
161
+ if (escaped) {
162
+ current += character;
163
+ tokenStarted = true;
164
+ escaped = false;
165
+ continue;
166
+ }
167
+ if (quote === "single") {
168
+ if (character === "'") quote = null;
169
+ else current += character;
170
+ tokenStarted = true;
171
+ continue;
172
+ }
173
+ if (quote === "double") {
174
+ if (character === "\"") quote = null;
175
+ else if (character === "\\") escaped = true;
176
+ else current += character;
177
+ tokenStarted = true;
178
+ continue;
179
+ }
180
+ if (character === "'") {
181
+ quote = "single";
182
+ tokenStarted = true;
183
+ } else if (character === "\"") {
184
+ quote = "double";
185
+ tokenStarted = true;
186
+ } else if (character === "\\") {
187
+ escaped = true;
188
+ tokenStarted = true;
189
+ } else if (/\s/u.test(character)) {
190
+ if (tokenStarted) {
191
+ args.push(current);
192
+ current = "";
193
+ tokenStarted = false;
194
+ }
195
+ } else {
196
+ current += character;
197
+ tokenStarted = true;
198
+ }
199
+ }
200
+ if (quote !== null || escaped) throw new Error("Command contains an unterminated quote or escape");
201
+ if (tokenStarted) args.push(current);
202
+ return args;
203
+ }
204
+ function confineUploadPath(value, workspaceRealPath) {
205
+ const candidate = resolveCandidatePath(value, workspaceRealPath);
206
+ try {
207
+ if (lstatSync(candidate).isSymbolicLink()) throw new Error("unsafe upload");
208
+ const realCandidate = realpathSync(candidate);
209
+ if (!isWithin(workspaceRealPath, realCandidate) || !statSync(realCandidate).isFile()) throw new Error("unsafe upload");
210
+ return realCandidate;
211
+ } catch {
212
+ throw new Error("Upload path must be a regular file inside the configured workspace");
213
+ }
214
+ }
215
+ function confineOutputPath(value, workspaceRealPath) {
216
+ const candidate = resolveCandidatePath(value, workspaceRealPath);
217
+ let parent;
218
+ try {
219
+ parent = realpathSync(dirname(candidate));
220
+ } catch {
221
+ throw new Error("Output path must have an existing directory inside the configured workspace");
222
+ }
223
+ if (!isWithin(workspaceRealPath, parent)) throw new Error("Output path must stay inside the configured workspace");
224
+ try {
225
+ const status = lstatSync(candidate);
226
+ if (status.isSymbolicLink() || status.isDirectory()) throw new Error("Output path must be a non-symlink file inside the configured workspace");
227
+ if (!isWithin(workspaceRealPath, realpathSync(candidate))) throw new Error("Output path must stay inside the configured workspace");
228
+ } catch (error) {
229
+ if ((isRecord(error) ? error.code : void 0) !== "ENOENT") throw new Error("Output path must be a non-symlink file inside the configured workspace");
230
+ }
231
+ return candidate;
232
+ }
233
+ function resolveCandidatePath(value, workspaceRealPath) {
234
+ const rawPath = requireBoundedString(value, "gws file path", MAX_FILE_PATH_CHARS);
235
+ if (hasControlCharacter(rawPath)) throw new Error("gws file path cannot contain control characters");
236
+ return isAbsolute(rawPath) ? resolve(rawPath) : resolve(workspaceRealPath, rawPath);
237
+ }
238
+ function isWithin(root, candidate) {
239
+ const child = relative(root, candidate);
240
+ return child === "" || !isAbsolute(child) && child !== ".." && !child.startsWith(`..${sep}`);
241
+ }
242
+ function validateGoogleEmail(value, label) {
243
+ const email = requireBoundedString(value, label, MAX_EMAIL_CHARS);
244
+ if (email !== email.trim() || email.includes("/") || email.includes("\\") || !EMAIL_PATTERN$1.test(email)) throw new Error(`${label} is invalid`);
245
+ return email;
246
+ }
247
+ function optionalDisplayText(value, label) {
248
+ if (value === void 0 || value === null) return void 0;
249
+ const text = requireBoundedString(value, label, MAX_DISPLAY_NAME_CHARS);
250
+ if (hasControlCharacter(text)) throw new Error(`${label} contains control characters`);
251
+ return text;
252
+ }
253
+ function optionalConnectedAt(value) {
254
+ if (value === void 0 || value === null) return void 0;
255
+ const connectedAt = requireBoundedString(value, "Google account connectedAt", 128);
256
+ if (Number.isNaN(Date.parse(connectedAt))) throw new Error("Google account connectedAt is invalid");
257
+ return connectedAt;
258
+ }
259
+ function requireBoundedString(value, label, maxLength) {
260
+ if (typeof value !== "string" || value.length < 1 || value.length > maxLength) throw new Error(`${label} must be a non-empty string of at most ${String(maxLength)} characters`);
261
+ return value;
262
+ }
263
+ function requireRecord(value, label) {
264
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
265
+ return value;
266
+ }
267
+ function isRecord(value) {
268
+ return value !== null && typeof value === "object" && !Array.isArray(value);
269
+ }
270
+ function hasControlCharacter(value) {
271
+ for (const character of value) {
272
+ const codePoint = character.codePointAt(0) ?? 0;
273
+ if (codePoint < 32 || codePoint === 127) return true;
274
+ }
275
+ return false;
276
+ }
277
+ const PLUGIN_VERSION = validatePackageVersion(createRequire(import.meta.url)("../package.json").version);
278
+ const GOOGLE_ACTIVATION_KEY = getActivationKey("google");
279
+ const RUNTIME_STATE_KEY = "__alfeGooglePluginRuntimeState";
280
+ const GWS_TIMEOUT_MS = 6e4;
281
+ const GWS_MAX_BUFFER_BYTES = 1024 * 1024;
282
+ const EMAIL_PATTERN = "^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$";
283
+ function createGooglePluginRuntimeState() {
284
+ return {
285
+ client: null,
286
+ workspacePath: null,
287
+ accounts: [],
288
+ refreshPromise: null,
289
+ generation: 0
290
+ };
291
+ }
292
+ function createGooglePlugin(dependencies = {}) {
293
+ const resolveRuntimeConfig = dependencies.resolveConfig ?? resolveConfig;
294
+ const createClient = dependencies.createClient ?? ((config) => new AgentApiClient(config));
295
+ const executeGws = dependencies.executeGws ?? executeGwsCommand;
296
+ const installErrorCapture = dependencies.installErrorCapture ?? installToolErrorCapture;
297
+ const homeDirectory = dependencies.homeDirectory ?? homedir();
298
+ const getState = () => dependencies.runtimeState ?? getGlobalRuntimeState();
299
+ const ensureClient = () => {
300
+ const state = getState();
301
+ if (state.client === null || state.workspacePath === null) {
302
+ const config = resolveRuntimeConfig();
303
+ state.client = createClient({
304
+ apiKey: config.apiKey,
305
+ apiUrl: config.apiUrl
306
+ });
307
+ state.workspacePath = config.workspacePath;
308
+ }
309
+ return {
310
+ client: state.client,
311
+ workspacePath: state.workspacePath
312
+ };
313
+ };
314
+ const refreshAccounts = async () => {
315
+ const state = getState();
316
+ if (state.refreshPromise !== null) return state.refreshPromise;
317
+ const { client } = ensureClient();
318
+ const generation = state.generation;
319
+ const refresh = client.getGoogleCredentials().then((value) => {
320
+ const accounts = normalizeGoogleAccounts(value, homeDirectory);
321
+ if (state.generation === generation && state.client === client) state.accounts = accounts;
322
+ return accounts;
323
+ });
324
+ state.refreshPromise = refresh;
325
+ try {
326
+ return await refresh;
327
+ } finally {
328
+ if (state.refreshPromise === refresh) state.refreshPromise = null;
329
+ }
330
+ };
331
+ const resolveAccount = async (value) => {
332
+ const selector = normalizeAccountSelector(value);
333
+ let account = getState().accounts.find((candidate) => candidate.email.toLowerCase() === selector);
334
+ if (account === void 0) account = (await refreshAccounts()).find((candidate) => candidate.email.toLowerCase() === selector);
335
+ if (account === void 0) throw publicToolError("Google account not found; call google_list_accounts first");
336
+ return account;
337
+ };
338
+ const tools = [
339
+ defineTool({
340
+ name: "google_list_accounts",
341
+ description: "List connected Google Workspace accounts. Use this before a credential-touching tool when the exact account email is not already known.",
342
+ parameters: Type.Object({}, { additionalProperties: false }),
343
+ handler: async () => {
344
+ const accounts = await refreshAccounts();
345
+ return {
346
+ accounts: accounts.map(({ email, displayName, connectedAt }) => ({
347
+ email,
348
+ displayName,
349
+ connectedAt
350
+ })),
351
+ count: accounts.length
352
+ };
353
+ }
354
+ }),
355
+ defineTool({
356
+ name: "google_run_command",
357
+ description: "Run a Google Workspace CLI command for one explicitly selected account. The command supports quoted JSON exactly like gws (for example: drive files list --params '{\"pageSize\": 10}'). Authentication commands are blocked. --upload and --output paths must remain inside the configured workspace. Use --dry-run to preview mutations; otherwise copy an exact destructive command into confirmCommand when requested.",
358
+ parameters: Type.Object({
359
+ command: Type.String({
360
+ description: "gws arguments without the leading gws executable",
361
+ minLength: 1,
362
+ maxLength: MAX_COMMAND_CHARS
363
+ }),
364
+ email: Type.String({
365
+ description: "Exact email returned by google_list_accounts",
366
+ minLength: 3,
367
+ maxLength: 320,
368
+ pattern: EMAIL_PATTERN
369
+ }),
370
+ confirmCommand: Type.Optional(Type.String({
371
+ description: "Exact command, required for destructive methods unless --dry-run is present",
372
+ minLength: 1,
373
+ maxLength: MAX_COMMAND_CHARS
374
+ }))
375
+ }, { additionalProperties: false }),
376
+ handler: async (params) => {
377
+ let parsed;
378
+ try {
379
+ parsed = parseGwsCommand(params.command, params.confirmCommand);
380
+ } catch (error) {
381
+ throw publicToolError(error instanceof Error ? error.message : "Invalid Google Workspace command");
382
+ }
383
+ const account = await resolveAccount(params.email);
384
+ const { workspacePath } = ensureClient();
385
+ const result = await executeGws({
386
+ args: parsed.args,
387
+ configDir: account.configDir,
388
+ workspacePath
389
+ });
390
+ if (result.exitCode !== 0) {
391
+ const diagnostic = safeGwsDiagnostic(result.stderr || result.stdout, [account.configDir, workspacePath]);
392
+ return {
393
+ status: "error",
394
+ error: `gws command failed with exit code ${String(result.exitCode)}`,
395
+ ...diagnostic ? { diagnostic } : {},
396
+ truncated: result.truncated
397
+ };
398
+ }
399
+ return {
400
+ account: account.email,
401
+ command: `gws ${parsed.command}`,
402
+ ...result
403
+ };
404
+ }
405
+ }),
406
+ defineTool({
407
+ name: "google_disconnect_account",
408
+ description: "Permanently disconnect one Google account from this agent. Copy the exact account email into confirmEmail only after the user approves.",
409
+ parameters: Type.Object({
410
+ email: Type.String({
411
+ description: "Exact email returned by google_list_accounts",
412
+ minLength: 3,
413
+ maxLength: 320,
414
+ pattern: EMAIL_PATTERN
415
+ }),
416
+ confirmEmail: Type.String({
417
+ description: "Exact selected account email confirming permanent disconnect",
418
+ minLength: 3,
419
+ maxLength: 320,
420
+ pattern: EMAIL_PATTERN
421
+ })
422
+ }, { additionalProperties: false }),
423
+ handler: async (params) => {
424
+ const account = await resolveAccount(params.email);
425
+ if (params.confirmEmail !== account.email) throw publicToolError("confirmEmail must exactly match the selected Google account email");
426
+ const { client } = ensureClient();
427
+ const result = await client.disconnectGoogleAccount(account.email);
428
+ let remaining;
429
+ let refreshRequired = false;
430
+ try {
431
+ remaining = normalizeGoogleAccounts(result, homeDirectory);
432
+ } catch {
433
+ remaining = [];
434
+ refreshRequired = true;
435
+ }
436
+ getState().accounts = remaining;
437
+ return {
438
+ message: `${account.email} has been disconnected`,
439
+ remainingAccounts: remaining.map(({ email, displayName, connectedAt }) => ({
440
+ email,
441
+ displayName,
442
+ connectedAt
443
+ })),
444
+ ...refreshRequired ? { refreshRequired: true } : {}
445
+ };
446
+ }
447
+ })
448
+ ];
449
+ const stop = (log, message) => {
450
+ const state = getState();
451
+ state.generation += 1;
452
+ state.client = null;
453
+ state.workspacePath = null;
454
+ state.accounts = [];
455
+ state.refreshPromise = null;
456
+ resetActivation(GOOGLE_ACTIVATION_KEY);
457
+ log.info(message);
458
+ };
459
+ return {
460
+ id: "@alfe.ai/openclaw-google",
461
+ name: "Alfe Google Workspace Plugin",
462
+ description: "Multi-account Google Workspace management with an explicit account selector",
463
+ version: PLUGIN_VERSION,
464
+ activate(api) {
465
+ installErrorCapture(api, { plugin: "openclaw-google" });
466
+ for (const tool of tools) api.registerTool(tool);
467
+ api.logger.info(`Registered ${String(tools.length)} Google tools: ${tools.map((tool) => tool.name).join(", ")}`);
468
+ api.registerService?.({
469
+ id: "alfe-google-workspace",
470
+ start: () => {
471
+ guardedStart(GOOGLE_ACTIVATION_KEY, api.logger, async () => {
472
+ const state = getState();
473
+ state.generation += 1;
474
+ ensureClient();
475
+ try {
476
+ const accounts = await refreshAccounts();
477
+ api.logger.info(`Cached ${String(accounts.length)} Google account(s)`);
478
+ } catch {
479
+ api.logger.warn("Google account pre-cache failed; tools will retry on demand");
480
+ }
481
+ api.logger.info("Alfe Google Workspace plugin activated");
482
+ });
483
+ },
484
+ stop: () => {
485
+ stop(api.logger, "Alfe Google Workspace plugin stopped");
486
+ }
487
+ });
488
+ },
489
+ deactivate(api) {
490
+ stop(api.logger, "Alfe Google Workspace plugin deactivated");
491
+ }
492
+ };
493
+ }
494
+ async function executeGwsCommand(options) {
495
+ const confined = confineGwsFileArguments(options.args, options.workspacePath);
496
+ const environment = buildGwsEnvironment(options.configDir);
497
+ return new Promise((resolveResult) => {
498
+ execFile("gws", confined.args, {
499
+ cwd: confined.workspacePath,
500
+ env: environment,
501
+ timeout: GWS_TIMEOUT_MS,
502
+ maxBuffer: GWS_MAX_BUFFER_BYTES,
503
+ windowsHide: true
504
+ }, (error, stdout, stderr) => {
505
+ const boundedStdout = boundToolOutput(stdout);
506
+ const boundedStderr = boundToolOutput(stderr || (error && typeof error.code === "string" ? "gws command could not be started" : ""));
507
+ resolveResult({
508
+ stdout: boundedStdout.text,
509
+ stderr: boundedStderr.text,
510
+ exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
511
+ truncated: boundedStdout.truncated || boundedStderr.truncated
512
+ });
513
+ });
514
+ });
515
+ }
516
+ function getGlobalRuntimeState() {
517
+ const globalRecord = globalThis;
518
+ const existing = globalRecord[RUNTIME_STATE_KEY];
519
+ if (isRuntimeState(existing)) return existing;
520
+ const state = createGooglePluginRuntimeState();
521
+ globalRecord[RUNTIME_STATE_KEY] = state;
522
+ return state;
523
+ }
524
+ function isRuntimeState(value) {
525
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
526
+ const candidate = value;
527
+ return (candidate.client === null || typeof candidate.client === "object") && (candidate.workspacePath === null || typeof candidate.workspacePath === "string") && Array.isArray(candidate.accounts) && (candidate.refreshPromise === null || candidate.refreshPromise instanceof Promise) && typeof candidate.generation === "number";
528
+ }
529
+ function validatePackageVersion(value) {
530
+ if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)) throw new Error("openclaw-google package version is invalid");
531
+ return value;
532
+ }
533
+ //#endregion
534
+ //#region src/plugin.ts
535
+ /** OpenClaw extension entry: default-only to preserve plugin loader interop. */
536
+ const plugin = createGooglePlugin();
537
+ //#endregion
538
+ export { plugin as t };
539
+
540
+ //# sourceMappingURL=plugin2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin2.js","names":["EMAIL_PATTERN"],"sources":["../src/boundary.ts","../src/runtime.ts","../src/plugin.ts"],"sourcesContent":["import {\n lstatSync,\n realpathSync,\n statSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport {\n dirname,\n isAbsolute,\n join,\n relative,\n resolve,\n sep,\n} from \"node:path\";\n\nconst MAX_ACCOUNTS = 128;\nconst MAX_EMAIL_CHARS = 320;\nconst MAX_DISPLAY_NAME_CHARS = 512;\nexport const MAX_COMMAND_CHARS = 64 * 1024;\nconst MAX_COMMAND_ARGS = 256;\nconst MAX_FILE_PATH_CHARS = 8_192;\nexport const MAX_TOOL_OUTPUT_BYTES = 256 * 1024;\nconst EMAIL_PATTERN = /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/u;\nconst DESTRUCTIVE_METHODS = new Set([\n \"batchclear\",\n \"batchdelete\",\n \"clear\",\n \"delete\",\n \"emptytrash\",\n \"purge\",\n \"remove\",\n \"trash\",\n \"wipe\",\n]);\nconst FILE_FLAGS = new Set([\"--output\", \"--upload\"]);\nconst CHILD_ENV_ALLOWLIST = [\n \"HOME\",\n \"HTTP_PROXY\",\n \"HTTPS_PROXY\",\n \"LANG\",\n \"LC_ALL\",\n \"LC_CTYPE\",\n \"NO_PROXY\",\n \"PATH\",\n \"SSL_CERT_DIR\",\n \"SSL_CERT_FILE\",\n \"TZ\",\n] as const;\n\nexport interface GoogleAccountInfo {\n email: string;\n displayName?: string;\n connectedAt?: string;\n configDir: string;\n}\n\nexport interface ParsedGwsCommand {\n command: string;\n args: string[];\n destructive: boolean;\n}\n\nexport interface BoundedOutput {\n text: string;\n truncated: boolean;\n}\n\nexport function normalizeGoogleAccounts(\n value: unknown,\n homeDirectory: string = homedir(),\n): GoogleAccountInfo[] {\n const root = requireRecord(value, \"Google accounts response\");\n if (!Array.isArray(root.accounts) || root.accounts.length > MAX_ACCOUNTS) {\n throw new Error(`Google accounts response must contain at most ${String(MAX_ACCOUNTS)} accounts`);\n }\n\n const selectors = new Set<string>();\n const configDirectories = new Set<string>();\n return root.accounts.map((raw, index) => {\n const account = requireRecord(raw, `Google account ${String(index)}`);\n const email = validateGoogleEmail(account.email, \"Google account email\");\n const selector = email.toLowerCase();\n if (selectors.has(selector)) throw new Error(\"Google accounts response contains a duplicate email\");\n selectors.add(selector);\n\n const configDir = resolveConfigDir(email, homeDirectory);\n const configKey = configDir.toLowerCase();\n if (configDirectories.has(configKey)) {\n throw new Error(\"Google account emails map to an ambiguous gws config directory\");\n }\n configDirectories.add(configKey);\n\n return {\n email,\n displayName: optionalDisplayText(account.displayName, \"Google account display name\"),\n connectedAt: optionalConnectedAt(account.connectedAt),\n configDir,\n };\n });\n}\n\nexport function normalizeAccountSelector(value: unknown): string {\n return validateGoogleEmail(value, \"Google account selector\").toLowerCase();\n}\n\nexport function sanitizeEmail(email: string): string {\n return validateGoogleEmail(email, \"Google account email\")\n .replace(/@/gu, \"-\")\n .replace(/\\./gu, \"-\");\n}\n\nexport function resolveConfigDir(\n email: string,\n homeDirectory: string = homedir(),\n): string {\n return join(homeDirectory, \".config\", `gws-${sanitizeEmail(email)}`);\n}\n\nexport function parseGwsCommand(\n value: unknown,\n confirmation: unknown,\n): ParsedGwsCommand {\n const command = requireBoundedString(value, \"command\", MAX_COMMAND_CHARS).trim();\n if (!command) throw new Error(\"Command cannot be empty\");\n if (hasControlCharacter(command)) throw new Error(\"Command cannot contain control characters\");\n\n const args = tokenizeCommand(command);\n if (args.length === 0) throw new Error(\"Command cannot be empty\");\n if (args.length > MAX_COMMAND_ARGS) {\n throw new Error(`Command cannot contain more than ${String(MAX_COMMAND_ARGS)} arguments`);\n }\n if (args[0]?.toLowerCase() === \"auth\") {\n throw new Error(\"Authentication commands are managed by the Google integration\");\n }\n\n const positional = args.slice(0, args.findIndex((arg) => arg.startsWith(\"--\")) === -1\n ? args.length\n : args.findIndex((arg) => arg.startsWith(\"--\")));\n const destructive = !args.includes(\"--dry-run\")\n && positional.some((arg) => DESTRUCTIVE_METHODS.has(arg.toLowerCase()));\n if (destructive && confirmation !== command) {\n throw new Error(\"confirmCommand must exactly match command for a destructive gws operation\");\n }\n\n return { command, args, destructive };\n}\n\nexport function confineGwsFileArguments(\n args: readonly string[],\n workspacePath: string,\n): { args: string[]; workspacePath: string } {\n let workspaceRealPath: string;\n try {\n workspaceRealPath = realpathSync(workspacePath);\n } catch {\n throw new Error(\"Configured workspace is unavailable\");\n }\n if (!statSync(workspaceRealPath).isDirectory()) {\n throw new Error(\"Configured workspace path is not a directory\");\n }\n const rewritten = [...args];\n for (let index = 0; index < rewritten.length; index += 1) {\n const argument = rewritten.at(index);\n if (argument === undefined) continue;\n\n const equalIndex = argument.indexOf(\"=\");\n const flag = equalIndex === -1 ? argument : argument.slice(0, equalIndex);\n if (!FILE_FLAGS.has(flag)) continue;\n\n const inlineValue = equalIndex === -1 ? undefined : argument.slice(equalIndex + 1);\n const valueIndex = inlineValue === undefined ? index + 1 : index;\n const rawPath = inlineValue ?? rewritten.at(valueIndex);\n if (rawPath === undefined || rawPath.length === 0 || rawPath.startsWith(\"--\")) {\n throw new Error(`${flag} requires a workspace-relative file path`);\n }\n const confined = flag === \"--upload\"\n ? confineUploadPath(rawPath, workspaceRealPath)\n : confineOutputPath(rawPath, workspaceRealPath);\n if (inlineValue === undefined) {\n rewritten[valueIndex] = confined;\n index = valueIndex;\n } else {\n rewritten[index] = `${flag}=${confined}`;\n }\n }\n return { args: rewritten, workspacePath: workspaceRealPath };\n}\n\nexport function buildGwsEnvironment(\n configDir: string,\n source: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n const environment = Object.create(null) as Record<string, string>;\n for (const name of CHILD_ENV_ALLOWLIST) {\n const value = source[name];\n if (value !== undefined && !hasControlCharacter(value)) environment[name] = value;\n }\n environment.GOOGLE_WORKSPACE_CLI_CONFIG_DIR = requireBoundedString(\n configDir,\n \"gws config directory\",\n MAX_FILE_PATH_CHARS,\n );\n if (hasControlCharacter(environment.GOOGLE_WORKSPACE_CLI_CONFIG_DIR)) {\n throw new Error(\"gws config directory cannot contain control characters\");\n }\n return environment;\n}\n\nexport function boundToolOutput(value: unknown): BoundedOutput {\n const buffer = Buffer.from(typeof value === \"string\" ? value : \"\", \"utf8\");\n if (buffer.byteLength <= MAX_TOOL_OUTPUT_BYTES) {\n return { text: buffer.toString(\"utf8\"), truncated: false };\n }\n return {\n text: `${buffer.subarray(0, MAX_TOOL_OUTPUT_BYTES).toString(\"utf8\")}\\n[output truncated]`,\n truncated: true,\n };\n}\n\nexport function safeGwsDiagnostic(value: string, secrets: readonly string[] = []): string {\n let redacted = value\n .slice(0, 4_096)\n .replace(/\\b(Bearer|Basic)\\s+\\S+/giu, \"$1 [REDACTED]\")\n .replace(\n /\\b(api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password)\\s*[=:]\\s*\\S+/giu,\n \"$1=[REDACTED]\",\n )\n .replace(/\\balfe_[A-Za-z0-9_-]{8,}/gu, \"[REDACTED]\");\n for (const secret of secrets) {\n if (secret.length >= 4) redacted = redacted.split(secret).join(\"[REDACTED]\");\n }\n let flattened = \"\";\n for (const character of redacted) {\n const codePoint = character.codePointAt(0) ?? 0;\n flattened += codePoint < 32 || codePoint === 127 ? \" \" : character;\n }\n return flattened.trim();\n}\n\nfunction tokenizeCommand(command: string): string[] {\n const args: string[] = [];\n let current = \"\";\n let tokenStarted = false;\n let quote: \"single\" | \"double\" | null = null;\n let escaped = false;\n\n for (const character of command) {\n if (escaped) {\n current += character;\n tokenStarted = true;\n escaped = false;\n continue;\n }\n if (quote === \"single\") {\n if (character === \"'\") quote = null;\n else current += character;\n tokenStarted = true;\n continue;\n }\n if (quote === \"double\") {\n if (character === '\"') quote = null;\n else if (character === \"\\\\\") escaped = true;\n else current += character;\n tokenStarted = true;\n continue;\n }\n if (character === \"'\") {\n quote = \"single\";\n tokenStarted = true;\n } else if (character === '\"') {\n quote = \"double\";\n tokenStarted = true;\n } else if (character === \"\\\\\") {\n escaped = true;\n tokenStarted = true;\n } else if (/\\s/u.test(character)) {\n if (tokenStarted) {\n args.push(current);\n current = \"\";\n tokenStarted = false;\n }\n } else {\n current += character;\n tokenStarted = true;\n }\n }\n if (quote !== null || escaped) throw new Error(\"Command contains an unterminated quote or escape\");\n if (tokenStarted) args.push(current);\n return args;\n}\n\nfunction confineUploadPath(value: string, workspaceRealPath: string): string {\n const candidate = resolveCandidatePath(value, workspaceRealPath);\n try {\n if (lstatSync(candidate).isSymbolicLink()) throw new Error(\"unsafe upload\");\n const realCandidate = realpathSync(candidate);\n if (!isWithin(workspaceRealPath, realCandidate) || !statSync(realCandidate).isFile()) {\n throw new Error(\"unsafe upload\");\n }\n return realCandidate;\n } catch {\n throw new Error(\"Upload path must be a regular file inside the configured workspace\");\n }\n}\n\nfunction confineOutputPath(value: string, workspaceRealPath: string): string {\n const candidate = resolveCandidatePath(value, workspaceRealPath);\n let parent: string;\n try {\n parent = realpathSync(dirname(candidate));\n } catch {\n throw new Error(\"Output path must have an existing directory inside the configured workspace\");\n }\n if (!isWithin(workspaceRealPath, parent)) {\n throw new Error(\"Output path must stay inside the configured workspace\");\n }\n try {\n const status = lstatSync(candidate);\n if (status.isSymbolicLink() || status.isDirectory()) {\n throw new Error(\"Output path must be a non-symlink file inside the configured workspace\");\n }\n const realCandidate = realpathSync(candidate);\n if (!isWithin(workspaceRealPath, realCandidate)) {\n throw new Error(\"Output path must stay inside the configured workspace\");\n }\n } catch (error) {\n const code = isRecord(error) ? error.code : undefined;\n if (code !== \"ENOENT\") {\n throw new Error(\"Output path must be a non-symlink file inside the configured workspace\");\n }\n }\n return candidate;\n}\n\nfunction resolveCandidatePath(value: string, workspaceRealPath: string): string {\n const rawPath = requireBoundedString(value, \"gws file path\", MAX_FILE_PATH_CHARS);\n if (hasControlCharacter(rawPath)) throw new Error(\"gws file path cannot contain control characters\");\n return isAbsolute(rawPath) ? resolve(rawPath) : resolve(workspaceRealPath, rawPath);\n}\n\nfunction isWithin(root: string, candidate: string): boolean {\n const child = relative(root, candidate);\n return child === \"\" || (!isAbsolute(child) && child !== \"..\" && !child.startsWith(`..${sep}`));\n}\n\nfunction validateGoogleEmail(value: unknown, label: string): string {\n const email = requireBoundedString(value, label, MAX_EMAIL_CHARS);\n if (\n email !== email.trim() ||\n email.includes(\"/\") ||\n email.includes(\"\\\\\") ||\n !EMAIL_PATTERN.test(email)\n ) {\n throw new Error(`${label} is invalid`);\n }\n return email;\n}\n\nfunction optionalDisplayText(value: unknown, label: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n const text = requireBoundedString(value, label, MAX_DISPLAY_NAME_CHARS);\n if (hasControlCharacter(text)) throw new Error(`${label} contains control characters`);\n return text;\n}\n\nfunction optionalConnectedAt(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n const connectedAt = requireBoundedString(value, \"Google account connectedAt\", 128);\n if (Number.isNaN(Date.parse(connectedAt))) throw new Error(\"Google account connectedAt is invalid\");\n return connectedAt;\n}\n\nfunction requireBoundedString(value: unknown, label: string, maxLength: number): string {\n if (typeof value !== \"string\" || value.length < 1 || value.length > maxLength) {\n throw new Error(`${label} must be a non-empty string of at most ${String(maxLength)} characters`);\n }\n return value;\n}\n\nfunction requireRecord(value: unknown, label: string): Record<string, unknown> {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${label} must be an object`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint < 32 || codePoint === 127) return true;\n }\n return false;\n}\n","import { execFile, type ExecFileException } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { resolveConfig, type ResolvedConfig } from \"@alfe.ai/config\";\nimport { AgentApiClient, installToolErrorCapture } from \"@alfe.ai/agent-api-client\";\nimport {\n defineTool,\n getActivationKey,\n guardedStart,\n publicToolError,\n resetActivation,\n type ToolDef,\n} from \"@alfe.ai/openclaw-plugin-kit\";\nimport {\n boundToolOutput,\n buildGwsEnvironment,\n confineGwsFileArguments,\n MAX_COMMAND_CHARS,\n normalizeAccountSelector,\n normalizeGoogleAccounts,\n parseGwsCommand,\n safeGwsDiagnostic,\n type GoogleAccountInfo,\n} from \"./boundary.js\";\n\nconst require = createRequire(import.meta.url);\nconst pkg = require(\"../package.json\") as { version?: unknown };\n\nexport const PLUGIN_VERSION = validatePackageVersion(pkg.version);\nexport const GOOGLE_ACTIVATION_KEY = getActivationKey(\"google\");\nconst RUNTIME_STATE_KEY = \"__alfeGooglePluginRuntimeState\";\nconst GWS_TIMEOUT_MS = 60_000;\nconst GWS_MAX_BUFFER_BYTES = 1024 * 1024;\nconst EMAIL_PATTERN = \"^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$\";\n\nexport interface PluginLogger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\ninterface PluginServiceContext {\n config?: Record<string, unknown>;\n workspaceDir?: string;\n stateDir?: string;\n logger?: PluginLogger;\n}\n\nexport interface PluginApi {\n logger: PluginLogger;\n registrationMode?:\n | \"full\"\n | \"discovery\"\n | \"tool-discovery\"\n | \"setup-only\"\n | \"setup-runtime\"\n | \"cli-metadata\";\n registerTool(tool: ToolDef<TSchema>): void;\n registerService?(service: {\n id: string;\n start: (context: PluginServiceContext) => void | Promise<void>;\n stop?: (context: PluginServiceContext) => void | Promise<void>;\n }): void;\n}\n\nexport interface GoogleClient {\n getGoogleCredentials(): Promise<unknown>;\n disconnectGoogleAccount(email: string): Promise<unknown>;\n}\n\nexport interface GwsExecutionOptions {\n args: string[];\n configDir: string;\n workspacePath: string;\n}\n\nexport interface GwsExecutionResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n truncated: boolean;\n}\n\nexport interface GooglePluginRuntimeState {\n client: GoogleClient | null;\n workspacePath: string | null;\n accounts: GoogleAccountInfo[];\n refreshPromise: Promise<GoogleAccountInfo[]> | null;\n generation: number;\n}\n\nexport interface GooglePluginDependencies {\n resolveConfig?: () => Pick<ResolvedConfig, \"apiKey\" | \"apiUrl\" | \"workspacePath\">;\n createClient?: (config: { apiKey: string; apiUrl: string }) => GoogleClient;\n executeGws?: (options: GwsExecutionOptions) => Promise<GwsExecutionResult>;\n installErrorCapture?: typeof installToolErrorCapture;\n runtimeState?: GooglePluginRuntimeState;\n homeDirectory?: string;\n}\n\nexport interface GooglePlugin {\n id: string;\n name: string;\n description: string;\n version: string;\n activate(api: PluginApi): void;\n deactivate(api: PluginApi): void;\n}\n\nexport function createGooglePluginRuntimeState(): GooglePluginRuntimeState {\n return {\n client: null,\n workspacePath: null,\n accounts: [],\n refreshPromise: null,\n generation: 0,\n };\n}\n\nexport function createGooglePlugin(\n dependencies: GooglePluginDependencies = {},\n): GooglePlugin {\n const resolveRuntimeConfig = dependencies.resolveConfig ?? resolveConfig;\n const createClient = dependencies.createClient ?? ((config) => new AgentApiClient(config));\n const executeGws = dependencies.executeGws ?? executeGwsCommand;\n const installErrorCapture = dependencies.installErrorCapture ?? installToolErrorCapture;\n const homeDirectory = dependencies.homeDirectory ?? homedir();\n const getState = (): GooglePluginRuntimeState => dependencies.runtimeState ?? getGlobalRuntimeState();\n\n const ensureClient = (): {\n client: GoogleClient;\n workspacePath: string;\n } => {\n const state = getState();\n if (state.client === null || state.workspacePath === null) {\n const config = resolveRuntimeConfig();\n state.client = createClient({ apiKey: config.apiKey, apiUrl: config.apiUrl });\n state.workspacePath = config.workspacePath;\n }\n return { client: state.client, workspacePath: state.workspacePath };\n };\n\n const refreshAccounts = async (): Promise<GoogleAccountInfo[]> => {\n const state = getState();\n if (state.refreshPromise !== null) return state.refreshPromise;\n const { client } = ensureClient();\n const generation = state.generation;\n const refresh = client.getGoogleCredentials().then((value) => {\n const accounts = normalizeGoogleAccounts(value, homeDirectory);\n if (state.generation === generation && state.client === client) state.accounts = accounts;\n return accounts;\n });\n state.refreshPromise = refresh;\n try {\n return await refresh;\n } finally {\n if (state.refreshPromise === refresh) state.refreshPromise = null;\n }\n };\n\n const resolveAccount = async (value: unknown): Promise<GoogleAccountInfo> => {\n const selector = normalizeAccountSelector(value);\n const state = getState();\n let account = state.accounts.find((candidate) => candidate.email.toLowerCase() === selector);\n if (account === undefined) {\n const accounts = await refreshAccounts();\n account = accounts.find((candidate) => candidate.email.toLowerCase() === selector);\n }\n if (account === undefined) {\n throw publicToolError(\"Google account not found; call google_list_accounts first\");\n }\n return account;\n };\n\n const tools: ToolDef<TSchema>[] = [\n defineTool({\n name: \"google_list_accounts\",\n description:\n \"List connected Google Workspace accounts. Use this before a credential-touching \" +\n \"tool when the exact account email is not already known.\",\n parameters: Type.Object({}, { additionalProperties: false }),\n handler: async () => {\n const accounts = await refreshAccounts();\n return {\n accounts: accounts.map(({ email, displayName, connectedAt }) => ({\n email,\n displayName,\n connectedAt,\n })),\n count: accounts.length,\n };\n },\n }),\n defineTool({\n name: \"google_run_command\",\n description:\n \"Run a Google Workspace CLI command for one explicitly selected account. The command \" +\n \"supports quoted JSON exactly like gws (for example: drive files list --params \" +\n \"'{\\\"pageSize\\\": 10}'). Authentication commands are blocked. --upload and --output \" +\n \"paths must remain inside the configured workspace. Use --dry-run to preview mutations; \" +\n \"otherwise copy an exact destructive command into confirmCommand when requested.\",\n parameters: Type.Object(\n {\n command: Type.String({\n description: \"gws arguments without the leading gws executable\",\n minLength: 1,\n maxLength: MAX_COMMAND_CHARS,\n }),\n email: Type.String({\n description: \"Exact email returned by google_list_accounts\",\n minLength: 3,\n maxLength: 320,\n pattern: EMAIL_PATTERN,\n }),\n confirmCommand: Type.Optional(Type.String({\n description: \"Exact command, required for destructive methods unless --dry-run is present\",\n minLength: 1,\n maxLength: MAX_COMMAND_CHARS,\n })),\n },\n { additionalProperties: false },\n ),\n handler: async (params) => {\n let parsed: ReturnType<typeof parseGwsCommand>;\n try {\n parsed = parseGwsCommand(params.command, params.confirmCommand);\n } catch (error) {\n throw publicToolError(\n error instanceof Error ? error.message : \"Invalid Google Workspace command\",\n );\n }\n const account = await resolveAccount(params.email);\n const { workspacePath } = ensureClient();\n const result = await executeGws({\n args: parsed.args,\n configDir: account.configDir,\n workspacePath,\n });\n if (result.exitCode !== 0) {\n const diagnostic = safeGwsDiagnostic(\n result.stderr || result.stdout,\n [account.configDir, workspacePath],\n );\n return {\n status: \"error\",\n error: `gws command failed with exit code ${String(result.exitCode)}`,\n ...(diagnostic ? { diagnostic } : {}),\n truncated: result.truncated,\n };\n }\n return {\n account: account.email,\n command: `gws ${parsed.command}`,\n ...result,\n };\n },\n }),\n defineTool({\n name: \"google_disconnect_account\",\n description:\n \"Permanently disconnect one Google account from this agent. Copy the exact account \" +\n \"email into confirmEmail only after the user approves.\",\n parameters: Type.Object(\n {\n email: Type.String({\n description: \"Exact email returned by google_list_accounts\",\n minLength: 3,\n maxLength: 320,\n pattern: EMAIL_PATTERN,\n }),\n confirmEmail: Type.String({\n description: \"Exact selected account email confirming permanent disconnect\",\n minLength: 3,\n maxLength: 320,\n pattern: EMAIL_PATTERN,\n }),\n },\n { additionalProperties: false },\n ),\n handler: async (params) => {\n const account = await resolveAccount(params.email);\n if (params.confirmEmail !== account.email) {\n throw publicToolError(\n \"confirmEmail must exactly match the selected Google account email\",\n );\n }\n const { client } = ensureClient();\n const result = await client.disconnectGoogleAccount(account.email);\n let remaining: GoogleAccountInfo[];\n let refreshRequired = false;\n try {\n remaining = normalizeGoogleAccounts(result, homeDirectory);\n } catch {\n remaining = [];\n refreshRequired = true;\n }\n getState().accounts = remaining;\n return {\n message: `${account.email} has been disconnected`,\n remainingAccounts: remaining.map(({ email, displayName, connectedAt }) => ({\n email,\n displayName,\n connectedAt,\n })),\n ...(refreshRequired ? { refreshRequired: true } : {}),\n };\n },\n }),\n ];\n\n const stop = (log: PluginLogger, message: string): void => {\n const state = getState();\n state.generation += 1;\n state.client = null;\n state.workspacePath = null;\n state.accounts = [];\n state.refreshPromise = null;\n resetActivation(GOOGLE_ACTIVATION_KEY);\n log.info(message);\n };\n\n return {\n id: \"@alfe.ai/openclaw-google\",\n name: \"Alfe Google Workspace Plugin\",\n description: \"Multi-account Google Workspace management with an explicit account selector\",\n version: PLUGIN_VERSION,\n\n activate(api: PluginApi): void {\n installErrorCapture(api, { plugin: \"openclaw-google\" });\n for (const tool of tools) api.registerTool(tool);\n api.logger.info(\n `Registered ${String(tools.length)} Google tools: ${tools.map((tool) => tool.name).join(\", \")}`,\n );\n\n api.registerService?.({\n id: \"alfe-google-workspace\",\n start: () => {\n guardedStart(GOOGLE_ACTIVATION_KEY, api.logger, async () => {\n const state = getState();\n state.generation += 1;\n ensureClient();\n try {\n const accounts = await refreshAccounts();\n api.logger.info(`Cached ${String(accounts.length)} Google account(s)`);\n } catch {\n api.logger.warn(\"Google account pre-cache failed; tools will retry on demand\");\n }\n api.logger.info(\"Alfe Google Workspace plugin activated\");\n });\n },\n stop: () => {\n stop(api.logger, \"Alfe Google Workspace plugin stopped\");\n },\n });\n },\n\n deactivate(api: PluginApi): void {\n stop(api.logger, \"Alfe Google Workspace plugin deactivated\");\n },\n };\n}\n\nexport async function executeGwsCommand(\n options: GwsExecutionOptions,\n): Promise<GwsExecutionResult> {\n const confined = confineGwsFileArguments(options.args, options.workspacePath);\n const environment = buildGwsEnvironment(options.configDir);\n return new Promise((resolveResult) => {\n execFile(\n \"gws\",\n confined.args,\n {\n cwd: confined.workspacePath,\n env: environment,\n timeout: GWS_TIMEOUT_MS,\n maxBuffer: GWS_MAX_BUFFER_BYTES,\n windowsHide: true,\n },\n (error: ExecFileException | null, stdout: string, stderr: string) => {\n const boundedStdout = boundToolOutput(stdout);\n const boundedStderr = boundToolOutput(stderr || (error && typeof error.code === \"string\"\n ? \"gws command could not be started\"\n : \"\"));\n resolveResult({\n stdout: boundedStdout.text,\n stderr: boundedStderr.text,\n exitCode: typeof error?.code === \"number\" ? error.code : error ? 1 : 0,\n truncated: boundedStdout.truncated || boundedStderr.truncated,\n });\n },\n );\n });\n}\n\nfunction getGlobalRuntimeState(): GooglePluginRuntimeState {\n const globalRecord = globalThis as Record<string, unknown>;\n const existing = globalRecord[RUNTIME_STATE_KEY];\n if (isRuntimeState(existing)) return existing;\n const state = createGooglePluginRuntimeState();\n globalRecord[RUNTIME_STATE_KEY] = state;\n return state;\n}\n\nfunction isRuntimeState(value: unknown): value is GooglePluginRuntimeState {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n const candidate = value as Partial<GooglePluginRuntimeState>;\n return (candidate.client === null || typeof candidate.client === \"object\")\n && (candidate.workspacePath === null || typeof candidate.workspacePath === \"string\")\n && Array.isArray(candidate.accounts)\n && (candidate.refreshPromise === null || candidate.refreshPromise instanceof Promise)\n && typeof candidate.generation === \"number\";\n}\n\nfunction validatePackageVersion(value: unknown): string {\n if (typeof value !== \"string\" || !/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/u.test(value)) {\n throw new Error(\"openclaw-google package version is invalid\");\n }\n return value;\n}\n","/** OpenClaw extension entry: default-only to preserve plugin loader interop. */\n\nimport { createGooglePlugin, type GooglePlugin } from \"./runtime.js\";\n\nconst plugin: GooglePlugin = createGooglePlugin();\n\nexport default plugin;\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "@alfe.ai/openclaw-google",
3
3
  "name": "Google Workspace",
4
- "description": "Multi-account Google Workspace management — list accounts and run gws commands with an explicit account selector",
4
+ "description": "Bounded multi-account Google Workspace commands with an explicit account selector",
5
5
  "entry": "./dist/plugin.js",
6
6
  "activation": { "onStartup": false },
7
7
  "contracts": {