opencrush 0.3.10 → 0.3.11

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 +141 -96
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -42073,10 +42073,19 @@ async function createFromPreset(apiKey, provider) {
42073
42073
  personality,
42074
42074
  backstory
42075
42075
  };
42076
- const spinner = ora("Saving character files...").start();
42077
- const blueprint = buildBlueprintFromPreset(config);
42076
+ const spinner = ora("Building character files...").start();
42077
+ let blueprint = buildBlueprintFromPreset(config);
42078
+ if (apiKey && provider) {
42079
+ try {
42080
+ blueprint = await enrichBlueprintWithLLM(blueprint, config, apiKey, provider);
42081
+ spinner.succeed(source_default.green(`${displayName} created!`));
42082
+ } catch {
42083
+ spinner.succeed(source_default.green(`${displayName} created! (basic \u2014 add API key for richer files)`));
42084
+ }
42085
+ } else {
42086
+ spinner.succeed(source_default.green(`${displayName} created!`));
42087
+ }
42078
42088
  writeCharacterFiles(folderName, blueprint);
42079
- spinner.succeed(source_default.green(`${displayName} created!`));
42080
42089
  const { hasPortrait, falKey } = await craftPortrait(config, folderName, apiKey, provider);
42081
42090
  printCreationSuccess(folderName, displayName);
42082
42091
  return { folderName, displayName, gender: preset.gender, hasPortrait, falKey };
@@ -42301,20 +42310,24 @@ function buildPortraitPrompt(config) {
42301
42310
  `wearing ${appearance.fashionStyle} style`,
42302
42311
  archetype.portraitBase,
42303
42312
  vibeHints,
42304
- "cinematic photography, ultra-detailed, 8k resolution, professional studio lighting, beautiful, editorial fashion, sharp focus"
42313
+ "cinematic photography, ultra-detailed, 8k resolution, beautiful, editorial fashion, sharp focus, atmospheric background matching character aesthetic, environmental storytelling, moody scene lighting"
42305
42314
  ];
42306
42315
  return parts.filter(Boolean).join(", ");
42307
42316
  }
