@openhands/extensions 0.16.0 → 0.17.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 (44) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/AGENTS.md +1 -0
  3. package/automations/README.md +75 -9
  4. package/automations/bundle-index.js +9 -0
  5. package/automations/catalog/github-pr-reviewer/manifest.json +23 -7
  6. package/automations/catalog/github-repo-monitor/manifest.json +28 -34
  7. package/automations/catalog/jira-issue-to-pr/manifest.json +3 -3
  8. package/automations/catalog.schema.json +102 -1
  9. package/automations/index.d.ts +48 -0
  10. package/automations/index.js +14 -0
  11. package/automations/interface.json +3 -1
  12. package/automations/interface.schema.json +6 -2
  13. package/index.d.ts +1 -1
  14. package/index.js +1 -1
  15. package/integrations/catalog/posthog.json +41 -3
  16. package/integrations/catalog/sonarqube.json +78 -0
  17. package/integrations/catalog-index.js +65 -63
  18. package/marketplaces/openhands-extensions.json +11 -0
  19. package/package.json +1 -1
  20. package/plugins/qa-changes/action.yml +1 -1
  21. package/pyproject.toml +1 -1
  22. package/python/openhands_extensions/_version.py +1 -1
  23. package/scripts/build-automation-catalog.mjs +30 -0
  24. package/scripts/build-skills-catalog.mjs +24 -7
  25. package/skills/add-skill/SKILL.md +1 -1
  26. package/skills/github-pr-reviewer/README.md +12 -6
  27. package/skills/github-pr-reviewer/SKILL.md +93 -31
  28. package/skills/github-pr-reviewer/commands/pr-reviewer-setup.md +1 -1
  29. package/skills/github-pr-reviewer/references/state-schema.md +68 -18
  30. package/skills/github-pr-reviewer/scripts/main.py +540 -103
  31. package/skills/github-pr-reviewer/tests/test_main.py +503 -0
  32. package/skills/index.d.ts +6 -0
  33. package/skills/index.js +29 -17
  34. package/skills/jira-issue-to-pr/scripts/main.py +4 -2
  35. package/skills/openhands-sdk/SKILL.md +2 -0
  36. package/skills/skill-creator/SKILL.md +1 -1
  37. package/skills/slack-channel-monitor/scripts/main.py +3 -2
  38. package/tests/fixtures/automations/capabilities.json +30 -0
  39. package/tests/fixtures/automations/github-pr-reviewer.json +319 -55
  40. package/tests/fixtures/automations/github-repo-monitor.json +153 -62
  41. package/tests/test_automation_setup.py +226 -31
  42. package/tests/test_catalogs.py +43 -0
  43. package/tests/test_skills_catalog.py +182 -36
  44. package/tests/test_slack_channel_monitor.py +1 -1
@@ -63,6 +63,11 @@ export interface AutomationFormField {
63
63
  default?: string;
64
64
  required: boolean;
65
65
  provider?: AutomationGitProvider;
66
+ /**
67
+ * repo-picker only. The field collects several repositories rather than one,
68
+ * and its value is a list. A whole-value placeholder resolves to that list.
69
+ */
70
+ multiple?: true;
66
71
  options?: AutomationFieldOption[];
67
72
  constraints?: AutomationFieldConstraints;
68
73
  }
@@ -96,12 +101,40 @@ export interface AutomationForm {
96
101
  args: AutomationFormFields;
97
102
  }
98
103
 
104
+ /** A config.json leaf: templated string, number, boolean, null, or a nesting of those. */
105
+ export type AutomationBundleConfigValue =
106
+ | string
107
+ | number
108
+ | boolean
109
+ | null
110
+ | AutomationBundleConfigValue[]
111
+ | { [key: string]: AutomationBundleConfigValue };
112
+
113
+ /**
114
+ * The script tarball a direct entry may ship instead of a prompt, for an
115
+ * automation that is deterministic machinery rather than judgement.
116
+ */
117
+ export interface AutomationBundle {
118
+ /** The command run inside the extracted tarball. */
119
+ entrypoint: string;
120
+ /** Script run once before the entrypoint. Absent when nothing to install. */
121
+ setupScript?: string;
122
+ /** Seconds a run may take, when the service default is not enough. */
123
+ timeout?: number;
124
+ /** Packed path -> the repository path the file is read from at build time. */
125
+ files: Record<string, string>;
126
+ /** Rendered from the form and packed as config.json beside the entrypoint. */
127
+ config: Record<string, AutomationBundleConfigValue>;
128
+ }
129
+
99
130
  export interface AutomationSetup {
100
131
  version: "1.0";
101
132
  mode: AutomationSetupMode;
102
133
  form: AutomationForm;
103
134
  /** direct only. What the automation is told to do. */
104
135
  prompt?: string;
136
+ /** direct only, and the alternative to `prompt`. Exactly one is present. */
137
+ bundle?: AutomationBundle;
105
138
  /** direct only, event trigger only. Which delivered events belong to it. */
106
139
  filter?: string;
107
140
  /**
@@ -337,6 +370,10 @@ export interface AutomationInterfaceEndpoints {
337
370
  validate: string;
338
371
  createPrompt: string;
339
372
  createPlugin: string;
373
+ /** The raw create endpoint, which a bundle entry is created through. */
374
+ createBundle: string;
375
+ /** Where a bundle's tarball is uploaded before that create call. */
376
+ uploads: string;
340
377
  }
