dexto 1.14.4 → 2.0.0-beta.0
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/dist/cli/auth/billing.d.ts +0 -6
- package/dist/cli/auth/billing.d.ts.map +1 -1
- package/dist/cli/auth/billing.js +0 -23
- package/dist/cli/auth/constants.d.ts +3 -31
- package/dist/cli/auth/constants.d.ts.map +1 -1
- package/dist/cli/auth/constants.js +9 -33
- package/dist/cli/auth/index.d.ts +2 -9
- package/dist/cli/auth/index.d.ts.map +1 -1
- package/dist/cli/auth/index.js +2 -7
- package/dist/cli/auth/sign-in-runtime.d.ts +12 -0
- package/dist/cli/auth/sign-in-runtime.d.ts.map +1 -0
- package/dist/cli/auth/sign-in-runtime.js +59 -0
- package/dist/cli/cloud/app-scopes.d.ts +4 -0
- package/dist/cli/cloud/app-scopes.d.ts.map +1 -0
- package/dist/cli/cloud/app-scopes.js +19 -0
- package/dist/cli/cloud/command.d.ts +143 -0
- package/dist/cli/cloud/command.d.ts.map +1 -0
- package/dist/cli/cloud/command.js +202 -0
- package/dist/cli/cloud/commands/account.d.ts +11 -0
- package/dist/cli/cloud/commands/account.d.ts.map +1 -0
- package/dist/cli/cloud/commands/account.js +128 -0
- package/dist/cli/cloud/commands/agents.d.ts +3 -0
- package/dist/cli/cloud/commands/agents.d.ts.map +1 -0
- package/dist/cli/cloud/commands/agents.js +524 -0
- package/dist/cli/cloud/commands/api.d.ts +3 -0
- package/dist/cli/cloud/commands/api.d.ts.map +1 -0
- package/dist/cli/cloud/commands/api.js +26 -0
- package/dist/cli/cloud/commands/app-files.d.ts +29 -0
- package/dist/cli/cloud/commands/app-files.d.ts.map +1 -0
- package/dist/cli/cloud/commands/app-files.js +72 -0
- package/dist/cli/cloud/commands/automations.d.ts +3 -0
- package/dist/cli/cloud/commands/automations.d.ts.map +1 -0
- package/dist/cli/cloud/commands/automations.js +232 -0
- package/dist/cli/cloud/commands/chats.d.ts +3 -0
- package/dist/cli/cloud/commands/chats.d.ts.map +1 -0
- package/dist/cli/cloud/commands/chats.js +189 -0
- package/dist/cli/cloud/commands/data-deletions.d.ts +3 -0
- package/dist/cli/cloud/commands/data-deletions.d.ts.map +1 -0
- package/dist/cli/cloud/commands/data-deletions.js +54 -0
- package/dist/cli/cloud/commands/deploys.d.ts +10 -0
- package/dist/cli/cloud/commands/deploys.d.ts.map +1 -0
- package/dist/cli/cloud/commands/deploys.js +223 -0
- package/dist/cli/cloud/commands/drive.d.ts +12 -0
- package/dist/cli/cloud/commands/drive.d.ts.map +1 -0
- package/dist/cli/cloud/commands/drive.js +320 -0
- package/dist/cli/cloud/commands/files.d.ts +24 -0
- package/dist/cli/cloud/commands/files.d.ts.map +1 -0
- package/dist/cli/cloud/commands/files.js +282 -0
- package/dist/cli/cloud/commands/find.d.ts +3 -0
- package/dist/cli/cloud/commands/find.d.ts.map +1 -0
- package/dist/cli/cloud/commands/find.js +63 -0
- package/dist/cli/cloud/commands/models.d.ts +3 -0
- package/dist/cli/cloud/commands/models.d.ts.map +1 -0
- package/dist/cli/cloud/commands/models.js +248 -0
- package/dist/cli/cloud/commands/profile.d.ts +3 -0
- package/dist/cli/cloud/commands/profile.d.ts.map +1 -0
- package/dist/cli/cloud/commands/profile.js +119 -0
- package/dist/cli/cloud/commands/projects.d.ts +5 -0
- package/dist/cli/cloud/commands/projects.d.ts.map +1 -0
- package/dist/cli/cloud/commands/projects.js +66 -0
- package/dist/cli/cloud/commands/publish.d.ts +3 -0
- package/dist/cli/cloud/commands/publish.d.ts.map +1 -0
- package/dist/cli/cloud/commands/publish.js +138 -0
- package/dist/cli/cloud/commands/sandboxes.d.ts +3 -0
- package/dist/cli/cloud/commands/sandboxes.d.ts.map +1 -0
- package/dist/cli/cloud/commands/sandboxes.js +265 -0
- package/dist/cli/cloud/commands/secrets.d.ts +3 -0
- package/dist/cli/cloud/commands/secrets.d.ts.map +1 -0
- package/dist/cli/cloud/commands/secrets.js +110 -0
- package/dist/cli/cloud/commands/skills.d.ts +3 -0
- package/dist/cli/cloud/commands/skills.d.ts.map +1 -0
- package/dist/cli/cloud/commands/skills.js +202 -0
- package/dist/cli/cloud/commands/web.d.ts +3 -0
- package/dist/cli/cloud/commands/web.d.ts.map +1 -0
- package/dist/cli/cloud/commands/web.js +53 -0
- package/dist/cli/cloud/commands/workspace-apps.d.ts +58 -0
- package/dist/cli/cloud/commands/workspace-apps.d.ts.map +1 -0
- package/dist/cli/cloud/commands/workspace-apps.js +656 -0
- package/dist/cli/cloud/commands/workspace.d.ts +36 -0
- package/dist/cli/cloud/commands/workspace.d.ts.map +1 -0
- package/dist/cli/cloud/commands/workspace.js +279 -0
- package/dist/cli/cloud/connection.d.ts +33 -0
- package/dist/cli/cloud/connection.d.ts.map +1 -0
- package/dist/cli/cloud/connection.js +38 -0
- package/dist/cli/cloud/events.d.ts +63 -0
- package/dist/cli/cloud/events.d.ts.map +1 -0
- package/dist/cli/cloud/events.js +72 -0
- package/dist/cli/cloud/host.d.ts +9 -0
- package/dist/cli/cloud/host.d.ts.map +1 -0
- package/dist/cli/cloud/host.js +105 -0
- package/dist/cli/cloud/local-chats.d.ts +73 -0
- package/dist/cli/cloud/local-chats.d.ts.map +1 -0
- package/dist/cli/cloud/local-chats.js +441 -0
- package/dist/cli/cloud/output.d.ts +34 -0
- package/dist/cli/cloud/output.d.ts.map +1 -0
- package/dist/cli/cloud/output.js +76 -0
- package/dist/cli/cloud/run.d.ts +26 -0
- package/dist/cli/cloud/run.d.ts.map +1 -0
- package/dist/cli/cloud/run.js +444 -0
- package/dist/cli/cloud/runtime.d.ts +33 -0
- package/dist/cli/cloud/runtime.d.ts.map +1 -0
- package/dist/cli/cloud/runtime.js +86 -0
- package/dist/cli/cloud/tar.d.ts +9 -0
- package/dist/cli/cloud/tar.d.ts.map +1 -0
- package/dist/cli/cloud/tar.js +69 -0
- package/dist/cli/cloud/transfer.d.ts +13 -0
- package/dist/cli/cloud/transfer.d.ts.map +1 -0
- package/dist/cli/cloud/transfer.js +35 -0
- package/dist/cli/cloud/v1.d.ts +19 -0
- package/dist/cli/cloud/v1.d.ts.map +1 -0
- package/dist/cli/cloud/v1.js +65 -0
- package/dist/cli/cloud/zip.d.ts +8 -0
- package/dist/cli/cloud/zip.d.ts.map +1 -0
- package/dist/cli/cloud/zip.js +66 -0
- package/dist/cli/commands/auth/sign-in-commands.d.ts +62 -0
- package/dist/cli/commands/auth/sign-in-commands.d.ts.map +1 -0
- package/dist/cli/commands/auth/sign-in-commands.js +247 -0
- package/dist/cli/commands/auth/sign-in-context.d.ts +16 -0
- package/dist/cli/commands/auth/sign-in-context.d.ts.map +1 -0
- package/dist/cli/commands/auth/sign-in-context.js +68 -0
- package/dist/cli/commands/cloud/register.d.ts +5 -0
- package/dist/cli/commands/cloud/register.d.ts.map +1 -1
- package/dist/cli/commands/cloud/register.js +42 -24
- package/dist/cli/commands/create-app.js +5 -6
- package/dist/cli/commands/create-image.d.ts.map +1 -1
- package/dist/cli/commands/create-image.js +9 -10
- package/dist/cli/commands/index.d.ts +0 -2
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +0 -2
- package/dist/cli/commands/init.d.ts +0 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +5 -26
- package/dist/cli/commands/run/headless.js +2 -2
- package/dist/cli/commands/run/output.d.ts +29 -10
- package/dist/cli/commands/run/output.d.ts.map +1 -1
- package/dist/cli/commands/run/output.js +44 -22
- package/dist/cli/commands/run/register.d.ts.map +1 -1
- package/dist/cli/commands/run/register.js +21 -9
- package/dist/cli/commands/session/register.d.ts.map +1 -1
- package/dist/cli/commands/session/register.js +51 -21
- package/dist/cli/commands/session-commands.js +1 -1
- package/dist/cli/commands/setup.js +17 -14
- package/dist/cli/help-json.d.ts +22 -0
- package/dist/cli/help-json.d.ts.map +1 -0
- package/dist/cli/help-json.js +20 -0
- package/dist/cli/modes/cli.d.ts.map +1 -1
- package/dist/cli/modes/cli.js +42 -9
- package/dist/cli/modes/server.js +8 -8
- package/dist/cli/modes/web.js +3 -3
- package/dist/cli/utils/dexto-auth-check.d.ts +1 -1
- package/dist/cli/utils/dexto-auth-check.d.ts.map +1 -1
- package/dist/cli/utils/dexto-auth-check.js +4 -5
- package/dist/cli/utils/dexto-setup.d.ts +4 -5
- package/dist/cli/utils/dexto-setup.d.ts.map +1 -1
- package/dist/cli/utils/dexto-setup.js +17 -14
- package/dist/cli/utils/provider-setup.js +1 -1
- package/dist/cli/utils/scaffolding-utils.d.ts +9 -3
- package/dist/cli/utils/scaffolding-utils.d.ts.map +1 -1
- package/dist/cli/utils/scaffolding-utils.js +48 -40
- package/dist/cli/utils/version-check.d.ts.map +1 -1
- package/dist/cli/utils/version-check.js +2 -1
- package/dist/index-main.js +33 -65
- package/package.json +8 -7
- package/dist/cli/auth/api-client.d.ts +0 -125
- package/dist/cli/auth/api-client.d.ts.map +0 -1
- package/dist/cli/auth/api-client.js +0 -585
- package/dist/cli/auth/device.d.ts +0 -14
- package/dist/cli/auth/device.d.ts.map +0 -1
- package/dist/cli/auth/device.js +0 -84
- package/dist/cli/auth/dexto-api-key.d.ts +0 -34
- package/dist/cli/auth/dexto-api-key.d.ts.map +0 -1
- package/dist/cli/auth/dexto-api-key.js +0 -160
- package/dist/cli/auth/login-persistence.d.ts +0 -15
- package/dist/cli/auth/login-persistence.d.ts.map +0 -1
- package/dist/cli/auth/login-persistence.js +0 -72
- package/dist/cli/auth/oauth.d.ts +0 -39
- package/dist/cli/auth/oauth.d.ts.map +0 -1
- package/dist/cli/auth/oauth.js +0 -434
- package/dist/cli/auth/service.d.ts +0 -24
- package/dist/cli/auth/service.d.ts.map +0 -1
- package/dist/cli/auth/service.js +0 -161
- package/dist/cli/auth/types.d.ts +0 -11
- package/dist/cli/auth/types.d.ts.map +0 -1
- package/dist/cli/auth/types.js +0 -1
- package/dist/cli/cloud-chat.d.ts +0 -18
- package/dist/cli/cloud-chat.d.ts.map +0 -1
- package/dist/cli/cloud-chat.js +0 -734
- package/dist/cli/commands/auth/index.d.ts +0 -4
- package/dist/cli/commands/auth/index.d.ts.map +0 -1
- package/dist/cli/commands/auth/index.js +0 -4
- package/dist/cli/commands/auth/login.d.ts +0 -12
- package/dist/cli/commands/auth/login.d.ts.map +0 -1
- package/dist/cli/commands/auth/login.js +0 -153
- package/dist/cli/commands/auth/logout.d.ts +0 -5
- package/dist/cli/commands/auth/logout.d.ts.map +0 -1
- package/dist/cli/commands/auth/logout.js +0 -81
- package/dist/cli/commands/auth/register.d.ts +0 -3
- package/dist/cli/commands/auth/register.d.ts.map +0 -1
- package/dist/cli/commands/auth/register.js +0 -96
- package/dist/cli/commands/auth/status.d.ts +0 -2
- package/dist/cli/commands/auth/status.d.ts.map +0 -1
- package/dist/cli/commands/auth/status.js +0 -22
- package/dist/cli/commands/billing/index.d.ts +0 -2
- package/dist/cli/commands/billing/index.d.ts.map +0 -1
- package/dist/cli/commands/billing/index.js +0 -2
- package/dist/cli/commands/billing/register.d.ts +0 -3
- package/dist/cli/commands/billing/register.d.ts.map +0 -1
- package/dist/cli/commands/billing/register.js +0 -20
- package/dist/cli/commands/billing/status.d.ts +0 -8
- package/dist/cli/commands/billing/status.d.ts.map +0 -1
- package/dist/cli/commands/billing/status.js +0 -71
- package/dist/cli/commands/cloud/discovery.d.ts +0 -17
- package/dist/cli/commands/cloud/discovery.d.ts.map +0 -1
- package/dist/cli/commands/cloud/discovery.js +0 -88
- package/dist/cli/commands/deploy/client.d.ts +0 -113
- package/dist/cli/commands/deploy/client.d.ts.map +0 -1
- package/dist/cli/commands/deploy/client.js +0 -519
- package/dist/cli/commands/deploy/config.d.ts +0 -37
- package/dist/cli/commands/deploy/config.d.ts.map +0 -1
- package/dist/cli/commands/deploy/config.js +0 -144
- package/dist/cli/commands/deploy/entry-agent.d.ts +0 -3
- package/dist/cli/commands/deploy/entry-agent.d.ts.map +0 -1
- package/dist/cli/commands/deploy/entry-agent.js +0 -70
- package/dist/cli/commands/deploy/index.d.ts +0 -12
- package/dist/cli/commands/deploy/index.d.ts.map +0 -1
- package/dist/cli/commands/deploy/index.js +0 -370
- package/dist/cli/commands/deploy/links.d.ts +0 -3
- package/dist/cli/commands/deploy/links.d.ts.map +0 -1
- package/dist/cli/commands/deploy/links.js +0 -53
- package/dist/cli/commands/deploy/register.d.ts +0 -6
- package/dist/cli/commands/deploy/register.d.ts.map +0 -1
- package/dist/cli/commands/deploy/register.js +0 -128
- package/dist/cli/commands/deploy/snapshot.d.ts +0 -12
- package/dist/cli/commands/deploy/snapshot.d.ts.map +0 -1
- package/dist/cli/commands/deploy/snapshot.js +0 -76
- package/dist/cli/commands/deploy/state.d.ts +0 -13
- package/dist/cli/commands/deploy/state.d.ts.map +0 -1
- package/dist/cli/commands/deploy/state.js +0 -122
- package/dist/cli/commands/span/register.d.ts +0 -6
- package/dist/cli/commands/span/register.d.ts.map +0 -1
- package/dist/cli/commands/span/register.js +0 -25
- package/dist/cli/commands/trace/client.d.ts +0 -98
- package/dist/cli/commands/trace/client.d.ts.map +0 -1
- package/dist/cli/commands/trace/client.js +0 -150
- package/dist/cli/commands/trace/format.d.ts +0 -5
- package/dist/cli/commands/trace/format.d.ts.map +0 -1
- package/dist/cli/commands/trace/format.js +0 -58
- package/dist/cli/commands/trace/index.d.ts +0 -21
- package/dist/cli/commands/trace/index.d.ts.map +0 -1
- package/dist/cli/commands/trace/index.js +0 -49
- package/dist/cli/commands/trace/register.d.ts +0 -6
- package/dist/cli/commands/trace/register.d.ts.map +0 -1
- package/dist/cli/commands/trace/register.js +0 -71
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
// Runs `dexto cloud …` and the other ported cloud commands: global options, help, the agent
|
|
2
|
+
// contract's JSON output and exit codes, and signing in again for a permission a command lacked.
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { CloudApiError, createCloudClient } from '@dexto/cloud-client';
|
|
5
|
+
import { CommandFailure, EXIT_CODE_MEANINGS, EXIT_CODES, MissingScopeFailure, signInCommand, UsageError, } from './command.js';
|
|
6
|
+
import { listWorkspaceApps, runWorkspaceApp, } from './commands/workspace-apps.js';
|
|
7
|
+
import { createOutput } from './output.js';
|
|
8
|
+
export const GLOBAL_OPTIONS = {
|
|
9
|
+
'api-base-url': {
|
|
10
|
+
description: 'The API origin (default https://api.dexto.ai, or DEXTO_API_BASE_URL).',
|
|
11
|
+
type: 'string',
|
|
12
|
+
valueName: 'URL',
|
|
13
|
+
},
|
|
14
|
+
'api-key': {
|
|
15
|
+
description: 'Use this key instead of DEXTO_API_KEY or the saved sign-in.',
|
|
16
|
+
type: 'string',
|
|
17
|
+
valueName: 'KEY',
|
|
18
|
+
},
|
|
19
|
+
profile: {
|
|
20
|
+
description: 'The saved sign-in to use for this run (or DEXTO_PROFILE); see dexto accounts list.',
|
|
21
|
+
type: 'string',
|
|
22
|
+
valueName: 'NAME',
|
|
23
|
+
},
|
|
24
|
+
project: {
|
|
25
|
+
description: "The Cloud project to act in when you're signed in (or DEXTO_CLOUD_PROJECT).",
|
|
26
|
+
type: 'string',
|
|
27
|
+
valueName: 'ID',
|
|
28
|
+
},
|
|
29
|
+
help: { description: 'Show help.', short: 'h', type: 'boolean' },
|
|
30
|
+
json: {
|
|
31
|
+
description: "Print JSON, failures included (on stderr). The default when stdout isn't a terminal.",
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
},
|
|
34
|
+
table: {
|
|
35
|
+
description: "Print tables for people even when stdout isn't a terminal.",
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const NO_OVERRIDES = {
|
|
40
|
+
apiBaseUrl: null,
|
|
41
|
+
apiKey: null,
|
|
42
|
+
cloudProject: null,
|
|
43
|
+
profile: null,
|
|
44
|
+
};
|
|
45
|
+
/** Runs one command line, such as `cloud secrets list --json`; returns its exit code. */
|
|
46
|
+
export async function runCloud(host, argv) {
|
|
47
|
+
return await run(host, argv, true);
|
|
48
|
+
}
|
|
49
|
+
async function run(host, argv, mayGrant) {
|
|
50
|
+
const { runtime } = host;
|
|
51
|
+
let globals;
|
|
52
|
+
try {
|
|
53
|
+
globals = extractGlobals(argv);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// The format isn't settled yet: JSON as asked, or when no one reads stdout.
|
|
57
|
+
return reportError({ overrides: NO_OVERRIDES, runtime }, argv.includes('--json') || !runtime.stdoutIsTerminal, groupHelp(host, argv.slice(0, 1)), error);
|
|
58
|
+
}
|
|
59
|
+
const { command, words, rest } = matchCommand(host.commands, globals.rest);
|
|
60
|
+
// `workspace` alone lists the apps; `workspace <app> …` is one of them, its operations from the
|
|
61
|
+
// server. The CLI's own `workspace` commands, such as `tools` and `drive put`, match above.
|
|
62
|
+
if (command === null && words[0] === 'workspace')
|
|
63
|
+
return await runWorkspace(host, globals, argv, mayGrant);
|
|
64
|
+
if (command === null) {
|
|
65
|
+
if (!host.commands.some((each) => startsWith(each.path, words)))
|
|
66
|
+
return reportError({ overrides: globals.overrides, runtime }, createOutput(runtime, globals.format).json, groupHelp(host, words.slice(0, 1)), new UsageError(`Unknown command: dexto ${words.join(' ')}`));
|
|
67
|
+
return printHelp(runtime, globals, groupHelp(host, words), groupHelpJson(host, words));
|
|
68
|
+
}
|
|
69
|
+
if (globals.help)
|
|
70
|
+
return printHelp(runtime, globals, commandHelp(command), commandHelpJson(command));
|
|
71
|
+
const output = createOutput(runtime, globals.format);
|
|
72
|
+
const signIn = { overrides: globals.overrides, runtime };
|
|
73
|
+
try {
|
|
74
|
+
const parsed = parseArgs({
|
|
75
|
+
allowPositionals: true,
|
|
76
|
+
args: [...rest],
|
|
77
|
+
options: Object.fromEntries(Object.entries(command.options ?? {}).map(([name, spec]) => [
|
|
78
|
+
name,
|
|
79
|
+
{
|
|
80
|
+
type: spec.type,
|
|
81
|
+
...(spec.multiple === true ? { multiple: true } : {}),
|
|
82
|
+
...(spec.short === undefined ? {} : { short: spec.short }),
|
|
83
|
+
},
|
|
84
|
+
])),
|
|
85
|
+
strict: true,
|
|
86
|
+
});
|
|
87
|
+
const required = (command.arguments ?? '').match(/<[^>]+>/gu)?.length ?? 0;
|
|
88
|
+
if (parsed.positionals.length < required)
|
|
89
|
+
throw new UsageError(`Missing arguments: ${command.arguments ?? ''}`);
|
|
90
|
+
return (await command.run(contextFor(host, globals, output), parsed)) ?? 0;
|
|
91
|
+
}
|
|
92
|
+
catch (thrown) {
|
|
93
|
+
const error = asMissingScope(signIn, thrown);
|
|
94
|
+
if (mayGrant &&
|
|
95
|
+
error instanceof MissingScopeFailure &&
|
|
96
|
+
runtime.prompt !== null &&
|
|
97
|
+
!output.json &&
|
|
98
|
+
(await askToGrant(host, globals.overrides, error)))
|
|
99
|
+
return await run(host, argv, false);
|
|
100
|
+
return reportError(signIn, output.json, commandHelp(command), error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** `workspace` or `workspace <app> [operation] [action] [--field value…]`. */
|
|
104
|
+
async function runWorkspace(host, globals, argv, mayGrant) {
|
|
105
|
+
const output = createOutput(host.runtime, globals.format);
|
|
106
|
+
const context = contextFor(host, globals, output);
|
|
107
|
+
const args = globals.rest.slice(1);
|
|
108
|
+
try {
|
|
109
|
+
if (args.every((arg) => arg.startsWith('-'))) {
|
|
110
|
+
const apps = await listWorkspaceApps(context);
|
|
111
|
+
return printHelp(host.runtime, globals, workspaceHelp(host, apps), {
|
|
112
|
+
apps,
|
|
113
|
+
...groupHelpJson(host, ['workspace']),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const appId = args[0] ?? '';
|
|
117
|
+
return await runWorkspaceApp(context, args, globals.help, host.commands
|
|
118
|
+
.filter((each) => each.path[0] === 'workspace' && each.path[1] === appId)
|
|
119
|
+
.map((each) => ({
|
|
120
|
+
name: each.path.slice(2).join(' '),
|
|
121
|
+
summary: each.summary,
|
|
122
|
+
usage: `dexto ${usage(each)}`,
|
|
123
|
+
})));
|
|
124
|
+
}
|
|
125
|
+
catch (thrown) {
|
|
126
|
+
const error = asMissingScope(context, thrown);
|
|
127
|
+
if (mayGrant &&
|
|
128
|
+
error instanceof MissingScopeFailure &&
|
|
129
|
+
host.runtime.prompt !== null &&
|
|
130
|
+
!output.json &&
|
|
131
|
+
(await askToGrant(host, globals.overrides, error)))
|
|
132
|
+
return await run(host, argv, false);
|
|
133
|
+
return reportError(context, output.json, workspaceHelp(host, null), error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** The workspace's apps, when listed, and the CLI's own workspace commands. */
|
|
137
|
+
function workspaceHelp(host, apps) {
|
|
138
|
+
const commands = host.commands.filter((command) => command.path[0] === 'workspace');
|
|
139
|
+
const usages = commands.map((command) => usage(command));
|
|
140
|
+
const width = Math.max(...usages.map((line) => line.length));
|
|
141
|
+
return [
|
|
142
|
+
'Usage: dexto workspace <app> [operation] [--field value…] [--yes]',
|
|
143
|
+
' dexto workspace <command>',
|
|
144
|
+
'',
|
|
145
|
+
'Use your Dexto workspace as yourself: the apps and tools your agents use over the Dexto MCP.',
|
|
146
|
+
'',
|
|
147
|
+
"Your workspace's apps (dexto workspace <app> lists what each does):",
|
|
148
|
+
...(apps === null
|
|
149
|
+
? [' Run dexto workspace while signed in to list them.']
|
|
150
|
+
: apps.length === 0
|
|
151
|
+
? [' None yet.']
|
|
152
|
+
: [` ${apps.map((app) => app.id).join(' · ')}`]),
|
|
153
|
+
'',
|
|
154
|
+
'Commands:',
|
|
155
|
+
...commands.map((command, index) => ` ${(usages[index] ?? '').padEnd(width)} ${command.summary}`),
|
|
156
|
+
'',
|
|
157
|
+
'Find what to run: dexto find "<what you want to do>"',
|
|
158
|
+
'',
|
|
159
|
+
].join('\n');
|
|
160
|
+
}
|
|
161
|
+
/** At a terminal, offers to sign in again for the permission; true once that sign-in succeeded. */
|
|
162
|
+
async function askToGrant(host, overrides, error) {
|
|
163
|
+
const answer = (await host.runtime.prompt?.(`${error.message} Sign in again to grant it? [y/N] `, {
|
|
164
|
+
secret: false,
|
|
165
|
+
})) ?? '';
|
|
166
|
+
if (!/^y(es)?$/iu.test(answer.trim()))
|
|
167
|
+
return false;
|
|
168
|
+
return await host.signInForScope(overrides, error.scope);
|
|
169
|
+
}
|
|
170
|
+
function contextFor(host, globals, output) {
|
|
171
|
+
const { overrides } = globals;
|
|
172
|
+
const connection = async () => {
|
|
173
|
+
const resolved = await host.resolveConnection(overrides);
|
|
174
|
+
if (resolved !== null)
|
|
175
|
+
return resolved;
|
|
176
|
+
throw new CommandFailure('needs_sign_in', 'Not signed in. Sign in, or set DEXTO_API_KEY to a developer key from your Cloud project.', signInCommand({ overrides, runtime: host.runtime }, null));
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
client: async () => {
|
|
180
|
+
const resolved = await connection();
|
|
181
|
+
return createCloudClient({
|
|
182
|
+
apiBaseUrl: resolved.apiBaseUrl,
|
|
183
|
+
token: resolved.token,
|
|
184
|
+
// A key acts in its own project; only a person's token names one.
|
|
185
|
+
cloudProject: resolved.kind === 'oauth' ? resolved.cloudProject : null,
|
|
186
|
+
fetch: host.runtime.fetch,
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
connection,
|
|
190
|
+
invocation: globals.invocation,
|
|
191
|
+
output,
|
|
192
|
+
overrides,
|
|
193
|
+
runtime: host.runtime,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** A `/v1` refusal that names the scope the sign-in lacks; any other error as it is. */
|
|
197
|
+
function asMissingScope(context, error) {
|
|
198
|
+
if (!(error instanceof CloudApiError) || error.status !== 403)
|
|
199
|
+
return error;
|
|
200
|
+
const details = error.details;
|
|
201
|
+
const scope = typeof details === 'object' && details !== null && 'scope' in details
|
|
202
|
+
? details.scope
|
|
203
|
+
: undefined;
|
|
204
|
+
if (typeof scope !== 'string')
|
|
205
|
+
return error;
|
|
206
|
+
return new MissingScopeFailure(context, scope, error.message);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Prints a failure and returns its exit code: JSON on stderr for agents, otherwise a message with
|
|
210
|
+
* the command's usage (for a usage mistake) or the command that fixes it.
|
|
211
|
+
*/
|
|
212
|
+
function reportError(context, json, help, error) {
|
|
213
|
+
const { runtime } = context;
|
|
214
|
+
const failure = describeFailure(context, error);
|
|
215
|
+
if (json) {
|
|
216
|
+
runtime.writeError(`${JSON.stringify({
|
|
217
|
+
error: {
|
|
218
|
+
code: failure.code,
|
|
219
|
+
message: failure.message,
|
|
220
|
+
...(failure.fix === null ? {} : { fix: failure.fix }),
|
|
221
|
+
...(failure.apiCode === null ? {} : { api_code: failure.apiCode }),
|
|
222
|
+
...(failure.details === null ? {} : { details: failure.details }),
|
|
223
|
+
},
|
|
224
|
+
}, null, 2)}\n`);
|
|
225
|
+
}
|
|
226
|
+
else if (failure.code === 'usage') {
|
|
227
|
+
runtime.writeError(`${failure.message}\n\n${help}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
runtime.writeError(`Error${failure.apiCode === null ? '' : ` (${failure.apiCode})`}: ${failure.message}\n`);
|
|
231
|
+
if (failure.details !== null)
|
|
232
|
+
runtime.writeError(`${JSON.stringify(failure.details, null, 2)}\n`);
|
|
233
|
+
if (failure.fix !== null)
|
|
234
|
+
runtime.writeError(`Run: ${failure.fix}\n`);
|
|
235
|
+
}
|
|
236
|
+
return EXIT_CODES[failure.code];
|
|
237
|
+
}
|
|
238
|
+
function describeFailure(context, error) {
|
|
239
|
+
const report = (code, message, fix, apiCode, details) => ({ apiCode, code, details: details ?? null, fix, message });
|
|
240
|
+
if (error instanceof UsageError || isParseArgsError(error))
|
|
241
|
+
return report('usage', error.message, null, null, null);
|
|
242
|
+
if (error instanceof CommandFailure)
|
|
243
|
+
return report(error.kind, error.message, error.fix, null, null);
|
|
244
|
+
if (error instanceof CloudApiError) {
|
|
245
|
+
if (error.status === 401)
|
|
246
|
+
return report('needs_sign_in', error.message, signInCommand(context, null), error.code, error.details);
|
|
247
|
+
return report(error.status === 403
|
|
248
|
+
? 'needs_permission'
|
|
249
|
+
: error.status === 404
|
|
250
|
+
? 'not_found'
|
|
251
|
+
: error.status === 400 || error.status === 413 || error.status === 422
|
|
252
|
+
? 'invalid_input'
|
|
253
|
+
: 'failed', error.message, null, error.code, error.details);
|
|
254
|
+
}
|
|
255
|
+
if (error instanceof Error)
|
|
256
|
+
return report('failed', error.message, null, null, null);
|
|
257
|
+
return report('failed', 'The command failed.', null, null, null);
|
|
258
|
+
}
|
|
259
|
+
/** Takes the global options out wherever they appear, before the command's own parsing. */
|
|
260
|
+
function extractGlobals(argv) {
|
|
261
|
+
const rest = [];
|
|
262
|
+
let apiBaseUrl = null;
|
|
263
|
+
let apiKey = null;
|
|
264
|
+
let cloudProject = null;
|
|
265
|
+
let profile = null;
|
|
266
|
+
let help = false;
|
|
267
|
+
let json = false;
|
|
268
|
+
let table = false;
|
|
269
|
+
const invocation = [];
|
|
270
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
271
|
+
const token = argv[index] ?? '';
|
|
272
|
+
if (token === '--') {
|
|
273
|
+
rest.push(...argv.slice(index));
|
|
274
|
+
invocation.push(...argv.slice(index));
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
const [name, inline] = token.startsWith('--') ? splitOption(token) : [token, null];
|
|
278
|
+
if (name === '--api-key' ||
|
|
279
|
+
name === '--api-base-url' ||
|
|
280
|
+
name === '--profile' ||
|
|
281
|
+
name === '--project') {
|
|
282
|
+
const value = inline ?? argv[index + 1];
|
|
283
|
+
// Never take another option as the value, so `--project --api-key KEY` can't leave
|
|
284
|
+
// the key behind as a word that gets printed back.
|
|
285
|
+
if (value === undefined || (inline === null && value.startsWith('-')))
|
|
286
|
+
throw new UsageError(`Missing value for ${name}.`);
|
|
287
|
+
if (inline === null)
|
|
288
|
+
index += 1;
|
|
289
|
+
if (name === '--api-key')
|
|
290
|
+
apiKey = value;
|
|
291
|
+
else {
|
|
292
|
+
invocation.push(name, value);
|
|
293
|
+
if (name === '--project')
|
|
294
|
+
cloudProject = value;
|
|
295
|
+
else if (name === '--profile')
|
|
296
|
+
profile = value;
|
|
297
|
+
else
|
|
298
|
+
apiBaseUrl = value;
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (token === '--json')
|
|
303
|
+
json = true;
|
|
304
|
+
else if (token === '--table')
|
|
305
|
+
table = true;
|
|
306
|
+
else if (token === '--help' || token === '-h')
|
|
307
|
+
help = true;
|
|
308
|
+
else
|
|
309
|
+
rest.push(token);
|
|
310
|
+
if (token !== '--help' && token !== '-h')
|
|
311
|
+
invocation.push(token);
|
|
312
|
+
}
|
|
313
|
+
if (json && table)
|
|
314
|
+
throw new UsageError('Pass --json or --table, not both.');
|
|
315
|
+
return {
|
|
316
|
+
format: json ? 'json' : table ? 'table' : 'auto',
|
|
317
|
+
help,
|
|
318
|
+
invocation,
|
|
319
|
+
overrides: { apiBaseUrl, apiKey, cloudProject, profile },
|
|
320
|
+
rest,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function splitOption(token) {
|
|
324
|
+
const equals = token.indexOf('=');
|
|
325
|
+
return equals === -1 ? [token, null] : [token.slice(0, equals), token.slice(equals + 1)];
|
|
326
|
+
}
|
|
327
|
+
/** The longest command whose path the leading words spell. */
|
|
328
|
+
function matchCommand(commands, tokens) {
|
|
329
|
+
const words = [];
|
|
330
|
+
for (const token of tokens) {
|
|
331
|
+
if (token.startsWith('-'))
|
|
332
|
+
break;
|
|
333
|
+
const next = [...words, token];
|
|
334
|
+
if (!commands.some((command) => startsWith(command.path, next)))
|
|
335
|
+
break;
|
|
336
|
+
words.push(token);
|
|
337
|
+
}
|
|
338
|
+
for (let length = words.length; length > 0; length -= 1) {
|
|
339
|
+
const prefix = words.slice(0, length);
|
|
340
|
+
const command = commands.find((each) => each.path.length === length && startsWith(each.path, prefix));
|
|
341
|
+
if (command !== undefined)
|
|
342
|
+
return { command, rest: tokens.slice(length), words: prefix };
|
|
343
|
+
}
|
|
344
|
+
const rest = tokens.slice(words.length);
|
|
345
|
+
// A word after a group that names none of its commands, such as `cloud chat`, is unknown.
|
|
346
|
+
const unknown = rest[0] !== undefined && !rest[0].startsWith('-') ? [rest[0]] : [];
|
|
347
|
+
return {
|
|
348
|
+
command: null,
|
|
349
|
+
rest,
|
|
350
|
+
words: words.length === 0
|
|
351
|
+
? tokens.filter((token) => !token.startsWith('-'))
|
|
352
|
+
: [...words, ...unknown],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function startsWith(path, prefix) {
|
|
356
|
+
return prefix.every((word, index) => path[index] === word);
|
|
357
|
+
}
|
|
358
|
+
/** Help as text for people, or as JSON where the output is JSON. */
|
|
359
|
+
function printHelp(runtime, globals, text, json) {
|
|
360
|
+
if (createOutput(runtime, globals.format).json)
|
|
361
|
+
runtime.writeOutput(`${JSON.stringify(json, null, 2)}\n`);
|
|
362
|
+
else
|
|
363
|
+
runtime.writeOutput(text);
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
/** A layer's own areas when it has them, otherwise its commands with their usage. */
|
|
367
|
+
function groupHelp(host, words) {
|
|
368
|
+
const areas = host.groups[words.join(' ')];
|
|
369
|
+
if (areas !== undefined) {
|
|
370
|
+
const width = Math.max(...areas.map((group) => group.name.length));
|
|
371
|
+
return [
|
|
372
|
+
`Usage: dexto ${words.join(' ')} <command>`,
|
|
373
|
+
'',
|
|
374
|
+
'Commands:',
|
|
375
|
+
...areas.map((group) => ` ${group.name.padEnd(width)} ${group.summary}`),
|
|
376
|
+
'',
|
|
377
|
+
'Global options:',
|
|
378
|
+
...optionLines(GLOBAL_OPTIONS),
|
|
379
|
+
'',
|
|
380
|
+
"Output is JSON when stdout isn't a terminal; a failure's JSON on stderr names the command",
|
|
381
|
+
'that fixes it. Exit codes: 0 done, 1 failed, 2 usage, 3 needs sign-in or permission,',
|
|
382
|
+
'4 needs --yes (pass it only once your person has agreed), 5 not found, 6 waiting on a',
|
|
383
|
+
'person (such as an approval).',
|
|
384
|
+
'',
|
|
385
|
+
].join('\n');
|
|
386
|
+
}
|
|
387
|
+
const commands = host.commands.filter((command) => startsWith(command.path, words));
|
|
388
|
+
const usages = commands.map((command) => usage(command));
|
|
389
|
+
const width = Math.max(...usages.map((line) => line.length));
|
|
390
|
+
return [
|
|
391
|
+
`Usage: dexto ${words.join(' ')} <command>`,
|
|
392
|
+
'',
|
|
393
|
+
'Commands:',
|
|
394
|
+
...commands.map((command, index) => ` ${(usages[index] ?? '').padEnd(width)} ${command.summary}`),
|
|
395
|
+
'',
|
|
396
|
+
].join('\n');
|
|
397
|
+
}
|
|
398
|
+
function groupHelpJson(host, words) {
|
|
399
|
+
return {
|
|
400
|
+
commands: host.commands
|
|
401
|
+
.filter((command) => startsWith(command.path, words))
|
|
402
|
+
.map((command) => ({ summary: command.summary, usage: `dexto ${usage(command)}` })),
|
|
403
|
+
exit_codes: EXIT_CODE_MEANINGS,
|
|
404
|
+
usage: `dexto ${words.join(' ')} <command>`,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function commandHelp(command) {
|
|
408
|
+
return [
|
|
409
|
+
`Usage: dexto ${usage(command)}${command.options === undefined ? '' : ' [options]'}`,
|
|
410
|
+
'',
|
|
411
|
+
command.summary,
|
|
412
|
+
'',
|
|
413
|
+
...(command.options === undefined ? [] : ['Options:', ...optionLines(command.options), '']),
|
|
414
|
+
'Global options:',
|
|
415
|
+
...optionLines(GLOBAL_OPTIONS),
|
|
416
|
+
'',
|
|
417
|
+
].join('\n');
|
|
418
|
+
}
|
|
419
|
+
function commandHelpJson(command) {
|
|
420
|
+
return {
|
|
421
|
+
options: Object.entries({ ...command.options, ...GLOBAL_OPTIONS }).map(([name, spec]) => ({
|
|
422
|
+
description: spec.description,
|
|
423
|
+
name: `--${name}`,
|
|
424
|
+
type: spec.type,
|
|
425
|
+
...(spec.multiple === true ? { multiple: true } : {}),
|
|
426
|
+
})),
|
|
427
|
+
summary: command.summary,
|
|
428
|
+
usage: `dexto ${usage(command)}`,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function usage(command) {
|
|
432
|
+
return [...command.path, ...(command.arguments === undefined ? [] : [command.arguments])].join(' ');
|
|
433
|
+
}
|
|
434
|
+
function optionLines(options) {
|
|
435
|
+
const labels = Object.entries(options).map(([name, spec]) => `${spec.short === undefined ? ' ' : `-${spec.short}, `}--${name}${spec.type === 'string' ? ` ${spec.valueName ?? 'VALUE'}` : ''}${spec.multiple === true ? ' ...' : ''}`);
|
|
436
|
+
const width = Math.max(...labels.map((label) => label.length));
|
|
437
|
+
return Object.values(options).map((spec, index) => ` ${(labels[index] ?? '').padEnd(width)} ${spec.description}`);
|
|
438
|
+
}
|
|
439
|
+
function isParseArgsError(error) {
|
|
440
|
+
return (error instanceof Error &&
|
|
441
|
+
'code' in error &&
|
|
442
|
+
typeof error.code === 'string' &&
|
|
443
|
+
error.code.startsWith('ERR_PARSE_ARGS_'));
|
|
444
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface CloudRuntime {
|
|
2
|
+
/** Whether to color what people read: a terminal that shows colors. */
|
|
3
|
+
readonly color: boolean;
|
|
4
|
+
readonly environment: Readonly<Record<string, string | undefined>>;
|
|
5
|
+
readonly fetch: typeof globalThis.fetch;
|
|
6
|
+
/** Where other agents keep their chats, such as ~/.claude and ~/.codex. */
|
|
7
|
+
readonly homeDirectory: string;
|
|
8
|
+
/** Asks the person at the terminal; null when stdin is not a terminal. */
|
|
9
|
+
readonly prompt: ((question: string, options: {
|
|
10
|
+
secret: boolean;
|
|
11
|
+
}) => Promise<string>) | null;
|
|
12
|
+
/** All of stdin as text, for piped input. */
|
|
13
|
+
readonly readStdin: () => Promise<string>;
|
|
14
|
+
/** Runs a program, such as a project's packer, and returns its exit code and what it printed. */
|
|
15
|
+
readonly runProcess: (command: string, args: readonly string[], options: {
|
|
16
|
+
readonly cwd: string;
|
|
17
|
+
}) => Promise<ProcessResult>;
|
|
18
|
+
readonly signal: AbortSignal;
|
|
19
|
+
/** Waits, ending early with an error when the command is cancelled. */
|
|
20
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
21
|
+
readonly stdoutIsTerminal: boolean;
|
|
22
|
+
/** Raw bytes to stdout, such as a downloaded file. */
|
|
23
|
+
readonly writeBytes: (chunk: Uint8Array) => Promise<void>;
|
|
24
|
+
readonly writeError: (text: string) => void;
|
|
25
|
+
readonly writeOutput: (text: string) => void;
|
|
26
|
+
}
|
|
27
|
+
export interface ProcessResult {
|
|
28
|
+
readonly exitCode: number;
|
|
29
|
+
/** Its stdout and stderr together, as a person would have seen them. */
|
|
30
|
+
readonly output: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function nodeCloudRuntime(signal: AbortSignal): CloudRuntime;
|
|
33
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/cli/cloud/runtime.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IACzB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACnE,QAAQ,CAAC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IACxC,2EAA2E;IAC3E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;IAC9F,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,iGAAiG;IACjG,QAAQ,CAAC,UAAU,EAAE,CACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAChC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CA2BlE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// The process the cloud commands run in, injected so tests drive the public command boundary.
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
export function nodeCloudRuntime(signal) {
|
|
8
|
+
return {
|
|
9
|
+
color: process.stdout.isTTY === true && chalk.level > 0,
|
|
10
|
+
environment: process.env,
|
|
11
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
12
|
+
homeDirectory: homedir(),
|
|
13
|
+
prompt: process.stdin.isTTY === true ? promptAtTerminal : null,
|
|
14
|
+
readStdin: async () => {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for await (const chunk of process.stdin)
|
|
17
|
+
chunks.push(Buffer.from(chunk));
|
|
18
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
19
|
+
},
|
|
20
|
+
runProcess: (command, args, options) => runProcess(command, args, options.cwd, signal),
|
|
21
|
+
signal,
|
|
22
|
+
sleep: (ms) => delay(ms, undefined, { signal }),
|
|
23
|
+
stdoutIsTerminal: process.stdout.isTTY === true,
|
|
24
|
+
writeBytes: (chunk) => new Promise((resolve, reject) => process.stdout.write(chunk, (error) => (error ? reject(error) : resolve()))),
|
|
25
|
+
writeError: (text) => {
|
|
26
|
+
process.stderr.write(text);
|
|
27
|
+
},
|
|
28
|
+
writeOutput: (text) => {
|
|
29
|
+
process.stdout.write(text);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function runProcess(command, args, cwd, signal) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const child = spawn(command, args, { cwd, signal, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
36
|
+
let output = '';
|
|
37
|
+
child.stdout.on('data', (chunk) => (output += chunk.toString('utf8')));
|
|
38
|
+
child.stderr.on('data', (chunk) => (output += chunk.toString('utf8')));
|
|
39
|
+
child.once('error', reject);
|
|
40
|
+
child.once('close', (code) => resolve({ exitCode: code ?? 1, output }));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function promptAtTerminal(question, options) {
|
|
44
|
+
if (options.secret)
|
|
45
|
+
return await readHidden(question);
|
|
46
|
+
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
47
|
+
try {
|
|
48
|
+
return await readline.question(question);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
readline.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Reads a line without echoing it, for API keys and secret values. */
|
|
55
|
+
function readHidden(question) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const input = process.stdin;
|
|
58
|
+
let value = '';
|
|
59
|
+
const finish = (error) => {
|
|
60
|
+
input.off('data', onData);
|
|
61
|
+
input.setRawMode(false);
|
|
62
|
+
input.pause();
|
|
63
|
+
process.stderr.write('\n');
|
|
64
|
+
if (error === null)
|
|
65
|
+
resolve(value);
|
|
66
|
+
else
|
|
67
|
+
reject(error);
|
|
68
|
+
};
|
|
69
|
+
const onData = (chunk) => {
|
|
70
|
+
for (const character of chunk.toString('utf8')) {
|
|
71
|
+
if (character === '\r' || character === '\n')
|
|
72
|
+
return finish(null);
|
|
73
|
+
if (character === '\u0003')
|
|
74
|
+
return finish(new Error('Cancelled.'));
|
|
75
|
+
if (character === '\u007f' || character === '\b')
|
|
76
|
+
value = value.slice(0, -1);
|
|
77
|
+
else
|
|
78
|
+
value += character;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
process.stderr.write(question);
|
|
82
|
+
input.setRawMode(true);
|
|
83
|
+
input.resume();
|
|
84
|
+
input.on('data', onData);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface TarFile {
|
|
2
|
+
readonly bytes: Uint8Array;
|
|
3
|
+
readonly executable: boolean;
|
|
4
|
+
/** Relative, with `/` between segments. */
|
|
5
|
+
readonly path: string;
|
|
6
|
+
}
|
|
7
|
+
/** The archive of these files in order, closed by its two empty blocks. */
|
|
8
|
+
export declare function tarArchive(files: readonly TarFile[]): Uint8Array<ArrayBuffer>;
|
|
9
|
+
//# sourceMappingURL=tar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../../src/cli/cloud/tar.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,OAAO;IACpB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CA6B7E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Writes a POSIX ustar archive as Dexto's Skill bundle import reads one: files, PAX for long paths.
|
|
2
|
+
const BLOCK_BYTES = 512;
|
|
3
|
+
const encoder = new globalThis.TextEncoder();
|
|
4
|
+
/** The archive of these files in order, closed by its two empty blocks. */
|
|
5
|
+
export function tarArchive(files) {
|
|
6
|
+
const blocks = [];
|
|
7
|
+
for (const [index, file] of files.entries()) {
|
|
8
|
+
// A path past the 100-byte name field rides in a PAX header for the entry after it.
|
|
9
|
+
const pax = paxPathBody(file.path);
|
|
10
|
+
if (pax !== null)
|
|
11
|
+
blocks.push(tarHeader({
|
|
12
|
+
mode: 0o644,
|
|
13
|
+
name: `PaxHeaders/${index}`,
|
|
14
|
+
size: pax.length,
|
|
15
|
+
type: 'x',
|
|
16
|
+
}), pax, padding(pax.length));
|
|
17
|
+
blocks.push(tarHeader({
|
|
18
|
+
mode: file.executable ? 0o755 : 0o644,
|
|
19
|
+
name: pax === null ? file.path : `file-${index}`,
|
|
20
|
+
size: file.bytes.length,
|
|
21
|
+
type: '0',
|
|
22
|
+
}), file.bytes, padding(file.bytes.length));
|
|
23
|
+
}
|
|
24
|
+
blocks.push(new Uint8Array(BLOCK_BYTES * 2));
|
|
25
|
+
return Buffer.concat(blocks);
|
|
26
|
+
}
|
|
27
|
+
function tarHeader(input) {
|
|
28
|
+
const header = new Uint8Array(BLOCK_BYTES);
|
|
29
|
+
writeText(header, 0, 100, input.name);
|
|
30
|
+
writeOctal(header, 100, 8, input.mode);
|
|
31
|
+
writeOctal(header, 108, 8, 0);
|
|
32
|
+
writeOctal(header, 116, 8, 0);
|
|
33
|
+
writeOctal(header, 124, 12, input.size);
|
|
34
|
+
writeOctal(header, 136, 12, 0);
|
|
35
|
+
// The checksum counts its own field as spaces.
|
|
36
|
+
header.fill(0x20, 148, 156);
|
|
37
|
+
writeText(header, 156, 1, input.type);
|
|
38
|
+
writeText(header, 257, 6, 'ustar');
|
|
39
|
+
writeText(header, 263, 2, '00');
|
|
40
|
+
const checksum = header.reduce((total, byte) => total + byte, 0);
|
|
41
|
+
writeText(header, 148, 6, checksum.toString(8).padStart(6, '0'));
|
|
42
|
+
header[154] = 0;
|
|
43
|
+
header[155] = 0x20;
|
|
44
|
+
return header;
|
|
45
|
+
}
|
|
46
|
+
function writeText(target, offset, length, value) {
|
|
47
|
+
const bytes = encoder.encode(value);
|
|
48
|
+
if (bytes.length > length)
|
|
49
|
+
throw new Error(`Tar header value is too long: ${value}`);
|
|
50
|
+
target.set(bytes, offset);
|
|
51
|
+
}
|
|
52
|
+
function writeOctal(target, offset, length, value) {
|
|
53
|
+
writeText(target, offset, length, `${value.toString(8).padStart(length - 1, '0')}\0`);
|
|
54
|
+
}
|
|
55
|
+
/** `<length> path=<path>\n`, whose length counts its own digits; null when the name field fits. */
|
|
56
|
+
function paxPathBody(path) {
|
|
57
|
+
if (encoder.encode(path).length <= 100)
|
|
58
|
+
return null;
|
|
59
|
+
let length = encoder.encode(` path=${path}\n`).length + 1;
|
|
60
|
+
for (;;) {
|
|
61
|
+
const record = encoder.encode(`${length} path=${path}\n`);
|
|
62
|
+
if (record.length === length)
|
|
63
|
+
return record;
|
|
64
|
+
length = record.length;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function padding(size) {
|
|
68
|
+
return new Uint8Array(Math.ceil(size / BLOCK_BYTES) * BLOCK_BYTES - size);
|
|
69
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CommandContext } from './command.js';
|
|
2
|
+
/** Where the bytes go: a short-lived URL, its method and the headers it expects. */
|
|
3
|
+
export interface TransferTarget {
|
|
4
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly url: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Streams `body` to the target with its length, and returns the JSON reply (null when there is
|
|
10
|
+
* none). A refusal fails with the server's own message.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sendBytes(context: CommandContext, target: TransferTarget, body: globalThis.Blob): Promise<unknown>;
|
|
13
|
+
//# sourceMappingURL=transfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../../src/cli/cloud/transfer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAEjF,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC3B,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,UAAU,CAAC,IAAI,GACtB,OAAO,CAAC,OAAO,CAAC,CAelB"}
|