@dharta/cli 0.6.77 → 0.6.78

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 (293) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +215 -11
  3. package/dist/agent.js +3 -2
  4. package/dist/agent.js.map +1 -1
  5. package/dist/api-error.js +6 -6
  6. package/dist/api-error.js.map +1 -1
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.js +860 -149
  9. package/dist/cli.js.map +1 -1
  10. package/dist/command-tree.d.ts +3 -0
  11. package/dist/command-tree.js +10 -0
  12. package/dist/command-tree.js.map +1 -0
  13. package/dist/commands/agents.d.ts +54 -17
  14. package/dist/commands/agents.js +246 -136
  15. package/dist/commands/agents.js.map +1 -1
  16. package/dist/commands/analytics.js +6 -5
  17. package/dist/commands/analytics.js.map +1 -1
  18. package/dist/commands/api.js +71 -86
  19. package/dist/commands/api.js.map +1 -1
  20. package/dist/commands/appearance.js +5 -6
  21. package/dist/commands/appearance.js.map +1 -1
  22. package/dist/commands/artifact.js +7 -6
  23. package/dist/commands/artifact.js.map +1 -1
  24. package/dist/commands/audit-log.js +2 -1
  25. package/dist/commands/audit-log.js.map +1 -1
  26. package/dist/commands/auth.js +26 -6
  27. package/dist/commands/auth.js.map +1 -1
  28. package/dist/commands/caps.js +11 -15
  29. package/dist/commands/caps.js.map +1 -1
  30. package/dist/commands/deploy.js +25 -10
  31. package/dist/commands/deploy.js.map +1 -1
  32. package/dist/commands/dev.d.ts +10 -1
  33. package/dist/commands/dev.js +56 -25
  34. package/dist/commands/dev.js.map +1 -1
  35. package/dist/commands/doctor.d.ts +2 -0
  36. package/dist/commands/doctor.js +60 -0
  37. package/dist/commands/doctor.js.map +1 -1
  38. package/dist/commands/embed-keys.js +4 -13
  39. package/dist/commands/embed-keys.js.map +1 -1
  40. package/dist/commands/env.js +4 -7
  41. package/dist/commands/env.js.map +1 -1
  42. package/dist/commands/files.js +3 -4
  43. package/dist/commands/files.js.map +1 -1
  44. package/dist/commands/fleet.d.ts +129 -0
  45. package/dist/commands/fleet.js +408 -0
  46. package/dist/commands/fleet.js.map +1 -0
  47. package/dist/commands/genui.d.ts +2 -0
  48. package/dist/commands/genui.js +53 -0
  49. package/dist/commands/genui.js.map +1 -0
  50. package/dist/commands/git-credential.js +2 -3
  51. package/dist/commands/git-credential.js.map +1 -1
  52. package/dist/commands/kartas.d.ts +9 -0
  53. package/dist/commands/kartas.js +33 -11
  54. package/dist/commands/kartas.js.map +1 -1
  55. package/dist/commands/keys.d.ts +17 -18
  56. package/dist/commands/keys.js +108 -19
  57. package/dist/commands/keys.js.map +1 -1
  58. package/dist/commands/login.js +2 -1
  59. package/dist/commands/login.js.map +1 -1
  60. package/dist/commands/members.d.ts +10 -0
  61. package/dist/commands/members.js +20 -6
  62. package/dist/commands/members.js.map +1 -1
  63. package/dist/commands/model-connections.d.ts +44 -0
  64. package/dist/commands/model-connections.js +435 -0
  65. package/dist/commands/model-connections.js.map +1 -0
  66. package/dist/commands/open.d.ts +2 -2
  67. package/dist/commands/open.js +4 -3
  68. package/dist/commands/open.js.map +1 -1
  69. package/dist/commands/org.d.ts +9 -0
  70. package/dist/commands/org.js +42 -0
  71. package/dist/commands/org.js.map +1 -0
  72. package/dist/commands/organization.d.ts +16 -0
  73. package/dist/commands/organization.js +118 -0
  74. package/dist/commands/organization.js.map +1 -0
  75. package/dist/commands/parity.js +1 -3
  76. package/dist/commands/parity.js.map +1 -1
  77. package/dist/commands/repos.d.ts +4 -0
  78. package/dist/commands/repos.js +17 -0
  79. package/dist/commands/repos.js.map +1 -0
  80. package/dist/commands/rollback.js +2 -3
  81. package/dist/commands/rollback.js.map +1 -1
  82. package/dist/commands/schedules.js +26 -21
  83. package/dist/commands/schedules.js.map +1 -1
  84. package/dist/commands/scopes.js +8 -1
  85. package/dist/commands/scopes.js.map +1 -1
  86. package/dist/commands/sessions.js +9 -3
  87. package/dist/commands/sessions.js.map +1 -1
  88. package/dist/commands/setup.js +5 -4
  89. package/dist/commands/setup.js.map +1 -1
  90. package/dist/commands/update.d.ts +1 -0
  91. package/dist/commands/update.js +24 -11
  92. package/dist/commands/update.js.map +1 -1
  93. package/dist/commands/webhooks.d.ts +4 -1
  94. package/dist/commands/webhooks.js +21 -16
  95. package/dist/commands/webhooks.js.map +1 -1
  96. package/dist/completions.js +24 -13
  97. package/dist/completions.js.map +1 -1
  98. package/dist/config.d.ts +12 -0
  99. package/dist/config.js +121 -7
  100. package/dist/config.js.map +1 -1
  101. package/dist/context.d.ts +2 -0
  102. package/dist/context.js +23 -7
  103. package/dist/context.js.map +1 -1
  104. package/dist/docs/cli-reference.d.ts +2 -0
  105. package/dist/docs/cli-reference.js +191 -0
  106. package/dist/docs/cli-reference.js.map +1 -0
  107. package/dist/exit-codes.d.ts +25 -0
  108. package/dist/exit-codes.js +107 -0
  109. package/dist/exit-codes.js.map +1 -0
  110. package/dist/genui.d.ts +178 -0
  111. package/dist/genui.js +1130 -0
  112. package/dist/genui.js.map +1 -0
  113. package/dist/harnesses.js +2 -1
  114. package/dist/harnesses.js.map +1 -1
  115. package/dist/jq.d.ts +4 -0
  116. package/dist/jq.js +91 -0
  117. package/dist/jq.js.map +1 -0
  118. package/dist/login-offer.js +6 -2
  119. package/dist/login-offer.js.map +1 -1
  120. package/dist/manifest.d.ts +12 -0
  121. package/dist/manifest.js +126 -0
  122. package/dist/manifest.js.map +1 -1
  123. package/dist/model-auth.d.ts +29 -0
  124. package/dist/model-auth.js +117 -0
  125. package/dist/model-auth.js.map +1 -0
  126. package/dist/model-settings.d.ts +23 -0
  127. package/dist/model-settings.js +186 -0
  128. package/dist/model-settings.js.map +1 -0
  129. package/dist/money.js +2 -1
  130. package/dist/money.js.map +1 -1
  131. package/dist/output.d.ts +6 -0
  132. package/dist/output.js +54 -0
  133. package/dist/output.js.map +1 -1
  134. package/dist/runtime.d.ts +1 -1
  135. package/dist/runtime.js +1 -1
  136. package/dist/scaffold.js +2 -1
  137. package/dist/scaffold.js.map +1 -1
  138. package/dist/validators.js +17 -11
  139. package/dist/validators.js.map +1 -1
  140. package/dist/version-check.d.ts +1 -1
  141. package/dist/version-check.js +1 -1
  142. package/dist/version-check.js.map +1 -1
  143. package/node_modules/@dharta/contracts/LICENSE +21 -0
  144. package/node_modules/@dharta/contracts/README.md +45 -0
  145. package/node_modules/@dharta/contracts/artifact-runtime.json +750 -0
  146. package/node_modules/@dharta/contracts/dist/activity.d.ts +62 -0
  147. package/node_modules/@dharta/contracts/dist/activity.d.ts.map +1 -0
  148. package/node_modules/@dharta/contracts/dist/activity.js +178 -0
  149. package/node_modules/@dharta/contracts/dist/activity.js.map +1 -0
  150. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.d.ts +20 -0
  151. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.d.ts.map +1 -0
  152. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.js +53 -0
  153. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.js.map +1 -0
  154. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.d.ts +37 -0
  155. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.d.ts.map +1 -0
  156. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.js +5107 -0
  157. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.js.map +1 -0
  158. package/node_modules/@dharta/contracts/dist/artifact-runtime.d.ts +154 -0
  159. package/node_modules/@dharta/contracts/dist/artifact-runtime.d.ts.map +1 -0
  160. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.d.ts +1670 -0
  161. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.d.ts.map +1 -0
  162. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.js +1903 -0
  163. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.js.map +1 -0
  164. package/node_modules/@dharta/contracts/dist/artifact-runtime.js +377 -0
  165. package/node_modules/@dharta/contracts/dist/artifact-runtime.js.map +1 -0
  166. package/node_modules/@dharta/contracts/dist/index.d.ts +6 -0
  167. package/node_modules/@dharta/contracts/dist/index.d.ts.map +1 -0
  168. package/node_modules/@dharta/contracts/dist/index.js +6 -0
  169. package/node_modules/@dharta/contracts/dist/index.js.map +1 -0
  170. package/node_modules/@dharta/contracts/dist/model-auth.d.ts +31 -0
  171. package/node_modules/@dharta/contracts/dist/model-auth.d.ts.map +1 -0
  172. package/node_modules/@dharta/contracts/dist/model-auth.js +45 -0
  173. package/node_modules/@dharta/contracts/dist/model-auth.js.map +1 -0
  174. package/node_modules/@dharta/contracts/dist/tasks.d.ts +44 -0
  175. package/node_modules/@dharta/contracts/dist/tasks.d.ts.map +1 -0
  176. package/node_modules/@dharta/contracts/dist/tasks.js +127 -0
  177. package/node_modules/@dharta/contracts/dist/tasks.js.map +1 -0
  178. package/node_modules/@dharta/contracts/dist/wire-events.d.ts +71 -0
  179. package/node_modules/@dharta/contracts/dist/wire-events.d.ts.map +1 -0
  180. package/node_modules/@dharta/contracts/dist/wire-events.js +48 -0
  181. package/node_modules/@dharta/contracts/dist/wire-events.js.map +1 -0
  182. package/node_modules/@dharta/contracts/model-auth-capabilities-v1.json +168 -0
  183. package/node_modules/@dharta/contracts/model-auth-reasons-v1.json +38 -0
  184. package/node_modules/@dharta/contracts/package.json +44 -0
  185. package/node_modules/@dharta/contracts/src/activity.ts +215 -0
  186. package/node_modules/@dharta/contracts/src/artifact-runtime-formats.ts +54 -0
  187. package/node_modules/@dharta/contracts/src/artifact-runtime-validators.generated.ts +29 -0
  188. package/node_modules/@dharta/contracts/src/artifact-runtime.generated.ts +2093 -0
  189. package/node_modules/@dharta/contracts/src/artifact-runtime.ts +520 -0
  190. package/node_modules/@dharta/contracts/src/index.ts +5 -0
  191. package/node_modules/@dharta/contracts/src/model-auth.ts +82 -0
  192. package/node_modules/@dharta/contracts/src/tasks.ts +142 -0
  193. package/node_modules/@dharta/contracts/src/wire-events.ts +150 -0
  194. package/node_modules/@dharta/contracts/wire-events.json +29 -0
  195. package/node_modules/@dharta/sdk/LICENSE +1 -1
  196. package/node_modules/@dharta/sdk/README.md +22 -8
  197. package/node_modules/@dharta/sdk/dist/errors.d.ts +9 -7
  198. package/node_modules/@dharta/sdk/dist/errors.d.ts.map +1 -1
  199. package/node_modules/@dharta/sdk/dist/errors.js +36 -18
  200. package/node_modules/@dharta/sdk/dist/errors.js.map +1 -1
  201. package/node_modules/@dharta/sdk/dist/fleet.d.ts +69 -0
  202. package/node_modules/@dharta/sdk/dist/fleet.d.ts.map +1 -0
  203. package/node_modules/@dharta/sdk/dist/fleet.js +240 -0
  204. package/node_modules/@dharta/sdk/dist/fleet.js.map +1 -0
  205. package/node_modules/@dharta/sdk/dist/http.d.ts +6 -2
  206. package/node_modules/@dharta/sdk/dist/http.d.ts.map +1 -1
  207. package/node_modules/@dharta/sdk/dist/http.js +71 -12
  208. package/node_modules/@dharta/sdk/dist/http.js.map +1 -1
  209. package/node_modules/@dharta/sdk/dist/idempotency.d.ts +12 -0
  210. package/node_modules/@dharta/sdk/dist/idempotency.d.ts.map +1 -0
  211. package/node_modules/@dharta/sdk/dist/idempotency.js +72 -0
  212. package/node_modules/@dharta/sdk/dist/idempotency.js.map +1 -0
  213. package/node_modules/@dharta/sdk/dist/index.d.ts +20 -6
  214. package/node_modules/@dharta/sdk/dist/index.d.ts.map +1 -1
  215. package/node_modules/@dharta/sdk/dist/index.js +27 -4
  216. package/node_modules/@dharta/sdk/dist/index.js.map +1 -1
  217. package/node_modules/@dharta/sdk/dist/resources.d.ts +108 -13
  218. package/node_modules/@dharta/sdk/dist/resources.d.ts.map +1 -1
  219. package/node_modules/@dharta/sdk/dist/resources.js +403 -57
  220. package/node_modules/@dharta/sdk/dist/resources.js.map +1 -1
  221. package/node_modules/@dharta/sdk/dist/types.d.ts +436 -16
  222. package/node_modules/@dharta/sdk/dist/types.d.ts.map +1 -1
  223. package/node_modules/@dharta/sdk/dist/types.js +31 -2
  224. package/node_modules/@dharta/sdk/dist/types.js.map +1 -1
  225. package/node_modules/@dharta/sdk/package.json +9 -3
  226. package/node_modules/@dharta/sdk/src/errors.ts +41 -18
  227. package/node_modules/@dharta/sdk/src/fleet.ts +339 -0
  228. package/node_modules/@dharta/sdk/src/http.ts +110 -17
  229. package/node_modules/@dharta/sdk/src/idempotency.ts +101 -0
  230. package/node_modules/@dharta/sdk/src/index.ts +50 -11
  231. package/node_modules/@dharta/sdk/src/resources.ts +549 -68
  232. package/node_modules/@dharta/sdk/src/types.ts +629 -20
  233. package/package.json +16 -6
  234. package/src/agent.ts +3 -2
  235. package/src/api-error.ts +6 -5
  236. package/src/cli.ts +978 -163
  237. package/src/command-tree.ts +13 -0
  238. package/src/commands/agents.ts +365 -167
  239. package/src/commands/analytics.ts +6 -5
  240. package/src/commands/api.ts +73 -92
  241. package/src/commands/appearance.ts +5 -6
  242. package/src/commands/artifact.ts +7 -6
  243. package/src/commands/audit-log.ts +2 -1
  244. package/src/commands/auth.ts +25 -5
  245. package/src/commands/caps.ts +11 -15
  246. package/src/commands/deploy.ts +18 -4
  247. package/src/commands/dev.ts +86 -25
  248. package/src/commands/doctor.ts +69 -0
  249. package/src/commands/embed-keys.ts +4 -14
  250. package/src/commands/env.ts +4 -7
  251. package/src/commands/files.ts +3 -4
  252. package/src/commands/fleet.ts +672 -0
  253. package/src/commands/genui.ts +59 -0
  254. package/src/commands/git-credential.ts +2 -3
  255. package/src/commands/kartas.ts +45 -11
  256. package/src/commands/keys.ts +152 -35
  257. package/src/commands/login.ts +2 -1
  258. package/src/commands/members.ts +31 -6
  259. package/src/commands/model-connections.ts +547 -0
  260. package/src/commands/open.ts +5 -4
  261. package/src/commands/org.ts +57 -0
  262. package/src/commands/organization.ts +153 -0
  263. package/src/commands/parity.ts +1 -3
  264. package/src/commands/repos.ts +23 -0
  265. package/src/commands/rollback.ts +2 -3
  266. package/src/commands/schedules.ts +33 -22
  267. package/src/commands/scopes.ts +8 -1
  268. package/src/commands/sessions.ts +9 -3
  269. package/src/commands/setup.ts +5 -4
  270. package/src/commands/update.ts +28 -12
  271. package/src/commands/webhooks.ts +30 -19
  272. package/src/completions.ts +25 -13
  273. package/src/config.ts +141 -9
  274. package/src/context.ts +34 -7
  275. package/src/docs/cli-reference.ts +206 -0
  276. package/src/exit-codes.ts +122 -0
  277. package/src/genui.ts +1398 -0
  278. package/src/harnesses.ts +2 -1
  279. package/src/jq.ts +109 -0
  280. package/src/login-offer.ts +6 -2
  281. package/src/manifest.ts +134 -0
  282. package/src/model-auth.ts +173 -0
  283. package/src/model-settings.ts +222 -0
  284. package/src/money.ts +5 -1
  285. package/src/output.ts +60 -0
  286. package/src/runtime.ts +1 -1
  287. package/src/scaffold.ts +2 -1
  288. package/src/validators.ts +18 -11
  289. package/src/version-check.ts +1 -1
  290. package/dist/commands/model-keys.d.ts +0 -22
  291. package/dist/commands/model-keys.js +0 -222
  292. package/dist/commands/model-keys.js.map +0 -1
  293. package/src/commands/model-keys.ts +0 -278
