@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,547 @@
|
|
|
1
|
+
// Public model-connection CRUD for `dharta models auth`.
|
|
2
|
+
import type { CliContext } from "../context.js";
|
|
3
|
+
import type {
|
|
4
|
+
ModelConnectionApiFormat,
|
|
5
|
+
ModelConnection,
|
|
6
|
+
ModelConnectionOffers,
|
|
7
|
+
ModelConnectionOffersUpdateParams,
|
|
8
|
+
ModelConnectionProvider,
|
|
9
|
+
ModelConnectionRotateParams,
|
|
10
|
+
} from "@dharta/sdk";
|
|
11
|
+
import { fmtTs, table } from "../output.js";
|
|
12
|
+
import { normalizeStoredConnectorSlug } from "../model-settings.js";
|
|
13
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
14
|
+
import { closestChoice, normalizeHttpUrl, normalizeNonBlank, parsePositiveInteger } from "../validators.js";
|
|
15
|
+
import { MODEL_CONNECTION_PROVIDERS } from "../model-auth.js";
|
|
16
|
+
|
|
17
|
+
interface ListEnvelope<T> {
|
|
18
|
+
data: T[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ModelConnectionEnvelope {
|
|
22
|
+
model_connection: ModelConnection;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// At most this many models in one enabled set, matching the server.
|
|
26
|
+
const MAX_OFFER_MODELS = 256;
|
|
27
|
+
|
|
28
|
+
export const MODEL_API_FORMATS = [
|
|
29
|
+
"anthropic_messages",
|
|
30
|
+
"openai_chat_completions",
|
|
31
|
+
"openai_responses",
|
|
32
|
+
] as const satisfies readonly ModelConnectionApiFormat[];
|
|
33
|
+
export interface ModelConnectionCreateOptions {
|
|
34
|
+
key?: string;
|
|
35
|
+
keyStdin?: boolean;
|
|
36
|
+
slug?: string;
|
|
37
|
+
endpointUrl?: string;
|
|
38
|
+
apiFormat?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ModelConnectionRotateOptions {
|
|
42
|
+
key?: string;
|
|
43
|
+
keyStdin?: boolean;
|
|
44
|
+
name?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ModelConnectionModelsOptions {
|
|
48
|
+
enable?: string;
|
|
49
|
+
enableStdin?: boolean;
|
|
50
|
+
default?: string;
|
|
51
|
+
clearDefault?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function modelConnectionsListData(ctx: CliContext): Promise<ModelConnection[]> {
|
|
55
|
+
const res = await ctx.http.request<ListEnvelope<ModelConnection>>({ path: "/model_connections" });
|
|
56
|
+
return res.data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function modelConnectionsList(ctx: CliContext): Promise<string> {
|
|
60
|
+
const keys = await modelConnectionsListData(ctx);
|
|
61
|
+
if (keys.length === 0) return "No model connections. Store one with: dharta models auth create <provider> <name> --key-stdin";
|
|
62
|
+
return table(keys.map((key) => ({
|
|
63
|
+
id: String(key.id),
|
|
64
|
+
slug: key.slug ?? "",
|
|
65
|
+
provider: key.provider,
|
|
66
|
+
name: key.name,
|
|
67
|
+
tail: key.tail,
|
|
68
|
+
created: fmtTs(key.created_at),
|
|
69
|
+
revoked: fmtTs(key.revoked_at),
|
|
70
|
+
})));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function modelConnectionsCreateData(
|
|
74
|
+
ctx: CliContext,
|
|
75
|
+
provider: ModelConnectionProvider,
|
|
76
|
+
name: string,
|
|
77
|
+
opts: ModelConnectionCreateOptions = {},
|
|
78
|
+
): Promise<ModelConnection> {
|
|
79
|
+
validateModelConnectionsCreateOptions(provider, name, opts);
|
|
80
|
+
const normalizedProvider = normalizeProvider(provider);
|
|
81
|
+
const normalizedName = normalizeModelConnectionName(name);
|
|
82
|
+
const plaintext = await resolvePlaintext(opts);
|
|
83
|
+
const body: Record<string, string> = { provider: normalizedProvider, name: normalizedName, plaintext };
|
|
84
|
+
if (opts.slug !== undefined) body.slug = normalizeStoredConnectorSlug(opts.slug, "--slug");
|
|
85
|
+
if (opts.endpointUrl) body.endpoint_url = normalizeEndpointUrl(opts.endpointUrl);
|
|
86
|
+
if (opts.apiFormat) body.api_format = normalizeApiFormat(opts.apiFormat);
|
|
87
|
+
try {
|
|
88
|
+
const res = await ctx.http.request<ModelConnectionEnvelope>({
|
|
89
|
+
method: "POST",
|
|
90
|
+
path: "/model_connections",
|
|
91
|
+
body,
|
|
92
|
+
});
|
|
93
|
+
return res.model_connection;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw addModelConnectionsWriteScopeGuidance(error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function modelConnectionsCreate(
|
|
100
|
+
ctx: CliContext,
|
|
101
|
+
provider: ModelConnectionProvider,
|
|
102
|
+
name: string,
|
|
103
|
+
opts: ModelConnectionCreateOptions = {},
|
|
104
|
+
): Promise<string> {
|
|
105
|
+
const key = await modelConnectionsCreateData(ctx, provider, name, opts);
|
|
106
|
+
// RFC 0070 §4.2.5: a stored connector with no enabled offer row resolves
|
|
107
|
+
// nothing, so name the command that finishes the job.
|
|
108
|
+
return `Stored ${key.provider} connector ${connectorLabel(key)} (${key.id}).\n` +
|
|
109
|
+
`It serves no turns until you enable a model: ${enableModelsHint(connectorLabel(key))}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// One spelling of the enable-and-default command, so every place that has to
|
|
113
|
+
// point at it points at the same thing.
|
|
114
|
+
function enableModelsHint(slug: string): string {
|
|
115
|
+
return `dharta models auth models ${slug} --enable <model> --default <model>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Address the connector by its slug: that is what a model reference and a
|
|
119
|
+
// connector order use.
|
|
120
|
+
function connectorLabel(key: ModelConnection): string {
|
|
121
|
+
return key.slug;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function validateModelConnectionsCreateOptions(provider: ModelConnectionProvider, name: string, opts: ModelConnectionCreateOptions = {}): void {
|
|
125
|
+
normalizeProvider(provider);
|
|
126
|
+
normalizeModelConnectionName(name);
|
|
127
|
+
if (opts.slug !== undefined) {
|
|
128
|
+
normalizeStoredConnectorSlug(opts.slug, "--slug");
|
|
129
|
+
}
|
|
130
|
+
if (opts.key !== undefined && opts.keyStdin) {
|
|
131
|
+
throw new CliUsageError("Use either --key or --key-stdin, not both.");
|
|
132
|
+
}
|
|
133
|
+
if (opts.key !== undefined) {
|
|
134
|
+
normalizePlaintext(opts.key, "--key");
|
|
135
|
+
} else if (!opts.keyStdin) {
|
|
136
|
+
throw new CliUsageError("Model connection plaintext is required. Pass --key or --key-stdin.");
|
|
137
|
+
} else if (process.stdin.isTTY) {
|
|
138
|
+
throw new CliUsageError(
|
|
139
|
+
"No piped input. Usage: echo $PROVIDER_API_KEY | dharta models auth create anthropic Claude --key-stdin",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (opts.endpointUrl !== undefined) {
|
|
143
|
+
normalizeEndpointUrl(opts.endpointUrl);
|
|
144
|
+
}
|
|
145
|
+
if (opts.apiFormat !== undefined) {
|
|
146
|
+
normalizeApiFormat(opts.apiFormat);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeProvider(provider: ModelConnectionProvider): ModelConnectionProvider {
|
|
151
|
+
const normalized = normalizeNonBlank(provider, "Model connection provider");
|
|
152
|
+
if ((MODEL_CONNECTION_PROVIDERS as readonly string[]).includes(normalized)) {
|
|
153
|
+
return normalized as ModelConnectionProvider;
|
|
154
|
+
}
|
|
155
|
+
const suggestion = closestChoice(normalized, MODEL_CONNECTION_PROVIDERS);
|
|
156
|
+
throw new CliUsageError(
|
|
157
|
+
`Unknown model connection provider ${normalized}. Allowed providers: ${MODEL_CONNECTION_PROVIDERS.join(", ")}.` +
|
|
158
|
+
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeApiFormat(apiFormat: string): ModelConnectionApiFormat {
|
|
163
|
+
const normalized = normalizeNonBlank(apiFormat, "--api-format");
|
|
164
|
+
if ((MODEL_API_FORMATS as readonly string[]).includes(normalized)) {
|
|
165
|
+
return normalized as ModelConnectionApiFormat;
|
|
166
|
+
}
|
|
167
|
+
const suggestion = closestChoice(normalized, MODEL_API_FORMATS);
|
|
168
|
+
throw new CliUsageError(
|
|
169
|
+
`Unknown --api-format ${normalized}. Allowed API formats: ${MODEL_API_FORMATS.join(", ")}.` +
|
|
170
|
+
(suggestion ? ` Did you mean ${suggestion}?` : ""),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function prepareModelConnectionsCreateOptions(
|
|
175
|
+
provider: ModelConnectionProvider,
|
|
176
|
+
name: string,
|
|
177
|
+
opts: ModelConnectionCreateOptions = {},
|
|
178
|
+
read: () => Promise<string> = readModelConnectionStdin,
|
|
179
|
+
): Promise<ModelConnectionCreateOptions> {
|
|
180
|
+
validateModelConnectionsCreateOptions(provider, name, opts);
|
|
181
|
+
if (!opts.keyStdin) return opts;
|
|
182
|
+
return { ...opts, key: normalizeModelConnectionStdin(await read()), keyStdin: false };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function modelConnectionsRotateData(
|
|
186
|
+
ctx: CliContext,
|
|
187
|
+
reference: string,
|
|
188
|
+
opts: ModelConnectionRotateOptions,
|
|
189
|
+
): Promise<ModelConnection> {
|
|
190
|
+
const normalizedReference = normalizeModelConnectionReference(reference);
|
|
191
|
+
validateModelConnectionsRotateOptions(reference, opts);
|
|
192
|
+
const body: ModelConnectionRotateParams = { plaintext: await resolveRotatePlaintext(opts) };
|
|
193
|
+
if (opts.name !== undefined) body.name = normalizeModelConnectionName(opts.name);
|
|
194
|
+
try {
|
|
195
|
+
// Through the SDK resource, not a hand-built request: a new endpoint ships
|
|
196
|
+
// an @dharta/sdk method and the curated command calls it (RFC 0048 D2), so
|
|
197
|
+
// the path, the wire shape, and the idempotency header have one owner.
|
|
198
|
+
return await ctx.sdk.modelConnections.rotate(modelConnectionReferenceId(normalizedReference), body);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw addModelConnectionsWriteScopeGuidance(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function modelConnectionsRotate(
|
|
205
|
+
ctx: CliContext,
|
|
206
|
+
reference: string,
|
|
207
|
+
opts: ModelConnectionRotateOptions,
|
|
208
|
+
): Promise<string> {
|
|
209
|
+
const key = await modelConnectionsRotateData(ctx, reference, opts);
|
|
210
|
+
return `Rotated ${key.provider} connector ${connectorLabel(key)} (${key.id}).`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function validateModelConnectionsRotateOptions(reference: string, opts: ModelConnectionRotateOptions): void {
|
|
214
|
+
normalizeModelConnectionReference(reference);
|
|
215
|
+
if (opts.name !== undefined) normalizeModelConnectionName(opts.name);
|
|
216
|
+
if (opts.key !== undefined && opts.keyStdin) {
|
|
217
|
+
throw new CliUsageError("Use either --key or --key-stdin, not both.");
|
|
218
|
+
}
|
|
219
|
+
if (opts.key !== undefined) {
|
|
220
|
+
normalizePlaintext(opts.key, "--key");
|
|
221
|
+
} else if (!opts.keyStdin) {
|
|
222
|
+
throw new CliUsageError("Model connection plaintext is required. Pass --key or --key-stdin.");
|
|
223
|
+
} else if (process.stdin.isTTY) {
|
|
224
|
+
throw new CliUsageError(ROTATE_STDIN.tty);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Read the piped secret before the command runs so `--json` output stays a
|
|
229
|
+
// single payload, mirroring prepareModelConnectionsCreateOptions.
|
|
230
|
+
export async function prepareModelConnectionsRotateOptions(
|
|
231
|
+
reference: string,
|
|
232
|
+
opts: ModelConnectionRotateOptions,
|
|
233
|
+
read: () => Promise<string> = () => readPipedStdin(ROTATE_STDIN),
|
|
234
|
+
): Promise<ModelConnectionRotateOptions> {
|
|
235
|
+
validateModelConnectionsRotateOptions(reference, opts);
|
|
236
|
+
if (!opts.keyStdin) return opts;
|
|
237
|
+
return { ...opts, key: normalizePipedStdin(await read(), ROTATE_STDIN), keyStdin: false };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function modelConnectionsModelsData(ctx: CliContext, reference: string): Promise<ModelConnectionOffers> {
|
|
241
|
+
const normalizedReference = normalizeModelConnectionReference(reference);
|
|
242
|
+
return ctx.sdk.modelConnections.models(modelConnectionReferenceId(normalizedReference));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function modelConnectionsModels(ctx: CliContext, reference: string): Promise<string> {
|
|
246
|
+
return formatModelConnectionOffers(await modelConnectionsModelsData(ctx, reference));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function modelConnectionsConfigureModelsData(
|
|
250
|
+
ctx: CliContext,
|
|
251
|
+
reference: string,
|
|
252
|
+
opts: ModelConnectionModelsOptions,
|
|
253
|
+
): Promise<ModelConnectionOffers> {
|
|
254
|
+
const normalizedReference = normalizeModelConnectionReference(reference);
|
|
255
|
+
validateModelConnectionsModelsOptions(reference, opts);
|
|
256
|
+
const enabled = parseEnabledModels(await resolveEnabledModels(opts));
|
|
257
|
+
const body: ModelConnectionOffersUpdateParams = { enabled };
|
|
258
|
+
if (opts.clearDefault) body.default = null;
|
|
259
|
+
if (opts.default !== undefined) body.default = normalizeDefaultModel(opts.default, enabled);
|
|
260
|
+
try {
|
|
261
|
+
return await ctx.sdk.modelConnections.configureModels(modelConnectionReferenceId(normalizedReference), body);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
throw addModelConnectionsWriteScopeGuidance(error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function modelConnectionsConfigureModels(
|
|
268
|
+
ctx: CliContext,
|
|
269
|
+
reference: string,
|
|
270
|
+
opts: ModelConnectionModelsOptions,
|
|
271
|
+
): Promise<string> {
|
|
272
|
+
return formatModelConnectionOffers(await modelConnectionsConfigureModelsData(ctx, reference, opts));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// `dharta models auth models <id|slug>` reads the offers; the same command
|
|
276
|
+
// writes them once you pass an enabled set.
|
|
277
|
+
export function modelConnectionsModelsWritesOffers(opts: ModelConnectionModelsOptions): boolean {
|
|
278
|
+
return opts.enable !== undefined || opts.enableStdin === true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function validateModelConnectionsModelsOptions(reference: string, opts: ModelConnectionModelsOptions): void {
|
|
282
|
+
normalizeModelConnectionReference(reference);
|
|
283
|
+
if (opts.enable !== undefined && opts.enableStdin) {
|
|
284
|
+
throw new CliUsageError("Use either --enable or --enable-stdin, not both.");
|
|
285
|
+
}
|
|
286
|
+
if (opts.default !== undefined && opts.clearDefault) {
|
|
287
|
+
throw new CliUsageError("Use either --default or --clear-default, not both.");
|
|
288
|
+
}
|
|
289
|
+
// A write replaces the whole enabled set, so a default-only call would
|
|
290
|
+
// disable every model. Make the caller state the set it wants.
|
|
291
|
+
if (!modelConnectionsModelsWritesOffers(opts)) {
|
|
292
|
+
if (opts.default !== undefined || opts.clearDefault) {
|
|
293
|
+
throw new CliUsageError(
|
|
294
|
+
"--default and --clear-default replace the enabled model set, so pass --enable <models> or --enable-stdin with them.",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (opts.enable !== undefined) parseEnabledModels(opts.enable);
|
|
300
|
+
if (opts.enableStdin && process.stdin.isTTY) {
|
|
301
|
+
throw new CliUsageError(ENABLE_STDIN.tty);
|
|
302
|
+
}
|
|
303
|
+
if (opts.default !== undefined && opts.enable !== undefined) {
|
|
304
|
+
normalizeDefaultModel(opts.default, parseEnabledModels(opts.enable));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Read the piped model list before the command runs so `--json` output stays
|
|
309
|
+
// a single payload.
|
|
310
|
+
export async function prepareModelConnectionsModelsOptions(
|
|
311
|
+
reference: string,
|
|
312
|
+
opts: ModelConnectionModelsOptions,
|
|
313
|
+
read: () => Promise<string> = () => readPipedStdin(ENABLE_STDIN),
|
|
314
|
+
): Promise<ModelConnectionModelsOptions> {
|
|
315
|
+
validateModelConnectionsModelsOptions(reference, opts);
|
|
316
|
+
if (!opts.enableStdin) return opts;
|
|
317
|
+
const piped = normalizePipedStdin(await read(), ENABLE_STDIN);
|
|
318
|
+
const prepared = { ...opts, enable: piped, enableStdin: false };
|
|
319
|
+
validateModelConnectionsModelsOptions(reference, prepared);
|
|
320
|
+
return prepared;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Revoking returns 204 with no body, so the payload reports what you asked to
|
|
324
|
+
// revoke: the numeric id for an all-digit argument, the slug otherwise.
|
|
325
|
+
export async function modelConnectionsRevokeData(
|
|
326
|
+
ctx: CliContext,
|
|
327
|
+
reference: string,
|
|
328
|
+
): Promise<{ id: number | string; revoked: boolean }> {
|
|
329
|
+
const normalizedReference = normalizeModelConnectionReference(reference);
|
|
330
|
+
try {
|
|
331
|
+
await ctx.http.request<void>({
|
|
332
|
+
method: "DELETE",
|
|
333
|
+
path: `/model_connections/${encodeURIComponent(normalizedReference)}`,
|
|
334
|
+
});
|
|
335
|
+
} catch (error) {
|
|
336
|
+
throw addModelConnectionsWriteScopeGuidance(error);
|
|
337
|
+
}
|
|
338
|
+
return { id: modelConnectionReferenceId(normalizedReference), revoked: true };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function modelConnectionsRevoke(ctx: CliContext, reference: string): Promise<string> {
|
|
342
|
+
const res = await modelConnectionsRevokeData(ctx, reference);
|
|
343
|
+
return `Revoked ${res.id}.`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function validateModelConnectionReference(reference: string): void {
|
|
347
|
+
normalizeModelConnectionReference(reference);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const CREATE_STDIN: StdinPrompt = {
|
|
351
|
+
tty: "No piped input. Usage: echo $PROVIDER_API_KEY | dharta models auth create anthropic Claude --key-stdin",
|
|
352
|
+
empty: "No model connection on stdin. Usage: echo $PROVIDER_API_KEY | dharta models auth create anthropic Claude --key-stdin",
|
|
353
|
+
};
|
|
354
|
+
const ROTATE_STDIN: StdinPrompt = {
|
|
355
|
+
tty: "No piped input. Usage: echo $PROVIDER_API_KEY | dharta models auth rotate openai-primary --key-stdin",
|
|
356
|
+
empty: "No model connection on stdin. Usage: echo $PROVIDER_API_KEY | dharta models auth rotate openai-primary --key-stdin",
|
|
357
|
+
};
|
|
358
|
+
const ENABLE_STDIN: StdinPrompt = {
|
|
359
|
+
tty: "No piped input. Usage: dharta models auth models openai-primary --enable-stdin < models.txt",
|
|
360
|
+
empty: "No model ids on stdin. Usage: dharta models auth models openai-primary --enable-stdin < models.txt",
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
function formatModelConnectionOffers(offers: ModelConnectionOffers): string {
|
|
364
|
+
const head = `Connector: ${offers.connector.slug} (${offers.connector.provider}, ${offers.connector.id})`;
|
|
365
|
+
const hint = enableModelsHint(offers.connector.slug);
|
|
366
|
+
if (offers.models.length === 0) {
|
|
367
|
+
return `${head}\n\nNo models. Enable some with: ${hint}`;
|
|
368
|
+
}
|
|
369
|
+
const rows = table(offers.models.map((offer) => ({
|
|
370
|
+
model: offer.model_id,
|
|
371
|
+
enabled: offer.enabled ? "yes" : "no",
|
|
372
|
+
default: offer.default ? "yes" : "",
|
|
373
|
+
})));
|
|
374
|
+
// A full catalog with nothing enabled reads as healthy but resolves nothing,
|
|
375
|
+
// so say it rather than leaving it to be read off the enabled column.
|
|
376
|
+
if (offers.models.every((offer) => !offer.enabled)) {
|
|
377
|
+
return `${head}\n\n${rows}\n\nNo models are enabled, so this connector serves no turns. Enable one with: ${hint}`;
|
|
378
|
+
}
|
|
379
|
+
return `${head}\n\n${rows}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Accept the numeric connector id or its organization-unique slug; both
|
|
383
|
+
// address the same connector on every /model_connections path. An all-digit argument
|
|
384
|
+
// is always an id, never a slug, so there is nothing to disambiguate: the
|
|
385
|
+
// console never mints an all-digit slug.
|
|
386
|
+
function normalizeModelConnectionReference(reference: string): string {
|
|
387
|
+
const normalized = normalizeNonBlank(reference, "Model connection id");
|
|
388
|
+
if (/^\d+$/.test(normalized)) return String(parseModelConnectionId(normalized));
|
|
389
|
+
try {
|
|
390
|
+
return normalizeStoredConnectorSlug(normalized, "Model connection slug");
|
|
391
|
+
} catch (error) {
|
|
392
|
+
// The argument is neither shape, so name both rather than only the one
|
|
393
|
+
// the slug validator happened to check. CliUsageError, because the numeric
|
|
394
|
+
// branch above already exits 2 through parsePositiveInteger and the same
|
|
395
|
+
// argument must not exit differently depending on which shape it missed.
|
|
396
|
+
throw new CliUsageError(
|
|
397
|
+
`${error instanceof Error ? error.message : String(error)} ` +
|
|
398
|
+
"Pass the numeric connector id or its slug from `dharta models auth list`.",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function modelConnectionReferenceId(normalizedReference: string): number | string {
|
|
404
|
+
return /^\d+$/.test(normalizedReference) ? Number(normalizedReference) : normalizedReference;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// An empty value is a deliberate empty set: `--enable ""` disables every
|
|
408
|
+
// model on the connector. It is not the same as omitting --enable, which
|
|
409
|
+
// makes the command a read.
|
|
410
|
+
function parseEnabledModels(value: string): string[] {
|
|
411
|
+
const models = value.split(/[\s,]+/).map((part) => part.trim()).filter(Boolean);
|
|
412
|
+
if (models.length > MAX_OFFER_MODELS) {
|
|
413
|
+
throw new CliUsageError(`--enable accepts at most ${MAX_OFFER_MODELS} models.`);
|
|
414
|
+
}
|
|
415
|
+
const seen = new Set<string>();
|
|
416
|
+
for (const model of models) {
|
|
417
|
+
if (seen.has(model)) throw new CliUsageError(`--enable lists ${model} more than once.`);
|
|
418
|
+
seen.add(model);
|
|
419
|
+
}
|
|
420
|
+
return models;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function normalizeDefaultModel(value: string, enabled: string[]): string {
|
|
424
|
+
const normalized = normalizeNonBlank(value, "--default");
|
|
425
|
+
if (enabled.length === 0) {
|
|
426
|
+
throw new CliUsageError(
|
|
427
|
+
"--default cannot be set when --enable is empty. Pass --clear-default to leave the connector with no default.",
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (!enabled.includes(normalized)) {
|
|
431
|
+
throw new CliUsageError(`--default ${normalized} is not in the enabled set. Add it to --enable, or pick one of: ${enabled.join(", ")}.`);
|
|
432
|
+
}
|
|
433
|
+
return normalized;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function resolveRotatePlaintext(opts: ModelConnectionRotateOptions): Promise<string> {
|
|
437
|
+
if (opts.key !== undefined && opts.keyStdin) {
|
|
438
|
+
throw new CliUsageError("Use either --key or --key-stdin, not both.");
|
|
439
|
+
}
|
|
440
|
+
if (opts.key !== undefined) return normalizePlaintext(opts.key, "--key");
|
|
441
|
+
if (opts.keyStdin) return readPipedStdin(ROTATE_STDIN);
|
|
442
|
+
throw new CliUsageError("Model connection plaintext is required. Pass --key or --key-stdin.");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function resolveEnabledModels(opts: ModelConnectionModelsOptions): Promise<string> {
|
|
446
|
+
if (opts.enable !== undefined && opts.enableStdin) {
|
|
447
|
+
throw new CliUsageError("Use either --enable or --enable-stdin, not both.");
|
|
448
|
+
}
|
|
449
|
+
if (opts.enable !== undefined) return opts.enable;
|
|
450
|
+
if (opts.enableStdin) return readPipedStdin(ENABLE_STDIN);
|
|
451
|
+
throw new CliUsageError("Pass --enable <models> or --enable-stdin to change the enabled model set.");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function resolvePlaintext(opts: ModelConnectionCreateOptions): Promise<string> {
|
|
455
|
+
if (opts.key !== undefined && opts.keyStdin) {
|
|
456
|
+
throw new CliUsageError("Use either --key or --key-stdin, not both.");
|
|
457
|
+
}
|
|
458
|
+
if (opts.key !== undefined) return normalizePlaintext(opts.key, "--key");
|
|
459
|
+
if (opts.keyStdin) {
|
|
460
|
+
return readModelConnectionStdin();
|
|
461
|
+
}
|
|
462
|
+
throw new CliUsageError("Model connection plaintext is required. Pass --key or --key-stdin.");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// A piped input can fail two ways: nothing is piped at all (a TTY, which
|
|
466
|
+
// would otherwise hang), or the pipe is empty. Each says what to run.
|
|
467
|
+
interface StdinPrompt {
|
|
468
|
+
tty: string;
|
|
469
|
+
empty: string;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function readModelConnectionStdin(): Promise<string> {
|
|
473
|
+
return readPipedStdin(CREATE_STDIN);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function normalizeModelConnectionStdin(value: string): string {
|
|
477
|
+
return normalizePipedStdin(value, CREATE_STDIN);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function readPipedStdin(prompt: StdinPrompt): Promise<string> {
|
|
481
|
+
if (process.stdin.isTTY) throw new CliUsageError(prompt.tty);
|
|
482
|
+
const chunks: Buffer[] = [];
|
|
483
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
484
|
+
return normalizePipedStdin(Buffer.concat(chunks).toString("utf8"), prompt);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function normalizePipedStdin(value: string, prompt: StdinPrompt): string {
|
|
488
|
+
const text = value.trim();
|
|
489
|
+
if (text) return text;
|
|
490
|
+
throw new CliUsageError(prompt.empty);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function normalizeModelConnectionName(name: string): string {
|
|
494
|
+
const normalized = name.trim();
|
|
495
|
+
if (!normalized) {
|
|
496
|
+
throw new CliUsageError("Model connection name cannot be blank.");
|
|
497
|
+
}
|
|
498
|
+
return normalized;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function parseModelConnectionId(id: string): number {
|
|
502
|
+
return parsePositiveInteger(id, "Model connection id");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function normalizePlaintext(value: string, flag: string): string {
|
|
506
|
+
const normalized = value.trim();
|
|
507
|
+
if (!normalized) {
|
|
508
|
+
throw new CliUsageError(`${flag} cannot be blank.`);
|
|
509
|
+
}
|
|
510
|
+
return normalized;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function normalizeEndpointUrl(value: string): string {
|
|
514
|
+
const normalized = normalizeHttpUrl(value, "--endpoint-url");
|
|
515
|
+
const parsed = new URL(normalized);
|
|
516
|
+
if (parsed.protocol !== "https:") {
|
|
517
|
+
throw new CliUsageError("--endpoint-url must use https.");
|
|
518
|
+
}
|
|
519
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
520
|
+
throw new CliUsageError("--endpoint-url cannot include credentials, a query string, or a fragment.");
|
|
521
|
+
}
|
|
522
|
+
return normalized;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Storing or revoking a connector needs `model_connections:write`, a tenant-admin
|
|
526
|
+
// scope that the `dharta login` device-flow credential deliberately never
|
|
527
|
+
// carries (that key is long-lived and non-admin, so it can't hold a
|
|
528
|
+
// forced-expiry admin scope). A CLI login therefore always 403s here. The
|
|
529
|
+
// generic scope hint says "manage key scopes", but the login key's scopes are
|
|
530
|
+
// fixed — you cannot add the scope to it — so name the real path: store the
|
|
531
|
+
// connector in the dashboard, or mint a bounded key and re-auth with a token.
|
|
532
|
+
function addModelConnectionsWriteScopeGuidance(error: unknown): Error {
|
|
533
|
+
if (!(error instanceof Error)) return new Error(String(error));
|
|
534
|
+
const status = (error as Error & { status?: unknown }).status;
|
|
535
|
+
if (
|
|
536
|
+
status === 403 &&
|
|
537
|
+
/model_connections:write/.test(error.message) &&
|
|
538
|
+
!error.message.includes("dharta login --with-token")
|
|
539
|
+
) {
|
|
540
|
+
error.message +=
|
|
541
|
+
`${error.message.endsWith(".") ? "" : "."} ` +
|
|
542
|
+
"The `dharta login` credential cannot hold model_connections:write. Store the connector from the dashboard " +
|
|
543
|
+
"(`dharta open model-connections`), or mint a bounded key with model_connections:write there (`dharta open api-keys`) " +
|
|
544
|
+
"and re-authenticate with `dharta login --with-token`.";
|
|
545
|
+
}
|
|
546
|
+
return error;
|
|
547
|
+
}
|
package/src/commands/open.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { CliContext } from "../context.js";
|
|
6
6
|
import { openBrowser } from "../browser.js";
|
|
7
7
|
import { inferAgentSlug, type AgentInferOptions } from "../agent.js";
|
|
8
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
8
9
|
|
|
9
10
|
export interface OpenOptions extends AgentInferOptions {
|
|
10
11
|
openBrowserImpl?: (url: string) => Promise<boolean>;
|
|
@@ -19,7 +20,7 @@ interface ProjectShow {
|
|
|
19
20
|
|
|
20
21
|
export interface OpenData {
|
|
21
22
|
slug?: string;
|
|
22
|
-
target: "agent" | "console" | "billing" | "security" | "api-keys" | "model-
|
|
23
|
+
target: "agent" | "console" | "billing" | "security" | "api-keys" | "model-connections" | "webhooks" | "connected-accounts";
|
|
23
24
|
url: string;
|
|
24
25
|
// null until the first deploy; the agent target falls back to the
|
|
25
26
|
// console rather than opening a dead chat page.
|
|
@@ -31,7 +32,7 @@ const HANDOFF_TARGETS = {
|
|
|
31
32
|
billing: "/billing",
|
|
32
33
|
security: "/account/security",
|
|
33
34
|
"api-keys": "/api_keys",
|
|
34
|
-
"model-
|
|
35
|
+
"model-connections": "/model_connections",
|
|
35
36
|
webhooks: "/webhook_endpoints",
|
|
36
37
|
"connected-accounts": "/account/connected_accounts",
|
|
37
38
|
} as const;
|
|
@@ -50,11 +51,11 @@ export function normalizeOpenTarget(target: string | undefined): string | undefi
|
|
|
50
51
|
export function validateOpenTarget(target: string | undefined): void {
|
|
51
52
|
const normalizedTarget = normalizeOpenTarget(target);
|
|
52
53
|
if (target !== undefined && normalizedTarget === "") {
|
|
53
|
-
throw new
|
|
54
|
+
throw new CliUsageError("target cannot be blank.");
|
|
54
55
|
}
|
|
55
56
|
if (normalizedTarget !== undefined && normalizedTarget !== "console" && !isHandoffTarget(normalizedTarget)) {
|
|
56
57
|
const suggestion = closestTarget(normalizedTarget);
|
|
57
|
-
throw new
|
|
58
|
+
throw new CliUsageError(
|
|
58
59
|
`Unknown target '${normalizedTarget}'. Use one of: ${OPEN_TARGETS_HELP}.` +
|
|
59
60
|
(suggestion ? ` Did you mean '${suggestion}'?` : ""),
|
|
60
61
|
);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CliContext } from "../context.js";
|
|
2
|
+
import type {
|
|
3
|
+
OrganizationRenameResult,
|
|
4
|
+
SupportAccessResult,
|
|
5
|
+
TranscriptAccessResult,
|
|
6
|
+
} from "@dharta/sdk";
|
|
7
|
+
import { normalizeNonBlank } from "../validators.js";
|
|
8
|
+
import { CliUsageError } from "../exit-codes.js";
|
|
9
|
+
|
|
10
|
+
// Mirrors Organization's name validation server-side, so a name that cannot
|
|
11
|
+
// land is rejected before it costs a round trip.
|
|
12
|
+
const MAX_ORGANIZATION_NAME_LENGTH = 200;
|
|
13
|
+
|
|
14
|
+
export async function orgRenameData(ctx: CliContext, name: string): Promise<OrganizationRenameResult> {
|
|
15
|
+
return ctx.sdk.organization.rename(normalizeOrganizationName(name));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function orgRename(ctx: CliContext, name: string): Promise<string> {
|
|
19
|
+
const res = await orgRenameData(ctx, name);
|
|
20
|
+
if (!res.changed) return `${res.organization.name} is already the organization name.`;
|
|
21
|
+
return `Renamed the organization to ${res.organization.name}. The slug stays ${res.organization.slug}.`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function orgSupportAccessData(ctx: CliContext, allowed: boolean): Promise<SupportAccessResult> {
|
|
25
|
+
return ctx.sdk.organization.setSupportAccess(allowed);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function orgSupportAccess(ctx: CliContext, allowed: boolean): Promise<string> {
|
|
29
|
+
const res = await orgSupportAccessData(ctx, allowed);
|
|
30
|
+
const state = res.support_access.allowed
|
|
31
|
+
? "Dharta support may access your session data for support tickets (always audited)"
|
|
32
|
+
: "Dharta support is blocked from accessing your session data";
|
|
33
|
+
return `${state}${res.changed ? "" : " (unchanged)"}.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function orgTranscriptAccessData(
|
|
37
|
+
ctx: CliContext,
|
|
38
|
+
enabled: boolean,
|
|
39
|
+
): Promise<TranscriptAccessResult> {
|
|
40
|
+
return ctx.sdk.organization.setTranscriptAccess(enabled);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function orgTranscriptAccess(ctx: CliContext, enabled: boolean): Promise<string> {
|
|
44
|
+
const res = await orgTranscriptAccessData(ctx, enabled);
|
|
45
|
+
const state = res.transcript_access.enabled
|
|
46
|
+
? "API keys may now read end-user transcripts (dharta sessions show)"
|
|
47
|
+
: "API transcript access disabled. Session metadata stays readable";
|
|
48
|
+
return `${state}${res.changed ? "" : " (unchanged)"}.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function normalizeOrganizationName(name: string): string {
|
|
52
|
+
const normalized = normalizeNonBlank(name, "Organization name");
|
|
53
|
+
if ([...normalized].length > MAX_ORGANIZATION_NAME_LENGTH) {
|
|
54
|
+
throw new CliUsageError(`Organization name cannot exceed ${MAX_ORGANIZATION_NAME_LENGTH} characters.`);
|
|
55
|
+
}
|
|
56
|
+
return normalized;
|
|
57
|
+
}
|