devskill 2.0.8 → 2.0.11

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/meta.ts CHANGED
@@ -4,6 +4,16 @@ export interface VendorSkillMeta {
4
4
  skills?: Record<string, string> // sourceSkillName -> outputSkillName. If omitted, auto-discovers all subdirectories.
5
5
  }
6
6
 
7
+ export interface RuleMeta {
8
+ file: string // path relative to repo root, e.g. 'rules/vue-style.md'
9
+ description?: string
10
+ }
11
+
12
+ export interface WorkflowMeta {
13
+ file: string // path relative to repo root, e.g. 'workflows/git-flow.md'
14
+ description?: string
15
+ }
16
+
7
17
  /**
8
18
  * Repositories to clone as submodules and generate skills from their docs.
9
19
  * After cloning, read docs and generate skills manually into skills/{name}/
@@ -16,6 +26,7 @@ export const submodules: Record<string, string> = {
16
26
  // 'react': 'https://github.com/reactjs/react.dev',
17
27
  'vue': 'https://github.com/vuejs/docs',
18
28
  'pinia': 'https://github.com/vuejs/pinia',
29
+ 'deepinfra': 'https://github.com/deepinfra/docs',
19
30
  }
20
31
 
21
32
  /**
@@ -134,7 +145,8 @@ export const collections: Record<string, string[]> = {
134
145
  'pinia-options',
135
146
  'builderx_spa-api',
136
147
  'builderx_spa-permission',
137
- 'builderx_spa-design'
148
+ 'builderx_spa-design',
149
+ 'git-commit',
138
150
  ],
139
151
  'builderx_api': [
140
152
  'builderx_api-schemas',
@@ -144,5 +156,44 @@ export const collections: Record<string, string[]> = {
144
156
  'builderx_api-redis',
145
157
  'builderx_api-rabbitmq',
146
158
  'builderx_api-mongodb',
159
+ 'git-commit',
160
+ 'deepinfra',
147
161
  ]
148
162
  }
163
+
164
+ /**
165
+ * Individual rule files (markdown) to install into AI tool rule directories.
166
+ * Format: { 'rule-name': { file: 'rules/rule-name.md', description: '...' } }
167
+ */
168
+ export const rules: Record<string, RuleMeta> = {
169
+ 'vue-style': {
170
+ file: 'rules/vue-style.md',
171
+ description: 'Vue 3 coding style and conventions',
172
+ },
173
+ 'typescript-strict': {
174
+ file: 'rules/typescript-strict.md',
175
+ description: 'Strict TypeScript conventions',
176
+ },
177
+ }
178
+
179
+ /**
180
+ * Individual workflow files (markdown) to install into AI tool workflow directories.
181
+ * Format: { 'workflow-name': { file: 'workflows/workflow-name.md', description: '...' } }
182
+ */
183
+ export const workflows: Record<string, WorkflowMeta> = {
184
+ 'git-flow': {
185
+ file: 'workflows/git-flow.md',
186
+ description: 'Feature branch git workflow',
187
+ },
188
+ }
189
+
190
+ /**
191
+ * Grouped collections of rules + workflows per project.
192
+ * Use `npm start rules` or `npm start workflows` to install a batch.
193
+ */
194
+ export const ruleCollections: Record<string, { rules?: string[], workflows?: string[] }> = {
195
+ 'builderx_spa': {
196
+ rules: ['vue-style', 'typescript-strict'],
197
+ workflows: ['git-flow'],
198
+ },
199
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devskill",
3
- "version": "2.0.8",
3
+ "version": "2.0.11",
4
4
  "description": "Equip Cursor, Windsurf, Cline, Antigravity, Claude Code, Codex, GitHub Copilot and other AI Agents with expert programming superpowers via a single interactive prompt.",
5
5
  "homepage": "https://vskill.vercel.app",
6
6
  "repository": {
@@ -0,0 +1,30 @@
1
+ # TypeScript Strict Rules
2
+
3
+ ## Strict Mode
4
+ - Always enable `"strict": true` in `tsconfig.json`
5
+ - No implicit `any` — always type function parameters
6
+ - Use `unknown` instead of `any` when type is uncertain
7
+
8
+ ## Types vs Interfaces
9
+ - Use `interface` for object shapes that may be extended
10
+ - Use `type` for unions, intersections, and computed types
11
+
12
+ ```ts
13
+ // Good: interface for extendable shapes
14
+ interface User {
15
+ id: string
16
+ name: string
17
+ }
18
+
19
+ // Good: type for unions
20
+ type Status = 'active' | 'inactive' | 'pending'
21
+ ```
22
+
23
+ ## Null Handling
24
+ - Use optional chaining `?.` and nullish coalescing `??`
25
+ - Avoid non-null assertion `!` — handle null explicitly
26
+ - Prefer explicit return types on exported functions
27
+
28
+ ## Generics
29
+ - Name generics descriptively: `TItem`, `TKey`, `TResponse`
30
+ - Constrain generics when possible: `<T extends object>`
@@ -0,0 +1,35 @@
1
+ # Vue Style Rules
2
+
3
+ ## Component Naming
4
+ - Use PascalCase for component names: `MyComponent.vue`
5
+ - Use kebab-case in templates: `<my-component />`
6
+ - Prefix base/generic components with `Base`: `BaseButton`, `BaseInput`
7
+
8
+ ## Script Setup
9
+ - Always use `<script setup lang="ts">` for Vue 3 components
10
+ - Use `defineProps` and `defineEmits` with TypeScript generics
11
+ - Define props interface separately for reusability
12
+
13
+ ```vue
14
+ <script setup lang="ts">
15
+ interface Props {
16
+ title: string
17
+ count?: number
18
+ }
19
+ const props = defineProps<Props>()
20
+ const emit = defineEmits<{
21
+ change: [value: number]
22
+ submit: []
23
+ }>()
24
+ </script>
25
+ ```
26
+
27
+ ## Template
28
+ - Use `v-bind` shorthand `:` and `v-on` shorthand `@`
29
+ - Prefer `v-show` for frequent toggles, `v-if` for conditional rendering
30
+ - Always provide `key` when using `v-for`
31
+
32
+ ## Composables
33
+ - Prefix with `use`: `useAuth`, `useCart`
34
+ - Always return reactive refs explicitly
35
+ - Keep side effects inside `onMounted` / `watchEffect`
package/scripts/cli.ts CHANGED
@@ -5,7 +5,7 @@ import process from 'node:process'
5
5
  import { fileURLToPath } from 'node:url'
6
6
  import * as p from '@clack/prompts'
7
7
  import prompts from 'prompts'
8
- import { collections, manual, submodules, vendors } from '../meta.ts'
8
+ import { collections, manual, ruleCollections, rules, submodules, vendors, workflows } from '../meta.ts'
9
9
 
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url))
11
11
  const root = join(__dirname, '..')
