@coreviz/cli 1.0.0 → 1.0.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/README.md +57 -4
- package/bin/cli.js +434 -16
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -12,16 +12,69 @@ npm install -g @coreviz/cli
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# Run directly with npx
|
|
15
|
-
npx @coreviz/cli
|
|
15
|
+
npx @coreviz/cli [command]
|
|
16
16
|
|
|
17
17
|
# Or if installed globally
|
|
18
|
-
coreviz
|
|
18
|
+
coreviz [command]
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
### Authentication
|
|
24
|
+
|
|
25
|
+
Login to CoreViz using device authorization:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
coreviz login
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Logout:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
coreviz logout
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Check login status:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
coreviz whoami
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### AI Features
|
|
44
|
+
|
|
45
|
+
Describe an image:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
coreviz describe path/to/image.jpg
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Edit an image with a text prompt:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
coreviz edit path/to/image.jpg --prompt "make it cyberpunk style"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Search local images using natural language:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
coreviz search "a document with a red header"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This will index the images in your current directory (creating a `.index.db` file) and return the top matches for your query.
|
|
64
|
+
|
|
21
65
|
## Development
|
|
22
66
|
|
|
23
|
-
|
|
67
|
+
1. Install dependencies:
|
|
68
|
+
```bash
|
|
69
|
+
cd cli
|
|
70
|
+
npm install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
2. Run local CLI:
|
|
74
|
+
```bash
|
|
75
|
+
node bin/cli.js --help
|
|
76
|
+
```
|
|
24
77
|
|
|
25
78
|
## License
|
|
26
79
|
|
|
27
|
-
MIT
|
|
80
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -1,18 +1,436 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createAuthClient } from "better-auth/client";
|
|
4
|
+
import { deviceAuthorizationClient } from "better-auth/client/plugins";
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import Conf from 'conf';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import process from 'process';
|
|
9
|
+
import { intro, outro, confirm, isCancel, cancel, text } from '@clack/prompts';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import yoctoSpinner from 'yocto-spinner';
|
|
12
|
+
import { CoreViz } from '@coreviz/sdk';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import Database from 'better-sqlite3';
|
|
2
16
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
dotenv.config();
|
|
18
|
+
|
|
19
|
+
const config = new Conf({ projectName: 'coreviz-cli' });
|
|
20
|
+
const program = new Command();
|
|
21
|
+
|
|
22
|
+
const authClient = createAuthClient({
|
|
23
|
+
baseURL: "https://lab.coreviz.io",
|
|
24
|
+
plugins: [
|
|
25
|
+
deviceAuthorizationClient()
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.name('coreviz')
|
|
31
|
+
.description('CoreViz CLI')
|
|
32
|
+
.version('1.0.1');
|
|
33
|
+
|
|
34
|
+
program.command('login')
|
|
35
|
+
.description('Login to CoreViz using device authorization')
|
|
36
|
+
.action(async () => {
|
|
37
|
+
intro(chalk.bgHex('#663399').white('CoreViz'));
|
|
38
|
+
|
|
39
|
+
const session = config.get('session');
|
|
40
|
+
if (session) {
|
|
41
|
+
const shouldReauth = await confirm({
|
|
42
|
+
message: "You're already logged in. Do you want to log in again?",
|
|
43
|
+
initialValue: false,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (isCancel(shouldReauth) || !shouldReauth) {
|
|
47
|
+
cancel("Login cancelled.");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const spinner = yoctoSpinner({ text: "Requesting device authorization..." });
|
|
53
|
+
spinner.start();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { data, error } = await authClient.device.code({
|
|
57
|
+
client_id: "coreviz-cli",
|
|
58
|
+
scope: "openid profile email",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
spinner.stop();
|
|
62
|
+
|
|
63
|
+
if (error) {
|
|
64
|
+
cancel(`Failed to request device authorization: ${error.message || error}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!data) {
|
|
69
|
+
cancel('No data received from server.');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { verification_uri, user_code, device_code, interval = 5, expires_in } = data;
|
|
74
|
+
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log(chalk.cyan("📱 Device Authorization Required"));
|
|
77
|
+
console.log("");
|
|
78
|
+
console.log(`Please visit: ${chalk.underline.blue(verification_uri)}`);
|
|
79
|
+
console.log(`Enter code: ${chalk.bold.green(user_code)}`);
|
|
80
|
+
console.log("");
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await open(verification_uri);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.log(chalk.yellow("Could not open browser automatically."));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.gray(`Waiting for authorization (expires in ${Math.floor(expires_in / 60)} minutes)...`));
|
|
89
|
+
|
|
90
|
+
const tokenData = await pollForToken(device_code, interval);
|
|
91
|
+
|
|
92
|
+
if (tokenData) {
|
|
93
|
+
config.set('session', tokenData);
|
|
94
|
+
|
|
95
|
+
// Fetch user info to display name
|
|
96
|
+
const { data: sessionData } = await authClient.getSession({
|
|
97
|
+
fetchOptions: {
|
|
98
|
+
headers: {
|
|
99
|
+
Authorization: `Bearer ${tokenData.access_token}`,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
outro(chalk.green(`✅ Login successful! Logged in as ${sessionData?.user?.name || sessionData?.user?.email || 'User'}`));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
} catch (e) {
|
|
108
|
+
spinner.stop();
|
|
109
|
+
cancel(`An unexpected error occurred: ${e.message}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
async function pollForToken(deviceCode, initialInterval) {
|
|
115
|
+
let pollingInterval = initialInterval;
|
|
116
|
+
const spinner = yoctoSpinner({ text: "Polling for authorization..." });
|
|
117
|
+
spinner.start();
|
|
118
|
+
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const poll = async () => {
|
|
121
|
+
try {
|
|
122
|
+
const { data, error } = await authClient.device.token({
|
|
123
|
+
client_id: "coreviz-cli",
|
|
124
|
+
device_code: deviceCode,
|
|
125
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (data) {
|
|
129
|
+
spinner.stop();
|
|
130
|
+
resolve(data);
|
|
131
|
+
return;
|
|
132
|
+
} else if (error) {
|
|
133
|
+
switch (error.error) {
|
|
134
|
+
case "authorization_pending":
|
|
135
|
+
// Continue polling
|
|
136
|
+
break;
|
|
137
|
+
case "slow_down":
|
|
138
|
+
pollingInterval += 5;
|
|
139
|
+
spinner.text = chalk.yellow(`Slowing down polling to ${pollingInterval}s...`);
|
|
140
|
+
break;
|
|
141
|
+
case "access_denied":
|
|
142
|
+
spinner.stop();
|
|
143
|
+
cancel("Access was denied by the user.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
break;
|
|
146
|
+
case "expired_token":
|
|
147
|
+
spinner.stop();
|
|
148
|
+
cancel("The device code has expired. Please try again.");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
// Ignore unknown errors and keep polling? Or fail?
|
|
153
|
+
// Better-auth might return other errors.
|
|
154
|
+
if (!['authorization_pending', 'slow_down'].includes(error.error)) {
|
|
155
|
+
spinner.stop();
|
|
156
|
+
cancel(`Error: ${error.error_description || error.message}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
spinner.stop();
|
|
164
|
+
cancel(`Network error: ${err.message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
setTimeout(poll, pollingInterval * 1000);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
setTimeout(poll, pollingInterval * 1000);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
program.command('logout')
|
|
176
|
+
.description('Logout')
|
|
177
|
+
.action(() => {
|
|
178
|
+
intro(chalk.bgHex('#663399').white('CoreViz'));
|
|
179
|
+
config.clear();
|
|
180
|
+
outro(chalk.green('Logged out successfully.'));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
program.command('whoami')
|
|
184
|
+
.description('Show current user')
|
|
185
|
+
.action(() => {
|
|
186
|
+
intro(chalk.bgHex('#663399').white('CoreViz'));
|
|
187
|
+
const session = config.get('session');
|
|
188
|
+
if (session && (session.user || session.access_token)) {
|
|
189
|
+
const userDisplay = session.user
|
|
190
|
+
? `${session.user.name} (${session.user.email})`
|
|
191
|
+
: 'Authenticated User (Token only)';
|
|
192
|
+
outro(chalk.green(`Logged in as: ${userDisplay}`));
|
|
193
|
+
} else {
|
|
194
|
+
outro(chalk.yellow('Not logged in.'));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
program.command('edit <image-path>')
|
|
199
|
+
.description('Edit an image using AI')
|
|
200
|
+
.option('-p, --prompt <prompt>', 'Text description of the desired edit')
|
|
201
|
+
.action(async (imagePath, options) => {
|
|
202
|
+
intro(chalk.bgHex('#663399').white('CoreViz'));
|
|
203
|
+
|
|
204
|
+
const session = config.get('session');
|
|
205
|
+
if (!session || !session.access_token) {
|
|
206
|
+
cancel('You are not logged in. Please run `coreviz login` first.');
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!fs.existsSync(imagePath)) {
|
|
211
|
+
cancel(`File not found: ${imagePath}`);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let prompt = options.prompt;
|
|
216
|
+
if (!prompt) {
|
|
217
|
+
prompt = await text({
|
|
218
|
+
message: 'What would you like to change in the image?',
|
|
219
|
+
placeholder: 'e.g., "Make it look like a painting" or "Add a red hat"',
|
|
220
|
+
validate(value) {
|
|
221
|
+
if (value.length === 0) return `Value is required!`;
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (isCancel(prompt)) {
|
|
226
|
+
cancel('Operation cancelled.');
|
|
227
|
+
process.exit(0);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const spinner = yoctoSpinner({ text: "Processing image..." });
|
|
232
|
+
spinner.start();
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const base64Image = readImageAsBase64(imagePath);
|
|
236
|
+
|
|
237
|
+
const coreviz = new CoreViz({ token: session.access_token });
|
|
238
|
+
const resultBase64 = await coreviz.edit(base64Image, {
|
|
239
|
+
prompt
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
spinner.stop();
|
|
243
|
+
|
|
244
|
+
// Save result
|
|
245
|
+
const outputFilename = `edited-${Date.now()}-${path.basename(imagePath)}`;
|
|
246
|
+
const outputBuffer = Buffer.from(resultBase64.replace(/^data:image\/\w+;base64,/, ""), 'base64');
|
|
247
|
+
fs.writeFileSync(outputFilename, outputBuffer);
|
|
248
|
+
|
|
249
|
+
outro(chalk.green(`✅ Image edited successfully! Saved as ${outputFilename}`));
|
|
250
|
+
|
|
251
|
+
} catch (error) {
|
|
252
|
+
spinner.stop();
|
|
253
|
+
cancel(`Failed to edit image: ${error.message}`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
program.command('describe <image-path>')
|
|
259
|
+
.description('Describe an image using AI')
|
|
260
|
+
.action(async (imagePath) => {
|
|
261
|
+
intro(chalk.bgHex('#663399').white('CoreViz'));
|
|
262
|
+
|
|
263
|
+
const session = config.get('session');
|
|
264
|
+
if (!session || !session.access_token) {
|
|
265
|
+
cancel('You are not logged in. Please run `coreviz login` first.');
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!fs.existsSync(imagePath)) {
|
|
270
|
+
cancel(`File not found: ${imagePath}`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const spinner = yoctoSpinner({ text: "Analyzing image..." });
|
|
275
|
+
spinner.start();
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const base64Image = readImageAsBase64(imagePath);
|
|
279
|
+
const coreviz = new CoreViz({ token: session.access_token });
|
|
280
|
+
const description = await coreviz.describe(base64Image);
|
|
281
|
+
|
|
282
|
+
spinner.stop();
|
|
283
|
+
|
|
284
|
+
outro(chalk.green('✅ Image description:'));
|
|
285
|
+
console.log(description);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
spinner.stop();
|
|
288
|
+
if (error.message === 'Insufficient credits') {
|
|
289
|
+
cancel('Insufficient credits. Please add credits to your account.');
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
cancel(`Failed to describe image: ${error.message}`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
program.command('search <query>')
|
|
298
|
+
.description('Search for images in the current directory using AI')
|
|
299
|
+
.action(async (query) => {
|
|
300
|
+
intro(chalk.bgHex('#663399').white('CoreViz'));
|
|
301
|
+
|
|
302
|
+
const session = config.get('session');
|
|
303
|
+
if (!session || !session.access_token) {
|
|
304
|
+
cancel('You are not logged in. Please run `coreviz login` first.');
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const spinner = yoctoSpinner({ text: "Indexing directory..." });
|
|
309
|
+
spinner.start();
|
|
310
|
+
|
|
311
|
+
const dbPath = path.join(process.cwd(), '.index.db');
|
|
312
|
+
const db = new Database(dbPath);
|
|
313
|
+
|
|
314
|
+
// Initialize DB
|
|
315
|
+
db.prepare(`
|
|
316
|
+
CREATE TABLE IF NOT EXISTS images (
|
|
317
|
+
path TEXT PRIMARY KEY,
|
|
318
|
+
mtime REAL,
|
|
319
|
+
embedding TEXT
|
|
320
|
+
)
|
|
321
|
+
`).run();
|
|
322
|
+
|
|
323
|
+
const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp', '.tiff'];
|
|
324
|
+
const files = fs.readdirSync(process.cwd())
|
|
325
|
+
.filter(file => imageExtensions.includes(path.extname(file).toLowerCase()));
|
|
326
|
+
|
|
327
|
+
if (files.length === 0) {
|
|
328
|
+
spinner.stop();
|
|
329
|
+
cancel('No images found in the current directory.');
|
|
330
|
+
process.exit(0);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const coreviz = new CoreViz({ token: session.access_token });
|
|
334
|
+
|
|
335
|
+
// Prepare statements
|
|
336
|
+
const getFile = db.prepare('SELECT mtime FROM images WHERE path = ?');
|
|
337
|
+
const upsertFile = db.prepare('INSERT OR REPLACE INTO images (path, mtime, embedding) VALUES (?, ?, ?)');
|
|
338
|
+
const deleteFile = db.prepare('DELETE FROM images WHERE path = ?');
|
|
339
|
+
|
|
340
|
+
// Clean up deleted files from index
|
|
341
|
+
const allIndexedFiles = db.prepare('SELECT path FROM images').all();
|
|
342
|
+
for (const row of allIndexedFiles) {
|
|
343
|
+
if (!files.includes(row.path)) {
|
|
344
|
+
deleteFile.run(row.path);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (const file of files) {
|
|
349
|
+
const filePath = path.join(process.cwd(), file);
|
|
350
|
+
const stats = fs.statSync(filePath);
|
|
351
|
+
const mtime = stats.mtimeMs;
|
|
352
|
+
|
|
353
|
+
const existing = getFile.get(file);
|
|
354
|
+
|
|
355
|
+
// Skip if already indexed and not modified
|
|
356
|
+
if (existing && existing.mtime === mtime) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
spinner.text = `Indexing ${file}...`;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const base64Image = readImageAsBase64(filePath);
|
|
364
|
+
const { embedding } = await coreviz.embed(base64Image, { type: 'image' });
|
|
365
|
+
|
|
366
|
+
upsertFile.run(file, mtime, JSON.stringify(embedding));
|
|
367
|
+
} catch (error) {
|
|
368
|
+
// Log error but continue
|
|
369
|
+
console.error(`Failed to index ${file}: ${error.message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
spinner.text = "Processing search query...";
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const { embedding: queryEmbedding } = await coreviz.embed(query, { type: 'text' });
|
|
377
|
+
|
|
378
|
+
const rows = db.prepare('SELECT path, embedding FROM images').all();
|
|
379
|
+
const results = [];
|
|
380
|
+
|
|
381
|
+
for (const row of rows) {
|
|
382
|
+
if (!row.embedding) continue;
|
|
383
|
+
|
|
384
|
+
const fileEmbedding = JSON.parse(row.embedding);
|
|
385
|
+
|
|
386
|
+
// Calculate cosine similarity
|
|
387
|
+
const similarity = cosineSimilarity(queryEmbedding, fileEmbedding);
|
|
388
|
+
results.push({ file: row.path, similarity });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Sort by similarity descending
|
|
392
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
393
|
+
|
|
394
|
+
spinner.stop();
|
|
395
|
+
|
|
396
|
+
outro(chalk.green(`✅ Search results for "${query}"`));
|
|
397
|
+
|
|
398
|
+
// Show top 5 results
|
|
399
|
+
results.slice(0, 5).forEach((result, i) => {
|
|
400
|
+
const score = (result.similarity * 100).toFixed(1);
|
|
401
|
+
console.log(`${i + 1}. ${chalk.bold(result.file)} ${chalk.gray(`(${score}%)`)}`);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
} catch (error) {
|
|
405
|
+
spinner.stop();
|
|
406
|
+
cancel(`Search failed: ${error.message}`);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
} finally {
|
|
409
|
+
db.close();
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
function cosineSimilarity(vecA, vecB) {
|
|
414
|
+
if (vecA.length !== vecB.length) return 0;
|
|
415
|
+
|
|
416
|
+
let dotProduct = 0;
|
|
417
|
+
let normA = 0;
|
|
418
|
+
let normB = 0;
|
|
419
|
+
|
|
420
|
+
for (let i = 0; i < vecA.length; i++) {
|
|
421
|
+
dotProduct += vecA[i] * vecB[i];
|
|
422
|
+
normA += vecA[i] * vecA[i];
|
|
423
|
+
normB += vecB[i] * vecB[i];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (normA === 0 || normB === 0) return 0;
|
|
427
|
+
|
|
428
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function readImageAsBase64(imagePath) {
|
|
432
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
433
|
+
return `data:image/${path.extname(imagePath).slice(1) || 'jpeg'};base64,${imageBuffer.toString('base64')}`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreviz/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "CoreViz CLI tool",
|
|
5
6
|
"main": "index.js",
|
|
6
7
|
"bin": {
|
|
@@ -23,5 +24,17 @@
|
|
|
23
24
|
"bugs": {
|
|
24
25
|
"url": "https://github.com/CoreViz/cli/issues"
|
|
25
26
|
},
|
|
26
|
-
"homepage": "https://github.com/CoreViz/cli#readme"
|
|
27
|
+
"homepage": "https://github.com/CoreViz/cli#readme",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@clack/prompts": "^0.11.0",
|
|
30
|
+
"@coreviz/sdk": "^1.0.3",
|
|
31
|
+
"better-auth": "^1.4.2",
|
|
32
|
+
"better-sqlite3": "^12.4.6",
|
|
33
|
+
"chalk": "^5.6.2",
|
|
34
|
+
"commander": "^14.0.2",
|
|
35
|
+
"conf": "^15.0.2",
|
|
36
|
+
"dotenv": "^17.2.3",
|
|
37
|
+
"open": "^11.0.0",
|
|
38
|
+
"yocto-spinner": "^1.0.0"
|
|
39
|
+
}
|
|
27
40
|
}
|