@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.
- package/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- 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
|
+
|