@autifyhq/aximo-cli 0.15.0 → 0.17.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 +19 -1
- package/dist/commands/device/device-config.d.ts +2 -2
- package/dist/commands/device/device-config.js +5 -8
- package/dist/commands/device/index.js +2 -1
- package/dist/commands/device/paths.d.ts +4 -0
- package/dist/commands/device/paths.js +12 -0
- package/dist/commands/device/reset.d.ts +1 -0
- package/dist/commands/device/reset.js +18 -3
- package/dist/commands/device/reset.test.js +13 -0
- package/dist/commands/device/testbox/box.d.ts +90 -0
- package/dist/commands/device/testbox/box.js +379 -0
- package/dist/commands/device/testbox/box.test.d.ts +1 -0
- package/dist/commands/device/testbox/box.test.js +815 -0
- package/dist/commands/device/testbox/container.d.ts +58 -0
- package/dist/commands/device/testbox/container.js +289 -0
- package/dist/commands/device/testbox/docker.d.ts +31 -0
- package/dist/commands/device/testbox/docker.js +78 -0
- package/dist/commands/device/testbox/docker.test.d.ts +1 -0
- package/dist/commands/device/testbox/docker.test.js +82 -0
- package/dist/commands/device/testbox/down.d.ts +7 -0
- package/dist/commands/device/testbox/down.js +14 -0
- package/dist/commands/device/testbox/errors.d.ts +4 -0
- package/dist/commands/device/testbox/errors.js +7 -0
- package/dist/commands/device/testbox/fake-docker.d.ts +25 -0
- package/dist/commands/device/testbox/fake-docker.js +35 -0
- package/dist/commands/device/testbox/index.d.ts +2 -0
- package/dist/commands/device/testbox/index.js +16 -0
- package/dist/commands/device/testbox/logs.d.ts +15 -0
- package/dist/commands/device/testbox/logs.js +43 -0
- package/dist/commands/device/testbox/prune.d.ts +11 -0
- package/dist/commands/device/testbox/prune.js +17 -0
- package/dist/commands/device/testbox/registration.d.ts +9 -0
- package/dist/commands/device/testbox/registration.js +56 -0
- package/dist/commands/device/testbox/reset.d.ts +7 -0
- package/dist/commands/device/testbox/reset.js +14 -0
- package/dist/commands/device/testbox/result.d.ts +8 -0
- package/dist/commands/device/testbox/result.js +23 -0
- package/dist/commands/device/testbox/result.test.d.ts +1 -0
- package/dist/commands/device/testbox/result.test.js +34 -0
- package/dist/commands/device/testbox/self-command.d.ts +12 -0
- package/dist/commands/device/testbox/self-command.js +36 -0
- package/dist/commands/device/testbox/self-command.test.d.ts +1 -0
- package/dist/commands/device/testbox/self-command.test.js +58 -0
- package/dist/commands/device/testbox/status.d.ts +7 -0
- package/dist/commands/device/testbox/status.js +11 -0
- package/dist/commands/device/testbox/up.d.ts +47 -0
- package/dist/commands/device/testbox/up.js +76 -0
- package/dist/commands/device/testbox/up.test.d.ts +1 -0
- package/dist/commands/device/testbox/up.test.js +52 -0
- package/dist/commands/device/testbox/url.d.ts +7 -0
- package/dist/commands/device/testbox/url.js +20 -0
- package/dist/commands/device/user-devices.d.ts +4 -0
- package/dist/commands/device/user-devices.js +18 -0
- package/dist/commands/mcp/server.d.ts +2 -2
- package/dist/commands/mcp/server.test.js +7 -2
- package/dist/commands/mcp/testbox-tools.test.d.ts +1 -0
- package/dist/commands/mcp/testbox-tools.test.js +278 -0
- package/dist/commands/mcp/tools.d.ts +8 -1
- package/dist/commands/mcp/tools.js +171 -20
- package/dist/commands/mcp/tools.test.js +59 -2
- package/dist/commands/schema.js +5 -1
- package/dist/commands/session/cancel.d.ts +20 -0
- package/dist/commands/session/cancel.js +39 -0
- package/dist/commands/session/index.js +1 -0
- package/dist/commands/session/session.test.js +38 -0
- package/dist/commands/trigger/plan/run.d.ts +1 -1
- package/dist/commands/trigger/plan/run.js +4 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +5 -0
- package/dist/errors.test.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { ARCHIVE_FIRST_HINT, handleApiError, isArchiveFirstConflict } from "../../errors.js";
|
|
6
|
+
import { ARCHIVE_FIRST_HINT, CREDIT_HINT, handleApiError, isArchiveFirstConflict, } from "../../errors.js";
|
|
7
7
|
import { HttpResponse, http, server, TEST_BASE_URL } from "../../test-utils.js";
|
|
8
8
|
import { defineTools } from "./tools.js";
|
|
9
9
|
beforeAll(() => {
|
|
@@ -27,6 +27,7 @@ describe("defineTools", () => {
|
|
|
27
27
|
const names = defineTools().map((t) => t.name);
|
|
28
28
|
expect(names).toEqual([
|
|
29
29
|
"aximo_list_projects",
|
|
30
|
+
"aximo_get_credits",
|
|
30
31
|
"aximo_list_devices",
|
|
31
32
|
"aximo_list_user_devices",
|
|
32
33
|
"aximo_list_uploads",
|
|
@@ -49,6 +50,7 @@ describe("defineTools", () => {
|
|
|
49
50
|
"aximo_delete_case",
|
|
50
51
|
"aximo_run_case",
|
|
51
52
|
"aximo_create_session",
|
|
53
|
+
"aximo_cancel_session",
|
|
52
54
|
"aximo_create_plan",
|
|
53
55
|
"aximo_add_case_to_plan",
|
|
54
56
|
"aximo_remove_case_from_plan",
|
|
@@ -62,6 +64,9 @@ describe("defineTools", () => {
|
|
|
62
64
|
"aximo_list_plan_runs",
|
|
63
65
|
"aximo_get_plan_run",
|
|
64
66
|
"aximo_save_session_as_case",
|
|
67
|
+
"aximo_start_testbox",
|
|
68
|
+
"aximo_get_testbox",
|
|
69
|
+
"aximo_stop_testbox",
|
|
65
70
|
]);
|
|
66
71
|
});
|
|
67
72
|
});
|
|
@@ -149,6 +154,37 @@ describe("aximo_list_cases", () => {
|
|
|
149
154
|
expect(parsed.success).toBe(false);
|
|
150
155
|
});
|
|
151
156
|
});
|
|
157
|
+
describe("aximo_get_credits", () => {
|
|
158
|
+
it("calls /api/organizations/credits and returns the payload", async () => {
|
|
159
|
+
const payload = {
|
|
160
|
+
creditUnit: "platform",
|
|
161
|
+
granted: 1000,
|
|
162
|
+
used: 250.5,
|
|
163
|
+
remaining: 749.5,
|
|
164
|
+
onDemandRemaining: 0,
|
|
165
|
+
metered: true,
|
|
166
|
+
blocked: false,
|
|
167
|
+
};
|
|
168
|
+
let captured = null;
|
|
169
|
+
server.use(http.get(`${TEST_BASE_URL}/api/organizations/credits`, ({ request: req }) => {
|
|
170
|
+
captured = { method: req.method, url: req.url };
|
|
171
|
+
return HttpResponse.json(payload);
|
|
172
|
+
}));
|
|
173
|
+
const result = await byName(defineTools(), "aximo_get_credits").handler({});
|
|
174
|
+
expect(result.isError).toBeUndefined();
|
|
175
|
+
expect(parsePayload(result)).toEqual(payload);
|
|
176
|
+
expect(captured.method).toBe("GET");
|
|
177
|
+
expect(new URL(captured.url).pathname).toBe("/api/organizations/credits");
|
|
178
|
+
});
|
|
179
|
+
it("says the server is too old instead of returning its HTML 404 page", async () => {
|
|
180
|
+
server.use(http.get(`${TEST_BASE_URL}/api/organizations/credits`, () => new HttpResponse("<!DOCTYPE html><html>404</html>", { status: 404 })));
|
|
181
|
+
const result = await byName(defineTools(), "aximo_get_credits").handler({});
|
|
182
|
+
expect(result.isError).toBe(true);
|
|
183
|
+
expect(parsePayload(result)).toEqual({
|
|
184
|
+
error: "This Aximo server does not support the credit balance yet",
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
152
188
|
describe("aximo_list_projects", () => {
|
|
153
189
|
it("calls /api/organizations/projects and returns the payload", async () => {
|
|
154
190
|
const payload = { projects: [{ id: "p1", name: "Default" }] };
|
|
@@ -288,6 +324,24 @@ describe("aximo_wait_for_session", () => {
|
|
|
288
324
|
expect(parsePayload(result)).toEqual({ error: "Session not found" });
|
|
289
325
|
});
|
|
290
326
|
});
|
|
327
|
+
describe("aximo_cancel_session", () => {
|
|
328
|
+
const sessionId = "78787878-7878-4787-9787-787878787878";
|
|
329
|
+
it("POSTs the cancel endpoint and returns the terminated session", async () => {
|
|
330
|
+
const payload = { success: true, sessionId, status: "terminated" };
|
|
331
|
+
server.use(http.post(`${TEST_BASE_URL}/api/sessions/${sessionId}/cancel`, () => HttpResponse.json(payload)));
|
|
332
|
+
const result = await byName(defineTools(), "aximo_cancel_session").handler({ sessionId });
|
|
333
|
+
expect(result.isError).toBeUndefined();
|
|
334
|
+
expect(parsePayload(result)).toEqual(payload);
|
|
335
|
+
});
|
|
336
|
+
it("reports a finished session as an error with no archive hint", async () => {
|
|
337
|
+
server.use(http.post(`${TEST_BASE_URL}/api/sessions/${sessionId}/cancel`, () => HttpResponse.json({ error: "Cannot cancel a session that has already finished" }, { status: 409 })));
|
|
338
|
+
const result = await byName(defineTools(), "aximo_cancel_session").handler({ sessionId });
|
|
339
|
+
expect(result.isError).toBe(true);
|
|
340
|
+
expect(parsePayload(result)).toEqual({
|
|
341
|
+
error: "Cannot cancel a session that has already finished",
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
291
345
|
describe("aximo_create_session", () => {
|
|
292
346
|
const projectId = "12121212-1212-4212-9212-121212121212";
|
|
293
347
|
it("POSTs /api/sessions with the prompt and device and returns sessionId", async () => {
|
|
@@ -370,7 +424,10 @@ describe("aximo_run_case", () => {
|
|
|
370
424
|
const tool = byName(defineTools(), "aximo_run_case");
|
|
371
425
|
const result = await tool.handler({ caseId });
|
|
372
426
|
expect(result.isError).toBe(true);
|
|
373
|
-
expect(parsePayload(result)
|
|
427
|
+
expect(parsePayload(result)).toEqual({
|
|
428
|
+
error: "Insufficient credits to start session",
|
|
429
|
+
hint: CREDIT_HINT,
|
|
430
|
+
});
|
|
374
431
|
});
|
|
375
432
|
it("maps a 429 to a rate-limit error an agent can back off on", async () => {
|
|
376
433
|
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, () => HttpResponse.json({ error: "Rate limit exceeded. Try again later." }, { status: 429, headers: { "Retry-After": "60" } })));
|
package/dist/commands/schema.js
CHANGED
|
@@ -42,6 +42,10 @@ export function getSchema() {
|
|
|
42
42
|
cmd("get", "GET /api/sessions/{sessionId}", [
|
|
43
43
|
arg("sessionId", "positional", "Session ID", true),
|
|
44
44
|
]),
|
|
45
|
+
cmd("cancel", "POST /api/sessions/{sessionId}/cancel", [
|
|
46
|
+
arg("sessionId", "positional", "Session ID", true),
|
|
47
|
+
arg("dry-run", "boolean", "Validate and show request without calling API"),
|
|
48
|
+
]),
|
|
45
49
|
cmd("save-as-case", "POST /api/sessions/{sessionId}/save-as-case", [
|
|
46
50
|
arg("sessionId", "positional", "Session ID", true),
|
|
47
51
|
arg("title", "string", ""),
|
|
@@ -105,7 +109,7 @@ export function getSchema() {
|
|
|
105
109
|
cmd("run", "POST /api/triggers/plans/{planId}/run", [
|
|
106
110
|
arg("planId", "positional", "Plan ID", true),
|
|
107
111
|
arg("model", "string", ""),
|
|
108
|
-
arg("mobile-config", "string", "JSON object string"),
|
|
112
|
+
arg("mobile-config", "string", "JSON object string: fallback device + app, used only for mobile cases with no device saved on the plan or the case"),
|
|
109
113
|
...MUTATE_ARGS,
|
|
110
114
|
]),
|
|
111
115
|
cmd("get", "GET /api/triggers/plans/{planId}/runs/{planRunId}", [
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ClientDeps } from "../../client.js";
|
|
2
|
+
import { type DryRunResult } from "../../mutate.js";
|
|
3
|
+
import type { CancelSessionResult, Envelope } from "../../types.js";
|
|
4
|
+
export interface SessionCancelOptions {
|
|
5
|
+
sessionId?: unknown;
|
|
6
|
+
dryRun?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function runSessionCancel(options: SessionCancelOptions, deps?: ClientDeps): Promise<Envelope<CancelSessionResult | DryRunResult>>;
|
|
9
|
+
declare const _default: import("citty").CommandDef<{
|
|
10
|
+
readonly sessionId: {
|
|
11
|
+
readonly type: "positional";
|
|
12
|
+
readonly description: "Session ID";
|
|
13
|
+
readonly required: true;
|
|
14
|
+
};
|
|
15
|
+
readonly "dry-run": {
|
|
16
|
+
readonly type: "boolean";
|
|
17
|
+
readonly description: "Validate and show request without calling API";
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import { request } from "../../client.js";
|
|
3
|
+
import { handleApiError } from "../../errors.js";
|
|
4
|
+
import { dryRunEnvelope } from "../../mutate.js";
|
|
5
|
+
import { formatSuccess, printResult, validationError } from "../../output.js";
|
|
6
|
+
import { requireId } from "../../validate.js";
|
|
7
|
+
export async function runSessionCancel(options, deps) {
|
|
8
|
+
let sessionId;
|
|
9
|
+
try {
|
|
10
|
+
sessionId = requireId("sessionId", options.sessionId);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return validationError(err);
|
|
14
|
+
}
|
|
15
|
+
const path = `/api/sessions/${encodeURIComponent(sessionId)}/cancel`;
|
|
16
|
+
if (options.dryRun) {
|
|
17
|
+
return dryRunEnvelope("POST", path);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const res = await request(path, { method: "POST" }, deps ?? {});
|
|
21
|
+
return formatSuccess(res);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
return handleApiError(err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export default defineCommand({
|
|
28
|
+
meta: {
|
|
29
|
+
name: "cancel",
|
|
30
|
+
description: "Cancel a queued or running session (stops it on its device)",
|
|
31
|
+
},
|
|
32
|
+
args: {
|
|
33
|
+
sessionId: { type: "positional", description: "Session ID", required: true },
|
|
34
|
+
"dry-run": { type: "boolean", description: "Validate and show request without calling API" },
|
|
35
|
+
},
|
|
36
|
+
async run({ args }) {
|
|
37
|
+
printResult(await runSessionCancel({ sessionId: args.sessionId, dryRun: args["dry-run"] }));
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -4,6 +4,7 @@ export default defineCommand({
|
|
|
4
4
|
subCommands: {
|
|
5
5
|
create: () => import("./create.js").then((m) => m.default),
|
|
6
6
|
get: () => import("./get.js").then((m) => m.default),
|
|
7
|
+
cancel: () => import("./cancel.js").then((m) => m.default),
|
|
7
8
|
export: () => import("./export.js").then((m) => m.default),
|
|
8
9
|
"save-as-case": () => import("./save-as-case.js").then((m) => m.default),
|
|
9
10
|
},
|
|
@@ -3,6 +3,7 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
5
5
|
import { server, http, HttpResponse, TEST_BASE_URL } from "../../test-utils.js";
|
|
6
|
+
import { runSessionCancel } from "./cancel.js";
|
|
6
7
|
import { runSessionCreate } from "./create.js";
|
|
7
8
|
import { runSessionExport } from "./export.js";
|
|
8
9
|
import { runSessionGet } from "./get.js";
|
|
@@ -130,6 +131,43 @@ describe("runSessionGet", () => {
|
|
|
130
131
|
expect(result.error.code).toBe("not_found");
|
|
131
132
|
});
|
|
132
133
|
});
|
|
134
|
+
describe("runSessionCancel", () => {
|
|
135
|
+
it("POSTs the cancel endpoint and returns the terminated session", async () => {
|
|
136
|
+
let method;
|
|
137
|
+
server.use(http.post(`${TEST_BASE_URL}/api/sessions/s1/cancel`, ({ request: req }) => {
|
|
138
|
+
method = req.method;
|
|
139
|
+
return HttpResponse.json({ success: true, sessionId: "s1", status: "terminated" });
|
|
140
|
+
}));
|
|
141
|
+
const result = await runSessionCancel({ sessionId: "s1" });
|
|
142
|
+
expect(method).toBe("POST");
|
|
143
|
+
expect(result).toEqual({
|
|
144
|
+
ok: true,
|
|
145
|
+
data: { success: true, sessionId: "s1", status: "terminated" },
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
it("shows the request without calling the API on --dry-run", async () => {
|
|
149
|
+
const result = await runSessionCancel({ sessionId: "s1", dryRun: true });
|
|
150
|
+
expect(result).toEqual({
|
|
151
|
+
ok: true,
|
|
152
|
+
data: { dryRun: true, method: "POST", url: `${TEST_BASE_URL}/api/sessions/s1/cancel` },
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
it("rejects an empty id", async () => {
|
|
156
|
+
const result = await runSessionCancel({ sessionId: "" });
|
|
157
|
+
expect(result.ok).toBe(false);
|
|
158
|
+
if (!result.ok)
|
|
159
|
+
expect(result.error.code).toBe("validation_error");
|
|
160
|
+
});
|
|
161
|
+
it("maps a finished session to conflict without the archive hint", async () => {
|
|
162
|
+
server.use(http.post(`${TEST_BASE_URL}/api/sessions/s1/cancel`, () => HttpResponse.json({ error: "Cannot cancel a session that has already finished" }, { status: 409 })));
|
|
163
|
+
const result = await runSessionCancel({ sessionId: "s1" });
|
|
164
|
+
expect(result.ok).toBe(false);
|
|
165
|
+
if (!result.ok) {
|
|
166
|
+
expect(result.error.code).toBe("conflict");
|
|
167
|
+
expect(result.error.hint).toBeUndefined();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
133
171
|
describe("runSessionExport", () => {
|
|
134
172
|
const sessionId = "44444444-4444-4444-9444-444444444444";
|
|
135
173
|
const defaultPdf = resolve(`${sessionId}.pdf`);
|
|
@@ -20,7 +20,7 @@ declare const _default: import("citty").CommandDef<{
|
|
|
20
20
|
};
|
|
21
21
|
readonly "mobile-config": {
|
|
22
22
|
readonly type: "string";
|
|
23
|
-
readonly description: "JSON object string";
|
|
23
|
+
readonly description: "JSON object string: fallback device + app, used only for mobile cases with no device saved on the plan or the case";
|
|
24
24
|
};
|
|
25
25
|
readonly "execution-mode": {
|
|
26
26
|
readonly type: "string";
|
|
@@ -48,7 +48,10 @@ export default defineCommand({
|
|
|
48
48
|
args: {
|
|
49
49
|
planId: { type: "positional", required: true },
|
|
50
50
|
model: { type: "string" },
|
|
51
|
-
"mobile-config": {
|
|
51
|
+
"mobile-config": {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "JSON object string: fallback device + app, used only for mobile cases with no device saved on the plan or the case",
|
|
54
|
+
},
|
|
52
55
|
"execution-mode": {
|
|
53
56
|
type: "string",
|
|
54
57
|
description: "parallel or sequential (defaults to the plan's stored setting)",
|
package/dist/errors.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { ErrorEnvelope } from "./types.js";
|
|
|
2
2
|
export declare function describeDetails(details: unknown): string | undefined;
|
|
3
3
|
export declare function withDetails(message: string, details: unknown): string;
|
|
4
4
|
export declare function featureHint(message: string, details: unknown): string | undefined;
|
|
5
|
+
export declare const CREDIT_HINT = "The organization is out of credits. Ask an Aximo organization admin to add credits, then retry.";
|
|
6
|
+
export declare function creditHint(message: string): string | undefined;
|
|
5
7
|
export declare const ARCHIVE_FIRST_HINT = "Archive the resource first, then retry: `aximo case archive <caseId>` / `aximo plan archive <planId>` (MCP: aximo_archive_case / aximo_archive_plan).";
|
|
6
8
|
export declare function isArchiveFirstConflict(message: string): boolean;
|
|
7
9
|
export declare function handleApiError(error: unknown): ErrorEnvelope;
|
package/dist/errors.js
CHANGED
|
@@ -85,6 +85,10 @@ export function featureHint(message, details) {
|
|
|
85
85
|
}
|
|
86
86
|
return undefined;
|
|
87
87
|
}
|
|
88
|
+
export const CREDIT_HINT = "The organization is out of credits. Ask an Aximo organization admin to add credits, then retry.";
|
|
89
|
+
export function creditHint(message) {
|
|
90
|
+
return /insufficient credits/i.test(message) ? CREDIT_HINT : undefined;
|
|
91
|
+
}
|
|
88
92
|
const NOT_FOUND_HINT = "Check the ID exists and you have access. List resources: aximo project list / aximo device list / aximo upload list";
|
|
89
93
|
const AUTH_HINT = "Run `aximo configure` to save credentials, or set AXIMO_API_KEY via `export AXIMO_API_KEY=your-key` or `node --env-file=.env`.";
|
|
90
94
|
// 403 is a permission outcome, not a credentials problem, so it no longer routes
|
|
@@ -142,6 +146,7 @@ export function handleApiError(error) {
|
|
|
142
146
|
// when the server says which role is missing. Never the credentials hint: a
|
|
143
147
|
// 403 means the key is valid.
|
|
144
148
|
const hint = featureHint(error.message, details) ??
|
|
149
|
+
creditHint(error.message) ??
|
|
145
150
|
(/admin|owner/i.test(message) ? ADMIN_ROLE_HINT : undefined);
|
|
146
151
|
return formatError("forbidden", message, false, hint);
|
|
147
152
|
}
|
package/dist/errors.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { describeDetails, featureHint, handleApiError, withDetails } from "./errors.js";
|
|
2
|
+
import { CREDIT_HINT, describeDetails, featureHint, handleApiError, withDetails, } from "./errors.js";
|
|
3
3
|
function httpErr(status, message = `status ${status}`, retryAfter) {
|
|
4
4
|
const err = Object.assign(new Error(message), { status });
|
|
5
5
|
if (retryAfter !== undefined)
|
|
@@ -27,6 +27,11 @@ describe("handleApiError", () => {
|
|
|
27
27
|
expect(r.error.code).toBe("forbidden");
|
|
28
28
|
expect(r.error.hint).toContain("admin or owner");
|
|
29
29
|
});
|
|
30
|
+
it("adds the credit hint when the organization is out of credits", () => {
|
|
31
|
+
const r = handleApiError(httpErr(403, "Insufficient credits to start session"));
|
|
32
|
+
expect(r.error.code).toBe("forbidden");
|
|
33
|
+
expect(r.error.hint).toBe(CREDIT_HINT);
|
|
34
|
+
});
|
|
30
35
|
it("maps 404 to not_found with list hint", () => {
|
|
31
36
|
const r = handleApiError(httpErr(404));
|
|
32
37
|
expect(r.error.code).toBe("not_found");
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { runPlanRemoveCase } from "./commands/plan/remove-case.js";
|
|
|
12
12
|
export { runProjectList } from "./commands/project/list.js";
|
|
13
13
|
export { runSessionCreate } from "./commands/session/create.js";
|
|
14
14
|
export { runSessionGet } from "./commands/session/get.js";
|
|
15
|
+
export { runSessionCancel } from "./commands/session/cancel.js";
|
|
15
16
|
export { runSessionSaveAsCase } from "./commands/session/save-as-case.js";
|
|
16
17
|
export { runTriggerCaseGet } from "./commands/trigger/case/get.js";
|
|
17
18
|
export { runTriggerCaseRun } from "./commands/trigger/case/run.js";
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { runPlanRemoveCase } from "./commands/plan/remove-case.js";
|
|
|
11
11
|
export { runProjectList } from "./commands/project/list.js";
|
|
12
12
|
export { runSessionCreate } from "./commands/session/create.js";
|
|
13
13
|
export { runSessionGet } from "./commands/session/get.js";
|
|
14
|
+
export { runSessionCancel } from "./commands/session/cancel.js";
|
|
14
15
|
export { runSessionSaveAsCase } from "./commands/session/save-as-case.js";
|
|
15
16
|
export { runTriggerCaseGet } from "./commands/trigger/case/get.js";
|
|
16
17
|
export { runTriggerCaseRun } from "./commands/trigger/case/run.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -231,6 +231,15 @@ export interface Project {
|
|
|
231
231
|
name?: string | undefined;
|
|
232
232
|
[key: string]: unknown;
|
|
233
233
|
}
|
|
234
|
+
export interface CreditBalance {
|
|
235
|
+
creditUnit: "legacy" | "platform";
|
|
236
|
+
granted: number;
|
|
237
|
+
used: number;
|
|
238
|
+
remaining: number;
|
|
239
|
+
onDemandRemaining: number;
|
|
240
|
+
metered: boolean;
|
|
241
|
+
blocked: boolean;
|
|
242
|
+
}
|
|
234
243
|
export interface CreateCaseInput {
|
|
235
244
|
projectId: string;
|
|
236
245
|
scenario: string;
|
|
@@ -305,6 +314,11 @@ export interface CreatePlanResult {
|
|
|
305
314
|
export interface PlanCaseResult {
|
|
306
315
|
success: boolean;
|
|
307
316
|
}
|
|
317
|
+
export interface CancelSessionResult {
|
|
318
|
+
success: boolean;
|
|
319
|
+
sessionId: string;
|
|
320
|
+
status: "terminated";
|
|
321
|
+
}
|
|
308
322
|
export interface SaveSessionAsCaseInput {
|
|
309
323
|
title?: string | undefined;
|
|
310
324
|
type?: Exclude<CaseType, "multi_device"> | undefined;
|