@2kw/ai 6.3.0-dev.13 → 6.3.0-dev.135

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 (49) 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 +17 -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 +107 -3
  14. package/dist/commands/config.d.ts +1 -1
  15. package/dist/commands/config.js +16 -2
  16. package/dist/commands/conversations.js +16 -0
  17. package/dist/commands/datasets.js +17 -15
  18. package/dist/commands/experiments.js +36 -26
  19. package/dist/commands/files.js +10 -28
  20. package/dist/commands/installations.js +46 -1
  21. package/dist/commands/knowledge-documents.js +24 -32
  22. package/dist/commands/knowledge.js +5 -0
  23. package/dist/commands/memory.d.ts +10 -0
  24. package/dist/commands/memory.js +132 -0
  25. package/dist/commands/prompts.js +9 -8
  26. package/dist/commands/schemas.js +15 -6
  27. package/dist/commands/skill-versions.js +27 -1
  28. package/dist/commands/skills.js +31 -1
  29. package/dist/commands/tracing.js +12 -17
  30. package/dist/index.js +2 -0
  31. package/dist/lib/agent-decide.d.ts +29 -2
  32. package/dist/lib/agent-decide.js +100 -3
  33. package/dist/lib/agent-run.d.ts +50 -3
  34. package/dist/lib/agent-run.js +166 -16
  35. package/dist/lib/approval-prompt.js +31 -1
  36. package/dist/lib/client.d.ts +8 -0
  37. package/dist/lib/client.js +18 -1
  38. package/dist/lib/config.d.ts +16 -0
  39. package/dist/lib/config.js +26 -1
  40. package/dist/lib/connect-pause.d.ts +71 -0
  41. package/dist/lib/connect-pause.js +147 -0
  42. package/dist/lib/errors.js +3 -1
  43. package/dist/lib/overlay.d.ts +10 -0
  44. package/dist/lib/overlay.js +20 -0
  45. package/dist/lib/skills-apply-preview.d.ts +19 -0
  46. package/dist/lib/skills-apply-preview.js +94 -0
  47. package/dist/lib/tracing-settings.d.ts +26 -0
  48. package/dist/lib/tracing-settings.js +25 -0
  49. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import { store, isJsonOutput, getActiveContext, getActiveContextName, getContextCount, setContext, DEFAULT_BASE_URL, } from "../lib/config.js";
4
4
  import { maskApiKey } from "../lib/redact.js";
