@2kw/ai 6.3.0-dev.12 → 6.3.0-dev.122

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 (53) hide show
  1. package/README.md +4 -1
  2. package/dist/agent-config/schema.d.ts +6 -3
  3. package/dist/agent-config/schema.js +15 -4
  4. package/dist/agent-config/template.js +1 -1
  5. package/dist/commands/agent-apply.js +1 -1
  6. package/dist/commands/agent-policy.d.ts +2 -1
  7. package/dist/commands/agent-policy.js +9 -2
  8. package/dist/commands/agent-run.d.ts +5 -0
  9. package/dist/commands/agent-run.js +47 -21
  10. package/dist/commands/agents.js +33 -6
  11. package/dist/commands/ai.js +3 -9
  12. package/dist/commands/auth.js +6 -1
  13. package/dist/commands/billing.js +5 -3
  14. package/dist/commands/config.d.ts +1 -1
  15. package/dist/commands/config.js +16 -2
  16. package/dist/commands/connectors.js +2 -1
  17. package/dist/commands/conversations.js +16 -0
  18. package/dist/commands/datasets.js +17 -15
  19. package/dist/commands/experiments.js +36 -26
  20. package/dist/commands/files.js +10 -28
  21. package/dist/commands/installations.js +46 -1
  22. package/dist/commands/knowledge-documents.js +24 -32
  23. package/dist/commands/knowledge.js +5 -0
  24. package/dist/commands/memory.d.ts +10 -0
  25. package/dist/commands/memory.js +132 -0
  26. package/dist/commands/prompts.js +9 -8
  27. package/dist/commands/schemas.js +15 -6
  28. package/dist/commands/settings.d.ts +13 -0
  29. package/dist/commands/settings.js +80 -0
  30. package/dist/commands/skill-versions.js +27 -1
  31. package/dist/commands/skills.js +31 -1
  32. package/dist/commands/tracing.js +12 -17
  33. package/dist/index.js +4 -0
  34. package/dist/lib/agent-decide.d.ts +29 -2
  35. package/dist/lib/agent-decide.js +100 -3
  36. package/dist/lib/agent-run.d.ts +50 -3
  37. package/dist/lib/agent-run.js +166 -16
  38. package/dist/lib/approval-prompt.js +31 -1
  39. package/dist/lib/client.d.ts +8 -0
  40. package/dist/lib/client.js +20 -1
  41. package/dist/lib/config.d.ts +16 -0
  42. package/dist/lib/config.js +26 -1
  43. package/dist/lib/connect-pause.d.ts +71 -0
  44. package/dist/lib/connect-pause.js +147 -0
  45. package/dist/lib/errors.d.ts +6 -0
  46. package/dist/lib/errors.js +8 -3
  47. package/dist/lib/overlay.d.ts +10 -0
  48. package/dist/lib/overlay.js +20 -0
  49. package/dist/lib/skills-apply-preview.d.ts +19 -0
  50. package/dist/lib/skills-apply-preview.js +94 -0
  51. package/dist/lib/tracing-settings.d.ts +26 -0
  52. package/dist/lib/tracing-settings.js +25 -0
  53. package/package.json +1 -1
@@ -5,6 +5,7 @@ import Table from "cli-table3";
5
5
  import { getClient, runAction } from "../lib/client.js";
6
6
  import { formatPage, formatDetail, formatList, formatSuccess, withSpinner } from "../lib/output.js";
7
7
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
8
+ import { assertNameNotBlank, overlay } from "../lib/overlay.js";
8
9
  import { isJsonOutput } from "../lib/config.js";
9
10
  const TERMINAL_STATUSES = new Set(["COMPLETED", "FAILED"]);
10
11
  /** Summarize a single cell of the comparison matrix (a variant's result for one item). */
