@sweny-ai/core 0.1.86 → 0.1.88

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.
@@ -12,4 +12,4 @@ export type FileConfig = Record<string, string | string[] | Record<string, unkno
12
12
  */
13
13
  export declare function loadConfigFile(cwd?: string): FileConfig;
14
14
  /** Starter config written by `sweny new`. */
15
- export declare const STARTER_CONFIG = "# .sweny.yml \u2014 SWEny project configuration\n# Commit this file. Secrets (API keys, tokens) go in .env (gitignored).\n#\n# Every key matches a CLI flag: \"time-range: 4h\" is the same as \"--time-range 4h\".\n# CLI flags override this file; env vars override this file; this file overrides defaults.\n\n# \u2500\u2500 Providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file\n# issue-tracker-provider: github-issues # github-issues | linear | jira\n# source-control-provider: github # github | gitlab\n# coding-agent-provider: claude # claude | codex | gemini\n# notification-provider: console # console | slack | teams | discord | email | webhook\n\n# \u2500\u2500 Investigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# time-range: 24h\n# severity-focus: errors\n# service-filter: \"*\"\n# investigation-depth: standard # quick | standard | thorough\n\n# \u2500\u2500 PR / branch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# base-branch: main\n# pr-labels: agent,triage,needs-review\n\n# \u2500\u2500 Paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# service-map-path: .github/service-map.yml\n# log-file: ./logs/errors.json # required when observability-provider is \"file\"\n\n# \u2500\u2500 Cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# cache-dir: .sweny/cache\n# cache-ttl: 86400\n\n# \u2500\u2500 Cloud reporting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Opt-in: paste your project token from cloud.sweny.ai to enable run reporting.\n# cloud-token: sweny_pk_...\n# Or set SWENY_CLOUD_TOKEN in your environment.\n\n# \u2500\u2500 MCP servers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Extend the coding agent with additional tools via MCP.\n# Value is a JSON object \u2014 each key is a server name you choose.\n# See docs/mcp-servers.md for a full catalog with copy-paste configs.\n#\n# Example: GitHub MCP server (query PRs, issues, CI run logs)\n# mcp-servers-json: '{\"github\":{\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-github@latest\"],\"env\":{\"GITHUB_PERSONAL_ACCESS_TOKEN\":\"ghp_...\"}}}'\n\n# \u2500\u2500 Local-only quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Uncomment to run without any external services (just an LLM API key):\n# observability-provider: file\n# log-file: ./sample-errors.json\n# issue-tracker-provider: file\n# source-control-provider: file\n# notification-provider: file\n# output-dir: .sweny/output\n\n# \u2500\u2500 Credentials (.env) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Copy the relevant block into your .env file and fill in the values.\n#\n# Claude (coding agent) \u2014 https://console.anthropic.com/settings/api-keys\n# ANTHROPIC_API_KEY=sk-ant-...\n#\n# GitHub (source control + issue tracker)\n# GITHUB_TOKEN=ghp_... # https://github.com/settings/tokens (repo + issues scopes)\n#\n# Datadog (observability) \u2014 https://app.datadoghq.com/organization-settings\n# DD_API_KEY=... # Organization Settings > API Keys\n# DD_APP_KEY=... # Organization Settings > Application Keys\n# DD_SITE=datadoghq.com # or datadoghq.eu, us3.datadoghq.com, etc.\n#\n# Sentry (observability) \u2014 https://sentry.io/settings/auth-tokens/\n# SENTRY_AUTH_TOKEN=sntrys_...\n# SENTRY_ORG=your-org-slug # from sentry.io/organizations/<slug>/\n# SENTRY_PROJECT=your-project # Project Settings > General > Project Slug\n#\n# Linear (issue tracker) \u2014 https://linear.app/settings/api\n# LINEAR_API_KEY=lin_api_...\n# LINEAR_TEAM_ID=... # Settings > Workspace > Teams > [team] > copy ID from URL\n# LINEAR_BUG_LABEL_ID=... # Settings > Labels > [label] > copy ID from URL\n#\n# Jira (issue tracker) \u2014 https://your-org.atlassian.net\n# JIRA_BASE_URL=https://your-org.atlassian.net\n# JIRA_EMAIL=you@company.com # your Atlassian account email\n# JIRA_API_TOKEN=... # https://id.atlassian.com/manage-profile/security/api-tokens\n#\n# Vercel (observability) \u2014 https://vercel.com/account/tokens\n# VERCEL_TOKEN=...\n# VERCEL_PROJECT_ID=prj_... # Project Settings > General > Project ID\n# VERCEL_TEAM_ID=team_... # optional, for team-owned projects\n#\n# Supabase (observability) \u2014 https://supabase.com/dashboard/account/tokens\n# SUPABASE_MANAGEMENT_KEY=...\n# SUPABASE_PROJECT_REF=... # Project Settings > General > Reference ID\n#\n# Netlify (observability) \u2014 https://app.netlify.com/user/applications#personal-access-tokens\n# NETLIFY_TOKEN=...\n# NETLIFY_SITE_ID=... # Site Settings > General > Site ID\n#\n# Fly.io (observability) \u2014 https://fly.io/user/personal_access_tokens\n# FLY_TOKEN=...\n# FLY_APP_NAME=... # the name of your Fly.io application\n#\n# Render (observability) \u2014 https://dashboard.render.com/u/settings\n# RENDER_API_KEY=...\n# RENDER_SERVICE_ID=srv-... # from your service's Settings page\n#\n# Prometheus (observability) \u2014 self-hosted or Grafana Cloud\n# PROMETHEUS_URL=http://prometheus.internal:9090\n# PROMETHEUS_TOKEN=... # optional, for secured instances\n#\n# PagerDuty (observability) \u2014 https://your-account.pagerduty.com/api_keys\n# PAGERDUTY_API_KEY=...\n#\n# Honeycomb (observability) \u2014 https://docs.honeycomb.io/api/\n# HONEYCOMB_API_KEY=...\n# HONEYCOMB_DATASET=... # dataset name (e.g. production)\n#\n# Heroku (observability) \u2014 https://devcenter.heroku.com/articles/platform-api-reference\n# HEROKU_API_KEY=... # https://dashboard.heroku.com/account\n# HEROKU_APP_NAME=... # the name of your Heroku application\n#\n# OpsGenie (observability) \u2014 https://support.atlassian.com/opsgenie/docs/api-key-management/\n# OPSGENIE_API_KEY=...\n# OPSGENIE_REGION=us # or eu for EU-hosted accounts\n#\n# Slack (notifications) \u2014 https://api.slack.com/apps\n# NOTIFICATION_WEBHOOK_URL=https://hooks.slack.com/services/...\n# # or use a bot token: SLACK_BOT_TOKEN=xoxb-...\n";
15
+ export declare const STARTER_CONFIG = "# .sweny.yml \u2014 SWEny project configuration\n# Commit this file. Secrets (API keys, tokens) go in .env (gitignored).\n#\n# Every key matches a CLI flag: \"time-range: 4h\" is the same as \"--time-range 4h\".\n# CLI flags override this file; env vars override this file; this file overrides defaults.\n\n# \u2500\u2500 Providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file\n# issue-tracker-provider: github-issues # github-issues | linear | jira\n# source-control-provider: github # github | gitlab\n# coding-agent-provider: claude # claude | codex | gemini\n# notification-provider: console # console | slack | teams | discord | email | webhook\n\n# \u2500\u2500 Investigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# time-range: 24h\n# severity-focus: errors\n# service-filter: \"*\"\n# investigation-depth: standard # quick | standard | thorough\n\n# \u2500\u2500 PR / branch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# base-branch: main\n# pr-labels: agent,triage,needs-review\n\n# \u2500\u2500 Paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# service-map-path: .github/service-map.yml\n# log-file: ./logs/errors.json # required when observability-provider is \"file\"\n\n# \u2500\u2500 Cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# cache-dir: .sweny/cache\n# cache-ttl: 86400\n\n# \u2500\u2500 Cloud reporting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Opt-in: paste your project token from cloud.sweny.ai to enable run reporting.\n# cloud-token: sweny_pk_...\n# Or set SWENY_CLOUD_TOKEN in your environment.\n\n# \u2500\u2500 MCP servers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Extend the coding agent with additional tools via MCP.\n# Value is a JSON object \u2014 each key is a server name you choose.\n# See https://docs.sweny.ai/advanced/mcp-servers/ for a full catalog with copy-paste configs.\n#\n# Example: GitHub MCP server (query PRs, issues, CI run logs)\n# mcp-servers-json: '{\"github\":{\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-github@latest\"],\"env\":{\"GITHUB_PERSONAL_ACCESS_TOKEN\":\"ghp_...\"}}}'\n\n# \u2500\u2500 Local-only quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Uncomment to run without any external services (just an LLM API key):\n# observability-provider: file\n# log-file: ./sample-errors.json\n# issue-tracker-provider: file\n# source-control-provider: file\n# notification-provider: file\n# output-dir: .sweny/output\n\n# \u2500\u2500 Credentials (.env) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Copy the relevant block into your .env file and fill in the values.\n#\n# Claude (coding agent) \u2014 https://console.anthropic.com/settings/api-keys\n# ANTHROPIC_API_KEY=sk-ant-...\n#\n# GitHub (source control + issue tracker)\n# GITHUB_TOKEN=ghp_... # https://github.com/settings/tokens (repo + issues scopes)\n#\n# Datadog (observability) \u2014 https://app.datadoghq.com/organization-settings\n# DD_API_KEY=... # Organization Settings > API Keys\n# DD_APP_KEY=... # Organization Settings > Application Keys\n# DD_SITE=datadoghq.com # or datadoghq.eu, us3.datadoghq.com, etc.\n#\n# Sentry (observability) \u2014 https://sentry.io/settings/auth-tokens/\n# SENTRY_AUTH_TOKEN=sntrys_...\n# SENTRY_ORG=your-org-slug # from sentry.io/organizations/<slug>/\n# SENTRY_PROJECT=your-project # Project Settings > General > Project Slug\n#\n# Linear (issue tracker) \u2014 https://linear.app/settings/api\n# LINEAR_API_KEY=lin_api_...\n# LINEAR_TEAM_ID=... # Settings > Workspace > Teams > [team] > copy ID from URL\n# LINEAR_BUG_LABEL_ID=... # Settings > Labels > [label] > copy ID from URL\n#\n# Jira (issue tracker) \u2014 https://your-org.atlassian.net\n# JIRA_BASE_URL=https://your-org.atlassian.net\n# JIRA_EMAIL=you@company.com # your Atlassian account email\n# JIRA_API_TOKEN=... # https://id.atlassian.com/manage-profile/security/api-tokens\n#\n# Vercel (observability) \u2014 https://vercel.com/account/tokens\n# VERCEL_TOKEN=...\n# VERCEL_PROJECT_ID=prj_... # Project Settings > General > Project ID\n# VERCEL_TEAM_ID=team_... # optional, for team-owned projects\n#\n# Supabase (observability) \u2014 https://supabase.com/dashboard/account/tokens\n# SUPABASE_MANAGEMENT_KEY=...\n# SUPABASE_PROJECT_REF=... # Project Settings > General > Reference ID\n#\n# Netlify (observability) \u2014 https://app.netlify.com/user/applications#personal-access-tokens\n# NETLIFY_TOKEN=...\n# NETLIFY_SITE_ID=... # Site Settings > General > Site ID\n#\n# Fly.io (observability) \u2014 https://fly.io/user/personal_access_tokens\n# FLY_TOKEN=...\n# FLY_APP_NAME=... # the name of your Fly.io application\n#\n# Render (observability) \u2014 https://dashboard.render.com/u/settings\n# RENDER_API_KEY=...\n# RENDER_SERVICE_ID=srv-... # from your service's Settings page\n#\n# Prometheus (observability) \u2014 self-hosted or Grafana Cloud\n# PROMETHEUS_URL=http://prometheus.internal:9090\n# PROMETHEUS_TOKEN=... # optional, for secured instances\n#\n# PagerDuty (observability) \u2014 https://your-account.pagerduty.com/api_keys\n# PAGERDUTY_API_KEY=...\n#\n# Honeycomb (observability) \u2014 https://docs.honeycomb.io/api/\n# HONEYCOMB_API_KEY=...\n# HONEYCOMB_DATASET=... # dataset name (e.g. production)\n#\n# Heroku (observability) \u2014 https://devcenter.heroku.com/articles/platform-api-reference\n# HEROKU_API_KEY=... # https://dashboard.heroku.com/account\n# HEROKU_APP_NAME=... # the name of your Heroku application\n#\n# OpsGenie (observability) \u2014 https://support.atlassian.com/opsgenie/docs/api-key-management/\n# OPSGENIE_API_KEY=...\n# OPSGENIE_REGION=us # or eu for EU-hosted accounts\n#\n# Slack (notifications) \u2014 https://api.slack.com/apps\n# NOTIFICATION_WEBHOOK_URL=https://hooks.slack.com/services/...\n# # or use a bot token: SLACK_BOT_TOKEN=xoxb-...\n";
@@ -139,7 +139,7 @@ export const STARTER_CONFIG = `# .sweny.yml — SWEny project configuration
139
139
  # ── MCP servers ───────────────────────────────────────────────────────
140
140
  # Extend the coding agent with additional tools via MCP.
141
141
  # Value is a JSON object — each key is a server name you choose.
142
- # See docs/mcp-servers.md for a full catalog with copy-paste configs.
142
+ # See https://docs.sweny.ai/advanced/mcp-servers/ for a full catalog with copy-paste configs.
143
143
  #
144
144
  # Example: GitHub MCP server (query PRs, issues, CI run logs)
145
145
  # mcp-servers-json: '{"github":{"type":"stdio","command":"npx","args":["-y","@modelcontextprotocol/server-github@latest"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"ghp_..."}}}'
package/dist/cli/main.js CHANGED
@@ -30,6 +30,7 @@ import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman,
30
30
  import { checkProviderConnectivity } from "./check.js";
31
31
  import { registerSetupCommand } from "./setup.js";
32
32
  import { registerPublishCommand } from "./publish.js";
33
+ import { registerSkillCommand } from "./skill.js";
33
34
  import { reportToCloud } from "./cloud-report.js";
34
35
  import { runUpgrade, fetchLatestFromNpm } from "./upgrade.js";
35
36
  import { maybeNudge, defaultCachePath } from "./version-check.js";
@@ -101,6 +102,7 @@ program
101
102
  });
102
103
  registerSetupCommand(program);
103
104
  registerPublishCommand(program);
105
+ registerSkillCommand(program);
104
106
  // ── Credential map builder ──────────────────────────────────────────
105
107
  // buildCredentialMap lives in ./credentials.ts so tests can import it
106
108
  // without triggering main.ts's top-level program.parse() side effect.
@@ -601,15 +603,21 @@ export async function workflowRunAction(file, options) {
601
603
  console.error(chalk.red(`\n Workflow cannot run — missing required skills:\n`));
602
604
  for (const err of validation.errors)
603
605
  console.error(chalk.red(` \u2717 ${err}`));
604
- if (validation.missing.length > 0) {
605
- console.error(chalk.dim(`\n Set the missing env vars and try again:`));
606
- for (const m of validation.missing) {
607
- if (m.missingEnv.length > 0) {
608
- console.error(chalk.dim(` - ${m.id}: ${m.missingEnv.join(", ")}`));
609
- }
606
+ const unknown = validation.missing.filter((m) => m.category === "unknown");
607
+ const envGaps = validation.missing.filter((m) => m.category !== "unknown" && m.missingEnv.length > 0);
608
+ if (unknown.length > 0) {
609
+ console.error(chalk.dim(`\n These skill IDs aren't built-in or discovered in .{claude,sweny,agents,gemini}/skills/.\n Scaffold one with:\n`));
610
+ for (const m of unknown) {
611
+ console.error(chalk.dim(` sweny skill new ${m.id}`));
612
+ }
613
+ }
614
+ if (envGaps.length > 0) {
615
+ console.error(chalk.dim(`\n These skills are built-in but their env vars aren't set:`));
616
+ for (const m of envGaps) {
617
+ console.error(chalk.dim(` - ${m.id}: ${m.missingEnv.join(", ")}`));
610
618
  }
611
619
  }
