@heyditto/cli 2.0.0 → 2.1.0

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.
@@ -0,0 +1,63 @@
1
+ import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { sessionsDir } from "../config.js";
4
+ function fileFor(id) {
5
+ if (!/^[A-Za-z0-9._:@-]{1,128}$/.test(id))
6
+ throw new Error(`invalid session id: ${id}`);
7
+ return path.join(sessionsDir(), `${id}.json`);
8
+ }
9
+ export async function writeSession(record) {
10
+ await mkdir(sessionsDir(), { recursive: true, mode: 0o700 });
11
+ await writeFile(fileFor(record.id), `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
12
+ }
13
+ export async function readSession(id) {
14
+ try {
15
+ return JSON.parse(await readFile(fileFor(id), "utf8"));
16
+ }
17
+ catch (err) {
18
+ if (err.code === "ENOENT")
19
+ return undefined;
20
+ throw err;
21
+ }
22
+ }
23
+ export async function removeSession(id) {
24
+ try {
25
+ await rm(fileFor(id));
26
+ return true;
27
+ }
28
+ catch (err) {
29
+ if (err.code === "ENOENT")
30
+ return false;
31
+ throw err;
32
+ }
33
+ }
34
+ /** All local records, most recently launched first. */
35
+ export async function listSessions() {
36
+ let names;
37
+ try {
38
+ names = await readdir(sessionsDir());
39
+ }
40
+ catch (err) {
41
+ if (err.code === "ENOENT")
42
+ return [];
43
+ throw err;
44
+ }
45
+ const records = [];
46
+ for (const name of names) {
47
+ if (!name.endsWith(".json"))
48
+ continue;
49
+ try {
50
+ records.push(JSON.parse(await readFile(path.join(sessionsDir(), name), "utf8")));
51
+ }
52
+ catch {
53
+ /* skip unreadable record */
54
+ }
55
+ }
56
+ return records.sort((a, b) => (b.lastLaunchedAt ?? "").localeCompare(a.lastLaunchedAt ?? ""));
57
+ }
58
+ /** Most recent record for a harness, preferring one launched from the same directory. */
59
+ export async function latestSession(harness, cwd) {
60
+ const all = (await listSessions()).filter((s) => s.harness === harness);
61
+ return all.find((s) => s.cwd === cwd || s.worktree === cwd) ?? all[0];
62
+ }
63
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/agents/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /** Shared types for the coding-agent launchers. Pure data, no I/O. */
2
+ export type Harness = "claude" | "codex";
3
+ export declare const HARNESSES: readonly Harness[];
4
+ export declare const KEY_EXPIRIES: readonly ["1h", "4h", "8h", "12h", "1d", "1w", "1mo", "3mo", "6mo", "1y", "never"];
5
+ export type KeyExpiry = (typeof KEY_EXPIRIES)[number];
6
+ /** Header the Ditto gateway groups turns by; every launch sends one. */
7
+ export declare const SESSION_HEADER = "X-Ditto-Session-Id";
8
+ /** Env var the Codex provider reads the endpoint key from. */
9
+ export declare const CODEX_KEY_ENV = "DITTO_INFERENCE_API_KEY";
10
+ /** Everything a harness planner needs to build argv + env. */
11
+ export interface PlanInput {
12
+ /** Inference gateway base URL ending in /v1, e.g. https://api.heyditto.ai/v1 */
13
+ baseUrl: string;
14
+ /** Plaintext endpoint key (already minted) or a placeholder for --dry-run. */
15
+ apiKey: string;
16
+ /** Ditto session id sent as X-Ditto-Session-Id. */
17
+ sessionId: string;
18
+ /** Model id to request; undefined leaves the harness default (gateway routes it). */
19
+ model?: string;
20
+ /** Harness-native session/thread id to resume, when resuming. */
21
+ resumeId?: string;
22
+ /** Resume the most recent harness session (claude --continue / codex resume --last). */
23
+ resumeLast?: boolean;
24
+ /** Headless prompt: claude -p / codex exec. */
25
+ prompt?: string;
26
+ yolo?: boolean;
27
+ yellow?: boolean;
28
+ plan?: boolean;
29
+ /** Extra argv forwarded verbatim to the harness. */
30
+ passthrough: string[];
31
+ /** Parent environment to derive the child environment from. */
32
+ env: NodeJS.ProcessEnv;
33
+ }
34
+ export interface HarnessPlan {
35
+ command: string;
36
+ args: string[];
37
+ /** Env vars set on the child (in addition to the inherited environment). */
38
+ envSet: Record<string, string>;
39
+ /** Env vars removed from the child environment. */
40
+ envUnset: string[];
41
+ /** Human-readable install hint when the binary is missing. */
42
+ installHint: string;
43
+ }
44
+ /** Strips the first `--` separator commander leaves in a variadic passthrough list. */
45
+ export declare function stripSeparator(args: string[]): string[];
46
+ /** Applies a plan's env changes to a copy of the given environment. */
47
+ export declare function childEnv(plan: HarnessPlan, parent: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
48
+ /** Base URL with a trailing /v1 removed: what ANTHROPIC_BASE_URL expects. */
49
+ export declare function apiRootOf(baseUrl: string): string;
50
+ export declare function maskKey(key: string): string;
@@ -0,0 +1,44 @@
1
+ /** Shared types for the coding-agent launchers. Pure data, no I/O. */
2
+ export const HARNESSES = ["claude", "codex"];
3
+ export const KEY_EXPIRIES = [
4
+ "1h",
5
+ "4h",
6
+ "8h",
7
+ "12h",
8
+ "1d",
9
+ "1w",
10
+ "1mo",
11
+ "3mo",
12
+ "6mo",
13
+ "1y",
14
+ "never",
15
+ ];
16
+ /** Header the Ditto gateway groups turns by; every launch sends one. */
17
+ export const SESSION_HEADER = "X-Ditto-Session-Id";
18
+ /** Env var the Codex provider reads the endpoint key from. */
19
+ export const CODEX_KEY_ENV = "DITTO_INFERENCE_API_KEY";
20
+ /** Strips the first `--` separator commander leaves in a variadic passthrough list. */
21
+ export function stripSeparator(args) {
22
+ const i = args.indexOf("--");
23
+ if (i === -1)
24
+ return args;
25
+ return [...args.slice(0, i), ...args.slice(i + 1)];
26
+ }
27
+ /** Applies a plan's env changes to a copy of the given environment. */
28
+ export function childEnv(plan, parent) {
29
+ const env = { ...parent };
30
+ for (const k of plan.envUnset)
31
+ delete env[k];
32
+ Object.assign(env, plan.envSet);
33
+ return env;
34
+ }
35
+ /** Base URL with a trailing /v1 removed: what ANTHROPIC_BASE_URL expects. */
36
+ export function apiRootOf(baseUrl) {
37
+ return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
38
+ }
39
+ export function maskKey(key) {
40
+ if (key.length <= 8)
41
+ return "…";
42
+ return `${key.slice(0, 10)}…${key.slice(-4)}`;
43
+ }
44
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAItE,MAAM,CAAC,MAAM,SAAS,GAAuB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAEjE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK;IACL,KAAK;IACL,IAAI;IACJ,OAAO;CACC,CAAC;AAGX,wEAAwE;AACxE,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC;AAEnD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAsCvD,uFAAuF;AACvF,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,QAAQ,CAAC,IAAiB,EAAE,MAAyB;IACnE,MAAM,GAAG,GAAsB,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IAChC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { Harness } from "./types.js";
2
+ /** Folder inside the repository that holds the CLI's worktrees (kept out of git). */
3
+ export declare const WORKTREES_DIR = ".worktrees";
4
+ /** `<harness>-<yyyymmdd-hhmm>`, e.g. `claude-20260904-1530`. */
5
+ export declare function defaultWorktreeName(harness: Harness, now?: Date): string;
6
+ export declare function validWorktreeName(name: string): boolean;
7
+ export declare function repoRoot(cwd: string): string | undefined;
8
+ /** Adds `.worktrees/` to the repo root .gitignore when it is not already listed. */
9
+ export declare function ensureGitignore(root: string): Promise<boolean>;
10
+ export interface WorktreeResult {
11
+ path: string;
12
+ branch: string;
13
+ created: boolean;
14
+ }
15
+ /**
16
+ * Creates (or reuses) `<repo>/.worktrees/<name>` on a branch of the same
17
+ * name, mirroring how Claude Code keeps worktrees inside the repository.
18
+ */
19
+ export declare function ensureWorktree(cwd: string, name: string): Promise<WorktreeResult>;
@@ -0,0 +1,72 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ /** Folder inside the repository that holds the CLI's worktrees (kept out of git). */
5
+ export const WORKTREES_DIR = ".worktrees";
6
+ function pad(n) {
7
+ return String(n).padStart(2, "0");
8
+ }
9
+ /** `<harness>-<yyyymmdd-hhmm>`, e.g. `claude-20260904-1530`. */
10
+ export function defaultWorktreeName(harness, now = new Date()) {
11
+ const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
12
+ `-${pad(now.getHours())}${pad(now.getMinutes())}`;
13
+ return `${harness}-${stamp}`;
14
+ }
15
+ export function validWorktreeName(name) {
16
+ return /^[A-Za-z0-9][A-Za-z0-9._\/-]{0,80}$/.test(name) && !name.includes("..");
17
+ }
18
+ function git(args, cwd) {
19
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
20
+ return { ok: res.status === 0, out: (res.stdout ?? "").trim(), err: (res.stderr ?? "").trim() };
21
+ }
22
+ export function repoRoot(cwd) {
23
+ const res = git(["rev-parse", "--show-toplevel"], cwd);
24
+ return res.ok && res.out ? res.out : undefined;
25
+ }
26
+ /** Adds `.worktrees/` to the repo root .gitignore when it is not already listed. */
27
+ export async function ensureGitignore(root) {
28
+ const file = path.join(root, ".gitignore");
29
+ let current = "";
30
+ try {
31
+ current = await readFile(file, "utf8");
32
+ }
33
+ catch (err) {
34
+ if (err.code !== "ENOENT")
35
+ throw err;
36
+ }
37
+ const listed = current
38
+ .split(/\r?\n/)
39
+ .map((l) => l.trim())
40
+ .some((l) => l === WORKTREES_DIR || l === `${WORKTREES_DIR}/` || l === `/${WORKTREES_DIR}` || l === `/${WORKTREES_DIR}/`);
41
+ if (listed)
42
+ return false;
43
+ const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
44
+ await appendFile(file, `${prefix}# Coding-agent worktrees created by heyditto\n${WORKTREES_DIR}/\n`);
45
+ return true;
46
+ }
47
+ /**
48
+ * Creates (or reuses) `<repo>/.worktrees/<name>` on a branch of the same
49
+ * name, mirroring how Claude Code keeps worktrees inside the repository.
50
+ */
51
+ export async function ensureWorktree(cwd, name) {
52
+ const root = repoRoot(cwd);
53
+ if (!root)
54
+ throw new Error(`--worktree needs a git repository (no repo found at ${cwd})`);
55
+ if (!validWorktreeName(name))
56
+ throw new Error(`invalid worktree name: ${name}`);
57
+ await ensureGitignore(root);
58
+ const dir = path.join(root, WORKTREES_DIR, name);
59
+ await mkdir(path.dirname(dir), { recursive: true });
60
+ const existing = git(["worktree", "list", "--porcelain"], root);
61
+ if (existing.ok && existing.out.split("\n").includes(`worktree ${dir}`)) {
62
+ return { path: dir, branch: name, created: false };
63
+ }
64
+ const branchExists = git(["show-ref", "--verify", "--quiet", `refs/heads/${name}`], root).ok;
65
+ const add = branchExists
66
+ ? git(["worktree", "add", dir, name], root)
67
+ : git(["worktree", "add", "-b", name, dir], root);
68
+ if (!add.ok)
69
+ throw new Error(`git worktree add failed: ${add.err || add.out}`);
70
+ return { path: dir, branch: name, created: true };
71
+ }
72
+ //# sourceMappingURL=worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/agents/worktree.ts"],"names":[],"mappings":"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"}
package/dist/api.d.ts ADDED
@@ -0,0 +1,110 @@
1
+ /** Minimal authenticated REST client for the Ditto management API. */
2
+ export declare class ApiError extends Error {
3
+ status: number;
4
+ body: string;
5
+ constructor(message: string, status: number, body: string);
6
+ }
7
+ export declare function apiBase(): string;
8
+ export declare function apiFetch<T>(path: string, init?: {
9
+ method?: string;
10
+ body?: unknown;
11
+ auth?: boolean;
12
+ }): Promise<T>;
13
+ export interface InferenceEndpoint {
14
+ id: string;
15
+ slug: string;
16
+ name: string;
17
+ model: string;
18
+ spendPeriod?: string;
19
+ spendLimitTokens?: number | null;
20
+ spentTokens?: number;
21
+ recordTrace?: boolean;
22
+ recallEnabled?: boolean;
23
+ recordEnabled?: boolean;
24
+ tools?: string[];
25
+ }
26
+ export interface InferenceEndpointsResponse {
27
+ baseUrl: string;
28
+ endpoints: InferenceEndpoint[];
29
+ limit?: number;
30
+ used?: number;
31
+ }
32
+ export declare function listEndpoints(): Promise<InferenceEndpointsResponse>;
33
+ export interface InferenceKey {
34
+ id: string;
35
+ endpointId: string;
36
+ name: string;
37
+ keyHint: string;
38
+ key?: string;
39
+ expiresAt?: string | null;
40
+ spendLimitTokens?: number | null;
41
+ spendPeriod?: string;
42
+ }
43
+ export interface CreateKeyInput {
44
+ name: string;
45
+ expiresIn: string;
46
+ spendLimitTokens?: number;
47
+ spendPeriod?: string;
48
+ }
49
+ export declare function createKey(endpointId: string, input: CreateKeyInput): Promise<InferenceKey>;
50
+ export declare function revokeKey(endpointId: string, keyId: string): Promise<void>;
51
+ export interface InferenceSession {
52
+ id: string;
53
+ endpointId: string;
54
+ sessionKey: string;
55
+ threadId?: string;
56
+ harness?: string;
57
+ model?: string;
58
+ turnCount?: number;
59
+ firstSeenAt?: string;
60
+ lastSeenAt?: string;
61
+ }
62
+ export declare function listRemoteSessions(endpointId: string): Promise<InferenceSession[]>;
63
+ export interface DeviceCode {
64
+ device_code: string;
65
+ user_code: string;
66
+ verification_url: string;
67
+ expires_in: number;
68
+ interval: number;
69
+ }
70
+ export declare function requestDeviceCode(): Promise<DeviceCode>;
71
+ export type DeviceTokenResult = {
72
+ status: "ok";
73
+ accessToken: string;
74
+ } | {
75
+ status: "pending";
76
+ } | {
77
+ status: "slow_down";
78
+ } | {
79
+ status: "denied";
80
+ } | {
81
+ status: "expired";
82
+ };
83
+ export declare function pollDeviceToken(deviceCode: string): Promise<DeviceTokenResult>;
84
+ export interface AgentConnection {
85
+ id: string;
86
+ agentId: string;
87
+ kind: string;
88
+ refId: string;
89
+ name: string;
90
+ sessionCooldownSeconds?: number;
91
+ createdAt: string;
92
+ lastUsedAt?: string | null;
93
+ expiresAt?: string | null;
94
+ revokedAt?: string | null;
95
+ }
96
+ export interface ChatAgent {
97
+ id: string;
98
+ kind: string;
99
+ name: string;
100
+ mainThreadId: string;
101
+ kgId?: string;
102
+ status: string;
103
+ pinned?: boolean;
104
+ threadCount?: number;
105
+ lastActivityAt?: string | null;
106
+ createdAt: string;
107
+ updatedAt: string;
108
+ connections?: AgentConnection[];
109
+ }
110
+ export declare function listChatAgents(): Promise<ChatAgent[]>;
package/dist/api.js ADDED
@@ -0,0 +1,119 @@
1
+ import { packageName, packageVersion, resolveApiKey } from "./config.js";
2
+ /** Minimal authenticated REST client for the Ditto management API. */
3
+ export class ApiError extends Error {
4
+ status;
5
+ body;
6
+ constructor(message, status, body) {
7
+ super(message);
8
+ this.name = "ApiError";
9
+ this.status = status;
10
+ this.body = body;
11
+ }
12
+ }
13
+ export function apiBase() {
14
+ return (process.env.DITTO_API_BASE || "https://api.heyditto.ai").replace(/\/+$/, "");
15
+ }
16
+ function userAgent() {
17
+ return `${packageName}/${packageVersion}`;
18
+ }
19
+ async function requireKey() {
20
+ const { key } = await resolveApiKey();
21
+ if (!key) {
22
+ throw new Error("no Ditto API key configured.\n\n" +
23
+ " Run: heyditto login\n" +
24
+ " Or save an existing key with: heyditto login <key>\n");
25
+ }
26
+ return key;
27
+ }
28
+ export async function apiFetch(path, init = {}) {
29
+ const headers = {
30
+ Accept: "application/json",
31
+ "User-Agent": userAgent(),
32
+ };
33
+ if (init.body !== undefined)
34
+ headers["Content-Type"] = "application/json";
35
+ if (init.auth !== false)
36
+ headers.Authorization = `Bearer ${await requireKey()}`;
37
+ const response = await fetch(`${apiBase()}${path}`, {
38
+ method: init.method ?? "GET",
39
+ headers,
40
+ body: init.body === undefined ? undefined : JSON.stringify(init.body),
41
+ });
42
+ if (!response.ok) {
43
+ const text = await response.text().catch(() => "");
44
+ let detail = text;
45
+ try {
46
+ const parsed = JSON.parse(text);
47
+ detail = parsed.message || parsed.error || text;
48
+ }
49
+ catch {
50
+ /* raw body */
51
+ }
52
+ const hint = response.status === 401
53
+ ? " (is the saved key valid? run `heyditto login`)"
54
+ : response.status === 403
55
+ ? " (the key is not allowed to manage inference endpoints)"
56
+ : "";
57
+ throw new ApiError(`${init.method ?? "GET"} ${path} failed: HTTP ${response.status}${detail ? ` - ${detail.slice(0, 300)}` : ""}${hint}`, response.status, text);
58
+ }
59
+ if (response.status === 204)
60
+ return undefined;
61
+ return (await response.json());
62
+ }
63
+ export async function listEndpoints() {
64
+ const res = await apiFetch("/api/v5/inference/endpoints");
65
+ return {
66
+ baseUrl: (res.baseUrl || `${apiBase()}/v1`).replace(/\/+$/, ""),
67
+ endpoints: res.endpoints ?? [],
68
+ limit: res.limit,
69
+ used: res.used,
70
+ };
71
+ }
72
+ export async function createKey(endpointId, input) {
73
+ const key = await apiFetch(`/api/v5/inference/endpoints/${encodeURIComponent(endpointId)}/keys`, { method: "POST", body: input });
74
+ if (!key.key)
75
+ throw new Error("key creation succeeded but no plaintext key was returned");
76
+ return key;
77
+ }
78
+ export async function revokeKey(endpointId, keyId) {
79
+ await apiFetch(`/api/v5/inference/endpoints/${encodeURIComponent(endpointId)}/keys/${encodeURIComponent(keyId)}`, { method: "DELETE" });
80
+ }
81
+ export async function listRemoteSessions(endpointId) {
82
+ const res = await apiFetch(`/api/v5/inference/endpoints/${encodeURIComponent(endpointId)}/sessions`);
83
+ return res.sessions ?? [];
84
+ }
85
+ export async function requestDeviceCode() {
86
+ const res = await apiFetch("/api/v2/mcp/device-code", {
87
+ method: "POST",
88
+ body: {},
89
+ auth: false,
90
+ });
91
+ if (!res.device_code || !res.user_code || !res.verification_url) {
92
+ throw new Error("device login is not available on this API (malformed device-code response)");
93
+ }
94
+ return res;
95
+ }
96
+ export async function pollDeviceToken(deviceCode) {
97
+ const res = await apiFetch("/api/v2/mcp/device-token", {
98
+ method: "POST",
99
+ auth: false,
100
+ body: { device_code: deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" },
101
+ });
102
+ if (res.access_token)
103
+ return { status: "ok", accessToken: res.access_token };
104
+ switch (res.error) {
105
+ case "authorization_pending":
106
+ return { status: "pending" };
107
+ case "slow_down":
108
+ return { status: "slow_down" };
109
+ case "access_denied":
110
+ return { status: "denied" };
111
+ default:
112
+ return { status: "expired" };
113
+ }
114
+ }
115
+ export async function listChatAgents() {
116
+ const out = await apiFetch("/api/v5/chat-agents");
117
+ return out.agents ?? [];
118
+ }
119
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}