@openhands/extensions 0.16.0 → 0.18.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 (70) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/AGENTS.md +1 -0
  3. package/README.md +5 -2
  4. package/automations/README.md +78 -11
  5. package/automations/bundle-index.js +18 -0
  6. package/automations/catalog/github-agents-md-maintainer/manifest.json +96 -0
  7. package/automations/catalog/github-issue-to-pr/manifest.json +107 -0
  8. package/automations/catalog/github-pr-reviewer/manifest.json +24 -7
  9. package/automations/catalog/github-repo-monitor/manifest.json +29 -34
  10. package/automations/catalog/jira-issue-to-pr/manifest.json +3 -3
  11. package/automations/catalog/news-digest/manifest.json +76 -0
  12. package/automations/catalog-index.js +13 -7
  13. package/automations/catalog.schema.json +112 -3
  14. package/automations/index.d.ts +66 -1
  15. package/automations/index.js +14 -0
  16. package/automations/interface.json +6 -2
  17. package/automations/interface.schema.json +6 -2
  18. package/index.d.ts +1 -1
  19. package/index.js +1 -1
  20. package/integrations/catalog/posthog.json +41 -3
  21. package/integrations/catalog/sonarqube.json +78 -0
  22. package/integrations/catalog-index.js +65 -63
  23. package/marketplaces/openhands-extensions.json +49 -0
  24. package/package.json +1 -1
  25. package/plugins/qa-changes/action.yml +1 -1
  26. package/pyproject.toml +1 -1
  27. package/python/openhands_extensions/_version.py +1 -1
  28. package/scripts/build-automation-catalog.mjs +30 -0
  29. package/scripts/build-skills-catalog.mjs +24 -7
  30. package/skills/add-skill/SKILL.md +1 -1
  31. package/skills/github-agents-md-maintainer/.plugin/plugin.json +19 -0
  32. package/skills/github-agents-md-maintainer/README.md +59 -0
  33. package/skills/github-agents-md-maintainer/SKILL.md +271 -0
  34. package/skills/github-agents-md-maintainer/commands/agents-md-setup.md +8 -0
  35. package/skills/github-agents-md-maintainer/references/state-schema.md +113 -0
  36. package/skills/github-agents-md-maintainer/scripts/main.py +1187 -0
  37. package/skills/github-issue-to-pr/.plugin/plugin.json +19 -0
  38. package/skills/github-issue-to-pr/README.md +84 -0
  39. package/skills/github-issue-to-pr/SKILL.md +373 -0
  40. package/skills/github-issue-to-pr/commands/issue-to-pr-setup.md +8 -0
  41. package/skills/github-issue-to-pr/references/state-schema.md +139 -0
  42. package/skills/github-issue-to-pr/scripts/main.py +1305 -0
  43. package/skills/github-pr-reviewer/README.md +12 -6
  44. package/skills/github-pr-reviewer/SKILL.md +93 -31
  45. package/skills/github-pr-reviewer/commands/pr-reviewer-setup.md +1 -1
  46. package/skills/github-pr-reviewer/references/state-schema.md +68 -18
  47. package/skills/github-pr-reviewer/scripts/main.py +540 -103
  48. package/skills/github-pr-reviewer/tests/test_main.py +503 -0
  49. package/skills/index.d.ts +6 -0
  50. package/skills/index.js +56 -17
  51. package/skills/jira-issue-to-pr/scripts/main.py +4 -2
  52. package/skills/news-digest/.plugin/plugin.json +19 -0
  53. package/skills/news-digest/README.md +71 -0
  54. package/skills/news-digest/SKILL.md +284 -0
  55. package/skills/news-digest/commands/news-digest-setup.md +8 -0
  56. package/skills/news-digest/references/state-schema.md +178 -0
  57. package/skills/news-digest/scripts/main.py +1250 -0
  58. package/skills/openhands-sdk/SKILL.md +2 -0
  59. package/skills/skill-creator/SKILL.md +1 -1
  60. package/skills/slack-channel-monitor/scripts/main.py +3 -2
  61. package/tests/fixtures/automations/capabilities.json +30 -0
  62. package/tests/fixtures/automations/github-pr-reviewer.json +319 -55
  63. package/tests/fixtures/automations/github-repo-monitor.json +230 -62
  64. package/tests/test_automation_setup.py +260 -31
  65. package/tests/test_catalogs.py +46 -1
  66. package/tests/test_github_agents_md_maintainer.py +352 -0
  67. package/tests/test_github_issue_to_pr.py +539 -0
  68. package/tests/test_news_digest.py +646 -0
  69. package/tests/test_skills_catalog.py +182 -36
  70. package/tests/test_slack_channel_monitor.py +1 -1
