@duvoai/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.
Files changed (95) hide show
  1. package/README.md +71 -11
  2. package/dist/bin/duvo.js +11726 -6
  3. package/dist/bin/duvo.js.map +7 -0
  4. package/package.json +10 -8
  5. package/dist/api/agent-schemas.d.ts +0 -521
  6. package/dist/api/agent-schemas.js +0 -36
  7. package/dist/api/parse-fields.d.ts +0 -25
  8. package/dist/api/parse-fields.js +0 -86
  9. package/dist/api/read-input.d.ts +0 -6
  10. package/dist/api/read-input.js +0 -28
  11. package/dist/api/run-schemas.d.ts +0 -621
  12. package/dist/api/run-schemas.js +0 -91
  13. package/dist/bin/create-program.d.ts +0 -7
  14. package/dist/bin/create-program.js +0 -34
  15. package/dist/bin/duvo.d.ts +0 -2
  16. package/dist/client/client.d.ts +0 -23
  17. package/dist/client/client.js +0 -96
  18. package/dist/commands/agents/create.d.ts +0 -2
  19. package/dist/commands/agents/create.js +0 -182
  20. package/dist/commands/agents/get.d.ts +0 -2
  21. package/dist/commands/agents/get.js +0 -36
  22. package/dist/commands/agents/index.d.ts +0 -2
  23. package/dist/commands/agents/index.js +0 -11
  24. package/dist/commands/agents/list.d.ts +0 -2
  25. package/dist/commands/agents/list.js +0 -61
  26. package/dist/commands/api-render.d.ts +0 -8
  27. package/dist/commands/api-render.js +0 -72
  28. package/dist/commands/api.d.ts +0 -2
  29. package/dist/commands/api.js +0 -90
  30. package/dist/commands/login.d.ts +0 -2
  31. package/dist/commands/login.js +0 -80
  32. package/dist/commands/logout.d.ts +0 -2
  33. package/dist/commands/logout.js +0 -38
  34. package/dist/commands/profiles/index.d.ts +0 -2
  35. package/dist/commands/profiles/index.js +0 -13
  36. package/dist/commands/profiles/list.d.ts +0 -2
  37. package/dist/commands/profiles/list.js +0 -22
  38. package/dist/commands/profiles/remove.d.ts +0 -2
  39. package/dist/commands/profiles/remove.js +0 -30
  40. package/dist/commands/profiles/rename.d.ts +0 -2
  41. package/dist/commands/profiles/rename.js +0 -20
  42. package/dist/commands/profiles/render-list.d.ts +0 -12
  43. package/dist/commands/profiles/render-list.js +0 -37
  44. package/dist/commands/profiles/use.d.ts +0 -2
  45. package/dist/commands/profiles/use.js +0 -17
  46. package/dist/commands/runs/get.d.ts +0 -2
  47. package/dist/commands/runs/get.js +0 -59
  48. package/dist/commands/runs/index.d.ts +0 -2
  49. package/dist/commands/runs/index.js +0 -17
  50. package/dist/commands/runs/messages.d.ts +0 -2
  51. package/dist/commands/runs/messages.js +0 -80
  52. package/dist/commands/runs/respond.d.ts +0 -2
  53. package/dist/commands/runs/respond.js +0 -105
  54. package/dist/commands/runs/send-message.d.ts +0 -2
  55. package/dist/commands/runs/send-message.js +0 -41
  56. package/dist/commands/runs/start.d.ts +0 -2
  57. package/dist/commands/runs/start.js +0 -54
  58. package/dist/commands/runs/stop.d.ts +0 -2
  59. package/dist/commands/runs/stop.js +0 -41
  60. package/dist/commands/shared/confirm.d.ts +0 -19
  61. package/dist/commands/shared/confirm.js +0 -28
  62. package/dist/commands/shared/pagination.d.ts +0 -8
  63. package/dist/commands/shared/pagination.js +0 -24
  64. package/dist/commands/shared/profile-flag.d.ts +0 -8
  65. package/dist/commands/shared/profile-flag.js +0 -17
  66. package/dist/commands/shared/require-auth.d.ts +0 -12
  67. package/dist/commands/shared/require-auth.js +0 -18
  68. package/dist/commands/whoami.d.ts +0 -2
  69. package/dist/commands/whoami.js +0 -14
  70. package/dist/config/defaults.d.ts +0 -3
  71. package/dist/config/defaults.js +0 -4
  72. package/dist/config/env.d.ts +0 -17
  73. package/dist/config/env.js +0 -13
  74. package/dist/config/paths.d.ts +0 -2
  75. package/dist/config/paths.js +0 -14
  76. package/dist/config/profiles.d.ts +0 -24
  77. package/dist/config/profiles.js +0 -78
  78. package/dist/config/resolve.d.ts +0 -29
  79. package/dist/config/resolve.js +0 -46
  80. package/dist/config/store.d.ts +0 -4
  81. package/dist/config/store.js +0 -94
  82. package/dist/config/types.d.ts +0 -8
  83. package/dist/config/types.js +0 -1
  84. package/dist/errors.d.ts +0 -22
  85. package/dist/errors.js +0 -42
  86. package/dist/output/format.d.ts +0 -11
  87. package/dist/output/format.js +0 -26
  88. package/dist/output/green.d.ts +0 -5
  89. package/dist/output/green.js +0 -18
  90. package/dist/output/json.d.ts +0 -6
  91. package/dist/output/json.js +0 -8
  92. package/dist/output/table.d.ts +0 -16
  93. package/dist/output/table.js +0 -35
  94. package/dist/output/text.d.ts +0 -9
  95. package/dist/output/text.js +0 -13
