@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
package/src/commands/webhooks.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import type { CliContext } from "../context.js";
|
|
3
|
-
import { WEBHOOK_EVENT_TYPES } from "@dharta/sdk";
|
|
2
|
+
import { WEBHOOK_EVENT_TYPES, type WebhookDelivery } from "@dharta/sdk";
|
|
4
3
|
import { fmtTs, table } from "../output.js";
|
|
5
4
|
import { closestChoice, normalizeNonBlank } from "../validators.js";
|
|
5
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
6
6
|
|
|
7
|
+
// The shape `Api::WebhookEndpointsController#serialize` returns. `id` is the
|
|
8
|
+
// integer primary key; it read `string` here, in the SDK types and in the
|
|
9
|
+
// OpenAPI document, and the controller has always emitted a number.
|
|
7
10
|
export interface WebhookEndpoint {
|
|
8
|
-
id:
|
|
11
|
+
id: number;
|
|
9
12
|
name: string;
|
|
10
13
|
url: string;
|
|
11
14
|
event_types: string[];
|
|
@@ -38,7 +41,7 @@ export async function webhooksList(ctx: CliContext): Promise<string> {
|
|
|
38
41
|
const data = await webhooksListData(ctx);
|
|
39
42
|
if (data.length === 0) return "No webhook endpoints. Create one with: dharta webhooks create <name> <url>";
|
|
40
43
|
return table(data.map((endpoint) => ({
|
|
41
|
-
id: endpoint.id,
|
|
44
|
+
id: String(endpoint.id),
|
|
42
45
|
name: endpoint.name,
|
|
43
46
|
url: endpoint.url,
|
|
44
47
|
state: endpoint.enabled ? "enabled" : "disabled",
|
|
@@ -59,7 +62,6 @@ export async function webhooksCreateData(
|
|
|
59
62
|
return ctx.http.request<EndpointEnvelope>({
|
|
60
63
|
method: "POST",
|
|
61
64
|
path: "/api/webhook_endpoints",
|
|
62
|
-
headers: idempotencyHeader(),
|
|
63
65
|
body: { name: normalizedName, url: normalizedUrl, event_types: parseEvents(opts.event) },
|
|
64
66
|
});
|
|
65
67
|
}
|
|
@@ -85,12 +87,11 @@ export async function webhooksUpdateData(
|
|
|
85
87
|
if (opts.url !== undefined) body.url = normalizeWebhookUrl(opts.url);
|
|
86
88
|
if (opts.event !== undefined) body.event_types = parseEvents(opts.event);
|
|
87
89
|
if (Object.keys(body).length === 0) {
|
|
88
|
-
throw new
|
|
90
|
+
throw new CliUsageError("Pass at least one update option: --name, --url, or --event.");
|
|
89
91
|
}
|
|
90
92
|
return ctx.http.request<EndpointEnvelope>({
|
|
91
93
|
method: "PATCH",
|
|
92
94
|
path: `/api/webhook_endpoints/${encodeURIComponent(normalizedId)}`,
|
|
93
|
-
headers: idempotencyHeader(),
|
|
94
95
|
body,
|
|
95
96
|
});
|
|
96
97
|
}
|
|
@@ -115,7 +116,7 @@ export function validateWebhooksUpdateInput(id: string, opts: { name?: string; u
|
|
|
115
116
|
if (hasUrl) normalizeWebhookUrl(opts.url!);
|
|
116
117
|
if (hasEvent) parseEvents(opts.event);
|
|
117
118
|
if (!hasName && !hasUrl && !hasEvent) {
|
|
118
|
-
throw new
|
|
119
|
+
throw new CliUsageError("Pass at least one update option: --name, --url, or --event.");
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -124,7 +125,6 @@ export async function webhooksEnableData(ctx: CliContext, id: string): Promise<E
|
|
|
124
125
|
return ctx.http.request<EndpointEnvelope>({
|
|
125
126
|
method: "POST",
|
|
126
127
|
path: `/api/webhook_endpoints/${encodeURIComponent(normalizedId)}/enable`,
|
|
127
|
-
headers: idempotencyHeader(),
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -133,12 +133,27 @@ export async function webhooksEnable(ctx: CliContext, id: string): Promise<strin
|
|
|
133
133
|
return `Enabled webhook endpoint ${res.webhook_endpoint.id}.`;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Queues one probe through the production delivery path: the same delivery
|
|
137
|
+
// row, the same signature, the same retry schedule. The answer is the QUEUED
|
|
138
|
+
// delivery, not its outcome - read that from `dharta webhooks list` and the
|
|
139
|
+
// deliveries the endpoint records.
|
|
140
|
+
export async function webhooksTestData(ctx: CliContext, id: string): Promise<WebhookDelivery> {
|
|
141
|
+
return ctx.sdk.webhookEndpoints.test(normalizeWebhookId(id));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function webhooksTest(ctx: CliContext, id: string): Promise<string> {
|
|
145
|
+
const delivery = await webhooksTestData(ctx, id);
|
|
146
|
+
return (
|
|
147
|
+
`Queued ${delivery.event_type} delivery ${delivery.id} (event ${delivery.event_id}) ` +
|
|
148
|
+
"to the endpoint. Its outcome lands on the delivery record, not here."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
136
152
|
export async function webhooksDeleteData(ctx: CliContext, id: string): Promise<{ id: string; deleted: boolean }> {
|
|
137
153
|
const normalizedId = normalizeWebhookId(id);
|
|
138
154
|
await ctx.http.request<void>({
|
|
139
155
|
method: "DELETE",
|
|
140
156
|
path: `/api/webhook_endpoints/${encodeURIComponent(normalizedId)}`,
|
|
141
|
-
headers: idempotencyHeader(),
|
|
142
157
|
});
|
|
143
158
|
return { id: normalizedId, deleted: true };
|
|
144
159
|
}
|
|
@@ -155,13 +170,13 @@ export function parseEvents(input: string[] | undefined): string[] {
|
|
|
155
170
|
.map((value) => value.trim())
|
|
156
171
|
.filter(Boolean);
|
|
157
172
|
if (events.length === 0) {
|
|
158
|
-
throw new
|
|
173
|
+
throw new CliUsageError("--event must include at least one event type, e.g. --event turn.completed.");
|
|
159
174
|
}
|
|
160
175
|
const allowed = new Set<string>(WEBHOOK_EVENT_TYPES);
|
|
161
176
|
const unknown = events.filter((event) => !allowed.has(event));
|
|
162
177
|
if (unknown.length > 0) {
|
|
163
178
|
const suggestion = unknown.length === 1 ? closestChoice(unknown[0]!, WEBHOOK_EVENT_TYPES) : null;
|
|
164
|
-
throw new
|
|
179
|
+
throw new CliUsageError(
|
|
165
180
|
`Unknown --event ${unknown.join(", ")}. Allowed events: ${WEBHOOK_EVENT_TYPES.join(", ")}.` +
|
|
166
181
|
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
167
182
|
);
|
|
@@ -169,14 +184,10 @@ export function parseEvents(input: string[] | undefined): string[] {
|
|
|
169
184
|
return events;
|
|
170
185
|
}
|
|
171
186
|
|
|
172
|
-
function idempotencyHeader(): Record<string, string> {
|
|
173
|
-
return { "Idempotency-Key": randomUUID() };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
187
|
function normalizeRequired(value: string, label: string): string {
|
|
177
188
|
const normalized = value.trim();
|
|
178
189
|
if (!normalized) {
|
|
179
|
-
throw new
|
|
190
|
+
throw new CliUsageError(`${label} cannot be blank.`);
|
|
180
191
|
}
|
|
181
192
|
return normalized;
|
|
182
193
|
}
|
|
@@ -187,10 +198,10 @@ function normalizeWebhookUrl(value: string): string {
|
|
|
187
198
|
try {
|
|
188
199
|
parsed = new URL(normalized);
|
|
189
200
|
} catch {
|
|
190
|
-
throw new
|
|
201
|
+
throw new CliUsageError("Webhook URL must be an absolute http(s) URL.");
|
|
191
202
|
}
|
|
192
203
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
193
|
-
throw new
|
|
204
|
+
throw new CliUsageError("Webhook URL must be an absolute http(s) URL.");
|
|
194
205
|
}
|
|
195
206
|
return normalized;
|
|
196
207
|
}
|
package/src/completions.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// CLI version's command surface; regenerate after updating.
|
|
5
5
|
|
|
6
6
|
import type { Argument, Command, Option } from "commander";
|
|
7
|
+
import { visibleSubcommands } from "./command-tree.js";
|
|
7
8
|
|
|
8
9
|
interface CommandSpec {
|
|
9
10
|
name: string;
|
|
@@ -17,6 +18,12 @@ interface CommandSpec {
|
|
|
17
18
|
subs: CommandSpec[];
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
// zsh looks up `_<command>` by convention and bash needs a legal shell
|
|
22
|
+
// identifier, so non-word characters in the binary name become underscores.
|
|
23
|
+
function completionFunctionName(bin: string): string {
|
|
24
|
+
return `_${bin.replace(/[^A-Za-z0-9_]/g, "_")}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
export function specFromCommand(cmd: Command): CommandSpec {
|
|
21
28
|
return {
|
|
22
29
|
name: cmd.name(),
|
|
@@ -27,9 +34,7 @@ export function specFromCommand(cmd: Command): CommandSpec {
|
|
|
27
34
|
"--help",
|
|
28
35
|
],
|
|
29
36
|
argChoices: cmd.registeredArguments.flatMap((a: Argument) => a.argChoices ?? []),
|
|
30
|
-
subs: cmd.
|
|
31
|
-
.filter((c) => !(c as unknown as { _hidden?: boolean })._hidden)
|
|
32
|
-
.map((c) => specFromCommand(c as Command)),
|
|
37
|
+
subs: visibleSubcommands(cmd).map((c) => specFromCommand(c)),
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -114,10 +119,15 @@ function escapeZshDescription(value: string): string {
|
|
|
114
119
|
export function bashCompletion(program: Command): string {
|
|
115
120
|
const spec = specFromCommand(program);
|
|
116
121
|
const entries = completionEntries(spec);
|
|
122
|
+
// The binary name comes from the program, never a literal: these scripts
|
|
123
|
+
// bound `karta` for the whole life of the renamed CLI, so `complete -F`
|
|
124
|
+
// attached to a command that no longer exists and completion was dead.
|
|
125
|
+
const bin = program.name();
|
|
126
|
+
const fn = completionFunctionName(bin);
|
|
117
127
|
const lines: string[] = [
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
`# bash completion for ${bin} - generated by \`${bin} completion bash\``,
|
|
129
|
+
`# Load with: source <(${bin} completion bash) (or append to ~/.bash_completion)`,
|
|
130
|
+
`${fn}() {`,
|
|
121
131
|
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
122
132
|
' local path="${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}"',
|
|
123
133
|
' local words=""',
|
|
@@ -130,7 +140,7 @@ export function bashCompletion(program: Command): string {
|
|
|
130
140
|
" esac",
|
|
131
141
|
' COMPREPLY=( $(compgen -W "$words" -- "$cur") )',
|
|
132
142
|
"}",
|
|
133
|
-
|
|
143
|
+
`complete -F ${fn} ${bin}`,
|
|
134
144
|
);
|
|
135
145
|
return lines.join("\n");
|
|
136
146
|
}
|
|
@@ -138,13 +148,15 @@ export function bashCompletion(program: Command): string {
|
|
|
138
148
|
export function zshCompletion(program: Command): string {
|
|
139
149
|
const spec = specFromCommand(program);
|
|
140
150
|
const entries = completionEntries(spec);
|
|
151
|
+
const bin = program.name();
|
|
152
|
+
const fn = completionFunctionName(bin);
|
|
141
153
|
|
|
142
154
|
const lines: string[] = [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
155
|
+
`#compdef ${bin}`,
|
|
156
|
+
`# zsh completion for ${bin} - generated by \`${bin} completion zsh\``,
|
|
157
|
+
`# Install: ${bin} completion zsh > ~/.zfunc/${fn} (with fpath+=(~/.zfunc) before compinit)`,
|
|
146
158
|
"",
|
|
147
|
-
|
|
159
|
+
`${fn}() {`,
|
|
148
160
|
" local -a reply",
|
|
149
161
|
' local path=""',
|
|
150
162
|
" if (( CURRENT > 2 )); then",
|
|
@@ -156,13 +168,13 @@ export function zshCompletion(program: Command): string {
|
|
|
156
168
|
lines.push(
|
|
157
169
|
` ${zshCasePattern(entry.path)})`,
|
|
158
170
|
entry.describedCommands
|
|
159
|
-
? ` reply=( ${entry.describedCommands} ); _describe -t commands '
|
|
171
|
+
? ` reply=( ${entry.describedCommands} ); _describe -t commands '${bin} command' reply`
|
|
160
172
|
: " true",
|
|
161
173
|
entry.words ? ` compadd -- ${entry.words}` : " true",
|
|
162
174
|
" return",
|
|
163
175
|
" ;;",
|
|
164
176
|
);
|
|
165
177
|
}
|
|
166
|
-
lines.push(" esac", "}", "",
|
|
178
|
+
lines.push(" esac", "}", "", `${fn} "$@"`);
|
|
167
179
|
return lines.join("\n");
|
|
168
180
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { promises as fs } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import TOML from "@iarna/toml";
|
|
6
|
+
import { assertIdempotencyKey, MAX_IDEMPOTENCY_KEY_LENGTH } from "@dharta/sdk";
|
|
7
|
+
import { CliUsageError } from "./exit-codes.js";
|
|
8
|
+
import { normalizeNonBlank } from "./validators.js";
|
|
5
9
|
|
|
6
10
|
// One host schema (RFC 0013 CLI-7, executing RFC 0011 HOST-5's CLI
|
|
7
11
|
// note). D11's coordinate system has three hosts, each with one auth
|
|
@@ -37,6 +41,115 @@ export function setHomeForTesting(home: string | undefined): void {
|
|
|
37
41
|
homeOverride = home;
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
// The profile named by the global `--profile` flag, recorded once per run by
|
|
45
|
+
// the root preAction hook in cli.ts. Resolution order for every command is:
|
|
46
|
+
// explicit --profile, then DHARTA_PROFILE, then the config's default_profile
|
|
47
|
+
// (the aws --profile shape, RFC 0048 D2).
|
|
48
|
+
let requestedProfile: string | undefined;
|
|
49
|
+
|
|
50
|
+
export function setRequestedProfile(name: string | undefined): void {
|
|
51
|
+
requestedProfile = name === undefined ? undefined : normalizeNonBlank(name, "--profile");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The Idempotency-Key named by the global `--idempotency-key` flag, recorded
|
|
55
|
+
// once per run by the same preAction hook. It rides here rather than in each
|
|
56
|
+
// command because it is a property of the RUN, not of one call: a CI job that
|
|
57
|
+
// reruns after an ambiguous failure has to present the key the first run used,
|
|
58
|
+
// and every curated command mints a fresh one per process otherwise.
|
|
59
|
+
//
|
|
60
|
+
// What a key may contain is the SDK's rule (`assertIdempotencyKey`), written
|
|
61
|
+
// down once in sdks/typescript/src/idempotency.ts and enforced by the client on
|
|
62
|
+
// every key it is handed. The CLI checks it here as well, so a bad flag value is
|
|
63
|
+
// a usage error before any request goes out rather than a failure part way
|
|
64
|
+
// through a run.
|
|
65
|
+
|
|
66
|
+
// A command that performs more than one mutation cannot send the root key on
|
|
67
|
+
// both: the server binds a key to the first request it saw and answers the
|
|
68
|
+
// second with a 422. Such a command derives one key per operation instead (see
|
|
69
|
+
// mintIdempotencyKey), which keeps the writes apart from each other and stable
|
|
70
|
+
// across reruns. The cap applies to the WHOLE header value, so the root has to
|
|
71
|
+
// leave room for the longest suffix we append.
|
|
72
|
+
export const MAX_OPERATION_SUFFIX_LENGTH = 55;
|
|
73
|
+
export { MAX_IDEMPOTENCY_KEY_LENGTH };
|
|
74
|
+
export const MAX_ROOT_IDEMPOTENCY_KEY_LENGTH =
|
|
75
|
+
MAX_IDEMPOTENCY_KEY_LENGTH - MAX_OPERATION_SUFFIX_LENGTH;
|
|
76
|
+
|
|
77
|
+
let requestedIdempotencyKey: string | undefined;
|
|
78
|
+
|
|
79
|
+
export function setRequestedIdempotencyKey(key: string | undefined): void {
|
|
80
|
+
if (key === undefined) {
|
|
81
|
+
requestedIdempotencyKey = undefined;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const normalized = normalizeNonBlank(key, "--idempotency-key");
|
|
85
|
+
try {
|
|
86
|
+
assertIdempotencyKey(normalized, "--idempotency-key");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// The SDK owns what a key may contain; the CLI owns what a rejected flag
|
|
89
|
+
// value exits with. Re-wrapping keeps both true and keeps the wording in
|
|
90
|
+
// one place.
|
|
91
|
+
throw new CliUsageError((error as Error).message);
|
|
92
|
+
}
|
|
93
|
+
if (normalized.length > MAX_ROOT_IDEMPOTENCY_KEY_LENGTH) {
|
|
94
|
+
throw new CliUsageError(
|
|
95
|
+
`--idempotency-key must be at most ${MAX_ROOT_IDEMPOTENCY_KEY_LENGTH} characters. ` +
|
|
96
|
+
`The server accepts ${MAX_IDEMPOTENCY_KEY_LENGTH}; the difference is reserved for the ` +
|
|
97
|
+
"per-operation suffix a multi-mutation command appends.",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
requestedIdempotencyKey = normalized;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function requestedIdempotencyKeyValue(): string | undefined {
|
|
104
|
+
return requestedIdempotencyKey;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Dotted lowercase, so a derived key reads as `<root>.sessions.turn.2` and the
|
|
108
|
+
// operation cannot smuggle in whitespace or a separator of its own.
|
|
109
|
+
const IDEMPOTENCY_OPERATION_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
110
|
+
|
|
111
|
+
// The Idempotency-Key for ONE mutation. With no root key this is a fresh UUID,
|
|
112
|
+
// which is what every command did before the flag existed. With a root key it
|
|
113
|
+
// is `<root>.<operation>`: two mutations in the same run get different keys, and
|
|
114
|
+
// the same mutation in a later run with the same root gets the same key, which
|
|
115
|
+
// is what makes the rerun replay instead of repeating the work.
|
|
116
|
+
//
|
|
117
|
+
// `operation` is written by us, never by the caller, so a bad one is a
|
|
118
|
+
// programming error and throws a plain Error (exit 1), not a usage error.
|
|
119
|
+
export function mintIdempotencyKey(operation: string): string {
|
|
120
|
+
if (!IDEMPOTENCY_OPERATION_PATTERN.test(operation)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Idempotency operation ${JSON.stringify(operation)} is not a dotted lowercase token.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const suffix = `.${operation}`;
|
|
126
|
+
if (suffix.length > MAX_OPERATION_SUFFIX_LENGTH) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Idempotency operation ${JSON.stringify(operation)} exceeds the ` +
|
|
129
|
+
`${MAX_OPERATION_SUFFIX_LENGTH}-character reserve.`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return requestedIdempotencyKey === undefined
|
|
133
|
+
? randomUUID()
|
|
134
|
+
: `${requestedIdempotencyKey}${suffix}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The profile named by the flag alone, ignoring DHARTA_PROFILE. An explicit
|
|
138
|
+
// flag is a deliberate act and outranks an ambient credential in the
|
|
139
|
+
// environment; the env var does not.
|
|
140
|
+
export function explicitProfileName(): string | undefined {
|
|
141
|
+
return requestedProfile;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The explicitly requested profile, or undefined to mean "the default one".
|
|
145
|
+
// Commands that operate ON a profile (login, auth logout) read this rather
|
|
146
|
+
// than the resolved name, so omitting the flag keeps their existing meaning.
|
|
147
|
+
export function requestedProfileName(): string | undefined {
|
|
148
|
+
if (requestedProfile !== undefined) return requestedProfile;
|
|
149
|
+
const fromEnv = process.env.DHARTA_PROFILE?.trim();
|
|
150
|
+
return fromEnv ? fromEnv : undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
40
153
|
export function configPath(): string {
|
|
41
154
|
const home = homeOverride ?? process.env.HOME ?? homedir();
|
|
42
155
|
return path.join(home, ".dharta", "config.toml");
|
|
@@ -109,17 +222,21 @@ export async function removeProfile(name: string): Promise<boolean> {
|
|
|
109
222
|
|
|
110
223
|
export async function activeProfile(): Promise<Profile> {
|
|
111
224
|
const cfg = await loadConfig();
|
|
225
|
+
const requested = requestedProfileName();
|
|
112
226
|
if (!cfg) {
|
|
113
227
|
throw new Error(
|
|
114
|
-
|
|
228
|
+
`Not logged in. Run \`dharta login${requested ? ` --profile ${requested}` : ""}\` ` +
|
|
229
|
+
"to store an API key in ~/.dharta/config.toml.",
|
|
115
230
|
);
|
|
116
231
|
}
|
|
117
|
-
const
|
|
232
|
+
const name = requested ?? cfg.default_profile;
|
|
233
|
+
const profile = cfg.profiles[name];
|
|
118
234
|
if (!profile) {
|
|
119
|
-
throw
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
235
|
+
throw requested
|
|
236
|
+
? profileNotFoundError(name)
|
|
237
|
+
: new CliUsageError(
|
|
238
|
+
`Default profile '${name}' not found in ${configPath()}. Run \`dharta login\` to recreate it.`,
|
|
239
|
+
);
|
|
123
240
|
}
|
|
124
241
|
return {
|
|
125
242
|
api_key: profile.api_key,
|
|
@@ -129,13 +246,28 @@ export async function activeProfile(): Promise<Profile> {
|
|
|
129
246
|
};
|
|
130
247
|
}
|
|
131
248
|
|
|
249
|
+
// One message for "you named a profile that is not stored", shared by
|
|
250
|
+
// activeProfile, `auth token`, and `auth logout` so the three agree on what
|
|
251
|
+
// happened and on the exit code (2, a usage error).
|
|
252
|
+
export function profileNotFoundError(name: string): CliUsageError {
|
|
253
|
+
return new CliUsageError(
|
|
254
|
+
`Profile '${name}' not found in ${configPath()}. ` +
|
|
255
|
+
`Run \`dharta login --profile ${name}\` to create it, or \`dharta auth status\` to list profiles.`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
132
259
|
// Base URL for commands that work without a credential (init's template
|
|
133
|
-
// fetch): env override, then the stored profile, then the default.
|
|
260
|
+
// fetch): env override, then the stored profile, then the default. An explicit
|
|
261
|
+
// --profile pins the host to that profile, the same rule buildContext applies.
|
|
134
262
|
export async function resolveBaseUrl(): Promise<string> {
|
|
135
|
-
|
|
263
|
+
const pinnedToProfile = explicitProfileName() !== undefined;
|
|
264
|
+
if (process.env.DHARTA_BASE_URL && !pinnedToProfile) return process.env.DHARTA_BASE_URL;
|
|
136
265
|
try {
|
|
137
266
|
return (await activeProfile()).base_url;
|
|
138
|
-
} catch {
|
|
267
|
+
} catch (err) {
|
|
268
|
+
// A profile the user named and that does not exist is their mistake, not a
|
|
269
|
+
// missing credential: say so instead of quietly using the default host.
|
|
270
|
+
if (pinnedToProfile) throw err;
|
|
139
271
|
return DEFAULT_BASE_URL;
|
|
140
272
|
}
|
|
141
273
|
}
|
package/src/context.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Dharta, HttpClient } from "@dharta/sdk";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
activeProfile,
|
|
4
|
+
explicitProfileName,
|
|
5
|
+
requestedIdempotencyKeyValue,
|
|
6
|
+
DEFAULT_BASE_URL,
|
|
7
|
+
} from "./config.js";
|
|
3
8
|
import { cliFormatError, cliFormatNetworkError } from "./api-error.js";
|
|
4
9
|
|
|
5
10
|
export interface CliContext {
|
|
@@ -13,6 +18,10 @@ export interface CliContext {
|
|
|
13
18
|
// `dharta files` can build a second HttpClient for the DATA-PLANE files host (a
|
|
14
19
|
// different origin than the control-plane base_url) while staying testable.
|
|
15
20
|
fetch?: typeof fetch;
|
|
21
|
+
// The run-wide `--idempotency-key`, already resolved. A command that builds a
|
|
22
|
+
// second control-plane client hands it over so the flag reaches that client's
|
|
23
|
+
// writes too; the two clients then behave identically.
|
|
24
|
+
idempotencyKey?: string;
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
// Allow tests to inject a stub fetch + base config without touching disk.
|
|
@@ -20,6 +29,9 @@ export interface CtxOverrides {
|
|
|
20
29
|
apiKey?: string;
|
|
21
30
|
baseUrl?: string;
|
|
22
31
|
fetch?: typeof fetch;
|
|
32
|
+
// Normally read off the global `--idempotency-key` flag. Tests that never go
|
|
33
|
+
// through the program's preAction hook pass it here.
|
|
34
|
+
idempotencyKey?: string;
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
export async function buildContext(
|
|
@@ -27,11 +39,17 @@ export async function buildContext(
|
|
|
27
39
|
): Promise<CliContext> {
|
|
28
40
|
let apiKey = over.apiKey;
|
|
29
41
|
let baseUrl = over.baseUrl;
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
// An explicit `--profile` outranks BOTH ambient settings: the key and the
|
|
43
|
+
// host are one credential. Without this, a shell that exports a key would
|
|
44
|
+
// silently send `--profile staging` at whatever org that key belongs to, and
|
|
45
|
+
// an exported DHARTA_BASE_URL would send the profile's key to a host it was
|
|
46
|
+
// never issued for. DHARTA_PROFILE does NOT get this treatment: two ambient
|
|
47
|
+
// settings, and the environment still wins.
|
|
48
|
+
const pinnedToProfile = explicitProfileName() !== undefined;
|
|
49
|
+
// Env overrides config everywhere (the GH_TOKEN pattern, RFC 0013 §2);
|
|
50
|
+
// explicit per-call overrides beat both.
|
|
51
|
+
if (!pinnedToProfile) baseUrl = baseUrl ?? process.env.DHARTA_BASE_URL;
|
|
52
|
+
if (!apiKey && process.env.DHARTA_API_KEY && !pinnedToProfile) {
|
|
35
53
|
apiKey = process.env.DHARTA_API_KEY;
|
|
36
54
|
}
|
|
37
55
|
if (!apiKey) {
|
|
@@ -41,12 +59,20 @@ export async function buildContext(
|
|
|
41
59
|
}
|
|
42
60
|
if (!baseUrl) baseUrl = DEFAULT_BASE_URL;
|
|
43
61
|
const fetchImpl = over.fetch;
|
|
62
|
+
// The global `--idempotency-key`, applied once here rather than threaded
|
|
63
|
+
// through every curated command. The client sends it on every request that
|
|
64
|
+
// does not name a key of its own, which is what makes a CI rerun after an
|
|
65
|
+
// ambiguous failure replay the first response instead of performing the
|
|
66
|
+
// mutation a second time. A command that writes more than once names a
|
|
67
|
+
// derived key per write, and those survive.
|
|
68
|
+
const idempotencyKey = over.idempotencyKey ?? requestedIdempotencyKeyValue();
|
|
44
69
|
const sdk = new Dharta({
|
|
45
70
|
apiKey,
|
|
46
71
|
baseUrl,
|
|
47
72
|
fetch: fetchImpl,
|
|
48
73
|
formatError: cliFormatError,
|
|
49
74
|
formatNetworkError: cliFormatNetworkError,
|
|
75
|
+
idempotencyKey,
|
|
50
76
|
});
|
|
51
77
|
const http = new HttpClient({
|
|
52
78
|
apiKey,
|
|
@@ -54,6 +80,7 @@ export async function buildContext(
|
|
|
54
80
|
fetch: fetchImpl,
|
|
55
81
|
formatError: cliFormatError,
|
|
56
82
|
formatNetworkError: cliFormatNetworkError,
|
|
83
|
+
idempotencyKey,
|
|
57
84
|
});
|
|
58
|
-
return { sdk, http, baseUrl, apiKey, fetch: fetchImpl };
|
|
85
|
+
return { sdk, http, baseUrl, apiKey, fetch: fetchImpl, idempotencyKey };
|
|
59
86
|
}
|