@mixio-pro/kalaasetu-mcp 1.0.15 → 1.0.17
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/package.json +1 -1
- package/src/storage/gcs.ts +0 -3
- package/src/tools/gemini.ts +24 -44
- package/src/tools/image-to-video.sdk-backup.ts +100 -43
- package/src/tools/image-to-video.ts +12 -21
- package/src/utils/fal.utils.ts +15 -7
- package/src/utils/url-file.ts +91 -0
package/package.json
CHANGED
package/src/storage/gcs.ts
CHANGED
|
@@ -18,9 +18,6 @@ export class GCSStorageProvider implements StorageProvider {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
async init(): Promise<void> {
|
|
21
|
-
console.log(
|
|
22
|
-
`Initializing GCS Storage Provider with bucket: ${this.bucket}`
|
|
23
|
-
);
|
|
24
21
|
// Verify we can get credentials
|
|
25
22
|
try {
|
|
26
23
|
await this.auth.getClient();
|
package/src/tools/gemini.ts
CHANGED
|
@@ -16,32 +16,23 @@ const ai = new GoogleGenAI({
|
|
|
16
16
|
apiKey: process.env.GEMINI_API_KEY || "",
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
import { ensureLocalFile } from "../utils/url-file";
|
|
20
|
+
|
|
19
21
|
async function fileToGenerativePart(filePath: string) {
|
|
20
|
-
const
|
|
22
|
+
const fileResult = await ensureLocalFile(filePath);
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`Is absolute: ${isAbsolute}\n` +
|
|
34
|
-
`CWD: ${process.cwd()}`
|
|
35
|
-
);
|
|
24
|
+
try {
|
|
25
|
+
const storage = getStorage();
|
|
26
|
+
const imageBytes = await storage.readFile(fileResult.path);
|
|
27
|
+
return {
|
|
28
|
+
inlineData: {
|
|
29
|
+
data: Buffer.from(imageBytes).toString("base64"),
|
|
30
|
+
mimeType: "image/jpeg",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
} finally {
|
|
34
|
+
fileResult.cleanup();
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
const imageBytes = await storage.readFile(filePath);
|
|
39
|
-
return {
|
|
40
|
-
inlineData: {
|
|
41
|
-
data: Buffer.from(imageBytes).toString("base64"),
|
|
42
|
-
mimeType: "image/jpeg",
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
36
|
}
|
|
46
37
|
|
|
47
38
|
// Helper function to save WAV file
|
|
@@ -165,29 +156,18 @@ async function processVideoInput(
|
|
|
165
156
|
},
|
|
166
157
|
};
|
|
167
158
|
} else {
|
|
168
|
-
// Local file processing - use File Upload API
|
|
169
|
-
|
|
159
|
+
// Local file processing or non-YouTube URL - use File Upload API
|
|
160
|
+
// ensureLocalFile handles downloading URLs if needed
|
|
161
|
+
const fileResult = await ensureLocalFile(input);
|
|
170
162
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
: path.resolve(process.cwd(), input);
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Video file not found: ${input}\n` +
|
|
181
|
-
`Resolved path: ${resolvedPath}\n` +
|
|
182
|
-
`Is absolute: ${isAbsolute}\n` +
|
|
183
|
-
`CWD: ${process.cwd()}`
|
|
184
|
-
);
|
|
163
|
+
try {
|
|
164
|
+
// Upload file to Gemini API
|
|
165
|
+
// We pass the local path (temp or original)
|
|
166
|
+
const uploadedFile = await uploadFileToGemini(fileResult.path);
|
|
167
|
+
return uploadedFile;
|
|
168
|
+
} finally {
|
|
169
|
+
fileResult.cleanup();
|
|
185
170
|
}
|
|
186
|
-
|
|
187
|
-
// Upload file to Gemini API
|
|
188
|
-
const uploadedFile = await uploadFileToGemini(input);
|
|
189
|
-
|
|
190
|
-
return uploadedFile;
|
|
191
171
|
}
|
|
192
172
|
}
|
|
193
173
|
|
|
@@ -15,27 +15,67 @@ function fileToBase64(filePath: string): { data: string; mimeType: string } {
|
|
|
15
15
|
const data = Buffer.from(buf).toString("base64");
|
|
16
16
|
// Detect mime type from extension
|
|
17
17
|
const ext = path.extname(filePath).toLowerCase();
|
|
18
|
-
const mimeType =
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const mimeType =
|
|
19
|
+
ext === ".jpg" || ext === ".jpeg"
|
|
20
|
+
? "image/jpeg"
|
|
21
|
+
: ext === ".png"
|
|
22
|
+
? "image/png"
|
|
23
|
+
: ext === ".webp"
|
|
24
|
+
? "image/webp"
|
|
25
|
+
: "image/png";
|
|
21
26
|
return { data, mimeType };
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export const imageToVideo = {
|
|
25
30
|
name: "imageToVideo",
|
|
26
|
-
description:
|
|
31
|
+
description:
|
|
32
|
+
"Generate videos from an image as starting first frame using Vertex Veo models (predictLongRunning + fetchPredictOperation).",
|
|
27
33
|
parameters: z.object({
|
|
28
34
|
prompt: z.string().describe("Text description for the video"),
|
|
29
|
-
image_path: z
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
image_path: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Path to source image for image-to-video generation"),
|
|
39
|
+
aspect_ratio: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Video aspect ratio: '16:9' or '9:16' (default: '9:16')"),
|
|
43
|
+
duration_seconds: z
|
|
44
|
+
.number()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Video duration in seconds: 4, 6, or 8 (default: 6)"),
|
|
47
|
+
resolution: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Video resolution: '720p' or '1080p' (default: '720p')"),
|
|
51
|
+
negative_prompt: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Text describing what not to include in the video"),
|
|
55
|
+
person_generation: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(
|
|
59
|
+
"Controls generation of people: 'allow_adult' (default for image-to-video) or 'allow_all'"
|
|
60
|
+
),
|
|
61
|
+
reference_images: z
|
|
62
|
+
.array(z.string())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Additional image paths for reference (max 3)"),
|
|
65
|
+
output_path: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe(
|
|
69
|
+
"Output MP4 file path (if multiple predictions, index suffix is added)"
|
|
70
|
+
),
|
|
71
|
+
gemini_api_key: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Gemini API key (uses GEMINI_API_KEY env var if not provided)"),
|
|
75
|
+
model_id: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Model ID (default: veo-2.0-generate-001)"),
|
|
39
79
|
}),
|
|
40
80
|
execute: async (args: {
|
|
41
81
|
prompt: string;
|
|
@@ -52,47 +92,49 @@ export const imageToVideo = {
|
|
|
52
92
|
}) => {
|
|
53
93
|
const apiKey = args.gemini_api_key || process.env.GEMINI_API_KEY;
|
|
54
94
|
if (!apiKey) {
|
|
55
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
"Gemini API key is required. Set GEMINI_API_KEY environment variable or pass gemini_api_key parameter. Get one at https://aistudio.google.com/app/apikey"
|
|
97
|
+
);
|
|
56
98
|
}
|
|
57
99
|
|
|
58
100
|
const model = args.model_id || "veo-2.0-generate-001";
|
|
59
|
-
|
|
101
|
+
|
|
60
102
|
// Initialize Google GenAI client
|
|
61
103
|
const genai = new GoogleGenAI({ apiKey });
|
|
62
104
|
|
|
63
105
|
// Build config for video generation
|
|
64
106
|
const config: any = {};
|
|
65
|
-
|
|
107
|
+
|
|
66
108
|
if (args.duration_seconds !== undefined) {
|
|
67
109
|
config.duration_seconds = args.duration_seconds;
|
|
68
110
|
} else {
|
|
69
111
|
config.duration_seconds = 6; // default
|
|
70
112
|
}
|
|
71
|
-
|
|
113
|
+
|
|
72
114
|
if (args.aspect_ratio) {
|
|
73
115
|
config.aspect_ratio = args.aspect_ratio;
|
|
74
116
|
}
|
|
75
117
|
|
|
76
118
|
try {
|
|
77
119
|
// Start video generation operation
|
|
78
|
-
console.
|
|
120
|
+
console.error(`Starting video generation with model: ${model}`);
|
|
79
121
|
let operation = await genai.models.generateVideos({
|
|
80
122
|
model,
|
|
81
123
|
prompt: args.prompt,
|
|
82
124
|
config,
|
|
83
125
|
});
|
|
84
126
|
|
|
85
|
-
console.
|
|
86
|
-
|
|
127
|
+
console.error("Operation started, waiting for completion...");
|
|
128
|
+
|
|
87
129
|
// Poll until operation is complete (max 10 minutes)
|
|
88
130
|
let tries = 0;
|
|
89
131
|
const maxTries = 60; // 10 minutes with 10s intervals
|
|
90
|
-
|
|
132
|
+
|
|
91
133
|
while (!operation.done && tries < maxTries) {
|
|
92
134
|
await wait(10000); // Wait 10 seconds
|
|
93
135
|
tries++;
|
|
94
|
-
console.
|
|
95
|
-
|
|
136
|
+
console.error(`Polling attempt ${tries}/${maxTries}...`);
|
|
137
|
+
|
|
96
138
|
operation = await genai.operations.getVideosOperation({
|
|
97
139
|
operation: operation,
|
|
98
140
|
});
|
|
@@ -102,60 +144,75 @@ export const imageToVideo = {
|
|
|
102
144
|
throw new Error("Video generation timed out after 10 minutes");
|
|
103
145
|
}
|
|
104
146
|
|
|
105
|
-
console.
|
|
106
|
-
console.
|
|
147
|
+
console.error("Operation completed!");
|
|
148
|
+
console.error(
|
|
149
|
+
"Full Response:",
|
|
150
|
+
JSON.stringify(operation.response, null, 2)
|
|
151
|
+
);
|
|
107
152
|
|
|
108
153
|
// Extract generated videos from response
|
|
109
154
|
const generatedVideos = operation.response?.generatedVideos || [];
|
|
110
|
-
|
|
155
|
+
|
|
111
156
|
if (!generatedVideos || generatedVideos.length === 0) {
|
|
112
157
|
const respStr = JSON.stringify(operation.response, null, 2);
|
|
113
|
-
return `Video generation completed but no videos found in response.\n\nFull Response:\n${respStr.slice(
|
|
158
|
+
return `Video generation completed but no videos found in response.\n\nFull Response:\n${respStr.slice(
|
|
159
|
+
0,
|
|
160
|
+
2000
|
|
161
|
+
)}${respStr.length > 2000 ? "\n...(truncated)" : ""}`;
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
// Download and save videos
|
|
117
165
|
const outputs: string[] = [];
|
|
118
|
-
|
|
166
|
+
|
|
119
167
|
for (let i = 0; i < generatedVideos.length; i++) {
|
|
120
168
|
const generatedVideo = generatedVideos[i];
|
|
121
169
|
const videoUri = generatedVideo?.video?.uri;
|
|
122
|
-
|
|
170
|
+
|
|
123
171
|
if (!videoUri) {
|
|
124
172
|
console.warn(`Video ${i} has no URI`);
|
|
125
173
|
continue;
|
|
126
174
|
}
|
|
127
175
|
|
|
128
|
-
console.
|
|
129
|
-
|
|
176
|
+
console.error(
|
|
177
|
+
`Downloading video ${i + 1}/${generatedVideos.length}...`
|
|
178
|
+
);
|
|
179
|
+
|
|
130
180
|
// Download video from URI
|
|
131
181
|
const videoUrl = `${videoUri}&key=${apiKey}`;
|
|
132
182
|
const response = await fetch(videoUrl);
|
|
133
|
-
|
|
183
|
+
|
|
134
184
|
if (!response.ok) {
|
|
135
|
-
throw new Error(
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Failed to download video: ${response.status} ${response.statusText}`
|
|
187
|
+
);
|
|
136
188
|
}
|
|
137
|
-
|
|
189
|
+
|
|
138
190
|
const buffer = await response.arrayBuffer();
|
|
139
|
-
|
|
191
|
+
|
|
140
192
|
// Save video to file
|
|
141
193
|
const filePath = args.output_path
|
|
142
|
-
?
|
|
143
|
-
|
|
194
|
+
? i === 0
|
|
195
|
+
? args.output_path
|
|
196
|
+
: args.output_path.replace(/\.mp4$/i, `_${i}.mp4`)
|
|
197
|
+
: `video_output_${Date.now()}${i === 0 ? "" : "_" + i}.mp4`;
|
|
144
198
|
const absPath = path.resolve(filePath);
|
|
145
|
-
|
|
199
|
+
|
|
146
200
|
fs.writeFileSync(absPath, Buffer.from(buffer));
|
|
147
201
|
outputs.push(absPath);
|
|
148
|
-
console.
|
|
202
|
+
console.error(`Saved video to: ${absPath}`);
|
|
149
203
|
}
|
|
150
204
|
|
|
151
205
|
if (outputs.length > 0) {
|
|
152
|
-
return `Video(s) saved successfully:\n${outputs
|
|
206
|
+
return `Video(s) saved successfully:\n${outputs
|
|
207
|
+
.map((p, i) => `${i + 1}. ${p}`)
|
|
208
|
+
.join("\n")}`;
|
|
153
209
|
}
|
|
154
210
|
|
|
155
211
|
return "Video generation completed but no videos were saved.";
|
|
156
|
-
|
|
157
212
|
} catch (error: any) {
|
|
158
|
-
throw new Error(
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Video generation failed: ${error.message || JSON.stringify(error)}`
|
|
215
|
+
);
|
|
159
216
|
}
|
|
160
217
|
},
|
|
161
218
|
};
|
|
@@ -11,32 +11,23 @@ async function wait(ms: number): Promise<void> {
|
|
|
11
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
import { ensureLocalFile } from "../utils/url-file";
|
|
15
|
+
|
|
14
16
|
async function fileToBase64(
|
|
15
17
|
filePath: string
|
|
16
18
|
): Promise<{ data: string; mimeType: string }> {
|
|
17
|
-
const
|
|
19
|
+
const fileResult = await ensureLocalFile(filePath);
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`File not found: ${filePath}\n` +
|
|
29
|
-
`Resolved path: ${resolvedPath}\n` +
|
|
30
|
-
`Is absolute: ${isAbsolute}\n` +
|
|
31
|
-
`CWD: ${process.cwd()}`
|
|
32
|
-
);
|
|
21
|
+
try {
|
|
22
|
+
const storage = getStorage();
|
|
23
|
+
const buf = await storage.readFile(fileResult.path);
|
|
24
|
+
const data = Buffer.from(buf).toString("base64");
|
|
25
|
+
// Default to PNG if not sure, similar to existing code
|
|
26
|
+
const mimeType = "image/png";
|
|
27
|
+
return { data, mimeType };
|
|
28
|
+
} finally {
|
|
29
|
+
fileResult.cleanup();
|
|
33
30
|
}
|
|
34
|
-
|
|
35
|
-
const buf = await storage.readFile(filePath);
|
|
36
|
-
const data = Buffer.from(buf).toString("base64");
|
|
37
|
-
// Default to PNG if not sure, similar to existing code
|
|
38
|
-
const mimeType = "image/png";
|
|
39
|
-
return { data, mimeType };
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
export const imageToVideo = {
|
package/src/utils/fal.utils.ts
CHANGED
|
@@ -8,14 +8,16 @@ export async function callFalModel(
|
|
|
8
8
|
const { falKey, logs = true } = options;
|
|
9
9
|
const key = falKey || process.env.FAL_KEY;
|
|
10
10
|
if (!key) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error(
|
|
12
|
+
"FAL_KEY is required. Provide it via fal_key parameter or FAL_KEY environment variable."
|
|
13
|
+
);
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
fal.config({
|
|
15
17
|
credentials: key,
|
|
16
18
|
});
|
|
17
19
|
|
|
18
|
-
console.
|
|
20
|
+
console.error(`[${modelName}] Submitting request to FAL AI...`);
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
23
|
const result = await fal.subscribe(modelName, {
|
|
@@ -23,23 +25,29 @@ export async function callFalModel(
|
|
|
23
25
|
logs,
|
|
24
26
|
onQueueUpdate: (update) => {
|
|
25
27
|
if (update.status === "IN_PROGRESS") {
|
|
26
|
-
console.
|
|
28
|
+
console.error(`[${modelName}] Status: ${update.status}`);
|
|
27
29
|
if (logs && "logs" in update && update.logs) {
|
|
28
30
|
update.logs.forEach((log) => {
|
|
29
|
-
console.
|
|
31
|
+
console.error(`[${modelName}] ${log.message}`);
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
} else if (update.status === "IN_QUEUE") {
|
|
33
|
-
console.
|
|
35
|
+
console.error(
|
|
36
|
+
`[${modelName}] Status: ${update.status} - Waiting in queue...`
|
|
37
|
+
);
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
console.
|
|
42
|
+
console.error(`[${modelName}] Generation completed successfully`);
|
|
39
43
|
|
|
40
44
|
return result;
|
|
41
45
|
} catch (error: any) {
|
|
42
46
|
console.error(`[${modelName}] Error:`, error);
|
|
43
|
-
throw new Error(
|
|
47
|
+
throw new Error(
|
|
48
|
+
`FAL AI ${modelName} generation failed: ${
|
|
49
|
+
error.message || JSON.stringify(error)
|
|
50
|
+
}`
|
|
51
|
+
);
|
|
44
52
|
}
|
|
45
53
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import { getStorage } from "../storage";
|
|
5
|
+
import { generateTimestampedFilename } from "./filename";
|
|
6
|
+
|
|
7
|
+
export interface LocalFileResult {
|
|
8
|
+
path: string;
|
|
9
|
+
isTemp: boolean;
|
|
10
|
+
cleanup: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ensures the input is a local file path.
|
|
15
|
+
* If input is a URL, downloads it to a temporary file.
|
|
16
|
+
* If input is a file path, returns it as is (resolving if necessary).
|
|
17
|
+
*/
|
|
18
|
+
export async function ensureLocalFile(input: string): Promise<LocalFileResult> {
|
|
19
|
+
const isUrl = input.startsWith("http://") || input.startsWith("https://");
|
|
20
|
+
|
|
21
|
+
if (isUrl) {
|
|
22
|
+
const tempDir = os.tmpdir();
|
|
23
|
+
// Use a timestamped filename to avoid collisions
|
|
24
|
+
// Try to guess extension from URL or default to bin/tmp
|
|
25
|
+
const urlPath = new URL(input).pathname;
|
|
26
|
+
const ext = path.extname(urlPath) || ".tmp";
|
|
27
|
+
// We'll use a simple name since generateTimestampedFilename expects a file path
|
|
28
|
+
const safeName = `mcp_download_${Date.now()}${ext}`;
|
|
29
|
+
const tempFilePath = path.join(tempDir, safeName);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(input);
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Failed to download URL: ${input} (${response.status} ${response.statusText})`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
40
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
41
|
+
fs.writeFileSync(tempFilePath, buffer);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
path: tempFilePath,
|
|
45
|
+
isTemp: true,
|
|
46
|
+
cleanup: () => {
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(tempFilePath)) {
|
|
49
|
+
fs.unlinkSync(tempFilePath);
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(`Failed to cleanup temp file ${tempFilePath}:`, e);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
// If download failed, clean up if we created the file
|
|
58
|
+
try {
|
|
59
|
+
if (fs.existsSync(tempFilePath)) {
|
|
60
|
+
fs.unlinkSync(tempFilePath);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
throw new Error(`Failed to process URL ${input}: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// It's a local file path (or at least we treat it as one)
|
|
67
|
+
// We verify existence using our storage abstraction or fs
|
|
68
|
+
const storage = getStorage();
|
|
69
|
+
const exists = await storage.exists(input);
|
|
70
|
+
|
|
71
|
+
if (!exists) {
|
|
72
|
+
// Try to provide more helpful error information
|
|
73
|
+
const isAbsolute = path.isAbsolute(input);
|
|
74
|
+
const resolvedPath = isAbsolute
|
|
75
|
+
? input
|
|
76
|
+
: path.resolve(process.cwd(), input);
|
|
77
|
+
throw new Error(
|
|
78
|
+
`File not found: ${input}\n` +
|
|
79
|
+
`Resolved path: ${resolvedPath}\n` +
|
|
80
|
+
`Is absolute: ${isAbsolute}\n` +
|
|
81
|
+
`CWD: ${process.cwd()}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
path: input,
|
|
87
|
+
isTemp: false,
|
|
88
|
+
cleanup: () => {}, // No-op for existing local files
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|