@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,59 @@
1
+ // `dharta genui` (RFC 0056): build and check the agent's generative-UI
2
+ // component catalog. Both subcommands are local-only - no auth, no network.
3
+ // The validation lives in ../genui.ts, shared with `dharta dev`'s doctor and
4
+ // the deploy pre-flight so all three report the same problem in the same words.
5
+
6
+ import * as path from "node:path";
7
+ import type { Command } from "commander";
8
+ import { buildGenui, formatGenuiStatus, genuiCheckData } from "../genui.js";
9
+ import { isJson, printJson } from "../output.js";
10
+ import { normalizeNonBlank } from "../validators.js";
11
+
12
+ function resolveDir(dirArg?: string): string {
13
+ return path.resolve(dirArg === undefined ? process.cwd() : normalizeNonBlank(dirArg, "path"));
14
+ }
15
+
16
+ // The full layout is in the CLI's README, under "Generative UI". Repeating it
17
+ // here would give it a second place to drift.
18
+ const SOURCE_LAYOUT = [
19
+ "",
20
+ "Sources live one directory per component under `source` in [genui.build]",
21
+ "(default genui/components), named exactly as the [genui] components array",
22
+ "names them: component.json, plus worker.js for tier 2 or index.html for",
23
+ "tier 3. The build refuses when the two disagree and never edits the array -",
24
+ "a component's tier is your trust declaration, not a value it can derive.",
25
+ "",
26
+ "See the `Generative UI` section of the @dharta/cli README for the full",
27
+ "layout and the component.json keys.",
28
+ ].join("\n");
29
+
30
+ export function registerGenuiCommands(program: Command): void {
31
+ const genui = program
32
+ .command("genui")
33
+ .description("Build and check the agent's generative-UI component catalog.")
34
+ .action(() => genui.help());
35
+
36
+ genui
37
+ .command("build")
38
+ .argument("[dir]", "Agent directory (default: current directory)")
39
+ .description("Compile `[genui]` source into the bundle and record its digest in dharta.toml.")
40
+ .addHelpText("after", SOURCE_LAYOUT)
41
+ .action(async (dirArg: string | undefined, _opts: unknown, cmd: Command) => {
42
+ const json = isJson(cmd);
43
+ const result = await buildGenui(
44
+ resolveDir(dirArg),
45
+ json ? () => {} : (line) => console.log(line),
46
+ );
47
+ if (json) printJson(result);
48
+ });
49
+
50
+ genui
51
+ .command("check")
52
+ .argument("[dir]", "Agent directory (default: current directory)")
53
+ .description("Verify the declared bundle exists, matches its digest, and is valid.")
54
+ .action(async (dirArg: string | undefined, _opts: unknown, cmd: Command) => {
55
+ const status = await genuiCheckData(resolveDir(dirArg));
56
+ if (isJson(cmd)) printJson(status);
57
+ else console.log(formatGenuiStatus(status));
58
+ });
59
+ }
@@ -1,6 +1,5 @@
1
1
  import { HttpClient } from "@dharta/sdk";
2
- import { randomUUID } from "node:crypto";
3
- import { activeProfile, type Profile } from "../config.js";
2
+ import { activeProfile, mintIdempotencyKey, type Profile } from "../config.js";
4
3
 
5
4
  // `dharta git-credential <operation>` - a git credential helper in the protocol sense,
6
5
  // wired by `dharta login` via `credential.<git host>.helper = !dharta git-credential`.
