@dharta/cli 0.6.77

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/agent.d.ts +17 -0
  4. package/dist/agent.js +134 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/api-error.d.ts +3 -0
  7. package/dist/api-error.js +80 -0
  8. package/dist/api-error.js.map +1 -0
  9. package/dist/browser.d.ts +1 -0
  10. package/dist/browser.js +19 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/cli.d.ts +5 -0
  13. package/dist/cli.js +1978 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/commands/agents.d.ts +131 -0
  16. package/dist/commands/agents.js +439 -0
  17. package/dist/commands/agents.js.map +1 -0
  18. package/dist/commands/analytics.d.ts +25 -0
  19. package/dist/commands/analytics.js +166 -0
  20. package/dist/commands/analytics.js.map +1 -0
  21. package/dist/commands/api.d.ts +19 -0
  22. package/dist/commands/api.js +278 -0
  23. package/dist/commands/api.js.map +1 -0
  24. package/dist/commands/appearance.d.ts +19 -0
  25. package/dist/commands/appearance.js +63 -0
  26. package/dist/commands/appearance.js.map +1 -0
  27. package/dist/commands/artifact.d.ts +33 -0
  28. package/dist/commands/artifact.js +235 -0
  29. package/dist/commands/artifact.js.map +1 -0
  30. package/dist/commands/audit-log.d.ts +32 -0
  31. package/dist/commands/audit-log.js +102 -0
  32. package/dist/commands/audit-log.js.map +1 -0
  33. package/dist/commands/auth.d.ts +47 -0
  34. package/dist/commands/auth.js +117 -0
  35. package/dist/commands/auth.js.map +1 -0
  36. package/dist/commands/caps.d.ts +82 -0
  37. package/dist/commands/caps.js +213 -0
  38. package/dist/commands/caps.js.map +1 -0
  39. package/dist/commands/create.d.ts +22 -0
  40. package/dist/commands/create.js +76 -0
  41. package/dist/commands/create.js.map +1 -0
  42. package/dist/commands/deploy.d.ts +45 -0
  43. package/dist/commands/deploy.js +892 -0
  44. package/dist/commands/deploy.js.map +1 -0
  45. package/dist/commands/dev.d.ts +33 -0
  46. package/dist/commands/dev.js +655 -0
  47. package/dist/commands/dev.js.map +1 -0
  48. package/dist/commands/directories.d.ts +60 -0
  49. package/dist/commands/directories.js +83 -0
  50. package/dist/commands/directories.js.map +1 -0
  51. package/dist/commands/doctor.d.ts +13 -0
  52. package/dist/commands/doctor.js +123 -0
  53. package/dist/commands/doctor.js.map +1 -0
  54. package/dist/commands/embed-keys.d.ts +61 -0
  55. package/dist/commands/embed-keys.js +165 -0
  56. package/dist/commands/embed-keys.js.map +1 -0
  57. package/dist/commands/env.d.ts +59 -0
  58. package/dist/commands/env.js +300 -0
  59. package/dist/commands/env.js.map +1 -0
  60. package/dist/commands/files.d.ts +14 -0
  61. package/dist/commands/files.js +84 -0
  62. package/dist/commands/files.js.map +1 -0
  63. package/dist/commands/git-credential.d.ts +1 -0
  64. package/dist/commands/git-credential.js +70 -0
  65. package/dist/commands/git-credential.js.map +1 -0
  66. package/dist/commands/kartas.d.ts +101 -0
  67. package/dist/commands/kartas.js +289 -0
  68. package/dist/commands/kartas.js.map +1 -0
  69. package/dist/commands/keys.d.ts +33 -0
  70. package/dist/commands/keys.js +141 -0
  71. package/dist/commands/keys.js.map +1 -0
  72. package/dist/commands/login.d.ts +22 -0
  73. package/dist/commands/login.js +78 -0
  74. package/dist/commands/login.js.map +1 -0
  75. package/dist/commands/logs.d.ts +14 -0
  76. package/dist/commands/logs.js +89 -0
  77. package/dist/commands/logs.js.map +1 -0
  78. package/dist/commands/members.d.ts +32 -0
  79. package/dist/commands/members.js +77 -0
  80. package/dist/commands/members.js.map +1 -0
  81. package/dist/commands/model-keys.d.ts +22 -0
  82. package/dist/commands/model-keys.js +222 -0
  83. package/dist/commands/model-keys.js.map +1 -0
  84. package/dist/commands/open.d.ts +29 -0
  85. package/dist/commands/open.js +119 -0
  86. package/dist/commands/open.js.map +1 -0
  87. package/dist/commands/parity.d.ts +15 -0
  88. package/dist/commands/parity.js +73 -0
  89. package/dist/commands/parity.js.map +1 -0
  90. package/dist/commands/rollback.d.ts +21 -0
  91. package/dist/commands/rollback.js +26 -0
  92. package/dist/commands/rollback.js.map +1 -0
  93. package/dist/commands/schedules.d.ts +52 -0
  94. package/dist/commands/schedules.js +231 -0
  95. package/dist/commands/schedules.js.map +1 -0
  96. package/dist/commands/scopes.d.ts +3 -0
  97. package/dist/commands/scopes.js +21 -0
  98. package/dist/commands/scopes.js.map +1 -0
  99. package/dist/commands/sessions.d.ts +53 -0
  100. package/dist/commands/sessions.js +142 -0
  101. package/dist/commands/sessions.js.map +1 -0
  102. package/dist/commands/setup.d.ts +28 -0
  103. package/dist/commands/setup.js +949 -0
  104. package/dist/commands/setup.js.map +1 -0
  105. package/dist/commands/status.d.ts +19 -0
  106. package/dist/commands/status.js +63 -0
  107. package/dist/commands/status.js.map +1 -0
  108. package/dist/commands/update.d.ts +14 -0
  109. package/dist/commands/update.js +120 -0
  110. package/dist/commands/update.js.map +1 -0
  111. package/dist/commands/usage.d.ts +3 -0
  112. package/dist/commands/usage.js +37 -0
  113. package/dist/commands/usage.js.map +1 -0
  114. package/dist/commands/webhooks.d.ts +47 -0
  115. package/dist/commands/webhooks.js +156 -0
  116. package/dist/commands/webhooks.js.map +1 -0
  117. package/dist/commands/whoami.d.ts +7 -0
  118. package/dist/commands/whoami.js +17 -0
  119. package/dist/commands/whoami.js.map +1 -0
  120. package/dist/completions.d.ts +13 -0
  121. package/dist/completions.js +120 -0
  122. package/dist/completions.js.map +1 -0
  123. package/dist/config.d.ts +20 -0
  124. package/dist/config.js +98 -0
  125. package/dist/config.js.map +1 -0
  126. package/dist/context.d.ts +14 -0
  127. package/dist/context.js +38 -0
  128. package/dist/context.js.map +1 -0
  129. package/dist/device-flow.d.ts +9 -0
  130. package/dist/device-flow.js +127 -0
  131. package/dist/device-flow.js.map +1 -0
  132. package/dist/harness-preflight.d.ts +24 -0
  133. package/dist/harness-preflight.js +252 -0
  134. package/dist/harness-preflight.js.map +1 -0
  135. package/dist/harnesses.d.ts +11 -0
  136. package/dist/harnesses.js +112 -0
  137. package/dist/harnesses.js.map +1 -0
  138. package/dist/index.d.ts +3 -0
  139. package/dist/index.js +3 -0
  140. package/dist/index.js.map +1 -0
  141. package/dist/login-offer.d.ts +9 -0
  142. package/dist/login-offer.js +36 -0
  143. package/dist/login-offer.js.map +1 -0
  144. package/dist/manifest.d.ts +15 -0
  145. package/dist/manifest.js +142 -0
  146. package/dist/manifest.js.map +1 -0
  147. package/dist/money.d.ts +1 -0
  148. package/dist/money.js +16 -0
  149. package/dist/money.js.map +1 -0
  150. package/dist/output.d.ts +2 -0
  151. package/dist/output.js +20 -0
  152. package/dist/output.js.map +1 -0
  153. package/dist/runtime.d.ts +19 -0
  154. package/dist/runtime.js +95 -0
  155. package/dist/runtime.js.map +1 -0
  156. package/dist/scaffold.d.ts +16 -0
  157. package/dist/scaffold.js +74 -0
  158. package/dist/scaffold.js.map +1 -0
  159. package/dist/setup-scan.d.ts +21 -0
  160. package/dist/setup-scan.js +127 -0
  161. package/dist/setup-scan.js.map +1 -0
  162. package/dist/templates.d.ts +14 -0
  163. package/dist/templates.js +65 -0
  164. package/dist/templates.js.map +1 -0
  165. package/dist/validators.d.ts +10 -0
  166. package/dist/validators.js +116 -0
  167. package/dist/validators.js.map +1 -0
  168. package/dist/version-check.d.ts +25 -0
  169. package/dist/version-check.js +68 -0
  170. package/dist/version-check.js.map +1 -0
  171. package/node_modules/@dharta/sdk/LICENSE +21 -0
  172. package/node_modules/@dharta/sdk/README.md +87 -0
  173. package/node_modules/@dharta/sdk/dist/errors.d.ts +20 -0
  174. package/node_modules/@dharta/sdk/dist/errors.d.ts.map +1 -0
  175. package/node_modules/@dharta/sdk/dist/errors.js +51 -0
  176. package/node_modules/@dharta/sdk/dist/errors.js.map +1 -0
  177. package/node_modules/@dharta/sdk/dist/http.d.ts +26 -0
  178. package/node_modules/@dharta/sdk/dist/http.d.ts.map +1 -0
  179. package/node_modules/@dharta/sdk/dist/http.js +136 -0
  180. package/node_modules/@dharta/sdk/dist/http.js.map +1 -0
  181. package/node_modules/@dharta/sdk/dist/index.d.ts +15 -0
  182. package/node_modules/@dharta/sdk/dist/index.d.ts.map +1 -0
  183. package/node_modules/@dharta/sdk/dist/index.js +21 -0
  184. package/node_modules/@dharta/sdk/dist/index.js.map +1 -0
  185. package/node_modules/@dharta/sdk/dist/resources.d.ts +48 -0
  186. package/node_modules/@dharta/sdk/dist/resources.d.ts.map +1 -0
  187. package/node_modules/@dharta/sdk/dist/resources.js +150 -0
  188. package/node_modules/@dharta/sdk/dist/resources.js.map +1 -0
  189. package/node_modules/@dharta/sdk/dist/types.d.ts +94 -0
  190. package/node_modules/@dharta/sdk/dist/types.d.ts.map +1 -0
  191. package/node_modules/@dharta/sdk/dist/types.js +59 -0
  192. package/node_modules/@dharta/sdk/dist/types.js.map +1 -0
  193. package/node_modules/@dharta/sdk/package.json +32 -0
  194. package/node_modules/@dharta/sdk/src/errors.ts +58 -0
  195. package/node_modules/@dharta/sdk/src/http.ts +166 -0
  196. package/node_modules/@dharta/sdk/src/index.ts +36 -0
  197. package/node_modules/@dharta/sdk/src/resources.ts +197 -0
  198. package/node_modules/@dharta/sdk/src/types.ts +179 -0
  199. package/package.json +38 -0
  200. package/src/agent.ts +147 -0
  201. package/src/api-error.ts +88 -0
  202. package/src/browser.ts +20 -0
  203. package/src/cli.ts +2339 -0
  204. package/src/commands/agents.ts +635 -0
  205. package/src/commands/analytics.ts +181 -0
  206. package/src/commands/api.ts +297 -0
  207. package/src/commands/appearance.ts +79 -0
  208. package/src/commands/artifact.ts +331 -0
  209. package/src/commands/audit-log.ts +125 -0
  210. package/src/commands/auth.ts +186 -0
  211. package/src/commands/caps.ts +302 -0
  212. package/src/commands/create.ts +114 -0
  213. package/src/commands/deploy.ts +1122 -0
  214. package/src/commands/dev.ts +792 -0
  215. package/src/commands/directories.ts +136 -0
  216. package/src/commands/doctor.ts +138 -0
  217. package/src/commands/embed-keys.ts +237 -0
  218. package/src/commands/env.ts +369 -0
  219. package/src/commands/files.ts +124 -0
  220. package/src/commands/git-credential.ts +78 -0
  221. package/src/commands/kartas.ts +439 -0
  222. package/src/commands/keys.ts +206 -0
  223. package/src/commands/login.ts +115 -0
  224. package/src/commands/logs.ts +111 -0
  225. package/src/commands/members.ts +109 -0
  226. package/src/commands/model-keys.ts +278 -0
  227. package/src/commands/open.ts +173 -0
  228. package/src/commands/parity.ts +96 -0
  229. package/src/commands/rollback.ts +45 -0
  230. package/src/commands/schedules.ts +290 -0
  231. package/src/commands/scopes.ts +22 -0
  232. package/src/commands/sessions.ts +225 -0
  233. package/src/commands/setup.ts +1176 -0
  234. package/src/commands/status.ts +94 -0
  235. package/src/commands/update.ts +157 -0
  236. package/src/commands/usage.ts +41 -0
  237. package/src/commands/webhooks.ts +200 -0
  238. package/src/commands/whoami.ts +24 -0
  239. package/src/completions.ts +168 -0
  240. package/src/config.ts +143 -0
  241. package/src/context.ts +59 -0
  242. package/src/device-flow.ts +174 -0
  243. package/src/harness-preflight.ts +293 -0
  244. package/src/harnesses.ts +117 -0
  245. package/src/index.ts +3 -0
  246. package/src/login-offer.ts +49 -0
  247. package/src/manifest.ts +153 -0
  248. package/src/money.ts +15 -0
  249. package/src/output.ts +22 -0
  250. package/src/runtime.ts +127 -0
  251. package/src/scaffold.ts +102 -0
  252. package/src/setup-scan.ts +154 -0
  253. package/src/templates.ts +85 -0
  254. package/src/validators.ts +124 -0
  255. package/src/version-check.ts +75 -0
  256. package/templates/blank/CLAUDE.md +21 -0
  257. package/templates/blank/dharta.toml +11 -0
  258. package/templates/faq-agent/CLAUDE.md +51 -0
  259. package/templates/faq-agent/dharta.toml +11 -0
