@2kw/ai 8.0.0-dev.1 → 8.0.0-dev.11

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.
@@ -2,10 +2,9 @@ import { Command } from "commander";
2
2
  import { writeFileSync } from "node:fs";
3
3
  import chalk from "chalk";
4
4
  import { getClient, resolveAuthHeader, runAction } from "../lib/client.js";
5
- import { resolveConfig } from "../lib/config.js";
5
+ import { isJsonOutput, resolveConfig } from "../lib/config.js";
6
6
  import { BackboneApiError } from "../lib/errors.js";
7
- import { formatPage, formatDetail, formatSuccess, withSpinner } from "../lib/output.js";
8
- import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
7
+ import { formatDetail, formatList, formatSuccess, withSpinner } from "../lib/output.js";
9
8
  import { fileToBlob } from "../lib/multipart.js";
10
9
  /**
11
10
  * Upload a single file with purpose=agent_input|knowledge. Both are multipart
@@ -59,15 +58,31 @@ async function downloadFile(command, fileId) {
59
58
  export function makeFilesCommand() {
60
59
  const cmd = new Command("files").description("Manage uploaded files");
61
60
  const list = new Command("list").description("List files");
62
- addPaginationOptions(list);
63
61
  list.option("--purpose <purpose>", "Filter by purpose (agent_input, knowledge)");
62
+ list.option("--limit <number>", "How many files to list (1-100)", "20");
63
+ list.option("--after <fileId>", "List the files after this one (the previous list's last id)");
64
+ list.option("--order <order>", "asc (oldest first) or desc (newest first)", "desc");
64
65
  list.action(async (opts, command) => {
65
66
  await runAction(command, async () => {
66
67
  const client = getClient(command);
67
68
  const { data } = await client.GET("/v1/files", {
68
- params: { query: { purpose: opts.purpose, ...paginationParams(opts) } },
69
+ params: {
70
+ query: {
71
+ purpose: opts.purpose,
72
+ limit: parseInt(opts.limit, 10),
73
+ after: opts.after,
74
+ order: opts.order,
75
+ },
76
+ },
69
77
  });
70
- formatPage(data, command, ["id", "filename", "purpose", "bytes", "created_at"]);
78
+ if (isJsonOutput(command)) {
79
+ console.log(JSON.stringify(data, null, 2));
80
+ return;
81
+ }
82
+ formatList(data?.data, command, ["id", "filename", "purpose", "bytes", "created_at"]);
83
+ if (data?.has_more) {
84
+ console.log(chalk.dim(`More files: --after ${data.last_id}`));
85
+ }
71
86
  });
72
87
  });
73
88
  cmd.addCommand(list);
@@ -86,10 +86,23 @@ export function makeKnowledgeCommand() {
86
86
  .option("--reranker-provider <id>", "New reranker provider ID")
87
87
  .option("--hybrid-search <bool>", "Enable hybrid search (true/false)")
88
88
  .option("--text-search-language <name>", "Change the text-search language (reindexes in the background); omit to leave it untouched")
89
+ .option("--cancel-text-search-reindex", "Abandon the text-search reindex in progress and keep the active language")
89
90
  .action(async (id, opts, command) => {
90
91
  await runAction(command, async () => {
91
92
  const client = getClient(command);
92
- const body = {};
93
+ // The update takes the full body: name, slug, provider, model and dimension are required on
94
+ // every PATCH, so start from the stored values and overlay the flags. Optional fields left
95
+ // out keep their stored values server-side.
96
+ const { data: current } = await client.GET("/v1/knowledge-bases/{id}", {
97
+ params: { path: { id } },
98
+ });
99
+ const body = {
100
+ name: current?.name,
101
+ slug: current?.slug,
102
+ embeddingProviderId: current?.embeddingProviderId,
103
+ embeddingModel: current?.embeddingModel,
104
+ embeddingDim: current?.embeddingDim,
105
+ };
93
106
  if (opts.name)
94
107
  body.name = opts.name;
95
108
  if (opts.description !== undefined)
@@ -112,6 +125,8 @@ export function makeKnowledgeCommand() {
112
125
  body.hybridSearchEnabled = opts.hybridSearch === "true";
113
126
  if (opts.textSearchLanguage)
114
127
  body.textSearchLanguage = opts.textSearchLanguage;
128
+ if (opts.cancelTextSearchReindex)
129
+ body.cancelTextSearchReindex = true;
115
130
  const { data } = await client.PATCH("/v1/knowledge-bases/{id}", {
116
131
  params: { path: { id } },
117
132
  body: body,
@@ -138,7 +138,7 @@ export function makeSkillsCommand() {
138
138
  const bundle = packSkillDirectory(path);
139
139
  if (bundle.skipped.length > 0) {
140
140
  // The server reports what it was sent; only the CLI can name what never left the disk.
141
- console.error(`Left out of the bundle (the importer takes SKILL.md, references/, assets/, scripts/): ${bundle.skipped.join(", ")}`);
141
+ console.error(`Left out of the bundle (the importer takes SKILL.md, references/, assets/, scripts/, examples/): ${bundle.skipped.join(", ")}`);
142
142
  }
143
143
  blob = new Blob([bundle.bytes], { type: "application/zip" });
144
144
  filename = bundle.filename;
@@ -171,6 +171,7 @@ export function makeSkillsCommand() {
171
171
  .option("-o, --output <file>", "Output path (default: <skill-name>-v<n>.zip)")
172
172
  .option("-d, --extract-to <dir>", "Unpack the bundle into this directory instead of writing a zip")
173
173
  .option("--force", "Write into a non-empty --extract-to directory")
174
+ .option("--include-examples", "Keep the confidential examples/ files (needs ADMIN or OWNER)")
174
175
  .action(async (id, versionNumber, opts, command) => {
175
176
  await runAction(command, async () => {
176
177
  if (opts.output && opts.extractTo)
@@ -189,14 +190,21 @@ export function makeSkillsCommand() {
189
190
  }
190
191
  const config = resolveConfig(command);
191
192
  const authHeader = await resolveAuthHeader(config);
193
+ let omitted = 0;
192
194
  const bytes = await withSpinner("Exporting skill...", async () => {
193
195
  const baseUrl = config.baseUrl.replace(/\/+$/, "");
194
- const res = await fetch(`${baseUrl}/v1/skills/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionNumber)}/export`, {
196
+ const query = opts.includeExamples ? "?includeExamples=true" : "";
197
+ const res = await fetch(`${baseUrl}/v1/skills/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionNumber)}/export${query}`, {
195
198
  headers: { Authorization: authHeader },
196
199
  });
197
200
  await checkResponse(res);
201
+ omitted = Number(res.headers.get("X-Skill-Examples-Omitted") ?? 0);
198
202
  return Buffer.from(await res.arrayBuffer());
199
203
  });
204
+ if (omitted > 0) {
205
+ // #926: a re-import of this bundle would drop them, so say so where the user looks.
206
+ console.error(`${omitted} confidential example file(s) left out; --include-examples (admin) keeps them`);
207
+ }
200
208
  if (opts.extractTo) {
201
209
  mkdirSync(opts.extractTo, { recursive: true });
202
210
  const written = unpackSkillBundle(new Uint8Array(bytes), opts.extractTo);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import { Command } from "commander";
3
+ import { Command, CommanderError } from "commander";
4
4
  import { makeAuthCommand } from "./commands/auth.js";
5
5
  const require = createRequire(import.meta.url);
6
6
  const pkg = require("../package.json");
@@ -78,5 +78,20 @@ program.addCommand(makeConnectorsCommand());
78
78
  program.addCommand(makeRelaysCommand());
79
79
  program.addCommand(makeBridgeCommand());
80
80
  program.addCommand(makeDocsCommand());
81
- program.parseAsync().then(() => updater.notify());
81
+ // Commander exits 1 on its own parse errors, and 1 is the exit code for API and network failures.
82
+ // Every command throws instead, so a usage error exits 2 like a CliUsageError (#697).
83
+ const throwOnExit = (cmd) => {
84
+ cmd.exitOverride();
85
+ cmd.commands.forEach(throwOnExit);
86
+ };
87
+ throwOnExit(program);
88
+ program
89
+ .parseAsync()
90
+ .then(() => updater.notify())
91
+ .catch((err) => {
92
+ if (!(err instanceof CommanderError))
93
+ throw err;
94
+ // --help, --version and a command group's help without a subcommand succeed.
95
+ process.exit(err.exitCode === 0 || err.code === "commander.help" ? 0 : 2);
96
+ });
82
97
  //# sourceMappingURL=index.js.map
@@ -60,7 +60,7 @@ export declare const CONVERSATION_MODES: readonly ConversationMode[];
60
60
  export declare const MODE_OPTION_HELP: string;
61
61
  /**
62
62
  * The `--mode` value, checked before any request is sent: anything but the three exact values is a
63
- * usage error (exit 2). Commander's `.choices()` is not used because it exits 1 through `process.exit`.
63
+ * usage error (exit 2).
64
64
  */
