@vectorasystems/cli 0.2.1 → 0.2.3
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
CHANGED
|
@@ -7,6 +7,47 @@ import { getConfig, getConfigValue } from '../lib/config-store.js';
|
|
|
7
7
|
import { handleError } from '../lib/errors.js';
|
|
8
8
|
import { renderTable, renderJson, renderTime, warn, info, success } from '../lib/output.js';
|
|
9
9
|
|
|
10
|
+
function extensionForContentType(contentType) {
|
|
11
|
+
const normalized = String(contentType || '').toLowerCase();
|
|
12
|
+
if (normalized.includes('markdown')) return 'md';
|
|
13
|
+
if (normalized.includes('json')) return 'json';
|
|
14
|
+
if (normalized.startsWith('text/')) return 'txt';
|
|
15
|
+
return 'bin';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stripKnownExtensions(filename) {
|
|
19
|
+
let base = String(filename || '').trim();
|
|
20
|
+
while (/\.(md|markdown|json|txt|text|bin)$/i.test(base)) {
|
|
21
|
+
base = base.replace(/\.(md|markdown|json|txt|text|bin)$/i, '');
|
|
22
|
+
}
|
|
23
|
+
return base;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fallbackFilename(artifact) {
|
|
27
|
+
const extension = extensionForContentType(artifact?.contentType);
|
|
28
|
+
if (artifact?.type === 'prd') return `PRD.${extension}`;
|
|
29
|
+
if (artifact?.type === 'roadmap-markdown') return `ROADMAP.${extension}`;
|
|
30
|
+
if (artifact?.type === 'validation-report-markdown') return `VALIDATION_REPORT.${extension}`;
|
|
31
|
+
if (artifact?.type === 'architecture-map') return `ARCHITECTURE_MAP.${extension}`;
|
|
32
|
+
if (artifact?.type === 'handoff-generic') return `VECTORA.${extension}`;
|
|
33
|
+
if (String(artifact?.type || '').startsWith('handoff-')) {
|
|
34
|
+
const target = artifact.type.slice('handoff-'.length).replace(/[^a-z0-9]+/gi, '_').toUpperCase();
|
|
35
|
+
return `VECTORA_${target}.${extension}`;
|
|
36
|
+
}
|
|
37
|
+
return `${artifact?.type || 'artifact'}-v${artifact?.version ?? 1}.${extension}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeFilename(filename, artifact) {
|
|
41
|
+
const extension = extensionForContentType(artifact?.contentType);
|
|
42
|
+
const fallback = fallbackFilename(artifact);
|
|
43
|
+
const trimmed = String(filename || '').trim();
|
|
44
|
+
if (!trimmed) return fallback;
|
|
45
|
+
|
|
46
|
+
const base = stripKnownExtensions(trimmed);
|
|
47
|
+
if (!base) return fallback;
|
|
48
|
+
return `${base}.${extension}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
10
51
|
/**
|
|
11
52
|
* vectora artifacts list [--project <id>] [--type <type>]
|
|
12
53
|
*/
|
|
@@ -100,10 +141,10 @@ export async function download(id, opts) {
|
|
|
100
141
|
return;
|
|
101
142
|
}
|
|
102
143
|
|
|
144
|
+
const artifact = await getArtifact(id);
|
|
103
145
|
// Get presigned URL + suggested filename from API
|
|
104
146
|
const { url, filename, inlineBase64 } = await getArtifactDownloadUrl(id);
|
|
105
|
-
|
|
106
|
-
const outPath = resolve(opts.out ?? filename);
|
|
147
|
+
const outPath = resolve(opts.out ?? normalizeFilename(filename, artifact));
|
|
107
148
|
|
|
108
149
|
// Local-storage fallback: API returns the file bytes inline when no
|
|
109
150
|
// presigned URL backend is configured.
|
package/src/lib/sse-client.js
CHANGED
|
@@ -48,15 +48,34 @@ export async function* parseSseStream(body) {
|
|
|
48
48
|
* @yields {{ event: string, data: object }}
|
|
49
49
|
*/
|
|
50
50
|
export async function* streamPhaseProgress(baseUrl, jobId, token) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
51
|
+
while (true) {
|
|
52
|
+
const res = await fetch(`${baseUrl}/v1/phases/${jobId}?token=${token}`);
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
if (!res.ok || !res.body) {
|
|
55
|
+
throw new Error(`Phase SSE failed: HTTP ${res.status}`);
|
|
56
|
+
}
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
let reconnect = false;
|
|
59
|
+
|
|
60
|
+
for await (const frame of parseSseStream(res.body)) {
|
|
61
|
+
if (frame.event === 'job:timeout') {
|
|
62
|
+
reconnect = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
yield frame;
|
|
67
|
+
|
|
68
|
+
if (frame.event === 'job:completed' || frame.event === 'job:failed') {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!reconnect) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
78
|
+
}
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
/**
|
|
@@ -7,15 +7,46 @@ import { writeFile } from 'node:fs/promises';
|
|
|
7
7
|
import { resolve } from 'node:path';
|
|
8
8
|
import { getProjectArtifacts, getArtifactDownloadUrl } from '../../lib/api-client.js';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
function extensionForContentType(contentType) {
|
|
11
|
+
const normalized = String(contentType || '').toLowerCase();
|
|
12
|
+
if (normalized.includes('markdown')) return 'md';
|
|
13
|
+
if (normalized.includes('json')) return 'json';
|
|
14
|
+
if (normalized.startsWith('text/')) return 'txt';
|
|
15
|
+
return 'bin';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stripKnownExtensions(filename) {
|
|
19
|
+
let base = String(filename || '').trim();
|
|
20
|
+
while (/\.(md|markdown|json|txt|text|bin)$/i.test(base)) {
|
|
21
|
+
base = base.replace(/\.(md|markdown|json|txt|text|bin)$/i, '');
|
|
22
|
+
}
|
|
23
|
+
return base;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fallbackFilename(artifact) {
|
|
27
|
+
const extension = extensionForContentType(artifact?.contentType);
|
|
28
|
+
if (artifact?.type === 'prd') return `PRD.${extension}`;
|
|
29
|
+
if (artifact?.type === 'roadmap-markdown') return `ROADMAP.${extension}`;
|
|
30
|
+
if (artifact?.type === 'validation-report-markdown') return `VALIDATION_REPORT.${extension}`;
|
|
31
|
+
if (artifact?.type === 'architecture-map') return `ARCHITECTURE_MAP.${extension}`;
|
|
32
|
+
if (artifact?.type === 'handoff-generic') return `VECTORA.${extension}`;
|
|
33
|
+
if (String(artifact?.type || '').startsWith('handoff-')) {
|
|
34
|
+
const target = artifact.type.slice('handoff-'.length).replace(/[^a-z0-9]+/gi, '_').toUpperCase();
|
|
35
|
+
return `VECTORA_${target}.${extension}`;
|
|
36
|
+
}
|
|
37
|
+
return `${artifact?.type || 'artifact'}-v${artifact?.version ?? 1}.${extension}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeFilename(filename, artifact) {
|
|
41
|
+
const extension = extensionForContentType(artifact?.contentType);
|
|
42
|
+
const fallback = fallbackFilename(artifact);
|
|
43
|
+
const trimmed = String(filename || '').trim();
|
|
44
|
+
if (!trimmed) return fallback;
|
|
45
|
+
|
|
46
|
+
const base = stripKnownExtensions(trimmed);
|
|
47
|
+
if (!base) return fallback;
|
|
48
|
+
return `${base}.${extension}`;
|
|
49
|
+
}
|
|
19
50
|
|
|
20
51
|
function formatDate(iso) {
|
|
21
52
|
return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
@@ -43,8 +74,9 @@ export function ArtifactBrowser({ projectId, onBack }) {
|
|
|
43
74
|
setSavedPath(null);
|
|
44
75
|
setError(null);
|
|
45
76
|
try {
|
|
77
|
+
const artifact = artifacts.find((entry) => entry.id === item.value);
|
|
46
78
|
const { url, filename, inlineBase64 } = await getArtifactDownloadUrl(item.value);
|
|
47
|
-
const outPath = resolve(filename);
|
|
79
|
+
const outPath = resolve(normalizeFilename(filename, artifact));
|
|
48
80
|
|
|
49
81
|
// Local-storage fallback: the API can return inline bytes when no
|
|
50
82
|
// presigned URL backend is available.
|