@customclaw/composio 0.0.6 → 0.0.8

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/config.d.ts CHANGED
@@ -9,6 +9,15 @@ export declare const ComposioConfigSchema: z.ZodObject<{
9
9
  defaultUserId: z.ZodOptional<z.ZodString>;
10
10
  allowedToolkits: z.ZodOptional<z.ZodArray<z.ZodString>>;
11
11
  blockedToolkits: z.ZodOptional<z.ZodArray<z.ZodString>>;
12
+ readOnlyMode: z.ZodDefault<z.ZodBoolean>;
13
+ sessionTags: z.ZodOptional<z.ZodArray<z.ZodEnum<{
14
+ readOnlyHint: "readOnlyHint";
15
+ destructiveHint: "destructiveHint";
16
+ idempotentHint: "idempotentHint";
17
+ openWorldHint: "openWorldHint";
18
+ }>>>;
19
+ allowedToolSlugs: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
+ blockedToolSlugs: z.ZodOptional<z.ZodArray<z.ZodString>>;
12
21
  }, z.core.$strip>;
13
22
  /**
14
23
  * Parse and validate plugin config with environment fallbacks
@@ -41,6 +50,26 @@ export declare const composioConfigUiHints: {
41
50
  help: string;
42
51
  advanced: boolean;
43
52
  };
53
+ readOnlyMode: {
54
+ label: string;
55
+ help: string;
56
+ advanced: boolean;
57
+ };
58
+ sessionTags: {
59
+ label: string;
60
+ help: string;
61
+ advanced: boolean;
62
+ };
63
+ allowedToolSlugs: {
64
+ label: string;
65
+ help: string;
66
+ advanced: boolean;
67
+ };
68
+ blockedToolSlugs: {
69
+ label: string;
70
+ help: string;
71
+ advanced: boolean;
72
+ };
44
73
  };
45
74
  /**
46
75
  * Plugin config schema object for openclaw
@@ -71,5 +100,25 @@ export declare const composioPluginConfigSchema: {
71
100
  help: string;
72
101
  advanced: boolean;
73
102
  };
103
+ readOnlyMode: {
104
+ label: string;
105
+ help: string;
106
+ advanced: boolean;
107
+ };
108
+ sessionTags: {
109
+ label: string;
110
+ help: string;
111
+ advanced: boolean;
112
+ };
113
+ allowedToolSlugs: {
114
+ label: string;
115
+ help: string;
116
+ advanced: boolean;
117
+ };
118
+ blockedToolSlugs: {
119
+ label: string;
120
+ help: string;
121
+ advanced: boolean;
122
+ };
74
123
  };
75
124
  };
package/dist/config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { LEGACY_ENTRY_FLAT_CONFIG_KEYS, LEGACY_SHAPE_ERROR, SESSION_TAGS, isRecord, normalizeSessionTags, normalizeToolkitList, normalizeToolSlugList, } from "./utils.js";
2
3
  /**
3
4
  * Zod schema for Composio plugin configuration
4
5
  */
@@ -8,23 +9,39 @@ export const ComposioConfigSchema = z.object({
8
9
  defaultUserId: z.string().optional(),
9
10
  allowedToolkits: z.array(z.string()).optional(),
10
11
  blockedToolkits: z.array(z.string()).optional(),
12
+ readOnlyMode: z.boolean().default(false),
13
+ sessionTags: z.array(z.enum(SESSION_TAGS)).optional(),
14
+ allowedToolSlugs: z.array(z.string()).optional(),
15
+ blockedToolSlugs: z.array(z.string()).optional(),
11
16
  });
12
17
  /**
13
18
  * Parse and validate plugin config with environment fallbacks
14
19
  */
