@dharta/cli 0.6.77 → 0.6.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +215 -11
  3. package/dist/agent.js +3 -2
  4. package/dist/agent.js.map +1 -1
  5. package/dist/api-error.js +6 -6
  6. package/dist/api-error.js.map +1 -1
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.js +860 -149
  9. package/dist/cli.js.map +1 -1
  10. package/dist/command-tree.d.ts +3 -0
  11. package/dist/command-tree.js +10 -0
  12. package/dist/command-tree.js.map +1 -0
  13. package/dist/commands/agents.d.ts +54 -17
  14. package/dist/commands/agents.js +246 -136
  15. package/dist/commands/agents.js.map +1 -1
  16. package/dist/commands/analytics.js +6 -5
  17. package/dist/commands/analytics.js.map +1 -1
  18. package/dist/commands/api.js +71 -86
  19. package/dist/commands/api.js.map +1 -1
  20. package/dist/commands/appearance.js +5 -6
  21. package/dist/commands/appearance.js.map +1 -1
  22. package/dist/commands/artifact.js +7 -6
  23. package/dist/commands/artifact.js.map +1 -1
  24. package/dist/commands/audit-log.js +2 -1
  25. package/dist/commands/audit-log.js.map +1 -1
  26. package/dist/commands/auth.js +26 -6
  27. package/dist/commands/auth.js.map +1 -1
  28. package/dist/commands/caps.js +11 -15
  29. package/dist/commands/caps.js.map +1 -1
  30. package/dist/commands/deploy.js +25 -10
  31. package/dist/commands/deploy.js.map +1 -1
  32. package/dist/commands/dev.d.ts +10 -1
  33. package/dist/commands/dev.js +56 -25
  34. package/dist/commands/dev.js.map +1 -1
  35. package/dist/commands/doctor.d.ts +2 -0
  36. package/dist/commands/doctor.js +60 -0
  37. package/dist/commands/doctor.js.map +1 -1
  38. package/dist/commands/embed-keys.js +4 -13
  39. package/dist/commands/embed-keys.js.map +1 -1
  40. package/dist/commands/env.js +4 -7
  41. package/dist/commands/env.js.map +1 -1
  42. package/dist/commands/files.js +3 -4
  43. package/dist/commands/files.js.map +1 -1
  44. package/dist/commands/fleet.d.ts +129 -0
  45. package/dist/commands/fleet.js +408 -0
  46. package/dist/commands/fleet.js.map +1 -0
  47. package/dist/commands/genui.d.ts +2 -0
  48. package/dist/commands/genui.js +53 -0
  49. package/dist/commands/genui.js.map +1 -0
  50. package/dist/commands/git-credential.js +2 -3
  51. package/dist/commands/git-credential.js.map +1 -1
  52. package/dist/commands/kartas.d.ts +9 -0
  53. package/dist/commands/kartas.js +33 -11
  54. package/dist/commands/kartas.js.map +1 -1
  55. package/dist/commands/keys.d.ts +17 -18
  56. package/dist/commands/keys.js +108 -19
  57. package/dist/commands/keys.js.map +1 -1
  58. package/dist/commands/login.js +2 -1
  59. package/dist/commands/login.js.map +1 -1
  60. package/dist/commands/members.d.ts +10 -0
  61. package/dist/commands/members.js +20 -6
  62. package/dist/commands/members.js.map +1 -1
  63. package/dist/commands/model-connections.d.ts +44 -0
  64. package/dist/commands/model-connections.js +435 -0
  65. package/dist/commands/model-connections.js.map +1 -0
  66. package/dist/commands/open.d.ts +2 -2
  67. package/dist/commands/open.js +4 -3
  68. package/dist/commands/open.js.map +1 -1
  69. package/dist/commands/org.d.ts +9 -0
  70. package/dist/commands/org.js +42 -0
  71. package/dist/commands/org.js.map +1 -0
  72. package/dist/commands/organization.d.ts +16 -0
  73. package/dist/commands/organization.js +118 -0
  74. package/dist/commands/organization.js.map +1 -0
  75. package/dist/commands/parity.js +1 -3
  76. package/dist/commands/parity.js.map +1 -1
  77. package/dist/commands/repos.d.ts +4 -0
  78. package/dist/commands/repos.js +17 -0
  79. package/dist/commands/repos.js.map +1 -0
  80. package/dist/commands/rollback.js +2 -3
  81. package/dist/commands/rollback.js.map +1 -1
  82. package/dist/commands/schedules.js +26 -21
  83. package/dist/commands/schedules.js.map +1 -1
  84. package/dist/commands/scopes.js +8 -1
  85. package/dist/commands/scopes.js.map +1 -1
  86. package/dist/commands/sessions.js +9 -3
  87. package/dist/commands/sessions.js.map +1 -1
  88. package/dist/commands/setup.js +5 -4
  89. package/dist/commands/setup.js.map +1 -1
  90. package/dist/commands/update.d.ts +1 -0
  91. package/dist/commands/update.js +24 -11
  92. package/dist/commands/update.js.map +1 -1
  93. package/dist/commands/webhooks.d.ts +4 -1
  94. package/dist/commands/webhooks.js +21 -16
  95. package/dist/commands/webhooks.js.map +1 -1
  96. package/dist/completions.js +24 -13
  97. package/dist/completions.js.map +1 -1
  98. package/dist/config.d.ts +12 -0
  99. package/dist/config.js +121 -7
  100. package/dist/config.js.map +1 -1
  101. package/dist/context.d.ts +2 -0
  102. package/dist/context.js +23 -7
  103. package/dist/context.js.map +1 -1
  104. package/dist/docs/cli-reference.d.ts +2 -0
  105. package/dist/docs/cli-reference.js +191 -0
  106. package/dist/docs/cli-reference.js.map +1 -0
  107. package/dist/exit-codes.d.ts +25 -0
  108. package/dist/exit-codes.js +107 -0
  109. package/dist/exit-codes.js.map +1 -0
  110. package/dist/genui.d.ts +178 -0
  111. package/dist/genui.js +1130 -0
  112. package/dist/genui.js.map +1 -0
  113. package/dist/harnesses.js +2 -1
  114. package/dist/harnesses.js.map +1 -1
  115. package/dist/jq.d.ts +4 -0
  116. package/dist/jq.js +91 -0
  117. package/dist/jq.js.map +1 -0
  118. package/dist/login-offer.js +6 -2
  119. package/dist/login-offer.js.map +1 -1
  120. package/dist/manifest.d.ts +12 -0
  121. package/dist/manifest.js +126 -0
  122. package/dist/manifest.js.map +1 -1
  123. package/dist/model-auth.d.ts +29 -0
  124. package/dist/model-auth.js +117 -0
  125. package/dist/model-auth.js.map +1 -0
  126. package/dist/model-settings.d.ts +23 -0
  127. package/dist/model-settings.js +186 -0
  128. package/dist/model-settings.js.map +1 -0
  129. package/dist/money.js +2 -1
  130. package/dist/money.js.map +1 -1
  131. package/dist/output.d.ts +6 -0
  132. package/dist/output.js +54 -0
  133. package/dist/output.js.map +1 -1
  134. package/dist/runtime.d.ts +1 -1
  135. package/dist/runtime.js +1 -1
  136. package/dist/scaffold.js +2 -1
  137. package/dist/scaffold.js.map +1 -1
  138. package/dist/validators.js +17 -11
  139. package/dist/validators.js.map +1 -1
  140. package/dist/version-check.d.ts +1 -1
  141. package/dist/version-check.js +1 -1
  142. package/dist/version-check.js.map +1 -1
  143. package/node_modules/@dharta/contracts/LICENSE +21 -0
  144. package/node_modules/@dharta/contracts/README.md +45 -0
  145. package/node_modules/@dharta/contracts/artifact-runtime.json +750 -0
  146. package/node_modules/@dharta/contracts/dist/activity.d.ts +62 -0
  147. package/node_modules/@dharta/contracts/dist/activity.d.ts.map +1 -0
  148. package/node_modules/@dharta/contracts/dist/activity.js +178 -0
  149. package/node_modules/@dharta/contracts/dist/activity.js.map +1 -0
  150. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.d.ts +20 -0
  151. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.d.ts.map +1 -0
  152. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.js +53 -0
  153. package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.js.map +1 -0
  154. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.d.ts +37 -0
  155. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.d.ts.map +1 -0
  156. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.js +5107 -0
  157. package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.js.map +1 -0
  158. package/node_modules/@dharta/contracts/dist/artifact-runtime.d.ts +154 -0
  159. package/node_modules/@dharta/contracts/dist/artifact-runtime.d.ts.map +1 -0
  160. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.d.ts +1670 -0
  161. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.d.ts.map +1 -0
  162. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.js +1903 -0
  163. package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.js.map +1 -0
  164. package/node_modules/@dharta/contracts/dist/artifact-runtime.js +377 -0
  165. package/node_modules/@dharta/contracts/dist/artifact-runtime.js.map +1 -0
  166. package/node_modules/@dharta/contracts/dist/index.d.ts +6 -0
  167. package/node_modules/@dharta/contracts/dist/index.d.ts.map +1 -0
  168. package/node_modules/@dharta/contracts/dist/index.js +6 -0
  169. package/node_modules/@dharta/contracts/dist/index.js.map +1 -0
  170. package/node_modules/@dharta/contracts/dist/model-auth.d.ts +31 -0
  171. package/node_modules/@dharta/contracts/dist/model-auth.d.ts.map +1 -0
  172. package/node_modules/@dharta/contracts/dist/model-auth.js +45 -0
  173. package/node_modules/@dharta/contracts/dist/model-auth.js.map +1 -0
  174. package/node_modules/@dharta/contracts/dist/tasks.d.ts +44 -0
  175. package/node_modules/@dharta/contracts/dist/tasks.d.ts.map +1 -0
  176. package/node_modules/@dharta/contracts/dist/tasks.js +127 -0
  177. package/node_modules/@dharta/contracts/dist/tasks.js.map +1 -0
  178. package/node_modules/@dharta/contracts/dist/wire-events.d.ts +71 -0
  179. package/node_modules/@dharta/contracts/dist/wire-events.d.ts.map +1 -0
  180. package/node_modules/@dharta/contracts/dist/wire-events.js +48 -0
  181. package/node_modules/@dharta/contracts/dist/wire-events.js.map +1 -0
  182. package/node_modules/@dharta/contracts/model-auth-capabilities-v1.json +168 -0
  183. package/node_modules/@dharta/contracts/model-auth-reasons-v1.json +38 -0
  184. package/node_modules/@dharta/contracts/package.json +44 -0
  185. package/node_modules/@dharta/contracts/src/activity.ts +215 -0
  186. package/node_modules/@dharta/contracts/src/artifact-runtime-formats.ts +54 -0
  187. package/node_modules/@dharta/contracts/src/artifact-runtime-validators.generated.ts +29 -0
  188. package/node_modules/@dharta/contracts/src/artifact-runtime.generated.ts +2093 -0
  189. package/node_modules/@dharta/contracts/src/artifact-runtime.ts +520 -0
  190. package/node_modules/@dharta/contracts/src/index.ts +5 -0
  191. package/node_modules/@dharta/contracts/src/model-auth.ts +82 -0
  192. package/node_modules/@dharta/contracts/src/tasks.ts +142 -0
  193. package/node_modules/@dharta/contracts/src/wire-events.ts +150 -0
  194. package/node_modules/@dharta/contracts/wire-events.json +29 -0
  195. package/node_modules/@dharta/sdk/LICENSE +1 -1
  196. package/node_modules/@dharta/sdk/README.md +22 -8
  197. package/node_modules/@dharta/sdk/dist/errors.d.ts +9 -7
  198. package/node_modules/@dharta/sdk/dist/errors.d.ts.map +1 -1
  199. package/node_modules/@dharta/sdk/dist/errors.js +36 -18
  200. package/node_modules/@dharta/sdk/dist/errors.js.map +1 -1
  201. package/node_modules/@dharta/sdk/dist/fleet.d.ts +69 -0
  202. package/node_modules/@dharta/sdk/dist/fleet.d.ts.map +1 -0
  203. package/node_modules/@dharta/sdk/dist/fleet.js +240 -0
  204. package/node_modules/@dharta/sdk/dist/fleet.js.map +1 -0
  205. package/node_modules/@dharta/sdk/dist/http.d.ts +6 -2
  206. package/node_modules/@dharta/sdk/dist/http.d.ts.map +1 -1
  207. package/node_modules/@dharta/sdk/dist/http.js +71 -12
  208. package/node_modules/@dharta/sdk/dist/http.js.map +1 -1
  209. package/node_modules/@dharta/sdk/dist/idempotency.d.ts +12 -0
  210. package/node_modules/@dharta/sdk/dist/idempotency.d.ts.map +1 -0
  211. package/node_modules/@dharta/sdk/dist/idempotency.js +72 -0
  212. package/node_modules/@dharta/sdk/dist/idempotency.js.map +1 -0
  213. package/node_modules/@dharta/sdk/dist/index.d.ts +20 -6
  214. package/node_modules/@dharta/sdk/dist/index.d.ts.map +1 -1
  215. package/node_modules/@dharta/sdk/dist/index.js +27 -4
  216. package/node_modules/@dharta/sdk/dist/index.js.map +1 -1
  217. package/node_modules/@dharta/sdk/dist/resources.d.ts +108 -13
  218. package/node_modules/@dharta/sdk/dist/resources.d.ts.map +1 -1
  219. package/node_modules/@dharta/sdk/dist/resources.js +403 -57
  220. package/node_modules/@dharta/sdk/dist/resources.js.map +1 -1
  221. package/node_modules/@dharta/sdk/dist/types.d.ts +436 -16
  222. package/node_modules/@dharta/sdk/dist/types.d.ts.map +1 -1
  223. package/node_modules/@dharta/sdk/dist/types.js +31 -2
  224. package/node_modules/@dharta/sdk/dist/types.js.map +1 -1
  225. package/node_modules/@dharta/sdk/package.json +9 -3
  226. package/node_modules/@dharta/sdk/src/errors.ts +41 -18
  227. package/node_modules/@dharta/sdk/src/fleet.ts +339 -0
  228. package/node_modules/@dharta/sdk/src/http.ts +110 -17
  229. package/node_modules/@dharta/sdk/src/idempotency.ts +101 -0
  230. package/node_modules/@dharta/sdk/src/index.ts +50 -11
  231. package/node_modules/@dharta/sdk/src/resources.ts +549 -68
  232. package/node_modules/@dharta/sdk/src/types.ts +629 -20
  233. package/package.json +16 -6
  234. package/src/agent.ts +3 -2
  235. package/src/api-error.ts +6 -5
  236. package/src/cli.ts +978 -163
  237. package/src/command-tree.ts +13 -0
  238. package/src/commands/agents.ts +365 -167
  239. package/src/commands/analytics.ts +6 -5
  240. package/src/commands/api.ts +73 -92
  241. package/src/commands/appearance.ts +5 -6
  242. package/src/commands/artifact.ts +7 -6
  243. package/src/commands/audit-log.ts +2 -1
  244. package/src/commands/auth.ts +25 -5
  245. package/src/commands/caps.ts +11 -15
  246. package/src/commands/deploy.ts +18 -4
  247. package/src/commands/dev.ts +86 -25
  248. package/src/commands/doctor.ts +69 -0
  249. package/src/commands/embed-keys.ts +4 -14
  250. package/src/commands/env.ts +4 -7
  251. package/src/commands/files.ts +3 -4
  252. package/src/commands/fleet.ts +672 -0
  253. package/src/commands/genui.ts +59 -0
  254. package/src/commands/git-credential.ts +2 -3
  255. package/src/commands/kartas.ts +45 -11
  256. package/src/commands/keys.ts +152 -35
  257. package/src/commands/login.ts +2 -1
  258. package/src/commands/members.ts +31 -6
  259. package/src/commands/model-connections.ts +547 -0
  260. package/src/commands/open.ts +5 -4
  261. package/src/commands/org.ts +57 -0
  262. package/src/commands/organization.ts +153 -0
  263. package/src/commands/parity.ts +1 -3
  264. package/src/commands/repos.ts +23 -0
  265. package/src/commands/rollback.ts +2 -3
  266. package/src/commands/schedules.ts +33 -22
  267. package/src/commands/scopes.ts +8 -1
  268. package/src/commands/sessions.ts +9 -3
  269. package/src/commands/setup.ts +5 -4
  270. package/src/commands/update.ts +28 -12
  271. package/src/commands/webhooks.ts +30 -19
  272. package/src/completions.ts +25 -13
  273. package/src/config.ts +141 -9
  274. package/src/context.ts +34 -7
  275. package/src/docs/cli-reference.ts +206 -0
  276. package/src/exit-codes.ts +122 -0
  277. package/src/genui.ts +1398 -0
  278. package/src/harnesses.ts +2 -1
  279. package/src/jq.ts +109 -0
  280. package/src/login-offer.ts +6 -2
  281. package/src/manifest.ts +134 -0
  282. package/src/model-auth.ts +173 -0
  283. package/src/model-settings.ts +222 -0
  284. package/src/money.ts +5 -1
  285. package/src/output.ts +60 -0
  286. package/src/runtime.ts +1 -1
  287. package/src/scaffold.ts +2 -1
  288. package/src/validators.ts +18 -11
  289. package/src/version-check.ts +1 -1
  290. package/dist/commands/model-keys.d.ts +0 -22
  291. package/dist/commands/model-keys.js +0 -222
  292. package/dist/commands/model-keys.js.map +0 -1
  293. package/src/commands/model-keys.ts +0 -278
