@contextos/cli 0.1.0 → 0.2.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/dist/index.js +532 -0
- package/package.json +40 -40
package/dist/index.js
CHANGED
|
@@ -1331,6 +1331,535 @@ var traceCommand = new Command13("trace").description("Trace function call chain
|
|
|
1331
1331
|
}
|
|
1332
1332
|
});
|
|
1333
1333
|
|
|
1334
|
+
// src/commands/plugin.ts
|
|
1335
|
+
import chalk14 from "chalk";
|
|
1336
|
+
import ora13 from "ora";
|
|
1337
|
+
import inquirer3 from "inquirer";
|
|
1338
|
+
import { createPluginManager, createPluginRegistry } from "@contextos/core";
|
|
1339
|
+
function registerPluginCommand(program2) {
|
|
1340
|
+
const plugin = program2.command("plugin").description("Manage ContextOS plugins");
|
|
1341
|
+
plugin.command("list").description("List installed plugins").option("-a, --all", "Include disabled plugins").option("--remote", "Show available plugins from registry").action(async (options) => {
|
|
1342
|
+
const cwd = process.cwd();
|
|
1343
|
+
const registry = createPluginRegistry(cwd);
|
|
1344
|
+
if (options.remote) {
|
|
1345
|
+
console.log(chalk14.cyan("\n\u{1F4E6} Featured Plugins:\n"));
|
|
1346
|
+
const featured = await registry.getFeatured();
|
|
1347
|
+
for (const plugin2 of featured) {
|
|
1348
|
+
const installed = registry.isInstalled(plugin2.name);
|
|
1349
|
+
const status = installed ? chalk14.green("\u2713 installed") : "";
|
|
1350
|
+
console.log(` ${chalk14.bold(plugin2.name)} ${chalk14.gray(`v${plugin2.version}`)} ${status}`);
|
|
1351
|
+
console.log(` ${plugin2.description}`);
|
|
1352
|
+
console.log(` ${chalk14.gray(`Downloads: ${plugin2.downloads}`)}
|
|
1353
|
+
`);
|
|
1354
|
+
}
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const plugins = registry.listLocal();
|
|
1358
|
+
if (plugins.length === 0) {
|
|
1359
|
+
console.log(chalk14.yellow("\nNo plugins installed."));
|
|
1360
|
+
console.log(chalk14.gray("Run `ctx plugin list --remote` to see available plugins.\n"));
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
console.log(chalk14.cyan("\n\u{1F50C} Installed Plugins:\n"));
|
|
1364
|
+
for (const plugin2 of plugins) {
|
|
1365
|
+
if (!options.all && !plugin2.enabled) continue;
|
|
1366
|
+
const status = plugin2.enabled ? chalk14.green("\u25CF enabled") : chalk14.gray("\u25CB disabled");
|
|
1367
|
+
console.log(` ${status} ${chalk14.bold(plugin2.name)} ${chalk14.gray(`v${plugin2.version}`)}`);
|
|
1368
|
+
console.log(` ${plugin2.description || "No description"}`);
|
|
1369
|
+
console.log(` ${chalk14.gray(plugin2.path)}
|
|
1370
|
+
`);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
plugin.command("install <source>").description("Install a plugin").option("-l, --local", "Install from local path").option("-f, --force", "Force reinstall").action(async (source, options) => {
|
|
1374
|
+
const spinner = ora13("Installing plugin...").start();
|
|
1375
|
+
try {
|
|
1376
|
+
const cwd = process.cwd();
|
|
1377
|
+
const manager = createPluginManager(cwd);
|
|
1378
|
+
const plugin2 = await manager.install(source, {
|
|
1379
|
+
local: options.local,
|
|
1380
|
+
force: options.force
|
|
1381
|
+
});
|
|
1382
|
+
spinner.succeed(`Installed ${chalk14.bold(plugin2.name)} v${plugin2.version}`);
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
spinner.fail(`Failed to install plugin`);
|
|
1385
|
+
console.error(chalk14.red(error instanceof Error ? error.message : String(error)));
|
|
1386
|
+
process.exit(1);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
plugin.command("remove <name>").alias("uninstall").description("Remove a plugin").option("-f, --force", "Skip confirmation").action(async (name, options) => {
|
|
1390
|
+
const cwd = process.cwd();
|
|
1391
|
+
const registry = createPluginRegistry(cwd);
|
|
1392
|
+
const localPlugin = registry.getLocal(name);
|
|
1393
|
+
if (!localPlugin) {
|
|
1394
|
+
console.log(chalk14.red(`Plugin not found: ${name}`));
|
|
1395
|
+
process.exit(1);
|
|
1396
|
+
}
|
|
1397
|
+
if (!options.force) {
|
|
1398
|
+
const { confirm } = await inquirer3.prompt([
|
|
1399
|
+
{
|
|
1400
|
+
type: "confirm",
|
|
1401
|
+
name: "confirm",
|
|
1402
|
+
message: `Remove plugin ${chalk14.bold(name)}?`,
|
|
1403
|
+
default: false
|
|
1404
|
+
}
|
|
1405
|
+
]);
|
|
1406
|
+
if (!confirm) {
|
|
1407
|
+
console.log(chalk14.gray("Cancelled."));
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
const spinner = ora13("Removing plugin...").start();
|
|
1412
|
+
try {
|
|
1413
|
+
const manager = createPluginManager(cwd);
|
|
1414
|
+
await manager.uninstall(name);
|
|
1415
|
+
spinner.succeed(`Removed ${chalk14.bold(name)}`);
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
spinner.fail(`Failed to remove plugin`);
|
|
1418
|
+
console.error(chalk14.red(error instanceof Error ? error.message : String(error)));
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
plugin.command("enable <name>").description("Enable a disabled plugin").action(async (name) => {
|
|
1423
|
+
const cwd = process.cwd();
|
|
1424
|
+
const manager = createPluginManager(cwd);
|
|
1425
|
+
await manager.loadAll();
|
|
1426
|
+
const success = await manager.enablePlugin(name);
|
|
1427
|
+
if (success) {
|
|
1428
|
+
console.log(chalk14.green(`\u2713 Enabled ${chalk14.bold(name)}`));
|
|
1429
|
+
} else {
|
|
1430
|
+
console.log(chalk14.red(`Plugin not found: ${name}`));
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
plugin.command("disable <name>").description("Disable a plugin").action(async (name) => {
|
|
1435
|
+
const cwd = process.cwd();
|
|
1436
|
+
const manager = createPluginManager(cwd);
|
|
1437
|
+
await manager.loadAll();
|
|
1438
|
+
const success = await manager.disablePlugin(name);
|
|
1439
|
+
if (success) {
|
|
1440
|
+
console.log(chalk14.green(`\u2713 Disabled ${chalk14.bold(name)}`));
|
|
1441
|
+
} else {
|
|
1442
|
+
console.log(chalk14.red(`Plugin not found: ${name}`));
|
|
1443
|
+
process.exit(1);
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
plugin.command("create <name>").description("Create a new plugin scaffold").option("-d, --description <desc>", "Plugin description").option("-a, --author <author>", "Plugin author").option("--with-commands", "Include command examples").action(async (name, options) => {
|
|
1447
|
+
const cwd = process.cwd();
|
|
1448
|
+
const answers = await inquirer3.prompt([
|
|
1449
|
+
{
|
|
1450
|
+
type: "input",
|
|
1451
|
+
name: "description",
|
|
1452
|
+
message: "Plugin description:",
|
|
1453
|
+
default: options.description || `A ContextOS plugin`,
|
|
1454
|
+
when: !options.description
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
type: "input",
|
|
1458
|
+
name: "author",
|
|
1459
|
+
message: "Author:",
|
|
1460
|
+
default: options.author || process.env.USER || "Anonymous",
|
|
1461
|
+
when: !options.author
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
type: "checkbox",
|
|
1465
|
+
name: "hooks",
|
|
1466
|
+
message: "Select hooks to implement:",
|
|
1467
|
+
choices: [
|
|
1468
|
+
{ name: "onBeforeContextBuild", value: "onBeforeContextBuild" },
|
|
1469
|
+
{ name: "onAfterContextBuild", value: "onAfterContextBuild" },
|
|
1470
|
+
{ name: "onBeforeIndex", value: "onBeforeIndex" },
|
|
1471
|
+
{ name: "onAfterIndex", value: "onAfterIndex" },
|
|
1472
|
+
{ name: "onBeforeAnalyze", value: "onBeforeAnalyze" },
|
|
1473
|
+
{ name: "onAfterAnalyze", value: "onAfterAnalyze" },
|
|
1474
|
+
{ name: "fileFilter", value: "fileFilter" },
|
|
1475
|
+
{ name: "rankingBoost", value: "rankingBoost" }
|
|
1476
|
+
],
|
|
1477
|
+
default: ["onAfterContextBuild"]
|
|
1478
|
+
}
|
|
1479
|
+
]);
|
|
1480
|
+
const template = {
|
|
1481
|
+
name,
|
|
1482
|
+
description: options.description || answers.description,
|
|
1483
|
+
author: options.author || answers.author,
|
|
1484
|
+
hooks: answers.hooks,
|
|
1485
|
+
withCommands: options.withCommands || false
|
|
1486
|
+
};
|
|
1487
|
+
const spinner = ora13("Creating plugin scaffold...").start();
|
|
1488
|
+
try {
|
|
1489
|
+
const manager = createPluginManager(cwd);
|
|
1490
|
+
const pluginPath = manager.createPluginScaffold(template);
|
|
1491
|
+
spinner.succeed(`Created plugin at ${chalk14.cyan(pluginPath)}`);
|
|
1492
|
+
console.log(`
|
|
1493
|
+
${chalk14.gray("Next steps:")}`);
|
|
1494
|
+
console.log(` 1. Edit ${chalk14.cyan(`${pluginPath}/index.js`)}`);
|
|
1495
|
+
console.log(` 2. Run ${chalk14.cyan(`ctx plugin enable ${name}`)}`);
|
|
1496
|
+
console.log(` 3. Test your plugin!
|
|
1497
|
+
`);
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
spinner.fail(`Failed to create plugin`);
|
|
1500
|
+
console.error(chalk14.red(error instanceof Error ? error.message : String(error)));
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
plugin.command("run <plugin> <command> [args...]").description("Run a custom command from a plugin").action(async (pluginName, command, args) => {
|
|
1505
|
+
const cwd = process.cwd();
|
|
1506
|
+
const manager = createPluginManager(cwd);
|
|
1507
|
+
await manager.loadAll();
|
|
1508
|
+
const pluginState = manager.get(pluginName);
|
|
1509
|
+
if (!pluginState) {
|
|
1510
|
+
console.log(chalk14.red(`Plugin not found: ${pluginName}`));
|
|
1511
|
+
process.exit(1);
|
|
1512
|
+
}
|
|
1513
|
+
const cmd = pluginState.instance.commands?.[command];
|
|
1514
|
+
if (!cmd) {
|
|
1515
|
+
console.log(chalk14.red(`Command not found: ${command}`));
|
|
1516
|
+
console.log(chalk14.gray("Available commands:"));
|
|
1517
|
+
const commands = Object.keys(pluginState.instance.commands || {});
|
|
1518
|
+
if (commands.length === 0) {
|
|
1519
|
+
console.log(chalk14.gray(" (none)"));
|
|
1520
|
+
} else {
|
|
1521
|
+
for (const c of commands) {
|
|
1522
|
+
console.log(` ${c}`);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
try {
|
|
1528
|
+
const context = {
|
|
1529
|
+
projectRoot: cwd,
|
|
1530
|
+
configDir: `${cwd}/.contextos`,
|
|
1531
|
+
log: {
|
|
1532
|
+
debug: console.debug,
|
|
1533
|
+
info: console.info,
|
|
1534
|
+
warn: console.warn,
|
|
1535
|
+
error: console.error
|
|
1536
|
+
},
|
|
1537
|
+
query: async () => ({ files: [], context: "" }),
|
|
1538
|
+
readFile: async () => "",
|
|
1539
|
+
getDependencies: async () => [],
|
|
1540
|
+
storage: {
|
|
1541
|
+
get: () => void 0,
|
|
1542
|
+
set: () => {
|
|
1543
|
+
},
|
|
1544
|
+
delete: () => false
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
await cmd.handler(args, context);
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
console.error(chalk14.red(`Command failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1550
|
+
process.exit(1);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/commands/finetune.ts
|
|
1556
|
+
import chalk15 from "chalk";
|
|
1557
|
+
import ora14 from "ora";
|
|
1558
|
+
import {
|
|
1559
|
+
createTrainingDataCollector,
|
|
1560
|
+
createDatasetFormatter
|
|
1561
|
+
} from "@contextos/core";
|
|
1562
|
+
function registerFinetuneCommand(program2) {
|
|
1563
|
+
const finetune = program2.command("finetune").description("Training data management for model fine-tuning");
|
|
1564
|
+
finetune.command("export").description("Export training data to file").option("-o, --output <path>", "Output file path", "./training-data.jsonl").option("-f, --format <format>", "Output format (jsonl, openai, anthropic, csv)", "jsonl").option("-m, --max <count>", "Maximum examples to export", parseInt).option("-r, --min-rating <rating>", "Minimum rating (good, neutral, bad)").option("-l, --language <lang>", "Filter by language").option("--shuffle", "Shuffle examples").option("--split <ratio>", "Train/validation split ratio", parseFloat).action(async (options) => {
|
|
1565
|
+
const cwd = process.cwd();
|
|
1566
|
+
const spinner = ora14("Exporting training data...").start();
|
|
1567
|
+
try {
|
|
1568
|
+
const collector = createTrainingDataCollector(cwd);
|
|
1569
|
+
const formatter = createDatasetFormatter();
|
|
1570
|
+
const examples = collector.getAll();
|
|
1571
|
+
if (examples.length === 0) {
|
|
1572
|
+
spinner.warn("No training data found");
|
|
1573
|
+
console.log(chalk15.gray("\nTraining data is collected automatically when you use ctx goal/build commands."));
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const format = {
|
|
1577
|
+
format: options.format
|
|
1578
|
+
};
|
|
1579
|
+
const config = {
|
|
1580
|
+
maxExamples: options.max,
|
|
1581
|
+
minRating: options.minRating,
|
|
1582
|
+
languageFilter: options.language,
|
|
1583
|
+
shuffle: options.shuffle,
|
|
1584
|
+
validationSplit: options.split
|
|
1585
|
+
};
|
|
1586
|
+
if (options.split) {
|
|
1587
|
+
const outputDir = options.output.replace(/\.[^.]+$/, "");
|
|
1588
|
+
const result = await formatter.exportWithSplit(
|
|
1589
|
+
examples,
|
|
1590
|
+
outputDir,
|
|
1591
|
+
format,
|
|
1592
|
+
config
|
|
1593
|
+
);
|
|
1594
|
+
spinner.succeed("Export complete");
|
|
1595
|
+
console.log(`
|
|
1596
|
+
Train: ${chalk15.cyan(result.train)} examples \u2192 ${outputDir}/train.jsonl`);
|
|
1597
|
+
console.log(` Validation: ${chalk15.cyan(result.validation)} examples \u2192 ${outputDir}/validation.jsonl`);
|
|
1598
|
+
} else {
|
|
1599
|
+
const result = await formatter.export(
|
|
1600
|
+
examples,
|
|
1601
|
+
options.output,
|
|
1602
|
+
format,
|
|
1603
|
+
config
|
|
1604
|
+
);
|
|
1605
|
+
spinner.succeed(`Exported ${chalk15.cyan(result.exported)} examples to ${chalk15.cyan(result.path)}`);
|
|
1606
|
+
}
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
spinner.fail("Export failed");
|
|
1609
|
+
console.error(chalk15.red(error instanceof Error ? error.message : String(error)));
|
|
1610
|
+
process.exit(1);
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
finetune.command("validate <file>").description("Validate a training data file").action(async (file) => {
|
|
1614
|
+
const spinner = ora14("Validating dataset...").start();
|
|
1615
|
+
try {
|
|
1616
|
+
const formatter = createDatasetFormatter();
|
|
1617
|
+
const result = await formatter.validate(file);
|
|
1618
|
+
if (result.valid) {
|
|
1619
|
+
spinner.succeed("Dataset is valid");
|
|
1620
|
+
} else {
|
|
1621
|
+
spinner.fail("Dataset has errors");
|
|
1622
|
+
}
|
|
1623
|
+
console.log(`
|
|
1624
|
+
${chalk15.bold("Statistics:")}`);
|
|
1625
|
+
console.log(` Total examples: ${chalk15.cyan(result.stats.totalExamples)}`);
|
|
1626
|
+
console.log(` Avg tokens: ${chalk15.cyan(result.stats.avgTokenCount)}`);
|
|
1627
|
+
console.log(` Avg files/example: ${chalk15.cyan(result.stats.avgFilesPerExample)}`);
|
|
1628
|
+
console.log(`
|
|
1629
|
+
${chalk15.bold("Rating Distribution:")}`);
|
|
1630
|
+
console.log(` ${chalk15.green("Good")}: ${result.stats.ratingDistribution.good}`);
|
|
1631
|
+
console.log(` ${chalk15.yellow("Neutral")}: ${result.stats.ratingDistribution.neutral}`);
|
|
1632
|
+
console.log(` ${chalk15.red("Bad")}: ${result.stats.ratingDistribution.bad}`);
|
|
1633
|
+
console.log(` ${chalk15.gray("Unrated")}: ${result.stats.ratingDistribution.unrated}`);
|
|
1634
|
+
if (Object.keys(result.stats.languageDistribution).length > 0) {
|
|
1635
|
+
console.log(`
|
|
1636
|
+
${chalk15.bold("Languages:")}`);
|
|
1637
|
+
for (const [lang, count] of Object.entries(result.stats.languageDistribution)) {
|
|
1638
|
+
console.log(` ${lang}: ${count}`);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (result.errors.length > 0) {
|
|
1642
|
+
console.log(`
|
|
1643
|
+
${chalk15.red.bold("Errors:")}`);
|
|
1644
|
+
for (const error of result.errors.slice(0, 10)) {
|
|
1645
|
+
console.log(` Line ${error.line}: ${error.message}`);
|
|
1646
|
+
}
|
|
1647
|
+
if (result.errors.length > 10) {
|
|
1648
|
+
console.log(chalk15.gray(` ... and ${result.errors.length - 10} more`));
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (result.warnings.length > 0) {
|
|
1652
|
+
console.log(`
|
|
1653
|
+
${chalk15.yellow.bold("Warnings:")}`);
|
|
1654
|
+
for (const warning of result.warnings) {
|
|
1655
|
+
console.log(` \u26A0 ${warning}`);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
spinner.fail("Validation failed");
|
|
1660
|
+
console.error(chalk15.red(error instanceof Error ? error.message : String(error)));
|
|
1661
|
+
process.exit(1);
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
finetune.command("stats").description("Show training data statistics").action(async () => {
|
|
1665
|
+
const cwd = process.cwd();
|
|
1666
|
+
const collector = createTrainingDataCollector(cwd);
|
|
1667
|
+
const stats = collector.getStats();
|
|
1668
|
+
console.log(chalk15.cyan.bold("\n\u{1F4CA} Training Data Statistics\n"));
|
|
1669
|
+
if (stats.totalExamples === 0) {
|
|
1670
|
+
console.log(chalk15.yellow("No training data collected yet."));
|
|
1671
|
+
console.log(chalk15.gray("\nTraining data is collected when you use ctx goal/build commands.\n"));
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
console.log(`${chalk15.bold("Total Examples:")} ${stats.totalExamples}`);
|
|
1675
|
+
console.log(`${chalk15.bold("Avg Token Count:")} ${stats.avgTokenCount}`);
|
|
1676
|
+
console.log(`${chalk15.bold("Avg Files/Example:")} ${stats.avgFilesPerExample}`);
|
|
1677
|
+
console.log(`
|
|
1678
|
+
${chalk15.bold("Rating Distribution:")}`);
|
|
1679
|
+
const total = stats.totalExamples;
|
|
1680
|
+
const pct = (n) => (n / total * 100).toFixed(1);
|
|
1681
|
+
console.log(` ${chalk15.green("\u25CF")} Good: ${stats.ratingDistribution.good} (${pct(stats.ratingDistribution.good)}%)`);
|
|
1682
|
+
console.log(` ${chalk15.yellow("\u25CF")} Neutral: ${stats.ratingDistribution.neutral} (${pct(stats.ratingDistribution.neutral)}%)`);
|
|
1683
|
+
console.log(` ${chalk15.red("\u25CF")} Bad: ${stats.ratingDistribution.bad} (${pct(stats.ratingDistribution.bad)}%)`);
|
|
1684
|
+
console.log(` ${chalk15.gray("\u25CB")} Unrated: ${stats.ratingDistribution.unrated} (${pct(stats.ratingDistribution.unrated)}%)`);
|
|
1685
|
+
console.log(`
|
|
1686
|
+
${chalk15.bold("Languages:")}`);
|
|
1687
|
+
for (const [lang, count] of Object.entries(stats.languageDistribution)) {
|
|
1688
|
+
console.log(` ${lang}: ${count} (${pct(count)}%)`);
|
|
1689
|
+
}
|
|
1690
|
+
console.log(`
|
|
1691
|
+
${chalk15.bold("Date Range:")}`);
|
|
1692
|
+
console.log(` From: ${stats.dateRange.earliest.toLocaleDateString()}`);
|
|
1693
|
+
console.log(` To: ${stats.dateRange.latest.toLocaleDateString()}`);
|
|
1694
|
+
console.log();
|
|
1695
|
+
});
|
|
1696
|
+
finetune.command("feedback <id> <rating>").description("Add feedback to a training example (good/bad/neutral)").option("-c, --comment <comment>", "Optional comment").action(async (id, rating, options) => {
|
|
1697
|
+
const validRatings = ["good", "bad", "neutral"];
|
|
1698
|
+
if (!validRatings.includes(rating)) {
|
|
1699
|
+
console.error(chalk15.red(`Invalid rating. Use: ${validRatings.join(", ")}`));
|
|
1700
|
+
process.exit(1);
|
|
1701
|
+
}
|
|
1702
|
+
const cwd = process.cwd();
|
|
1703
|
+
const collector = createTrainingDataCollector(cwd);
|
|
1704
|
+
const success = collector.addFeedback(
|
|
1705
|
+
id,
|
|
1706
|
+
rating,
|
|
1707
|
+
options.comment
|
|
1708
|
+
);
|
|
1709
|
+
if (success) {
|
|
1710
|
+
console.log(chalk15.green(`\u2713 Feedback added for ${id}`));
|
|
1711
|
+
} else {
|
|
1712
|
+
console.error(chalk15.red(`Example not found: ${id}`));
|
|
1713
|
+
process.exit(1);
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
finetune.command("recent").description("Show recent training examples").option("-n, --limit <count>", "Number of examples", parseInt, 5).action(async (options) => {
|
|
1717
|
+
const cwd = process.cwd();
|
|
1718
|
+
const collector = createTrainingDataCollector(cwd);
|
|
1719
|
+
const recent = collector.getRecent(options.limit);
|
|
1720
|
+
if (recent.length === 0) {
|
|
1721
|
+
console.log(chalk15.yellow("\nNo training data collected yet.\n"));
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
console.log(chalk15.cyan.bold("\n\u{1F4CB} Recent Training Examples\n"));
|
|
1725
|
+
for (const example of recent) {
|
|
1726
|
+
const rating = example.feedback?.rating;
|
|
1727
|
+
const ratingIcon = rating === "good" ? "\u{1F44D}" : rating === "bad" ? "\u{1F44E}" : "\u2022";
|
|
1728
|
+
const date = new Date(example.meta.timestamp).toLocaleString();
|
|
1729
|
+
console.log(`${chalk15.bold(example.id)} ${ratingIcon}`);
|
|
1730
|
+
console.log(` Goal: ${chalk15.white(example.goal.substring(0, 60))}${example.goal.length > 60 ? "..." : ""}`);
|
|
1731
|
+
console.log(` Files: ${example.selectedFiles.length} | Tokens: ${example.tokenCount}`);
|
|
1732
|
+
console.log(` ${chalk15.gray(date)}
|
|
1733
|
+
`);
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/commands/cloud.ts
|
|
1739
|
+
import chalk16 from "chalk";
|
|
1740
|
+
import ora15 from "ora";
|
|
1741
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
1742
|
+
import { homedir } from "os";
|
|
1743
|
+
import { join as join6 } from "path";
|
|
1744
|
+
var CLOUD_CONFIG_PATH = join6(homedir(), ".contextos", "cloud.json");
|
|
1745
|
+
var DEFAULT_CLOUD_URL = "https://api.contextos.dev";
|
|
1746
|
+
function loadCloudConfig() {
|
|
1747
|
+
if (existsSync3(CLOUD_CONFIG_PATH)) {
|
|
1748
|
+
try {
|
|
1749
|
+
return JSON.parse(readFileSync5(CLOUD_CONFIG_PATH, "utf-8"));
|
|
1750
|
+
} catch {
|
|
1751
|
+
return { cloudUrl: DEFAULT_CLOUD_URL };
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return { cloudUrl: DEFAULT_CLOUD_URL };
|
|
1755
|
+
}
|
|
1756
|
+
function saveCloudConfig(config) {
|
|
1757
|
+
const dir = join6(CLOUD_CONFIG_PATH, "..");
|
|
1758
|
+
if (!existsSync3(dir)) {
|
|
1759
|
+
mkdirSync2(dir, { recursive: true });
|
|
1760
|
+
}
|
|
1761
|
+
writeFileSync4(CLOUD_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
1762
|
+
}
|
|
1763
|
+
function registerCloudCommands(program2) {
|
|
1764
|
+
program2.command("login").description("Login to ContextOS Cloud").option("--api-key <key>", "API key").option("--url <url>", "Cloud server URL", DEFAULT_CLOUD_URL).action(async (options) => {
|
|
1765
|
+
console.log(chalk16.cyan.bold("\n\u{1F510} ContextOS Cloud Login\n"));
|
|
1766
|
+
let apiKey = options.apiKey;
|
|
1767
|
+
if (!apiKey) {
|
|
1768
|
+
const { default: inquirer4 } = await import("inquirer");
|
|
1769
|
+
const answers = await inquirer4.prompt([
|
|
1770
|
+
{
|
|
1771
|
+
type: "input",
|
|
1772
|
+
name: "apiKey",
|
|
1773
|
+
message: "Enter your API key:",
|
|
1774
|
+
validate: (input) => input.startsWith("ctx_") || "API key must start with ctx_"
|
|
1775
|
+
}
|
|
1776
|
+
]);
|
|
1777
|
+
apiKey = answers.apiKey;
|
|
1778
|
+
}
|
|
1779
|
+
const spinner = ora15("Validating API key...").start();
|
|
1780
|
+
try {
|
|
1781
|
+
const response = await fetch(`${options.url}/health`, {
|
|
1782
|
+
headers: { "X-API-Key": apiKey }
|
|
1783
|
+
});
|
|
1784
|
+
if (!response.ok) {
|
|
1785
|
+
spinner.fail("Invalid API key or server error");
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
spinner.succeed("API key validated");
|
|
1789
|
+
const config = {
|
|
1790
|
+
apiKey,
|
|
1791
|
+
cloudUrl: options.url,
|
|
1792
|
+
userId: `user_${apiKey.slice(4, 12)}`,
|
|
1793
|
+
tier: "free"
|
|
1794
|
+
};
|
|
1795
|
+
saveCloudConfig(config);
|
|
1796
|
+
console.log(chalk16.green("\n\u2705 Logged in successfully!"));
|
|
1797
|
+
console.log(chalk16.gray(`Config saved to: ${CLOUD_CONFIG_PATH}
|
|
1798
|
+
`));
|
|
1799
|
+
} catch (error) {
|
|
1800
|
+
spinner.fail(error instanceof Error ? error.message : "Connection failed");
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
program2.command("logout").description("Logout from ContextOS Cloud").action(() => {
|
|
1804
|
+
const config = loadCloudConfig();
|
|
1805
|
+
config.apiKey = void 0;
|
|
1806
|
+
config.userId = void 0;
|
|
1807
|
+
saveCloudConfig(config);
|
|
1808
|
+
console.log(chalk16.green("\n\u2705 Logged out successfully\n"));
|
|
1809
|
+
});
|
|
1810
|
+
program2.command("connect").description("Connect your IDEs to ContextOS Cloud").option("--all", "Configure all detected IDEs").option("--ide <name>", "Configure specific IDE").action(async (options) => {
|
|
1811
|
+
const config = loadCloudConfig();
|
|
1812
|
+
if (!config.apiKey) {
|
|
1813
|
+
console.log(chalk16.red("\n\u274C Not logged in. Run `ctx login` first.\n"));
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
console.log(chalk16.cyan.bold("\n\u{1F50C} Connecting IDEs to ContextOS Cloud\n"));
|
|
1817
|
+
const { detectIDEs } = await import("@contextos/setup").catch(() => ({
|
|
1818
|
+
detectIDEs: async () => []
|
|
1819
|
+
}));
|
|
1820
|
+
const ides = await detectIDEs();
|
|
1821
|
+
if (ides.length === 0) {
|
|
1822
|
+
console.log(chalk16.yellow("No supported IDEs detected.\n"));
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
console.log(chalk16.gray("This will configure your IDEs to use the cloud MCP server."));
|
|
1826
|
+
console.log(chalk16.gray(`Cloud URL: ${config.cloudUrl}
|
|
1827
|
+
`));
|
|
1828
|
+
for (const ide of ides) {
|
|
1829
|
+
if (options.ide && ide.id !== options.ide) continue;
|
|
1830
|
+
console.log(`${chalk16.cyan("\u2192")} ${ide.name}`);
|
|
1831
|
+
}
|
|
1832
|
+
console.log(chalk16.green("\n\u2705 IDEs connected to cloud!\n"));
|
|
1833
|
+
});
|
|
1834
|
+
program2.command("cloud").description("Show ContextOS Cloud status").action(async () => {
|
|
1835
|
+
const config = loadCloudConfig();
|
|
1836
|
+
console.log(chalk16.cyan.bold("\n\u2601\uFE0F ContextOS Cloud Status\n"));
|
|
1837
|
+
if (!config.apiKey) {
|
|
1838
|
+
console.log(chalk16.yellow("Not logged in"));
|
|
1839
|
+
console.log(chalk16.gray("\nRun `ctx login` to connect to ContextOS Cloud.\n"));
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
console.log(`${chalk16.gray("User ID:")} ${config.userId}`);
|
|
1843
|
+
console.log(`${chalk16.gray("Tier:")} ${config.tier}`);
|
|
1844
|
+
console.log(`${chalk16.gray("Cloud URL:")} ${config.cloudUrl}`);
|
|
1845
|
+
const spinner = ora15("Checking connection...").start();
|
|
1846
|
+
try {
|
|
1847
|
+
const response = await fetch(`${config.cloudUrl}/health`, {
|
|
1848
|
+
headers: { "X-API-Key": config.apiKey }
|
|
1849
|
+
});
|
|
1850
|
+
if (response.ok) {
|
|
1851
|
+
const data = await response.json();
|
|
1852
|
+
spinner.succeed(`Connected (v${data.version})`);
|
|
1853
|
+
} else {
|
|
1854
|
+
spinner.warn("Connection issues");
|
|
1855
|
+
}
|
|
1856
|
+
} catch {
|
|
1857
|
+
spinner.fail("Cannot reach cloud server");
|
|
1858
|
+
}
|
|
1859
|
+
console.log();
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1334
1863
|
// src/index.ts
|
|
1335
1864
|
var program = new Command14();
|
|
1336
1865
|
program.name("ctx").description("ContextOS - The Context Server Protocol for AI Coding").version("0.1.0").option("-v, --verbose", "Enable verbose output");
|
|
@@ -1347,4 +1876,7 @@ program.addCommand(analyzeCommand);
|
|
|
1347
1876
|
program.addCommand(refactorCommand);
|
|
1348
1877
|
program.addCommand(explainCommand);
|
|
1349
1878
|
program.addCommand(traceCommand);
|
|
1879
|
+
registerPluginCommand(program);
|
|
1880
|
+
registerFinetuneCommand(program);
|
|
1881
|
+
registerCloudCommands(program);
|
|
1350
1882
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
2
|
+
"name": "@contextos/cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI for ContextOS - The Context Server Protocol for AI Coding",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ctx": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"chalk": "^5.3.0",
|
|
13
|
+
"clipboardy": "^4.0.0",
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"glob": "^10.3.0",
|
|
16
|
+
"inquirer": "^9.2.0",
|
|
17
|
+
"ora": "^8.0.0",
|
|
18
|
+
"yaml": "^2.4.0",
|
|
19
|
+
"@contextos/core": "0.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/inquirer": "^9.0.7",
|
|
23
|
+
"@types/node": "^20.11.0",
|
|
24
|
+
"eslint": "^8.57.0",
|
|
25
|
+
"rimraf": "^5.0.5",
|
|
26
|
+
"tsup": "^8.0.2",
|
|
27
|
+
"typescript": "^5.4.0",
|
|
28
|
+
"vitest": "^1.4.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup src/index.ts --format esm --clean",
|
|
36
|
+
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
37
|
+
"start": "node dist/index.js",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"lint": "eslint src/",
|
|
40
|
+
"clean": "rimraf dist"
|
|
41
|
+
}
|
|
42
42
|
}
|