@botiverse/testbed-cli 0.1.0 → 0.2.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/lib/main.mjs +82 -2
- package/package.json +2 -2
package/lib/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { resolveAuth } from "./auth.mjs";
|
|
|
6
6
|
import { makeClient, ApiError } from "./client.mjs";
|
|
7
7
|
import { runAgentLogin } from "./agent-login.mjs";
|
|
8
8
|
|
|
9
|
-
export const USAGE = `testbed — cloud phones + the acceptance
|
|
9
|
+
export const USAGE = `testbed — cloud phones + the acceptance testbed in the Raft release loop
|
|
10
10
|
|
|
11
11
|
testbed login agent: PKCE login via raft (no browser); human: how to set TESTBED_TOKEN
|
|
12
12
|
testbed logout agent: forget the stored session
|
|
@@ -21,6 +21,15 @@ export const USAGE = `testbed — cloud phones + the acceptance stamp in the Raf
|
|
|
21
21
|
testbed repair-adb <lease-id>
|
|
22
22
|
testbed diagnose-adb <lease-id> [--pubkey-sha256 HEX]
|
|
23
23
|
|
|
24
|
+
testbed verify start [--ttl MIN] [--instance ID] [--request-id ID] [--app-ref LABEL] [--apk-key fast/<id> --apk-sha256 HEX]
|
|
25
|
+
open a human verification session (takes one pool device)
|
|
26
|
+
testbed verify show <session-id>
|
|
27
|
+
testbed verify renew <session-id> [--minutes MIN]
|
|
28
|
+
testbed verify end <session-id>
|
|
29
|
+
testbed verify accounts <session-id> QA accounts the verifier may log in with
|
|
30
|
+
testbed verify login <session-id> --account <account-id>
|
|
31
|
+
testbed verify reinstall <session-id> --apk-key fast/<id> --apk-sha256 HEX
|
|
32
|
+
|
|
24
33
|
testbed cases acceptance case library
|
|
25
34
|
testbed run <case-id...> [--app --ref --apk --apk-run --release --by]
|
|
26
35
|
testbed runs
|
|
@@ -84,7 +93,7 @@ export async function main(argv, opts) {
|
|
|
84
93
|
case "login": {
|
|
85
94
|
if (auth.mode !== "agent") {
|
|
86
95
|
out("Not a managed Raft agent. Humans and CI use TESTBED_TOKEN:");
|
|
87
|
-
out(` browser: ${auth.apiBase}/login (then reuse the
|
|
96
|
+
out(` browser: ${auth.apiBase}/login (then reuse the testbed_session cookie value as TESTBED_TOKEN)`);
|
|
88
97
|
out(" CI: a deploy token issued by the testbed owner");
|
|
89
98
|
return exit(0);
|
|
90
99
|
}
|
|
@@ -174,6 +183,77 @@ export async function main(argv, opts) {
|
|
|
174
183
|
emit(d, () => out(JSON.stringify(d, null, 2)));
|
|
175
184
|
return exit(0);
|
|
176
185
|
}
|
|
186
|
+
case "verify": {
|
|
187
|
+
const sub = args.shift();
|
|
188
|
+
const vs = (id) => `/verify-sessions/${encodeURIComponent(id)}`;
|
|
189
|
+
const fmt = (t) => (t ? new Date(t).toISOString().slice(0, 16) : "-");
|
|
190
|
+
const describe = (sess) => {
|
|
191
|
+
out(`session ${sess.id} — ${sess.state}${sess.instance_id ? ` on ${sess.instance_id}` : ""}, expires ${fmt(sess.expires_at)}`);
|
|
192
|
+
out(`${auth.apiBase.replace(/\/api$/, "")}/verify/${sess.id}`);
|
|
193
|
+
if (sess.app_ref) out(`app: ${sess.app_ref}`);
|
|
194
|
+
if (sess.qa_account_label) out(`qa account: ${sess.qa_account_label}`);
|
|
195
|
+
};
|
|
196
|
+
switch (sub) {
|
|
197
|
+
case "start": {
|
|
198
|
+
const ttl = takeFlag(args, "ttl"); const instance = takeFlag(args, "instance"); const requestId = takeFlag(args, "request-id");
|
|
199
|
+
const appRef = takeFlag(args, "app-ref"); const apkKey = takeFlag(args, "apk-key"); const apkSha = takeFlag(args, "apk-sha256");
|
|
200
|
+
if ((apkKey === undefined) !== (apkSha === undefined)) throw new Error("usage: --apk-key and --apk-sha256 go together");
|
|
201
|
+
const d = await api("POST", "/verify-sessions", {
|
|
202
|
+
ttl_minutes: ttl !== undefined ? Number(ttl) : undefined, instance_id: instance, request_id: requestId,
|
|
203
|
+
app_ref: appRef, app_oss_key: apkKey, app_sha256: apkSha,
|
|
204
|
+
});
|
|
205
|
+
emit(d, () => describe(d.session));
|
|
206
|
+
return exit(0);
|
|
207
|
+
}
|
|
208
|
+
case "show": {
|
|
209
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify show <session-id>");
|
|
210
|
+
const d = await api("GET", vs(id));
|
|
211
|
+
emit(d, () => describe(d.session));
|
|
212
|
+
return exit(0);
|
|
213
|
+
}
|
|
214
|
+
case "renew": {
|
|
215
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify renew <session-id> [--minutes MIN]");
|
|
216
|
+
const minutes = takeFlag(args, "minutes");
|
|
217
|
+
const d = await api("POST", `${vs(id)}/renew`, minutes !== undefined ? { minutes: Number(minutes) } : {});
|
|
218
|
+
emit(d, () => out(`session ${id} now until ${new Date(d.expires_at).toISOString()} (lease until ${new Date(d.lease_expires_at).toISOString()})`));
|
|
219
|
+
return exit(0);
|
|
220
|
+
}
|
|
221
|
+
case "end": {
|
|
222
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify end <session-id>");
|
|
223
|
+
const d = await api("POST", `${vs(id)}/end`, {});
|
|
224
|
+
emit(d, () => out(d.already_terminal ? `session ${id} was already over` : `ended session ${id}; device reclaim continues asynchronously`));
|
|
225
|
+
return exit(0);
|
|
226
|
+
}
|
|
227
|
+
case "accounts": {
|
|
228
|
+
const id = args[0]; if (!id) throw new Error("usage: testbed verify accounts <session-id>");
|
|
229
|
+
const d = await api("GET", `${vs(id)}/qa-accounts`);
|
|
230
|
+
emit(d, () => {
|
|
231
|
+
table(d.accounts.map((a) => ({
|
|
232
|
+
account: a.id, label: a.label, email: a.login_email ?? "-", env: a.environment ?? "-",
|
|
233
|
+
state: a.in_use ? "in use" : a.cooldown_until && a.cooldown_until > Date.now() ? "cooldown" : "free",
|
|
234
|
+
})), ["account", "label", "email", "env", "state"], out);
|
|
235
|
+
if (d.current) out(`\ncurrently logged in: ${d.current.account_label ?? d.current.account_id}`);
|
|
236
|
+
});
|
|
237
|
+
return exit(0);
|
|
238
|
+
}
|
|
239
|
+
case "login": {
|
|
240
|
+
const id = args[0]; const account = takeFlag(args, "account");
|
|
241
|
+
if (!id || !account) throw new Error("usage: testbed verify login <session-id> --account <account-id>");
|
|
242
|
+
const d = await api("POST", `${vs(id)}/login-qa`, { account_id: account });
|
|
243
|
+
emit(d, () => out(`logged ${d.account_label ?? account} into session ${id}`));
|
|
244
|
+
return exit(0);
|
|
245
|
+
}
|
|
246
|
+
case "reinstall": {
|
|
247
|
+
const id = args[0]; const apkKey = takeFlag(args, "apk-key"); const apkSha = takeFlag(args, "apk-sha256");
|
|
248
|
+
if (!id || !apkKey || !apkSha) throw new Error("usage: testbed verify reinstall <session-id> --apk-key fast/<id> --apk-sha256 HEX");
|
|
249
|
+
const d = await api("POST", `${vs(id)}/reinstall`, { app_oss_key: apkKey, app_sha256: apkSha });
|
|
250
|
+
emit(d, () => out(`reinstalled on session ${id}: ${d.status ?? "ok"}`));
|
|
251
|
+
return exit(0);
|
|
252
|
+
}
|
|
253
|
+
default:
|
|
254
|
+
throw new Error("usage: testbed verify <start|show|renew|end|accounts|login|reinstall> …");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
177
257
|
case "cases": {
|
|
178
258
|
const d = await api("GET", "/cases");
|
|
179
259
|
emit(d, () => table(d.cases.map((c) => ({ id: c.id, tier: c.tier, rev: c.revision, title: c.title })), ["id", "tier", "rev", "title"], out));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botiverse/testbed-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for testbed \u2014 cloud phones and the acceptance
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI for testbed \u2014 cloud phones and the acceptance testbed in the Raft release loop",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|