@openhands/extensions 0.5.0 → 0.7.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 (135) 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 +34 -4
  6. package/integrations/README.md +25 -9
  7. package/integrations/catalog/airtable.json +16 -10
  8. package/integrations/catalog/apify.json +0 -2
  9. package/integrations/catalog/asana.json +33 -0
  10. package/integrations/catalog/atlassian.json +5 -8
  11. package/integrations/catalog/bitbucket.json +34 -0
  12. package/integrations/catalog/box.json +34 -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 +31 -0
  16. package/integrations/catalog/clickhouse.json +1 -2
  17. package/integrations/catalog/cloudflare-bindings.json +5 -8
  18. package/integrations/catalog/cloudflare-browser-rendering.json +5 -8
  19. package/integrations/catalog/cloudflare-builds.json +6 -9
  20. package/integrations/catalog/cloudflare-docs.json +4 -7
  21. package/integrations/catalog/cloudflare-observability.json +5 -8
  22. package/integrations/catalog/confluence.json +33 -0
  23. package/integrations/catalog/datadog.json +34 -0
  24. package/integrations/catalog/deepwiki.json +3 -7
  25. package/integrations/catalog/discord.json +34 -0
  26. package/integrations/catalog/elevenlabs.json +27 -28
  27. package/integrations/catalog/everything.json +0 -2
  28. package/integrations/catalog/exa.json +0 -2
  29. package/integrations/catalog/fetch.json +0 -2
  30. package/integrations/catalog/figma.json +35 -9
  31. package/integrations/catalog/filesystem.json +0 -3
  32. package/integrations/catalog/firecrawl.json +0 -2
  33. package/integrations/catalog/git.json +0 -3
  34. package/integrations/catalog/github.json +34 -10
  35. package/integrations/catalog/gmail.json +33 -0
  36. package/integrations/catalog/google-calendar.json +33 -0
  37. package/integrations/catalog/google-docs.json +33 -0
  38. package/integrations/catalog/google-drive.json +33 -0
  39. package/integrations/catalog/google-sheets.json +33 -0
  40. package/integrations/catalog/hubspot.json +34 -0
  41. package/integrations/catalog/huggingface.json +1 -2
  42. package/integrations/catalog/intercom.json +34 -0
  43. package/integrations/catalog/jira.json +33 -0
  44. package/integrations/catalog/kagi.json +2 -3
  45. package/integrations/catalog/linear.json +24 -16
  46. package/integrations/catalog/memory.json +0 -2
  47. package/integrations/catalog/microsoft-outlook.json +33 -0
  48. package/integrations/catalog/microsoft-teams.json +33 -0
  49. package/integrations/catalog/miro.json +42 -0
  50. package/integrations/catalog/monday.json +42 -0
  51. package/integrations/catalog/mongodb.json +1 -2
  52. package/integrations/catalog/neon.json +0 -2
  53. package/integrations/catalog/netlify.json +33 -0
  54. package/integrations/catalog/notion.json +30 -10
  55. package/integrations/catalog/obsidian.json +1 -3
  56. package/integrations/catalog/okta.json +33 -0
  57. package/integrations/catalog/onedrive.json +33 -0
  58. package/integrations/catalog/ordinal.json +33 -0
  59. package/integrations/catalog/paypal.json +5 -8
  60. package/integrations/catalog/playwright.json +0 -2
  61. package/integrations/catalog/posthog.json +38 -0
  62. package/integrations/catalog/quickbooks.json +86 -0
  63. package/integrations/catalog/redis.json +1 -3
  64. package/integrations/catalog/resend.json +1 -2
  65. package/integrations/catalog/salesforce.json +44 -0
  66. package/integrations/catalog/sentry.json +47 -14
  67. package/integrations/catalog/sequential-thinking.json +0 -2
  68. package/integrations/catalog/sharepoint.json +33 -0
  69. package/integrations/catalog/slack.json +49 -10
  70. package/integrations/catalog/stripe.json +39 -14
  71. package/integrations/catalog/supabase.json +31 -9
  72. package/integrations/catalog/tavily.json +0 -2
  73. package/integrations/catalog/time.json +0 -2
  74. package/integrations/catalog/trello.json +34 -0
  75. package/integrations/catalog/vercel.json +33 -0
  76. package/integrations/catalog/webflow.json +34 -0
  77. package/integrations/catalog/xero.json +33 -0
  78. package/integrations/catalog/zoom.json +33 -0
  79. package/integrations/catalog-index.js +151 -0
  80. package/integrations/catalog.schema.json +255 -0
  81. package/integrations/index.d.ts +49 -69
  82. package/integrations/index.js +36 -206
  83. package/marketplaces/openhands-extensions.json +40 -2
  84. package/package.json +4 -7
  85. package/plugins/cobol-modernization/README.md +3 -3
  86. package/plugins/cobol-modernization/skills/{cobol-modernization-overview → cobol-modernization}/SKILL.md +1 -1
  87. package/plugins/magic-test/skills/magic-word/SKILL.md +1 -0
  88. package/plugins/migration-scoring/README.md +1 -1
  89. package/plugins/pr-review/scripts/prompt.py +1 -1
  90. package/pyproject.toml +18 -3
  91. package/python/openhands_extensions/__init__.py +24 -0
  92. package/python/openhands_extensions/_version.py +25 -0
  93. package/python/openhands_extensions/integrations.py +92 -0
  94. package/release-please-config.json +2 -1
  95. package/scripts/build-integration-catalog.mjs +45 -0
  96. package/skills/agent-canvas-environment/.plugin/plugin.json +19 -0
  97. package/skills/agent-canvas-environment/README.md +5 -0
  98. package/skills/agent-canvas-environment/SKILL.md +202 -0
  99. package/skills/bitbucket/README.md +23 -40
  100. package/skills/bitbucket/SKILL.md +43 -31
  101. package/skills/bitbucket-cloud/.plugin/plugin.json +18 -0
  102. package/skills/bitbucket-cloud/README.md +15 -0
  103. package/skills/bitbucket-cloud/SKILL.md +46 -0
  104. package/skills/bitbucket-data-center/.plugin/plugin.json +19 -0
  105. package/skills/bitbucket-data-center/README.md +20 -0
  106. package/skills/bitbucket-data-center/SKILL.md +53 -0
  107. package/skills/code-review/SKILL.md +1 -1
  108. package/skills/code-review/references/supply-chain-security.md +2 -2
  109. package/skills/github-pr-reviewer/references/state-schema.md +18 -4
  110. package/skills/github-pr-reviewer/scripts/main.py +66 -12
  111. package/skills/github-repo-monitor/references/state-schema.md +11 -4
  112. package/skills/github-repo-monitor/scripts/main.py +67 -19
  113. package/skills/github-repo-monitor/tests/test_main.py +67 -8
  114. package/skills/index.js +34 -8
  115. package/skills/openhands-api/README.md +5 -1
  116. package/skills/openhands-api/SKILL.md +74 -5
  117. package/skills/openhands-automation/SKILL.md +3 -1
  118. package/skills/openhands-automation/references/custom-automation.md +166 -0
  119. package/skills/openhands-sdk/SKILL.md +8 -3
  120. package/skills/slack-channel-monitor/README.md +3 -3
  121. package/skills/slack-channel-monitor/SKILL.md +3 -2
  122. package/skills/slack-channel-monitor/references/state-schema.md +11 -4
  123. package/skills/slack-channel-monitor/scripts/main.py +111 -41
  124. package/tests/test_catalog_schema.py +47 -0
  125. package/tests/test_catalogs.py +47 -3
  126. package/tests/test_integration_catalog_in_sync.py +252 -0
  127. package/tests/test_slack_channel_monitor.py +142 -0
  128. package/tests/test_version_alignment.py +64 -0
  129. package/integrations/logos.d.ts +0 -5
  130. package/integrations/logos.js +0 -94
  131. package/integrations/oauth-provider-catalog.js +0 -553
  132. package/integrations/oauth-provider-registration-defaults.js +0 -610
  133. /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/SKILL.md +0 -0
  134. /package/plugins/cobol-modernization/skills/{mainfraime-removal → mainframe-removal}/references/cics-transformation-examples.md +0 -0
  135. /package/plugins/migration-scoring/skills/{migration-scoring-overview → migration-scoring}/SKILL.md +0 -0
