@prompts-gpt/client 0.2.2 → 0.2.4

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/index.js CHANGED
@@ -1,11 +1,26 @@
1
1
  import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import { ensureGitignoreEntry } from "./runtime.js";
5
+ export { DEFAULT_RUN_ARTIFACTS_DIR, DEFAULT_RUN_CONFIG_PATH, ORCHESTRATION_AGENT_PROFILES, DEFAULT_MODELS, normalizeOrchestrationAgent, normalizeConcreteProvider, loadRunConfig, detectProviders, doctor, initRunConfig, runBatch, runPrompt, resolveRunProvider, resolveTimeoutSeconds, resolveDefaultPromptFile, assertPromptFitsLaunch, warnModelProviderMismatch, executeProviderCommandWithRetries, executeProviderCommand, captureWorktreeStatus, buildWorktreeDelta, buildProviderCommand, formatCombinedOutput, appendFileSafe, aggregateTokenUsage, validateRunConfig, discoverWorkspaceAssets, emptyTokenUsage, extractTokenUsageFromLog, hasTokenUsage, isCI, readTokenUsageFromLog, } from "./runtime.js";
6
+ export { sweepPrompt, acquireSweepLock, releaseSweepLock, parseStreamJsonToolCounts, streamJsonHasResult, extractIterationSummary, buildIterationPrompt, runPreFlight, writeSweepManifest, } from "./sweep.js";
4
7
  export const DEFAULT_PROMPTS_GPT_API_URL = "https://prompts-gpt.com";
5
8
  export const DEFAULT_PROMPTS_GPT_OUT_DIR = ".prompts-gpt";
6
9
  export const PROMPTS_GPT_CREDENTIALS_FILE = ".credentials.json";
7
10
  export const PROMPTS_GPT_MANIFEST_FILE = "manifest.json";
8
- export const SUPPORTED_AGENT_TARGETS = ["codex", "cursor", "vscode", "copilot"];
11
+ export const SUPPORTED_AGENT_TARGETS = [
12
+ "codex",
13
+ "claude-code",
14
+ "cursor",
15
+ "vscode",
16
+ "copilot",
17
+ "continue",
18
+ "gemini-cli",
19
+ "windsurf",
20
+ "cline",
21
+ "junie",
22
+ "amp",
23
+ ];
9
24
  export class PromptsGptApiError extends Error {
10
25
  status;
11
26
  code;
@@ -20,7 +35,7 @@ export class PromptsGptApiError extends Error {
20
35
  this.code = options?.code ?? "UNKNOWN_ERROR";
21
36
  this.recovery = options?.recovery ?? "Retry the request or create a fresh project token.";
22
37
  this.requestId = options?.requestId ?? null;
23
- this.fieldErrors = options?.fieldErrors;
38
+ this.fieldErrors = options?.fieldErrors ? Object.freeze({ ...options.fieldErrors }) : undefined;
24
39
  this.retryAfterMs = options?.retryAfterMs ?? null;
25
40
  }
26
41
  }
@@ -33,11 +48,16 @@ export class PromptsGptClient {
33
48
  token;
34
49
  fetchImpl;
35
50
  timeoutMs;
51
+ accountId;
36
52
  constructor(options) {
37
53
  this.apiUrl = safeNormalizeApiUrl(options.apiUrl);
38
54
  this.token = options.token?.trim() || null;
39
55
  this.fetchImpl = options.fetch;
40
56
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
57
+ this.accountId = options.accountId?.trim() || null;
58
+ if (this.token && this.apiUrl.startsWith("http://") && !this.apiUrl.includes("localhost") && !this.apiUrl.includes("127.0.0.1")) {
59
+ throw new PromptsGptApiError("Refusing to send credentials over unencrypted HTTP. Use HTTPS or localhost.", { code: "INSECURE_TRANSPORT", recovery: "Change the API URL to use https://." });
60
+ }
41
61
  }
42
62
  async getProject(options = {}) {
43
63
  const data = await this.request("/api/sdk/v1/project", options);
@@ -121,7 +141,11 @@ export class PromptsGptClient {
121
141
  authorization: `Bearer ${this.token}`,
122
142
  accept: "application/json",
123
143
  "x-prompts-gpt-client": `@prompts-gpt/client/${getClientVersion()}`,
144
+ "x-prompts-gpt-build": getBuildFingerprint(),
124
145
  };
146
+ if (this.accountId) {
147
+ headers["x-prompts-gpt-account"] = this.accountId;
148
+ }
125
149
  if (!options.omitUserAgent) {
126
150
  headers["user-agent"] = `prompts-gpt-client/${getClientVersion()}`;
127
151
  }
@@ -245,7 +269,9 @@ function normalizeTimeoutMs(value) {
245
269
  recovery: "Provide a timeout greater than 0.",
246
270
  });
247
271
  }