@@ -389,14 +389,40 @@ async function cleanup(skipPrompt = false) {
389
389
  }
390
390
  }
391
391
 
392
- async function installSkills(targetCollectionNames: string[] = []) {
393
- const spinner = p.spinner()
394
-
392
+ function getAllCollections(): Record<string, string[]> {
395
393
  const allCollections: Record<string, string[]> = { ...collections }
396
394
  for (const [vendorName, config] of Object.entries(vendors)) {
397
395
  const vendorConfig = config as VendorConfig
398
- allCollections[vendorName] = Object.values(vendorConfig.skills)
396
+ let vendorSkills: string[] = []
397
+
398
+ if (vendorConfig.skills && Object.keys(vendorConfig.skills).length > 0) {
399
+ vendorSkills = Object.values(vendorConfig.skills)
400
+ } else {
401
+ const vendorPath = join(root, 'vendor', vendorName)
402
+ const vendorSkillsPath = join(vendorPath, 'skills')
403
+ const searchPath = existsSync(vendorSkillsPath) ? vendorSkillsPath : (existsSync(vendorPath) ? vendorPath : null)
404
+
405
+ if (searchPath) {
406
+ const entries = readdirSync(searchPath, { withFileTypes: true })
407
+ for (const entry of entries) {
408
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
409
+ vendorSkills.push(entry.name)
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ if (vendorSkills.length > 0) {
416
+ allCollections[vendorName] = vendorSkills
417
+ }
399
418
  }
419
+ return allCollections
420
+ }
421
+
422
+ async function installSkills(targetCollectionNames: string[] = []) {
423
+ const spinner = p.spinner()
424
+
425
+ const allCollections = getAllCollections()
400
426
 
401
427
  if (Object.keys(allCollections).length === 0) {
402
428
  p.log.warn('No collections or vendors defined in meta.ts')
@@ -661,32 +687,7 @@ async function findSkills(query?: string) {
661
687
  const allSkills = getExistingSkillNames()
662
688
  p.intro(`Search results for ${query ? `"${query}"` : 'all'}`)
663
689
 
664
- const allCollections: Record<string, string[]> = { ...collections }
665
- for (const [vendorName, config] of Object.entries(vendors)) {
666
- const vendorConfig = config as VendorConfig
667
- let vendorSkills: string[] = []
668
-
669
- if (vendorConfig.skills && Object.keys(vendorConfig.skills).length > 0) {
670
- vendorSkills = Object.values(vendorConfig.skills)
671
- } else {
672
- const vendorPath = join(root, 'vendor', vendorName)
673
- const vendorSkillsPath = join(vendorPath, 'skills')
674
- const searchPath = existsSync(vendorSkillsPath) ? vendorSkillsPath : (existsSync(vendorPath) ? vendorPath : null)
675
-
676
- if (searchPath) {
677
- const entries = readdirSync(searchPath, { withFileTypes: true })
678
- for (const entry of entries) {
679
- if (entry.isDirectory() && !entry.name.startsWith('.')) {
680
- vendorSkills.push(entry.name)
681
- }
682
- }
683
- }
684
- }
685
-
686
- if (vendorSkills.length > 0) {
687
- allCollections[vendorName] = vendorSkills
688
- }
689
- }
690
+ const allCollections = getAllCollections()
690
691
 
691
692
  let q = ''
692
693
  if (!query) {
@@ -725,7 +726,242 @@ async function findSkills(query?: string) {
725
726
  }
726
727
  }
727
728
 
729
+ // ─── Rules & Workflows ────────────────────────────────────────────────────────
730
+
731
+ function getExistingRuleNames(): string[] {
732
+ return Object.keys(rules)
733
+ }
734
+
735
+ function getExistingWorkflowNames(): string[] {
736
+ return Object.keys(workflows)
737
+ }
738
+
739
+ async function installRules(targetRuleNames: string[] = []) {
740
+ const spinner = p.spinner()
741
+
742
+ const allRules = getExistingRuleNames()
743
+ if (allRules.length === 0) {
744
+ p.log.warn('No rules defined in meta.ts')
745
+ return
746
+ }
747
+
748
+ let selectedRules: string[] = []
749
+
750
+ if (targetRuleNames.length > 0) {
751
+ const invalid = targetRuleNames.filter(n => !allRules.includes(n))
752
+ if (invalid.length > 0) {
753
+ p.log.error(`Rules not found: ${invalid.join(', ')}`)
754
+ return
755
+ }
756
+ selectedRules = targetRuleNames
757
+ }
758
+ else {
759
+ const response = await prompts({
760
+ type: 'autocompleteMultiselect',
761
+ name: 'selected',
762
+ message: 'Select rules to install (Space to select, Enter to confirm)',
763
+ choices: allRules.map(name => ({
764
+ title: name,
765
+ value: name,
766
+ description: rules[name].description ?? '',
767
+ })),
768
+ })
769
+ if (!response.selected || response.selected.length === 0) {
770
+ p.log.warn('No rules selected (or canceled)')
771
+ return
772
+ }
773
+ selectedRules = response.selected
774
+ }
775
+
776
+ const targetProject = await p.text({
777
+ message: 'Enter target project directory path',
778
+ initialValue: process.cwd(),
779
+ placeholder: process.cwd(),
780
+ validate: value => {
781
+ if (!value) return 'Path is required'
782
+ if (!existsSync(value)) return 'Directory does not exist'
783
+ },
784
+ })
785
+ if (p.isCancel(targetProject)) { p.cancel('Cancelled'); return }
786
+
787
+ const toolChoice = await p.select({
788
+ message: 'Which AI tool are you installing rules for?',
789
+ options: [
790
+ { value: '.cursor/rules', label: 'Cursor (.cursor/rules/)' },
791
+ { value: '.windsurf/rules', label: 'Windsurf (.windsurf/rules/)' },
792
+ { value: '.claude', label: 'Claude Code (.claude/)' },
793
+ { value: '.agents/rules', label: 'Antigravity (.agents/rules/)' },
794
+ { value: 'rules', label: 'Generic (rules/)' },
795
+ { value: 'custom', label: 'Custom path...' },
796
+ ],
797
+ })
798
+ if (p.isCancel(toolChoice)) { p.cancel('Cancelled'); return }
799
+
800
+ let rulesDirName = toolChoice as string
801
+ if (toolChoice === 'custom') {
802
+ const customDir = await p.text({ message: 'Enter custom path:', initialValue: 'rules', placeholder: 'rules' })
803
+ if (p.isCancel(customDir)) { p.cancel('Cancelled'); return }
804
+ rulesDirName = customDir as string
805
+ }
806
+
807
+ const targetDir = join(targetProject as string, rulesDirName)
808
+ if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true })
809
+
810
+ spinner.start(`Installing ${selectedRules.length} rule(s) to ${targetDir}...`)
811
+ let successCount = 0
812
+ for (const ruleName of selectedRules) {
813
+ const meta = rules[ruleName]
814
+ const srcFile = join(root, meta.file)
815
+ if (!existsSync(srcFile)) {
816
+ p.log.warn(`Rule file not found: ${meta.file}`)
817
+ continue
818
+ }
819
+ const destFile = join(targetDir, `${ruleName}.md`)
820
+ cpSync(srcFile, destFile)
821
+ successCount++
822
+ }
823
+ spinner.stop(`Installed ${successCount}/${selectedRules.length} rule(s)`)
824
+ }
825
+
826
+ async function installWorkflows(targetWorkflowNames: string[] = []) {
827
+ const spinner = p.spinner()
828
+
829
+ const allWorkflows = getExistingWorkflowNames()
830
+ if (allWorkflows.length === 0) {
831
+ p.log.warn('No workflows defined in meta.ts')
832
+ return
833
+ }
834
+
835
+ let selectedWorkflows: string[] = []
836
+
837
+ if (targetWorkflowNames.length > 0) {
838
+ const invalid = targetWorkflowNames.filter(n => !allWorkflows.includes(n))
839
+ if (invalid.length > 0) {
840
+ p.log.error(`Workflows not found: ${invalid.join(', ')}`)
841
+ return
842
+ }
843
+ selectedWorkflows = targetWorkflowNames
844
+ }
845
+ else {
846
+ const response = await prompts({
847
+ type: 'autocompleteMultiselect',
848
+ name: 'selected',
849
+ message: 'Select workflows to install (Space to select, Enter to confirm)',
850
+ choices: allWorkflows.map(name => ({
851
+ title: name,
852
+ value: name,
853
+ description: workflows[name].description ?? '',
854
+ })),
855
+ })
856
+ if (!response.selected || response.selected.length === 0) {
857
+ p.log.warn('No workflows selected (or canceled)')
858
+ return
859
+ }
860
+ selectedWorkflows = response.selected
861
+ }
862
+
863
+ const targetProject = await p.text({
864
+ message: 'Enter target project directory path',
865
+ initialValue: process.cwd(),
866
+ placeholder: process.cwd(),
867
+ validate: value => {
868
+ if (!value) return 'Path is required'
869
+ if (!existsSync(value)) return 'Directory does not exist'
870
+ },
871
+ })
872
+ if (p.isCancel(targetProject)) { p.cancel('Cancelled'); return }
873
+
874
+ const toolChoice = await p.select({
875
+ message: 'Which AI tool are you installing workflows for?',
876
+ options: [
877
+ { value: '.agents/workflows', label: 'Antigravity (.agents/workflows/)' },
878
+ { value: '.cursor/workflows', label: 'Cursor (.cursor/workflows/)' },
879
+ { value: '.windsurf/workflows', label: 'Windsurf (.windsurf/workflows/)' },
880
+ { value: '.claude/workflows', label: 'Claude Code (.claude/workflows/)' },
881
+ { value: 'workflows', label: 'Generic (workflows/)' },
882
+ { value: 'custom', label: 'Custom path...' },
883
+ ],
884
+ })
885
+ if (p.isCancel(toolChoice)) { p.cancel('Cancelled'); return }
886
+
887
+ let workflowsDirName = toolChoice as string
888
+ if (toolChoice === 'custom') {
889
+ const customDir = await p.text({ message: 'Enter custom path:', initialValue: 'workflows', placeholder: 'workflows' })
890
+ if (p.isCancel(customDir)) { p.cancel('Cancelled'); return }
891
+ workflowsDirName = customDir as string
892
+ }
893
+
894
+ const targetDir = join(targetProject as string, workflowsDirName)
895
+ if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true })
896
+
897
+ spinner.start(`Installing ${selectedWorkflows.length} workflow(s) to ${targetDir}...`)
898
+ let successCount = 0
899
+ for (const workflowName of selectedWorkflows) {
900
+ const meta = workflows[workflowName]
901
+ const srcFile = join(root, meta.file)
902
+ if (!existsSync(srcFile)) {
903
+ p.log.warn(`Workflow file not found: ${meta.file}`)
904
+ continue
905
+ }
906
+ const destFile = join(targetDir, `${workflowName}.md`)
907
+ cpSync(srcFile, destFile)
908
+ successCount++
909
+ }
910
+ spinner.stop(`Installed ${successCount}/${selectedWorkflows.length} workflow(s)`)
911
+ }
912
+
913
+ async function findRulesAndWorkflows(query?: string) {
914
+ p.intro(`Search results for ${query ? `"${query}"` : 'all'}`)
915
+
916
+ let q = ''
917
+ if (!query) {
918
+ const userInput = await p.text({
919
+ message: 'Enter keyword to search (leave empty to list all):',
920
+ placeholder: 'e.g. vue',
921
+ })
922
+ if (p.isCancel(userInput)) { p.cancel('Cancelled'); return }
923
+ q = (userInput as string).toLowerCase()
924
+ }
925
+ else {
926
+ q = query.toLowerCase()
927
+ }
928
+
929
+ const matchedRuleCollections = Object.keys(ruleCollections).filter(c => c.toLowerCase().includes(q))
930
+ if (matchedRuleCollections.length > 0) {
931
+ p.log.success('📦 Rule Collections found:')
932
+ for (const c of matchedRuleCollections) {
933
+ const col = ruleCollections[c]
934
+ const rulesCount = col.rules?.length ?? 0
935
+ const wfCount = col.workflows?.length ?? 0
936
+ p.log.message(` - ${c} (${rulesCount} rules, ${wfCount} workflows)`)
937
+ }
938
+ }
939
+
940
+ const matchedRules = Object.keys(rules).filter(r => r.toLowerCase().includes(q))
941
+ if (matchedRules.length > 0) {
942
+ p.log.success('📋 Rules found:')
943
+ for (const r of matchedRules) {
944
+ p.log.message(` - ${r}: ${rules[r].description ?? ''} -> \`npx devskill rules ${r}\``)
945
+ }
946
+ }
947
+ else if (q) {
948
+ p.log.warn('No rules found matching query')
949
+ }
950
+
951
+ const matchedWorkflows = Object.keys(workflows).filter(w => w.toLowerCase().includes(q))
952
+ if (matchedWorkflows.length > 0) {
953
+ p.log.success('⚙️ Workflows found:')
954
+ for (const w of matchedWorkflows) {
955
+ p.log.message(` - ${w}: ${workflows[w].description ?? ''} -> \`npx devskill workflows ${w}\``)
956
+ }
957
+ }
958
+ else if (q) {
959
+ p.log.warn('No workflows found matching query')
960
+ }
961
+ }
962
+
728
963
  async function main() {
964
+
729
965
  const args = process.argv.slice(2)
730
966
  const skipPrompt = args.includes('-y') || args.includes('--yes')
731
967
 
@@ -784,9 +1020,31 @@ async function main() {
784
1020
  return
785
1021
  }
786
1022
 
1023
+ if (command === 'rules') {
1024
+ const namesJoined = targetNames.length > 0 ? ` (${targetNames.join(', ')})` : ''
1025
+ p.intro(`Skills Manager - Rules${namesJoined}`)
1026
+ await installRules(targetNames)
1027
+ p.outro('Done')
1028
+ return
1029
+ }
1030
+
1031
+ if (command === 'workflows') {
1032
+ const namesJoined = targetNames.length > 0 ? ` (${targetNames.join(', ')})` : ''
1033
+ p.intro(`Skills Manager - Workflows${namesJoined}`)
1034
+ await installWorkflows(targetNames)
1035
+ p.outro('Done')
1036
+ return
1037
+ }
1038
+
1039
+ if (command === 'find-rules') {
1040
+ await findRulesAndWorkflows(targetName)
1041
+ p.outro('Done')
1042
+ return
1043
+ }
1044
+
787
1045
  if (skipPrompt) {
788
1046
  p.log.error('Command required when using -y flag')
789
- p.log.info('Available commands: find, install, add, init, sync, check, cleanup')
1047
+ p.log.info('Available commands: find, install, add, rules, workflows, find-rules, init, sync, check, cleanup')
790
1048
  process.exit(1)
791
1049
  }
792
1050
 
@@ -795,10 +1053,13 @@ async function main() {
795
1053
  const action = await p.select({
796
1054
  message: 'What would you like to do?',
797
1055
  options: [
798
- { value: 'install', label: 'Install collections', hint: 'Copy entire skill collections to a local project' },
1056
+ { value: 'install', label: 'Install skill collections', hint: 'Copy entire skill collections to a local project' },
799
1057
  { value: 'add', label: 'Add specific skills', hint: 'Choose individual skills to add to your project' },
800
1058
  { value: 'find', label: 'Find a skill or collection', hint: 'Search by keyword' },
801
- { value: 'sync', label: 'Sync submodules', hint: 'Pull latest and sync Type 2 skills' },
1059
+ { value: 'rules', label: 'Install rules', hint: 'Copy rule files to your AI tool rules directory' },
1060
+ { value: 'workflows', label: 'Install workflows', hint: 'Copy workflow files to your AI tool workflows directory' },
1061
+ { value: 'find-rules', label: 'Find rules or workflows', hint: 'Search rules/workflows by keyword' },
1062
+ { value: 'sync', label: 'Sync submodules', hint: 'Pull latest and sync vendor skills' },
802
1063
  { value: 'init', label: 'Init submodules', hint: 'Add new submodules from meta.ts' },
803
1064
  { value: 'check', label: 'Check updates', hint: 'See available updates' },
804
1065
  { value: 'cleanup', label: 'Cleanup', hint: 'Remove skills not listed in meta.ts' },
@@ -832,6 +1093,15 @@ async function main() {
832
1093
  case 'cleanup':
833
1094
  await cleanup()
834
1095
  break
1096
+ case 'rules':
1097
+ await installRules()
1098
+ break
1099
+ case 'workflows':
1100
+ await installWorkflows()
1101
+ break
1102
+ case 'find-rules':
1103
+ await findRulesAndWorkflows()
1104
+ break
835
1105
  }
836
1106
 
837
1107
  p.outro('Done')
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: deepinfra
3
+ description: Integration guidelines for DeepInfra API. Directs the agent to adapt DeepInfra REST API calls to the target repository's standard HTTP client.
4
+ metadata:
5
+ author: AI Assistant
6
+ version: "1.0.1"
7
+ source: Generated from sources/deepinfra
8
+ ---
9
+
10
+ # DeepInfra API
11
+
12
+ > This skill focuses on integrating DeepInfra's REST API into any project. **Do not use language-specific SDKs** (like the `openai` NodeJS package or LangChain) unless the project already heavily depends on them.
13
+
14
+ ## Key Integration Rules
15
+
16
+ - **Use the Repository's Native HTTP Client**: Before writing code to call the DeepInfra API, **you must analyze the current repository** to identify how external HTTP requests are typically made (e.g., a custom `useFetch` wrapper, an Axios instance, Elixir `Req`, or standard `fetch`).
17
+ - **Adapt to Existing Patterns**: Send the DeepInfra request using the exact fetching mechanism discovered in the project. Do not introduce raw `fetch` commands or new libraries if a standard API wrapper already exists.
18
+ - **Endpoint**: The base URL for most AI inference tasks (chat completions) is `POST https://api.deepinfra.com/v1/openai/chat/completions`.
19
+ - **Authentication**: You must set the `Authorization: Bearer <API_KEY>` header. Always read the API key from environment variables (e.g., `process.env.DEEPINFRA_API_KEY` or `System.get_env("DEEPINFRA_API_KEY")`) rather than hardcoding.
20
+ - **Headers**: Always include `Content-Type: application/json`.
21
+
22
+ ## Quick Reference
23
+
24
+ ### Standard JSON Payload
25
+
26
+ Adapt this payload to the project's standard API caller:
27
+
28
+ ```json
29
+ {
30
+ "model": "meta-llama/Meta-Llama-3-70B-Instruct",
31
+ "messages": [
32
+ {
33
+ "role": "user",
34
+ "content": "Hello!"
35
+ }
36
+ ]
37
+ }
38
+ ```
39
+
40
+ ### Example Response Structure
41
+
42
+ DeepInfra yields an OpenAI-compatible JSON structure. Handle the response parsing according to the repository's typical data-transformation layers:
43
+
44
+ ```json
45
+ {
46
+ "id": "chatcmpl-guMTxWgpFf",
47
+ "object": "chat.completion",
48
+ "created": 1694623155,
49
+ "model": "meta-llama/Meta-Llama-3-70B-Instruct",
50
+ "choices": [
51
+ {
52
+ "index": 0,
53
+ "message": {
54
+ "role": "assistant",
55
+ "content": "Hello! It's nice to meet you."
56
+ },
57
+ "finish_reason": "stop"
58
+ }
59
+ ],
60
+ "usage": {
61
+ "prompt_tokens": 15,
62
+ "completion_tokens": 16,
63
+ "total_tokens": 31
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Supported Models
69
+
70
+ Always use the exact model ID from the DeepInfra catalog, such as:
71
+ - `deepseek-ai/DeepSeek-V3`
72
+ - `meta-llama/Meta-Llama-3-70B-Instruct`
@@ -0,0 +1,52 @@
1
+ ---
2
+ description: Feature branch git workflow
3
+ ---
4
+
5
+ ## Creating a Feature Branch
6
+
7
+ 1. Ensure you are on the latest `main` branch:
8
+ ```bash
9
+ git checkout main
10
+ git pull origin main
11
+ ```
12
+
13
+ 2. Create and checkout a new feature branch:
14
+ ```bash
15
+ git checkout -b feature/<ticket-id>-short-description
16
+ # Example: feature/BX-123-add-user-login
17
+ ```
18
+
19
+ ## During Development
20
+
21
+ 3. Commit frequently with meaningful messages following Conventional Commits:
22
+ ```
23
+ feat: add login form validation
24
+ fix: handle empty email edge case
25
+ chore: update dependencies
26
+ ```
27
+
28
+ 4. Push branch to remote regularly:
29
+ ```bash
30
+ git push origin feature/<branch-name>
31
+ ```
32
+
33
+ ## Creating a Pull Request
34
+
35
+ 5. Rebase on `main` before opening a PR to keep history clean:
36
+ ```bash
37
+ git fetch origin
38
+ git rebase origin/main
39
+ ```
40
+
41
+ 6. Open a Pull Request with:
42
+ - Title matching the ticket: `feat: BX-123 Add user login`
43
+ - Description of changes and testing steps
44
+ - Link to the ticket/issue
45
+
46
+ ## After Merge
47
+
48
+ 7. Delete the feature branch locally and remotely:
49
+ ```bash
50
+ git branch -d feature/<branch-name>
51
+ git push origin --delete feature/<branch-name>
52
+ ```