@@ -1,11 +1,31 @@
1
1
  import type { CliContext } from "../context.js";
2
+ import type {
3
+ AgentChangeResponse,
4
+ AgentCreateParams,
5
+ AgentCreateResponse,
6
+ AgentDeployResponse,
7
+ AgentFromTemplateParams,
8
+ AgentFromTemplateResponse,
9
+ AgentLinkGithubResponse,
10
+ GithubRepository,
11
+ ReleaseRetryResponse,
12
+ } from "@dharta/sdk";
13
+ import { isAgentLinkGithubResponse } from "@dharta/sdk";
2
14
  import { fmtTs, table } from "../output.js";
3
15
  import { validateAgentSlug } from "../agent.js";
4
- import { closestChoice, parseReleaseVersion } from "../validators.js";
5
- import { randomUUID } from "node:crypto";
6
-
7
- const AGENT_MODEL_SOURCES = ["inherit", "platform"] as const;
8
- const MAX_OUTPUT_TOKENS = 1_000_000;
16
+ import { CliUsageError } from "../exit-codes.js";
17
+ import { closestChoice, normalizeNonBlank, parsePositiveInteger, parseReleaseVersion } from "../validators.js";
18
+ import {
19
+ type ModelParameterOptions,
20
+ formatModelParameters,
21
+ modelParameterOptionPresent,
22
+ modelParameterPatch,
23
+ normalizeConnectorSlug,
24
+ normalizeModelParameterOptions,
25
+ normalizeModelReference,
26
+ parseConnectorOrder,
27
+ validateModelParameterOptions,
28
+ } from "../model-settings.js";
9
29
 
