runtrim 0.1.9 → 0.1.10

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.
Files changed (2) hide show
  1. package/dist-cli/runtrim.js +171 -122
  2. package/package.json +1 -1
@@ -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 stopConditions = [
1971
- ...(_c = c.stopRules) != null ? _c : [],
1972
- ...((_d = c.whenToAsk) != null ? _d : []).map((s) => `Stop if: ${s}`)
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 if a forbidden area needs to be touched.",
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 these files first:",
2071
- "- RUNTRIM.md",
2072
- "- .runtrim/contracts/latest.md",
2073
- "- .runtrim/memory/current.md",
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
- "- If a change requires touching a forbidden area, STOP and tell the user.",
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 Bridge. Do not edit manually."
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 if a forbidden area must be touched.";
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
- " - If scope must expand, stop immediately and tell the user.",
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
- const runtrimMd = [
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
- if (result !== "skipped") agentResults.push({ file: filename, result });
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",
@@ -6411,6 +6442,14 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
6411
6442
  const freshRuns = loadAllRuns(cwd);
6412
6443
  const updatedRun = (_k = freshRuns.find((r) => r.id === activeRun.id)) != null ? _k : activeRun;
6413
6444
  writeMemoryFromRuns(updatedRun, freshRuns, config, cwd);
6445
+ archiveContract(cwd, activeRun.id);
6446
+ archiveMemory(cwd, activeRun.id);
6447
+ writeCanonicalRuntrimMd(cwd, projectName);
6448
+ writeRestingContract(cwd);
6449
+ writeRestingMemory(cwd);
6450
+ const bridgeRemovals = [];
6451
+ if (removeBridgeBlock(path10.join(cwd, "CLAUDE.md"))) bridgeRemovals.push("CLAUDE.md");
6452
+ if (removeBridgeBlock(path10.join(cwd, "AGENTS.md"))) bridgeRemovals.push("AGENTS.md");
6414
6453
  let synced = false;
6415
6454
  if (options.sync !== false) {
6416
6455
  const globalAuth = loadGlobalAuth();
@@ -6501,6 +6540,16 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
6501
6540
  console.log(DIM(" ") + (synced ? chalk.white("Completed.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
6502
6541
  console.log("");
6503
6542
  }
6543
+ console.log(GO_ACCENT.bold("Protocol"));
6544
+ console.log(DIM(" ") + chalk.white("RUNTRIM.md restored"));
6545
+ console.log(DIM(" ") + chalk.white("latest contract archived"));
6546
+ console.log(DIM(" ") + chalk.white("current memory reset"));
6547
+ if (bridgeRemovals.length > 0) {
6548
+ for (const f of bridgeRemovals) {
6549
+ console.log(DIM(" ") + chalk.white(`${f} bridge block removed`));
6550
+ }
6551
+ }
6552
+ console.log("");
6504
6553
  });
6505
6554
  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
6555
  var _a2, _b, _c, _d, _e, _f;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtrim",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "CLI guard layer that scopes AI coding runs before they waste tokens.",
5
5
  "license": "MIT",
6
6
  "author": "RunTrim",