amai 0.0.4 → 0.0.6

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/cli.cjs CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var pc4 = require('picocolors');
5
- var WebSocket = require('ws');
6
- var events = require('events');
4
+ var pc5 = require('picocolors');
5
+ var WebSocket2 = require('ws');
7
6
  var zod = require('zod');
8
7
  var promises = require('fs/promises');
9
8
  var path9 = require('path');
10
9
  var fs3 = require('fs');
11
10
  var os2 = require('os');
11
+ var crypto = require('crypto');
12
12
  var child_process = require('child_process');
13
13
  var util = require('util');
14
14
  var hono = require('hono');
@@ -20,8 +20,8 @@ var readline = require('readline');
20
20
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
21
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
22
 
23
- var pc4__default = /*#__PURE__*/_interopDefault(pc4);
24
- var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
23
+ var pc5__default = /*#__PURE__*/_interopDefault(pc5);
24
+ var WebSocket2__default = /*#__PURE__*/_interopDefault(WebSocket2);
25
25
  var path9__default = /*#__PURE__*/_interopDefault(path9);
26
26
  var fs3__default = /*#__PURE__*/_interopDefault(fs3);
27
27
  var os2__default = /*#__PURE__*/_interopDefault(os2);
@@ -612,15 +612,133 @@ function calculateDiffStats(oldContent, newContent) {
612
612
  }
613
613
  return { linesAdded, linesRemoved };
614
614
  }
615
-
616
- // src/tools/apply-patch.ts
615
+ var CheckpointStore = class {
616
+ checkpoints = /* @__PURE__ */ new Map();
617
+ fileCheckpoints = /* @__PURE__ */ new Map();
618
+ // filePath -> checkpointIds
619
+ /**
620
+ * Compute SHA-256 hash of content
621
+ */
622
+ computeHash(content) {
623
+ return crypto.createHash("sha256").update(content, "utf8").digest("hex");
624
+ }
625
+ /**
626
+ * Create a new checkpoint before an edit operation
627
+ */
628
+ createCheckpoint(id, filePath, beforeContent, afterContent) {
629
+ const checkpoint = {
630
+ id,
631
+ filePath,
632
+ beforeContent,
633
+ afterContent,
634
+ beforeHash: this.computeHash(beforeContent),
635
+ afterHash: this.computeHash(afterContent),
636
+ timestamp: Date.now()
637
+ };
638
+ this.checkpoints.set(id, checkpoint);
639
+ const fileCheckpointIds = this.fileCheckpoints.get(filePath) || [];
640
+ fileCheckpointIds.push(id);
641
+ this.fileCheckpoints.set(filePath, fileCheckpointIds);
642
+ return checkpoint;
643
+ }
644
+ /**
645
+ * Get a checkpoint by ID
646
+ */
647
+ getCheckpoint(id) {
648
+ return this.checkpoints.get(id);
649
+ }
650
+ /**
651
+ * Get all checkpoints for a file (ordered by timestamp)
652
+ */
653
+ getCheckpointsForFile(filePath) {
654
+ const ids = this.fileCheckpoints.get(filePath) || [];
655
+ return ids.map((id) => this.checkpoints.get(id)).filter((cp) => cp !== void 0).sort((a, b) => a.timestamp - b.timestamp);
656
+ }
657
+ /**
658
+ * Verify if current file content matches expected state
659
+ * Returns true if safe to revert
660
+ */
661
+ verifyFileState(checkpointId, currentContent) {
662
+ const checkpoint = this.checkpoints.get(checkpointId);
663
+ if (!checkpoint) {
664
+ return {
665
+ safe: false,
666
+ reason: "Checkpoint not found"
667
+ };
668
+ }
669
+ const currentHash = this.computeHash(currentContent);
670
+ if (currentHash === checkpoint.afterHash) {
671
+ return {
672
+ safe: true,
673
+ checkpoint,
674
+ currentHash
675
+ };
676
+ }
677
+ if (currentHash === checkpoint.beforeHash) {
678
+ return {
679
+ safe: false,
680
+ reason: "File appears to already be reverted",
681
+ checkpoint,
682
+ currentHash
683
+ };
684
+ }
685
+ return {
686
+ safe: false,
687
+ reason: "File was modified after this edit. Current content does not match expected state.",
688
+ checkpoint,
689
+ currentHash
690
+ };
691
+ }
692
+ /**
693
+ * Remove a checkpoint after successful revert or accept
694
+ */
695
+ removeCheckpoint(id) {
696
+ const checkpoint = this.checkpoints.get(id);
697
+ if (!checkpoint) return false;
698
+ this.checkpoints.delete(id);
699
+ const fileCheckpointIds = this.fileCheckpoints.get(checkpoint.filePath);
700
+ if (fileCheckpointIds) {
701
+ const filtered = fileCheckpointIds.filter((cpId) => cpId !== id);
702
+ if (filtered.length === 0) {
703
+ this.fileCheckpoints.delete(checkpoint.filePath);
704
+ } else {
705
+ this.fileCheckpoints.set(checkpoint.filePath, filtered);
706
+ }
707
+ }
708
+ return true;
709
+ }
710
+ /**
711
+ * Get all checkpoints (for debugging/listing)
712
+ */
713
+ getAllCheckpoints() {
714
+ return Array.from(this.checkpoints.values()).sort((a, b) => b.timestamp - a.timestamp);
715
+ }
716
+ /**
717
+ * Clear all checkpoints (for cleanup)
718
+ */
719
+ clear() {
720
+ this.checkpoints.clear();
721
+ this.fileCheckpoints.clear();
722
+ }
723
+ /**
724
+ * Get statistics
725
+ */
726
+ getStats() {
727
+ return {
728
+ totalCheckpoints: this.checkpoints.size,
729
+ filesTracked: this.fileCheckpoints.size
730
+ };
731
+ }
732
+ };
733
+ var checkpointStore = new CheckpointStore();
617
734
  zod.z.object({
618
735
  file_path: zod.z.string().describe("The path to the file you want to search and replace in. You can use either a relative path in the workspace or an absolute path. If an absolute path is provided, it will be preserved as is"),
619
736
  new_string: zod.z.string().describe("The edited text to replace the old_string (must be different from the old_string)"),
620
- old_string: zod.z.string().describe("The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)")
737
+ old_string: zod.z.string().describe("The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)"),
738
+ toolCallId: zod.z.string().optional().describe("Optional tool call ID for checkpoint tracking")
621
739
  });
