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