@4pm/cli 1.20.0 → 1.21.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/index.js +47 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -42819,12 +42819,13 @@ function authedUrl2(url, token) {
42819
42819
  if (url.startsWith("https://")) return url.replace("https://", `https://x-access-token:${token}@`);
42820
42820
  return url;
42821
42821
  }
42822
- function buildPrompt4(tickets) {
42822
+ function buildPrompt4(tickets, customPrompt) {
42823
42823
  const transcript = tickets.map((t2) => {
42824
42824
  const thread = t2.messages.map((m2) => ` [${m2.author}] ${m2.body}`).join("\n");
42825
42825
  return `### Ticket ${t2.id} \u2014 ${t2.subject} (${t2.category})
42826
42826
  ${thread}`;
42827
42827
  }).join("\n\n");
42828
+ const ids = tickets.map((t2) => t2.id);
42828
42829
  return [
42829
42830
  "You are maintaining the 4PM product FAQ, stored as markdown in THIS git repository (your CWD).",
42830
42831
  "From the resolved support tickets below, distil reusable, product-general FAQ entries and add or",
@@ -42839,11 +42840,45 @@ ${thread}`;
42839
42840
  "- Only edit markdown documentation files; do not touch code, CI, or unrelated files.",
42840
42841
  "- Do NOT run git commit/push or open a PR yourself \u2014 just leave the edited files in the working",
42841
42842
  " tree; the surrounding tooling handles commit + PR.",
42843
+ ...customPrompt?.trim() ? ["", "Additional instruction from the operator (follow it too):", customPrompt.trim()] : [],
42842
42844
  "",
42843
42845
  "===== RESOLVED TICKETS =====",
42844
- transcript
42846
+ transcript,
42847
+ "",
42848
+ "===== REQUIRED FINAL OUTPUT =====",
42849
+ "After finishing all file edits, output \u2014 as the LAST thing, on its own \u2014 a single fenced JSON",
42850
+ "block summarizing what you did PER TICKET (created/updated which FAQ entry, or skipped + why):",
42851
+ "```json",
42852
+ `{"perTicket":[${ids.map((id) => `{"ticketId":"${id}","summary":"..."}`).join(",")}]}`,
42853
+ "```",
42854
+ "Include exactly one entry per ticket id above; keep each summary to one or two sentences."
42845
42855
  ].join("\n");
42846
42856
  }
42857
+ function parsePerTicket(text) {
42858
+ const idx = text.lastIndexOf('"perTicket"');
42859
+ if (idx === -1) return [];
42860
+ const open = text.lastIndexOf("{", idx);
42861
+ if (open === -1) return [];
42862
+ let depth = 0;
42863
+ for (let i = open; i < text.length; i++) {
42864
+ if (text[i] === "{") depth++;
42865
+ else if (text[i] === "}") {
42866
+ depth--;
42867
+ if (depth === 0) {
42868
+ try {
42869
+ const obj = JSON.parse(text.slice(open, i + 1));
42870
+ if (Array.isArray(obj.perTicket)) {
42871
+ return obj.perTicket.filter((e) => !!e && typeof e.ticketId === "string").map((e) => ({ ticketId: e.ticketId, summary: String(e.summary ?? "") }));
42872
+ }
42873
+ } catch {
42874
+ return [];
42875
+ }
42876
+ return [];
42877
+ }
42878
+ }
42879
+ }
42880
+ return [];
42881
+ }
42847
42882
  function runClaudeOnce2(cmd, profile, prompt, cwd2, extraEnv) {
42848
42883
  return new Promise((resolve10, reject) => {
42849
42884
  const isClaude = cmd.includes("claude");
@@ -42889,12 +42924,13 @@ async function runClaudeWithFailover2(prompt, cwd2, ai2) {
42889
42924
  let lastReason = "no attempt";
42890
42925
  for (let i = 0; i < attempts.length; i++) {
42891
42926
  const { code, out, err, usage } = await runClaudeOnce2(ai2.cmd, attempts[i], prompt, cwd2, ai2.env);
42892
- if (code === 0) return usage;
42927
+ if (code === 0) return { usage, text: out.trim() };
42893
42928
  lastReason = `exited ${code}: ${(err || out).slice(0, 500)}`;
42894
42929
  if (i === attempts.length - 1) break;
42895
42930
  }
42896
42931
  throw new Error(`claude ${lastReason}`);
42897
42932
  }
42933
+ var MAX_OUTPUT_CHARS = 8e3;
42898
42934
  async function runFaqCompose(req, ai2) {
42899
42935
  if (!req.repo.token) return { prUrl: "", error: "no write token" };
42900
42936
  const workDir = join24(tmpdir3(), "4pm-faq-sync", randomUUID3());
@@ -42906,10 +42942,14 @@ async function runFaqCompose(req, ai2) {
42906
42942
  await execFileAsync2("git", ["-C", workDir, "config", "user.name", "4PM FAQ Bot"], { env: gitEnv });
42907
42943
  await execFileAsync2("git", ["-C", workDir, "config", "user.email", "faq-bot@4pm.app"], { env: gitEnv });
42908
42944
  await execFileAsync2("git", ["-C", workDir, "checkout", "-b", req.headBranch], { env: gitEnv });
42909
- const usage = await runClaudeWithFailover2(buildPrompt4(req.tickets), workDir, ai2);
42945
+ const { usage, text } = await runClaudeWithFailover2(buildPrompt4(req.tickets, req.customPrompt), workDir, ai2);
42946
+ const output = text.slice(0, MAX_OUTPUT_CHARS);
42947
+ const perTicket = parsePerTicket(text);
42910
42948
  await execFileAsync2("git", ["-C", workDir, "add", "-A"], { env: gitEnv });
42911
42949
  const staged = await execFileAsync2("git", ["-C", workDir, "diff", "--cached", "--name-only"], { env: gitEnv });
42912
- if (!staged.stdout.trim()) return { prUrl: "", tokens: usage.tokens || estimateTokens2(""), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
42950
+ if (!staged.stdout.trim()) {
42951
+ return { prUrl: "", output, perTicket, tokens: usage.tokens || estimateTokens2(text), finishedAt: (/* @__PURE__ */ new Date()).toISOString() };
42952
+ }
42913
42953
  await execFileAsync2("git", ["-C", workDir, "commit", "-m", "docs(faq): synthesize from support tickets (4PM)"], { env: gitEnv });
42914
42954
  await execFileAsync2("git", ["-C", workDir, "push", "-u", "origin", req.headBranch], { env: gitEnv });
42915
42955
  let prUrl = "";
@@ -42927,6 +42967,8 @@ async function runFaqCompose(req, ai2) {
42927
42967
  return {
42928
42968
  prUrl,
42929
42969
  branch: req.headBranch,
42970
+ output,
42971
+ perTicket,
42930
42972
  tokens: u2.tokens,
42931
42973
  ...usage.tokens > 0 ? { tokensBreakdown: { input: usage.input, output: usage.output, cacheRead: usage.cacheRead, cacheCreation: usage.cacheCreation } } : {},
42932
42974
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",