@openhands/extensions 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.github/workflows/pypi-publish.yml +104 -0
  2. package/.github/workflows/tests.yml +6 -0
  3. package/.release-please-manifest.json +1 -1
  4. package/AGENTS.md +1 -1
  5. package/README.md +31 -3
  6. package/integrations/README.md +17 -7
  7. package/integrations/catalog/airtable.json +40 -10
  8. package/integrations/catalog/apify.json +0 -2
  9. package/integrations/catalog/asana.json +38 -0
  10. package/integrations/catalog/atlassian.json +5 -8
  11. package/integrations/catalog/bitbucket.json +39 -0
  12. package/integrations/catalog/box.json +39 -0
  13. package/integrations/catalog/brave-search.json +1 -2
  14. package/integrations/catalog/browser-mcp.json +0 -3
  15. package/integrations/catalog/canva.json +35 -0
  16. package/integrations/catalog/clickhouse.json +1 -2
  17. package/integrations/catalog/clickup.json +36 -0
  18. package/integrations/catalog/cloudflare-bindings.json +5 -8
  19. package/integrations/catalog/cloudflare-browser-rendering.json +5 -8
  20. package/integrations/catalog/cloudflare-builds.json +6 -9
  21. package/integrations/catalog/cloudflare-docs.json +4 -7
  22. package/integrations/catalog/cloudflare-observability.json +5 -8
  23. package/integrations/catalog/confluence.json +38 -0
  24. package/integrations/catalog/datadog.json +34 -0
  25. package/integrations/catalog/deepwiki.json +3 -7
  26. package/integrations/catalog/discord.json +39 -0
  27. package/integrations/catalog/dropbox.json +38 -0
  28. package/integrations/catalog/elevenlabs.json +27 -28
  29. package/integrations/catalog/everything.json +0 -2
  30. package/integrations/catalog/exa.json +0 -2
  31. package/integrations/catalog/fetch.json +0 -2
  32. package/integrations/catalog/figma.json +41 -9
  33. package/integrations/catalog/filesystem.json +0 -3
  34. package/integrations/catalog/firecrawl.json +0 -2
  35. package/integrations/catalog/freshdesk.json +38 -0
  36. package/integrations/catalog/git.json +0 -3
  37. package/integrations/catalog/github.json +43 -10
  38. package/integrations/catalog/gitlab.json +38 -0
  39. package/integrations/catalog/gmail.json +38 -0
  40. package/integrations/catalog/google-calendar.json +38 -0
  41. package/integrations/catalog/google-docs.json +38 -0
  42. package/integrations/catalog/google-drive.json +38 -0
  43. package/integrations/catalog/google-sheets.json +38 -0
  44. package/integrations/catalog/hubspot.json +34 -0
  45. package/integrations/catalog/huggingface.json +1 -2
  46. package/integrations/catalog/intercom.json +39 -0
  47. package/integrations/catalog/jira.json +38 -0
  48. package/integrations/catalog/kagi.json +2 -3
  49. package/integrations/catalog/linear.json +47 -16
  50. package/integrations/catalog/mailchimp.json +38 -0
  51. package/integrations/catalog/memory.json +0 -2
  52. package/integrations/catalog/microsoft-outlook.json +38 -0
  53. package/integrations/catalog/microsoft-teams.json +38 -0
  54. package/integrations/catalog/miro.json +38 -0
  55. package/integrations/catalog/monday.json +38 -0
  56. package/integrations/catalog/mongodb.json +1 -2
  57. package/integrations/catalog/neon.json +0 -2
  58. package/integrations/catalog/netlify.json +38 -0
  59. package/integrations/catalog/notion.json +37 -10
  60. package/integrations/catalog/obsidian.json +1 -3
  61. package/integrations/catalog/okta.json +38 -0
  62. package/integrations/catalog/onedrive.json +38 -0
  63. package/integrations/catalog/ordinal.json +33 -0
  64. package/integrations/catalog/paypal.json +5 -8
  65. package/integrations/catalog/pipedrive.json +39 -0
  66. package/integrations/catalog/plaid.json +31 -0
  67. package/integrations/catalog/playwright.json +0 -2
  68. package/integrations/catalog/posthog.json +38 -0
  69. package/integrations/catalog/quickbooks.json +38 -0
  70. package/integrations/catalog/redis.json +1 -3
  71. package/integrations/catalog/resend.json +1 -2
  72. package/integrations/catalog/salesforce.json +38 -0
  73. package/integrations/catalog/sentry.json +52 -14
  74. package/integrations/catalog/sequential-thinking.json +0 -2
  75. package/integrations/catalog/servicenow.json +39 -0
  76. package/integrations/catalog/sharepoint.json +38 -0
  77. package/integrations/catalog/shopify.json +38 -0
  78. package/integrations/catalog/slack.json +49 -10
  79. package/integrations/catalog/stripe.json +44 -14
  80. package/integrations/catalog/supabase.json +36 -9
  81. package/integrations/catalog/tavily.json +0 -2
  82. package/integrations/catalog/time.json +0 -2
  83. package/integrations/catalog/trello.json +31 -0
  84. package/integrations/catalog/vercel.json +38 -0
  85. package/integrations/catalog/webflow.json +34 -0
  86. package/integrations/catalog/xero.json +38 -0
  87. package/integrations/catalog/zendesk.json +38 -0
  88. package/integrations/catalog/zoom.json +38 -0
  89. package/integrations/catalog-index.js +171 -0
  90. package/integrations/index.d.ts +24 -60
  91. package/integrations/index.js +29 -207
  92. package/marketplaces/openhands-extensions.json +14 -1
  93. package/package.json +4 -7
  94. package/plugins/cobol-modernization/README.md +3 -3
  95. package/plugins/cobol-modernization/skills/{cobol-modernization-overview → cobol-modernization}/SKILL.md +1 -1
  96. package/plugins/magic-test/skills/magic-word/SKILL.md +1 -0
  97. package/plugins/migration-scoring/README.md +1 -1
  98. package/pyproject.toml +17 -3
  99. package/python/openhands_extensions/__init__.py +24 -0
  100. package/python/openhands_extensions/_version.py +24 -0
  101. package/python/openhands_extensions/integrations.py +92 -0
  102. package/release-please-config.json +2 -1
  103. package/scripts/build-integration-catalog.mjs +45 -0
  104. package/skills/agent-canvas-environment/.plugin/plugin.json +19 -0
  105. package/skills/agent-canvas-environment/README.md +5 -0
  106. package/skills/agent-canvas-environment/SKILL.md +141 -0
  107. package/skills/github-pr-reviewer/references/state-schema.md +18 -4
  108. package/skills/github-pr-reviewer/scripts/main.py +66 -12
  109. package/skills/github-repo-monitor/references/state-schema.md +11 -4
  110. package/skills/github-repo-monitor/scripts/main.py +67 -19
  111. package/skills/github-repo-monitor/tests/test_main.py +67 -8
  112. package/skills/index.js +19 -5
  113. package/skills/openhands-api/README.md +5 -1
  114. package/skills/openhands-api/SKILL.md +74 -5
  115. package/skills/openhands-automation/SKILL.md +3 -1
  116. package/skills/openhands-automation/references/custom-automation.md +166 -0
  117. package/skills/openhands-sdk/SKILL.md +8 -2
  118. package/skills/slack-channel-monitor/README.md +3 -3
  119. package/skills/slack-channel-monitor/SKILL.md +3 -2
  120. package/skills/slack-channel-monitor/references/state-schema.md +11 -4
  121. package/skills/slack-channel-monitor/scripts/main.py +111 -41
  122. package/tests/test_catalogs.py +21 -3
  123. package/tests/test_integration_catalog_in_sync.py +204 -0
  124. package/tests/test_slack_channel_monitor.py +142 -0
  125. package/tests/test_version_alignment.py +64 -0
  126. package/integrations/logos.d.ts +0 -5
  127. package/integrations/logos.js +0 -94
  128. package/integrations/oauth-provider-catalog.js +0 -553
  129. package/integrations/oauth-provider-registration-defaults.js +0 -610
  130. /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/SKILL.md +0 -0
  131. /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/references/cics-transformation-examples.md +0 -0
  132. /package/plugins/migration-scoring/skills/{migration-scoring-overview → migration-scoring}/SKILL.md +0 -0
