@kweaver-ai/kweaver-sdk 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -180,7 +180,9 @@ kweaver skill list/market/get/register/status/delete/content/read-file/download/
180
180
  kweaver vega health/stats/inspect/sql/catalog/resource/connector-type
181
181
  kweaver context-loader config set/use/list/show
182
182
  kweaver context-loader kn-search/query-object-instance/...
183
- kweaver call <path> [-X METHOD] [-d BODY] [-H header]
183
+ kweaver toolbox create/list/publish/unpublish/delete
184
+ kweaver tool upload/list/enable/disable
185
+ kweaver call <path> [-X METHOD] [-d BODY] [-H header] [-F key=value]
184
186
  ```
185
187
 
186
188
  ### Dataflow CLI examples
@@ -211,6 +213,25 @@ kweaver vega sql -d '{"resource_type":"mysql","query":"SELECT * FROM {{res-1}} L
211
213
 
212
214
  If both `-d` and `--query` / `--resource-type` are present, **only `-d` is used**.
213
215
 
216
+ ### Register an Agent toolbox
217
+
218
+ ```bash
219
+ # 1. Create a toolbox pointing at your service
220
+ kweaver toolbox create \
221
+ --name my_actions \
222
+ --service-url http://my-svc:8080 \
223
+ --description "Demo action backend"
224
+ # → {"box_id":"<BOX_ID>"}
225
+
226
+ # 2. Upload an OpenAPI spec as a tool
227
+ kweaver tool upload --toolbox <BOX_ID> ./openapi.json
228
+ # → {"success_ids":["<TOOL_ID>"]}
229
+
230
+ # 3. Publish the toolbox and enable the tool
231
+ kweaver toolbox publish <BOX_ID>
232
+ kweaver tool enable --toolbox <BOX_ID> <TOOL_ID>
233
+ ```
234
+
214
235
  **No-auth platforms:** If OAuth is not enabled, use `kweaver auth <url> --no-auth` (or run a normal `auth login`; a **404** on `POST /oauth2/clients` switches to no-auth automatically). Credentials are still saved under `~/.kweaver/` and work with `auth use` / `auth list`. Optional: `KWEAVER_NO_AUTH=1` with `KWEAVER_BASE_URL` when no token env is set. SDK: `new KWeaverClient({ baseUrl, auth: false })` or `kweaver.configure({ baseUrl, auth: false })`.
215
236
 
216
237
  ## Environment Variables
@@ -14,7 +14,7 @@ export interface DataflowCreateBody {
14
14
  }
15
15
  export interface DataflowResult {
16
16
  status: "success" | "completed" | "failed" | "error";
17
- reason?: string;
17
+ reason?: unknown;
18
18
  }
19
19
  export interface CreateDataflowOptions {
20
20
  baseUrl: string;
@@ -0,0 +1,47 @@
1
+ interface BaseOpts {
2
+ baseUrl: string;
3
+ accessToken: string;
4
+ businessDomain?: string;
5
+ }
6
+ export interface CreateToolboxOptions extends BaseOpts {
7
+ name: string;
8
+ description: string;
9
+ serviceUrl: string;
10
+ metadataType?: "openapi";
11
+ source?: string;
12
+ }
13
+ export declare function createToolbox(opts: CreateToolboxOptions): Promise<string>;
14
+ export interface DeleteToolboxOptions extends BaseOpts {
15
+ boxId: string;
16
+ }
17
+ export declare function deleteToolbox(opts: DeleteToolboxOptions): Promise<void>;
18
+ export interface SetToolboxStatusOptions extends BaseOpts {
19
+ boxId: string;
20
+ status: "published" | "draft";
21
+ }
22
+ export declare function setToolboxStatus(opts: SetToolboxStatusOptions): Promise<void>;
23
+ export interface UploadToolOptions extends BaseOpts {
24
+ boxId: string;
25
+ filePath: string;
26
+ metadataType?: "openapi";
27
+ }
28
+ export declare function uploadTool(opts: UploadToolOptions): Promise<string>;
29
+ export interface SetToolStatusesOptions extends BaseOpts {
30
+ boxId: string;
31
+ updates: Array<{
32
+ toolId: string;
33
+ status: "enabled" | "disabled";
34
+ }>;
35
+ }
36
+ export declare function setToolStatuses(opts: SetToolStatusesOptions): Promise<void>;
37
+ export interface ListToolboxesOptions extends BaseOpts {
38
+ keyword?: string;
39
+ limit?: number;
40
+ offset?: number;
41
+ }
42
+ export declare function listToolboxes(opts: ListToolboxesOptions): Promise<string>;
43
+ export interface ListToolsOptions extends BaseOpts {
44
+ boxId: string;
45
+ }
46
+ export declare function listTools(opts: ListToolsOptions): Promise<string>;
47
+ export {};
@@ -0,0 +1,90 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename } from "node:path";
3
+ import { fetchTextOrThrow } from "../utils/http.js";
4
+ import { buildHeaders } from "./headers.js";
5
+ // Backend endpoints under /api/agent-operator-integration/v1/tool-box.
6
+ //
7
+ // Verified against kweaver/examples/03-action-lifecycle/run.sh (lines 78–197):
8
+ // POST /tool-box create
9
+ // DELETE /tool-box/{id} delete
10
+ // POST /tool-box/{id}/status publish/draft
11
+ // POST /tool-box/{id}/tool upload tool (multipart)
12
+ // POST /tool-box/{id}/tools/status enable/disable (batch)
13
+ //
14
+ // Verified during Task 8 e2e against the live backend (2026-04-18):
15
+ // GET /tool-box?keyword=&limit=&offset= list toolboxes
16
+ // GET /tool-box/{id}/tool list tools
17
+ const PATH = "/api/agent-operator-integration/v1/tool-box";
18
+ function url(base, suffix = "") {
19
+ return `${base.replace(/\/+$/, "")}${PATH}${suffix}`;
20
+ }
21
+ export async function createToolbox(opts) {
22
+ const body = JSON.stringify({
23
+ metadata_type: opts.metadataType ?? "openapi",
24
+ box_name: opts.name,
25
+ box_desc: opts.description,
26
+ box_svc_url: opts.serviceUrl,
27
+ source: opts.source ?? "custom",
28
+ });
29
+ const { body: text } = await fetchTextOrThrow(url(opts.baseUrl), {
30
+ method: "POST",
31
+ headers: { ...buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"), "content-type": "application/json" },
32
+ body,
33
+ });
34
+ return text;
35
+ }
36
+ export async function deleteToolbox(opts) {
37
+ await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}`), {
38
+ method: "DELETE",
39
+ headers: buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"),
40
+ });
41
+ }
42
+ export async function setToolboxStatus(opts) {
43
+ await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}/status`), {
44
+ method: "POST",
45
+ headers: { ...buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"), "content-type": "application/json" },
46
+ body: JSON.stringify({ status: opts.status }),
47
+ });
48
+ }
49
+ export async function uploadTool(opts) {
50
+ const buf = await readFile(opts.filePath);
51
+ const form = new FormData();
52
+ form.append("metadata_type", opts.metadataType ?? "openapi");
53
+ form.append("data", new Blob([buf]), basename(opts.filePath));
54
+ const { body: text } = await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}/tool`), {
55
+ method: "POST",
56
+ headers: buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"),
57
+ body: form,
58
+ });
59
+ return text;
60
+ }
61
+ export async function setToolStatuses(opts) {
62
+ const body = JSON.stringify(opts.updates.map((u) => ({ tool_id: u.toolId, status: u.status })));
63
+ await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}/tools/status`), {
64
+ method: "POST",
65
+ headers: { ...buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"), "content-type": "application/json" },
66
+ body,
67
+ });
68
+ }
69
+ export async function listToolboxes(opts) {
70
+ const qp = new URLSearchParams();
71
+ if (opts.keyword !== undefined)
72
+ qp.set("keyword", opts.keyword);
73
+ if (opts.limit !== undefined)
74
+ qp.set("limit", String(opts.limit));
75
+ if (opts.offset !== undefined)
76
+ qp.set("offset", String(opts.offset));
77
+ const suffix = qp.toString() ? `?${qp}` : "";
78
+ const { body } = await fetchTextOrThrow(url(opts.baseUrl, suffix), {
79
+ method: "GET",
80
+ headers: buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"),
81
+ });
82
+ return body;
83
+ }
84
+ export async function listTools(opts) {
85
+ const { body } = await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}/tool`), {
86
+ method: "GET",
87
+ headers: buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"),
88
+ });
89
+ return body;
90
+ }
package/dist/cli.js CHANGED
@@ -12,6 +12,8 @@ import { runExploreCommand } from "./commands/explore.js";
12
12
  import { runDataviewCommand } from "./commands/dataview.js";