248
- return Math.min(Math.trunc(value), 300_000);
272
+ const MAX_API_TIMEOUT_MS = 600_000;
273
+ const capped = Math.min(Math.trunc(value), MAX_API_TIMEOUT_MS);
274
+ return capped;
249
275
  }
250
276
  function serializePromptQuery(query) {
251
277
  const params = new URLSearchParams();
@@ -340,11 +366,37 @@ function parseRetryAfterHeader(value) {
340
366
  return Math.min(Math.max(retryAt - Date.now(), 0), 600_000);
341
367
  }
342
368
  function sleep(ms) {
343
- return new Promise((resolve) => setTimeout(resolve, ms));
369
+ return new Promise((resolve) => {
370
+ setTimeout(resolve, ms);
371
+ });
344
372
  }
345
373
  function generateRequestId() {
346
- const hex = Array.from({ length: 8 }, () => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
347
- return `pgcli_${hex}`;
374
+ try {
375
+ const crypto = globalThis.crypto;
376
+ const bytes = new Uint8Array(8);
377
+ crypto.getRandomValues(bytes);
378
+ return `pgcli_${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
379
+ }
380
+ catch {
381
+ const hex = Array.from({ length: 8 }, () => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
382
+ return `pgcli_${hex}`;
383
+ }
384
+ }
385
+ const BUILD_TS = "dev";
386
+ const BUILD_ACCOUNT_ID = "unattributed";
387
+ let cachedBuildFingerprint = null;
388
+ function getBuildFingerprint() {
389
+ if (cachedBuildFingerprint)
390
+ return cachedBuildFingerprint;
391
+ cachedBuildFingerprint = `${getClientVersion()}/${BUILD_TS}/${BUILD_ACCOUNT_ID}`;
392
+ return cachedBuildFingerprint;
393
+ }
394
+ export function getAttribution() {
395
+ return {
396
+ version: getClientVersion(),
397
+ buildTs: BUILD_TS,
398
+ accountId: BUILD_ACCOUNT_ID,
399
+ };
348
400
  }
349
401
  let cachedVersion = null;
350
402
  function getClientVersion() {
@@ -381,16 +433,24 @@ export async function loadLocalCredentials(cwd = process.cwd()) {
381
433
  if (!existsSync(credentialsPath))
382
434
  return null;
383
435
  try {
384
- const parsed = JSON.parse(await readFile(credentialsPath, "utf8"));
436
+ const raw = await readFile(credentialsPath, "utf8");
437
+ const parsed = JSON.parse(raw);
438
+ if (!parsed || typeof parsed !== "object")
439
+ return null;
385
440
  let apiUrl = DEFAULT_PROMPTS_GPT_API_URL;
386
441
  if (typeof parsed.apiUrl === "string") {
387
442
  try {
388
- new URL(parsed.apiUrl);
389
- apiUrl = parsed.apiUrl;
443
+ const url = new URL(parsed.apiUrl);
444
+ if (url.protocol === "https:" || url.protocol === "http:") {
445
+ apiUrl = parsed.apiUrl;
446
+ }
390
447
  }
391
- catch { }
448
+ catch { /* invalid URL — use default */ }
392
449
  }
393
450
  const rawToken = typeof parsed.token === "string" ? parsed.token.trim() : null;
451
+ if (rawToken && !rawToken.startsWith("pgpt_")) {
452
+ return { token: null, apiUrl };
453
+ }
394
454
  return {
395
455
  token: rawToken || null,
396
456
  apiUrl,
@@ -446,6 +506,9 @@ export async function writePromptMarkdownFiles(prompts, options = {}) {
446
506
  }
447
507
  export async function writeAgentFiles(prompts, options = {}) {
448
508
  const cwd = options.cwd ?? process.cwd();
509
+ if (!existsSync(cwd)) {
510
+ throw new Error(`Working directory does not exist: ${cwd}`);
511
+ }
449
512
  const targets = normalizeAgentTargets(options.agent ?? options.agents ?? "all");
450
513
  const overwrite = Boolean(options.overwriteAgentFiles);
451
514
  const written = [];
@@ -494,9 +557,13 @@ export async function writePromptManifest(prompts, options = {}) {
494
557
  const normalizedPrompts = assertUniquePromptFileStems(prompts);
495
558
  await mkdir(outDir, { recursive: true });
496
559
  const manifestPath = path.join(outDir, PROMPTS_GPT_MANIFEST_FILE);
560
+ const attribution = getAttribution();
497
561
  const payload = {
498
562
  version: 1,
499
563
  generatedAt: new Date().toISOString(),
564
+ generatedBy: `@prompts-gpt/client@${attribution.version}`,
565
+ accountId: attribution.accountId,
566
+ buildFingerprint: `${attribution.version}/${attribution.buildTs}/${attribution.accountId}`,
500
567
  count: normalizedPrompts.length,
501
568
  prompts: normalizedPrompts.map(({ prompt, stem }) => ({
502
569
  slug: stem,
@@ -567,24 +634,47 @@ function buildAgentFiles(target, prompts) {
567
634
  ].join("\n"),
568
635
  }];
569
636
  }
637
+ if (target === "claude-code") {
638
+ return [{
639
+ path: "CLAUDE.md",
640
+ managedBlock: true,
641
+ content: [
642
+ "# Prompts-GPT Claude Code Instructions",
643
+ "",
644
+ "Prompts synced by `prompts-gpt sync` live in `.prompts-gpt/`. Start with [.prompts-gpt/manifest.json](.prompts-gpt/manifest.json), then open the prompt packs linked below before starting related work.",
645
+ "",
646
+ "## Available Prompt Packs",
647
+ ...prompts.map(({ prompt, stem }) => `- [${prompt.title}](.prompts-gpt/${stem}.md)`),
648
+ "",
649
+ "When a prompt pack is relevant, load it, adapt variables to the current task, and keep verification tied to the prompt's acceptance criteria.",
650
+ "",
651
+ ].join("\n"),
652
+ }];
653
+ }
570
654
  if (target === "cursor") {
571
- return prompts.map(({ prompt, stem }) => ({
572
- path: `.cursor/rules/prompts-gpt-${stem}.mdc`,
573
- content: [
574
- "---",
575
- `description: ${yamlScalar(prompt.summary || prompt.title)}`,
576
- "globs: []",
577
- "alwaysApply: false",
578
- "---",
579
- "",
580
- `# ${prompt.title}`,
581
- "",
582
- prompt.promptText,
583
- "",
584
- prompt.usageNotes ? `Usage notes: ${prompt.usageNotes}` : "",
585
- "",
586
- ].filter(Boolean).join("\n"),
587
- }));
655
+ return prompts.flatMap(({ prompt, stem }) => ([
656
+ {
657
+ path: `.cursor/rules/prompts-gpt-${stem}.mdc`,
658
+ content: [
659
+ "---",
660
+ `description: ${yamlScalar(prompt.summary || prompt.title)}`,
661
+ "globs: []",
662
+ "alwaysApply: false",
663
+ "---",
664
+ "",
665
+ `# ${prompt.title}`,
666
+ "",
667
+ prompt.promptText,
668
+ "",
669
+ prompt.usageNotes ? `Usage notes: ${prompt.usageNotes}` : "",
670
+ "",
671
+ ].filter(Boolean).join("\n"),
672
+ },
673
+ {
674
+ path: `.cursor/commands/prompts-gpt-${stem}.md`,
675
+ content: formatCursorCommandMarkdown(prompt, stem),
676
+ },
677
+ ]));
588
678
  }
