@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.
- package/bin/locus.js +801 -169
- 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
|
|
|
@@ -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:
|
|
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
|
-
|
|
3246
|
+
const isImagePath = looksLikeImagePathChunk(chunk);
|
|
3247
|
+
if (!looksLikePaste(chunk) && !isImagePath) {
|
|
2733
3248
|
return false;
|
|
2734
3249
|
}
|
|
2735
3250
|
const normalized = normalizeLineEndings(chunk);
|
|
2736
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
4968
|
-
import { join as
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
5043
|
-
if (
|
|
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(
|
|
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 (!
|
|
5740
|
+
if (!existsSync11(path))
|
|
5194
5741
|
return null;
|
|
5195
|
-
return
|
|
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
|
|
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("/") ?
|
|
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
|
|
5717
|
-
import { dirname as dirname4, join as
|
|
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 =
|
|
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 (!
|
|
6309
|
+
if (!existsSync12(this.filePath))
|
|
5763
6310
|
return;
|
|
5764
|
-
const content =
|
|
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 (!
|
|
5773
|
-
|
|
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
|
-
|
|
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
|
|
5805
|
-
mkdirSync as
|
|
6351
|
+
existsSync as existsSync13,
|
|
6352
|
+
mkdirSync as mkdirSync9,
|
|
5806
6353
|
readdirSync as readdirSync5,
|
|
5807
|
-
readFileSync as
|
|
6354
|
+
readFileSync as readFileSync10,
|
|
5808
6355
|
unlinkSync as unlinkSync3,
|
|
5809
|
-
writeFileSync as
|
|
6356
|
+
writeFileSync as writeFileSync7
|
|
5810
6357
|
} from "node:fs";
|
|
5811
|
-
import { basename as basename3, join as
|
|
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 =
|
|
5817
|
-
if (!
|
|
5818
|
-
|
|
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
|
|
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 (
|
|
6395
|
+
if (existsSync13(exactPath)) {
|
|
5849
6396
|
try {
|
|
5850
|
-
return JSON.parse(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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(
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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
|
|
6738
|
-
mkdirSync as
|
|
6739
|
-
readFileSync as
|
|
7284
|
+
existsSync as existsSync14,
|
|
7285
|
+
mkdirSync as mkdirSync10,
|
|
7286
|
+
readFileSync as readFileSync11,
|
|
6740
7287
|
unlinkSync as unlinkSync4,
|
|
6741
|
-
writeFileSync as
|
|
7288
|
+
writeFileSync as writeFileSync8
|
|
6742
7289
|
} from "node:fs";
|
|
6743
|
-
import { dirname as dirname5, join as
|
|
7290
|
+
import { dirname as dirname5, join as join14 } from "node:path";
|
|
6744
7291
|
function getRunStatePath(projectRoot) {
|
|
6745
|
-
return
|
|
7292
|
+
return join14(projectRoot, ".locus", "run-state.json");
|
|
6746
7293
|
}
|
|
6747
7294
|
function loadRunState(projectRoot) {
|
|
6748
7295
|
const path = getRunStatePath(projectRoot);
|
|
6749
|
-
if (!
|
|
7296
|
+
if (!existsSync14(path))
|
|
6750
7297
|
return null;
|
|
6751
7298
|
try {
|
|
6752
|
-
return JSON.parse(
|
|
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 (!
|
|
6762
|
-
|
|
7308
|
+
if (!existsSync14(dir)) {
|
|
7309
|
+
mkdirSync10(dir, { recursive: true });
|
|
6763
7310
|
}
|
|
6764
|
-
|
|
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 (
|
|
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
|
|
6911
|
-
import { join as
|
|
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
|
|
7474
|
+
return join15(projectRoot, ".locus", "worktrees");
|
|
6928
7475
|
}
|
|
6929
7476
|
function getWorktreePath(projectRoot, issueNumber) {
|
|
6930
|
-
return
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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
|
|
7700
|
-
mkdirSync as
|
|
8268
|
+
existsSync as existsSync16,
|
|
8269
|
+
mkdirSync as mkdirSync11,
|
|
7701
8270
|
readdirSync as readdirSync7,
|
|
7702
|
-
readFileSync as
|
|
7703
|
-
writeFileSync as
|
|
8271
|
+
readFileSync as readFileSync12,
|
|
8272
|
+
writeFileSync as writeFileSync9
|
|
7704
8273
|
} from "node:fs";
|
|
7705
|
-
import { join as
|
|
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>
|
|
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
|
|
8305
|
+
return join16(projectRoot, ".locus", "plans");
|
|
7736
8306
|
}
|
|
7737
8307
|
function ensurePlansDir(projectRoot) {
|
|
7738
8308
|
const dir = getPlansDir(projectRoot);
|
|
7739
|
-
if (!
|
|
7740
|
-
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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:")}
|
|
8474
|
+
${bold("Approving plan:")}
|
|
7903
8475
|
`);
|
|
7904
|
-
if (
|
|
7905
|
-
process.stderr.write(` ${dim(`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,
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
8127
|
-
if (
|
|
8128
|
-
const content =
|
|
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 =
|
|
8134
|
-
if (
|
|
8135
|
-
const content =
|
|
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
|
|
8313
|
-
import { join as
|
|
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 =
|
|
8471
|
-
if (
|
|
8472
|
-
const content =
|
|
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
|
|
8775
|
-
mkdirSync as
|
|
9346
|
+
existsSync as existsSync18,
|
|
9347
|
+
mkdirSync as mkdirSync12,
|
|
8776
9348
|
readdirSync as readdirSync8,
|
|
8777
|
-
readFileSync as
|
|
9349
|
+
readFileSync as readFileSync14,
|
|
8778
9350
|
unlinkSync as unlinkSync5,
|
|
8779
|
-
writeFileSync as
|
|
9351
|
+
writeFileSync as writeFileSync10
|
|
8780
9352
|
} from "node:fs";
|
|
8781
|
-
import { join as
|
|
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
|
|
9375
|
+
return join18(projectRoot, ".locus", "discussions");
|
|
8804
9376
|
}
|
|
8805
9377
|
function ensureDiscussionsDir(projectRoot) {
|
|
8806
9378
|
const dir = getDiscussionsDir(projectRoot);
|
|
8807
|
-
if (!
|
|
8808
|
-
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
9064
|
-
if (
|
|
9065
|
-
const content =
|
|
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 =
|
|
9071
|
-
if (
|
|
9072
|
-
const content =
|
|
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
|
|
9132
|
-
import { join as
|
|
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
|
|
9724
|
+
return join19(projectRoot, ".locus", "artifacts");
|
|
9153
9725
|
}
|
|
9154
9726
|
function listArtifacts(projectRoot) {
|
|
9155
9727
|
const dir = getArtifactsDir(projectRoot);
|
|
9156
|
-
if (!
|
|
9728
|
+
if (!existsSync19(dir))
|
|
9157
9729
|
return [];
|
|
9158
9730
|
return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
9159
|
-
const filePath =
|
|
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 =
|
|
9173
|
-
if (!
|
|
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:
|
|
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
|
|
9342
|
-
import { join as
|
|
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 =
|
|
9347
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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);
|