13
13
  import { runSkillCommand } from "./commands/skill.js";
14
14
  import { runTokenCommand } from "./commands/token.js";
15
+ import { runToolboxCommand } from "./commands/toolbox.js";
16
+ import { runToolCommand } from "./commands/tool.js";
15
17
  import { runVegaCommand } from "./commands/vega.js";
16
18
  function printHelp() {
17
19
  console.log(`kweaver
@@ -99,6 +101,15 @@ Usage:
99
101
  kweaver skill read-file <skill-id> <rel-path> [--raw] [--output file]
100
102
  kweaver skill download|install <skill-id> [path] [options]
101
103
 
104
+ kweaver toolbox create --name <n> --service-url <url> [--description <d>] [-bd value]
105
+ kweaver toolbox list [--keyword X] [--limit N] [--offset N] [-bd value]
106
+ kweaver toolbox publish|unpublish <box-id> [-bd value]
107
+ kweaver toolbox delete <box-id> [-y] [-bd value]
108
+
109
+ kweaver tool upload --toolbox <box-id> <openapi-spec-path> [--metadata-type openapi]
110
+ kweaver tool list --toolbox <box-id> [-bd value]
111
+ kweaver tool enable|disable --toolbox <box-id> <tool-id>... [-bd value]
112
+
102
113
  kweaver vega health|stats|inspect
103
114
  kweaver vega catalog list|get|health|test-connection|discover|resources [options]
104
115
  kweaver vega resource list|get|query [options]
@@ -129,6 +140,8 @@ Commands:
129
140
  object-type, relation-type, subgraph, action-type, action-execution, action-log)
130
141
  config Per-platform configuration (business domain)
131
142
  skill Skill registry and market (register, search, progressive read, download/install)
143
+ toolbox Agent toolbox lifecycle (create, list, publish, delete)
144
+ tool Tools inside a toolbox (upload OpenAPI spec, list, enable/disable)
132
145
  vega Vega observability (catalog, resource, query/sql, connector-type, health/stats/inspect)
133
146
  context-loader Context-loader MCP (config, tools, resources, prompts, kn-search, query-*, etc.)
134
147
  help Show this message`);
@@ -195,6 +208,12 @@ export async function run(argv) {
195
208
  if (command === "skill") {
196
209
  return runSkillCommand(rest);
197
210
  }
211
+ if (command === "toolbox") {
212
+ return runToolboxCommand(rest);
213
+ }
214
+ if (command === "tool") {
215
+ return runToolCommand(rest);
216
+ }
198
217
  if (command === "context-loader" || command === "context") {
199
218
  return runContextLoaderCommand(rest);
200
219
  }
@@ -49,6 +49,7 @@ export declare function parseKnCreateFromCsvArgs(args: string[]): {
49
49
  batchSize: number;
50
50
  tables: string[];
51
51
  build: boolean;
52
+ recreate: boolean;
52
53
  timeout: number;
53
54
  businessDomain: string;
54
55
  };
@@ -706,6 +706,7 @@ Options:
706
706
  --tables <a,b> Tables to include in KN (default: all imported)
707
707
  --build (default) Build after creation
708
708
  --no-build Skip build
709
+ --recreate Use "insert" mode on first batch (only effective for new tables)
709
710
  --timeout <n> Build timeout in seconds (default: 300)
710
711
  -bd, --biz-domain Business domain (default: bd_public)`;
711
712
  export function parseKnCreateFromCsvArgs(args) {
@@ -716,6 +717,7 @@ export function parseKnCreateFromCsvArgs(args) {
716
717
  let batchSize = 500;
717
718
  let tablesStr = "";
718
719
  let build = true;
720
+ let recreate = false;
719
721
  let timeout = 300;
720
722
  let businessDomain = "";
721
723
  for (let i = 0; i < args.length; i += 1) {
@@ -752,6 +754,10 @@ export function parseKnCreateFromCsvArgs(args) {
752
754
  build = false;
753
755
  continue;
754
756
  }
757
+ if (arg === "--recreate") {
758
+ recreate = true;
759
+ continue;
760
+ }
755
761
  if (arg === "--timeout" && args[i + 1]) {
756
762
  timeout = parseInt(args[++i], 10);
757
763
  if (Number.isNaN(timeout) || timeout < 1)
@@ -772,7 +778,7 @@ export function parseKnCreateFromCsvArgs(args) {
772
778
  }
773
779
  if (!businessDomain)
774
780
  businessDomain = resolveBusinessDomain();
775
- return { dsId, files, name, tablePrefix, batchSize, tables, build, timeout, businessDomain };
781
+ return { dsId, files, name, tablePrefix, batchSize, tables, build, recreate, timeout, businessDomain };
776
782
  }
777
783
  export async function runKnCreateFromCsvCommand(args) {
778
784
  let options;
@@ -795,6 +801,7 @@ export async function runKnCreateFromCsvCommand(args) {
795
801
  "--table-prefix", options.tablePrefix,
796
802
  "--batch-size", String(options.batchSize),
797
803
  "-bd", options.businessDomain,
804
+ ...(options.recreate ? ["--recreate"] : []),
798
805
  ];
799
806
  const importResult = await runDsImportCsv(importArgs);
800
807
  if (importResult.code !== 0) {
@@ -1,8 +1,18 @@
1
+ export type FormField = {
2
+ name: string;
3
+ kind: "string";
4
+ value: string;
5
+ } | {
6
+ name: string;
7
+ kind: "file";
8
+ path: string;
9
+ };
1
10
  export interface CallInvocation {
2
11
  url: string;
3
12
  method: string;
4
13
  headers: Headers;
5
14
  body?: string;
15
+ formFields?: FormField[];
6
16
  pretty: boolean;
7
17
  verbose: boolean;
8
18
  businessDomain: string;
@@ -1,3 +1,5 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename } from "node:path";
1
3
  import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
2
4
  import { isNoAuth } from "../config/no-auth.js";
3
5
  import { HttpError } from "../utils/http.js";
@@ -6,6 +8,7 @@ export function parseCallArgs(args) {
6
8
  const headers = new Headers();
7
9
  let method = "GET";
8
10
  let body;
11
+ const formFields = [];
9
12
  let url;
10
13
  let pretty = true;
11
14
  let verbose = false;
@@ -40,6 +43,26 @@ export function parseCallArgs(args) {
40
43
  index += 1;
41
44
  continue;
42
45
  }
46
+ if (arg === "-F" || arg === "--form") {
47
+ const raw = args[index + 1];
48
+ if (!raw)
49
+ throw new Error("Missing value for -F flag");
50
+ const eq = raw.indexOf("=");
51
+ if (eq === -1)
52
+ throw new Error(`Invalid -F format: ${raw} (expected key=value or key=@path)`);
53
+ const name = raw.slice(0, eq);
54
+ const rhs = raw.slice(eq + 1);
55
+ if (rhs.startsWith("@")) {
56
+ formFields.push({ name, kind: "file", path: rhs.slice(1) });
57
+ }
58
+ else {
59
+ formFields.push({ name, kind: "string", value: rhs });
60
+ }
61
+ if (method === "GET")
62
+ method = "POST";
63
+ index += 1;
64
+ continue;
65
+ }
43
66
  if (arg === "--pretty") {
44
67
  pretty = true;
45
68
  continue;
@@ -70,9 +93,21 @@ export function parseCallArgs(args) {
70
93
  if (!url) {
71
94
  throw new Error("Missing request URL");
72
95
  }
96
+ if (formFields.length > 0 && body !== undefined) {
97
+ throw new Error("-F and -d are mutually exclusive");
98
+ }
73
99
  if (!businessDomain)
74
100
  businessDomain = resolveBusinessDomain();
75
- return { url, method, headers, body, pretty, verbose, businessDomain };
101
+ return {
102
+ url,
103
+ method,
104
+ headers,
105
+ body,
106
+ formFields: formFields.length > 0 ? formFields : undefined,
107
+ pretty,
108
+ verbose,
109
+ businessDomain,
110
+ };
76
111
  }
77
112
  function injectAuthHeaders(headers, accessToken, businessDomain) {
78
113
  if (!isNoAuth(accessToken)) {
@@ -115,12 +150,17 @@ export function formatVerboseRequest(invocation) {
115
150
  for (const [name, value] of entries) {
116
151
  lines.push(` ${name}: ${value}`);
117
152
  }
118
- lines.push(`Body: ${invocation.body ? "present" : "empty"}`);
153
+ const bodyDesc = invocation.formFields && invocation.formFields.length > 0
154
+ ? `multipart (${invocation.formFields.length} field${invocation.formFields.length > 1 ? "s" : ""})`
155
+ : invocation.body
156
+ ? "present"
157
+ : "empty";
158
+ lines.push(`Body: ${bodyDesc}`);
119
159
  return lines;
120
160
  }
121
161
  export async function runCallCommand(args) {
122
162
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
123
- console.log(`kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [--pretty] [--verbose] [-bd value]
163
+ console.log(`kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [-F key=value] [--pretty] [--verbose] [-bd value]
124
164
 
125
165
  Call an API with curl-style flags and auto-injected token headers.
126
166
 
@@ -129,6 +169,7 @@ Options:
129
169
  -X, --request HTTP method (default: GET)
130
170
  -H, --header Extra header (repeatable)
131
171
  -d, --data, --data-raw JSON request body (sets Content-Type: application/json if not set)
172
+ -F, --form Multipart form field. -F key=value or -F key=@/path/to/file. Repeatable. Mutually exclusive with -d.
132
173
  -bd, --biz-domain Override x-business-domain (default: bd_public)
133
174
  -v, --verbose Print request info to stderr
134
175
  --pretty Pretty-print JSON output (default)`);
@@ -150,7 +191,22 @@ Options:
150
191
  : invocation.url;
151
192
  const headers = new Headers(invocation.headers);
152
193
  injectAuthHeaders(headers, token.accessToken, invocation.businessDomain);
153
- if (invocation.body !== undefined &&
194
+ let requestBody = invocation.body;
195
+ if (invocation.formFields && invocation.formFields.length > 0) {
196
+ const form = new FormData();
197
+ for (const field of invocation.formFields) {
198
+ if (field.kind === "string") {
199
+ form.append(field.name, field.value);
200
+ }
201
+ else {
202
+ const buf = await readFile(field.path);
203
+ form.append(field.name, new Blob([buf]), basename(field.path));
204
+ }
205
+ }
206
+ requestBody = form;
207
+ // do not set content-type — fetch sets multipart boundary
208
+ }
209
+ else if (invocation.body !== undefined &&
154
210
  invocation.body.length > 0 &&
155
211
  !headers.has("content-type") &&
156
212
  !headers.has("Content-Type")) {
@@ -164,7 +220,7 @@ Options:
164
220
  const response = await fetch(url, {
165
221
  method: invocation.method,
166
222
  headers,
167
- body: invocation.body,
223
+ body: requestBody,
168
224
  });
169
225
  const rawText = await response.text();
170
226
  const text = stripSseDoneMarker(rawText, response.headers.get("content-type"));
@@ -466,7 +466,7 @@ export async function runDsImportCsv(args) {
466
466
  process.stderr.write(`${elapsed}s\n`);
467
467
  }
468
468
  catch (err) {
469
- const msg = err instanceof Error ? err.message : String(err);
469
+ const msg = formatHttpError(err);
470
470
  process.stderr.write(`FAILED\n`);
471
471
  console.error(`[${tableName}] batch ${batchLabel} error: ${msg}`);
472
472
  batchFailed = true;
@@ -19,7 +19,7 @@ export interface DagBodyOptions {
19
19
  tableExist: boolean;
20
20
  data: Array<Record<string, string | null>>;
21
21
  fieldMappings: FieldMapping[];
22
- /** When true on the first batch (`tableExist` false), use overwrite to drop/recreate table before import. */
22
+ /** When true on the first batch (`tableExist` false), use "insert" to force table recreation. */
23
23
  recreate?: boolean;
24
24
  }
25
25
  /**
@@ -82,7 +82,9 @@ export function buildFieldMappings(headers) {
82
82
  export function buildDagBody(options) {
83
83
  const { datasourceId, datasourceType, tableName, tableExist, data, fieldMappings, recreate } = options;
84
84
  const ts = Date.now();
85
- const operateType = tableExist ? "append" : recreate ? "overwrite" : "append";
85
+ // "insert" creates/replaces the table; "append" adds rows to an existing table.
86
+ // With --recreate, use "insert" on first batch to force table recreation when schema changed.
87
+ const operateType = tableExist ? "append" : recreate ? "insert" : "append";
86
88
  const triggerStep = {
87
89
  id: "step-trigger",
88
90
  title: "Trigger",
@@ -0,0 +1,16 @@
1
+ export declare function runToolCommand(args: string[]): Promise<number>;
2
+ export interface ToolUploadOptions {
3
+ boxId: string;
4
+ filePath: string;
5
+ metadataType: "openapi";
6
+ businessDomain: string;
7
+ pretty: boolean;
8
+ }
9
+ export declare function parseToolUploadArgs(args: string[]): ToolUploadOptions;
10
+ export interface ToolStatusOptions {
11
+ boxId: string;
12
+ toolIds: string[];
13
+ status: "enabled" | "disabled";
14
+ businessDomain: string;
15
+ }
16
+ export declare function parseToolStatusArgs(args: string[], status: "enabled" | "disabled"): ToolStatusOptions;
@@ -0,0 +1,208 @@
1
+ import { access } from "node:fs/promises";
2
+ import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
3
+ import { listTools, setToolStatuses, uploadTool } from "../api/toolboxes.js";
4
+ import { formatCallOutput } from "./call.js";
5
+ import { resolveBusinessDomain } from "../config/store.js";
6
+ const HELP = `kweaver tool
7
+
8
+ Subcommands:
9
+ upload --toolbox <box-id> <openapi-spec-path> [--metadata-type openapi]
10
+ Upload an OpenAPI spec file as a tool
11
+ list --toolbox <box-id> List tools in a toolbox
12
+ enable --toolbox <box-id> <tool-id>... Enable one or more tools
13
+ disable --toolbox <box-id> <tool-id>... Disable one or more tools
14
+
15
+ Options:
16
+ -bd, --biz-domain <s> Business domain (default: bd_public)
17
+ --pretty Pretty-print JSON (default)
18
+ --compact Single-line JSON (pipeline-friendly)`;
19
+ export async function runToolCommand(args) {
20
+ const [subcommand, ...rest] = args;
21
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
22
+ console.log(HELP);
23
+ return 0;
24
+ }
25
+ const dispatch = () => {
26
+ if (subcommand === "upload")
27
+ return runToolUpload(rest);
28
+ if (subcommand === "list")
29
+ return runToolList(rest);
30
+ if (subcommand === "enable")
31
+ return runToolStatus(rest, "enabled");
32
+ if (subcommand === "disable")
33
+ return runToolStatus(rest, "disabled");
34
+ return Promise.resolve(-1);
35
+ };
36
+ try {
37
+ return await with401RefreshRetry(async () => {
38
+ const code = await dispatch();
39
+ if (code === -1) {
40
+ console.error(`Unknown tool subcommand: ${subcommand}`);
41
+ return 1;
42
+ }
43
+ return code;
44
+ });
45
+ }
46
+ catch (error) {
47
+ console.error(formatHttpError(error));
48
+ return 1;
49
+ }
50
+ }
51
+ export function parseToolUploadArgs(args) {
52
+ let boxId = "";
53
+ let filePath = "";
54
+ let metadataType = "openapi";
55
+ let businessDomain = "";
56
+ let pretty = true;
57
+ for (let i = 0; i < args.length; i += 1) {
58
+ const a = args[i];
59
+ if (a === "--toolbox" && args[i + 1]) {
60
+ boxId = args[++i];
61
+ continue;
62
+ }
63
+ if (a === "--metadata-type" && args[i + 1]) {
64
+ const val = args[++i];
65
+ if (val !== "openapi") {
66
+ throw new Error(`Unsupported --metadata-type: ${val} (only "openapi" is supported)`);
67
+ }
68
+ metadataType = val;
69
+ continue;
70
+ }
71
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
72
+ businessDomain = args[++i];
73
+ continue;
74
+ }
75
+ if (a === "--pretty") {
76
+ pretty = true;
77
+ continue;
78
+ }
79
+ if (a === "--compact") {
80
+ pretty = false;
81
+ continue;
82
+ }
83
+ if (!a.startsWith("-") && !filePath) {
84
+ filePath = a;
85
+ continue;
86
+ }
87
+ }
88
+ if (!boxId)
89
+ throw new Error("Missing required flag: --toolbox");
90
+ if (!filePath)
91
+ throw new Error("Missing required positional argument: <file-path>");
92
+ if (!businessDomain)
93
+ businessDomain = resolveBusinessDomain();
94
+ return { boxId, filePath, metadataType, businessDomain, pretty };
95
+ }
96
+ async function runToolUpload(args) {
97
+ let opts;
98
+ try {
99
+ opts = parseToolUploadArgs(args);
100
+ }
101
+ catch (e) {
102
+ console.error(e instanceof Error ? e.message : String(e));
103
+ return 1;
104
+ }
105
+ try {
106
+ await access(opts.filePath);
107
+ }
108
+ catch {
109
+ console.error(`File not found: ${opts.filePath}`);
110
+ return 1;
111
+ }
112
+ const token = await ensureValidToken();
113
+ const body = await uploadTool({
114
+ baseUrl: token.baseUrl,
115
+ accessToken: token.accessToken,
116
+ businessDomain: opts.businessDomain,
117
+ boxId: opts.boxId,
118
+ filePath: opts.filePath,
119
+ metadataType: opts.metadataType,
120
+ });
121
+ console.log(formatCallOutput(body, opts.pretty));
122
+ return 0;
123
+ }
124
+ // ── list ──────────────────────────────────────────────────────────────────────
125
+ async function runToolList(args) {
126
+ let boxId = "";
127
+ let businessDomain = "";
128
+ let pretty = true;
129
+ for (let i = 0; i < args.length; i += 1) {
130
+ const a = args[i];
131
+ if (a === "--toolbox" && args[i + 1]) {
132
+ boxId = args[++i];
133
+ continue;
134
+ }
135
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
136
+ businessDomain = args[++i];
137
+ continue;
138
+ }
139
+ if (a === "--pretty") {
140
+ pretty = true;
141
+ continue;
142
+ }
143
+ if (a === "--compact") {
144
+ pretty = false;
145
+ continue;
146
+ }
147
+ }
148
+ if (!boxId) {
149
+ console.error("Missing required flag: --toolbox");
150
+ return 1;
151
+ }
152
+ if (!businessDomain)
153
+ businessDomain = resolveBusinessDomain();
154
+ const token = await ensureValidToken();
155
+ const body = await listTools({
156
+ baseUrl: token.baseUrl,
157
+ accessToken: token.accessToken,
158
+ businessDomain,
159
+ boxId,
160
+ });
161
+ console.log(formatCallOutput(body, pretty));
162
+ return 0;
163
+ }
164
+ export function parseToolStatusArgs(args, status) {
165
+ let boxId = "";
166
+ let businessDomain = "";
167
+ const toolIds = [];
168
+ for (let i = 0; i < args.length; i += 1) {
169
+ const a = args[i];
170
+ if (a === "--toolbox" && args[i + 1]) {
171
+ boxId = args[++i];
172
+ continue;
173
+ }
174
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
175
+ businessDomain = args[++i];
176
+ continue;
177
+ }
178
+ if (!a.startsWith("-"))
179
+ toolIds.push(a);
180
+ }
181
+ if (!boxId)
182
+ throw new Error("Missing required flag: --toolbox");
183
+ if (toolIds.length === 0)
184
+ throw new Error("Missing tool id(s)");
185
+ if (!businessDomain)
186
+ businessDomain = resolveBusinessDomain();
187
+ return { boxId, toolIds, status, businessDomain };
188
+ }
189
+ async function runToolStatus(args, status) {
190
+ let opts;
191
+ try {
192
+ opts = parseToolStatusArgs(args, status);
193
+ }
194
+ catch (e) {
195
+ console.error(e instanceof Error ? e.message : String(e));
196
+ return 1;
197
+ }
198
+ const token = await ensureValidToken();
199
+ await setToolStatuses({
200
+ baseUrl: token.baseUrl,
201
+ accessToken: token.accessToken,
202
+ businessDomain: opts.businessDomain,
203
+ boxId: opts.boxId,
204
+ updates: opts.toolIds.map((toolId) => ({ toolId, status: opts.status })),
205
+ });
206
+ console.error(`${status === "enabled" ? "Enabled" : "Disabled"} ${opts.toolIds.length} tool(s) in toolbox ${opts.boxId}`);
207
+ return 0;
208
+ }
@@ -0,0 +1,14 @@
1
+ export declare function runToolboxCommand(args: string[]): Promise<number>;
2
+ export interface ToolboxCreateOptions {
3
+ name: string;
4
+ serviceUrl: string;
5
+ description: string;
6
+ businessDomain: string;
7
+ pretty: boolean;
8
+ }
9
+ export declare function parseToolboxCreateArgs(args: string[]): ToolboxCreateOptions;
10
+ export interface ToolboxSetStatusOptions {
11
+ boxId: string;
12
+ businessDomain: string;
13
+ }
14
+ export declare function parseToolboxSetStatusArgs(args: string[]): ToolboxSetStatusOptions;
@@ -0,0 +1,256 @@
1
+ import { createInterface } from "node:readline";
2
+ import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
3
+ import { createToolbox, deleteToolbox, listToolboxes, setToolboxStatus } from "../api/toolboxes.js";
4
+ import { formatCallOutput } from "./call.js";
5
+ import { resolveBusinessDomain } from "../config/store.js";
6
+ const HELP = `kweaver toolbox
7
+
8
+ Subcommands:
9
+ create --name <n> --service-url <url> [--description <d>] Create a new toolbox
10
+ list [--keyword <s>] [--limit <n>] [--offset <n>] List toolboxes
11
+ publish <box-id> Publish a toolbox (status=published)
12
+ unpublish <box-id> Unpublish (status=draft)
13
+ delete <box-id> [-y|--yes] Delete a toolbox
14
+
15
+ Options:
16
+ -bd, --biz-domain <s> Business domain (default: bd_public)
17
+ --pretty Pretty-print JSON (default)
18
+ --compact Single-line JSON (pipeline-friendly)`;
19
+ export async function runToolboxCommand(args) {
20
+ const [subcommand, ...rest] = args;
21
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
22
+ console.log(HELP);
23
+ return 0;
24
+ }
25
+ const dispatch = () => {
26
+ if (subcommand === "create")
27
+ return runToolboxCreate(rest);
28
+ if (subcommand === "list")
29
+ return runToolboxList(rest);
30
+ if (subcommand === "publish")
31
+ return runToolboxSetStatus(rest, "published");
32
+ if (subcommand === "unpublish")
33
+ return runToolboxSetStatus(rest, "draft");
34
+ if (subcommand === "delete")
35
+ return runToolboxDelete(rest);
36
+ return Promise.resolve(-1);
37
+ };
38
+ try {
39
+ return await with401RefreshRetry(async () => {
40
+ const code = await dispatch();
41
+ if (code === -1) {
42
+ console.error(`Unknown toolbox subcommand: ${subcommand}`);
43
+ return 1;
44
+ }
45
+ return code;
46
+ });
47
+ }
48
+ catch (error) {
49
+ console.error(formatHttpError(error));
50
+ return 1;
51
+ }
52
+ }
53
+ export function parseToolboxCreateArgs(args) {
54
+ let name = "";
55
+ let serviceUrl = "";
56
+ let description = "";
57
+ let businessDomain = "";
58
+ let pretty = true;
59
+ for (let i = 0; i < args.length; i += 1) {
60
+ const a = args[i];
61
+ if (a === "--name" && args[i + 1]) {
62
+ name = args[++i];
63
+ continue;
64
+ }
65
+ if (a === "--service-url" && args[i + 1]) {
66
+ serviceUrl = args[++i];
67
+ continue;
68
+ }
69
+ if (a === "--description" && args[i + 1]) {
70
+ description = args[++i];
71
+ continue;
72
+ }
73
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
74
+ businessDomain = args[++i];
75
+ continue;
76
+ }
77
+ if (a === "--pretty") {
78
+ pretty = true;
79
+ continue;
80
+ }
81
+ if (a === "--compact") {
82
+ pretty = false;
83
+ continue;
84
+ }
85
+ }
86
+ if (!name)
87
+ throw new Error("Missing required flag: --name");
88
+ if (!serviceUrl)
89
+ throw new Error("Missing required flag: --service-url");
90
+ if (!businessDomain)
91
+ businessDomain = resolveBusinessDomain();
92
+ return { name, serviceUrl, description, businessDomain, pretty };
93
+ }
94
+ async function runToolboxCreate(args) {
95
+ let opts;
96
+ try {
97
+ opts = parseToolboxCreateArgs(args);
98
+ }
99
+ catch (e) {
100
+ console.error(e instanceof Error ? e.message : String(e));
101
+ return 1;
102
+ }
103
+ const token = await ensureValidToken();
104
+ const body = await createToolbox({
105
+ baseUrl: token.baseUrl,
106
+ accessToken: token.accessToken,
107
+ name: opts.name,
108
+ description: opts.description,
109
+ serviceUrl: opts.serviceUrl,
110
+ businessDomain: opts.businessDomain,
111
+ });
112
+ console.log(formatCallOutput(body, opts.pretty));
113
+ return 0;
114
+ }
115
+ // ── list ──────────────────────────────────────────────────────────────────────
116
+ async function runToolboxList(args) {
117
+ let keyword;
118
+ let limit;
119
+ let offset;
120
+ let businessDomain = "";
121
+ let pretty = true;
122
+ for (let i = 0; i < args.length; i += 1) {
123
+ const a = args[i];
124
+ if (a === "--keyword" && args[i + 1]) {
125
+ keyword = args[++i];
126
+ continue;
127
+ }
128
+ if (a === "--limit" && args[i + 1]) {
129
+ const n = parseInt(args[++i], 10);
130
+ if (Number.isNaN(n)) {
131
+ console.error("--limit must be a number");
132
+ return 1;
133
+ }
134
+ limit = n;
135
+ continue;
136
+ }
137
+ if (a === "--offset" && args[i + 1]) {
138
+ const n = parseInt(args[++i], 10);
139
+ if (Number.isNaN(n)) {
140
+ console.error("--offset must be a number");
141
+ return 1;
142
+ }
143
+ offset = n;
144
+ continue;
145
+ }
146
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
147
+ businessDomain = args[++i];
148
+ continue;
149
+ }
150
+ if (a === "--pretty") {
151
+ pretty = true;
152
+ continue;
153
+ }
154
+ if (a === "--compact") {
155
+ pretty = false;
156
+ continue;
157
+ }
158
+ }
159
+ if (!businessDomain)
160
+ businessDomain = resolveBusinessDomain();
161
+ const token = await ensureValidToken();
162
+ const body = await listToolboxes({
163
+ baseUrl: token.baseUrl,
164
+ accessToken: token.accessToken,
165
+ businessDomain,
166
+ keyword, limit, offset,
167
+ });
168
+ console.log(formatCallOutput(body, pretty));
169
+ return 0;
170
+ }
171
+ export function parseToolboxSetStatusArgs(args) {
172
+ let boxId = "";
173
+ let businessDomain = "";
174
+ for (let i = 0; i < args.length; i += 1) {
175
+ const a = args[i];
176
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
177
+ businessDomain = args[++i];
178
+ continue;
179
+ }
180
+ if (!a.startsWith("-"))
181
+ boxId = a;
182
+ }
183
+ if (!boxId)
184
+ throw new Error("Missing required argument: <box-id>");
185
+ if (!businessDomain)
186
+ businessDomain = resolveBusinessDomain();
187
+ return { boxId, businessDomain };
188
+ }
189
+ async function runToolboxSetStatus(args, status) {
190
+ let opts;
191
+ try {
192
+ opts = parseToolboxSetStatusArgs(args);
193
+ }
194
+ catch (e) {
195
+ console.error(`Usage: kweaver toolbox ${status === "published" ? "publish" : "unpublish"} <box-id>`);
196
+ return 1;
197
+ }
198
+ const token = await ensureValidToken();
199
+ await setToolboxStatus({
200
+ baseUrl: token.baseUrl,
201
+ accessToken: token.accessToken,
202
+ businessDomain: opts.businessDomain,
203
+ boxId: opts.boxId,
204
+ status,
205
+ });
206
+ console.error(`${status === "published" ? "Published" : "Unpublished"} toolbox ${opts.boxId}`);
207
+ return 0;
208
+ }
209
+ // ── delete ────────────────────────────────────────────────────────────────────
210
+ function confirmYes(prompt) {
211
+ return new Promise((resolve) => {
212
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
213
+ rl.question(`${prompt} [y/N] `, (answer) => {
214
+ rl.close();
215
+ const t = answer.trim().toLowerCase();
216
+ resolve(t === "y" || t === "yes");
217
+ });
218
+ });
219
+ }
220
+ async function runToolboxDelete(args) {
221
+ let boxId = "";
222
+ let yes = false;
223
+ let businessDomain = "";
224
+ for (let i = 0; i < args.length; i += 1) {
225
+ const a = args[i];
226
+ if (a === "--yes" || a === "-y")
227
+ yes = true;
228
+ else if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
229
+ businessDomain = args[++i];
230
+ }
231
+ else if (!a.startsWith("-"))
232
+ boxId = a;
233
+ }
234
+ if (!boxId) {
235
+ console.error("Usage: kweaver toolbox delete <box-id> [-y|--yes]");
236
+ return 1;
237
+ }
238
+ if (!yes) {
239
+ const ok = await confirmYes(`Delete toolbox ${boxId}?`);
240
+ if (!ok) {
241
+ console.error("Aborted.");
242
+ return 1;
243
+ }
244
+ }
245
+ if (!businessDomain)
246
+ businessDomain = resolveBusinessDomain();
247
+ const token = await ensureValidToken();
248
+ await deleteToolbox({
249
+ baseUrl: token.baseUrl,
250
+ accessToken: token.accessToken,
251
+ businessDomain,
252
+ boxId,
253
+ });
254
+ console.error(`Deleted toolbox ${boxId}`);
255
+ return 0;
256
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kweaver-ai/kweaver-sdk",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "KWeaver TypeScript SDK — CLI tool and programmatic API for knowledge networks and Decision Agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",