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/deploy.js
CHANGED
|
@@ -15,33 +15,67 @@ import chalk from 'chalk';
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { createSpinner, CH } from '../utils/logger.js';
|
|
18
|
+
import { validateExtractedWorkspace } from '../utils/workspace-boundary.js';
|
|
19
|
+
import { guardedFetch } from '../utils/network-policy.js';
|
|
20
|
+
import { CliCommandError, commandFailure } from '../utils/command-contract.js';
|
|
18
21
|
import inquirer from 'inquirer';
|
|
19
22
|
export class DeployCommand {
|
|
20
23
|
config;
|
|
21
|
-
logger;
|
|
22
24
|
apiBase;
|
|
23
|
-
constructor(config,
|
|
25
|
+
constructor(config, _logger) {
|
|
24
26
|
this.config = config;
|
|
25
|
-
this.logger = logger;
|
|
26
27
|
this.apiBase = this.config.get('apiUrl') || 'https://coder.vigthoria.io';
|
|
27
28
|
}
|
|
28
29
|
getAuthHeaders() {
|
|
29
|
-
const token = this.config.get('authToken');
|
|
30
|
-
return
|
|
31
|
-
'Authorization': `Bearer ${token}`,
|
|
32
|
-
'Content-Type': 'application/json'
|
|
33
|
-
};
|
|
30
|
+
const token = process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken');
|
|
31
|
+
return token
|
|
32
|
+
? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
|
|
33
|
+
: { 'Content-Type': 'application/json' };
|
|
34
34
|
}
|
|
35
35
|
isAuthenticated() {
|
|
36
|
-
return !!this.config.get('authToken');
|
|
36
|
+
return !!(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN || this.config.get('authToken'));
|
|
37
37
|
}
|
|
38
38
|
requireAuth() {
|
|
39
39
|
if (!this.isAuthenticated()) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
throw new CliCommandError('Authentication required. Run `vigthoria login` first.', {
|
|
41
|
+
code: 'AUTH_REQUIRED', category: 'authentication',
|
|
42
|
+
});
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Deployment is a Coder-server operation. A client-local absolute path is
|
|
47
|
+
* neither portable nor a valid remote workspace binding, so resolve the
|
|
48
|
+
* validated local project to an existing server-owned project identifier
|
|
49
|
+
* before starting a deployment mutation.
|
|
50
|
+
*/
|
|
51
|
+
async resolveServerProject(projectPath) {
|
|
52
|
+
const projectDir = fs.realpathSync(projectPath || process.cwd());
|
|
53
|
+
validateExtractedWorkspace(projectDir, { ignoreNames: ['node_modules', '.git', 'dist', 'build'] });
|
|
54
|
+
const projectInfo = this.detectProjectInfo(projectDir);
|
|
55
|
+
const response = await guardedFetch(`${this.apiBase}/api/projects`, {
|
|
56
|
+
headers: this.getAuthHeaders(),
|
|
57
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error('Unable to resolve the project in Coder before deployment');
|
|
60
|
+
}
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
const localNames = new Set([
|
|
63
|
+
projectInfo.name.trim().toLowerCase(),
|
|
64
|
+
path.basename(projectDir).trim().toLowerCase(),
|
|
65
|
+
].filter(Boolean));
|
|
66
|
+
const match = (data.projects || []).find((project) => {
|
|
67
|
+
const remoteName = String(project.project_name || project.name || '').trim().toLowerCase();
|
|
68
|
+
return localNames.has(remoteName);
|
|
69
|
+
});
|
|
70
|
+
const projectId = match?.id ?? match?.project_id;
|
|
71
|
+
if (projectId === undefined || projectId === null || String(projectId).trim() === '') {
|
|
72
|
+
throw new Error(`Project "${projectInfo.name}" is not synchronized with Coder. Upload or create it in Coder before deploying.`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
projectId: typeof projectId === 'number' ? projectId : String(projectId),
|
|
76
|
+
projectName: String(match?.project_name || match?.name || projectInfo.name),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
45
79
|
/**
|
|
46
80
|
* Interactive deploy wizard
|
|
47
81
|
*/
|
|
@@ -91,16 +125,15 @@ export class DeployCommand {
|
|
|
91
125
|
this.requireAuth();
|
|
92
126
|
const spinner = createSpinner('Deploying to preview...').start();
|
|
93
127
|
try {
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const response = await fetch(`${this.apiBase}/api/hosting/deploy/preview`, {
|
|
128
|
+
const project = await this.resolveServerProject(projectPath);
|
|
129
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/deploy/preview`, {
|
|
97
130
|
method: 'POST',
|
|
98
131
|
headers: this.getAuthHeaders(),
|
|
99
132
|
body: JSON.stringify({
|
|
100
|
-
|
|
101
|
-
|
|
133
|
+
projectId: project.projectId,
|
|
134
|
+
projectName: project.projectName,
|
|
102
135
|
})
|
|
103
|
-
});
|
|
136
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
104
137
|
if (!response.ok) {
|
|
105
138
|
const error = await response.json();
|
|
106
139
|
throw new Error(error.error || 'Failed to deploy');
|
|
@@ -114,49 +147,40 @@ export class DeployCommand {
|
|
|
114
147
|
}
|
|
115
148
|
catch (error) {
|
|
116
149
|
spinner.stop();
|
|
117
|
-
|
|
118
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
119
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
120
|
-
process.exitCode = 1;
|
|
150
|
+
throw commandFailure(error, { code: 'DEPLOY_PREVIEW_FAILED' });
|
|
121
151
|
}
|
|
122
152
|
}
|
|
123
153
|
/**
|
|
124
154
|
* Deploy to Vigthoria subdomain
|
|
125
155
|
*/
|
|
126
156
|
async deployToSubdomain(subdomain, projectPath) {
|
|
157
|
+
this.requireAuth();
|
|
127
158
|
const spinner = createSpinner(`Deploying to ${subdomain}.vigthoria.io...`).start();
|
|
128
159
|
try {
|
|
129
160
|
// Validate subdomain format
|
|
130
161
|
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(subdomain) || subdomain.length < 3) {
|
|
131
|
-
throw new
|
|
162
|
+
throw new CliCommandError('Subdomain must be 3+ chars, lowercase alphanumeric with hyphens', {
|
|
163
|
+
code: 'SUBDOMAIN_INVALID', category: 'usage',
|
|
164
|
+
});
|
|
132
165
|
}
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
const response = await fetch(`${this.apiBase}/api/hosting/deploy/subdomain`, {
|
|
166
|
+
const project = await this.resolveServerProject(projectPath);
|
|
167
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/deploy/subdomain`, {
|
|
136
168
|
method: 'POST',
|
|
137
169
|
headers: this.getAuthHeaders(),
|
|
138
170
|
body: JSON.stringify({
|
|
139
171
|
subdomain,
|
|
140
|
-
|
|
141
|
-
|
|
172
|
+
projectId: project.projectId,
|
|
173
|
+
projectName: project.projectName,
|
|
142
174
|
})
|
|
143
|
-
});
|
|
175
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
144
176
|
const data = await response.json();
|
|
145
177
|
if (!response.ok || !data.success) {
|
|
146
178
|
if (data.requiresSubscription) {
|
|
147
179
|
spinner.stop();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
message: 'Would you like to subscribe now?',
|
|
153
|
-
default: true
|
|
154
|
-
}]);
|
|
155
|
-
if (proceed && data.checkoutUrl) {
|
|
156
|
-
console.log(chalk.cyan(`\n🔗 Opening checkout: ${data.checkoutUrl}`));
|
|
157
|
-
console.log(chalk.gray('Please open this URL in your browser to subscribe.\n'));
|
|
158
|
-
}
|
|
159
|
-
return;
|
|
180
|
+
throw new CliCommandError('Subdomain hosting requires a subscription (€4.99/mo)', {
|
|
181
|
+
code: 'DEPLOY_SUBSCRIPTION_REQUIRED', category: 'authorization',
|
|
182
|
+
details: data.checkoutUrl ? { checkoutUrl: data.checkoutUrl } : undefined,
|
|
183
|
+
});
|
|
160
184
|
}
|
|
161
185
|
throw new Error(data.error || 'Failed to deploy');
|
|
162
186
|
}
|
|
@@ -169,44 +193,34 @@ export class DeployCommand {
|
|
|
169
193
|
}
|
|
170
194
|
catch (error) {
|
|
171
195
|
spinner.stop();
|
|
172
|
-
|
|
173
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
174
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
196
|
+
throw commandFailure(error, { code: 'DEPLOY_SUBDOMAIN_FAILED' });
|
|
175
197
|
}
|
|
176
198
|
}
|
|
177
199
|
/**
|
|
178
200
|
* Deploy to custom domain
|
|
179
201
|
*/
|
|
180
202
|
async deployToCustomDomain(domain, projectPath) {
|
|
203
|
+
this.requireAuth();
|
|
181
204
|
const spinner = createSpinner(`Setting up ${domain}...`).start();
|
|
182
205
|
try {
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const response = await fetch(`${this.apiBase}/api/hosting/deploy/custom`, {
|
|
206
|
+
const project = await this.resolveServerProject(projectPath);
|
|
207
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/deploy/custom`, {
|
|
186
208
|
method: 'POST',
|
|
187
209
|
headers: this.getAuthHeaders(),
|
|
188
210
|
body: JSON.stringify({
|
|
189
211
|
domain,
|
|
190
|
-
|
|
191
|
-
|
|
212
|
+
projectId: project.projectId,
|
|
213
|
+
projectName: project.projectName,
|
|
192
214
|
})
|
|
193
|
-
});
|
|
215
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
194
216
|
const data = await response.json();
|
|
195
217
|
if (!response.ok || !data.success) {
|
|
196
218
|
if (data.requiresSubscription) {
|
|
197
219
|
spinner.stop();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
message: 'Would you like to subscribe now?',
|
|
203
|
-
default: true
|
|
204
|
-
}]);
|
|
205
|
-
if (proceed && data.checkoutUrl) {
|
|
206
|
-
console.log(chalk.cyan(`\n🔗 Checkout URL: ${data.checkoutUrl}`));
|
|
207
|
-
console.log(chalk.gray('Please open this URL in your browser to subscribe.\n'));
|
|
208
|
-
}
|
|
209
|
-
return;
|
|
220
|
+
throw new CliCommandError('Custom domain hosting requires a subscription (€9.99/mo)', {
|
|
221
|
+
code: 'DEPLOY_SUBSCRIPTION_REQUIRED', category: 'authorization',
|
|
222
|
+
details: data.checkoutUrl ? { checkoutUrl: data.checkoutUrl } : undefined,
|
|
223
|
+
});
|
|
210
224
|
}
|
|
211
225
|
throw new Error(data.error || 'Failed to deploy');
|
|
212
226
|
}
|
|
@@ -224,9 +238,7 @@ export class DeployCommand {
|
|
|
224
238
|
}
|
|
225
239
|
catch (error) {
|
|
226
240
|
spinner.stop();
|
|
227
|
-
|
|
228
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
229
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
241
|
+
throw commandFailure(error, { code: 'DEPLOY_CUSTOM_DOMAIN_FAILED' });
|
|
230
242
|
}
|
|
231
243
|
}
|
|
232
244
|
/**
|
|
@@ -271,9 +283,9 @@ export class DeployCommand {
|
|
|
271
283
|
async showPlans() {
|
|
272
284
|
const spinner = createSpinner('Fetching hosting plans...').start();
|
|
273
285
|
try {
|
|
274
|
-
const response = await
|
|
286
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/plans`, {
|
|
275
287
|
headers: this.getAuthHeaders()
|
|
276
|
-
});
|
|
288
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
277
289
|
if (!response.ok) {
|
|
278
290
|
throw new Error('Failed to fetch plans');
|
|
279
291
|
}
|
|
@@ -301,9 +313,7 @@ export class DeployCommand {
|
|
|
301
313
|
}
|
|
302
314
|
catch (error) {
|
|
303
315
|
spinner.stop();
|
|
304
|
-
|
|
305
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
306
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
316
|
+
throw commandFailure(error, { code: 'DEPLOY_PLANS_FAILED' });
|
|
307
317
|
}
|
|
308
318
|
}
|
|
309
319
|
/**
|
|
@@ -313,9 +323,9 @@ export class DeployCommand {
|
|
|
313
323
|
this.requireAuth();
|
|
314
324
|
const spinner = createSpinner('Fetching deployments...').start();
|
|
315
325
|
try {
|
|
316
|
-
const response = await
|
|
326
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/domains`, {
|
|
317
327
|
headers: this.getAuthHeaders()
|
|
318
|
-
});
|
|
328
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
319
329
|
if (!response.ok) {
|
|
320
330
|
throw new Error('Failed to fetch deployments');
|
|
321
331
|
}
|
|
@@ -342,9 +352,7 @@ export class DeployCommand {
|
|
|
342
352
|
}
|
|
343
353
|
catch (error) {
|
|
344
354
|
spinner.stop();
|
|
345
|
-
|
|
346
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
347
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
355
|
+
throw commandFailure(error, { code: 'DEPLOY_LIST_FAILED' });
|
|
348
356
|
}
|
|
349
357
|
}
|
|
350
358
|
/**
|
|
@@ -357,9 +365,9 @@ export class DeployCommand {
|
|
|
357
365
|
const endpoint = domain
|
|
358
366
|
? `${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}/status`
|
|
359
367
|
: `${this.apiBase}/api/hosting/status`;
|
|
360
|
-
const response = await
|
|
368
|
+
const response = await guardedFetch(endpoint, {
|
|
361
369
|
headers: this.getAuthHeaders()
|
|
362
|
-
});
|
|
370
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
363
371
|
if (!response.ok) {
|
|
364
372
|
throw new Error('Failed to fetch status');
|
|
365
373
|
}
|
|
@@ -370,9 +378,7 @@ export class DeployCommand {
|
|
|
370
378
|
}
|
|
371
379
|
catch (error) {
|
|
372
380
|
spinner.stop();
|
|
373
|
-
|
|
374
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
375
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
381
|
+
throw commandFailure(error, { code: 'DEPLOY_STATUS_FAILED' });
|
|
376
382
|
}
|
|
377
383
|
}
|
|
378
384
|
/**
|
|
@@ -382,11 +388,11 @@ export class DeployCommand {
|
|
|
382
388
|
this.requireAuth();
|
|
383
389
|
const spinner = createSpinner(`Verifying DNS for ${domain}...`).start();
|
|
384
390
|
try {
|
|
385
|
-
const response = await
|
|
391
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/domain/verify`, {
|
|
386
392
|
method: 'POST',
|
|
387
393
|
headers: this.getAuthHeaders(),
|
|
388
394
|
body: JSON.stringify({ domain })
|
|
389
|
-
});
|
|
395
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
390
396
|
const data = await response.json();
|
|
391
397
|
if (!response.ok || !data.success) {
|
|
392
398
|
throw new Error(data.error || 'Verification failed');
|
|
@@ -404,9 +410,7 @@ export class DeployCommand {
|
|
|
404
410
|
}
|
|
405
411
|
catch (error) {
|
|
406
412
|
spinner.stop();
|
|
407
|
-
|
|
408
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
409
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
413
|
+
throw commandFailure(error, { code: 'DEPLOY_VERIFY_FAILED' });
|
|
410
414
|
}
|
|
411
415
|
}
|
|
412
416
|
/**
|
|
@@ -426,10 +430,10 @@ export class DeployCommand {
|
|
|
426
430
|
}
|
|
427
431
|
const spinner = createSpinner(`Removing ${domain}...`).start();
|
|
428
432
|
try {
|
|
429
|
-
const response = await
|
|
433
|
+
const response = await guardedFetch(`${this.apiBase}/api/hosting/domain/${encodeURIComponent(domain)}`, {
|
|
430
434
|
method: 'DELETE',
|
|
431
435
|
headers: this.getAuthHeaders()
|
|
432
|
-
});
|
|
436
|
+
}, { audience: 'coder', maxRedirects: 0 });
|
|
433
437
|
if (!response.ok) {
|
|
434
438
|
const error = await response.json();
|
|
435
439
|
throw new Error(error.error || 'Failed to remove');
|
|
@@ -439,9 +443,7 @@ export class DeployCommand {
|
|
|
439
443
|
}
|
|
440
444
|
catch (error) {
|
|
441
445
|
spinner.stop();
|
|
442
|
-
|
|
443
|
-
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
444
|
-
console.log(chalk.red(`\n❌ Error: ${errMsg}\n`));
|
|
446
|
+
throw commandFailure(error, { code: 'DEPLOY_REMOVE_FAILED' });
|
|
445
447
|
}
|
|
446
448
|
}
|
|
447
449
|
/**
|
|
@@ -9,8 +9,7 @@ type DeviceOptions = {
|
|
|
9
9
|
port?: string;
|
|
10
10
|
};
|
|
11
11
|
export declare class DeviceCommand {
|
|
12
|
-
|
|
13
|
-
constructor(_config: Config, logger: Logger);
|
|
12
|
+
constructor(_config: Config, _logger: Logger);
|
|
14
13
|
status(options?: DeviceOptions): Promise<void>;
|
|
15
14
|
list(options?: DeviceOptions): Promise<void>;
|
|
16
15
|
screenshot(options?: DeviceOptions): Promise<void>;
|
package/dist/commands/device.js
CHANGED
|
@@ -5,12 +5,11 @@ import * as path from 'path';
|
|
|
5
5
|
import { execFile, spawn } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { CH } from '../utils/logger.js';
|
|
8
|
+
import { safeChildProcessEnv } from '../utils/secret-policy.js';
|
|
9
|
+
import { CliCommandError, formatSuccessJson } from '../utils/command-contract.js';
|
|
8
10
|
const execFileAsync = promisify(execFile);
|
|
9
11
|
export class DeviceCommand {
|
|
10
|
-
|
|
11
|
-
constructor(_config, logger) {
|
|
12
|
-
this.logger = logger;
|
|
13
|
-
}
|
|
12
|
+
constructor(_config, _logger) { }
|
|
14
13
|
async status(options = {}) {
|
|
15
14
|
const adbPath = this.findAdbPath();
|
|
16
15
|
const devices = adbPath ? await this.getDevices(adbPath) : [];
|
|
@@ -21,7 +20,7 @@ export class DeviceCommand {
|
|
|
21
20
|
devices,
|
|
22
21
|
};
|
|
23
22
|
if (options.json) {
|
|
24
|
-
console.log(
|
|
23
|
+
console.log(formatSuccessJson('device status', payload));
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
26
|
console.log();
|
|
@@ -49,7 +48,7 @@ export class DeviceCommand {
|
|
|
49
48
|
const adbPath = this.requireAdbPath();
|
|
50
49
|
const devices = await this.getDevices(adbPath);
|
|
51
50
|
if (options.json) {
|
|
52
|
-
console.log(
|
|
51
|
+
console.log(formatSuccessJson('device list', { devices }));
|
|
53
52
|
return;
|
|
54
53
|
}
|
|
55
54
|
if (devices.length === 0) {
|
|
@@ -65,7 +64,7 @@ export class DeviceCommand {
|
|
|
65
64
|
const deviceId = await this.resolveDeviceId(adbPath, options.device);
|
|
66
65
|
const output = path.resolve(options.output || `vigthoria-android-screenshot-${this.timestamp()}.png`);
|
|
67
66
|
const args = this.withDevice(deviceId, ['exec-out', 'screencap', '-p']);
|
|
68
|
-
const result = await execFileAsync(adbPath, args, { encoding: 'buffer', maxBuffer: 20 * 1024 * 1024 });
|
|
67
|
+
const result = await execFileAsync(adbPath, args, { encoding: 'buffer', maxBuffer: 20 * 1024 * 1024, env: safeChildProcessEnv() });
|
|
69
68
|
fs.writeFileSync(output, result.stdout);
|
|
70
69
|
console.log(chalk.green(`Saved screenshot: ${output}`));
|
|
71
70
|
}
|
|
@@ -78,7 +77,7 @@ export class DeviceCommand {
|
|
|
78
77
|
const adbPath = this.requireAdbPath();
|
|
79
78
|
const deviceId = await this.resolveDeviceId(adbPath, options.device);
|
|
80
79
|
const result = await this.runAdb(adbPath, this.withDevice(deviceId, ['install', '-r', resolvedApk]), 120000);
|
|
81
|
-
this.printResult(result, options.json);
|
|
80
|
+
this.printResult('device install', result, options.json);
|
|
82
81
|
}
|
|
83
82
|
async launch(packageName, options = {}) {
|
|
84
83
|
if (!packageName)
|
|
@@ -89,7 +88,7 @@ export class DeviceCommand {
|
|
|
89
88
|
? ['shell', 'am', 'start', '-n', `${packageName}/${options.activity}`]
|
|
90
89
|
: ['shell', 'monkey', '-p', packageName, '-c', 'android.intent.category.LAUNCHER', '1'];
|
|
91
90
|
const result = await this.runAdb(adbPath, this.withDevice(deviceId, args));
|
|
92
|
-
this.printResult(result, options.json);
|
|
91
|
+
this.printResult('device launch', result, options.json);
|
|
93
92
|
}
|
|
94
93
|
async logs(options = {}) {
|
|
95
94
|
const adbPath = this.requireAdbPath();
|
|
@@ -98,10 +97,23 @@ export class DeviceCommand {
|
|
|
98
97
|
const args = this.withDevice(deviceId, options.follow ? ['logcat'] : ['logcat', '-d', '-t', lines]);
|
|
99
98
|
if (options.follow) {
|
|
100
99
|
await new Promise((resolve, reject) => {
|
|
101
|
-
const child = spawn(adbPath, args, { stdio: 'inherit' });
|
|
102
|
-
|
|
100
|
+
const child = spawn(adbPath, args, { stdio: 'inherit', env: safeChildProcessEnv() });
|
|
101
|
+
let interrupted = false;
|
|
102
|
+
const onInterrupt = () => {
|
|
103
|
+
interrupted = true;
|
|
104
|
+
child.kill('SIGINT');
|
|
105
|
+
};
|
|
106
|
+
process.once('SIGINT', onInterrupt);
|
|
107
|
+
const finish = () => process.removeListener('SIGINT', onInterrupt);
|
|
108
|
+
child.on('error', (error) => {
|
|
109
|
+
finish();
|
|
110
|
+
reject(error);
|
|
111
|
+
});
|
|
103
112
|
child.on('exit', (code) => {
|
|
104
|
-
|
|
113
|
+
finish();
|
|
114
|
+
if (interrupted)
|
|
115
|
+
reject(new CliCommandError('Device log stream cancelled by user.', { code: 'DEVICE_LOGS_CANCELLED', category: 'cancelled' }));
|
|
116
|
+
else if (code && code !== 0)
|
|
105
117
|
reject(new Error(`adb logcat exited with code ${code}`));
|
|
106
118
|
else
|
|
107
119
|
resolve();
|
|
@@ -110,26 +122,26 @@ export class DeviceCommand {
|
|
|
110
122
|
return;
|
|
111
123
|
}
|
|
112
124
|
const result = await this.runAdb(adbPath, args, 30000, 8 * 1024 * 1024);
|
|
113
|
-
this.printResult(result, options.json);
|
|
125
|
+
this.printResult('device logs', result, options.json);
|
|
114
126
|
}
|
|
115
127
|
async tcpip(options = {}) {
|
|
116
128
|
const adbPath = this.requireAdbPath();
|
|
117
129
|
const deviceId = await this.resolveDeviceId(adbPath, options.device);
|
|
118
130
|
const port = String(options.port || '5555');
|
|
119
131
|
const result = await this.runAdb(adbPath, this.withDevice(deviceId, ['tcpip', port]));
|
|
120
|
-
this.printResult(result, options.json);
|
|
132
|
+
this.printResult('device tcpip', result, options.json);
|
|
121
133
|
}
|
|
122
134
|
async connect(address, options = {}) {
|
|
123
135
|
if (!address)
|
|
124
136
|
throw new Error('Device address is required, for example 192.168.1.30:5555.');
|
|
125
137
|
const adbPath = this.requireAdbPath();
|
|
126
138
|
const result = await this.runAdb(adbPath, ['connect', address]);
|
|
127
|
-
this.printResult(result, options.json);
|
|
139
|
+
this.printResult('device connect', result, options.json);
|
|
128
140
|
}
|
|
129
141
|
async disconnect(address, options = {}) {
|
|
130
142
|
const adbPath = this.requireAdbPath();
|
|
131
143
|
const result = await this.runAdb(adbPath, address ? ['disconnect', address] : ['disconnect']);
|
|
132
|
-
this.printResult(result, options.json);
|
|
144
|
+
this.printResult('device disconnect', result, options.json);
|
|
133
145
|
}
|
|
134
146
|
async getDevices(adbPath) {
|
|
135
147
|
const result = await this.runAdb(adbPath, ['devices', '-l']);
|
|
@@ -176,7 +188,7 @@ export class DeviceCommand {
|
|
|
176
188
|
}
|
|
177
189
|
async runAdb(adbPath, args, timeout = 30000, maxBuffer = 1024 * 1024) {
|
|
178
190
|
try {
|
|
179
|
-
const result = await execFileAsync(adbPath, args, { timeout, maxBuffer });
|
|
191
|
+
const result = await execFileAsync(adbPath, args, { timeout, maxBuffer, env: safeChildProcessEnv() });
|
|
180
192
|
return { success: true, stdout: String(result.stdout || '').trim(), stderr: String(result.stderr || '').trim() };
|
|
181
193
|
}
|
|
182
194
|
catch (error) {
|
|
@@ -188,17 +200,21 @@ export class DeviceCommand {
|
|
|
188
200
|
};
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
|
-
printResult(result, json = false) {
|
|
203
|
+
printResult(command, result, json = false) {
|
|
204
|
+
if (!result.success) {
|
|
205
|
+
throw new CliCommandError(result.stderr || `${command} failed`, {
|
|
206
|
+
code: 'DEVICE_COMMAND_FAILED',
|
|
207
|
+
details: { command, stdout: result.stdout },
|
|
208
|
+
});
|
|
209
|
+
}
|
|
192
210
|
if (json) {
|
|
193
|
-
console.log(
|
|
211
|
+
console.log(formatSuccessJson(command, result));
|
|
194
212
|
return;
|
|
195
213
|
}
|
|
196
214
|
if (result.stdout)
|
|
197
215
|
console.log(result.stdout);
|
|
198
216
|
if (result.stderr)
|
|
199
217
|
console.error(result.success ? chalk.gray(result.stderr) : chalk.red(result.stderr));
|
|
200
|
-
if (!result.success)
|
|
201
|
-
process.exitCode = 1;
|
|
202
218
|
}
|
|
203
219
|
requireAdbPath() {
|
|
204
220
|
const adbPath = this.findAdbPath();
|