@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,197 @@
1
+ import type { HttpClient } from "./http.js";
2
+ import type {
3
+ ApiKey,
4
+ ApiKeyCreateResponse,
5
+ ApiKeyScope,
6
+ BillingStatus,
7
+ ModelKey,
8
+ ModelKeyCreateParams,
9
+ ModelKeyProvider,
10
+ UsageSummary,
11
+ WebhookEndpoint,
12
+ WebhookEndpointCreateResponse,
13
+ WebhookEventType,
14
+ } from "./types.js";
15
+
16
+ interface ListEnvelope<T> {
17
+ data: T[];
18
+ }
19
+
20
+ interface ModelKeyEnvelope {
21
+ model_key: ModelKey;
22
+ }
23
+
24
+ interface WebhookEndpointEnvelope {
25
+ webhook_endpoint: WebhookEndpoint;
26
+ }
27
+
28
+ export class ApiKeysResource {
29
+ constructor(private readonly http: HttpClient) {}
30
+
31
+ async list(): Promise<ApiKey[]> {
32
+ const res = await this.http.request<ListEnvelope<ApiKey>>({ path: "/api_keys" });
33
+ return res.data;
34
+ }
35
+
36
+ async create(name: string, scopes: ApiKeyScope[]): Promise<ApiKeyCreateResponse> {
37
+ return this.http.request<ApiKeyCreateResponse>({
38
+ method: "POST",
39
+ path: "/api_keys",
40
+ headers: idempotencyHeaders(),
41
+ body: { name, scopes },
42
+ });
43
+ }
44
+
45
+ async rename(id: string, name: string): Promise<ApiKey> {
46
+ const res = await this.http.request<{ api_key: ApiKey }>({
47
+ method: "PATCH",
48
+ path: `/api_keys/${encodeURIComponent(id)}`,
49
+ headers: idempotencyHeaders(),
50
+ body: { name },
51
+ });
52
+ return res.api_key;
53
+ }
54
+
55
+ async revoke(id: string): Promise<void> {
56
+ await this.http.request<void>({
57
+ method: "DELETE",
58
+ path: `/api_keys/${encodeURIComponent(id)}`,
59
+ headers: idempotencyHeaders(),
60
+ });
61
+ }
62
+ }
63
+
64
+ export class ModelKeysResource {
65
+ constructor(private readonly http: HttpClient) {}
66
+
67
+ async list(): Promise<ModelKey[]> {
68
+ const res = await this.http.request<ListEnvelope<ModelKey>>({ path: "/model_keys" });
69
+ return res.data;
70
+ }
71
+
72
+ async create(provider: ModelKeyProvider, plaintext: string, name?: string): Promise<ModelKey>;
73
+ async create(params: ModelKeyCreateParams): Promise<ModelKey>;
74
+ async create(
75
+ providerOrParams: ModelKeyProvider | ModelKeyCreateParams,
76
+ plaintext?: string,
77
+ name?: string,
78
+ ): Promise<ModelKey> {
79
+ const body = typeof providerOrParams === "string"
80
+ ? { provider: providerOrParams, plaintext, name }
81
+ : providerOrParams;
82
+ const res = await this.http.request<ModelKeyEnvelope>({
83
+ method: "POST",
84
+ path: "/model_keys",
85
+ headers: idempotencyHeaders(),
86
+ body,
87
+ });
88
+ return res.model_key;
89
+ }
90
+
91
+ async revoke(id: string | number): Promise<void> {
92
+ await this.http.request<void>({
93
+ method: "DELETE",
94
+ path: `/model_keys/${encodeURIComponent(String(id))}`,
95
+ headers: idempotencyHeaders(),
96
+ });
97
+ }
98
+ }
99
+
100
+ export interface WebhookEndpointCreateParams {
101
+ url: string;
102
+ name?: string;
103
+ event_types?: WebhookEventType[];
104
+ }
105
+
106
+ export interface WebhookEndpointUpdateParams {
107
+ name?: string;
108
+ url?: string;
109
+ event_types?: WebhookEventType[];
110
+ }
111
+
112
+ export class WebhookEndpointsResource {
113
+ constructor(private readonly http: HttpClient) {}
114
+
115
+ async list(): Promise<WebhookEndpoint[]> {
116
+ const res = await this.http.request<ListEnvelope<WebhookEndpoint>>({ path: "/api/webhook_endpoints" });
117
+ return res.data;
118
+ }
119
+
120
+ async create(params: WebhookEndpointCreateParams): Promise<WebhookEndpointCreateResponse> {
121
+ return this.http.request<WebhookEndpointCreateResponse>({
122
+ method: "POST",
123
+ path: "/api/webhook_endpoints",
124
+ headers: idempotencyHeaders(),
125
+ body: params,
126
+ });
127
+ }
128
+
129
+ async update(id: string, params: WebhookEndpointUpdateParams): Promise<WebhookEndpoint> {
130
+ const res = await this.http.request<WebhookEndpointEnvelope>({
131
+ method: "PATCH",
132
+ path: `/api/webhook_endpoints/${encodeURIComponent(id)}`,
133
+ headers: idempotencyHeaders(),
134
+ body: params,
135
+ });
136
+ return res.webhook_endpoint;
137
+ }
138
+
139
+ async enable(id: string): Promise<WebhookEndpoint> {
140
+ const res = await this.http.request<WebhookEndpointEnvelope>({
141
+ method: "POST",
142
+ path: `/api/webhook_endpoints/${encodeURIComponent(id)}/enable`,
143
+ headers: idempotencyHeaders(),
144
+ });
145
+ return res.webhook_endpoint;
146
+ }
147
+
148
+ async delete(id: string): Promise<void> {
149
+ await this.http.request<void>({
150
+ method: "DELETE",
151
+ path: `/api/webhook_endpoints/${encodeURIComponent(id)}`,
152
+ headers: idempotencyHeaders(),
153
+ });
154
+ }
155
+ }
156
+
157
+ export class UsageResource {
158
+ constructor(private readonly http: HttpClient) {}
159
+
160
+ async summary(): Promise<UsageSummary> {
161
+ return this.http.request<UsageSummary>({ path: "/api/usage" });
162
+ }
163
+ }
164
+
165
+ export class BillingResource {
166
+ constructor(private readonly http: HttpClient) {}
167
+
168
+ async status(): Promise<BillingStatus> {
169
+ return this.http.request<BillingStatus>({ path: "/api/billing" });
170
+ }
171
+ }
172
+
173
+ function idempotencyHeaders(): Record<string, string> {
174
+ return { "Idempotency-Key": randomIdempotencyKey() };
175
+ }
176
+
177
+ function randomIdempotencyKey(): string {
178
+ if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
179
+ const bytes = new Uint8Array(16);
180
+ if (globalThis.crypto?.getRandomValues) {
181
+ globalThis.crypto.getRandomValues(bytes);
182
+ } else {
183
+ for (let i = 0; i < bytes.length; i += 1) {
184
+ bytes[i] = Math.floor(Math.random() * 256);
185
+ }
186
+ }
187
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
188
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
189
+ const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0"));
190
+ return [
191
+ hex.slice(0, 4).join(""),
192
+ hex.slice(4, 6).join(""),
193
+ hex.slice(6, 8).join(""),
194
+ hex.slice(8, 10).join(""),
195
+ hex.slice(10, 16).join(""),
196
+ ].join("-");
197
+ }
@@ -0,0 +1,179 @@
1
+ // Hand-written types mirroring the Rails OpenAPI spec
2
+ // (apps/dharta-web/app/lib/openapi/spec.rb). Keep tight: only what
3
+ // the Dharta API actually returns / accepts. If a server field
4
+ // disappears, this file should drop it on the next bump rather than
5
+ // carrying dead optionals forever.
6
+
7
+ export const API_KEY_SCOPES = [
8
+ "agents:read",
9
+ "appearance:read",
10
+ "analytics:read",
11
+ "caps:read",
12
+ "directories:read",
13
+ "embed_keys:read",
14
+ "env:read",
15
+ "keys:read",
16
+ "members:read",
17
+ "model_keys:read",
18
+ "releases:read",
19
+ "schedules:read",
20
+ "sessions:read",
21
+ "source_repos:read",
22
+ "webhooks:read",
23
+ "agents:write",
24
+ "appearance:write",
25
+ "env:write",
26
+ "releases:write",
27
+ "schedules:write",
28
+ "webhooks:write",
29
+ "embed_keys:write",
30
+ "caps:write",
31
+ "source_repos:write",
32
+ "env:secrets:read",
33
+ "source_archives:read",
34
+ "workspace_tokens:create",
35
+ "audit_log:export",
36
+ "members:invite",
37
+ "members:write",
38
+ "members:owner_role:write",
39
+ "agents:delete",
40
+ "keys:write",
41
+ "model_keys:write",
42
+ "billing:read",
43
+ ] as const;
44
+
45
+ export type ApiKeyScope = (typeof API_KEY_SCOPES)[number];
46
+
47
+ export type ModelKeyProvider =
48
+ | "anthropic"
49
+ | "openai"
50
+ | "bedrock"
51
+ | "vertex"
52
+ | "openrouter"
53
+ | "xai"
54
+ | "google";
55
+
56
+ export type ModelApiFormat =
57
+ | "anthropic_messages"
58
+ | "openai_chat_completions"
59
+ | "openai_responses"
60
+ | "bedrock_converse"
61
+ | "vertex_generate_content";
62
+
63
+ export type ModelReasoningEffort = "low" | "medium" | "high";
64
+
65
+ export interface ModelParameters {
66
+ temperature?: number;
67
+ top_p?: number;
68
+ max_output_tokens?: number;
69
+ reasoning_effort?: ModelReasoningEffort;
70
+ provider_options?: {
71
+ openai?: {
72
+ service_tier?: "auto" | "default" | "flex" | "priority";
73
+ verbosity?: "low" | "medium" | "high";
74
+ };
75
+ };
76
+ }
77
+
78
+ export const WEBHOOK_EVENT_TYPES = [
79
+ "api_key.created",
80
+ "api_key.revoked",
81
+ "subscription.created",
82
+ "subscription.updated",
83
+ "subscription.canceled",
84
+ "invoice.paid",
85
+ "invoice.payment_failed",
86
+ "organization.suspended",
87
+ "organization.resumed",
88
+ "usage.budget_threshold_reached",
89
+ "usage.budget_exhausted",
90
+ "session.started",
91
+ "turn.completed",
92
+ "turn.failed",
93
+ ] as const;
94
+
95
+ export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
96
+
97
+ export interface ApiKey {
98
+ id: string;
99
+ name: string;
100
+ prefix: string;
101
+ scopes: ApiKeyScope[];
102
+ mode?: "live" | "test";
103
+ expires_at?: string | null;
104
+ created_at: string;
105
+ last_used_at: string | null;
106
+ revoked_at: string | null;
107
+ }
108
+
109
+ export interface ApiKeyCreateResponse {
110
+ api_key: ApiKey;
111
+ // Plaintext secret — shown exactly once, on create. Store it now.
112
+ secret: string;
113
+ }
114
+
115
+ export interface ModelKey {
116
+ id: number;
117
+ provider: ModelKeyProvider;
118
+ name: string;
119
+ tail: string;
120
+ auth_type: "api_key" | "oauth_subscription";
121
+ endpoint_url: string | null;
122
+ api_format: ModelApiFormat | null;
123
+ created_at: string;
124
+ last_verified_at: string | null;
125
+ verification_error: string | null;
126
+ revoked_at: string | null;
127
+ }
128
+
129
+ export interface ModelKeyCreateParams {
130
+ provider: ModelKeyProvider;
131
+ plaintext: string;
132
+ name?: string;
133
+ auth_type?: "api_key" | "oauth_subscription";
134
+ endpoint_url?: string;
135
+ api_format?: ModelApiFormat;
136
+ }
137
+
138
+ export interface WebhookEndpoint {
139
+ id: string;
140
+ name: string;
141
+ url: string;
142
+ event_types: WebhookEventType[];
143
+ enabled: boolean;
144
+ consecutive_failures: number;
145
+ disabled_at: string | null;
146
+ created_at: string;
147
+ }
148
+
149
+ export interface WebhookEndpointCreateResponse {
150
+ webhook_endpoint: WebhookEndpoint;
151
+ secret: string;
152
+ }
153
+
154
+ export interface UsageSummary {
155
+ totals: {
156
+ input_tokens?: number;
157
+ output_tokens?: number;
158
+ requests?: number;
159
+ [k: string]: number | undefined;
160
+ };
161
+ budget: {
162
+ monthly_cap_cents: number | null;
163
+ period_start: string;
164
+ period_end: string;
165
+ } | null;
166
+ }
167
+
168
+ export interface BillingStatus {
169
+ plan_key: string;
170
+ stripe_configured: boolean;
171
+ subscription_state: string | null;
172
+ current_period_end: string | null;
173
+ }
174
+
175
+ export interface ErrorBody {
176
+ error: string;
177
+ message?: string;
178
+ details?: Record<string, unknown>;
179
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@dharta/cli",
3
+ "version": "0.6.77",
4
+ "description": "Command-line interface for the Dharta API.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "dharta": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "src",
13
+ "README.md",
14
+ "templates"
15
+ ],
16
+ "bundledDependencies": [
17
+ "@dharta/sdk"
18
+ ],
19
+ "scripts": {
20
+ "build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest"
23
+ },
24
+ "engines": {
25
+ "node": ">=18.17.0"
26
+ },
27
+ "dependencies": {
28
+ "@dharta/sdk": "^0.6.77",
29
+ "@iarna/toml": "^2.2.5",
30
+ "commander": "^15.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^26.0.1",
34
+ "msw": "^2.14.6",
35
+ "typescript": "^6.0.3",
36
+ "vitest": "^4.1.9"
37
+ }
38
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,147 @@
1
+ // Agent identity inference, shared by every per-agent command (RFC 0024 + the
2
+ // CLI surface spec). The agent a command targets comes from its dharta.toml -
3
+ // NOT the `dharta` git remote, which now points at the SOURCE REPO (one repo,
4
+ // many agents), so remote-slug inference would resolve the wrong thing.
5
+
6
+ import * as path from "node:path";
7
+ import { existsSync, statSync } from "node:fs";
8
+ import { findGitRoot, scanAgentFolders, describeFolder, type AgentFolder } from "./setup-scan.js";
9
+ import { normalizeNonBlank } from "./validators.js";
10
+
11
+ const AGENT_SLUG_FORMAT = /^[a-z0-9][a-z0-9-]{0,62}$/;
12
+ const MAX_AGENT_NAME_CHARACTERS = 100;
13
+
14
+ export interface AgentInferOptions {
15
+ agent?: string; // -a/--agent (or an explicit [slug]); the deployed agent
16
+ project?: string; // deprecated alias for `agent` (the -p/--project flag)
17
+ path?: string; // [path] positional - the agent's folder
18
+ dir?: string; // cwd (default process.cwd())
19
+ env?: NodeJS.ProcessEnv;
20
+ scan?: (root: string, opts: { isGitRepo: boolean }) => Promise<AgentFolder[]>; // test seam
21
+ }
22
+
23
+ // Resolve the agent slug a per-agent command operates on. Order (explicit
24
+ // beats inferred): --agent/-p, an explicit [path], the nearest containing
25
+ // agent folder, the repo's sole agent, then DHARTA_PROJECT (CI). A multi-agent
26
+ // repo root with no selector is a loud error rather than a wrong guess.
27
+ // Identity for a scanned agent folder: an explicit dharta.toml `slug` wins
28
+ // (pinned identity, free-form display name); otherwise derive from the name.
29
+ function slugForFolder(folder: { slug?: string; name?: string }): string {
30
+ if (folder.slug) return validateAgentSlug(folder.slug, "dharta.toml slug");
31
+ return deriveSlug(folder.name as string);
32
+ }
33
+
34
+ export async function inferAgentSlug(opts: AgentInferOptions = {}): Promise<string> {
35
+ validateAgentSelectorOptions(opts);
36
+ if (opts.agent !== undefined) return normalizeAgentSlug(opts.agent, "--agent");
37
+ if (opts.project !== undefined) return normalizeAgentSlug(opts.project, "--project");
38
+
39
+ const cwd = path.resolve(opts.dir ?? process.cwd());
40
+
41
+ if (opts.path) {
42
+ const dir = path.resolve(cwd, opts.path);
43
+ const folder = await describeFolder(dir, path.basename(dir));
44
+ if (folder.name) return slugForFolder(folder);
45
+ throw new Error(`No agent at ${opts.path} - it has no dharta.toml with a name.`);
46
+ }
47
+
48
+ const gitRoot = findGitRoot(cwd);
49
+ let localDir = cwd;
50
+ for (;;) {
51
+ if (existsSync(path.join(localDir, "dharta.toml"))) {
52
+ const local = await describeFolder(localDir, path.basename(localDir));
53
+ if (local.name) return slugForFolder(local);
54
+ throw new Error(`${path.join(localDir, "dharta.toml")} must declare an agent name.`);
55
+ }
56
+ if (gitRoot && localDir === path.resolve(gitRoot)) break;
57
+ const parent = path.dirname(localDir);
58
+ if (parent === localDir) break;
59
+ localDir = parent;
60
+ }
61
+ const scan = opts.scan ?? scanAgentFolders;
62
+ const agents = (await scan(gitRoot ?? cwd, { isGitRepo: !!gitRoot })).filter((f) => f.name);
63
+ const containing = agents
64
+ .filter((folder) => cwd === path.resolve(folder.dir) || cwd.startsWith(`${path.resolve(folder.dir)}${path.sep}`))
65
+ .sort((a, b) => path.resolve(b.dir).length - path.resolve(a.dir).length);
66
+ if (containing.length > 0) return slugForFolder(containing[0]);
67
+ if (agents.length === 1) return slugForFolder(agents[0]);
68
+
69
+ // The sole local agent wins over the env (local context beats CI); the env
70
+ // is the fallback when the local context is empty or ambiguous (a monorepo
71
+ // CI job names its target with DHARTA_PROJECT).
72
+ const envAgent = (opts.env ?? process.env).DHARTA_PROJECT;
73
+ if (envAgent !== undefined) return normalizeAgentSlug(envAgent, "DHARTA_PROJECT");
74
+
75
+ if (agents.length === 0) {
76
+ throw new Error(
77
+ "No agent here. Run inside an agent folder (one with a dharta.toml), or pass --agent <slug>.",
78
+ );
79
+ }
80
+ const list = agents.map((f) => f.rel).join(", ");
81
+ throw new Error(`Multiple agents here (${list}). Pass --agent <slug> or a [path] to pick one.`);
82
+ }
83
+
84
+ export function validateAgentSelectorOptions(opts: Pick<AgentInferOptions, "agent" | "project" | "dir"> = {}): void {
85
+ if (opts.agent !== undefined) validateAgentSlug(opts.agent, "--agent");
86
+ if (opts.project !== undefined) validateAgentSlug(opts.project, "--project");
87
+ if (opts.dir === undefined) return;
88
+ const dir = path.resolve(normalizeNonBlank(opts.dir, "--dir"));
89
+ try {
90
+ if (!statSync(dir).isDirectory()) {
91
+ throw new Error(`Cannot use --dir ${dir}: expected a directory.`);
92
+ }
93
+ } catch (error) {
94
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
95
+ throw new Error(`Cannot use --dir ${dir}: directory does not exist.`);
96
+ }
97
+ throw error;
98
+ }
99
+ }
100
+
101
+ // https://git.dharta.ai/{org}/{slug}.git -> slug. Still used by `deploy` to read
102
+ // the wired `dharta` remote (a source-repo slug there, which is what deploy wants).
103
+ export function slugFromRemote(url: string): string | null {
104
+ const match = url.match(/\/([^/]+)\/([^/]+?)(\.git)?$/);
105
+ return match ? match[2] : null;
106
+ }
107
+
108
+ // Port of the server's Agent.derive_slug: lowercase, underscores/spaces
109
+ // -> hyphens, drop disallowed chars, truncate to 63, strip edge hyphens, fall
110
+ // back to "agent". Shared by setup (default name), the agent resolver above,
111
+ // and the live preview JS.
112
+ export function deriveSlug(name: string): string {
113
+ let base = name.toLowerCase().replace(/[_\s]+/g, "-").replace(/[^a-z0-9-]/g, "");
114
+ base = base.slice(0, 63).replace(/^-+|-+$/g, "");
115
+ return base || "agent";
116
+ }
117
+
118
+ export function validateAgentSlug(value: string, label: string): string {
119
+ const normalized = normalizeNonBlank(value, label);
120
+ if (!AGENT_SLUG_FORMAT.test(normalized)) {
121
+ const suggestion = slugSuggestion(normalized);
122
+ throw new Error(
123
+ `${label} must be a valid agent slug: lowercase letters, digits, hyphens; max 63.` +
124
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
125
+ );
126
+ }
127
+ return normalized;
128
+ }
129
+
130
+ // Mirrors Agent's public display-name contract. Count Unicode code points,
131
+ // rather than UTF-16 code units, so client-side validation agrees with Ruby's
132
+ // String#length for non-ASCII names.
133
+ export function validateAgentName(value: string, label = "agent name"): string {
134
+ const normalized = normalizeNonBlank(value, label);
135
+ if ([...normalized].length > MAX_AGENT_NAME_CHARACTERS) {
136
+ throw new Error(`${label} must be 1-${MAX_AGENT_NAME_CHARACTERS} characters.`);
137
+ }
138
+ return normalized;
139
+ }
140
+
141
+ const normalizeAgentSlug = validateAgentSlug;
142
+
143
+ function slugSuggestion(value: string): string | null {
144
+ if (!/^[A-Za-z0-9 _-]+$/.test(value)) return null;
145
+ const suggestion = deriveSlug(value);
146
+ return suggestion !== value && AGENT_SLUG_FORMAT.test(suggestion) ? suggestion : null;
147
+ }
@@ -0,0 +1,88 @@
1
+ // Error formatting for the CLI's control-plane calls. Passed to the SDK
2
+ // HttpClient as `formatError` so CLI errors keep their human message + the
3
+ // `err.status`/`err.body` shape (instead of the SDK's typed errorFor()
4
+ // hierarchy, which the CLI doesn't surface).
5
+
6
+ import { SENSITIVE_API_KEY_SCOPES } from "./commands/scopes.js";
7
+
8
+ export function cliFormatError(status: number, parsed: unknown): Error {
9
+ // Server messages already name fixes (e.g. the transcript-access 403 points
10
+ // at the settings page) - keep them verbatim. Rails sends `message`; the
11
+ // agent host (FastAPI) sends `detail`; some raw upload endpoints send
12
+ // `error`.
13
+ const body = parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
14
+ const serverMessage =
15
+ typeof body.message === "string"
16
+ ? body.message
17
+ : typeof body.detail === "string"
18
+ ? body.detail
19
+ : typeof body.error === "string" && !looksLikeMachineCode(body.error)
20
+ ? body.error
21
+ : null;
22
+ let message = serverMessage ?? `Dharta API error ${status}`;
23
+ if (status === 401) {
24
+ message += `${message.endsWith(".") ? "" : "."} Run \`dharta login\` to re-authenticate (or check DHARTA_API_KEY).`;
25
+ } else if (status === 403 && /scope/i.test(message)) {
26
+ message += `${message.endsWith(".") ? "" : "."} Run \`dharta auth status\` to inspect the active key, or \`dharta open api-keys\` to manage key scopes.`;
27
+ const sensitiveScopes = missingSensitiveScopes(message);
28
+ if (sensitiveScopes.length > 0) {
29
+ message += ` Sensitive scopes (${sensitiveScopes.join(", ")}) require a bounded key with --expires-at within 90 days.`;
30
+ }
31
+ } else if (status === 404 && (!serverMessage || isGenericNotFound(serverMessage))) {
32
+ message +=
33
+ `${message.endsWith(".") ? "" : "."} ` +
34
+ "Check the id or agent slug; for agent commands, run `dharta agent list`. " +
35
+ "Run `dharta auth status` to confirm the active host; DHARTA_BASE_URL must point at the Dharta control API.";
36
+ }
37
+ const err = new Error(message) as Error & { status?: number; body?: unknown };
38
+ err.status = status;
39
+ err.body = parsed;
40
+ return err;
41
+ }
42
+
43
+ export function cliFormatNetworkError(url: string, error: unknown): Error {
44
+ const origin = new URL(url).origin;
45
+ return new Error(
46
+ `Could not reach Dharta at ${origin} (${formatCause(error)}). ` +
47
+ "Check your network and DHARTA_BASE_URL, then try again.",
48
+ );
49
+ }
50
+
51
+ export async function cliFormatRawError(res: Response): Promise<Error> {
52
+ const text = await res.text();
53
+ let parsed: unknown = null;
54
+ if (text.length > 0) {
55
+ try {
56
+ parsed = JSON.parse(text);
57
+ } catch {
58
+ parsed = text;
59
+ }
60
+ }
61
+ return cliFormatError(res.status, parsed);
62
+ }
63
+
64
+ function missingSensitiveScopes(message: string): string[] {
65
+ const mentioned = new Set(mentionedScopes(message));
66
+ if (/one of required scopes/i.test(message) && Array.from(mentioned).some((scope) => !SENSITIVE_API_KEY_SCOPES.has(scope))) {
67
+ return [];
68
+ }
69
+ return Array.from(SENSITIVE_API_KEY_SCOPES).filter((scope) => mentioned.has(scope));
70
+ }
71
+
72
+ function mentionedScopes(message: string): string[] {
73
+ const required = message.match(/required scopes?:\s*([a-z0-9_:,\s-]+)/i)?.[1];
74
+ const source = required ?? message;
75
+ return source.match(/[a-z][a-z0-9_]*(?::[a-z][a-z0-9_]*)?/g) ?? [];
76
+ }
77
+
78
+ function formatCause(error: unknown): string {
79
+ return error instanceof Error && error.message ? error.message : String(error);
80
+ }
81
+
82
+ function looksLikeMachineCode(value: string): boolean {
83
+ return /^[a-z][a-z0-9_:-]*$/.test(value);
84
+ }
85
+
86
+ function isGenericNotFound(value: string): boolean {
87
+ return /^resource not found\.?$/i.test(value.trim()) || /^not found\.?$/i.test(value.trim());
88
+ }
package/src/browser.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ // Best-effort browser launch; callers always print the URL too, so a
4
+ // headless box still works.
5
+ export function openBrowser(url: string): Promise<boolean> {
6
+ const cmd =
7
+ process.platform === "darwin"
8
+ ? "open"
9
+ : process.platform === "win32"
10
+ ? "explorer"
11
+ : "xdg-open";
12
+ return new Promise((resolve) => {
13
+ const proc = spawn(cmd, [url], { stdio: "ignore", detached: true });
14
+ proc.on("error", () => resolve(false));
15
+ proc.on("spawn", () => {
16
+ proc.unref();
17
+ resolve(true);
18
+ });
19
+ });
20
+ }