@octo-cyber/ai 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/ai.module.d.ts +1 -0
  2. package/dist/ai.module.d.ts.map +1 -1
  3. package/dist/ai.module.js +21 -2
  4. package/dist/ai.module.js.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +8 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/skill/controllers/skill.controller.d.ts +33 -0
  10. package/dist/skill/controllers/skill.controller.d.ts.map +1 -0
  11. package/dist/skill/controllers/skill.controller.js +264 -0
  12. package/dist/skill/controllers/skill.controller.js.map +1 -0
  13. package/dist/skill/entities/octo-skill.entity.d.ts +22 -0
  14. package/dist/skill/entities/octo-skill.entity.d.ts.map +1 -0
  15. package/dist/skill/entities/octo-skill.entity.js +91 -0
  16. package/dist/skill/entities/octo-skill.entity.js.map +1 -0
  17. package/dist/skill/index.d.ts +7 -0
  18. package/dist/skill/index.d.ts.map +1 -0
  19. package/dist/skill/index.js +17 -0
  20. package/dist/skill/index.js.map +1 -0
  21. package/dist/skill/services/skill-generator.service.d.ts +47 -0
  22. package/dist/skill/services/skill-generator.service.d.ts.map +1 -0
  23. package/dist/skill/services/skill-generator.service.js +232 -0
  24. package/dist/skill/services/skill-generator.service.js.map +1 -0
  25. package/dist/skill/services/skill-installer.service.d.ts +38 -0
  26. package/dist/skill/services/skill-installer.service.d.ts.map +1 -0
  27. package/dist/skill/services/skill-installer.service.js +150 -0
  28. package/dist/skill/services/skill-installer.service.js.map +1 -0
  29. package/dist/skill/services/skill-registry.service.d.ts +60 -0
  30. package/dist/skill/services/skill-registry.service.d.ts.map +1 -0
  31. package/dist/skill/services/skill-registry.service.js +147 -0
  32. package/dist/skill/services/skill-registry.service.js.map +1 -0
  33. package/dist/skill/types.d.ts +45 -0
  34. package/dist/skill/types.d.ts.map +1 -0
  35. package/dist/skill/types.js +6 -0
  36. package/dist/skill/types.js.map +1 -0
  37. package/package.json +4 -4
  38. package/web/components/SkillCard.tsx +139 -0
  39. package/web/components/SkillEditorDialog.tsx +77 -0
  40. package/web/components/SkillInstallDialog.tsx +134 -0
  41. package/web/index.ts +2 -0
  42. package/web/manifest.ts +5 -4
  43. package/web/messages/en-US.json +53 -0
  44. package/web/messages/zh-CN.json +53 -0
  45. package/web/pages/SkillsPage.tsx +206 -0
  46. package/web/services/skill-service.ts +89 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-registry.service.js","sourceRoot":"","sources":["../../../src/skill/services/skill-registry.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Skill-related type definitions for the SKILL.md generation system.
