@2kw/ai 6.2.0-dev.23 → 6.2.0-dev.25

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.
Files changed (39) hide show
  1. package/README.md +2 -2
  2. package/dist/agent-config/export.d.ts +31 -0
  3. package/dist/agent-config/export.js +87 -0
  4. package/dist/agent-config/file.d.ts +20 -0
  5. package/dist/agent-config/file.js +137 -0
  6. package/dist/agent-config/plan.d.ts +33 -0
  7. package/dist/agent-config/plan.js +100 -0
  8. package/dist/agent-config/schema.d.ts +420 -0
  9. package/dist/agent-config/schema.js +222 -0
  10. package/dist/agent-config/template.d.ts +6 -0
  11. package/dist/agent-config/template.js +59 -0
  12. package/dist/agent-config/validate.d.ts +29 -0
  13. package/dist/agent-config/validate.js +123 -0
  14. package/dist/commands/agent-apply.d.ts +9 -0
  15. package/dist/commands/agent-apply.js +176 -0
  16. package/dist/commands/agent-export.d.ts +3 -0
  17. package/dist/commands/agent-export.js +100 -0
  18. package/dist/commands/agent-init.d.ts +4 -0
  19. package/dist/commands/agent-init.js +54 -0
  20. package/dist/commands/agent-run.d.ts +5 -0
  21. package/dist/commands/agent-run.js +187 -0
  22. package/dist/commands/agent-versions.js +12 -7
  23. package/dist/commands/agents.js +24 -9
  24. package/dist/commands/ai.d.ts +5 -37
  25. package/dist/commands/ai.js +29 -136
  26. package/dist/lib/agent-decide.d.ts +40 -0
  27. package/dist/lib/agent-decide.js +128 -0
  28. package/dist/lib/agent-lookup.d.ts +14 -0
  29. package/dist/lib/agent-lookup.js +70 -0
  30. package/dist/lib/agent-models.d.ts +28 -0
  31. package/dist/lib/agent-models.js +53 -0
  32. package/dist/lib/agent-run.d.ts +83 -0
  33. package/dist/lib/agent-run.js +170 -0
  34. package/dist/lib/approval-prompt.d.ts +18 -0
  35. package/dist/lib/approval-prompt.js +84 -0
  36. package/dist/lib/client.js +6 -2
  37. package/dist/lib/errors.d.ts +12 -0
  38. package/dist/lib/errors.js +46 -2
  39. package/package.json +5 -2