@@ -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",
@@ -670,6 +681,32 @@
670
681
  "official"
671
682
  ]
672
683
  },
684
+ {
685
+ "name": "github-agents-md-maintainer",
686
+ "source": "./skills/github-agents-md-maintainer",
687
+ "description": "Create an automation that keeps AGENTS.md current in one or more GitHub repositories. On a schedule an agent reads the repository, creates or updates AGENTS.md, and opens a pull request - staying quiet while one of its own pull requests is still open.",
688
+ "category": "automations",
689
+ "keywords": [
690
+ "github",
691
+ "agents-md",
692
+ "documentation",
693
+ "maintenance",
694
+ "automation"
695
+ ]
696
+ },
697
+ {
698
+ "name": "github-issue-to-pr",
699
+ "source": "./skills/github-issue-to-pr",
700
+ "description": "Create an automation that implements GitHub issues when a configurable trigger label is applied. Clones the default branch for the agent, then commits, pushes, and opens the pull request itself - the agent is handed no GitHub credential.",
701
+ "category": "automations",
702
+ "keywords": [
703
+ "github",
704
+ "issue",
705
+ "pull-request",
706
+ "automation",
707
+ "integration"
708
+ ]
709
+ },
673
710
  {
674
711
  "name": "github-pr-reviewer",
675
712
  "source": "./skills/github-pr-reviewer",
@@ -770,6 +807,18 @@
770
807
  "jira",
771
808
  "atlassian"
772
809
  ]
810
+ },
811
+ {
812
+ "name": "news-digest",
813
+ "source": "./skills/news-digest",
814
+ "description": "Create an automation that reads public RSS and Atom feeds on a schedule, keeps what is new and matches the configured topics, and has an agent write a short digest of it. Needs no credentials.",
815
+ "category": "automations",
816
+ "keywords": [
817
+ "news",
818
+ "rss",
819
+ "atom",
820
+ "digest"
821
+ ]
773
822
  }
774
823
  ]