3
+ */
4
+ /** Supported AI tool targets for skill installation. */
5
+ export type SkillTarget = 'claude' | 'cursor' | 'codex';
6
+ /** Information about an AI tool installation target. */
7
+ export interface SkillTargetInfo {
8
+ id: SkillTarget;
9
+ name: string;
10
+ /** Base directory for skills (e.g. ~/.claude/skills/) */
11
+ skillsDir: string;
12
+ /** How to detect if the tool is installed */
13
+ detectDir: string;
14
+ isInstalled: boolean;
15
+ }
16
+ /** Source type for a skill. */
17
+ export type SkillSourceType = 'module' | 'custom';
18
+ /** Parameters for generating a skill from a module. */
19
+ export interface SkillGenerateParams {
20
+ /** Module name (e.g. 'auth', 'erp'). */
21
+ module: string;
22
+ /** Base URL of the Octo server (e.g. 'http://localhost:9988'). */
23
+ baseUrl: string;
24
+ /** API Key to embed in the skill (full key string). */
25
+ apiKey?: string;
26
+ }
27
+ /** Result of skill generation. */
28
+ export interface SkillGenerateResult {
29
+ name: string;
30
+ description: string;
31
+ content: string;
32
+ sourceModule: string;
33
+ version: string;
34
+ }
35
+ /** Endpoint info extracted from RouteRegistry for skill generation. */
36
+ export interface EndpointInfo {
37
+ method: string;
38
+ path: string;
39
+ summary: string;
40
+ permission?: string;
41
+ controller: string;
42
+ controllerName?: string;
43
+ tags?: string[];
44
+ }
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/skill/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAExD,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,+BAA+B;AAC/B,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAElD,uDAAuD;AACvD,MAAM,WAAW,mBAAmB;IAClC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kCAAkC;AAClC,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Skill-related type definitions for the SKILL.md generation system.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/skill/types.ts"],"names":[],"mappings":";AAAA;;GAEG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octo-cyber/ai",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Octo AI module — Agent + Capability + SyncBridge + client registration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,9 +22,9 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@octo-cyber/task": "^0.5.1",
26
- "@octo-cyber/core": "^0.5.1",
27
- "@octo-cyber/auth": "^0.5.1"
25
+ "@octo-cyber/core": "^0.5.4",
26
+ "@octo-cyber/auth": "^0.5.5",
27
+ "@octo-cyber/task": "^0.5.1"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": "^19.0.0",
@@ -0,0 +1,139 @@
1
+ 'use client'
2
+
3
+ import { useTranslations } from 'next-intl'
4
+ import {
5
+ Download,
6
+ Edit,
7
+ MoreHorizontal,
8
+ Trash2,
9
+ ToggleLeft,
10
+ ToggleRight,
11
+ CheckCircle2,
12
+ } from 'lucide-react'
13
+
14
+ import { Badge } from '@octo-cyber/ui/components/ui/badge'
15
+ import { Button } from '@octo-cyber/ui/components/ui/button'
16
+ import {
17
+ Card,
18
+ CardContent,
19
+ CardDescription,
20
+ CardHeader,
21
+ CardTitle,
22
+ } from '@octo-cyber/ui/components/ui/card'
23
+ import {
24
+ DropdownMenu,
25
+ DropdownMenuContent,
26
+ DropdownMenuItem,
27
+ DropdownMenuSeparator,
28
+ DropdownMenuTrigger,
29
+ } from '@octo-cyber/ui/components/ui/dropdown-menu'
30
+
31
+ import type { SkillInfo, SkillTargetInfo } from '../services/skill-service'
32
+
33
+ interface SkillCardProps {
34
+ skill: SkillInfo
35
+ targets: SkillTargetInfo[]
36
+ onInstall: () => void
37
+ onEdit: () => void
38
+ onToggle: () => void
39
+ onDelete: () => void
40
+ }
41
+
42
+ export default function SkillCard({
43
+ skill,
44
+ targets,
45
+ onInstall,
46
+ onEdit,
47
+ onToggle,
48
+ onDelete,
49
+ }: SkillCardProps) {
50
+ const t = useTranslations('aiSkills')
51
+
52
+ const installedTargets = skill.installedTargets ?? []
53
+
54
+ return (
55
+ <Card className="flex flex-col">
56
+ <CardHeader className="flex-row items-start justify-between space-y-0 pb-2">
57
+ <div className="space-y-1">
58
+ <CardTitle className="text-base font-semibold">
59
+ {skill.name}
60
+ </CardTitle>
61
+ <CardDescription className="line-clamp-2 text-sm">
62
+ {skill.description}
63
+ </CardDescription>
64
+ </div>
65
+ <DropdownMenu>
66
+ <DropdownMenuTrigger asChild>
67
+ <Button variant="ghost" size="icon" className="h-8 w-8">
68
+ <MoreHorizontal className="h-4 w-4" />
69
+ </Button>
70
+ </DropdownMenuTrigger>
71
+ <DropdownMenuContent align="end">
72
+ <DropdownMenuItem onClick={onEdit}>
73
+ <Edit className="mr-2 h-4 w-4" />
74
+ {t('actions.edit')}
75
+ </DropdownMenuItem>
76
+ <DropdownMenuItem onClick={onToggle}>
77
+ {skill.isEnabled ? (
78
+ <ToggleLeft className="mr-2 h-4 w-4" />
79
+ ) : (
80
+ <ToggleRight className="mr-2 h-4 w-4" />
81
+ )}
82
+ {skill.isEnabled ? t('actions.disable') : t('actions.enable')}
83
+ </DropdownMenuItem>
84
+ <DropdownMenuSeparator />
85
+ <DropdownMenuItem onClick={onDelete} className="text-destructive">
86
+ <Trash2 className="mr-2 h-4 w-4" />
87
+ {t('actions.delete')}
88
+ </DropdownMenuItem>
89
+ </DropdownMenuContent>
90
+ </DropdownMenu>
91
+ </CardHeader>
92
+
93
+ <CardContent className="flex flex-1 flex-col justify-between gap-3">
94
+ {/* Meta info */}
95
+ <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
96
+ <Badge variant={skill.isEnabled ? 'default' : 'secondary'} className="text-xs">
97
+ {skill.isEnabled ? t('status.enabled') : t('status.disabled')}
98
+ </Badge>
99
+ <span>{t('meta.source')}: {skill.sourceModule}</span>
100
+ <span>v{skill.version}</span>
101
+ {skill.isEdited && (
102
+ <Badge variant="outline" className="text-xs">
103
+ {t('meta.edited')}
104
+ </Badge>
105
+ )}
106
+ </div>
107
+
108
+ {/* Install status */}
109
+ <div className="flex flex-wrap items-center gap-1.5">
110
+ {targets.filter((tgt) => tgt.isInstalled).map((tgt) => {
111
+ const isInstalled = installedTargets.includes(tgt.id)
112
+ return (
113
+ <Badge
114
+ key={tgt.id}
115
+ variant={isInstalled ? 'default' : 'outline'}
116
+ className="text-xs"
117
+ >
118
+ {isInstalled && <CheckCircle2 className="mr-1 h-3 w-3" />}
119
+ {tgt.name}
120
+ </Badge>
121
+ )
122
+ })}
123
+ </div>
124
+
125
+ {/* Install button */}
126
+ <Button
127
+ variant="outline"
128
+ size="sm"
129
+ className="w-full"
130
+ onClick={onInstall}
131
+ disabled={!skill.isEnabled}
132
+ >
133
+ <Download className="mr-2 h-4 w-4" />
134
+ {t('actions.install')}
135
+ </Button>
136
+ </CardContent>
137
+ </Card>
138
+ )
139
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useTranslations } from 'next-intl'
5
+ import { Loader2 } from 'lucide-react'
6
+
7
+ import { Button } from '@octo-cyber/ui/components/ui/button'
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '@octo-cyber/ui/components/ui/dialog'
16
+ import { Textarea } from '@octo-cyber/ui/components/ui/textarea'
17
+
18
+ import type { SkillInfo } from '../services/skill-service'
19
+
20
+ interface SkillEditorDialogProps {
21
+ skill: SkillInfo
22
+ open: boolean
23
+ onOpenChange: (open: boolean) => void
24
+ onSave: (id: string, content: string) => Promise<void>
25
+ }
26
+
27
+ export default function SkillEditorDialog({
28
+ skill,
29
+ open,
30
+ onOpenChange,
31
+ onSave,
32
+ }: SkillEditorDialogProps) {
33
+ const t = useTranslations('aiSkills')
34
+ const tc = useTranslations('common')
35
+
36
+ const [content, setContent] = useState(skill.content)
37
+ const [saving, setSaving] = useState(false)
38
+
39
+ const handleSave = async () => {
40
+ setSaving(true)
41
+ try {
42
+ await onSave(skill.id, content)
43
+ } finally {
44
+ setSaving(false)
45
+ }
46
+ }
47
+
48
+ return (
49
+ <Dialog open={open} onOpenChange={onOpenChange}>
50
+ <DialogContent className="sm:max-w-3xl max-h-[80vh]">
51
+ <DialogHeader>
52
+ <DialogTitle>{t('editor.title', { name: skill.name })}</DialogTitle>
53
+ <DialogDescription>
54
+ {t('editor.description')}
55
+ </DialogDescription>
56
+ </DialogHeader>
57
+
58
+ <Textarea
59
+ value={content}
60
+ onChange={(e) => setContent(e.target.value)}
61
+ className="min-h-[400px] font-mono text-sm"
62
+ placeholder="SKILL.md content..."
63
+ />
64
+
65
+ <DialogFooter>
66
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
67
+ {tc('cancel')}
68
+ </Button>
69
+ <Button onClick={handleSave} disabled={saving}>
70
+ {saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
71
+ {tc('save')}
72
+ </Button>
73
+ </DialogFooter>
74
+ </DialogContent>
75
+ </Dialog>
76
+ )
77
+ }
@@ -0,0 +1,134 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback } from 'react'
4
+ import { useTranslations } from 'next-intl'
5
+ import { toast } from 'sonner'
6
+ import { CheckCircle2, Loader2 } from 'lucide-react'
7
+
8
+ import { Button } from '@octo-cyber/ui/components/ui/button'
9
+ import { Checkbox } from '@octo-cyber/ui/components/ui/checkbox'
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogDescription,
14
+ DialogFooter,
15
+ DialogHeader,
16
+ DialogTitle,
17
+ } from '@octo-cyber/ui/components/ui/dialog'
18
+ import { Label } from '@octo-cyber/ui/components/ui/label'
19
+
20
+ import { skillService } from '../services/skill-service'
21
+ import type { SkillInfo, SkillTargetInfo } from '../services/skill-service'
22
+
23
+ interface SkillInstallDialogProps {
24
+ skill: SkillInfo
25
+ targets: SkillTargetInfo[]
26
+ open: boolean
27
+ onOpenChange: (open: boolean) => void
28
+ onDone: () => void
29
+ }
30
+
31
+ export default function SkillInstallDialog({
32
+ skill,
33
+ targets,
34
+ open,
35
+ onOpenChange,
36
+ onDone,
37
+ }: SkillInstallDialogProps) {
38
+ const t = useTranslations('aiSkills')
39
+ const tc = useTranslations('common')
40
+
41
+ const availableTargets = targets.filter((tgt) => tgt.isInstalled)
42
+ const [selected, setSelected] = useState<string[]>(
43
+ skill.installedTargets ?? [],
44
+ )
45
+ const [installing, setInstalling] = useState(false)
46
+
47
+ const toggleTarget = useCallback((id: string) => {
48
+ setSelected((prev) =>
49
+ prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
50
+ )
51
+ }, [])
52
+
53
+ const handleInstall = useCallback(async () => {
54
+ if (selected.length === 0) return
55
+ setInstalling(true)
56
+ try {
57
+ const result = await skillService.install(skill.id, selected)
58
+ if (result.failed.length > 0) {
59
+ toast.error(t('toast.installPartial', {
60
+ failed: result.failed.map((f) => f.target).join(', '),
61
+ }))
62
+ } else {
63
+ toast.success(t('toast.installed', {
64
+ targets: selected.join(', '),
65
+ }))
66
+ }
67
+ if (result.apiKeyCreated) {
68
+ toast.success(t('toast.apiKeyCreated'))
69
+ }
70
+ onDone()
71
+ } catch {
72
+ toast.error(t('toast.installFailed'))
73
+ } finally {
74
+ setInstalling(false)
75
+ }
76
+ }, [skill.id, selected, t, onDone])
77
+
78
+ return (
79
+ <Dialog open={open} onOpenChange={onOpenChange}>
80
+ <DialogContent className="sm:max-w-md">
81
+ <DialogHeader>
82
+ <DialogTitle>{t('installDialog.title')}</DialogTitle>
83
+ <DialogDescription>
84
+ {t('installDialog.description', { name: skill.name })}
85
+ </DialogDescription>
86
+ </DialogHeader>
87
+
88
+ <div className="space-y-3 py-4">
89
+ {availableTargets.length === 0 ? (
90
+ <p className="text-sm text-muted-foreground">
91
+ {t('installDialog.noTargets')}
92
+ </p>
93
+ ) : (
94
+ availableTargets.map((tgt) => {
95
+ const isAlreadyInstalled = (skill.installedTargets ?? []).includes(tgt.id)
96
+ return (
97
+ <div key={tgt.id} className="flex items-center space-x-3">
98
+ <Checkbox
99
+ id={`target-${tgt.id}`}
100
+ checked={selected.includes(tgt.id)}
101
+ onCheckedChange={() => toggleTarget(tgt.id)}
102
+ />
103
+ <Label htmlFor={`target-${tgt.id}`} className="flex items-center gap-2 text-sm">
104
+ {tgt.name}
105
+ {isAlreadyInstalled && (
106
+ <CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
107
+ )}
108
+ </Label>
109
+ </div>
110
+ )
111
+ })
112
+ )}
113
+ </div>
114
+
115
+ <p className="text-xs text-muted-foreground">
116
+ {t('installDialog.apiKeyNote')}
117
+ </p>
118
+
119
+ <DialogFooter>
120
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
121
+ {tc('cancel')}
122
+ </Button>
123
+ <Button
124
+ onClick={handleInstall}
125
+ disabled={installing || selected.length === 0}
126
+ >
127
+ {installing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
128
+ {t('actions.install')}
129
+ </Button>
130
+ </DialogFooter>
131
+ </DialogContent>
132
+ </Dialog>
133
+ )
134
+ }
package/web/index.ts CHANGED
@@ -14,6 +14,8 @@ export { default as aiMessagesEnUS } from './messages/en-US.json';
14
14
  // Pages
15
15
  export { default as AiCliToolsPage } from './pages/AiCliToolsPage';
16
16
  export { default as WorkspacesPage } from './pages/WorkspacesPage';
17
+ export { default as SkillsPage } from './pages/SkillsPage';
17
18
 
18
19
  // Services
19
20
  export { aiCliService } from './services/ai-cli-service';
21
+ export { skillService } from './services/skill-service';
package/web/manifest.ts CHANGED
@@ -5,10 +5,11 @@ export const aiFrontendManifest: IFrontendManifest = {
5
5
  icon: 'Terminal',
6
6
  color: 'cyan',
7
7
  position: 'main',
8
- titleKey: 'aiCli.title',
9
- descriptionKey: 'aiCli.description',
8
+ titleKey: 'aiTools.title',
9
+ descriptionKey: 'aiTools.description',
10
10
  pages: [
11
- { path: '/ai-tools', titleKey: 'aiCli.pages.tools', icon: 'Terminal' },
12
- { path: '/ai-tools/workspaces', titleKey: 'aiCli.pages.workspaces', icon: 'Terminal' },
11
+ { path: '/ai-tools', titleKey: 'aiTools.pages.tools', icon: 'Terminal', component: '@octo-cyber/ai/web/pages/AiCliToolsPage' },
12
+ { path: '/ai-tools/workspaces', titleKey: 'aiTools.pages.workspaces', icon: 'Terminal', component: '@octo-cyber/ai/web/pages/WorkspacesPage' },
13
+ { path: '/ai-tools/skills', titleKey: 'aiTools.pages.skills', icon: 'Sparkles', component: '@octo-cyber/ai/web/pages/SkillsPage' },
13
14
  ],
14
15
  };
@@ -77,5 +77,58 @@
77
77
  "pullFailed": "Pull failed"
78
78
  }