15
20
  export function parseComposioConfig(value) {
16
- const raw = value && typeof value === "object" && !Array.isArray(value)
17
- ? value
18
- : {};
19
- // Allow API key from config.apiKey, top-level apiKey, or environment
20
- const configObj = raw.config;
21
- const apiKey = (typeof configObj?.apiKey === "string" && configObj.apiKey.trim()) ||
22
- (typeof raw.apiKey === "string" && raw.apiKey.trim()) ||
21
+ const raw = isRecord(value) ? value : {};
22
+ const configObj = isRecord(raw.config) ? raw.config : undefined;
23
+ if (configObj) {
24
+ const hasLegacyFlatKeys = LEGACY_ENTRY_FLAT_CONFIG_KEYS.some((key) => key in raw);
25
+ if (hasLegacyFlatKeys) {
26
+ throw new Error(LEGACY_SHAPE_ERROR);
27
+ }
28
+ }
29
+ const source = configObj ?? raw;
30
+ const enabled = typeof raw.enabled === "boolean" ? raw.enabled : true;
31
+ const readOnlyMode = typeof source.readOnlyMode === "boolean" ? source.readOnlyMode : false;
32
+ const apiKey = (typeof source.apiKey === "string" && source.apiKey.trim()) ||
23
33
  process.env.COMPOSIO_API_KEY ||
24
34
  "";
25
35
  return ComposioConfigSchema.parse({
26
- ...raw,
36
+ enabled,
27
37
  apiKey,
38
+ defaultUserId: typeof source.defaultUserId === "string" ? source.defaultUserId : undefined,
39
+ allowedToolkits: normalizeToolkitList(Array.isArray(source.allowedToolkits) ? source.allowedToolkits : undefined),
40
+ blockedToolkits: normalizeToolkitList(Array.isArray(source.blockedToolkits) ? source.blockedToolkits : undefined),
41
+ readOnlyMode,
42
+ sessionTags: normalizeSessionTags(Array.isArray(source.sessionTags) ? source.sessionTags : undefined),
43
+ allowedToolSlugs: normalizeToolSlugList(Array.isArray(source.allowedToolSlugs) ? source.allowedToolSlugs : undefined),
44
+ blockedToolSlugs: normalizeToolSlugList(Array.isArray(source.blockedToolSlugs) ? source.blockedToolSlugs : undefined),
28
45
  });
29
46
  }