775
824
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/extensions",
3
- "version": "0.16.0",
3
+ "version": "0.18.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.18.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.18.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
+ );
@@ -36,8 +36,8 @@ export const SKILL_CATEGORY_IDS = [
36
36
 
37
37
  const FALLBACK_CATEGORY = "other";
38
38
 
39
- /** Build a `skill directory name -> {category, file}` map from every manifest. */
40
- export function buildCategoryMap(marketplacesDir) {
39
+ /** Build a `skill directory name -> {category, defaultEnabled, file}` map from every manifest. */
40
+ export function buildEntryMap(marketplacesDir) {
41
41
  const map = new Map();
42
42
 
43
43
  for (const filename of readdirSync(marketplacesDir).filter((f) => f.endsWith(".json")).sort()) {
@@ -56,14 +56,27 @@ export function buildCategoryMap(marketplacesDir) {
56
56
  );
57
57
  }
58
58
 
59
+ if ("defaultEnabled" in entry && typeof entry.defaultEnabled !== "boolean") {
60
+ throw new Error(
61
+ `${filename}: skill "${name}" has defaultEnabled ${JSON.stringify(entry.defaultEnabled)}, expected a boolean`,
62
+ );
63
+ }
64
+
65
+ const defaultEnabled = entry.defaultEnabled === true;
66
+
59
67
  const existing = map.get(name);
60
68
  if (existing && existing.category !== category) {
61
69
  throw new Error(
62
70
  `Conflicting categories for skill "${name}": ${existing.file} says "${existing.category}", ${filename} says "${category}"`,
63
71
  );
64
72
  }
73
+ if (existing && existing.defaultEnabled !== defaultEnabled) {
74
+ throw new Error(
75
+ `Conflicting defaultEnabled for skill "${name}": ${existing.file} says ${existing.defaultEnabled}, ${filename} says ${defaultEnabled}`,
76
+ );
77
+ }
65
78
 
66
- map.set(name, { category, file: filename });
79
+ map.set(name, { category, defaultEnabled, file: filename });
67
80
  }
68
81
  }
69
82
 
@@ -114,7 +127,7 @@ export function parseFrontmatter(raw) {
114
127
  */
115
128
  export function buildCatalog(skillsDir, marketplacesDir = MARKETPLACES_DIR) {
116
129
  const entries = [];
117
- const categories = buildCategoryMap(marketplacesDir);
130
+ const manifestEntries = buildEntryMap(marketplacesDir);
118
131
  const uncategorized = [];
119
132
 
120
133
  for (const dir of readdirSync(skillsDir).sort()) {
@@ -133,7 +146,7 @@ export function buildCatalog(skillsDir, marketplacesDir = MARKETPLACES_DIR) {
133
146
  const fm = parseFrontmatter(parts[1]);
134
147
  const body = parts.slice(2).join("---").trim();
135
148
 
136
- const mapped = categories.get(dir);
149
+ const mapped = manifestEntries.get(dir);
137
150
  if (!mapped) uncategorized.push(dir);
138
151
 
139
152
  entries.push({
@@ -142,6 +155,8 @@ export function buildCatalog(skillsDir, marketplacesDir = MARKETPLACES_DIR) {
142
155
  triggers: fm.triggers,
143
156
  content: body,
144
157
  category: mapped?.category ?? FALLBACK_CATEGORY,
158
+ // Only when true, so off-by-default has one shape.
159
+ ...(mapped?.defaultEnabled ? { defaultEnabled: true } : {}),
145
160
  ...(fm.license ? { license: fm.license } : {}),
146
161
  ...(fm.compatibility ? { compatibility: fm.compatibility } : {}),
147
162
  });
@@ -160,14 +175,16 @@ export function buildCatalog(skillsDir, marketplacesDir = MARKETPLACES_DIR) {
160
175
  const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
161
176
  if (isMain) {
162
177
  const entries = buildCatalog(SKILLS_DIR);
178
+ const defaultEnabledNames = entries.filter((e) => e.defaultEnabled).map((e) => e.name);
163
179
 
164
180
  const source = `// Auto-generated by scripts/build-skills-catalog.mjs — do not edit.
165
- // Source of truth: skills/*/SKILL.md and marketplaces/*.json (category)
181
+ // Source of truth: skills/*/SKILL.md and marketplaces/*.json (category, defaultEnabled)
166
182
  export const SKILL_CATEGORY_IDS = ${JSON.stringify(SKILL_CATEGORY_IDS)};
167
183
  export const SKILLS_CATALOG = ${JSON.stringify(entries, null, 2)};
184
+ export const DEFAULT_ENABLED_SKILL_NAMES = ${JSON.stringify(defaultEnabledNames)};
168
185
  export default SKILLS_CATALOG;
169
186
  `;
170
187
 
171
188
  writeFileSync(OUTPUT, source);
172
- console.log(`Generated ${OUTPUT} with ${entries.length} skills`);
189
+ console.log(`Generated ${OUTPUT} with ${entries.length} skills (${defaultEnabledNames.length} default-enabled)`);
173
190
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: add-skill
3
- description: Add an external skill from a GitHub repository to the current workspace. Use when users want to import, install, or add a skill from a GitHub URL (e.g., `/add-skill https://github.com/OpenHands/extensions/tree/main/skills/codereview` or "add the codereview skill from https://github.com/OpenHands/extensions/"). Handles fetching the skill files and placing them in .agents/skills/.
3
+ description: Import an existing skill from a GitHub repository URL into the current workspace. Use only when the user provides or references a GitHub URL/repo to fetch from (e.g., `/add-skill https://github.com/OpenHands/extensions/tree/main/skills/codereview` or "add the codereview skill from https://github.com/OpenHands/extensions/"). Handles fetching the skill files and placing them in .agents/skills/. This does not author new skills — to create a new skill from scratch (no source URL), use the skill-creator skill instead.
4
4
  ---
5
5
 
6
6
  # Add Skill
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "github-agents-md-maintainer",
3
+ "version": "1.0.0",
4
+ "description": "Create an automation that keeps AGENTS.md current in one or more GitHub repositories. On a schedule it clones the default branch, has an agent read the repository and create or update AGENTS.md, and opens a pull request - staying quiet while one of its own pull requests is still open.",
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
+ "github",
14
+ "agents-md",
15
+ "documentation",
16
+ "maintenance",
17
+ "automation"
18
+ ]
19
+ }
@@ -0,0 +1,59 @@
1
+ # AGENTS.md Maintainer
2
+
3
+ Create an automation that keeps `AGENTS.md` current in one or more GitHub
4
+ repositories, on a schedule.
5
+
6
+ ## Trigger
7
+
8
+ This skill is activated by:
9
+
10
+ - `/agents-md:setup`
11
+
12
+ ## Features
13
+
14
+ - Creates `AGENTS.md` when it is missing, updates it when the repository has
15
+ moved on, and leaves it alone when it is accurate
16
+ - Runs weekly by default, keyed by ISO week, so extra runs inside a week are
17
+ harmless
18
+ - Stays quiet while one of its own pull requests is still open, instead of
19
+ stacking a pull request per week over the same file
20
+ - Maintains several repositories from one automation, each with its own state
21
+ - Clones the default branch for the agent and removes the clone when the run ends
22
+ - Lets the agent open the pull request, and opens it in Python when the agent
23
+ did not - a failed push or a dead conversation never loses the work
24
+ - Opens draft pull requests by default, titled `docs: update AGENTS.md` or
25
+ `docs: add AGENTS.md`
26
+
27
+ ## What the agent is asked to do
28
+
29
+ Read the repository - layout, the build, test, lint and format commands as they
30
+ are actually defined, language and framework versions, contributing docs - and
31
+ edit `AGENTS.md` to match. It is told to treat an existing file as someone
32
+ else's writing: correct what is wrong, add what is missing, remove what no
33
+ longer exists, and leave the rest alone. Only knowledge that helps in most
34
+ future tasks belongs there, which is the rule the `agent-memory` skill sets out;
35
+ task-specific notes and unverified commands do not.
36
+
37
+ ## Prerequisites
38
+
39
+ Set `GITHUB_PERSONAL_ACCESS_TOKEN` in OpenHands Settings -> Secrets, able to
40
+ read the repositories, **write contents** (to push the branch) and **write pull
41
+ requests**. No issue permission is needed - this automation never comments on an
42
+ issue.
43
+
44
+ The automation runtime must have `git` available.
45
+
46
+ ## Quick Start
47
+
48
+ Ask OpenHands:
49
+
50
+ > "Set up an AGENTS.md maintenance automation for `myorg/backend` and
51
+ > `myorg/frontend`, weekly."
52
+
53
+ ## See Also
54
+
55
+ - [SKILL.md](SKILL.md) - Full setup workflow reference
56
+ - [references/state-schema.md](references/state-schema.md) - State document and
57
+ task lifecycle
58
+ - [../agent-memory/SKILL.md](../agent-memory/SKILL.md) - What belongs in an
59
+ AGENTS.md, and what does not