65
65
  export declare function parseModeOption(raw: string | undefined): ConversationMode | undefined;
66
66
  /** The `backbone:mode` input item (S1 D7); always appended last. */
@@ -13,7 +13,7 @@ function isConversationMode(value) {
13
13
  }
14
14
  /**
15
15
  * The `--mode` value, checked before any request is sent: anything but the three exact values is a
16
- * usage error (exit 2). Commander's `.choices()` is not used because it exits 1 through `process.exit`.
16
+ * usage error (exit 2).
17
17
  */
18
18
  export function parseModeOption(raw) {
19
19
  if (raw === undefined)
@@ -2,11 +2,12 @@ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { unzipSync, zipSync } from "fflate";
4
4
  /**
5
- * The importer takes SKILL.md plus these three directories and reports anything else as
5
+ * The importer takes SKILL.md plus these four directories and reports anything else as
6
6
  * `unsupported-path` (spec §4). Packing only these keeps a stray .git or node_modules out of
7
- * the upload instead of spending the 20 MB bundle cap on it.
7
+ * the upload instead of spending the 20 MB bundle cap on it. `examples/` holds confidential
8
+ * files (#926): imported like the rest, left out of a plain export.
8
9
  */
9
- const RESOURCE_DIRS = ["references", "assets", "scripts"];
10
+ const RESOURCE_DIRS = ["references", "assets", "scripts", "examples"];
10
11
  function byName(left, right) {
11
12
  return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
12
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "8.0.0-dev.1",
3
+ "version": "8.0.0-dev.11",
4
4
  "description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
5
5
  "keywords": [
6
6
  "cli",