@@ -88,52 +88,9 @@ export interface IntegrationConnectionOption {
88
88
  auth: IntegrationAuthConfig;
89
89
  }
90
90
 
91
- export interface OAuthProviderRegistrationDefaults {
92
- provider?: IntegrationProvider;
93
- authModes?: IntegrationAuthStrategy[];
94
- authStrategy?: IntegrationAuthStrategy;
95
- credentialLabel?: string;
96
- credentialPlaceholder?: string;
97
- credentialHelp?: string;
98
- apiKeyHeaderName?: string;
99
- apiBaseUrl?: string;
100
- serverUrl?: string;
101
- openApiUrl?: string;
102
- authorizationUrl?: string;
103
- tokenUrl?: string;
104
- scopes?: string[];
105
- optionalScopes?: string[];
106
- toolScopes?: string[];
107
- scopeSeparator?: "space" | "comma";
108
- pkce?: boolean;
109
- clientAuthentication?: "basic" | "body" | "none";
110
- registrationUrl?: string;
111
- additionalAuthorizationParams?: Record<string, string>;
112
- additionalTokenParams?: Record<string, string>;
113
- toolName?: string;
114
- toolDescription?: string;
115
- requestMethod?: string;
116
- requestPath?: string;
117
- }
118
-
119
- export interface OAuthProviderCatalogOption {
120
- slug: string;
121
- name: string;
122
- description: string;
123
- categories: string[];
124
- authStrategy: IntegrationAuthStrategy;
125
- availability: "oauth_ready" | "manual_token" | "planned";
126
- managedConnectorSlug?: string;
127
- appUrl?: string;
128
- docsUrl?: string;
129
- notes: string;
130
- popularityRank: number;
131
- registrationDefaults?: OAuthProviderRegistrationDefaults;
132
- }
133
91
 