341
378
 
342
379
  export interface AutomationInterfaceManifest {
@@ -365,4 +402,15 @@ export function listAutomationCatalog(): RecommendedAutomation[];
365
402
  export function getAutomationCatalogEntry(
366
403
  id: string,
367
404
  ): RecommendedAutomation | undefined;
405
+ /**
406
+ * Return the files a bundle entry ships, keyed by the path each takes inside
407
+ * the tarball, as an independent copy. Undefined for an entry with no bundle.
408
+ *
409
+ * The contents are inlined at build time from the repository paths
410
+ * `setup.bundle.files` names, because a host packing the tarball has the
411
+ * published package but not the repository.
412
+ */
413
+ export function getAutomationBundleFiles(
414
+ id: string,
415
+ ): Record<string, string> | undefined;
368
416
  export default AUTOMATION_CATALOG;
@@ -5,6 +5,7 @@
5
5
  * directory. `catalog-index.js` is generated from that directory so the JS
6
6
  * package can statically import each JSON file without an aggregate JSON asset.
7
7
  */
8
+ import { AUTOMATION_BUNDLE_FILES } from "./bundle-index.js";
8
9
  import { AUTOMATION_CATALOG_ENTRIES } from "./catalog-index.js";
9
10
  import interfaceManifest from "./interface.json" with { type: "json" };
10
11
 
@@ -21,6 +22,19 @@ export const getAutomationCatalogEntry = (id) => {
21
22
 
22
23
  export const AUTOMATION_CATALOG = clone(AUTOMATIONS);
23
24
 
25
+ /**
26
+ * The files a bundle entry ships, keyed by entry id and then by the path each
27
+ * file takes inside the tarball. Generated from the repository paths the
28
+ * entry's `setup.bundle.files` names, because a host packing the tarball has
29
+ * the package but not the repository.
30
+ *
31
+ * Absent for every entry that does not ship a bundle.
32
+ */
33
+ export const getAutomationBundleFiles = (id) => {
34
+ const files = AUTOMATION_BUNDLE_FILES[id];
35
+ return files ? clone(files) : undefined;
36
+ };
37
+
24
38
  /**
25
39
  * The production Automation interface manifest: the domain-level facts of the
26
40
  * interface, hand-authored in `automations/interface.json`.
@@ -144,7 +144,9 @@
144
144
  "capabilities": "/v1/capabilities",
145
145
  "validate": "/v1/validate",
146
146
  "createPrompt": "/v1/preset/prompt",
147
- "createPlugin": "/v1/preset/plugin"
147
+ "createPlugin": "/v1/preset/plugin",
148
+ "createBundle": "/v1",
149
+ "uploads": "/v1/uploads"
148
150
  },
149
151
  "featuredAutomationIds": [
150
152
  "github-pr-reviewer",
@@ -178,7 +178,9 @@
178
178
  "capabilities",
179
179
  "validate",
180
180
  "createPrompt",
181
- "createPlugin"
181
+ "createPlugin",
182
+ "createBundle",
183
+ "uploads"
182
184
  ],
183
185
  "properties": {
184
186
  "list": { "$ref": "#/$defs/plainEndpoint" },
@@ -190,7 +192,9 @@
190
192
  "capabilities": { "$ref": "#/$defs/plainEndpoint" },
191
193
  "validate": { "$ref": "#/$defs/plainEndpoint" },
192
194
  "createPrompt": { "$ref": "#/$defs/plainEndpoint" },
193
- "createPlugin": { "$ref": "#/$defs/plainEndpoint" }
195
+ "createPlugin": { "$ref": "#/$defs/plainEndpoint" },
196
+ "createBundle": { "$ref": "#/$defs/plainEndpoint" },
197
+ "uploads": { "$ref": "#/$defs/plainEndpoint" }
194
198
  }
195
199
  },
196
200
  "featuredAutomationIds": {
package/index.d.ts CHANGED
@@ -42,5 +42,5 @@ export type {
42
42
  AutomationInterfacePages,
43
43
  AutomationInterfaceRoutes,
44
44
  } from "./automations/index.js";
45
- export { SKILLS_CATALOG } from "./skills/index.js";
45
+ export { SKILLS_CATALOG, DEFAULT_ENABLED_SKILL_NAMES } from "./skills/index.js";
46
46
  export type { SkillCatalogEntry } from "./skills/index.js";
package/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { INTEGRATION_CATALOG } from "./integrations/index.js";
2
2
  export { AUTOMATION_CATALOG, AUTOMATION_INTERFACE } from "./automations/index.js";
3
- export { SKILLS_CATALOG } from "./skills/index.js";
3
+ export { SKILLS_CATALOG, DEFAULT_ENABLED_SKILL_NAMES } from "./skills/index.js";
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "appUrl": "https://posthog.com",
10
10
  "docsUrl": "https://posthog.com/docs/model-context-protocol",
11
- "notes": "Relevant for product analytics and experimentation workflows.",
11
+ "notes": "Uses PostHog's official hosted MCP server. OAuth is preferred; the bearer-token option remains for clients or deployments that require a personal API key. The OAuth URL defaults to read-only mode; remove readonly=true only when write access has been approved.",
12
12
  "popularityRank": 37,
13
13
  "iconBg": "#1D4AFF",
14
14
  "logoUrl": "https://cdn.simpleicons.org/posthog/FFFFFF",
@@ -18,8 +18,44 @@
18
18
  "product",
19
19
  "experiments"
20
20
  ],
21
- "installHint": "Authenticate with a PostHog personal API key (PostHog → User settings → API keys, MCP Server preset) — sent as a Bearer token.",
21
+ "installHint": "Prefer OAuth. The default OAuth connection uses read-only MCP mode; edit the URL or use the bearer-key fallback only when a workflow needs approved write access. For API keys, create a PostHog personal API key with the MCP Server preset.",
22
22
  "connectionOptions": [
23
+ {
24
+ "id": "oauth",
25
+ "provider": "mcp",
26
+ "auth": {
27
+ "strategy": "oauth2",
28
+ "oauth": {
29
+ "authorizationUrl": "https://oauth.posthog.com/oauth/authorize/",
30
+ "tokenUrl": "https://oauth.posthog.com/oauth/token/",
31
+ "registrationUrl": "https://oauth.posthog.com/oauth/register/",
32
+ "scopes": [
33
+ "openid",
34
+ "profile",
35
+ "email",
36
+ "organization:read",
37
+ "project:read",
38
+ "dashboard:read",
39
+ "insight:read",
40
+ "query:read",
41
+ "event_definition:read"
42
+ ],
43
+ "pkce": true,
44
+ "clientAuthentication": "none",
45
+ "additionalAuthorizationParams": {
46
+ "resource": "https://mcp.posthog.com/mcp"
47
+ },
48
+ "additionalTokenParams": {
49
+ "resource": "https://mcp.posthog.com/mcp"
50
+ }
51
+ }
52
+ },
53
+ "transport": {
54
+ "kind": "shttp",
55
+ "url": "https://mcp.posthog.com/mcp?readonly=true",
56
+ "urlEditable": true
57
+ }
58
+ },
23
59
  {
24
60
  "id": "api-key",
25
61
  "provider": "mcp",
@@ -31,7 +67,9 @@
31
67
  "strategy": "bearer",
32
68
  "credentialLabel": "PostHog personal API key",
33
69
  "credentialPlaceholder": "Paste your PostHog personal API key",
34
- "credentialHelp": "Create one under PostHog → User settings → API keys (MCP Server preset). Sent as Authorization: Bearer <token>."
70
+ "credentialHelp": "Create one under PostHog > User settings > API keys using the MCP Server preset. Sent as Authorization: Bearer <token>.",
71
+ "credentialSecretName": "POSTHOG_PERSONAL_API_KEY",
72
+ "saveCredentialAsSecretByDefault": true
35
73
  }
36
74
  }
37
75
  ]
@@ -0,0 +1,78 @@
1
+ {
2
+ "id": "sonarqube",
3
+ "name": "SonarQube",
4
+ "description": "Inspect code quality and security findings, quality gates, and analyze code snippets from SonarQube Server or Cloud.",
5
+ "categories": [
6
+ "Engineering",
7
+ "Security"
8
+ ],
9
+ "appUrl": "https://www.sonarsource.com/products/sonarqube/mcp-server/",
10
+ "docsUrl": "https://github.com/SonarSource/sonarqube-mcp-server",
11
+ "notes": "Uses SonarSource's official SonarQube MCP Server, run locally as a Docker container. Provide a SonarQube user token. Set SONARQUBE_ORG for SonarQube Cloud, or SONARQUBE_URL for a self-managed SonarQube Server (or https://sonarqube.us for SonarQube Cloud US).",
12
+ "popularityRank": 43,
13
+ "installHint": "SONARQUBE_TOKEN is always required. For SonarQube Cloud, set your organization key in SONARQUBE_ORG. For a self-managed SonarQube Server, set SONARQUBE_URL to your server URL; for SonarQube Cloud US, set SONARQUBE_URL to https://sonarqube.us. Requires a container runtime such as Docker.",
14
+ "connectionOptions": [
15
+ {
16
+ "id": "api",
17
+ "provider": "mcp",
18
+ "transport": {
19
+ "kind": "stdio",
20
+ "serverName": "sonarqube",
21
+ "command": "docker",
22
+ "args": [
23
+ "run",
24
+ "--init",
25
+ "--pull=always",
26
+ "-i",
27
+ "--rm",
28
+ "-e",
29
+ "SONARQUBE_TOKEN",
30
+ "-e",
31
+ "SONARQUBE_ORG",
32
+ "-e",
33
+ "SONARQUBE_URL",
34
+ "sonarsource/sonarqube-mcp"
35
+ ],
36
+ "envFields": [
37
+ {
38
+ "key": "SONARQUBE_TOKEN",
39
+ "label": "SonarQube token",
40
+ "type": "password",
41
+ "required": true,
42
+ "helperText": "User token generated in your SonarQube Server or Cloud account settings.",
43
+ "helperLink": "https://docs.sonarsource.com/sonarqube-cloud/managing-your-account/managing-tokens/"
44
+ },
45
+ {
46
+ "key": "SONARQUBE_ORG",
47
+ "label": "Organization key (SonarQube Cloud)",
48
+ "type": "text",
49
+ "required": false,
50
+ "placeholder": "my-org",
51
+ "helperText": "Required for SonarQube Cloud. Your organization key, found in SonarQube Cloud."
52
+ },
53
+ {
54
+ "key": "SONARQUBE_URL",
55
+ "label": "Server URL (SonarQube Server)",
56
+ "type": "text",
57
+ "required": false,
58
+ "placeholder": "https://sonarqube.example.com",
59
+ "helperText": "Required for a self-managed SonarQube Server. For SonarQube Cloud US, use https://sonarqube.us."
60
+ }
61
+ ]
62
+ },
63
+ "auth": {
64
+ "strategy": "api_key"
65
+ }
66
+ }
67
+ ],
68
+ "iconBg": "#126ED3",
69
+ "logoUrl": "https://cdn.simpleicons.org/sonarqubeserver/FFFFFF",
70
+ "keywords": [
71
+ "code quality",
72
+ "static analysis",
73
+ "security",
74
+ "sast",
75
+ "quality gate",
76
+ "sonarcloud"
77
+ ]
78
+ }
@@ -14,69 +14,70 @@ import entry8 from "./catalog/elevenlabs.json" with { type: "json" };
14
14
  import entry9 from "./catalog/bitbucket.json" with { type: "json" };
15
15
  import entry10 from "./catalog/xero.json" with { type: "json" };
16
16
  import entry11 from "./catalog/quickbooks.json" with { type: "json" };
17
- import entry12 from "./catalog/okta.json" with { type: "json" };
18
- import entry13 from "./catalog/netlify.json" with { type: "json" };
19
- import entry14 from "./catalog/vercel.json" with { type: "json" };
20
- import entry15 from "./catalog/supabase.json" with { type: "json" };
21
- import entry16 from "./catalog/posthog.json" with { type: "json" };
22
- import entry17 from "./catalog/sentry.json" with { type: "json" };
23
- import entry18 from "./catalog/datadog.json" with { type: "json" };
24
- import entry19 from "./catalog/canva.json" with { type: "json" };
25
- import entry20 from "./catalog/miro.json" with { type: "json" };
26
- import entry21 from "./catalog/webflow.json" with { type: "json" };
27
- import entry22 from "./catalog/zoom.json" with { type: "json" };
28
- import entry23 from "./catalog/discord.json" with { type: "json" };
29
- import entry24 from "./catalog/stripe.json" with { type: "json" };
30
- import entry25 from "./catalog/intercom.json" with { type: "json" };
31
- import entry26 from "./catalog/hubspot.json" with { type: "json" };
32
- import entry27 from "./catalog/salesforce.json" with { type: "json" };
33
- import entry28 from "./catalog/sharepoint.json" with { type: "json" };
34
- import entry29 from "./catalog/onedrive.json" with { type: "json" };
35
- import entry30 from "./catalog/microsoft-teams.json" with { type: "json" };
36
- import entry31 from "./catalog/microsoft-outlook.json" with { type: "json" };
37
- import entry32 from "./catalog/box.json" with { type: "json" };
38
- import entry33 from "./catalog/airtable.json" with { type: "json" };
39
- import entry34 from "./catalog/monday.json" with { type: "json" };
40
- import entry35 from "./catalog/trello.json" with { type: "json" };
41
- import entry36 from "./catalog/asana.json" with { type: "json" };
42
- import entry37 from "./catalog/confluence.json" with { type: "json" };
43
- import entry38 from "./catalog/jira.json" with { type: "json" };
44
- import entry39 from "./catalog/google-calendar.json" with { type: "json" };
45
- import entry40 from "./catalog/gmail.json" with { type: "json" };
46
- import entry41 from "./catalog/google-sheets.json" with { type: "json" };
47
- import entry42 from "./catalog/google-drive.json" with { type: "json" };
48
- import entry43 from "./catalog/figma.json" with { type: "json" };
49
- import entry44 from "./catalog/google-docs.json" with { type: "json" };
50
- import entry45 from "./catalog/apify.json" with { type: "json" };
51
- import entry46 from "./catalog/atlassian-rovo.json" with { type: "json" };
52
- import entry47 from "./catalog/brave-search.json" with { type: "json" };
53
- import entry48 from "./catalog/browser-mcp.json" with { type: "json" };
54
- import entry49 from "./catalog/clickhouse.json" with { type: "json" };
55
- import entry50 from "./catalog/cloudflare-bindings.json" with { type: "json" };
56
- import entry51 from "./catalog/cloudflare-browser-rendering.json" with { type: "json" };
57
- import entry52 from "./catalog/cloudflare-builds.json" with { type: "json" };
58
- import entry53 from "./catalog/cloudflare-docs.json" with { type: "json" };
59
- import entry54 from "./catalog/cloudflare-observability.json" with { type: "json" };
60
- import entry55 from "./catalog/deepwiki.json" with { type: "json" };
61
- import entry56 from "./catalog/everything.json" with { type: "json" };
62
- import entry57 from "./catalog/exa.json" with { type: "json" };
63
- import entry58 from "./catalog/fetch.json" with { type: "json" };
64
- import entry59 from "./catalog/filesystem.json" with { type: "json" };
65
- import entry60 from "./catalog/firecrawl.json" with { type: "json" };
66
- import entry61 from "./catalog/git.json" with { type: "json" };
67
- import entry62 from "./catalog/huggingface.json" with { type: "json" };
68
- import entry63 from "./catalog/kagi.json" with { type: "json" };
69
- import entry64 from "./catalog/memory.json" with { type: "json" };
70
- import entry65 from "./catalog/mongodb.json" with { type: "json" };
71
- import entry66 from "./catalog/neon.json" with { type: "json" };
72
- import entry67 from "./catalog/obsidian.json" with { type: "json" };
73
- import entry68 from "./catalog/paypal.json" with { type: "json" };
74
- import entry69 from "./catalog/playwright.json" with { type: "json" };
75
- import entry70 from "./catalog/redis.json" with { type: "json" };
76
- import entry71 from "./catalog/resend.json" with { type: "json" };
77
- import entry72 from "./catalog/sequential-thinking.json" with { type: "json" };
78
- import entry73 from "./catalog/superhuman-mail.json" with { type: "json" };
79
- import entry74 from "./catalog/time.json" with { type: "json" };
17
+ import entry12 from "./catalog/sonarqube.json" with { type: "json" };
18
+ import entry13 from "./catalog/okta.json" with { type: "json" };
19
+ import entry14 from "./catalog/netlify.json" with { type: "json" };
20
+ import entry15 from "./catalog/vercel.json" with { type: "json" };
21
+ import entry16 from "./catalog/supabase.json" with { type: "json" };
22
+ import entry17 from "./catalog/posthog.json" with { type: "json" };
23
+ import entry18 from "./catalog/sentry.json" with { type: "json" };
24
+ import entry19 from "./catalog/datadog.json" with { type: "json" };
25
+ import entry20 from "./catalog/canva.json" with { type: "json" };
26
+ import entry21 from "./catalog/miro.json" with { type: "json" };
27
+ import entry22 from "./catalog/webflow.json" with { type: "json" };
28
+ import entry23 from "./catalog/zoom.json" with { type: "json" };
29
+ import entry24 from "./catalog/discord.json" with { type: "json" };
30
+ import entry25 from "./catalog/stripe.json" with { type: "json" };
31
+ import entry26 from "./catalog/intercom.json" with { type: "json" };
32
+ import entry27 from "./catalog/hubspot.json" with { type: "json" };
33
+ import entry28 from "./catalog/salesforce.json" with { type: "json" };
34
+ import entry29 from "./catalog/sharepoint.json" with { type: "json" };
35
+ import entry30 from "./catalog/onedrive.json" with { type: "json" };
36
+ import entry31 from "./catalog/microsoft-teams.json" with { type: "json" };
37
+ import entry32 from "./catalog/microsoft-outlook.json" with { type: "json" };
38
+ import entry33 from "./catalog/box.json" with { type: "json" };
39
+ import entry34 from "./catalog/airtable.json" with { type: "json" };
40
+ import entry35 from "./catalog/monday.json" with { type: "json" };
41
+ import entry36 from "./catalog/trello.json" with { type: "json" };
42
+ import entry37 from "./catalog/asana.json" with { type: "json" };
43
+ import entry38 from "./catalog/confluence.json" with { type: "json" };
44
+ import entry39 from "./catalog/jira.json" with { type: "json" };
45
+ import entry40 from "./catalog/google-calendar.json" with { type: "json" };
46
+ import entry41 from "./catalog/gmail.json" with { type: "json" };
47
+ import entry42 from "./catalog/google-sheets.json" with { type: "json" };
48
+ import entry43 from "./catalog/google-drive.json" with { type: "json" };
49
+ import entry44 from "./catalog/figma.json" with { type: "json" };
50
+ import entry45 from "./catalog/google-docs.json" with { type: "json" };
51
+ import entry46 from "./catalog/apify.json" with { type: "json" };
52
+ import entry47 from "./catalog/atlassian-rovo.json" with { type: "json" };
53
+ import entry48 from "./catalog/brave-search.json" with { type: "json" };
54
+ import entry49 from "./catalog/browser-mcp.json" with { type: "json" };
55
+ import entry50 from "./catalog/clickhouse.json" with { type: "json" };
56
+ import entry51 from "./catalog/cloudflare-bindings.json" with { type: "json" };
57
+ import entry52 from "./catalog/cloudflare-browser-rendering.json" with { type: "json" };
58
+ import entry53 from "./catalog/cloudflare-builds.json" with { type: "json" };
59
+ import entry54 from "./catalog/cloudflare-docs.json" with { type: "json" };
60
+ import entry55 from "./catalog/cloudflare-observability.json" with { type: "json" };
61
+ import entry56 from "./catalog/deepwiki.json" with { type: "json" };
62
+ import entry57 from "./catalog/everything.json" with { type: "json" };
63
+ import entry58 from "./catalog/exa.json" with { type: "json" };
64
+ import entry59 from "./catalog/fetch.json" with { type: "json" };
65
+ import entry60 from "./catalog/filesystem.json" with { type: "json" };
66
+ import entry61 from "./catalog/firecrawl.json" with { type: "json" };
67
+ import entry62 from "./catalog/git.json" with { type: "json" };
68
+ import entry63 from "./catalog/huggingface.json" with { type: "json" };
69
+ import entry64 from "./catalog/kagi.json" with { type: "json" };
70
+ import entry65 from "./catalog/memory.json" with { type: "json" };
71
+ import entry66 from "./catalog/mongodb.json" with { type: "json" };
72
+ import entry67 from "./catalog/neon.json" with { type: "json" };
73
+ import entry68 from "./catalog/obsidian.json" with { type: "json" };
74
+ import entry69 from "./catalog/paypal.json" with { type: "json" };
75
+ import entry70 from "./catalog/playwright.json" with { type: "json" };
76
+ import entry71 from "./catalog/redis.json" with { type: "json" };
77
+ import entry72 from "./catalog/resend.json" with { type: "json" };
78
+ import entry73 from "./catalog/sequential-thinking.json" with { type: "json" };
79
+ import entry74 from "./catalog/superhuman-mail.json" with { type: "json" };
80
+ import entry75 from "./catalog/time.json" with { type: "json" };
80
81
 
81
82
  export const INTEGRATION_CATALOG_ENTRIES = [
82
83
  entry0,
@@ -154,4 +155,5 @@ export const INTEGRATION_CATALOG_ENTRIES = [
154
155
  entry72,
155
156
  entry73,
156
157
  entry74,
158
+ entry75,
157
159
  ];
@@ -29,6 +29,7 @@
29
29
  "source": "./skills/add-skill",
30
30
  "description": "Add (import) an OpenHands skill from a GitHub repository into the current workspace.",
31
31
  "category": "agent-authoring",
32
+ "defaultEnabled": true,
32
33
  "keywords": [
33
34
  "skill",
34
35
  "import",
@@ -41,6 +42,7 @@
41
42
  "source": "./skills/agent-memory",
42
43
  "description": "Persist and retrieve repository-specific knowledge using AGENTS.md files. Use when you want to save important information about a codebase (build commands, code style, workflows) for future sessions.",
43
44
  "category": "agent-authoring",
45
+ "defaultEnabled": true,
44
46
  "keywords": [
45
47
  "memory",
46
48
  "knowledge",
@@ -53,6 +55,7 @@
53
55
  "source": "./skills/agent-sdk-builder",
54
56
  "description": "Guided workflow for building custom AI agents using the OpenHands Software Agent SDK. Use when you want to create a new agent through an interactive interview process that gathers requirements and generates implementation plans.",
55
57
  "category": "agent-authoring",
58
+ "defaultEnabled": true,
56
59
  "keywords": [
57
60
  "agent",
58
61
  "sdk",
@@ -80,6 +83,7 @@
80
83
  "source": "./skills/openhands-automation",
81
84
  "description": "Create and manage OpenHands automations - scheduled tasks that run in sandboxes. Use the prompt preset to create automations from natural language, or manage existing automations.",
82
85
  "category": "automations",
86
+ "defaultEnabled": true,
83
87
  "keywords": [
84
88
  "automation",
85
89
  "cron",
@@ -93,6 +97,7 @@
93
97
  "source": "./skills/agent-canvas-environment",
94
98
  "description": "Work effectively inside a local Agent Canvas environment, including local agent-server auth, safe workspace hygiene, and local conversation delegation.",
95
99
  "category": "agent-authoring",
100
+ "defaultEnabled": true,
96
101
  "keywords": [
97
102
  "agent-canvas",
98
103
  "openhands",
@@ -106,6 +111,7 @@
106
111
  "source": "./skills/openhands-sdk",
107
112
  "description": "Reference skill for the OpenHands Software Agent SDK - build AI agents with custom tools, LLM configuration, conversations, sub-agent delegation, MCP integration, security, and persistence.",
108
113
  "category": "agent-authoring",
114
+ "defaultEnabled": true,
109
115
  "keywords": [
110
116
  "sdk",
111
117
  "agent",
@@ -182,6 +188,7 @@
182
188
  "source": "./skills/code-review",
183
189
  "description": "Rigorous code review focusing on data structures, simplicity, security, pragmatism, and risk/safety evaluation. Provides brutally honest, actionable feedback on pull requests or merge requests, including a risk assessment for every review. Use when reviewing code changes.",
184
190
  "category": "code-quality",
191
+ "defaultEnabled": true,
185
192
  "keywords": [
186
193
  "code-review",
187
194
  "quality",
@@ -232,6 +239,7 @@
232
239
  "source": "./skills/docker",
233
240
  "description": "Run Docker commands within a container environment, including starting the Docker daemon and managing containers. Use when building, running, or managing Docker containers and images.",
234
241
  "category": "environment",
242
+ "defaultEnabled": true,
235
243
  "keywords": [
236
244
  "docker",
237
245
  "container",
@@ -268,6 +276,7 @@
268
276
  "source": "./skills/github",
269
277
  "description": "Interact with GitHub repositories, pull requests, issues, and workflows using the GITHUB_TOKEN environment variable and GitHub CLI. Use when working with code hosted on GitHub or managing GitHub resources.",
270
278
  "category": "code-hosting",
279
+ "defaultEnabled": true,
271
280
  "keywords": [
272
281
  "github",
273
282
  "git",
@@ -444,6 +453,7 @@
444
453
  "source": "./skills/openhands-api",
445
454
  "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/.",
446
455
  "category": "agent-authoring",
456
+ "defaultEnabled": true,
447
457
  "keywords": [
448
458
  "openhands",
449
459
  "api",
@@ -534,6 +544,7 @@
534
544
  "source": "./skills/skill-creator",
535
545
  "description": "Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.",
536
546
  "category": "agent-authoring",
547
+ "defaultEnabled": true,
537
548
  "keywords": [
538
549
  "skill",
539
550
  "plugin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/extensions",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Public OpenHands extension catalogs for skills, plugins, integrations, and automation templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -160,7 +160,7 @@ runs:
160
160
  # so we enforce the time limit via the coreutils timeout command.
161
161
  TIMEOUT_SECONDS=$((TIMEOUT_MINUTES * 60))
162
162
  timeout "${TIMEOUT_SECONDS}" \
163
- uv run --no-project --with openhands-sdk --with openhands-tools --with 'lmnr<0.7.53' \
163
+ uv run --no-project --with openhands-sdk --with openhands-tools --with lmnr \
164
164
  python ../extensions/plugins/qa-changes/scripts/agent_script.py
165
165
 
166
166
  - name: Upload logs as artifact
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-extensions"
3
- version = "0.16.0"
3
+ version = "0.17.0"
4
4
  description = "OpenHands extensions, plugins, and skills (Python bindings for the integration catalog)"
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -18,7 +18,7 @@ from importlib.metadata import PackageNotFoundError, version
18
18
  #: Kept in lock-step with pyproject.toml/package.json by the version test.
19
19
  #: release-please only rewrites a semver found on the annotated line itself, so
20
20
  #: the annotation must stay on the assignment; on its own line it is a no-op.
21
- _FALLBACK_VERSION = "0.16.0" # x-release-please-version
21
+ _FALLBACK_VERSION = "0.17.0" # x-release-please-version
22
22
 
23
23
  try:
24
24
  __version__: str = version("openhands-extensions")
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  const root = process.cwd();
5
5
  const catalogDir = path.join(root, "automations", "catalog");
6
6
  const outputPath = path.join(root, "automations", "catalog-index.js");
7
+ const bundleOutputPath = path.join(root, "automations", "bundle-index.js");
7
8
 
8
9
  const directories = (await readdir(catalogDir, { withFileTypes: true }))
9
10
  .filter((entry) => entry.isDirectory())
@@ -37,6 +38,25 @@ const imports = indexedAutomations
37
38
  .join("\n");
38
39
  const entries = indexedAutomations.map(({ importName }) => ` ${importName},`).join("\n");
39
40
 
41
+ // A bundle entry names its files by repository-relative path, so the one copy
42
+ // of a script shipped by both a skill and the catalog stays in one place. The
43
+ // package cannot ship a path, though - a browser host has no repository - so
44
+ // the contents are inlined here at build time.
45
+ const bundles = {};
46
+ for (const { entry } of automations) {
47
+ const files = entry.setup?.bundle?.files;
48
+ if (!files) continue;
49
+ bundles[entry.id] = {};
50
+ for (const [packedPath, source] of Object.entries(files).sort()) {
51
+ if (!/^(skills|automations)\//.test(source) || source.includes("..")) {
52
+ throw new Error(
53
+ `${entry.id}: bundle file "${packedPath}" names ${source}, which is outside skills/ or automations/`,
54
+ );
55
+ }
56
+ bundles[entry.id][packedPath] = await readFile(path.join(root, source), "utf8");
57
+ }
58
+ }
59
+
40
60
  const header = `// This file is auto-generated by scripts/build-automation-catalog.mjs.
41
61
  // Do not edit it manually. To update it after changing automations/catalog/*/manifest.json,
42
62
  // run: npm run build:automations
@@ -49,3 +69,13 @@ ${entries}
49
69
  ];
50
70
  `;
51
71
  await writeFile(outputPath, body);
72
+
73
+ const bundleHeader = `// This file is auto-generated by scripts/build-automation-catalog.mjs.
74
+ // Do not edit it manually. It inlines the files each bundle entry ships, read from
75
+ // the repository paths its manifest names. To update it, run: npm run build:automations
76
+
77
+ `;
78
+ await writeFile(
79
+ bundleOutputPath,
80
+ `${bundleHeader}export const AUTOMATION_BUNDLE_FILES = ${JSON.stringify(bundles, null, 2)};\n`,
81
+ );