@@ -1,212 +1,42 @@
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;
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 clone = (value) => JSON.parse(JSON.stringify(value));
11
+ const INTEGRATIONS = INTEGRATION_CATALOG_ENTRIES;
12
+ const INTEGRATION_BY_ID = new Map(INTEGRATIONS.map((entry) => [entry.id, entry]));
13
+
14
+ const entrySupportsMcp = (entry) =>
15
+ entry.connectionOptions.some((option) => option.provider === "mcp");
16
+
17
+ const entrySupportsOauth = (entry) =>
18
+ entry.connectionOptions.some((option) => option.auth?.strategy === "oauth2");
19
+
20
+ export const listIntegrationCatalog = (filter) => {
21
+ const entries =
22
+ !filter || (filter.mcp === undefined && filter.oauth === undefined)
23
+ ? INTEGRATIONS
24
+ : INTEGRATIONS.filter((entry) => {
25
+ const mcpOk =
26
+ filter.mcp === undefined || entrySupportsMcp(entry) === filter.mcp;
27
+ const oauthOk =
28
+ filter.oauth === undefined ||
29
+ entrySupportsOauth(entry) === filter.oauth;
30
+ return mcpOk && oauthOk;
31
+ });
32
+ return clone(entries);
148
33
  };
149
34
 
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
- };
35
+ export const getIntegrationCatalogEntry = (id) => {
36
+ const entry = INTEGRATION_BY_ID.get(id);
37
+ return entry ? clone(entry) : undefined;
168
38
  };
