@dayofweek/dcli 1.4.0 → 1.5.0
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 +26 -0
- package/dist/bin/dcli.js +187 -2
- package/dist/client.d.ts +64 -0
- package/dist/client.js +94 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,32 @@ https://field.dayofweek.com/brain/<areaId>/source/<sourceId>
|
|
|
85
85
|
|
|
86
86
|
Unknown hosts, user-info, ports, query strings, fragments, encoded path separators, malformed identifiers, and oversized input are rejected before any API request.
|
|
87
87
|
|
|
88
|
+
## Entity knowledge
|
|
89
|
+
|
|
90
|
+
Knowledge documents attached to an entity — read them, add to them, mirror them
|
|
91
|
+
out into another knowledge base.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Read
|
|
95
|
+
dcli knowledge list --entity <entityId> --json
|
|
96
|
+
dcli knowledge list --entity <entityId> --full --json # whole content, not excerpts
|
|
97
|
+
dcli knowledge get <documentId> --json
|
|
98
|
+
dcli knowledge search "cold chain" --entity <entityId> --json
|
|
99
|
+
|
|
100
|
+
# Add a markdown note — submitted for human review
|
|
101
|
+
dcli knowledge add --entity <entityId> --title "Variety catalogue" --file note.md
|
|
102
|
+
|
|
103
|
+
# Mirror an entity's sources to disk as markdown with provenance front-matter
|
|
104
|
+
dcli knowledge export --entity <entityId> --out ./knowledge
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`add` submits a proposal by default: a person approves it before it lands. If
|
|
108
|
+
your token has admin rights you can pass `--direct` to skip review, and
|
|
109
|
+
`dcli knowledge attach --file article.pdf` to upload a binary source. Both write
|
|
110
|
+
immediately, so treat them as something you do when a person has asked for that
|
|
111
|
+
specific document — not as the normal path. The server rejects them for
|
|
112
|
+
non-admin tokens.
|
|
113
|
+
|
|
88
114
|
## Legacy platform commands
|
|
89
115
|
|
|
90
116
|
Existing read/proposal workflows remain compatible:
|
package/dist/bin/dcli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { ApiError, DayOfWeekClient } from "../client.js";
|
|
3
|
+
import { ApiError, DayOfWeekClient, toArrayBuffer } from "../client.js";
|
|
4
4
|
import { getToken, getApiUrl, saveConfig, loadConfig, saveCredential, deleteCredential } from "../config.js";
|
|
5
5
|
import { browserLogin } from "../auth/login.js";
|
|
6
6
|
import { parseBrainResource } from "../uri.js";
|
|
7
|
-
import { accessSync, constants, readFileSync, existsSync, statSync } from "node:fs";
|
|
7
|
+
import { accessSync, constants, mkdirSync, readFileSync, existsSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, basename, resolve, relative, sep } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
@@ -462,6 +462,191 @@ agent
|
|
|
462
462
|
const result = await client.getProposal(proposalId);
|
|
463
463
|
output(result);
|
|
464
464
|
});
|
|
465
|
+
// ── Knowledge Commands ───────────────────────────────────────────────────────
|
|
466
|
+
const MIME_BY_EXT = {
|
|
467
|
+
".pdf": "application/pdf",
|
|
468
|
+
".md": "text/markdown",
|
|
469
|
+
".txt": "text/plain",
|
|
470
|
+
".csv": "text/csv",
|
|
471
|
+
".json": "application/json",
|
|
472
|
+
".html": "text/html",
|
|
473
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
474
|
+
".doc": "application/msword",
|
|
475
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
476
|
+
".xls": "application/vnd.ms-excel",
|
|
477
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
478
|
+
};
|
|
479
|
+
function guessMimeType(path) {
|
|
480
|
+
const dot = path.lastIndexOf(".");
|
|
481
|
+
const ext = dot === -1 ? "" : path.slice(dot).toLowerCase();
|
|
482
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
483
|
+
}
|
|
484
|
+
/** Filesystem-safe name for an exported document, keeping it recognisable. */
|
|
485
|
+
function exportFileName(doc) {
|
|
486
|
+
const base = (doc.fileName || doc.title || doc.id || "document").trim();
|
|
487
|
+
const stripped = base.replace(/\.(md|txt)$/i, "");
|
|
488
|
+
const safe = stripped.replace(/[/\\:*?"<>|]+/g, "-").replace(/\s+/g, " ").slice(0, 120);
|
|
489
|
+
return `${safe || doc.id || "document"}.md`;
|
|
490
|
+
}
|
|
491
|
+
const knowledge = program
|
|
492
|
+
.command("knowledge")
|
|
493
|
+
.description("Read, add and export entity knowledge documents");
|
|
494
|
+
knowledge
|
|
495
|
+
.command("list")
|
|
496
|
+
.description("List knowledge documents on an entity")
|
|
497
|
+
.requiredOption("--entity <entityId>", "Target entity id")
|
|
498
|
+
.option("--full", "Include each document's full content, not an excerpt")
|
|
499
|
+
.option("--org <org>", "Organization (admin only)")
|
|
500
|
+
.action(async (opts) => {
|
|
501
|
+
const client = getClient();
|
|
502
|
+
output(await client.listKnowledge({ entity: opts.entity, full: opts.full, org: opts.org }));
|
|
503
|
+
});
|
|
504
|
+
knowledge
|
|
505
|
+
.command("get <documentId>")
|
|
506
|
+
.description("Show one knowledge document with full content")
|
|
507
|
+
.option("--org <org>", "Organization (admin only)")
|
|
508
|
+
.action(async (documentId, opts) => {
|
|
509
|
+
const client = getClient();
|
|
510
|
+
output(await client.getKnowledge(documentId, opts.org));
|
|
511
|
+
});
|
|
512
|
+
knowledge
|
|
513
|
+
.command("search <query>")
|
|
514
|
+
.description("Semantic search across knowledge documents")
|
|
515
|
+
.option("--entity <entityId>", "Scope to one entity's org tree")
|
|
516
|
+
.option("--types <types>", "Comma-separated sourceType filter")
|
|
517
|
+
.option("--limit <count>", "Max hits", parseInt)
|
|
518
|
+
.option("--all-orgs", "Search every org (admin only)")
|
|
519
|
+
.option("--org <org>", "Organization (admin only)")
|
|
520
|
+
.action(async (query, opts) => {
|
|
521
|
+
const client = getClient();
|
|
522
|
+
output(await client.searchKnowledge({
|
|
523
|
+
query,
|
|
524
|
+
entity: opts.entity,
|
|
525
|
+
types: opts.types
|
|
526
|
+
? String(opts.types).split(",").map((t) => t.trim()).filter(Boolean)
|
|
527
|
+
: undefined,
|
|
528
|
+
limit: opts.limit,
|
|
529
|
+
allOrgs: opts.allOrgs,
|
|
530
|
+
org: opts.org,
|
|
531
|
+
}));
|
|
532
|
+
});
|
|
533
|
+
knowledge
|
|
534
|
+
.command("add")
|
|
535
|
+
.description("Add a markdown knowledge note (proposal by default)")
|
|
536
|
+
.requiredOption("--entity <entityId>", "Target entity id")
|
|
537
|
+
.requiredOption("--title <title>", "Document title")
|
|
538
|
+
.option("--file <path>", "Markdown file to read (- for stdin)")
|
|
539
|
+
.option("--content <text>", "Inline content instead of --file")
|
|
540
|
+
.option("--source-type <type>", "research | website | competitive_intel | …", "research")
|
|
541
|
+
.option("--source-url <url>", "Where the content came from")
|
|
542
|
+
.option("--source-description <text>", "Short provenance note")
|
|
543
|
+
.option("--confidence <n>", "0-1, helps reviewers prioritize", parseFloat)
|
|
544
|
+
.option("--source-agent <name>", "Attribution for the submitting agent")
|
|
545
|
+
.option("--direct", "Write immediately instead of proposing. Admin only — ask the operator first")
|
|
546
|
+
.option("--org <org>", "Organization (admin only)")
|
|
547
|
+
.action(async (opts) => {
|
|
548
|
+
let content;
|
|
549
|
+
if (opts.content) {
|
|
550
|
+
content = String(opts.content);
|
|
551
|
+
}
|
|
552
|
+
else if (opts.file) {
|
|
553
|
+
content = opts.file === "-" ? readFileSync(0, "utf8") : readFileSync(opts.file, "utf8");
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
throw new Error("Provide --file or --content");
|
|
557
|
+
}
|
|
558
|
+
if (!content.trim())
|
|
559
|
+
throw new Error("Content is empty");
|
|
560
|
+
const client = getClient();
|
|
561
|
+
output(await client.addKnowledge({
|
|
562
|
+
entityId: opts.entity,
|
|
563
|
+
title: opts.title,
|
|
564
|
+
content,
|
|
565
|
+
sourceType: opts.sourceType,
|
|
566
|
+
sourceUrl: opts.sourceUrl,
|
|
567
|
+
sourceDescription: opts.sourceDescription,
|
|
568
|
+
confidence: opts.confidence,
|
|
569
|
+
sourceAgent: opts.sourceAgent,
|
|
570
|
+
direct: Boolean(opts.direct),
|
|
571
|
+
org: opts.org,
|
|
572
|
+
}));
|
|
573
|
+
});
|
|
574
|
+
knowledge
|
|
575
|
+
.command("attach")
|
|
576
|
+
.description("Attach a file (PDF, DOCX, XLSX …) as a source. Admin only")
|
|
577
|
+
.requiredOption("--entity <entityId>", "Target entity id")
|
|
578
|
+
.requiredOption("--file <path>", "File to upload")
|
|
579
|
+
.option("--name <fileName>", "Stored file name (defaults to the file's own)")
|
|
580
|
+
.option("--mime <mimeType>", "Content type (guessed from the extension)")
|
|
581
|
+
.option("--source-type <type>", "research | website | contract | …", "other")
|
|
582
|
+
.option("--source-url <url>", "Where the file came from")
|
|
583
|
+
.option("--source-description <text>", "Short provenance note")
|
|
584
|
+
.option("--org <org>", "Organization (admin only)")
|
|
585
|
+
.action(async (opts) => {
|
|
586
|
+
if (!existsSync(opts.file))
|
|
587
|
+
throw new Error(`File not found: ${opts.file}`);
|
|
588
|
+
const data = readFileSync(opts.file);
|
|
589
|
+
if (data.byteLength === 0)
|
|
590
|
+
throw new Error("File is empty");
|
|
591
|
+
const client = getClient();
|
|
592
|
+
output(await client.attachKnowledgeFile({
|
|
593
|
+
entityId: opts.entity,
|
|
594
|
+
data: toArrayBuffer(data),
|
|
595
|
+
fileName: opts.name ?? basename(opts.file),
|
|
596
|
+
mimeType: opts.mime ?? guessMimeType(opts.file),
|
|
597
|
+
sourceType: opts.sourceType,
|
|
598
|
+
sourceUrl: opts.sourceUrl,
|
|
599
|
+
sourceDescription: opts.sourceDescription,
|
|
600
|
+
org: opts.org,
|
|
601
|
+
}));
|
|
602
|
+
});
|
|
603
|
+
knowledge
|
|
604
|
+
.command("export")
|
|
605
|
+
.description("Write an entity's knowledge documents to disk as markdown")
|
|
606
|
+
.requiredOption("--entity <entityId>", "Source entity id")
|
|
607
|
+
.requiredOption("--out <dir>", "Directory to write into (created if missing)")
|
|
608
|
+
.option("--org <org>", "Organization (admin only)")
|
|
609
|
+
.action(async (opts) => {
|
|
610
|
+
const client = getClient();
|
|
611
|
+
const docs = await client.listKnowledge({ entity: opts.entity, full: true, org: opts.org });
|
|
612
|
+
mkdirSync(opts.out, { recursive: true });
|
|
613
|
+
const written = [];
|
|
614
|
+
const skipped = [];
|
|
615
|
+
for (const doc of docs) {
|
|
616
|
+
const body = typeof doc.content === "string" ? doc.content : "";
|
|
617
|
+
if (!body) {
|
|
618
|
+
// A binary source whose text extraction hasn't finished has nothing to
|
|
619
|
+
// mirror yet. Report it rather than writing an empty file.
|
|
620
|
+
skipped.push({
|
|
621
|
+
id: doc.id,
|
|
622
|
+
title: doc.title,
|
|
623
|
+
reason: doc.processingStatus && doc.processingStatus !== "completed"
|
|
624
|
+
? `processingStatus=${doc.processingStatus}`
|
|
625
|
+
: "no extracted content",
|
|
626
|
+
});
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
const frontMatter = [
|
|
630
|
+
"---",
|
|
631
|
+
`document_id: ${doc.id}`,
|
|
632
|
+
`title: ${JSON.stringify(doc.title ?? "")}`,
|
|
633
|
+
`source_type: ${doc.sourceType ?? "other"}`,
|
|
634
|
+
...(doc.sourceUrl ? [`source_url: ${doc.sourceUrl}`] : []),
|
|
635
|
+
...(doc.sourceDescription
|
|
636
|
+
? [`source_description: ${JSON.stringify(doc.sourceDescription)}`]
|
|
637
|
+
: []),
|
|
638
|
+
`entity_id: ${opts.entity}`,
|
|
639
|
+
...(doc.createdAt ? [`created_at: ${new Date(doc.createdAt).toISOString()}`] : []),
|
|
640
|
+
"exported_from: dayofweek-platform",
|
|
641
|
+
"---",
|
|
642
|
+
"",
|
|
643
|
+
].join("\n");
|
|
644
|
+
const target = join(opts.out, exportFileName(doc));
|
|
645
|
+
writeFileSync(target, `${frontMatter}${body}\n`, "utf8");
|
|
646
|
+
written.push({ id: doc.id, file: target, bytes: Buffer.byteLength(body, "utf8") });
|
|
647
|
+
}
|
|
648
|
+
output({ entity: opts.entity, out: opts.out, total: docs.length, written, skipped });
|
|
649
|
+
});
|
|
465
650
|
// ── Skill Commands ───────────────────────────────────────────────────────────
|
|
466
651
|
const skill = program.command("skill").description("Manage the Day of Week agent skill");
|
|
467
652
|
function resolveTargetDirs(target, bundleName, customDir) {
|
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* HTTP client for the Day of Week platform REST API.
|
|
3
3
|
* All calls go through the proxy at field.dayofweek.com/app/api/dcli.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Node's Buffer is a Uint8Array view over a possibly-larger, possibly-shared
|
|
7
|
+
* backing store, which is not assignable to BodyInit. Copy out the exact bytes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toArrayBuffer(view: Uint8Array): ArrayBuffer;
|
|
5
10
|
export declare class ApiError extends Error {
|
|
6
11
|
status: number;
|
|
7
12
|
code?: string | undefined;
|
|
@@ -154,6 +159,64 @@ export declare class DayOfWeekClient {
|
|
|
154
159
|
truncated: boolean;
|
|
155
160
|
total: number;
|
|
156
161
|
}>;
|
|
162
|
+
/**
|
|
163
|
+
* List the knowledge documents attached to an entity. `full` returns each
|
|
164
|
+
* document's whole content instead of an excerpt, which is what you want when
|
|
165
|
+
* mirroring an entity's sources into an external knowledge base.
|
|
166
|
+
*/
|
|
167
|
+
listKnowledge(opts: {
|
|
168
|
+
entity: string;
|
|
169
|
+
full?: boolean;
|
|
170
|
+
org?: string;
|
|
171
|
+
}): Promise<any[]>;
|
|
172
|
+
getKnowledge(documentId: string, org?: string): Promise<any>;
|
|
173
|
+
/** Semantic search across knowledge documents. */
|
|
174
|
+
searchKnowledge(opts: {
|
|
175
|
+
query: string;
|
|
176
|
+
entity?: string;
|
|
177
|
+
types?: string[];
|
|
178
|
+
limit?: number;
|
|
179
|
+
allOrgs?: boolean;
|
|
180
|
+
org?: string;
|
|
181
|
+
}): Promise<any>;
|
|
182
|
+
/**
|
|
183
|
+
* Add a markdown knowledge note.
|
|
184
|
+
*
|
|
185
|
+
* Without `direct` this submits a proposal for human review — the default,
|
|
186
|
+
* and the only option non-admin tokens have. With `direct: true` an admin
|
|
187
|
+
* token writes the document immediately, skipping review. Ask the operator
|
|
188
|
+
* before doing that; see references/admin.md in the served skill.
|
|
189
|
+
*/
|
|
190
|
+
addKnowledge(input: {
|
|
191
|
+
entityId: string;
|
|
192
|
+
title: string;
|
|
193
|
+
content: string;
|
|
194
|
+
sourceType?: string;
|
|
195
|
+
sourceUrl?: string;
|
|
196
|
+
sourceDescription?: string;
|
|
197
|
+
confidence?: number;
|
|
198
|
+
sourceAgent?: string;
|
|
199
|
+
direct?: boolean;
|
|
200
|
+
org?: string;
|
|
201
|
+
}): Promise<any>;
|
|
202
|
+
/**
|
|
203
|
+
* Attach a binary source (PDF, DOCX, XLSX, …) to an entity. Admin only.
|
|
204
|
+
*
|
|
205
|
+
* Three hops: ask for an upload URL, send the bytes straight to Convex
|
|
206
|
+
* storage, then register the resulting storageId. The bytes never pass
|
|
207
|
+
* through function arguments, so file size isn't bounded by an arg limit.
|
|
208
|
+
*/
|
|
209
|
+
attachKnowledgeFile(input: {
|
|
210
|
+
entityId: string;
|
|
211
|
+
/** Raw file bytes. Buffer callers: pass `toArrayBuffer(buf)` below. */
|
|
212
|
+
data: ArrayBuffer;
|
|
213
|
+
fileName: string;
|
|
214
|
+
mimeType: string;
|
|
215
|
+
sourceType?: string;
|
|
216
|
+
sourceUrl?: string;
|
|
217
|
+
sourceDescription?: string;
|
|
218
|
+
org?: string;
|
|
219
|
+
}): Promise<any>;
|
|
157
220
|
getSkillBundle(name?: string): Promise<{
|
|
158
221
|
name: string;
|
|
159
222
|
version: string;
|
|
@@ -172,6 +235,7 @@ export declare class DayOfWeekClient {
|
|
|
172
235
|
getSchema(): Promise<any>;
|
|
173
236
|
private get;
|
|
174
237
|
private post;
|
|
238
|
+
private put;
|
|
175
239
|
private patch;
|
|
176
240
|
private delete;
|
|
177
241
|
private request;
|
package/dist/client.js
CHANGED
|
@@ -8,6 +8,15 @@ import { basename, dirname, join } from "node:path";
|
|
|
8
8
|
import { Readable, Transform } from "node:stream";
|
|
9
9
|
import { pipeline } from "node:stream/promises";
|
|
10
10
|
const DEFAULT_BASE_URL = "https://field.dayofweek.com/app/api/dcli";
|
|
11
|
+
/**
|
|
12
|
+
* Node's Buffer is a Uint8Array view over a possibly-larger, possibly-shared
|
|
13
|
+
* backing store, which is not assignable to BodyInit. Copy out the exact bytes.
|
|
14
|
+
*/
|
|
15
|
+
export function toArrayBuffer(view) {
|
|
16
|
+
const out = new ArrayBuffer(view.byteLength);
|
|
17
|
+
new Uint8Array(out).set(view);
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
11
20
|
export class ApiError extends Error {
|
|
12
21
|
status;
|
|
13
22
|
code;
|
|
@@ -279,6 +288,88 @@ export class DayOfWeekClient {
|
|
|
279
288
|
const qs = params.toString();
|
|
280
289
|
return this.get(`/admin/proposals${qs ? `?${qs}` : ""}`);
|
|
281
290
|
}
|
|
291
|
+
// ── Knowledge ─────────────────────────────────────────────────────────────
|
|
292
|
+
/**
|
|
293
|
+
* List the knowledge documents attached to an entity. `full` returns each
|
|
294
|
+
* document's whole content instead of an excerpt, which is what you want when
|
|
295
|
+
* mirroring an entity's sources into an external knowledge base.
|
|
296
|
+
*/
|
|
297
|
+
async listKnowledge(opts) {
|
|
298
|
+
const params = new URLSearchParams({ entity: opts.entity });
|
|
299
|
+
if (opts.full)
|
|
300
|
+
params.set("full", "1");
|
|
301
|
+
if (opts.org)
|
|
302
|
+
params.set("org", opts.org);
|
|
303
|
+
return this.get(`/knowledge?${params.toString()}`);
|
|
304
|
+
}
|
|
305
|
+
async getKnowledge(documentId, org) {
|
|
306
|
+
const params = new URLSearchParams({ document: documentId });
|
|
307
|
+
if (org)
|
|
308
|
+
params.set("org", org);
|
|
309
|
+
return this.get(`/knowledge?${params.toString()}`);
|
|
310
|
+
}
|
|
311
|
+
/** Semantic search across knowledge documents. */
|
|
312
|
+
async searchKnowledge(opts) {
|
|
313
|
+
const params = new URLSearchParams({ q: opts.query });
|
|
314
|
+
if (opts.entity)
|
|
315
|
+
params.set("entity", opts.entity);
|
|
316
|
+
if (opts.types?.length)
|
|
317
|
+
params.set("types", opts.types.join(","));
|
|
318
|
+
if (opts.limit)
|
|
319
|
+
params.set("limit", String(opts.limit));
|
|
320
|
+
if (opts.allOrgs)
|
|
321
|
+
params.set("allOrgs", "1");
|
|
322
|
+
if (opts.org)
|
|
323
|
+
params.set("org", opts.org);
|
|
324
|
+
return this.get(`/knowledge?${params.toString()}`);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Add a markdown knowledge note.
|
|
328
|
+
*
|
|
329
|
+
* Without `direct` this submits a proposal for human review — the default,
|
|
330
|
+
* and the only option non-admin tokens have. With `direct: true` an admin
|
|
331
|
+
* token writes the document immediately, skipping review. Ask the operator
|
|
332
|
+
* before doing that; see references/admin.md in the served skill.
|
|
333
|
+
*/
|
|
334
|
+
async addKnowledge(input) {
|
|
335
|
+
return this.post("/knowledge", input);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Attach a binary source (PDF, DOCX, XLSX, …) to an entity. Admin only.
|
|
339
|
+
*
|
|
340
|
+
* Three hops: ask for an upload URL, send the bytes straight to Convex
|
|
341
|
+
* storage, then register the resulting storageId. The bytes never pass
|
|
342
|
+
* through function arguments, so file size isn't bounded by an arg limit.
|
|
343
|
+
*/
|
|
344
|
+
async attachKnowledgeFile(input) {
|
|
345
|
+
const { uploadUrl } = await this.post("/knowledge/file", {
|
|
346
|
+
entityId: input.entityId,
|
|
347
|
+
org: input.org,
|
|
348
|
+
});
|
|
349
|
+
const byteSize = input.data.byteLength;
|
|
350
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
351
|
+
method: "POST",
|
|
352
|
+
headers: { "Content-Type": input.mimeType },
|
|
353
|
+
body: input.data,
|
|
354
|
+
});
|
|
355
|
+
if (!uploadRes.ok) {
|
|
356
|
+
throw new ApiError(uploadRes.status, `Upload to storage failed: ${uploadRes.status} ${uploadRes.statusText}`);
|
|
357
|
+
}
|
|
358
|
+
const uploaded = (await uploadRes.json());
|
|
359
|
+
if (!uploaded.storageId)
|
|
360
|
+
throw new Error("Storage upload returned no storageId");
|
|
361
|
+
return this.put("/knowledge/file", {
|
|
362
|
+
entityId: input.entityId,
|
|
363
|
+
org: input.org,
|
|
364
|
+
storageId: uploaded.storageId,
|
|
365
|
+
fileName: input.fileName,
|
|
366
|
+
mimeType: input.mimeType,
|
|
367
|
+
byteSize,
|
|
368
|
+
sourceType: input.sourceType,
|
|
369
|
+
sourceUrl: input.sourceUrl,
|
|
370
|
+
sourceDescription: input.sourceDescription,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
282
373
|
// ── Skill ─────────────────────────────────────────────────────────────────
|
|
283
374
|
async getSkillBundle(name) {
|
|
284
375
|
return this.get(`/skill${name ? `?name=${encodeURIComponent(name)}` : ""}`);
|
|
@@ -297,6 +388,9 @@ export class DayOfWeekClient {
|
|
|
297
388
|
async post(path, body) {
|
|
298
389
|
return this.request(path, { method: "POST", body: JSON.stringify(body) });
|
|
299
390
|
}
|
|
391
|
+
async put(path, body) {
|
|
392
|
+
return this.request(path, { method: "PUT", body: JSON.stringify(body) });
|
|
393
|
+
}
|
|
300
394
|
async patch(path, body) {
|
|
301
395
|
return this.request(path, { method: "PATCH", body: JSON.stringify(body) });
|
|
302
396
|
}
|