@autifyhq/aximo-cli 0.12.0 → 0.14.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/README.md +15 -5
- package/dist/client.d.ts +5 -0
- package/dist/client.js +23 -22
- package/dist/client.test.js +28 -0
- package/dist/commands/case/archive.d.ts +20 -0
- package/dist/commands/case/archive.js +42 -0
- package/dist/commands/case/case.test.js +181 -0
- package/dist/commands/case/delete.js +4 -1
- package/dist/commands/case/index.js +2 -0
- package/dist/commands/case/restore.d.ts +20 -0
- package/dist/commands/case/restore.js +42 -0
- package/dist/commands/case/update.d.ts +22 -0
- package/dist/commands/case/update.js +34 -3
- package/dist/commands/device/status.d.ts +1 -10
- package/dist/commands/mcp/server.test.js +11 -1
- package/dist/commands/mcp/tools.js +245 -9
- package/dist/commands/mcp/tools.test.js +449 -2
- package/dist/commands/plan/archive.d.ts +20 -0
- package/dist/commands/plan/archive.js +42 -0
- package/dist/commands/plan/delete.d.ts +20 -0
- package/dist/commands/plan/delete.js +42 -0
- package/dist/commands/plan/index.js +3 -0
- package/dist/commands/plan/plan.test.js +115 -3
- package/dist/commands/plan/restore.d.ts +20 -0
- package/dist/commands/plan/restore.js +42 -0
- package/dist/commands/upload/upload.test.js +5 -2
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +110 -2
- package/dist/errors.test.js +106 -3
- package/dist/types.d.ts +41 -3
- package/dist/validate.d.ts +1 -0
- package/dist/validate.js +15 -3
- package/dist/validate.test.js +18 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { request } from "../../client.js";
|
|
|
3
3
|
import { handleApiError } from "../../errors.js";
|
|
4
4
|
import { dryRunEnvelope, resolveJsonOverride } from "../../mutate.js";
|
|
5
5
|
import { formatSuccess, printResult, validationError } from "../../output.js";
|
|
6
|
-
import { optionalString, parseCaseFormat, parseCaseType, requireId } from "../../validate.js";
|
|
6
|
+
import { optionalString, parseCaseFormat, parseCaseType, parseJsonArray, parseJsonObject, requireId, } from "../../validate.js";
|
|
7
7
|
export async function runCaseUpdate(options, deps) {
|
|
8
8
|
let caseId;
|
|
9
9
|
let body;
|
|
@@ -21,6 +21,7 @@ export async function runCaseUpdate(options, deps) {
|
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
23
|
try {
|
|
24
|
+
const desktopDevice = optionalString("desktopDevice", options.desktopDevice);
|
|
24
25
|
body = {
|
|
25
26
|
title: optionalString("title", options.title),
|
|
26
27
|
description: optionalString("description", options.description),
|
|
@@ -28,6 +29,10 @@ export async function runCaseUpdate(options, deps) {
|
|
|
28
29
|
type: parseCaseType(options.type),
|
|
29
30
|
format: parseCaseFormat(options.format),
|
|
30
31
|
startUrl: optionalString("startUrl", options.startUrl),
|
|
32
|
+
apiMobileConfig: parseJsonObject("mobileConfig", options.mobileConfig),
|
|
33
|
+
apiDesktopConfig: desktopDevice === undefined ? undefined : { userDeviceId: desktopDevice },
|
|
34
|
+
apiMultiDeviceConfig: parseJsonArray("multiDeviceConfig", options.multiDeviceConfig),
|
|
35
|
+
apiWebConfig: parseJsonObject("webConfig", options.webConfig),
|
|
31
36
|
};
|
|
32
37
|
}
|
|
33
38
|
catch (err) {
|
|
@@ -54,11 +59,33 @@ export default defineCommand({
|
|
|
54
59
|
args: {
|
|
55
60
|
caseId: { type: "positional", required: true },
|
|
56
61
|
title: { type: "string" },
|
|
57
|
-
description: {
|
|
62
|
+
description: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Cannot clear; use --json '{\"description\": null}' for that",
|
|
65
|
+
},
|
|
58
66
|
scenario: { type: "string" },
|
|
59
67
|
type: { type: "string", description: "web | mobile | desktop | multi_device" },
|
|
60
68
|
format: { type: "string", description: "gherkin | markdown | plain_text" },
|
|
61
|
-
"start-url": {
|
|
69
|
+
"start-url": {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Cannot clear; use --json '{\"startUrl\": null}' for that",
|
|
72
|
+
},
|
|
73
|
+
"mobile-config": {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "JSON object for apiMobileConfig ({deviceArn, deviceName, platform?, uploadArn?, uploadType?, appName?, bundleId?, apps?}); replaces the whole config. Clear with --json '{\"apiMobileConfig\":null}'",
|
|
76
|
+
},
|
|
77
|
+
"desktop-device": {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Registered BYOD desktop device id (aximo device status) for apiDesktopConfig. Clear with --json '{\"apiDesktopConfig\":null}'",
|
|
80
|
+
},
|
|
81
|
+
"multi-device-config": {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "JSON array of 2-3 device entries ({kind: web|mobile, name, ...}) for apiMultiDeviceConfig; replaces the whole config. Clear with --json '{\"apiMultiDeviceConfig\":null}'",
|
|
84
|
+
},
|
|
85
|
+
"web-config": {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "JSON object for apiWebConfig ({hostnameOverrideIds: [...]}); replaces the whole config. Clear with --json '{\"apiWebConfig\":null}'",
|
|
88
|
+
},
|
|
62
89
|
json: { type: "string", description: "Raw JSON request body (replaces individual flags)" },
|
|
63
90
|
"dry-run": { type: "boolean", description: "Validate and show request without calling API" },
|
|
64
91
|
},
|
|
@@ -71,6 +98,10 @@ export default defineCommand({
|
|
|
71
98
|
type: args.type,
|
|
72
99
|
format: args.format,
|
|
73
100
|
startUrl: args["start-url"],
|
|
101
|
+
mobileConfig: args["mobile-config"],
|
|
102
|
+
desktopDevice: args["desktop-device"],
|
|
103
|
+
multiDeviceConfig: args["multi-device-config"],
|
|
104
|
+
webConfig: args["web-config"],
|
|
74
105
|
json: args.json,
|
|
75
106
|
dryRun: args["dry-run"],
|
|
76
107
|
}));
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { type ClientDeps } from "../../client.js";
|
|
2
|
-
import type { Envelope } from "../../types.js";
|
|
3
|
-
interface UserDevice {
|
|
4
|
-
id: string;
|
|
5
|
-
name: string;
|
|
6
|
-
platform: string;
|
|
7
|
-
os?: string;
|
|
8
|
-
status: string;
|
|
9
|
-
lastSeenAt?: string;
|
|
10
|
-
createdAt: string;
|
|
11
|
-
}
|
|
2
|
+
import type { Envelope, UserDevice } from "../../types.js";
|
|
12
3
|
export declare function runDeviceStatus(deps?: ClientDeps): Promise<Envelope<{
|
|
13
4
|
devices: UserDevice[];
|
|
14
5
|
}>>;
|
|
@@ -24,7 +24,7 @@ async function connect() {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
describe("createMcpServer wiring", () => {
|
|
27
|
-
it("exposes all
|
|
27
|
+
it("exposes all 29 tools through tools/list with valid JSON Schema", async () => {
|
|
28
28
|
const { client, cleanup } = await connect();
|
|
29
29
|
try {
|
|
30
30
|
const { tools } = await client.listTools();
|
|
@@ -37,17 +37,27 @@ describe("createMcpServer wiring", () => {
|
|
|
37
37
|
"aximo_get_session_screenshots",
|
|
38
38
|
"aximo_get_session_timeline",
|
|
39
39
|
"aximo_list_cases",
|
|
40
|
+
"aximo_list_devices",
|
|
40
41
|
"aximo_list_projects",
|
|
41
42
|
"aximo_list_sessions",
|
|
43
|
+
"aximo_list_uploads",
|
|
44
|
+
"aximo_list_user_devices",
|
|
42
45
|
"aximo_create_case",
|
|
43
46
|
"aximo_update_case",
|
|
47
|
+
"aximo_archive_case",
|
|
48
|
+
"aximo_restore_case",
|
|
44
49
|
"aximo_delete_case",
|
|
50
|
+
"aximo_run_case",
|
|
45
51
|
"aximo_create_plan",
|
|
46
52
|
"aximo_add_case_to_plan",
|
|
47
53
|
"aximo_remove_case_from_plan",
|
|
48
54
|
"aximo_list_plans",
|
|
49
55
|
"aximo_get_plan",
|
|
50
56
|
"aximo_update_plan",
|
|
57
|
+
"aximo_archive_plan",
|
|
58
|
+
"aximo_restore_plan",
|
|
59
|
+
"aximo_delete_plan",
|
|
60
|
+
"aximo_run_plan",
|
|
51
61
|
"aximo_list_plan_runs",
|
|
52
62
|
"aximo_get_plan_run",
|
|
53
63
|
"aximo_save_session_as_case",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { request } from "../../client.js";
|
|
3
|
+
import { ARCHIVE_FIRST_HINT, featureHint, isArchiveFirstConflict, withDetails, } from "../../errors.js";
|
|
3
4
|
function ok(payload) {
|
|
4
5
|
return {
|
|
5
6
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
@@ -23,18 +24,31 @@ async function call(fn) {
|
|
|
23
24
|
catch (e) {
|
|
24
25
|
if (!(e instanceof Error))
|
|
25
26
|
return err(String(e));
|
|
26
|
-
const status = e
|
|
27
|
+
const { status, details } = e;
|
|
28
|
+
const message = withDetails(e.message, details);
|
|
27
29
|
if (status === 401)
|
|
28
30
|
return err("Unauthorized: check AXIMO_API_KEY", "Run `aximo configure`.");
|
|
29
31
|
if (status === 403) {
|
|
30
|
-
return err(
|
|
32
|
+
return err(message || "Forbidden", featureHint(e.message, details) ?? "API key may not have access to this resource.");
|
|
31
33
|
}
|
|
32
34
|
if (status === 404)
|
|
33
|
-
return err(
|
|
34
|
-
if (status === 429)
|
|
35
|
-
|
|
35
|
+
return err(message || "Not found");
|
|
36
|
+
if (status === 429) {
|
|
37
|
+
const retryAfter = e.retryAfter;
|
|
38
|
+
return err("Rate limited", retryAfter
|
|
39
|
+
? `Back off and retry after ${retryAfter} seconds.`
|
|
40
|
+
: "Back off before retrying; Aximo rate-limits requests per organization.");
|
|
41
|
+
}
|
|
42
|
+
// Mirrors the 409 branch in handleApiError. Without this, an archive-first
|
|
43
|
+
// 409 falls through to the bare `HTTP 409: ...` case below and the agent is
|
|
44
|
+
// told to call a REST endpoint it has no way to reach, while the tool that
|
|
45
|
+
// would work goes unmentioned. Keyed on the details-enriched message, the
|
|
46
|
+
// same input handleApiError feeds the predicate, so the two cannot disagree.
|
|
47
|
+
if (status === 409) {
|
|
48
|
+
return err(message || "Conflict", isArchiveFirstConflict(message) ? ARCHIVE_FIRST_HINT : undefined);
|
|
49
|
+
}
|
|
36
50
|
if (typeof status === "number")
|
|
37
|
-
return err(`HTTP ${status}: ${
|
|
51
|
+
return err(`HTTP ${status}: ${message}`);
|
|
38
52
|
if (e.message.startsWith("No API key configured")) {
|
|
39
53
|
return err("API key not configured", "Run `aximo configure` and restart the MCP server.");
|
|
40
54
|
}
|
|
@@ -54,6 +68,18 @@ function buildQuery(params) {
|
|
|
54
68
|
}
|
|
55
69
|
return out;
|
|
56
70
|
}
|
|
71
|
+
// Mobile app selection accepted by the run triggers (multi-app, max 3).
|
|
72
|
+
// ARN fields are the encrypted tokens Aximo returns, never raw AWS ARNs.
|
|
73
|
+
const runAppsSchema = z
|
|
74
|
+
.array(z.object({
|
|
75
|
+
uploadArn: z
|
|
76
|
+
.string()
|
|
77
|
+
.describe("Encrypted upload token (from aximo_get_case config or `aximo upload`)."),
|
|
78
|
+
uploadType: z.enum(["own_app", "existing", "sample"]),
|
|
79
|
+
appName: z.string().nullable(),
|
|
80
|
+
bundleId: z.string().optional(),
|
|
81
|
+
}))
|
|
82
|
+
.max(3);
|
|
57
83
|
export function defineTools(deps = {}) {
|
|
58
84
|
return [
|
|
59
85
|
{
|
|
@@ -66,6 +92,53 @@ export function defineTools(deps = {}) {
|
|
|
66
92
|
return res;
|
|
67
93
|
}),
|
|
68
94
|
},
|
|
95
|
+
{
|
|
96
|
+
name: "aximo_list_devices",
|
|
97
|
+
title: "List Device Farm devices",
|
|
98
|
+
description: "List the cloud (AWS Device Farm) mobile devices available to your organization — not BYOD devices (see aximo_list_user_devices). Each device's arn is the encrypted token to pass as deviceArn, and its name as deviceName, in a case's apiMobileConfig or multi-device mobile entries (aximo_update_case) and in per-run overrides (aximo_run_case, aximo_run_plan). Filter by platform (ANDROID | IOS) or availableOnly.",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
platform: z.enum(["ANDROID", "IOS"]).optional(),
|
|
101
|
+
availableOnly: z
|
|
102
|
+
.boolean()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("Only devices that are currently free to run on."),
|
|
105
|
+
},
|
|
106
|
+
handler: async ({ platform, availableOnly }) => call(async () => {
|
|
107
|
+
return await request("/api/devices", { query: { platform, availableOnly: availableOnly || undefined } }, deps);
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "aximo_list_user_devices",
|
|
112
|
+
title: "List registered BYOD devices",
|
|
113
|
+
description: "List the BYOD (bring-your-own) desktop and mobile devices registered with `aximo device register` by the owner of this API key — other members' devices are not included. Each device's id is the userDeviceId to pass in a case's apiDesktopConfig or multi-device local entries (aximo_update_case) and in per-run overrides. status shows whether the device is online. Returns 404 when BYOD is not enabled for the organization.",
|
|
114
|
+
inputSchema: {},
|
|
115
|
+
handler: async () => call(async () => {
|
|
116
|
+
try {
|
|
117
|
+
return await request("/api/devices/user", {}, deps);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
// The route answers a bare 404 when the org has no BYOD feature.
|
|
121
|
+
if (e.status === 404) {
|
|
122
|
+
throw Object.assign(new Error("BYOD is not enabled for this organization"), {
|
|
123
|
+
status: 403,
|
|
124
|
+
details: { feature: "byodEnabled" },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "aximo_list_uploads",
|
|
133
|
+
title: "List app uploads",
|
|
134
|
+
description: "List the mobile app binaries (APK / IPA) uploaded to your organization. Each upload's uploadArn is the encrypted token to pass as uploadArn in a case's apiMobileConfig or multi-device mobile entries (aximo_update_case) and in per-run overrides; only uploads with status succeeded can be installed. identifier is the app's bundleId. Filter by fileType (ANDROID_APP | IOS_APP).",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
fileType: z.enum(["ANDROID_APP", "IOS_APP"]).optional(),
|
|
137
|
+
},
|
|
138
|
+
handler: async ({ fileType }) => call(async () => {
|
|
139
|
+
return await request("/api/uploads", { query: { fileType } }, deps);
|
|
140
|
+
}),
|
|
141
|
+
},
|
|
69
142
|
{
|
|
70
143
|
name: "aximo_list_sessions",
|
|
71
144
|
title: "List sessions",
|
|
@@ -215,15 +288,36 @@ export function defineTools(deps = {}) {
|
|
|
215
288
|
{
|
|
216
289
|
name: "aximo_update_case",
|
|
217
290
|
title: "Update test case",
|
|
218
|
-
description: "Update a test case's title, description, scenario, type, format, or startUrl (pass null to clear startUrl). Omitted fields are left unchanged; at least one field is required.
|
|
291
|
+
description: "Update a test case's title, description, scenario, type, format, or startUrl (pass null to clear description or startUrl; an empty description string is stored as-is, not a clear). Omitted fields are left unchanged; at least one field is required. Pass apiMobileConfig / apiDesktopConfig / apiMultiDeviceConfig / apiWebConfig to set the case's default targets — the same shapes aximo_get_case returns; null clears one, omit leaves it unchanged. Each config value is replaced whole — send the complete config object you want stored (never a partial), and omit a config key entirely to leave that target unchanged. deviceArn = aximo_list_devices devices[].arn and deviceName = devices[].name; uploadArn = aximo_list_uploads uploads[].uploadArn (status succeeded), bundleId = uploads[].identifier, uploadType is usually \"own_app\"; userDeviceId = aximo_list_user_devices devices[].id. apiMobileConfig.platform is ANDROID|IOS; multi-device mobilePlatform is android|ios. There is no top-level userDeviceId/deviceArn here — they live inside the config objects. Desktop and multi-device require the organization's BYOD / multi-device feature. You can set type in the same call.",
|
|
219
292
|
inputSchema: {
|
|
220
293
|
caseId: z.string().uuid(),
|
|
221
294
|
title: z.string().min(1).max(500).optional(),
|
|
222
|
-
description: z.string().optional(),
|
|
295
|
+
description: z.string().nullable().optional(),
|
|
223
296
|
scenario: z.string().min(1).optional(),
|
|
224
297
|
type: z.enum(["web", "mobile", "desktop", "multi_device"]).optional(),
|
|
225
298
|
format: z.enum(["gherkin", "markdown", "plain_text"]).optional(),
|
|
226
299
|
startUrl: z.string().url().nullable().optional(),
|
|
300
|
+
apiMobileConfig: z
|
|
301
|
+
.record(z.unknown())
|
|
302
|
+
.nullable()
|
|
303
|
+
.optional()
|
|
304
|
+
.describe("{ deviceArn, deviceName, platform?: ANDROID|IOS, uploadArn?, uploadType?: own_app|existing|sample, appName?, bundleId?, apps?: [{ uploadArn, uploadType, appName, bundleId? }] (max 3) }. null clears."),
|
|
305
|
+
apiDesktopConfig: z
|
|
306
|
+
.object({ userDeviceId: z.string().uuid() })
|
|
307
|
+
.passthrough()
|
|
308
|
+
.nullable()
|
|
309
|
+
.optional()
|
|
310
|
+
.describe("{ userDeviceId } of a registered BYOD desktop device (aximo_list_user_devices); name/platform/os are resolved server-side. null clears."),
|
|
311
|
+
apiMultiDeviceConfig: z
|
|
312
|
+
.array(z.record(z.unknown()))
|
|
313
|
+
.nullable()
|
|
314
|
+
.optional()
|
|
315
|
+
.describe('2-3 entries, each { kind: "web", name, startUrl?, userDeviceId? } or { kind: "mobile", name, mobilePlatform: android|ios, deviceArn?, deviceName?, userDeviceId?, uploadArn?, uploadType?, appName?, bundleId?, apps? }. null clears.'),
|
|
316
|
+
apiWebConfig: z
|
|
317
|
+
.record(z.unknown())
|
|
318
|
+
.nullable()
|
|
319
|
+
.optional()
|
|
320
|
+
.describe("{ hostnameOverrideIds?: string[] }. null clears."),
|
|
227
321
|
},
|
|
228
322
|
handler: async ({ caseId, ...body }) => call(async () => {
|
|
229
323
|
if (Object.keys(body).length === 0) {
|
|
@@ -232,10 +326,32 @@ export function defineTools(deps = {}) {
|
|
|
232
326
|
return await request(`/api/cases/${encodeURIComponent(caseId)}`, { method: "PATCH", body }, deps);
|
|
233
327
|
}),
|
|
234
328
|
},
|
|
329
|
+
{
|
|
330
|
+
name: "aximo_archive_case",
|
|
331
|
+
title: "Archive test case",
|
|
332
|
+
description: "Archive a test case: hidden from lists, plans, and new runs; run history kept. Idempotent. Required before aximo_delete_case can permanently delete it.",
|
|
333
|
+
inputSchema: {
|
|
334
|
+
caseId: z.string().uuid(),
|
|
335
|
+
},
|
|
336
|
+
handler: async ({ caseId }) => call(async () => {
|
|
337
|
+
return await request(`/api/cases/${encodeURIComponent(caseId)}/archive`, { method: "POST" }, deps);
|
|
338
|
+
}),
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: "aximo_restore_case",
|
|
342
|
+
title: "Restore test case",
|
|
343
|
+
description: "Restore an archived test case: it returns to lists, directory counts, and plan membership. Idempotent. A case whose directory was deleted while archived returns to the project root.",
|
|
344
|
+
inputSchema: {
|
|
345
|
+
caseId: z.string().uuid(),
|
|
346
|
+
},
|
|
347
|
+
handler: async ({ caseId }) => call(async () => {
|
|
348
|
+
return await request(`/api/cases/${encodeURIComponent(caseId)}/restore`, { method: "POST" }, deps);
|
|
349
|
+
}),
|
|
350
|
+
},
|
|
235
351
|
{
|
|
236
352
|
name: "aximo_delete_case",
|
|
237
353
|
title: "Delete test case",
|
|
238
|
-
description: "Permanently delete
|
|
354
|
+
description: "Permanently delete an already-archived test case. Archive it first with aximo_archive_case. Requires an organization admin or owner API key. It is removed from any plans it belongs to, and its rows in past plan runs go with it, so those runs list fewer cases than their stored totals; the agent sessions themselves are kept but detached. This cannot be undone.",
|
|
239
355
|
inputSchema: {
|
|
240
356
|
caseId: z.string().uuid(),
|
|
241
357
|
},
|
|
@@ -243,6 +359,45 @@ export function defineTools(deps = {}) {
|
|
|
243
359
|
return await request(`/api/cases/${encodeURIComponent(caseId)}`, { method: "DELETE" }, deps);
|
|
244
360
|
}),
|
|
245
361
|
},
|
|
362
|
+
{
|
|
363
|
+
name: "aximo_run_case",
|
|
364
|
+
title: "Run test case",
|
|
365
|
+
description: "Start a run of a saved test case and return the new sessionId. The case must be apiEnabled (aximo_get_case shows this). All fields except caseId are per-run overrides of the case's stored per-target configuration: omit them to run with the saved defaults. Poll aximo_get_session with the returned sessionId to follow the run; wait at least 5 seconds between polls.",
|
|
366
|
+
inputSchema: {
|
|
367
|
+
caseId: z.string().uuid(),
|
|
368
|
+
model: z
|
|
369
|
+
.string()
|
|
370
|
+
.optional()
|
|
371
|
+
.describe("Agent model override (e.g. 'sonnet', 'haiku', 'auto'). Server-validated."),
|
|
372
|
+
hostnameOverrideIds: z
|
|
373
|
+
.array(z.string().uuid())
|
|
374
|
+
.optional()
|
|
375
|
+
.describe("Web cases: hostname override ids to apply for this run."),
|
|
376
|
+
userDeviceId: z
|
|
377
|
+
.string()
|
|
378
|
+
.uuid()
|
|
379
|
+
.optional()
|
|
380
|
+
.describe("A user-owned (BYOD) device id; overrides the device bound on the case."),
|
|
381
|
+
deviceArn: z
|
|
382
|
+
.string()
|
|
383
|
+
.optional()
|
|
384
|
+
.describe("Mobile cases: encrypted device token; overrides the saved device."),
|
|
385
|
+
deviceName: z.string().optional(),
|
|
386
|
+
platform: z.enum(["ANDROID", "IOS"]).optional(),
|
|
387
|
+
uploadArn: z
|
|
388
|
+
.string()
|
|
389
|
+
.optional()
|
|
390
|
+
.describe("Mobile cases: encrypted app upload token; overrides the saved app."),
|
|
391
|
+
uploadType: z.enum(["own_app", "existing", "sample"]).optional(),
|
|
392
|
+
appName: z.string().optional(),
|
|
393
|
+
apps: runAppsSchema
|
|
394
|
+
.optional()
|
|
395
|
+
.describe("Mobile multi-app runs: up to 3 apps to install; overrides the saved apps and takes precedence over the single-app uploadArn/uploadType/appName fields."),
|
|
396
|
+
},
|
|
397
|
+
handler: async ({ caseId, ...body }) => call(async () => {
|
|
398
|
+
return await request(`/api/triggers/cases/${encodeURIComponent(caseId)}/run`, { method: "POST", body }, deps);
|
|
399
|
+
}),
|
|
400
|
+
},
|
|
246
401
|
{
|
|
247
402
|
name: "aximo_create_plan",
|
|
248
403
|
title: "Create test plan",
|
|
@@ -323,6 +478,87 @@ export function defineTools(deps = {}) {
|
|
|
323
478
|
return await request(`/api/plans/${encodeURIComponent(planId)}`, { method: "PATCH", body }, deps);
|
|
324
479
|
}),
|
|
325
480
|
},
|
|
481
|
+
{
|
|
482
|
+
name: "aximo_archive_plan",
|
|
483
|
+
title: "Archive test plan",
|
|
484
|
+
description: "Archive a test plan: hidden from lists and blocked from new runs; its cases, run history, and schedule are all kept. The scheduler skips archived plans, so restoring one resumes its normal cadence. Idempotent. Required before aximo_delete_plan can permanently delete it.",
|
|
485
|
+
inputSchema: {
|
|
486
|
+
planId: z.string().uuid(),
|
|
487
|
+
},
|
|
488
|
+
handler: async ({ planId }) => call(async () => {
|
|
489
|
+
return await request(`/api/plans/${encodeURIComponent(planId)}/archive`, { method: "POST" }, deps);
|
|
490
|
+
}),
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: "aximo_restore_plan",
|
|
494
|
+
title: "Restore test plan",
|
|
495
|
+
description: "Restore an archived test plan: it returns to lists and can be run again, and its schedule resumes exactly as configured. Idempotent.",
|
|
496
|
+
inputSchema: {
|
|
497
|
+
planId: z.string().uuid(),
|
|
498
|
+
},
|
|
499
|
+
handler: async ({ planId }) => call(async () => {
|
|
500
|
+
return await request(`/api/plans/${encodeURIComponent(planId)}/restore`, { method: "POST" }, deps);
|
|
501
|
+
}),
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "aximo_delete_plan",
|
|
505
|
+
title: "Delete test plan",
|
|
506
|
+
description: "Permanently delete an already-archived test plan. Archive it first with aximo_archive_plan. Requires an organization admin or owner API key. Its member cases are kept, and so is its run history: those runs are detached from the plan and remain readable as standalone results. This cannot be undone.",
|
|
507
|
+
inputSchema: {
|
|
508
|
+
planId: z.string().uuid(),
|
|
509
|
+
},
|
|
510
|
+
handler: async ({ planId }) => call(async () => {
|
|
511
|
+
return await request(`/api/plans/${encodeURIComponent(planId)}`, { method: "DELETE" }, deps);
|
|
512
|
+
}),
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
name: "aximo_run_plan",
|
|
516
|
+
title: "Run test plan",
|
|
517
|
+
description: "Start a run of a test plan (all its cases) and return the new planRunId, totalCases, and the effective executionMode. The plan must be apiEnabled (aximo_get_plan shows this). All fields except planId are per-run overrides of the plan's stored settings: omit them to run with the saved defaults. Poll aximo_get_plan_run with the returned planRunId to follow the run; wait at least 5 seconds between polls.",
|
|
518
|
+
inputSchema: {
|
|
519
|
+
planId: z.string().uuid(),
|
|
520
|
+
model: z
|
|
521
|
+
.string()
|
|
522
|
+
.optional()
|
|
523
|
+
.describe("Agent model override (e.g. 'sonnet', 'haiku', 'auto'). Server-validated."),
|
|
524
|
+
executionMode: z
|
|
525
|
+
.enum(["parallel", "sequential"])
|
|
526
|
+
.optional()
|
|
527
|
+
.describe("Override the plan's stored Execution setting for this run only."),
|
|
528
|
+
mobileConfig: z
|
|
529
|
+
.object({
|
|
530
|
+
deviceArn: z.string().describe("Encrypted device token."),
|
|
531
|
+
deviceName: z.string(),
|
|
532
|
+
platform: z.enum(["ANDROID", "IOS"]).optional(),
|
|
533
|
+
uploadArn: z.string().optional().describe("Encrypted app upload token."),
|
|
534
|
+
uploadType: z.enum(["own_app", "existing", "sample"]).optional(),
|
|
535
|
+
appName: z.string().optional(),
|
|
536
|
+
apps: runAppsSchema
|
|
537
|
+
.optional()
|
|
538
|
+
.describe("Takes precedence over the single-app uploadArn/uploadType/appName fields."),
|
|
539
|
+
})
|
|
540
|
+
.optional()
|
|
541
|
+
.describe("Device + app for the plan's mobile cases; overrides the plan's stored config."),
|
|
542
|
+
desktopConfig: z
|
|
543
|
+
.object({
|
|
544
|
+
devices: z.array(z.object({ userDeviceId: z.string().uuid() })).min(1),
|
|
545
|
+
})
|
|
546
|
+
.optional()
|
|
547
|
+
.describe("User-owned (BYOD) desktop devices to run desktop cases on; overrides the plan's stored desktop config."),
|
|
548
|
+
variables: z
|
|
549
|
+
.array(z.object({
|
|
550
|
+
name: z
|
|
551
|
+
.string()
|
|
552
|
+
.describe("Uppercase letters, digits, underscores; must start with a letter."),
|
|
553
|
+
value: z.string(),
|
|
554
|
+
}))
|
|
555
|
+
.optional()
|
|
556
|
+
.describe("Per-run variable values merged over the plan's stored variables by name; never persisted on the plan."),
|
|
557
|
+
},
|
|
558
|
+
handler: async ({ planId, ...body }) => call(async () => {
|
|
559
|
+
return await request(`/api/triggers/plans/${encodeURIComponent(planId)}/run`, { method: "POST", body }, deps);
|
|
560
|
+
}),
|
|
561
|
+
},
|
|
326
562
|
{
|
|
327
563
|
name: "aximo_list_plan_runs",
|
|
328
564
|
title: "List plan runs",
|