@autifyhq/aximo-cli 0.1.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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +379 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +21 -0
  5. package/dist/client.d.ts +18 -0
  6. package/dist/client.js +90 -0
  7. package/dist/client.test.d.ts +1 -0
  8. package/dist/client.test.js +127 -0
  9. package/dist/commands/configure.d.ts +24 -0
  10. package/dist/commands/configure.js +103 -0
  11. package/dist/commands/configure.test.d.ts +1 -0
  12. package/dist/commands/configure.test.js +96 -0
  13. package/dist/commands/device/connect.d.ts +2 -0
  14. package/dist/commands/device/connect.js +84 -0
  15. package/dist/commands/device/doctor.d.ts +34 -0
  16. package/dist/commands/device/doctor.js +421 -0
  17. package/dist/commands/device/doctor.test.d.ts +1 -0
  18. package/dist/commands/device/doctor.test.js +89 -0
  19. package/dist/commands/device/index.d.ts +2 -0
  20. package/dist/commands/device/index.js +16 -0
  21. package/dist/commands/device/list.d.ts +20 -0
  22. package/dist/commands/device/list.js +40 -0
  23. package/dist/commands/device/list.test.d.ts +1 -0
  24. package/dist/commands/device/list.test.js +38 -0
  25. package/dist/commands/device/logs.d.ts +34 -0
  26. package/dist/commands/device/logs.js +160 -0
  27. package/dist/commands/device/logs.test.d.ts +1 -0
  28. package/dist/commands/device/logs.test.js +70 -0
  29. package/dist/commands/device/paths.d.ts +12 -0
  30. package/dist/commands/device/paths.js +40 -0
  31. package/dist/commands/device/ping.d.ts +21 -0
  32. package/dist/commands/device/ping.js +66 -0
  33. package/dist/commands/device/ping.test.d.ts +1 -0
  34. package/dist/commands/device/ping.test.js +68 -0
  35. package/dist/commands/device/register.d.ts +23 -0
  36. package/dist/commands/device/register.js +83 -0
  37. package/dist/commands/device/reset.d.ts +30 -0
  38. package/dist/commands/device/reset.js +67 -0
  39. package/dist/commands/device/reset.test.d.ts +1 -0
  40. package/dist/commands/device/reset.test.js +69 -0
  41. package/dist/commands/device/setup.d.ts +25 -0
  42. package/dist/commands/device/setup.js +105 -0
  43. package/dist/commands/device/setup.test.d.ts +1 -0
  44. package/dist/commands/device/setup.test.js +64 -0
  45. package/dist/commands/device/status.d.ts +16 -0
  46. package/dist/commands/device/status.js +20 -0
  47. package/dist/commands/device/unregister.d.ts +14 -0
  48. package/dist/commands/device/unregister.js +60 -0
  49. package/dist/commands/health/check.d.ts +5 -0
  50. package/dist/commands/health/check.js +20 -0
  51. package/dist/commands/health/check.test.d.ts +1 -0
  52. package/dist/commands/health/check.test.js +26 -0
  53. package/dist/commands/health/index.d.ts +2 -0
  54. package/dist/commands/health/index.js +7 -0
  55. package/dist/commands/project/index.d.ts +2 -0
  56. package/dist/commands/project/index.js +7 -0
  57. package/dist/commands/project/list.d.ts +7 -0
  58. package/dist/commands/project/list.js +20 -0
  59. package/dist/commands/project/list.test.d.ts +1 -0
  60. package/dist/commands/project/list.test.js +28 -0
  61. package/dist/commands/schema.d.ts +27 -0
  62. package/dist/commands/schema.js +146 -0
  63. package/dist/commands/schema.test.d.ts +1 -0
  64. package/dist/commands/schema.test.js +38 -0
  65. package/dist/commands/session/create.d.ts +78 -0
  66. package/dist/commands/session/create.js +89 -0
  67. package/dist/commands/session/get.d.ts +11 -0
  68. package/dist/commands/session/get.js +30 -0
  69. package/dist/commands/session/index.d.ts +2 -0
  70. package/dist/commands/session/index.js +8 -0
  71. package/dist/commands/session/session.test.d.ts +1 -0
  72. package/dist/commands/session/session.test.js +128 -0
  73. package/dist/commands/trigger/case/get.d.ts +14 -0
  74. package/dist/commands/trigger/case/get.js +33 -0
  75. package/dist/commands/trigger/case/index.d.ts +2 -0
  76. package/dist/commands/trigger/case/index.js +8 -0
  77. package/dist/commands/trigger/case/run.d.ts +58 -0
  78. package/dist/commands/trigger/case/run.js +81 -0
  79. package/dist/commands/trigger/index.d.ts +2 -0
  80. package/dist/commands/trigger/index.js +8 -0
  81. package/dist/commands/trigger/plan/get.d.ts +14 -0
  82. package/dist/commands/trigger/plan/get.js +33 -0
  83. package/dist/commands/trigger/plan/index.d.ts +2 -0
  84. package/dist/commands/trigger/plan/index.js +8 -0
  85. package/dist/commands/trigger/plan/run.d.ts +33 -0
  86. package/dist/commands/trigger/plan/run.js +63 -0
  87. package/dist/commands/trigger/trigger.test.d.ts +1 -0
  88. package/dist/commands/trigger/trigger.test.js +107 -0
  89. package/dist/commands/upload/create.d.ts +32 -0
  90. package/dist/commands/upload/create.js +65 -0
  91. package/dist/commands/upload/get.d.ts +10 -0
  92. package/dist/commands/upload/get.js +30 -0
  93. package/dist/commands/upload/index.d.ts +2 -0
  94. package/dist/commands/upload/index.js +11 -0
  95. package/dist/commands/upload/list.d.ts +15 -0
  96. package/dist/commands/upload/list.js +31 -0
  97. package/dist/commands/upload/put.d.ts +24 -0
  98. package/dist/commands/upload/put.js +62 -0
  99. package/dist/commands/upload/submit.d.ts +19 -0
  100. package/dist/commands/upload/submit.js +36 -0
  101. package/dist/commands/upload/upload.test.d.ts +1 -0
  102. package/dist/commands/upload/upload.test.js +133 -0
  103. package/dist/config.d.ts +7 -0
  104. package/dist/config.js +36 -0
  105. package/dist/desktop-server/agent-sync.d.ts +19 -0
  106. package/dist/desktop-server/agent-sync.js +141 -0
  107. package/dist/desktop-server/agent-sync.test.d.ts +1 -0
  108. package/dist/desktop-server/agent-sync.test.js +130 -0
  109. package/dist/desktop-server/setup.d.ts +19 -0
  110. package/dist/desktop-server/setup.js +200 -0
  111. package/dist/errors.d.ts +2 -0
  112. package/dist/errors.js +71 -0
  113. package/dist/errors.test.d.ts +1 -0
  114. package/dist/errors.test.js +88 -0
  115. package/dist/index.d.ts +19 -0
  116. package/dist/index.js +17 -0
  117. package/dist/mutate.d.ts +13 -0
  118. package/dist/mutate.js +25 -0
  119. package/dist/mutate.test.d.ts +1 -0
  120. package/dist/mutate.test.js +49 -0
  121. package/dist/output.d.ts +5 -0
  122. package/dist/output.js +23 -0
  123. package/dist/output.test.d.ts +1 -0
  124. package/dist/output.test.js +42 -0
  125. package/dist/test-setup.d.ts +1 -0
  126. package/dist/test-setup.js +13 -0
  127. package/dist/test-utils.d.ts +4 -0
  128. package/dist/test-utils.js +5 -0
  129. package/dist/types.d.ts +121 -0
  130. package/dist/types.js +2 -0
  131. package/dist/validate.d.ts +14 -0
  132. package/dist/validate.js +103 -0
  133. package/dist/validate.test.d.ts +1 -0
  134. package/dist/validate.test.js +96 -0
  135. package/package.json +72 -0
