@prompts-gpt/client 0.2.2 → 0.2.3

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