169
39
 
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
- };
192
- };
193
-
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
- }
206
-
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
- });
40
+ export const INTEGRATION_CATALOG = clone(INTEGRATIONS);
211
41
 
212
42
  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",
@@ -117,10 +130,35 @@
117
130
  {
118
131
  "name": "bitbucket",
119
132
  "source": "./skills/bitbucket",
120
- "description": "Interact with Bitbucket repositories and pull requests using the BITBUCKET_TOKEN environment variable. Use when working with code hosted on Bitbucket or managing Bitbucket resources via API.",
133
+ "description": "Bitbucket integration hub. Detects whether the repository is on Bitbucket Cloud or Bitbucket Data Center and directs you to the matching detailed skill (bitbucket-cloud or bitbucket-data-center). Use for any Bitbucket repository or pull request task.",
134
+ "category": "integration",
135
+ "keywords": [
136
+ "bitbucket",
137
+ "git",
138
+ "pull-request"
139
+ ]
140
+ },
141
+ {
142
+ "name": "bitbucket-cloud",
143
+ "source": "./skills/bitbucket-cloud",
144
+ "description": "Bitbucket Cloud (bitbucket.org) specifics — authenticate with BITBUCKET_TOKEN, use the REST API v2, workspace/repo_slug repositories, and the create_bitbucket_pr tool. Loaded on demand by the bitbucket skill once a Cloud environment is detected.",
145
+ "category": "integration",
146
+ "keywords": [
147
+ "bitbucket",
148
+ "bitbucket-cloud",
149
+ "git",
150
+ "pull-request"
151
+ ]
152
+ },
153
+ {
154
+ "name": "bitbucket-data-center",
155
+ "source": "./skills/bitbucket-data-center",
156
+ "description": "Bitbucket Data Center (self-hosted Bitbucket Server) specifics — authenticate with BITBUCKET_DATA_CENTER_TOKEN, use the REST API 1.0, PROJECT/repo_slug repositories, scm/ git remotes, and the create_bitbucket_data_center_pr tool. Loaded on demand by the bitbucket skill once a Data Center environment is detected.",
121
157
  "category": "integration",
122
158
  "keywords": [
123
159
  "bitbucket",
160
+ "bitbucket-data-center",
161
+ "bitbucket-server",
124
162
  "git",
125
163
  "pull-request"
126
164
  ]
@@ -404,7 +442,7 @@
404
442
  {
405
443
  "name": "openhands-api",
406
444
  "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/.",
445
+ "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
446
  "category": "development",
409
447
  "keywords": [
410
448
  "openhands",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/extensions",
3
- "version": "0.5.0",
3
+ "version": "0.7.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
@@ -76,7 +76,7 @@ PROMPT = """{skill_trigger}
76
76
 
77
77
  When posting a review, keep the review body brief unless your active review instructions require a longer structured format.
78
78
 
79
- For dependency update PRs, do **NOT** approve a target version that was published less than 7 days ago.
79
+ For dependency update PRs, do **NOT** approve a target version that was published less than 7 days ago. First-party packages maintained by the same organization as the reviewed repository are intentionally excluded from this 7-day waiting rule, but still scrutinize them for supply-chain risk.
80
80
 
81
81
  Review the PR changes below and identify issues that need to be addressed.
82
82
 
package/pyproject.toml CHANGED
@@ -1,12 +1,27 @@
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.7.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",
22
+ "jsonschema>=4.23",
12
23
  ]
24
+
25
+ [tool.pytest.ini_options]
26
+ pythonpath = ["python"]
27
+ 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,25 @@
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
+ # x-release-please-update-version
20
+ _FALLBACK_VERSION = "0.7.0"
21
+
22
+ try:
23
+ __version__: str = version("openhands-extensions")
24
+ except PackageNotFoundError: # not installed (e.g. raw source checkout)
25
+ __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
  }
@@ -0,0 +1,45 @@
1
+ import { readdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ const root = process.cwd();
5
+ const catalogDir = path.join(root, "integrations", "catalog");
6
+ const outputPath = path.join(root, "integrations", "catalog-index.js");
7
+
8
+ const files = (await readdir(catalogDir))
9
+ .filter((file) => file.endsWith(".json"))
10
+ .sort((left, right) => left.localeCompare(right));
11
+
12
+ const integrations = await Promise.all(
13
+ files.map(async (file) => ({
14
+ file,
15
+ entry: JSON.parse(await readFile(path.join(catalogDir, file), "utf8")),
16
+ })),
17
+ );
18
+
19
+ integrations.sort((left, right) => {
20
+ const rank = (right.entry.popularityRank ?? -1) - (left.entry.popularityRank ?? -1);
21
+ return rank || left.entry.id.localeCompare(right.entry.id);
22
+ });
23
+
24
+ const indexedIntegrations = integrations.map((integration, index) => ({
25
+ ...integration,
26
+ importName: `entry${index}`,
27
+ }));
28
+
29
+ const imports = indexedIntegrations
30
+ .map(({ file, importName }) => `import ${importName} from "./catalog/${file}" with { type: "json" };`)
31
+ .join("\n");
32
+ const entries = indexedIntegrations.map(({ importName }) => ` ${importName},`).join("\n");
33
+
34
+ const header = `// This file is auto-generated by scripts/build-integration-catalog.mjs.
35
+ // Do not edit it manually. To update it after changing integrations/catalog/*.json,
36
+ // run: npm run build:integrations
37
+
38
+ `;
39
+ const body = `${header}${imports}
40
+
41
+ export const INTEGRATION_CATALOG_ENTRIES = [
42
+ ${entries}
43
+ ];
44
+ `;
45
+ await writeFile(outputPath, body);
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "agent-canvas-environment",
3
+ "version": "1.0.0",
4
+ "description": "Work effectively inside a local Agent Canvas environment, including local agent-server auth, frontend/backend port discovery, safe workspace hygiene, and local conversation delegation.",
5
+ "author": {
6
+ "name": "OpenHands",
7
+ "email": "contact@all-hands.dev"
8
+ },
9
+ "homepage": "https://github.com/OpenHands/extensions",
10
+ "repository": "https://github.com/OpenHands/extensions",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "agent-canvas",
14
+ "openhands",
15
+ "local",
16
+ "conversation",
17
+ "delegation"
18
+ ]
19
+ }