glirastes 0.3.0 → 0.3.2
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/CHANGELOG.md +86 -0
- package/README.md +154 -9
- package/codegen/package.json +5 -0
- package/dist/cjs/codegen/asset-sync.d.ts +20 -0
- package/dist/cjs/codegen/asset-sync.js +28 -0
- package/dist/cjs/codegen/coverage.d.ts +66 -0
- package/dist/cjs/codegen/coverage.js +408 -0
- package/dist/cjs/codegen/endpoint-scanner.d.ts +16 -0
- package/dist/cjs/codegen/endpoint-scanner.js +197 -0
- package/dist/cjs/codegen/format.d.ts +17 -0
- package/dist/cjs/codegen/format.js +34 -0
- package/dist/cjs/codegen/fs-utils.d.ts +45 -0
- package/dist/cjs/codegen/fs-utils.js +165 -0
- package/dist/cjs/codegen/index.d.ts +13 -0
- package/dist/cjs/codegen/index.js +33 -0
- package/dist/cjs/codegen/mcp-generator.d.ts +50 -0
- package/dist/cjs/codegen/mcp-generator.js +803 -0
- package/dist/cjs/codegen/module-generator.d.ts +11 -0
- package/dist/cjs/codegen/module-generator.js +353 -0
- package/dist/cjs/codegen/nestjs-scanner.d.ts +43 -0
- package/dist/cjs/codegen/nestjs-scanner.js +241 -0
- package/dist/cjs/codegen/output-recommender.d.ts +23 -0
- package/dist/cjs/codegen/output-recommender.js +33 -0
- package/dist/cjs/codegen/scaffolder.d.ts +36 -0
- package/dist/cjs/codegen/scaffolder.js +262 -0
- package/dist/cjs/codegen/skill-generator.d.ts +49 -0
- package/dist/cjs/codegen/skill-generator.js +967 -0
- package/dist/cjs/codegen/types.d.ts +52 -0
- package/dist/cjs/codegen/types.js +16 -0
- package/dist/cjs/codegen/ui-scanner.d.ts +24 -0
- package/dist/cjs/codegen/ui-scanner.js +202 -0
- package/dist/cjs/codegen/validator.d.ts +35 -0
- package/dist/cjs/codegen/validator.js +292 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/openapi/index.d.ts +78 -0
- package/dist/cjs/openapi/index.js +347 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/react/audio-worklet-processor.d.ts +16 -0
- package/dist/cjs/react/audio-worklet-processor.js +27 -0
- package/dist/cjs/react/bridges/rest-session-bridge.d.ts +113 -0
- package/dist/cjs/react/bridges/rest-session-bridge.js +209 -0
- package/dist/cjs/react/components/ai-chat-panel.d.ts +12 -0
- package/dist/cjs/react/components/ai-chat-panel.js +67 -0
- package/dist/cjs/react/components/ai-trigger-button.d.ts +2 -0
- package/dist/cjs/react/components/ai-trigger-button.js +239 -0
- package/dist/cjs/react/components/approval-card.d.ts +9 -0
- package/dist/cjs/react/components/approval-card.js +24 -0
- package/dist/cjs/react/components/bulk-approval-card.d.ts +9 -0
- package/dist/cjs/react/components/bulk-approval-card.js +36 -0
- package/dist/cjs/react/components/chat-input.d.ts +10 -0
- package/dist/cjs/react/components/chat-input.js +60 -0
- package/dist/cjs/react/components/chat-window.d.ts +2 -0
- package/dist/cjs/react/components/chat-window.js +134 -0
- package/dist/cjs/react/components/confirm-dialog.d.ts +32 -0
- package/dist/cjs/react/components/confirm-dialog.js +48 -0
- package/dist/cjs/react/components/floating-chat-ui.d.ts +90 -0
- package/dist/cjs/react/components/floating-chat-ui.js +163 -0
- package/dist/cjs/react/components/mention-chip.d.ts +16 -0
- package/dist/cjs/react/components/mention-chip.js +42 -0
- package/dist/cjs/react/components/mention-input.d.ts +8 -0
- package/dist/cjs/react/components/mention-input.js +117 -0
- package/dist/cjs/react/components/message-bubble.d.ts +11 -0
- package/dist/cjs/react/components/message-bubble.js +171 -0
- package/dist/cjs/react/components/message-list.d.ts +8 -0
- package/dist/cjs/react/components/message-list.js +52 -0
- package/dist/cjs/react/components/pipeline-timeline.d.ts +7 -0
- package/dist/cjs/react/components/pipeline-timeline.js +72 -0
- package/dist/cjs/react/components/recording-bar.d.ts +2 -0
- package/dist/cjs/react/components/recording-bar.js +81 -0
- package/dist/cjs/react/components/recording-controls.d.ts +18 -0
- package/dist/cjs/react/components/recording-controls.js +38 -0
- package/dist/cjs/react/components/rich-mention-input.d.ts +2 -0
- package/dist/cjs/react/components/rich-mention-input.js +571 -0
- package/dist/cjs/react/components/session-switcher.d.ts +14 -0
- package/dist/cjs/react/components/session-switcher.js +133 -0
- package/dist/cjs/react/components/suggestion-bar.d.ts +9 -0
- package/dist/cjs/react/components/suggestion-bar.js +47 -0
- package/dist/cjs/react/components/suggestion-chip.d.ts +9 -0
- package/dist/cjs/react/components/suggestion-chip.js +14 -0
- package/dist/cjs/react/components/voice-input-addon.d.ts +13 -0
- package/dist/cjs/react/components/voice-input-addon.js +31 -0
- package/dist/cjs/react/components/voice-input-button.d.ts +2 -0
- package/dist/cjs/react/components/voice-input-button.js +55 -0
- package/dist/cjs/react/components/wavesurfer-loader.d.ts +5 -0
- package/dist/cjs/react/components/wavesurfer-loader.js +36 -0
- package/dist/cjs/react/hooks/use-ai-chat.d.ts +22 -0
- package/dist/cjs/react/hooks/use-ai-chat.js +51 -0
- package/dist/cjs/react/hooks/use-approvals.d.ts +23 -0
- package/dist/cjs/react/hooks/use-approvals.js +63 -0
- package/dist/cjs/react/hooks/use-deepgram-transcription.d.ts +2 -0
- package/dist/cjs/react/hooks/use-deepgram-transcription.js +329 -0
- package/dist/cjs/react/hooks/use-drag-resize-panel.d.ts +48 -0
- package/dist/cjs/react/hooks/use-drag-resize-panel.js +308 -0
- package/dist/cjs/react/hooks/use-draggable-position.d.ts +25 -0
- package/dist/cjs/react/hooks/use-draggable-position.js +134 -0
- package/dist/cjs/react/hooks/use-keyboard-shortcut.d.ts +20 -0
- package/dist/cjs/react/hooks/use-keyboard-shortcut.js +93 -0
- package/dist/cjs/react/hooks/use-mentions.d.ts +23 -0
- package/dist/cjs/react/hooks/use-mentions.js +42 -0
- package/dist/cjs/react/hooks/use-suggestions.d.ts +22 -0
- package/dist/cjs/react/hooks/use-suggestions.js +39 -0
- package/dist/cjs/react/index.d.ts +43 -0
- package/dist/cjs/react/index.js +111 -0
- package/dist/cjs/react/provider/ai-chat-provider.d.ts +45 -0
- package/dist/cjs/react/provider/ai-chat-provider.js +1024 -0
- package/dist/cjs/react/provider/chat-context.d.ts +84 -0
- package/dist/cjs/react/provider/chat-context.js +13 -0
- package/dist/cjs/react/template.d.ts +84 -0
- package/dist/cjs/react/template.js +90 -0
- package/dist/cjs/react/themes.d.ts +24 -0
- package/dist/cjs/react/themes.js +273 -0
- package/dist/cjs/react/transports/langgraph/index.d.ts +2 -0
- package/dist/cjs/react/transports/langgraph/index.js +8 -0
- package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.d.ts +62 -0
- package/dist/cjs/react/transports/langgraph/langgraph-ai-chat.js +94 -0
- package/dist/cjs/react/transports/langgraph.d.ts +71 -0
- package/dist/cjs/react/transports/langgraph.js +369 -0
- package/dist/cjs/react/transports/vercel/index.d.ts +2 -0
- package/dist/cjs/react/transports/vercel/index.js +8 -0
- package/dist/cjs/react/transports/vercel/vercel-ai-chat.d.ts +49 -0
- package/dist/cjs/react/transports/vercel/vercel-ai-chat.js +81 -0
- package/dist/cjs/react/transports/vercel.d.ts +30 -0
- package/dist/cjs/react/transports/vercel.js +69 -0
- package/dist/cjs/react/types.d.ts +760 -0
- package/dist/cjs/react/types.js +2 -0
- package/dist/cjs/react/ui.d.ts +73 -0
- package/dist/cjs/react/ui.js +183 -0
- package/dist/cjs/react/utils/create-approval-description.d.ts +53 -0
- package/dist/cjs/react/utils/create-approval-description.js +122 -0
- package/dist/cjs/react/utils/create-tool-result-handler.d.ts +90 -0
- package/dist/cjs/react/utils/create-tool-result-handler.js +168 -0
- package/dist/cjs/react/utils/mention-markup.d.ts +13 -0
- package/dist/cjs/react/utils/mention-markup.js +117 -0
- package/dist/cjs/react/utils/merge-pipeline-steps.d.ts +19 -0
- package/dist/cjs/react/utils/merge-pipeline-steps.js +81 -0
- package/dist/cjs/react/utils/sanitize-stored-messages.d.ts +23 -0
- package/dist/cjs/react/utils/sanitize-stored-messages.js +52 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.d.ts +10 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-auth.guard.js +42 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.d.ts +32 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat-module-options.interface.js +4 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.d.ts +11 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.controller.js +80 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.d.ts +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.module.js +119 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.d.ts +13 -0
- package/dist/cjs/server/adapters/nestjs/chat/ai-chat.service.js +131 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.d.ts +7 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/ai-chat-request.dto.js +52 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.d.ts +19 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message-part.dto.js +108 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.d.ts +8 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/chat-message.dto.js +59 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/index.d.ts +3 -0
- package/dist/cjs/server/adapters/nestjs/chat/dto/index.js +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/index.d.ts +3 -0
- package/dist/cjs/server/adapters/nestjs/chat/index.js +9 -0
- package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +4 -0
- package/dist/cjs/server/adapters/nestjs/chat/speech-stream.gateway.js +73 -0
- package/dist/cjs/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
- package/dist/cjs/server/adapters/nestjs/chat/ws-loader.js +72 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.d.ts +6 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-module.decorator.js +13 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.d.ts +5 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-param.decorator.js +15 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.d.ts +6 -0
- package/dist/cjs/server/adapters/nestjs/decorators/ai-tool.decorator.js +22 -0
- package/dist/cjs/server/adapters/nestjs/decorators/constants.d.ts +4 -0
- package/dist/cjs/server/adapters/nestjs/decorators/constants.js +7 -0
- package/dist/cjs/server/adapters/nestjs/index.d.ts +14 -0
- package/dist/cjs/server/adapters/nestjs/index.js +42 -0
- package/dist/cjs/server/adapters/nestjs/metadata.d.ts +39 -0
- package/dist/cjs/server/adapters/nestjs/metadata.js +36 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.d.ts +36 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools-explorer.service.js +159 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.d.ts +37 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.module.js +44 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.d.ts +28 -0
- package/dist/cjs/server/adapters/nestjs/module/ai-tools.service.js +41 -0
- package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.d.ts +16 -0
- package/dist/cjs/server/adapters/nestjs/scanner/nestjs-scanner.js +117 -0
- package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.d.ts +26 -0
- package/dist/cjs/server/adapters/nestjs/scanner/schema-builder.js +259 -0
- package/dist/cjs/server/adapters/nestjs/swagger.d.ts +7 -0
- package/dist/cjs/server/adapters/nestjs/swagger.js +39 -0
- package/dist/cjs/server/adapters/nextjs/context-helpers.d.ts +22 -0
- package/dist/cjs/server/adapters/nextjs/context-helpers.js +60 -0
- package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.d.ts +94 -0
- package/dist/cjs/server/adapters/nextjs/create-ai-chat-handler.js +965 -0
- package/dist/cjs/server/adapters/nextjs/filter-fallback-error.d.ts +11 -0
- package/dist/cjs/server/adapters/nextjs/filter-fallback-error.js +41 -0
- package/dist/cjs/server/adapters/nextjs/index.d.ts +8 -0
- package/dist/cjs/server/adapters/nextjs/index.js +22 -0
- package/dist/cjs/server/adapters/nextjs/prepare-model-messages.d.ts +58 -0
- package/dist/cjs/server/adapters/nextjs/prepare-model-messages.js +114 -0
- package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.d.ts +64 -0
- package/dist/cjs/server/adapters/nextjs/sanitize-model-messages.js +489 -0
- package/dist/cjs/server/adapters/nextjs/server-action-helper.d.ts +7 -0
- package/dist/cjs/server/adapters/nextjs/server-action-helper.js +40 -0
- package/dist/cjs/server/adapters/nextjs/types.d.ts +246 -0
- package/dist/cjs/server/adapters/nextjs/types.js +2 -0
- package/dist/cjs/server/adapters/nextjs/with-ai-tool.d.ts +9 -0
- package/dist/cjs/server/adapters/nextjs/with-ai-tool.js +12 -0
- package/dist/cjs/server/core/followup-tool.d.ts +9 -0
- package/dist/cjs/server/core/followup-tool.js +82 -0
- package/dist/cjs/server/core/index.d.ts +139 -0
- package/dist/cjs/server/core/index.js +818 -0
- package/dist/cjs/server/core/intent-classifier.d.ts +94 -0
- package/dist/cjs/server/core/intent-classifier.js +324 -0
- package/dist/cjs/server/core/model-selector.d.ts +12 -0
- package/dist/cjs/server/core/model-selector.js +48 -0
- package/dist/cjs/server/core/module-router.d.ts +14 -0
- package/dist/cjs/server/core/module-router.js +110 -0
- package/dist/cjs/server/core/zod-compact.d.ts +18 -0
- package/dist/cjs/server/core/zod-compact.js +129 -0
- package/dist/cjs/server/index.d.ts +6 -0
- package/dist/cjs/server/index.js +28 -0
- package/dist/cjs/server/lancer/cache.d.ts +9 -0
- package/dist/cjs/server/lancer/cache.js +33 -0
- package/dist/cjs/server/lancer/client.d.ts +2 -0
- package/dist/cjs/server/lancer/client.js +279 -0
- package/dist/cjs/server/lancer/errors.d.ts +13 -0
- package/dist/cjs/server/lancer/errors.js +21 -0
- package/dist/cjs/server/lancer/index.d.ts +3 -0
- package/dist/cjs/server/lancer/index.js +7 -0
- package/dist/cjs/server/lancer/telemetry-buffer.d.ts +15 -0
- package/dist/cjs/server/lancer/telemetry-buffer.js +64 -0
- package/dist/cjs/server/lancer/types.d.ts +201 -0
- package/dist/cjs/server/lancer/types.js +5 -0
- package/dist/cjs/server/pii-shield/aegis-detector.d.ts +26 -0
- package/dist/cjs/server/pii-shield/aegis-detector.js +26 -0
- package/dist/cjs/server/pii-shield/create-pii-shield.d.ts +2 -0
- package/dist/cjs/server/pii-shield/create-pii-shield.js +191 -0
- package/dist/cjs/server/pii-shield/deep-walk.d.ts +1 -0
- package/dist/cjs/server/pii-shield/deep-walk.js +19 -0
- package/dist/cjs/server/pii-shield/fake-data.d.ts +5 -0
- package/dist/cjs/server/pii-shield/fake-data.js +170 -0
- package/dist/cjs/server/pii-shield/index.d.ts +4 -0
- package/dist/cjs/server/pii-shield/index.js +9 -0
- package/dist/cjs/server/pii-shield/leakage.d.ts +1 -0
- package/dist/cjs/server/pii-shield/leakage.js +15 -0
- package/dist/cjs/server/pii-shield/session-mapping.d.ts +12 -0
- package/dist/cjs/server/pii-shield/session-mapping.js +70 -0
- package/dist/cjs/server/pipeline.d.ts +26 -0
- package/dist/cjs/server/pipeline.js +127 -0
- package/dist/cjs/server/testing/approvals.d.ts +11 -0
- package/dist/cjs/server/testing/approvals.js +41 -0
- package/dist/cjs/server/testing/combination.d.ts +3 -0
- package/dist/cjs/server/testing/combination.js +83 -0
- package/dist/cjs/server/testing/create-test-suite.d.ts +29 -0
- package/dist/cjs/server/testing/create-test-suite.js +81 -0
- package/dist/cjs/server/testing/edge-cases.d.ts +8 -0
- package/dist/cjs/server/testing/edge-cases.js +277 -0
- package/dist/cjs/server/testing/guardrails.d.ts +14 -0
- package/dist/cjs/server/testing/guardrails.js +152 -0
- package/dist/cjs/server/testing/helpers.d.ts +14 -0
- package/dist/cjs/server/testing/helpers.js +119 -0
- package/dist/cjs/server/testing/index.d.ts +12 -0
- package/dist/cjs/server/testing/index.js +37 -0
- package/dist/cjs/server/testing/mock-executor.d.ts +8 -0
- package/dist/cjs/server/testing/mock-executor.js +40 -0
- package/dist/cjs/server/testing/regression.d.ts +7 -0
- package/dist/cjs/server/testing/regression.js +27 -0
- package/dist/cjs/server/testing/routing.d.ts +12 -0
- package/dist/cjs/server/testing/routing.js +63 -0
- package/dist/cjs/server/testing/schema-consistency.d.ts +6 -0
- package/dist/cjs/server/testing/schema-consistency.js +56 -0
- package/dist/cjs/server/testing/smoke.d.ts +11 -0
- package/dist/cjs/server/testing/smoke.js +64 -0
- package/dist/cjs/server/testing/types.d.ts +156 -0
- package/dist/cjs/server/testing/types.js +2 -0
- package/dist/cjs/types.d.ts +696 -0
- package/dist/cjs/types.js +91 -0
- package/dist/cli/commands/install-skills.d.ts +17 -0
- package/dist/cli/commands/install-skills.js +185 -0
- package/dist/cli/commands/install-skills.js.map +1 -0
- package/dist/cli/index.js +25 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/react/components/ai-trigger-button.js +4 -6
- package/dist/react/components/ai-trigger-button.js.map +1 -1
- package/dist/react/components/recording-bar.js +4 -6
- package/dist/react/components/recording-bar.js.map +1 -1
- package/dist/react/components/wavesurfer-loader.d.ts +5 -0
- package/dist/react/components/wavesurfer-loader.js +34 -0
- package/dist/react/components/wavesurfer-loader.js.map +1 -0
- package/dist/server/adapters/nestjs/chat/ai-chat.module.d.ts +1 -1
- package/dist/server/adapters/nestjs/chat/ai-chat.module.js +3 -2
- package/dist/server/adapters/nestjs/chat/ai-chat.module.js.map +1 -1
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.d.ts +1 -1
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js +10 -8
- package/dist/server/adapters/nestjs/chat/speech-stream.gateway.js.map +1 -1
- package/dist/server/adapters/nestjs/chat/ws-loader.d.ts +5 -0
- package/dist/server/adapters/nestjs/chat/ws-loader.js +37 -0
- package/dist/server/adapters/nestjs/chat/ws-loader.js.map +1 -0
- package/openapi/package.json +5 -0
- package/package.json +59 -16
- package/react/langgraph/package.json +5 -0
- package/react/package.json +5 -0
- package/react/vercel/package.json +5 -0
- package/server/nestjs/package.json +5 -0
- package/server/nextjs/package.json +5 -0
- package/server/package.json +5 -0
- package/server/testing/package.json +5 -0
- package/skills/README.md +107 -0
- package/skills/building-glirastes-chat-ui/SKILL.md +378 -0
- package/skills/integrating-glirastes-nestjs/SKILL.md +346 -0
- package/skills/integrating-glirastes-nextjs/SKILL.md +312 -0
- package/skills/integrating-glirastes-nextjs-with-nestjs-backend/SKILL.md +359 -0
- package/skills/maintaining-glirastes-tools/SKILL.md +467 -0
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAgentSkills = generateAgentSkills;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const fs_utils_js_1 = require("./fs-utils.js");
|
|
7
|
+
const endpoint_scanner_js_1 = require("./endpoint-scanner.js");
|
|
8
|
+
const ui_scanner_js_1 = require("./ui-scanner.js");
|
|
9
|
+
const types_js_1 = require("./types.js");
|
|
10
|
+
/**
|
|
11
|
+
* Extract tool information from a source file for skill generation.
|
|
12
|
+
* Parses defineEndpointTool / defineUiTool blocks via regex.
|
|
13
|
+
*/
|
|
14
|
+
async function extractSkillTools(absolutePath) {
|
|
15
|
+
const content = await (0, fs_utils_js_1.readFileSafe)(absolutePath);
|
|
16
|
+
const tools = [];
|
|
17
|
+
// Match endpoint tool blocks
|
|
18
|
+
const endpointBlocks = (0, fs_utils_js_1.extractBalancedBlocks)(content, /define(?:Ai)?EndpointTool\(\s*/);
|
|
19
|
+
for (const block of endpointBlocks) {
|
|
20
|
+
const tool = parseToolBlock(block, 'endpoint');
|
|
21
|
+
if (tool)
|
|
22
|
+
tools.push(tool);
|
|
23
|
+
}
|
|
24
|
+
// Match UI tool blocks (they don't have method/path, so we synthesize POST + /api/ui/{toolName})
|
|
25
|
+
const uiBlocks = (0, fs_utils_js_1.extractBalancedBlocks)(content, /define(?:Ai)?UiTool\(\s*/);
|
|
26
|
+
for (const block of uiBlocks) {
|
|
27
|
+
const tool = parseToolBlock(block, 'ui');
|
|
28
|
+
if (tool)
|
|
29
|
+
tools.push(tool);
|
|
30
|
+
}
|
|
31
|
+
return tools;
|
|
32
|
+
}
|
|
33
|
+
function parseToolBlock(block, kind) {
|
|
34
|
+
const toolNameMatch = block.match(/toolName\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
35
|
+
if (!toolNameMatch)
|
|
36
|
+
return null;
|
|
37
|
+
// Match description — handle backtick templates (multiline) and regular quotes
|
|
38
|
+
const descMatch = block.match(/description\s*:\s*`([^`]+)`/) ??
|
|
39
|
+
block.match(/description\s*:\s*['"]([^'"]+)['"]/);
|
|
40
|
+
const methodMatch = block.match(/method\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
41
|
+
const pathMatch = block.match(/path\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
42
|
+
const moduleMatch = block.match(/module\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
43
|
+
const approvalMatch = block.match(/needsApproval\s*:\s*(true|false)/);
|
|
44
|
+
// Extract allowedRoles
|
|
45
|
+
const rolesMatch = block.match(/allowedRoles\s*:\s*\[([^\]]*)\]/);
|
|
46
|
+
let allowedRoles;
|
|
47
|
+
if (rolesMatch) {
|
|
48
|
+
allowedRoles = rolesMatch[1]
|
|
49
|
+
.match(/['"`]([^'"`]+)['"`]/g)
|
|
50
|
+
?.map((s) => s.replace(/['"`]/g, ''));
|
|
51
|
+
}
|
|
52
|
+
// Extract input fields from z.object({...})
|
|
53
|
+
const inputFields = extractZodFields(block, 'inputSchema');
|
|
54
|
+
const outputFields = extractZodFields(block, 'outputSchema');
|
|
55
|
+
const toolName = toolNameMatch[1];
|
|
56
|
+
return {
|
|
57
|
+
toolName,
|
|
58
|
+
description: descMatch?.[1] ?? `${toolName} tool`,
|
|
59
|
+
method: methodMatch?.[1] ?? (kind === 'ui' ? 'POST' : 'GET'),
|
|
60
|
+
path: pathMatch?.[1] ?? (kind === 'ui' ? `/api/ui/${toolName}` : `/api/${toolName}`),
|
|
61
|
+
module: moduleMatch?.[1],
|
|
62
|
+
allowedRoles,
|
|
63
|
+
needsApproval: approvalMatch ? approvalMatch[1] === 'true' : undefined,
|
|
64
|
+
inputFields,
|
|
65
|
+
outputFields,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract field names and approximate types from a Zod schema block.
|
|
70
|
+
*/
|
|
71
|
+
function extractZodFields(block, schemaKey) {
|
|
72
|
+
const fields = [];
|
|
73
|
+
// Find the schema block: inputSchema: z.object({...})
|
|
74
|
+
const schemaPattern = new RegExp(`${schemaKey}\\s*:\\s*z\\.object\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)`);
|
|
75
|
+
const schemaMatch = block.match(schemaPattern);
|
|
76
|
+
if (!schemaMatch)
|
|
77
|
+
return fields;
|
|
78
|
+
const schemaContent = schemaMatch[1];
|
|
79
|
+
// Match field definitions: fieldName: z.string(), fieldName: z.number().optional(), etc.
|
|
80
|
+
const fieldPattern = /(\w+)\s*:\s*z\.(\w+)\((.*?)\)((?:\.\w+\([^)]*\))*)/g;
|
|
81
|
+
let fieldMatch;
|
|
82
|
+
while ((fieldMatch = fieldPattern.exec(schemaContent)) !== null) {
|
|
83
|
+
const name = fieldMatch[1];
|
|
84
|
+
const zodType = fieldMatch[2];
|
|
85
|
+
const chainedMethods = fieldMatch[4] ?? '';
|
|
86
|
+
const isOptional = chainedMethods.includes('.optional()');
|
|
87
|
+
const descriptionMatch = chainedMethods.match(/\.describe\(\s*['"`]([^'"`]+)['"`]\s*\)/);
|
|
88
|
+
const typeMap = {
|
|
89
|
+
string: 'string',
|
|
90
|
+
number: 'number',
|
|
91
|
+
boolean: 'boolean',
|
|
92
|
+
date: 'string (ISO date)',
|
|
93
|
+
enum: 'string (enum)',
|
|
94
|
+
array: 'array',
|
|
95
|
+
object: 'object',
|
|
96
|
+
uuid: 'string (UUID)',
|
|
97
|
+
};
|
|
98
|
+
fields.push({
|
|
99
|
+
name,
|
|
100
|
+
type: typeMap[zodType] ?? zodType,
|
|
101
|
+
required: !isOptional,
|
|
102
|
+
description: descriptionMatch?.[1],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return fields;
|
|
106
|
+
}
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Markdown Skill Generation (Claude Code format)
|
|
109
|
+
// ============================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Compact tool reference for module files.
|
|
112
|
+
* ~40% fewer lines than the original toolToMarkdown.
|
|
113
|
+
*/
|
|
114
|
+
function toolToCompactMarkdown(tool) {
|
|
115
|
+
const lines = [];
|
|
116
|
+
lines.push(`### ${tool.toolName} — \`${tool.method} ${tool.path}\``);
|
|
117
|
+
lines.push(tool.description);
|
|
118
|
+
const meta = [];
|
|
119
|
+
if (tool.allowedRoles?.length)
|
|
120
|
+
meta.push(`Roles: ${tool.allowedRoles.join(', ')}`);
|
|
121
|
+
if (tool.needsApproval)
|
|
122
|
+
meta.push('Requires approval');
|
|
123
|
+
if (meta.length)
|
|
124
|
+
lines.push(meta.join(' | '));
|
|
125
|
+
if (tool.inputFields.length > 0) {
|
|
126
|
+
lines.push('');
|
|
127
|
+
lines.push('| Param | Type | Req | Description |');
|
|
128
|
+
lines.push('|-------|------|-----|-------------|');
|
|
129
|
+
for (const f of tool.inputFields) {
|
|
130
|
+
lines.push(`| ${f.name} | ${f.type} | ${f.required ? 'yes' : '-'} | ${f.description ?? ''} |`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (tool.outputFields.length > 0) {
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push('**Response:**');
|
|
136
|
+
for (const field of tool.outputFields) {
|
|
137
|
+
lines.push(`- \`${field.name}\`: ${field.type}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
lines.push('');
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Original verbose tool markdown (kept for monolithic fallback).
|
|
145
|
+
*/
|
|
146
|
+
function toolToMarkdown(tool, baseUrl) {
|
|
147
|
+
const lines = [];
|
|
148
|
+
lines.push(`### \`${tool.toolName}\``);
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push(tool.description);
|
|
151
|
+
lines.push('');
|
|
152
|
+
lines.push(`- **Method:** \`${tool.method}\``);
|
|
153
|
+
lines.push(`- **Endpoint:** \`${baseUrl}${tool.path}\``);
|
|
154
|
+
if (tool.module) {
|
|
155
|
+
lines.push(`- **Module:** ${tool.module}`);
|
|
156
|
+
}
|
|
157
|
+
if (tool.needsApproval) {
|
|
158
|
+
lines.push(`- **Requires approval:** yes`);
|
|
159
|
+
}
|
|
160
|
+
if (tool.allowedRoles?.length) {
|
|
161
|
+
lines.push(`- **Allowed roles:** ${tool.allowedRoles.join(', ')}`);
|
|
162
|
+
}
|
|
163
|
+
if (tool.inputFields.length > 0) {
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push('**Parameters:**');
|
|
166
|
+
lines.push('');
|
|
167
|
+
lines.push('| Name | Type | Required | Description |');
|
|
168
|
+
lines.push('|------|------|----------|-------------|');
|
|
169
|
+
for (const field of tool.inputFields) {
|
|
170
|
+
lines.push(`| \`${field.name}\` | ${field.type} | ${field.required ? 'yes' : 'no'} | ${field.description ?? ''} |`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (tool.outputFields.length > 0) {
|
|
174
|
+
lines.push('');
|
|
175
|
+
lines.push('**Response fields:**');
|
|
176
|
+
lines.push('');
|
|
177
|
+
for (const field of tool.outputFields) {
|
|
178
|
+
lines.push(`- \`${field.name}\`: ${field.type}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
lines.push('');
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Build a CSO-optimized description for the router SKILL.md.
|
|
186
|
+
* Lists concrete capabilities the user might ask about.
|
|
187
|
+
*/
|
|
188
|
+
function buildRouterDescription(config, byModule) {
|
|
189
|
+
const capabilities = [];
|
|
190
|
+
for (const [moduleName, moduleTools] of byModule) {
|
|
191
|
+
const label = moduleName.replace(/_/g, ' ');
|
|
192
|
+
const verbs = moduleTools
|
|
193
|
+
.slice(0, 3)
|
|
194
|
+
.map((t) => t.toolName.replace(/_/g, ' '))
|
|
195
|
+
.join(', ');
|
|
196
|
+
capabilities.push(`${label} (${verbs})`);
|
|
197
|
+
}
|
|
198
|
+
const authHint = config.auth.type === 'bearer'
|
|
199
|
+
? `Requires ${config.auth.tokenEnvVar} env var.`
|
|
200
|
+
: config.auth.type === 'api-key'
|
|
201
|
+
? `Requires ${config.auth.keyEnvVar} env var.`
|
|
202
|
+
: '';
|
|
203
|
+
// Extract user-facing action verbs from tool names for CSO
|
|
204
|
+
const actionVerbs = new Set();
|
|
205
|
+
for (const [, moduleTools] of byModule) {
|
|
206
|
+
for (const t of moduleTools) {
|
|
207
|
+
const verb = t.toolName.split('_')[0];
|
|
208
|
+
if (['list', 'get', 'create', 'update', 'delete', 'assign', 'sync', 'navigate', 'open', 'bulk'].includes(verb)) {
|
|
209
|
+
actionVerbs.add(verb);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const parts = [
|
|
214
|
+
`Manages ${capabilities.map(c => c.split(' (')[0]).join(', ')} in ${config.appName}.`,
|
|
215
|
+
`Use when user asks to ${Array.from(actionVerbs).slice(0, 6).join('/')} data.`,
|
|
216
|
+
`Covers: ${capabilities.join('; ')}.`,
|
|
217
|
+
authHint,
|
|
218
|
+
config.description ?? '',
|
|
219
|
+
].filter(Boolean);
|
|
220
|
+
return parts.join(' ').slice(0, 1024);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Build a CSO-optimized description for a module file.
|
|
224
|
+
*/
|
|
225
|
+
function buildModuleDescription(config, moduleName, moduleTools) {
|
|
226
|
+
const readCount = moduleTools.filter((t) => t.method === 'GET').length;
|
|
227
|
+
const writeCount = moduleTools.length - readCount;
|
|
228
|
+
const label = moduleName.replace(/_/g, ' ');
|
|
229
|
+
const verbs = moduleTools.map((t) => t.toolName.replace(/_/g, ' ')).join(', ');
|
|
230
|
+
const typeSummary = [
|
|
231
|
+
readCount > 0 ? `${readCount} read` : '',
|
|
232
|
+
writeCount > 0 ? `${writeCount} write` : '',
|
|
233
|
+
].filter(Boolean).join(', ');
|
|
234
|
+
return `${label} module for ${config.appName} API. Use when: ${verbs}. ${typeSummary} endpoint${moduleTools.length !== 1 ? 's' : ''}.`.slice(0, 1024);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Generate the router SKILL.md that links to per-module files.
|
|
238
|
+
*/
|
|
239
|
+
function generateRouterSkill(config, tools, byModule) {
|
|
240
|
+
const lines = [];
|
|
241
|
+
const slug = slugify(config.appName);
|
|
242
|
+
const readTools = tools.filter((t) => t.method === 'GET');
|
|
243
|
+
const writeTools = tools.filter((t) => t.method !== 'GET');
|
|
244
|
+
// Collect all module keywords for triggers
|
|
245
|
+
const allKeywords = [];
|
|
246
|
+
for (const [moduleName, moduleTools] of byModule) {
|
|
247
|
+
allKeywords.push(moduleName.replace(/_/g, ' '));
|
|
248
|
+
for (const t of moduleTools) {
|
|
249
|
+
allKeywords.push(t.toolName.replace(/_/g, ' '));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const description = buildRouterDescription(config, byModule);
|
|
253
|
+
// ── YAML Frontmatter ──
|
|
254
|
+
lines.push('---');
|
|
255
|
+
lines.push(`name: managing-${slug}`);
|
|
256
|
+
lines.push(`description: ${description}`);
|
|
257
|
+
lines.push(`compatibility: Requires curl and jq. Needs network access to ${config.baseUrl}.`);
|
|
258
|
+
lines.push(`allowed-tools: "Bash(curl:*) Bash(jq:*)"`);
|
|
259
|
+
lines.push('---');
|
|
260
|
+
lines.push('');
|
|
261
|
+
// ── Title & Overview ──
|
|
262
|
+
lines.push(`# ${config.appName} API`);
|
|
263
|
+
lines.push('');
|
|
264
|
+
lines.push(`**${tools.length} tools** (${readTools.length} read, ${writeTools.length} write) across **${byModule.size} module(s)**.`);
|
|
265
|
+
lines.push('');
|
|
266
|
+
lines.push(`**Base URL:** \`${config.baseUrl}\``);
|
|
267
|
+
lines.push('');
|
|
268
|
+
// ── Authentication ──
|
|
269
|
+
lines.push('## Authentication');
|
|
270
|
+
lines.push('');
|
|
271
|
+
lines.push(generateAuthInstructions(config.auth));
|
|
272
|
+
lines.push('');
|
|
273
|
+
// ── Module Overview Table ──
|
|
274
|
+
lines.push('## Modules');
|
|
275
|
+
lines.push('');
|
|
276
|
+
lines.push('| Module | Tools | Read | Write | Key tools |');
|
|
277
|
+
lines.push('|--------|-------|------|-------|-----------|');
|
|
278
|
+
for (const [moduleName, moduleTools] of byModule) {
|
|
279
|
+
const modSlug = moduleName.replace(/_/g, '-');
|
|
280
|
+
const readCount = moduleTools.filter((t) => t.method === 'GET').length;
|
|
281
|
+
const writeCount = moduleTools.length - readCount;
|
|
282
|
+
const keyTools = moduleTools.slice(0, 3).map((t) => `\`${t.toolName}\``).join(', ');
|
|
283
|
+
lines.push(`| [${moduleName}](${modSlug}.md) | ${moduleTools.length} | ${readCount} | ${writeCount} | ${keyTools} |`);
|
|
284
|
+
}
|
|
285
|
+
lines.push('');
|
|
286
|
+
// ── Request Format ──
|
|
287
|
+
lines.push('## Making Requests');
|
|
288
|
+
lines.push('');
|
|
289
|
+
lines.push('```bash');
|
|
290
|
+
lines.push(`# GET request (read operations)`);
|
|
291
|
+
lines.push(`curl -s -H "Authorization: Bearer $TOKEN" \\`);
|
|
292
|
+
lines.push(` "${config.baseUrl}/api/<path>" | jq .`);
|
|
293
|
+
lines.push('');
|
|
294
|
+
lines.push(`# POST/PATCH/PUT request (write operations)`);
|
|
295
|
+
lines.push(`curl -s -X POST -H "Authorization: Bearer $TOKEN" \\`);
|
|
296
|
+
lines.push(` -H "Content-Type: application/json" \\`);
|
|
297
|
+
lines.push(` -d '{"key": "value"}' \\`);
|
|
298
|
+
lines.push(` "${config.baseUrl}/api/<path>" | jq .`);
|
|
299
|
+
lines.push('```');
|
|
300
|
+
lines.push('');
|
|
301
|
+
// ── Destructive Action Warning ──
|
|
302
|
+
if (writeTools.length > 0) {
|
|
303
|
+
lines.push('## Write Operations');
|
|
304
|
+
lines.push('');
|
|
305
|
+
lines.push('Before executing write operations (POST, PATCH, PUT, DELETE):');
|
|
306
|
+
lines.push('');
|
|
307
|
+
lines.push('1. Show the user what data will be modified');
|
|
308
|
+
lines.push('2. If `needsApproval` is marked for the tool, **always ask for confirmation**');
|
|
309
|
+
lines.push('3. After a successful write, report what changed');
|
|
310
|
+
lines.push('');
|
|
311
|
+
}
|
|
312
|
+
// ── Troubleshooting ──
|
|
313
|
+
lines.push('## Troubleshooting');
|
|
314
|
+
lines.push('');
|
|
315
|
+
lines.push('### Error: 401 Unauthorized');
|
|
316
|
+
lines.push('Cause: Token is missing, expired, or invalid.');
|
|
317
|
+
lines.push('Solution: Re-authenticate and obtain a fresh token (see Authentication).');
|
|
318
|
+
lines.push('');
|
|
319
|
+
lines.push('### Error: 403 Forbidden');
|
|
320
|
+
lines.push('Cause: The current user role does not have permission for this tool.');
|
|
321
|
+
lines.push('Solution: Check the `allowedRoles` for the tool and verify the user has the required role.');
|
|
322
|
+
lines.push('');
|
|
323
|
+
lines.push('### Error: 422 Validation Error');
|
|
324
|
+
lines.push('Cause: Request body does not match the expected schema.');
|
|
325
|
+
lines.push('Solution: Check the parameter table for the tool and ensure all required fields are provided with correct types.');
|
|
326
|
+
lines.push('');
|
|
327
|
+
return lines.join('\n');
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Generate a per-module skill file with compact tool references.
|
|
331
|
+
*/
|
|
332
|
+
function generateModuleSkill(config, moduleName, moduleTools) {
|
|
333
|
+
const lines = [];
|
|
334
|
+
const slug = slugify(config.appName);
|
|
335
|
+
const modSlug = moduleName.replace(/_/g, '-');
|
|
336
|
+
const description = buildModuleDescription(config, moduleName, moduleTools);
|
|
337
|
+
const readTools = moduleTools.filter((t) => t.method === 'GET');
|
|
338
|
+
const writeTools = moduleTools.filter((t) => t.method !== 'GET');
|
|
339
|
+
// ── YAML Frontmatter ──
|
|
340
|
+
lines.push('---');
|
|
341
|
+
lines.push(`name: ${slug}-api-${modSlug}`);
|
|
342
|
+
lines.push(`description: ${description}`);
|
|
343
|
+
lines.push('---');
|
|
344
|
+
lines.push('');
|
|
345
|
+
// ── Title ──
|
|
346
|
+
const label = moduleName.replace(/_/g, ' ');
|
|
347
|
+
lines.push(`# ${label}`);
|
|
348
|
+
lines.push('');
|
|
349
|
+
lines.push(`${moduleTools.length} tools (${readTools.length} read, ${writeTools.length} write) | Base URL: \`${config.baseUrl}\``);
|
|
350
|
+
lines.push('');
|
|
351
|
+
// ── Tool Reference ──
|
|
352
|
+
lines.push('## Tools');
|
|
353
|
+
lines.push('');
|
|
354
|
+
for (const tool of moduleTools) {
|
|
355
|
+
lines.push(toolToCompactMarkdown(tool));
|
|
356
|
+
}
|
|
357
|
+
// ── Examples ──
|
|
358
|
+
const exampleReadTool = readTools[0];
|
|
359
|
+
const exampleWriteTool = writeTools[0];
|
|
360
|
+
if (exampleReadTool || exampleWriteTool) {
|
|
361
|
+
lines.push('## Examples');
|
|
362
|
+
lines.push('');
|
|
363
|
+
if (exampleReadTool) {
|
|
364
|
+
lines.push(`### Read: ${exampleReadTool.toolName.replace(/_/g, ' ')}`);
|
|
365
|
+
lines.push('');
|
|
366
|
+
lines.push('```bash');
|
|
367
|
+
lines.push(`curl -s -H "Authorization: Bearer $TOKEN" \\`);
|
|
368
|
+
lines.push(` "${config.baseUrl}${exampleReadTool.path}" | jq .`);
|
|
369
|
+
lines.push('```');
|
|
370
|
+
lines.push('');
|
|
371
|
+
}
|
|
372
|
+
if (exampleWriteTool) {
|
|
373
|
+
const bodyFields = exampleWriteTool.inputFields
|
|
374
|
+
.filter((f) => f.required)
|
|
375
|
+
.map((f) => `"${f.name}": "<value>"`)
|
|
376
|
+
.join(', ');
|
|
377
|
+
lines.push(`### Write: ${exampleWriteTool.toolName.replace(/_/g, ' ')}`);
|
|
378
|
+
lines.push('');
|
|
379
|
+
lines.push('```bash');
|
|
380
|
+
lines.push(`curl -s -X ${exampleWriteTool.method} -H "Authorization: Bearer $TOKEN" \\`);
|
|
381
|
+
lines.push(` -H "Content-Type: application/json" \\`);
|
|
382
|
+
lines.push(` -d '{${bodyFields}}' \\`);
|
|
383
|
+
lines.push(` "${config.baseUrl}${exampleWriteTool.path}" | jq .`);
|
|
384
|
+
lines.push('```');
|
|
385
|
+
lines.push('');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return lines.join('\n');
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Generate modular skill files: one router SKILL.md + one file per module.
|
|
392
|
+
* Returns an array of { relativePath, content } for the caller to write.
|
|
393
|
+
*/
|
|
394
|
+
function generateModularSkills(config, tools) {
|
|
395
|
+
const files = [];
|
|
396
|
+
// Group tools by module
|
|
397
|
+
const byModule = new Map();
|
|
398
|
+
for (const tool of tools) {
|
|
399
|
+
const mod = tool.module ?? 'general';
|
|
400
|
+
if (!byModule.has(mod))
|
|
401
|
+
byModule.set(mod, []);
|
|
402
|
+
byModule.get(mod).push(tool);
|
|
403
|
+
}
|
|
404
|
+
// Generate router SKILL.md
|
|
405
|
+
files.push({
|
|
406
|
+
relativePath: 'SKILL.md',
|
|
407
|
+
content: generateRouterSkill(config, tools, byModule),
|
|
408
|
+
});
|
|
409
|
+
// Generate per-module files
|
|
410
|
+
for (const [moduleName, moduleTools] of byModule) {
|
|
411
|
+
const modSlug = moduleName.replace(/_/g, '-');
|
|
412
|
+
files.push({
|
|
413
|
+
relativePath: `${modSlug}.md`,
|
|
414
|
+
content: generateModuleSkill(config, moduleName, moduleTools),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return files;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Generate a single monolithic SKILL.md (legacy fallback).
|
|
421
|
+
* Renamed from the original generateMarkdownSkill.
|
|
422
|
+
*/
|
|
423
|
+
function generateMonolithicSkill(config, tools) {
|
|
424
|
+
const lines = [];
|
|
425
|
+
const slug = slugify(config.appName);
|
|
426
|
+
// Group tools by module (needed for multiple sections)
|
|
427
|
+
const byModule = new Map();
|
|
428
|
+
for (const tool of tools) {
|
|
429
|
+
const mod = tool.module ?? 'general';
|
|
430
|
+
if (!byModule.has(mod))
|
|
431
|
+
byModule.set(mod, []);
|
|
432
|
+
byModule.get(mod).push(tool);
|
|
433
|
+
}
|
|
434
|
+
const moduleNames = Array.from(byModule.keys());
|
|
435
|
+
const readTools = tools.filter((t) => t.method === 'GET');
|
|
436
|
+
const writeTools = tools.filter((t) => t.method !== 'GET');
|
|
437
|
+
// ── YAML Frontmatter (Anthropic spec: name + description required) ──
|
|
438
|
+
// description: what it does + when to use it + trigger phrases (max 1024 chars)
|
|
439
|
+
const triggerPhrases = moduleNames.slice(0, 4).map((m) => m.replace(/_/g, ' ')).join(', ');
|
|
440
|
+
const descriptionParts = [
|
|
441
|
+
`Interact with the ${config.appName} platform API.`,
|
|
442
|
+
`Use when the user wants to query or modify data via ${config.appName},`,
|
|
443
|
+
`mentions ${triggerPhrases},`,
|
|
444
|
+
`or asks to call the ${config.appName} API.`,
|
|
445
|
+
config.description ? config.description : '',
|
|
446
|
+
].filter(Boolean);
|
|
447
|
+
const description = descriptionParts.join(' ').slice(0, 1024);
|
|
448
|
+
lines.push('---');
|
|
449
|
+
lines.push(`name: ${slug}-api`);
|
|
450
|
+
lines.push(`description: ${description}`);
|
|
451
|
+
if (config.version) {
|
|
452
|
+
lines.push(`metadata:`);
|
|
453
|
+
lines.push(` version: ${config.version}`);
|
|
454
|
+
lines.push(` author: auto-generated by glirastes`);
|
|
455
|
+
lines.push(` category: platform-api`);
|
|
456
|
+
}
|
|
457
|
+
lines.push(`compatibility: Requires curl and jq. Needs network access to ${config.baseUrl}.`);
|
|
458
|
+
lines.push(`allowed-tools: "Bash(curl:*) Bash(jq:*)"`);
|
|
459
|
+
lines.push('---');
|
|
460
|
+
lines.push('');
|
|
461
|
+
// ── Title & Overview ──
|
|
462
|
+
lines.push(`# ${config.appName} API`);
|
|
463
|
+
lines.push('');
|
|
464
|
+
lines.push(`This skill gives you access to the ${config.appName} platform API.`);
|
|
465
|
+
lines.push(`It exposes **${tools.length} tools** (${readTools.length} read, ${writeTools.length} write) across **${byModule.size} module(s)**.`);
|
|
466
|
+
lines.push('');
|
|
467
|
+
lines.push(`**Base URL:** \`${config.baseUrl}\``);
|
|
468
|
+
lines.push('');
|
|
469
|
+
// ── Instructions ──
|
|
470
|
+
lines.push('## Instructions');
|
|
471
|
+
lines.push('');
|
|
472
|
+
// Step 1: Authentication
|
|
473
|
+
lines.push('### Step 1: Authenticate');
|
|
474
|
+
lines.push('');
|
|
475
|
+
lines.push(generateAuthInstructions(config.auth));
|
|
476
|
+
lines.push('');
|
|
477
|
+
// Step 2: Choose the right tool
|
|
478
|
+
lines.push('### Step 2: Choose the right tool');
|
|
479
|
+
lines.push('');
|
|
480
|
+
lines.push('Select a tool based on the user\'s intent:');
|
|
481
|
+
lines.push('');
|
|
482
|
+
for (const [moduleName, moduleTools] of byModule) {
|
|
483
|
+
const readCount = moduleTools.filter((t) => t.method === 'GET').length;
|
|
484
|
+
const writeCount = moduleTools.length - readCount;
|
|
485
|
+
lines.push(`- **${moduleName}**: ${moduleTools.map((t) => `\`${t.toolName}\``).join(', ')} (${readCount} read, ${writeCount} write)`);
|
|
486
|
+
}
|
|
487
|
+
lines.push('');
|
|
488
|
+
// Step 3: Make the request
|
|
489
|
+
lines.push('### Step 3: Make the request');
|
|
490
|
+
lines.push('');
|
|
491
|
+
lines.push('```bash');
|
|
492
|
+
lines.push(`# GET request (read operations)`);
|
|
493
|
+
lines.push(`curl -s -H "Authorization: Bearer $TOKEN" \\`);
|
|
494
|
+
lines.push(` "${config.baseUrl}/api/<path>" | jq .`);
|
|
495
|
+
lines.push('');
|
|
496
|
+
lines.push(`# POST/PATCH/PUT request (write operations)`);
|
|
497
|
+
lines.push(`curl -s -X POST -H "Authorization: Bearer $TOKEN" \\`);
|
|
498
|
+
lines.push(` -H "Content-Type: application/json" \\`);
|
|
499
|
+
lines.push(` -d '{"key": "value"}' \\`);
|
|
500
|
+
lines.push(` "${config.baseUrl}/api/<path>" | jq .`);
|
|
501
|
+
lines.push('```');
|
|
502
|
+
lines.push('');
|
|
503
|
+
// Step 4: Confirm destructive actions
|
|
504
|
+
if (writeTools.length > 0) {
|
|
505
|
+
lines.push('### Step 4: Confirm destructive actions');
|
|
506
|
+
lines.push('');
|
|
507
|
+
lines.push('Before executing write operations (POST, PATCH, PUT, DELETE):');
|
|
508
|
+
lines.push('');
|
|
509
|
+
lines.push('1. Show the user what data will be modified');
|
|
510
|
+
lines.push('2. If `needsApproval` is marked for the tool, **always ask for confirmation**');
|
|
511
|
+
lines.push('3. After a successful write, report what changed');
|
|
512
|
+
lines.push('');
|
|
513
|
+
}
|
|
514
|
+
// ── Tool Reference (grouped by module) ──
|
|
515
|
+
lines.push('## Tool Reference');
|
|
516
|
+
lines.push('');
|
|
517
|
+
for (const [moduleName, moduleTools] of byModule) {
|
|
518
|
+
lines.push(`### Module: ${moduleName}`);
|
|
519
|
+
lines.push('');
|
|
520
|
+
for (const tool of moduleTools) {
|
|
521
|
+
lines.push(toolToMarkdown(tool, config.baseUrl));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// ── Examples ──
|
|
525
|
+
lines.push('## Examples');
|
|
526
|
+
lines.push('');
|
|
527
|
+
// Auto-generate examples from actual tools
|
|
528
|
+
const exampleReadTool = readTools[0];
|
|
529
|
+
const exampleWriteTool = writeTools[0];
|
|
530
|
+
if (exampleReadTool) {
|
|
531
|
+
lines.push(`### Example 1: Read data`);
|
|
532
|
+
lines.push('');
|
|
533
|
+
lines.push(`User says: "Show me the ${exampleReadTool.toolName.replace(/_/g, ' ').replace(/^(list|get) /, '')}"`);
|
|
534
|
+
lines.push('');
|
|
535
|
+
lines.push('Actions:');
|
|
536
|
+
lines.push(`1. Authenticate with the API`);
|
|
537
|
+
lines.push(`2. Call \`${exampleReadTool.method} ${config.baseUrl}${exampleReadTool.path}\``);
|
|
538
|
+
lines.push(`3. Format and display results to the user`);
|
|
539
|
+
lines.push('');
|
|
540
|
+
lines.push('```bash');
|
|
541
|
+
lines.push(`curl -s -H "Authorization: Bearer $TOKEN" \\`);
|
|
542
|
+
lines.push(` "${config.baseUrl}${exampleReadTool.path}" | jq .`);
|
|
543
|
+
lines.push('```');
|
|
544
|
+
lines.push('');
|
|
545
|
+
}
|
|
546
|
+
if (exampleWriteTool) {
|
|
547
|
+
lines.push(`### Example 2: Write data`);
|
|
548
|
+
lines.push('');
|
|
549
|
+
lines.push(`User says: "${exampleWriteTool.toolName.replace(/_/g, ' ')}"`);
|
|
550
|
+
lines.push('');
|
|
551
|
+
lines.push('Actions:');
|
|
552
|
+
lines.push(`1. Authenticate with the API`);
|
|
553
|
+
lines.push(`2. Confirm the action with the user${exampleWriteTool.needsApproval ? ' (requires approval)' : ''}`);
|
|
554
|
+
const bodyFields = exampleWriteTool.inputFields
|
|
555
|
+
.filter((f) => f.required)
|
|
556
|
+
.map((f) => `"${f.name}": "<value>"`)
|
|
557
|
+
.join(', ');
|
|
558
|
+
lines.push(`3. Call \`${exampleWriteTool.method} ${config.baseUrl}${exampleWriteTool.path}\` with body \`{${bodyFields}}\``);
|
|
559
|
+
lines.push(`4. Report the result`);
|
|
560
|
+
lines.push('');
|
|
561
|
+
}
|
|
562
|
+
// ── Troubleshooting ──
|
|
563
|
+
lines.push('## Troubleshooting');
|
|
564
|
+
lines.push('');
|
|
565
|
+
lines.push('### Error: 401 Unauthorized');
|
|
566
|
+
lines.push('Cause: Token is missing, expired, or invalid.');
|
|
567
|
+
lines.push('Solution: Re-authenticate and obtain a fresh token (see Step 1).');
|
|
568
|
+
lines.push('');
|
|
569
|
+
lines.push('### Error: 403 Forbidden');
|
|
570
|
+
lines.push('Cause: The current user role does not have permission for this tool.');
|
|
571
|
+
lines.push('Solution: Check the `allowedRoles` for the tool and verify the user has the required role.');
|
|
572
|
+
lines.push('');
|
|
573
|
+
lines.push('### Error: 422 Validation Error');
|
|
574
|
+
lines.push('Cause: Request body does not match the expected schema.');
|
|
575
|
+
lines.push('Solution: Check the parameter table for the tool and ensure all required fields are provided with correct types.');
|
|
576
|
+
lines.push('');
|
|
577
|
+
return lines.join('\n');
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Generate auth instructions in a step-by-step format (for the Instructions section).
|
|
581
|
+
*/
|
|
582
|
+
function generateAuthInstructions(auth) {
|
|
583
|
+
switch (auth.type) {
|
|
584
|
+
case 'bearer':
|
|
585
|
+
return [
|
|
586
|
+
`This API uses **Bearer token** authentication.`,
|
|
587
|
+
``,
|
|
588
|
+
`\`\`\`bash`,
|
|
589
|
+
`# Ensure the token is available`,
|
|
590
|
+
`TOKEN="\$${auth.tokenEnvVar}"`,
|
|
591
|
+
``,
|
|
592
|
+
`# Every request must include:`,
|
|
593
|
+
`# -H "Authorization: Bearer $TOKEN"`,
|
|
594
|
+
`\`\`\``,
|
|
595
|
+
``,
|
|
596
|
+
`Expected result: Requests return 200. If you get 401, the token is missing or expired.`,
|
|
597
|
+
auth.description ? `\n${auth.description}` : '',
|
|
598
|
+
].join('\n');
|
|
599
|
+
case 'api-key':
|
|
600
|
+
return [
|
|
601
|
+
`This API uses **API key** authentication via the \`${auth.headerName}\` header.`,
|
|
602
|
+
``,
|
|
603
|
+
`\`\`\`bash`,
|
|
604
|
+
`API_KEY="\$${auth.keyEnvVar}"`,
|
|
605
|
+
``,
|
|
606
|
+
`# Every request must include:`,
|
|
607
|
+
`# -H "${auth.headerName}: $API_KEY"`,
|
|
608
|
+
`\`\`\``,
|
|
609
|
+
``,
|
|
610
|
+
`Expected result: Requests return 200. If you get 401, the API key is invalid.`,
|
|
611
|
+
auth.description ? `\n${auth.description}` : '',
|
|
612
|
+
].join('\n');
|
|
613
|
+
case 'oauth2':
|
|
614
|
+
return [
|
|
615
|
+
`This API uses **OAuth 2.0 Client Credentials** flow.`,
|
|
616
|
+
``,
|
|
617
|
+
`\`\`\`bash`,
|
|
618
|
+
`# 1. Obtain access token`,
|
|
619
|
+
`TOKEN=$(curl -s -X POST ${auth.tokenUrl} \\`,
|
|
620
|
+
` -d "grant_type=client_credentials" \\`,
|
|
621
|
+
` -d "client_id=\$${auth.clientIdEnvVar}" \\`,
|
|
622
|
+
` -d "client_secret=\$${auth.clientSecretEnvVar}"${auth.scopes ? ` \\\n -d "scope=${auth.scopes.join(' ')}"` : ''} \\`,
|
|
623
|
+
` | jq -r '.access_token')`,
|
|
624
|
+
``,
|
|
625
|
+
`# 2. Verify token was obtained`,
|
|
626
|
+
`[ -n "$TOKEN" ] && echo "Auth OK" || echo "Auth FAILED"`,
|
|
627
|
+
`\`\`\``,
|
|
628
|
+
``,
|
|
629
|
+
`Expected result: "Auth OK". If "Auth FAILED", check client credentials.`,
|
|
630
|
+
auth.description ? `\n${auth.description}` : '',
|
|
631
|
+
].join('\n');
|
|
632
|
+
case 'cookie':
|
|
633
|
+
return [
|
|
634
|
+
`This API uses **cookie-based** session authentication.`,
|
|
635
|
+
`The agent must first authenticate via the login endpoint and pass cookies in subsequent requests.`,
|
|
636
|
+
``,
|
|
637
|
+
`\`\`\`bash`,
|
|
638
|
+
`# Store cookies from login`,
|
|
639
|
+
`curl -s -c cookies.txt -X POST <login-url> \\`,
|
|
640
|
+
` -H "Content-Type: application/json" \\`,
|
|
641
|
+
` -d '{"username": "...", "password": "..."}'`,
|
|
642
|
+
``,
|
|
643
|
+
`# Use cookies in subsequent requests`,
|
|
644
|
+
`curl -s -b cookies.txt <api-url>`,
|
|
645
|
+
`\`\`\``,
|
|
646
|
+
auth.description ? `\n${auth.description}` : '',
|
|
647
|
+
].join('\n');
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// ============================================================================
|
|
651
|
+
// JSON Manifest Generation (machine-readable for Codex / Agent SDK)
|
|
652
|
+
// ============================================================================
|
|
653
|
+
function toolToJsonSchema(tool) {
|
|
654
|
+
const parameters = {
|
|
655
|
+
type: 'object',
|
|
656
|
+
properties: {},
|
|
657
|
+
required: [],
|
|
658
|
+
};
|
|
659
|
+
for (const field of tool.inputFields) {
|
|
660
|
+
const typeMap = {
|
|
661
|
+
string: 'string',
|
|
662
|
+
number: 'number',
|
|
663
|
+
boolean: 'boolean',
|
|
664
|
+
'string (ISO date)': 'string',
|
|
665
|
+
'string (enum)': 'string',
|
|
666
|
+
'string (UUID)': 'string',
|
|
667
|
+
array: 'array',
|
|
668
|
+
object: 'object',
|
|
669
|
+
};
|
|
670
|
+
const prop = {
|
|
671
|
+
type: typeMap[field.type] ?? 'string',
|
|
672
|
+
};
|
|
673
|
+
if (field.description)
|
|
674
|
+
prop.description = field.description;
|
|
675
|
+
if (field.type === 'string (UUID)')
|
|
676
|
+
prop.format = 'uuid';
|
|
677
|
+
if (field.type === 'string (ISO date)')
|
|
678
|
+
prop.format = 'date-time';
|
|
679
|
+
parameters.properties[field.name] = prop;
|
|
680
|
+
if (field.required) {
|
|
681
|
+
parameters.required.push(field.name);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const result = {
|
|
685
|
+
name: tool.toolName,
|
|
686
|
+
description: tool.description,
|
|
687
|
+
method: tool.method,
|
|
688
|
+
path: tool.path,
|
|
689
|
+
parameters,
|
|
690
|
+
};
|
|
691
|
+
if (tool.outputFields.length > 0) {
|
|
692
|
+
const response = {
|
|
693
|
+
type: 'object',
|
|
694
|
+
properties: {},
|
|
695
|
+
};
|
|
696
|
+
for (const field of tool.outputFields) {
|
|
697
|
+
response.properties[field.name] = {
|
|
698
|
+
type: field.type === 'number' ? 'number' : 'string',
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
result.response = response;
|
|
702
|
+
}
|
|
703
|
+
if (tool.needsApproval)
|
|
704
|
+
result.needsApproval = true;
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
function generateJsonManifest(config, tools) {
|
|
708
|
+
const modules = {};
|
|
709
|
+
for (const tool of tools) {
|
|
710
|
+
const mod = tool.module ?? 'general';
|
|
711
|
+
if (!modules[mod])
|
|
712
|
+
modules[mod] = [];
|
|
713
|
+
modules[mod].push(tool.toolName);
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
name: `${slugify(config.appName)}-api`,
|
|
717
|
+
description: config.description ?? `Agent skill for ${config.appName} platform API`,
|
|
718
|
+
version: config.version,
|
|
719
|
+
baseUrl: config.baseUrl,
|
|
720
|
+
auth: config.auth,
|
|
721
|
+
tools: tools.map(toolToJsonSchema),
|
|
722
|
+
modules,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
// ============================================================================
|
|
726
|
+
// Optimize Meta-Skill Template
|
|
727
|
+
// ============================================================================
|
|
728
|
+
/**
|
|
729
|
+
* Generate the "optimize-generated-skills" meta-skill SKILL.md.
|
|
730
|
+
* This skill teaches a coding agent how to review and upgrade the
|
|
731
|
+
* auto-generated API skills to production quality.
|
|
732
|
+
*
|
|
733
|
+
* Replaces {APP_NAME} with the actual app name slug.
|
|
734
|
+
*/
|
|
735
|
+
function generateOptimizeSkillTemplate(appName) {
|
|
736
|
+
const slug = slugify(appName);
|
|
737
|
+
return `---
|
|
738
|
+
name: optimize-generated-skills
|
|
739
|
+
description: Use after running \`glirastes generate-skills\` to review and improve auto-generated API skill files. Covers frontmatter CSO, token efficiency, description quality, parameter completeness, and adding domain context the generator cannot infer.
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
# Optimize Generated Skills
|
|
743
|
+
|
|
744
|
+
## Overview
|
|
745
|
+
|
|
746
|
+
Auto-generated skills from \`glirastes generate-skills\` are structurally correct but lack domain context, workflow knowledge, and CSO polish. This skill guides you through reviewing and upgrading them to production quality.
|
|
747
|
+
|
|
748
|
+
**Target:** The generated skill at \`./${slug}-api/SKILL.md\` and its module files.
|
|
749
|
+
|
|
750
|
+
## When to Use
|
|
751
|
+
|
|
752
|
+
- Right after running \`glirastes generate-skills\`
|
|
753
|
+
- When an agent struggles to find or use the generated skill correctly
|
|
754
|
+
- When adding new tools and regenerating (review diff, preserve manual additions)
|
|
755
|
+
|
|
756
|
+
## Review Process
|
|
757
|
+
|
|
758
|
+
Work through each step in order. After each step, re-read the file to verify changes.
|
|
759
|
+
|
|
760
|
+
### Step 1: Router SKILL.md
|
|
761
|
+
|
|
762
|
+
Open \`./${slug}-api/SKILL.md\` and check:
|
|
763
|
+
|
|
764
|
+
**Frontmatter:**
|
|
765
|
+
- \`description\` starts with "Use when..." (not "Interact with..." or "Tool for...")
|
|
766
|
+
- Includes searchable keywords: module names, key tool names, error codes (401, 403, 422)
|
|
767
|
+
- Under 500 characters; no workflow summary (CSO: triggers only, never process)
|
|
768
|
+
- \`allowed-tools\` matches what the agent actually needs (curl, jq, etc.)
|
|
769
|
+
|
|
770
|
+
**Body:**
|
|
771
|
+
- Module table is accurate (tool counts, key tools)
|
|
772
|
+
- Auth section has correct env var names
|
|
773
|
+
- Total line count < 100
|
|
774
|
+
|
|
775
|
+
### Step 2: Module Files
|
|
776
|
+
|
|
777
|
+
For each \`<module>.md\` in \`./${slug}-api/\`:
|
|
778
|
+
|
|
779
|
+
**Descriptions:**
|
|
780
|
+
- Replace generic "Tool for X" with actionable descriptions
|
|
781
|
+
- Include WHEN to use: \`"Use to look up user UUIDs before assigning tasks"\`
|
|
782
|
+
- Include WHAT it returns: \`"Returns list with IDs, titles, status"\`
|
|
783
|
+
- Add LLM hints for mutations: \`"IMPORTANT: ask for title first"\`, \`"DO NOT invent IDs"\`
|
|
784
|
+
|
|
785
|
+
**Parameter tables:**
|
|
786
|
+
- Every required field has a description (not blank)
|
|
787
|
+
- Types match actual API behavior (e.g., \`string (UUID)\` not just \`string\`)
|
|
788
|
+
- Optional fields explain when to use them
|
|
789
|
+
|
|
790
|
+
**Language consistency:**
|
|
791
|
+
- All descriptions in one language (all English OR all German, not mixed)
|
|
792
|
+
- If the consumer app's UI language differs from English, match it
|
|
793
|
+
|
|
794
|
+
**Line count:**
|
|
795
|
+
- Each module file < 500 lines
|
|
796
|
+
- If over 500: split into sub-modules or compress rarely-used tools into a summary table
|
|
797
|
+
|
|
798
|
+
### Step 3: Token Efficiency
|
|
799
|
+
|
|
800
|
+
**Router:** Should be < 100 lines. If longer:
|
|
801
|
+
- Move troubleshooting to a separate \`troubleshooting.md\` (only if > 3 error patterns)
|
|
802
|
+
- Keep auth and module table inline
|
|
803
|
+
|
|
804
|
+
**Module files:** Compress where possible:
|
|
805
|
+
- Collapse tools with identical parameter shapes into a single entry with variants
|
|
806
|
+
- Remove empty "Response" sections
|
|
807
|
+
- Use \`--help\` references instead of documenting every optional param
|
|
808
|
+
|
|
809
|
+
**Rarely-used tools:** If a module has > 15 tools:
|
|
810
|
+
- Move rarely-used ones to an "Additional Tools" section at the bottom
|
|
811
|
+
- Keep the top 5-8 most common tools prominent
|
|
812
|
+
|
|
813
|
+
### Step 4: Domain Context
|
|
814
|
+
|
|
815
|
+
This is the highest-value step. Add knowledge the generator cannot infer:
|
|
816
|
+
|
|
817
|
+
**Business rules:**
|
|
818
|
+
\`\`\`markdown
|
|
819
|
+
## Business Rules
|
|
820
|
+
- Tasks can only be assigned to members of the same group
|
|
821
|
+
- Deleting a group reassigns all tasks to "unassigned"
|
|
822
|
+
- Holiday calendar entries require admin role
|
|
823
|
+
\`\`\`
|
|
824
|
+
|
|
825
|
+
**Common workflows:**
|
|
826
|
+
\`\`\`markdown
|
|
827
|
+
## Common Workflows
|
|
828
|
+
|
|
829
|
+
### Move a task to another group
|
|
830
|
+
1. \`list_groups\` to get target group ID
|
|
831
|
+
2. \`update_task\` with new \`groupId\`
|
|
832
|
+
Note: task keeps its assignee only if assignee is in the target group.
|
|
833
|
+
\`\`\`
|
|
834
|
+
|
|
835
|
+
**Error recovery:**
|
|
836
|
+
\`\`\`markdown
|
|
837
|
+
## Error Recovery
|
|
838
|
+
- 409 Conflict on task update: re-fetch the task (optimistic locking)
|
|
839
|
+
- 422 on create with assigneeId: the user UUID is wrong, use \`find_user\` first
|
|
840
|
+
\`\`\`
|
|
841
|
+
|
|
842
|
+
**Cross-module references:**
|
|
843
|
+
\`\`\`markdown
|
|
844
|
+
<!-- In task-mutation.md -->
|
|
845
|
+
**Prerequisite:** Use \`find_user\` (in [group-management](group-management.md)) to resolve user names to UUIDs before assigning.
|
|
846
|
+
\`\`\`
|
|
847
|
+
|
|
848
|
+
### Step 5: Verify
|
|
849
|
+
|
|
850
|
+
Run through this checklist and fix any remaining issues:
|
|
851
|
+
|
|
852
|
+
\`\`\`
|
|
853
|
+
- [ ] Router SKILL.md < 100 lines
|
|
854
|
+
- [ ] Each module file < 500 lines
|
|
855
|
+
- [ ] All descriptions are specific (no "Tool for X" patterns)
|
|
856
|
+
- [ ] Language is consistent per file
|
|
857
|
+
- [ ] Parameter descriptions filled in for required fields
|
|
858
|
+
- [ ] Common workflows documented (at least 2)
|
|
859
|
+
- [ ] Error codes and symptoms in frontmatter keywords
|
|
860
|
+
- [ ] Cross-references between related modules
|
|
861
|
+
- [ ] Business rules section added where applicable
|
|
862
|
+
- [ ] Auth env var name matches actual .env
|
|
863
|
+
\`\`\`
|
|
864
|
+
|
|
865
|
+
## After Optimization
|
|
866
|
+
|
|
867
|
+
- Commit the optimized files (they live in \`.claude/skills/generated/\`)
|
|
868
|
+
- Note: re-running \`glirastes generate-skills\` will overwrite your changes
|
|
869
|
+
- To preserve manual additions across regeneration, consider moving domain context to a separate file (e.g., \`${slug}-api/domain-context.md\`) and referencing it from the module files
|
|
870
|
+
|
|
871
|
+
## Anti-Patterns
|
|
872
|
+
|
|
873
|
+
| Pattern | Fix |
|
|
874
|
+
|---------|-----|
|
|
875
|
+
| "Tool for creating items" | "Create a new item. Ask for title first. Returns the created item with its UUID." |
|
|
876
|
+
| Empty parameter descriptions | Fill in what the parameter does and any constraints |
|
|
877
|
+
| Mixed English/German in one file | Pick one language, be consistent |
|
|
878
|
+
| 800-line module file | Split by sub-domain or compress rarely-used tools |
|
|
879
|
+
| Workflow in frontmatter description | Move to body; description = triggers only |
|
|
880
|
+
| Duplicated auth instructions in modules | Reference router SKILL.md instead |
|
|
881
|
+
`;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Generate agent skill files from discovered tool definitions.
|
|
885
|
+
*
|
|
886
|
+
* Scans the project for endpoint and UI tool definitions and produces:
|
|
887
|
+
* - Modular Markdown skill files (one router SKILL.md + one file per module)
|
|
888
|
+
* - A JSON manifest (machine-readable for Codex / Agent SDK)
|
|
889
|
+
*
|
|
890
|
+
* Both formats contain the full tool catalog with auth instructions,
|
|
891
|
+
* enabling autonomous agents to interact with the platform API.
|
|
892
|
+
*/
|
|
893
|
+
async function generateAgentSkills(options) {
|
|
894
|
+
const opts = (0, types_js_1.resolveDefaults)(options);
|
|
895
|
+
const formats = options.formats ?? ['markdown', 'json'];
|
|
896
|
+
const outputDir = options.skillOutputDir
|
|
897
|
+
? (0, node_path_1.join)(opts.rootDir, options.skillOutputDir)
|
|
898
|
+
: (0, node_path_1.join)(opts.rootDir, '.claude', 'skills', 'generated');
|
|
899
|
+
// Scan all tool files
|
|
900
|
+
const endpointScan = await (0, endpoint_scanner_js_1.scanEndpointTools)(options);
|
|
901
|
+
const uiScan = await (0, ui_scanner_js_1.scanUiTools)(options);
|
|
902
|
+
// Extract tool metadata from all discovered files
|
|
903
|
+
const allTools = [];
|
|
904
|
+
for (const file of [...endpointScan.files, ...uiScan.files]) {
|
|
905
|
+
const tools = await extractSkillTools(file.absolutePath);
|
|
906
|
+
allTools.push(...tools);
|
|
907
|
+
}
|
|
908
|
+
// Count modules
|
|
909
|
+
const moduleSet = new Set(allTools.map((t) => t.module ?? 'general'));
|
|
910
|
+
await (0, promises_1.mkdir)(outputDir, { recursive: true });
|
|
911
|
+
const result = {
|
|
912
|
+
toolCount: allTools.length,
|
|
913
|
+
moduleCount: moduleSet.size,
|
|
914
|
+
tools: allTools,
|
|
915
|
+
};
|
|
916
|
+
// Generate modular Markdown skills — one router + one file per module:
|
|
917
|
+
// .claude/skills/generated/<app>-api/SKILL.md (router)
|
|
918
|
+
// .claude/skills/generated/<app>-api/<module>.md (per module)
|
|
919
|
+
if (formats.includes('markdown')) {
|
|
920
|
+
const skillFiles = generateModularSkills(options.skillConfig, allTools);
|
|
921
|
+
const skillFolderName = `${slugify(options.skillConfig.appName)}-api`;
|
|
922
|
+
const skillDir = (0, node_path_1.join)(outputDir, skillFolderName);
|
|
923
|
+
await (0, promises_1.mkdir)(skillDir, { recursive: true });
|
|
924
|
+
const writtenPaths = [];
|
|
925
|
+
for (const file of skillFiles) {
|
|
926
|
+
const filePath = (0, node_path_1.join)(skillDir, file.relativePath);
|
|
927
|
+
await (0, promises_1.writeFile)(filePath, file.content, 'utf-8');
|
|
928
|
+
writtenPaths.push(filePath);
|
|
929
|
+
}
|
|
930
|
+
// markdownPath points to the router SKILL.md for backward compat
|
|
931
|
+
result.markdownPath = (0, node_path_1.join)(skillDir, 'SKILL.md');
|
|
932
|
+
result.markdownPaths = writtenPaths;
|
|
933
|
+
// Generate optimize meta-skill alongside the API skill
|
|
934
|
+
const optimizeDir = (0, node_path_1.join)(outputDir, 'optimize-generated-skills');
|
|
935
|
+
await (0, promises_1.mkdir)(optimizeDir, { recursive: true });
|
|
936
|
+
const optimizeContent = generateOptimizeSkillTemplate(options.skillConfig.appName);
|
|
937
|
+
const optimizePath = (0, node_path_1.join)(optimizeDir, 'SKILL.md');
|
|
938
|
+
await (0, promises_1.writeFile)(optimizePath, optimizeContent, 'utf-8');
|
|
939
|
+
writtenPaths.push(optimizePath);
|
|
940
|
+
if (!opts.quiet) {
|
|
941
|
+
console.log(`\u2713 Generated skill (Markdown, modular): ${writtenPaths.length} files in ${skillDir}`);
|
|
942
|
+
for (const p of writtenPaths) {
|
|
943
|
+
console.log(` - ${p}`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
// Generate JSON manifest
|
|
948
|
+
if (formats.includes('json')) {
|
|
949
|
+
const manifest = generateJsonManifest(options.skillConfig, allTools);
|
|
950
|
+
const jsonPath = (0, node_path_1.join)(outputDir, `${slugify(options.skillConfig.appName)}-api.skill.json`);
|
|
951
|
+
await (0, promises_1.writeFile)(jsonPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
952
|
+
result.jsonPath = jsonPath;
|
|
953
|
+
if (!opts.quiet) {
|
|
954
|
+
console.log(`\u2713 Generated skill (JSON): ${jsonPath}`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (!opts.quiet) {
|
|
958
|
+
console.log(` ${allTools.length} tools across ${moduleSet.size} modules`);
|
|
959
|
+
}
|
|
960
|
+
return result;
|
|
961
|
+
}
|
|
962
|
+
function slugify(s) {
|
|
963
|
+
return s
|
|
964
|
+
.toLowerCase()
|
|
965
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
966
|
+
.replace(/^-|-$/g, '');
|
|
967
|
+
}
|