@@ -0,0 +1,153 @@
1
+ // Organization-wide model settings (RFC 0070). These are the defaults every
2
+ // agent inherits until it overrides them with `dharta agent model`.
3
+
4
+ import type { CliContext } from "../context.js";
5
+ import {
6
+ MODEL_SETTINGS_ENDPOINT_KINDS,
7
+ type ModelSettingsEndpointKind,
8
+ type OrganizationModelSettings,
9
+ type OrganizationModelSettingsUpdateParams,
10
+ } from "@dharta/sdk";
11
+ import { closestChoice, normalizeHttpUrl, normalizeNonBlank } from "../validators.js";
12
+ import {
13
+ type ModelParameterOptions,
14
+ PLATFORM_CONNECTOR_SLUG,
15
+ formatModelParameters,
16
+ modelParameterOptionPresent,
17
+ modelParameterPatch,
18
+ normalizeModelParameterOptions,
19
+ normalizeModelReference,
20
+ parseConnectorOrder,
21
+ validateModelParameterOptions,
22
+ } from "../model-settings.js";
23
+ import { CliUsageError } from "../exit-codes.js";
24
+
25
+ export interface OrgModelSetOptions extends ModelParameterOptions {
26
+ connector?: string;
27
+ modelRef?: string;
28
+ endpointUrl?: string;
29
+ endpointKind?: string;
30
+ }
31
+
32
+ export const ORG_ENDPOINT_KINDS = MODEL_SETTINGS_ENDPOINT_KINDS;
33
+
34
+ export async function orgModelShowData(ctx: CliContext): Promise<OrganizationModelSettings> {
35
+ // Through the SDK resource, not a hand-built request (RFC 0048 D2).
36
+ return ctx.sdk.organization.modelSettings.get();
37
+ }
38
+
39
+ export async function orgModelShow(ctx: CliContext): Promise<string> {
40
+ return formatOrganizationModelSettings(await orgModelShowData(ctx));
41
+ }
42
+
43
+ export async function orgModelSetData(
44
+ ctx: CliContext,
45
+ opts: OrgModelSetOptions,
46
+ ): Promise<OrganizationModelSettings> {
47
+ const normalized = validateOrgModelSetOptions(opts);
48
+ const body: OrganizationModelSettingsUpdateParams = {};
49
+ if (normalized.connector !== undefined) {
50
+ body.connector_order = parseConnectorOrder(normalized.connector, "--connector");
51
+ }
52
+ if (normalized.modelRef !== undefined) {
53
+ body.model_reference = normalizeModelReference(normalized.modelRef, "--model-ref");
54
+ }
55
+ // An empty --endpoint-url clears the override, the same way the console's
56
+ // custom-endpoint form clears it. The kind is switched, never cleared: the
57
+ // column is NOT NULL, and clearing an override means clearing the URL.
58
+ if (normalized.endpointUrl !== undefined) {
59
+ body.provider_endpoint_url = normalized.endpointUrl === ""
60
+ ? ""
61
+ : normalizeEndpointUrl(normalized.endpointUrl);
62
+ }
63
+ if (normalized.endpointKind !== undefined) {
64
+ body.provider_endpoint_kind = normalizeEndpointKind(normalized.endpointKind);
65
+ }
66
+ const modelParameters = modelParameterPatch(normalized);
67
+ if (Object.keys(modelParameters).length > 0) body.model_parameters = modelParameters;
68
+ return ctx.sdk.organization.modelSettings.update(body);
69
+ }
70
+
71
+ export async function orgModelSet(ctx: CliContext, opts: OrgModelSetOptions): Promise<string> {
72
+ return formatOrganizationModelSettings(await orgModelSetData(ctx, opts));
73
+ }
74
+
75
+ export function validateOrgModelSetOptions(opts: OrgModelSetOptions): OrgModelSetOptions {
76
+ const normalized: OrgModelSetOptions = {
77
+ ...normalizeModelParameterOptions(opts),
78
+ // `--connector ""` and `--model-ref ""` are meaningful: an empty
79
+ // connector order falls back to the platform connector, and an empty
80
+ // model reference falls back to the connector default.
81
+ connector: opts.connector?.trim(),
82
+ modelRef: opts.modelRef?.trim(),
83
+ // `--endpoint-url ""` is meaningful too: it clears the override.
84
+ endpointUrl: opts.endpointUrl?.trim(),
85
+ endpointKind: opts.endpointKind?.trim(),
86
+ };
87
+ if (
88
+ normalized.connector === undefined &&
89
+ normalized.modelRef === undefined &&
90
+ normalized.endpointUrl === undefined &&
91
+ normalized.endpointKind === undefined &&
92
+ !modelParameterOptionPresent(normalized)
93
+ ) {
94
+ throw new CliUsageError(
95
+ "Pass at least one connector option, model reference option, endpoint option, or parameter option.",
96
+ );
97
+ }
98
+ if (normalized.connector !== undefined) parseConnectorOrder(normalized.connector, "--connector");
99
+ if (normalized.modelRef !== undefined) normalizeModelReference(normalized.modelRef, "--model-ref");
100
+ if (normalized.endpointUrl) normalizeEndpointUrl(normalized.endpointUrl);
101
+ if (normalized.endpointKind !== undefined) normalizeEndpointKind(normalized.endpointKind);
102
+ validateModelParameterOptions(normalized);
103
+ return normalized;
104
+ }
105
+
106
+ export function formatOrganizationModelSettings(settings: OrganizationModelSettings): string {
107
+ const order = settings.connector_order ?? [];
108
+ const parameters = formatModelParameters(settings.model_parameters);
109
+ return [
110
+ `Connector order: ${order.length > 0
111
+ ? order.join(", ")
112
+ : `unset (resolves to ${PLATFORM_CONNECTOR_SLUG}, the platform connector)`}`,
113
+ `Model reference: ${settings.model_reference || "unset (the connector default model is used)"}`,
114
+ `Model parameters: ${parameters || "none"}`,
115
+ `Custom endpoint: ${settings.provider_endpoint_url
116
+ ? `${settings.provider_endpoint_url} (${settings.provider_endpoint_kind})`
117
+ : "none (each connector calls its provider's standard API)"}`,
118
+ `Resolved model: ${formatResolved(settings)}`,
119
+ ].join("\n");
120
+ }
121
+
122
+ function normalizeEndpointUrl(value: string): string {
123
+ const normalized = normalizeHttpUrl(value, "--endpoint-url");
124
+ if (new URL(normalized).protocol !== "https:") {
125
+ throw new CliUsageError("--endpoint-url must use https. Pass an empty string to clear the override.");
126
+ }
127
+ return normalized;
128
+ }
129
+
130
+ function normalizeEndpointKind(value: string): ModelSettingsEndpointKind {
131
+ const normalized = normalizeNonBlank(value, "--endpoint-kind");
132
+ if ((ORG_ENDPOINT_KINDS as readonly string[]).includes(normalized)) {
133
+ return normalized as ModelSettingsEndpointKind;
134
+ }
135
+ const suggestion = closestChoice(normalized, ORG_ENDPOINT_KINDS);
136
+ throw new CliUsageError(
137
+ `Unknown --endpoint-kind ${normalized}. Allowed kinds: ${ORG_ENDPOINT_KINDS.join(", ")}.` +
138
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
139
+ );
140
+ }
141
+
142
+ // The SDK types `resolved` as a discriminated union, but this parses a server
143
+ // response, so the field guards below stay: a partial payload prints what it
144
+ // can rather than the string "undefined".
145
+ function formatResolved(settings: OrganizationModelSettings): string {
146
+ const resolved = settings.resolved;
147
+ if (!resolved) return "unavailable";
148
+ if (resolved.error) return `unavailable (${resolved.error})`;
149
+ const model = resolved.model_id ?? "provider default";
150
+ const provider = resolved.provider ? `${resolved.provider}/` : "";
151
+ const connector = resolved.connector ? ` through connector ${resolved.connector}` : "";
152
+ return `${provider}${model}${connector}`;
153
+ }
@@ -6,7 +6,6 @@
6
6
  // parity of code + config, not infrastructure.
