vigthoria-cli 1.13.20 → 1.13.23
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 +63 -128
- 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 +3 -42
- package/dist/commands/chat.js +378 -1123
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +23 -29
- 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 +207 -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 +244 -198
- 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 +368 -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 +124 -1589
- 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 +44 -266
- package/dist/utils/api.js +647 -3180
- 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 +220 -94
- 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 +5 -6
- package/dist/utils/fastAgentRouter.js +17 -25
- 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 +15 -0
- package/dist/utils/preview-screenshot-adapter.js +51 -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/runtime-temp.d.ts +76 -0
- package/dist/utils/runtime-temp.js +492 -0
- package/dist/utils/secret-policy.d.ts +20 -0
- package/dist/utils/secret-policy.js +160 -0
- package/dist/utils/session.d.ts +17 -3
- 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 +226 -512
- 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 +115 -142
- package/install.sh +112 -81
- package/package.json +43 -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 +45 -0
- package/scripts/release/validate-live-service-gates.sh +93 -0
- package/scripts/release/validate-no-go-gates.sh +145 -174
- 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
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import * as path from 'path';
|
|
18
|
-
import {
|
|
18
|
+
import { guardedFetch } from '../utils/network-policy.js';
|
|
19
19
|
import { createSpinner, CH } from '../utils/logger.js';
|
|
20
|
-
|
|
20
|
+
import { createOperationId } from '../utils/mutation-journal.js';
|
|
21
|
+
import { containsHighConfidenceSecret, isSensitivePath, safeChildProcessEnv } from '../utils/secret-policy.js';
|
|
22
|
+
import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
|
|
23
|
+
import { createRuntimeTempDirectory, removeRuntimeTempDirectory } from '../utils/runtime-temp.js';
|
|
24
|
+
import { assertSafeDestinationPath, assertSafeRelativePath, inspectZipArchive, mergeValidatedWorkspace, resolveWorkspacePath, validateExtractedWorkspace, } from '../utils/workspace-boundary.js';
|
|
21
25
|
import inquirer from 'inquirer';
|
|
22
|
-
import * as archiverModule from 'archiver';
|
|
23
|
-
import { createWriteStream } from 'fs';
|
|
24
26
|
const MAX_REPOSITORY_CONTENT_BYTES = 100 * 1024 * 1024;
|
|
25
27
|
const MAX_REPOSITORY_REQUEST_BYTES = 120 * 1024 * 1024;
|
|
26
28
|
class RepositoryApiError extends Error {
|
|
@@ -33,6 +35,24 @@ class RepositoryApiError extends Error {
|
|
|
33
35
|
this.payload = payload || {};
|
|
34
36
|
}
|
|
35
37
|
}
|
|
38
|
+
export class RepositoryMutationAmbiguousError extends Error {
|
|
39
|
+
operationId;
|
|
40
|
+
mutationOutcome = 'unknown';
|
|
41
|
+
partialMutation = 'unknown';
|
|
42
|
+
constructor(operationId, cause) {
|
|
43
|
+
super(`Repository mutation outcome is unknown for operation ${operationId}; no alternate backend retry was attempted.`);
|
|
44
|
+
this.name = 'RepositoryMutationAmbiguousError';
|
|
45
|
+
this.operationId = operationId;
|
|
46
|
+
if (cause instanceof Error)
|
|
47
|
+
this.cause = cause;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function canFallbackRepositoryRequest(method, status) {
|
|
51
|
+
const normalized = method.toUpperCase();
|
|
52
|
+
if (normalized === 'GET' || normalized === 'HEAD')
|
|
53
|
+
return true;
|
|
54
|
+
return status !== undefined && [401, 403, 404, 413].includes(status);
|
|
55
|
+
}
|
|
36
56
|
function formatBytes(bytes) {
|
|
37
57
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
38
58
|
}
|
|
@@ -49,24 +69,35 @@ async function readResponsePayload(response) {
|
|
|
49
69
|
}
|
|
50
70
|
export class RepoCommand {
|
|
51
71
|
config;
|
|
52
|
-
logger;
|
|
53
72
|
apiBase;
|
|
54
73
|
communityBase;
|
|
55
74
|
communityToken;
|
|
56
|
-
constructor(config,
|
|
75
|
+
constructor(config, _logger) {
|
|
57
76
|
this.config = config;
|
|
58
|
-
this.logger = logger;
|
|
59
77
|
this.apiBase = this.config.get('apiUrl') || 'https://coder.vigthoria.io';
|
|
60
78
|
this.communityBase = process.env.VIGTHORIA_COMMUNITY_API_URL || 'https://community.vigthoria.io';
|
|
61
79
|
this.communityToken = process.env.VIGTHORIA_COMMUNITY_TOKEN || null;
|
|
62
80
|
}
|
|
63
|
-
getAuthHeaders() {
|
|
64
|
-
const token =
|
|
81
|
+
getAuthHeaders(audience) {
|
|
82
|
+
const token = audience === 'coder'
|
|
83
|
+
? process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken')
|
|
84
|
+
: this.communityToken;
|
|
65
85
|
return {
|
|
66
|
-
'Authorization': `Bearer ${token}
|
|
67
|
-
'Content-Type': 'application/json'
|
|
86
|
+
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
|
87
|
+
'Content-Type': 'application/json',
|
|
68
88
|
};
|
|
69
89
|
}
|
|
90
|
+
getRepositoryAudienceForUrl(rawUrl) {
|
|
91
|
+
const hostname = new URL(rawUrl).hostname.toLowerCase();
|
|
92
|
+
if (hostname === 'coder.vigthoria.io')
|
|
93
|
+
return 'coder';
|
|
94
|
+
if (hostname === 'community.vigthoria.io')
|
|
95
|
+
return 'community';
|
|
96
|
+
throw new Error('Repository download URL is outside the Coder and Community trust boundary');
|
|
97
|
+
}
|
|
98
|
+
getAuthHeadersForUrl(rawUrl) {
|
|
99
|
+
return this.getAuthHeaders(this.getRepositoryAudienceForUrl(rawUrl));
|
|
100
|
+
}
|
|
70
101
|
async ensureCommunityAuth() {
|
|
71
102
|
if (this.communityToken) {
|
|
72
103
|
return;
|
|
@@ -76,58 +107,62 @@ export class RepoCommand {
|
|
|
76
107
|
if (!email || !password) {
|
|
77
108
|
return;
|
|
78
109
|
}
|
|
79
|
-
const response = await
|
|
110
|
+
const response = await guardedFetch(`${this.communityBase}/api/auth/login`, {
|
|
80
111
|
method: 'POST',
|
|
81
112
|
headers: { 'Content-Type': 'application/json' },
|
|
82
113
|
body: JSON.stringify({ email, password })
|
|
83
|
-
});
|
|
114
|
+
}, { audience: 'community' });
|
|
84
115
|
const data = await response.json();
|
|
85
116
|
if (!response.ok || !data.token) {
|
|
86
117
|
throw new Error(data.error || data.message || 'Community login failed');
|
|
87
118
|
}
|
|
88
119
|
this.communityToken = data.token;
|
|
89
120
|
}
|
|
90
|
-
async repoFetch(apiPath, init = {}) {
|
|
121
|
+
async repoFetch(apiPath, init = {}, options = {}) {
|
|
91
122
|
const normalizedApiPath = apiPath.startsWith('/api/repo') ? apiPath : `/api/repo${apiPath.startsWith('/') ? apiPath : `/${apiPath}`}`;
|
|
92
123
|
const proxyPath = `${this.apiBase}/api/community-repo${normalizedApiPath.replace('/api/repo', '')}`;
|
|
93
124
|
const directPath = `${this.communityBase}${normalizedApiPath}`;
|
|
94
|
-
const
|
|
95
|
-
|
|
125
|
+
const method = String(init.method || 'GET').toUpperCase();
|
|
126
|
+
const readOnly = options.readOnly === true || method === 'GET' || method === 'HEAD';
|
|
127
|
+
const mutationId = readOnly ? undefined : createOperationId(options.operationId);
|
|
128
|
+
const attempt = async (url, audience) => {
|
|
129
|
+
if (audience === 'community' && !this.communityToken)
|
|
130
|
+
await this.ensureCommunityAuth();
|
|
131
|
+
const response = await guardedFetch(url, {
|
|
96
132
|
...init,
|
|
97
133
|
headers: {
|
|
98
|
-
...this.getAuthHeaders(),
|
|
134
|
+
...this.getAuthHeaders(audience),
|
|
135
|
+
...(mutationId ? {
|
|
136
|
+
'Idempotency-Key': mutationId,
|
|
137
|
+
'X-Vigthoria-Operation-Id': mutationId,
|
|
138
|
+
} : {}),
|
|
99
139
|
...(init.headers || {})
|
|
100
140
|
}
|
|
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
|
-
}
|
|
141
|
+
}, { audience });
|
|
112
142
|
return response;
|
|
113
143
|
};
|
|
114
144
|
try {
|
|
115
|
-
const proxyResponse = await attempt(proxyPath,
|
|
145
|
+
const proxyResponse = await attempt(proxyPath, 'coder');
|
|
116
146
|
// A large repository payload can exceed the Coder API's general
|
|
117
147
|
// 50 MiB parser while remaining valid under Community's 100 MiB
|
|
118
148
|
// repository contract. Retry that one status directly.
|
|
119
|
-
const shouldTryDirectCommunity =
|
|
149
|
+
const shouldTryDirectCommunity = readOnly || canFallbackRepositoryRequest(method, proxyResponse.status);
|
|
120
150
|
if (proxyResponse.ok || (proxyResponse.status < 500 && !shouldTryDirectCommunity)) {
|
|
121
151
|
return proxyResponse;
|
|
122
152
|
}
|
|
123
153
|
if (shouldTryDirectCommunity) {
|
|
124
154
|
await proxyResponse.body?.cancel();
|
|
125
155
|
}
|
|
156
|
+
else {
|
|
157
|
+
return proxyResponse;
|
|
158
|
+
}
|
|
126
159
|
}
|
|
127
|
-
catch {
|
|
128
|
-
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (!readOnly) {
|
|
162
|
+
throw new RepositoryMutationAmbiguousError(mutationId, error);
|
|
163
|
+
}
|
|
129
164
|
}
|
|
130
|
-
return attempt(directPath,
|
|
165
|
+
return attempt(directPath, 'community');
|
|
131
166
|
}
|
|
132
167
|
collectProjectFiles(projectPath) {
|
|
133
168
|
const files = [];
|
|
@@ -140,15 +175,26 @@ export class RepoCommand {
|
|
|
140
175
|
}
|
|
141
176
|
const fullPath = path.join(dir, entry.name);
|
|
142
177
|
const relativePath = path.relative(projectPath, fullPath);
|
|
178
|
+
if (isSensitivePath(relativePath)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (entry.isSymbolicLink()) {
|
|
182
|
+
throw new Error(`Repository push refused symbolic link or junction: ${relativePath}`);
|
|
183
|
+
}
|
|
143
184
|
if (entry.isDirectory()) {
|
|
185
|
+
resolveWorkspacePath(projectPath, relativePath);
|
|
144
186
|
walk(fullPath);
|
|
145
187
|
continue;
|
|
146
188
|
}
|
|
147
|
-
if (!entry.isFile()
|
|
189
|
+
if (!entry.isFile()) {
|
|
190
|
+
throw new Error(`Repository push refused special or unreadable entry: ${relativePath}`);
|
|
191
|
+
}
|
|
192
|
+
if (ignoreSuffixes.some((suffix) => entry.name.endsWith(suffix))) {
|
|
148
193
|
continue;
|
|
149
194
|
}
|
|
150
195
|
try {
|
|
151
|
-
const
|
|
196
|
+
const containedPath = resolveWorkspacePath(projectPath, relativePath);
|
|
197
|
+
const raw = fs.readFileSync(containedPath);
|
|
152
198
|
let content;
|
|
153
199
|
let encoding;
|
|
154
200
|
try {
|
|
@@ -159,9 +205,14 @@ export class RepoCommand {
|
|
|
159
205
|
if (looksBinary)
|
|
160
206
|
throw new Error('binary content');
|
|
161
207
|
content = new TextDecoder('utf-8', { fatal: true }).decode(raw);
|
|
208
|
+
if (containsHighConfidenceSecret(content)) {
|
|
209
|
+
throw new Error(`Repository push refused ${relativePath}: detected secret material`);
|
|
210
|
+
}
|
|
162
211
|
encoding = 'utf8';
|
|
163
212
|
}
|
|
164
|
-
catch {
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (error instanceof Error && error.message.startsWith('Repository push refused'))
|
|
215
|
+
throw error;
|
|
165
216
|
content = raw.toString('base64');
|
|
166
217
|
encoding = 'base64';
|
|
167
218
|
}
|
|
@@ -172,8 +223,8 @@ export class RepoCommand {
|
|
|
172
223
|
size: raw.length,
|
|
173
224
|
});
|
|
174
225
|
}
|
|
175
|
-
catch {
|
|
176
|
-
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw new Error(`Repository push could not safely read ${relativePath}: ${error.message}`);
|
|
177
228
|
}
|
|
178
229
|
}
|
|
179
230
|
};
|
|
@@ -250,13 +301,14 @@ export class RepoCommand {
|
|
|
250
301
|
console.log(chalk.cyan(` Retry fixed content: ${review.retryCommand || `vigthoria repo push --retry ${review.reviewId}`}\n`));
|
|
251
302
|
}
|
|
252
303
|
isAuthenticated() {
|
|
253
|
-
const token = this.config.get('authToken');
|
|
304
|
+
const token = process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken');
|
|
254
305
|
return !!token;
|
|
255
306
|
}
|
|
256
307
|
requireAuth() {
|
|
257
308
|
if (!this.isAuthenticated()) {
|
|
258
|
-
|
|
259
|
-
|
|
309
|
+
throw new CliCommandError('Authentication required. Run: vigthoria login', {
|
|
310
|
+
code: 'AUTH_REQUIRED', category: 'authentication',
|
|
311
|
+
});
|
|
260
312
|
}
|
|
261
313
|
}
|
|
262
314
|
/**
|
|
@@ -321,44 +373,6 @@ export class RepoCommand {
|
|
|
321
373
|
}
|
|
322
374
|
return { name, description, techStack: [...new Set(techStack)] };
|
|
323
375
|
}
|
|
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
376
|
/**
|
|
363
377
|
* Push a project to Vigthoria Repository
|
|
364
378
|
*/
|
|
@@ -366,8 +380,9 @@ export class RepoCommand {
|
|
|
366
380
|
this.requireAuth();
|
|
367
381
|
const projectPath = options.path ? path.resolve(options.path) : process.cwd();
|
|
368
382
|
if (!fs.existsSync(projectPath)) {
|
|
369
|
-
|
|
370
|
-
|
|
383
|
+
throw new CliCommandError(`Path does not exist: ${projectPath}`, {
|
|
384
|
+
code: 'PROJECT_PATH_NOT_FOUND', category: 'configuration',
|
|
385
|
+
});
|
|
371
386
|
}
|
|
372
387
|
const spinner = createSpinner('Analyzing project...').start();
|
|
373
388
|
try {
|
|
@@ -428,7 +443,9 @@ export class RepoCommand {
|
|
|
428
443
|
if (contentBytes > MAX_REPOSITORY_CONTENT_BYTES) {
|
|
429
444
|
throw new Error(`Repository content is ${formatBytes(contentBytes)} and exceeds the ${formatBytes(MAX_REPOSITORY_CONTENT_BYTES)} limit`);
|
|
430
445
|
}
|
|
446
|
+
const operationId = createOperationId();
|
|
431
447
|
const pushPayload = JSON.stringify({
|
|
448
|
+
operationId,
|
|
432
449
|
projectName: answers.name,
|
|
433
450
|
description: answers.description,
|
|
434
451
|
techStack: projectInfo.techStack.join(', '),
|
|
@@ -447,7 +464,7 @@ export class RepoCommand {
|
|
|
447
464
|
const registerResponse = await this.repoFetch('/api/repo/push', {
|
|
448
465
|
method: 'POST',
|
|
449
466
|
body: pushPayload
|
|
450
|
-
});
|
|
467
|
+
}, { operationId });
|
|
451
468
|
if (!registerResponse.ok) {
|
|
452
469
|
const error = await readResponsePayload(registerResponse);
|
|
453
470
|
throw new RepositoryApiError(registerResponse.status, error, 'Failed to push project');
|
|
@@ -467,7 +484,6 @@ export class RepoCommand {
|
|
|
467
484
|
}
|
|
468
485
|
catch (error) {
|
|
469
486
|
spinner.stop();
|
|
470
|
-
this.logger.error('Push failed');
|
|
471
487
|
if (error instanceof RepositoryApiError && error.payload?.code === 'REPOSITORY_SECURITY_REVIEW') {
|
|
472
488
|
this.printSecurityReview({
|
|
473
489
|
reviewId: error.payload.reviewId,
|
|
@@ -478,8 +494,7 @@ export class RepoCommand {
|
|
|
478
494
|
retryCommand: error.payload.retryCommand,
|
|
479
495
|
});
|
|
480
496
|
}
|
|
481
|
-
|
|
482
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
497
|
+
throw commandFailure(error, { code: 'REPOSITORY_PUSH_FAILED', message: this.formatRepoError(error) });
|
|
483
498
|
}
|
|
484
499
|
}
|
|
485
500
|
/**
|
|
@@ -499,7 +514,7 @@ export class RepoCommand {
|
|
|
499
514
|
}
|
|
500
515
|
spinner.stop();
|
|
501
516
|
if (options.json) {
|
|
502
|
-
console.log(
|
|
517
|
+
console.log(formatSuccessJson('repo review', reviewId ? payload.review : payload.reviews));
|
|
503
518
|
return;
|
|
504
519
|
}
|
|
505
520
|
if (reviewId) {
|
|
@@ -519,7 +534,101 @@ export class RepoCommand {
|
|
|
519
534
|
}
|
|
520
535
|
catch (error) {
|
|
521
536
|
spinner.stop();
|
|
522
|
-
|
|
537
|
+
throw commandFailure(error, { code: 'REPOSITORY_REVIEW_FAILED', message: this.formatRepoError(error) });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async materializeProject(data, repo, requestedName, options, publicDownload = false) {
|
|
541
|
+
const remoteProjectName = String(data.project?.project_name || data.projectName || repo.name || repo.project_name || requestedName);
|
|
542
|
+
const outputPath = options.output
|
|
543
|
+
? assertSafeDestinationPath(path.resolve(options.output))
|
|
544
|
+
: resolveWorkspacePath(process.cwd(), assertSafeRelativePath(remoteProjectName), { allowMissing: true });
|
|
545
|
+
if (fs.existsSync(outputPath) && !options.force) {
|
|
546
|
+
const { overwrite } = await inquirer.prompt([{
|
|
547
|
+
type: 'confirm',
|
|
548
|
+
name: 'overwrite',
|
|
549
|
+
message: `Directory ${outputPath} exists. Overwrite?`,
|
|
550
|
+
default: false
|
|
551
|
+
}]);
|
|
552
|
+
if (!overwrite) {
|
|
553
|
+
throw new CliCommandError('Repository materialization cancelled by user', {
|
|
554
|
+
code: 'REPOSITORY_PULL_CANCELLED', category: 'cancelled',
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const downloadSpinner = createSpinner('Downloading project files...').start();
|
|
559
|
+
try {
|
|
560
|
+
if (fs.existsSync(outputPath))
|
|
561
|
+
validateExtractedWorkspace(outputPath);
|
|
562
|
+
if (data.downloadUrl) {
|
|
563
|
+
const archiveResponse = await guardedFetch(data.downloadUrl, {
|
|
564
|
+
headers: publicDownload
|
|
565
|
+
? { 'Content-Type': 'application/json' }
|
|
566
|
+
: this.getAuthHeadersForUrl(data.downloadUrl)
|
|
567
|
+
}, { audience: this.getRepositoryAudienceForUrl(data.downloadUrl) });
|
|
568
|
+
if (!archiveResponse.ok)
|
|
569
|
+
throw new Error('Failed to download project archive');
|
|
570
|
+
const archiveBuffer = Buffer.from(await archiveResponse.arrayBuffer());
|
|
571
|
+
inspectZipArchive(archiveBuffer);
|
|
572
|
+
const stagingRoot = createRuntimeTempDirectory('repo-pull-');
|
|
573
|
+
const tempArchive = path.join(stagingRoot, 'archive.zip');
|
|
574
|
+
const extractedPath = path.join(stagingRoot, 'extracted');
|
|
575
|
+
try {
|
|
576
|
+
fs.mkdirSync(extractedPath, { mode: 0o700 });
|
|
577
|
+
fs.writeFileSync(tempArchive, archiveBuffer, { mode: 0o600 });
|
|
578
|
+
if (process.platform === 'win32') {
|
|
579
|
+
const { execFileSync } = await import('child_process');
|
|
580
|
+
execFileSync('powershell', [
|
|
581
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
582
|
+
'Expand-Archive -LiteralPath $env:VIGTHORIA_ARCHIVE_PATH -DestinationPath $env:VIGTHORIA_ARCHIVE_DESTINATION -Force'
|
|
583
|
+
], {
|
|
584
|
+
stdio: 'ignore',
|
|
585
|
+
timeout: 120_000,
|
|
586
|
+
windowsHide: true,
|
|
587
|
+
env: safeChildProcessEnv(process.env, {
|
|
588
|
+
VIGTHORIA_ARCHIVE_PATH: tempArchive,
|
|
589
|
+
VIGTHORIA_ARCHIVE_DESTINATION: extractedPath,
|
|
590
|
+
}),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
const { execFileSync } = await import('child_process');
|
|
595
|
+
execFileSync('unzip', ['-o', tempArchive, '-d', extractedPath], { stdio: 'ignore', timeout: 120_000, env: safeChildProcessEnv() });
|
|
596
|
+
}
|
|
597
|
+
validateExtractedWorkspace(extractedPath);
|
|
598
|
+
mergeValidatedWorkspace(extractedPath, outputPath);
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
removeRuntimeTempDirectory(stagingRoot);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else if (data.files) {
|
|
605
|
+
const stagingRoot = createRuntimeTempDirectory('repo-inline-');
|
|
606
|
+
try {
|
|
607
|
+
for (const file of data.files) {
|
|
608
|
+
const filePath = resolveWorkspacePath(stagingRoot, file.path, { allowMissing: true });
|
|
609
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
610
|
+
fs.writeFileSync(filePath, file.content, { mode: 0o600 });
|
|
611
|
+
}
|
|
612
|
+
validateExtractedWorkspace(stagingRoot);
|
|
613
|
+
mergeValidatedWorkspace(stagingRoot, outputPath);
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
removeRuntimeTempDirectory(stagingRoot);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
throw new Error('Repository response contained neither an archive nor inline files');
|
|
621
|
+
}
|
|
622
|
+
downloadSpinner.succeed(chalk.green('Project materialized successfully!'));
|
|
623
|
+
console.log(chalk.cyan('\n📁 Project extracted to:'));
|
|
624
|
+
console.log(chalk.white(` ${outputPath}`));
|
|
625
|
+
console.log(chalk.gray(`\n Tech Stack: ${data.project?.tech_stack || repo.tech_stack || 'Unknown'}`));
|
|
626
|
+
console.log(chalk.gray(` Description: ${data.project?.description || data.description || repo.description || '(none)'}`));
|
|
627
|
+
console.log();
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
downloadSpinner.stop();
|
|
631
|
+
throw error;
|
|
523
632
|
}
|
|
524
633
|
}
|
|
525
634
|
/**
|
|
@@ -533,81 +642,18 @@ export class RepoCommand {
|
|
|
533
642
|
const response = await this.repoFetch('/api/repo/pull', {
|
|
534
643
|
method: 'POST',
|
|
535
644
|
body: JSON.stringify({ projectId: repo.id })
|
|
536
|
-
});
|
|
645
|
+
}, { readOnly: true });
|
|
537
646
|
if (!response.ok) {
|
|
538
647
|
const error = await readResponsePayload(response);
|
|
539
648
|
throw new Error(error.error || error.message || 'Project not found');
|
|
540
649
|
}
|
|
541
650
|
const data = await response.json();
|
|
542
651
|
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();
|
|
652
|
+
await this.materializeProject(data, repo, projectName, options);
|
|
605
653
|
}
|
|
606
654
|
catch (error) {
|
|
607
655
|
spinner.stop();
|
|
608
|
-
this.
|
|
609
|
-
const errMsg = this.formatRepoError(error);
|
|
610
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
656
|
+
throw commandFailure(error, { code: 'REPOSITORY_PULL_FAILED', message: this.formatRepoError(error) });
|
|
611
657
|
}
|
|
612
658
|
}
|
|
613
659
|
/**
|
|
@@ -704,9 +750,7 @@ export class RepoCommand {
|
|
|
704
750
|
}
|
|
705
751
|
catch (error) {
|
|
706
752
|
spinner.stop();
|
|
707
|
-
this.
|
|
708
|
-
const errMsg = this.formatRepoError(error);
|
|
709
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
753
|
+
throw commandFailure(error, { code: 'REPOSITORY_LIST_FAILED', message: this.formatRepoError(error) });
|
|
710
754
|
}
|
|
711
755
|
}
|
|
712
756
|
/**
|
|
@@ -718,10 +762,10 @@ export class RepoCommand {
|
|
|
718
762
|
const projectInfo = this.detectProjectInfo(projectPath);
|
|
719
763
|
const spinner = createSpinner('Checking sync status...').start();
|
|
720
764
|
try {
|
|
721
|
-
const response = await
|
|
765
|
+
const response = await guardedFetch(`${this.apiBase}/api/repo/status/${encodeURIComponent(projectInfo.name)}`, {
|
|
722
766
|
method: 'GET',
|
|
723
|
-
headers: this.getAuthHeaders()
|
|
724
|
-
});
|
|
767
|
+
headers: this.getAuthHeaders('coder')
|
|
768
|
+
}, { audience: 'coder' });
|
|
725
769
|
if (!response.ok) {
|
|
726
770
|
spinner.succeed('Project not tracked in Vigthoria Community Repository');
|
|
727
771
|
console.log(chalk.yellow('\n📦 Current project is not synced with Vigthoria Community Repository.\n'));
|
|
@@ -755,9 +799,7 @@ export class RepoCommand {
|
|
|
755
799
|
}
|
|
756
800
|
catch (error) {
|
|
757
801
|
spinner.stop();
|
|
758
|
-
|
|
759
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
760
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
802
|
+
throw commandFailure(error, { code: 'REPOSITORY_STATUS_FAILED' });
|
|
761
803
|
}
|
|
762
804
|
}
|
|
763
805
|
/**
|
|
@@ -767,14 +809,15 @@ export class RepoCommand {
|
|
|
767
809
|
this.requireAuth();
|
|
768
810
|
const spinner = createSpinner('Generating share link...').start();
|
|
769
811
|
try {
|
|
770
|
-
const
|
|
812
|
+
const operationId = createOperationId();
|
|
813
|
+
const response = await this.repoFetch('/api/repo/share', {
|
|
771
814
|
method: 'POST',
|
|
772
|
-
headers: this.getAuthHeaders(),
|
|
773
815
|
body: JSON.stringify({
|
|
816
|
+
operationId,
|
|
774
817
|
projectName,
|
|
775
818
|
expiresIn: options.expires || '7d'
|
|
776
819
|
})
|
|
777
|
-
});
|
|
820
|
+
}, { operationId });
|
|
778
821
|
if (!response.ok) {
|
|
779
822
|
const error = await response.json();
|
|
780
823
|
throw new Error(error.error || 'Failed to generate share link');
|
|
@@ -788,32 +831,33 @@ export class RepoCommand {
|
|
|
788
831
|
}
|
|
789
832
|
catch (error) {
|
|
790
833
|
spinner.stop();
|
|
791
|
-
|
|
792
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
793
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
834
|
+
throw commandFailure(error, { code: 'REPOSITORY_SHARE_FAILED' });
|
|
794
835
|
}
|
|
795
836
|
}
|
|
796
837
|
/**
|
|
797
838
|
* Delete a project from Vigthoria Repository
|
|
798
839
|
*/
|
|
799
|
-
async delete(projectName) {
|
|
840
|
+
async delete(projectName, options = {}) {
|
|
800
841
|
this.requireAuth();
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
842
|
+
const confirm = options.yes === true
|
|
843
|
+
? true
|
|
844
|
+
: (await inquirer.prompt([{
|
|
845
|
+
type: 'confirm',
|
|
846
|
+
name: 'confirm',
|
|
847
|
+
message: chalk.red(`Are you sure you want to delete "${projectName}" from Vigthoria Community Repository?`),
|
|
848
|
+
default: false
|
|
849
|
+
}])).confirm;
|
|
807
850
|
if (!confirm) {
|
|
808
851
|
console.log(chalk.yellow(`\n${CH.warnEmoji} Delete cancelled.\n`));
|
|
809
852
|
return;
|
|
810
853
|
}
|
|
811
854
|
const spinner = createSpinner('Deleting project...').start();
|
|
812
855
|
try {
|
|
813
|
-
const
|
|
856
|
+
const operationId = createOperationId();
|
|
857
|
+
const response = await this.repoFetch(`/api/repo/projects/${encodeURIComponent(projectName)}`, {
|
|
814
858
|
method: 'DELETE',
|
|
815
|
-
headers:
|
|
816
|
-
});
|
|
859
|
+
headers: { 'X-Vigthoria-Operation-Id': operationId },
|
|
860
|
+
}, { operationId });
|
|
817
861
|
if (!response.ok) {
|
|
818
862
|
const error = await response.json();
|
|
819
863
|
throw new Error(error.error || 'Failed to delete project');
|
|
@@ -823,9 +867,7 @@ export class RepoCommand {
|
|
|
823
867
|
}
|
|
824
868
|
catch (error) {
|
|
825
869
|
spinner.stop();
|
|
826
|
-
|
|
827
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
828
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
870
|
+
throw commandFailure(error, { code: 'REPOSITORY_DELETE_FAILED' });
|
|
829
871
|
}
|
|
830
872
|
}
|
|
831
873
|
/**
|
|
@@ -844,16 +886,22 @@ export class RepoCommand {
|
|
|
844
886
|
const resolvedName = projectName || this.detectProjectInfo(process.cwd()).name;
|
|
845
887
|
const spinner = createSpinner(`Opening "${resolvedName}" in ${label}...`).start();
|
|
846
888
|
try {
|
|
847
|
-
const
|
|
889
|
+
const operationId = createOperationId();
|
|
890
|
+
const response = await guardedFetch(`${this.apiBase}/api/engine-import`, {
|
|
848
891
|
method: 'POST',
|
|
849
|
-
headers:
|
|
892
|
+
headers: {
|
|
893
|
+
...this.getAuthHeaders('coder'),
|
|
894
|
+
'Idempotency-Key': operationId,
|
|
895
|
+
'X-Vigthoria-Operation-Id': operationId,
|
|
896
|
+
},
|
|
850
897
|
body: JSON.stringify({
|
|
898
|
+
operationId,
|
|
851
899
|
engine,
|
|
852
900
|
projectName: resolvedName,
|
|
853
901
|
shopId: options.shopId || 'default',
|
|
854
902
|
source: 'repo'
|
|
855
903
|
})
|
|
856
|
-
});
|
|
904
|
+
}, { audience: 'coder' });
|
|
857
905
|
if (!response.ok) {
|
|
858
906
|
const err = await readResponsePayload(response);
|
|
859
907
|
throw new Error(err.error || `Engine import failed (${response.status})`);
|
|
@@ -873,11 +921,11 @@ export class RepoCommand {
|
|
|
873
921
|
}
|
|
874
922
|
const safeUrl = parsedUrl.href;
|
|
875
923
|
if (platform === 'win32')
|
|
876
|
-
execFileSync('
|
|
924
|
+
execFileSync('rundll32.exe', ['url.dll,FileProtocolHandler', safeUrl], { stdio: 'ignore', timeout: 10_000, windowsHide: true, env: safeChildProcessEnv() });
|
|
877
925
|
else if (platform === 'darwin')
|
|
878
|
-
execFileSync('open', [safeUrl], { stdio: 'ignore' });
|
|
926
|
+
execFileSync('open', [safeUrl], { stdio: 'ignore', timeout: 10_000, env: safeChildProcessEnv() });
|
|
879
927
|
else
|
|
880
|
-
execFileSync('xdg-open', [safeUrl], { stdio: 'ignore' });
|
|
928
|
+
execFileSync('xdg-open', [safeUrl], { stdio: 'ignore', timeout: 10_000, env: safeChildProcessEnv() });
|
|
881
929
|
}
|
|
882
930
|
catch { /* browser open is optional */ }
|
|
883
931
|
}
|
|
@@ -887,9 +935,7 @@ export class RepoCommand {
|
|
|
887
935
|
}
|
|
888
936
|
catch (error) {
|
|
889
937
|
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'));
|
|
938
|
+
throw commandFailure(error, { code: 'REPOSITORY_ENGINE_IMPORT_FAILED' });
|
|
893
939
|
}
|
|
894
940
|
}
|
|
895
941
|
/**
|
|
@@ -901,24 +947,24 @@ export class RepoCommand {
|
|
|
901
947
|
const projectId = projectIdMatch ? projectIdMatch[1] : projectUrl;
|
|
902
948
|
const spinner = createSpinner(`Cloning project...`).start();
|
|
903
949
|
try {
|
|
904
|
-
const response = await
|
|
950
|
+
const response = await guardedFetch(`${this.apiBase}/api/repo/clone/${projectId}`, {
|
|
905
951
|
method: 'GET',
|
|
906
|
-
headers: this.isAuthenticated() ? this.getAuthHeaders() : { 'Content-Type': 'application/json' }
|
|
907
|
-
});
|
|
952
|
+
headers: this.isAuthenticated() ? this.getAuthHeaders('coder') : { 'Content-Type': 'application/json' }
|
|
953
|
+
}, { audience: 'coder' });
|
|
908
954
|
if (!response.ok) {
|
|
909
955
|
const error = await response.json();
|
|
910
956
|
throw new Error(error.error || 'Project not found or not public');
|
|
911
957
|
}
|
|
912
958
|
const data = await response.json();
|
|
913
959
|
spinner.succeed(`Found project: ${chalk.cyan(data.project.project_name)}`);
|
|
914
|
-
//
|
|
915
|
-
|
|
960
|
+
// Public clones materialize the clone response directly. They must
|
|
961
|
+
// not route through authenticated `pull`, and public archive
|
|
962
|
+
// downloads must not inherit a user credential.
|
|
963
|
+
await this.materializeProject(data, data.project, data.project.project_name, options, true);
|
|
916
964
|
}
|
|
917
965
|
catch (error) {
|
|
918
966
|
spinner.stop();
|
|
919
|
-
|
|
920
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
921
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
967
|
+
throw commandFailure(error, { code: 'REPOSITORY_CLONE_FAILED' });
|
|
922
968
|
}
|
|
923
969
|
}
|
|
924
970
|
}
|