@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
package/src/cli.ts CHANGED
@@ -3,9 +3,14 @@ import { readFileSync, realpathSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import * as readline from "node:readline";
5
5
  import { Argument, Command, InvalidArgumentError, Option } from "commander";
6
- import { API_KEY_SCOPES, type ApiKeyScope, type ModelKeyProvider } from "@dharta/sdk";
6
+ import { API_KEY_SCOPES, type ApiKeyScope, type ModelConnectionProvider } from "@dharta/sdk";
7
7
  import { buildContext } from "./context.js";
8
- import { resolveBaseUrl } from "./config.js";
8
+ import {
9
+ requestedProfileName,
10
+ resolveBaseUrl,
11
+ setRequestedIdempotencyKey,
12
+ setRequestedProfile,
13
+ } from "./config.js";
9
14
  import { inferAgentSlug, validateAgentSelectorOptions, validateAgentSlug } from "./agent.js";
10
15
  import { login } from "./commands/login.js";
11
16
  import {
@@ -28,28 +33,62 @@ import {
28
33
  type AnalyticsView,
29
34
  } from "./commands/analytics.js";
30
35
  import {
36
+ keysBulk,
37
+ keysBulkData,
31
38
  keysCreate,
32
39
  keysCreateData,
40
+ keysDisable,
41
+ keysDisableData,
42
+ keysEnable,
43
+ keysEnableData,
33
44
  keysList,
34
45
  keysListData,
35
46
  keysRename,
36
47
  keysRenameData,
37
48
  keysRevoke,
38
49
  keysRevokeData,
50
+ keysRotate,
51
+ keysRotateData,
52
+ KEY_BULK_ACTIONS,
53
+ KEY_ROTATION_GRACE_PERIODS,
54
+ validateKeysBulkInput,
39
55
  validateKeysCreateInput,
56
+ validateKeysRotateInput,
40
57
  } from "./commands/keys.js";
41
58
  import {
42
- modelKeysCreate,
43
- modelKeysCreateData,
44
- modelKeysList,
45
- modelKeysListData,
46
- modelKeysRevoke,
47
- modelKeysRevokeData,
48
- prepareModelKeysCreateOptions,
59
+ modelConnectionsConfigureModels,
60
+ modelConnectionsConfigureModelsData,
61
+ modelConnectionsCreate,
62
+ modelConnectionsCreateData,
63
+ modelConnectionsList,
64
+ modelConnectionsListData,
65
+ modelConnectionsModels,
66
+ modelConnectionsModelsData,
67
+ modelConnectionsModelsWritesOffers,
68
+ modelConnectionsRevoke,
69
+ modelConnectionsRevokeData,
70
+ modelConnectionsRotate,
71
+ modelConnectionsRotateData,
72
+ prepareModelConnectionsCreateOptions,
73
+ prepareModelConnectionsModelsOptions,
74
+ prepareModelConnectionsRotateOptions,
49
75
  MODEL_API_FORMATS,
50
- validateModelKeyId,
51
- validateModelKeysCreateOptions,
52
- } from "./commands/model-keys.js";
76
+ type ModelConnectionModelsOptions,
77
+ type ModelConnectionRotateOptions,
78
+ validateModelConnectionReference,
79
+ validateModelConnectionsCreateOptions,
80
+ validateModelConnectionsModelsOptions,
81
+ validateModelConnectionsRotateOptions,
82
+ } from "./commands/model-connections.js";
83
+ import {
84
+ type OrgModelSetOptions,
85
+ ORG_ENDPOINT_KINDS,
86
+ orgModelSet,
87
+ orgModelSetData,
88
+ orgModelShow,
89
+ orgModelShowData,
90
+ validateOrgModelSetOptions,
91
+ } from "./commands/organization.js";
53
92
  import {
54
93
  membersInvite,
55
94
  membersInviteData,
@@ -57,10 +96,24 @@ import {
57
96
  membersListData,
58
97
  membersRemove,
59
98
  membersRemoveData,
99
+ membersResendInvitation,
100
+ membersResendInvitationData,
101
+ membersRevokeInvitation,
102
+ membersRevokeInvitationData,
60
103
  membersSetRole,
61
104
  membersSetRoleData,
62
105
  validateMembersInviteInput,
63
106
  } from "./commands/members.js";
107
+ import {
108
+ orgRename,
109
+ orgRenameData,
110
+ orgSupportAccess,
111
+ orgSupportAccessData,
112
+ orgTranscriptAccess,
113
+ orgTranscriptAccessData,
114
+ normalizeOrganizationName,
115
+ } from "./commands/org.js";
116
+ import { reposDisconnectGithub, reposDisconnectGithubData } from "./commands/repos.js";
64
117
  import {
65
118
  kartaMembersGrant,
66
119
  kartaMembersGrantData,
@@ -88,21 +141,41 @@ import {
88
141
  kartaEnvSetData,
89
142
  kartaEnvUnset,
90
143
  kartaEnvUnsetData,
144
+ kartaShare,
145
+ kartaShareData,
146
+ kartaUnshare,
147
+ kartaUnshareData,
91
148
  validateKartaCreateOptions,
92
149
  validateKartaConfigureOptions,
93
150
  validateKartaNameOptions,
94
151
  } from "./commands/kartas.js";
95
152
  import { usage as usageCmd, usageData } from "./commands/usage.js";
96
153
  import {
154
+ type AgentCreateFromTemplateOptions,
155
+ type AgentCreateOptions,
97
156
  type AgentModelSetOptions,
98
157
  agentsActivateRelease,
99
158
  agentsActivateReleaseData,
159
+ agentsCreate,
160
+ agentsCreateData,
161
+ agentsCreateFromTemplate,
162
+ agentsCreateFromTemplateData,
100
163
  agentsDashboardEditsSet,
101
164
  agentsDashboardEditsSetData,
102
165
  agentsDelete,
103
166
  agentsDeleteData,
167
+ agentsDurableWorkspaceSet,
168
+ agentsDurableWorkspaceSetData,
169
+ agentsGithubRepos,
170
+ agentsGithubReposData,
104
171
  agentsList,
105
172
  agentsListData,
173
+ agentsRedeploy,
174
+ agentsRedeployData,
175
+ agentsRename,
176
+ agentsRenameData,
177
+ agentsRetryRelease,
178
+ agentsRetryReleaseData,
106
179
  agentsModelSet,
107
180
  agentsModelSetData,
108
181
  agentsReleases,
@@ -118,6 +191,8 @@ import {
118
191
  agentsWorkspaceAccessSet,
119
192
  agentsWorkspaceAccessSetData,
120
193
  parseWorkspaceAccessAssignments,
194
+ validateAgentCreateFromTemplateInput,
195
+ validateAgentCreateInput,
121
196
  validateAgentModelSetOptions,
122
197
  } from "./commands/agents.js";
123
198
  import {
@@ -185,6 +260,58 @@ import {
185
260
  directorySessionsData,
186
261
  validateDirectoryOptions,
187
262
  } from "./commands/directories.js";
263
+ import {
264
+ type AnalyticsSaveOptions,
265
+ type AssignOptions,
266
+ type ClassifyOptions,
267
+ type ExportCreateOptions,
268
+ type OperationCreateOptions,
269
+ type RunbookOptions,
270
+ type SnoozeOptions,
271
+ type ViewWriteOptions,
272
+ FLEET_ENTITY_TYPES,
273
+ FLEET_SELECTION_MODES,
274
+ FLEET_SNOOZE_DURATIONS,
275
+ FLEET_VIEW_VISIBILITIES,
276
+ fleetAnalyticsSave,
277
+ fleetAnalyticsSaveData,
278
+ fleetAnalyticsUnpin,
279
+ fleetAnalyticsUnpinData,
280
+ fleetExportCreate,
281
+ fleetExportCreateData,
282
+ fleetFinding,
283
+ fleetFindingAssign,
284
+ fleetFindingAssignData,
285
+ fleetFindingClassify,
286
+ fleetFindingClassifyData,
287
+ fleetFindingData,
288
+ fleetFindingSnooze,
289
+ fleetFindingSnoozeData,
290
+ fleetFindingUpdate,
291
+ fleetFindingUpdateData,
292
+ fleetOperationCancel,
293
+ fleetOperationCancelData,
294
+ fleetOperationConfirm,
295
+ fleetOperationConfirmData,
296
+ fleetOperationCreate,
297
+ fleetOperationCreateData,
298
+ fleetOperationRetry,
299
+ fleetOperationRetryData,
300
+ fleetRollout,
301
+ fleetRolloutData,
302
+ fleetRolloutRollback,
303
+ fleetRolloutRollbackData,
304
+ fleetViewCreate,
305
+ fleetViewCreateData,
306
+ fleetViewDelete,
307
+ fleetViewDeleteData,
308
+ fleetViewFavorite,
309
+ fleetViewFavoriteData,
310
+ fleetViewUnfavorite,
311
+ fleetViewUnfavoriteData,
312
+ fleetViewUpdate,
313
+ fleetViewUpdateData,
314
+ } from "./commands/fleet.js";
188
315
  import {
189
316
  parseEvents,
190
317
  webhooksCreate,
@@ -195,6 +322,8 @@ import {
195
322
  webhooksEnableData,
196
323
  webhooksList,
197
324
  webhooksListData,
325
+ webhooksTest,
326
+ webhooksTestData,
198
327
  webhooksUpdate,
199
328
  webhooksUpdateData,
200
329
  validateWebhooksCreateInput,
@@ -211,6 +340,8 @@ import { offerLogin } from "./login-offer.js";
211
340
  import { doctor } from "./commands/doctor.js";
212
341
  import { dev as devCmd } from "./commands/dev.js";
213
342
  import { registerArtifactCommands } from "./commands/artifact.js";
343
+ import { registerGenuiCommands } from "./commands/genui.js";
344
+ import { flushJsonPayload, isJson, printJson, resetJsonPayload } from "./output.js";
214
345
  import {
215
346
  isHandoffTarget,
216
347
  open as openCmd,
@@ -251,13 +382,15 @@ import { update } from "./commands/update.js";
251
382
  import { filesLs, filesLsData, filesGet } from "./commands/files.js";
252
383
  import { gitCredential } from "./commands/git-credential.js";
253
384
  import { bashCompletion, zshCompletion } from "./completions.js";
385
+ import { visibleSubcommands } from "./command-tree.js";
386
+ import { CliUsageError, exitCodeForError, isCommanderError, isNotAuthenticatedError } from "./exit-codes.js";
387
+ import { armJqFilter } from "./jq.js";
254
388
  import { closestChoice, normalizeHttpUrl, normalizeNonBlank, parsePositiveInteger, parseReleaseVersion } from "./validators.js";
389
+ import { DEV_MODEL_AUTH_VALUES, MODEL_CONNECTION_PROVIDERS, type DevModelAuth } from "./model-auth.js";
255
390
 
256
391
  const MEMBER_ROLES = ["owner", "admin", "developer", "billing", "viewer"] as const;
257
392
  const DHARTA_ACCESS_AUDIENCES = ["members", "anonymous", "off"] as const;
258
393
  const DHARTA_MEMBER_ROLES = ["viewer", "participant", "manager"] as const;
259
- const MODEL_KEY_AUTH_TYPES = ["api_key", "oauth_subscription"] as const;
260
- const AGENT_MODEL_SOURCES = ["inherit", "platform"] as const;
261
394
  const CLI_API_KEY_SCOPES = API_KEY_SCOPES;
262
395
  const STATE_HELP = "State values: on/off, enabled/disabled, true/false.";
263
396
 
@@ -275,29 +408,6 @@ interface DirectorySelectorOptions extends AgentSelectorOptions {
275
408
  limit?: string;
276
409
  }
277
410
 
278
- // --json output (RFC 0013 §3): every command that produces a result
279
- // payload prints it as JSON when --json is set. Progress narration
280
- // (device-flow codes, download logs) moves to stderr so stdout stays
281
- // parseable. Commands whose stdout is a stream, a REPL, a script, or a
282
- // protocol rather than a result payload (dev, logs, completion,
283
- // git-credential, and `deploy --mode git` whose output is the live
284
- // push sideband) reject --json. `deploy --mode folder` returns a
285
- // single payload, so it supports --json.
286
- //
287
- // Mechanics: the flag is registered on every node of the command tree
288
- // so commander's
289
- // outermost option scan consumes it wherever it appears on the line -
290
- // so actions read it through optsWithGlobals() on their Command. Commands
291
- // that always reject JSON keep a hidden parser option so help/completions
292
- // do not advertise an invalid mode.
293
- function isJson(cmd: Command): boolean {
294
- return Boolean(cmd.optsWithGlobals().json);
295
- }
296
-
297
- function printJson(payload: unknown): void {
298
- console.log(JSON.stringify(payload, null, 2));
299
- }
300
-
301
411
  function commandOpts<T extends object>(cmd: Command): T {
302
412
  return cmd.optsWithGlobals() as T;
303
413
  }
@@ -306,7 +416,7 @@ function validatePositionalAgentSelector(slug: string | undefined, opts: Pick<Ag
306
416
  if (slug === undefined) return;
307
417
  validateAgentSlug(slug, "agent slug");
308
418
  if (opts.agent !== undefined || opts.project !== undefined) {
309
- throw new Error("Use either the positional agent slug or --agent/--project, not both.");
419
+ throw new CliUsageError("Use either the positional agent slug or --agent/--project, not both.");
310
420
  }
311
421
  }
312
422
 
@@ -341,11 +451,75 @@ async function resolveAgentOptions<T extends AgentSelectorOptions>(
341
451
  }
342
452
 
343
453
  function rejectJson(cmd: Command, name: string): void {
344
- if (isJson(cmd)) {
345
- throw new Error(
346
- `--json is not supported for \`${name}\` - its output is not a single payload. Run it without --json.`,
347
- );
348
- }
454
+ if (!isJson(cmd)) return;
455
+ // --jq is rejected wherever --json is, and for the same reason: there is no
456
+ // payload to filter. Name the flag the user actually passed.
457
+ const flag = cmd.optsWithGlobals().jq !== undefined ? "--jq" : "--json";
458
+ throw new CliUsageError(
459
+ `${flag} is not supported for \`${name}\` - its output is not a single payload. Run it without ${flag}.`,
460
+ );
461
+ }
462
+
463
+ // A command group prints help and issues no request, so an Idempotency-Key on
464
+ // one is a mistake with nowhere to land. Read-only LEAF commands are not
465
+ // rejected: see the P9 entry in the RFC 0048 implementation log for why.
466
+ function rejectIdempotencyKey(cmd: Command, name: string): void {
467
+ if (cmd.optsWithGlobals().idempotencyKey === undefined) return;
468
+ throw new CliUsageError(
469
+ `--idempotency-key is not supported for \`${name}\` - it prints help and sends no request. ` +
470
+ "Put it on the command that performs the mutation.",
471
+ );
472
+ }
473
+
474
+ // `--idempotency-key` names an `Idempotency-Key` for a write to the Dharta API.
475
+ // These commands perform no mutation the key could protect, so accepting one
476
+ // would silently ignore it, which is precisely the outcome a caller reaching for
477
+ // the flag is trying to avoid.
478
+ //
479
+ // Several of them DO talk to Dharta, and the message must not claim otherwise:
480
+ //
481
+ // - `login` runs before a credential exists. Its device-flow POSTs carry no
482
+ // key and the server installs no idempotency filter on them, and it then
483
+ // reads `/me` to learn who the new key belongs to. Reads and unprotected
484
+ // POSTs, no mutation.
485
+ // - `create` fetches `/agent_templates` and writes the result to disk.
486
+ // - `setup` writes files, and with `--login` it runs the login flow above.
487
+ // - `auth token` and `auth logout` read and rewrite the local profile.
488
+ // - `genui` builds a bundle, `artifact` spawns a local helper.
489
+ // - `doctor` and `update` talk to the package registry, never to Dharta.
490
+ // - `completion` prints a shell script.
491
+ //
492
+ // Read-only Dharta commands (`ps`, `usage`, `webhooks list`) perform no mutation
493
+ // either and are deliberately NOT refused. They are steps in a run that writes,
494
+ // the header rides along and is ignored server-side, and a script that sets one
495
+ // flag for a whole pipeline should not have to special-case them. The commands
496
+ // below are not that: a caller who puts the key on `dharta login` is expressing
497
+ // an intent it can never honour, so saying so beats accepting it.
498
+ const NON_MUTATING_COMMAND_PATHS = [
499
+ "dharta login",
500
+ "dharta auth token",
501
+ "dharta auth logout",
502
+ "dharta create",
503
+ "dharta setup",
504
+ "dharta genui",
505
+ "dharta artifact",
506
+ "dharta doctor",
507
+ "dharta update",
508
+ "dharta completion",
509
+ ];
510
+
511
+ export function isNonMutatingCommandPath(path: string): boolean {
512
+ return NON_MUTATING_COMMAND_PATHS.some((entry) => path === entry || path.startsWith(`${entry} `));
513
+ }
514
+
515
+ function rejectNonMutatingIdempotencyKey(cmd: Command, key: string | undefined): void {
516
+ if (key === undefined) return;
517
+ const path = commandPath(cmd);
518
+ if (!isNonMutatingCommandPath(path)) return;
519
+ throw new CliUsageError(
520
+ `--idempotency-key is not supported for \`${path}\` - it performs no mutation the key could protect. ` +
521
+ "The flag applies to commands that write to the Dharta API.",
522
+ );
349
523
  }
350
524
 
351
525
  function helpOnlyAction(name: string) {
@@ -363,6 +537,7 @@ function helpOnlyAction(name: string) {
363
537
  );
364
538
  }
365
539
  rejectJson(cmd, name);
540
+ rejectIdempotencyKey(cmd, name);
366
541
  cmd.outputHelp();
367
542
  };
368
543
  }
@@ -385,27 +560,14 @@ function closestCommand(input: string, commands: string[]): string | null {
385
560
 
386
561
  function commandSuggestionTokens(cmd: Command): string[] {
387
562
  return Array.from(
388
- new Set(
389
- cmd.commands
390
- .filter((child) => !(child as unknown as { _hidden?: boolean })._hidden)
391
- .flatMap((child) => [child.name(), ...child.aliases()]),
392
- ),
563
+ new Set(visibleSubcommands(cmd).flatMap((child) => [child.name(), ...child.aliases()])),
393
564
  );
394
565
  }
395
566
 
396
567
  export function buildProgram(): Command {
397
568
  const program = new Command();
398
- // Commander 12 has no native help groups, so render the top-level command
399
- // list grouped (addHelpText below) and suppress commander's default flat
400
- // list on the ROOT only - subcommand help (e.g. `dharta agent --help`) keeps
401
- // commander's default rendering. `defaultHelp` is captured before the
402
- // override so it supplies the unmodified list for subcommands.
403
- const defaultHelp = program.createHelp();
404
569
  program
405
570
  .name("dharta")
406
- .configureHelp({
407
- visibleCommands: (cmd) => (cmd.parent === null ? [] : defaultHelp.visibleCommands(cmd)),
408
- })
409
571
  .description(
410
572
  "Build, run, and deploy AI agents on dharta.ai - one tool from create to open.\n" +
411
573
  "Agents are harness-native folders (Claude Code, OpenCode, DeepAgents, Goose, Codex CLI) plus dharta.toml; `dharta deploy` ships one selected folder.",
@@ -419,52 +581,9 @@ export function buildProgram(): Command {
419
581
  .addHelpText(
420
582
  "after",
421
583
  [
422
- "",
423
- "Agent lifecycle",
424
- " create Scaffold a brand-new agent",
425
- " setup Configure an agent here; write dharta.toml",
426
- " dev Preview locally as a user would (hot reload)",
427
- " deploy Deploy this agent folder",
428
- " open Open the live hosted page",
429
- " status Every agent here and its live release",
430
- "",
431
- "Operate",
432
- " sessions Inspect an agent's sessions",
433
- " session View one session transcript",
434
- " ps Sessions active right now",
435
- " logs Stream runtime logs",
436
- " files Read a karta's workspace files",
437
- " send One-shot message to a deployed agent",
438
- " rollback Roll back to a previous release",
439
- " env Environment bindings",
440
- "",
441
- "Manage",
442
- " agent List and show your agents",
443
- " artifact Designate durable workspace work product",
444
- "",
445
- "Account",
446
- " login Sign in (device flow)",
447
- " whoami Show the authenticated principal",
448
- " auth Status, tokens, logout",
449
- " audit-log Export audit events",
450
- " analytics Usage and cost analytics",
451
- " keys API keys",
452
- " model-keys BYOK provider credentials",
453
- " members Organization members and invitations",
454
- " kartas Durable Dharta identity, URLs, and ACLs",
455
- " api Authenticated public API passthrough",
456
- " embed-keys Publishable widget keys",
457
- " caps Spend caps and monthly budget",
458
- " appearance Agent appearance theme",
459
- " schedules Recurring agent runs",
460
- " directories Agent-scoped kartas, users, sessions",
461
- " webhooks Event delivery endpoints",
462
- "",
463
- "Utilities",
464
- " usage · doctor · update · completion",
465
584
  "",
466
585
  "Golden path: dharta login -> create -> setup -> dev -> deploy -> open",
467
- "Run `karta <command> --help` for details. Docs: https://docs.dharta.ai",
586
+ "Run `dharta <command> --help` for details. Docs: https://docs.dharta.ai",
468
587
  ].join("\n"),
469
588
  )
470
589
  .action(helpOnlyAction("dharta"));
@@ -477,25 +596,23 @@ export function buildProgram(): Command {
477
596
  .option("--with-token", "Read an API key from stdin instead (CI, air-gapped)")
478
597
  .option("--base-url <url>", "Override the API base URL")
479
598
  .option("--git-base-url <url>", "Override the git server base URL")
480
- .option("--profile <name>", "Named profile (default: default)")
481
599
  .action(
482
600
  async (
483
601
  opts: {
484
602
  withToken?: boolean;
485
603
  baseUrl?: string;
486
604
  gitBaseUrl?: string;
487
- profile?: string;
488
605
  },
489
606
  cmd: Command,
490
607
  ) => {
491
608
  if (opts.baseUrl !== undefined) normalizeHttpUrl(opts.baseUrl, "--base-url");
492
609
  if (opts.gitBaseUrl !== undefined) normalizeHttpUrl(opts.gitBaseUrl, "--git-base-url");
493
- if (opts.profile !== undefined) normalizeNonBlank(opts.profile, "--profile");
494
610
  const json = isJson(cmd);
495
611
  const result = await login({
496
612
  baseUrl: opts.baseUrl,
497
613
  gitBaseUrl: opts.gitBaseUrl,
498
- profile: opts.profile,
614
+ // The global --profile names the profile this login writes.
615
+ profile: requestedProfileName(),
499
616
  apiKey: opts.withToken ? await readStdin() : undefined,
500
617
  // In JSON mode the device-flow narration (code, approve URL)
501
618
  // still has to reach the human - on stderr.
@@ -536,11 +653,14 @@ export function buildProgram(): Command {
536
653
  });
537
654
  auth
538
655
  .command("logout")
539
- .description("Remove the stored credential (the key stays revocable on the dashboard).")
540
- .option("--profile <name>", "Profile to remove (default: the default profile)")
541
- .action(async (opts: { profile?: string }, cmd: Command) => {
542
- if (isJson(cmd)) printJson(await authLogoutData(opts.profile));
543
- else console.log(await authLogout(opts.profile));
656
+ .description(
657
+ "Remove the stored credential (the key stays revocable on the dashboard). Removes the default profile unless --profile names another.",
658
+ )
659
+ .action(async (_opts: unknown, cmd: Command) => {
660
+ // The global --profile names the profile this removes.
661
+ const profile = requestedProfileName();
662
+ if (isJson(cmd)) printJson(await authLogoutData(profile));
663
+ else console.log(await authLogout(profile));
544
664
  });
545
665
 
546
666
  program
@@ -598,7 +718,7 @@ export function buildProgram(): Command {
598
718
  ) => {
599
719
  const json = isJson(cmd);
600
720
  if (json && opts.login) {
601
- throw new Error("--login is not supported with `dharta setup --json`. Run `dharta setup --json` first, then `dharta login`.");
721
+ throw new CliUsageError("--login is not supported with `dharta setup --json`. Run `dharta setup --json` first, then `dharta login`.");
602
722
  }
603
723
  const interactive = !json && !opts.yes && process.stdin.isTTY;
604
724
  const rl = interactive
@@ -665,7 +785,7 @@ export function buildProgram(): Command {
665
785
  }, cmd: Command) => {
666
786
  validateAuditLogInput(opts);
667
787
  if (opts.csv && isJson(cmd)) {
668
- throw new Error("Use either --csv or --json for audit-log, not both.");
788
+ throw new CliUsageError("Use either --csv or --json for audit-log, not both.");
669
789
  }
670
790
  const ctx = await buildContext();
671
791
  if (isJson(cmd)) printJson(await auditLogData(ctx, opts));
@@ -717,7 +837,10 @@ export function buildProgram(): Command {
717
837
 
718
838
  program
719
839
  .command("api [method] [path]")
720
- .description("Call an allowlisted public JSON API endpoint with the active profile.")
840
+ .description(
841
+ "Call any public JSON API endpoint with the active profile. Unstable escape hatch: the curated commands are the stable contract, and this passthrough follows the API. " +
842
+ "Reachable paths start with /api/, /me, /version, /api_keys, /model_connections, or /agent_templates; what the request may do is decided server-side by the key's scopes.",
843
+ )
721
844
  .option("-F, --field <key=value>", "Add a JSON-coerced field to the request body", collect, [])
722
845
  .option("-f, --raw-field <key=value>", "Add a string field to the request body", collect, [])
723
846
  .option("-H, --header <name:value>", "Add a safe request header", collect, [])
@@ -782,47 +905,195 @@ export function buildProgram(): Command {
782
905
  if (isJson(cmd)) printJson(await keysRevokeData(ctx, id));
783
906
  else console.log(await keysRevoke(ctx, id));
784
907
  });
785
-
786
- const modelKeys = program.command("model-keys").description("Manage BYOK provider credentials.").action(helpOnlyAction("dharta model-keys"));
787
- modelKeys
908
+ keys
909
+ .command("rotate <id>")
910
+ .description("Replace a key with a new one carrying the same scopes, then retire the original.")
911
+ .addOption(
912
+ choiceOption(
913
+ "--grace-period <window>",
914
+ "How long the rotated key keeps working (default 24h)",
915
+ KEY_ROTATION_GRACE_PERIODS,
916
+ ),
917
+ )
918
+ .action(async (id: string, opts: { gracePeriod?: string }, cmd: Command) => {
919
+ validateKeysRotateInput(id, opts);
920
+ const ctx = await buildContext();
921
+ if (isJson(cmd)) printJson(await keysRotateData(ctx, id, opts));
922
+ else console.log(await keysRotate(ctx, id, opts));
923
+ });
924
+ keys
925
+ .command("disable <id>")
926
+ .description("Suspend an API key without revoking it.")
927
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
928
+ normalizeNonBlank(id, "API key id");
929
+ const ctx = await buildContext();
930
+ if (isJson(cmd)) printJson(await keysDisableData(ctx, id));
931
+ else console.log(await keysDisable(ctx, id));
932
+ });
933
+ keys
934
+ .command("enable <id>")
935
+ .description("Return a disabled API key to service.")
936
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
937
+ normalizeNonBlank(id, "API key id");
938
+ const ctx = await buildContext();
939
+ if (isJson(cmd)) printJson(await keysEnableData(ctx, id));
940
+ else console.log(await keysEnable(ctx, id));
941
+ });
942
+ keys
943
+ .command("bulk")
944
+ .description("Apply one state change to several API keys atomically.")
945
+ .addArgument(choiceArgument("<action>", "state change to apply", KEY_BULK_ACTIONS))
946
+ .argument("<id...>", "API key ids")
947
+ .action(async (action: string, ids: string[], _opts: unknown, cmd: Command) => {
948
+ validateKeysBulkInput(action, ids);
949
+ const ctx = await buildContext();
950
+ if (isJson(cmd)) printJson(await keysBulkData(ctx, action, ids));
951
+ else console.log(await keysBulk(ctx, action, ids));
952
+ });
953
+
954
+ const models = program
955
+ .command("models")
956
+ .description("Manage model configuration and authentication.")
957
+ .action(helpOnlyAction("dharta models"));
958
+ const modelConnections = models
959
+ .command("auth")
960
+ .description("Manage model provider connections.")
961
+ .action(helpOnlyAction("dharta models auth"));
962
+ modelConnections
788
963
  .command("list")
789
964
  .description("List model provider credentials.")
790
965
  .action(async (_opts: unknown, cmd: Command) => {
791
966
  const ctx = await buildContext();
792
- if (isJson(cmd)) printJson(await modelKeysListData(ctx));
793
- else console.log(await modelKeysList(ctx));
967
+ if (isJson(cmd)) printJson(await modelConnectionsListData(ctx));
968
+ else console.log(await modelConnectionsList(ctx));
794
969
  });
795
- modelKeys
970
+ modelConnections
796
971
  .command("create")
797
972
  .description("Store an encrypted model provider credential.")
798
- .addArgument(choiceArgument("<provider>", "provider", ["anthropic", "openai", "bedrock", "vertex", "openrouter", "xai", "google"]))
973
+ .addArgument(choiceArgument("<provider>", "provider", MODEL_CONNECTION_PROVIDERS))
799
974
  .argument("<name>", "connector name")
800
975
  .option("--key <plaintext>", "Provider API key plaintext")
801
976
  .option("--key-stdin", "Read provider API key plaintext from stdin")
802
- .addOption(choiceOption("--auth-type <type>", "api_key or oauth_subscription", MODEL_KEY_AUTH_TYPES))
977
+ .option("--slug <slug>", "Connector slug used by --connector and model references (default: derived from the name)")
803
978
  .option("--endpoint-url <url>", "Provider endpoint URL override")
804
979
  .addOption(choiceOption("--api-format <format>", "Explicit provider API format", MODEL_API_FORMATS))
805
- .action(async (provider: ModelKeyProvider, name: string, opts: {
980
+ .action(async (provider: ModelConnectionProvider, name: string, opts: {
806
981
  key?: string;
807
982
  keyStdin?: boolean;
808
- authType?: string;
983
+ slug?: string;
809
984
  endpointUrl?: string;
810
985
  apiFormat?: string;
811
986
  }, cmd: Command) => {
812
- validateModelKeysCreateOptions(provider, name, opts);
813
- const preparedOpts = await prepareModelKeysCreateOptions(provider, name, opts);
987
+ validateModelConnectionsCreateOptions(provider, name, opts);
988
+ const preparedOpts = await prepareModelConnectionsCreateOptions(provider, name, opts);
989
+ const ctx = await buildContext();
990
+ if (isJson(cmd)) printJson(await modelConnectionsCreateData(ctx, provider, name, preparedOpts));
991
+ else console.log(await modelConnectionsCreate(ctx, provider, name, preparedOpts));
992
+ });
993
+ modelConnections
994
+ .command("rotate <id|slug>")
995
+ .description("Replace a connector's stored credential, keeping its slug and every selection that names it.")
996
+ .option("--key <plaintext>", "New provider API key plaintext")
997
+ .option("--key-stdin", "Read the new provider API key plaintext from stdin")
998
+ .option("--name <name>", "Rename the connector while rotating it")
999
+ .action(async (reference: string, opts: ModelConnectionRotateOptions, cmd: Command) => {
1000
+ validateModelConnectionsRotateOptions(reference, opts);
1001
+ const preparedOpts = await prepareModelConnectionsRotateOptions(reference, opts);
1002
+ const ctx = await buildContext();
1003
+ if (isJson(cmd)) printJson(await modelConnectionsRotateData(ctx, reference, preparedOpts));
1004
+ else console.log(await modelConnectionsRotate(ctx, reference, preparedOpts));
1005
+ });
1006
+ modelConnections
1007
+ .command("models <id|slug>")
1008
+ .description("Show the models a connector serves, or replace its enabled set and default.")
1009
+ .option("--enable <models>", "Comma-separated model ids to enable; this replaces the whole enabled set")
1010
+ .option("--enable-stdin", "Read the model ids to enable from stdin")
1011
+ .option("--default <model>", "Connector default model; must be in the enabled set")
1012
+ .option("--clear-default", "Leave the connector with no default model")
1013
+ .action(async (reference: string, opts: ModelConnectionModelsOptions, cmd: Command) => {
1014
+ validateModelConnectionsModelsOptions(reference, opts);
1015
+ if (!modelConnectionsModelsWritesOffers(opts)) {
1016
+ validateModelConnectionReference(reference);
1017
+ const readCtx = await buildContext();
1018
+ if (isJson(cmd)) printJson(await modelConnectionsModelsData(readCtx, reference));
1019
+ else console.log(await modelConnectionsModels(readCtx, reference));
1020
+ return;
1021
+ }
1022
+ const preparedOpts = await prepareModelConnectionsModelsOptions(reference, opts);
814
1023
  const ctx = await buildContext();
815
- if (isJson(cmd)) printJson(await modelKeysCreateData(ctx, provider, name, preparedOpts));
816
- else console.log(await modelKeysCreate(ctx, provider, name, preparedOpts));
1024
+ if (isJson(cmd)) printJson(await modelConnectionsConfigureModelsData(ctx, reference, preparedOpts));
1025
+ else console.log(await modelConnectionsConfigureModels(ctx, reference, preparedOpts));
817
1026
  });
818
- modelKeys
819
- .command("revoke <id>")
1027
+ modelConnections
1028
+ .command("revoke <id|slug>")
820
1029
  .description("Revoke a model provider credential.")
821
- .action(async (id: string, _opts: unknown, cmd: Command) => {
822
- validateModelKeyId(id);
1030
+ .action(async (reference: string, _opts: unknown, cmd: Command) => {
1031
+ validateModelConnectionReference(reference);
823
1032
  const ctx = await buildContext();
824
- if (isJson(cmd)) printJson(await modelKeysRevokeData(ctx, id));
825
- else console.log(await modelKeysRevoke(ctx, id));
1033
+ if (isJson(cmd)) printJson(await modelConnectionsRevokeData(ctx, reference));
1034
+ else console.log(await modelConnectionsRevoke(ctx, reference));
1035
+ });
1036
+
1037
+ const org = program
1038
+ .command("org")
1039
+ .description("Manage organization settings.")
1040
+ .action(helpOnlyAction("dharta org"));
1041
+ const orgModel = org
1042
+ .command("model")
1043
+ .description("Show the organization connector order, model reference, parameters, and what they resolve to.")
1044
+ .action(async (_opts: unknown, cmd: Command) => {
1045
+ const ctx = await buildContext();
1046
+ if (isJson(cmd)) printJson(await orgModelShowData(ctx));
1047
+ else console.log(await orgModelShow(ctx));
1048
+ });
1049
+ orgModel
1050
+ .command("set")
1051
+ .description("Update the organization connector order, model reference, endpoint override, and portable model parameters.")
1052
+ .option("--connector <slugs>", "Ordered connector slugs from `dharta models auth list`, or an empty string to unset")
1053
+ .option("--model-ref <reference>", "Model reference: <model>, connector:<slug>/<model>, or an empty string to unset")
1054
+ .option("--endpoint-url <url>", "Public HTTPS endpoint to call instead of the provider's standard API, or an empty string to clear")
1055
+ .addOption(choiceOption("--endpoint-kind <kind>", "Wire protocol for --endpoint-url", ORG_ENDPOINT_KINDS))
1056
+ .option("--temperature <number>", "Sampling temperature from 0 to 2")
1057
+ .option("--clear-temperature", "Clear the organization temperature")
1058
+ .option("--top-p <number>", "Nucleus sampling probability above 0 and at most 1")
1059
+ .option("--clear-top-p", "Clear the organization top-p")
1060
+ .option("--max-output-tokens <number>", "Positive output token limit")
1061
+ .option("--clear-max-output-tokens", "Clear the organization output token limit")
1062
+ .addOption(choiceOption("--reasoning-effort <level>", "Reasoning effort", ["low", "medium", "high"]))
1063
+ .option("--clear-reasoning-effort", "Clear the organization reasoning effort")
1064
+ .action(async (opts: OrgModelSetOptions, cmd: Command) => {
1065
+ validateOrgModelSetOptions(opts);
1066
+ const ctx = await buildContext();
1067
+ if (isJson(cmd)) printJson(await orgModelSetData(ctx, opts));
1068
+ else console.log(await orgModelSet(ctx, opts));
1069
+ });
1070
+
1071
+ org
1072
+ .command("rename <name>")
1073
+ .description("Change the organization display name. The slug never changes.")
1074
+ .action(async (name: string, _opts: unknown, cmd: Command) => {
1075
+ normalizeOrganizationName(name);
1076
+ const ctx = await buildContext();
1077
+ if (isJson(cmd)) printJson(await orgRenameData(ctx, name));
1078
+ else console.log(await orgRename(ctx, name));
1079
+ });
1080
+ org
1081
+ .command("support-access <state>")
1082
+ .description(`Allow or block Dharta support access to session content. ${STATE_HELP}`)
1083
+ .action(async (state: string, _opts: unknown, cmd: Command) => {
1084
+ const allowed = parseOnOff(state, "state");
1085
+ const ctx = await buildContext();
1086
+ if (isJson(cmd)) printJson(await orgSupportAccessData(ctx, allowed));
1087
+ else console.log(await orgSupportAccess(ctx, allowed));
1088
+ });
1089
+ org
1090
+ .command("transcript-access <state>")
1091
+ .description(`Allow or block API keys from reading end-user transcripts. ${STATE_HELP}`)
1092
+ .action(async (state: string, _opts: unknown, cmd: Command) => {
1093
+ const enabled = parseOnOff(state, "state");
1094
+ const ctx = await buildContext();
1095
+ if (isJson(cmd)) printJson(await orgTranscriptAccessData(ctx, enabled));
1096
+ else console.log(await orgTranscriptAccess(ctx, enabled));
826
1097
  });
827
1098
 
828
1099
  const members = program.command("members").description("Manage organization members.").action(helpOnlyAction("dharta members"));
@@ -840,7 +1111,7 @@ export function buildProgram(): Command {
840
1111
  .addOption(choiceOption("--role <role>", "owner, admin, developer, billing, or viewer", MEMBER_ROLES))
841
1112
  .action(async (email: string, opts: { role?: string }, cmd: Command) => {
842
1113
  if (opts.role === undefined) {
843
- throw new Error("--role is required. Usage: dharta members invite <email> --role <role>");
1114
+ throw new CliUsageError("--role is required. Usage: dharta members invite <email> --role <role>");
844
1115
  }
845
1116
  validateMembersInviteInput(email);
846
1117
  const ctx = await buildContext();
@@ -853,7 +1124,7 @@ export function buildProgram(): Command {
853
1124
  .addOption(choiceOption("--role <role>", "owner, admin, developer, billing, or viewer", MEMBER_ROLES))
854
1125
  .action(async (id: string, opts: { role?: string }, cmd: Command) => {
855
1126
  if (opts.role === undefined) {
856
- throw new Error("--role is required. Usage: dharta members set-role <id> --role <role>");
1127
+ throw new CliUsageError("--role is required. Usage: dharta members set-role <id> --role <role>");
857
1128
  }
858
1129
  parsePositiveInteger(id, "member id");
859
1130
  const ctx = await buildContext();
@@ -869,6 +1140,35 @@ export function buildProgram(): Command {
869
1140
  if (isJson(cmd)) printJson(await membersRemoveData(ctx, id));
870
1141
  else console.log(await membersRemove(ctx, id));
871
1142
  });
1143
+ members
1144
+ .command("revoke-invitation <id>")
1145
+ .description("Revoke a pending invitation.")
1146
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1147
+ parsePositiveInteger(id, "invitation id");
1148
+ const ctx = await buildContext();
1149
+ if (isJson(cmd)) printJson(await membersRevokeInvitationData(ctx, id));
1150
+ else console.log(await membersRevokeInvitation(ctx, id));
1151
+ });
1152
+ members
1153
+ .command("resend-invitation <id>")
1154
+ .description("Re-send a pending invitation, keeping the original link.")
1155
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1156
+ parsePositiveInteger(id, "invitation id");
1157
+ const ctx = await buildContext();
1158
+ if (isJson(cmd)) printJson(await membersResendInvitationData(ctx, id));
1159
+ else console.log(await membersResendInvitation(ctx, id));
1160
+ });
1161
+
1162
+ const repos = program.command("repos").description("Manage hosted source repositories.").action(helpOnlyAction("dharta repos"));
1163
+ repos
1164
+ .command("disconnect-github <slug>")
1165
+ .description("Stop a GitHub repository from producing releases. Live releases are untouched.")
1166
+ .action(async (slug: string, _opts: unknown, cmd: Command) => {
1167
+ validateAgentSlug(slug, "source repo slug");
1168
+ const ctx = await buildContext();
1169
+ if (isJson(cmd)) printJson(await reposDisconnectGithubData(ctx, slug));
1170
+ else console.log(await reposDisconnectGithub(ctx, slug));
1171
+ });
872
1172
 
873
1173
  const kartas = program.command("kartas").description("Manage durable kartas and virtual employees.").action(helpOnlyAction("dharta kartas"));
874
1174
  kartas
@@ -993,12 +1293,31 @@ export function buildProgram(): Command {
993
1293
  .addOption(choiceOption("--role <role>", "viewer, participant, or manager", DHARTA_MEMBER_ROLES))
994
1294
  .action(async (id: string, membershipId: string, opts: { role?: string }, cmd: Command) => {
995
1295
  if (opts.role === undefined) {
996
- throw new Error("--role is required. Usage: dharta kartas set-role <id> <membership-id> --role <role>");
1296
+ throw new CliUsageError("--role is required. Usage: dharta kartas set-role <id> <membership-id> --role <role>");
997
1297
  }
998
1298
  const ctx = await buildContext();
999
1299
  if (isJson(cmd)) printJson(await kartaMembersSetRoleData(ctx, id, membershipId, opts.role));
1000
1300
  else console.log(await kartaMembersSetRole(ctx, id, membershipId, opts.role));
1001
1301
  });
1302
+ kartas
1303
+ .command("share <id>")
1304
+ .description("Mint a no-sign-in link to a karta and reveal it once.")
1305
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1306
+ normalizeNonBlank(id, "karta id");
1307
+ const ctx = await buildContext();
1308
+ if (isJson(cmd)) printJson(await kartaShareData(ctx, id));
1309
+ else console.log(await kartaShare(ctx, id));
1310
+ });
1311
+ kartas
1312
+ .command("unshare <id> <share-id>")
1313
+ .description("Revoke a karta share link.")
1314
+ .action(async (id: string, shareId: string, _opts: unknown, cmd: Command) => {
1315
+ normalizeNonBlank(id, "karta id");
1316
+ normalizeNonBlank(shareId, "share id");
1317
+ const ctx = await buildContext();
1318
+ if (isJson(cmd)) printJson(await kartaUnshareData(ctx, id, shareId));
1319
+ else console.log(await kartaUnshare(ctx, id, shareId));
1320
+ });
1002
1321
  kartas
1003
1322
  .command("revoke <id> <membership-id>")
1004
1323
  .description("Remove a person's access to a dharta.")
@@ -1210,7 +1529,7 @@ export function buildProgram(): Command {
1210
1529
  else console.log(await appearanceGet(ctx, resolved));
1211
1530
  });
1212
1531
  appearanceOpts(appearance.command("set"))
1213
- .description("Merge appearance JSON from a local file.")
1532
+ .description("Merge appearance JSON from a local file. A token set to null in the file is cleared and falls back to the preset.")
1214
1533
  .option("--file <file>", "JSON file containing appearance tokens")
1215
1534
  .action(async (_opts: { agent?: string; project?: string; dir?: string; file?: string }, cmd: Command) => {
1216
1535
  const opts = commandOpts<{ agent?: string; project?: string; dir?: string; file?: string }>(cmd);
@@ -1342,6 +1661,232 @@ export function buildProgram(): Command {
1342
1661
  else console.log(await directorySessions(ctx, resolved));
1343
1662
  });
1344
1663
 
1664
+ // `fleet` mirrors the fleet console's write side. Reads live under
1665
+ // `directories` and `analytics`; everything here changes something.
1666
+ const fleet = program.command("fleet").description("Run fleet-wide changes, triage, and exports.").action(helpOnlyAction("dharta fleet"));
1667
+
1668
+ const fleetOperations = fleet.command("operations").description("Preview and apply bulk fleet changes.").action(helpOnlyAction("dharta fleet operations"));
1669
+ fleetOperations
1670
+ .command("create")
1671
+ .description("Freeze a selection into a draft operation. Nothing is applied until you confirm it.")
1672
+ .option("--action <key>", "Action to run, for example set_lifecycle or pause_serving")
1673
+ .option("--entity-type <type>", `Entity type (${FLEET_ENTITY_TYPES.join(", ")})`)
1674
+ .option("--selection-mode <mode>", `How targets are chosen (${FLEET_SELECTION_MODES.join(", ")})`)
1675
+ .option("--id <id...>", "Target row id; repeat for more")
1676
+ .option("--saved-view <id>", "Take the targets from a saved view")
1677
+ .option("--filter <query>", "Take the targets from a catalog filter")
1678
+ .option("--param <key=value...>", "Action parameter; repeat for more")
1679
+ .action(async (opts: OperationCreateOptions, cmd: Command) => {
1680
+ const ctx = await buildContext();
1681
+ if (isJson(cmd)) printJson(await fleetOperationCreateData(ctx, opts));
1682
+ else console.log(await fleetOperationCreate(ctx, opts));
1683
+ });
1684
+ fleetOperations
1685
+ .command("confirm <id>")
1686
+ .description("Apply a draft operation. Needs fleet:operations:write.")
1687
+ .option("--phrase <phrase>", "The confirmation phrase the draft returned")
1688
+ .action(async (id: string, opts: { phrase?: string }, cmd: Command) => {
1689
+ const ctx = await buildContext();
1690
+ if (isJson(cmd)) printJson(await fleetOperationConfirmData(ctx, id, opts.phrase));
1691
+ else console.log(await fleetOperationConfirm(ctx, id, opts.phrase));
1692
+ });
1693
+ fleetOperations
1694
+ .command("cancel <id>")
1695
+ .description("Stop an operation's remaining targets. Needs fleet:operations:write.")
1696
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1697
+ const ctx = await buildContext();
1698
+ if (isJson(cmd)) printJson(await fleetOperationCancelData(ctx, id));
1699
+ else console.log(await fleetOperationCancel(ctx, id));
1700
+ });
1701
+ fleetOperations
1702
+ .command("retry <id>")
1703
+ .description("Requeue only the failed targets. Needs fleet:operations:write.")
1704
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1705
+ const ctx = await buildContext();
1706
+ if (isJson(cmd)) printJson(await fleetOperationRetryData(ctx, id));
1707
+ else console.log(await fleetOperationRetry(ctx, id));
1708
+ });
1709
+
1710
+ const fleetRollouts = fleet.command("rollouts").description("Drive a staged release rollout.").action(helpOnlyAction("dharta fleet rollouts"));
1711
+ // Every rollout verb needs fleet:operations:write, not just the two the
1712
+ // console gates with a step-up: pausing or resuming a release mid-flight
1713
+ // moves live releases the same way advancing it does.
1714
+ for (const [verb, description] of [
1715
+ ["advance", "Move the rollout to its next ring. Needs fleet:operations:write."],
1716
+ ["pause", "Hold the rollout where it is. Needs fleet:operations:write."],
1717
+ ["resume", "Resume a paused rollout. Needs fleet:operations:write."],
1718
+ [
1719
+ "evaluate",
1720
+ "Run the health evaluation now instead of waiting for the window. Needs fleet:operations:write.",
1721
+ ],
1722
+ ] as const) {
1723
+ fleetRollouts
1724
+ .command(`${verb} <id>`)
1725
+ .description(description)
1726
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1727
+ const ctx = await buildContext();
1728
+ if (isJson(cmd)) printJson(await fleetRolloutData(ctx, verb, id));
1729
+ else console.log(await fleetRollout(ctx, verb, id));
1730
+ });
1731
+ }
1732
+ fleetRollouts
1733
+ .command("rollback <id>")
1734
+ .description("Revert every target to the previous release. Needs fleet:operations:write.")
1735
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1736
+ const ctx = await buildContext();
1737
+ if (isJson(cmd)) printJson(await fleetRolloutRollbackData(ctx, id));
1738
+ else console.log(await fleetRolloutRollback(ctx, id));
1739
+ });
1740
+
1741
+ const fleetFindings = fleet.command("findings").description("Triage anomaly findings.").action(helpOnlyAction("dharta fleet findings"));
1742
+ for (const [verb, description] of [
1743
+ ["acknowledge", "Take ownership of a finding."],
1744
+ ["resolve", "Close a finding."],
1745
+ ["reopen", "Reopen a resolved or snoozed finding."],
1746
+ ["promote", "Raise a finding to an incident."],
1747
+ ] as const) {
1748
+ fleetFindings
1749
+ .command(`${verb} <id>`)
1750
+ .description(description)
1751
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1752
+ const ctx = await buildContext();
1753
+ if (isJson(cmd)) printJson(await fleetFindingData(ctx, verb, id));
1754
+ else console.log(await fleetFinding(ctx, verb, id));
1755
+ });
1756
+ }
1757
+ fleetFindings
1758
+ .command("snooze <id>")
1759
+ .description("Hide a finding until the window is up.")
1760
+ .option("--duration <window>", `Snooze window (${FLEET_SNOOZE_DURATIONS.join(", ")})`)
1761
+ .action(async (id: string, opts: SnoozeOptions, cmd: Command) => {
1762
+ const ctx = await buildContext();
1763
+ if (isJson(cmd)) printJson(await fleetFindingSnoozeData(ctx, id, opts));
1764
+ else console.log(await fleetFindingSnooze(ctx, id, opts));
1765
+ });
1766
+ fleetFindings
1767
+ .command("classify <id>")
1768
+ .description("Record what a finding turned out to be.")
1769
+ .option("--as <classification>", "Classification to record")
1770
+ .action(async (id: string, opts: ClassifyOptions, cmd: Command) => {
1771
+ const ctx = await buildContext();
1772
+ if (isJson(cmd)) printJson(await fleetFindingClassifyData(ctx, id, opts));
1773
+ else console.log(await fleetFindingClassify(ctx, id, opts));
1774
+ });
1775
+ fleetFindings
1776
+ .command("assign <id>")
1777
+ .description("Assign a finding to a member, or clear the owner.")
1778
+ .option("--membership-id <id>", "Organization membership id to assign")
1779
+ .option("--unassign", "Clear the current owner")
1780
+ .action(async (id: string, opts: AssignOptions, cmd: Command) => {
1781
+ const ctx = await buildContext();
1782
+ if (isJson(cmd)) printJson(await fleetFindingAssignData(ctx, id, opts));
1783
+ else console.log(await fleetFindingAssign(ctx, id, opts));
1784
+ });
1785
+ fleetFindings
1786
+ .command("update <id>")
1787
+ .description("Set or clear a finding's runbook link.")
1788
+ .option("--runbook-url <url>", "Runbook URL; pass an empty string to clear")
1789
+ .action(async (id: string, opts: RunbookOptions, cmd: Command) => {
1790
+ const ctx = await buildContext();
1791
+ if (isJson(cmd)) printJson(await fleetFindingUpdateData(ctx, id, opts));
1792
+ else console.log(await fleetFindingUpdate(ctx, id, opts));
1793
+ });
1794
+
1795
+ const fleetViews = fleet.command("views").description("Manage saved fleet views.").action(helpOnlyAction("dharta fleet views"));
1796
+ const viewWriteOpts = (cmd: ReturnType<typeof fleetViews.command>) =>
1797
+ cmd
1798
+ .option("--entity-type <type>", "Entity type the view lists")
1799
+ .option("--name <name>", "View name")
1800
+ .option("--definition <json>", "Filters, columns and sort, as a JSON object")
1801
+ .option("--description <text>", "What the view is for")
1802
+ .option("--visibility <level>", `Who can see it (${FLEET_VIEW_VISIBILITIES.join(", ")})`)
1803
+ .option("--team-id <id>", "Owning team, for team visibility")
1804
+ .option("--organization-default", "Make it the organization's default view");
1805
+ viewWriteOpts(fleetViews.command("create"))
1806
+ .description("Create a saved view.")
1807
+ .action(async (opts: ViewWriteOptions, cmd: Command) => {
1808
+ const ctx = await buildContext();
1809
+ if (isJson(cmd)) printJson(await fleetViewCreateData(ctx, opts));
1810
+ else console.log(await fleetViewCreate(ctx, opts));
1811
+ });
1812
+ viewWriteOpts(fleetViews.command("update <id>"))
1813
+ .description("Change a saved view.")
1814
+ .action(async (id: string, opts: ViewWriteOptions, cmd: Command) => {
1815
+ const ctx = await buildContext();
1816
+ if (isJson(cmd)) printJson(await fleetViewUpdateData(ctx, id, opts));
1817
+ else console.log(await fleetViewUpdate(ctx, id, opts));
1818
+ });
1819
+ fleetViews
1820
+ .command("delete <id>")
1821
+ .alias("rm")
1822
+ .description("Delete a saved view.")
1823
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1824
+ const ctx = await buildContext();
1825
+ if (isJson(cmd)) printJson(await fleetViewDeleteData(ctx, id));
1826
+ else console.log(await fleetViewDelete(ctx, id));
1827
+ });
1828
+ fleetViews
1829
+ .command("favorite <id>")
1830
+ .description("Favorite a saved view for the key's owner.")
1831
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1832
+ const ctx = await buildContext();
1833
+ if (isJson(cmd)) printJson(await fleetViewFavoriteData(ctx, id));
1834
+ else console.log(await fleetViewFavorite(ctx, id));
1835
+ });
1836
+ fleetViews
1837
+ .command("unfavorite <id>")
1838
+ .description("Remove a saved view from the key owner's favorites.")
1839
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1840
+ const ctx = await buildContext();
1841
+ if (isJson(cmd)) printJson(await fleetViewUnfavoriteData(ctx, id));
1842
+ else console.log(await fleetViewUnfavorite(ctx, id));
1843
+ });
1844
+
1845
+ const fleetAnalytics = fleet.command("analytics").description("Save and unpin Analytics charts.").action(helpOnlyAction("dharta fleet analytics"));
1846
+ fleetAnalytics
1847
+ .command("save")
1848
+ .description("Save an explorer chart and pin it to your Analytics list.")
1849
+ .option("--name <name>", "Chart name")
1850
+ .option("--metric <metric>", "Metric to chart. Defaults to turns.")
1851
+ .option("--description <text>", "What the chart shows")
1852
+ .option("--range <range>", "Time range, for example 30d")
1853
+ .option("--starts-at <timestamp>", "Custom range start")
1854
+ .option("--ends-at <timestamp>", "Custom range end")
1855
+ .option("--scope-kind <kind>", "Scope the chart to a kind of row")
1856
+ .option("--scope-ids <ids>", "Comma-separated scope ids")
1857
+ .option("--bucket <bucket>", "Time bucket, for example day")
1858
+ .option("--aggregation <aggregation>", "How values are combined")
1859
+ .option("--comparison <comparison>", "Comparison series to draw")
1860
+ .option("--comparison-saved-view <id>", "Saved cohort to compare against")
1861
+ .option("--breakdown <field...>", "Break the series down by a field; repeat for more")
1862
+ .option("--event <type...>", "Event type to include; repeat for more")
1863
+ .option("--filter <key=value...>", "Filter; repeat for more")
1864
+ .action(async (opts: AnalyticsSaveOptions, cmd: Command) => {
1865
+ const ctx = await buildContext();
1866
+ if (isJson(cmd)) printJson(await fleetAnalyticsSaveData(ctx, opts));
1867
+ else console.log(await fleetAnalyticsSave(ctx, opts));
1868
+ });
1869
+ fleetAnalytics
1870
+ .command("unpin <id>")
1871
+ .description("Remove a chart from your Analytics list. The saved view survives.")
1872
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1873
+ const ctx = await buildContext();
1874
+ if (isJson(cmd)) printJson(await fleetAnalyticsUnpinData(ctx, id));
1875
+ else console.log(await fleetAnalyticsUnpin(ctx, id));
1876
+ });
1877
+
1878
+ const fleetExports = fleet.command("exports").description("Export catalog rows as CSV.").action(helpOnlyAction("dharta fleet exports"));
1879
+ fleetExports
1880
+ .command("create")
1881
+ .description("Write the named rows to stdout as CSV.")
1882
+ .option("--entity-type <type>", `Entity type (${FLEET_ENTITY_TYPES.join(", ")})`)
1883
+ .option("--id <id...>", "Row id to export; repeat for more")
1884
+ .action(async (opts: ExportCreateOptions, cmd: Command) => {
1885
+ const ctx = await buildContext();
1886
+ if (isJson(cmd)) printJson(await fleetExportCreateData(ctx, opts));
1887
+ else console.log(await fleetExportCreate(ctx, opts));
1888
+ });
1889
+
1345
1890
  const webhooks = program.command("webhooks").description("Manage webhook endpoints.").action(helpOnlyAction("dharta webhooks"));
