@traits-dev/cli 0.4.0 → 0.6.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Justin Hambleton
3
+ Copyright (c) 2026 FRNTR, LLC
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @traits-dev/cli
2
2
 
3
- CLI for traits.dev voice profile workflows: init, validate, compile, eval, and import.
3
+ CLI for traits.dev voice profile workflows: init, validate, compile, eval, import, and migrate.
4
4
 
5
5
  ## Install
6
6
 
@@ -13,7 +13,9 @@ pnpm add -D @traits-dev/cli
13
13
  ```bash
14
14
  traits init --template resolve profiles/resolve.yaml
15
15
  traits validate profiles/resolve.yaml
16
+ traits validate profiles/resolve.yaml --format sarif
16
17
  traits compile profiles/resolve.yaml --model gpt-4o
18
+ traits migrate profiles/legacy-v1-5.yaml --to v1.6 --normalize-extends
17
19
  traits eval profiles/resolve.yaml --tier 1
18
20
  ```
19
21
 
package/dist/traits.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/bin/traits.ts
4
- import fs4 from "fs";
5
- import path6 from "path";
4
+ import fs5 from "fs";
5
+ import path7 from "path";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
8
  // src/commands/compile.ts
@@ -267,7 +267,7 @@ function printEvalUsage(out = process.stderr) {
267
267
  "Options:",
268
268
  " --model <model> Model target (required)",
269
269
  " --tier <1|2|3> Highest tier to run (default: highest available)",
270
- " --suite <name> Built-in baseline suite: support|healthcare|developer",
270
+ " --suite <name> Built-in baseline suite: support|healthcare|developer|educator|advisor",
271
271
  " --provider <name> Judge provider for Tier 3: auto|openai|anthropic",
272
272
  " --embedding-model <name> Embedding model for Tier 2 (OpenAI)",
273
273
  " --judge-model <name> Judge model for Tier 3 provider",
@@ -402,10 +402,12 @@ function parseEvalArgs(args) {
402
402
  if (!["auto", "openai", "anthropic"].includes(result.provider)) {
403
403
  return { error: 'Invalid "--provider" value. Expected auto, openai, or anthropic.' };
404
404
  }
405
- if (result.suite != null && !["support", "healthcare", "developer"].includes(
405
+ if (result.suite != null && !["support", "healthcare", "developer", "educator", "advisor"].includes(
406
406
  result.suite
407
407
  )) {
408
- return { error: 'Invalid "--suite" value. Expected support, healthcare, or developer.' };
408
+ return {
409
+ error: 'Invalid "--suite" value. Expected support, healthcare, developer, educator, or advisor.'
410
+ };
409
411
  }
410
412
  if (result.suite != null && result.samplesPath != null) {
411
413
  return { error: 'Use either "--suite" or "--samples/--scenarios", not both.' };
@@ -443,7 +445,7 @@ function loadSamples(options, cwd) {
443
445
  const suite = loadBuiltInEvalSuite(options.suite);
444
446
  if (!suite) {
445
447
  throw new Error(
446
- `Unknown suite "${options.suite}". Expected support, healthcare, or developer.`
448
+ `Unknown suite "${options.suite}". Expected support, healthcare, developer, educator, or advisor.`
447
449
  );
448
450
  }
449
451
  return suite.scenarios.map((scenario) => ({
@@ -1446,21 +1448,314 @@ function importHelp(out = process.stdout) {
1446
1448
  printImportUsage(out);
1447
1449
  }
1448
1450
 
1449
- // src/commands/validate.ts
1451
+ // src/commands/migrate.ts
1452
+ import fs4 from "fs";
1450
1453
  import path5 from "path";
1454
+ import { loadProfileFile, validateResolvedProfile as validateResolvedProfile2 } from "@traits-dev/core";
1455
+ import { renderImportedProfileYAML, toValidationResultObject as toValidationResultObject4 } from "@traits-dev/core/internal";
1456
+ var SCHEMA_ORDER = {
1457
+ "v1.4": 0,
1458
+ "v1.5": 1,
1459
+ "v1.6": 2
1460
+ };
1461
+ function printMigrateUsage(out = process.stderr) {
1462
+ out.write(
1463
+ [
1464
+ "Usage:",
1465
+ " traits migrate <profile-path> [options]",
1466
+ "",
1467
+ "Options:",
1468
+ " --to <version> Target schema version (default: v1.6; supported: v1.5, v1.6)",
1469
+ " --output <path> Output file path (default: <name>.<target>.yaml)",
1470
+ " --in-place Overwrite the source file (requires --force if file exists)",
1471
+ " --force Overwrite existing destination file",
1472
+ " --normalize-extends Convert single-string extends to array form (v1.6 target only)",
1473
+ " --json Output structured JSON summary",
1474
+ " --verbose Include additional command metadata",
1475
+ " --no-color Disable colorized output",
1476
+ ""
1477
+ ].join("\n")
1478
+ );
1479
+ }
1480
+ function parseMigrateArgs(args) {
1481
+ const result = {
1482
+ profilePath: null,
1483
+ to: "v1.6",
1484
+ outputPath: null,
1485
+ inPlace: false,
1486
+ force: false,
1487
+ normalizeExtends: false,
1488
+ json: false,
1489
+ verbose: false,
1490
+ noColor: false
1491
+ };
1492
+ const positionals = [];
1493
+ for (let index = 0; index < args.length; index += 1) {
1494
+ const arg = args[index];
1495
+ if (arg === "--in-place") {
1496
+ result.inPlace = true;
1497
+ continue;
1498
+ }
1499
+ if (arg === "--force") {
1500
+ result.force = true;
1501
+ continue;
1502
+ }
1503
+ if (arg === "--normalize-extends") {
1504
+ result.normalizeExtends = true;
1505
+ continue;
1506
+ }
1507
+ if (arg === "--json") {
1508
+ result.json = true;
1509
+ continue;
1510
+ }
1511
+ if (arg === "--verbose") {
1512
+ result.verbose = true;
1513
+ continue;
1514
+ }
1515
+ if (arg === "--no-color") {
1516
+ result.noColor = true;
1517
+ continue;
1518
+ }
1519
+ if (arg === "--to" || arg === "--output") {
1520
+ const value = args[index + 1];
1521
+ if (!value) return { error: `Missing value for "${arg}"` };
1522
+ if (arg === "--to") {
1523
+ if (value !== "v1.5" && value !== "v1.6") {
1524
+ return {
1525
+ error: `Unsupported "--to" value "${value}". Currently supported: v1.5, v1.6`
1526
+ };
1527
+ }
1528
+ result.to = value;
1529
+ } else {
1530
+ result.outputPath = value;
1531
+ }
1532
+ index += 1;
1533
+ continue;
1534
+ }
1535
+ if (arg.startsWith("--")) {
1536
+ return { error: `Unknown option "${arg}"` };
1537
+ }
1538
+ positionals.push(arg);
1539
+ }
1540
+ if (positionals.length !== 1) {
1541
+ return { error: "Expected exactly one profile path argument" };
1542
+ }
1543
+ result.profilePath = positionals[0];
1544
+ if (result.inPlace && result.outputPath) {
1545
+ return { error: 'Use either "--in-place" or "--output", not both.' };
1546
+ }
1547
+ if (result.normalizeExtends && result.to !== "v1.6") {
1548
+ return { error: '"--normalize-extends" requires "--to v1.6".' };
1549
+ }
1550
+ return { value: result };
1551
+ }
1552
+ function resolveDefaultOutputPath(sourcePath, toVersion) {
1553
+ const ext = path5.extname(sourcePath) || ".yaml";
1554
+ const base = sourcePath.slice(0, sourcePath.length - ext.length);
1555
+ return `${base}.${toVersion}${ext}`;
1556
+ }
1557
+ function writeFileAtomic2(filePath, contents) {
1558
+ fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
1559
+ const tempFile = path5.join(
1560
+ path5.dirname(filePath),
1561
+ `.${path5.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
1562
+ );
1563
+ fs4.writeFileSync(tempFile, contents, "utf8");
1564
+ fs4.renameSync(tempFile, filePath);
1565
+ }
1566
+ function migrateV14ToV15(profile) {
1567
+ const migrated = JSON.parse(JSON.stringify(profile));
1568
+ migrated.schema = "v1.5";
1569
+ if (!migrated.capabilities) {
1570
+ migrated.capabilities = {
1571
+ tools: [],
1572
+ constraints: [],
1573
+ handoff: {
1574
+ trigger: "Request requires unavailable operations.",
1575
+ action: "Offer handoff to a human operator."
1576
+ }
1577
+ };
1578
+ return { migrated, capabilitiesAdded: true };
1579
+ }
1580
+ return { migrated, capabilitiesAdded: false };
1581
+ }
1582
+ function migrateV15ToV16(profile, options) {
1583
+ const migrated = JSON.parse(JSON.stringify(profile));
1584
+ migrated.schema = "v1.6";
1585
+ if (options.normalizeExtends && typeof migrated.extends === "string") {
1586
+ const normalized = migrated.extends.trim();
1587
+ if (normalized.length > 0) {
1588
+ migrated.extends = [normalized];
1589
+ return { migrated, extendsNormalized: true };
1590
+ }
1591
+ }
1592
+ return { migrated, extendsNormalized: false };
1593
+ }
1594
+ function asSupportedSourceSchema(value) {
1595
+ if (value === "v1.4" || value === "v1.5") return value;
1596
+ return null;
1597
+ }
1598
+ function runMigrate(args, io = process) {
1599
+ const parsed = parseMigrateArgs(args);
1600
+ if ("error" in parsed) {
1601
+ io.stderr.write(`Error: ${parsed.error}
1602
+
1603
+ `);
1604
+ printMigrateUsage(io.stderr);
1605
+ return 1;
1606
+ }
1607
+ const options = parsed.value;
1608
+ if (!options.profilePath) {
1609
+ io.stderr.write("Error: Missing profile path\n\n");
1610
+ printMigrateUsage(io.stderr);
1611
+ return 1;
1612
+ }
1613
+ const sourcePath = path5.resolve(io.cwd(), options.profilePath);
1614
+ const destinationPath = options.inPlace ? sourcePath : options.outputPath ? path5.resolve(io.cwd(), options.outputPath) : resolveDefaultOutputPath(sourcePath, options.to);
1615
+ if (!fs4.existsSync(sourcePath)) {
1616
+ io.stderr.write(`Error: Source profile does not exist: ${sourcePath}
1617
+ `);
1618
+ return 1;
1619
+ }
1620
+ if (fs4.existsSync(destinationPath) && !options.force) {
1621
+ io.stderr.write(
1622
+ `Error: Destination file already exists: ${destinationPath}. Use --force to overwrite.
1623
+ `
1624
+ );
1625
+ return 1;
1626
+ }
1627
+ let loaded;
1628
+ try {
1629
+ loaded = loadProfileFile(sourcePath);
1630
+ } catch (error) {
1631
+ io.stderr.write(
1632
+ `Error: Failed to read profile: ${error instanceof Error ? error.message : String(error)}
1633
+ `
1634
+ );
1635
+ return 1;
1636
+ }
1637
+ if (loaded.schema === "v1.6") {
1638
+ io.stderr.write(
1639
+ 'Error: Source profile is already at "v1.6". Nothing to migrate.\n'
1640
+ );
1641
+ return 1;
1642
+ }
1643
+ const sourceSchema = asSupportedSourceSchema(loaded.schema);
1644
+ if (!sourceSchema) {
1645
+ io.stderr.write(
1646
+ `Error: Migration source schema must be "v1.4" or "v1.5". Found "${loaded.schema ?? "unknown"}".
1647
+ `
1648
+ );
1649
+ return 1;
1650
+ }
1651
+ if (SCHEMA_ORDER[options.to] < SCHEMA_ORDER[sourceSchema]) {
1652
+ io.stderr.write(
1653
+ `Error: Downgrade is not supported (source "${sourceSchema}" -> target "${options.to}").
1654
+ `
1655
+ );
1656
+ return 1;
1657
+ }
1658
+ if (SCHEMA_ORDER[options.to] === SCHEMA_ORDER[sourceSchema]) {
1659
+ io.stderr.write(`Error: Source profile already uses target schema "${options.to}".
1660
+ `);
1661
+ return 1;
1662
+ }
1663
+ let migrated = loaded;
1664
+ let capabilitiesAdded = false;
1665
+ let extendsNormalized = false;
1666
+ let currentSchema = sourceSchema;
1667
+ if (currentSchema === "v1.4") {
1668
+ const step = migrateV14ToV15(migrated);
1669
+ migrated = step.migrated;
1670
+ capabilitiesAdded = step.capabilitiesAdded;
1671
+ currentSchema = "v1.5";
1672
+ }
1673
+ if (options.to === "v1.6" && currentSchema === "v1.5") {
1674
+ const step = migrateV15ToV16(migrated, {
1675
+ normalizeExtends: options.normalizeExtends
1676
+ });
1677
+ migrated = step.migrated;
1678
+ extendsNormalized = step.extendsNormalized;
1679
+ currentSchema = "v1.6";
1680
+ }
1681
+ if (currentSchema !== options.to) {
1682
+ io.stderr.write(
1683
+ `Error: Failed to migrate profile to target schema "${options.to}". Final schema: "${currentSchema}".
1684
+ `
1685
+ );
1686
+ return 2;
1687
+ }
1688
+ const validation = validateResolvedProfile2(migrated, {
1689
+ strict: false
1690
+ });
1691
+ if (validation.errors.length > 0) {
1692
+ io.stderr.write(
1693
+ `Error: Migrated profile is invalid and was not written (${validation.errors.length} error(s)).
1694
+ `
1695
+ );
1696
+ return 2;
1697
+ }
1698
+ const yaml = renderImportedProfileYAML(migrated);
1699
+ writeFileAtomic2(destinationPath, yaml);
1700
+ if (options.json) {
1701
+ io.stdout.write(
1702
+ `${JSON.stringify(
1703
+ {
1704
+ migrated: true,
1705
+ sourcePath,
1706
+ outputPath: destinationPath,
1707
+ fromSchema: sourceSchema,
1708
+ toSchema: options.to,
1709
+ capabilitiesAdded,
1710
+ extendsNormalized,
1711
+ validation: toValidationResultObject4(validation)
1712
+ },
1713
+ null,
1714
+ 2
1715
+ )}
1716
+ `
1717
+ );
1718
+ return 0;
1719
+ }
1720
+ if (options.verbose) {
1721
+ io.stdout.write(`Source: ${sourcePath}
1722
+ `);
1723
+ io.stdout.write(`Output: ${destinationPath}
1724
+ `);
1725
+ io.stdout.write(`Capabilities added: ${capabilitiesAdded ? "yes" : "no"}
1726
+ `);
1727
+ io.stdout.write(`Extends normalized: ${extendsNormalized ? "yes" : "no"}
1728
+ `);
1729
+ io.stdout.write(`Validation warnings: ${validation.warnings.length}
1730
+
1731
+ `);
1732
+ }
1733
+ io.stdout.write(`Migrated profile schema ${sourceSchema} -> ${options.to}
1734
+ `);
1735
+ io.stdout.write(`Wrote: ${destinationPath}
1736
+ `);
1737
+ return 0;
1738
+ }
1739
+ function migrateHelp(out = process.stdout) {
1740
+ printMigrateUsage(out);
1741
+ }
1742
+
1743
+ // src/commands/validate.ts
1744
+ import path6 from "path";
1451
1745
  import { validateProfile as validateProfile2 } from "@traits-dev/core";
1452
1746
  import {
1453
1747
  formatValidationResult as formatValidationResult4,
1454
- toValidationResultObject as toValidationResultObject4
1748
+ toValidationResultObject as toValidationResultObject5
1455
1749
  } from "@traits-dev/core/internal";
1456
1750
  function printValidateUsage(out = process.stderr) {
1457
1751
  out.write(
1458
1752
  [
1459
1753
  "Usage:",
1460
- " traits validate <profile-path> [--json] [--strict] [--bundled-profiles-dir <dir>]",
1754
+ " traits validate <profile-path> [options]",
1461
1755
  "",
1462
1756
  "Options:",
1463
1757
  " --json Output structured JSON",
1758
+ " --format <text|json|sarif> Output format (default: text)",
1464
1759
  " --strict Promote warnings to errors",
1465
1760
  " --verbose Include additional command metadata",
1466
1761
  " --no-color Disable colorized output",
@@ -1474,6 +1769,7 @@ function parseValidateArgs(args) {
1474
1769
  profilePath: null,
1475
1770
  strict: false,
1476
1771
  json: false,
1772
+ format: "text",
1477
1773
  verbose: false,
1478
1774
  noColor: false,
1479
1775
  bundledProfilesDir: null
@@ -1487,6 +1783,7 @@ function parseValidateArgs(args) {
1487
1783
  }
1488
1784
  if (arg === "--json") {
1489
1785
  result.json = true;
1786
+ result.format = "json";
1490
1787
  continue;
1491
1788
  }
1492
1789
  if (arg === "--verbose") {
@@ -1506,6 +1803,20 @@ function parseValidateArgs(args) {
1506
1803
  index += 1;
1507
1804
  continue;
1508
1805
  }
1806
+ if (arg === "--format") {
1807
+ const nextValue = args[index + 1];
1808
+ if (!nextValue) {
1809
+ return { error: 'Missing value for "--format"' };
1810
+ }
1811
+ const normalized = String(nextValue).toLowerCase();
1812
+ if (normalized !== "text" && normalized !== "json" && normalized !== "sarif") {
1813
+ return { error: 'Invalid "--format" value. Expected text, json, or sarif.' };
1814
+ }
1815
+ result.format = normalized;
1816
+ result.json = normalized === "json";
1817
+ index += 1;
1818
+ continue;
1819
+ }
1509
1820
  if (arg.startsWith("--")) {
1510
1821
  return { error: `Unknown option "${arg}"` };
1511
1822
  }
@@ -1517,6 +1828,54 @@ function parseValidateArgs(args) {
1517
1828
  result.profilePath = positionals[0];
1518
1829
  return { value: result };
1519
1830
  }
1831
+ function toRelativePath(cwd, filePath) {
1832
+ const relative = path6.relative(cwd, filePath);
1833
+ if (relative && !relative.startsWith("..")) return relative;
1834
+ return filePath;
1835
+ }
1836
+ function buildSarifReport(validation, profilePath, cwd) {
1837
+ const diagnostics = [...validation.errors, ...validation.warnings];
1838
+ const uniqueRuleIds = [...new Set(diagnostics.map((diagnostic) => diagnostic.code))];
1839
+ const artifactUri = toRelativePath(cwd, profilePath);
1840
+ const rules = uniqueRuleIds.map((ruleId) => ({
1841
+ id: ruleId,
1842
+ shortDescription: {
1843
+ text: `traits.dev ${ruleId}`
1844
+ }
1845
+ }));
1846
+ const results = diagnostics.map((diagnostic) => ({
1847
+ ruleId: diagnostic.code,
1848
+ level: diagnostic.severity === "error" ? "error" : "warning",
1849
+ message: {
1850
+ text: diagnostic.message
1851
+ },
1852
+ locations: [
1853
+ {
1854
+ physicalLocation: {
1855
+ artifactLocation: {
1856
+ uri: artifactUri
1857
+ }
1858
+ }
1859
+ }
1860
+ ]
1861
+ }));
1862
+ return {
1863
+ version: "2.1.0",
1864
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
1865
+ runs: [
1866
+ {
1867
+ tool: {
1868
+ driver: {
1869
+ name: "traits.dev",
1870
+ informationUri: "https://github.com/justinhambleton/traits",
1871
+ rules
1872
+ }
1873
+ },
1874
+ results
1875
+ }
1876
+ ]
1877
+ };
1878
+ }
1520
1879
  function runValidate(args, io = process) {
1521
1880
  const parsed = parseValidateArgs(args);
1522
1881
  if ("error" in parsed) {
@@ -1532,14 +1891,18 @@ function runValidate(args, io = process) {
1532
1891
  printValidateUsage(io.stderr);
1533
1892
  return 1;
1534
1893
  }
1535
- const bundledProfilesDir = options.bundledProfilesDir ? path5.resolve(io.cwd(), options.bundledProfilesDir) : path5.resolve(io.cwd(), "profiles");
1536
- const profilePath = path5.resolve(io.cwd(), options.profilePath);
1894
+ const bundledProfilesDir = options.bundledProfilesDir ? path6.resolve(io.cwd(), options.bundledProfilesDir) : path6.resolve(io.cwd(), "profiles");
1895
+ const profilePath = path6.resolve(io.cwd(), options.profilePath);
1537
1896
  const result = validateProfile2(profilePath, {
1538
1897
  strict: options.strict,
1539
1898
  bundledProfilesDir
1540
1899
  });
1541
- if (options.json) {
1542
- io.stdout.write(`${JSON.stringify(toValidationResultObject4(result), null, 2)}
1900
+ if (options.format === "json") {
1901
+ io.stdout.write(`${JSON.stringify(toValidationResultObject5(result), null, 2)}
1902
+ `);
1903
+ } else if (options.format === "sarif") {
1904
+ const sarif = buildSarifReport(result, profilePath, io.cwd());
1905
+ io.stdout.write(`${JSON.stringify(sarif, null, 2)}
1543
1906
  `);
1544
1907
  } else {
1545
1908
  if (options.verbose) {
@@ -1561,14 +1924,14 @@ function validateHelp(out = process.stdout) {
1561
1924
  }
1562
1925
 
1563
1926
  // src/bin/traits.ts
1564
- var CLI_DIR = path6.dirname(fileURLToPath(import.meta.url));
1927
+ var CLI_DIR = path7.dirname(fileURLToPath(import.meta.url));
1565
1928
  var PACKAGE_JSON_CANDIDATES = [
1566
- path6.resolve(CLI_DIR, "../package.json"),
1567
- path6.resolve(CLI_DIR, "../../package.json")
1929
+ path7.resolve(CLI_DIR, "../package.json"),
1930
+ path7.resolve(CLI_DIR, "../../package.json")
1568
1931
  ];
1569
1932
  function resolvePackageJsonPath() {
1570
1933
  for (const candidate of PACKAGE_JSON_CANDIDATES) {
1571
- if (fs4.existsSync(candidate)) return candidate;
1934
+ if (fs5.existsSync(candidate)) return candidate;
1572
1935
  }
1573
1936
  return PACKAGE_JSON_CANDIDATES[0];
1574
1937
  }
@@ -1586,6 +1949,7 @@ function printRootUsage(out = process.stdout) {
1586
1949
  " compile <profile-path> Compile a profile for a target model",
1587
1950
  " eval <profile-path> Evaluate profile responses (Tier 1 scaffold)",
1588
1951
  " import [prompt-path] Import a profile from an existing system prompt",
1952
+ " migrate <profile-path> Migrate profile schema (up to v1.6)",
1589
1953
  " validate <profile-path> Validate a voice profile",
1590
1954
  "",
1591
1955
  "Global flags:",
@@ -1605,7 +1969,7 @@ function printRootUsage(out = process.stdout) {
1605
1969
  }
1606
1970
  function readCliVersion() {
1607
1971
  try {
1608
- const raw = fs4.readFileSync(PACKAGE_JSON_PATH, "utf8");
1972
+ const raw = fs5.readFileSync(PACKAGE_JSON_PATH, "utf8");
1609
1973
  const pkg = JSON.parse(raw);
1610
1974
  return String(pkg.version ?? "0.0.0");
1611
1975
  } catch {
@@ -1651,7 +2015,7 @@ function withGlobalFlags(command, commandArgs, flags) {
1651
2015
  if (flags.noColor && !args.includes("--no-color")) {
1652
2016
  args.push("--no-color");
1653
2017
  }
1654
- if ((command === "validate" || command === "compile" || command === "eval" || command === "import") && flags.json && !args.includes("--json")) {
2018
+ if ((command === "validate" || command === "compile" || command === "eval" || command === "import" || command === "migrate") && flags.json && !args.includes("--json")) {
1655
2019
  args.push("--json");
1656
2020
  }
1657
2021
  return args;
@@ -1717,6 +2081,13 @@ async function run(argv, io = process) {
1717
2081
  }
1718
2082
  return runImport(commandArgs, io);
1719
2083
  }
2084
+ if (command === "migrate") {
2085
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
2086
+ migrateHelp(io.stdout);
2087
+ return 0;
2088
+ }
2089
+ return runMigrate(commandArgs, io);
2090
+ }
1720
2091
  io.stderr.write(`Error: Unknown command "${command}"
1721
2092
 
1722
2093
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@traits-dev/cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "traits.dev command-line interface for voice profile init, validate, compile, eval, and import workflows.",
5
5
  "keywords": [
6
6
  "traits-dev",
@@ -41,7 +41,7 @@
41
41
  "provenance": true
42
42
  },
43
43
  "dependencies": {
44
- "@traits-dev/core": "^0.4.0"
44
+ "@traits-dev/core": "^0.6.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^25.2.3",