@fixprompt/cli 0.4.0 → 0.5.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/cli.js +123 -20
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -288,36 +288,139 @@ function appendEnvLocal(cwd, token, projectId, projectSlug, endpoint) {
|
|
|
288
288
|
writeFileSync(envPath, block.replace(/^\n/, ""));
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
|
-
function
|
|
292
|
-
const
|
|
293
|
-
|
|
291
|
+
function detectRuntime(cwd) {
|
|
292
|
+
const pj = join(cwd, "package.json");
|
|
293
|
+
if (!existsSync(pj)) return "unknown";
|
|
294
|
+
try {
|
|
295
|
+
const j = JSON.parse(readFileSync(pj, "utf8"));
|
|
296
|
+
const deps = { ...j.dependencies || {}, ...j.devDependencies || {} };
|
|
297
|
+
if (deps["react-native"] || deps["expo"]) return "react-native";
|
|
298
|
+
if (deps["next"]) return "next";
|
|
299
|
+
if (deps["react"] || deps["vite"] || deps["react-scripts"]) return "browser";
|
|
300
|
+
if (deps["@nestjs/core"] || deps["express"] || deps["fastify"]) return "node";
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
return "unknown";
|
|
304
|
+
}
|
|
305
|
+
var FIXLOOP_SECTION_MARKER = "<!-- fixloop:claude-section -->";
|
|
306
|
+
function buildClaudeMdSection(projectId, projectSlug, runtime) {
|
|
307
|
+
const sdkRows = {
|
|
308
|
+
"react-native": {
|
|
309
|
+
pkg: "@fixprompt/react-native",
|
|
310
|
+
entry: "App.tsx / index.js \u2014 call before the first render",
|
|
311
|
+
example: "import { initFixPrompt } from '@fixprompt/react-native';\ninitFixPrompt({\n projectKey: process.env.EXPO_PUBLIC_FIXPROMPT_KEY!,\n source: '<slug>-prod',\n service: '<slug>',\n env: 'prod',\n});"
|
|
312
|
+
},
|
|
313
|
+
next: {
|
|
314
|
+
pkg: "@fixprompt/browser (client) + @fixprompt/node (server, github:goscha01/geos-loghub-client until @fixprompt/node ships)",
|
|
315
|
+
entry: "src/app/providers.tsx for client (useEffect-init), instrumentation.ts or server-start for Node",
|
|
316
|
+
example: `// client (in a "use client" component, useEffect):
|
|
317
|
+
import { initFixPrompt } from '@fixprompt/browser';
|
|
318
|
+
initFixPrompt({ projectKey: process.env.NEXT_PUBLIC_FIXPROMPT_KEY!, source: '<slug>-prod', service: '<slug>', env: 'prod' });`
|
|
319
|
+
},
|
|
320
|
+
browser: {
|
|
321
|
+
pkg: "@fixprompt/browser",
|
|
322
|
+
entry: "src/main.{ts,tsx} / index.{js,tsx} \u2014 top of file, before render",
|
|
323
|
+
example: "import { initFixPrompt } from '@fixprompt/browser';\ninitFixPrompt({\n projectKey: import.meta.env.VITE_FIXPROMPT_KEY, // or REACT_APP_FIXPROMPT_KEY for CRA\n source: '<slug>-prod',\n service: '<slug>',\n env: import.meta.env.PROD ? 'prod' : 'dev',\n});"
|
|
324
|
+
},
|
|
325
|
+
node: {
|
|
326
|
+
pkg: "@geos/loghub-client (npm install @geos/loghub-client@github:goscha01/geos-loghub-client \u2014 @fixprompt/node not yet on npm)",
|
|
327
|
+
entry: "src/index.{ts,js} / src/main.{ts,js} \u2014 at server start, before route registration",
|
|
328
|
+
example: "const { loghubLog } = require('@geos/loghub-client');\n// per-request middleware:\napp.use((req, res, next) => {\n res.on('finish', () => loghubLog({ service: '<slug>', app: '<slug>', env: 'prod', level: res.statusCode >= 500 ? 'error' : 'info', message: 'http_request', attrs: { method: req.method, path: req.originalUrl, status: res.statusCode } }));\n next();\n});"
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const sdk = runtime === "unknown" ? {
|
|
332
|
+
pkg: "@fixprompt/browser | @fixprompt/react-native | @geos/loghub-client (Node) \u2014 pick by stack",
|
|
333
|
+
entry: "(read package.json to determine runtime)",
|
|
334
|
+
example: "// see https://fixloop-dashboard.vercel.app/onboarding for runtime-specific snippets"
|
|
335
|
+
} : sdkRows[runtime];
|
|
336
|
+
const slugLine = projectSlug ? `\`${projectSlug}\`` : "(see .env.local)";
|
|
337
|
+
const sdkExampleWithSlug = sdk.example.replace(/<slug>/g, projectSlug ?? "<slug>");
|
|
338
|
+
return [
|
|
294
339
|
"",
|
|
295
|
-
|
|
340
|
+
FIXLOOP_SECTION_MARKER,
|
|
341
|
+
"## FixLoop \u2014 production error logs (auto-fetch on bug reports + dev loop)",
|
|
296
342
|
"",
|
|
297
|
-
|
|
343
|
+
`This project is wired to FixLoop. Two behaviors expected of the coding agent reading this file:`,
|
|
298
344
|
"",
|
|
299
|
-
"1.
|
|
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.**",
|
|
345
|
+
"### 1. Install the SDK on first use (skip if already in `package.json`)",
|
|
303
346
|
"",
|
|
304
|
-
|
|
347
|
+
`Detected runtime: **${runtime}**. Recommended SDK: \`${sdk.pkg}\``,
|
|
305
348
|
"",
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
349
|
+
`- Entry point: ${sdk.entry}`,
|
|
350
|
+
`- Init snippet:`,
|
|
351
|
+
"",
|
|
352
|
+
"```ts",
|
|
353
|
+
sdkExampleWithSlug,
|
|
309
354
|
"```",
|
|
310
355
|
"",
|
|
311
|
-
"
|
|
356
|
+
"After install, the SDK ships every uncaught error to the broker \u2192 Grafana Loki under `service_name=" + (projectSlug ?? "<slug>") + "`. Subsequent debug runs read these errors back via the curl in step 2.",
|
|
357
|
+
"",
|
|
358
|
+
"### 2. Debug loop on bug reports",
|
|
359
|
+
"",
|
|
360
|
+
`Trigger phrases: "X is broken", "doesn't work", "crashes", "is failing", pasted error stacks, "users are reporting Y", TestFlight/App Store bug reports.`,
|
|
361
|
+
"",
|
|
362
|
+
"Loop until the log query returns no new matches:",
|
|
363
|
+
"",
|
|
364
|
+
"1. **Fetch recent errors** \u2014 env vars come from `.env.local` (gitignored \u2014 never commit):",
|
|
365
|
+
" ```sh",
|
|
366
|
+
' curl -sH "x-fixprompt-read-token: $FIXPROMPT_READ_TOKEN" \\',
|
|
367
|
+
' "$FIXPROMPT_BROKER_URL/projects/$FIXPROMPT_PROJECT_ID/logs?since=24h&level=error,warn,fatal&limit=200"',
|
|
368
|
+
" ```",
|
|
369
|
+
" Query params: `since` (15m..7d), `level` (csv), `q` (substring), `limit` (1..500).",
|
|
370
|
+
"",
|
|
371
|
+
'2. **Parse `entries[]`** \u2014 each has `ts`, `level`, `message`, optional `attrs` + `stack`. Find the entries matching the user\'s symptom. Cite specific lines in your response (e.g. *"the `Cannot read property foo of null` at -200ms vs `[Storage] read failed` at -240ms suggests AsyncStorage is racing the migration"*).',
|
|
312
372
|
"",
|
|
313
|
-
|
|
314
|
-
|
|
373
|
+
"3. **Apply a fix** grounded in what the logs show. **Do not ask the user to paste log lines \u2014 fetch them yourself.**",
|
|
374
|
+
"",
|
|
375
|
+
"4. **Verify locally** \u2014 run the build / dev server / tests so the fix compiles and the affected path no longer throws.",
|
|
376
|
+
"",
|
|
377
|
+
"5. **Re-fetch with `since=5m`** after deploy. If the same error still appears OR new errors appear, **loop back to step 2** with the latest entries as context. Stop when the query returns no new matches against the user's symptom.",
|
|
378
|
+
"",
|
|
379
|
+
"### Project",
|
|
380
|
+
"",
|
|
381
|
+
`- Slug: ${slugLine}`,
|
|
382
|
+
`- Dashboard: https://fixloop-dashboard.vercel.app/issues/${projectId}`,
|
|
383
|
+
`- Manage / revoke the read token: https://fixloop-dashboard.vercel.app/integrations/${projectId}`,
|
|
315
384
|
""
|
|
316
|
-
].
|
|
385
|
+
].join("\n");
|
|
386
|
+
}
|
|
387
|
+
function stripFixloopSection(body) {
|
|
388
|
+
const lines = body.split("\n");
|
|
389
|
+
let start = -1;
|
|
390
|
+
for (let i = 0; i < lines.length; i++) {
|
|
391
|
+
if (lines[i].trim() === FIXLOOP_SECTION_MARKER) {
|
|
392
|
+
start = i;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
if (lines[i].startsWith("## FixLoop \u2014")) {
|
|
396
|
+
start = i;
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (start === -1) return body;
|
|
401
|
+
const realStart = start > 0 && lines[start - 1] === "" ? start - 1 : start;
|
|
402
|
+
let end = lines.length;
|
|
403
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
404
|
+
if (lines[i].startsWith("## ") && !lines[i].startsWith("## FixLoop \u2014")) {
|
|
405
|
+
end = i;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const trimmedTail = lines.slice(end);
|
|
410
|
+
while (trimmedTail.length > 0 && realStart > 0 && lines[realStart - 1] === "" && trimmedTail[0] === "") {
|
|
411
|
+
trimmedTail.shift();
|
|
412
|
+
}
|
|
413
|
+
return [...lines.slice(0, realStart), ...trimmedTail].join("\n");
|
|
414
|
+
}
|
|
415
|
+
function appendClaudeMdGuidance(cwd, projectId, projectSlug) {
|
|
416
|
+
const claudePath = join(cwd, "CLAUDE.md");
|
|
417
|
+
const runtime = detectRuntime(cwd);
|
|
418
|
+
const section = buildClaudeMdSection(projectId, projectSlug, runtime);
|
|
317
419
|
if (existsSync(claudePath)) {
|
|
318
420
|
const existing = readFileSync(claudePath, "utf8");
|
|
319
|
-
|
|
320
|
-
|
|
421
|
+
const stripped = stripFixloopSection(existing);
|
|
422
|
+
const next = stripped.endsWith("\n") ? stripped + section.replace(/^\n/, "") : stripped + section;
|
|
423
|
+
writeFileSync(claudePath, next);
|
|
321
424
|
} else {
|
|
322
425
|
writeFileSync(claudePath, "# Project\n" + section);
|
|
323
426
|
}
|
|
@@ -444,7 +547,7 @@ async function connect(args) {
|
|
|
444
547
|
|
|
445
548
|
// src/version.ts
|
|
446
549
|
var CLI_NAME = "@fixprompt/cli";
|
|
447
|
-
var CLI_VERSION = "0.
|
|
550
|
+
var CLI_VERSION = "0.5.0";
|
|
448
551
|
|
|
449
552
|
// src/cli.ts
|
|
450
553
|
var DEFAULT_ENDPOINT2 = "https://geosloghub-production.up.railway.app";
|
package/package.json
CHANGED