@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,90 @@
1
+ import { exec } from 'child_process'
2
+ import util from 'util'
3
+ import fs from 'fs'
4
+
5
+ const execPromise = util.promisify(exec)
6
+
7
+ function validateRepoCwd(cwd) {
8
+ const target = cwd || process.cwd()
9
+ if (!fs.existsSync(target)) {
10
+ return { valid: false, error: `Direktori '${target}' tidak ditemukan.` }
11
+ }
12
+ return { valid: true, target }
13
+ }
14
+
15
+ function formatGitError(err, target) {
16
+ const msg = err?.message || String(err)
17
+ if (msg.includes('not a git repository')) {
18
+ return `Folder '${target}' bukan repositori Git.`
19
+ }
20
+ return msg
21
+ }
22
+
23
+ /**
24
+ * Mendapatkan status berkas repositori git (git status --short)
25
+ */
26
+ export async function getGitStatus(cwd) {
27
+ const check = validateRepoCwd(cwd)
28
+ if (!check.valid) return { success: false, error: check.error }
29
+ try {
30
+ const { stdout } = await execPromise('git status --short', { cwd: check.target })
31
+ return {
32
+ success: true,
33
+ status: stdout.trim() || 'Working tree clean (tidak ada perubahan berkas).'
34
+ }
35
+ } catch (err) {
36
+ return { success: false, error: formatGitError(err, check.target) }
37
+ }
38
+ }
39
+ /**
40
+ * Mendapatkan perubahan baris kode (git diff)
41
+ */
42
+ export async function getGitDiff(cwd, filePath = '') {
43
+ const check = validateRepoCwd(cwd)
44
+ if (!check.valid) return { success: false, error: check.error }
45
+ try {
46
+ const cleanPath = filePath ? ` -- "${filePath.trim()}"` : ''
47
+ const { stdout } = await execPromise(`git diff${cleanPath}`, { cwd: check.target })
48
+ return {
49
+ success: true,
50
+ diff: stdout.trim() || 'Tidak ada perbedaan baris kode yang belum di-commit.'
51
+ }
52
+ } catch (err) {
53
+ return { success: false, error: formatGitError(err, check.target) }
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Membuat checkpoint commit git otomatis
59
+ */
60
+ export async function gitCommit(cwd, message = 'Mark Agent Checkpoint') {
61
+ const check = validateRepoCwd(cwd)
62
+ if (!check.valid) return { success: false, error: check.error }
63
+ try {
64
+ await execPromise('git add -A', { cwd: check.target })
65
+ const safeMsg = message.replace(/"/g, '\\"')
66
+ const { stdout } = await execPromise(`git commit -m "${safeMsg}"`, { cwd: check.target })
67
+ return { success: true, message: stdout.trim() }
68
+ } catch (err) {
69
+ return { success: false, error: formatGitError(err, check.target) }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Me-rollback perubahan file ke HEAD
75
+ */
76
+ export async function gitRevert(cwd, filePath = '') {
77
+ const check = validateRepoCwd(cwd)
78
+ if (!check.valid) return { success: false, error: check.error }
79
+ try {
80
+ if (filePath && filePath.trim()) {
81
+ await execPromise(`git checkout -- "${filePath.trim()}"`, { cwd: check.target })
82
+ return { success: true, message: `Berhasil me-rollback perubahan pada berkas ${filePath}.` }
83
+ } else {
84
+ await execPromise('git reset --hard HEAD', { cwd: check.target })
85
+ return { success: true, message: 'Berhasil me-rollback seluruh repositori ke HEAD.' }
86
+ }
87
+ } catch (err) {
88
+ return { success: false, error: formatGitError(err, check.target) }
89
+ }
90
+ }
@@ -0,0 +1,69 @@
1
+ import { google } from 'googleapis'
2
+ import { getAuthClient } from './google-service.js'
3
+
4
+ /**
5
+ * Helper to initialize the Calendar API.
6
+ */
7
+ export async function getCalendarApi(clientId, clientSecret) {
8
+ const auth = await getAuthClient(clientId, clientSecret)
9
+ if (!auth) throw new Error('Not connected to Google Workspace.')
10
+ return google.calendar({ version: 'v3', auth })
11
+ }
12
+
13
+ /**
14
+ * gcalendar-list: Get upcoming events.
15
+ */
16
+ export async function listEvents(clientId, clientSecret, maxResults = 10, timeMin = new Date().toISOString()) {
17
+ const calendar = await getCalendarApi(clientId, clientSecret)
18
+ const res = await calendar.events.list({
19
+ calendarId: 'primary',
20
+ timeMin,
21
+ maxResults,
22
+ singleEvents: true,
23
+ orderBy: 'startTime',
24
+ })
25
+ return res.data.items.map(event => ({
26
+ id: event.id,
27
+ summary: event.summary,
28
+ description: event.description,
29
+ start: event.start.dateTime || event.start.date,
30
+ end: event.end.dateTime || event.end.date,
31
+ link: event.htmlLink
32
+ }))
33
+ }
34
+
35
+ /**
36
+ * gcalendar-create: Create a new event.
37
+ */
38
+ export async function createEvent(clientId, clientSecret, summary, description, startTime, endTime) {
39
+ const calendar = await getCalendarApi(clientId, clientSecret)
40
+ const event = {
41
+ summary,
42
+ description,
43
+ start: {
44
+ dateTime: new Date(startTime).toISOString(),
45
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
46
+ },
47
+ end: {
48
+ dateTime: new Date(endTime).toISOString(),
49
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
50
+ },
51
+ }
52
+ const res = await calendar.events.insert({
53
+ calendarId: 'primary',
54
+ resource: event,
55
+ })
56
+ return res.data
57
+ }
58
+
59
+ /**
60
+ * gcalendar-delete: Delete an event.
61
+ */
62
+ export async function deleteEvent(clientId, clientSecret, eventId) {
63
+ const calendar = await getCalendarApi(clientId, clientSecret)
64
+ await calendar.events.delete({
65
+ calendarId: 'primary',
66
+ eventId,
67
+ })
68
+ return { success: true, message: 'Event deleted successfully.' }
69
+ }
@@ -0,0 +1,263 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { google } from 'googleapis'
4
+ import { getAuthClient } from './google-service.js'
5
+
6
+ /**
7
+ * Helper to initialize the Drive API.
8
+ */
9
+ export async function getDriveApi(clientId, clientSecret) {
10
+ const auth = await getAuthClient(clientId, clientSecret)
11
+ if (!auth) throw new Error('Not connected to Google Workspace.')
12
+ return google.drive({ version: 'v3', auth })
13
+ }
14
+
15
+ /**
16
+ * gdrive-search: Searches files by name or mimeType.
17
+ */
18
+ export async function searchFiles(clientId, clientSecret, query, maxResults = 10) {
19
+ const drive = await getDriveApi(clientId, clientSecret)
20
+ // Build a simple query, or use the raw query if advanced
21
+ // Let's assume the query is a simple string for name matching
22
+ // Or the agent can pass standard Google Drive 'q' syntax.
23
+ const q = query.includes(':') ? query : `name contains '${query}' and trashed = false`
24
+
25
+ const res = await drive.files.list({
26
+ q,
27
+ pageSize: maxResults,
28
+ fields: 'nextPageToken, files(id, name, mimeType, modifiedTime)'
29
+ })
30
+ return res.data.files
31
+ }
32
+
33
+ /**
34
+ * gdrive-list: List recent files or files in a folder.
35
+ */
36
+ export async function listFiles(clientId, clientSecret, folderId = null, maxResults = 10) {
37
+ const drive = await getDriveApi(clientId, clientSecret)
38
+ const q = folderId ? `'${folderId}' in parents and trashed = false` : `trashed = false`
39
+ const res = await drive.files.list({
40
+ q,
41
+ pageSize: maxResults,
42
+ orderBy: 'modifiedTime desc',
43
+ fields: 'nextPageToken, files(id, name, mimeType, modifiedTime)'
44
+ })
45
+ return res.data.files
46
+ }
47
+
48
+ /**
49
+ * gdrive-read: Read text content from a Google Doc, Sheet, or raw text file.
50
+ */
51
+ export async function readFile(clientId, clientSecret, fileId) {
52
+ const drive = await getDriveApi(clientId, clientSecret)
53
+
54
+ // First get file metadata to know its mimeType
55
+ const fileMeta = await drive.files.get({ fileId, fields: 'name, mimeType' })
56
+ const mimeType = fileMeta.data.mimeType
57
+
58
+ try {
59
+ if (mimeType === 'application/vnd.google-apps.document') {
60
+ // Google Docs -> export as text
61
+ const res = await drive.files.export({
62
+ fileId,
63
+ mimeType: 'text/plain'
64
+ })
65
+ return res.data
66
+ } else if (mimeType === 'application/vnd.google-apps.spreadsheet') {
67
+ // Google Sheets -> export as CSV
68
+ const res = await drive.files.export({
69
+ fileId,
70
+ mimeType: 'text/csv'
71
+ })
72
+ return res.data
73
+ } else {
74
+ // Standard files (txt, etc) -> download directly
75
+ const res = await drive.files.get(
76
+ {
77
+ fileId,
78
+ alt: 'media'
79
+ },
80
+ { responseType: 'text' }
81
+ )
82
+ return res.data
83
+ }
84
+ } catch (error) {
85
+ throw new Error(`Failed to read file: ${error.message}`)
86
+ }
87
+ }
88
+
89
+ /**
90
+ * gdrive-upload: Upload local file to Google Drive.
91
+ */
92
+ export async function uploadFile(clientId, clientSecret, filePath) {
93
+ if (!fs.existsSync(filePath)) {
94
+ throw new Error(`Berkas tidak ditemukan: ${filePath}`)
95
+ }
96
+ const drive = await getDriveApi(clientId, clientSecret)
97
+ const name = path.basename(filePath)
98
+ const ext = path.extname(filePath).toLowerCase()
99
+ const mimeMap = {
100
+ '.pdf': 'application/pdf',
101
+ '.txt': 'text/plain',
102
+ '.md': 'text/markdown',
103
+ '.json': 'application/json',
104
+ '.csv': 'text/csv',
105
+ '.html': 'text/html',
106
+ '.htm': 'text/html',
107
+ '.png': 'image/png',
108
+ '.jpg': 'image/jpeg',
109
+ '.jpeg': 'image/jpeg',
110
+ '.webp': 'image/webp',
111
+ '.gif': 'image/gif',
112
+ '.svg': 'image/svg+xml',
113
+ '.zip': 'application/zip',
114
+ '.tar': 'application/x-tar',
115
+ '.gz': 'application/gzip',
116
+ '.doc': 'application/msword',
117
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
118
+ '.xls': 'application/vnd.ms-excel',
119
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
120
+ '.ppt': 'application/vnd.ms-powerpoint',
121
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
122
+ '.mp3': 'audio/mpeg',
123
+ '.wav': 'audio/wav',
124
+ '.mp4': 'video/mp4',
125
+ '.mkv': 'video/x-matroska'
126
+ }
127
+ const mimeType = mimeMap[ext] || 'application/octet-stream'
128
+
129
+ const res = await drive.files.create({
130
+ requestBody: { name, mimeType },
131
+ media: { mimeType, body: fs.createReadStream(filePath) },
132
+ fields: 'id, name, mimeType, webViewLink, size'
133
+ })
134
+ return res.data
135
+ }
136
+
137
+ /**
138
+ * gdrive-create: Create an empty Google Doc or Sheet.
139
+ */
140
+ export async function createFile(clientId, clientSecret, name, type = 'doc') {
141
+ const drive = await getDriveApi(clientId, clientSecret)
142
+ const mimeMap = {
143
+ doc: 'application/vnd.google-apps.document',
144
+ sheet: 'application/vnd.google-apps.spreadsheet',
145
+ folder: 'application/vnd.google-apps.folder'
146
+ }
147
+ const mimeType = mimeMap[type] || mimeMap['doc']
148
+
149
+ const res = await drive.files.create({
150
+ requestBody: { name, mimeType },
151
+ fields: 'id, name, webViewLink'
152
+ })
153
+ return res.data
154
+ }
155
+
156
+ /**
157
+ * gdrive-move: Move a file to a specific folder.
158
+ */
159
+ export async function moveFile(clientId, clientSecret, fileId, folderId) {
160
+ const drive = await getDriveApi(clientId, clientSecret)
161
+
162
+ // Get current parents
163
+ const file = await drive.files.get({ fileId, fields: 'parents' })
164
+ const previousParents = file.data.parents ? file.data.parents.join(',') : ''
165
+
166
+ // Move
167
+ const res = await drive.files.update({
168
+ fileId,
169
+ addParents: folderId,
170
+ removeParents: previousParents,
171
+ fields: 'id, parents'
172
+ })
173
+ return res.data
174
+ }
175
+
176
+ /**
177
+ * gdrive-copy: Copy a file.
178
+ */
179
+ export async function copyFile(clientId, clientSecret, fileId, newName) {
180
+ const drive = await getDriveApi(clientId, clientSecret)
181
+ const res = await drive.files.copy({
182
+ fileId,
183
+ requestBody: { name: newName },
184
+ fields: 'id, name, webViewLink'
185
+ })
186
+ return res.data
187
+ }
188
+
189
+ /**
190
+ * gdrive-info: Get Google Drive storage info.
191
+ */
192
+ export async function getDriveInfo(clientId, clientSecret) {
193
+ const drive = await getDriveApi(clientId, clientSecret)
194
+ const res = await drive.about.get({
195
+ fields: 'storageQuota, user'
196
+ })
197
+
198
+ const quota = res.data.storageQuota
199
+ const user = res.data.user
200
+
201
+ if (!quota) return { error: 'Storage quota not available.' }
202
+
203
+ const limit = parseInt(quota.limit, 10) || 0
204
+ const usage = parseInt(quota.usage, 10) || 0
205
+ const usageInDrive = parseInt(quota.usageInDrive, 10) || 0
206
+ const usageInDriveTrash = parseInt(quota.usageInDriveTrash, 10) || 0
207
+
208
+ const formatBytes = (bytes) => {
209
+ if (bytes === 0) return '0 B'
210
+ const k = 1024
211
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
212
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
213
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
214
+ }
215
+
216
+ return {
217
+ user: user.emailAddress,
218
+ totalStorage: formatBytes(limit),
219
+ usedStorage: formatBytes(usage),
220
+ freeStorage: formatBytes(limit - usage),
221
+ usagePercentage: limit > 0 ? ((usage / limit) * 100).toFixed(1) + '%' : 'N/A',
222
+ driveUsage: formatBytes(usageInDrive),
223
+ trashUsage: formatBytes(usageInDriveTrash)
224
+ }
225
+ }
226
+
227
+ /**
228
+ * gdrive-share: Share file or change file permission (e.g. public anyone reader, or specific email).
229
+ */
230
+ export async function shareFile(
231
+ clientId,
232
+ clientSecret,
233
+ fileId,
234
+ role = 'reader',
235
+ type = 'anyone',
236
+ emailAddress = null
237
+ ) {
238
+ const drive = await getDriveApi(clientId, clientSecret)
239
+ const permission = {
240
+ role,
241
+ type
242
+ }
243
+ if (emailAddress && (type === 'user' || type === 'group')) {
244
+ permission.emailAddress = emailAddress
245
+ }
246
+
247
+ const permRes = await drive.permissions.create({
248
+ fileId,
249
+ requestBody: permission,
250
+ fields: 'id, type, role, emailAddress'
251
+ })
252
+
253
+ const fileRes = await drive.files.get({
254
+ fileId,
255
+ fields: 'id, name, webViewLink'
256
+ })
257
+
258
+ return {
259
+ permission: permRes.data,
260
+ name: fileRes.data.name,
261
+ webViewLink: fileRes.data.webViewLink
262
+ }
263
+ }
@@ -0,0 +1,131 @@
1
+ import { google } from 'googleapis'
2
+ import { getAuthClient } from './google-service.js'
3
+
4
+ /**
5
+ * Helper to initialize the Gmail API.
6
+ */
7
+ export async function getGmailApi(clientId, clientSecret) {
8
+ const auth = await getAuthClient(clientId, clientSecret)
9
+ if (!auth) throw new Error('Not connected to Google Workspace.')
10
+ return google.gmail({ version: 'v1', auth })
11
+ }
12
+
13
+ /**
14
+ * gmail-search: Search emails.
15
+ */
16
+ export async function searchEmails(clientId, clientSecret, query = 'is:unread', maxResults = 10) {
17
+ const gmail = await getGmailApi(clientId, clientSecret)
18
+ const res = await gmail.users.messages.list({
19
+ userId: 'me',
20
+ q: query,
21
+ maxResults
22
+ })
23
+
24
+ if (!res.data.messages) return []
25
+
26
+ // Fetch details for each message
27
+ const messages = await Promise.all(res.data.messages.map(async (msg) => {
28
+ const detail = await gmail.users.messages.get({
29
+ userId: 'me',
30
+ id: msg.id,
31
+ format: 'metadata',
32
+ metadataHeaders: ['From', 'Subject', 'Date']
33
+ })
34
+
35
+ const headers = detail.data.payload.headers
36
+ return {
37
+ id: detail.data.id,
38
+ snippet: detail.data.snippet,
39
+ from: headers.find(h => h.name === 'From')?.value || 'Unknown',
40
+ subject: headers.find(h => h.name === 'Subject')?.value || 'No Subject',
41
+ date: headers.find(h => h.name === 'Date')?.value || ''
42
+ }
43
+ }))
44
+
45
+ return messages
46
+ }
47
+
48
+ /**
49
+ * gmail-read: Get full body of an email.
50
+ */
51
+ export async function readEmail(clientId, clientSecret, messageId) {
52
+ const gmail = await getGmailApi(clientId, clientSecret)
53
+ const res = await gmail.users.messages.get({
54
+ userId: 'me',
55
+ id: messageId,
56
+ format: 'full'
57
+ })
58
+
59
+ // Gmail bodies are base64url encoded. We need to decode it.
60
+ const getBody = (payload) => {
61
+ if (payload.body && payload.body.data) {
62
+ return Buffer.from(payload.body.data, 'base64').toString('utf-8')
63
+ }
64
+ if (payload.parts) {
65
+ for (const part of payload.parts) {
66
+ if (part.mimeType === 'text/plain') {
67
+ return Buffer.from(part.body.data, 'base64').toString('utf-8')
68
+ }
69
+ }
70
+ }
71
+ return 'No plain text body found.'
72
+ }
73
+
74
+ const body = getBody(res.data.payload)
75
+ const headers = res.data.payload.headers
76
+
77
+ return {
78
+ id: res.data.id,
79
+ from: headers.find(h => h.name === 'From')?.value,
80
+ subject: headers.find(h => h.name === 'Subject')?.value,
81
+ date: headers.find(h => h.name === 'Date')?.value,
82
+ body
83
+ }
84
+ }
85
+
86
+ /**
87
+ * gmail-send: Send an email.
88
+ */
89
+ export async function sendEmail(clientId, clientSecret, to, subject, bodyText) {
90
+ const gmail = await getGmailApi(clientId, clientSecret)
91
+
92
+ // Construct raw email according to RFC 2822
93
+ const rawEmail = [
94
+ `To: ${to}`,
95
+ `Subject: ${subject}`,
96
+ 'Content-Type: text/plain; charset="UTF-8"',
97
+ '',
98
+ bodyText
99
+ ].join('\n')
100
+
101
+ // Base64url encode
102
+ const encodedEmail = Buffer.from(rawEmail)
103
+ .toString('base64')
104
+ .replace(/\+/g, '-')
105
+ .replace(/\//g, '_')
106
+ .replace(/=+$/, '')
107
+
108
+ const res = await gmail.users.messages.send({
109
+ userId: 'me',
110
+ requestBody: {
111
+ raw: encodedEmail
112
+ }
113
+ })
114
+ return res.data
115
+ }
116
+
117
+ /**
118
+ * gmail-mark-read: Mark an email as read.
119
+ */
120
+ export async function markAsRead(clientId, clientSecret, messageId) {
121
+ const gmail = await getGmailApi(clientId, clientSecret)
122
+ await gmail.users.messages.modify({
123
+ userId: 'me',
124
+ id: messageId,
125
+ requestBody: {
126
+ removeLabelIds: ['UNREAD']
127
+ }
128
+ })
129
+ return { success: true, messageId }
130
+ }
131
+