1346
1891
  webhooks
1347
1892
  .command("list")
@@ -1384,6 +1929,15 @@ export function buildProgram(): Command {
1384
1929
  if (isJson(cmd)) printJson(await webhooksEnableData(ctx, id));
1385
1930
  else console.log(await webhooksEnable(ctx, id));
1386
1931
  });
1932
+ webhooks
1933
+ .command("test <id>")
1934
+ .description("Send one test event through the real delivery path.")
1935
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1936
+ normalizeNonBlank(id, "Webhook endpoint id");
1937
+ const ctx = await buildContext();
1938
+ if (isJson(cmd)) printJson(await webhooksTestData(ctx, id));
1939
+ else console.log(await webhooksTest(ctx, id));
1940
+ });
1387
1941
  webhooks
1388
1942
  .command("delete <id>")
1389
1943
  .alias("rm")
@@ -1438,6 +1992,7 @@ export function buildProgram(): Command {
1438
1992
  .option("--user <id>", "Simulate a verified end user (per-user memory behavior)")
1439
1993
  .option("--port <port>", "Serve the local agent API on a stable port (for widgets/frontends)")
1440
1994
  .option("--verbose", "Stream agent-runtime output and parity details")
1995
+ .addOption(choiceOption("--model-auth <method>", "Anthropic authentication: api-key or claude", DEV_MODEL_AUTH_VALUES))
1441
1996
  .option("--release <vN>", "Run the exact built tree prod serves (audited archive pull)")
1442
1997
  .addOption(choiceOption("--env <mode>", "Bind prod env vars instead of .env", ["remote"]))
1443
1998
  .option("--include-secrets", "With --env remote: fetch secret values (requires env:secrets:read; audited)")
@@ -1451,6 +2006,7 @@ export function buildProgram(): Command {
1451
2006
  user?: string;
1452
2007
  port?: string;
1453
2008
  verbose?: boolean;
2009
+ modelAuth?: DevModelAuth;
1454
2010
  release?: string;
1455
2011
  env?: string;
1456
2012
  includeSecrets?: boolean;
@@ -1461,7 +2017,7 @@ export function buildProgram(): Command {
1461
2017
  ) => {
1462
2018
  rejectJson(cmd, "dharta dev");
1463
2019
  if (pathArg !== undefined && opts.dir !== undefined) {
1464
- throw new Error("Use either [path] or --dir, not both.");
2020
+ throw new CliUsageError("Use either [path] or --dir, not both.");
1465
2021
  }
1466
2022
  const dir = pathArg !== undefined
1467
2023
  ? normalizeNonBlank(pathArg, "path")
@@ -1470,7 +2026,7 @@ export function buildProgram(): Command {
1470
2026
  : undefined;
1471
2027
  if (opts.release !== undefined) parseReleaseVersion(opts.release, "--release");
1472
2028
  if (opts.includeSecrets && opts.env !== "remote") {
1473
- throw new Error("--include-secrets requires --env remote.");
2029
+ throw new CliUsageError("--include-secrets requires --env remote.");
1474
2030
  }
1475
2031
  validateAgentSelectorOptions({ agent: opts.agent, project: opts.project });
1476
2032
  await devCmd({
@@ -1478,6 +2034,7 @@ export function buildProgram(): Command {
1478
2034
  message: opts.message,
1479
2035
  user: opts.user,
1480
2036
  verbose: opts.verbose,
2037
+ modelAuth: opts.modelAuth,
1481
2038
  port: opts.port ? parsePort(opts.port) : undefined,
1482
2039
  release: opts.release,
1483
2040
  envRemote: opts.env === "remote",
@@ -1488,6 +2045,7 @@ export function buildProgram(): Command {
1488
2045
  );
1489
2046
 
1490
2047
  registerArtifactCommands(program);
2048
+ registerGenuiCommands(program);
1491
2049
 
1492
2050
  const sessionsOpts = <T extends Command>(cmd: T): T =>
1493
2051
  cmd
@@ -1809,9 +2367,8 @@ export function buildProgram(): Command {
1809
2367
  .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1810
2368
  .option("-p, --project <slug>", "Alias of --agent")
1811
2369
  .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1812
- .addOption(choiceOption("--source <source>", "inherit or platform; omit when using --connector-id", AGENT_MODEL_SOURCES))
1813
- .option("--connector-id <id>", "Numeric BYOK connector id from `dharta model-keys list`; omit --source")
1814
- .option("--model <model>", "Provider model id")
2370
+ .option("--connector <slugs>", "Ordered connection slugs from `dharta models auth list`; use karta for the platform connection or an empty string to inherit")
2371
+ .option("--model-ref <reference>", "Model reference: <model>, connector:<slug>/<model>, or an empty string for the connector default")
1815
2372
  .option("--temperature <number>", "Sampling temperature from 0 to 2")
1816
2373
  .option("--clear-temperature", "Inherit temperature")
1817
2374
  .option("--top-p <number>", "Nucleus sampling probability above 0 and at most 1")
@@ -1896,6 +2453,132 @@ export function buildProgram(): Command {
1896
2453
  if (isJson(cmd)) printJson(await agentsWorkspaceAccessSetData(ctx, resolved, patch));
1897
2454
  else console.log(await agentsWorkspaceAccessSet(ctx, resolved, patch));
1898
2455
  });
2456
+ agent
2457
+ .command("rename <name> [slug]")
2458
+ .description("Change an agent's display name; the slug stays as it is.")
2459
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
2460
+ .option("-p, --project <slug>", "Alias of --agent")
2461
+ .option("--dir <dir>", "Agent directory (default: current directory)")
2462
+ .action(async (
2463
+ name: string,
2464
+ slugArg: string | undefined,
2465
+ _opts: { agent?: string; project?: string; dir?: string },
2466
+ cmd: Command,
2467
+ ) => {
2468
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
2469
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
2470
+ const ctx = await buildContext();
2471
+ const resolved = resolvedOpts.agent;
2472
+ if (isJson(cmd)) printJson(await agentsRenameData(ctx, resolved, name));
2473
+ else console.log(await agentsRename(ctx, resolved, name));
2474
+ });
2475
+ agent
2476
+ .command("durable-workspace <state> [slug]")
2477
+ .description(
2478
+ `Enable or disable the durable workspace for an agent's kartas - a cost setting, since durable storage is billed per GB. ${STATE_HELP}`,
2479
+ )
2480
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
2481
+ .option("-p, --project <slug>", "Alias of --agent")
2482
+ .option("--dir <dir>", "Agent directory (default: current directory)")
2483
+ .action(async (
2484
+ state: string,
2485
+ slugArg: string | undefined,
2486
+ _opts: { agent?: string; project?: string; dir?: string },
2487
+ cmd: Command,
2488
+ ) => {
2489
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
2490
+ const enabled = parseOnOff(state, "state");
2491
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
2492
+ const ctx = await buildContext();
2493
+ const resolved = resolvedOpts.agent;
2494
+ if (isJson(cmd)) printJson(await agentsDurableWorkspaceSetData(ctx, resolved, enabled));
2495
+ else console.log(await agentsDurableWorkspaceSet(ctx, resolved, enabled));
2496
+ });
2497
+ agent
2498
+ .command("create [name]")
2499
+ .description(
2500
+ "Create a hosted agent, or link an installed GitHub repository as its source with --github-repo. `dharta create` scaffolds locally instead.",
2501
+ )
2502
+ .option("--slug <slug>", "Agent or source-repo slug (default: derived from the name or repository)")
2503
+ .option(
2504
+ "--github-repo <repo>",
2505
+ "Installed GitHub repository to link as the source of truth, by id or owner/name (see `dharta agent github-repos`)",
2506
+ )
2507
+ .option("--branch <branch>", "Deploy branch for the linked repository; needs --github-repo")
2508
+ .action(async (name: string | undefined, _opts: AgentCreateOptions, cmd: Command) => {
2509
+ const opts = commandOpts<AgentCreateOptions>(cmd);
2510
+ validateAgentCreateInput(name, opts);
2511
+ const ctx = await buildContext();
2512
+ if (isJson(cmd)) printJson(await agentsCreateData(ctx, name, opts));
2513
+ else console.log(await agentsCreate(ctx, name, opts));
2514
+ });
2515
+ agent
2516
+ .command("github-repos")
2517
+ .description("List the GitHub repositories the Dharta App is installed on, with the ids `--github-repo` takes.")
2518
+ .action(async (_opts: Record<string, never>, cmd: Command) => {
2519
+ const ctx = await buildContext();
2520
+ if (isJson(cmd)) printJson(await agentsGithubReposData(ctx));
2521
+ else console.log(await agentsGithubRepos(ctx));
2522
+ });
2523
+ agent
2524
+ .command("create-from-template <template> <name>")
2525
+ .description(
2526
+ "Create a hosted agent from a built-in template and publish its first release. `dharta create` scaffolds locally instead.",
2527
+ )
2528
+ .option("--slug <slug>", "Agent slug (default: derived from the name)")
2529
+ .option("--description <text>", "Short description")
2530
+ .option("--harness <type>", "Harness slug (default: Dharta's curated default)")
2531
+ .option("--harness-version <version>", "Harness version; needs --harness")
2532
+ .action(async (
2533
+ template: string,
2534
+ name: string,
2535
+ _opts: AgentCreateFromTemplateOptions,
2536
+ cmd: Command,
2537
+ ) => {
2538
+ const opts = commandOpts<AgentCreateFromTemplateOptions>(cmd);
2539
+ validateAgentCreateFromTemplateInput(template, name, opts);
2540
+ const ctx = await buildContext();
2541
+ if (isJson(cmd)) printJson(await agentsCreateFromTemplateData(ctx, template, name, opts));
2542
+ else console.log(await agentsCreateFromTemplate(ctx, template, name, opts));
2543
+ });
2544
+ agent
2545
+ .command("redeploy [slug]")
2546
+ .description("Re-sync a GitHub-connected agent and rebuild it from deploy-branch HEAD.")
2547
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
2548
+ .option("-p, --project <slug>", "Alias of --agent")
2549
+ .option("--dir <dir>", "Agent directory (default: current directory)")
2550
+ .action(async (
2551
+ slugArg: string | undefined,
2552
+ _opts: { agent?: string; project?: string; dir?: string },
2553
+ cmd: Command,
2554
+ ) => {
2555
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
2556
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
2557
+ const ctx = await buildContext();
2558
+ const resolved = resolvedOpts.agent;
2559
+ if (isJson(cmd)) printJson(await agentsRedeployData(ctx, resolved));
2560
+ else console.log(await agentsRedeploy(ctx, resolved));
2561
+ });
2562
+ agent
2563
+ .command("retry <version> [slug]")
2564
+ .description("Retry a failed browser-created release; it republishes as the next version.")
2565
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
2566
+ .option("-p, --project <slug>", "Alias of --agent")
2567
+ .option("--dir <dir>", "Agent directory (default: current directory)")
2568
+ .action(async (
2569
+ version: string,
2570
+ slugArg: string | undefined,
2571
+ _opts: { agent?: string; project?: string; dir?: string },
2572
+ cmd: Command,
2573
+ ) => {
2574
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
2575
+ parseVersion(version);
2576
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
2577
+ const ctx = await buildContext();
2578
+ const resolved = resolvedOpts.agent;
2579
+ if (isJson(cmd)) printJson(await agentsRetryReleaseData(ctx, resolved, version));
2580
+ else console.log(await agentsRetryRelease(ctx, resolved, version));
2581
+ });
1899
2582
  agent
1900
2583
  .command("delete [slug]")
1901
2584
  .description("Archive an agent.")
@@ -1943,7 +2626,7 @@ export function buildProgram(): Command {
1943
2626
  // owner of target/config resolution and can narrate legacy inference.
1944
2627
  const mode = rawMode ?? (await inferMode(dir));
1945
2628
  if (opts.reconcileSource && mode !== "git") {
1946
- throw new Error(
2629
+ throw new CliUsageError(
1947
2630
  "--reconcile-source is only valid for git deploys. " +
1948
2631
  "Remove it, or use `--mode git` to reconcile Dharta's hosted source branch.",
1949
2632
  );
@@ -1951,7 +2634,7 @@ export function buildProgram(): Command {
1951
2634
  // git mode's output is the live push sideband, not a payload.
1952
2635
  const json = mode === "folder" && isJson(cmd);
1953
2636
  if (mode === "git" && isJson(cmd)) {
1954
- throw new Error(
2637
+ throw new CliUsageError(
1955
2638
  "--json is only supported for `dharta deploy --mode folder`; git deploy streams git push output. Run `dharta deploy` without --json or pass `--mode folder`.",
1956
2639
  );
1957
2640
  }
@@ -1978,7 +2661,7 @@ export function buildProgram(): Command {
1978
2661
  if (opts.agent !== undefined || opts.project !== undefined) {
1979
2662
  const slug = opts.agent ?? opts.project ?? "";
1980
2663
  if (slug.trim()) validateAgentSlug(slug, opts.agent !== undefined ? "--agent" : "--project");
1981
- throw new Error("`dharta status` is repo-wide. Use `dharta agent show <slug>` for one remote agent, or `dharta status --dir <path>` for a local repo.");
2664
+ throw new CliUsageError("`dharta status` is repo-wide. Use `dharta agent show <slug>` for one remote agent, or `dharta status --dir <path>` for a local repo.");
1982
2665
  }
1983
2666
  validateAgentSelectorOptions(opts);
1984
2667
  let ctx: Awaited<ReturnType<typeof buildContext>> | null = null;
@@ -1986,7 +2669,7 @@ export function buildProgram(): Command {
1986
2669
  try {
1987
2670
  ctx = await buildContext();
1988
2671
  } catch (err) {
1989
- if (!isNotLoggedInError(err)) throw err;
2672
+ if (!isNotAuthenticatedError(err)) throw err;
1990
2673
  authMessage = err instanceof Error ? err.message : String(err);
1991
2674
  }
1992
2675
  if (isJson(cmd)) printJson(await statusData(ctx, { dir: opts.dir, authMessage }));
@@ -2062,26 +2745,151 @@ export function buildProgram(): Command {
2062
2745
  // commands and help-only command groups keep a hidden parser option
2063
2746
  // because they reject --json.
2064
2747
  for (const cmd of allCommands(program)) {
2748
+ const jq = new Option("--jq <expr>", "Filter the JSON result through jq (implies --json)");
2065
2749
  if (hidesJsonHelp(cmd)) {
2066
2750
  cmd.addOption(new Option("--json", "Print the result as JSON (machine-readable)").hideHelp());
2751
+ cmd.addOption(jq.hideHelp());
2067
2752
  } else {
2068
2753
  cmd.option("--json", jsonHelpDescription(cmd));
2754
+ cmd.addOption(jq);
2069
2755
  }
2756
+ // --profile rides the same mechanic: registered on every node so the flag
2757
+ // parses wherever it appears on the line, read back through
2758
+ // optsWithGlobals(). Hidden on the pure command groups, which only print
2759
+ // help and never resolve a credential.
2760
+ const profile = new Option(
2761
+ "--profile <name>",
2762
+ "Profile in ~/.dharta/config.toml to use (env: DHARTA_PROFILE)",
2763
+ );
2764
+ if (HELP_ONLY_COMMAND_PATHS.has(commandPath(cmd)) && cmd.parent !== null) profile.hideHelp();
2765
+ cmd.addOption(profile);
2766
+ // --idempotency-key rides the same mechanic for the same reason: it has to
2767
+ // parse wherever it sits on the line, and one flag has to reach every
2768
+ // request the run makes. Hidden on the help-only groups, which issue none.
2769
+ const idempotencyKey = new Option(
2770
+ "--idempotency-key <key>",
2771
+ "Send this Idempotency-Key on every request the command makes",
2772
+ );
2773
+ if (HELP_ONLY_COMMAND_PATHS.has(commandPath(cmd)) && cmd.parent !== null) idempotencyKey.hideHelp();
2774
+ cmd.addOption(idempotencyKey);
2070
2775
  }
2776
+
2777
+ // One place decides the active profile for the whole run. preAction hooks are
2778
+ // inherited by every subcommand, so this fires exactly once, before the
2779
+ // action, with the flag resolved from wherever it sat on the command line.
2780
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
2781
+ const opts = actionCommand.optsWithGlobals();
2782
+ setRequestedProfile(opts.profile as string | undefined);
2783
+ rejectNonMutatingIdempotencyKey(actionCommand, opts.idempotencyKey as string | undefined);
2784
+ setRequestedIdempotencyKey(opts.idempotencyKey as string | undefined);
2785
+ resetJsonPayload();
2786
+ // Compile the filter before the action runs, so a typo costs nothing.
2787
+ await armJqFilter(opts.jq as string | undefined);
2788
+ });
2789
+
2790
+ // The filtered payload is written after the action resolves. postAction runs
2791
+ // for every entry point (parseAsync from the bin, or straight from a test),
2792
+ // and does not run when the action threw - which is right, because a failed
2793
+ // command has no payload to filter.
2794
+ program.hook("postAction", async () => {
2795
+ await flushJsonPayload();
2796
+ });
2797
+ assignTopLevelHelpGroups(program);
2071
2798
  installMissingArgumentChoiceHints(program);
2072
2799
  installTopLevelUnknownCommandGuard(program);
2800
+ installExitOverride(program);
2073
2801
 
2074
2802
  return program;
2075
2803
  }
2076
2804
 
2077
- function isNotLoggedInError(err: unknown): boolean {
2078
- return err instanceof Error && /^Not logged in\./.test(err.message);
2805
+ // The root help used to carry a hand-written, hand-ordered list of commands in
2806
+ // addHelpText, with commander's own list suppressed - two surfaces to keep in
2807
+ // step, and the list drifted (`files` and `artifact` were never in it).
2808
+ // Commander 15 groups natively, so the groups are declared here, on the tree,
2809
+ // and both `dharta --help` and the generated docs reference read them from it.
2810
+ export const TOP_LEVEL_HELP_GROUPS = [
2811
+ ["Setup and auth", ["login", "auth", "whoami", "create", "setup"]],
2812
+ ["Local and deploy", ["dev", "deploy", "status", "rollback", "logs"]],
2813
+ ["Runtime operations", ["open", "sessions", "session", "ps", "send", "files", "artifact", "schedules"]],
2814
+ ["Agent configuration", ["agent", "env", "appearance", "genui"]],
2815
+ [
2816
+ "Management",
2817
+ [
2818
+ "usage",
2819
+ "analytics",
2820
+ "directories",
2821
+ "fleet",
2822
+ "kartas",
2823
+ "caps",
2824
+ "embed-keys",
2825
+ "webhooks",
2826
+ "audit-log",
2827
+ "keys",
2828
+ "models",
2829
+ "org",
2830
+ "members",
2831
+ "repos",
2832
+ "api",
2833
+ ],
2834
+ ],
2835
+ ["Utilities", ["doctor", "update", "completion"]],
2836
+ ] as const satisfies ReadonlyArray<readonly [string, readonly string[]]>;
2837
+
2838
+ function assignTopLevelHelpGroups(program: Command): void {
2839
+ const order = new Map<string, number>();
2840
+ for (const [group, names] of TOP_LEVEL_HELP_GROUPS) {
2841
+ for (const name of names) {
2842
+ const cmd = program.commands.find((candidate) => candidate.name() === name);
2843
+ if (!cmd) {
2844
+ throw new Error(`Help group '${group}' names '${name}', which is not a top-level command.`);
2845
+ }
2846
+ cmd.helpGroup(`${group}:`);
2847
+ order.set(name, order.size);
2848
+ }
2849
+ }
2850
+ // Commander discovers group headings in registration order, so the array
2851
+ // itself has to be in group order for the help to read top to bottom.
2852
+ // Ungrouped (hidden) commands keep their place at the end. The array is
2853
+ // typed readonly but is the live list commander renders from, so sorting it
2854
+ // is the ordering hook - there is no public setter.
2855
+ (program.commands as Command[]).sort(
2856
+ (a, b) => (order.get(a.name()) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.name()) ?? Number.MAX_SAFE_INTEGER),
2857
+ );
2858
+ }
2859
+
2860
+ // Commander calls process.exit(1) itself for a bad flag, which flattens every
2861
+ // usage mistake into the same code as a failed API call. exitOverride makes it
2862
+ // throw instead, so the single catch in the bin block maps it to EXIT_USAGE.
2863
+ // The setting is per-command, not inherited, so every node gets it.
2864
+ function installExitOverride(program: Command): void {
2865
+ for (const cmd of allCommands(program)) cmd.exitOverride();
2079
2866
  }
2080
2867
 
2081
2868
  export async function main(argv: string[]): Promise<void> {
2082
2869
  await buildProgram().parseAsync(argv);
2083
2870
  }
2084
2871
 
2872
+ // The single place a failure turns into an exit status. Returns the code so
2873
+ // the bin block can `process.exit` with it; commander has already printed its
2874
+ // own message, so only CLI-raised errors are narrated here.
2875
+ export function reportCliFailure(err: unknown, write: (line: string) => void = (line) => process.stderr.write(line)): number {
2876
+ const code = exitCodeForError(err);
2877
+ if (!isCommanderError(err)) {
2878
+ const msg = err instanceof Error ? err.message : String(err);
2879
+ write(`dharta: ${msg}${retryAdvice(err)}\n`);
2880
+ }
2881
+ return code;
2882
+ }
2883
+
2884
+ // A rate-limited response carries how long to wait in `Retry-After`, and the
2885
+ // server's own message never repeats it. Without this the user is told they
2886
+ // were throttled and left to guess for how long.
2887
+ function retryAdvice(err: unknown): string {
2888
+ const seconds = (err as { retryAfterSeconds?: unknown })?.retryAfterSeconds;
2889
+ if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds < 0) return "";
2890
+ return ` Retry after ${seconds} seconds.`;
2891
+ }
2892
+
2085
2893
  function allCommands(root: Command): Command[] {
2086
2894
  const out: Command[] = [];
2087
2895
  const visit = (cmd: Command): void => {
@@ -2098,8 +2906,11 @@ function hidesJsonHelp(cmd: Command): boolean {
2098
2906
  "dharta auth",
2099
2907
  "dharta analytics",
2100
2908
  "dharta keys",
2101
- "dharta model-keys",
2909
+ "dharta models",
2910
+ "dharta models auth",
2911
+ "dharta org",
2102
2912
  "dharta members",
2913
+ "dharta repos",
2103
2914
  "dharta embed-keys",
2104
2915
  "dharta caps",
2105
2916
  "dharta appearance",
@@ -2108,6 +2919,7 @@ function hidesJsonHelp(cmd: Command): boolean {
2108
2919
  "dharta webhooks",
2109
2920
  "dharta env",
2110
2921
  "dharta agent",
2922
+ "dharta genui",
2111
2923
  "dharta git-credential",
2112
2924
  "dharta dev",
2113
2925
  "dharta logs",
@@ -2141,8 +2953,11 @@ const HELP_ONLY_COMMAND_PATHS = new Set([
2141
2953
  "dharta auth",
2142
2954
  "dharta analytics",
2143
2955
  "dharta keys",
2144
- "dharta model-keys",
2956
+ "dharta models",
2957
+ "dharta models auth",
2958
+ "dharta org",
2145
2959
  "dharta members",
2960
+ "dharta repos",
2146
2961
  "dharta embed-keys",
2147
2962
  "dharta caps",
2148
2963
  "dharta caps budget",
@@ -2152,6 +2967,7 @@ const HELP_ONLY_COMMAND_PATHS = new Set([
2152
2967
  "dharta webhooks",
2153
2968
  "dharta env",
2154
2969
  "dharta agent",
2970
+ "dharta genui",
2155
2971
  ]);
2156
2972
 
2157
2973
  function rejectUnknownTopLevelCommand(
@@ -2173,8 +2989,9 @@ function rejectUnknownTopLevelCommand(
2173
2989
  const path = commandPath(cursor);
2174
2990
  if (!HELP_ONLY_COMMAND_PATHS.has(path)) return;
2175
2991
 
2176
- const visible = cursor.commands.filter((cmd) => !(cmd as unknown as { _hidden?: boolean })._hidden);
2177
- const child = visible.find((cmd) => cmd.name() === token || cmd.aliases().includes(token));
2992
+ const child = visibleSubcommands(cursor).find(
2993
+ (cmd) => cmd.name() === token || cmd.aliases().includes(token),
2994
+ );
2178
2995
  if (child) {
2179
2996
  cursor = child as Command;
2180
2997
  continue;
@@ -2265,7 +3082,7 @@ function parseOnOff(value: string, name: string): boolean {
2265
3082
  if (["on", "true", "enabled", "enable", "resume", "resumed"].includes(normalized)) return true;
2266
3083
  if (["off", "false", "disabled", "disable", "pause", "paused"].includes(normalized)) return false;
2267
3084
  const suggestion = closestChoice(normalized, ["on", "off", "enabled", "disabled", "true", "false"]);
2268
- throw new Error(
3085
+ throw new CliUsageError(
2269
3086
  `${name} must be on/off, enabled/disabled, or true/false.` +
2270
3087
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
2271
3088
  );
@@ -2275,7 +3092,7 @@ export async function readStdin(): Promise<string> {
2275
3092
  if (process.stdin.isTTY) {
2276
3093
  // Reading a TTY would block silently until Ctrl-D — fail with the
2277
3094
  // usage instead (gh parity: --with-token requires piped input).
2278
- throw new Error(
3095
+ throw new CliUsageError(
2279
3096
  "No piped input. Usage: echo $DHARTA_API_KEY | dharta login --with-token",
2280
3097
  );
2281
3098
  }
@@ -2283,7 +3100,7 @@ export async function readStdin(): Promise<string> {
2283
3100
  for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
2284
3101
  const token = Buffer.concat(chunks).toString("utf8").trim();
2285
3102
  if (!token) {
2286
- throw new Error("No token on stdin. Usage: echo $DHARTA_API_KEY | dharta login --with-token");
3103
+ throw new CliUsageError("No token on stdin. Usage: echo $DHARTA_API_KEY | dharta login --with-token");
2287
3104
  }
2288
3105
  return token;
2289
3106
  }
@@ -2293,26 +3110,26 @@ function parseVersion(input: string): number {
2293
3110
  try {
2294
3111
  return parseReleaseVersion(input, "version");
2295
3112
  } catch {
2296
- throw new Error(`Invalid version: ${input.trim()}. Use e.g. v3 or 3.`);
3113
+ throw new CliUsageError(`Invalid version: ${input.trim()}. Use e.g. v3 or 3.`);
2297
3114
  }
2298
3115
  }
2299
3116
 
2300
3117
  function parseRollbackTarget(input: string | undefined): number {
2301
3118
  if (input === undefined) {
2302
- throw new Error("Pass --to <version>. Usage: dharta rollback [slug] --to v3");
3119
+ throw new CliUsageError("Pass --to <version>. Usage: dharta rollback [slug] --to v3");
2303
3120
  }
2304
3121
  return parseVersion(input);
2305
3122
  }
2306
3123
 
2307
3124
  function parsePort(input: string): number {
2308
3125
  const normalized = input.trim();
2309
- if (!normalized) throw new Error("--port cannot be blank.");
3126
+ if (!normalized) throw new CliUsageError("--port cannot be blank.");
2310
3127
  if (!/^[1-9]\d*$/.test(normalized)) {
2311
- throw new Error(`--port must be an integer between 1 and 65535.`);
3128
+ throw new CliUsageError(`--port must be an integer between 1 and 65535.`);
2312
3129
  }
2313
3130
  const port = Number(normalized);
2314
3131
  if (port > 65535) {
2315
- throw new Error(`--port must be an integer between 1 and 65535.`);
3132
+ throw new CliUsageError(`--port must be an integer between 1 and 65535.`);
2316
3133
  }
2317
3134
  return port;
2318
3135
  }
@@ -2332,8 +3149,6 @@ const invokedAsBin = (() => {
2332
3149
  })();
2333
3150
  if (invokedAsBin) {
2334
3151
  main(process.argv).catch((err) => {
2335
- const msg = err instanceof Error ? err.message : String(err);
2336
- process.stderr.write(`dharta: ${msg}\n`);
2337
- process.exit(1);
3152
+ process.exit(reportCliFailure(err));
2338
3153
  });
2339
3154
  }