@dharta/cli 0.6.77
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 +21 -0
- package/README.md +167 -0
- package/dist/agent.d.ts +17 -0
- package/dist/agent.js +134 -0
- package/dist/agent.js.map +1 -0
- package/dist/api-error.d.ts +3 -0
- package/dist/api-error.js +80 -0
- package/dist/api-error.js.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +19 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +1978 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.d.ts +131 -0
- package/dist/commands/agents.js +439 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/analytics.d.ts +25 -0
- package/dist/commands/analytics.js +166 -0
- package/dist/commands/analytics.js.map +1 -0
- package/dist/commands/api.d.ts +19 -0
- package/dist/commands/api.js +278 -0
- package/dist/commands/api.js.map +1 -0
- package/dist/commands/appearance.d.ts +19 -0
- package/dist/commands/appearance.js +63 -0
- package/dist/commands/appearance.js.map +1 -0
- package/dist/commands/artifact.d.ts +33 -0
- package/dist/commands/artifact.js +235 -0
- package/dist/commands/artifact.js.map +1 -0
- package/dist/commands/audit-log.d.ts +32 -0
- package/dist/commands/audit-log.js +102 -0
- package/dist/commands/audit-log.js.map +1 -0
- package/dist/commands/auth.d.ts +47 -0
- package/dist/commands/auth.js +117 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/caps.d.ts +82 -0
- package/dist/commands/caps.js +213 -0
- package/dist/commands/caps.js.map +1 -0
- package/dist/commands/create.d.ts +22 -0
- package/dist/commands/create.js +76 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/deploy.d.ts +45 -0
- package/dist/commands/deploy.js +892 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +33 -0
- package/dist/commands/dev.js +655 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/directories.d.ts +60 -0
- package/dist/commands/directories.js +83 -0
- package/dist/commands/directories.js.map +1 -0
- package/dist/commands/doctor.d.ts +13 -0
- package/dist/commands/doctor.js +123 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/embed-keys.d.ts +61 -0
- package/dist/commands/embed-keys.js +165 -0
- package/dist/commands/embed-keys.js.map +1 -0
- package/dist/commands/env.d.ts +59 -0
- package/dist/commands/env.js +300 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/files.d.ts +14 -0
- package/dist/commands/files.js +84 -0
- package/dist/commands/files.js.map +1 -0
- package/dist/commands/git-credential.d.ts +1 -0
- package/dist/commands/git-credential.js +70 -0
- package/dist/commands/git-credential.js.map +1 -0
- package/dist/commands/kartas.d.ts +101 -0
- package/dist/commands/kartas.js +289 -0
- package/dist/commands/kartas.js.map +1 -0
- package/dist/commands/keys.d.ts +33 -0
- package/dist/commands/keys.js +141 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/login.d.ts +22 -0
- package/dist/commands/login.js +78 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logs.d.ts +14 -0
- package/dist/commands/logs.js +89 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/members.d.ts +32 -0
- package/dist/commands/members.js +77 -0
- package/dist/commands/members.js.map +1 -0
- package/dist/commands/model-keys.d.ts +22 -0
- package/dist/commands/model-keys.js +222 -0
- package/dist/commands/model-keys.js.map +1 -0
- package/dist/commands/open.d.ts +29 -0
- package/dist/commands/open.js +119 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/parity.d.ts +15 -0
- package/dist/commands/parity.js +73 -0
- package/dist/commands/parity.js.map +1 -0
- package/dist/commands/rollback.d.ts +21 -0
- package/dist/commands/rollback.js +26 -0
- package/dist/commands/rollback.js.map +1 -0
- package/dist/commands/schedules.d.ts +52 -0
- package/dist/commands/schedules.js +231 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/scopes.d.ts +3 -0
- package/dist/commands/scopes.js +21 -0
- package/dist/commands/scopes.js.map +1 -0
- package/dist/commands/sessions.d.ts +53 -0
- package/dist/commands/sessions.js +142 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/setup.d.ts +28 -0
- package/dist/commands/setup.js +949 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +19 -0
- package/dist/commands/status.js +63 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.js +120 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/usage.d.ts +3 -0
- package/dist/commands/usage.js +37 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/webhooks.d.ts +47 -0
- package/dist/commands/webhooks.js +156 -0
- package/dist/commands/webhooks.js.map +1 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/completions.d.ts +13 -0
- package/dist/completions.js +120 -0
- package/dist/completions.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +98 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +14 -0
- package/dist/context.js +38 -0
- package/dist/context.js.map +1 -0
- package/dist/device-flow.d.ts +9 -0
- package/dist/device-flow.js +127 -0
- package/dist/device-flow.js.map +1 -0
- package/dist/harness-preflight.d.ts +24 -0
- package/dist/harness-preflight.js +252 -0
- package/dist/harness-preflight.js.map +1 -0
- package/dist/harnesses.d.ts +11 -0
- package/dist/harnesses.js +112 -0
- package/dist/harnesses.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/login-offer.d.ts +9 -0
- package/dist/login-offer.js +36 -0
- package/dist/login-offer.js.map +1 -0
- package/dist/manifest.d.ts +15 -0
- package/dist/manifest.js +142 -0
- package/dist/manifest.js.map +1 -0
- package/dist/money.d.ts +1 -0
- package/dist/money.js +16 -0
- package/dist/money.js.map +1 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.js +20 -0
- package/dist/output.js.map +1 -0
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.js +95 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scaffold.d.ts +16 -0
- package/dist/scaffold.js +74 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/setup-scan.d.ts +21 -0
- package/dist/setup-scan.js +127 -0
- package/dist/setup-scan.js.map +1 -0
- package/dist/templates.d.ts +14 -0
- package/dist/templates.js +65 -0
- package/dist/templates.js.map +1 -0
- package/dist/validators.d.ts +10 -0
- package/dist/validators.js +116 -0
- package/dist/validators.js.map +1 -0
- package/dist/version-check.d.ts +25 -0
- package/dist/version-check.js +68 -0
- package/dist/version-check.js.map +1 -0
- package/node_modules/@dharta/sdk/LICENSE +21 -0
- package/node_modules/@dharta/sdk/README.md +87 -0
- package/node_modules/@dharta/sdk/dist/errors.d.ts +20 -0
- package/node_modules/@dharta/sdk/dist/errors.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/errors.js +51 -0
- package/node_modules/@dharta/sdk/dist/errors.js.map +1 -0
- package/node_modules/@dharta/sdk/dist/http.d.ts +26 -0
- package/node_modules/@dharta/sdk/dist/http.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/http.js +136 -0
- package/node_modules/@dharta/sdk/dist/http.js.map +1 -0
- package/node_modules/@dharta/sdk/dist/index.d.ts +15 -0
- package/node_modules/@dharta/sdk/dist/index.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/index.js +21 -0
- package/node_modules/@dharta/sdk/dist/index.js.map +1 -0
- package/node_modules/@dharta/sdk/dist/resources.d.ts +48 -0
- package/node_modules/@dharta/sdk/dist/resources.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/resources.js +150 -0
- package/node_modules/@dharta/sdk/dist/resources.js.map +1 -0
- package/node_modules/@dharta/sdk/dist/types.d.ts +94 -0
- package/node_modules/@dharta/sdk/dist/types.d.ts.map +1 -0
- package/node_modules/@dharta/sdk/dist/types.js +59 -0
- package/node_modules/@dharta/sdk/dist/types.js.map +1 -0
- package/node_modules/@dharta/sdk/package.json +32 -0
- package/node_modules/@dharta/sdk/src/errors.ts +58 -0
- package/node_modules/@dharta/sdk/src/http.ts +166 -0
- package/node_modules/@dharta/sdk/src/index.ts +36 -0
- package/node_modules/@dharta/sdk/src/resources.ts +197 -0
- package/node_modules/@dharta/sdk/src/types.ts +179 -0
- package/package.json +38 -0
- package/src/agent.ts +147 -0
- package/src/api-error.ts +88 -0
- package/src/browser.ts +20 -0
- package/src/cli.ts +2339 -0
- package/src/commands/agents.ts +635 -0
- package/src/commands/analytics.ts +181 -0
- package/src/commands/api.ts +297 -0
- package/src/commands/appearance.ts +79 -0
- package/src/commands/artifact.ts +331 -0
- package/src/commands/audit-log.ts +125 -0
- package/src/commands/auth.ts +186 -0
- package/src/commands/caps.ts +302 -0
- package/src/commands/create.ts +114 -0
- package/src/commands/deploy.ts +1122 -0
- package/src/commands/dev.ts +792 -0
- package/src/commands/directories.ts +136 -0
- package/src/commands/doctor.ts +138 -0
- package/src/commands/embed-keys.ts +237 -0
- package/src/commands/env.ts +369 -0
- package/src/commands/files.ts +124 -0
- package/src/commands/git-credential.ts +78 -0
- package/src/commands/kartas.ts +439 -0
- package/src/commands/keys.ts +206 -0
- package/src/commands/login.ts +115 -0
- package/src/commands/logs.ts +111 -0
- package/src/commands/members.ts +109 -0
- package/src/commands/model-keys.ts +278 -0
- package/src/commands/open.ts +173 -0
- package/src/commands/parity.ts +96 -0
- package/src/commands/rollback.ts +45 -0
- package/src/commands/schedules.ts +290 -0
- package/src/commands/scopes.ts +22 -0
- package/src/commands/sessions.ts +225 -0
- package/src/commands/setup.ts +1176 -0
- package/src/commands/status.ts +94 -0
- package/src/commands/update.ts +157 -0
- package/src/commands/usage.ts +41 -0
- package/src/commands/webhooks.ts +200 -0
- package/src/commands/whoami.ts +24 -0
- package/src/completions.ts +168 -0
- package/src/config.ts +143 -0
- package/src/context.ts +59 -0
- package/src/device-flow.ts +174 -0
- package/src/harness-preflight.ts +293 -0
- package/src/harnesses.ts +117 -0
- package/src/index.ts +3 -0
- package/src/login-offer.ts +49 -0
- package/src/manifest.ts +153 -0
- package/src/money.ts +15 -0
- package/src/output.ts +22 -0
- package/src/runtime.ts +127 -0
- package/src/scaffold.ts +102 -0
- package/src/setup-scan.ts +154 -0
- package/src/templates.ts +85 -0
- package/src/validators.ts +124 -0
- package/src/version-check.ts +75 -0
- package/templates/blank/CLAUDE.md +21 -0
- package/templates/blank/dharta.toml +11 -0
- package/templates/faq-agent/CLAUDE.md +51 -0
- package/templates/faq-agent/dharta.toml +11 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { inferAgentSlug, type AgentInferOptions } from "../agent.js";
|
|
2
|
+
import type { CliContext } from "../context.js";
|
|
3
|
+
import { fmtTs, table } from "../output.js";
|
|
4
|
+
import { parsePositiveInteger } from "../validators.js";
|
|
5
|
+
|
|
6
|
+
interface ListEnvelope<T> {
|
|
7
|
+
data: T[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DirectoryInstance {
|
|
11
|
+
id: string;
|
|
12
|
+
scoped_id: string;
|
|
13
|
+
agent_slug: string | null;
|
|
14
|
+
seats_count: number;
|
|
15
|
+
sessions_count: number;
|
|
16
|
+
last_at: string | null;
|
|
17
|
+
tokens: number;
|
|
18
|
+
cpu_seconds: number | null;
|
|
19
|
+
disk_bytes: number | null;
|
|
20
|
+
spend_micros: number;
|
|
21
|
+
cap_cents: number | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DirectoryEndUser {
|
|
25
|
+
id: string;
|
|
26
|
+
agents_count: number;
|
|
27
|
+
kartas_count: number;
|
|
28
|
+
sessions_count: number;
|
|
29
|
+
first_seen: string | null;
|
|
30
|
+
last_seen: string | null;
|
|
31
|
+
tokens: number;
|
|
32
|
+
spend_micros: number;
|
|
33
|
+
cap_cents: number | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DirectorySession {
|
|
37
|
+
id: string;
|
|
38
|
+
agent_slug: string | null;
|
|
39
|
+
karta_id: string | null;
|
|
40
|
+
users_count: number;
|
|
41
|
+
models_count: number;
|
|
42
|
+
model: string | null;
|
|
43
|
+
started_at: string | null;
|
|
44
|
+
last_at: string | null;
|
|
45
|
+
turns: number;
|
|
46
|
+
input_tokens: number;
|
|
47
|
+
output_tokens: number;
|
|
48
|
+
tokens: number;
|
|
49
|
+
cost_micros: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DirectoryOptions extends AgentInferOptions {
|
|
53
|
+
limit?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function directoryInstancesData(ctx: CliContext, opts: DirectoryOptions): Promise<{ slug: string; data: DirectoryInstance[] }> {
|
|
57
|
+
validateDirectoryOptions(opts);
|
|
58
|
+
const slug = await inferAgentSlug(opts);
|
|
59
|
+
const res = await ctx.http.request<ListEnvelope<DirectoryInstance>>({
|
|
60
|
+
path: directoryPath(slug, "instances", opts),
|
|
61
|
+
});
|
|
62
|
+
return { slug, data: res.data };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function directoryInstances(ctx: CliContext, opts: DirectoryOptions): Promise<string> {
|
|
66
|
+
const { slug, data } = await directoryInstancesData(ctx, opts);
|
|
67
|
+
if (data.length === 0) return `No kartas for ${slug}. Generate activity with: dharta send "hello" --agent ${slug}`;
|
|
68
|
+
return table(data.map((row) => ({
|
|
69
|
+
id: row.id,
|
|
70
|
+
sessions: String(row.sessions_count),
|
|
71
|
+
users: String(row.seats_count),
|
|
72
|
+
tokens: String(row.tokens),
|
|
73
|
+
last: fmtTs(row.last_at),
|
|
74
|
+
cap: formatCents(row.cap_cents),
|
|
75
|
+
})));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function directoryEndUsersData(ctx: CliContext, opts: DirectoryOptions): Promise<{ slug: string; data: DirectoryEndUser[] }> {
|
|
79
|
+
validateDirectoryOptions(opts);
|
|
80
|
+
const slug = await inferAgentSlug(opts);
|
|
81
|
+
const res = await ctx.http.request<ListEnvelope<DirectoryEndUser>>({
|
|
82
|
+
path: directoryPath(slug, "end_users", opts),
|
|
83
|
+
});
|
|
84
|
+
return { slug, data: res.data };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function directoryEndUsers(ctx: CliContext, opts: DirectoryOptions): Promise<string> {
|
|
88
|
+
const { slug, data } = await directoryEndUsersData(ctx, opts);
|
|
89
|
+
if (data.length === 0) return `No users for ${slug}. Generate activity with: dharta send "hello" --agent ${slug}`;
|
|
90
|
+
return table(data.map((row) => ({
|
|
91
|
+
id: row.id,
|
|
92
|
+
kartas: String(row.kartas_count),
|
|
93
|
+
sessions: String(row.sessions_count),
|
|
94
|
+
tokens: String(row.tokens),
|
|
95
|
+
last: fmtTs(row.last_seen),
|
|
96
|
+
cap: formatCents(row.cap_cents),
|
|
97
|
+
})));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function directorySessionsData(ctx: CliContext, opts: DirectoryOptions): Promise<{ slug: string; data: DirectorySession[] }> {
|
|
101
|
+
validateDirectoryOptions(opts);
|
|
102
|
+
const slug = await inferAgentSlug(opts);
|
|
103
|
+
const res = await ctx.http.request<ListEnvelope<DirectorySession>>({
|
|
104
|
+
path: directoryPath(slug, "sessions", opts),
|
|
105
|
+
});
|
|
106
|
+
return { slug, data: res.data };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function directorySessions(ctx: CliContext, opts: DirectoryOptions): Promise<string> {
|
|
110
|
+
const { slug, data } = await directorySessionsData(ctx, opts);
|
|
111
|
+
if (data.length === 0) return `No sessions for ${slug}. Generate activity with: dharta send "hello" --agent ${slug}`;
|
|
112
|
+
return table(data.map((row) => ({
|
|
113
|
+
id: row.id,
|
|
114
|
+
karta: row.karta_id ?? "—",
|
|
115
|
+
users: String(row.users_count),
|
|
116
|
+
turns: String(row.turns),
|
|
117
|
+
tokens: String(row.tokens),
|
|
118
|
+
last: fmtTs(row.last_at),
|
|
119
|
+
})));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatCents(cents: number | null): string {
|
|
123
|
+
if (cents === null) return "—";
|
|
124
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function validateDirectoryOptions(opts: Pick<DirectoryOptions, "limit"> = {}): void {
|
|
128
|
+
if (opts.limit !== undefined) parsePositiveInteger(opts.limit, "--limit");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function directoryPath(slug: string, resource: "instances" | "end_users" | "sessions", opts: DirectoryOptions): string {
|
|
132
|
+
const params = new URLSearchParams();
|
|
133
|
+
if (opts.limit !== undefined) params.set("limit", String(parsePositiveInteger(opts.limit, "--limit")));
|
|
134
|
+
const query = params.toString();
|
|
135
|
+
return `/api/agents/${encodeURIComponent(slug)}/directories/${resource}${query ? `?${query}` : ""}`;
|
|
136
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// `dharta doctor` (RFC 0013 CLI-3 slice): diagnose the things that break
|
|
2
|
+
// the golden path before they break it — PATH shadowing from the retired
|
|
3
|
+
// Python CLI, the uv bootstrap, the DHARTA_RUNTIME escape hatch, and
|
|
4
|
+
// login state. The Phase-2 [env] contract checks (CLI-8) extend this.
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import { loadConfig } from "../config.js";
|
|
8
|
+
import { resolveRuntime, whichAll, type RuntimeResolveOptions } from "../runtime.js";
|
|
9
|
+
import { installChannel, packageRoot } from "./update.js";
|
|
10
|
+
import { checkNpmLatestVersion, NPM_LATEST_URL } from "../version-check.js";
|
|
11
|
+
|
|
12
|
+
export interface DoctorReport {
|
|
13
|
+
lines: string[];
|
|
14
|
+
ok: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DoctorOptions extends RuntimeResolveOptions {
|
|
18
|
+
installRoot?: string;
|
|
19
|
+
checkLatest?: boolean;
|
|
20
|
+
currentVersion?: string;
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
latestUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function doctor(opts: DoctorOptions = {}): Promise<DoctorReport> {
|
|
26
|
+
const env = opts.env ?? process.env;
|
|
27
|
+
const lines: string[] = [];
|
|
28
|
+
let ok = true;
|
|
29
|
+
|
|
30
|
+
// PATH shadowing: a stale pip-installed `dharta` ahead of this CLI
|
|
31
|
+
// makes every command behave like the retired Python CLI.
|
|
32
|
+
const kartas = await whichAll("dharta", env);
|
|
33
|
+
if (kartas.length > 1) {
|
|
34
|
+
ok = false;
|
|
35
|
+
lines.push("✗ Multiple `dharta` executables on PATH - the first one wins:");
|
|
36
|
+
for (const hit of kartas) lines.push(` ${hit}`);
|
|
37
|
+
lines.push(" Fix: remove the stale install (often `pip uninstall dharta-runtime` or delete the older path).");
|
|
38
|
+
} else if (kartas.length === 1) {
|
|
39
|
+
lines.push(`✓ PATH has a single \`dharta\` (${kartas[0]})`);
|
|
40
|
+
} else {
|
|
41
|
+
lines.push("- No `dharta` on PATH (running via npx or a dev build?).");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// DHARTA_RUNTIME escape hatch, if set, must point at something real.
|
|
45
|
+
let runtimeOverrideBroken = false;
|
|
46
|
+
if (env.DHARTA_RUNTIME) {
|
|
47
|
+
const stat = await fs.stat(env.DHARTA_RUNTIME).catch(() => null);
|
|
48
|
+
if (stat) {
|
|
49
|
+
lines.push(`✓ DHARTA_RUNTIME override -> ${env.DHARTA_RUNTIME} (${stat.isDirectory() ? "checkout" : "executable"})`);
|
|
50
|
+
} else {
|
|
51
|
+
ok = false;
|
|
52
|
+
runtimeOverrideBroken = true;
|
|
53
|
+
lines.push(`✗ DHARTA_RUNTIME points at ${env.DHARTA_RUNTIME}, which does not exist.`);
|
|
54
|
+
lines.push(" Fix: unset DHARTA_RUNTIME, or point it at a dharta-runtime checkout or executable.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Runtime bootstrap: can `dharta dev` launch the runtime at all?
|
|
59
|
+
// (Skipped when the override is the already-reported problem.)
|
|
60
|
+
if (!runtimeOverrideBroken) {
|
|
61
|
+
try {
|
|
62
|
+
const runtime = await resolveRuntime(opts);
|
|
63
|
+
lines.push(`✓ Agent runtime resolves: ${runtime.description}`);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
ok = false;
|
|
66
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
67
|
+
lines.push("✗ Agent runtime cannot start:");
|
|
68
|
+
for (const l of msg.split("\n")) lines.push(` ${l}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (opts.checkLatest !== false) {
|
|
73
|
+
const version = await checkLatestVersion(opts);
|
|
74
|
+
if (version.status === "outdated") {
|
|
75
|
+
ok = false;
|
|
76
|
+
lines.push(`✗ CLI is outdated: ${version.current} installed, ${version.latest} available.`);
|
|
77
|
+
lines.push(` Fix: ${version.fix}`);
|
|
78
|
+
} else if (version.status === "current") {
|
|
79
|
+
lines.push(`✓ CLI version is current: ${version.current}`);
|
|
80
|
+
} else if (version.status === "ahead") {
|
|
81
|
+
lines.push(`- CLI version ${version.current} is newer than the latest published npm version (${version.latest}).`);
|
|
82
|
+
} else {
|
|
83
|
+
lines.push(`- Could not check latest CLI version: ${version.reason}.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Login state (informational; commands fail with the fix anyway).
|
|
88
|
+
if (env.DHARTA_API_KEY) {
|
|
89
|
+
lines.push("✓ Authenticated via DHARTA_API_KEY (environment)");
|
|
90
|
+
} else {
|
|
91
|
+
try {
|
|
92
|
+
const cfg = await loadConfig();
|
|
93
|
+
if (cfg?.profiles[cfg.default_profile]) {
|
|
94
|
+
lines.push(`✓ Logged in (profile '${cfg.default_profile}')`);
|
|
95
|
+
} else {
|
|
96
|
+
lines.push("- Not logged in. Run `dharta login` before API-backed commands; local checks and browser handoffs still work.");
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
// doctor must diagnose a corrupt config, not die on it.
|
|
100
|
+
ok = false;
|
|
101
|
+
lines.push(`✗ ~/.dharta/config.toml is unreadable (${err instanceof Error ? err.message : err}).`);
|
|
102
|
+
lines.push(" Fix: repair or delete it, then `dharta login` again.");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { lines, ok };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type VersionCheck =
|
|
110
|
+
| { status: "current"; current: string }
|
|
111
|
+
| { status: "ahead"; current: string; latest: string }
|
|
112
|
+
| { status: "outdated"; current: string; latest: string; fix: string }
|
|
113
|
+
| { status: "unknown"; reason: string };
|
|
114
|
+
|
|
115
|
+
async function checkLatestVersion(opts: DoctorOptions): Promise<VersionCheck> {
|
|
116
|
+
const root = opts.installRoot ?? packageRoot();
|
|
117
|
+
const result = await checkNpmLatestVersion({
|
|
118
|
+
root,
|
|
119
|
+
currentVersion: opts.currentVersion,
|
|
120
|
+
fetchImpl: opts.fetchImpl,
|
|
121
|
+
latestUrl: opts.latestUrl ?? NPM_LATEST_URL,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (result.status === "unknown") return { status: "unknown", reason: result.reason };
|
|
125
|
+
if (result.status === "current") return { status: "current", current: result.current };
|
|
126
|
+
if (result.status === "ahead") return { status: "ahead", current: result.current, latest: result.latest };
|
|
127
|
+
|
|
128
|
+
const channel = await installChannel(root).catch(() => "npm" as const);
|
|
129
|
+
return {
|
|
130
|
+
status: "outdated",
|
|
131
|
+
current: result.current,
|
|
132
|
+
latest: result.latest,
|
|
133
|
+
fix:
|
|
134
|
+
channel === "standalone"
|
|
135
|
+
? "run `dharta update`"
|
|
136
|
+
: "run `npm install -g @dharta/cli@latest`",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { inferAgentSlug, type AgentInferOptions } from "../agent.js";
|
|
3
|
+
import type { CliContext } from "../context.js";
|
|
4
|
+
import { fmtTs, table } from "../output.js";
|
|
5
|
+
import { normalizeHttpUrl, normalizeNonBlank } from "../validators.js";
|
|
6
|
+
|
|
7
|
+
export interface EmbedKey {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
prefix: string;
|
|
11
|
+
allowed_origins: string[];
|
|
12
|
+
disabled_at: string | null;
|
|
13
|
+
revoked_at: string | null;
|
|
14
|
+
created_at: string;
|
|
15
|
+
last_used_at: string | null;
|
|
16
|
+
anonymous_max_cost_micros_per_period?: number | null;
|
|
17
|
+
anonymous_period_unit?: string | null;
|
|
18
|
+
agent_instance_id?: string | null;
|
|
19
|
+
identity_verification_enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface IdentityEnvelope {
|
|
23
|
+
embed_key: EmbedKey;
|
|
24
|
+
identity_verification_secret?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ListEnvelope<T> {
|
|
28
|
+
data: T[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface EmbedKeyEnvelope {
|
|
32
|
+
embed_key: EmbedKey;
|
|
33
|
+
secret?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface EmbedKeyOptions extends AgentInferOptions {
|
|
37
|
+
origins?: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function embedKeysListData(ctx: CliContext, opts: AgentInferOptions): Promise<{ slug: string; data: EmbedKey[] }> {
|
|
41
|
+
const slug = await inferAgentSlug(opts);
|
|
42
|
+
const res = await ctx.http.request<ListEnvelope<EmbedKey>>({
|
|
43
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys`,
|
|
44
|
+
});
|
|
45
|
+
return { slug, data: res.data };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function embedKeysList(ctx: CliContext, opts: AgentInferOptions): Promise<string> {
|
|
49
|
+
const { slug, data } = await embedKeysListData(ctx, opts);
|
|
50
|
+
if (data.length === 0) return `No embed keys for ${slug}. Create one with: dharta embed-keys create <name> --agent ${slug}`;
|
|
51
|
+
return table(data.map((key) => row(key)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function embedKeysCreateData(
|
|
55
|
+
ctx: CliContext,
|
|
56
|
+
name: string,
|
|
57
|
+
opts: EmbedKeyOptions,
|
|
58
|
+
): Promise<{ slug: string } & EmbedKeyEnvelope> {
|
|
59
|
+
validateEmbedKeysCreateInput(name, opts);
|
|
60
|
+
const normalizedName = normalizeEmbedKeyName(name);
|
|
61
|
+
const normalizedOrigins = parseOrigins(opts.origins);
|
|
62
|
+
const slug = await inferAgentSlug(opts);
|
|
63
|
+
const res = await ctx.http.request<EmbedKeyEnvelope>({
|
|
64
|
+
method: "POST",
|
|
65
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys`,
|
|
66
|
+
headers: idempotencyHeader(),
|
|
67
|
+
body: {
|
|
68
|
+
name: normalizedName,
|
|
69
|
+
allowed_origins: normalizedOrigins,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return { slug, ...res };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function embedKeysCreate(ctx: CliContext, name: string, opts: EmbedKeyOptions): Promise<string> {
|
|
76
|
+
const res = await embedKeysCreateData(ctx, name, opts);
|
|
77
|
+
return [
|
|
78
|
+
`Created embed key ${res.embed_key.prefix} (${res.embed_key.id}) for ${res.slug}.`,
|
|
79
|
+
"Secret (shown once - update your widget now):",
|
|
80
|
+
res.secret ?? "",
|
|
81
|
+
].join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function validateEmbedKeysCreateInput(name: string, opts: EmbedKeyOptions): void {
|
|
85
|
+
normalizeEmbedKeyName(name);
|
|
86
|
+
if (opts.origins !== undefined) parseOrigins(opts.origins);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function embedKeysRotateData(ctx: CliContext, id: string, opts: AgentInferOptions): Promise<{ slug: string } & EmbedKeyEnvelope> {
|
|
90
|
+
const normalizedId = normalizeNonBlank(id, "Embed key id");
|
|
91
|
+
const slug = await inferAgentSlug(opts);
|
|
92
|
+
const res = await ctx.http.request<EmbedKeyEnvelope>({
|
|
93
|
+
method: "POST",
|
|
94
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys/${encodeURIComponent(normalizedId)}/rotate`,
|
|
95
|
+
headers: idempotencyHeader(),
|
|
96
|
+
});
|
|
97
|
+
return { slug, ...res };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function embedKeysRotate(ctx: CliContext, id: string, opts: AgentInferOptions): Promise<string> {
|
|
101
|
+
const res = await embedKeysRotateData(ctx, id, opts);
|
|
102
|
+
return [
|
|
103
|
+
`Rotated embed key ${res.embed_key.prefix} (${res.embed_key.id}) for ${res.slug}.`,
|
|
104
|
+
"Secret (shown once - update your widget now):",
|
|
105
|
+
res.secret ?? "",
|
|
106
|
+
].join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function embedKeysToggleData(
|
|
110
|
+
ctx: CliContext,
|
|
111
|
+
id: string,
|
|
112
|
+
action: "enable" | "disable",
|
|
113
|
+
opts: AgentInferOptions,
|
|
114
|
+
): Promise<{ slug: string } & EmbedKeyEnvelope> {
|
|
115
|
+
const normalizedId = normalizeNonBlank(id, "Embed key id");
|
|
116
|
+
const slug = await inferAgentSlug(opts);
|
|
117
|
+
const res = await ctx.http.request<EmbedKeyEnvelope>({
|
|
118
|
+
method: "POST",
|
|
119
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys/${encodeURIComponent(normalizedId)}/${action}`,
|
|
120
|
+
headers: idempotencyHeader(),
|
|
121
|
+
});
|
|
122
|
+
return { slug, ...res };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function embedKeysToggle(
|
|
126
|
+
ctx: CliContext,
|
|
127
|
+
id: string,
|
|
128
|
+
action: "enable" | "disable",
|
|
129
|
+
opts: AgentInferOptions,
|
|
130
|
+
): Promise<string> {
|
|
131
|
+
const res = await embedKeysToggleData(ctx, id, action, opts);
|
|
132
|
+
return `${action === "enable" ? "Enabled" : "Disabled"} embed key ${res.embed_key.prefix} (${res.embed_key.id}).`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function embedKeysSetIdentityData(
|
|
136
|
+
ctx: CliContext,
|
|
137
|
+
id: string,
|
|
138
|
+
opts: AgentInferOptions,
|
|
139
|
+
): Promise<{ slug: string } & IdentityEnvelope> {
|
|
140
|
+
const normalizedId = normalizeNonBlank(id, "Embed key id");
|
|
141
|
+
const slug = await inferAgentSlug(opts);
|
|
142
|
+
const res = await ctx.http.request<IdentityEnvelope>({
|
|
143
|
+
method: "POST",
|
|
144
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys/${encodeURIComponent(normalizedId)}/identity`,
|
|
145
|
+
headers: idempotencyHeader(),
|
|
146
|
+
});
|
|
147
|
+
return { slug, ...res };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function embedKeysSetIdentity(ctx: CliContext, id: string, opts: AgentInferOptions): Promise<string> {
|
|
151
|
+
const res = await embedKeysSetIdentityData(ctx, id, opts);
|
|
152
|
+
return [
|
|
153
|
+
`Enabled verified identity for embed key ${res.embed_key.prefix} (${res.embed_key.id}) on ${res.slug}.`,
|
|
154
|
+
"Identity signing secret (shown once - copy it into your backend now; it will not be shown again):",
|
|
155
|
+
res.identity_verification_secret ?? "",
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function embedKeysClearIdentityData(
|
|
160
|
+
ctx: CliContext,
|
|
161
|
+
id: string,
|
|
162
|
+
opts: AgentInferOptions,
|
|
163
|
+
): Promise<{ slug: string } & EmbedKeyEnvelope> {
|
|
164
|
+
const normalizedId = normalizeNonBlank(id, "Embed key id");
|
|
165
|
+
const slug = await inferAgentSlug(opts);
|
|
166
|
+
const res = await ctx.http.request<EmbedKeyEnvelope>({
|
|
167
|
+
method: "DELETE",
|
|
168
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys/${encodeURIComponent(normalizedId)}/identity`,
|
|
169
|
+
headers: idempotencyHeader(),
|
|
170
|
+
});
|
|
171
|
+
return { slug, ...res };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function embedKeysClearIdentity(ctx: CliContext, id: string, opts: AgentInferOptions): Promise<string> {
|
|
175
|
+
const res = await embedKeysClearIdentityData(ctx, id, opts);
|
|
176
|
+
return `Disabled verified identity for embed key ${res.embed_key.prefix} (${res.embed_key.id}) on ${res.slug}.`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function embedKeysRevokeData(ctx: CliContext, id: string, opts: AgentInferOptions): Promise<{ slug: string; id: string; revoked: boolean }> {
|
|
180
|
+
const normalizedId = normalizeNonBlank(id, "Embed key id");
|
|
181
|
+
const slug = await inferAgentSlug(opts);
|
|
182
|
+
await ctx.http.request<void>({
|
|
183
|
+
method: "DELETE",
|
|
184
|
+
path: `/api/agents/${encodeURIComponent(slug)}/embed_keys/${encodeURIComponent(normalizedId)}`,
|
|
185
|
+
headers: idempotencyHeader(),
|
|
186
|
+
});
|
|
187
|
+
return { slug, id: normalizedId, revoked: true };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function embedKeysRevoke(ctx: CliContext, id: string, opts: AgentInferOptions): Promise<string> {
|
|
191
|
+
const res = await embedKeysRevokeData(ctx, id, opts);
|
|
192
|
+
return `Revoked embed key ${res.id} for ${res.slug}.`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseOrigins(input: string[] | undefined): string[] {
|
|
196
|
+
if (input === undefined) return [];
|
|
197
|
+
const origins = input
|
|
198
|
+
.flatMap((value) => value.split(/[,\s]+/))
|
|
199
|
+
.map((value) => value.trim())
|
|
200
|
+
.filter(Boolean);
|
|
201
|
+
if (origins.length === 0) {
|
|
202
|
+
throw new Error("--origin must include at least one origin, e.g. --origin https://app.example.com.");
|
|
203
|
+
}
|
|
204
|
+
return origins.map((origin) => normalizeOrigin(origin));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeOrigin(origin: string): string {
|
|
208
|
+
const normalized = normalizeHttpUrl(origin, "--origin");
|
|
209
|
+
const parsed = new URL(normalized);
|
|
210
|
+
if (parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
211
|
+
throw new Error("--origin must be an origin like https://app.example.com, without a path, query, or fragment.");
|
|
212
|
+
}
|
|
213
|
+
return parsed.origin;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function row(key: EmbedKey): Record<string, string> {
|
|
217
|
+
return {
|
|
218
|
+
prefix: key.prefix,
|
|
219
|
+
name: key.name,
|
|
220
|
+
origins: key.allowed_origins.join(",") || "-",
|
|
221
|
+
disabled: fmtTs(key.disabled_at),
|
|
222
|
+
revoked: fmtTs(key.revoked_at),
|
|
223
|
+
created: fmtTs(key.created_at),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function idempotencyHeader(): Record<string, string> {
|
|
228
|
+
return { "Idempotency-Key": randomUUID() };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function normalizeEmbedKeyName(name: string): string {
|
|
232
|
+
const normalized = name.trim();
|
|
233
|
+
if (!normalized) {
|
|
234
|
+
throw new Error("Embed key name cannot be blank.");
|
|
235
|
+
}
|
|
236
|
+
return normalized;
|
|
237
|
+
}
|