@@ -92,21 +93,24 @@ export function makeExperimentsCommand() {
92
93
  .option("--metadata <json>", "Metadata (JSON)")
93
94
  .action(async (id, opts, command) => {
94
95
  await runAction(command, async () => {
96
+ assertNameNotBlank(opts.name);
97
+ const changes = {
98
+ name: opts.name,
99
+ description: opts.description,
100
+ type: opts.type,
101
+ datasetVersionId: opts.datasetVersion,
102
+ metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
103
+ };
95
104
  const client = getClient(command);
96
- const body = {};
97
- if (opts.name)
98
- body.name = opts.name;
99
- if (opts.description !== undefined)
100
- body.description = opts.description;
101
- if (opts.type !== undefined)
102
- body.type = opts.type;
103
- if (opts.datasetVersion !== undefined)
104
- body.datasetVersionId = opts.datasetVersion;
105
- if (opts.metadata)
106
- body.metadata = JSON.parse(opts.metadata);
105
+ // PUT /v1/experiments/{id} replaces the whole experiment, so an omitted field would be stored as null.
106
+ const { data: current } = await client.GET("/v1/experiments/{id}", {
107
+ params: { path: { id } },
108
+ });
109
+ if (!current)
110
+ throw new Error(`Experiment ${id} could not be read.`);
107
111
  const { data } = await client.PUT("/v1/experiments/{id}", {
108
112
  params: { path: { id } },
109
- body: body,
113
+ body: overlay(current, changes),
110
114
  });
111
115
  formatDetail(data, command);
112
116
  });
@@ -131,7 +135,7 @@ export function makeExperimentsCommand() {
131
135
  .description("Add a variant to an experiment")
132
136
  .requiredOption("--experiment <id>", "Experiment ID")
133
137
  .requiredOption("-n, --name <name>", "Variant name")
134
- .requiredOption("--task-type <type>", "Task type")
138
+ .requiredOption("--task-type <type>", "Task type: extraction or agent")
135
139
  .requiredOption("--config <json>", "Variant configuration (JSON)")
136
140
  .option("-d, --description <text>", "Variant description")
137
141
  .option("--sort-order <n>", "Sort order", parseInt)
@@ -179,27 +183,33 @@ export function makeExperimentsCommand() {
179
183
  .argument("<variantId>", "Variant ID")
180
184
  .requiredOption("--experiment <id>", "Experiment ID")
181
185
  .option("-n, --name <name>", "Variant name")
182
- .option("--task-type <type>", "Task type")
186
+ .option("--task-type <type>", "Task type; must equal the variant's current one (it cannot change)")
183
187
  .option("--config <json>", "Variant configuration (JSON)")
184
188
  .option("-d, --description <text>", "Variant description")
185
189
  .option("--sort-order <n>", "Sort order", parseInt)
186
190
  .action(async (variantId, opts, command) => {
187
191
  await runAction(command, async () => {
192
+ assertNameNotBlank(opts.name);
193
+ const changes = {
194
+ name: opts.name,
195
+ taskType: opts.taskType,
196
+ configuration: opts.config ? JSON.parse(opts.config) : undefined,
197
+ description: opts.description,
198
+ sortOrder: opts.sortOrder,
199
+ };
188
200
  const client = getClient(command);
189
- const body = {};
190
- if (opts.name !== undefined)
191
- body.name = opts.name;
192
- if (opts.taskType !== undefined)
193
- body.taskType = opts.taskType;
194
- if (opts.config)
195
- body.configuration = JSON.parse(opts.config);
196
- if (opts.description !== undefined)
197
- body.description = opts.description;
198
- if (opts.sortOrder !== undefined)
199
- body.sortOrder = opts.sortOrder;
201
+ // The variant PUT writes name, description and configuration as sent, so an omitted one would
202
+ // be cleared. There is no single-variant GET: read it from the list. The read's version goes
203
+ // back with the body, so a change made since is refused with 409.
204
+ const { data: variants } = await client.GET("/v1/experiments/{id}/variants", {
205
+ params: { path: { id: opts.experiment } },
206
+ });
207
+ const current = variants?.find((v) => v.id === variantId);
208
+ if (!current)
209
+ throw new Error(`Variant ${variantId} is not a variant of experiment ${opts.experiment}.`);
200
210
  const { data } = await client.PUT("/v1/experiments/{id}/variants/{variantId}", {
201
211
  params: { path: { id: opts.experiment, variantId } },
202
- body: body,
212
+ body: overlay(current, changes),
203
213
  });
204
214
  formatDetail(data, command);
205
215
  });
@@ -8,40 +8,22 @@ import { formatPage, formatDetail, formatSuccess, withSpinner } from "../lib/out
8
8
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
9
9
  import { fileToBlob } from "../lib/multipart.js";
10
10
  /**
11
- * Upload a single file with purpose=agent_input|knowledge. Bypasses the typed
12
- * client the same way convert.ts and transcribe.ts do: the generated types
13
- * describe the multipart body as an opaque `file: string`, which is not
14
- * something FormData can produce a match for.
11
+ * Upload a single file with purpose=agent_input|knowledge. The generated type
12
+ * describes the "file" part as a binary string, which FormData cannot match,
13
+ * so the placeholder body satisfies the type and bodySerializer sends the
14
+ * real part.
15
15
  */
16
16
  async function uploadFile(command, filePath, purpose) {
17
- const config = resolveConfig(command);
18
- const authHeader = await resolveAuthHeader(config);
19
17
  const { blob, filename } = fileToBlob(filePath);
20
18
  const formData = new FormData();
21
19
  formData.append("file", blob, filename);
22
- const baseUrl = config.baseUrl.replace(/\/+$/, "");
23
- const url = `${baseUrl}/v1/files?purpose=${encodeURIComponent(purpose)}`;
24
- const res = await fetch(url, {
25
- method: "POST",
26
- headers: { Authorization: authHeader },
27
- body: formData,
20
+ const client = getClient(command);
21
+ const { data } = await client.POST("/v1/files", {
22
+ params: { query: { purpose } },
23
+ body: { file: "" },
24
+ bodySerializer: () => formData,
28
25
  });
29
- if (!res.ok) {
30
- let body;
31
- try {
32
- body = await res.json();
33
- }
34
- catch {
35
- body = {
36
- error: res.statusText,
37
- message: `HTTP ${res.status}: ${res.statusText}`,
38
- status: res.status,
39
- timestamp: new Date().toISOString(),
40
- };
41
- }
42
- throw new BackboneApiError(body);
43
- }
44
- return (await res.json());
26
+ return data;
45
27
  }
46
28
  /** Stream a file's bytes from /v1/files/{fileId}/content via a raw fetch. */
47
29
  async function downloadFile(command, fileId) {
@@ -1,7 +1,22 @@
1
1
  import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
+ import { CliUsageError } from "../lib/errors.js";
3
4
  import { formatPage, formatDetail, formatList, formatSuccess } from "../lib/output.js";
4
5
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
6
+ const MIN_MONTHLY_PERCENT = 0;
7
+ const MAX_MONTHLY_PERCENT = 1000;
8
+ /**
9
+ * Validated inside the action rather than as commander's argument parser, so the refusal
10
+ * travels through `runAction` and exits 2 like every other usage error (see `parsePort` in
11
+ * connectors.ts).
12
+ */
13
+ function parseMonthlyPercent(value) {
14
+ const percent = /^\d+$/.test(value) ? Number(value) : NaN;
15
+ if (!Number.isInteger(percent) || percent < MIN_MONTHLY_PERCENT || percent > MAX_MONTHLY_PERCENT) {
16
+ throw new CliUsageError(`percent must be a whole number between ${MIN_MONTHLY_PERCENT} and ${MAX_MONTHLY_PERCENT}, not '${value}'`);
17
+ }
18
+ return percent;
19
+ }
5
20
  export function makeInstallationsCommand() {
6
21
  const cmd = new Command("installations").description("Manage surface installations");
7
22
  const list = new Command("list").description("List installations");
@@ -73,7 +88,7 @@ export function makeInstallationsCommand() {
73
88
  body.status = opts.status;
74
89
  const { data } = await client.PUT("/v1/installations/{id}", {
75
90
  params: { path: { id } },
76
- body: body,
91
+ body,
77
92
  });
78
93
  formatDetail(data, command);
79
94
  });
@@ -172,6 +187,36 @@ export function makeInstallationsCommand() {
172
187
  });
173
188
  });
174
189
  cmd.addCommand(keys);
190
+ const budget = new Command("budget").description("Manage an installation's monthly budget: a share of the organisation's pool");
191
+ budget
192
+ .command("get")
193
+ .description("Read an installation's current budget: percent, period, and usage")
194
+ .argument("<id>", "Installation ID")
195
+ .action(async (id, _opts, command) => {
196
+ await runAction(command, async () => {
197
+ const client = getClient(command);
198
+ const { data } = await client.GET("/v1/installations/{id}/budget", {
199
+ params: { path: { id } },
200
+ });
201
+ formatDetail(data, command);
202
+ });
203
+ });
204
+ budget
205
+ .command("set")
206
+ .description("Set or change an installation's monthly budget share, 0-1000 percent")
207
+ .argument("<id>", "Installation ID")
208
+ .argument("<percent>", "Monthly percent of the organisation's pool, 0-1000")
209
+ .action(async (id, percent, _opts, command) => {
210
+ await runAction(command, async () => {
211
+ const client = getClient(command);
212
+ const { data } = await client.PUT("/v1/installations/{id}/budget", {
213
+ params: { path: { id } },
214
+ body: { monthlyPercent: parseMonthlyPercent(percent) },
215
+ });
216
+ formatDetail(data, command);
217
+ });
218
+ });
219
+ cmd.addCommand(budget);
175
220
  return cmd;
176
221
  }
177
222
  //# sourceMappingURL=installations.js.map
@@ -1,49 +1,29 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
- import { getClient, resolveAuthHeader, runAction } from "../lib/client.js";
4
- import { resolveConfig, isJsonOutput } from "../lib/config.js";
5
- import { BackboneApiError } from "../lib/errors.js";
3
+ import { getClient, runAction } from "../lib/client.js";
4
+ import { isJsonOutput } from "../lib/config.js";
6
5
  import { formatPage, formatDetail, formatSuccess, withSpinner } from "../lib/output.js";
7
6
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
8
7
  import { fileToBlob } from "../lib/multipart.js";
9
8
  /**
10
- * Upload one or more local files to a knowledge base. Bypasses the typed
11
- * client the same way convert.ts's multipartConvert does: springdoc mis-shapes
12
- * this endpoint's `files` parameter as a query array of binary strings, but the
13
- * controller (`KnowledgeDocumentController.upload`) actually reads it off a
14
- * multipart/form-data body with repeated "files" parts.
9
+ * Upload one or more local files to a knowledge base as repeated "files"
10
+ * parts of a multipart/form-data body. The generated type describes each part
11
+ * as a binary string, which FormData cannot match, so the placeholder body
12
+ * satisfies the type and bodySerializer sends the real parts.
15
13
  */
16
14
  async function uploadDocuments(command, knowledgeBaseId, paths) {
17
- const config = resolveConfig(command);
18
- const authHeader = await resolveAuthHeader(config);
19
15
  const formData = new FormData();
20
16
  for (const p of paths) {
21
17
  const { blob, filename } = fileToBlob(p);
22
18
  formData.append("files", blob, filename);
23
19
  }
24
- const baseUrl = config.baseUrl.replace(/\/+$/, "");
25
- const url = `${baseUrl}/v1/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/documents`;
26
- const res = await fetch(url, {
27
- method: "POST",
28
- headers: { Authorization: authHeader },
29
- body: formData,
20
+ const client = getClient(command);
21
+ const { data } = await client.POST("/v1/knowledge-bases/{knowledgeBaseId}/documents", {
22
+ params: { path: { knowledgeBaseId } },
23
+ body: { files: [] },
24
+ bodySerializer: () => formData,
30
25
  });
31
- if (!res.ok) {
32
- let body;
33
- try {
34
- body = await res.json();
35
- }
36
- catch {
37
- body = {
38
- error: res.statusText,
39
- message: `HTTP ${res.status}: ${res.statusText}`,
40
- status: res.status,
41
- timestamp: new Date().toISOString(),
42
- };
43
- }
44
- throw new BackboneApiError(body);
45
- }
46
- return (await res.json());
26
+ return data ?? [];
47
27
  }
48
28
  function printUploadResults(results, command) {
49
29
  if (isJsonOutput(command)) {
@@ -119,6 +99,18 @@ export function makeKnowledgeDocumentsCommand() {
119
99
  formatDetail(data, command);
120
100
  });
121
101
  });
102
+ cmd
103
+ .command("retry")
104
+ .description("Re-ingest a document whose newest revision failed, from its stored file")
105
+ .argument("<documentId>", "Document ID")
106
+ .requiredOption("--kb <id>", "Knowledge base ID")
107
+ .action(async (documentId, opts, command) => {
108
+ await runAction(command, async () => {
109
+ const client = getClient(command);
110
+ const { data } = await client.POST("/v1/knowledge-bases/{knowledgeBaseId}/documents/{documentId}/retry", { params: { path: { knowledgeBaseId: opts.kb, documentId } } });
111
+ formatDetail(data, command);
112
+ });
113
+ });
122
114
  cmd
123
115
  .command("delete")
124
116
  .description("Delete a document and take every revision out of retrieval")
@@ -47,6 +47,7 @@ export function makeKnowledgeCommand() {
47
47
  .option("--parent-chunk-size <n>", "Parent chunk size (hierarchical chunking)", parseInt)
48
48
  .option("--reranker-provider <id>", "Reranker provider ID")
49
49
  .option("--hybrid-search <bool>", "Enable hybrid search (true/false)")
50
+ .option("--text-search-language <name>", "Text-search language for the lexical leg (default simple)")
50
51
  .action(async (opts, command) => {
51
52
  await runAction(command, async () => {
52
53
  const client = getClient(command);
@@ -64,6 +65,7 @@ export function makeKnowledgeCommand() {
64
65
  parentChunkSize: opts.parentChunkSize,
65
66
  rerankerProviderId: opts.rerankerProvider,
66
67
  hybridSearchEnabled: opts.hybridSearch !== undefined ? opts.hybridSearch === "true" : undefined,
68
+ textSearchLanguage: opts.textSearchLanguage,
67
69
  },
68
70
  });
69
71
  formatDetail(data, command);
@@ -83,6 +85,7 @@ export function makeKnowledgeCommand() {
83
85
  .option("--parent-chunk-size <n>", "New parent chunk size", parseInt)
84
86
  .option("--reranker-provider <id>", "New reranker provider ID")
85
87
  .option("--hybrid-search <bool>", "Enable hybrid search (true/false)")
88
+ .option("--text-search-language <name>", "Change the text-search language (reindexes in the background); omit to leave it untouched")
86
89
  .action(async (id, opts, command) => {
87
90
  await runAction(command, async () => {
88
91
  const client = getClient(command);
@@ -107,6 +110,8 @@ export function makeKnowledgeCommand() {
107
110
  body.rerankerProviderId = opts.rerankerProvider;
108
111
  if (opts.hybridSearch !== undefined)
109
112
  body.hybridSearchEnabled = opts.hybridSearch === "true";
113
+ if (opts.textSearchLanguage)
114
+ body.textSearchLanguage = opts.textSearchLanguage;
110
115
  const { data } = await client.PATCH("/v1/knowledge-bases/{id}", {
111
116
  params: { path: { id } },
112
117
  body: body,
@@ -0,0 +1,10 @@
1
+ import { Command } from "commander";
2
+ /** The If-Match value for a version a GET returned: the quoted ETag the API expects. */
3
+ export declare function ifMatchHeader(version: string): string;
4
+ /**
5
+ * Agent memory (agent memory spec §7.1, §7.4). `ls`, `cat`, `put`, `rm` and `forget` act on
6
+ * the caller's own memory; `members` and `erase` are for ADMIN and OWNER and never show
7
+ * paths or content. Content is lossless: `cat` prints it raw, `put` sends the file as is.
8
+ */
9
+ export declare function makeMemoryCommand(): Command;
10
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1,132 @@
1
+ import { Command } from "commander";
2
+ import { readFileSync } 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 { formatDetail, formatList, formatSuccess } from "../lib/output.js";
7
+ /** The If-Match value for a version a GET returned: the quoted ETag the API expects. */
8
+ export function ifMatchHeader(version) {
9
+ if (!/^\d{1,18}$/.test(version)) {
10
+ throw new CliUsageError(`--if-match takes the file's version, a whole number: ${version}`);
11
+ }
12
+ return `"${version}"`;
13
+ }
14
+ function requireYes(opts, what) {
15
+ if (!opts.yes) {
16
+ throw new CliUsageError(`${what} cannot be undone. Pass --yes to confirm.`);
17
+ }
18
+ }
19
+ /**
20
+ * Agent memory (agent memory spec §7.1, §7.4). `ls`, `cat`, `put`, `rm` and `forget` act on
21
+ * the caller's own memory; `members` and `erase` are for ADMIN and OWNER and never show
22
+ * paths or content. Content is lossless: `cat` prints it raw, `put` sends the file as is.
23
+ */
24
+ export function makeMemoryCommand() {
25
+ const cmd = new Command("memory").description("Your agent memory files; member totals and erasure for admins");
26
+ cmd
27
+ .command("ls")
28
+ .description("List your memory files")
29
+ .action(async (_opts, command) => {
30
+ await runAction(command, async () => {
31
+ const client = getClient(command);
32
+ const { data } = await client.GET("/v1/memories/me/files");
33
+ formatList(data, command, [
34
+ "path",
35
+ "sizeBytes",
36
+ "version",
37
+ "updatedAt",
38
+ "lastWrittenByAgentId",
39
+ ]);
40
+ });
41
+ });
42
+ cmd
43
+ .command("cat <path>")
44
+ .description("Print one of your memory files, raw (--json for the file object)")
45
+ .action(async (path, _opts, command) => {
46
+ await runAction(command, async () => {
47
+ const client = getClient(command);
48
+ const { data } = await client.GET("/v1/memories/me/file", {
49
+ params: { query: { path } },
50
+ });
51
+ if (isJsonOutput(command)) {
52
+ formatDetail(data, command);
53
+ }
54
+ else {
55
+ process.stdout.write(data.content);
56
+ }
57
+ });
58
+ });
59
+ cmd
60
+ .command("put <path>")
61
+ .description("Create or replace one of your memory files from a local UTF-8 file")
62
+ .requiredOption("--file <file>", "Local file whose content is stored")
63
+ .option("--if-match <version>", "Only replace the file if it still has this version")
64
+ .action(async (path, opts, command) => {
65
+ await runAction(command, async () => {
66
+ // An empty --if-match (e.g. an unset shell variable) must be refused, never dropped
67
+ // into an unconditional overwrite.
68
+ const headers = opts.ifMatch !== undefined ? { "If-Match": ifMatchHeader(opts.ifMatch) } : undefined;
69
+ const content = readFileSync(opts.file, "utf8");
70
+ const client = getClient(command);
71
+ const { data } = await client.PUT("/v1/memories/me/file", {
72
+ params: { query: { path } },
73
+ body: { content },
74
+ headers,
75
+ });
76
+ formatDetail(data, command);
77
+ });
78
+ });
79
+ cmd
80
+ .command("rm <path>")
81
+ .description("Delete one of your memory files, or a directory with everything below it")
82
+ .action(async (path, _opts, command) => {
83
+ await runAction(command, async () => {
84
+ const client = getClient(command);
85
+ await client.DELETE("/v1/memories/me/file", { params: { query: { path } } });
86
+ formatSuccess(`Deleted ${path}`, command);
87
+ });
88
+ });
89
+ cmd
90
+ .command("forget")
91
+ .description("Delete your whole memory (cannot be undone)")
92
+ .option("--yes", "Confirm")
93
+ .action(async (opts, command) => {
94
+ await runAction(command, async () => {
95
+ requireYes(opts, "Forgetting your whole memory");
96
+ const client = getClient(command);
97
+ await client.DELETE("/v1/memories/me");
98
+ formatSuccess("Forgot your whole memory", command);
99
+ });
100
+ });
101
+ cmd
102
+ .command("members")
103
+ .description("Memory totals per member, without paths or content (ADMIN+)")
104
+ .action(async (_opts, command) => {
105
+ await runAction(command, async () => {
106
+ const client = getClient(command);
107
+ const { data } = await client.GET("/v1/memories/users");
108
+ formatList(data, command, [
109
+ "userId",
110
+ "fileCount",
111
+ "totalBytes",
112
+ "lastUpdatedAt",
113
+ ]);
114
+ });
115
+ });
116
+ cmd
117
+ .command("erase <userId>")
118
+ .description("Erase a member's whole memory (ADMIN+, cannot be undone)")
119
+ .option("--yes", "Confirm")
120
+ .action(async (userId, opts, command) => {
121
+ await runAction(command, async () => {
122
+ requireYes(opts, "Erasing a member's memory");
123
+ const client = getClient(command);
124
+ await client.DELETE("/v1/memories/users/{userId}", {
125
+ params: { path: { userId } },
126
+ });
127
+ formatSuccess(`Erased the memory of ${userId}`, command);
128
+ });
129
+ });
130
+ return cmd;
131
+ }
132
+ //# sourceMappingURL=memory.js.map
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
3
  import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
4
4
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
5
+ import { assertNameNotBlank, overlay } from "../lib/overlay.js";
5
6
  import { makePromptVersionsCommand } from "./prompt-versions.js";
6
7
  import { makePromptLabelsCommand } from "./prompt-labels.js";
7
8
  export function makePromptsCommand() {
@@ -59,17 +60,17 @@ export function makePromptsCommand() {
59
60
  .option("--type <type>", "New type")
60
61
  .action(async (id, opts, command) => {
61
62
  await runAction(command, async () => {
63
+ assertNameNotBlank(opts.name);
62
64
  const client = getClient(command);
63
- const body = {};
64
- if (opts.name)
65
- body.name = opts.name;
66
- if (opts.description !== undefined)
67
- body.description = opts.description;
68
- if (opts.type !== undefined)
69
- body.type = opts.type;
65
+ // PUT /v1/prompts/{id} replaces the whole prompt, so an omitted field would be stored as null.
66
+ const { data: current } = await client.GET("/v1/prompts/{id}", {
67
+ params: { path: { id } },
68
+ });
69
+ if (!current)
70
+ throw new Error(`Prompt ${id} could not be read.`);
70
71
  const { data } = await client.PUT("/v1/prompts/{id}", {
71
72
  params: { path: { id } },
72
- body: body,
73
+ body: overlay(current, { name: opts.name, description: opts.description, type: opts.type }),
73
74
  });
74
75
  formatDetail(data, command);
75
76
  });
@@ -58,15 +58,24 @@ export function makeSchemasCommand() {
58
58
  .option("-d, --description <text>", "New description")
59
59
  .action(async (id, opts, command) => {
60
60
  await runAction(command, async () => {
61
+ let name = opts.name;
62
+ let description = opts.description;
63
+ if (name !== undefined && !name.trim())
64
+ throw new Error("--name must not be empty.");
61
65
  const client = getClient(command);
62
- const body = {};
63
- if (opts.name)
64
- body.name = opts.name;
65
- if (opts.description !== undefined)
66
- body.description = opts.description;
66
+ // PUT /v1/schemas/{id} replaces the whole schema, so an omitted field would be stored as null.
67
+ if (name === undefined || description === undefined) {
68
+ const { data: current } = await client.GET("/v1/schemas/{id}", {
69
+ params: { path: { id } },
70
+ });
71
+ if (!current)
72
+ throw new Error(`Schema ${id} could not be read.`);
73
+ name ??= current.name;
74
+ description ??= current.description;
75
+ }
67
76
  const { data } = await client.PUT("/v1/schemas/{id}", {
68
77
  params: { path: { id } },
69
- body: body,
78
+ body: { name, description },
70
79
  });
71
80
  formatDetail(data, command);
72
81
  });
@@ -0,0 +1,13 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Parses the value a setting takes: a JSON object. The server checks its shape per key
4
+ * (for agents.default: {"agentId": "<id>"}); this only refuses what can never be valid.
5
+ */
6
+ export declare function parseSettingValue(raw: string): Record<string, unknown>;
7
+ /**
8
+ * Organization and member settings (agent-first chat spec §3.1, §4). Without --org every
9
+ * write targets the caller's own member tier; --org writes the organization's, which needs
10
+ * the key's write role (ADMIN or OWNER for agents.default).
11
+ */
12
+ export declare function makeSettingsCommand(): Command;
13
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1,80 @@
1
+ import { Command } from "commander";
2
+ import { getClient, runAction } from "../lib/client.js";
3
+ import { CliUsageError } from "../lib/errors.js";
4
+ import { formatDetail } from "../lib/output.js";
5
+ /**
6
+ * Parses the value a setting takes: a JSON object. The server checks its shape per key
7
+ * (for agents.default: {"agentId": "<id>"}); this only refuses what can never be valid.
8
+ */
9
+ export function parseSettingValue(raw) {
10
+ let parsed;
11
+ try {
12
+ parsed = JSON.parse(raw);
13
+ }
14
+ catch {
15
+ throw new CliUsageError(`Setting value is not valid JSON: ${raw}`);
16
+ }
17
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
18
+ throw new CliUsageError('Setting value must be a JSON object, e.g. \'{"agentId":"agt_123"}\'');
19
+ }
20
+ return parsed;
21
+ }
22
+ /**
23
+ * Organization and member settings (agent-first chat spec §3.1, §4). Without --org every
24
+ * write targets the caller's own member tier; --org writes the organization's, which needs
25
+ * the key's write role (ADMIN or OWNER for agents.default).
26
+ */
27
+ export function makeSettingsCommand() {
28
+ const cmd = new Command("settings").description("Organization and member settings (e.g. agents.default)");
29
+ cmd
30
+ .command("get <key>")
31
+ .description("Show a setting: the effective value, its source and both tiers")
32
+ .action(async (key, _opts, command) => {
33
+ await runAction(command, async () => {
34
+ const client = getClient(command);
35
+ const { data } = await client.GET("/v1/settings/{key}", {
36
+ params: { path: { key } },
37
+ });
38
+ formatDetail(data, command);
39
+ });
40
+ });
41
+ cmd
42
+ .command("set <key> <json>")
43
+ .description("Set your own value, or the organization's with --org (ADMIN+)")
44
+ .option("--org", "Write the organization's value instead of your own")
45
+ .action(async (key, json, opts, command) => {
46
+ await runAction(command, async () => {
47
+ const client = getClient(command);
48
+ const body = { value: parseSettingValue(json) };
49
+ const { data } = opts.org
50
+ ? await client.PUT("/v1/settings/{key}/org", {
51
+ params: { path: { key } },
52
+ body,
53
+ })
54
+ : await client.PUT("/v1/settings/{key}/member", {
55
+ params: { path: { key } },
56
+ body,
57
+ });
58
+ formatDetail(data, command);
59
+ });
60
+ });
61
+ cmd
62
+ .command("unset <key>")
63
+ .description("Clear your own value, or the organization's with --org (ADMIN+)")
64
+ .option("--org", "Clear the organization's value instead of your own")
65
+ .action(async (key, opts, command) => {
66
+ await runAction(command, async () => {
67
+ const client = getClient(command);
68
+ const { data } = opts.org
69
+ ? await client.DELETE("/v1/settings/{key}/org", {
70
+ params: { path: { key } },
71
+ })
72
+ : await client.DELETE("/v1/settings/{key}/member", {
73
+ params: { path: { key } },
74
+ });
75
+ formatDetail(data, command);
76
+ });
77
+ });
78
+ return cmd;
79
+ }
80
+ //# sourceMappingURL=settings.js.map