@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,302 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { CliContext } from "../context.js";
3
+ import { parseDollarAmountToCents } from "../money.js";
4
+
5
+ export interface Budget {
6
+ period: string;
7
+ max_cost_cents: number | null;
8
+ stop_usage: boolean;
9
+ alerts_enabled: boolean;
10
+ alert_threshold_pct: number;
11
+ additional_recipient_user_ids: number[];
12
+ max_total_tokens: number | null;
13
+ max_input_tokens: number | null;
14
+ max_output_tokens: number | null;
15
+ }
16
+
17
+ export interface BudgetUsage {
18
+ period_cost_cents: number;
19
+ total_tokens: number;
20
+ percent_used: number | null;
21
+ }
22
+
23
+ export interface BudgetEnvelope {
24
+ budget: Budget;
25
+ usage: BudgetUsage;
26
+ }
27
+
28
+ export interface BudgetSetOptions {
29
+ amount?: string;
30
+ hardStop?: boolean;
31
+ soft?: boolean;
32
+ enableAlerts?: boolean;
33
+ disableAlerts?: boolean;
34
+ alertThreshold?: string;
35
+ recipientUserId?: string[];
36
+ }
37
+
38
+ export interface EntityCap {
39
+ id: number;
40
+ cap_cents: number;
41
+ created_at: string;
42
+ updated_at: string;
43
+ }
44
+
45
+ export interface InstanceCapEnvelope {
46
+ type: "instance";
47
+ raw_instance_id: string;
48
+ agent_instance_id: string;
49
+ spend_micros: number;
50
+ cap: EntityCap | null;
51
+ changed?: boolean;
52
+ }
53
+
54
+ export interface EndUserCapEnvelope {
55
+ type: "end_user";
56
+ end_user_id: string;
57
+ spend_micros: number;
58
+ cap: EntityCap | null;
59
+ changed?: boolean;
60
+ }
61
+
62
+ export interface SeatCapEnvelope {
63
+ type: "seat";
64
+ raw_instance_id: string;
65
+ agent_instance_id: string;
66
+ end_user_id: string;
67
+ spend_micros: number;
68
+ cap: EntityCap | null;
69
+ changed?: boolean;
70
+ }
71
+
72
+ export interface CapSetOptions {
73
+ amount?: string;
74
+ clear?: boolean;
75
+ }
76
+
77
+ export async function capsBudgetData(ctx: CliContext): Promise<BudgetEnvelope> {
78
+ return ctx.http.request<BudgetEnvelope>({ path: "/api/limits/budget" });
79
+ }
80
+
81
+ export async function capsBudget(ctx: CliContext): Promise<string> {
82
+ const { budget, usage } = await capsBudgetData(ctx);
83
+ const hasBudget = budget.max_cost_cents !== null;
84
+ return [
85
+ `Monthly budget: ${formatMoney(budget.max_cost_cents)}`,
86
+ `Usage this period: ${formatMoney(usage.period_cost_cents)}`,
87
+ `Hard stop: ${hasBudget ? (budget.stop_usage ? "enabled" : "disabled") : "not active (no monthly budget)"}`,
88
+ `Alerts: ${hasBudget ? (budget.alerts_enabled ? `enabled at ${budget.alert_threshold_pct}%` : "disabled") : "not active (no monthly budget)"}`,
89
+ ].join("\n");
90
+ }
91
+
92
+ export async function capsBudgetSetData(ctx: CliContext, opts: BudgetSetOptions): Promise<BudgetEnvelope> {
93
+ validateBudgetSetOptions(opts);
94
+ const body: Record<string, unknown> = {
95
+ max_cost_cents: dollarsToCents(opts.amount),
96
+ };
97
+ const stopUsage = stopUsageOption(opts);
98
+ if (stopUsage !== undefined) body.stop_usage = stopUsage;
99
+ const alertsEnabled = alertsOption(opts);
100
+ if (alertsEnabled !== undefined) body.alerts_enabled = alertsEnabled;
101
+ if (opts.alertThreshold !== undefined) {
102
+ body.alert_threshold_pct = parseWholeNumberInRange(opts.alertThreshold, "--alert-threshold", 0, 100);
103
+ }
104
+ if (opts.recipientUserId?.length) {
105
+ body.additional_recipient_user_ids = opts.recipientUserId.map((id) => parsePositiveWholeNumber(id, "--recipient-user-id"));
106
+ }
107
+
108
+ return ctx.http.request<BudgetEnvelope>({
109
+ method: "PATCH",
110
+ path: "/api/limits/budget",
111
+ headers: { "Idempotency-Key": randomUUID() },
112
+ body,
113
+ });
114
+ }
115
+
116
+ export async function capsBudgetSet(ctx: CliContext, opts: BudgetSetOptions): Promise<string> {
117
+ const { budget } = await capsBudgetSetData(ctx, opts);
118
+ return `Monthly budget set to ${formatMoney(budget.max_cost_cents)}${budget.stop_usage ? " (usage stops at the limit)" : ""}.`;
119
+ }
120
+
121
+ export async function capsInstanceData(ctx: CliContext, instance: string): Promise<InstanceCapEnvelope> {
122
+ const normalizedInstance = normalizeRequired(instance, "instance id");
123
+ return ctx.http.request<InstanceCapEnvelope>({ path: `/api/limits/instances/${encodeURIComponent(normalizedInstance)}/cap` });
124
+ }
125
+
126
+ export async function capsInstance(ctx: CliContext, instance: string): Promise<string> {
127
+ const normalizedInstance = normalizeRequired(instance, "instance id");
128
+ return formatEntityCap(await capsInstanceData(ctx, normalizedInstance), `Instance ${normalizedInstance}`);
129
+ }
130
+
131
+ export async function capsInstanceSetData(ctx: CliContext, instance: string, opts: CapSetOptions): Promise<InstanceCapEnvelope> {
132
+ const normalizedInstance = normalizeRequired(instance, "instance id");
133
+ validateCapSetOptions(opts);
134
+ return ctx.http.request<InstanceCapEnvelope>({
135
+ method: "PATCH",
136
+ path: `/api/limits/instances/${encodeURIComponent(normalizedInstance)}/cap`,
137
+ headers: { "Idempotency-Key": randomUUID() },
138
+ body: capBody(opts),
139
+ });
140
+ }
141
+
142
+ export async function capsInstanceSet(ctx: CliContext, instance: string, opts: CapSetOptions): Promise<string> {
143
+ const normalizedInstance = normalizeRequired(instance, "instance id");
144
+ const result = await capsInstanceSetData(ctx, normalizedInstance, opts);
145
+ return capSetMessage(`Instance ${normalizedInstance}`, result.cap, result.changed);
146
+ }
147
+
148
+ export async function capsEndUserData(ctx: CliContext, endUserId: string): Promise<EndUserCapEnvelope> {
149
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
150
+ return ctx.http.request<EndUserCapEnvelope>({ path: `/api/limits/end_users/${encodeURIComponent(normalizedEndUserId)}/cap` });
151
+ }
152
+
153
+ export async function capsEndUser(ctx: CliContext, endUserId: string): Promise<string> {
154
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
155
+ return formatEntityCap(await capsEndUserData(ctx, normalizedEndUserId), `End-user ${normalizedEndUserId}`);
156
+ }
157
+
158
+ export async function capsEndUserSetData(ctx: CliContext, endUserId: string, opts: CapSetOptions): Promise<EndUserCapEnvelope> {
159
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
160
+ validateCapSetOptions(opts);
161
+ return ctx.http.request<EndUserCapEnvelope>({
162
+ method: "PATCH",
163
+ path: `/api/limits/end_users/${encodeURIComponent(normalizedEndUserId)}/cap`,
164
+ headers: { "Idempotency-Key": randomUUID() },
165
+ body: capBody(opts),
166
+ });
167
+ }
168
+
169
+ export async function capsEndUserSet(ctx: CliContext, endUserId: string, opts: CapSetOptions): Promise<string> {
170
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
171
+ const result = await capsEndUserSetData(ctx, normalizedEndUserId, opts);
172
+ return capSetMessage(`End-user ${normalizedEndUserId}`, result.cap, result.changed);
173
+ }
174
+
175
+ export async function capsSeatData(ctx: CliContext, instance: string, endUserId: string): Promise<SeatCapEnvelope> {
176
+ const normalizedInstance = normalizeRequired(instance, "instance id");
177
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
178
+ return ctx.http.request<SeatCapEnvelope>({
179
+ path: `/api/limits/instances/${encodeURIComponent(normalizedInstance)}/seat_cap?end_user_id=${encodeURIComponent(normalizedEndUserId)}`,
180
+ });
181
+ }
182
+
183
+ export async function capsSeat(ctx: CliContext, instance: string, endUserId: string): Promise<string> {
184
+ const normalizedInstance = normalizeRequired(instance, "instance id");
185
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
186
+ return formatEntityCap(await capsSeatData(ctx, normalizedInstance, normalizedEndUserId), `Seat ${normalizedEndUserId} on ${normalizedInstance}`);
187
+ }
188
+
189
+ export async function capsSeatSetData(ctx: CliContext, instance: string, endUserId: string, opts: CapSetOptions): Promise<SeatCapEnvelope> {
190
+ const normalizedInstance = normalizeRequired(instance, "instance id");
191
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
192
+ validateCapSetOptions(opts);
193
+ return ctx.http.request<SeatCapEnvelope>({
194
+ method: "PATCH",
195
+ path: `/api/limits/instances/${encodeURIComponent(normalizedInstance)}/seat_cap`,
196
+ headers: { "Idempotency-Key": randomUUID() },
197
+ body: { ...capBody(opts), end_user_id: normalizedEndUserId },
198
+ });
199
+ }
200
+
201
+ export async function capsSeatSet(ctx: CliContext, instance: string, endUserId: string, opts: CapSetOptions): Promise<string> {
202
+ const normalizedInstance = normalizeRequired(instance, "instance id");
203
+ const normalizedEndUserId = normalizeRequired(endUserId, "end-user id");
204
+ const result = await capsSeatSetData(ctx, normalizedInstance, normalizedEndUserId, opts);
205
+ return capSetMessage(`Seat ${normalizedEndUserId} on ${normalizedInstance}`, result.cap, result.changed);
206
+ }
207
+
208
+ function dollarsToCents(value: string | undefined): number {
209
+ if (value === undefined) {
210
+ throw new Error("Pass --amount <dollars>. Usage: dharta caps budget set --amount 250");
211
+ }
212
+ return parseDollarAmountToCents(value, "--amount");
213
+ }
214
+
215
+ export function validateBudgetSetOptions(opts: BudgetSetOptions): void {
216
+ dollarsToCents(opts.amount);
217
+ stopUsageOption(opts);
218
+ alertsOption(opts);
219
+ if (opts.alertThreshold !== undefined) {
220
+ parseWholeNumberInRange(opts.alertThreshold, "--alert-threshold", 0, 100);
221
+ }
222
+ for (const id of opts.recipientUserId ?? []) {
223
+ parsePositiveWholeNumber(id, "--recipient-user-id");
224
+ }
225
+ }
226
+
227
+ export function validateCapSetOptions(opts: CapSetOptions): void {
228
+ capBody(opts);
229
+ }
230
+
231
+ function capBody(opts: CapSetOptions): Record<string, unknown> {
232
+ if (opts.clear && opts.amount) throw new Error("Use either --amount or --clear, not both.");
233
+ if (opts.clear) return { clear: true };
234
+ if (!opts.amount) throw new Error("Pass --amount <dollars> or --clear.");
235
+ return { cap_cents: dollarsToCents(opts.amount) };
236
+ }
237
+
238
+ function parseWholeNumberInRange(value: string, flag: string, min: number, max: number): number {
239
+ const normalized = value.trim();
240
+ if (!/^\d+$/.test(normalized)) throw new Error(`${flag} must be a whole number.`);
241
+ const parsed = Number(normalized);
242
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
243
+ throw new Error(`${flag} must be a whole number from ${min} to ${max}.`);
244
+ }
245
+ return parsed;
246
+ }
247
+
248
+ function parsePositiveWholeNumber(value: string, flag: string): number {
249
+ const normalized = value.trim();
250
+ if (!/^\d+$/.test(normalized)) throw new Error(`${flag} must be a whole number.`);
251
+ const parsed = Number(normalized);
252
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
253
+ throw new Error(`${flag} must be a positive whole number.`);
254
+ }
255
+ return parsed;
256
+ }
257
+
258
+ function stopUsageOption(opts: BudgetSetOptions): boolean | undefined {
259
+ if (opts.hardStop && opts.soft) throw new Error("Use either --hard-stop or --soft, not both.");
260
+ if (opts.hardStop) return true;
261
+ if (opts.soft) return false;
262
+ return undefined;
263
+ }
264
+
265
+ function alertsOption(opts: BudgetSetOptions): boolean | undefined {
266
+ if (opts.enableAlerts && opts.disableAlerts) {
267
+ throw new Error("Use either --enable-alerts or --disable-alerts, not both.");
268
+ }
269
+ if (opts.enableAlerts) return true;
270
+ if (opts.disableAlerts) return false;
271
+ return undefined;
272
+ }
273
+
274
+ function formatMoney(cents: number | null): string {
275
+ if (cents === null) return "unlimited";
276
+ return `$${(cents / 100).toFixed(2)}`;
277
+ }
278
+
279
+ function formatMicros(micros: number): string {
280
+ return `$${(micros / 100_000_000).toFixed(4)}`;
281
+ }
282
+
283
+ function formatEntityCap(result: { spend_micros: number; cap: EntityCap | null }, label: string): string {
284
+ return [
285
+ `${label}`,
286
+ `Spend this period: ${formatMicros(result.spend_micros)}`,
287
+ `Cap: ${result.cap ? formatMoney(result.cap.cap_cents) : "none"}`,
288
+ ].join("\n");
289
+ }
290
+
291
+ function capSetMessage(label: string, cap: EntityCap | null, changed: boolean | undefined): string {
292
+ const state = cap ? formatMoney(cap.cap_cents) : "cleared";
293
+ return `${label} cap ${state}${changed === false ? " (unchanged)" : ""}.`;
294
+ }
295
+
296
+ function normalizeRequired(value: string, label: string): string {
297
+ const normalized = value.trim();
298
+ if (!normalized) {
299
+ throw new Error(`${label} cannot be blank.`);
300
+ }
301
+ return normalized;
302
+ }
@@ -0,0 +1,114 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { renderManifest } from "../manifest.js";
4
+ import { scaffoldTemplate } from "../scaffold.js";
5
+ import { deriveSlug } from "../agent.js";
6
+ import { normalizeNonBlank } from "../validators.js";
7
+ import { DEFAULT_CREATE_HARNESS, hasAgentMarker, parseHarnessType, type HarnessType } from "../harnesses.js";
8
+
9
+ // `dharta create [path]` (RFC 0024): bring a brand-new agent into existence -
10
+ // lay down a starter template + a default (deploy-off) dharta.toml. It does ONE
11
+ // thing: scaffold. Configuration - the deploy gate, delivery method, rename,
12
+ // git/github wiring - is `dharta setup`'s job. One agent per folder: create
13
+ // refuses a folder that already holds an agent (pointing at setup), and setup
14
+ // refuses one that doesn't (pointing back here), so the two never overlap.
15
+
16
+ export interface CreateOptions {
17
+ cwd?: string;
18
+ name?: string;
19
+ template?: string; // starter template (default "blank")
20
+ harness?: string; // supported harness type (default "claude-code")
21
+ harnessVersion?: string; // optional exact/range pin; omitted = curated default
22
+ baseUrl: string; // template source (control plane; falls back to bundled)
23
+ fetchImpl?: typeof fetch;
24
+ log?: (line: string) => void;
25
+ }
26
+
27
+ export interface CreateResult {
28
+ folder: string; // relative to cwd
29
+ name: string;
30
+ template: string;
31
+ harness: HarnessType;
32
+ harnessVersion?: string;
33
+ instructionFile: "CLAUDE.md" | "AGENTS.md";
34
+ manifestPath: string; // absolute
35
+ files: string[]; // everything written, relative to the folder
36
+ }
37
+
38
+ export async function create(pathArg: string | undefined, opts: CreateOptions): Promise<CreateResult> {
39
+ const cwd = path.resolve(opts.cwd ?? ".");
40
+ const log = opts.log ?? ((line: string) => console.error(line));
41
+ const pathInput = pathArg === undefined ? undefined : normalizeNonBlank(pathArg, "path");
42
+ const targetDir = path.resolve(cwd, pathInput ?? ".");
43
+ const rel = path.relative(cwd, targetDir) || ".";
44
+ const name = opts.name === undefined ? deriveSlug(path.basename(targetDir)) : normalizeNonBlank(opts.name, "--name");
45
+ const template = opts.template === undefined ? undefined : normalizeNonBlank(opts.template, "--template");
46
+ const harness = parseHarnessType(opts.harness, "--harness") ?? DEFAULT_CREATE_HARNESS;
47
+ const harnessVersion = opts.harnessVersion === undefined
48
+ ? undefined
49
+ : normalizeNonBlank(opts.harnessVersion, "--harness-version");
50
+ // Echo what the user typed (a clean `lifecoach`) in messages rather than the
51
+ // resolved ../../ path when they pass an absolute or distant target.
52
+ const shown = pathInput && pathInput !== "." ? pathInput : null;
53
+ const here = shown ?? "this folder";
54
+ const at = shown ? ` ${shown}` : "";
55
+
56
+ await validateTargetDirectory(targetDir, here);
57
+
58
+ // One agent per folder: a marker means an agent already lives here - that's
59
+ // setup's job, not create's. Refuse rather than scaffold over it.
60
+ if (hasAgentMarker(targetDir)) {
61
+ throw new Error(
62
+ `An agent already exists in ${here}. Configure it with \`dharta setup${at}\` instead.`,
63
+ );
64
+ }
65
+
66
+ // Scaffold the template (CLAUDE.md / .claude / .gitignore - never dharta.toml,
67
+ // which we own below).
68
+ const scaffold = await scaffoldTemplate(targetDir, {
69
+ template,
70
+ harness,
71
+ baseUrl: opts.baseUrl,
72
+ fetchImpl: opts.fetchImpl,
73
+ log,
74
+ });
75
+
76
+ // Write the self-documenting manifest. A brand-new agent is deploy = false
77
+ // (the D6 fail-safe); `dharta setup --enable` flips the gate when you're ready.
78
+ const manifestPath = path.join(targetDir, "dharta.toml");
79
+ await fs.writeFile(manifestPath, renderManifest({ name, deploy: false, harness, harnessVersion }));
80
+
81
+ const files = [...scaffold.files, "dharta.toml"];
82
+ log(
83
+ `Created ${harness} agent '${name}' in ${here} from the '${scaffold.template}' template ` +
84
+ `(${files.join(", ")}).`,
85
+ );
86
+ log(`Next: edit ${scaffold.instructionFile}, chat locally with \`dharta dev${at}\`, then \`dharta setup${at}\` to set the deploy gate + delivery method.`);
87
+
88
+ return {
89
+ folder: rel,
90
+ name,
91
+ template: scaffold.template,
92
+ harness,
93
+ harnessVersion,
94
+ instructionFile: scaffold.instructionFile,
95
+ manifestPath,
96
+ files,
97
+ };
98
+ }
99
+
100
+ async function validateTargetDirectory(targetDir: string, label: string): Promise<void> {
101
+ try {
102
+ const stat = await fs.stat(targetDir);
103
+ if (!stat.isDirectory()) {
104
+ throw new Error(`Cannot create agent in ${label}: expected a directory, got a file.`);
105
+ }
106
+ } catch (error) {
107
+ const code = (error as NodeJS.ErrnoException).code;
108
+ if (code === "ENOENT") return;
109
+ if (code === "ENOTDIR") {
110
+ throw new Error(`Cannot create agent in ${label}: a parent path is not a directory.`);
111
+ }
112
+ throw error;
113
+ }
114
+ }