10
30
  export interface AgentSummary {
11
31
  id: string | number;
@@ -38,16 +58,35 @@ export interface AgentDetail extends AgentSummary {
38
58
  resolved_model?: ResolvedAgentModel;
39
59
  }
40
60
 
41
- export interface ResolvedAgentModel {
42
- model_id: string | null;
43
- model_key_id: number | null;
44
- auth_type: string;
45
- provider: string;
46
- endpoint_url: string | null;
47
- api_format: string;
48
- custom_endpoint: boolean;
49
- parameters: Record<string, unknown>;
50
- }
61
+ // Either the route resolved, or it carries a stable failure code and nothing
62
+ // else. Narrow on `error` before reading the rest, the same way the SDK's
63
+ // ResolvedOrganizationModel is read.
64
+ export type ResolvedAgentModel =
65
+ | {
66
+ error?: never;
67
+ model_id: string | null;
68
+ model_connection_id: number | null;
69
+ // The selected connector's slug, or `karta` for the platform connector.
70
+ connector: string | null;
71
+ credential_kind: string;
72
+ provider: string;
73
+ endpoint_url: string | null;
74
+ api_format: string;
75
+ custom_endpoint: boolean;
76
+ parameters: Record<string, unknown>;
77
+ }
78
+ | {
79
+ error: string;
80
+ model_id?: never;
81
+ model_connection_id?: never;
82
+ connector?: never;
83
+ credential_kind?: never;
84
+ provider?: never;
85
+ endpoint_url?: never;
86
+ api_format?: never;
87
+ custom_endpoint?: never;
88
+ parameters?: never;
89
+ };
51
90
 
52
91
  interface AgentShowEnvelope {
53
92
  agent: {
@@ -79,9 +118,11 @@ export interface AgentSettingsAgent {
79
118
  workspace_access?: WorkspaceAccessMatrix;
80
119
  model: {
81
120
  source: string;
82
- key_id: number | null;
121
+ model_connection_id: number | null;
83
122
  model: string | null;
84
123
  parameters: Record<string, unknown>;
124
+ connector_order: string[];
125
+ model_reference: string;
85
126
  };
86
127
  }
87
128
 
@@ -182,11 +223,14 @@ export async function agentsShow(ctx: CliContext, slug: string): Promise<string>
182
223
  }
183
224
 
184
225
  function formatResolvedModel(route: ResolvedAgentModel): string {
226
+ // An organization with no resolvable model still has agents worth reading.
227
+ // Say which failure it is rather than printing a route made of blanks.
228
+ if (route.error !== undefined) {
229
+ return `Resolved model: unavailable (${route.error})`;
230
+ }
185
231
  const model = route.model_id ?? "provider default";
186
- const connector = route.model_key_id == null ? "platform" : `connector ${route.model_key_id}`;
187
- const parameters = Object.entries(route.parameters)
188
- .map(([name, value]) => `${name}=${JSON.stringify(value)}`)
189
- .join(", ");
232
+ const connector = resolvedConnectorLabel(route);
233
+ const parameters = formatModelParameters(route.parameters);
190
234
  return [
191
235
  `Resolved model: ${route.provider}/${model} (${route.api_format}, ${connector})`,
192
236
  route.endpoint_url ? `Model endpoint: ${route.endpoint_url}` : null,
@@ -194,6 +238,14 @@ function formatResolvedModel(route: ResolvedAgentModel): string {
194
238
  ].filter((line): line is string => line !== null).join("\n");
195
239
  }
196
240
 
241
+ // Name the connector the way every other command does: by slug, which is
242
+ // also what `--connector` and a model reference accept.
243
+ function resolvedConnectorLabel(route: ResolvedAgentModel): string {
244
+ return route.connector === "karta" || route.connector === null
245
+ ? "platform"
246
+ : `connector ${route.connector}`;
247
+ }
248
+
197
249
  function isReleaseScopeError(error: unknown): boolean {
198
250
  if (!(error instanceof Error)) return false;
199
251
  const status = (error as Error & { status?: unknown }).status;
@@ -263,7 +315,6 @@ export async function agentsActivateReleaseData(
263
315
  return ctx.http.request<{ agent: ActivationAgent; release: ReleaseSummary; previous_release: ReleaseSummary | null }>({
264
316
  method: "POST",
265
317
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/releases/${encodeURIComponent(releaseVersion)}/activate`,
266
- headers: { "Idempotency-Key": randomUUID() },
267
318
  });
268
319
  }
269
320
 
@@ -282,7 +333,6 @@ export async function agentsServingSetData(
282
333
  return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
283
334
  method: "PATCH",
284
335
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/serving`,
285
- headers: { "Idempotency-Key": randomUUID() },
286
336
  body: { enabled },
287
337
  });
288
338
  }
@@ -302,7 +352,6 @@ export async function agentsDashboardEditsSetData(
302
352
  return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
303
353
  method: "PATCH",
304
354
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/dashboard_edits`,
305
- headers: { "Idempotency-Key": randomUUID() },
306
355
  body: { enabled },
307
356
  });
308
357
  }
@@ -313,18 +362,9 @@ export async function agentsDashboardEditsSet(ctx: CliContext, slug: string, ena
313
362
  return `Dashboard edits ${state} for ${res.agent.slug}${res.changed ? "" : " (unchanged)"}.`;
314
363
  }
315
364
 
316
- export interface AgentModelSetOptions {
317
- source?: string;
318
- connectorId?: string;
319
- model?: string;
320
- temperature?: string;
321
- clearTemperature?: boolean;
322
- topP?: string;
323
- clearTopP?: boolean;
324
- maxOutputTokens?: string;
325
- clearMaxOutputTokens?: boolean;
326
- reasoningEffort?: string;
327
- clearReasoningEffort?: boolean;
365
+ export interface AgentModelSetOptions extends ModelParameterOptions {
366
+ connector?: string;
367
+ modelRef?: string;
328
368
  }
329
369
 
330
370
  export async function agentsModelSetData(
@@ -335,16 +375,17 @@ export async function agentsModelSetData(
335
375
  const normalizedSlug = normalizeAgentSlug(slug);
336
376
  const normalized = validateAgentModelSetOptions(opts);
337
377
  const body: Record<string, unknown> = {};
338
- if (normalized.source !== undefined || normalized.connectorId !== undefined) {
339
- body.model_connector = modelConnector(normalized);
378
+ if (normalized.connector !== undefined) {
379
+ body.connector_order = parseConnectorOrder(normalized.connector, "--connector");
380
+ }
381
+ if (normalized.modelRef !== undefined) {
382
+ body.model_reference = normalizeModelReference(normalized.modelRef, "--model-ref");
340
383
  }
341
- if (normalized.model !== undefined) body.default_model_id = normalized.model;
342
384
  const modelParameters = modelParameterPatch(normalized);
343
385
  if (Object.keys(modelParameters).length > 0) body.model_parameters = modelParameters;
344
386
  return ctx.http.request<{ agent: AgentSettingsAgent }>({
345
387
  method: "PATCH",
346
388
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/model`,
347
- headers: { "Idempotency-Key": randomUUID() },
348
389
  body,
349
390
  });
350
391
  }
@@ -352,7 +393,9 @@ export async function agentsModelSetData(
352
393
  export async function agentsModelSet(ctx: CliContext, slug: string, opts: AgentModelSetOptions): Promise<string> {
353
394
  const res = await agentsModelSetData(ctx, slug, opts);
354
395
  const model = res.agent.model.model ? ` model ${res.agent.model.model}` : "";
355
- return `Model override for ${res.agent.slug}: ${res.agent.model.source}${model}.`;
396
+ const order = res.agent.model.connector_order;
397
+ const source = order.length > 0 ? order.join(", ") : res.agent.model.source;
398
+ return `Model override for ${res.agent.slug}: ${source}${model}.`;
356
399
  }
357
400
 
358
401
  export function validateAgentModelSetOptions(opts: AgentModelSetOptions): AgentModelSetOptions {
@@ -370,7 +413,6 @@ export async function agentsWorkspaceInspectableSetData(
370
413
  return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
371
414
  method: "PATCH",
372
415
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/workspace_inspectable`,
373
- headers: { "Idempotency-Key": randomUUID() },
374
416
  body: { enabled },
375
417
  });
376
418
  }
@@ -396,19 +438,19 @@ export type WorkspaceAccessPatch = Record<string, Record<string, boolean>>;
396
438
  // the API merges. Unknown paths and non-on/off values fail loud with a suggestion.
397
439
  export function parseWorkspaceAccessAssignments(assignments: string[]): WorkspaceAccessPatch {
398
440
  if (assignments.length === 0) {
399
- throw new Error("Provide at least one grant, e.g. users.widget=on");
441
+ throw new CliUsageError("Provide at least one grant, e.g. users.widget=on");
400
442
  }
401
443
  const patch: WorkspaceAccessPatch = {};
402
444
  for (const raw of assignments) {
403
445
  const eq = raw.indexOf("=");
404
446
  if (eq === -1) {
405
- throw new Error(`Invalid grant "${raw}". Use <path>=<on|off>, e.g. users.widget=on.`);
447
+ throw new CliUsageError(`Invalid grant "${raw}". Use <path>=<on|off>, e.g. users.widget=on.`);
406
448
  }
407
449
  const path = raw.slice(0, eq).trim();
408
450
  const value = raw.slice(eq + 1);
409
451
  if (!WORKSPACE_ACCESS_PATHS.includes(path as (typeof WORKSPACE_ACCESS_PATHS)[number])) {
410
452
  const suggestion = closestChoice(path, WORKSPACE_ACCESS_PATHS);
411
- throw new Error(
453
+ throw new CliUsageError(
412
454
  `Unknown grant "${path}". One of: ${WORKSPACE_ACCESS_PATHS.join(", ")}.` +
413
455
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
414
456
  );
@@ -423,7 +465,7 @@ function parseGrantValue(value: string, path: string): boolean {
423
465
  const normalized = value.trim().toLowerCase();
424
466
  if (["on", "true", "enabled", "enable", "1"].includes(normalized)) return true;
425
467
  if (["off", "false", "disabled", "disable", "0"].includes(normalized)) return false;
426
- throw new Error(`Grant "${path}" must be on/off, enabled/disabled, or true/false (got "${value}").`);
468
+ throw new CliUsageError(`Grant "${path}" must be on/off, enabled/disabled, or true/false (got "${value}").`);
427
469
  }
428
470
 
429
471
  export async function agentsWorkspaceAccessShowData(ctx: CliContext, slug: string): Promise<WorkspaceAccessMatrix> {
@@ -459,7 +501,6 @@ export async function agentsWorkspaceAccessSetData(
459
501
  return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
460
502
  method: "PATCH",
461
503
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/workspace_access`,
462
- headers: { "Idempotency-Key": randomUUID() },
463
504
  body: { workspace_access: patch },
464
505
  });
465
506
  }
@@ -476,160 +517,317 @@ export async function agentsWorkspaceAccessSet(
476
517
  return `Workspace access updated for ${res.agent.slug}: ${paths.join(", ")}${res.changed ? "" : " (unchanged)"}.`;
477
518
  }
478
519
 
479
- export async function agentsDeleteData(ctx: CliContext, slug: string): Promise<{ agent: AgentSettingsAgent; archived: boolean }> {
520
+ // RFC 0048 P4. From here down the commands go through the SDK resource rather
521
+ // than a hand-built request: every new endpoint ships an @dharta/sdk method and
522
+ // the curated command calls it, so the wire shape and the idempotency header
523
+ // have one owner.
524
+
525
+ export async function agentsRenameData(
526
+ ctx: CliContext,
527
+ slug: string,
528
+ name: string,
529
+ ): Promise<AgentChangeResponse> {
480
530
  const normalizedSlug = normalizeAgentSlug(slug);
481
- return ctx.http.request<{ agent: AgentSettingsAgent; archived: boolean }>({
482
- method: "DELETE",
483
- path: `/api/agents/${encodeURIComponent(normalizedSlug)}`,
484
- headers: { "Idempotency-Key": randomUUID() },
485
- });
531
+ return ctx.sdk.agents.rename(normalizedSlug, normalizeAgentName(name));
486
532
  }
487
533
 
488
- export async function agentsDelete(ctx: CliContext, slug: string): Promise<string> {
489
- const res = await agentsDeleteData(ctx, slug);
490
- return `Archived ${res.agent.slug}.`;
534
+ export async function agentsRename(ctx: CliContext, slug: string, name: string): Promise<string> {
535
+ const res = await agentsRenameData(ctx, slug, name);
536
+ const unchanged = res.changed ? "" : " (unchanged)";
537
+ return `${res.agent.slug} is now displayed as ${JSON.stringify(res.agent.name)}${unchanged}. The slug is unchanged.`;
491
538
  }
492
539
 
493
- function modelConnector(opts: AgentModelSetOptions): string {
494
- if (opts.connectorId) return `key:${opts.connectorId}`;
495
- if (opts.source === "platform") return "platform";
496
- if (opts.source === "inherit" || !opts.source) return "inherit";
497
- throw new Error("--source must be inherit or platform.");
540
+ export async function agentsDurableWorkspaceSetData(
541
+ ctx: CliContext,
542
+ slug: string,
543
+ enabled: boolean,
544
+ ): Promise<AgentChangeResponse> {
545
+ const normalizedSlug = normalizeAgentSlug(slug);
546
+ return ctx.sdk.agents.setDurableWorkspace(normalizedSlug, enabled);
498
547
  }
499
548
 
500
- function validateModelSetOptions(opts: AgentModelSetOptions): void {
501
- const hasSource = opts.source !== undefined;
502
- const hasConnector = opts.connectorId !== undefined;
503
- const hasModel = opts.model !== undefined;
504
- const hasParameters = modelParameterOptionPresent(opts);
549
+ export async function agentsDurableWorkspaceSet(
550
+ ctx: CliContext,
551
+ slug: string,
552
+ enabled: boolean,
553
+ ): Promise<string> {
554
+ const res = await agentsDurableWorkspaceSetData(ctx, slug, enabled);
555
+ const state = enabled ? "enabled" : "disabled";
556
+ if (!res.changed) return `Durable workspace already ${state} for ${res.agent.slug}.`;
557
+ const effect = enabled
558
+ ? "Takes effect on the next release."
559
+ : "Takes effect on the next release; stored data is kept.";
560
+ return `Durable workspace ${state} for ${res.agent.slug}. ${effect}`;
561
+ }
505
562
 
506
- if (!hasSource && !hasConnector && !hasModel && !hasParameters) {
507
- throw new Error(
508
- "Pass at least one model option, connector option, or parameter option.",
509
- );
510
- }
511
- if (hasSource && hasConnector) {
512
- throw new Error("Use either --source or --connector-id, not both.");
563
+ export interface AgentCreateOptions {
564
+ slug?: string;
565
+ githubRepo?: string;
566
+ branch?: string;
567
+ }
568
+
569
+ export async function agentsCreateData(
570
+ ctx: CliContext,
571
+ name: string | undefined,
572
+ opts: AgentCreateOptions = {},
573
+ ): Promise<AgentCreateResponse | AgentLinkGithubResponse> {
574
+ const params = validateAgentCreateInput(name, opts);
575
+ if (typeof params.githubRepositoryId === "string") {
576
+ params.githubRepositoryId = await resolveGithubRepositoryId(ctx, params.githubRepositoryId);
513
577
  }
514
- if (hasSource && !(AGENT_MODEL_SOURCES as readonly string[]).includes(opts.source as string)) {
515
- const suggestion = closestChoice(opts.source as string, AGENT_MODEL_SOURCES);
516
- throw new Error(
517
- `Unknown --source ${opts.source}. Allowed sources: ${AGENT_MODEL_SOURCES.join(", ")}.` +
518
- (suggestion ? ` Did you mean ${suggestion}?` : ""),
519
- );
578
+ return ctx.sdk.agents.create(params);
579
+ }
580
+
581
+ // `--github-repo` takes either the numeric id or the owner/name a person can
582
+ // actually read off GitHub. The name is resolved through the same listing
583
+ // `dharta agent github-repos` prints, so an unknown one names the alternatives
584
+ // rather than reaching the server as a 404.
585
+ async function resolveGithubRepositoryId(ctx: CliContext, fullName: string): Promise<number> {
586
+ let data: GithubRepository[];
587
+ try {
588
+ ({ data } = await ctx.sdk.githubRepositories.list());
589
+ } catch (error) {
590
+ // The listing rides source_repos:read, which a deploy key holding only
591
+ // agents:write does not have. Without this the command fails naming a scope
592
+ // the user never asked for, on a step they did not know it was taking.
593
+ if ((error as { status?: number }).status === 403) {
594
+ throw new CliUsageError(
595
+ `Resolving --github-repo ${fullName} needs the source_repos:read scope, because it looks the ` +
596
+ "repository up in your installations. This key does not have it. Pass the numeric id " +
597
+ "instead, which needs no extra scope, or use a key with source_repos:read.",
598
+ );
599
+ }
600
+ throw error;
520
601
  }
521
- if (hasConnector && !isPositiveNumericId(opts.connectorId as string)) {
522
- throw new Error("--connector-id must be a positive numeric model connector id from `dharta model-keys list`.");
602
+ const wanted = fullName.toLowerCase();
603
+ const match = data.find((repo) => repo.full_name.toLowerCase() === wanted);
604
+ if (match) return match.id;
605
+
606
+ const known = data.map((repo) => repo.full_name);
607
+ const suggestion = closestChoice(fullName, known);
608
+ const tail = suggestion
609
+ ? ` Did you mean ${suggestion}?`
610
+ : known.length > 0
611
+ ? ` Installed: ${known.slice(0, 10).join(", ")}.`
612
+ : " No repositories are installed for this organization; install the Dharta GitHub App first.";
613
+ // A usage error, not a failure: the user typed a name that does not exist, so
614
+ // this exits 2 like every other bad-argument path rather than 1.
615
+ throw new CliUsageError(`${fullName} is not an installed GitHub repository.${tail}`);
616
+ }
617
+
618
+ export async function agentsGithubReposData(ctx: CliContext): Promise<{ data: GithubRepository[] }> {
619
+ return ctx.sdk.githubRepositories.list();
620
+ }
621
+
622
+ export async function agentsGithubRepos(ctx: CliContext): Promise<string> {
623
+ const { data } = await agentsGithubReposData(ctx);
624
+ if (data.length === 0) {
625
+ return "No GitHub repositories are installed for this organization. Install the Dharta GitHub App, then try again.";
523
626
  }
524
- validateParameterPair(opts.temperature, opts.clearTemperature, "--temperature", "--clear-temperature");
525
- validateParameterPair(opts.topP, opts.clearTopP, "--top-p", "--clear-top-p");
526
- validateParameterPair(
527
- opts.maxOutputTokens,
528
- opts.clearMaxOutputTokens,
529
- "--max-output-tokens",
530
- "--clear-max-output-tokens",
531
- );
532
- validateParameterPair(
533
- opts.reasoningEffort,
534
- opts.clearReasoningEffort,
535
- "--reasoning-effort",
536
- "--clear-reasoning-effort",
627
+ return table(
628
+ data.map((repo) => ({
629
+ id: String(repo.id),
630
+ repository: repo.full_name,
631
+ branch: repo.default_branch,
632
+ account: repo.installation?.account_login ?? "-",
633
+ linked: repo.linked ? "yes" : "no",
634
+ })),
537
635
  );
538
- if (opts.temperature !== undefined) parseBoundedNumber(opts.temperature, "--temperature", 0, 2, false);
539
- if (opts.topP !== undefined) parseBoundedNumber(opts.topP, "--top-p", 0, 1, true);
540
- if (opts.maxOutputTokens !== undefined) {
541
- const value = parsePositiveInteger(opts.maxOutputTokens, "--max-output-tokens");
542
- if (value > MAX_OUTPUT_TOKENS) {
543
- throw new Error(`--max-output-tokens must be at most ${MAX_OUTPUT_TOKENS}.`);
544
- }
636
+ }
637
+
638
+ export async function agentsCreate(
639
+ ctx: CliContext,
640
+ name: string | undefined,
641
+ opts: AgentCreateOptions = {},
642
+ ): Promise<string> {
643
+ const res = await agentsCreateData(ctx, name, opts);
644
+ if (isAgentLinkGithubResponse(res)) {
645
+ const verb = res.created ? "Linked" : "Re-linked";
646
+ // A sync that could not be queued is a 503 from the server, so it never
647
+ // reaches here: it throws, and the command exits non-zero. Re-running the
648
+ // command is the right retry - the server never stores a 5xx, so a retry
649
+ // re-drives the sync whether it reuses the key or mints a new one.
650
+ return [
651
+ `${verb} ${res.source_repo.github_repository} as ${res.source_repo.slug} ` +
652
+ `(branch ${res.source_repo.deploy_branch}).`,
653
+ "An agent appears after the sync, when the deploy branch has exactly one enabled dharta.toml.",
654
+ "Watch for it with: dharta agent list",
655
+ ].join("\n");
656
+ }
657
+ const verb = res.created ? "Created" : "Already exists:";
658
+ return [
659
+ `${verb} ${res.agent.slug} (branch ${res.agent.deploy_branch}).`,
660
+ `Clone: ${res.clone_url}`,
661
+ ].join("\n");
662
+ }
663
+
664
+ // Exported so the command layer can reject bad input before it costs a request.
665
+ export function validateAgentCreateInput(
666
+ name: string | undefined,
667
+ opts: AgentCreateOptions = {},
668
+ ): AgentCreateParams {
669
+ const params: AgentCreateParams = {};
670
+ if (opts.githubRepo !== undefined) {
671
+ // An owner/name is kept as a string and resolved against the installed
672
+ // listing later; a bare number is validated here so a typo costs no request.
673
+ params.githubRepositoryId = opts.githubRepo.includes("/")
674
+ ? normalizeGithubFullName(opts.githubRepo)
675
+ : parsePositiveInteger(opts.githubRepo, "--github-repo");
676
+ }
677
+ if (name !== undefined) params.name = normalizeAgentName(name);
678
+ if (params.name === undefined && params.githubRepositoryId === undefined) {
679
+ throw new CliUsageError("Pass a name, or --github-repo <id> to link an installed GitHub repository.");
545
680
  }
546
- if (opts.reasoningEffort !== undefined && !["low", "medium", "high"].includes(opts.reasoningEffort)) {
547
- throw new Error("--reasoning-effort must be low, medium, or high.");
681
+ if (opts.slug !== undefined) params.slug = normalizeAgentSlug(opts.slug);
682
+ if (opts.branch !== undefined) {
683
+ if (opts.githubRepo === undefined) {
684
+ throw new CliUsageError("--branch needs --github-repo: the deploy branch belongs to the linked repository.");
685
+ }
686
+ params.deployBranch = normalizeNonBlank(opts.branch, "--branch");
548
687
  }
688
+ return params;
549
689
  }
550
690
 
551
- function isPositiveNumericId(value: string): boolean {
552
- if (!/^\d+$/.test(value)) return false;
553
- const parsed = Number(value);
554
- return Number.isSafeInteger(parsed) && parsed > 0;
691
+ export interface AgentCreateFromTemplateOptions {
692
+ slug?: string;
693
+ description?: string;
694
+ harness?: string;
695
+ harnessVersion?: string;
555
696
  }
556
697
 
557
- function normalizeModelSetOptions(opts: AgentModelSetOptions): AgentModelSetOptions {
558
- return {
559
- source: normalizeOptionalFlag(opts.source, "--source"),
560
- connectorId: normalizeOptionalFlag(opts.connectorId, "--connector-id"),
561
- model: normalizeOptionalFlag(opts.model, "--model"),
562
- temperature: normalizeOptionalFlag(opts.temperature, "--temperature"),
563
- clearTemperature: opts.clearTemperature,
564
- topP: normalizeOptionalFlag(opts.topP, "--top-p"),
565
- clearTopP: opts.clearTopP,
566
- maxOutputTokens: normalizeOptionalFlag(opts.maxOutputTokens, "--max-output-tokens"),
567
- clearMaxOutputTokens: opts.clearMaxOutputTokens,
568
- reasoningEffort: normalizeOptionalFlag(opts.reasoningEffort, "--reasoning-effort"),
569
- clearReasoningEffort: opts.clearReasoningEffort,
698
+ export async function agentsCreateFromTemplateData(
699
+ ctx: CliContext,
700
+ template: string,
701
+ name: string,
702
+ opts: AgentCreateFromTemplateOptions = {},
703
+ ): Promise<AgentFromTemplateResponse> {
704
+ const params = validateAgentCreateFromTemplateInput(template, name, opts);
705
+ return ctx.sdk.agents.createFromTemplate(params);
706
+ }
707
+
708
+ export async function agentsCreateFromTemplate(
709
+ ctx: CliContext,
710
+ template: string,
711
+ name: string,
712
+ opts: AgentCreateFromTemplateOptions = {},
713
+ ): Promise<string> {
714
+ const res = await agentsCreateFromTemplateData(ctx, template, name, opts);
715
+ const version = res.release ? `v${res.release.version}` : "the first release";
716
+ const state = res.release?.status === "active" && !res.state
717
+ ? `${version} is live.`
718
+ : `${version} is still building - watch it with \`dharta agent releases ${res.agent.slug}\`.`;
719
+ return [
720
+ `Created ${res.agent.slug} from the '${normalizeNonBlank(template, "template")}' template. ${state}`,
721
+ `Clone: ${res.clone_url}`,
722
+ `Open it with: dharta open ${res.agent.slug}`,
723
+ ].join("\n");
724
+ }
725
+
726
+ // Exported so the command layer can reject bad input before it costs a request.
727
+ export function validateAgentCreateFromTemplateInput(
728
+ template: string,
729
+ name: string,
730
+ opts: AgentCreateFromTemplateOptions = {},
731
+ ): AgentFromTemplateParams {
732
+ const params: AgentFromTemplateParams = {
733
+ template: normalizeNonBlank(template, "template"),
734
+ name: normalizeAgentName(name),
570
735
  };
736
+ if (opts.slug !== undefined) params.slug = normalizeAgentSlug(opts.slug);
737
+ if (opts.description !== undefined) params.description = normalizeNonBlank(opts.description, "--description");
738
+ if (opts.harness !== undefined) params.harness = normalizeNonBlank(opts.harness, "--harness");
739
+ if (opts.harnessVersion !== undefined) {
740
+ if (opts.harness === undefined) {
741
+ throw new CliUsageError("--harness-version needs --harness: a version belongs to one harness.");
742
+ }
743
+ params.harnessVersion = normalizeNonBlank(opts.harnessVersion, "--harness-version");
744
+ }
745
+ return params;
571
746
  }
572
747
 
573
- function modelParameterOptionPresent(opts: AgentModelSetOptions): boolean {
574
- return opts.temperature !== undefined || opts.clearTemperature === true ||
575
- opts.topP !== undefined || opts.clearTopP === true ||
576
- opts.maxOutputTokens !== undefined || opts.clearMaxOutputTokens === true ||
577
- opts.reasoningEffort !== undefined || opts.clearReasoningEffort === true;
748
+ export async function agentsRedeployData(ctx: CliContext, slug: string): Promise<AgentDeployResponse> {
749
+ const normalizedSlug = normalizeAgentSlug(slug);
750
+ return ctx.sdk.agents.deploy(normalizedSlug);
578
751
  }
579
752
 
580
- function modelParameterPatch(opts: AgentModelSetOptions): Record<string, number | string | null> {
581
- const patch: Record<string, number | string | null> = {};
582
- if (opts.temperature !== undefined) patch.temperature = Number(opts.temperature);
583
- if (opts.clearTemperature) patch.temperature = null;
584
- if (opts.topP !== undefined) patch.top_p = Number(opts.topP);
585
- if (opts.clearTopP) patch.top_p = null;
586
- if (opts.maxOutputTokens !== undefined) patch.max_output_tokens = Number(opts.maxOutputTokens);
587
- if (opts.clearMaxOutputTokens) patch.max_output_tokens = null;
588
- if (opts.reasoningEffort !== undefined) patch.reasoning_effort = opts.reasoningEffort;
589
- if (opts.clearReasoningEffort) patch.reasoning_effort = null;
590
- return patch;
753
+ export async function agentsRedeploy(ctx: CliContext, slug: string): Promise<string> {
754
+ const res = await agentsRedeployData(ctx, slug);
755
+ const repo = res.source_repo.github_repository ?? res.source_repo.slug;
756
+ return [
757
+ `Re-sync queued for ${repo}; ${res.agent.slug} rebuilds from ${res.source_repo.deploy_branch}.`,
758
+ `Watch it with: dharta agent releases ${res.agent.slug}`,
759
+ ].join("\n");
591
760
  }
592
761
 
593
- function validateParameterPair(
594
- value: string | undefined,
595
- clear: boolean | undefined,
596
- setFlag: string,
597
- clearFlag: string,
598
- ): void {
599
- if (value !== undefined && clear) throw new Error(`Use either ${setFlag} or ${clearFlag}, not both.`);
600
- }
601
-
602
- function parseBoundedNumber(
603
- value: string,
604
- flag: string,
605
- minimum: number,
606
- maximum: number,
607
- exclusiveMinimum: boolean,
608
- ): number {
609
- const parsed = Number(value);
610
- const minimumOk = exclusiveMinimum ? parsed > minimum : parsed >= minimum;
611
- if (!Number.isFinite(parsed) || !minimumOk || parsed > maximum) {
612
- const lower = exclusiveMinimum ? `greater than ${minimum}` : `at least ${minimum}`;
613
- throw new Error(`${flag} must be a finite number ${lower} and at most ${maximum}.`);
614
- }
615
- return parsed;
762
+ export async function agentsRetryReleaseData(
763
+ ctx: CliContext,
764
+ slug: string,
765
+ version: string,
766
+ ): Promise<ReleaseRetryResponse> {
767
+ const normalizedSlug = normalizeAgentSlug(slug);
768
+ const releaseVersion = parseReleaseVersion(version, "release version");
769
+ return ctx.sdk.releases.retry(normalizedSlug, releaseVersion);
770
+ }
771
+
772
+ export async function agentsRetryRelease(ctx: CliContext, slug: string, version: string): Promise<string> {
773
+ const res = await agentsRetryReleaseData(ctx, slug, version);
774
+ const state = res.state === "pending" ? "is still building" : "is built";
775
+ return `Retried v${res.retried_release.version} as v${res.release.version}, which ${state}.`;
616
776
  }
617
777
 
618
- function parsePositiveInteger(value: string, flag: string): number {
619
- const parsed = Number(value);
620
- if (!Number.isSafeInteger(parsed) || parsed <= 0) {
621
- throw new Error(`${flag} must be a positive whole number.`);
778
+ function normalizeGithubFullName(value: string): string {
779
+ const normalized = normalizeNonBlank(value, "--github-repo");
780
+ if (!/^[^/\s]+\/[^/\s]+$/.test(normalized)) {
781
+ throw new CliUsageError("--github-repo must be a repository id or an owner/name, e.g. acme-eng/support-bot.");
622
782
  }
623
- return parsed;
783
+ return normalized;
624
784
  }
625
785
 
626
- function normalizeOptionalFlag(value: string | undefined, flag: string): string | undefined {
627
- if (value === undefined) return undefined;
628
- const normalized = value.trim();
629
- if (normalized.length === 0) throw new Error(`${flag} cannot be blank.`);
786
+ function normalizeAgentName(name: string): string {
787
+ const normalized = normalizeNonBlank(name, "agent name");
788
+ if (normalized.length > 100) throw new CliUsageError("Agent name must be 1-100 characters.");
630
789
  return normalized;
631
790
  }
632
791
 
792
+ export async function agentsDeleteData(ctx: CliContext, slug: string): Promise<{ agent: AgentSettingsAgent; archived: boolean }> {
793
+ const normalizedSlug = normalizeAgentSlug(slug);
794
+ return ctx.http.request<{ agent: AgentSettingsAgent; archived: boolean }>({
795
+ method: "DELETE",
796
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}`,
797
+ });
798
+ }
799
+
800
+ export async function agentsDelete(ctx: CliContext, slug: string): Promise<string> {
801
+ const res = await agentsDeleteData(ctx, slug);
802
+ return `Archived ${res.agent.slug}.`;
803
+ }
804
+
805
+ function validateModelSetOptions(opts: AgentModelSetOptions): void {
806
+ const hasConnector = opts.connector !== undefined;
807
+ const hasModelRef = opts.modelRef !== undefined;
808
+ const hasParameters = modelParameterOptionPresent(opts);
809
+
810
+ if (!hasConnector && !hasModelRef && !hasParameters) {
811
+ throw new CliUsageError(
812
+ "Pass at least one connector option, model reference option, or parameter option.",
813
+ );
814
+ }
815
+ if (hasConnector) parseConnectorOrder(opts.connector as string, "--connector");
816
+ if (hasModelRef) normalizeModelReference(opts.modelRef as string, "--model-ref");
817
+ validateModelParameterOptions(opts);
818
+ }
819
+
820
+ function normalizeModelSetOptions(opts: AgentModelSetOptions): AgentModelSetOptions {
821
+ return {
822
+ ...normalizeModelParameterOptions(opts),
823
+ // `--connector ""` and `--model-ref ""` are meaningful: an empty
824
+ // connector order inherits from the organization, and an empty model
825
+ // reference falls back to the connector default.
826
+ connector: opts.connector?.trim(),
827
+ modelRef: opts.modelRef?.trim(),
828
+ };
829
+ }
830
+
633
831
  function normalizeAgentSlug(slug: string): string {
634
832
  return validateAgentSlug(slug, "agent slug");
635
833
  }