30
47
  /**
@@ -54,6 +71,26 @@ export const composioConfigUiHints = {
54
71
  help: "Block specific toolkits from being used",
55
72
  advanced: true,
56
73
  },
74
+ readOnlyMode: {
75
+ label: "Read-Only Mode",
76
+ help: "Block likely-destructive tool actions by token matching; allow specific slugs with allowedToolSlugs if needed",
77
+ advanced: true,
78
+ },
79
+ sessionTags: {
80
+ label: "Session Tags",
81
+ help: "Composio Tool Router behavior tags (e.g., readOnlyHint, destructiveHint)",
82
+ advanced: true,
83
+ },
84
+ allowedToolSlugs: {
85
+ label: "Allowed Tool Slugs",
86
+ help: "Optional explicit allowlist for tool slugs (UPPERCASE)",
87
+ advanced: true,
88
+ },
89
+ blockedToolSlugs: {
90
+ label: "Blocked Tool Slugs",
91
+ help: "Explicit denylist for tool slugs (UPPERCASE)",
92
+ advanced: true,
93
+ },
57
94
  };
58
95
  /**
59
96
  * Plugin config schema object for openclaw
package/dist/index.d.ts CHANGED
@@ -49,6 +49,26 @@ declare const composioPlugin: {
49
49
  help: string;
50
50
  advanced: boolean;
51
51
  };
52
+ readOnlyMode: {
53
+ label: string;
54
+ help: string;
55
+ advanced: boolean;
56
+ };
57
+ sessionTags: {
58
+ label: string;
59
+ help: string;
60
+ advanced: boolean;
61
+ };
62
+ allowedToolSlugs: {
63
+ label: string;
64
+ help: string;
65
+ advanced: boolean;
66
+ };
67
+ blockedToolSlugs: {
68
+ label: string;
69
+ help: string;
70
+ advanced: boolean;
71
+ };
52
72
  };
53
73
  };
54
74
  register(api: any): void;
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { createComposioSearchTool } from "./tools/search.js";
4
4
  import { createComposioExecuteTool } from "./tools/execute.js";
5
5
  import { createComposioConnectionsTool } from "./tools/connections.js";
6
6
  import { registerComposioCli } from "./cli.js";
7
+ import { LEGACY_SHAPE_ERROR, hasLegacyFlatEntryConfig } from "./utils.js";
7
8
  /**
8
9
  * Composio Tool Router Plugin for OpenClaw
9
10
  *
@@ -31,7 +32,27 @@ const composioPlugin = {
31
32
  "Search, authenticate, and execute tools for Gmail, Slack, GitHub, Notion, and more.",
32
33
  configSchema: composioPluginConfigSchema,
33
34
  register(api) {
35
+ if (hasLegacyFlatEntryConfig(api?.config)) {
36
+ throw new Error(LEGACY_SHAPE_ERROR);
37
+ }
34
38
  const config = parseComposioConfig(api.pluginConfig);
39
+ let client = null;
40
+ const ensureClient = () => {
41
+ if (!config.apiKey) {
42
+ throw new Error("Composio API key required. Run 'openclaw composio setup' or set COMPOSIO_API_KEY.");
43
+ }
44
+ if (!client) {
45
+ client = createComposioClient(config);
46
+ }
47
+ return client;
48
+ };
49
+ // Register CLI commands even without API key so setup/status tooling remains available.
50
+ api.registerCli(({ program }) => registerComposioCli({
51
+ program,
52
+ getClient: config.apiKey ? ensureClient : undefined,
53
+ config,
54
+ logger: api.logger,
55
+ }), { commands: ["composio"] });
35
56
  if (!config.enabled) {
36
57
  api.logger.debug("[composio] Plugin disabled in config");
37
58
  return;
@@ -40,13 +61,6 @@ const composioPlugin = {
40
61
  api.logger.warn("[composio] No API key configured. Set COMPOSIO_API_KEY env var or plugins.composio.apiKey in config.");
41
62
  return;
42
63
  }
43
- let client = null;
44
- const ensureClient = () => {
45
- if (!client) {
46
- client = createComposioClient(config);
47
- }
48
- return client;
49
- };
50
64
  // Register tools (lazily create client on first use)
51
65
  api.registerTool({
52
66
  ...createComposioSearchTool(ensureClient(), config),
@@ -66,13 +80,6 @@ const composioPlugin = {
66
80
  return createComposioConnectionsTool(ensureClient(), config).execute(toolCallId, params);
67
81
  },
68
82
  });
69
- // Register CLI commands
70
- api.registerCli(({ program }) => registerComposioCli({
71
- program,
72
- client: ensureClient(),
73
- config,
74
- logger: api.logger,
75
- }), { commands: ["composio"] });
76
83
  api.logger.info("[composio] Plugin registered with 3 tools and CLI commands");
77
84
  },
78
85
  };
@@ -4,10 +4,11 @@ import type { ComposioConfig } from "../types.js";
4
4
  * Tool parameters for composio_manage_connections
5
5
  */
6
6
  export declare const ComposioManageConnectionsToolSchema: import("@sinclair/typebox").TObject<{
7
- action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">]>;
7
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">, import("@sinclair/typebox").TLiteral<"accounts">]>;
8
8
  toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
9
  toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
10
10
  user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
11
+ statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
11
12
  }>;
