@tuned-tensor/cli 0.5.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/README.md CHANGED
@@ -46,9 +46,12 @@ tt local support-agent › run --dry-run
46
46
 
47
47
  Commands are routed to the mode shown in the prompt. Prefix a single command
48
48
  with `cloud` or `local` to override the mode without switching it. Useful
49
- session commands include `/help`, `/status`, `/context`, `/mode`, `/cd`,
50
- `/clear`, and `/exit`. The shell keeps normal terminal scrollback and command
51
- history only for the current process.
49
+ session commands include `/help`, `/status`, `/context`, `/mode`, `/model`,
50
+ `/cd`, `/clear`, and `/exit`. `/model` shows the model in play the active
51
+ local model, or the cloud spec's base model — and `/model <id>` activates a
52
+ verified local model. Mistyped session commands suggest the closest match.
53
+ The shell keeps normal terminal scrollback and command history only for the
54
+ current process.
52
55
 
53
56
  Explicit commands remain non-interactive, including in CI. `tt --help` shows
54
57
  the complete command surface, `tt status` inspects both targets without a
package/dist/index.js CHANGED
@@ -1290,6 +1290,7 @@ var SLASH_COMMANDS = [
1290
1290
  { path: "/context", description: "Show the current project and backend context." },
1291
1291
  { path: "/mode cloud", description: "Use the managed cloud workflow." },
1292
1292
  { path: "/mode local", description: "Use the local GPU workflow." },
1293
+ { path: "/model", description: "Show the active model; /model <id> activates a local model." },
1293
1294
  { path: "/cd", description: "Change the shell's working directory." },
1294
1295
  { path: "/clear", description: "Clear the terminal." },
1295
1296
  { path: "/exit", description: "Exit the TT shell." }
@@ -1711,10 +1712,49 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
1711
1712
  "status",
1712
1713
  "context",
1713
1714
  "mode",
1715
+ "model",
1714
1716
  "clear",
1715
1717
  "cd",
1716
1718
  "exit"
1717
1719
  ]);