622
740
  var apply_patch = async function(input, projectCwd) {
623
- const { file_path, new_string, old_string } = input;
741
+ const { file_path, new_string, old_string, toolCallId } = input;
624
742
  try {
625
743
  if (!file_path) {
626
744
  return {
@@ -695,6 +813,13 @@ var apply_patch = async function(input, projectCwd) {
695
813
  };
696
814
  }
697
815
  const newContent = fileContent.replace(old_string, new_string);
816
+ const checkpointId = toolCallId || crypto.randomUUID();
817
+ const checkpoint = checkpointStore.createCheckpoint(
818
+ checkpointId,
819
+ absolute_file_path,
820
+ fileContent,
821
+ newContent
822
+ );
698
823
  try {
699
824
  await promises.writeFile(absolute_file_path, newContent, "utf-8");
700
825
  const diffStats = calculateDiffStats(fileContent, newContent);
@@ -704,9 +829,14 @@ var apply_patch = async function(input, projectCwd) {
704
829
  new_string,
705
830
  linesAdded: diffStats.linesAdded,
706
831
  linesRemoved: diffStats.linesRemoved,
707
- message: `Successfully replaced string in file: ${file_path}`
832
+ message: `Successfully replaced string in file: ${file_path}`,
833
+ // Include checkpoint info for frontend
834
+ checkpointId: checkpoint.id,
835
+ beforeHash: checkpoint.beforeHash,
836
+ afterHash: checkpoint.afterHash
708
837
  };
709
838
  } catch (error) {
839
+ checkpointStore.removeCheckpoint(checkpointId);
710
840
  return {
711
841
  success: false,
712
842
  message: `Failed to write to file: ${file_path}`,
@@ -724,10 +854,11 @@ var apply_patch = async function(input, projectCwd) {
724
854
  zod.z.object({
725
855
  target_file: zod.z.string().describe("The relative path to the file to modify. The tool will create any directories in the path that don't exist"),
726
856
  content: zod.z.string().describe("The content to write to the file"),
727
- providedNewFile: zod.z.boolean().describe("The new file content to write to the file").optional()
857
+ providedNewFile: zod.z.boolean().describe("The new file content to write to the file").optional(),
858
+ toolCallId: zod.z.string().optional().describe("Optional tool call ID for checkpoint tracking")
728
859
  });
729
860
  var editFiles = async function(input, projectCwd) {
730
- const { target_file, content, providedNewFile } = input;
861
+ const { target_file, content, providedNewFile, toolCallId } = input;
731
862
  try {
732
863
  if (projectCwd) {
733
864
  const validation = validatePath(target_file, projectCwd);
@@ -759,7 +890,19 @@ var editFiles = async function(input, projectCwd) {
759
890
  isNewFile = true;
760
891
  }
761
892
  }
762
- await fs3__default.default.promises.writeFile(filePath, content);
893
+ const checkpointId = toolCallId || crypto.randomUUID();
894
+ const checkpoint = checkpointStore.createCheckpoint(
895
+ checkpointId,
896
+ filePath,
897
+ existingContent,
898
+ content
899
+ );
900
+ try {
901
+ await fs3__default.default.promises.writeFile(filePath, content);
902
+ } catch (writeError) {
903
+ checkpointStore.removeCheckpoint(checkpointId);
904
+ throw writeError;
905
+ }
763
906
  const diffStats = calculateDiffStats(existingContent, content);
764
907
  if (isNewFile) {
765
908
  return {
@@ -769,7 +912,11 @@ var editFiles = async function(input, projectCwd) {
769
912
  new_string: content,
770
913
  message: `Created new file: ${target_file}`,
771
914
  linesAdded: diffStats.linesAdded,
772
- linesRemoved: diffStats.linesRemoved
915
+ linesRemoved: diffStats.linesRemoved,
916
+ // Include checkpoint info for frontend
917
+ checkpointId: checkpoint.id,
918
+ beforeHash: checkpoint.beforeHash,
919
+ afterHash: checkpoint.afterHash
773
920
  };
774
921
  } else {
775
922
  return {
@@ -779,7 +926,11 @@ var editFiles = async function(input, projectCwd) {
779
926
  new_string: content,
780
927
  message: `Modified file: ${target_file}`,
781
928
  linesAdded: diffStats.linesAdded,
782
- linesRemoved: diffStats.linesRemoved
929
+ linesRemoved: diffStats.linesRemoved,
930
+ // Include checkpoint info for frontend
931
+ checkpointId: checkpoint.id,
932
+ beforeHash: checkpoint.beforeHash,
933
+ afterHash: checkpoint.afterHash
783
934
  };
784
935
  }
785
936
  } catch (error) {
@@ -1274,15 +1425,6 @@ var startHttpServer = (connection) => {
1274
1425
  controller.enqueue(encoder.encode(`data: ${JSON.stringify({ connected: initialStatus === "open" })}
1275
1426
 
1276
1427
  `));
1277
- const statusHandler = (data) => {
1278
- try {
1279
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1280
-
1281
- `));
1282
- } catch {
1283
- }
1284
- };
1285
- statusEmitter.on("status", statusHandler);
1286
1428
  const heartbeatInterval = setInterval(() => {
1287
1429
  try {
1288
1430
  const currentStatus = wsConnection ? getConnectionStatus(wsConnection) : "closed";
@@ -1293,7 +1435,6 @@ var startHttpServer = (connection) => {
1293
1435
  }
1294
1436
  }, 15e3);
1295
1437
  c.req.raw.signal.addEventListener("abort", () => {
1296
- statusEmitter.off("status", statusHandler);
1297
1438
  clearInterval(heartbeatInterval);
1298
1439
  });
1299
1440
  }
@@ -1335,7 +1476,15 @@ var startHttpServer = (connection) => {
1335
1476
  });
1336
1477
  app.post("/revert", async (c) => {
1337
1478
  try {
1338
- const { filePath, oldString, newString, projectCwd } = await c.req.json();
1479
+ const {
1480
+ filePath,
1481
+ oldString,
1482
+ newString,
1483
+ projectCwd,
1484
+ checkpointId,
1485
+ expectedAfterHash,
1486
+ force = false
1487
+ } = await c.req.json();
1339
1488
  if (!filePath || oldString === void 0) {
1340
1489
  return c.json({ error: "filePath and oldString required" }, 400);
1341
1490
  }
@@ -1359,25 +1508,127 @@ var startHttpServer = (connection) => {
1359
1508
  }
1360
1509
  return c.json({ error: `Failed to read file: ${error.message}` }, 500);
1361
1510
  }
1511
+ if (checkpointId) {
1512
+ const verification = checkpointStore.verifyFileState(checkpointId, currentContent);
1513
+ if (!verification.safe && !force) {
1514
+ return c.json({
1515
+ success: false,
1516
+ conflict: true,
1517
+ error: verification.reason,
1518
+ currentHash: verification.currentHash,
1519
+ expectedHash: verification.checkpoint?.afterHash,
1520
+ checkpointId
1521
+ }, 409);
1522
+ }
1523
+ if (verification.checkpoint) {
1524
+ try {
1525
+ await promises.writeFile(resolved, verification.checkpoint.beforeContent, "utf-8");
1526
+ checkpointStore.removeCheckpoint(checkpointId);
1527
+ return c.json({ success: true, usedCheckpoint: true });
1528
+ } catch (writeError) {
1529
+ return c.json({ error: `Failed to write file: ${writeError.message}` }, 500);
1530
+ }
1531
+ }
1532
+ }
1533
+ if (expectedAfterHash && !force) {
1534
+ const currentHash = checkpointStore.computeHash(currentContent);
1535
+ if (currentHash !== expectedAfterHash) {
1536
+ return c.json({
1537
+ success: false,
1538
+ conflict: true,
1539
+ error: "File was modified after this edit. Current content does not match expected state.",
1540
+ currentHash,
1541
+ expectedHash: expectedAfterHash
1542
+ }, 409);
1543
+ }
1544
+ }
1362
1545
  let finalContent;
1363
1546
  if (newString && newString !== oldString) {
1364
1547
  if (!currentContent.includes(newString)) {
1365
- return c.json({ error: "Cannot revert: the new content is not found in the current file. The file may have been modified." }, 400);
1548
+ return c.json({
1549
+ success: false,
1550
+ conflict: true,
1551
+ error: "Cannot revert: the new content is not found in the current file. The file may have been modified."
1552
+ }, 409);
1366
1553
  }
1367
1554
  const occurrences = currentContent.split(newString).length - 1;
1368
1555
  if (occurrences > 1) {
1369
- return c.json({ error: "Cannot revert: the new content appears multiple times in the file" }, 400);
1556
+ return c.json({
1557
+ success: false,
1558
+ conflict: true,
1559
+ error: "Cannot revert: the new content appears multiple times in the file"
1560
+ }, 409);
1370
1561
  }
1371
1562
  finalContent = currentContent.replace(newString, oldString);
1372
1563
  } else {
1373
1564
  finalContent = oldString;
1374
1565
  }
1375
1566
  await promises.writeFile(resolved, finalContent, "utf-8");
1567
+ if (checkpointId) {
1568
+ checkpointStore.removeCheckpoint(checkpointId);
1569
+ }
1376
1570
  return c.json({ success: true });
1377
1571
  } catch (error) {
1378
1572
  return c.json({ error: error.message }, 500);
1379
1573
  }
1380
1574
  });
1575
+ app.post("/revert/force", async (c) => {
1576
+ try {
1577
+ const { filePath, checkpointId, projectCwd } = await c.req.json();
1578
+ if (!checkpointId) {
1579
+ return c.json({ error: "checkpointId is required for force revert" }, 400);
1580
+ }
1581
+ const checkpoint = checkpointStore.getCheckpoint(checkpointId);
1582
+ if (!checkpoint) {
1583
+ return c.json({ error: "Checkpoint not found" }, 404);
1584
+ }
1585
+ let resolved;
1586
+ if (projectCwd) {
1587
+ resolved = path9__default.default.isAbsolute(filePath || checkpoint.filePath) ? filePath || checkpoint.filePath : path9__default.default.resolve(projectCwd, filePath || checkpoint.filePath);
1588
+ const normalizedResolved = path9__default.default.normalize(resolved);
1589
+ const normalizedCwd = path9__default.default.normalize(projectCwd);
1590
+ if (!normalizedResolved.startsWith(normalizedCwd)) {
1591
+ return c.json({ error: "Path is outside project directory" }, 403);
1592
+ }
1593
+ } else {
1594
+ resolved = checkpoint.filePath;
1595
+ }
1596
+ try {
1597
+ await promises.writeFile(resolved, checkpoint.beforeContent, "utf-8");
1598
+ checkpointStore.removeCheckpoint(checkpointId);
1599
+ return c.json({ success: true, forced: true });
1600
+ } catch (writeError) {
1601
+ return c.json({ error: `Failed to write file: ${writeError.message}` }, 500);
1602
+ }
1603
+ } catch (error) {
1604
+ return c.json({ error: error.message }, 500);
1605
+ }
1606
+ });
1607
+ app.get("/checkpoints/:checkpointId", (c) => {
1608
+ const checkpointId = c.req.param("checkpointId");
1609
+ const checkpoint = checkpointStore.getCheckpoint(checkpointId);
1610
+ if (!checkpoint) {
1611
+ return c.json({ error: "Checkpoint not found" }, 404);
1612
+ }
1613
+ return c.json({
1614
+ id: checkpoint.id,
1615
+ filePath: checkpoint.filePath,
1616
+ beforeHash: checkpoint.beforeHash,
1617
+ afterHash: checkpoint.afterHash,
1618
+ timestamp: checkpoint.timestamp
1619
+ });
1620
+ });
1621
+ app.get("/checkpoints", (c) => {
1622
+ const stats = checkpointStore.getStats();
1623
+ const checkpoints = checkpointStore.getAllCheckpoints().map((cp) => ({
1624
+ id: cp.id,
1625
+ filePath: cp.filePath,
1626
+ beforeHash: cp.beforeHash,
1627
+ afterHash: cp.afterHash,
1628
+ timestamp: cp.timestamp
1629
+ }));
1630
+ return c.json({ stats, checkpoints });
1631
+ });
1381
1632
  app.get("/projects", (c) => {
1382
1633
  const projects = projectRegistry.list();
1383
1634
  return c.json({ projects });
@@ -1423,7 +1674,7 @@ function saveTokens(tokens) {
1423
1674
  "utf8"
1424
1675
  );
1425
1676
  } catch (error) {
1426
- console.error(pc4__default.default.red("Failed to save credentials"), error);
1677
+ console.error(pc5__default.default.red("Failed to save credentials"), error);
1427
1678
  }
1428
1679
  }
1429
1680
  function logout() {
@@ -1432,7 +1683,7 @@ function logout() {
1432
1683
  fs3__default.default.unlinkSync(CREDENTIALS_PATH);
1433
1684
  }
1434
1685
  } catch (error) {
1435
- console.error(pc4__default.default.red("Failed to logout"), error);
1686
+ console.error(pc5__default.default.red("Failed to logout"), error);
1436
1687
  }
1437
1688
  }
1438
1689
  function getTokens() {
@@ -1507,27 +1758,27 @@ async function pollForTokens({
1507
1758
  async function login() {
1508
1759
  try {
1509
1760
  const device = await authorizeDevice();
1510
- console.log(pc4__default.default.bold("To sign in, follow these steps:"));
1761
+ console.log(pc5__default.default.bold("To sign in, follow these steps:"));
1511
1762
  if (device.verification_uri_complete) {
1512
1763
  console.log(
1513
- ` 1. Open this URL in your browser: ${pc4__default.default.cyan(
1764
+ ` 1. Open this URL in your browser: ${pc5__default.default.cyan(
1514
1765
  device.verification_uri_complete
1515
1766
  )}`
1516
1767
  );
1517
1768
  } else {
1518
1769
  console.log(
1519
- ` 1. Open this URL in your browser: ${pc4__default.default.cyan(
1770
+ ` 1. Open this URL in your browser: ${pc5__default.default.cyan(
1520
1771
  device.verification_uri
1521
1772
  )}`
1522
1773
  );
1523
1774
  console.log(
1524
- ` 2. Enter this code when prompted: ${pc4__default.default.bold(device.user_code)}`
1775
+ ` 2. Enter this code when prompted: ${pc5__default.default.bold(device.user_code)}`
1525
1776
  );
1526
1777
  }
1527
1778
  console.log(" 3. Come back here; we will detect when you finish logging in.");
1528
1779
  console.log();
1529
1780
  console.log(
1530
- pc4__default.default.gray(
1781
+ pc5__default.default.gray(
1531
1782
  `Waiting for authorization (expires in ~${Math.round(
1532
1783
  device.expires_in / 60
1533
1784
  )} minutes)...`
@@ -1539,14 +1790,28 @@ async function login() {
1539
1790
  expiresIn: device.expires_in,
1540
1791
  interval: device.interval
1541
1792
  });
1542
- console.log(pc4__default.default.green("Successfully authenticated!"));
1793
+ console.log(pc5__default.default.green("Successfully authenticated!"));
1543
1794
  saveTokens(tokens);
1544
1795
  return tokens;
1545
1796
  } catch (error) {
1546
- console.error(pc4__default.default.red(error.message || "Login failed"));
1797
+ console.error(pc5__default.default.red(error.message || "Login failed"));
1547
1798
  throw error;
1548
1799
  }
1549
1800
  }
1801
+ var getUserId = () => {
1802
+ try {
1803
+ if (!fs3__default.default.existsSync(CREDENTIALS_PATH)) {
1804
+ return;
1805
+ }
1806
+ const raw = fs3__default.default.readFileSync(CREDENTIALS_PATH, "utf8");
1807
+ const data = JSON.parse(raw);
1808
+ return {
1809
+ userId: data.user.id
1810
+ };
1811
+ } catch {
1812
+ throw new Error("Error while getting userId");
1813
+ }
1814
+ };
1550
1815
  var ExplanationSchema = zod.z.object({
1551
1816
  explanation: zod.z.string().describe("One sentence explanation as to why this tool is being used")
1552
1817
  });
@@ -1635,8 +1900,201 @@ var runTerminalCommand = async (input, projectCwd) => {
1635
1900
  }
1636
1901
  };
1637
1902
 
1903
+ // src/lib/rpc-handlers.ts
1904
+ var rpcHandlers = {
1905
+ "daemon:get_workspace_folders": async () => {
1906
+ const projects = projectRegistry.list();
1907
+ return {
1908
+ folders: projects.map((p) => ({
1909
+ id: p.id,
1910
+ cwd: p.cwd,
1911
+ name: p.name,
1912
+ active: p.active
1913
+ }))
1914
+ };
1915
+ },
1916
+ "daemon:get_environment": async ({ gitRepositoryUrl }) => {
1917
+ const projects = projectRegistry.list();
1918
+ if (projects.length === 0) {
1919
+ const error = {
1920
+ _tag: "ProjectUnlinkedError",
1921
+ message: `Getting a local project by git repository URL "${gitRepositoryUrl}" returned an unlinked project. Please link it by running \`amai link <project name> <path to project directory>\``
1922
+ };
1923
+ throw error;
1924
+ }
1925
+ return {
1926
+ project: projects[0],
1927
+ env: {
1928
+ platform: process.platform,
1929
+ arch: process.arch,
1930
+ nodeVersion: process.version
1931
+ }
1932
+ };
1933
+ },
1934
+ "daemon:get_context": async ({ cwd }) => {
1935
+ try {
1936
+ const files = getContext(cwd);
1937
+ return { files, cwd };
1938
+ } catch (error) {
1939
+ const rpcError = {
1940
+ _tag: "ContextError",
1941
+ message: error.message || "Failed to get context"
1942
+ };
1943
+ throw rpcError;
1944
+ }
1945
+ },
1946
+ "daemon:get_ide_projects": async () => {
1947
+ const projects = await scanIdeProjects();
1948
+ return { projects };
1949
+ },
1950
+ "daemon:register_project": async ({ projectId, cwd, name }) => {
1951
+ if (!projectId || !cwd) {
1952
+ const error = {
1953
+ _tag: "ValidationError",
1954
+ message: "projectId and cwd are required"
1955
+ };
1956
+ throw error;
1957
+ }
1958
+ projectRegistry.register(projectId, cwd, name);
1959
+ return { success: true, projectId, cwd };
1960
+ },
1961
+ "daemon:unregister_project": async ({ projectId }) => {
1962
+ if (!projectId) {
1963
+ const error = {
1964
+ _tag: "ValidationError",
1965
+ message: "projectId is required"
1966
+ };
1967
+ throw error;
1968
+ }
1969
+ projectRegistry.unregister(projectId);
1970
+ return { success: true, projectId };
1971
+ },
1972
+ "daemon:get_project": async ({ projectId }) => {
1973
+ const project = projectRegistry.getProject(projectId);
1974
+ if (!project) {
1975
+ const error = {
1976
+ _tag: "ProjectNotFoundError",
1977
+ message: `Project not found: ${projectId}`
1978
+ };
1979
+ throw error;
1980
+ }
1981
+ return { project };
1982
+ },
1983
+ "daemon:list_projects": async () => {
1984
+ const projects = projectRegistry.list();
1985
+ return { projects };
1986
+ },
1987
+ "daemon:status": async () => {
1988
+ return {
1989
+ connected: true,
1990
+ timestamp: Date.now(),
1991
+ platform: process.platform,
1992
+ arch: process.arch
1993
+ };
1994
+ }
1995
+ };
1996
+ var reconnectTimeout = null;
1997
+ var connectToUserStreams = async (serverUrl) => {
1998
+ const userId = getUserId();
1999
+ if (!userId?.userId) {
2000
+ throw new Error("User ID not found");
2001
+ }
2002
+ const params = new URLSearchParams({
2003
+ userId: userId.userId,
2004
+ type: "cli"
2005
+ });
2006
+ const tokens = getTokens();
2007
+ if (!tokens) {
2008
+ throw new Error("No tokens found");
2009
+ }
2010
+ const wsUrl = `${serverUrl}/api/v1/user-streams?${params.toString()}`;
2011
+ const ws = new WebSocket2__default.default(wsUrl, {
2012
+ headers: {
2013
+ Authorization: `Bearer ${tokens.access_token}`
2014
+ }
2015
+ });
2016
+ ws.on("open", () => {
2017
+ console.log(pc5__default.default.green("CLI connected to user-streams"));
2018
+ if (reconnectTimeout) {
2019
+ clearTimeout(reconnectTimeout);
2020
+ reconnectTimeout = null;
2021
+ }
2022
+ });
2023
+ ws.on("message", async (event) => {
2024
+ try {
2025
+ const message = JSON.parse(event.toString());
2026
+ if (message._tag === "rpc_call") {
2027
+ const { requestId, method, input } = message;
2028
+ console.log(pc5__default.default.gray(`RPC call: ${method}`));
2029
+ const handler = rpcHandlers[method];
2030
+ if (!handler) {
2031
+ ws.send(JSON.stringify({
2032
+ _tag: "rpc_result",
2033
+ requestId,
2034
+ data: {
2035
+ _tag: "UnknownMethodError",
2036
+ message: `Unknown RPC method: ${method}`
2037
+ }
2038
+ }));
2039
+ console.log(pc5__default.default.yellow(`Unknown RPC method: ${method}`));
2040
+ return;
2041
+ }
2042
+ try {
2043
+ const result = await handler(input || {});
2044
+ ws.send(JSON.stringify({
2045
+ _tag: "rpc_result",
2046
+ requestId,
2047
+ data: result
2048
+ }));
2049
+ console.log(pc5__default.default.green(`RPC completed: ${method}`));
2050
+ } catch (error) {
2051
+ const rpcError = error._tag ? error : {
2052
+ _tag: "RpcError",
2053
+ message: error.message || String(error)
2054
+ };
2055
+ ws.send(JSON.stringify({
2056
+ _tag: "rpc_result",
2057
+ requestId,
2058
+ data: rpcError
2059
+ }));
2060
+ console.log(pc5__default.default.red(`RPC failed: ${method} - ${rpcError.message}`));
2061
+ }
2062
+ return;
2063
+ }
2064
+ if (message.type === "presence_request") {
2065
+ if (message.status === "connected") {
2066
+ ws.send(JSON.stringify({
2067
+ type: "presence_request",
2068
+ status: "connected"
2069
+ }));
2070
+ }
2071
+ if (message.status === "disconnected") {
2072
+ ws.send(JSON.stringify({
2073
+ type: "presence_request",
2074
+ status: "disconnected"
2075
+ }));
2076
+ }
2077
+ }
2078
+ } catch (parseError) {
2079
+ console.error(pc5__default.default.red(`Failed to parse message: ${parseError}`));
2080
+ }
2081
+ });
2082
+ ws.on("close", (code, reason) => {
2083
+ console.log(pc5__default.default.yellow(`CLI disconnected from user-streams (code: ${code})`));
2084
+ console.log(pc5__default.default.gray("Reconnecting in 5 seconds..."));
2085
+ reconnectTimeout = setTimeout(() => {
2086
+ connectToUserStreams(serverUrl).catch((err) => {
2087
+ console.error(pc5__default.default.red(`Reconnection failed: ${err.message}`));
2088
+ });
2089
+ }, 5e3);
2090
+ });
2091
+ ws.on("error", (error) => {
2092
+ console.error(pc5__default.default.red(`User streams WebSocket error: ${error.message}`));
2093
+ });
2094
+ return ws;
2095
+ };
2096
+
1638
2097
  // src/server.ts
1639
- var statusEmitter = new events.EventEmitter();
1640
2098
  var toolExecutors = {
1641
2099
  editFile: editFiles,
1642
2100
  deleteFile,
@@ -1648,7 +2106,7 @@ var toolExecutors = {
1648
2106
  runTerminalCommand
1649
2107
  };
1650
2108
  function getConnectionStatus(ws) {
1651
- return ws.readyState === WebSocket__default.default.CONNECTING ? "connecting" : ws.readyState === WebSocket__default.default.OPEN ? "open" : ws.readyState === WebSocket__default.default.CLOSING ? "closing" : "closed";
2109
+ return ws.readyState === WebSocket2__default.default.CONNECTING ? "connecting" : ws.readyState === WebSocket2__default.default.OPEN ? "open" : ws.readyState === WebSocket2__default.default.CLOSING ? "closing" : "closed";
1652
2110
  }
1653
2111
  function connectToServer2(serverUrl = DEFAULT_SERVER_URL) {
1654
2112
  const tokens = getTokens();
@@ -1656,14 +2114,13 @@ function connectToServer2(serverUrl = DEFAULT_SERVER_URL) {
1656
2114
  throw new Error("No tokens found");
1657
2115
  }
1658
2116
  const wsUrl = `${serverUrl}/agent-streams`;
1659
- const ws = new WebSocket__default.default(wsUrl, {
2117
+ const ws = new WebSocket2__default.default(wsUrl, {
1660
2118
  headers: {
1661
2119
  "Authorization": `Bearer ${tokens.access_token}`
1662
2120
  }
1663
2121
  });
1664
2122
  ws.on("open", () => {
1665
- console.log(pc4__default.default.green("Connected to server agent streams"));
1666
- statusEmitter.emit("status", { connected: true });
2123
+ console.log(pc5__default.default.green("Connected to server agent streams"));
1667
2124
  });
1668
2125
  ws.on("message", async (data) => {
1669
2126
  const message = JSON.parse(data.toString());
@@ -1680,33 +2137,32 @@ function connectToServer2(serverUrl = DEFAULT_SERVER_URL) {
1680
2137
  id: message.id,
1681
2138
  result
1682
2139
  }));
1683
- console.log(pc4__default.default.green(`Tool completed: ${message.tool}`));
2140
+ console.log(pc5__default.default.green(`Tool completed: ${message.tool}`));
1684
2141
  } catch (error) {
1685
2142
  ws.send(JSON.stringify({
1686
2143
  type: "tool_result",
1687
2144
  id: message.id,
1688
2145
  error: error.message
1689
2146
  }));
1690
- console.error(pc4__default.default.red(`Tool failed: ${message.tool} ${error.message}`));
2147
+ console.error(pc5__default.default.red(`Tool failed: ${message.tool} ${error.message}`));
1691
2148
  }
1692
2149
  }
1693
2150
  });
1694
2151
  ws.on("close", () => {
1695
- console.log(pc4__default.default.red("Disconnected from server. Reconnecting in 5s..."));
1696
- statusEmitter.emit("status", { connected: false });
2152
+ console.log(pc5__default.default.red("Disconnected from server. Reconnecting in 5s..."));
1697
2153
  setTimeout(() => connectToServer2(serverUrl), 5e3);
1698
2154
  });
1699
2155
  ws.on("error", (error) => {
1700
- console.error(pc4__default.default.red(`WebSocket error: ${error.message}`));
1701
- statusEmitter.emit("status", { connected: false });
2156
+ console.error(pc5__default.default.red(`WebSocket error: ${error.message}`));
1702
2157
  });
1703
2158
  return ws;
1704
2159
  }
1705
2160
  async function main() {
1706
2161
  const serverUrl = DEFAULT_SERVER_URL;
1707
- console.log(pc4__default.default.green("Starting local amai..."));
1708
- console.log(pc4__default.default.gray(`Connecting to server at ${serverUrl}`));
2162
+ console.log(pc5__default.default.green("Starting local amai..."));
2163
+ console.log(pc5__default.default.gray(`Connecting to server at ${serverUrl}`));
1709
2164
  const connection = connectToServer2(serverUrl);
2165
+ await connectToUserStreams(serverUrl);
1710
2166
  startHttpServer(connection);
1711
2167
  }
1712
2168
  var execAsync2 = util.promisify(child_process.exec);
@@ -1758,22 +2214,22 @@ async function installCodeServer() {
1758
2214
  if (!fs3__default.default.existsSync(STORAGE_DIR)) {
1759
2215
  fs3__default.default.mkdirSync(STORAGE_DIR, { recursive: true });
1760
2216
  }
1761
- console.log(pc4__default.default.cyan(`Downloading code-server v${CODE_SERVER_VERSION}...`));
1762
- console.log(pc4__default.default.gray(downloadUrl));
2217
+ console.log(pc5__default.default.cyan(`Downloading code-server v${CODE_SERVER_VERSION}...`));
2218
+ console.log(pc5__default.default.gray(downloadUrl));
1763
2219
  const response = await fetch(downloadUrl);
1764
2220
  if (!response.ok) {
1765
2221
  throw new Error(`Failed to download code-server: ${response.statusText}`);
1766
2222
  }
1767
2223
  const buffer = await response.arrayBuffer();
1768
2224
  await fs3__default.default.promises.writeFile(tarballPath, Buffer.from(buffer));
1769
- console.log(pc4__default.default.cyan("Extracting code-server..."));
2225
+ console.log(pc5__default.default.cyan("Extracting code-server..."));
1770
2226
  await execAsync2(`tar -xzf ${tarballPath} -C ${CODE_DIR}`);
1771
2227
  await fs3__default.default.promises.unlink(tarballPath);
1772
2228
  const binPath = getCodeServerBin();
1773
2229
  if (fs3__default.default.existsSync(binPath)) {
1774
2230
  await fs3__default.default.promises.chmod(binPath, 493);
1775
2231
  }
1776
- console.log(pc4__default.default.green("\u2713 code-server installed successfully"));
2232
+ console.log(pc5__default.default.green("\u2713 code-server installed successfully"));
1777
2233
  }
1778
2234
  async function killExistingCodeServer() {
1779
2235
  try {
@@ -1812,7 +2268,7 @@ async function startCodeServer(cwd) {
1812
2268
  }
1813
2269
  } catch {
1814
2270
  }
1815
- console.log(pc4__default.default.cyan(`Starting code-server in ${workDir}...`));
2271
+ console.log(pc5__default.default.cyan(`Starting code-server in ${workDir}...`));
1816
2272
  const codeServer = child_process.spawn(
1817
2273
  binPath,
1818
2274
  [
@@ -1831,7 +2287,7 @@ async function startCodeServer(cwd) {
1831
2287
  stdio: ["ignore", "pipe", "pipe"]
1832
2288
  }
1833
2289
  );
1834
- console.log(pc4__default.default.green(`\u2713 code-server running at http://localhost:8081/?folder=${encodeURIComponent(workDir)}`));
2290
+ console.log(pc5__default.default.green(`\u2713 code-server running at http://localhost:8081/?folder=${encodeURIComponent(workDir)}`));
1835
2291
  return codeServer;
1836
2292
  }
1837
2293
  var __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
@@ -1895,7 +2351,7 @@ function getDaemonPid() {
1895
2351
  return null;
1896
2352
  }
1897
2353
  }
1898
- var VERSION = "0.0.4";
2354
+ var VERSION = "0.0.6";
1899
2355
  var PROJECT_DIR = process.cwd();
1900
2356
  function promptUser(question) {
1901
2357
  const rl = readline__default.default.createInterface({
@@ -1911,19 +2367,19 @@ function promptUser(question) {
1911
2367
  }
1912
2368
  async function startWithCodeServer() {
1913
2369
  if (!isCodeServerInstalled()) {
1914
- console.log(pc4__default.default.cyan("First run detected. Setting up code-server..."));
2370
+ console.log(pc5__default.default.cyan("First run detected. Setting up code-server..."));
1915
2371
  try {
1916
2372
  await installCodeServer();
1917
2373
  } catch (error) {
1918
- console.error(pc4__default.default.red(`Failed to install code-server: ${error.message}`));
1919
- console.log(pc4__default.default.yellow("Continuing without code-server..."));
2374
+ console.error(pc5__default.default.red(`Failed to install code-server: ${error.message}`));
2375
+ console.log(pc5__default.default.yellow("Continuing without code-server..."));
1920
2376
  }
1921
2377
  }
1922
2378
  if (isCodeServerInstalled()) {
1923
2379
  try {
1924
2380
  await startCodeServer(PROJECT_DIR);
1925
2381
  } catch (error) {
1926
- console.error(pc4__default.default.red(`Failed to start code-server: ${error.message}`));
2382
+ console.error(pc5__default.default.red(`Failed to start code-server: ${error.message}`));
1927
2383
  }
1928
2384
  }
1929
2385
  main();
@@ -1931,7 +2387,7 @@ async function startWithCodeServer() {
1931
2387
  var args = process.argv.slice(2);
1932
2388
  if (args[0] === "--help" || args[0] === "-h") {
1933
2389
  console.log(`
1934
- ${pc4__default.default.bold("amai cli")} ${pc4__default.default.gray(VERSION)}
2390
+ ${pc5__default.default.bold("amai cli")} ${pc5__default.default.gray(VERSION)}
1935
2391
 
1936
2392
  Usage: amai [command] [options]
1937
2393
 
@@ -1959,32 +2415,32 @@ Example:
1959
2415
  }
1960
2416
  if (args[0] === "start") {
1961
2417
  if (isDaemonRunning()) {
1962
- console.log(pc4__default.default.yellow("Daemon is already running"));
2418
+ console.log(pc5__default.default.yellow("Daemon is already running"));
1963
2419
  process.exit(0);
1964
2420
  }
1965
2421
  if (!isAuthenticated()) {
1966
- console.log(pc4__default.default.yellow("Not authenticated. Please log in first."));
2422
+ console.log(pc5__default.default.yellow("Not authenticated. Please log in first."));
1967
2423
  login().then(() => {
1968
- console.log(pc4__default.default.green("Starting daemon..."));
2424
+ console.log(pc5__default.default.green("Starting daemon..."));
1969
2425
  startDaemon();
1970
- console.log(pc4__default.default.green("Daemon started successfully"));
2426
+ console.log(pc5__default.default.green("Daemon started successfully"));
1971
2427
  process.exit(0);
1972
2428
  }).catch(() => {
1973
- console.error(pc4__default.default.red("Login failed. Cannot start daemon."));
2429
+ console.error(pc5__default.default.red("Login failed. Cannot start daemon."));
1974
2430
  process.exit(1);
1975
2431
  });
1976
2432
  } else {
1977
2433
  startDaemon();
1978
- console.log(pc4__default.default.green(pc4__default.default.bold("amai started in background mode")));
1979
- console.log(pc4__default.default.gray(`Tip: You can check status any time with ${pc4__default.default.bold("amai status")}`));
2434
+ console.log(pc5__default.default.green(pc5__default.default.bold("amai started in background mode")));
2435
+ console.log(pc5__default.default.gray(`Tip: You can check status any time with ${pc5__default.default.bold("amai status")}`));
1980
2436
  process.exit(0);
1981
2437
  }
1982
2438
  }
1983
2439
  if (args[0] === "stop") {
1984
2440
  if (stopDaemon()) {
1985
- console.log(pc4__default.default.green("Daemon stopped successfully"));
2441
+ console.log(pc5__default.default.green("Daemon stopped successfully"));
1986
2442
  } else {
1987
- console.log(pc4__default.default.yellow("Daemon was not running"));
2443
+ console.log(pc5__default.default.yellow("Daemon was not running"));
1988
2444
  }
1989
2445
  process.exit(0);
1990
2446
  }
@@ -1992,9 +2448,9 @@ if (args[0] === "status") {
1992
2448
  const running = isDaemonRunning();
1993
2449
  const pid = getDaemonPid();
1994
2450
  if (running && pid) {
1995
- console.log(pc4__default.default.green(`Daemon is running (PID: ${pid})`));
2451
+ console.log(pc5__default.default.green(`Daemon is running (PID: ${pid})`));
1996
2452
  } else {
1997
- console.log(pc4__default.default.yellow("Daemon is not running"));
2453
+ console.log(pc5__default.default.yellow("Daemon is not running"));
1998
2454
  }
1999
2455
  process.exit(0);
2000
2456
  }
@@ -2002,36 +2458,36 @@ if (args[0] === "project") {
2002
2458
  if (args[1] === "add") {
2003
2459
  const projectPath = args[2];
2004
2460
  if (!projectPath) {
2005
- console.error(pc4__default.default.red("Please provide a project path"));
2461
+ console.error(pc5__default.default.red("Please provide a project path"));
2006
2462
  console.log("Usage: amai project add <path>");
2007
2463
  process.exit(1);
2008
2464
  }
2009
2465
  const resolvedPath = path9__default.default.resolve(projectPath);
2010
2466
  if (!fs3__default.default.existsSync(resolvedPath)) {
2011
- console.error(pc4__default.default.red(`Path does not exist: ${resolvedPath}`));
2467
+ console.error(pc5__default.default.red(`Path does not exist: ${resolvedPath}`));
2012
2468
  process.exit(1);
2013
2469
  }
2014
2470
  if (!fs3__default.default.statSync(resolvedPath).isDirectory()) {
2015
- console.error(pc4__default.default.red(`Path is not a directory: ${resolvedPath}`));
2471
+ console.error(pc5__default.default.red(`Path is not a directory: ${resolvedPath}`));
2016
2472
  process.exit(1);
2017
2473
  }
2018
2474
  const projectId = path9__default.default.basename(resolvedPath);
2019
2475
  projectRegistry.register(projectId, resolvedPath);
2020
- console.log(pc4__default.default.green(`Project registered: ${projectId} -> ${resolvedPath}`));
2476
+ console.log(pc5__default.default.green(`Project registered: ${projectId} -> ${resolvedPath}`));
2021
2477
  process.exit(0);
2022
2478
  } else if (args[1] === "list") {
2023
2479
  const projects = projectRegistry.list();
2024
2480
  if (projects.length === 0) {
2025
- console.log(pc4__default.default.yellow("No projects registered"));
2481
+ console.log(pc5__default.default.yellow("No projects registered"));
2026
2482
  } else {
2027
- console.log(pc4__default.default.bold("Registered projects:"));
2483
+ console.log(pc5__default.default.bold("Registered projects:"));
2028
2484
  projects.forEach((project) => {
2029
- console.log(` ${pc4__default.default.cyan(project.id)}: ${project.cwd} ${project.active ? pc4__default.default.green("(active)") : ""}`);
2485
+ console.log(` ${pc5__default.default.cyan(project.id)}: ${project.cwd} ${project.active ? pc5__default.default.green("(active)") : ""}`);
2030
2486
  });
2031
2487
  }
2032
2488
  process.exit(0);
2033
2489
  } else {
2034
- console.error(pc4__default.default.red(`Unknown project command: ${args[1]}`));
2490
+ console.error(pc5__default.default.red(`Unknown project command: ${args[1]}`));
2035
2491
  console.log('Use "amai project add <path>" or "amai project list"');
2036
2492
  process.exit(1);
2037
2493
  }
@@ -2040,39 +2496,39 @@ if (args[0] === "login" || args[0] === "--login") {
2040
2496
  login().then(() => process.exit(0)).catch(() => process.exit(1));
2041
2497
  } else if (args[0] === "logout" || args[0] === "--logout") {
2042
2498
  logout();
2043
- console.log(pc4__default.default.green("Logged out successfully"));
2499
+ console.log(pc5__default.default.green("Logged out successfully"));
2044
2500
  process.exit(0);
2045
2501
  } else {
2046
2502
  (async () => {
2047
2503
  if (!isAuthenticated()) {
2048
- console.log(pc4__default.default.yellow("Not authenticated. Please log in first."));
2504
+ console.log(pc5__default.default.yellow("Not authenticated. Please log in first."));
2049
2505
  try {
2050
2506
  await login();
2051
2507
  } catch {
2052
- console.error(pc4__default.default.red("Login failed. Cannot start server."));
2508
+ console.error(pc5__default.default.red("Login failed. Cannot start server."));
2053
2509
  process.exit(1);
2054
2510
  }
2055
2511
  }
2056
2512
  if (isDaemonRunning()) {
2057
- console.log(pc4__default.default.yellow('Daemon is already running. Use "amai status" to check its status.'));
2513
+ console.log(pc5__default.default.yellow('Daemon is already running. Use "amai status" to check its status.'));
2058
2514
  process.exit(0);
2059
2515
  }
2060
2516
  console.log("");
2061
- console.log(pc4__default.default.bold("How would you like to run amai?"));
2062
- console.log(pc4__default.default.gray("Background mode is highly recommended for better performance and stability."));
2517
+ console.log(pc5__default.default.bold("How would you like to run amai?"));
2518
+ console.log(pc5__default.default.gray("Background mode is highly recommended for better performance and stability."));
2063
2519
  const answer = await promptUser(
2064
- pc4__default.default.cyan("Run in background? (Y/n): ")
2520
+ pc5__default.default.cyan("Run in background? (Y/n): ")
2065
2521
  );
2066
2522
  const runInBackground = answer === "" || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
2067
2523
  if (runInBackground) {
2068
- console.log(pc4__default.default.green("Starting daemon in background..."));
2524
+ console.log(pc5__default.default.green("Starting daemon in background..."));
2069
2525
  startDaemon();
2070
- console.log(pc4__default.default.green("Daemon started successfully!"));
2071
- console.log(pc4__default.default.gray('Use "amai status" to check daemon status.'));
2072
- console.log(pc4__default.default.gray('Use "amai stop" to stop the daemon.'));
2526
+ console.log(pc5__default.default.green("Daemon started successfully!"));
2527
+ console.log(pc5__default.default.gray('Use "amai status" to check daemon status.'));
2528
+ console.log(pc5__default.default.gray('Use "amai stop" to stop the daemon.'));
2073
2529
  process.exit(0);
2074
2530
  } else {
2075
- console.log(pc4__default.default.yellow("Starting in foreground mode..."));
2531
+ console.log(pc5__default.default.yellow("Starting in foreground mode..."));
2076
2532
  startWithCodeServer();
2077
2533
  }
2078
2534
  })();