package/src/cli.ts ADDED
@@ -0,0 +1,2339 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, realpathSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import * as readline from "node:readline";
5
+ import { Argument, Command, InvalidArgumentError, Option } from "commander";
6
+ import { API_KEY_SCOPES, type ApiKeyScope, type ModelKeyProvider } from "@dharta/sdk";
7
+ import { buildContext } from "./context.js";
8
+ import { resolveBaseUrl } from "./config.js";
9
+ import { inferAgentSlug, validateAgentSelectorOptions, validateAgentSlug } from "./agent.js";
10
+ import { login } from "./commands/login.js";
11
+ import {
12
+ authLogout,
13
+ authLogoutData,
14
+ authStatus,
15
+ authStatusData,
16
+ authToken,
17
+ } from "./commands/auth.js";
18
+ import { whoami, whoamiData } from "./commands/whoami.js";
19
+ import { api as apiCmd, formatApiResult, prepareApiOptions, validateApiRequest } from "./commands/api.js";
20
+ import { auditLog, auditLogData, validateAuditLogInput } from "./commands/audit-log.js";
21
+ import {
22
+ COST_ANALYTICS_GROUPS,
23
+ COST_ANALYTICS_RANGES,
24
+ ENTITY_ANALYTICS_RANGES,
25
+ analytics,
26
+ analyticsData,
27
+ normalizeAnalyticsOptions,
28
+ type AnalyticsView,
29
+ } from "./commands/analytics.js";
30
+ import {
31
+ keysCreate,
32
+ keysCreateData,
33
+ keysList,
34
+ keysListData,
35
+ keysRename,
36
+ keysRenameData,
37
+ keysRevoke,
38
+ keysRevokeData,
39
+ validateKeysCreateInput,
40
+ } from "./commands/keys.js";
41
+ import {
42
+ modelKeysCreate,
43
+ modelKeysCreateData,
44
+ modelKeysList,
45
+ modelKeysListData,
46
+ modelKeysRevoke,
47
+ modelKeysRevokeData,
48
+ prepareModelKeysCreateOptions,
49
+ MODEL_API_FORMATS,
50
+ validateModelKeyId,
51
+ validateModelKeysCreateOptions,
52
+ } from "./commands/model-keys.js";
53
+ import {
54
+ membersInvite,
55
+ membersInviteData,
56
+ membersList,
57
+ membersListData,
58
+ membersRemove,
59
+ membersRemoveData,
60
+ membersSetRole,
61
+ membersSetRoleData,
62
+ validateMembersInviteInput,
63
+ } from "./commands/members.js";
64
+ import {
65
+ kartaMembersGrant,
66
+ kartaMembersGrantData,
67
+ kartaMembersRevoke,
68
+ kartaMembersRevokeData,
69
+ kartaMembersSetRole,
70
+ kartaMembersSetRoleData,
71
+ kartasArchive,
72
+ kartasArchiveData,
73
+ kartasConfigure,
74
+ kartasConfigureData,
75
+ kartasRestore,
76
+ kartasRestoreData,
77
+ kartasCreate,
78
+ kartasCreateData,
79
+ kartasList,
80
+ kartasListData,
81
+ kartasName,
82
+ kartasNameData,
83
+ kartasShow,
84
+ kartasShowData,
85
+ kartaEnvList,
86
+ kartaEnvListData,
87
+ kartaEnvSet,
88
+ kartaEnvSetData,
89
+ kartaEnvUnset,
90
+ kartaEnvUnsetData,
91
+ validateKartaCreateOptions,
92
+ validateKartaConfigureOptions,
93
+ validateKartaNameOptions,
94
+ } from "./commands/kartas.js";
95
+ import { usage as usageCmd, usageData } from "./commands/usage.js";
96
+ import {
97
+ type AgentModelSetOptions,
98
+ agentsActivateRelease,
99
+ agentsActivateReleaseData,
100
+ agentsDashboardEditsSet,
101
+ agentsDashboardEditsSetData,
102
+ agentsDelete,
103
+ agentsDeleteData,
104
+ agentsList,
105
+ agentsListData,
106
+ agentsModelSet,
107
+ agentsModelSetData,
108
+ agentsReleases,
109
+ agentsReleasesData,
110
+ agentsShow,
111
+ agentsShowData,
112
+ agentsServingSet,
113
+ agentsServingSetData,
114
+ agentsWorkspaceInspectableSet,
115
+ agentsWorkspaceInspectableSetData,
116
+ agentsWorkspaceAccessShow,
117
+ agentsWorkspaceAccessShowData,
118
+ agentsWorkspaceAccessSet,
119
+ agentsWorkspaceAccessSetData,
120
+ parseWorkspaceAccessAssignments,
121
+ validateAgentModelSetOptions,
122
+ } from "./commands/agents.js";
123
+ import {
124
+ embedKeysClearIdentity,
125
+ embedKeysClearIdentityData,
126
+ embedKeysCreate,
127
+ embedKeysCreateData,
128
+ embedKeysList,
129
+ embedKeysListData,
130
+ embedKeysRevoke,
131
+ embedKeysRevokeData,
132
+ embedKeysRotate,
133
+ embedKeysRotateData,
134
+ embedKeysSetIdentity,
135
+ embedKeysSetIdentityData,
136
+ embedKeysToggle,
137
+ embedKeysToggleData,
138
+ parseOrigins,
139
+ validateEmbedKeysCreateInput,
140
+ } from "./commands/embed-keys.js";
141
+ import {
142
+ capsEndUser,
143
+ capsEndUserData,
144
+ capsEndUserSet,
145
+ capsEndUserSetData,
146
+ capsBudget,
147
+ capsBudgetData,
148
+ capsBudgetSet,
149
+ capsBudgetSetData,
150
+ capsInstance,
151
+ capsInstanceData,
152
+ capsInstanceSet,
153
+ capsInstanceSetData,
154
+ capsSeat,
155
+ capsSeatData,
156
+ capsSeatSet,
157
+ capsSeatSetData,
158
+ validateBudgetSetOptions,
159
+ validateCapSetOptions,
160
+ } from "./commands/caps.js";
161
+ import {
162
+ appearanceGet,
163
+ appearanceGetData,
164
+ appearanceSet,
165
+ appearanceSetData,
166
+ validateAppearanceSetInput,
167
+ } from "./commands/appearance.js";
168
+ import {
169
+ schedulesAction,
170
+ schedulesActionData,
171
+ schedulesCreate,
172
+ schedulesCreateData,
173
+ schedulesDelete,
174
+ schedulesDeleteData,
175
+ schedulesList,
176
+ schedulesListData,
177
+ validateSchedulesCreateInput,
178
+ } from "./commands/schedules.js";
179
+ import {
180
+ directoryEndUsers,
181
+ directoryEndUsersData,
182
+ directoryInstances,
183
+ directoryInstancesData,
184
+ directorySessions,
185
+ directorySessionsData,
186
+ validateDirectoryOptions,
187
+ } from "./commands/directories.js";
188
+ import {
189
+ parseEvents,
190
+ webhooksCreate,
191
+ webhooksCreateData,
192
+ webhooksDelete,
193
+ webhooksDeleteData,
194
+ webhooksEnable,
195
+ webhooksEnableData,
196
+ webhooksList,
197
+ webhooksListData,
198
+ webhooksUpdate,
199
+ webhooksUpdateData,
200
+ validateWebhooksCreateInput,
201
+ validateWebhooksUpdateInput,
202
+ } from "./commands/webhooks.js";
203
+ import { rollback, rollbackData } from "./commands/rollback.js";
204
+ import { deploy, inferMode, validateDeployDir } from "./commands/deploy.js";
205
+ import { status, statusData } from "./commands/status.js";
206
+ import { logsTail, validateLogsOptions } from "./commands/logs.js";
207
+ import { create } from "./commands/create.js";
208
+ import { setup } from "./commands/setup.js";
209
+ import { HARNESS_TYPES } from "./harnesses.js";
210
+ import { offerLogin } from "./login-offer.js";
211
+ import { doctor } from "./commands/doctor.js";
212
+ import { dev as devCmd } from "./commands/dev.js";
213
+ import { registerArtifactCommands } from "./commands/artifact.js";
214
+ import {
215
+ isHandoffTarget,
216
+ open as openCmd,
217
+ openData,
218
+ openHandoff,
219
+ openHandoffData,
220
+ OPEN_TARGETS_HELP,
221
+ normalizeOpenTarget,
222
+ validateOpenTarget,
223
+ } from "./commands/open.js";
224
+ import {
225
+ envDiff,
226
+ envDiffData,
227
+ envList,
228
+ envListData,
229
+ envPull,
230
+ envPullData,
231
+ envSet,
232
+ envSetData,
233
+ envUnset,
234
+ envUnsetData,
235
+ validateEnvAssignment,
236
+ validateEnvDiffOptions,
237
+ validateEnvName,
238
+ validateEnvPullOptions,
239
+ } from "./commands/env.js";
240
+ import {
241
+ ps as psCmd,
242
+ psData,
243
+ run as runCmd,
244
+ sessionsList,
245
+ sessionsListData,
246
+ sessionsShow,
247
+ sessionsShowData,
248
+ validateSessionsListOptions,
249
+ } from "./commands/sessions.js";
250
+ import { update } from "./commands/update.js";
251
+ import { filesLs, filesLsData, filesGet } from "./commands/files.js";
252
+ import { gitCredential } from "./commands/git-credential.js";
253
+ import { bashCompletion, zshCompletion } from "./completions.js";
254
+ import { closestChoice, normalizeHttpUrl, normalizeNonBlank, parsePositiveInteger, parseReleaseVersion } from "./validators.js";
255
+
256
+ const MEMBER_ROLES = ["owner", "admin", "developer", "billing", "viewer"] as const;
257
+ const DHARTA_ACCESS_AUDIENCES = ["members", "anonymous", "off"] as const;
258
+ 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
+ const CLI_API_KEY_SCOPES = API_KEY_SCOPES;
262
+ const STATE_HELP = "State values: on/off, enabled/disabled, true/false.";
263
+
264
+ interface AgentSelectorOptions {
265
+ agent?: string;
266
+ project?: string;
267
+ dir?: string;
268
+ }
269
+
270
+ interface SessionListSelectorOptions extends AgentSelectorOptions {
271
+ limit?: string;
272
+ }
273
+
274
+ interface DirectorySelectorOptions extends AgentSelectorOptions {
275
+ limit?: string;
276
+ }
277
+
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
+ function commandOpts<T extends object>(cmd: Command): T {
302
+ return cmd.optsWithGlobals() as T;
303
+ }
304
+
305
+ function validatePositionalAgentSelector(slug: string | undefined, opts: Pick<AgentSelectorOptions, "agent" | "project">): void {
306
+ if (slug === undefined) return;
307
+ validateAgentSlug(slug, "agent slug");
308
+ if (opts.agent !== undefined || opts.project !== undefined) {
309
+ throw new Error("Use either the positional agent slug or --agent/--project, not both.");
310
+ }
311
+ }
312
+
313
+ function validateCreateInput(pathArg: string | undefined, opts: {
314
+ name?: string;
315
+ template?: string;
316
+ harnessVersion?: string;
317
+ }): void {
318
+ if (pathArg !== undefined) normalizeNonBlank(pathArg, "path");
319
+ if (opts.name !== undefined) normalizeNonBlank(opts.name, "--name");
320
+ if (opts.template !== undefined) normalizeNonBlank(opts.template, "--template");
321
+ if (opts.harnessVersion !== undefined) normalizeNonBlank(opts.harnessVersion, "--harness-version");
322
+ }
323
+
324
+ type ResolvedAgentOptions<T extends AgentSelectorOptions> = Omit<T, "agent" | "project"> & {
325
+ agent: string;
326
+ project?: undefined;
327
+ };
328
+
329
+ async function resolveAgentOptions<T extends AgentSelectorOptions>(
330
+ opts: T,
331
+ positionalSlug?: string,
332
+ ): Promise<ResolvedAgentOptions<T>> {
333
+ validatePositionalAgentSelector(positionalSlug, opts);
334
+ validateAgentSelectorOptions(opts);
335
+ const agent = await inferAgentSlug({
336
+ agent: positionalSlug ?? opts.agent,
337
+ project: positionalSlug === undefined ? opts.project : undefined,
338
+ dir: opts.dir,
339
+ });
340
+ return { ...opts, agent, project: undefined } as ResolvedAgentOptions<T>;
341
+ }
342
+
343
+ 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
+ }
349
+ }
350
+
351
+ function helpOnlyAction(name: string) {
352
+ return (...args: unknown[]): void => {
353
+ const cmd = args[args.length - 1] as Command;
354
+ const extra = cmd.args.filter((arg): arg is string => typeof arg === "string");
355
+ if (extra.length > 0) {
356
+ const unknown = extra.join(" ");
357
+ const path = commandPath(cmd);
358
+ const suggestion = closestCommand(extra[0], commandSuggestionTokens(cmd));
359
+ cmd.error(
360
+ `error: unknown command '${unknown}'${path === "dharta" ? "" : ` for ${path}`}. ` +
361
+ `Run \`${path} --help\` for available commands.` +
362
+ (suggestion ? ` Did you mean \`${suggestion}\`?` : ""),
363
+ );
364
+ }
365
+ rejectJson(cmd, name);
366
+ cmd.outputHelp();
367
+ };
368
+ }
369
+
370
+ const logToStderr = (line: string) => process.stderr.write(line + "\n");
371
+
372
+ function commandPath(cmd: Command): string {
373
+ const names: string[] = [];
374
+ let cursor: Command | null = cmd;
375
+ while (cursor) {
376
+ names.unshift(cursor.name());
377
+ cursor = cursor.parent;
378
+ }
379
+ return names.join(" ");
380
+ }
381
+
382
+ function closestCommand(input: string, commands: string[]): string | null {
383
+ return closestChoice(input, commands);
384
+ }
385
+
386
+ function commandSuggestionTokens(cmd: Command): string[] {
387
+ 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
+ ),
393
+ );
394
+ }
395
+
396
+ export function buildProgram(): Command {
397
+ 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
+ program
405
+ .name("dharta")
406
+ .configureHelp({
407
+ visibleCommands: (cmd) => (cmd.parent === null ? [] : defaultHelp.visibleCommands(cmd)),
408
+ })
409
+ .description(
410
+ "Build, run, and deploy AI agents on dharta.ai - one tool from create to open.\n" +
411
+ "Agents are harness-native folders (Claude Code, OpenCode, DeepAgents, Goose, Codex CLI) plus dharta.toml; `dharta deploy` ships one selected folder.",
412
+ )
413
+ // Read from package.json so `--version` can never drift from the published
414
+ // package or the release pin-pair (the 0.3.0-vs-0.3.1 mismatch that shipped).
415
+ .version(
416
+ (JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string })
417
+ .version,
418
+ )
419
+ .addHelpText(
420
+ "after",
421
+ [
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
+ "",
466
+ "Golden path: dharta login -> create -> setup -> dev -> deploy -> open",
467
+ "Run `karta <command> --help` for details. Docs: https://docs.dharta.ai",
468
+ ].join("\n"),
469
+ )
470
+ .action(helpOnlyAction("dharta"));
471
+
472
+ program
473
+ .command("login")
474
+ .description(
475
+ "Sign in via your browser (device flow) and store the credential in ~/.dharta/config.toml",
476
+ )
477
+ .option("--with-token", "Read an API key from stdin instead (CI, air-gapped)")
478
+ .option("--base-url <url>", "Override the API base URL")
479
+ .option("--git-base-url <url>", "Override the git server base URL")
480
+ .option("--profile <name>", "Named profile (default: default)")
481
+ .action(
482
+ async (
483
+ opts: {
484
+ withToken?: boolean;
485
+ baseUrl?: string;
486
+ gitBaseUrl?: string;
487
+ profile?: string;
488
+ },
489
+ cmd: Command,
490
+ ) => {
491
+ if (opts.baseUrl !== undefined) normalizeHttpUrl(opts.baseUrl, "--base-url");
492
+ if (opts.gitBaseUrl !== undefined) normalizeHttpUrl(opts.gitBaseUrl, "--git-base-url");
493
+ if (opts.profile !== undefined) normalizeNonBlank(opts.profile, "--profile");
494
+ const json = isJson(cmd);
495
+ const result = await login({
496
+ baseUrl: opts.baseUrl,
497
+ gitBaseUrl: opts.gitBaseUrl,
498
+ profile: opts.profile,
499
+ apiKey: opts.withToken ? await readStdin() : undefined,
500
+ // In JSON mode the device-flow narration (code, approve URL)
501
+ // still has to reach the human - on stderr.
502
+ log: json ? logToStderr : undefined,
503
+ });
504
+ if (json) {
505
+ printJson(result);
506
+ return;
507
+ }
508
+ // keyName is "CLI - <hostname>" from the browser flow; show just
509
+ // the machine name. Token-flow keys keep their given name.
510
+ const identity = result.keyName.replace(/^CLI - /, "");
511
+ console.log(`✓ Logged in to ${result.organization.name} as ${identity}`);
512
+ console.log(
513
+ result.helperInstalled
514
+ ? "✓ git push dharta - enabled"
515
+ : "✗ git push dharta - not enabled (is git installed?)\n" +
516
+ ' git push will prompt: username "dharta", password = your API key',
517
+ );
518
+ },
519
+ );
520
+
521
+ const auth = program.command("auth").description("Manage CLI authentication.").action(helpOnlyAction("dharta auth"));
522
+ auth
523
+ .command("status")
524
+ .description("Show who you are, per profile.")
525
+ .action(async (_opts: unknown, cmd: Command) => {
526
+ if (isJson(cmd)) printJson(await authStatusData());
527
+ else console.log(await authStatus());
528
+ });
529
+ auth
530
+ .command("token")
531
+ .description("Print the active API token (for piping).")
532
+ .action(async (_opts: unknown, cmd: Command) => {
533
+ const token = await authToken();
534
+ if (isJson(cmd)) printJson({ token });
535
+ else console.log(token);
536
+ });
537
+ auth
538
+ .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));
544
+ });
545
+
546
+ program
547
+ .command("create [path]")
548
+ .description(
549
+ "Scaffold a brand-new agent in a supported harness format: a starter template + a default dharta.toml (deploy off). Configure delivery + the deploy gate afterward with `dharta setup`. Local only.",
550
+ )
551
+ .option("--name <name>", "agent name (default: the folder's slug)")
552
+ .option("--template <key>", "starter template (default: blank); see /agent_templates")
553
+ .addOption(choiceOption("--harness <type>", "harness format to scaffold", HARNESS_TYPES).default("claude-code"))
554
+ .option("--harness-version <version>", "optional harness version/range pin; omit for Dharta's curated default")
555
+ .action(
556
+ async (
557
+ pathArg: string | undefined,
558
+ opts: { name?: string; template?: string; harness?: string; harnessVersion?: string },
559
+ cmd: Command,
560
+ ) => {
561
+ validateCreateInput(pathArg, opts);
562
+ const json = isJson(cmd);
563
+ const result = await create(pathArg, {
564
+ name: opts.name,
565
+ template: opts.template,
566
+ harness: opts.harness,
567
+ harnessVersion: opts.harnessVersion,
568
+ baseUrl: await resolveBaseUrl(),
569
+ log: json ? logToStderr : (line: string) => console.log(line),
570
+ });
571
+ if (json) printJson(result);
572
+ },
573
+ );
574
+
575
+ program
576
+ .command("setup [path]")
577
+ .description(
578
+ "Configure an existing agent (name, deploy gate, persisted git/folder method, optional CI) and write a self-documenting dharta.toml. Destination-agnostic - does NOT deploy.",
579
+ )
580
+ .option("--name <name>", "agent name (default: the folder's slug)")
581
+ .option("--enable", "set deploy = true (allow this agent to deploy)")
582
+ .option("--disable", "set deploy = false (configured, not deployed)")
583
+ .addOption(
584
+ choiceOption(
585
+ "--method <m>",
586
+ "persisted transport: git | folder (`github` remains a compatibility alias for folder + --ci github)",
587
+ ["git", "folder", "github"],
588
+ ),
589
+ )
590
+ .addOption(choiceOption("--ci <provider>", "optional automation provider: github", ["github"]))
591
+ .option("--login", "also log in (device flow) - optional; setup works without it")
592
+ .option("-y, --yes", "non-interactive: take defaults, skip confirmations (including git init)")
593
+ .action(
594
+ async (
595
+ pathArg: string | undefined,
596
+ opts: { name?: string; enable?: boolean; disable?: boolean; method?: "git" | "github" | "folder"; ci?: "github"; login?: boolean; yes?: boolean },
597
+ cmd: Command,
598
+ ) => {
599
+ const json = isJson(cmd);
600
+ if (json && opts.login) {
601
+ throw new Error("--login is not supported with `dharta setup --json`. Run `dharta setup --json` first, then `dharta login`.");
602
+ }
603
+ const interactive = !json && !opts.yes && process.stdin.isTTY;
604
+ const rl = interactive
605
+ ? readline.createInterface({ input: process.stdin, output: process.stderr })
606
+ : null;
607
+ const ask = rl
608
+ ? (q: string) => new Promise<string>((resolve) => rl.question(q, resolve))
609
+ : undefined;
610
+ try {
611
+ const result = await setup(pathArg, {
612
+ name: opts.name,
613
+ enable: opts.enable,
614
+ disable: opts.disable,
615
+ method: opts.method,
616
+ ci: opts.ci,
617
+ yes: opts.yes,
618
+ ask,
619
+ log: json ? logToStderr : (line: string) => console.log(line),
620
+ });
621
+ if (json) printJson(result);
622
+ // Optional login (DEP-9): the bridge to `dharta deploy`. Never blocks setup.
623
+ await offerLogin({
624
+ force: opts.login,
625
+ ask,
626
+ log: json ? logToStderr : (line: string) => console.log(line),
627
+ });
628
+ } finally {
629
+ rl?.close();
630
+ }
631
+ },
632
+ );
633
+
634
+ // Git credential helper protocol endpoint, wired by `dharta login`
635
+ // (credential.<git host>.helper = !dharta git-credential). Hidden:
636
+ // humans never call it.
637
+ program
638
+ .command("git-credential <operation>", { hidden: true })
639
+ .description("Git credential helper (installed by `dharta login`).")
640
+ .action(async (operation: string, _opts: unknown, cmd: Command) => {
641
+ rejectJson(cmd, "dharta git-credential");
642
+ const chunks: Buffer[] = [];
643
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
644
+ process.stdout.write(
645
+ await gitCredential(operation, Buffer.concat(chunks).toString("utf8")),
646
+ );
647
+ });
648
+
649
+ program
650
+ .command("audit-log")
651
+ .description("Export audit events (requires audit_log:export).")
652
+ .option("--csv", "Print CSV instead of a table")
653
+ .option("--action <action>", "Filter by audit action")
654
+ .option("--user-id <id>", "Filter by actor user id")
655
+ .option("--from <date>", "Filter from date (YYYY-MM-DD)")
656
+ .option("--to <date>", "Filter to date (YYYY-MM-DD)")
657
+ .option("--limit <n>", "Maximum rows to return")
658
+ .action(async (opts: {
659
+ csv?: boolean;
660
+ action?: string;
661
+ userId?: string;
662
+ from?: string;
663
+ to?: string;
664
+ limit?: string;
665
+ }, cmd: Command) => {
666
+ validateAuditLogInput(opts);
667
+ if (opts.csv && isJson(cmd)) {
668
+ throw new Error("Use either --csv or --json for audit-log, not both.");
669
+ }
670
+ const ctx = await buildContext();
671
+ if (isJson(cmd)) printJson(await auditLogData(ctx, opts));
672
+ else console.log(await auditLog(ctx, opts));
673
+ });
674
+
675
+ const analyticsCmd = program.command("analytics").description("Show usage and cost analytics.").action(helpOnlyAction("dharta analytics"));
676
+ const analyticsAction = (view: AnalyticsView) => async (
677
+ opts: { range?: string; groupBy?: string; from?: string; to?: string; limit?: string },
678
+ cmd: Command,
679
+ ) => {
680
+ normalizeAnalyticsOptions(view, opts);
681
+ const ctx = await buildContext();
682
+ if (isJson(cmd)) printJson(await analyticsData(ctx, view, opts));
683
+ else console.log(await analytics(ctx, view, opts));
684
+ };
685
+ for (const view of ["agents", "users", "instances", "sessions", "models"] as const) {
686
+ analyticsCmd
687
+ .command(view)
688
+ .description(`Show ${view} analytics.`)
689
+ .addOption(
690
+ choiceOption("--range <range>", "Window: 7d, 30d, or 90d", ENTITY_ANALYTICS_RANGES),
691
+ )
692
+ .option("--limit <n>", "Maximum rows")
693
+ .action(analyticsAction(view));
694
+ }
695
+ analyticsCmd
696
+ .command("cost")
697
+ .description("Show cost analytics.")
698
+ .addOption(
699
+ choiceOption("--range <range>", "Window: mtd, 7d, 30d, 90d, last_month, or custom", COST_ANALYTICS_RANGES),
700
+ )
701
+ .addOption(
702
+ choiceOption("--group-by <field>", "agent, karta, session, user, or model", COST_ANALYTICS_GROUPS),
703
+ )
704
+ .option("--from <date>", "Custom start date (YYYY-MM-DD; implies --range custom)")
705
+ .option("--to <date>", "Custom end date (YYYY-MM-DD; implies --range custom)")
706
+ .option("--limit <n>", "Maximum rows")
707
+ .action(analyticsAction("cost"));
708
+
709
+ program
710
+ .command("whoami")
711
+ .description("Show the currently authenticated principal.")
712
+ .action(async (_opts: unknown, cmd: Command) => {
713
+ const ctx = await buildContext();
714
+ if (isJson(cmd)) printJson(await whoamiData(ctx));
715
+ else console.log(await whoami(ctx));
716
+ });
717
+
718
+ program
719
+ .command("api [method] [path]")
720
+ .description("Call an allowlisted public JSON API endpoint with the active profile.")
721
+ .option("-F, --field <key=value>", "Add a JSON-coerced field to the request body", collect, [])
722
+ .option("-f, --raw-field <key=value>", "Add a string field to the request body", collect, [])
723
+ .option("-H, --header <name:value>", "Add a safe request header", collect, [])
724
+ .option("--body-stdin", "Read the raw request body from stdin")
725
+ .action(async (method: string | undefined, path: string | undefined, opts: {
726
+ field?: string[];
727
+ rawField?: string[];
728
+ header?: string[];
729
+ bodyStdin?: boolean;
730
+ }, cmd: Command) => {
731
+ validateApiRequest(method, path, opts);
732
+ const preparedOpts = await prepareApiOptions(opts);
733
+ const ctx = await buildContext();
734
+ const result = await apiCmd(ctx, method, path, preparedOpts);
735
+ if (isJson(cmd)) printJson(result.body);
736
+ else {
737
+ const output = formatApiResult(result);
738
+ if (output) console.log(output);
739
+ }
740
+ });
741
+
742
+ const keys = program.command("keys").description("Manage API keys.").action(helpOnlyAction("dharta keys"));
743
+ keys
744
+ .command("list")
745
+ .description("List API keys for the current organization (requires keys:read or keys:write).")
746
+ .action(async (_opts: unknown, cmd: Command) => {
747
+ const ctx = await buildContext();
748
+ if (isJson(cmd)) printJson(await keysListData(ctx));
749
+ else console.log(await keysList(ctx));
750
+ });
751
+ keys
752
+ .command("create <name>")
753
+ .description("Mint a new API key.")
754
+ .addOption(
755
+ choiceListOption("--scope <scope...>", "operation scopes", CLI_API_KEY_SCOPES)
756
+ .default([]),
757
+ )
758
+ .option("--expires-at <iso>", "Expiry timestamp; required within 90 days for sensitive scopes")
759
+ .action(async (name: string, opts: { scope: ApiKeyScope[]; expiresAt?: string }, cmd: Command) => {
760
+ validateKeysCreateInput(name, opts.scope, { expiresAt: opts.expiresAt });
761
+ const ctx = await buildContext();
762
+ const keyOpts = { expiresAt: opts.expiresAt };
763
+ if (isJson(cmd)) printJson(await keysCreateData(ctx, name, opts.scope, keyOpts));
764
+ else console.log(await keysCreate(ctx, name, opts.scope, keyOpts));
765
+ });
766
+ keys
767
+ .command("rename <id> <name>")
768
+ .description("Rename an API key by id.")
769
+ .action(async (id: string, name: string, _opts: unknown, cmd: Command) => {
770
+ normalizeNonBlank(id, "API key id");
771
+ normalizeNonBlank(name, "API key name");
772
+ const ctx = await buildContext();
773
+ if (isJson(cmd)) printJson(await keysRenameData(ctx, id, name));
774
+ else console.log(await keysRename(ctx, id, name));
775
+ });
776
+ keys
777
+ .command("revoke <id>")
778
+ .description("Revoke an API key by id.")
779
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
780
+ normalizeNonBlank(id, "API key id");
781
+ const ctx = await buildContext();
782
+ if (isJson(cmd)) printJson(await keysRevokeData(ctx, id));
783
+ else console.log(await keysRevoke(ctx, id));
784
+ });
785
+
786
+ const modelKeys = program.command("model-keys").description("Manage BYOK provider credentials.").action(helpOnlyAction("dharta model-keys"));
787
+ modelKeys
788
+ .command("list")
789
+ .description("List model provider credentials.")
790
+ .action(async (_opts: unknown, cmd: Command) => {
791
+ const ctx = await buildContext();
792
+ if (isJson(cmd)) printJson(await modelKeysListData(ctx));
793
+ else console.log(await modelKeysList(ctx));
794
+ });
795
+ modelKeys
796
+ .command("create")
797
+ .description("Store an encrypted model provider credential.")
798
+ .addArgument(choiceArgument("<provider>", "provider", ["anthropic", "openai", "bedrock", "vertex", "openrouter", "xai", "google"]))
799
+ .argument("<name>", "connector name")
800
+ .option("--key <plaintext>", "Provider API key plaintext")
801
+ .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))
803
+ .option("--endpoint-url <url>", "Provider endpoint URL override")
804
+ .addOption(choiceOption("--api-format <format>", "Explicit provider API format", MODEL_API_FORMATS))
805
+ .action(async (provider: ModelKeyProvider, name: string, opts: {
806
+ key?: string;
807
+ keyStdin?: boolean;
808
+ authType?: string;
809
+ endpointUrl?: string;
810
+ apiFormat?: string;
811
+ }, cmd: Command) => {
812
+ validateModelKeysCreateOptions(provider, name, opts);
813
+ const preparedOpts = await prepareModelKeysCreateOptions(provider, name, opts);
814
+ 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));
817
+ });
818
+ modelKeys
819
+ .command("revoke <id>")
820
+ .description("Revoke a model provider credential.")
821
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
822
+ validateModelKeyId(id);
823
+ const ctx = await buildContext();
824
+ if (isJson(cmd)) printJson(await modelKeysRevokeData(ctx, id));
825
+ else console.log(await modelKeysRevoke(ctx, id));
826
+ });
827
+
828
+ const members = program.command("members").description("Manage organization members.").action(helpOnlyAction("dharta members"));
829
+ members
830
+ .command("list")
831
+ .description("List members and pending invitations.")
832
+ .action(async (_opts: unknown, cmd: Command) => {
833
+ const ctx = await buildContext();
834
+ if (isJson(cmd)) printJson(await membersListData(ctx));
835
+ else console.log(await membersList(ctx));
836
+ });
837
+ members
838
+ .command("invite <email>")
839
+ .description("Invite a member.")
840
+ .addOption(choiceOption("--role <role>", "owner, admin, developer, billing, or viewer", MEMBER_ROLES))
841
+ .action(async (email: string, opts: { role?: string }, cmd: Command) => {
842
+ if (opts.role === undefined) {
843
+ throw new Error("--role is required. Usage: dharta members invite <email> --role <role>");
844
+ }
845
+ validateMembersInviteInput(email);
846
+ const ctx = await buildContext();
847
+ if (isJson(cmd)) printJson(await membersInviteData(ctx, email, opts.role));
848
+ else console.log(await membersInvite(ctx, email, opts.role));
849
+ });
850
+ members
851
+ .command("set-role <id>")
852
+ .description("Change a member role.")
853
+ .addOption(choiceOption("--role <role>", "owner, admin, developer, billing, or viewer", MEMBER_ROLES))
854
+ .action(async (id: string, opts: { role?: string }, cmd: Command) => {
855
+ if (opts.role === undefined) {
856
+ throw new Error("--role is required. Usage: dharta members set-role <id> --role <role>");
857
+ }
858
+ parsePositiveInteger(id, "member id");
859
+ const ctx = await buildContext();
860
+ if (isJson(cmd)) printJson(await membersSetRoleData(ctx, id, opts.role));
861
+ else console.log(await membersSetRole(ctx, id, opts.role));
862
+ });
863
+ members
864
+ .command("remove <id>")
865
+ .description("Remove a member.")
866
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
867
+ parsePositiveInteger(id, "member id");
868
+ const ctx = await buildContext();
869
+ if (isJson(cmd)) printJson(await membersRemoveData(ctx, id));
870
+ else console.log(await membersRemove(ctx, id));
871
+ });
872
+
873
+ const kartas = program.command("kartas").description("Manage durable kartas and virtual employees.").action(helpOnlyAction("dharta kartas"));
874
+ kartas
875
+ .command("create <name>")
876
+ .description("Create a named karta with a stable URL and managed access.")
877
+ .option("--agent <slug>", "Owning agent slug")
878
+ .option("--slug <slug>", "Vanity slug beneath the agent URL")
879
+ .addOption(choiceOption("--access <audience>", "members (specific people), anonymous (anyone with link), or off", DHARTA_ACCESS_AUDIENCES).default("members"))
880
+ .option("--member <email>", "Organization user to add; repeat for more", collect, [])
881
+ .addOption(choiceOption("--member-role <role>", "Role for people added with --member", DHARTA_MEMBER_ROLES).default("participant"))
882
+ .action(async (name: string, opts: { agent?: string; slug?: string; access?: string; member?: string[]; memberRole?: string }, cmd: Command) => {
883
+ const options = { ...opts, members: opts.member };
884
+ validateKartaCreateOptions(name, options);
885
+ const ctx = await buildContext();
886
+ if (isJson(cmd)) printJson(await kartasCreateData(ctx, name, options));
887
+ else console.log(await kartasCreate(ctx, name, options));
888
+ });
889
+ kartas
890
+ .command("list")
891
+ .description("List durable kartas and their stable URLs.")
892
+ .option("--agent <slug>", "Limit results to one owning agent")
893
+ .option("--named", "Show only kartas with a name and vanity slug")
894
+ .option("--archived", "Show archived kartas instead of active ones")
895
+ .action(async (opts: { agent?: string; named?: boolean; archived?: boolean }, cmd: Command) => {
896
+ const ctx = await buildContext();
897
+ if (isJson(cmd)) printJson(await kartasListData(ctx, opts));
898
+ else console.log(await kartasList(ctx, opts));
899
+ });
900
+ kartas
901
+ .command("show <id>")
902
+ .description("Show one karta, its stable URL, and how access is managed.")
903
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
904
+ normalizeNonBlank(id, "dharta id");
905
+ const ctx = await buildContext();
906
+ if (isJson(cmd)) printJson(await kartasShowData(ctx, id));
907
+ else console.log(await kartasShow(ctx, id));
908
+ });
909
+ kartas
910
+ .command("name <id>")
911
+ .description("Give an existing karta a name and vanity slug without changing access.")
912
+ .option("--name <name>", "Display name")
913
+ .option("--slug <slug>", "Vanity slug beneath the agent URL")
914
+ .action(async (id: string, opts: { name?: string; slug?: string }, cmd: Command) => {
915
+ validateKartaNameOptions(opts);
916
+ const ctx = await buildContext();
917
+ if (isJson(cmd)) printJson(await kartasNameData(ctx, id, opts));
918
+ else console.log(await kartasName(ctx, id, opts));
919
+ });
920
+ kartas
921
+ .command("configure <id>")
922
+ .description("Set a karta name, vanity slug, and stable URL access.")
923
+ .option("--name <name>", "Display name")
924
+ .option("--slug <slug>", "Vanity slug beneath the agent URL")
925
+ .addOption(choiceOption("--access <audience>", "members (specific people), anonymous (anyone with link), or off", DHARTA_ACCESS_AUDIENCES))
926
+ .action(async (id: string, opts: { name?: string; slug?: string; access?: string }, cmd: Command) => {
927
+ validateKartaConfigureOptions(opts);
928
+ const ctx = await buildContext();
929
+ if (isJson(cmd)) printJson(await kartasConfigureData(ctx, id, opts));
930
+ else console.log(await kartasConfigure(ctx, id, opts));
931
+ });
932
+ kartas
933
+ .command("archive <id>")
934
+ .alias("delete")
935
+ .description("Retire a karta (reversible; its files and history are kept).")
936
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
937
+ normalizeNonBlank(id, "dharta id");
938
+ const ctx = await buildContext();
939
+ if (isJson(cmd)) printJson(await kartasArchiveData(ctx, id));
940
+ else console.log(await kartasArchive(ctx, id));
941
+ });
942
+ kartas
943
+ .command("restore <id>")
944
+ .description("Bring an archived karta back, with its files and history.")
945
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
946
+ normalizeNonBlank(id, "dharta id");
947
+ const ctx = await buildContext();
948
+ if (isJson(cmd)) printJson(await kartasRestoreData(ctx, id));
949
+ else console.log(await kartasRestore(ctx, id));
950
+ });
951
+ const kartaEnv = kartas
952
+ .command("env")
953
+ .description("Configure ONE karta's environment (its own credentials and settings).")
954
+ .action(helpOnlyAction("dharta kartas env"));
955
+ kartaEnv
956
+ .command("list <agent> <karta>")
957
+ .description("List a karta's environment variables. Secret values are never shown.")
958
+ .action(async (agent: string, karta: string, _opts: unknown, cmd: Command) => {
959
+ const ctx = await buildContext();
960
+ if (isJson(cmd)) printJson(await kartaEnvListData(ctx, agent, karta));
961
+ else console.log(await kartaEnvList(ctx, agent, karta));
962
+ });
963
+ kartaEnv
964
+ .command("set <agent> <karta> <name> <value>")
965
+ .description("Set one variable on one dharta.")
966
+ .option("--plain", "Store as a readable value (default: secret)")
967
+ .action(async (agent: string, karta: string, name: string, value: string, opts: { plain?: boolean }, cmd: Command) => {
968
+ const ctx = await buildContext();
969
+ const options = { secret: opts.plain ? false : undefined };
970
+ if (isJson(cmd)) printJson(await kartaEnvSetData(ctx, agent, karta, name, value, options));
971
+ else console.log(await kartaEnvSet(ctx, agent, karta, name, value, options));
972
+ });
973
+ kartaEnv
974
+ .command("unset <agent> <karta> <name>")
975
+ .description("Remove one variable from one dharta.")
976
+ .action(async (agent: string, karta: string, name: string, _opts: unknown, cmd: Command) => {
977
+ const ctx = await buildContext();
978
+ if (isJson(cmd)) printJson(await kartaEnvUnsetData(ctx, agent, karta, name));
979
+ else console.log(await kartaEnvUnset(ctx, agent, karta, name));
980
+ });
981
+ kartas
982
+ .command("grant <id> <email>")
983
+ .description("Add an organization user to a karta with managed access.")
984
+ .addOption(choiceOption("--role <role>", "viewer, participant, or manager", DHARTA_MEMBER_ROLES).default("participant"))
985
+ .action(async (id: string, email: string, opts: { role: string }, cmd: Command) => {
986
+ const ctx = await buildContext();
987
+ if (isJson(cmd)) printJson(await kartaMembersGrantData(ctx, id, email, opts.role));
988
+ else console.log(await kartaMembersGrant(ctx, id, email, opts.role));
989
+ });
990
+ kartas
991
+ .command("set-role <id> <membership-id>")
992
+ .description("Change a person's role on a karta with managed access.")
993
+ .addOption(choiceOption("--role <role>", "viewer, participant, or manager", DHARTA_MEMBER_ROLES))
994
+ .action(async (id: string, membershipId: string, opts: { role?: string }, cmd: Command) => {
995
+ if (opts.role === undefined) {
996
+ throw new Error("--role is required. Usage: dharta kartas set-role <id> <membership-id> --role <role>");
997
+ }
998
+ const ctx = await buildContext();
999
+ if (isJson(cmd)) printJson(await kartaMembersSetRoleData(ctx, id, membershipId, opts.role));
1000
+ else console.log(await kartaMembersSetRole(ctx, id, membershipId, opts.role));
1001
+ });
1002
+ kartas
1003
+ .command("revoke <id> <membership-id>")
1004
+ .description("Remove a person's access to a dharta.")
1005
+ .action(async (id: string, membershipId: string, _opts: unknown, cmd: Command) => {
1006
+ const ctx = await buildContext();
1007
+ if (isJson(cmd)) printJson(await kartaMembersRevokeData(ctx, id, membershipId));
1008
+ else console.log(await kartaMembersRevoke(ctx, id, membershipId));
1009
+ });
1010
+
1011
+ const embedKeys = program.command("embed-keys").description("Manage publishable widget embed keys.").action(helpOnlyAction("dharta embed-keys"));
1012
+ const embedKeyOpts = (cmd: ReturnType<typeof embedKeys.command>) =>
1013
+ cmd
1014
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1015
+ .option("-p, --project <slug>", "Alias of --agent")
1016
+ .option("--dir <dir>", "Agent directory (default: current directory)");
1017
+ embedKeyOpts(embedKeys.command("list"))
1018
+ .description("List publishable embed keys for an agent.")
1019
+ .action(async (_opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1020
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1021
+ const resolved = await resolveAgentOptions(opts);
1022
+ const ctx = await buildContext();
1023
+ if (isJson(cmd)) printJson(await embedKeysListData(ctx, resolved));
1024
+ else console.log(await embedKeysList(ctx, resolved));
1025
+ });
1026
+ embedKeyOpts(embedKeys.command("create <name>"))
1027
+ .description("Create a publishable embed key and reveal its secret once.")
1028
+ .option("--origin <origin...>", "Allowed browser origin; repeat or comma-separate")
1029
+ .action(async (name: string, _opts: { agent?: string; project?: string; dir?: string; origin?: string[] }, cmd: Command) => {
1030
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string; origin?: string[] }>(cmd);
1031
+ const origins = opts.origin === undefined ? undefined : parseOrigins(opts.origin);
1032
+ validateEmbedKeysCreateInput(name, { origins });
1033
+ const resolved = await resolveAgentOptions({ ...opts, origins });
1034
+ const ctx = await buildContext();
1035
+ if (isJson(cmd)) printJson(await embedKeysCreateData(ctx, name, resolved));
1036
+ else console.log(await embedKeysCreate(ctx, name, resolved));
1037
+ });
1038
+ embedKeyOpts(embedKeys.command("rotate <id>"))
1039
+ .description("Rotate an embed key and reveal the replacement secret once.")
1040
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1041
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1042
+ normalizeNonBlank(id, "Embed key id");
1043
+ const resolved = await resolveAgentOptions(opts);
1044
+ const ctx = await buildContext();
1045
+ if (isJson(cmd)) printJson(await embedKeysRotateData(ctx, id, resolved));
1046
+ else console.log(await embedKeysRotate(ctx, id, resolved));
1047
+ });
1048
+ for (const action of ["enable", "disable"] as const) {
1049
+ embedKeyOpts(embedKeys.command(`${action} <id>`))
1050
+ .description(`${action === "enable" ? "Enable" : "Disable"} an embed key.`)
1051
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1052
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1053
+ normalizeNonBlank(id, "Embed key id");
1054
+ const resolved = await resolveAgentOptions(opts);
1055
+ const ctx = await buildContext();
1056
+ if (isJson(cmd)) printJson(await embedKeysToggleData(ctx, id, action, resolved));
1057
+ else console.log(await embedKeysToggle(ctx, id, action, resolved));
1058
+ });
1059
+ }
1060
+ embedKeyOpts(embedKeys.command("revoke <id>"))
1061
+ .description("Revoke an embed key permanently.")
1062
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1063
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1064
+ normalizeNonBlank(id, "Embed key id");
1065
+ const resolved = await resolveAgentOptions(opts);
1066
+ const ctx = await buildContext();
1067
+ if (isJson(cmd)) printJson(await embedKeysRevokeData(ctx, id, resolved));
1068
+ else console.log(await embedKeysRevoke(ctx, id, resolved));
1069
+ });
1070
+ embedKeyOpts(embedKeys.command("enable-identity <id>"))
1071
+ .description("Enable verified widget identity and reveal the signing secret once (re-run to rotate it).")
1072
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1073
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1074
+ normalizeNonBlank(id, "Embed key id");
1075
+ const resolved = await resolveAgentOptions(opts);
1076
+ const ctx = await buildContext();
1077
+ if (isJson(cmd)) printJson(await embedKeysSetIdentityData(ctx, id, resolved));
1078
+ else console.log(await embedKeysSetIdentity(ctx, id, resolved));
1079
+ });
1080
+ embedKeyOpts(embedKeys.command("disable-identity <id>"))
1081
+ .description("Disable verified widget identity for an embed key.")
1082
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1083
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1084
+ normalizeNonBlank(id, "Embed key id");
1085
+ const resolved = await resolveAgentOptions(opts);
1086
+ const ctx = await buildContext();
1087
+ if (isJson(cmd)) printJson(await embedKeysClearIdentityData(ctx, id, resolved));
1088
+ else console.log(await embedKeysClearIdentity(ctx, id, resolved));
1089
+ });
1090
+
1091
+ const caps = program.command("caps").description("Manage spend caps and the monthly budget.").action(helpOnlyAction("dharta caps"));
1092
+ const addBudgetSetCommand = (cmd: Command, description = "Set the monthly budget amount.") =>
1093
+ cmd
1094
+ .description(description)
1095
+ .option("--amount <dollars>", "Budget amount in dollars")
1096
+ .option("--hard-stop", "Stop usage when the budget is reached")
1097
+ .option("--soft", "Track spend and alerts without stopping usage")
1098
+ .option("--enable-alerts", "Enable threshold alerts")
1099
+ .option("--disable-alerts", "Disable threshold alerts")
1100
+ .option("--alert-threshold <percent>", "Alert threshold percent")
1101
+ .option("--recipient-user-id <id...>", "Additional alert recipient user id")
1102
+ .action(
1103
+ async (
1104
+ opts: {
1105
+ amount?: string;
1106
+ hardStop?: boolean;
1107
+ soft?: boolean;
1108
+ enableAlerts?: boolean;
1109
+ disableAlerts?: boolean;
1110
+ alertThreshold?: string;
1111
+ recipientUserId?: string[];
1112
+ },
1113
+ cmd: Command,
1114
+ ) => {
1115
+ validateBudgetSetOptions(opts);
1116
+ const ctx = await buildContext();
1117
+ if (isJson(cmd)) printJson(await capsBudgetSetData(ctx, opts));
1118
+ else console.log(await capsBudgetSet(ctx, opts));
1119
+ },
1120
+ );
1121
+ const capsBudgetCmd = caps
1122
+ .command("budget")
1123
+ .description("Show the monthly budget and current spend.")
1124
+ .action(async (_opts: unknown, cmd: Command) => {
1125
+ const ctx = await buildContext();
1126
+ if (isJson(cmd)) printJson(await capsBudgetData(ctx));
1127
+ else console.log(await capsBudget(ctx));
1128
+ });
1129
+ addBudgetSetCommand(capsBudgetCmd.command("set"));
1130
+ addBudgetSetCommand(caps.command("set"), "Set the monthly budget amount (alias of `dharta caps budget set`).");
1131
+ caps
1132
+ .command("instance <id>")
1133
+ .description("Show the spend cap for a durable karta.")
1134
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1135
+ normalizeNonBlank(id, "instance id");
1136
+ const ctx = await buildContext();
1137
+ if (isJson(cmd)) printJson(await capsInstanceData(ctx, id));
1138
+ else console.log(await capsInstance(ctx, id));
1139
+ });
1140
+ caps
1141
+ .command("instance-set <id>")
1142
+ .description("Set or clear a durable karta spend cap.")
1143
+ .option("--amount <dollars>", "Cap amount in dollars")
1144
+ .option("--clear", "Clear the cap")
1145
+ .action(async (id: string, opts: { amount?: string; clear?: boolean }, cmd: Command) => {
1146
+ normalizeNonBlank(id, "instance id");
1147
+ validateCapSetOptions(opts);
1148
+ const ctx = await buildContext();
1149
+ if (isJson(cmd)) printJson(await capsInstanceSetData(ctx, id, opts));
1150
+ else console.log(await capsInstanceSet(ctx, id, opts));
1151
+ });
1152
+ caps
1153
+ .command("end-user <id>")
1154
+ .description("Show the global spend cap for a verified end-user.")
1155
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1156
+ normalizeNonBlank(id, "end-user id");
1157
+ const ctx = await buildContext();
1158
+ if (isJson(cmd)) printJson(await capsEndUserData(ctx, id));
1159
+ else console.log(await capsEndUser(ctx, id));
1160
+ });
1161
+ caps
1162
+ .command("end-user-set <id>")
1163
+ .description("Set or clear the global spend cap for a verified end-user.")
1164
+ .option("--amount <dollars>", "Cap amount in dollars")
1165
+ .option("--clear", "Clear the cap")
1166
+ .action(async (id: string, opts: { amount?: string; clear?: boolean }, cmd: Command) => {
1167
+ normalizeNonBlank(id, "end-user id");
1168
+ validateCapSetOptions(opts);
1169
+ const ctx = await buildContext();
1170
+ if (isJson(cmd)) printJson(await capsEndUserSetData(ctx, id, opts));
1171
+ else console.log(await capsEndUserSet(ctx, id, opts));
1172
+ });
1173
+ caps
1174
+ .command("seat <instance> <end-user-id>")
1175
+ .description("Show the per-seat spend cap for an end-user on a durable karta.")
1176
+ .action(async (instance: string, endUserId: string, _opts: unknown, cmd: Command) => {
1177
+ normalizeNonBlank(instance, "instance id");
1178
+ normalizeNonBlank(endUserId, "end-user id");
1179
+ const ctx = await buildContext();
1180
+ if (isJson(cmd)) printJson(await capsSeatData(ctx, instance, endUserId));
1181
+ else console.log(await capsSeat(ctx, instance, endUserId));
1182
+ });
1183
+ caps
1184
+ .command("seat-set <instance> <end-user-id>")
1185
+ .description("Set or clear the per-seat spend cap for an end-user on a durable karta.")
1186
+ .option("--amount <dollars>", "Cap amount in dollars")
1187
+ .option("--clear", "Clear the cap")
1188
+ .action(async (instance: string, endUserId: string, opts: { amount?: string; clear?: boolean }, cmd: Command) => {
1189
+ normalizeNonBlank(instance, "instance id");
1190
+ normalizeNonBlank(endUserId, "end-user id");
1191
+ validateCapSetOptions(opts);
1192
+ const ctx = await buildContext();
1193
+ if (isJson(cmd)) printJson(await capsSeatSetData(ctx, instance, endUserId, opts));
1194
+ else console.log(await capsSeatSet(ctx, instance, endUserId, opts));
1195
+ });
1196
+
1197
+ const appearance = program.command("appearance").description("Get or update an agent's appearance theme.").action(helpOnlyAction("dharta appearance"));
1198
+ const appearanceOpts = (cmd: ReturnType<typeof appearance.command>) =>
1199
+ cmd
1200
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1201
+ .option("-p, --project <slug>", "Alias of --agent")
1202
+ .option("--dir <dir>", "Agent directory (default: current directory)");
1203
+ appearanceOpts(appearance.command("get"))
1204
+ .description("Print the stored appearance JSON for an agent.")
1205
+ .action(async (_opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1206
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1207
+ const resolved = await resolveAgentOptions(opts);
1208
+ const ctx = await buildContext();
1209
+ if (isJson(cmd)) printJson(await appearanceGetData(ctx, resolved));
1210
+ else console.log(await appearanceGet(ctx, resolved));
1211
+ });
1212
+ appearanceOpts(appearance.command("set"))
1213
+ .description("Merge appearance JSON from a local file.")
1214
+ .option("--file <file>", "JSON file containing appearance tokens")
1215
+ .action(async (_opts: { agent?: string; project?: string; dir?: string; file?: string }, cmd: Command) => {
1216
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string; file?: string }>(cmd);
1217
+ await validateAppearanceSetInput(opts);
1218
+ const resolved = await resolveAgentOptions(opts);
1219
+ const ctx = await buildContext();
1220
+ if (isJson(cmd)) printJson(await appearanceSetData(ctx, resolved));
1221
+ else console.log(await appearanceSet(ctx, resolved));
1222
+ });
1223
+
1224
+ const schedules = program.command("schedules").description("Manage recurring agent runs.").action(helpOnlyAction("dharta schedules"));
1225
+ const scheduleOpts = (cmd: ReturnType<typeof schedules.command>) =>
1226
+ cmd
1227
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1228
+ .option("-p, --project <slug>", "Alias of --agent")
1229
+ .option("--dir <dir>", "Agent directory (default: current directory)");
1230
+ scheduleOpts(schedules.command("list"))
1231
+ .description("List recurring runs for an agent.")
1232
+ .action(async (_opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1233
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1234
+ const resolved = await resolveAgentOptions(opts);
1235
+ const ctx = await buildContext();
1236
+ if (isJson(cmd)) printJson(await schedulesListData(ctx, resolved));
1237
+ else console.log(await schedulesList(ctx, resolved));
1238
+ });
1239
+ scheduleOpts(schedules.command("create"))
1240
+ .description("Create a recurring run.")
1241
+ .option("--cron <expr>", "Cron expression")
1242
+ .option("--timezone <tz>", "Timezone (default: UTC)")
1243
+ .option("--message <text>", "Initial message")
1244
+ .option("--budget <dollars>", "Run budget in dollars")
1245
+ .option("--budget-cents <cents>", "Run budget in integer cents")
1246
+ .option("--release-version <version>", "Pin runs to a release version")
1247
+ .option("--end-user-id <id>", "End-user id to run as")
1248
+ .option("--run-now", "Queue one run immediately after creating the recurring run")
1249
+ .action(async (
1250
+ _opts: {
1251
+ agent?: string;
1252
+ project?: string;
1253
+ dir?: string;
1254
+ cron?: string;
1255
+ timezone?: string;
1256
+ message?: string;
1257
+ budget?: string;
1258
+ budgetCents?: string;
1259
+ releaseVersion?: string;
1260
+ endUserId?: string;
1261
+ runNow?: boolean;
1262
+ },
1263
+ cmd: Command,
1264
+ ) => {
1265
+ const opts = commandOpts<{
1266
+ agent?: string;
1267
+ project?: string;
1268
+ dir?: string;
1269
+ cron?: string;
1270
+ timezone?: string;
1271
+ message?: string;
1272
+ budget?: string;
1273
+ budgetCents?: string;
1274
+ releaseVersion?: string;
1275
+ endUserId?: string;
1276
+ runNow?: boolean;
1277
+ }>(cmd);
1278
+ validateSchedulesCreateInput(opts);
1279
+ const resolved = await resolveAgentOptions(opts);
1280
+ const ctx = await buildContext();
1281
+ if (isJson(cmd)) printJson(await schedulesCreateData(ctx, resolved));
1282
+ else console.log(await schedulesCreate(ctx, resolved));
1283
+ });
1284
+ for (const action of ["pause", "resume", "run"] as const) {
1285
+ scheduleOpts(schedules.command(`${action} <id>`))
1286
+ .description(`${action === "run" ? "Queue" : action === "pause" ? "Pause" : "Resume"} a recurring run.`)
1287
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1288
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1289
+ parsePositiveInteger(normalizeNonBlank(id, "Schedule id"), "Schedule id");
1290
+ const resolved = await resolveAgentOptions(opts);
1291
+ const ctx = await buildContext();
1292
+ if (isJson(cmd)) printJson(await schedulesActionData(ctx, id, action, resolved));
1293
+ else console.log(await schedulesAction(ctx, id, action, resolved));
1294
+ });
1295
+ }
1296
+ scheduleOpts(schedules.command("delete <id>").alias("rm"))
1297
+ .description("Delete a recurring run.")
1298
+ .action(async (id: string, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1299
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1300
+ parsePositiveInteger(normalizeNonBlank(id, "Schedule id"), "Schedule id");
1301
+ const resolved = await resolveAgentOptions(opts);
1302
+ const ctx = await buildContext();
1303
+ if (isJson(cmd)) printJson(await schedulesDeleteData(ctx, id, resolved));
1304
+ else console.log(await schedulesDelete(ctx, id, resolved));
1305
+ });
1306
+
1307
+ const directories = program.command("directories").description("Read agent-scoped kartas, users, and sessions.").action(helpOnlyAction("dharta directories"));
1308
+ const directoryOpts = (cmd: ReturnType<typeof directories.command>) =>
1309
+ cmd
1310
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1311
+ .option("-p, --project <slug>", "Alias of --agent")
1312
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1313
+ .option("--limit <n>", "Maximum rows to return");
1314
+ directoryOpts(directories.command("instances"))
1315
+ .description("List durable kartas for an agent.")
1316
+ .action(async (_opts: DirectorySelectorOptions, cmd: Command) => {
1317
+ const opts = commandOpts<DirectorySelectorOptions>(cmd);
1318
+ validateDirectoryOptions(opts);
1319
+ const resolved = await resolveAgentOptions(opts);
1320
+ const ctx = await buildContext();
1321
+ if (isJson(cmd)) printJson(await directoryInstancesData(ctx, resolved));
1322
+ else console.log(await directoryInstances(ctx, resolved));
1323
+ });
1324
+ directoryOpts(directories.command("end-users").alias("users"))
1325
+ .description("List verified users for an agent.")
1326
+ .action(async (_opts: DirectorySelectorOptions, cmd: Command) => {
1327
+ const opts = commandOpts<DirectorySelectorOptions>(cmd);
1328
+ validateDirectoryOptions(opts);
1329
+ const resolved = await resolveAgentOptions(opts);
1330
+ const ctx = await buildContext();
1331
+ if (isJson(cmd)) printJson(await directoryEndUsersData(ctx, resolved));
1332
+ else console.log(await directoryEndUsers(ctx, resolved));
1333
+ });
1334
+ directoryOpts(directories.command("sessions"))
1335
+ .description("List sessions for an agent.")
1336
+ .action(async (_opts: DirectorySelectorOptions, cmd: Command) => {
1337
+ const opts = commandOpts<DirectorySelectorOptions>(cmd);
1338
+ validateDirectoryOptions(opts);
1339
+ const resolved = await resolveAgentOptions(opts);
1340
+ const ctx = await buildContext();
1341
+ if (isJson(cmd)) printJson(await directorySessionsData(ctx, resolved));
1342
+ else console.log(await directorySessions(ctx, resolved));
1343
+ });
1344
+
1345
+ const webhooks = program.command("webhooks").description("Manage webhook endpoints.").action(helpOnlyAction("dharta webhooks"));
1346
+ webhooks
1347
+ .command("list")
1348
+ .description("List webhook endpoints.")
1349
+ .action(async (_opts: unknown, cmd: Command) => {
1350
+ const ctx = await buildContext();
1351
+ if (isJson(cmd)) printJson(await webhooksListData(ctx));
1352
+ else console.log(await webhooksList(ctx));
1353
+ });
1354
+ webhooks
1355
+ .command("create <name> <url>")
1356
+ .description("Create a webhook endpoint and reveal its signing secret once.")
1357
+ .option("--event <event...>", "Subscribed event type; repeat or comma-separate")
1358
+ .action(async (name: string, url: string, opts: { event?: string[] }, cmd: Command) => {
1359
+ const resolved = { event: parseEvents(opts.event) };
1360
+ validateWebhooksCreateInput(name, url, resolved);
1361
+ const ctx = await buildContext();
1362
+ if (isJson(cmd)) printJson(await webhooksCreateData(ctx, name, url, resolved));
1363
+ else console.log(await webhooksCreate(ctx, name, url, resolved));
1364
+ });
1365
+ webhooks
1366
+ .command("update <id>")
1367
+ .description("Update a webhook endpoint.")
1368
+ .option("--name <name>", "Endpoint name")
1369
+ .option("--url <url>", "Destination URL")
1370
+ .option("--event <event...>", "Subscribed event type; repeat or comma-separate")
1371
+ .action(async (id: string, opts: { name?: string; url?: string; event?: string[] }, cmd: Command) => {
1372
+ const resolved = { name: opts.name, url: opts.url, event: opts.event ? parseEvents(opts.event) : undefined };
1373
+ validateWebhooksUpdateInput(id, resolved);
1374
+ const ctx = await buildContext();
1375
+ if (isJson(cmd)) printJson(await webhooksUpdateData(ctx, id, resolved));
1376
+ else console.log(await webhooksUpdate(ctx, id, resolved));
1377
+ });
1378
+ webhooks
1379
+ .command("enable <id>")
1380
+ .description("Re-enable a disabled webhook endpoint.")
1381
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1382
+ normalizeNonBlank(id, "Webhook endpoint id");
1383
+ const ctx = await buildContext();
1384
+ if (isJson(cmd)) printJson(await webhooksEnableData(ctx, id));
1385
+ else console.log(await webhooksEnable(ctx, id));
1386
+ });
1387
+ webhooks
1388
+ .command("delete <id>")
1389
+ .alias("rm")
1390
+ .description("Delete a webhook endpoint.")
1391
+ .action(async (id: string, _opts: unknown, cmd: Command) => {
1392
+ normalizeNonBlank(id, "Webhook endpoint id");
1393
+ const ctx = await buildContext();
1394
+ if (isJson(cmd)) printJson(await webhooksDeleteData(ctx, id));
1395
+ else console.log(await webhooksDelete(ctx, id));
1396
+ });
1397
+
1398
+ program
1399
+ .command("open [target]")
1400
+ .description(
1401
+ `Open an agent page or browser handoff target. Targets: ${OPEN_TARGETS_HELP}.`,
1402
+ )
1403
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)").option("-p, --project <slug>", "Alias of --agent")
1404
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1405
+ .action(
1406
+ async (
1407
+ target: string | undefined,
1408
+ opts: { agent?: string; project?: string; dir?: string },
1409
+ cmd: Command,
1410
+ ) => {
1411
+ const normalizedTarget = normalizeOpenTarget(target);
1412
+ validateOpenTarget(normalizedTarget);
1413
+ if (isHandoffTarget(normalizedTarget)) {
1414
+ const baseUrl = await resolveBaseUrl();
1415
+ if (isJson(cmd)) printJson(openHandoffData(baseUrl, normalizedTarget));
1416
+ else console.log(await openHandoff(baseUrl, normalizedTarget));
1417
+ return;
1418
+ }
1419
+ const needsAgent = normalizedTarget !== "console" ||
1420
+ opts.agent !== undefined ||
1421
+ opts.project !== undefined ||
1422
+ opts.dir !== undefined;
1423
+ const resolved = needsAgent ? await resolveAgentOptions(opts) : opts;
1424
+ const ctx = await buildContext();
1425
+ // --json prints the URL object and does NOT open the browser.
1426
+ if (isJson(cmd)) printJson(await openData(ctx, normalizedTarget, resolved));
1427
+ else console.log(await openCmd(ctx, normalizedTarget, resolved));
1428
+ },
1429
+ );
1430
+
1431
+ program
1432
+ .command("dev [path]")
1433
+ .description(
1434
+ "Preview your agent locally the way a USER experiences it - behind the same consumer session API production serves (widget parity), with hot reload. Distinct from running `claude` in the folder, which is authoring. [path] (or cwd) selects the agent; in a multi-agent repo it picks the sole one or asks.",
1435
+ )
1436
+ .option("--dir <dir>", "Agent directory (alias for [path]; default: current directory)")
1437
+ .option("-m, --message <text>", "Send one message and exit")
1438
+ .option("--user <id>", "Simulate a verified end user (per-user memory behavior)")
1439
+ .option("--port <port>", "Serve the local agent API on a stable port (for widgets/frontends)")
1440
+ .option("--verbose", "Stream agent-runtime output and parity details")
1441
+ .option("--release <vN>", "Run the exact built tree prod serves (audited archive pull)")
1442
+ .addOption(choiceOption("--env <mode>", "Bind prod env vars instead of .env", ["remote"]))
1443
+ .option("--include-secrets", "With --env remote: fetch secret values (requires env:secrets:read; audited)")
1444
+ .option("-a, --agent <slug>", "Agent for --release/--env (default: the agent here)").option("-p, --project <slug>", "Alias of --agent")
1445
+ .action(
1446
+ async (
1447
+ pathArg: string | undefined,
1448
+ opts: {
1449
+ dir?: string;
1450
+ message?: string;
1451
+ user?: string;
1452
+ port?: string;
1453
+ verbose?: boolean;
1454
+ release?: string;
1455
+ env?: string;
1456
+ includeSecrets?: boolean;
1457
+ agent?: string;
1458
+ project?: string;
1459
+ },
1460
+ cmd: Command,
1461
+ ) => {
1462
+ rejectJson(cmd, "dharta dev");
1463
+ if (pathArg !== undefined && opts.dir !== undefined) {
1464
+ throw new Error("Use either [path] or --dir, not both.");
1465
+ }
1466
+ const dir = pathArg !== undefined
1467
+ ? normalizeNonBlank(pathArg, "path")
1468
+ : opts.dir !== undefined
1469
+ ? normalizeNonBlank(opts.dir, "--dir")
1470
+ : undefined;
1471
+ if (opts.release !== undefined) parseReleaseVersion(opts.release, "--release");
1472
+ if (opts.includeSecrets && opts.env !== "remote") {
1473
+ throw new Error("--include-secrets requires --env remote.");
1474
+ }
1475
+ validateAgentSelectorOptions({ agent: opts.agent, project: opts.project });
1476
+ await devCmd({
1477
+ dir,
1478
+ message: opts.message,
1479
+ user: opts.user,
1480
+ verbose: opts.verbose,
1481
+ port: opts.port ? parsePort(opts.port) : undefined,
1482
+ release: opts.release,
1483
+ envRemote: opts.env === "remote",
1484
+ includeSecrets: opts.includeSecrets,
1485
+ project: opts.agent ?? opts.project,
1486
+ });
1487
+ },
1488
+ );
1489
+
1490
+ registerArtifactCommands(program);
1491
+
1492
+ const sessionsOpts = <T extends Command>(cmd: T): T =>
1493
+ cmd
1494
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1495
+ .option("-p, --project <slug>", "Alias of --agent")
1496
+ .option("--dir <dir>", "Agent directory (default: current directory)");
1497
+ const sessionsListOpts = <T extends Command>(cmd: T): T =>
1498
+ sessionsOpts(cmd)
1499
+ .option("--limit <n>", "Maximum rows to display");
1500
+ const sessionListSelector = async (cmd: Command, slugArg?: string): Promise<ResolvedAgentOptions<SessionListSelectorOptions>> => {
1501
+ const opts = commandOpts<SessionListSelectorOptions>(cmd);
1502
+ validateSessionsListOptions(opts);
1503
+ return resolveAgentOptions(opts, slugArg);
1504
+ };
1505
+ const sessionsListAction = async (cmd: Command, slugArg?: string) => {
1506
+ const opts = await sessionListSelector(cmd, slugArg);
1507
+ const ctx = await buildContext();
1508
+ if (isJson(cmd)) printJson(await sessionsListData(ctx, opts));
1509
+ else console.log(await sessionsList(ctx, opts));
1510
+ };
1511
+ const sessionsShowAction = async (id: string, cmd: Command, slugArg?: string) => {
1512
+ normalizeNonBlank(id, "Session id");
1513
+ const opts = await resolveAgentOptions(commandOpts<AgentSelectorOptions>(cmd), slugArg);
1514
+ const ctx = await buildContext();
1515
+ if (isJson(cmd)) printJson(await sessionsShowData(ctx, id, opts));
1516
+ else console.log(await sessionsShow(ctx, id, opts));
1517
+ };
1518
+
1519
+ const sessions = program
1520
+ .command("sessions")
1521
+ .description("List transcript sessions for a deployed agent; use `show`/`view` for transcripts.")
1522
+ .action(async (_opts: AgentSelectorOptions, cmd: Command) => sessionsListAction(cmd));
1523
+ sessionsListOpts(sessions);
1524
+ sessionsOpts(sessions
1525
+ .command("show <id>")
1526
+ .alias("view")
1527
+ .description("Render a session transcript (requires the org's transcript-access setting)."))
1528
+ .action(async (id: string, _opts: AgentSelectorOptions, cmd: Command) => sessionsShowAction(id, cmd));
1529
+ sessionsOpts(program
1530
+ .command("session <id>")
1531
+ .description("Render a session transcript; alias of `dharta sessions view`."))
1532
+ .action(async (id: string, _opts: AgentSelectorOptions, cmd: Command) => sessionsShowAction(id, cmd));
1533
+
1534
+ program
1535
+ .command("ps")
1536
+ .description("Sessions active right now: count, age, model, tokens.")
1537
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)").option("-p, --project <slug>", "Alias of --agent")
1538
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1539
+ .option("--limit <n>", "Maximum rows to display")
1540
+ .action(async (_opts: SessionListSelectorOptions, cmd: Command) => {
1541
+ const opts = commandOpts<SessionListSelectorOptions>(cmd);
1542
+ validateSessionsListOptions(opts);
1543
+ const resolved = await resolveAgentOptions(opts);
1544
+ const ctx = await buildContext();
1545
+ if (isJson(cmd)) printJson(await psData(ctx, resolved));
1546
+ else console.log(await psCmd(ctx, resolved));
1547
+ });
1548
+
1549
+ const files = program
1550
+ .command("files")
1551
+ .description("Read a karta's durable workspace files (owner channel, RFC 0060).")
1552
+ .action(helpOnlyAction("dharta files"));
1553
+ files
1554
+ .command("ls <agent> <karta>")
1555
+ .description("List a karta's durable workspace files (path and size).")
1556
+ .action(async (agent: string, karta: string, _opts: unknown, cmd: Command) => {
1557
+ const ctx = await buildContext();
1558
+ if (isJson(cmd)) printJson(await filesLsData(ctx, agent, karta));
1559
+ else console.log(await filesLs(ctx, agent, karta));
1560
+ });
1561
+ files
1562
+ .command("get <agent> <karta> <path>")
1563
+ .description("Fetch one workspace file; writes to --out, or streams bytes to stdout.")
1564
+ .option("-o, --out <file>", "Write the file's bytes to this path instead of stdout")
1565
+ .action(async (
1566
+ agent: string,
1567
+ karta: string,
1568
+ path: string,
1569
+ opts: { out?: string },
1570
+ cmd: Command,
1571
+ ) => {
1572
+ // Raw bytes, not a single JSON payload — --json has no meaning here.
1573
+ rejectJson(cmd, "dharta files get");
1574
+ const out = opts.out !== undefined ? normalizeNonBlank(opts.out, "--out") : undefined;
1575
+ const ctx = await buildContext();
1576
+ const msg = await filesGet(ctx, agent, karta, path, out);
1577
+ if (msg) console.log(msg);
1578
+ });
1579
+
1580
+ program
1581
+ .command("send <message>")
1582
+ .alias("run")
1583
+ .description("Send a message to a DEPLOYED agent and print its reply (prod smoke test).")
1584
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1585
+ .option("-p, --project <slug>", "Alias of --agent")
1586
+ .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1587
+ .option("--session-id <id>", "Continue an existing session instead of opening a new one")
1588
+ .action(
1589
+ async (
1590
+ message: string,
1591
+ _opts: { agent?: string; project?: string; dir?: string; sessionId?: string },
1592
+ cmd: Command,
1593
+ ) => {
1594
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string; sessionId?: string }>(cmd);
1595
+ normalizeNonBlank(message, "message");
1596
+ if (opts.sessionId !== undefined) normalizeNonBlank(opts.sessionId, "Session id");
1597
+ const resolved = await resolveAgentOptions(opts);
1598
+ const ctx = await buildContext();
1599
+ const result = await runCmd(ctx, message, { ...resolved, json: isJson(cmd) });
1600
+ if (isJson(cmd)) printJson(result);
1601
+ },
1602
+ );
1603
+
1604
+ const env = program
1605
+ .command("env")
1606
+ .description("Manage the agent's environment bindings (declare-or-bind).")
1607
+ .action(helpOnlyAction("dharta env"));
1608
+ const projectOpts = (cmd: ReturnType<typeof env.command>) =>
1609
+ cmd
1610
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)").option("-p, --project <slug>", "Alias of --agent")
1611
+ .option("--dir <dir>", "Agent directory (default: current directory)");
1612
+ projectOpts(env.command("list"))
1613
+ .description("List prod env bindings (values masked; --reveal to show).")
1614
+ .option("--reveal", "Print full values instead of masking them")
1615
+ .action(async (_opts: AgentSelectorOptions & { reveal?: boolean }, cmd: Command) => {
1616
+ const opts = commandOpts<AgentSelectorOptions & { reveal?: boolean }>(cmd);
1617
+ const resolved = await resolveAgentOptions(opts);
1618
+ const ctx = await buildContext();
1619
+ // --json returns raw values (machine-readable, opt-in); the human table
1620
+ // masks by default so a routine `env list` never leaks a token.
1621
+ if (isJson(cmd)) printJson(await envListData(ctx, resolved));
1622
+ else console.log(await envList(ctx, { ...resolved, reveal: opts.reveal === true }));
1623
+ });
1624
+ projectOpts(env.command("set <assignment>"))
1625
+ .description("Bind NAME=value in prod (--secret excludes it from pull).")
1626
+ .option("--secret", "Mark the value secret (cannot be unmarked without deleting)")
1627
+ .action(
1628
+ async (
1629
+ assignment: string,
1630
+ _opts: AgentSelectorOptions & { secret?: boolean },
1631
+ cmd: Command,
1632
+ ) => {
1633
+ const opts = commandOpts<AgentSelectorOptions & { secret?: boolean }>(cmd);
1634
+ validateEnvAssignment(assignment);
1635
+ const resolved = await resolveAgentOptions(opts);
1636
+ const ctx = await buildContext();
1637
+ if (isJson(cmd)) printJson(await envSetData(ctx, assignment, resolved));
1638
+ else console.log(await envSet(ctx, assignment, resolved));
1639
+ },
1640
+ );
1641
+ projectOpts(env.command("unset <name>"))
1642
+ .description("Remove a prod env binding.")
1643
+ .action(async (name: string, _opts: AgentSelectorOptions, cmd: Command) => {
1644
+ const opts = commandOpts<AgentSelectorOptions>(cmd);
1645
+ validateEnvName(name);
1646
+ const resolved = await resolveAgentOptions(opts);
1647
+ const ctx = await buildContext();
1648
+ if (isJson(cmd)) printJson(await envUnsetData(ctx, name, resolved));
1649
+ else console.log(await envUnset(ctx, name, resolved));
1650
+ });
1651
+ projectOpts(env.command("pull"))
1652
+ .description("Hydrate .env from prod values (audited; secrets excluded by default).")
1653
+ .option("--force", "Overwrite an existing .env")
1654
+ .option("--include-secrets", "Also fetch secret-marked values (requires env:secrets:read; audited)")
1655
+ .action(
1656
+ async (
1657
+ _opts: AgentSelectorOptions & { force?: boolean; includeSecrets?: boolean },
1658
+ cmd: Command,
1659
+ ) => {
1660
+ const opts = commandOpts<AgentSelectorOptions & { force?: boolean; includeSecrets?: boolean }>(cmd);
1661
+ await validateEnvPullOptions(opts);
1662
+ const resolved = await resolveAgentOptions(opts);
1663
+ const ctx = await buildContext();
1664
+ if (isJson(cmd)) printJson(await envPullData(ctx, resolved));
1665
+ else console.log(await envPull(ctx, resolved));
1666
+ },
1667
+ );
1668
+ projectOpts(env.command("diff"))
1669
+ .description("Report drift between .env, prod bindings, and dharta.toml [env].")
1670
+ .action(async (_opts: AgentSelectorOptions, cmd: Command) => {
1671
+ const opts = commandOpts<AgentSelectorOptions>(cmd);
1672
+ await validateEnvDiffOptions(opts);
1673
+ const resolved = await resolveAgentOptions(opts);
1674
+ const ctx = await buildContext();
1675
+ if (isJson(cmd)) printJson(await envDiffData(ctx, resolved));
1676
+ else console.log(await envDiff(ctx, resolved));
1677
+ });
1678
+
1679
+ program
1680
+ .command("update")
1681
+ .description("Self-update (standalone installs; npm installs update via npm).")
1682
+ .action(async (_opts: unknown, cmd: Command) => {
1683
+ if (isJson(cmd)) printJson({ message: await update({ log: logToStderr }) });
1684
+ else console.log(await update());
1685
+ });
1686
+
1687
+ program
1688
+ .command("doctor")
1689
+ .description("Diagnose the local setup: PATH shadowing, runtime bootstrap, login state.")
1690
+ .action(async (_opts: unknown, cmd: Command) => {
1691
+ const report = await doctor();
1692
+ if (isJson(cmd)) printJson({ ok: report.ok, checks: report.lines });
1693
+ else console.log(report.lines.join("\n"));
1694
+ if (!report.ok) process.exitCode = 1;
1695
+ });
1696
+
1697
+ program
1698
+ .command("usage")
1699
+ .description("Show usage totals and budget for the current period.")
1700
+ .action(async (_opts: unknown, cmd: Command) => {
1701
+ const ctx = await buildContext();
1702
+ if (isJson(cmd)) printJson(await usageData(ctx));
1703
+ else console.log(await usageCmd(ctx));
1704
+ });
1705
+
1706
+ const agent = program.command("agent").description("Inspect and manage your agents.").action(helpOnlyAction("dharta agent"));
1707
+ agent
1708
+ .command("list")
1709
+ .description("List all agents in your org.")
1710
+ .action(async (_opts: unknown, cmd: Command) => {
1711
+ const ctx = await buildContext();
1712
+ if (isJson(cmd)) printJson(await agentsListData(ctx));
1713
+ else console.log(await agentsList(ctx));
1714
+ });
1715
+ agent
1716
+ .command("show [slug]")
1717
+ .description("Show an agent + its recent releases (the agent in this folder if no slug given).")
1718
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1719
+ .option("-p, --project <slug>", "Alias of --agent")
1720
+ .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1721
+ .action(async (slug: string | undefined, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1722
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1723
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
1724
+ const ctx = await buildContext();
1725
+ const resolved = resolvedOpts.agent;
1726
+ if (isJson(cmd)) printJson(await agentsShowData(ctx, resolved));
1727
+ else console.log(await agentsShow(ctx, resolved));
1728
+ });
1729
+ const agentSessions = agent
1730
+ .command("sessions [slug]")
1731
+ .description("List transcript sessions for an agent; use `view <id>` for transcripts.")
1732
+ .action(async (slug: string | undefined, _opts: AgentSelectorOptions, cmd: Command) => sessionsListAction(cmd, slug));
1733
+ sessionsListOpts(agentSessions);
1734
+ sessionsOpts(agentSessions
1735
+ .command("show <id> [slug]")
1736
+ .alias("view")
1737
+ .description("Render a session transcript; alias of `dharta sessions show`."))
1738
+ .action(async (id: string, slug: string | undefined, _opts: AgentSelectorOptions, cmd: Command) =>
1739
+ sessionsShowAction(id, cmd, slug),
1740
+ );
1741
+ sessionsOpts(agent
1742
+ .command("session <id> [slug]")
1743
+ .description("Render a session transcript; alias of `dharta sessions view`."))
1744
+ .action(async (id: string, slug: string | undefined, _opts: AgentSelectorOptions, cmd: Command) =>
1745
+ sessionsShowAction(id, cmd, slug),
1746
+ );
1747
+ agent
1748
+ .command("releases [slug]")
1749
+ .description("List an agent's releases.")
1750
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1751
+ .option("-p, --project <slug>", "Alias of --agent")
1752
+ .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1753
+ .action(async (slug: string | undefined, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1754
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1755
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
1756
+ const ctx = await buildContext();
1757
+ const resolved = resolvedOpts.agent;
1758
+ if (isJson(cmd)) printJson(await agentsReleasesData(ctx, resolved));
1759
+ else console.log(await agentsReleases(ctx, resolved));
1760
+ });
1761
+ agent
1762
+ .command("activate <version> [slug]")
1763
+ .description("Activate an existing production release.")
1764
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1765
+ .option("-p, --project <slug>", "Alias of --agent")
1766
+ .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1767
+ .action(async (version: string, slug: string | undefined, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1768
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1769
+ parseVersion(version);
1770
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
1771
+ const ctx = await buildContext();
1772
+ const resolved = resolvedOpts.agent;
1773
+ if (isJson(cmd)) printJson(await agentsActivateReleaseData(ctx, resolved, version));
1774
+ else console.log(await agentsActivateRelease(ctx, resolved, version));
1775
+ });
1776
+ agent
1777
+ .command("serving <state> [slug]")
1778
+ .description(`Resume or pause serving for an agent. ${STATE_HELP}`)
1779
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1780
+ .option("-p, --project <slug>", "Alias of --agent")
1781
+ .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1782
+ .action(async (state: string, slug: string | undefined, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1783
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1784
+ const enabled = parseOnOff(state, "state");
1785
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
1786
+ const ctx = await buildContext();
1787
+ const resolved = resolvedOpts.agent;
1788
+ if (isJson(cmd)) printJson(await agentsServingSetData(ctx, resolved, enabled));
1789
+ else console.log(await agentsServingSet(ctx, resolved, enabled));
1790
+ });
1791
+ agent
1792
+ .command("dashboard-edits <state> [slug]")
1793
+ .description(`Enable or disable browser-console edits for an agent. ${STATE_HELP}`)
1794
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1795
+ .option("-p, --project <slug>", "Alias of --agent")
1796
+ .option("--dir <dir>", "Directory to resolve the agent from (default: current directory)")
1797
+ .action(async (state: string, slug: string | undefined, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1798
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1799
+ const enabled = parseOnOff(state, "state");
1800
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
1801
+ const ctx = await buildContext();
1802
+ const resolved = resolvedOpts.agent;
1803
+ if (isJson(cmd)) printJson(await agentsDashboardEditsSetData(ctx, resolved, enabled));
1804
+ else console.log(await agentsDashboardEditsSet(ctx, resolved, enabled));
1805
+ });
1806
+ agent
1807
+ .command("model [slug]")
1808
+ .description("Set an agent model override.")
1809
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1810
+ .option("-p, --project <slug>", "Alias of --agent")
1811
+ .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")
1815
+ .option("--temperature <number>", "Sampling temperature from 0 to 2")
1816
+ .option("--clear-temperature", "Inherit temperature")
1817
+ .option("--top-p <number>", "Nucleus sampling probability above 0 and at most 1")
1818
+ .option("--clear-top-p", "Inherit top-p")
1819
+ .option("--max-output-tokens <number>", "Positive output token limit")
1820
+ .option("--clear-max-output-tokens", "Inherit the output token limit")
1821
+ .addOption(choiceOption("--reasoning-effort <level>", "Reasoning effort", ["low", "medium", "high"]))
1822
+ .option("--clear-reasoning-effort", "Inherit reasoning effort")
1823
+ .action(async (
1824
+ slug: string | undefined,
1825
+ _opts: AgentModelSetOptions & { agent?: string; project?: string; dir?: string },
1826
+ cmd: Command,
1827
+ ) => {
1828
+ const opts = commandOpts<AgentModelSetOptions & { agent?: string; project?: string; dir?: string }>(cmd);
1829
+ validateAgentModelSetOptions(opts);
1830
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
1831
+ const ctx = await buildContext();
1832
+ const resolved = resolvedOpts.agent;
1833
+ if (isJson(cmd)) printJson(await agentsModelSetData(ctx, resolved, opts));
1834
+ else console.log(await agentsModelSet(ctx, resolved, opts));
1835
+ });
1836
+ agent
1837
+ .command("workspace-inspectable <state> [slug]")
1838
+ .description(`Enable or disable durable workspace inspection for an agent. ${STATE_HELP}`)
1839
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1840
+ .option("-p, --project <slug>", "Alias of --agent")
1841
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1842
+ .action(async (
1843
+ state: string,
1844
+ slugArg: string | undefined,
1845
+ _opts: { agent?: string; project?: string; dir?: string },
1846
+ cmd: Command,
1847
+ ) => {
1848
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1849
+ const enabled = parseOnOff(state, "state");
1850
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
1851
+ const ctx = await buildContext();
1852
+ const resolved = resolvedOpts.agent;
1853
+ if (isJson(cmd)) printJson(await agentsWorkspaceInspectableSetData(ctx, resolved, enabled));
1854
+ else console.log(await agentsWorkspaceInspectableSet(ctx, resolved, enabled));
1855
+ });
1856
+ const workspaceAccess = agent
1857
+ .command("workspace-access")
1858
+ .description("Show or set an agent's workspace access grants (RFC 0060).");
1859
+ workspaceAccess
1860
+ .command("show [slug]")
1861
+ .description("Show the audience x channel workspace access grant matrix.")
1862
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1863
+ .option("-p, --project <slug>", "Alias of --agent")
1864
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1865
+ .action(async (
1866
+ slugArg: string | undefined,
1867
+ _opts: { agent?: string; project?: string; dir?: string },
1868
+ cmd: Command,
1869
+ ) => {
1870
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1871
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
1872
+ const ctx = await buildContext();
1873
+ const resolved = resolvedOpts.agent;
1874
+ if (isJson(cmd)) printJson(await agentsWorkspaceAccessShowData(ctx, resolved));
1875
+ else console.log(await agentsWorkspaceAccessShow(ctx, resolved));
1876
+ });
1877
+ workspaceAccess
1878
+ .command("set <grant> [slug]")
1879
+ .description(
1880
+ "Grant or revoke one access path (<path>=<on|off>). Paths: owners.git, owners.api, users.widget, users.agent_definition. e.g. dharta agent workspace-access set users.widget=on",
1881
+ )
1882
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1883
+ .option("-p, --project <slug>", "Alias of --agent")
1884
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1885
+ .action(async (
1886
+ grant: string,
1887
+ slugArg: string | undefined,
1888
+ _opts: { agent?: string; project?: string; dir?: string },
1889
+ cmd: Command,
1890
+ ) => {
1891
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1892
+ const patch = parseWorkspaceAccessAssignments([grant]);
1893
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
1894
+ const ctx = await buildContext();
1895
+ const resolved = resolvedOpts.agent;
1896
+ if (isJson(cmd)) printJson(await agentsWorkspaceAccessSetData(ctx, resolved, patch));
1897
+ else console.log(await agentsWorkspaceAccessSet(ctx, resolved, patch));
1898
+ });
1899
+ agent
1900
+ .command("delete [slug]")
1901
+ .description("Archive an agent.")
1902
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)")
1903
+ .option("-p, --project <slug>", "Alias of --agent")
1904
+ .option("--dir <dir>", "Agent directory (default: current directory)")
1905
+ .action(async (slugArg: string | undefined, _opts: { agent?: string; project?: string; dir?: string }, cmd: Command) => {
1906
+ const opts = commandOpts<{ agent?: string; project?: string; dir?: string }>(cmd);
1907
+ const resolvedOpts = await resolveAgentOptions(opts, slugArg);
1908
+ const ctx = await buildContext();
1909
+ const resolved = resolvedOpts.agent;
1910
+ if (isJson(cmd)) printJson(await agentsDeleteData(ctx, resolved));
1911
+ else console.log(await agentsDelete(ctx, resolved));
1912
+ });
1913
+
1914
+ program
1915
+ .command("deploy [slug]")
1916
+ .description(
1917
+ "Deploy exactly one agent: the dharta.toml at/above --dir (default: cwd). Its deploy_method selects git or folder; --mode is a one-run override.",
1918
+ )
1919
+ .option("--dir <dir>", "Agent directory, or a subdirectory inside it (default: cwd)")
1920
+ .option(
1921
+ "--reconcile-source",
1922
+ "git only: intentionally align Dharta's hosted source branch to local HEAD after inspecting divergent history",
1923
+ )
1924
+ .addOption(
1925
+ choiceOption(
1926
+ "--mode <mode>",
1927
+ "one-run override: git (agent-targeted push) or folder (selected-folder upload). Default: deploy_method in dharta.toml.",
1928
+ ["git", "folder"],
1929
+ ),
1930
+ )
1931
+ .action(
1932
+ async (
1933
+ slug: string | undefined,
1934
+ opts: { dir?: string; mode?: "git" | "folder"; reconcileSource?: boolean },
1935
+ cmd: Command,
1936
+ ) => {
1937
+ const rawMode = opts.mode;
1938
+ if (slug !== undefined) validateAgentSlug(slug, "agent slug");
1939
+ const dir = normalizeNonBlank(opts.dir ?? ".", "--dir");
1940
+ validateDeployDir(dir);
1941
+ // Resolve the effective mode here only to enforce the JSON contract.
1942
+ // deploy() receives only an explicit override so it remains the one
1943
+ // owner of target/config resolution and can narrate legacy inference.
1944
+ const mode = rawMode ?? (await inferMode(dir));
1945
+ if (opts.reconcileSource && mode !== "git") {
1946
+ throw new Error(
1947
+ "--reconcile-source is only valid for git deploys. " +
1948
+ "Remove it, or use `--mode git` to reconcile Dharta's hosted source branch.",
1949
+ );
1950
+ }
1951
+ // git mode's output is the live push sideband, not a payload.
1952
+ const json = mode === "folder" && isJson(cmd);
1953
+ if (mode === "git" && isJson(cmd)) {
1954
+ throw new Error(
1955
+ "--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
+ );
1957
+ }
1958
+ const ctx = await buildContext();
1959
+ const result = await deploy(ctx, slug, {
1960
+ dir,
1961
+ mode: rawMode,
1962
+ reconcileSource: opts.reconcileSource,
1963
+ log: json ? logToStderr : undefined,
1964
+ });
1965
+ if (json) printJson(result);
1966
+ },
1967
+ );
1968
+
1969
+ program
1970
+ .command("status")
1971
+ .description(
1972
+ "Show every agent in this repo (resolved from the git root) and its live release on the active profile.",
1973
+ )
1974
+ .option("--dir <dir>", "Directory to scan (default: cwd; the whole repo when inside one)")
1975
+ .addOption(new Option("-a, --agent <slug>").hideHelp())
1976
+ .addOption(new Option("-p, --project <slug>").hideHelp())
1977
+ .action(async (opts: { dir?: string; agent?: string; project?: string }, cmd: Command) => {
1978
+ if (opts.agent !== undefined || opts.project !== undefined) {
1979
+ const slug = opts.agent ?? opts.project ?? "";
1980
+ 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.");
1982
+ }
1983
+ validateAgentSelectorOptions(opts);
1984
+ let ctx: Awaited<ReturnType<typeof buildContext>> | null = null;
1985
+ let authMessage: string | undefined;
1986
+ try {
1987
+ ctx = await buildContext();
1988
+ } catch (err) {
1989
+ if (!isNotLoggedInError(err)) throw err;
1990
+ authMessage = err instanceof Error ? err.message : String(err);
1991
+ }
1992
+ if (isJson(cmd)) printJson(await statusData(ctx, { dir: opts.dir, authMessage }));
1993
+ else console.log(await status(ctx, { dir: opts.dir, authMessage }));
1994
+ });
1995
+
1996
+ program
1997
+ .command("rollback [slug]")
1998
+ .description("Roll back to a previous release (requires releases:write).")
1999
+ .option("--to <version>", "target release, e.g. v3 or 3")
2000
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)").option("-p, --project <slug>", "Alias of --agent")
2001
+ .option("--dir <dir>", "Agent directory (default: current directory)")
2002
+ .action(
2003
+ async (
2004
+ slug: string | undefined,
2005
+ _opts: { to?: string; agent?: string; project?: string; dir?: string },
2006
+ cmd: Command,
2007
+ ) => {
2008
+ const opts = commandOpts<{ to?: string; agent?: string; project?: string; dir?: string }>(cmd);
2009
+ const v = parseRollbackTarget(opts.to);
2010
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
2011
+ const ctx = await buildContext();
2012
+ const resolved = resolvedOpts.agent;
2013
+ if (isJson(cmd)) printJson(await rollbackData(ctx, resolved, v));
2014
+ else console.log(await rollback(ctx, resolved, v));
2015
+ },
2016
+ );
2017
+
2018
+ program
2019
+ .command("logs [slug]")
2020
+ .description("Stream or poll agent logs (slug inferred from this folder if omitted).")
2021
+ .option("--tail", "Stream logs as they arrive (SSE if available)")
2022
+ .option("--since <iso>", "Only show logs after this timestamp (polling fallback)")
2023
+ .option("-a, --agent <slug>", "Agent slug (default: the agent in this folder)").option("-p, --project <slug>", "Alias of --agent")
2024
+ .option("--dir <dir>", "Agent directory (default: current directory)")
2025
+ .action(
2026
+ async (
2027
+ slug: string | undefined,
2028
+ _opts: { tail?: boolean; since?: string; agent?: string; project?: string; dir?: string },
2029
+ cmd: Command,
2030
+ ) => {
2031
+ const opts = commandOpts<{ tail?: boolean; since?: string; agent?: string; project?: string; dir?: string }>(cmd);
2032
+ rejectJson(cmd, "dharta logs");
2033
+ validateLogsOptions(opts);
2034
+ const resolvedOpts = await resolveAgentOptions(opts, slug);
2035
+ const ctx = await buildContext();
2036
+ const resolved = resolvedOpts.agent;
2037
+ const emitted = await logsTail(
2038
+ ctx,
2039
+ resolved,
2040
+ { tail: opts.tail, since: opts.since },
2041
+ (line) => process.stdout.write(line + "\n"),
2042
+ );
2043
+ if (!opts.tail && emitted === 0) {
2044
+ const window = opts.since?.trim() ? `since ${opts.since.trim()}` : "in the last minute";
2045
+ console.log(`No log entries for ${resolved} ${window}.`);
2046
+ }
2047
+ },
2048
+ );
2049
+
2050
+ program
2051
+ .command("completion")
2052
+ .description("Print a shell completion script (pipe it into your shell's setup).")
2053
+ .addArgument(choiceArgument("<shell>", "target shell", ["bash", "zsh"]))
2054
+ .action((shell: string, _opts: unknown, cmd: Command) => {
2055
+ rejectJson(cmd, "dharta completion");
2056
+ console.log(shell === "zsh" ? zshCompletion(program) : bashCompletion(program));
2057
+ });
2058
+
2059
+ // RFC 0013 §3: --json on every command. Registered on every node of
2060
+ // the command tree so the outermost scan consumes the flag and
2061
+ // isJson() reads it back through optsWithGlobals(). Stream/protocol
2062
+ // commands and help-only command groups keep a hidden parser option
2063
+ // because they reject --json.
2064
+ for (const cmd of allCommands(program)) {
2065
+ if (hidesJsonHelp(cmd)) {
2066
+ cmd.addOption(new Option("--json", "Print the result as JSON (machine-readable)").hideHelp());
2067
+ } else {
2068
+ cmd.option("--json", jsonHelpDescription(cmd));
2069
+ }
2070
+ }
2071
+ installMissingArgumentChoiceHints(program);
2072
+ installTopLevelUnknownCommandGuard(program);
2073
+
2074
+ return program;
2075
+ }
2076
+
2077
+ function isNotLoggedInError(err: unknown): boolean {
2078
+ return err instanceof Error && /^Not logged in\./.test(err.message);
2079
+ }
2080
+
2081
+ export async function main(argv: string[]): Promise<void> {
2082
+ await buildProgram().parseAsync(argv);
2083
+ }
2084
+
2085
+ function allCommands(root: Command): Command[] {
2086
+ const out: Command[] = [];
2087
+ const visit = (cmd: Command): void => {
2088
+ out.push(cmd);
2089
+ for (const sub of cmd.commands) visit(sub as Command);
2090
+ };
2091
+ visit(root);
2092
+ return out;
2093
+ }
2094
+
2095
+ function hidesJsonHelp(cmd: Command): boolean {
2096
+ return [
2097
+ "dharta",
2098
+ "dharta auth",
2099
+ "dharta analytics",
2100
+ "dharta keys",
2101
+ "dharta model-keys",
2102
+ "dharta members",
2103
+ "dharta embed-keys",
2104
+ "dharta caps",
2105
+ "dharta appearance",
2106
+ "dharta schedules",
2107
+ "dharta directories",
2108
+ "dharta webhooks",
2109
+ "dharta env",
2110
+ "dharta agent",
2111
+ "dharta git-credential",
2112
+ "dharta dev",
2113
+ "dharta logs",
2114
+ "dharta completion",
2115
+ ].includes(commandPath(cmd));
2116
+ }
2117
+
2118
+ function jsonHelpDescription(cmd: Command): string {
2119
+ if (commandPath(cmd) === "dharta deploy") {
2120
+ return "Print JSON for --mode folder uploads; git mode streams git push output";
2121
+ }
2122
+ return "Print the result as JSON (machine-readable)";
2123
+ }
2124
+
2125
+ function installTopLevelUnknownCommandGuard(program: Command): void {
2126
+ const originalParseAsync = program.parseAsync.bind(program);
2127
+ program.parseAsync = async (argv?: readonly string[], options?: Parameters<Command["parseAsync"]>[1]) => {
2128
+ rejectUnknownTopLevelCommand(program, argv, options);
2129
+ return originalParseAsync(argv as string[] | undefined, options);
2130
+ };
2131
+
2132
+ const originalParse = program.parse.bind(program);
2133
+ program.parse = (argv?: readonly string[], options?: Parameters<Command["parse"]>[1]) => {
2134
+ rejectUnknownTopLevelCommand(program, argv, options);
2135
+ return originalParse(argv as string[] | undefined, options);
2136
+ };
2137
+ }
2138
+
2139
+ const HELP_ONLY_COMMAND_PATHS = new Set([
2140
+ "dharta",
2141
+ "dharta auth",
2142
+ "dharta analytics",
2143
+ "dharta keys",
2144
+ "dharta model-keys",
2145
+ "dharta members",
2146
+ "dharta embed-keys",
2147
+ "dharta caps",
2148
+ "dharta caps budget",
2149
+ "dharta appearance",
2150
+ "dharta schedules",
2151
+ "dharta directories",
2152
+ "dharta webhooks",
2153
+ "dharta env",
2154
+ "dharta agent",
2155
+ ]);
2156
+
2157
+ function rejectUnknownTopLevelCommand(
2158
+ program: Command,
2159
+ argv?: readonly string[],
2160
+ options?: Parameters<Command["parse"]>[1],
2161
+ ): void {
2162
+ const args = argv ? [...argv].slice(options?.from === "user" ? 0 : 2) : process.argv.slice(2);
2163
+ let cursor: Command = program;
2164
+ for (let index = 0; index < args.length; index += 1) {
2165
+ const token = args[index] ?? "";
2166
+ if (token === "--") return;
2167
+ if (token.startsWith("-")) {
2168
+ const option = commandOption(cursor, token);
2169
+ if (!option) return;
2170
+ if (!token.includes("=") && optionTakesValue(option)) index += 1;
2171
+ continue;
2172
+ }
2173
+ const path = commandPath(cursor);
2174
+ if (!HELP_ONLY_COMMAND_PATHS.has(path)) return;
2175
+
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));
2178
+ if (child) {
2179
+ cursor = child as Command;
2180
+ continue;
2181
+ }
2182
+
2183
+ const suggestion = closestCommand(token, commandSuggestionTokens(cursor));
2184
+ cursor.error(
2185
+ `error: unknown command '${token}'${path === "dharta" ? "" : ` for ${path}`}. ` +
2186
+ `Run \`${path} --help\` for available commands.` +
2187
+ (suggestion ? ` Did you mean \`${suggestion}\`?` : ""),
2188
+ );
2189
+ }
2190
+ }
2191
+
2192
+ function commandOption(cmd: Command, token: string): Command["options"][number] | undefined {
2193
+ const flag = token.includes("=") ? token.slice(0, token.indexOf("=")) : token;
2194
+ return cmd.options.find((option) => option.long === flag || option.short === flag);
2195
+ }
2196
+
2197
+ function optionTakesValue(option: Command["options"][number]): boolean {
2198
+ return Boolean(
2199
+ (option as unknown as { required?: boolean; optional?: boolean }).required ||
2200
+ (option as unknown as { required?: boolean; optional?: boolean }).optional,
2201
+ );
2202
+ }
2203
+
2204
+ function collect(value: string, previous: string[]): string[] {
2205
+ previous.push(value);
2206
+ return previous;
2207
+ }
2208
+
2209
+ function installMissingArgumentChoiceHints(root: Command): void {
2210
+ for (const cmd of allCommands(root)) {
2211
+ const missingArgument = (cmd as unknown as { missingArgument?: (name: string) => void }).missingArgument;
2212
+ if (!missingArgument) continue;
2213
+ (cmd as unknown as { missingArgument: (name: string) => void }).missingArgument = (name: string): void => {
2214
+ const arg = cmd.registeredArguments.find((candidate) => candidate.name() === name);
2215
+ const choices = arg?.argChoices ?? [];
2216
+ const choicesHint = choices.length > 0 ? ` Allowed choices are ${choices.join(", ")}.` : "";
2217
+ cmd.error(
2218
+ `error: missing required argument '${name}'.${choicesHint} Usage: ${formatCommandUsage(cmd)}`,
2219
+ { code: "commander.missingArgument" },
2220
+ );
2221
+ };
2222
+ }
2223
+ }
2224
+
2225
+ function formatCommandUsage(cmd: Command): string {
2226
+ return `${commandPath(cmd)} ${cmd.usage()}`.trim();
2227
+ }
2228
+
2229
+ function choiceOption<const T extends readonly string[]>(flags: string, description: string, allowed: T): Option {
2230
+ return new Option(flags, description).choices([...allowed]).argParser(choiceValue(allowed));
2231
+ }
2232
+
2233
+ function choiceListOption<const T extends readonly string[]>(flags: string, description: string, allowed: T): Option {
2234
+ let sawExplicitValue = false;
2235
+ const parseChoice = choiceValue(allowed);
2236
+ return new Option(flags, description)
2237
+ .choices([...allowed])
2238
+ .argParser((value: string, previous: T[number][] = []) => {
2239
+ const explicitValues = sawExplicitValue ? previous : [];
2240
+ sawExplicitValue = true;
2241
+ const values = value.split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
2242
+ if (values.length === 0) throw new InvalidArgumentError(`Allowed choices are ${allowed.join(", ")}.`);
2243
+ return [...explicitValues, ...values.map(parseChoice)];
2244
+ });
2245
+ }
2246
+
2247
+ function choiceArgument<const T extends readonly string[]>(name: string, description: string, allowed: T): Argument {
2248
+ return new Argument(name, description).choices([...allowed]).argParser(choiceValue(allowed));
2249
+ }
2250
+
2251
+ function choiceValue<const T extends readonly string[]>(allowed: T): (value: string) => T[number] {
2252
+ return (value: string): T[number] => {
2253
+ const normalized = value.trim();
2254
+ if ((allowed as readonly string[]).includes(normalized)) return normalized as T[number];
2255
+ const suggestion = closestChoice(normalized, allowed);
2256
+ throw new InvalidArgumentError(
2257
+ `Allowed choices are ${allowed.join(", ")}.` +
2258
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
2259
+ );
2260
+ };
2261
+ }
2262
+
2263
+ function parseOnOff(value: string, name: string): boolean {
2264
+ const normalized = value.trim().toLowerCase();
2265
+ if (["on", "true", "enabled", "enable", "resume", "resumed"].includes(normalized)) return true;
2266
+ if (["off", "false", "disabled", "disable", "pause", "paused"].includes(normalized)) return false;
2267
+ const suggestion = closestChoice(normalized, ["on", "off", "enabled", "disabled", "true", "false"]);
2268
+ throw new Error(
2269
+ `${name} must be on/off, enabled/disabled, or true/false.` +
2270
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
2271
+ );
2272
+ }
2273
+
2274
+ export async function readStdin(): Promise<string> {
2275
+ if (process.stdin.isTTY) {
2276
+ // Reading a TTY would block silently until Ctrl-D — fail with the
2277
+ // usage instead (gh parity: --with-token requires piped input).
2278
+ throw new Error(
2279
+ "No piped input. Usage: echo $DHARTA_API_KEY | dharta login --with-token",
2280
+ );
2281
+ }
2282
+ const chunks: Buffer[] = [];
2283
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
2284
+ const token = Buffer.concat(chunks).toString("utf8").trim();
2285
+ if (!token) {
2286
+ throw new Error("No token on stdin. Usage: echo $DHARTA_API_KEY | dharta login --with-token");
2287
+ }
2288
+ return token;
2289
+ }
2290
+
2291
+ function parseVersion(input: string): number {
2292
+ normalizeNonBlank(input, "version");
2293
+ try {
2294
+ return parseReleaseVersion(input, "version");
2295
+ } catch {
2296
+ throw new Error(`Invalid version: ${input.trim()}. Use e.g. v3 or 3.`);
2297
+ }
2298
+ }
2299
+
2300
+ function parseRollbackTarget(input: string | undefined): number {
2301
+ if (input === undefined) {
2302
+ throw new Error("Pass --to <version>. Usage: dharta rollback [slug] --to v3");
2303
+ }
2304
+ return parseVersion(input);
2305
+ }
2306
+
2307
+ function parsePort(input: string): number {
2308
+ const normalized = input.trim();
2309
+ if (!normalized) throw new Error("--port cannot be blank.");
2310
+ if (!/^[1-9]\d*$/.test(normalized)) {
2311
+ throw new Error(`--port must be an integer between 1 and 65535.`);
2312
+ }
2313
+ const port = Number(normalized);
2314
+ if (port > 65535) {
2315
+ throw new Error(`--port must be an integer between 1 and 65535.`);
2316
+ }
2317
+ return port;
2318
+ }
2319
+
2320
+ // Only run when invoked as a bin — vitest imports this module without
2321
+ // driving it. argv[1] is realpath'd because the installed bin is a
2322
+ // SYMLINK (npm link / npm i -g puts <prefix>/bin/dharta -> dist/cli.js):
2323
+ // a filename regex on argv[1] never matches the symlink name, so the
2324
+ // CLI would exit 0 silently for every global invocation.
2325
+ const invokedAsBin = (() => {
2326
+ if (typeof process === "undefined" || !process.argv[1]) return false;
2327
+ try {
2328
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
2329
+ } catch {
2330
+ return false;
2331
+ }
2332
+ })();
2333
+ if (invokedAsBin) {
2334
+ 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);
2338
+ });
2339
+ }