134
92
  export interface IntegrationCatalogEntry {
135
93
  id: string;
136
- kind: IntegrationProvider;
137
94
  name: string;
138
95
  description: string;
139
96
  categories?: string[];
@@ -142,28 +99,35 @@ export interface IntegrationCatalogEntry {
142
99
  notes?: string;
143
100
  iconBg?: string;
144
101
  iconColor?: string;
102
+ logoUrl?: string;
145
103
  keywords?: string[];
146
104
  popularityRank?: number;
147
- runtimeAvailability?: "all" | "local";
148
- catalogStatus?: "oauth_ready" | "manual_token" | "planned";
149
- managedConnectorSlug?: string;
150
- authStrategy?: IntegrationAuthStrategy;
151
105
  installHint?: string;
152
- defaultConnectionOptionId?: string;
153
106
  connectionOptions: IntegrationConnectionOption[];
154
- registrationDefaults?: OAuthProviderRegistrationDefaults;
155
107
  }
156
108
 
157
- export const INTEGRATION_CATALOG: IntegrationCatalogEntry[];
158
- export function listOAuthProviderCatalog(): OAuthProviderCatalogOption[];
159
- export function getOAuthProviderRegistrationDefaults(
160
- slug: string,
161
- ): OAuthProviderRegistrationDefaults | undefined;
162
-
163
- export const hubspotMcpServerUrl: string;
164
- export const hubspotMcpAuthorizationUrl: string;
165
- export const hubspotMcpTokenUrl: string;
166
- export const hubspotRequiredScopes: readonly string[];
167
- export const hubspotOptionalScopes: readonly string[];
109
+ /**
110
+ * Filter for {@link listIntegrationCatalog}. Each dimension is tri-state:
111
+ * `true` keeps only entries that support that connector type, `false` keeps
112
+ * only entries that do not, and `undefined` leaves that dimension unfiltered.
113
+ */
114
+ export interface IntegrationCatalogFilter {
115
+ /** Filter on whether the entry exposes at least one `mcp` connector. */
116
+ mcp?: boolean;
117
+ /** Filter on whether the entry exposes at least one `oauth2` connector. */
118
+ oauth?: boolean;
119
+ }
168
120
 
121
+ export const INTEGRATION_CATALOG: IntegrationCatalogEntry[];
122
+ /**
123
+ * Return the full integration catalog, optionally filtered by connector type.
124
+ * Reads the generated static import index over `integrations/catalog/<id>.json`.
125
+ * Returns the cached array; callers must treat it as read-only.
126
+ */
127
+ export function listIntegrationCatalog(
128
+ filter?: IntegrationCatalogFilter,
129
+ ): IntegrationCatalogEntry[];
130
+ export function getIntegrationCatalogEntry(
131
+ id: string,
132
+ ): IntegrationCatalogEntry | undefined;
169
133
  export default INTEGRATION_CATALOG;
@@ -1,212 +1,34 @@
1
- import airtable from "./catalog/airtable.json" with { type: "json" };
2
- import apify from "./catalog/apify.json" with { type: "json" };
3
- import atlassian from "./catalog/atlassian.json" with { type: "json" };
4
- import brave_search from "./catalog/brave-search.json" with { type: "json" };
5
- import browser_mcp from "./catalog/browser-mcp.json" with { type: "json" };
6
- import clickhouse from "./catalog/clickhouse.json" with { type: "json" };
7
- import cloudflare_bindings from "./catalog/cloudflare-bindings.json" with { type: "json" };
8
- import cloudflare_browser_rendering from "./catalog/cloudflare-browser-rendering.json" with { type: "json" };
9
- import cloudflare_builds from "./catalog/cloudflare-builds.json" with { type: "json" };
10
- import cloudflare_docs from "./catalog/cloudflare-docs.json" with { type: "json" };
11
- import cloudflare_observability from "./catalog/cloudflare-observability.json" with { type: "json" };
12
- import deepwiki from "./catalog/deepwiki.json" with { type: "json" };
13
- import elevenlabs from "./catalog/elevenlabs.json" with { type: "json" };
14
- import everything from "./catalog/everything.json" with { type: "json" };
15
- import exa from "./catalog/exa.json" with { type: "json" };
16
- import fetch from "./catalog/fetch.json" with { type: "json" };
17
- import figma from "./catalog/figma.json" with { type: "json" };
18
- import filesystem from "./catalog/filesystem.json" with { type: "json" };
19
- import firecrawl from "./catalog/firecrawl.json" with { type: "json" };
20
- import git from "./catalog/git.json" with { type: "json" };
21
- import github from "./catalog/github.json" with { type: "json" };
22
- import huggingface from "./catalog/huggingface.json" with { type: "json" };
23
- import kagi from "./catalog/kagi.json" with { type: "json" };
24
- import linear from "./catalog/linear.json" with { type: "json" };
25
- import memory from "./catalog/memory.json" with { type: "json" };
26
- import mongodb from "./catalog/mongodb.json" with { type: "json" };
27
- import neon from "./catalog/neon.json" with { type: "json" };
28
- import notion from "./catalog/notion.json" with { type: "json" };
29
- import obsidian from "./catalog/obsidian.json" with { type: "json" };
30
- import paypal from "./catalog/paypal.json" with { type: "json" };
31
- import playwright from "./catalog/playwright.json" with { type: "json" };
32
- import redis from "./catalog/redis.json" with { type: "json" };
33
- import resend from "./catalog/resend.json" with { type: "json" };
34
- import sentry from "./catalog/sentry.json" with { type: "json" };
35
- import sequential_thinking from "./catalog/sequential-thinking.json" with { type: "json" };
36
- import slack from "./catalog/slack.json" with { type: "json" };
37
- import stripe from "./catalog/stripe.json" with { type: "json" };
38
- import supabase from "./catalog/supabase.json" with { type: "json" };
39
- import tavily from "./catalog/tavily.json" with { type: "json" };
40
- import time from "./catalog/time.json" with { type: "json" };
41
- import { listOAuthProviderCatalog } from "./oauth-provider-catalog.js";
42
- export { listOAuthProviderCatalog } from "./oauth-provider-catalog.js";
43
- export {
44
- getOAuthProviderRegistrationDefaults,
45
- hubspotMcpAuthorizationUrl,
46
- hubspotMcpServerUrl,
47
- hubspotMcpTokenUrl,
48
- hubspotOptionalScopes,
49
- hubspotRequiredScopes,
50
- } from "./oauth-provider-registration-defaults.js";
51
-
52
- const DIRECT_INTEGRATIONS = [
53
- airtable,
54
- apify,
55
- atlassian,
56
- brave_search,
57
- browser_mcp,
58
- clickhouse,
59
- cloudflare_bindings,
60
- cloudflare_browser_rendering,
61
- cloudflare_builds,
62
- cloudflare_docs,
63
- cloudflare_observability,
64
- deepwiki,
65
- elevenlabs,
66
- everything,
67
- exa,
68
- fetch,
69
- figma,
70
- filesystem,
71
- firecrawl,
72
- git,
73
- github,
74
- huggingface,
75
- kagi,
76
- linear,
77
- memory,
78
- mongodb,
79
- neon,
80
- notion,
81
- obsidian,
82
- paypal,
83
- playwright,
84
- redis,
85
- resend,
86
- sentry,
87
- sequential_thinking,
88
- slack,
89
- stripe,
90
- supabase,
91
- tavily,
92
- time,
93
- ];
94
-
95
- const optionIdForDefaults = (defaults) => {
96
- const strategy = defaults?.authStrategy ?? (defaults?.authorizationUrl || defaults?.tokenUrl ? "oauth2" : "oauth2");
97
- if (strategy === "oauth2") return "oauth";
98
- if (strategy === "none") return "none";
99
- return "api";
100
- };
101
-
102
- const providerConnectionOption = (provider) => {
103
- const defaults = provider.registrationDefaults;
104
- if (!defaults) return null;
105
- const option = {
106
- id: optionIdForDefaults(defaults),
107
- provider: defaults.provider ?? (defaults.serverUrl ? "mcp" : "http"),
108
- auth: {
109
- strategy: defaults.authStrategy ?? provider.authStrategy ?? "oauth2",
110
- authModes: defaults.authModes,
111
- credentialLabel: defaults.credentialLabel,
112
- credentialPlaceholder: defaults.credentialPlaceholder,
113
- credentialHelp: defaults.credentialHelp,
114
- apiKeyHeaderName: defaults.apiKeyHeaderName,
115
- oauth: defaults.authorizationUrl || defaults.tokenUrl ? {
116
- authorizationUrl: defaults.authorizationUrl,
117
- tokenUrl: defaults.tokenUrl,
118
- scopes: defaults.scopes ?? [],
119
- optionalScopes: defaults.optionalScopes,
120
- toolScopes: defaults.toolScopes,
121
- scopeSeparator: defaults.scopeSeparator,
122
- pkce: defaults.pkce,
123
- clientAuthentication: defaults.clientAuthentication,
124
- registrationUrl: defaults.registrationUrl,
125
- additionalAuthorizationParams: defaults.additionalAuthorizationParams,
126
- additionalTokenParams: defaults.additionalTokenParams,
127
- } : undefined,
128
- },
129
- };
130
-
131
- if (option.provider === "mcp") {
132
- option.transport = { kind: "shttp", url: defaults.serverUrl };
133
- } else {
134
- option.http = {
135
- apiBaseUrl: defaults.apiBaseUrl,
136
- openApiUrl: defaults.openApiUrl,
137
- defaultTool: defaults.toolName ? {
138
- name: defaults.toolName,
139
- description: defaults.toolDescription,
140
- method: defaults.requestMethod,
141
- path: defaults.requestPath,
142
- scopes: defaults.toolScopes,
143
- } : undefined,
144
- };
145
- }
146
-
147
- return option;
148
- };
149
-
150
- const providerIntegration = (provider) => {
151
- const option = providerConnectionOption(provider);
152
- return {
153
- id: provider.slug,
154
- kind: option?.provider ?? "http",
155
- name: provider.name,
156
- description: provider.description,
157
- categories: provider.categories,
158
- appUrl: provider.appUrl,
159
- docsUrl: provider.docsUrl,
160
- notes: provider.notes,
161
- catalogStatus: provider.availability,
162
- managedConnectorSlug: provider.managedConnectorSlug,
163
- authStrategy: provider.authStrategy,
164
- popularityRank: provider.popularityRank,
165
- registrationDefaults: provider.registrationDefaults,
166
- ...(option ? { defaultConnectionOptionId: option.id, connectionOptions: [option] } : { connectionOptions: [] }),
167
- };
168
- };
169
-
170
- const mergeOptions = (left = [], right = []) => {
171
- const options = new Map();
172
- for (const option of left) options.set(option.id, option);
173
- for (const option of right) {
174
- if (!options.has(option.id)) options.set(option.id, option);
175
- }
176
- return [...options.values()];
177
- };
178
-
179
- const mergeIntegration = (base, override) => {
180
- const connectionOptions = mergeOptions(base.connectionOptions, override.connectionOptions);
181
- return {
182
- ...base,
183
- ...override,
184
- catalogStatus: base.catalogStatus ?? override.catalogStatus,
185
- managedConnectorSlug: base.managedConnectorSlug ?? override.managedConnectorSlug,
186
- authStrategy: base.authStrategy ?? override.authStrategy,
187
- registrationDefaults: base.registrationDefaults ?? override.registrationDefaults,
188
- connectionOptions,
189
- defaultConnectionOptionId:
190
- base.defaultConnectionOptionId ?? override.defaultConnectionOptionId ?? connectionOptions[0]?.id,
191
- };
1
+ /**
2
+ * Runtime integration catalog.
3
+ *
4
+ * The source of truth is the hand-authored `integrations/catalog/<id>.json`
5
+ * directory. `catalog-index.js` is generated from that directory so the JS
6
+ * package can statically import each JSON file without an aggregate JSON asset.
7
+ */
8
+ import { INTEGRATION_CATALOG_ENTRIES } from "./catalog-index.js";
9
+
10
+ const INTEGRATIONS = INTEGRATION_CATALOG_ENTRIES;
11
+ const INTEGRATION_BY_ID = new Map(INTEGRATIONS.map((entry) => [entry.id, entry]));
12
+
13
+ const entrySupportsMcp = (entry) =>
14
+ entry.connectionOptions.some((option) => option.provider === "mcp");
15
+
16
+ const entrySupportsOauth = (entry) =>
17
+ entry.connectionOptions.some((option) => option.auth?.strategy === "oauth2");
18
+
19
+ export const listIntegrationCatalog = (filter) => {
20
+ if (!filter) return INTEGRATIONS;
21
+ const { mcp, oauth } = filter;
22
+ if (mcp === undefined && oauth === undefined) return INTEGRATIONS;
23
+ return INTEGRATIONS.filter((entry) => {
24
+ const mcpOk = mcp === undefined || entrySupportsMcp(entry) === mcp;
25
+ const oauthOk = oauth === undefined || entrySupportsOauth(entry) === oauth;
26
+ return mcpOk && oauthOk;
27
+ });
192
28
  };
193
29
 
194
- const entriesById = new Map();
195
- for (const provider of listOAuthProviderCatalog().map(providerIntegration)) {
196
- entriesById.set(provider.id, provider);
197
- }
198
- for (const direct of DIRECT_INTEGRATIONS) {
199
- entriesById.set(
200
- direct.id,
201
- entriesById.has(direct.id)
202
- ? mergeIntegration(entriesById.get(direct.id), direct)
203
- : direct,
204
- );
205
- }
30
+ export const getIntegrationCatalogEntry = (id) => INTEGRATION_BY_ID.get(id);
206
31
 
207
- export const INTEGRATION_CATALOG = [...entriesById.values()].sort((a, b) => {
208
- const rankDelta = (b.popularityRank ?? 0) - (a.popularityRank ?? 0);
209
- return rankDelta || a.name.localeCompare(b.name);
210
- });
32
+ export const INTEGRATION_CATALOG = INTEGRATIONS;
211
33
 
212
34
  export default INTEGRATION_CATALOG;
@@ -88,6 +88,19 @@
88
88
  "openhands"
89
89
  ]
90
90
  },
