@mazees/mark 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +209 -0
  3. package/bin/mark.js +27 -0
  4. package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
  5. package/out/renderer/assets/index-G2fADWO-.js +4768 -0
  6. package/out/renderer/assets/index-f3mVse3c.css +1 -0
  7. package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
  8. package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
  9. package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
  10. package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
  11. package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
  12. package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
  13. package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
  14. package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
  15. package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
  16. package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  17. package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  18. package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  19. package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  20. package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  21. package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  22. package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  23. package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  24. package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
  25. package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
  26. package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
  27. package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
  28. package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
  29. package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
  30. package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
  31. package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
  32. package/out/renderer/favicon.ico +0 -0
  33. package/out/renderer/icon-256.png +0 -0
  34. package/out/renderer/icon.svg +1 -0
  35. package/out/renderer/index.html +25 -0
  36. package/out/renderer/manifest.json +25 -0
  37. package/out/renderer/robot-simulator.html +1640 -0
  38. package/package.json +106 -0
  39. package/src/cli/index.js +332 -0
  40. package/src/cli/monitor.js +413 -0
  41. package/src/cli/theme.js +347 -0
  42. package/src/launcher/MarkTray.cs +227 -0
  43. package/src/main/browser-agent.js +1132 -0
  44. package/src/main/git-service.js +90 -0
  45. package/src/main/google/google-calendar.js +69 -0
  46. package/src/main/google/google-drive.js +263 -0
  47. package/src/main/google/google-gmail.js +131 -0
  48. package/src/main/google/google-service.js +241 -0
  49. package/src/main/node-tools.js +23 -0
  50. package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
  51. package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
  52. package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
  53. package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
  54. package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
  55. package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
  56. package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
  57. package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
  58. package/src/main/plugins/plugin-loader.js +306 -0
  59. package/src/main/skills/skill-manager.js +265 -0
  60. package/src/main/syntax-validator.js +455 -0
  61. package/src/main/task-daemon.js +117 -0
  62. package/src/main/telegram/telegram-service.js +835 -0
  63. package/src/main/tools/browser-tools.js +516 -0
  64. package/src/main/tools/file-tools.js +1126 -0
  65. package/src/main/tools/git-tools.js +79 -0
  66. package/src/main/tools/google-tools.js +566 -0
  67. package/src/main/tools/index.js +36 -0
  68. package/src/main/tools/media-tools.js +42 -0
  69. package/src/main/tools/system-tools.js +295 -0
  70. package/src/main/tools/task-tools.js +73 -0
  71. package/src/main/tools/telegram-tools.js +101 -0
  72. package/src/renderer/index.html +24 -0
  73. package/src/renderer/public/favicon.ico +0 -0
  74. package/src/renderer/public/icon-256.png +0 -0
  75. package/src/renderer/public/icon.svg +1 -0
  76. package/src/renderer/public/manifest.json +25 -0
  77. package/src/renderer/public/robot-simulator.html +1640 -0
  78. package/src/renderer/src/App.jsx +304 -0
  79. package/src/renderer/src/api/ai/awareness.js +294 -0
  80. package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
  81. package/src/renderer/src/api/ai/contextCompactor.js +129 -0
  82. package/src/renderer/src/api/ai/contextManager.js +1130 -0
  83. package/src/renderer/src/api/ai/core.js +167 -0
  84. package/src/renderer/src/api/ai/index.js +4 -0
  85. package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
  86. package/src/renderer/src/api/ai/persona.js +115 -0
  87. package/src/renderer/src/api/ai/planning.js +317 -0
  88. package/src/renderer/src/api/ai/relationship.js +177 -0
  89. package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
  90. package/src/renderer/src/api/ai/tools.js +167 -0
  91. package/src/renderer/src/api/ai/utils.js +359 -0
  92. package/src/renderer/src/api/db.js +1013 -0
  93. package/src/renderer/src/api/oramaStore.js +592 -0
  94. package/src/renderer/src/api/ragPipeline.js +87 -0
  95. package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
  96. package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
  97. package/src/renderer/src/api/subagent/subagentStore.js +173 -0
  98. package/src/renderer/src/api/taskExecutor.js +18 -0
  99. package/src/renderer/src/api/taskStore.js +315 -0
  100. package/src/renderer/src/api/tools/core-tools.js +707 -0
  101. package/src/renderer/src/api/tools/index.js +151 -0
  102. package/src/renderer/src/api/turnPairMigrator.js +200 -0
  103. package/src/renderer/src/api/vectorMemory.js +136 -0
  104. package/src/renderer/src/api/wakeWord.js +101 -0
  105. package/src/renderer/src/api/web-bridge.js +885 -0
  106. package/src/renderer/src/api/webSpeech.js +178 -0
  107. package/src/renderer/src/api/workspaceRag.js +76 -0
  108. package/src/renderer/src/assets/electron.svg +10 -0
  109. package/src/renderer/src/assets/icon.svg +1 -0
  110. package/src/renderer/src/assets/main.css +589 -0
  111. package/src/renderer/src/assets/music-cover.png +0 -0
  112. package/src/renderer/src/assets/wavy-lines.svg +25 -0
  113. package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
  114. package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
  115. package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
  116. package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
  117. package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
  118. package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
  119. package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
  120. package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
  121. package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
  122. package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
  123. package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
  124. package/src/renderer/src/components/Chat/index.js +11 -0
  125. package/src/renderer/src/components/ChatList.jsx +434 -0
  126. package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
  127. package/src/renderer/src/components/Versions.jsx +15 -0
  128. package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
  129. package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
  130. package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
  131. package/src/renderer/src/components/config/CameraSection.jsx +100 -0
  132. package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
  133. package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
  134. package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
  135. package/src/renderer/src/components/config/index.js +5 -0
  136. package/src/renderer/src/components/core/Avatar.jsx +2197 -0
  137. package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
  138. package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
  139. package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
  140. package/src/renderer/src/components/core/HoloCard.jsx +64 -0
  141. package/src/renderer/src/components/core/InputBar.jsx +1346 -0
  142. package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
  143. package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
  144. package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
  145. package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
  146. package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
  147. package/src/renderer/src/components/core/native-skills.js +43 -0
  148. package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
  149. package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
  150. package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
  151. package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
  152. package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
  153. package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
  154. package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
  155. package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
  156. package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
  157. package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
  158. package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
  159. package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
  160. package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
  161. package/src/renderer/src/contexts/ChatContext.jsx +61 -0
  162. package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
  163. package/src/renderer/src/data/faqData.js +202 -0
  164. package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
  165. package/src/renderer/src/hooks/agent/index.js +4 -0
  166. package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
  167. package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
  168. package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
  169. package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
  170. package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
  171. package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
  172. package/src/renderer/src/hooks/useAwareness.js +289 -0
  173. package/src/renderer/src/hooks/useChatArchiver.js +52 -0
  174. package/src/renderer/src/hooks/useConfirm.jsx +68 -0
  175. package/src/renderer/src/hooks/useMarkAgent.js +454 -0
  176. package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
  177. package/src/renderer/src/hooks/useVAD.js +511 -0
  178. package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
  179. package/src/renderer/src/main.jsx +12 -0
  180. package/src/renderer/src/pages/ChatStudio.jsx +922 -0
  181. package/src/renderer/src/pages/Configuration.jsx +360 -0
  182. package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
  183. package/src/renderer/src/pages/Guidebook.jsx +917 -0
  184. package/src/renderer/src/pages/Knowledge.jsx +241 -0
  185. package/src/renderer/src/pages/MarkHome.jsx +491 -0
  186. package/src/renderer/src/pages/NeuralCore.jsx +786 -0
  187. package/src/renderer/src/pages/Plugins.jsx +630 -0
  188. package/src/renderer/src/pages/SkillEditor.jsx +380 -0
  189. package/src/renderer/src/pages/Skills.jsx +219 -0
  190. package/src/renderer/src/pages/Subagents.jsx +342 -0
  191. package/src/renderer/src/pages/TelegramBot.jsx +343 -0
  192. package/src/renderer/src/utils/chatContentParser.js +203 -0
  193. package/src/renderer/src/utils/toolClusters.js +201 -0
  194. package/src/server/bin/sha3_wasm_bg.wasm +0 -0
  195. package/src/server/config-manager.js +57 -0
  196. package/src/server/index.js +195 -0
  197. package/src/server/launcher.js +87 -0
  198. package/src/server/memory/db-store.js +758 -0
  199. package/src/server/memory/orama-store.js +177 -0
  200. package/src/server/memory/vector-engine.js +109 -0
  201. package/src/server/routes/ai.routes.js +280 -0
  202. package/src/server/routes/awareness.routes.js +62 -0
  203. package/src/server/routes/chat.routes.js +61 -0
  204. package/src/server/routes/config.routes.js +41 -0
  205. package/src/server/routes/index.js +35 -0
  206. package/src/server/routes/integrations.routes.js +116 -0
  207. package/src/server/routes/memory.routes.js +256 -0
  208. package/src/server/routes/plugins.routes.js +72 -0
  209. package/src/server/routes/skills.routes.js +139 -0
  210. package/src/server/routes/subagents.routes.js +70 -0
  211. package/src/server/routes/tasks.routes.js +175 -0
  212. package/src/server/routes/workspace.routes.js +127 -0
  213. package/src/server/services/ai/ai-utils.js +161 -0
  214. package/src/server/services/ai/openai-provider.js +757 -0
  215. package/src/server/services/ai/web-provider.js +729 -0
  216. package/src/server/services/ai-bridge.js +46 -0
  217. package/src/server/services/deepseek-web.js +956 -0
  218. package/src/server/services/gemini-web.js +170 -0
  219. package/src/server/services/notification-service.js +91 -0
  220. package/src/server/services/updater.js +125 -0
  221. package/src/server/tools/awareness-tracker.js +292 -0
  222. package/src/server/tools/group-tools.js +1164 -0
  223. package/src/server/tools/media-tools.js +244 -0
  224. package/src/server/tools/pc-agent.js +327 -0
  225. package/src/server/tools/screen-service.js +51 -0
  226. package/src/server/ws-hub.js +168 -0
  227. package/src/shared/parsers/mark-tag-parser.js +275 -0
