@locusai/cli 0.17.9 → 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 +801 -169
  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,20 +8265,20 @@ __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
7709
8278
 
7710
8279
  ${bold("Usage:")}
7711
8280
  locus plan "<directive>" ${dim("# AI creates a plan file")}
7712
- locus plan approve <id> ${dim("# Create GitHub issues from saved plan")}
8281
+ locus plan approve <id> [--sprint <name>] ${dim("# Create GitHub issues from saved plan")}
7713
8282
  locus plan list ${dim("# List saved plans")}
7714
8283
  locus plan show <id> ${dim("# Show a saved plan")}
7715
8284
  locus plan --from-issues --sprint <name> ${dim("# Organize existing issues")}
@@ -7723,6 +8292,7 @@ ${bold("Examples:")}
7723
8292
  locus plan "Build user authentication with OAuth"
7724
8293
  locus plan "Improve API performance" --sprint "Sprint 3"
7725
8294
  locus plan approve abc123
8295
+ locus plan approve abc123 --sprint "Sprint 3"
7726
8296
  locus plan list
7727
8297
  locus plan --from-issues --sprint "Sprint 2"
7728
8298
 
@@ -7732,12 +8302,12 @@ function normalizeSprintName(name) {
7732
8302
  return name.trim().toLowerCase();
7733
8303
  }
7734
8304
  function getPlansDir(projectRoot) {
7735
- return join14(projectRoot, ".locus", "plans");
8305
+ return join16(projectRoot, ".locus", "plans");
7736
8306
  }
