@asteroidai/cli 1.9.12 → 1.9.28
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/asteroid.js +387 -53
- package/dist/core.js +14 -2
- package/package.json +3 -8
- package/src/account.ts +0 -224
- package/src/args.ts +0 -39
- package/src/auth/credentials.ts +0 -141
- package/src/auth/store.ts +0 -145
- package/src/auth/workos.ts +0 -171
- package/src/commands.ts +0 -13
- package/src/config.ts +0 -125
- package/src/core.ts +0 -41
- package/src/generated/commands.ts +0 -4772
- package/src/http.ts +0 -115
- package/src/main.ts +0 -49
- package/src/output.test.ts +0 -71
- package/src/output.ts +0 -176
- package/src/program.test.ts +0 -155
- package/src/program.ts +0 -291
- package/src/reference.ts +0 -183
- package/src/request.test.ts +0 -164
- package/src/request.ts +0 -252
- package/src/run.test.ts +0 -431
- package/src/run.ts +0 -229
- package/src/sync/activities.ts +0 -80
- package/src/sync/commands.ts +0 -63
- package/src/sync/execution-pull.ts +0 -273
- package/src/sync/node-files.ts +0 -47
- package/src/sync/paths.ts +0 -45
- package/src/sync/run.ts +0 -136
- package/src/sync/sync.test.ts +0 -502
- package/src/sync/types.ts +0 -58
- package/src/sync/workflow-pull.ts +0 -185
- package/src/sync/workflow-push.ts +0 -161
- package/src/types.ts +0 -46
- package/src/ui.ts +0 -30
package/src/sync/types.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { BuiltRequest } from "../request";
|
|
2
|
-
|
|
3
|
-
export interface LocalFiles {
|
|
4
|
-
read(path: string): Promise<Uint8Array | undefined>;
|
|
5
|
-
write(path: string, bytes: Uint8Array): Promise<void>;
|
|
6
|
-
remove(path: string): Promise<void>;
|
|
7
|
-
list(dir: string): Promise<string[]>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface Remote {
|
|
11
|
-
json(request: BuiltRequest): Promise<unknown>;
|
|
12
|
-
download(url: string): Promise<Uint8Array>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface Sync {
|
|
16
|
-
remote: Remote;
|
|
17
|
-
files: LocalFiles;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface FileEntry {
|
|
21
|
-
path: string;
|
|
22
|
-
kind: string;
|
|
23
|
-
size: number;
|
|
24
|
-
content?: string;
|
|
25
|
-
contentBase64?: string;
|
|
26
|
-
checksum?: string;
|
|
27
|
-
fileId?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface FileTree {
|
|
31
|
-
rev: number;
|
|
32
|
-
version?: number;
|
|
33
|
-
versionId: string;
|
|
34
|
-
workflowName: string;
|
|
35
|
-
files: FileEntry[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const MANIFEST = ".asteroid.json";
|
|
39
|
-
|
|
40
|
-
export interface Manifest {
|
|
41
|
-
workflowId: string;
|
|
42
|
-
versionId: string;
|
|
43
|
-
head: boolean;
|
|
44
|
-
rev: number;
|
|
45
|
-
files: Record<string, string>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function get(
|
|
49
|
-
path: string,
|
|
50
|
-
query: Record<string, string> = {},
|
|
51
|
-
): BuiltRequest {
|
|
52
|
-
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function statusOf(error: unknown): number | undefined {
|
|
56
|
-
const status = (error as { status?: unknown } | null)?.status;
|
|
57
|
-
return typeof status === "number" ? status : undefined;
|
|
58
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import type { BuiltRequest } from "../request";
|
|
2
|
-
import {
|
|
3
|
-
isContained,
|
|
4
|
-
join,
|
|
5
|
-
sha256,
|
|
6
|
-
workflowDirName,
|
|
7
|
-
workflowRoot,
|
|
8
|
-
} from "./paths";
|
|
9
|
-
import {
|
|
10
|
-
type FileEntry,
|
|
11
|
-
type FileTree,
|
|
12
|
-
get,
|
|
13
|
-
MANIFEST,
|
|
14
|
-
type Manifest,
|
|
15
|
-
type Sync,
|
|
16
|
-
} from "./types";
|
|
17
|
-
|
|
18
|
-
const CONCURRENCY = 16;
|
|
19
|
-
|
|
20
|
-
export interface WorkflowPullOptions {
|
|
21
|
-
workflowId: string;
|
|
22
|
-
request: BuiltRequest;
|
|
23
|
-
dir?: string;
|
|
24
|
-
root?: string;
|
|
25
|
-
manifest?: boolean;
|
|
26
|
-
maxFileBytes?: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface WorkflowPullResult {
|
|
30
|
-
workflowId: string;
|
|
31
|
-
workflowName: string;
|
|
32
|
-
versionId: string;
|
|
33
|
-
version?: number;
|
|
34
|
-
rev: number;
|
|
35
|
-
dir: string;
|
|
36
|
-
head: boolean;
|
|
37
|
-
written: number;
|
|
38
|
-
shared: number;
|
|
39
|
-
skipped: string[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function pullDir(root: string, tree: FileTree, head: boolean): string {
|
|
43
|
-
const base = workflowRoot(root, tree.workflowName);
|
|
44
|
-
if (head) return join(base, "head");
|
|
45
|
-
return join(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function agentFileBytes(
|
|
49
|
-
sync: Sync,
|
|
50
|
-
workflowId: string,
|
|
51
|
-
tree: FileTree,
|
|
52
|
-
entry: FileEntry,
|
|
53
|
-
): Promise<Uint8Array> {
|
|
54
|
-
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
55
|
-
const { url } = (await sync.remote.json(get(path))) as { url: string };
|
|
56
|
-
return sync.remote.download(url);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function entryBytes(
|
|
60
|
-
sync: Sync,
|
|
61
|
-
workflowId: string,
|
|
62
|
-
tree: FileTree,
|
|
63
|
-
entry: FileEntry,
|
|
64
|
-
): Promise<Uint8Array | undefined> {
|
|
65
|
-
if (entry.contentBase64 !== undefined)
|
|
66
|
-
return Buffer.from(entry.contentBase64, "base64");
|
|
67
|
-
if (entry.content !== undefined) return Buffer.from(entry.content, "utf8");
|
|
68
|
-
if (!entry.fileId) return undefined;
|
|
69
|
-
return agentFileBytes(sync, workflowId, tree, entry);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function writeEntries(
|
|
73
|
-
sync: Sync,
|
|
74
|
-
options: WorkflowPullOptions,
|
|
75
|
-
tree: FileTree,
|
|
76
|
-
dir: string,
|
|
77
|
-
) {
|
|
78
|
-
const hashes: Record<string, string> = {};
|
|
79
|
-
const skipped: string[] = [];
|
|
80
|
-
let shared = 0;
|
|
81
|
-
const wanted = tree.files.filter((entry) => {
|
|
82
|
-
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
83
|
-
else if (
|
|
84
|
-
options.maxFileBytes !== undefined &&
|
|
85
|
-
entry.size > options.maxFileBytes
|
|
86
|
-
)
|
|
87
|
-
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
88
|
-
else return true;
|
|
89
|
-
return false;
|
|
90
|
-
});
|
|
91
|
-
for (let i = 0; i < wanted.length; i += CONCURRENCY) {
|
|
92
|
-
await Promise.all(
|
|
93
|
-
wanted.slice(i, i + CONCURRENCY).map(async (entry) => {
|
|
94
|
-
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
95
|
-
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
96
|
-
await sync.files.write(join(dir, entry.path), bytes);
|
|
97
|
-
hashes[entry.path] = sha256(bytes);
|
|
98
|
-
if (entry.kind === "agent_file") shared++;
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return { hashes, skipped, shared };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function removeStale(sync: Sync, dir: string, keep: Set<string>) {
|
|
106
|
-
for (const path of await sync.files.list(dir)) {
|
|
107
|
-
if (!keep.has(path) && path !== MANIFEST)
|
|
108
|
-
await sync.files.remove(join(dir, path));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export async function writeManifest(
|
|
113
|
-
sync: Sync,
|
|
114
|
-
dir: string,
|
|
115
|
-
manifest: Manifest,
|
|
116
|
-
) {
|
|
117
|
-
const text = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
118
|
-
await sync.files.write(join(dir, MANIFEST), Buffer.from(text, "utf8"));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function pullWorkflow(
|
|
122
|
-
sync: Sync,
|
|
123
|
-
options: WorkflowPullOptions,
|
|
124
|
-
): Promise<WorkflowPullResult> {
|
|
125
|
-
const request = {
|
|
126
|
-
...options.request,
|
|
127
|
-
query: new URLSearchParams(options.request.query),
|
|
128
|
-
};
|
|
129
|
-
request.query.set("contents", "all");
|
|
130
|
-
const tree = (await sync.remote.json(request)) as FileTree;
|
|
131
|
-
const head = request.path.endsWith("/head/files");
|
|
132
|
-
const dir = options.dir ?? pullDir(options.root ?? "", tree, head);
|
|
133
|
-
const { hashes, skipped, shared } = await writeEntries(
|
|
134
|
-
sync,
|
|
135
|
-
options,
|
|
136
|
-
tree,
|
|
137
|
-
dir,
|
|
138
|
-
);
|
|
139
|
-
await removeStale(sync, dir, new Set(Object.keys(hashes)));
|
|
140
|
-
if (options.manifest !== false) {
|
|
141
|
-
await writeManifest(sync, dir, {
|
|
142
|
-
workflowId: options.workflowId,
|
|
143
|
-
versionId: tree.versionId,
|
|
144
|
-
head,
|
|
145
|
-
rev: tree.rev,
|
|
146
|
-
files: hashes,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
workflowId: options.workflowId,
|
|
151
|
-
workflowName: tree.workflowName,
|
|
152
|
-
versionId: tree.versionId,
|
|
153
|
-
version: tree.version,
|
|
154
|
-
rev: tree.rev,
|
|
155
|
-
dir,
|
|
156
|
-
head,
|
|
157
|
-
written: Object.keys(hashes).length,
|
|
158
|
-
shared,
|
|
159
|
-
skipped,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function formatWorkflowPull(result: WorkflowPullResult): string {
|
|
164
|
-
const definition = result.written - result.shared;
|
|
165
|
-
const files =
|
|
166
|
-
result.written === 0
|
|
167
|
-
? "Files: none written"
|
|
168
|
-
: result.shared === 0
|
|
169
|
-
? `Files: ${result.written} written`
|
|
170
|
-
: `Files: ${result.written} written (${definition} workflow definition, ${result.shared} shared)`;
|
|
171
|
-
return [
|
|
172
|
-
`Workflow: ${result.workflowName} (${result.workflowId})`,
|
|
173
|
-
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
174
|
-
`Written to: ${result.dir}/`,
|
|
175
|
-
files,
|
|
176
|
-
result.skipped.length > 0
|
|
177
|
-
? `Skipped:\n${result.skipped.map((s) => ` - ${s}`).join("\n")}`
|
|
178
|
-
: null,
|
|
179
|
-
result.head
|
|
180
|
-
? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.`
|
|
181
|
-
: "This is a pinned version. It is read-only.",
|
|
182
|
-
]
|
|
183
|
-
.filter(Boolean)
|
|
184
|
-
.join("\n");
|
|
185
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { UsageError } from "../args";
|
|
2
|
-
import { join, sha256 } from "./paths";
|
|
3
|
-
import {
|
|
4
|
-
type FileTree,
|
|
5
|
-
get,
|
|
6
|
-
MANIFEST,
|
|
7
|
-
type Manifest,
|
|
8
|
-
statusOf,
|
|
9
|
-
type Sync,
|
|
10
|
-
} from "./types";
|
|
11
|
-
import { writeManifest } from "./workflow-pull";
|
|
12
|
-
|
|
13
|
-
export interface WorkflowPushResult {
|
|
14
|
-
workflowId: string;
|
|
15
|
-
dir: string;
|
|
16
|
-
written: string[];
|
|
17
|
-
deleted: string[];
|
|
18
|
-
rev: number;
|
|
19
|
-
versionId: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function readManifest(sync: Sync, dir: string): Promise<Manifest> {
|
|
23
|
-
const bytes = await sync.files.read(join(dir, MANIFEST));
|
|
24
|
-
if (!bytes) {
|
|
25
|
-
throw new UsageError(
|
|
26
|
-
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8")) as Manifest;
|
|
30
|
-
if (!manifest.head) {
|
|
31
|
-
throw new UsageError(
|
|
32
|
-
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return manifest;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function localChanges(sync: Sync, dir: string, manifest: Manifest) {
|
|
39
|
-
const local = (await sync.files.list(dir)).filter(
|
|
40
|
-
(path) => path !== MANIFEST,
|
|
41
|
-
);
|
|
42
|
-
const writes: { path: string; contentBase64: string; hash: string }[] = [];
|
|
43
|
-
for (const path of local) {
|
|
44
|
-
const bytes = await sync.files.read(join(dir, path));
|
|
45
|
-
if (!bytes) continue;
|
|
46
|
-
const hash = sha256(bytes);
|
|
47
|
-
if (manifest.files[path] === hash) continue;
|
|
48
|
-
writes.push({
|
|
49
|
-
path,
|
|
50
|
-
contentBase64: Buffer.from(bytes).toString("base64"),
|
|
51
|
-
hash,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
const present = new Set(local);
|
|
55
|
-
const deletes = Object.keys(manifest.files).filter(
|
|
56
|
-
(path) => !present.has(path),
|
|
57
|
-
);
|
|
58
|
-
return { writes, deletes };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function patch(
|
|
62
|
-
sync: Sync,
|
|
63
|
-
manifest: Manifest,
|
|
64
|
-
writes: { path: string; contentBase64: string }[],
|
|
65
|
-
deletes: string[],
|
|
66
|
-
) {
|
|
67
|
-
try {
|
|
68
|
-
await sync.remote.json({
|
|
69
|
-
method: "PATCH",
|
|
70
|
-
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
71
|
-
query: new URLSearchParams(),
|
|
72
|
-
json: { baseRev: manifest.rev, writes, deletes },
|
|
73
|
-
});
|
|
74
|
-
} catch (error) {
|
|
75
|
-
if (statusOf(error) !== 409) throw error;
|
|
76
|
-
throw new UsageError(
|
|
77
|
-
`the workflow head changed since your pull (rev ${manifest.rev}). Copy your edits aside, run \`asteroid workflow pull ${manifest.workflowId}\`, reapply them, then push again.`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function refresh(
|
|
83
|
-
sync: Sync,
|
|
84
|
-
dir: string,
|
|
85
|
-
manifest: Manifest,
|
|
86
|
-
pushed: Map<string, string>,
|
|
87
|
-
) {
|
|
88
|
-
const tree = (await sync.remote.json(
|
|
89
|
-
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`),
|
|
90
|
-
)) as FileTree;
|
|
91
|
-
const files: Record<string, string> = {};
|
|
92
|
-
for (const entry of tree.files) {
|
|
93
|
-
if (entry.content !== undefined) {
|
|
94
|
-
const bytes = Buffer.from(entry.content, "utf8");
|
|
95
|
-
files[entry.path] = sha256(bytes);
|
|
96
|
-
if (pushed.get(entry.path) !== files[entry.path])
|
|
97
|
-
await sync.files.write(join(dir, entry.path), bytes);
|
|
98
|
-
} else {
|
|
99
|
-
const local = await sync.files.read(join(dir, entry.path));
|
|
100
|
-
const hash = local
|
|
101
|
-
? sha256(local)
|
|
102
|
-
: (pushed.get(entry.path) ?? entry.checksum);
|
|
103
|
-
if (hash) files[entry.path] = hash;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const path of await sync.files.list(dir)) {
|
|
107
|
-
if (path !== MANIFEST && !(path in files))
|
|
108
|
-
await sync.files.remove(join(dir, path));
|
|
109
|
-
}
|
|
110
|
-
const next = { ...manifest, versionId: tree.versionId, rev: tree.rev, files };
|
|
111
|
-
await writeManifest(sync, dir, next);
|
|
112
|
-
return next;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export async function pushWorkflow(
|
|
116
|
-
sync: Sync,
|
|
117
|
-
dir: string,
|
|
118
|
-
workflowId?: string,
|
|
119
|
-
): Promise<WorkflowPushResult> {
|
|
120
|
-
const manifest = await readManifest(sync, dir);
|
|
121
|
-
if (workflowId !== undefined && manifest.workflowId !== workflowId) {
|
|
122
|
-
throw new UsageError(
|
|
123
|
-
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
127
|
-
const result = {
|
|
128
|
-
workflowId: manifest.workflowId,
|
|
129
|
-
dir,
|
|
130
|
-
written: writes.map((w) => w.path),
|
|
131
|
-
deleted: deletes,
|
|
132
|
-
};
|
|
133
|
-
if (writes.length === 0 && deletes.length === 0) {
|
|
134
|
-
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
135
|
-
}
|
|
136
|
-
await patch(
|
|
137
|
-
sync,
|
|
138
|
-
manifest,
|
|
139
|
-
writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
|
|
140
|
-
deletes,
|
|
141
|
-
);
|
|
142
|
-
const next = await refresh(
|
|
143
|
-
sync,
|
|
144
|
-
dir,
|
|
145
|
-
manifest,
|
|
146
|
-
new Map(writes.map((w) => [w.path, w.hash])),
|
|
147
|
-
);
|
|
148
|
-
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function formatWorkflowPush(result: WorkflowPushResult): string {
|
|
152
|
-
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
153
|
-
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).`;
|
|
154
|
-
}
|
|
155
|
-
return [
|
|
156
|
-
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.`,
|
|
157
|
-
...result.written.map((path) => ` + ${path}`),
|
|
158
|
-
...result.deleted.map((path) => ` - ${path}`),
|
|
159
|
-
`Head is now rev ${result.rev} (version ${result.versionId}).`,
|
|
160
|
-
].join("\n");
|
|
161
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export type ValueType =
|
|
2
|
-
| "string"
|
|
3
|
-
| "integer"
|
|
4
|
-
| "number"
|
|
5
|
-
| "boolean"
|
|
6
|
-
| "string[]"
|
|
7
|
-
| "integer[]"
|
|
8
|
-
| "number[]"
|
|
9
|
-
| "json";
|
|
10
|
-
|
|
11
|
-
export interface Option {
|
|
12
|
-
name: string;
|
|
13
|
-
flag: string;
|
|
14
|
-
type: ValueType;
|
|
15
|
-
required: boolean;
|
|
16
|
-
description?: string;
|
|
17
|
-
enum?: string[];
|
|
18
|
-
explode?: boolean;
|
|
19
|
-
negatable?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface BodySpec {
|
|
23
|
-
required: boolean;
|
|
24
|
-
contentType: "application/json" | "multipart/form-data";
|
|
25
|
-
fields: Option[];
|
|
26
|
-
files: string[];
|
|
27
|
-
credentialOrganization: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface CliCommand {
|
|
31
|
-
command: string;
|
|
32
|
-
tool: string;
|
|
33
|
-
title: string;
|
|
34
|
-
description: string;
|
|
35
|
-
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
36
|
-
path: string;
|
|
37
|
-
args: Option[];
|
|
38
|
-
query: Option[];
|
|
39
|
-
body?: BodySpec;
|
|
40
|
-
binary: boolean;
|
|
41
|
-
readOnly: boolean;
|
|
42
|
-
destructive: boolean;
|
|
43
|
-
local?: boolean;
|
|
44
|
-
files?: "pull" | "push";
|
|
45
|
-
capability?: "publish" | "run_execution";
|
|
46
|
-
}
|
package/src/ui.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as clack from "@clack/prompts";
|
|
2
|
-
|
|
3
|
-
export interface Choice {
|
|
4
|
-
value: string;
|
|
5
|
-
label: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface Ui {
|
|
9
|
-
select(message: string, choices: Choice[]): Promise<string | undefined>;
|
|
10
|
-
wait<T>(message: string, done: string, work: Promise<T>): Promise<T>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const clackUi: Ui = {
|
|
14
|
-
async select(message, choices) {
|
|
15
|
-
const value = await clack.select({ message, options: choices });
|
|
16
|
-
return clack.isCancel(value) ? undefined : value;
|
|
17
|
-
},
|
|
18
|
-
async wait(message, done, work) {
|
|
19
|
-
const spinner = clack.spinner();
|
|
20
|
-
spinner.start(message);
|
|
21
|
-
try {
|
|
22
|
-
const result = await work;
|
|
23
|
-
spinner.stop(done);
|
|
24
|
-
return result;
|
|
25
|
-
} catch (error) {
|
|
26
|
-
spinner.stop(message);
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
};
|