79
79
  }
80
+ },
81
+ "aiSkills": {
82
+ "title": "AI Skills",
83
+ "description": "Manage SKILL.md files generated from system APIs, install to Claude Code, Cursor, and more",
84
+ "empty": "No skills yet. Click the button below to generate.",
85
+ "tabs": {
86
+ "all": "All",
87
+ "enabled": "Enabled",
88
+ "installed": "Installed"
89
+ },
90
+ "status": {
91
+ "enabled": "Enabled",
92
+ "disabled": "Disabled"
93
+ },
94
+ "meta": {
95
+ "source": "Source",
96
+ "edited": "Edited"
97
+ },
98
+ "actions": {
99
+ "generate": "Generate Skills",
100
+ "install": "Install",
101
+ "edit": "Edit",
102
+ "enable": "Enable",
103
+ "disable": "Disable",
104
+ "delete": "Delete"
105
+ },
106
+ "installDialog": {
107
+ "title": "Install Skill",
108
+ "description": "Select AI tools to install {name}",
109
+ "noTargets": "No installed AI tools detected",
110
+ "apiKeyNote": "An API Key will be auto-created and embedded in the Skill file. AI tools can call the API directly."
111
+ },
112
+ "editor": {
113
+ "title": "Edit {name}",
114
+ "description": "Modify SKILL.md content. Saving marks it as 'Edited' to prevent auto-regeneration overwrite."
115
+ },
116
+ "toast": {
117
+ "loadFailed": "Failed to load skills",
118
+ "generateDone": "Generation complete: {created} created, {updated} updated",
119
+ "generateFailed": "Generation failed",
120
+ "installed": "Installed to {targets}",
121
+ "installFailed": "Installation failed",
122
+ "installPartial": "Partial installation failure: {failed}",
123
+ "apiKeyCreated": "API Key auto-created",
124
+ "saved": "Saved",
125
+ "saveFailed": "Save failed",
126
+ "toggleFailed": "Operation failed",
127
+ "deleted": "Deleted",
128
+ "deleteFailed": "Delete failed"
129
+ },
130
+ "pages": {
131
+ "skills": "AI Skills"
132
+ }
80
133
  }
