amai 0.0.4 → 0.0.5
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 +276 -14
- package/dist/cli.js +276 -14
- package/dist/lib/daemon-entry.cjs +275 -13
- package/dist/lib/daemon-entry.js +275 -13
- package/dist/server.cjs +275 -13
- package/dist/server.js +275 -13
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -9,6 +9,7 @@ var promises = require('fs/promises');
|
|
|
9
9
|
var path9 = require('path');
|
|
10
10
|
var fs3 = require('fs');
|
|
11
11
|
var os2 = require('os');
|
|
12
|
+
var crypto = require('crypto');
|
|
12
13
|
var child_process = require('child_process');
|
|
13
14
|
var util = require('util');
|
|
14
15
|
var hono = require('hono');
|
|
@@ -612,15 +613,133 @@ function calculateDiffStats(oldContent, newContent) {
|
|
|
612
613
|
}
|
|
613
614
|
return { linesAdded, linesRemoved };
|
|
614
615
|
}
|
|
615
|
-
|
|
616
|
-
|
|
616
|
+
var CheckpointStore = class {
|
|
617
|
+
checkpoints = /* @__PURE__ */ new Map();
|
|
618
|
+
fileCheckpoints = /* @__PURE__ */ new Map();
|
|
619
|
+
// filePath -> checkpointIds
|
|
620
|
+
/**
|
|
621
|
+
* Compute SHA-256 hash of content
|
|
622
|
+
*/
|
|
623
|
+
computeHash(content) {
|
|
624
|
+
return crypto.createHash("sha256").update(content, "utf8").digest("hex");
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Create a new checkpoint before an edit operation
|
|
628
|
+
*/
|
|
629
|
+
createCheckpoint(id, filePath, beforeContent, afterContent) {
|
|
630
|
+
const checkpoint = {
|
|
631
|
+
id,
|
|
632
|
+
filePath,
|
|
633
|
+
beforeContent,
|
|
634
|
+
afterContent,
|
|
635
|
+
beforeHash: this.computeHash(beforeContent),
|
|
636
|
+
afterHash: this.computeHash(afterContent),
|
|
637
|
+
timestamp: Date.now()
|
|
638
|
+
};
|
|
639
|
+
this.checkpoints.set(id, checkpoint);
|
|
640
|
+
const fileCheckpointIds = this.fileCheckpoints.get(filePath) || [];
|
|
641
|
+
fileCheckpointIds.push(id);
|
|
642
|
+
this.fileCheckpoints.set(filePath, fileCheckpointIds);
|
|
643
|
+
return checkpoint;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Get a checkpoint by ID
|
|
647
|
+
*/
|
|
648
|
+
getCheckpoint(id) {
|
|
649
|
+
return this.checkpoints.get(id);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Get all checkpoints for a file (ordered by timestamp)
|
|
653
|
+
*/
|
|
654
|
+
getCheckpointsForFile(filePath) {
|
|
655
|
+
const ids = this.fileCheckpoints.get(filePath) || [];
|
|
656
|
+
return ids.map((id) => this.checkpoints.get(id)).filter((cp) => cp !== void 0).sort((a, b) => a.timestamp - b.timestamp);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Verify if current file content matches expected state
|
|
660
|
+
* Returns true if safe to revert
|
|
661
|
+
*/
|
|
662
|
+
verifyFileState(checkpointId, currentContent) {
|
|
663
|
+
const checkpoint = this.checkpoints.get(checkpointId);
|
|
664
|
+
if (!checkpoint) {
|
|
665
|
+
return {
|
|
666
|
+
safe: false,
|
|
667
|
+
reason: "Checkpoint not found"
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
const currentHash = this.computeHash(currentContent);
|
|
671
|
+
if (currentHash === checkpoint.afterHash) {
|
|
672
|
+
return {
|
|
673
|
+
safe: true,
|
|
674
|
+
checkpoint,
|
|
675
|
+
currentHash
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (currentHash === checkpoint.beforeHash) {
|
|
679
|
+
return {
|
|
680
|
+
safe: false,
|
|
681
|
+
reason: "File appears to already be reverted",
|
|
682
|
+
checkpoint,
|
|
683
|
+
currentHash
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
safe: false,
|
|
688
|
+
reason: "File was modified after this edit. Current content does not match expected state.",
|
|
689
|
+
checkpoint,
|
|
690
|
+
currentHash
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Remove a checkpoint after successful revert or accept
|
|
695
|
+
*/
|
|
696
|
+
removeCheckpoint(id) {
|
|
697
|
+
const checkpoint = this.checkpoints.get(id);
|
|
698
|
+
if (!checkpoint) return false;
|
|
699
|
+
this.checkpoints.delete(id);
|
|
700
|
+
const fileCheckpointIds = this.fileCheckpoints.get(checkpoint.filePath);
|
|
701
|
+
if (fileCheckpointIds) {
|
|
702
|
+
const filtered = fileCheckpointIds.filter((cpId) => cpId !== id);
|
|
703
|
+
if (filtered.length === 0) {
|
|
704
|
+
this.fileCheckpoints.delete(checkpoint.filePath);
|
|
705
|
+
} else {
|
|
706
|
+
this.fileCheckpoints.set(checkpoint.filePath, filtered);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Get all checkpoints (for debugging/listing)
|
|
713
|
+
*/
|
|
714
|
+
getAllCheckpoints() {
|
|
715
|
+
return Array.from(this.checkpoints.values()).sort((a, b) => b.timestamp - a.timestamp);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Clear all checkpoints (for cleanup)
|
|
719
|
+
*/
|
|
720
|
+
clear() {
|
|
721
|
+
this.checkpoints.clear();
|
|
722
|
+
this.fileCheckpoints.clear();
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Get statistics
|
|
726
|
+
*/
|
|
727
|
+
getStats() {
|
|
728
|
+
return {
|
|
729
|
+
totalCheckpoints: this.checkpoints.size,
|
|
730
|
+
filesTracked: this.fileCheckpoints.size
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
var checkpointStore = new CheckpointStore();
|
|
617
735
|
zod.z.object({
|
|
618
736
|
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
737
|
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)")
|
|
738
|
+
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)"),
|
|
739
|
+
toolCallId: zod.z.string().optional().describe("Optional tool call ID for checkpoint tracking")
|
|
621
740
|
});
|
|
622
741
|
var apply_patch = async function(input, projectCwd) {
|
|
623
|
-
const { file_path, new_string, old_string } = input;
|
|
742
|
+
const { file_path, new_string, old_string, toolCallId } = input;
|
|
624
743
|
try {
|
|
625
744
|
if (!file_path) {
|
|
626
745
|
return {
|
|
@@ -695,6 +814,13 @@ var apply_patch = async function(input, projectCwd) {
|
|
|
695
814
|
};
|
|
696
815
|
}
|
|
697
816
|
const newContent = fileContent.replace(old_string, new_string);
|
|
817
|
+
const checkpointId = toolCallId || crypto.randomUUID();
|
|
818
|
+
const checkpoint = checkpointStore.createCheckpoint(
|
|
819
|
+
checkpointId,
|
|
820
|
+
absolute_file_path,
|
|
821
|
+
fileContent,
|
|
822
|
+
newContent
|
|
823
|
+
);
|
|
698
824
|
try {
|
|
699
825
|
await promises.writeFile(absolute_file_path, newContent, "utf-8");
|
|
700
826
|
const diffStats = calculateDiffStats(fileContent, newContent);
|
|
@@ -704,9 +830,14 @@ var apply_patch = async function(input, projectCwd) {
|
|
|
704
830
|
new_string,
|
|
705
831
|
linesAdded: diffStats.linesAdded,
|
|
706
832
|
linesRemoved: diffStats.linesRemoved,
|
|
707
|
-
message: `Successfully replaced string in file: ${file_path}
|
|
833
|
+
message: `Successfully replaced string in file: ${file_path}`,
|
|
834
|
+
// Include checkpoint info for frontend
|
|
835
|
+
checkpointId: checkpoint.id,
|
|
836
|
+
beforeHash: checkpoint.beforeHash,
|
|
837
|
+
afterHash: checkpoint.afterHash
|
|
708
838
|
};
|
|
709
839
|
} catch (error) {
|
|
840
|
+
checkpointStore.removeCheckpoint(checkpointId);
|
|
710
841
|
return {
|
|
711
842
|
success: false,
|
|
712
843
|
message: `Failed to write to file: ${file_path}`,
|
|
@@ -724,10 +855,11 @@ var apply_patch = async function(input, projectCwd) {
|
|
|
724
855
|
zod.z.object({
|
|
725
856
|
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
857
|
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()
|
|
858
|
+
providedNewFile: zod.z.boolean().describe("The new file content to write to the file").optional(),
|
|
859
|
+
toolCallId: zod.z.string().optional().describe("Optional tool call ID for checkpoint tracking")
|
|
728
860
|
});
|
|
729
861
|
var editFiles = async function(input, projectCwd) {
|
|
730
|
-
const { target_file, content, providedNewFile } = input;
|
|
862
|
+
const { target_file, content, providedNewFile, toolCallId } = input;
|
|
731
863
|
try {
|
|
732
864
|
if (projectCwd) {
|
|
733
865
|
const validation = validatePath(target_file, projectCwd);
|
|
@@ -759,7 +891,19 @@ var editFiles = async function(input, projectCwd) {
|
|
|
759
891
|
isNewFile = true;
|
|
760
892
|
}
|
|
761
893
|
}
|
|
762
|
-
|
|
894
|
+
const checkpointId = toolCallId || crypto.randomUUID();
|
|
895
|
+
const checkpoint = checkpointStore.createCheckpoint(
|
|
896
|
+
checkpointId,
|
|
897
|
+
filePath,
|
|
898
|
+
existingContent,
|
|
899
|
+
content
|
|
900
|
+
);
|
|
901
|
+
try {
|
|
902
|
+
await fs3__default.default.promises.writeFile(filePath, content);
|
|
903
|
+
} catch (writeError) {
|
|
904
|
+
checkpointStore.removeCheckpoint(checkpointId);
|
|
905
|
+
throw writeError;
|
|
906
|
+
}
|
|
763
907
|
const diffStats = calculateDiffStats(existingContent, content);
|
|
764
908
|
if (isNewFile) {
|
|
765
909
|
return {
|
|
@@ -769,7 +913,11 @@ var editFiles = async function(input, projectCwd) {
|
|
|
769
913
|
new_string: content,
|
|
770
914
|
message: `Created new file: ${target_file}`,
|
|
771
915
|
linesAdded: diffStats.linesAdded,
|
|
772
|
-
linesRemoved: diffStats.linesRemoved
|
|
916
|
+
linesRemoved: diffStats.linesRemoved,
|
|
917
|
+
// Include checkpoint info for frontend
|
|
918
|
+
checkpointId: checkpoint.id,
|
|
919
|
+
beforeHash: checkpoint.beforeHash,
|
|
920
|
+
afterHash: checkpoint.afterHash
|
|
773
921
|
};
|
|
774
922
|
} else {
|
|
775
923
|
return {
|
|
@@ -779,7 +927,11 @@ var editFiles = async function(input, projectCwd) {
|
|
|
779
927
|
new_string: content,
|
|
780
928
|
message: `Modified file: ${target_file}`,
|
|
781
929
|
linesAdded: diffStats.linesAdded,
|
|
782
|
-
linesRemoved: diffStats.linesRemoved
|
|
930
|
+
linesRemoved: diffStats.linesRemoved,
|
|
931
|
+
// Include checkpoint info for frontend
|
|
932
|
+
checkpointId: checkpoint.id,
|
|
933
|
+
beforeHash: checkpoint.beforeHash,
|
|
934
|
+
afterHash: checkpoint.afterHash
|
|
783
935
|
};
|
|
784
936
|
}
|
|
785
937
|
} catch (error) {
|
|
@@ -1335,7 +1487,15 @@ var startHttpServer = (connection) => {
|
|
|
1335
1487
|
});
|
|
1336
1488
|
app.post("/revert", async (c) => {
|
|
1337
1489
|
try {
|
|
1338
|
-
const {
|
|
1490
|
+
const {
|
|
1491
|
+
filePath,
|
|
1492
|
+
oldString,
|
|
1493
|
+
newString,
|
|
1494
|
+
projectCwd,
|
|
1495
|
+
checkpointId,
|
|
1496
|
+
expectedAfterHash,
|
|
1497
|
+
force = false
|
|
1498
|
+
} = await c.req.json();
|
|
1339
1499
|
if (!filePath || oldString === void 0) {
|
|
1340
1500
|
return c.json({ error: "filePath and oldString required" }, 400);
|
|
1341
1501
|
}
|
|
@@ -1359,25 +1519,127 @@ var startHttpServer = (connection) => {
|
|
|
1359
1519
|
}
|
|
1360
1520
|
return c.json({ error: `Failed to read file: ${error.message}` }, 500);
|
|
1361
1521
|
}
|
|
1522
|
+
if (checkpointId) {
|
|
1523
|
+
const verification = checkpointStore.verifyFileState(checkpointId, currentContent);
|
|
1524
|
+
if (!verification.safe && !force) {
|
|
1525
|
+
return c.json({
|
|
1526
|
+
success: false,
|
|
1527
|
+
conflict: true,
|
|
1528
|
+
error: verification.reason,
|
|
1529
|
+
currentHash: verification.currentHash,
|
|
1530
|
+
expectedHash: verification.checkpoint?.afterHash,
|
|
1531
|
+
checkpointId
|
|
1532
|
+
}, 409);
|
|
1533
|
+
}
|
|
1534
|
+
if (verification.checkpoint) {
|
|
1535
|
+
try {
|
|
1536
|
+
await promises.writeFile(resolved, verification.checkpoint.beforeContent, "utf-8");
|
|
1537
|
+
checkpointStore.removeCheckpoint(checkpointId);
|
|
1538
|
+
return c.json({ success: true, usedCheckpoint: true });
|
|
1539
|
+
} catch (writeError) {
|
|
1540
|
+
return c.json({ error: `Failed to write file: ${writeError.message}` }, 500);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (expectedAfterHash && !force) {
|
|
1545
|
+
const currentHash = checkpointStore.computeHash(currentContent);
|
|
1546
|
+
if (currentHash !== expectedAfterHash) {
|
|
1547
|
+
return c.json({
|
|
1548
|
+
success: false,
|
|
1549
|
+
conflict: true,
|
|
1550
|
+
error: "File was modified after this edit. Current content does not match expected state.",
|
|
1551
|
+
currentHash,
|
|
1552
|
+
expectedHash: expectedAfterHash
|
|
1553
|
+
}, 409);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1362
1556
|
let finalContent;
|
|
1363
1557
|
if (newString && newString !== oldString) {
|
|
1364
1558
|
if (!currentContent.includes(newString)) {
|
|
1365
|
-
return c.json({
|
|
1559
|
+
return c.json({
|
|
1560
|
+
success: false,
|
|
1561
|
+
conflict: true,
|
|
1562
|
+
error: "Cannot revert: the new content is not found in the current file. The file may have been modified."
|
|
1563
|
+
}, 409);
|
|
1366
1564
|
}
|
|
1367
1565
|
const occurrences = currentContent.split(newString).length - 1;
|
|
1368
1566
|
if (occurrences > 1) {
|
|
1369
|
-
return c.json({
|
|
1567
|
+
return c.json({
|
|
1568
|
+
success: false,
|
|
1569
|
+
conflict: true,
|
|
1570
|
+
error: "Cannot revert: the new content appears multiple times in the file"
|
|
1571
|
+
}, 409);
|
|
1370
1572
|
}
|
|
1371
1573
|
finalContent = currentContent.replace(newString, oldString);
|
|
1372
1574
|
} else {
|
|
1373
1575
|
finalContent = oldString;
|
|
1374
1576
|
}
|
|
1375
1577
|
await promises.writeFile(resolved, finalContent, "utf-8");
|
|
1578
|
+
if (checkpointId) {
|
|
1579
|
+
checkpointStore.removeCheckpoint(checkpointId);
|
|
1580
|
+
}
|
|
1376
1581
|
return c.json({ success: true });
|
|
1377
1582
|
} catch (error) {
|
|
1378
1583
|
return c.json({ error: error.message }, 500);
|
|
1379
1584
|
}
|
|
1380
1585
|
});
|
|
1586
|
+
app.post("/revert/force", async (c) => {
|
|
1587
|
+
try {
|
|
1588
|
+
const { filePath, checkpointId, projectCwd } = await c.req.json();
|
|
1589
|
+
if (!checkpointId) {
|
|
1590
|
+
return c.json({ error: "checkpointId is required for force revert" }, 400);
|
|
1591
|
+
}
|
|
1592
|
+
const checkpoint = checkpointStore.getCheckpoint(checkpointId);
|
|
1593
|
+
if (!checkpoint) {
|
|
1594
|
+
return c.json({ error: "Checkpoint not found" }, 404);
|
|
1595
|
+
}
|
|
1596
|
+
let resolved;
|
|
1597
|
+
if (projectCwd) {
|
|
1598
|
+
resolved = path9__default.default.isAbsolute(filePath || checkpoint.filePath) ? filePath || checkpoint.filePath : path9__default.default.resolve(projectCwd, filePath || checkpoint.filePath);
|
|
1599
|
+
const normalizedResolved = path9__default.default.normalize(resolved);
|
|
1600
|
+
const normalizedCwd = path9__default.default.normalize(projectCwd);
|
|
1601
|
+
if (!normalizedResolved.startsWith(normalizedCwd)) {
|
|
1602
|
+
return c.json({ error: "Path is outside project directory" }, 403);
|
|
1603
|
+
}
|
|
1604
|
+
} else {
|
|
1605
|
+
resolved = checkpoint.filePath;
|
|
1606
|
+
}
|
|
1607
|
+
try {
|
|
1608
|
+
await promises.writeFile(resolved, checkpoint.beforeContent, "utf-8");
|
|
1609
|
+
checkpointStore.removeCheckpoint(checkpointId);
|
|
1610
|
+
return c.json({ success: true, forced: true });
|
|
1611
|
+
} catch (writeError) {
|
|
1612
|
+
return c.json({ error: `Failed to write file: ${writeError.message}` }, 500);
|
|
1613
|
+
}
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
return c.json({ error: error.message }, 500);
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
app.get("/checkpoints/:checkpointId", (c) => {
|
|
1619
|
+
const checkpointId = c.req.param("checkpointId");
|
|
1620
|
+
const checkpoint = checkpointStore.getCheckpoint(checkpointId);
|
|
1621
|
+
if (!checkpoint) {
|
|
1622
|
+
return c.json({ error: "Checkpoint not found" }, 404);
|
|
1623
|
+
}
|
|
1624
|
+
return c.json({
|
|
1625
|
+
id: checkpoint.id,
|
|
1626
|
+
filePath: checkpoint.filePath,
|
|
1627
|
+
beforeHash: checkpoint.beforeHash,
|
|
1628
|
+
afterHash: checkpoint.afterHash,
|
|
1629
|
+
timestamp: checkpoint.timestamp
|
|
1630
|
+
});
|
|
1631
|
+
});
|
|
1632
|
+
app.get("/checkpoints", (c) => {
|
|
1633
|
+
const stats = checkpointStore.getStats();
|
|
1634
|
+
const checkpoints = checkpointStore.getAllCheckpoints().map((cp) => ({
|
|
1635
|
+
id: cp.id,
|
|
1636
|
+
filePath: cp.filePath,
|
|
1637
|
+
beforeHash: cp.beforeHash,
|
|
1638
|
+
afterHash: cp.afterHash,
|
|
1639
|
+
timestamp: cp.timestamp
|
|
1640
|
+
}));
|
|
1641
|
+
return c.json({ stats, checkpoints });
|
|
1642
|
+
});
|
|
1381
1643
|
app.get("/projects", (c) => {
|
|
1382
1644
|
const projects = projectRegistry.list();
|
|
1383
1645
|
return c.json({ projects });
|
|
@@ -1895,7 +2157,7 @@ function getDaemonPid() {
|
|
|
1895
2157
|
return null;
|
|
1896
2158
|
}
|
|
1897
2159
|
}
|
|
1898
|
-
var VERSION = "0.0.
|
|
2160
|
+
var VERSION = "0.0.5";
|
|
1899
2161
|
var PROJECT_DIR = process.cwd();
|
|
1900
2162
|
function promptUser(question) {
|
|
1901
2163
|
const rl = readline__default.default.createInterface({
|