veryfront 0.1.71 → 0.1.73

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 (110) hide show
  1. package/esm/cli/commands/files/command-help.d.ts +3 -0
  2. package/esm/cli/commands/files/command-help.d.ts.map +1 -0
  3. package/esm/cli/commands/files/command-help.js +38 -0
  4. package/esm/cli/commands/files/command.d.ts +105 -0
  5. package/esm/cli/commands/files/command.d.ts.map +1 -0
  6. package/esm/cli/commands/files/command.js +250 -0
  7. package/esm/cli/commands/files/handler.d.ts +3 -0
  8. package/esm/cli/commands/files/handler.d.ts.map +1 -0
  9. package/esm/cli/commands/files/handler.js +4 -0
  10. package/esm/cli/commands/files/index.d.ts +4 -0
  11. package/esm/cli/commands/files/index.d.ts.map +1 -0
  12. package/esm/cli/commands/files/index.js +2 -0
  13. package/esm/cli/commands/knowledge/command-help.d.ts +3 -0
  14. package/esm/cli/commands/knowledge/command-help.d.ts.map +1 -0
  15. package/esm/cli/commands/knowledge/command-help.js +38 -0
  16. package/esm/cli/commands/knowledge/command.d.ts +122 -0
  17. package/esm/cli/commands/knowledge/command.d.ts.map +1 -0
  18. package/esm/cli/commands/knowledge/command.js +382 -0
  19. package/esm/cli/commands/knowledge/handler.d.ts +3 -0
  20. package/esm/cli/commands/knowledge/handler.d.ts.map +1 -0
  21. package/esm/cli/commands/knowledge/handler.js +4 -0
  22. package/esm/cli/commands/knowledge/index.d.ts +3 -0
  23. package/esm/cli/commands/knowledge/index.d.ts.map +1 -0
  24. package/esm/cli/commands/knowledge/index.js +2 -0
  25. package/esm/cli/commands/knowledge/parser-source.d.ts +2 -0
  26. package/esm/cli/commands/knowledge/parser-source.d.ts.map +1 -0
  27. package/esm/cli/commands/knowledge/parser-source.js +415 -0
  28. package/esm/cli/commands/uploads/command-help.d.ts +3 -0
  29. package/esm/cli/commands/uploads/command-help.d.ts.map +1 -0
  30. package/esm/cli/commands/uploads/command-help.js +43 -0
  31. package/esm/cli/commands/uploads/command.d.ts +140 -0
  32. package/esm/cli/commands/uploads/command.d.ts.map +1 -0
  33. package/esm/cli/commands/uploads/command.js +323 -0
  34. package/esm/cli/commands/uploads/handler.d.ts +3 -0
  35. package/esm/cli/commands/uploads/handler.d.ts.map +1 -0
  36. package/esm/cli/commands/uploads/handler.js +4 -0
  37. package/esm/cli/commands/uploads/index.d.ts +4 -0
  38. package/esm/cli/commands/uploads/index.d.ts.map +1 -0
  39. package/esm/cli/commands/uploads/index.js +2 -0
  40. package/esm/cli/help/command-definitions.d.ts.map +1 -1
  41. package/esm/cli/help/command-definitions.js +6 -0
  42. package/esm/cli/router.d.ts.map +1 -1
  43. package/esm/cli/router.js +6 -0
  44. package/esm/deno.js +1 -1
  45. package/esm/src/errors/error-registry.d.ts +2 -0
  46. package/esm/src/errors/error-registry.d.ts.map +1 -1
  47. package/esm/src/errors/error-registry.js +8 -0
  48. package/esm/src/errors/index.d.ts +1 -1
  49. package/esm/src/errors/index.d.ts.map +1 -1
  50. package/esm/src/errors/index.js +1 -1
  51. package/esm/src/html/html-shell-generator.d.ts.map +1 -1
  52. package/esm/src/html/html-shell-generator.js +6 -0
  53. package/esm/src/platform/compat/media-types.d.ts +5 -0
  54. package/esm/src/platform/compat/media-types.d.ts.map +1 -0
  55. package/esm/src/platform/compat/media-types.js +19 -0
  56. package/esm/src/platform/index.d.ts +1 -0
  57. package/esm/src/platform/index.d.ts.map +1 -1
  58. package/esm/src/platform/index.js +2 -0
  59. package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
  60. package/esm/src/rendering/orchestrator/pipeline.js +116 -105
  61. package/esm/src/server/bootstrap.js +5 -1
  62. package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts +4 -0
  63. package/esm/src/server/dev-server/error-overlay/error-formatter.d.ts.map +1 -1
  64. package/esm/src/server/dev-server/error-overlay/error-formatter.js +15 -0
  65. package/esm/src/server/dev-server/error-overlay/html-template.d.ts +1 -1
  66. package/esm/src/server/dev-server/error-overlay/html-template.d.ts.map +1 -1
  67. package/esm/src/server/dev-server/error-overlay/html-template.js +131 -8
  68. package/esm/src/server/dev-server/error-overlay/index.d.ts +1 -1
  69. package/esm/src/server/dev-server/error-overlay/index.d.ts.map +1 -1
  70. package/esm/src/server/dev-server/error-overlay/index.js +1 -1
  71. package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts +1 -1
  72. package/esm/src/server/dev-server/error-overlay/overlay-renderer.d.ts.map +1 -1
  73. package/esm/src/server/dev-server/error-overlay/overlay-renderer.js +2 -2
  74. package/esm/src/server/dev-server/request-handler.d.ts.map +1 -1
  75. package/esm/src/server/dev-server/request-handler.js +6 -2
  76. package/esm/src/server/services/rendering/ssr.service.d.ts.map +1 -1
  77. package/esm/src/server/services/rendering/ssr.service.js +9 -2
  78. package/esm/src/server/utils/error-html.d.ts.map +1 -1
  79. package/esm/src/server/utils/error-html.js +26 -6
  80. package/package.json +2 -1
  81. package/src/cli/commands/files/command-help.ts +40 -0
  82. package/src/cli/commands/files/command.ts +328 -0
  83. package/src/cli/commands/files/handler.ts +6 -0
  84. package/src/cli/commands/files/index.ts +19 -0
  85. package/src/cli/commands/knowledge/command-help.ts +40 -0
  86. package/src/cli/commands/knowledge/command.ts +513 -0
  87. package/src/cli/commands/knowledge/handler.ts +6 -0
  88. package/src/cli/commands/knowledge/index.ts +2 -0
  89. package/src/cli/commands/knowledge/parser-source.ts +415 -0
  90. package/src/cli/commands/uploads/command-help.ts +45 -0
  91. package/src/cli/commands/uploads/command.ts +465 -0
  92. package/src/cli/commands/uploads/handler.ts +6 -0
  93. package/src/cli/commands/uploads/index.ts +23 -0
  94. package/src/cli/help/command-definitions.ts +6 -0
  95. package/src/cli/router.ts +6 -0
  96. package/src/deno.js +1 -1
  97. package/src/src/errors/error-registry.ts +9 -0
  98. package/src/src/errors/index.ts +1 -0
  99. package/src/src/html/html-shell-generator.ts +9 -0
  100. package/src/src/platform/compat/media-types.ts +23 -0
  101. package/src/src/platform/index.ts +3 -0
  102. package/src/src/rendering/orchestrator/pipeline.ts +186 -172
  103. package/src/src/server/bootstrap.ts +6 -1
  104. package/src/src/server/dev-server/error-overlay/error-formatter.ts +21 -0
  105. package/src/src/server/dev-server/error-overlay/html-template.ts +139 -8
  106. package/src/src/server/dev-server/error-overlay/index.ts +1 -0
  107. package/src/src/server/dev-server/error-overlay/overlay-renderer.ts +2 -1
  108. package/src/src/server/dev-server/request-handler.ts +6 -2
  109. package/src/src/server/services/rendering/ssr.service.ts +9 -2
  110. package/src/src/server/utils/error-html.ts +29 -6