5
- const ALLOWED_KEYS = ["apiKey", "baseUrl"];
5
+ const ALLOWED_KEYS = ["apiKey", "baseUrl", "memory"];
6
6
  const KEY_ALIASES = {
7
7
  "api-key": "apiKey",
8
8
  "base-url": "baseUrl",
@@ -43,6 +43,14 @@ export function applyConfigSet(key, value) {
43
43
  setContext(contextName, entry);
44
44
  return { clearedSession };
45
45
  }
46
+ if (key === "memory") {
47
+ if (value !== "true" && value !== "false") {
48
+ throw new Error('memory takes true or false');
49
+ }
50
+ entry.memory = value === "true";
51
+ setContext(contextName, entry);
52
+ return { clearedSession: false };
53
+ }
46
54
  entry[key] = value;
47
55
  setContext(contextName, entry);
48
56
  return { clearedSession: false };
@@ -82,7 +90,12 @@ export function makeConfigCommand() {
82
90
  const validKey = validateKey(key);
83
91
  const ctx = getActiveContext();
84
92
  const raw = ctx?.[validKey];
85
- const value = raw && validKey === "apiKey" ? maskApiKey(raw) : raw;
93
+ // A stored "" apiKey stays "" (shown as "(not set)"); only a real key is masked.
94
+ const value = raw === undefined
95
+ ? undefined
96
+ : validKey === "apiKey" && typeof raw === "string"
97
+ ? raw && maskApiKey(raw)
98
+ : String(raw);
86
99
  if (isJsonOutput(command)) {
87
100
  console.log(JSON.stringify({ key: validKey, value: value ?? null }));
88
101
  }
@@ -103,6 +116,7 @@ export function makeConfigCommand() {
103
116
  const values = {
104
117
  apiKey: ctx?.apiKey ? maskApiKey(ctx.apiKey) : undefined,
105
118
  baseUrl: ctx?.baseUrl,
119
+ memory: ctx?.memory === undefined ? undefined : String(ctx.memory),
106
120
  };
107
121
  if (isJsonOutput(command)) {
108
122
  console.log(JSON.stringify(values, null, 2));
@@ -98,6 +98,22 @@ export function makeConversationsCommand() {
98
98
  formatSuccess(`Conversation ${conversationId} deleted.`, command);
99
99
  });
100
100
  });
101
+ cmd
102
+ .command("cancel")
103
+ .description("Ask a running turn of a conversation to stop")
104
+ .argument("<conversationId>", "Conversation ID")
105
+ .requiredOption("--turn <turnId>", "The turn id the client sent as the Backbone-Turn-Id header")
106
+ .action(async (conversationId, opts, command) => {
107
+ await runAction(command, async () => {
108
+ const client = getClient(command);
109
+ await client.POST("/v1/conversations/{conversationId}/cancel", {
110
+ params: { path: { conversationId } },
111
+ body: { turn_id: opts.turn },
112
+ });
113
+ // Accepted, not performed: the turn's own response says whether it stopped.
114
+ formatSuccess(`Stop requested for turn ${opts.turn} of conversation ${conversationId}.`, command);
115
+ });
116
+ });
101
117
  const items = new Command("items").description("List a conversation's items");
102
118
  items
103
119
  .command("list")
@@ -3,6 +3,7 @@ import { getClient, runAction } from "../lib/client.js";
3
3
  import { resolveLatestVersionId } from "../lib/datasets.js";
4
4
  import { formatPage, formatDetail, formatList, formatSuccess } from "../lib/output.js";
5
5
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
6
+ import { assertNameNotBlank, overlay } from "../lib/overlay.js";
6
7
  import { makeDatasetVersionsCommand } from "./dataset-versions.js";
7
8
  export function makeDatasetsCommand() {
8
9
  const cmd = new Command("datasets").description("Manage datasets");
@@ -77,24 +78,25 @@ export function makeDatasetsCommand() {
77
78
  .option("--metadata <json>", "Metadata (JSON)")
78
79
  .action(async (id, opts, command) => {
79
80
  await runAction(command, async () => {
81
+ assertNameNotBlank(opts.name);
82
+ const changes = {
83
+ name: opts.name,
84
+ description: opts.description,
85
+ type: opts.type,
86
+ inputSchema: opts.inputSchema ? JSON.parse(opts.inputSchema) : undefined,
87
+ expectedOutputSchema: opts.expectedOutputSchema ? JSON.parse(opts.expectedOutputSchema) : undefined,
88
+ metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
89
+ };
80
90
  const client = getClient(command);
81
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
- const body = {};
83
- if (opts.name)
84
- body.name = opts.name;
85
- if (opts.description !== undefined)
86
- body.description = opts.description;
87
- if (opts.type !== undefined)
88
- body.type = opts.type;
89
- if (opts.inputSchema)
90
- body.inputSchema = JSON.parse(opts.inputSchema);
91
- if (opts.expectedOutputSchema)
92
- body.expectedOutputSchema = JSON.parse(opts.expectedOutputSchema);
93
- if (opts.metadata)
94
- body.metadata = JSON.parse(opts.metadata);
91
+ // PUT /v1/datasets/{id} replaces the whole dataset, so an omitted field would be stored as null.
92
+ const { data: current } = await client.GET("/v1/datasets/{id}", {
93
+ params: { path: { id } },
94
+ });
95
+ if (!current)
96
+ throw new Error(`Dataset ${id} could not be read.`);
95
97
  const { data } = await client.PUT("/v1/datasets/{id}", {
96
98
  params: { path: { id } },
97
- body: body,
99
+ body: overlay(current, changes),
98
100
  });
99
101
  formatDetail(data, command);
100
102
  });
@@ -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
  });