42308
42317
  async function optimizePortraitPrompt(basePrompt, config, apiKey, provider) {
42309
- const system = `You are an expert at writing image generation prompts for portrait photography.
42318
+ const system = `You are an expert at writing image generation prompts for character portraits.
42310
42319
  Given a character description and base prompt, write an optimized FLUX/Stable Diffusion prompt.
42311
- Rules: under 200 words, focus on visual details only, include quality tags, match the character's aesthetic.
42320
+ Rules:
42321
+ - Under 200 words, focus on visual details, include quality tags
42322
+ - ALWAYS include a scene-appropriate background that matches the character's vibe and lifestyle (e.g. neon cityscape for cyberpunk, cozy caf\xE9 for warm characters, art studio for artists, rain-soaked street for moody characters)
42323
+ - NEVER use plain white, studio, or blank backgrounds
42324
+ - The background should tell a story about who this character is
42312
42325
  Return ONLY the prompt text \u2014 no explanation, no quotes.`;
42313
42326
  const userMsg = `Character: ${config.name}, ${config.gender}
42314
42327
  Archetype: ${config.archetype.label}
42315
42328
  Base prompt: ${basePrompt}
42316
42329
 
42317
- Optimize this into a vivid, effective portrait generation prompt. Preserve all physical details.`;
42330
+ Optimize this into a vivid portrait prompt with an atmospheric background that fits the character's world. Preserve all physical details.`;
42318
42331
  try {
42319
42332
  const result = await callLLMDirect(provider, apiKey, system, [{ role: "user", content: userMsg }], 300);
42320
42333
  return result.trim() || basePrompt;
@@ -42358,6 +42371,38 @@ function openImage(filePath) {
42358
42371
  (0, import_child_process2.exec)(cmd, () => {
42359
42372
  });
42360
42373
  }
42374
+ async function enrichBlueprintWithLLM(blueprint, config, apiKey, provider) {
42375
+ const system = `You are a character writer creating rich companion profiles. Write in second person removed \u2014 describe the character as they are, not to them. Be specific, vivid, contradictory. Real people have texture.
42376
+
42377
+ RULES:
42378
+ - Expand appearance into 2-3 paragraphs of vivid prose (not bullet points)
42379
+ - Add a ## Background section (2-3 paragraphs about their history, how they got here)
42380
+ - Flesh out the SOUL with specific examples, not generic traits
42381
+ - Keep the markdown structure and frontmatter exactly as given
42382
+ - Write in English only
42383
+ - Do NOT add any sections that aren't in the original
42384
+ - Return the FULL expanded file content, not just the additions`;
42385
+ const identityPrompt = `Expand this IDENTITY.md into a rich, detailed character file. The appearance section should be vivid prose paragraphs (like a novel character introduction), not a list. Add a ## Background section at the end with 2-3 paragraphs of backstory.
42386
+
42387
+ Current file:
42388
+ ${blueprint.identity}
42389
+
42390
+ Character context: ${config.archetype.label} archetype, personality traits: ${config.personality.join(", ")}${config.backstory ? ", backstory moment: " + config.backstory : ""}`;
42391
+ const soulPrompt = `Expand this SOUL.md into a richer version. Add specific examples to each section. The "Voice & Vibe" section should be 2+ paragraphs. "Loves" and "Dislikes" should have 5-7 items each with colorful detail. Add a "## Things She Does" section with 6-8 specific behavioral habits. Keep the same structure.
42392
+
42393
+ Current file:
42394
+ ${blueprint.soul}`;
42395
+ const [enrichedIdentity, enrichedSoul] = await Promise.all([
42396
+ callLLMDirect(provider, apiKey, system, [{ role: "user", content: identityPrompt }], 2e3).catch(() => blueprint.identity),
42397
+ callLLMDirect(provider, apiKey, system, [{ role: "user", content: soulPrompt }], 2e3).catch(() => blueprint.soul)
42398
+ ]);
42399
+ return {
42400
+ identity: enrichedIdentity || blueprint.identity,
42401
+ soul: enrichedSoul || blueprint.soul,
42402
+ user: blueprint.user,
42403
+ memory: blueprint.memory
42404
+ };
42405
+ }
42361
42406
  function buildBlueprintFromPreset(config) {
42362
42407
  const { archetype, name, appearance, personality, backstory } = config;
42363
42408
  const renameIn = (text) => text.replace(new RegExp(`# ${escapeRegex(archetype.id.charAt(0).toUpperCase() + archetype.id.slice(1))}`, "g"), `# ${name}`).replace(new RegExp(`\\b${escapeRegex(archetype.id)}\\b`, "gi"), name).replace(new RegExp(`\\b(Yuna|Valentina|Hana|Nyx|Hu Lan|Riot|Kai|Eli)\\b`, "g"), name);
@@ -245110,7 +245155,64 @@ async function runSetupWizard() {
245110
245155
  const TOTAL = 4;
245111
245156
  const step = (n2, label) => console.log(source_default.cyan(`
245112
245157
  [${n2}/${TOTAL}] ${label}`));
245113
- step(1, "Pick your companion");
245158
+ step(1, "Choose your AI brain");
245159
+ console.log(source_default.gray(" Pick whichever you already have a key for.\n"));
245160
+ const envValues = {};
245161
+ const providerChoices = [
245162
+ PROVIDER_INFO.find((p2) => p2.id === "anthropic"),
245163
+ PROVIDER_INFO.find((p2) => p2.id === "openai"),
245164
+ PROVIDER_INFO.find((p2) => p2.id === "deepseek"),
245165
+ PROVIDER_INFO.find((p2) => p2.isLocal)
245166
+ ].filter(Boolean);
245167
+ const { llmProvider } = await esm_default12.prompt([{
245168
+ type: "list",
245169
+ name: "llmProvider",
245170
+ message: "Which AI provider?",
245171
+ choices: providerChoices.map((p2) => ({
245172
+ name: `${p2.emoji} ${p2.name}
245173
+ ${source_default.gray(p2.tagline)}`,
245174
+ value: p2.id,
245175
+ short: p2.name
245176
+ }))
245177
+ }]);
245178
+ envValues.LLM_PROVIDER = llmProvider;
245179
+ const providerInfo = getProviderInfo(llmProvider);
245180
+ let collectedApiKey;
245181
+ let collectedProvider;
245182
+ if (llmProvider !== "ollama") {
245183
+ console.log(source_default.yellow(`
245184
+ Get your API key: ${providerInfo.keyUrl}`));
245185
+ await openBrowser(providerInfo.keyUrl);
245186
+ if (llmProvider === "anthropic") {
245187
+ console.log(source_default.gray(' Sign up \u2192 API Keys \u2192 Create Key \u2192 copy the key (starts with "sk-ant-")\n'));
245188
+ } else if (llmProvider === "openai") {
245189
+ console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create new secret key\n"));
245190
+ } else if (llmProvider === "deepseek") {
245191
+ console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create key (new users get free credits)\n"));
245192
+ }
245193
+ const { apiKey } = await esm_default12.prompt([{
245194
+ type: "password",
245195
+ name: "apiKey",
245196
+ message: `Paste your ${providerInfo.name} API key:`,
245197
+ mask: "*",
245198
+ validate: (v2) => {
245199
+ if (!v2.trim()) return "API key required";
245200
+ if (providerInfo.keyPrefix && !v2.startsWith(providerInfo.keyPrefix)) {
245201
+ return `Should start with "${providerInfo.keyPrefix}"`;
245202
+ }
245203
+ return true;
245204
+ }
245205
+ }]);
245206
+ envValues[providerInfo.envKey] = apiKey;
245207
+ collectedApiKey = apiKey;
245208
+ collectedProvider = llmProvider;
245209
+ } else {
245210
+ console.log(source_default.yellow("\n Make sure Ollama is running: https://ollama.ai"));
245211
+ console.log(source_default.gray(" Run: ollama pull qwen2.5:7b\n"));
245212
+ envValues.OLLAMA_BASE_URL = "http://localhost:11434";
245213
+ envValues.OLLAMA_MODEL = "qwen2.5:7b";
245214
+ }
245215
+ step(2, "Pick your companion");
245114
245216
  const characters = getExistingCharacters();
245115
245217
  let characterName;
245116
245218
  let characterCreatedNew = false;
@@ -245137,7 +245239,7 @@ async function runSetupWizard() {
245137
245239
  choices
245138
245240
  }]);
245139
245241
  if (pick === "__new__") {
245140
- const created = await createCharacterFlow();
245242
+ const created = await createCharacterFlow(collectedApiKey, collectedProvider);
245141
245243
  characterName = created.folderName;
245142
245244
  characterCreatedNew = true;
245143
245245
  gender = created.gender;
@@ -245148,14 +245250,17 @@ async function runSetupWizard() {
245148
245250
  }
245149
245251
  } else {
245150
245252
  console.log(source_default.gray(" No companions found yet \u2014 let's create one!\n"));
245151
- const created = await createCharacterFlow();
245253
+ const created = await createCharacterFlow(collectedApiKey, collectedProvider);
245152
245254
  characterName = created.folderName;
245153
245255
  characterCreatedNew = true;
245154
245256
  gender = created.gender;
245155
245257
  }
245258
+ envValues.CHARACTER_NAME = characterName;
245156
245259
  const pro = pronouns(gender);
245157
- const envValues = { CHARACTER_NAME: characterName };
245158
- step(2, `Where do you want to chat with ${characterName}?`);
245260
+ if (characterCreatedNew && collectedApiKey) {
245261
+ await runTestChat(characterName, collectedApiKey, collectedProvider ?? "anthropic");
245262
+ }
245263
+ step(3, `Where do you want to chat with ${characterName}?`);
245159
245264
  const { platforms } = await esm_default12.prompt([{
245160
245265
  type: "checkbox",
245161
245266
  name: "platforms",
@@ -245221,61 +245326,6 @@ async function runSetupWizard() {
245221
245326
  WhatsApp needs no setup! A QR code will appear when ${characterName} starts.`));
245222
245327
  console.log(source_default.gray(" Scan it with WhatsApp \u2192 Linked Devices on your phone."));
245223
245328
  }
245224
- step(3, `Give ${characterName} a brain`);
245225
- console.log(source_default.gray(" Pick whichever you already have a key for.\n"));
245226
- const providerChoices = [
245227
- PROVIDER_INFO.find((p2) => p2.id === "anthropic"),
245228
- PROVIDER_INFO.find((p2) => p2.id === "openai"),
245229
- PROVIDER_INFO.find((p2) => p2.id === "deepseek"),
245230
- PROVIDER_INFO.find((p2) => p2.isLocal)
245231
- ].filter(Boolean);
245232
- const { llmProvider } = await esm_default12.prompt([{
245233
- type: "list",
245234
- name: "llmProvider",
245235
- message: "Which AI provider?",
245236
- choices: providerChoices.map((p2) => ({
245237
- name: `${p2.emoji} ${p2.name}
245238
- ${source_default.gray(p2.tagline)}`,
245239
- value: p2.id,
245240
- short: p2.name
245241
- }))
245242
- }]);
245243
- envValues.LLM_PROVIDER = llmProvider;
245244
- const providerInfo = getProviderInfo(llmProvider);
245245
- if (llmProvider !== "ollama") {
245246
- console.log(source_default.yellow(`
245247
- Get your API key: ${providerInfo.keyUrl}`));
245248
- await openBrowser(providerInfo.keyUrl);
245249
- if (llmProvider === "anthropic") {
245250
- console.log(source_default.gray(' Sign up \u2192 API Keys \u2192 Create Key \u2192 copy the key (starts with "sk-ant-")\n'));
245251
- } else if (llmProvider === "openai") {
245252
- console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create new secret key\n"));
245253
- } else if (llmProvider === "deepseek") {
245254
- console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create key (new users get free credits)\n"));
245255
- }
245256
- const { apiKey } = await esm_default12.prompt([{
245257
- type: "password",
245258
- name: "apiKey",
245259
- message: `Paste your ${providerInfo.name} API key:`,
245260
- mask: "*",
245261
- validate: (v2) => {
245262
- if (!v2.trim()) return "API key required";
245263
- if (providerInfo.keyPrefix && !v2.startsWith(providerInfo.keyPrefix)) {
245264
- return `Should start with "${providerInfo.keyPrefix}"`;
245265
- }
245266
- return true;
245267
- }
245268
- }]);
245269
- envValues[providerInfo.envKey] = apiKey;
245270
- if (characterCreatedNew) {
245271
- await runTestChat(characterName, apiKey, llmProvider);
245272
- }
245273
- } else {
245274
- console.log(source_default.yellow("\n Make sure Ollama is running: https://ollama.ai"));
245275
- console.log(source_default.gray(" Run: ollama pull qwen2.5:7b\n"));
245276
- envValues.OLLAMA_BASE_URL = "http://localhost:11434";
245277
- envValues.OLLAMA_MODEL = "qwen2.5:7b";
245278
- }
245279
245329
  step(4, "Extras");
245280
245330
  console.log(source_default.gray(" All optional \u2014 skip everything by pressing Enter.\n"));
245281
245331
  const quickChoices = [
@@ -245520,7 +245570,7 @@ function parseIdentity(identityPath) {
245520
245570
  const age = (ageMatch == null ? void 0 : ageMatch[1]) ?? "??";
245521
245571
  const fromMatch = content.match(/\*\*From:\*\*\s*(.+)/i);
245522
245572
  const locationRaw = ((_b2 = fromMatch == null ? void 0 : fromMatch[1]) == null ? void 0 : _b2.trim()) ?? "Unknown";
245523
- const location = locationRaw.replace(/\s*\(.*\)\s*$/, "").trim();
245573
+ const location = locationRaw.replace(/\s*\(.*\)/, "").split(" \u2014 ")[0].trim();
245524
245574
  const hobbiesMatch = content.match(/\*\*Hobbies:\*\*\s*(.+)/i);
245525
245575
  const hobbiesRaw = (hobbiesMatch == null ? void 0 : hobbiesMatch[1]) ?? "";
245526
245576
  const tags = hobbiesRaw.split(",").map((t2) => t2.trim()).filter(Boolean).slice(0, 5);
@@ -245549,18 +245599,20 @@ function pickGradient(name) {
245549
245599
  return gradients[hash % gradients.length];
245550
245600
  }
245551
245601
  function createSvgOverlay(data, gradient) {
245552
- const textX = PORTRAIT_X + PORTRAIT_SIZE + 60;
245553
- const tagY = 280;
245554
- const maxTagX = WIDTH - 40;
245602
+ const textX = PORTRAIT_X + PORTRAIT_SIZE + 50;
245603
+ const maxTagX = WIDTH - 30;
245604
+ const nameY = 210;
245605
+ const metaY = nameY + 45;
245606
+ const tagStartY = metaY + 50;
245555
245607
  let inlineTags = "";
245556
245608
  let offsetX = textX;
245557
- let rowY = tagY;
245558
- const rowHeight = 34;
245609
+ let rowY = tagStartY;
245610
+ const rowHeight = 30;
245559
245611
  const maxRows = 2;
245560
245612
  let currentRow = 1;
245561
245613
  for (const tag of data.tags) {
245562
- const label = tag.length > 22 ? tag.slice(0, 21) + "\u2026" : tag;
245563
- const w2 = label.length * 8.5 + 24;
245614
+ const label = tag.length > 18 ? tag.slice(0, 17) + "\u2026" : tag;
245615
+ const w2 = label.length * 7.5 + 16;
245564
245616
  if (offsetX + w2 > maxTagX) {
245565
245617
  if (currentRow >= maxRows) break;
245566
245618
  currentRow++;
@@ -245568,12 +245620,12 @@ function createSvgOverlay(data, gradient) {
245568
245620
  rowY += rowHeight;
245569
245621
  }
245570
245622
  inlineTags += `
245571
- <rect x="${offsetX - 2}" y="${rowY - 16}" width="${w2}" height="26" rx="13" fill="rgba(255,255,255,0.10)" />
245572
- <text x="${offsetX + 10}" y="${rowY}" font-family="system-ui, -apple-system, sans-serif" font-size="13" fill="#c0c0c0">${escapeXml(label)}</text>
245623
+ <rect x="${offsetX}" y="${rowY - 14}" width="${w2}" height="22" rx="11" fill="rgba(255,255,255,0.10)" />
245624
+ <text x="${offsetX + 8}" y="${rowY + 1}" font-family="system-ui, -apple-system, sans-serif" font-size="12" fill="#c0c0c0">${escapeXml(label)}</text>
245573
245625
  `;
245574
- offsetX += w2 + 8;
245626
+ offsetX += w2 + 6;
245575
245627
  }
245576
- const descriptionY = tagY + currentRow * rowHeight + 16;
245628
+ const descriptionY = tagStartY + currentRow * rowHeight + 14;
245577
245629
  return `<svg width="${WIDTH}" height="${HEIGHT}" xmlns="http://www.w3.org/2000/svg">
245578
245630
  <defs>
245579
245631
  <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
@@ -245585,28 +245637,21 @@ function createSvgOverlay(data, gradient) {
245585
245637
  </clipPath>
245586
245638
  </defs>
245587
245639
 
245588
- <!-- Background -->
245589
245640
  <rect width="${WIDTH}" height="${HEIGHT}" fill="url(#bg)" />
245590
245641
 
245591
- <!-- Subtle border around portrait area -->
245592
- <circle cx="${PORTRAIT_X + PORTRAIT_SIZE / 2}" cy="${PORTRAIT_Y + PORTRAIT_SIZE / 2}" r="${PORTRAIT_SIZE / 2 + 3}" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="2" />
245642
+ <circle cx="${PORTRAIT_X + PORTRAIT_SIZE / 2}" cy="${PORTRAIT_Y + PORTRAIT_SIZE / 2}" r="${PORTRAIT_SIZE / 2 + 2}" fill="none" stroke="rgba(255,255,255,0.12)" stroke-width="2" />
245593
245643
 
245594
- <!-- Character Name -->
245595
- <text x="${textX}" y="170" font-family="system-ui, -apple-system, sans-serif" font-size="48" font-weight="bold" fill="white">${escapeXml(data.name)}</text>
245644
+ <text x="${textX}" y="${nameY}" font-family="system-ui, -apple-system, sans-serif" font-size="44" font-weight="bold" fill="white">${escapeXml(data.name)}</text>
245596
245645
 
245597
- <!-- Age + Location -->
245598
- <text x="${textX}" y="215" font-family="system-ui, -apple-system, sans-serif" font-size="18" fill="#a0a0a0">${escapeXml(data.age)} \xB7 ${escapeXml(truncate(data.location, 60))}</text>
245646
+ <text x="${textX}" y="${metaY}" font-family="system-ui, -apple-system, sans-serif" font-size="16" fill="#a0a0a0">${escapeXml(data.age)} \xB7 ${escapeXml(truncate(data.location, 35))}</text>
245599
245647
 
245600
- <!-- Personality Tags (inline) -->
245601
245648
  ${inlineTags}
245602
245649
 
245603
- <!-- One-line description -->
245604
- <text x="${textX}" y="${descriptionY}" font-family="system-ui, -apple-system, sans-serif" font-size="15" fill="#b0b0b0" font-style="italic">${escapeXml(truncate(data.description, 70))}</text>
245650
+ <text x="${textX}" y="${descriptionY}" font-family="system-ui, -apple-system, sans-serif" font-size="14" fill="#b0b0b0" font-style="italic">${escapeXml(truncate(data.description, 60))}</text>
245605
245651
 
245606
- <!-- Bottom bar -->
245607
- <rect x="0" y="${HEIGHT - 50}" width="${WIDTH}" height="50" fill="rgba(0,0,0,0.3)" />
245608
- <text x="40" y="${HEIGHT - 20}" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="bold" fill="#ff69b4">Opencrush</text>
245609
- <text x="${WIDTH - 40}" y="${HEIGHT - 20}" font-family="system-ui, -apple-system, sans-serif" font-size="13" fill="#888" text-anchor="end">github.com/Hollandchirs/Opencrush</text>
245652
+ <rect x="0" y="${HEIGHT - 44}" width="${WIDTH}" height="44" fill="rgba(0,0,0,0.3)" />
245653
+ <text x="36" y="${HEIGHT - 17}" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="bold" fill="#ff69b4">Opencrush</text>
245654
+ <text x="${WIDTH - 36}" y="${HEIGHT - 17}" font-family="system-ui, -apple-system, sans-serif" font-size="12" fill="#888" text-anchor="end">github.com/Hollandchirs/Opencrush</text>
245610
245655
  </svg>`;
245611
245656
  }
245612
245657
  function escapeXml(str2) {
@@ -245639,7 +245684,7 @@ async function generateCard(characterName) {
245639
245684
  }
245640
245685
  const svgOverlay = createSvgOverlay(data, gradient);
245641
245686
  const svgBuffer = Buffer.from(svgOverlay);
245642
- let card = sharp(svgBuffer, { density: 150 }).resize(WIDTH, HEIGHT);
245687
+ let card = sharp(svgBuffer, { density: 300 }).resize(WIDTH, HEIGHT);
245643
245688
  if (refImagePath) {
245644
245689
  const circleMask = Buffer.from(
245645
245690
  `<svg width="${PORTRAIT_SIZE}" height="${PORTRAIT_SIZE}">
@@ -245672,9 +245717,9 @@ var init_card = __esm({
245672
245717
  init_source();
245673
245718
  WIDTH = 1200;
245674
245719
  HEIGHT = 630;
245675
- PORTRAIT_SIZE = 280;
245720
+ PORTRAIT_SIZE = 260;
245676
245721
  PORTRAIT_X = 80;
245677
- PORTRAIT_Y = 100;
245722
+ PORTRAIT_Y = (HEIGHT - PORTRAIT_SIZE) / 2 - 10;
245678
245723
  }
245679
245724
  });
245680
245725
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencrush",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Your AI companion lives on your device. She watches dramas, listens to music, and thinks of you.",
5
5
  "bin": {
6
6
  "opencrush": "dist/index.js"