replicas-engine 0.1.19 → 0.1.21

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/src/index.js +335 -31
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import "dotenv/config";
5
5
  import { serve } from "@hono/node-server";
6
6
  import { Hono as Hono3 } from "hono";
7
- import { readFile as readFile2 } from "fs/promises";
7
+ import { readFile as readFile3 } from "fs/promises";
8
8
  import { execSync as execSync2 } from "child_process";
9
9
 
10
10
  // src/middleware/auth.ts
@@ -34,6 +34,7 @@ import { Hono } from "hono";
34
34
 
35
35
  // src/services/codex-manager.ts
36
36
  import { Codex } from "@openai/codex-sdk";
37
+ import { randomUUID } from "crypto";
37
38
 
38
39
  // src/utils/jsonl-reader.ts
39
40
  import { readFile } from "fs/promises";
@@ -55,7 +56,7 @@ async function readJSONL(filePath) {
55
56
  }
56
57
 
57
58
  // src/services/codex-manager.ts
58
- import { readdir, stat } from "fs/promises";
59
+ import { readdir, stat, writeFile, mkdir } from "fs/promises";
59
60
  import { join } from "path";
60
61
  import { homedir } from "os";
61
62
 
@@ -511,13 +512,14 @@ var MessageQueue = class {
511
512
  * Add a message to the queue or start processing immediately if not busy
512
513
  * @returns Object indicating whether the message was queued or started processing
513
514
  */
514
- async enqueue(message, model, customInstructions) {
515
+ async enqueue(message, model, customInstructions, images) {
515
516
  const messageId = this.generateMessageId();
516
517
  const queuedMessage = {
517
518
  id: messageId,
518
519
  message,
519
520
  model,
520
521
  customInstructions,
522
+ images,
521
523
  queuedAt: (/* @__PURE__ */ new Date()).toISOString()
522
524
  };
523
525
  if (this.processing) {
@@ -542,7 +544,8 @@ var MessageQueue = class {
542
544
  await this.processMessage(
543
545
  queuedMessage.message,
544
546
  queuedMessage.model,
545
- queuedMessage.customInstructions
547
+ queuedMessage.customInstructions,
548
+ queuedMessage.images
546
549
  );
547
550
  } catch (error) {
548
551
  console.error("[MessageQueue] Error processing message:", error);
@@ -601,6 +604,66 @@ var MessageQueue = class {
601
604
  }
602
605
  };
603
606
 
607
+ // src/utils/image-utils.ts
608
+ function inferMediaType(url, contentType) {
609
+ if (contentType) {
610
+ const normalized = contentType.toLowerCase().split(";")[0].trim();
611
+ if (normalized === "image/png" || normalized === "image/jpeg" || normalized === "image/gif" || normalized === "image/webp") {
612
+ return normalized;
613
+ }
614
+ if (normalized === "image/jpg") {
615
+ return "image/jpeg";
616
+ }
617
+ }
618
+ const urlLower = url.toLowerCase();
619
+ if (urlLower.includes(".png")) return "image/png";
620
+ if (urlLower.includes(".jpg") || urlLower.includes(".jpeg")) return "image/jpeg";
621
+ if (urlLower.includes(".gif")) return "image/gif";
622
+ if (urlLower.includes(".webp")) return "image/webp";
623
+ return "image/jpeg";
624
+ }
625
+ async function fetchImageAsBase64(url) {
626
+ const headers = {};
627
+ if (new URL(url).hostname === "uploads.linear.app") {
628
+ const token = process.env.LINEAR_TOKEN;
629
+ if (token) {
630
+ headers["Authorization"] = `Bearer ${token}`;
631
+ }
632
+ }
633
+ const response = await fetch(url, { headers });
634
+ if (!response.ok) {
635
+ throw new Error(`Failed to fetch image from ${url}: ${response.status} ${response.statusText}`);
636
+ }
637
+ const contentType = response.headers.get("content-type");
638
+ const mediaType = inferMediaType(url, contentType);
639
+ const arrayBuffer = await response.arrayBuffer();
640
+ const buffer = Buffer.from(arrayBuffer);
641
+ const data = buffer.toString("base64");
642
+ return {
643
+ type: "base64",
644
+ media_type: mediaType,
645
+ data
646
+ };
647
+ }
648
+ async function normalizeImages(images) {
649
+ const normalized = [];
650
+ for (const image of images) {
651
+ if (image.source.type === "base64") {
652
+ normalized.push({
653
+ type: "image",
654
+ source: image.source
655
+ });
656
+ } else if (image.source.type === "url") {
657
+ const base64Source = await fetchImageAsBase64(image.source.url);
658
+ normalized.push({
659
+ type: "image",
660
+ source: base64Source
661
+ });
662
+ }
663
+ }
664
+ return normalized;
665
+ }
666
+
604
667
  // src/services/codex-manager.ts
605
668
  var CodexManager = class {
606
669
  codex;
@@ -608,6 +671,8 @@ var CodexManager = class {
608
671
  currentThread = null;
609
672
  workingDirectory;
610
673
  messageQueue;
674
+ baseSystemPrompt;
675
+ tempImageDir;
611
676
  constructor(workingDirectory) {
612
677
  this.codex = new Codex();
613
678
  if (workingDirectory) {
@@ -621,6 +686,7 @@ var CodexManager = class {
621
686
  this.workingDirectory = workspaceHome;
622
687
  }
623
688
  }
689
+ this.tempImageDir = join(homedir(), ".replicas", "codex", "temp-images");
624
690
  this.messageQueue = new MessageQueue(this.processMessageInternal.bind(this));
625
691
  }
626
692
  isProcessing() {
@@ -631,8 +697,8 @@ var CodexManager = class {
631
697
  * If already processing, adds to queue.
632
698
  * @returns Object with queued status, messageId, and position in queue
633
699
  */
634
- async enqueueMessage(message, model, customInstructions) {
635
- return this.messageQueue.enqueue(message, model, customInstructions);
700
+ async enqueueMessage(message, model, customInstructions, images) {
701
+ return this.messageQueue.enqueue(message, model, customInstructions, images);
636
702
  }
637
703
  /**
638
704
  * Get the current queue status
@@ -640,19 +706,54 @@ var CodexManager = class {
640
706
  getQueueStatus() {
641
707
  return this.messageQueue.getStatus();
642
708
  }
709
+ /**
710
+ * Set the base system prompt from replicas.json
711
+ * This will be combined with any custom instructions passed to individual messages
712
+ */
713
+ setBaseSystemPrompt(prompt) {
714
+ this.baseSystemPrompt = prompt;
715
+ }
716
+ /**
717
+ * Get the current base system prompt
718
+ */
719
+ getBaseSystemPrompt() {
720
+ return this.baseSystemPrompt;
721
+ }
643
722
  /**
644
723
  * Legacy sendMessage method - now uses the queue internally
645
724
  * @deprecated Use enqueueMessage for better control over queue status
646
725
  */
647
- async sendMessage(message, model, customInstructions) {
648
- await this.enqueueMessage(message, model, customInstructions);
726
+ async sendMessage(message, model, customInstructions, images) {
727
+ await this.enqueueMessage(message, model, customInstructions, images);
728
+ }
729
+ /**
730
+ * Helper method to save normalized images to temp files for Codex SDK
731
+ * @returns Array of temp file paths
732
+ */
733
+ async saveImagesToTempFiles(images) {
734
+ await mkdir(this.tempImageDir, { recursive: true });
735
+ const tempPaths = [];
736
+ for (const image of images) {
737
+ const ext = image.source.media_type.split("/")[1] || "png";
738
+ const filename = `img_${randomUUID()}.${ext}`;
739
+ const filepath = join(this.tempImageDir, filename);
740
+ const buffer = Buffer.from(image.source.data, "base64");
741
+ await writeFile(filepath, buffer);
742
+ tempPaths.push(filepath);
743
+ }
744
+ return tempPaths;
649
745
  }
650
746
  /**
651
747
  * Internal method that actually processes the message
652
748
  */
653
- async processMessageInternal(message, model, customInstructions) {
749
+ async processMessageInternal(message, model, customInstructions, images) {
654
750
  const linearSessionId = process.env.LINEAR_SESSION_ID;
751
+ let tempImagePaths = [];
655
752
  try {
753
+ if (images && images.length > 0) {
754
+ const normalizedImages = await normalizeImages(images);
755
+ tempImagePaths = await this.saveImagesToTempFiles(normalizedImages);
756
+ }
656
757
  if (!this.currentThread) {
657
758
  if (this.currentThreadId) {
658
759
  this.currentThread = this.codex.resumeThread(this.currentThreadId, {
@@ -668,8 +769,16 @@ var CodexManager = class {
668
769
  sandboxMode: "danger-full-access",
669
770
  model: model || "gpt-5.1-codex"
670
771
  });
671
- if (customInstructions) {
672
- message = customInstructions + "\n" + message;
772
+ let combinedInstructions;
773
+ if (this.baseSystemPrompt && customInstructions) {
774
+ combinedInstructions = `${this.baseSystemPrompt}
775
+
776
+ ${customInstructions}`;
777
+ } else {
778
+ combinedInstructions = this.baseSystemPrompt || customInstructions;
779
+ }
780
+ if (combinedInstructions) {
781
+ message = combinedInstructions + "\n" + message;
673
782
  }
674
783
  const { events: events2 } = await this.currentThread.runStreamed("Hello");
675
784
  for await (const event of events2) {
@@ -683,7 +792,17 @@ var CodexManager = class {
683
792
  }
684
793
  }
685
794
  }
686
- const { events } = await this.currentThread.runStreamed(message);
795
+ let input;
796
+ if (tempImagePaths.length > 0) {
797
+ const inputItems = [
798
+ { type: "text", text: message },
799
+ ...tempImagePaths.map((path5) => ({ type: "local_image", path: path5 }))
800
+ ];
801
+ input = inputItems;
802
+ } else {
803
+ input = message;
804
+ }
805
+ const { events } = await this.currentThread.runStreamed(input);
687
806
  for await (const event of events) {
688
807
  if (linearSessionId) {
689
808
  const linearEvent = convertCodexEvent(event, linearSessionId);
@@ -814,11 +933,11 @@ var codexManager = new CodexManager();
814
933
  codex.post("/send", async (c) => {
815
934
  try {
816
935
  const body = await c.req.json();
817
- const { message, model, customInstructions } = body;
936
+ const { message, model, customInstructions, images } = body;
818
937
  if (!message || typeof message !== "string") {
819
938
  return c.json({ error: "Message is required and must be a string" }, 400);
820
939
  }
821
- const result = await codexManager.enqueueMessage(message, model, customInstructions);
940
+ const result = await codexManager.enqueueMessage(message, model, customInstructions, images);
822
941
  const response = {
823
942
  success: true,
824
943
  message: result.queued ? `Message queued at position ${result.position}` : "Message sent successfully",
@@ -937,7 +1056,7 @@ import {
937
1056
  query
938
1057
  } from "@anthropic-ai/claude-agent-sdk";
939
1058
  import { join as join2 } from "path";
940
- import { mkdir, appendFile, rm } from "fs/promises";
1059
+ import { mkdir as mkdir2, appendFile, rm } from "fs/promises";
941
1060
  import { homedir as homedir2 } from "os";
942
1061
  var ClaudeManager = class {
943
1062
  workingDirectory;
@@ -945,6 +1064,7 @@ var ClaudeManager = class {
945
1064
  sessionId = null;
946
1065
  initialized;
947
1066
  messageQueue;
1067
+ baseSystemPrompt;
948
1068
  constructor(workingDirectory) {
949
1069
  if (workingDirectory) {
950
1070
  this.workingDirectory = workingDirectory;
@@ -969,9 +1089,9 @@ var ClaudeManager = class {
969
1089
  * If already processing, adds to queue.
970
1090
  * @returns Object with queued status, messageId, and position in queue
971
1091
  */
972
- async enqueueMessage(message, model, customInstructions) {
1092
+ async enqueueMessage(message, model, customInstructions, images) {
973
1093
  await this.initialized;
974
- return this.messageQueue.enqueue(message, model, customInstructions);
1094
+ return this.messageQueue.enqueue(message, model, customInstructions, images);
975
1095
  }
976
1096
  /**
977
1097
  * Get the current queue status
@@ -979,33 +1099,60 @@ var ClaudeManager = class {
979
1099
  getQueueStatus() {
980
1100
  return this.messageQueue.getStatus();
981
1101
  }
1102
+ /**
1103
+ * Set the base system prompt from replicas.json
1104
+ * This will be combined with any custom instructions passed to individual messages
1105
+ */
1106
+ setBaseSystemPrompt(prompt) {
1107
+ this.baseSystemPrompt = prompt;
1108
+ }
1109
+ /**
1110
+ * Get the current base system prompt
1111
+ */
1112
+ getBaseSystemPrompt() {
1113
+ return this.baseSystemPrompt;
1114
+ }
982
1115
  /**
983
1116
  * Legacy sendMessage method - now uses the queue internally
984
1117
  * @deprecated Use enqueueMessage for better control over queue status
985
1118
  */
986
- async sendMessage(message, model, customInstructions) {
987
- await this.enqueueMessage(message, model, customInstructions);
1119
+ async sendMessage(message, model, customInstructions, images) {
1120
+ await this.enqueueMessage(message, model, customInstructions, images);
988
1121
  }
989
1122
  /**
990
1123
  * Internal method that actually processes the message
991
1124
  */
992
- async processMessageInternal(message, model, customInstructions) {
1125
+ async processMessageInternal(message, model, customInstructions, images) {
993
1126
  const linearSessionId = process.env.LINEAR_SESSION_ID;
994
1127
  if (!message || !message.trim()) {
995
1128
  throw new Error("Message cannot be empty");
996
1129
  }
997
1130
  await this.initialized;
998
1131
  try {
1132
+ const content = [
1133
+ {
1134
+ type: "text",
1135
+ text: message
1136
+ }
1137
+ ];
1138
+ if (images && images.length > 0) {
1139
+ const normalizedImages = await normalizeImages(images);
1140
+ for (const image of normalizedImages) {
1141
+ content.push({
1142
+ type: "image",
1143
+ source: {
1144
+ type: "base64",
1145
+ media_type: image.source.media_type,
1146
+ data: image.source.data
1147
+ }
1148
+ });
1149
+ }
1150
+ }
999
1151
  const userMessage = {
1000
1152
  type: "user",
1001
1153
  message: {
1002
1154
  role: "user",
1003
- content: [
1004
- {
1005
- type: "text",
1006
- text: message
1007
- }
1008
- ]
1155
+ content
1009
1156
  },
1010
1157
  parent_tool_use_id: null,
1011
1158
  session_id: this.sessionId ?? ""
@@ -1014,6 +1161,14 @@ var ClaudeManager = class {
1014
1161
  const promptIterable = (async function* () {
1015
1162
  yield userMessage;
1016
1163
  })();
1164
+ let combinedInstructions;
1165
+ if (this.baseSystemPrompt && customInstructions) {
1166
+ combinedInstructions = `${this.baseSystemPrompt}
1167
+
1168
+ ${customInstructions}`;
1169
+ } else {
1170
+ combinedInstructions = this.baseSystemPrompt || customInstructions;
1171
+ }
1017
1172
  const response = query({
1018
1173
  prompt: promptIterable,
1019
1174
  options: {
@@ -1025,7 +1180,7 @@ var ClaudeManager = class {
1025
1180
  systemPrompt: {
1026
1181
  type: "preset",
1027
1182
  preset: "claude_code",
1028
- append: customInstructions
1183
+ append: combinedInstructions
1029
1184
  },
1030
1185
  env: process.env,
1031
1186
  model: model || "opus"
@@ -1084,7 +1239,7 @@ var ClaudeManager = class {
1084
1239
  }
1085
1240
  async initialize() {
1086
1241
  const historyDir = join2(homedir2(), ".replicas", "claude");
1087
- await mkdir(historyDir, { recursive: true });
1242
+ await mkdir2(historyDir, { recursive: true });
1088
1243
  }
1089
1244
  async handleMessage(message) {
1090
1245
  if ("session_id" in message && message.session_id && !this.sessionId) {
@@ -1109,11 +1264,11 @@ var claudeManager = new ClaudeManager();
1109
1264
  claude.post("/send", async (c) => {
1110
1265
  try {
1111
1266
  const body = await c.req.json();
1112
- const { message, model, customInstructions } = body;
1267
+ const { message, model, customInstructions, images } = body;
1113
1268
  if (!message || typeof message !== "string") {
1114
1269
  return c.json({ error: "Message is required and must be a string" }, 400);
1115
1270
  }
1116
- const result = await claudeManager.enqueueMessage(message, model, customInstructions);
1271
+ const result = await claudeManager.enqueueMessage(message, model, customInstructions, images);
1117
1272
  const response = {
1118
1273
  success: true,
1119
1274
  message: result.queued ? `Message queued at position ${result.position}` : "Message sent successfully",
@@ -1568,6 +1723,148 @@ async function initializeGitRepository() {
1568
1723
  }
1569
1724
  }
1570
1725
 
1726
+ // src/services/replicas-config.ts
1727
+ import { readFile as readFile2 } from "fs/promises";
1728
+ import { existsSync as existsSync2 } from "fs";
1729
+ import { join as join3 } from "path";
1730
+ import { homedir as homedir3 } from "os";
1731
+ import { exec } from "child_process";
1732
+ import { promisify } from "util";
1733
+ var execAsync = promisify(exec);
1734
+ var ReplicasConfigService = class {
1735
+ config = null;
1736
+ workingDirectory;
1737
+ hooksExecuted = false;
1738
+ constructor() {
1739
+ const repoName = process.env.REPLICAS_REPO_NAME;
1740
+ const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME || homedir3();
1741
+ if (repoName) {
1742
+ this.workingDirectory = join3(workspaceHome, "workspaces", repoName);
1743
+ } else {
1744
+ this.workingDirectory = workspaceHome;
1745
+ }
1746
+ }
1747
+ /**
1748
+ * Initialize the service by reading replicas.json and executing start hooks
1749
+ */
1750
+ async initialize() {
1751
+ await this.loadConfig();
1752
+ await this.executeStartHooks();
1753
+ }
1754
+ /**
1755
+ * Load and parse the replicas.json config file
1756
+ */
1757
+ async loadConfig() {
1758
+ const configPath = join3(this.workingDirectory, "replicas.json");
1759
+ if (!existsSync2(configPath)) {
1760
+ console.log("No replicas.json found in workspace directory");
1761
+ this.config = null;
1762
+ return;
1763
+ }
1764
+ try {
1765
+ const data = await readFile2(configPath, "utf-8");
1766
+ const config = JSON.parse(data);
1767
+ if (config.copy && !Array.isArray(config.copy)) {
1768
+ throw new Error('Invalid replicas.json: "copy" must be an array of file paths');
1769
+ }
1770
+ if (config.ports && !Array.isArray(config.ports)) {
1771
+ throw new Error('Invalid replicas.json: "ports" must be an array of port numbers');
1772
+ }
1773
+ if (config.ports && !config.ports.every((p) => typeof p === "number")) {
1774
+ throw new Error("Invalid replicas.json: all ports must be numbers");
1775
+ }
1776
+ if (config.systemPrompt && typeof config.systemPrompt !== "string") {
1777
+ throw new Error('Invalid replicas.json: "systemPrompt" must be a string');
1778
+ }
1779
+ if (config.startHook) {
1780
+ if (typeof config.startHook !== "object" || Array.isArray(config.startHook)) {
1781
+ throw new Error('Invalid replicas.json: "startHook" must be an object with "commands" array');
1782
+ }
1783
+ if (!Array.isArray(config.startHook.commands)) {
1784
+ throw new Error('Invalid replicas.json: "startHook.commands" must be an array of shell commands');
1785
+ }
1786
+ if (!config.startHook.commands.every((cmd) => typeof cmd === "string")) {
1787
+ throw new Error("Invalid replicas.json: all startHook.commands entries must be strings");
1788
+ }
1789
+ if (config.startHook.timeout !== void 0 && (typeof config.startHook.timeout !== "number" || config.startHook.timeout <= 0)) {
1790
+ throw new Error('Invalid replicas.json: "startHook.timeout" must be a positive number');
1791
+ }
1792
+ }
1793
+ this.config = config;
1794
+ console.log("Loaded replicas.json config:", {
1795
+ hasSystemPrompt: !!config.systemPrompt,
1796
+ startHookCount: config.startHook?.commands.length ?? 0
1797
+ });
1798
+ } catch (error) {
1799
+ if (error instanceof SyntaxError) {
1800
+ console.error("Failed to parse replicas.json:", error.message);
1801
+ } else if (error instanceof Error) {
1802
+ console.error("Error loading replicas.json:", error.message);
1803
+ }
1804
+ this.config = null;
1805
+ }
1806
+ }
1807
+ /**
1808
+ * Execute all start hooks defined in replicas.json
1809
+ */
1810
+ async executeStartHooks() {
1811
+ if (this.hooksExecuted) {
1812
+ console.log("Start hooks already executed, skipping");
1813
+ return;
1814
+ }
1815
+ const startHookConfig = this.config?.startHook;
1816
+ if (!startHookConfig || startHookConfig.commands.length === 0) {
1817
+ this.hooksExecuted = true;
1818
+ return;
1819
+ }
1820
+ const timeout = startHookConfig.timeout ?? 3e5;
1821
+ const hooks = startHookConfig.commands;
1822
+ console.log(`Executing ${hooks.length} start hook(s) with timeout ${timeout}ms...`);
1823
+ for (const hook of hooks) {
1824
+ try {
1825
+ console.log(`Running start hook: ${hook}`);
1826
+ const { stdout, stderr } = await execAsync(hook, {
1827
+ cwd: this.workingDirectory,
1828
+ timeout,
1829
+ env: process.env
1830
+ });
1831
+ if (stdout) {
1832
+ console.log(`[${hook}] stdout:`, stdout);
1833
+ }
1834
+ if (stderr) {
1835
+ console.warn(`[${hook}] stderr:`, stderr);
1836
+ }
1837
+ console.log(`Start hook completed: ${hook}`);
1838
+ } catch (error) {
1839
+ if (error instanceof Error) {
1840
+ console.error(`Start hook failed: ${hook}`, error.message);
1841
+ }
1842
+ }
1843
+ }
1844
+ this.hooksExecuted = true;
1845
+ console.log("All start hooks completed");
1846
+ }
1847
+ /**
1848
+ * Get the system prompt from replicas.json
1849
+ */
1850
+ getSystemPrompt() {
1851
+ return this.config?.systemPrompt;
1852
+ }
1853
+ /**
1854
+ * Get the full config object
1855
+ */
1856
+ getConfig() {
1857
+ return this.config;
1858
+ }
1859
+ /**
1860
+ * Check if start hooks have been executed
1861
+ */
1862
+ hasExecutedHooks() {
1863
+ return this.hooksExecuted;
1864
+ }
1865
+ };
1866
+ var replicasConfigService = new ReplicasConfigService();
1867
+
1571
1868
  // src/index.ts
1572
1869
  var READY_MESSAGE = "========= REPLICAS WORKSPACE READY ==========";
1573
1870
  var COMPLETION_MESSAGE = "========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========";
@@ -1583,7 +1880,7 @@ function checkActiveSSHSessions() {
1583
1880
  var app = new Hono3();
1584
1881
  app.get("/health", async (c) => {
1585
1882
  try {
1586
- const logContent = await readFile2("/var/log/cloud-init-output.log", "utf-8");
1883
+ const logContent = await readFile3("/var/log/cloud-init-output.log", "utf-8");
1587
1884
  let status;
1588
1885
  if (logContent.includes(COMPLETION_MESSAGE)) {
1589
1886
  status = "active";
@@ -1648,6 +1945,13 @@ serve(
1648
1945
  } else {
1649
1946
  console.warn(`Git initialization warning: ${gitResult.error}`);
1650
1947
  }
1948
+ await replicasConfigService.initialize();
1949
+ const systemPrompt = replicasConfigService.getSystemPrompt();
1950
+ if (systemPrompt) {
1951
+ claudeManager.setBaseSystemPrompt(systemPrompt);
1952
+ codexManager.setBaseSystemPrompt(systemPrompt);
1953
+ console.log("Applied system prompt from replicas.json to Claude and Codex managers");
1954
+ }
1651
1955
  await githubTokenManager.start();
1652
1956
  await claudeTokenManager.start();
1653
1957
  await codexTokenManager.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",