589
679
  if (target === "vscode") {
590
680
  return [
@@ -604,12 +694,12 @@ function buildAgentFiles(target, prompts) {
604
694
  path: ".github/instructions/prompts-gpt.instructions.md",
605
695
  content: [
606
696
  "---",
607
- 'applyTo: "AGENTS.md,.prompts-gpt/**/*.md,.github/copilot-instructions.md,.github/prompts/**/*.prompt.md,.cursor/rules/**/*.mdc,.vscode/prompts-gpt.code-snippets"',
697
+ 'applyTo: "AGENTS.md,.prompts-gpt/**/*.md,.github/copilot-instructions.md,.github/prompts/**/*.prompt.md,.cursor/rules/**/*.mdc,.cursor/commands/**/*.md,.vscode/prompts-gpt.code-snippets"',
608
698
  "---",
609
699
  "",
610
700
  "# Prompts-GPT managed artifacts",
611
701
  "",
612
- "These files are generated or refreshed by `prompts-gpt sync` and `prompts-gpt install-agents`.",
702
+ "These files are generated or refreshed by `prompts-gpt sync`.",
613
703
  "",
614
704
  "- Treat `.prompts-gpt/manifest.json` as the source of truth for discoverable prompt packs and generated agent files.",
615
705
  "- Prefer updating the upstream prompt pack or rerunning sync instead of manually editing generated agent artifacts.",
@@ -629,6 +719,110 @@ function buildAgentFiles(target, prompts) {
629
719
  content: formatCopilotPromptMarkdown(prompt, stem),
630
720
  }));
631
721
  }
722
+ if (target === "continue") {
723
+ return prompts.map(({ prompt, stem }) => ({
724
+ path: `.continue/rules/prompts-gpt-${stem}.md`,
725
+ content: [
726
+ `# ${prompt.title}`,
727
+ "",
728
+ `[Canonical prompt pack](../../.prompts-gpt/${stem}.md)`,
729
+ "",
730
+ prompt.summary ?? "",
731
+ "",
732
+ prompt.promptText ?? "",
733
+ "",
734
+ prompt.usageNotes ? `Usage notes: ${prompt.usageNotes}` : "",
735
+ "",
736
+ ].filter(Boolean).join("\n"),
737
+ }));
738
+ }
739
+ if (target === "gemini-cli") {
740
+ return [{
741
+ path: "GEMINI.md",
742
+ managedBlock: true,
743
+ content: [
744
+ "# Prompts-GPT Gemini CLI Instructions",
745
+ "",
746
+ "Prompts synced by `prompts-gpt sync` live in `.prompts-gpt/`. Start with [.prompts-gpt/manifest.json](.prompts-gpt/manifest.json), then open the prompt packs linked below before starting related work.",
747
+ "",
748
+ "## Available Prompt Packs",
749
+ ...prompts.map(({ prompt, stem }) => `- [${prompt.title}](.prompts-gpt/${stem}.md)`),
750
+ "",
751
+ "When a prompt pack is relevant, load it, adapt variables to the current task, and keep verification tied to the prompt's acceptance criteria.",
752
+ "",
753
+ ].join("\n"),
754
+ }];
755
+ }
756
+ if (target === "windsurf") {
757
+ return prompts.map(({ prompt, stem }) => ({
758
+ path: `.windsurf/rules/prompts-gpt-${stem}.md`,
759
+ content: [
760
+ `# ${prompt.title}`,
761
+ "",
762
+ `[Canonical prompt pack](../../.prompts-gpt/${stem}.md)`,
763
+ "",
764
+ prompt.summary ?? "",
765
+ "",
766
+ prompt.promptText ?? "",
767
+ "",
768
+ "Use this as a workspace rule for Cascade or Devin Local when the prompt pack matches the task.",
769
+ prompt.usageNotes ? `Usage notes: ${prompt.usageNotes}` : "",
770
+ "",
771
+ ].filter(Boolean).join("\n"),
772
+ }));
773
+ }
774
+ if (target === "cline") {
775
+ return prompts.map(({ prompt, stem }) => ({
776
+ path: `.clinerules/prompts-gpt-${stem}.md`,
777
+ content: [
778
+ `# ${prompt.title}`,
779
+ "",
780
+ `[Canonical prompt pack](../.prompts-gpt/${stem}.md)`,
781
+ "",
782
+ prompt.summary ?? "",
783
+ "",
784
+ prompt.promptText ?? "",
785
+ "",
786
+ "Enable this rule when the current Cline task matches the linked prompt pack.",
787
+ prompt.usageNotes ? `Usage notes: ${prompt.usageNotes}` : "",
788
+ "",
789
+ ].filter(Boolean).join("\n"),
790
+ }));
791
+ }
792
+ if (target === "junie") {
793
+ return [{
794
+ path: ".junie/guidelines.md",
795
+ managedBlock: true,
796
+ content: [
797
+ "# Prompts-GPT Junie Guidelines",
798
+ "",
799
+ "Prompts synced by `prompts-gpt sync` live in `.prompts-gpt/`. Start with [.prompts-gpt/manifest.json](.prompts-gpt/manifest.json), then open the prompt packs linked below before starting related work.",
800
+ "",
801
+ "## Available Prompt Packs",
802
+ ...prompts.map(({ prompt, stem }) => `- [${prompt.title}](../.prompts-gpt/${stem}.md)`),
803
+ "",
804
+ "When a prompt pack is relevant, load it, adapt variables to the current task, and keep verification tied to the prompt's acceptance criteria.",
805
+ "",
806
+ ].join("\n"),
807
+ }];
808
+ }
809
+ if (target === "amp") {
810
+ return [{
811
+ path: "AGENT.md",
812
+ managedBlock: true,
813
+ content: [
814
+ "# Prompts-GPT Amp Instructions",
815
+ "",
816
+ "Prompts synced by `prompts-gpt sync` live in `.prompts-gpt/`. Start with [.prompts-gpt/manifest.json](.prompts-gpt/manifest.json), then open the prompt packs linked below before starting related work.",
817
+ "",
818
+ "## Available Prompt Packs",
819
+ ...prompts.map(({ prompt, stem }) => `- [${prompt.title}](.prompts-gpt/${stem}.md)`),
820
+ "",
821
+ "When a prompt pack is relevant, load it, adapt variables to the current task, and keep verification tied to the prompt's acceptance criteria.",
822
+ "",
823
+ ].join("\n"),
824
+ }];
825
+ }
632
826
  return [];
633
827
  }