612
- console.error("");
620
+ console.error(chalk.dim(`\n Run \`sweny skill list\` to see what's available.\n`));
613
621
  process.exit(1);
614
622
  return;
615
623
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `sweny skill` command group — discover, list, and scaffold skills.
3
+ *
4
+ * Skills are the unit of capability a workflow node names in its `skills`
5
+ * array. Sweny ships built-ins (github, linear, slack, ...) and discovers
6
+ * custom skills from `.{gemini,agents,claude,sweny}/skills/<id>/SKILL.md`.
7
+ *
8
+ * This module owns the *authoring* side of that loop:
9
+ *
10
+ * sweny skill new <id> scaffold a SKILL.md template
11
+ * sweny skill list list built-in + custom skills together
12
+ *
13
+ * Discovery and validation already live in `../skills/custom-loader.js`
14
+ * and `../skills/index.js`; this CLI is a thin authoring shell over them.
15
+ */
16
+ import { Command } from "commander";
17
+ declare const VALID_CATEGORIES: readonly ["general", "git", "tasks", "notification", "observability", "data"];
18
+ type SkillCategory = (typeof VALID_CATEGORIES)[number];
19
+ /** Build the SKILL.md body for a fresh scaffold. Exported for tests. */
20
+ export declare function renderSkillTemplate(opts: {
21
+ id: string;
22
+ description: string;
23
+ category: SkillCategory;
24
+ }): string;
25
+ /** Register the `sweny skill` subcommand group on the parent program. */
26
+ export declare function registerSkillCommand(program: Command): void;
27
+ export {};
@@ -0,0 +1,200 @@
1
+ /**
2
+ * `sweny skill` command group — discover, list, and scaffold skills.
3
+ *
4
+ * Skills are the unit of capability a workflow node names in its `skills`
5
+ * array. Sweny ships built-ins (github, linear, slack, ...) and discovers
6
+ * custom skills from `.{gemini,agents,claude,sweny}/skills/<id>/SKILL.md`.
7
+ *
8
+ * This module owns the *authoring* side of that loop:
9
+ *
10
+ * sweny skill new <id> scaffold a SKILL.md template
11
+ * sweny skill list list built-in + custom skills together
12
+ *
13
+ * Discovery and validation already live in `../skills/custom-loader.js`
14
+ * and `../skills/index.js`; this CLI is a thin authoring shell over them.
15
+ */
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+ import chalk from "chalk";
19
+ import { builtinSkills } from "../skills/index.js";
20
+ import { configuredSkillsWithDiagnostics, discoverSkillsWithDiagnostics } from "../skills/custom-loader.js";
21
+ // Mirror of the loader's regex so authoring-side validation matches discovery.
22
+ const VALID_SKILL_ID = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
23
+ const VALID_CATEGORIES = ["general", "git", "tasks", "notification", "observability", "data"];
24
+ const HARNESS_DIRS = {
25
+ claude: ".claude/skills",
26
+ sweny: ".sweny/skills",
27
+ agents: ".agents/skills",
28
+ gemini: ".gemini/skills",
29
+ };
30
+ /** Build the SKILL.md body for a fresh scaffold. Exported for tests. */
31
+ export function renderSkillTemplate(opts) {
32
+ const { id, description, category } = opts;
33
+ const lines = [];
34
+ lines.push("---");
35
+ lines.push(`name: ${id}`);
36
+ lines.push(`description: ${description}`);
37
+ lines.push(`category: ${category}`);
38
+ lines.push("# config:");
39
+ lines.push("# EXAMPLE_ENV_VAR:");
40
+ lines.push("# description: Human-readable description shown by `sweny check`");
41
+ lines.push("# required: true");
42
+ lines.push("# mcp:");
43
+ lines.push("# command: npx");
44
+ lines.push('# args: ["-y", "@your-org/your-mcp-server"]');
45
+ lines.push("# env:");
46
+ lines.push("# SOMETHING: ${SOMETHING}");
47
+ lines.push("---");
48
+ lines.push("");
49
+ lines.push(`# ${id}`);
50
+ lines.push("");
51
+ lines.push(description);
52
+ lines.push("");
53
+ lines.push("## When to use");
54
+ lines.push("");
55
+ lines.push(`Reference this skill from a workflow node when the node needs ${description.toLowerCase()}.`);
56
+ lines.push("");
57
+ lines.push("```yaml");
58
+ lines.push("nodes:");
59
+ lines.push(" my_node:");
60
+ lines.push(" name: My Node");
61
+ lines.push(` instruction: >-`);
62
+ lines.push(` Use the ${id} capability described in this skill.`);
63
+ lines.push(` skills: [${id}]`);
64
+ lines.push("```");
65
+ lines.push("");
66
+ lines.push("## What this skill provides");
67
+ lines.push("");
68
+ lines.push("Describe the contract this skill exposes. Custom skills are *instruction-only* by default — the markdown body becomes guidance the LLM sees when the node runs. Add an `mcp:` block in the frontmatter above to wire in tool execution via an MCP server.");
69
+ lines.push("");
70
+ lines.push("Concrete things to document here:");
71
+ lines.push("");
72
+ lines.push("- Required environment variables (also declare in `config:` above so `sweny check` validates them).");
73
+ lines.push("- The exact commands or tool calls the node should make.");
74
+ lines.push("- Common failure modes and how to recover.");
75
+ lines.push("- Any preconditions the workflow must satisfy before invoking this skill.");
76
+ lines.push("");
77
+ lines.push("## Example invocation");
78
+ lines.push("");
79
+ lines.push("Replace this section with a concrete usage example, including the exact bash, MCP tool call, or LLM prompt the node should produce.");
80
+ lines.push("");
81
+ return lines.join("\n");
82
+ }
83
+ function runSkillNew(idArg, options) {
84
+ const id = idArg.toLowerCase();
85
+ if (!VALID_SKILL_ID.test(id) || id.includes("--") || id.length > 64) {
86
+ console.error(chalk.red(` Invalid skill id "${idArg}".\n Skill ids must be lowercase alphanumeric + hyphens (no consecutive hyphens), max 64 chars.`));
87
+ process.exit(2);
88
+ return;
89
+ }
90
+ const description = (options.description ?? `Custom ${id} skill`).trim();
91
+ const category = (options.category ?? "general");
92
+ if (!VALID_CATEGORIES.includes(category)) {
93
+ console.error(chalk.red(` Invalid category "${category}".\n Allowed: ${VALID_CATEGORIES.join(", ")}`));
94
+ process.exit(2);
95
+ return;
96
+ }
97
+ const harness = options.harness ?? "claude";
98
+ const baseDir = HARNESS_DIRS[harness];
99
+ if (!baseDir) {
100
+ console.error(chalk.red(` Unknown harness "${harness}". Allowed: ${Object.keys(HARNESS_DIRS).join(", ")}`));
101
+ process.exit(2);
102
+ return;
103
+ }
104
+ const cwd = process.cwd();
105
+ const skillDir = path.join(cwd, baseDir, id);
106
+ const skillFile = path.join(skillDir, "SKILL.md");
107
+ if (fs.existsSync(skillFile) && !options.force) {
108
+ console.error(chalk.red(` ${path.relative(cwd, skillFile)} already exists. Pass --force to overwrite.`));
109
+ process.exit(1);
110
+ return;
111
+ }
112
+ fs.mkdirSync(skillDir, { recursive: true });
113
+ fs.writeFileSync(skillFile, renderSkillTemplate({ id, description, category }), "utf-8");
114
+ const rel = path.relative(cwd, skillFile);
115
+ console.log(chalk.green(`\n Created ${rel}\n`));
116
+ console.log(chalk.dim(" Next steps:"));
117
+ console.log(chalk.dim(` 1. Open ${rel} and replace the placeholder sections.`));
118
+ console.log(chalk.dim(` 2. If your skill needs env vars, uncomment the 'config:' block in the frontmatter.`));
119
+ console.log(chalk.dim(` 3. If your skill executes tools via MCP, uncomment the 'mcp:' block.`));
120
+ console.log(chalk.dim(` 4. Reference the skill in a workflow node: skills: [${id}]`));
121
+ console.log(chalk.dim(` 5. Run \`sweny skill list\` to confirm it discovers correctly.`));
122
+ console.log();
123
+ }
124
+ function runSkillList(options) {
125
+ const cwd = process.cwd();
126
+ // We want the FULL list — built-in + custom — even when env vars
127
+ // aren't set. configuredSkills filters by env, which hides authoring-
128
+ // time skills the user just scaffolded. So combine sources directly.
129
+ const { skills: customSkills, warnings: customWarnings } = discoverSkillsWithDiagnostics(cwd);
130
+ const customIds = new Set(customSkills.map((s) => s.id));
131
+ const builtinList = builtinSkills.filter((s) => !customIds.has(s.id));
132
+ // Configured map drives the "configured?" badge — required env vars present.
133
+ const configuredIds = new Set(configuredSkillsWithDiagnostics(process.env, cwd).skills.map((s) => s.id));
134
+ if (options.json) {
135
+ const data = [
136
+ ...builtinList.map((s) => ({
137
+ id: s.id,
138
+ kind: "builtin",
139
+ category: s.category,
140
+ description: s.description,
141
+ configured: configuredIds.has(s.id),
142
+ })),
143
+ ...customSkills.map((s) => ({
144
+ id: s.id,
145
+ kind: "custom",
146
+ category: s.category,
147
+ description: s.description,
148
+ configured: configuredIds.has(s.id),
149
+ })),
150
+ ];
151
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
152
+ return;
153
+ }
154
+ const renderRow = (s, kind) => {
155
+ const tag = kind === "custom" ? chalk.magenta("custom") : chalk.dim("builtin");
156
+ const ok = configuredIds.has(s.id) ? chalk.green("✓") : chalk.yellow("·");
157
+ console.log(` ${ok} ${chalk.cyan(s.id)} ${chalk.dim(`(${s.category})`)} ${tag}`);
158
+ console.log(chalk.dim(` ${s.description}`));
159
+ };
160
+ console.log(chalk.bold("\nAvailable skills:\n"));
161
+ console.log(chalk.dim(" ✓ = configured (required env vars present)"));
162
+ console.log(chalk.dim(" · = present but not configured"));
163
+ console.log();
164
+ if (builtinList.length === 0 && customSkills.length === 0) {
165
+ console.log(chalk.dim(" (no skills found)\n"));
166
+ }
167
+ else {
168
+ for (const s of builtinList)
169
+ renderRow(s, "builtin");
170
+ for (const s of customSkills)
171
+ renderRow(s, "custom");
172
+ }
173
+ if (customWarnings.length > 0) {
174
+ console.log();
175
+ console.log(chalk.yellow(` ${customWarnings.length} skill discovery warning(s):`));
176
+ for (const w of customWarnings) {
177
+ console.log(chalk.yellow(` ${w.kind}: ${w.message}`));
178
+ }
179
+ }
180
+ console.log();
181
+ console.log(chalk.dim(` Scaffold a new skill: sweny skill new <id>`));
182
+ console.log();
183
+ }
184
+ /** Register the `sweny skill` subcommand group on the parent program. */
185
+ export function registerSkillCommand(program) {
186
+ const skillCmd = program.command("skill").description("Author and inspect skills");
187
+ skillCmd
188
+ .command("new <id>")
189
+ .description("Scaffold a new SKILL.md in .claude/skills/<id>/")
190
+ .option("-d, --description <text>", "One-line description (required field in frontmatter)")
191
+ .option("-c, --category <category>", `Skill category (${VALID_CATEGORIES.join("|")})`, "general")
192
+ .option("--harness <harness>", `Where to place the skill (${Object.keys(HARNESS_DIRS).join("|")})`, "claude")
193
+ .option("--force", "Overwrite an existing SKILL.md")
194
+ .action((id, options) => runSkillNew(id, options));
195
+ skillCmd
196
+ .command("list")
197
+ .description("List built-in and custom skills together with configuration status")
198
+ .option("--json", "Output as JSON array")
199
+ .action((options) => runSkillList(options));
200
+ }
@@ -99,6 +99,10 @@ function parseSkillMd(content, path) {
99
99
  }
