@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,154 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import TOML from "@iarna/toml";
|
|
5
|
+
import {
|
|
6
|
+
AGENT_MARKERS,
|
|
7
|
+
hasAgentMarker,
|
|
8
|
+
markerFolderRel,
|
|
9
|
+
readDeclaredHarnessType,
|
|
10
|
+
sniffHarnessType,
|
|
11
|
+
type HarnessType,
|
|
12
|
+
} from "./harnesses.js";
|
|
13
|
+
import type { DeployMethod } from "./manifest.js";
|
|
14
|
+
|
|
15
|
+
// `dharta setup` lists every agent folder so you can pick one to configure
|
|
16
|
+
// (RFC 0024 DEP-4 / D10). A folder counts if it holds ANY harness/karta
|
|
17
|
+
// marker - configured or not, repo root included. Detection never
|
|
18
|
+
// auto-deploys; it just surfaces candidates.
|
|
19
|
+
export const MARKERS = [...AGENT_MARKERS];
|
|
20
|
+
|
|
21
|
+
export interface AgentFolder {
|
|
22
|
+
dir: string; // absolute
|
|
23
|
+
rel: string; // relative to the scan root ("." = the root itself)
|
|
24
|
+
name?: string; // from dharta.toml when configured (display name)
|
|
25
|
+
slug?: string; // explicit identity from dharta.toml; when absent, derived from name
|
|
26
|
+
configured: boolean; // dharta.toml present
|
|
27
|
+
deployEnabled: boolean; // deploy = true in dharta.toml
|
|
28
|
+
deployMethod?: DeployMethod; // explicit per-agent deployment transport
|
|
29
|
+
harnessType?: HarnessType; // declared in dharta.toml or sniffed from native markers
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Walk up from `start` for a directory containing .git; null if none.
|
|
33
|
+
export function findGitRoot(start: string): string | null {
|
|
34
|
+
let dir = path.resolve(start);
|
|
35
|
+
for (;;) {
|
|
36
|
+
if (existsSync(path.join(dir, ".git"))) return dir;
|
|
37
|
+
const parent = path.dirname(dir);
|
|
38
|
+
if (parent === dir) return null;
|
|
39
|
+
dir = parent;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ScanOptions {
|
|
44
|
+
isGitRepo?: boolean;
|
|
45
|
+
// Test seam: returns NUL-joined `git ls-files -z` output, or null.
|
|
46
|
+
runGit?: (args: string[], cwd: string) => Promise<string | null>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Every agent folder under `root`, sorted by relative path. In a git repo we
|
|
50
|
+
// union git's view of marker files - tracked AND new-but-not-ignored, at ANY
|
|
51
|
+
// depth (`--cached --others --exclude-standard`) - with a shallow walk that
|
|
52
|
+
// also catches directory markers like `.claude`. Outside git it is the shallow
|
|
53
|
+
// walk alone (root + immediate subdirs, RFC 0024 §4). The `--others` half is
|
|
54
|
+
// what surfaces a freshly `dharta create`d folder before it is committed.
|
|
55
|
+
export async function scanAgentFolders(root: string, opts: ScanOptions = {}): Promise<AgentFolder[]> {
|
|
56
|
+
const absRoot = path.resolve(root);
|
|
57
|
+
const rels = new Set<string>();
|
|
58
|
+
|
|
59
|
+
if (opts.isGitRepo) {
|
|
60
|
+
const listed = await (opts.runGit ?? gitLsFiles)(
|
|
61
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
62
|
+
absRoot,
|
|
63
|
+
);
|
|
64
|
+
for (const p of (listed ?? "").split("\0")) {
|
|
65
|
+
if (!p) continue;
|
|
66
|
+
const folder = markerFolderRel(p);
|
|
67
|
+
if (!folder) continue;
|
|
68
|
+
rels.add(folder);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await shallowWalk(absRoot, rels);
|
|
72
|
+
|
|
73
|
+
const folders: AgentFolder[] = [];
|
|
74
|
+
for (const rel of [...rels].sort()) {
|
|
75
|
+
folders.push(await describeFolder(path.resolve(absRoot, rel), rel));
|
|
76
|
+
}
|
|
77
|
+
return folders;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Destination-agnostic setup table: folder, name, configured?, deploy enabled?,
|
|
81
|
+
// and transport. Live release/version is joined separately by `dharta status`
|
|
82
|
+
// when an authenticated profile is available.
|
|
83
|
+
export function renderTable(folders: AgentFolder[]): string {
|
|
84
|
+
const headers = ["AGENT FOLDER", "NAME", "HARNESS", "CONFIGURED", "DEPLOY", "METHOD"];
|
|
85
|
+
const rows = folders.map((f) => [
|
|
86
|
+
f.rel === "." ? "./ (repo root)" : `./${f.rel}`,
|
|
87
|
+
f.name ?? "-",
|
|
88
|
+
f.harnessType ?? "-",
|
|
89
|
+
f.configured ? "yes" : "no",
|
|
90
|
+
f.configured ? (f.deployEnabled ? "enabled" : "disabled") : "-",
|
|
91
|
+
f.deployMethod ?? "-",
|
|
92
|
+
]);
|
|
93
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
94
|
+
const fmt = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
95
|
+
return [fmt(headers), ...rows.map(fmt)].join("\n");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function shallowWalk(absRoot: string, rels: Set<string>): Promise<void> {
|
|
99
|
+
if (await hasMarker(absRoot)) rels.add(".");
|
|
100
|
+
let entries: import("node:fs").Dirent[];
|
|
101
|
+
try {
|
|
102
|
+
entries = await fs.readdir(absRoot, { withFileTypes: true });
|
|
103
|
+
} catch {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const e of entries) {
|
|
107
|
+
// Skip dot-dirs (incl. .git) and node_modules as CANDIDATES - a marker
|
|
108
|
+
// like .claude makes its PARENT an agent folder, not itself.
|
|
109
|
+
if (!e.isDirectory() || e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
110
|
+
if (await hasMarker(path.join(absRoot, e.name))) rels.add(e.name);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function hasMarker(dir: string): Promise<boolean> {
|
|
115
|
+
return hasAgentMarker(dir);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function describeFolder(dir: string, rel: string): Promise<AgentFolder> {
|
|
119
|
+
const folder: AgentFolder = { dir, rel: rel === "" ? "." : rel, configured: false, deployEnabled: false };
|
|
120
|
+
const tomlPath = path.join(dir, "dharta.toml");
|
|
121
|
+
if (!existsSync(tomlPath)) {
|
|
122
|
+
folder.harnessType = sniffHarnessType(dir);
|
|
123
|
+
return folder;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
folder.configured = true;
|
|
127
|
+
try {
|
|
128
|
+
const raw = await fs.readFile(tomlPath, "utf8");
|
|
129
|
+
const parsed = TOML.parse(raw) as Record<string, unknown>;
|
|
130
|
+
if (typeof parsed.name === "string") folder.name = parsed.name;
|
|
131
|
+
if (typeof parsed.slug === "string") folder.slug = parsed.slug;
|
|
132
|
+
folder.deployEnabled = parsed.deploy === true;
|
|
133
|
+
if (parsed.deploy_method === "git" || parsed.deploy_method === "folder") {
|
|
134
|
+
folder.deployMethod = parsed.deploy_method;
|
|
135
|
+
}
|
|
136
|
+
folder.harnessType = readDeclaredHarnessType(raw) ?? sniffHarnessType(dir);
|
|
137
|
+
} catch {
|
|
138
|
+
// A malformed dharta.toml is still a configured folder; it stays
|
|
139
|
+
// nameless here and the manifest writer / build server reports the
|
|
140
|
+
// parse error when it matters.
|
|
141
|
+
folder.harnessType = sniffHarnessType(dir);
|
|
142
|
+
}
|
|
143
|
+
return folder;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function gitLsFiles(args: string[], cwd: string): Promise<string | null> {
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
const proc = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
149
|
+
const out: Buffer[] = [];
|
|
150
|
+
proc.stdout.on("data", (c: Buffer) => out.push(c));
|
|
151
|
+
proc.on("error", () => resolve(null));
|
|
152
|
+
proc.on("close", (code) => resolve(code === 0 ? Buffer.concat(out).toString("utf8") : null));
|
|
153
|
+
});
|
|
154
|
+
}
|
package/src/templates.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Template resolution for `dharta setup` scaffolding (RFC 0013 CLI-2 / 0024
|
|
2
|
+
// DEP-4c, decision D9): fetch the current set from the control plane so CLI
|
|
3
|
+
// and dashboard scaffolds never drift, with the launch set bundled in the
|
|
4
|
+
// package as the offline fallback.
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
export interface Template {
|
|
11
|
+
key: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
files: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ResolvedTemplates {
|
|
18
|
+
templates: Template[];
|
|
19
|
+
source: "control-plane" | "bundled";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// How long the control-plane fetch gets before we give up and take the bundled
|
|
23
|
+
// set below. `dharta create` is often a new user's first command, so it must
|
|
24
|
+
// not hang on a blackholed network. Exported because anything that kills the
|
|
25
|
+
// CLI on its own clock - the packed-CLI smoke test - has to allow strictly
|
|
26
|
+
// longer than this: kill the process first and the fallback never runs, so the
|
|
27
|
+
// only evidence left is an empty stdout+stderr.
|
|
28
|
+
export const TEMPLATE_FETCH_TIMEOUT_MS = 3000;
|
|
29
|
+
|
|
30
|
+
export async function resolveTemplates(
|
|
31
|
+
baseUrl: string,
|
|
32
|
+
fetchImpl?: typeof fetch,
|
|
33
|
+
): Promise<ResolvedTemplates> {
|
|
34
|
+
try {
|
|
35
|
+
const f = (fetchImpl ?? globalThis.fetch).bind(globalThis);
|
|
36
|
+
const res = await f(`${baseUrl.replace(/\/+$/, "")}/agent_templates`, {
|
|
37
|
+
headers: { Accept: "application/json" },
|
|
38
|
+
signal: AbortSignal.timeout(TEMPLATE_FETCH_TIMEOUT_MS),
|
|
39
|
+
});
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
const body = (await res.json()) as { templates?: Template[] };
|
|
42
|
+
if (Array.isArray(body.templates) && body.templates.length > 0) {
|
|
43
|
+
return { templates: body.templates, source: "control-plane" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// fall through to the bundled set
|
|
48
|
+
}
|
|
49
|
+
return { templates: await bundledTemplates(), source: "bundled" };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The launch set shipped inside the package (templates/ at the package
|
|
53
|
+
// root, vendored from dharta-web's lib/agent_templates — a test pins the
|
|
54
|
+
// two trees identical).
|
|
55
|
+
export async function bundledTemplates(): Promise<Template[]> {
|
|
56
|
+
const root = bundledTemplatesRoot();
|
|
57
|
+
const meta: Record<string, { title: string; description: string }> = {
|
|
58
|
+
blank: {
|
|
59
|
+
title: "Blank",
|
|
60
|
+
description: "A minimal skeleton. Start from an empty native instruction outline.",
|
|
61
|
+
},
|
|
62
|
+
"faq-agent": {
|
|
63
|
+
title: "FAQ agent",
|
|
64
|
+
description:
|
|
65
|
+
"A worked support-agent example: persona, knowledge section, escalation rule.",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const templates: Template[] = [];
|
|
70
|
+
for (const key of Object.keys(meta)) {
|
|
71
|
+
const dir = path.join(root, key);
|
|
72
|
+
const files: Record<string, string> = {};
|
|
73
|
+
for (const name of await fs.readdir(dir)) {
|
|
74
|
+
files[name] = await fs.readFile(path.join(dir, name), "utf8");
|
|
75
|
+
}
|
|
76
|
+
templates.push({ key, ...meta[key], files });
|
|
77
|
+
}
|
|
78
|
+
return templates;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function bundledTemplatesRoot(): string {
|
|
82
|
+
// src/templates.ts compiles to dist/templates.js; templates/ sits at
|
|
83
|
+
// the package root either way.
|
|
84
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
85
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export function validateDateOnly(value: string, flag: string): void {
|
|
2
|
+
normalizeDateOnly(value, flag);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function normalizeDateOnly(value: string, flag: string): string {
|
|
6
|
+
const normalized = value.trim();
|
|
7
|
+
if (!isDateOnly(normalized)) {
|
|
8
|
+
throw new Error(`${flag} must use YYYY-MM-DD, e.g. 2026-05-18.`);
|
|
9
|
+
}
|
|
10
|
+
return normalized;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateIsoTimestamp(value: string, flag: string): void {
|
|
14
|
+
normalizeIsoTimestamp(value, flag);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function normalizeIsoTimestamp(value: string, flag: string): string {
|
|
18
|
+
const normalized = value.trim();
|
|
19
|
+
const match = normalized.match(/^(\d{4}-\d{2}-\d{2})T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw isoTimestampError(flag);
|
|
22
|
+
}
|
|
23
|
+
if (!isDateOnly(match[1]!)) throw isoTimestampError(flag);
|
|
24
|
+
|
|
25
|
+
const date = new Date(normalized);
|
|
26
|
+
if (Number.isNaN(date.getTime())) throw isoTimestampError(flag);
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseReleaseVersion(value: string, flag: string): number {
|
|
31
|
+
const normalized = value.trim();
|
|
32
|
+
if (!normalized) throw new Error(`${flag} cannot be blank.`);
|
|
33
|
+
const stripped = normalized.startsWith("v") ? normalized.slice(1) : normalized;
|
|
34
|
+
if (!/^[1-9]\d*$/.test(stripped)) {
|
|
35
|
+
throw new Error(`${flag} must be a release version like v3 or 3.`);
|
|
36
|
+
}
|
|
37
|
+
const parsed = Number(stripped);
|
|
38
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
39
|
+
throw new Error(`${flag} must be a release version like v3 or 3.`);
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parsePositiveInteger(value: string, label: string): number {
|
|
45
|
+
const normalized = value.trim();
|
|
46
|
+
if (!/^[1-9]\d*$/.test(normalized)) {
|
|
47
|
+
throw new Error(`${label} must be a positive whole number.`);
|
|
48
|
+
}
|
|
49
|
+
const parsed = Number(normalized);
|
|
50
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
51
|
+
throw new Error(`${label} must be a positive whole number.`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function normalizeNonBlank(value: string, label: string): string {
|
|
57
|
+
const normalized = value.trim();
|
|
58
|
+
if (!normalized) throw new Error(`${label} cannot be blank.`);
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function normalizeHttpUrl(value: string, flag: string): string {
|
|
63
|
+
const normalized = normalizeNonBlank(value, flag);
|
|
64
|
+
let parsed: URL;
|
|
65
|
+
try {
|
|
66
|
+
parsed = new URL(normalized);
|
|
67
|
+
} catch {
|
|
68
|
+
throw new Error(`${flag} must be an absolute http(s) URL.`);
|
|
69
|
+
}
|
|
70
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
71
|
+
throw new Error(`${flag} must be an absolute http(s) URL.`);
|
|
72
|
+
}
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function normalizeTimezone(value: string, flag: string): string {
|
|
77
|
+
const normalized = normalizeNonBlank(value, flag);
|
|
78
|
+
try {
|
|
79
|
+
Intl.DateTimeFormat("en-US", { timeZone: normalized }).format(new Date());
|
|
80
|
+
} catch {
|
|
81
|
+
throw new Error(`${flag} must be a valid IANA timezone, e.g. UTC or America/Los_Angeles.`);
|
|
82
|
+
}
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function closestChoice(value: string, allowed: readonly string[]): string | null {
|
|
87
|
+
if (value.length === 0) return null;
|
|
88
|
+
let best: { choice: string; distance: number } | null = null;
|
|
89
|
+
for (const choice of allowed) {
|
|
90
|
+
const distance = levenshtein(value, choice);
|
|
91
|
+
if (distance <= 2 && (!best || distance < best.distance)) {
|
|
92
|
+
best = { choice, distance };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return best?.choice ?? null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isDateOnly(value: string): boolean {
|
|
99
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
100
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
101
|
+
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isoTimestampError(flag: string): Error {
|
|
105
|
+
return new Error(`${flag} must be an ISO timestamp, e.g. 2026-05-18T00:00:00Z.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function levenshtein(a: string, b: string): number {
|
|
109
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
110
|
+
const curr = Array<number>(b.length + 1);
|
|
111
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
112
|
+
curr[0] = i;
|
|
113
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
114
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
115
|
+
curr[j] = Math.min(
|
|
116
|
+
curr[j - 1]! + 1,
|
|
117
|
+
prev[j]! + 1,
|
|
118
|
+
prev[j - 1]! + cost,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
prev.splice(0, prev.length, ...curr);
|
|
122
|
+
}
|
|
123
|
+
return prev[b.length]!;
|
|
124
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const NPM_LATEST_URL = "https://registry.npmjs.org/@dharta.ai%2fcli/latest";
|
|
5
|
+
|
|
6
|
+
export interface VersionCheckOptions {
|
|
7
|
+
root: string;
|
|
8
|
+
currentVersion?: string;
|
|
9
|
+
fetchImpl?: typeof fetch;
|
|
10
|
+
latestUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type VersionCheck =
|
|
14
|
+
| { status: "current"; current: string; latest: string }
|
|
15
|
+
| { status: "ahead"; current: string; latest: string }
|
|
16
|
+
| { status: "outdated"; current: string; latest: string }
|
|
17
|
+
| { status: "unknown"; reason: string; current?: string };
|
|
18
|
+
|
|
19
|
+
export async function checkNpmLatestVersion(opts: VersionCheckOptions): Promise<VersionCheck> {
|
|
20
|
+
const current = opts.currentVersion ?? (await readPackageVersion(opts.root));
|
|
21
|
+
if (!current) return { status: "unknown", reason: "could not read package.json" };
|
|
22
|
+
|
|
23
|
+
const latest = await fetchLatestVersion(opts);
|
|
24
|
+
if (!latest) return { status: "unknown", reason: "npm registry did not return a version", current };
|
|
25
|
+
|
|
26
|
+
const comparison = compareVersions(current, latest);
|
|
27
|
+
if (comparison === null) return { status: "unknown", reason: `could not compare ${current} with ${latest}`, current };
|
|
28
|
+
if (comparison === 0) return { status: "current", current, latest };
|
|
29
|
+
if (comparison > 0) return { status: "ahead", current, latest };
|
|
30
|
+
return { status: "outdated", current, latest };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readPackageVersion(root: string): Promise<string | null> {
|
|
34
|
+
try {
|
|
35
|
+
const raw = await fs.readFile(path.join(root, "package.json"), "utf8");
|
|
36
|
+
const parsed = JSON.parse(raw) as { version?: unknown };
|
|
37
|
+
return typeof parsed.version === "string" ? parsed.version : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function fetchLatestVersion(opts: VersionCheckOptions): Promise<string | null> {
|
|
44
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
45
|
+
const url = opts.latestUrl ?? NPM_LATEST_URL;
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetchImpl(url, { signal: controller.signal });
|
|
50
|
+
if (!res.ok) return null;
|
|
51
|
+
const body = (await res.json()) as { version?: unknown };
|
|
52
|
+
return typeof body.version === "string" ? body.version : null;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
} finally {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compareVersions(a: string, b: string): -1 | 0 | 1 | null {
|
|
61
|
+
const left = parseVersion(a);
|
|
62
|
+
const right = parseVersion(b);
|
|
63
|
+
if (!left || !right) return null;
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
if (left[i] < right[i]) return -1;
|
|
66
|
+
if (left[i] > right[i]) return 1;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseVersion(version: string): [number, number, number] | null {
|
|
72
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
73
|
+
if (!match) return null;
|
|
74
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
75
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# My agent
|
|
2
|
+
|
|
3
|
+
Describe who your agent is and how it should behave. This file is the
|
|
4
|
+
agent's instructions - it reads them at the start of every conversation.
|
|
5
|
+
|
|
6
|
+
## Personality
|
|
7
|
+
|
|
8
|
+
- Friendly and concise.
|
|
9
|
+
|
|
10
|
+
## What you can help with
|
|
11
|
+
|
|
12
|
+
- Replace this list with the things your agent should handle.
|
|
13
|
+
|
|
14
|
+
## Knowledge
|
|
15
|
+
|
|
16
|
+
Paste the facts your agent should know here: product details, policies,
|
|
17
|
+
FAQ answers. The agent sees this whole file every conversation.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- If you don't know the answer, say so honestly. Never make things up.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Dharta deploy manifest. This source template is Claude-shaped; create flows
|
|
2
|
+
# keep CLAUDE.md for Claude Code or translate it to the selected harness's
|
|
3
|
+
# native instruction file. No build step or entry module is needed.
|
|
4
|
+
[harness]
|
|
5
|
+
type = "claude-code"
|
|
6
|
+
|
|
7
|
+
# Declare the environment variables your agent depends on; bind them with
|
|
8
|
+
# .env locally and `dharta env set` in production (declare-or-bind).
|
|
9
|
+
[env]
|
|
10
|
+
# Example:
|
|
11
|
+
# MY_API_URL = { required = true, description = "Backend base URL" }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Sage - the Acme Outfitters support assistant
|
|
2
|
+
|
|
3
|
+
You are **Sage**, the friendly support assistant for **Acme Outfitters**,
|
|
4
|
+
an online outdoor-gear shop.
|
|
5
|
+
|
|
6
|
+
> Replace the name, company, and the Knowledge section below with your
|
|
7
|
+
> own. Everything in this file is the agent's instructions - it reads the
|
|
8
|
+
> whole file at the start of every conversation.
|
|
9
|
+
|
|
10
|
+
## Personality
|
|
11
|
+
|
|
12
|
+
- Warm, clear, and brief. Sound like a helpful human, not a script.
|
|
13
|
+
- Greet a brand-new conversation with one short, welcoming line.
|
|
14
|
+
- Keep replies to 1-3 sentences unless the customer asks for detail.
|
|
15
|
+
|
|
16
|
+
## Knowledge
|
|
17
|
+
|
|
18
|
+
Answer questions using only the facts below. If the answer isn't here,
|
|
19
|
+
say so honestly instead of guessing.
|
|
20
|
+
|
|
21
|
+
### Shipping
|
|
22
|
+
|
|
23
|
+
- Standard shipping takes 3-5 business days and costs $5.
|
|
24
|
+
- Orders over $50 ship free.
|
|
25
|
+
- We currently ship within the United States only.
|
|
26
|
+
|
|
27
|
+
### Returns & exchanges
|
|
28
|
+
|
|
29
|
+
- Unused items can be returned within 30 days for a full refund.
|
|
30
|
+
- Exchanges are free - we email a prepaid return label.
|
|
31
|
+
- Sale items are final sale.
|
|
32
|
+
|
|
33
|
+
### Products
|
|
34
|
+
|
|
35
|
+
- *TrailLite* day pack: 22L, water-resistant, fits a 15" laptop.
|
|
36
|
+
- *Basecamp* tent: 2-person, three-season, sets up in under 5 minutes.
|
|
37
|
+
- *Ember* insulated bottle: keeps drinks hot 12 hours, cold 24 hours.
|
|
38
|
+
|
|
39
|
+
### Account & orders
|
|
40
|
+
|
|
41
|
+
- Order confirmations and tracking links are emailed automatically.
|
|
42
|
+
- Customers can check order status from the link in their confirmation
|
|
43
|
+
email.
|
|
44
|
+
|
|
45
|
+
## Escalation
|
|
46
|
+
|
|
47
|
+
If the customer asks about something not covered here - a specific
|
|
48
|
+
order's status, a billing problem, or anything that needs a human - say
|
|
49
|
+
you'll connect them with the team and ask them to email
|
|
50
|
+
**support@example.com**. Never invent order details or make promises on
|
|
51
|
+
the team's behalf.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Dharta deploy manifest. This source template is Claude-shaped; create flows
|
|
2
|
+
# keep CLAUDE.md for Claude Code or translate it to the selected harness's
|
|
3
|
+
# native instruction file. No build step or entry module is needed.
|
|
4
|
+
[harness]
|
|
5
|
+
type = "claude-code"
|
|
6
|
+
|
|
7
|
+
# Declare the environment variables your agent depends on; bind them with
|
|
8
|
+
# .env locally and `dharta env set` in production (declare-or-bind).
|
|
9
|
+
[env]
|
|
10
|
+
# Example:
|
|
11
|
+
# MY_API_URL = { required = true, description = "Backend base URL" }
|