@@ -0,0 +1,24 @@
1
+ import type { Envelope } from "../types.js";
2
+ export interface ConfigureResult {
3
+ path: string;
4
+ baseUrl: string;
5
+ apiKeyConfigured: boolean;
6
+ }
7
+ export interface RunConfigureOptions {
8
+ baseUrl?: string;
9
+ apiKey?: string;
10
+ promptBaseUrl?: (currentOrDefault: string) => Promise<string>;
11
+ promptApiKey?: (currentRedacted: string | null) => Promise<string>;
12
+ }
13
+ export declare function runConfigure(options?: RunConfigureOptions): Promise<Envelope<ConfigureResult>>;
14
+ declare const _default: import("citty").CommandDef<{
15
+ readonly "base-url": {
16
+ readonly type: "string";
17
+ readonly description: "Set base URL without prompting";
18
+ };
19
+ readonly "api-key": {
20
+ readonly type: "string";
21
+ readonly description: "Set API key without prompting";
22
+ };
23
+ }>;
24
+ export default _default;
@@ -0,0 +1,103 @@
1
+ import { defineCommand } from "citty";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { DEFAULT_BASE_URL } from "../client.js";
4
+ import { getConfigPath, loadConfig, saveConfig } from "../config.js";
5
+ import { formatError, formatSuccess, printResult } from "../output.js";
6
+ function redactKey(value) {
7
+ if (value.length <= 8)
8
+ return "***";
9
+ return `${value.slice(0, 4)}…${value.slice(-4)}`;
10
+ }
11
+ async function resolveBaseUrl(explicit, fallback, prompt) {
12
+ if (explicit !== undefined) {
13
+ const trimmed = explicit.trim();
14
+ return trimmed === "" ? fallback : trimmed;
15
+ }
16
+ if (prompt) {
17
+ const answer = (await prompt(fallback)).trim();
18
+ return answer === "" ? fallback : answer;
19
+ }
20
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
21
+ try {
22
+ const answer = (await rl.question(`Aximo base URL [${fallback}]: `)).trim();
23
+ return answer === "" ? fallback : answer;
24
+ }
25
+ finally {
26
+ rl.close();
27
+ }
28
+ }
29
+ async function resolveApiKey(explicit, existing, prompt) {
30
+ const hasExisting = typeof existing === "string" && existing.length > 0;
31
+ const hint = hasExisting ? redactKey(existing) : null;
32
+ if (explicit !== undefined) {
33
+ const trimmed = explicit.trim();
34
+ if (trimmed === "") {
35
+ if (!hasExisting)
36
+ return { error: "API key cannot be empty" };
37
+ return { apiKey: existing };
38
+ }
39
+ return { apiKey: trimmed };
40
+ }
41
+ const answer = prompt
42
+ ? (await prompt(hint)).trim()
43
+ : await askTTY(`API key [${hint ?? "required"}]: `);
44
+ if (answer === "") {
45
+ if (!hasExisting)
46
+ return { error: "API key is required" };
47
+ return { apiKey: existing };
48
+ }
49
+ return { apiKey: answer };
50
+ }
51
+ async function askTTY(prompt) {
52
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
53
+ try {
54
+ return (await rl.question(prompt)).trim();
55
+ }
56
+ finally {
57
+ rl.close();
58
+ }
59
+ }
60
+ export async function runConfigure(options = {}) {
61
+ const existing = loadConfig();
62
+ const baseFallback = existing.baseUrl ?? DEFAULT_BASE_URL;
63
+ const needsPrompt = options.baseUrl === undefined || options.apiKey === undefined;
64
+ const hasPromptFns = Boolean(options.promptBaseUrl || options.promptApiKey);
65
+ if (needsPrompt && !hasPromptFns && !process.stdin.isTTY) {
66
+ return formatError("validation_error", "Non-interactive shell: pass --base-url and --api-key", false);
67
+ }
68
+ const baseUrl = await resolveBaseUrl(options.baseUrl, baseFallback, options.promptBaseUrl);
69
+ const apiKeyResult = await resolveApiKey(options.apiKey, existing.apiKey, options.promptApiKey);
70
+ if ("error" in apiKeyResult) {
71
+ return formatError("validation_error", apiKeyResult.error, false);
72
+ }
73
+ const normalizedBase = baseUrl.replace(/\/+$/, "");
74
+ const config = { baseUrl: normalizedBase, apiKey: apiKeyResult.apiKey };
75
+ saveConfig(config);
76
+ return formatSuccess({
77
+ path: getConfigPath(),
78
+ baseUrl: normalizedBase,
79
+ apiKeyConfigured: true,
80
+ });
81
+ }
82
+ export default defineCommand({
83
+ meta: {
84
+ name: "configure",
85
+ description: "Store API key and base URL in $AXIMO_HOME/config.json (mode 0600)",
86
+ },
87
+ args: {
88
+ "base-url": { type: "string", description: "Set base URL without prompting" },
89
+ "api-key": { type: "string", description: "Set API key without prompting" },
90
+ },
91
+ async run({ args }) {
92
+ const options = {};
93
+ if (args["base-url"] !== undefined)
94
+ options.baseUrl = args["base-url"];
95
+ if (args["api-key"] !== undefined)
96
+ options.apiKey = args["api-key"];
97
+ const result = await runConfigure(options);
98
+ printResult(result);
99
+ if (result.ok) {
100
+ console.error(`\nConfig saved to ${result.data.path} (mode 0600). Keep this file secret — it contains your API key in plain text.`);
101
+ }
102
+ },
103
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
5
+ import { runConfigure } from "./configure.js";
6
+ let tempHome;
7
+ beforeAll(() => {
8
+ tempHome = mkdtempSync(join(tmpdir(), "aximo-configure-"));
9
+ process.env["AXIMO_HOME"] = tempHome;
10
+ });
11
+ afterAll(() => {
12
+ delete process.env["AXIMO_HOME"];
13
+ rmSync(tempHome, { recursive: true, force: true });
14
+ });
15
+ beforeEach(() => {
16
+ rmSync(tempHome, { recursive: true, force: true });
17
+ mkdirSync(tempHome, { recursive: true });
18
+ });
19
+ function readSavedConfig() {
20
+ return JSON.parse(readFileSync(join(tempHome, "config.json"), "utf-8"));
21
+ }
22
+ describe("runConfigure", () => {
23
+ it("writes both base URL and API key from flags", async () => {
24
+ const result = await runConfigure({
25
+ baseUrl: "https://custom.example.com/",
26
+ apiKey: "sk-test-123",
27
+ });
28
+ expect(result.ok).toBe(true);
29
+ if (!result.ok)
30
+ return;
31
+ expect(result.data.baseUrl).toBe("https://custom.example.com");
32
+ expect(result.data.apiKeyConfigured).toBe(true);
33
+ const saved = readSavedConfig();
34
+ expect(saved.apiKey).toBe("sk-test-123");
35
+ expect(saved.baseUrl).toBe("https://custom.example.com");
36
+ });
37
+ it("defaults empty URL prompt to the Aximo default when no prior config", async () => {
38
+ const result = await runConfigure({
39
+ promptBaseUrl: async () => "",
40
+ promptApiKey: async () => "sk-new",
41
+ });
42
+ expect(result.ok).toBe(true);
43
+ if (!result.ok)
44
+ return;
45
+ expect(result.data.baseUrl).toBe("https://aximo.autify.com");
46
+ expect(readSavedConfig().baseUrl).toBe("https://aximo.autify.com");
47
+ });
48
+ it("keeps the saved base URL when prompt is left empty", async () => {
49
+ writeFileSync(join(tempHome, "config.json"), JSON.stringify({ baseUrl: "https://saved.example.com", apiKey: "sk-old" }));
50
+ const result = await runConfigure({
51
+ promptBaseUrl: async () => "",
52
+ promptApiKey: async () => "",
53
+ });
54
+ expect(result.ok).toBe(true);
55
+ if (!result.ok)
56
+ return;
57
+ expect(result.data.baseUrl).toBe("https://saved.example.com");
58
+ const saved = readSavedConfig();
59
+ expect(saved.baseUrl).toBe("https://saved.example.com");
60
+ expect(saved.apiKey).toBe("sk-old");
61
+ });
62
+ it("fails when API key is empty and nothing is saved", async () => {
63
+ const result = await runConfigure({
64
+ promptBaseUrl: async () => "https://x.example.com",
65
+ promptApiKey: async () => "",
66
+ });
67
+ expect(result.ok).toBe(false);
68
+ if (!result.ok)
69
+ expect(result.error.code).toBe("validation_error");
70
+ expect(existsSync(join(tempHome, "config.json"))).toBe(false);
71
+ });
72
+ it("passes a redacted existing key hint to the prompt", async () => {
73
+ writeFileSync(join(tempHome, "config.json"), JSON.stringify({ baseUrl: "https://saved.example.com", apiKey: "sk-secretvalue-xyz" }));
74
+ let seenHint = "unset";
75
+ await runConfigure({
76
+ baseUrl: "https://saved.example.com",
77
+ promptApiKey: async (hint) => {
78
+ seenHint = hint;
79
+ return "";
80
+ },
81
+ });
82
+ expect(seenHint).not.toBeNull();
83
+ expect(seenHint).toContain("sk-s");
84
+ expect(seenHint).not.toContain("secretvalue");
85
+ });
86
+ it("normalizes trailing slashes on the base URL", async () => {
87
+ const result = await runConfigure({
88
+ baseUrl: "https://example.com////",
89
+ apiKey: "sk-1",
90
+ });
91
+ expect(result.ok).toBe(true);
92
+ if (!result.ok)
93
+ return;
94
+ expect(result.data.baseUrl).toBe("https://example.com");
95
+ });
96
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("citty").CommandDef<{}>;
2
+ export default _default;
@@ -0,0 +1,84 @@
1
+ import { defineCommand } from "citty";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { getBaseUrl } from "../../client.js";
4
+ import { formatError, printResult } from "../../output.js";
5
+ import { getDeviceConfigPath } from "./paths.js";
6
+ function loadConfig() {
7
+ const configPath = getDeviceConfigPath();
8
+ if (!existsSync(configPath))
9
+ return null;
10
+ try {
11
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
12
+ if (!parsed || typeof parsed !== "object")
13
+ return null;
14
+ const raw = parsed;
15
+ if (typeof raw.deviceId !== "string" ||
16
+ typeof raw.deviceToken !== "string" ||
17
+ typeof raw.name !== "string") {
18
+ return null;
19
+ }
20
+ return { deviceId: raw.deviceId, deviceToken: raw.deviceToken, name: raw.name };
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function getRelayUrl() {
27
+ const base = getBaseUrl();
28
+ return `${base.replace(/^http/, "ws")}/devices/tunnel`;
29
+ }
30
+ async function connectToRelay(config) {
31
+ const { ensurePythonReady, startDesktopRemote } = await import("../../desktop-server/setup.js");
32
+ console.log("Preparing Python environment...");
33
+ const { pythonPath, scriptPath, agentVersion } = await ensurePythonReady();
34
+ console.log(`Python ready: ${pythonPath}`);
35
+ console.log(`Agent version: ${agentVersion}`);
36
+ const relayUrl = getRelayUrl();
37
+ console.log(`Starting desktop agent, relay: ${relayUrl}`);
38
+ let remoteProcess = startDesktopRemote(pythonPath, scriptPath, {
39
+ relayUrl,
40
+ deviceToken: config.deviceToken,
41
+ deviceName: config.name,
42
+ });
43
+ let shuttingDown = false;
44
+ const cleanup = () => {
45
+ shuttingDown = true;
46
+ if (remoteProcess) {
47
+ remoteProcess.kill();
48
+ remoteProcess = null;
49
+ }
50
+ };
51
+ process.on("SIGINT", () => {
52
+ console.log("\nDisconnecting...");
53
+ cleanup();
54
+ process.exit(0);
55
+ });
56
+ process.on("SIGTERM", () => {
57
+ cleanup();
58
+ process.exit(0);
59
+ });
60
+ remoteProcess.on("exit", (code) => {
61
+ if (!shuttingDown) {
62
+ console.error(`Desktop agent exited with code ${code}`);
63
+ process.exit(code ?? 1);
64
+ }
65
+ });
66
+ await new Promise(() => { });
67
+ }
68
+ export default defineCommand({
69
+ meta: { name: "connect", description: "Connect this device to Aximo for test execution" },
70
+ args: {},
71
+ async run() {
72
+ const config = loadConfig();
73
+ if (!config) {
74
+ printResult(formatError("validation_error", 'No device registered. Run: aximo device register --name "My PC"', false));
75
+ return;
76
+ }
77
+ try {
78
+ await connectToRelay(config);
79
+ }
80
+ catch (error) {
81
+ printResult(formatError("api_error", error instanceof Error ? error.message : String(error), true));
82
+ }
83
+ },
84
+ });
@@ -0,0 +1,34 @@
1
+ import { type ClientDeps } from "../../client.js";
2
+ import type { Envelope } from "../../types.js";
3
+ export type CheckStatus = "pass" | "warn" | "fail" | "info";
4
+ export interface Check {
5
+ name: string;
6
+ status: CheckStatus;
7
+ message: string;
8
+ hint?: string;
9
+ }
10
+ export interface DoctorReport {
11
+ checks: Check[];
12
+ summary: {
13
+ pass: number;
14
+ warn: number;
15
+ fail: number;
16
+ info: number;
17
+ };
18
+ }
19
+ export interface RunDoctorOptions {
20
+ skipRelay?: boolean;
21
+ skipApi?: boolean;
22
+ }
23
+ export declare function runDeviceDoctor(options?: RunDoctorOptions, deps?: ClientDeps): Promise<Envelope<DoctorReport>>;
24
+ declare const _default: import("citty").CommandDef<{
25
+ readonly "skip-relay": {
26
+ readonly type: "boolean";
27
+ readonly description: "Skip WebSocket reachability check";
28
+ };
29
+ readonly "skip-api": {
30
+ readonly type: "boolean";
31
+ readonly description: "Skip Aximo API calls (offline check only)";
32
+ };
33
+ }>;
34
+ export default _default;