@@ -1,80 +0,0 @@
1
- import { isCancel, password, text } from "@clack/prompts";
2
- import { DEFAULT_API_BASE_URL } from "../config/defaults.js";
3
- import { addProfile, isValidProfileName } from "../config/profiles.js";
4
- import { readConfig, writeConfig } from "../config/store.js";
5
- import { AuthCliError, CancelCliError, CliError } from "../errors.js";
6
- export function registerLogin(program) {
7
- program
8
- .command("login")
9
- .description("Store an API key under a named profile")
10
- .option("--name <profile>", "profile name to store this key under (prompted if omitted)")
11
- .option("--api-key <key>", "API key (non-interactive; skips the masked prompt)")
12
- .option("--base-url <url>", `API base URL for this profile (default: ${DEFAULT_API_BASE_URL})`)
13
- .action(async (options) => {
14
- await runLogin(options);
15
- });
16
- }
17
- async function runLogin(options) {
18
- const config = await readConfig();
19
- const existingCount = Object.keys(config.profiles).length;
20
- const name = await resolveProfileName(options.name, existingCount);
21
- const apiKey = await resolveApiKey(options.apiKey);
22
- const apiBaseUrl = options.baseUrl ?? DEFAULT_API_BASE_URL;
23
- const next = addProfile(config, name, { apiKey, apiBaseUrl });
24
- await writeConfig(next);
25
- const isNew = !(name in config.profiles);
26
- const verb = isNew ? "Saved" : "Updated";
27
- console.log(`${verb} profile "${name}".`);
28
- if (next.defaultProfile === name && config.defaultProfile !== name) {
29
- console.log(`Set "${name}" as the default profile.`);
30
- }
31
- }
32
- async function resolveProfileName(flag, existingCount) {
33
- if (flag !== undefined) {
34
- if (!isValidProfileName(flag)) {
35
- throw new CliError(`Invalid profile name "${flag}". Use lowercase letters, digits, ` +
36
- `".", "-" or "_", max 40 chars.`);
37
- }
38
- return flag;
39
- }
40
- const placeholder = existingCount === 0 ? "default" : "";
41
- const answer = await text({
42
- message: "Profile name",
43
- placeholder: placeholder || "e.g. acme",
44
- defaultValue: placeholder,
45
- validate: (value) => {
46
- if (!value || value.length === 0) {
47
- return "Profile name is required.";
48
- }
49
- if (!isValidProfileName(value)) {
50
- return 'Use lowercase letters, digits, ".", "-" or "_" only (max 40 chars).';
51
- }
52
- return undefined;
53
- },
54
- });
55
- if (isCancel(answer)) {
56
- throw new CancelCliError("Login cancelled.");
57
- }
58
- return answer;
59
- }
60
- async function resolveApiKey(flag) {
61
- if (flag !== undefined) {
62
- if (flag.length === 0) {
63
- throw new AuthCliError("--api-key cannot be empty.");
64
- }
65
- return flag;
66
- }
67
- const answer = await password({
68
- message: "API key",
69
- validate: (value) => {
70
- if (!value || value.length === 0) {
71
- return "API key is required.";
72
- }
73
- return undefined;
74
- },
75
- });
76
- if (isCancel(answer)) {
77
- throw new CancelCliError("Login cancelled.");
78
- }
79
- return answer;
80
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerLogout(program: Command): void;
@@ -1,38 +0,0 @@
1
- import { removeProfile } from "../config/profiles.js";
2
- import { readConfig, writeConfig } from "../config/store.js";
3
- import { confirmDestructive } from "./shared/confirm.js";
4
- export function registerLogout(program) {
5
- program
6
- .command("logout")
7
- .description("Remove a stored profile (default: the default one)")
8
- .option("--name <profile>", "profile to remove (defaults to the default profile)")
9
- .option("-y, --yes", "skip the confirmation prompt")
10
- .action(async (options) => {
11
- await runLogout(options);
12
- });
13
- }
14
- async function runLogout(options) {
15
- const config = await readConfig();
16
- const target = options.name ?? config.defaultProfile;
17
- if (!target) {
18
- console.log("No profile to remove — you are not logged in.");
19
- return;
20
- }
21
- if (!(target in config.profiles)) {
22
- console.log(`Profile "${target}" does not exist.`);
23
- return;
24
- }
25
- await confirmDestructive({
26
- message: `Remove profile "${target}"?`,
27
- skip: options.yes ?? false,
28
- });
29
- const next = removeProfile(config, target);
30
- await writeConfig(next);
31
- console.log(`Removed profile "${target}".`);
32
- if (next.defaultProfile && next.defaultProfile !== config.defaultProfile) {
33
- console.log(`Default profile is now "${next.defaultProfile}".`);
34
- }
35
- else if (!next.defaultProfile) {
36
- console.log("No profiles remaining.");
37
- }
38
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerProfiles(program: Command): void;
@@ -1,13 +0,0 @@
1
- import { registerList } from "./list.js";
2
- import { registerRemove } from "./remove.js";
3
- import { registerRename } from "./rename.js";
4
- import { registerUse } from "./use.js";
5
- export function registerProfiles(program) {
6
- const profiles = program
7
- .command("profiles")
8
- .description("Manage stored API-key profiles");
9
- registerList(profiles);
10
- registerUse(profiles);
11
- registerRename(profiles);
12
- registerRemove(profiles);
13
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerList(profiles: Command): void;
@@ -1,22 +0,0 @@
1
- import { listProfiles } from "../../config/profiles.js";
2
- import { readConfig } from "../../config/store.js";
3
- import { renderProfileJson, renderProfileTable } from "./render-list.js";
4
- export function registerList(profiles) {
5
- profiles
6
- .command("list", { isDefault: true })
7
- .description("List stored profiles; mark the default one with ▶")
8
- .option("--json", "output raw JSON")
9
- .action(async (options) => {
10
- const config = await readConfig();
11
- const items = listProfiles(config);
12
- if (options.json) {
13
- console.log(renderProfileJson(items));
14
- return;
15
- }
16
- if (items.length === 0) {
17
- console.log("No profiles stored. Run `duvo login` to add one.");
18
- return;
19
- }
20
- console.log(renderProfileTable(items));
21
- });
22
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRemove(profiles: Command): void;
@@ -1,30 +0,0 @@
1
- import { removeProfile } from "../../config/profiles.js";
2
- import { readConfig, writeConfig } from "../../config/store.js";
3
- import { confirmDestructive } from "../shared/confirm.js";
4
- export function registerRemove(profiles) {
5
- profiles
6
- .command("remove <name>")
7
- .description("Remove a stored profile (alias of `duvo logout --name`)")
8
- .option("-y, --yes", "skip the confirmation prompt")
9
- .action(async (name, options) => {
10
- const config = await readConfig();
11
- if (!(name in config.profiles)) {
12
- console.log(`Profile "${name}" does not exist.`);
13
- return;
14
- }
15
- await confirmDestructive({
16
- message: `Remove profile "${name}"?`,
17
- skip: options.yes ?? false,
18
- });
19
- const next = removeProfile(config, name);
20
- await writeConfig(next);
21
- console.log(`Removed profile "${name}".`);
22
- if (next.defaultProfile &&
23
- next.defaultProfile !== config.defaultProfile) {
24
- console.log(`Default profile is now "${next.defaultProfile}".`);
25
- }
26
- else if (!next.defaultProfile) {
27
- console.log("No profiles remaining.");
28
- }
29
- });
30
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRename(profiles: Command): void;
@@ -1,20 +0,0 @@
1
- import { renameProfile } from "../../config/profiles.js";
2
- import { readConfig, writeConfig } from "../../config/store.js";
3
- import { CliError, EXIT_CODE } from "../../errors.js";
4
- export function registerRename(profiles) {
5
- profiles
6
- .command("rename <old> <new>")
7
- .description("Rename a stored profile, preserving its credentials")
8
- .action(async (oldName, newName) => {
9
- const config = await readConfig();
10
- try {
11
- const next = renameProfile(config, oldName, newName);
12
- await writeConfig(next);
13
- console.log(`Renamed profile "${oldName}" to "${newName}".`);
14
- }
15
- catch (error) {
16
- const message = error instanceof Error ? error.message : String(error);
17
- throw new CliError(message, EXIT_CODE.GENERIC);
18
- }
19
- });
20
- }
@@ -1,12 +0,0 @@
1
- import { type ProfileSummary } from "../../config/profiles.js";
2
- /**
3
- * Format the profile list as an aligned text table. Header sits behind
4
- * two spaces so it lines up with the single-char marker + space that
5
- * prefixes each data row.
6
- */
7
- export declare function renderProfileTable(items: readonly ProfileSummary[]): string;
8
- /**
9
- * JSON output mirrors the text view's masked keys — never emit raw
10
- * credentials from a listing command.
11
- */
12
- export declare function renderProfileJson(items: readonly ProfileSummary[]): string;
@@ -1,37 +0,0 @@
1
- import { padRight, truncateApiKey } from "../../output/format.js";
2
- const DEFAULT_MARKER = "▶";
3
- const NON_DEFAULT_MARKER = " ";
4
- function toRow(item) {
5
- return {
6
- marker: item.isDefault ? DEFAULT_MARKER : NON_DEFAULT_MARKER,
7
- name: item.name,
8
- apiKey: truncateApiKey(item.apiKey),
9
- apiBaseUrl: item.apiBaseUrl,
10
- };
11
- }
12
- /**
13
- * Format the profile list as an aligned text table. Header sits behind
14
- * two spaces so it lines up with the single-char marker + space that
15
- * prefixes each data row.
16
- */
17
- export function renderProfileTable(items) {
18
- const rows = items.map(toRow);
19
- const nameWidth = Math.max(4, ...rows.map((r) => r.name.length));
20
- const keyWidth = Math.max(7, ...rows.map((r) => r.apiKey.length));
21
- const lines = [];
22
- lines.push(` ${padRight("NAME", nameWidth)} ${padRight("KEY", keyWidth)} BASE URL`);
23
- for (const row of rows) {
24
- lines.push(`${row.marker} ${padRight(row.name, nameWidth)} ${padRight(row.apiKey, keyWidth)} ${row.apiBaseUrl}`);
25
- }
26
- return lines.join("\n");
27
- }
28
- /**
29
- * JSON output mirrors the text view's masked keys — never emit raw
30
- * credentials from a listing command.
31
- */
32
- export function renderProfileJson(items) {
33
- const masked = items.map((item) => {
34
- return { ...item, apiKey: truncateApiKey(item.apiKey) };
35
- });
36
- return JSON.stringify(masked, null, 2);
37
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerUse(profiles: Command): void;
@@ -1,17 +0,0 @@
1
- import { setDefaultProfile } from "../../config/profiles.js";
2
- import { readConfig, writeConfig } from "../../config/store.js";
3
- import { AuthCliError } from "../../errors.js";
4
- export function registerUse(profiles) {
5
- profiles
6
- .command("use <name>")
7
- .description("Switch the default profile")
8
- .action(async (name) => {
9
- const config = await readConfig();
10
- if (!(name in config.profiles)) {
11
- throw new AuthCliError(`Profile "${name}" does not exist. Run \`duvo profiles list\` to see available profiles.`);
12
- }
13
- const next = setDefaultProfile(config, name);
14
- await writeConfig(next);
15
- console.log(`Default profile is now "${name}".`);
16
- });
17
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRunsGet(runs: Command): void;
@@ -1,59 +0,0 @@
1
- import { getRunResponseSchema } from "../../api/run-schemas.js";
2
- import { httpRequest } from "../../client/client.js";
3
- import { CliError } from "../../errors.js";
4
- import { printJson } from "../../output/json.js";
5
- import { renderKeyValue } from "../../output/text.js";
6
- import { requireAuthenticatedConfig } from "../shared/require-auth.js";
7
- export function registerRunsGet(runs) {
8
- runs
9
- .command("get <id>")
10
- .description("Get an agent run by ID")
11
- .option("--json", "output raw JSON from the API")
12
- .action(async function getAction(id, options) {
13
- const { apiKey, apiBaseUrl } = await requireAuthenticatedConfig(this);
14
- const raw = await httpRequest({
15
- method: "GET",
16
- path: `/v1/runs/${encodeURIComponent(id)}`,
17
- apiKey,
18
- apiBaseUrl,
19
- });
20
- if (options.json) {
21
- printJson(raw);
22
- return;
23
- }
24
- const parsed = getRunResponseSchema.safeParse(raw);
25
- if (!parsed.success) {
26
- throw new CliError(`Unexpected response from the API: ${parsed.error.message}`);
27
- }
28
- const { run, pending_human_request: pendingHumanRequest } = parsed.data;
29
- if (!run) {
30
- throw new CliError(`Run "${id}" not found.`);
31
- }
32
- console.log(renderKeyValue([
33
- { label: "ID", value: run.id },
34
- { label: "Agent", value: run.agent_id },
35
- { label: "Build", value: run.build_id },
36
- { label: "Status", value: run.status },
37
- { label: "Source", value: run.source ?? "" },
38
- { label: "Sandbox", value: run.sandbox_id ?? "" },
39
- { label: "Started", value: run.started_at ?? "" },
40
- { label: "Completed", value: run.completed_at ?? "" },
41
- { label: "Created", value: run.created_at },
42
- { label: "Updated", value: run.updated_at ?? "" },
43
- ]));
44
- if (pendingHumanRequest) {
45
- console.log("");
46
- console.log("Pending human request:");
47
- console.log(renderKeyValue([
48
- { label: "ID", value: pendingHumanRequest.id },
49
- { label: "Type", value: pendingHumanRequest.type },
50
- { label: "Title", value: pendingHumanRequest.title },
51
- {
52
- label: "Description",
53
- value: pendingHumanRequest.description ?? "",
54
- },
55
- { label: "Created", value: pendingHumanRequest.created_at },
56
- ]));
57
- }
58
- });
59
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRuns(program: Command): void;
@@ -1,17 +0,0 @@
1
- import { registerRunsGet } from "./get.js";
2
- import { registerRunsMessages } from "./messages.js";
3
- import { registerRunsRespond } from "./respond.js";
4
- import { registerRunsSendMessage } from "./send-message.js";
5
- import { registerRunsStart } from "./start.js";
6
- import { registerRunsStop } from "./stop.js";
7
- export function registerRuns(program) {
8
- const runs = program
9
- .command("runs")
10
- .description("Manage agent runs in the Duvo public API");
11
- registerRunsStart(runs);
12
- registerRunsGet(runs);
13
- registerRunsMessages(runs);
14
- registerRunsSendMessage(runs);
15
- registerRunsRespond(runs);
16
- registerRunsStop(runs);
17
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRunsMessages(runs: Command): void;
@@ -1,80 +0,0 @@
1
- import { listRunMessagesResponseSchema, } from "../../api/run-schemas.js";
2
- import { httpRequest } from "../../client/client.js";
3
- import { CliError } from "../../errors.js";
4
- import { printJson } from "../../output/json.js";
5
- import { renderTable } from "../../output/table.js";
6
- import { parseLimit, parseOffset } from "../shared/pagination.js";
7
- import { requireAuthenticatedConfig } from "../shared/require-auth.js";
8
- const DEFAULT_LIMIT = 20;
9
- const MIN_LIMIT = 1;
10
- const MAX_LIMIT = 50;
11
- const TEXT_PREVIEW_WIDTH = 60;
12
- const TABLE_COLUMNS = [
13
- { header: "ID", cell: (row) => row.id },
14
- { header: "ROLE", cell: (row) => row.role },
15
- { header: "TYPE", cell: (row) => row.type },
16
- { header: "TIMESTAMP", cell: (row) => row.timestamp },
17
- {
18
- header: "PREVIEW",
19
- cell: previewCell,
20
- maxWidth: TEXT_PREVIEW_WIDTH,
21
- },
22
- ];
23
- function previewCell(row) {
24
- if (row.text_content) {
25
- return row.text_content.replace(/\s+/g, " ").trim();
26
- }
27
- if (row.tool_call) {
28
- return `tool_call ${row.tool_call.name}`;
29
- }
30
- if (row.tool_result) {
31
- return `tool_result ${row.tool_result.tool_call_id}`;
32
- }
33
- return "";
34
- }
35
- export function registerRunsMessages(runs) {
36
- runs
37
- .command("messages <id>")
38
- .description("List messages for an agent run")
39
- .option("--limit <n>", `page size (${MIN_LIMIT}-${MAX_LIMIT})`)
40
- .option("--offset <n>", "number of messages to skip")
41
- .option("--json", "output raw JSON from the API")
42
- .action(async function messagesAction(id, options) {
43
- const limit = parseLimit(options.limit, {
44
- defaultValue: DEFAULT_LIMIT,
45
- min: MIN_LIMIT,
46
- max: MAX_LIMIT,
47
- });
48
- const offset = parseOffset(options.offset);
49
- const { apiKey, apiBaseUrl } = await requireAuthenticatedConfig(this);
50
- const searchParams = new URLSearchParams();
51
- searchParams.append("limit", String(limit));
52
- searchParams.append("offset", String(offset));
53
- const raw = await httpRequest({
54
- method: "GET",
55
- path: `/v1/runs/${encodeURIComponent(id)}/messages`,
56
- apiKey,
57
- apiBaseUrl,
58
- searchParams,
59
- });
60
- if (options.json) {
61
- printJson(raw);
62
- return;
63
- }
64
- const parsed = listRunMessagesResponseSchema.safeParse(raw);
65
- if (!parsed.success) {
66
- throw new CliError(`Unexpected response from the API: ${parsed.error.message}`);
67
- }
68
- const response = parsed.data;
69
- if (response.messages.length === 0) {
70
- console.log("No messages found.");
71
- return;
72
- }
73
- console.log(renderTable(TABLE_COLUMNS, response.messages));
74
- const resolvedOffset = response.offset ?? offset;
75
- const summary = response.total === null || response.total === undefined
76
- ? `Showing ${response.messages.length} · offset ${resolvedOffset}`
77
- : `Showing ${response.messages.length} of ${response.total} · offset ${resolvedOffset}`;
78
- console.log(summary);
79
- });
80
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRunsRespond(runs: Command): void;
@@ -1,105 +0,0 @@
1
- import { getRunResponseSchema, respondHumanRequestResponseSchema, } from "../../api/run-schemas.js";
2
- import { httpRequest } from "../../client/client.js";
3
- import { CliError } from "../../errors.js";
4
- import { green } from "../../output/green.js";
5
- import { printJson } from "../../output/json.js";
6
- import { renderKeyValue } from "../../output/text.js";
7
- import { requireAuthenticatedConfig } from "../shared/require-auth.js";
8
- function collectAnswer(value, previous = []) {
9
- return [...previous, value];
10
- }
11
- function parseAnswers(raw) {
12
- const answers = Object.create(null);
13
- for (const entry of raw) {
14
- const eq = entry.indexOf("=");
15
- if (eq === -1 || eq === 0) {
16
- throw new CliError(`Invalid --answer "${entry}". Expected "question=answer".`);
17
- }
18
- const question = entry.slice(0, eq);
19
- const answer = entry.slice(eq + 1);
20
- if (Object.prototype.hasOwnProperty.call(answers, question)) {
21
- throw new CliError(`Duplicate --answer for question "${question}". Each question may be answered once.`);
22
- }
23
- answers[question] = answer;
24
- }
25
- return answers;
26
- }
27
- function buildBody(options) {
28
- const approveSet = options.approve === true;
29
- const denySet = options.deny === true;
30
- const answers = options.answer ?? [];
31
- if (approveSet && denySet) {
32
- throw new CliError("Pass either --approve or --deny, not both.");
33
- }
34
- if ((approveSet || denySet) && answers.length > 0) {
35
- throw new CliError("Pass either --approve/--deny (for approval-type requests) or --answer (for question-type requests), not both.");
36
- }
37
- if (approveSet) {
38
- return { approved: true };
39
- }
40
- if (denySet) {
41
- return { approved: false };
42
- }
43
- if (answers.length > 0) {
44
- return { answers: parseAnswers(answers) };
45
- }
46
- throw new CliError("Provide --approve, --deny, or at least one --answer question=answer.");
47
- }
48
- async function resolvePendingRequestId(runId, apiKey, apiBaseUrl) {
49
- const raw = await httpRequest({
50
- method: "GET",
51
- path: `/v1/runs/${encodeURIComponent(runId)}`,
52
- apiKey,
53
- apiBaseUrl,
54
- });
55
- const parsed = getRunResponseSchema.safeParse(raw);
56
- if (!parsed.success) {
57
- throw new CliError(`Unexpected response from the API: ${parsed.error.message}`);
58
- }
59
- const pending = parsed.data.pending_human_request;
60
- if (!pending) {
61
- throw new CliError(`Run ${runId} has no pending human request. Pass the request ID explicitly.`);
62
- }
63
- return pending.id;
64
- }
65
- export function registerRunsRespond(runs) {
66
- runs
67
- .command("respond <run-id> [request-id]")
68
- .description("Respond to a human-in-the-loop request on a run (defaults to the run's pending request)")
69
- .option("--approve", "approve an approval-type request")
70
- .option("--deny", "deny an approval-type request")
71
- .option("--answer <question=answer>", "answer a question-type request (repeatable)", collectAnswer, [])
72
- .option("--json", "output raw JSON from the API")
73
- .action(async function respondAction(runId, requestIdArg, options) {
74
- const body = buildBody(options);
75
- const { apiKey, apiBaseUrl } = await requireAuthenticatedConfig(this);
76
- const requestId = requestIdArg ??
77
- (await resolvePendingRequestId(runId, apiKey, apiBaseUrl));
78
- const raw = await httpRequest({
79
- method: "POST",
80
- path: `/v1/runs/${encodeURIComponent(runId)}/human-requests/${encodeURIComponent(requestId)}/respond`,
81
- apiKey,
82
- apiBaseUrl,
83
- body,
84
- });
85
- if (options.json) {
86
- printJson(raw);
87
- return;
88
- }
89
- const parsed = respondHumanRequestResponseSchema.safeParse(raw);
90
- if (!parsed.success) {
91
- throw new CliError(`Unexpected response from the API: ${parsed.error.message}`);
92
- }
93
- const { human_request: humanRequest } = parsed.data;
94
- console.log(green("Response sent!"));
95
- console.log(renderKeyValue([
96
- { label: "ID", value: humanRequest.id },
97
- { label: "Run", value: humanRequest.run_id },
98
- { label: "Title", value: humanRequest.title },
99
- { label: "Description", value: humanRequest.description ?? "" },
100
- { label: "Response", value: humanRequest.response ?? "" },
101
- { label: "Responded", value: humanRequest.responded_at ?? "" },
102
- { label: "Created", value: humanRequest.created_at },
103
- ]));
104
- });
105
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRunsSendMessage(runs: Command): void;
@@ -1,41 +0,0 @@
1
- import { postRunMessageResponseSchema } from "../../api/run-schemas.js";
2
- import { httpRequest } from "../../client/client.js";
3
- import { CliError } from "../../errors.js";
4
- import { green } from "../../output/green.js";
5
- import { printJson } from "../../output/json.js";
6
- import { renderKeyValue } from "../../output/text.js";
7
- import { requireAuthenticatedConfig } from "../shared/require-auth.js";
8
- export function registerRunsSendMessage(runs) {
9
- runs
10
- .command("send-message <id>")
11
- .description("Post a message to an agent run")
12
- .requiredOption("--message <text>", "the message content to send")
13
- .option("--json", "output raw JSON from the API")
14
- .action(async function sendMessageAction(id, options) {
15
- const { apiKey, apiBaseUrl } = await requireAuthenticatedConfig(this);
16
- const raw = await httpRequest({
17
- method: "POST",
18
- path: `/v1/runs/${encodeURIComponent(id)}/messages`,
19
- apiKey,
20
- apiBaseUrl,
21
- body: { message: options.message },
22
- });
23
- if (options.json) {
24
- printJson(raw);
25
- return;
26
- }
27
- const parsed = postRunMessageResponseSchema.safeParse(raw);
28
- if (!parsed.success) {
29
- throw new CliError(`Unexpected response from the API: ${parsed.error.message}`);
30
- }
31
- const { message } = parsed.data;
32
- console.log(green("Message sent!"));
33
- console.log(renderKeyValue([
34
- { label: "ID", value: message.id },
35
- { label: "Role", value: message.role },
36
- { label: "Type", value: message.type },
37
- { label: "Timestamp", value: message.timestamp },
38
- { label: "Content", value: message.text_content },
39
- ]));
40
- });
41
- }
@@ -1,2 +0,0 @@
1
- import { type Command } from "commander";
2
- export declare function registerRunsStart(runs: Command): void;