@fixprompt/cli 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.
Files changed (2) hide show
  1. package/dist/cli.js +105 -14
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -87,7 +87,7 @@ function detectContext() {
87
87
  }
88
88
 
89
89
  // src/connect.ts
90
- import { readFileSync, readdirSync, statSync } from "fs";
90
+ import { appendFileSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
91
91
  import { join } from "path";
92
92
  import { spawnSync } from "child_process";
93
93
  var DEFAULT_ENDPOINT = "https://geosloghub-production.up.railway.app";
@@ -255,6 +255,80 @@ function writeToProvider(envVar, token, provider, target, force, cwd) {
255
255
  return writeVercel(envVar, token, target);
256
256
  }
257
257
  }
258
+ function ensureGitignoreHas(cwd, pattern) {
259
+ const gi = join(cwd, ".gitignore");
260
+ let s = "";
261
+ try {
262
+ s = readFileSync(gi, "utf8");
263
+ } catch {
264
+ }
265
+ const lines = s.split(/\r?\n/);
266
+ if (lines.some((l) => l.trim() === pattern)) return false;
267
+ const sep = s.length === 0 || s.endsWith("\n") ? "" : "\n";
268
+ writeFileSync(gi, s + sep + "\n# FixLoop\n" + pattern + "\n");
269
+ return true;
270
+ }
271
+ function appendEnvLocal(cwd, token, projectId, projectSlug, endpoint) {
272
+ const envPath = join(cwd, ".env.local");
273
+ const lines = [
274
+ "",
275
+ "# FixLoop \u2014 read token for the local coding agent. Gitignored.",
276
+ `# Generated ${(/* @__PURE__ */ new Date()).toISOString()}`,
277
+ `# Manage / revoke: https://fixloop-dashboard.vercel.app/integrations/${projectId}`,
278
+ `FIXPROMPT_PROJECT_ID=${projectId}`
279
+ ];
280
+ if (projectSlug) lines.push(`FIXPROMPT_PROJECT_SLUG=${projectSlug}`);
281
+ lines.push(`FIXPROMPT_READ_TOKEN=${token}`);
282
+ lines.push(`FIXPROMPT_BROKER_URL=${endpoint}`);
283
+ lines.push("");
284
+ const block = lines.join("\n");
285
+ if (existsSync(envPath)) {
286
+ appendFileSync(envPath, block);
287
+ } else {
288
+ writeFileSync(envPath, block.replace(/^\n/, ""));
289
+ }
290
+ }
291
+ function appendClaudeMdGuidance(cwd, projectId, projectSlug) {
292
+ const claudePath = join(cwd, "CLAUDE.md");
293
+ const section = [
294
+ "",
295
+ "## FixLoop \u2014 production error logs (auto-fetch on bug reports)",
296
+ "",
297
+ 'This project ships errors to FixLoop. When the user reports a bug, crash, error, "X is broken", or pastes an error stack:',
298
+ "",
299
+ "1. Read the FixLoop env vars from `.env.local` (gitignored \u2014 never commit).",
300
+ "2. `curl` the broker for recent error lines.",
301
+ "3. Parse the response `entries[]` \u2014 each has `ts`, `level`, `message`, optional `attrs` + `stack`. Match against the user's symptom.",
302
+ "4. Propose a fix grounded in what the logs show. **Do not ask the user to paste log lines \u2014 fetch them yourself.**",
303
+ "",
304
+ "### Fetch logs",
305
+ "",
306
+ "```sh",
307
+ 'curl -sH "x-fixprompt-read-token: $FIXPROMPT_READ_TOKEN" \\',
308
+ ' "$FIXPROMPT_BROKER_URL/projects/$FIXPROMPT_PROJECT_ID/logs?since=24h&level=error,warn,fatal&limit=200"',
309
+ "```",
310
+ "",
311
+ "Query params: `since` (15m..7d), `level` (csv: debug,info,warn,error,fatal), `q` (substring), `limit` (1..500).",
312
+ "",
313
+ projectSlug ? `**Project**: \`${projectSlug}\`.` : "",
314
+ `Manage / revoke: https://fixloop-dashboard.vercel.app/integrations/${projectId}`,
315
+ ""
316
+ ].filter(Boolean).join("\n");
317
+ if (existsSync(claudePath)) {
318
+ const existing = readFileSync(claudePath, "utf8");
319
+ if (existing.includes("## FixLoop \u2014")) return;
320
+ appendFileSync(claudePath, section);
321
+ } else {
322
+ writeFileSync(claudePath, "# Project\n" + section);
323
+ }
324
+ }
325
+ function writeLocalDiscovery(opts) {
326
+ console.log(` \xB7 writing .env.local + CLAUDE.md guidance to ${opts.cwd}\u2026`);
327
+ const giChanged = ensureGitignoreHas(opts.cwd, ".env.local");
328
+ if (giChanged) console.log(" + appended .env.local to .gitignore");
329
+ appendEnvLocal(opts.cwd, opts.token, opts.projectId, opts.projectSlug, opts.endpoint);
330
+ appendClaudeMdGuidance(opts.cwd, opts.projectId, opts.projectSlug);
331
+ }
258
332
  async function connect(args) {
259
333
  const cwd = process.cwd();
260
334
  const scopeRaw = (args.flags.scope ?? "deploy").toLowerCase();
@@ -272,7 +346,7 @@ async function connect(args) {
272
346
  }
273
347
  const endpoint = (args.flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
274
348
  let projectId = args.flags["project-id"] ?? process.env.FIXPROMPT_PROJECT_ID ?? null;
275
- const projectSlug = args.flags["project-slug"] ?? null;
349
+ let projectSlug = args.flags["project-slug"] ?? null;
276
350
  if (!projectId && projectSlug) {
277
351
  const res = await fetch(`${endpoint}/admin/projects/by-slug/${encodeURIComponent(projectSlug)}`, {
278
352
  headers: { "x-internal-admin-token": adminToken }
@@ -284,6 +358,7 @@ async function connect(args) {
284
358
  }
285
359
  const parsed = JSON.parse(text);
286
360
  projectId = parsed.id;
361
+ projectSlug = parsed.slug;
287
362
  console.log(` resolved --project-slug=${projectSlug} \u2192 ${parsed.id} (${parsed.name})`);
288
363
  }
289
364
  if (!projectId) {
@@ -293,8 +368,9 @@ async function connect(args) {
293
368
  process.exit(1);
294
369
  }
295
370
  const explicit = args.flags.provider;
296
- const provider = explicit ?? detectProvider(cwd);
297
- if (!provider) {
371
+ const detectedProvider = explicit ?? detectProvider(cwd);
372
+ const scopeNeedsProvider = scope !== "read";
373
+ if (scopeNeedsProvider && !detectedProvider) {
298
374
  console.error(
299
375
  `Couldn't detect a provider from ${cwd}. Pass --provider <eas|vercel|github-actions>.
300
376
  Detection looks for: app.json/eas.json (EAS), .vercel/project.json (Vercel),
@@ -302,8 +378,10 @@ async function connect(args) {
302
378
  );
303
379
  process.exit(1);
304
380
  }
381
+ const provider = detectedProvider;
305
382
  const target = {
306
- provider,
383
+ provider: provider ?? "eas",
384
+ // unused when provider is null; placeholder
307
385
  easEnv: args.flags["eas-env"] ?? "production",
308
386
  vercelProjectId: args.flags["vercel-project-id"] ?? void 0,
309
387
  vercelTargets: typeof args.flags["vercel-targets"] === "string" ? args.flags["vercel-targets"].split(",").map((t) => t.trim()) : void 0
@@ -311,7 +389,7 @@ async function connect(args) {
311
389
  const labelBase = args.flags.label ?? `${provider}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
312
390
  console.log(`fixprompt connect`);
313
391
  console.log(` scope : ${scope}`);
314
- console.log(` provider : ${provider}`);
392
+ console.log(` provider : ${provider ?? "(none \u2014 read-only, writing local files)"}`);
315
393
  console.log(` project_id : ${projectId}`);
316
394
  console.log(` endpoint : ${endpoint}`);
317
395
  console.log(` label : ${labelBase}`);
@@ -326,12 +404,24 @@ async function connect(args) {
326
404
  \u2022 Minting ${s} token via broker\u2026`);
327
405
  const minted = await mintToken({ endpoint, adminToken, projectId, label, scope: s });
328
406
  console.log(` \u2713 token_id ${minted.token_id}`);
329
- await writeToProvider(envVar, minted.token, provider, target, force, cwd);
407
+ if (s === "read") {
408
+ writeLocalDiscovery({
409
+ cwd,
410
+ token: minted.token,
411
+ projectId,
412
+ projectSlug,
413
+ endpoint
414
+ });
415
+ } else if (provider) {
416
+ await writeToProvider(envVar, minted.token, provider, target, force, cwd);
417
+ }
330
418
  mintedTokens.push({ scope: s, token_id: minted.token_id, envVar });
331
419
  }
332
- console.log(`
333
- \u2713 ${mintedTokens.map((m) => m.envVar).join(" + ")} now set on ${provider}.`);
334
- if (mintedTokens.some((m) => m.scope === "deploy")) {
420
+ const readMinted = mintedTokens.some((m) => m.scope === "read");
421
+ const deployMinted = mintedTokens.some((m) => m.scope === "deploy");
422
+ console.log("");
423
+ if (deployMinted && provider) {
424
+ console.log(`\u2713 FIXPROMPT_DEPLOY_TOKEN set on ${provider}.`);
335
425
  if (provider === "eas") {
336
426
  console.log(` Next OTA: npm run release:ota --branch=production --message="..."`);
337
427
  } else if (provider === "github-actions") {
@@ -340,9 +430,10 @@ async function connect(args) {
340
430
  console.log(` The deploy token is in your Vercel project env.`);
341
431
  }
342
432
  }
343
- if (mintedTokens.some((m) => m.scope === "read")) {
344
- console.log(` Customer-side agents can now fetch logs via FIXPROMPT_READ_TOKEN \u2014`);
345
- console.log(` see the fixprompt-debug skill at fixloop-dashboard.vercel.app/claude-skill.md.`);
433
+ if (readMinted) {
434
+ console.log(`\u2713 FIXPROMPT_READ_TOKEN written to .env.local; CLAUDE.md updated.`);
435
+ console.log(` Coding agents (Claude Code / Cursor / Copilot) opening this repo will see`);
436
+ console.log(` the FixLoop section in CLAUDE.md and know to fetch logs on bug reports.`);
346
437
  }
347
438
  console.log(`
348
439
  Revoke any of these from the dashboard \u2192 Integrations.`);
@@ -353,7 +444,7 @@ async function connect(args) {
353
444
 
354
445
  // src/version.ts
355
446
  var CLI_NAME = "@fixprompt/cli";
356
- var CLI_VERSION = "0.3.0";
447
+ var CLI_VERSION = "0.4.0";
357
448
 
358
449
  // src/cli.ts
359
450
  var DEFAULT_ENDPOINT2 = "https://geosloghub-production.up.railway.app";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fixprompt/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "FixPrompt CLI — annotate deployments and ship them to the broker so the dashboard knows what changed.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {