zilmate 1.9.9 → 1.10.2
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/.agents/skills/ad-creative/SKILL.md +362 -0
- package/.agents/skills/ad-creative/evals/evals.json +90 -0
- package/.agents/skills/ad-creative/references/generative-tools.md +637 -0
- package/.agents/skills/ad-creative/references/platform-specs.md +213 -0
- package/.agents/skills/ai-seo/SKILL.md +489 -0
- package/.agents/skills/ai-seo/evals/evals.json +90 -0
- package/.agents/skills/ai-seo/references/content-patterns.md +285 -0
- package/.agents/skills/ai-seo/references/content-types.md +71 -0
- package/.agents/skills/ai-seo/references/okf.md +104 -0
- package/.agents/skills/ai-seo/references/platform-ranking-factors.md +154 -0
- package/.agents/skills/clerk-backend-api/SKILL.md +426 -0
- package/.agents/skills/clerk-backend-api/evals/evals.json +87 -0
- package/.agents/skills/clerk-backend-api/scripts/api-specs-context.sh +30 -0
- package/.agents/skills/clerk-backend-api/scripts/execute-request.sh +88 -0
- package/.agents/skills/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
- package/.agents/skills/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
- package/.agents/skills/clerk-backend-api/scripts/extract-tags.js +14 -0
- package/.agents/skills/copy-editing/SKILL.md +457 -0
- package/.agents/skills/copy-editing/evals/evals.json +89 -0
- package/.agents/skills/copy-editing/references/checklist.md +66 -0
- package/.agents/skills/copy-editing/references/content-refresh.md +38 -0
- package/.agents/skills/copy-editing/references/plain-english-alternatives.md +394 -0
- package/.agents/skills/hyperframes/SKILL.md +163 -0
- package/.agents/skills/hyperframes-cli/SKILL.md +109 -0
- package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
- package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +51 -0
- package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
- package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
- package/.agents/skills/hyperframes-cli/references/preview-render.md +109 -0
- package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
- package/.agents/skills/hyperframes-media/SKILL.md +97 -0
- package/.agents/skills/hyperframes-media/assets/sfx/CREDITS.md +35 -0
- package/.agents/skills/hyperframes-media/assets/sfx/manifest.json +97 -0
- package/.agents/skills/hyperframes-media/references/bgm.md +72 -0
- package/.agents/skills/hyperframes-media/references/captions/authoring.md +159 -0
- package/.agents/skills/hyperframes-media/references/captions/motion.md +87 -0
- package/.agents/skills/hyperframes-media/references/captions/transcript-handling.md +97 -0
- package/.agents/skills/hyperframes-media/references/remove-background.md +143 -0
- package/.agents/skills/hyperframes-media/references/requirements.md +29 -0
- package/.agents/skills/hyperframes-media/references/sfx.md +42 -0
- package/.agents/skills/hyperframes-media/references/transcribe.md +52 -0
- package/.agents/skills/hyperframes-media/references/tts-to-captions.md +24 -0
- package/.agents/skills/hyperframes-media/references/tts.md +135 -0
- package/.agents/skills/hyperframes-media/scripts/audio.mjs +282 -0
- package/.agents/skills/hyperframes-media/scripts/heygen-tts.mjs +121 -0
- package/.agents/skills/hyperframes-media/scripts/lib/bgm.mjs +235 -0
- package/.agents/skills/hyperframes-media/scripts/lib/heygen.mjs +131 -0
- package/.agents/skills/hyperframes-media/scripts/lib/sfx.mjs +128 -0
- package/.agents/skills/hyperframes-media/scripts/lib/tts.mjs +220 -0
- package/.agents/skills/hyperframes-media/scripts/lyria-recipe.py +128 -0
- package/.agents/skills/hyperframes-media/scripts/wait-bgm.mjs +167 -0
- package/.agents/skills/hyperframes-registry/SKILL.md +101 -0
- package/.agents/skills/hyperframes-registry/examples/add-block.md +51 -0
- package/.agents/skills/hyperframes-registry/examples/add-component.md +73 -0
- package/.agents/skills/hyperframes-registry/references/contributing.md +166 -0
- package/.agents/skills/hyperframes-registry/references/demo-html-pattern.md +54 -0
- package/.agents/skills/hyperframes-registry/references/discovery.md +215 -0
- package/.agents/skills/hyperframes-registry/references/install-locations.md +45 -0
- package/.agents/skills/hyperframes-registry/references/templates.md +417 -0
- package/.agents/skills/hyperframes-registry/references/wiring-blocks.md +61 -0
- package/.agents/skills/hyperframes-registry/references/wiring-components.md +39 -0
- package/.agents/skills/marketing-psychology/SKILL.md +455 -0
- package/.agents/skills/marketing-psychology/evals/evals.json +88 -0
- package/.agents/skills/mintlify/SKILL.md +328 -0
- package/.agents/skills/product-launch-video/SKILL.md +206 -0
- package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
- package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
- package/.agents/skills/product-launch-video/references/story-design.md +373 -0
- package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
- package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/product-launch-video/scripts/audio.mjs +253 -0
- package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
- package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
- package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
- package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
- package/.agents/skills/product-launch-video/scripts/transitions.mjs +320 -0
- package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +74 -0
- package/.agents/skills/prompt-engineering-patterns/SKILL.md +144 -0
- package/.agents/skills/prompt-engineering-patterns/assets/few-shot-examples.json +106 -0
- package/.agents/skills/prompt-engineering-patterns/assets/prompt-template-library.md +264 -0
- package/.agents/skills/prompt-engineering-patterns/references/chain-of-thought.md +412 -0
- package/.agents/skills/prompt-engineering-patterns/references/details.md +338 -0
- package/.agents/skills/prompt-engineering-patterns/references/few-shot-learning.md +386 -0
- package/.agents/skills/prompt-engineering-patterns/references/prompt-optimization.md +428 -0
- package/.agents/skills/prompt-engineering-patterns/references/prompt-templates.md +484 -0
- package/.agents/skills/prompt-engineering-patterns/references/system-prompts.md +195 -0
- package/.agents/skills/prompt-engineering-patterns/scripts/optimize-prompt.py +279 -0
- package/.agents/skills/remotion-to-hyperframes/SKILL.md +127 -0
- package/.agents/skills/remotion-to-hyperframes/assets/.gitkeep +0 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/run.sh +249 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/README.md +41 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json +26 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html +61 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/package.json +14 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/remotion.config.ts +13 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/Root.tsx +13 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/TitleCard.tsx +34 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/index.ts +4 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/tsconfig.json +15 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md +54 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/expected.json +29 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/index.html +118 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/package.json +14 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/remotion.config.ts +13 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/src/MultiScene.tsx +105 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/src/Root.tsx +13 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/src/index.ts +4 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/remotion-src/tsconfig.json +15 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/setup.sh +34 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/README.md +85 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/expected.json +41 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/index.html +269 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/package.json +15 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/remotion.config.ts +13 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/Root.tsx +27 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/Stargazed.tsx +37 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/components/AnimatedNumber.tsx +23 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/components/StatCard.tsx +59 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/components/UnderlinedText.tsx +47 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/index.ts +4 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/scenes/OutroScene.tsx +18 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/scenes/StatsScene.tsx +34 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/src/scenes/TitleScene.tsx +55 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/remotion-src/tsconfig.json +15 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/README.md +51 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/01-use-state.tsx +29 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/02-use-effect-deps.tsx +29 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/03-async-metadata.tsx +39 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/04-third-party-react.tsx +30 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/05-lambda-config.tsx +38 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/06-warnings-only.tsx +36 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/07-custom-hook.tsx +28 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/cases/08-mixed.tsx +41 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/expected.json +85 -0
- package/.agents/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/validate.sh +110 -0
- package/.agents/skills/remotion-to-hyperframes/references/api-map.md +142 -0
- package/.agents/skills/remotion-to-hyperframes/references/escape-hatch.md +115 -0
- package/.agents/skills/remotion-to-hyperframes/references/eval.md +140 -0
- package/.agents/skills/remotion-to-hyperframes/references/fonts.md +112 -0
- package/.agents/skills/remotion-to-hyperframes/references/limitations.md +136 -0
- package/.agents/skills/remotion-to-hyperframes/references/lottie.md +121 -0
- package/.agents/skills/remotion-to-hyperframes/references/media.md +149 -0
- package/.agents/skills/remotion-to-hyperframes/references/parameters.md +167 -0
- package/.agents/skills/remotion-to-hyperframes/references/sequencing.md +195 -0
- package/.agents/skills/remotion-to-hyperframes/references/timing.md +165 -0
- package/.agents/skills/remotion-to-hyperframes/references/transitions.md +114 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/.gitkeep +0 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/frame_strip.sh +107 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/lint_source.py +358 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/render_diff.sh +103 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/tests/fixtures/blocker.tsx +50 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/tests/fixtures/clean.tsx +46 -0
- package/.agents/skills/remotion-to-hyperframes/scripts/tests/smoke.sh +90 -0
- package/.agents/skills/seo-audit/SKILL.md +497 -0
- package/.agents/skills/seo-audit/evals/evals.json +136 -0
- package/.agents/skills/seo-audit/references/ai-writing-detection.md +200 -0
- package/.agents/skills/seo-audit/references/international-seo.md +230 -0
- package/README.md +4 -2
- package/SDK.md +64 -1
- package/dist/agents/coding.agent.d.ts +558 -0
- package/dist/agents/coding.agent.d.ts.map +1 -1
- package/dist/agents/coding.agent.js +8 -0
- package/dist/agents/coding.agent.js.map +1 -1
- package/dist/agents/developer-helper.agent.d.ts +712 -0
- package/dist/agents/developer-helper.agent.d.ts.map +1 -1
- package/dist/agents/developer-helper.agent.js +6 -0
- package/dist/agents/developer-helper.agent.js.map +1 -1
- package/dist/agents/docs-research.agent.d.ts +8 -0
- package/dist/agents/docs-research.agent.d.ts.map +1 -1
- package/dist/agents/finance.agent.d.ts +8 -0
- package/dist/agents/finance.agent.d.ts.map +1 -1
- package/dist/agents/manager.d.ts +837 -2
- package/dist/agents/manager.d.ts.map +1 -1
- package/dist/agents/manager.js +28 -0
- package/dist/agents/manager.js.map +1 -1
- package/dist/agents/security.agent.d.ts +165 -3
- package/dist/agents/security.agent.d.ts.map +1 -1
- package/dist/agents/security.agent.js +3 -0
- package/dist/agents/security.agent.js.map +1 -1
- package/dist/agents/swarm/main.js +1 -1
- package/dist/agents/swarm/main.js.map +1 -1
- package/dist/agents/swarm/registry.d.ts.map +1 -1
- package/dist/agents/swarm/registry.js +232 -26
- package/dist/agents/swarm/registry.js.map +1 -1
- package/dist/cli/doctor.d.ts +14 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +91 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/menu.d.ts.map +1 -1
- package/dist/cli/menu.js +102 -0
- package/dist/cli/menu.js.map +1 -1
- package/dist/cli/setup.d.ts +10 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +178 -0
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/swarm.d.ts +1 -0
- package/dist/cli/swarm.d.ts.map +1 -1
- package/dist/cli/swarm.js +47 -8
- package/dist/cli/swarm.js.map +1 -1
- package/dist/cli/theme.d.ts +0 -24
- package/dist/cli/theme.d.ts.map +1 -1
- package/dist/cli/theme.js +6 -2
- package/dist/cli/theme.js.map +1 -1
- package/dist/config/env.d.ts +10 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +38 -17
- package/dist/config/env.js.map +1 -1
- package/dist/index.js +96 -5
- package/dist/index.js.map +1 -1
- package/dist/jobs/trigger-orchestrator.d.ts +5 -1
- package/dist/jobs/trigger-orchestrator.d.ts.map +1 -1
- package/dist/jobs/trigger-orchestrator.js +29 -9
- package/dist/jobs/trigger-orchestrator.js.map +1 -1
- package/dist/jobs/trigger-router.d.ts.map +1 -1
- package/dist/jobs/trigger-router.js +3 -2
- package/dist/jobs/trigger-router.js.map +1 -1
- package/dist/memory/corporate-wiki.d.ts +15 -0
- package/dist/memory/corporate-wiki.d.ts.map +1 -0
- package/dist/memory/corporate-wiki.js +152 -0
- package/dist/memory/corporate-wiki.js.map +1 -0
- package/dist/memory/scratchpad.d.ts +2 -0
- package/dist/memory/scratchpad.d.ts.map +1 -1
- package/dist/memory/scratchpad.js +27 -0
- package/dist/memory/scratchpad.js.map +1 -1
- package/dist/observability/doctor.d.ts.map +1 -1
- package/dist/observability/doctor.js +12 -4
- package/dist/observability/doctor.js.map +1 -1
- package/dist/observability/traces.d.ts +72 -0
- package/dist/observability/traces.d.ts.map +1 -0
- package/dist/observability/traces.js +1134 -0
- package/dist/observability/traces.js.map +1 -0
- package/dist/runtime/swarm.d.ts +1 -1
- package/dist/runtime/swarm.d.ts.map +1 -1
- package/dist/runtime/swarm.js +54 -25
- package/dist/runtime/swarm.js.map +1 -1
- package/dist/safety/approvals.d.ts +2 -2
- package/dist/safety/approvals.d.ts.map +1 -1
- package/dist/safety/approvals.js +47 -1
- package/dist/safety/approvals.js.map +1 -1
- package/dist/safety/redaction.d.ts.map +1 -1
- package/dist/safety/redaction.js +10 -0
- package/dist/safety/redaction.js.map +1 -1
- package/dist/tools/cloud.tool.d.ts +394 -0
- package/dist/tools/cloud.tool.d.ts.map +1 -0
- package/dist/tools/cloud.tool.js +784 -0
- package/dist/tools/cloud.tool.js.map +1 -0
- package/dist/tools/computer-use.tool.d.ts +2 -2
- package/dist/tools/corporate-wiki.tool.d.ts +22 -0
- package/dist/tools/corporate-wiki.tool.d.ts.map +1 -0
- package/dist/tools/corporate-wiki.tool.js +43 -0
- package/dist/tools/corporate-wiki.tool.js.map +1 -0
- package/dist/tools/devops.tool.d.ts +162 -0
- package/dist/tools/devops.tool.d.ts.map +1 -0
- package/dist/tools/devops.tool.js +377 -0
- package/dist/tools/devops.tool.js.map +1 -0
- package/dist/tools/multimedia.tool.d.ts +126 -0
- package/dist/tools/multimedia.tool.d.ts.map +1 -0
- package/dist/tools/multimedia.tool.js +533 -0
- package/dist/tools/multimedia.tool.js.map +1 -0
- package/dist/tools/osint.tool.d.ts +6 -6
- package/dist/tools/sandbox-dev.tool.d.ts +15 -0
- package/dist/tools/sandbox-dev.tool.d.ts.map +1 -0
- package/dist/tools/sandbox-dev.tool.js +146 -0
- package/dist/tools/sandbox-dev.tool.js.map +1 -0
- package/dist/tools/scratchpad.tool.d.ts +8 -0
- package/dist/tools/scratchpad.tool.d.ts.map +1 -1
- package/dist/tools/scratchpad.tool.js +40 -1
- package/dist/tools/scratchpad.tool.js.map +1 -1
- package/dist/tools/setup-assistant.tool.d.ts +1 -1
- package/dist/tools/swarm-ops.tool.d.ts +14 -0
- package/dist/tools/swarm-ops.tool.d.ts.map +1 -1
- package/dist/tools/swarm-ops.tool.js +47 -0
- package/dist/tools/swarm-ops.tool.js.map +1 -1
- package/dist/tools/sysops.tool.d.ts +157 -0
- package/dist/tools/sysops.tool.d.ts.map +1 -0
- package/dist/tools/sysops.tool.js +643 -0
- package/dist/tools/sysops.tool.js.map +1 -0
- package/dist/voice/deepgram.d.ts.map +1 -1
- package/dist/voice/deepgram.js +30 -2
- package/dist/voice/deepgram.js.map +1 -1
- package/package.json +8 -1
- package/scripts/release-github.mjs +23 -16
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync, createReadStream } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { tool } from 'ai';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { emitProgress } from '../runtime/progress.js';
|
|
9
|
+
import { resolveWorkspaceRoot } from '../workspace/paths.js';
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
async function commandExists(command) {
|
|
12
|
+
const probe = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
13
|
+
try {
|
|
14
|
+
await execFileAsync(probe, [command], { windowsHide: true, timeout: 5000 });
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function detectContentType(filePath) {
|
|
22
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
23
|
+
switch (ext) {
|
|
24
|
+
case '.png': return 'image/png';
|
|
25
|
+
case '.webp': return 'image/webp';
|
|
26
|
+
case '.jpg':
|
|
27
|
+
case '.jpeg': return 'image/jpeg';
|
|
28
|
+
case '.gif': return 'image/gif';
|
|
29
|
+
case '.svg': return 'image/svg+xml';
|
|
30
|
+
case '.mp3': return 'audio/mpeg';
|
|
31
|
+
case '.wav': return 'audio/wav';
|
|
32
|
+
case '.mp4': return 'video/mp4';
|
|
33
|
+
case '.json': return 'application/json';
|
|
34
|
+
case '.html': return 'text/html';
|
|
35
|
+
case '.css': return 'text/css';
|
|
36
|
+
case '.js':
|
|
37
|
+
case '.mjs': return 'text/javascript';
|
|
38
|
+
case '.pdf': return 'application/pdf';
|
|
39
|
+
case '.txt': return 'text/plain';
|
|
40
|
+
case '.xml': return 'application/xml';
|
|
41
|
+
case '.zip': return 'application/zip';
|
|
42
|
+
default: return 'application/octet-stream';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const cloudTools = {
|
|
46
|
+
listCloudBucket: tool({
|
|
47
|
+
description: 'List, explore, and search files inside a remote cloud bucket/store (AWS S3, Google Cloud Storage, or Vercel Blob). Works on Windows, macOS, and Linux.',
|
|
48
|
+
inputSchema: z.object({
|
|
49
|
+
provider: z.enum(['s3', 'gcs', 'vercel-blob']).describe('The cloud storage provider.'),
|
|
50
|
+
bucketName: z.string().optional().describe('The name of the bucket (required for S3 and GCS, ignored for Vercel Blob).'),
|
|
51
|
+
prefix: z.string().optional().describe('An optional prefix or folder path to filter files (e.g. "backups/").'),
|
|
52
|
+
limit: z.number().int().min(1).max(5000).optional().default(100).describe('Maximum number of files to list.'),
|
|
53
|
+
}),
|
|
54
|
+
execute: async ({ provider, bucketName, prefix, limit }) => {
|
|
55
|
+
emitProgress({ type: 'fetch:start', label: `Listing ${provider} bucket`, detail: bucketName || 'vercel-blob' });
|
|
56
|
+
if (provider !== 'vercel-blob' && !bucketName) {
|
|
57
|
+
emitProgress({ type: 'fetch:end', label: 'Cloud list failed', detail: 'Bucket name required' });
|
|
58
|
+
return { error: `Bucket name is required for provider "${provider}".` };
|
|
59
|
+
}
|
|
60
|
+
// TIER 1: Official Node.js SDKs
|
|
61
|
+
if (provider === 's3') {
|
|
62
|
+
try {
|
|
63
|
+
const { S3Client, ListObjectsV2Command } = await import('@aws-sdk/client-s3');
|
|
64
|
+
emitProgress({ type: 'step', label: 'Using S3 SDK' });
|
|
65
|
+
const client = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
|
|
66
|
+
const command = new ListObjectsV2Command({
|
|
67
|
+
Bucket: bucketName,
|
|
68
|
+
Prefix: prefix,
|
|
69
|
+
MaxKeys: limit,
|
|
70
|
+
});
|
|
71
|
+
const response = await client.send(command);
|
|
72
|
+
const files = (response.Contents || []).map((item) => ({
|
|
73
|
+
key: item.Key || '',
|
|
74
|
+
size: item.Size || 0,
|
|
75
|
+
lastModified: item.LastModified ? item.LastModified.toISOString() : '',
|
|
76
|
+
url: `https://${bucketName}.s3.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com/${item.Key}`,
|
|
77
|
+
}));
|
|
78
|
+
emitProgress({ type: 'fetch:end', label: 'S3 files listed', detail: `Found ${files.length} file(s)` });
|
|
79
|
+
return { provider, bucket: bucketName, files };
|
|
80
|
+
}
|
|
81
|
+
catch (sdkError) {
|
|
82
|
+
emitProgress({ type: 'step', label: 'S3 SDK unavailable or failed', detail: sdkError.message });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (provider === 'gcs') {
|
|
86
|
+
try {
|
|
87
|
+
const { Storage } = await import('@google-cloud/storage');
|
|
88
|
+
emitProgress({ type: 'step', label: 'Using GCS SDK' });
|
|
89
|
+
const storage = new Storage();
|
|
90
|
+
const gcsOptions = { maxResults: limit };
|
|
91
|
+
if (prefix) {
|
|
92
|
+
gcsOptions.prefix = prefix;
|
|
93
|
+
}
|
|
94
|
+
const [gcsFiles] = await storage.bucket(bucketName).getFiles(gcsOptions);
|
|
95
|
+
const files = gcsFiles.map((file) => ({
|
|
96
|
+
key: file.name,
|
|
97
|
+
size: file.metadata.size ? parseInt(file.metadata.size, 10) : 0,
|
|
98
|
+
lastModified: file.metadata.updated || '',
|
|
99
|
+
url: `https://storage.googleapis.com/${bucketName}/${file.name}`,
|
|
100
|
+
}));
|
|
101
|
+
emitProgress({ type: 'fetch:end', label: 'GCS files listed', detail: `Found ${files.length} file(s)` });
|
|
102
|
+
return { provider, bucket: bucketName, files };
|
|
103
|
+
}
|
|
104
|
+
catch (sdkError) {
|
|
105
|
+
emitProgress({ type: 'step', label: 'GCS SDK unavailable or failed', detail: sdkError.message });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (provider === 'vercel-blob') {
|
|
109
|
+
try {
|
|
110
|
+
const { list } = await import('@vercel/blob');
|
|
111
|
+
emitProgress({ type: 'step', label: 'Using Vercel Blob SDK' });
|
|
112
|
+
const listOptions = { limit };
|
|
113
|
+
if (prefix) {
|
|
114
|
+
listOptions.prefix = prefix;
|
|
115
|
+
}
|
|
116
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
117
|
+
listOptions.token = process.env.BLOB_READ_WRITE_TOKEN;
|
|
118
|
+
}
|
|
119
|
+
const response = await list(listOptions);
|
|
120
|
+
const files = response.blobs.map((blob) => ({
|
|
121
|
+
key: blob.pathname,
|
|
122
|
+
size: blob.size,
|
|
123
|
+
lastModified: blob.uploadedAt ? blob.uploadedAt.toISOString() : '',
|
|
124
|
+
url: blob.url,
|
|
125
|
+
}));
|
|
126
|
+
emitProgress({ type: 'fetch:end', label: 'Vercel Blobs listed', detail: `Found ${files.length} files` });
|
|
127
|
+
return { provider, files };
|
|
128
|
+
}
|
|
129
|
+
catch (sdkError) {
|
|
130
|
+
emitProgress({ type: 'step', label: 'Vercel Blob SDK unavailable or failed', detail: sdkError.message });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// TIER 2: Native CLI Fallbacks
|
|
134
|
+
if (provider === 's3' && (await commandExists('aws'))) {
|
|
135
|
+
try {
|
|
136
|
+
emitProgress({ type: 'step', label: 'Falling back to AWS CLI' });
|
|
137
|
+
const s3Uri = `s3://${bucketName}/${prefix || ''}`;
|
|
138
|
+
const { stdout } = await execFileAsync('aws', ['s3', 'ls', s3Uri], { windowsHide: true, timeout: 15000 });
|
|
139
|
+
const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
140
|
+
const files = lines.map((line) => {
|
|
141
|
+
const parts = line.trim().split(/\s+/);
|
|
142
|
+
if (parts[0] === 'PRE') {
|
|
143
|
+
return { key: parts.slice(1).join(' '), size: 0, isFolder: true };
|
|
144
|
+
}
|
|
145
|
+
const lastModified = `${parts[0] || ''} ${parts[1] || ''}`.trim();
|
|
146
|
+
const size = parseInt(parts[2] || '0', 10);
|
|
147
|
+
const key = parts.slice(3).join(' ');
|
|
148
|
+
return {
|
|
149
|
+
key: (prefix || '') + key,
|
|
150
|
+
size,
|
|
151
|
+
lastModified,
|
|
152
|
+
url: `https://${bucketName}.s3.amazonaws.com/${prefix || ''}${key}`,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
emitProgress({ type: 'fetch:end', label: 'S3 files listed (CLI)', detail: `Found ${files.length} item(s)` });
|
|
156
|
+
return { provider, bucket: bucketName, method: 'aws-cli', files };
|
|
157
|
+
}
|
|
158
|
+
catch (cliError) {
|
|
159
|
+
emitProgress({ type: 'step', label: 'AWS CLI fallback failed', detail: cliError.message });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else if (provider === 'gcs' && (await commandExists('gsutil'))) {
|
|
163
|
+
try {
|
|
164
|
+
emitProgress({ type: 'step', label: 'Falling back to gsutil CLI' });
|
|
165
|
+
const gcsUri = `gs://${bucketName}/${prefix || ''}`;
|
|
166
|
+
const { stdout } = await execFileAsync('gsutil', ['ls', '-l', gcsUri], { windowsHide: true, timeout: 15000 });
|
|
167
|
+
const lines = stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
168
|
+
const files = [];
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
if (trimmed.startsWith('TOTAL:'))
|
|
172
|
+
continue;
|
|
173
|
+
const parts = trimmed.split(/\s+/);
|
|
174
|
+
if (parts.length >= 3) {
|
|
175
|
+
const size = parseInt(parts[0] || '0', 10);
|
|
176
|
+
const lastModified = parts[1] || '';
|
|
177
|
+
const key = parts.slice(2).join(' ').replace(`gs://${bucketName}/`, '');
|
|
178
|
+
files.push({
|
|
179
|
+
key,
|
|
180
|
+
size,
|
|
181
|
+
lastModified,
|
|
182
|
+
url: `https://storage.googleapis.com/${bucketName}/${key}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
emitProgress({ type: 'fetch:end', label: 'GCS files listed (gsutil)', detail: `Found ${files.length} item(s)` });
|
|
187
|
+
return { provider, bucket: bucketName, method: 'gsutil', files };
|
|
188
|
+
}
|
|
189
|
+
catch (cliError) {
|
|
190
|
+
emitProgress({ type: 'step', label: 'gsutil fallback failed', detail: cliError.message });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (provider === 'vercel-blob' && (await commandExists('vercel'))) {
|
|
194
|
+
try {
|
|
195
|
+
emitProgress({ type: 'step', label: 'Falling back to Vercel CLI' });
|
|
196
|
+
const args = ['blob', 'list'];
|
|
197
|
+
if (prefix)
|
|
198
|
+
args.push('--prefix', prefix);
|
|
199
|
+
if (limit)
|
|
200
|
+
args.push('--limit', String(limit));
|
|
201
|
+
const { stdout } = await execFileAsync('vercel', args, { windowsHide: true, timeout: 15000 });
|
|
202
|
+
const parsed = JSON.parse(stdout.trim());
|
|
203
|
+
const files = (parsed.blobs || []).map((blob) => ({
|
|
204
|
+
key: blob.pathname,
|
|
205
|
+
size: blob.size,
|
|
206
|
+
lastModified: blob.uploadedAt,
|
|
207
|
+
url: blob.url,
|
|
208
|
+
}));
|
|
209
|
+
emitProgress({ type: 'fetch:end', label: 'Vercel Blobs listed (CLI)' });
|
|
210
|
+
return { provider, method: 'vercel-cli', files };
|
|
211
|
+
}
|
|
212
|
+
catch (cliError) {
|
|
213
|
+
emitProgress({ type: 'step', label: 'Vercel CLI fallback failed', detail: cliError.message });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
emitProgress({ type: 'tool:error', label: 'Bucket listing failed', detail: 'No SDK or CLI available' });
|
|
217
|
+
return {
|
|
218
|
+
error: `Failed to list files. Please configure appropriate credentials for "${provider}" or install the "${provider === 'vercel-blob' ? 'vercel' : (provider === 's3' ? 'aws' : 'gcloud')}" CLI utility.`,
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
uploadFileToCloud: tool({
|
|
223
|
+
description: 'Securely upload local files (database dumps, screenshots, or logs) to a remote object store (AWS S3, Google Cloud Storage, or Vercel Blob) with Content-Type preservation. Works on Windows, macOS, and Linux.',
|
|
224
|
+
inputSchema: z.object({
|
|
225
|
+
provider: z.enum(['s3', 'gcs', 'vercel-blob']).describe('The cloud storage provider.'),
|
|
226
|
+
localFilePath: z.string().min(1).describe('Relative or absolute path to the local file to upload in the workspace.'),
|
|
227
|
+
bucketName: z.string().optional().describe('The name of the bucket (required for S3 and GCS, ignored for Vercel Blob).'),
|
|
228
|
+
destinationKey: z.string().min(1).describe('The destination key/path inside the bucket (e.g., "backups/db.sql").'),
|
|
229
|
+
}),
|
|
230
|
+
execute: async ({ provider, localFilePath, bucketName, destinationKey }) => {
|
|
231
|
+
emitProgress({ type: 'tool:start', label: `Uploading file to ${provider}`, detail: localFilePath });
|
|
232
|
+
const root = resolveWorkspaceRoot();
|
|
233
|
+
const resolvedLocalPath = path.isAbsolute(localFilePath) ? localFilePath : path.join(root, localFilePath);
|
|
234
|
+
if (!existsSync(resolvedLocalPath)) {
|
|
235
|
+
emitProgress({ type: 'tool:error', label: 'Upload failed', detail: 'Local file not found' });
|
|
236
|
+
return { error: `Local file not found at path: ${localFilePath}` };
|
|
237
|
+
}
|
|
238
|
+
if (provider !== 'vercel-blob' && !bucketName) {
|
|
239
|
+
emitProgress({ type: 'tool:error', label: 'Upload failed', detail: 'Bucket name required' });
|
|
240
|
+
return { error: `Bucket name is required for provider "${provider}".` };
|
|
241
|
+
}
|
|
242
|
+
const contentType = detectContentType(resolvedLocalPath);
|
|
243
|
+
// TIER 1: Official Node.js SDKs
|
|
244
|
+
if (provider === 's3') {
|
|
245
|
+
try {
|
|
246
|
+
const { S3Client } = await import('@aws-sdk/client-s3');
|
|
247
|
+
const { Upload } = await import('@aws-sdk/lib-storage');
|
|
248
|
+
emitProgress({ type: 'step', label: 'Uploading using S3 Upload SDK', detail: `ContentType: ${contentType}` });
|
|
249
|
+
const client = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
|
|
250
|
+
const fileStream = createReadStream(resolvedLocalPath);
|
|
251
|
+
const uploader = new Upload({
|
|
252
|
+
client,
|
|
253
|
+
params: {
|
|
254
|
+
Bucket: bucketName,
|
|
255
|
+
Key: destinationKey,
|
|
256
|
+
Body: fileStream,
|
|
257
|
+
ContentType: contentType,
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
await uploader.done();
|
|
261
|
+
const publicUrl = `https://${bucketName}.s3.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com/${destinationKey}`;
|
|
262
|
+
emitProgress({ type: 'tool:end', label: 'S3 upload successful', detail: destinationKey });
|
|
263
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, url: publicUrl, contentType };
|
|
264
|
+
}
|
|
265
|
+
catch (sdkError) {
|
|
266
|
+
emitProgress({ type: 'step', label: 'S3 SDK upload failed or unavailable', detail: sdkError.message });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else if (provider === 'gcs') {
|
|
270
|
+
try {
|
|
271
|
+
const { Storage } = await import('@google-cloud/storage');
|
|
272
|
+
emitProgress({ type: 'step', label: 'Uploading using GCS SDK', detail: `ContentType: ${contentType}` });
|
|
273
|
+
const storage = new Storage();
|
|
274
|
+
await storage.bucket(bucketName).upload(resolvedLocalPath, {
|
|
275
|
+
destination: destinationKey,
|
|
276
|
+
metadata: {
|
|
277
|
+
contentType: contentType,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
const publicUrl = `https://storage.googleapis.com/${bucketName}/${destinationKey}`;
|
|
281
|
+
emitProgress({ type: 'tool:end', label: 'GCS upload successful', detail: destinationKey });
|
|
282
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, url: publicUrl, contentType };
|
|
283
|
+
}
|
|
284
|
+
catch (sdkError) {
|
|
285
|
+
emitProgress({ type: 'step', label: 'GCS SDK upload failed or unavailable', detail: sdkError.message });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if (provider === 'vercel-blob') {
|
|
289
|
+
try {
|
|
290
|
+
const { put } = await import('@vercel/blob');
|
|
291
|
+
emitProgress({ type: 'step', label: 'Uploading using Vercel Blob SDK', detail: `ContentType: ${contentType}` });
|
|
292
|
+
const fileBuffer = await readFile(resolvedLocalPath);
|
|
293
|
+
const putOptions = {
|
|
294
|
+
access: 'public',
|
|
295
|
+
contentType: contentType,
|
|
296
|
+
};
|
|
297
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
298
|
+
putOptions.token = process.env.BLOB_READ_WRITE_TOKEN;
|
|
299
|
+
}
|
|
300
|
+
const response = await put(destinationKey, fileBuffer, putOptions);
|
|
301
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob upload successful', detail: destinationKey });
|
|
302
|
+
return { success: true, provider, key: response.pathname, url: response.url, contentType };
|
|
303
|
+
}
|
|
304
|
+
catch (sdkError) {
|
|
305
|
+
emitProgress({ type: 'step', label: 'Vercel Blob SDK upload failed or unavailable', detail: sdkError.message });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// TIER 2: Native CLI Fallbacks
|
|
309
|
+
if (provider === 's3' && (await commandExists('aws'))) {
|
|
310
|
+
try {
|
|
311
|
+
emitProgress({ type: 'step', label: 'Uploading using AWS CLI cp' });
|
|
312
|
+
const s3Uri = `s3://${bucketName}/${destinationKey}`;
|
|
313
|
+
await execFileAsync('aws', ['s3', 'cp', resolvedLocalPath, s3Uri, '--content-type', contentType], { windowsHide: true, timeout: 60000 });
|
|
314
|
+
const publicUrl = `https://${bucketName}.s3.amazonaws.com/${destinationKey}`;
|
|
315
|
+
emitProgress({ type: 'tool:end', label: 'S3 upload successful (CLI)' });
|
|
316
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, url: publicUrl, contentType, method: 'aws-cli' };
|
|
317
|
+
}
|
|
318
|
+
catch (cliError) {
|
|
319
|
+
emitProgress({ type: 'step', label: 'AWS CLI cp upload failed', detail: cliError.message });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (provider === 'gcs' && (await commandExists('gsutil'))) {
|
|
323
|
+
try {
|
|
324
|
+
emitProgress({ type: 'step', label: 'Uploading using gsutil CLI cp' });
|
|
325
|
+
const gcsUri = `gs://${bucketName}/${destinationKey}`;
|
|
326
|
+
// Set header with -h
|
|
327
|
+
await execFileAsync('gsutil', ['-h', `Content-Type:${contentType}`, 'cp', resolvedLocalPath, gcsUri], { windowsHide: true, timeout: 60000 });
|
|
328
|
+
const publicUrl = `https://storage.googleapis.com/${bucketName}/${destinationKey}`;
|
|
329
|
+
emitProgress({ type: 'tool:end', label: 'GCS upload successful (CLI)' });
|
|
330
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, url: publicUrl, contentType, method: 'gsutil' };
|
|
331
|
+
}
|
|
332
|
+
catch (cliError) {
|
|
333
|
+
emitProgress({ type: 'step', label: 'gsutil cp upload failed', detail: cliError.message });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (provider === 'vercel-blob' && (await commandExists('vercel'))) {
|
|
337
|
+
try {
|
|
338
|
+
emitProgress({ type: 'step', label: 'Uploading using Vercel CLI put' });
|
|
339
|
+
const { stdout } = await execFileAsync('vercel', ['blob', 'put', resolvedLocalPath], { windowsHide: true, timeout: 60000 });
|
|
340
|
+
const matchedUrl = stdout.trim().match(/https:\/\/\S+/);
|
|
341
|
+
const publicUrl = matchedUrl ? (matchedUrl[0] || stdout.trim()) : stdout.trim();
|
|
342
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob upload successful (CLI)' });
|
|
343
|
+
return { success: true, provider, key: destinationKey, url: publicUrl, method: 'vercel-cli' };
|
|
344
|
+
}
|
|
345
|
+
catch (cliError) {
|
|
346
|
+
emitProgress({ type: 'step', label: 'Vercel CLI put upload failed', detail: cliError.message });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
emitProgress({ type: 'tool:error', label: 'Upload failed', detail: 'No SDK or CLI available' });
|
|
350
|
+
return {
|
|
351
|
+
error: `Failed to upload file. Please configure appropriate credentials for "${provider}" or install the "${provider === 'vercel-blob' ? 'vercel' : (provider === 's3' ? 'aws' : 'gcloud')}" CLI utility.`,
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
downloadFileFromCloud: tool({
|
|
356
|
+
description: 'Download a remote file from cloud storage (AWS S3, Google Cloud Storage, or Vercel Blob) directly to a local workspace path. Works on Windows, macOS, and Linux.',
|
|
357
|
+
inputSchema: z.object({
|
|
358
|
+
provider: z.enum(['s3', 'gcs', 'vercel-blob']).describe('The cloud storage provider.'),
|
|
359
|
+
bucketName: z.string().optional().describe('The name of the bucket (required for S3 and GCS, ignored for Vercel Blob).'),
|
|
360
|
+
destinationKey: z.string().min(1).describe('The remote key or pathname of the file to download (or a full HTTPS URL for Vercel Blob).'),
|
|
361
|
+
localFilePath: z.string().min(1).describe('The local destination path in the workspace (relative or absolute).'),
|
|
362
|
+
}),
|
|
363
|
+
execute: async ({ provider, bucketName, destinationKey, localFilePath }) => {
|
|
364
|
+
emitProgress({ type: 'tool:start', label: `Downloading file from ${provider}`, detail: destinationKey });
|
|
365
|
+
const root = resolveWorkspaceRoot();
|
|
366
|
+
const resolvedLocalPath = path.isAbsolute(localFilePath) ? localFilePath : path.join(root, localFilePath);
|
|
367
|
+
const outputDir = path.dirname(resolvedLocalPath);
|
|
368
|
+
if (!existsSync(outputDir)) {
|
|
369
|
+
const fs = await import('node:fs/promises');
|
|
370
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
371
|
+
}
|
|
372
|
+
if (provider !== 'vercel-blob' && !bucketName) {
|
|
373
|
+
emitProgress({ type: 'tool:error', label: 'Download failed', detail: 'Bucket name required' });
|
|
374
|
+
return { error: `Bucket name is required for provider "${provider}".` };
|
|
375
|
+
}
|
|
376
|
+
const { createWriteStream } = await import('node:fs');
|
|
377
|
+
const { pipeline } = await import('node:stream/promises');
|
|
378
|
+
// TIER 1: Official Node.js SDKs
|
|
379
|
+
if (provider === 's3') {
|
|
380
|
+
try {
|
|
381
|
+
const { S3Client, GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
382
|
+
emitProgress({ type: 'step', label: 'Downloading using S3 SDK GetObject' });
|
|
383
|
+
const client = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
|
|
384
|
+
const command = new GetObjectCommand({
|
|
385
|
+
Bucket: bucketName,
|
|
386
|
+
Key: destinationKey,
|
|
387
|
+
});
|
|
388
|
+
const response = await client.send(command);
|
|
389
|
+
const body = response.Body;
|
|
390
|
+
if (!body) {
|
|
391
|
+
throw new Error('S3 GetObject returned an empty body.');
|
|
392
|
+
}
|
|
393
|
+
const fileStream = createWriteStream(resolvedLocalPath);
|
|
394
|
+
await pipeline(body, fileStream);
|
|
395
|
+
emitProgress({ type: 'tool:end', label: 'S3 download successful', detail: resolvedLocalPath });
|
|
396
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, localPath: resolvedLocalPath };
|
|
397
|
+
}
|
|
398
|
+
catch (sdkError) {
|
|
399
|
+
emitProgress({ type: 'step', label: 'S3 SDK download failed or unavailable', detail: sdkError.message });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (provider === 'gcs') {
|
|
403
|
+
try {
|
|
404
|
+
const { Storage } = await import('@google-cloud/storage');
|
|
405
|
+
emitProgress({ type: 'step', label: 'Downloading using GCS SDK' });
|
|
406
|
+
const storage = new Storage();
|
|
407
|
+
await storage.bucket(bucketName).file(destinationKey).download({
|
|
408
|
+
destination: resolvedLocalPath,
|
|
409
|
+
});
|
|
410
|
+
emitProgress({ type: 'tool:end', label: 'GCS download successful', detail: resolvedLocalPath });
|
|
411
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, localPath: resolvedLocalPath };
|
|
412
|
+
}
|
|
413
|
+
catch (sdkError) {
|
|
414
|
+
emitProgress({ type: 'step', label: 'GCS SDK download failed or unavailable', detail: sdkError.message });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (provider === 'vercel-blob') {
|
|
418
|
+
try {
|
|
419
|
+
const { list } = await import('@vercel/blob');
|
|
420
|
+
emitProgress({ type: 'step', label: 'Downloading using Vercel Blob API' });
|
|
421
|
+
let downloadUrl = destinationKey;
|
|
422
|
+
if (!destinationKey.startsWith('http://') && !destinationKey.startsWith('https://')) {
|
|
423
|
+
const listOptions = { limit: 10, prefix: destinationKey };
|
|
424
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
425
|
+
listOptions.token = process.env.BLOB_READ_WRITE_TOKEN;
|
|
426
|
+
}
|
|
427
|
+
const response = await list(listOptions);
|
|
428
|
+
const matched = response.blobs.find(b => b.pathname === destinationKey);
|
|
429
|
+
if (matched) {
|
|
430
|
+
downloadUrl = matched.url;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
throw new Error(`No Vercel Blob found with pathname "${destinationKey}".`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const res = await fetch(downloadUrl);
|
|
437
|
+
if (!res.ok) {
|
|
438
|
+
throw new Error(`Failed to fetch Vercel Blob: HTTP ${res.status} ${res.statusText}`);
|
|
439
|
+
}
|
|
440
|
+
if (!res.body) {
|
|
441
|
+
throw new Error('Vercel Blob fetch body is empty.');
|
|
442
|
+
}
|
|
443
|
+
const { Readable } = await import('node:stream');
|
|
444
|
+
const fileStream = createWriteStream(resolvedLocalPath);
|
|
445
|
+
await pipeline(Readable.fromWeb(res.body), fileStream);
|
|
446
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob download successful', detail: resolvedLocalPath });
|
|
447
|
+
return { success: true, provider, url: downloadUrl, localPath: resolvedLocalPath };
|
|
448
|
+
}
|
|
449
|
+
catch (sdkError) {
|
|
450
|
+
emitProgress({ type: 'step', label: 'Vercel Blob download failed or unavailable', detail: sdkError.message });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// TIER 2: Native CLI Fallbacks
|
|
454
|
+
if (provider === 's3' && (await commandExists('aws'))) {
|
|
455
|
+
try {
|
|
456
|
+
emitProgress({ type: 'step', label: 'Downloading using AWS CLI cp' });
|
|
457
|
+
const s3Uri = `s3://${bucketName}/${destinationKey}`;
|
|
458
|
+
await execFileAsync('aws', ['s3', 'cp', s3Uri, resolvedLocalPath], { windowsHide: true, timeout: 60000 });
|
|
459
|
+
emitProgress({ type: 'tool:end', label: 'S3 download successful (CLI)', detail: resolvedLocalPath });
|
|
460
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, localPath: resolvedLocalPath, method: 'aws-cli' };
|
|
461
|
+
}
|
|
462
|
+
catch (cliError) {
|
|
463
|
+
emitProgress({ type: 'step', label: 'AWS CLI cp download failed', detail: cliError.message });
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else if (provider === 'gcs' && (await commandExists('gsutil'))) {
|
|
467
|
+
try {
|
|
468
|
+
emitProgress({ type: 'step', label: 'Downloading using gsutil CLI cp' });
|
|
469
|
+
const gcsUri = `gs://${bucketName}/${destinationKey}`;
|
|
470
|
+
await execFileAsync('gsutil', ['cp', gcsUri, resolvedLocalPath], { windowsHide: true, timeout: 60000 });
|
|
471
|
+
emitProgress({ type: 'tool:end', label: 'GCS download successful (CLI)', detail: resolvedLocalPath });
|
|
472
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, localPath: resolvedLocalPath, method: 'gsutil' };
|
|
473
|
+
}
|
|
474
|
+
catch (cliError) {
|
|
475
|
+
emitProgress({ type: 'step', label: 'gsutil cp download failed', detail: cliError.message });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else if (provider === 'vercel-blob') {
|
|
479
|
+
try {
|
|
480
|
+
emitProgress({ type: 'step', label: 'Downloading using native HTTP fetch fallback' });
|
|
481
|
+
let downloadUrl = destinationKey;
|
|
482
|
+
if (!destinationKey.startsWith('http://') && !destinationKey.startsWith('https://')) {
|
|
483
|
+
throw new Error('Vercel CLI fallback requires a full blob HTTP URL for downloading.');
|
|
484
|
+
}
|
|
485
|
+
const res = await fetch(downloadUrl);
|
|
486
|
+
if (!res.ok) {
|
|
487
|
+
throw new Error(`Fetch failed: HTTP ${res.status}`);
|
|
488
|
+
}
|
|
489
|
+
if (!res.body) {
|
|
490
|
+
throw new Error('Empty response body.');
|
|
491
|
+
}
|
|
492
|
+
const { Readable } = await import('node:stream');
|
|
493
|
+
const fileStream = createWriteStream(resolvedLocalPath);
|
|
494
|
+
await pipeline(Readable.fromWeb(res.body), fileStream);
|
|
495
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob download successful (HTTP fetch)' });
|
|
496
|
+
return { success: true, provider, url: downloadUrl, localPath: resolvedLocalPath, method: 'http-fetch' };
|
|
497
|
+
}
|
|
498
|
+
catch (fetchError) {
|
|
499
|
+
emitProgress({ type: 'step', label: 'HTTP fetch fallback failed', detail: fetchError.message });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
emitProgress({ type: 'tool:error', label: 'Download failed', detail: 'No SDK or CLI available' });
|
|
503
|
+
return {
|
|
504
|
+
error: `Failed to download file. Please configure appropriate credentials for "${provider}" or install the "${provider === 'vercel-blob' ? 'vercel' : (provider === 's3' ? 'aws' : 'gcloud')}" CLI utility.`,
|
|
505
|
+
};
|
|
506
|
+
},
|
|
507
|
+
}),
|
|
508
|
+
deleteCloudFile: tool({
|
|
509
|
+
description: 'Delete a file or recursively purge directories from cloud storage (AWS S3, Google Cloud Storage, or Vercel Blob). Works on Windows, macOS, and Linux.',
|
|
510
|
+
inputSchema: z.object({
|
|
511
|
+
provider: z.enum(['s3', 'gcs', 'vercel-blob']).describe('The cloud storage provider.'),
|
|
512
|
+
bucketName: z.string().optional().describe('The name of the bucket (required for S3 and GCS, ignored for Vercel Blob).'),
|
|
513
|
+
destinationKey: z.string().min(1).describe('The remote key, pathname, or directory prefix to delete.'),
|
|
514
|
+
recursive: z.boolean().optional().describe('If true, performs a recursive deletion of all nested objects under this path/prefix.'),
|
|
515
|
+
}),
|
|
516
|
+
execute: async ({ provider, bucketName, destinationKey, recursive }) => {
|
|
517
|
+
emitProgress({ type: 'tool:start', label: `Deleting from ${provider}`, detail: `${destinationKey}${recursive ? ' (recursively)' : ''}` });
|
|
518
|
+
if (provider !== 'vercel-blob' && !bucketName) {
|
|
519
|
+
emitProgress({ type: 'tool:error', label: 'Delete failed', detail: 'Bucket name required' });
|
|
520
|
+
return { error: `Bucket name is required for provider "${provider}".` };
|
|
521
|
+
}
|
|
522
|
+
// TIER 1: Official Node.js SDKs
|
|
523
|
+
if (provider === 's3') {
|
|
524
|
+
try {
|
|
525
|
+
const { S3Client, DeleteObjectCommand, DeleteObjectsCommand, ListObjectsV2Command } = await import('@aws-sdk/client-s3');
|
|
526
|
+
emitProgress({ type: 'step', label: 'Deleting using S3 SDK' });
|
|
527
|
+
const client = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
|
|
528
|
+
if (recursive) {
|
|
529
|
+
emitProgress({ type: 'step', label: 'Listing objects for recursive S3 delete' });
|
|
530
|
+
const listCommand = new ListObjectsV2Command({
|
|
531
|
+
Bucket: bucketName,
|
|
532
|
+
Prefix: destinationKey,
|
|
533
|
+
});
|
|
534
|
+
const listRes = await client.send(listCommand);
|
|
535
|
+
const contents = listRes.Contents || [];
|
|
536
|
+
if (contents.length === 0) {
|
|
537
|
+
emitProgress({ type: 'tool:end', label: 'S3 recursive delete completed', detail: '0 objects found' });
|
|
538
|
+
return { success: true, provider, bucket: bucketName, deletedCount: 0 };
|
|
539
|
+
}
|
|
540
|
+
// AWS supports batch deletion up to 1000 objects
|
|
541
|
+
const deleteCommand = new DeleteObjectsCommand({
|
|
542
|
+
Bucket: bucketName,
|
|
543
|
+
Delete: {
|
|
544
|
+
Objects: contents.map(item => ({ Key: item.Key })),
|
|
545
|
+
Quiet: true,
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
await client.send(deleteCommand);
|
|
549
|
+
emitProgress({ type: 'tool:end', label: 'S3 recursive delete successful', detail: `${contents.length} objects` });
|
|
550
|
+
return { success: true, provider, bucket: bucketName, deletedCount: contents.length };
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
const command = new DeleteObjectCommand({
|
|
554
|
+
Bucket: bucketName,
|
|
555
|
+
Key: destinationKey,
|
|
556
|
+
});
|
|
557
|
+
await client.send(command);
|
|
558
|
+
emitProgress({ type: 'tool:end', label: 'S3 delete successful', detail: destinationKey });
|
|
559
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch (sdkError) {
|
|
563
|
+
emitProgress({ type: 'step', label: 'S3 SDK delete failed or unavailable', detail: sdkError.message });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else if (provider === 'gcs') {
|
|
567
|
+
try {
|
|
568
|
+
const { Storage } = await import('@google-cloud/storage');
|
|
569
|
+
emitProgress({ type: 'step', label: 'Deleting using GCS SDK' });
|
|
570
|
+
const storage = new Storage();
|
|
571
|
+
const bucket = storage.bucket(bucketName);
|
|
572
|
+
if (recursive) {
|
|
573
|
+
emitProgress({ type: 'step', label: 'Purging GCS prefix recursively' });
|
|
574
|
+
await bucket.deleteFiles({ prefix: destinationKey });
|
|
575
|
+
emitProgress({ type: 'tool:end', label: 'GCS recursive delete successful', detail: destinationKey });
|
|
576
|
+
return { success: true, provider, bucket: bucketName, prefix: destinationKey, recursive: true };
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
await bucket.file(destinationKey).delete();
|
|
580
|
+
emitProgress({ type: 'tool:end', label: 'GCS delete successful', detail: destinationKey });
|
|
581
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch (sdkError) {
|
|
585
|
+
emitProgress({ type: 'step', label: 'GCS SDK delete failed or unavailable', detail: sdkError.message });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
else if (provider === 'vercel-blob') {
|
|
589
|
+
try {
|
|
590
|
+
const { list, del } = await import('@vercel/blob');
|
|
591
|
+
emitProgress({ type: 'step', label: 'Deleting using Vercel Blob SDK' });
|
|
592
|
+
const delOptions = {};
|
|
593
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
594
|
+
delOptions.token = process.env.BLOB_READ_WRITE_TOKEN;
|
|
595
|
+
}
|
|
596
|
+
if (recursive) {
|
|
597
|
+
emitProgress({ type: 'step', label: 'Listing blobs for recursive Vercel Blob delete' });
|
|
598
|
+
const listRes = await list({ prefix: destinationKey, ...delOptions });
|
|
599
|
+
const urls = listRes.blobs.map(b => b.url);
|
|
600
|
+
if (urls.length === 0) {
|
|
601
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob recursive delete completed', detail: '0 objects found' });
|
|
602
|
+
return { success: true, provider, deletedCount: 0 };
|
|
603
|
+
}
|
|
604
|
+
await del(urls, delOptions);
|
|
605
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob recursive delete successful', detail: `${urls.length} files` });
|
|
606
|
+
return { success: true, provider, deletedCount: urls.length };
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
let deleteUrl = destinationKey;
|
|
610
|
+
if (!destinationKey.startsWith('http://') && !destinationKey.startsWith('https://')) {
|
|
611
|
+
const response = await list({ prefix: destinationKey, limit: 10, ...delOptions });
|
|
612
|
+
const matched = response.blobs.find(b => b.pathname === destinationKey);
|
|
613
|
+
if (matched) {
|
|
614
|
+
deleteUrl = matched.url;
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
throw new Error(`No Vercel Blob found with pathname "${destinationKey}".`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
await del(deleteUrl, delOptions);
|
|
621
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob delete successful', detail: deleteUrl });
|
|
622
|
+
return { success: true, provider, url: deleteUrl };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (sdkError) {
|
|
626
|
+
emitProgress({ type: 'step', label: 'Vercel Blob SDK delete failed or unavailable', detail: sdkError.message });
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// TIER 2: Native CLI Fallbacks
|
|
630
|
+
if (provider === 's3' && (await commandExists('aws'))) {
|
|
631
|
+
try {
|
|
632
|
+
emitProgress({ type: 'step', label: 'Deleting using AWS CLI' });
|
|
633
|
+
const s3Uri = `s3://${bucketName}/${destinationKey}`;
|
|
634
|
+
const args = ['s3', 'rm', s3Uri];
|
|
635
|
+
if (recursive) {
|
|
636
|
+
args.push('--recursive');
|
|
637
|
+
}
|
|
638
|
+
await execFileAsync('aws', args, { windowsHide: true, timeout: 30000 });
|
|
639
|
+
emitProgress({ type: 'tool:end', label: 'S3 delete successful (CLI)' });
|
|
640
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, recursive, method: 'aws-cli' };
|
|
641
|
+
}
|
|
642
|
+
catch (cliError) {
|
|
643
|
+
emitProgress({ type: 'step', label: 'AWS CLI rm delete failed', detail: cliError.message });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else if (provider === 'gcs' && (await commandExists('gsutil'))) {
|
|
647
|
+
try {
|
|
648
|
+
emitProgress({ type: 'step', label: 'Deleting using gsutil CLI' });
|
|
649
|
+
const gcsUri = `gs://${bucketName}/${destinationKey}`;
|
|
650
|
+
const args = ['rm'];
|
|
651
|
+
if (recursive) {
|
|
652
|
+
args.push('-r');
|
|
653
|
+
}
|
|
654
|
+
args.push(gcsUri);
|
|
655
|
+
await execFileAsync('gsutil', args, { windowsHide: true, timeout: 30000 });
|
|
656
|
+
emitProgress({ type: 'tool:end', label: 'GCS delete successful (CLI)' });
|
|
657
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, recursive, method: 'gsutil' };
|
|
658
|
+
}
|
|
659
|
+
catch (cliError) {
|
|
660
|
+
emitProgress({ type: 'step', label: 'gsutil rm delete failed', detail: cliError.message });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else if (provider === 'vercel-blob' && (await commandExists('vercel'))) {
|
|
664
|
+
try {
|
|
665
|
+
emitProgress({ type: 'step', label: 'Deleting using Vercel CLI blob delete' });
|
|
666
|
+
if (recursive) {
|
|
667
|
+
throw new Error('Vercel CLI fallback does not support recursive deletion. Please use SDK.');
|
|
668
|
+
}
|
|
669
|
+
let deleteUrl = destinationKey;
|
|
670
|
+
if (!destinationKey.startsWith('http://') && !destinationKey.startsWith('https://')) {
|
|
671
|
+
throw new Error('Vercel CLI fallback requires a full blob HTTP URL for deleting.');
|
|
672
|
+
}
|
|
673
|
+
await execFileAsync('vercel', ['blob', 'delete', deleteUrl], { windowsHide: true, timeout: 20000 });
|
|
674
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob delete successful (CLI)', detail: deleteUrl });
|
|
675
|
+
return { success: true, provider, url: deleteUrl, method: 'vercel-cli' };
|
|
676
|
+
}
|
|
677
|
+
catch (cliError) {
|
|
678
|
+
emitProgress({ type: 'step', label: 'Vercel CLI delete failed', detail: cliError.message });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
emitProgress({ type: 'tool:error', label: 'Delete failed', detail: 'No SDK or CLI available' });
|
|
682
|
+
return {
|
|
683
|
+
error: `Failed to delete file. Please configure appropriate credentials for "${provider}" or install the "${provider === 'vercel-blob' ? 'vercel' : (provider === 's3' ? 'aws' : 'gcloud')}" CLI utility.`,
|
|
684
|
+
};
|
|
685
|
+
},
|
|
686
|
+
}),
|
|
687
|
+
generateCloudSignedUrl: tool({
|
|
688
|
+
description: 'Generate a temporary pre-signed URL for S3 or GCS to allow secure read/write operations, or return the public URL for Vercel Blob. Highly resilient with local fallback warning systems.',
|
|
689
|
+
inputSchema: z.object({
|
|
690
|
+
provider: z.enum(['s3', 'gcs', 'vercel-blob']).describe('The cloud storage provider.'),
|
|
691
|
+
bucketName: z.string().optional().describe('The name of the bucket (required for S3 and GCS, ignored for Vercel Blob).'),
|
|
692
|
+
destinationKey: z.string().min(1).describe('The remote key or path to sign.'),
|
|
693
|
+
expiresIn: z.number().int().min(1).optional().default(3600).describe('Expiration window in seconds (default 3600 / 1 hour).'),
|
|
694
|
+
operation: z.enum(['read', 'write']).optional().default('read').describe('The allowed access level: "read" (GET) or "write" (PUT). Default is "read".'),
|
|
695
|
+
}),
|
|
696
|
+
execute: async ({ provider, bucketName, destinationKey, expiresIn = 3600, operation = 'read' }) => {
|
|
697
|
+
emitProgress({ type: 'tool:start', label: `Generating signed URL (${operation}) on ${provider}`, detail: destinationKey });
|
|
698
|
+
if (provider !== 'vercel-blob' && !bucketName) {
|
|
699
|
+
emitProgress({ type: 'tool:error', label: 'Signed URL failed', detail: 'Bucket name required' });
|
|
700
|
+
return { error: `Bucket name is required for provider "${provider}".` };
|
|
701
|
+
}
|
|
702
|
+
// S3 presigned URL
|
|
703
|
+
if (provider === 's3') {
|
|
704
|
+
try {
|
|
705
|
+
const { S3Client, GetObjectCommand, PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
706
|
+
const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
|
707
|
+
const client = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
|
|
708
|
+
const command = operation === 'write'
|
|
709
|
+
? new PutObjectCommand({ Bucket: bucketName, Key: destinationKey })
|
|
710
|
+
: new GetObjectCommand({ Bucket: bucketName, Key: destinationKey });
|
|
711
|
+
const signedUrl = await getSignedUrl(client, command, { expiresIn });
|
|
712
|
+
emitProgress({ type: 'tool:end', label: 'S3 presigned URL generated successfully' });
|
|
713
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, url: signedUrl, operation, expires: expiresIn };
|
|
714
|
+
}
|
|
715
|
+
catch (sdkError) {
|
|
716
|
+
emitProgress({ type: 'step', label: 'S3 presigned URL SDK generation failed', detail: sdkError.message });
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else if (provider === 'gcs') {
|
|
720
|
+
try {
|
|
721
|
+
const { Storage } = await import('@google-cloud/storage');
|
|
722
|
+
const storage = new Storage();
|
|
723
|
+
const bucket = storage.bucket(bucketName);
|
|
724
|
+
const file = bucket.file(destinationKey);
|
|
725
|
+
const [signedUrl] = await file.getSignedUrl({
|
|
726
|
+
version: 'v4',
|
|
727
|
+
action: operation === 'write' ? 'write' : 'read',
|
|
728
|
+
expires: Date.now() + expiresIn * 1000,
|
|
729
|
+
});
|
|
730
|
+
emitProgress({ type: 'tool:end', label: 'GCS presigned URL generated successfully' });
|
|
731
|
+
return { success: true, provider, bucket: bucketName, key: destinationKey, url: signedUrl, operation, expires: expiresIn };
|
|
732
|
+
}
|
|
733
|
+
catch (sdkError) {
|
|
734
|
+
emitProgress({ type: 'step', label: 'GCS presigned URL SDK generation failed', detail: sdkError.message });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
else if (provider === 'vercel-blob') {
|
|
738
|
+
try {
|
|
739
|
+
const { list } = await import('@vercel/blob');
|
|
740
|
+
emitProgress({ type: 'step', label: 'Resolving Vercel Blob URL' });
|
|
741
|
+
const listOptions = { limit: 10, prefix: destinationKey };
|
|
742
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
743
|
+
listOptions.token = process.env.BLOB_READ_WRITE_TOKEN;
|
|
744
|
+
}
|
|
745
|
+
const response = await list(listOptions);
|
|
746
|
+
const matched = response.blobs.find(b => b.pathname === destinationKey);
|
|
747
|
+
const fileUrl = matched ? matched.url : `https://blob.vercel-storage.com/${destinationKey}`;
|
|
748
|
+
emitProgress({ type: 'tool:end', label: 'Vercel Blob resolved' });
|
|
749
|
+
return {
|
|
750
|
+
success: true,
|
|
751
|
+
provider,
|
|
752
|
+
key: destinationKey,
|
|
753
|
+
url: fileUrl,
|
|
754
|
+
note: 'Vercel Blob operates with persistent secure tokens. Temporary S3-style signatures are not applicable, standard URL is returned.',
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
catch (sdkError) {
|
|
758
|
+
emitProgress({ type: 'step', label: 'Vercel Blob resolver failed', detail: sdkError.message });
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// Tier 3: Local signature warnings and fallback URL generators
|
|
762
|
+
if (provider === 's3') {
|
|
763
|
+
const fallbackUrl = `https://${bucketName}.s3.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com/${destinationKey}`;
|
|
764
|
+
emitProgress({ type: 'tool:end', label: 'Signed URL fallback constructed' });
|
|
765
|
+
return {
|
|
766
|
+
success: false,
|
|
767
|
+
error: 'Could not generate S3 presigned URL locally. Check AWS credentials.',
|
|
768
|
+
url: fallbackUrl,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
else if (provider === 'gcs') {
|
|
772
|
+
const fallbackUrl = `https://storage.googleapis.com/${bucketName}/${destinationKey}`;
|
|
773
|
+
emitProgress({ type: 'tool:end', label: 'Signed URL fallback constructed' });
|
|
774
|
+
return {
|
|
775
|
+
success: false,
|
|
776
|
+
error: 'Could not generate GCS presigned URL locally. Check Google credentials.',
|
|
777
|
+
url: fallbackUrl,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
return { error: `Failed to generate a temporary pre-signed URL for provider ${provider}. Check your configurations.` };
|
|
781
|
+
},
|
|
782
|
+
}),
|
|
783
|
+
};
|
|
784
|
+
//# sourceMappingURL=cloud.tool.js.map
|