runtrim 0.1.8 → 0.1.9

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 +382 -33
  2. package/package.json +1 -1
@@ -4165,13 +4165,274 @@ program.command("start").description("Guided RunTrim onboarding and daily loop")
4165
4165
  console.log("");
4166
4166
  }
4167
4167
  });
4168
- program.command("init").description("Initialize RunTrim in the current project").option("--refresh", "Refresh baseline audit/rules/memory without overwriting config").action(async (options) => {
4168
+ var PROTOCOL_BLOCK_START = "<!-- RUNTRIM_PROTOCOL_START -->";
4169
+ var PROTOCOL_BLOCK_END = "<!-- RUNTRIM_PROTOCOL_END -->";
4170
+ var PROTOCOL_POINTER_BLOCK = `
4171
+ ${PROTOCOL_BLOCK_START}
4172
+ This repo uses RunTrim as the guarded AI coding protocol.
4173
+ Before editing code, read RUNTRIM.md.
4174
+ Start every task with: runtrim go "<task>"
4175
+ Stay inside .runtrim/contracts/latest.md.
4176
+ After edits, ask the user to run: runtrim finish
4177
+ ${PROTOCOL_BLOCK_END}
4178
+ `;
4179
+ function upsertProtocolBlock(filePath) {
4180
+ if (!fs10.existsSync(filePath)) return "skipped";
4181
+ const content = fs10.readFileSync(filePath, "utf-8");
4182
+ const startIdx = content.indexOf(PROTOCOL_BLOCK_START);
4183
+ const endIdx = content.indexOf(PROTOCOL_BLOCK_END);
4184
+ if (startIdx !== -1 && endIdx !== -1) {
4185
+ const before = content.slice(0, startIdx);
4186
+ const after = content.slice(endIdx + PROTOCOL_BLOCK_END.length);
4187
+ const newContent = before + PROTOCOL_POINTER_BLOCK.trimStart() + after.replace(/^\n/, "");
4188
+ if (newContent === content) return "unchanged";
4189
+ fs10.writeFileSync(filePath, newContent, "utf-8");
4190
+ return "updated";
4191
+ }
4192
+ fs10.writeFileSync(filePath, content.trimEnd() + "\n" + PROTOCOL_POINTER_BLOCK, "utf-8");
4193
+ return "updated";
4194
+ }
4195
+ function createMinimalAgentPointerFile(filePath, filename) {
4196
+ const label = filename === "CLAUDE.md" ? "Claude Code" : "AI agents";
4197
+ const content = [
4198
+ `# ${label} Instructions`,
4199
+ "",
4200
+ "This repo uses RunTrim as the guarded AI coding protocol.",
4201
+ "Read RUNTRIM.md before editing any code.",
4202
+ "",
4203
+ PROTOCOL_POINTER_BLOCK.trim(),
4204
+ ""
4205
+ ].join("\n");
4206
+ fs10.writeFileSync(filePath, content, "utf-8");
4207
+ }
4208
+ function installProtocol(cwd, baseline, opts = {}) {
4209
+ var _a2, _b, _c, _d, _e, _f, _g;
4210
+ const configDir = getConfigDir(cwd);
4211
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4212
+ const extraFolders = [
4213
+ path10.join(configDir, "contracts"),
4214
+ path10.join(configDir, "memory"),
4215
+ path10.join(configDir, "bridge"),
4216
+ path10.join(configDir, "reports")
4217
+ ];
4218
+ const createdFolders = [];
4219
+ for (const dir of extraFolders) {
4220
+ if (!fs10.existsSync(dir)) {
4221
+ fs10.mkdirSync(dir, { recursive: true });
4222
+ createdFolders.push(dir.replace(cwd + path10.sep, "").replace(/\\/g, "/"));
4223
+ }
4224
+ }
4225
+ const scripts = (_a2 = baseline.scripts) != null ? _a2 : {};
4226
+ const buildCmd = (_d = (_c = (_b = scripts["build"]) != null ? _b : scripts["build:web"]) != null ? _c : scripts["build:all"]) != null ? _d : null;
4227
+ const testCmd = (_f = (_e = scripts["test"]) != null ? _e : scripts["test:run"]) != null ? _f : null;
4228
+ const runtrimMdPath = path10.join(cwd, "RUNTRIM.md");
4229
+ 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");
4279
+ const projectJsonPath = path10.join(configDir, "project.json");
4280
+ const projectJsonExists = fs10.existsSync(projectJsonPath);
4281
+ const projectJson = {
4282
+ name: baseline.projectName,
4283
+ stack: baseline.detectedStack,
4284
+ packageManager: baseline.packageManager,
4285
+ buildCommand: buildCmd,
4286
+ testCommand: testCmd,
4287
+ detectedAt: now
4288
+ };
4289
+ fs10.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2), "utf-8");
4290
+ const policiesPath = path10.join(configDir, "policies.json");
4291
+ const policiesJsonExists = fs10.existsSync(policiesPath);
4292
+ const detectedSensitive = ((_g = baseline.riskSurfaces) != null ? _g : []).map((s) => s.type.toLowerCase());
4293
+ const policies = {
4294
+ version: 1,
4295
+ protected: [
4296
+ ".env*",
4297
+ "secrets",
4298
+ "*.key",
4299
+ "*.pem",
4300
+ "auth/**",
4301
+ "middleware.ts",
4302
+ "prisma/schema.prisma",
4303
+ "prisma/migrations/**",
4304
+ "database/migrations/**",
4305
+ "stripe/**",
4306
+ "billing/**",
4307
+ "payment/**",
4308
+ "webhooks/**",
4309
+ "package-lock.json",
4310
+ "pnpm-lock.yaml",
4311
+ "yarn.lock",
4312
+ ".next/**",
4313
+ "dist/**",
4314
+ "build/**",
4315
+ "node_modules/**"
4316
+ ],
4317
+ sensitive: [
4318
+ "auth",
4319
+ "billing",
4320
+ "payment",
4321
+ "middleware",
4322
+ "database",
4323
+ "schema",
4324
+ "migrations",
4325
+ "env",
4326
+ "secrets",
4327
+ "webhooks",
4328
+ ...detectedSensitive.filter((s) => !["auth", "billing", "payment", "middleware", "database", "env", "secrets", "webhooks"].includes(s))
4329
+ ],
4330
+ note: "These areas require explicit task scope before any agent edits.",
4331
+ updatedAt: now
4332
+ };
4333
+ fs10.writeFileSync(policiesPath, JSON.stringify(policies, null, 2), "utf-8");
4334
+ const baselineMdPath = path10.join(configDir, "memory", "baseline.md");
4335
+ const baselineMdExists = fs10.existsSync(baselineMdPath);
4336
+ const protectedList = policies.protected.slice(0, 10).map((p) => `- ${p}`).join("\n");
4337
+ const stackLine = baseline.detectedStack.join(", ") || "unknown";
4338
+ const baselineMd = [
4339
+ "# RunTrim Memory \u2014 Baseline",
4340
+ "",
4341
+ `Project: ${baseline.projectName}`,
4342
+ `Stack: ${stackLine}`,
4343
+ `Package manager: ${baseline.packageManager}`,
4344
+ ...buildCmd ? [`Build: ${buildCmd}`] : [],
4345
+ ...testCmd ? [`Test: ${testCmd}`] : [],
4346
+ "",
4347
+ "## Protected areas",
4348
+ "",
4349
+ protectedList,
4350
+ "",
4351
+ "## Project rules",
4352
+ "",
4353
+ '- Start every AI task with: runtrim go "<task>"',
4354
+ "- Stay inside the scoped contract.",
4355
+ "- Run runtrim finish after agent edits.",
4356
+ "- No unrelated refactors during a task.",
4357
+ "- Never touch .env files.",
4358
+ "",
4359
+ "## Prior agent decisions",
4360
+ "",
4361
+ "No prior runs recorded. This is the baseline for this project.",
4362
+ "",
4363
+ "---",
4364
+ `Created by runtrim init. Updated: ${now}`
4365
+ ].join("\n");
4366
+ fs10.writeFileSync(baselineMdPath, baselineMd, "utf-8");
4367
+ const agentResults = [];
4368
+ const agentTargets = ["CLAUDE.md", "AGENTS.md"];
4369
+ for (const filename of agentTargets) {
4370
+ const filePath = path10.join(cwd, filename);
4371
+ if (fs10.existsSync(filePath)) {
4372
+ const result = upsertProtocolBlock(filePath);
4373
+ if (result !== "skipped") agentResults.push({ file: filename, result });
4374
+ } else if (opts.agentFiles) {
4375
+ createMinimalAgentPointerFile(filePath, filename);
4376
+ agentResults.push({ file: filename, result: "created" });
4377
+ } else {
4378
+ agentResults.push({ file: filename, result: "skipped" });
4379
+ }
4380
+ }
4381
+ let cursorResult = "skipped";
4382
+ const cursorDir = path10.join(cwd, ".cursor");
4383
+ const cursorExists = fs10.existsSync(cursorDir);
4384
+ if (opts.cursor || cursorExists) {
4385
+ const rulesDir = path10.join(cursorDir, "rules");
4386
+ const mdcPath = path10.join(rulesDir, "runtrim.mdc");
4387
+ if (!fs10.existsSync(rulesDir)) fs10.mkdirSync(rulesDir, { recursive: true });
4388
+ const existed = fs10.existsSync(mdcPath);
4389
+ const cursorMdc = [
4390
+ "---",
4391
+ "description: RunTrim guarded AI coding protocol",
4392
+ "alwaysApply: true",
4393
+ "---",
4394
+ "",
4395
+ "# RunTrim Protocol",
4396
+ "",
4397
+ "This repo uses RunTrim as the guarded AI coding control layer.",
4398
+ "",
4399
+ "## Before editing any code",
4400
+ "",
4401
+ "1. Read `RUNTRIM.md` in the repo root.",
4402
+ "2. If `.runtrim/contracts/latest.md` exists, read the active contract.",
4403
+ "",
4404
+ "## Rules",
4405
+ "",
4406
+ "- Stay inside the allowed scope defined in the contract.",
4407
+ "- Do not touch forbidden files or unrelated systems.",
4408
+ "- Stop immediately if scope must expand.",
4409
+ "- Do not read or write `.env` files.",
4410
+ "- Do not refactor outside the task scope.",
4411
+ "",
4412
+ "## After editing",
4413
+ "",
4414
+ "Tell the user to run: `runtrim finish`"
4415
+ ].join("\n");
4416
+ fs10.writeFileSync(mdcPath, cursorMdc, "utf-8");
4417
+ cursorResult = existed ? "updated" : "created";
4418
+ }
4419
+ return {
4420
+ runtrimMd: runtrimMdExists ? "updated" : "created",
4421
+ projectJson: projectJsonExists ? "updated" : "created",
4422
+ policiesJson: policiesJsonExists ? "updated" : "created",
4423
+ baselineMd: baselineMdExists ? "updated" : "created",
4424
+ folders: createdFolders,
4425
+ agentFiles: agentResults,
4426
+ cursorRules: cursorResult
4427
+ };
4428
+ }
4429
+ program.command("init").description("Install the RunTrim protocol in the current project").option("--refresh", "Refresh baseline audit, rules, and memory without overwriting config").option("--agent-files", "Create CLAUDE.md and AGENTS.md if missing, with RunTrim pointer").option("--cursor", "Create .cursor/rules/runtrim.mdc Cursor agent instructions").action(async (options) => {
4169
4430
  var _a2;
4170
4431
  const cwd = process.cwd();
4171
4432
  const allowed = await ensureRepoAllowedForFree(cwd);
4172
4433
  if (!allowed) return;
4173
4434
  console.log("");
4174
- console.log(BOLD("RunTrim") + DIM(" init"));
4435
+ console.log(GO_ACCENT.bold("RunTrim init"));
4175
4436
  console.log("");
4176
4437
  const initResult = await initializeRunTrim(cwd, {
4177
4438
  refresh: options.refresh,
@@ -4179,33 +4440,46 @@ program.command("init").description("Initialize RunTrim in the current project")
4179
4440
  });
4180
4441
  if (!initResult.ok) return;
4181
4442
  const baseline = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
4182
- const scriptNames = Object.keys(baseline.scripts);
4183
- const starterCreated = fs10.existsSync(path10.join(getConfigDir(cwd), "latest-prompt.md"));
4184
- console.log(ACCENT.bold(" RunTrim init"));
4185
- console.log("");
4186
- console.log(DIM(" Project detected"));
4187
- console.log(DIM(" Name ") + chalk.white(baseline.projectName));
4188
- console.log(DIM(" Stack ") + chalk.white(baseline.detectedStack.join(", ") || "unknown"));
4189
- console.log(DIM(" Package ") + chalk.white(baseline.packageManager));
4190
- console.log("");
4191
- console.log(DIM(" Scripts found"));
4192
- console.log(DIM(" ") + chalk.white(scriptNames.length ? scriptNames.join(", ") : "none"));
4443
+ const protocol = installProtocol(cwd, baseline, {
4444
+ agentFiles: options.agentFiles,
4445
+ cursor: options.cursor
4446
+ });
4447
+ const stackLine = baseline.detectedStack.length ? baseline.detectedStack.join(" + ") : "unknown stack";
4448
+ console.log(DIM(" Project"));
4449
+ console.log(chalk.white(" " + baseline.projectName));
4450
+ console.log(DIM(" " + stackLine));
4193
4451
  console.log("");
4194
- console.log(DIM(" Risk surfaces"));
4195
- for (const s of baseline.riskSurfaces.slice(0, 8)) {
4196
- console.log(DIM(" - ") + chalk.white(s.type));
4452
+ console.log(DIM(" Protocol"));
4453
+ const protocolFiles = [
4454
+ ["RUNTRIM.md", protocol.runtrimMd],
4455
+ [".runtrim/project.json", protocol.projectJson],
4456
+ [".runtrim/policies.json", protocol.policiesJson],
4457
+ [".runtrim/memory/baseline.md", protocol.baselineMd]
4458
+ ];
4459
+ for (const [file, result] of protocolFiles) {
4460
+ console.log(DIM(" ") + chalk.white(file.padEnd(34)) + DIM(result));
4197
4461
  }
4198
4462
  console.log("");
4199
- console.log(DIM(options.refresh ? " Files refreshed" : " Files created"));
4200
- console.log(DIM(" .runtrim/config.json"));
4201
- console.log(DIM(" .runtrim/project-audit.json"));
4202
- console.log(DIM(" .runtrim/rules.md"));
4203
- console.log(DIM(" .runtrim/memory.md"));
4204
- console.log(DIM(" .runtrim/runs/"));
4205
- if (starterCreated) console.log(DIM(" .runtrim/latest-prompt.md"));
4463
+ console.log(DIM(" Agent pointers"));
4464
+ for (const { file, result } of protocol.agentFiles) {
4465
+ const color = result === "skipped" ? DIM : chalk.white;
4466
+ console.log(DIM(" ") + color(file.padEnd(34)) + DIM(result));
4467
+ }
4468
+ if (protocol.cursorRules !== "skipped") {
4469
+ console.log(DIM(" ") + chalk.white(".cursor/rules/runtrim.mdc".padEnd(34)) + DIM(protocol.cursorRules));
4470
+ } else if (options.cursor) {
4471
+ console.log(DIM(" Cursor rules skipped (.cursor/ not found and --cursor not passed)"));
4472
+ }
4206
4473
  console.log("");
4474
+ if (protocol.folders.length > 0) {
4475
+ console.log(DIM(" Folders created"));
4476
+ for (const f of protocol.folders) {
4477
+ console.log(DIM(" " + f + "/"));
4478
+ }
4479
+ console.log("");
4480
+ }
4207
4481
  console.log(DIM(" Next"));
4208
- console.log(chalk.white(' runtrim prepare "describe your next AI coding task"'));
4482
+ console.log(chalk.white(' runtrim go "your first task"'));
4209
4483
  console.log("");
4210
4484
  });
4211
4485
  var agentCommand = program.command("agent").description("Show or configure local agent execution settings");
@@ -4877,7 +5151,7 @@ program.command("prepare <task>").description("Prepare a guarded prompt without
4877
5151
  }
4878
5152
  );
