dexto 1.6.12 → 1.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/analytics/events.d.ts +1 -1
- package/dist/analytics/events.d.ts.map +1 -1
- package/dist/cli/auth/constants.d.ts.map +1 -1
- package/dist/cli/auth/constants.js +1 -1
- package/dist/cli/commands/agents/install.d.ts.map +1 -0
- package/dist/cli/commands/{install.js → agents/install.js} +4 -4
- package/dist/cli/commands/{list-agents.d.ts → agents/list.d.ts} +1 -1
- package/dist/cli/commands/agents/list.d.ts.map +1 -0
- package/dist/cli/commands/{list-agents.js → agents/list.js} +2 -2
- package/dist/cli/commands/agents/register.js +5 -5
- package/dist/cli/commands/{sync-agents.d.ts → agents/sync.d.ts} +10 -2
- package/dist/cli/commands/agents/sync.d.ts.map +1 -0
- package/dist/cli/commands/{sync-agents.js → agents/sync.js} +54 -5
- package/dist/cli/commands/agents/uninstall.d.ts +18 -0
- package/dist/cli/commands/agents/uninstall.d.ts.map +1 -0
- package/dist/cli/commands/agents/uninstall.js +141 -0
- package/dist/cli/commands/deploy/client.d.ts +51 -0
- package/dist/cli/commands/deploy/client.d.ts.map +1 -0
- package/dist/cli/commands/deploy/client.js +269 -0
- package/dist/cli/commands/deploy/config.d.ts +81 -0
- package/dist/cli/commands/deploy/config.d.ts.map +1 -0
- package/dist/cli/commands/deploy/config.js +144 -0
- package/dist/cli/commands/deploy/entry-agent.d.ts +3 -0
- package/dist/cli/commands/deploy/entry-agent.d.ts.map +1 -0
- package/dist/cli/commands/deploy/entry-agent.js +22 -0
- package/dist/cli/commands/deploy/index.d.ts +11 -0
- package/dist/cli/commands/deploy/index.d.ts.map +1 -0
- package/dist/cli/commands/deploy/index.js +283 -0
- package/dist/cli/commands/deploy/links.d.ts +3 -0
- package/dist/cli/commands/deploy/links.d.ts.map +1 -0
- package/dist/cli/commands/deploy/links.js +53 -0
- package/dist/cli/commands/deploy/register.d.ts +6 -0
- package/dist/cli/commands/deploy/register.d.ts.map +1 -0
- package/dist/cli/commands/deploy/register.js +109 -0
- package/dist/cli/commands/deploy/snapshot.d.ts +12 -0
- package/dist/cli/commands/deploy/snapshot.d.ts.map +1 -0
- package/dist/cli/commands/deploy/snapshot.js +76 -0
- package/dist/cli/commands/deploy/state.d.ts +21 -0
- package/dist/cli/commands/deploy/state.d.ts.map +1 -0
- package/dist/cli/commands/deploy/state.js +122 -0
- package/dist/cli/commands/index.d.ts +6 -4
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +304 -31
- package/dist/cli/commands/uninstall.d.ts +9 -12
- package/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/dist/cli/commands/uninstall.js +99 -113
- package/dist/cli/commands/upgrade.d.ts +15 -0
- package/dist/cli/commands/upgrade.d.ts.map +1 -0
- package/dist/cli/commands/upgrade.js +106 -0
- package/dist/cli/modes/cli.d.ts.map +1 -1
- package/dist/cli/modes/cli.js +0 -12
- package/dist/cli/utils/config-validation.d.ts.map +1 -1
- package/dist/cli/utils/config-validation.js +34 -20
- package/dist/cli/utils/self-management.d.ts +93 -0
- package/dist/cli/utils/self-management.d.ts.map +1 -0
- package/dist/cli/utils/self-management.js +423 -0
- package/dist/cli/utils/version-check.d.ts +1 -1
- package/dist/cli/utils/version-check.d.ts.map +1 -1
- package/dist/cli/utils/version-check.js +53 -19
- package/dist/index-main.js +59 -2
- package/dist/webui/assets/{index-CNiOYnOb.js → index-UDAdxmci.js} +187 -187
- package/dist/webui/index.html +1 -1
- package/package.json +13 -11
- package/dist/cli/commands/install.d.ts.map +0 -1
- package/dist/cli/commands/list-agents.d.ts.map +0 -1
- package/dist/cli/commands/sync-agents.d.ts.map +0 -1
- /package/dist/cli/commands/{install.d.ts → agents/install.d.ts} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import { findDextoProjectRoot } from '@dexto/agent-management';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import { confirmOrExit } from '../../utils/prompt-helpers.js';
|
|
8
|
+
import { createCloudDefaultDeployConfig, createWorkspaceDeployConfig, getDeployConfigPath, isWorkspaceDeployAgent, loadDeployConfig, resolveWorkspaceDeployAgentPath, saveDeployConfig, } from './config.js';
|
|
9
|
+
import { createDeployClient } from './client.js';
|
|
10
|
+
import { discoverPrimaryWorkspaceAgent, isAgentYamlPath } from './entry-agent.js';
|
|
11
|
+
import { getCloudAgentDashboardUrl } from './links.js';
|
|
12
|
+
import { loadWorkspaceDeployLink, removeWorkspaceDeployLink, saveWorkspaceDeployLink, } from './state.js';
|
|
13
|
+
import { createWorkspaceSnapshot } from './snapshot.js';
|
|
14
|
+
function isInteractive(options) {
|
|
15
|
+
return options?.interactive !== false;
|
|
16
|
+
}
|
|
17
|
+
function describeDeployAgent(config) {
|
|
18
|
+
if (isWorkspaceDeployAgent(config.agent)) {
|
|
19
|
+
return `workspace agent (${config.agent.path})`;
|
|
20
|
+
}
|
|
21
|
+
return 'default cloud agent';
|
|
22
|
+
}
|
|
23
|
+
function formatCloudAgentSummary(input) {
|
|
24
|
+
return [
|
|
25
|
+
`Cloud agent: ${input.cloudAgentId}`,
|
|
26
|
+
`Status: ${input.status}`,
|
|
27
|
+
`Agent URL: ${input.agentUrl}`,
|
|
28
|
+
`Dashboard: ${getCloudAgentDashboardUrl(input.cloudAgentId)}`,
|
|
29
|
+
].join('\n');
|
|
30
|
+
}
|
|
31
|
+
function getErrorMessage(error) {
|
|
32
|
+
return error instanceof Error ? error.message : String(error);
|
|
33
|
+
}
|
|
34
|
+
function formatCloudAgentStatus(status) {
|
|
35
|
+
switch (status) {
|
|
36
|
+
case 'ready':
|
|
37
|
+
return chalk.green(status);
|
|
38
|
+
case 'stopped':
|
|
39
|
+
return chalk.yellow(status);
|
|
40
|
+
case 'failed':
|
|
41
|
+
case 'deleted':
|
|
42
|
+
return chalk.red(status);
|
|
43
|
+
default:
|
|
44
|
+
return chalk.cyan(status);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function formatCloudAgentListItem(cloudAgent, linkedToWorkspace) {
|
|
48
|
+
const prefix = linkedToWorkspace ? chalk.green('→') : ' ';
|
|
49
|
+
const trimmedName = cloudAgent.name?.trim();
|
|
50
|
+
const heading = trimmedName && trimmedName !== cloudAgent.cloudAgentId
|
|
51
|
+
? `${chalk.cyan(trimmedName)} ${chalk.gray(`(${cloudAgent.cloudAgentId})`)}`
|
|
52
|
+
: chalk.cyan(cloudAgent.cloudAgentId);
|
|
53
|
+
const status = `${chalk.gray('[')}${formatCloudAgentStatus(cloudAgent.state.status)}${chalk.gray(']')}`;
|
|
54
|
+
const lines = [`${prefix} ${heading} ${status}`];
|
|
55
|
+
if (linkedToWorkspace) {
|
|
56
|
+
lines.push(` ${chalk.green('Linked to this workspace')}`);
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
function resolveWorkspaceRoot() {
|
|
61
|
+
return findDextoProjectRoot(process.cwd()) ?? process.cwd();
|
|
62
|
+
}
|
|
63
|
+
async function resolveDeployConfig(workspaceRoot) {
|
|
64
|
+
const existingConfig = await loadDeployConfig(workspaceRoot);
|
|
65
|
+
if (existingConfig) {
|
|
66
|
+
return existingConfig;
|
|
67
|
+
}
|
|
68
|
+
const primaryWorkspaceAgent = await discoverPrimaryWorkspaceAgent(workspaceRoot);
|
|
69
|
+
const nextConfig = primaryWorkspaceAgent
|
|
70
|
+
? createWorkspaceDeployConfig(primaryWorkspaceAgent)
|
|
71
|
+
: createCloudDefaultDeployConfig();
|
|
72
|
+
await saveDeployConfig(workspaceRoot, nextConfig);
|
|
73
|
+
p.note(`${describeDeployAgent(nextConfig)}\n${path.relative(workspaceRoot, getDeployConfigPath(workspaceRoot))}`, 'Saved deploy config');
|
|
74
|
+
return nextConfig;
|
|
75
|
+
}
|
|
76
|
+
function ensureWorkspaceAgentExists(workspaceRoot, config) {
|
|
77
|
+
if (!isWorkspaceDeployAgent(config.agent)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const entryAgentPath = resolveWorkspaceDeployAgentPath(workspaceRoot, config.agent.path);
|
|
81
|
+
if (!existsSync(entryAgentPath)) {
|
|
82
|
+
throw new Error(`Workspace agent not found: ${config.agent.path}. Update ${path.relative(workspaceRoot, getDeployConfigPath(workspaceRoot))} before deploying.`);
|
|
83
|
+
}
|
|
84
|
+
if (!isAgentYamlPath(config.agent.path)) {
|
|
85
|
+
throw new Error(`Workspace agent must be a .yml or .yaml file: ${config.agent.path}`);
|
|
86
|
+
}
|
|
87
|
+
return entryAgentPath;
|
|
88
|
+
}
|
|
89
|
+
export async function handleDeployCommand(options) {
|
|
90
|
+
void options;
|
|
91
|
+
p.intro(chalk.inverse('Deploy Workspace'));
|
|
92
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
93
|
+
const deployConfig = await resolveDeployConfig(workspaceRoot);
|
|
94
|
+
ensureWorkspaceAgentExists(workspaceRoot, deployConfig);
|
|
95
|
+
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
96
|
+
const spinner = p.spinner();
|
|
97
|
+
const client = createDeployClient();
|
|
98
|
+
const workspaceSnapshotInput = isWorkspaceDeployAgent(deployConfig.agent)
|
|
99
|
+
? {
|
|
100
|
+
workspaceRoot,
|
|
101
|
+
workspaceAgentPath: deployConfig.agent.path,
|
|
102
|
+
exclude: deployConfig.exclude,
|
|
103
|
+
}
|
|
104
|
+
: {
|
|
105
|
+
workspaceRoot,
|
|
106
|
+
exclude: deployConfig.exclude,
|
|
107
|
+
};
|
|
108
|
+
let snapshot = null;
|
|
109
|
+
try {
|
|
110
|
+
spinner.start('Packaging workspace snapshot...');
|
|
111
|
+
snapshot = await createWorkspaceSnapshot({
|
|
112
|
+
...workspaceSnapshotInput,
|
|
113
|
+
});
|
|
114
|
+
spinner.message('Uploading workspace and provisioning sandbox...');
|
|
115
|
+
const deployed = await client.deployWorkspace({
|
|
116
|
+
agent: deployConfig.agent,
|
|
117
|
+
snapshotPath: snapshot.archivePath,
|
|
118
|
+
...(deployLink?.cloudAgentId ? { cloudAgentId: deployLink.cloudAgentId } : {}),
|
|
119
|
+
});
|
|
120
|
+
let linkSyncError = null;
|
|
121
|
+
try {
|
|
122
|
+
await saveWorkspaceDeployLink(workspaceRoot, {
|
|
123
|
+
cloudAgentId: deployed.cloudAgentId,
|
|
124
|
+
agentUrl: deployed.agentUrl,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
linkSyncError = error;
|
|
129
|
+
}
|
|
130
|
+
spinner.stop(chalk.green('✓ Workspace deployed'));
|
|
131
|
+
p.outro([
|
|
132
|
+
formatCloudAgentSummary({
|
|
133
|
+
cloudAgentId: deployed.cloudAgentId,
|
|
134
|
+
status: deployed.state.status,
|
|
135
|
+
agentUrl: deployed.agentUrl,
|
|
136
|
+
}),
|
|
137
|
+
...(linkSyncError
|
|
138
|
+
? [
|
|
139
|
+
'',
|
|
140
|
+
`Warning: deployment succeeded, but failed to save local link state (${getErrorMessage(linkSyncError)})`,
|
|
141
|
+
'Run `dexto deploy` again in this workspace to re-link.',
|
|
142
|
+
]
|
|
143
|
+
: []),
|
|
144
|
+
'',
|
|
145
|
+
'Next steps:',
|
|
146
|
+
`- Open the dashboard to inspect and interact with the deployment`,
|
|
147
|
+
`- Run \`dexto deploy status\` from this workspace`,
|
|
148
|
+
].join('\n'));
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
spinner.stop(chalk.red('✗ Deploy failed'));
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
if (snapshot) {
|
|
156
|
+
await snapshot.cleanup();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export async function handleDeployListCommand() {
|
|
161
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
162
|
+
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
163
|
+
const client = createDeployClient();
|
|
164
|
+
const cloudAgents = await client.listCloudAgents();
|
|
165
|
+
if (cloudAgents.length === 0) {
|
|
166
|
+
p.outro('No cloud deployments found.\nRun `dexto deploy` from this workspace to create one.');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const linkedCloudAgentId = deployLink?.cloudAgentId ?? null;
|
|
170
|
+
const sortedCloudAgents = [...cloudAgents].sort((left, right) => {
|
|
171
|
+
if (left.cloudAgentId === linkedCloudAgentId && right.cloudAgentId !== linkedCloudAgentId) {
|
|
172
|
+
return -1;
|
|
173
|
+
}
|
|
174
|
+
if (right.cloudAgentId === linkedCloudAgentId && left.cloudAgentId !== linkedCloudAgentId) {
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
return 0;
|
|
178
|
+
});
|
|
179
|
+
const linkedVisible = linkedCloudAgentId !== null &&
|
|
180
|
+
sortedCloudAgents.some((cloudAgent) => cloudAgent.cloudAgentId === linkedCloudAgentId);
|
|
181
|
+
const lines = [
|
|
182
|
+
'Cloud deployments',
|
|
183
|
+
'',
|
|
184
|
+
...sortedCloudAgents.flatMap((cloudAgent, index) => {
|
|
185
|
+
const entry = formatCloudAgentListItem(cloudAgent, cloudAgent.cloudAgentId === linkedCloudAgentId);
|
|
186
|
+
return index === 0 ? [entry] : ['', entry];
|
|
187
|
+
}),
|
|
188
|
+
];
|
|
189
|
+
if (linkedCloudAgentId && !linkedVisible) {
|
|
190
|
+
lines.push('', chalk.yellow(`This workspace is linked to ${linkedCloudAgentId}, but that deployment was not returned by the cloud API.`));
|
|
191
|
+
}
|
|
192
|
+
p.outro(lines.join('\n'));
|
|
193
|
+
}
|
|
194
|
+
export async function handleDeployOpenCommand() {
|
|
195
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
196
|
+
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
197
|
+
if (!deployLink) {
|
|
198
|
+
throw new Error('This workspace is not linked to a cloud deployment yet. Run `dexto deploy` first.');
|
|
199
|
+
}
|
|
200
|
+
const dashboardUrl = getCloudAgentDashboardUrl(deployLink.cloudAgentId);
|
|
201
|
+
try {
|
|
202
|
+
await open(dashboardUrl);
|
|
203
|
+
p.outro(`Opened dashboard for ${deployLink.cloudAgentId}\n${dashboardUrl}`);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
p.outro([
|
|
207
|
+
`Unable to open the dashboard automatically (${getErrorMessage(error)})`,
|
|
208
|
+
dashboardUrl,
|
|
209
|
+
].join('\n'));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function handleDeployStatusCommand() {
|
|
213
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
214
|
+
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
215
|
+
if (!deployLink) {
|
|
216
|
+
throw new Error('This workspace is not linked to a cloud deployment yet. Run `dexto deploy` first.');
|
|
217
|
+
}
|
|
218
|
+
const client = createDeployClient();
|
|
219
|
+
const status = await client.getCloudAgent(deployLink.cloudAgentId);
|
|
220
|
+
p.outro(formatCloudAgentSummary({
|
|
221
|
+
cloudAgentId: status.cloudAgentId,
|
|
222
|
+
status: status.state.status,
|
|
223
|
+
agentUrl: status.agentUrl,
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
export async function handleDeployStopCommand() {
|
|
227
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
228
|
+
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
229
|
+
if (!deployLink) {
|
|
230
|
+
throw new Error('This workspace is not linked to a cloud deployment yet. Run `dexto deploy` first.');
|
|
231
|
+
}
|
|
232
|
+
const spinner = p.spinner();
|
|
233
|
+
spinner.start('Stopping cloud sandbox...');
|
|
234
|
+
try {
|
|
235
|
+
const client = createDeployClient();
|
|
236
|
+
const result = await client.stopCloudAgent(deployLink.cloudAgentId);
|
|
237
|
+
spinner.stop(chalk.green('✓ Cloud sandbox stopped'));
|
|
238
|
+
p.outro([
|
|
239
|
+
`Cloud agent: ${result.cloudAgentId}`,
|
|
240
|
+
`Stopped: ${result.stopped ? 'yes' : 'no'}`,
|
|
241
|
+
`Already stopped: ${result.alreadyStopped ? 'yes' : 'no'}`,
|
|
242
|
+
`Snapshot status: ${result.snapshotStatus}`,
|
|
243
|
+
].join('\n'));
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
spinner.stop(chalk.red('✗ Stop failed'));
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
export async function handleDeployDeleteCommand(options) {
|
|
251
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
252
|
+
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
253
|
+
if (!deployLink) {
|
|
254
|
+
throw new Error('This workspace is not linked to a cloud deployment yet. Run `dexto deploy` first.');
|
|
255
|
+
}
|
|
256
|
+
if (isInteractive(options)) {
|
|
257
|
+
await confirmOrExit({
|
|
258
|
+
message: `Delete cloud deployment ${deployLink.cloudAgentId}?`,
|
|
259
|
+
initialValue: false,
|
|
260
|
+
}, 'Delete cancelled');
|
|
261
|
+
}
|
|
262
|
+
const spinner = p.spinner();
|
|
263
|
+
spinner.start('Deleting cloud deployment...');
|
|
264
|
+
try {
|
|
265
|
+
const client = createDeployClient();
|
|
266
|
+
const result = await client.deleteCloudAgent(deployLink.cloudAgentId);
|
|
267
|
+
let linkRemoveError = null;
|
|
268
|
+
try {
|
|
269
|
+
await removeWorkspaceDeployLink(workspaceRoot);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
linkRemoveError = error;
|
|
273
|
+
}
|
|
274
|
+
spinner.stop(chalk.green('✓ Cloud deployment deleted'));
|
|
275
|
+
p.outro(linkRemoveError
|
|
276
|
+
? `Deleted ${result.cloudAgentId}\nWarning: failed to remove local deploy link state (${getErrorMessage(linkRemoveError)})`
|
|
277
|
+
: `Deleted ${result.cloudAgentId}`);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
spinner.stop(chalk.red('✗ Delete failed'));
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/links.ts"],"names":[],"mappings":"AAgCA,wBAAgB,uBAAuB,IAAI,MAAM,CA2BhD;AAED,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const APP_URL_ENV_VAR = 'DEXTO_APP_URL';
|
|
2
|
+
const SANDBOX_URL_ENV_VAR = 'DEXTO_SANDBOX_URL';
|
|
3
|
+
const DEFAULT_APP_URL = 'https://app.dexto.ai';
|
|
4
|
+
const LOCAL_DASHBOARD_URL = 'http://localhost:5173';
|
|
5
|
+
function normalizeBaseUrl(value) {
|
|
6
|
+
const trimmed = value?.trim();
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(trimmed);
|
|
12
|
+
url.pathname = '';
|
|
13
|
+
url.search = '';
|
|
14
|
+
url.hash = '';
|
|
15
|
+
return url.toString().replace(/\/$/, '');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isLocalHostUrl(value) {
|
|
22
|
+
const normalized = normalizeBaseUrl(value);
|
|
23
|
+
if (!normalized) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const hostname = new URL(normalized).hostname;
|
|
27
|
+
return hostname === 'localhost' || hostname === '127.0.0.1';
|
|
28
|
+
}
|
|
29
|
+
export function resolveDashboardBaseUrl() {
|
|
30
|
+
const explicitAppUrl = normalizeBaseUrl(process.env[APP_URL_ENV_VAR]);
|
|
31
|
+
if (explicitAppUrl) {
|
|
32
|
+
return explicitAppUrl;
|
|
33
|
+
}
|
|
34
|
+
if (isLocalHostUrl(process.env[SANDBOX_URL_ENV_VAR]) ||
|
|
35
|
+
isLocalHostUrl(process.env.DEXTO_API_URL)) {
|
|
36
|
+
return LOCAL_DASHBOARD_URL;
|
|
37
|
+
}
|
|
38
|
+
const normalizedApiUrl = normalizeBaseUrl(process.env.DEXTO_API_URL);
|
|
39
|
+
if (normalizedApiUrl) {
|
|
40
|
+
const apiUrl = new URL(normalizedApiUrl);
|
|
41
|
+
if (apiUrl.hostname === 'api.dexto.ai') {
|
|
42
|
+
return DEFAULT_APP_URL;
|
|
43
|
+
}
|
|
44
|
+
if (apiUrl.hostname.startsWith('api.')) {
|
|
45
|
+
apiUrl.hostname = `app.${apiUrl.hostname.slice('api.'.length)}`;
|
|
46
|
+
return apiUrl.toString().replace(/\/$/, '');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return DEFAULT_APP_URL;
|
|
50
|
+
}
|
|
51
|
+
export function getCloudAgentDashboardUrl(cloudAgentId) {
|
|
52
|
+
return `${resolveDashboardBaseUrl()}/dashboard/cloud-agents/${encodeURIComponent(cloudAgentId)}`;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/register.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,MAAM,WAAW,4BAA4B;IACzC,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,OAAO,EAAE,EAAE,4BAA4B,GAAG,IAAI,CAqHrF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ExitSignal, safeExit, withAnalytics } from '../../../analytics/wrapper.js';
|
|
2
|
+
export function registerDeployCommand({ program }) {
|
|
3
|
+
const deployCommand = program
|
|
4
|
+
.command('deploy')
|
|
5
|
+
.description('Deploy the current workspace to a cloud sandbox');
|
|
6
|
+
deployCommand.addHelpText('after', `
|
|
7
|
+
Examples:
|
|
8
|
+
$ dexto deploy
|
|
9
|
+
$ dexto deploy list
|
|
10
|
+
$ dexto deploy open
|
|
11
|
+
$ dexto deploy status
|
|
12
|
+
$ dexto deploy stop
|
|
13
|
+
$ dexto deploy delete
|
|
14
|
+
`);
|
|
15
|
+
deployCommand.option('--no-interactive', 'Disable interactive prompts').action(withAnalytics('deploy', async (options) => {
|
|
16
|
+
try {
|
|
17
|
+
const { handleDeployCommand } = await import('./index.js');
|
|
18
|
+
await handleDeployCommand(options);
|
|
19
|
+
safeExit('deploy', 0);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
if (err instanceof ExitSignal)
|
|
23
|
+
throw err;
|
|
24
|
+
console.error(`❌ dexto deploy command failed: ${err}`);
|
|
25
|
+
safeExit('deploy', 1, 'error');
|
|
26
|
+
}
|
|
27
|
+
}));
|
|
28
|
+
deployCommand
|
|
29
|
+
.command('list')
|
|
30
|
+
.description('List cloud deployments for your account')
|
|
31
|
+
.action(withAnalytics('deploy list', async () => {
|
|
32
|
+
try {
|
|
33
|
+
const { handleDeployListCommand } = await import('./index.js');
|
|
34
|
+
await handleDeployListCommand();
|
|
35
|
+
safeExit('deploy list', 0);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err instanceof ExitSignal)
|
|
39
|
+
throw err;
|
|
40
|
+
console.error(`❌ dexto deploy list command failed: ${err}`);
|
|
41
|
+
safeExit('deploy list', 1, 'error');
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
deployCommand
|
|
45
|
+
.command('open')
|
|
46
|
+
.description('Open the linked cloud deployment in the dashboard')
|
|
47
|
+
.action(withAnalytics('deploy open', async () => {
|
|
48
|
+
try {
|
|
49
|
+
const { handleDeployOpenCommand } = await import('./index.js');
|
|
50
|
+
await handleDeployOpenCommand();
|
|
51
|
+
safeExit('deploy open', 0);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err instanceof ExitSignal)
|
|
55
|
+
throw err;
|
|
56
|
+
console.error(`❌ dexto deploy open command failed: ${err}`);
|
|
57
|
+
safeExit('deploy open', 1, 'error');
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
deployCommand
|
|
61
|
+
.command('status')
|
|
62
|
+
.description('Show the linked cloud deployment for the current workspace')
|
|
63
|
+
.action(withAnalytics('deploy status', async () => {
|
|
64
|
+
try {
|
|
65
|
+
const { handleDeployStatusCommand } = await import('./index.js');
|
|
66
|
+
await handleDeployStatusCommand();
|
|
67
|
+
safeExit('deploy status', 0);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (err instanceof ExitSignal)
|
|
71
|
+
throw err;
|
|
72
|
+
console.error(`❌ dexto deploy status command failed: ${err}`);
|
|
73
|
+
safeExit('deploy status', 1, 'error');
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
deployCommand
|
|
77
|
+
.command('stop')
|
|
78
|
+
.description('Stop the linked cloud sandbox for the current workspace')
|
|
79
|
+
.action(withAnalytics('deploy stop', async () => {
|
|
80
|
+
try {
|
|
81
|
+
const { handleDeployStopCommand } = await import('./index.js');
|
|
82
|
+
await handleDeployStopCommand();
|
|
83
|
+
safeExit('deploy stop', 0);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err instanceof ExitSignal)
|
|
87
|
+
throw err;
|
|
88
|
+
console.error(`❌ dexto deploy stop command failed: ${err}`);
|
|
89
|
+
safeExit('deploy stop', 1, 'error');
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
deployCommand
|
|
93
|
+
.command('delete')
|
|
94
|
+
.description('Delete the linked cloud deployment and unlink this workspace')
|
|
95
|
+
.option('--no-interactive', 'Disable confirmation prompts')
|
|
96
|
+
.action(withAnalytics('deploy delete', async (options) => {
|
|
97
|
+
try {
|
|
98
|
+
const { handleDeployDeleteCommand } = await import('./index.js');
|
|
99
|
+
await handleDeployDeleteCommand(options);
|
|
100
|
+
safeExit('deploy delete', 0);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (err instanceof ExitSignal)
|
|
104
|
+
throw err;
|
|
105
|
+
console.error(`❌ dexto deploy delete command failed: ${err}`);
|
|
106
|
+
safeExit('deploy delete', 1, 'error');
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface WorkspaceSnapshotResult {
|
|
2
|
+
archivePath: string;
|
|
3
|
+
sizeBytes: number;
|
|
4
|
+
cleanup: () => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function shouldExcludeRelativePath(relativePath: string, excludePatterns: string[]): boolean;
|
|
7
|
+
export declare function createWorkspaceSnapshot(input: {
|
|
8
|
+
workspaceRoot: string;
|
|
9
|
+
workspaceAgentPath?: string;
|
|
10
|
+
exclude: string[];
|
|
11
|
+
}): Promise<WorkspaceSnapshotResult>;
|
|
12
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/snapshot.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,uBAAuB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAiBD,wBAAgB,yBAAyB,CACrC,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,EAAE,GAC1B,OAAO,CAoBT;AAED,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAiDnC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { minimatch } from 'minimatch';
|
|
5
|
+
import { create as createTar } from 'tar';
|
|
6
|
+
import { normalizeWorkspaceRelativePath } from './config.js';
|
|
7
|
+
function normalizeArchivePath(value) {
|
|
8
|
+
return value
|
|
9
|
+
.replace(/\\/g, '/')
|
|
10
|
+
.replace(/^\.\/+/, '')
|
|
11
|
+
.replace(/\/+$/, '');
|
|
12
|
+
}
|
|
13
|
+
function normalizeExcludePattern(value) {
|
|
14
|
+
return value
|
|
15
|
+
.trim()
|
|
16
|
+
.replace(/\\/g, '/')
|
|
17
|
+
.replace(/^\.\/+/, '')
|
|
18
|
+
.replace(/\/+$/, '');
|
|
19
|
+
}
|
|
20
|
+
export function shouldExcludeRelativePath(relativePath, excludePatterns) {
|
|
21
|
+
const normalizedRelativePath = normalizeWorkspaceRelativePath(relativePath);
|
|
22
|
+
const basename = path.posix.basename(normalizedRelativePath);
|
|
23
|
+
return excludePatterns.some((pattern) => {
|
|
24
|
+
const normalizedPattern = normalizeExcludePattern(pattern);
|
|
25
|
+
if (normalizedPattern.length === 0) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (normalizedRelativePath === normalizedPattern ||
|
|
29
|
+
normalizedRelativePath.startsWith(`${normalizedPattern}/`)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return (minimatch(normalizedRelativePath, normalizedPattern, { dot: true }) ||
|
|
33
|
+
minimatch(basename, normalizedPattern, { dot: true }));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function createWorkspaceSnapshot(input) {
|
|
37
|
+
const workspaceRoot = path.resolve(input.workspaceRoot);
|
|
38
|
+
const workspaceAgentPath = typeof input.workspaceAgentPath === 'string'
|
|
39
|
+
? normalizeWorkspaceRelativePath(input.workspaceAgentPath)
|
|
40
|
+
: null;
|
|
41
|
+
if (workspaceAgentPath && shouldExcludeRelativePath(workspaceAgentPath, input.exclude)) {
|
|
42
|
+
throw new Error(`Deploy config excludes the selected workspace agent: ${workspaceAgentPath}. Remove it from exclude before deploying.`);
|
|
43
|
+
}
|
|
44
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dexto-deploy-'));
|
|
45
|
+
const archivePath = path.join(tempDir, 'workspace.tar.gz');
|
|
46
|
+
const topLevelEntries = await fs.readdir(workspaceRoot);
|
|
47
|
+
try {
|
|
48
|
+
await createTar({
|
|
49
|
+
cwd: workspaceRoot,
|
|
50
|
+
file: archivePath,
|
|
51
|
+
gzip: true,
|
|
52
|
+
portable: true,
|
|
53
|
+
noMtime: true,
|
|
54
|
+
prefix: 'workspace',
|
|
55
|
+
filter: (entryPath) => {
|
|
56
|
+
const relativePath = normalizeArchivePath(entryPath);
|
|
57
|
+
if (relativePath.length === 0) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return !shouldExcludeRelativePath(relativePath, input.exclude);
|
|
61
|
+
},
|
|
62
|
+
}, topLevelEntries);
|
|
63
|
+
const stats = await fs.stat(archivePath);
|
|
64
|
+
return {
|
|
65
|
+
archivePath,
|
|
66
|
+
sizeBytes: stats.size,
|
|
67
|
+
cleanup: async () => {
|
|
68
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const DeployLinkSchema: z.ZodObject<{
|
|
3
|
+
cloudAgentId: z.ZodString;
|
|
4
|
+
agentUrl: z.ZodOptional<z.ZodString>;
|
|
5
|
+
updatedAt: z.ZodString;
|
|
6
|
+
}, "strict", z.ZodTypeAny, {
|
|
7
|
+
cloudAgentId: string;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
agentUrl?: string | undefined;
|
|
10
|
+
}, {
|
|
11
|
+
cloudAgentId: string;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
agentUrl?: string | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
export type DeployLink = z.output<typeof DeployLinkSchema>;
|
|
16
|
+
type DeployLinkInput = Pick<z.input<typeof DeployLinkSchema>, 'cloudAgentId' | 'agentUrl'>;
|
|
17
|
+
export declare function loadWorkspaceDeployLink(workspaceRoot: string): Promise<DeployLink | null>;
|
|
18
|
+
export declare function saveWorkspaceDeployLink(workspaceRoot: string, link: DeployLinkInput): Promise<void>;
|
|
19
|
+
export declare function removeWorkspaceDeployLink(workspaceRoot: string): Promise<void>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/state.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,QAAA,MAAM,gBAAgB;;;;;;;;;;;;EAMT,CAAC;AASd,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC3D,KAAK,eAAe,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,EAAE,cAAc,GAAG,UAAU,CAAC,CAAC;AA4F3F,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAG/F;AAED,wBAAsB,uBAAuB,CACzC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpF"}
|