634
828
  function buildVsCodeSnippets(prompts) {
@@ -689,13 +883,52 @@ function buildDiscoverablePromptFiles(stem, prompt) {
689
883
  return {
690
884
  markdown: `.prompts-gpt/${stem}.md`,
691
885
  codexInstructions: supports("codex") ? "AGENTS.md" : null,
886
+ claudeCodeInstructions: supports("claude-code") ? "CLAUDE.md" : null,
692
887
  cursorRule: supports("cursor") ? `.cursor/rules/prompts-gpt-${stem}.mdc` : null,
888
+ cursorCommand: supports("cursor") ? `.cursor/commands/prompts-gpt-${stem}.md` : null,
693
889
  vscodeInstructions: supports("vscode") ? ".github/copilot-instructions.md" : null,
694
890
  copilotPathInstructions: supports("vscode") ? ".github/instructions/prompts-gpt.instructions.md" : null,
695
891
  vscodeSnippets: supports("vscode") ? ".vscode/prompts-gpt.code-snippets" : null,
696
892
  copilotPrompt: supports("copilot") ? `.github/prompts/prompts-gpt-${stem}.prompt.md` : null,
893
+ continueRule: supports("continue") ? `.continue/rules/prompts-gpt-${stem}.md` : null,
894
+ geminiInstructions: supports("gemini-cli") ? "GEMINI.md" : null,
895
+ windsurfRule: supports("windsurf") ? `.windsurf/rules/prompts-gpt-${stem}.md` : null,
896
+ clineRule: supports("cline") ? `.clinerules/prompts-gpt-${stem}.md` : null,
897
+ junieGuidelines: supports("junie") ? ".junie/guidelines.md" : null,
898
+ ampInstructions: supports("amp") ? "AGENT.md" : null,
697
899
  };
698
900
  }
901
+ function formatCursorCommandMarkdown(prompt, stem) {
902
+ const lines = [
903
+ `# ${prompt.title}`,
904
+ "",
905
+ `[Canonical prompt pack](../../.prompts-gpt/${stem}.md)`,
906
+ "",
907
+ prompt.summary ?? "",
908
+ "",
909
+ "## Task",
910
+ "",
911
+ prompt.promptText ?? "",
912
+ ];
913
+ if (prompt.variables?.length) {
914
+ lines.push("", "## Inputs", "");
915
+ for (const variable of prompt.variables) {
916
+ lines.push(`- ${String(variable).replace(/[{}$]/g, "")}`);
917
+ }
918
+ }
919
+ if (prompt.usageNotes) {
920
+ lines.push("", "## Usage Notes", "", prompt.usageNotes);
921
+ }
922
+ lines.push("", "Verify the output against `.prompts-gpt/manifest.json` and the linked canonical prompt pack.", "");
923
+ return lines
924
+ .reduce((acc, line, index, list) => {
925
+ if (line === "" && index > 0 && list[index - 1] === "" && (index < 2 || list[index - 2] === ""))
926
+ return acc;
927
+ acc.push(line);
928
+ return acc;
929
+ }, [])
930
+ .join("\n");
931
+ }
699
932
  function formatCopilotPromptMarkdown(prompt, stem) {
700
933
  const sections = [
701
934
  "---",
@@ -755,8 +988,9 @@ function escapeMarkdownLinkText(value) {
755
988
  return value.replace(/[[\]]/g, "\\$&");
756
989
  }
757
990
  async function writePromptIndex(prompts, { outDir }) {
758
- const indexPath = path.join(outDir, "README.md");
759
- const content = [
991
+ const indexPath = path.resolve(outDir, "README.md");
992
+ assertInside(indexPath, path.resolve(outDir));
993
+ const managedContent = [
760
994
  "# Prompts-GPT Prompt Packs",
761
995
  "",
762
996
  "These prompts were synced by `prompts-gpt`. Re-run `prompts-gpt sync` to refresh Markdown and agent files.",
@@ -770,8 +1004,12 @@ async function writePromptIndex(prompts, { outDir }) {
770
1004
  }),
771
1005
  "",
772
1006
  ].join("\n");
773
- assertInside(indexPath, outDir);
774
- await writeFile(indexPath, content);
1007
+ let existing = "";
1008
+ try {
1009
+ existing = await readFile(indexPath, "utf8");
1010
+ }
1011
+ catch { }
1012
+ await writeFile(indexPath, upsertManagedBlock(existing, managedContent));
775
1013
  }
776
1014
  function normalizeAgentTargets(value) {
777
1015
  const raw = Array.isArray(value) ? value.join(",") : String(value ?? "all");
@@ -834,8 +1072,8 @@ function assertInside(filePath, directory) {
834
1072
  }
835
1073
  }
836
1074
  function safeSlug(value) {
837
- const raw = String(value ?? "");
838
- if (!raw.trim())
1075
+ const raw = String(value ?? "").trim();
1076
+ if (!raw)
839
1077
  return "prompt";
840
1078
  const slug = raw
841
1079
  .toLowerCase()
@@ -844,7 +1082,9 @@ function safeSlug(value) {
844
1082
  .replace(/[^a-z0-9]+/g, "-")
845
1083
  .replace(/^-+|-+$/g, "")
846
1084
  .slice(0, 90);
847
- return slug || "prompt";
1085
+ if (!slug || slug === "-")
1086
+ return "prompt";
1087
+ return slug;
848
1088
  }
849
1089
  function yamlScalar(value) {
850
1090
  const s = String(value ?? "");
@@ -853,15 +1093,4 @@ function yamlScalar(value) {
853
1093
  }
854
1094
  return JSON.stringify(s);
855
1095
  }
856
- async function ensureGitignoreEntry(cwd, entry) {
857
- const gitignorePath = path.resolve(cwd, ".gitignore");
858
- const existing = existsSync(gitignorePath) ? await readFile(gitignorePath, "utf8") : "";
859
- const eol = existing.includes("\r\n") ? "\r\n" : "\n";
860
- const lines = existing.split(/\r?\n/).map((line) => line.trim());
861
- if (lines.includes(entry))
862
- return;
863
- const needsLeadingNewline = existing.length > 0 && !existing.endsWith("\n") && !existing.endsWith("\r\n");
864
- const prefix = needsLeadingNewline ? eol : "";
865
- await writeFile(gitignorePath, `${existing}${prefix}${entry}${eol}`);
866
- }
867
1096
  //# sourceMappingURL=index.js.map