@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CliContext } from "../context.js";
|
|
2
2
|
import { table } from "../output.js";
|
|
3
3
|
import { closestChoice, normalizeDateOnly, parsePositiveInteger } from "../validators.js";
|
|
4
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
4
5
|
|
|
5
6
|
export type AnalyticsView = "agents" | "users" | "instances" | "sessions" | "models" | "cost";
|
|
6
7
|
|
|
@@ -64,7 +65,7 @@ export function normalizeAnalyticsOptions(view: AnalyticsView, opts: AnalyticsOp
|
|
|
64
65
|
if (normalized.range === undefined) {
|
|
65
66
|
normalized.range = "custom";
|
|
66
67
|
} else if (normalized.range !== "custom") {
|
|
67
|
-
throw new
|
|
68
|
+
throw new CliUsageError("--from/--to require --range custom.");
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -72,17 +73,17 @@ export function normalizeAnalyticsOptions(view: AnalyticsView, opts: AnalyticsOp
|
|
|
72
73
|
const allowed = view === "cost" ? COST_ANALYTICS_RANGES : ENTITY_ANALYTICS_RANGES;
|
|
73
74
|
if (!(allowed as readonly string[]).includes(normalized.range)) {
|
|
74
75
|
const suggestion = closestChoice(normalized.range, allowed);
|
|
75
|
-
throw new
|
|
76
|
+
throw new CliUsageError(
|
|
76
77
|
`--range must be one of: ${allowed.join(", ")}.` +
|
|
77
78
|
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
78
79
|
);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
if (normalized.groupBy !== undefined) {
|
|
82
|
-
if (view !== "cost") throw new
|
|
83
|
+
if (view !== "cost") throw new CliUsageError("--group-by is only supported for analytics cost.");
|
|
83
84
|
if (!(COST_ANALYTICS_GROUPS as readonly string[]).includes(normalized.groupBy)) {
|
|
84
85
|
const suggestion = closestChoice(normalized.groupBy, COST_ANALYTICS_GROUPS);
|
|
85
|
-
throw new
|
|
86
|
+
throw new CliUsageError(
|
|
86
87
|
`--group-by must be one of: ${COST_ANALYTICS_GROUPS.join(", ")}.` +
|
|
87
88
|
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
88
89
|
);
|
|
@@ -96,7 +97,7 @@ export function normalizeAnalyticsOptions(view: AnalyticsView, opts: AnalyticsOp
|
|
|
96
97
|
|
|
97
98
|
function normalizeRequiredFilter(value: string, flag: string): string {
|
|
98
99
|
const normalized = value.trim();
|
|
99
|
-
if (!normalized) throw new
|
|
100
|
+
if (!normalized) throw new CliUsageError(`${flag} cannot be blank.`);
|
|
100
101
|
return normalized;
|
|
101
102
|
}
|
|
102
103
|
|
package/src/commands/api.ts
CHANGED
|
@@ -1,77 +1,21 @@
|
|
|
1
1
|
import type { CliContext } from "../context.js";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { cliFormatError } from "../api-error.js";
|
|
3
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
4
4
|
import { closestChoice } from "../validators.js";
|
|
5
5
|
|
|
6
6
|
const METHOD_NAMES = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const;
|
|
7
7
|
const METHODS = new Set<string>(METHOD_NAMES);
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/^\/api\/kartas\/[^/?#]+(?:[?#]|$)/,
|
|
20
|
-
/^\/api\/agents(?:[?#]|$)/,
|
|
21
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}(?:[?#]|$)/,
|
|
22
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/invocations(?:[?#]|$)/,
|
|
23
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/logs(?:[?#]|$)/,
|
|
24
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/env(?:[?#]|$)/,
|
|
25
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/releases(?:[?#]|$)/,
|
|
26
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/appearance(?:[?#]|$)/,
|
|
27
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/schedules(?:[?#]|$)/,
|
|
28
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/directories\/(?:instances|end_users|sessions)(?:[?#]|$)/,
|
|
29
|
-
/^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/embed_keys(?:[?#]|$)/,
|
|
30
|
-
/^\/api\/limits\/budget(?:[?#]|$)/,
|
|
31
|
-
/^\/api\/limits\/instances\/[^/?#]+\/(?:cap|seat_cap)(?:[?#]|$)/,
|
|
32
|
-
/^\/api\/limits\/end_users\/[^/?#]+\/cap(?:[?#]|$)/,
|
|
33
|
-
/^\/api\/webhook_endpoints(?:[?#]|$)/,
|
|
34
|
-
/^\/api_keys(?:[?#]|$)/,
|
|
35
|
-
/^\/model_keys(?:[?#]|$)/,
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
interface WriteAllowlistEntry {
|
|
39
|
-
methods: readonly string[];
|
|
40
|
-
pattern: RegExp;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const WRITE_ALLOWLIST: WriteAllowlistEntry[] = [
|
|
44
|
-
{ methods: ["PATCH"], pattern: /^\/api\/kartas\/[^/?#]+(?:[?#]|$)/ },
|
|
45
|
-
{ methods: ["POST"], pattern: /^\/api\/kartas\/[^/?#]+\/members(?:[?#]|$)/ },
|
|
46
|
-
{ methods: ["PATCH", "DELETE"], pattern: /^\/api\/kartas\/[^/?#]+\/members\/[^/?#]+(?:[?#]|$)/ },
|
|
47
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/embed_keys(?:[?#]|$)/ },
|
|
48
|
-
{ methods: ["PATCH", "DELETE"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/embed_keys\/[^/?#]+(?:[?#]|$)/ },
|
|
49
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/embed_keys\/[^/?#]+\/(?:rotate|disable|enable)(?:[?#]|$)/ },
|
|
50
|
-
{ methods: ["PATCH"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/appearance(?:[?#]|$)/ },
|
|
51
|
-
{ methods: ["PUT", "DELETE"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/env\/[^/?#]+(?:[?#]|$)/ },
|
|
52
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/env\/pull(?:[?#]|$)/ },
|
|
53
|
-
{ methods: ["PATCH"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/(?:serving|dashboard_edits|model)(?:[?#]|$)/ },
|
|
54
|
-
{ methods: ["PATCH"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/workspace_inspectable(?:[?#]|$)/ },
|
|
55
|
-
{ methods: ["DELETE"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}(?:[?#]|$)/ },
|
|
56
|
-
{ methods: ["POST"], pattern: /^\/api\/members\/invitations(?:[?#]|$)/ },
|
|
57
|
-
{ methods: ["PATCH", "DELETE"], pattern: /^\/api\/members\/[^/?#]+(?:[?#]|$)/ },
|
|
58
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/schedules(?:[?#]|$)/ },
|
|
59
|
-
{ methods: ["DELETE"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/schedules\/[^/?#]+(?:[?#]|$)/ },
|
|
60
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/schedules\/[^/?#]+\/(?:pause|resume|run)(?:[?#]|$)/ },
|
|
61
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/releases\/v?\d+\/activate(?:[?#]|$)/ },
|
|
62
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/rollback(?:[?#]|$)/ },
|
|
63
|
-
{ methods: ["POST"], pattern: /^\/api\/agents\/[a-z0-9][a-z0-9-]{0,62}\/workspace_tokens(?:[?#]|$)/ },
|
|
64
|
-
{ methods: ["PATCH"], pattern: /^\/api\/limits\/budget(?:[?#]|$)/ },
|
|
65
|
-
{ methods: ["PATCH"], pattern: /^\/api\/limits\/instances\/[^/?#]+\/(?:cap|seat_cap)(?:[?#]|$)/ },
|
|
66
|
-
{ methods: ["PATCH"], pattern: /^\/api\/limits\/end_users\/[^/?#]+\/cap(?:[?#]|$)/ },
|
|
67
|
-
{ methods: ["POST"], pattern: /^\/api\/webhook_endpoints(?:[?#]|$)/ },
|
|
68
|
-
{ methods: ["PATCH", "DELETE"], pattern: /^\/api\/webhook_endpoints\/[^/?#]+(?:[?#]|$)/ },
|
|
69
|
-
{ methods: ["POST"], pattern: /^\/api\/webhook_endpoints\/[^/?#]+\/enable(?:[?#]|$)/ },
|
|
70
|
-
{ methods: ["POST"], pattern: /^\/api_keys(?:[?#]|$)/ },
|
|
71
|
-
{ methods: ["PATCH", "DELETE"], pattern: /^\/api_keys\/[^/?#]+(?:[?#]|$)/ },
|
|
72
|
-
{ methods: ["POST"], pattern: /^\/model_keys(?:[?#]|$)/ },
|
|
73
|
-
{ methods: ["DELETE"], pattern: /^\/model_keys\/[^/?#]+(?:[?#]|$)/ },
|
|
74
|
-
];
|
|
9
|
+
// Prefix-only reachability (RFC 0048 D1, founder decision 2026-08-31). The CLI
|
|
10
|
+
// used to carry a hand-maintained read/write allowlist, which meant every new
|
|
11
|
+
// public endpoint was unreachable until someone remembered to add a regex here
|
|
12
|
+
// - a second, always-stale copy of the server's route table. The server's
|
|
13
|
+
// scope model is the authorization boundary; this list only keeps the
|
|
14
|
+
// passthrough pointed at the public JSON API instead of, say, a Devise HTML
|
|
15
|
+
// form. Matching is by exact first path segment, so `/apix` and `/me2` are
|
|
16
|
+
// rejected while `/me?fields=x` is allowed.
|
|
17
|
+
const ALLOWED_PREFIXES = ["/api/", "/me", "/version", "/api_keys", "/model_connections", "/agent_templates"] as const;
|
|
18
|
+
const ALLOWED_FIRST_SEGMENTS = new Set(ALLOWED_PREFIXES.map((prefix) => prefix.replace(/^\/|\/$/g, "")));
|
|
75
19
|
|
|
76
20
|
export interface ApiOptions {
|
|
77
21
|
field?: string[];
|
|
@@ -86,12 +30,17 @@ export async function api(ctx: CliContext, methodOrPath: string | undefined, pat
|
|
|
86
30
|
|
|
87
31
|
const headers = parseHeaders(opts.header ?? []);
|
|
88
32
|
const body = await buildBody(opts);
|
|
33
|
+
// `HttpClient.raw` sends no Accept of its own, so fetch defaults to `*/*`.
|
|
34
|
+
// The /api_keys and /model_connections routes are mounted behind a JSON
|
|
35
|
+
// constraint and fall through to the console HTML controller without this,
|
|
36
|
+
// which reads as a mystifying redirect to the sign-in page. A caller who
|
|
37
|
+
// names their own Accept keeps it.
|
|
38
|
+
if (!hasHeader(headers, "accept")) {
|
|
39
|
+
headers.Accept = "application/json";
|
|
40
|
+
}
|
|
89
41
|
if (body !== undefined && !hasHeader(headers, "content-type")) {
|
|
90
42
|
headers["Content-Type"] = "application/json";
|
|
91
43
|
}
|
|
92
|
-
if (method !== "GET" && !hasHeader(headers, "idempotency-key")) {
|
|
93
|
-
headers["Idempotency-Key"] = randomUUID();
|
|
94
|
-
}
|
|
95
44
|
const res = await ctx.http.raw(path, { method, headers, body });
|
|
96
45
|
const text = await res.text();
|
|
97
46
|
const parsed = parseMaybeJson(text);
|
|
@@ -110,9 +59,9 @@ export async function api(ctx: CliContext, methodOrPath: string | undefined, pat
|
|
|
110
59
|
export function validateApiRequest(methodOrPath: string | undefined, pathArg: string | undefined, opts: ApiOptions) {
|
|
111
60
|
const { method, path } = normalizeTarget(methodOrPath, pathArg, hasFields(opts));
|
|
112
61
|
if (method === "GET" && hasFields(opts)) {
|
|
113
|
-
throw new
|
|
62
|
+
throw new CliUsageError("GET requests cannot include --field, --raw-field, or --body-stdin. Use POST, PUT, or PATCH for request bodies.");
|
|
114
63
|
}
|
|
115
|
-
|
|
64
|
+
assertAllowedPrefix(method, path);
|
|
116
65
|
parseHeaders(opts.header ?? []);
|
|
117
66
|
validateBodyFields(opts);
|
|
118
67
|
return { method, path };
|
|
@@ -123,7 +72,7 @@ export async function prepareApiOptions(opts: ApiOptions, read: () => Promise<st
|
|
|
123
72
|
validateBodyFields(opts);
|
|
124
73
|
if (!opts.bodyStdin) return opts;
|
|
125
74
|
const body = await read();
|
|
126
|
-
if (body.length === 0) throw new
|
|
75
|
+
if (body.length === 0) throw new CliUsageError("--body-stdin was set, but stdin was empty.");
|
|
127
76
|
return { ...opts, body, bodyStdin: false };
|
|
128
77
|
}
|
|
129
78
|
|
|
@@ -136,21 +85,21 @@ function normalizeTarget(methodOrPath: string | undefined, pathArg: string | und
|
|
|
136
85
|
const first = methodOrPath?.trim();
|
|
137
86
|
const second = pathArg?.trim();
|
|
138
87
|
if (!first) {
|
|
139
|
-
throw new
|
|
88
|
+
throw new CliUsageError("Usage: dharta api [METHOD] /path");
|
|
140
89
|
}
|
|
141
90
|
|
|
142
91
|
let method: string;
|
|
143
92
|
let path: string;
|
|
144
93
|
const maybeMethod = first.toUpperCase();
|
|
145
94
|
if (METHODS.has(maybeMethod) && !second) {
|
|
146
|
-
throw new
|
|
95
|
+
throw new CliUsageError("Usage: dharta api [METHOD] /path");
|
|
147
96
|
}
|
|
148
97
|
if (METHODS.has(maybeMethod)) {
|
|
149
98
|
method = maybeMethod;
|
|
150
99
|
path = second as string;
|
|
151
100
|
} else if (second) {
|
|
152
101
|
const suggestion = closestChoice(maybeMethod, METHOD_NAMES);
|
|
153
|
-
throw new
|
|
102
|
+
throw new CliUsageError(
|
|
154
103
|
`Unknown API method ${maybeMethod}. Allowed methods: ${METHOD_NAMES.join(", ")}.` +
|
|
155
104
|
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
156
105
|
);
|
|
@@ -160,26 +109,58 @@ function normalizeTarget(methodOrPath: string | undefined, pathArg: string | und
|
|
|
160
109
|
}
|
|
161
110
|
|
|
162
111
|
if (!path.startsWith("/")) {
|
|
163
|
-
throw new
|
|
112
|
+
throw new CliUsageError("API path must start with '/', e.g. /me or /api/agents/support-bot");
|
|
164
113
|
}
|
|
165
114
|
if (/^\/\//.test(path) || /^[a-z][a-z0-9+.-]*:\/\//i.test(path)) {
|
|
166
|
-
throw new
|
|
115
|
+
throw new CliUsageError("API path must be relative to the Dharta API host.");
|
|
167
116
|
}
|
|
168
117
|
return { method, path };
|
|
169
118
|
}
|
|
170
119
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
120
|
+
function assertAllowedPrefix(method: string, path: string): void {
|
|
121
|
+
// Check the path the SERVER will see, not the one that was typed.
|
|
122
|
+
// HttpClient.raw concatenates baseUrl + path and fetch resolves the result
|
|
123
|
+
// with the WHATWG URL parser, which collapses dot segments and treats a
|
|
124
|
+
// backslash as a separator: `/api/../users/sign_in`, `/api/%2e%2e/users`,
|
|
125
|
+
// and `/api\..\users` all arrive as paths the prefix list does not allow.
|
|
126
|
+
const normalized = normalizedPathname(path);
|
|
127
|
+
const typed = path.split(/[?#]/)[0];
|
|
128
|
+
if (normalized !== typed) {
|
|
129
|
+
throw new CliUsageError(
|
|
130
|
+
`API path '${typed}' is not the path that would be requested ('${normalized}'). ` +
|
|
131
|
+
"Write the literal path; '..', '.', and '\\' are not part of any Dharta API route.",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (hasAllowedPrefix(normalized)) return;
|
|
174
135
|
|
|
175
136
|
const suggestion = apiRouteSuggestion(path);
|
|
176
|
-
throw new
|
|
177
|
-
|
|
178
|
-
"Use the matching `karta <command>` for unsupported writes." +
|
|
137
|
+
throw new CliUsageError(
|
|
138
|
+
`\`dharta api\` reaches the public JSON API only. Allowed path prefixes: ${ALLOWED_PREFIXES.join(", ")}.` +
|
|
179
139
|
(suggestion ? ` Did you mean ${method} ${suggestion}?` : ""),
|
|
180
140
|
);
|
|
181
141
|
}
|
|
182
142
|
|
|
143
|
+
// The pathname fetch would resolve this to. The base is a placeholder: only
|
|
144
|
+
// the path matters, and the caller has already rejected absolute and
|
|
145
|
+
// protocol-relative inputs.
|
|
146
|
+
function normalizedPathname(path: string): string {
|
|
147
|
+
try {
|
|
148
|
+
return new URL(path, "http://placeholder").pathname;
|
|
149
|
+
} catch {
|
|
150
|
+
throw new CliUsageError(`API path '${path}' is not a valid URL path.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function hasAllowedPrefix(path: string): boolean {
|
|
155
|
+
const segments = path.split("/");
|
|
156
|
+
const first = segments[1] ?? "";
|
|
157
|
+
if (!ALLOWED_FIRST_SEGMENTS.has(first)) return false;
|
|
158
|
+
// `/api` is a namespace, never a route of its own, so it needs a second
|
|
159
|
+
// segment. The other prefixes name real endpoints.
|
|
160
|
+
if (first === "api") return (segments[2] ?? "") !== "";
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
183
164
|
function apiRouteSuggestion(path: string): string | null {
|
|
184
165
|
const replacements: Array<[RegExp, string]> = [
|
|
185
166
|
[/^\/analytics(?=\/|[?#]|$)/, "/api/analytics"],
|
|
@@ -205,7 +186,7 @@ function hasStructuredFields(opts: ApiOptions): boolean {
|
|
|
205
186
|
|
|
206
187
|
function validateBodyFields(opts: ApiOptions): void {
|
|
207
188
|
if (opts.bodyStdin && hasStructuredFields(opts)) {
|
|
208
|
-
throw new
|
|
189
|
+
throw new CliUsageError("Use either --body-stdin or --field/--raw-field, not both.");
|
|
209
190
|
}
|
|
210
191
|
for (const raw of opts.field ?? []) splitAssignment(raw, "--field");
|
|
211
192
|
for (const raw of opts.rawField ?? []) splitAssignment(raw, "--raw-field");
|
|
@@ -232,13 +213,13 @@ function parseHeaders(input: string[]): Record<string, string> {
|
|
|
232
213
|
const headers: Record<string, string> = {};
|
|
233
214
|
for (const raw of input) {
|
|
234
215
|
const idx = raw.indexOf(":");
|
|
235
|
-
if (idx <= 0) throw new
|
|
216
|
+
if (idx <= 0) throw new CliUsageError(`Invalid --header '${raw}'. Use 'Name: value'.`);
|
|
236
217
|
const key = raw.slice(0, idx).trim();
|
|
237
218
|
const value = raw.slice(idx + 1).trim();
|
|
238
|
-
if (!key || !value) throw new
|
|
219
|
+
if (!key || !value) throw new CliUsageError(`Invalid --header '${raw}'. Use 'Name: value'.`);
|
|
239
220
|
const lower = key.toLowerCase();
|
|
240
221
|
if (lower === "authorization" || lower === "host" || lower.startsWith("x-forwarded-")) {
|
|
241
|
-
throw new
|
|
222
|
+
throw new CliUsageError(`Unsafe --header '${key}' cannot be overridden. The CLI manages auth and connection headers.`);
|
|
242
223
|
}
|
|
243
224
|
headers[key] = value;
|
|
244
225
|
}
|
|
@@ -253,9 +234,9 @@ function hasHeader(headers: Record<string, string>, name: string): boolean {
|
|
|
253
234
|
|
|
254
235
|
function splitAssignment(raw: string, flag: string): [string, string] {
|
|
255
236
|
const idx = raw.indexOf("=");
|
|
256
|
-
if (idx <= 0) throw new
|
|
237
|
+
if (idx <= 0) throw new CliUsageError(`Invalid ${flag} '${raw}'. Use key=value.`);
|
|
257
238
|
const key = raw.slice(0, idx).trim();
|
|
258
|
-
if (!key) throw new
|
|
239
|
+
if (!key) throw new CliUsageError(`Invalid ${flag} '${raw}'. Use key=value.`);
|
|
259
240
|
return [key, raw.slice(idx + 1)];
|
|
260
241
|
}
|
|
261
242
|
|
|
@@ -285,13 +266,13 @@ function parseMaybeJson(text: string): unknown {
|
|
|
285
266
|
|
|
286
267
|
async function readStdin(): Promise<string> {
|
|
287
268
|
if (process.stdin.isTTY) {
|
|
288
|
-
throw new
|
|
269
|
+
throw new CliUsageError(
|
|
289
270
|
"No piped input. Usage: echo '{\"name\":\"Example\"}' | dharta api POST /api/agents/support-bot/embed_keys --body-stdin",
|
|
290
271
|
);
|
|
291
272
|
}
|
|
292
273
|
const chunks: Buffer[] = [];
|
|
293
274
|
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
294
275
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
295
|
-
if (text.length === 0) throw new
|
|
276
|
+
if (text.length === 0) throw new CliUsageError("--body-stdin was set, but stdin was empty.");
|
|
296
277
|
return text;
|
|
297
278
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import { readFile } from "node:fs/promises";
|
|
3
2
|
import { inferAgentSlug, type AgentInferOptions } from "../agent.js";
|
|
4
3
|
import type { CliContext } from "../context.js";
|
|
5
4
|
import { normalizeNonBlank } from "../validators.js";
|
|
5
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
6
6
|
|
|
7
7
|
export interface AppearanceEnvelope {
|
|
8
8
|
agent: {
|
|
@@ -32,13 +32,12 @@ export async function appearanceGet(ctx: CliContext, opts: AgentInferOptions): P
|
|
|
32
32
|
|
|
33
33
|
export async function appearanceSetData(ctx: CliContext, opts: AppearanceOptions): Promise<AppearanceEnvelope> {
|
|
34
34
|
await validateAppearanceSetInput(opts);
|
|
35
|
-
if (!opts.file) throw new
|
|
35
|
+
if (!opts.file) throw new CliUsageError("Pass --file <file>. Usage: dharta appearance set --file appearance.json");
|
|
36
36
|
const slug = await inferAgentSlug(opts);
|
|
37
37
|
const appearance = await readAppearanceFile(opts.file);
|
|
38
38
|
return ctx.http.request<AppearanceEnvelope>({
|
|
39
39
|
method: "PATCH",
|
|
40
40
|
path: `/api/agents/${encodeURIComponent(slug)}/appearance`,
|
|
41
|
-
headers: { "Idempotency-Key": randomUUID() },
|
|
42
41
|
body: { appearance },
|
|
43
42
|
});
|
|
44
43
|
}
|
|
@@ -49,7 +48,7 @@ export async function appearanceSet(ctx: CliContext, opts: AppearanceOptions): P
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
export async function validateAppearanceSetInput(opts: AppearanceOptions): Promise<void> {
|
|
52
|
-
if (!opts.file) throw new
|
|
51
|
+
if (!opts.file) throw new CliUsageError("Pass --file <file>. Usage: dharta appearance set --file appearance.json");
|
|
53
52
|
await readAppearanceFile(opts.file);
|
|
54
53
|
}
|
|
55
54
|
|
|
@@ -69,11 +68,11 @@ async function readAppearanceFile(file: string): Promise<Record<string, unknown>
|
|
|
69
68
|
try {
|
|
70
69
|
parsed = JSON.parse(text);
|
|
71
70
|
} catch {
|
|
72
|
-
throw new
|
|
71
|
+
throw new CliUsageError(`--file ${normalized} must contain valid JSON.`);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
76
|
-
throw new
|
|
75
|
+
throw new CliUsageError(`--file ${normalized} must contain a JSON object.`);
|
|
77
76
|
}
|
|
78
77
|
return parsed as Record<string, unknown>;
|
|
79
78
|
}
|
package/src/commands/artifact.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { resolveRuntime, spawnRuntime } from "../runtime.js";
|
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { Command } from "commander";
|
|
5
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
5
6
|
|
|
6
7
|
export async function artifact(args: string[]): Promise<void> {
|
|
7
8
|
if (args[0] !== "run") {
|
|
@@ -154,7 +155,7 @@ export function registerArtifactCommands(program: Command): void {
|
|
|
154
155
|
.option("--id <id>", "Stable schema-2 Artifact ID")
|
|
155
156
|
.action(async (opts: { key?: string; id?: string }) => {
|
|
156
157
|
if ((!opts.key && !opts.id) || (opts.key && opts.id)) {
|
|
157
|
-
throw new
|
|
158
|
+
throw new CliUsageError(
|
|
158
159
|
"Exactly one of --key or --id is required. See `dharta artifact remove --help`.",
|
|
159
160
|
);
|
|
160
161
|
}
|
|
@@ -234,7 +235,7 @@ type ArtifactPutCliOptions = {
|
|
|
234
235
|
|
|
235
236
|
async function runPut(opts: ArtifactPutCliOptions): Promise<void> {
|
|
236
237
|
if (!opts.title) {
|
|
237
|
-
throw new
|
|
238
|
+
throw new CliUsageError("--title is required. See `dharta artifact put --help`.");
|
|
238
239
|
}
|
|
239
240
|
const requestedAgentPolicy = Boolean(
|
|
240
241
|
opts.agentAuthority.length
|
|
@@ -253,13 +254,13 @@ async function runPut(opts: ArtifactPutCliOptions): Promise<void> {
|
|
|
253
254
|
|| opts.provenance || opts.continuation.length
|
|
254
255
|
);
|
|
255
256
|
if (v2 && legacy) {
|
|
256
|
-
throw new
|
|
257
|
+
throw new CliUsageError(
|
|
257
258
|
"Legacy and schema-2 Artifact options cannot be mixed. See `dharta artifact put --help`.",
|
|
258
259
|
);
|
|
259
260
|
}
|
|
260
261
|
if (v2) {
|
|
261
262
|
if (!opts.id || !opts.root || Boolean(opts.entry) === Boolean(opts.run)) {
|
|
262
|
-
throw new
|
|
263
|
+
throw new CliUsageError(
|
|
263
264
|
"Schema-2 Artifacts require --id, --root, and exactly one of --entry or --run. "
|
|
264
265
|
+ "See `dharta artifact put --help`.",
|
|
265
266
|
);
|
|
@@ -277,7 +278,7 @@ async function runPut(opts: ArtifactPutCliOptions): Promise<void> {
|
|
|
277
278
|
.map(([name]) => name)
|
|
278
279
|
: [];
|
|
279
280
|
if (missingAgentPolicy.length > 0) {
|
|
280
|
-
throw new
|
|
281
|
+
throw new CliUsageError(
|
|
281
282
|
`Agent activation requires a complete policy; missing ${missingAgentPolicy.join(", ")}. `
|
|
282
283
|
+ "See `dharta artifact put --help`.",
|
|
283
284
|
);
|
|
@@ -305,7 +306,7 @@ async function runPut(opts: ArtifactPutCliOptions): Promise<void> {
|
|
|
305
306
|
}));
|
|
306
307
|
}
|
|
307
308
|
if (!opts.key || !opts.kind || !opts.entrypoint) {
|
|
308
|
-
throw new
|
|
309
|
+
throw new CliUsageError(
|
|
309
310
|
"Artifacts require --key, --kind, and --entrypoint. "
|
|
310
311
|
+ "See `dharta artifact put --help`.",
|
|
311
312
|
);
|
|
@@ -2,6 +2,7 @@ import type { CliContext } from "../context.js";
|
|
|
2
2
|
import { cliFormatRawError } from "../api-error.js";
|
|
3
3
|
import { fmtTs, table } from "../output.js";
|
|
4
4
|
import { parsePositiveInteger, validateDateOnly } from "../validators.js";
|
|
5
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
5
6
|
|
|
6
7
|
interface AuditLogResponse {
|
|
7
8
|
data: AuditLogEvent[];
|
|
@@ -106,7 +107,7 @@ function normalizeAuditLogOptions(opts: AuditLogOptions): AuditLogOptions {
|
|
|
106
107
|
function normalizeOptionalFilter(value: string | undefined, flag: string): string | undefined {
|
|
107
108
|
if (value === undefined) return undefined;
|
|
108
109
|
const normalized = value.trim();
|
|
109
|
-
if (normalized.length === 0) throw new
|
|
110
|
+
if (normalized.length === 0) throw new CliUsageError(`${flag} cannot be blank.`);
|
|
110
111
|
return normalized;
|
|
111
112
|
}
|
|
112
113
|
|
package/src/commands/auth.ts
CHANGED
|
@@ -4,12 +4,16 @@ import { HttpClient } from "@dharta/sdk";
|
|
|
4
4
|
import { cliFormatError, cliFormatNetworkError } from "../api-error.js";
|
|
5
5
|
import {
|
|
6
6
|
configPath,
|
|
7
|
+
explicitProfileName,
|
|
7
8
|
loadConfig,
|
|
9
|
+
profileNotFoundError,
|
|
8
10
|
removeProfile,
|
|
11
|
+
requestedProfileName,
|
|
9
12
|
DEFAULT_BASE_URL,
|
|
10
13
|
} from "../config.js";
|
|
11
14
|
import { normalizeNonBlank } from "../validators.js";
|
|
12
15
|
import { formatApiKeyExpiry, formatScopes } from "./scopes.js";
|
|
16
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
13
17
|
|
|
14
18
|
// GET /me — the control plane's key-introspection endpoint.
|
|
15
19
|
export interface Me {
|
|
@@ -142,14 +146,26 @@ function formatCause(error: unknown): string {
|
|
|
142
146
|
return error instanceof Error && error.message ? error.message : String(error);
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
// Print the active token for piping (gh auth token parity).
|
|
146
|
-
//
|
|
149
|
+
// Print the active token for piping (gh auth token parity). An ambient
|
|
150
|
+
// DHARTA_API_KEY wins, matching every other command, unless --profile was
|
|
151
|
+
// passed.
|
|
147
152
|
export async function authToken(): Promise<string> {
|
|
153
|
+
// Same precedence as buildContext: an explicit --profile is a deliberate act
|
|
154
|
+
// and outranks an ambient key, so `dharta --profile ci auth token` cannot
|
|
155
|
+
// print a credential from another organization.
|
|
148
156
|
const envKey = process.env.DHARTA_API_KEY;
|
|
149
|
-
if (envKey) return envKey;
|
|
157
|
+
if (envKey && explicitProfileName() === undefined) return envKey;
|
|
150
158
|
const cfg = await loadConfig();
|
|
151
|
-
|
|
159
|
+
// The global --profile picks which stored credential this prints, so
|
|
160
|
+
// `dharta --profile ci auth token` pipes the ci key, not the default one.
|
|
161
|
+
const requested = requestedProfileName();
|
|
162
|
+
const name = requested ?? cfg?.default_profile;
|
|
163
|
+
const profile = name === undefined ? undefined : cfg?.profiles[name];
|
|
152
164
|
if (!profile) {
|
|
165
|
+
// A profile the user named and that is not stored is a usage error, the
|
|
166
|
+
// same as everywhere else. Only "nothing is stored at all" is a
|
|
167
|
+
// not-authenticated case.
|
|
168
|
+
if (requested !== undefined) throw profileNotFoundError(requested);
|
|
153
169
|
throw new Error("Not logged in. Run `dharta login` to sign in via your browser.");
|
|
154
170
|
}
|
|
155
171
|
return profile.api_key;
|
|
@@ -163,6 +179,7 @@ export async function authLogoutData(
|
|
|
163
179
|
const requestedName = profileName === undefined ? undefined : normalizeNonBlank(profileName, "--profile");
|
|
164
180
|
const cfg = await loadConfig();
|
|
165
181
|
if (!cfg) {
|
|
182
|
+
if (requestedName !== undefined) throw profileNotFoundError(requestedName);
|
|
166
183
|
throw new Error(
|
|
167
184
|
"Not logged in - nothing to remove. `dharta auth status` shows what is stored.",
|
|
168
185
|
);
|
|
@@ -170,7 +187,10 @@ export async function authLogoutData(
|
|
|
170
187
|
const name = requestedName ?? cfg.default_profile;
|
|
171
188
|
const removed = await removeProfile(name);
|
|
172
189
|
if (!removed) {
|
|
173
|
-
|
|
190
|
+
// Naming a profile that is not there is a usage error, and it reads the
|
|
191
|
+
// same as it does on every other command.
|
|
192
|
+
if (requestedName !== undefined) throw profileNotFoundError(name);
|
|
193
|
+
throw new CliUsageError(
|
|
174
194
|
`No profile named '${name}' in ${configPath()}. Run \`dharta auth status\` to list profiles.`,
|
|
175
195
|
);
|
|
176
196
|
}
|
package/src/commands/caps.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { CliContext } from "../context.js";
|
|
3
2
|
import { parseDollarAmountToCents } from "../money.js";
|
|
3
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
4
4
|
|
|
5
5
|
export interface Budget {
|
|
6
6
|
period: string;
|
|
@@ -108,7 +108,6 @@ export async function capsBudgetSetData(ctx: CliContext, opts: BudgetSetOptions)
|
|
|
108
108
|
return ctx.http.request<BudgetEnvelope>({
|
|
109
109
|
method: "PATCH",
|
|
110
110
|
path: "/api/limits/budget",
|
|
111
|
-
headers: { "Idempotency-Key": randomUUID() },
|
|
112
111
|
body,
|
|
113
112
|
});
|
|
114
113
|
}
|
|
@@ -134,7 +133,6 @@ export async function capsInstanceSetData(ctx: CliContext, instance: string, opt
|
|
|
134
133
|
return ctx.http.request<InstanceCapEnvelope>({
|
|
135
134
|
method: "PATCH",
|
|
136
135
|
path: `/api/limits/instances/${encodeURIComponent(normalizedInstance)}/cap`,
|
|
137
|
-
headers: { "Idempotency-Key": randomUUID() },
|
|
138
136
|
body: capBody(opts),
|
|
139
137
|
});
|
|
140
138
|
}
|
|
@@ -161,7 +159,6 @@ export async function capsEndUserSetData(ctx: CliContext, endUserId: string, opt
|
|
|
161
159
|
return ctx.http.request<EndUserCapEnvelope>({
|
|
162
160
|
method: "PATCH",
|
|
163
161
|
path: `/api/limits/end_users/${encodeURIComponent(normalizedEndUserId)}/cap`,
|
|
164
|
-
headers: { "Idempotency-Key": randomUUID() },
|
|
165
162
|
body: capBody(opts),
|
|
166
163
|
});
|
|
167
164
|
}
|
|
@@ -193,7 +190,6 @@ export async function capsSeatSetData(ctx: CliContext, instance: string, endUser
|
|
|
193
190
|
return ctx.http.request<SeatCapEnvelope>({
|
|
194
191
|
method: "PATCH",
|
|
195
192
|
path: `/api/limits/instances/${encodeURIComponent(normalizedInstance)}/seat_cap`,
|
|
196
|
-
headers: { "Idempotency-Key": randomUUID() },
|
|
197
193
|
body: { ...capBody(opts), end_user_id: normalizedEndUserId },
|
|
198
194
|
});
|
|
199
195
|
}
|
|
@@ -207,7 +203,7 @@ export async function capsSeatSet(ctx: CliContext, instance: string, endUserId:
|
|
|
207
203
|
|
|
208
204
|
function dollarsToCents(value: string | undefined): number {
|
|
209
205
|
if (value === undefined) {
|
|
210
|
-
throw new
|
|
206
|
+
throw new CliUsageError("Pass --amount <dollars>. Usage: dharta caps budget set --amount 250");
|
|
211
207
|
}
|
|
212
208
|
return parseDollarAmountToCents(value, "--amount");
|
|
213
209
|
}
|
|
@@ -229,34 +225,34 @@ export function validateCapSetOptions(opts: CapSetOptions): void {
|
|
|
229
225
|
}
|
|
230
226
|
|
|
231
227
|
function capBody(opts: CapSetOptions): Record<string, unknown> {
|
|
232
|
-
if (opts.clear && opts.amount) throw new
|
|
228
|
+
if (opts.clear && opts.amount) throw new CliUsageError("Use either --amount or --clear, not both.");
|
|
233
229
|
if (opts.clear) return { clear: true };
|
|
234
|
-
if (!opts.amount) throw new
|
|
230
|
+
if (!opts.amount) throw new CliUsageError("Pass --amount <dollars> or --clear.");
|
|
235
231
|
return { cap_cents: dollarsToCents(opts.amount) };
|
|
236
232
|
}
|
|
237
233
|
|
|
238
234
|
function parseWholeNumberInRange(value: string, flag: string, min: number, max: number): number {
|
|
239
235
|
const normalized = value.trim();
|
|
240
|
-
if (!/^\d+$/.test(normalized)) throw new
|
|
236
|
+
if (!/^\d+$/.test(normalized)) throw new CliUsageError(`${flag} must be a whole number.`);
|
|
241
237
|
const parsed = Number(normalized);
|
|
242
238
|
if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
|
|
243
|
-
throw new
|
|
239
|
+
throw new CliUsageError(`${flag} must be a whole number from ${min} to ${max}.`);
|
|
244
240
|
}
|
|
245
241
|
return parsed;
|
|
246
242
|
}
|
|
247
243
|
|
|
248
244
|
function parsePositiveWholeNumber(value: string, flag: string): number {
|
|
249
245
|
const normalized = value.trim();
|
|
250
|
-
if (!/^\d+$/.test(normalized)) throw new
|
|
246
|
+
if (!/^\d+$/.test(normalized)) throw new CliUsageError(`${flag} must be a whole number.`);
|
|
251
247
|
const parsed = Number(normalized);
|
|
252
248
|
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
253
|
-
throw new
|
|
249
|
+
throw new CliUsageError(`${flag} must be a positive whole number.`);
|
|
254
250
|
}
|
|
255
251
|
return parsed;
|
|
256
252
|
}
|
|
257
253
|
|
|
258
254
|
function stopUsageOption(opts: BudgetSetOptions): boolean | undefined {
|
|
259
|
-
if (opts.hardStop && opts.soft) throw new
|
|
255
|
+
if (opts.hardStop && opts.soft) throw new CliUsageError("Use either --hard-stop or --soft, not both.");
|
|
260
256
|
if (opts.hardStop) return true;
|
|
261
257
|
if (opts.soft) return false;
|
|
262
258
|
return undefined;
|
|
@@ -264,7 +260,7 @@ function stopUsageOption(opts: BudgetSetOptions): boolean | undefined {
|
|
|
264
260
|
|
|
265
261
|
function alertsOption(opts: BudgetSetOptions): boolean | undefined {
|
|
266
262
|
if (opts.enableAlerts && opts.disableAlerts) {
|
|
267
|
-
throw new
|
|
263
|
+
throw new CliUsageError("Use either --enable-alerts or --disable-alerts, not both.");
|
|
268
264
|
}
|
|
269
265
|
if (opts.enableAlerts) return true;
|
|
270
266
|
if (opts.disableAlerts) return false;
|
|
@@ -296,7 +292,7 @@ function capSetMessage(label: string, cap: EntityCap | null, changed: boolean |
|
|
|
296
292
|
function normalizeRequired(value: string, label: string): string {
|
|
297
293
|
const normalized = value.trim();
|
|
298
294
|
if (!normalized) {
|
|
299
|
-
throw new
|
|
295
|
+
throw new CliUsageError(`${label} cannot be blank.`);
|
|
300
296
|
}
|
|
301
297
|
return normalized;
|
|
302
298
|
}
|