@pikaa-ai/pikaa 0.3.13 → 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 +775 -355
- package/dist/index.js +709 -319
- package/package.json +1 -1
- package/templates/base/groupy_prompt.md +28 -2
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) {
|
|
@@ -1028,6 +1155,17 @@ async function runTurn(session, turnContext, input) {
|
|
|
1028
1155
|
}
|
|
1029
1156
|
continue;
|
|
1030
1157
|
}
|
|
1158
|
+
if (!currentAgentText.trim() && toolCallRequests.length === 0) {
|
|
1159
|
+
if (iteration === 1 && iteration < turnContext.maxIterations) {
|
|
1160
|
+
session.addHistoryItem({
|
|
1161
|
+
id: `msg_nudge_${Date.now()}`,
|
|
1162
|
+
type: "user_message",
|
|
1163
|
+
content: "Please proceed with executing the task. Provide your complete analysis or call the required tools now.",
|
|
1164
|
+
createdAt: Date.now()
|
|
1165
|
+
});
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1031
1169
|
break;
|
|
1032
1170
|
}
|
|
1033
1171
|
const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
|
|
@@ -1334,13 +1472,13 @@ class Session {
|
|
|
1334
1472
|
type: "StatusChanged",
|
|
1335
1473
|
status: "waiting_approval"
|
|
1336
1474
|
});
|
|
1337
|
-
return new Promise((
|
|
1475
|
+
return new Promise((resolve5) => {
|
|
1338
1476
|
this.pendingApprovals.set(params.approvalId, (approved) => {
|
|
1339
1477
|
this.emitEvent({
|
|
1340
1478
|
type: "StatusChanged",
|
|
1341
1479
|
status: "running"
|
|
1342
1480
|
});
|
|
1343
|
-
|
|
1481
|
+
resolve5(approved);
|
|
1344
1482
|
});
|
|
1345
1483
|
});
|
|
1346
1484
|
}
|
|
@@ -1363,13 +1501,13 @@ class Session {
|
|
|
1363
1501
|
type: "StatusChanged",
|
|
1364
1502
|
status: "waiting_user_input"
|
|
1365
1503
|
});
|
|
1366
|
-
return new Promise((
|
|
1504
|
+
return new Promise((resolve5) => {
|
|
1367
1505
|
this.pendingUserQuestions.set(params.questionId, (answer) => {
|
|
1368
1506
|
this.emitEvent({
|
|
1369
1507
|
type: "StatusChanged",
|
|
1370
1508
|
status: "running"
|
|
1371
1509
|
});
|
|
1372
|
-
|
|
1510
|
+
resolve5(answer);
|
|
1373
1511
|
});
|
|
1374
1512
|
});
|
|
1375
1513
|
}
|
|
@@ -1399,7 +1537,7 @@ class Session {
|
|
|
1399
1537
|
return handleTurnInput(this, { text, images });
|
|
1400
1538
|
}
|
|
1401
1539
|
async promptAndWait(text, images, timeoutMs = 30000) {
|
|
1402
|
-
return new Promise((
|
|
1540
|
+
return new Promise((resolve5, reject) => {
|
|
1403
1541
|
const timer = setTimeout(() => {
|
|
1404
1542
|
unsub();
|
|
1405
1543
|
reject(new Error(`Turn timed out after ${timeoutMs}ms`));
|
|
@@ -1408,7 +1546,7 @@ class Session {
|
|
|
1408
1546
|
if (event.msg.type === "TurnCompleted") {
|
|
1409
1547
|
clearTimeout(timer);
|
|
1410
1548
|
unsub();
|
|
1411
|
-
|
|
1549
|
+
resolve5();
|
|
1412
1550
|
} else if (event.msg.type === "Error") {
|
|
1413
1551
|
clearTimeout(timer);
|
|
1414
1552
|
unsub();
|
|
@@ -1427,8 +1565,8 @@ class Session {
|
|
|
1427
1565
|
if (this.submissionQueue.length > 0) {
|
|
1428
1566
|
yield this.submissionQueue.shift();
|
|
1429
1567
|
} else {
|
|
1430
|
-
const nextSub = await new Promise((
|
|
1431
|
-
this.submissionResolvers.push(
|
|
1568
|
+
const nextSub = await new Promise((resolve5) => {
|
|
1569
|
+
this.submissionResolvers.push(resolve5);
|
|
1432
1570
|
});
|
|
1433
1571
|
yield nextSub;
|
|
1434
1572
|
}
|
|
@@ -1442,9 +1580,9 @@ class Session {
|
|
|
1442
1580
|
}
|
|
1443
1581
|
}
|
|
1444
1582
|
// src/tools/handlers/apply-patch.ts
|
|
1445
|
-
import { existsSync as
|
|
1446
|
-
import { resolve as
|
|
1447
|
-
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";
|
|
1448
1586
|
var applyPatchTool = {
|
|
1449
1587
|
name: "apply_patch",
|
|
1450
1588
|
description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
|
|
@@ -1471,7 +1609,7 @@ var applyPatchTool = {
|
|
|
1471
1609
|
if (!rawPath) {
|
|
1472
1610
|
return { output: "Error: 'path' parameter is required", isError: true };
|
|
1473
1611
|
}
|
|
1474
|
-
const filePath =
|
|
1612
|
+
const filePath = resolve5(ctx.cwd, rawPath);
|
|
1475
1613
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1476
1614
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1477
1615
|
if (ctx.execPolicy) {
|
|
@@ -1490,7 +1628,7 @@ var applyPatchTool = {
|
|
|
1490
1628
|
}
|
|
1491
1629
|
}
|
|
1492
1630
|
}
|
|
1493
|
-
if (!
|
|
1631
|
+
if (!existsSync5(filePath)) {
|
|
1494
1632
|
if (targetContent) {
|
|
1495
1633
|
return {
|
|
1496
1634
|
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.`,
|
|
@@ -1498,7 +1636,7 @@ var applyPatchTool = {
|
|
|
1498
1636
|
};
|
|
1499
1637
|
}
|
|
1500
1638
|
try {
|
|
1501
|
-
|
|
1639
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
1502
1640
|
writeFileSync2(filePath, replacementContent, "utf8");
|
|
1503
1641
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1504
1642
|
} catch (err) {
|
|
@@ -1624,7 +1762,7 @@ class WindowsSandbox {
|
|
|
1624
1762
|
}
|
|
1625
1763
|
|
|
1626
1764
|
// src/security/kernel/linux.ts
|
|
1627
|
-
import { existsSync as
|
|
1765
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1628
1766
|
|
|
1629
1767
|
class LinuxSandbox {
|
|
1630
1768
|
hasBwrap = false;
|
|
@@ -1635,7 +1773,7 @@ class LinuxSandbox {
|
|
|
1635
1773
|
if (process.platform !== "linux") {
|
|
1636
1774
|
return;
|
|
1637
1775
|
}
|
|
1638
|
-
this.hasBwrap =
|
|
1776
|
+
this.hasBwrap = existsSync6("/usr/bin/bwrap") || existsSync6("/bin/bwrap") || existsSync6("/usr/local/bin/bwrap");
|
|
1639
1777
|
}
|
|
1640
1778
|
wrapCommand(cmd, profile) {
|
|
1641
1779
|
if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
|
|
@@ -1669,7 +1807,7 @@ class LinuxSandbox {
|
|
|
1669
1807
|
}
|
|
1670
1808
|
|
|
1671
1809
|
// src/security/kernel/macos.ts
|
|
1672
|
-
import { existsSync as
|
|
1810
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1673
1811
|
|
|
1674
1812
|
class MacOSSandbox {
|
|
1675
1813
|
hasSandboxExec = false;
|
|
@@ -1680,7 +1818,7 @@ class MacOSSandbox {
|
|
|
1680
1818
|
if (process.platform !== "darwin") {
|
|
1681
1819
|
return;
|
|
1682
1820
|
}
|
|
1683
|
-
this.hasSandboxExec =
|
|
1821
|
+
this.hasSandboxExec = existsSync7("/usr/bin/sandbox-exec");
|
|
1684
1822
|
}
|
|
1685
1823
|
generateProfile(profile) {
|
|
1686
1824
|
const rules = [
|
|
@@ -1719,7 +1857,7 @@ class MacOSSandbox {
|
|
|
1719
1857
|
}
|
|
1720
1858
|
|
|
1721
1859
|
// src/security/kernel/manager.ts
|
|
1722
|
-
import { resolve as
|
|
1860
|
+
import { resolve as resolve6, normalize } from "path";
|
|
1723
1861
|
|
|
1724
1862
|
class KernelSandboxManager {
|
|
1725
1863
|
windowsSandbox;
|
|
@@ -1741,10 +1879,10 @@ class KernelSandboxManager {
|
|
|
1741
1879
|
};
|
|
1742
1880
|
}
|
|
1743
1881
|
buildDefaultProfile(cwd, allowNetwork = true) {
|
|
1744
|
-
const normCwd = normalize(
|
|
1882
|
+
const normCwd = normalize(resolve6(cwd));
|
|
1745
1883
|
return {
|
|
1746
1884
|
kind: "workspace-write",
|
|
1747
|
-
readableRoots: [normCwd,
|
|
1885
|
+
readableRoots: [normCwd, resolve6(process.cwd())],
|
|
1748
1886
|
writableRoots: [normCwd],
|
|
1749
1887
|
allowNetwork,
|
|
1750
1888
|
limits: {
|
|
@@ -1790,21 +1928,19 @@ var globalKernelSandbox = new KernelSandboxManager;
|
|
|
1790
1928
|
|
|
1791
1929
|
// src/storage/prefix-rules-store.ts
|
|
1792
1930
|
import { Database } from "bun:sqlite";
|
|
1793
|
-
import { existsSync as
|
|
1794
|
-
import { dirname as
|
|
1795
|
-
import { homedir as homedir3 } from "os";
|
|
1796
|
-
|
|
1931
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
|
|
1932
|
+
import { dirname as dirname4, resolve as resolve7 } from "path";
|
|
1797
1933
|
class PrefixRulesStore {
|
|
1798
1934
|
db;
|
|
1799
1935
|
constructor(dbOrPath) {
|
|
1800
1936
|
if (dbOrPath instanceof Database) {
|
|
1801
1937
|
this.db = dbOrPath;
|
|
1802
1938
|
} else {
|
|
1803
|
-
const effectivePath = dbOrPath ||
|
|
1939
|
+
const effectivePath = dbOrPath || getPrefixRulesDbPath();
|
|
1804
1940
|
if (effectivePath !== ":memory:") {
|
|
1805
|
-
const dir =
|
|
1806
|
-
if (!
|
|
1807
|
-
|
|
1941
|
+
const dir = dirname4(effectivePath);
|
|
1942
|
+
if (!existsSync8(dir)) {
|
|
1943
|
+
mkdirSync4(dir, { recursive: true });
|
|
1808
1944
|
}
|
|
1809
1945
|
}
|
|
1810
1946
|
this.db = new Database(effectivePath);
|
|
@@ -1827,7 +1963,7 @@ class PrefixRulesStore {
|
|
|
1827
1963
|
addRule(workspacePath, prefixTokens) {
|
|
1828
1964
|
if (!prefixTokens || prefixTokens.length === 0)
|
|
1829
1965
|
return;
|
|
1830
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
1966
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve7(workspacePath);
|
|
1831
1967
|
const tokensJson = JSON.stringify(prefixTokens);
|
|
1832
1968
|
const id = `${normalizedWs}:${tokensJson}`;
|
|
1833
1969
|
const query = this.db.prepare(`
|
|
@@ -1844,7 +1980,7 @@ class PrefixRulesStore {
|
|
|
1844
1980
|
isApproved(workspacePath, commandTokens) {
|
|
1845
1981
|
if (!commandTokens || commandTokens.length === 0)
|
|
1846
1982
|
return false;
|
|
1847
|
-
const normalizedWs =
|
|
1983
|
+
const normalizedWs = resolve7(workspacePath);
|
|
1848
1984
|
const query = this.db.prepare(`
|
|
1849
1985
|
SELECT prefix_tokens FROM approved_prefix_rules
|
|
1850
1986
|
WHERE workspace_path = $ws OR workspace_path = '*'
|
|
@@ -1863,7 +1999,7 @@ class PrefixRulesStore {
|
|
|
1863
1999
|
listRules(workspacePath) {
|
|
1864
2000
|
let rows;
|
|
1865
2001
|
if (workspacePath) {
|
|
1866
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2002
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve7(workspacePath);
|
|
1867
2003
|
const query = this.db.prepare(`
|
|
1868
2004
|
SELECT prefix_tokens FROM approved_prefix_rules
|
|
1869
2005
|
WHERE workspace_path = $ws OR workspace_path = '*'
|
|
@@ -1882,7 +2018,7 @@ class PrefixRulesStore {
|
|
|
1882
2018
|
}).filter((r) => r.length > 0);
|
|
1883
2019
|
}
|
|
1884
2020
|
removeRule(workspacePath, prefixTokens) {
|
|
1885
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2021
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve7(workspacePath);
|
|
1886
2022
|
const tokensJson = JSON.stringify(prefixTokens);
|
|
1887
2023
|
const id = `${normalizedWs}:${tokensJson}`;
|
|
1888
2024
|
const query = this.db.prepare(`
|
|
@@ -2015,7 +2151,7 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2015
2151
|
} catch {}
|
|
2016
2152
|
});
|
|
2017
2153
|
}
|
|
2018
|
-
const timeoutPromise = new Promise((
|
|
2154
|
+
const timeoutPromise = new Promise((resolve8) => setTimeout(() => resolve8({ isTimeout: true }), timeoutMs));
|
|
2019
2155
|
const result = await Promise.race([
|
|
2020
2156
|
proc.exited.then(async (code) => {
|
|
2021
2157
|
const stdout = await new Response(proc.stdout).text();
|
|
@@ -2059,8 +2195,8 @@ ${result.stderr.trim()}`);
|
|
|
2059
2195
|
}
|
|
2060
2196
|
var shellTool = createShellTool();
|
|
2061
2197
|
// src/tools/handlers/file-ops.ts
|
|
2062
|
-
import { readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as
|
|
2063
|
-
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";
|
|
2064
2200
|
var readFileTool = {
|
|
2065
2201
|
name: "read_file",
|
|
2066
2202
|
description: "Read the full text content of a file.",
|
|
@@ -2072,8 +2208,8 @@ var readFileTool = {
|
|
|
2072
2208
|
required: ["path"]
|
|
2073
2209
|
},
|
|
2074
2210
|
async execute(args, ctx) {
|
|
2075
|
-
const filePath =
|
|
2076
|
-
if (!
|
|
2211
|
+
const filePath = resolve8(ctx.cwd, String(args.path || ""));
|
|
2212
|
+
if (!existsSync9(filePath)) {
|
|
2077
2213
|
return { output: `Error: File not found: '${args.path}'`, isError: true };
|
|
2078
2214
|
}
|
|
2079
2215
|
try {
|
|
@@ -2094,15 +2230,15 @@ var listDirTool = {
|
|
|
2094
2230
|
}
|
|
2095
2231
|
},
|
|
2096
2232
|
async execute(args, ctx) {
|
|
2097
|
-
const dirPath =
|
|
2098
|
-
if (!
|
|
2233
|
+
const dirPath = resolve8(ctx.cwd, String(args.path || "."));
|
|
2234
|
+
if (!existsSync9(dirPath)) {
|
|
2099
2235
|
return { output: `Error: Directory not found: '${args.path}'`, isError: true };
|
|
2100
2236
|
}
|
|
2101
2237
|
try {
|
|
2102
|
-
const entries =
|
|
2238
|
+
const entries = readdirSync2(dirPath);
|
|
2103
2239
|
const formatted = entries.map((entry) => {
|
|
2104
|
-
const full =
|
|
2105
|
-
const isDir =
|
|
2240
|
+
const full = resolve8(dirPath, entry);
|
|
2241
|
+
const isDir = statSync2(full).isDirectory();
|
|
2106
2242
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2107
2243
|
});
|
|
2108
2244
|
return { output: formatted.join(`
|
|
@@ -2125,7 +2261,7 @@ var writeFileTool = {
|
|
|
2125
2261
|
},
|
|
2126
2262
|
async execute(args, ctx) {
|
|
2127
2263
|
const rawPath = String(args.path || "");
|
|
2128
|
-
const filePath =
|
|
2264
|
+
const filePath = resolve8(ctx.cwd, rawPath);
|
|
2129
2265
|
if (ctx.execPolicy) {
|
|
2130
2266
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2131
2267
|
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
@@ -2143,7 +2279,7 @@ var writeFileTool = {
|
|
|
2143
2279
|
}
|
|
2144
2280
|
}
|
|
2145
2281
|
try {
|
|
2146
|
-
|
|
2282
|
+
mkdirSync5(dirname5(filePath), { recursive: true });
|
|
2147
2283
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
2148
2284
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2149
2285
|
} catch (err) {
|
|
@@ -2266,8 +2402,8 @@ var updatePlanTool = {
|
|
|
2266
2402
|
}
|
|
2267
2403
|
};
|
|
2268
2404
|
// src/search/engine.ts
|
|
2269
|
-
import { readdirSync as
|
|
2270
|
-
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";
|
|
2271
2407
|
var DEFAULT_IGNORE_DIRS = new Set([
|
|
2272
2408
|
".git",
|
|
2273
2409
|
"node_modules",
|
|
@@ -2312,8 +2448,8 @@ var BINARY_EXTENSIONS = new Set([
|
|
|
2312
2448
|
|
|
2313
2449
|
class FileSearchEngine {
|
|
2314
2450
|
grep(cwd, options) {
|
|
2315
|
-
const searchRoot =
|
|
2316
|
-
if (!
|
|
2451
|
+
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2452
|
+
if (!existsSync10(searchRoot)) {
|
|
2317
2453
|
return { matches: [], totalMatches: 0, truncated: false };
|
|
2318
2454
|
}
|
|
2319
2455
|
const maxResults = options.maxResults || 50;
|
|
@@ -2362,8 +2498,8 @@ class FileSearchEngine {
|
|
|
2362
2498
|
return { matches, totalMatches, truncated };
|
|
2363
2499
|
}
|
|
2364
2500
|
findFiles(cwd, options) {
|
|
2365
|
-
const searchRoot =
|
|
2366
|
-
if (!
|
|
2501
|
+
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2502
|
+
if (!existsSync10(searchRoot))
|
|
2367
2503
|
return [];
|
|
2368
2504
|
const maxResults = options.maxResults || 100;
|
|
2369
2505
|
const gitignoreRules = this.loadGitignoreRules(searchRoot);
|
|
@@ -2402,8 +2538,8 @@ class FileSearchEngine {
|
|
|
2402
2538
|
}
|
|
2403
2539
|
loadGitignoreRules(root) {
|
|
2404
2540
|
const rules = new Set;
|
|
2405
|
-
const gitignorePath =
|
|
2406
|
-
if (
|
|
2541
|
+
const gitignorePath = join4(root, ".gitignore");
|
|
2542
|
+
if (existsSync10(gitignorePath)) {
|
|
2407
2543
|
try {
|
|
2408
2544
|
const lines = readFileSync6(gitignorePath, "utf8").split(`
|
|
2409
2545
|
`);
|
|
@@ -2420,7 +2556,7 @@ class FileSearchEngine {
|
|
|
2420
2556
|
collectFiles(dir, root, gitignoreRules, includePattern) {
|
|
2421
2557
|
const results = [];
|
|
2422
2558
|
try {
|
|
2423
|
-
const stat =
|
|
2559
|
+
const stat = statSync3(dir);
|
|
2424
2560
|
if (!stat.isDirectory()) {
|
|
2425
2561
|
if (!this.isBinary(dir)) {
|
|
2426
2562
|
results.push(dir);
|
|
@@ -2434,9 +2570,9 @@ class FileSearchEngine {
|
|
|
2434
2570
|
while (queue.length > 0) {
|
|
2435
2571
|
const currentDir = queue.shift();
|
|
2436
2572
|
try {
|
|
2437
|
-
const entries =
|
|
2573
|
+
const entries = readdirSync3(currentDir, { withFileTypes: true });
|
|
2438
2574
|
for (const entry of entries) {
|
|
2439
|
-
const fullPath =
|
|
2575
|
+
const fullPath = join4(currentDir, entry.name);
|
|
2440
2576
|
const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
|
|
2441
2577
|
if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
|
|
2442
2578
|
continue;
|
|
@@ -2833,6 +2969,111 @@ ${loaded.instructions}`
|
|
|
2833
2969
|
}
|
|
2834
2970
|
|
|
2835
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
|
+
}
|
|
2836
3077
|
function createRememberTool(store) {
|
|
2837
3078
|
return {
|
|
2838
3079
|
name: "remember",
|
|
@@ -2843,16 +3084,15 @@ function createRememberTool(store) {
|
|
|
2843
3084
|
category: {
|
|
2844
3085
|
type: "string",
|
|
2845
3086
|
description: "Category of the memory.",
|
|
2846
|
-
enum: ["preference", "guideline", "architecture", "note"]
|
|
3087
|
+
enum: ["preference", "guideline", "architecture", "note", "user", "feedback", "project", "reference"]
|
|
2847
3088
|
},
|
|
2848
3089
|
content: {
|
|
2849
3090
|
type: "string",
|
|
2850
3091
|
description: "The concise rule, preference, or fact to remember permanently."
|
|
2851
3092
|
},
|
|
2852
|
-
|
|
3093
|
+
name: {
|
|
2853
3094
|
type: "string",
|
|
2854
|
-
description: "
|
|
2855
|
-
enum: ["global", "workspace"]
|
|
3095
|
+
description: "Optional topic name."
|
|
2856
3096
|
}
|
|
2857
3097
|
},
|
|
2858
3098
|
required: ["category", "content"]
|
|
@@ -2860,22 +3100,30 @@ function createRememberTool(store) {
|
|
|
2860
3100
|
async execute(args, context) {
|
|
2861
3101
|
const category = args.category || "preference";
|
|
2862
3102
|
const content = String(args.content || "");
|
|
2863
|
-
const
|
|
3103
|
+
const name = args.name ? String(args.name) : undefined;
|
|
2864
3104
|
if (!content) {
|
|
2865
3105
|
return { output: "Error: memory content cannot be empty", isError: true };
|
|
2866
3106
|
}
|
|
2867
3107
|
const entry = store.addMemory({
|
|
2868
3108
|
category,
|
|
2869
3109
|
content,
|
|
2870
|
-
|
|
3110
|
+
name,
|
|
2871
3111
|
cwd: context.cwd
|
|
2872
3112
|
});
|
|
2873
3113
|
return {
|
|
2874
|
-
output: `Successfully saved to
|
|
3114
|
+
output: `Successfully saved to Auto-Memory bank: [${entry.category}] "${entry.name || entry.content}"`
|
|
2875
3115
|
};
|
|
2876
3116
|
}
|
|
2877
3117
|
};
|
|
2878
3118
|
}
|
|
3119
|
+
function createAutoMemoryTools(store) {
|
|
3120
|
+
return [
|
|
3121
|
+
createSaveMemoryTool(store),
|
|
3122
|
+
createReadMemoryTool(store),
|
|
3123
|
+
createListMemoriesTool(store),
|
|
3124
|
+
createRememberTool(store)
|
|
3125
|
+
];
|
|
3126
|
+
}
|
|
2879
3127
|
|
|
2880
3128
|
// src/worktree/tools.ts
|
|
2881
3129
|
function createWorktreeTools(manager) {
|
|
@@ -3010,7 +3258,9 @@ function createDefaultTools(options = {}) {
|
|
|
3010
3258
|
router2.register(createSkillTool(options.skillsLoader));
|
|
3011
3259
|
}
|
|
3012
3260
|
if (options.memoryStore) {
|
|
3013
|
-
|
|
3261
|
+
for (const tool of createAutoMemoryTools(options.memoryStore)) {
|
|
3262
|
+
router2.register(tool);
|
|
3263
|
+
}
|
|
3014
3264
|
}
|
|
3015
3265
|
if (options.worktreeManager) {
|
|
3016
3266
|
for (const tool of createWorktreeTools(options.worktreeManager)) {
|
|
@@ -3021,8 +3271,8 @@ function createDefaultTools(options = {}) {
|
|
|
3021
3271
|
}
|
|
3022
3272
|
|
|
3023
3273
|
// src/agents/roles.ts
|
|
3024
|
-
import { existsSync as
|
|
3025
|
-
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";
|
|
3026
3276
|
|
|
3027
3277
|
class AgentRoleRegistry {
|
|
3028
3278
|
roles = new Map;
|
|
@@ -3106,14 +3356,14 @@ class AgentRoleRegistry {
|
|
|
3106
3356
|
return cycle === 0 ? base : `${base}_${cycle + 1}`;
|
|
3107
3357
|
}
|
|
3108
3358
|
loadRolesFromDir(dirPath) {
|
|
3109
|
-
const fullPath =
|
|
3110
|
-
if (!
|
|
3359
|
+
const fullPath = resolve10(dirPath);
|
|
3360
|
+
if (!existsSync11(fullPath))
|
|
3111
3361
|
return;
|
|
3112
|
-
const entries =
|
|
3362
|
+
const entries = readdirSync4(fullPath);
|
|
3113
3363
|
for (const entry of entries) {
|
|
3114
3364
|
if (entry.endsWith(".json")) {
|
|
3115
3365
|
try {
|
|
3116
|
-
const content = readFileSync7(
|
|
3366
|
+
const content = readFileSync7(join5(fullPath, entry), "utf8");
|
|
3117
3367
|
const parsed = JSON.parse(content);
|
|
3118
3368
|
if (parsed.name && parsed.systemPrompt) {
|
|
3119
3369
|
this.registerRole(parsed);
|
|
@@ -3165,21 +3415,19 @@ function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
|
|
|
3165
3415
|
|
|
3166
3416
|
// src/agents/graph-store.ts
|
|
3167
3417
|
import { Database as Database2 } from "bun:sqlite";
|
|
3168
|
-
import { resolve as
|
|
3169
|
-
import { existsSync as
|
|
3170
|
-
import { homedir as homedir4 } from "os";
|
|
3171
|
-
|
|
3418
|
+
import { resolve as resolve11 } from "path";
|
|
3419
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
3172
3420
|
class AgentGraphStore {
|
|
3173
3421
|
db;
|
|
3174
3422
|
constructor(dbPathOrDb) {
|
|
3175
3423
|
if (dbPathOrDb instanceof Database2) {
|
|
3176
3424
|
this.db = dbPathOrDb;
|
|
3177
3425
|
} else {
|
|
3178
|
-
const dbPath = dbPathOrDb ||
|
|
3426
|
+
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
3179
3427
|
if (dbPath !== ":memory:") {
|
|
3180
|
-
const dir =
|
|
3181
|
-
if (!
|
|
3182
|
-
|
|
3428
|
+
const dir = resolve11(dbPath, "..");
|
|
3429
|
+
if (!existsSync12(dir)) {
|
|
3430
|
+
mkdirSync6(dir, { recursive: true });
|
|
3183
3431
|
}
|
|
3184
3432
|
}
|
|
3185
3433
|
this.db = new Database2(dbPath);
|
|
@@ -3302,8 +3550,8 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
3302
3550
|
});
|
|
3303
3551
|
let resolvePromise;
|
|
3304
3552
|
let rejectPromise;
|
|
3305
|
-
const taskPromise = new Promise((
|
|
3306
|
-
resolvePromise =
|
|
3553
|
+
const taskPromise = new Promise((resolve12, reject) => {
|
|
3554
|
+
resolvePromise = resolve12;
|
|
3307
3555
|
rejectPromise = reject;
|
|
3308
3556
|
});
|
|
3309
3557
|
const handle = {
|
|
@@ -3603,8 +3851,8 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
3603
3851
|
}
|
|
3604
3852
|
|
|
3605
3853
|
// src/mcp/manager.ts
|
|
3606
|
-
import { existsSync as
|
|
3607
|
-
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";
|
|
3608
3856
|
|
|
3609
3857
|
// src/mcp/client.ts
|
|
3610
3858
|
class McpClient {
|
|
@@ -3998,13 +4246,13 @@ class StdioTransport {
|
|
|
3998
4246
|
if (this.isClosed || !this.proc || !this.proc.stdin) {
|
|
3999
4247
|
throw new GroupyError("MCP Stdio transport is closed");
|
|
4000
4248
|
}
|
|
4001
|
-
return new Promise((
|
|
4249
|
+
return new Promise((resolve12, reject) => {
|
|
4002
4250
|
const timeoutMs = 30000;
|
|
4003
4251
|
const timer = setTimeout(() => {
|
|
4004
4252
|
this.pendingRequests.delete(request.id);
|
|
4005
4253
|
reject(new GroupyError(`MCP request timed out after ${timeoutMs}ms (method: ${request.method})`));
|
|
4006
4254
|
}, timeoutMs);
|
|
4007
|
-
this.pendingRequests.set(request.id, { resolve:
|
|
4255
|
+
this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
|
|
4008
4256
|
try {
|
|
4009
4257
|
const payload = JSON.stringify(request) + `
|
|
4010
4258
|
`;
|
|
@@ -4137,12 +4385,12 @@ class SseTransport {
|
|
|
4137
4385
|
if (!this.messageUrl) {
|
|
4138
4386
|
this.messageUrl = this.endpointUrl;
|
|
4139
4387
|
}
|
|
4140
|
-
return new Promise((
|
|
4388
|
+
return new Promise((resolve12, reject) => {
|
|
4141
4389
|
const timer = setTimeout(() => {
|
|
4142
4390
|
this.pendingRequests.delete(request.id);
|
|
4143
4391
|
reject(new GroupyError(`MCP SSE request timed out (method: ${request.method})`));
|
|
4144
4392
|
}, 30000);
|
|
4145
|
-
this.pendingRequests.set(request.id, { resolve:
|
|
4393
|
+
this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
|
|
4146
4394
|
fetch(this.messageUrl, {
|
|
4147
4395
|
method: "POST",
|
|
4148
4396
|
headers: {
|
|
@@ -4219,8 +4467,8 @@ class McpManager {
|
|
|
4219
4467
|
}
|
|
4220
4468
|
}
|
|
4221
4469
|
async loadConfigFile(filePath) {
|
|
4222
|
-
const fullPath =
|
|
4223
|
-
if (!
|
|
4470
|
+
const fullPath = resolve12(filePath);
|
|
4471
|
+
if (!existsSync13(fullPath))
|
|
4224
4472
|
return;
|
|
4225
4473
|
this.loadedConfigFiles.add(fullPath);
|
|
4226
4474
|
try {
|
|
@@ -4473,13 +4721,13 @@ class McpManager {
|
|
|
4473
4721
|
`);
|
|
4474
4722
|
}
|
|
4475
4723
|
saveServerToConfigFile(filePath, name, config) {
|
|
4476
|
-
const fullPath =
|
|
4477
|
-
const dir =
|
|
4478
|
-
if (!
|
|
4479
|
-
|
|
4724
|
+
const fullPath = resolve12(filePath);
|
|
4725
|
+
const dir = dirname6(fullPath);
|
|
4726
|
+
if (!existsSync13(dir)) {
|
|
4727
|
+
mkdirSync7(dir, { recursive: true });
|
|
4480
4728
|
}
|
|
4481
4729
|
let existing = { mcpServers: {} };
|
|
4482
|
-
if (
|
|
4730
|
+
if (existsSync13(fullPath)) {
|
|
4483
4731
|
try {
|
|
4484
4732
|
const content = readFileSync8(fullPath, "utf8");
|
|
4485
4733
|
existing = JSON.parse(content);
|
|
@@ -4493,8 +4741,8 @@ class McpManager {
|
|
|
4493
4741
|
this.loadedConfigFiles.add(fullPath);
|
|
4494
4742
|
}
|
|
4495
4743
|
removeServerFromConfigFile(filePath, name) {
|
|
4496
|
-
const fullPath =
|
|
4497
|
-
if (!
|
|
4744
|
+
const fullPath = resolve12(filePath);
|
|
4745
|
+
if (!existsSync13(fullPath))
|
|
4498
4746
|
return false;
|
|
4499
4747
|
try {
|
|
4500
4748
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -4524,11 +4772,11 @@ class McpManager {
|
|
|
4524
4772
|
}
|
|
4525
4773
|
}
|
|
4526
4774
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
4527
|
-
const workspaceConfig =
|
|
4528
|
-
if (
|
|
4775
|
+
const workspaceConfig = join6(cwd, ".mcp.json");
|
|
4776
|
+
if (existsSync13(workspaceConfig))
|
|
4529
4777
|
return workspaceConfig;
|
|
4530
|
-
const altConfig =
|
|
4531
|
-
if (
|
|
4778
|
+
const altConfig = join6(cwd, "mcp_config.json");
|
|
4779
|
+
if (existsSync13(altConfig))
|
|
4532
4780
|
return altConfig;
|
|
4533
4781
|
return workspaceConfig;
|
|
4534
4782
|
}
|
|
@@ -4549,17 +4797,16 @@ class McpManager {
|
|
|
4549
4797
|
|
|
4550
4798
|
// src/storage/sqlite-store.ts
|
|
4551
4799
|
import { Database as Database3 } from "bun:sqlite";
|
|
4552
|
-
import { existsSync as
|
|
4553
|
-
import { dirname as
|
|
4554
|
-
import { homedir as homedir5 } from "os";
|
|
4800
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
4801
|
+
import { dirname as dirname7 } from "path";
|
|
4555
4802
|
class SqliteThreadStore {
|
|
4556
4803
|
db;
|
|
4557
4804
|
constructor(dbPath) {
|
|
4558
4805
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
4559
4806
|
if (effectivePath !== ":memory:") {
|
|
4560
|
-
const dir =
|
|
4561
|
-
if (!
|
|
4562
|
-
|
|
4807
|
+
const dir = dirname7(effectivePath);
|
|
4808
|
+
if (!existsSync14(dir)) {
|
|
4809
|
+
mkdirSync8(dir, { recursive: true });
|
|
4563
4810
|
}
|
|
4564
4811
|
}
|
|
4565
4812
|
this.db = new Database3(effectivePath);
|
|
@@ -4568,7 +4815,7 @@ class SqliteThreadStore {
|
|
|
4568
4815
|
this.initSchema();
|
|
4569
4816
|
}
|
|
4570
4817
|
getDefaultDbPath() {
|
|
4571
|
-
return
|
|
4818
|
+
return getThreadsDbPath();
|
|
4572
4819
|
}
|
|
4573
4820
|
initSchema() {
|
|
4574
4821
|
this.db.exec(`
|
|
@@ -4793,9 +5040,9 @@ class SessionPersistenceManager {
|
|
|
4793
5040
|
}
|
|
4794
5041
|
|
|
4795
5042
|
// src/skills/loader.ts
|
|
4796
|
-
import { existsSync as
|
|
4797
|
-
import { resolve as resolve13, join as
|
|
4798
|
-
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";
|
|
4799
5046
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
4800
5047
|
|
|
4801
5048
|
class SkillsLoader {
|
|
@@ -4865,16 +5112,16 @@ class SkillsLoader {
|
|
|
4865
5112
|
resolve13(cwd, "skills")
|
|
4866
5113
|
];
|
|
4867
5114
|
for (const cand of candidates) {
|
|
4868
|
-
if (
|
|
5115
|
+
if (existsSync15(cand) && !roots.includes(cand)) {
|
|
4869
5116
|
roots.push(cand);
|
|
4870
5117
|
}
|
|
4871
5118
|
}
|
|
4872
5119
|
}
|
|
4873
5120
|
if (this.includeGlobal) {
|
|
4874
|
-
roots.push(
|
|
5121
|
+
roots.push(getGlobalSkillsDir(), resolve13(homedir3(), ".gemini", "config", "skills"));
|
|
4875
5122
|
}
|
|
4876
5123
|
roots.push(...this.customRoots.map((r) => resolve13(r)));
|
|
4877
|
-
return roots.filter((r) =>
|
|
5124
|
+
return roots.filter((r) => existsSync15(r));
|
|
4878
5125
|
}
|
|
4879
5126
|
discoverSkills(cwd, options) {
|
|
4880
5127
|
return this.listSkills(cwd, options);
|
|
@@ -4890,12 +5137,12 @@ class SkillsLoader {
|
|
|
4890
5137
|
const discovered = new Map;
|
|
4891
5138
|
for (const root of roots) {
|
|
4892
5139
|
try {
|
|
4893
|
-
const entries =
|
|
5140
|
+
const entries = readdirSync5(root, { withFileTypes: true });
|
|
4894
5141
|
for (const entry of entries) {
|
|
4895
5142
|
if (entry.isDirectory()) {
|
|
4896
|
-
const skillDir =
|
|
4897
|
-
const skillFilePath =
|
|
4898
|
-
if (
|
|
5143
|
+
const skillDir = join7(root, entry.name);
|
|
5144
|
+
const skillFilePath = join7(skillDir, "SKILL.md");
|
|
5145
|
+
if (existsSync15(skillFilePath)) {
|
|
4899
5146
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
4900
5147
|
if (meta && !discovered.has(meta.name)) {
|
|
4901
5148
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -5018,149 +5265,288 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
5018
5265
|
}
|
|
5019
5266
|
|
|
5020
5267
|
// src/memories/store.ts
|
|
5021
|
-
import { existsSync as
|
|
5022
|
-
import { resolve as resolve14, dirname as
|
|
5023
|
-
import {
|
|
5024
|
-
|
|
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";
|
|
5025
5271
|
class MemoryStore {
|
|
5026
5272
|
globalPath;
|
|
5027
5273
|
customWorkspacePath;
|
|
5028
5274
|
constructor(options = {}) {
|
|
5029
|
-
this.globalPath = options.globalPath ||
|
|
5275
|
+
this.globalPath = options.globalPath || getGlobalMemoriesPath();
|
|
5030
5276
|
this.customWorkspacePath = options.workspacePath;
|
|
5031
5277
|
}
|
|
5032
|
-
|
|
5033
|
-
|
|
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
|
+
}
|
|
5034
5290
|
}
|
|
5035
|
-
|
|
5036
|
-
const
|
|
5037
|
-
const
|
|
5038
|
-
const
|
|
5039
|
-
|
|
5040
|
-
mkdirSync8(dir, { recursive: true });
|
|
5041
|
-
}
|
|
5042
|
-
const existingEntries = this.readMemoryFile(targetFile, scope);
|
|
5043
|
-
const normalized = params.content.trim();
|
|
5044
|
-
const duplicate = existingEntries.find((e) => e.category === params.category && e.content.toLowerCase() === normalized.toLowerCase());
|
|
5045
|
-
if (duplicate) {
|
|
5046
|
-
return duplicate;
|
|
5047
|
-
}
|
|
5048
|
-
const newEntry = {
|
|
5049
|
-
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
5050
|
-
category: params.category,
|
|
5051
|
-
content: normalized,
|
|
5052
|
-
scope,
|
|
5053
|
-
createdAt: Date.now()
|
|
5054
|
-
};
|
|
5055
|
-
existingEntries.push(newEntry);
|
|
5056
|
-
this.writeMemoryFile(targetFile, existingEntries);
|
|
5057
|
-
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}`;
|
|
5058
5296
|
}
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
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
|
+
};
|
|
5064
5368
|
}
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
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
|
+
}
|
|
5068
5386
|
try {
|
|
5069
|
-
const
|
|
5070
|
-
|
|
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(`
|
|
5071
5395
|
`);
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
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;
|
|
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;
|
|
5095
5425
|
}
|
|
5426
|
+
} else {
|
|
5427
|
+
bodyLines.push(line);
|
|
5096
5428
|
}
|
|
5097
|
-
return entries;
|
|
5098
|
-
} catch {
|
|
5099
|
-
return [];
|
|
5100
5429
|
}
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5430
|
+
return {
|
|
5431
|
+
type,
|
|
5432
|
+
name,
|
|
5433
|
+
description,
|
|
5434
|
+
modified,
|
|
5435
|
+
content: bodyLines.join(`
|
|
5436
|
+
`).trim(),
|
|
5437
|
+
filePath
|
|
5108
5438
|
};
|
|
5109
|
-
|
|
5110
|
-
|
|
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 {}
|
|
5111
5457
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
`;
|
|
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})`);
|
|
5121
5466
|
}
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
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 "";
|
|
5128
5486
|
}
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
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 {}
|
|
5135
5499
|
}
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
`;
|
|
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;
|
|
5142
5514
|
}
|
|
5143
|
-
|
|
5144
|
-
|
|
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;
|
|
5145
5529
|
}
|
|
5146
5530
|
formatMemoriesPrompt(cwd) {
|
|
5147
|
-
const
|
|
5148
|
-
if (
|
|
5531
|
+
const indexContent = this.loadMemoryIndex(cwd);
|
|
5532
|
+
if (!indexContent)
|
|
5149
5533
|
return "";
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
##
|
|
5153
|
-
<
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
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
|
+
`);
|
|
5158
5544
|
}
|
|
5159
5545
|
}
|
|
5160
5546
|
|
|
5161
5547
|
// src/worktree/manager.ts
|
|
5162
|
-
import { resolve as resolve16, join as
|
|
5163
|
-
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";
|
|
5164
5550
|
|
|
5165
5551
|
// src/worktree/git.ts
|
|
5166
5552
|
import { resolve as resolve15 } from "path";
|
|
@@ -5310,15 +5696,15 @@ class WorktreeManager {
|
|
|
5310
5696
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
5311
5697
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve16(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve16(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
5312
5698
|
const worktreeParent = resolve16(targetDir, "..");
|
|
5313
|
-
if (!
|
|
5314
|
-
|
|
5699
|
+
if (!existsSync17(worktreeParent)) {
|
|
5700
|
+
mkdirSync10(worktreeParent, { recursive: true });
|
|
5315
5701
|
}
|
|
5316
5702
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
5317
5703
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
5318
5704
|
if (!result.success) {
|
|
5319
5705
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
5320
5706
|
}
|
|
5321
|
-
const metaPath =
|
|
5707
|
+
const metaPath = join9(targetDir, "groupy-thread.json");
|
|
5322
5708
|
try {
|
|
5323
5709
|
writeFileSync6(metaPath, JSON.stringify({
|
|
5324
5710
|
version: 1,
|
|
@@ -5344,8 +5730,8 @@ class WorktreeManager {
|
|
|
5344
5730
|
return [];
|
|
5345
5731
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
5346
5732
|
return worktrees.map((wt) => {
|
|
5347
|
-
const metaPath =
|
|
5348
|
-
if (
|
|
5733
|
+
const metaPath = join9(wt.path, "groupy-thread.json");
|
|
5734
|
+
if (existsSync17(metaPath)) {
|
|
5349
5735
|
try {
|
|
5350
5736
|
const raw = JSON.parse(readFileSync11(metaPath, "utf8"));
|
|
5351
5737
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -5423,7 +5809,7 @@ class WorktreeManager {
|
|
|
5423
5809
|
}
|
|
5424
5810
|
}
|
|
5425
5811
|
// src/auth/oauth.ts
|
|
5426
|
-
import { randomBytes, createHash } from "crypto";
|
|
5812
|
+
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
5427
5813
|
import { exec } from "child_process";
|
|
5428
5814
|
class AuthClient {
|
|
5429
5815
|
store;
|
|
@@ -5576,7 +5962,7 @@ class AuthClient {
|
|
|
5576
5962
|
return randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9]/g, "").slice(0, 64);
|
|
5577
5963
|
}
|
|
5578
5964
|
generateCodeChallenge(verifier) {
|
|
5579
|
-
return
|
|
5965
|
+
return createHash2("sha256").update(verifier).digest("base64url");
|
|
5580
5966
|
}
|
|
5581
5967
|
}
|
|
5582
5968
|
// src/cli/ui/colors.ts
|
|
@@ -6043,7 +6429,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
|
|
|
6043
6429
|
// package.json
|
|
6044
6430
|
var package_default = {
|
|
6045
6431
|
name: "@pikaa-ai/pikaa",
|
|
6046
|
-
version: "0.3.
|
|
6432
|
+
version: "0.3.15",
|
|
6047
6433
|
description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
|
|
6048
6434
|
main: "./dist/index.js",
|
|
6049
6435
|
module: "./dist/index.js",
|
|
@@ -6121,7 +6507,7 @@ function getCliVersion(options = {}) {
|
|
|
6121
6507
|
}
|
|
6122
6508
|
|
|
6123
6509
|
// src/cli/ui/animation/banner-animation.ts
|
|
6124
|
-
import { homedir as
|
|
6510
|
+
import { homedir as homedir4 } from "os";
|
|
6125
6511
|
import { execSync } from "child_process";
|
|
6126
6512
|
var ROSE = "\x1B[38;2;205;105;74m";
|
|
6127
6513
|
var ROSE_DIM = "\x1B[38;2;120;60;45m";
|
|
@@ -6131,7 +6517,7 @@ var BOLD = "\x1B[1m";
|
|
|
6131
6517
|
var ITALIC = "\x1B[3m";
|
|
6132
6518
|
var RESET = "\x1B[0m";
|
|
6133
6519
|
function shortenPath(cwd) {
|
|
6134
|
-
const home =
|
|
6520
|
+
const home = homedir4();
|
|
6135
6521
|
if (cwd.startsWith(home)) {
|
|
6136
6522
|
return `~${cwd.slice(home.length).replace(/\\/g, "/")}`;
|
|
6137
6523
|
}
|
|
@@ -7509,8 +7895,8 @@ async function promptInteractiveList(config) {
|
|
|
7509
7895
|
}
|
|
7510
7896
|
|
|
7511
7897
|
// src/security/scanner.ts
|
|
7512
|
-
import { existsSync as
|
|
7513
|
-
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";
|
|
7514
7900
|
var SECURITY_RULES = [
|
|
7515
7901
|
{
|
|
7516
7902
|
id: "SEC-001",
|
|
@@ -7648,21 +8034,21 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
7648
8034
|
const findings = [];
|
|
7649
8035
|
let scannedCount = 0;
|
|
7650
8036
|
function walk(current) {
|
|
7651
|
-
if (scannedCount >= maxFiles || !
|
|
8037
|
+
if (scannedCount >= maxFiles || !existsSync18(current))
|
|
7652
8038
|
return;
|
|
7653
8039
|
let entries;
|
|
7654
8040
|
try {
|
|
7655
|
-
entries =
|
|
8041
|
+
entries = readdirSync7(current);
|
|
7656
8042
|
} catch {
|
|
7657
8043
|
return;
|
|
7658
8044
|
}
|
|
7659
8045
|
for (const entry of entries) {
|
|
7660
8046
|
if (scannedCount >= maxFiles)
|
|
7661
8047
|
break;
|
|
7662
|
-
const fullPath =
|
|
8048
|
+
const fullPath = join10(current, entry);
|
|
7663
8049
|
let stat;
|
|
7664
8050
|
try {
|
|
7665
|
-
stat =
|
|
8051
|
+
stat = statSync4(fullPath);
|
|
7666
8052
|
} catch {
|
|
7667
8053
|
continue;
|
|
7668
8054
|
}
|
|
@@ -7767,8 +8153,8 @@ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
|
|
|
7767
8153
|
var SQLITE_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
|
|
7768
8154
|
|
|
7769
8155
|
// src/init/project-analyzer.ts
|
|
7770
|
-
import { existsSync as
|
|
7771
|
-
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";
|
|
7772
8158
|
|
|
7773
8159
|
class ProjectAnalyzer {
|
|
7774
8160
|
cwd;
|
|
@@ -7786,8 +8172,8 @@ class ProjectAnalyzer {
|
|
|
7786
8172
|
const architectureNotes = [];
|
|
7787
8173
|
const codeConventions = [];
|
|
7788
8174
|
let description = readmeInfo.description;
|
|
7789
|
-
const pkgPath =
|
|
7790
|
-
if (
|
|
8175
|
+
const pkgPath = join11(this.cwd, "package.json");
|
|
8176
|
+
if (existsSync19(pkgPath)) {
|
|
7791
8177
|
try {
|
|
7792
8178
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
7793
8179
|
if (!description && pkg.description)
|
|
@@ -7860,8 +8246,8 @@ class ProjectAnalyzer {
|
|
|
7860
8246
|
}
|
|
7861
8247
|
} catch {}
|
|
7862
8248
|
}
|
|
7863
|
-
const tsconfigPath =
|
|
7864
|
-
if (
|
|
8249
|
+
const tsconfigPath = join11(this.cwd, "tsconfig.json");
|
|
8250
|
+
if (existsSync19(tsconfigPath)) {
|
|
7865
8251
|
try {
|
|
7866
8252
|
const tsconfig = JSON.parse(readFileSync13(tsconfigPath, "utf8"));
|
|
7867
8253
|
if (tsconfig.compilerOptions?.strict) {
|
|
@@ -7872,8 +8258,8 @@ class ProjectAnalyzer {
|
|
|
7872
8258
|
}
|
|
7873
8259
|
} catch {}
|
|
7874
8260
|
}
|
|
7875
|
-
const cargoPath =
|
|
7876
|
-
if (
|
|
8261
|
+
const cargoPath = join11(this.cwd, "Cargo.toml");
|
|
8262
|
+
if (existsSync19(cargoPath)) {
|
|
7877
8263
|
try {
|
|
7878
8264
|
commands.dev = commands.dev || "cargo run";
|
|
7879
8265
|
commands.build = commands.build || "cargo build";
|
|
@@ -7882,8 +8268,8 @@ class ProjectAnalyzer {
|
|
|
7882
8268
|
frameworks.push("Rust Cargo");
|
|
7883
8269
|
} catch {}
|
|
7884
8270
|
}
|
|
7885
|
-
const goModPath =
|
|
7886
|
-
if (
|
|
8271
|
+
const goModPath = join11(this.cwd, "go.mod");
|
|
8272
|
+
if (existsSync19(goModPath)) {
|
|
7887
8273
|
try {
|
|
7888
8274
|
commands.dev = commands.dev || "go run .";
|
|
7889
8275
|
commands.build = commands.build || "go build ./...";
|
|
@@ -7892,37 +8278,37 @@ class ProjectAnalyzer {
|
|
|
7892
8278
|
frameworks.push("Go Modules");
|
|
7893
8279
|
} catch {}
|
|
7894
8280
|
}
|
|
7895
|
-
const pyprojectPath =
|
|
7896
|
-
const requirementsPath =
|
|
7897
|
-
if (
|
|
8281
|
+
const pyprojectPath = join11(this.cwd, "pyproject.toml");
|
|
8282
|
+
const requirementsPath = join11(this.cwd, "requirements.txt");
|
|
8283
|
+
if (existsSync19(pyprojectPath) || existsSync19(requirementsPath)) {
|
|
7898
8284
|
commands.test = commands.test || "pytest";
|
|
7899
8285
|
commands.lint = commands.lint || "ruff check .";
|
|
7900
|
-
if (
|
|
8286
|
+
if (existsSync19(join11(this.cwd, "uv.lock"))) {
|
|
7901
8287
|
frameworks.push("uv");
|
|
7902
8288
|
commands.test = "uv run pytest";
|
|
7903
|
-
} else if (
|
|
8289
|
+
} else if (existsSync19(join11(this.cwd, "poetry.lock"))) {
|
|
7904
8290
|
frameworks.push("Poetry");
|
|
7905
8291
|
commands.test = "poetry run pytest";
|
|
7906
8292
|
}
|
|
7907
8293
|
}
|
|
7908
|
-
if (
|
|
8294
|
+
if (existsSync19(join11(this.cwd, "Dockerfile"))) {
|
|
7909
8295
|
infrastructure.push("Docker");
|
|
7910
8296
|
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
7911
8297
|
commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
|
|
7912
8298
|
}
|
|
7913
|
-
if (
|
|
8299
|
+
if (existsSync19(join11(this.cwd, "nginx.conf"))) {
|
|
7914
8300
|
infrastructure.push("Nginx");
|
|
7915
8301
|
}
|
|
7916
|
-
if (
|
|
8302
|
+
if (existsSync19(join11(this.cwd, "src/api.ts")) || existsSync19(join11(this.cwd, "src/api"))) {
|
|
7917
8303
|
architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
|
|
7918
8304
|
}
|
|
7919
|
-
if (
|
|
8305
|
+
if (existsSync19(join11(this.cwd, "src/components"))) {
|
|
7920
8306
|
architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
|
|
7921
8307
|
}
|
|
7922
|
-
if (
|
|
8308
|
+
if (existsSync19(join11(this.cwd, "src/types.ts")) || existsSync19(join11(this.cwd, "src/types"))) {
|
|
7923
8309
|
architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
|
|
7924
8310
|
}
|
|
7925
|
-
if (
|
|
8311
|
+
if (existsSync19(join11(this.cwd, ".env.example"))) {
|
|
7926
8312
|
architectureNotes.push("Environment configuration template is in `.env.example`.");
|
|
7927
8313
|
}
|
|
7928
8314
|
if (commands.typecheck || commands.lint || commands.test) {
|
|
@@ -7939,7 +8325,7 @@ class ProjectAnalyzer {
|
|
|
7939
8325
|
let hasExistingInstructions = false;
|
|
7940
8326
|
let existingInstructionFile;
|
|
7941
8327
|
for (const f of instructionFiles) {
|
|
7942
|
-
if (
|
|
8328
|
+
if (existsSync19(join11(this.cwd, f))) {
|
|
7943
8329
|
hasExistingInstructions = true;
|
|
7944
8330
|
existingInstructionFile = f;
|
|
7945
8331
|
break;
|
|
@@ -8020,8 +8406,8 @@ class ProjectAnalyzer {
|
|
|
8020
8406
|
extractReadmeMetadata() {
|
|
8021
8407
|
const readmeFiles = ["README.md", "readme.md", "README.MD"];
|
|
8022
8408
|
for (const file of readmeFiles) {
|
|
8023
|
-
const fullPath =
|
|
8024
|
-
if (
|
|
8409
|
+
const fullPath = join11(this.cwd, file);
|
|
8410
|
+
if (existsSync19(fullPath)) {
|
|
8025
8411
|
try {
|
|
8026
8412
|
const content = readFileSync13(fullPath, "utf8");
|
|
8027
8413
|
const lines = content.split(`
|
|
@@ -8046,8 +8432,8 @@ class ProjectAnalyzer {
|
|
|
8046
8432
|
return {};
|
|
8047
8433
|
}
|
|
8048
8434
|
detectProjectName() {
|
|
8049
|
-
const pkgPath =
|
|
8050
|
-
if (
|
|
8435
|
+
const pkgPath = join11(this.cwd, "package.json");
|
|
8436
|
+
if (existsSync19(pkgPath)) {
|
|
8051
8437
|
try {
|
|
8052
8438
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
8053
8439
|
if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
|
|
@@ -8055,73 +8441,73 @@ class ProjectAnalyzer {
|
|
|
8055
8441
|
}
|
|
8056
8442
|
} catch {}
|
|
8057
8443
|
}
|
|
8058
|
-
const cargoPath =
|
|
8059
|
-
if (
|
|
8444
|
+
const cargoPath = join11(this.cwd, "Cargo.toml");
|
|
8445
|
+
if (existsSync19(cargoPath)) {
|
|
8060
8446
|
try {
|
|
8061
8447
|
const match = readFileSync13(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
|
|
8062
8448
|
if (match?.[1])
|
|
8063
8449
|
return match[1];
|
|
8064
8450
|
} catch {}
|
|
8065
8451
|
}
|
|
8066
|
-
const goModPath =
|
|
8067
|
-
if (
|
|
8452
|
+
const goModPath = join11(this.cwd, "go.mod");
|
|
8453
|
+
if (existsSync19(goModPath)) {
|
|
8068
8454
|
try {
|
|
8069
8455
|
const match = readFileSync13(goModPath, "utf8").match(/module\s+([^\s]+)/);
|
|
8070
8456
|
if (match?.[1])
|
|
8071
|
-
return
|
|
8457
|
+
return basename3(match[1]);
|
|
8072
8458
|
} catch {}
|
|
8073
8459
|
}
|
|
8074
|
-
return
|
|
8460
|
+
return basename3(this.cwd);
|
|
8075
8461
|
}
|
|
8076
8462
|
detectLanguages() {
|
|
8077
8463
|
const langs = new Set;
|
|
8078
|
-
if (
|
|
8464
|
+
if (existsSync19(join11(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
|
|
8079
8465
|
langs.add("TypeScript");
|
|
8080
8466
|
}
|
|
8081
|
-
if (
|
|
8467
|
+
if (existsSync19(join11(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
|
|
8082
8468
|
langs.add("JavaScript");
|
|
8083
8469
|
}
|
|
8084
|
-
if (
|
|
8470
|
+
if (existsSync19(join11(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
|
|
8085
8471
|
langs.add("Rust");
|
|
8086
8472
|
}
|
|
8087
|
-
if (
|
|
8473
|
+
if (existsSync19(join11(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
|
|
8088
8474
|
langs.add("Go");
|
|
8089
8475
|
}
|
|
8090
|
-
if (
|
|
8476
|
+
if (existsSync19(join11(this.cwd, "pyproject.toml")) || existsSync19(join11(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
|
|
8091
8477
|
langs.add("Python");
|
|
8092
8478
|
}
|
|
8093
|
-
if (
|
|
8479
|
+
if (existsSync19(join11(this.cwd, "pom.xml")) || existsSync19(join11(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
|
|
8094
8480
|
langs.add("Java");
|
|
8095
8481
|
}
|
|
8096
|
-
if (
|
|
8482
|
+
if (existsSync19(join11(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
|
|
8097
8483
|
langs.add("C/C++");
|
|
8098
8484
|
}
|
|
8099
8485
|
return Array.from(langs);
|
|
8100
8486
|
}
|
|
8101
8487
|
detectPackageManager() {
|
|
8102
|
-
if (
|
|
8488
|
+
if (existsSync19(join11(this.cwd, "bun.lockb")) || existsSync19(join11(this.cwd, "bun.lock")))
|
|
8103
8489
|
return "bun";
|
|
8104
|
-
if (
|
|
8490
|
+
if (existsSync19(join11(this.cwd, "pnpm-lock.yaml")))
|
|
8105
8491
|
return "pnpm";
|
|
8106
|
-
if (
|
|
8492
|
+
if (existsSync19(join11(this.cwd, "yarn.lock")))
|
|
8107
8493
|
return "yarn";
|
|
8108
|
-
if (
|
|
8494
|
+
if (existsSync19(join11(this.cwd, "package-lock.json")))
|
|
8109
8495
|
return "npm";
|
|
8110
|
-
if (
|
|
8496
|
+
if (existsSync19(join11(this.cwd, "Cargo.lock")) || existsSync19(join11(this.cwd, "Cargo.toml")))
|
|
8111
8497
|
return "cargo";
|
|
8112
|
-
if (
|
|
8498
|
+
if (existsSync19(join11(this.cwd, "uv.lock")))
|
|
8113
8499
|
return "uv";
|
|
8114
|
-
if (
|
|
8500
|
+
if (existsSync19(join11(this.cwd, "poetry.lock")))
|
|
8115
8501
|
return "poetry";
|
|
8116
|
-
if (
|
|
8502
|
+
if (existsSync19(join11(this.cwd, "go.sum")) || existsSync19(join11(this.cwd, "go.mod")))
|
|
8117
8503
|
return "go";
|
|
8118
|
-
if (
|
|
8504
|
+
if (existsSync19(join11(this.cwd, "package.json")))
|
|
8119
8505
|
return "npm";
|
|
8120
8506
|
return;
|
|
8121
8507
|
}
|
|
8122
8508
|
hasFileWithExtension(...exts) {
|
|
8123
8509
|
try {
|
|
8124
|
-
const entries =
|
|
8510
|
+
const entries = readdirSync8(this.cwd);
|
|
8125
8511
|
return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
|
|
8126
8512
|
} catch {
|
|
8127
8513
|
return false;
|
|
@@ -8129,16 +8515,16 @@ class ProjectAnalyzer {
|
|
|
8129
8515
|
}
|
|
8130
8516
|
}
|
|
8131
8517
|
// src/init/init-command.ts
|
|
8132
|
-
import { existsSync as
|
|
8133
|
-
import { join as
|
|
8518
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync7 } from "fs";
|
|
8519
|
+
import { join as join12 } from "path";
|
|
8134
8520
|
function runProjectInit(options = {}) {
|
|
8135
8521
|
const cwd = options.cwd || process.cwd();
|
|
8136
8522
|
const filename = options.filename || "AGENTS.md";
|
|
8137
|
-
const targetPath =
|
|
8523
|
+
const targetPath = join12(cwd, filename);
|
|
8138
8524
|
const analyzer = new ProjectAnalyzer(cwd);
|
|
8139
8525
|
const analysis = analyzer.analyze();
|
|
8140
8526
|
const content = analyzer.generateAgentsMarkdown(analysis);
|
|
8141
|
-
const alreadyExists =
|
|
8527
|
+
const alreadyExists = existsSync20(targetPath);
|
|
8142
8528
|
writeFileSync7(targetPath, content, "utf8");
|
|
8143
8529
|
return {
|
|
8144
8530
|
success: true,
|
|
@@ -8513,7 +8899,7 @@ Browser authorization opened automatically. If not, open:`);
|
|
|
8513
8899
|
console.log(style.dim(`Waiting for authorization callback on port 1455 ...`));
|
|
8514
8900
|
const creds = await waitForToken();
|
|
8515
8901
|
console.log(style.green(`
|
|
8516
|
-
\u2713 Authentication Successful! Token saved to ~/.
|
|
8902
|
+
\u2713 Authentication Successful! Token saved to ~/.pikaa/credentials.json`));
|
|
8517
8903
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
8518
8904
|
} catch (err) {
|
|
8519
8905
|
console.log(style.yellow(`
|
|
@@ -8531,7 +8917,7 @@ Falling back to Direct Terminal Login:`));
|
|
|
8531
8917
|
password
|
|
8532
8918
|
});
|
|
8533
8919
|
console.log(style.green(`
|
|
8534
|
-
\u2713 Successfully logged in! Token saved to ~/.
|
|
8920
|
+
\u2713 Successfully logged in! Token saved to ~/.pikaa/credentials.json`));
|
|
8535
8921
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
8536
8922
|
} catch (directErr) {
|
|
8537
8923
|
console.error(style.red(`
|
|
@@ -8744,7 +9130,7 @@ async function handleSkillsCommand(ctx, args) {
|
|
|
8744
9130
|
const skills = loader.listSkills(ctx.session.cwd, { includeDisabled: true });
|
|
8745
9131
|
if (skills.length === 0) {
|
|
8746
9132
|
console.log(style.dim(`
|
|
8747
|
-
No domain skills discovered in .agents/skills/ or ~/.
|
|
9133
|
+
No domain skills discovered in .agents/skills/ or ~/.pikaa/skills/
|
|
8748
9134
|
`));
|
|
8749
9135
|
return;
|
|
8750
9136
|
}
|
|
@@ -8800,17 +9186,51 @@ async function handleSkillsCommand(ctx, args) {
|
|
|
8800
9186
|
function printMemories(ctx) {
|
|
8801
9187
|
const store2 = ctx.memoryStore;
|
|
8802
9188
|
if (!store2) {
|
|
8803
|
-
console.log(style.yellow(
|
|
9189
|
+
console.log(style.yellow(`
|
|
9190
|
+
Memory store not active.
|
|
9191
|
+
`));
|
|
8804
9192
|
return;
|
|
8805
9193
|
}
|
|
8806
|
-
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
|
+
};
|
|
8807
9220
|
console.log();
|
|
8808
|
-
|
|
8809
|
-
|
|
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}`);
|
|
8810
9228
|
} else {
|
|
8811
|
-
console.log(
|
|
8812
|
-
for (const
|
|
8813
|
-
|
|
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}`);
|
|
8814
9234
|
}
|
|
8815
9235
|
}
|
|
8816
9236
|
console.log();
|
|
@@ -9190,7 +9610,7 @@ function printReleaseNotes() {
|
|
|
9190
9610
|
"",
|
|
9191
9611
|
" " + BOLD2 + WHITE2 + "\uD83D\uDE80 What's New in " + version + " (Latest)" + RESET2,
|
|
9192
9612
|
" " + ROSE2 + "\u2022" + RESET2 + " " + WHITE2 + BOLD2 + "Persistent Default AI Model" + RESET2 + ": Switch via " + ROSE2 + "/model" + RESET2 + " and save",
|
|
9193
|
-
" preference across sessions in ~/.
|
|
9613
|
+
" preference across sessions in ~/.pikaa/credentials.json.",
|
|
9194
9614
|
" " + ROSE2 + "\u2022" + RESET2 + " " + WHITE2 + BOLD2 + "Real-time Git Branch Detection" + RESET2 + ": Header displays active branch",
|
|
9195
9615
|
" (\uE0A0 main) alongside user subscription tier (Groupy Pro / Max).",
|
|
9196
9616
|
" " + ROSE2 + "\u2022" + RESET2 + " " + WHITE2 + BOLD2 + "Claude Code Terminal UI Parity" + RESET2 + ": Authentic pixel emblem, fieldset",
|
|
@@ -9569,9 +9989,9 @@ class MarkdownHighlighter {
|
|
|
9569
9989
|
}
|
|
9570
9990
|
|
|
9571
9991
|
// src/cli/update-checker.ts
|
|
9572
|
-
import { existsSync as
|
|
9573
|
-
import { homedir as
|
|
9574
|
-
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";
|
|
9575
9995
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9576
9996
|
function parseSemver(v) {
|
|
9577
9997
|
const clean = v.replace(/^v/, "").trim();
|
|
@@ -9592,8 +10012,8 @@ function isNewerVersion(current, remote) {
|
|
|
9592
10012
|
return remPatch > curPatch;
|
|
9593
10013
|
}
|
|
9594
10014
|
function getUpdateCachePath() {
|
|
9595
|
-
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME ||
|
|
9596
|
-
return
|
|
10015
|
+
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join13(homedir5(), ".pikaa");
|
|
10016
|
+
return join13(baseDir, "update-cache.json");
|
|
9597
10017
|
}
|
|
9598
10018
|
async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
|
|
9599
10019
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
@@ -9626,7 +10046,7 @@ async function checkForUpdates(options = {}) {
|
|
|
9626
10046
|
const cachePath = options.cachePath || getUpdateCachePath();
|
|
9627
10047
|
const now = Date.now();
|
|
9628
10048
|
let cached = null;
|
|
9629
|
-
if (!options.force &&
|
|
10049
|
+
if (!options.force && existsSync21(cachePath)) {
|
|
9630
10050
|
try {
|
|
9631
10051
|
const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
|
|
9632
10052
|
if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
|
|
@@ -9656,9 +10076,9 @@ async function checkForUpdates(options = {}) {
|
|
|
9656
10076
|
return null;
|
|
9657
10077
|
}
|
|
9658
10078
|
try {
|
|
9659
|
-
const parentDir =
|
|
9660
|
-
if (!
|
|
9661
|
-
|
|
10079
|
+
const parentDir = join13(cachePath, "..");
|
|
10080
|
+
if (!existsSync21(parentDir)) {
|
|
10081
|
+
mkdirSync11(parentDir, { recursive: true });
|
|
9662
10082
|
}
|
|
9663
10083
|
const cacheData = {
|
|
9664
10084
|
lastChecked: now,
|
|
@@ -10080,7 +10500,7 @@ async function main() {
|
|
|
10080
10500
|
resolve21(cwd, "mcp_config.json")
|
|
10081
10501
|
].filter(Boolean);
|
|
10082
10502
|
for (const cfg of candidateConfigs) {
|
|
10083
|
-
if (
|
|
10503
|
+
if (existsSync22(cfg)) {
|
|
10084
10504
|
try {
|
|
10085
10505
|
await mcpManager.loadConfigFile(cfg);
|
|
10086
10506
|
mcpManager.registerToolsIntoRouter(tools4);
|
|
@@ -10194,7 +10614,7 @@ Open the following link in your browser to complete authorization:`);
|
|
|
10194
10614
|
Waiting for browser callback on http://localhost:1455/auth/callback ...`));
|
|
10195
10615
|
const creds = await waitForToken();
|
|
10196
10616
|
console.log(style.green(`
|
|
10197
|
-
\u2713 Authentication Successful! Token saved to ~/.
|
|
10617
|
+
\u2713 Authentication Successful! Token saved to ~/.pikaa/credentials.json`));
|
|
10198
10618
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
10199
10619
|
} catch (err) {
|
|
10200
10620
|
console.log(style.yellow(`
|
|
@@ -10213,7 +10633,7 @@ Falling back to Direct Terminal Login:`));
|
|
|
10213
10633
|
password: password.trim()
|
|
10214
10634
|
});
|
|
10215
10635
|
console.log(style.green(`
|
|
10216
|
-
\u2713 Successfully logged in! Token saved to ~/.
|
|
10636
|
+
\u2713 Successfully logged in! Token saved to ~/.pikaa/credentials.json`));
|
|
10217
10637
|
console.log(style.dim(` Gateway Base URL: ${creds.baseUrl}`));
|
|
10218
10638
|
} catch (directErr) {
|
|
10219
10639
|
rl.close();
|
|
@@ -10287,7 +10707,7 @@ function printSkillsList(loader, cwd) {
|
|
|
10287
10707
|
const skills = loader.discoverSkills(cwd);
|
|
10288
10708
|
console.log();
|
|
10289
10709
|
if (skills.length === 0) {
|
|
10290
|
-
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/"));
|
|
10291
10711
|
} else {
|
|
10292
10712
|
console.log(style.bold("Discovered Domain Skills:"));
|
|
10293
10713
|
for (const s of skills) {
|