4879
5153
  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) => {
4880
- var _a2, _b, _c, _d;
5154
+ var _a2, _b, _c, _d, _e, _f, _g;
4881
5155
  const cwd = process.cwd();
4882
5156
  const allowed = await ensureRepoAllowedForFree(cwd);
4883
5157
  if (!allowed) return;
@@ -4886,6 +5160,38 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4886
5160
  if (!initResult.ok) return;
4887
5161
  }
4888
5162
  const config = loadConfig(cwd);
5163
+ const globalAuth = loadGlobalAuth();
5164
+ const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
5165
+ const apiBase = resolveApiBase(config);
5166
+ if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
5167
+ let serverResult = null;
5168
+ try {
5169
+ const res = await fetch(`${apiBase}/api/cli/usage/bridge-run`, {
5170
+ method: "POST",
5171
+ headers: { Authorization: `Bearer ${rawToken}` }
5172
+ });
5173
+ if (res.ok) {
5174
+ const body = await res.json();
5175
+ serverResult = {
5176
+ allowed: body.allowed,
5177
+ plan: body.plan,
5178
+ used: (_c = body.usage.bridgeRunsUsed) != null ? _c : 0,
5179
+ limit: body.usage.bridgeRunsLimit
5180
+ };
5181
+ }
5182
+ } catch (e) {
5183
+ }
5184
+ if (serverResult !== null && !serverResult.allowed) {
5185
+ printBridgeLimitMessage(serverResult.used);
5186
+ return;
5187
+ }
5188
+ } else {
5189
+ const check = checkAndIncrementLocalUsage();
5190
+ if (!check.allowed) {
5191
+ printBridgeLimitMessage(check.used);
5192
+ return;
5193
+ }
5194
+ }
4889
5195
  const auditSpinner = oraFactory({ text: " Auditing task...", color: "blue" }).start();