91
+ {
92
+ "name": "agent-canvas-environment",
93
+ "source": "./skills/agent-canvas-environment",
94
+ "description": "Work effectively inside a local Agent Canvas environment, including local agent-server auth, safe workspace hygiene, and local conversation delegation.",
95
+ "category": "development",
96
+ "keywords": [
97
+ "agent-canvas",
98
+ "openhands",
99
+ "local",
100
+ "conversation",
101
+ "delegation"
102
+ ]
103
+ },
91
104
  {
92
105
  "name": "openhands-sdk",
93
106
  "source": "./skills/openhands-sdk",
@@ -404,7 +417,7 @@
404
417
  {
405
418
  "name": "openhands-api",
406
419
  "source": "./skills/openhands-api",
407
- "description": "Use the OpenHands Cloud REST API (V1) to create and manage app conversations, including multi-conversation delegation workflows, and to access sandbox agent-server endpoints. Includes minimal Python and TypeScript clients under scripts/.",
420
+ "description": "Use the OpenHands Cloud REST API (V1) and agent-server APIs to create and manage Cloud or local backend conversations, including multi-conversation delegation workflows. Includes minimal Python and TypeScript clients under scripts/.",
408
421
  "category": "development",
409
422
  "keywords": [
410
423
  "openhands",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/extensions",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Public OpenHands extension catalogs for skills, plugins, integrations, and automation templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,9 @@
9
9
  "url": "https://github.com/OpenHands/extensions"
10
10
  },
11
11
  "scripts": {
12
- "build:skills": "node scripts/build-skills-catalog.mjs"
12
+ "build:skills": "node scripts/build-skills-catalog.mjs",
13
+ "build:integrations": "node scripts/build-integration-catalog.mjs",
14
+ "build": "npm run build:skills && npm run build:integrations"
13
15
  },
14
16
  "engines": {
15
17
  "node": ">=18.20.0"
@@ -39,11 +41,6 @@
39
41
  "import": "./integrations/index.js",
40
42
  "default": "./integrations/index.js"
41
43
  },
42
- "./integrations/logos": {
43
- "types": "./integrations/logos.d.ts",
44
- "import": "./integrations/logos.js",
45
- "default": "./integrations/logos.js"
46
- },
47
44
  "./integrations/catalog/*.json": "./integrations/catalog/*.json",
48
45
  "./skills": {
49
46
  "types": "./skills/index.d.ts",
@@ -36,11 +36,11 @@ plugins/cobol-modernization/
36
36
  └── skills/ # Workflow phase skills
37
37
  ├── build-setup/ # Phase 1: Build environment
38
38
  │ └── SKILL.md
39
- ├── cobol-modernization-overview/ # Plugin overview
39
+ ├── cobol-modernization/ # Plugin overview
40
40
  │ └── SKILL.md
41
41
  ├── mainframe-planning/ # Phase 2: Transformation planning
42
42
  │ └── SKILL.md
43
- ├── mainfraime-removal/ # Phase 3: Mainframe dependency removal
43
+ ├── mainframe-removal/ # Phase 3: Mainframe dependency removal
44
44
  │ ├── SKILL.md
45
45
  │ └── references/
46
46
  │ └── cics-transformation-examples.md
@@ -139,7 +139,7 @@ You can also run individual phases by using the specific skill definitions:
139
139
 
140
140
  - See [skills/build-setup/SKILL.md](skills/build-setup/SKILL.md) for build setup only
141
141
  - See [skills/mainframe-planning/SKILL.md](skills/mainframe-planning/SKILL.md) for planning
142
- - See [skills/mainfraime-removal/SKILL.md](skills/mainfraime-removal/SKILL.md) for mainframe removal
142
+ - See [skills/mainframe-removal/SKILL.md](skills/mainframe-removal/SKILL.md) for mainframe removal
143
143
  - See [skills/to-java-migration/SKILL.md](skills/to-java-migration/SKILL.md) for Java translation
144
144
 
145
145
  ## Troubleshooting
@@ -67,7 +67,7 @@ Creates a transformation guide without modifying code:
67
67
 
68
68
  ### Phase 3: Mainframe Removal
69
69
 
70
- See [../mainfraime-removal/SKILL.md](../mainfraime-removal/SKILL.md)
70
+ See [../mainframe-removal/SKILL.md](../mainframe-removal/SKILL.md)
71
71
 
72
72
  Applies the transformation guide:
73
73
  - Replaces EXEC CICS commands with file I/O
@@ -1,4 +1,5 @@
1
1
  ---
2
+ name: magic-word
2
3
  description: A test skill that responds to the magic word "alakazam" with a specific phrase
3
4
  triggers:
4
5
  - alakazam
@@ -34,7 +34,7 @@ uv run python -m lc_sdk_examples.migration_scoring \
34
34
  plugins/migration-scoring/
35
35
  ├── README.md # This file
36
36
  └── skills/ # Workflow phase skills
37
- ├── migration-scoring-overview/ # Plugin overview
37
+ ├── migration-scoring/ # Plugin overview
38
38
  │ └── SKILL.md
39
39
  ├── migration-mapping/ # Phase 1: Source-to-target mapping
40
40
  │ └── SKILL.md
package/pyproject.toml CHANGED
@@ -1,12 +1,26 @@
1
1
  [project]
2
- name = "extensions"
3
- version = "0.5.0"
4
- description = "OpenHands extensions, plugins, and skills"
2
+ name = "openhands-extensions"
3
+ version = "0.6.0"
4
+ description = "OpenHands extensions, plugins, and skills (Python bindings for the integration catalog)"
5
5
  requires-python = ">=3.12"
6
6
 
7
+ [build-system]
8
+ requires = ["hatchling>=1.21"]
9
+ build-backend = "hatchling.build"
10
+
11
+ [tool.hatch.build.targets.wheel]
12
+ packages = ["python/openhands_extensions"]
13
+
14
+ [tool.hatch.build.targets.wheel.force-include]
15
+ "integrations/catalog" = "openhands_extensions/catalog"
16
+
7
17
  [dependency-groups]
8
18
  test = [
9
19
  "pytest>=8.0",
10
20
  "requests>=2.31",
11
21
  "openhands-sdk>=0.3",
12
22
  ]
23
+
24
+ [tool.pytest.ini_options]
25
+ pythonpath = ["python"]
26
+ testpaths = ["tests"]
@@ -0,0 +1,24 @@
1
+ """Python bindings for the OpenHands extensions catalogs.
2
+
3
+ Mirrors the JavaScript package (``@openhands/extensions``): both read the same
4
+ hand-authored ``integrations/catalog/<id>.json`` files. The JavaScript package
5
+ uses a generated static import index; the Python package loads the packaged
6
+ individual JSON files directly. See ``integrations.py`` for the catalog API,
7
+ including filtering by connector type (mcp / oauth).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from ._version import __version__
13
+ from .integrations import (
14
+ INTEGRATION_CATALOG_SNAPSHOT,
15
+ get_integration_catalog_entry,
16
+ list_integration_catalog,
17
+ )
18
+
19
+ __all__ = [
20
+ "INTEGRATION_CATALOG_SNAPSHOT",
21
+ "__version__",
22
+ "get_integration_catalog_entry",
23
+ "list_integration_catalog",
24
+ ]
@@ -0,0 +1,24 @@
1
+ """Package version, derived from installed package metadata.
2
+
3
+ The single source of truth is ``pyproject.toml``'s ``[project].version``
4
+ (which ``release-please`` bumps together with ``package.json`` via
5
+ ``release-please-config.json`` -> ``extra-files``). At runtime we read the
6
+ installed distribution metadata via ``importlib.metadata`` so there is no
7
+ second hand-maintained version string. ``_FALLBACK_VERSION`` is only used
8
+ when the package is imported without being installed (e.g. straight off a
9
+ source checkout with no build), and ``tests/test_version_alignment.py``
10
+ asserts it stays in lock-step with ``pyproject.toml`` / ``package.json``.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from importlib.metadata import PackageNotFoundError, version
16
+
17
+ #: Fallback used only when the package is not installed (no dist metadata).
18
+ #: Kept in lock-step with pyproject.toml/package.json by the version test.
19
+ _FALLBACK_VERSION = "0.6.0"
20
+
21
+ try:
22
+ __version__: str = version("openhands-extensions")
23
+ except PackageNotFoundError: # not installed (e.g. raw source checkout)
24
+ __version__ = _FALLBACK_VERSION
@@ -0,0 +1,92 @@
1
+ """Python bindings for the OpenHands extensions integration catalog.
2
+
3
+ The source of truth is the hand-authored ``integrations/catalog/<id>.json``
4
+ directory. Wheels include those individual JSON files directly; no aggregate
5
+ catalog JSON is authored or packaged.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import copy
11
+ import json
12
+ from functools import lru_cache
13
+ from importlib import resources
14
+ from pathlib import Path
15
+ from typing import Any, Iterable
16
+
17
+ __all__ = [
18
+ "INTEGRATION_CATALOG_SNAPSHOT",
19
+ "get_integration_catalog_entry",
20
+ "list_integration_catalog",
21
+ ]
22
+
23
+
24
+ def _repo_catalog_dir() -> Path:
25
+ return Path(__file__).resolve().parents[2] / "integrations" / "catalog"
26
+
27
+
28
+ def _catalog_files() -> Iterable[Any]:
29
+ packaged = resources.files(__package__).joinpath("catalog")
30
+ if packaged.is_dir():
31
+ return sorted(
32
+ (path for path in packaged.iterdir() if path.name.endswith(".json")),
33
+ key=lambda path: path.name,
34
+ )
35
+
36
+ repo_catalog = _repo_catalog_dir()
37
+ return sorted(repo_catalog.glob("*.json"), key=lambda path: path.name)
38
+
39
+
40
+ def _read_json(path: Any) -> dict[str, Any]:
41
+ return json.loads(path.read_text(encoding="utf-8"))
42
+
43
+
44
+ @lru_cache(maxsize=1)
45
+ def _integrations() -> tuple[dict[str, Any], ...]:
46
+ entries = [_read_json(path) for path in _catalog_files()]
47
+ entries.sort(
48
+ key=lambda entry: (-(entry.get("popularityRank") if entry.get("popularityRank") is not None else -1), entry["id"]),
49
+ )
50
+ return tuple(entries)
51
+
52
+
53
+ @lru_cache(maxsize=1)
54
+ def _integration_by_id() -> dict[str, dict[str, Any]]:
55
+ return {entry["id"]: entry for entry in _integrations()}
56
+
57
+
58
+ def _entry_supports_mcp(entry: dict[str, Any]) -> bool:
59
+ return any(option.get("provider") == "mcp" for option in entry.get("connectionOptions", []))
60
+
61
+
62
+ def _entry_supports_oauth(entry: dict[str, Any]) -> bool:
63
+ return any(
64
+ option.get("auth", {}).get("strategy") == "oauth2"
65
+ for option in entry.get("connectionOptions", [])
66
+ )
67
+
68
+
69
+ def list_integration_catalog(
70
+ mcp: bool | None = None,
71
+ oauth: bool | None = None,
72
+ ) -> list[dict[str, Any]]:
73
+ """Return the integration catalog, optionally filtered by connector type."""
74
+ result = []
75
+ for entry in _integrations():
76
+ if mcp is not None and _entry_supports_mcp(entry) != mcp:
77
+ continue
78
+ if oauth is not None and _entry_supports_oauth(entry) != oauth:
79
+ continue
80
+ result.append(copy.deepcopy(entry))
81
+ return result
82
+
83
+
84
+ def get_integration_catalog_entry(id: str) -> dict[str, Any] | None:
85
+ """Return one integration catalog entry by id, or ``None``."""
86
+ entry = _integration_by_id().get(id)
87
+ return copy.deepcopy(entry) if entry is not None else None
88
+
89
+
90
+ INTEGRATION_CATALOG_SNAPSHOT: dict[str, Any] = {
91
+ "integrations": copy.deepcopy(list(_integrations()))
92
+ }
@@ -9,7 +9,8 @@
9
9
  "include-component-in-tag": false,
10
10
  "skip-changelog": true,
11
11
  "extra-files": [
12
- { "type": "toml", "path": "pyproject.toml", "jsonpath": "$.project.version" }
12
+ { "type": "toml", "path": "pyproject.toml", "jsonpath": "$.project.version" },
13
+ "python/openhands_extensions/_version.py"
13
14
  ]
14
15
  }
15
16
  }