vigthoria-cli 1.13.18 → 1.13.22
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/completions/_vigthoria +49 -0
- package/completions/vigthoria.bash +6 -0
- package/completions/vigthoria.fish +41 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +84 -135
- package/dist/commands/background.d.ts +0 -2
- package/dist/commands/background.js +26 -34
- package/dist/commands/bridge.js +45 -41
- package/dist/commands/cancel.d.ts +1 -0
- package/dist/commands/cancel.js +22 -16
- package/dist/commands/chat.d.ts +5 -42
- package/dist/commands/chat.js +462 -1122
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +32 -42
- package/dist/commands/conversation-registration.d.ts +4 -0
- package/dist/commands/conversation-registration.js +122 -0
- package/dist/commands/deploy.d.ts +8 -2
- package/dist/commands/deploy.js +92 -90
- package/dist/commands/device.d.ts +1 -2
- package/dist/commands/device.js +37 -21
- package/dist/commands/doctor-registration.d.ts +4 -0
- package/dist/commands/doctor-registration.js +200 -0
- package/dist/commands/edit.js +22 -33
- package/dist/commands/explain.js +7 -10
- package/dist/commands/fork.d.ts +1 -2
- package/dist/commands/fork.js +72 -22
- package/dist/commands/game.d.ts +1 -1
- package/dist/commands/game.js +17 -6
- package/dist/commands/generate.js +7 -9
- package/dist/commands/history.js +11 -7
- package/dist/commands/hub.d.ts +2 -2
- package/dist/commands/hub.js +34 -35
- package/dist/commands/legion.d.ts +0 -1
- package/dist/commands/legion.js +62 -79
- package/dist/commands/music.d.ts +1 -2
- package/dist/commands/music.js +62 -27
- package/dist/commands/platform-registration.d.ts +4 -0
- package/dist/commands/platform-registration.js +45 -0
- package/dist/commands/preview.d.ts +0 -1
- package/dist/commands/preview.js +82 -35
- package/dist/commands/product-run-registration.d.ts +4 -0
- package/dist/commands/product-run-registration.js +490 -0
- package/dist/commands/replay.d.ts +1 -2
- package/dist/commands/replay.js +15 -11
- package/dist/commands/repo.d.ts +14 -7
- package/dist/commands/repo.js +245 -196
- package/dist/commands/review.js +9 -12
- package/dist/commands/security.d.ts +1 -2
- package/dist/commands/security.js +12 -6
- package/dist/commands/update-registration.d.ts +3 -0
- package/dist/commands/update-registration.js +338 -0
- package/dist/commands/v4-registration.d.ts +4 -0
- package/dist/commands/v4-registration.js +24 -0
- package/dist/commands/v4.d.ts +1 -1
- package/dist/commands/v4.js +46 -8
- package/dist/commands/wallet.d.ts +1 -3
- package/dist/commands/wallet.js +18 -45
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +52 -39
- package/dist/index.js +122 -1498
- package/dist/utils/agent-stream-state.d.ts +27 -0
- package/dist/utils/agent-stream-state.js +93 -0
- package/dist/utils/agentRoute.d.ts +2 -1
- package/dist/utils/agentRoute.js +22 -9
- package/dist/utils/agentRunOutcome.d.ts +16 -0
- package/dist/utils/agentRunOutcome.js +37 -2
- package/dist/utils/api-client-factory.d.ts +8 -0
- package/dist/utils/api-client-factory.js +8 -0
- package/dist/utils/api.d.ts +45 -269
- package/dist/utils/api.js +664 -3210
- package/dist/utils/auth-session.d.ts +41 -0
- package/dist/utils/auth-session.js +224 -0
- package/dist/utils/brain-hub-client.d.ts +0 -4
- package/dist/utils/brain-hub-client.js +6 -3
- package/dist/utils/bridge-client.js +2 -0
- package/dist/utils/capability-health-service.d.ts +48 -0
- package/dist/utils/capability-health-service.js +64 -0
- package/dist/utils/chat-prompt-policy.d.ts +18 -0
- package/dist/utils/chat-prompt-policy.js +61 -0
- package/dist/utils/chat-tool-call-parser.d.ts +11 -0
- package/dist/utils/chat-tool-call-parser.js +71 -0
- package/dist/utils/cli-state.d.ts +22 -4
- package/dist/utils/cli-state.js +105 -47
- package/dist/utils/code-operations-service.d.ts +104 -0
- package/dist/utils/code-operations-service.js +837 -0
- package/dist/utils/codebase-indexer.d.ts +1 -0
- package/dist/utils/codebase-indexer.js +28 -8
- package/dist/utils/command-contract.d.ts +61 -0
- package/dist/utils/command-contract.js +149 -0
- package/dist/utils/command-policy.d.ts +10 -0
- package/dist/utils/command-policy.js +54 -0
- package/dist/utils/config.d.ts +13 -5
- package/dist/utils/config.js +227 -99
- package/dist/utils/context-ranker.js +11 -2
- package/dist/utils/context-state.d.ts +39 -0
- package/dist/utils/context-state.js +52 -0
- package/dist/utils/deckEvents.d.ts +0 -5
- package/dist/utils/deckEvents.js +3 -2
- package/dist/utils/desktop-bridge-client.js +2 -0
- package/dist/utils/direct-output-policy.d.ts +3 -0
- package/dist/utils/direct-output-policy.js +76 -0
- package/dist/utils/durable-json.d.ts +57 -0
- package/dist/utils/durable-json.js +319 -0
- package/dist/utils/fastAgentRouter.d.ts +6 -7
- package/dist/utils/fastAgentRouter.js +21 -30
- package/dist/utils/files.js +21 -13
- package/dist/utils/frontend-preview-service.d.ts +76 -0
- package/dist/utils/frontend-preview-service.js +482 -0
- package/dist/utils/goaEvents.d.ts +0 -4
- package/dist/utils/goaEvents.js +5 -2
- package/dist/utils/logger.d.ts +2 -1
- package/dist/utils/logger.js +27 -20
- package/dist/utils/mcp-context-client.d.ts +24 -0
- package/dist/utils/mcp-context-client.js +58 -0
- package/dist/utils/model-governance.d.ts +18 -0
- package/dist/utils/model-governance.js +75 -0
- package/dist/utils/model-transport-service.d.ts +44 -0
- package/dist/utils/model-transport-service.js +111 -0
- package/dist/utils/mutation-journal.d.ts +42 -0
- package/dist/utils/mutation-journal.js +387 -0
- package/dist/utils/network-policy.d.ts +24 -0
- package/dist/utils/network-policy.js +202 -0
- package/dist/utils/operator-client.d.ts +45 -0
- package/dist/utils/operator-client.js +159 -0
- package/dist/utils/post-write-validator.js +7 -9
- package/dist/utils/preview-screenshot-adapter.d.ts +13 -0
- package/dist/utils/preview-screenshot-adapter.js +36 -0
- package/dist/utils/process-policy.d.ts +23 -0
- package/dist/utils/process-policy.js +284 -0
- package/dist/utils/project-memory.d.ts +13 -3
- package/dist/utils/project-memory.js +70 -17
- package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
- package/dist/utils/prompt-workspace-resolver.js +64 -0
- package/dist/utils/release-install.d.ts +11 -0
- package/dist/utils/release-install.js +155 -0
- package/dist/utils/release-policy.d.ts +64 -0
- package/dist/utils/release-policy.js +293 -0
- package/dist/utils/requestIntent.js +22 -3
- package/dist/utils/runtime-capability.d.ts +8 -0
- package/dist/utils/runtime-capability.js +16 -0
- package/dist/utils/secret-policy.d.ts +20 -0
- package/dist/utils/secret-policy.js +160 -0
- package/dist/utils/session.d.ts +34 -2
- package/dist/utils/session.js +354 -110
- package/dist/utils/tool-approval-service.d.ts +17 -0
- package/dist/utils/tool-approval-service.js +78 -0
- package/dist/utils/tool-capability-providers.d.ts +57 -0
- package/dist/utils/tool-capability-providers.js +57 -0
- package/dist/utils/tools.d.ts +2 -23
- package/dist/utils/tools.js +216 -508
- package/dist/utils/update-policy.d.ts +18 -0
- package/dist/utils/update-policy.js +137 -0
- package/dist/utils/v3-agent-client.d.ts +40 -0
- package/dist/utils/v3-agent-client.js +165 -0
- package/dist/utils/v3-workspace-service.d.ts +28 -0
- package/dist/utils/v3-workspace-service.js +279 -0
- package/dist/utils/vigflow-client.d.ts +95 -0
- package/dist/utils/vigflow-client.js +105 -0
- package/dist/utils/workspace-boundary.d.ts +45 -0
- package/dist/utils/workspace-boundary.js +377 -0
- package/dist/utils/workspace-brain-service.d.ts +2 -0
- package/dist/utils/workspace-brain-service.js +11 -1
- package/dist/utils/workspace-cache.js +37 -15
- package/dist/utils/workspace-stream.js +35 -10
- package/install.ps1 +86 -143
- package/install.sh +76 -81
- package/package.json +44 -9
- package/release-policy.json +43 -0
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- package/scripts/release/generate-completions.mjs +24 -0
- package/scripts/release/install-release.mjs +10 -0
- package/scripts/release/publish-cli-release.mjs +116 -0
- package/scripts/release/publish-cli-release.sh +2 -70
- package/scripts/release/resolve-release-manifest.mjs +26 -0
- package/scripts/release/test-balanced-model-live.sh +42 -0
- package/scripts/release/validate-live-service-gates.sh +90 -0
- package/scripts/release/validate-no-go-gates.sh +135 -172
- package/scripts/release/verify-runtime-consistency.mjs +4 -1
- package/dist/commands/index.d.ts +0 -12
- package/dist/commands/index.js +0 -194
- package/dist/commands/update.d.ts +0 -9
- package/dist/commands/update.js +0 -301
- package/dist/utils/templateInstantPath.d.ts +0 -18
- package/dist/utils/templateInstantPath.js +0 -228
package/dist/commands/repo.js
CHANGED
|
@@ -16,11 +16,14 @@ import chalk from 'chalk';
|
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import { createRequire } from 'node:module';
|
|
19
|
+
import { guardedFetch } from '../utils/network-policy.js';
|
|
19
20
|
import { createSpinner, CH } from '../utils/logger.js';
|
|
21
|
+
import { createOperationId } from '../utils/mutation-journal.js';
|
|
22
|
+
import { containsHighConfidenceSecret, isSensitivePath, safeChildProcessEnv } from '../utils/secret-policy.js';
|
|
23
|
+
import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
|
|
24
|
+
import { assertSafeDestinationPath, assertSafeRelativePath, inspectZipArchive, mergeValidatedWorkspace, resolveWorkspacePath, validateExtractedWorkspace, } from '../utils/workspace-boundary.js';
|
|
20
25
|
const require = createRequire(import.meta.url);
|
|
21
26
|
import inquirer from 'inquirer';
|
|
22
|
-
import * as archiverModule from 'archiver';
|
|
23
|
-
import { createWriteStream } from 'fs';
|
|
24
27
|
const MAX_REPOSITORY_CONTENT_BYTES = 100 * 1024 * 1024;
|
|
25
28
|
const MAX_REPOSITORY_REQUEST_BYTES = 120 * 1024 * 1024;
|
|
26
29
|
class RepositoryApiError extends Error {
|
|
@@ -33,6 +36,24 @@ class RepositoryApiError extends Error {
|
|
|
33
36
|
this.payload = payload || {};
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
export class RepositoryMutationAmbiguousError extends Error {
|
|
40
|
+
operationId;
|
|
41
|
+
mutationOutcome = 'unknown';
|
|
42
|
+
partialMutation = 'unknown';
|
|
43
|
+
constructor(operationId, cause) {
|
|
44
|
+
super(`Repository mutation outcome is unknown for operation ${operationId}; no alternate backend retry was attempted.`);
|
|
45
|
+
this.name = 'RepositoryMutationAmbiguousError';
|
|
46
|
+
this.operationId = operationId;
|
|
47
|
+
if (cause instanceof Error)
|
|
48
|
+
this.cause = cause;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function canFallbackRepositoryRequest(method, status) {
|
|
52
|
+
const normalized = method.toUpperCase();
|
|
53
|
+
if (normalized === 'GET' || normalized === 'HEAD')
|
|
54
|
+
return true;
|
|
55
|
+
return status !== undefined && [401, 403, 404, 413].includes(status);
|
|
56
|
+
}
|
|
36
57
|
function formatBytes(bytes) {
|
|
37
58
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
38
59
|
}
|
|
@@ -49,24 +70,35 @@ async function readResponsePayload(response) {
|
|
|
49
70
|
}
|
|
50
71
|
export class RepoCommand {
|
|
51
72
|
config;
|
|
52
|
-
logger;
|
|
53
73
|
apiBase;
|
|
54
74
|
communityBase;
|
|
55
75
|
communityToken;
|
|
56
|
-
constructor(config,
|
|
76
|
+
constructor(config, _logger) {
|
|
57
77
|
this.config = config;
|
|
58
|
-
this.logger = logger;
|
|
59
78
|
this.apiBase = this.config.get('apiUrl') || 'https://coder.vigthoria.io';
|
|
60
79
|
this.communityBase = process.env.VIGTHORIA_COMMUNITY_API_URL || 'https://community.vigthoria.io';
|
|
61
80
|
this.communityToken = process.env.VIGTHORIA_COMMUNITY_TOKEN || null;
|
|
62
81
|
}
|
|
63
|
-
getAuthHeaders() {
|
|
64
|
-
const token =
|
|
82
|
+
getAuthHeaders(audience) {
|
|
83
|
+
const token = audience === 'coder'
|
|
84
|
+
? process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken')
|
|
85
|
+
: this.communityToken;
|
|
65
86
|
return {
|
|
66
|
-
'Authorization': `Bearer ${token}
|
|
67
|
-
'Content-Type': 'application/json'
|
|
87
|
+
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
|
88
|
+
'Content-Type': 'application/json',
|
|
68
89
|
};
|
|
69
90
|
}
|
|
91
|
+
getRepositoryAudienceForUrl(rawUrl) {
|
|
92
|
+
const hostname = new URL(rawUrl).hostname.toLowerCase();
|
|
93
|
+
if (hostname === 'coder.vigthoria.io')
|
|
94
|
+
return 'coder';
|
|
95
|
+
if (hostname === 'community.vigthoria.io')
|
|
96
|
+
return 'community';
|
|
97
|
+
throw new Error('Repository download URL is outside the Coder and Community trust boundary');
|
|
98
|
+
}
|
|
99
|
+
getAuthHeadersForUrl(rawUrl) {
|
|
100
|
+
return this.getAuthHeaders(this.getRepositoryAudienceForUrl(rawUrl));
|
|
101
|
+
}
|
|
70
102
|
async ensureCommunityAuth() {
|
|
71
103
|
if (this.communityToken) {
|
|
72
104
|
return;
|
|
@@ -76,58 +108,62 @@ export class RepoCommand {
|
|
|
76
108
|
if (!email || !password) {
|
|
77
109
|
return;
|
|
78
110
|
}
|
|
79
|
-
const response = await
|
|
111
|
+
const response = await guardedFetch(`${this.communityBase}/api/auth/login`, {
|
|
80
112
|
method: 'POST',
|
|
81
113
|
headers: { 'Content-Type': 'application/json' },
|
|
82
114
|
body: JSON.stringify({ email, password })
|
|
83
|
-
});
|
|
115
|
+
}, { audience: 'community' });
|
|
84
116
|
const data = await response.json();
|
|
85
117
|
if (!response.ok || !data.token) {
|
|
86
118
|
throw new Error(data.error || data.message || 'Community login failed');
|
|
87
119
|
}
|
|
88
120
|
this.communityToken = data.token;
|
|
89
121
|
}
|
|
90
|
-
async repoFetch(apiPath, init = {}) {
|
|
122
|
+
async repoFetch(apiPath, init = {}, options = {}) {
|
|
91
123
|
const normalizedApiPath = apiPath.startsWith('/api/repo') ? apiPath : `/api/repo${apiPath.startsWith('/') ? apiPath : `/${apiPath}`}`;
|
|
92
124
|
const proxyPath = `${this.apiBase}/api/community-repo${normalizedApiPath.replace('/api/repo', '')}`;
|
|
93
125
|
const directPath = `${this.communityBase}${normalizedApiPath}`;
|
|
94
|
-
const
|
|
95
|
-
|
|
126
|
+
const method = String(init.method || 'GET').toUpperCase();
|
|
127
|
+
const readOnly = options.readOnly === true || method === 'GET' || method === 'HEAD';
|
|
128
|
+
const mutationId = readOnly ? undefined : createOperationId(options.operationId);
|
|
129
|
+
const attempt = async (url, audience) => {
|
|
130
|
+
if (audience === 'community' && !this.communityToken)
|
|
131
|
+
await this.ensureCommunityAuth();
|
|
132
|
+
const response = await guardedFetch(url, {
|
|
96
133
|
...init,
|
|
97
134
|
headers: {
|
|
98
|
-
...this.getAuthHeaders(),
|
|
135
|
+
...this.getAuthHeaders(audience),
|
|
136
|
+
...(mutationId ? {
|
|
137
|
+
'Idempotency-Key': mutationId,
|
|
138
|
+
'X-Vigthoria-Operation-Id': mutationId,
|
|
139
|
+
} : {}),
|
|
99
140
|
...(init.headers || {})
|
|
100
141
|
}
|
|
101
|
-
});
|
|
102
|
-
// Both 401 (missing/expired credentials) and 403 (a valid Coder
|
|
103
|
-
// token that Community cannot accept) must activate the explicit
|
|
104
|
-
// Community credential fallback. Previously a stale/incompatible
|
|
105
|
-
// Coder token made configured Community credentials unreachable.
|
|
106
|
-
if ([401, 403].includes(response.status) && allowCommunityAuthRetry && !this.communityToken) {
|
|
107
|
-
await this.ensureCommunityAuth();
|
|
108
|
-
if (this.communityToken) {
|
|
109
|
-
return attempt(url, false);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
142
|
+
}, { audience });
|
|
112
143
|
return response;
|
|
113
144
|
};
|
|
114
145
|
try {
|
|
115
|
-
const proxyResponse = await attempt(proxyPath,
|
|
146
|
+
const proxyResponse = await attempt(proxyPath, 'coder');
|
|
116
147
|
// A large repository payload can exceed the Coder API's general
|
|
117
148
|
// 50 MiB parser while remaining valid under Community's 100 MiB
|
|
118
149
|
// repository contract. Retry that one status directly.
|
|
119
|
-
const shouldTryDirectCommunity =
|
|
150
|
+
const shouldTryDirectCommunity = readOnly || canFallbackRepositoryRequest(method, proxyResponse.status);
|
|
120
151
|
if (proxyResponse.ok || (proxyResponse.status < 500 && !shouldTryDirectCommunity)) {
|
|
121
152
|
return proxyResponse;
|
|
122
153
|
}
|
|
123
154
|
if (shouldTryDirectCommunity) {
|
|
124
155
|
await proxyResponse.body?.cancel();
|
|
125
156
|
}
|
|
157
|
+
else {
|
|
158
|
+
return proxyResponse;
|
|
159
|
+
}
|
|
126
160
|
}
|
|
127
|
-
catch {
|
|
128
|
-
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (!readOnly) {
|
|
163
|
+
throw new RepositoryMutationAmbiguousError(mutationId, error);
|
|
164
|
+
}
|
|
129
165
|
}
|
|
130
|
-
return attempt(directPath,
|
|
166
|
+
return attempt(directPath, 'community');
|
|
131
167
|
}
|
|
132
168
|
collectProjectFiles(projectPath) {
|
|
133
169
|
const files = [];
|
|
@@ -140,15 +176,26 @@ export class RepoCommand {
|
|
|
140
176
|
}
|
|
141
177
|
const fullPath = path.join(dir, entry.name);
|
|
142
178
|
const relativePath = path.relative(projectPath, fullPath);
|
|
179
|
+
if (isSensitivePath(relativePath)) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (entry.isSymbolicLink()) {
|
|
183
|
+
throw new Error(`Repository push refused symbolic link or junction: ${relativePath}`);
|
|
184
|
+
}
|
|
143
185
|
if (entry.isDirectory()) {
|
|
186
|
+
resolveWorkspacePath(projectPath, relativePath);
|
|
144
187
|
walk(fullPath);
|
|
145
188
|
continue;
|
|
146
189
|
}
|
|
147
|
-
if (!entry.isFile()
|
|
190
|
+
if (!entry.isFile()) {
|
|
191
|
+
throw new Error(`Repository push refused special or unreadable entry: ${relativePath}`);
|
|
192
|
+
}
|
|
193
|
+
if (ignoreSuffixes.some((suffix) => entry.name.endsWith(suffix))) {
|
|
148
194
|
continue;
|
|
149
195
|
}
|
|
150
196
|
try {
|
|
151
|
-
const
|
|
197
|
+
const containedPath = resolveWorkspacePath(projectPath, relativePath);
|
|
198
|
+
const raw = fs.readFileSync(containedPath);
|
|
152
199
|
let content;
|
|
153
200
|
let encoding;
|
|
154
201
|
try {
|
|
@@ -159,9 +206,14 @@ export class RepoCommand {
|
|
|
159
206
|
if (looksBinary)
|
|
160
207
|
throw new Error('binary content');
|
|
161
208
|
content = new TextDecoder('utf-8', { fatal: true }).decode(raw);
|
|
209
|
+
if (containsHighConfidenceSecret(content)) {
|
|
210
|
+
throw new Error(`Repository push refused ${relativePath}: detected secret material`);
|
|
211
|
+
}
|
|
162
212
|
encoding = 'utf8';
|
|
163
213
|
}
|
|
164
|
-
catch {
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error instanceof Error && error.message.startsWith('Repository push refused'))
|
|
216
|
+
throw error;
|
|
165
217
|
content = raw.toString('base64');
|
|
166
218
|
encoding = 'base64';
|
|
167
219
|
}
|
|
@@ -172,8 +224,8 @@ export class RepoCommand {
|
|
|
172
224
|
size: raw.length,
|
|
173
225
|
});
|
|
174
226
|
}
|
|
175
|
-
catch {
|
|
176
|
-
|
|
227
|
+
catch (error) {
|
|
228
|
+
throw new Error(`Repository push could not safely read ${relativePath}: ${error.message}`);
|
|
177
229
|
}
|
|
178
230
|
}
|
|
179
231
|
};
|
|
@@ -250,13 +302,14 @@ export class RepoCommand {
|
|
|
250
302
|
console.log(chalk.cyan(` Retry fixed content: ${review.retryCommand || `vigthoria repo push --retry ${review.reviewId}`}\n`));
|
|
251
303
|
}
|
|
252
304
|
isAuthenticated() {
|
|
253
|
-
const token = this.config.get('authToken');
|
|
305
|
+
const token = process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken');
|
|
254
306
|
return !!token;
|
|
255
307
|
}
|
|
256
308
|
requireAuth() {
|
|
257
309
|
if (!this.isAuthenticated()) {
|
|
258
|
-
|
|
259
|
-
|
|
310
|
+
throw new CliCommandError('Authentication required. Run: vigthoria login', {
|
|
311
|
+
code: 'AUTH_REQUIRED', category: 'authentication',
|
|
312
|
+
});
|
|
260
313
|
}
|
|
261
314
|
}
|
|
262
315
|
/**
|
|
@@ -321,44 +374,6 @@ export class RepoCommand {
|
|
|
321
374
|
}
|
|
322
375
|
return { name, description, techStack: [...new Set(techStack)] };
|
|
323
376
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Create a zip archive of a project
|
|
326
|
-
*/
|
|
327
|
-
async createProjectArchive(projectPath) {
|
|
328
|
-
const tempDir = path.join(require('os').tmpdir(), 'vigthoria-cli');
|
|
329
|
-
if (!fs.existsSync(tempDir)) {
|
|
330
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
331
|
-
}
|
|
332
|
-
const archivePath = path.join(tempDir, `project-${Date.now()}.zip`);
|
|
333
|
-
const output = createWriteStream(archivePath);
|
|
334
|
-
// Archiver 8 is ESM-only and exposes format-specific constructors
|
|
335
|
-
// instead of the legacy default factory.
|
|
336
|
-
const archive = new archiverModule.ZipArchive({ zlib: { level: 9 } });
|
|
337
|
-
return new Promise((resolve, reject) => {
|
|
338
|
-
output.on('close', () => resolve(archivePath));
|
|
339
|
-
archive.on('error', (err) => reject(err));
|
|
340
|
-
archive.pipe(output);
|
|
341
|
-
// Ignore patterns
|
|
342
|
-
const ignorePatterns = [
|
|
343
|
-
'node_modules/**',
|
|
344
|
-
'.git/**',
|
|
345
|
-
'dist/**',
|
|
346
|
-
'build/**',
|
|
347
|
-
'__pycache__/**',
|
|
348
|
-
'.venv/**',
|
|
349
|
-
'venv/**',
|
|
350
|
-
'*.log',
|
|
351
|
-
'.env',
|
|
352
|
-
'.DS_Store'
|
|
353
|
-
];
|
|
354
|
-
archive.glob('**/*', {
|
|
355
|
-
cwd: projectPath,
|
|
356
|
-
ignore: ignorePatterns,
|
|
357
|
-
dot: true
|
|
358
|
-
});
|
|
359
|
-
archive.finalize();
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
377
|
/**
|
|
363
378
|
* Push a project to Vigthoria Repository
|
|
364
379
|
*/
|
|
@@ -366,8 +381,9 @@ export class RepoCommand {
|
|
|
366
381
|
this.requireAuth();
|
|
367
382
|
const projectPath = options.path ? path.resolve(options.path) : process.cwd();
|
|
368
383
|
if (!fs.existsSync(projectPath)) {
|
|
369
|
-
|
|
370
|
-
|
|
384
|
+
throw new CliCommandError(`Path does not exist: ${projectPath}`, {
|
|
385
|
+
code: 'PROJECT_PATH_NOT_FOUND', category: 'configuration',
|
|
386
|
+
});
|
|
371
387
|
}
|
|
372
388
|
const spinner = createSpinner('Analyzing project...').start();
|
|
373
389
|
try {
|
|
@@ -428,7 +444,9 @@ export class RepoCommand {
|
|
|
428
444
|
if (contentBytes > MAX_REPOSITORY_CONTENT_BYTES) {
|
|
429
445
|
throw new Error(`Repository content is ${formatBytes(contentBytes)} and exceeds the ${formatBytes(MAX_REPOSITORY_CONTENT_BYTES)} limit`);
|
|
430
446
|
}
|
|
447
|
+
const operationId = createOperationId();
|
|
431
448
|
const pushPayload = JSON.stringify({
|
|
449
|
+
operationId,
|
|
432
450
|
projectName: answers.name,
|
|
433
451
|
description: answers.description,
|
|
434
452
|
techStack: projectInfo.techStack.join(', '),
|
|
@@ -447,7 +465,7 @@ export class RepoCommand {
|
|
|
447
465
|
const registerResponse = await this.repoFetch('/api/repo/push', {
|
|
448
466
|
method: 'POST',
|
|
449
467
|
body: pushPayload
|
|
450
|
-
});
|
|
468
|
+
}, { operationId });
|
|
451
469
|
if (!registerResponse.ok) {
|
|
452
470
|
const error = await readResponsePayload(registerResponse);
|
|
453
471
|
throw new RepositoryApiError(registerResponse.status, error, 'Failed to push project');
|
|
@@ -467,7 +485,6 @@ export class RepoCommand {
|
|
|
467
485
|
}
|
|
468
486
|
catch (error) {
|
|
469
487
|
spinner.stop();
|
|
470
|
-
this.logger.error('Push failed');
|
|
471
488
|
if (error instanceof RepositoryApiError && error.payload?.code === 'REPOSITORY_SECURITY_REVIEW') {
|
|
472
489
|
this.printSecurityReview({
|
|
473
490
|
reviewId: error.payload.reviewId,
|
|
@@ -478,8 +495,7 @@ export class RepoCommand {
|
|
|
478
495
|
retryCommand: error.payload.retryCommand,
|
|
479
496
|
});
|
|
480
497
|
}
|
|
481
|
-
|
|
482
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
498
|
+
throw commandFailure(error, { code: 'REPOSITORY_PUSH_FAILED', message: this.formatRepoError(error) });
|
|
483
499
|
}
|
|
484
500
|
}
|
|
485
501
|
/**
|
|
@@ -499,7 +515,7 @@ export class RepoCommand {
|
|
|
499
515
|
}
|
|
500
516
|
spinner.stop();
|
|
501
517
|
if (options.json) {
|
|
502
|
-
console.log(
|
|
518
|
+
console.log(formatSuccessJson('repo review', reviewId ? payload.review : payload.reviews));
|
|
503
519
|
return;
|
|
504
520
|
}
|
|
505
521
|
if (reviewId) {
|
|
@@ -519,7 +535,103 @@ export class RepoCommand {
|
|
|
519
535
|
}
|
|
520
536
|
catch (error) {
|
|
521
537
|
spinner.stop();
|
|
522
|
-
|
|
538
|
+
throw commandFailure(error, { code: 'REPOSITORY_REVIEW_FAILED', message: this.formatRepoError(error) });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async materializeProject(data, repo, requestedName, options, publicDownload = false) {
|
|
542
|
+
const remoteProjectName = String(data.project?.project_name || data.projectName || repo.name || repo.project_name || requestedName);
|
|
543
|
+
const outputPath = options.output
|
|
544
|
+
? assertSafeDestinationPath(path.resolve(options.output))
|
|
545
|
+
: resolveWorkspacePath(process.cwd(), assertSafeRelativePath(remoteProjectName), { allowMissing: true });
|
|
546
|
+
if (fs.existsSync(outputPath) && !options.force) {
|
|
547
|
+
const { overwrite } = await inquirer.prompt([{
|
|
548
|
+
type: 'confirm',
|
|
549
|
+
name: 'overwrite',
|
|
550
|
+
message: `Directory ${outputPath} exists. Overwrite?`,
|
|
551
|
+
default: false
|
|
552
|
+
}]);
|
|
553
|
+
if (!overwrite) {
|
|
554
|
+
throw new CliCommandError('Repository materialization cancelled by user', {
|
|
555
|
+
code: 'REPOSITORY_PULL_CANCELLED', category: 'cancelled',
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const downloadSpinner = createSpinner('Downloading project files...').start();
|
|
560
|
+
try {
|
|
561
|
+
if (fs.existsSync(outputPath))
|
|
562
|
+
validateExtractedWorkspace(outputPath);
|
|
563
|
+
if (data.downloadUrl) {
|
|
564
|
+
const archiveResponse = await guardedFetch(data.downloadUrl, {
|
|
565
|
+
headers: publicDownload
|
|
566
|
+
? { 'Content-Type': 'application/json' }
|
|
567
|
+
: this.getAuthHeadersForUrl(data.downloadUrl)
|
|
568
|
+
}, { audience: this.getRepositoryAudienceForUrl(data.downloadUrl) });
|
|
569
|
+
if (!archiveResponse.ok)
|
|
570
|
+
throw new Error('Failed to download project archive');
|
|
571
|
+
const archiveBuffer = Buffer.from(await archiveResponse.arrayBuffer());
|
|
572
|
+
inspectZipArchive(archiveBuffer);
|
|
573
|
+
const os = require('os');
|
|
574
|
+
const stagingRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'vigthoria-pull-'));
|
|
575
|
+
const tempArchive = path.join(stagingRoot, 'archive.zip');
|
|
576
|
+
const extractedPath = path.join(stagingRoot, 'extracted');
|
|
577
|
+
try {
|
|
578
|
+
fs.mkdirSync(extractedPath, { mode: 0o700 });
|
|
579
|
+
fs.writeFileSync(tempArchive, archiveBuffer, { mode: 0o600 });
|
|
580
|
+
if (os.platform() === 'win32') {
|
|
581
|
+
const { execFileSync } = await import('child_process');
|
|
582
|
+
execFileSync('powershell', [
|
|
583
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
584
|
+
'Expand-Archive -LiteralPath $env:VIGTHORIA_ARCHIVE_PATH -DestinationPath $env:VIGTHORIA_ARCHIVE_DESTINATION -Force'
|
|
585
|
+
], {
|
|
586
|
+
stdio: 'ignore',
|
|
587
|
+
timeout: 120_000,
|
|
588
|
+
windowsHide: true,
|
|
589
|
+
env: safeChildProcessEnv(process.env, {
|
|
590
|
+
VIGTHORIA_ARCHIVE_PATH: tempArchive,
|
|
591
|
+
VIGTHORIA_ARCHIVE_DESTINATION: extractedPath,
|
|
592
|
+
}),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
const { execFileSync } = await import('child_process');
|
|
597
|
+
execFileSync('unzip', ['-o', tempArchive, '-d', extractedPath], { stdio: 'ignore', timeout: 120_000, env: safeChildProcessEnv() });
|
|
598
|
+
}
|
|
599
|
+
validateExtractedWorkspace(extractedPath);
|
|
600
|
+
mergeValidatedWorkspace(extractedPath, outputPath);
|
|
601
|
+
}
|
|
602
|
+
finally {
|
|
603
|
+
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else if (data.files) {
|
|
607
|
+
const os = require('os');
|
|
608
|
+
const stagingRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'vigthoria-pull-inline-'));
|
|
609
|
+
try {
|
|
610
|
+
for (const file of data.files) {
|
|
611
|
+
const filePath = resolveWorkspacePath(stagingRoot, file.path, { allowMissing: true });
|
|
612
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
613
|
+
fs.writeFileSync(filePath, file.content, { mode: 0o600 });
|
|
614
|
+
}
|
|
615
|
+
validateExtractedWorkspace(stagingRoot);
|
|
616
|
+
mergeValidatedWorkspace(stagingRoot, outputPath);
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
throw new Error('Repository response contained neither an archive nor inline files');
|
|
624
|
+
}
|
|
625
|
+
downloadSpinner.succeed(chalk.green('Project materialized successfully!'));
|
|
626
|
+
console.log(chalk.cyan('\n📁 Project extracted to:'));
|
|
627
|
+
console.log(chalk.white(` ${outputPath}`));
|
|
628
|
+
console.log(chalk.gray(`\n Tech Stack: ${data.project?.tech_stack || repo.tech_stack || 'Unknown'}`));
|
|
629
|
+
console.log(chalk.gray(` Description: ${data.project?.description || data.description || repo.description || '(none)'}`));
|
|
630
|
+
console.log();
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
downloadSpinner.stop();
|
|
634
|
+
throw error;
|
|
523
635
|
}
|
|
524
636
|
}
|
|
525
637
|
/**
|
|
@@ -533,81 +645,18 @@ export class RepoCommand {
|
|
|
533
645
|
const response = await this.repoFetch('/api/repo/pull', {
|
|
534
646
|
method: 'POST',
|
|
535
647
|
body: JSON.stringify({ projectId: repo.id })
|
|
536
|
-
});
|
|
648
|
+
}, { readOnly: true });
|
|
537
649
|
if (!response.ok) {
|
|
538
650
|
const error = await readResponsePayload(response);
|
|
539
651
|
throw new Error(error.error || error.message || 'Project not found');
|
|
540
652
|
}
|
|
541
653
|
const data = await response.json();
|
|
542
654
|
spinner.succeed(`Found project: ${chalk.cyan(data.project?.project_name || data.projectName || repo.name || repo.project_name || projectName)}`);
|
|
543
|
-
|
|
544
|
-
? path.resolve(options.output)
|
|
545
|
-
: path.join(process.cwd(), data.project?.project_name || data.projectName || repo.name || repo.project_name || projectName);
|
|
546
|
-
if (fs.existsSync(outputPath) && !options.force) {
|
|
547
|
-
const { overwrite } = await inquirer.prompt([{
|
|
548
|
-
type: 'confirm',
|
|
549
|
-
name: 'overwrite',
|
|
550
|
-
message: `Directory ${outputPath} exists. Overwrite?`,
|
|
551
|
-
default: false
|
|
552
|
-
}]);
|
|
553
|
-
if (!overwrite) {
|
|
554
|
-
console.log(chalk.yellow(`\n${CH.warnEmoji} Pull cancelled.\n`));
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
const downloadSpinner = createSpinner('Downloading project files...').start();
|
|
559
|
-
// Create output directory
|
|
560
|
-
fs.mkdirSync(outputPath, { recursive: true });
|
|
561
|
-
// If we have a download URL, fetch and extract
|
|
562
|
-
if (data.downloadUrl) {
|
|
563
|
-
const archiveResponse = await fetch(data.downloadUrl, {
|
|
564
|
-
headers: this.getAuthHeaders()
|
|
565
|
-
});
|
|
566
|
-
if (!archiveResponse.ok) {
|
|
567
|
-
throw new Error('Failed to download project archive');
|
|
568
|
-
}
|
|
569
|
-
// Save and extract the archive
|
|
570
|
-
const tempArchive = path.join(require('os').tmpdir(), `vigthoria-pull-${Date.now()}.zip`);
|
|
571
|
-
const archiveBuffer = Buffer.from(await archiveResponse.arrayBuffer());
|
|
572
|
-
fs.writeFileSync(tempArchive, archiveBuffer);
|
|
573
|
-
// Extract archive - cross-platform
|
|
574
|
-
const os = require('os');
|
|
575
|
-
const platform = os.platform();
|
|
576
|
-
if (platform === 'win32') {
|
|
577
|
-
// Use PowerShell's Expand-Archive — args as array to prevent injection
|
|
578
|
-
const { execFileSync } = await import('child_process');
|
|
579
|
-
execFileSync('powershell', [
|
|
580
|
-
'-NoProfile', '-NonInteractive', '-Command',
|
|
581
|
-
`Expand-Archive -Path '${tempArchive.replace(/'/g, "''")}' -DestinationPath '${outputPath.replace(/'/g, "''")}' -Force`
|
|
582
|
-
], { stdio: 'ignore', windowsHide: true });
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
// Use unzip on Unix-like systems — args as array to prevent injection
|
|
586
|
-
const { execFileSync } = await import('child_process');
|
|
587
|
-
execFileSync('unzip', ['-o', tempArchive, '-d', outputPath], { stdio: 'ignore' });
|
|
588
|
-
}
|
|
589
|
-
fs.unlinkSync(tempArchive);
|
|
590
|
-
}
|
|
591
|
-
else if (data.files) {
|
|
592
|
-
// If we have files inline (small projects)
|
|
593
|
-
for (const file of data.files) {
|
|
594
|
-
const filePath = path.join(outputPath, file.path);
|
|
595
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
596
|
-
fs.writeFileSync(filePath, file.content);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
downloadSpinner.succeed(chalk.green('Project pulled successfully!'));
|
|
600
|
-
console.log(chalk.cyan('\n📁 Project extracted to:'));
|
|
601
|
-
console.log(chalk.white(` ${outputPath}`));
|
|
602
|
-
console.log(chalk.gray(`\n Tech Stack: ${data.project?.tech_stack || repo.tech_stack || 'Unknown'}`));
|
|
603
|
-
console.log(chalk.gray(` Description: ${data.project?.description || data.description || repo.description || '(none)'}`));
|
|
604
|
-
console.log();
|
|
655
|
+
await this.materializeProject(data, repo, projectName, options);
|
|
605
656
|
}
|
|
606
657
|
catch (error) {
|
|
607
658
|
spinner.stop();
|
|
608
|
-
this.
|
|
609
|
-
const errMsg = this.formatRepoError(error);
|
|
610
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
659
|
+
throw commandFailure(error, { code: 'REPOSITORY_PULL_FAILED', message: this.formatRepoError(error) });
|
|
611
660
|
}
|
|
612
661
|
}
|
|
613
662
|
/**
|
|
@@ -704,9 +753,7 @@ export class RepoCommand {
|
|
|
704
753
|
}
|
|
705
754
|
catch (error) {
|
|
706
755
|
spinner.stop();
|
|
707
|
-
this.
|
|
708
|
-
const errMsg = this.formatRepoError(error);
|
|
709
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
756
|
+
throw commandFailure(error, { code: 'REPOSITORY_LIST_FAILED', message: this.formatRepoError(error) });
|
|
710
757
|
}
|
|
711
758
|
}
|
|
712
759
|
/**
|
|
@@ -718,10 +765,10 @@ export class RepoCommand {
|
|
|
718
765
|
const projectInfo = this.detectProjectInfo(projectPath);
|
|
719
766
|
const spinner = createSpinner('Checking sync status...').start();
|
|
720
767
|
try {
|
|
721
|
-
const response = await
|
|
768
|
+
const response = await guardedFetch(`${this.apiBase}/api/repo/status/${encodeURIComponent(projectInfo.name)}`, {
|
|
722
769
|
method: 'GET',
|
|
723
|
-
headers: this.getAuthHeaders()
|
|
724
|
-
});
|
|
770
|
+
headers: this.getAuthHeaders('coder')
|
|
771
|
+
}, { audience: 'coder' });
|
|
725
772
|
if (!response.ok) {
|
|
726
773
|
spinner.succeed('Project not tracked in Vigthoria Community Repository');
|
|
727
774
|
console.log(chalk.yellow('\n📦 Current project is not synced with Vigthoria Community Repository.\n'));
|
|
@@ -755,9 +802,7 @@ export class RepoCommand {
|
|
|
755
802
|
}
|
|
756
803
|
catch (error) {
|
|
757
804
|
spinner.stop();
|
|
758
|
-
|
|
759
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
760
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
805
|
+
throw commandFailure(error, { code: 'REPOSITORY_STATUS_FAILED' });
|
|
761
806
|
}
|
|
762
807
|
}
|
|
763
808
|
/**
|
|
@@ -767,14 +812,15 @@ export class RepoCommand {
|
|
|
767
812
|
this.requireAuth();
|
|
768
813
|
const spinner = createSpinner('Generating share link...').start();
|
|
769
814
|
try {
|
|
770
|
-
const
|
|
815
|
+
const operationId = createOperationId();
|
|
816
|
+
const response = await this.repoFetch('/api/repo/share', {
|
|
771
817
|
method: 'POST',
|
|
772
|
-
headers: this.getAuthHeaders(),
|
|
773
818
|
body: JSON.stringify({
|
|
819
|
+
operationId,
|
|
774
820
|
projectName,
|
|
775
821
|
expiresIn: options.expires || '7d'
|
|
776
822
|
})
|
|
777
|
-
});
|
|
823
|
+
}, { operationId });
|
|
778
824
|
if (!response.ok) {
|
|
779
825
|
const error = await response.json();
|
|
780
826
|
throw new Error(error.error || 'Failed to generate share link');
|
|
@@ -788,32 +834,33 @@ export class RepoCommand {
|
|
|
788
834
|
}
|
|
789
835
|
catch (error) {
|
|
790
836
|
spinner.stop();
|
|
791
|
-
|
|
792
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
793
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
837
|
+
throw commandFailure(error, { code: 'REPOSITORY_SHARE_FAILED' });
|
|
794
838
|
}
|
|
795
839
|
}
|
|
796
840
|
/**
|
|
797
841
|
* Delete a project from Vigthoria Repository
|
|
798
842
|
*/
|
|
799
|
-
async delete(projectName) {
|
|
843
|
+
async delete(projectName, options = {}) {
|
|
800
844
|
this.requireAuth();
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
845
|
+
const confirm = options.yes === true
|
|
846
|
+
? true
|
|
847
|
+
: (await inquirer.prompt([{
|
|
848
|
+
type: 'confirm',
|
|
849
|
+
name: 'confirm',
|
|
850
|
+
message: chalk.red(`Are you sure you want to delete "${projectName}" from Vigthoria Community Repository?`),
|
|
851
|
+
default: false
|
|
852
|
+
}])).confirm;
|
|
807
853
|
if (!confirm) {
|
|
808
854
|
console.log(chalk.yellow(`\n${CH.warnEmoji} Delete cancelled.\n`));
|
|
809
855
|
return;
|
|
810
856
|
}
|
|
811
857
|
const spinner = createSpinner('Deleting project...').start();
|
|
812
858
|
try {
|
|
813
|
-
const
|
|
859
|
+
const operationId = createOperationId();
|
|
860
|
+
const response = await this.repoFetch(`/api/repo/projects/${encodeURIComponent(projectName)}`, {
|
|
814
861
|
method: 'DELETE',
|
|
815
|
-
headers:
|
|
816
|
-
});
|
|
862
|
+
headers: { 'X-Vigthoria-Operation-Id': operationId },
|
|
863
|
+
}, { operationId });
|
|
817
864
|
if (!response.ok) {
|
|
818
865
|
const error = await response.json();
|
|
819
866
|
throw new Error(error.error || 'Failed to delete project');
|
|
@@ -823,9 +870,7 @@ export class RepoCommand {
|
|
|
823
870
|
}
|
|
824
871
|
catch (error) {
|
|
825
872
|
spinner.stop();
|
|
826
|
-
|
|
827
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
828
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
873
|
+
throw commandFailure(error, { code: 'REPOSITORY_DELETE_FAILED' });
|
|
829
874
|
}
|
|
830
875
|
}
|
|
831
876
|
/**
|
|
@@ -844,16 +889,22 @@ export class RepoCommand {
|
|
|
844
889
|
const resolvedName = projectName || this.detectProjectInfo(process.cwd()).name;
|
|
845
890
|
const spinner = createSpinner(`Opening "${resolvedName}" in ${label}...`).start();
|
|
846
891
|
try {
|
|
847
|
-
const
|
|
892
|
+
const operationId = createOperationId();
|
|
893
|
+
const response = await guardedFetch(`${this.apiBase}/api/engine-import`, {
|
|
848
894
|
method: 'POST',
|
|
849
|
-
headers:
|
|
895
|
+
headers: {
|
|
896
|
+
...this.getAuthHeaders('coder'),
|
|
897
|
+
'Idempotency-Key': operationId,
|
|
898
|
+
'X-Vigthoria-Operation-Id': operationId,
|
|
899
|
+
},
|
|
850
900
|
body: JSON.stringify({
|
|
901
|
+
operationId,
|
|
851
902
|
engine,
|
|
852
903
|
projectName: resolvedName,
|
|
853
904
|
shopId: options.shopId || 'default',
|
|
854
905
|
source: 'repo'
|
|
855
906
|
})
|
|
856
|
-
});
|
|
907
|
+
}, { audience: 'coder' });
|
|
857
908
|
if (!response.ok) {
|
|
858
909
|
const err = await readResponsePayload(response);
|
|
859
910
|
throw new Error(err.error || `Engine import failed (${response.status})`);
|
|
@@ -873,11 +924,11 @@ export class RepoCommand {
|
|
|
873
924
|
}
|
|
874
925
|
const safeUrl = parsedUrl.href;
|
|
875
926
|
if (platform === 'win32')
|
|
876
|
-
execFileSync('
|
|
927
|
+
execFileSync('rundll32.exe', ['url.dll,FileProtocolHandler', safeUrl], { stdio: 'ignore', timeout: 10_000, windowsHide: true, env: safeChildProcessEnv() });
|
|
877
928
|
else if (platform === 'darwin')
|
|
878
|
-
execFileSync('open', [safeUrl], { stdio: 'ignore' });
|
|
929
|
+
execFileSync('open', [safeUrl], { stdio: 'ignore', timeout: 10_000, env: safeChildProcessEnv() });
|
|
879
930
|
else
|
|
880
|
-
execFileSync('xdg-open', [safeUrl], { stdio: 'ignore' });
|
|
931
|
+
execFileSync('xdg-open', [safeUrl], { stdio: 'ignore', timeout: 10_000, env: safeChildProcessEnv() });
|
|
881
932
|
}
|
|
882
933
|
catch { /* browser open is optional */ }
|
|
883
934
|
}
|
|
@@ -887,9 +938,7 @@ export class RepoCommand {
|
|
|
887
938
|
}
|
|
888
939
|
catch (error) {
|
|
889
940
|
spinner.stop();
|
|
890
|
-
|
|
891
|
-
console.log(chalk.red(`\n❌ ${label} import failed: ${errMsg}\n`));
|
|
892
|
-
console.log(chalk.gray('Tip: Make sure your project is pushed first with `vigthoria repo push`\n'));
|
|
941
|
+
throw commandFailure(error, { code: 'REPOSITORY_ENGINE_IMPORT_FAILED' });
|
|
893
942
|
}
|
|
894
943
|
}
|
|
895
944
|
/**
|
|
@@ -901,24 +950,24 @@ export class RepoCommand {
|
|
|
901
950
|
const projectId = projectIdMatch ? projectIdMatch[1] : projectUrl;
|
|
902
951
|
const spinner = createSpinner(`Cloning project...`).start();
|
|
903
952
|
try {
|
|
904
|
-
const response = await
|
|
953
|
+
const response = await guardedFetch(`${this.apiBase}/api/repo/clone/${projectId}`, {
|
|
905
954
|
method: 'GET',
|
|
906
|
-
headers: this.isAuthenticated() ? this.getAuthHeaders() : { 'Content-Type': 'application/json' }
|
|
907
|
-
});
|
|
955
|
+
headers: this.isAuthenticated() ? this.getAuthHeaders('coder') : { 'Content-Type': 'application/json' }
|
|
956
|
+
}, { audience: 'coder' });
|
|
908
957
|
if (!response.ok) {
|
|
909
958
|
const error = await response.json();
|
|
910
959
|
throw new Error(error.error || 'Project not found or not public');
|
|
911
960
|
}
|
|
912
961
|
const data = await response.json();
|
|
913
962
|
spinner.succeed(`Found project: ${chalk.cyan(data.project.project_name)}`);
|
|
914
|
-
//
|
|
915
|
-
|
|
963
|
+
// Public clones materialize the clone response directly. They must
|
|
964
|
+
// not route through authenticated `pull`, and public archive
|
|
965
|
+
// downloads must not inherit a user credential.
|
|
966
|
+
await this.materializeProject(data, data.project, data.project.project_name, options, true);
|
|
916
967
|
}
|
|
917
968
|
catch (error) {
|
|
918
969
|
spinner.stop();
|
|
919
|
-
|
|
920
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
921
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
970
|
+
throw commandFailure(error, { code: 'REPOSITORY_CLONE_FAILED' });
|
|
922
971
|
}
|
|
923
972
|
}
|
|
924
973
|
}
|