@locusai/cli 0.17.10 → 0.17.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/locus.js +737 -157
- 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: (
|
|
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 =
|
|
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
|
|
2319
|
+
existsSync as existsSync9,
|
|
1826
2320
|
readdirSync as readdirSync2,
|
|
1827
|
-
readFileSync as
|
|
2321
|
+
readFileSync as readFileSync7,
|
|
1828
2322
|
statSync as statSync2,
|
|
1829
2323
|
unlinkSync as unlinkSync2
|
|
1830
2324
|
} from "node:fs";
|
|
1831
|
-
import { join as
|
|
2325
|
+
import { join as join8 } from "node:path";
|
|
1832
2326
|
async function logsCommand(cwd, options) {
|
|
1833
|
-
const logsDir =
|
|
1834
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1890
|
-
if (
|
|
1891
|
-
const content =
|
|
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 (!
|
|
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 =
|
|
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) =>
|
|
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
|
|
2251
|
-
import { homedir as
|
|
2252
|
-
import { basename, extname, join as
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
2418
|
-
|
|
2911
|
+
if (!existsSync10(STABLE_DIR)) {
|
|
2912
|
+
mkdirSync7(STABLE_DIR, { recursive: true });
|
|
2419
2913
|
}
|
|
2420
|
-
const dest =
|
|
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 =
|
|
2934
|
+
STABLE_DIR = join9(tmpdir(), "locus-images");
|
|
2441
2935
|
PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
|
|
2442
2936
|
});
|
|
2443
2937
|
|
|
@@ -3112,10 +3606,13 @@ function listenForInterrupt(onInterrupt, onForceExit) {
|
|
|
3112
3606
|
const wasRaw = stdin.isRaw;
|
|
3113
3607
|
stdin.setRawMode(true);
|
|
3114
3608
|
stdin.resume();
|
|
3609
|
+
process.stderr.write(ENABLE_BRACKETED_PASTE);
|
|
3115
3610
|
let interrupted = false;
|
|
3116
3611
|
let interruptTime = 0;
|
|
3117
3612
|
const handler = (data) => {
|
|
3118
3613
|
const seq = data.toString();
|
|
3614
|
+
if (seq.startsWith(PASTE_START) || seq === PASTE_END)
|
|
3615
|
+
return;
|
|
3119
3616
|
if (seq === "\x1B" || seq === "\x03") {
|
|
3120
3617
|
const now = Date.now();
|
|
3121
3618
|
if (interrupted && now - interruptTime < 2000 && onForceExit) {
|
|
@@ -3135,6 +3632,7 @@ ${dim("Press")} ${yellow("ESC")} ${dim("again to force exit")}\r
|
|
|
3135
3632
|
stdin.on("data", handler);
|
|
3136
3633
|
return () => {
|
|
3137
3634
|
stdin.removeListener("data", handler);
|
|
3635
|
+
process.stderr.write(DISABLE_BRACKETED_PASTE);
|
|
3138
3636
|
if (wasRaw !== undefined && stdin.isTTY) {
|
|
3139
3637
|
stdin.setRawMode(wasRaw);
|
|
3140
3638
|
}
|
|
@@ -3202,7 +3700,7 @@ var init_input_handler = __esm(() => {
|
|
|
3202
3700
|
});
|
|
3203
3701
|
|
|
3204
3702
|
// src/ai/claude.ts
|
|
3205
|
-
import { execSync as execSync4, spawn } from "node:child_process";
|
|
3703
|
+
import { execSync as execSync4, spawn as spawn2 } from "node:child_process";
|
|
3206
3704
|
|
|
3207
3705
|
class ClaudeRunner {
|
|
3208
3706
|
name = "claude";
|
|
@@ -3248,7 +3746,7 @@ class ClaudeRunner {
|
|
|
3248
3746
|
const env = { ...process.env };
|
|
3249
3747
|
delete env.CLAUDECODE;
|
|
3250
3748
|
delete env.CLAUDE_CODE;
|
|
3251
|
-
this.process =
|
|
3749
|
+
this.process = spawn2("claude", args, {
|
|
3252
3750
|
cwd: options.cwd,
|
|
3253
3751
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3254
3752
|
env
|
|
@@ -3330,7 +3828,7 @@ var init_claude = __esm(() => {
|
|
|
3330
3828
|
});
|
|
3331
3829
|
|
|
3332
3830
|
// src/ai/codex.ts
|
|
3333
|
-
import { execSync as execSync5, spawn as
|
|
3831
|
+
import { execSync as execSync5, spawn as spawn3 } from "node:child_process";
|
|
3334
3832
|
function buildCodexArgs(model) {
|
|
3335
3833
|
const args = ["exec", "--full-auto", "--skip-git-repo-check"];
|
|
3336
3834
|
if (model) {
|
|
@@ -3374,7 +3872,7 @@ class CodexRunner {
|
|
|
3374
3872
|
return new Promise((resolve2) => {
|
|
3375
3873
|
let output = "";
|
|
3376
3874
|
let errorOutput = "";
|
|
3377
|
-
this.process =
|
|
3875
|
+
this.process = spawn3("codex", args, {
|
|
3378
3876
|
cwd: options.cwd,
|
|
3379
3877
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3380
3878
|
env: { ...process.env }
|
|
@@ -4964,8 +5462,8 @@ var init_sprint = __esm(() => {
|
|
|
4964
5462
|
|
|
4965
5463
|
// src/core/prompt-builder.ts
|
|
4966
5464
|
import { execSync as execSync6 } from "node:child_process";
|
|
4967
|
-
import { existsSync as
|
|
4968
|
-
import { join as
|
|
5465
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
|
|
5466
|
+
import { join as join10 } from "node:path";
|
|
4969
5467
|
function buildExecutionPrompt(ctx) {
|
|
4970
5468
|
const sections = [];
|
|
4971
5469
|
sections.push(buildSystemContext(ctx.projectRoot));
|
|
@@ -4995,13 +5493,13 @@ function buildFeedbackPrompt(ctx) {
|
|
|
4995
5493
|
}
|
|
4996
5494
|
function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
|
|
4997
5495
|
const sections = [];
|
|
4998
|
-
const locusmd = readFileSafe(
|
|
5496
|
+
const locusmd = readFileSafe(join10(projectRoot, "LOCUS.md"));
|
|
4999
5497
|
if (locusmd) {
|
|
5000
5498
|
sections.push(`# Project Instructions
|
|
5001
5499
|
|
|
5002
5500
|
${locusmd}`);
|
|
5003
5501
|
}
|
|
5004
|
-
const learnings = readFileSafe(
|
|
5502
|
+
const learnings = readFileSafe(join10(projectRoot, ".locus", "LEARNINGS.md"));
|
|
5005
5503
|
if (learnings) {
|
|
5006
5504
|
sections.push(`# Past Learnings
|
|
5007
5505
|
|
|
@@ -5027,24 +5525,24 @@ ${userMessage}`);
|
|
|
5027
5525
|
}
|
|
5028
5526
|
function buildSystemContext(projectRoot) {
|
|
5029
5527
|
const parts = ["# System Context"];
|
|
5030
|
-
const locusmd = readFileSafe(
|
|
5528
|
+
const locusmd = readFileSafe(join10(projectRoot, "LOCUS.md"));
|
|
5031
5529
|
if (locusmd) {
|
|
5032
5530
|
parts.push(`## Project Instructions (LOCUS.md)
|
|
5033
5531
|
|
|
5034
5532
|
${locusmd}`);
|
|
5035
5533
|
}
|
|
5036
|
-
const learnings = readFileSafe(
|
|
5534
|
+
const learnings = readFileSafe(join10(projectRoot, ".locus", "LEARNINGS.md"));
|
|
5037
5535
|
if (learnings) {
|
|
5038
5536
|
parts.push(`## Past Learnings
|
|
5039
5537
|
|
|
5040
5538
|
${learnings}`);
|
|
5041
5539
|
}
|
|
5042
|
-
const discussionsDir =
|
|
5043
|
-
if (
|
|
5540
|
+
const discussionsDir = join10(projectRoot, ".locus", "discussions");
|
|
5541
|
+
if (existsSync11(discussionsDir)) {
|
|
5044
5542
|
try {
|
|
5045
5543
|
const files = readdirSync3(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
5046
5544
|
for (const file of files) {
|
|
5047
|
-
const content = readFileSafe(
|
|
5545
|
+
const content = readFileSafe(join10(discussionsDir, file));
|
|
5048
5546
|
if (content) {
|
|
5049
5547
|
parts.push(`## Discussion: ${file.replace(".md", "")}
|
|
5050
5548
|
|
|
@@ -5190,9 +5688,9 @@ function buildFeedbackInstructions() {
|
|
|
5190
5688
|
}
|
|
5191
5689
|
function readFileSafe(path) {
|
|
5192
5690
|
try {
|
|
5193
|
-
if (!
|
|
5691
|
+
if (!existsSync11(path))
|
|
5194
5692
|
return null;
|
|
5195
|
-
return
|
|
5693
|
+
return readFileSync8(path, "utf-8");
|
|
5196
5694
|
} catch {
|
|
5197
5695
|
return null;
|
|
5198
5696
|
}
|
|
@@ -5622,7 +6120,7 @@ var init_commands = __esm(() => {
|
|
|
5622
6120
|
|
|
5623
6121
|
// src/repl/completions.ts
|
|
5624
6122
|
import { readdirSync as readdirSync4 } from "node:fs";
|
|
5625
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
6123
|
+
import { basename as basename2, dirname as dirname3, join as join11 } from "node:path";
|
|
5626
6124
|
|
|
5627
6125
|
class SlashCommandCompletion {
|
|
5628
6126
|
commands;
|
|
@@ -5677,7 +6175,7 @@ class FilePathCompletion {
|
|
|
5677
6175
|
}
|
|
5678
6176
|
findMatches(partial) {
|
|
5679
6177
|
try {
|
|
5680
|
-
const dir = partial.includes("/") ?
|
|
6178
|
+
const dir = partial.includes("/") ? join11(this.projectRoot, dirname3(partial)) : this.projectRoot;
|
|
5681
6179
|
const prefix = basename2(partial);
|
|
5682
6180
|
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
5683
6181
|
return entries.filter((e) => {
|
|
@@ -5713,14 +6211,14 @@ class CombinedCompletion {
|
|
|
5713
6211
|
var init_completions = () => {};
|
|
5714
6212
|
|
|
5715
6213
|
// src/repl/input-history.ts
|
|
5716
|
-
import { existsSync as
|
|
5717
|
-
import { dirname as dirname4, join as
|
|
6214
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6215
|
+
import { dirname as dirname4, join as join12 } from "node:path";
|
|
5718
6216
|
|
|
5719
6217
|
class InputHistory {
|
|
5720
6218
|
entries = [];
|
|
5721
6219
|
filePath;
|
|
5722
6220
|
constructor(projectRoot) {
|
|
5723
|
-
this.filePath =
|
|
6221
|
+
this.filePath = join12(projectRoot, ".locus", "sessions", ".input-history");
|
|
5724
6222
|
this.load();
|
|
5725
6223
|
}
|
|
5726
6224
|
add(text) {
|
|
@@ -5759,9 +6257,9 @@ class InputHistory {
|
|
|
5759
6257
|
}
|
|
5760
6258
|
load() {
|
|
5761
6259
|
try {
|
|
5762
|
-
if (!
|
|
6260
|
+
if (!existsSync12(this.filePath))
|
|
5763
6261
|
return;
|
|
5764
|
-
const content =
|
|
6262
|
+
const content = readFileSync9(this.filePath, "utf-8");
|
|
5765
6263
|
this.entries = content.split(`
|
|
5766
6264
|
`).map((line) => this.unescape(line)).filter(Boolean);
|
|
5767
6265
|
} catch {}
|
|
@@ -5769,12 +6267,12 @@ class InputHistory {
|
|
|
5769
6267
|
save() {
|
|
5770
6268
|
try {
|
|
5771
6269
|
const dir = dirname4(this.filePath);
|
|
5772
|
-
if (!
|
|
5773
|
-
|
|
6270
|
+
if (!existsSync12(dir)) {
|
|
6271
|
+
mkdirSync8(dir, { recursive: true });
|
|
5774
6272
|
}
|
|
5775
6273
|
const content = this.entries.map((e) => this.escape(e)).join(`
|
|
5776
6274
|
`);
|
|
5777
|
-
|
|
6275
|
+
writeFileSync6(this.filePath, content, "utf-8");
|
|
5778
6276
|
} catch {}
|
|
5779
6277
|
}
|
|
5780
6278
|
escape(text) {
|
|
@@ -5801,21 +6299,21 @@ var init_model_config = __esm(() => {
|
|
|
5801
6299
|
// src/repl/session-manager.ts
|
|
5802
6300
|
import { randomBytes } from "node:crypto";
|
|
5803
6301
|
import {
|
|
5804
|
-
existsSync as
|
|
5805
|
-
mkdirSync as
|
|
6302
|
+
existsSync as existsSync13,
|
|
6303
|
+
mkdirSync as mkdirSync9,
|
|
5806
6304
|
readdirSync as readdirSync5,
|
|
5807
|
-
readFileSync as
|
|
6305
|
+
readFileSync as readFileSync10,
|
|
5808
6306
|
unlinkSync as unlinkSync3,
|
|
5809
|
-
writeFileSync as
|
|
6307
|
+
writeFileSync as writeFileSync7
|
|
5810
6308
|
} from "node:fs";
|
|
5811
|
-
import { basename as basename3, join as
|
|
6309
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
5812
6310
|
|
|
5813
6311
|
class SessionManager {
|
|
5814
6312
|
sessionsDir;
|
|
5815
6313
|
constructor(projectRoot) {
|
|
5816
|
-
this.sessionsDir =
|
|
5817
|
-
if (!
|
|
5818
|
-
|
|
6314
|
+
this.sessionsDir = join13(projectRoot, ".locus", "sessions");
|
|
6315
|
+
if (!existsSync13(this.sessionsDir)) {
|
|
6316
|
+
mkdirSync9(this.sessionsDir, { recursive: true });
|
|
5819
6317
|
}
|
|
5820
6318
|
}
|
|
5821
6319
|
create(options) {
|
|
@@ -5840,14 +6338,14 @@ class SessionManager {
|
|
|
5840
6338
|
}
|
|
5841
6339
|
isPersisted(sessionOrId) {
|
|
5842
6340
|
const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
|
|
5843
|
-
return
|
|
6341
|
+
return existsSync13(this.getSessionPath(sessionId));
|
|
5844
6342
|
}
|
|
5845
6343
|
load(idOrPrefix) {
|
|
5846
6344
|
const files = this.listSessionFiles();
|
|
5847
6345
|
const exactPath = this.getSessionPath(idOrPrefix);
|
|
5848
|
-
if (
|
|
6346
|
+
if (existsSync13(exactPath)) {
|
|
5849
6347
|
try {
|
|
5850
|
-
return JSON.parse(
|
|
6348
|
+
return JSON.parse(readFileSync10(exactPath, "utf-8"));
|
|
5851
6349
|
} catch {
|
|
5852
6350
|
return null;
|
|
5853
6351
|
}
|
|
@@ -5855,7 +6353,7 @@ class SessionManager {
|
|
|
5855
6353
|
const matches = files.filter((f) => basename3(f, ".json").startsWith(idOrPrefix));
|
|
5856
6354
|
if (matches.length === 1) {
|
|
5857
6355
|
try {
|
|
5858
|
-
return JSON.parse(
|
|
6356
|
+
return JSON.parse(readFileSync10(matches[0], "utf-8"));
|
|
5859
6357
|
} catch {
|
|
5860
6358
|
return null;
|
|
5861
6359
|
}
|
|
@@ -5868,7 +6366,7 @@ class SessionManager {
|
|
|
5868
6366
|
save(session) {
|
|
5869
6367
|
session.updated = new Date().toISOString();
|
|
5870
6368
|
const path = this.getSessionPath(session.id);
|
|
5871
|
-
|
|
6369
|
+
writeFileSync7(path, `${JSON.stringify(session, null, 2)}
|
|
5872
6370
|
`, "utf-8");
|
|
5873
6371
|
}
|
|
5874
6372
|
addMessage(session, message) {
|
|
@@ -5880,7 +6378,7 @@ class SessionManager {
|
|
|
5880
6378
|
const sessions = [];
|
|
5881
6379
|
for (const file of files) {
|
|
5882
6380
|
try {
|
|
5883
|
-
const session = JSON.parse(
|
|
6381
|
+
const session = JSON.parse(readFileSync10(file, "utf-8"));
|
|
5884
6382
|
sessions.push({
|
|
5885
6383
|
id: session.id,
|
|
5886
6384
|
created: session.created,
|
|
@@ -5895,7 +6393,7 @@ class SessionManager {
|
|
|
5895
6393
|
}
|
|
5896
6394
|
delete(sessionId) {
|
|
5897
6395
|
const path = this.getSessionPath(sessionId);
|
|
5898
|
-
if (
|
|
6396
|
+
if (existsSync13(path)) {
|
|
5899
6397
|
unlinkSync3(path);
|
|
5900
6398
|
return true;
|
|
5901
6399
|
}
|
|
@@ -5907,7 +6405,7 @@ class SessionManager {
|
|
|
5907
6405
|
let pruned = 0;
|
|
5908
6406
|
const withStats = files.map((f) => {
|
|
5909
6407
|
try {
|
|
5910
|
-
const session = JSON.parse(
|
|
6408
|
+
const session = JSON.parse(readFileSync10(f, "utf-8"));
|
|
5911
6409
|
return { path: f, updated: new Date(session.updated).getTime() };
|
|
5912
6410
|
} catch {
|
|
5913
6411
|
return { path: f, updated: 0 };
|
|
@@ -5925,7 +6423,7 @@ class SessionManager {
|
|
|
5925
6423
|
const remaining = withStats.length - pruned;
|
|
5926
6424
|
if (remaining > MAX_SESSIONS) {
|
|
5927
6425
|
const toRemove = remaining - MAX_SESSIONS;
|
|
5928
|
-
const alive = withStats.filter((e) =>
|
|
6426
|
+
const alive = withStats.filter((e) => existsSync13(e.path));
|
|
5929
6427
|
for (let i = 0;i < toRemove && i < alive.length; i++) {
|
|
5930
6428
|
try {
|
|
5931
6429
|
unlinkSync3(alive[i].path);
|
|
@@ -5940,7 +6438,7 @@ class SessionManager {
|
|
|
5940
6438
|
}
|
|
5941
6439
|
listSessionFiles() {
|
|
5942
6440
|
try {
|
|
5943
|
-
return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) =>
|
|
6441
|
+
return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join13(this.sessionsDir, f));
|
|
5944
6442
|
} catch {
|
|
5945
6443
|
return [];
|
|
5946
6444
|
}
|
|
@@ -5949,7 +6447,7 @@ class SessionManager {
|
|
|
5949
6447
|
return randomBytes(6).toString("hex");
|
|
5950
6448
|
}
|
|
5951
6449
|
getSessionPath(sessionId) {
|
|
5952
|
-
return
|
|
6450
|
+
return join13(this.sessionsDir, `${sessionId}.json`);
|
|
5953
6451
|
}
|
|
5954
6452
|
}
|
|
5955
6453
|
var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
|
|
@@ -6734,22 +7232,22 @@ var init_conflict = __esm(() => {
|
|
|
6734
7232
|
|
|
6735
7233
|
// src/core/run-state.ts
|
|
6736
7234
|
import {
|
|
6737
|
-
existsSync as
|
|
6738
|
-
mkdirSync as
|
|
6739
|
-
readFileSync as
|
|
7235
|
+
existsSync as existsSync14,
|
|
7236
|
+
mkdirSync as mkdirSync10,
|
|
7237
|
+
readFileSync as readFileSync11,
|
|
6740
7238
|
unlinkSync as unlinkSync4,
|
|
6741
|
-
writeFileSync as
|
|
7239
|
+
writeFileSync as writeFileSync8
|
|
6742
7240
|
} from "node:fs";
|
|
6743
|
-
import { dirname as dirname5, join as
|
|
7241
|
+
import { dirname as dirname5, join as join14 } from "node:path";
|
|
6744
7242
|
function getRunStatePath(projectRoot) {
|
|
6745
|
-
return
|
|
7243
|
+
return join14(projectRoot, ".locus", "run-state.json");
|
|
6746
7244
|
}
|
|
6747
7245
|
function loadRunState(projectRoot) {
|
|
6748
7246
|
const path = getRunStatePath(projectRoot);
|
|
6749
|
-
if (!
|
|
7247
|
+
if (!existsSync14(path))
|
|
6750
7248
|
return null;
|
|
6751
7249
|
try {
|
|
6752
|
-
return JSON.parse(
|
|
7250
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
6753
7251
|
} catch {
|
|
6754
7252
|
getLogger().warn("Corrupted run-state.json, ignoring");
|
|
6755
7253
|
return null;
|
|
@@ -6758,15 +7256,15 @@ function loadRunState(projectRoot) {
|
|
|
6758
7256
|
function saveRunState(projectRoot, state) {
|
|
6759
7257
|
const path = getRunStatePath(projectRoot);
|
|
6760
7258
|
const dir = dirname5(path);
|
|
6761
|
-
if (!
|
|
6762
|
-
|
|
7259
|
+
if (!existsSync14(dir)) {
|
|
7260
|
+
mkdirSync10(dir, { recursive: true });
|
|
6763
7261
|
}
|
|
6764
|
-
|
|
7262
|
+
writeFileSync8(path, `${JSON.stringify(state, null, 2)}
|
|
6765
7263
|
`, "utf-8");
|
|
6766
7264
|
}
|
|
6767
7265
|
function clearRunState(projectRoot) {
|
|
6768
7266
|
const path = getRunStatePath(projectRoot);
|
|
6769
|
-
if (
|
|
7267
|
+
if (existsSync14(path)) {
|
|
6770
7268
|
unlinkSync4(path);
|
|
6771
7269
|
}
|
|
6772
7270
|
}
|
|
@@ -6907,8 +7405,8 @@ var init_shutdown = __esm(() => {
|
|
|
6907
7405
|
|
|
6908
7406
|
// src/core/worktree.ts
|
|
6909
7407
|
import { execSync as execSync11 } from "node:child_process";
|
|
6910
|
-
import { existsSync as
|
|
6911
|
-
import { join as
|
|
7408
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
|
|
7409
|
+
import { join as join15 } from "node:path";
|
|
6912
7410
|
function git3(args, cwd) {
|
|
6913
7411
|
return execSync11(`git ${args}`, {
|
|
6914
7412
|
cwd,
|
|
@@ -6924,10 +7422,10 @@ function gitSafe2(args, cwd) {
|
|
|
6924
7422
|
}
|
|
6925
7423
|
}
|
|
6926
7424
|
function getWorktreeDir(projectRoot) {
|
|
6927
|
-
return
|
|
7425
|
+
return join15(projectRoot, ".locus", "worktrees");
|
|
6928
7426
|
}
|
|
6929
7427
|
function getWorktreePath(projectRoot, issueNumber) {
|
|
6930
|
-
return
|
|
7428
|
+
return join15(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
|
|
6931
7429
|
}
|
|
6932
7430
|
function generateBranchName(issueNumber) {
|
|
6933
7431
|
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
@@ -6947,7 +7445,7 @@ function getWorktreeBranch(worktreePath) {
|
|
|
6947
7445
|
function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
6948
7446
|
const log = getLogger();
|
|
6949
7447
|
const worktreePath = getWorktreePath(projectRoot, issueNumber);
|
|
6950
|
-
if (
|
|
7448
|
+
if (existsSync15(worktreePath)) {
|
|
6951
7449
|
log.verbose(`Worktree already exists for issue #${issueNumber}`);
|
|
6952
7450
|
const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
|
|
6953
7451
|
return {
|
|
@@ -6974,7 +7472,7 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
|
6974
7472
|
function removeWorktree(projectRoot, issueNumber) {
|
|
6975
7473
|
const log = getLogger();
|
|
6976
7474
|
const worktreePath = getWorktreePath(projectRoot, issueNumber);
|
|
6977
|
-
if (!
|
|
7475
|
+
if (!existsSync15(worktreePath)) {
|
|
6978
7476
|
log.verbose(`Worktree for issue #${issueNumber} does not exist`);
|
|
6979
7477
|
return;
|
|
6980
7478
|
}
|
|
@@ -6993,7 +7491,7 @@ function removeWorktree(projectRoot, issueNumber) {
|
|
|
6993
7491
|
function listWorktrees(projectRoot) {
|
|
6994
7492
|
const log = getLogger();
|
|
6995
7493
|
const worktreeDir = getWorktreeDir(projectRoot);
|
|
6996
|
-
if (!
|
|
7494
|
+
if (!existsSync15(worktreeDir)) {
|
|
6997
7495
|
return [];
|
|
6998
7496
|
}
|
|
6999
7497
|
const entries = readdirSync6(worktreeDir).filter((entry) => entry.startsWith("issue-"));
|
|
@@ -7013,7 +7511,7 @@ function listWorktrees(projectRoot) {
|
|
|
7013
7511
|
if (!match)
|
|
7014
7512
|
continue;
|
|
7015
7513
|
const issueNumber = Number.parseInt(match[1], 10);
|
|
7016
|
-
const path =
|
|
7514
|
+
const path = join15(worktreeDir, entry);
|
|
7017
7515
|
const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
|
|
7018
7516
|
let resolvedPath;
|
|
7019
7517
|
try {
|
|
@@ -7253,7 +7751,18 @@ ${bold("Summary:")}
|
|
|
7253
7751
|
issue: t.issue,
|
|
7254
7752
|
title: issues.find((i) => i.number === t.issue)?.title
|
|
7255
7753
|
}));
|
|
7256
|
-
await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
7754
|
+
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
7755
|
+
if (prNumber !== undefined) {
|
|
7756
|
+
try {
|
|
7757
|
+
execSync12(`git checkout ${config.agent.baseBranch}`, {
|
|
7758
|
+
cwd: projectRoot,
|
|
7759
|
+
encoding: "utf-8",
|
|
7760
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7761
|
+
});
|
|
7762
|
+
process.stderr.write(` ${dim(`Checked out ${config.agent.baseBranch}`)}
|
|
7763
|
+
`);
|
|
7764
|
+
} catch {}
|
|
7765
|
+
}
|
|
7257
7766
|
}
|
|
7258
7767
|
if (stats.failed === 0) {
|
|
7259
7768
|
clearRunState(projectRoot);
|
|
@@ -7490,7 +7999,18 @@ ${bold("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fail
|
|
|
7490
7999
|
`);
|
|
7491
8000
|
if (isSprintRun && state.branch && state.sprint && finalStats.done > 0) {
|
|
7492
8001
|
const completedTasks = state.tasks.filter((t) => t.status === "done").map((t) => ({ issue: t.issue }));
|
|
7493
|
-
await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
8002
|
+
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
8003
|
+
if (prNumber !== undefined) {
|
|
8004
|
+
try {
|
|
8005
|
+
execSync12(`git checkout ${config.agent.baseBranch}`, {
|
|
8006
|
+
cwd: projectRoot,
|
|
8007
|
+
encoding: "utf-8",
|
|
8008
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
8009
|
+
});
|
|
8010
|
+
process.stderr.write(` ${dim(`Checked out ${config.agent.baseBranch}`)}
|
|
8011
|
+
`);
|
|
8012
|
+
} catch {}
|
|
8013
|
+
}
|
|
7494
8014
|
}
|
|
7495
8015
|
if (finalStats.failed === 0) {
|
|
7496
8016
|
clearRunState(projectRoot);
|
|
@@ -7696,13 +8216,13 @@ __export(exports_plan, {
|
|
|
7696
8216
|
parsePlanArgs: () => parsePlanArgs
|
|
7697
8217
|
});
|
|
7698
8218
|
import {
|
|
7699
|
-
existsSync as
|
|
7700
|
-
mkdirSync as
|
|
8219
|
+
existsSync as existsSync16,
|
|
8220
|
+
mkdirSync as mkdirSync11,
|
|
7701
8221
|
readdirSync as readdirSync7,
|
|
7702
|
-
readFileSync as
|
|
7703
|
-
writeFileSync as
|
|
8222
|
+
readFileSync as readFileSync12,
|
|
8223
|
+
writeFileSync as writeFileSync9
|
|
7704
8224
|
} from "node:fs";
|
|
7705
|
-
import { join as
|
|
8225
|
+
import { join as join16 } from "node:path";
|
|
7706
8226
|
function printHelp() {
|
|
7707
8227
|
process.stderr.write(`
|
|
7708
8228
|
${bold("locus plan")} — AI-powered sprint planning
|
|
@@ -7733,12 +8253,12 @@ function normalizeSprintName(name) {
|
|
|
7733
8253
|
return name.trim().toLowerCase();
|
|
7734
8254
|
}
|
|
7735
8255
|
function getPlansDir(projectRoot) {
|
|
7736
|
-
return
|
|
8256
|
+
return join16(projectRoot, ".locus", "plans");
|
|
7737
8257
|
}
|
|
7738
8258
|
function ensurePlansDir(projectRoot) {
|
|
7739
8259
|
const dir = getPlansDir(projectRoot);
|
|
7740
|
-
if (!
|
|
7741
|
-
|
|
8260
|
+
if (!existsSync16(dir)) {
|
|
8261
|
+
mkdirSync11(dir, { recursive: true });
|
|
7742
8262
|
}
|
|
7743
8263
|
return dir;
|
|
7744
8264
|
}
|
|
@@ -7747,14 +8267,14 @@ function generateId() {
|
|
|
7747
8267
|
}
|
|
7748
8268
|
function loadPlanFile(projectRoot, id) {
|
|
7749
8269
|
const dir = getPlansDir(projectRoot);
|
|
7750
|
-
if (!
|
|
8270
|
+
if (!existsSync16(dir))
|
|
7751
8271
|
return null;
|
|
7752
8272
|
const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
|
|
7753
8273
|
const match = files.find((f) => f.startsWith(id));
|
|
7754
8274
|
if (!match)
|
|
7755
8275
|
return null;
|
|
7756
8276
|
try {
|
|
7757
|
-
const content =
|
|
8277
|
+
const content = readFileSync12(join16(dir, match), "utf-8");
|
|
7758
8278
|
return JSON.parse(content);
|
|
7759
8279
|
} catch {
|
|
7760
8280
|
return null;
|
|
@@ -7800,7 +8320,7 @@ async function planCommand(projectRoot, args, flags = {}) {
|
|
|
7800
8320
|
}
|
|
7801
8321
|
function handleListPlans(projectRoot) {
|
|
7802
8322
|
const dir = getPlansDir(projectRoot);
|
|
7803
|
-
if (!
|
|
8323
|
+
if (!existsSync16(dir)) {
|
|
7804
8324
|
process.stderr.write(`${dim("No saved plans yet.")}
|
|
7805
8325
|
`);
|
|
7806
8326
|
return;
|
|
@@ -7818,7 +8338,7 @@ ${bold("Saved Plans:")}
|
|
|
7818
8338
|
for (const file of files) {
|
|
7819
8339
|
const id = file.replace(".json", "");
|
|
7820
8340
|
try {
|
|
7821
|
-
const content =
|
|
8341
|
+
const content = readFileSync12(join16(dir, file), "utf-8");
|
|
7822
8342
|
const plan = JSON.parse(content);
|
|
7823
8343
|
const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
|
|
7824
8344
|
const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
|
|
@@ -7831,7 +8351,7 @@ ${bold("Saved Plans:")}
|
|
|
7831
8351
|
}
|
|
7832
8352
|
process.stderr.write(`
|
|
7833
8353
|
`);
|
|
7834
|
-
process.stderr.write(` Approve a plan: ${bold("locus plan approve <id>")}
|
|
8354
|
+
process.stderr.write(` Approve a plan: ${bold("locus plan approve <id> --sprint <name>")}
|
|
7835
8355
|
|
|
7836
8356
|
`);
|
|
7837
8357
|
}
|
|
@@ -7929,7 +8449,7 @@ ${bold("Approving plan:")}
|
|
|
7929
8449
|
async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
|
|
7930
8450
|
const id = generateId();
|
|
7931
8451
|
const plansDir = ensurePlansDir(projectRoot);
|
|
7932
|
-
const planPath =
|
|
8452
|
+
const planPath = join16(plansDir, `${id}.json`);
|
|
7933
8453
|
const planPathRelative = `.locus/plans/${id}.json`;
|
|
7934
8454
|
const displayDirective = directive;
|
|
7935
8455
|
process.stderr.write(`
|
|
@@ -7961,7 +8481,7 @@ ${red("✗")} Planning failed: ${aiResult.error}
|
|
|
7961
8481
|
`);
|
|
7962
8482
|
return;
|
|
7963
8483
|
}
|
|
7964
|
-
if (!
|
|
8484
|
+
if (!existsSync16(planPath)) {
|
|
7965
8485
|
process.stderr.write(`
|
|
7966
8486
|
${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
|
|
7967
8487
|
`);
|
|
@@ -7971,7 +8491,7 @@ ${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
|
|
|
7971
8491
|
}
|
|
7972
8492
|
let plan;
|
|
7973
8493
|
try {
|
|
7974
|
-
const content =
|
|
8494
|
+
const content = readFileSync12(planPath, "utf-8");
|
|
7975
8495
|
plan = JSON.parse(content);
|
|
7976
8496
|
} catch {
|
|
7977
8497
|
process.stderr.write(`
|
|
@@ -7993,7 +8513,7 @@ ${yellow("⚠")} Plan file has no issues.
|
|
|
7993
8513
|
plan.sprint = sprintName;
|
|
7994
8514
|
if (!plan.createdAt)
|
|
7995
8515
|
plan.createdAt = new Date().toISOString();
|
|
7996
|
-
|
|
8516
|
+
writeFileSync9(planPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
7997
8517
|
process.stderr.write(`
|
|
7998
8518
|
${bold("Plan saved:")} ${cyan(id)}
|
|
7999
8519
|
|
|
@@ -8126,16 +8646,16 @@ function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, pla
|
|
|
8126
8646
|
parts.push(`SPRINT: ${sprintName}`);
|
|
8127
8647
|
}
|
|
8128
8648
|
parts.push("");
|
|
8129
|
-
const locusPath =
|
|
8130
|
-
if (
|
|
8131
|
-
const content =
|
|
8649
|
+
const locusPath = join16(projectRoot, "LOCUS.md");
|
|
8650
|
+
if (existsSync16(locusPath)) {
|
|
8651
|
+
const content = readFileSync12(locusPath, "utf-8");
|
|
8132
8652
|
parts.push("PROJECT CONTEXT (LOCUS.md):");
|
|
8133
8653
|
parts.push(content.slice(0, 3000));
|
|
8134
8654
|
parts.push("");
|
|
8135
8655
|
}
|
|
8136
|
-
const learningsPath =
|
|
8137
|
-
if (
|
|
8138
|
-
const content =
|
|
8656
|
+
const learningsPath = join16(projectRoot, ".locus", "LEARNINGS.md");
|
|
8657
|
+
if (existsSync16(learningsPath)) {
|
|
8658
|
+
const content = readFileSync12(learningsPath, "utf-8");
|
|
8139
8659
|
parts.push("PAST LEARNINGS:");
|
|
8140
8660
|
parts.push(content.slice(0, 2000));
|
|
8141
8661
|
parts.push("");
|
|
@@ -8312,8 +8832,8 @@ __export(exports_review, {
|
|
|
8312
8832
|
reviewCommand: () => reviewCommand
|
|
8313
8833
|
});
|
|
8314
8834
|
import { execSync as execSync13 } from "node:child_process";
|
|
8315
|
-
import { existsSync as
|
|
8316
|
-
import { join as
|
|
8835
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
|
|
8836
|
+
import { join as join17 } from "node:path";
|
|
8317
8837
|
function printHelp2() {
|
|
8318
8838
|
process.stderr.write(`
|
|
8319
8839
|
${bold("locus review")} — AI-powered code review
|
|
@@ -8470,9 +8990,9 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
|
|
|
8470
8990
|
const parts = [];
|
|
8471
8991
|
parts.push(`You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.`);
|
|
8472
8992
|
parts.push("");
|
|
8473
|
-
const locusPath =
|
|
8474
|
-
if (
|
|
8475
|
-
const content =
|
|
8993
|
+
const locusPath = join17(projectRoot, "LOCUS.md");
|
|
8994
|
+
if (existsSync17(locusPath)) {
|
|
8995
|
+
const content = readFileSync13(locusPath, "utf-8");
|
|
8476
8996
|
parts.push("PROJECT CONTEXT:");
|
|
8477
8997
|
parts.push(content.slice(0, 2000));
|
|
8478
8998
|
parts.push("");
|
|
@@ -8774,14 +9294,14 @@ __export(exports_discuss, {
|
|
|
8774
9294
|
discussCommand: () => discussCommand
|
|
8775
9295
|
});
|
|
8776
9296
|
import {
|
|
8777
|
-
existsSync as
|
|
8778
|
-
mkdirSync as
|
|
9297
|
+
existsSync as existsSync18,
|
|
9298
|
+
mkdirSync as mkdirSync12,
|
|
8779
9299
|
readdirSync as readdirSync8,
|
|
8780
|
-
readFileSync as
|
|
9300
|
+
readFileSync as readFileSync14,
|
|
8781
9301
|
unlinkSync as unlinkSync5,
|
|
8782
|
-
writeFileSync as
|
|
9302
|
+
writeFileSync as writeFileSync10
|
|
8783
9303
|
} from "node:fs";
|
|
8784
|
-
import { join as
|
|
9304
|
+
import { join as join18 } from "node:path";
|
|
8785
9305
|
function printHelp4() {
|
|
8786
9306
|
process.stderr.write(`
|
|
8787
9307
|
${bold("locus discuss")} — AI-powered architectural discussions
|
|
@@ -8803,12 +9323,12 @@ ${bold("Examples:")}
|
|
|
8803
9323
|
`);
|
|
8804
9324
|
}
|
|
8805
9325
|
function getDiscussionsDir(projectRoot) {
|
|
8806
|
-
return
|
|
9326
|
+
return join18(projectRoot, ".locus", "discussions");
|
|
8807
9327
|
}
|
|
8808
9328
|
function ensureDiscussionsDir(projectRoot) {
|
|
8809
9329
|
const dir = getDiscussionsDir(projectRoot);
|
|
8810
|
-
if (!
|
|
8811
|
-
|
|
9330
|
+
if (!existsSync18(dir)) {
|
|
9331
|
+
mkdirSync12(dir, { recursive: true });
|
|
8812
9332
|
}
|
|
8813
9333
|
return dir;
|
|
8814
9334
|
}
|
|
@@ -8842,7 +9362,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
8842
9362
|
}
|
|
8843
9363
|
function listDiscussions(projectRoot) {
|
|
8844
9364
|
const dir = getDiscussionsDir(projectRoot);
|
|
8845
|
-
if (!
|
|
9365
|
+
if (!existsSync18(dir)) {
|
|
8846
9366
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
8847
9367
|
`);
|
|
8848
9368
|
return;
|
|
@@ -8859,7 +9379,7 @@ ${bold("Discussions:")}
|
|
|
8859
9379
|
`);
|
|
8860
9380
|
for (const file of files) {
|
|
8861
9381
|
const id = file.replace(".md", "");
|
|
8862
|
-
const content =
|
|
9382
|
+
const content = readFileSync14(join18(dir, file), "utf-8");
|
|
8863
9383
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
8864
9384
|
const title = titleMatch ? titleMatch[1] : id;
|
|
8865
9385
|
const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
|
|
@@ -8877,7 +9397,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
8877
9397
|
return;
|
|
8878
9398
|
}
|
|
8879
9399
|
const dir = getDiscussionsDir(projectRoot);
|
|
8880
|
-
if (!
|
|
9400
|
+
if (!existsSync18(dir)) {
|
|
8881
9401
|
process.stderr.write(`${red("✗")} No discussions found.
|
|
8882
9402
|
`);
|
|
8883
9403
|
return;
|
|
@@ -8889,7 +9409,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
8889
9409
|
`);
|
|
8890
9410
|
return;
|
|
8891
9411
|
}
|
|
8892
|
-
const content =
|
|
9412
|
+
const content = readFileSync14(join18(dir, match), "utf-8");
|
|
8893
9413
|
process.stdout.write(`${content}
|
|
8894
9414
|
`);
|
|
8895
9415
|
}
|
|
@@ -8900,7 +9420,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8900
9420
|
return;
|
|
8901
9421
|
}
|
|
8902
9422
|
const dir = getDiscussionsDir(projectRoot);
|
|
8903
|
-
if (!
|
|
9423
|
+
if (!existsSync18(dir)) {
|
|
8904
9424
|
process.stderr.write(`${red("✗")} No discussions found.
|
|
8905
9425
|
`);
|
|
8906
9426
|
return;
|
|
@@ -8912,7 +9432,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8912
9432
|
`);
|
|
8913
9433
|
return;
|
|
8914
9434
|
}
|
|
8915
|
-
unlinkSync5(
|
|
9435
|
+
unlinkSync5(join18(dir, match));
|
|
8916
9436
|
process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
|
|
8917
9437
|
`);
|
|
8918
9438
|
}
|
|
@@ -8925,7 +9445,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
8925
9445
|
return;
|
|
8926
9446
|
}
|
|
8927
9447
|
const dir = getDiscussionsDir(projectRoot);
|
|
8928
|
-
if (!
|
|
9448
|
+
if (!existsSync18(dir)) {
|
|
8929
9449
|
process.stderr.write(`${red("✗")} No discussions found.
|
|
8930
9450
|
`);
|
|
8931
9451
|
return;
|
|
@@ -8937,7 +9457,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
8937
9457
|
`);
|
|
8938
9458
|
return;
|
|
8939
9459
|
}
|
|
8940
|
-
const content =
|
|
9460
|
+
const content = readFileSync14(join18(dir, match), "utf-8");
|
|
8941
9461
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
8942
9462
|
const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
|
|
8943
9463
|
await planCommand(projectRoot, [
|
|
@@ -9049,7 +9569,7 @@ ${turn.content}`;
|
|
|
9049
9569
|
...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
|
|
9050
9570
|
].join(`
|
|
9051
9571
|
`);
|
|
9052
|
-
|
|
9572
|
+
writeFileSync10(join18(dir, `${id}.md`), markdown, "utf-8");
|
|
9053
9573
|
process.stderr.write(`
|
|
9054
9574
|
${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
|
|
9055
9575
|
`);
|
|
@@ -9063,16 +9583,16 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
|
|
|
9063
9583
|
const parts = [];
|
|
9064
9584
|
parts.push(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
|
|
9065
9585
|
parts.push("");
|
|
9066
|
-
const locusPath =
|
|
9067
|
-
if (
|
|
9068
|
-
const content =
|
|
9586
|
+
const locusPath = join18(projectRoot, "LOCUS.md");
|
|
9587
|
+
if (existsSync18(locusPath)) {
|
|
9588
|
+
const content = readFileSync14(locusPath, "utf-8");
|
|
9069
9589
|
parts.push("PROJECT CONTEXT:");
|
|
9070
9590
|
parts.push(content.slice(0, 3000));
|
|
9071
9591
|
parts.push("");
|
|
9072
9592
|
}
|
|
9073
|
-
const learningsPath =
|
|
9074
|
-
if (
|
|
9075
|
-
const content =
|
|
9593
|
+
const learningsPath = join18(projectRoot, ".locus", "LEARNINGS.md");
|
|
9594
|
+
if (existsSync18(learningsPath)) {
|
|
9595
|
+
const content = readFileSync14(learningsPath, "utf-8");
|
|
9076
9596
|
parts.push("PAST LEARNINGS:");
|
|
9077
9597
|
parts.push(content.slice(0, 2000));
|
|
9078
9598
|
parts.push("");
|
|
@@ -9131,8 +9651,8 @@ __export(exports_artifacts, {
|
|
|
9131
9651
|
formatDate: () => formatDate2,
|
|
9132
9652
|
artifactsCommand: () => artifactsCommand
|
|
9133
9653
|
});
|
|
9134
|
-
import { existsSync as
|
|
9135
|
-
import { join as
|
|
9654
|
+
import { existsSync as existsSync19, readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync4 } from "node:fs";
|
|
9655
|
+
import { join as join19 } from "node:path";
|
|
9136
9656
|
function printHelp5() {
|
|
9137
9657
|
process.stderr.write(`
|
|
9138
9658
|
${bold("locus artifacts")} — View and manage AI-generated artifacts
|
|
@@ -9152,14 +9672,14 @@ ${dim("Artifact names support partial matching.")}
|
|
|
9152
9672
|
`);
|
|
9153
9673
|
}
|
|
9154
9674
|
function getArtifactsDir(projectRoot) {
|
|
9155
|
-
return
|
|
9675
|
+
return join19(projectRoot, ".locus", "artifacts");
|
|
9156
9676
|
}
|
|
9157
9677
|
function listArtifacts(projectRoot) {
|
|
9158
9678
|
const dir = getArtifactsDir(projectRoot);
|
|
9159
|
-
if (!
|
|
9679
|
+
if (!existsSync19(dir))
|
|
9160
9680
|
return [];
|
|
9161
9681
|
return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
9162
|
-
const filePath =
|
|
9682
|
+
const filePath = join19(dir, fileName);
|
|
9163
9683
|
const stat = statSync4(filePath);
|
|
9164
9684
|
return {
|
|
9165
9685
|
name: fileName.replace(/\.md$/, ""),
|
|
@@ -9172,12 +9692,12 @@ function listArtifacts(projectRoot) {
|
|
|
9172
9692
|
function readArtifact(projectRoot, name) {
|
|
9173
9693
|
const dir = getArtifactsDir(projectRoot);
|
|
9174
9694
|
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
9175
|
-
const filePath =
|
|
9176
|
-
if (!
|
|
9695
|
+
const filePath = join19(dir, fileName);
|
|
9696
|
+
if (!existsSync19(filePath))
|
|
9177
9697
|
return null;
|
|
9178
9698
|
const stat = statSync4(filePath);
|
|
9179
9699
|
return {
|
|
9180
|
-
content:
|
|
9700
|
+
content: readFileSync15(filePath, "utf-8"),
|
|
9181
9701
|
info: {
|
|
9182
9702
|
name: fileName.replace(/\.md$/, ""),
|
|
9183
9703
|
fileName,
|
|
@@ -9341,17 +9861,17 @@ init_context();
|
|
|
9341
9861
|
init_logger();
|
|
9342
9862
|
init_rate_limiter();
|
|
9343
9863
|
init_terminal();
|
|
9344
|
-
import { existsSync as
|
|
9345
|
-
import { join as
|
|
9864
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16 } from "node:fs";
|
|
9865
|
+
import { join as join20 } from "node:path";
|
|
9346
9866
|
import { fileURLToPath } from "node:url";
|
|
9347
9867
|
function getCliVersion() {
|
|
9348
9868
|
const fallbackVersion = "0.0.0";
|
|
9349
|
-
const packageJsonPath =
|
|
9350
|
-
if (!
|
|
9869
|
+
const packageJsonPath = join20(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
|
|
9870
|
+
if (!existsSync20(packageJsonPath)) {
|
|
9351
9871
|
return fallbackVersion;
|
|
9352
9872
|
}
|
|
9353
9873
|
try {
|
|
9354
|
-
const parsed = JSON.parse(
|
|
9874
|
+
const parsed = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
|
|
9355
9875
|
return parsed.version ?? fallbackVersion;
|
|
9356
9876
|
} catch {
|
|
9357
9877
|
return fallbackVersion;
|
|
@@ -9369,7 +9889,9 @@ function parseArgs(argv) {
|
|
|
9369
9889
|
clean: false,
|
|
9370
9890
|
resume: false,
|
|
9371
9891
|
dryRun: false,
|
|
9372
|
-
check: false
|
|
9892
|
+
check: false,
|
|
9893
|
+
upgrade: false,
|
|
9894
|
+
list: false
|
|
9373
9895
|
};
|
|
9374
9896
|
const positional = [];
|
|
9375
9897
|
let i = 0;
|
|
@@ -9389,8 +9911,25 @@ function parseArgs(argv) {
|
|
|
9389
9911
|
flags.help = true;
|
|
9390
9912
|
break;
|
|
9391
9913
|
case "--version":
|
|
9392
|
-
case "-V":
|
|
9393
|
-
|
|
9914
|
+
case "-V": {
|
|
9915
|
+
const nextToken = rawArgs[i + 1];
|
|
9916
|
+
if (nextToken !== undefined && /^\d/.test(nextToken)) {
|
|
9917
|
+
flags.installVersion = rawArgs[++i];
|
|
9918
|
+
} else {
|
|
9919
|
+
flags.version = true;
|
|
9920
|
+
}
|
|
9921
|
+
break;
|
|
9922
|
+
}
|
|
9923
|
+
case "-v":
|
|
9924
|
+
flags.installVersion = rawArgs[++i];
|
|
9925
|
+
break;
|
|
9926
|
+
case "--upgrade":
|
|
9927
|
+
case "-u":
|
|
9928
|
+
flags.upgrade = true;
|
|
9929
|
+
break;
|
|
9930
|
+
case "--list":
|
|
9931
|
+
case "-l":
|
|
9932
|
+
flags.list = true;
|
|
9394
9933
|
break;
|
|
9395
9934
|
case "--json-stream":
|
|
9396
9935
|
flags.jsonStream = true;
|
|
@@ -9459,6 +9998,10 @@ ${bold("Commands:")}
|
|
|
9459
9998
|
${cyan("status")} Dashboard view of current state
|
|
9460
9999
|
${cyan("config")} View and manage settings
|
|
9461
10000
|
${cyan("logs")} View, tail, and manage execution logs
|
|
10001
|
+
${cyan("install")} Install a community package
|
|
10002
|
+
${cyan("uninstall")} Remove an installed package
|
|
10003
|
+
${cyan("packages")} Manage installed packages (list, outdated)
|
|
10004
|
+
${cyan("pkg")} ${dim("<name> [cmd]")} Run a command from an installed package
|
|
9462
10005
|
${cyan("upgrade")} Check for and install updates
|
|
9463
10006
|
|
|
9464
10007
|
${bold("Options:")}
|
|
@@ -9504,7 +10047,7 @@ async function main() {
|
|
|
9504
10047
|
try {
|
|
9505
10048
|
const root = getGitRoot(cwd);
|
|
9506
10049
|
if (isInitialized(root)) {
|
|
9507
|
-
logDir =
|
|
10050
|
+
logDir = join20(root, ".locus", "logs");
|
|
9508
10051
|
getRateLimiter(root);
|
|
9509
10052
|
}
|
|
9510
10053
|
} catch {}
|
|
@@ -9534,6 +10077,43 @@ async function main() {
|
|
|
9534
10077
|
logger.destroy();
|
|
9535
10078
|
return;
|
|
9536
10079
|
}
|
|
10080
|
+
if (command === "install") {
|
|
10081
|
+
if (parsed.flags.list) {
|
|
10082
|
+
const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));
|
|
10083
|
+
await packagesCommand2(["list"], {});
|
|
10084
|
+
logger.destroy();
|
|
10085
|
+
return;
|
|
10086
|
+
}
|
|
10087
|
+
const { installCommand: installCommand2 } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
10088
|
+
const installFlags = {};
|
|
10089
|
+
if (parsed.flags.installVersion) {
|
|
10090
|
+
installFlags.version = parsed.flags.installVersion;
|
|
10091
|
+
}
|
|
10092
|
+
if (parsed.flags.upgrade) {
|
|
10093
|
+
installFlags.upgrade = "true";
|
|
10094
|
+
}
|
|
10095
|
+
await installCommand2(parsed.args, installFlags);
|
|
10096
|
+
logger.destroy();
|
|
10097
|
+
return;
|
|
10098
|
+
}
|
|
10099
|
+
if (command === "uninstall") {
|
|
10100
|
+
const { uninstallCommand: uninstallCommand2 } = await Promise.resolve().then(() => (init_uninstall(), exports_uninstall));
|
|
10101
|
+
await uninstallCommand2(parsed.args, {});
|
|
10102
|
+
logger.destroy();
|
|
10103
|
+
return;
|
|
10104
|
+
}
|
|
10105
|
+
if (command === "packages") {
|
|
10106
|
+
const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));
|
|
10107
|
+
await packagesCommand2(parsed.args, {});
|
|
10108
|
+
logger.destroy();
|
|
10109
|
+
return;
|
|
10110
|
+
}
|
|
10111
|
+
if (command === "pkg") {
|
|
10112
|
+
const { pkgCommand: pkgCommand2 } = await Promise.resolve().then(() => (init_pkg(), exports_pkg));
|
|
10113
|
+
await pkgCommand2(parsed.args, {});
|
|
10114
|
+
logger.destroy();
|
|
10115
|
+
return;
|
|
10116
|
+
}
|
|
9537
10117
|
let projectRoot;
|
|
9538
10118
|
try {
|
|
9539
10119
|
projectRoot = getGitRoot(cwd);
|