@alfe.ai/openclaw-google 0.0.1

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/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const require_plugin = require("./plugin.cjs");
2
+ module.exports = require_plugin;
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.cjs";
2
+ export { plugin as default };
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.js";
2
+ export { plugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.js";
2
+ export { plugin as default };
@@ -0,0 +1,214 @@
1
+ let _sinclair_typebox = require("@sinclair/typebox");
2
+ let _alfe_ai_config = require("@alfe.ai/config");
3
+ let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
4
+ let node_child_process = require("node:child_process");
5
+ let node_path = require("node:path");
6
+ let node_os = require("node:os");
7
+ //#region src/plugin.ts
8
+ /**
9
+ * @alfe/openclaw-google — OpenClaw native plugin
10
+ *
11
+ * Registers Google Workspace account management tools with OpenClaw.
12
+ * Provides multi-account awareness so the agent can:
13
+ * - List connected Google accounts
14
+ * - Run gws CLI commands targeting a specific account
15
+ * - Set the default account
16
+ * - Disconnect an account
17
+ */
18
+ function ok(data) {
19
+ return {
20
+ content: [{
21
+ type: "text",
22
+ text: JSON.stringify(data)
23
+ }],
24
+ details: data
25
+ };
26
+ }
27
+ function errResult(message) {
28
+ return {
29
+ content: [{
30
+ type: "text",
31
+ text: JSON.stringify({ error: message })
32
+ }],
33
+ details: { error: message }
34
+ };
35
+ }
36
+ function defineTool(def) {
37
+ return {
38
+ name: def.name,
39
+ description: def.description,
40
+ label: def.name,
41
+ parameters: def.parameters,
42
+ execute: async (_toolCallId, params) => {
43
+ try {
44
+ return ok(await def.handler(params));
45
+ } catch (e) {
46
+ return errResult(e instanceof Error ? e.message : "Unknown error");
47
+ }
48
+ }
49
+ };
50
+ }
51
+ function sanitizeEmail(email) {
52
+ return email.replace(/@/g, "-").replace(/\./g, "-");
53
+ }
54
+ function resolveConfigDir(email, isDefault) {
55
+ const dirName = isDefault ? "gws" : `gws-${sanitizeEmail(email)}`;
56
+ return (0, node_path.join)((0, node_os.homedir)(), ".config", dirName);
57
+ }
58
+ let client = null;
59
+ let cachedAccounts = [];
60
+ function getClient() {
61
+ if (!client) throw new Error("Google plugin not initialized — no API client");
62
+ return client;
63
+ }
64
+ async function refreshAccountCache() {
65
+ const creds = await getClient().getGoogleCredentials();
66
+ cachedAccounts = (creds.accounts ?? [{
67
+ email: creds.email,
68
+ isDefault: true,
69
+ displayName: void 0
70
+ }]).map((a, i) => ({
71
+ email: a.email,
72
+ isDefault: i === 0 ? true : a.isDefault,
73
+ displayName: a.displayName,
74
+ configDir: resolveConfigDir(a.email, i === 0 ? true : a.isDefault)
75
+ }));
76
+ return cachedAccounts;
77
+ }
78
+ function findAccount(email) {
79
+ if (!email) {
80
+ const def = cachedAccounts.find((a) => a.isDefault);
81
+ if (!def) {
82
+ if (cachedAccounts.length === 0) throw new Error("No Google accounts connected");
83
+ return cachedAccounts[0];
84
+ }
85
+ return def;
86
+ }
87
+ const account = cachedAccounts.find((a) => a.email === email);
88
+ if (!account) throw new Error(`Google account "${email}" not found. Available: ${cachedAccounts.map((a) => a.email).join(", ")}`);
89
+ return account;
90
+ }
91
+ function runGwsCommand(args, configDir) {
92
+ return new Promise((resolve) => {
93
+ (0, node_child_process.execFile)("gws", args, {
94
+ env: {
95
+ ...process.env,
96
+ GOOGLE_WORKSPACE_CLI_CONFIG_DIR: configDir
97
+ },
98
+ timeout: 6e4,
99
+ maxBuffer: 10 * 1024 * 1024
100
+ }, (error, stdout, stderr) => {
101
+ resolve({
102
+ stdout,
103
+ stderr,
104
+ exitCode: error?.code ?? (error ? 1 : 0)
105
+ });
106
+ });
107
+ });
108
+ }
109
+ const googleTools = [
110
+ defineTool({
111
+ name: "google_list_accounts",
112
+ description: "List all connected Google Workspace accounts. Shows email, display name, whether it's the default account, and the gws CLI config directory path. Use this to resolve which account to target (e.g., 'Kevin\\'s emails' → kevin@alfe.ai).",
113
+ parameters: _sinclair_typebox.Type.Object({}),
114
+ handler: async () => {
115
+ const accounts = await refreshAccountCache();
116
+ return {
117
+ accounts: accounts.map((a) => ({
118
+ email: a.email,
119
+ displayName: a.displayName,
120
+ isDefault: a.isDefault,
121
+ configDir: a.configDir
122
+ })),
123
+ count: accounts.length
124
+ };
125
+ }
126
+ }),
127
+ defineTool({
128
+ name: "google_run_command",
129
+ description: "Run a gws (Google Workspace CLI) command targeting a specific account. Automatically sets GOOGLE_WORKSPACE_CLI_CONFIG_DIR for the target account. If no email is specified, uses the default account. Example: google_run_command({ command: 'gmail list', email: 'kevin@alfe.ai' })",
130
+ parameters: _sinclair_typebox.Type.Object({
131
+ command: _sinclair_typebox.Type.String({ description: "The gws CLI command and arguments (e.g., 'gmail list', 'calendar agenda', 'drive list')" }),
132
+ email: _sinclair_typebox.Type.Optional(_sinclair_typebox.Type.String({ description: "Email of the Google account to use. Omit to use the default account." }))
133
+ }),
134
+ handler: async (params) => {
135
+ const { command, email } = params;
136
+ const account = findAccount(email);
137
+ const args = command.split(/\s+/).filter(Boolean);
138
+ if (args.length === 0) throw new Error("Command cannot be empty");
139
+ const result = await runGwsCommand(args, account.configDir);
140
+ return {
141
+ account: account.email,
142
+ command: `gws ${command}`,
143
+ ...result
144
+ };
145
+ }
146
+ }),
147
+ defineTool({
148
+ name: "google_set_default_account",
149
+ description: "Set a specific Google account as the default. The default account is used when no email is specified in google_run_command.",
150
+ parameters: _sinclair_typebox.Type.Object({ email: _sinclair_typebox.Type.String({ description: "Email of the Google account to set as default" }) }),
151
+ handler: async (params) => {
152
+ const { email } = params;
153
+ const result = await getClient().setDefaultGoogleAccount(email);
154
+ await refreshAccountCache();
155
+ return {
156
+ message: `${email} is now the default Google account`,
157
+ accounts: result.accounts
158
+ };
159
+ }
160
+ }),
161
+ defineTool({
162
+ name: "google_disconnect_account",
163
+ description: "Disconnect a specific Google account from this agent. Revokes the OAuth token and removes the account. If this was the default account, the next account is promoted.",
164
+ parameters: _sinclair_typebox.Type.Object({ email: _sinclair_typebox.Type.String({ description: "Email of the Google account to disconnect" }) }),
165
+ handler: async (params) => {
166
+ const { email } = params;
167
+ const result = await getClient().disconnectGoogleAccount(email);
168
+ await refreshAccountCache();
169
+ return {
170
+ message: `${email} has been disconnected`,
171
+ remainingAccounts: result.accounts
172
+ };
173
+ }
174
+ })
175
+ ];
176
+ const plugin = {
177
+ id: "@alfe.ai/openclaw-google",
178
+ name: "Alfe Google Workspace Plugin",
179
+ description: "Multi-account Google Workspace management — list accounts, run gws commands, manage defaults",
180
+ version: "0.0.1",
181
+ activate(api) {
182
+ const log = api.logger;
183
+ for (const tool of googleTools) api.registerTool(tool);
184
+ log.info(`Registered ${googleTools.length.toString()} Google tools: ${googleTools.map((t) => t.name).join(", ")}`);
185
+ if (!globalThis.__googlePluginActivated) {
186
+ globalThis.__googlePluginActivated = true;
187
+ log.info("Alfe Google Workspace plugin activating...");
188
+ try {
189
+ const config = (0, _alfe_ai_config.resolveConfig)();
190
+ client = new _alfe_ai_agent_api_client.AgentApiClient({
191
+ apiKey: config.apiKey,
192
+ apiUrl: config.apiUrl
193
+ });
194
+ refreshAccountCache().then((accounts) => {
195
+ log.info(`Cached ${accounts.length.toString()} Google account(s): ${accounts.map((a) => a.email).join(", ")}`);
196
+ }).catch((err) => {
197
+ log.warn(`Failed to pre-cache Google accounts: ${err instanceof Error ? err.message : "unknown"}`);
198
+ });
199
+ } catch (err) {
200
+ log.error(`Failed to resolve config: ${err instanceof Error ? err.message : "unknown"}`);
201
+ log.warn("Google tools will fail — no API config available");
202
+ }
203
+ log.info("Alfe Google Workspace plugin activated");
204
+ }
205
+ },
206
+ deactivate(api) {
207
+ globalThis.__googlePluginActivated = false;
208
+ client = null;
209
+ cachedAccounts = [];
210
+ api.logger.info("Alfe Google Workspace plugin deactivated");
211
+ }
212
+ };
213
+ //#endregion
214
+ module.exports = plugin;
@@ -0,0 +1,42 @@
1
+ import { TSchema } from "@sinclair/typebox";
2
+
3
+ //#region src/plugin.d.ts
4
+
5
+ interface Logger {
6
+ info(msg: string, ...args: unknown[]): void;
7
+ warn(msg: string, ...args: unknown[]): void;
8
+ error(msg: string, ...args: unknown[]): void;
9
+ debug(msg: string, ...args: unknown[]): void;
10
+ }
11
+ interface OpenClawPluginApi {
12
+ logger: Logger;
13
+ registerTool(tool: ToolDef): void;
14
+ registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
15
+ on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
16
+ priority?: number;
17
+ }): void;
18
+ }
19
+ interface ToolDef {
20
+ name: string;
21
+ description: string;
22
+ label: string;
23
+ parameters: TSchema;
24
+ execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{
25
+ content: {
26
+ type: "text";
27
+ text: string;
28
+ }[];
29
+ details: unknown;
30
+ }>;
31
+ }
32
+ declare const plugin: {
33
+ id: string;
34
+ name: string;
35
+ description: string;
36
+ version: string;
37
+ activate(api: OpenClawPluginApi): void;
38
+ deactivate(api: OpenClawPluginApi): void;
39
+ };
40
+ //#endregion
41
+ export { plugin as default };
42
+ //# sourceMappingURL=plugin.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.cts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;AA6BmE,UAXzD,MAAA,CAgBO;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MAIH,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAC0B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAA4B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;AAAO,UAdjE,iBAAA,CAkRT;EAAA,MAAA,EAjRS,MAiRT;cAtCe,CAAA,IAAA,EA1OK,OA0OL,CAAA,EAAA,IAAA;uBAgCE,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAzQqD,OAyQrD,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;EAAiB,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAAA,IAAA,GAxQyB,OAwQzB,CAAA,IAAA,CAAA,EAAA,QAAA,EAAA;;;;UAnQzB,OAAA;;;;cAII;wCAC0B,4BAA4B;;;;;;;;cAwN9D;;;;;gBAMU;kBAgCE"}
@@ -0,0 +1,42 @@
1
+ import { TSchema } from "@sinclair/typebox";
2
+
3
+ //#region src/plugin.d.ts
4
+
5
+ interface Logger {
6
+ info(msg: string, ...args: unknown[]): void;
7
+ warn(msg: string, ...args: unknown[]): void;
8
+ error(msg: string, ...args: unknown[]): void;
9
+ debug(msg: string, ...args: unknown[]): void;
10
+ }
11
+ interface OpenClawPluginApi {
12
+ logger: Logger;
13
+ registerTool(tool: ToolDef): void;
14
+ registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;
15
+ on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: {
16
+ priority?: number;
17
+ }): void;
18
+ }
19
+ interface ToolDef {
20
+ name: string;
21
+ description: string;
22
+ label: string;
23
+ parameters: TSchema;
24
+ execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{
25
+ content: {
26
+ type: "text";
27
+ text: string;
28
+ }[];
29
+ details: unknown;
30
+ }>;
31
+ }
32
+ declare const plugin: {
33
+ id: string;
34
+ name: string;
35
+ description: string;
36
+ version: string;
37
+ activate(api: OpenClawPluginApi): void;
38
+ deactivate(api: OpenClawPluginApi): void;
39
+ };
40
+ //#endregion
41
+ export { plugin as default };
42
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/plugin.ts"],"mappings":";;;;AA6BmE,UAXzD,MAAA,CAgBO;EAAA,IAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;MAIH,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAC0B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;OAA4B,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,IAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA;;AAAO,UAdjE,iBAAA,CAkRT;EAAA,MAAA,EAjRS,MAiRT;cAtCe,CAAA,IAAA,EA1OK,OA0OL,CAAA,EAAA,IAAA;uBAgCE,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAzQqD,OAyQrD,CAAA,OAAA,CAAA,CAAA,EAAA,IAAA;EAAiB,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAAA,IAAA,GAxQyB,OAwQzB,CAAA,IAAA,CAAA,EAAA,QAAA,EAAA;;;;UAnQzB,OAAA;;;;cAII;wCAC0B,4BAA4B;;;;;;;;cAwN9D;;;;;gBAMU;kBAgCE"}
package/dist/plugin.js ADDED
@@ -0,0 +1,216 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { resolveConfig } from "@alfe.ai/config";
3
+ import { AgentApiClient } from "@alfe.ai/agent-api-client";
4
+ import { execFile } from "node:child_process";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ //#region src/plugin.ts
8
+ /**
9
+ * @alfe/openclaw-google — OpenClaw native plugin
10
+ *
11
+ * Registers Google Workspace account management tools with OpenClaw.
12
+ * Provides multi-account awareness so the agent can:
13
+ * - List connected Google accounts
14
+ * - Run gws CLI commands targeting a specific account
15
+ * - Set the default account
16
+ * - Disconnect an account
17
+ */
18
+ function ok(data) {
19
+ return {
20
+ content: [{
21
+ type: "text",
22
+ text: JSON.stringify(data)
23
+ }],
24
+ details: data
25
+ };
26
+ }
27
+ function errResult(message) {
28
+ return {
29
+ content: [{
30
+ type: "text",
31
+ text: JSON.stringify({ error: message })
32
+ }],
33
+ details: { error: message }
34
+ };
35
+ }
36
+ function defineTool(def) {
37
+ return {
38
+ name: def.name,
39
+ description: def.description,
40
+ label: def.name,
41
+ parameters: def.parameters,
42
+ execute: async (_toolCallId, params) => {
43
+ try {
44
+ return ok(await def.handler(params));
45
+ } catch (e) {
46
+ return errResult(e instanceof Error ? e.message : "Unknown error");
47
+ }
48
+ }
49
+ };
50
+ }
51
+ function sanitizeEmail(email) {
52
+ return email.replace(/@/g, "-").replace(/\./g, "-");
53
+ }
54
+ function resolveConfigDir(email, isDefault) {
55
+ const dirName = isDefault ? "gws" : `gws-${sanitizeEmail(email)}`;
56
+ return join(homedir(), ".config", dirName);
57
+ }
58
+ let client = null;
59
+ let cachedAccounts = [];
60
+ function getClient() {
61
+ if (!client) throw new Error("Google plugin not initialized — no API client");
62
+ return client;
63
+ }
64
+ async function refreshAccountCache() {
65
+ const creds = await getClient().getGoogleCredentials();
66
+ cachedAccounts = (creds.accounts ?? [{
67
+ email: creds.email,
68
+ isDefault: true,
69
+ displayName: void 0
70
+ }]).map((a, i) => ({
71
+ email: a.email,
72
+ isDefault: i === 0 ? true : a.isDefault,
73
+ displayName: a.displayName,
74
+ configDir: resolveConfigDir(a.email, i === 0 ? true : a.isDefault)
75
+ }));
76
+ return cachedAccounts;
77
+ }
78
+ function findAccount(email) {
79
+ if (!email) {
80
+ const def = cachedAccounts.find((a) => a.isDefault);
81
+ if (!def) {
82
+ if (cachedAccounts.length === 0) throw new Error("No Google accounts connected");
83
+ return cachedAccounts[0];
84
+ }
85
+ return def;
86
+ }
87
+ const account = cachedAccounts.find((a) => a.email === email);
88
+ if (!account) throw new Error(`Google account "${email}" not found. Available: ${cachedAccounts.map((a) => a.email).join(", ")}`);
89
+ return account;
90
+ }
91
+ function runGwsCommand(args, configDir) {
92
+ return new Promise((resolve) => {
93
+ execFile("gws", args, {
94
+ env: {
95
+ ...process.env,
96
+ GOOGLE_WORKSPACE_CLI_CONFIG_DIR: configDir
97
+ },
98
+ timeout: 6e4,
99
+ maxBuffer: 10 * 1024 * 1024
100
+ }, (error, stdout, stderr) => {
101
+ resolve({
102
+ stdout,
103
+ stderr,
104
+ exitCode: error?.code ?? (error ? 1 : 0)
105
+ });
106
+ });
107
+ });
108
+ }
109
+ const googleTools = [
110
+ defineTool({
111
+ name: "google_list_accounts",
112
+ description: "List all connected Google Workspace accounts. Shows email, display name, whether it's the default account, and the gws CLI config directory path. Use this to resolve which account to target (e.g., 'Kevin\\'s emails' → kevin@alfe.ai).",
113
+ parameters: Type.Object({}),
114
+ handler: async () => {
115
+ const accounts = await refreshAccountCache();
116
+ return {
117
+ accounts: accounts.map((a) => ({
118
+ email: a.email,
119
+ displayName: a.displayName,
120
+ isDefault: a.isDefault,
121
+ configDir: a.configDir
122
+ })),
123
+ count: accounts.length
124
+ };
125
+ }
126
+ }),
127
+ defineTool({
128
+ name: "google_run_command",
129
+ description: "Run a gws (Google Workspace CLI) command targeting a specific account. Automatically sets GOOGLE_WORKSPACE_CLI_CONFIG_DIR for the target account. If no email is specified, uses the default account. Example: google_run_command({ command: 'gmail list', email: 'kevin@alfe.ai' })",
130
+ parameters: Type.Object({
131
+ command: Type.String({ description: "The gws CLI command and arguments (e.g., 'gmail list', 'calendar agenda', 'drive list')" }),
132
+ email: Type.Optional(Type.String({ description: "Email of the Google account to use. Omit to use the default account." }))
133
+ }),
134
+ handler: async (params) => {
135
+ const { command, email } = params;
136
+ const account = findAccount(email);
137
+ const args = command.split(/\s+/).filter(Boolean);
138
+ if (args.length === 0) throw new Error("Command cannot be empty");
139
+ const result = await runGwsCommand(args, account.configDir);
140
+ return {
141
+ account: account.email,
142
+ command: `gws ${command}`,
143
+ ...result
144
+ };
145
+ }
146
+ }),
147
+ defineTool({
148
+ name: "google_set_default_account",
149
+ description: "Set a specific Google account as the default. The default account is used when no email is specified in google_run_command.",
150
+ parameters: Type.Object({ email: Type.String({ description: "Email of the Google account to set as default" }) }),
151
+ handler: async (params) => {
152
+ const { email } = params;
153
+ const result = await getClient().setDefaultGoogleAccount(email);
154
+ await refreshAccountCache();
155
+ return {
156
+ message: `${email} is now the default Google account`,
157
+ accounts: result.accounts
158
+ };
159
+ }
160
+ }),
161
+ defineTool({
162
+ name: "google_disconnect_account",
163
+ description: "Disconnect a specific Google account from this agent. Revokes the OAuth token and removes the account. If this was the default account, the next account is promoted.",
164
+ parameters: Type.Object({ email: Type.String({ description: "Email of the Google account to disconnect" }) }),
165
+ handler: async (params) => {
166
+ const { email } = params;
167
+ const result = await getClient().disconnectGoogleAccount(email);
168
+ await refreshAccountCache();
169
+ return {
170
+ message: `${email} has been disconnected`,
171
+ remainingAccounts: result.accounts
172
+ };
173
+ }
174
+ })
175
+ ];
176
+ const plugin = {
177
+ id: "@alfe.ai/openclaw-google",
178
+ name: "Alfe Google Workspace Plugin",
179
+ description: "Multi-account Google Workspace management — list accounts, run gws commands, manage defaults",
180
+ version: "0.0.1",
181
+ activate(api) {
182
+ const log = api.logger;
183
+ for (const tool of googleTools) api.registerTool(tool);
184
+ log.info(`Registered ${googleTools.length.toString()} Google tools: ${googleTools.map((t) => t.name).join(", ")}`);
185
+ if (!globalThis.__googlePluginActivated) {
186
+ globalThis.__googlePluginActivated = true;
187
+ log.info("Alfe Google Workspace plugin activating...");
188
+ try {
189
+ const config = resolveConfig();
190
+ client = new AgentApiClient({
191
+ apiKey: config.apiKey,
192
+ apiUrl: config.apiUrl
193
+ });
194
+ refreshAccountCache().then((accounts) => {
195
+ log.info(`Cached ${accounts.length.toString()} Google account(s): ${accounts.map((a) => a.email).join(", ")}`);
196
+ }).catch((err) => {
197
+ log.warn(`Failed to pre-cache Google accounts: ${err instanceof Error ? err.message : "unknown"}`);
198
+ });
199
+ } catch (err) {
200
+ log.error(`Failed to resolve config: ${err instanceof Error ? err.message : "unknown"}`);
201
+ log.warn("Google tools will fail — no API config available");
202
+ }
203
+ log.info("Alfe Google Workspace plugin activated");
204
+ }
205
+ },
206
+ deactivate(api) {
207
+ globalThis.__googlePluginActivated = false;
208
+ client = null;
209
+ cachedAccounts = [];
210
+ api.logger.info("Alfe Google Workspace plugin deactivated");
211
+ }
212
+ };
213
+ //#endregion
214
+ export { plugin as default };
215
+
216
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * @alfe/openclaw-google — OpenClaw native plugin\n *\n * Registers Google Workspace account management tools with OpenClaw.\n * Provides multi-account awareness so the agent can:\n * - List connected Google accounts\n * - Run gws CLI commands targeting a specific account\n * - Set the default account\n * - Disconnect an account\n */\n\nimport { Type, type TSchema } from \"@sinclair/typebox\";\nimport { resolveConfig } from \"@alfe.ai/config\";\nimport { AgentApiClient } from \"@alfe.ai/agent-api-client\";\nimport { execFile } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\ninterface Logger {\n info(msg: string, ...args: unknown[]): void;\n warn(msg: string, ...args: unknown[]): void;\n error(msg: string, ...args: unknown[]): void;\n debug(msg: string, ...args: unknown[]): void;\n}\n\ninterface OpenClawPluginApi {\n logger: Logger;\n registerTool(tool: ToolDef): void;\n registerGatewayMethod(name: string, handler: (...args: unknown[]) => Promise<unknown>): void;\n on(event: string, handler: (...args: unknown[]) => void | Promise<void>, options?: { priority?: number }): void;\n}\n\n// ── Tool types ───────────────────────────────────────────────\n\ninterface ToolDef {\n name: string;\n description: string;\n label: string;\n parameters: TSchema;\n execute: (toolCallId: string, params: Record<string, unknown>) => Promise<{\n content: { type: \"text\"; text: string }[];\n details: unknown;\n }>;\n}\n\nfunction ok(data: unknown) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data) }], details: data };\n}\n\nfunction errResult(message: string) {\n return { content: [{ type: \"text\" as const, text: JSON.stringify({ error: message }) }], details: { error: message } };\n}\n\nfunction defineTool(def: {\n name: string;\n description: string;\n parameters: TSchema;\n handler: (params: Record<string, unknown>) => Promise<unknown>;\n}): ToolDef {\n return {\n name: def.name,\n description: def.description,\n label: def.name,\n parameters: def.parameters,\n execute: async (_toolCallId: string, params: Record<string, unknown>) => {\n try {\n const result = await def.handler(params);\n return ok(result);\n } catch (e: unknown) {\n return errResult(e instanceof Error ? e.message : \"Unknown error\");\n }\n },\n };\n}\n\n// ── Account config dir resolution ───────────────────────────\n\ninterface GoogleAccountInfo {\n email: string;\n isDefault: boolean;\n displayName?: string;\n configDir: string;\n}\n\nfunction sanitizeEmail(email: string): string {\n return email.replace(/@/g, \"-\").replace(/\\./g, \"-\");\n}\n\nfunction resolveConfigDir(email: string, isDefault: boolean): string {\n const dirName = isDefault ? \"gws\" : `gws-${sanitizeEmail(email)}`;\n return join(homedir(), \".config\", dirName);\n}\n\n// ── State ───────────────────────────────────────────────────\n\nlet client: AgentApiClient | null = null;\nlet cachedAccounts: GoogleAccountInfo[] = [];\n\nfunction getClient(): AgentApiClient {\n if (!client) throw new Error(\"Google plugin not initialized — no API client\");\n return client;\n}\n\nasync function refreshAccountCache(): Promise<GoogleAccountInfo[]> {\n const creds = await getClient().getGoogleCredentials();\n const accounts = creds.accounts ?? [{\n email: creds.email,\n isDefault: true,\n displayName: undefined,\n }];\n\n cachedAccounts = accounts.map((a, i) => ({\n email: a.email,\n isDefault: i === 0 ? true : a.isDefault,\n displayName: a.displayName,\n configDir: resolveConfigDir(a.email, i === 0 ? true : a.isDefault),\n }));\n\n return cachedAccounts;\n}\n\nfunction findAccount(email?: string): GoogleAccountInfo {\n if (!email) {\n const def = cachedAccounts.find((a) => a.isDefault);\n if (!def) {\n if (cachedAccounts.length === 0) throw new Error(\"No Google accounts connected\");\n return cachedAccounts[0];\n }\n return def;\n }\n\n const account = cachedAccounts.find((a) => a.email === email);\n if (!account) {\n throw new Error(\n `Google account \"${email}\" not found. Available: ${cachedAccounts.map((a) => a.email).join(\", \")}`,\n );\n }\n return account;\n}\n\n// ── gws command execution ───────────────────────────────────\n\nfunction runGwsCommand(args: string[], configDir: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n return new Promise((resolve) => {\n const env = { ...process.env, GOOGLE_WORKSPACE_CLI_CONFIG_DIR: configDir };\n execFile(\"gws\", args, { env, timeout: 60_000, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {\n resolve({\n stdout,\n stderr,\n exitCode: error?.code ?? (error ? 1 : 0),\n });\n });\n });\n}\n\n// ── Tool definitions ─────────────────────────────────────────\n\nconst googleTools: ToolDef[] = [\n defineTool({\n name: \"google_list_accounts\",\n description:\n \"List all connected Google Workspace accounts. Shows email, display name, \" +\n \"whether it's the default account, and the gws CLI config directory path. \" +\n \"Use this to resolve which account to target (e.g., 'Kevin\\\\'s emails' → kevin@alfe.ai).\",\n parameters: Type.Object({}),\n handler: async () => {\n const accounts = await refreshAccountCache();\n return {\n accounts: accounts.map((a) => ({\n email: a.email,\n displayName: a.displayName,\n isDefault: a.isDefault,\n configDir: a.configDir,\n })),\n count: accounts.length,\n };\n },\n }),\n\n defineTool({\n name: \"google_run_command\",\n description:\n \"Run a gws (Google Workspace CLI) command targeting a specific account. \" +\n \"Automatically sets GOOGLE_WORKSPACE_CLI_CONFIG_DIR for the target account. \" +\n \"If no email is specified, uses the default account. \" +\n \"Example: google_run_command({ command: 'gmail list', email: 'kevin@alfe.ai' })\",\n parameters: Type.Object({\n command: Type.String({\n description: \"The gws CLI command and arguments (e.g., 'gmail list', 'calendar agenda', 'drive list')\",\n }),\n email: Type.Optional(\n Type.String({\n description: \"Email of the Google account to use. Omit to use the default account.\",\n }),\n ),\n }),\n handler: async (params) => {\n const { command, email } = params as { command: string; email?: string };\n const account = findAccount(email);\n\n const args = command.split(/\\s+/).filter(Boolean);\n if (args.length === 0) throw new Error(\"Command cannot be empty\");\n\n const result = await runGwsCommand(args, account.configDir);\n\n return {\n account: account.email,\n command: `gws ${command}`,\n ...result,\n };\n },\n }),\n\n defineTool({\n name: \"google_set_default_account\",\n description:\n \"Set a specific Google account as the default. The default account is \" +\n \"used when no email is specified in google_run_command.\",\n parameters: Type.Object({\n email: Type.String({ description: \"Email of the Google account to set as default\" }),\n }),\n handler: async (params) => {\n const { email } = params as { email: string };\n const result = await getClient().setDefaultGoogleAccount(email);\n await refreshAccountCache();\n return {\n message: `${email} is now the default Google account`,\n accounts: result.accounts,\n };\n },\n }),\n\n defineTool({\n name: \"google_disconnect_account\",\n description:\n \"Disconnect a specific Google account from this agent. \" +\n \"Revokes the OAuth token and removes the account. \" +\n \"If this was the default account, the next account is promoted.\",\n parameters: Type.Object({\n email: Type.String({ description: \"Email of the Google account to disconnect\" }),\n }),\n handler: async (params) => {\n const { email } = params as { email: string };\n const result = await getClient().disconnectGoogleAccount(email);\n await refreshAccountCache();\n return {\n message: `${email} has been disconnected`,\n remainingAccounts: result.accounts,\n };\n },\n }),\n];\n\n// ── Plugin definition ────────────────────────────────────────\n\nconst plugin = {\n id: \"@alfe.ai/openclaw-google\",\n name: \"Alfe Google Workspace Plugin\",\n description: \"Multi-account Google Workspace management — list accounts, run gws commands, manage defaults\",\n version: \"0.0.1\",\n\n activate(api: OpenClawPluginApi) {\n const log = api.logger;\n\n for (const tool of googleTools) {\n api.registerTool(tool);\n }\n log.info(`Registered ${googleTools.length.toString()} Google tools: ${googleTools.map((t) => t.name).join(\", \")}`);\n\n if (!(globalThis as Record<string, unknown>).__googlePluginActivated) {\n (globalThis as Record<string, unknown>).__googlePluginActivated = true;\n log.info(\"Alfe Google Workspace plugin activating...\");\n\n try {\n const config = resolveConfig();\n client = new AgentApiClient({ apiKey: config.apiKey, apiUrl: config.apiUrl });\n\n refreshAccountCache()\n .then((accounts) => {\n log.info(`Cached ${accounts.length.toString()} Google account(s): ${accounts.map((a) => a.email).join(\", \")}`);\n })\n .catch((err: unknown) => {\n log.warn(`Failed to pre-cache Google accounts: ${err instanceof Error ? err.message : \"unknown\"}`);\n });\n } catch (err: unknown) {\n log.error(`Failed to resolve config: ${err instanceof Error ? err.message : \"unknown\"}`);\n log.warn(\"Google tools will fail — no API config available\");\n }\n\n log.info(\"Alfe Google Workspace plugin activated\");\n }\n },\n\n deactivate(api: OpenClawPluginApi) {\n (globalThis as Record<string, unknown>).__googlePluginActivated = false;\n client = null;\n cachedAccounts = [];\n api.logger.info(\"Alfe Google Workspace plugin deactivated\");\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "@alfe.ai/openclaw-google",
3
+ "name": "Google Workspace",
4
+ "version": "0.0.1",
5
+ "description": "Multi-account Google Workspace management — list accounts, run gws commands, manage defaults",
6
+ "entry": "./dist/plugin.js",
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {}
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@alfe.ai/openclaw-google",
3
+ "version": "0.0.1",
4
+ "description": "OpenClaw Google Workspace plugin — multi-account management and gws CLI integration",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.cjs",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./plugin": {
15
+ "types": "./dist/plugin.d.ts",
16
+ "require": "./dist/plugin.cjs",
17
+ "import": "./dist/plugin.js"
18
+ }
19
+ },
20
+ "openclaw": {
21
+ "extensions": [
22
+ "./dist/plugin.js"
23
+ ]
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "openclaw.plugin.json"
28
+ ],
29
+ "dependencies": {
30
+ "@sinclair/typebox": "^0.34.48",
31
+ "@alfe.ai/agent-api-client": "0.0.7",
32
+ "@alfe.ai/config": "0.0.7"
33
+ },
34
+ "scripts": {
35
+ "build": "tsdown",
36
+ "dev": "tsdown --watch",
37
+ "test": "vitest run --passWithNoTests",
38
+ "lint": "eslint ."
39
+ }
40
+ }