12
13
  /**
13
14
  * Create the composio_manage_connections tool
@@ -17,10 +18,11 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
17
18
  label: string;
18
19
  description: string;
19
20
  parameters: import("@sinclair/typebox").TObject<{
20
- action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">]>;
21
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"list">, import("@sinclair/typebox").TLiteral<"accounts">]>;
21
22
  toolkit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
22
23
  toolkits: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
23
24
  user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
25
+ statuses: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
24
26
  }>;
25
27
  execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
26
28
  content: {
@@ -32,6 +34,22 @@ export declare function createComposioConnectionsTool(client: ComposioClient, _c
32
34
  count: number;
33
35
  toolkits: string[];
34
36
  };
37
+ } | {
38
+ content: {
39
+ type: string;
40
+ text: string;
41
+ }[];
42
+ details: {
43
+ action: string;
44
+ count: number;
45
+ accounts: {
46
+ id: string;
47
+ toolkit: string;
48
+ user_id: string | undefined;
49
+ status: string | undefined;
50
+ auth_config_id: string | undefined;
51
+ }[];
52
+ };
35
53
  } | {
36
54
  content: {
37
55
  type: string;
@@ -1,16 +1,10 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- const CONNECTION_PROBES = {
3
- affinity: {
4
- toolSlug: "AFFINITY_GET_METADATA_ON_ALL_LISTS",
5
- args: { limit: 1 },
6
- },
7
- };
8
2
  /**
9
3
  * Tool parameters for composio_manage_connections
10
4
  */