1720
+ var WORKFLOW_ROOT_COMMANDS = new Set(
1721
+ COMMAND_CATALOG.map((command) => command.path.split(" ", 1)[0])
1722
+ );
1723
+ function editDistance(left, right) {
1724
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
1725
+ for (let i = 1; i <= left.length; i += 1) {
1726
+ let diagonal = previous[0];
1727
+ previous[0] = i;
1728
+ for (let j = 1; j <= right.length; j += 1) {
1729
+ const above = previous[j];
1730
+ previous[j] = Math.min(
1731
+ previous[j] + 1,
1732
+ previous[j - 1] + 1,
1733
+ diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
1734
+ );
1735
+ diagonal = above;
1736
+ }
1737
+ }
1738
+ return previous[right.length];
1739
+ }
1740
+ function suggestSlashCommands(rawName) {
1741
+ const names = [...SLASH_NAMES].sort();
1742
+ const prefixMatches = names.filter((name) => name.startsWith(rawName));
1743
+ if (prefixMatches.length > 0) return prefixMatches;
1744
+ return names.filter((name) => editDistance(name, rawName) <= 2);
1745
+ }
1746
+ function unknownSlashCommandError(rawName) {
1747
+ if (WORKFLOW_ROOT_COMMANDS.has(rawName)) {
1748
+ return new ShellParseError(
1749
+ `Unknown session command: /${rawName}. Workflow commands need no slash \u2014 try "${rawName} --help".`
1750
+ );
1751
+ }
1752
+ const suggestions = suggestSlashCommands(rawName);
1753
+ const hint = suggestions.length > 0 ? ` Did you mean ${suggestions.map((name) => `/${name}`).join(" or ")}?` : "";
1754
+ return new ShellParseError(
1755
+ `Unknown session command: /${rawName}.${hint} Use /help to see available commands.`
1756
+ );
1757
+ }
1718
1758
  function parseSlashCommand(input) {
1719
1759
  const trimmed = input.trim();
1720
1760
  if (!trimmed) return null;
@@ -1730,7 +1770,7 @@ function parseSlashCommand(input) {
1730
1770
  const tokens = tokenizeShellInput(trimmed);
1731
1771
  const rawName = tokens[0].slice(1).toLowerCase();
1732
1772
  if (!SLASH_NAMES.has(rawName)) {
1733
- throw new ShellParseError(`Unknown session command: /${rawName}. Use /help to see available commands.`);
1773
+ throw unknownSlashCommandError(rawName);
1734
1774
  }
1735
1775
  return {
1736
1776
  name: rawName,
@@ -1740,6 +1780,22 @@ function parseSlashCommand(input) {
1740
1780
  function errorMessage(error) {
1741
1781
  return error instanceof Error ? error.message : String(error);
1742
1782
  }
1783
+ var successMark = () => chalk2.green("\u2713");
1784
+ var errorMark = () => chalk2.red("\u2717");
1785
+ var accent = chalk2.hex("#8B5CF6");
1786
+ var DETAIL_LABEL_WIDTH = 15;
1787
+ function styleDetailLines(lines) {
1788
+ return lines.map((line) => {
1789
+ if (line.length <= DETAIL_LABEL_WIDTH) return line;
1790
+ const label = line.slice(0, DETAIL_LABEL_WIDTH);
1791
+ if (!label.trim()) return line;
1792
+ const color = label.startsWith("Warning") ? chalk2.yellow : accent.bold;
1793
+ return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
1794
+ });
1795
+ }
1796
+ function detailLine(label, value) {
1797
+ return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
1798
+ }
1743
1799
  function assertNoArgs(command, args) {
1744
1800
  if (args.length > 0) {
1745
1801
  throw new ShellParseError(`/${command} does not accept arguments.`);
@@ -1754,47 +1810,67 @@ function expandDirectory(value, cwd, env) {
1754
1810
  function helpText(mode, query, palette = false) {
1755
1811
  const groups = groupedCatalog(mode, query);
1756
1812
  const lines = [];
1757
- lines.push(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`);
1813
+ lines.push(accent.bold(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
1758
1814
  if (groups.size === 0) {
1759
- lines.push(" No matching commands.");
1815
+ lines.push(chalk2.dim(" No matching commands."));
1760
1816
  } else {
1761
1817
  for (const [group, commands] of groups) {
1762
- lines.push(`
1763
- ${group}`);
1818
+ lines.push("");
1819
+ lines.push(chalk2.bold(group));
1764
1820
  for (const command of commands) {
1765
- lines.push(` ${command.path.padEnd(22)} ${command.description}`);
1821
+ lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
1766
1822
  }
1767
1823
  }
1768
1824
  }
1769
1825
  if (!query) {
1770
- lines.push("\nSession");
1826
+ lines.push("");
1827
+ lines.push(chalk2.bold("Session"));
1771
1828
  for (const command of SLASH_COMMANDS) {
1772
- lines.push(` ${command.path.padEnd(22)} ${command.description}`);
1829
+ lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
1773
1830
  }
1774
- lines.push(" ? Alias for /help.");
1775
- lines.push("\nUse Tab to complete commands. Shell operators and shell escapes are disabled.");
1831
+ lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
1832
+ lines.push(chalk2.dim(
1833
+ "\nTab completes commands. Shell operators and shell escapes are disabled."
1834
+ ));
1776
1835
  }
1777
1836
  return `${lines.join("\n")}
1778
1837
  `;
1779
1838
  }
1839
+ function activeModelLabel(snapshot) {
1840
+ return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
1841
+ }
1842
+ function logoRows() {
1843
+ const muted = chalk2.dim("\u2588\u2588");
1844
+ return [
1845
+ `${chalk2.hex("#A78BFA")("\u2588\u2588")} ${muted} ${muted}`,
1846
+ `${muted} ${accent("\u2588\u2588")} ${muted}`,
1847
+ `${muted} ${muted} ${chalk2.hex("#7C3AED")("\u2588\u2588")}`
1848
+ ];
1849
+ }
1780
1850
  function renderShellBanner(snapshot) {
1781
1851
  const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
1782
- return [
1783
- chalk2.bold.cyan("Tuned Tensor"),
1784
- ` ${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec}`,
1785
- chalk2.dim(" /help for commands \xB7 /mode cloud|local to switch workflows"),
1786
- ""
1787
- ].join("\n");
1852
+ const heading = snapshot.version ? `${accent.bold("Tuned Tensor")} ${chalk2.dim(`v${snapshot.version}`)}` : accent.bold("Tuned Tensor");
1853
+ const textRows = [
1854
+ heading,
1855
+ `${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`,
1856
+ chalk2.dim("/help for commands \xB7 Tab completes \xB7 /mode cloud|local switches")
1857
+ ];
1858
+ const logo = logoRows();
1859
+ const lines = textRows.map((row, index) => `${logo[index]} ${row}`);
1860
+ return `${lines.join("\n")}
1861
+
1862
+ `;
1788
1863
  }
1789
1864
  function renderShellPrompt(snapshot) {
1790
- return `${chalk2.cyan("tt")} ${chalk2.bold(snapshot.mode)} ${chalk2.dim(snapshot.context.projectName)} \u203A `;
1865
+ return `${accent("tt")} ${chalk2.bold(snapshot.mode)} ${chalk2.dim(snapshot.context.projectName)} \u203A `;
1791
1866
  }
1792
1867
  var TunedTensorShellSession = class _TunedTensorShellSession {
1793
- constructor(runner, io, env, contextProvider, initialContext) {
1868
+ constructor(runner, io, env, contextProvider, version, initialContext) {
1794
1869
  this.runner = runner;
1795
1870
  this.io = io;
1796
1871
  this.env = env;
1797
1872
  this.contextProvider = contextProvider;
1873
+ this.version = version;
1798
1874
  this.mode = initialContext.inferredTarget;
1799
1875
  this.modeSource = initialContext.targetSource;
1800
1876
  this.cwd = initialContext.cwd;
@@ -1814,6 +1890,7 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1814
1890
  options.io,
1815
1891
  env,
1816
1892
  contextProvider,
1893
+ options.version,
1817
1894
  initialContext
1818
1895
  );
1819
1896
  }
@@ -1822,7 +1899,8 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1822
1899
  mode: this.mode,
1823
1900
  modeSource: this.modeSource,
1824
1901
  cwd: this.cwd,
1825
- context: this.context
1902
+ context: this.context,
1903
+ version: this.version
1826
1904
  };
1827
1905
  }
1828
1906
  prompt() {
@@ -1843,6 +1921,18 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1843
1921
  this.io.write(`${lines.join("\n")}
1844
1922
  `);
1845
1923
  }
1924
+ modelLines() {
1925
+ if (this.mode === "local") {
1926
+ return styleDetailLines([
1927
+ detailLine("Active model", this.context.local.activeModelId ?? "base"),
1928
+ detailLine("Change", "/model <id> to activate a verified local model")
1929
+ ]);
1930
+ }
1931
+ return styleDetailLines([
1932
+ detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
1933
+ detailLine("Change", "edit base_model in tunedtensor.json, then run push")
1934
+ ]);
1935
+ }
1846
1936
  async handleSlash(command) {
1847
1937
  switch (command.name) {
1848
1938
  case "palette":
@@ -1854,12 +1944,14 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1854
1944
  case "status":
1855
1945
  assertNoArgs("status", command.args);
1856
1946
  await this.refreshContext();
1857
- this.writeLines(formatShellStatus(this.context, this.mode));
1947
+ this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
1858
1948
  return "continue";
1859
1949
  case "context":
1860
1950
  assertNoArgs("context", command.args);
1861
1951
  await this.refreshContext();
1862
- this.writeLines(formatShellContext(this.context, this.mode, this.modeSource));
1952
+ this.writeLines(styleDetailLines(
1953
+ formatShellContext(this.context, this.mode, this.modeSource)
1954
+ ));
1863
1955
  return "continue";
1864
1956
  case "mode": {
1865
1957
  if (command.args.length === 0) {
@@ -1872,10 +1964,32 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1872
1964
  }
1873
1965
  this.mode = command.args[0];
1874
1966
  this.modeSource = "session";
1875
- this.io.write(`Workflow switched to ${this.mode}.
1967
+ this.io.write(`${successMark()} Workflow switched to ${this.mode}.
1876
1968
  `);
1877
1969
  return "continue";
1878
1970
  }
1971
+ case "model": {
1972
+ if (command.args.length === 0) {
1973
+ await this.refreshContext();
1974
+ this.writeLines(this.modelLines());
1975
+ return "continue";
1976
+ }
1977
+ if (command.args.length !== 1) {
1978
+ throw new ShellParseError("Usage: /model [model-id]");
1979
+ }
1980
+ if (this.mode !== "local") {
1981
+ throw new ShellParseError(
1982
+ "Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
1983
+ );
1984
+ }
1985
+ await this.runner({
1986
+ target: "local",
1987
+ args: ["models", "activate", command.args[0]],
1988
+ cwd: this.cwd
1989
+ });
1990
+ await this.refreshContext();
1991
+ return "continue";
1992
+ }
1879
1993
  case "clear":
1880
1994
  assertNoArgs("clear", command.args);
1881
1995
  this.io.clear();
@@ -1896,7 +2010,7 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1896
2010
  }
1897
2011
  this.cwd = nextDirectory;
1898
2012
  await this.refreshContext();
1899
- this.io.write(`Directory: ${this.cwd}
2013
+ this.io.write(`${successMark()} Directory: ${this.cwd}
1900
2014
  `);
1901
2015
  return "continue";
1902
2016
  }
@@ -1907,6 +2021,7 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1907
2021
  }
1908
2022
  async handleLine(input) {
1909
2023
  try {
2024
+ if (/^(exit|quit)$/i.test(input.trim())) return "exit";
1910
2025
  const slash = parseSlashCommand(input);
1911
2026
  if (slash) return await this.handleSlash(slash);
1912
2027
  const routed = routeShellCommand(input, this.mode);
@@ -1919,7 +2034,7 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
1919
2034
  await this.refreshContext();
1920
2035
  return "continue";
1921
2036
  } catch (error) {
1922
- this.io.writeError(`${chalk2.red("Error:")} ${errorMessage(error)}
2037
+ this.io.writeError(`${errorMark()} ${errorMessage(error)}
1923
2038
  `);
1924
2039
  return "continue";
1925
2040
  }
@@ -1981,7 +2096,8 @@ async function startInteractiveShell(options) {
1981
2096
  runner: foregroundRunner,
1982
2097
  io: streamIO(output, error),
1983
2098
  cwd: options.cwd,
1984
- env: options.env
2099
+ env: options.env,
2100
+ version: options.version
1985
2101
  });
1986
2102
  const completer = createCommandCompleter(() => session.snapshot().mode);
1987
2103
  readline = createInterface({
@@ -6042,7 +6158,8 @@ function createProgram(version, runtime = {}) {
6042
6158
  output: runtime.stdout ?? process.stdout,
6043
6159
  error: runtime.stderr ?? process.stderr,
6044
6160
  cwd,
6045
- env: shellEnvironment
6161
+ env: shellEnvironment,
6162
+ version
6046
6163
  });
6047
6164
  };
6048
6165
  program.name("tt").description("Tuned Tensor \u2014 one terminal for cloud and local fine-tuning").version(version).option("-k, --api-key <key>", "API key (overrides stored key)").option(
@@ -6182,7 +6299,8 @@ async function runCli(version, runtime = {}) {
6182
6299
  output: runtime.stdout ?? process.stdout,
6183
6300
  error: runtime.stderr ?? process.stderr,
6184
6301
  cwd: runtime.cwd ?? process.cwd(),
6185
- env
6302
+ env,
6303
+ version
6186
6304
  });
6187
6305
  return;
6188
6306
  }
@@ -6191,7 +6309,7 @@ async function runCli(version, runtime = {}) {
6191
6309
  }
6192
6310
 
6193
6311
  // src/index.ts
6194
- runCli("0.5.0").catch((err) => {
6312
+ runCli("0.6.0").catch((err) => {
6195
6313
  reportError(err);
6196
6314
  process.exit(1);
6197
6315
  });