7737
8307
  function ensurePlansDir(projectRoot) {
7738
8308
  const dir = getPlansDir(projectRoot);
7739
- if (!existsSync13(dir)) {
7740
- mkdirSync10(dir, { recursive: true });
8309
+ if (!existsSync16(dir)) {
8310
+ mkdirSync11(dir, { recursive: true });
7741
8311
  }
7742
8312
  return dir;
7743
8313
  }
@@ -7746,14 +8316,14 @@ function generateId() {
7746
8316
  }
7747
8317
  function loadPlanFile(projectRoot, id) {
7748
8318
  const dir = getPlansDir(projectRoot);
7749
- if (!existsSync13(dir))
8319
+ if (!existsSync16(dir))
7750
8320
  return null;
7751
8321
  const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
7752
8322
  const match = files.find((f) => f.startsWith(id));
7753
8323
  if (!match)
7754
8324
  return null;
7755
8325
  try {
7756
- const content = readFileSync10(join14(dir, match), "utf-8");
8326
+ const content = readFileSync12(join16(dir, match), "utf-8");
7757
8327
  return JSON.parse(content);
7758
8328
  } catch {
7759
8329
  return null;
@@ -7771,7 +8341,8 @@ async function planCommand(projectRoot, args, flags = {}) {
7771
8341
  return handleShowPlan(projectRoot, args[1]);
7772
8342
  }
7773
8343
  if (args[0] === "approve") {
7774
- return handleApprovePlan(projectRoot, args[1], flags);
8344
+ const approveArgs = parsePlanArgs(args.slice(2));
8345
+ return handleApprovePlan(projectRoot, args[1], { ...flags, dryRun: flags.dryRun || approveArgs.dryRun }, approveArgs.sprintName);
7775
8346
  }
7776
8347
  const parsedArgs = parsePlanArgs(args);
7777
8348
  if (parsedArgs.error) {
@@ -7798,7 +8369,7 @@ async function planCommand(projectRoot, args, flags = {}) {
7798
8369
  }
7799
8370
  function handleListPlans(projectRoot) {
7800
8371
  const dir = getPlansDir(projectRoot);
7801
- if (!existsSync13(dir)) {
8372
+ if (!existsSync16(dir)) {
7802
8373
  process.stderr.write(`${dim("No saved plans yet.")}
7803
8374
  `);
7804
8375
  return;
@@ -7816,7 +8387,7 @@ ${bold("Saved Plans:")}
7816
8387
  for (const file of files) {
7817
8388
  const id = file.replace(".json", "");
7818
8389
  try {
7819
- const content = readFileSync10(join14(dir, file), "utf-8");
8390
+ const content = readFileSync12(join16(dir, file), "utf-8");
7820
8391
  const plan = JSON.parse(content);
7821
8392
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
7822
8393
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -7829,7 +8400,7 @@ ${bold("Saved Plans:")}
7829
8400
  }
7830
8401
  process.stderr.write(`
7831
8402
  `);
7832
- 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>")}
7833
8404
 
7834
8405
  `);
7835
8406
  }
@@ -7870,11 +8441,11 @@ ${bold("Plan:")} ${cyan(plan.directive)}
7870
8441
  }
7871
8442
  process.stderr.write(`
7872
8443
  `);
7873
- process.stderr.write(` Approve: ${bold(`locus plan approve ${plan.id.slice(0, 8)}`)}
8444
+ process.stderr.write(` Approve: ${bold(`locus plan approve ${plan.id.slice(0, 8)}`)} ${dim("(--sprint <name> to assign to a sprint)")}
7874
8445
 
7875
8446
  `);
7876
8447
  }
7877
- async function handleApprovePlan(projectRoot, id, flags) {
8448
+ async function handleApprovePlan(projectRoot, id, flags, sprintOverride) {
7878
8449
  if (!id) {
7879
8450
  process.stderr.write(`${red("✗")} Please provide a plan ID.
7880
8451
  `);
@@ -7898,11 +8469,12 @@ async function handleApprovePlan(projectRoot, id, flags) {
7898
8469
  return;
7899
8470
  }
7900
8471
  const config = loadConfig(projectRoot);
8472
+ const sprintName = sprintOverride ?? plan.sprint ?? undefined;
7901
8473
  process.stderr.write(`
7902
- ${bold("Approving plan:")} ${cyan(plan.directive)}
8474
+ ${bold("Approving plan:")}
7903
8475
  `);
7904
- if (plan.sprint) {
7905
- process.stderr.write(` ${dim(`Sprint: ${plan.sprint}`)}
8476
+ if (sprintName) {
8477
+ process.stderr.write(` ${dim(`Sprint: ${sprintName}`)}
7906
8478
  `);
7907
8479
  }
7908
8480
  process.stderr.write(`
@@ -7921,12 +8493,12 @@ ${bold("Approving plan:")} ${cyan(plan.directive)}
7921
8493
  `);
7922
8494
  return;
7923
8495
  }
7924
- await createPlannedIssues(projectRoot, config, plan.issues, plan.sprint ?? undefined);
8496
+ await createPlannedIssues(projectRoot, config, plan.issues, sprintName);
7925
8497
  }
7926
8498
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
7927
8499
  const id = generateId();
7928
8500
  const plansDir = ensurePlansDir(projectRoot);
7929
- const planPath = join14(plansDir, `${id}.json`);
8501
+ const planPath = join16(plansDir, `${id}.json`);
7930
8502
  const planPathRelative = `.locus/plans/${id}.json`;
7931
8503
  const displayDirective = directive;
7932
8504
  process.stderr.write(`
@@ -7958,7 +8530,7 @@ ${red("✗")} Planning failed: ${aiResult.error}
7958
8530
  `);
7959
8531
  return;
7960
8532
  }
7961
- if (!existsSync13(planPath)) {
8533
+ if (!existsSync16(planPath)) {
7962
8534
  process.stderr.write(`
7963
8535
  ${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
7964
8536
  `);
@@ -7968,7 +8540,7 @@ ${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
7968
8540
  }
7969
8541
  let plan;
7970
8542
  try {
7971
- const content = readFileSync10(planPath, "utf-8");
8543
+ const content = readFileSync12(planPath, "utf-8");
7972
8544
  plan = JSON.parse(content);
7973
8545
  } catch {
7974
8546
  process.stderr.write(`
@@ -7990,7 +8562,7 @@ ${yellow("⚠")} Plan file has no issues.
7990
8562
  plan.sprint = sprintName;
7991
8563
  if (!plan.createdAt)
7992
8564
  plan.createdAt = new Date().toISOString();
7993
- writeFileSync8(planPath, JSON.stringify(plan, null, 2), "utf-8");
8565
+ writeFileSync9(planPath, JSON.stringify(plan, null, 2), "utf-8");
7994
8566
  process.stderr.write(`
7995
8567
  ${bold("Plan saved:")} ${cyan(id)}
7996
8568
 
@@ -8123,16 +8695,16 @@ function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, pla
8123
8695
  parts.push(`SPRINT: ${sprintName}`);
8124
8696
  }
8125
8697
  parts.push("");
8126
- const locusPath = join14(projectRoot, "LOCUS.md");
8127
- if (existsSync13(locusPath)) {
8128
- const content = readFileSync10(locusPath, "utf-8");
8698
+ const locusPath = join16(projectRoot, "LOCUS.md");
8699
+ if (existsSync16(locusPath)) {
8700
+ const content = readFileSync12(locusPath, "utf-8");
8129
8701
  parts.push("PROJECT CONTEXT (LOCUS.md):");
8130
8702
  parts.push(content.slice(0, 3000));
8131
8703
  parts.push("");
8132
8704
  }
8133
- const learningsPath = join14(projectRoot, ".locus", "LEARNINGS.md");
8134
- if (existsSync13(learningsPath)) {
8135
- 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");
8136
8708
  parts.push("PAST LEARNINGS:");
8137
8709
  parts.push(content.slice(0, 2000));
8138
8710
  parts.push("");
@@ -8309,8 +8881,8 @@ __export(exports_review, {
8309
8881
  reviewCommand: () => reviewCommand
8310
8882
  });
8311
8883
  import { execSync as execSync13 } from "node:child_process";
8312
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
8313
- 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";
8314
8886
  function printHelp2() {
8315
8887
  process.stderr.write(`
8316
8888
  ${bold("locus review")} — AI-powered code review
@@ -8467,9 +9039,9 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
8467
9039
  const parts = [];
8468
9040
  parts.push(`You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.`);
8469
9041
  parts.push("");
8470
- const locusPath = join15(projectRoot, "LOCUS.md");
8471
- if (existsSync14(locusPath)) {
8472
- const content = readFileSync11(locusPath, "utf-8");
9042
+ const locusPath = join17(projectRoot, "LOCUS.md");
9043
+ if (existsSync17(locusPath)) {
9044
+ const content = readFileSync13(locusPath, "utf-8");
8473
9045
  parts.push("PROJECT CONTEXT:");
8474
9046
  parts.push(content.slice(0, 2000));
8475
9047
  parts.push("");
@@ -8771,14 +9343,14 @@ __export(exports_discuss, {
8771
9343
  discussCommand: () => discussCommand
8772
9344
  });
8773
9345
  import {
8774
- existsSync as existsSync15,
8775
- mkdirSync as mkdirSync11,
9346
+ existsSync as existsSync18,
9347
+ mkdirSync as mkdirSync12,
8776
9348
  readdirSync as readdirSync8,
8777
- readFileSync as readFileSync12,
9349
+ readFileSync as readFileSync14,
8778
9350
  unlinkSync as unlinkSync5,
8779
- writeFileSync as writeFileSync9
9351
+ writeFileSync as writeFileSync10
8780
9352
  } from "node:fs";
8781
- import { join as join16 } from "node:path";
9353
+ import { join as join18 } from "node:path";
8782
9354
  function printHelp4() {
8783
9355
  process.stderr.write(`
8784
9356
  ${bold("locus discuss")} — AI-powered architectural discussions
@@ -8800,12 +9372,12 @@ ${bold("Examples:")}
8800
9372
  `);
8801
9373
  }
8802
9374
  function getDiscussionsDir(projectRoot) {
8803
- return join16(projectRoot, ".locus", "discussions");
9375
+ return join18(projectRoot, ".locus", "discussions");
8804
9376
  }
8805
9377
  function ensureDiscussionsDir(projectRoot) {
8806
9378
  const dir = getDiscussionsDir(projectRoot);
8807
- if (!existsSync15(dir)) {
8808
- mkdirSync11(dir, { recursive: true });
9379
+ if (!existsSync18(dir)) {
9380
+ mkdirSync12(dir, { recursive: true });
8809
9381
  }
8810
9382
  return dir;
8811
9383
  }
@@ -8839,7 +9411,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
8839
9411
  }
8840
9412
  function listDiscussions(projectRoot) {
8841
9413
  const dir = getDiscussionsDir(projectRoot);
8842
- if (!existsSync15(dir)) {
9414
+ if (!existsSync18(dir)) {
8843
9415
  process.stderr.write(`${dim("No discussions yet.")}
8844
9416
  `);
8845
9417
  return;
@@ -8856,7 +9428,7 @@ ${bold("Discussions:")}
8856
9428
  `);
8857
9429
  for (const file of files) {
8858
9430
  const id = file.replace(".md", "");
8859
- const content = readFileSync12(join16(dir, file), "utf-8");
9431
+ const content = readFileSync14(join18(dir, file), "utf-8");
8860
9432
  const titleMatch = content.match(/^#\s+(.+)/m);
8861
9433
  const title = titleMatch ? titleMatch[1] : id;
8862
9434
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -8874,7 +9446,7 @@ function showDiscussion(projectRoot, id) {
8874
9446
  return;
8875
9447
  }
8876
9448
  const dir = getDiscussionsDir(projectRoot);
8877
- if (!existsSync15(dir)) {
9449
+ if (!existsSync18(dir)) {
8878
9450
  process.stderr.write(`${red("✗")} No discussions found.
8879
9451
  `);
8880
9452
  return;
@@ -8886,7 +9458,7 @@ function showDiscussion(projectRoot, id) {
8886
9458
  `);
8887
9459
  return;
8888
9460
  }
8889
- const content = readFileSync12(join16(dir, match), "utf-8");
9461
+ const content = readFileSync14(join18(dir, match), "utf-8");
8890
9462
  process.stdout.write(`${content}
8891
9463
  `);
8892
9464
  }
@@ -8897,7 +9469,7 @@ function deleteDiscussion(projectRoot, id) {
8897
9469
  return;
8898
9470
  }
8899
9471
  const dir = getDiscussionsDir(projectRoot);
8900
- if (!existsSync15(dir)) {
9472
+ if (!existsSync18(dir)) {
8901
9473
  process.stderr.write(`${red("✗")} No discussions found.
8902
9474
  `);
8903
9475
  return;
@@ -8909,7 +9481,7 @@ function deleteDiscussion(projectRoot, id) {
8909
9481
  `);
8910
9482
  return;
8911
9483
  }
8912
- unlinkSync5(join16(dir, match));
9484
+ unlinkSync5(join18(dir, match));
8913
9485
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
8914
9486
  `);
8915
9487
  }
@@ -8922,7 +9494,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
8922
9494
  return;
8923
9495
  }
8924
9496
  const dir = getDiscussionsDir(projectRoot);
8925
- if (!existsSync15(dir)) {
9497
+ if (!existsSync18(dir)) {
8926
9498
  process.stderr.write(`${red("✗")} No discussions found.
8927
9499
  `);
8928
9500
  return;
@@ -8934,7 +9506,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
8934
9506
  `);
8935
9507
  return;
8936
9508
  }
8937
- const content = readFileSync12(join16(dir, match), "utf-8");
9509
+ const content = readFileSync14(join18(dir, match), "utf-8");
8938
9510
  const titleMatch = content.match(/^#\s+(.+)/m);
8939
9511
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
8940
9512
  await planCommand(projectRoot, [
@@ -9046,7 +9618,7 @@ ${turn.content}`;
9046
9618
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
9047
9619
  ].join(`
9048
9620
  `);
9049
- writeFileSync9(join16(dir, `${id}.md`), markdown, "utf-8");
9621
+ writeFileSync10(join18(dir, `${id}.md`), markdown, "utf-8");
9050
9622
  process.stderr.write(`
9051
9623
  ${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
9052
9624
  `);
@@ -9060,16 +9632,16 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
9060
9632
  const parts = [];
9061
9633
  parts.push(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
9062
9634
  parts.push("");
9063
- const locusPath = join16(projectRoot, "LOCUS.md");
9064
- if (existsSync15(locusPath)) {
9065
- const content = readFileSync12(locusPath, "utf-8");
9635
+ const locusPath = join18(projectRoot, "LOCUS.md");
9636
+ if (existsSync18(locusPath)) {
9637
+ const content = readFileSync14(locusPath, "utf-8");
9066
9638
  parts.push("PROJECT CONTEXT:");
9067
9639
  parts.push(content.slice(0, 3000));
9068
9640
  parts.push("");
9069
9641
  }
9070
- const learningsPath = join16(projectRoot, ".locus", "LEARNINGS.md");
9071
- if (existsSync15(learningsPath)) {
9072
- 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");
9073
9645
  parts.push("PAST LEARNINGS:");
9074
9646
  parts.push(content.slice(0, 2000));
9075
9647
  parts.push("");
@@ -9128,8 +9700,8 @@ __export(exports_artifacts, {
9128
9700
  formatDate: () => formatDate2,
9129
9701
  artifactsCommand: () => artifactsCommand
9130
9702
  });
9131
- import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
9132
- 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";
9133
9705
  function printHelp5() {
9134
9706
  process.stderr.write(`
9135
9707
  ${bold("locus artifacts")} — View and manage AI-generated artifacts
@@ -9149,14 +9721,14 @@ ${dim("Artifact names support partial matching.")}
9149
9721
  `);
9150
9722
  }
9151
9723
  function getArtifactsDir(projectRoot) {
9152
- return join17(projectRoot, ".locus", "artifacts");
9724
+ return join19(projectRoot, ".locus", "artifacts");
9153
9725
  }
9154
9726
  function listArtifacts(projectRoot) {
9155
9727
  const dir = getArtifactsDir(projectRoot);
9156
- if (!existsSync16(dir))
9728
+ if (!existsSync19(dir))
9157
9729
  return [];
9158
9730
  return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
9159
- const filePath = join17(dir, fileName);
9731
+ const filePath = join19(dir, fileName);
9160
9732
  const stat = statSync4(filePath);
9161
9733
  return {
9162
9734
  name: fileName.replace(/\.md$/, ""),
@@ -9169,12 +9741,12 @@ function listArtifacts(projectRoot) {
9169
9741
  function readArtifact(projectRoot, name) {
9170
9742
  const dir = getArtifactsDir(projectRoot);
9171
9743
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
9172
- const filePath = join17(dir, fileName);
9173
- if (!existsSync16(filePath))
9744
+ const filePath = join19(dir, fileName);
9745
+ if (!existsSync19(filePath))
9174
9746
  return null;
9175
9747
  const stat = statSync4(filePath);
9176
9748
  return {
9177
- content: readFileSync13(filePath, "utf-8"),
9749
+ content: readFileSync15(filePath, "utf-8"),
9178
9750
  info: {
9179
9751
  name: fileName.replace(/\.md$/, ""),
9180
9752
  fileName,
@@ -9338,17 +9910,17 @@ init_context();
9338
9910
  init_logger();
9339
9911
  init_rate_limiter();
9340
9912
  init_terminal();
9341
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
9342
- 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";
9343
9915
  import { fileURLToPath } from "node:url";
9344
9916
  function getCliVersion() {
9345
9917
  const fallbackVersion = "0.0.0";
9346
- const packageJsonPath = join18(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
9347
- if (!existsSync17(packageJsonPath)) {
9918
+ const packageJsonPath = join20(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
9919
+ if (!existsSync20(packageJsonPath)) {
9348
9920
  return fallbackVersion;
9349
9921
  }
9350
9922
  try {
9351
- const parsed = JSON.parse(readFileSync14(packageJsonPath, "utf-8"));
9923
+ const parsed = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
9352
9924
  return parsed.version ?? fallbackVersion;
9353
9925
  } catch {
9354
9926
  return fallbackVersion;
@@ -9366,7 +9938,9 @@ function parseArgs(argv) {
9366
9938
  clean: false,
9367
9939
  resume: false,
9368
9940
  dryRun: false,
9369
- check: false
9941
+ check: false,
9942
+ upgrade: false,
9943
+ list: false
9370
9944
  };
9371
9945
  const positional = [];
9372
9946
  let i = 0;
@@ -9386,8 +9960,25 @@ function parseArgs(argv) {
9386
9960
  flags.help = true;
9387
9961
  break;
9388
9962
  case "--version":
9389
- case "-V":
9390
- 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;
9391
9982
  break;
9392
9983
  case "--json-stream":
9393
9984
  flags.jsonStream = true;
@@ -9456,6 +10047,10 @@ ${bold("Commands:")}
9456
10047
  ${cyan("status")} Dashboard view of current state
9457
10048
  ${cyan("config")} View and manage settings
9458
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
9459
10054
  ${cyan("upgrade")} Check for and install updates
9460
10055
 
9461
10056
  ${bold("Options:")}
@@ -9501,7 +10096,7 @@ async function main() {
9501
10096
  try {
9502
10097
  const root = getGitRoot(cwd);
9503
10098
  if (isInitialized(root)) {
9504
- logDir = join18(root, ".locus", "logs");
10099
+ logDir = join20(root, ".locus", "logs");
9505
10100
  getRateLimiter(root);
9506
10101
  }
9507
10102
  } catch {}
@@ -9531,6 +10126,43 @@ async function main() {
9531
10126
  logger.destroy();
9532
10127
  return;
9533
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
+ }
9534
10166
  let projectRoot;
9535
10167
  try {
9536
10168
  projectRoot = getGitRoot(cwd);