11
5
  export const ComposioManageConnectionsToolSchema = Type.Object({
12
- action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list")], {
13
- description: "Action to perform: 'status' to check connections, 'create' to initiate auth, 'list' to list toolkits",
6
+ action: Type.Union([Type.Literal("status"), Type.Literal("create"), Type.Literal("list"), Type.Literal("accounts")], {
7
+ description: "Action to perform: 'status' to check connections, 'create' to initiate auth, 'list' to list toolkits, 'accounts' to inspect connected accounts",
14
8
  }),
15
9
  toolkit: Type.Optional(Type.String({
16
10
  description: "Toolkit name for 'status' or 'create' actions (e.g., 'github', 'gmail')",
@@ -21,6 +15,9 @@ export const ComposioManageConnectionsToolSchema = Type.Object({
21
15
  user_id: Type.Optional(Type.String({
22
16
  description: "User ID for session scoping (uses default if not provided)",
23
17
  })),
18
+ statuses: Type.Optional(Type.Array(Type.String(), {
19
+ description: "Optional connection statuses filter for 'accounts' (e.g., ['ACTIVE'])",
20
+ })),
24
21
  });
25
22
  /**
26
23
  * Create the composio_manage_connections tool
@@ -30,7 +27,8 @@ export function createComposioConnectionsTool(client, _config) {
30
27
  name: "composio_manage_connections",
31
28
  label: "Composio Manage Connections",
32
29
  description: "Manage Composio toolkit connections. Use action='status' to check if a toolkit is connected, " +
33
- "action='create' to generate an auth URL when disconnected, or action='list' to see available toolkits. " +
30
+ "action='create' to generate an auth URL when disconnected, action='list' to see available toolkits, " +
31
+ "or action='accounts' to inspect connected accounts across user IDs. " +
34
32
  "Check connection status before executing tools with composio_execute_tool.",
35
33
  parameters: ComposioManageConnectionsToolSchema,
36
34
  async execute(_toolCallId, params) {
@@ -50,6 +48,38 @@ export function createComposioConnectionsTool(client, _config) {
50
48
  details: response,
51
49
  };
52
50
  }
51
+ case "accounts": {
52
+ let toolkits;
53
+ if (typeof params.toolkit === "string" && params.toolkit.trim()) {
54
+ toolkits = [params.toolkit.trim()];
55
+ }
56
+ else if (Array.isArray(params.toolkits)) {
57
+ toolkits = params.toolkits.filter((t) => typeof t === "string" && t.trim() !== "");
58
+ }
59
+ const statuses = Array.isArray(params.statuses)
60
+ ? params.statuses.filter((s) => typeof s === "string" && s.trim() !== "")
61
+ : ["ACTIVE"];
62
+ const accounts = await client.listConnectedAccounts({
63
+ toolkits,
64
+ userIds: userId ? [userId] : undefined,
65
+ statuses,
66
+ });
67
+ const response = {
68
+ action: "accounts",
69
+ count: accounts.length,
70
+ accounts: accounts.map((a) => ({
71
+ id: a.id,
72
+ toolkit: a.toolkit,
73
+ user_id: a.userId,
74
+ status: a.status,
75
+ auth_config_id: a.authConfigId,
76
+ })),
77
+ };
78
+ return {
79
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
80
+ details: response,
81
+ };
82
+ }
53
83
  case "create": {
54
84
  const toolkit = String(params.toolkit || "").trim();
55
85
  if (!toolkit) {
@@ -89,25 +119,6 @@ export function createComposioConnectionsTool(client, _config) {
89
119
  toolkitsToCheck = params.toolkits.filter((t) => typeof t === "string" && t.trim() !== "");
90
120
  }
91
121
  const statuses = await client.getConnectionStatus(toolkitsToCheck, userId);
92
- // Fallback probe for API-key style integrations where
93
- // connection.isActive can be false despite successful tool execution
94
- if (toolkitsToCheck && toolkitsToCheck.length > 0) {
95
- for (const status of statuses) {
96
- if (status.connected)
97
- continue;
98
- const probe = CONNECTION_PROBES[String(status.toolkit || "").toLowerCase()];
99
- if (!probe)
100
- continue;
101
- try {
102
- const probeResult = await client.executeTool(probe.toolSlug, probe.args, userId);
103
- if (probeResult?.success)
104
- status.connected = true;
105
- }
106
- catch {
107
- // keep false if probe fails
108
- }
109
- }
110
- }
111
122
  const response = {
112
123
  action: "status",
113
124
  count: statuses.length,
@@ -7,6 +7,7 @@ export declare const ComposioExecuteToolSchema: import("@sinclair/typebox").TObj
7
7
  tool_slug: import("@sinclair/typebox").TString;
8
8
  arguments: import("@sinclair/typebox").TUnknown;
9
9
  user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
10
+ connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
10
11
  }>;
11
12
  /**
12
13
  * Create the composio_execute_tool tool
@@ -19,6 +20,7 @@ export declare function createComposioExecuteTool(client: ComposioClient, _confi
19
20
  tool_slug: import("@sinclair/typebox").TString;
20
21
  arguments: import("@sinclair/typebox").TUnknown;
21
22
  user_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
23
+ connected_account_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
22
24
  }>;
23
25
  execute(_toolCallId: string, params: Record<string, unknown>): Promise<{
24
26
  content: {
@@ -12,6 +12,9 @@ export const ComposioExecuteToolSchema = Type.Object({
12
12
  user_id: Type.Optional(Type.String({
13
13
  description: "User ID for session scoping (uses default if not provided)",
14
14
  })),
15
+ connected_account_id: Type.Optional(Type.String({
16
+ description: "Optional connected account ID to pin execution to a specific account when multiple are connected",
17
+ })),
15
18
  });
16
19
  /**
17
20
  * Create the composio_execute_tool tool
@@ -43,8 +46,9 @@ export function createComposioExecuteTool(client, _config) {
43
46
  ? rawArgs
44
47
  : {};
45
48
  const userId = typeof params.user_id === "string" ? params.user_id : undefined;
49
+ const connectedAccountId = typeof params.connected_account_id === "string" ? params.connected_account_id : undefined;
46
50
  try {
47
- const result = await client.executeTool(toolSlug, args, userId);
51
+ const result = await client.executeTool(toolSlug, args, userId, connectedAccountId);
48
52
  const response = {
49
53
  tool_slug: toolSlug,
50
54
  success: result.success,
package/dist/types.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * Composio Tool Router types for OpenClaw integration
3
3
  */
4
+ export type ComposioSessionTag = "readOnlyHint" | "destructiveHint" | "idempotentHint" | "openWorldHint";
4
5
  export interface ComposioConfig {
5
6
  enabled: boolean;
6
7
  apiKey?: string;
7
8
  defaultUserId?: string;
8
9
  allowedToolkits?: string[];
9
10
  blockedToolkits?: string[];
11
+ readOnlyMode?: boolean;
12
+ sessionTags?: ComposioSessionTag[];
13
+ allowedToolSlugs?: string[];
14
+ blockedToolSlugs?: string[];
10
15
  }
11
16
  export interface ToolSearchResult {
12
17
  name: string;
@@ -26,3 +31,13 @@ export interface ConnectionStatus {
26
31
  userId?: string;
27
32
  authUrl?: string;
28
33
  }
34
+ export interface ConnectedAccountSummary {
35
+ id: string;
36
+ toolkit: string;
37
+ userId?: string;
38
+ status?: string;
39
+ authConfigId?: string;
40
+ isDisabled?: boolean;
41
+ createdAt?: string;
42
+ updatedAt?: string;
43
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComposioSessionTag } from "./types.js";
2
+ export declare const SESSION_TAGS: readonly ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"];
3
+ export declare const LEGACY_ENTRY_FLAT_CONFIG_KEYS: readonly ["apiKey", "defaultUserId", "allowedToolkits", "blockedToolkits", "readOnlyMode", "sessionTags", "allowedToolSlugs", "blockedToolSlugs"];
4
+ export declare const LEGACY_SHAPE_ERROR = "Legacy Composio config shape detected. Run 'openclaw composio setup'.";
5
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
6
+ export declare function normalizeToolkitSlug(value: unknown): string;
7
+ export declare function normalizeToolSlug(value: unknown): string;
8
+ export declare function normalizeToolkitList(values?: readonly unknown[]): string[] | undefined;
9
+ export declare function normalizeToolSlugList(values?: readonly unknown[]): string[] | undefined;
10
+ export declare function normalizeSessionTags(values?: readonly unknown[]): ComposioSessionTag[] | undefined;
11
+ export declare function hasLegacyFlatEntryConfig(config: unknown): boolean;
12
+ export declare function stripLegacyFlatConfigKeys(entry: Record<string, unknown>): Record<string, unknown>;
package/dist/utils.js ADDED
@@ -0,0 +1,76 @@
1
+ export const SESSION_TAGS = [
2
+ "readOnlyHint",
3
+ "destructiveHint",
4
+ "idempotentHint",
5
+ "openWorldHint",
6
+ ];
7
+ export const LEGACY_ENTRY_FLAT_CONFIG_KEYS = [
8
+ "apiKey",
9
+ "defaultUserId",
10
+ "allowedToolkits",
11
+ "blockedToolkits",
12
+ "readOnlyMode",
13
+ "sessionTags",
14
+ "allowedToolSlugs",
15
+ "blockedToolSlugs",
16
+ ];
17
+ export const LEGACY_SHAPE_ERROR = "Legacy Composio config shape detected. Run 'openclaw composio setup'.";
18
+ const SESSION_TAG_SET = new Set(SESSION_TAGS);
19
+ export function isRecord(value) {
20
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
21
+ }
22
+ export function normalizeToolkitSlug(value) {
23
+ return String(value ?? "").trim().toLowerCase();
24
+ }
25
+ export function normalizeToolSlug(value) {
26
+ return String(value ?? "").trim().toUpperCase();
27
+ }
28
+ export function normalizeToolkitList(values) {
29
+ if (!values || values.length === 0)
30
+ return undefined;
31
+ const normalized = values
32
+ .filter((item) => typeof item === "string")
33
+ .map((item) => normalizeToolkitSlug(item))
34
+ .filter(Boolean);
35
+ if (normalized.length === 0)
36
+ return undefined;
37
+ return Array.from(new Set(normalized));
38
+ }
39
+ export function normalizeToolSlugList(values) {
40
+ if (!values || values.length === 0)
41
+ return undefined;
42
+ const normalized = values
43
+ .filter((item) => typeof item === "string")
44
+ .map((item) => normalizeToolSlug(item))
45
+ .filter(Boolean);
46
+ if (normalized.length === 0)
47
+ return undefined;
48
+ return Array.from(new Set(normalized));
49
+ }
50
+ export function normalizeSessionTags(values) {
51
+ if (!values || values.length === 0)
52
+ return undefined;
53
+ const normalized = values
54
+ .filter((item) => typeof item === "string")
55
+ .map((item) => item.trim())
56
+ .filter((item) => SESSION_TAG_SET.has(item));
57
+ if (normalized.length === 0)
58
+ return undefined;
59
+ return Array.from(new Set(normalized));
60
+ }
61
+ export function hasLegacyFlatEntryConfig(config) {
62
+ const root = isRecord(config) ? config : undefined;
63
+ const plugins = isRecord(root?.plugins) ? root.plugins : undefined;
64
+ const entries = isRecord(plugins?.entries) ? plugins.entries : undefined;
65
+ const composioEntry = isRecord(entries?.composio) ? entries.composio : undefined;
66
+ if (!composioEntry)
67
+ return false;
68
+ return LEGACY_ENTRY_FLAT_CONFIG_KEYS.some((key) => key in composioEntry);
69
+ }
70
+ export function stripLegacyFlatConfigKeys(entry) {
71
+ const sanitized = { ...entry };
72
+ for (const key of LEGACY_ENTRY_FLAT_CONFIG_KEYS) {
73
+ delete sanitized[key];
74
+ }
75
+ return sanitized;
76
+ }
@@ -27,6 +27,25 @@
27
27
  "type": "array",
28
28
  "items": { "type": "string" },
29
29
  "description": "Block specific toolkits"
30
+ },
31
+ "readOnlyMode": {
32
+ "type": "boolean",
33
+ "description": "Block likely destructive tool actions by default"
34
+ },
35
+ "sessionTags": {
36
+ "type": "array",
37
+ "items": { "type": "string", "enum": ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"] },
38
+ "description": "Optional Tool Router behavior tags"
39
+ },
40
+ "allowedToolSlugs": {
41
+ "type": "array",
42
+ "items": { "type": "string" },
43
+ "description": "Optional explicit allowlist for tool slugs (UPPERCASE)"
44
+ },
45
+ "blockedToolSlugs": {
46
+ "type": "array",
47
+ "items": { "type": "string" },
48
+ "description": "Explicit denylist for tool slugs (UPPERCASE)"
30
49
  }
31
50
  }
32
51
  },
@@ -53,6 +72,26 @@
53
72
  "label": "Blocked Toolkits",
54
73
  "help": "Block specific toolkits from being used",
55
74
  "advanced": true
75
+ },
76
+ "readOnlyMode": {
77
+ "label": "Read-Only Mode",
78
+ "help": "Block likely-destructive tool actions by default",
79
+ "advanced": true
80
+ },
81
+ "sessionTags": {
82
+ "label": "Session Tags",
83
+ "help": "Tool Router behavior tags (e.g., readOnlyHint, destructiveHint)",
84
+ "advanced": true
85
+ },
86
+ "allowedToolSlugs": {
87
+ "label": "Allowed Tool Slugs",
88
+ "help": "Optional explicit allowlist for tool slugs (UPPERCASE)",
89
+ "advanced": true
90
+ },
91
+ "blockedToolSlugs": {
92
+ "label": "Blocked Tool Slugs",
93
+ "help": "Explicit denylist for tool slugs (UPPERCASE)",
94
+ "advanced": true
56
95
  }
57
96
  }
58
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@customclaw/composio",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "description": "Composio Tool Router plugin for OpenClaw — access 1000+ third-party integrations",
6
6
  "main": "dist/index.js",
@@ -11,8 +11,9 @@
11
11
  ]
12
12
  },
13
13
  "scripts": {
14
- "build": "tsc",
14
+ "build": "rm -rf dist && tsc",
15
15
  "test": "vitest run",
16
+ "test:live": "vitest run src/live.integration.test.ts",
16
17
  "prepublishOnly": "npm run build"
17
18
  },
18
19
  "files": [
@@ -1 +0,0 @@
1
- export {};