@pikaa-ai/pikaa 0.3.14 → 0.3.15
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.js +764 -355
- package/dist/index.js +698 -319
- package/package.json +1 -1
- package/templates/base/groupy_prompt.md +14 -0
package/dist/cli.js
CHANGED
|
@@ -3,21 +3,143 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
5
|
import { resolve as resolve21 } from "path";
|
|
6
|
-
import { existsSync as
|
|
6
|
+
import { existsSync as existsSync22 } from "fs";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
|
|
9
9
|
// src/auth/store.ts
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
11
|
-
import { resolve } from "path";
|
|
10
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
11
|
+
import { resolve as resolve2 } from "path";
|
|
12
|
+
|
|
13
|
+
// src/config/paths.ts
|
|
14
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
15
|
+
import { resolve, join } from "path";
|
|
12
16
|
import { homedir } from "os";
|
|
17
|
+
function getPikaaHomeDir() {
|
|
18
|
+
const envDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME;
|
|
19
|
+
if (envDir) {
|
|
20
|
+
return resolve(envDir);
|
|
21
|
+
}
|
|
22
|
+
return resolve(homedir(), ".pikaa");
|
|
23
|
+
}
|
|
24
|
+
function getLegacyGroupyHomeDir() {
|
|
25
|
+
if (process.env.GROUPY_HOME) {
|
|
26
|
+
return resolve(process.env.GROUPY_HOME);
|
|
27
|
+
}
|
|
28
|
+
return resolve(homedir(), ".groupy");
|
|
29
|
+
}
|
|
30
|
+
var hasMigrated = false;
|
|
31
|
+
function copyDirRecursiveSync(src, dest) {
|
|
32
|
+
if (!existsSync(src))
|
|
33
|
+
return;
|
|
34
|
+
if (!existsSync(dest)) {
|
|
35
|
+
mkdirSync(dest, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
const entries = readdirSync(src);
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const srcPath = join(src, entry);
|
|
40
|
+
const destPath = join(dest, entry);
|
|
41
|
+
const stat = statSync(srcPath);
|
|
42
|
+
if (stat.isDirectory()) {
|
|
43
|
+
copyDirRecursiveSync(srcPath, destPath);
|
|
44
|
+
} else if (!existsSync(destPath)) {
|
|
45
|
+
try {
|
|
46
|
+
copyFileSync(srcPath, destPath);
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function ensurePikaaHomeMigrated(force = false) {
|
|
52
|
+
const pikaaHome = getPikaaHomeDir();
|
|
53
|
+
const legacyHome = getLegacyGroupyHomeDir();
|
|
54
|
+
try {
|
|
55
|
+
if (!existsSync(pikaaHome)) {
|
|
56
|
+
mkdirSync(pikaaHome, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
if (hasMigrated && !force) {
|
|
60
|
+
return pikaaHome;
|
|
61
|
+
}
|
|
62
|
+
hasMigrated = true;
|
|
63
|
+
try {
|
|
64
|
+
if (existsSync(legacyHome) && legacyHome !== pikaaHome) {
|
|
65
|
+
const legacyCreds = join(legacyHome, "credentials.json");
|
|
66
|
+
const pikaaCreds = join(pikaaHome, "credentials.json");
|
|
67
|
+
if (existsSync(legacyCreds) && !existsSync(pikaaCreds)) {
|
|
68
|
+
copyFileSync(legacyCreds, pikaaCreds);
|
|
69
|
+
}
|
|
70
|
+
const legacyThreads = join(legacyHome, "groupy_threads.db");
|
|
71
|
+
const pikaaThreads = join(pikaaHome, "pikaa_threads.db");
|
|
72
|
+
if (existsSync(legacyThreads) && !existsSync(pikaaThreads)) {
|
|
73
|
+
copyFileSync(legacyThreads, pikaaThreads);
|
|
74
|
+
}
|
|
75
|
+
const legacyRules = join(legacyHome, "groupy_rules.db");
|
|
76
|
+
const pikaaRules = join(pikaaHome, "pikaa_rules.db");
|
|
77
|
+
if (existsSync(legacyRules) && !existsSync(pikaaRules)) {
|
|
78
|
+
copyFileSync(legacyRules, pikaaRules);
|
|
79
|
+
}
|
|
80
|
+
const legacyGraph = join(legacyHome, "agent_graph.db");
|
|
81
|
+
const pikaaGraph = join(pikaaHome, "agent_graph.db");
|
|
82
|
+
if (existsSync(legacyGraph) && !existsSync(pikaaGraph)) {
|
|
83
|
+
copyFileSync(legacyGraph, pikaaGraph);
|
|
84
|
+
}
|
|
85
|
+
const legacyMemories = join(legacyHome, "memories.md");
|
|
86
|
+
const pikaaMemories = join(pikaaHome, "memories.md");
|
|
87
|
+
if (existsSync(legacyMemories) && !existsSync(pikaaMemories)) {
|
|
88
|
+
copyFileSync(legacyMemories, pikaaMemories);
|
|
89
|
+
}
|
|
90
|
+
copyDirRecursiveSync(join(legacyHome, "skills"), join(pikaaHome, "skills"));
|
|
91
|
+
copyDirRecursiveSync(join(legacyHome, "templates"), join(pikaaHome, "templates"));
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
return pikaaHome;
|
|
95
|
+
}
|
|
96
|
+
function getCredentialsPath() {
|
|
97
|
+
ensurePikaaHomeMigrated();
|
|
98
|
+
return join(getPikaaHomeDir(), "credentials.json");
|
|
99
|
+
}
|
|
100
|
+
function getThreadsDbPath() {
|
|
101
|
+
ensurePikaaHomeMigrated();
|
|
102
|
+
return join(getPikaaHomeDir(), "pikaa_threads.db");
|
|
103
|
+
}
|
|
104
|
+
function getPrefixRulesDbPath() {
|
|
105
|
+
ensurePikaaHomeMigrated();
|
|
106
|
+
return join(getPikaaHomeDir(), "pikaa_rules.db");
|
|
107
|
+
}
|
|
108
|
+
function getAgentGraphDbPath() {
|
|
109
|
+
ensurePikaaHomeMigrated();
|
|
110
|
+
return join(getPikaaHomeDir(), "agent_graph.db");
|
|
111
|
+
}
|
|
112
|
+
function getGlobalSkillsDir() {
|
|
113
|
+
ensurePikaaHomeMigrated();
|
|
114
|
+
return join(getPikaaHomeDir(), "skills");
|
|
115
|
+
}
|
|
116
|
+
function getGlobalTemplatesDir() {
|
|
117
|
+
ensurePikaaHomeMigrated();
|
|
118
|
+
return join(getPikaaHomeDir(), "templates");
|
|
119
|
+
}
|
|
120
|
+
function getGlobalMemoriesPath() {
|
|
121
|
+
ensurePikaaHomeMigrated();
|
|
122
|
+
return join(getPikaaHomeDir(), "memories.md");
|
|
123
|
+
}
|
|
124
|
+
function getProjectsDir() {
|
|
125
|
+
ensurePikaaHomeMigrated();
|
|
126
|
+
const dir = join(getPikaaHomeDir(), "projects");
|
|
127
|
+
if (!existsSync(dir)) {
|
|
128
|
+
try {
|
|
129
|
+
mkdirSync(dir, { recursive: true });
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
return dir;
|
|
133
|
+
}
|
|
13
134
|
|
|
135
|
+
// src/auth/store.ts
|
|
14
136
|
class CredentialsStore {
|
|
15
137
|
filePath;
|
|
16
138
|
constructor(customPath) {
|
|
17
|
-
this.filePath = customPath ||
|
|
139
|
+
this.filePath = customPath || getCredentialsPath();
|
|
18
140
|
}
|
|
19
141
|
load() {
|
|
20
|
-
if (!
|
|
142
|
+
if (!existsSync2(this.filePath))
|
|
21
143
|
return null;
|
|
22
144
|
try {
|
|
23
145
|
const raw = readFileSync(this.filePath, "utf8");
|
|
@@ -48,14 +170,14 @@ class CredentialsStore {
|
|
|
48
170
|
return creds?.user;
|
|
49
171
|
}
|
|
50
172
|
save(credentials) {
|
|
51
|
-
const dir =
|
|
52
|
-
if (!
|
|
53
|
-
|
|
173
|
+
const dir = resolve2(this.filePath, "..");
|
|
174
|
+
if (!existsSync2(dir)) {
|
|
175
|
+
mkdirSync2(dir, { recursive: true });
|
|
54
176
|
}
|
|
55
177
|
writeFileSync(this.filePath, JSON.stringify(credentials, null, 2), "utf8");
|
|
56
178
|
}
|
|
57
179
|
clear() {
|
|
58
|
-
if (
|
|
180
|
+
if (existsSync2(this.filePath)) {
|
|
59
181
|
try {
|
|
60
182
|
unlinkSync(this.filePath);
|
|
61
183
|
return true;
|
|
@@ -633,14 +755,13 @@ function formatWorldStatePrompt(state) {
|
|
|
633
755
|
}
|
|
634
756
|
|
|
635
757
|
// src/prompts/loader.ts
|
|
636
|
-
import { existsSync as
|
|
637
|
-
import { resolve as
|
|
758
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
759
|
+
import { resolve as resolve3, join as join2 } from "path";
|
|
638
760
|
import { homedir as homedir2 } from "os";
|
|
639
|
-
|
|
640
761
|
class PromptTemplateLoader {
|
|
641
762
|
builtInTemplatesDir;
|
|
642
763
|
constructor(builtInDir) {
|
|
643
|
-
this.builtInTemplatesDir = builtInDir ||
|
|
764
|
+
this.builtInTemplatesDir = builtInDir || resolve3(join2(import.meta.dir, "..", "..", "templates"));
|
|
644
765
|
}
|
|
645
766
|
loadTemplate(relativePath, variables = {}, cwd) {
|
|
646
767
|
const rawContent = this.resolveTemplateContent(relativePath, cwd);
|
|
@@ -649,21 +770,27 @@ class PromptTemplateLoader {
|
|
|
649
770
|
resolveTemplateContent(relativePath, cwd) {
|
|
650
771
|
const normalizedRel = relativePath.replace(/^\/+/, "");
|
|
651
772
|
if (cwd) {
|
|
652
|
-
const workspacePath =
|
|
653
|
-
if (
|
|
773
|
+
const workspacePath = join2(cwd, ".agents", "templates", normalizedRel);
|
|
774
|
+
if (existsSync3(workspacePath)) {
|
|
654
775
|
try {
|
|
655
776
|
return readFileSync2(workspacePath, "utf-8");
|
|
656
777
|
} catch {}
|
|
657
778
|
}
|
|
658
779
|
}
|
|
659
|
-
const globalPath =
|
|
660
|
-
if (
|
|
780
|
+
const globalPath = join2(getGlobalTemplatesDir(), normalizedRel);
|
|
781
|
+
if (existsSync3(globalPath)) {
|
|
661
782
|
try {
|
|
662
783
|
return readFileSync2(globalPath, "utf-8");
|
|
663
784
|
} catch {}
|
|
664
785
|
}
|
|
665
|
-
const
|
|
666
|
-
if (
|
|
786
|
+
const legacyGlobalPath = join2(homedir2(), ".groupy", "templates", normalizedRel);
|
|
787
|
+
if (existsSync3(legacyGlobalPath)) {
|
|
788
|
+
try {
|
|
789
|
+
return readFileSync2(legacyGlobalPath, "utf-8");
|
|
790
|
+
} catch {}
|
|
791
|
+
}
|
|
792
|
+
const builtInPath = join2(this.builtInTemplatesDir, normalizedRel);
|
|
793
|
+
if (existsSync3(builtInPath)) {
|
|
667
794
|
try {
|
|
668
795
|
return readFileSync2(builtInPath, "utf-8");
|
|
669
796
|
} catch {}
|
|
@@ -680,8 +807,8 @@ class PromptTemplateLoader {
|
|
|
680
807
|
var globalPromptLoader = new PromptTemplateLoader;
|
|
681
808
|
|
|
682
809
|
// src/prompts/agents-md.ts
|
|
683
|
-
import { existsSync as
|
|
684
|
-
import { resolve as
|
|
810
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
811
|
+
import { resolve as resolve4, join as join3, dirname as dirname2 } from "path";
|
|
685
812
|
var DEFAULT_AGENTS_MD_FILENAMES = [
|
|
686
813
|
"AGENTS.override.md",
|
|
687
814
|
"AGENTS.md",
|
|
@@ -696,28 +823,28 @@ var AGENTS_MD_SEPARATOR = `
|
|
|
696
823
|
|
|
697
824
|
class AgentsMdLoader {
|
|
698
825
|
findProjectRoot(startDir) {
|
|
699
|
-
let current =
|
|
826
|
+
let current = resolve4(startDir);
|
|
700
827
|
while (true) {
|
|
701
|
-
if (
|
|
828
|
+
if (existsSync4(join3(current, ".git"))) {
|
|
702
829
|
return current;
|
|
703
830
|
}
|
|
704
|
-
const parent =
|
|
831
|
+
const parent = dirname2(current);
|
|
705
832
|
if (parent === current) {
|
|
706
|
-
return
|
|
833
|
+
return resolve4(startDir);
|
|
707
834
|
}
|
|
708
835
|
current = parent;
|
|
709
836
|
}
|
|
710
837
|
}
|
|
711
838
|
collectDirectoryHierarchy(targetDir, rootDir) {
|
|
712
839
|
const hierarchy = [];
|
|
713
|
-
let current =
|
|
714
|
-
const normalizedRoot =
|
|
840
|
+
let current = resolve4(targetDir);
|
|
841
|
+
const normalizedRoot = resolve4(rootDir);
|
|
715
842
|
while (true) {
|
|
716
843
|
hierarchy.unshift(current);
|
|
717
844
|
if (current === normalizedRoot) {
|
|
718
845
|
break;
|
|
719
846
|
}
|
|
720
|
-
const parent =
|
|
847
|
+
const parent = dirname2(current);
|
|
721
848
|
if (parent === current) {
|
|
722
849
|
break;
|
|
723
850
|
}
|
|
@@ -732,8 +859,8 @@ class AgentsMdLoader {
|
|
|
732
859
|
const sourcePaths = [];
|
|
733
860
|
for (const dir of dirHierarchy) {
|
|
734
861
|
for (const filename of fallbackFilenames) {
|
|
735
|
-
const filePath =
|
|
736
|
-
if (
|
|
862
|
+
const filePath = join3(dir, filename);
|
|
863
|
+
if (existsSync4(filePath)) {
|
|
737
864
|
try {
|
|
738
865
|
const content = readFileSync3(filePath, "utf-8").trim();
|
|
739
866
|
if (content) {
|
|
@@ -1345,13 +1472,13 @@ class Session {
|
|
|
1345
1472
|
type: "StatusChanged",
|
|
1346
1473
|
status: "waiting_approval"
|
|
1347
1474
|
});
|
|
1348
|
-
return new Promise((
|
|
1475
|
+
return new Promise((resolve5) => {
|
|
1349
1476
|
this.pendingApprovals.set(params.approvalId, (approved) => {
|
|
1350
1477
|
this.emitEvent({
|
|
1351
1478
|
type: "StatusChanged",
|
|
1352
1479
|
status: "running"
|
|
1353
1480
|
});
|
|
1354
|
-
|
|
1481
|
+
resolve5(approved);
|
|
1355
1482
|
});
|
|
1356
1483
|
});
|
|
1357
1484
|
}
|
|
@@ -1374,13 +1501,13 @@ class Session {
|
|
|
1374
1501
|
type: "StatusChanged",
|
|
1375
1502
|
status: "waiting_user_input"
|
|
1376
1503
|
});
|
|
1377
|
-
return new Promise((
|
|
1504
|
+
return new Promise((resolve5) => {
|
|
1378
1505
|
this.pendingUserQuestions.set(params.questionId, (answer) => {
|
|
1379
1506
|
this.emitEvent({
|
|
1380
1507
|
type: "StatusChanged",
|
|
1381
1508
|
status: "running"
|
|
1382
1509
|
});
|
|
1383
|
-
|
|
1510
|
+
resolve5(answer);
|
|
1384
1511
|
});
|
|
1385
1512
|
});
|
|
1386
1513
|
}
|
|
@@ -1410,7 +1537,7 @@ class Session {
|
|
|
1410
1537
|
return handleTurnInput(this, { text, images });
|
|
1411
1538
|
}
|
|
1412
1539
|
async promptAndWait(text, images, timeoutMs = 30000) {
|
|
1413
|
-
return new Promise((
|
|
1540
|
+
return new Promise((resolve5, reject) => {
|
|
1414
1541
|
const timer = setTimeout(() => {
|
|
1415
1542
|
unsub();
|
|
1416
1543
|
reject(new Error(`Turn timed out after ${timeoutMs}ms`));
|
|
@@ -1419,7 +1546,7 @@ class Session {
|
|
|
1419
1546
|
if (event.msg.type === "TurnCompleted") {
|
|
1420
1547
|
clearTimeout(timer);
|
|
1421
1548
|
unsub();
|
|
1422
|
-
|
|
1549
|
+
resolve5();
|
|
1423
1550
|
} else if (event.msg.type === "Error") {
|
|
1424
1551
|
clearTimeout(timer);
|
|
1425
1552
|
unsub();
|
|
@@ -1438,8 +1565,8 @@ class Session {
|
|
|
1438
1565
|
if (this.submissionQueue.length > 0) {
|
|
1439
1566
|
yield this.submissionQueue.shift();
|
|
1440
1567
|
} else {
|
|
1441
|
-
const nextSub = await new Promise((
|
|
1442
|
-
this.submissionResolvers.push(
|
|
1568
|
+
const nextSub = await new Promise((resolve5) => {
|
|
1569
|
+
this.submissionResolvers.push(resolve5);
|
|
1443
1570
|
});
|
|
1444
1571
|
yield nextSub;
|
|
1445
1572
|
}
|
|
@@ -1453,9 +1580,9 @@ class Session {
|
|
|
1453
1580
|
}
|
|
1454
1581
|
}
|
|
1455
1582
|
// src/tools/handlers/apply-patch.ts
|
|
1456
|
-
import { existsSync as
|
|
1457
|
-
import { resolve as
|
|
1458
|
-
import { mkdirSync as
|
|
1583
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
1584
|
+
import { resolve as resolve5, dirname as dirname3 } from "path";
|
|
1585
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1459
1586
|
var applyPatchTool = {
|
|
1460
1587
|
name: "apply_patch",
|
|
1461
1588
|
description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
|
|
@@ -1482,7 +1609,7 @@ var applyPatchTool = {
|
|
|
1482
1609
|
if (!rawPath) {
|
|
1483
1610
|
return { output: "Error: 'path' parameter is required", isError: true };
|
|
1484
1611
|
}
|
|
1485
|
-
const filePath =
|
|
1612
|
+
const filePath = resolve5(ctx.cwd, rawPath);
|
|
1486
1613
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1487
1614
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1488
1615
|
if (ctx.execPolicy) {
|
|
@@ -1501,7 +1628,7 @@ var applyPatchTool = {
|
|
|
1501
1628
|
}
|
|
1502
1629
|
}
|
|
1503
1630
|
}
|
|
1504
|
-
if (!
|
|
1631
|
+
if (!existsSync5(filePath)) {
|
|
1505
1632
|
if (targetContent) {
|
|
1506
1633
|
return {
|
|
1507
1634
|
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.`,
|
|
@@ -1509,7 +1636,7 @@ var applyPatchTool = {
|
|
|
1509
1636
|
};
|
|
1510
1637
|
}
|
|
1511
1638
|
try {
|
|
1512
|
-
|
|
1639
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
1513
1640
|
writeFileSync2(filePath, replacementContent, "utf8");
|
|
1514
1641
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1515
1642
|
} catch (err) {
|
|
@@ -1635,7 +1762,7 @@ class WindowsSandbox {
|
|
|
1635
1762
|
}
|
|
1636
1763
|
|
|
1637
1764
|
// src/security/kernel/linux.ts
|
|
1638
|
-
import { existsSync as
|
|
1765
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1639
1766
|
|
|
1640
1767
|
class LinuxSandbox {
|
|
1641
1768
|
hasBwrap = false;
|
|
@@ -1646,7 +1773,7 @@ class LinuxSandbox {
|
|
|
1646
1773
|
if (process.platform !== "linux") {
|
|
1647
1774
|
return;
|
|
1648
1775
|
}
|
|
1649
|
-
this.hasBwrap =
|
|
1776
|
+
this.hasBwrap = existsSync6("/usr/bin/bwrap") || existsSync6("/bin/bwrap") || existsSync6("/usr/local/bin/bwrap");
|
|
1650
1777
|
}
|
|
1651
1778
|
wrapCommand(cmd, profile) {
|
|
1652
1779
|
if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
|
|
@@ -1680,7 +1807,7 @@ class LinuxSandbox {
|
|
|
1680
1807
|
}
|
|
1681
1808
|
|
|
1682
1809
|
// src/security/kernel/macos.ts
|
|
1683
|
-
import { existsSync as
|
|
1810
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1684
1811
|
|
|
1685
1812
|
class MacOSSandbox {
|
|
1686
1813
|
hasSandboxExec = false;
|
|
@@ -1691,7 +1818,7 @@ class MacOSSandbox {
|
|
|
1691
1818
|
if (process.platform !== "darwin") {
|
|
1692
1819
|
return;
|
|
1693
1820
|
}
|
|
1694
|
-
this.hasSandboxExec =
|
|
1821
|
+
this.hasSandboxExec = existsSync7("/usr/bin/sandbox-exec");
|
|
1695
1822
|
}
|
|
1696
1823
|
generateProfile(profile) {
|
|
1697
1824
|
const rules = [
|
|
@@ -1730,7 +1857,7 @@ class MacOSSandbox {
|
|
|
1730
1857
|
}
|
|
1731
1858
|
|
|
1732
1859
|
// src/security/kernel/manager.ts
|
|
1733
|
-
import { resolve as
|
|
1860
|
+
import { resolve as resolve6, normalize } from "path";
|
|
1734
1861
|
|
|
1735
1862
|
class KernelSandboxManager {
|
|
1736
1863
|
windowsSandbox;
|
|
@@ -1752,10 +1879,10 @@ class KernelSandboxManager {
|
|
|
1752
1879
|
};
|
|
1753
1880
|
}
|
|
1754
1881
|
buildDefaultProfile(cwd, allowNetwork = true) {
|
|
1755
|
-
const normCwd = normalize(
|
|
1882
|
+
const normCwd = normalize(resolve6(cwd));
|
|
1756
1883
|
return {
|
|
1757
1884
|
kind: "workspace-write",
|
|
1758
|
-
readableRoots: [normCwd,
|
|
1885
|
+
readableRoots: [normCwd, resolve6(process.cwd())],
|
|
1759
1886
|
writableRoots: [normCwd],
|
|
1760
1887
|
allowNetwork,
|
|
1761
1888
|
limits: {
|
|
@@ -1801,21 +1928,19 @@ var globalKernelSandbox = new KernelSandboxManager;
|
|
|
1801
1928
|
|
|
1802
1929
|
// src/storage/prefix-rules-store.ts
|
|
1803
1930
|
import { Database } from "bun:sqlite";
|
|
1804
|
-
import { existsSync as
|
|
1805
|
-
import { dirname as
|
|
1806
|
-
import { homedir as homedir3 } from "os";
|
|
1807
|
-
|
|
1931
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
1932
|
+
import { dirname as dirname4, resolve as resolve7 } from "path";
|
|
1808
1933
|
class PrefixRulesStore {
|
|
1809
1934
|
db;
|
|
1810
1935
|
constructor(dbOrPath) {
|
|
1811
1936
|
if (dbOrPath instanceof Database) {
|
|
1812
1937
|
this.db = dbOrPath;
|
|
1813
1938
|
} else {
|
|
1814
|
-
const effectivePath = dbOrPath ||
|
|
1939
|
+
const effectivePath = dbOrPath || getPrefixRulesDbPath();
|
|
1815
1940
|
if (effectivePath !== ":memory:") {
|
|
1816
|
-
const dir =
|
|
1817
|
-
if (!
|
|
1818
|
-
|
|
1941
|
+
const dir = dirname4(effectivePath);
|
|
1942
|
+
if (!existsSync8(dir)) {
|
|
1943
|
+
mkdirSync4(dir, { recursive: true });
|
|
1819
1944
|
}
|
|
1820
1945
|
}
|
|
1821
1946
|
this.db = new Database(effectivePath);
|
|
@@ -1838,7 +1963,7 @@ class PrefixRulesStore {
|
|
|
1838
1963
|
addRule(workspacePath, prefixTokens) {
|
|
1839
1964
|
if (!prefixTokens || prefixTokens.length === 0)
|
|
1840
1965
|
return;
|
|
1841
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
1966
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve7(workspacePath);
|
|
1842
1967
|
const tokensJson = JSON.stringify(prefixTokens);
|
|
1843
1968
|
const id = `${normalizedWs}:${tokensJson}`;
|
|
1844
1969
|
const query = this.db.prepare(`
|
|
@@ -1855,7 +1980,7 @@ class PrefixRulesStore {
|
|
|
1855
1980
|
isApproved(workspacePath, commandTokens) {
|
|
1856
1981
|
if (!commandTokens || commandTokens.length === 0)
|
|
1857
1982
|
return false;
|
|
1858
|
-
const normalizedWs =
|
|
1983
|
+
const normalizedWs = resolve7(workspacePath);
|
|
1859
1984
|
const query = this.db.prepare(`
|
|
1860
1985
|
SELECT prefix_tokens FROM approved_prefix_rules
|
|
1861
1986
|
WHERE workspace_path = $ws OR workspace_path = '*'
|
|
@@ -1874,7 +1999,7 @@ class PrefixRulesStore {
|
|
|
1874
1999
|
listRules(workspacePath) {
|
|
1875
2000
|
let rows;
|
|
1876
2001
|
if (workspacePath) {
|
|
1877
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2002
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve7(workspacePath);
|
|
1878
2003
|
const query = this.db.prepare(`
|
|
1879
2004
|
SELECT prefix_tokens FROM approved_prefix_rules
|
|
1880
2005
|
WHERE workspace_path = $ws OR workspace_path = '*'
|
|
@@ -1893,7 +2018,7 @@ class PrefixRulesStore {
|
|
|
1893
2018
|
}).filter((r) => r.length > 0);
|
|
1894
2019
|
}
|
|
1895
2020
|
removeRule(workspacePath, prefixTokens) {
|
|
1896
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2021
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve7(workspacePath);
|
|
1897
2022
|
const tokensJson = JSON.stringify(prefixTokens);
|
|
1898
2023
|
const id = `${normalizedWs}:${tokensJson}`;
|
|
1899
2024
|
const query = this.db.prepare(`
|
|
@@ -2026,7 +2151,7 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2026
2151
|
} catch {}
|
|
2027
2152
|
});
|
|
2028
2153
|
}
|
|
2029
|
-
const timeoutPromise = new Promise((
|
|
2154
|
+
const timeoutPromise = new Promise((resolve8) => setTimeout(() => resolve8({ isTimeout: true }), timeoutMs));
|
|
2030
2155
|
const result = await Promise.race([
|
|
2031
2156
|
proc.exited.then(async (code) => {
|
|
2032
2157
|
const stdout = await new Response(proc.stdout).text();
|
|
@@ -2070,8 +2195,8 @@ ${result.stderr.trim()}`);
|
|
|
2070
2195
|
}
|
|
2071
2196
|
var shellTool = createShellTool();
|
|
2072
2197
|
// src/tools/handlers/file-ops.ts
|
|
2073
|
-
import { readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as
|
|
2074
|
-
import { resolve as
|
|
2198
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync9, statSync as statSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
2199
|
+
import { resolve as resolve8, dirname as dirname5 } from "path";
|
|
2075
2200
|
var readFileTool = {
|
|
2076
2201
|
name: "read_file",
|
|
2077
2202
|
description: "Read the full text content of a file.",
|
|
@@ -2083,8 +2208,8 @@ var readFileTool = {
|
|
|
2083
2208
|
required: ["path"]
|
|
2084
2209
|
},
|
|
2085
2210
|
async execute(args, ctx) {
|
|
2086
|
-
const filePath =
|
|
2087
|
-
if (!
|
|
2211
|
+
const filePath = resolve8(ctx.cwd, String(args.path || ""));
|
|
2212
|
+
if (!existsSync9(filePath)) {
|
|
2088
2213
|
return { output: `Error: File not found: '${args.path}'`, isError: true };
|
|
2089
2214
|
}
|
|
2090
2215
|
try {
|
|
@@ -2105,15 +2230,15 @@ var listDirTool = {
|
|
|
2105
2230
|
}
|
|
2106
2231
|
},
|
|
2107
2232
|
async execute(args, ctx) {
|
|
2108
|
-
const dirPath =
|
|
2109
|
-
if (!
|
|
2233
|
+
const dirPath = resolve8(ctx.cwd, String(args.path || "."));
|
|
2234
|
+
if (!existsSync9(dirPath)) {
|
|
2110
2235
|
return { output: `Error: Directory not found: '${args.path}'`, isError: true };
|
|
2111
2236
|
}
|
|
2112
2237
|
try {
|
|
2113
|
-
const entries =
|
|
2238
|
+
const entries = readdirSync2(dirPath);
|
|
2114
2239
|
const formatted = entries.map((entry) => {
|
|
2115
|
-
const full =
|
|
2116
|
-
const isDir =
|
|
2240
|
+
const full = resolve8(dirPath, entry);
|
|
2241
|
+
const isDir = statSync2(full).isDirectory();
|
|
2117
2242
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2118
2243
|
});
|
|
2119
2244
|
return { output: formatted.join(`
|
|
@@ -2136,7 +2261,7 @@ var writeFileTool = {
|
|
|
2136
2261
|
},
|
|
2137
2262
|
async execute(args, ctx) {
|
|
2138
2263
|
const rawPath = String(args.path || "");
|
|
2139
|
-
const filePath =
|
|
2264
|
+
const filePath = resolve8(ctx.cwd, rawPath);
|
|
2140
2265
|
if (ctx.execPolicy) {
|
|
2141
2266
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2142
2267
|
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
@@ -2154,7 +2279,7 @@ var writeFileTool = {
|
|
|
2154
2279
|
}
|
|
2155
2280
|
}
|
|
2156
2281
|
try {
|
|
2157
|
-
|
|
2282
|
+
mkdirSync5(dirname5(filePath), { recursive: true });
|
|
2158
2283
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
2159
2284
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2160
2285
|
} catch (err) {
|
|
@@ -2277,8 +2402,8 @@ var updatePlanTool = {
|
|
|
2277
2402
|
}
|
|
2278
2403
|
};
|
|
2279
2404
|
// src/search/engine.ts
|
|
2280
|
-
import { readdirSync as
|
|
2281
|
-
import { resolve as
|
|
2405
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3, existsSync as existsSync10 } from "fs";
|
|
2406
|
+
import { resolve as resolve9, relative, join as join4, extname } from "path";
|
|
2282
2407
|
var DEFAULT_IGNORE_DIRS = new Set([
|
|
2283
2408
|
".git",
|
|
2284
2409
|
"node_modules",
|
|
@@ -2323,8 +2448,8 @@ var BINARY_EXTENSIONS = new Set([
|
|
|
2323
2448
|
|
|
2324
2449
|
class FileSearchEngine {
|
|
2325
2450
|
grep(cwd, options) {
|
|
2326
|
-
const searchRoot =
|
|
2327
|
-
if (!
|
|
2451
|
+
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2452
|
+
if (!existsSync10(searchRoot)) {
|
|
2328
2453
|
return { matches: [], totalMatches: 0, truncated: false };
|
|
2329
2454
|
}
|
|
2330
2455
|
const maxResults = options.maxResults || 50;
|
|
@@ -2373,8 +2498,8 @@ class FileSearchEngine {
|
|
|
2373
2498
|
return { matches, totalMatches, truncated };
|
|
2374
2499
|
}
|
|
2375
2500
|
findFiles(cwd, options) {
|
|
2376
|
-
const searchRoot =
|
|
2377
|
-
if (!
|
|
2501
|
+
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2502
|
+
if (!existsSync10(searchRoot))
|
|
2378
2503
|
return [];
|
|
2379
2504
|
const maxResults = options.maxResults || 100;
|
|
2380
2505
|
const gitignoreRules = this.loadGitignoreRules(searchRoot);
|
|
@@ -2413,8 +2538,8 @@ class FileSearchEngine {
|
|
|
2413
2538
|
}
|
|
2414
2539
|
loadGitignoreRules(root) {
|
|
2415
2540
|
const rules = new Set;
|
|
2416
|
-
const gitignorePath =
|
|
2417
|
-
if (
|
|
2541
|
+
const gitignorePath = join4(root, ".gitignore");
|
|
2542
|
+
if (existsSync10(gitignorePath)) {
|
|
2418
2543
|
try {
|
|
2419
2544
|
const lines = readFileSync6(gitignorePath, "utf8").split(`
|
|
2420
2545
|
`);
|
|
@@ -2431,7 +2556,7 @@ class FileSearchEngine {
|
|
|
2431
2556
|
collectFiles(dir, root, gitignoreRules, includePattern) {
|
|
2432
2557
|
const results = [];
|
|
2433
2558
|
try {
|
|
2434
|
-
const stat =
|
|
2559
|
+
const stat = statSync3(dir);
|
|
2435
2560
|
if (!stat.isDirectory()) {
|
|
2436
2561
|
if (!this.isBinary(dir)) {
|
|
2437
2562
|
results.push(dir);
|
|
@@ -2445,9 +2570,9 @@ class FileSearchEngine {
|
|
|
2445
2570
|
while (queue.length > 0) {
|
|
2446
2571
|
const currentDir = queue.shift();
|
|
2447
2572
|
try {
|
|
2448
|
-
const entries =
|
|
2573
|
+
const entries = readdirSync3(currentDir, { withFileTypes: true });
|
|
2449
2574
|
for (const entry of entries) {
|
|
2450
|
-
const fullPath =
|
|
2575
|
+
const fullPath = join4(currentDir, entry.name);
|
|
2451
2576
|
const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
|
|
2452
2577
|
if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
|
|
2453
2578
|
continue;
|
|
@@ -2844,6 +2969,111 @@ ${loaded.instructions}`
|
|
|
2844
2969
|
}
|
|
2845
2970
|
|
|
2846
2971
|
// src/memories/tool.ts
|
|
2972
|
+
function createSaveMemoryTool(store) {
|
|
2973
|
+
return {
|
|
2974
|
+
name: "save_memory",
|
|
2975
|
+
description: "Save a persistent memory note to the project's Auto-Memory bank. Categories: 'user' (role, workflow style, tooling preferences), 'feedback' (user corrections, guidelines), 'project' (external context, environments, deadlines), 'reference' (links, issue trackers, dashboards). Do NOT save facts easily discovered in code or git history.",
|
|
2976
|
+
parameters: {
|
|
2977
|
+
type: "object",
|
|
2978
|
+
properties: {
|
|
2979
|
+
category: {
|
|
2980
|
+
type: "string",
|
|
2981
|
+
description: "Category of memory: 'user', 'feedback', 'project', or 'reference'.",
|
|
2982
|
+
enum: ["user", "feedback", "project", "reference"]
|
|
2983
|
+
},
|
|
2984
|
+
name: {
|
|
2985
|
+
type: "string",
|
|
2986
|
+
description: "Short, descriptive snake_case identifier for this memory topic (e.g. 'testing_strategy', 'preferred_framework', 'staging_api')."
|
|
2987
|
+
},
|
|
2988
|
+
description: {
|
|
2989
|
+
type: "string",
|
|
2990
|
+
description: "One-line summary to display in the MEMORY.md index (e.g. 'Prefers Vitest without database mocks')."
|
|
2991
|
+
},
|
|
2992
|
+
content: {
|
|
2993
|
+
type: "string",
|
|
2994
|
+
description: "Detailed description of the fact, preference, or learned correction."
|
|
2995
|
+
}
|
|
2996
|
+
},
|
|
2997
|
+
required: ["category", "name", "content"]
|
|
2998
|
+
},
|
|
2999
|
+
async execute(args, context) {
|
|
3000
|
+
const category = args.category || "project";
|
|
3001
|
+
const name = String(args.name || `topic_${Date.now()}`);
|
|
3002
|
+
const content = String(args.content || "").trim();
|
|
3003
|
+
const description = args.description ? String(args.description).trim() : undefined;
|
|
3004
|
+
if (!content) {
|
|
3005
|
+
return { output: "Error: memory content cannot be empty", isError: true };
|
|
3006
|
+
}
|
|
3007
|
+
const entry = store.saveTopicMemory({
|
|
3008
|
+
category,
|
|
3009
|
+
name,
|
|
3010
|
+
description,
|
|
3011
|
+
content,
|
|
3012
|
+
cwd: context.cwd
|
|
3013
|
+
});
|
|
3014
|
+
return {
|
|
3015
|
+
output: `\u2713 Saved Auto-Memory topic: [${entry.category}] "${entry.name}" -> ${entry.filePath}`
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
function createReadMemoryTool(store) {
|
|
3021
|
+
return {
|
|
3022
|
+
name: "read_memory",
|
|
3023
|
+
description: "Read the full details of a specific Auto-Memory topic file recorded in the project's memory index.",
|
|
3024
|
+
parameters: {
|
|
3025
|
+
type: "object",
|
|
3026
|
+
properties: {
|
|
3027
|
+
topic: {
|
|
3028
|
+
type: "string",
|
|
3029
|
+
description: "Name or filename of the memory topic to read (e.g. 'testing_strategy' or 'feedback_testing.md')."
|
|
3030
|
+
}
|
|
3031
|
+
},
|
|
3032
|
+
required: ["topic"]
|
|
3033
|
+
},
|
|
3034
|
+
async execute(args, context) {
|
|
3035
|
+
const topic = String(args.topic || "").trim();
|
|
3036
|
+
if (!topic) {
|
|
3037
|
+
return { output: "Error: topic name is required", isError: true };
|
|
3038
|
+
}
|
|
3039
|
+
const memory = store.readTopicMemory(topic, context.cwd);
|
|
3040
|
+
if (!memory) {
|
|
3041
|
+
return {
|
|
3042
|
+
output: `No memory topic found matching '${topic}' in this project.`,
|
|
3043
|
+
isError: true
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
return {
|
|
3047
|
+
output: `# Topic: ${memory.name} (${memory.type})
|
|
3048
|
+
Modified: ${memory.modified}
|
|
3049
|
+
|
|
3050
|
+
${memory.content}`
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
function createListMemoriesTool(store) {
|
|
3056
|
+
return {
|
|
3057
|
+
name: "list_memories",
|
|
3058
|
+
description: "List all persistent memory topics and index for the current project repository.",
|
|
3059
|
+
parameters: {
|
|
3060
|
+
type: "object",
|
|
3061
|
+
properties: {}
|
|
3062
|
+
},
|
|
3063
|
+
async execute(_args, context) {
|
|
3064
|
+
const topics = store.listProjectMemories(context.cwd);
|
|
3065
|
+
if (topics.length === 0) {
|
|
3066
|
+
return { output: "No persistent Auto-Memories have been recorded for this project yet." };
|
|
3067
|
+
}
|
|
3068
|
+
const lines = topics.map((t) => `\u2022 [${t.type}] **${t.name}**: ${t.description || t.content.split(`
|
|
3069
|
+
`)[0]} (file: ${t.filePath})`);
|
|
3070
|
+
return { output: `Project Auto-Memories (${topics.length} topics):
|
|
3071
|
+
|
|
3072
|
+
${lines.join(`
|
|
3073
|
+
`)}` };
|
|
3074
|
+
}
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
2847
3077
|
function createRememberTool(store) {
|
|
2848
3078
|
return {
|
|
2849
3079
|
name: "remember",
|
|
@@ -2854,16 +3084,15 @@ function createRememberTool(store) {
|
|
|
2854
3084
|
category: {
|
|
2855
3085
|
type: "string",
|
|
2856
3086
|
description: "Category of the memory.",
|
|
2857
|
-
enum: ["preference", "guideline", "architecture", "note"]
|
|
3087
|
+
enum: ["preference", "guideline", "architecture", "note", "user", "feedback", "project", "reference"]
|
|
2858
3088
|
},
|
|
2859
3089
|
content: {
|
|
2860
3090
|
type: "string",
|
|
2861
3091
|
description: "The concise rule, preference, or fact to remember permanently."
|
|
2862
3092
|
},
|
|
2863
|
-
|
|
3093
|
+
name: {
|
|
2864
3094
|
type: "string",
|
|
2865
|
-
description: "
|
|
2866
|
-
enum: ["global", "workspace"]
|
|
3095
|
+
description: "Optional topic name."
|
|
2867
3096
|
}
|
|
2868
3097
|
},
|
|
2869
3098
|
required: ["category", "content"]
|
|
@@ -2871,22 +3100,30 @@ function createRememberTool(store) {
|
|
|
2871
3100
|
async execute(args, context) {
|
|
2872
3101
|
const category = args.category || "preference";
|
|
2873
3102
|
const content = String(args.content || "");
|
|
2874
|
-
const
|
|
3103
|
+
const name = args.name ? String(args.name) : undefined;
|
|
2875
3104
|
if (!content) {
|
|
2876
3105
|
return { output: "Error: memory content cannot be empty", isError: true };
|
|
2877
3106
|
}
|
|
2878
3107
|
const entry = store.addMemory({
|
|
2879
3108
|
category,
|
|
2880
3109
|
content,
|
|
2881
|
-
|
|
3110
|
+
name,
|
|
2882
3111
|
cwd: context.cwd
|
|
2883
3112
|
});
|
|
2884
3113
|
return {
|
|
2885
|
-
output: `Successfully saved to
|
|
3114
|
+
output: `Successfully saved to Auto-Memory bank: [${entry.category}] "${entry.name || entry.content}"`
|
|
2886
3115
|
};
|
|
2887
3116
|
}
|
|
2888
3117
|
};
|
|
2889
3118
|
}
|
|
3119
|
+
function createAutoMemoryTools(store) {
|
|
3120
|
+
return [
|
|
3121
|
+
createSaveMemoryTool(store),
|
|
3122
|
+
createReadMemoryTool(store),
|
|
3123
|
+
createListMemoriesTool(store),
|
|
3124
|
+
createRememberTool(store)
|
|
3125
|
+
];
|
|
3126
|
+
}
|
|
2890
3127
|
|
|
2891
3128
|
// src/worktree/tools.ts
|
|
2892
3129
|
function createWorktreeTools(manager) {
|
|
@@ -3021,7 +3258,9 @@ function createDefaultTools(options = {}) {
|
|
|
3021
3258
|
router2.register(createSkillTool(options.skillsLoader));
|
|
3022
3259
|
}
|
|
3023
3260
|
if (options.memoryStore) {
|
|
3024
|
-
|
|
3261
|
+
for (const tool of createAutoMemoryTools(options.memoryStore)) {
|
|
3262
|
+
router2.register(tool);
|
|
3263
|
+
}
|
|
3025
3264
|
}
|
|
3026
3265
|
if (options.worktreeManager) {
|
|
3027
3266
|
for (const tool of createWorktreeTools(options.worktreeManager)) {
|
|
@@ -3032,8 +3271,8 @@ function createDefaultTools(options = {}) {
|
|
|
3032
3271
|
}
|
|
3033
3272
|
|
|
3034
3273
|
// src/agents/roles.ts
|
|
3035
|
-
import { existsSync as
|
|
3036
|
-
import { resolve as
|
|
3274
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
|
|
3275
|
+
import { resolve as resolve10, join as join5 } from "path";
|
|
3037
3276
|
|
|
3038
3277
|
class AgentRoleRegistry {
|
|
3039
3278
|
roles = new Map;
|
|
@@ -3117,14 +3356,14 @@ class AgentRoleRegistry {
|
|
|
3117
3356
|
return cycle === 0 ? base : `${base}_${cycle + 1}`;
|
|
3118
3357
|
}
|
|
3119
3358
|
loadRolesFromDir(dirPath) {
|
|
3120
|
-
const fullPath =
|
|
3121
|
-
if (!
|
|
3359
|
+
const fullPath = resolve10(dirPath);
|
|
3360
|
+
if (!existsSync11(fullPath))
|
|
3122
3361
|
return;
|
|
3123
|
-
const entries =
|
|
3362
|
+
const entries = readdirSync4(fullPath);
|
|
3124
3363
|
for (const entry of entries) {
|
|
3125
3364
|
if (entry.endsWith(".json")) {
|
|
3126
3365
|
try {
|
|
3127
|
-
const content = readFileSync7(
|
|
3366
|
+
const content = readFileSync7(join5(fullPath, entry), "utf8");
|
|
3128
3367
|
const parsed = JSON.parse(content);
|
|
3129
3368
|
if (parsed.name && parsed.systemPrompt) {
|
|
3130
3369
|
this.registerRole(parsed);
|
|
@@ -3176,21 +3415,19 @@ function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
|
|
|
3176
3415
|
|
|
3177
3416
|
// src/agents/graph-store.ts
|
|
3178
3417
|
import { Database as Database2 } from "bun:sqlite";
|
|
3179
|
-
import { resolve as
|
|
3180
|
-
import { existsSync as
|
|
3181
|
-
import { homedir as homedir4 } from "os";
|
|
3182
|
-
|
|
3418
|
+
import { resolve as resolve11 } from "path";
|
|
3419
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
3183
3420
|
class AgentGraphStore {
|
|
3184
3421
|
db;
|
|
3185
3422
|
constructor(dbPathOrDb) {
|
|
3186
3423
|
if (dbPathOrDb instanceof Database2) {
|
|
3187
3424
|
this.db = dbPathOrDb;
|
|
3188
3425
|
} else {
|
|
3189
|
-
const dbPath = dbPathOrDb ||
|
|
3426
|
+
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
3190
3427
|
if (dbPath !== ":memory:") {
|
|
3191
|
-
const dir =
|
|
3192
|
-
if (!
|
|
3193
|
-
|
|
3428
|
+
const dir = resolve11(dbPath, "..");
|
|
3429
|
+
if (!existsSync12(dir)) {
|
|
3430
|
+
mkdirSync6(dir, { recursive: true });
|
|
3194
3431
|
}
|
|
3195
3432
|
}
|
|
3196
3433
|
this.db = new Database2(dbPath);
|
|
@@ -3313,8 +3550,8 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
3313
3550
|
});
|
|
3314
3551
|
let resolvePromise;
|
|
3315
3552
|
let rejectPromise;
|
|
3316
|
-
const taskPromise = new Promise((
|
|
3317
|
-
resolvePromise =
|
|
3553
|
+
const taskPromise = new Promise((resolve12, reject) => {
|
|
3554
|
+
resolvePromise = resolve12;
|
|
3318
3555
|
rejectPromise = reject;
|
|
3319
3556
|
});
|
|
3320
3557
|
const handle = {
|
|
@@ -3614,8 +3851,8 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
3614
3851
|
}
|
|
3615
3852
|
|
|
3616
3853
|
// src/mcp/manager.ts
|
|
3617
|
-
import { existsSync as
|
|
3618
|
-
import { resolve as
|
|
3854
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
3855
|
+
import { resolve as resolve12, dirname as dirname6, join as join6 } from "path";
|
|
3619
3856
|
|
|
3620
3857
|
// src/mcp/client.ts
|
|
3621
3858
|
class McpClient {
|
|
@@ -4009,13 +4246,13 @@ class StdioTransport {
|
|
|
4009
4246
|
if (this.isClosed || !this.proc || !this.proc.stdin) {
|
|
4010
4247
|
throw new GroupyError("MCP Stdio transport is closed");
|
|
4011
4248
|
}
|
|
4012
|
-
return new Promise((
|
|
4249
|
+
return new Promise((resolve12, reject) => {
|
|
4013
4250
|
const timeoutMs = 30000;
|
|
4014
4251
|
const timer = setTimeout(() => {
|
|
4015
4252
|
this.pendingRequests.delete(request.id);
|
|
4016
4253
|
reject(new GroupyError(`MCP request timed out after ${timeoutMs}ms (method: ${request.method})`));
|
|
4017
4254
|
}, timeoutMs);
|
|
4018
|
-
this.pendingRequests.set(request.id, { resolve:
|
|
4255
|
+
this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
|
|
4019
4256
|
try {
|
|
4020
4257
|
const payload = JSON.stringify(request) + `
|
|
4021
4258
|
`;
|
|
@@ -4148,12 +4385,12 @@ class SseTransport {
|
|
|
4148
4385
|
if (!this.messageUrl) {
|
|
4149
4386
|
this.messageUrl = this.endpointUrl;
|
|
4150
4387
|
}
|
|
4151
|
-
return new Promise((
|
|
4388
|
+
return new Promise((resolve12, reject) => {
|
|
4152
4389
|
const timer = setTimeout(() => {
|
|
4153
4390
|
this.pendingRequests.delete(request.id);
|
|
4154
4391
|
reject(new GroupyError(`MCP SSE request timed out (method: ${request.method})`));
|
|
4155
4392
|
}, 30000);
|
|
4156
|
-
this.pendingRequests.set(request.id, { resolve:
|
|
4393
|
+
this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
|
|
4157
4394
|
fetch(this.messageUrl, {
|
|
4158
4395
|
method: "POST",
|
|
4159
4396
|
headers: {
|
|
@@ -4230,8 +4467,8 @@ class McpManager {
|
|
|
4230
4467
|
}
|
|
4231
4468
|
}
|
|
4232
4469
|
async loadConfigFile(filePath) {
|
|
4233
|
-
const fullPath =
|
|
4234
|
-
if (!
|
|
4470
|
+
const fullPath = resolve12(filePath);
|
|
4471
|
+
if (!existsSync13(fullPath))
|
|
4235
4472
|
return;
|
|
4236
4473
|
this.loadedConfigFiles.add(fullPath);
|
|
4237
4474
|
try {
|
|
@@ -4484,13 +4721,13 @@ class McpManager {
|
|
|
4484
4721
|
`);
|
|
4485
4722
|
}
|
|
4486
4723
|
saveServerToConfigFile(filePath, name, config) {
|
|
4487
|
-
const fullPath =
|
|
4488
|
-
const dir =
|
|
4489
|
-
if (!
|
|
4490
|
-
|
|
4724
|
+
const fullPath = resolve12(filePath);
|
|
4725
|
+
const dir = dirname6(fullPath);
|
|
4726
|
+
if (!existsSync13(dir)) {
|
|
4727
|
+
mkdirSync7(dir, { recursive: true });
|
|
4491
4728
|
}
|
|
4492
4729
|
let existing = { mcpServers: {} };
|
|
4493
|
-
if (
|
|
4730
|
+
if (existsSync13(fullPath)) {
|
|
4494
4731
|
try {
|
|
4495
4732
|
const content = readFileSync8(fullPath, "utf8");
|
|
4496
4733
|
existing = JSON.parse(content);
|
|
@@ -4504,8 +4741,8 @@ class McpManager {
|
|
|
4504
4741
|
this.loadedConfigFiles.add(fullPath);
|
|
4505
4742
|
}
|
|
4506
4743
|
removeServerFromConfigFile(filePath, name) {
|
|
4507
|
-
const fullPath =
|
|
4508
|
-
if (!
|
|
4744
|
+
const fullPath = resolve12(filePath);
|
|
4745
|
+
if (!existsSync13(fullPath))
|
|
4509
4746
|
return false;
|
|
4510
4747
|
try {
|
|
4511
4748
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -4535,11 +4772,11 @@ class McpManager {
|
|
|
4535
4772
|
}
|
|
4536
4773
|
}
|
|
4537
4774
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
4538
|
-
const workspaceConfig =
|
|
4539
|
-
if (
|
|
4775
|
+
const workspaceConfig = join6(cwd, ".mcp.json");
|
|
4776
|
+
if (existsSync13(workspaceConfig))
|
|
4540
4777
|
return workspaceConfig;
|
|
4541
|
-
const altConfig =
|
|
4542
|
-
if (
|
|
4778
|
+
const altConfig = join6(cwd, "mcp_config.json");
|
|
4779
|
+
if (existsSync13(altConfig))
|
|
4543
4780
|
return altConfig;
|
|
4544
4781
|
return workspaceConfig;
|
|
4545
4782
|
}
|
|
@@ -4560,17 +4797,16 @@ class McpManager {
|
|
|
4560
4797
|
|
|
4561
4798
|
// src/storage/sqlite-store.ts
|
|
4562
4799
|
import { Database as Database3 } from "bun:sqlite";
|
|
4563
|
-
import { existsSync as
|
|
4564
|
-
import { dirname as
|
|
4565
|
-
import { homedir as homedir5 } from "os";
|
|
4800
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
4801
|
+
import { dirname as dirname7 } from "path";
|
|
4566
4802
|
class SqliteThreadStore {
|
|
4567
4803
|
db;
|
|
4568
4804
|
constructor(dbPath) {
|
|
4569
4805
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
4570
4806
|
if (effectivePath !== ":memory:") {
|
|
4571
|
-
const dir =
|
|
4572
|
-
if (!
|
|
4573
|
-
|
|
4807
|
+
const dir = dirname7(effectivePath);
|
|
4808
|
+
if (!existsSync14(dir)) {
|
|
4809
|
+
mkdirSync8(dir, { recursive: true });
|
|
4574
4810
|
}
|
|
4575
4811
|
}
|
|
4576
4812
|
this.db = new Database3(effectivePath);
|
|
@@ -4579,7 +4815,7 @@ class SqliteThreadStore {
|
|
|
4579
4815
|
this.initSchema();
|
|
4580
4816
|
}
|
|
4581
4817
|
getDefaultDbPath() {
|
|
4582
|
-
return
|
|
4818
|
+
return getThreadsDbPath();
|
|
4583
4819
|
}
|
|
4584
4820
|
initSchema() {
|
|
4585
4821
|
this.db.exec(`
|
|
@@ -4804,9 +5040,9 @@ class SessionPersistenceManager {
|
|
|
4804
5040
|
}
|
|
4805
5041
|
|
|
4806
5042
|
// src/skills/loader.ts
|
|
4807
|
-
import { existsSync as
|
|
4808
|
-
import { resolve as resolve13, join as
|
|
4809
|
-
import { homedir as
|
|
5043
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
5044
|
+
import { resolve as resolve13, join as join7 } from "path";
|
|
5045
|
+
import { homedir as homedir3 } from "os";
|
|
4810
5046
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
4811
5047
|
|
|
4812
5048
|
class SkillsLoader {
|
|
@@ -4876,16 +5112,16 @@ class SkillsLoader {
|
|
|
4876
5112
|
resolve13(cwd, "skills")
|
|
4877
5113
|
];
|
|
4878
5114
|
for (const cand of candidates) {
|
|
4879
|
-
if (
|
|
5115
|
+
if (existsSync15(cand) && !roots.includes(cand)) {
|
|
4880
5116
|
roots.push(cand);
|
|
4881
5117
|
}
|
|
4882
5118
|
}
|
|
4883
5119
|
}
|
|
4884
5120
|
if (this.includeGlobal) {
|
|
4885
|
-
roots.push(
|
|
5121
|
+
roots.push(getGlobalSkillsDir(), resolve13(homedir3(), ".gemini", "config", "skills"));
|
|
4886
5122
|
}
|
|
4887
5123
|
roots.push(...this.customRoots.map((r) => resolve13(r)));
|
|
4888
|
-
return roots.filter((r) =>
|
|
5124
|
+
return roots.filter((r) => existsSync15(r));
|
|
4889
5125
|
}
|
|
4890
5126
|
discoverSkills(cwd, options) {
|
|
4891
5127
|
return this.listSkills(cwd, options);
|
|
@@ -4901,12 +5137,12 @@ class SkillsLoader {
|
|
|
4901
5137
|
const discovered = new Map;
|
|
4902
5138
|
for (const root of roots) {
|
|
4903
5139
|
try {
|
|
4904
|
-
const entries =
|
|
5140
|
+
const entries = readdirSync5(root, { withFileTypes: true });
|
|
4905
5141
|
for (const entry of entries) {
|
|
4906
5142
|
if (entry.isDirectory()) {
|
|
4907
|
-
const skillDir =
|
|
4908
|
-
const skillFilePath =
|
|
4909
|
-
if (
|
|
5143
|
+
const skillDir = join7(root, entry.name);
|
|
5144
|
+
const skillFilePath = join7(skillDir, "SKILL.md");
|
|
5145
|
+
if (existsSync15(skillFilePath)) {
|
|
4910
5146
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
4911
5147
|
if (meta && !discovered.has(meta.name)) {
|
|
4912
5148
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -5029,149 +5265,288 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
5029
5265
|
}
|
|
5030
5266
|
|
|
5031
5267
|
// src/memories/store.ts
|
|
5032
|
-
import { existsSync as
|
|
5033
|
-
import { resolve as resolve14, dirname as
|
|
5034
|
-
import {
|
|
5035
|
-
|
|
5268
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync9, readdirSync as readdirSync6 } from "fs";
|
|
5269
|
+
import { resolve as resolve14, join as join8, basename, dirname as dirname8 } from "path";
|
|
5270
|
+
import { createHash } from "crypto";
|
|
5036
5271
|
class MemoryStore {
|
|
5037
5272
|
globalPath;
|
|
5038
5273
|
customWorkspacePath;
|
|
5039
5274
|
constructor(options = {}) {
|
|
5040
|
-
this.globalPath = options.globalPath ||
|
|
5275
|
+
this.globalPath = options.globalPath || getGlobalMemoriesPath();
|
|
5041
5276
|
this.customWorkspacePath = options.workspacePath;
|
|
5042
5277
|
}
|
|
5043
|
-
|
|
5044
|
-
|
|
5278
|
+
findProjectRoot(cwd) {
|
|
5279
|
+
let current = resolve14(cwd);
|
|
5280
|
+
while (true) {
|
|
5281
|
+
if (existsSync16(join8(current, ".git"))) {
|
|
5282
|
+
return current;
|
|
5283
|
+
}
|
|
5284
|
+
const parent = dirname8(current);
|
|
5285
|
+
if (parent === current) {
|
|
5286
|
+
return resolve14(cwd);
|
|
5287
|
+
}
|
|
5288
|
+
current = parent;
|
|
5289
|
+
}
|
|
5045
5290
|
}
|
|
5046
|
-
|
|
5047
|
-
const
|
|
5048
|
-
const
|
|
5049
|
-
const
|
|
5050
|
-
|
|
5051
|
-
mkdirSync8(dir, { recursive: true });
|
|
5052
|
-
}
|
|
5053
|
-
const existingEntries = this.readMemoryFile(targetFile, scope);
|
|
5054
|
-
const normalized = params.content.trim();
|
|
5055
|
-
const duplicate = existingEntries.find((e) => e.category === params.category && e.content.toLowerCase() === normalized.toLowerCase());
|
|
5056
|
-
if (duplicate) {
|
|
5057
|
-
return duplicate;
|
|
5058
|
-
}
|
|
5059
|
-
const newEntry = {
|
|
5060
|
-
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
5061
|
-
category: params.category,
|
|
5062
|
-
content: normalized,
|
|
5063
|
-
scope,
|
|
5064
|
-
createdAt: Date.now()
|
|
5065
|
-
};
|
|
5066
|
-
existingEntries.push(newEntry);
|
|
5067
|
-
this.writeMemoryFile(targetFile, existingEntries);
|
|
5068
|
-
return newEntry;
|
|
5291
|
+
getProjectSlug(cwd) {
|
|
5292
|
+
const root = this.findProjectRoot(cwd);
|
|
5293
|
+
const folderName = basename(root).toLowerCase().replace(/[^a-z0-9_-]/g, "-") || "project";
|
|
5294
|
+
const hash = createHash("sha256").update(resolve14(root)).digest("hex").slice(0, 6);
|
|
5295
|
+
return `${folderName}-${hash}`;
|
|
5069
5296
|
}
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5297
|
+
getProjectMemoryDir(cwd) {
|
|
5298
|
+
if (this.customWorkspacePath) {
|
|
5299
|
+
const dir2 = resolve14(this.customWorkspacePath);
|
|
5300
|
+
if (!existsSync16(dir2)) {
|
|
5301
|
+
try {
|
|
5302
|
+
mkdirSync9(dir2, { recursive: true });
|
|
5303
|
+
} catch {}
|
|
5304
|
+
}
|
|
5305
|
+
return dir2;
|
|
5306
|
+
}
|
|
5307
|
+
const slug = this.getProjectSlug(cwd);
|
|
5308
|
+
const dir = join8(getProjectsDir(), slug, "memory");
|
|
5309
|
+
if (!existsSync16(dir)) {
|
|
5310
|
+
try {
|
|
5311
|
+
mkdirSync9(dir, { recursive: true });
|
|
5312
|
+
} catch {}
|
|
5313
|
+
}
|
|
5314
|
+
return dir;
|
|
5315
|
+
}
|
|
5316
|
+
getMemoryIndexPath(cwd) {
|
|
5317
|
+
return join8(this.getProjectMemoryDir(cwd), "MEMORY.md");
|
|
5318
|
+
}
|
|
5319
|
+
normalizeCategory(raw) {
|
|
5320
|
+
const cat = raw.toLowerCase().trim();
|
|
5321
|
+
if (cat === "user" || cat === "preference")
|
|
5322
|
+
return "user";
|
|
5323
|
+
if (cat === "feedback" || cat === "guideline")
|
|
5324
|
+
return "feedback";
|
|
5325
|
+
if (cat === "project" || cat === "architecture")
|
|
5326
|
+
return "project";
|
|
5327
|
+
if (cat === "reference" || cat === "note")
|
|
5328
|
+
return "reference";
|
|
5329
|
+
return "project";
|
|
5330
|
+
}
|
|
5331
|
+
saveTopicMemory(params) {
|
|
5332
|
+
const type = this.normalizeCategory(params.category);
|
|
5333
|
+
const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
|
|
5334
|
+
const memoryDir = this.getProjectMemoryDir(params.cwd);
|
|
5335
|
+
const fileName = `${type}_${sanitizedName}.md`;
|
|
5336
|
+
const filePath = join8(memoryDir, fileName);
|
|
5337
|
+
const nowIso = new Date().toISOString();
|
|
5338
|
+
const cleanContent = params.content.trim();
|
|
5339
|
+
const desc = (params.description || cleanContent.split(`
|
|
5340
|
+
`)[0] || sanitizedName).replace(/[\r\n]+/g, " ");
|
|
5341
|
+
const frontmatter = [
|
|
5342
|
+
"---",
|
|
5343
|
+
`type: ${type}`,
|
|
5344
|
+
`name: ${sanitizedName}`,
|
|
5345
|
+
`description: ${desc}`,
|
|
5346
|
+
`modified: ${nowIso}`,
|
|
5347
|
+
"---",
|
|
5348
|
+
"",
|
|
5349
|
+
`# ${sanitizedName.replace(/_/g, " ").toUpperCase()}`,
|
|
5350
|
+
"",
|
|
5351
|
+
cleanContent,
|
|
5352
|
+
""
|
|
5353
|
+
].join(`
|
|
5354
|
+
`);
|
|
5355
|
+
writeFileSync5(filePath, frontmatter, "utf8");
|
|
5356
|
+
this.syncMemoryIndex(params.cwd);
|
|
5357
|
+
return {
|
|
5358
|
+
id: `mem_${sanitizedName}`,
|
|
5359
|
+
category: type,
|
|
5360
|
+
name: sanitizedName,
|
|
5361
|
+
description: desc,
|
|
5362
|
+
content: cleanContent,
|
|
5363
|
+
scope: "project",
|
|
5364
|
+
createdAt: Date.now(),
|
|
5365
|
+
modifiedAt: Date.now(),
|
|
5366
|
+
filePath
|
|
5367
|
+
};
|
|
5075
5368
|
}
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5369
|
+
readTopicMemory(topicNameOrFile, cwd) {
|
|
5370
|
+
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5371
|
+
let targetPath = join8(memoryDir, topicNameOrFile);
|
|
5372
|
+
if (!existsSync16(targetPath)) {
|
|
5373
|
+
if (!topicNameOrFile.endsWith(".md")) {
|
|
5374
|
+
targetPath = join8(memoryDir, `${topicNameOrFile}.md`);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
if (!existsSync16(targetPath)) {
|
|
5378
|
+
const files = readdirSync6(memoryDir);
|
|
5379
|
+
const match = files.find((f) => f.includes(topicNameOrFile));
|
|
5380
|
+
if (match) {
|
|
5381
|
+
targetPath = join8(memoryDir, match);
|
|
5382
|
+
} else {
|
|
5383
|
+
return null;
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5079
5386
|
try {
|
|
5080
|
-
const
|
|
5081
|
-
|
|
5387
|
+
const raw = readFileSync10(targetPath, "utf8");
|
|
5388
|
+
return this.parseTopicFile(raw, targetPath);
|
|
5389
|
+
} catch {
|
|
5390
|
+
return null;
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
parseTopicFile(raw, filePath) {
|
|
5394
|
+
const lines = raw.split(`
|
|
5082
5395
|
`);
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
id: `mem_${entries.length + 1}`,
|
|
5100
|
-
category: currentCategory,
|
|
5101
|
-
content: itemText,
|
|
5102
|
-
scope,
|
|
5103
|
-
createdAt: Date.now()
|
|
5104
|
-
});
|
|
5105
|
-
}
|
|
5396
|
+
let inFm = false;
|
|
5397
|
+
let type = "project";
|
|
5398
|
+
let name = basename(filePath, ".md");
|
|
5399
|
+
let description;
|
|
5400
|
+
let modified = new Date().toISOString();
|
|
5401
|
+
const bodyLines = [];
|
|
5402
|
+
for (let i = 0;i < lines.length; i++) {
|
|
5403
|
+
const line = lines[i];
|
|
5404
|
+
if (i === 0 && line.trim() === "---") {
|
|
5405
|
+
inFm = true;
|
|
5406
|
+
continue;
|
|
5407
|
+
}
|
|
5408
|
+
if (inFm) {
|
|
5409
|
+
if (line.trim() === "---") {
|
|
5410
|
+
inFm = false;
|
|
5411
|
+
continue;
|
|
5106
5412
|
}
|
|
5413
|
+
const colonIdx = line.indexOf(":");
|
|
5414
|
+
if (colonIdx !== -1) {
|
|
5415
|
+
const key = line.slice(0, colonIdx).trim();
|
|
5416
|
+
const val = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
5417
|
+
if (key === "type")
|
|
5418
|
+
type = this.normalizeCategory(val);
|
|
5419
|
+
else if (key === "name")
|
|
5420
|
+
name = val;
|
|
5421
|
+
else if (key === "description")
|
|
5422
|
+
description = val;
|
|
5423
|
+
else if (key === "modified")
|
|
5424
|
+
modified = val;
|
|
5425
|
+
}
|
|
5426
|
+
} else {
|
|
5427
|
+
bodyLines.push(line);
|
|
5107
5428
|
}
|
|
5108
|
-
return entries;
|
|
5109
|
-
} catch {
|
|
5110
|
-
return [];
|
|
5111
5429
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5430
|
+
return {
|
|
5431
|
+
type,
|
|
5432
|
+
name,
|
|
5433
|
+
description,
|
|
5434
|
+
modified,
|
|
5435
|
+
content: bodyLines.join(`
|
|
5436
|
+
`).trim(),
|
|
5437
|
+
filePath
|
|
5119
5438
|
};
|
|
5120
|
-
|
|
5121
|
-
|
|
5439
|
+
}
|
|
5440
|
+
syncMemoryIndex(cwd) {
|
|
5441
|
+
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5442
|
+
const indexPath = join8(memoryDir, "MEMORY.md");
|
|
5443
|
+
const files = existsSync16(memoryDir) ? readdirSync6(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
5444
|
+
const items = [];
|
|
5445
|
+
for (const f of files) {
|
|
5446
|
+
try {
|
|
5447
|
+
const full = join8(memoryDir, f);
|
|
5448
|
+
const parsed = this.parseTopicFile(readFileSync10(full, "utf8"), full);
|
|
5449
|
+
items.push({
|
|
5450
|
+
type: parsed.type,
|
|
5451
|
+
name: parsed.name,
|
|
5452
|
+
desc: parsed.description || parsed.content.split(`
|
|
5453
|
+
`)[0] || parsed.name,
|
|
5454
|
+
file: f
|
|
5455
|
+
});
|
|
5456
|
+
} catch {}
|
|
5122
5457
|
}
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
`;
|
|
5458
|
+
const indexLines = [
|
|
5459
|
+
"# Project Auto-Memory Index",
|
|
5460
|
+
"",
|
|
5461
|
+
"This index is loaded at session startup. Detailed topics can be retrieved via read_memory tool.",
|
|
5462
|
+
""
|
|
5463
|
+
];
|
|
5464
|
+
for (const item of items) {
|
|
5465
|
+
indexLines.push(`- [${item.type}] **${item.name}**: ${item.desc} (topic: ${item.file})`);
|
|
5132
5466
|
}
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5467
|
+
const boundedLines = indexLines.slice(0, 200);
|
|
5468
|
+
writeFileSync5(indexPath, boundedLines.join(`
|
|
5469
|
+
`) + `
|
|
5470
|
+
`, "utf8");
|
|
5471
|
+
}
|
|
5472
|
+
loadMemoryIndex(cwd) {
|
|
5473
|
+
const indexPath = this.getMemoryIndexPath(cwd);
|
|
5474
|
+
if (!existsSync16(indexPath))
|
|
5475
|
+
return "";
|
|
5476
|
+
try {
|
|
5477
|
+
const raw = readFileSync10(indexPath, "utf8");
|
|
5478
|
+
const byteLimit = 25 * 1024;
|
|
5479
|
+
const sliced = raw.length > byteLimit ? raw.slice(0, byteLimit) : raw;
|
|
5480
|
+
const lines = sliced.split(`
|
|
5481
|
+
`).slice(0, 200);
|
|
5482
|
+
return lines.join(`
|
|
5483
|
+
`).trim();
|
|
5484
|
+
} catch {
|
|
5485
|
+
return "";
|
|
5139
5486
|
}
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5487
|
+
}
|
|
5488
|
+
listProjectMemories(cwd) {
|
|
5489
|
+
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5490
|
+
if (!existsSync16(memoryDir))
|
|
5491
|
+
return [];
|
|
5492
|
+
const files = readdirSync6(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
5493
|
+
const list = [];
|
|
5494
|
+
for (const f of files) {
|
|
5495
|
+
try {
|
|
5496
|
+
const full = join8(memoryDir, f);
|
|
5497
|
+
list.push(this.parseTopicFile(readFileSync10(full, "utf8"), full));
|
|
5498
|
+
} catch {}
|
|
5146
5499
|
}
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
`;
|
|
5500
|
+
return list;
|
|
5501
|
+
}
|
|
5502
|
+
addMemory(params) {
|
|
5503
|
+
const cwd = params.cwd || process.cwd();
|
|
5504
|
+
const type = this.normalizeCategory(params.category);
|
|
5505
|
+
const name = params.name || `${type}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
5506
|
+
const entry = this.saveTopicMemory({
|
|
5507
|
+
category: type,
|
|
5508
|
+
name,
|
|
5509
|
+
content: params.content,
|
|
5510
|
+
cwd
|
|
5511
|
+
});
|
|
5512
|
+
if (params.scope) {
|
|
5513
|
+
entry.scope = params.scope;
|
|
5153
5514
|
}
|
|
5154
|
-
|
|
5155
|
-
|
|
5515
|
+
return entry;
|
|
5516
|
+
}
|
|
5517
|
+
getAllMemories(cwd) {
|
|
5518
|
+
const projectTopics = this.listProjectMemories(cwd).map((t) => ({
|
|
5519
|
+
id: `mem_${t.name}`,
|
|
5520
|
+
category: t.type,
|
|
5521
|
+
name: t.name,
|
|
5522
|
+
description: t.description,
|
|
5523
|
+
content: t.content,
|
|
5524
|
+
scope: t.type === "user" ? "global" : "workspace",
|
|
5525
|
+
createdAt: new Date(t.modified).getTime() || Date.now(),
|
|
5526
|
+
filePath: t.filePath
|
|
5527
|
+
}));
|
|
5528
|
+
return projectTopics;
|
|
5156
5529
|
}
|
|
5157
5530
|
formatMemoriesPrompt(cwd) {
|
|
5158
|
-
const
|
|
5159
|
-
if (
|
|
5531
|
+
const indexContent = this.loadMemoryIndex(cwd);
|
|
5532
|
+
if (!indexContent)
|
|
5160
5533
|
return "";
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
##
|
|
5164
|
-
<
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5534
|
+
return [
|
|
5535
|
+
"",
|
|
5536
|
+
"## Project Auto-Memory (Persistent Learnings)",
|
|
5537
|
+
"<auto_memory>",
|
|
5538
|
+
indexContent,
|
|
5539
|
+
"</auto_memory>",
|
|
5540
|
+
"Apply these persistent project learnings, user preferences, and feedback across all tasks.",
|
|
5541
|
+
"If more context is needed for a specific topic, retrieve it using the `read_memory` tool."
|
|
5542
|
+
].join(`
|
|
5543
|
+
`);
|
|
5169
5544
|
}
|
|
5170
5545
|
}
|
|
5171
5546
|
|
|
5172
5547
|
// src/worktree/manager.ts
|
|
5173
|
-
import { resolve as resolve16, join as
|
|
5174
|
-
import { existsSync as
|
|
5548
|
+
import { resolve as resolve16, join as join9 } from "path";
|
|
5549
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6, readFileSync as readFileSync11 } from "fs";
|
|
5175
5550
|
|
|
5176
5551
|
// src/worktree/git.ts
|
|
5177
5552
|
import { resolve as resolve15 } from "path";
|
|
@@ -5321,15 +5696,15 @@ class WorktreeManager {
|
|
|
5321
5696
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
5322
5697
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve16(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve16(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
5323
5698
|
const worktreeParent = resolve16(targetDir, "..");
|
|
5324
|
-
if (!
|
|
5325
|
-
|
|
5699
|
+
if (!existsSync17(worktreeParent)) {
|
|
5700
|
+
mkdirSync10(worktreeParent, { recursive: true });
|
|
5326
5701
|
}
|
|
5327
5702
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
5328
5703
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
5329
5704
|
if (!result.success) {
|
|
5330
5705
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
5331
5706
|
}
|
|
5332
|
-
const metaPath =
|
|
5707
|
+
const metaPath = join9(targetDir, "groupy-thread.json");
|
|
5333
5708
|
try {
|
|
5334
5709
|
writeFileSync6(metaPath, JSON.stringify({
|
|
5335
5710
|
version: 1,
|
|
@@ -5355,8 +5730,8 @@ class WorktreeManager {
|
|
|
5355
5730
|
return [];
|
|
5356
5731
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
5357
5732
|
return worktrees.map((wt) => {
|
|
5358
|
-
const metaPath =
|
|
5359
|
-
if (
|
|
5733
|
+
const metaPath = join9(wt.path, "groupy-thread.json");
|
|
5734
|
+
if (existsSync17(metaPath)) {
|
|
5360
5735
|
try {
|
|
5361
5736
|
const raw = JSON.parse(readFileSync11(metaPath, "utf8"));
|
|
5362
5737
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -5434,7 +5809,7 @@ class WorktreeManager {
|
|
|
5434
5809
|
}
|
|
5435
5810
|
}
|
|
5436
5811
|
// src/auth/oauth.ts
|
|
5437
|
-
import { randomBytes, createHash } from "crypto";
|
|
5812
|
+
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
5438
5813
|
import { exec } from "child_process";
|
|
5439
5814
|
class AuthClient {
|
|
5440
5815
|
store;
|
|
@@ -5587,7 +5962,7 @@ class AuthClient {
|
|
|
5587
5962
|
return randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9]/g, "").slice(0, 64);
|
|
5588
5963
|
}
|
|
5589
5964
|
generateCodeChallenge(verifier) {
|
|
5590
|
-
return
|
|
5965
|
+
return createHash2("sha256").update(verifier).digest("base64url");
|
|
5591
5966
|
}
|
|
5592
5967
|
}
|
|
5593
5968
|
// src/cli/ui/colors.ts
|
|
@@ -6054,7 +6429,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
|
|
|
6054
6429
|
// package.json
|
|
6055
6430
|
var package_default = {
|
|
6056
6431
|
name: "@pikaa-ai/pikaa",
|
|
6057
|
-
version: "0.3.
|
|
6432
|
+
version: "0.3.15",
|
|
6058
6433
|
description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
|
|
6059
6434
|
main: "./dist/index.js",
|
|
6060
6435
|
module: "./dist/index.js",
|
|
@@ -6132,7 +6507,7 @@ function getCliVersion(options = {}) {
|
|
|
6132
6507
|
}
|
|
6133
6508
|
|
|
6134
6509
|
// src/cli/ui/animation/banner-animation.ts
|
|
6135
|
-
import { homedir as
|
|
6510
|
+
import { homedir as homedir4 } from "os";
|
|
6136
6511
|
import { execSync } from "child_process";
|
|
6137
6512
|
var ROSE = "\x1B[38;2;205;105;74m";
|
|
6138
6513
|
var ROSE_DIM = "\x1B[38;2;120;60;45m";
|
|
@@ -6142,7 +6517,7 @@ var BOLD = "\x1B[1m";
|
|
|
6142
6517
|
var ITALIC = "\x1B[3m";
|
|
6143
6518
|
var RESET = "\x1B[0m";
|
|
6144
6519
|
function shortenPath(cwd) {
|
|
6145
|
-
const home =
|
|
6520
|
+
const home = homedir4();
|
|
6146
6521
|
if (cwd.startsWith(home)) {
|
|
6147
6522
|
return `~${cwd.slice(home.length).replace(/\\/g, "/")}`;
|
|
6148
6523
|
}
|
|
@@ -7520,8 +7895,8 @@ async function promptInteractiveList(config) {
|
|
|
7520
7895
|
}
|
|
7521
7896
|
|
|
7522
7897
|
// src/security/scanner.ts
|
|
7523
|
-
import { existsSync as
|
|
7524
|
-
import { join as
|
|
7898
|
+
import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
|
|
7899
|
+
import { join as join10, relative as relative2, resolve as resolve17 } from "path";
|
|
7525
7900
|
var SECURITY_RULES = [
|
|
7526
7901
|
{
|
|
7527
7902
|
id: "SEC-001",
|
|
@@ -7659,21 +8034,21 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
7659
8034
|
const findings = [];
|
|
7660
8035
|
let scannedCount = 0;
|
|
7661
8036
|
function walk(current) {
|
|
7662
|
-
if (scannedCount >= maxFiles || !
|
|
8037
|
+
if (scannedCount >= maxFiles || !existsSync18(current))
|
|
7663
8038
|
return;
|
|
7664
8039
|
let entries;
|
|
7665
8040
|
try {
|
|
7666
|
-
entries =
|
|
8041
|
+
entries = readdirSync7(current);
|
|
7667
8042
|
} catch {
|
|
7668
8043
|
return;
|
|
7669
8044
|
}
|
|
7670
8045
|
for (const entry of entries) {
|
|
7671
8046
|
if (scannedCount >= maxFiles)
|
|
7672
8047
|
break;
|
|
7673
|
-
const fullPath =
|
|
8048
|
+
const fullPath = join10(current, entry);
|
|
7674
8049
|
let stat;
|
|
7675
8050
|
try {
|
|
7676
|
-
stat =
|
|
8051
|
+
stat = statSync4(fullPath);
|
|
7677
8052
|
} catch {
|
|
7678
8053
|
continue;
|
|
7679
8054
|
}
|
|
@@ -7778,8 +8153,8 @@ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
|
|
|
7778
8153
|
var SQLITE_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
|
|
7779
8154
|
|
|
7780
8155
|
// src/init/project-analyzer.ts
|
|
7781
|
-
import { existsSync as
|
|
7782
|
-
import { join as
|
|
8156
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13, readdirSync as readdirSync8 } from "fs";
|
|
8157
|
+
import { join as join11, basename as basename3 } from "path";
|
|
7783
8158
|
|
|
7784
8159
|
class ProjectAnalyzer {
|
|
7785
8160
|
cwd;
|
|
@@ -7797,8 +8172,8 @@ class ProjectAnalyzer {
|
|
|
7797
8172
|
const architectureNotes = [];
|
|
7798
8173
|
const codeConventions = [];
|
|
7799
8174
|
let description = readmeInfo.description;
|
|
7800
|
-
const pkgPath =
|
|
7801
|
-
if (
|
|
8175
|
+
const pkgPath = join11(this.cwd, "package.json");
|
|
8176
|
+
if (existsSync19(pkgPath)) {
|
|
7802
8177
|
try {
|
|
7803
8178
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
7804
8179
|
if (!description && pkg.description)
|
|
@@ -7871,8 +8246,8 @@ class ProjectAnalyzer {
|
|
|
7871
8246
|
}
|
|
7872
8247
|
} catch {}
|
|
7873
8248
|
}
|
|
7874
|
-
const tsconfigPath =
|
|
7875
|
-
if (
|
|
8249
|
+
const tsconfigPath = join11(this.cwd, "tsconfig.json");
|
|
8250
|
+
if (existsSync19(tsconfigPath)) {
|
|
7876
8251
|
try {
|
|
7877
8252
|
const tsconfig = JSON.parse(readFileSync13(tsconfigPath, "utf8"));
|
|
7878
8253
|
if (tsconfig.compilerOptions?.strict) {
|
|
@@ -7883,8 +8258,8 @@ class ProjectAnalyzer {
|
|
|
7883
8258
|
}
|
|
7884
8259
|
} catch {}
|
|
7885
8260
|
}
|
|
7886
|
-
const cargoPath =
|
|
7887
|
-
if (
|
|
8261
|
+
const cargoPath = join11(this.cwd, "Cargo.toml");
|
|
8262
|
+
if (existsSync19(cargoPath)) {
|
|
7888
8263
|
try {
|
|
7889
8264
|
commands.dev = commands.dev || "cargo run";
|
|
7890
8265
|
commands.build = commands.build || "cargo build";
|
|
@@ -7893,8 +8268,8 @@ class ProjectAnalyzer {
|
|
|
7893
8268
|
frameworks.push("Rust Cargo");
|
|
7894
8269
|
} catch {}
|
|
7895
8270
|
}
|
|
7896
|
-
const goModPath =
|
|
7897
|
-
if (
|
|
8271
|
+
const goModPath = join11(this.cwd, "go.mod");
|
|
8272
|
+
if (existsSync19(goModPath)) {
|
|
7898
8273
|
try {
|
|
7899
8274
|
commands.dev = commands.dev || "go run .";
|
|
7900
8275
|
commands.build = commands.build || "go build ./...";
|
|
@@ -7903,37 +8278,37 @@ class ProjectAnalyzer {
|
|
|
7903
8278
|
frameworks.push("Go Modules");
|
|
7904
8279
|
} catch {}
|
|
7905
8280
|
}
|
|
7906
|
-
const pyprojectPath =
|
|
7907
|
-
const requirementsPath =
|
|
7908
|
-
if (
|
|
8281
|
+
const pyprojectPath = join11(this.cwd, "pyproject.toml");
|
|
8282
|
+
const requirementsPath = join11(this.cwd, "requirements.txt");
|
|
8283
|
+
if (existsSync19(pyprojectPath) || existsSync19(requirementsPath)) {
|
|
7909
8284
|
commands.test = commands.test || "pytest";
|
|
7910
8285
|
commands.lint = commands.lint || "ruff check .";
|
|
7911
|
-
if (
|
|
8286
|
+
if (existsSync19(join11(this.cwd, "uv.lock"))) {
|
|
7912
8287
|
frameworks.push("uv");
|
|
7913
8288
|
commands.test = "uv run pytest";
|
|
7914
|
-
} else if (
|
|
8289
|
+
} else if (existsSync19(join11(this.cwd, "poetry.lock"))) {
|
|
7915
8290
|
frameworks.push("Poetry");
|
|
7916
8291
|
commands.test = "poetry run pytest";
|
|
7917
8292
|
}
|
|
7918
8293
|
}
|
|
7919
|
-
if (
|
|
8294
|
+
if (existsSync19(join11(this.cwd, "Dockerfile"))) {
|
|
7920
8295
|
infrastructure.push("Docker");
|
|
7921
8296
|
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
7922
8297
|
commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
|
|
7923
8298
|
}
|
|
7924
|
-
if (
|
|
8299
|
+
if (existsSync19(join11(this.cwd, "nginx.conf"))) {
|
|
7925
8300
|
infrastructure.push("Nginx");
|
|
7926
8301
|
}
|
|
7927
|
-
if (
|
|
8302
|
+
if (existsSync19(join11(this.cwd, "src/api.ts")) || existsSync19(join11(this.cwd, "src/api"))) {
|
|
7928
8303
|
architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
|
|
7929
8304
|
}
|
|
7930
|
-
if (
|
|
8305
|
+
if (existsSync19(join11(this.cwd, "src/components"))) {
|
|
7931
8306
|
architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
|
|
7932
8307
|
}
|
|
7933
|
-
if (
|
|
8308
|
+
if (existsSync19(join11(this.cwd, "src/types.ts")) || existsSync19(join11(this.cwd, "src/types"))) {
|
|
7934
8309
|
architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
|
|
7935
8310
|
}
|
|
7936
|
-
if (
|
|
8311
|
+
if (existsSync19(join11(this.cwd, ".env.example"))) {
|
|
7937
8312
|
architectureNotes.push("Environment configuration template is in `.env.example`.");
|
|
7938
8313
|
}
|
|
7939
8314
|
if (commands.typecheck || commands.lint || commands.test) {
|
|
@@ -7950,7 +8325,7 @@ class ProjectAnalyzer {
|
|
|
7950
8325
|
let hasExistingInstructions = false;
|
|
7951
8326
|
let existingInstructionFile;
|
|
7952
8327
|
for (const f of instructionFiles) {
|
|
7953
|
-
if (
|
|
8328
|
+
if (existsSync19(join11(this.cwd, f))) {
|
|
7954
8329
|
hasExistingInstructions = true;
|
|
7955
8330
|
existingInstructionFile = f;
|
|
7956
8331
|
break;
|
|
@@ -8031,8 +8406,8 @@ class ProjectAnalyzer {
|
|
|
8031
8406
|
extractReadmeMetadata() {
|
|
8032
8407
|
const readmeFiles = ["README.md", "readme.md", "README.MD"];
|
|
8033
8408
|
for (const file of readmeFiles) {
|
|
8034
|
-
const fullPath =
|
|
8035
|
-
if (
|
|
8409
|
+
const fullPath = join11(this.cwd, file);
|
|
8410
|
+
if (existsSync19(fullPath)) {
|
|
8036
8411
|
try {
|
|
8037
8412
|
const content = readFileSync13(fullPath, "utf8");
|
|
8038
8413
|
const lines = content.split(`
|
|
@@ -8057,8 +8432,8 @@ class ProjectAnalyzer {
|
|
|
8057
8432
|
return {};
|
|
8058
8433
|
}
|
|
8059
8434
|
detectProjectName() {
|
|
8060
|
-
const pkgPath =
|
|
8061
|
-
if (
|
|
8435
|
+
const pkgPath = join11(this.cwd, "package.json");
|
|
8436
|
+
if (existsSync19(pkgPath)) {
|
|
8062
8437
|
try {
|
|
8063
8438
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
8064
8439
|
if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
|
|
@@ -8066,73 +8441,73 @@ class ProjectAnalyzer {
|
|
|
8066
8441
|
}
|
|
8067
8442
|
} catch {}
|
|
8068
8443
|
}
|
|
8069
|
-
const cargoPath =
|
|
8070
|
-
if (
|
|
8444
|
+
const cargoPath = join11(this.cwd, "Cargo.toml");
|
|
8445
|
+
if (existsSync19(cargoPath)) {
|
|
8071
8446
|
try {
|
|
8072
8447
|
const match = readFileSync13(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
|
|
8073
8448
|
if (match?.[1])
|
|
8074
8449
|
return match[1];
|
|
8075
8450
|
} catch {}
|
|
8076
8451
|
}
|
|
8077
|
-
const goModPath =
|
|
8078
|
-
if (
|
|
8452
|
+
const goModPath = join11(this.cwd, "go.mod");
|
|
8453
|
+
if (existsSync19(goModPath)) {
|
|
8079
8454
|
try {
|
|
8080
8455
|
const match = readFileSync13(goModPath, "utf8").match(/module\s+([^\s]+)/);
|
|
8081
8456
|
if (match?.[1])
|
|
8082
|
-
return
|
|
8457
|
+
return basename3(match[1]);
|
|
8083
8458
|
} catch {}
|
|
8084
8459
|
}
|
|
8085
|
-
return
|
|
8460
|
+
return basename3(this.cwd);
|
|
8086
8461
|
}
|
|
8087
8462
|
detectLanguages() {
|
|
8088
8463
|
const langs = new Set;
|
|
8089
|
-
if (
|
|
8464
|
+
if (existsSync19(join11(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
|
|
8090
8465
|
langs.add("TypeScript");
|
|
8091
8466
|
}
|
|
8092
|
-
if (
|
|
8467
|
+
if (existsSync19(join11(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
|
|
8093
8468
|
langs.add("JavaScript");
|
|
8094
8469
|
}
|
|
8095
|
-
if (
|
|
8470
|
+
if (existsSync19(join11(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
|
|
8096
8471
|
langs.add("Rust");
|
|
8097
8472
|
}
|
|
8098
|
-
if (
|
|
8473
|
+
if (existsSync19(join11(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
|
|
8099
8474
|
langs.add("Go");
|
|
8100
8475
|
}
|
|
8101
|
-
if (
|
|
8476
|
+
if (existsSync19(join11(this.cwd, "pyproject.toml")) || existsSync19(join11(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
|
|
8102
8477
|
langs.add("Python");
|
|
8103
8478
|
}
|
|
8104
|
-
if (
|
|
8479
|
+
if (existsSync19(join11(this.cwd, "pom.xml")) || existsSync19(join11(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
|
|
8105
8480
|
langs.add("Java");
|
|
8106
8481
|
}
|
|
8107
|
-
if (
|
|
8482
|
+
if (existsSync19(join11(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
|
|
8108
8483
|
langs.add("C/C++");
|
|
8109
8484
|
}
|
|
8110
8485
|
return Array.from(langs);
|
|
8111
8486
|
}
|
|
8112
8487
|
detectPackageManager() {
|
|
8113
|
-
if (
|
|
8488
|
+
if (existsSync19(join11(this.cwd, "bun.lockb")) || existsSync19(join11(this.cwd, "bun.lock")))
|
|
8114
8489
|
return "bun";
|
|
8115
|
-
if (
|
|
8490
|
+
if (existsSync19(join11(this.cwd, "pnpm-lock.yaml")))
|
|
8116
8491
|
return "pnpm";
|
|
8117
|
-
if (
|
|
8492
|
+
if (existsSync19(join11(this.cwd, "yarn.lock")))
|
|
8118
8493
|
return "yarn";
|
|
8119
|
-
if (
|
|
8494
|
+
if (existsSync19(join11(this.cwd, "package-lock.json")))
|
|
8120
8495
|
return "npm";
|
|
8121
|
-
if (
|
|
8496
|
+
if (existsSync19(join11(this.cwd, "Cargo.lock")) || existsSync19(join11(this.cwd, "Cargo.toml")))
|
|
8122
8497
|
return "cargo";
|
|
8123
|
-
if (
|
|
8498
|
+
if (existsSync19(join11(this.cwd, "uv.lock")))
|
|
8124
8499
|
return "uv";
|
|
8125
|
-
if (
|
|
8500
|
+
if (existsSync19(join11(this.cwd, "poetry.lock")))
|
|
8126
8501
|
return "poetry";
|
|
8127
|
-
if (
|
|
8502
|
+
if (existsSync19(join11(this.cwd, "go.sum")) || existsSync19(join11(this.cwd, "go.mod")))
|
|
8128
8503
|
return "go";
|
|
8129
|
-
if (
|
|
8504
|
+
if (existsSync19(join11(this.cwd, "package.json")))
|
|
8130
8505
|
return "npm";
|
|
8131
8506
|
return;
|
|
8132
8507
|
}
|
|
8133
8508
|
hasFileWithExtension(...exts) {
|
|
8134
8509
|
try {
|
|
8135
|
-
const entries =
|
|
8510
|
+
const entries = readdirSync8(this.cwd);
|
|
8136
8511
|
return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
|
|
8137
8512
|
} catch {
|
|
8138
8513
|
return false;
|
|
@@ -8140,16 +8515,16 @@ class ProjectAnalyzer {
|
|
|
8140
8515
|
}
|
|
8141
8516
|
}
|
|
8142
8517
|
// src/init/init-command.ts
|
|
8143
|
-
import { existsSync as
|
|
8144
|
-
import { join as
|
|
8518
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync7 } from "fs";
|
|
8519
|
+
import { join as join12 } from "path";
|
|
8145
8520
|
function runProjectInit(options = {}) {
|
|
8146
8521
|
const cwd = options.cwd || process.cwd();
|
|
8147
8522
|
const filename = options.filename || "AGENTS.md";
|
|
8148
|
-
const targetPath =
|
|
8523
|
+
const targetPath = join12(cwd, filename);
|
|
8149
8524
|
const analyzer = new ProjectAnalyzer(cwd);
|
|
8150
8525
|
const analysis = analyzer.analyze();
|
|
8151
8526
|
const content = analyzer.generateAgentsMarkdown(analysis);
|
|
8152
|
-
const alreadyExists =
|
|
8527
|
+
const alreadyExists = existsSync20(targetPath);
|
|
8153
8528
|
writeFileSync7(targetPath, content, "utf8");
|
|
8154
8529
|
return {
|
|
8155
8530
|
success: true,
|
|
@@ -8524,7 +8899,7 @@ Browser authorization opened automatically. If not, open:`);
|
|
|
8524
8899
|
console.log(style.dim(`Waiting for authorization callback on port 1455 ...`));
|
|
8525
8900
|
const creds = await waitForToken();
|
|
8526
8901
|
console.log(style.green(`
|
|
8527
|
-
\u2713 Authentication Successful! Token saved to ~/.
|
|
8902
|
+
\u2713 Authentication Successful! Token saved to ~/.pikaa/credentials.json`));
|
|
8528
8903
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
8529
8904
|
} catch (err) {
|
|
8530
8905
|
console.log(style.yellow(`
|
|
@@ -8542,7 +8917,7 @@ Falling back to Direct Terminal Login:`));
|
|
|
8542
8917
|
password
|
|
8543
8918
|
});
|
|
8544
8919
|
console.log(style.green(`
|
|
8545
|
-
\u2713 Successfully logged in! Token saved to ~/.
|
|
8920
|
+
\u2713 Successfully logged in! Token saved to ~/.pikaa/credentials.json`));
|
|
8546
8921
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
8547
8922
|
} catch (directErr) {
|
|
8548
8923
|
console.error(style.red(`
|
|
@@ -8755,7 +9130,7 @@ async function handleSkillsCommand(ctx, args) {
|
|
|
8755
9130
|
const skills = loader.listSkills(ctx.session.cwd, { includeDisabled: true });
|
|
8756
9131
|
if (skills.length === 0) {
|
|
8757
9132
|
console.log(style.dim(`
|
|
8758
|
-
No domain skills discovered in .agents/skills/ or ~/.
|
|
9133
|
+
No domain skills discovered in .agents/skills/ or ~/.pikaa/skills/
|
|
8759
9134
|
`));
|
|
8760
9135
|
return;
|
|
8761
9136
|
}
|
|
@@ -8811,17 +9186,51 @@ async function handleSkillsCommand(ctx, args) {
|
|
|
8811
9186
|
function printMemories(ctx) {
|
|
8812
9187
|
const store2 = ctx.memoryStore;
|
|
8813
9188
|
if (!store2) {
|
|
8814
|
-
console.log(style.yellow(
|
|
9189
|
+
console.log(style.yellow(`
|
|
9190
|
+
Memory store not active.
|
|
9191
|
+
`));
|
|
8815
9192
|
return;
|
|
8816
9193
|
}
|
|
8817
|
-
const
|
|
9194
|
+
const cwd = ctx.session.cwd;
|
|
9195
|
+
const memoryDir = store2.getProjectMemoryDir(cwd);
|
|
9196
|
+
const topics = store2.listProjectMemories(cwd);
|
|
9197
|
+
const indexContent = store2.loadMemoryIndex(cwd);
|
|
9198
|
+
const BOLD2 = "\x1B[1m";
|
|
9199
|
+
const RESET2 = "\x1B[0m";
|
|
9200
|
+
const DIM = "\x1B[2m";
|
|
9201
|
+
const CYAN = "\x1B[38;2;120;190;255m";
|
|
9202
|
+
const GREEN = "\x1B[38;2;140;220;140m";
|
|
9203
|
+
const ORANGE = "\x1B[38;2;217;119;87m";
|
|
9204
|
+
const PURPLE = "\x1B[38;2;190;140;240m";
|
|
9205
|
+
const YELLOW = "\x1B[38;2;250;210;110m";
|
|
9206
|
+
const getCategoryColor = (cat) => {
|
|
9207
|
+
switch (cat.toLowerCase()) {
|
|
9208
|
+
case "user":
|
|
9209
|
+
return CYAN;
|
|
9210
|
+
case "feedback":
|
|
9211
|
+
return GREEN;
|
|
9212
|
+
case "project":
|
|
9213
|
+
return ORANGE;
|
|
9214
|
+
case "reference":
|
|
9215
|
+
return PURPLE;
|
|
9216
|
+
default:
|
|
9217
|
+
return YELLOW;
|
|
9218
|
+
}
|
|
9219
|
+
};
|
|
8818
9220
|
console.log();
|
|
8819
|
-
|
|
8820
|
-
|
|
9221
|
+
console.log(` ${BOLD2}\uD83E\uDDE0 Project Auto-Memory Bank${RESET2}`);
|
|
9222
|
+
console.log(` ${DIM}Directory: ${memoryDir}${RESET2}`);
|
|
9223
|
+
console.log(` ${DIM}Status: ${GREEN}Active (Loaded into turn context \u2264200 lines)${RESET2}`);
|
|
9224
|
+
console.log();
|
|
9225
|
+
if (topics.length === 0) {
|
|
9226
|
+
console.log(` ${DIM}No persistent topic memories saved for this project yet.${RESET2}`);
|
|
9227
|
+
console.log(` ${DIM}As you work, Pikaa automatically records user preferences, feedback, and project context.${RESET2}`);
|
|
8821
9228
|
} else {
|
|
8822
|
-
console.log(
|
|
8823
|
-
for (const
|
|
8824
|
-
|
|
9229
|
+
console.log(` ${BOLD2}Learned Memory Topics (${topics.length}):${RESET2}`);
|
|
9230
|
+
for (const t of topics) {
|
|
9231
|
+
const color = getCategoryColor(t.type);
|
|
9232
|
+
console.log(` \u2022 ${color}[${t.type}]${RESET2} ${BOLD2}${t.name}${RESET2}: ${DIM}${t.description || t.content.split(`
|
|
9233
|
+
`)[0]}${RESET2}`);
|
|
8825
9234
|
}
|
|
8826
9235
|
}
|
|
8827
9236
|
console.log();
|
|
@@ -9201,7 +9610,7 @@ function printReleaseNotes() {
|
|
|
9201
9610
|
"",
|
|
9202
9611
|
" " + BOLD2 + WHITE2 + "\uD83D\uDE80 What's New in " + version + " (Latest)" + RESET2,
|
|
9203
9612
|
" " + ROSE2 + "\u2022" + RESET2 + " " + WHITE2 + BOLD2 + "Persistent Default AI Model" + RESET2 + ": Switch via " + ROSE2 + "/model" + RESET2 + " and save",
|
|
9204
|
-
" preference across sessions in ~/.
|
|
9613
|
+
" preference across sessions in ~/.pikaa/credentials.json.",
|
|
9205
9614
|
" " + ROSE2 + "\u2022" + RESET2 + " " + WHITE2 + BOLD2 + "Real-time Git Branch Detection" + RESET2 + ": Header displays active branch",
|
|
9206
9615
|
" (\uE0A0 main) alongside user subscription tier (Groupy Pro / Max).",
|
|
9207
9616
|
" " + ROSE2 + "\u2022" + RESET2 + " " + WHITE2 + BOLD2 + "Claude Code Terminal UI Parity" + RESET2 + ": Authentic pixel emblem, fieldset",
|
|
@@ -9580,9 +9989,9 @@ class MarkdownHighlighter {
|
|
|
9580
9989
|
}
|
|
9581
9990
|
|
|
9582
9991
|
// src/cli/update-checker.ts
|
|
9583
|
-
import { existsSync as
|
|
9584
|
-
import { homedir as
|
|
9585
|
-
import { join as
|
|
9992
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
|
|
9993
|
+
import { homedir as homedir5 } from "os";
|
|
9994
|
+
import { join as join13 } from "path";
|
|
9586
9995
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9587
9996
|
function parseSemver(v) {
|
|
9588
9997
|
const clean = v.replace(/^v/, "").trim();
|
|
@@ -9603,8 +10012,8 @@ function isNewerVersion(current, remote) {
|
|
|
9603
10012
|
return remPatch > curPatch;
|
|
9604
10013
|
}
|
|
9605
10014
|
function getUpdateCachePath() {
|
|
9606
|
-
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME ||
|
|
9607
|
-
return
|
|
10015
|
+
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join13(homedir5(), ".pikaa");
|
|
10016
|
+
return join13(baseDir, "update-cache.json");
|
|
9608
10017
|
}
|
|
9609
10018
|
async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
|
|
9610
10019
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
@@ -9637,7 +10046,7 @@ async function checkForUpdates(options = {}) {
|
|
|
9637
10046
|
const cachePath = options.cachePath || getUpdateCachePath();
|
|
9638
10047
|
const now = Date.now();
|
|
9639
10048
|
let cached = null;
|
|
9640
|
-
if (!options.force &&
|
|
10049
|
+
if (!options.force && existsSync21(cachePath)) {
|
|
9641
10050
|
try {
|
|
9642
10051
|
const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
|
|
9643
10052
|
if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
|
|
@@ -9667,9 +10076,9 @@ async function checkForUpdates(options = {}) {
|
|
|
9667
10076
|
return null;
|
|
9668
10077
|
}
|
|
9669
10078
|
try {
|
|
9670
|
-
const parentDir =
|
|
9671
|
-
if (!
|
|
9672
|
-
|
|
10079
|
+
const parentDir = join13(cachePath, "..");
|
|
10080
|
+
if (!existsSync21(parentDir)) {
|
|
10081
|
+
mkdirSync11(parentDir, { recursive: true });
|
|
9673
10082
|
}
|
|
9674
10083
|
const cacheData = {
|
|
9675
10084
|
lastChecked: now,
|
|
@@ -10091,7 +10500,7 @@ async function main() {
|
|
|
10091
10500
|
resolve21(cwd, "mcp_config.json")
|
|
10092
10501
|
].filter(Boolean);
|
|
10093
10502
|
for (const cfg of candidateConfigs) {
|
|
10094
|
-
if (
|
|
10503
|
+
if (existsSync22(cfg)) {
|
|
10095
10504
|
try {
|
|
10096
10505
|
await mcpManager.loadConfigFile(cfg);
|
|
10097
10506
|
mcpManager.registerToolsIntoRouter(tools4);
|
|
@@ -10205,7 +10614,7 @@ Open the following link in your browser to complete authorization:`);
|
|
|
10205
10614
|
Waiting for browser callback on http://localhost:1455/auth/callback ...`));
|
|
10206
10615
|
const creds = await waitForToken();
|
|
10207
10616
|
console.log(style.green(`
|
|
10208
|
-
\u2713 Authentication Successful! Token saved to ~/.
|
|
10617
|
+
\u2713 Authentication Successful! Token saved to ~/.pikaa/credentials.json`));
|
|
10209
10618
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
10210
10619
|
} catch (err) {
|
|
10211
10620
|
console.log(style.yellow(`
|
|
@@ -10224,7 +10633,7 @@ Falling back to Direct Terminal Login:`));
|
|
|
10224
10633
|
password: password.trim()
|
|
10225
10634
|
});
|
|
10226
10635
|
console.log(style.green(`
|
|
10227
|
-
\u2713 Successfully logged in! Token saved to ~/.
|
|
10636
|
+
\u2713 Successfully logged in! Token saved to ~/.pikaa/credentials.json`));
|
|
10228
10637
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
10229
10638
|
} catch (directErr) {
|
|
10230
10639
|
rl.close();
|
|
@@ -10298,7 +10707,7 @@ function printSkillsList(loader, cwd) {
|
|
|
10298
10707
|
const skills = loader.discoverSkills(cwd);
|
|
10299
10708
|
console.log();
|
|
10300
10709
|
if (skills.length === 0) {
|
|
10301
|
-
console.log(style.dim("No skills found in .agents/skills/ or ~/.
|
|
10710
|
+
console.log(style.dim("No skills found in .agents/skills/ or ~/.pikaa/skills/"));
|
|
10302
10711
|
} else {
|
|
10303
10712
|
console.log(style.bold("Discovered Domain Skills:"));
|
|
10304
10713
|
for (const s of skills) {
|