@dunnewold-labs/mr-manager 0.3.0 → 0.4.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/index.mjs +807 -1010
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// cli/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command28 } from "commander";
|
|
5
5
|
import { existsSync as existsSync15 } from "fs";
|
|
6
6
|
import { homedir as homedir4 } from "os";
|
|
7
7
|
import { join as join13 } from "path";
|
|
@@ -696,7 +696,7 @@ async function runTest(options) {
|
|
|
696
696
|
postUpdate,
|
|
697
697
|
onProgress
|
|
698
698
|
} = options;
|
|
699
|
-
const
|
|
699
|
+
const log3 = onProgress || (() => {
|
|
700
700
|
});
|
|
701
701
|
const result = {
|
|
702
702
|
status: "passed",
|
|
@@ -709,37 +709,37 @@ async function runTest(options) {
|
|
|
709
709
|
let wtPath = null;
|
|
710
710
|
const worktreeName = `mr-test-${taskId.slice(0, 8)}`;
|
|
711
711
|
const timeoutHandle = setTimeout(() => {
|
|
712
|
-
|
|
712
|
+
log3("Test timed out after 5 minutes");
|
|
713
713
|
if (devProc) devProc.kill("SIGTERM");
|
|
714
714
|
}, 5 * 60 * 1e3);
|
|
715
715
|
try {
|
|
716
|
-
|
|
716
|
+
log3("Extracting branch from MR/PR link...");
|
|
717
717
|
const branch = extractBranchFromLink(taskLink, localPath);
|
|
718
718
|
if (!branch) {
|
|
719
719
|
throw new Error(`Could not extract branch from link: ${taskLink}`);
|
|
720
720
|
}
|
|
721
|
-
|
|
722
|
-
|
|
721
|
+
log3(`Branch: ${branch}`);
|
|
722
|
+
log3("Creating git worktree...");
|
|
723
723
|
wtPath = createWorktree(localPath, branch, worktreeName);
|
|
724
|
-
|
|
725
|
-
|
|
724
|
+
log3(`Worktree created at ${wtPath}`);
|
|
725
|
+
log3("Installing dependencies...");
|
|
726
726
|
try {
|
|
727
727
|
installDependencies(wtPath);
|
|
728
728
|
} catch (err) {
|
|
729
|
-
|
|
729
|
+
log3(`Warning: dependency install failed: ${err.message}`);
|
|
730
730
|
}
|
|
731
|
-
|
|
731
|
+
log3("Starting dev server...");
|
|
732
732
|
const port = await findAvailablePort(4e3);
|
|
733
733
|
devProc = await startDevServer(wtPath, port);
|
|
734
734
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
735
|
-
|
|
735
|
+
log3(`Dev server running on ${baseUrl}`);
|
|
736
736
|
await browseRunner(["goto", baseUrl]);
|
|
737
737
|
const plan = customPlan || buildDefaultTestPlan(baseUrl);
|
|
738
|
-
|
|
738
|
+
log3(`Executing ${plan.length}-step test plan...`);
|
|
739
739
|
for (let i = 0; i < plan.length; i++) {
|
|
740
740
|
const step = plan[i];
|
|
741
741
|
const stepDesc = step.description || `${step.command} ${(step.args || []).join(" ")}`;
|
|
742
|
-
|
|
742
|
+
log3(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
|
|
743
743
|
try {
|
|
744
744
|
if (step.command.startsWith("assert")) {
|
|
745
745
|
const assertResult = await evaluateAssertion(step, i, browseRunner);
|
|
@@ -789,7 +789,7 @@ async function runTest(options) {
|
|
|
789
789
|
}
|
|
790
790
|
} catch (err) {
|
|
791
791
|
result.errors.push(`Step ${i + 1} (${step.command}): ${err.message}`);
|
|
792
|
-
|
|
792
|
+
log3(`Step ${i + 1} error: ${err.message}`);
|
|
793
793
|
}
|
|
794
794
|
}
|
|
795
795
|
const totalAssertions = result.assertions.length;
|
|
@@ -1906,6 +1906,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
1906
1906
|
const active = /* @__PURE__ */ new Map();
|
|
1907
1907
|
const failed = /* @__PURE__ */ new Map();
|
|
1908
1908
|
const queued = /* @__PURE__ */ new Set();
|
|
1909
|
+
const finishing = /* @__PURE__ */ new Set();
|
|
1910
|
+
let pollRunning = false;
|
|
1909
1911
|
const approvalQueue = [];
|
|
1910
1912
|
let approvalRunning = false;
|
|
1911
1913
|
const flags = [
|
|
@@ -1990,32 +1992,94 @@ var watchCommand = new Command8("watch").description(
|
|
|
1990
1992
|
active.set(task.id, { process: child, title: task.title, repoDir });
|
|
1991
1993
|
child.on("exit", async (code) => {
|
|
1992
1994
|
active.delete(task.id);
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1995
|
+
finishing.add(task.id);
|
|
1996
|
+
try {
|
|
1997
|
+
if (code === 0) {
|
|
1998
|
+
try {
|
|
1999
|
+
const researchPath = resolve(repoDir, "research.md");
|
|
2000
|
+
if (existsSync6(researchPath)) {
|
|
2001
|
+
try {
|
|
2002
|
+
const researchContent = readFileSync5(researchPath, "utf-8");
|
|
2003
|
+
const existingResearch = existingResources.find((r) => r.type === "research");
|
|
2004
|
+
if (existingResearch) {
|
|
2005
|
+
await api.patch(`/api/tasks/${task.id}/resources/${existingResearch.id}`, {
|
|
2006
|
+
content: researchContent
|
|
2007
|
+
});
|
|
2008
|
+
logSuccess(prefix, `Updated existing research resource`);
|
|
2009
|
+
} else {
|
|
2010
|
+
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2011
|
+
type: "research",
|
|
2012
|
+
title: `Research \u2014 ${task.title}`,
|
|
2013
|
+
content: researchContent
|
|
2014
|
+
});
|
|
2015
|
+
logSuccess(prefix, `Uploaded research.md as task resource`);
|
|
2016
|
+
}
|
|
2017
|
+
unlinkSync(researchPath);
|
|
2018
|
+
} catch (err) {
|
|
2019
|
+
logWarn(prefix, `Failed to upload research resource: ${err.message}`);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (sessionId) {
|
|
2023
|
+
try {
|
|
2024
|
+
const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
|
|
2025
|
+
if (sessionEntries && sessionEntries.length > 0) {
|
|
2026
|
+
const sessionContent = JSON.stringify(sessionEntries);
|
|
2027
|
+
const existingSessionLog = existingResources.find((r) => r.type === "session-log");
|
|
2028
|
+
if (existingSessionLog) {
|
|
2029
|
+
await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
|
|
2030
|
+
content: sessionContent
|
|
2031
|
+
});
|
|
2032
|
+
logSuccess(prefix, `Updated session log resource (${sessionEntries.length} messages)`);
|
|
2033
|
+
} else {
|
|
2034
|
+
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2035
|
+
type: "session-log",
|
|
2036
|
+
title: `Session Log \u2014 ${task.title}`,
|
|
2037
|
+
content: sessionContent
|
|
2038
|
+
});
|
|
2039
|
+
logSuccess(prefix, `Uploaded session log (${sessionEntries.length} messages)`);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
} catch (err) {
|
|
2043
|
+
logWarn(prefix, `Failed to upload session log: ${err.message}`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
const noMrPath = resolve(repoDir, ".mr-no-mr");
|
|
2047
|
+
const noMrRequested = existsSync6(noMrPath);
|
|
2048
|
+
let noMrDescription;
|
|
2049
|
+
if (noMrRequested) {
|
|
2050
|
+
noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
|
|
2051
|
+
unlinkSync(noMrPath);
|
|
2052
|
+
logSuccess(prefix, `No ${vcs === "gitlab" ? "MR" : "PR"} needed \u2014 ${noMrDescription}`);
|
|
2053
|
+
}
|
|
2054
|
+
const prLabel = vcs === "gitlab" ? "MR" : "PR";
|
|
2055
|
+
let prUrl = null;
|
|
2056
|
+
if (!noMrRequested) {
|
|
2057
|
+
prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
|
|
2058
|
+
if (!prUrl) {
|
|
2059
|
+
prUrl = await extractPrUrlFromUpdates(task.id);
|
|
2060
|
+
if (prUrl) {
|
|
2061
|
+
logInfo(prefix, `Found ${prLabel} URL from agent updates: ${paint("cyan", prUrl)}`);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (prUrl) {
|
|
2065
|
+
logSuccess(prefix, `${prLabel} ready: ${paint("cyan", prUrl)}`);
|
|
2006
2066
|
} else {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
title: `Research \u2014 ${task.title}`,
|
|
2010
|
-
content: researchContent
|
|
2011
|
-
});
|
|
2012
|
-
logSuccess(prefix, `Uploaded research.md as task resource`);
|
|
2067
|
+
logWarn(prefix, `No ${prLabel} found for branch ${paint("cyan", branchName)}`);
|
|
2068
|
+
await postTaskUpdate(task.id, `Agent finished \u2014 no ${prLabel} found for branch ${branchName}`, "system");
|
|
2013
2069
|
}
|
|
2014
|
-
unlinkSync(researchPath);
|
|
2015
|
-
} catch (err) {
|
|
2016
|
-
logWarn(prefix, `Failed to upload research resource: ${err.message}`);
|
|
2017
2070
|
}
|
|
2071
|
+
await api.patch(`/api/tasks/${task.id}`, {
|
|
2072
|
+
status: "review",
|
|
2073
|
+
...prUrl ? { link: prUrl } : {}
|
|
2074
|
+
});
|
|
2075
|
+
logSuccess(prefix, `"${paint("bold", task.title)}" marked ready for review`);
|
|
2076
|
+
await postTaskUpdate(task.id, noMrRequested ? `Task marked ready for review \u2014 no ${prLabel} needed: ${noMrDescription}` : "Task marked ready for review", "system");
|
|
2077
|
+
} catch (err) {
|
|
2078
|
+
logError(prefix, `Failed to update task: ${err.message}`);
|
|
2018
2079
|
}
|
|
2080
|
+
} else {
|
|
2081
|
+
logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
|
|
2082
|
+
await postTaskUpdate(task.id, `Agent failed with exit code ${code}`, "system");
|
|
2019
2083
|
if (sessionId) {
|
|
2020
2084
|
try {
|
|
2021
2085
|
const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
|
|
@@ -2026,79 +2090,22 @@ var watchCommand = new Command8("watch").description(
|
|
|
2026
2090
|
await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
|
|
2027
2091
|
content: sessionContent
|
|
2028
2092
|
});
|
|
2029
|
-
logSuccess(prefix, `Updated session log resource (${sessionEntries.length} messages)`);
|
|
2030
2093
|
} else {
|
|
2031
2094
|
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2032
2095
|
type: "session-log",
|
|
2033
2096
|
title: `Session Log \u2014 ${task.title}`,
|
|
2034
2097
|
content: sessionContent
|
|
2035
2098
|
});
|
|
2036
|
-
logSuccess(prefix, `Uploaded session log (${sessionEntries.length} messages)`);
|
|
2037
2099
|
}
|
|
2100
|
+
logInfo(prefix, `Uploaded session log for failed task (${sessionEntries.length} messages)`);
|
|
2038
2101
|
}
|
|
2039
|
-
} catch
|
|
2040
|
-
logWarn(prefix, `Failed to upload session log: ${err.message}`);
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
const noMrPath = resolve(repoDir, ".mr-no-mr");
|
|
2044
|
-
const noMrRequested = existsSync6(noMrPath);
|
|
2045
|
-
let noMrDescription;
|
|
2046
|
-
if (noMrRequested) {
|
|
2047
|
-
noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
|
|
2048
|
-
unlinkSync(noMrPath);
|
|
2049
|
-
logSuccess(prefix, `No ${vcs === "gitlab" ? "MR" : "PR"} needed \u2014 ${noMrDescription}`);
|
|
2050
|
-
}
|
|
2051
|
-
const prLabel = vcs === "gitlab" ? "MR" : "PR";
|
|
2052
|
-
let prUrl = null;
|
|
2053
|
-
if (!noMrRequested) {
|
|
2054
|
-
prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
|
|
2055
|
-
if (!prUrl) {
|
|
2056
|
-
prUrl = await extractPrUrlFromUpdates(task.id);
|
|
2057
|
-
if (prUrl) {
|
|
2058
|
-
logInfo(prefix, `Found ${prLabel} URL from agent updates: ${paint("cyan", prUrl)}`);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
if (prUrl) {
|
|
2062
|
-
logSuccess(prefix, `${prLabel} ready: ${paint("cyan", prUrl)}`);
|
|
2063
|
-
} else {
|
|
2064
|
-
logWarn(prefix, `No ${prLabel} found for branch ${paint("cyan", branchName)}`);
|
|
2065
|
-
await postTaskUpdate(task.id, `Agent finished \u2014 no ${prLabel} found for branch ${branchName}`, "system");
|
|
2102
|
+
} catch {
|
|
2066
2103
|
}
|
|
2067
2104
|
}
|
|
2068
|
-
await api.patch(`/api/tasks/${task.id}`, {
|
|
2069
|
-
status: "review",
|
|
2070
|
-
...prUrl ? { link: prUrl } : {}
|
|
2071
|
-
});
|
|
2072
|
-
logSuccess(prefix, `"${paint("bold", task.title)}" marked ready for review`);
|
|
2073
|
-
await postTaskUpdate(task.id, noMrRequested ? `Task marked ready for review \u2014 no ${prLabel} needed: ${noMrDescription}` : "Task marked ready for review", "system");
|
|
2074
|
-
} catch (err) {
|
|
2075
|
-
logError(prefix, `Failed to update task: ${err.message}`);
|
|
2076
|
-
}
|
|
2077
|
-
} else {
|
|
2078
|
-
logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
|
|
2079
|
-
await postTaskUpdate(task.id, `Agent failed with exit code ${code}`, "system");
|
|
2080
|
-
if (sessionId) {
|
|
2081
|
-
try {
|
|
2082
|
-
const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
|
|
2083
|
-
if (sessionEntries && sessionEntries.length > 0) {
|
|
2084
|
-
const sessionContent = JSON.stringify(sessionEntries);
|
|
2085
|
-
const existingSessionLog = existingResources.find((r) => r.type === "session-log");
|
|
2086
|
-
if (existingSessionLog) {
|
|
2087
|
-
await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
|
|
2088
|
-
content: sessionContent
|
|
2089
|
-
});
|
|
2090
|
-
} else {
|
|
2091
|
-
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2092
|
-
type: "session-log",
|
|
2093
|
-
title: `Session Log \u2014 ${task.title}`,
|
|
2094
|
-
content: sessionContent
|
|
2095
|
-
});
|
|
2096
|
-
}
|
|
2097
|
-
logInfo(prefix, `Uploaded session log for failed task (${sessionEntries.length} messages)`);
|
|
2098
|
-
}
|
|
2099
|
-
} catch {
|
|
2100
|
-
}
|
|
2101
2105
|
}
|
|
2106
|
+
} finally {
|
|
2107
|
+
queued.delete(task.id);
|
|
2108
|
+
finishing.delete(task.id);
|
|
2102
2109
|
}
|
|
2103
2110
|
});
|
|
2104
2111
|
}
|
|
@@ -2135,49 +2142,54 @@ var watchCommand = new Command8("watch").description(
|
|
|
2135
2142
|
active.set(task.id, { process: child, title: task.title, repoDir });
|
|
2136
2143
|
child.on("exit", async (code) => {
|
|
2137
2144
|
active.delete(task.id);
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
const prdPath = resolve(repoDir, "prd.md");
|
|
2142
|
-
let prdContent;
|
|
2145
|
+
finishing.add(task.id);
|
|
2146
|
+
try {
|
|
2147
|
+
if (code === 0) {
|
|
2143
2148
|
try {
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
unlinkSync(prdPath);
|
|
2147
|
-
} catch {
|
|
2148
|
-
logWarn(prefix, `No prd.md file found in ${repoDir} \u2014 PRD may have been posted inline`);
|
|
2149
|
-
}
|
|
2150
|
-
if (prdContent) {
|
|
2149
|
+
const prdPath = resolve(repoDir, "prd.md");
|
|
2150
|
+
let prdContent;
|
|
2151
2151
|
try {
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2152
|
+
prdContent = readFileSync5(prdPath, "utf-8");
|
|
2153
|
+
logInfo(prefix, `Read PRD from ${paint("cyan", "prd.md")} (${prdContent.length} chars)`);
|
|
2154
|
+
unlinkSync(prdPath);
|
|
2155
|
+
} catch {
|
|
2156
|
+
logWarn(prefix, `No prd.md file found in ${repoDir} \u2014 PRD may have been posted inline`);
|
|
2157
|
+
}
|
|
2158
|
+
if (prdContent) {
|
|
2159
|
+
try {
|
|
2160
|
+
if (existingPlanResource) {
|
|
2161
|
+
await api.patch(`/api/tasks/${task.id}/resources/${existingPlanResource.id}`, {
|
|
2162
|
+
content: prdContent
|
|
2163
|
+
});
|
|
2164
|
+
logSuccess(prefix, `Updated existing PRD resource`);
|
|
2165
|
+
} else {
|
|
2166
|
+
await api.post(`/api/tasks/${task.id}/resources`, {
|
|
2167
|
+
type: "plan",
|
|
2168
|
+
title: `PRD \u2014 ${task.title}`,
|
|
2169
|
+
content: prdContent
|
|
2170
|
+
});
|
|
2171
|
+
logSuccess(prefix, `Uploaded PRD as task resource`);
|
|
2172
|
+
}
|
|
2173
|
+
} catch (err) {
|
|
2174
|
+
logWarn(prefix, `Failed to upload PRD resource: ${err.message}`);
|
|
2164
2175
|
}
|
|
2165
|
-
} catch (err) {
|
|
2166
|
-
logWarn(prefix, `Failed to upload PRD resource: ${err.message}`);
|
|
2167
2176
|
}
|
|
2177
|
+
await api.patch(`/api/tasks/${task.id}`, {
|
|
2178
|
+
status: "review",
|
|
2179
|
+
...prdContent ? { prdContent } : {}
|
|
2180
|
+
});
|
|
2181
|
+
logSuccess(prefix, `"${paint("bold", task.title)}" PRD generated and marked ready for review`);
|
|
2182
|
+
await postTaskUpdate(task.id, "PRD generated \u2014 ready for review", "system");
|
|
2183
|
+
} catch (err) {
|
|
2184
|
+
logError(prefix, `Failed to update task: ${err.message}`);
|
|
2168
2185
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
});
|
|
2173
|
-
logSuccess(prefix, `"${paint("bold", task.title)}" PRD generated and marked ready for review`);
|
|
2174
|
-
await postTaskUpdate(task.id, "PRD generated \u2014 ready for review", "system");
|
|
2175
|
-
} catch (err) {
|
|
2176
|
-
logError(prefix, `Failed to update task: ${err.message}`);
|
|
2186
|
+
} else {
|
|
2187
|
+
logError(prefix, `"${paint("bold", task.title)}" PRD generation failed (exit ${code}), leaving status unchanged`);
|
|
2188
|
+
await postTaskUpdate(task.id, `PRD generation failed with exit code ${code}`, "system");
|
|
2177
2189
|
}
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2190
|
+
} finally {
|
|
2191
|
+
queued.delete(task.id);
|
|
2192
|
+
finishing.delete(task.id);
|
|
2181
2193
|
}
|
|
2182
2194
|
});
|
|
2183
2195
|
}
|
|
@@ -2208,42 +2220,48 @@ var watchCommand = new Command8("watch").description(
|
|
|
2208
2220
|
const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, proto.title);
|
|
2209
2221
|
active.set(`proto-${proto.id}`, { process: child, title: proto.title, repoDir });
|
|
2210
2222
|
child.on("exit", async (code) => {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2223
|
+
const key = `proto-${proto.id}`;
|
|
2224
|
+
active.delete(key);
|
|
2225
|
+
finishing.add(key);
|
|
2226
|
+
try {
|
|
2227
|
+
if (code === 0) {
|
|
2228
|
+
try {
|
|
2229
|
+
const protoPattern = /^prototype-\d+\.html$/;
|
|
2230
|
+
const found = readdirSync(repoDir).filter((f) => protoPattern.test(f)).sort();
|
|
2231
|
+
const files = found.map((f) => ({
|
|
2232
|
+
name: f,
|
|
2233
|
+
content: readFileSync5(resolve(repoDir, f), "utf-8")
|
|
2234
|
+
}));
|
|
2235
|
+
if (files.length === 0) {
|
|
2236
|
+
logError(prefix, `No prototype HTML files found in ${repoDir}`);
|
|
2237
|
+
await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
await api.patch(`/api/prototypes/${proto.id}`, { status: "completed", files });
|
|
2241
|
+
logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${files.length} file(s)`);
|
|
2242
|
+
for (const file of files) {
|
|
2243
|
+
try {
|
|
2244
|
+
unlinkSync(resolve(repoDir, file.name));
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
} catch (err) {
|
|
2249
|
+
logError(prefix, `Failed to upload prototype: ${err.message}`);
|
|
2229
2250
|
try {
|
|
2230
|
-
|
|
2251
|
+
await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
|
|
2231
2252
|
} catch {
|
|
2232
2253
|
}
|
|
2233
2254
|
}
|
|
2234
|
-
}
|
|
2235
|
-
logError(prefix, `
|
|
2255
|
+
} else {
|
|
2256
|
+
logError(prefix, `"${paint("bold", proto.title)}" prototype failed (exit ${code})`);
|
|
2236
2257
|
try {
|
|
2237
2258
|
await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
|
|
2238
2259
|
} catch {
|
|
2239
2260
|
}
|
|
2240
2261
|
}
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" });
|
|
2245
|
-
} catch {
|
|
2246
|
-
}
|
|
2262
|
+
} finally {
|
|
2263
|
+
queued.delete(key);
|
|
2264
|
+
finishing.delete(key);
|
|
2247
2265
|
}
|
|
2248
2266
|
});
|
|
2249
2267
|
}
|
|
@@ -2259,16 +2277,22 @@ var watchCommand = new Command8("watch").description(
|
|
|
2259
2277
|
const child = spawnAgent(agent, workDir, prompt2, prefix, void 0, project.name);
|
|
2260
2278
|
active.set(`repo-${project.id}`, { process: child, title: project.name, repoDir: workDir });
|
|
2261
2279
|
child.on("exit", async (code) => {
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2280
|
+
const key = `repo-${project.id}`;
|
|
2281
|
+
active.delete(key);
|
|
2282
|
+
finishing.add(key);
|
|
2283
|
+
try {
|
|
2284
|
+
if (code === 0) {
|
|
2285
|
+
logSuccess(prefix, `"${paint("bold", project.name)}" repo creation complete`);
|
|
2286
|
+
} else {
|
|
2287
|
+
logError(prefix, `"${paint("bold", project.name)}" repo creation failed (exit ${code})`);
|
|
2288
|
+
try {
|
|
2289
|
+
await api.patch(`/api/projects/${project.id}`, { repoCreationStatus: "failed" });
|
|
2290
|
+
} catch {
|
|
2291
|
+
}
|
|
2271
2292
|
}
|
|
2293
|
+
} finally {
|
|
2294
|
+
queued.delete(key);
|
|
2295
|
+
finishing.delete(key);
|
|
2272
2296
|
}
|
|
2273
2297
|
});
|
|
2274
2298
|
}
|
|
@@ -2286,85 +2310,91 @@ var watchCommand = new Command8("watch").description(
|
|
|
2286
2310
|
const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, idea.title);
|
|
2287
2311
|
active.set(`idea-${idea.id}`, { process: child, title: idea.title, repoDir });
|
|
2288
2312
|
child.on("exit", async (code) => {
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
let protoHtml;
|
|
2295
|
-
let followUpTasks;
|
|
2296
|
-
const planPath = resolve(repoDir, "idea-plan.md");
|
|
2297
|
-
const tasksPath = resolve(repoDir, "idea-tasks.json");
|
|
2298
|
-
const protoPath = resolve(repoDir, "idea-prototype.html");
|
|
2299
|
-
try {
|
|
2300
|
-
plan = readFileSync5(planPath, "utf-8");
|
|
2301
|
-
} catch {
|
|
2302
|
-
}
|
|
2303
|
-
try {
|
|
2304
|
-
protoHtml = readFileSync5(protoPath, "utf-8");
|
|
2305
|
-
} catch {
|
|
2306
|
-
}
|
|
2313
|
+
const key = `idea-${idea.id}`;
|
|
2314
|
+
active.delete(key);
|
|
2315
|
+
finishing.add(key);
|
|
2316
|
+
try {
|
|
2317
|
+
if (code === 0) {
|
|
2307
2318
|
try {
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2319
|
+
let plan;
|
|
2320
|
+
let protoHtml;
|
|
2321
|
+
let followUpTasks;
|
|
2322
|
+
const planPath = resolve(repoDir, "idea-plan.md");
|
|
2323
|
+
const tasksPath = resolve(repoDir, "idea-tasks.json");
|
|
2324
|
+
const protoPath = resolve(repoDir, "idea-prototype.html");
|
|
2325
|
+
try {
|
|
2326
|
+
plan = readFileSync5(planPath, "utf-8");
|
|
2327
|
+
} catch {
|
|
2312
2328
|
}
|
|
2313
|
-
} catch {
|
|
2314
|
-
}
|
|
2315
|
-
if (!plan && !protoHtml) {
|
|
2316
|
-
logError(prefix, `No output files found in ${repoDir}`);
|
|
2317
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
2318
|
-
return;
|
|
2319
|
-
}
|
|
2320
|
-
const updateData = { status: "generated" };
|
|
2321
|
-
if (plan) updateData.plan = plan;
|
|
2322
|
-
if (followUpTasks) updateData.followUpTasks = followUpTasks;
|
|
2323
|
-
if (protoHtml) {
|
|
2324
2329
|
try {
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2330
|
+
protoHtml = readFileSync5(protoPath, "utf-8");
|
|
2331
|
+
} catch {
|
|
2332
|
+
}
|
|
2333
|
+
try {
|
|
2334
|
+
const tasksRaw = readFileSync5(tasksPath, "utf-8");
|
|
2335
|
+
const parsed = JSON.parse(tasksRaw);
|
|
2336
|
+
if (Array.isArray(parsed)) {
|
|
2337
|
+
followUpTasks = parsed.filter((t) => t && typeof t === "object" && "title" in t);
|
|
2338
|
+
}
|
|
2339
|
+
} catch {
|
|
2340
|
+
}
|
|
2341
|
+
if (!plan && !protoHtml) {
|
|
2342
|
+
logError(prefix, `No output files found in ${repoDir}`);
|
|
2343
|
+
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
const updateData = { status: "generated" };
|
|
2347
|
+
if (plan) updateData.plan = plan;
|
|
2348
|
+
if (followUpTasks) updateData.followUpTasks = followUpTasks;
|
|
2349
|
+
if (protoHtml) {
|
|
2350
|
+
try {
|
|
2351
|
+
const proto = await api.post("/api/prototypes", {
|
|
2352
|
+
title: `${idea.title} \u2014 Idea Prototype`,
|
|
2353
|
+
prompt: idea.description || idea.title,
|
|
2354
|
+
variantCount: 1,
|
|
2355
|
+
projectId: idea.projectId ?? null
|
|
2356
|
+
});
|
|
2357
|
+
await api.patch(`/api/prototypes/${proto.id}`, {
|
|
2358
|
+
status: "completed",
|
|
2359
|
+
files: [{ name: "idea-prototype.html", content: protoHtml }]
|
|
2360
|
+
});
|
|
2361
|
+
updateData.generatedPrototypeId = proto.id;
|
|
2362
|
+
logSuccess(prefix, `Prototype created: ${paint("gray", proto.id.slice(0, 8))}`);
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
logError(prefix, `Failed to create prototype: ${err.message}`);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
await api.patch(`/api/ideas/${idea.id}`, updateData);
|
|
2368
|
+
logSuccess(prefix, `"${paint("bold", idea.title)}" generation complete`);
|
|
2369
|
+
try {
|
|
2370
|
+
unlinkSync(planPath);
|
|
2371
|
+
} catch {
|
|
2372
|
+
}
|
|
2373
|
+
try {
|
|
2374
|
+
unlinkSync(tasksPath);
|
|
2375
|
+
} catch {
|
|
2376
|
+
}
|
|
2377
|
+
try {
|
|
2378
|
+
unlinkSync(protoPath);
|
|
2379
|
+
} catch {
|
|
2380
|
+
}
|
|
2381
|
+
} catch (err) {
|
|
2382
|
+
logError(prefix, `Failed to upload idea output: ${err.message}`);
|
|
2383
|
+
try {
|
|
2384
|
+
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
2385
|
+
} catch {
|
|
2339
2386
|
}
|
|
2340
2387
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
try {
|
|
2344
|
-
unlinkSync(planPath);
|
|
2345
|
-
} catch {
|
|
2346
|
-
}
|
|
2347
|
-
try {
|
|
2348
|
-
unlinkSync(tasksPath);
|
|
2349
|
-
} catch {
|
|
2350
|
-
}
|
|
2351
|
-
try {
|
|
2352
|
-
unlinkSync(protoPath);
|
|
2353
|
-
} catch {
|
|
2354
|
-
}
|
|
2355
|
-
} catch (err) {
|
|
2356
|
-
logError(prefix, `Failed to upload idea output: ${err.message}`);
|
|
2388
|
+
} else {
|
|
2389
|
+
logError(prefix, `"${paint("bold", idea.title)}" generation failed (exit ${code})`);
|
|
2357
2390
|
try {
|
|
2358
2391
|
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
2359
2392
|
} catch {
|
|
2360
2393
|
}
|
|
2361
2394
|
}
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
2366
|
-
} catch {
|
|
2367
|
-
}
|
|
2395
|
+
} finally {
|
|
2396
|
+
queued.delete(key);
|
|
2397
|
+
finishing.delete(key);
|
|
2368
2398
|
}
|
|
2369
2399
|
});
|
|
2370
2400
|
}
|
|
@@ -2443,329 +2473,342 @@ ${divider}`);
|
|
|
2443
2473
|
}
|
|
2444
2474
|
}
|
|
2445
2475
|
async function poll() {
|
|
2446
|
-
|
|
2447
|
-
|
|
2476
|
+
if (pollRunning) return;
|
|
2477
|
+
pollRunning = true;
|
|
2448
2478
|
try {
|
|
2449
|
-
queuedTasks
|
|
2450
|
-
delegatedTasks
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
const tasks = [...nonTestQueued, ...nonTestDelegated];
|
|
2458
|
-
const config = loadConfig();
|
|
2459
|
-
const activeTaskIds = new Set(tasks.map((t) => t.id));
|
|
2460
|
-
for (const [taskId, entry] of active) {
|
|
2461
|
-
if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
|
|
2462
|
-
if (!activeTaskIds.has(taskId)) {
|
|
2463
|
-
logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer active, terminating\u2026`);
|
|
2464
|
-
entry.process.kill("SIGTERM");
|
|
2465
|
-
active.delete(taskId);
|
|
2466
|
-
queued.delete(taskId);
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "idea-", "test-"];
|
|
2470
|
-
for (const taskId of failed.keys()) {
|
|
2471
|
-
if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
|
|
2472
|
-
if (!activeTaskIds.has(taskId)) failed.delete(taskId);
|
|
2473
|
-
}
|
|
2474
|
-
for (const taskId of queued) {
|
|
2475
|
-
if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
|
|
2476
|
-
if (!activeTaskIds.has(taskId)) queued.delete(taskId);
|
|
2477
|
-
}
|
|
2478
|
-
for (const task of tasks) {
|
|
2479
|
-
if (queued.has(task.id)) continue;
|
|
2480
|
-
if (failed.has(task.id)) continue;
|
|
2481
|
-
const sid = shortId(task.id);
|
|
2482
|
-
const prefix = taskTag(sid);
|
|
2483
|
-
const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
|
|
2484
|
-
if (!repoDir) {
|
|
2485
|
-
const reason = `no linked directory found under ${rootDir}`;
|
|
2486
|
-
logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
|
|
2487
|
-
failed.set(task.id, reason);
|
|
2488
|
-
continue;
|
|
2489
|
-
}
|
|
2490
|
-
if (!existsSync6(repoDir)) {
|
|
2491
|
-
const reason = `linked directory "${repoDir}" does not exist`;
|
|
2492
|
-
logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
|
|
2493
|
-
failed.set(task.id, reason);
|
|
2494
|
-
continue;
|
|
2479
|
+
let queuedTasks;
|
|
2480
|
+
let delegatedTasks;
|
|
2481
|
+
try {
|
|
2482
|
+
queuedTasks = await api.get("/api/tasks?status=queued");
|
|
2483
|
+
delegatedTasks = await api.get("/api/tasks?status=delegated");
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
logError(watchTag(), `Failed to fetch tasks: ${err.message}`);
|
|
2486
|
+
return;
|
|
2495
2487
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2488
|
+
const nonTestQueued = queuedTasks.filter((t) => t.mode !== "testing");
|
|
2489
|
+
const nonTestDelegated = delegatedTasks.filter((t) => t.mode !== "testing");
|
|
2490
|
+
const tasks = [...nonTestQueued, ...nonTestDelegated];
|
|
2491
|
+
const config = loadConfig();
|
|
2492
|
+
const activeTaskIds = new Set(tasks.map((t) => t.id));
|
|
2493
|
+
for (const [taskId, entry] of active) {
|
|
2494
|
+
if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
|
|
2495
|
+
if (!activeTaskIds.has(taskId)) {
|
|
2496
|
+
logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer active, terminating\u2026`);
|
|
2497
|
+
entry.process.kill("SIGTERM");
|
|
2498
|
+
active.delete(taskId);
|
|
2499
|
+
queued.delete(taskId);
|
|
2502
2500
|
}
|
|
2503
2501
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
`${paint("yellow", "[dry-run]")} would dispatch "${paint("bold", task.title)}" (${paint("gray", task.id)}) in ${paint("cyan", repoDir)}`
|
|
2509
|
-
);
|
|
2510
|
-
continue;
|
|
2502
|
+
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "idea-", "test-"];
|
|
2503
|
+
for (const taskId of failed.keys()) {
|
|
2504
|
+
if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
|
|
2505
|
+
if (!activeTaskIds.has(taskId)) failed.delete(taskId);
|
|
2511
2506
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
processApprovalQueue();
|
|
2517
|
-
} else {
|
|
2518
|
-
dispatchTask(task, repoDir);
|
|
2507
|
+
for (const taskId of queued) {
|
|
2508
|
+
if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
|
|
2509
|
+
if (finishing.has(taskId)) continue;
|
|
2510
|
+
if (!activeTaskIds.has(taskId)) queued.delete(taskId);
|
|
2519
2511
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2512
|
+
for (const task of tasks) {
|
|
2513
|
+
if (queued.has(task.id)) continue;
|
|
2514
|
+
if (finishing.has(task.id)) continue;
|
|
2515
|
+
if (failed.has(task.id)) continue;
|
|
2516
|
+
const sid = shortId(task.id);
|
|
2517
|
+
const prefix = taskTag(sid);
|
|
2518
|
+
const repoDir = findDirectoryForProject(config, task.projectId, rootDir);
|
|
2519
|
+
if (!repoDir) {
|
|
2520
|
+
const reason = `no linked directory found under ${rootDir}`;
|
|
2521
|
+
logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
|
|
2522
|
+
failed.set(task.id, reason);
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
if (!existsSync6(repoDir)) {
|
|
2526
|
+
const reason = `linked directory "${repoDir}" does not exist`;
|
|
2527
|
+
logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
|
|
2528
|
+
failed.set(task.id, reason);
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
if (task.status === "queued") {
|
|
2532
|
+
try {
|
|
2533
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "delegated" });
|
|
2534
|
+
} catch (err) {
|
|
2535
|
+
logError(prefix, `Failed to mark task as delegated: ${err.message}`);
|
|
2536
|
+
continue;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
queued.add(task.id);
|
|
2540
|
+
if (dryRun) {
|
|
2541
|
+
logInfo(
|
|
2542
|
+
watchTag(),
|
|
2543
|
+
`${paint("yellow", "[dry-run]")} would dispatch "${paint("bold", task.title)}" (${paint("gray", task.id)}) in ${paint("cyan", repoDir)}`
|
|
2544
|
+
);
|
|
2545
|
+
continue;
|
|
2546
|
+
}
|
|
2547
|
+
if (task.mode === "planning") {
|
|
2548
|
+
dispatchPlanModeTask(task, repoDir);
|
|
2549
|
+
} else if (planApproval) {
|
|
2550
|
+
approvalQueue.push({ task, repoDir });
|
|
2551
|
+
processApprovalQueue();
|
|
2552
|
+
} else {
|
|
2553
|
+
dispatchTask(task, repoDir);
|
|
2554
|
+
}
|
|
2554
2555
|
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2556
|
+
let prototypes = [];
|
|
2557
|
+
try {
|
|
2558
|
+
prototypes = await api.get("/api/prototypes?status=in_progress");
|
|
2559
|
+
} catch (err) {
|
|
2560
|
+
logError(watchTag(), `Failed to fetch prototypes: ${err.message}`);
|
|
2559
2561
|
}
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2562
|
+
const inProgressProtoKeys = new Set(prototypes.map((p) => `proto-${p.id}`));
|
|
2563
|
+
for (const [key, entry] of active) {
|
|
2564
|
+
if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) {
|
|
2565
|
+
logWarn(watchTag(), `Prototype ${paint("yellow", key)} no longer in_progress, terminating\u2026`);
|
|
2566
|
+
entry.process.kill("SIGTERM");
|
|
2567
|
+
active.delete(key);
|
|
2568
|
+
queued.delete(key);
|
|
2569
|
+
}
|
|
2564
2570
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
if (dryRun) {
|
|
2568
|
-
logInfo(
|
|
2569
|
-
watchTag(),
|
|
2570
|
-
`${paint("yellow", "[dry-run]")} would dispatch prototype "${paint("bold", proto.title)}" in ${paint("cyan", repoDir)}`
|
|
2571
|
-
);
|
|
2572
|
-
continue;
|
|
2571
|
+
for (const key of failed.keys()) {
|
|
2572
|
+
if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) failed.delete(key);
|
|
2573
2573
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
const testTasks = queuedTasks.filter((t) => t.mode === "testing");
|
|
2577
|
-
for (const task of testTasks) {
|
|
2578
|
-
const key = `test-${task.id}`;
|
|
2579
|
-
if (queued.has(key)) continue;
|
|
2580
|
-
if (failed.has(key)) continue;
|
|
2581
|
-
const sid = shortId(task.id);
|
|
2582
|
-
const prefix = testTag(sid);
|
|
2583
|
-
if (!task.link) {
|
|
2584
|
-
logWarn(prefix, `"${task.title}": no MR/PR link \u2014 skipping`);
|
|
2585
|
-
continue;
|
|
2574
|
+
for (const key of queued) {
|
|
2575
|
+
if (key.startsWith("proto-") && !inProgressProtoKeys.has(key) && !finishing.has(key)) queued.delete(key);
|
|
2586
2576
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
);
|
|
2593
|
-
continue;
|
|
2577
|
+
const activeProtoCount = [...active.keys()].filter((k) => k.startsWith("proto-")).length;
|
|
2578
|
+
let protoSlots = Math.max(0, 2 - activeProtoCount);
|
|
2579
|
+
for (const proto of prototypes) {
|
|
2580
|
+
if (protoSlots <= 0) break;
|
|
2581
|
+
const key = `proto-${proto.id}`;
|
|
2582
|
+
if (queued.has(key)) continue;
|
|
2583
|
+
if (finishing.has(key)) continue;
|
|
2584
|
+
if (failed.has(key)) continue;
|
|
2585
|
+
const sid = shortId(proto.id);
|
|
2586
|
+
const prefix = protoTag(sid);
|
|
2587
|
+
if (!proto.projectId) {
|
|
2588
|
+
logWarn(prefix, `"${proto.title}": no projectId \u2014 skipping`);
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
const repoDir = findDirectoryForProject(config, proto.projectId, rootDir);
|
|
2592
|
+
if (!repoDir) {
|
|
2593
|
+
logWarn(prefix, `"${proto.title}": no linked directory found \u2014 skipping`);
|
|
2594
|
+
continue;
|
|
2595
|
+
}
|
|
2596
|
+
if (!existsSync6(repoDir)) {
|
|
2597
|
+
logError(prefix, `"${proto.title}": linked directory "${repoDir}" does not exist \u2014 skipping`);
|
|
2598
|
+
failed.set(key, `directory does not exist: ${repoDir}`);
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
queued.add(key);
|
|
2602
|
+
protoSlots--;
|
|
2603
|
+
if (dryRun) {
|
|
2604
|
+
logInfo(
|
|
2605
|
+
watchTag(),
|
|
2606
|
+
`${paint("yellow", "[dry-run]")} would dispatch prototype "${paint("bold", proto.title)}" in ${paint("cyan", repoDir)}`
|
|
2607
|
+
);
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
dispatchPrototypeJob(proto, repoDir);
|
|
2594
2611
|
}
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2612
|
+
const testTasks = queuedTasks.filter((t) => t.mode === "testing");
|
|
2613
|
+
for (const task of testTasks) {
|
|
2614
|
+
const key = `test-${task.id}`;
|
|
2615
|
+
if (queued.has(key)) continue;
|
|
2616
|
+
if (finishing.has(key)) continue;
|
|
2617
|
+
if (failed.has(key)) continue;
|
|
2618
|
+
const sid = shortId(task.id);
|
|
2619
|
+
const prefix = testTag(sid);
|
|
2620
|
+
if (!task.link) {
|
|
2621
|
+
logWarn(prefix, `"${task.title}": no MR/PR link \u2014 skipping`);
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
queued.add(key);
|
|
2625
|
+
if (dryRun) {
|
|
2626
|
+
logInfo(
|
|
2627
|
+
watchTag(),
|
|
2628
|
+
`${paint("yellow", "[dry-run]")} would run test for "${paint("bold", task.title)}"`
|
|
2629
|
+
);
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
(async () => {
|
|
2633
|
+
logDispatch(prefix, `Running test for "${paint("bold", task.title)}"\u2026`);
|
|
2614
2634
|
try {
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2635
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "delegated" });
|
|
2636
|
+
await postTaskUpdate(task.id, "Test started \u2014 setting up environment\u2026");
|
|
2637
|
+
let localPath;
|
|
2638
|
+
try {
|
|
2639
|
+
const project = await api.get(`/api/projects/${task.projectId}`);
|
|
2640
|
+
localPath = project.localPath ?? void 0;
|
|
2641
|
+
} catch {
|
|
2620
2642
|
}
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
const formData = new FormData();
|
|
2636
|
-
const blob = new Blob([imageBuffer], { type: "image/png" });
|
|
2637
|
-
formData.append("file", blob, fileName);
|
|
2638
|
-
formData.append("prefix", "test-screenshots");
|
|
2639
|
-
const uploadRes = await fetch(`${cfg.apiUrl}/api/upload`, {
|
|
2640
|
-
method: "POST",
|
|
2641
|
-
headers: { Authorization: `Bearer ${cfg.apiKey}` },
|
|
2642
|
-
body: formData
|
|
2643
|
-
});
|
|
2644
|
-
if (!uploadRes.ok) return null;
|
|
2645
|
-
const uploadData = await uploadRes.json();
|
|
2646
|
-
await api.post(`/api/tasks/${task.id}/updates`, {
|
|
2647
|
-
message,
|
|
2648
|
-
imageUrl: uploadData.url,
|
|
2649
|
-
source: "system"
|
|
2650
|
-
});
|
|
2651
|
-
return uploadData.url;
|
|
2652
|
-
} catch {
|
|
2653
|
-
return null;
|
|
2643
|
+
if (!localPath) {
|
|
2644
|
+
const cfg = loadConfig();
|
|
2645
|
+
localPath = findDirectoryForProject(cfg, task.projectId, rootDir) ?? void 0;
|
|
2646
|
+
}
|
|
2647
|
+
if (!localPath) {
|
|
2648
|
+
throw new Error("Local repo path not configured. Run `mr link` in the project directory.");
|
|
2649
|
+
}
|
|
2650
|
+
let testPlan;
|
|
2651
|
+
try {
|
|
2652
|
+
const resources = await api.get(`/api/tasks/${task.id}/resources`);
|
|
2653
|
+
const planResource = resources.find((r) => r.type === "test-plan");
|
|
2654
|
+
if (planResource) {
|
|
2655
|
+
testPlan = JSON.parse(planResource.content);
|
|
2656
|
+
logInfo(prefix, `Using agent-authored test plan (${testPlan.length} steps)`);
|
|
2654
2657
|
}
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2658
|
+
} catch {
|
|
2659
|
+
}
|
|
2660
|
+
const result = await runTest({
|
|
2661
|
+
taskId: task.id,
|
|
2662
|
+
taskLink: task.link,
|
|
2663
|
+
localPath,
|
|
2664
|
+
testPlan,
|
|
2665
|
+
browseRunner: runBrowseCommand2,
|
|
2666
|
+
uploadScreenshot: async (screenshotPath, message) => {
|
|
2667
|
+
try {
|
|
2668
|
+
const { readFileSync: readFileSync12 } = await import("fs");
|
|
2669
|
+
const cfg = loadConfig();
|
|
2670
|
+
const imageBuffer = readFileSync12(screenshotPath);
|
|
2671
|
+
const fileName = screenshotPath.split("/").pop() || "test-screenshot.png";
|
|
2672
|
+
const formData = new FormData();
|
|
2673
|
+
const blob = new Blob([imageBuffer], { type: "image/png" });
|
|
2674
|
+
formData.append("file", blob, fileName);
|
|
2675
|
+
formData.append("prefix", "test-screenshots");
|
|
2676
|
+
const uploadRes = await fetch(`${cfg.apiUrl}/api/upload`, {
|
|
2677
|
+
method: "POST",
|
|
2678
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` },
|
|
2679
|
+
body: formData
|
|
2680
|
+
});
|
|
2681
|
+
if (!uploadRes.ok) return null;
|
|
2682
|
+
const uploadData = await uploadRes.json();
|
|
2683
|
+
await api.post(`/api/tasks/${task.id}/updates`, {
|
|
2684
|
+
message,
|
|
2685
|
+
imageUrl: uploadData.url,
|
|
2686
|
+
source: "system"
|
|
2687
|
+
});
|
|
2688
|
+
return uploadData.url;
|
|
2689
|
+
} catch {
|
|
2690
|
+
return null;
|
|
2691
|
+
}
|
|
2692
|
+
},
|
|
2693
|
+
postUpdate: async (message, imageUrl) => {
|
|
2694
|
+
await postTaskUpdate(task.id, message);
|
|
2695
|
+
},
|
|
2696
|
+
onProgress: (msg) => logInfo(prefix, msg)
|
|
2697
|
+
});
|
|
2698
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "completed", testResult: result.status });
|
|
2699
|
+
logSuccess(prefix, result.summary);
|
|
2700
|
+
} catch (err) {
|
|
2701
|
+
logError(prefix, `Test failed: ${err.message}`);
|
|
2702
|
+
try {
|
|
2703
|
+
await api.patch(`/api/tasks/${task.id}`, { status: "completed", testResult: "failed" });
|
|
2704
|
+
await postTaskUpdate(task.id, `Test failed: ${err.message}`);
|
|
2705
|
+
} catch {
|
|
2706
|
+
}
|
|
2707
|
+
failed.set(key, err.message);
|
|
2708
|
+
} finally {
|
|
2709
|
+
queued.delete(key);
|
|
2669
2710
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2711
|
+
})();
|
|
2712
|
+
}
|
|
2713
|
+
let pendingProjects = [];
|
|
2714
|
+
try {
|
|
2715
|
+
pendingProjects = await api.get("/api/projects?repoCreationStatus=pending");
|
|
2716
|
+
} catch (err) {
|
|
2717
|
+
logError(watchTag(), `Failed to fetch pending projects: ${err.message}`);
|
|
2718
|
+
}
|
|
2719
|
+
for (const project of pendingProjects) {
|
|
2720
|
+
const key = `repo-${project.id}`;
|
|
2721
|
+
if (queued.has(key)) continue;
|
|
2722
|
+
if (finishing.has(key)) continue;
|
|
2723
|
+
if (failed.has(key)) continue;
|
|
2724
|
+
queued.add(key);
|
|
2725
|
+
if (dryRun) {
|
|
2726
|
+
logInfo(
|
|
2727
|
+
watchTag(),
|
|
2728
|
+
`${paint("yellow", "[dry-run]")} would create repo for "${paint("bold", project.name)}" in ${paint("cyan", rootDir)}`
|
|
2729
|
+
);
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
dispatchRepoCreation(project, rootDir);
|
|
2733
|
+
}
|
|
2734
|
+
let generatingIdeas = [];
|
|
2735
|
+
try {
|
|
2736
|
+
generatingIdeas = await api.get("/api/ideas?status=generating");
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
logError(watchTag(), `Failed to fetch generating ideas: ${err.message}`);
|
|
2739
|
+
}
|
|
2740
|
+
const inProgressIdeaKeys = new Set(generatingIdeas.map((i) => `idea-${i.id}`));
|
|
2741
|
+
for (const [key, entry] of active) {
|
|
2742
|
+
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) {
|
|
2743
|
+
logWarn(watchTag(), `Idea ${paint("yellow", key)} no longer generating, terminating\u2026`);
|
|
2744
|
+
entry.process.kill("SIGTERM");
|
|
2745
|
+
active.delete(key);
|
|
2672
2746
|
queued.delete(key);
|
|
2673
2747
|
}
|
|
2674
|
-
})();
|
|
2675
|
-
}
|
|
2676
|
-
let pendingProjects = [];
|
|
2677
|
-
try {
|
|
2678
|
-
pendingProjects = await api.get("/api/projects?repoCreationStatus=pending");
|
|
2679
|
-
} catch (err) {
|
|
2680
|
-
logError(watchTag(), `Failed to fetch pending projects: ${err.message}`);
|
|
2681
|
-
}
|
|
2682
|
-
for (const project of pendingProjects) {
|
|
2683
|
-
const key = `repo-${project.id}`;
|
|
2684
|
-
if (queued.has(key)) continue;
|
|
2685
|
-
if (failed.has(key)) continue;
|
|
2686
|
-
queued.add(key);
|
|
2687
|
-
if (dryRun) {
|
|
2688
|
-
logInfo(
|
|
2689
|
-
watchTag(),
|
|
2690
|
-
`${paint("yellow", "[dry-run]")} would create repo for "${paint("bold", project.name)}" in ${paint("cyan", rootDir)}`
|
|
2691
|
-
);
|
|
2692
|
-
continue;
|
|
2693
2748
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
let generatingIdeas = [];
|
|
2697
|
-
try {
|
|
2698
|
-
generatingIdeas = await api.get("/api/ideas?status=generating");
|
|
2699
|
-
} catch (err) {
|
|
2700
|
-
logError(watchTag(), `Failed to fetch generating ideas: ${err.message}`);
|
|
2701
|
-
}
|
|
2702
|
-
const inProgressIdeaKeys = new Set(generatingIdeas.map((i) => `idea-${i.id}`));
|
|
2703
|
-
for (const [key, entry] of active) {
|
|
2704
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) {
|
|
2705
|
-
logWarn(watchTag(), `Idea ${paint("yellow", key)} no longer generating, terminating\u2026`);
|
|
2706
|
-
entry.process.kill("SIGTERM");
|
|
2707
|
-
active.delete(key);
|
|
2708
|
-
queued.delete(key);
|
|
2749
|
+
for (const key of failed.keys()) {
|
|
2750
|
+
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) failed.delete(key);
|
|
2709
2751
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2752
|
+
for (const key of queued) {
|
|
2753
|
+
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key) && !finishing.has(key)) queued.delete(key);
|
|
2754
|
+
}
|
|
2755
|
+
const activeIdeaCount = [...active.keys()].filter((k) => k.startsWith("idea-")).length;
|
|
2756
|
+
let ideaSlots = Math.max(0, 1 - activeIdeaCount);
|
|
2757
|
+
for (const idea of generatingIdeas) {
|
|
2758
|
+
if (ideaSlots <= 0) break;
|
|
2759
|
+
const key = `idea-${idea.id}`;
|
|
2760
|
+
if (queued.has(key)) continue;
|
|
2761
|
+
if (finishing.has(key)) continue;
|
|
2762
|
+
if (failed.has(key)) continue;
|
|
2763
|
+
const sid = shortId(idea.id);
|
|
2764
|
+
const prefix = ideaTag(sid);
|
|
2765
|
+
let repoDir;
|
|
2766
|
+
if (idea.projectId) {
|
|
2767
|
+
const dir = findDirectoryForProject(config, idea.projectId, rootDir);
|
|
2768
|
+
if (!dir) {
|
|
2769
|
+
logWarn(prefix, `"${idea.title}": no linked directory found \u2014 skipping`);
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
repoDir = dir;
|
|
2773
|
+
} else {
|
|
2774
|
+
repoDir = rootDir;
|
|
2775
|
+
}
|
|
2776
|
+
queued.add(key);
|
|
2777
|
+
ideaSlots--;
|
|
2778
|
+
if (dryRun) {
|
|
2779
|
+
logInfo(
|
|
2780
|
+
watchTag(),
|
|
2781
|
+
`${paint("yellow", "[dry-run]")} would dispatch idea "${paint("bold", idea.title)}" in ${paint("cyan", repoDir)}`
|
|
2782
|
+
);
|
|
2731
2783
|
continue;
|
|
2732
2784
|
}
|
|
2733
|
-
repoDir
|
|
2734
|
-
} else {
|
|
2735
|
-
repoDir = rootDir;
|
|
2785
|
+
dispatchIdeaJob(idea, repoDir);
|
|
2736
2786
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
`${paint("yellow", "[dry-run]")} would dispatch idea "${paint("bold", idea.title)}" in ${paint("cyan", repoDir)}`
|
|
2743
|
-
);
|
|
2744
|
-
continue;
|
|
2787
|
+
let pendingScans = [];
|
|
2788
|
+
try {
|
|
2789
|
+
pendingScans = await api.get("/api/scans?status=pending&limit=5");
|
|
2790
|
+
} catch (err) {
|
|
2791
|
+
logError(watchTag(), `Failed to fetch pending scans: ${err.message}`);
|
|
2745
2792
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
logInfo(
|
|
2763
|
-
watchTag(),
|
|
2764
|
-
`${paint("yellow", "[dry-run]")} would run scan ${paint("yellow", sid)} for project ${paint("cyan", scan.projectId)}`
|
|
2765
|
-
);
|
|
2766
|
-
continue;
|
|
2793
|
+
for (const scan of pendingScans) {
|
|
2794
|
+
const key = `scan-${scan.id}`;
|
|
2795
|
+
if (queued.has(key)) continue;
|
|
2796
|
+
if (finishing.has(key)) continue;
|
|
2797
|
+
if (failed.has(key)) continue;
|
|
2798
|
+
const sid = shortId(scan.id);
|
|
2799
|
+
const prefix = `${paint("magenta", `[scan:${sid}]`)}`;
|
|
2800
|
+
queued.add(key);
|
|
2801
|
+
if (dryRun) {
|
|
2802
|
+
logInfo(
|
|
2803
|
+
watchTag(),
|
|
2804
|
+
`${paint("yellow", "[dry-run]")} would run scan ${paint("yellow", sid)} for project ${paint("cyan", scan.projectId)}`
|
|
2805
|
+
);
|
|
2806
|
+
continue;
|
|
2807
|
+
}
|
|
2808
|
+
dispatchScan(scan, prefix, key);
|
|
2767
2809
|
}
|
|
2768
|
-
|
|
2810
|
+
} finally {
|
|
2811
|
+
pollRunning = false;
|
|
2769
2812
|
}
|
|
2770
2813
|
}
|
|
2771
2814
|
async function shutdown() {
|
|
@@ -2944,9 +2987,11 @@ var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark
|
|
|
2944
2987
|
console.log(`\u2713 Subtask completed: ${subtask.title}`);
|
|
2945
2988
|
});
|
|
2946
2989
|
|
|
2947
|
-
// cli/commands/
|
|
2990
|
+
// cli/commands/up.ts
|
|
2948
2991
|
import { Command as Command13 } from "commander";
|
|
2949
2992
|
import { spawn as spawn5 } from "child_process";
|
|
2993
|
+
import { resolve as resolve2 } from "path";
|
|
2994
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2950
2995
|
var c4 = {
|
|
2951
2996
|
reset: "\x1B[0m",
|
|
2952
2997
|
bold: "\x1B[1m",
|
|
@@ -2964,270 +3009,23 @@ function paint4(color, text) {
|
|
|
2964
3009
|
function timestamp2() {
|
|
2965
3010
|
return paint4("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
2966
3011
|
}
|
|
2967
|
-
function
|
|
2968
|
-
return paint4("
|
|
2969
|
-
}
|
|
2970
|
-
function shortId2(id) {
|
|
2971
|
-
return id.slice(0, 8);
|
|
3012
|
+
function upTag() {
|
|
3013
|
+
return paint4("cyan", "[up]");
|
|
2972
3014
|
}
|
|
2973
3015
|
function log(msg) {
|
|
2974
|
-
console.log(`${timestamp2()} ${
|
|
2975
|
-
}
|
|
2976
|
-
function logOk(msg) {
|
|
2977
|
-
console.log(`${timestamp2()} ${digestTag()} ${paint4("green", "\u2713")} ${msg}`);
|
|
3016
|
+
console.log(`${timestamp2()} ${upTag()} ${msg}`);
|
|
2978
3017
|
}
|
|
2979
3018
|
function logErr(msg) {
|
|
2980
|
-
console.error(`${timestamp2()} ${
|
|
2981
|
-
}
|
|
2982
|
-
function buildPrompt(messages) {
|
|
2983
|
-
const emailsText = messages.map(
|
|
2984
|
-
(msg, i) => `Email ${i + 1}:
|
|
2985
|
-
ID: ${msg.id}
|
|
2986
|
-
Subject: ${msg.subject}
|
|
2987
|
-
From: ${msg.sender} <${msg.senderEmail}>
|
|
2988
|
-
Date: ${msg.date}
|
|
2989
|
-
Snippet: ${msg.snippet}
|
|
2990
|
-
Body preview: ${msg.body.slice(0, 500)}`
|
|
2991
|
-
).join("\n\n---\n\n");
|
|
2992
|
-
return `You are an email triage assistant. Analyze the following unread emails and produce a structured digest.
|
|
2993
|
-
|
|
2994
|
-
For each email, determine:
|
|
2995
|
-
1. Is it relevant/actionable (requires attention, is personal, work-related, or important)?
|
|
2996
|
-
2. Or is it not relevant (newsletters, marketing, automated notifications, promotions)?
|
|
2997
|
-
3. Assign a category that groups it with similar emails (e.g. "Finance & Trading", "Real Estate", "Work", "Personal", "Promotions & Newsletters", "Travel", "Health", etc.).
|
|
2998
|
-
4. Write a 2-3 sentence summary of the email's content and what action (if any) is needed.
|
|
2999
|
-
|
|
3000
|
-
Also write a 2-3 sentence executive summary of the overall inbox state.
|
|
3001
|
-
|
|
3002
|
-
Here are the unread emails:
|
|
3003
|
-
|
|
3004
|
-
${emailsText}
|
|
3005
|
-
|
|
3006
|
-
Respond with a JSON object in this exact format:
|
|
3007
|
-
{
|
|
3008
|
-
"summary": "2-3 sentence overview of the inbox",
|
|
3009
|
-
"items": [
|
|
3010
|
-
{
|
|
3011
|
-
"emailId": "the email ID",
|
|
3012
|
-
"summary": "2-3 sentence summary of this email's content and any action needed",
|
|
3013
|
-
"isRelevant": true or false,
|
|
3014
|
-
"relevanceReason": "brief explanation of why relevant or not",
|
|
3015
|
-
"category": "short category label (2-4 words)"
|
|
3016
|
-
}
|
|
3017
|
-
]
|
|
3018
|
-
}
|
|
3019
|
-
|
|
3020
|
-
Be conservative with "isRelevant: false" \u2014 only mark as not relevant if clearly promotional, newsletter, or automated. Personal emails, work emails, and anything requiring a response should be relevant. Group similar emails under the same category label.`;
|
|
3021
|
-
}
|
|
3022
|
-
function runAgent(prompt2, agent) {
|
|
3023
|
-
return new Promise((resolve7, reject) => {
|
|
3024
|
-
let bin;
|
|
3025
|
-
let args;
|
|
3026
|
-
if (agent === "codex") {
|
|
3027
|
-
bin = "codex";
|
|
3028
|
-
args = ["exec", "--full-auto", prompt2];
|
|
3029
|
-
} else {
|
|
3030
|
-
bin = "claude";
|
|
3031
|
-
args = ["-p", "--dangerously-skip-permissions", prompt2];
|
|
3032
|
-
}
|
|
3033
|
-
const child = spawn5(bin, args, {
|
|
3034
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3035
|
-
});
|
|
3036
|
-
let output = "";
|
|
3037
|
-
let errOutput = "";
|
|
3038
|
-
child.stdout?.on("data", (d) => {
|
|
3039
|
-
output += d.toString();
|
|
3040
|
-
});
|
|
3041
|
-
child.stderr?.on("data", (d) => {
|
|
3042
|
-
errOutput += d.toString();
|
|
3043
|
-
});
|
|
3044
|
-
child.on("exit", (code) => {
|
|
3045
|
-
if (code === 0) resolve7(output.trim());
|
|
3046
|
-
else reject(new Error(`${bin} exited with code ${code}
|
|
3047
|
-
${errOutput.trim()}`));
|
|
3048
|
-
});
|
|
3049
|
-
});
|
|
3050
|
-
}
|
|
3051
|
-
async function processDigest(digest, agent) {
|
|
3052
|
-
const sid = shortId2(digest.id);
|
|
3053
|
-
if (digest.rawMessages.length === 0) {
|
|
3054
|
-
await api.post(`/api/email-digest/${digest.id}/complete`, {
|
|
3055
|
-
summary: "No unread emails found in your inbox.",
|
|
3056
|
-
items: []
|
|
3057
|
-
});
|
|
3058
|
-
logOk(`${paint4("yellow", sid)} completed (no emails)`);
|
|
3059
|
-
return;
|
|
3060
|
-
}
|
|
3061
|
-
log(`${paint4("yellow", sid)} analyzing ${paint4("cyan", String(digest.rawMessages.length))} emails\u2026`);
|
|
3062
|
-
const prompt2 = buildPrompt(digest.rawMessages);
|
|
3063
|
-
const output = await runAgent(prompt2, agent);
|
|
3064
|
-
const jsonMatch = output.match(/\{[\s\S]*\}/);
|
|
3065
|
-
if (!jsonMatch) {
|
|
3066
|
-
throw new Error("Could not extract JSON from Claude output");
|
|
3067
|
-
}
|
|
3068
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
3069
|
-
const msgMap = new Map(digest.rawMessages.map((m) => [m.id, m]));
|
|
3070
|
-
const items = parsed.items.map((item) => {
|
|
3071
|
-
const msg = msgMap.get(item.emailId);
|
|
3072
|
-
if (!msg) return null;
|
|
3073
|
-
return {
|
|
3074
|
-
emailId: msg.id,
|
|
3075
|
-
threadId: msg.threadId,
|
|
3076
|
-
subject: msg.subject,
|
|
3077
|
-
sender: msg.sender,
|
|
3078
|
-
senderEmail: msg.senderEmail,
|
|
3079
|
-
date: msg.date,
|
|
3080
|
-
snippet: msg.snippet,
|
|
3081
|
-
summary: item.summary || msg.snippet,
|
|
3082
|
-
isRelevant: item.isRelevant,
|
|
3083
|
-
relevanceReason: item.relevanceReason,
|
|
3084
|
-
category: item.category || (item.isRelevant ? "General" : "Promotions & Newsletters"),
|
|
3085
|
-
archived: false
|
|
3086
|
-
};
|
|
3087
|
-
}).filter((item) => item !== null);
|
|
3088
|
-
await api.post(`/api/email-digest/${digest.id}/complete`, {
|
|
3089
|
-
summary: parsed.summary,
|
|
3090
|
-
items
|
|
3091
|
-
});
|
|
3092
|
-
const relevantItems = items.filter((i) => i.isRelevant);
|
|
3093
|
-
const noiseItems = items.filter((i) => !i.isRelevant);
|
|
3094
|
-
logOk(
|
|
3095
|
-
`${paint4("yellow", sid)} done \u2014 ${paint4("green", String(relevantItems.length))} relevant, ${paint4("gray", String(noiseItems.length))} not relevant`
|
|
3096
|
-
);
|
|
3097
|
-
console.log("");
|
|
3098
|
-
console.log(` ${paint4("bold", "Summary:")} ${parsed.summary}`);
|
|
3099
|
-
console.log("");
|
|
3100
|
-
if (relevantItems.length > 0) {
|
|
3101
|
-
console.log(` ${paint4("bold", paint4("green", "Important"))}`);
|
|
3102
|
-
for (const item of relevantItems) {
|
|
3103
|
-
console.log(` ${paint4("cyan", "\u2022")} ${item.subject} ${paint4("dim", `\u2014 ${item.sender}`)}`);
|
|
3104
|
-
}
|
|
3105
|
-
console.log("");
|
|
3106
|
-
}
|
|
3107
|
-
if (noiseItems.length > 0) {
|
|
3108
|
-
console.log(` ${paint4("dim", "Not important")}`);
|
|
3109
|
-
for (const item of noiseItems) {
|
|
3110
|
-
console.log(` ${paint4("dim", `\u2022 ${item.subject} \u2014 ${item.sender}`)}`);
|
|
3111
|
-
}
|
|
3112
|
-
console.log("");
|
|
3113
|
-
}
|
|
3114
|
-
}
|
|
3115
|
-
async function generateDigest() {
|
|
3116
|
-
let result;
|
|
3117
|
-
try {
|
|
3118
|
-
result = await api.post("/api/email-digest/generate");
|
|
3119
|
-
} catch (err) {
|
|
3120
|
-
logErr(`Auto-generate failed: ${err.message}`);
|
|
3121
|
-
return;
|
|
3122
|
-
}
|
|
3123
|
-
if (result.needsReauth) {
|
|
3124
|
-
logErr("Auto-generate skipped: Gmail re-authentication required");
|
|
3125
|
-
return;
|
|
3126
|
-
}
|
|
3127
|
-
if (result.status === "completed") {
|
|
3128
|
-
log(`Auto-generated digest ${paint4("yellow", shortId2(result.id))} \u2014 no new emails`);
|
|
3129
|
-
} else {
|
|
3130
|
-
log(`Auto-generated digest ${paint4("yellow", shortId2(result.id))} \u2014 queued for processing`);
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
var digestCommand = new Command13("digest").description("Process pending email digests using an AI coding agent (run alongside mr watch)").option("--interval <seconds>", "Polling interval in seconds", "15").option("--generate-interval <seconds>", "How often to auto-generate new digests", "3600").option("--once", "Process pending digests once and exit", false).option("--agent <agent>", "AI agent to use: claude or codex", "claude").action(async (opts) => {
|
|
3134
|
-
const intervalMs = parseInt(opts.interval, 10) * 1e3;
|
|
3135
|
-
const generateIntervalMs = parseInt(opts.generateInterval, 10) * 1e3;
|
|
3136
|
-
const once = opts.once;
|
|
3137
|
-
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
3138
|
-
const processing = /* @__PURE__ */ new Set();
|
|
3139
|
-
const banner = [
|
|
3140
|
-
``,
|
|
3141
|
-
paint4("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2566\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557`),
|
|
3142
|
-
paint4("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2551 \u2566\u2551\u2563 \u255A\u2550\u2557 \u2551`),
|
|
3143
|
-
paint4("magenta", ` \u2569 \u2569\u2569\u255A\u2550 \u2550\u2569\u255D\u2569\u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u2569`),
|
|
3144
|
-
paint4("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
3145
|
-
paint4("dim", ` email digest processor \xB7 powered by ${agent}`),
|
|
3146
|
-
``
|
|
3147
|
-
].join("\n");
|
|
3148
|
-
console.log(banner);
|
|
3149
|
-
console.log(
|
|
3150
|
-
` interval=${paint4("cyan", opts.interval + "s")} generate-interval=${paint4("cyan", opts.generateInterval + "s")}${once ? ` ${paint4("yellow", "once")}` : ""}
|
|
3151
|
-
`
|
|
3152
|
-
);
|
|
3153
|
-
async function poll() {
|
|
3154
|
-
let pending;
|
|
3155
|
-
try {
|
|
3156
|
-
pending = await api.get("/api/email-digest/pending");
|
|
3157
|
-
} catch (err) {
|
|
3158
|
-
logErr(`Failed to fetch pending digests: ${err.message}`);
|
|
3159
|
-
return;
|
|
3160
|
-
}
|
|
3161
|
-
if (pending.length === 0) return;
|
|
3162
|
-
const digest = pending[pending.length - 1];
|
|
3163
|
-
if (processing.has(digest.id)) return;
|
|
3164
|
-
log(`Processing digest ${paint4("yellow", shortId2(digest.id))} (${paint4("cyan", String(digest.rawMessages.length))} emails)`);
|
|
3165
|
-
processing.add(digest.id);
|
|
3166
|
-
processDigest(digest, agent).catch((err) => logErr(`${shortId2(digest.id)} failed: ${err.message}`)).finally(() => processing.delete(digest.id));
|
|
3167
|
-
}
|
|
3168
|
-
process.on("SIGINT", () => {
|
|
3169
|
-
console.log(`
|
|
3170
|
-
${timestamp2()} ${digestTag()} Shutting down\u2026`);
|
|
3171
|
-
process.exit(0);
|
|
3172
|
-
});
|
|
3173
|
-
await generateDigest();
|
|
3174
|
-
await poll();
|
|
3175
|
-
if (once) {
|
|
3176
|
-
const wait = () => new Promise((resolve7) => {
|
|
3177
|
-
if (processing.size === 0) return resolve7();
|
|
3178
|
-
const check = setInterval(() => {
|
|
3179
|
-
if (processing.size === 0) {
|
|
3180
|
-
clearInterval(check);
|
|
3181
|
-
resolve7();
|
|
3182
|
-
}
|
|
3183
|
-
}, 500);
|
|
3184
|
-
});
|
|
3185
|
-
await wait();
|
|
3186
|
-
} else {
|
|
3187
|
-
setInterval(poll, intervalMs);
|
|
3188
|
-
setInterval(generateDigest, generateIntervalMs);
|
|
3189
|
-
}
|
|
3190
|
-
});
|
|
3191
|
-
|
|
3192
|
-
// cli/commands/up.ts
|
|
3193
|
-
import { Command as Command14 } from "commander";
|
|
3194
|
-
import { spawn as spawn6 } from "child_process";
|
|
3195
|
-
import { resolve as resolve2 } from "path";
|
|
3196
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3197
|
-
var c5 = {
|
|
3198
|
-
reset: "\x1B[0m",
|
|
3199
|
-
bold: "\x1B[1m",
|
|
3200
|
-
dim: "\x1B[2m",
|
|
3201
|
-
cyan: "\x1B[36m",
|
|
3202
|
-
green: "\x1B[32m",
|
|
3203
|
-
yellow: "\x1B[33m",
|
|
3204
|
-
red: "\x1B[31m",
|
|
3205
|
-
magenta: "\x1B[35m",
|
|
3206
|
-
gray: "\x1B[90m"
|
|
3207
|
-
};
|
|
3208
|
-
function paint5(color, text) {
|
|
3209
|
-
return `${c5[color]}${text}${c5.reset}`;
|
|
3210
|
-
}
|
|
3211
|
-
function timestamp3() {
|
|
3212
|
-
return paint5("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
3213
|
-
}
|
|
3214
|
-
function upTag() {
|
|
3215
|
-
return paint5("cyan", "[up]");
|
|
3216
|
-
}
|
|
3217
|
-
function log2(msg) {
|
|
3218
|
-
console.log(`${timestamp3()} ${upTag()} ${msg}`);
|
|
3219
|
-
}
|
|
3220
|
-
function logErr2(msg) {
|
|
3221
|
-
console.error(`${timestamp3()} ${upTag()} ${paint5("red", "\u2717")} ${msg}`);
|
|
3019
|
+
console.error(`${timestamp2()} ${upTag()} ${paint4("red", "\u2717")} ${msg}`);
|
|
3222
3020
|
}
|
|
3223
3021
|
function spawnMr(args) {
|
|
3224
3022
|
const entry = process.argv[1];
|
|
3225
|
-
const child =
|
|
3023
|
+
const child = spawn5(process.execPath, [entry, ...args], {
|
|
3226
3024
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3227
3025
|
});
|
|
3228
3026
|
return child;
|
|
3229
3027
|
}
|
|
3230
|
-
var upCommand = new
|
|
3028
|
+
var upCommand = new Command13("up").description("Run mr watch and mr digest together with a single command").option("--interval <seconds>", "Polling interval for both watch and digest", "15").option("--watch-interval <seconds>", "Override polling interval for mr watch").option("--digest-interval <seconds>", "Override polling interval for mr digest").option("--dry-run", "Pass --dry-run to mr watch", false).option("--plan-approval", "Pass --plan-approval to mr watch", false).option("--root <dir>", "Pass --root to mr watch (default: cwd)").option("--agent <agent>", "AI agent to use: claude or codex", "claude").action((opts) => {
|
|
3231
3029
|
const watchInterval = opts.watchInterval ?? opts.interval;
|
|
3232
3030
|
const digestInterval = opts.digestInterval ?? opts.interval;
|
|
3233
3031
|
const agent = opts.agent === "codex" ? "codex" : "claude";
|
|
@@ -3238,21 +3036,21 @@ var upCommand = new Command14("up").description("Run mr watch and mr digest toge
|
|
|
3238
3036
|
const digestArgs = ["digest", "--interval", digestInterval, "--agent", agent];
|
|
3239
3037
|
const banner = [
|
|
3240
3038
|
``,
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3039
|
+
paint4("cyan", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2566 \u2566\u2554\u2550\u2557`),
|
|
3040
|
+
paint4("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551 \u2551\u2560\u2550\u255D`),
|
|
3041
|
+
paint4("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u2569 `),
|
|
3042
|
+
paint4("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
3043
|
+
paint4("dim", ` mr watch + mr digest \xB7 powered by ${agent}`),
|
|
3246
3044
|
``
|
|
3247
3045
|
].join("\n");
|
|
3248
3046
|
console.log(banner);
|
|
3249
3047
|
const flags = [
|
|
3250
|
-
`watch-interval=${
|
|
3251
|
-
`digest-interval=${
|
|
3252
|
-
`agent=${
|
|
3253
|
-
...opts.dryRun ? [
|
|
3254
|
-
...opts.planApproval ? [
|
|
3255
|
-
...opts.root ? [`root=${
|
|
3048
|
+
`watch-interval=${paint4("cyan", watchInterval + "s")}`,
|
|
3049
|
+
`digest-interval=${paint4("cyan", digestInterval + "s")}`,
|
|
3050
|
+
`agent=${paint4("cyan", agent)}`,
|
|
3051
|
+
...opts.dryRun ? [paint4("yellow", "dry-run")] : [],
|
|
3052
|
+
...opts.planApproval ? [paint4("yellow", "plan-approval")] : [],
|
|
3053
|
+
...opts.root ? [`root=${paint4("cyan", resolve2(opts.root))}`] : []
|
|
3256
3054
|
].join(" ");
|
|
3257
3055
|
console.log(` ${flags}
|
|
3258
3056
|
`);
|
|
@@ -3263,12 +3061,12 @@ var upCommand = new Command14("up").description("Run mr watch and mr digest toge
|
|
|
3263
3061
|
proc.stderr?.on("data", (d) => process.stderr.write(d));
|
|
3264
3062
|
proc.on("exit", (code, signal) => {
|
|
3265
3063
|
if (signal !== "SIGTERM" && signal !== "SIGINT") {
|
|
3266
|
-
|
|
3064
|
+
logErr(`mr ${label} exited unexpectedly (code=${code ?? "?"}, signal=${signal ?? "none"})`);
|
|
3267
3065
|
}
|
|
3268
3066
|
});
|
|
3269
3067
|
}
|
|
3270
3068
|
function shutdown() {
|
|
3271
|
-
|
|
3069
|
+
log("Shutting down\u2026");
|
|
3272
3070
|
watchProc.kill("SIGTERM");
|
|
3273
3071
|
digestProc.kill("SIGTERM");
|
|
3274
3072
|
setTimeout(() => process.exit(0), 500);
|
|
@@ -3278,8 +3076,8 @@ var upCommand = new Command14("up").description("Run mr watch and mr digest toge
|
|
|
3278
3076
|
});
|
|
3279
3077
|
|
|
3280
3078
|
// cli/commands/prototype.ts
|
|
3281
|
-
import { Command as
|
|
3282
|
-
var
|
|
3079
|
+
import { Command as Command14 } from "commander";
|
|
3080
|
+
var c5 = {
|
|
3283
3081
|
reset: "\x1B[0m",
|
|
3284
3082
|
bold: "\x1B[1m",
|
|
3285
3083
|
dim: "\x1B[2m",
|
|
@@ -3290,25 +3088,25 @@ var c6 = {
|
|
|
3290
3088
|
blue: "\x1B[34m",
|
|
3291
3089
|
gray: "\x1B[90m"
|
|
3292
3090
|
};
|
|
3293
|
-
function
|
|
3294
|
-
return `${
|
|
3091
|
+
function paint5(color, text) {
|
|
3092
|
+
return `${c5[color]}${text}${c5.reset}`;
|
|
3295
3093
|
}
|
|
3296
3094
|
function statusBadge(status) {
|
|
3297
3095
|
switch (status) {
|
|
3298
3096
|
case "pending":
|
|
3299
|
-
return
|
|
3097
|
+
return paint5("gray", "\u25CB pending");
|
|
3300
3098
|
case "in_progress":
|
|
3301
|
-
return
|
|
3099
|
+
return paint5("cyan", "\u27F3 in_progress");
|
|
3302
3100
|
case "completed":
|
|
3303
|
-
return
|
|
3101
|
+
return paint5("green", "\u2713 completed");
|
|
3304
3102
|
case "failed":
|
|
3305
|
-
return
|
|
3103
|
+
return paint5("red", "\u2717 failed");
|
|
3306
3104
|
default:
|
|
3307
|
-
return
|
|
3105
|
+
return paint5("gray", status);
|
|
3308
3106
|
}
|
|
3309
3107
|
}
|
|
3310
|
-
var prototypeCommand = new
|
|
3311
|
-
new
|
|
3108
|
+
var prototypeCommand = new Command14("prototype").description("Manage prototypes").addCommand(
|
|
3109
|
+
new Command14("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
|
|
3312
3110
|
const params = new URLSearchParams();
|
|
3313
3111
|
if (!opts.all) {
|
|
3314
3112
|
const projectId = getLinkedProjectId();
|
|
@@ -3320,21 +3118,21 @@ var prototypeCommand = new Command15("prototype").description("Manage prototypes
|
|
|
3320
3118
|
}
|
|
3321
3119
|
const prototypes = await api.get(`/api/prototypes?${params.toString()}`);
|
|
3322
3120
|
if (prototypes.length === 0) {
|
|
3323
|
-
console.log(
|
|
3121
|
+
console.log(paint5("gray", "No prototypes found."));
|
|
3324
3122
|
return;
|
|
3325
3123
|
}
|
|
3326
3124
|
console.log();
|
|
3327
3125
|
for (const p of prototypes) {
|
|
3328
3126
|
const date = new Date(p.createdAt).toLocaleDateString();
|
|
3329
3127
|
console.log(
|
|
3330
|
-
` ${
|
|
3128
|
+
` ${paint5("bold", p.title)} ${statusBadge(p.status)} ${paint5("gray", p.id.slice(0, 8))} ${paint5("dim", date)}`
|
|
3331
3129
|
);
|
|
3332
|
-
console.log(` ${
|
|
3130
|
+
console.log(` ${paint5("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
|
|
3333
3131
|
console.log();
|
|
3334
3132
|
}
|
|
3335
3133
|
})
|
|
3336
3134
|
).addCommand(
|
|
3337
|
-
new
|
|
3135
|
+
new Command14("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project)").option("--variants <count>", "Number of variants to generate (1-50)", "5").action(async (title, opts) => {
|
|
3338
3136
|
const projectId = opts.project ?? getLinkedProjectId();
|
|
3339
3137
|
if (!projectId) {
|
|
3340
3138
|
console.error('No project linked. Run "mr link <project-id>" or use --project.');
|
|
@@ -3348,37 +3146,37 @@ var prototypeCommand = new Command15("prototype").description("Manage prototypes
|
|
|
3348
3146
|
projectId
|
|
3349
3147
|
});
|
|
3350
3148
|
console.log();
|
|
3351
|
-
console.log(` ${
|
|
3352
|
-
console.log(` ${
|
|
3353
|
-
console.log(` ${
|
|
3149
|
+
console.log(` ${paint5("green", "\u2713")} Created prototype: ${paint5("bold", prototype.title)}`);
|
|
3150
|
+
console.log(` ${paint5("gray", "ID:")} ${prototype.id}`);
|
|
3151
|
+
console.log(` ${paint5("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
3354
3152
|
console.log();
|
|
3355
3153
|
})
|
|
3356
3154
|
).addCommand(
|
|
3357
|
-
new
|
|
3155
|
+
new Command14("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
|
|
3358
3156
|
const prototype = await api.patch(`/api/prototypes/${id}`, {
|
|
3359
3157
|
status: "in_progress"
|
|
3360
3158
|
});
|
|
3361
3159
|
console.log();
|
|
3362
|
-
console.log(` ${
|
|
3363
|
-
console.log(` ${
|
|
3160
|
+
console.log(` ${paint5("cyan", "\u27F3")} Started: ${paint5("bold", prototype.title)}`);
|
|
3161
|
+
console.log(` ${paint5("gray", "The watch agent will pick this up shortly.")}`);
|
|
3364
3162
|
console.log();
|
|
3365
3163
|
})
|
|
3366
3164
|
).addCommand(
|
|
3367
|
-
new
|
|
3165
|
+
new Command14("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
|
|
3368
3166
|
const prototype = await api.patch(`/api/prototypes/${id}`, {
|
|
3369
3167
|
status: "in_progress",
|
|
3370
3168
|
files: null
|
|
3371
3169
|
});
|
|
3372
3170
|
console.log();
|
|
3373
|
-
console.log(` ${
|
|
3171
|
+
console.log(` ${paint5("cyan", "\u27F3")} Retrying: ${paint5("bold", prototype.title)}`);
|
|
3374
3172
|
console.log();
|
|
3375
3173
|
})
|
|
3376
3174
|
);
|
|
3377
3175
|
|
|
3378
3176
|
// cli/commands/setup.ts
|
|
3379
|
-
import { Command as
|
|
3177
|
+
import { Command as Command15 } from "commander";
|
|
3380
3178
|
import { exec as exec2 } from "child_process";
|
|
3381
|
-
var
|
|
3179
|
+
var c6 = {
|
|
3382
3180
|
reset: "\x1B[0m",
|
|
3383
3181
|
bold: "\x1B[1m",
|
|
3384
3182
|
dim: "\x1B[2m",
|
|
@@ -3389,8 +3187,8 @@ var c7 = {
|
|
|
3389
3187
|
magenta: "\x1B[35m",
|
|
3390
3188
|
gray: "\x1B[90m"
|
|
3391
3189
|
};
|
|
3392
|
-
function
|
|
3393
|
-
return `${
|
|
3190
|
+
function paint6(color, text) {
|
|
3191
|
+
return `${c6[color]}${text}${c6.reset}`;
|
|
3394
3192
|
}
|
|
3395
3193
|
function commandExists(cmd) {
|
|
3396
3194
|
return new Promise((resolve7) => {
|
|
@@ -3634,60 +3432,60 @@ async function checkApiConnectivity() {
|
|
|
3634
3432
|
}
|
|
3635
3433
|
}
|
|
3636
3434
|
function printResults(checks) {
|
|
3637
|
-
const maxNameLen = Math.max(...checks.map((
|
|
3435
|
+
const maxNameLen = Math.max(...checks.map((c11) => c11.name.length));
|
|
3638
3436
|
let allOk = true;
|
|
3639
3437
|
for (const check of checks) {
|
|
3640
3438
|
const isOptional = check.optional ?? false;
|
|
3641
|
-
const icon = check.ok ?
|
|
3439
|
+
const icon = check.ok ? paint6("green", "\u2713") : isOptional ? paint6("yellow", "\u25CB") : paint6("red", "\u2717");
|
|
3642
3440
|
const name = check.name.padEnd(maxNameLen);
|
|
3643
|
-
const msg = check.ok ?
|
|
3441
|
+
const msg = check.ok ? paint6("green", check.message) : isOptional ? paint6("yellow", check.message) : paint6("red", check.message);
|
|
3644
3442
|
console.log(` ${icon} ${name} ${msg}`);
|
|
3645
3443
|
if (!check.ok && !isOptional) allOk = false;
|
|
3646
3444
|
}
|
|
3647
3445
|
return allOk;
|
|
3648
3446
|
}
|
|
3649
3447
|
async function autoFix(checks, agent) {
|
|
3650
|
-
const { spawn:
|
|
3651
|
-
const ghInstalled = checks.find((
|
|
3652
|
-
const ghAuthed = checks.find((
|
|
3653
|
-
const mrAuthed = checks.find((
|
|
3654
|
-
const claudeCheck = checks.find((
|
|
3448
|
+
const { spawn: spawn9 } = await import("child_process");
|
|
3449
|
+
const ghInstalled = checks.find((c11) => c11.name === "GitHub CLI (gh)").ok;
|
|
3450
|
+
const ghAuthed = checks.find((c11) => c11.name === "GitHub CLI auth").ok;
|
|
3451
|
+
const mrAuthed = checks.find((c11) => c11.name === "Mr. Manager CLI auth").ok;
|
|
3452
|
+
const claudeCheck = checks.find((c11) => c11.name === "Claude Code (claude)");
|
|
3655
3453
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
3656
|
-
console.log(
|
|
3657
|
-
console.log(
|
|
3454
|
+
console.log(paint6("cyan", " Installing Claude Code..."));
|
|
3455
|
+
console.log(paint6("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
3658
3456
|
await new Promise((resolve7) => {
|
|
3659
|
-
const child =
|
|
3457
|
+
const child = spawn9("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
|
|
3660
3458
|
child.on("exit", () => resolve7());
|
|
3661
3459
|
});
|
|
3662
3460
|
console.log("");
|
|
3663
3461
|
}
|
|
3664
3462
|
if (ghInstalled && !ghAuthed) {
|
|
3665
|
-
console.log(
|
|
3463
|
+
console.log(paint6("cyan", " Running gh auth login..."));
|
|
3666
3464
|
await new Promise((resolve7) => {
|
|
3667
|
-
const child =
|
|
3465
|
+
const child = spawn9("gh", ["auth", "login"], { stdio: "inherit" });
|
|
3668
3466
|
child.on("exit", () => resolve7());
|
|
3669
3467
|
});
|
|
3670
3468
|
console.log("");
|
|
3671
3469
|
}
|
|
3672
3470
|
if (!mrAuthed) {
|
|
3673
|
-
console.log(
|
|
3471
|
+
console.log(paint6("cyan", " Running mr login..."));
|
|
3674
3472
|
const entry = process.argv[1];
|
|
3675
3473
|
await new Promise((resolve7) => {
|
|
3676
|
-
const child =
|
|
3474
|
+
const child = spawn9(process.execPath, [entry, "login"], { stdio: "inherit" });
|
|
3677
3475
|
child.on("exit", () => resolve7());
|
|
3678
3476
|
});
|
|
3679
3477
|
console.log("");
|
|
3680
3478
|
}
|
|
3681
3479
|
}
|
|
3682
|
-
var setupCommand = new
|
|
3480
|
+
var setupCommand = new Command15("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
|
|
3683
3481
|
const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
|
|
3684
3482
|
const banner = [
|
|
3685
3483
|
``,
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3484
|
+
paint6("cyan", ` \u2554\u2566\u2557\u2551\u2550\u2557 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557\u2551 \u2551\u2554\u2550\u2557`),
|
|
3485
|
+
paint6("magenta", ` \u2551\u2551\u2551\u2560\u2566\u2563 \u255A\u2550\u2557\u2551\u2563 \u2551 \u2551 \u2551\u2560\u2550\u2563`),
|
|
3486
|
+
paint6("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u255A\u2550\u255D \u2569 \u255A\u2550\u255D\u2569 `),
|
|
3487
|
+
paint6("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
3488
|
+
paint6("dim", ` environment check for mr watch (agent: ${agent})`),
|
|
3691
3489
|
``
|
|
3692
3490
|
].join("\n");
|
|
3693
3491
|
console.log(banner);
|
|
@@ -3707,18 +3505,18 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
|
|
|
3707
3505
|
const allOk = printResults(checks);
|
|
3708
3506
|
console.log("");
|
|
3709
3507
|
if (allOk) {
|
|
3710
|
-
console.log(
|
|
3508
|
+
console.log(paint6("green", " All checks passed! You're ready to run mr watch."));
|
|
3711
3509
|
if (agent === "claude") {
|
|
3712
|
-
console.log(
|
|
3510
|
+
console.log(paint6("dim", " Tip: check other agents with --agent codex or --agent gemini"));
|
|
3713
3511
|
}
|
|
3714
3512
|
console.log("");
|
|
3715
3513
|
return;
|
|
3716
3514
|
}
|
|
3717
|
-
const fixes = checks.filter((
|
|
3515
|
+
const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
|
|
3718
3516
|
if (fixes.length > 0) {
|
|
3719
|
-
console.log(
|
|
3517
|
+
console.log(paint6("yellow", " To fix:"));
|
|
3720
3518
|
for (const fix of fixes) {
|
|
3721
|
-
console.log(` ${
|
|
3519
|
+
console.log(` ${paint6("dim", "\u2192")} ${paint6("bold", fix.name)}: ${fix.fix}`);
|
|
3722
3520
|
}
|
|
3723
3521
|
console.log("");
|
|
3724
3522
|
}
|
|
@@ -3729,8 +3527,8 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
|
|
|
3729
3527
|
});
|
|
3730
3528
|
|
|
3731
3529
|
// cli/commands/update.ts
|
|
3732
|
-
import { Command as
|
|
3733
|
-
var updateCommand = new
|
|
3530
|
+
import { Command as Command16 } from "commander";
|
|
3531
|
+
var updateCommand = new Command16("update").description("Post a status update to a task (used by agents to report progress)").argument("<task-id>", "Task ID").argument("<message>", "Status update message").option("--source <source>", "Update source: agent, system, or user", "agent").action(async (taskId, message, opts) => {
|
|
3734
3532
|
await api.post(`/api/tasks/${taskId}/updates`, {
|
|
3735
3533
|
message,
|
|
3736
3534
|
source: opts.source
|
|
@@ -3739,11 +3537,11 @@ var updateCommand = new Command17("update").description("Post a status update to
|
|
|
3739
3537
|
});
|
|
3740
3538
|
|
|
3741
3539
|
// cli/commands/screenshot.ts
|
|
3742
|
-
import { Command as
|
|
3540
|
+
import { Command as Command17 } from "commander";
|
|
3743
3541
|
import { readFileSync as readFileSync6, existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
3744
3542
|
import { join as join8 } from "path";
|
|
3745
3543
|
import { tmpdir } from "os";
|
|
3746
|
-
var screenshotCommand = new
|
|
3544
|
+
var screenshotCommand = new Command17("screenshot").description(
|
|
3747
3545
|
"Take or attach a screenshot to a task update (agents use this to show their work)"
|
|
3748
3546
|
).argument("<task-id>", "Task ID").argument("[file]", "Path to an image file (if omitted, uses headless browser to screenshot the app)").option("-m, --message <message>", "Optional message to include with the screenshot").option("-u, --url <url>", "Custom URL to screenshot (defaults to the task's project page)").action(async (taskId, file, opts) => {
|
|
3749
3547
|
let filePath = file;
|
|
@@ -3834,10 +3632,10 @@ var screenshotCommand = new Command18("screenshot").description(
|
|
|
3834
3632
|
});
|
|
3835
3633
|
|
|
3836
3634
|
// cli/commands/resume.ts
|
|
3837
|
-
import { Command as
|
|
3838
|
-
import { spawn as
|
|
3635
|
+
import { Command as Command18 } from "commander";
|
|
3636
|
+
import { spawn as spawn6 } from "child_process";
|
|
3839
3637
|
import { resolve as resolve3 } from "path";
|
|
3840
|
-
var
|
|
3638
|
+
var c7 = {
|
|
3841
3639
|
reset: "\x1B[0m",
|
|
3842
3640
|
bold: "\x1B[1m",
|
|
3843
3641
|
dim: "\x1B[2m",
|
|
@@ -3848,15 +3646,15 @@ var c8 = {
|
|
|
3848
3646
|
magenta: "\x1B[35m",
|
|
3849
3647
|
gray: "\x1B[90m"
|
|
3850
3648
|
};
|
|
3851
|
-
function
|
|
3852
|
-
return `${
|
|
3649
|
+
function paint7(color, text) {
|
|
3650
|
+
return `${c7[color]}${text}${c7.reset}`;
|
|
3853
3651
|
}
|
|
3854
|
-
var resumeCommand = new
|
|
3652
|
+
var resumeCommand = new Command18("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
|
|
3855
3653
|
const task = await api.get(`/api/tasks/${taskId}`);
|
|
3856
3654
|
if (!task.claudeSessionId) {
|
|
3857
3655
|
console.error(
|
|
3858
3656
|
`
|
|
3859
|
-
${
|
|
3657
|
+
${paint7("red", "\u2717")} No Claude session found for task ${paint7("dim", taskId.slice(0, 8))}`
|
|
3860
3658
|
);
|
|
3861
3659
|
console.error(
|
|
3862
3660
|
` The task may not have been dispatched yet.
|
|
@@ -3867,16 +3665,16 @@ var resumeCommand = new Command19("resume").description("Resume an interactive C
|
|
|
3867
3665
|
if (task.status === "completed" || task.status === "todo") {
|
|
3868
3666
|
console.error(
|
|
3869
3667
|
`
|
|
3870
|
-
${
|
|
3668
|
+
${paint7("yellow", "\u26A0")} Task ${paint7("dim", taskId.slice(0, 8))} has already completed.`
|
|
3871
3669
|
);
|
|
3872
3670
|
console.error(
|
|
3873
|
-
` Session ID: ${
|
|
3671
|
+
` Session ID: ${paint7("cyan", task.claudeSessionId)}`
|
|
3874
3672
|
);
|
|
3875
3673
|
console.error(
|
|
3876
3674
|
` View the session log in the Mr. Manager web UI, or run:`
|
|
3877
3675
|
);
|
|
3878
3676
|
console.error(
|
|
3879
|
-
` ${
|
|
3677
|
+
` ${paint7("dim", `claude --resume ${task.claudeSessionId}`)}
|
|
3880
3678
|
`
|
|
3881
3679
|
);
|
|
3882
3680
|
process.exit(0);
|
|
@@ -3895,16 +3693,16 @@ var resumeCommand = new Command19("resume").description("Resume an interactive C
|
|
|
3895
3693
|
const sid = taskId.slice(0, 8);
|
|
3896
3694
|
console.log([
|
|
3897
3695
|
``,
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
` ${
|
|
3696
|
+
paint7("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint7("bold", "resume"),
|
|
3697
|
+
paint7("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + paint7("dim", "\u2500".repeat(44)),
|
|
3698
|
+
paint7("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + task.title,
|
|
3699
|
+
` ${paint7("gray", sid)}`,
|
|
3902
3700
|
``,
|
|
3903
|
-
` ${
|
|
3904
|
-
` ${
|
|
3701
|
+
` ${paint7("dim", "session")} ${paint7("cyan", task.claudeSessionId)}`,
|
|
3702
|
+
` ${paint7("dim", "cwd")} ${paint7("cyan", cwd)}`,
|
|
3905
3703
|
``
|
|
3906
3704
|
].join("\n"));
|
|
3907
|
-
const child =
|
|
3705
|
+
const child = spawn6("claude", ["--resume", task.claudeSessionId], {
|
|
3908
3706
|
cwd,
|
|
3909
3707
|
stdio: "inherit"
|
|
3910
3708
|
});
|
|
@@ -3913,15 +3711,15 @@ var resumeCommand = new Command19("resume").description("Resume an interactive C
|
|
|
3913
3711
|
});
|
|
3914
3712
|
child.on("error", (err) => {
|
|
3915
3713
|
console.error(`
|
|
3916
|
-
${
|
|
3714
|
+
${paint7("red", "\u2717")} Failed to launch Claude: ${err.message}
|
|
3917
3715
|
`);
|
|
3918
3716
|
process.exit(1);
|
|
3919
3717
|
});
|
|
3920
3718
|
});
|
|
3921
3719
|
|
|
3922
3720
|
// cli/commands/browse.ts
|
|
3923
|
-
import { Command as
|
|
3924
|
-
import { execSync as execSync5, spawn as
|
|
3721
|
+
import { Command as Command19 } from "commander";
|
|
3722
|
+
import { execSync as execSync5, spawn as spawn7 } from "child_process";
|
|
3925
3723
|
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
3926
3724
|
import { join as join9 } from "path";
|
|
3927
3725
|
var BROWSE_DIR2 = join9(import.meta.dirname, "..", "..", "browse");
|
|
@@ -3962,7 +3760,7 @@ async function ensureDevServer() {
|
|
|
3962
3760
|
}
|
|
3963
3761
|
const port = await findAvailablePort2(3e3);
|
|
3964
3762
|
console.log(`[browse] Starting dev server on port ${port}...`);
|
|
3965
|
-
const devProc =
|
|
3763
|
+
const devProc = spawn7("npm", ["run", "dev", "--", "--port", String(port)], {
|
|
3966
3764
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3967
3765
|
detached: true,
|
|
3968
3766
|
cwd: join9(import.meta.dirname, "..", ".."),
|
|
@@ -3984,7 +3782,7 @@ async function ensureDevServer() {
|
|
|
3984
3782
|
}
|
|
3985
3783
|
throw new Error("Dev server failed to start within 30s");
|
|
3986
3784
|
}
|
|
3987
|
-
var browseCommand = new
|
|
3785
|
+
var browseCommand = new Command19("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
|
|
3988
3786
|
"--task-id <id>",
|
|
3989
3787
|
"Attach screenshot to a task update (only for screenshot command)"
|
|
3990
3788
|
).option("--dev", "Auto-start local dev server before browsing").allowUnknownOption(true).action(
|
|
@@ -4067,10 +3865,10 @@ var browseCommand = new Command20("browse").description("Control a headless brow
|
|
|
4067
3865
|
);
|
|
4068
3866
|
|
|
4069
3867
|
// cli/commands/set-path.ts
|
|
4070
|
-
import { Command as
|
|
3868
|
+
import { Command as Command20 } from "commander";
|
|
4071
3869
|
import { resolve as resolve4 } from "path";
|
|
4072
3870
|
import { existsSync as existsSync9 } from "fs";
|
|
4073
|
-
var setPathCommand = new
|
|
3871
|
+
var setPathCommand = new Command20("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
|
|
4074
3872
|
const absolutePath = resolve4(pathArg);
|
|
4075
3873
|
if (!existsSync9(absolutePath)) {
|
|
4076
3874
|
console.error(`Error: Path does not exist: ${absolutePath}`);
|
|
@@ -4088,9 +3886,9 @@ var setPathCommand = new Command21("set-path").description("Set or update the lo
|
|
|
4088
3886
|
});
|
|
4089
3887
|
|
|
4090
3888
|
// cli/commands/test.ts
|
|
4091
|
-
import { Command as
|
|
3889
|
+
import { Command as Command21 } from "commander";
|
|
4092
3890
|
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
4093
|
-
var testCommand = new
|
|
3891
|
+
var testCommand = new Command21("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").action(async (taskId, opts) => {
|
|
4094
3892
|
const config = loadConfig();
|
|
4095
3893
|
console.log("[test] Fetching task...");
|
|
4096
3894
|
let task;
|
|
@@ -4210,11 +4008,11 @@ var testCommand = new Command22("test").description("Run automated browser test
|
|
|
4210
4008
|
});
|
|
4211
4009
|
|
|
4212
4010
|
// cli/commands/features.ts
|
|
4213
|
-
import { Command as
|
|
4011
|
+
import { Command as Command22 } from "commander";
|
|
4214
4012
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "fs";
|
|
4215
4013
|
import { resolve as resolve5, sep as sep2 } from "path";
|
|
4216
4014
|
var FEATURES_FILE3 = ".mr-features.md";
|
|
4217
|
-
var
|
|
4015
|
+
var c8 = {
|
|
4218
4016
|
reset: "\x1B[0m",
|
|
4219
4017
|
bold: "\x1B[1m",
|
|
4220
4018
|
dim: "\x1B[2m",
|
|
@@ -4224,8 +4022,8 @@ var c9 = {
|
|
|
4224
4022
|
magenta: "\x1B[35m",
|
|
4225
4023
|
gray: "\x1B[90m"
|
|
4226
4024
|
};
|
|
4227
|
-
function
|
|
4228
|
-
return `${
|
|
4025
|
+
function paint8(color, text) {
|
|
4026
|
+
return `${c8[color]}${text}${c8.reset}`;
|
|
4229
4027
|
}
|
|
4230
4028
|
function resolveProjectRoot2() {
|
|
4231
4029
|
const cwd = process.cwd();
|
|
@@ -4244,7 +4042,7 @@ function readFeatures2() {
|
|
|
4244
4042
|
if (!existsSync11(path)) return null;
|
|
4245
4043
|
return readFileSync9(path, "utf-8");
|
|
4246
4044
|
}
|
|
4247
|
-
var featuresCommand = new
|
|
4045
|
+
var featuresCommand = new Command22("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
|
|
4248
4046
|
if (opts.path) {
|
|
4249
4047
|
console.log(getFeaturesPath());
|
|
4250
4048
|
return;
|
|
@@ -4253,30 +4051,30 @@ var featuresCommand = new Command23("features").description("View or update the
|
|
|
4253
4051
|
const content2 = readFileSync9(resolve5(opts.file), "utf-8");
|
|
4254
4052
|
const featuresPath = getFeaturesPath();
|
|
4255
4053
|
writeFileSync5(featuresPath, content2);
|
|
4256
|
-
console.log(`${
|
|
4054
|
+
console.log(`${paint8("green", "\u2713")} Updated ${paint8("cyan", featuresPath)} from ${paint8("cyan", opts.file)}`);
|
|
4257
4055
|
return;
|
|
4258
4056
|
}
|
|
4259
4057
|
if (opts.update) {
|
|
4260
4058
|
const featuresPath = getFeaturesPath();
|
|
4261
4059
|
writeFileSync5(featuresPath, opts.update);
|
|
4262
|
-
console.log(`${
|
|
4060
|
+
console.log(`${paint8("green", "\u2713")} Updated ${paint8("cyan", featuresPath)}`);
|
|
4263
4061
|
return;
|
|
4264
4062
|
}
|
|
4265
4063
|
const content = readFeatures2();
|
|
4266
4064
|
if (!content) {
|
|
4267
|
-
console.log(
|
|
4268
|
-
console.log(
|
|
4065
|
+
console.log(paint8("dim", `No features document found. One will be created when an agent completes a task.`));
|
|
4066
|
+
console.log(paint8("dim", `Path: ${getFeaturesPath()}`));
|
|
4269
4067
|
return;
|
|
4270
4068
|
}
|
|
4271
4069
|
console.log(content);
|
|
4272
4070
|
});
|
|
4273
4071
|
|
|
4274
4072
|
// cli/commands/no-mr.ts
|
|
4275
|
-
import { Command as
|
|
4073
|
+
import { Command as Command23 } from "commander";
|
|
4276
4074
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
4277
4075
|
import { resolve as resolve6 } from "path";
|
|
4278
4076
|
var NO_MR_FILE = ".mr-no-mr";
|
|
4279
|
-
var noMrCommand = new
|
|
4077
|
+
var noMrCommand = new Command23("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
|
|
4280
4078
|
const filePath = resolve6(process.cwd(), NO_MR_FILE);
|
|
4281
4079
|
writeFileSync6(filePath, description, "utf-8");
|
|
4282
4080
|
await api.post(`/api/tasks/${taskId}/updates`, {
|
|
@@ -4288,8 +4086,8 @@ var noMrCommand = new Command24("no-mr").description("Signal that a task does no
|
|
|
4288
4086
|
});
|
|
4289
4087
|
|
|
4290
4088
|
// cli/commands/mobile.ts
|
|
4291
|
-
import { Command as
|
|
4292
|
-
function
|
|
4089
|
+
import { Command as Command24 } from "commander";
|
|
4090
|
+
function paint9(color, text) {
|
|
4293
4091
|
const colors = {
|
|
4294
4092
|
cyan: "\x1B[36m",
|
|
4295
4093
|
green: "\x1B[32m",
|
|
@@ -4300,7 +4098,7 @@ function paint10(color, text) {
|
|
|
4300
4098
|
};
|
|
4301
4099
|
return `${colors[color] ?? ""}${text}${colors.reset}`;
|
|
4302
4100
|
}
|
|
4303
|
-
var mobileCommand = new
|
|
4101
|
+
var mobileCommand = new Command24("mobile").description(
|
|
4304
4102
|
"Start the Web-to-Mobile Conversion Wizard for the linked project"
|
|
4305
4103
|
).argument("[project-id]", "Project ID (defaults to linked project)").option("--framework <framework>", "Framework: react-native or native").option("--url <url>", "Web app URL for analysis").action(
|
|
4306
4104
|
async (projectIdArg, opts) => {
|
|
@@ -4312,7 +4110,7 @@ var mobileCommand = new Command25("mobile").description(
|
|
|
4312
4110
|
process.exit(1);
|
|
4313
4111
|
}
|
|
4314
4112
|
console.log(
|
|
4315
|
-
|
|
4113
|
+
paint9("cyan", "mobile") + paint9("dim", " \u2014 starting conversion wizard")
|
|
4316
4114
|
);
|
|
4317
4115
|
const task = await api.post("/api/tasks", {
|
|
4318
4116
|
title: "Convert to Mobile App",
|
|
@@ -4343,13 +4141,13 @@ var mobileCommand = new Command25("mobile").description(
|
|
|
4343
4141
|
}
|
|
4344
4142
|
console.log(
|
|
4345
4143
|
`
|
|
4346
|
-
${
|
|
4144
|
+
${paint9("green", "\u2713")} Wizard initialized. Open the web UI to continue:
|
|
4347
4145
|
\u2192 Task ID: ${task.id}
|
|
4348
4146
|
\u2192 Use the "Convert to Mobile" button on the project page`
|
|
4349
4147
|
);
|
|
4350
4148
|
}
|
|
4351
4149
|
);
|
|
4352
|
-
var statusSubcommand = new
|
|
4150
|
+
var statusSubcommand = new Command24("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
|
|
4353
4151
|
const resources = await api.get(
|
|
4354
4152
|
`/api/tasks/${taskId}/resources`
|
|
4355
4153
|
);
|
|
@@ -4362,7 +4160,7 @@ var statusSubcommand = new Command25("status").description("Show mobile conversi
|
|
|
4362
4160
|
}
|
|
4363
4161
|
try {
|
|
4364
4162
|
const state = JSON.parse(wizardState.content);
|
|
4365
|
-
console.log(
|
|
4163
|
+
console.log(paint9("cyan", "Mobile Conversion Status"));
|
|
4366
4164
|
console.log(` Phase: ${state.phase}`);
|
|
4367
4165
|
if (state.framework) {
|
|
4368
4166
|
console.log(` Framework: ${state.framework}`);
|
|
@@ -4385,10 +4183,10 @@ var statusSubcommand = new Command25("status").description("Show mobile conversi
|
|
|
4385
4183
|
mobileCommand.addCommand(statusSubcommand);
|
|
4386
4184
|
|
|
4387
4185
|
// cli/commands/scan.ts
|
|
4388
|
-
import { Command as
|
|
4186
|
+
import { Command as Command25 } from "commander";
|
|
4389
4187
|
|
|
4390
4188
|
// lib/scanner/index.ts
|
|
4391
|
-
import { spawn as
|
|
4189
|
+
import { spawn as spawn8 } from "child_process";
|
|
4392
4190
|
|
|
4393
4191
|
// lib/scanner/config.ts
|
|
4394
4192
|
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
@@ -4854,10 +4652,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
4854
4652
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
4855
4653
|
|
|
4856
4654
|
**Components:**
|
|
4857
|
-
${codebaseAnalysis.components.slice(0, 30).map((
|
|
4655
|
+
${codebaseAnalysis.components.slice(0, 30).map((c11) => `- ${c11}`).join("\n")}
|
|
4858
4656
|
|
|
4859
4657
|
**Recent Git Commits:**
|
|
4860
|
-
${codebaseAnalysis.recentCommits.slice(0, 15).map((
|
|
4658
|
+
${codebaseAnalysis.recentCommits.slice(0, 15).map((c11) => `- ${c11}`).join("\n")}
|
|
4861
4659
|
|
|
4862
4660
|
**Completed Tasks:**
|
|
4863
4661
|
${context.completedTasks.slice(0, 20).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -5067,7 +4865,7 @@ async function fetchScanContext(opts) {
|
|
|
5067
4865
|
}
|
|
5068
4866
|
function runClaude(prompt2) {
|
|
5069
4867
|
return new Promise((resolve7, reject) => {
|
|
5070
|
-
const child =
|
|
4868
|
+
const child = spawn8("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
5071
4869
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5072
4870
|
});
|
|
5073
4871
|
let output = "";
|
|
@@ -5123,7 +4921,7 @@ function parseSynthesisOutput(output) {
|
|
|
5123
4921
|
}
|
|
5124
4922
|
|
|
5125
4923
|
// cli/commands/scan.ts
|
|
5126
|
-
var
|
|
4924
|
+
var c9 = {
|
|
5127
4925
|
reset: "\x1B[0m",
|
|
5128
4926
|
bold: "\x1B[1m",
|
|
5129
4927
|
dim: "\x1B[2m",
|
|
@@ -5134,53 +4932,53 @@ var c10 = {
|
|
|
5134
4932
|
magenta: "\x1B[35m",
|
|
5135
4933
|
gray: "\x1B[90m"
|
|
5136
4934
|
};
|
|
5137
|
-
function
|
|
5138
|
-
return `${
|
|
4935
|
+
function paint10(color, text) {
|
|
4936
|
+
return `${c9[color]}${text}${c9.reset}`;
|
|
5139
4937
|
}
|
|
5140
|
-
function
|
|
5141
|
-
return
|
|
4938
|
+
function timestamp3() {
|
|
4939
|
+
return paint10("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
5142
4940
|
}
|
|
5143
4941
|
function scanTag() {
|
|
5144
|
-
return
|
|
4942
|
+
return paint10("magenta", "[scan]");
|
|
5145
4943
|
}
|
|
5146
|
-
function
|
|
5147
|
-
console.log(`${
|
|
4944
|
+
function log2(msg) {
|
|
4945
|
+
console.log(`${timestamp3()} ${scanTag()} ${msg}`);
|
|
5148
4946
|
}
|
|
5149
|
-
function
|
|
5150
|
-
console.log(`${
|
|
4947
|
+
function logOk(msg) {
|
|
4948
|
+
console.log(`${timestamp3()} ${scanTag()} ${paint10("green", "\u2713")} ${msg}`);
|
|
5151
4949
|
}
|
|
5152
|
-
function
|
|
5153
|
-
console.error(`${
|
|
4950
|
+
function logErr2(msg) {
|
|
4951
|
+
console.error(`${timestamp3()} ${scanTag()} ${paint10("red", "\u2717")} ${msg}`);
|
|
5154
4952
|
}
|
|
5155
|
-
var scanCommand = new
|
|
4953
|
+
var scanCommand = new Command25("scan").description("Run a product scan on the current project \u2014 analyzes codebase, crawls the app, and surfaces findings").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing scan report ID (created by UI trigger)").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
|
|
5156
4954
|
const config = loadConfig();
|
|
5157
4955
|
if (!config.apiKey) {
|
|
5158
|
-
|
|
4956
|
+
logErr2('Not authenticated. Run "mr login" first.');
|
|
5159
4957
|
process.exit(1);
|
|
5160
4958
|
}
|
|
5161
4959
|
const banner = [
|
|
5162
4960
|
``,
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
4961
|
+
paint10("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
|
|
4962
|
+
paint10("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
|
|
4963
|
+
paint10("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
|
|
4964
|
+
paint10("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
4965
|
+
paint10("dim", ` autonomous product scanner`),
|
|
5168
4966
|
``
|
|
5169
4967
|
].join("\n");
|
|
5170
4968
|
console.log(banner);
|
|
5171
4969
|
const projectId = opts.project || getLinkedProjectId();
|
|
5172
4970
|
if (!projectId) {
|
|
5173
|
-
|
|
4971
|
+
logErr2('No project linked. Run "mr link" or pass --project <id>.');
|
|
5174
4972
|
process.exit(1);
|
|
5175
4973
|
}
|
|
5176
4974
|
let project;
|
|
5177
4975
|
try {
|
|
5178
4976
|
project = await api.get(`/api/projects/${projectId}`);
|
|
5179
4977
|
} catch {
|
|
5180
|
-
|
|
4978
|
+
logErr2(`Failed to fetch project ${projectId}`);
|
|
5181
4979
|
process.exit(1);
|
|
5182
4980
|
}
|
|
5183
|
-
|
|
4981
|
+
log2(`Scanning project: ${paint10("cyan", project.name)}`);
|
|
5184
4982
|
let projectPath = project.localPath;
|
|
5185
4983
|
if (!projectPath) {
|
|
5186
4984
|
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
@@ -5196,12 +4994,12 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5196
4994
|
let reportId;
|
|
5197
4995
|
if (opts.report) {
|
|
5198
4996
|
reportId = opts.report;
|
|
5199
|
-
|
|
4997
|
+
log2(`Using existing scan report ${paint10("yellow", reportId.slice(0, 8))}`);
|
|
5200
4998
|
} else {
|
|
5201
4999
|
try {
|
|
5202
5000
|
const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
|
|
5203
5001
|
if (scans.length > 0) {
|
|
5204
|
-
|
|
5002
|
+
logErr2("A scan is already in progress for this project. Wait for it to complete.");
|
|
5205
5003
|
process.exit(1);
|
|
5206
5004
|
}
|
|
5207
5005
|
} catch {
|
|
@@ -5212,9 +5010,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5212
5010
|
status: "pending"
|
|
5213
5011
|
});
|
|
5214
5012
|
reportId = report.id;
|
|
5215
|
-
|
|
5013
|
+
log2(`Created scan report ${paint10("yellow", reportId.slice(0, 8))}`);
|
|
5216
5014
|
} catch (err) {
|
|
5217
|
-
|
|
5015
|
+
logErr2(`Failed to create scan report: ${err.message}`);
|
|
5218
5016
|
process.exit(1);
|
|
5219
5017
|
}
|
|
5220
5018
|
}
|
|
@@ -5225,7 +5023,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5225
5023
|
try {
|
|
5226
5024
|
const current = await api.get(`/api/scans/${reportId}`);
|
|
5227
5025
|
if (current.status === "cancelled") {
|
|
5228
|
-
|
|
5026
|
+
log2(paint10("yellow", "Scan was cancelled \u2014 aborting."));
|
|
5229
5027
|
process.exit(0);
|
|
5230
5028
|
}
|
|
5231
5029
|
} catch {
|
|
@@ -5239,9 +5037,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5239
5037
|
apiUrl: config.apiUrl,
|
|
5240
5038
|
apiKey: config.apiKey,
|
|
5241
5039
|
runBrowse: runBrowseCommand2,
|
|
5242
|
-
onLog:
|
|
5040
|
+
onLog: log2,
|
|
5243
5041
|
onProgress: (phase, detail) => {
|
|
5244
|
-
|
|
5042
|
+
log2(`${paint10("dim", `[${phase}]`)} ${detail}`);
|
|
5245
5043
|
}
|
|
5246
5044
|
});
|
|
5247
5045
|
let wasCancelled = false;
|
|
@@ -5253,7 +5051,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5253
5051
|
} catch {
|
|
5254
5052
|
}
|
|
5255
5053
|
if (wasCancelled) {
|
|
5256
|
-
|
|
5054
|
+
log2(paint10("yellow", "Scan was cancelled by user \u2014 discarding results."));
|
|
5257
5055
|
process.exit(0);
|
|
5258
5056
|
}
|
|
5259
5057
|
await api.patch(`/api/scans/${reportId}`, {
|
|
@@ -5264,37 +5062,37 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5264
5062
|
scanDurationMs: result.scanDurationMs,
|
|
5265
5063
|
routesCrawled: result.routesCrawled
|
|
5266
5064
|
});
|
|
5267
|
-
|
|
5065
|
+
logOk(`Scan complete \u2014 ${paint10("cyan", String(result.findings.length))} findings`);
|
|
5268
5066
|
console.log("");
|
|
5269
|
-
console.log(` ${
|
|
5067
|
+
console.log(` ${paint10("bold", "Summary:")} ${result.summary}`);
|
|
5270
5068
|
console.log("");
|
|
5271
5069
|
const high = result.findings.filter((f) => f.priority === "high");
|
|
5272
5070
|
const medium = result.findings.filter((f) => f.priority === "medium");
|
|
5273
5071
|
const low = result.findings.filter((f) => f.priority === "low");
|
|
5274
5072
|
if (high.length > 0) {
|
|
5275
|
-
console.log(` ${
|
|
5073
|
+
console.log(` ${paint10("bold", paint10("red", `High Priority (${high.length})`))}`);
|
|
5276
5074
|
for (const f of high) {
|
|
5277
|
-
console.log(` ${
|
|
5278
|
-
console.log(` ${
|
|
5075
|
+
console.log(` ${paint10("red", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5076
|
+
console.log(` ${paint10("dim", f.description.slice(0, 120))}`);
|
|
5279
5077
|
}
|
|
5280
5078
|
console.log("");
|
|
5281
5079
|
}
|
|
5282
5080
|
if (medium.length > 0) {
|
|
5283
|
-
console.log(` ${
|
|
5081
|
+
console.log(` ${paint10("bold", paint10("yellow", `Medium Priority (${medium.length})`))}`);
|
|
5284
5082
|
for (const f of medium) {
|
|
5285
|
-
console.log(` ${
|
|
5083
|
+
console.log(` ${paint10("yellow", "\u25CF")} [${f.type}] ${f.title}`);
|
|
5286
5084
|
}
|
|
5287
5085
|
console.log("");
|
|
5288
5086
|
}
|
|
5289
5087
|
if (low.length > 0) {
|
|
5290
|
-
console.log(` ${
|
|
5088
|
+
console.log(` ${paint10("dim", `Low Priority (${low.length})`)} `);
|
|
5291
5089
|
for (const f of low) {
|
|
5292
|
-
console.log(` ${
|
|
5090
|
+
console.log(` ${paint10("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
5293
5091
|
}
|
|
5294
5092
|
console.log("");
|
|
5295
5093
|
}
|
|
5296
5094
|
} catch (err) {
|
|
5297
|
-
|
|
5095
|
+
logErr2(`Scan failed: ${err.message}`);
|
|
5298
5096
|
try {
|
|
5299
5097
|
await api.patch(`/api/scans/${reportId}`, {
|
|
5300
5098
|
status: "failed",
|
|
@@ -5307,8 +5105,8 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
5307
5105
|
});
|
|
5308
5106
|
|
|
5309
5107
|
// cli/commands/idea.ts
|
|
5310
|
-
import { Command as
|
|
5311
|
-
var
|
|
5108
|
+
import { Command as Command26 } from "commander";
|
|
5109
|
+
var c10 = {
|
|
5312
5110
|
reset: "\x1B[0m",
|
|
5313
5111
|
bold: "\x1B[1m",
|
|
5314
5112
|
dim: "\x1B[2m",
|
|
@@ -5320,27 +5118,27 @@ var c11 = {
|
|
|
5320
5118
|
gray: "\x1B[90m",
|
|
5321
5119
|
magenta: "\x1B[35m"
|
|
5322
5120
|
};
|
|
5323
|
-
function
|
|
5324
|
-
return `${
|
|
5121
|
+
function paint11(color, text) {
|
|
5122
|
+
return `${c10[color]}${text}${c10.reset}`;
|
|
5325
5123
|
}
|
|
5326
5124
|
function statusBadge2(status) {
|
|
5327
5125
|
switch (status) {
|
|
5328
5126
|
case "draft":
|
|
5329
|
-
return
|
|
5127
|
+
return paint11("gray", "\u25CB draft");
|
|
5330
5128
|
case "generating":
|
|
5331
|
-
return
|
|
5129
|
+
return paint11("cyan", "\u27F3 generating");
|
|
5332
5130
|
case "generated":
|
|
5333
|
-
return
|
|
5131
|
+
return paint11("green", "\u2713 generated");
|
|
5334
5132
|
case "promoted":
|
|
5335
|
-
return
|
|
5133
|
+
return paint11("magenta", "\u2191 promoted");
|
|
5336
5134
|
case "archived":
|
|
5337
|
-
return
|
|
5135
|
+
return paint11("dim", "\u2298 archived");
|
|
5338
5136
|
default:
|
|
5339
|
-
return
|
|
5137
|
+
return paint11("gray", status);
|
|
5340
5138
|
}
|
|
5341
5139
|
}
|
|
5342
|
-
var ideaCommand = new
|
|
5343
|
-
new
|
|
5140
|
+
var ideaCommand = new Command26("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
|
|
5141
|
+
new Command26("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
5344
5142
|
const params = new URLSearchParams();
|
|
5345
5143
|
if (!opts.all) {
|
|
5346
5144
|
const projectId = getLinkedProjectId();
|
|
@@ -5351,23 +5149,23 @@ var ideaCommand = new Command27("idea").description("Manage ideas \u2014 brainst
|
|
|
5351
5149
|
if (opts.status) params.set("status", opts.status);
|
|
5352
5150
|
const ideas = await api.get(`/api/ideas?${params.toString()}`);
|
|
5353
5151
|
if (ideas.length === 0) {
|
|
5354
|
-
console.log(
|
|
5152
|
+
console.log(paint11("gray", "No ideas found."));
|
|
5355
5153
|
return;
|
|
5356
5154
|
}
|
|
5357
5155
|
console.log();
|
|
5358
5156
|
for (const idea of ideas) {
|
|
5359
5157
|
const date = new Date(idea.createdAt).toLocaleDateString();
|
|
5360
5158
|
console.log(
|
|
5361
|
-
` ${
|
|
5159
|
+
` ${paint11("bold", idea.title)} ${statusBadge2(idea.status)} ${paint11("gray", idea.id.slice(0, 8))} ${paint11("dim", date)}`
|
|
5362
5160
|
);
|
|
5363
5161
|
if (idea.description) {
|
|
5364
|
-
console.log(` ${
|
|
5162
|
+
console.log(` ${paint11("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
|
|
5365
5163
|
}
|
|
5366
5164
|
console.log();
|
|
5367
5165
|
}
|
|
5368
5166
|
})
|
|
5369
5167
|
).addCommand(
|
|
5370
|
-
new
|
|
5168
|
+
new Command26("create").description("Create a new idea").argument("<title>", "Title of the idea").option("--description <desc>", "Description of the idea").option("--project <projectId>", "Project ID (defaults to linked project)").option("--generate", "Immediately start generating plan & prototype").action(async (title, opts) => {
|
|
5371
5169
|
const projectId = opts.project ?? getLinkedProjectId() ?? null;
|
|
5372
5170
|
const idea = await api.post("/api/ideas", {
|
|
5373
5171
|
title,
|
|
@@ -5375,59 +5173,59 @@ var ideaCommand = new Command27("idea").description("Manage ideas \u2014 brainst
|
|
|
5375
5173
|
projectId
|
|
5376
5174
|
});
|
|
5377
5175
|
console.log();
|
|
5378
|
-
console.log(` ${
|
|
5379
|
-
console.log(` ${
|
|
5176
|
+
console.log(` ${paint11("green", "\u2713")} Created idea: ${paint11("bold", idea.title)}`);
|
|
5177
|
+
console.log(` ${paint11("gray", "ID:")} ${idea.id}`);
|
|
5380
5178
|
if (opts.generate) {
|
|
5381
5179
|
await api.post(`/api/ideas/${idea.id}/generate`);
|
|
5382
|
-
console.log(` ${
|
|
5180
|
+
console.log(` ${paint11("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
5383
5181
|
}
|
|
5384
5182
|
console.log();
|
|
5385
5183
|
})
|
|
5386
5184
|
).addCommand(
|
|
5387
|
-
new
|
|
5185
|
+
new Command26("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
|
|
5388
5186
|
const idea = await api.post(`/api/ideas/${id}/generate`);
|
|
5389
5187
|
console.log();
|
|
5390
|
-
console.log(` ${
|
|
5391
|
-
console.log(` ${
|
|
5188
|
+
console.log(` ${paint11("cyan", "\u27F3")} Generating: ${paint11("bold", idea.title)}`);
|
|
5189
|
+
console.log(` ${paint11("gray", "The watch agent will pick this up shortly.")}`);
|
|
5392
5190
|
console.log();
|
|
5393
5191
|
})
|
|
5394
5192
|
).addCommand(
|
|
5395
|
-
new
|
|
5193
|
+
new Command26("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
|
|
5396
5194
|
const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
|
|
5397
5195
|
console.log();
|
|
5398
|
-
console.log(` ${
|
|
5399
|
-
console.log(` ${
|
|
5196
|
+
console.log(` ${paint11("cyan", "\u27F3")} Feedback sent for: ${paint11("bold", idea.title)}`);
|
|
5197
|
+
console.log(` ${paint11("gray", "The watch agent will re-generate with your feedback.")}`);
|
|
5400
5198
|
console.log();
|
|
5401
5199
|
})
|
|
5402
5200
|
).addCommand(
|
|
5403
|
-
new
|
|
5201
|
+
new Command26("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
|
|
5404
5202
|
const result = await api.post(`/api/ideas/${id}/promote`);
|
|
5405
5203
|
console.log();
|
|
5406
|
-
console.log(` ${
|
|
5407
|
-
console.log(` ${
|
|
5204
|
+
console.log(` ${paint11("green", "\u2713")} Promoted idea to task: ${paint11("bold", result.task.title)}`);
|
|
5205
|
+
console.log(` ${paint11("gray", "Task ID:")} ${result.task.id}`);
|
|
5408
5206
|
console.log();
|
|
5409
5207
|
})
|
|
5410
5208
|
).addCommand(
|
|
5411
|
-
new
|
|
5209
|
+
new Command26("spin-up").description("Spin up a new project with a GitHub repo from an idea").argument("<id>", "Idea ID").option("--name <name>", "Custom project name (defaults to idea title)").action(async (id, opts) => {
|
|
5412
5210
|
const body = {};
|
|
5413
5211
|
if (opts.name) body.name = opts.name;
|
|
5414
5212
|
const result = await api.post(`/api/ideas/${id}/spin-up`, body);
|
|
5415
5213
|
console.log();
|
|
5416
|
-
console.log(` ${
|
|
5417
|
-
console.log(` ${
|
|
5214
|
+
console.log(` ${paint11("green", "\u2713")} Spinning up project: ${paint11("bold", result.project.name)}`);
|
|
5215
|
+
console.log(` ${paint11("gray", "Project ID:")} ${result.project.id}`);
|
|
5418
5216
|
if (result.tasks && result.tasks.length > 0) {
|
|
5419
|
-
console.log(` ${
|
|
5217
|
+
console.log(` ${paint11("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
|
|
5420
5218
|
for (const task of result.tasks) {
|
|
5421
|
-
console.log(` ${
|
|
5219
|
+
console.log(` ${paint11("gray", "\u2022")} ${task.title}`);
|
|
5422
5220
|
}
|
|
5423
5221
|
}
|
|
5424
|
-
console.log(` ${
|
|
5222
|
+
console.log(` ${paint11("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
|
|
5425
5223
|
console.log();
|
|
5426
5224
|
})
|
|
5427
5225
|
);
|
|
5428
5226
|
|
|
5429
5227
|
// cli/commands/doctor.ts
|
|
5430
|
-
import { Command as
|
|
5228
|
+
import { Command as Command27 } from "commander";
|
|
5431
5229
|
import { existsSync as existsSync14 } from "fs";
|
|
5432
5230
|
import { homedir as homedir3 } from "os";
|
|
5433
5231
|
import { join as join12 } from "path";
|
|
@@ -5475,12 +5273,12 @@ async function checkProjectLink() {
|
|
|
5475
5273
|
optional: true
|
|
5476
5274
|
};
|
|
5477
5275
|
}
|
|
5478
|
-
var doctorCommand = new
|
|
5276
|
+
var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
5479
5277
|
const banner = [
|
|
5480
5278
|
``,
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5279
|
+
paint6("cyan", ` MR DOCTOR`),
|
|
5280
|
+
paint6("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
5281
|
+
paint6("dim", ` diagnosing your mr environment`),
|
|
5484
5282
|
``
|
|
5485
5283
|
].join("\n");
|
|
5486
5284
|
console.log(banner);
|
|
@@ -5502,15 +5300,15 @@ var doctorCommand = new Command28("doctor").description("Diagnose Mr. Manager CL
|
|
|
5502
5300
|
const allOk = printResults(checks);
|
|
5503
5301
|
console.log("");
|
|
5504
5302
|
if (allOk) {
|
|
5505
|
-
console.log(
|
|
5303
|
+
console.log(paint6("green", " Everything looks good!"));
|
|
5506
5304
|
console.log("");
|
|
5507
5305
|
return;
|
|
5508
5306
|
}
|
|
5509
|
-
const fixes = checks.filter((
|
|
5307
|
+
const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
|
|
5510
5308
|
if (fixes.length > 0) {
|
|
5511
|
-
console.log(
|
|
5309
|
+
console.log(paint6("yellow", " To fix:"));
|
|
5512
5310
|
for (const fix of fixes) {
|
|
5513
|
-
console.log(` ${
|
|
5311
|
+
console.log(` ${paint6("dim", "\u2192")} ${paint6("bold", fix.name)}: ${fix.fix}`);
|
|
5514
5312
|
}
|
|
5515
5313
|
console.log("");
|
|
5516
5314
|
}
|
|
@@ -5524,7 +5322,7 @@ var userArgs = process.argv.slice(2);
|
|
|
5524
5322
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
5525
5323
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
5526
5324
|
if (isFirstRun && !shouldBypass) {
|
|
5527
|
-
const
|
|
5325
|
+
const c11 = {
|
|
5528
5326
|
reset: "\x1B[0m",
|
|
5529
5327
|
bold: "\x1B[1m",
|
|
5530
5328
|
dim: "\x1B[2m",
|
|
@@ -5534,28 +5332,28 @@ if (isFirstRun && !shouldBypass) {
|
|
|
5534
5332
|
magenta: "\x1B[35m"
|
|
5535
5333
|
};
|
|
5536
5334
|
console.log("");
|
|
5537
|
-
console.log(`${
|
|
5538
|
-
console.log(`${
|
|
5539
|
-
console.log(`${
|
|
5540
|
-
console.log(`${
|
|
5335
|
+
console.log(`${c11.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c11.reset}`);
|
|
5336
|
+
console.log(`${c11.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c11.reset}`);
|
|
5337
|
+
console.log(`${c11.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c11.reset}`);
|
|
5338
|
+
console.log(`${c11.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c11.reset}`);
|
|
5541
5339
|
console.log("");
|
|
5542
|
-
console.log(`${
|
|
5543
|
-
console.log(`${
|
|
5340
|
+
console.log(`${c11.bold} Welcome to Mr. Manager!${c11.reset}`);
|
|
5341
|
+
console.log(`${c11.dim} Let's get you set up in a few quick steps.${c11.reset}`);
|
|
5544
5342
|
console.log("");
|
|
5545
|
-
console.log(` ${
|
|
5546
|
-
console.log(` ${
|
|
5343
|
+
console.log(` ${c11.yellow}Step 1:${c11.reset} Authenticate via Google OAuth`);
|
|
5344
|
+
console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr login${c11.reset}`);
|
|
5547
5345
|
console.log("");
|
|
5548
|
-
console.log(` ${
|
|
5549
|
-
console.log(` ${
|
|
5346
|
+
console.log(` ${c11.yellow}Step 2:${c11.reset} Verify your environment`);
|
|
5347
|
+
console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr setup${c11.reset}`);
|
|
5550
5348
|
console.log("");
|
|
5551
|
-
console.log(` ${
|
|
5552
|
-
console.log(` ${
|
|
5349
|
+
console.log(` ${c11.yellow}Step 3:${c11.reset} Link a repo and start watching`);
|
|
5350
|
+
console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr link${c11.reset} ${c11.dim}&&${c11.reset} ${c11.cyan}mr watch${c11.reset}`);
|
|
5553
5351
|
console.log("");
|
|
5554
|
-
console.log(`${
|
|
5352
|
+
console.log(`${c11.dim} Or run ${c11.reset}${c11.cyan}mr login${c11.reset}${c11.dim} to get started now.${c11.reset}`);
|
|
5555
5353
|
console.log("");
|
|
5556
5354
|
process.exit(0);
|
|
5557
5355
|
}
|
|
5558
|
-
var program = new
|
|
5356
|
+
var program = new Command28();
|
|
5559
5357
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version("0.2.0");
|
|
5560
5358
|
program.addCommand(initCommand);
|
|
5561
5359
|
program.addCommand(authCommand);
|
|
@@ -5572,7 +5370,6 @@ program.addCommand(undelegateCommand);
|
|
|
5572
5370
|
program.addCommand(createCommand);
|
|
5573
5371
|
program.addCommand(completeCommand);
|
|
5574
5372
|
program.addCommand(subtaskCompleteCommand);
|
|
5575
|
-
program.addCommand(digestCommand);
|
|
5576
5373
|
program.addCommand(upCommand);
|
|
5577
5374
|
program.addCommand(prototypeCommand);
|
|
5578
5375
|
program.addCommand(setupCommand);
|