codeply-cli 0.1.0 → 0.2.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.
- package/bin/codeply.js +125 -14
- package/lib/agent.mjs +157 -18
- package/lib/ai.js +221 -15
- package/lib/apply-limit.js +13 -3
- package/lib/config.js +63 -16
- package/lib/edit-engine.js +1 -1
- package/lib/skills.js +358 -0
- package/lib/tools.mjs +63 -6
- package/lib/tui.mjs +96 -3
- package/package.json +3 -2
- package/skills/SOURCE.md +58 -0
- package/skills/accessibility/SKILL.md +147 -0
- package/skills/agent-architecture-audit/SKILL.md +257 -0
- package/skills/agent-eval/SKILL.md +147 -0
- package/skills/agent-harness-construction/SKILL.md +74 -0
- package/skills/agent-introspection-debugging/SKILL.md +154 -0
- package/skills/agent-payment-x402/SKILL.md +225 -0
- package/skills/agent-self-evaluation/SKILL.md +182 -0
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
- package/skills/agent-sort/SKILL.md +216 -0
- package/skills/agentic-engineering/SKILL.md +64 -0
- package/skills/agentic-os/SKILL.md +388 -0
- package/skills/ai-first-engineering/SKILL.md +52 -0
- package/skills/ai-regression-testing/SKILL.md +386 -0
- package/skills/android-clean-architecture/SKILL.md +340 -0
- package/skills/angular-developer/SKILL.md +155 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +410 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +59 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/skills/angular-developer/references/e2e-testing.md +56 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/mcp.md +108 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +122 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +77 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +795 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/skills/api-connector-builder/SKILL.md +121 -0
- package/skills/api-design/SKILL.md +524 -0
- package/skills/architecture-decision-records/SKILL.md +180 -0
- package/skills/article-writing/SKILL.md +80 -0
- package/skills/automation-audit-ops/SKILL.md +143 -0
- package/skills/autonomous-agent-harness/SKILL.md +274 -0
- package/skills/autonomous-loops/SKILL.md +611 -0
- package/skills/backend-patterns/SKILL.md +562 -0
- package/skills/benchmark/SKILL.md +95 -0
- package/skills/benchmark-methodology/SKILL.md +191 -0
- package/skills/benchmark-optimization-loop/SKILL.md +71 -0
- package/skills/blender-motion-state-inspection/SKILL.md +165 -0
- package/skills/blueprint/SKILL.md +106 -0
- package/skills/brand-discovery/SKILL.md +145 -0
- package/skills/brand-discovery/references/10_purpose-why.md +40 -0
- package/skills/brand-discovery/references/20_positioning.md +44 -0
- package/skills/brand-discovery/references/30_audience-niche.md +52 -0
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
- package/skills/brand-discovery/references/50_voice-tone.md +59 -0
- package/skills/brand-discovery/references/60_narrative-story.md +50 -0
- package/skills/brand-discovery/references/70_founder-tension.md +49 -0
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
- package/skills/brand-voice/SKILL.md +98 -0
- package/skills/brand-voice/references/voice-profile-schema.md +55 -0
- package/skills/browser-qa/SKILL.md +105 -0
- package/skills/bun-runtime/SKILL.md +85 -0
- package/skills/canary-watch/SKILL.md +108 -0
- package/skills/carrier-relationship-management/SKILL.md +212 -0
- package/skills/cisco-ios-patterns/SKILL.md +164 -0
- package/skills/ck/SKILL.md +148 -0
- package/skills/claude-devfleet/SKILL.md +112 -0
- package/skills/click-path-audit/SKILL.md +245 -0
- package/skills/clickhouse-io/SKILL.md +445 -0
- package/skills/code-tour/SKILL.md +254 -0
- package/skills/codebase-onboarding/SKILL.md +234 -0
- package/skills/codehealth-mcp/SKILL.md +167 -0
- package/skills/coding-standards/SKILL.md +551 -0
- package/skills/competitive-platform-analysis/SKILL.md +214 -0
- package/skills/competitive-report-structure/SKILL.md +162 -0
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
- package/skills/config-gc/SKILL.md +120 -0
- package/skills/configure-ecc/SKILL.md +385 -0
- package/skills/connections-optimizer/SKILL.md +190 -0
- package/skills/content-engine/SKILL.md +132 -0
- package/skills/content-hash-cache-pattern/SKILL.md +162 -0
- package/skills/context-budget/SKILL.md +136 -0
- package/skills/continuous-agent-loop/SKILL.md +46 -0
- package/skills/continuous-learning/SKILL.md +132 -0
- package/skills/continuous-learning-v2/SKILL.md +361 -0
- package/skills/contract-first/SKILL.md +287 -0
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
- package/skills/cost-tracking/SKILL.md +97 -0
- package/skills/council/SKILL.md +204 -0
- package/skills/cpp-coding-standards/SKILL.md +724 -0
- package/skills/cpp-testing/SKILL.md +325 -0
- package/skills/crosspost/SKILL.md +112 -0
- package/skills/csharp-testing/SKILL.md +322 -0
- package/skills/customer-billing-ops/SKILL.md +141 -0
- package/skills/customs-trade-compliance/SKILL.md +263 -0
- package/skills/dart-flutter-patterns/SKILL.md +564 -0
- package/skills/dashboard-builder/SKILL.md +109 -0
- package/skills/data-scraper-agent/SKILL.md +765 -0
- package/skills/data-throughput-accelerator/SKILL.md +74 -0
- package/skills/database-migrations/SKILL.md +430 -0
- package/skills/deep-research/SKILL.md +160 -0
- package/skills/defi-amm-security/SKILL.md +167 -0
- package/skills/delivery-gate/SKILL.md +126 -0
- package/skills/deployment-patterns/SKILL.md +428 -0
- package/skills/design-system/SKILL.md +83 -0
- package/skills/django-celery/SKILL.md +458 -0
- package/skills/django-patterns/SKILL.md +735 -0
- package/skills/django-security/SKILL.md +644 -0
- package/skills/django-tdd/SKILL.md +730 -0
- package/skills/django-verification/SKILL.md +470 -0
- package/skills/dmux-workflows/SKILL.md +192 -0
- package/skills/docker-patterns/SKILL.md +365 -0
- package/skills/documentation-lookup/SKILL.md +91 -0
- package/skills/dotnet-patterns/SKILL.md +322 -0
- package/skills/dynamic-workflow-mode/SKILL.md +124 -0
- package/skills/e2e-testing/SKILL.md +327 -0
- package/skills/ecc-guide/SKILL.md +190 -0
- package/skills/ecc-recipes/SKILL.md +149 -0
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
- package/skills/email-ops/SKILL.md +122 -0
- package/skills/energy-procurement/SKILL.md +228 -0
- package/skills/enterprise-agent-ops/SKILL.md +51 -0
- package/skills/error-handling/SKILL.md +377 -0
- package/skills/eval-harness/SKILL.md +271 -0
- package/skills/evm-token-decimals/SKILL.md +131 -0
- package/skills/exa-search/SKILL.md +108 -0
- package/skills/fal-ai-media/SKILL.md +289 -0
- package/skills/fastapi-patterns/SKILL.md +514 -0
- package/skills/finance-billing-ops/SKILL.md +128 -0
- package/skills/flox-environments/SKILL.md +497 -0
- package/skills/flutter-dart-code-review/SKILL.md +436 -0
- package/skills/foundation-models-on-device/SKILL.md +243 -0
- package/skills/frontend-a11y/SKILL.md +446 -0
- package/skills/frontend-design-direction/SKILL.md +93 -0
- package/skills/frontend-patterns/SKILL.md +657 -0
- package/skills/frontend-slides/SKILL.md +185 -0
- package/skills/fsharp-testing/SKILL.md +281 -0
- package/skills/gan-style-harness/SKILL.md +279 -0
- package/skills/gateguard/SKILL.md +133 -0
- package/skills/generating-python-installer/SKILL.md +820 -0
- package/skills/git-workflow/SKILL.md +716 -0
- package/skills/github-ops/SKILL.md +145 -0
- package/skills/golang-patterns/SKILL.md +676 -0
- package/skills/golang-testing/SKILL.md +721 -0
- package/skills/google-workspace-ops/SKILL.md +96 -0
- package/skills/growth-log/SKILL.md +128 -0
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
- package/skills/healthcare-emr-patterns/SKILL.md +160 -0
- package/skills/healthcare-eval-harness/SKILL.md +208 -0
- package/skills/healthcare-phi-compliance/SKILL.md +146 -0
- package/skills/hermes-imports/SKILL.md +89 -0
- package/skills/hexagonal-architecture/SKILL.md +277 -0
- package/skills/hipaa-compliance/SKILL.md +79 -0
- package/skills/homelab-network-readiness/SKILL.md +170 -0
- package/skills/homelab-network-setup/SKILL.md +130 -0
- package/skills/homelab-pihole-dns/SKILL.md +275 -0
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
- package/skills/hookify-rules/SKILL.md +128 -0
- package/skills/inherit-legacy-style/SKILL.md +157 -0
- package/skills/intent-driven-development/SKILL.md +360 -0
- package/skills/inventory-demand-planning/SKILL.md +247 -0
- package/skills/investor-materials/SKILL.md +97 -0
- package/skills/investor-outreach/SKILL.md +92 -0
- package/skills/ios-icon-gen/SKILL.md +158 -0
- package/skills/iterative-retrieval/SKILL.md +212 -0
- package/skills/ito-basket-compare/SKILL.md +64 -0
- package/skills/ito-compute/SKILL.md +129 -0
- package/skills/ito-data-atlas-agent/SKILL.md +64 -0
- package/skills/ito-market-intelligence/SKILL.md +61 -0
- package/skills/ito-trade-planner/SKILL.md +68 -0
- package/skills/java-coding-standards/SKILL.md +384 -0
- package/skills/jira-integration/SKILL.md +303 -0
- package/skills/jpa-patterns/SKILL.md +152 -0
- package/skills/knowledge-ops/SKILL.md +155 -0
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
- package/skills/kotlin-patterns/SKILL.md +712 -0
- package/skills/kotlin-testing/SKILL.md +825 -0
- package/skills/kubernetes-patterns/SKILL.md +756 -0
- package/skills/laravel-patterns/SKILL.md +416 -0
- package/skills/laravel-plugin-discovery/SKILL.md +230 -0
- package/skills/laravel-security/SKILL.md +948 -0
- package/skills/laravel-tdd/SKILL.md +675 -0
- package/skills/laravel-verification/SKILL.md +180 -0
- package/skills/latency-critical-systems/SKILL.md +75 -0
- package/skills/lead-intelligence/SKILL.md +322 -0
- package/skills/liquid-glass-design/SKILL.md +279 -0
- package/skills/llm-trading-agent-security/SKILL.md +147 -0
- package/skills/logistics-exception-management/SKILL.md +222 -0
- package/skills/loop-design-check/SKILL.md +143 -0
- package/skills/mailtrap-email-integration/SKILL.md +77 -0
- package/skills/make-interfaces-feel-better/SKILL.md +152 -0
- package/skills/manim-video/SKILL.md +90 -0
- package/skills/market-research/SKILL.md +76 -0
- package/skills/marketing-campaign/SKILL.md +114 -0
- package/skills/mcp-server-patterns/SKILL.md +70 -0
- package/skills/messages-ops/SKILL.md +105 -0
- package/skills/ml-adoption-playbook/SKILL.md +57 -0
- package/skills/mle-workflow/SKILL.md +348 -0
- package/skills/motion-advanced/SKILL.md +596 -0
- package/skills/motion-foundations/SKILL.md +299 -0
- package/skills/motion-patterns/SKILL.md +434 -0
- package/skills/motion-ui/SKILL.md +576 -0
- package/skills/mysql-patterns/SKILL.md +413 -0
- package/skills/nanoclaw-repl/SKILL.md +34 -0
- package/skills/nestjs-patterns/SKILL.md +231 -0
- package/skills/netmiko-ssh-automation/SKILL.md +174 -0
- package/skills/network-bgp-diagnostics/SKILL.md +168 -0
- package/skills/network-config-validation/SKILL.md +211 -0
- package/skills/network-interface-health/SKILL.md +153 -0
- package/skills/nextjs-turbopack/SKILL.md +58 -0
- package/skills/nodejs-keccak256/SKILL.md +103 -0
- package/skills/nutrient-document-processing/SKILL.md +168 -0
- package/skills/nuxt4-patterns/SKILL.md +101 -0
- package/skills/openclaw-persona-forge/SKILL.md +289 -0
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
- package/skills/openclaw-persona-forge/references/output-template.md +166 -0
- package/skills/opensource-pipeline/SKILL.md +256 -0
- package/skills/orch-add-feature/SKILL.md +45 -0
- package/skills/orch-build-mvp/SKILL.md +49 -0
- package/skills/orch-change-feature/SKILL.md +43 -0
- package/skills/orch-fix-defect/SKILL.md +43 -0
- package/skills/orch-pipeline/SKILL.md +121 -0
- package/skills/orch-refine-code/SKILL.md +44 -0
- package/skills/parallel-execution-optimizer/SKILL.md +74 -0
- package/skills/perl-patterns/SKILL.md +505 -0
- package/skills/perl-security/SKILL.md +504 -0
- package/skills/perl-testing/SKILL.md +476 -0
- package/skills/plan-canvas/SKILL.md +153 -0
- package/skills/plan-orchestrate/SKILL.md +263 -0
- package/skills/plankton-code-quality/SKILL.md +237 -0
- package/skills/postgres-patterns/SKILL.md +148 -0
- package/skills/prediction-market-oracle-research/SKILL.md +64 -0
- package/skills/prediction-market-risk-review/SKILL.md +61 -0
- package/skills/prisma-patterns/SKILL.md +401 -0
- package/skills/product-capability/SKILL.md +142 -0
- package/skills/product-lens/SKILL.md +93 -0
- package/skills/production-audit/SKILL.md +207 -0
- package/skills/production-scheduling/SKILL.md +238 -0
- package/skills/project-flow-ops/SKILL.md +112 -0
- package/skills/prompt-optimizer/SKILL.md +398 -0
- package/skills/python-patterns/SKILL.md +751 -0
- package/skills/python-testing/SKILL.md +817 -0
- package/skills/pytorch-patterns/SKILL.md +397 -0
- package/skills/quality-nonconformance/SKILL.md +260 -0
- package/skills/quarkus-patterns/SKILL.md +723 -0
- package/skills/quarkus-security/SKILL.md +468 -0
- package/skills/quarkus-tdd/SKILL.md +812 -0
- package/skills/quarkus-verification/SKILL.md +480 -0
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
- package/skills/react-native-patterns/SKILL.md +326 -0
- package/skills/react-patterns/SKILL.md +342 -0
- package/skills/react-performance/SKILL.md +575 -0
- package/skills/react-testing/SKILL.md +424 -0
- package/skills/recsys-pipeline-architect/SKILL.md +115 -0
- package/skills/recursive-decision-ledger/SKILL.md +81 -0
- package/skills/redis-patterns/SKILL.md +404 -0
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
- package/skills/remotion-video-creation/SKILL.md +43 -0
- package/skills/repo-scan/SKILL.md +79 -0
- package/skills/research-ops/SKILL.md +113 -0
- package/skills/returns-reverse-logistics/SKILL.md +240 -0
- package/skills/rules-distill/SKILL.md +265 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/safety-guard/SKILL.md +76 -0
- package/skills/santa-method/SKILL.md +307 -0
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
- package/skills/scientific-db-uspto-database/SKILL.md +178 -0
- package/skills/scientific-pkg-gget/SKILL.md +167 -0
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
- package/skills/search-first/SKILL.md +183 -0
- package/skills/security-bounty-hunter/SKILL.md +100 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-scan/SKILL.md +166 -0
- package/skills/seo/SKILL.md +155 -0
- package/skills/skill-comply/SKILL.md +59 -0
- package/skills/skill-scout/SKILL.md +141 -0
- package/skills/skill-stocktake/SKILL.md +195 -0
- package/skills/social-graph-ranker/SKILL.md +155 -0
- package/skills/social-publisher/SKILL.md +130 -0
- package/skills/springboot-patterns/SKILL.md +315 -0
- package/skills/springboot-security/SKILL.md +273 -0
- package/skills/springboot-tdd/SKILL.md +159 -0
- package/skills/springboot-verification/SKILL.md +232 -0
- package/skills/strategic-compact/SKILL.md +142 -0
- package/skills/swift-actor-persistence/SKILL.md +144 -0
- package/skills/swift-concurrency-6-2/SKILL.md +216 -0
- package/skills/swift-protocol-di-testing/SKILL.md +191 -0
- package/skills/swiftui-patterns/SKILL.md +259 -0
- package/skills/taste/SKILL.md +264 -0
- package/skills/taste/references/genre-taxonomy.md +87 -0
- package/skills/tdd-workflow/SKILL.md +583 -0
- package/skills/team-agent-orchestration/SKILL.md +111 -0
- package/skills/team-builder/SKILL.md +169 -0
- package/skills/terminal-ops/SKILL.md +110 -0
- package/skills/tinystruct-patterns/SKILL.md +279 -0
- package/skills/tinystruct-patterns/references/architecture.md +90 -0
- package/skills/tinystruct-patterns/references/data-handling.md +60 -0
- package/skills/tinystruct-patterns/references/database.md +99 -0
- package/skills/tinystruct-patterns/references/routing.md +64 -0
- package/skills/tinystruct-patterns/references/system-usage.md +97 -0
- package/skills/tinystruct-patterns/references/testing.md +72 -0
- package/skills/token-budget-advisor/SKILL.md +134 -0
- package/skills/ui-demo/SKILL.md +466 -0
- package/skills/ui-to-vue/SKILL.md +135 -0
- package/skills/uncloud/SKILL.md +344 -0
- package/skills/unified-memory/SKILL.md +170 -0
- package/skills/unified-notifications-ops/SKILL.md +188 -0
- package/skills/verification-loop/SKILL.md +129 -0
- package/skills/video-editing/SKILL.md +311 -0
- package/skills/videodb/SKILL.md +375 -0
- package/skills/visa-doc-translate/SKILL.md +117 -0
- package/skills/vite-patterns/SKILL.md +450 -0
- package/skills/vue-patterns/SKILL.md +471 -0
- package/skills/windows-desktop-e2e/SKILL.md +888 -0
- package/skills/workspace-surface-audit/SKILL.md +126 -0
- package/skills/x-api/SKILL.md +235 -0
package/lib/skills.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codeply skills.
|
|
3
|
+
*
|
|
4
|
+
* A skill is a directory with a SKILL.md: YAML frontmatter (`name`,
|
|
5
|
+
* `description`) followed by a markdown body of instructions. This is the same
|
|
6
|
+
* convention Claude Code and affaan-m/ECC use — see skills/SOURCE.md for where
|
|
7
|
+
* the bundled set came from and why the format was copied rather than invented.
|
|
8
|
+
*
|
|
9
|
+
* Two-level progressive disclosure — this matters at 281 skills:
|
|
10
|
+
*
|
|
11
|
+
* 1. Per-skill: buildSystemPrompt() only ever injects a skill's NAME and a
|
|
12
|
+
* capped one-line description. The full body — which can run to several
|
|
13
|
+
* KB with reference files — loads on demand via <codeply:use_skill>. The
|
|
14
|
+
* alternative, concatenating every skill's full body into the system
|
|
15
|
+
* prompt always, would resend ~3.4MB per request for the bundled set
|
|
16
|
+
* alone.
|
|
17
|
+
*
|
|
18
|
+
* 2. Across the library: even name+description for all 281 is ~44KB
|
|
19
|
+
* (~11,000 tokens) — resent on every step of every turn, that alone would
|
|
20
|
+
* make the skill library cost more than it saves. So the system prompt by
|
|
21
|
+
* default only lists a curated subset (DAILY_SKILLS below, ~2KB) covering
|
|
22
|
+
* general coding work; the complete 281 are still fully installed and
|
|
23
|
+
* usable, just one cheap `list_skills` action away instead of force-fed
|
|
24
|
+
* into every request. DAILY_SKILLS is not a cut Codeply invented — it's
|
|
25
|
+
* ECC's own `.agents/skills/` curation of its 281-skill `skills/`
|
|
26
|
+
* library (see skills/SOURCE.md), reused rather than re-decided.
|
|
27
|
+
*
|
|
28
|
+
* Two sources, later wins on a name collision:
|
|
29
|
+
* built-in <repo>/skills/<name>/SKILL.md — vendored, ships with Codeply
|
|
30
|
+
* user ~/.codeply/skills/<name>/SKILL.md — `codeply skill install`
|
|
31
|
+
*/
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const os = require('os');
|
|
35
|
+
|
|
36
|
+
const BUILTIN_DIR = path.join(__dirname, '..', 'skills');
|
|
37
|
+
const USER_DIR = path.join(os.homedir(), '.codeply', 'skills');
|
|
38
|
+
|
|
39
|
+
const MAX_BODY_CHARS = 8000; // fed to the model on demand; keep one skill from eating the whole budget
|
|
40
|
+
|
|
41
|
+
// ECC's own curated subset of its full library — see skills/SOURCE.md. Kept as
|
|
42
|
+
// a name list rather than a directory so a user-installed skill with one of
|
|
43
|
+
// these names (or a future re-vendor that drops/renames one) degrades
|
|
44
|
+
// gracefully instead of erroring.
|
|
45
|
+
const DAILY_SKILLS = new Set([
|
|
46
|
+
'agent-introspection-debugging', 'agent-sort', 'api-design', 'article-writing',
|
|
47
|
+
'backend-patterns', 'benchmark-methodology', 'brand-discovery', 'brand-voice',
|
|
48
|
+
'bun-runtime', 'coding-standards', 'competitive-platform-analysis',
|
|
49
|
+
'competitive-report-structure', 'content-engine', 'crosspost', 'deep-research',
|
|
50
|
+
'dmux-workflows', 'documentation-lookup', 'e2e-testing', 'eval-harness',
|
|
51
|
+
'exa-search', 'fal-ai-media', 'frontend-patterns', 'frontend-slides',
|
|
52
|
+
'investor-materials', 'investor-outreach', 'market-research', 'mcp-server-patterns',
|
|
53
|
+
'mle-workflow', 'nextjs-turbopack', 'plan-canvas', 'product-capability',
|
|
54
|
+
'security-review', 'strategic-compact', 'tdd-workflow', 'unified-memory',
|
|
55
|
+
'verification-loop', 'video-editing', 'x-api',
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
function listSkillDirs(root, source) {
|
|
59
|
+
let entries;
|
|
60
|
+
try { entries = fs.readdirSync(root, { withFileTypes: true }); }
|
|
61
|
+
catch { return []; }
|
|
62
|
+
|
|
63
|
+
return entries
|
|
64
|
+
.filter((e) => e.isDirectory())
|
|
65
|
+
.map((e) => ({ name: e.name, dir: path.join(root, e.name), source }))
|
|
66
|
+
.filter((s) => fs.existsSync(path.join(s.dir, 'SKILL.md')));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Minimal YAML frontmatter reader for exactly the two keys skills use.
|
|
71
|
+
* Handles plain `key: value` and folded/literal block scalars (`key: >-` /
|
|
72
|
+
* `key: |-` etc.) since real skills in the wild use both — a naive
|
|
73
|
+
* single-line regex silently truncates ~10% of the bundled set's descriptions.
|
|
74
|
+
*/
|
|
75
|
+
function parseFrontmatter(text) {
|
|
76
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
77
|
+
if (!m) return { name: null, description: null, body: text };
|
|
78
|
+
|
|
79
|
+
const fmLines = m[1].split(/\r?\n/);
|
|
80
|
+
const values = {};
|
|
81
|
+
for (let i = 0; i < fmLines.length; i++) {
|
|
82
|
+
const line = fmLines[i];
|
|
83
|
+
const kv = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
|
|
84
|
+
if (!kv) continue;
|
|
85
|
+
const [, key, rest] = kv;
|
|
86
|
+
|
|
87
|
+
const block = rest.match(/^([|>])(-|\+)?\s*$/);
|
|
88
|
+
if (block) {
|
|
89
|
+
const folded = block[1] === '>';
|
|
90
|
+
const collected = [];
|
|
91
|
+
let j = i + 1;
|
|
92
|
+
let indent = null;
|
|
93
|
+
while (j < fmLines.length) {
|
|
94
|
+
const l = fmLines[j];
|
|
95
|
+
if (l.trim() === '') { collected.push(''); j++; continue; }
|
|
96
|
+
const lineIndent = l.match(/^\s*/)[0].length;
|
|
97
|
+
if (indent === null) indent = lineIndent;
|
|
98
|
+
if (lineIndent < indent) break;
|
|
99
|
+
collected.push(l.slice(indent));
|
|
100
|
+
j++;
|
|
101
|
+
}
|
|
102
|
+
i = j - 1;
|
|
103
|
+
values[key] = folded
|
|
104
|
+
? collected.join(' ').replace(/\s+/g, ' ').trim()
|
|
105
|
+
: collected.join('\n').replace(/\n+$/, '');
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Strip a matching pair of quotes, nothing fancier — these are simple labels.
|
|
110
|
+
values[key] = rest.replace(/^["'](.*)["']$/, '$1').trim();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
name: values.name || null,
|
|
115
|
+
description: values.description || null,
|
|
116
|
+
body: text.slice(m[0].length),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Every skill available right now, built-in and user, deduped by name. */
|
|
121
|
+
function listSkills() {
|
|
122
|
+
const found = new Map();
|
|
123
|
+
for (const s of listSkillDirs(BUILTIN_DIR, 'built-in')) found.set(s.name, s);
|
|
124
|
+
// User skills are installed deliberately, so a same-named one wins.
|
|
125
|
+
for (const s of listSkillDirs(USER_DIR, 'user')) found.set(s.name, s);
|
|
126
|
+
|
|
127
|
+
const skills = [];
|
|
128
|
+
for (const s of found.values()) {
|
|
129
|
+
let raw;
|
|
130
|
+
try { raw = fs.readFileSync(path.join(s.dir, 'SKILL.md'), 'utf8'); }
|
|
131
|
+
catch { continue; }
|
|
132
|
+
const { name, description } = parseFrontmatter(raw);
|
|
133
|
+
const finalName = name || s.name;
|
|
134
|
+
skills.push({
|
|
135
|
+
name: finalName,
|
|
136
|
+
description: description || '(no description)',
|
|
137
|
+
source: s.source,
|
|
138
|
+
dir: s.dir,
|
|
139
|
+
// A user-installed skill overriding a daily one keeps its daily status —
|
|
140
|
+
// the name is still what buildSystemPrompt filters on, not the source.
|
|
141
|
+
daily: DAILY_SKILLS.has(finalName),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const INDEX_DESC_MAX = 140; // per-skill cap in the always-injected index; use_skill has the rest
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Compact index of skill name + a capped description, one per line.
|
|
151
|
+
*
|
|
152
|
+
* `daily` (default) is what goes in the system prompt on every request — see
|
|
153
|
+
* the module doc for why the full 281 is too large to inject unconditionally.
|
|
154
|
+
* `all` is for the list_skills action and `/skill list`, where the cost is
|
|
155
|
+
* paid once, on demand, instead of on every step.
|
|
156
|
+
*/
|
|
157
|
+
function formatSkillIndex(skills = listSkills(), { all = false } = {}) {
|
|
158
|
+
const shown = all ? skills : skills.filter((s) => s.daily);
|
|
159
|
+
if (shown.length === 0) return null;
|
|
160
|
+
return shown
|
|
161
|
+
.map((s) => {
|
|
162
|
+
const d = s.description.length > INDEX_DESC_MAX
|
|
163
|
+
? s.description.slice(0, INDEX_DESC_MAX - 1).trim() + '…'
|
|
164
|
+
: s.description;
|
|
165
|
+
return `- ${s.name}: ${d}`;
|
|
166
|
+
})
|
|
167
|
+
.join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The full body of one skill, truncated to a sane size, or null if unknown. */
|
|
171
|
+
function loadSkillBody(name) {
|
|
172
|
+
const skill = listSkills().find((s) => s.name === name);
|
|
173
|
+
if (!skill) return null;
|
|
174
|
+
|
|
175
|
+
let raw;
|
|
176
|
+
try { raw = fs.readFileSync(path.join(skill.dir, 'SKILL.md'), 'utf8'); }
|
|
177
|
+
catch { return null; }
|
|
178
|
+
const { body } = parseFrontmatter(raw);
|
|
179
|
+
const trimmed = body.trim();
|
|
180
|
+
|
|
181
|
+
if (trimmed.length <= MAX_BODY_CHARS) return trimmed;
|
|
182
|
+
return trimmed.slice(0, MAX_BODY_CHARS) +
|
|
183
|
+
`\n\n[… truncated. The rest is at ${path.join(skill.dir, 'SKILL.md')} if you need it via read_file.]`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Copy a local directory containing a SKILL.md into the user skill store.
|
|
188
|
+
* Named separately from the GitHub path below because it never touches the
|
|
189
|
+
* network — used by both `codeply skill install <local-dir>` and as the last
|
|
190
|
+
* step after a GitHub download lands in a temp directory.
|
|
191
|
+
*/
|
|
192
|
+
function installFromLocalDir(sourceDir, name) {
|
|
193
|
+
const skillMd = path.join(sourceDir, 'SKILL.md');
|
|
194
|
+
if (!fs.existsSync(skillMd)) {
|
|
195
|
+
return { ok: false, error: `No SKILL.md found in ${sourceDir}` };
|
|
196
|
+
}
|
|
197
|
+
const raw = fs.readFileSync(skillMd, 'utf8');
|
|
198
|
+
const { name: fmName } = parseFrontmatter(raw);
|
|
199
|
+
const finalName = name || fmName || path.basename(sourceDir);
|
|
200
|
+
|
|
201
|
+
const dest = path.join(USER_DIR, finalName);
|
|
202
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
203
|
+
copyRecursive(sourceDir, dest);
|
|
204
|
+
return { ok: true, name: finalName, dest };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function copyRecursive(src, dest) {
|
|
208
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
209
|
+
const s = path.join(src, entry.name);
|
|
210
|
+
const d = path.join(dest, entry.name);
|
|
211
|
+
if (entry.isDirectory()) {
|
|
212
|
+
fs.mkdirSync(d, { recursive: true });
|
|
213
|
+
copyRecursive(s, d);
|
|
214
|
+
} else if (entry.isFile()) {
|
|
215
|
+
fs.copyFileSync(s, d);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Installing from GitHub ─────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Parse anything a user might paste for a GitHub skill source:
|
|
224
|
+
* owner/repo
|
|
225
|
+
* https://github.com/owner/repo
|
|
226
|
+
* https://github.com/owner/repo/tree/<branch>/<subpath...>
|
|
227
|
+
* https://github.com/owner/repo/blob/<branch>/<subpath...>/SKILL.md
|
|
228
|
+
*/
|
|
229
|
+
function parseGitHubSource(source) {
|
|
230
|
+
const s = source.trim().replace(/\/+$/, '');
|
|
231
|
+
|
|
232
|
+
let m = s.match(/^(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)(?:\/(tree|blob)\/([^/]+)(?:\/(.*))?)?$/i);
|
|
233
|
+
if (m) {
|
|
234
|
+
const [, owner, repo, , branch, subpath] = m;
|
|
235
|
+
return { owner, repo: repo.replace(/\.git$/, ''), branch: branch || null, subpath: subpath || '' };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
m = s.match(/^([\w.-]+)\/([\w.-]+)$/);
|
|
239
|
+
if (m) return { owner: m[1], repo: m[2], branch: null, subpath: '' };
|
|
240
|
+
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function githubJson(url) {
|
|
245
|
+
const res = await fetch(url, { headers: { Accept: 'application/vnd.github+json' } });
|
|
246
|
+
const body = await res.json().catch(() => ({}));
|
|
247
|
+
if (!res.ok) throw new Error(body.message || `GitHub API returned HTTP ${res.status}`);
|
|
248
|
+
return body;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Install one or more skills from a GitHub repo.
|
|
253
|
+
*
|
|
254
|
+
* If `subpath` points directly at a skill (a directory whose own SKILL.md
|
|
255
|
+
* lives there), that one skill is installed under `name` (or its own frontmatter
|
|
256
|
+
* name). Otherwise every SKILL.md found anywhere under `subpath` (the whole
|
|
257
|
+
* repo, if none was given) is installed — this is what makes a bare repo URL
|
|
258
|
+
* like affaan-m/ECC pull in its entire skill library in one command, and also
|
|
259
|
+
* what lets a single-skill link install just that one.
|
|
260
|
+
*/
|
|
261
|
+
async function installFromGitHub(source, explicitName) {
|
|
262
|
+
const parsed = parseGitHubSource(source);
|
|
263
|
+
if (!parsed) return { ok: false, error: `Not a recognizable GitHub source: "${source}"` };
|
|
264
|
+
const { owner, repo, subpath } = parsed;
|
|
265
|
+
|
|
266
|
+
let branch = parsed.branch;
|
|
267
|
+
if (!branch) {
|
|
268
|
+
try {
|
|
269
|
+
const repoInfo = await githubJson(`https://api.github.com/repos/${owner}/${repo}`);
|
|
270
|
+
branch = repoInfo.default_branch || 'main';
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return { ok: false, error: `Could not reach ${owner}/${repo}: ${e.message}` };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let tree;
|
|
277
|
+
try {
|
|
278
|
+
const treeData = await githubJson(
|
|
279
|
+
`https://api.github.com/repos/${owner}/${repo}/git/trees/${encodeURIComponent(branch)}?recursive=1`
|
|
280
|
+
);
|
|
281
|
+
if (treeData.truncated) {
|
|
282
|
+
// GitHub's own recursion cap (huge monorepos) — rare, but better to say
|
|
283
|
+
// so than to silently install an incomplete skill.
|
|
284
|
+
return { ok: false, error: `${owner}/${repo}@${branch} is too large to list in one call. Point --install at a narrower subpath.` };
|
|
285
|
+
}
|
|
286
|
+
tree = treeData.tree || [];
|
|
287
|
+
} catch (e) {
|
|
288
|
+
return { ok: false, error: `Could not read ${owner}/${repo}@${branch}: ${e.message}` };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const cleanSubpath = subpath.replace(/\/SKILL\.md$/i, '').replace(/\/+$/, '');
|
|
292
|
+
const scoped = cleanSubpath
|
|
293
|
+
? tree.filter((t) => t.path === cleanSubpath || t.path.startsWith(cleanSubpath + '/'))
|
|
294
|
+
: tree;
|
|
295
|
+
|
|
296
|
+
const skillMdFiles = scoped.filter((t) => t.type === 'blob' && /(^|\/)SKILL\.md$/i.test(t.path));
|
|
297
|
+
if (skillMdFiles.length === 0) {
|
|
298
|
+
return { ok: false, error: `No SKILL.md found under ${owner}/${repo}${cleanSubpath ? '/' + cleanSubpath : ''}.` };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const skillDirs = skillMdFiles.map((f) => f.path.replace(/\/SKILL\.md$/i, ''));
|
|
302
|
+
const singleSkill = skillDirs.length === 1;
|
|
303
|
+
|
|
304
|
+
const installed = [];
|
|
305
|
+
const failed = [];
|
|
306
|
+
|
|
307
|
+
for (const dir of skillDirs) {
|
|
308
|
+
const filesInDir = tree.filter((t) =>
|
|
309
|
+
t.type === 'blob' && (t.path === `${dir}/SKILL.md` || t.path.startsWith(`${dir}/references/`))
|
|
310
|
+
);
|
|
311
|
+
const rawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
|
312
|
+
|
|
313
|
+
const fetched = [];
|
|
314
|
+
let ok = true;
|
|
315
|
+
for (const f of filesInDir) {
|
|
316
|
+
try {
|
|
317
|
+
const res = await fetch(`${rawBase}/${f.path}`);
|
|
318
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
319
|
+
fetched.push({ relPath: f.path.slice(dir.length + 1), content: await res.text() });
|
|
320
|
+
} catch (e) {
|
|
321
|
+
ok = false;
|
|
322
|
+
failed.push({ dir, error: e.message });
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (!ok) continue;
|
|
327
|
+
|
|
328
|
+
const skillMd = fetched.find((f) => f.relPath === 'SKILL.md');
|
|
329
|
+
const { name: fmName } = parseFrontmatter(skillMd.content);
|
|
330
|
+
const finalName = (singleSkill && explicitName) || fmName || path.basename(dir);
|
|
331
|
+
|
|
332
|
+
const dest = path.join(USER_DIR, finalName);
|
|
333
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
334
|
+
for (const f of fetched) {
|
|
335
|
+
const target = path.join(dest, f.relPath);
|
|
336
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
337
|
+
fs.writeFileSync(target, f.content, 'utf8');
|
|
338
|
+
}
|
|
339
|
+
installed.push({ name: finalName, fileCount: fetched.length, dest });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { ok: installed.length > 0, installed, failed, source: `${owner}/${repo}@${branch}` };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function removeSkill(name) {
|
|
346
|
+
const dest = path.join(USER_DIR, name);
|
|
347
|
+
if (!fs.existsSync(dest)) {
|
|
348
|
+
return { ok: false, error: `No user-installed skill named "${name}" (built-in skills can't be removed this way).` };
|
|
349
|
+
}
|
|
350
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
351
|
+
return { ok: true };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = {
|
|
355
|
+
BUILTIN_DIR, USER_DIR, MAX_BODY_CHARS, DAILY_SKILLS,
|
|
356
|
+
parseFrontmatter, listSkills, formatSkillIndex, loadSkillBody,
|
|
357
|
+
installFromLocalDir, installFromGitHub, parseGitHubSource, removeSkill,
|
|
358
|
+
};
|
package/lib/tools.mjs
CHANGED
|
@@ -19,6 +19,7 @@ const require = createRequire(import.meta.url);
|
|
|
19
19
|
const editEngine = require('./edit-engine.js');
|
|
20
20
|
const applyLimit = require('./apply-limit.js');
|
|
21
21
|
const config = require('./config.js');
|
|
22
|
+
const skills = require('./skills.js');
|
|
22
23
|
|
|
23
24
|
// A write is a real "apply" against the shared 100/day cap only when it's
|
|
24
25
|
// actually spending the shared codeply proxy — Ollama (local or the user's
|
|
@@ -328,7 +329,63 @@ async function run(args, ctx) {
|
|
|
328
329
|
});
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Load one skill's full instructions on demand. Read-only, no approval — the
|
|
334
|
+
* whole point of the skill index living in the system prompt (see
|
|
335
|
+
* lib/skills.js) is that only a name and a one-line description cost anything
|
|
336
|
+
* until the agent actually decides a skill is relevant, at which point this is
|
|
337
|
+
* the one call that pulls in the rest.
|
|
338
|
+
*/
|
|
339
|
+
async function use_skill(args, ctx) {
|
|
340
|
+
const name = (args.name || '').trim();
|
|
341
|
+
if (!name) return { ok: false, output: 'use_skill needs a <name>.' };
|
|
342
|
+
|
|
343
|
+
const body = skills.loadSkillBody(name);
|
|
344
|
+
if (body == null) {
|
|
345
|
+
// Capped rather than dumping the full library (281 names) into a reply
|
|
346
|
+
// that only exists because of one wrong guess.
|
|
347
|
+
const all = skills.listSkills().map((s) => s.name);
|
|
348
|
+
const shown = all.slice(0, 40).join(', ') + (all.length > 40 ? `, … (${all.length - 40} more — see list_skills)` : '');
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
output: `No skill named "${name}". ${all.length ? `Some available: ${shown}` : '(none installed)'}`,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return { ok: true, output: body, meta: { label: name } };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The full skill catalog — the counterpart to the DAILY-only index that
|
|
359
|
+
* always rides in the system prompt (see lib/skills.js). One call, paid once,
|
|
360
|
+
* only when the task actually needs something outside that curated set.
|
|
361
|
+
*
|
|
362
|
+
* At 281 skills, name+description together run past MAX_TOOL_OUTPUT — an
|
|
363
|
+
* optional query narrows to matching names/descriptions before formatting, so
|
|
364
|
+
* the normal case (looking for "something about X") returns a handful of
|
|
365
|
+
* relevant lines instead of a wall of text truncated at an arbitrary,
|
|
366
|
+
* alphabetically-biased cutoff.
|
|
367
|
+
*/
|
|
368
|
+
async function list_skills(args) {
|
|
369
|
+
const all = skills.listSkills();
|
|
370
|
+
const query = (args.query || '').trim().toLowerCase();
|
|
371
|
+
const matched = query
|
|
372
|
+
? all.filter((s) => s.name.toLowerCase().includes(query) || s.description.toLowerCase().includes(query))
|
|
373
|
+
: all;
|
|
374
|
+
|
|
375
|
+
if (query && matched.length === 0) {
|
|
376
|
+
return { ok: true, output: `No skills matched "${query}" out of ${all.length} total.`, meta: { count: 0 } };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const index = skills.formatSkillIndex(matched, { all: true });
|
|
380
|
+
const header = query ? `${matched.length} of ${all.length} skills match "${query}":\n` : `${all.length} skills:\n`;
|
|
381
|
+
return {
|
|
382
|
+
ok: true,
|
|
383
|
+
output: truncate(header + (index || '(none)')),
|
|
384
|
+
meta: { label: query || 'all skills', count: matched.length },
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export const TOOLS = { list_dir, read_file, write_file, edit_file, search, run, use_skill, list_skills };
|
|
332
389
|
|
|
333
390
|
export const TOOL_NEEDS_APPROVAL = new Set(['write_file', 'edit_file', 'run']);
|
|
334
391
|
|
|
@@ -340,6 +397,8 @@ export const TOOL_DISPLAY = {
|
|
|
340
397
|
edit_file: { verb: 'edit', icon: '✎' },
|
|
341
398
|
search: { verb: 'search', icon: '▸' },
|
|
342
399
|
run: { verb: 'run', icon: '$' },
|
|
400
|
+
use_skill: { verb: 'skill', icon: '★' },
|
|
401
|
+
list_skills:{ verb: 'skills', icon: '★' },
|
|
343
402
|
};
|
|
344
403
|
|
|
345
404
|
export async function executeTool(name, args, ctx) {
|
|
@@ -355,17 +414,15 @@ export async function executeTool(name, args, ctx) {
|
|
|
355
414
|
if (capped) {
|
|
356
415
|
const limit = await applyLimit.checkApplyLimit();
|
|
357
416
|
if (!limit.allowed) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
output: limit.error || `Daily apply limit reached (${limit.count}/${limit.limit}). This is shared with the Codeply desktop app and resets at midnight UTC.`,
|
|
361
|
-
};
|
|
417
|
+
const base = limit.error || `Daily apply limit reached (${limit.count}/${limit.limit}). This is shared with the Codeply desktop app and resets at midnight UTC.`;
|
|
418
|
+
return { ok: false, output: `${base} ${config.byokHint()}` };
|
|
362
419
|
}
|
|
363
420
|
}
|
|
364
421
|
|
|
365
422
|
try {
|
|
366
423
|
const result = await fn(args, ctx);
|
|
367
424
|
if (capped && result.ok && result.meta?.wrote) {
|
|
368
|
-
await applyLimit.recordApplyEvent(args.path);
|
|
425
|
+
await applyLimit.recordApplyEvent(args.path, result.meta.added, result.meta.removed);
|
|
369
426
|
}
|
|
370
427
|
return result;
|
|
371
428
|
} catch (e) {
|
package/lib/tui.mjs
CHANGED
|
@@ -26,10 +26,12 @@ import Spinner from 'ink-spinner';
|
|
|
26
26
|
import { createRequire } from 'module';
|
|
27
27
|
import path from 'path';
|
|
28
28
|
import os from 'os';
|
|
29
|
+
import fs from 'fs';
|
|
29
30
|
|
|
30
31
|
const require = createRequire(import.meta.url);
|
|
31
32
|
const auth = require('../lib/auth.js');
|
|
32
33
|
const appConfig = require('../lib/config.js');
|
|
34
|
+
const skills = require('../lib/skills.js');
|
|
33
35
|
const VERSION = require('../package.json').version;
|
|
34
36
|
import {
|
|
35
37
|
renderMarkdown, Panel, hexToAnsi, hexToAnsiBg, fillRow, SURFACE,
|
|
@@ -122,6 +124,8 @@ const COMMANDS = [
|
|
|
122
124
|
{ name: '/help', description: 'Show available commands' },
|
|
123
125
|
{ name: '/clear', description: 'Clear conversation and context' },
|
|
124
126
|
{ name: '/mode', description: 'Cycle Build → Plan → Ask' },
|
|
127
|
+
{ name: '/provider', description: 'Show or switch model provider (BYOK: openrouter, groq, anthropic, openai)' },
|
|
128
|
+
{ name: '/skill', description: 'List, install, or remove agent skills' },
|
|
125
129
|
{ name: '/keys', description: 'Show what your terminal sends for a key' },
|
|
126
130
|
{ name: '/quit', description: 'Exit Codeply' },
|
|
127
131
|
];
|
|
@@ -579,7 +583,7 @@ const App = () => {
|
|
|
579
583
|
});
|
|
580
584
|
|
|
581
585
|
// ── slash commands ──
|
|
582
|
-
const runCommand = async (cmd) => {
|
|
586
|
+
const runCommand = async (cmd, args = '') => {
|
|
583
587
|
if (cmd === '/login') {
|
|
584
588
|
push({ role: 'system', content: 'Opening sign-in…' });
|
|
585
589
|
try {
|
|
@@ -616,6 +620,94 @@ const App = () => {
|
|
|
616
620
|
const next = (modeIndex + 1) % MODES.length;
|
|
617
621
|
setModeIndex(next);
|
|
618
622
|
push({ role: 'system', content: `Mode → ${MODES[next]} — ${MODE_HINT[MODES[next]]}` });
|
|
623
|
+
} else if (cmd === '/provider') {
|
|
624
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
625
|
+
const name = parts[0];
|
|
626
|
+
|
|
627
|
+
if (!name) {
|
|
628
|
+
const cfg = appConfig.getConfig();
|
|
629
|
+
let msg = `Provider: ${appConfig.describeProvider(cfg)}\n` +
|
|
630
|
+
`Providers: ${appConfig.PROVIDERS.join(', ')}\n` +
|
|
631
|
+
'Set with /provider <name> [key] — e.g. /provider openrouter sk-or-v1-...';
|
|
632
|
+
if (cfg.provider === 'codeply') msg += `\n\n${appConfig.byokHint()}`;
|
|
633
|
+
push({ role: 'system', content: msg });
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (!appConfig.PROVIDERS.includes(name)) {
|
|
638
|
+
push({ role: 'error', content: `Unknown provider "${name}". Use one of: ${appConfig.PROVIDERS.join(', ')}.` });
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const key = parts[1];
|
|
643
|
+
const patch = { provider: name };
|
|
644
|
+
if (key && (appConfig.BYOK_PROVIDERS.includes(name) || name === 'ollama')) {
|
|
645
|
+
patch[name] = { apiKey: key };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const saved = appConfig.saveConfig(patch);
|
|
649
|
+
if (!saved.ok) {
|
|
650
|
+
push({ role: 'error', content: `Could not save config: ${saved.error}` });
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
push({ role: 'system', content: `Provider → ${appConfig.describeProvider(appConfig.getConfig())}` });
|
|
654
|
+
} else if (cmd === '/keys') {
|
|
655
|
+
push({ role: 'system', content: 'Press any key (or combination)…' });
|
|
656
|
+
setKeyProbe(true);
|
|
657
|
+
} else if (cmd === '/skill') {
|
|
658
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
659
|
+
const sub = parts[0];
|
|
660
|
+
|
|
661
|
+
if (!sub || sub === 'list') {
|
|
662
|
+
const list = skills.listSkills();
|
|
663
|
+
if (list.length === 0) {
|
|
664
|
+
push({ role: 'system', content: 'No skills available.' });
|
|
665
|
+
} else {
|
|
666
|
+
const lines = list.map((s) => `${s.name.padEnd(28)} [${s.source}] ${s.description}`);
|
|
667
|
+
push({ role: 'system', content: `Skills (${list.length})\n` + lines.join('\n') +
|
|
668
|
+
'\n\n/skill install <path-or-github-url> add one\n/skill remove <name> remove a user-installed one' });
|
|
669
|
+
}
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (sub === 'install') {
|
|
674
|
+
const source = parts.slice(1).join(' ');
|
|
675
|
+
if (!source) { push({ role: 'error', content: 'Usage: /skill install <local-path-or-github-url>' }); return; }
|
|
676
|
+
push({ role: 'system', content: `Installing from ${source}…` });
|
|
677
|
+
try {
|
|
678
|
+
const isLocal = fs.existsSync(source);
|
|
679
|
+
const result = isLocal
|
|
680
|
+
? skills.installFromLocalDir(path.resolve(cwd, source))
|
|
681
|
+
: await skills.installFromGitHub(source);
|
|
682
|
+
if (isLocal) {
|
|
683
|
+
if (!result.ok) push({ role: 'error', content: result.error });
|
|
684
|
+
else push({ role: 'system', content: `Installed "${result.name}" → ${result.dest}` });
|
|
685
|
+
} else if (!result.ok) {
|
|
686
|
+
push({ role: 'error', content: result.error || `No skills installed from ${source}.` });
|
|
687
|
+
} else {
|
|
688
|
+
const names = result.installed.map((i) => i.name).join(', ');
|
|
689
|
+
push({ role: 'system', content: `Installed ${result.installed.length} skill(s) from ${result.source}: ${names}` });
|
|
690
|
+
if (result.failed.length) {
|
|
691
|
+
push({ role: 'error', content: `${result.failed.length} failed: ${result.failed.map((f) => f.dir).join(', ')}` });
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} catch (e) {
|
|
695
|
+
push({ role: 'error', content: `Install failed: ${e.message}` });
|
|
696
|
+
}
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (sub === 'remove') {
|
|
701
|
+
const name = parts[1];
|
|
702
|
+
if (!name) { push({ role: 'error', content: 'Usage: /skill remove <name>' }); return; }
|
|
703
|
+
const result = skills.removeSkill(name);
|
|
704
|
+
push(result.ok
|
|
705
|
+
? { role: 'system', content: `Removed "${name}".` }
|
|
706
|
+
: { role: 'error', content: result.error });
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
push({ role: 'error', content: `Unknown /skill subcommand "${sub}". Use list, install, or remove.` });
|
|
619
711
|
} else if (cmd === '/quit' || cmd === '/exit') {
|
|
620
712
|
exit();
|
|
621
713
|
} else {
|
|
@@ -639,7 +731,8 @@ const App = () => {
|
|
|
639
731
|
setInput('');
|
|
640
732
|
|
|
641
733
|
if (submitValue.startsWith('/')) {
|
|
642
|
-
|
|
734
|
+
const [first, ...rest] = submitValue.split(/\s+/);
|
|
735
|
+
await runCommand(first.toLowerCase(), rest.join(' '));
|
|
643
736
|
return;
|
|
644
737
|
}
|
|
645
738
|
|
|
@@ -713,7 +806,7 @@ const App = () => {
|
|
|
713
806
|
const provider = React.useMemo(() => appConfig.getConfig(), []);
|
|
714
807
|
const usingOllama = provider.provider === 'ollama';
|
|
715
808
|
const account = usingOllama
|
|
716
|
-
? appConfig.
|
|
809
|
+
? appConfig.describeProviderShort(provider)
|
|
717
810
|
: session ? session.user.email : 'not signed in';
|
|
718
811
|
|
|
719
812
|
// Advertise the key that actually inserts a newline in THIS terminal.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeply-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Codeply from the terminal — an interactive AI coding agent, or a single apply <instruction> --file <path> command.",
|
|
5
5
|
"main": "bin/codeply.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
-
"lib"
|
|
11
|
+
"lib",
|
|
12
|
+
"skills"
|
|
12
13
|
],
|
|
13
14
|
"engines": {
|
|
14
15
|
"node": ">=18"
|
package/skills/SOURCE.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Where these skills came from
|
|
2
|
+
|
|
3
|
+
The skill library in this directory — all 281 of them — is vendored from
|
|
4
|
+
[affaan-m/ECC](https://github.com/affaan-m/ECC) ("Every Coding Copilot"), an
|
|
5
|
+
open-source collection of agent skills for Claude Code, Codex, and other
|
|
6
|
+
harnesses. It's their full `skills/` library, not a subset — every `SKILL.md`
|
|
7
|
+
and its `references/*.md` companions, unmodified.
|
|
8
|
+
|
|
9
|
+
Codeply loads its skill format directly from that project's convention: a
|
|
10
|
+
`SKILL.md` per skill with YAML frontmatter (`name`, `description`) and a
|
|
11
|
+
markdown body. See [lib/skills.js](../lib/skills.js) for the loader.
|
|
12
|
+
|
|
13
|
+
Only `SKILL.md` and `references/*.md` were vendored — each skill's
|
|
14
|
+
`agents/openai.yaml` (a tool schema for a different harness's native
|
|
15
|
+
function-calling) was left out, since Codeply calls skills through its own
|
|
16
|
+
text-based action protocol and has no use for it.
|
|
17
|
+
|
|
18
|
+
Not every one of the 281 is loaded into the agent's context by default —
|
|
19
|
+
`lib/skills.js` shows a curated "daily" subset (ECC's own `.agents/skills/`
|
|
20
|
+
curation of its full library) to keep the always-injected index small; the
|
|
21
|
+
rest are still fully installed and reachable with `list_skills <query>` or
|
|
22
|
+
`codeply skill show <name>`.
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
ECC is MIT-licensed. Its license and copyright notice, reproduced here as MIT
|
|
27
|
+
requires when redistributing:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
MIT License
|
|
31
|
+
|
|
32
|
+
Copyright (c) 2026 Affaan Mustafa
|
|
33
|
+
|
|
34
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
35
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
36
|
+
in the Software without restriction, including without limitation the rights
|
|
37
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
38
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
39
|
+
furnished to do so, subject to the following conditions:
|
|
40
|
+
|
|
41
|
+
The above copyright notice and this permission notice shall be included in all
|
|
42
|
+
copies or substantial portions of the Software.
|
|
43
|
+
|
|
44
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
45
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
46
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
47
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
48
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
49
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
50
|
+
SOFTWARE.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Adding your own
|
|
54
|
+
|
|
55
|
+
Anything you drop in `~/.codeply/skills/<name>/SKILL.md` (same frontmatter
|
|
56
|
+
format) is picked up the same way, and takes priority over a bundled skill of
|
|
57
|
+
the same name. `codeply skill install <path-or-github-url>` automates that —
|
|
58
|
+
see `codeply skill --help`.
|