@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.
- package/LICENSE +1 -1
- package/README.md +215 -11
- package/dist/agent.js +3 -2
- package/dist/agent.js.map +1 -1
- package/dist/api-error.js +6 -6
- package/dist/api-error.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +860 -149
- package/dist/cli.js.map +1 -1
- package/dist/command-tree.d.ts +3 -0
- package/dist/command-tree.js +10 -0
- package/dist/command-tree.js.map +1 -0
- package/dist/commands/agents.d.ts +54 -17
- package/dist/commands/agents.js +246 -136
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/analytics.js +6 -5
- package/dist/commands/analytics.js.map +1 -1
- package/dist/commands/api.js +71 -86
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/appearance.js +5 -6
- package/dist/commands/appearance.js.map +1 -1
- package/dist/commands/artifact.js +7 -6
- package/dist/commands/artifact.js.map +1 -1
- package/dist/commands/audit-log.js +2 -1
- package/dist/commands/audit-log.js.map +1 -1
- package/dist/commands/auth.js +26 -6
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/caps.js +11 -15
- package/dist/commands/caps.js.map +1 -1
- package/dist/commands/deploy.js +25 -10
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +10 -1
- package/dist/commands/dev.js +56 -25
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +60 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/embed-keys.js +4 -13
- package/dist/commands/embed-keys.js.map +1 -1
- package/dist/commands/env.js +4 -7
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/files.js +3 -4
- package/dist/commands/files.js.map +1 -1
- package/dist/commands/fleet.d.ts +129 -0
- package/dist/commands/fleet.js +408 -0
- package/dist/commands/fleet.js.map +1 -0
- package/dist/commands/genui.d.ts +2 -0
- package/dist/commands/genui.js +53 -0
- package/dist/commands/genui.js.map +1 -0
- package/dist/commands/git-credential.js +2 -3
- package/dist/commands/git-credential.js.map +1 -1
- package/dist/commands/kartas.d.ts +9 -0
- package/dist/commands/kartas.js +33 -11
- package/dist/commands/kartas.js.map +1 -1
- package/dist/commands/keys.d.ts +17 -18
- package/dist/commands/keys.js +108 -19
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/login.js +2 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/members.d.ts +10 -0
- package/dist/commands/members.js +20 -6
- package/dist/commands/members.js.map +1 -1
- package/dist/commands/model-connections.d.ts +44 -0
- package/dist/commands/model-connections.js +435 -0
- package/dist/commands/model-connections.js.map +1 -0
- package/dist/commands/open.d.ts +2 -2
- package/dist/commands/open.js +4 -3
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/org.d.ts +9 -0
- package/dist/commands/org.js +42 -0
- package/dist/commands/org.js.map +1 -0
- package/dist/commands/organization.d.ts +16 -0
- package/dist/commands/organization.js +118 -0
- package/dist/commands/organization.js.map +1 -0
- package/dist/commands/parity.js +1 -3
- package/dist/commands/parity.js.map +1 -1
- package/dist/commands/repos.d.ts +4 -0
- package/dist/commands/repos.js +17 -0
- package/dist/commands/repos.js.map +1 -0
- package/dist/commands/rollback.js +2 -3
- package/dist/commands/rollback.js.map +1 -1
- package/dist/commands/schedules.js +26 -21
- package/dist/commands/schedules.js.map +1 -1
- package/dist/commands/scopes.js +8 -1
- package/dist/commands/scopes.js.map +1 -1
- package/dist/commands/sessions.js +9 -3
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/setup.js +5 -4
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +24 -11
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/webhooks.d.ts +4 -1
- package/dist/commands/webhooks.js +21 -16
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/completions.js +24 -13
- package/dist/completions.js.map +1 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.js +121 -7
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.js +23 -7
- package/dist/context.js.map +1 -1
- package/dist/docs/cli-reference.d.ts +2 -0
- package/dist/docs/cli-reference.js +191 -0
- package/dist/docs/cli-reference.js.map +1 -0
- package/dist/exit-codes.d.ts +25 -0
- package/dist/exit-codes.js +107 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/genui.d.ts +178 -0
- package/dist/genui.js +1130 -0
- package/dist/genui.js.map +1 -0
- package/dist/harnesses.js +2 -1
- package/dist/harnesses.js.map +1 -1
- package/dist/jq.d.ts +4 -0
- package/dist/jq.js +91 -0
- package/dist/jq.js.map +1 -0
- package/dist/login-offer.js +6 -2
- package/dist/login-offer.js.map +1 -1
- package/dist/manifest.d.ts +12 -0
- package/dist/manifest.js +126 -0
- package/dist/manifest.js.map +1 -1
- package/dist/model-auth.d.ts +29 -0
- package/dist/model-auth.js +117 -0
- package/dist/model-auth.js.map +1 -0
- package/dist/model-settings.d.ts +23 -0
- package/dist/model-settings.js +186 -0
- package/dist/model-settings.js.map +1 -0
- package/dist/money.js +2 -1
- package/dist/money.js.map +1 -1
- package/dist/output.d.ts +6 -0
- package/dist/output.js +54 -0
- package/dist/output.js.map +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/scaffold.js +2 -1
- package/dist/scaffold.js.map +1 -1
- package/dist/validators.js +17 -11
- package/dist/validators.js.map +1 -1
- package/dist/version-check.d.ts +1 -1
- package/dist/version-check.js +1 -1
- package/dist/version-check.js.map +1 -1
- package/node_modules/@dharta/contracts/LICENSE +21 -0
- package/node_modules/@dharta/contracts/README.md +45 -0
- package/node_modules/@dharta/contracts/artifact-runtime.json +750 -0
- package/node_modules/@dharta/contracts/dist/activity.d.ts +62 -0
- package/node_modules/@dharta/contracts/dist/activity.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/activity.js +178 -0
- package/node_modules/@dharta/contracts/dist/activity.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.d.ts +20 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.js +53 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-formats.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.d.ts +37 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.js +5107 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime-validators.generated.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.d.ts +154 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.d.ts +1670 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.js +1903 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.generated.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.js +377 -0
- package/node_modules/@dharta/contracts/dist/artifact-runtime.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/index.d.ts +6 -0
- package/node_modules/@dharta/contracts/dist/index.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/index.js +6 -0
- package/node_modules/@dharta/contracts/dist/index.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/model-auth.d.ts +31 -0
- package/node_modules/@dharta/contracts/dist/model-auth.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/model-auth.js +45 -0
- package/node_modules/@dharta/contracts/dist/model-auth.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/tasks.d.ts +44 -0
- package/node_modules/@dharta/contracts/dist/tasks.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/tasks.js +127 -0
- package/node_modules/@dharta/contracts/dist/tasks.js.map +1 -0
- package/node_modules/@dharta/contracts/dist/wire-events.d.ts +71 -0
- package/node_modules/@dharta/contracts/dist/wire-events.d.ts.map +1 -0
- package/node_modules/@dharta/contracts/dist/wire-events.js +48 -0
- package/node_modules/@dharta/contracts/dist/wire-events.js.map +1 -0
- package/node_modules/@dharta/contracts/model-auth-capabilities-v1.json +168 -0
- package/node_modules/@dharta/contracts/model-auth-reasons-v1.json +38 -0
- package/node_modules/@dharta/contracts/package.json +44 -0
- package/node_modules/@dharta/contracts/src/activity.ts +215 -0
- package/node_modules/@dharta/contracts/src/artifact-runtime-formats.ts +54 -0
- package/node_modules/@dharta/contracts/src/artifact-runtime-validators.generated.ts +29 -0
- package/node_modules/@dharta/contracts/src/artifact-runtime.generated.ts +2093 -0
- package/node_modules/@dharta/contracts/src/artifact-runtime.ts +520 -0
- package/node_modules/@dharta/contracts/src/index.ts +5 -0
- package/node_modules/@dharta/contracts/src/model-auth.ts +82 -0
- package/node_modules/@dharta/contracts/src/tasks.ts +142 -0
- package/node_modules/@dharta/contracts/src/wire-events.ts +150 -0
- package/node_modules/@dharta/contracts/wire-events.json +29 -0
- package/node_modules/@dharta/sdk/LICENSE +1 -1
- package/node_modules/@dharta/sdk/README.md +22 -8
- package/node_modules/@dharta/sdk/dist/errors.d.ts +9 -7
- package/node_modules/@dharta/sdk/dist/errors.d.ts.map +1 -1
- package/node_modules/@dharta/sdk/dist/errors.js +36 -18
- package/node_modules/@dharta/sdk/dist/errors.js.map +1 -1
- package/node_modules/@dharta/sdk/dist/fleet.d.ts +69 -0
- package/node_modules/@dharta/sdk/dist/fleet.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/fleet.js +240 -0
- package/node_modules/@dharta/sdk/dist/fleet.js.map +1 -0
- package/node_modules/@dharta/sdk/dist/http.d.ts +6 -2
- package/node_modules/@dharta/sdk/dist/http.d.ts.map +1 -1
- package/node_modules/@dharta/sdk/dist/http.js +71 -12
- package/node_modules/@dharta/sdk/dist/http.js.map +1 -1
- package/node_modules/@dharta/sdk/dist/idempotency.d.ts +12 -0
- package/node_modules/@dharta/sdk/dist/idempotency.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/idempotency.js +72 -0
- package/node_modules/@dharta/sdk/dist/idempotency.js.map +1 -0
- package/node_modules/@dharta/sdk/dist/index.d.ts +20 -6
- package/node_modules/@dharta/sdk/dist/index.d.ts.map +1 -1
- package/node_modules/@dharta/sdk/dist/index.js +27 -4
- package/node_modules/@dharta/sdk/dist/index.js.map +1 -1
- package/node_modules/@dharta/sdk/dist/resources.d.ts +108 -13
- package/node_modules/@dharta/sdk/dist/resources.d.ts.map +1 -1
- package/node_modules/@dharta/sdk/dist/resources.js +403 -57
- package/node_modules/@dharta/sdk/dist/resources.js.map +1 -1
- package/node_modules/@dharta/sdk/dist/types.d.ts +436 -16
- package/node_modules/@dharta/sdk/dist/types.d.ts.map +1 -1
- package/node_modules/@dharta/sdk/dist/types.js +31 -2
- package/node_modules/@dharta/sdk/dist/types.js.map +1 -1
- package/node_modules/@dharta/sdk/package.json +9 -3
- package/node_modules/@dharta/sdk/src/errors.ts +41 -18
- package/node_modules/@dharta/sdk/src/fleet.ts +339 -0
- package/node_modules/@dharta/sdk/src/http.ts +110 -17
- package/node_modules/@dharta/sdk/src/idempotency.ts +101 -0
- package/node_modules/@dharta/sdk/src/index.ts +50 -11
- package/node_modules/@dharta/sdk/src/resources.ts +549 -68
- package/node_modules/@dharta/sdk/src/types.ts +629 -20
- package/package.json +16 -6
- package/src/agent.ts +3 -2
- package/src/api-error.ts +6 -5
- package/src/cli.ts +978 -163
- package/src/command-tree.ts +13 -0
- package/src/commands/agents.ts +365 -167
- package/src/commands/analytics.ts +6 -5
- package/src/commands/api.ts +73 -92
- package/src/commands/appearance.ts +5 -6
- package/src/commands/artifact.ts +7 -6
- package/src/commands/audit-log.ts +2 -1
- package/src/commands/auth.ts +25 -5
- package/src/commands/caps.ts +11 -15
- package/src/commands/deploy.ts +18 -4
- package/src/commands/dev.ts +86 -25
- package/src/commands/doctor.ts +69 -0
- package/src/commands/embed-keys.ts +4 -14
- package/src/commands/env.ts +4 -7
- package/src/commands/files.ts +3 -4
- package/src/commands/fleet.ts +672 -0
- package/src/commands/genui.ts +59 -0
- package/src/commands/git-credential.ts +2 -3
- package/src/commands/kartas.ts +45 -11
- package/src/commands/keys.ts +152 -35
- package/src/commands/login.ts +2 -1
- package/src/commands/members.ts +31 -6
- package/src/commands/model-connections.ts +547 -0
- package/src/commands/open.ts +5 -4
- package/src/commands/org.ts +57 -0
- package/src/commands/organization.ts +153 -0
- package/src/commands/parity.ts +1 -3
- package/src/commands/repos.ts +23 -0
- package/src/commands/rollback.ts +2 -3
- package/src/commands/schedules.ts +33 -22
- package/src/commands/scopes.ts +8 -1
- package/src/commands/sessions.ts +9 -3
- package/src/commands/setup.ts +5 -4
- package/src/commands/update.ts +28 -12
- package/src/commands/webhooks.ts +30 -19
- package/src/completions.ts +25 -13
- package/src/config.ts +141 -9
- package/src/context.ts +34 -7
- package/src/docs/cli-reference.ts +206 -0
- package/src/exit-codes.ts +122 -0
- package/src/genui.ts +1398 -0
- package/src/harnesses.ts +2 -1
- package/src/jq.ts +109 -0
- package/src/login-offer.ts +6 -2
- package/src/manifest.ts +134 -0
- package/src/model-auth.ts +173 -0
- package/src/model-settings.ts +222 -0
- package/src/money.ts +5 -1
- package/src/output.ts +60 -0
- package/src/runtime.ts +1 -1
- package/src/scaffold.ts +2 -1
- package/src/validators.ts +18 -11
- package/src/version-check.ts +1 -1
- package/dist/commands/model-keys.d.ts +0 -22
- package/dist/commands/model-keys.js +0 -222
- package/dist/commands/model-keys.js.map +0 -1
- 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 {
|
|
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
|
-
|
|
70
|
+
idempotencyKey: mintIdempotencyKey("workspace-token"),
|
|
72
71
|
body: { instance },
|
|
73
72
|
});
|
|
74
73
|
return res.token ?? null;
|
package/src/commands/kartas.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
);
|
package/src/commands/keys.ts
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
import type { CliContext } from "../context.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
302
|
+
throw new CliUsageError("Sensitive scopes require --expires-at within 90 days.");
|
|
186
303
|
}
|
|
187
304
|
return normalized;
|
|
188
305
|
}
|
package/src/commands/login.ts
CHANGED
|
@@ -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
|
|
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
|
);
|
package/src/commands/members.ts
CHANGED
|
@@ -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
|
|
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
|
|
130
|
+
throw new CliUsageError(
|
|
106
131
|
`Unknown member role ${normalized}. Allowed roles: ${MEMBER_ROLES.join(", ")}.` +
|
|
107
132
|
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
108
133
|
);
|