81
134
  }
@@ -77,5 +77,58 @@
77
77
  "pullFailed": "拉取失败"
78
78
  }
79
79
  }
80
+ },
81
+ "aiSkills": {
82
+ "title": "AI Skills",
83
+ "description": "管理系统 API 生成的 Skill 文件,安装到 Claude Code、Cursor 等 AI 工具",
84
+ "empty": "暂无 Skill,点击下方按钮生成",
85
+ "tabs": {
86
+ "all": "全部",
87
+ "enabled": "已启用",
88
+ "installed": "已安装"
89
+ },
90
+ "status": {
91
+ "enabled": "已启用",
92
+ "disabled": "已禁用"
93
+ },
94
+ "meta": {
95
+ "source": "来源",
96
+ "edited": "已编辑"
97
+ },
98
+ "actions": {
99
+ "generate": "生成 Skills",
100
+ "install": "安装",
101
+ "edit": "编辑",
102
+ "enable": "启用",
103
+ "disable": "禁用",
104
+ "delete": "删除"
105
+ },
106
+ "installDialog": {
107
+ "title": "安装 Skill",
108
+ "description": "选择要安装 {name} 的 AI 工具",
109
+ "noTargets": "未检测到已安装的 AI 工具",
110
+ "apiKeyNote": "安装时会自动创建 API Key 并写入 Skill 文件,AI 工具可直接调用 API。"
111
+ },
112
+ "editor": {
113
+ "title": "编辑 {name}",
114
+ "description": "修改 SKILL.md 内容。保存后将标记为「已编辑」,自动生成不会覆盖。"
115
+ },
116
+ "toast": {
117
+ "loadFailed": "加载 Skill 列表失败",
118
+ "generateDone": "生成完成:{created} 个新建,{updated} 个更新",
119
+ "generateFailed": "生成失败",
120
+ "installed": "已安装到 {targets}",
121
+ "installFailed": "安装失败",
122
+ "installPartial": "部分安装失败:{failed}",
123
+ "apiKeyCreated": "已自动创建 API Key",
124
+ "saved": "已保存",
125
+ "saveFailed": "保存失败",
126
+ "toggleFailed": "操作失败",
127
+ "deleted": "已删除",
128
+ "deleteFailed": "删除失败"
129
+ },
130
+ "pages": {
131
+ "skills": "AI Skills"
132
+ }
80
133
  }
81
134
  }