@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
@@ -0,0 +1,635 @@
1
+ import type { CliContext } from "../context.js";
2
+ import { fmtTs, table } from "../output.js";
3
+ import { validateAgentSlug } from "../agent.js";
4
+ import { closestChoice, parseReleaseVersion } from "../validators.js";
5
+ import { randomUUID } from "node:crypto";
6
+
7
+ const AGENT_MODEL_SOURCES = ["inherit", "platform"] as const;
8
+ const MAX_OUTPUT_TOKENS = 1_000_000;
9
+
10
+ export interface AgentSummary {
11
+ id: string | number;
12
+ slug: string;
13
+ name: string;
14
+ deploy_branch: string;
15
+ current_version: number | null;
16
+ current_status: string | null;
17
+ archived_at?: string | null;
18
+ }
19
+
20
+ export interface ReleaseSummary {
21
+ id: string;
22
+ version: number;
23
+ status: string;
24
+ commit_sha: string;
25
+ current?: boolean;
26
+ activatable?: boolean;
27
+ created_at?: string | null;
28
+ error_message?: string | null;
29
+ }
30
+
31
+ export interface AgentDetail extends AgentSummary {
32
+ clone_url: string;
33
+ agent_url: string;
34
+ console_url: string;
35
+ live_version: number | null;
36
+ releases: ReleaseSummary[];
37
+ releases_error?: string;
38
+ resolved_model?: ResolvedAgentModel;
39
+ }
40
+
41
+ export interface ResolvedAgentModel {
42
+ model_id: string | null;
43
+ model_key_id: number | null;
44
+ auth_type: string;
45
+ provider: string;
46
+ endpoint_url: string | null;
47
+ api_format: string;
48
+ custom_endpoint: boolean;
49
+ parameters: Record<string, unknown>;
50
+ }
51
+
52
+ interface AgentShowEnvelope {
53
+ agent: {
54
+ id: string | number;
55
+ slug: string;
56
+ name: string;
57
+ deploy_branch: string;
58
+ resolved_model?: ResolvedAgentModel;
59
+ };
60
+ clone_url: string;
61
+ agent_url: string;
62
+ console_url: string;
63
+ live_version: number | null;
64
+ }
65
+
66
+ export interface WorkspaceAccessMatrix {
67
+ owners: { git: boolean; api: boolean };
68
+ users: { widget: boolean; agent_definition: boolean };
69
+ }
70
+
71
+ export interface AgentSettingsAgent {
72
+ id: string;
73
+ slug: string;
74
+ name: string;
75
+ deploy_enabled: boolean;
76
+ serving_status: string;
77
+ dashboard_edits_enabled: boolean;
78
+ workspace_inspectable?: boolean;
79
+ workspace_access?: WorkspaceAccessMatrix;
80
+ model: {
81
+ source: string;
82
+ key_id: number | null;
83
+ model: string | null;
84
+ parameters: Record<string, unknown>;
85
+ };
86
+ }
87
+
88
+ interface ActivationAgent {
89
+ id: string;
90
+ slug: string;
91
+ name: string;
92
+ }
93
+
94
+ interface ListEnvelope<T> {
95
+ data: T[];
96
+ }
97
+
98
+ export async function agentsListData(ctx: CliContext): Promise<AgentSummary[]> {
99
+ try {
100
+ const res = await ctx.http.request<ListEnvelope<AgentSummary>>({
101
+ path: "/api/agents",
102
+ });
103
+ return res.data;
104
+ } catch (error) {
105
+ throw addAgentsListNotFoundGuidance(error);
106
+ }
107
+ }
108
+
109
+ export async function agentsList(ctx: CliContext): Promise<string> {
110
+ const data = await agentsListData(ctx);
111
+ if (data.length === 0) return "No agents yet. Create one with: dharta create";
112
+ return table(
113
+ data.map((p) => ({
114
+ slug: p.slug,
115
+ name: p.name,
116
+ branch: p.deploy_branch,
117
+ current: p.current_version == null ? "—" : `v${p.current_version}`,
118
+ status: p.current_status ?? "—",
119
+ })),
120
+ );
121
+ }
122
+
123
+ export async function agentsShowData(
124
+ ctx: CliContext,
125
+ slug: string,
126
+ ): Promise<AgentDetail> {
127
+ const normalizedSlug = normalizeAgentSlug(slug);
128
+ const shown = await ctx.http.request<AgentShowEnvelope>({
129
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}`,
130
+ });
131
+ const releasesResult = await agentsReleasesData(ctx, normalizedSlug)
132
+ .then((releases) => ({ releases, error: undefined }))
133
+ .catch((error: unknown) => {
134
+ if (isReleaseScopeError(error)) {
135
+ return { releases: [], error: error instanceof Error ? error.message : String(error) };
136
+ }
137
+ throw error;
138
+ });
139
+ const releases = releasesResult.releases;
140
+ const current = releases.find((release) => release.current) ?? null;
141
+ return {
142
+ ...shown.agent,
143
+ clone_url: shown.clone_url,
144
+ agent_url: shown.agent_url,
145
+ console_url: shown.console_url,
146
+ live_version: shown.live_version ?? null,
147
+ current_version: shown.live_version ?? current?.version ?? null,
148
+ current_status: current?.status ?? null,
149
+ releases,
150
+ releases_error: releasesResult.error,
151
+ };
152
+ }
153
+
154
+ export async function agentsShow(ctx: CliContext, slug: string): Promise<string> {
155
+ const p = await agentsShowData(ctx, slug);
156
+ const current =
157
+ p.current_version == null
158
+ ? "—"
159
+ : p.current_status
160
+ ? `v${p.current_version} (${p.current_status})`
161
+ : `v${p.current_version}`;
162
+ const head = [
163
+ `Agent: ${p.name} (${p.slug})`,
164
+ `Deploy branch: ${p.deploy_branch}`,
165
+ `Current release: ${current}`,
166
+ ...(p.resolved_model ? [formatResolvedModel(p.resolved_model)] : []),
167
+ ].join("\n");
168
+ const releases = p.releases_error
169
+ ? `Unavailable: ${p.releases_error}`
170
+ : p.releases.length
171
+ ? table(
172
+ p.releases.map((r) => ({
173
+ version: `v${r.version}`,
174
+ status: r.status,
175
+ error: releaseError(r),
176
+ commit: r.commit_sha.slice(0, 8),
177
+ created: fmtTs(r.created_at),
178
+ })),
179
+ )
180
+ : `No releases yet. Deploy it with: dharta deploy ${p.slug}`;
181
+ return `${head}\n\nRecent releases:\n${releases}`;
182
+ }
183
+
184
+ function formatResolvedModel(route: ResolvedAgentModel): string {
185
+ const model = route.model_id ?? "provider default";
186
+ const connector = route.model_key_id == null ? "platform" : `connector ${route.model_key_id}`;
187
+ const parameters = Object.entries(route.parameters)
188
+ .map(([name, value]) => `${name}=${JSON.stringify(value)}`)
189
+ .join(", ");
190
+ return [
191
+ `Resolved model: ${route.provider}/${model} (${route.api_format}, ${connector})`,
192
+ route.endpoint_url ? `Model endpoint: ${route.endpoint_url}` : null,
193
+ parameters ? `Model parameters: ${parameters}` : null,
194
+ ].filter((line): line is string => line !== null).join("\n");
195
+ }
196
+
197
+ function isReleaseScopeError(error: unknown): boolean {
198
+ if (!(error instanceof Error)) return false;
199
+ const status = (error as Error & { status?: unknown }).status;
200
+ return status === 403 && /releases:read|releases:write|required scopes/i.test(error.message);
201
+ }
202
+
203
+ function addAgentsListNotFoundGuidance(error: unknown): Error {
204
+ if (error instanceof Error) {
205
+ const status = (error as Error & { status?: unknown }).status;
206
+ if (status === 404 && !error.message.includes("Could not list agents")) {
207
+ const guided = new Error(
208
+ "Could not list agents: the active Dharta host returned 404 for /api/agents. " +
209
+ "Run `dharta auth status` to confirm DHARTA_BASE_URL points at the Dharta control API; " +
210
+ "if it does, the server needs the public API route deployed for this CLI version.",
211
+ ) as Error & { status?: unknown; body?: unknown };
212
+ guided.status = status;
213
+ guided.body = (error as Error & { body?: unknown }).body;
214
+ return guided;
215
+ }
216
+ return error;
217
+ }
218
+ return new Error(String(error));
219
+ }
220
+
221
+ export async function agentsReleasesData(
222
+ ctx: CliContext,
223
+ slug: string,
224
+ ): Promise<ReleaseSummary[]> {
225
+ const normalizedSlug = normalizeAgentSlug(slug);
226
+ const res = await ctx.http.request<ListEnvelope<ReleaseSummary>>({
227
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/releases`,
228
+ });
229
+ return res.data;
230
+ }
231
+
232
+ export async function agentsReleases(
233
+ ctx: CliContext,
234
+ slug: string,
235
+ ): Promise<string> {
236
+ const normalizedSlug = normalizeAgentSlug(slug);
237
+ const data = await agentsReleasesData(ctx, normalizedSlug);
238
+ if (data.length === 0) return `No releases for ${normalizedSlug}. Deploy it with: dharta deploy ${normalizedSlug}`;
239
+ return table(
240
+ data.map((r) => ({
241
+ version: `v${r.version}`,
242
+ status: r.status,
243
+ error: releaseError(r),
244
+ commit: r.commit_sha.slice(0, 8),
245
+ created: fmtTs(r.created_at),
246
+ })),
247
+ );
248
+ }
249
+
250
+ function releaseError(release: ReleaseSummary): string {
251
+ if (release.status !== "failed" || !release.error_message) return "";
252
+ const oneLine = release.error_message.replace(/\s+/g, " ").trim();
253
+ return oneLine.length > 96 ? `${oneLine.slice(0, 93)}...` : oneLine;
254
+ }
255
+
256
+ export async function agentsActivateReleaseData(
257
+ ctx: CliContext,
258
+ slug: string,
259
+ version: string,
260
+ ): Promise<{ agent: ActivationAgent; release: ReleaseSummary; previous_release: ReleaseSummary | null }> {
261
+ const normalizedSlug = normalizeAgentSlug(slug);
262
+ const releaseVersion = parseReleaseVersion(version, "release version");
263
+ return ctx.http.request<{ agent: ActivationAgent; release: ReleaseSummary; previous_release: ReleaseSummary | null }>({
264
+ method: "POST",
265
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/releases/${encodeURIComponent(releaseVersion)}/activate`,
266
+ headers: { "Idempotency-Key": randomUUID() },
267
+ });
268
+ }
269
+
270
+ export async function agentsActivateRelease(ctx: CliContext, slug: string, version: string): Promise<string> {
271
+ const res = await agentsActivateReleaseData(ctx, slug, version);
272
+ const previous = res.previous_release ? ` Previous: v${res.previous_release.version}.` : "";
273
+ return `Activated ${res.agent.slug} v${res.release.version}.${previous}`;
274
+ }
275
+
276
+ export async function agentsServingSetData(
277
+ ctx: CliContext,
278
+ slug: string,
279
+ enabled: boolean,
280
+ ): Promise<{ agent: AgentSettingsAgent; changed: boolean }> {
281
+ const normalizedSlug = normalizeAgentSlug(slug);
282
+ return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
283
+ method: "PATCH",
284
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/serving`,
285
+ headers: { "Idempotency-Key": randomUUID() },
286
+ body: { enabled },
287
+ });
288
+ }
289
+
290
+ export async function agentsServingSet(ctx: CliContext, slug: string, enabled: boolean): Promise<string> {
291
+ const res = await agentsServingSetData(ctx, slug, enabled);
292
+ const verb = enabled ? "serving" : "paused";
293
+ return `${res.agent.slug} is ${verb}${res.changed ? "" : " (unchanged)"}.`;
294
+ }
295
+
296
+ export async function agentsDashboardEditsSetData(
297
+ ctx: CliContext,
298
+ slug: string,
299
+ enabled: boolean,
300
+ ): Promise<{ agent: AgentSettingsAgent; changed: boolean }> {
301
+ const normalizedSlug = normalizeAgentSlug(slug);
302
+ return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
303
+ method: "PATCH",
304
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/dashboard_edits`,
305
+ headers: { "Idempotency-Key": randomUUID() },
306
+ body: { enabled },
307
+ });
308
+ }
309
+
310
+ export async function agentsDashboardEditsSet(ctx: CliContext, slug: string, enabled: boolean): Promise<string> {
311
+ const res = await agentsDashboardEditsSetData(ctx, slug, enabled);
312
+ const state = enabled ? "enabled" : "disabled";
313
+ return `Dashboard edits ${state} for ${res.agent.slug}${res.changed ? "" : " (unchanged)"}.`;
314
+ }
315
+
316
+ export interface AgentModelSetOptions {
317
+ source?: string;
318
+ connectorId?: string;
319
+ model?: string;
320
+ temperature?: string;
321
+ clearTemperature?: boolean;
322
+ topP?: string;
323
+ clearTopP?: boolean;
324
+ maxOutputTokens?: string;
325
+ clearMaxOutputTokens?: boolean;
326
+ reasoningEffort?: string;
327
+ clearReasoningEffort?: boolean;
328
+ }
329
+
330
+ export async function agentsModelSetData(
331
+ ctx: CliContext,
332
+ slug: string,
333
+ opts: AgentModelSetOptions,
334
+ ): Promise<{ agent: AgentSettingsAgent }> {
335
+ const normalizedSlug = normalizeAgentSlug(slug);
336
+ const normalized = validateAgentModelSetOptions(opts);
337
+ const body: Record<string, unknown> = {};
338
+ if (normalized.source !== undefined || normalized.connectorId !== undefined) {
339
+ body.model_connector = modelConnector(normalized);
340
+ }
341
+ if (normalized.model !== undefined) body.default_model_id = normalized.model;
342
+ const modelParameters = modelParameterPatch(normalized);
343
+ if (Object.keys(modelParameters).length > 0) body.model_parameters = modelParameters;
344
+ return ctx.http.request<{ agent: AgentSettingsAgent }>({
345
+ method: "PATCH",
346
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/model`,
347
+ headers: { "Idempotency-Key": randomUUID() },
348
+ body,
349
+ });
350
+ }
351
+
352
+ export async function agentsModelSet(ctx: CliContext, slug: string, opts: AgentModelSetOptions): Promise<string> {
353
+ const res = await agentsModelSetData(ctx, slug, opts);
354
+ const model = res.agent.model.model ? ` model ${res.agent.model.model}` : "";
355
+ return `Model override for ${res.agent.slug}: ${res.agent.model.source}${model}.`;
356
+ }
357
+
358
+ export function validateAgentModelSetOptions(opts: AgentModelSetOptions): AgentModelSetOptions {
359
+ const normalized = normalizeModelSetOptions(opts);
360
+ validateModelSetOptions(normalized);
361
+ return normalized;
362
+ }
363
+
364
+ export async function agentsWorkspaceInspectableSetData(
365
+ ctx: CliContext,
366
+ slug: string,
367
+ enabled: boolean,
368
+ ): Promise<{ agent: AgentSettingsAgent; changed: boolean }> {
369
+ const normalizedSlug = normalizeAgentSlug(slug);
370
+ return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
371
+ method: "PATCH",
372
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/workspace_inspectable`,
373
+ headers: { "Idempotency-Key": randomUUID() },
374
+ body: { enabled },
375
+ });
376
+ }
377
+
378
+ export async function agentsWorkspaceInspectableSet(ctx: CliContext, slug: string, enabled: boolean): Promise<string> {
379
+ const res = await agentsWorkspaceInspectableSetData(ctx, slug, enabled);
380
+ const state = enabled ? "enabled" : "disabled";
381
+ return `Workspace inspection ${state} for ${res.agent.slug}${res.changed ? "" : " (unchanged)"}.`;
382
+ }
383
+
384
+ // RFC 0060 D1 — the audience x channel grant matrix. The four dotted grants map
385
+ // onto the nested `workspace_access` shape the API PATCHes.
386
+ export const WORKSPACE_ACCESS_PATHS = [
387
+ "owners.git",
388
+ "owners.api",
389
+ "users.widget",
390
+ "users.agent_definition",
391
+ ] as const;
392
+
393
+ export type WorkspaceAccessPatch = Record<string, Record<string, boolean>>;
394
+
395
+ // Parse `path=on|off` assignments (e.g. "users.widget=on") into the nested patch
396
+ // the API merges. Unknown paths and non-on/off values fail loud with a suggestion.
397
+ export function parseWorkspaceAccessAssignments(assignments: string[]): WorkspaceAccessPatch {
398
+ if (assignments.length === 0) {
399
+ throw new Error("Provide at least one grant, e.g. users.widget=on");
400
+ }
401
+ const patch: WorkspaceAccessPatch = {};
402
+ for (const raw of assignments) {
403
+ const eq = raw.indexOf("=");
404
+ if (eq === -1) {
405
+ throw new Error(`Invalid grant "${raw}". Use <path>=<on|off>, e.g. users.widget=on.`);
406
+ }
407
+ const path = raw.slice(0, eq).trim();
408
+ const value = raw.slice(eq + 1);
409
+ if (!WORKSPACE_ACCESS_PATHS.includes(path as (typeof WORKSPACE_ACCESS_PATHS)[number])) {
410
+ const suggestion = closestChoice(path, WORKSPACE_ACCESS_PATHS);
411
+ throw new Error(
412
+ `Unknown grant "${path}". One of: ${WORKSPACE_ACCESS_PATHS.join(", ")}.` +
413
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
414
+ );
415
+ }
416
+ const [audience, channel] = path.split(".");
417
+ (patch[audience] ??= {})[channel] = parseGrantValue(value, path);
418
+ }
419
+ return patch;
420
+ }
421
+
422
+ function parseGrantValue(value: string, path: string): boolean {
423
+ const normalized = value.trim().toLowerCase();
424
+ if (["on", "true", "enabled", "enable", "1"].includes(normalized)) return true;
425
+ if (["off", "false", "disabled", "disable", "0"].includes(normalized)) return false;
426
+ throw new Error(`Grant "${path}" must be on/off, enabled/disabled, or true/false (got "${value}").`);
427
+ }
428
+
429
+ export async function agentsWorkspaceAccessShowData(ctx: CliContext, slug: string): Promise<WorkspaceAccessMatrix> {
430
+ const normalizedSlug = normalizeAgentSlug(slug);
431
+ const res = await ctx.http.request<{ agent: { workspace_access?: WorkspaceAccessMatrix } }>({
432
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}`,
433
+ });
434
+ const matrix = res.agent.workspace_access;
435
+ if (!matrix) {
436
+ throw new Error("This Dharta server does not report workspace access. Upgrade the server (RFC 0060).");
437
+ }
438
+ return matrix;
439
+ }
440
+
441
+ export async function agentsWorkspaceAccessShow(ctx: CliContext, slug: string): Promise<string> {
442
+ const wa = await agentsWorkspaceAccessShowData(ctx, slug);
443
+ const onoff = (granted: boolean): string => (granted ? "on" : "off");
444
+ return [
445
+ "Workspace access grants:",
446
+ ` owners.git: ${onoff(wa.owners.git)}`,
447
+ ` owners.api: ${onoff(wa.owners.api)}`,
448
+ ` users.widget: ${onoff(wa.users.widget)}`,
449
+ ` users.agent_definition: ${onoff(wa.users.agent_definition)}`,
450
+ ].join("\n");
451
+ }
452
+
453
+ export async function agentsWorkspaceAccessSetData(
454
+ ctx: CliContext,
455
+ slug: string,
456
+ patch: WorkspaceAccessPatch,
457
+ ): Promise<{ agent: AgentSettingsAgent; changed: boolean }> {
458
+ const normalizedSlug = normalizeAgentSlug(slug);
459
+ return ctx.http.request<{ agent: AgentSettingsAgent; changed: boolean }>({
460
+ method: "PATCH",
461
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}/workspace_access`,
462
+ headers: { "Idempotency-Key": randomUUID() },
463
+ body: { workspace_access: patch },
464
+ });
465
+ }
466
+
467
+ export async function agentsWorkspaceAccessSet(
468
+ ctx: CliContext,
469
+ slug: string,
470
+ patch: WorkspaceAccessPatch,
471
+ ): Promise<string> {
472
+ const res = await agentsWorkspaceAccessSetData(ctx, slug, patch);
473
+ const paths = Object.entries(patch).flatMap(([audience, channels]) =>
474
+ Object.entries(channels).map(([channel, granted]) => `${audience}.${channel}=${granted ? "on" : "off"}`),
475
+ );
476
+ return `Workspace access updated for ${res.agent.slug}: ${paths.join(", ")}${res.changed ? "" : " (unchanged)"}.`;
477
+ }
478
+
479
+ export async function agentsDeleteData(ctx: CliContext, slug: string): Promise<{ agent: AgentSettingsAgent; archived: boolean }> {
480
+ const normalizedSlug = normalizeAgentSlug(slug);
481
+ return ctx.http.request<{ agent: AgentSettingsAgent; archived: boolean }>({
482
+ method: "DELETE",
483
+ path: `/api/agents/${encodeURIComponent(normalizedSlug)}`,
484
+ headers: { "Idempotency-Key": randomUUID() },
485
+ });
486
+ }
487
+
488
+ export async function agentsDelete(ctx: CliContext, slug: string): Promise<string> {
489
+ const res = await agentsDeleteData(ctx, slug);
490
+ return `Archived ${res.agent.slug}.`;
491
+ }
492
+
493
+ function modelConnector(opts: AgentModelSetOptions): string {
494
+ if (opts.connectorId) return `key:${opts.connectorId}`;
495
+ if (opts.source === "platform") return "platform";
496
+ if (opts.source === "inherit" || !opts.source) return "inherit";
497
+ throw new Error("--source must be inherit or platform.");
498
+ }
499
+
500
+ function validateModelSetOptions(opts: AgentModelSetOptions): void {
501
+ const hasSource = opts.source !== undefined;
502
+ const hasConnector = opts.connectorId !== undefined;
503
+ const hasModel = opts.model !== undefined;
504
+ const hasParameters = modelParameterOptionPresent(opts);
505
+
506
+ if (!hasSource && !hasConnector && !hasModel && !hasParameters) {
507
+ throw new Error(
508
+ "Pass at least one model option, connector option, or parameter option.",
509
+ );
510
+ }
511
+ if (hasSource && hasConnector) {
512
+ throw new Error("Use either --source or --connector-id, not both.");
513
+ }
514
+ if (hasSource && !(AGENT_MODEL_SOURCES as readonly string[]).includes(opts.source as string)) {
515
+ const suggestion = closestChoice(opts.source as string, AGENT_MODEL_SOURCES);
516
+ throw new Error(
517
+ `Unknown --source ${opts.source}. Allowed sources: ${AGENT_MODEL_SOURCES.join(", ")}.` +
518
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
519
+ );
520
+ }
521
+ if (hasConnector && !isPositiveNumericId(opts.connectorId as string)) {
522
+ throw new Error("--connector-id must be a positive numeric model connector id from `dharta model-keys list`.");
523
+ }
524
+ validateParameterPair(opts.temperature, opts.clearTemperature, "--temperature", "--clear-temperature");
525
+ validateParameterPair(opts.topP, opts.clearTopP, "--top-p", "--clear-top-p");
526
+ validateParameterPair(
527
+ opts.maxOutputTokens,
528
+ opts.clearMaxOutputTokens,
529
+ "--max-output-tokens",
530
+ "--clear-max-output-tokens",
531
+ );
532
+ validateParameterPair(
533
+ opts.reasoningEffort,
534
+ opts.clearReasoningEffort,
535
+ "--reasoning-effort",
536
+ "--clear-reasoning-effort",
537
+ );
538
+ if (opts.temperature !== undefined) parseBoundedNumber(opts.temperature, "--temperature", 0, 2, false);
539
+ if (opts.topP !== undefined) parseBoundedNumber(opts.topP, "--top-p", 0, 1, true);
540
+ if (opts.maxOutputTokens !== undefined) {
541
+ const value = parsePositiveInteger(opts.maxOutputTokens, "--max-output-tokens");
542
+ if (value > MAX_OUTPUT_TOKENS) {
543
+ throw new Error(`--max-output-tokens must be at most ${MAX_OUTPUT_TOKENS}.`);
544
+ }
545
+ }
546
+ if (opts.reasoningEffort !== undefined && !["low", "medium", "high"].includes(opts.reasoningEffort)) {
547
+ throw new Error("--reasoning-effort must be low, medium, or high.");
548
+ }
549
+ }
550
+
551
+ function isPositiveNumericId(value: string): boolean {
552
+ if (!/^\d+$/.test(value)) return false;
553
+ const parsed = Number(value);
554
+ return Number.isSafeInteger(parsed) && parsed > 0;
555
+ }
556
+
557
+ function normalizeModelSetOptions(opts: AgentModelSetOptions): AgentModelSetOptions {
558
+ return {
559
+ source: normalizeOptionalFlag(opts.source, "--source"),
560
+ connectorId: normalizeOptionalFlag(opts.connectorId, "--connector-id"),
561
+ model: normalizeOptionalFlag(opts.model, "--model"),
562
+ temperature: normalizeOptionalFlag(opts.temperature, "--temperature"),
563
+ clearTemperature: opts.clearTemperature,
564
+ topP: normalizeOptionalFlag(opts.topP, "--top-p"),
565
+ clearTopP: opts.clearTopP,
566
+ maxOutputTokens: normalizeOptionalFlag(opts.maxOutputTokens, "--max-output-tokens"),
567
+ clearMaxOutputTokens: opts.clearMaxOutputTokens,
568
+ reasoningEffort: normalizeOptionalFlag(opts.reasoningEffort, "--reasoning-effort"),
569
+ clearReasoningEffort: opts.clearReasoningEffort,
570
+ };
571
+ }
572
+
573
+ function modelParameterOptionPresent(opts: AgentModelSetOptions): boolean {
574
+ return opts.temperature !== undefined || opts.clearTemperature === true ||
575
+ opts.topP !== undefined || opts.clearTopP === true ||
576
+ opts.maxOutputTokens !== undefined || opts.clearMaxOutputTokens === true ||
577
+ opts.reasoningEffort !== undefined || opts.clearReasoningEffort === true;
578
+ }
579
+
580
+ function modelParameterPatch(opts: AgentModelSetOptions): Record<string, number | string | null> {
581
+ const patch: Record<string, number | string | null> = {};
582
+ if (opts.temperature !== undefined) patch.temperature = Number(opts.temperature);
583
+ if (opts.clearTemperature) patch.temperature = null;
584
+ if (opts.topP !== undefined) patch.top_p = Number(opts.topP);
585
+ if (opts.clearTopP) patch.top_p = null;
586
+ if (opts.maxOutputTokens !== undefined) patch.max_output_tokens = Number(opts.maxOutputTokens);
587
+ if (opts.clearMaxOutputTokens) patch.max_output_tokens = null;
588
+ if (opts.reasoningEffort !== undefined) patch.reasoning_effort = opts.reasoningEffort;
589
+ if (opts.clearReasoningEffort) patch.reasoning_effort = null;
590
+ return patch;
591
+ }
592
+
593
+ function validateParameterPair(
594
+ value: string | undefined,
595
+ clear: boolean | undefined,
596
+ setFlag: string,
597
+ clearFlag: string,
598
+ ): void {
599
+ if (value !== undefined && clear) throw new Error(`Use either ${setFlag} or ${clearFlag}, not both.`);
600
+ }
601
+
602
+ function parseBoundedNumber(
603
+ value: string,
604
+ flag: string,
605
+ minimum: number,
606
+ maximum: number,
607
+ exclusiveMinimum: boolean,
608
+ ): number {
609
+ const parsed = Number(value);
610
+ const minimumOk = exclusiveMinimum ? parsed > minimum : parsed >= minimum;
611
+ if (!Number.isFinite(parsed) || !minimumOk || parsed > maximum) {
612
+ const lower = exclusiveMinimum ? `greater than ${minimum}` : `at least ${minimum}`;
613
+ throw new Error(`${flag} must be a finite number ${lower} and at most ${maximum}.`);
614
+ }
615
+ return parsed;
616
+ }
617
+
618
+ function parsePositiveInteger(value: string, flag: string): number {
619
+ const parsed = Number(value);
620
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
621
+ throw new Error(`${flag} must be a positive whole number.`);
622
+ }
623
+ return parsed;
624
+ }
625
+
626
+ function normalizeOptionalFlag(value: string | undefined, flag: string): string | undefined {
627
+ if (value === undefined) return undefined;
628
+ const normalized = value.trim();
629
+ if (normalized.length === 0) throw new Error(`${flag} cannot be blank.`);
630
+ return normalized;
631
+ }
632
+
633
+ function normalizeAgentSlug(slug: string): string {
634
+ return validateAgentSlug(slug, "agent slug");
635
+ }