@locusai/cli 0.17.10 → 0.17.11

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 (2) hide show
  1. package/bin/locus.js +790 -161
  2. package/package.json +1 -1
package/bin/locus.js CHANGED
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, {
6
10
  get: all[name],
7
11
  enumerable: true,
8
12
  configurable: true,
9
- set: (newValue) => all[name] = () => newValue
13
+ set: __exportSetter.bind(all, name)
10
14
  });
11
15
  };
12
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -1321,7 +1325,7 @@ function reopenMilestone(owner, repo, milestoneNumber, options = {}) {
1321
1325
  gh(`api repos/${owner}/${repo}/milestones/${milestoneNumber} -X PATCH -f state=open`, options);
1322
1326
  }
1323
1327
  function createPR(title, body, head, base, options = {}) {
1324
- const result = gh(`pr create --title ${JSON.stringify(title)} --body ${JSON.stringify(body)} --head ${JSON.stringify(head)} --base ${JSON.stringify(base)}`, options);
1328
+ const result = ghExec(["pr", "create", "--title", title, "--body", body, "--head", head, "--base", base], options);
1325
1329
  const match = result.match(/\/pull\/(\d+)/);
1326
1330
  if (!match) {
1327
1331
  throw new Error(`Could not extract PR number from: ${result}`);
@@ -1683,6 +1687,496 @@ var init_init = __esm(() => {
1683
1687
  ];
1684
1688
  });
1685
1689
 
1690
+ // src/packages/registry.ts
1691
+ import {
1692
+ existsSync as existsSync6,
1693
+ mkdirSync as mkdirSync6,
1694
+ readFileSync as readFileSync5,
1695
+ renameSync,
1696
+ writeFileSync as writeFileSync5
1697
+ } from "node:fs";
1698
+ import { homedir as homedir2 } from "node:os";
1699
+ import { join as join6 } from "node:path";
1700
+ function getPackagesDir() {
1701
+ const dir = join6(homedir2(), ".locus", "packages");
1702
+ if (!existsSync6(dir)) {
1703
+ mkdirSync6(dir, { recursive: true });
1704
+ }
1705
+ const pkgJson = join6(dir, "package.json");
1706
+ if (!existsSync6(pkgJson)) {
1707
+ writeFileSync5(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
1708
+ `, "utf-8");
1709
+ }
1710
+ return dir;
1711
+ }
1712
+ function getRegistryPath() {
1713
+ return join6(getPackagesDir(), "registry.json");
1714
+ }
1715
+ function loadRegistry() {
1716
+ const registryPath = getRegistryPath();
1717
+ if (!existsSync6(registryPath)) {
1718
+ return { packages: {} };
1719
+ }
1720
+ try {
1721
+ const raw = readFileSync5(registryPath, "utf-8");
1722
+ const parsed = JSON.parse(raw);
1723
+ if (typeof parsed === "object" && parsed !== null && "packages" in parsed && typeof parsed.packages === "object") {
1724
+ return parsed;
1725
+ }
1726
+ return { packages: {} };
1727
+ } catch {
1728
+ return { packages: {} };
1729
+ }
1730
+ }
1731
+ function saveRegistry(registry) {
1732
+ const registryPath = getRegistryPath();
1733
+ const tmp = `${registryPath}.tmp`;
1734
+ writeFileSync5(tmp, `${JSON.stringify(registry, null, 2)}
1735
+ `, "utf-8");
1736
+ renameSync(tmp, registryPath);
1737
+ }
1738
+ function resolvePackageBinary(packageName) {
1739
+ const fullName = normalizePackageName(packageName);
1740
+ const binPath = join6(getPackagesDir(), "node_modules", ".bin", fullName);
1741
+ return existsSync6(binPath) ? binPath : null;
1742
+ }
1743
+ function normalizePackageName(input) {
1744
+ if (input.startsWith("@")) {
1745
+ return input;
1746
+ }
1747
+ if (input.startsWith("locus-")) {
1748
+ return input;
1749
+ }
1750
+ return `locus-${input}`;
1751
+ }
1752
+ var init_registry = () => {};
1753
+
1754
+ // src/commands/pkg.ts
1755
+ var exports_pkg = {};
1756
+ __export(exports_pkg, {
1757
+ pkgCommand: () => pkgCommand,
1758
+ listInstalledPackages: () => listInstalledPackages
1759
+ });
1760
+ import { spawn } from "node:child_process";
1761
+ import { existsSync as existsSync7 } from "node:fs";
1762
+ function listInstalledPackages() {
1763
+ const registry = loadRegistry();
1764
+ const entries = Object.values(registry.packages);
1765
+ if (entries.length === 0) {
1766
+ process.stderr.write(`${yellow("⚠")} No packages installed.
1767
+ `);
1768
+ process.stderr.write(` Install one with: ${bold("locus install <package>")}
1769
+ `);
1770
+ return;
1771
+ }
1772
+ process.stderr.write(`
1773
+ ${bold("Installed packages:")}
1774
+
1775
+ `);
1776
+ for (const entry of entries) {
1777
+ const { name, version, manifest } = entry;
1778
+ const displayName = manifest.displayName || name;
1779
+ process.stderr.write(` ${bold(cyan(displayName))} ${dim(`v${version}`)}
1780
+ `);
1781
+ if (manifest.description) {
1782
+ process.stderr.write(` ${dim(manifest.description)}
1783
+ `);
1784
+ }
1785
+ if (manifest.commands.length > 0) {
1786
+ process.stderr.write(` Commands: ${manifest.commands.map((c) => green(c)).join(", ")}
1787
+ `);
1788
+ }
1789
+ process.stderr.write(` Run: ${bold(`locus pkg ${name.replace(/^locus-/, "")} --help`)}
1790
+ `);
1791
+ process.stderr.write(`
1792
+ `);
1793
+ }
1794
+ }
1795
+ function spawnPackageBinary(binaryPath, args) {
1796
+ return new Promise((resolve) => {
1797
+ const child = spawn(binaryPath, args, {
1798
+ stdio: "inherit",
1799
+ env: { ...process.env, LOCUS_PKG: "1" }
1800
+ });
1801
+ const onSigint = () => {
1802
+ child.kill("SIGINT");
1803
+ };
1804
+ process.on("SIGINT", onSigint);
1805
+ child.on("error", (err) => {
1806
+ process.removeListener("SIGINT", onSigint);
1807
+ process.stderr.write(`${red("✗")} Failed to spawn binary: ${err.message}
1808
+ `);
1809
+ resolve(1);
1810
+ });
1811
+ child.on("close", (code, signal) => {
1812
+ process.removeListener("SIGINT", onSigint);
1813
+ if (signal) {
1814
+ resolve(signal === "SIGINT" ? 130 : 1);
1815
+ } else {
1816
+ resolve(code ?? 0);
1817
+ }
1818
+ });
1819
+ });
1820
+ }
1821
+ async function pkgCommand(args, _flags) {
1822
+ const packageInput = args[0];
1823
+ if (!packageInput) {
1824
+ listInstalledPackages();
1825
+ return;
1826
+ }
1827
+ const packageName = normalizePackageName(packageInput);
1828
+ const registry = loadRegistry();
1829
+ const entry = registry.packages[packageName];
1830
+ if (!entry) {
1831
+ process.stderr.write(`${red("✗")} Package ${bold(`'${packageInput}'`)} is not installed.
1832
+ `);
1833
+ process.stderr.write(` Run: ${bold(`locus install ${packageInput}`)}
1834
+ `);
1835
+ process.exit(1);
1836
+ return;
1837
+ }
1838
+ const binaryPath = entry.binaryPath;
1839
+ if (!binaryPath || !existsSync7(binaryPath)) {
1840
+ process.stderr.write(`${red("✗")} Binary for ${bold(packageName)} not found on disk.
1841
+ `);
1842
+ if (binaryPath) {
1843
+ process.stderr.write(` Expected: ${dim(binaryPath)}
1844
+ `);
1845
+ }
1846
+ process.stderr.write(` Try reinstalling: ${bold(`locus install ${packageInput} --upgrade`)}
1847
+ `);
1848
+ process.exit(1);
1849
+ return;
1850
+ }
1851
+ const remainingArgs = args.slice(1);
1852
+ const exitCode = await spawnPackageBinary(binaryPath, remainingArgs);
1853
+ process.exit(exitCode);
1854
+ }
1855
+ var init_pkg = __esm(() => {
1856
+ init_terminal();
1857
+ init_registry();
1858
+ });
1859
+
1860
+ // src/commands/packages.ts
1861
+ var exports_packages = {};
1862
+ __export(exports_packages, {
1863
+ packagesCommand: () => packagesCommand
1864
+ });
1865
+ import { spawnSync } from "node:child_process";
1866
+ function checkOutdated() {
1867
+ const registry = loadRegistry();
1868
+ const entries = Object.values(registry.packages);
1869
+ if (entries.length === 0) {
1870
+ process.stderr.write(`${yellow("⚠")} No packages installed.
1871
+ `);
1872
+ process.stderr.write(` Install one with: ${bold("locus install <package>")}
1873
+ `);
1874
+ return;
1875
+ }
1876
+ process.stderr.write(`
1877
+ Checking for updates...
1878
+
1879
+ `);
1880
+ const outdated = [];
1881
+ const upToDate = [];
1882
+ for (const entry of entries) {
1883
+ const result = spawnSync("npm", ["view", entry.name, "version"], {
1884
+ encoding: "utf-8",
1885
+ shell: false
1886
+ });
1887
+ if (result.error || result.status !== 0) {
1888
+ process.stderr.write(`${yellow("⚠")} Could not check ${bold(entry.name)}: ${result.error?.message ?? "npm view failed"}
1889
+ `);
1890
+ continue;
1891
+ }
1892
+ const latest = (result.stdout ?? "").trim();
1893
+ if (!latest) {
1894
+ process.stderr.write(`${yellow("⚠")} Could not determine latest version for ${bold(entry.name)}
1895
+ `);
1896
+ continue;
1897
+ }
1898
+ if (latest !== entry.version) {
1899
+ outdated.push({ name: entry.name, installed: entry.version, latest });
1900
+ } else {
1901
+ upToDate.push(entry.name);
1902
+ }
1903
+ }
1904
+ if (outdated.length === 0) {
1905
+ process.stderr.write(`${green("✓")} All packages are up to date.
1906
+
1907
+ `);
1908
+ return;
1909
+ }
1910
+ process.stderr.write(`${bold("Outdated packages:")}
1911
+
1912
+ `);
1913
+ const nameWidth = Math.max("Package".length, ...outdated.map((e) => e.name.length));
1914
+ const installedWidth = Math.max("Installed".length, ...outdated.map((e) => e.installed.length));
1915
+ const latestWidth = Math.max("Latest".length, ...outdated.map((e) => e.latest.length));
1916
+ const pad = (s, w) => s.padEnd(w);
1917
+ process.stderr.write(` ${bold(pad("Package", nameWidth))} ${dim(pad("Installed", installedWidth))} ${green(pad("Latest", latestWidth))}
1918
+ `);
1919
+ process.stderr.write(` ${"-".repeat(nameWidth)} ${"-".repeat(installedWidth)} ${"-".repeat(latestWidth)}
1920
+ `);
1921
+ for (const entry of outdated) {
1922
+ process.stderr.write(` ${cyan(pad(entry.name, nameWidth))} ${dim(pad(entry.installed, installedWidth))} ${green(pad(entry.latest, latestWidth))}
1923
+ `);
1924
+ }
1925
+ process.stderr.write(`
1926
+ `);
1927
+ process.stderr.write(` Run ${bold("locus install <package> --upgrade")} to upgrade a package.
1928
+
1929
+ `);
1930
+ if (upToDate.length > 0) {
1931
+ process.stderr.write(` ${dim(`${upToDate.length} package(s) already up to date.`)}
1932
+
1933
+ `);
1934
+ }
1935
+ }
1936
+ async function packagesCommand(args, _flags) {
1937
+ const subcommand = args[0] ?? "list";
1938
+ switch (subcommand) {
1939
+ case "list":
1940
+ listInstalledPackages();
1941
+ break;
1942
+ case "outdated":
1943
+ checkOutdated();
1944
+ break;
1945
+ default:
1946
+ process.stderr.write(`${red("✗")} Unknown subcommand: ${bold(subcommand)}
1947
+ `);
1948
+ process.stderr.write(` Available: ${bold("list")}, ${bold("outdated")}
1949
+ `);
1950
+ process.exit(1);
1951
+ }
1952
+ }
1953
+ var init_packages = __esm(() => {
1954
+ init_terminal();
1955
+ init_registry();
1956
+ init_pkg();
1957
+ });
1958
+
1959
+ // src/commands/install.ts
1960
+ var exports_install = {};
1961
+ __export(exports_install, {
1962
+ installCommand: () => installCommand
1963
+ });
1964
+ import { spawnSync as spawnSync2 } from "node:child_process";
1965
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
1966
+ import { join as join7 } from "node:path";
1967
+ function parsePackageArg(raw) {
1968
+ if (raw.startsWith("@")) {
1969
+ const slashIdx = raw.indexOf("/");
1970
+ if (slashIdx !== -1) {
1971
+ const afterSlash = raw.slice(slashIdx + 1);
1972
+ const atIdx2 = afterSlash.indexOf("@");
1973
+ if (atIdx2 !== -1) {
1974
+ return {
1975
+ name: raw.slice(0, slashIdx + 1 + atIdx2),
1976
+ version: afterSlash.slice(atIdx2 + 1) || undefined
1977
+ };
1978
+ }
1979
+ }
1980
+ return { name: raw, version: undefined };
1981
+ }
1982
+ const atIdx = raw.indexOf("@");
1983
+ if (atIdx !== -1) {
1984
+ return {
1985
+ name: raw.slice(0, atIdx),
1986
+ version: raw.slice(atIdx + 1) || undefined
1987
+ };
1988
+ }
1989
+ return { name: raw, version: undefined };
1990
+ }
1991
+ async function installCommand(args, flags) {
1992
+ const rawArg = args[0];
1993
+ if (!rawArg) {
1994
+ process.stderr.write(`${red("✗")} Usage: locus install <package> [--version <ver>] [--upgrade]
1995
+ `);
1996
+ process.exit(1);
1997
+ return;
1998
+ }
1999
+ const { name: packageInput, version: inlineVersion } = parsePackageArg(rawArg);
2000
+ const pinnedVersion = flags.version ?? inlineVersion;
2001
+ const isUpgrade = flags.upgrade === "true";
2002
+ const packageName = normalizePackageName(packageInput);
2003
+ const versionSuffix = pinnedVersion ? `@${pinnedVersion}` : "@latest";
2004
+ const packageSpec = `${packageName}${versionSuffix}`;
2005
+ const registry = loadRegistry();
2006
+ const existing = registry.packages[packageName];
2007
+ if (existing && !isUpgrade) {
2008
+ process.stderr.write(`${green("✓")} ${bold(packageName)} is already installed ${dim(`(v${existing.version})`)}
2009
+ `);
2010
+ process.stderr.write(` To upgrade, run: ${bold(`locus install ${packageInput} --upgrade`)}
2011
+ `);
2012
+ return;
2013
+ }
2014
+ const packagesDir = getPackagesDir();
2015
+ const action = existing ? "Upgrading" : "Installing";
2016
+ process.stderr.write(`
2017
+ ${action} ${bold(packageSpec)}...
2018
+
2019
+ `);
2020
+ const result = spawnSync2("npm", ["install", packageSpec], {
2021
+ cwd: packagesDir,
2022
+ stdio: "inherit",
2023
+ shell: false
2024
+ });
2025
+ if (result.error) {
2026
+ process.stderr.write(`
2027
+ ${red("✗")} Could not run npm: ${result.error.message}
2028
+ `);
2029
+ process.stderr.write(` Make sure npm is installed and available in your PATH.
2030
+ `);
2031
+ process.exit(1);
2032
+ return;
2033
+ }
2034
+ if (result.status !== 0) {
2035
+ process.stderr.write(`
2036
+ ${red("✗")} Failed to install ${bold(packageSpec)}.
2037
+ `);
2038
+ process.stderr.write(` Make sure the package exists on npm and you have network access.
2039
+ `);
2040
+ process.exit(1);
2041
+ return;
2042
+ }
2043
+ const installedPkgJsonPath = join7(packagesDir, "node_modules", packageName, "package.json");
2044
+ if (!existsSync8(installedPkgJsonPath)) {
2045
+ process.stderr.write(`
2046
+ ${red("✗")} Package installed but package.json not found at:
2047
+ `);
2048
+ process.stderr.write(` ${dim(installedPkgJsonPath)}
2049
+ `);
2050
+ process.exit(1);
2051
+ return;
2052
+ }
2053
+ let installedPkgJson;
2054
+ try {
2055
+ installedPkgJson = JSON.parse(readFileSync6(installedPkgJsonPath, "utf-8"));
2056
+ } catch {
2057
+ process.stderr.write(`
2058
+ ${red("✗")} Could not parse installed package.json.
2059
+ `);
2060
+ process.exit(1);
2061
+ return;
2062
+ }
2063
+ const installedVersion = installedPkgJson.version ?? "unknown";
2064
+ const locusManifest = installedPkgJson.locus;
2065
+ if (!locusManifest) {
2066
+ process.stderr.write(`
2067
+ ${yellow("⚠")} ${bold(packageName)} does not have a "locus" field in package.json.
2068
+ `);
2069
+ process.stderr.write(` It may not integrate fully with the Locus CLI.
2070
+ `);
2071
+ }
2072
+ const binaryPath = resolvePackageBinary(packageName) ?? "";
2073
+ if (!binaryPath) {
2074
+ process.stderr.write(`
2075
+ ${yellow("⚠")} No binary found for ${bold(packageName)} in node_modules/.bin/.
2076
+ `);
2077
+ }
2078
+ const manifest = locusManifest ?? {
2079
+ displayName: packageName,
2080
+ description: "",
2081
+ commands: [],
2082
+ version: installedVersion
2083
+ };
2084
+ const entry = {
2085
+ name: packageName,
2086
+ version: installedVersion,
2087
+ installedAt: new Date().toISOString(),
2088
+ binaryPath,
2089
+ manifest
2090
+ };
2091
+ registry.packages[packageName] = entry;
2092
+ saveRegistry(registry);
2093
+ const verb = existing ? "upgraded" : "installed";
2094
+ process.stderr.write(`
2095
+ ${green("✓")} Package ${verb} successfully!
2096
+
2097
+ `);
2098
+ process.stderr.write(` Package: ${bold(cyan(packageName))}
2099
+ `);
2100
+ process.stderr.write(` Version: ${bold(installedVersion)}
2101
+ `);
2102
+ if (manifest.commands.length > 0) {
2103
+ process.stderr.write(` Commands: ${manifest.commands.map((c) => bold(c)).join(", ")}
2104
+ `);
2105
+ }
2106
+ if (binaryPath) {
2107
+ process.stderr.write(` Binary: ${dim(binaryPath)}
2108
+ `);
2109
+ }
2110
+ process.stderr.write(`
2111
+ `);
2112
+ }
2113
+ var init_install = __esm(() => {
2114
+ init_terminal();
2115
+ init_registry();
2116
+ });
2117
+
2118
+ // src/commands/uninstall.ts
2119
+ var exports_uninstall = {};
2120
+ __export(exports_uninstall, {
2121
+ uninstallCommand: () => uninstallCommand
2122
+ });
2123
+ import { spawnSync as spawnSync3 } from "node:child_process";
2124
+ async function uninstallCommand(args, _flags) {
2125
+ const rawArg = args[0];
2126
+ if (!rawArg) {
2127
+ process.stderr.write(`${red("✗")} Usage: locus uninstall <package>
2128
+ `);
2129
+ process.exit(1);
2130
+ return;
2131
+ }
2132
+ const packageName = normalizePackageName(rawArg);
2133
+ const registry = loadRegistry();
2134
+ const existing = registry.packages[packageName];
2135
+ if (!existing) {
2136
+ process.stderr.write(`${red("✗")} Package ${bold(`'${rawArg}'`)} is not installed.
2137
+ `);
2138
+ process.stderr.write(` Run ${bold("locus packages list")} to see installed packages.
2139
+ `);
2140
+ process.exit(1);
2141
+ return;
2142
+ }
2143
+ const { version } = existing;
2144
+ const packagesDir = getPackagesDir();
2145
+ process.stderr.write(`
2146
+ Uninstalling ${bold(cyan(packageName))} ${dim(`v${version}`)}...
2147
+
2148
+ `);
2149
+ const result = spawnSync3("npm", ["uninstall", packageName], {
2150
+ cwd: packagesDir,
2151
+ stdio: "inherit",
2152
+ shell: false
2153
+ });
2154
+ if (result.error) {
2155
+ process.stderr.write(`
2156
+ ${red("✗")} Could not run npm: ${result.error.message}
2157
+ `);
2158
+ process.stderr.write(` Make sure npm is installed and available in your PATH.
2159
+ `);
2160
+ process.exit(1);
2161
+ return;
2162
+ }
2163
+ if (result.status !== 0) {
2164
+ process.stderr.write(`
2165
+ ${red("✗")} npm uninstall failed for ${bold(packageName)}.
2166
+ `);
2167
+ process.exit(1);
2168
+ return;
2169
+ }
2170
+ delete registry.packages[packageName];
2171
+ saveRegistry(registry);
2172
+ process.stderr.write(`${green("✓")} Uninstalled ${bold(cyan(packageName))} ${dim(`v${version}`)}
2173
+ `);
2174
+ }
2175
+ var init_uninstall = __esm(() => {
2176
+ init_terminal();
2177
+ init_registry();
2178
+ });
2179
+
1686
2180
  // src/commands/config.ts
1687
2181
  var exports_config = {};
1688
2182
  __export(exports_config, {
@@ -1822,16 +2316,16 @@ __export(exports_logs, {
1822
2316
  logsCommand: () => logsCommand
1823
2317
  });
1824
2318
  import {
1825
- existsSync as existsSync6,
2319
+ existsSync as existsSync9,
1826
2320
  readdirSync as readdirSync2,
1827
- readFileSync as readFileSync5,
2321
+ readFileSync as readFileSync7,
1828
2322
  statSync as statSync2,
1829
2323
  unlinkSync as unlinkSync2
1830
2324
  } from "node:fs";
1831
- import { join as join6 } from "node:path";
2325
+ import { join as join8 } from "node:path";
1832
2326
  async function logsCommand(cwd, options) {
1833
- const logsDir = join6(cwd, ".locus", "logs");
1834
- if (!existsSync6(logsDir)) {
2327
+ const logsDir = join8(cwd, ".locus", "logs");
2328
+ if (!existsSync9(logsDir)) {
1835
2329
  process.stderr.write(`${dim("No logs found.")}
1836
2330
  `);
1837
2331
  return;
@@ -1851,7 +2345,7 @@ async function logsCommand(cwd, options) {
1851
2345
  return viewLog(logFiles[0], options.level, options.lines ?? 50);
1852
2346
  }
1853
2347
  function viewLog(logFile, levelFilter, maxLines) {
1854
- const content = readFileSync5(logFile, "utf-8");
2348
+ const content = readFileSync7(logFile, "utf-8");
1855
2349
  const lines = content.trim().split(`
1856
2350
  `).filter(Boolean);
1857
2351
  process.stderr.write(`
@@ -1886,9 +2380,9 @@ async function tailLog(logFile, levelFilter) {
1886
2380
  process.stderr.write(`${bold("Tailing:")} ${dim(logFile)} ${dim("(Ctrl+C to stop)")}
1887
2381
 
1888
2382
  `);
1889
- let lastSize = existsSync6(logFile) ? statSync2(logFile).size : 0;
1890
- if (existsSync6(logFile)) {
1891
- const content = readFileSync5(logFile, "utf-8");
2383
+ let lastSize = existsSync9(logFile) ? statSync2(logFile).size : 0;
2384
+ if (existsSync9(logFile)) {
2385
+ const content = readFileSync7(logFile, "utf-8");
1892
2386
  const lines = content.trim().split(`
1893
2387
  `).filter(Boolean);
1894
2388
  const recent = lines.slice(-10);
@@ -1906,12 +2400,12 @@ async function tailLog(logFile, levelFilter) {
1906
2400
  }
1907
2401
  return new Promise((resolve) => {
1908
2402
  const interval = setInterval(() => {
1909
- if (!existsSync6(logFile))
2403
+ if (!existsSync9(logFile))
1910
2404
  return;
1911
2405
  const currentSize = statSync2(logFile).size;
1912
2406
  if (currentSize <= lastSize)
1913
2407
  return;
1914
- const content = readFileSync5(logFile, "utf-8");
2408
+ const content = readFileSync7(logFile, "utf-8");
1915
2409
  const allLines = content.trim().split(`
1916
2410
  `).filter(Boolean);
1917
2411
  const oldContent = content.slice(0, lastSize);
@@ -1966,7 +2460,7 @@ function cleanLogs(logsDir) {
1966
2460
  `);
1967
2461
  }
1968
2462
  function getLogFiles(logsDir) {
1969
- return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) => join6(logsDir, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
2463
+ return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) => join8(logsDir, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
1970
2464
  }
1971
2465
  function formatEntry(entry) {
1972
2466
  const time = dim(new Date(entry.ts).toLocaleTimeString());
@@ -2247,9 +2741,9 @@ var init_stream_renderer = __esm(() => {
2247
2741
  });
2248
2742
 
2249
2743
  // src/repl/image-detect.ts
2250
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
2251
- import { homedir as homedir2, tmpdir } from "node:os";
2252
- import { basename, extname, join as join7, resolve } from "node:path";
2744
+ import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
2745
+ import { homedir as homedir3, tmpdir } from "node:os";
2746
+ import { basename, extname, join as join9, resolve } from "node:path";
2253
2747
  function detectImages(input) {
2254
2748
  const detected = [];
2255
2749
  const byResolved = new Map;
@@ -2347,7 +2841,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
2347
2841
  return;
2348
2842
  let resolved = stripQuotes(rawPath).replace(/\\ /g, " ");
2349
2843
  if (resolved.startsWith("~/")) {
2350
- resolved = join7(homedir2(), resolved.slice(2));
2844
+ resolved = join9(homedir3(), resolved.slice(2));
2351
2845
  }
2352
2846
  resolved = resolve(resolved);
2353
2847
  const existing = byResolved.get(resolved);
@@ -2360,7 +2854,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
2360
2854
  ]);
2361
2855
  return;
2362
2856
  }
2363
- const exists = existsSync7(resolved);
2857
+ const exists = existsSync10(resolved);
2364
2858
  let stablePath = resolved;
2365
2859
  if (exists) {
2366
2860
  stablePath = copyToStable(resolved);
@@ -2414,10 +2908,10 @@ function dedupeByResolvedPath(images) {
2414
2908
  }
2415
2909
  function copyToStable(sourcePath) {
2416
2910
  try {
2417
- if (!existsSync7(STABLE_DIR)) {
2418
- mkdirSync6(STABLE_DIR, { recursive: true });
2911
+ if (!existsSync10(STABLE_DIR)) {
2912
+ mkdirSync7(STABLE_DIR, { recursive: true });
2419
2913
  }
2420
- const dest = join7(STABLE_DIR, `${Date.now()}-${basename(sourcePath)}`);
2914
+ const dest = join9(STABLE_DIR, `${Date.now()}-${basename(sourcePath)}`);
2421
2915
  copyFileSync(sourcePath, dest);
2422
2916
  return dest;
2423
2917
  } catch {
@@ -2437,7 +2931,7 @@ var init_image_detect = __esm(() => {
2437
2931
  ".tif",
2438
2932
  ".tiff"
2439
2933
  ]);
2440
- STABLE_DIR = join7(tmpdir(), "locus-images");
2934
+ STABLE_DIR = join9(tmpdir(), "locus-images");
2441
2935
  PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
2442
2936
  });
2443
2937
 
@@ -2482,6 +2976,8 @@ class InputHandler {
2482
2976
  let pasteBuffer = "";
2483
2977
  let imageAttachments = [];
2484
2978
  let nextImageId = 1;
2979
+ let pasteAttachments = [];
2980
+ let nextPasteId = 1;
2485
2981
  const history = this.getHistory();
2486
2982
  let historyIndex = -1;
2487
2983
  let historyDraft = "";
@@ -2527,6 +3023,15 @@ class InputHandler {
2527
3023
  preferredColumn = null;
2528
3024
  resetHistoryNavigation();
2529
3025
  };
3026
+ const insertRaw = (text) => {
3027
+ if (!text)
3028
+ return;
3029
+ const next = buffer.slice(0, cursor) + text + buffer.slice(cursor);
3030
+ cursor += text.length;
3031
+ setBuffer(next);
3032
+ preferredColumn = null;
3033
+ resetHistoryNavigation();
3034
+ };
2530
3035
  const deleteBeforeCursor = () => {
2531
3036
  if (cursor === 0)
2532
3037
  return;
@@ -2628,6 +3133,8 @@ class InputHandler {
2628
3133
  setBuffer(history[historyIndex] ?? "");
2629
3134
  imageAttachments = [];
2630
3135
  nextImageId = 1;
3136
+ pasteAttachments = [];
3137
+ nextPasteId = 1;
2631
3138
  cursor = buffer.length;
2632
3139
  preferredColumn = null;
2633
3140
  };
@@ -2639,12 +3146,16 @@ class InputHandler {
2639
3146
  setBuffer(history[historyIndex] ?? "");
2640
3147
  imageAttachments = [];
2641
3148
  nextImageId = 1;
3149
+ pasteAttachments = [];
3150
+ nextPasteId = 1;
2642
3151
  } else {
2643
3152
  historyIndex = -1;
2644
3153
  setBuffer(historyDraft);
2645
3154
  historyDraft = "";
2646
3155
  imageAttachments = [];
2647
3156
  nextImageId = 1;
3157
+ pasteAttachments = [];
3158
+ nextPasteId = 1;
2648
3159
  }
2649
3160
  cursor = buffer.length;
2650
3161
  preferredColumn = null;
@@ -2699,9 +3210,10 @@ class InputHandler {
2699
3210
  imageAttachments = normalized.attachments;
2700
3211
  nextImageId = normalized.nextId;
2701
3212
  const images = collectReferencedAttachments(buffer, imageAttachments);
3213
+ const finalText = expandPastePlaceholders(buffer, pasteAttachments);
2702
3214
  out.write(`\r
2703
3215
  `);
2704
- finish({ type: "submit", text: buffer, images });
3216
+ finish({ type: "submit", text: finalText, images });
2705
3217
  };
2706
3218
  const clearBufferOrInterrupt = () => {
2707
3219
  if (buffer.length > 0) {
@@ -2709,6 +3221,8 @@ class InputHandler {
2709
3221
  cursor = 0;
2710
3222
  imageAttachments = [];
2711
3223
  nextImageId = 1;
3224
+ pasteAttachments = [];
3225
+ nextPasteId = 1;
2712
3226
  preferredColumn = null;
2713
3227
  resetHistoryNavigation();
2714
3228
  render();
@@ -2729,11 +3243,19 @@ ${dim("Press Ctrl+C again to exit")}\r
2729
3243
  const maybeHeuristicPaste = (chunk) => {
2730
3244
  if (chunk.includes(ESC))
2731
3245
  return false;
2732
- if (!looksLikePaste(chunk) && !looksLikeImagePathChunk(chunk)) {
3246
+ const isImagePath = looksLikeImagePathChunk(chunk);
3247
+ if (!looksLikePaste(chunk) && !isImagePath) {
2733
3248
  return false;
2734
3249
  }
2735
3250
  const normalized = normalizeLineEndings(chunk);
2736
- insertText(normalized);
3251
+ if (!isImagePath && normalized.includes(`
3252
+ `)) {
3253
+ const id = nextPasteId++;
3254
+ pasteAttachments.push({ id, content: normalized });
3255
+ insertRaw(buildPastePlaceholder(id, normalized));
3256
+ } else {
3257
+ insertText(normalized);
3258
+ }
2737
3259
  render();
2738
3260
  return true;
2739
3261
  };
@@ -2858,8 +3380,16 @@ ${dim("Press Ctrl+C again to exit")}\r
2858
3380
  pasteBuffer += pending.slice(0, endIdx);
2859
3381
  pending = pending.slice(endIdx + PASTE_END.length);
2860
3382
  isPasting = false;
2861
- insertText(normalizeLineEndings(pasteBuffer));
3383
+ const pasteContent = normalizeLineEndings(pasteBuffer);
2862
3384
  pasteBuffer = "";
3385
+ if (pasteContent.includes(`
3386
+ `)) {
3387
+ const id = nextPasteId++;
3388
+ pasteAttachments.push({ id, content: pasteContent });
3389
+ insertRaw(buildPastePlaceholder(id, pasteContent));
3390
+ } else {
3391
+ insertText(pasteContent);
3392
+ }
2863
3393
  render();
2864
3394
  continue;
2865
3395
  }
@@ -3016,6 +3546,19 @@ function looksLikePaste(chunk) {
3016
3546
  return chunk.includes(`
3017
3547
  `) || chunk.includes("\r");
3018
3548
  }
3549
+ function buildPastePlaceholder(id, content) {
3550
+ const lines = content.split(`
3551
+ `);
3552
+ const lineCount = content.endsWith(`
3553
+ `) ? lines.length - 1 : lines.length;
3554
+ return `[Pasted text #${id} +${lineCount} lines]`;
3555
+ }
3556
+ function expandPastePlaceholders(text, attachments) {
3557
+ return text.replace(/\[Pasted text #(\d+) \+\d+ lines\]/g, (_, idStr) => {
3558
+ const id = parseInt(idStr, 10);
3559
+ return attachments.find((a) => a.id === id)?.content ?? "";
3560
+ });
3561
+ }
3019
3562
  function looksLikeImagePathChunk(chunk) {
3020
3563
  if (chunk.length <= 1)
3021
3564
  return false;
@@ -3112,10 +3655,13 @@ function listenForInterrupt(onInterrupt, onForceExit) {
3112
3655
  const wasRaw = stdin.isRaw;
3113
3656
  stdin.setRawMode(true);
3114
3657
  stdin.resume();
3658
+ process.stderr.write(ENABLE_BRACKETED_PASTE);
3115
3659
  let interrupted = false;
3116
3660
  let interruptTime = 0;
3117
3661
  const handler = (data) => {
3118
3662
  const seq = data.toString();
3663
+ if (seq.startsWith(PASTE_START) || seq === PASTE_END)
3664
+ return;
3119
3665
  if (seq === "\x1B" || seq === "\x03") {
3120
3666
  const now = Date.now();
3121
3667
  if (interrupted && now - interruptTime < 2000 && onForceExit) {
@@ -3135,6 +3681,7 @@ ${dim("Press")} ${yellow("ESC")} ${dim("again to force exit")}\r
3135
3681
  stdin.on("data", handler);
3136
3682
  return () => {
3137
3683
  stdin.removeListener("data", handler);
3684
+ process.stderr.write(DISABLE_BRACKETED_PASTE);
3138
3685
  if (wasRaw !== undefined && stdin.isTTY) {
3139
3686
  stdin.setRawMode(wasRaw);
3140
3687
  }
@@ -3202,7 +3749,7 @@ var init_input_handler = __esm(() => {
3202
3749
  });
3203
3750
 
3204
3751
  // src/ai/claude.ts
3205
- import { execSync as execSync4, spawn } from "node:child_process";
3752
+ import { execSync as execSync4, spawn as spawn2 } from "node:child_process";
3206
3753
 
3207
3754
  class ClaudeRunner {
3208
3755
  name = "claude";
@@ -3248,7 +3795,7 @@ class ClaudeRunner {
3248
3795
  const env = { ...process.env };
3249
3796
  delete env.CLAUDECODE;
3250
3797
  delete env.CLAUDE_CODE;
3251
- this.process = spawn("claude", args, {
3798
+ this.process = spawn2("claude", args, {
3252
3799
  cwd: options.cwd,
3253
3800
  stdio: ["pipe", "pipe", "pipe"],
3254
3801
  env
@@ -3330,7 +3877,7 @@ var init_claude = __esm(() => {
3330
3877
  });
3331
3878
 
3332
3879
  // src/ai/codex.ts
3333
- import { execSync as execSync5, spawn as spawn2 } from "node:child_process";
3880
+ import { execSync as execSync5, spawn as spawn3 } from "node:child_process";
3334
3881
  function buildCodexArgs(model) {
3335
3882
  const args = ["exec", "--full-auto", "--skip-git-repo-check"];
3336
3883
  if (model) {
@@ -3374,7 +3921,7 @@ class CodexRunner {
3374
3921
  return new Promise((resolve2) => {
3375
3922
  let output = "";
3376
3923
  let errorOutput = "";
3377
- this.process = spawn2("codex", args, {
3924
+ this.process = spawn3("codex", args, {
3378
3925
  cwd: options.cwd,
3379
3926
  stdio: ["pipe", "pipe", "pipe"],
3380
3927
  env: { ...process.env }
@@ -4964,8 +5511,8 @@ var init_sprint = __esm(() => {
4964
5511
 
4965
5512
  // src/core/prompt-builder.ts
4966
5513
  import { execSync as execSync6 } from "node:child_process";
4967
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
4968
- import { join as join8 } from "node:path";
5514
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
5515
+ import { join as join10 } from "node:path";
4969
5516
  function buildExecutionPrompt(ctx) {
4970
5517
  const sections = [];
4971
5518
  sections.push(buildSystemContext(ctx.projectRoot));
@@ -4995,13 +5542,13 @@ function buildFeedbackPrompt(ctx) {
4995
5542
  }
4996
5543
  function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
4997
5544
  const sections = [];
4998
- const locusmd = readFileSafe(join8(projectRoot, "LOCUS.md"));
5545
+ const locusmd = readFileSafe(join10(projectRoot, "LOCUS.md"));
4999
5546
  if (locusmd) {
5000
5547
  sections.push(`# Project Instructions
5001
5548
 
5002
5549
  ${locusmd}`);
5003
5550
  }
5004
- const learnings = readFileSafe(join8(projectRoot, ".locus", "LEARNINGS.md"));
5551
+ const learnings = readFileSafe(join10(projectRoot, ".locus", "LEARNINGS.md"));
5005
5552
  if (learnings) {
5006
5553
  sections.push(`# Past Learnings
5007
5554
 
@@ -5027,24 +5574,24 @@ ${userMessage}`);
5027
5574
  }
5028
5575
  function buildSystemContext(projectRoot) {
5029
5576
  const parts = ["# System Context"];
5030
- const locusmd = readFileSafe(join8(projectRoot, "LOCUS.md"));
5577
+ const locusmd = readFileSafe(join10(projectRoot, "LOCUS.md"));
5031
5578
  if (locusmd) {
5032
5579
  parts.push(`## Project Instructions (LOCUS.md)
5033
5580
 
5034
5581
  ${locusmd}`);
5035
5582
  }
5036
- const learnings = readFileSafe(join8(projectRoot, ".locus", "LEARNINGS.md"));
5583
+ const learnings = readFileSafe(join10(projectRoot, ".locus", "LEARNINGS.md"));
5037
5584
  if (learnings) {
5038
5585
  parts.push(`## Past Learnings
5039
5586
 
5040
5587
  ${learnings}`);
5041
5588
  }
5042
- const discussionsDir = join8(projectRoot, ".locus", "discussions");
5043
- if (existsSync8(discussionsDir)) {
5589
+ const discussionsDir = join10(projectRoot, ".locus", "discussions");
5590
+ if (existsSync11(discussionsDir)) {
5044
5591
  try {
5045
5592
  const files = readdirSync3(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
5046
5593
  for (const file of files) {
5047
- const content = readFileSafe(join8(discussionsDir, file));
5594
+ const content = readFileSafe(join10(discussionsDir, file));
5048
5595
  if (content) {
5049
5596
  parts.push(`## Discussion: ${file.replace(".md", "")}
5050
5597
 
@@ -5190,9 +5737,9 @@ function buildFeedbackInstructions() {
5190
5737
  }
5191
5738
  function readFileSafe(path) {
5192
5739
  try {
5193
- if (!existsSync8(path))
5740
+ if (!existsSync11(path))
5194
5741
  return null;
5195
- return readFileSync6(path, "utf-8");
5742
+ return readFileSync8(path, "utf-8");
5196
5743
  } catch {
5197
5744
  return null;
5198
5745
  }
@@ -5622,7 +6169,7 @@ var init_commands = __esm(() => {
5622
6169
 
5623
6170
  // src/repl/completions.ts
5624
6171
  import { readdirSync as readdirSync4 } from "node:fs";
5625
- import { basename as basename2, dirname as dirname3, join as join9 } from "node:path";
6172
+ import { basename as basename2, dirname as dirname3, join as join11 } from "node:path";
5626
6173
 
5627
6174
  class SlashCommandCompletion {
5628
6175
  commands;
@@ -5677,7 +6224,7 @@ class FilePathCompletion {
5677
6224
  }
5678
6225
  findMatches(partial) {
5679
6226
  try {
5680
- const dir = partial.includes("/") ? join9(this.projectRoot, dirname3(partial)) : this.projectRoot;
6227
+ const dir = partial.includes("/") ? join11(this.projectRoot, dirname3(partial)) : this.projectRoot;
5681
6228
  const prefix = basename2(partial);
5682
6229
  const entries = readdirSync4(dir, { withFileTypes: true });
5683
6230
  return entries.filter((e) => {
@@ -5713,14 +6260,14 @@ class CombinedCompletion {
5713
6260
  var init_completions = () => {};
5714
6261
 
5715
6262
  // src/repl/input-history.ts
5716
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
5717
- import { dirname as dirname4, join as join10 } from "node:path";
6263
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
6264
+ import { dirname as dirname4, join as join12 } from "node:path";
5718
6265
 
5719
6266
  class InputHistory {
5720
6267
  entries = [];
5721
6268
  filePath;
5722
6269
  constructor(projectRoot) {
5723
- this.filePath = join10(projectRoot, ".locus", "sessions", ".input-history");
6270
+ this.filePath = join12(projectRoot, ".locus", "sessions", ".input-history");
5724
6271
  this.load();
5725
6272
  }
5726
6273
  add(text) {
@@ -5759,9 +6306,9 @@ class InputHistory {
5759
6306
  }
5760
6307
  load() {
5761
6308
  try {
5762
- if (!existsSync9(this.filePath))
6309
+ if (!existsSync12(this.filePath))
5763
6310
  return;
5764
- const content = readFileSync7(this.filePath, "utf-8");
6311
+ const content = readFileSync9(this.filePath, "utf-8");
5765
6312
  this.entries = content.split(`
5766
6313
  `).map((line) => this.unescape(line)).filter(Boolean);
5767
6314
  } catch {}
@@ -5769,12 +6316,12 @@ class InputHistory {
5769
6316
  save() {
5770
6317
  try {
5771
6318
  const dir = dirname4(this.filePath);
5772
- if (!existsSync9(dir)) {
5773
- mkdirSync7(dir, { recursive: true });
6319
+ if (!existsSync12(dir)) {
6320
+ mkdirSync8(dir, { recursive: true });
5774
6321
  }
5775
6322
  const content = this.entries.map((e) => this.escape(e)).join(`
5776
6323
  `);
5777
- writeFileSync5(this.filePath, content, "utf-8");
6324
+ writeFileSync6(this.filePath, content, "utf-8");
5778
6325
  } catch {}
5779
6326
  }
5780
6327
  escape(text) {
@@ -5801,21 +6348,21 @@ var init_model_config = __esm(() => {
5801
6348
  // src/repl/session-manager.ts
5802
6349
  import { randomBytes } from "node:crypto";
5803
6350
  import {
5804
- existsSync as existsSync10,
5805
- mkdirSync as mkdirSync8,
6351
+ existsSync as existsSync13,
6352
+ mkdirSync as mkdirSync9,
5806
6353
  readdirSync as readdirSync5,
5807
- readFileSync as readFileSync8,
6354
+ readFileSync as readFileSync10,
5808
6355
  unlinkSync as unlinkSync3,
5809
- writeFileSync as writeFileSync6
6356
+ writeFileSync as writeFileSync7
5810
6357
  } from "node:fs";
5811
- import { basename as basename3, join as join11 } from "node:path";
6358
+ import { basename as basename3, join as join13 } from "node:path";
5812
6359
 
5813
6360
  class SessionManager {
5814
6361
  sessionsDir;
5815
6362
  constructor(projectRoot) {
5816
- this.sessionsDir = join11(projectRoot, ".locus", "sessions");
5817
- if (!existsSync10(this.sessionsDir)) {
5818
- mkdirSync8(this.sessionsDir, { recursive: true });
6363
+ this.sessionsDir = join13(projectRoot, ".locus", "sessions");
6364
+ if (!existsSync13(this.sessionsDir)) {
6365
+ mkdirSync9(this.sessionsDir, { recursive: true });
5819
6366
  }
5820
6367
  }
5821
6368
  create(options) {
@@ -5840,14 +6387,14 @@ class SessionManager {
5840
6387
  }
5841
6388
  isPersisted(sessionOrId) {
5842
6389
  const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
5843
- return existsSync10(this.getSessionPath(sessionId));
6390
+ return existsSync13(this.getSessionPath(sessionId));
5844
6391
  }
5845
6392
  load(idOrPrefix) {
5846
6393
  const files = this.listSessionFiles();
5847
6394
  const exactPath = this.getSessionPath(idOrPrefix);
5848
- if (existsSync10(exactPath)) {
6395
+ if (existsSync13(exactPath)) {
5849
6396
  try {
5850
- return JSON.parse(readFileSync8(exactPath, "utf-8"));
6397
+ return JSON.parse(readFileSync10(exactPath, "utf-8"));
5851
6398
  } catch {
5852
6399
  return null;
5853
6400
  }
@@ -5855,7 +6402,7 @@ class SessionManager {
5855
6402
  const matches = files.filter((f) => basename3(f, ".json").startsWith(idOrPrefix));
5856
6403
  if (matches.length === 1) {
5857
6404
  try {
5858
- return JSON.parse(readFileSync8(matches[0], "utf-8"));
6405
+ return JSON.parse(readFileSync10(matches[0], "utf-8"));
5859
6406
  } catch {
5860
6407
  return null;
5861
6408
  }
@@ -5868,7 +6415,7 @@ class SessionManager {
5868
6415
  save(session) {
5869
6416
  session.updated = new Date().toISOString();
5870
6417
  const path = this.getSessionPath(session.id);
5871
- writeFileSync6(path, `${JSON.stringify(session, null, 2)}
6418
+ writeFileSync7(path, `${JSON.stringify(session, null, 2)}
5872
6419
  `, "utf-8");
5873
6420
  }
5874
6421
  addMessage(session, message) {
@@ -5880,7 +6427,7 @@ class SessionManager {
5880
6427
  const sessions = [];
5881
6428
  for (const file of files) {
5882
6429
  try {
5883
- const session = JSON.parse(readFileSync8(file, "utf-8"));
6430
+ const session = JSON.parse(readFileSync10(file, "utf-8"));
5884
6431
  sessions.push({
5885
6432
  id: session.id,
5886
6433
  created: session.created,
@@ -5895,7 +6442,7 @@ class SessionManager {
5895
6442
  }
5896
6443
  delete(sessionId) {
5897
6444
  const path = this.getSessionPath(sessionId);
5898
- if (existsSync10(path)) {
6445
+ if (existsSync13(path)) {
5899
6446
  unlinkSync3(path);
5900
6447
  return true;
5901
6448
  }
@@ -5907,7 +6454,7 @@ class SessionManager {
5907
6454
  let pruned = 0;
5908
6455
  const withStats = files.map((f) => {
5909
6456
  try {
5910
- const session = JSON.parse(readFileSync8(f, "utf-8"));
6457
+ const session = JSON.parse(readFileSync10(f, "utf-8"));
5911
6458
  return { path: f, updated: new Date(session.updated).getTime() };
5912
6459
  } catch {
5913
6460
  return { path: f, updated: 0 };
@@ -5925,7 +6472,7 @@ class SessionManager {
5925
6472
  const remaining = withStats.length - pruned;
5926
6473
  if (remaining > MAX_SESSIONS) {
5927
6474
  const toRemove = remaining - MAX_SESSIONS;
5928
- const alive = withStats.filter((e) => existsSync10(e.path));
6475
+ const alive = withStats.filter((e) => existsSync13(e.path));
5929
6476
  for (let i = 0;i < toRemove && i < alive.length; i++) {
5930
6477
  try {
5931
6478
  unlinkSync3(alive[i].path);
@@ -5940,7 +6487,7 @@ class SessionManager {
5940
6487
  }
5941
6488
  listSessionFiles() {
5942
6489
  try {
5943
- return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join11(this.sessionsDir, f));
6490
+ return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join13(this.sessionsDir, f));
5944
6491
  } catch {
5945
6492
  return [];
5946
6493
  }
@@ -5949,7 +6496,7 @@ class SessionManager {
5949
6496
  return randomBytes(6).toString("hex");
5950
6497
  }
5951
6498
  getSessionPath(sessionId) {
5952
- return join11(this.sessionsDir, `${sessionId}.json`);
6499
+ return join13(this.sessionsDir, `${sessionId}.json`);
5953
6500
  }
5954
6501
  }
5955
6502
  var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
@@ -6734,22 +7281,22 @@ var init_conflict = __esm(() => {
6734
7281
 
6735
7282
  // src/core/run-state.ts
6736
7283
  import {
6737
- existsSync as existsSync11,
6738
- mkdirSync as mkdirSync9,
6739
- readFileSync as readFileSync9,
7284
+ existsSync as existsSync14,
7285
+ mkdirSync as mkdirSync10,
7286
+ readFileSync as readFileSync11,
6740
7287
  unlinkSync as unlinkSync4,
6741
- writeFileSync as writeFileSync7
7288
+ writeFileSync as writeFileSync8
6742
7289
  } from "node:fs";
6743
- import { dirname as dirname5, join as join12 } from "node:path";
7290
+ import { dirname as dirname5, join as join14 } from "node:path";
6744
7291
  function getRunStatePath(projectRoot) {
6745
- return join12(projectRoot, ".locus", "run-state.json");
7292
+ return join14(projectRoot, ".locus", "run-state.json");
6746
7293
  }
6747
7294
  function loadRunState(projectRoot) {
6748
7295
  const path = getRunStatePath(projectRoot);
6749
- if (!existsSync11(path))
7296
+ if (!existsSync14(path))
6750
7297
  return null;
6751
7298
  try {
6752
- return JSON.parse(readFileSync9(path, "utf-8"));
7299
+ return JSON.parse(readFileSync11(path, "utf-8"));
6753
7300
  } catch {
6754
7301
  getLogger().warn("Corrupted run-state.json, ignoring");
6755
7302
  return null;
@@ -6758,15 +7305,15 @@ function loadRunState(projectRoot) {
6758
7305
  function saveRunState(projectRoot, state) {
6759
7306
  const path = getRunStatePath(projectRoot);
6760
7307
  const dir = dirname5(path);
6761
- if (!existsSync11(dir)) {
6762
- mkdirSync9(dir, { recursive: true });
7308
+ if (!existsSync14(dir)) {
7309
+ mkdirSync10(dir, { recursive: true });
6763
7310
  }
6764
- writeFileSync7(path, `${JSON.stringify(state, null, 2)}
7311
+ writeFileSync8(path, `${JSON.stringify(state, null, 2)}
6765
7312
  `, "utf-8");
6766
7313
  }
6767
7314
  function clearRunState(projectRoot) {
6768
7315
  const path = getRunStatePath(projectRoot);
6769
- if (existsSync11(path)) {
7316
+ if (existsSync14(path)) {
6770
7317
  unlinkSync4(path);
6771
7318
  }
6772
7319
  }
@@ -6907,8 +7454,8 @@ var init_shutdown = __esm(() => {
6907
7454
 
6908
7455
  // src/core/worktree.ts
6909
7456
  import { execSync as execSync11 } from "node:child_process";
6910
- import { existsSync as existsSync12, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
6911
- import { join as join13 } from "node:path";
7457
+ import { existsSync as existsSync15, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
7458
+ import { join as join15 } from "node:path";
6912
7459
  function git3(args, cwd) {
6913
7460
  return execSync11(`git ${args}`, {
6914
7461
  cwd,
@@ -6924,10 +7471,10 @@ function gitSafe2(args, cwd) {
6924
7471
  }
6925
7472
  }
6926
7473
  function getWorktreeDir(projectRoot) {
6927
- return join13(projectRoot, ".locus", "worktrees");
7474
+ return join15(projectRoot, ".locus", "worktrees");
6928
7475
  }
6929
7476
  function getWorktreePath(projectRoot, issueNumber) {
6930
- return join13(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
7477
+ return join15(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
6931
7478
  }
6932
7479
  function generateBranchName(issueNumber) {
6933
7480
  const randomSuffix = Math.random().toString(36).slice(2, 8);
@@ -6947,7 +7494,7 @@ function getWorktreeBranch(worktreePath) {
6947
7494
  function createWorktree(projectRoot, issueNumber, baseBranch) {
6948
7495
  const log = getLogger();
6949
7496
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
6950
- if (existsSync12(worktreePath)) {
7497
+ if (existsSync15(worktreePath)) {
6951
7498
  log.verbose(`Worktree already exists for issue #${issueNumber}`);
6952
7499
  const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
6953
7500
  return {
@@ -6974,7 +7521,7 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
6974
7521
  function removeWorktree(projectRoot, issueNumber) {
6975
7522
  const log = getLogger();
6976
7523
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
6977
- if (!existsSync12(worktreePath)) {
7524
+ if (!existsSync15(worktreePath)) {
6978
7525
  log.verbose(`Worktree for issue #${issueNumber} does not exist`);
6979
7526
  return;
6980
7527
  }
@@ -6993,7 +7540,7 @@ function removeWorktree(projectRoot, issueNumber) {
6993
7540
  function listWorktrees(projectRoot) {
6994
7541
  const log = getLogger();
6995
7542
  const worktreeDir = getWorktreeDir(projectRoot);
6996
- if (!existsSync12(worktreeDir)) {
7543
+ if (!existsSync15(worktreeDir)) {
6997
7544
  return [];
6998
7545
  }
6999
7546
  const entries = readdirSync6(worktreeDir).filter((entry) => entry.startsWith("issue-"));
@@ -7013,7 +7560,7 @@ function listWorktrees(projectRoot) {
7013
7560
  if (!match)
7014
7561
  continue;
7015
7562
  const issueNumber = Number.parseInt(match[1], 10);
7016
- const path = join13(worktreeDir, entry);
7563
+ const path = join15(worktreeDir, entry);
7017
7564
  const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
7018
7565
  let resolvedPath;
7019
7566
  try {
@@ -7253,7 +7800,18 @@ ${bold("Summary:")}
7253
7800
  issue: t.issue,
7254
7801
  title: issues.find((i) => i.number === t.issue)?.title
7255
7802
  }));
7256
- await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
7803
+ const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
7804
+ if (prNumber !== undefined) {
7805
+ try {
7806
+ execSync12(`git checkout ${config.agent.baseBranch}`, {
7807
+ cwd: projectRoot,
7808
+ encoding: "utf-8",
7809
+ stdio: ["pipe", "pipe", "pipe"]
7810
+ });
7811
+ process.stderr.write(` ${dim(`Checked out ${config.agent.baseBranch}`)}
7812
+ `);
7813
+ } catch {}
7814
+ }
7257
7815
  }
7258
7816
  if (stats.failed === 0) {
7259
7817
  clearRunState(projectRoot);
@@ -7490,7 +8048,18 @@ ${bold("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fail
7490
8048
  `);
7491
8049
  if (isSprintRun && state.branch && state.sprint && finalStats.done > 0) {
7492
8050
  const completedTasks = state.tasks.filter((t) => t.status === "done").map((t) => ({ issue: t.issue }));
7493
- await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
8051
+ const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
8052
+ if (prNumber !== undefined) {
8053
+ try {
8054
+ execSync12(`git checkout ${config.agent.baseBranch}`, {
8055
+ cwd: projectRoot,
8056
+ encoding: "utf-8",
8057
+ stdio: ["pipe", "pipe", "pipe"]
8058
+ });
8059
+ process.stderr.write(` ${dim(`Checked out ${config.agent.baseBranch}`)}
8060
+ `);
8061
+ } catch {}
8062
+ }
7494
8063
  }
7495
8064
  if (finalStats.failed === 0) {
7496
8065
  clearRunState(projectRoot);
@@ -7696,13 +8265,13 @@ __export(exports_plan, {
7696
8265
  parsePlanArgs: () => parsePlanArgs
7697
8266
  });
7698
8267
  import {
7699
- existsSync as existsSync13,
7700
- mkdirSync as mkdirSync10,
8268
+ existsSync as existsSync16,
8269
+ mkdirSync as mkdirSync11,
7701
8270
  readdirSync as readdirSync7,
7702
- readFileSync as readFileSync10,
7703
- writeFileSync as writeFileSync8
8271
+ readFileSync as readFileSync12,
8272
+ writeFileSync as writeFileSync9
7704
8273
  } from "node:fs";
7705
- import { join as join14 } from "node:path";
8274
+ import { join as join16 } from "node:path";
7706
8275
  function printHelp() {
7707
8276
  process.stderr.write(`
7708
8277
  ${bold("locus plan")} — AI-powered sprint planning
@@ -7733,12 +8302,12 @@ function normalizeSprintName(name) {
7733
8302
  return name.trim().toLowerCase();
7734
8303
  }
7735
8304
  function getPlansDir(projectRoot) {
7736
- return join14(projectRoot, ".locus", "plans");
8305
+ return join16(projectRoot, ".locus", "plans");
7737
8306
  }
7738
8307
  function ensurePlansDir(projectRoot) {
7739
8308
  const dir = getPlansDir(projectRoot);
7740
- if (!existsSync13(dir)) {
7741
- mkdirSync10(dir, { recursive: true });
8309
+ if (!existsSync16(dir)) {
8310
+ mkdirSync11(dir, { recursive: true });
7742
8311
  }
7743
8312
  return dir;
7744
8313
  }
@@ -7747,14 +8316,14 @@ function generateId() {
7747
8316
  }
7748
8317
  function loadPlanFile(projectRoot, id) {
7749
8318
  const dir = getPlansDir(projectRoot);
7750
- if (!existsSync13(dir))
8319
+ if (!existsSync16(dir))
7751
8320
  return null;
7752
8321
  const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
7753
8322
  const match = files.find((f) => f.startsWith(id));
7754
8323
  if (!match)
7755
8324
  return null;
7756
8325
  try {
7757
- const content = readFileSync10(join14(dir, match), "utf-8");
8326
+ const content = readFileSync12(join16(dir, match), "utf-8");
7758
8327
  return JSON.parse(content);
7759
8328
  } catch {
7760
8329
  return null;
@@ -7800,7 +8369,7 @@ async function planCommand(projectRoot, args, flags = {}) {
7800
8369
  }
7801
8370
  function handleListPlans(projectRoot) {
7802
8371
  const dir = getPlansDir(projectRoot);
7803
- if (!existsSync13(dir)) {
8372
+ if (!existsSync16(dir)) {
7804
8373
  process.stderr.write(`${dim("No saved plans yet.")}
7805
8374
  `);
7806
8375
  return;
@@ -7818,7 +8387,7 @@ ${bold("Saved Plans:")}
7818
8387
  for (const file of files) {
7819
8388
  const id = file.replace(".json", "");
7820
8389
  try {
7821
- const content = readFileSync10(join14(dir, file), "utf-8");
8390
+ const content = readFileSync12(join16(dir, file), "utf-8");
7822
8391
  const plan = JSON.parse(content);
7823
8392
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
7824
8393
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -7831,7 +8400,7 @@ ${bold("Saved Plans:")}
7831
8400
  }
7832
8401
  process.stderr.write(`
7833
8402
  `);
7834
- process.stderr.write(` Approve a plan: ${bold("locus plan approve <id>")}
8403
+ process.stderr.write(` Approve a plan: ${bold("locus plan approve <id> --sprint <name>")}
7835
8404
 
7836
8405
  `);
7837
8406
  }
@@ -7929,7 +8498,7 @@ ${bold("Approving plan:")}
7929
8498
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
7930
8499
  const id = generateId();
7931
8500
  const plansDir = ensurePlansDir(projectRoot);
7932
- const planPath = join14(plansDir, `${id}.json`);
8501
+ const planPath = join16(plansDir, `${id}.json`);
7933
8502
  const planPathRelative = `.locus/plans/${id}.json`;
7934
8503
  const displayDirective = directive;
7935
8504
  process.stderr.write(`
@@ -7961,7 +8530,7 @@ ${red("✗")} Planning failed: ${aiResult.error}
7961
8530
  `);
7962
8531
  return;
7963
8532
  }
7964
- if (!existsSync13(planPath)) {
8533
+ if (!existsSync16(planPath)) {
7965
8534
  process.stderr.write(`
7966
8535
  ${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
7967
8536
  `);
@@ -7971,7 +8540,7 @@ ${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
7971
8540
  }
7972
8541
  let plan;
7973
8542
  try {
7974
- const content = readFileSync10(planPath, "utf-8");
8543
+ const content = readFileSync12(planPath, "utf-8");
7975
8544
  plan = JSON.parse(content);
7976
8545
  } catch {
7977
8546
  process.stderr.write(`
@@ -7993,7 +8562,7 @@ ${yellow("⚠")} Plan file has no issues.
7993
8562
  plan.sprint = sprintName;
7994
8563
  if (!plan.createdAt)
7995
8564
  plan.createdAt = new Date().toISOString();
7996
- writeFileSync8(planPath, JSON.stringify(plan, null, 2), "utf-8");
8565
+ writeFileSync9(planPath, JSON.stringify(plan, null, 2), "utf-8");
7997
8566
  process.stderr.write(`
7998
8567
  ${bold("Plan saved:")} ${cyan(id)}
7999
8568
 
@@ -8126,16 +8695,16 @@ function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, pla
8126
8695
  parts.push(`SPRINT: ${sprintName}`);
8127
8696
  }
8128
8697
  parts.push("");
8129
- const locusPath = join14(projectRoot, "LOCUS.md");
8130
- if (existsSync13(locusPath)) {
8131
- const content = readFileSync10(locusPath, "utf-8");
8698
+ const locusPath = join16(projectRoot, "LOCUS.md");
8699
+ if (existsSync16(locusPath)) {
8700
+ const content = readFileSync12(locusPath, "utf-8");
8132
8701
  parts.push("PROJECT CONTEXT (LOCUS.md):");
8133
8702
  parts.push(content.slice(0, 3000));
8134
8703
  parts.push("");
8135
8704
  }
8136
- const learningsPath = join14(projectRoot, ".locus", "LEARNINGS.md");
8137
- if (existsSync13(learningsPath)) {
8138
- const content = readFileSync10(learningsPath, "utf-8");
8705
+ const learningsPath = join16(projectRoot, ".locus", "LEARNINGS.md");
8706
+ if (existsSync16(learningsPath)) {
8707
+ const content = readFileSync12(learningsPath, "utf-8");
8139
8708
  parts.push("PAST LEARNINGS:");
8140
8709
  parts.push(content.slice(0, 2000));
8141
8710
  parts.push("");
@@ -8312,8 +8881,8 @@ __export(exports_review, {
8312
8881
  reviewCommand: () => reviewCommand
8313
8882
  });
8314
8883
  import { execSync as execSync13 } from "node:child_process";
8315
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
8316
- import { join as join15 } from "node:path";
8884
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
8885
+ import { join as join17 } from "node:path";
8317
8886
  function printHelp2() {
8318
8887
  process.stderr.write(`
8319
8888
  ${bold("locus review")} — AI-powered code review
@@ -8470,9 +9039,9 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
8470
9039
  const parts = [];
8471
9040
  parts.push(`You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.`);
8472
9041
  parts.push("");
8473
- const locusPath = join15(projectRoot, "LOCUS.md");
8474
- if (existsSync14(locusPath)) {
8475
- const content = readFileSync11(locusPath, "utf-8");
9042
+ const locusPath = join17(projectRoot, "LOCUS.md");
9043
+ if (existsSync17(locusPath)) {
9044
+ const content = readFileSync13(locusPath, "utf-8");
8476
9045
  parts.push("PROJECT CONTEXT:");
8477
9046
  parts.push(content.slice(0, 2000));
8478
9047
  parts.push("");
@@ -8774,14 +9343,14 @@ __export(exports_discuss, {
8774
9343
  discussCommand: () => discussCommand
8775
9344
  });
8776
9345
  import {
8777
- existsSync as existsSync15,
8778
- mkdirSync as mkdirSync11,
9346
+ existsSync as existsSync18,
9347
+ mkdirSync as mkdirSync12,
8779
9348
  readdirSync as readdirSync8,
8780
- readFileSync as readFileSync12,
9349
+ readFileSync as readFileSync14,
8781
9350
  unlinkSync as unlinkSync5,
8782
- writeFileSync as writeFileSync9
9351
+ writeFileSync as writeFileSync10
8783
9352
  } from "node:fs";
8784
- import { join as join16 } from "node:path";
9353
+ import { join as join18 } from "node:path";
8785
9354
  function printHelp4() {
8786
9355
  process.stderr.write(`
8787
9356
  ${bold("locus discuss")} — AI-powered architectural discussions
@@ -8803,12 +9372,12 @@ ${bold("Examples:")}
8803
9372
  `);
8804
9373
  }
8805
9374
  function getDiscussionsDir(projectRoot) {
8806
- return join16(projectRoot, ".locus", "discussions");
9375
+ return join18(projectRoot, ".locus", "discussions");
8807
9376
  }
8808
9377
  function ensureDiscussionsDir(projectRoot) {
8809
9378
  const dir = getDiscussionsDir(projectRoot);
8810
- if (!existsSync15(dir)) {
8811
- mkdirSync11(dir, { recursive: true });
9379
+ if (!existsSync18(dir)) {
9380
+ mkdirSync12(dir, { recursive: true });
8812
9381
  }
8813
9382
  return dir;
8814
9383
  }
@@ -8842,7 +9411,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
8842
9411
  }
8843
9412
  function listDiscussions(projectRoot) {
8844
9413
  const dir = getDiscussionsDir(projectRoot);
8845
- if (!existsSync15(dir)) {
9414
+ if (!existsSync18(dir)) {
8846
9415
  process.stderr.write(`${dim("No discussions yet.")}
8847
9416
  `);
8848
9417
  return;
@@ -8859,7 +9428,7 @@ ${bold("Discussions:")}
8859
9428
  `);
8860
9429
  for (const file of files) {
8861
9430
  const id = file.replace(".md", "");
8862
- const content = readFileSync12(join16(dir, file), "utf-8");
9431
+ const content = readFileSync14(join18(dir, file), "utf-8");
8863
9432
  const titleMatch = content.match(/^#\s+(.+)/m);
8864
9433
  const title = titleMatch ? titleMatch[1] : id;
8865
9434
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -8877,7 +9446,7 @@ function showDiscussion(projectRoot, id) {
8877
9446
  return;
8878
9447
  }
8879
9448
  const dir = getDiscussionsDir(projectRoot);
8880
- if (!existsSync15(dir)) {
9449
+ if (!existsSync18(dir)) {
8881
9450
  process.stderr.write(`${red("✗")} No discussions found.
8882
9451
  `);
8883
9452
  return;
@@ -8889,7 +9458,7 @@ function showDiscussion(projectRoot, id) {
8889
9458
  `);
8890
9459
  return;
8891
9460
  }
8892
- const content = readFileSync12(join16(dir, match), "utf-8");
9461
+ const content = readFileSync14(join18(dir, match), "utf-8");
8893
9462
  process.stdout.write(`${content}
8894
9463
  `);
8895
9464
  }
@@ -8900,7 +9469,7 @@ function deleteDiscussion(projectRoot, id) {
8900
9469
  return;
8901
9470
  }
8902
9471
  const dir = getDiscussionsDir(projectRoot);
8903
- if (!existsSync15(dir)) {
9472
+ if (!existsSync18(dir)) {
8904
9473
  process.stderr.write(`${red("✗")} No discussions found.
8905
9474
  `);
8906
9475
  return;
@@ -8912,7 +9481,7 @@ function deleteDiscussion(projectRoot, id) {
8912
9481
  `);
8913
9482
  return;
8914
9483
  }
8915
- unlinkSync5(join16(dir, match));
9484
+ unlinkSync5(join18(dir, match));
8916
9485
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
8917
9486
  `);
8918
9487
  }
@@ -8925,7 +9494,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
8925
9494
  return;
8926
9495
  }
8927
9496
  const dir = getDiscussionsDir(projectRoot);
8928
- if (!existsSync15(dir)) {
9497
+ if (!existsSync18(dir)) {
8929
9498
  process.stderr.write(`${red("✗")} No discussions found.
8930
9499
  `);
8931
9500
  return;
@@ -8937,7 +9506,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
8937
9506
  `);
8938
9507
  return;
8939
9508
  }
8940
- const content = readFileSync12(join16(dir, match), "utf-8");
9509
+ const content = readFileSync14(join18(dir, match), "utf-8");
8941
9510
  const titleMatch = content.match(/^#\s+(.+)/m);
8942
9511
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
8943
9512
  await planCommand(projectRoot, [
@@ -9049,7 +9618,7 @@ ${turn.content}`;
9049
9618
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
9050
9619
  ].join(`
9051
9620
  `);
9052
- writeFileSync9(join16(dir, `${id}.md`), markdown, "utf-8");
9621
+ writeFileSync10(join18(dir, `${id}.md`), markdown, "utf-8");
9053
9622
  process.stderr.write(`
9054
9623
  ${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
9055
9624
  `);
@@ -9063,16 +9632,16 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
9063
9632
  const parts = [];
9064
9633
  parts.push(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
9065
9634
  parts.push("");
9066
- const locusPath = join16(projectRoot, "LOCUS.md");
9067
- if (existsSync15(locusPath)) {
9068
- const content = readFileSync12(locusPath, "utf-8");
9635
+ const locusPath = join18(projectRoot, "LOCUS.md");
9636
+ if (existsSync18(locusPath)) {
9637
+ const content = readFileSync14(locusPath, "utf-8");
9069
9638
  parts.push("PROJECT CONTEXT:");
9070
9639
  parts.push(content.slice(0, 3000));
9071
9640
  parts.push("");
9072
9641
  }
9073
- const learningsPath = join16(projectRoot, ".locus", "LEARNINGS.md");
9074
- if (existsSync15(learningsPath)) {
9075
- const content = readFileSync12(learningsPath, "utf-8");
9642
+ const learningsPath = join18(projectRoot, ".locus", "LEARNINGS.md");
9643
+ if (existsSync18(learningsPath)) {
9644
+ const content = readFileSync14(learningsPath, "utf-8");
9076
9645
  parts.push("PAST LEARNINGS:");
9077
9646
  parts.push(content.slice(0, 2000));
9078
9647
  parts.push("");
@@ -9131,8 +9700,8 @@ __export(exports_artifacts, {
9131
9700
  formatDate: () => formatDate2,
9132
9701
  artifactsCommand: () => artifactsCommand
9133
9702
  });
9134
- import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
9135
- import { join as join17 } from "node:path";
9703
+ import { existsSync as existsSync19, readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync4 } from "node:fs";
9704
+ import { join as join19 } from "node:path";
9136
9705
  function printHelp5() {
9137
9706
  process.stderr.write(`
9138
9707
  ${bold("locus artifacts")} — View and manage AI-generated artifacts
@@ -9152,14 +9721,14 @@ ${dim("Artifact names support partial matching.")}
9152
9721
  `);
9153
9722
  }
9154
9723
  function getArtifactsDir(projectRoot) {
9155
- return join17(projectRoot, ".locus", "artifacts");
9724
+ return join19(projectRoot, ".locus", "artifacts");
9156
9725
  }
9157
9726
  function listArtifacts(projectRoot) {
9158
9727
  const dir = getArtifactsDir(projectRoot);
9159
- if (!existsSync16(dir))
9728
+ if (!existsSync19(dir))
9160
9729
  return [];
9161
9730
  return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
9162
- const filePath = join17(dir, fileName);
9731
+ const filePath = join19(dir, fileName);
9163
9732
  const stat = statSync4(filePath);
9164
9733
  return {
9165
9734
  name: fileName.replace(/\.md$/, ""),
@@ -9172,12 +9741,12 @@ function listArtifacts(projectRoot) {
9172
9741
  function readArtifact(projectRoot, name) {
9173
9742
  const dir = getArtifactsDir(projectRoot);
9174
9743
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
9175
- const filePath = join17(dir, fileName);
9176
- if (!existsSync16(filePath))
9744
+ const filePath = join19(dir, fileName);
9745
+ if (!existsSync19(filePath))
9177
9746
  return null;
9178
9747
  const stat = statSync4(filePath);
9179
9748
  return {
9180
- content: readFileSync13(filePath, "utf-8"),
9749
+ content: readFileSync15(filePath, "utf-8"),
9181
9750
  info: {
9182
9751
  name: fileName.replace(/\.md$/, ""),
9183
9752
  fileName,
@@ -9341,17 +9910,17 @@ init_context();
9341
9910
  init_logger();
9342
9911
  init_rate_limiter();
9343
9912
  init_terminal();
9344
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
9345
- import { join as join18 } from "node:path";
9913
+ import { existsSync as existsSync20, readFileSync as readFileSync16 } from "node:fs";
9914
+ import { join as join20 } from "node:path";
9346
9915
  import { fileURLToPath } from "node:url";
9347
9916
  function getCliVersion() {
9348
9917
  const fallbackVersion = "0.0.0";
9349
- const packageJsonPath = join18(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
9350
- if (!existsSync17(packageJsonPath)) {
9918
+ const packageJsonPath = join20(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
9919
+ if (!existsSync20(packageJsonPath)) {
9351
9920
  return fallbackVersion;
9352
9921
  }
9353
9922
  try {
9354
- const parsed = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
9923
+ const parsed = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
9355
9924
  return parsed.version ?? fallbackVersion;
9356
9925
  } catch {
9357
9926
  return fallbackVersion;
@@ -9369,7 +9938,9 @@ function parseArgs(argv) {
9369
9938
  clean: false,
9370
9939
  resume: false,
9371
9940
  dryRun: false,
9372
- check: false
9941
+ check: false,
9942
+ upgrade: false,
9943
+ list: false
9373
9944
  };
9374
9945
  const positional = [];
9375
9946
  let i = 0;
@@ -9389,8 +9960,25 @@ function parseArgs(argv) {
9389
9960
  flags.help = true;
9390
9961
  break;
9391
9962
  case "--version":
9392
- case "-V":
9393
- flags.version = true;
9963
+ case "-V": {
9964
+ const nextToken = rawArgs[i + 1];
9965
+ if (nextToken !== undefined && /^\d/.test(nextToken)) {
9966
+ flags.installVersion = rawArgs[++i];
9967
+ } else {
9968
+ flags.version = true;
9969
+ }
9970
+ break;
9971
+ }
9972
+ case "-v":
9973
+ flags.installVersion = rawArgs[++i];
9974
+ break;
9975
+ case "--upgrade":
9976
+ case "-u":
9977
+ flags.upgrade = true;
9978
+ break;
9979
+ case "--list":
9980
+ case "-l":
9981
+ flags.list = true;
9394
9982
  break;
9395
9983
  case "--json-stream":
9396
9984
  flags.jsonStream = true;
@@ -9459,6 +10047,10 @@ ${bold("Commands:")}
9459
10047
  ${cyan("status")} Dashboard view of current state
9460
10048
  ${cyan("config")} View and manage settings
9461
10049
  ${cyan("logs")} View, tail, and manage execution logs
10050
+ ${cyan("install")} Install a community package
10051
+ ${cyan("uninstall")} Remove an installed package
10052
+ ${cyan("packages")} Manage installed packages (list, outdated)
10053
+ ${cyan("pkg")} ${dim("<name> [cmd]")} Run a command from an installed package
9462
10054
  ${cyan("upgrade")} Check for and install updates
9463
10055
 
9464
10056
  ${bold("Options:")}
@@ -9504,7 +10096,7 @@ async function main() {
9504
10096
  try {
9505
10097
  const root = getGitRoot(cwd);
9506
10098
  if (isInitialized(root)) {
9507
- logDir = join18(root, ".locus", "logs");
10099
+ logDir = join20(root, ".locus", "logs");
9508
10100
  getRateLimiter(root);
9509
10101
  }
9510
10102
  } catch {}
@@ -9534,6 +10126,43 @@ async function main() {
9534
10126
  logger.destroy();
9535
10127
  return;
9536
10128
  }
10129
+ if (command === "install") {
10130
+ if (parsed.flags.list) {
10131
+ const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));
10132
+ await packagesCommand2(["list"], {});
10133
+ logger.destroy();
10134
+ return;
10135
+ }
10136
+ const { installCommand: installCommand2 } = await Promise.resolve().then(() => (init_install(), exports_install));
10137
+ const installFlags = {};
10138
+ if (parsed.flags.installVersion) {
10139
+ installFlags.version = parsed.flags.installVersion;
10140
+ }
10141
+ if (parsed.flags.upgrade) {
10142
+ installFlags.upgrade = "true";
10143
+ }
10144
+ await installCommand2(parsed.args, installFlags);
10145
+ logger.destroy();
10146
+ return;
10147
+ }
10148
+ if (command === "uninstall") {
10149
+ const { uninstallCommand: uninstallCommand2 } = await Promise.resolve().then(() => (init_uninstall(), exports_uninstall));
10150
+ await uninstallCommand2(parsed.args, {});
10151
+ logger.destroy();
10152
+ return;
10153
+ }
10154
+ if (command === "packages") {
10155
+ const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));
10156
+ await packagesCommand2(parsed.args, {});
10157
+ logger.destroy();
10158
+ return;
10159
+ }
10160
+ if (command === "pkg") {
10161
+ const { pkgCommand: pkgCommand2 } = await Promise.resolve().then(() => (init_pkg(), exports_pkg));
10162
+ await pkgCommand2(parsed.args, {});
10163
+ logger.destroy();
10164
+ return;
10165
+ }
9537
10166
  let projectRoot;
9538
10167
  try {
9539
10168
  projectRoot = getGitRoot(cwd);