runtrim 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cli/runtrim.js +292 -214
- package/package.json +1 -1
package/dist-cli/runtrim.js
CHANGED
|
@@ -1947,15 +1947,6 @@ import fs7 from "fs";
|
|
|
1947
1947
|
import path7 from "path";
|
|
1948
1948
|
var BRIDGE_START = "<!-- RUNTRIM_BRIDGE_START -->";
|
|
1949
1949
|
var BRIDGE_END = "<!-- RUNTRIM_BRIDGE_END -->";
|
|
1950
|
-
var BRIDGE_BLOCK = `
|
|
1951
|
-
${BRIDGE_START}
|
|
1952
|
-
Before editing code, read RUNTRIM.md and .runtrim/contracts/latest.md.
|
|
1953
|
-
Stay inside the active scoped contract.
|
|
1954
|
-
Do not touch forbidden files or unrelated systems.
|
|
1955
|
-
If scope expands, stop and ask for a new RunTrim run.
|
|
1956
|
-
After editing, run: runtrim finish
|
|
1957
|
-
${BRIDGE_END}
|
|
1958
|
-
`;
|
|
1959
1950
|
var TOKEN_BUDGET_MAP = {
|
|
1960
1951
|
low: 1e4,
|
|
1961
1952
|
medium: 25e3,
|
|
@@ -1967,10 +1958,9 @@ function deriveBridgeContext(task, contract, recentRuns, projectName) {
|
|
|
1967
1958
|
const c = contract.contract;
|
|
1968
1959
|
const riskLevel = (_a2 = contract.wasteRiskAfter) != null ? _a2 : "medium";
|
|
1969
1960
|
const tokenBudget = (_b = TOKEN_BUDGET_MAP[riskLevel]) != null ? _b : 25e3;
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
];
|
|
1961
|
+
const stopFromRules = (_c = c.stopRules) != null ? _c : [];
|
|
1962
|
+
const stopFromAsk = ((_d = c.whenToAsk) != null ? _d : []).map((s) => `Stop and ask before: ${s}`);
|
|
1963
|
+
const stopConditions = [...stopFromRules, ...stopFromAsk];
|
|
1974
1964
|
const verificationSteps = (_e = c.successCriteria) != null ? _e : [];
|
|
1975
1965
|
const lines = recentRuns.slice(0, 3).map((r) => {
|
|
1976
1966
|
const day = new Date(r.createdAt).toLocaleDateString("en-US", {
|
|
@@ -1994,6 +1984,126 @@ ${lines.join("\n")}` : "No prior runs. This is the first run for this project.";
|
|
|
1994
1984
|
projectName
|
|
1995
1985
|
};
|
|
1996
1986
|
}
|
|
1987
|
+
function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
1988
|
+
const lines = [
|
|
1989
|
+
"# RunTrim Protocol",
|
|
1990
|
+
"",
|
|
1991
|
+
...projectName ? [`Project: ${projectName}`, ""] : [],
|
|
1992
|
+
"This repo uses RunTrim as the guarded AI coding control layer.",
|
|
1993
|
+
"",
|
|
1994
|
+
"## How to start an AI coding task",
|
|
1995
|
+
"",
|
|
1996
|
+
"```",
|
|
1997
|
+
'runtrim go "<task>"',
|
|
1998
|
+
"```",
|
|
1999
|
+
"",
|
|
2000
|
+
"RunTrim creates a scoped contract, loads project memory, and generates a guarded prompt.",
|
|
2001
|
+
"",
|
|
2002
|
+
"## How to use your agent",
|
|
2003
|
+
"",
|
|
2004
|
+
"Paste the guarded prompt into Claude Code, Codex, Cursor, or any other coding agent.",
|
|
2005
|
+
"",
|
|
2006
|
+
"## After edits",
|
|
2007
|
+
"",
|
|
2008
|
+
"```",
|
|
2009
|
+
"runtrim finish",
|
|
2010
|
+
"```",
|
|
2011
|
+
"",
|
|
2012
|
+
"RunTrim checks changed files, detects drift, scores risk, and saves the run report.",
|
|
2013
|
+
"",
|
|
2014
|
+
"## If you are an AI coding agent",
|
|
2015
|
+
"",
|
|
2016
|
+
"1. Read `.runtrim/contracts/latest.md`.",
|
|
2017
|
+
" - If `Status: active` \u2014 a live task exists. Follow the contract strictly.",
|
|
2018
|
+
' - If `Status: none` \u2014 no active task. Ask the user to run `runtrim go "<task>"` first.',
|
|
2019
|
+
"2. Do not assume any prior task is still active.",
|
|
2020
|
+
"3. Stay inside the allowed scope defined in the contract.",
|
|
2021
|
+
"4. Stop and ask before touching any forbidden area.",
|
|
2022
|
+
"5. Do not read or write `.env` files or secrets.",
|
|
2023
|
+
"6. After editing, tell the user to run: `runtrim finish`",
|
|
2024
|
+
"",
|
|
2025
|
+
"---",
|
|
2026
|
+
`Protocol: runtrim init. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2027
|
+
];
|
|
2028
|
+
fs7.writeFileSync(path7.join(cwd, "RUNTRIM.md"), lines.join("\n"), "utf-8");
|
|
2029
|
+
}
|
|
2030
|
+
function writeRestingContract(cwd = process.cwd()) {
|
|
2031
|
+
const dir = path7.join(getConfigDir(cwd), "contracts");
|
|
2032
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
2033
|
+
const content = [
|
|
2034
|
+
"# RunTrim Contract",
|
|
2035
|
+
"",
|
|
2036
|
+
"Status: none",
|
|
2037
|
+
"",
|
|
2038
|
+
"No active RunTrim contract.",
|
|
2039
|
+
"",
|
|
2040
|
+
"Start one with:",
|
|
2041
|
+
"",
|
|
2042
|
+
"```",
|
|
2043
|
+
'runtrim go "<your task>"',
|
|
2044
|
+
"```",
|
|
2045
|
+
"",
|
|
2046
|
+
"---",
|
|
2047
|
+
`Reset by runtrim finish. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2048
|
+
].join("\n");
|
|
2049
|
+
fs7.writeFileSync(path7.join(dir, "latest.md"), content, "utf-8");
|
|
2050
|
+
}
|
|
2051
|
+
function writeRestingMemory(cwd = process.cwd()) {
|
|
2052
|
+
const dir = path7.join(getConfigDir(cwd), "memory");
|
|
2053
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
2054
|
+
const content = [
|
|
2055
|
+
"# RunTrim Memory",
|
|
2056
|
+
"",
|
|
2057
|
+
"Status: none",
|
|
2058
|
+
"",
|
|
2059
|
+
"No active RunTrim session.",
|
|
2060
|
+
"",
|
|
2061
|
+
"Project baseline is in `.runtrim/memory/baseline.md`.",
|
|
2062
|
+
"",
|
|
2063
|
+
"Start a new session with:",
|
|
2064
|
+
"",
|
|
2065
|
+
"```",
|
|
2066
|
+
'runtrim go "<your task>"',
|
|
2067
|
+
"```",
|
|
2068
|
+
"",
|
|
2069
|
+
"---",
|
|
2070
|
+
`Reset by runtrim finish. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2071
|
+
].join("\n");
|
|
2072
|
+
fs7.writeFileSync(path7.join(dir, "current.md"), content, "utf-8");
|
|
2073
|
+
}
|
|
2074
|
+
function archiveContract(cwd, runId) {
|
|
2075
|
+
const contractsDir = path7.join(getConfigDir(cwd), "contracts");
|
|
2076
|
+
const latestPath = path7.join(contractsDir, "latest.md");
|
|
2077
|
+
if (!fs7.existsSync(latestPath)) return;
|
|
2078
|
+
const content = fs7.readFileSync(latestPath, "utf-8");
|
|
2079
|
+
if (content.includes("Status: none")) return;
|
|
2080
|
+
const archiveDir = path7.join(contractsDir, "archive");
|
|
2081
|
+
if (!fs7.existsSync(archiveDir)) fs7.mkdirSync(archiveDir, { recursive: true });
|
|
2082
|
+
fs7.writeFileSync(path7.join(archiveDir, `${runId}.md`), content, "utf-8");
|
|
2083
|
+
}
|
|
2084
|
+
function archiveMemory(cwd, runId) {
|
|
2085
|
+
const memoryDir = path7.join(getConfigDir(cwd), "memory");
|
|
2086
|
+
const currentPath = path7.join(memoryDir, "current.md");
|
|
2087
|
+
if (!fs7.existsSync(currentPath)) return;
|
|
2088
|
+
const content = fs7.readFileSync(currentPath, "utf-8");
|
|
2089
|
+
if (content.includes("Status: none")) return;
|
|
2090
|
+
const archiveDir = path7.join(memoryDir, "archive");
|
|
2091
|
+
if (!fs7.existsSync(archiveDir)) fs7.mkdirSync(archiveDir, { recursive: true });
|
|
2092
|
+
fs7.writeFileSync(path7.join(archiveDir, `${runId}.md`), content, "utf-8");
|
|
2093
|
+
}
|
|
2094
|
+
function removeBridgeBlock(filePath) {
|
|
2095
|
+
if (!fs7.existsSync(filePath)) return false;
|
|
2096
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2097
|
+
const startIdx = content.indexOf(BRIDGE_START);
|
|
2098
|
+
const endIdx = content.indexOf(BRIDGE_END);
|
|
2099
|
+
if (startIdx === -1 || endIdx === -1) return false;
|
|
2100
|
+
const before = content.slice(0, startIdx).trimEnd();
|
|
2101
|
+
const after = content.slice(endIdx + BRIDGE_END.length).replace(/^\n+/, "\n");
|
|
2102
|
+
const newContent = (before + after).trimEnd() + "\n";
|
|
2103
|
+
if (newContent === content) return false;
|
|
2104
|
+
fs7.writeFileSync(filePath, newContent, "utf-8");
|
|
2105
|
+
return true;
|
|
2106
|
+
}
|
|
1997
2107
|
function writeContractFile(ctx, cwd = process.cwd()) {
|
|
1998
2108
|
const dir = path7.join(getConfigDir(cwd), "contracts");
|
|
1999
2109
|
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
@@ -2001,13 +2111,15 @@ function writeContractFile(ctx, cwd = process.cwd()) {
|
|
|
2001
2111
|
const forbidLines = ctx.forbiddenScope.length > 0 ? ctx.forbiddenScope.map((s) => `- ${s}`) : ["- .env* files", "- auth logic", "- database schema / migrations", "- billing and payment logic"];
|
|
2002
2112
|
const stopLines = ctx.stopConditions.length > 0 ? ctx.stopConditions.map((s) => `- ${s}`) : [
|
|
2003
2113
|
"- Stop if scope must expand beyond the task.",
|
|
2004
|
-
"- Stop
|
|
2114
|
+
"- Stop and ask before touching any forbidden area.",
|
|
2005
2115
|
"- Stop if more than 5 files require changes."
|
|
2006
2116
|
];
|
|
2007
2117
|
const verifyLines = ctx.verificationSteps.length > 0 ? ctx.verificationSteps.map((s) => `- ${s}`) : ["- Verify the behavior described in the task.", "- Check no unrelated files changed."];
|
|
2008
2118
|
const lines = [
|
|
2009
2119
|
"# RunTrim Active Contract",
|
|
2010
2120
|
"",
|
|
2121
|
+
"Status: active",
|
|
2122
|
+
"",
|
|
2011
2123
|
`Task: ${ctx.task}`,
|
|
2012
2124
|
`Goal: ${ctx.goal}`,
|
|
2013
2125
|
`Risk: ${ctx.riskLevel}`,
|
|
@@ -2039,6 +2151,8 @@ function writeMemoryFile(ctx, cwd = process.cwd()) {
|
|
|
2039
2151
|
const lines = [
|
|
2040
2152
|
"# RunTrim Memory Pack",
|
|
2041
2153
|
"",
|
|
2154
|
+
"Status: active",
|
|
2155
|
+
"",
|
|
2042
2156
|
`Project: ${ctx.projectName}`,
|
|
2043
2157
|
`Current task: ${ctx.task}`,
|
|
2044
2158
|
`Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
@@ -2067,15 +2181,17 @@ function writeBridgeInstructions(cwd = process.cwd()) {
|
|
|
2067
2181
|
"",
|
|
2068
2182
|
"## Before editing",
|
|
2069
2183
|
"",
|
|
2070
|
-
"Read
|
|
2071
|
-
"
|
|
2072
|
-
"- .
|
|
2073
|
-
|
|
2184
|
+
"1. Read `RUNTRIM.md`.",
|
|
2185
|
+
"2. Read `.runtrim/contracts/latest.md`.",
|
|
2186
|
+
" - If `Status: active` \u2014 follow the contract strictly.",
|
|
2187
|
+
' - If `Status: none` \u2014 stop. Ask the user to run `runtrim go "<task>"` first.',
|
|
2188
|
+
"3. If the contract is active, read `.runtrim/memory/current.md` for session context.",
|
|
2189
|
+
" If no active session, read `.runtrim/memory/baseline.md` for project baseline.",
|
|
2074
2190
|
"",
|
|
2075
2191
|
"## During editing",
|
|
2076
2192
|
"",
|
|
2077
2193
|
"- Stay within the allowed scope defined in the contract.",
|
|
2078
|
-
"-
|
|
2194
|
+
"- Stop and ask the user before touching any forbidden area.",
|
|
2079
2195
|
"- Make minimal, targeted changes only.",
|
|
2080
2196
|
"- Do not refactor, rename, or reorganize outside the task scope.",
|
|
2081
2197
|
"- Do not add console.log statements or debug artifacts.",
|
|
@@ -2088,56 +2204,10 @@ function writeBridgeInstructions(cwd = process.cwd()) {
|
|
|
2088
2204
|
"- Do not run runtrim commands yourself unless explicitly asked.",
|
|
2089
2205
|
"",
|
|
2090
2206
|
"---",
|
|
2091
|
-
"Generated by RunTrim
|
|
2207
|
+
"Generated by RunTrim. Do not edit manually."
|
|
2092
2208
|
];
|
|
2093
2209
|
fs7.writeFileSync(path7.join(dir, "agent-instructions.md"), lines.join("\n"), "utf-8");
|
|
2094
2210
|
}
|
|
2095
|
-
function writeRootProtocolFile(ctx, cwd = process.cwd()) {
|
|
2096
|
-
const lines = [
|
|
2097
|
-
"# RunTrim Protocol",
|
|
2098
|
-
"",
|
|
2099
|
-
"This project is guarded by RunTrim Bridge Mode.",
|
|
2100
|
-
"",
|
|
2101
|
-
"## Active session",
|
|
2102
|
-
"",
|
|
2103
|
-
`Task: ${ctx.task}`,
|
|
2104
|
-
`Risk: ${ctx.riskLevel}`,
|
|
2105
|
-
`Token budget: ~${ctx.tokenBudget.toLocaleString()}`,
|
|
2106
|
-
"",
|
|
2107
|
-
"## Before editing",
|
|
2108
|
-
"",
|
|
2109
|
-
"Read the active contract before making any changes:",
|
|
2110
|
-
"",
|
|
2111
|
-
"```",
|
|
2112
|
-
".runtrim/contracts/latest.md",
|
|
2113
|
-
"```",
|
|
2114
|
-
"",
|
|
2115
|
-
"## Rules",
|
|
2116
|
-
"",
|
|
2117
|
-
"- Stay inside the active scoped contract.",
|
|
2118
|
-
"- Do not touch forbidden files or unrelated systems.",
|
|
2119
|
-
"- If scope must expand, stop and ask the user to start a new RunTrim session.",
|
|
2120
|
-
"- Keep changes minimal and targeted.",
|
|
2121
|
-
"- Do not refactor, reorganize, or rename outside the direct task.",
|
|
2122
|
-
"",
|
|
2123
|
-
"## After editing",
|
|
2124
|
-
"",
|
|
2125
|
-
"The user will run: runtrim finish",
|
|
2126
|
-
"",
|
|
2127
|
-
"Do not attempt to run runtrim commands yourself.",
|
|
2128
|
-
"",
|
|
2129
|
-
"---",
|
|
2130
|
-
`Generated by RunTrim Bridge. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2131
|
-
];
|
|
2132
|
-
fs7.writeFileSync(path7.join(cwd, "RUNTRIM.md"), lines.join("\n"), "utf-8");
|
|
2133
|
-
}
|
|
2134
|
-
function appendBridgeBlock(filePath) {
|
|
2135
|
-
if (!fs7.existsSync(filePath)) return false;
|
|
2136
|
-
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2137
|
-
if (content.includes(BRIDGE_START)) return false;
|
|
2138
|
-
fs7.writeFileSync(filePath, content.trimEnd() + "\n" + BRIDGE_BLOCK, "utf-8");
|
|
2139
|
-
return true;
|
|
2140
|
-
}
|
|
2141
2211
|
function writeBridgeFiles(ctx, cwd) {
|
|
2142
2212
|
const written = [];
|
|
2143
2213
|
const managedPaths = [];
|
|
@@ -2145,22 +2215,18 @@ function writeBridgeFiles(ctx, cwd) {
|
|
|
2145
2215
|
managedPaths.push(relativePath);
|
|
2146
2216
|
written.push(label != null ? label : relativePath);
|
|
2147
2217
|
};
|
|
2148
|
-
writeRootProtocolFile(ctx, cwd);
|
|
2149
|
-
track("RUNTRIM.md");
|
|
2150
2218
|
writeContractFile(ctx, cwd);
|
|
2151
2219
|
track(".runtrim/contracts/latest.md");
|
|
2152
2220
|
writeMemoryFile(ctx, cwd);
|
|
2153
2221
|
track(".runtrim/memory/current.md");
|
|
2154
2222
|
writeBridgeInstructions(cwd);
|
|
2155
2223
|
track(".runtrim/bridge/agent-instructions.md");
|
|
2156
|
-
if (appendBridgeBlock(path7.join(cwd, "CLAUDE.md"))) track("CLAUDE.md", "CLAUDE.md (bridge block appended)");
|
|
2157
|
-
if (appendBridgeBlock(path7.join(cwd, "AGENTS.md"))) track("AGENTS.md", "AGENTS.md (bridge block appended)");
|
|
2158
2224
|
return { written, managedPaths };
|
|
2159
2225
|
}
|
|
2160
2226
|
function buildBridgePrompt(contractText, ctx) {
|
|
2161
2227
|
const allowedList = ctx.allowedScope.length > 0 ? ctx.allowedScope.map((s) => ` - ${s}`).join("\n") : " - Defined in .runtrim/contracts/latest.md";
|
|
2162
2228
|
const forbidList = ctx.forbiddenScope.length > 0 ? ctx.forbiddenScope.map((s) => ` - ${s}`).join("\n") : " - .env files, auth, database schema, billing, payments";
|
|
2163
|
-
const stopList = ctx.stopConditions.length > 0 ? ctx.stopConditions.slice(0, 4).map((s) => ` - ${s}`).join("\n") : " - Stop if more than 5 files need changes.\n - Stop
|
|
2229
|
+
const stopList = ctx.stopConditions.length > 0 ? ctx.stopConditions.slice(0, 4).map((s) => ` - ${s}`).join("\n") : " - Stop if more than 5 files need changes.\n - Stop and ask before touching any forbidden area.";
|
|
2164
2230
|
const header = [
|
|
2165
2231
|
"RUNTRIM BRIDGE SESSION",
|
|
2166
2232
|
`Task: ${ctx.task}`,
|
|
@@ -2182,7 +2248,7 @@ function buildBridgePrompt(contractText, ctx) {
|
|
|
2182
2248
|
" - Read RUNTRIM.md and .runtrim/contracts/latest.md before touching any file.",
|
|
2183
2249
|
" - Inspect first. List relevant files before opening them.",
|
|
2184
2250
|
" - Make the minimal change required. No unrelated refactors.",
|
|
2185
|
-
" -
|
|
2251
|
+
" - Stop and ask before touching any forbidden area.",
|
|
2186
2252
|
" - After editing, tell the user which files changed. They will run: runtrim finish",
|
|
2187
2253
|
""
|
|
2188
2254
|
].join("\n");
|
|
@@ -4227,55 +4293,7 @@ function installProtocol(cwd, baseline, opts = {}) {
|
|
|
4227
4293
|
const testCmd = (_f = (_e = scripts["test"]) != null ? _e : scripts["test:run"]) != null ? _f : null;
|
|
4228
4294
|
const runtrimMdPath = path10.join(cwd, "RUNTRIM.md");
|
|
4229
4295
|
const runtrimMdExists = fs10.existsSync(runtrimMdPath);
|
|
4230
|
-
|
|
4231
|
-
"# RunTrim Protocol",
|
|
4232
|
-
"",
|
|
4233
|
-
"This repo uses RunTrim as the guarded AI coding control layer.",
|
|
4234
|
-
"",
|
|
4235
|
-
"## Starting an AI coding task",
|
|
4236
|
-
"",
|
|
4237
|
-
"Before any agent touches code, run:",
|
|
4238
|
-
"",
|
|
4239
|
-
"```",
|
|
4240
|
-
'runtrim go "<describe the task>"',
|
|
4241
|
-
"```",
|
|
4242
|
-
"",
|
|
4243
|
-
"This creates a scoped contract, loads project memory, and generates the guarded prompt for your agent.",
|
|
4244
|
-
"",
|
|
4245
|
-
"## Using your agent",
|
|
4246
|
-
"",
|
|
4247
|
-
"Paste the guarded prompt into Claude Code, Codex, Cursor, or any other AI coding agent.",
|
|
4248
|
-
"The agent receives allowed scope, forbidden areas, stop rules, and verification requirements.",
|
|
4249
|
-
"",
|
|
4250
|
-
"## After edits",
|
|
4251
|
-
"",
|
|
4252
|
-
"Run:",
|
|
4253
|
-
"",
|
|
4254
|
-
"```",
|
|
4255
|
-
"runtrim finish",
|
|
4256
|
-
"```",
|
|
4257
|
-
"",
|
|
4258
|
-
"This checks changed files, detects drift, scores risk, and saves the run report.",
|
|
4259
|
-
"",
|
|
4260
|
-
"## If you are an AI coding agent",
|
|
4261
|
-
"",
|
|
4262
|
-
"1. Read `.runtrim/contracts/latest.md` before touching any file.",
|
|
4263
|
-
"2. Stay inside the allowed scope defined in the contract.",
|
|
4264
|
-
"3. Do not touch forbidden systems or unrelated files.",
|
|
4265
|
-
"4. Stop immediately if scope must expand beyond the contract.",
|
|
4266
|
-
"5. Do not read, write, or reference `.env` files or secrets.",
|
|
4267
|
-
"6. Do not refactor code outside the direct task.",
|
|
4268
|
-
"7. After editing, tell the user to run: `runtrim finish`",
|
|
4269
|
-
"",
|
|
4270
|
-
"## Active contract",
|
|
4271
|
-
"",
|
|
4272
|
-
"If `.runtrim/contracts/latest.md` exists, it contains the active task contract.",
|
|
4273
|
-
"Follow it exactly.",
|
|
4274
|
-
"",
|
|
4275
|
-
"---",
|
|
4276
|
-
`Generated by RunTrim. Updated: ${now}`
|
|
4277
|
-
].join("\n");
|
|
4278
|
-
fs10.writeFileSync(runtrimMdPath, runtrimMd, "utf-8");
|
|
4296
|
+
writeCanonicalRuntrimMd(cwd, baseline.projectName);
|
|
4279
4297
|
const projectJsonPath = path10.join(configDir, "project.json");
|
|
4280
4298
|
const projectJsonExists = fs10.existsSync(projectJsonPath);
|
|
4281
4299
|
const projectJson = {
|
|
@@ -4369,8 +4387,9 @@ function installProtocol(cwd, baseline, opts = {}) {
|
|
|
4369
4387
|
for (const filename of agentTargets) {
|
|
4370
4388
|
const filePath = path10.join(cwd, filename);
|
|
4371
4389
|
if (fs10.existsSync(filePath)) {
|
|
4390
|
+
removeBridgeBlock(filePath);
|
|
4372
4391
|
const result = upsertProtocolBlock(filePath);
|
|
4373
|
-
|
|
4392
|
+
agentResults.push({ file: filename, result: result === "skipped" ? "unchanged" : result });
|
|
4374
4393
|
} else if (opts.agentFiles) {
|
|
4375
4394
|
createMinimalAgentPointerFile(filePath, filename);
|
|
4376
4395
|
agentResults.push({ file: filename, result: "created" });
|
|
@@ -4416,6 +4435,18 @@ function installProtocol(cwd, baseline, opts = {}) {
|
|
|
4416
4435
|
fs10.writeFileSync(mdcPath, cursorMdc, "utf-8");
|
|
4417
4436
|
cursorResult = existed ? "updated" : "created";
|
|
4418
4437
|
}
|
|
4438
|
+
const contractsDir = path10.join(configDir, "contracts");
|
|
4439
|
+
const latestContractPath = path10.join(contractsDir, "latest.md");
|
|
4440
|
+
const contractIsStale = fs10.existsSync(latestContractPath) && !fs10.readFileSync(latestContractPath, "utf-8").includes("Status: none");
|
|
4441
|
+
if (!fs10.existsSync(latestContractPath) || contractIsStale) {
|
|
4442
|
+
writeRestingContract(cwd);
|
|
4443
|
+
}
|
|
4444
|
+
const memoryDir = path10.join(configDir, "memory");
|
|
4445
|
+
const currentMemoryPath = path10.join(memoryDir, "current.md");
|
|
4446
|
+
const memoryIsStale = fs10.existsSync(currentMemoryPath) && !fs10.readFileSync(currentMemoryPath, "utf-8").includes("Status: none");
|
|
4447
|
+
if (!fs10.existsSync(currentMemoryPath) || memoryIsStale) {
|
|
4448
|
+
writeRestingMemory(cwd);
|
|
4449
|
+
}
|
|
4419
4450
|
return {
|
|
4420
4451
|
runtrimMd: runtrimMdExists ? "updated" : "created",
|
|
4421
4452
|
projectJson: projectJsonExists ? "updated" : "created",
|
|
@@ -5151,7 +5182,7 @@ program.command("prepare <task>").description("Prepare a guarded prompt without
|
|
|
5151
5182
|
}
|
|
5152
5183
|
);
|
|
5153
5184
|
program.command("go <task>").description("Bridge Mode: generate a scoped contract, write protocol files, and prepare the guarded prompt").option("--no-clipboard", "Print prompt to terminal instead of copying to clipboard").option("--no-sync", "Skip cloud sync even if a CLI token is configured").option("--no-bridge", "Skip bridge file writing (RUNTRIM.md, contracts, memory)").option("--print", "Always print the prompt to terminal in addition to copying").option("--monitor", "Open local panel monitor in the background (best effort)").action(async (task, options) => {
|
|
5154
|
-
var _a2, _b, _c, _d, _e
|
|
5185
|
+
var _a2, _b, _c, _d, _e;
|
|
5155
5186
|
const cwd = process.cwd();
|
|
5156
5187
|
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
5157
5188
|
if (!allowed) return;
|
|
@@ -5258,33 +5289,19 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
|
|
|
5258
5289
|
const copied = doCopy ? await copyToClipboardSafe(fullPrompt) : false;
|
|
5259
5290
|
if (options.monitor) void tryLaunchPanelMonitorDetached(cwd);
|
|
5260
5291
|
updateRun(run.id, { bridgeManagedFiles: bridgeManagedPaths }, cwd);
|
|
5261
|
-
let
|
|
5292
|
+
let cloudSync = { status: "skipped_no_token" };
|
|
5262
5293
|
if (options.sync !== false) {
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
projectAudit: projectAudit != null ? projectAudit : null,
|
|
5273
|
-
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
5274
|
-
runs: freshRuns
|
|
5275
|
-
});
|
|
5276
|
-
const apiBase2 = resolveApiBase(config);
|
|
5277
|
-
const r = await fetch(`${apiBase2}/api/sync`, {
|
|
5278
|
-
method: "POST",
|
|
5279
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken2}` },
|
|
5280
|
-
body: JSON.stringify(payload)
|
|
5281
|
-
});
|
|
5282
|
-
synced = r.ok;
|
|
5283
|
-
} catch (e) {
|
|
5284
|
-
}
|
|
5285
|
-
}
|
|
5294
|
+
cloudSync = await syncRunsToCloud({
|
|
5295
|
+
cwd,
|
|
5296
|
+
config,
|
|
5297
|
+
projectName,
|
|
5298
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
5299
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
5300
|
+
runs: loadAllRuns(cwd),
|
|
5301
|
+
markPendingRunIds: [run.id]
|
|
5302
|
+
});
|
|
5286
5303
|
}
|
|
5287
|
-
const riskColor = (
|
|
5304
|
+
const riskColor = (_e = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _e : chalk.white;
|
|
5288
5305
|
console.log("");
|
|
5289
5306
|
console.log(GO_ACCENT.bold("RunTrim go"));
|
|
5290
5307
|
console.log("");
|
|
@@ -5314,7 +5331,15 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
|
|
|
5314
5331
|
}
|
|
5315
5332
|
if (options.sync !== false) {
|
|
5316
5333
|
console.log(GO_ACCENT.bold("Cloud sync"));
|
|
5317
|
-
|
|
5334
|
+
if (cloudSync.status === "synced") {
|
|
5335
|
+
console.log(chalk.white(" Started run synced."));
|
|
5336
|
+
} else if (cloudSync.status === "failed") {
|
|
5337
|
+
console.log(chalk.yellow(" Failed. Run saved locally. Use runtrim sync later."));
|
|
5338
|
+
} else if (cloudSync.status === "skipped_no_token" || cloudSync.status === "skipped_invalid_token") {
|
|
5339
|
+
console.log(DIM(" Skipped. Run runtrim login to connect your dashboard."));
|
|
5340
|
+
} else {
|
|
5341
|
+
console.log(DIM(" Skipped."));
|
|
5342
|
+
}
|
|
5318
5343
|
console.log("");
|
|
5319
5344
|
}
|
|
5320
5345
|
console.log(GO_ACCENT.bold("Prompt"));
|
|
@@ -6235,6 +6260,54 @@ function resolveApiBase(config) {
|
|
|
6235
6260
|
return "https://www.runtrim.com";
|
|
6236
6261
|
}
|
|
6237
6262
|
}
|
|
6263
|
+
async function syncRunsToCloud(input) {
|
|
6264
|
+
var _a2, _b, _c;
|
|
6265
|
+
const { cwd, config, projectName, projectAudit, memoryMarkdown, runs, markPendingRunIds } = input;
|
|
6266
|
+
const globalAuth = loadGlobalAuth();
|
|
6267
|
+
const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
|
|
6268
|
+
if (!rawToken) return { status: "skipped_no_token" };
|
|
6269
|
+
if (!rawToken.startsWith("rt_live_")) return { status: "skipped_invalid_token" };
|
|
6270
|
+
if (runs.length === 0) return { status: "skipped_no_runs" };
|
|
6271
|
+
try {
|
|
6272
|
+
const payload = buildSyncPayload({
|
|
6273
|
+
cwd,
|
|
6274
|
+
projectName,
|
|
6275
|
+
config,
|
|
6276
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6277
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6278
|
+
runs
|
|
6279
|
+
});
|
|
6280
|
+
const apiBase = resolveApiBase(config);
|
|
6281
|
+
const res = await fetch(`${apiBase}/api/sync`, {
|
|
6282
|
+
method: "POST",
|
|
6283
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
|
|
6284
|
+
body: JSON.stringify(payload)
|
|
6285
|
+
});
|
|
6286
|
+
const body = await res.json().catch(() => ({}));
|
|
6287
|
+
if (!res.ok || !body.ok) {
|
|
6288
|
+
if (markPendingRunIds && markPendingRunIds.length > 0) {
|
|
6289
|
+
for (const id of markPendingRunIds) updateRun(id, { pendingSync: true }, cwd);
|
|
6290
|
+
}
|
|
6291
|
+
return {
|
|
6292
|
+
status: "failed",
|
|
6293
|
+
error: body.error,
|
|
6294
|
+
details: body.details
|
|
6295
|
+
};
|
|
6296
|
+
}
|
|
6297
|
+
for (const run of runs) {
|
|
6298
|
+
if (run.pendingSync) updateRun(run.id, { pendingSync: false }, cwd);
|
|
6299
|
+
}
|
|
6300
|
+
return {
|
|
6301
|
+
status: "synced",
|
|
6302
|
+
syncedRuns: (_c = body.syncedRuns) != null ? _c : payload.runs.length
|
|
6303
|
+
};
|
|
6304
|
+
} catch (e) {
|
|
6305
|
+
if (markPendingRunIds && markPendingRunIds.length > 0) {
|
|
6306
|
+
for (const id of markPendingRunIds) updateRun(id, { pendingSync: true }, cwd);
|
|
6307
|
+
}
|
|
6308
|
+
return { status: "failed" };
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6238
6311
|
program.command("login").description("Connect this machine to your RunTrim cloud account").option("--token <token>", "CLI token (skip interactive prompt)").action(async (opts) => {
|
|
6239
6312
|
var _a2, _b, _c, _d, _e;
|
|
6240
6313
|
console.log("");
|
|
@@ -6306,7 +6379,7 @@ program.command("login").description("Connect this machine to your RunTrim cloud
|
|
|
6306
6379
|
console.log("");
|
|
6307
6380
|
});
|
|
6308
6381
|
program.command("finish").description("Bridge Mode: evaluate agent output, check scope, mark run completed, and sync").option("--no-sync", "Skip cloud sync even if a CLI token is configured").action(async (options) => {
|
|
6309
|
-
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m
|
|
6382
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
6310
6383
|
const cwd = process.cwd();
|
|
6311
6384
|
console.log("");
|
|
6312
6385
|
console.log(GO_ACCENT.bold("RunTrim finish"));
|
|
@@ -6411,41 +6484,36 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
6411
6484
|
const freshRuns = loadAllRuns(cwd);
|
|
6412
6485
|
const updatedRun = (_k = freshRuns.find((r) => r.id === activeRun.id)) != null ? _k : activeRun;
|
|
6413
6486
|
writeMemoryFromRuns(updatedRun, freshRuns, config, cwd);
|
|
6414
|
-
|
|
6487
|
+
archiveContract(cwd, activeRun.id);
|
|
6488
|
+
archiveMemory(cwd, activeRun.id);
|
|
6489
|
+
writeCanonicalRuntrimMd(cwd, projectName);
|
|
6490
|
+
writeRestingContract(cwd);
|
|
6491
|
+
writeRestingMemory(cwd);
|
|
6492
|
+
const bridgeRemovals = [];
|
|
6493
|
+
if (removeBridgeBlock(path10.join(cwd, "CLAUDE.md"))) bridgeRemovals.push("CLAUDE.md");
|
|
6494
|
+
if (removeBridgeBlock(path10.join(cwd, "AGENTS.md"))) bridgeRemovals.push("AGENTS.md");
|
|
6495
|
+
let cloudSync = { status: "skipped_no_token" };
|
|
6415
6496
|
if (options.sync !== false) {
|
|
6416
|
-
const
|
|
6417
|
-
const rawToken = (_m = (_l = globalAuth == null ? void 0 : globalAuth.token) != null ? _l : config.syncToken) != null ? _m : null;
|
|
6418
|
-
if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
|
|
6497
|
+
const memoryMarkdown = (() => {
|
|
6419
6498
|
try {
|
|
6420
|
-
|
|
6421
|
-
try {
|
|
6422
|
-
return readMemory(cwd);
|
|
6423
|
-
} catch (e) {
|
|
6424
|
-
return null;
|
|
6425
|
-
}
|
|
6426
|
-
})();
|
|
6427
|
-
const payload = buildSyncPayload({
|
|
6428
|
-
cwd,
|
|
6429
|
-
projectName,
|
|
6430
|
-
config,
|
|
6431
|
-
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6432
|
-
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6433
|
-
runs: freshRuns
|
|
6434
|
-
});
|
|
6435
|
-
const apiBase = resolveApiBase(config);
|
|
6436
|
-
const r = await fetch(`${apiBase}/api/sync`, {
|
|
6437
|
-
method: "POST",
|
|
6438
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
|
|
6439
|
-
body: JSON.stringify(payload)
|
|
6440
|
-
});
|
|
6441
|
-
synced = r.ok;
|
|
6499
|
+
return readMemory(cwd);
|
|
6442
6500
|
} catch (e) {
|
|
6501
|
+
return null;
|
|
6443
6502
|
}
|
|
6444
|
-
}
|
|
6503
|
+
})();
|
|
6504
|
+
cloudSync = await syncRunsToCloud({
|
|
6505
|
+
cwd,
|
|
6506
|
+
config,
|
|
6507
|
+
projectName,
|
|
6508
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6509
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6510
|
+
runs: freshRuns,
|
|
6511
|
+
markPendingRunIds: [activeRun.id]
|
|
6512
|
+
});
|
|
6445
6513
|
}
|
|
6446
6514
|
const scopeColor = scopeDriftStatus === "passed" ? chalk.green : scopeDriftStatus === "forbidden_touched" ? chalk.red : chalk.yellow;
|
|
6447
|
-
const riskAfter = (
|
|
6448
|
-
const riskColor = (
|
|
6515
|
+
const riskAfter = (_l = activeRun.contract.wasteRiskAfter) != null ? _l : "medium";
|
|
6516
|
+
const riskColor = (_m = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[riskAfter]) != null ? _m : chalk.white;
|
|
6449
6517
|
console.log(GO_ACCENT.bold("Run"));
|
|
6450
6518
|
console.log(chalk.white(" " + truncate(activeRun.task, 70)));
|
|
6451
6519
|
console.log("");
|
|
@@ -6498,31 +6566,35 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
6498
6566
|
}
|
|
6499
6567
|
if (options.sync !== false) {
|
|
6500
6568
|
console.log(GO_ACCENT.bold("Cloud sync"));
|
|
6501
|
-
|
|
6569
|
+
if (cloudSync.status === "synced") {
|
|
6570
|
+
console.log(chalk.white(" Completed run synced."));
|
|
6571
|
+
} else if (cloudSync.status === "failed") {
|
|
6572
|
+
console.log(chalk.yellow(" Failed. Run saved locally. Use runtrim sync later."));
|
|
6573
|
+
} else if (cloudSync.status === "skipped_no_token" || cloudSync.status === "skipped_invalid_token") {
|
|
6574
|
+
console.log(DIM(" Skipped. Run runtrim login to connect your dashboard."));
|
|
6575
|
+
} else {
|
|
6576
|
+
console.log(DIM(" Skipped."));
|
|
6577
|
+
}
|
|
6502
6578
|
console.log("");
|
|
6503
6579
|
}
|
|
6580
|
+
console.log(GO_ACCENT.bold("Protocol"));
|
|
6581
|
+
console.log(DIM(" ") + chalk.white("RUNTRIM.md restored"));
|
|
6582
|
+
console.log(DIM(" ") + chalk.white("latest contract archived"));
|
|
6583
|
+
console.log(DIM(" ") + chalk.white("current memory reset"));
|
|
6584
|
+
if (bridgeRemovals.length > 0) {
|
|
6585
|
+
for (const f of bridgeRemovals) {
|
|
6586
|
+
console.log(DIM(" ") + chalk.white(`${f} bridge block removed`));
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
console.log("");
|
|
6504
6590
|
});
|
|
6505
6591
|
program.command("sync").description("Sync local run history and project memory to your RunTrim dashboard").option("--dry-run", "Show what would be synced without uploading").action(async (opts) => {
|
|
6506
|
-
var _a2, _b
|
|
6592
|
+
var _a2, _b;
|
|
6507
6593
|
const cwd = process.cwd();
|
|
6508
6594
|
console.log("");
|
|
6509
6595
|
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
6510
6596
|
console.log("");
|
|
6511
|
-
const globalAuth = loadGlobalAuth();
|
|
6512
6597
|
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
6513
|
-
const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
|
|
6514
|
-
if (!rawToken) {
|
|
6515
|
-
console.log(chalk.yellow(" No CLI token found."));
|
|
6516
|
-
console.log(DIM(" Run ") + GO_ACCENT("runtrim login") + DIM(" to connect cloud sync."));
|
|
6517
|
-
console.log(DIM(" Local CLI still works without a token."));
|
|
6518
|
-
console.log("");
|
|
6519
|
-
return;
|
|
6520
|
-
}
|
|
6521
|
-
if (!rawToken.startsWith("rt_live_")) {
|
|
6522
|
-
console.log(chalk.yellow(" Stored token format is invalid. Re-run: runtrim login"));
|
|
6523
|
-
console.log("");
|
|
6524
|
-
return;
|
|
6525
|
-
}
|
|
6526
6598
|
const apiBase = resolveApiBase(config);
|
|
6527
6599
|
const syncUrl = `${apiBase}/api/sync`;
|
|
6528
6600
|
const runs = loadAllRuns(cwd);
|
|
@@ -6533,6 +6605,7 @@ program.command("sync").description("Sync local run history and project memory t
|
|
|
6533
6605
|
return;
|
|
6534
6606
|
}
|
|
6535
6607
|
const projectAudit = loadProjectAudit(cwd);
|
|
6608
|
+
const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
|
|
6536
6609
|
const memoryMarkdown = (() => {
|
|
6537
6610
|
try {
|
|
6538
6611
|
return readMemory(cwd);
|
|
@@ -6542,7 +6615,7 @@ program.command("sync").description("Sync local run history and project memory t
|
|
|
6542
6615
|
})();
|
|
6543
6616
|
const payload = buildSyncPayload({
|
|
6544
6617
|
cwd,
|
|
6545
|
-
projectName
|
|
6618
|
+
projectName,
|
|
6546
6619
|
config,
|
|
6547
6620
|
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6548
6621
|
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
@@ -6553,41 +6626,46 @@ program.command("sync").description("Sync local run history and project memory t
|
|
|
6553
6626
|
console.log(DIM(" API ") + chalk.white(syncUrl));
|
|
6554
6627
|
console.log("");
|
|
6555
6628
|
if (opts.dryRun) {
|
|
6556
|
-
console.log(ACCENT.bold(" Dry run \
|
|
6629
|
+
console.log(ACCENT.bold(" Dry run \uFFFD nothing uploaded."));
|
|
6557
6630
|
console.log("");
|
|
6558
6631
|
return;
|
|
6559
6632
|
}
|
|
6560
6633
|
const spinner = oraFactory({ text: " Syncing...", color: "blue" }).start();
|
|
6561
6634
|
try {
|
|
6562
|
-
const
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6635
|
+
const result = await syncRunsToCloud({
|
|
6636
|
+
cwd,
|
|
6637
|
+
config,
|
|
6638
|
+
projectName,
|
|
6639
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6640
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6641
|
+
runs
|
|
6569
6642
|
});
|
|
6570
|
-
|
|
6571
|
-
if (!res.ok || !body.ok) {
|
|
6643
|
+
if (result.status !== "synced") {
|
|
6572
6644
|
spinner.fail(" Sync failed.");
|
|
6573
6645
|
console.log("");
|
|
6574
|
-
if (
|
|
6575
|
-
console.log(chalk.
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6646
|
+
if (result.status === "skipped_no_token") {
|
|
6647
|
+
console.log(chalk.yellow(" No CLI token found."));
|
|
6648
|
+
console.log(DIM(" Run ") + GO_ACCENT("runtrim login") + DIM(" to connect cloud sync."));
|
|
6649
|
+
console.log(DIM(" Local CLI still works without a token."));
|
|
6650
|
+
} else if (result.status === "skipped_invalid_token") {
|
|
6651
|
+
console.log(chalk.yellow(" Stored token format is invalid. Re-run: runtrim login"));
|
|
6652
|
+
} else if (result.error) {
|
|
6653
|
+
console.log(chalk.red(" Error: ") + chalk.white(result.error));
|
|
6654
|
+
if (result.details) console.log(chalk.red(" Details: ") + chalk.white(result.details));
|
|
6655
|
+
} else {
|
|
6656
|
+
console.log(chalk.yellow(" Failed. Run saved locally. Use runtrim sync later."));
|
|
6579
6657
|
}
|
|
6580
6658
|
console.log("");
|
|
6581
6659
|
return;
|
|
6582
6660
|
}
|
|
6583
6661
|
spinner.succeed(" Sync complete.");
|
|
6584
6662
|
console.log("");
|
|
6585
|
-
|
|
6586
|
-
console.log(
|
|
6663
|
+
const syncedRuns = (_b = result.syncedRuns) != null ? _b : payload.runs.length;
|
|
6664
|
+
console.log(ACCENT.bold(" Synced") + chalk.white(` ${syncedRuns} run${syncedRuns === 1 ? "" : "s"}`));
|
|
6587
6665
|
console.log("");
|
|
6588
6666
|
console.log(DIM(" View at ") + GO_ACCENT(`${apiBase}/app`));
|
|
6589
6667
|
console.log("");
|
|
6590
|
-
} catch (
|
|
6668
|
+
} catch (e) {
|
|
6591
6669
|
spinner.fail(" Network error. Check your connection.");
|
|
6592
6670
|
console.log("");
|
|
6593
6671
|
}
|