7
7
 
8
8
  import { promises as fs } from "node:fs";
9
- import { randomUUID } from "node:crypto";
10
9
  import * as os from "node:os";
11
10
  import * as path from "node:path";
12
11
  import { spawn } from "node:child_process";
@@ -36,7 +35,7 @@ export async function prepareReleaseDir(
36
35
  }
37
36
  const bytes = Buffer.from(await res.arrayBuffer());
38
37
 
39
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), `karta-${slug}-v${meta.version}-`));
38
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), `dharta-${slug}-v${meta.version}-`));
40
39
  const tarball = path.join(dir, "release.tar.gz");
41
40
  await fs.writeFile(tarball, bytes);
42
41
  await untar(tarball, dir);
@@ -57,7 +56,6 @@ export async function fetchRemoteEnv(
57
56
  }>({
58
57
  method: "POST",
59
58
  path: `/api/agents/${encodeURIComponent(slug)}/env/pull`,
60
- headers: { "Idempotency-Key": randomUUID() },
61
59
  body: { include_secrets: !!opts.includeSecrets },
62
60
  });
63
61
 
@@ -0,0 +1,23 @@
1
+ import type { CliContext } from "../context.js";
2
+ import type { SourceRepoDisconnectResult } from "@dharta/sdk";
3
+ import { validateAgentSlug } from "../agent.js";
4
+
5
+ export async function reposDisconnectGithubData(
6
+ ctx: CliContext,
7
+ slug: string,
8
+ ): Promise<SourceRepoDisconnectResult> {
9
+ // A source repo carries the same slug shape an agent does; it is provisioned
10
+ // from one.
11
+ return ctx.sdk.sourceRepos.disconnectGithub(validateAgentSlug(slug, "source repo slug"));
12
+ }
13
+
14
+ export async function reposDisconnectGithub(ctx: CliContext, slug: string): Promise<string> {
15
+ const res = await reposDisconnectGithubData(ctx, slug);
16
+ if (!res.changed) {
17
+ return `GitHub auto-deploy is already disconnected for ${res.source_repo.slug}.`;
18
+ }
19
+ return [
20
+ `Disconnected GitHub auto-deploy for ${res.github_repository}.`,
21
+ "Current releases remain live; per-agent Actions can deploy independently.",
22
+ ].join(" ");
23
+ }
@@ -1,8 +1,8 @@
1
1
  // Re-deploy a previous release by pointer flip. (Forward deploys are