4890
5196
  await new Promise((r) => setTimeout(r, 180));
4891
5197
  const audit = auditTask(task, config, cwd);
@@ -4917,7 +5223,7 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4917
5223
  }
4918
5224
  const runs = loadAllRuns(cwd);
4919
5225
  const projectAudit = loadProjectAudit(cwd);
4920
- const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
5226
+ const projectName = (_d = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _d : path10.basename(cwd);
4921
5227
  const memoryMarkdown = (() => {
4922
5228
  try {
4923
5229
  return readMemory(cwd);
@@ -4954,9 +5260,9 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4954
5260
  updateRun(run.id, { bridgeManagedFiles: bridgeManagedPaths }, cwd);
4955
5261
  let synced = false;
4956
5262
  if (options.sync !== false) {
4957
- const globalAuth = loadGlobalAuth();
4958
- const rawToken = (_c = (_b = globalAuth == null ? void 0 : globalAuth.token) != null ? _b : config.syncToken) != null ? _c : null;
4959
- if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
5263
+ const globalAuth2 = loadGlobalAuth();
5264
+ const rawToken2 = (_f = (_e = globalAuth2 == null ? void 0 : globalAuth2.token) != null ? _e : config.syncToken) != null ? _f : null;
5265
+ if (rawToken2 == null ? void 0 : rawToken2.startsWith("rt_live_")) {
4960
5266
  try {
4961
5267
  const freshRuns = loadAllRuns(cwd);
4962
5268
  const payload = buildSyncPayload({
@@ -4967,10 +5273,10 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4967
5273
  memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
4968
5274
  runs: freshRuns
4969
5275
  });
4970
- const apiBase = resolveApiBase(config);
4971
- const r = await fetch(`${apiBase}/api/sync`, {
5276
+ const apiBase2 = resolveApiBase(config);
5277
+ const r = await fetch(`${apiBase2}/api/sync`, {
4972
5278
  method: "POST",
4973
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
5279
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken2}` },
4974
5280
  body: JSON.stringify(payload)
4975
5281
  });
4976
5282
  synced = r.ok;
@@ -4978,7 +5284,7 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4978
5284
  }
4979
5285
  }
4980
5286
  }
4981
- const riskColor = (_d = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _d : chalk.white;
5287
+ const riskColor = (_g = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _g : chalk.white;
4982
5288
  console.log("");
4983
5289
  console.log(GO_ACCENT.bold("RunTrim go"));
4984
5290
  console.log("");
@@ -5858,6 +6164,49 @@ var statusColors = {
5858
6164
  drift_detected: chalk.red,
5859
6165
  blocked: chalk.red
5860
6166
  };
6167
+ var GLOBAL_USAGE_FILE = path10.join(os3.homedir(), ".runtrim", "usage.json");
6168
+ var FREE_BRIDGE_LIMIT_LOCAL = 5;
6169
+ function currentUsagePeriod() {
6170
+ const d = /* @__PURE__ */ new Date();
6171
+ return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;
6172
+ }
6173
+ function loadLocalUsageRuns() {
6174
+ var _a2;
6175
+ try {
6176
+ const raw = JSON.parse(fs10.readFileSync(GLOBAL_USAGE_FILE, "utf-8"));
6177
+ return (_a2 = raw.bridgeRuns) != null ? _a2 : {};
6178
+ } catch (e) {
6179
+ return {};
6180
+ }
6181
+ }
6182
+ function saveLocalUsageRuns(bridgeRuns) {
6183
+ const dir = path10.dirname(GLOBAL_USAGE_FILE);
6184
+ if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
6185
+ fs10.writeFileSync(GLOBAL_USAGE_FILE, JSON.stringify({ bridgeRuns }, null, 2), "utf-8");
6186
+ }
6187
+ function checkAndIncrementLocalUsage() {
6188
+ var _a2;
6189
+ const period = currentUsagePeriod();
6190
+ const runs = loadLocalUsageRuns();
6191
+ const used = (_a2 = runs[period]) != null ? _a2 : 0;
6192
+ if (used >= FREE_BRIDGE_LIMIT_LOCAL) {
6193
+ return { allowed: false, used };
6194
+ }
6195
+ runs[period] = used + 1;
6196
+ saveLocalUsageRuns(runs);
6197
+ return { allowed: true, used: used + 1 };
6198
+ }
6199
+ function printBridgeLimitMessage(used) {
6200
+ console.log("");
6201
+ console.log(chalk.red.bold(" Free Bridge limit reached."));
6202
+ console.log("");
6203
+ console.log(chalk.white(` You have used ${used} local guarded run${used === 1 ? "" : "s"} this month.`));
6204
+ console.log(DIM(" Upgrade to Pro for unlimited Bridge Mode, cloud sync, project memory,"));
6205
+ console.log(DIM(" reports, and continuation history."));
6206
+ console.log("");
6207
+ console.log(chalk.white(" https://www.runtrim.com/pricing"));
6208
+ console.log("");
6209
+ }
5861
6210
  var GLOBAL_AUTH_DIR = path10.join(os3.homedir(), ".runtrim");
5862
6211
  var GLOBAL_AUTH_FILE = path10.join(GLOBAL_AUTH_DIR, "auth.json");
5863
6212
  function loadGlobalAuth() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtrim",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "CLI guard layer that scopes AI coding runs before they waste tokens.",
5
5
  "license": "MIT",
6
6
  "author": "RunTrim",