100
100
  // Narrow the loosely-typed YAML output into the fields we expect
101
101
  const fm = raw;
102
+ const VALID_CATEGORIES = ["general", "git", "tasks", "notification", "observability", "data"];
103
+ const category = typeof fm.category === "string" && VALID_CATEGORIES.includes(fm.category)
104
+ ? fm.category
105
+ : "general";
102
106
  const id = String(fm.name);
103
107
  if (!VALID_SKILL_ID.test(id) || id.includes("--") || id.length > 64) {
104
108
  return {
@@ -145,7 +149,7 @@ function parseSkillMd(content, path) {
145
149
  id,
146
150
  name: id,
147
151
  description: typeof fm.description === "string" ? fm.description : `Custom skill: ${id}`,
148
- category: "general",
152
+ category,
149
153
  config,
150
154
  tools: [],
151
155
  instruction: instruction || undefined,
package/dist/types.d.ts CHANGED
@@ -23,7 +23,7 @@ export interface ConfigField {
23
23
  env?: string;
24
24
  }
25
25
  /** Skill categories — used for validation and grouping */
26
- export type SkillCategory = "git" | "observability" | "tasks" | "notification" | "general";
26
+ export type SkillCategory = "git" | "observability" | "tasks" | "notification" | "data" | "general";
27
27
  /** A skill groups related tools with shared config requirements */
28
28
  export interface Skill {
29
29
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.86",
3
+ "version": "0.1.88",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"