2
2
  // `dharta deploy` / `git push dharta`; this is the rollback-only path.)
3
3
  import type { CliContext } from "../context.js";
4
- import { randomUUID } from "node:crypto";
5
4
  import { validateAgentSlug } from "../agent.js";
5
+ import { CliUsageError } from "../exit-codes.js";
6
6
 
7
7
  export interface RollbackResponse {
8
8
  agent: { id: string; slug: string; name: string };
@@ -20,7 +20,6 @@ export async function rollbackData(
20
20
  return ctx.http.request<RollbackResponse>({
21
21
  method: "POST",
22
22
  path: `/api/agents/${encodeURIComponent(normalizedSlug)}/rollback`,
23
- headers: { "Idempotency-Key": randomUUID() },
24
23
  body: { to_version: normalizedVersion },
25
24
  });
26
25
  }
@@ -39,7 +38,7 @@ export async function rollback(
39
38
 
40
39
  function normalizeRollbackVersion(toVersion: number): number {
41
40
  if (!Number.isSafeInteger(toVersion) || toVersion <= 0) {
42
- throw new Error("version must be a positive whole number.");
41
+ throw new CliUsageError("version must be a positive whole number.");
43
42
  }
44
43
  return toVersion;
45
44
  }
@@ -1,9 +1,10 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { inferAgentSlug, type AgentInferOptions } from "../agent.js";
2
+ import { mintIdempotencyKey } from "../config.js";
3
3
  import type { CliContext } from "../context.js";
4
4
  import { parseDollarAmountToCents } from "../money.js";
5
5
  import { fmtTs, table } from "../output.js";
6
6
  import { normalizeNonBlank, normalizeTimezone, parsePositiveInteger, parseReleaseVersion } from "../validators.js";
7
+ import { CliUsageError } from "../exit-codes.js";
7
8
 
8
9
  export interface Schedule {
9
10
  id: number;
@@ -76,20 +77,26 @@ export async function schedulesCreateData(
76
77
  initial_message: normalizeRequired(message, "--message"),
77
78
  };
78
79
  if (opts.budget !== undefined && opts.budgetCents !== undefined) {
79
- throw new Error("Use either --budget or --budget-cents, not both.");
80
+ throw new CliUsageError("Use either --budget or --budget-cents, not both.");
80
81
  }
81
82
  if (opts.budget !== undefined) body.budget_cents = parseDollarAmountToCents(opts.budget, "--budget");
82
83
  if (opts.budgetCents !== undefined) body.budget_cents = parsePositiveWholeNumber(opts.budgetCents, "--budget-cents");
83
84
  if (opts.releaseVersion !== undefined) body.release_version = parseReleaseVersion(opts.releaseVersion, "--release-version");
84
85
  if (opts.endUserId !== undefined) body.end_user_id = normalizeRequired(opts.endUserId, "--end-user-id");
86
+ // `--run-now` makes this command write twice. Both writes derive a key from
87
+ // the caller's `--idempotency-key` rather than presenting it as given: the
88
+ // server binds a key to the request it first saw, so one key on both would
89
+ // answer the run with a 422 and leave a schedule behind that the failure
90
+ // message never mentions. Both derive whether or not `--run-now` was passed,
91
+ // so the key a write carries does not depend on a flag.
85
92
  const res = await ctx.http.request<ScheduleEnvelope>({
86
93
  method: "POST",
87
94
  path: `/api/agents/${encodeURIComponent(slug)}/schedules`,
88
- headers: idempotencyHeader(),
95
+ idempotencyKey: mintIdempotencyKey("schedules.create"),
89
96
  body: { schedule: body },
90
97
  });
91
98
  if (opts.runNow) {
92
- const run = await runScheduleNow(ctx, slug, String(res.schedule.id));
99
+ const run = await runScheduleNow(ctx, slug, String(res.schedule.id), mintIdempotencyKey("schedules.run"));
93
100
  return { slug, ...res, queued: run.queued ?? true };
94
101
  }
95
102
  return { slug, ...res };
@@ -106,7 +113,7 @@ export function validateSchedulesCreateInput(opts: ScheduleCreateOptions): void
106
113
  opts.message === undefined ? "--message <text>" : null,
107
114
  ].filter((flag): flag is string => flag !== null);
108
115
  if (missing.length > 0) {
109
- throw new Error(
116
+ throw new CliUsageError(
110
117
  `Pass ${formatMissingFlags(missing)}. ` +
111
118
  "Usage: dharta schedules create --cron '0 9 * * *' --message 'Daily check-in'",
112
119
  );
@@ -117,7 +124,7 @@ export function validateSchedulesCreateInput(opts: ScheduleCreateOptions): void
117
124
  if (opts.timezone !== undefined) normalizeTimezone(opts.timezone, "--timezone");
118
125
  normalizeRequired(message, "--message");
119
126
  if (opts.budget !== undefined && opts.budgetCents !== undefined) {
120
- throw new Error("Use either --budget or --budget-cents, not both.");
127
+ throw new CliUsageError("Use either --budget or --budget-cents, not both.");
121
128
  }
122
129
  if (opts.budget !== undefined) parseDollarAmountToCents(opts.budget, "--budget");
123
130
  if (opts.budgetCents !== undefined) parsePositiveWholeNumber(opts.budgetCents, "--budget-cents");
@@ -133,7 +140,7 @@ function validateCron(value: string): void {
133
140
  const cron = normalizeRequired(value, "--cron");
134
141
  const fields = cron.split(/\s+/);
135
142
  if (fields.length !== 5 || fields.some((field) => field.length === 0)) {
136
- throw new Error("--cron must be a standard 5-field cron expression, e.g. '0 9 * * *'.");
143
+ throw new CliUsageError("--cron must be a standard 5-field cron expression, e.g. '0 9 * * *'.");
137
144
  }
138
145
  fields.forEach((field, index) => validateCronField(field, CRON_FIELD_SPECS[index]));
139
146
  }
@@ -164,7 +171,7 @@ function validateCronField(field: string, spec: CronFieldSpec): void {
164
171
  const [base, step, extra] = item.split("/");
165
172
  if (extra !== undefined || !base) throw unsupportedCronField(spec);
166
173
  if (step !== undefined && !/^[1-9]\d*$/.test(step)) {
167
- throw new Error(`--cron ${spec.name} field step must be a positive whole number.`);
174
+ throw new CliUsageError(`--cron ${spec.name} field step must be a positive whole number.`);
168
175
  }
169
176
  if (base === "*") continue;
170
177
 
@@ -178,7 +185,7 @@ function validateCronField(field: string, spec: CronFieldSpec): void {
178
185
  const rangeStart = parseCronToken(start, spec);
179
186
  const rangeEnd = parseCronToken(end, spec);
180
187
  if (rangeStart > rangeEnd) {
181
- throw new Error(`--cron ${spec.name} field range must start before it ends.`);
188
+ throw new CliUsageError(`--cron ${spec.name} field range must start before it ends.`);
182
189
  }
183
190
  }
184
191
  }
@@ -190,13 +197,15 @@ function parseCronToken(token: string, spec: CronFieldSpec): number {
190
197
  if (!/^\d+$/.test(token)) throw unsupportedCronField(spec);
191
198
  const parsed = Number(token);
192
199
  if (parsed < spec.min || parsed > spec.max) {
193
- throw new Error(`--cron ${spec.name} field must be ${spec.min}-${spec.max}.`);
200
+ throw new CliUsageError(`--cron ${spec.name} field must be ${spec.min}-${spec.max}.`);
194
201
  }
195
202
  return parsed;
196
203
  }
197
204
 
198
205
  function unsupportedCronField(spec: CronFieldSpec): Error {
199
- return new Error(`--cron ${spec.name} field is not supported. Use numbers, *, ranges, lists, and steps.`);
206
+ return new CliUsageError(
207
+ `--cron ${spec.name} field is not supported. Use numbers, *, ranges, lists, and steps.`,
208
+ );
200
209
  }
201
210
 
202
211
  export async function schedulesActionData(
@@ -212,7 +221,6 @@ export async function schedulesActionData(
212
221
  : await ctx.http.request<ScheduleEnvelope>({
213
222
  method: "POST",
214
223
  path: `/api/agents/${encodeURIComponent(slug)}/schedules/${encodeURIComponent(normalizedId)}/${action}`,
215
- headers: idempotencyHeader(),
216
224
  });
217
225
  return { slug, ...res };
218
226
  }
@@ -238,7 +246,6 @@ export async function schedulesDeleteData(
238
246
  await ctx.http.request<void>({
239
247
  method: "DELETE",
240
248
  path: `/api/agents/${encodeURIComponent(slug)}/schedules/${encodeURIComponent(normalizedId)}`,
241
- headers: idempotencyHeader(),
242
249
  });
243
250
  return { slug, id: normalizedId, deleted: true };
244
251
  }
@@ -250,10 +257,10 @@ export async function schedulesDelete(ctx: CliContext, id: string, opts: AgentIn
250
257
 
251
258
  function parsePositiveWholeNumber(value: string, flag: string): number {
252
259
  const normalized = value.trim();
253
- if (!/^\d+$/.test(normalized)) throw new Error(`${flag} must be a whole number.`);
260
+ if (!/^\d+$/.test(normalized)) throw new CliUsageError(`${flag} must be a whole number.`);
254
261
  const parsed = Number(normalized);
255
262
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {
256
- throw new Error(`${flag} must be a positive whole number.`);
263
+ throw new CliUsageError(`${flag} must be a positive whole number.`);
257
264
  }
258
265
  return parsed;
259
266
  }
@@ -263,22 +270,26 @@ function formatBudget(cents: number | null): string {
263
270
  return `$${(cents / 100).toFixed(2)}`;
264
271
  }
265
272
 
266
- function idempotencyHeader(): Record<string, string> {
267
- return { "Idempotency-Key": randomUUID() };
268
- }
269
-
270
- function runScheduleNow(ctx: CliContext, slug: string, id: string): Promise<ScheduleEnvelope> {
273
+ // `idempotencyKey` is set only by the caller that writes more than once.
274
+ // `dharta schedules run` writes once, so it leaves this undefined and the
275
+ // client sends the caller's key exactly as given.
276
+ function runScheduleNow(
277
+ ctx: CliContext,
278
+ slug: string,
279
+ id: string,
280
+ idempotencyKey?: string,
281
+ ): Promise<ScheduleEnvelope> {
271
282
  return ctx.http.request<ScheduleEnvelope>({
272
283
  method: "POST",
273
284
  path: `/api/agents/${encodeURIComponent(slug)}/schedules/${encodeURIComponent(id)}/run`,
274
- headers: idempotencyHeader(),
285
+ idempotencyKey,
275
286
  });
276
287
  }
277
288
 
278
289
  function normalizeRequired(value: string, flag: string): string {
279
290
  const normalized = value.trim();
280
291
  if (!normalized) {
281
- throw new Error(`${flag} cannot be blank.`);
292
+ throw new CliUsageError(`${flag} cannot be blank.`);
282
293
  }
283
294
  return normalized;
284
295
  }
@@ -4,12 +4,19 @@ export const SENSITIVE_API_KEY_SCOPES = new Set([
4
4
  "workspace_tokens:create",
5
5
  "workspace:read",
6
6
  "audit_log:export",
7
+ "fleet:subjects:read",
8
+ "fleet:subjects:write",
9
+ "fleet:operations:write",
7
10
  "members:invite",
8
11
  "members:write",
9
12
  "members:owner_role:write",
10
13
  "agents:delete",
11
14
  "keys:write",
12
- "model_keys:write",
15
+ "model_connections:write",
16
+ "organization:support_access:write",
17
+ // A webhook destination is an egress trust root: any URL is accepted, and an
18
+ // endpoint with no event list receives every event type.
19
+ "webhooks:write",
13
20
  ]);
14
21
 
15
22
  export function formatScopes(scopes: readonly string[], separator = ", "): string {
@@ -6,13 +6,13 @@
6
6
  // org's transcript-access setting.
7
7
 
8
8
  import { HttpClient } from "@dharta/sdk";
9
- import { randomUUID } from "node:crypto";
10
9
  import type { CliContext } from "../context.js";
11
10
  import { cliFormatError, cliFormatNetworkError } from "../api-error.js";
12
11
  import { inferAgentSlug, type AgentInferOptions } from "../agent.js";
13
12
  import { runTurn, type TurnIo } from "./dev.js";
14
13
  import { table } from "../output.js";
15
14
  import { normalizeNonBlank, parsePositiveInteger } from "../validators.js";
15
+ import { mintIdempotencyKey } from "../config.js";
16
16
 
17
17
  export interface SessionsOptions extends AgentInferOptions {
18
18
  fetchImpl?: typeof fetch;
@@ -50,12 +50,16 @@ async function agentApi(
50
50
  return {
51
51
  slug,
52
52
  agentUrl: shown.agent_url,
53
+ // The run-wide key rides along here exactly as it does on the control-plane
54
+ // client. It fills in for a write that names no key of its own; `send`
55
+ // names one per write, so both of its mutations keep theirs.
53
56
  http: new HttpClient({
54
57
  apiKey: ctx.apiKey,
55
58
  baseUrl: shown.agent_url,
56
59
  fetch: opts.fetchImpl,
57
60
  formatError: cliFormatError,
58
61
  formatNetworkError: cliFormatNetworkError,
62
+ idempotencyKey: ctx.idempotencyKey,
59
63
  }),
60
64
  };
61
65
  }
@@ -189,7 +193,7 @@ export async function run(
189
193
  method: "POST",
190
194
  path: "/v1/sessions",
191
195
  body: { metadata: { source: "dharta-run" } },
192
- headers: { "Idempotency-Key": randomUUID() },
196
+ idempotencyKey: mintIdempotencyKey("sessions.create"),
193
197
  });
194
198
  sessionId = session.id;
195
199
  }
@@ -212,7 +216,9 @@ export async function run(
212
216
  return "n";
213
217
  },
214
218
  };
215
- await runTurn(agentUrl, authedFetch, sessionId, normalizedMessage, io);
219
+ await runTurn(agentUrl, authedFetch, sessionId, normalizedMessage, io, {
220
+ operation: "sessions.turn",
221
+ });
216
222
  // The renderer appends one trailing newline to close the streamed line;
217
223
  // drop it from the returned/JSON payload so the reply is exactly the text.
218
224
  const replyText = reply.endsWith("\n") ? reply.slice(0, -1) : reply;
@@ -7,6 +7,7 @@ import { deriveSlug, validateAgentName } from "../agent.js";
7
7
  import { defaultGitRunner, rejectOverlappingAgentRoots, type GitRunner } from "./deploy.js";
8
8
  import { closestChoice, normalizeNonBlank } from "../validators.js";
9
9
  import { DEFAULT_CREATE_HARNESS, hasAgentMarker } from "../harnesses.js";
10
+ import { CliUsageError } from "../exit-codes.js";
10
11
 
11
12
  // `dharta setup [path]` (RFC 0024 DEP-4): the one front door. It scans the
12
13
  // repo for agent folders, lets you pick one, and writes its (self-
@@ -65,7 +66,7 @@ export async function setup(pathArg: string | undefined, opts: SetupOptions = {}
65
66
  const parsedMethod = parseMethod(opts.method); // validate configuration up front
66
67
  const forcedMethod = parsedMethod.method;
67
68
  const ci = parseCi(opts.ci) ?? parsedMethod.ci;
68
- if (opts.enable && opts.disable) throw new Error("Use either --enable or --disable, not both.");
69
+ if (opts.enable && opts.disable) throw new CliUsageError("Use either --enable or --disable, not both.");
69
70
  if (opts.name !== undefined) validateAgentName(opts.name, "--name");
70
71
  // Resolve and validate an explicit target before setup can initialize git or
71
72
  // write anything. Besides avoiding a stray .git on typos, this establishes a
@@ -380,7 +381,7 @@ function parseMethod(raw: string | undefined): ParsedMethodInput {
380
381
  }
381
382
  if (!METHODS.includes(normalized as Method)) {
382
383
  const suggestion = closestChoice(normalized, METHOD_INPUTS);
383
- throw new Error(
384
+ throw new CliUsageError(
384
385
  `Unknown --method '${normalized}'. Use git or folder.` +
385
386
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
386
387
  );
@@ -393,7 +394,7 @@ function parseCi(raw: string | undefined): CiProvider | undefined {
393
394
  const normalized = normalizeNonBlank(raw, "--ci");
394
395
  if (!CI_PROVIDERS.includes(normalized as CiProvider)) {
395
396
  const suggestion = closestChoice(normalized, CI_PROVIDERS);
396
- throw new Error(
397
+ throw new CliUsageError(
397
398
  `Unknown --ci '${normalized}'. Use github.` +
398
399
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
399
400
  );
@@ -939,7 +940,7 @@ async function safeAtomicWriteFile(
939
940
  const parent = path.dirname(destination);
940
941
  const temporary = path.join(
941
942
  parent,
942
- `.${path.basename(destination)}.karta-${process.pid}-${randomBytes(12).toString("hex")}.tmp`,
943
+ `.${path.basename(destination)}.dharta-${process.pid}-${randomBytes(12).toString("hex")}.tmp`,
943
944
  );
944
945
  const noFollow = constants.O_NOFOLLOW ?? 0;
945
946
  const flags = constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | noFollow;
@@ -16,6 +16,11 @@ import { checkNpmLatestVersion } from "../version-check.js";
16
16
  export const DOWNLOAD_BASE =
17
17
  process.env.DHARTA_CLI_DOWNLOAD_BASE ?? "https://cli.dharta.ai";
18
18
 
19
+ // The marker packaging/build-standalone.sh writes into the tarball root. The
20
+ // build derives it from package.json's `bin`; this is the one place the CLI
21
+ // side states it, and packaging-parity.test.ts pins the two together.
22
+ export const STANDALONE_MARKER = ".dharta-standalone";
23
+
19
24
  export interface UpdateOptions {
20
25
  installRoot?: string; // test seam (default: the package root)
21
26
  currentVersion?: string;
@@ -34,7 +39,7 @@ export function packageRoot(): string {
34
39
 
35
40
  export async function installChannel(root: string): Promise<"standalone" | "npm"> {
36
41
  try {
37
- await fs.access(path.join(root, ".dharta-standalone"));
42
+ await fs.access(path.join(root, STANDALONE_MARKER));
38
43
  return "standalone";
39
44
  } catch {
40
45
  return "npm";
@@ -42,7 +47,7 @@ export async function installChannel(root: string): Promise<"standalone" | "npm"
42
47
  }
43
48
 
44
49
  export function tarballName(platform: string, arch: string): string {
45
- return `karta-${platform}-${arch}.tar.gz`;
50
+ return `dharta-${platform}-${arch}.tar.gz`;
46
51
  }
47
52
 
48
53
  export async function update(opts: UpdateOptions = {}): Promise<string> {
@@ -56,7 +61,7 @@ export async function update(opts: UpdateOptions = {}): Promise<string> {
56
61
  fetchImpl: opts.fetchImpl,
57
62
  latestUrl: opts.latestUrl,
58
63
  });
59
- if (version.status === "current") return `karta is already current (${version.current}).`;
64
+ if (version.status === "current") return `dharta is already current (${version.current}).`;
60
65
  if (version.status === "ahead") {
61
66
  return `dharta ${version.current} is newer than the latest published npm version (${version.latest}).`;
62
67
  }
@@ -65,7 +70,7 @@ export async function update(opts: UpdateOptions = {}): Promise<string> {
65
70
  ? `\nInstalled: ${version.current}\nLatest: ${version.latest}`
66
71
  : `\nCould not check the latest npm version (${version.reason}).`;
67
72
  return (
68
- "This karta was installed via npm - update it with:\n" +
73
+ "This dharta was installed via npm - update it with:\n" +
69
74
  " npm install -g @dharta/cli@latest" +
70
75
  suffix
71
76
  );
@@ -106,18 +111,29 @@ export async function update(opts: UpdateOptions = {}): Promise<string> {
106
111
 
107
112
  const staging = await fs.mkdtemp(path.join(os.tmpdir(), "dharta-update-"));
108
113
  const tarball = path.join(staging, "update.tar.gz");
114
+ const unpacked = path.join(staging, "unpacked");
115
+ await fs.mkdir(unpacked);
109
116
  await fs.writeFile(tarball, bytes);
110
- await untar(tarball, staging);
111
-
112
- // The tarball contains a single karta/ directory; its contents
113
- // replace the install root. Extract fully BEFORE touching the live
114
- // install so a failed download never leaves a half-updated CLI.
115
- const extracted = path.join(staging, "dharta");
116
- await fs.access(path.join(extracted, ".dharta-standalone"));
117
+ await untar(tarball, unpacked);
118
+
119
+ // The tarball holds exactly one directory, named for the command it installs.
120
+ // Read that name off the payload rather than assuming it: assuming it is what
121
+ // broke this path through the rename, when the build started writing a
122
+ // directory this code was not looking for. Extract fully BEFORE touching the
123
+ // live install so a failed download never leaves a half-updated CLI.
124
+ const [entry, ...extra] = await fs.readdir(unpacked);
125
+ if (!entry || extra.length > 0) {
126
+ throw new Error(
127
+ "The downloaded update did not contain a single CLI directory. " +
128
+ "Nothing was changed; retry, or reinstall with the install script.",
129
+ );
130
+ }
131
+ const extracted = path.join(unpacked, entry);
132
+ await fs.access(path.join(extracted, STANDALONE_MARKER));
117
133
 
118
134
  await replaceContents(extracted, root);
119
135
  await fs.rm(staging, { recursive: true, force: true });
120
- return "Updated. Run `karta --version` to confirm.";
136
+ return "Updated. Run `dharta --version` to confirm.";
121
137
  }
122
138
 
123
139
  async function fetchUpdate(url: string, fetchImpl: typeof fetch): Promise<Response> {