@wityai/root2-cli
Version:
Command-line interface for Root2 vector memory layer
423 lines ⢠15.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.importDirCommand = importDirCommand;
const chalk_1 = __importDefault(require("chalk"));
const inquirer_1 = __importDefault(require("inquirer"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const glob_1 = require("glob");
const root2_api_client_1 = require("@wityai/root2-api-client");
const config_1 = require("../utils/config");
const formatting_1 = require("../utils/formatting");
const spinner_1 = require("../utils/spinner");
const memory_blocks_1 = require("./memory-blocks");
function importDirCommand(program) {
program
.command("import-dir")
.alias("id")
.description("Import a directory and its contents into Root2 knowledge base")
.argument("<dirPath>", "Path to the directory to import")
.option("--memory-block <id>", "Memory block ID to import into")
.option("-r, --recursive", "Include subdirectories recursively", false)
.option("--include <pattern>", 'File pattern to include (e.g., "*.{pdf,txt,md}")')
.option("--exclude <pattern>", "File pattern to exclude")
.option("--max-files <number>", "Maximum number of files to import", "100")
.option("--metadata <metadata>", "JSON string of metadata to attach to all files")
.option("--dry-run", "Preview what would be imported without actually importing")
.option("-v, --verbose", "Show detailed progress information")
.action(async (dirPath, options) => {
try {
const config = await (0, config_1.validateConfig)();
const apiClient = new root2_api_client_1.Root2Api({
clientId: config.clientId,
apiKey: config.apiKey,
apiHost: config.apiHost,
enableLogging: config.verbose || options.verbose || false,
});
await importDirectory(apiClient, dirPath, options);
}
catch (error) {
console.error(formatting_1.format.error(error instanceof Error ? error.message : "Unknown error"));
process.exit(1);
}
});
program
.command("import-dir-interactive")
.alias("idi")
.description("Import a directory with interactive prompts")
.action(async () => {
try {
const config = await (0, config_1.validateConfig)();
const apiClient = new root2_api_client_1.Root2Api({
clientId: config.clientId,
apiKey: config.apiKey,
apiHost: config.apiHost,
enableLogging: config.verbose || false,
});
await interactiveDirImport(apiClient);
}
catch (error) {
console.error(formatting_1.format.error(error instanceof Error ? error.message : "Unknown error"));
process.exit(1);
}
});
}
async function importDirectory(apiClient, dirPath, options) {
let memoryBlockId;
if (options.memoryBlock) {
if (!(0, memory_blocks_1.isValidMemoryBlockId)(options.memoryBlock)) {
throw new Error(`Invalid memory block ID format: ${options.memoryBlock}`);
}
memoryBlockId = options.memoryBlock;
}
else {
memoryBlockId = await (0, memory_blocks_1.selectMemoryBlock)(apiClient);
}
const resolvedPath = path.resolve(dirPath);
if (!(await fs.pathExists(resolvedPath))) {
throw new Error(`Directory not found: ${dirPath}`);
}
const stats = await fs.stat(resolvedPath);
if (!stats.isDirectory()) {
throw new Error(`Path is not a directory: ${dirPath}`);
}
let metadata;
if (options.metadata) {
try {
metadata = JSON.parse(options.metadata);
}
catch (error) {
throw new Error("Invalid metadata JSON format");
}
}
console.log(chalk_1.default.cyan("\nš Scanning directory..."));
const files = await findFilesToImport(resolvedPath, {
recursive: options.recursive,
includePattern: options.include,
excludePattern: options.exclude,
maxFiles: parseInt(options.maxFiles),
});
if (files.length === 0) {
console.log(formatting_1.format.warning("No files found matching the criteria."));
return;
}
console.log(chalk_1.default.cyan("\nš Import Summary:"));
console.log(` ${chalk_1.default.blue("Directory")}: ${formatting_1.format.filePath(resolvedPath)}`);
console.log(` ${chalk_1.default.blue("Files Found")}: ${files.length}`);
console.log(` ${chalk_1.default.blue("Recursive")}: ${options.recursive ? "Yes" : "No"}`);
if (options.include) {
console.log(` ${chalk_1.default.blue("Include Pattern")}: ${options.include}`);
}
if (options.exclude) {
console.log(` ${chalk_1.default.blue("Exclude Pattern")}: ${options.exclude}`);
}
if (metadata) {
console.log(` ${chalk_1.default.blue("Metadata")}: ${JSON.stringify(metadata, null, 2)}`);
}
const fileTypes = getFileTypeBreakdown(files);
if (Object.keys(fileTypes).length > 0) {
console.log("\nš File Types:");
Object.entries(fileTypes).forEach(([ext, count]) => {
console.log(` ${ext || "No extension"}: ${count} file(s)`);
});
}
if (options.verbose) {
console.log("\nš Files to import:");
files.forEach((file, index) => {
console.log(` ${index + 1}. ${path.relative(resolvedPath, file)}`);
});
}
if (options.dryRun) {
console.log(formatting_1.format.info("\nš Dry run mode - no actual import will be performed"));
console.log(formatting_1.format.success("ā
Directory scan completed. Ready for import."));
return;
}
const { shouldProceed } = await inquirer_1.default.prompt([
{
type: "confirm",
name: "shouldProceed",
message: `Proceed with importing ${files.length} file(s)?`,
default: true,
},
]);
if (!shouldProceed) {
console.log(formatting_1.format.info("Import cancelled."));
return;
}
const result = await (0, spinner_1.withSpinner)(`Importing directory: ${path.basename(dirPath)}...`, () => apiClient.importDir(memoryBlockId, {
dirPath: resolvedPath,
recursive: options.recursive,
includePattern: options.include,
excludePattern: options.exclude,
metadata,
}), {
successText: "Directory import completed",
errorText: "Directory import failed",
});
if (result.success && result.data) {
const successCount = Array.isArray(result.data) ? result.data.length : 1;
console.log(formatting_1.format.success(`\nā
Import completed successfully!`));
console.log(` ${chalk_1.default.blue("Files Processed")}: ${successCount}`);
if (options.verbose && Array.isArray(result.data)) {
console.log("\nš Import Results:");
result.data.forEach((item, index) => {
console.log(` ${index + 1}. ${item.id || "N/A"} - ${item.status || "N/A"}`);
});
}
}
else {
throw new Error(result.error || "Import failed with unknown error");
}
}
async function interactiveDirImport(apiClient) {
console.log(chalk_1.default.cyan("\nš Interactive Directory Import\n"));
const memoryBlockId = await (0, memory_blocks_1.selectMemoryBlock)(apiClient);
const answers = await inquirer_1.default.prompt([
{
type: "input",
name: "dirPath",
message: "Enter the directory path to import:",
validate: async (input) => {
if (!input.trim()) {
return "Directory path is required";
}
const resolvedPath = path.resolve(input.trim());
if (!(await fs.pathExists(resolvedPath))) {
return `Directory not found: ${input}`;
}
const stats = await fs.stat(resolvedPath);
if (!stats.isDirectory()) {
return `Path is not a directory: ${input}`;
}
return true;
},
},
{
type: "confirm",
name: "recursive",
message: "Import subdirectories recursively?",
default: true,
},
{
type: "input",
name: "includePattern",
message: 'File pattern to include (optional, e.g., "*.{pdf,txt,md}"):',
default: "",
},
{
type: "input",
name: "excludePattern",
message: "File pattern to exclude (optional):",
default: "",
},
{
type: "input",
name: "maxFiles",
message: "Maximum number of files to import:",
default: "100",
validate: (input) => {
const num = parseInt(input);
if (isNaN(num) || num < 1 || num > 1000) {
return "Max files must be a number between 1 and 1000";
}
return true;
},
},
{
type: "confirm",
name: "addMetadata",
message: "Add metadata to all imported files?",
default: false,
},
]);
let metadata;
if (answers.addMetadata) {
const metadataAnswers = await collectMetadata();
if (Object.keys(metadataAnswers).length > 0) {
metadata = metadataAnswers;
}
}
const options = {
memoryBlock: memoryBlockId,
recursive: answers.recursive,
include: answers.includePattern || undefined,
exclude: answers.excludePattern || undefined,
maxFiles: answers.maxFiles,
metadata: metadata ? JSON.stringify(metadata) : undefined,
verbose: false,
dryRun: false,
};
await importDirectory(apiClient, answers.dirPath, options);
}
async function findFilesToImport(dirPath, options) {
const { recursive = false, includePattern, excludePattern, maxFiles = 100, } = options;
let pattern = recursive ? "**/*" : "*";
if (includePattern) {
pattern = recursive ? `**/${includePattern}` : includePattern;
}
const globOptions = {
cwd: dirPath,
absolute: true,
nodir: true,
};
let files = await (0, glob_1.glob)(pattern, globOptions);
if (excludePattern) {
const excludeFiles = await (0, glob_1.glob)(recursive ? `**/${excludePattern}` : excludePattern, globOptions);
const excludeSet = new Set(excludeFiles);
files = files.filter((file) => !excludeSet.has(file));
}
files = files.filter((file) => isSupportedFileType(file));
if (files.length > maxFiles) {
files = files.slice(0, maxFiles);
}
return files;
}
function getFileTypeBreakdown(files) {
const breakdown = {};
files.forEach((file) => {
const ext = path.extname(file).toLowerCase() || "No extension";
breakdown[ext] = (breakdown[ext] || 0) + 1;
});
return breakdown;
}
async function collectMetadata() {
const metadata = {};
console.log(chalk_1.default.yellow("\nš·ļø Adding Metadata (press Enter with empty key to finish):\n"));
while (true) {
const { key } = await inquirer_1.default.prompt([
{
type: "input",
name: "key",
message: "Metadata key:",
default: "",
},
]);
if (!key.trim()) {
break;
}
const { value, valueType } = await inquirer_1.default.prompt([
{
type: "list",
name: "valueType",
message: "Value type:",
choices: [
{ name: "String", value: "string" },
{ name: "Number", value: "number" },
{ name: "Boolean", value: "boolean" },
{ name: "JSON", value: "json" },
],
default: "string",
},
{
type: "input",
name: "value",
message: `Value for "${key}":`,
validate: (input, answers) => {
if (!input.trim() && answers.valueType !== "boolean") {
return "Value is required";
}
if (answers.valueType === "number" && isNaN(Number(input))) {
return "Value must be a valid number";
}
if (answers.valueType === "json") {
try {
JSON.parse(input);
}
catch {
return "Value must be valid JSON";
}
}
return true;
},
},
]);
switch (valueType) {
case "number":
metadata[key] = Number(value);
break;
case "boolean":
metadata[key] = ["true", "yes", "1", "on"].includes(value.toLowerCase());
break;
case "json":
metadata[key] = JSON.parse(value);
break;
case "string":
default:
metadata[key] = value;
break;
}
console.log(chalk_1.default.green(`ā Added: ${key} = ${JSON.stringify(metadata[key])}`));
}
return metadata;
}
function isSupportedFileType(filePath) {
const supportedExtensions = [
".txt",
".md",
".pdf",
".doc",
".docx",
".html",
".htm",
".json",
".csv",
".rtf",
".odt",
".pages",
".xml",
".js",
".ts",
".py",
".java",
".c",
".cpp",
".h",
".hpp",
".cs",
".php",
".rb",
".go",
".rs",
".swift",
".kt",
];
const ext = path.extname(filePath).toLowerCase();
return supportedExtensions.includes(ext);
}
//# sourceMappingURL=import-dir.js.map