@@ -0,0 +1,59 @@
1
+ import { stringify } from "yaml";
2
+ import { AGENT_SCHEMA_ID } from "./schema.js";
3
+ /**
4
+ * Commented starter file. Prose comments start with "# " + an uppercase letter; YAML
5
+ * examples start with "# " + a lowercase key, a space or "-" (the template test relies on it).
6
+ */
7
+ export function agentTemplate(name) {
8
+ const nameLine = stringify({ name }, { lineWidth: 0 }).trimEnd();
9
+ return `# yaml-language-server: $schema=${AGENT_SCHEMA_ID}
10
+ # Apply with: 2kw agents apply -f <this file> --dry-run, then without --dry-run.
11
+ # A change to model, instructions, tools, hitlPolicy, options or skills publishes a new version and moves the latest label.
12
+ # Full reference: 2kw agents schema
13
+ ${nameLine}
14
+ description: What this agent is for.
15
+ # List models with: 2kw ai models
16
+ model: provider/model-name
17
+ # Or an ordered list: the first entry is the default, pick another per run with 2kw agents run <agent> --model <entry>.
18
+ # models:
19
+ # - provider/model-name
20
+ # - provider/other-model
21
+ instructions: |
22
+ Describe the agent's job, its inputs, and what a good answer looks like.
23
+ tools: []
24
+ # Uncomment the tools this agent may use and delete the empty list above.
25
+ # tools:
26
+ # - type: file_search
27
+ # config:
28
+ # knowledgeBaseIds: [kb_id]
29
+ # - type: backbone.extraction
30
+ # config:
31
+ # schemaIds: [schema_id]
32
+ # - type: backbone.document_convert
33
+ # - type: backbone.todo
34
+ # - type: backbone.skill
35
+ # skills: [skill-name@latest]
36
+ # - type: function
37
+ # name: lookup_order
38
+ # description: Look up an order by number
39
+ # endpoint: https://example.com/hooks/lookup-order
40
+ # secret: \${AGENT_LOOKUP_ORDER_SECRET}
41
+ # parameters:
42
+ # type: object
43
+ # properties:
44
+ # order: { type: string }
45
+ # required: [order]
46
+ # annotations: { readOnlyHint: true, destructiveHint: false }
47
+ # Approval policy. Defaults: read allow, write approve, destructive block. Rules can only tighten.
48
+ # hitlPolicy:
49
+ # version: 1
50
+ # classes: { read: allow, write: approve, destructive: block }
51
+ # tools:
52
+ # "lookup_*": read
53
+ # Skill bindings, at most 20. Ref is a label or version number; default latest.
54
+ # skills:
55
+ # - name: skill-name
56
+ # ref: latest
57
+ `;
58
+ }
59
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1,29 @@
1
+ export interface ConfigIssue {
2
+ pointer: string;
3
+ message: string;
4
+ }
5
+ export interface AgentConfig {
6
+ name: string;
7
+ description?: string;
8
+ /** One model; with `models`, equal to its first entry. At least one of the two is present. */
9
+ model?: string;
10
+ /** Ordered model list (#591): first = default. */
11
+ models?: string[];
12
+ instructions?: string;
13
+ tools?: Record<string, unknown>[];
14
+ hitlPolicy?: Record<string, unknown>;
15
+ options?: Record<string, unknown>;
16
+ skills?: {
17
+ name: string;
18
+ ref?: string;
19
+ }[];
20
+ }
21
+ /**
22
+ * A deep copy of `doc` in the form the backend actually checks: explicit nulls it treats as absent
23
+ * are dropped, and the parts of hitlPolicy it matches case-insensitively are lowercased
24
+ * (HitlPolicy.java parseClasses/action/parseTools). The structure is unchanged, so issue pointers
25
+ * still address the author's file. Only for validation: callers send the raw document.
26
+ */
27
+ export declare function normalizeForValidation(doc: unknown): unknown;
28
+ export declare function validateAgentConfig(raw: unknown): ConfigIssue[];
29
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1,123 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { agentSchema, NON_AUTHORABLE_TOOL_TYPES } from "./schema.js";
3
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
4
+ const validate = ajv.compile(agentSchema);
5
+ /** Top-level keys whose explicit `null` the backend reads as absent. */
6
+ const NULLABLE_KEYS = ["description", "models", "instructions", "tools", "hitlPolicy", "options", "skills"];
7
+ /** Tool-rule values the backend matches case-insensitively; the actions (approve, auto, deny) are exact. */
8
+ const TOOL_RULE_CLASSES = new Set(["read", "write", "destructive"]);
9
+ const isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
10
+ /**
11
+ * A deep copy of `doc` in the form the backend actually checks: explicit nulls it treats as absent
12
+ * are dropped, and the parts of hitlPolicy it matches case-insensitively are lowercased
13
+ * (HitlPolicy.java parseClasses/action/parseTools). The structure is unchanged, so issue pointers
14
+ * still address the author's file. Only for validation: callers send the raw document.
15
+ */
16
+ export function normalizeForValidation(doc) {
17
+ if (!isPlainObject(doc))
18
+ return doc;
19
+ const copy = structuredClone(doc);
20
+ for (const key of NULLABLE_KEYS) {
21
+ if (copy[key] === null)
22
+ delete copy[key];
23
+ }
24
+ if (Array.isArray(copy.skills)) {
25
+ for (const skill of copy.skills) {
26
+ if (isPlainObject(skill) && skill.ref === null)
27
+ delete skill.ref;
28
+ }
29
+ }
30
+ if (Array.isArray(copy.tools)) {
31
+ for (const tool of copy.tools) {
32
+ if (isPlainObject(tool) && tool.annotations === null)
33
+ delete tool.annotations;
34
+ }
35
+ }
36
+ const policy = copy.hitlPolicy;
37
+ if (isPlainObject(policy)) {
38
+ const lower = (v) => (typeof v === "string" ? v.toLowerCase() : v);
39
+ if ("default" in policy)
40
+ policy.default = lower(policy.default);
41
+ if (isPlainObject(policy.classes)) {
42
+ policy.classes = Object.fromEntries(Object.entries(policy.classes).map(([k, v]) => [k.toLowerCase(), lower(v)]));
43
+ }
44
+ if (isPlainObject(policy.tools)) {
45
+ policy.tools = Object.fromEntries(Object.entries(policy.tools).map(([pattern, v]) => {
46
+ const folded = lower(v);
47
+ return [pattern, typeof folded === "string" && TOOL_RULE_CLASSES.has(folded) ? folded : v];
48
+ }));
49
+ }
50
+ }
51
+ return copy;
52
+ }
53
+ function describe(e) {
54
+ // if/then wrappers restate the real error one level up; drop them. `not` only marks
55
+ // non-authorable tool types, which validateAgentConfig reports with the reason.
56
+ if (e.keyword === "if" || e.keyword === "false schema" || e.keyword === "not")
57
+ return undefined;
58
+ const pointer = e.instancePath || "/";
59
+ const params = e.params;
60
+ // The root anyOf is "model or models"; its two branch errors restate the one message below.
61
+ if (e.schemaPath.startsWith("#/anyOf/"))
62
+ return undefined;
63
+ if (e.schemaPath === "#/anyOf")
64
+ return { pointer, message: 'missing required key "model" (or "models")' };
65
+ if (e.keyword === "additionalProperties") {
66
+ return { pointer, message: `unknown key "${params.additionalProperty}"` };
67
+ }
68
+ if (e.keyword === "required") {
69
+ return { pointer, message: `missing required key "${params.missingProperty}"` };
70
+ }
71
+ if (e.keyword === "enum") {
72
+ const allowed = params.allowedValues.filter((v) => typeof v !== "string" || v === v.toLowerCase());
73
+ return { pointer, message: `must be one of the allowed values: ${allowed.join(", ")}` };
74
+ }
75
+ if (e.keyword === "const") {
76
+ return { pointer, message: `must be ${JSON.stringify(params.allowedValue)}` };
77
+ }
78
+ return { pointer, message: e.message ?? e.keyword };
79
+ }
80
+ export function validateAgentConfig(raw) {
81
+ const doc = normalizeForValidation(raw);
82
+ const issues = [];
83
+ if (!validate(doc)) {
84
+ for (const e of validate.errors ?? []) {
85
+ const issue = describe(e);
86
+ if (issue)
87
+ issues.push(issue);
88
+ }
89
+ }
90
+ const record = (doc ?? {});
91
+ // The backend derives model := models[0] and refuses a different explicit model (400).
92
+ if (typeof record.model === "string" && Array.isArray(record.models) && typeof record.models[0] === "string") {
93
+ if (record.model !== record.models[0]) {
94
+ issues.push({
95
+ pointer: "/model",
96
+ message: `must equal the first entry of models ("${record.models[0]}"), the default; or drop model`,
97
+ });
98
+ }
99
+ }
100
+ if (Array.isArray(record.tools)) {
101
+ record.tools.forEach((tool, i) => {
102
+ const type = tool?.type;
103
+ if (typeof type === "string" && Object.hasOwn(NON_AUTHORABLE_TOOL_TYPES, type)) {
104
+ issues.push({ pointer: `/tools/${i}/type`, message: `type "${type}" cannot be authored: ${NON_AUTHORABLE_TOOL_TYPES[type]}` });
105
+ }
106
+ });
107
+ }
108
+ if (Array.isArray(record.skills)) {
109
+ const seen = new Set();
110
+ record.skills.forEach((skill, i) => {
111
+ const name = skill?.name;
112
+ if (typeof name !== "string")
113
+ return;
114
+ if (seen.has(name))
115
+ issues.push({ pointer: `/skills/${i}/name`, message: `duplicate skill "${name}"` });
116
+ seen.add(name);
117
+ });
118
+ }
119
+ // Dedupe on the (pointer, message) pair; JSON keeps the key unambiguous without a control-character separator.
120
+ const unique = new Map(issues.map((i) => [JSON.stringify([i.pointer, i.message]), i]));
121
+ return [...unique.values()];
122
+ }
123
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,9 @@
1
+ import { Command } from "commander";
2
+ import type { components } from "../generated/openapi.js";
3
+ import { type ApiClient } from "../lib/agent-lookup.js";
4
+ import type { AgentConfig } from "../agent-config/validate.js";
5
+ export type AgentVersionDTO = components["schemas"]["AgentVersionDTO"];
6
+ export declare function getLatestVersion(client: ApiClient, agentId: string): Promise<AgentVersionDTO | undefined>;
7
+ export declare function versionBody(config: AgentConfig, changeDescription: string): Record<string, unknown>;
8
+ export declare function makeAgentApplyCommand(): Command;
9
+ //# sourceMappingURL=agent-apply.d.ts.map
@@ -0,0 +1,176 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { getClient, runAction } from "../lib/client.js";
4
+ import { isJsonOutput } from "../lib/config.js";
5
+ import { BackboneApiError, CliUsageError } from "../lib/errors.js";
6
+ import { findAgentByName } from "../lib/agent-lookup.js";
7
+ import { loadAgentConfig, maskSecrets } from "../agent-config/file.js";
8
+ import { CONFIG_FIELDS, planApply } from "../agent-config/plan.js";
9
+ export async function getLatestVersion(client, agentId) {
10
+ try {
11
+ const { data } = await client.GET("/v1/agents/{agentId}/versions/latest", { params: { path: { agentId } } });
12
+ return data;
13
+ }
14
+ catch (err) {
15
+ if (err instanceof BackboneApiError && err.status === 404)
16
+ return undefined;
17
+ throw err;
18
+ }
19
+ }
20
+ export function versionBody(config, changeDescription) {
21
+ const body = {};
22
+ const source = config;
23
+ for (const field of CONFIG_FIELDS) {
24
+ if (source[field] !== undefined)
25
+ body[field] = source[field];
26
+ }
27
+ body.changeDescription = changeDescription;
28
+ return body;
29
+ }
30
+ /**
31
+ * The complete agent DTO for POST /v1/agents and PUT /v1/agents/{id}. The backend's PUT maps every DTO field
32
+ * onto the row without skipping nulls (#624), so an absent key clears the column: `skills` (NOT NULL) must
33
+ * always be sent, and a PUT must carry `latestVersionId` or the agent loses its head pointer. Nullable
34
+ * columns (instructions, options, hitl_policy) are omitted when the file omits them, which clears them to
35
+ * SQL NULL exactly as createVersion does; an explicit JSON null would arrive as a NullNode instead.
36
+ */
37
+ function agentBody(config, latestVersionId) {
38
+ const body = {
39
+ name: config.name,
40
+ description: config.description ?? "",
41
+ };
42
+ // `!= null`: the config is the raw file, which may spell an absent value as `null`.
43
+ // The backend derives model := models[0], so the list alone is a complete pair (#591).
44
+ if (config.model != null)
45
+ body.model = config.model;
46
+ if (config.models != null)
47
+ body.models = config.models;
48
+ if (config.instructions != null)
49
+ body.instructions = config.instructions;
50
+ body.tools = config.tools ?? [];
51
+ if (config.hitlPolicy != null)
52
+ body.hitlPolicy = config.hitlPolicy;
53
+ if (config.options != null)
54
+ body.options = config.options;
55
+ body.skills = config.skills ?? [];
56
+ if (latestVersionId !== undefined)
57
+ body.latestVersionId = latestVersionId;
58
+ return body;
59
+ }
60
+ async function moveLabel(client, agentId, label, versionId) {
61
+ const { data: labels } = await client.GET("/v1/agents/{agentId}/labels", { params: { path: { agentId } } });
62
+ if ((labels ?? []).some((l) => l.name === label)) {
63
+ await client.PUT("/v1/agents/{agentId}/labels/{labelName}", {
64
+ params: { path: { agentId, labelName: label } },
65
+ body: { agentVersionId: versionId },
66
+ });
67
+ }
68
+ else {
69
+ await client.POST("/v1/agents/{agentId}/labels", {
70
+ params: { path: { agentId } },
71
+ body: { name: label, agentVersionId: versionId },
72
+ });
73
+ }
74
+ }
75
+ function printText(result) {
76
+ const verb = result.dryRun ? `would be ${result.action}` : result.action;
77
+ const version = result.version !== null
78
+ ? ` (version ${result.version}; ${result.labels.join(", ")})`
79
+ : result.dryRun
80
+ ? ` (${result.labels.join(", ")})`
81
+ : "";
82
+ console.log(`${chalk.bold(result.agent)} ${verb}${version}`);
83
+ for (const c of result.changes) {
84
+ const mark = c.from === undefined ? chalk.green("+") : c.to === undefined ? chalk.red("-") : chalk.yellow("~");
85
+ console.log(` ${mark} ${c.field}`);
86
+ }
87
+ }
88
+ export function makeAgentApplyCommand() {
89
+ return new Command("apply")
90
+ .description("Create or update an agent from agent.yaml; a changed config publishes a new version")
91
+ .requiredOption("-f, --file <path>", "Agent file (.yaml, .yml or .json; - for stdin)")
92
+ .option("--label <name>", "Also point this label at the resulting version")
93
+ .option("-m, --message <text>", "Change description for a new version", "2kw agents apply")
94
+ .option("--dry-run", "Validate and show the plan without writing")
95
+ .action(async (opts, command) => {
96
+ await runAction(command, async () => {
97
+ if (opts.label === "latest") {
98
+ throw new CliUsageError("The latest label moves automatically; choose another label name.");
99
+ }
100
+ const { config, secrets } = loadAgentConfig(opts.file, process.env);
101
+ const client = getClient(command);
102
+ const agent = await findAgentByName(client, config.name);
103
+ const latest = agent ? await getLatestVersion(client, String(agent.id)) : undefined;
104
+ const plan = planApply(config, agent ? { agent, latest: latest } : undefined);
105
+ let agentId = agent?.id ? String(agent.id) : null;
106
+ let version = null;
107
+ // A dry run reports the labels that would apply; a real run only those it actually set.
108
+ const labels = opts.label && opts.dryRun ? ["latest", opts.label] : ["latest"];
109
+ if (!opts.dryRun) {
110
+ let versionId = latest?.id;
111
+ version = latest?.versionNumber ?? null;
112
+ if (plan.action === "created") {
113
+ let created;
114
+ try {
115
+ ({ data: created } = await client.POST("/v1/agents", {
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
+ body: agentBody(config),
118
+ }));
119
+ }
120
+ catch (err) {
121
+ if (err instanceof BackboneApiError && err.status === 500) {
122
+ throw new BackboneApiError({
123
+ status: err.status,
124
+ error: err.errorType,
125
+ message: `${err.message} — an agent named "${config.name}" may already exist`,
126
+ timestamp: err.timestamp,
127
+ code: err.code,
128
+ });
129
+ }
130
+ throw err;
131
+ }
132
+ if (!created?.id)
133
+ throw new Error("Agent was created but the response has no id");
134
+ agentId = String(created.id);
135
+ }
136
+ if (plan.action === "created" || plan.action === "versioned") {
137
+ const { data: v } = await client.POST("/v1/agents/{agentId}/versions", {
138
+ params: { path: { agentId: agentId } },
139
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140
+ body: versionBody(config, opts.message),
141
+ });
142
+ versionId = v?.id;
143
+ version = v?.versionNumber ?? null;
144
+ }
145
+ if ((plan.action === "versioned" && plan.descriptionChanged) || plan.action === "updated") {
146
+ // PUT replaces the whole agent row: send the file's config (never the fetched agent's, which is
147
+ // stale after a new version) and the head pointer, the new version's id when one was just published.
148
+ const head = plan.action === "versioned" ? versionId : agent?.latestVersionId;
149
+ await client.PUT("/v1/agents/{id}", {
150
+ params: { path: { id: agentId } },
151
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
+ body: agentBody(config, head),
153
+ });
154
+ }
155
+ if (opts.label && versionId) {
156
+ await moveLabel(client, agentId, opts.label, versionId);
157
+ labels.push(opts.label);
158
+ }
159
+ }
160
+ const result = maskSecrets({
161
+ agent: config.name,
162
+ agentId,
163
+ action: plan.action,
164
+ dryRun: !!opts.dryRun,
165
+ version,
166
+ labels,
167
+ changes: plan.changes,
168
+ }, secrets);
169
+ if (isJsonOutput(command))
170
+ console.log(JSON.stringify(result, null, 2));
171
+ else
172
+ printText(result);
173
+ });
174
+ });
175
+ }
176
+ //# sourceMappingURL=agent-apply.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function makeAgentExportCommand(): Command;
3
+ //# sourceMappingURL=agent-export.d.ts.map
@@ -0,0 +1,100 @@
1
+ import { Command } from "commander";
2
+ import { existsSync, statSync, writeFileSync } from "node:fs";
3
+ import { getClient, runAction } from "../lib/client.js";
4
+ import { isJsonOutput } from "../lib/config.js";
5
+ import { CliUsageError } from "../lib/errors.js";
6
+ import { resolveAgent, splitAgentRef } from "../lib/agent-lookup.js";
7
+ import { dropInertTools, escapePlaceholders, redactSecrets, toAgentYaml, versionToConfig, } from "../agent-config/export.js";
8
+ import { getLatestVersion } from "./agent-apply.js";
9
+ /**
10
+ * Exclusive create unless forced: the early existence check runs before any request, so a file
11
+ * created while the export was fetching must still not be overwritten.
12
+ */
13
+ function writeOutput(file, text, force) {
14
+ try {
15
+ writeFileSync(file, text, { flag: force ? "w" : "wx" });
16
+ }
17
+ catch (err) {
18
+ const code = err.code;
19
+ // "wx" reports an existing directory as EEXIST too, where --force would not help.
20
+ const isDirectory = code === "EEXIST" && (statSync(file, { throwIfNoEntry: false })?.isDirectory() ?? false);
21
+ if (code === "EEXIST" && !isDirectory) {
22
+ throw new CliUsageError(`${file} exists. Pass --force to overwrite.`);
23
+ }
24
+ const reason = isDirectory ? "EISDIR" : (code ?? err.message);
25
+ throw new CliUsageError(`${file}: cannot write file (${reason})`);
26
+ }
27
+ }
28
+ export function makeAgentExportCommand() {
29
+ return new Command("export")
30
+ .description("Write an agent version as agent.yaml (webhook secrets become ${ENV} placeholders)")
31
+ .argument("<agent>", "Agent name or id, optionally with @label (default latest)")
32
+ .option("-o, --output <file>", "Write to a file instead of stdout")
33
+ .option("--force", "Overwrite an existing output file")
34
+ .option("--include-secrets", "Write webhook secrets in plaintext")
35
+ .action(async (ref, opts, command) => {
36
+ await runAction(command, async () => {
37
+ const { name, label = "latest", model } = splitAgentRef(ref);
38
+ if (model !== undefined) {
39
+ throw new CliUsageError("export takes <agent>[@label]; a #model override has no meaning here.");
40
+ }
41
+ if (opts.output && existsSync(opts.output) && !opts.force) {
42
+ throw new CliUsageError(`${opts.output} exists. Pass --force to overwrite.`);
43
+ }
44
+ const client = getClient(command);
45
+ const agent = await resolveAgent(client, name);
46
+ const agentId = String(agent.id);
47
+ let version;
48
+ if (label === "latest") {
49
+ version = await getLatestVersion(client, agentId);
50
+ }
51
+ else {
52
+ const { data: labels } = await client.GET("/v1/agents/{agentId}/labels", { params: { path: { agentId } } });
53
+ const hit = (labels ?? []).find((l) => l.name === label);
54
+ if (!hit?.agentVersionId) {
55
+ const known = (labels ?? []).map((l) => l.name).join(", ") || "none";
56
+ throw new CliUsageError(`Label '${label}' not found on ${agent.name}. Labels: ${known}`);
57
+ }
58
+ ({ data: version } = await client.GET("/v1/agents/{agentId}/versions/{versionId}", {
59
+ params: { path: { agentId, versionId: hit.agentVersionId } },
60
+ }));
61
+ }
62
+ if (!version)
63
+ throw new CliUsageError(`${agent.name} has no published version.`);
64
+ const config = escapePlaceholders(versionToConfig(agent, version));
65
+ const inert = dropInertTools(config.tools);
66
+ if (inert.tools)
67
+ config.tools = inert.tools;
68
+ else
69
+ delete config.tools;
70
+ if (inert.dropped.length) {
71
+ console.error(`Dropped tool entries that have no effect at run time: ${inert.dropped.join(", ")}`);
72
+ }
73
+ let envNames = [];
74
+ if (!opts.includeSecrets) {
75
+ const redacted = redactSecrets(config.tools);
76
+ if (redacted.tools)
77
+ config.tools = redacted.tools;
78
+ envNames = redacted.envNames;
79
+ }
80
+ const text = isJsonOutput(command)
81
+ ? `${JSON.stringify(config, null, 2)}\n`
82
+ : toAgentYaml(config, {
83
+ agent: agent.name,
84
+ label,
85
+ versionNumber: version.versionNumber ?? null,
86
+ date: new Date().toISOString().slice(0, 10),
87
+ });
88
+ if (opts.output) {
89
+ writeOutput(opts.output, text, Boolean(opts.force));
90
+ }
91
+ else {
92
+ process.stdout.write(text);
93
+ }
94
+ if (envNames.length) {
95
+ console.error(`Secrets replaced by placeholders; set before apply: ${envNames.join(", ")}`);
96
+ }
97
+ });
98
+ });
99
+ }
100
+ //# sourceMappingURL=agent-export.js.map
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ export declare function makeAgentInitCommand(): Command;
3
+ export declare function makeAgentSchemaCommand(): Command;
4
+ //# sourceMappingURL=agent-init.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { Command } from "commander";
2
+ import { statSync, writeFileSync } from "node:fs";
3
+ import { parse } from "yaml";
4
+ import { runAction } from "../lib/client.js";
5
+ import { CliUsageError } from "../lib/errors.js";
6
+ import { agentSchema } from "../agent-config/schema.js";
7
+ import { agentTemplate } from "../agent-config/template.js";
8
+ import { validateAgentConfig } from "../agent-config/validate.js";
9
+ export function makeAgentInitCommand() {
10
+ return new Command("init")
11
+ .description("Write a commented agent.yaml template")
12
+ .argument("<name>", "Agent name")
13
+ .option("-o, --output <file>", "Output file", "agent.yaml")
14
+ .option("--force", "Overwrite an existing file")
15
+ .action(async (rawName, opts, command) => {
16
+ await runAction(command, async () => {
17
+ const name = rawName.trim();
18
+ if (!name)
19
+ throw new CliUsageError("Agent name must not be empty.");
20
+ const text = agentTemplate(name);
21
+ const nameIssue = validateAgentConfig(parse(text)).find((issue) => issue.pointer === "/name");
22
+ if (nameIssue)
23
+ throw new CliUsageError(`Invalid agent name: ${nameIssue.message}`);
24
+ writeTemplate(opts.output, text, Boolean(opts.force));
25
+ console.error(`Wrote ${opts.output}. Next: 2kw agents apply -f ${opts.output} --dry-run`);
26
+ });
27
+ });
28
+ }
29
+ /** Exclusive create unless forced, so nothing is overwritten between a check and the write. */
30
+ function writeTemplate(file, text, force) {
31
+ try {
32
+ writeFileSync(file, text, { flag: force ? "w" : "wx" });
33
+ }
34
+ catch (err) {
35
+ const code = err.code;
36
+ // "wx" reports an existing directory as EEXIST too, where --force would not help.
37
+ const isDirectory = code === "EEXIST" && (statSync(file, { throwIfNoEntry: false })?.isDirectory() ?? false);
38
+ if (code === "EEXIST" && !isDirectory) {
39
+ throw new CliUsageError(`${file} exists. Pass --force to overwrite.`);
40
+ }
41
+ const reason = isDirectory ? "EISDIR" : (code ?? err.message);
42
+ throw new CliUsageError(`${file}: cannot write file (${reason})`);
43
+ }
44
+ }
45
+ export function makeAgentSchemaCommand() {
46
+ return new Command("schema")
47
+ .description("Print the JSON Schema for agent.yaml")
48
+ .action(async (_opts, command) => {
49
+ await runAction(command, async () => {
50
+ console.log(JSON.stringify(agentSchema, null, 2));
51
+ });
52
+ });
53
+ }
54
+ //# sourceMappingURL=agent-init.js.map
@@ -0,0 +1,5 @@
1
+ import { Command } from "commander";
2
+ export declare function resolveRunInput(arg: string | undefined, inputFile: string | undefined, readStdin?: () => string | undefined): string;
3
+ export declare function makeAgentRunCommand(): Command;
4
+ export declare function makeAgentDecideCommand(): Command;
5
+ //# sourceMappingURL=agent-run.d.ts.map