@@ -0,0 +1,513 @@
1
+ import * as dntShim from "../../../_dnt.shims.js";
2
+ import { z } from "zod";
3
+ import { createFileSystem } from "../../../src/platform/index.js";
4
+ import { basename, extname, join, normalize, relative } from "../../../src/platform/compat/path/index.js";
5
+ import { withSpan } from "../../../src/observability/tracing/otlp-setup.js";
6
+ import { cliLogger } from "../../utils/index.js";
7
+ import { type ApiClient, createApiClient, resolveConfigWithAuth } from "../../shared/config.js";
8
+ import type { ParsedArgs } from "../../shared/types.js";
9
+ import { downloadUploadToFile, listAllUploads, type UploadItem } from "../uploads/command.js";
10
+ import { putRemoteFileFromLocal } from "../files/command.js";
11
+ import { knowledgeIngestPythonSource } from "./parser-source.js";
12
+
13
+ const SUPPORTED_EXTENSIONS = new Set([
14
+ ".pdf",
15
+ ".csv",
16
+ ".tsv",
17
+ ".docx",
18
+ ".xlsx",
19
+ ".xls",
20
+ ".pptx",
21
+ ".html",
22
+ ".htm",
23
+ ".txt",
24
+ ".json",
25
+ ".md",
26
+ ".mdx",
27
+ ]);
28
+
29
+ export interface KnowledgeParserResult {
30
+ success: true;
31
+ source_path: string;
32
+ source_filename: string;
33
+ source_type: string;
34
+ slug: string;
35
+ sandbox_output_path: string;
36
+ suggested_project_path: string;
37
+ description: string;
38
+ title: string;
39
+ summary: string;
40
+ stats: Record<string, unknown>;
41
+ warnings: string[];
42
+ }
43
+
44
+ export interface KnowledgeIngestFileResult {
45
+ source: string;
46
+ localSourcePath: string;
47
+ outputPath: string;
48
+ remotePath: string;
49
+ slug: string;
50
+ sourceType: string;
51
+ summary: string;
52
+ stats: Record<string, unknown>;
53
+ warnings: string[];
54
+ }
55
+
56
+ type KnowledgeSource =
57
+ | { kind: "local"; input: string; localPath: string }
58
+ | { kind: "upload"; input: string; uploadPath: string; localPath: string };
59
+
60
+ type DownloadResult = { uploadPath: string; localPath: string; bytes?: number };
61
+
62
+ const KnowledgeIngestArgsSchema = z.object({
63
+ projectSlug: z.string().optional(),
64
+ projectDir: z.string().optional(),
65
+ source: z.string().optional(),
66
+ path: z.string().optional(),
67
+ all: z.boolean().default(false),
68
+ recursive: z.boolean().default(false),
69
+ outputDir: z.string().optional(),
70
+ knowledgePath: z.string().default("knowledge"),
71
+ description: z.string().optional(),
72
+ slug: z.string().optional(),
73
+ json: z.boolean().default(false),
74
+ quiet: z.boolean().default(false),
75
+ });
76
+
77
+ export type KnowledgeIngestOptions = z.infer<typeof KnowledgeIngestArgsSchema>;
78
+
79
+ function getStringArg(args: ParsedArgs, ...keys: string[]): string | undefined {
80
+ for (const key of keys) {
81
+ const value = args[key];
82
+ if (typeof value === "string" && value) return value;
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ function getBooleanArg(args: ParsedArgs, ...keys: string[]): boolean {
88
+ return keys.some((key) => Boolean(args[key]));
89
+ }
90
+
91
+ function printJson(value: unknown): void {
92
+ console.log(JSON.stringify(value, null, 2));
93
+ }
94
+
95
+ function showKnowledgeUsage(): void {
96
+ console.log(`
97
+ Veryfront Knowledge
98
+
99
+ Usage:
100
+ veryfront knowledge ingest <source> [options]
101
+ veryfront knowledge ingest --path <prefix-or-dir> --all [options]
102
+
103
+ Subcommands:
104
+ ingest Orchestrate upload resolution, parsing, and knowledge file writes
105
+ `);
106
+ }
107
+
108
+ export function parseKnowledgeIngestArgs(
109
+ args: ParsedArgs,
110
+ ): z.SafeParseReturnType<unknown, KnowledgeIngestOptions> {
111
+ return KnowledgeIngestArgsSchema.safeParse({
112
+ projectSlug: getStringArg(args, "project", "p", "project-slug"),
113
+ projectDir: getStringArg(args, "project-dir", "dir", "d"),
114
+ source: typeof args._[2] === "string" ? args._[2] : undefined,
115
+ path: getStringArg(args, "path"),
116
+ all: getBooleanArg(args, "all"),
117
+ recursive: getBooleanArg(args, "recursive"),
118
+ outputDir: getStringArg(args, "output-dir"),
119
+ knowledgePath: getStringArg(args, "knowledge-path") ?? "knowledge",
120
+ description: getStringArg(args, "description", "desc"),
121
+ slug: getStringArg(args, "slug"),
122
+ json: getBooleanArg(args, "json", "j"),
123
+ quiet: getBooleanArg(args, "quiet", "q"),
124
+ });
125
+ }
126
+
127
+ export function normalizeKnowledgeInputPath(inputPath: string): string {
128
+ const normalizedPath = normalize(inputPath).replace(/^\/+/, "").replace(/\\/g, "/");
129
+ if (!normalizedPath || normalizedPath.startsWith("..") || normalizedPath.startsWith("/")) {
130
+ throw new Error(`Invalid knowledge input path: ${inputPath}`);
131
+ }
132
+ return normalizedPath;
133
+ }
134
+
135
+ export function normalizeProjectUploadPath(inputPath: string): string {
136
+ const normalizedPath = normalizeKnowledgeInputPath(inputPath);
137
+ return normalizedPath === "uploads" ? "" : normalizedPath.replace(/^uploads\/+/, "");
138
+ }
139
+
140
+ export function formatKnowledgeUploadSource(uploadPath: string): string {
141
+ const normalizedPath = normalizeKnowledgeInputPath(uploadPath);
142
+ return normalizedPath === "uploads" || normalizedPath.startsWith("uploads/")
143
+ ? normalizedPath
144
+ : `uploads/${normalizedPath}`;
145
+ }
146
+
147
+ export function isLikelyLocalPath(value: string): boolean {
148
+ return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") ||
149
+ /^[A-Za-z]:[\\/]/.test(value);
150
+ }
151
+
152
+ function isProjectUploadReference(value: string): boolean {
153
+ if (isLikelyLocalPath(value)) return false;
154
+ const normalizedValue = normalize(value).replace(/\\/g, "/").replace(/^\/+/, "");
155
+ return normalizedValue === "uploads" || normalizedValue.startsWith("uploads/");
156
+ }
157
+
158
+ function isSupportedKnowledgeFile(path: string): boolean {
159
+ return SUPPORTED_EXTENSIONS.has(extname(path).toLowerCase());
160
+ }
161
+
162
+ function slugify(value: string): string {
163
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "document";
164
+ }
165
+
166
+ function defaultOutputRoot(): Promise<string> {
167
+ return dntShim.Deno.makeTempDir({ prefix: "veryfront-knowledge-" });
168
+ }
169
+
170
+ export function resolveKnowledgeDownloadOutputDir(outputDir: string): string {
171
+ return join(outputDir, ".uploads");
172
+ }
173
+
174
+ async function collectLocalFiles(root: string, recursive: boolean): Promise<string[]> {
175
+ const fs = createFileSystem();
176
+ const stat = await fs.stat(root);
177
+ if (stat.isFile) return isSupportedKnowledgeFile(root) ? [root] : [];
178
+ if (!stat.isDirectory) return [];
179
+
180
+ const files: string[] = [];
181
+ async function walk(dir: string): Promise<void> {
182
+ for await (const entry of fs.readDir(dir)) {
183
+ const entryPath = join(dir, entry.name);
184
+ if (entry.isDirectory) {
185
+ if (recursive) await walk(entryPath);
186
+ continue;
187
+ }
188
+ if (entry.isFile && isSupportedKnowledgeFile(entryPath)) {
189
+ files.push(entryPath);
190
+ }
191
+ }
192
+ }
193
+
194
+ await walk(root);
195
+ return files.sort();
196
+ }
197
+
198
+ function buildSourceReference(source: KnowledgeSource): string {
199
+ return source.kind === "upload" ? formatKnowledgeUploadSource(source.uploadPath) : source.input;
200
+ }
201
+
202
+ function buildSuggestedSlug(source: KnowledgeSource, index: number): string {
203
+ const normalized = normalize(
204
+ source.kind === "upload" ? source.uploadPath : source.localPath,
205
+ ).replace(/\\/g, "/");
206
+
207
+ let stripped: string;
208
+ if (source.kind === "upload") {
209
+ stripped = normalized
210
+ .replace(/^\/workspace\/uploads\//, "")
211
+ .replace(/^\/workspace\//, "")
212
+ .replace(/^uploads\//, "")
213
+ .replace(/\.[^.]+$/, "");
214
+ } else if (normalized.startsWith("/workspace/uploads/")) {
215
+ stripped = normalized.replace(/^\/workspace\/uploads\//, "").replace(/\.[^.]+$/, "");
216
+ } else if (normalized.startsWith("/workspace/")) {
217
+ stripped = normalized.replace(/^\/workspace\//, "").replace(/\.[^.]+$/, "");
218
+ } else if (normalized.startsWith("/")) {
219
+ stripped = basename(normalized, extname(normalized));
220
+ } else {
221
+ stripped = normalized.replace(/\.[^.]+$/, "");
222
+ }
223
+
224
+ return slugify(stripped || basename(normalized, extname(normalized)) || `document-${index + 1}`);
225
+ }
226
+ function ensureUniqueSlugs(sources: KnowledgeSource[]): string[] {
227
+ const counts = new Map<string, number>();
228
+ return sources.map((source, index) => {
229
+ const baseSlug = buildSuggestedSlug(source, index);
230
+ const nextCount = (counts.get(baseSlug) ?? 0) + 1;
231
+ counts.set(baseSlug, nextCount);
232
+ return nextCount === 1 ? baseSlug : `${baseSlug}-${nextCount}`;
233
+ });
234
+ }
235
+
236
+ export function deriveKnowledgeRemotePath(
237
+ outputPath: string,
238
+ outputDir: string,
239
+ knowledgePath: string,
240
+ ): string {
241
+ const relativeOutputPath = relative(outputDir, outputPath).replace(/\\/g, "/");
242
+ if (!relativeOutputPath || relativeOutputPath.startsWith("..")) {
243
+ throw new Error(`Output path is outside output directory: ${outputPath}`);
244
+ }
245
+ const prefix = normalizeKnowledgeInputPath(knowledgePath);
246
+ const normalizedRelative = normalize(relativeOutputPath).replace(/^\/+/, "");
247
+ return `${prefix}/${normalizedRelative}`.replace(/\\/g, "/");
248
+ }
249
+
250
+ export function createKnowledgeIngestResult(input: {
251
+ source: string;
252
+ localSourcePath: string;
253
+ outputPath: string;
254
+ remotePath: string;
255
+ parser: Pick<KnowledgeParserResult, "slug" | "stats" | "warnings" | "source_type" | "summary">;
256
+ }): KnowledgeIngestFileResult {
257
+ return {
258
+ source: input.source,
259
+ localSourcePath: input.localSourcePath,
260
+ outputPath: input.outputPath,
261
+ remotePath: input.remotePath,
262
+ slug: input.parser.slug,
263
+ sourceType: input.parser.source_type,
264
+ summary: input.parser.summary,
265
+ stats: input.parser.stats,
266
+ warnings: input.parser.warnings,
267
+ };
268
+ }
269
+
270
+ export async function runKnowledgeParser(input: {
271
+ filePath: string;
272
+ outputDir: string;
273
+ description?: string;
274
+ slug?: string;
275
+ sourceReference?: string;
276
+ }): Promise<KnowledgeParserResult> {
277
+ const tempDir = await dntShim.Deno.makeTempDir({ prefix: "veryfront-knowledge-parser-" });
278
+ const inputJsonPath = `${tempDir}/input.json`;
279
+ const outputJsonPath = `${tempDir}/output.json`;
280
+ const scriptPath = `${tempDir}/ingest_document_to_knowledge.py`;
281
+
282
+ try {
283
+ await dntShim.Deno.writeTextFile(
284
+ inputJsonPath,
285
+ JSON.stringify({
286
+ file_path: input.filePath,
287
+ output_dir: input.outputDir,
288
+ description: input.description,
289
+ slug: input.slug,
290
+ source_reference: input.sourceReference,
291
+ }),
292
+ );
293
+ await dntShim.Deno.writeTextFile(scriptPath, knowledgeIngestPythonSource);
294
+
295
+ let result: dntShim.Deno.CommandOutput;
296
+ try {
297
+ result = await new dntShim.Deno.Command("python3", {
298
+ args: [scriptPath, "--input-json", inputJsonPath, "--output-json", outputJsonPath],
299
+ stdout: "piped",
300
+ stderr: "piped",
301
+ }).output();
302
+ } catch (error) {
303
+ if (error instanceof dntShim.Deno.errors.NotFound) {
304
+ throw new Error(
305
+ "knowledge ingest requires python3. Install python3 and the supported parser packages, or run the command inside the Veryfront sandbox.",
306
+ );
307
+ }
308
+ throw error;
309
+ }
310
+
311
+ if (result.code !== 0) {
312
+ const stderr = new TextDecoder().decode(result.stderr).trim();
313
+ throw new Error(`knowledge ingest parser failed${stderr ? `: ${stderr}` : ""}`);
314
+ }
315
+
316
+ const raw = await dntShim.Deno.readTextFile(outputJsonPath);
317
+ return JSON.parse(raw) as KnowledgeParserResult;
318
+ } finally {
319
+ await dntShim.Deno.remove(tempDir, { recursive: true }).catch(() => undefined);
320
+ }
321
+ }
322
+
323
+ export async function collectKnowledgeSources(
324
+ options: Pick<KnowledgeIngestOptions, "source" | "path" | "all" | "recursive">,
325
+ deps: {
326
+ client: ApiClient;
327
+ projectSlug: string;
328
+ downloadUploads: (uploadPaths: string[]) => Promise<DownloadResult[]>;
329
+ },
330
+ ): Promise<KnowledgeSource[]> {
331
+ const fs = createFileSystem();
332
+
333
+ if (options.source) {
334
+ if (!isProjectUploadReference(options.source) && await fs.exists(options.source)) {
335
+ const localFiles = await collectLocalFiles(options.source, options.recursive);
336
+ if (!localFiles.length) throw new Error(`No supported files found at ${options.source}`);
337
+ return localFiles.map((localPath) => ({ kind: "local", input: options.source!, localPath }));
338
+ }
339
+
340
+ if (isLikelyLocalPath(options.source)) {
341
+ throw new Error(`Local file not found: ${options.source}`);
342
+ }
343
+
344
+ const uploadPath = normalizeProjectUploadPath(options.source);
345
+ const downloads = await deps.downloadUploads([uploadPath]);
346
+ return downloads.map((download) => ({
347
+ kind: "upload",
348
+ input: options.source!,
349
+ uploadPath: download.uploadPath,
350
+ localPath: download.localPath,
351
+ }));
352
+ }
353
+
354
+ if (!options.path || !options.all) {
355
+ throw new Error("Provide a source path or use --path with --all.");
356
+ }
357
+
358
+ if (!isProjectUploadReference(options.path) && await fs.exists(options.path)) {
359
+ const localFiles = await collectLocalFiles(options.path, options.recursive);
360
+ if (!localFiles.length) throw new Error(`No supported files found under ${options.path}`);
361
+ return localFiles.map((localPath) => ({ kind: "local", input: options.path!, localPath }));
362
+ }
363
+
364
+ const displayUploadPrefix = normalizeKnowledgeInputPath(options.path);
365
+ const uploadPrefix = normalizeProjectUploadPath(options.path);
366
+
367
+ const listUploadsForPrefix = async (pathPrefix?: string): Promise<UploadItem[]> =>
368
+ listAllUploads(deps.client, deps.projectSlug, {
369
+ path: pathPrefix || undefined,
370
+ recursive: options.recursive ?? true,
371
+ limit: 100,
372
+ });
373
+
374
+ let uploads = await listUploadsForPrefix(uploadPrefix || undefined);
375
+ let uploadTargets = uploads
376
+ .filter((item: UploadItem) => item.type !== "folder" && isSupportedKnowledgeFile(item.path))
377
+ .map((item: UploadItem) => item.path);
378
+
379
+ if (!uploadTargets.length && uploadPrefix && !uploadPrefix.endsWith("/")) {
380
+ uploads = await listUploadsForPrefix(`${uploadPrefix}/`);
381
+ uploadTargets = uploads
382
+ .filter((item: UploadItem) => item.type !== "folder" && isSupportedKnowledgeFile(item.path))
383
+ .map((item: UploadItem) => item.path);
384
+ }
385
+
386
+ if (!uploadTargets.length) {
387
+ throw new Error(`No supported uploads found under ${displayUploadPrefix}`);
388
+ }
389
+
390
+ const downloads = await deps.downloadUploads(uploadTargets);
391
+ return downloads.map((download) => ({
392
+ kind: "upload",
393
+ input: options.path!,
394
+ uploadPath: download.uploadPath,
395
+ localPath: download.localPath,
396
+ }));
397
+ }
398
+
399
+ export async function ingestResolvedSources(
400
+ sources: KnowledgeSource[],
401
+ options: KnowledgeIngestOptions,
402
+ deps: {
403
+ client: ApiClient;
404
+ projectSlug: string;
405
+ outputDir: string;
406
+ runParser: typeof runKnowledgeParser;
407
+ uploadKnowledgeFile: (remotePath: string, localPath: string) => Promise<{ path: string }>;
408
+ },
409
+ ): Promise<KnowledgeIngestFileResult[]> {
410
+ const slugs = options.slug && sources.length === 1 ? [options.slug] : ensureUniqueSlugs(sources);
411
+ const results: KnowledgeIngestFileResult[] = [];
412
+
413
+ for (const [index, source] of sources.entries()) {
414
+ const parser = await deps.runParser({
415
+ filePath: source.localPath,
416
+ outputDir: deps.outputDir,
417
+ description: options.description,
418
+ slug: slugs[index],
419
+ sourceReference: buildSourceReference(source),
420
+ });
421
+ const remotePath = deriveKnowledgeRemotePath(
422
+ parser.sandbox_output_path,
423
+ deps.outputDir,
424
+ options.knowledgePath,
425
+ );
426
+ const uploaded = await deps.uploadKnowledgeFile(remotePath, parser.sandbox_output_path);
427
+ results.push(
428
+ createKnowledgeIngestResult({
429
+ source: buildSourceReference(source),
430
+ localSourcePath: source.localPath,
431
+ outputPath: parser.sandbox_output_path,
432
+ remotePath: uploaded.path,
433
+ parser,
434
+ }),
435
+ );
436
+ }
437
+
438
+ return results;
439
+ }
440
+
441
+ export async function knowledgeCommand(args: ParsedArgs): Promise<void> {
442
+ const subcommand = typeof args._[1] === "string" ? args._[1] : undefined;
443
+
444
+ if (!subcommand || subcommand === "help") {
445
+ showKnowledgeUsage();
446
+ return;
447
+ }
448
+
449
+ await withSpan("cli.command.knowledge", async () => {
450
+ switch (subcommand) {
451
+ case "ingest": {
452
+ const parsed = parseKnowledgeIngestArgs(args);
453
+ if (!parsed.success) {
454
+ throw new Error(`Invalid knowledge ingest arguments: ${parsed.error.message}`);
455
+ }
456
+
457
+ const options = parsed.data;
458
+ let config = await resolveConfigWithAuth(options.projectDir);
459
+ if (options.projectSlug) config = { ...config, projectSlug: options.projectSlug };
460
+
461
+ const client = createApiClient(config);
462
+ const outputDir = options.outputDir ?? await defaultOutputRoot();
463
+ const shouldCleanupOutputDir = options.outputDir === undefined;
464
+ const downloadOutputDir = resolveKnowledgeDownloadOutputDir(outputDir);
465
+
466
+ try {
467
+ const sources = await collectKnowledgeSources(options, {
468
+ client,
469
+ projectSlug: config.projectSlug,
470
+ downloadUploads: (uploadPaths) =>
471
+ Promise.all(
472
+ uploadPaths.map((uploadPath) =>
473
+ downloadUploadToFile(client, config.projectSlug, uploadPath, downloadOutputDir)
474
+ ),
475
+ ),
476
+ });
477
+
478
+ const results = await ingestResolvedSources(sources, options, {
479
+ client,
480
+ projectSlug: config.projectSlug,
481
+ outputDir,
482
+ runParser: runKnowledgeParser,
483
+ uploadKnowledgeFile: (remotePath, localPath) =>
484
+ putRemoteFileFromLocal(client, config.projectSlug, remotePath, localPath),
485
+ });
486
+
487
+ if (options.json) {
488
+ printJson(results);
489
+ return;
490
+ }
491
+
492
+ for (const result of results) {
493
+ if (!options.quiet) {
494
+ cliLogger.info(`Ingested ${result.source} -> ${result.remotePath}`);
495
+ cliLogger.info(` ${result.summary}`);
496
+ }
497
+ }
498
+ } finally {
499
+ if (shouldCleanupOutputDir) {
500
+ await Promise.all([
501
+ dntShim.Deno.remove(outputDir, { recursive: true }).catch(() => undefined),
502
+ dntShim.Deno.remove(downloadOutputDir, { recursive: true }).catch(() => undefined),
503
+ ]);
504
+ }
505
+ }
506
+ return;
507
+ }
508
+
509
+ default:
510
+ showKnowledgeUsage();
511
+ }
512
+ });
513
+ }
@@ -0,0 +1,6 @@
1
+ import type { ParsedArgs } from "../../shared/types.js";
2
+ import { knowledgeCommand } from "./command.js";
3
+
4
+ export async function handleKnowledgeCommand(args: ParsedArgs): Promise<void> {
5
+ await knowledgeCommand(args);
6
+ }
@@ -0,0 +1,2 @@
1
+ export { handleKnowledgeCommand } from "./handler.js";
2
+ export { knowledgeCommand } from "./command.js";