agendex-cli 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1194 -182
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1058,14 +1058,14 @@ var init_cleanup = __esm(() => {
|
|
|
1058
1058
|
});
|
|
1059
1059
|
|
|
1060
1060
|
// src/cli.ts
|
|
1061
|
-
import { spawn as
|
|
1062
|
-
import { existsSync as
|
|
1063
|
-
import { resolve as
|
|
1064
|
-
import { fileURLToPath as
|
|
1061
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
1062
|
+
import { existsSync as existsSync11, statSync as statSync2, writeSync } from "node:fs";
|
|
1063
|
+
import { resolve as resolve8 } from "node:path";
|
|
1064
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1065
1065
|
|
|
1066
1066
|
// ../shared/src/adapters/catalog.ts
|
|
1067
|
-
import { homedir as
|
|
1068
|
-
import { join as
|
|
1067
|
+
import { homedir as homedir7 } from "node:os";
|
|
1068
|
+
import { join as join7 } from "node:path";
|
|
1069
1069
|
|
|
1070
1070
|
// ../shared/src/adapters/claude-code.ts
|
|
1071
1071
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -1748,6 +1748,453 @@ var ohMyOpencodeAdapter = {
|
|
|
1748
1748
|
}
|
|
1749
1749
|
};
|
|
1750
1750
|
|
|
1751
|
+
// ../shared/src/adapters/plannotator.ts
|
|
1752
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1753
|
+
import { readFile as readFile6, stat as stat6 } from "node:fs/promises";
|
|
1754
|
+
import { homedir as homedir6 } from "node:os";
|
|
1755
|
+
import { basename as basename5, dirname, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
|
|
1756
|
+
var REQUEST_TIMEOUT_MS = 5000;
|
|
1757
|
+
var PROJECT_PLANS_DIRNAME = "@plans";
|
|
1758
|
+
function getPlannotatorDir() {
|
|
1759
|
+
return process.env.AGENDEX_PLANNOTATOR_DIR || join6(homedir6(), ".plannotator");
|
|
1760
|
+
}
|
|
1761
|
+
function getPlansDir() {
|
|
1762
|
+
return join6(getPlannotatorDir(), "plans");
|
|
1763
|
+
}
|
|
1764
|
+
function getSessionsDir() {
|
|
1765
|
+
return join6(getPlannotatorDir(), "sessions");
|
|
1766
|
+
}
|
|
1767
|
+
function isRecord3(value) {
|
|
1768
|
+
return typeof value === "object" && value !== null;
|
|
1769
|
+
}
|
|
1770
|
+
function normalizeStatusFromFilename(filePath) {
|
|
1771
|
+
const name = basename5(filePath, ".md");
|
|
1772
|
+
if (name.endsWith("-approved"))
|
|
1773
|
+
return "approved";
|
|
1774
|
+
if (name.endsWith("-denied"))
|
|
1775
|
+
return "denied";
|
|
1776
|
+
return "unknown";
|
|
1777
|
+
}
|
|
1778
|
+
function stripStatusSuffix(title) {
|
|
1779
|
+
return title.replace(/-(approved|denied)$/i, "");
|
|
1780
|
+
}
|
|
1781
|
+
function cleanTitle2(title) {
|
|
1782
|
+
return title.replace(/\d{4}-\d{2}-\d{2}/g, "").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1783
|
+
}
|
|
1784
|
+
function extractTitle5(content, filePath) {
|
|
1785
|
+
const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
1786
|
+
if (heading)
|
|
1787
|
+
return heading.replace(/^Plan:\s*/i, "").trim();
|
|
1788
|
+
const name = stripStatusSuffix(basename5(filePath, ".md"));
|
|
1789
|
+
return cleanTitle2(name) || "Plannotator Plan";
|
|
1790
|
+
}
|
|
1791
|
+
function isAnnotationFile(filePath) {
|
|
1792
|
+
return filePath.endsWith(".annotations.md");
|
|
1793
|
+
}
|
|
1794
|
+
function isSnapshotFile(filePath) {
|
|
1795
|
+
if (!filePath.endsWith(".md"))
|
|
1796
|
+
return false;
|
|
1797
|
+
if (isAnnotationFile(filePath))
|
|
1798
|
+
return false;
|
|
1799
|
+
const status = normalizeStatusFromFilename(filePath);
|
|
1800
|
+
return status === "approved" || status === "denied";
|
|
1801
|
+
}
|
|
1802
|
+
function isProjectPlansPath(filePath) {
|
|
1803
|
+
return resolve2(filePath).split(sep2).includes(PROJECT_PLANS_DIRNAME);
|
|
1804
|
+
}
|
|
1805
|
+
function projectPlansDirForPath(filePath) {
|
|
1806
|
+
const parts = resolve2(filePath).split(sep2);
|
|
1807
|
+
const index = parts.lastIndexOf(PROJECT_PLANS_DIRNAME);
|
|
1808
|
+
if (index === -1)
|
|
1809
|
+
return;
|
|
1810
|
+
return parts.slice(0, index + 1).join(sep2) || sep2;
|
|
1811
|
+
}
|
|
1812
|
+
function isProjectPlanFile(filePath) {
|
|
1813
|
+
return filePath.endsWith(".md") && !isAnnotationFile(filePath) && isProjectPlansPath(filePath);
|
|
1814
|
+
}
|
|
1815
|
+
function isSessionFile2(filePath) {
|
|
1816
|
+
return filePath.endsWith(".json");
|
|
1817
|
+
}
|
|
1818
|
+
function isAlive(pid) {
|
|
1819
|
+
try {
|
|
1820
|
+
process.kill(pid, 0);
|
|
1821
|
+
return true;
|
|
1822
|
+
} catch {
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function isSafePlannotatorUrl(rawUrl) {
|
|
1827
|
+
try {
|
|
1828
|
+
const url = new URL(rawUrl);
|
|
1829
|
+
if (url.protocol !== "http:")
|
|
1830
|
+
return false;
|
|
1831
|
+
if (url.username || url.password)
|
|
1832
|
+
return false;
|
|
1833
|
+
if (url.pathname !== "/" && url.pathname !== "")
|
|
1834
|
+
return false;
|
|
1835
|
+
if (url.search || url.hash)
|
|
1836
|
+
return false;
|
|
1837
|
+
const hostname = url.hostname.toLowerCase();
|
|
1838
|
+
if (hostname === "localhost")
|
|
1839
|
+
return true;
|
|
1840
|
+
if (hostname === "127.0.0.1")
|
|
1841
|
+
return true;
|
|
1842
|
+
if (hostname === "::1" || hostname === "[::1]")
|
|
1843
|
+
return true;
|
|
1844
|
+
return false;
|
|
1845
|
+
} catch {
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function apiUrl(baseUrl, path) {
|
|
1850
|
+
const url = new URL(baseUrl);
|
|
1851
|
+
url.pathname = path;
|
|
1852
|
+
url.search = "";
|
|
1853
|
+
url.hash = "";
|
|
1854
|
+
return url.toString();
|
|
1855
|
+
}
|
|
1856
|
+
async function fetchJson(url, init) {
|
|
1857
|
+
const controller = new AbortController;
|
|
1858
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1859
|
+
try {
|
|
1860
|
+
const res = await fetch(url, {
|
|
1861
|
+
...init,
|
|
1862
|
+
redirect: "error",
|
|
1863
|
+
signal: controller.signal
|
|
1864
|
+
});
|
|
1865
|
+
if (!res.ok)
|
|
1866
|
+
return null;
|
|
1867
|
+
return await res.json();
|
|
1868
|
+
} catch {
|
|
1869
|
+
return null;
|
|
1870
|
+
} finally {
|
|
1871
|
+
clearTimeout(timer);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
async function postJson(url, body) {
|
|
1875
|
+
const controller = new AbortController;
|
|
1876
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1877
|
+
try {
|
|
1878
|
+
const res = await fetch(url, {
|
|
1879
|
+
method: "POST",
|
|
1880
|
+
headers: { "Content-Type": "application/json" },
|
|
1881
|
+
body: JSON.stringify(body),
|
|
1882
|
+
redirect: "error",
|
|
1883
|
+
signal: controller.signal
|
|
1884
|
+
});
|
|
1885
|
+
return res.ok;
|
|
1886
|
+
} catch {
|
|
1887
|
+
return false;
|
|
1888
|
+
} finally {
|
|
1889
|
+
clearTimeout(timer);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
function normalizeMode(value) {
|
|
1893
|
+
if (value === "plan" || value === "review" || value === "annotate" || value === "archive") {
|
|
1894
|
+
return value;
|
|
1895
|
+
}
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
function parseSessionInfo(raw) {
|
|
1899
|
+
if (!isRecord3(raw))
|
|
1900
|
+
return null;
|
|
1901
|
+
const pid = typeof raw.pid === "number" && Number.isFinite(raw.pid) ? raw.pid : undefined;
|
|
1902
|
+
const port = typeof raw.port === "number" && Number.isFinite(raw.port) ? raw.port : undefined;
|
|
1903
|
+
const url = typeof raw.url === "string" ? raw.url : undefined;
|
|
1904
|
+
if (!pid || !url)
|
|
1905
|
+
return null;
|
|
1906
|
+
return {
|
|
1907
|
+
pid,
|
|
1908
|
+
port,
|
|
1909
|
+
url,
|
|
1910
|
+
mode: normalizeMode(raw.mode),
|
|
1911
|
+
project: typeof raw.project === "string" ? raw.project : undefined,
|
|
1912
|
+
startedAt: typeof raw.startedAt === "string" ? raw.startedAt : undefined,
|
|
1913
|
+
label: typeof raw.label === "string" ? raw.label : undefined,
|
|
1914
|
+
origin: typeof raw.origin === "string" ? raw.origin : undefined,
|
|
1915
|
+
reviewId: typeof raw.reviewId === "string" ? raw.reviewId : undefined,
|
|
1916
|
+
sourcePlanPath: typeof raw.sourcePlanPath === "string" ? raw.sourcePlanPath : undefined
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
function parseDate2(value) {
|
|
1920
|
+
if (!value)
|
|
1921
|
+
return;
|
|
1922
|
+
const date = new Date(value);
|
|
1923
|
+
if (Number.isNaN(date.getTime()))
|
|
1924
|
+
return;
|
|
1925
|
+
return date;
|
|
1926
|
+
}
|
|
1927
|
+
function metadataRecord(metadata) {
|
|
1928
|
+
return {
|
|
1929
|
+
source: "plannotator",
|
|
1930
|
+
sourceAdapter: "plannotator",
|
|
1931
|
+
plannotator: metadata
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function annotationsPathForSnapshot(filePath) {
|
|
1935
|
+
const snapshotBase = basename5(filePath, ".md").replace(/-(approved|denied)$/i, "");
|
|
1936
|
+
return join6(dirname(filePath), `${snapshotBase}.annotations.md`);
|
|
1937
|
+
}
|
|
1938
|
+
function snapshotPathsForAnnotation(filePath) {
|
|
1939
|
+
const base = basename5(filePath, ".annotations.md");
|
|
1940
|
+
return [
|
|
1941
|
+
join6(dirname(filePath), `${base}-approved.md`),
|
|
1942
|
+
join6(dirname(filePath), `${base}-denied.md`)
|
|
1943
|
+
];
|
|
1944
|
+
}
|
|
1945
|
+
function projectPlanPathsForAnnotation(filePath) {
|
|
1946
|
+
const base = basename5(filePath, ".annotations.md");
|
|
1947
|
+
return [join6(dirname(filePath), `${base}.md`), ...snapshotPathsForAnnotation(filePath)];
|
|
1948
|
+
}
|
|
1949
|
+
async function countAnnotationHeadings(filePath) {
|
|
1950
|
+
if (!existsSync3(filePath))
|
|
1951
|
+
return;
|
|
1952
|
+
try {
|
|
1953
|
+
const raw = await readFile6(filePath, "utf-8");
|
|
1954
|
+
const headings = raw.match(/^#{1,6}\s+/gm);
|
|
1955
|
+
return headings?.length ?? (raw.trim() ? 1 : 0);
|
|
1956
|
+
} catch {
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
async function parseSnapshot(filePath) {
|
|
1961
|
+
try {
|
|
1962
|
+
const content = await readFile6(filePath, "utf-8");
|
|
1963
|
+
const stats = await stat6(filePath);
|
|
1964
|
+
const status = normalizeStatusFromFilename(filePath);
|
|
1965
|
+
const annotationsPath = annotationsPathForSnapshot(filePath);
|
|
1966
|
+
const annotationCount = await countAnnotationHeadings(annotationsPath);
|
|
1967
|
+
const metadata = {
|
|
1968
|
+
kind: "snapshot",
|
|
1969
|
+
mode: "plan",
|
|
1970
|
+
status,
|
|
1971
|
+
annotationsPath: existsSync3(annotationsPath) ? annotationsPath : undefined,
|
|
1972
|
+
annotationCount,
|
|
1973
|
+
writebackCapable: false
|
|
1974
|
+
};
|
|
1975
|
+
return [
|
|
1976
|
+
{
|
|
1977
|
+
id: hashPath(filePath),
|
|
1978
|
+
agent: "plannotator",
|
|
1979
|
+
title: extractTitle5(content, filePath),
|
|
1980
|
+
content,
|
|
1981
|
+
filePath,
|
|
1982
|
+
format: "md",
|
|
1983
|
+
createdAt: stats.birthtime,
|
|
1984
|
+
updatedAt: stats.mtime,
|
|
1985
|
+
metadata: metadataRecord(metadata)
|
|
1986
|
+
}
|
|
1987
|
+
];
|
|
1988
|
+
} catch {
|
|
1989
|
+
return [];
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
async function parseAnnotationCompanion(filePath) {
|
|
1993
|
+
const plans = [];
|
|
1994
|
+
for (const snapshotPath of snapshotPathsForAnnotation(filePath)) {
|
|
1995
|
+
if (!existsSync3(snapshotPath))
|
|
1996
|
+
continue;
|
|
1997
|
+
plans.push(...await parseSnapshot(snapshotPath));
|
|
1998
|
+
}
|
|
1999
|
+
return plans;
|
|
2000
|
+
}
|
|
2001
|
+
async function parseProjectPlan(filePath) {
|
|
2002
|
+
try {
|
|
2003
|
+
const content = await readFile6(filePath, "utf-8");
|
|
2004
|
+
const stats = await stat6(filePath);
|
|
2005
|
+
const projectPlansDir = projectPlansDirForPath(filePath);
|
|
2006
|
+
const projectRoot = projectPlansDir ? dirname(projectPlansDir) : undefined;
|
|
2007
|
+
const status = normalizeStatusFromFilename(filePath);
|
|
2008
|
+
const annotationsPath = annotationsPathForSnapshot(filePath);
|
|
2009
|
+
const annotationCount = await countAnnotationHeadings(annotationsPath);
|
|
2010
|
+
const metadata = {
|
|
2011
|
+
kind: "project-plan",
|
|
2012
|
+
mode: "plan",
|
|
2013
|
+
status,
|
|
2014
|
+
annotationsPath: existsSync3(annotationsPath) ? annotationsPath : undefined,
|
|
2015
|
+
annotationCount,
|
|
2016
|
+
sourcePlanPath: filePath,
|
|
2017
|
+
project: projectRoot ? basename5(projectRoot) : undefined,
|
|
2018
|
+
writebackCapable: false
|
|
2019
|
+
};
|
|
2020
|
+
return [
|
|
2021
|
+
{
|
|
2022
|
+
id: hashPath(filePath),
|
|
2023
|
+
agent: "plannotator",
|
|
2024
|
+
title: extractTitle5(content, filePath),
|
|
2025
|
+
content,
|
|
2026
|
+
filePath,
|
|
2027
|
+
format: "md",
|
|
2028
|
+
createdAt: stats.birthtime,
|
|
2029
|
+
updatedAt: stats.mtime,
|
|
2030
|
+
workspace: projectRoot,
|
|
2031
|
+
metadata: metadataRecord(metadata)
|
|
2032
|
+
}
|
|
2033
|
+
];
|
|
2034
|
+
} catch {
|
|
2035
|
+
return [];
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
async function parseProjectAnnotationCompanion(filePath) {
|
|
2039
|
+
const plans = [];
|
|
2040
|
+
const seen = new Set;
|
|
2041
|
+
for (const planPath of projectPlanPathsForAnnotation(filePath)) {
|
|
2042
|
+
const resolved = resolve2(planPath);
|
|
2043
|
+
if (seen.has(resolved))
|
|
2044
|
+
continue;
|
|
2045
|
+
seen.add(resolved);
|
|
2046
|
+
if (!existsSync3(planPath))
|
|
2047
|
+
continue;
|
|
2048
|
+
plans.push(...await parseProjectPlan(planPath));
|
|
2049
|
+
}
|
|
2050
|
+
return plans;
|
|
2051
|
+
}
|
|
2052
|
+
async function parseLiveSession(filePath) {
|
|
2053
|
+
try {
|
|
2054
|
+
const raw = JSON.parse(await readFile6(filePath, "utf-8"));
|
|
2055
|
+
const session = parseSessionInfo(raw);
|
|
2056
|
+
if (!session?.pid || !session.url)
|
|
2057
|
+
return [];
|
|
2058
|
+
if (!isAlive(session.pid))
|
|
2059
|
+
return [];
|
|
2060
|
+
if (!isSafePlannotatorUrl(session.url))
|
|
2061
|
+
return [];
|
|
2062
|
+
const mode = session.mode ?? "plan";
|
|
2063
|
+
if (mode === "archive")
|
|
2064
|
+
return [];
|
|
2065
|
+
const planResponse = await fetchJson(apiUrl(session.url, "/api/plan"));
|
|
2066
|
+
if (!planResponse?.plan)
|
|
2067
|
+
return [];
|
|
2068
|
+
const stats = await stat6(filePath);
|
|
2069
|
+
const createdAt = parseDate2(session.startedAt) ?? stats.birthtime;
|
|
2070
|
+
const origin = planResponse.origin ?? session.origin;
|
|
2071
|
+
const responseMode = normalizeMode(planResponse.mode) ?? mode;
|
|
2072
|
+
const workspace = planResponse.projectRoot;
|
|
2073
|
+
const sourcePlanPath = session.sourcePlanPath ?? planResponse.filePath;
|
|
2074
|
+
const metadata = {
|
|
2075
|
+
kind: "live-session",
|
|
2076
|
+
mode: responseMode,
|
|
2077
|
+
status: "pending",
|
|
2078
|
+
origin,
|
|
2079
|
+
url: session.url,
|
|
2080
|
+
pid: session.pid,
|
|
2081
|
+
port: session.port,
|
|
2082
|
+
project: session.project ?? planResponse.versionInfo?.project,
|
|
2083
|
+
label: session.label,
|
|
2084
|
+
reviewId: session.reviewId,
|
|
2085
|
+
sessionPath: filePath,
|
|
2086
|
+
sourcePlanPath,
|
|
2087
|
+
startedAt: session.startedAt,
|
|
2088
|
+
writebackCapable: true
|
|
2089
|
+
};
|
|
2090
|
+
return [
|
|
2091
|
+
{
|
|
2092
|
+
id: hashPath(filePath),
|
|
2093
|
+
agent: origin ?? "plannotator",
|
|
2094
|
+
title: extractTitle5(planResponse.plan, session.label ?? filePath),
|
|
2095
|
+
content: planResponse.plan,
|
|
2096
|
+
filePath: sourcePlanPath ?? filePath,
|
|
2097
|
+
format: "md",
|
|
2098
|
+
createdAt,
|
|
2099
|
+
updatedAt: stats.mtime,
|
|
2100
|
+
workspace,
|
|
2101
|
+
metadata: metadataRecord(metadata)
|
|
2102
|
+
}
|
|
2103
|
+
];
|
|
2104
|
+
} catch {
|
|
2105
|
+
return [];
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
function getPlannotatorMetadata(plan) {
|
|
2109
|
+
const metadata = plan.metadata.plannotator;
|
|
2110
|
+
if (!isRecord3(metadata))
|
|
2111
|
+
return;
|
|
2112
|
+
return metadata;
|
|
2113
|
+
}
|
|
2114
|
+
function formatWritebackFeedback(_plan, payload) {
|
|
2115
|
+
const sections = ["# Agendex Plan Feedback", "The user reviewed this plan in Agendex Cloud."];
|
|
2116
|
+
if (payload.feedback.trim()) {
|
|
2117
|
+
sections.push("## Feedback", payload.feedback.trim());
|
|
2118
|
+
}
|
|
2119
|
+
if (payload.revisedContent?.trim()) {
|
|
2120
|
+
sections.push("## Requested revision", "Use this revised plan content as the target shape when you update and resubmit the plan:", payload.revisedContent.trim());
|
|
2121
|
+
}
|
|
2122
|
+
if (payload.annotations?.length) {
|
|
2123
|
+
sections.push("## Typed annotations", JSON.stringify(payload.annotations, null, 2));
|
|
2124
|
+
}
|
|
2125
|
+
sections.push("Please revise the plan and resubmit it for Plannotator review.");
|
|
2126
|
+
return sections.join(`
|
|
2127
|
+
|
|
2128
|
+
`);
|
|
2129
|
+
}
|
|
2130
|
+
function annotationsForEndpoint(annotations) {
|
|
2131
|
+
return annotations ?? [];
|
|
2132
|
+
}
|
|
2133
|
+
var plannotatorAdapter = {
|
|
2134
|
+
agent: "plannotator",
|
|
2135
|
+
writable: false,
|
|
2136
|
+
getSearchPaths() {
|
|
2137
|
+
return [getPlansDir(), getSessionsDir()];
|
|
2138
|
+
},
|
|
2139
|
+
getWatchPaths() {
|
|
2140
|
+
return [getPlansDir(), getSessionsDir()];
|
|
2141
|
+
},
|
|
2142
|
+
matches(filePath) {
|
|
2143
|
+
const normalized = resolve2(filePath);
|
|
2144
|
+
const plansDir2 = resolve2(getPlansDir());
|
|
2145
|
+
const sessionsDir2 = resolve2(getSessionsDir());
|
|
2146
|
+
if (normalized.startsWith(plansDir2 + sep2))
|
|
2147
|
+
return isSnapshotFile(filePath) || isAnnotationFile(filePath);
|
|
2148
|
+
if (normalized.startsWith(sessionsDir2 + sep2))
|
|
2149
|
+
return isSessionFile2(filePath);
|
|
2150
|
+
if (isProjectPlansPath(filePath))
|
|
2151
|
+
return isProjectPlanFile(filePath) || isAnnotationFile(filePath);
|
|
2152
|
+
return false;
|
|
2153
|
+
},
|
|
2154
|
+
async parse(filePath) {
|
|
2155
|
+
const normalized = resolve2(filePath);
|
|
2156
|
+
const plansDir2 = resolve2(getPlansDir());
|
|
2157
|
+
const sessionsDir2 = resolve2(getSessionsDir());
|
|
2158
|
+
if (normalized.startsWith(plansDir2 + sep2) && isSnapshotFile(filePath)) {
|
|
2159
|
+
return await parseSnapshot(filePath);
|
|
2160
|
+
}
|
|
2161
|
+
if (normalized.startsWith(plansDir2 + sep2) && isAnnotationFile(filePath)) {
|
|
2162
|
+
return await parseAnnotationCompanion(filePath);
|
|
2163
|
+
}
|
|
2164
|
+
if (normalized.startsWith(sessionsDir2 + sep2) && isSessionFile2(filePath)) {
|
|
2165
|
+
return await parseLiveSession(filePath);
|
|
2166
|
+
}
|
|
2167
|
+
if (isProjectPlanFile(filePath)) {
|
|
2168
|
+
return await parseProjectPlan(filePath);
|
|
2169
|
+
}
|
|
2170
|
+
if (isProjectPlansPath(filePath) && isAnnotationFile(filePath)) {
|
|
2171
|
+
return await parseProjectAnnotationCompanion(filePath);
|
|
2172
|
+
}
|
|
2173
|
+
return [];
|
|
2174
|
+
},
|
|
2175
|
+
async write() {
|
|
2176
|
+
return false;
|
|
2177
|
+
},
|
|
2178
|
+
async requestChanges(plan, payload) {
|
|
2179
|
+
const metadata = getPlannotatorMetadata(plan);
|
|
2180
|
+
if (!metadata?.url || !metadata.writebackCapable)
|
|
2181
|
+
return false;
|
|
2182
|
+
if (!isSafePlannotatorUrl(metadata.url))
|
|
2183
|
+
return false;
|
|
2184
|
+
const feedback = formatWritebackFeedback(plan, payload);
|
|
2185
|
+
if (metadata.mode === "review" || metadata.mode === "annotate") {
|
|
2186
|
+
return await postJson(apiUrl(metadata.url, "/api/feedback"), {
|
|
2187
|
+
feedback,
|
|
2188
|
+
annotations: annotationsForEndpoint(payload.annotations)
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
return await postJson(apiUrl(metadata.url, "/api/deny"), {
|
|
2192
|
+
feedback,
|
|
2193
|
+
planSave: { enabled: true }
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
|
|
1751
2198
|
// ../shared/src/adapters/stub.ts
|
|
1752
2199
|
function createStubAdapter(agent, searchPaths, matchExt) {
|
|
1753
2200
|
return {
|
|
@@ -1762,7 +2209,7 @@ function createStubAdapter(agent, searchPaths, matchExt) {
|
|
|
1762
2209
|
}
|
|
1763
2210
|
|
|
1764
2211
|
// ../shared/src/adapters/catalog.ts
|
|
1765
|
-
var home =
|
|
2212
|
+
var home = homedir7();
|
|
1766
2213
|
var ADAPTER_AGENT_ALIASES = {
|
|
1767
2214
|
amp: "amp",
|
|
1768
2215
|
antigravity: "antigravity",
|
|
@@ -1792,6 +2239,7 @@ var ADAPTER_AGENT_ALIASES = {
|
|
|
1792
2239
|
opencode: "oh-my-opencode",
|
|
1793
2240
|
openhands: "openhands",
|
|
1794
2241
|
pi: "pi",
|
|
2242
|
+
plannotator: "plannotator",
|
|
1795
2243
|
qoder: "qoder",
|
|
1796
2244
|
"qwen-code": "qwen-code",
|
|
1797
2245
|
replit: "replit",
|
|
@@ -1812,7 +2260,7 @@ var CATALOG = [
|
|
|
1812
2260
|
group: "universal",
|
|
1813
2261
|
implemented: false,
|
|
1814
2262
|
defaultEnabled: true,
|
|
1815
|
-
createAdapter: () => createStubAdapter("amp", [
|
|
2263
|
+
createAdapter: () => createStubAdapter("amp", [join7(home, ".amp")], ".json")
|
|
1816
2264
|
},
|
|
1817
2265
|
{
|
|
1818
2266
|
id: "antigravity",
|
|
@@ -1820,7 +2268,7 @@ var CATALOG = [
|
|
|
1820
2268
|
group: "other",
|
|
1821
2269
|
implemented: false,
|
|
1822
2270
|
defaultEnabled: false,
|
|
1823
|
-
createAdapter: () => createStubAdapter("antigravity", [
|
|
2271
|
+
createAdapter: () => createStubAdapter("antigravity", [join7(home, ".gemini", "antigravity")], ".json")
|
|
1824
2272
|
},
|
|
1825
2273
|
{
|
|
1826
2274
|
id: "augment",
|
|
@@ -1828,7 +2276,7 @@ var CATALOG = [
|
|
|
1828
2276
|
group: "other",
|
|
1829
2277
|
implemented: false,
|
|
1830
2278
|
defaultEnabled: false,
|
|
1831
|
-
createAdapter: () => createStubAdapter("augment", [
|
|
2279
|
+
createAdapter: () => createStubAdapter("augment", [join7(home, ".augment")], ".json")
|
|
1832
2280
|
},
|
|
1833
2281
|
{
|
|
1834
2282
|
id: "claude-code",
|
|
@@ -1844,7 +2292,7 @@ var CATALOG = [
|
|
|
1844
2292
|
group: "other",
|
|
1845
2293
|
implemented: false,
|
|
1846
2294
|
defaultEnabled: false,
|
|
1847
|
-
createAdapter: () => createStubAdapter("openclaw", [
|
|
2295
|
+
createAdapter: () => createStubAdapter("openclaw", [join7(home, ".openclaw")], ".md")
|
|
1848
2296
|
},
|
|
1849
2297
|
{
|
|
1850
2298
|
id: "cline",
|
|
@@ -1853,8 +2301,8 @@ var CATALOG = [
|
|
|
1853
2301
|
implemented: false,
|
|
1854
2302
|
defaultEnabled: true,
|
|
1855
2303
|
createAdapter: () => createStubAdapter("cline", [
|
|
1856
|
-
|
|
1857
|
-
|
|
2304
|
+
join7(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev"),
|
|
2305
|
+
join7(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
|
|
1858
2306
|
], ".json")
|
|
1859
2307
|
},
|
|
1860
2308
|
{
|
|
@@ -1863,7 +2311,7 @@ var CATALOG = [
|
|
|
1863
2311
|
group: "other",
|
|
1864
2312
|
implemented: false,
|
|
1865
2313
|
defaultEnabled: false,
|
|
1866
|
-
createAdapter: () => createStubAdapter("codebuddy", [
|
|
2314
|
+
createAdapter: () => createStubAdapter("codebuddy", [join7(home, ".codebuddy")], ".md")
|
|
1867
2315
|
},
|
|
1868
2316
|
{
|
|
1869
2317
|
id: "codex",
|
|
@@ -1879,7 +2327,7 @@ var CATALOG = [
|
|
|
1879
2327
|
group: "other",
|
|
1880
2328
|
implemented: false,
|
|
1881
2329
|
defaultEnabled: false,
|
|
1882
|
-
createAdapter: () => createStubAdapter("command-code", [
|
|
2330
|
+
createAdapter: () => createStubAdapter("command-code", [join7(home, ".commandcode")], ".md")
|
|
1883
2331
|
},
|
|
1884
2332
|
{
|
|
1885
2333
|
id: "continue",
|
|
@@ -1895,7 +2343,7 @@ var CATALOG = [
|
|
|
1895
2343
|
group: "other",
|
|
1896
2344
|
implemented: false,
|
|
1897
2345
|
defaultEnabled: false,
|
|
1898
|
-
createAdapter: () => createStubAdapter("crush", [
|
|
2346
|
+
createAdapter: () => createStubAdapter("crush", [join7(home, ".config", "crush")], ".json")
|
|
1899
2347
|
},
|
|
1900
2348
|
{
|
|
1901
2349
|
id: "cursor",
|
|
@@ -1911,7 +2359,7 @@ var CATALOG = [
|
|
|
1911
2359
|
group: "other",
|
|
1912
2360
|
implemented: false,
|
|
1913
2361
|
defaultEnabled: true,
|
|
1914
|
-
createAdapter: () => createStubAdapter("droid", [
|
|
2362
|
+
createAdapter: () => createStubAdapter("droid", [join7(home, ".factory", "droids")], ".md")
|
|
1915
2363
|
},
|
|
1916
2364
|
{
|
|
1917
2365
|
id: "gemini-cli",
|
|
@@ -1919,7 +2367,7 @@ var CATALOG = [
|
|
|
1919
2367
|
group: "universal",
|
|
1920
2368
|
implemented: false,
|
|
1921
2369
|
defaultEnabled: false,
|
|
1922
|
-
createAdapter: () => createStubAdapter("gemini-cli", [
|
|
2370
|
+
createAdapter: () => createStubAdapter("gemini-cli", [join7(home, ".gemini")], ".md")
|
|
1923
2371
|
},
|
|
1924
2372
|
{
|
|
1925
2373
|
id: "github-copilot",
|
|
@@ -1927,7 +2375,7 @@ var CATALOG = [
|
|
|
1927
2375
|
group: "universal",
|
|
1928
2376
|
implemented: false,
|
|
1929
2377
|
defaultEnabled: true,
|
|
1930
|
-
createAdapter: () => createStubAdapter("copilot-chat", [
|
|
2378
|
+
createAdapter: () => createStubAdapter("copilot-chat", [join7(home, ".vscode", "User", "workspaceStorage")], ".json")
|
|
1931
2379
|
},
|
|
1932
2380
|
{
|
|
1933
2381
|
id: "goose",
|
|
@@ -1935,7 +2383,7 @@ var CATALOG = [
|
|
|
1935
2383
|
group: "other",
|
|
1936
2384
|
implemented: false,
|
|
1937
2385
|
defaultEnabled: false,
|
|
1938
|
-
createAdapter: () => createStubAdapter("goose", [
|
|
2386
|
+
createAdapter: () => createStubAdapter("goose", [join7(home, ".config", "goose")], ".json")
|
|
1939
2387
|
},
|
|
1940
2388
|
{
|
|
1941
2389
|
id: "junie",
|
|
@@ -1943,7 +2391,7 @@ var CATALOG = [
|
|
|
1943
2391
|
group: "other",
|
|
1944
2392
|
implemented: false,
|
|
1945
2393
|
defaultEnabled: false,
|
|
1946
|
-
createAdapter: () => createStubAdapter("junie", [
|
|
2394
|
+
createAdapter: () => createStubAdapter("junie", [join7(home, ".junie")], ".json")
|
|
1947
2395
|
},
|
|
1948
2396
|
{
|
|
1949
2397
|
id: "iflow-cli",
|
|
@@ -1951,7 +2399,7 @@ var CATALOG = [
|
|
|
1951
2399
|
group: "other",
|
|
1952
2400
|
implemented: false,
|
|
1953
2401
|
defaultEnabled: false,
|
|
1954
|
-
createAdapter: () => createStubAdapter("iflow-cli", [
|
|
2402
|
+
createAdapter: () => createStubAdapter("iflow-cli", [join7(home, ".iflow")], ".json")
|
|
1955
2403
|
},
|
|
1956
2404
|
{
|
|
1957
2405
|
id: "kilo",
|
|
@@ -1959,7 +2407,7 @@ var CATALOG = [
|
|
|
1959
2407
|
group: "other",
|
|
1960
2408
|
implemented: false,
|
|
1961
2409
|
defaultEnabled: true,
|
|
1962
|
-
createAdapter: () => createStubAdapter("kilo-cli", [
|
|
2410
|
+
createAdapter: () => createStubAdapter("kilo-cli", [join7(home, ".kilo")], ".md")
|
|
1963
2411
|
},
|
|
1964
2412
|
{
|
|
1965
2413
|
id: "kimi-cli",
|
|
@@ -1967,7 +2415,7 @@ var CATALOG = [
|
|
|
1967
2415
|
group: "universal",
|
|
1968
2416
|
implemented: false,
|
|
1969
2417
|
defaultEnabled: false,
|
|
1970
|
-
createAdapter: () => createStubAdapter("kimi-cli", [
|
|
2418
|
+
createAdapter: () => createStubAdapter("kimi-cli", [join7(home, ".kimi")], ".md")
|
|
1971
2419
|
},
|
|
1972
2420
|
{
|
|
1973
2421
|
id: "kiro-cli",
|
|
@@ -1975,7 +2423,7 @@ var CATALOG = [
|
|
|
1975
2423
|
group: "other",
|
|
1976
2424
|
implemented: false,
|
|
1977
2425
|
defaultEnabled: false,
|
|
1978
|
-
createAdapter: () => createStubAdapter("kiro-cli", [
|
|
2426
|
+
createAdapter: () => createStubAdapter("kiro-cli", [join7(home, ".kiro")], ".json")
|
|
1979
2427
|
},
|
|
1980
2428
|
{
|
|
1981
2429
|
id: "kode",
|
|
@@ -1983,7 +2431,7 @@ var CATALOG = [
|
|
|
1983
2431
|
group: "other",
|
|
1984
2432
|
implemented: false,
|
|
1985
2433
|
defaultEnabled: false,
|
|
1986
|
-
createAdapter: () => createStubAdapter("kode", [
|
|
2434
|
+
createAdapter: () => createStubAdapter("kode", [join7(home, ".kode")], ".json")
|
|
1987
2435
|
},
|
|
1988
2436
|
{
|
|
1989
2437
|
id: "mcpjam",
|
|
@@ -1991,7 +2439,7 @@ var CATALOG = [
|
|
|
1991
2439
|
group: "other",
|
|
1992
2440
|
implemented: false,
|
|
1993
2441
|
defaultEnabled: false,
|
|
1994
|
-
createAdapter: () => createStubAdapter("mcpjam", [
|
|
2442
|
+
createAdapter: () => createStubAdapter("mcpjam", [join7(home, ".mcpjam")], ".json")
|
|
1995
2443
|
},
|
|
1996
2444
|
{
|
|
1997
2445
|
id: "mistral-vibe",
|
|
@@ -1999,7 +2447,7 @@ var CATALOG = [
|
|
|
1999
2447
|
group: "other",
|
|
2000
2448
|
implemented: false,
|
|
2001
2449
|
defaultEnabled: false,
|
|
2002
|
-
createAdapter: () => createStubAdapter("mistral-vibe", [
|
|
2450
|
+
createAdapter: () => createStubAdapter("mistral-vibe", [join7(home, ".vibe")], ".json")
|
|
2003
2451
|
},
|
|
2004
2452
|
{
|
|
2005
2453
|
id: "mux",
|
|
@@ -2007,7 +2455,7 @@ var CATALOG = [
|
|
|
2007
2455
|
group: "other",
|
|
2008
2456
|
implemented: false,
|
|
2009
2457
|
defaultEnabled: false,
|
|
2010
|
-
createAdapter: () => createStubAdapter("mux", [
|
|
2458
|
+
createAdapter: () => createStubAdapter("mux", [join7(home, ".mux")], ".json")
|
|
2011
2459
|
},
|
|
2012
2460
|
{
|
|
2013
2461
|
id: "opencode",
|
|
@@ -2023,7 +2471,7 @@ var CATALOG = [
|
|
|
2023
2471
|
group: "other",
|
|
2024
2472
|
implemented: false,
|
|
2025
2473
|
defaultEnabled: false,
|
|
2026
|
-
createAdapter: () => createStubAdapter("openhands", [
|
|
2474
|
+
createAdapter: () => createStubAdapter("openhands", [join7(home, ".openhands")], ".json")
|
|
2027
2475
|
},
|
|
2028
2476
|
{
|
|
2029
2477
|
id: "pi",
|
|
@@ -2031,7 +2479,15 @@ var CATALOG = [
|
|
|
2031
2479
|
group: "other",
|
|
2032
2480
|
implemented: false,
|
|
2033
2481
|
defaultEnabled: false,
|
|
2034
|
-
createAdapter: () => createStubAdapter("pi", [
|
|
2482
|
+
createAdapter: () => createStubAdapter("pi", [join7(home, ".pi", "agent")], ".md")
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
id: "plannotator",
|
|
2486
|
+
displayName: "Plannotator",
|
|
2487
|
+
group: "universal",
|
|
2488
|
+
implemented: true,
|
|
2489
|
+
defaultEnabled: false,
|
|
2490
|
+
createAdapter: () => plannotatorAdapter
|
|
2035
2491
|
},
|
|
2036
2492
|
{
|
|
2037
2493
|
id: "qoder",
|
|
@@ -2039,7 +2495,7 @@ var CATALOG = [
|
|
|
2039
2495
|
group: "other",
|
|
2040
2496
|
implemented: false,
|
|
2041
2497
|
defaultEnabled: false,
|
|
2042
|
-
createAdapter: () => createStubAdapter("qoder", [
|
|
2498
|
+
createAdapter: () => createStubAdapter("qoder", [join7(home, ".qoder")], ".json")
|
|
2043
2499
|
},
|
|
2044
2500
|
{
|
|
2045
2501
|
id: "qwen-code",
|
|
@@ -2047,7 +2503,7 @@ var CATALOG = [
|
|
|
2047
2503
|
group: "other",
|
|
2048
2504
|
implemented: false,
|
|
2049
2505
|
defaultEnabled: false,
|
|
2050
|
-
createAdapter: () => createStubAdapter("qwen-code", [
|
|
2506
|
+
createAdapter: () => createStubAdapter("qwen-code", [join7(home, ".qwen")], ".json")
|
|
2051
2507
|
},
|
|
2052
2508
|
{
|
|
2053
2509
|
id: "replit",
|
|
@@ -2055,7 +2511,7 @@ var CATALOG = [
|
|
|
2055
2511
|
group: "other",
|
|
2056
2512
|
implemented: false,
|
|
2057
2513
|
defaultEnabled: false,
|
|
2058
|
-
createAdapter: () => createStubAdapter("replit", [
|
|
2514
|
+
createAdapter: () => createStubAdapter("replit", [join7(home, ".replit")], ".md")
|
|
2059
2515
|
},
|
|
2060
2516
|
{
|
|
2061
2517
|
id: "roo",
|
|
@@ -2063,7 +2519,7 @@ var CATALOG = [
|
|
|
2063
2519
|
group: "other",
|
|
2064
2520
|
implemented: false,
|
|
2065
2521
|
defaultEnabled: false,
|
|
2066
|
-
createAdapter: () => createStubAdapter("roo", [
|
|
2522
|
+
createAdapter: () => createStubAdapter("roo", [join7(home, ".roo")], ".md")
|
|
2067
2523
|
},
|
|
2068
2524
|
{
|
|
2069
2525
|
id: "trae",
|
|
@@ -2071,7 +2527,7 @@ var CATALOG = [
|
|
|
2071
2527
|
group: "other",
|
|
2072
2528
|
implemented: false,
|
|
2073
2529
|
defaultEnabled: false,
|
|
2074
|
-
createAdapter: () => createStubAdapter("trae", [
|
|
2530
|
+
createAdapter: () => createStubAdapter("trae", [join7(home, ".trae")], ".md")
|
|
2075
2531
|
},
|
|
2076
2532
|
{
|
|
2077
2533
|
id: "trae-cn",
|
|
@@ -2079,7 +2535,7 @@ var CATALOG = [
|
|
|
2079
2535
|
group: "other",
|
|
2080
2536
|
implemented: false,
|
|
2081
2537
|
defaultEnabled: false,
|
|
2082
|
-
createAdapter: () => createStubAdapter("trae-cn", [
|
|
2538
|
+
createAdapter: () => createStubAdapter("trae-cn", [join7(home, ".trae-cn")], ".md")
|
|
2083
2539
|
},
|
|
2084
2540
|
{
|
|
2085
2541
|
id: "windsurf",
|
|
@@ -2087,7 +2543,7 @@ var CATALOG = [
|
|
|
2087
2543
|
group: "other",
|
|
2088
2544
|
implemented: false,
|
|
2089
2545
|
defaultEnabled: true,
|
|
2090
|
-
createAdapter: () => createStubAdapter("windsurf", [
|
|
2546
|
+
createAdapter: () => createStubAdapter("windsurf", [join7(home, ".cascade_backups")], ".md")
|
|
2091
2547
|
},
|
|
2092
2548
|
{
|
|
2093
2549
|
id: "zencoder",
|
|
@@ -2095,7 +2551,7 @@ var CATALOG = [
|
|
|
2095
2551
|
group: "other",
|
|
2096
2552
|
implemented: false,
|
|
2097
2553
|
defaultEnabled: false,
|
|
2098
|
-
createAdapter: () => createStubAdapter("zencoder", [
|
|
2554
|
+
createAdapter: () => createStubAdapter("zencoder", [join7(home, ".zencoder")], ".json")
|
|
2099
2555
|
},
|
|
2100
2556
|
{
|
|
2101
2557
|
id: "neovate",
|
|
@@ -2103,7 +2559,7 @@ var CATALOG = [
|
|
|
2103
2559
|
group: "other",
|
|
2104
2560
|
implemented: false,
|
|
2105
2561
|
defaultEnabled: false,
|
|
2106
|
-
createAdapter: () => createStubAdapter("neovate", [
|
|
2562
|
+
createAdapter: () => createStubAdapter("neovate", [join7(home, ".neovate")], ".json")
|
|
2107
2563
|
},
|
|
2108
2564
|
{
|
|
2109
2565
|
id: "pochi",
|
|
@@ -2111,7 +2567,7 @@ var CATALOG = [
|
|
|
2111
2567
|
group: "other",
|
|
2112
2568
|
implemented: false,
|
|
2113
2569
|
defaultEnabled: false,
|
|
2114
|
-
createAdapter: () => createStubAdapter("pochi", [
|
|
2570
|
+
createAdapter: () => createStubAdapter("pochi", [join7(home, ".pochi")], ".json")
|
|
2115
2571
|
},
|
|
2116
2572
|
{
|
|
2117
2573
|
id: "adal",
|
|
@@ -2119,7 +2575,7 @@ var CATALOG = [
|
|
|
2119
2575
|
group: "other",
|
|
2120
2576
|
implemented: false,
|
|
2121
2577
|
defaultEnabled: false,
|
|
2122
|
-
createAdapter: () => createStubAdapter("adal", [
|
|
2578
|
+
createAdapter: () => createStubAdapter("adal", [join7(home, ".adal")], ".json")
|
|
2123
2579
|
},
|
|
2124
2580
|
{
|
|
2125
2581
|
id: "aider",
|
|
@@ -2127,7 +2583,7 @@ var CATALOG = [
|
|
|
2127
2583
|
group: "other",
|
|
2128
2584
|
implemented: false,
|
|
2129
2585
|
defaultEnabled: true,
|
|
2130
|
-
createAdapter: () => createStubAdapter("aider", [
|
|
2586
|
+
createAdapter: () => createStubAdapter("aider", [join7(home, ".aider")], ".aider.chat.history.md")
|
|
2131
2587
|
}
|
|
2132
2588
|
];
|
|
2133
2589
|
function getAdapterCatalog() {
|
|
@@ -2198,9 +2654,9 @@ function getActiveAdapters() {
|
|
|
2198
2654
|
var activeAdapters = resolveAdapters(getDefaultAdapterIds());
|
|
2199
2655
|
// ../shared/src/config.ts
|
|
2200
2656
|
import { randomBytes } from "node:crypto";
|
|
2201
|
-
import { existsSync as
|
|
2202
|
-
import { homedir as
|
|
2203
|
-
import { join as
|
|
2657
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
|
2658
|
+
import { homedir as homedir8 } from "node:os";
|
|
2659
|
+
import { join as join8, resolve as resolve3 } from "node:path";
|
|
2204
2660
|
|
|
2205
2661
|
// ../shared/src/setup/adapter-selection.ts
|
|
2206
2662
|
init_dist2();
|
|
@@ -2264,7 +2720,7 @@ function getHomeDir() {
|
|
|
2264
2720
|
if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
|
|
2265
2721
|
return `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`;
|
|
2266
2722
|
}
|
|
2267
|
-
return
|
|
2723
|
+
return homedir8();
|
|
2268
2724
|
}
|
|
2269
2725
|
function setDevMode(dev) {
|
|
2270
2726
|
devModeOverride = dev;
|
|
@@ -2275,16 +2731,19 @@ function isDevMode() {
|
|
|
2275
2731
|
return process.env.AGENDEX_DEV === "1";
|
|
2276
2732
|
}
|
|
2277
2733
|
function getConfigDir() {
|
|
2278
|
-
|
|
2734
|
+
const override = process.env.AGENDEX_CONFIG_DIR?.trim();
|
|
2735
|
+
if (override)
|
|
2736
|
+
return resolve3(expandHomePath(override));
|
|
2737
|
+
return join8(getHomeDir(), isDevMode() ? ".agendex-dev" : ".agendex");
|
|
2279
2738
|
}
|
|
2280
2739
|
function ensureConfigDir() {
|
|
2281
2740
|
const dir = getConfigDir();
|
|
2282
|
-
if (!
|
|
2741
|
+
if (!existsSync4(dir))
|
|
2283
2742
|
mkdirSync(dir, { recursive: true });
|
|
2284
2743
|
}
|
|
2285
2744
|
function readStoredConfig() {
|
|
2286
2745
|
const cfgPath = getConfigPath();
|
|
2287
|
-
if (!
|
|
2746
|
+
if (!existsSync4(cfgPath))
|
|
2288
2747
|
return null;
|
|
2289
2748
|
try {
|
|
2290
2749
|
const raw = JSON.parse(readFileSync3(cfgPath, "utf-8"));
|
|
@@ -2302,7 +2761,7 @@ function normalizeAdapterIds(input) {
|
|
|
2302
2761
|
}
|
|
2303
2762
|
function expandHomePath(p) {
|
|
2304
2763
|
if (p.startsWith("~/") || p === "~")
|
|
2305
|
-
return
|
|
2764
|
+
return join8(getHomeDir(), p.slice(1));
|
|
2306
2765
|
return p;
|
|
2307
2766
|
}
|
|
2308
2767
|
function resolveCustomPlanDirPath(userPath) {
|
|
@@ -2310,7 +2769,7 @@ function resolveCustomPlanDirPath(userPath) {
|
|
|
2310
2769
|
if (!trimmed) {
|
|
2311
2770
|
throw new Error("Custom plan directory path must not be empty");
|
|
2312
2771
|
}
|
|
2313
|
-
return
|
|
2772
|
+
return resolve3(expandHomePath(trimmed));
|
|
2314
2773
|
}
|
|
2315
2774
|
function normalizeCustomPlanDirs(input) {
|
|
2316
2775
|
if (!Array.isArray(input))
|
|
@@ -2375,6 +2834,7 @@ function loadOrCreateToken() {
|
|
|
2375
2834
|
return existing.token;
|
|
2376
2835
|
const token = generateToken();
|
|
2377
2836
|
saveConfig({
|
|
2837
|
+
...existing ?? {},
|
|
2378
2838
|
configVersion: 3,
|
|
2379
2839
|
token,
|
|
2380
2840
|
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
@@ -2401,7 +2861,7 @@ function loadOrCreateDeviceId() {
|
|
|
2401
2861
|
return deviceId;
|
|
2402
2862
|
}
|
|
2403
2863
|
function getConfigPath() {
|
|
2404
|
-
return
|
|
2864
|
+
return join8(getConfigDir(), "config.json");
|
|
2405
2865
|
}
|
|
2406
2866
|
async function loadOrInitConfig(options = {}) {
|
|
2407
2867
|
const configureAdapters = Boolean(options.configureAdapters);
|
|
@@ -2448,9 +2908,10 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2448
2908
|
var CLI_DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
|
|
2449
2909
|
var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
|
|
2450
2910
|
// ../shared/src/services/plan-service.ts
|
|
2451
|
-
import { existsSync as
|
|
2452
|
-
import { lstat, mkdir, readdir as readdir2, readFile as
|
|
2453
|
-
import {
|
|
2911
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, realpathSync, statSync } from "node:fs";
|
|
2912
|
+
import { lstat, mkdir, readdir as readdir2, readFile as readFile7, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
|
|
2913
|
+
import { homedir as homedir9 } from "node:os";
|
|
2914
|
+
import { dirname as dirname2, join as join9, resolve as resolve4, sep as sep3 } from "node:path";
|
|
2454
2915
|
|
|
2455
2916
|
// ../shared/src/services/plan-value.ts
|
|
2456
2917
|
function isLowValuePlan(plan) {
|
|
@@ -2780,12 +3241,15 @@ function annotatePlanValueMetadata(plan) {
|
|
|
2780
3241
|
|
|
2781
3242
|
// ../shared/src/services/plan-service.ts
|
|
2782
3243
|
function getUserPlansDir() {
|
|
2783
|
-
return
|
|
3244
|
+
return join9(getConfigDir(), "plans");
|
|
2784
3245
|
}
|
|
2785
3246
|
var store = new Map;
|
|
2786
3247
|
var MAX_DEPTH = 6;
|
|
2787
3248
|
var DISCOVERY_MAX_DEPTH = 4;
|
|
2788
|
-
var PROJECT_PLAN_MARKERS = [
|
|
3249
|
+
var PROJECT_PLAN_MARKERS = [
|
|
3250
|
+
{ marker: ".sisyphus/plans", agent: "oh-my-opencode" },
|
|
3251
|
+
{ marker: "@plans", agent: "plannotator" }
|
|
3252
|
+
];
|
|
2789
3253
|
var SKIP_DIRS = new Set([
|
|
2790
3254
|
"node_modules",
|
|
2791
3255
|
".git",
|
|
@@ -2818,19 +3282,58 @@ var SKIP_DIRS = new Set([
|
|
|
2818
3282
|
"Google Drive",
|
|
2819
3283
|
"iCloud Drive"
|
|
2820
3284
|
]);
|
|
3285
|
+
function getRuntimeHomeDir() {
|
|
3286
|
+
const homeFromEnv = process.env.HOME || process.env.USERPROFILE || (process.env.HOMEDRIVE && process.env.HOMEPATH ? `${process.env.HOMEDRIVE}${process.env.HOMEPATH}` : undefined);
|
|
3287
|
+
return resolve4(homeFromEnv || homedir9());
|
|
3288
|
+
}
|
|
2821
3289
|
function discoverProjectPlanDirs() {
|
|
2822
|
-
const home2 =
|
|
3290
|
+
const home2 = canonicalPath(getRuntimeHomeDir());
|
|
2823
3291
|
const results = [];
|
|
2824
|
-
|
|
2825
|
-
|
|
3292
|
+
const seen = new Set;
|
|
3293
|
+
let nearestAncestorMarkerRoot;
|
|
3294
|
+
function addResult(dir, agent) {
|
|
3295
|
+
const resolved = canonicalPath(dir);
|
|
3296
|
+
const key = `${agent}:${resolved}`;
|
|
3297
|
+
if (seen.has(key))
|
|
2826
3298
|
return;
|
|
3299
|
+
seen.add(key);
|
|
3300
|
+
results.push({ dir: resolved, agent });
|
|
3301
|
+
}
|
|
3302
|
+
function inspectMarkers(dir) {
|
|
3303
|
+
let found = false;
|
|
2827
3304
|
for (const { marker, agent } of PROJECT_PLAN_MARKERS) {
|
|
2828
|
-
const candidate =
|
|
2829
|
-
if (
|
|
2830
|
-
|
|
3305
|
+
const candidate = join9(dir, marker);
|
|
3306
|
+
if (existsSync5(candidate)) {
|
|
3307
|
+
addResult(candidate, agent);
|
|
3308
|
+
found = true;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
return found;
|
|
3312
|
+
}
|
|
3313
|
+
function walkAncestorsForCurrentProject() {
|
|
3314
|
+
let dir = canonicalPath(process.cwd());
|
|
3315
|
+
while (true) {
|
|
3316
|
+
if (inspectMarkers(dir)) {
|
|
3317
|
+
nearestAncestorMarkerRoot = dir;
|
|
2831
3318
|
return;
|
|
2832
3319
|
}
|
|
3320
|
+
if (dir === home2)
|
|
3321
|
+
return;
|
|
3322
|
+
const parent = dirname2(dir);
|
|
3323
|
+
if (parent === dir)
|
|
3324
|
+
return;
|
|
3325
|
+
dir = parent;
|
|
2833
3326
|
}
|
|
3327
|
+
}
|
|
3328
|
+
function isAncestorOfNearestMarker(dir) {
|
|
3329
|
+
return Boolean(nearestAncestorMarkerRoot?.startsWith(dir + sep3));
|
|
3330
|
+
}
|
|
3331
|
+
function walk(dir, depth) {
|
|
3332
|
+
if (depth > DISCOVERY_MAX_DEPTH)
|
|
3333
|
+
return;
|
|
3334
|
+
const resolved = canonicalPath(dir);
|
|
3335
|
+
if (!isAncestorOfNearestMarker(resolved) && inspectMarkers(dir))
|
|
3336
|
+
return;
|
|
2834
3337
|
let names;
|
|
2835
3338
|
try {
|
|
2836
3339
|
names = readdirSync3(dir);
|
|
@@ -2842,13 +3345,14 @@ function discoverProjectPlanDirs() {
|
|
|
2842
3345
|
continue;
|
|
2843
3346
|
if (SKIP_DIRS.has(name))
|
|
2844
3347
|
continue;
|
|
2845
|
-
const full =
|
|
3348
|
+
const full = join9(dir, name);
|
|
2846
3349
|
try {
|
|
2847
3350
|
if (statSync(full).isDirectory())
|
|
2848
3351
|
walk(full, depth + 1);
|
|
2849
3352
|
} catch {}
|
|
2850
3353
|
}
|
|
2851
3354
|
}
|
|
3355
|
+
walkAncestorsForCurrentProject();
|
|
2852
3356
|
walk(home2, 0);
|
|
2853
3357
|
return results;
|
|
2854
3358
|
}
|
|
@@ -2862,9 +3366,9 @@ function notifyPlansChanged() {
|
|
|
2862
3366
|
async function walkDir(dir, depth = 0, seen = new Set) {
|
|
2863
3367
|
if (depth > MAX_DEPTH)
|
|
2864
3368
|
return [];
|
|
2865
|
-
if (!
|
|
3369
|
+
if (!existsSync5(dir))
|
|
2866
3370
|
return [];
|
|
2867
|
-
const real =
|
|
3371
|
+
const real = resolve4(dir);
|
|
2868
3372
|
if (seen.has(real))
|
|
2869
3373
|
return [];
|
|
2870
3374
|
seen.add(real);
|
|
@@ -2872,7 +3376,7 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2872
3376
|
try {
|
|
2873
3377
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
2874
3378
|
for (const entry of entries) {
|
|
2875
|
-
const full =
|
|
3379
|
+
const full = join9(dir, entry.name);
|
|
2876
3380
|
try {
|
|
2877
3381
|
const stats = await lstat(full);
|
|
2878
3382
|
if (stats.isSymbolicLink())
|
|
@@ -2889,8 +3393,8 @@ async function walkDir(dir, depth = 0, seen = new Set) {
|
|
|
2889
3393
|
}
|
|
2890
3394
|
async function parseGenericMarkdownPlan(filePath, extraMetadata) {
|
|
2891
3395
|
try {
|
|
2892
|
-
const content = await
|
|
2893
|
-
const stats = await
|
|
3396
|
+
const content = await readFile7(filePath, "utf-8");
|
|
3397
|
+
const stats = await stat7(filePath);
|
|
2894
3398
|
let agent = (typeof extraMetadata.agentHint === "string" ? extraMetadata.agentHint : "") || "unknown";
|
|
2895
3399
|
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
2896
3400
|
if (fmMatch) {
|
|
@@ -2918,7 +3422,7 @@ async function parseGenericMarkdownPlan(filePath, extraMetadata) {
|
|
|
2918
3422
|
}
|
|
2919
3423
|
async function scanUserPlans(into) {
|
|
2920
3424
|
const userPlansDir = getUserPlansDir();
|
|
2921
|
-
if (!
|
|
3425
|
+
if (!existsSync5(userPlansDir))
|
|
2922
3426
|
return;
|
|
2923
3427
|
const files = await walkDir(userPlansDir);
|
|
2924
3428
|
for (const file of files) {
|
|
@@ -2932,19 +3436,27 @@ async function scanUserPlans(into) {
|
|
|
2932
3436
|
function getCustomPlanDirs() {
|
|
2933
3437
|
return loadConfig()?.customPlanDirs ?? [];
|
|
2934
3438
|
}
|
|
3439
|
+
function canonicalPath(path) {
|
|
3440
|
+
const resolved = resolve4(path);
|
|
3441
|
+
try {
|
|
3442
|
+
return realpathSync(resolved);
|
|
3443
|
+
} catch {
|
|
3444
|
+
return resolved;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
2935
3447
|
function pathsOverlapFilesystemTree(a, b) {
|
|
2936
|
-
const ra =
|
|
2937
|
-
const rb =
|
|
3448
|
+
const ra = canonicalPath(a);
|
|
3449
|
+
const rb = canonicalPath(b);
|
|
2938
3450
|
if (ra === rb)
|
|
2939
3451
|
return true;
|
|
2940
|
-
if (ra.startsWith(rb +
|
|
3452
|
+
if (ra.startsWith(rb + sep3))
|
|
2941
3453
|
return true;
|
|
2942
|
-
if (rb.startsWith(ra +
|
|
3454
|
+
if (rb.startsWith(ra + sep3))
|
|
2943
3455
|
return true;
|
|
2944
3456
|
return false;
|
|
2945
3457
|
}
|
|
2946
3458
|
function overlapsAnyRoot(candidate, roots) {
|
|
2947
|
-
const resolvedCandidate =
|
|
3459
|
+
const resolvedCandidate = canonicalPath(candidate);
|
|
2948
3460
|
for (const root of roots) {
|
|
2949
3461
|
if (pathsOverlapFilesystemTree(resolvedCandidate, root))
|
|
2950
3462
|
return true;
|
|
@@ -2953,9 +3465,9 @@ function overlapsAnyRoot(candidate, roots) {
|
|
|
2953
3465
|
}
|
|
2954
3466
|
async function scanCustomPlanDirs(coveredPaths, into) {
|
|
2955
3467
|
const dirs = getCustomPlanDirs();
|
|
2956
|
-
const userPlansDir =
|
|
3468
|
+
const userPlansDir = canonicalPath(getUserPlansDir());
|
|
2957
3469
|
for (const dir of dirs) {
|
|
2958
|
-
const resolved =
|
|
3470
|
+
const resolved = canonicalPath(dir);
|
|
2959
3471
|
if (overlapsAnyRoot(resolved, coveredPaths)) {
|
|
2960
3472
|
console.log(`[agendex] skipping custom dir (overlaps adapter / discovered coverage): ${dir}`);
|
|
2961
3473
|
continue;
|
|
@@ -2964,7 +3476,7 @@ async function scanCustomPlanDirs(coveredPaths, into) {
|
|
|
2964
3476
|
console.log(`[agendex] skipping custom dir (overlaps user plans): ${dir}`);
|
|
2965
3477
|
continue;
|
|
2966
3478
|
}
|
|
2967
|
-
if (!
|
|
3479
|
+
if (!existsSync5(dir)) {
|
|
2968
3480
|
console.log(`[agendex] skipping custom dir (not found): ${dir}`);
|
|
2969
3481
|
continue;
|
|
2970
3482
|
}
|
|
@@ -2993,7 +3505,7 @@ async function scan() {
|
|
|
2993
3505
|
const coveredPaths = new Set;
|
|
2994
3506
|
for (const adapter of adapters) {
|
|
2995
3507
|
for (const searchPath of adapter.getSearchPaths()) {
|
|
2996
|
-
coveredPaths.add(
|
|
3508
|
+
coveredPaths.add(canonicalPath(searchPath));
|
|
2997
3509
|
const files = await walkDir(searchPath);
|
|
2998
3510
|
for (const file of files) {
|
|
2999
3511
|
if (!adapter.matches(file))
|
|
@@ -3008,7 +3520,7 @@ async function scan() {
|
|
|
3008
3520
|
}
|
|
3009
3521
|
const discovered = discoverProjectPlanDirs();
|
|
3010
3522
|
for (const { dir, agent } of discovered) {
|
|
3011
|
-
const resolvedDir =
|
|
3523
|
+
const resolvedDir = canonicalPath(dir);
|
|
3012
3524
|
if (coveredPaths.has(resolvedDir))
|
|
3013
3525
|
continue;
|
|
3014
3526
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
@@ -3042,29 +3554,86 @@ function getAll() {
|
|
|
3042
3554
|
function getIndexablePlans() {
|
|
3043
3555
|
return getAll().filter(isIndexablePlan);
|
|
3044
3556
|
}
|
|
3557
|
+
function getById(id) {
|
|
3558
|
+
return store.get(id);
|
|
3559
|
+
}
|
|
3560
|
+
function getPlanSourceAdapter(plan) {
|
|
3561
|
+
const sourceAdapter = plan.metadata.sourceAdapter;
|
|
3562
|
+
return typeof sourceAdapter === "string" && sourceAdapter.trim() ? sourceAdapter : undefined;
|
|
3563
|
+
}
|
|
3564
|
+
function findAdapterForPlan(plan) {
|
|
3565
|
+
const adapters = getActiveAdapters();
|
|
3566
|
+
const sourceAdapter = getPlanSourceAdapter(plan);
|
|
3567
|
+
return adapters.find((adapter) => adapter.agent === sourceAdapter) ?? adapters.find((adapter) => adapter.agent === plan.agent);
|
|
3568
|
+
}
|
|
3569
|
+
async function requestChanges(id, payload) {
|
|
3570
|
+
const plan = store.get(id);
|
|
3571
|
+
if (!plan)
|
|
3572
|
+
return false;
|
|
3573
|
+
const adapter = findAdapterForPlan(plan);
|
|
3574
|
+
if (!adapter?.requestChanges)
|
|
3575
|
+
return false;
|
|
3576
|
+
const ok = await adapter.requestChanges(plan, payload);
|
|
3577
|
+
if (ok) {
|
|
3578
|
+
const plannotator = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? { ...plan.metadata.plannotator, lastWritebackStatus: "sent", lastWritebackAt: Date.now() } : undefined;
|
|
3579
|
+
if (plannotator)
|
|
3580
|
+
plan.metadata = { ...plan.metadata, plannotator };
|
|
3581
|
+
plan.updatedAt = new Date;
|
|
3582
|
+
notifyPlansChanged();
|
|
3583
|
+
}
|
|
3584
|
+
return ok;
|
|
3585
|
+
}
|
|
3586
|
+
function planBelongsToAdapter(plan, adapter) {
|
|
3587
|
+
if (plan.agent === adapter.agent)
|
|
3588
|
+
return true;
|
|
3589
|
+
return adapter.agent === "plannotator" && typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null;
|
|
3590
|
+
}
|
|
3591
|
+
function removePlansForPath(filePath, adapter) {
|
|
3592
|
+
const normalized = resolve4(filePath);
|
|
3593
|
+
const removed = [];
|
|
3594
|
+
for (const [id, plan] of store.entries()) {
|
|
3595
|
+
const planPath = resolve4(plan.filePath);
|
|
3596
|
+
const sessionPath = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? plan.metadata.plannotator.sessionPath : undefined;
|
|
3597
|
+
if ((planPath === normalized || sessionPath === normalized) && (!adapter || planBelongsToAdapter(plan, adapter))) {
|
|
3598
|
+
removed.push(plan);
|
|
3599
|
+
store.delete(id);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
if (removed.length > 0)
|
|
3603
|
+
notifyPlansChanged();
|
|
3604
|
+
return removed;
|
|
3605
|
+
}
|
|
3045
3606
|
async function rescanFile(filePath) {
|
|
3046
3607
|
const adapters = getActiveAdapters();
|
|
3047
|
-
const normalized =
|
|
3608
|
+
const normalized = resolve4(filePath);
|
|
3609
|
+
const removedPlans = [];
|
|
3048
3610
|
for (const adapter of adapters) {
|
|
3049
3611
|
if (!adapter.matches(filePath))
|
|
3050
3612
|
continue;
|
|
3051
|
-
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) =>
|
|
3613
|
+
const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) => resolve4(d2.dir));
|
|
3052
3614
|
const allSearchPaths = [
|
|
3053
|
-
...adapter.getSearchPaths().map((sp) =>
|
|
3615
|
+
...adapter.getSearchPaths().map((sp) => resolve4(sp)),
|
|
3054
3616
|
...discoveredDirs
|
|
3055
3617
|
];
|
|
3056
|
-
const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp +
|
|
3618
|
+
const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep3) || normalized === sp);
|
|
3057
3619
|
if (!isInSearchPath)
|
|
3058
3620
|
continue;
|
|
3059
|
-
const
|
|
3621
|
+
const rawPlans = await adapter.parse(filePath);
|
|
3622
|
+
if (rawPlans.length === 0) {
|
|
3623
|
+
removedPlans.push(...removePlansForPath(filePath, adapter));
|
|
3624
|
+
continue;
|
|
3625
|
+
}
|
|
3626
|
+
const plans = rawPlans.map(annotatePlanValueMetadata);
|
|
3060
3627
|
for (const plan of plans) {
|
|
3061
3628
|
store.set(plan.id, plan);
|
|
3062
3629
|
}
|
|
3063
3630
|
notifyPlansChanged();
|
|
3064
3631
|
return plans;
|
|
3065
3632
|
}
|
|
3066
|
-
|
|
3067
|
-
|
|
3633
|
+
if (removedPlans.length > 0)
|
|
3634
|
+
return removedPlans;
|
|
3635
|
+
const userPlansDir = resolve4(getUserPlansDir());
|
|
3636
|
+
if (normalized.endsWith(".md") && (normalized.startsWith(userPlansDir + sep3) || normalized === userPlansDir)) {
|
|
3068
3637
|
const plan = await parseGenericMarkdownPlan(filePath, { userCreated: true });
|
|
3069
3638
|
if (plan) {
|
|
3070
3639
|
store.set(plan.id, plan);
|
|
@@ -3075,8 +3644,8 @@ async function rescanFile(filePath) {
|
|
|
3075
3644
|
if (normalized.endsWith(".md")) {
|
|
3076
3645
|
const customDirs = getCustomPlanDirs();
|
|
3077
3646
|
for (const dir of customDirs) {
|
|
3078
|
-
const resolvedDir =
|
|
3079
|
-
if (normalized.startsWith(resolvedDir +
|
|
3647
|
+
const resolvedDir = resolve4(dir);
|
|
3648
|
+
if (normalized.startsWith(resolvedDir + sep3) || normalized === resolvedDir) {
|
|
3080
3649
|
const plan = await parseGenericMarkdownPlan(filePath, {
|
|
3081
3650
|
source: "custom-dir",
|
|
3082
3651
|
customDir: dir
|
|
@@ -3092,8 +3661,8 @@ async function rescanFile(filePath) {
|
|
|
3092
3661
|
return [];
|
|
3093
3662
|
}
|
|
3094
3663
|
// ../shared/src/services/watcher.ts
|
|
3095
|
-
import { existsSync as
|
|
3096
|
-
import { join as
|
|
3664
|
+
import { existsSync as existsSync6, watch } from "node:fs";
|
|
3665
|
+
import { join as join10, resolve as resolve5 } from "node:path";
|
|
3097
3666
|
var debounceTimer = null;
|
|
3098
3667
|
var pendingFiles = new Set;
|
|
3099
3668
|
var activeWatchers = [];
|
|
@@ -3111,13 +3680,13 @@ function closeAllWatchers() {
|
|
|
3111
3680
|
activeWatchers.length = 0;
|
|
3112
3681
|
}
|
|
3113
3682
|
function watchDir(dir, matchFn, onChange) {
|
|
3114
|
-
if (!
|
|
3683
|
+
if (!existsSync6(dir))
|
|
3115
3684
|
return;
|
|
3116
3685
|
try {
|
|
3117
3686
|
const watcher = watch(dir, { recursive: true }, async (_event, filename) => {
|
|
3118
3687
|
if (!filename)
|
|
3119
3688
|
return;
|
|
3120
|
-
const fullPath =
|
|
3689
|
+
const fullPath = join10(dir, filename);
|
|
3121
3690
|
if (!matchFn(fullPath))
|
|
3122
3691
|
return;
|
|
3123
3692
|
pendingFiles.add(fullPath);
|
|
@@ -3150,23 +3719,23 @@ function setupWatchers(onChange) {
|
|
|
3150
3719
|
const watchedPaths = new Set;
|
|
3151
3720
|
for (const adapter of adapters) {
|
|
3152
3721
|
for (const watchPath of adapter.getWatchPaths()) {
|
|
3153
|
-
watchedPaths.add(
|
|
3722
|
+
watchedPaths.add(resolve5(watchPath));
|
|
3154
3723
|
watchDir(watchPath, (f) => adapter.matches(f), onChange);
|
|
3155
3724
|
}
|
|
3156
3725
|
}
|
|
3157
3726
|
const discovered = discoverProjectPlanDirs();
|
|
3158
3727
|
for (const { dir, agent } of discovered) {
|
|
3159
|
-
if (watchedPaths.has(
|
|
3728
|
+
if (watchedPaths.has(resolve5(dir)))
|
|
3160
3729
|
continue;
|
|
3161
3730
|
const adapter = adapters.find((a) => a.agent === agent);
|
|
3162
3731
|
if (!adapter)
|
|
3163
3732
|
continue;
|
|
3164
|
-
watchedPaths.add(
|
|
3733
|
+
watchedPaths.add(resolve5(dir));
|
|
3165
3734
|
watchDir(dir, (f) => adapter.matches(f), onChange);
|
|
3166
3735
|
}
|
|
3167
3736
|
const customDirs = getCustomPlanDirs();
|
|
3168
3737
|
for (const dir of customDirs) {
|
|
3169
|
-
const resolvedCustom =
|
|
3738
|
+
const resolvedCustom = resolve5(dir);
|
|
3170
3739
|
let overlaps = false;
|
|
3171
3740
|
for (const watched of watchedPaths) {
|
|
3172
3741
|
if (pathsOverlapFilesystemTree(resolvedCustom, watched)) {
|
|
@@ -3186,15 +3755,15 @@ import { request as httpsRequest } from "node:https";
|
|
|
3186
3755
|
import { hostname as osHostname } from "node:os";
|
|
3187
3756
|
|
|
3188
3757
|
// src/pid.ts
|
|
3189
|
-
import { existsSync as
|
|
3758
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3190
3759
|
import { hostname } from "node:os";
|
|
3191
|
-
import { dirname, join as
|
|
3760
|
+
import { dirname as dirname3, join as join11 } from "node:path";
|
|
3192
3761
|
function getPidPath() {
|
|
3193
|
-
return
|
|
3762
|
+
return join11(getConfigDir(), "daemon.pid");
|
|
3194
3763
|
}
|
|
3195
3764
|
function writePid() {
|
|
3196
3765
|
const path = getPidPath();
|
|
3197
|
-
mkdirSync2(
|
|
3766
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
3198
3767
|
const info = {
|
|
3199
3768
|
pid: process.pid,
|
|
3200
3769
|
startedAtMs: Date.now(),
|
|
@@ -3204,7 +3773,7 @@ function writePid() {
|
|
|
3204
3773
|
}
|
|
3205
3774
|
function readPidInfo() {
|
|
3206
3775
|
const path = getPidPath();
|
|
3207
|
-
if (!
|
|
3776
|
+
if (!existsSync7(path))
|
|
3208
3777
|
return null;
|
|
3209
3778
|
const raw = readFileSync4(path, "utf-8").trim();
|
|
3210
3779
|
const asNumber = Number(raw);
|
|
@@ -3356,6 +3925,7 @@ async function sendHeartbeat() {
|
|
|
3356
3925
|
}
|
|
3357
3926
|
async function sendShutdown() {
|
|
3358
3927
|
try {
|
|
3928
|
+
getCloudConfig();
|
|
3359
3929
|
cachedDeviceId ??= loadOrCreateDeviceId();
|
|
3360
3930
|
await deleteDaemons([cachedDeviceId]);
|
|
3361
3931
|
} catch {}
|
|
@@ -3376,6 +3946,7 @@ async function refreshToken(currentToken, convexUrl) {
|
|
|
3376
3946
|
return null;
|
|
3377
3947
|
return { token: body.token, expiresAt: body.expiresAt ?? 0 };
|
|
3378
3948
|
}
|
|
3949
|
+
var REQUEST_TIMEOUT_MS2 = Number.parseInt(process.env.AGENDEX_HTTP_TIMEOUT_MS ?? "", 10) || 1e4;
|
|
3379
3950
|
function requestText(urlString, options) {
|
|
3380
3951
|
const url = new URL(urlString);
|
|
3381
3952
|
const request = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
@@ -3383,11 +3954,12 @@ function requestText(urlString, options) {
|
|
|
3383
3954
|
if (options.body) {
|
|
3384
3955
|
headers["Content-Length"] = String(Buffer.byteLength(options.body));
|
|
3385
3956
|
}
|
|
3386
|
-
return new Promise((
|
|
3957
|
+
return new Promise((resolve6, reject) => {
|
|
3387
3958
|
const req = request(url, {
|
|
3388
3959
|
agent: false,
|
|
3389
3960
|
headers,
|
|
3390
|
-
method: options.method
|
|
3961
|
+
method: options.method,
|
|
3962
|
+
timeout: REQUEST_TIMEOUT_MS2
|
|
3391
3963
|
}, (res) => {
|
|
3392
3964
|
let body = "";
|
|
3393
3965
|
res.setEncoding("utf8");
|
|
@@ -3395,7 +3967,7 @@ function requestText(urlString, options) {
|
|
|
3395
3967
|
body += chunk;
|
|
3396
3968
|
});
|
|
3397
3969
|
res.on("end", () => {
|
|
3398
|
-
|
|
3970
|
+
resolve6({
|
|
3399
3971
|
status: res.statusCode ?? 0,
|
|
3400
3972
|
body
|
|
3401
3973
|
});
|
|
@@ -3403,12 +3975,71 @@ function requestText(urlString, options) {
|
|
|
3403
3975
|
res.on("error", reject);
|
|
3404
3976
|
});
|
|
3405
3977
|
req.on("error", reject);
|
|
3978
|
+
req.on("timeout", () => {
|
|
3979
|
+
req.destroy(new Error(`Request to ${url.host} timed out after ${REQUEST_TIMEOUT_MS2}ms`));
|
|
3980
|
+
});
|
|
3406
3981
|
if (options.body) {
|
|
3407
3982
|
req.write(options.body);
|
|
3408
3983
|
}
|
|
3409
3984
|
req.end();
|
|
3410
3985
|
});
|
|
3411
3986
|
}
|
|
3987
|
+
function authHeaders(token, contentType = false) {
|
|
3988
|
+
return {
|
|
3989
|
+
Authorization: `Bearer ${token}`,
|
|
3990
|
+
Connection: "close",
|
|
3991
|
+
...contentType && { "Content-Type": "application/json" }
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
async function fetchPlannotatorWritebacks(limit = 10) {
|
|
3995
|
+
const { token, convexUrl } = getCloudConfig();
|
|
3996
|
+
cachedDeviceId ??= loadOrCreateDeviceId();
|
|
3997
|
+
const url = `${convexUrl}/api/cli/plannotator/writebacks?deviceId=${encodeURIComponent(cachedDeviceId)}&limit=${limit}`;
|
|
3998
|
+
let activeToken = token;
|
|
3999
|
+
let res = await requestText(url, {
|
|
4000
|
+
method: "GET",
|
|
4001
|
+
headers: authHeaders(activeToken)
|
|
4002
|
+
});
|
|
4003
|
+
if (res.status === 401) {
|
|
4004
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
4005
|
+
if (refreshed) {
|
|
4006
|
+
activeToken = refreshed;
|
|
4007
|
+
res = await requestText(url, {
|
|
4008
|
+
method: "GET",
|
|
4009
|
+
headers: authHeaders(activeToken)
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
if (res.status === 401)
|
|
4014
|
+
throw new AuthExpiredError;
|
|
4015
|
+
if (res.status < 200 || res.status >= 300)
|
|
4016
|
+
return [];
|
|
4017
|
+
const body = JSON.parse(res.body);
|
|
4018
|
+
return body.writebacks ?? [];
|
|
4019
|
+
}
|
|
4020
|
+
async function reportPlannotatorWriteback(writebackId, status, error) {
|
|
4021
|
+
const { token, convexUrl } = getCloudConfig();
|
|
4022
|
+
const url = `${convexUrl}/api/cli/plannotator/writebacks/report`;
|
|
4023
|
+
let activeToken = token;
|
|
4024
|
+
const body = JSON.stringify({ writebackId, status, error });
|
|
4025
|
+
let res = await requestText(url, {
|
|
4026
|
+
method: "POST",
|
|
4027
|
+
headers: authHeaders(activeToken, true),
|
|
4028
|
+
body
|
|
4029
|
+
});
|
|
4030
|
+
if (res.status === 401) {
|
|
4031
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
4032
|
+
if (refreshed) {
|
|
4033
|
+
activeToken = refreshed;
|
|
4034
|
+
res = await requestText(url, {
|
|
4035
|
+
method: "POST",
|
|
4036
|
+
headers: authHeaders(activeToken, true),
|
|
4037
|
+
body
|
|
4038
|
+
});
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
return res.status >= 200 && res.status < 300;
|
|
4042
|
+
}
|
|
3412
4043
|
async function fetchDevices() {
|
|
3413
4044
|
const { token, convexUrl } = getCloudConfig();
|
|
3414
4045
|
const url = `${convexUrl}/api/cli/devices`;
|
|
@@ -3509,6 +4140,7 @@ async function login(siteUrlOverride) {
|
|
|
3509
4140
|
token: existing?.token,
|
|
3510
4141
|
cloudToken: callback.token,
|
|
3511
4142
|
convexUrl: callback.convexUrl,
|
|
4143
|
+
deviceId: existing?.deviceId,
|
|
3512
4144
|
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
3513
4145
|
customPlanDirs: existing?.customPlanDirs ?? []
|
|
3514
4146
|
};
|
|
@@ -3527,6 +4159,7 @@ function logout() {
|
|
|
3527
4159
|
token: existing.token,
|
|
3528
4160
|
cloudToken: undefined,
|
|
3529
4161
|
convexUrl: undefined,
|
|
4162
|
+
deviceId: existing.deviceId,
|
|
3530
4163
|
enabledAdapters: existing.enabledAdapters,
|
|
3531
4164
|
customPlanDirs: existing.customPlanDirs
|
|
3532
4165
|
};
|
|
@@ -3539,14 +4172,14 @@ async function startCallbackServer() {
|
|
|
3539
4172
|
let timeout;
|
|
3540
4173
|
let settle;
|
|
3541
4174
|
let fail;
|
|
3542
|
-
const result = new Promise((
|
|
3543
|
-
settle =
|
|
4175
|
+
const result = new Promise((resolve6, reject) => {
|
|
4176
|
+
settle = resolve6;
|
|
3544
4177
|
fail = reject;
|
|
3545
4178
|
});
|
|
3546
4179
|
const finish = (value) => {
|
|
3547
4180
|
if (!settle || !fail)
|
|
3548
4181
|
return;
|
|
3549
|
-
const
|
|
4182
|
+
const resolve6 = settle;
|
|
3550
4183
|
const reject = fail;
|
|
3551
4184
|
settle = undefined;
|
|
3552
4185
|
fail = undefined;
|
|
@@ -3563,7 +4196,7 @@ async function startCallbackServer() {
|
|
|
3563
4196
|
reject(value);
|
|
3564
4197
|
return;
|
|
3565
4198
|
}
|
|
3566
|
-
|
|
4199
|
+
resolve6(value);
|
|
3567
4200
|
};
|
|
3568
4201
|
server.on("request", (req, res) => {
|
|
3569
4202
|
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -3597,8 +4230,8 @@ async function startCallbackServer() {
|
|
|
3597
4230
|
sockets.delete(socket);
|
|
3598
4231
|
});
|
|
3599
4232
|
});
|
|
3600
|
-
await new Promise((
|
|
3601
|
-
server.listen(0, "127.0.0.1", () =>
|
|
4233
|
+
await new Promise((resolve6, reject) => {
|
|
4234
|
+
server.listen(0, "127.0.0.1", () => resolve6());
|
|
3602
4235
|
server.once("error", reject);
|
|
3603
4236
|
});
|
|
3604
4237
|
server.once("error", (error) => {
|
|
@@ -3678,19 +4311,77 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
3678
4311
|
|
|
3679
4312
|
// src/daemon.ts
|
|
3680
4313
|
import { spawn as spawn2 } from "node:child_process";
|
|
3681
|
-
import { resolve as
|
|
4314
|
+
import { resolve as resolve6 } from "node:path";
|
|
3682
4315
|
import { fileURLToPath } from "node:url";
|
|
3683
4316
|
|
|
4317
|
+
// src/adapters.ts
|
|
4318
|
+
var PLANNOTATOR_ADAPTER_ID = "plannotator";
|
|
4319
|
+
function envFlag(name) {
|
|
4320
|
+
const value = process.env[name]?.trim().toLowerCase();
|
|
4321
|
+
if (!value)
|
|
4322
|
+
return;
|
|
4323
|
+
if (value === "1" || value === "true" || value === "yes" || value === "on")
|
|
4324
|
+
return true;
|
|
4325
|
+
if (value === "0" || value === "false" || value === "no" || value === "off")
|
|
4326
|
+
return false;
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
function shouldEnablePlannotatorSync(config) {
|
|
4330
|
+
const explicit = envFlag("AGENDEX_PLANNOTATOR_SYNC");
|
|
4331
|
+
if (explicit !== undefined)
|
|
4332
|
+
return explicit;
|
|
4333
|
+
return Boolean(config.cloudToken && config.convexUrl);
|
|
4334
|
+
}
|
|
4335
|
+
function resolveCliAdapterIds(config) {
|
|
4336
|
+
const ids = config.enabledAdapters.length > 0 ? [...config.enabledAdapters] : getDefaultAdapterIds();
|
|
4337
|
+
if (shouldEnablePlannotatorSync(config) && !ids.includes(PLANNOTATOR_ADAPTER_ID)) {
|
|
4338
|
+
ids.push(PLANNOTATOR_ADAPTER_ID);
|
|
4339
|
+
}
|
|
4340
|
+
return ids;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
// src/payload.ts
|
|
4344
|
+
var SYNC_METADATA_KEY = "agendexSync";
|
|
4345
|
+
function isRecord4(value) {
|
|
4346
|
+
return typeof value === "object" && value !== null;
|
|
4347
|
+
}
|
|
4348
|
+
function withSyncDeviceMetadata(metadata, deviceId) {
|
|
4349
|
+
if (!deviceId)
|
|
4350
|
+
return metadata;
|
|
4351
|
+
const existing = isRecord4(metadata[SYNC_METADATA_KEY]) ? metadata[SYNC_METADATA_KEY] : {};
|
|
4352
|
+
return {
|
|
4353
|
+
...metadata,
|
|
4354
|
+
[SYNC_METADATA_KEY]: {
|
|
4355
|
+
...existing,
|
|
4356
|
+
deviceId
|
|
4357
|
+
}
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
function planToSyncPayload(plan, deviceId) {
|
|
4361
|
+
return {
|
|
4362
|
+
localPlanId: plan.id,
|
|
4363
|
+
agent: plan.agent,
|
|
4364
|
+
title: plan.title,
|
|
4365
|
+
content: plan.content,
|
|
4366
|
+
format: plan.format,
|
|
4367
|
+
filePath: plan.filePath,
|
|
4368
|
+
workspace: plan.workspace,
|
|
4369
|
+
metadata: withSyncDeviceMetadata(plan.metadata, deviceId),
|
|
4370
|
+
createdAt: plan.createdAt.getTime(),
|
|
4371
|
+
updatedAt: plan.updatedAt.getTime()
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
|
|
3684
4375
|
// src/sync-cache.ts
|
|
3685
4376
|
import { createHash as createHash2 } from "node:crypto";
|
|
3686
|
-
import { existsSync as
|
|
3687
|
-
import { join as
|
|
4377
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4378
|
+
import { join as join12 } from "node:path";
|
|
3688
4379
|
function getCachePath() {
|
|
3689
|
-
return
|
|
4380
|
+
return join12(getConfigDir(), "sync-cache.json");
|
|
3690
4381
|
}
|
|
3691
4382
|
function loadSyncCache() {
|
|
3692
4383
|
const cachePath = getCachePath();
|
|
3693
|
-
if (!
|
|
4384
|
+
if (!existsSync8(cachePath))
|
|
3694
4385
|
return {};
|
|
3695
4386
|
try {
|
|
3696
4387
|
const raw = JSON.parse(readFileSync5(cachePath, "utf-8"));
|
|
@@ -3703,7 +4394,7 @@ function loadSyncCache() {
|
|
|
3703
4394
|
}
|
|
3704
4395
|
function saveSyncCache(cache, options) {
|
|
3705
4396
|
const dir = getConfigDir();
|
|
3706
|
-
if (!
|
|
4397
|
+
if (!existsSync8(dir))
|
|
3707
4398
|
mkdirSync3(dir, { recursive: true });
|
|
3708
4399
|
const cachePath = getCachePath();
|
|
3709
4400
|
if (options?.replace) {
|
|
@@ -3729,32 +4420,71 @@ function computePayloadHash(payload) {
|
|
|
3729
4420
|
return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
3730
4421
|
}
|
|
3731
4422
|
|
|
4423
|
+
// src/writeback-delivery-cache.ts
|
|
4424
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4425
|
+
import { join as join13 } from "node:path";
|
|
4426
|
+
var MAX_PENDING_WRITEBACK_REPORTS = 1000;
|
|
4427
|
+
function getCachePath2() {
|
|
4428
|
+
return join13(getConfigDir(), "plannotator-writebacks-delivered.json");
|
|
4429
|
+
}
|
|
4430
|
+
function isPendingWritebackReportStatus(value) {
|
|
4431
|
+
return value === "sent" || value === "failed" || value === "expired";
|
|
4432
|
+
}
|
|
4433
|
+
function normalizeReports(input) {
|
|
4434
|
+
if (!Array.isArray(input))
|
|
4435
|
+
return [];
|
|
4436
|
+
const reports = new Map;
|
|
4437
|
+
for (const item of input) {
|
|
4438
|
+
if (typeof item === "string" && item.length > 0) {
|
|
4439
|
+
reports.set(item, "sent");
|
|
4440
|
+
continue;
|
|
4441
|
+
}
|
|
4442
|
+
if (item && typeof item === "object" && "id" in item && "status" in item && typeof item.id === "string" && item.id.length > 0 && isPendingWritebackReportStatus(item.status)) {
|
|
4443
|
+
reports.set(item.id, item.status);
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return [...reports.entries()].slice(-MAX_PENDING_WRITEBACK_REPORTS);
|
|
4447
|
+
}
|
|
4448
|
+
function loadPendingWritebackReports() {
|
|
4449
|
+
const cachePath = getCachePath2();
|
|
4450
|
+
if (!existsSync9(cachePath))
|
|
4451
|
+
return new Map;
|
|
4452
|
+
try {
|
|
4453
|
+
return new Map(normalizeReports(JSON.parse(readFileSync6(cachePath, "utf-8"))));
|
|
4454
|
+
} catch {
|
|
4455
|
+
return new Map;
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
function savePendingWritebackReports(reports) {
|
|
4459
|
+
try {
|
|
4460
|
+
const dir = getConfigDir();
|
|
4461
|
+
if (!existsSync9(dir))
|
|
4462
|
+
mkdirSync4(dir, { recursive: true });
|
|
4463
|
+
const normalizedReports = normalizeReports([...reports].map(([id, status]) => ({ id, status })));
|
|
4464
|
+
writeFileSync4(getCachePath2(), JSON.stringify(normalizedReports.map(([id, status]) => ({ id, status }))));
|
|
4465
|
+
return true;
|
|
4466
|
+
} catch {
|
|
4467
|
+
return false;
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
|
|
3732
4471
|
// src/daemon.ts
|
|
3733
4472
|
var MAX_RESTARTS = 5;
|
|
3734
4473
|
var RESTART_WINDOW_MS = 60000;
|
|
3735
4474
|
var RESTART_DELAY_MS = 5000;
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
agent: plan.agent,
|
|
3740
|
-
title: plan.title,
|
|
3741
|
-
content: plan.content,
|
|
3742
|
-
format: plan.format,
|
|
3743
|
-
filePath: plan.filePath,
|
|
3744
|
-
workspace: plan.workspace,
|
|
3745
|
-
metadata: plan.metadata,
|
|
3746
|
-
createdAt: plan.createdAt.getTime(),
|
|
3747
|
-
updatedAt: plan.updatedAt.getTime()
|
|
3748
|
-
};
|
|
3749
|
-
}
|
|
4475
|
+
var PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS = 15000;
|
|
4476
|
+
var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
|
|
4477
|
+
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the request-changes payload.";
|
|
3750
4478
|
async function runWorker() {
|
|
3751
4479
|
const config = await loadOrInitConfig();
|
|
3752
|
-
const
|
|
4480
|
+
const adapterIds = resolveCliAdapterIds(config);
|
|
4481
|
+
const adapters = resolveAdapters(adapterIds);
|
|
3753
4482
|
setActiveAdapters(adapters);
|
|
3754
|
-
console.log(`[agendex] daemon starting with ${
|
|
4483
|
+
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
3755
4484
|
await sendHeartbeat();
|
|
3756
4485
|
const syncCache = loadSyncCache();
|
|
3757
4486
|
const syncQueue = [];
|
|
4487
|
+
const pendingWritebackReports = loadPendingWritebackReports();
|
|
3758
4488
|
let syncing = false;
|
|
3759
4489
|
async function tryRefreshToken() {
|
|
3760
4490
|
const cfg = loadConfig();
|
|
@@ -3819,6 +4549,89 @@ async function runWorker() {
|
|
|
3819
4549
|
if (syncQueue.length > 0)
|
|
3820
4550
|
processSyncQueue();
|
|
3821
4551
|
}
|
|
4552
|
+
function persistPendingWritebackReports() {
|
|
4553
|
+
if (!savePendingWritebackReports(pendingWritebackReports)) {
|
|
4554
|
+
console.error("[agendex] failed to persist Plannotator write-back delivery cache");
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
async function reportPendingWriteback(writebackId) {
|
|
4558
|
+
const status = pendingWritebackReports.get(writebackId);
|
|
4559
|
+
if (!status)
|
|
4560
|
+
return;
|
|
4561
|
+
const reported = await reportPlannotatorWriteback(writebackId, status, status === "expired" ? PLANNOTATOR_WRITEBACK_EXPIRED_ERROR : status === "failed" ? PLANNOTATOR_WRITEBACK_FAILED_ERROR : undefined);
|
|
4562
|
+
if (reported) {
|
|
4563
|
+
pendingWritebackReports.delete(writebackId);
|
|
4564
|
+
persistPendingWritebackReports();
|
|
4565
|
+
} else {
|
|
4566
|
+
console.error("[agendex] failed to report write-back status for", writebackId, "- will retry");
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
async function handlePlannotatorWriteback(job) {
|
|
4570
|
+
if (pendingWritebackReports.has(job._id)) {
|
|
4571
|
+
await reportPendingWriteback(job._id);
|
|
4572
|
+
return;
|
|
4573
|
+
}
|
|
4574
|
+
if (job.expiresAt <= Date.now()) {
|
|
4575
|
+
pendingWritebackReports.set(job._id, "expired");
|
|
4576
|
+
persistPendingWritebackReports();
|
|
4577
|
+
await reportPendingWriteback(job._id);
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
let localPlan = getById(job.localPlanId);
|
|
4581
|
+
if (!localPlan) {
|
|
4582
|
+
await scan();
|
|
4583
|
+
localPlan = getById(job.localPlanId);
|
|
4584
|
+
}
|
|
4585
|
+
if (!localPlan) {
|
|
4586
|
+
if (job.deviceId) {
|
|
4587
|
+
pendingWritebackReports.set(job._id, "failed");
|
|
4588
|
+
persistPendingWritebackReports();
|
|
4589
|
+
await reportPendingWriteback(job._id);
|
|
4590
|
+
}
|
|
4591
|
+
return;
|
|
4592
|
+
}
|
|
4593
|
+
const ok = await requestChanges(job.localPlanId, {
|
|
4594
|
+
feedback: job.feedback,
|
|
4595
|
+
revisedContent: job.revisedContent,
|
|
4596
|
+
annotations: job.annotations,
|
|
4597
|
+
source: job.source,
|
|
4598
|
+
writebackId: job._id,
|
|
4599
|
+
requestedAt: Date.now()
|
|
4600
|
+
});
|
|
4601
|
+
if (ok) {
|
|
4602
|
+
const updatedPlan = getById(job.localPlanId);
|
|
4603
|
+
if (updatedPlan)
|
|
4604
|
+
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId));
|
|
4605
|
+
pendingWritebackReports.set(job._id, "sent");
|
|
4606
|
+
persistPendingWritebackReports();
|
|
4607
|
+
await reportPendingWriteback(job._id);
|
|
4608
|
+
processSyncQueue();
|
|
4609
|
+
return;
|
|
4610
|
+
}
|
|
4611
|
+
pendingWritebackReports.set(job._id, "failed");
|
|
4612
|
+
persistPendingWritebackReports();
|
|
4613
|
+
await reportPendingWriteback(job._id);
|
|
4614
|
+
}
|
|
4615
|
+
let pollingWritebacks = false;
|
|
4616
|
+
async function pollPlannotatorWritebacks() {
|
|
4617
|
+
if (pollingWritebacks)
|
|
4618
|
+
return;
|
|
4619
|
+
pollingWritebacks = true;
|
|
4620
|
+
try {
|
|
4621
|
+
const jobs = await fetchPlannotatorWritebacks();
|
|
4622
|
+
for (const job of jobs) {
|
|
4623
|
+
await handlePlannotatorWriteback(job);
|
|
4624
|
+
}
|
|
4625
|
+
} catch (err) {
|
|
4626
|
+
if (err instanceof Error && err.name === "AuthExpiredError") {
|
|
4627
|
+
console.error("[agendex] session expired. Run `agendex login` to re-authenticate.");
|
|
4628
|
+
} else {
|
|
4629
|
+
console.error("[agendex] Plannotator write-back polling failed:", err);
|
|
4630
|
+
}
|
|
4631
|
+
} finally {
|
|
4632
|
+
pollingWritebacks = false;
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
3822
4635
|
setOnPlansChanged(() => {});
|
|
3823
4636
|
console.log(`[agendex] initial scan...`);
|
|
3824
4637
|
await scan();
|
|
@@ -3829,7 +4642,7 @@ async function runWorker() {
|
|
|
3829
4642
|
let initialQueuedSyncable = 0;
|
|
3830
4643
|
let initialQueuedLowValue = 0;
|
|
3831
4644
|
for (const plan of plans) {
|
|
3832
|
-
const payload =
|
|
4645
|
+
const payload = planToSyncPayload(plan, config.deviceId);
|
|
3833
4646
|
const hash = computePayloadHash(payload);
|
|
3834
4647
|
if (syncCache[plan.id] === hash) {
|
|
3835
4648
|
initialSkipped++;
|
|
@@ -3852,9 +4665,13 @@ async function runWorker() {
|
|
|
3852
4665
|
console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
|
|
3853
4666
|
await processSyncQueue();
|
|
3854
4667
|
setInterval(() => void sendHeartbeat(), CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
4668
|
+
if (shouldEnablePlannotatorSync(config)) {
|
|
4669
|
+
setInterval(() => void pollPlannotatorWritebacks(), PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS);
|
|
4670
|
+
pollPlannotatorWritebacks();
|
|
4671
|
+
}
|
|
3855
4672
|
startWatching((changedPlans) => {
|
|
3856
4673
|
for (const plan of changedPlans) {
|
|
3857
|
-
syncQueue.push(
|
|
4674
|
+
syncQueue.push(planToSyncPayload(plan, config.deviceId));
|
|
3858
4675
|
}
|
|
3859
4676
|
processSyncQueue();
|
|
3860
4677
|
});
|
|
@@ -3884,17 +4701,17 @@ async function startSupervisor() {
|
|
|
3884
4701
|
};
|
|
3885
4702
|
process.on("SIGTERM", shutdown);
|
|
3886
4703
|
process.on("SIGINT", shutdown);
|
|
3887
|
-
const scriptPath =
|
|
4704
|
+
const scriptPath = resolve6(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
|
|
3888
4705
|
const restartTimes = [];
|
|
3889
4706
|
while (!stopping) {
|
|
3890
4707
|
workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
|
|
3891
4708
|
stdio: ["ignore", "inherit", "inherit"]
|
|
3892
4709
|
});
|
|
3893
|
-
const exitCode = await new Promise((
|
|
3894
|
-
workerProc?.once("exit", (code) =>
|
|
4710
|
+
const exitCode = await new Promise((resolve7) => {
|
|
4711
|
+
workerProc?.once("exit", (code) => resolve7(code));
|
|
3895
4712
|
workerProc?.once("error", (error) => {
|
|
3896
4713
|
console.error("[agendex] failed to spawn worker:", error);
|
|
3897
|
-
|
|
4714
|
+
resolve7(1);
|
|
3898
4715
|
});
|
|
3899
4716
|
});
|
|
3900
4717
|
workerProc = null;
|
|
@@ -3917,23 +4734,10 @@ async function startSupervisor() {
|
|
|
3917
4734
|
}
|
|
3918
4735
|
|
|
3919
4736
|
// src/sync.ts
|
|
3920
|
-
function planToPayload2(plan) {
|
|
3921
|
-
return {
|
|
3922
|
-
localPlanId: plan.id,
|
|
3923
|
-
agent: plan.agent,
|
|
3924
|
-
title: plan.title,
|
|
3925
|
-
content: plan.content,
|
|
3926
|
-
format: plan.format,
|
|
3927
|
-
filePath: plan.filePath,
|
|
3928
|
-
workspace: plan.workspace,
|
|
3929
|
-
metadata: plan.metadata,
|
|
3930
|
-
createdAt: plan.createdAt.getTime(),
|
|
3931
|
-
updatedAt: plan.updatedAt.getTime()
|
|
3932
|
-
};
|
|
3933
|
-
}
|
|
3934
4737
|
async function syncAll(force = false) {
|
|
3935
4738
|
const config = await loadOrInitConfig();
|
|
3936
|
-
const
|
|
4739
|
+
const adapterIds = resolveCliAdapterIds(config);
|
|
4740
|
+
const adapters = resolveAdapters(adapterIds);
|
|
3937
4741
|
setActiveAdapters(adapters);
|
|
3938
4742
|
console.log(`[agendex] Scanning local plans...`);
|
|
3939
4743
|
await scan();
|
|
@@ -3951,7 +4755,7 @@ async function syncAll(force = false) {
|
|
|
3951
4755
|
let failed = 0;
|
|
3952
4756
|
for (const plan of [...syncablePlans, ...lowValuePlans]) {
|
|
3953
4757
|
activePlanIds.add(plan.id);
|
|
3954
|
-
const payload =
|
|
4758
|
+
const payload = planToSyncPayload(plan, config.deviceId);
|
|
3955
4759
|
const hash = computePayloadHash(payload);
|
|
3956
4760
|
if (!force && cache[plan.id] === hash) {
|
|
3957
4761
|
skipped++;
|
|
@@ -3981,14 +4785,20 @@ async function syncAll(force = false) {
|
|
|
3981
4785
|
console.log(`[agendex] Sync complete: ${synced} synced${lowValueSuffix}, ${skipped} unchanged, ${failed} failed`);
|
|
3982
4786
|
}
|
|
3983
4787
|
|
|
4788
|
+
// src/upgrade.ts
|
|
4789
|
+
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
4790
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
4791
|
+
import { dirname as dirname4, resolve as resolve7, sep as sep4 } from "node:path";
|
|
4792
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4793
|
+
|
|
3984
4794
|
// src/version.ts
|
|
3985
|
-
import { existsSync as
|
|
4795
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3986
4796
|
import { tmpdir } from "node:os";
|
|
3987
|
-
import { join as
|
|
4797
|
+
import { join as join14 } from "node:path";
|
|
3988
4798
|
// package.json
|
|
3989
4799
|
var package_default = {
|
|
3990
4800
|
name: "agendex-cli",
|
|
3991
|
-
version: "0.
|
|
4801
|
+
version: "0.16.0",
|
|
3992
4802
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
3993
4803
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
3994
4804
|
repository: {
|
|
@@ -4032,14 +4842,14 @@ var package_default = {
|
|
|
4032
4842
|
|
|
4033
4843
|
// src/version.ts
|
|
4034
4844
|
var CLI_VERSION = package_default.version;
|
|
4035
|
-
var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ??
|
|
4845
|
+
var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ?? join14(tmpdir(), ".agendex-update-cache.json");
|
|
4036
4846
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4037
4847
|
var UPDATE_URL = process.env.AGENDEX_UPDATE_URL ?? "https://registry.npmjs.org/agendex-cli/latest";
|
|
4038
4848
|
function readCache(current) {
|
|
4039
4849
|
try {
|
|
4040
|
-
if (!
|
|
4850
|
+
if (!existsSync10(CACHE_FILE))
|
|
4041
4851
|
return null;
|
|
4042
|
-
const { result, ts } = JSON.parse(
|
|
4852
|
+
const { result, ts } = JSON.parse(readFileSync7(CACHE_FILE, "utf8"));
|
|
4043
4853
|
if (Date.now() - ts > CACHE_TTL_MS)
|
|
4044
4854
|
return null;
|
|
4045
4855
|
return normalizeResult(result, current);
|
|
@@ -4049,14 +4859,16 @@ function readCache(current) {
|
|
|
4049
4859
|
}
|
|
4050
4860
|
function writeCache(result) {
|
|
4051
4861
|
try {
|
|
4052
|
-
|
|
4862
|
+
writeFileSync5(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
|
|
4053
4863
|
} catch {}
|
|
4054
4864
|
}
|
|
4055
|
-
async function checkForUpdate() {
|
|
4865
|
+
async function checkForUpdate(options = {}) {
|
|
4056
4866
|
const current = CLI_VERSION;
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4867
|
+
if (!options.forceRefresh) {
|
|
4868
|
+
const cached = readCache(current);
|
|
4869
|
+
if (cached)
|
|
4870
|
+
return cached;
|
|
4871
|
+
}
|
|
4060
4872
|
try {
|
|
4061
4873
|
const controller = new AbortController;
|
|
4062
4874
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
@@ -4064,21 +4876,27 @@ async function checkForUpdate() {
|
|
|
4064
4876
|
signal: controller.signal
|
|
4065
4877
|
}).finally(() => clearTimeout(timeout));
|
|
4066
4878
|
if (!res.ok) {
|
|
4067
|
-
return { updateAvailable: false, current, latest: current };
|
|
4879
|
+
return { checked: false, updateAvailable: false, current, latest: current };
|
|
4068
4880
|
}
|
|
4069
4881
|
const data = await res.json();
|
|
4070
4882
|
const latest = data.version;
|
|
4071
|
-
const result = {
|
|
4883
|
+
const result = {
|
|
4884
|
+
checked: true,
|
|
4885
|
+
updateAvailable: isNewer(latest, current),
|
|
4886
|
+
current,
|
|
4887
|
+
latest
|
|
4888
|
+
};
|
|
4072
4889
|
writeCache(result);
|
|
4073
4890
|
return result;
|
|
4074
4891
|
} catch {
|
|
4075
|
-
return { updateAvailable: false, current, latest: current };
|
|
4892
|
+
return { checked: false, updateAvailable: false, current, latest: current };
|
|
4076
4893
|
}
|
|
4077
4894
|
}
|
|
4078
4895
|
function normalizeResult(result, current) {
|
|
4079
4896
|
if (typeof result.latest !== "string")
|
|
4080
4897
|
return null;
|
|
4081
4898
|
return {
|
|
4899
|
+
checked: true,
|
|
4082
4900
|
updateAvailable: isNewer(result.latest, current),
|
|
4083
4901
|
current,
|
|
4084
4902
|
latest: result.latest
|
|
@@ -4098,6 +4916,195 @@ function isNewer(latest, current) {
|
|
|
4098
4916
|
return false;
|
|
4099
4917
|
}
|
|
4100
4918
|
|
|
4919
|
+
// src/upgrade.ts
|
|
4920
|
+
var PACKAGE_NAME = "agendex-cli";
|
|
4921
|
+
var moduleDir = dirname4(fileURLToPath2(import.meta.url));
|
|
4922
|
+
function getPackageRoot() {
|
|
4923
|
+
try {
|
|
4924
|
+
return realpathSync2(resolve7(moduleDir, ".."));
|
|
4925
|
+
} catch {
|
|
4926
|
+
return resolve7(moduleDir, "..");
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
function detectPackageManager(packageRoot) {
|
|
4930
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
4931
|
+
const execpath = process.env.npm_execpath ?? "";
|
|
4932
|
+
if (userAgent.startsWith("bun/") || execpath.includes("bun"))
|
|
4933
|
+
return "bun";
|
|
4934
|
+
if (userAgent.startsWith("pnpm/") || execpath.includes("pnpm"))
|
|
4935
|
+
return "pnpm";
|
|
4936
|
+
if (userAgent.startsWith("yarn/") || execpath.includes("yarn"))
|
|
4937
|
+
return "yarn";
|
|
4938
|
+
const lower = packageRoot.toLowerCase();
|
|
4939
|
+
if (lower.includes(`${sep4}.bun${sep4}`) || lower.includes("/.bun/"))
|
|
4940
|
+
return "bun";
|
|
4941
|
+
if (lower.includes(`${sep4}pnpm${sep4}`) || lower.includes("/pnpm/"))
|
|
4942
|
+
return "pnpm";
|
|
4943
|
+
if (lower.includes(`${sep4}yarn${sep4}`) || lower.includes("/yarn/"))
|
|
4944
|
+
return "yarn";
|
|
4945
|
+
if (typeof globalThis.Bun !== "undefined" || process.versions.bun) {
|
|
4946
|
+
return "bun";
|
|
4947
|
+
}
|
|
4948
|
+
return "npm";
|
|
4949
|
+
}
|
|
4950
|
+
function readYarnVersion() {
|
|
4951
|
+
const result = spawnSync("yarn", ["--version"], {
|
|
4952
|
+
encoding: "utf8",
|
|
4953
|
+
shell: process.platform === "win32"
|
|
4954
|
+
});
|
|
4955
|
+
if (result.error || result.status !== 0)
|
|
4956
|
+
return null;
|
|
4957
|
+
return result.stdout.trim() || null;
|
|
4958
|
+
}
|
|
4959
|
+
function parseMajorVersion(version) {
|
|
4960
|
+
const match = version.match(/^(\d+)/);
|
|
4961
|
+
if (!match)
|
|
4962
|
+
return null;
|
|
4963
|
+
const major = Number(match[1]);
|
|
4964
|
+
return Number.isFinite(major) ? major : null;
|
|
4965
|
+
}
|
|
4966
|
+
function buildGlobalInstallCommand(pm) {
|
|
4967
|
+
const pkgSpec = `${PACKAGE_NAME}@latest`;
|
|
4968
|
+
switch (pm) {
|
|
4969
|
+
case "bun":
|
|
4970
|
+
return {
|
|
4971
|
+
supported: true,
|
|
4972
|
+
command: { bin: "bun", args: ["add", "-g", pkgSpec], display: `bun add -g ${pkgSpec}` }
|
|
4973
|
+
};
|
|
4974
|
+
case "pnpm":
|
|
4975
|
+
return {
|
|
4976
|
+
supported: true,
|
|
4977
|
+
command: {
|
|
4978
|
+
bin: "pnpm",
|
|
4979
|
+
args: ["add", "-g", pkgSpec],
|
|
4980
|
+
display: `pnpm add -g ${pkgSpec}`
|
|
4981
|
+
}
|
|
4982
|
+
};
|
|
4983
|
+
case "yarn": {
|
|
4984
|
+
const yarnVersion = readYarnVersion();
|
|
4985
|
+
const yarnMajorVersion = yarnVersion ? parseMajorVersion(yarnVersion) : null;
|
|
4986
|
+
if (yarnMajorVersion !== null && yarnMajorVersion >= 2) {
|
|
4987
|
+
return {
|
|
4988
|
+
supported: false,
|
|
4989
|
+
reason: `automatic upgrade with Yarn only supports Yarn Classic (v1); detected Yarn v${yarnVersion}.`,
|
|
4990
|
+
manualCommand: `npm install -g ${pkgSpec}`
|
|
4991
|
+
};
|
|
4992
|
+
}
|
|
4993
|
+
return {
|
|
4994
|
+
supported: true,
|
|
4995
|
+
command: {
|
|
4996
|
+
bin: "yarn",
|
|
4997
|
+
args: ["global", "add", pkgSpec],
|
|
4998
|
+
display: `yarn global add ${pkgSpec}`
|
|
4999
|
+
}
|
|
5000
|
+
};
|
|
5001
|
+
}
|
|
5002
|
+
default:
|
|
5003
|
+
return {
|
|
5004
|
+
supported: true,
|
|
5005
|
+
command: {
|
|
5006
|
+
bin: "npm",
|
|
5007
|
+
args: ["install", "-g", pkgSpec],
|
|
5008
|
+
display: `npm install -g ${pkgSpec}`
|
|
5009
|
+
}
|
|
5010
|
+
};
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
function isLikelyGlobalInstall(packageRoot) {
|
|
5014
|
+
const normalized = packageRoot.replace(/\\/g, "/");
|
|
5015
|
+
if (!normalized.includes("/node_modules/")) {
|
|
5016
|
+
return false;
|
|
5017
|
+
}
|
|
5018
|
+
const globalMarkers = [
|
|
5019
|
+
"/lib/node_modules/",
|
|
5020
|
+
"/pnpm/global/",
|
|
5021
|
+
"/yarn/global/",
|
|
5022
|
+
"/.bun/install/global/",
|
|
5023
|
+
"/.bun/install/",
|
|
5024
|
+
"/.config/yarn/global/",
|
|
5025
|
+
"/AppData/Roaming/npm/",
|
|
5026
|
+
"/AppData/Local/Yarn/",
|
|
5027
|
+
"/AppData/Local/pnpm/"
|
|
5028
|
+
];
|
|
5029
|
+
return globalMarkers.some((marker) => normalized.includes(marker));
|
|
5030
|
+
}
|
|
5031
|
+
async function runUpgrade(opts) {
|
|
5032
|
+
const packageRoot = getPackageRoot();
|
|
5033
|
+
const pm = detectPackageManager(packageRoot);
|
|
5034
|
+
const isGlobal = isLikelyGlobalInstall(packageRoot);
|
|
5035
|
+
if (!isGlobal) {
|
|
5036
|
+
process.stderr.write(`[agendex] this CLI appears to be running from a local checkout or linked install:
|
|
5037
|
+
` + `[agendex] ${packageRoot}
|
|
5038
|
+
` + `[agendex] automatic upgrade only supports global installs.
|
|
5039
|
+
` + `[agendex] reinstall globally (e.g. \`npm install -g ${PACKAGE_NAME}@latest\`) or update the source repo manually.
|
|
5040
|
+
`);
|
|
5041
|
+
return 1;
|
|
5042
|
+
}
|
|
5043
|
+
const { checked, updateAvailable, current, latest } = await checkForUpdate({
|
|
5044
|
+
forceRefresh: true
|
|
5045
|
+
});
|
|
5046
|
+
if (checked && !updateAvailable && !opts.force) {
|
|
5047
|
+
process.stdout.write(`[agendex] already up to date (v${current})
|
|
5048
|
+
`);
|
|
5049
|
+
return 0;
|
|
5050
|
+
}
|
|
5051
|
+
if (!checked) {
|
|
5052
|
+
process.stderr.write(`[agendex] could not verify the latest version; attempting upgrade anyway...
|
|
5053
|
+
`);
|
|
5054
|
+
}
|
|
5055
|
+
const commandResult = buildGlobalInstallCommand(pm);
|
|
5056
|
+
if (!commandResult.supported) {
|
|
5057
|
+
process.stderr.write(`[agendex] ${commandResult.reason}
|
|
5058
|
+
`);
|
|
5059
|
+
process.stderr.write(`[agendex] install the latest CLI manually, e.g. \`${commandResult.manualCommand}\`.
|
|
5060
|
+
`);
|
|
5061
|
+
return 1;
|
|
5062
|
+
}
|
|
5063
|
+
const cmd = commandResult.command;
|
|
5064
|
+
if (checked && updateAvailable) {
|
|
5065
|
+
process.stdout.write(`[agendex] upgrading: v${current} → v${latest}
|
|
5066
|
+
`);
|
|
5067
|
+
} else if (opts.force) {
|
|
5068
|
+
process.stdout.write(`[agendex] reinstalling v${CLI_VERSION} (forced)
|
|
5069
|
+
`);
|
|
5070
|
+
}
|
|
5071
|
+
process.stdout.write(`[agendex] running: ${cmd.display}
|
|
5072
|
+
`);
|
|
5073
|
+
return await new Promise((resolveExit) => {
|
|
5074
|
+
const child = spawn3(cmd.bin, cmd.args, {
|
|
5075
|
+
stdio: "inherit",
|
|
5076
|
+
shell: process.platform === "win32",
|
|
5077
|
+
env: process.env
|
|
5078
|
+
});
|
|
5079
|
+
let didError = false;
|
|
5080
|
+
child.on("error", (err) => {
|
|
5081
|
+
didError = true;
|
|
5082
|
+
process.stderr.write(`[agendex] failed to run ${cmd.bin}: ${err instanceof Error ? err.message : String(err)}
|
|
5083
|
+
`);
|
|
5084
|
+
process.stderr.write(`[agendex] you can run it manually: ${cmd.display}
|
|
5085
|
+
`);
|
|
5086
|
+
resolveExit(1);
|
|
5087
|
+
});
|
|
5088
|
+
child.on("close", (code) => {
|
|
5089
|
+
if (didError)
|
|
5090
|
+
return;
|
|
5091
|
+
if (code === 0) {
|
|
5092
|
+
process.stdout.write(`[agendex] upgrade complete.
|
|
5093
|
+
`);
|
|
5094
|
+
process.stdout.write(`[agendex] note: if the daemon is running, restart it: \`agendex stop && agendex start\`
|
|
5095
|
+
`);
|
|
5096
|
+
resolveExit(0);
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
process.stderr.write(`[agendex] upgrade failed with exit code ${code ?? "unknown"}
|
|
5100
|
+
`);
|
|
5101
|
+
process.stderr.write(`[agendex] you can run it manually: ${cmd.display}
|
|
5102
|
+
`);
|
|
5103
|
+
resolveExit(code ?? 1);
|
|
5104
|
+
});
|
|
5105
|
+
});
|
|
5106
|
+
}
|
|
5107
|
+
|
|
4101
5108
|
// src/web.ts
|
|
4102
5109
|
async function openAgendexWeb(siteUrlOverride) {
|
|
4103
5110
|
const base = siteUrlOverride ?? getDefaultSiteUrl();
|
|
@@ -4135,7 +5142,7 @@ function firstCommandToken(argv) {
|
|
|
4135
5142
|
return;
|
|
4136
5143
|
}
|
|
4137
5144
|
var command = firstCommandToken(args) ?? "start";
|
|
4138
|
-
var cliEntry =
|
|
5145
|
+
var cliEntry = resolve8(process.argv[1] ?? fileURLToPath3(import.meta.url));
|
|
4139
5146
|
async function main() {
|
|
4140
5147
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
4141
5148
|
if (command === "--version" || command === "-v") {
|
|
@@ -4153,16 +5160,16 @@ async function main() {
|
|
|
4153
5160
|
"add-dir",
|
|
4154
5161
|
"remove-dir",
|
|
4155
5162
|
"list-dirs",
|
|
5163
|
+
"upgrade",
|
|
4156
5164
|
"help",
|
|
4157
5165
|
"--help",
|
|
4158
5166
|
"-h"
|
|
4159
5167
|
].includes(command);
|
|
4160
5168
|
if (!isInternal && !isPassthrough) {
|
|
4161
|
-
const { updateAvailable, current, latest } = await checkForUpdate();
|
|
4162
|
-
if (updateAvailable) {
|
|
4163
|
-
writeStderr(`[agendex] update
|
|
4164
|
-
writeStderr(`[agendex] run:
|
|
4165
|
-
return 1;
|
|
5169
|
+
const { checked, updateAvailable, current, latest } = await checkForUpdate();
|
|
5170
|
+
if (checked && updateAvailable) {
|
|
5171
|
+
writeStderr(`[agendex] update available: v${current} → v${latest}`);
|
|
5172
|
+
writeStderr(`[agendex] run: agendex upgrade`);
|
|
4166
5173
|
}
|
|
4167
5174
|
}
|
|
4168
5175
|
switch (command) {
|
|
@@ -4201,7 +5208,7 @@ async function main() {
|
|
|
4201
5208
|
const daemonArgs = [cliEntry, "start", "--daemon"];
|
|
4202
5209
|
if (devFlag)
|
|
4203
5210
|
daemonArgs.push("--dev");
|
|
4204
|
-
const child =
|
|
5211
|
+
const child = spawn4(process.execPath, daemonArgs, {
|
|
4205
5212
|
detached: true,
|
|
4206
5213
|
stdio: "ignore",
|
|
4207
5214
|
env: { ...process.env, ...devFlag ? { AGENDEX_DEV: "1" } : {} }
|
|
@@ -4335,7 +5342,7 @@ async function main() {
|
|
|
4335
5342
|
return 1;
|
|
4336
5343
|
}
|
|
4337
5344
|
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
4338
|
-
if (!
|
|
5345
|
+
if (!existsSync11(resolved)) {
|
|
4339
5346
|
writeStderr(`[agendex] path does not exist: ${resolved}`);
|
|
4340
5347
|
return 1;
|
|
4341
5348
|
}
|
|
@@ -4354,7 +5361,7 @@ async function main() {
|
|
|
4354
5361
|
const { request } = await import("node:http");
|
|
4355
5362
|
const body = JSON.stringify({ path: resolved });
|
|
4356
5363
|
try {
|
|
4357
|
-
const res = await new Promise((
|
|
5364
|
+
const res = await new Promise((resolve9, reject) => {
|
|
4358
5365
|
const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
|
|
4359
5366
|
method: "POST",
|
|
4360
5367
|
headers: {
|
|
@@ -4368,7 +5375,7 @@ async function main() {
|
|
|
4368
5375
|
res2.on("data", (chunk) => {
|
|
4369
5376
|
data += chunk;
|
|
4370
5377
|
});
|
|
4371
|
-
res2.on("end", () =>
|
|
5378
|
+
res2.on("end", () => resolve9({ status: res2.statusCode ?? 0, body: data }));
|
|
4372
5379
|
res2.on("error", reject);
|
|
4373
5380
|
});
|
|
4374
5381
|
req.on("error", reject);
|
|
@@ -4496,6 +5503,9 @@ async function main() {
|
|
|
4496
5503
|
}
|
|
4497
5504
|
return 0;
|
|
4498
5505
|
}
|
|
5506
|
+
case "upgrade": {
|
|
5507
|
+
return await runUpgrade({ force: args.includes("--force") });
|
|
5508
|
+
}
|
|
4499
5509
|
case "help":
|
|
4500
5510
|
case "--help":
|
|
4501
5511
|
case "-h": {
|
|
@@ -4522,6 +5532,8 @@ Usage:
|
|
|
4522
5532
|
agendex cleanup Interactively remove cloud daemons
|
|
4523
5533
|
agendex cleanup --stale Auto-remove all stale daemons
|
|
4524
5534
|
agendex status Show current config state + daemon status
|
|
5535
|
+
agendex upgrade Upgrade the globally installed CLI to the latest version
|
|
5536
|
+
agendex upgrade --force Reinstall latest even if already up to date
|
|
4525
5537
|
agendex help Show this help message
|
|
4526
5538
|
agendex --version Print CLI version
|
|
4527
5539
|
agendex -v Print CLI version
|
|
@@ -4550,13 +5562,13 @@ function flushStream(stream) {
|
|
|
4550
5562
|
if (stream.destroyed || !stream.writable) {
|
|
4551
5563
|
return Promise.resolve();
|
|
4552
5564
|
}
|
|
4553
|
-
return new Promise((
|
|
5565
|
+
return new Promise((resolve9, reject) => {
|
|
4554
5566
|
stream.write("", (error) => {
|
|
4555
5567
|
if (error) {
|
|
4556
5568
|
reject(error);
|
|
4557
5569
|
return;
|
|
4558
5570
|
}
|
|
4559
|
-
|
|
5571
|
+
resolve9();
|
|
4560
5572
|
});
|
|
4561
5573
|
});
|
|
4562
5574
|
}
|