@@ -0,0 +1,380 @@
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { useParams, useNavigate } from 'react-router-dom'
3
+ import { Save, Trash2, Terminal, Folder, File as FileIcon, Plus, FolderPlus, Edit2, ChevronRight, ChevronDown, Heading1, Heading2, Bold, Italic, CheckSquare, Code, Link, Quote, List } from 'lucide-react'
4
+ import Editor from '@monaco-editor/react'
5
+ import { webApi } from '../api/web-bridge'
6
+
7
+ const FileTreeNode = ({ node, level = 0, selectedPath, onSelect, onRename, onDelete, expandedFolders, toggleFolder }) => {
8
+ const isSelected = selectedPath === node.path
9
+ const isExpanded = expandedFolders[node.path]
10
+
11
+ return (
12
+ <div className="select-none">
13
+ <div
14
+ className={`flex items-center gap-1.5 py-1 px-2 cursor-pointer hover:bg-white/10 transition-colors group ${isSelected ? 'bg-primary/20 text-primary' : 'text-gray-300'}`}
15
+ style={{ paddingLeft: `${level * 12 + 8}px` }}
16
+ onClick={() => {
17
+ if (node.type === 'folder') {
18
+ toggleFolder(node.path)
19
+ } else {
20
+ onSelect(node.path)
21
+ }
22
+ }}
23
+ >
24
+ <div className="flex-shrink-0 w-4 h-4 flex items-center justify-center">
25
+ {node.type === 'folder' ? (
26
+ isExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />
27
+ ) : (
28
+ <FileIcon size={14} className="opacity-70" />
29
+ )}
30
+ </div>
31
+
32
+ {node.type === 'folder' && (
33
+ <Folder size={14} className={isExpanded ? 'text-blue-400' : 'text-blue-400/70'} />
34
+ )}
35
+
36
+ <span className="text-sm truncate flex-1">{node.name}</span>
37
+
38
+ <div className="opacity-0 group-hover:opacity-100 flex items-center gap-1">
39
+ <button
40
+ className="p-1 hover:bg-white/20 rounded text-gray-400 hover:text-white transition-colors"
41
+ onClick={(e) => { e.stopPropagation(); onRename(node) }}
42
+ title="Rename"
43
+ >
44
+ <Edit2 size={12} />
45
+ </button>
46
+ <button
47
+ className="p-1 hover:bg-red-500/20 rounded text-gray-400 hover:text-red-400 transition-colors"
48
+ onClick={(e) => { e.stopPropagation(); onDelete(node) }}
49
+ title="Delete"
50
+ >
51
+ <Trash2 size={12} />
52
+ </button>
53
+ </div>
54
+ </div>
55
+
56
+ {node.type === 'folder' && isExpanded && node.children && (
57
+ <div>
58
+ {node.children.map((child) => (
59
+ <FileTreeNode
60
+ key={child.path}
61
+ node={child}
62
+ level={level + 1}
63
+ selectedPath={selectedPath}
64
+ onSelect={onSelect}
65
+ onRename={onRename}
66
+ onDelete={onDelete}
67
+ expandedFolders={expandedFolders}
68
+ toggleFolder={toggleFolder}
69
+ />
70
+ ))}
71
+ </div>
72
+ )}
73
+ </div>
74
+ )
75
+ }
76
+
77
+ const SkillEditor = () => {
78
+ const { id } = useParams()
79
+ const navigate = useNavigate()
80
+
81
+ const [tree, setTree] = useState([])
82
+ const [selectedPath, setSelectedPath] = useState('SKILL.md')
83
+ const [content, setContent] = useState('')
84
+ const [isEditing, setIsEditing] = useState(false)
85
+ const [isLoading, setIsLoading] = useState(true)
86
+ const [expandedFolders, setExpandedFolders] = useState({})
87
+ const editorRef = useRef(null)
88
+
89
+ const handleFormat = (format) => {
90
+ if (!editorRef.current) return
91
+ const editor = editorRef.current
92
+ const selection = editor.getSelection()
93
+ const model = editor.getModel()
94
+ if (!selection || !model) return
95
+
96
+ const selectedText = model.getValueInRange(selection)
97
+
98
+ let textToInsert = ''
99
+
100
+ switch (format) {
101
+ case 'h1': textToInsert = `# ${selectedText || 'Heading 1'}`; break;
102
+ case 'h2': textToInsert = `## ${selectedText || 'Heading 2'}`; break;
103
+ case 'bold': textToInsert = `**${selectedText || 'teks tebal'}**`; break;
104
+ case 'italic': textToInsert = `*${selectedText || 'teks miring'}*`; break;
105
+ case 'check': textToInsert = `- [ ] ${selectedText || 'Tugas baru'}`; break;
106
+ case 'list': textToInsert = `- ${selectedText || 'Item list'}`; break;
107
+ case 'code': textToInsert = `\`\`\`javascript\n${selectedText || '// Tulis kode di sini'}\n\`\`\``; break;
108
+ case 'quote': textToInsert = `> ${selectedText || 'Kutipan'}`; break;
109
+ case 'link': textToInsert = `[${selectedText || 'Teks Link'}](https://url-di-sini)`; break;
110
+ default: return;
111
+ }
112
+
113
+ editor.executeEdits('toolbar', [{
114
+ range: selection,
115
+ text: textToInsert,
116
+ forceMoveMarkers: true
117
+ }])
118
+ editor.focus()
119
+ }
120
+
121
+ const loadTree = async () => {
122
+ try {
123
+ const data = await webApi.getSkillTree(id)
124
+ setTree(data)
125
+ } catch (e) {
126
+ console.error(e)
127
+ }
128
+ }
129
+
130
+ const loadFileContent = async (path) => {
131
+ setIsLoading(true)
132
+ try {
133
+ const text = await webApi.readSkillFile(id, path)
134
+ setContent(text)
135
+ setSelectedPath(path)
136
+ setIsEditing(false)
137
+ } catch (e) {
138
+ console.error(e)
139
+ } finally {
140
+ setIsLoading(false)
141
+ }
142
+ }
143
+
144
+ useEffect(() => {
145
+ loadTree().then(() => loadFileContent('SKILL.md'))
146
+ }, [id])
147
+
148
+ const toggleFolder = (path) => {
149
+ setExpandedFolders(prev => ({ ...prev, [path]: !prev[path] }))
150
+ }
151
+
152
+ const handleSave = async () => {
153
+ try {
154
+ await webApi.saveSkillFile(id, selectedPath, content)
155
+ setIsEditing(false)
156
+ } catch (e) {
157
+ console.error(e)
158
+ }
159
+ }
160
+
161
+ const handleCreateItem = async (isFolder) => {
162
+ const defaultPath = selectedPath.includes('/') ? selectedPath.substring(0, selectedPath.lastIndexOf('/')) : ''
163
+ const itemName = prompt(`Nama ${isFolder ? 'folder' : 'file'} baru:`)
164
+ if (!itemName) return
165
+
166
+ const newPath = defaultPath ? `${defaultPath}/${itemName}` : itemName
167
+
168
+ try {
169
+ await webApi.createSkillItem(id, newPath, isFolder)
170
+ await loadTree()
171
+ if (!isFolder) {
172
+ loadFileContent(newPath)
173
+ } else {
174
+ setExpandedFolders(prev => ({ ...prev, [newPath]: true }))
175
+ }
176
+ } catch (e) {
177
+ console.error(e)
178
+ alert('Gagal membuat item')
179
+ }
180
+ }
181
+
182
+ const handleDeleteItem = async (node) => {
183
+ if (!confirm(`Hapus ${node.type} "${node.name}"?`)) return
184
+ try {
185
+ await webApi.deleteSkillItem(id, node.path)
186
+ await loadTree()
187
+ if (selectedPath === node.path || selectedPath.startsWith(node.path + '/')) {
188
+ loadFileContent('SKILL.md')
189
+ }
190
+ } catch (e) {
191
+ console.error(e)
192
+ }
193
+ }
194
+
195
+ const handleRenameItem = async (node) => {
196
+ const newName = prompt('Nama baru:', node.name)
197
+ if (!newName || newName === node.name) return
198
+
199
+ const basePath = node.path.includes('/') ? node.path.substring(0, node.path.lastIndexOf('/')) : ''
200
+ const newPath = basePath ? `${basePath}/${newName}` : newName
201
+
202
+ try {
203
+ await webApi.renameSkillItem(id, node.path, newPath)
204
+ await loadTree()
205
+ if (selectedPath === node.path) {
206
+ setSelectedPath(newPath)
207
+ }
208
+ } catch (e) {
209
+ console.error(e)
210
+ }
211
+ }
212
+
213
+ const handleDeleteSkill = async () => {
214
+ if (!confirm(`Hapus keseluruhan skill ${id} beserta semua filenya?`)) return
215
+ try {
216
+ await webApi.deleteSkill(id)
217
+ navigate('/skills')
218
+ } catch (e) {
219
+ console.error(e)
220
+ }
221
+ }
222
+
223
+ return (
224
+ <div className="h-full flex flex-col p-6 overflow-hidden relative font-['Poppins',sans-serif] bg-base-300 rounded-xl border border-white/5 shadow-2xl">
225
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,oklch(var(--n))_0%,transparent_70%)] opacity-20 pointer-events-none" />
226
+ <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/stardust.png')] opacity-10 pointer-events-none" />
227
+
228
+ <div className="relative z-10 flex flex-col h-full">
229
+ {/* Header */}
230
+ <div className="flex items-center justify-between mb-4">
231
+ <div className="flex items-center gap-4">
232
+ <button
233
+ type="button"
234
+ onClick={() => navigate('/skills')}
235
+ className="btn btn-ghost btn-sm btn-circle shrink-0"
236
+ style={{ WebkitAppRegion: 'no-drag' }}
237
+ title="Kembali ke Skills"
238
+ >
239
+ <svg
240
+ xmlns="http://www.w3.org/2000/svg"
241
+ width="1.2em"
242
+ height="1.2em"
243
+ fill="none"
244
+ viewBox="0 0 24 24"
245
+ stroke="currentColor"
246
+ strokeWidth={2}
247
+ >
248
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
249
+ </svg>
250
+ </button>
251
+ <div>
252
+ <h1 className="text-2xl font-bold flex items-center gap-2">
253
+ <Terminal className="text-emerald-400" size={22} />
254
+ <span>{id}</span>
255
+ </h1>
256
+ <p className="opacity-50 text-xs mt-0.5">Workspace Editor</p>
257
+ </div>
258
+ </div>
259
+
260
+ <div className="flex items-center gap-2">
261
+ <button
262
+ className="btn btn-outline btn-error gap-2"
263
+ onClick={handleDeleteSkill}
264
+ >
265
+ <Trash2 size={16} /> Delete Skill
266
+ </button>
267
+ <button
268
+ className={`btn gap-2 ${isEditing ? 'btn-primary' : 'btn-disabled'}`}
269
+ onClick={handleSave}
270
+ disabled={!isEditing}
271
+ >
272
+ <Save size={16} /> Save Changes
273
+ </button>
274
+ </div>
275
+ </div>
276
+
277
+ {/* IDE Layout */}
278
+ <div className="flex-1 flex gap-4 overflow-hidden">
279
+
280
+ {/* Sidebar: Explorer */}
281
+ <div className="w-64 flex flex-col bg-[#1e1e1e]/80 rounded-2xl border border-white/10 overflow-hidden shadow-2xl">
282
+ <div className="p-3 flex items-center justify-between border-b border-white/5 bg-black/20">
283
+ <span className="text-xs font-bold uppercase tracking-wider text-gray-400">Explorer</span>
284
+ <div className="flex gap-1">
285
+ <button
286
+ className="p-1.5 hover:bg-white/10 rounded-md text-gray-400 transition-colors"
287
+ onClick={() => handleCreateItem(false)}
288
+ title="New File"
289
+ >
290
+ <Plus size={14} />
291
+ </button>
292
+ <button
293
+ className="p-1.5 hover:bg-white/10 rounded-md text-gray-400 transition-colors"
294
+ onClick={() => handleCreateItem(true)}
295
+ title="New Folder"
296
+ >
297
+ <FolderPlus size={14} />
298
+ </button>
299
+ </div>
300
+ </div>
301
+
302
+ <div className="flex-1 overflow-y-auto py-2 custom-scrollbar">
303
+ {tree.map(node => (
304
+ <FileTreeNode
305
+ key={node.path}
306
+ node={node}
307
+ selectedPath={selectedPath}
308
+ onSelect={loadFileContent}
309
+ onRename={handleRenameItem}
310
+ onDelete={handleDeleteItem}
311
+ expandedFolders={expandedFolders}
312
+ toggleFolder={toggleFolder}
313
+ />
314
+ ))}
315
+ </div>
316
+ </div>
317
+
318
+ {/* Editor Container */}
319
+ <div className="flex-1 flex flex-col bg-[#1e1e1e] rounded-2xl border border-white/10 overflow-hidden shadow-2xl relative">
320
+ <div className="bg-black/20 border-b border-white/5 px-4 py-2 flex items-center justify-between">
321
+ <div className="flex items-center gap-2">
322
+ <FileIcon size={14} className="text-gray-400" />
323
+ <span className="text-sm text-gray-300 font-mono">{selectedPath}</span>
324
+ {isEditing && <span className="w-2 h-2 rounded-full bg-primary ml-2"></span>}
325
+ </div>
326
+
327
+ {selectedPath.endsWith('.md') && (
328
+ <div className="flex items-center gap-1 bg-black/30 rounded-lg p-1 border border-white/5">
329
+ <button onClick={() => handleFormat('h1')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Heading 1"><Heading1 size={14} /></button>
330
+ <button onClick={() => handleFormat('h2')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Heading 2"><Heading2 size={14} /></button>
331
+ <div className="w-px h-4 bg-white/10 mx-1"></div>
332
+ <button onClick={() => handleFormat('bold')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Bold"><Bold size={14} /></button>
333
+ <button onClick={() => handleFormat('italic')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Italic"><Italic size={14} /></button>
334
+ <div className="w-px h-4 bg-white/10 mx-1"></div>
335
+ <button onClick={() => handleFormat('check')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Checklist"><CheckSquare size={14} /></button>
336
+ <button onClick={() => handleFormat('list')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Bulleted List"><List size={14} /></button>
337
+ <div className="w-px h-4 bg-white/10 mx-1"></div>
338
+ <button onClick={() => handleFormat('code')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Code Block"><Code size={14} /></button>
339
+ <button onClick={() => handleFormat('link')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Link"><Link size={14} /></button>
340
+ <button onClick={() => handleFormat('quote')} className="p-1.5 hover:bg-white/10 rounded text-gray-400 hover:text-white transition-colors" title="Quote"><Quote size={14} /></button>
341
+ </div>
342
+ )}
343
+ </div>
344
+
345
+ <div className="flex-1 relative">
346
+ {isLoading ? (
347
+ <div className="flex items-center justify-center h-full text-gray-500">
348
+ Loading editor...
349
+ </div>
350
+ ) : (
351
+ <Editor
352
+ height="100%"
353
+ defaultLanguage={selectedPath.endsWith('.md') ? 'markdown' : selectedPath.endsWith('.json') ? 'json' : 'javascript'}
354
+ theme="vs-dark"
355
+ value={content}
356
+ onMount={(editor) => { editorRef.current = editor }}
357
+ onChange={(val) => {
358
+ setContent(val || '')
359
+ setIsEditing(true)
360
+ }}
361
+ options={{
362
+ minimap: { enabled: false },
363
+ fontSize: 14,
364
+ wordWrap: 'on',
365
+ lineNumbers: 'on',
366
+ padding: { top: 16 },
367
+ fontFamily: "'Fira Code', 'JetBrains Mono', monospace",
368
+ }}
369
+ />
370
+ )}
371
+ </div>
372
+ </div>
373
+
374
+ </div>
375
+ </div>
376
+ </div>
377
+ )
378
+ }
379
+
380
+ export default SkillEditor
@@ -0,0 +1,219 @@
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { Plus, FileText, Upload, RefreshCw } from 'lucide-react'
3
+ import { useNavigate } from 'react-router-dom'
4
+ import { webApi } from '../api/web-bridge'
5
+
6
+ const Skills = () => {
7
+ const [skills, setSkills] = useState([])
8
+ const [newSkillName, setNewSkillName] = useState('')
9
+ const [isRefreshing, setIsRefreshing] = useState(false)
10
+ const fileInputRef = useRef(null)
11
+ const navigate = useNavigate()
12
+
13
+ const loadSkills = async () => {
14
+ try {
15
+ setIsRefreshing(true)
16
+ const list = await webApi.getSkills()
17
+ setSkills(list)
18
+ } catch (e) {
19
+ console.error(e)
20
+ } finally {
21
+ setTimeout(() => setIsRefreshing(false), 500)
22
+ }
23
+ }
24
+
25
+ useEffect(() => {
26
+ loadSkills()
27
+ const unsub = webApi.onSkillsUpdated(() => {
28
+ loadSkills()
29
+ })
30
+ return () => {
31
+ if (typeof unsub === 'function') unsub()
32
+ }
33
+ }, [])
34
+
35
+ const handleCreateNew = async () => {
36
+ if (!newSkillName.trim()) return
37
+ const name = newSkillName.trim().replace(/\s+/g, '-').toLowerCase()
38
+ try {
39
+ const template = `---
40
+ name: ${name}
41
+ description: Tulis deskripsi singkat tentang skill ini...
42
+ author: User
43
+ tags: []
44
+ ---
45
+
46
+ # Skill: ${name}
47
+
48
+ Tulis instruksi mendetail untuk AI di sini...
49
+ - Gunakan format checklist [ ] jika ada langkah-langkah.
50
+ - Tambahkan blok Critical Rules untuk batasan mutlak.
51
+ `
52
+ await webApi.saveSkillFile(name, 'SKILL.md', template)
53
+ setNewSkillName('')
54
+ navigate(`/skill-editor/${name}`)
55
+ } catch (e) {
56
+ console.error(e)
57
+ }
58
+ }
59
+
60
+ const handleFileSelected = async (e) => {
61
+ const file = e.target.files?.[0]
62
+ if (!file) return
63
+
64
+ try {
65
+ setIsRefreshing(true)
66
+ const result = await webApi.installSkill(file)
67
+ if (result.success) {
68
+ await loadSkills()
69
+ } else {
70
+ alert(result.error || 'Gagal menginstal skill package.')
71
+ }
72
+ } catch (err) {
73
+ console.error(err)
74
+ alert('Gagal mengunggah dan menginstal skill: ' + err.message)
75
+ } finally {
76
+ if (fileInputRef.current) fileInputRef.current.value = ''
77
+ setIsRefreshing(false)
78
+ }
79
+ }
80
+
81
+ return (
82
+ <div className="h-screen bg-base-300 text-base-content overflow-hidden relative font-['Poppins',sans-serif]">
83
+ {/* Hidden File Input for ZIP Upload */}
84
+ <input
85
+ type="file"
86
+ ref={fileInputRef}
87
+ onChange={handleFileSelected}
88
+ accept=".zip"
89
+ className="hidden"
90
+ />
91
+
92
+ {/* Background Ambience */}
93
+ <div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,oklch(var(--n))_0%,transparent_70%)] opacity-20 pointer-events-none" />
94
+ <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/stardust.png')] opacity-10 pointer-events-none" />
95
+
96
+ {/* Main Content Area */}
97
+ <div className="relative z-10 w-full h-full overflow-y-auto custom-scrollbar">
98
+ <div className="max-w-2xl mx-auto px-4 py-8 pb-32 space-y-8">
99
+ {/* Page Header */}
100
+ <div className="flex items-center justify-between">
101
+ <div className="flex items-center gap-4">
102
+ <button
103
+ type="button"
104
+ onClick={() => navigate('/')}
105
+ className="btn btn-ghost btn-sm btn-circle shrink-0"
106
+ style={{ WebkitAppRegion: 'no-drag' }}
107
+ title="Kembali"
108
+ >
109
+ <svg
110
+ xmlns="http://www.w3.org/2000/svg"
111
+ width="1.2em"
112
+ height="1.2em"
113
+ fill="none"
114
+ viewBox="0 0 24 24"
115
+ stroke="currentColor"
116
+ strokeWidth={2}
117
+ >
118
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
119
+ </svg>
120
+ </button>
121
+ <div>
122
+ <h1 className="text-2xl font-bold">Mark Skills</h1>
123
+ <p className="opacity-50 text-sm mt-1">
124
+ Daftar kemampuan kustom AI yang diinstal via Markdown.
125
+ </p>
126
+ </div>
127
+ </div>
128
+ <div className="flex items-center gap-2">
129
+ <button
130
+ className="btn btn-ghost btn-sm btn-circle"
131
+ onClick={loadSkills}
132
+ style={{ WebkitAppRegion: 'no-drag' }}
133
+ title="Refresh Skills"
134
+ >
135
+ <RefreshCw size={16} className={isRefreshing ? 'animate-spin' : ''} />
136
+ </button>
137
+ <button
138
+ className="btn btn-outline btn-success btn-sm gap-2"
139
+ onClick={() => fileInputRef.current?.click()}
140
+ style={{ WebkitAppRegion: 'no-drag' }}
141
+ >
142
+ <Upload size={14} /> Install Skill (.zip)
143
+ </button>
144
+ </div>
145
+ </div>
146
+
147
+ {/* Form Create New */}
148
+ <section className="space-y-5">
149
+ <div className="card bg-base-100/50 backdrop-blur-xl border border-base-content/10 shadow-xl">
150
+ <div className="card-body">
151
+ <h2 className="card-title text-base font-bold uppercase tracking-wider opacity-70">
152
+ Buat Skill Baru
153
+ </h2>
154
+ <div className="flex gap-2 mt-2">
155
+ <input
156
+ type="text"
157
+ placeholder="Nama skill (cth: copywriter)..."
158
+ className="input input-bordered w-full bg-base-200/50"
159
+ value={newSkillName}
160
+ onChange={(e) => setNewSkillName(e.target.value)}
161
+ onKeyDown={(e) => e.key === 'Enter' && handleCreateNew()}
162
+ />
163
+ <button className="btn btn-primary gap-2" onClick={handleCreateNew}>
164
+ <Plus size={16} /> Buat
165
+ </button>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </section>
170
+
171
+ {/* Grid List */}
172
+ <section className="space-y-5">
173
+ <h2 className="text-base font-bold uppercase tracking-wider opacity-70">
174
+ Installed Skills
175
+ </h2>
176
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
177
+ {skills.map((skillObj) => {
178
+ const targetKey = skillObj.folderName || skillObj.name
179
+ return (
180
+ <div
181
+ key={targetKey}
182
+ className="card bg-base-100/50 backdrop-blur-xl border border-base-content/10 shadow-xl hover:border-primary/30 transition-all cursor-pointer group"
183
+ onClick={() => navigate(`/skill-editor/${targetKey}`)}
184
+ >
185
+ <div className="card-body p-5">
186
+ <div className="flex items-start gap-4">
187
+ <div className="p-3 bg-base-200 rounded-xl group-hover:text-primary transition-colors">
188
+ <FileText size={24} />
189
+ </div>
190
+ <div className="flex-1 overflow-hidden">
191
+ <h3 className="font-semibold text-lg truncate group-hover:text-primary transition-colors">
192
+ {skillObj.name}
193
+ </h3>
194
+ <p className="text-xs opacity-70 mt-1 line-clamp-2">
195
+ {skillObj.description}
196
+ </p>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ )
202
+ })}
203
+
204
+ {skills.length === 0 && (
205
+ <div className="col-span-full flex flex-col items-center justify-center p-12 text-base-content/50 border border-dashed border-base-content/10 rounded-2xl bg-base-100/20">
206
+ <FileText size={48} className="opacity-20 mb-4" />
207
+ <p className="text-lg font-medium">Belum ada skill.</p>
208
+ <p className="text-sm opacity-60">Install dari file .zip atau buat baru di atas!</p>
209
+ </div>
210
+ )}
211
+ </div>
212
+ </section>
213
+ </div>
214
+ </div>
215
+ </div>
216
+ )
217
+ }
218
+
219
+ export default Skills