@@ -68,7 +67,7 @@ async function mintWorkspaceToken(
68
67
  const res = await http.request<{ token?: string }>({
69
68
  method: "POST",
70
69
  path: `/api/agents/${encodeURIComponent(agent)}/workspace_tokens`,
71
- headers: { "Idempotency-Key": randomUUID() },
70
+ idempotencyKey: mintIdempotencyKey("workspace-token"),
72
71
  body: { instance },
73
72
  });
74
73
  return res.token ?? null;
@@ -1,8 +1,9 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import type { CliContext } from "../context.js";
2
+ import type { KartaShareCreateResponse } from "@dharta/sdk";
3
3
  import { table } from "../output.js";
4
4
  import { maskEnvValue } from "./env.js";
5
5
  import { closestChoice, normalizeNonBlank } from "../validators.js";
6
+ import { CliUsageError } from "../exit-codes.js";
6
7
 
7
8
  const DHARTA_AUDIENCES = ["members", "anonymous", "off"] as const;
8
9
  const DHARTA_ROLES = ["viewer", "participant", "manager"] as const;
@@ -75,7 +76,6 @@ export async function kartasCreateData(
75
76
  return ctx.http.request<{ karta: KartaRecord }>({
76
77
  method: "POST",
77
78
  path: "/api/kartas",
78
- headers: { "Idempotency-Key": randomUUID() },
79
79
  body: {
80
80
  agent,
81
81
  name: name.trim(),
@@ -147,7 +147,7 @@ export async function kartasShow(ctx: CliContext, id: string): Promise<string> {
147
147
 
148
148
  export function validateKartaConfigureOptions(options: KartaConfigureOptions): void {
149
149
  if (options.name === undefined && options.slug === undefined && options.access === undefined) {
150
- throw new Error("Set at least one of --name, --slug, or --access.");
150
+ throw new CliUsageError("Set at least one of --name, --slug, or --access.");
151
151
  }
152
152
  if (options.name !== undefined) normalizeNonBlank(options.name, "dharta name");
153
153
  if (options.slug !== undefined) normalizeNonBlank(options.slug, "dharta vanity slug");
@@ -190,7 +190,6 @@ export async function kartasConfigureData(
190
190
  return ctx.http.request<{ karta: KartaRecord }>({
191
191
  method: "PATCH",
192
192
  path: `/api/kartas/${encodeURIComponent(kartaId)}`,
193
- headers: { "Idempotency-Key": randomUUID() },
194
193
  body,
195
194
  });
196
195
  }
@@ -214,7 +213,6 @@ export async function kartasArchiveData(
214
213
  return ctx.http.request<{ karta: KartaRecord }>({
215
214
  method: "DELETE",
216
215
  path: `/api/kartas/${encodeURIComponent(kartaId)}`,
217
- headers: { "Idempotency-Key": randomUUID() },
218
216
  });
219
217
  }
220
218
 
@@ -234,7 +232,6 @@ export async function kartasRestoreData(
234
232
  return ctx.http.request<{ karta: KartaRecord }>({
235
233
  method: "POST",
236
234
  path: `/api/kartas/${encodeURIComponent(kartaId)}/restore`,
237
- headers: { "Idempotency-Key": randomUUID() },
238
235
  });
239
236
  }
240
237
 
@@ -245,6 +242,46 @@ export async function kartasRestore(ctx: CliContext, id: string): Promise<string
245
242
  : `Restored ${karta.name ?? karta.id}.`;
246
243
  }
247
244
 
245
+ // A no-sign-in link to one karta. Anyone holding it gets their own session
246
+ // against the SAME durable workspace and spends from its budget, so the secret
247
+ // and the link are shown once and are never readable again. Two kartas cannot
248
+ // take one: a members-only karta has a member list instead, and a karta no
249
+ // agent has run yet has no host to publish under.
250
+ export async function kartaShareData(
251
+ ctx: CliContext,
252
+ id: string,
253
+ ): Promise<KartaShareCreateResponse> {
254
+ return ctx.sdk.kartas.createShare(normalizeNonBlank(id, "karta id"));
255
+ }
256
+
257
+ export async function kartaShare(ctx: CliContext, id: string): Promise<string> {
258
+ const res = await kartaShareData(ctx, id);
259
+ return [
260
+ `Shared karta ${res.share.agent_instance_id} as ${res.share.prefix}.`,
261
+ "",
262
+ "Link (shown once - store it now):",
263
+ res.url,
264
+ "",
265
+ `Revoke it with: dharta kartas unshare ${res.share.agent_instance_id} ${res.share.id}`,
266
+ ].join("\n");
267
+ }
268
+
269
+ export async function kartaUnshareData(
270
+ ctx: CliContext,
271
+ id: string,
272
+ shareId: string,
273
+ ): Promise<{ id: string; share_id: string; revoked: true }> {
274
+ const kartaId = normalizeNonBlank(id, "karta id");
275
+ const share = normalizeNonBlank(shareId, "share id");
276
+ await ctx.sdk.kartas.revokeShare(kartaId, share);
277
+ return { id: kartaId, share_id: share, revoked: true };
278
+ }
279
+
280
+ export async function kartaUnshare(ctx: CliContext, id: string, shareId: string): Promise<string> {
281
+ const res = await kartaUnshareData(ctx, id, shareId);
282
+ return `Revoked share ${res.share_id} on karta ${res.id}. The link stops working immediately.`;
283
+ }
284
+
248
285
  export async function kartaMembersGrantData(
249
286
  ctx: CliContext,
250
287
  id: string,
@@ -257,7 +294,6 @@ export async function kartaMembersGrantData(
257
294
  return ctx.http.request<{ member: KartaMember }>({
258
295
  method: "POST",
259
296
  path: `/api/kartas/${encodeURIComponent(kartaId)}/members`,
260
- headers: { "Idempotency-Key": randomUUID() },
261
297
  body: { email: normalizedEmail, role: normalizedRole },
262
298
  });
263
299
  }
@@ -279,7 +315,6 @@ export async function kartaMembersSetRoleData(
279
315
  return ctx.http.request<{ member: KartaMember }>({
280
316
  method: "PATCH",
281
317
  path: `/api/kartas/${encodeURIComponent(kartaId)}/members/${encodeURIComponent(memberId)}`,
282
- headers: { "Idempotency-Key": randomUUID() },
283
318
  body: { role: normalizedRole },
284
319
  });
285
320
  }
@@ -304,7 +339,6 @@ export async function kartaMembersRevokeData(
304
339
  await ctx.http.request<void>({
305
340
  method: "DELETE",
306
341
  path: `/api/kartas/${encodeURIComponent(kartaId)}/members/${encodeURIComponent(memberId)}`,
307
- headers: { "Idempotency-Key": randomUUID() },
308
342
  });
309
343
  return { id: kartaId, membership_id: memberId, revoked: true };
310
344
  }
@@ -318,7 +352,7 @@ function normalizeAudience(audience: string): KartaAudience {
318
352
  const normalized = normalizeNonBlank(audience, "karta access audience");
319
353
  if ((DHARTA_AUDIENCES as readonly string[]).includes(normalized)) return normalized as KartaAudience;
320
354
  const suggestion = closestChoice(normalized, DHARTA_AUDIENCES);
321
- throw new Error(
355
+ throw new CliUsageError(
322
356
  `Unknown karta access ${normalized}. Allowed values: ${DHARTA_AUDIENCES.join(", ")}.` +
323
357
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
324
358
  );
@@ -328,7 +362,7 @@ function normalizeRole(role: string): KartaRole {
328
362
  const normalized = normalizeNonBlank(role, "karta member role");
329
363
  if ((DHARTA_ROLES as readonly string[]).includes(normalized)) return normalized as KartaRole;
330
364
  const suggestion = closestChoice(normalized, DHARTA_ROLES);
331
- throw new Error(
365
+ throw new CliUsageError(
332
366
  `Unknown karta member role ${normalized}. Allowed roles: ${DHARTA_ROLES.join(", ")}.` +
333
367
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
334
368
  );
@@ -1,31 +1,28 @@
1
1
  import type { CliContext } from "../context.js";
2
- import { API_KEY_SCOPES, type ApiKeyScope } from "@dharta/sdk";
3
- import { randomUUID } from "node:crypto";
2
+ import {
3
+ API_KEY_BULK_ACTIONS,
4
+ API_KEY_ROTATION_GRACE_PERIODS,
5
+ API_KEY_SCOPES,
6
+ type ApiKey,
7
+ type ApiKeyBulkAction,
8
+ type ApiKeyBulkUpdateResponse,
9
+ type ApiKeyRotateResponse,
10
+ type ApiKeyRotationGracePeriod,
11
+ type ApiKeyScope,
12
+ type ApiKeyStateResponse,
13
+ } from "@dharta/sdk";
4
14
  import { fmtTs, table } from "../output.js";
5
- import { closestChoice, normalizeIsoTimestamp, normalizeNonBlank } from "../validators.js";
15
+ import { closestChoice, normalizeIsoTimestamp, normalizeNonBlank, parsePositiveInteger } from "../validators.js";
6
16
  import { formatScopes, SENSITIVE_API_KEY_SCOPES } from "./scopes.js";
17
+ import { CliUsageError } from "../exit-codes.js";
18
+
19
+ export const KEY_BULK_ACTIONS = API_KEY_BULK_ACTIONS;
20
+ export const KEY_ROTATION_GRACE_PERIODS = API_KEY_ROTATION_GRACE_PERIODS;
7
21
 
8
22
  interface ListEnvelope<T> {
9
23
  data: T[];
10
24
  }
11
25
 
12
- interface ApiKey {
13
- id: string;
14
- name: string;
15
- prefix: string;
16
- scopes: ApiKeyScope[];
17
- mode?: string;
18
- expires_at?: string | null;
19
- created_at: string;
20
- last_used_at: string | null;
21
- revoked_at: string | null;
22
- }
23
-
24
- interface ApiKeyCreateResponse {
25
- api_key: ApiKey;
26
- secret: string;
27
- }
28
-
29
26
  export interface ApiKeyCreateOptions {
30
27
  expiresAt?: string;
31
28
  }
@@ -45,6 +42,7 @@ export async function keysList(ctx: CliContext): Promise<string> {
45
42
  if (keys.length === 0) return "No API keys. Create one with: dharta keys create <name> --scope agents:read";
46
43
  return table(
47
44
  keys.map((k) => ({
45
+ id: String(k.id),
48
46
  prefix: k.prefix,
49
47
  name: k.name,
50
48
  scopes: formatScopes(k.scopes, ","),
@@ -64,13 +62,11 @@ export async function keysCreateData(
64
62
  validateKeysCreateInput(name, scopes, opts);
65
63
  const normalizedScopes = normalizeScopes(scopes);
66
64
  const normalizedOpts = normalizeCreateOptions(normalizedScopes, opts);
67
- const body: Record<string, unknown> = { name: normalizeKeyName(name), scopes: normalizedScopes };
68
- if (normalizedOpts.expiresAt !== undefined) body.expires_at = normalizedOpts.expiresAt;
69
- return ctx.http.request<ApiKeyCreateResponse>({
70
- method: "POST",
71
- path: "/api_keys",
72
- body,
73
- headers: { "Idempotency-Key": randomUUID() },
65
+ // Through the SDK resource, not a hand-built request: every new endpoint
66
+ // ships an @dharta/sdk method and the curated command calls it (RFC 0048
67
+ // D2), so the wire shape and the idempotency header have one owner.
68
+ return ctx.sdk.apiKeys.create(normalizeKeyName(name), normalizedScopes, {
69
+ expiresAt: normalizedOpts.expiresAt,
74
70
  });
75
71
  }
76
72
 
@@ -108,7 +104,6 @@ export async function keysRevokeData(
108
104
  await ctx.http.request<void>({
109
105
  method: "DELETE",
110
106
  path: `/api_keys/${encodeURIComponent(normalizedId)}`,
111
- headers: { "Idempotency-Key": randomUUID() },
112
107
  });
113
108
  return { id: normalizedId, revoked: true };
114
109
  }
@@ -129,7 +124,6 @@ export async function keysRenameData(
129
124
  method: "PATCH",
130
125
  path: `/api_keys/${encodeURIComponent(normalizedId)}`,
131
126
  body: { name: normalizedName },
132
- headers: { "Idempotency-Key": randomUUID() },
133
127
  });
134
128
  return res.api_key;
135
129
  }
@@ -139,24 +133,147 @@ export async function keysRename(ctx: CliContext, id: string, name: string): Pro
139
133
  return `Renamed ${key.prefix} to ${key.name}.`;
140
134
  }
141
135
 
136
+ export interface KeysRotateOptions {
137
+ gracePeriod?: string;
138
+ }
139
+
140
+ export function validateKeysRotateInput(id: string, opts: KeysRotateOptions = {}): void {
141
+ normalizeNonBlank(id, "API key id");
142
+ if (opts.gracePeriod !== undefined) normalizeGracePeriod(opts.gracePeriod);
143
+ }
144
+
145
+ export async function keysRotateData(
146
+ ctx: CliContext,
147
+ id: string,
148
+ opts: KeysRotateOptions = {},
149
+ ): Promise<ApiKeyRotateResponse> {
150
+ const normalizedId = normalizeNonBlank(id, "API key id");
151
+ const gracePeriod = opts.gracePeriod === undefined ? undefined : normalizeGracePeriod(opts.gracePeriod);
152
+ return ctx.sdk.apiKeys.rotate(normalizedId, { gracePeriod });
153
+ }
154
+
155
+ export async function keysRotate(
156
+ ctx: CliContext,
157
+ id: string,
158
+ opts: KeysRotateOptions = {},
159
+ ): Promise<string> {
160
+ const res = await keysRotateData(ctx, id, opts);
161
+ return [
162
+ `Rotated ${res.previous_key.prefix} into ${res.api_key.prefix} (${res.api_key.id}).`,
163
+ `Scopes: ${res.api_key.scopes.join(",")}`,
164
+ `The rotated key stops working at ${res.grace_ends_at}.`,
165
+ "",
166
+ "Secret (shown once - store it now):",
167
+ res.secret,
168
+ ].join("\n");
169
+ }
170
+
171
+ export async function keysDisableData(ctx: CliContext, id: string): Promise<ApiKeyStateResponse> {
172
+ return ctx.sdk.apiKeys.disable(normalizeNonBlank(id, "API key id"));
173
+ }
174
+
175
+ export async function keysDisable(ctx: CliContext, id: string): Promise<string> {
176
+ const res = await keysDisableData(ctx, id);
177
+ return res.changed
178
+ ? `Disabled ${res.api_key.prefix}. Re-enable it with: dharta keys enable ${res.api_key.id}`
179
+ : `${res.api_key.prefix} was already disabled.`;
180
+ }
181
+
182
+ export async function keysEnableData(ctx: CliContext, id: string): Promise<ApiKeyStateResponse> {
183
+ return ctx.sdk.apiKeys.enable(normalizeNonBlank(id, "API key id"));
184
+ }
185
+
186
+ export async function keysEnable(ctx: CliContext, id: string): Promise<string> {
187
+ const res = await keysEnableData(ctx, id);
188
+ return res.changed
189
+ ? `Enabled ${res.api_key.prefix}.`
190
+ : `${res.api_key.prefix} was already enabled.`;
191
+ }
192
+
193
+ export function validateKeysBulkInput(action: string, ids: string[]): void {
194
+ normalizeBulkAction(action);
195
+ normalizeBulkIds(ids);
196
+ }
197
+
198
+ export async function keysBulkData(
199
+ ctx: CliContext,
200
+ action: string,
201
+ ids: string[],
202
+ ): Promise<ApiKeyBulkUpdateResponse> {
203
+ return ctx.sdk.apiKeys.bulkUpdate(normalizeBulkAction(action), normalizeBulkIds(ids));
204
+ }
205
+
206
+ export async function keysBulk(ctx: CliContext, action: string, ids: string[]): Promise<string> {
207
+ const res = await keysBulkData(ctx, action, ids);
208
+ const lines = [
209
+ `${BULK_PAST_TENSE[res.action]} ${res.updated.length} of ${res.updated.length + res.skipped.length} keys.`,
210
+ ];
211
+ if (res.updated.length > 0) lines.push(`Changed: ${res.updated.map((key) => key.prefix).join(", ")}`);
212
+ // A skip is the answer, not a failure: the action did not apply to that key.
213
+ if (res.skipped.length > 0) {
214
+ lines.push(`Already in that state: ${res.skipped.map((key) => key.prefix).join(", ")}`);
215
+ }
216
+ return lines.join("\n");
217
+ }
218
+
219
+ const BULK_PAST_TENSE: Record<ApiKeyBulkAction, string> = {
220
+ disable: "Disabled",
221
+ enable: "Enabled",
222
+ revoke: "Revoked",
223
+ };
224
+
225
+ function normalizeGracePeriod(value: string): ApiKeyRotationGracePeriod {
226
+ const normalized = normalizeNonBlank(value, "--grace-period");
227
+ if ((KEY_ROTATION_GRACE_PERIODS as readonly string[]).includes(normalized)) {
228
+ return normalized as ApiKeyRotationGracePeriod;
229
+ }
230
+ const suggestion = closestChoice(normalized, KEY_ROTATION_GRACE_PERIODS);
231
+ throw new CliUsageError(
232
+ `Unknown --grace-period ${normalized}. Allowed windows: ${KEY_ROTATION_GRACE_PERIODS.join(", ")}.` +
233
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
234
+ );
235
+ }
236
+
237
+ function normalizeBulkAction(value: string): ApiKeyBulkAction {
238
+ const normalized = normalizeNonBlank(value, "Bulk action");
239
+ if ((KEY_BULK_ACTIONS as readonly string[]).includes(normalized)) {
240
+ return normalized as ApiKeyBulkAction;
241
+ }
242
+ const suggestion = closestChoice(normalized, KEY_BULK_ACTIONS);
243
+ throw new CliUsageError(
244
+ `Unknown bulk action ${normalized}. Allowed actions: ${KEY_BULK_ACTIONS.join(", ")}.` +
245
+ (suggestion ? ` Did you mean ${suggestion}?` : ""),
246
+ );
247
+ }
248
+
249
+ // Ids are the numeric primary key the API answers, so a typo is caught here
250
+ // rather than becoming a silently ignored id in the batch.
251
+ function normalizeBulkIds(ids: string[]): number[] {
252
+ const normalized = ids.map((id) => parsePositiveInteger(id, "API key id"));
253
+ if (normalized.length === 0) {
254
+ throw new CliUsageError("Name at least one API key id, e.g. dharta keys bulk disable 12 13.");
255
+ }
256
+ return [...new Set(normalized)];
257
+ }
258
+
142
259
  function normalizeKeyName(name: string): string {
143
260
  const normalized = name.trim();
144
261
  if (!normalized) {
145
- throw new Error("API key name cannot be blank.");
262
+ throw new CliUsageError("API key name cannot be blank.");
146
263
  }
147
264
  return normalized;
148
265
  }
149
266
 
150
267
  function normalizeScopes(scopes: ApiKeyScope[]): ApiKeyScope[] {
151
268
  if (scopes.length === 0) {
152
- throw new Error("Pass at least one --scope, e.g. --scope agents:read.");
269
+ throw new CliUsageError("Pass at least one --scope, e.g. --scope agents:read.");
153
270
  }
154
271
  const allowed = new Set<string>(API_KEY_SCOPES);
155
272
  const normalized = scopes.map((scope) => normalizeNonBlank(scope, "API key scope") as ApiKeyScope);
156
273
  const unknown = normalized.filter((scope) => !allowed.has(scope));
157
274
  if (unknown.length > 0) {
158
275
  const suggestion = unknown.length === 1 ? closestChoice(unknown[0]!, API_KEY_SCOPES) : null;
159
- throw new Error(
276
+ throw new CliUsageError(
160
277
  `Unknown --scope ${unknown.join(", ")}. Allowed scopes: ${API_KEY_SCOPES.join(", ")}.` +
161
278
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
162
279
  );
@@ -169,7 +286,7 @@ function normalizeCreateOptions(scopes: ApiKeyScope[], opts: ApiKeyCreateOptions
169
286
  if (opts.expiresAt !== undefined) {
170
287
  normalized.expiresAt = normalizeIsoTimestamp(opts.expiresAt, "--expires-at");
171
288
  if (new Date(normalized.expiresAt).getTime() <= Date.now()) {
172
- throw new Error("--expires-at must be in the future.");
289
+ throw new CliUsageError("--expires-at must be in the future.");
173
290
  }
174
291
  }
175
292
 
@@ -177,12 +294,12 @@ function normalizeCreateOptions(scopes: ApiKeyScope[], opts: ApiKeyCreateOptions
177
294
  if (forced.length === 0) return normalized;
178
295
 
179
296
  if (normalized.expiresAt === undefined) {
180
- throw new Error(
297
+ throw new CliUsageError(
181
298
  `Sensitive scopes require --expires-at within 90 days: ${forced.join(", ")}.`,
182
299
  );
183
300
  }
184
301
  if (new Date(normalized.expiresAt).getTime() > Date.now() + MAX_FORCED_EXPIRY_MS) {
185
- throw new Error("Sensitive scopes require --expires-at within 90 days.");
302
+ throw new CliUsageError("Sensitive scopes require --expires-at within 90 days.");
186
303
  }
187
304
  return normalized;
188
305
  }
@@ -10,6 +10,7 @@ import {
10
10
  import { deviceFlowLogin, type DeviceFlowOptions } from "../device-flow.js";
11
11
  import type { Me } from "./auth.js";
12
12
  import { normalizeHttpUrl, normalizeNonBlank } from "../validators.js";
13
+ import { CliUsageError } from "../exit-codes.js";
13
14
 
14
15
  export interface LoginOptions {
15
16
  baseUrl?: string;
@@ -50,7 +51,7 @@ export async function login(opts: LoginOptions = {}): Promise<LoginResult> {
50
51
  ...opts.deviceFlow,
51
52
  }));
52
53
  if (!key) {
53
- throw new Error(
54
+ throw new CliUsageError(
54
55
  "No API key provided. Run `dharta login` for the browser flow, or pipe a key: " +
55
56
  "echo $DHARTA_API_KEY | dharta login --with-token",
56
57
  );
@@ -1,7 +1,8 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import type { CliContext } from "../context.js";
2
+ import type { MemberInvitation } from "@dharta/sdk";
3
3
  import { table } from "../output.js";
4
4
  import { closestChoice, normalizeNonBlank, parsePositiveInteger } from "../validators.js";
5
+ import { CliUsageError } from "../exit-codes.js";
5
6
 
6
7
  const MEMBER_ROLES = ["owner", "admin", "developer", "billing", "viewer"] as const;
7
8
  type MemberRole = (typeof MEMBER_ROLES)[number];
@@ -44,7 +45,6 @@ export async function membersInviteData(ctx: CliContext, email: string, role: st
44
45
  method: "POST",
45
46
  path: "/api/members/invitations",
46
47
  body: { email: normalizedEmail, role: normalizedRole },
47
- headers: { "Idempotency-Key": randomUUID() },
48
48
  });
49
49
  }
50
50
 
@@ -64,7 +64,6 @@ export async function membersSetRoleData(ctx: CliContext, id: string, role: stri
64
64
  method: "PATCH",
65
65
  path: `/api/members/${encodeURIComponent(memberId)}`,
66
66
  body: { role: normalizedRole },
67
- headers: { "Idempotency-Key": randomUUID() },
68
67
  });
69
68
  }
70
69
 
@@ -78,7 +77,6 @@ export async function membersRemoveData(ctx: CliContext, id: string): Promise<{
78
77
  await ctx.http.request<void>({
79
78
  method: "DELETE",
80
79
  path: `/api/members/${encodeURIComponent(memberId)}`,
81
- headers: { "Idempotency-Key": randomUUID() },
82
80
  });
83
81
  return { id: String(memberId), removed: true };
84
82
  }
@@ -88,10 +86,37 @@ export async function membersRemove(ctx: CliContext, id: string): Promise<string
88
86
  return `Removed member ${res.id}.`;
89
87
  }
90
88
 
89
+ export async function membersRevokeInvitationData(
90
+ ctx: CliContext,
91
+ id: string,
92
+ ): Promise<{ id: string; revoked: boolean }> {
93
+ const invitationId = parsePositiveInteger(id, "invitation id");
94
+ await ctx.sdk.members.revokeInvitation(invitationId);
95
+ return { id: String(invitationId), revoked: true };
96
+ }
97
+
98
+ export async function membersRevokeInvitation(ctx: CliContext, id: string): Promise<string> {
99
+ const res = await membersRevokeInvitationData(ctx, id);
100
+ return `Revoked invitation ${res.id}.`;
101
+ }
102
+
103
+ export async function membersResendInvitationData(
104
+ ctx: CliContext,
105
+ id: string,
106
+ ): Promise<{ invitation: MemberInvitation }> {
107
+ const invitationId = parsePositiveInteger(id, "invitation id");
108
+ return { invitation: await ctx.sdk.members.resendInvitation(invitationId) };
109
+ }
110
+
111
+ export async function membersResendInvitation(ctx: CliContext, id: string): Promise<string> {
112
+ const res = await membersResendInvitationData(ctx, id);
113
+ return `Re-sent the invitation to ${res.invitation.email}. The original link still works.`;
114
+ }
115
+
91
116
  function normalizeEmail(email: string): string {
92
117
  const normalized = email.trim();
93
118
  if (!normalized) {
94
- throw new Error("Member email cannot be blank.");
119
+ throw new CliUsageError("Member email cannot be blank.");
95
120
  }
96
121
  return normalized;
97
122
  }
@@ -102,7 +127,7 @@ function normalizeRole(role: string): MemberRole {
102
127
  return normalized as MemberRole;
103
128
  }
104
129
  const suggestion = closestChoice(normalized, MEMBER_ROLES);
105
- throw new Error(
130
+ throw new CliUsageError(
106
131
  `Unknown member role ${normalized}. Allowed roles: ${MEMBER_ROLES.join(", ")}.` +
107
132
  (suggestion ? ` Did you mean ${suggestion}?` : ""),
108
133
  );