panopticon-cli 0.5.1 → 0.5.3
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/{agents-5OPQKM5K.js → agents-DMPT32H7.js} +7 -6
- package/dist/{chunk-OWHXCGVO.js → chunk-2V2DQ3IX.js} +378 -101
- package/dist/chunk-2V2DQ3IX.js.map +1 -0
- package/dist/{chunk-F5555J3A.js → chunk-4HST45MO.js} +13 -27
- package/dist/chunk-4HST45MO.js.map +1 -0
- package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
- package/dist/chunk-6N2KBSJA.js +452 -0
- package/dist/chunk-6N2KBSJA.js.map +1 -0
- package/dist/{chunk-VHKSS7QX.js → chunk-D67AQTHF.js} +21 -19
- package/dist/chunk-D67AQTHF.js.map +1 -0
- package/dist/{chunk-4YSYJ4HM.js → chunk-DFNVHK3N.js} +2 -2
- package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
- package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
- package/dist/chunk-HRU7S4TA.js.map +1 -0
- package/dist/{chunk-NLQRED36.js → chunk-KBHRXV5T.js} +3 -3
- package/dist/chunk-KBHRXV5T.js.map +1 -0
- package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
- package/dist/chunk-MOPGR3CL.js.map +1 -0
- package/dist/{chunk-2V4NF7J2.js → chunk-RLZQB7HS.js} +2 -2
- package/dist/chunk-RLZQB7HS.js.map +1 -0
- package/dist/{chunk-76F6DSVS.js → chunk-T7BBPDEJ.js} +11 -7
- package/dist/chunk-T7BBPDEJ.js.map +1 -0
- package/dist/chunk-USYP2SBE.js +317 -0
- package/dist/chunk-USYP2SBE.js.map +1 -0
- package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
- package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
- package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
- package/dist/chunk-ZP6EWSZV.js.map +1 -0
- package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
- package/dist/chunk-ZTYHZMEC.js.map +1 -0
- package/dist/cli/index.js +1381 -777
- package/dist/cli/index.js.map +1 -1
- package/dist/config-yaml-OVZLKFMA.js +18 -0
- package/dist/dashboard/prompts/merge-agent.md +7 -5
- package/dist/dashboard/prompts/review-agent.md +12 -1
- package/dist/dashboard/prompts/test-agent.md +3 -1
- package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
- package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/dashboard/public/index.html +5 -3
- package/dist/dashboard/server.js +4647 -2723
- package/dist/{feedback-writer-VRMMWWTW.js → feedback-writer-T43PI5S2.js} +2 -2
- package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{projects-CFX3RTDL.js → projects-KVM3MN3Y.js} +2 -2
- package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
- package/dist/{remote-workspace-7FPGF2RM.js → remote-workspace-XX6ARE6I.js} +3 -3
- package/dist/{review-status-TDPSOU5J.js → review-status-XKUKZF6J.js} +3 -2
- package/dist/{specialist-context-WGUUYDWY.js → specialist-context-53AWO6AE.js} +6 -5
- package/dist/{specialist-context-WGUUYDWY.js.map → specialist-context-53AWO6AE.js.map} +1 -1
- package/dist/{specialist-logs-XJB5TCKJ.js → specialist-logs-QREUJ4HN.js} +6 -5
- package/dist/{specialists-5LBRHYFA.js → specialists-2DBBXRCK.js} +6 -5
- package/dist/{traefik-WFMQX2LY.js → traefik-5GL3Q7DJ.js} +3 -3
- package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
- package/dist/{workspace-manager-E434Z45T.js → workspace-manager-ALBR62AS.js} +5 -5
- package/dist/workspace-manager-ALBR62AS.js.map +1 -0
- package/package.json +1 -1
- package/scripts/record-cost-event.js +8424 -42
- package/scripts/work-agent-stop-hook +221 -24
- package/dist/chunk-2V4NF7J2.js.map +0 -1
- package/dist/chunk-76F6DSVS.js.map +0 -1
- package/dist/chunk-CWELWPWQ.js.map +0 -1
- package/dist/chunk-F5555J3A.js.map +0 -1
- package/dist/chunk-FTCPTHIJ.js.map +0 -1
- package/dist/chunk-HJSM6E6U.js.map +0 -1
- package/dist/chunk-NLQRED36.js.map +0 -1
- package/dist/chunk-OWHXCGVO.js.map +0 -1
- package/dist/chunk-PELXV435.js.map +0 -1
- package/dist/chunk-VHKSS7QX.js.map +0 -1
- package/dist/chunk-YGJ54GW2.js +0 -96
- package/dist/chunk-YGJ54GW2.js.map +0 -1
- package/dist/dashboard/public/assets/index-Ce6q21Fm.js +0 -743
- package/dist/dashboard/public/assets/index-NzpI0ItZ.css +0 -32
- package/dist/git-utils-I2UDKNZH.js +0 -131
- package/dist/git-utils-I2UDKNZH.js.map +0 -1
- /package/dist/{agents-5OPQKM5K.js.map → agents-DMPT32H7.js.map} +0 -0
- /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
- /package/dist/{chunk-4YSYJ4HM.js.map → chunk-DFNVHK3N.js.map} +0 -0
- /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
- /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
- /package/dist/{feedback-writer-VRMMWWTW.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
- /package/dist/{projects-CFX3RTDL.js.map → hume-CKJJ3OUU.js.map} +0 -0
- /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
- /package/dist/{review-status-TDPSOU5J.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
- /package/dist/{remote-workspace-7FPGF2RM.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
- /package/dist/{specialist-logs-XJB5TCKJ.js.map → review-status-XKUKZF6J.js.map} +0 -0
- /package/dist/{specialists-5LBRHYFA.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
- /package/dist/{traefik-WFMQX2LY.js.map → specialists-2DBBXRCK.js.map} +0 -0
- /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
- /package/dist/{workspace-manager-E434Z45T.js.map → tunnel-BKC7KLBX.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
checkBudget,
|
|
3
4
|
checkSpecialistQueue,
|
|
4
5
|
clearSessionId,
|
|
5
6
|
completeSpecialistTask,
|
|
7
|
+
createBudget,
|
|
8
|
+
deleteBudget,
|
|
9
|
+
formatCost,
|
|
10
|
+
generateReport,
|
|
11
|
+
getAllBudgets,
|
|
12
|
+
getAllProjectSpecialistStatuses,
|
|
6
13
|
getAllSpecialistStatus,
|
|
14
|
+
getDailySummary,
|
|
7
15
|
getEnabledSpecialists,
|
|
16
|
+
getMonthlySummary,
|
|
8
17
|
getNextSpecialistTask,
|
|
9
18
|
getProjectDirs,
|
|
10
19
|
getSessionFiles,
|
|
@@ -12,38 +21,47 @@ import {
|
|
|
12
21
|
getSpecialistMetadata,
|
|
13
22
|
getSpecialistStatus,
|
|
14
23
|
getTmuxSessionName,
|
|
24
|
+
getWeeklySummary,
|
|
25
|
+
init_cost,
|
|
15
26
|
init_jsonl_parser,
|
|
16
27
|
init_specialists,
|
|
17
28
|
initializeEnabledSpecialists,
|
|
18
29
|
isRunning,
|
|
19
30
|
parseClaudeSession,
|
|
31
|
+
readIssueCosts,
|
|
32
|
+
readTodayCosts,
|
|
20
33
|
recordWake,
|
|
21
34
|
setSessionId,
|
|
35
|
+
summarizeCosts,
|
|
22
36
|
wakeSpecialist,
|
|
23
37
|
wakeSpecialistOrQueue,
|
|
24
38
|
wakeSpecialistWithTask
|
|
25
|
-
} from "../chunk-
|
|
39
|
+
} from "../chunk-2V2DQ3IX.js";
|
|
26
40
|
import {
|
|
27
41
|
cleanupTemplateFiles,
|
|
28
42
|
ensureProjectCerts,
|
|
29
43
|
generatePanopticonTraefikConfig,
|
|
30
44
|
generateTlsConfig
|
|
31
|
-
} from "../chunk-
|
|
45
|
+
} from "../chunk-KBHRXV5T.js";
|
|
32
46
|
import {
|
|
33
47
|
applyProjectTemplateOverlay,
|
|
34
48
|
createWorkspace,
|
|
35
49
|
init_skills_merge,
|
|
36
50
|
init_workspace_manager,
|
|
37
51
|
mergeSkillsIntoWorkspace,
|
|
38
|
-
removeWorkspace
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
import "../chunk-
|
|
52
|
+
removeWorkspace
|
|
53
|
+
} from "../chunk-4HST45MO.js";
|
|
54
|
+
import "../chunk-ZTYHZMEC.js";
|
|
55
|
+
import "../chunk-HOGYHJ2G.js";
|
|
56
|
+
import {
|
|
57
|
+
init_workspace_config,
|
|
58
|
+
replacePlaceholders
|
|
59
|
+
} from "../chunk-MOPGR3CL.js";
|
|
42
60
|
import {
|
|
43
61
|
init_remote_agents,
|
|
44
62
|
isRemoteAgentRunning,
|
|
45
63
|
spawnRemoteAgent
|
|
46
|
-
} from "../chunk-
|
|
64
|
+
} from "../chunk-565HZ6VV.js";
|
|
47
65
|
import {
|
|
48
66
|
autoRecoverAgents,
|
|
49
67
|
detectCrashedAgents,
|
|
@@ -66,7 +84,7 @@ import {
|
|
|
66
84
|
saveSessionId,
|
|
67
85
|
spawnAgent,
|
|
68
86
|
stopAgent
|
|
69
|
-
} from "../chunk-
|
|
87
|
+
} from "../chunk-D67AQTHF.js";
|
|
70
88
|
import {
|
|
71
89
|
checkHook,
|
|
72
90
|
clearHook,
|
|
@@ -83,7 +101,7 @@ import {
|
|
|
83
101
|
sendKeysAsync,
|
|
84
102
|
sendMail,
|
|
85
103
|
sessionExists
|
|
86
|
-
} from "../chunk-
|
|
104
|
+
} from "../chunk-HRU7S4TA.js";
|
|
87
105
|
import {
|
|
88
106
|
createShadowState,
|
|
89
107
|
getPendingSyncCount,
|
|
@@ -106,12 +124,15 @@ import {
|
|
|
106
124
|
import {
|
|
107
125
|
createExeProvider,
|
|
108
126
|
init_exe_provider
|
|
109
|
-
} from "../chunk-
|
|
127
|
+
} from "../chunk-ZDNQFWR5.js";
|
|
110
128
|
import {
|
|
129
|
+
closeDatabase,
|
|
130
|
+
getDatabase,
|
|
131
|
+
init_database,
|
|
111
132
|
init_review_status,
|
|
112
133
|
loadReviewStatuses,
|
|
113
134
|
saveReviewStatuses
|
|
114
|
-
} from "../chunk-
|
|
135
|
+
} from "../chunk-6N2KBSJA.js";
|
|
115
136
|
import "../chunk-JQBV3Q2W.js";
|
|
116
137
|
import {
|
|
117
138
|
addAlias,
|
|
@@ -129,19 +150,17 @@ import {
|
|
|
129
150
|
restoreBackup,
|
|
130
151
|
syncHooks,
|
|
131
152
|
syncStatusline
|
|
132
|
-
} from "../chunk-
|
|
153
|
+
} from "../chunk-DFNVHK3N.js";
|
|
133
154
|
import "../chunk-AQXETQHW.js";
|
|
134
155
|
import {
|
|
135
156
|
createTracker,
|
|
136
157
|
createTrackerFromConfig,
|
|
137
158
|
init_factory
|
|
138
|
-
} from "../chunk-
|
|
159
|
+
} from "../chunk-T7BBPDEJ.js";
|
|
139
160
|
import {
|
|
140
|
-
init_config_yaml,
|
|
141
161
|
init_settings,
|
|
142
|
-
loadConfig as loadConfig2,
|
|
143
162
|
loadSettings
|
|
144
|
-
} from "../chunk-
|
|
163
|
+
} from "../chunk-USYP2SBE.js";
|
|
145
164
|
import {
|
|
146
165
|
getDashboardApiUrl,
|
|
147
166
|
getDefaultConfig,
|
|
@@ -163,7 +182,11 @@ import {
|
|
|
163
182
|
registerProject,
|
|
164
183
|
resolveProjectFromIssue,
|
|
165
184
|
unregisterProject
|
|
166
|
-
} from "../chunk-
|
|
185
|
+
} from "../chunk-RLZQB7HS.js";
|
|
186
|
+
import {
|
|
187
|
+
init_config_yaml,
|
|
188
|
+
loadConfig as loadConfig2
|
|
189
|
+
} from "../chunk-ZP6EWSZV.js";
|
|
167
190
|
import {
|
|
168
191
|
NotImplementedError,
|
|
169
192
|
init_interface
|
|
@@ -204,11 +227,6 @@ import {
|
|
|
204
227
|
init_platform,
|
|
205
228
|
syncDnsToWindows
|
|
206
229
|
} from "../chunk-JT4O4YVM.js";
|
|
207
|
-
import "../chunk-PELXV435.js";
|
|
208
|
-
import {
|
|
209
|
-
init_workspace_config,
|
|
210
|
-
replacePlaceholders
|
|
211
|
-
} from "../chunk-CWELWPWQ.js";
|
|
212
230
|
import {
|
|
213
231
|
__require,
|
|
214
232
|
init_esm_shims
|
|
@@ -216,11 +234,11 @@ import {
|
|
|
216
234
|
|
|
217
235
|
// src/cli/index.ts
|
|
218
236
|
init_esm_shims();
|
|
219
|
-
import { readFileSync as
|
|
220
|
-
import { join as
|
|
221
|
-
import { homedir as
|
|
222
|
-
import { Command } from "commander";
|
|
223
|
-
import
|
|
237
|
+
import { readFileSync as readFileSync49, existsSync as existsSync56 } from "fs";
|
|
238
|
+
import { join as join56 } from "path";
|
|
239
|
+
import { homedir as homedir27 } from "os";
|
|
240
|
+
import { Command as Command2 } from "commander";
|
|
241
|
+
import chalk62 from "chalk";
|
|
224
242
|
|
|
225
243
|
// src/cli/commands/init.ts
|
|
226
244
|
init_esm_shims();
|
|
@@ -933,9 +951,9 @@ init_agents();
|
|
|
933
951
|
init_projects();
|
|
934
952
|
import chalk6 from "chalk";
|
|
935
953
|
import ora4 from "ora";
|
|
936
|
-
import { existsSync as
|
|
937
|
-
import { join as
|
|
938
|
-
import { homedir as
|
|
954
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
955
|
+
import { join as join9, dirname as dirname4 } from "path";
|
|
956
|
+
import { homedir as homedir4 } from "os";
|
|
939
957
|
|
|
940
958
|
// src/lib/prd-draft.ts
|
|
941
959
|
init_esm_shims();
|
|
@@ -949,6 +967,63 @@ function hasPRDDraft(issueId) {
|
|
|
949
967
|
return existsSync5(getPRDDraftPath(issueId));
|
|
950
968
|
}
|
|
951
969
|
|
|
970
|
+
// src/lib/tracker-utils.ts
|
|
971
|
+
init_esm_shims();
|
|
972
|
+
init_projects();
|
|
973
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
974
|
+
import { join as join6 } from "path";
|
|
975
|
+
import { homedir as homedir3 } from "os";
|
|
976
|
+
function parseGitHubRepos() {
|
|
977
|
+
const repos = [];
|
|
978
|
+
const envFile = join6(homedir3(), ".panopticon.env");
|
|
979
|
+
if (existsSync6(envFile)) {
|
|
980
|
+
const content = readFileSync4(envFile, "utf-8");
|
|
981
|
+
const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
|
|
982
|
+
if (reposMatch) {
|
|
983
|
+
repos.push(...reposMatch[1].trim().split(",").map((r) => {
|
|
984
|
+
const [repoPath, prefix] = r.trim().split(":");
|
|
985
|
+
const [owner, repo] = (repoPath || "").split("/");
|
|
986
|
+
return { owner: owner || "", repo: repo || "", prefix: (prefix || "").toUpperCase() };
|
|
987
|
+
}).filter((r) => r.owner && r.repo && r.prefix));
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (repos.length === 0) {
|
|
991
|
+
try {
|
|
992
|
+
const { projects } = loadProjectsConfig();
|
|
993
|
+
for (const [, project2] of Object.entries(projects)) {
|
|
994
|
+
if (project2.github_repo) {
|
|
995
|
+
const [owner, repo] = project2.github_repo.split("/");
|
|
996
|
+
const prefix = project2.linear_team || "";
|
|
997
|
+
if (owner && repo && prefix) {
|
|
998
|
+
repos.push({ owner, repo, prefix: prefix.toUpperCase() });
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
} catch {
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return repos;
|
|
1006
|
+
}
|
|
1007
|
+
function extractIssuePrefix(issueId) {
|
|
1008
|
+
return issueId.split("-")[0].toUpperCase();
|
|
1009
|
+
}
|
|
1010
|
+
function resolveGitHubIssue(issueId) {
|
|
1011
|
+
const prefix = extractIssuePrefix(issueId);
|
|
1012
|
+
const repos = parseGitHubRepos();
|
|
1013
|
+
for (const repoConfig of repos) {
|
|
1014
|
+
if (repoConfig.prefix === prefix) {
|
|
1015
|
+
const number = parseInt(issueId.split("-")[1], 10);
|
|
1016
|
+
if (!isNaN(number)) {
|
|
1017
|
+
return { isGitHub: true, ...repoConfig, number };
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return { isGitHub: false };
|
|
1022
|
+
}
|
|
1023
|
+
function isGitHubIssue(issueId) {
|
|
1024
|
+
return resolveGitHubIssue(issueId).isGitHub;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
952
1027
|
// src/lib/shadow-mode.ts
|
|
953
1028
|
init_esm_shims();
|
|
954
1029
|
init_config_yaml();
|
|
@@ -1026,20 +1101,20 @@ async function isRemoteAvailable() {
|
|
|
1026
1101
|
|
|
1027
1102
|
// src/lib/cloister/work-agent-prompt.ts
|
|
1028
1103
|
init_esm_shims();
|
|
1029
|
-
import { existsSync as
|
|
1030
|
-
import { join as
|
|
1104
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync7, statSync as statSync3 } from "fs";
|
|
1105
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
1031
1106
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1032
1107
|
|
|
1033
1108
|
// src/lib/template.ts
|
|
1034
1109
|
init_esm_shims();
|
|
1035
1110
|
init_paths();
|
|
1036
|
-
import { readFileSync as
|
|
1037
|
-
import { join as
|
|
1111
|
+
import { readFileSync as readFileSync5, existsSync as existsSync7, readdirSync as readdirSync6 } from "fs";
|
|
1112
|
+
import { join as join7 } from "path";
|
|
1038
1113
|
function loadTemplate(templatePath) {
|
|
1039
|
-
if (!
|
|
1114
|
+
if (!existsSync7(templatePath)) {
|
|
1040
1115
|
throw new Error(`Template not found: ${templatePath}`);
|
|
1041
1116
|
}
|
|
1042
|
-
return
|
|
1117
|
+
return readFileSync5(templatePath, "utf8");
|
|
1043
1118
|
}
|
|
1044
1119
|
function processEnvBlocks(template, env) {
|
|
1045
1120
|
let result = template.replace(
|
|
@@ -1081,17 +1156,17 @@ function generateClaudeMd(projectPath, variables) {
|
|
|
1081
1156
|
"warnings.md"
|
|
1082
1157
|
];
|
|
1083
1158
|
for (const section of defaultOrder) {
|
|
1084
|
-
const sectionPath =
|
|
1085
|
-
if (
|
|
1159
|
+
const sectionPath = join7(CLAUDE_MD_TEMPLATES, section);
|
|
1160
|
+
if (existsSync7(sectionPath)) {
|
|
1086
1161
|
const content = loadTemplate(sectionPath);
|
|
1087
1162
|
sections.push(substituteVariables(content, variables));
|
|
1088
1163
|
}
|
|
1089
1164
|
}
|
|
1090
|
-
const projectSections =
|
|
1091
|
-
if (
|
|
1165
|
+
const projectSections = join7(projectPath, ".panopticon", "claude-md", "sections");
|
|
1166
|
+
if (existsSync7(projectSections)) {
|
|
1092
1167
|
const projectFiles = readdirSync6(projectSections).filter((f) => f.endsWith(".md")).sort();
|
|
1093
1168
|
for (const file of projectFiles) {
|
|
1094
|
-
const content = loadTemplate(
|
|
1169
|
+
const content = loadTemplate(join7(projectSections, file));
|
|
1095
1170
|
sections.push(substituteVariables(content, variables));
|
|
1096
1171
|
}
|
|
1097
1172
|
}
|
|
@@ -1118,28 +1193,28 @@ init_interface();
|
|
|
1118
1193
|
var __filename4 = fileURLToPath3(import.meta.url);
|
|
1119
1194
|
var __dirname4 = dirname3(__filename4);
|
|
1120
1195
|
function resolvePromptsDir() {
|
|
1121
|
-
const direct =
|
|
1122
|
-
if (
|
|
1196
|
+
const direct = join8(__dirname4, "prompts");
|
|
1197
|
+
if (existsSync8(direct) && existsSync8(join8(direct, "work-agent.md"))) {
|
|
1123
1198
|
return direct;
|
|
1124
1199
|
}
|
|
1125
1200
|
let packageRoot = __dirname4;
|
|
1126
1201
|
if (packageRoot.includes("/src/")) {
|
|
1127
1202
|
packageRoot = packageRoot.replace(/\/src\/.*$/, "");
|
|
1128
1203
|
} else {
|
|
1129
|
-
packageRoot =
|
|
1204
|
+
packageRoot = join8(packageRoot, "..", "..");
|
|
1130
1205
|
}
|
|
1131
|
-
const fromRoot =
|
|
1132
|
-
if (
|
|
1206
|
+
const fromRoot = join8(packageRoot, "src", "lib", "cloister", "prompts");
|
|
1207
|
+
if (existsSync8(fromRoot)) {
|
|
1133
1208
|
return fromRoot;
|
|
1134
1209
|
}
|
|
1135
1210
|
return direct;
|
|
1136
1211
|
}
|
|
1137
1212
|
function buildWorkAgentPrompt(ctx) {
|
|
1138
|
-
const templatePath =
|
|
1139
|
-
if (!
|
|
1213
|
+
const templatePath = join8(resolvePromptsDir(), "work-agent.md");
|
|
1214
|
+
if (!existsSync8(templatePath)) {
|
|
1140
1215
|
throw new Error(`Work agent prompt template not found at ${templatePath}`);
|
|
1141
1216
|
}
|
|
1142
|
-
let template =
|
|
1217
|
+
let template = readFileSync6(templatePath, "utf-8");
|
|
1143
1218
|
let beadsTasksStr = "";
|
|
1144
1219
|
let stitchDesignsStr = "";
|
|
1145
1220
|
let polyrepoContextStr = "";
|
|
@@ -1184,8 +1259,8 @@ ${template}
|
|
|
1184
1259
|
<!-- panopticon:orchestration-context-end -->`;
|
|
1185
1260
|
}
|
|
1186
1261
|
function readPendingFeedback(workspacePath) {
|
|
1187
|
-
const feedbackDir =
|
|
1188
|
-
if (!
|
|
1262
|
+
const feedbackDir = join8(workspacePath, ".planning", "feedback");
|
|
1263
|
+
if (!existsSync8(feedbackDir)) return "";
|
|
1189
1264
|
try {
|
|
1190
1265
|
const files = readdirSync7(feedbackDir).filter((f) => f.endsWith(".md")).sort();
|
|
1191
1266
|
if (files.length === 0) return "";
|
|
@@ -1209,8 +1284,8 @@ var COMMENT_BODY_LIMIT = 500;
|
|
|
1209
1284
|
var TOTAL_CONTEXT_LIMIT = 2e3;
|
|
1210
1285
|
async function getTrackerContext(issueId, workspacePath) {
|
|
1211
1286
|
let stateMtime = null;
|
|
1212
|
-
const statePath =
|
|
1213
|
-
if (
|
|
1287
|
+
const statePath = join8(workspacePath, ".planning", "STATE.md");
|
|
1288
|
+
if (existsSync8(statePath)) {
|
|
1214
1289
|
try {
|
|
1215
1290
|
stateMtime = statSync3(statePath).mtime;
|
|
1216
1291
|
} catch {
|
|
@@ -1305,9 +1380,9 @@ async function getTrackerContext(issueId, workspacePath) {
|
|
|
1305
1380
|
return "";
|
|
1306
1381
|
}
|
|
1307
1382
|
function readPlanningContext(workspacePath) {
|
|
1308
|
-
const statePath =
|
|
1309
|
-
if (
|
|
1310
|
-
return
|
|
1383
|
+
const statePath = join8(workspacePath, ".planning", "STATE.md");
|
|
1384
|
+
if (existsSync8(statePath)) {
|
|
1385
|
+
return readFileSync6(statePath, "utf-8");
|
|
1311
1386
|
}
|
|
1312
1387
|
return null;
|
|
1313
1388
|
}
|
|
@@ -1362,14 +1437,14 @@ function readBeadsTasks(workspacePath, projectRoot, issueId) {
|
|
|
1362
1437
|
const stateContent = readPlanningContext(workspacePath);
|
|
1363
1438
|
const beadsIds = stateContent ? extractBeadsIdsFromState(stateContent) : [];
|
|
1364
1439
|
const beadsPaths = [
|
|
1365
|
-
|
|
1366
|
-
|
|
1440
|
+
join8(workspacePath, ".beads", "issues.jsonl"),
|
|
1441
|
+
join8(projectRoot, ".beads", "issues.jsonl")
|
|
1367
1442
|
];
|
|
1368
1443
|
const seenIds = /* @__PURE__ */ new Set();
|
|
1369
1444
|
for (const beadsPath of beadsPaths) {
|
|
1370
|
-
if (!
|
|
1445
|
+
if (!existsSync8(beadsPath)) continue;
|
|
1371
1446
|
try {
|
|
1372
|
-
const content =
|
|
1447
|
+
const content = readFileSync6(beadsPath, "utf-8");
|
|
1373
1448
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
1374
1449
|
for (const line of lines) {
|
|
1375
1450
|
try {
|
|
@@ -1425,9 +1500,9 @@ function buildPolyrepoContext(issueId, workspacePath) {
|
|
|
1425
1500
|
|
|
1426
1501
|
// src/cli/commands/work/issue.ts
|
|
1427
1502
|
function getLinearApiKey() {
|
|
1428
|
-
const envFile =
|
|
1429
|
-
if (
|
|
1430
|
-
const content =
|
|
1503
|
+
const envFile = join9(homedir4(), ".panopticon.env");
|
|
1504
|
+
if (existsSync9(envFile)) {
|
|
1505
|
+
const content = readFileSync7(envFile, "utf-8");
|
|
1431
1506
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
1432
1507
|
if (match) return match[1].trim();
|
|
1433
1508
|
}
|
|
@@ -1500,26 +1575,26 @@ function findLocalWorkspace(issueId, labels = []) {
|
|
|
1500
1575
|
const resolved = resolveProjectFromIssue(issueId, labels);
|
|
1501
1576
|
if (resolved) {
|
|
1502
1577
|
const workspaceName = `feature-${normalizedId}`;
|
|
1503
|
-
const workspacePath =
|
|
1504
|
-
if (
|
|
1578
|
+
const workspacePath = join9(resolved.projectPath, "workspaces", workspaceName);
|
|
1579
|
+
if (existsSync9(workspacePath)) {
|
|
1505
1580
|
return workspacePath;
|
|
1506
1581
|
}
|
|
1507
|
-
const altPath =
|
|
1508
|
-
if (
|
|
1582
|
+
const altPath = join9(resolved.projectPath, "workspaces", normalizedId);
|
|
1583
|
+
if (existsSync9(altPath)) {
|
|
1509
1584
|
return altPath;
|
|
1510
1585
|
}
|
|
1511
1586
|
}
|
|
1512
1587
|
let dir = process.cwd();
|
|
1513
1588
|
for (let i = 0; i < 10; i++) {
|
|
1514
|
-
const workspacesDir =
|
|
1515
|
-
if (
|
|
1589
|
+
const workspacesDir = join9(dir, "workspaces");
|
|
1590
|
+
if (existsSync9(workspacesDir)) {
|
|
1516
1591
|
const workspaceName = `feature-${normalizedId}`;
|
|
1517
|
-
const workspacePath =
|
|
1518
|
-
if (
|
|
1592
|
+
const workspacePath = join9(workspacesDir, workspaceName);
|
|
1593
|
+
if (existsSync9(workspacePath)) {
|
|
1519
1594
|
return workspacePath;
|
|
1520
1595
|
}
|
|
1521
|
-
const altPath =
|
|
1522
|
-
if (
|
|
1596
|
+
const altPath = join9(workspacesDir, normalizedId);
|
|
1597
|
+
if (existsSync9(altPath)) {
|
|
1523
1598
|
return altPath;
|
|
1524
1599
|
}
|
|
1525
1600
|
}
|
|
@@ -1557,7 +1632,7 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
|
|
|
1557
1632
|
if (!remoteMetadata) {
|
|
1558
1633
|
spinner.text = "Remote workspace not found, creating...";
|
|
1559
1634
|
try {
|
|
1560
|
-
const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-
|
|
1635
|
+
const { createRemoteWorkspace: createRemoteWorkspace2 } = await import("../remote-workspace-XX6ARE6I.js");
|
|
1561
1636
|
remoteMetadata = await createRemoteWorkspace2(issueId, { spinner });
|
|
1562
1637
|
} catch (error) {
|
|
1563
1638
|
spinner.fail(`Failed to create remote workspace: ${error.message}`);
|
|
@@ -1609,6 +1684,23 @@ async function handleRemoteWorkspace(issueId, options, spinner) {
|
|
|
1609
1684
|
createShadowState(issueId, "open", "pan work issue");
|
|
1610
1685
|
updateShadowState(issueId, "in_progress", "pan work issue");
|
|
1611
1686
|
console.log(chalk6.cyan(` \u{1F47B} Shadow mode: tracking status locally`));
|
|
1687
|
+
} else if (isGitHubIssue(issueId)) {
|
|
1688
|
+
const gh = resolveGitHubIssue(issueId);
|
|
1689
|
+
if (gh.isGitHub) {
|
|
1690
|
+
try {
|
|
1691
|
+
const { loadConfig: loadYamlConfig } = await import("../config-yaml-OVZLKFMA.js");
|
|
1692
|
+
const yamlConfig = loadYamlConfig();
|
|
1693
|
+
const token = yamlConfig.config.trackerKeys?.github || process.env.GITHUB_TOKEN;
|
|
1694
|
+
if (token) {
|
|
1695
|
+
const { Octokit } = await import("@octokit/rest");
|
|
1696
|
+
const octokit = new Octokit({ auth: token });
|
|
1697
|
+
await octokit.issues.addLabels({ owner: gh.owner, repo: gh.repo, issue_number: gh.number, labels: ["in-progress"] });
|
|
1698
|
+
console.log(chalk6.green(` \u2713 Updated ${issueId.toUpperCase()} to In Progress`));
|
|
1699
|
+
}
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
console.warn(chalk6.dim(` \u26A0 Could not update GitHub label: ${err.message}`));
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1612
1704
|
} else if (isLinearIssue(issueId)) {
|
|
1613
1705
|
const apiKey = getLinearApiKey();
|
|
1614
1706
|
if (apiKey) {
|
|
@@ -1653,7 +1745,7 @@ function findProjectRoot(issueId, labels = []) {
|
|
|
1653
1745
|
}
|
|
1654
1746
|
let dir = process.cwd();
|
|
1655
1747
|
for (let i = 0; i < 10; i++) {
|
|
1656
|
-
if (
|
|
1748
|
+
if (existsSync9(join9(dir, "workspaces")) || existsSync9(join9(dir, ".git")) || existsSync9(join9(dir, "CLAUDE.md"))) {
|
|
1657
1749
|
return dir;
|
|
1658
1750
|
}
|
|
1659
1751
|
const parent = dirname4(dir);
|
|
@@ -1662,13 +1754,16 @@ function findProjectRoot(issueId, labels = []) {
|
|
|
1662
1754
|
}
|
|
1663
1755
|
return process.cwd();
|
|
1664
1756
|
}
|
|
1757
|
+
function hasBeadsTasks(workspacePath) {
|
|
1758
|
+
return existsSync9(join9(workspacePath, ".beads", "issues.jsonl"));
|
|
1759
|
+
}
|
|
1665
1760
|
function validateAndCleanStateFile(workspacePath, issueId) {
|
|
1666
|
-
const statePath =
|
|
1667
|
-
if (!
|
|
1761
|
+
const statePath = join9(workspacePath, ".planning", "STATE.md");
|
|
1762
|
+
if (!existsSync9(statePath)) {
|
|
1668
1763
|
return { valid: true, removed: false };
|
|
1669
1764
|
}
|
|
1670
1765
|
try {
|
|
1671
|
-
const content =
|
|
1766
|
+
const content = readFileSync7(statePath, "utf-8");
|
|
1672
1767
|
const firstLine = content.split("\n")[0] || "";
|
|
1673
1768
|
const issueMatch = firstLine.match(/^#\s*([A-Z]+-\d+)/i);
|
|
1674
1769
|
if (issueMatch) {
|
|
@@ -1762,6 +1857,16 @@ async function issueCommand(id, options) {
|
|
|
1762
1857
|
if (stateValidation.removed) {
|
|
1763
1858
|
spinner.warn(`Cleaned stale planning state from ${stateValidation.wrongIssue}`);
|
|
1764
1859
|
}
|
|
1860
|
+
if (!hasBeadsTasks(workspace)) {
|
|
1861
|
+
spinner.fail(`No beads tasks found for ${id}`);
|
|
1862
|
+
console.log("");
|
|
1863
|
+
console.log(chalk6.red(`Planning must create a task breakdown before work begins.`));
|
|
1864
|
+
console.log(chalk6.dim(`Run planning again and ensure it creates beads with "bd create".`));
|
|
1865
|
+
console.log("");
|
|
1866
|
+
console.log(chalk6.bold("To re-run planning:"));
|
|
1867
|
+
console.log(` ${chalk6.cyan(`pan work plan ${id}`)}`);
|
|
1868
|
+
process.exit(1);
|
|
1869
|
+
}
|
|
1765
1870
|
spinner.text = "Building agent prompt with planning context...";
|
|
1766
1871
|
const trackerContext = await getTrackerContext(id, workspace);
|
|
1767
1872
|
const prompt = buildWorkAgentPrompt({ issueId: id, env: "LOCAL", workspacePath: workspace, projectRoot, trackerContext });
|
|
@@ -1779,14 +1884,6 @@ async function issueCommand(id, options) {
|
|
|
1779
1884
|
createShadowState(id, "open", "pan work issue");
|
|
1780
1885
|
updateShadowState(id, "in_progress", "pan work issue");
|
|
1781
1886
|
console.log(chalk6.cyan(` \u{1F47B} Shadow mode: tracking status locally`));
|
|
1782
|
-
} else if (isLinearIssue(id)) {
|
|
1783
|
-
const apiKey = getLinearApiKey();
|
|
1784
|
-
if (apiKey) {
|
|
1785
|
-
const updated = await updateLinearToInProgress(apiKey, id);
|
|
1786
|
-
if (updated) {
|
|
1787
|
-
console.log(chalk6.green(` \u2713 Updated ${id.toUpperCase()} to In Progress`));
|
|
1788
|
-
}
|
|
1789
|
-
}
|
|
1790
1887
|
}
|
|
1791
1888
|
console.log("");
|
|
1792
1889
|
console.log(chalk6.bold("Agent Details:"));
|
|
@@ -1816,14 +1913,14 @@ async function issueCommand(id, options) {
|
|
|
1816
1913
|
init_esm_shims();
|
|
1817
1914
|
init_agents();
|
|
1818
1915
|
import chalk7 from "chalk";
|
|
1819
|
-
import { existsSync as
|
|
1820
|
-
import { join as
|
|
1916
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, statSync as statSync4, readdirSync as readdirSync9 } from "fs";
|
|
1917
|
+
import { join as join10, basename as basename2 } from "path";
|
|
1821
1918
|
init_tldr_daemon();
|
|
1822
1919
|
function readContextPercent(agentId) {
|
|
1823
|
-
const ctxFile =
|
|
1920
|
+
const ctxFile = join10(getAgentDir(agentId), "context-pct");
|
|
1824
1921
|
try {
|
|
1825
|
-
if (
|
|
1826
|
-
const val = parseInt(
|
|
1922
|
+
if (existsSync10(ctxFile)) {
|
|
1923
|
+
const val = parseInt(readFileSync8(ctxFile, "utf8").trim(), 10);
|
|
1827
1924
|
return isNaN(val) ? null : val;
|
|
1828
1925
|
}
|
|
1829
1926
|
} catch {
|
|
@@ -1899,17 +1996,17 @@ async function statusCommand(options) {
|
|
|
1899
1996
|
}
|
|
1900
1997
|
}
|
|
1901
1998
|
function readTldrIndexData(workspacePath) {
|
|
1902
|
-
const tldrPath =
|
|
1903
|
-
if (!
|
|
1999
|
+
const tldrPath = join10(workspacePath, ".tldr");
|
|
2000
|
+
if (!existsSync10(tldrPath)) {
|
|
1904
2001
|
return { fileCount: null, edgeCount: null, ageMs: null };
|
|
1905
2002
|
}
|
|
1906
2003
|
let fileCount = null;
|
|
1907
2004
|
let edgeCount = null;
|
|
1908
2005
|
let ageMs = null;
|
|
1909
|
-
const cgPath =
|
|
1910
|
-
if (
|
|
2006
|
+
const cgPath = join10(tldrPath, "cache", "call_graph.json");
|
|
2007
|
+
if (existsSync10(cgPath)) {
|
|
1911
2008
|
try {
|
|
1912
|
-
const cg = JSON.parse(
|
|
2009
|
+
const cg = JSON.parse(readFileSync8(cgPath, "utf-8"));
|
|
1913
2010
|
if (Array.isArray(cg.edges)) {
|
|
1914
2011
|
edgeCount = cg.edges.length;
|
|
1915
2012
|
const files = /* @__PURE__ */ new Set();
|
|
@@ -1922,10 +2019,10 @@ function readTldrIndexData(workspacePath) {
|
|
|
1922
2019
|
} catch {
|
|
1923
2020
|
}
|
|
1924
2021
|
}
|
|
1925
|
-
const langPath =
|
|
1926
|
-
if (
|
|
2022
|
+
const langPath = join10(tldrPath, "languages.json");
|
|
2023
|
+
if (existsSync10(langPath)) {
|
|
1927
2024
|
try {
|
|
1928
|
-
const langData = JSON.parse(
|
|
2025
|
+
const langData = JSON.parse(readFileSync8(langPath, "utf-8"));
|
|
1929
2026
|
if (langData.timestamp) {
|
|
1930
2027
|
ageMs = Date.now() - langData.timestamp * 1e3;
|
|
1931
2028
|
}
|
|
@@ -1961,20 +2058,20 @@ async function tldrIndexStatusCommand(projectRoot = process.cwd()) {
|
|
|
1961
2058
|
const projectName = basename2(projectRoot);
|
|
1962
2059
|
const mainEntries = [];
|
|
1963
2060
|
const workspaceEntries = [];
|
|
1964
|
-
const mainVenvPath =
|
|
1965
|
-
if (
|
|
2061
|
+
const mainVenvPath = join10(projectRoot, ".venv");
|
|
2062
|
+
if (existsSync10(mainVenvPath)) {
|
|
1966
2063
|
const service = getTldrDaemonService(projectRoot, mainVenvPath);
|
|
1967
2064
|
const status = await service.getStatus();
|
|
1968
2065
|
const { fileCount, edgeCount, ageMs } = readTldrIndexData(projectRoot);
|
|
1969
2066
|
mainEntries.push({ label: `Main (${projectName})`, running: status.running, fileCount, edgeCount, ageMs });
|
|
1970
2067
|
}
|
|
1971
|
-
const workspacesDir =
|
|
1972
|
-
if (
|
|
2068
|
+
const workspacesDir = join10(projectRoot, "workspaces");
|
|
2069
|
+
if (existsSync10(workspacesDir)) {
|
|
1973
2070
|
const dirs = readdirSync9(workspacesDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith("feature-"));
|
|
1974
2071
|
for (const ws of dirs) {
|
|
1975
|
-
const wsPath =
|
|
1976
|
-
const wsVenvPath =
|
|
1977
|
-
if (
|
|
2072
|
+
const wsPath = join10(workspacesDir, ws.name);
|
|
2073
|
+
const wsVenvPath = join10(wsPath, ".venv");
|
|
2074
|
+
if (existsSync10(wsVenvPath)) {
|
|
1978
2075
|
const service = getTldrDaemonService(wsPath, wsVenvPath);
|
|
1979
2076
|
const status = await service.getStatus();
|
|
1980
2077
|
const { fileCount, edgeCount, ageMs } = readTldrIndexData(wsPath);
|
|
@@ -2082,8 +2179,8 @@ init_esm_shims();
|
|
|
2082
2179
|
init_agents();
|
|
2083
2180
|
init_paths();
|
|
2084
2181
|
import chalk10 from "chalk";
|
|
2085
|
-
import { existsSync as
|
|
2086
|
-
import { join as
|
|
2182
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
2183
|
+
import { join as join11 } from "path";
|
|
2087
2184
|
async function pendingCommand() {
|
|
2088
2185
|
const agents = listRunningAgents().filter((a) => !a.tmuxActive && a.status !== "error");
|
|
2089
2186
|
if (agents.length === 0) {
|
|
@@ -2096,9 +2193,9 @@ async function pendingCommand() {
|
|
|
2096
2193
|
console.log(`${chalk10.cyan(agent.issueId)}`);
|
|
2097
2194
|
console.log(` Agent: ${agent.id}`);
|
|
2098
2195
|
console.log(` Workspace: ${chalk10.dim(agent.workspace)}`);
|
|
2099
|
-
const completionFile =
|
|
2100
|
-
if (
|
|
2101
|
-
const content =
|
|
2196
|
+
const completionFile = join11(AGENTS_DIR, agent.id, "completion.md");
|
|
2197
|
+
if (existsSync11(completionFile)) {
|
|
2198
|
+
const content = readFileSync9(completionFile, "utf8");
|
|
2102
2199
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
|
|
2103
2200
|
if (firstLine) {
|
|
2104
2201
|
console.log(` Summary: ${chalk10.dim(firstLine.trim())}`);
|
|
@@ -2115,14 +2212,14 @@ init_agents();
|
|
|
2115
2212
|
init_paths();
|
|
2116
2213
|
import chalk11 from "chalk";
|
|
2117
2214
|
import ora5 from "ora";
|
|
2118
|
-
import { existsSync as
|
|
2119
|
-
import { join as
|
|
2120
|
-
import { homedir as
|
|
2215
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync3, readFileSync as readFileSync10 } from "fs";
|
|
2216
|
+
import { join as join12 } from "path";
|
|
2217
|
+
import { homedir as homedir5 } from "os";
|
|
2121
2218
|
import { execSync as execSync2 } from "child_process";
|
|
2122
2219
|
function getLinearApiKey2() {
|
|
2123
|
-
const envFile =
|
|
2124
|
-
if (
|
|
2125
|
-
const content =
|
|
2220
|
+
const envFile = join12(homedir5(), ".panopticon.env");
|
|
2221
|
+
if (existsSync12(envFile)) {
|
|
2222
|
+
const content = readFileSync10(envFile, "utf-8");
|
|
2126
2223
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
2127
2224
|
if (match) return match[1].trim();
|
|
2128
2225
|
}
|
|
@@ -2259,7 +2356,7 @@ async function approveCommand(id, options = {}) {
|
|
|
2259
2356
|
state.status = "stopped";
|
|
2260
2357
|
state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
|
|
2261
2358
|
saveAgentState(state);
|
|
2262
|
-
const approvedFile =
|
|
2359
|
+
const approvedFile = join12(AGENTS_DIR, agentId, "approved");
|
|
2263
2360
|
writeFileSync3(approvedFile, JSON.stringify({
|
|
2264
2361
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2265
2362
|
prMerged,
|
|
@@ -2425,40 +2522,6 @@ function cleanupWorkflowLabels(currentLabels, targetState) {
|
|
|
2425
2522
|
return cleaned;
|
|
2426
2523
|
}
|
|
2427
2524
|
|
|
2428
|
-
// src/lib/tracker-utils.ts
|
|
2429
|
-
init_esm_shims();
|
|
2430
|
-
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
2431
|
-
import { join as join12 } from "path";
|
|
2432
|
-
import { homedir as homedir5 } from "os";
|
|
2433
|
-
function parseGitHubRepos() {
|
|
2434
|
-
const envFile = join12(homedir5(), ".panopticon.env");
|
|
2435
|
-
if (!existsSync12(envFile)) return [];
|
|
2436
|
-
const content = readFileSync10(envFile, "utf-8");
|
|
2437
|
-
const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
|
|
2438
|
-
if (!reposMatch) return [];
|
|
2439
|
-
return reposMatch[1].trim().split(",").map((r) => {
|
|
2440
|
-
const [repoPath, prefix] = r.trim().split(":");
|
|
2441
|
-
const [owner, repo] = (repoPath || "").split("/");
|
|
2442
|
-
return { owner: owner || "", repo: repo || "", prefix: (prefix || "").toUpperCase() };
|
|
2443
|
-
}).filter((r) => r.owner && r.repo && r.prefix);
|
|
2444
|
-
}
|
|
2445
|
-
function extractIssuePrefix(issueId) {
|
|
2446
|
-
return issueId.split("-")[0].toUpperCase();
|
|
2447
|
-
}
|
|
2448
|
-
function resolveGitHubIssue(issueId) {
|
|
2449
|
-
const prefix = extractIssuePrefix(issueId);
|
|
2450
|
-
const repos = parseGitHubRepos();
|
|
2451
|
-
for (const repoConfig of repos) {
|
|
2452
|
-
if (repoConfig.prefix === prefix) {
|
|
2453
|
-
const number = parseInt(issueId.split("-")[1], 10);
|
|
2454
|
-
if (!isNaN(number)) {
|
|
2455
|
-
return { isGitHub: true, ...repoConfig, number };
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
return { isGitHub: false };
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
2525
|
// src/cli/commands/work/done.ts
|
|
2463
2526
|
var execAsync = promisify(exec);
|
|
2464
2527
|
function getLinearApiKey3() {
|
|
@@ -2526,9 +2589,9 @@ async function updateGitHubToInReview(issueId, comment) {
|
|
|
2526
2589
|
try {
|
|
2527
2590
|
const ghConfig = getGitHubConfig();
|
|
2528
2591
|
if (!ghConfig) return false;
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
const { owner, repo
|
|
2592
|
+
const number = parseInt(issueId.split("-")[1], 10);
|
|
2593
|
+
const repoConfig = ghConfig.repos.find((r) => r.prefix === "PAN") || ghConfig.repos[0];
|
|
2594
|
+
const { owner, repo } = repoConfig;
|
|
2532
2595
|
const token = ghConfig.token;
|
|
2533
2596
|
const headers = {
|
|
2534
2597
|
"Authorization": `token ${token}`,
|
|
@@ -2564,59 +2627,10 @@ async function doneCommand(id, options = {}) {
|
|
|
2564
2627
|
const issueId = id.replace(/^agent-/i, "").toUpperCase();
|
|
2565
2628
|
const agentId = `agent-${issueId.toLowerCase()}`;
|
|
2566
2629
|
if (!options.force) {
|
|
2567
|
-
const { getAgentState: getAgentState2 } = await import("../agents-
|
|
2630
|
+
const { getAgentState: getAgentState2 } = await import("../agents-DMPT32H7.js");
|
|
2568
2631
|
const agentState = getAgentState2(agentId);
|
|
2569
2632
|
const workspacePath = agentState?.workspace;
|
|
2570
2633
|
if (workspacePath && existsSync13(workspacePath)) {
|
|
2571
|
-
try {
|
|
2572
|
-
const { getReviewStatus: getReviewStatus2 } = await import("../review-status-TDPSOU5J.js");
|
|
2573
|
-
const { getWorkspaceCommitHashes } = await import("../git-utils-I2UDKNZH.js");
|
|
2574
|
-
const { getDashboardApiUrl: getDashboardApiUrl2 } = await import("../config-4CJNUE3O.js");
|
|
2575
|
-
const reviewStatus = getReviewStatus2(issueId);
|
|
2576
|
-
if (reviewStatus) {
|
|
2577
|
-
const isBlockedOrFailed = ["blocked", "failed"].includes(reviewStatus.reviewStatus);
|
|
2578
|
-
if (isBlockedOrFailed) {
|
|
2579
|
-
const dashboardUrl = getDashboardApiUrl2();
|
|
2580
|
-
console.error(chalk12.red(`
|
|
2581
|
-
\u2716 Cannot mark work done \u2014 review is ${reviewStatus.reviewStatus} for ${issueId}
|
|
2582
|
-
`));
|
|
2583
|
-
console.error(" The review agent found issues that must be addressed before this issue can be completed.");
|
|
2584
|
-
console.error(" Fix the issues, commit your changes, then re-submit for review:\n");
|
|
2585
|
-
console.error(chalk12.cyan(` curl -X POST ${dashboardUrl}/api/workspaces/${issueId}/request-review \\`));
|
|
2586
|
-
console.error(chalk12.cyan(` -H "Content-Type: application/json" -d '{}'`));
|
|
2587
|
-
console.error("");
|
|
2588
|
-
console.error(chalk12.dim(" Use --force to skip this check."));
|
|
2589
|
-
console.error("");
|
|
2590
|
-
process.exit(1);
|
|
2591
|
-
}
|
|
2592
|
-
if (reviewStatus.lastReviewCommits) {
|
|
2593
|
-
const currentHashes = await getWorkspaceCommitHashes(workspacePath);
|
|
2594
|
-
const allKeys = /* @__PURE__ */ new Set([
|
|
2595
|
-
...Object.keys(currentHashes),
|
|
2596
|
-
...Object.keys(reviewStatus.lastReviewCommits)
|
|
2597
|
-
]);
|
|
2598
|
-
const commitsChanged = [...allKeys].some(
|
|
2599
|
-
(k) => currentHashes[k] !== reviewStatus.lastReviewCommits[k]
|
|
2600
|
-
);
|
|
2601
|
-
if (commitsChanged) {
|
|
2602
|
-
const dashboardUrl = getDashboardApiUrl2();
|
|
2603
|
-
console.error(chalk12.red(`
|
|
2604
|
-
\u2716 Cannot mark work done \u2014 commits changed since last review for ${issueId}
|
|
2605
|
-
`));
|
|
2606
|
-
console.error(" New commits were added after the last review/test run. The specialists");
|
|
2607
|
-
console.error(" must review and test your latest code before work can be marked done.");
|
|
2608
|
-
console.error(" Re-submit for review:\n");
|
|
2609
|
-
console.error(chalk12.cyan(` curl -X POST ${dashboardUrl}/api/workspaces/${issueId}/request-review \\`));
|
|
2610
|
-
console.error(chalk12.cyan(` -H "Content-Type: application/json" -d '{}'`));
|
|
2611
|
-
console.error("");
|
|
2612
|
-
console.error(chalk12.dim(" Use --force to skip this check."));
|
|
2613
|
-
console.error("");
|
|
2614
|
-
process.exit(1);
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
} catch {
|
|
2619
|
-
}
|
|
2620
2634
|
const failures = [];
|
|
2621
2635
|
try {
|
|
2622
2636
|
const { stdout } = await execAsync("bd list --status open --limit 0 --json", { cwd: workspacePath });
|
|
@@ -2683,14 +2697,14 @@ async function doneCommand(id, options = {}) {
|
|
|
2683
2697
|
try {
|
|
2684
2698
|
let trackerUpdated = false;
|
|
2685
2699
|
let shadowModeActive = false;
|
|
2686
|
-
const
|
|
2700
|
+
const isGitHubIssue2 = issueId.startsWith("PAN-");
|
|
2687
2701
|
const skipTrackerUpdate = shouldSkipTrackerUpdate(issueId, options.shadow);
|
|
2688
2702
|
if (skipTrackerUpdate) {
|
|
2689
2703
|
shadowModeActive = true;
|
|
2690
2704
|
spinner.text = "Updating shadow state...";
|
|
2691
2705
|
updateShadowState(issueId, "closed", "pan work done");
|
|
2692
2706
|
console.log(chalk12.cyan(` \u{1F47B} Shadow mode: status updated locally`));
|
|
2693
|
-
} else if (
|
|
2707
|
+
} else if (isGitHubIssue2) {
|
|
2694
2708
|
spinner.text = "Updating GitHub labels...";
|
|
2695
2709
|
trackerUpdated = await updateGitHubToInReview(issueId, options.comment);
|
|
2696
2710
|
if (trackerUpdated) {
|
|
@@ -2712,7 +2726,7 @@ async function doneCommand(id, options = {}) {
|
|
|
2712
2726
|
console.log(chalk12.dim(" LINEAR_API_KEY not set - skipping status update"));
|
|
2713
2727
|
}
|
|
2714
2728
|
}
|
|
2715
|
-
const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-
|
|
2729
|
+
const { getAgentState: getAgentState2, saveAgentState: saveAgentState2 } = await import("../agents-DMPT32H7.js");
|
|
2716
2730
|
const existingState = getAgentState2(agentId);
|
|
2717
2731
|
if (existingState) {
|
|
2718
2732
|
existingState.status = "stopped";
|
|
@@ -3330,48 +3344,79 @@ async function runDiscoveryPhase(issue, complexity, prdContent) {
|
|
|
3330
3344
|
async function planCommand(id, options = {}) {
|
|
3331
3345
|
const spinner = ora7(`Creating execution plan for ${id}...`).start();
|
|
3332
3346
|
try {
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
process.
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3347
|
+
const ghResolution = resolveGitHubIssue(id);
|
|
3348
|
+
let issueData;
|
|
3349
|
+
if (ghResolution.isGitHub) {
|
|
3350
|
+
spinner.text = "Fetching issue from GitHub...";
|
|
3351
|
+
const { loadConfig: loadYamlConfig } = await import("../config-yaml-OVZLKFMA.js");
|
|
3352
|
+
const yamlConfig = loadYamlConfig();
|
|
3353
|
+
const token = yamlConfig.config.trackerKeys?.github || process.env.GITHUB_TOKEN;
|
|
3354
|
+
if (!token) {
|
|
3355
|
+
spinner.fail("GitHub token not found");
|
|
3356
|
+
process.exit(1);
|
|
3357
|
+
}
|
|
3358
|
+
const { Octokit } = await import("@octokit/rest");
|
|
3359
|
+
const octokit = new Octokit({ auth: token });
|
|
3360
|
+
const { data: ghIssue } = await octokit.issues.get({
|
|
3361
|
+
owner: ghResolution.owner,
|
|
3362
|
+
repo: ghResolution.repo,
|
|
3363
|
+
issue_number: ghResolution.number
|
|
3364
|
+
});
|
|
3365
|
+
issueData = {
|
|
3366
|
+
id: String(ghIssue.id),
|
|
3367
|
+
identifier: id,
|
|
3368
|
+
title: ghIssue.title,
|
|
3369
|
+
description: ghIssue.body || void 0,
|
|
3370
|
+
url: ghIssue.html_url,
|
|
3371
|
+
state: { name: ghIssue.state === "open" ? "Todo" : "Done" },
|
|
3372
|
+
priority: 0,
|
|
3373
|
+
labels: (ghIssue.labels || []).map((l) => ({ name: typeof l === "string" ? l : l.name || "" })),
|
|
3374
|
+
assignee: ghIssue.assignee ? { name: ghIssue.assignee.login } : void 0
|
|
3375
|
+
};
|
|
3376
|
+
} else {
|
|
3377
|
+
const apiKey = getLinearApiKey4();
|
|
3378
|
+
if (!apiKey) {
|
|
3379
|
+
spinner.fail("LINEAR_API_KEY not found");
|
|
3380
|
+
console.log("");
|
|
3381
|
+
console.log(chalk13.dim("Set it in ~/.panopticon.env:"));
|
|
3382
|
+
console.log(" LINEAR_API_KEY=lin_api_xxxxx");
|
|
3383
|
+
process.exit(1);
|
|
3384
|
+
}
|
|
3385
|
+
spinner.text = "Fetching issue from Linear...";
|
|
3386
|
+
const { LinearClient: LinearClient2 } = await import("@linear/sdk");
|
|
3387
|
+
const client = new LinearClient2({ apiKey });
|
|
3388
|
+
const me = await client.viewer;
|
|
3389
|
+
const teams = await me.teams();
|
|
3390
|
+
const team = teams.nodes[0];
|
|
3391
|
+
if (!team) {
|
|
3392
|
+
spinner.fail("No Linear team found");
|
|
3393
|
+
process.exit(1);
|
|
3394
|
+
}
|
|
3395
|
+
const searchResult = await team.issues({ first: 100 });
|
|
3396
|
+
const issue = searchResult.nodes.find(
|
|
3397
|
+
(i) => i.identifier.toUpperCase() === id.toUpperCase()
|
|
3398
|
+
);
|
|
3399
|
+
if (!issue) {
|
|
3400
|
+
spinner.fail(`Issue not found: ${id}`);
|
|
3401
|
+
process.exit(1);
|
|
3402
|
+
}
|
|
3403
|
+
const state = await issue.state;
|
|
3404
|
+
const assignee = await issue.assignee;
|
|
3405
|
+
const project2 = await issue.project;
|
|
3406
|
+
const labels = await issue.labels();
|
|
3407
|
+
issueData = {
|
|
3408
|
+
id: issue.id,
|
|
3409
|
+
identifier: issue.identifier,
|
|
3410
|
+
title: issue.title,
|
|
3411
|
+
description: issue.description || void 0,
|
|
3412
|
+
url: issue.url,
|
|
3413
|
+
state: { name: state?.name || "Unknown" },
|
|
3414
|
+
priority: issue.priority,
|
|
3415
|
+
labels: labels.nodes.map((l) => ({ name: l.name })),
|
|
3416
|
+
assignee: assignee ? { name: assignee.name } : void 0,
|
|
3417
|
+
project: project2 ? { name: project2.name } : void 0
|
|
3418
|
+
};
|
|
3358
3419
|
}
|
|
3359
|
-
const state = await issue.state;
|
|
3360
|
-
const assignee = await issue.assignee;
|
|
3361
|
-
const project2 = await issue.project;
|
|
3362
|
-
const labels = await issue.labels();
|
|
3363
|
-
const issueData = {
|
|
3364
|
-
id: issue.id,
|
|
3365
|
-
identifier: issue.identifier,
|
|
3366
|
-
title: issue.title,
|
|
3367
|
-
description: issue.description || void 0,
|
|
3368
|
-
url: issue.url,
|
|
3369
|
-
state: { name: state?.name || "Unknown" },
|
|
3370
|
-
priority: issue.priority,
|
|
3371
|
-
labels: labels.nodes.map((l) => ({ name: l.name })),
|
|
3372
|
-
assignee: assignee ? { name: assignee.name } : void 0,
|
|
3373
|
-
project: project2 ? { name: project2.name } : void 0
|
|
3374
|
-
};
|
|
3375
3420
|
spinner.text = "Searching for related PRDs...";
|
|
3376
3421
|
const prdFiles = await findPRDFiles(id);
|
|
3377
3422
|
spinner.text = "Analyzing complexity...";
|
|
@@ -3504,6 +3549,7 @@ init_esm_shims();
|
|
|
3504
3549
|
init_config();
|
|
3505
3550
|
import chalk14 from "chalk";
|
|
3506
3551
|
import ora8 from "ora";
|
|
3552
|
+
init_projects();
|
|
3507
3553
|
var PRIORITY_LABELS = {
|
|
3508
3554
|
0: chalk14.dim("None"),
|
|
3509
3555
|
1: chalk14.red("Urgent"),
|
|
@@ -3519,21 +3565,40 @@ var STATE_COLORS = {
|
|
|
3519
3565
|
function getTrackerConfig(trackerType) {
|
|
3520
3566
|
const config2 = loadConfig();
|
|
3521
3567
|
const trackerConfig = config2.trackers[trackerType];
|
|
3522
|
-
if (
|
|
3523
|
-
return
|
|
3568
|
+
if (trackerConfig) {
|
|
3569
|
+
return {
|
|
3570
|
+
type: trackerType,
|
|
3571
|
+
apiKeyEnv: trackerConfig.api_key_env,
|
|
3572
|
+
team: trackerConfig.team,
|
|
3573
|
+
tokenEnv: trackerConfig.token_env,
|
|
3574
|
+
owner: trackerConfig.owner,
|
|
3575
|
+
repo: trackerConfig.repo,
|
|
3576
|
+
projectId: trackerConfig.project_id,
|
|
3577
|
+
server: trackerConfig.server,
|
|
3578
|
+
workspace: trackerConfig.workspace,
|
|
3579
|
+
project: trackerConfig.project
|
|
3580
|
+
};
|
|
3524
3581
|
}
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3582
|
+
if (trackerType === "github") {
|
|
3583
|
+
try {
|
|
3584
|
+
const { projects } = loadProjectsConfig();
|
|
3585
|
+
for (const [, project2] of Object.entries(projects)) {
|
|
3586
|
+
if (project2.github_repo) {
|
|
3587
|
+
const [owner, repo] = project2.github_repo.split("/");
|
|
3588
|
+
if (owner && repo) {
|
|
3589
|
+
return {
|
|
3590
|
+
type: "github",
|
|
3591
|
+
tokenEnv: "GITHUB_TOKEN",
|
|
3592
|
+
owner,
|
|
3593
|
+
repo
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
} catch {
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
return null;
|
|
3537
3602
|
}
|
|
3538
3603
|
function getConfiguredTrackers() {
|
|
3539
3604
|
const config2 = loadConfig();
|
|
@@ -3542,6 +3607,16 @@ function getConfiguredTrackers() {
|
|
|
3542
3607
|
if (config2.trackers.github) trackers.push("github");
|
|
3543
3608
|
if (config2.trackers.gitlab) trackers.push("gitlab");
|
|
3544
3609
|
if (config2.trackers.rally) trackers.push("rally");
|
|
3610
|
+
if (!trackers.includes("github") || !trackers.includes("gitlab")) {
|
|
3611
|
+
try {
|
|
3612
|
+
const { projects } = loadProjectsConfig();
|
|
3613
|
+
for (const [, project2] of Object.entries(projects)) {
|
|
3614
|
+
if (project2.github_repo && !trackers.includes("github")) trackers.push("github");
|
|
3615
|
+
if (project2.gitlab_repo && !trackers.includes("gitlab")) trackers.push("gitlab");
|
|
3616
|
+
}
|
|
3617
|
+
} catch {
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3545
3620
|
return trackers;
|
|
3546
3621
|
}
|
|
3547
3622
|
function displayIssues(issues, trackerName) {
|
|
@@ -5196,10 +5271,10 @@ Previous state: ${issue.state}`
|
|
|
5196
5271
|
console.log(chalk21.green(`\u2713 ${issue.identifier} reopened and ready for re-work`));
|
|
5197
5272
|
console.log("");
|
|
5198
5273
|
try {
|
|
5199
|
-
const { getAgentState: getAgentState2 } = await import("../agents-
|
|
5274
|
+
const { getAgentState: getAgentState2 } = await import("../agents-DMPT32H7.js");
|
|
5200
5275
|
const agentId = `agent-${id.toLowerCase()}`;
|
|
5201
5276
|
const agentState = getAgentState2(agentId);
|
|
5202
|
-
const agentRunning = agentState?.status === "
|
|
5277
|
+
const agentRunning = agentState?.status === "running" || agentState?.status === "starting";
|
|
5203
5278
|
if (agentRunning) {
|
|
5204
5279
|
console.log(chalk21.dim("Agent is still running. Send it context about the re-work:"));
|
|
5205
5280
|
console.log(` pan tell ${id} "Issue reopened. <describe what needs to change>"`);
|
|
@@ -5317,7 +5392,6 @@ import { join as join21 } from "path";
|
|
|
5317
5392
|
import { existsSync as existsSync22, rmSync, readFileSync as readFileSync20 } from "fs";
|
|
5318
5393
|
import { exec as exec4 } from "child_process";
|
|
5319
5394
|
import { promisify as promisify4 } from "util";
|
|
5320
|
-
init_workspace_manager();
|
|
5321
5395
|
var execAsync4 = promisify4(exec4);
|
|
5322
5396
|
async function wipeCommand(issueId, options) {
|
|
5323
5397
|
const issueLower = issueId.toLowerCase();
|
|
@@ -5430,23 +5504,6 @@ async function wipeCommand(issueId, options) {
|
|
|
5430
5504
|
} catch (e) {
|
|
5431
5505
|
}
|
|
5432
5506
|
}
|
|
5433
|
-
if (projectPath) {
|
|
5434
|
-
const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
|
|
5435
|
-
if (existsSync22(workspacePath)) {
|
|
5436
|
-
try {
|
|
5437
|
-
const projName = projectPath.split("/").pop() || "";
|
|
5438
|
-
const dockerResult = await stopWorkspaceDocker(workspacePath, projName, issueLower);
|
|
5439
|
-
if (dockerResult.steps.length > 0) {
|
|
5440
|
-
for (const step of dockerResult.steps) {
|
|
5441
|
-
cleanupLog.push(step);
|
|
5442
|
-
console.log(chalk24.green(` \u2713 ${step}`));
|
|
5443
|
-
}
|
|
5444
|
-
}
|
|
5445
|
-
} catch (e) {
|
|
5446
|
-
console.log(chalk24.yellow(` \u26A0 Docker cleanup: ${e.message}`));
|
|
5447
|
-
}
|
|
5448
|
-
}
|
|
5449
|
-
}
|
|
5450
5507
|
if (options.workspace && projectPath) {
|
|
5451
5508
|
const workspacePath = join21(projectPath, "workspaces", `feature-${issueLower}`);
|
|
5452
5509
|
if (existsSync22(workspacePath)) {
|
|
@@ -6261,6 +6318,9 @@ async function syncMainCommand(id) {
|
|
|
6261
6318
|
// src/cli/commands/work/close-out.ts
|
|
6262
6319
|
init_esm_shims();
|
|
6263
6320
|
import chalk31 from "chalk";
|
|
6321
|
+
import { existsSync as existsSync30, readFileSync as readFileSync26 } from "fs";
|
|
6322
|
+
import { join as join29 } from "path";
|
|
6323
|
+
import { homedir as homedir14 } from "os";
|
|
6264
6324
|
|
|
6265
6325
|
// src/lib/lifecycle/index.ts
|
|
6266
6326
|
init_esm_shims();
|
|
@@ -6412,11 +6472,17 @@ async function archivePlanning(ctx, opts = {}) {
|
|
|
6412
6472
|
return results;
|
|
6413
6473
|
}
|
|
6414
6474
|
|
|
6415
|
-
// src/lib/lifecycle/
|
|
6475
|
+
// src/lib/lifecycle/clean-planning.ts
|
|
6416
6476
|
init_esm_shims();
|
|
6417
6477
|
import { exec as exec6 } from "child_process";
|
|
6418
6478
|
import { promisify as promisify6 } from "util";
|
|
6419
6479
|
var execAsync6 = promisify6(exec6);
|
|
6480
|
+
|
|
6481
|
+
// src/lib/lifecycle/close-issue.ts
|
|
6482
|
+
init_esm_shims();
|
|
6483
|
+
import { exec as exec7 } from "child_process";
|
|
6484
|
+
import { promisify as promisify7 } from "util";
|
|
6485
|
+
var execAsync7 = promisify7(exec7);
|
|
6420
6486
|
var CLOSED_OUT_LABEL = "closed-out";
|
|
6421
6487
|
var CLOSED_OUT_COLOR = "1d4ed8";
|
|
6422
6488
|
var WORKFLOW_LABELS2 = ["in-progress", "in-review", "needs-close-out"];
|
|
@@ -6477,7 +6543,7 @@ async function closeGitHubDirect(ctx, comment) {
|
|
|
6477
6543
|
const { owner, repo, number } = ctx.github;
|
|
6478
6544
|
try {
|
|
6479
6545
|
const commentArg = comment ? ` --comment "${comment.replace(/"/g, '\\"')}"` : "";
|
|
6480
|
-
await
|
|
6546
|
+
await execAsync7(
|
|
6481
6547
|
`gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,
|
|
6482
6548
|
{ encoding: "utf-8" }
|
|
6483
6549
|
);
|
|
@@ -6495,7 +6561,7 @@ async function closeGitHubPr(ctx) {
|
|
|
6495
6561
|
const issueLower = ctx.issueId.toLowerCase();
|
|
6496
6562
|
const branchName = `feature/${issueLower}`;
|
|
6497
6563
|
try {
|
|
6498
|
-
const { stdout: prListRaw } = await
|
|
6564
|
+
const { stdout: prListRaw } = await execAsync7(
|
|
6499
6565
|
`gh pr list --repo ${owner}/${repo} --head "${branchName}" --state open --json number --jq '.[0].number'`,
|
|
6500
6566
|
{ encoding: "utf-8" }
|
|
6501
6567
|
);
|
|
@@ -6503,7 +6569,7 @@ async function closeGitHubPr(ctx) {
|
|
|
6503
6569
|
if (!prNumber) {
|
|
6504
6570
|
return stepSkipped(step, ["No open PR found for branch"]);
|
|
6505
6571
|
}
|
|
6506
|
-
await
|
|
6572
|
+
await execAsync7(
|
|
6507
6573
|
`gh pr close ${prNumber} --repo ${owner}/${repo} --comment "Merged via Panopticon lifecycle"`,
|
|
6508
6574
|
{ encoding: "utf-8" }
|
|
6509
6575
|
);
|
|
@@ -6512,8 +6578,14 @@ async function closeGitHubPr(ctx) {
|
|
|
6512
6578
|
return stepSkipped(step, [`PR close failed (non-fatal): ${err.message}`]);
|
|
6513
6579
|
}
|
|
6514
6580
|
}
|
|
6581
|
+
var _linearRateLimitUntil = 0;
|
|
6582
|
+
var LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
6515
6583
|
async function closeLinearDirect(ctx, apiKey) {
|
|
6516
6584
|
const step = "close-issue:transition";
|
|
6585
|
+
if (Date.now() < _linearRateLimitUntil) {
|
|
6586
|
+
const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 6e4);
|
|
6587
|
+
return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);
|
|
6588
|
+
}
|
|
6517
6589
|
try {
|
|
6518
6590
|
const { LinearClient: LinearClient2 } = await import("@linear/sdk");
|
|
6519
6591
|
const client = new LinearClient2({ apiKey });
|
|
@@ -6540,7 +6612,13 @@ async function closeLinearDirect(ctx, apiKey) {
|
|
|
6540
6612
|
}
|
|
6541
6613
|
return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);
|
|
6542
6614
|
} catch (err) {
|
|
6543
|
-
|
|
6615
|
+
const message = err.message;
|
|
6616
|
+
if (message.includes("Rate limit") || message.includes("rate limit") || message.includes("429")) {
|
|
6617
|
+
_linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;
|
|
6618
|
+
console.warn(`[close-issue] Linear rate limit hit \u2014 circuit breaker activated for 1 hour`);
|
|
6619
|
+
return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated \u2014 no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);
|
|
6620
|
+
}
|
|
6621
|
+
return stepFailed(step, `Linear close failed: ${message}`);
|
|
6544
6622
|
}
|
|
6545
6623
|
}
|
|
6546
6624
|
async function closeRallyDirect(ctx) {
|
|
@@ -6595,16 +6673,16 @@ async function applyLabelGitHub(ctx) {
|
|
|
6595
6673
|
if (!ctx.github) return stepSkipped(step);
|
|
6596
6674
|
const { owner, repo, number } = ctx.github;
|
|
6597
6675
|
try {
|
|
6598
|
-
await
|
|
6676
|
+
await execAsync7(
|
|
6599
6677
|
`gh label create "${CLOSED_OUT_LABEL}" --repo ${owner}/${repo} --color "${CLOSED_OUT_COLOR}" --description "Verified and closed out" --force 2>/dev/null || true`,
|
|
6600
6678
|
{ encoding: "utf-8" }
|
|
6601
6679
|
);
|
|
6602
|
-
await
|
|
6680
|
+
await execAsync7(
|
|
6603
6681
|
`gh issue edit ${number} --repo ${owner}/${repo} --add-label "${CLOSED_OUT_LABEL}"`,
|
|
6604
6682
|
{ encoding: "utf-8" }
|
|
6605
6683
|
);
|
|
6606
6684
|
for (const label of WORKFLOW_LABELS2) {
|
|
6607
|
-
await
|
|
6685
|
+
await execAsync7(
|
|
6608
6686
|
`gh issue edit ${number} --repo ${owner}/${repo} --remove-label "${label}" 2>/dev/null || true`,
|
|
6609
6687
|
{ encoding: "utf-8" }
|
|
6610
6688
|
);
|
|
@@ -6661,9 +6739,9 @@ init_paths();
|
|
|
6661
6739
|
init_tmux();
|
|
6662
6740
|
import { existsSync as existsSync28, rmSync as rmSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
6663
6741
|
import { join as join27, basename as basename5 } from "path";
|
|
6664
|
-
import { exec as
|
|
6665
|
-
import { promisify as
|
|
6666
|
-
var
|
|
6742
|
+
import { exec as exec8 } from "child_process";
|
|
6743
|
+
import { promisify as promisify8 } from "util";
|
|
6744
|
+
var execAsync8 = promisify8(exec8);
|
|
6667
6745
|
async function killTmuxSessions(issueLower) {
|
|
6668
6746
|
const step = "teardown:tmux-sessions";
|
|
6669
6747
|
const patterns = [
|
|
@@ -6677,7 +6755,7 @@ async function killTmuxSessions(issueLower) {
|
|
|
6677
6755
|
for (const session of patterns) {
|
|
6678
6756
|
if (sessionExists(session)) {
|
|
6679
6757
|
try {
|
|
6680
|
-
await
|
|
6758
|
+
await execAsync8(`tmux kill-session -t ${session}`);
|
|
6681
6759
|
killed++;
|
|
6682
6760
|
} catch {
|
|
6683
6761
|
}
|
|
@@ -6706,8 +6784,8 @@ async function stopTldrDaemon(workspacePath) {
|
|
|
6706
6784
|
async function stopDocker(workspacePath, projectName, issueLower) {
|
|
6707
6785
|
const step = "teardown:docker";
|
|
6708
6786
|
try {
|
|
6709
|
-
const { stopWorkspaceDocker
|
|
6710
|
-
await
|
|
6787
|
+
const { stopWorkspaceDocker } = await import("../workspace-manager-ALBR62AS.js");
|
|
6788
|
+
await stopWorkspaceDocker(workspacePath, projectName, issueLower);
|
|
6711
6789
|
return stepOk(step, ["Stopped Docker containers"]);
|
|
6712
6790
|
} catch {
|
|
6713
6791
|
return stepSkipped(step, ["Docker cleanup skipped (not running or failed)"]);
|
|
@@ -6719,7 +6797,7 @@ async function removeWorktree(projectPath, workspacePath) {
|
|
|
6719
6797
|
return stepSkipped(step, ["Workspace directory does not exist"]);
|
|
6720
6798
|
}
|
|
6721
6799
|
try {
|
|
6722
|
-
await
|
|
6800
|
+
await execAsync8(`git worktree remove "${workspacePath}" --force`, { cwd: projectPath });
|
|
6723
6801
|
return stepOk(step, ["Removed git worktree"]);
|
|
6724
6802
|
} catch {
|
|
6725
6803
|
try {
|
|
@@ -6753,13 +6831,13 @@ async function deleteBranches(projectPath, issueLower) {
|
|
|
6753
6831
|
const branchName = `feature/${issueLower}`;
|
|
6754
6832
|
const details = [];
|
|
6755
6833
|
try {
|
|
6756
|
-
await
|
|
6834
|
+
await execAsync8(`git branch -D "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
|
|
6757
6835
|
details.push(`Deleted local branch ${branchName}`);
|
|
6758
6836
|
} catch {
|
|
6759
6837
|
details.push(`Local branch ${branchName} not found (already deleted)`);
|
|
6760
6838
|
}
|
|
6761
6839
|
try {
|
|
6762
|
-
await
|
|
6840
|
+
await execAsync8(`git push origin --delete "${branchName}"`, { cwd: projectPath, encoding: "utf-8" });
|
|
6763
6841
|
details.push(`Deleted remote branch ${branchName}`);
|
|
6764
6842
|
} catch {
|
|
6765
6843
|
details.push(`Remote branch ${branchName} not found (already deleted)`);
|
|
@@ -6816,7 +6894,7 @@ function buildPlaceholders(ctx, opts, workspacePath) {
|
|
|
6816
6894
|
async function removeTunnelConfig(tunnelConfig, placeholders) {
|
|
6817
6895
|
const step = "teardown:tunnel";
|
|
6818
6896
|
try {
|
|
6819
|
-
const { removeTunnelIngress } = await import("../tunnel-
|
|
6897
|
+
const { removeTunnelIngress } = await import("../tunnel-BKC7KLBX.js");
|
|
6820
6898
|
const result = await removeTunnelIngress(tunnelConfig, placeholders);
|
|
6821
6899
|
return stepOk(step, result.steps || ["Removed tunnel ingress"]);
|
|
6822
6900
|
} catch (err) {
|
|
@@ -6826,7 +6904,7 @@ async function removeTunnelConfig(tunnelConfig, placeholders) {
|
|
|
6826
6904
|
async function removeHumeEviConfig(humeConfig, placeholders) {
|
|
6827
6905
|
const step = "teardown:hume";
|
|
6828
6906
|
try {
|
|
6829
|
-
const { deleteHumeConfig } = await import("../hume-
|
|
6907
|
+
const { deleteHumeConfig } = await import("../hume-CKJJ3OUU.js");
|
|
6830
6908
|
const result = await deleteHumeConfig(humeConfig, placeholders);
|
|
6831
6909
|
return stepOk(step, result.steps || ["Removed Hume EVI config"]);
|
|
6832
6910
|
} catch (err) {
|
|
@@ -6874,18 +6952,18 @@ async function teardownWorkspace(ctx, opts = {}) {
|
|
|
6874
6952
|
|
|
6875
6953
|
// src/lib/lifecycle/compact-beads.ts
|
|
6876
6954
|
init_esm_shims();
|
|
6877
|
-
import { exec as
|
|
6878
|
-
import { promisify as
|
|
6879
|
-
var
|
|
6955
|
+
import { exec as exec9 } from "child_process";
|
|
6956
|
+
import { promisify as promisify9 } from "util";
|
|
6957
|
+
var execAsync9 = promisify9(exec9);
|
|
6880
6958
|
|
|
6881
6959
|
// src/lib/lifecycle/workflows.ts
|
|
6882
6960
|
init_esm_shims();
|
|
6883
6961
|
init_paths();
|
|
6884
6962
|
import { existsSync as existsSync29, readFileSync as readFileSync25 } from "fs";
|
|
6885
6963
|
import { join as join28 } from "path";
|
|
6886
|
-
import { exec as
|
|
6887
|
-
import { promisify as
|
|
6888
|
-
var
|
|
6964
|
+
import { exec as exec10 } from "child_process";
|
|
6965
|
+
import { promisify as promisify10 } from "util";
|
|
6966
|
+
var execAsync10 = promisify10(exec10);
|
|
6889
6967
|
function buildResult(workflow, issueId, steps, startTime) {
|
|
6890
6968
|
return {
|
|
6891
6969
|
workflow,
|
|
@@ -6928,7 +7006,7 @@ async function verifyBranchMerged(ctx) {
|
|
|
6928
7006
|
const branchName = `feature/${issueLower}`;
|
|
6929
7007
|
try {
|
|
6930
7008
|
try {
|
|
6931
|
-
const { loadReviewStatuses: loadReviewStatuses3 } = await import("../review-status-
|
|
7009
|
+
const { loadReviewStatuses: loadReviewStatuses3 } = await import("../review-status-XKUKZF6J.js");
|
|
6932
7010
|
const statuses = loadReviewStatuses3();
|
|
6933
7011
|
const issueKey = ctx.issueId.toUpperCase();
|
|
6934
7012
|
if (statuses[issueKey]?.mergeStatus === "merged") {
|
|
@@ -6936,19 +7014,19 @@ async function verifyBranchMerged(ctx) {
|
|
|
6936
7014
|
}
|
|
6937
7015
|
} catch {
|
|
6938
7016
|
}
|
|
6939
|
-
const { stdout: branchExists } = await
|
|
7017
|
+
const { stdout: branchExists } = await execAsync10(
|
|
6940
7018
|
`git branch --list "${branchName}" 2>/dev/null || true`,
|
|
6941
7019
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6942
7020
|
);
|
|
6943
7021
|
if (branchExists.trim()) {
|
|
6944
7022
|
try {
|
|
6945
|
-
await
|
|
7023
|
+
await execAsync10(
|
|
6946
7024
|
`git merge-base --is-ancestor ${branchName} main`,
|
|
6947
7025
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6948
7026
|
);
|
|
6949
7027
|
return stepOk(step, ["All commits merged to main"]);
|
|
6950
7028
|
} catch {
|
|
6951
|
-
const { stdout: unmerged } = await
|
|
7029
|
+
const { stdout: unmerged } = await execAsync10(
|
|
6952
7030
|
`git log main..${branchName} --oneline 2>/dev/null || true`,
|
|
6953
7031
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6954
7032
|
);
|
|
@@ -6956,21 +7034,21 @@ async function verifyBranchMerged(ctx) {
|
|
|
6956
7034
|
return stepFailed(step, `${count} unmerged commit(s) on ${branchName}. Merge before closing out.`);
|
|
6957
7035
|
}
|
|
6958
7036
|
}
|
|
6959
|
-
const { stdout: remoteBranch } = await
|
|
7037
|
+
const { stdout: remoteBranch } = await execAsync10(
|
|
6960
7038
|
`git ls-remote --heads origin "${branchName}" 2>/dev/null || true`,
|
|
6961
7039
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6962
7040
|
);
|
|
6963
7041
|
if (remoteBranch.trim()) {
|
|
6964
|
-
await
|
|
7042
|
+
await execAsync10(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {
|
|
6965
7043
|
});
|
|
6966
7044
|
try {
|
|
6967
|
-
await
|
|
7045
|
+
await execAsync10(
|
|
6968
7046
|
`git merge-base --is-ancestor origin/${branchName} main`,
|
|
6969
7047
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6970
7048
|
);
|
|
6971
7049
|
return stepOk(step, ["Remote branch fully merged"]);
|
|
6972
7050
|
} catch {
|
|
6973
|
-
const { stdout: remoteUnmerged } = await
|
|
7051
|
+
const { stdout: remoteUnmerged } = await execAsync10(
|
|
6974
7052
|
`git log main..origin/${branchName} --oneline 2>/dev/null || true`,
|
|
6975
7053
|
{ cwd: ctx.projectPath, encoding: "utf-8" }
|
|
6976
7054
|
);
|
|
@@ -6986,7 +7064,7 @@ async function verifyBranchMerged(ctx) {
|
|
|
6986
7064
|
async function clearReviewStatusStep(issueId) {
|
|
6987
7065
|
const step = "clear-review-status";
|
|
6988
7066
|
try {
|
|
6989
|
-
const { clearReviewStatus: clearReviewStatus2 } = await import("../review-status-
|
|
7067
|
+
const { clearReviewStatus: clearReviewStatus2 } = await import("../review-status-XKUKZF6J.js");
|
|
6990
7068
|
clearReviewStatus2(issueId.toUpperCase());
|
|
6991
7069
|
return stepOk(step, ["Review status cleared"]);
|
|
6992
7070
|
} catch {
|
|
@@ -7010,6 +7088,20 @@ async function clearReviewStatusStep(issueId) {
|
|
|
7010
7088
|
|
|
7011
7089
|
// src/cli/commands/work/close-out.ts
|
|
7012
7090
|
init_projects();
|
|
7091
|
+
function getGitHubConfig2() {
|
|
7092
|
+
const envFile = join29(homedir14(), ".panopticon.env");
|
|
7093
|
+
if (!existsSync30(envFile)) return null;
|
|
7094
|
+
const content = readFileSync26(envFile, "utf-8");
|
|
7095
|
+
const reposMatch = content.match(/GITHUB_REPOS=(.+)/);
|
|
7096
|
+
if (!reposMatch) return null;
|
|
7097
|
+
const repoStr = reposMatch[1].trim();
|
|
7098
|
+
const parts = repoStr.split(",")[0];
|
|
7099
|
+
if (!parts) return null;
|
|
7100
|
+
const [ownerRepo, prefix] = parts.split(":");
|
|
7101
|
+
const [owner, repo] = ownerRepo.split("/");
|
|
7102
|
+
if (!owner || !repo) return null;
|
|
7103
|
+
return { owner, repo, prefix: prefix || repo.toUpperCase().replace(/-CLI$/, "").replace(/-/g, "") };
|
|
7104
|
+
}
|
|
7013
7105
|
async function closeOutCommand(issueId, options) {
|
|
7014
7106
|
if (process.env.PANOPTICON_AGENT_ID) {
|
|
7015
7107
|
console.error(chalk31.red("Close-out is a human-only operation. Agents cannot close out issues."));
|
|
@@ -7032,11 +7124,22 @@ async function closeOutCommand(issueId, options) {
|
|
|
7032
7124
|
console.error(chalk31.red(`Could not resolve project for ${issueId}`));
|
|
7033
7125
|
process.exit(1);
|
|
7034
7126
|
}
|
|
7035
|
-
const
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7127
|
+
const isGitHub = issueUpper.startsWith("PAN-");
|
|
7128
|
+
let owner;
|
|
7129
|
+
let repo;
|
|
7130
|
+
let number;
|
|
7131
|
+
if (isGitHub) {
|
|
7132
|
+
const ghConfig = getGitHubConfig2();
|
|
7133
|
+
if (ghConfig) {
|
|
7134
|
+
owner = ghConfig.owner;
|
|
7135
|
+
repo = ghConfig.repo;
|
|
7136
|
+
number = parseInt(issueId.replace(/^PAN-/i, ""), 10);
|
|
7137
|
+
} else {
|
|
7138
|
+
owner = "eltmon";
|
|
7139
|
+
repo = "panopticon-cli";
|
|
7140
|
+
number = parseInt(issueId.replace(/^PAN-/i, ""), 10);
|
|
7141
|
+
}
|
|
7142
|
+
}
|
|
7040
7143
|
if (!options.force) {
|
|
7041
7144
|
console.log(chalk31.yellow(`
|
|
7042
7145
|
Close-out ceremony for ${issueUpper}
|
|
@@ -7095,13 +7198,13 @@ Running close-out for ${issueUpper}...
|
|
|
7095
7198
|
// src/cli/commands/work/linear-states.ts
|
|
7096
7199
|
init_esm_shims();
|
|
7097
7200
|
import chalk32 from "chalk";
|
|
7098
|
-
import { readFileSync as
|
|
7099
|
-
import { homedir as
|
|
7100
|
-
import { join as
|
|
7201
|
+
import { readFileSync as readFileSync27, existsSync as existsSync31 } from "fs";
|
|
7202
|
+
import { homedir as homedir15 } from "os";
|
|
7203
|
+
import { join as join30 } from "path";
|
|
7101
7204
|
function getLinearApiKey8() {
|
|
7102
|
-
const envFile =
|
|
7103
|
-
if (
|
|
7104
|
-
const content =
|
|
7205
|
+
const envFile = join30(homedir15(), ".panopticon.env");
|
|
7206
|
+
if (existsSync31(envFile)) {
|
|
7207
|
+
const content = readFileSync27(envFile, "utf-8");
|
|
7105
7208
|
const match = content.match(/LINEAR_API_KEY=(.+)/);
|
|
7106
7209
|
if (match) return match[1].trim();
|
|
7107
7210
|
}
|
|
@@ -7290,8 +7393,8 @@ function registerWorkCommands(program2) {
|
|
|
7290
7393
|
init_esm_shims();
|
|
7291
7394
|
import chalk33 from "chalk";
|
|
7292
7395
|
import ora16 from "ora";
|
|
7293
|
-
import { existsSync as
|
|
7294
|
-
import { join as
|
|
7396
|
+
import { existsSync as existsSync32, writeFileSync as writeFileSync10, rmSync as rmSync4, readFileSync as readFileSync28, realpathSync } from "fs";
|
|
7397
|
+
import { join as join31, basename as basename6 } from "path";
|
|
7295
7398
|
|
|
7296
7399
|
// src/lib/worktree.ts
|
|
7297
7400
|
init_esm_shims();
|
|
@@ -7354,13 +7457,13 @@ init_agents();
|
|
|
7354
7457
|
init_projects();
|
|
7355
7458
|
init_workspace_manager();
|
|
7356
7459
|
init_config();
|
|
7357
|
-
import { exec as
|
|
7358
|
-
import { promisify as
|
|
7359
|
-
import { homedir as
|
|
7360
|
-
var
|
|
7460
|
+
import { exec as exec11 } from "child_process";
|
|
7461
|
+
import { promisify as promisify11 } from "util";
|
|
7462
|
+
import { homedir as homedir16 } from "os";
|
|
7463
|
+
var execAsync11 = promisify11(exec11);
|
|
7361
7464
|
async function getBeadsVersion() {
|
|
7362
7465
|
try {
|
|
7363
|
-
const { stdout } = await
|
|
7466
|
+
const { stdout } = await execAsync11("bd --version", { encoding: "utf-8" });
|
|
7364
7467
|
const match = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
7365
7468
|
if (match) {
|
|
7366
7469
|
const [, , minor, patch] = match.map(Number);
|
|
@@ -7386,21 +7489,21 @@ async function initializeWorkspaceBeads(workspacePath, issueId) {
|
|
|
7386
7489
|
if (beadsVersion >= 4701) {
|
|
7387
7490
|
const workspaceLabel = `workspace:${issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
|
|
7388
7491
|
const title = `${issueId.toUpperCase()}: Implementation`;
|
|
7389
|
-
const { stdout } = await
|
|
7492
|
+
const { stdout } = await execAsync11(
|
|
7390
7493
|
`bd create --title "${title}" --priority 1 --type task --labels "${workspaceLabel}" 2>&1`,
|
|
7391
7494
|
{ cwd: workspacePath, encoding: "utf-8" }
|
|
7392
7495
|
);
|
|
7393
7496
|
const match = stdout.match(/([a-z]+-[a-z0-9]+)/);
|
|
7394
7497
|
return { success: true, beadId: match?.[1] };
|
|
7395
7498
|
} else {
|
|
7396
|
-
const beadsDir =
|
|
7397
|
-
if (
|
|
7499
|
+
const beadsDir = join31(workspacePath, ".beads");
|
|
7500
|
+
if (existsSync32(beadsDir)) {
|
|
7398
7501
|
rmSync4(beadsDir, { recursive: true, force: true });
|
|
7399
7502
|
}
|
|
7400
7503
|
const prefix = "workspace";
|
|
7401
|
-
await
|
|
7504
|
+
await execAsync11(`bd init --prefix ${prefix}`, { cwd: workspacePath, encoding: "utf-8" });
|
|
7402
7505
|
const title = `${issueId.toUpperCase()}: Implementation`;
|
|
7403
|
-
const { stdout } = await
|
|
7506
|
+
const { stdout } = await execAsync11(
|
|
7404
7507
|
`bd create --title "${title}" --priority 1 --type task --json`,
|
|
7405
7508
|
{ cwd: workspacePath, encoding: "utf-8" }
|
|
7406
7509
|
);
|
|
@@ -7523,7 +7626,7 @@ async function createCommand(issueId, options) {
|
|
|
7523
7626
|
const dockerFlag = options.docker ? " --docker" : "";
|
|
7524
7627
|
const cmd = `${projectConfig.workspace_command} ${normalizedId}${dockerFlag}`;
|
|
7525
7628
|
try {
|
|
7526
|
-
const { stdout } = await
|
|
7629
|
+
const { stdout } = await execAsync11(cmd, {
|
|
7527
7630
|
cwd: projectConfig.path,
|
|
7528
7631
|
encoding: "utf-8",
|
|
7529
7632
|
timeout: options.docker ? 3e5 : 12e4
|
|
@@ -7559,8 +7662,8 @@ async function createCommand(issueId, options) {
|
|
|
7559
7662
|
projectRoot = process.cwd();
|
|
7560
7663
|
}
|
|
7561
7664
|
}
|
|
7562
|
-
const workspacesDir =
|
|
7563
|
-
const workspacePath =
|
|
7665
|
+
const workspacesDir = join31(projectRoot, "workspaces");
|
|
7666
|
+
const workspacePath = join31(workspacesDir, folderName);
|
|
7564
7667
|
if (options.dryRun) {
|
|
7565
7668
|
spinner.info("Dry run mode");
|
|
7566
7669
|
console.log("");
|
|
@@ -7573,11 +7676,11 @@ async function createCommand(issueId, options) {
|
|
|
7573
7676
|
console.log(` Branch: ${chalk33.cyan(branchName)}`);
|
|
7574
7677
|
return;
|
|
7575
7678
|
}
|
|
7576
|
-
if (
|
|
7679
|
+
if (existsSync32(workspacePath)) {
|
|
7577
7680
|
spinner.fail(`Workspace already exists: ${workspacePath}`);
|
|
7578
7681
|
process.exit(1);
|
|
7579
7682
|
}
|
|
7580
|
-
if (!
|
|
7683
|
+
if (!existsSync32(join31(projectRoot, ".git"))) {
|
|
7581
7684
|
spinner.fail("Not a git repository. Run this from the project root.");
|
|
7582
7685
|
process.exit(1);
|
|
7583
7686
|
}
|
|
@@ -7601,7 +7704,7 @@ async function createCommand(issueId, options) {
|
|
|
7601
7704
|
BEAD_ID: workspaceBeadId
|
|
7602
7705
|
};
|
|
7603
7706
|
const claudeMd = generateClaudeMd(projectRoot, variables);
|
|
7604
|
-
writeFileSync10(
|
|
7707
|
+
writeFileSync10(join31(workspacePath, "CLAUDE.md"), claudeMd);
|
|
7605
7708
|
let skillsResult = { added: [], updated: [], skipped: [], overlayed: [] };
|
|
7606
7709
|
if (options.skills !== false) {
|
|
7607
7710
|
spinner.text = "Merging skills and agents...";
|
|
@@ -7611,20 +7714,20 @@ async function createCommand(issueId, options) {
|
|
|
7611
7714
|
let dockerError;
|
|
7612
7715
|
if (options.docker) {
|
|
7613
7716
|
const composeLocations = [
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7717
|
+
join31(workspacePath, "docker-compose.yml"),
|
|
7718
|
+
join31(workspacePath, "docker-compose.yaml"),
|
|
7719
|
+
join31(workspacePath, ".devcontainer", "docker-compose.yml"),
|
|
7720
|
+
join31(workspacePath, ".devcontainer", "docker-compose.yaml"),
|
|
7721
|
+
join31(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml"),
|
|
7722
|
+
join31(workspacePath, ".devcontainer", "compose.yml"),
|
|
7723
|
+
join31(workspacePath, ".devcontainer", "compose.yaml")
|
|
7621
7724
|
];
|
|
7622
|
-
const composeFile = composeLocations.find((f) =>
|
|
7725
|
+
const composeFile = composeLocations.find((f) => existsSync32(f));
|
|
7623
7726
|
if (composeFile) {
|
|
7624
7727
|
spinner.text = "Starting Docker containers...";
|
|
7625
7728
|
try {
|
|
7626
|
-
const composeDir =
|
|
7627
|
-
await
|
|
7729
|
+
const composeDir = join31(composeFile, "..");
|
|
7730
|
+
await execAsync11(`docker compose -f "${composeFile}" up -d --build`, {
|
|
7628
7731
|
cwd: composeDir,
|
|
7629
7732
|
encoding: "utf-8",
|
|
7630
7733
|
timeout: 3e5
|
|
@@ -7690,15 +7793,15 @@ async function listCommand2(options) {
|
|
|
7690
7793
|
const workspaces2 = [];
|
|
7691
7794
|
if (isPolyrepo && config2.workspace?.repos) {
|
|
7692
7795
|
for (const repo of config2.workspace.repos) {
|
|
7693
|
-
const repoPath =
|
|
7694
|
-
if (!
|
|
7796
|
+
const repoPath = join31(config2.path, repo.path);
|
|
7797
|
+
if (!existsSync32(join31(repoPath, ".git"))) continue;
|
|
7695
7798
|
const repoWorktrees = listWorktrees(repoPath);
|
|
7696
7799
|
for (const wt of repoWorktrees) {
|
|
7697
7800
|
if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
|
|
7698
7801
|
const parts = wt.path.split("/workspaces/");
|
|
7699
7802
|
if (parts.length > 1) {
|
|
7700
7803
|
const workspaceDir = parts[1].split("/")[0];
|
|
7701
|
-
const canonicalPath =
|
|
7804
|
+
const canonicalPath = join31(config2.path, "workspaces", workspaceDir);
|
|
7702
7805
|
if (!workspaces2.some((w) => w.path === canonicalPath)) {
|
|
7703
7806
|
workspaces2.push({ ...wt, path: canonicalPath });
|
|
7704
7807
|
}
|
|
@@ -7707,7 +7810,7 @@ async function listCommand2(options) {
|
|
|
7707
7810
|
}
|
|
7708
7811
|
}
|
|
7709
7812
|
} else {
|
|
7710
|
-
if (!
|
|
7813
|
+
if (!existsSync32(join31(config2.path, ".git"))) continue;
|
|
7711
7814
|
const worktrees2 = listWorktrees(config2.path);
|
|
7712
7815
|
for (const wt of worktrees2) {
|
|
7713
7816
|
if (wt.path.includes("/workspaces/") || wt.path.includes("\\workspaces\\")) {
|
|
@@ -7747,7 +7850,7 @@ ${proj.projectName}
|
|
|
7747
7850
|
return;
|
|
7748
7851
|
}
|
|
7749
7852
|
const projectRoot = process.cwd();
|
|
7750
|
-
if (!
|
|
7853
|
+
if (!existsSync32(join31(projectRoot, ".git"))) {
|
|
7751
7854
|
console.error(chalk33.red("Not a git repository."));
|
|
7752
7855
|
if (projects.length > 0) {
|
|
7753
7856
|
console.log(chalk33.dim("Tip: Use --all to list workspaces across all registered projects."));
|
|
@@ -7817,7 +7920,7 @@ async function destroyCommand(issueId, options) {
|
|
|
7817
7920
|
spinner.text = "Running custom remove command...";
|
|
7818
7921
|
const cmd = `${projectConfig.workspace_remove_command} ${normalizedId}`;
|
|
7819
7922
|
try {
|
|
7820
|
-
const { stdout } = await
|
|
7923
|
+
const { stdout } = await execAsync11(cmd, {
|
|
7821
7924
|
cwd: projectConfig.path,
|
|
7822
7925
|
encoding: "utf-8",
|
|
7823
7926
|
timeout: 12e4
|
|
@@ -7843,17 +7946,17 @@ async function destroyCommand(issueId, options) {
|
|
|
7843
7946
|
projectRoot = process.cwd();
|
|
7844
7947
|
}
|
|
7845
7948
|
}
|
|
7846
|
-
const workspacePath =
|
|
7847
|
-
if (!
|
|
7848
|
-
const cwdPath =
|
|
7849
|
-
if (projectRoot !== process.cwd() &&
|
|
7949
|
+
const workspacePath = join31(projectRoot, "workspaces", folderName);
|
|
7950
|
+
if (!existsSync32(workspacePath)) {
|
|
7951
|
+
const cwdPath = join31(process.cwd(), "workspaces", folderName);
|
|
7952
|
+
if (projectRoot !== process.cwd() && existsSync32(cwdPath)) {
|
|
7850
7953
|
projectRoot = process.cwd();
|
|
7851
7954
|
} else {
|
|
7852
7955
|
spinner.fail(`Workspace not found: ${workspacePath}`);
|
|
7853
7956
|
process.exit(1);
|
|
7854
7957
|
}
|
|
7855
7958
|
}
|
|
7856
|
-
const finalWorkspacePath =
|
|
7959
|
+
const finalWorkspacePath = join31(projectRoot, "workspaces", folderName);
|
|
7857
7960
|
spinner.text = "Removing git worktree...";
|
|
7858
7961
|
removeWorktree2(projectRoot, finalWorkspacePath);
|
|
7859
7962
|
spinner.succeed(`Workspace destroyed: ${folderName}`);
|
|
@@ -7892,7 +7995,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7892
7995
|
}
|
|
7893
7996
|
if (!projectId) {
|
|
7894
7997
|
try {
|
|
7895
|
-
const { stdout } = await
|
|
7998
|
+
const { stdout } = await execAsync11("git remote get-url origin", {
|
|
7896
7999
|
cwd: projectRoot,
|
|
7897
8000
|
encoding: "utf-8"
|
|
7898
8001
|
});
|
|
@@ -7918,7 +8021,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7918
8021
|
await exe.createVm(vmName);
|
|
7919
8022
|
let repoUrl = "";
|
|
7920
8023
|
try {
|
|
7921
|
-
const { stdout } = await
|
|
8024
|
+
const { stdout } = await execAsync11("git remote get-url origin", {
|
|
7922
8025
|
cwd: projectRoot,
|
|
7923
8026
|
encoding: "utf-8"
|
|
7924
8027
|
});
|
|
@@ -7939,13 +8042,13 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7939
8042
|
await exe.ssh(vmName, `ssh-keyscan -t ed25519,rsa ${gitHost} >> ~/.ssh/known_hosts 2>/dev/null`);
|
|
7940
8043
|
}
|
|
7941
8044
|
const sshKeyPaths = [
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
8045
|
+
join31(homedir16(), ".panopticon", "ssh", "exe-dev-key"),
|
|
8046
|
+
join31(homedir16(), ".ssh", "id_ed25519"),
|
|
8047
|
+
join31(homedir16(), ".ssh", "id_rsa")
|
|
7945
8048
|
];
|
|
7946
|
-
const sshKeyPath = sshKeyPaths.find((p) =>
|
|
8049
|
+
const sshKeyPath = sshKeyPaths.find((p) => existsSync32(p));
|
|
7947
8050
|
if (sshKeyPath) {
|
|
7948
|
-
const sshKeyBase64 = Buffer.from(
|
|
8051
|
+
const sshKeyBase64 = Buffer.from(readFileSync28(sshKeyPath, "utf-8")).toString("base64");
|
|
7949
8052
|
const keyFilename = sshKeyPath.includes("id_rsa") ? "id_rsa" : "id_ed25519";
|
|
7950
8053
|
await exe.ssh(vmName, `echo '${sshKeyBase64}' | base64 -d > ~/.ssh/${keyFilename} && chmod 600 ~/.ssh/${keyFilename}`);
|
|
7951
8054
|
}
|
|
@@ -7961,11 +8064,11 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
7961
8064
|
await exe.ssh(vmName, "mkdir -p ~/workspace");
|
|
7962
8065
|
for (const repo of projectConfig.workspace.repos) {
|
|
7963
8066
|
spinner.text = `Cloning ${repo.name}...`;
|
|
7964
|
-
const rawRepoPath =
|
|
7965
|
-
const actualRepoPath =
|
|
8067
|
+
const rawRepoPath = join31(projectRoot, repo.path);
|
|
8068
|
+
const actualRepoPath = existsSync32(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;
|
|
7966
8069
|
let repoRemoteUrl;
|
|
7967
8070
|
try {
|
|
7968
|
-
const { stdout } = await
|
|
8071
|
+
const { stdout } = await execAsync11("git remote get-url origin", {
|
|
7969
8072
|
cwd: actualRepoPath,
|
|
7970
8073
|
encoding: "utf-8"
|
|
7971
8074
|
});
|
|
@@ -8001,7 +8104,7 @@ async function createRemoteWorkspace(issueId, normalizedId, branchName, spinner,
|
|
|
8001
8104
|
spinner.text = "Configuring Claude Code...";
|
|
8002
8105
|
await exe.ssh(vmName, `mkdir -p ~/.claude`);
|
|
8003
8106
|
try {
|
|
8004
|
-
const { stdout: credentials } = await
|
|
8107
|
+
const { stdout: credentials } = await execAsync11(
|
|
8005
8108
|
'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
|
|
8006
8109
|
{ encoding: "utf-8" }
|
|
8007
8110
|
);
|
|
@@ -8153,8 +8256,8 @@ async function migrateCommand(issueId, options) {
|
|
|
8153
8256
|
}
|
|
8154
8257
|
spinner.text = "Syncing beads...";
|
|
8155
8258
|
try {
|
|
8156
|
-
await
|
|
8157
|
-
await
|
|
8259
|
+
await execAsync11("bd sync", { encoding: "utf-8" });
|
|
8260
|
+
await execAsync11('git add .beads/ && git commit -m "Sync beads before migration" && git push', { encoding: "utf-8" });
|
|
8158
8261
|
} catch {
|
|
8159
8262
|
}
|
|
8160
8263
|
const branchName = `feature/${normalizedId}`;
|
|
@@ -8295,8 +8398,8 @@ async function destroyRemoteWorkspace(issueId, normalizedId, metadata, spinner,
|
|
|
8295
8398
|
} catch {
|
|
8296
8399
|
}
|
|
8297
8400
|
}
|
|
8298
|
-
const metadataFile =
|
|
8299
|
-
if (
|
|
8401
|
+
const metadataFile = join31(WORKSPACES_DIR, `${normalizedId}.yaml`);
|
|
8402
|
+
if (existsSync32(metadataFile)) {
|
|
8300
8403
|
rmSync4(metadataFile);
|
|
8301
8404
|
}
|
|
8302
8405
|
spinner.succeed(`Remote workspace ${issueId} destroyed`);
|
|
@@ -8322,9 +8425,9 @@ async function updateCommand(issueId, options) {
|
|
|
8322
8425
|
process.exit(1);
|
|
8323
8426
|
}
|
|
8324
8427
|
const workspaceConfig = projectConfig.workspace;
|
|
8325
|
-
const workspacesDir =
|
|
8326
|
-
const workspacePath =
|
|
8327
|
-
if (!
|
|
8428
|
+
const workspacesDir = join31(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
|
|
8429
|
+
const workspacePath = join31(workspacesDir, folderName);
|
|
8430
|
+
if (!existsSync32(workspacePath)) {
|
|
8328
8431
|
spinner.fail(`Workspace not found: ${workspacePath}`);
|
|
8329
8432
|
process.exit(1);
|
|
8330
8433
|
}
|
|
@@ -8344,7 +8447,7 @@ async function updateCommand(issueId, options) {
|
|
|
8344
8447
|
const result = mergeSkillsIntoWorkspace(workspacePath);
|
|
8345
8448
|
if (workspaceConfig?.agent?.template_dir && (workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks)) {
|
|
8346
8449
|
spinner.text = "Applying project template overlay...";
|
|
8347
|
-
const templateDir =
|
|
8450
|
+
const templateDir = join31(projectConfig.path, workspaceConfig.agent.template_dir);
|
|
8348
8451
|
const overlayed = applyProjectTemplateOverlay(workspacePath, templateDir);
|
|
8349
8452
|
result.overlayed = overlayed;
|
|
8350
8453
|
}
|
|
@@ -8376,12 +8479,12 @@ import ora17 from "ora";
|
|
|
8376
8479
|
// src/lib/test-runner.ts
|
|
8377
8480
|
init_esm_shims();
|
|
8378
8481
|
init_workspace_config();
|
|
8379
|
-
import { existsSync as
|
|
8380
|
-
import { join as
|
|
8381
|
-
import { exec as
|
|
8382
|
-
import { promisify as
|
|
8383
|
-
import { homedir as
|
|
8384
|
-
var
|
|
8482
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
8483
|
+
import { join as join32, basename as basename7 } from "path";
|
|
8484
|
+
import { exec as exec12 } from "child_process";
|
|
8485
|
+
import { promisify as promisify12 } from "util";
|
|
8486
|
+
import { homedir as homedir17 } from "os";
|
|
8487
|
+
var execAsync12 = promisify12(exec12);
|
|
8385
8488
|
function formatDuration(seconds) {
|
|
8386
8489
|
const minutes = Math.floor(seconds / 60);
|
|
8387
8490
|
const secs = seconds % 60;
|
|
@@ -8431,8 +8534,8 @@ function parseTestOutput(output, type) {
|
|
|
8431
8534
|
return { passed, failed };
|
|
8432
8535
|
}
|
|
8433
8536
|
async function runTestSuite(testName, testConfig, workspacePath, placeholders, reportsDir, timestamp) {
|
|
8434
|
-
const testPath =
|
|
8435
|
-
const logFile =
|
|
8537
|
+
const testPath = join32(workspacePath, testConfig.path);
|
|
8538
|
+
const logFile = join32(reportsDir, `${testName}-${timestamp}.log`);
|
|
8436
8539
|
const result = {
|
|
8437
8540
|
name: testName,
|
|
8438
8541
|
status: "pending",
|
|
@@ -8455,7 +8558,7 @@ async function runTestSuite(testName, testConfig, workspacePath, placeholders, r
|
|
|
8455
8558
|
}
|
|
8456
8559
|
const startTime = Date.now();
|
|
8457
8560
|
try {
|
|
8458
|
-
const { stdout, stderr } = await
|
|
8561
|
+
const { stdout, stderr } = await execAsync12(command, {
|
|
8459
8562
|
cwd: testPath,
|
|
8460
8563
|
env,
|
|
8461
8564
|
timeout: 6e5,
|
|
@@ -8483,7 +8586,7 @@ async function runTestSuite(testName, testConfig, workspacePath, placeholders, r
|
|
|
8483
8586
|
result.duration = formatDuration(Math.floor((endTime - startTime) / 1e3));
|
|
8484
8587
|
return result;
|
|
8485
8588
|
}
|
|
8486
|
-
function
|
|
8589
|
+
function generateReport2(result) {
|
|
8487
8590
|
const lines = [
|
|
8488
8591
|
`# Test Run Report - ${result.target}`,
|
|
8489
8592
|
"",
|
|
@@ -8524,10 +8627,10 @@ function generateReport(result) {
|
|
|
8524
8627
|
async function sendNotification(result) {
|
|
8525
8628
|
const title = `Tests (${result.target}): ${result.overallStatus === "passed" ? "\u2705 All Passed" : "\u274C Failed"}`;
|
|
8526
8629
|
const message = result.overallStatus === "passed" ? "All test suites passed" : `${result.totalFailures} suite(s) failed. Check report: ${result.reportFile}`;
|
|
8527
|
-
const notifyScript =
|
|
8528
|
-
if (
|
|
8630
|
+
const notifyScript = join32(homedir17(), ".panopticon", "bin", "notify-complete");
|
|
8631
|
+
if (existsSync33(notifyScript)) {
|
|
8529
8632
|
try {
|
|
8530
|
-
await
|
|
8633
|
+
await execAsync12(`"${notifyScript}" "${result.target}" "${message}"`);
|
|
8531
8634
|
} catch {
|
|
8532
8635
|
}
|
|
8533
8636
|
}
|
|
@@ -8543,12 +8646,12 @@ async function runTests(options) {
|
|
|
8543
8646
|
let target;
|
|
8544
8647
|
let baseUrl;
|
|
8545
8648
|
if (featureName) {
|
|
8546
|
-
const workspacesDir =
|
|
8649
|
+
const workspacesDir = join32(projectConfig.path, workspaceConfig?.workspaces_dir || "workspaces");
|
|
8547
8650
|
const featureFolder2 = `feature-${featureName}`;
|
|
8548
|
-
workspacePath =
|
|
8651
|
+
workspacePath = join32(workspacesDir, featureFolder2);
|
|
8549
8652
|
target = featureFolder2;
|
|
8550
8653
|
baseUrl = workspaceConfig?.dns?.domain ? `https://${featureFolder2}.${workspaceConfig.dns.domain}` : `http://localhost:3000`;
|
|
8551
|
-
if (!
|
|
8654
|
+
if (!existsSync33(workspacePath)) {
|
|
8552
8655
|
throw new Error(`Workspace not found: ${workspacePath}`);
|
|
8553
8656
|
}
|
|
8554
8657
|
} else {
|
|
@@ -8565,12 +8668,13 @@ async function runTests(options) {
|
|
|
8565
8668
|
DOMAIN: workspaceConfig?.dns?.domain || "localhost",
|
|
8566
8669
|
PROJECT_NAME: basename7(projectConfig.path),
|
|
8567
8670
|
PROJECT_PATH: projectConfig.path,
|
|
8671
|
+
PROJECTS_DIR: join32(projectConfig.path, ".."),
|
|
8568
8672
|
WORKSPACE_PATH: workspacePath
|
|
8569
8673
|
};
|
|
8570
|
-
const reportsDir =
|
|
8674
|
+
const reportsDir = join32(projectConfig.path, "reports");
|
|
8571
8675
|
mkdirSync13(reportsDir, { recursive: true });
|
|
8572
8676
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
8573
|
-
const reportFile =
|
|
8677
|
+
const reportFile = join32(reportsDir, `test-run-${target}-${timestamp}.md`);
|
|
8574
8678
|
const result = {
|
|
8575
8679
|
target,
|
|
8576
8680
|
baseUrl,
|
|
@@ -8608,7 +8712,7 @@ async function runTests(options) {
|
|
|
8608
8712
|
console.log("");
|
|
8609
8713
|
}
|
|
8610
8714
|
result.overallStatus = result.totalFailures === 0 ? "passed" : "failed";
|
|
8611
|
-
const report =
|
|
8715
|
+
const report = generateReport2(result);
|
|
8612
8716
|
writeFileSync11(reportFile, report);
|
|
8613
8717
|
console.log("==============================================");
|
|
8614
8718
|
console.log(`COMPLETE: ${result.overallStatus === "passed" ? "\u2705 ALL PASSED" : "\u274C FAILED"}`);
|
|
@@ -8734,21 +8838,21 @@ import chalk35 from "chalk";
|
|
|
8734
8838
|
import ora18 from "ora";
|
|
8735
8839
|
import inquirer5 from "inquirer";
|
|
8736
8840
|
import { execSync as execSync4 } from "child_process";
|
|
8737
|
-
import { existsSync as
|
|
8738
|
-
import { join as
|
|
8739
|
-
import { homedir as
|
|
8841
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync12, readFileSync as readFileSync29, copyFileSync, readdirSync as readdirSync15, statSync as statSync6 } from "fs";
|
|
8842
|
+
import { join as join33 } from "path";
|
|
8843
|
+
import { homedir as homedir18 } from "os";
|
|
8740
8844
|
function registerInstallCommand(program2) {
|
|
8741
8845
|
program2.command("install").description("Install Panopticon prerequisites").option("--check", "Check prerequisites only").option("--minimal", "Skip Traefik and mkcert (use port-based routing)").option("--skip-mkcert", "Skip mkcert/HTTPS setup").option("--skip-docker", "Skip Docker network setup").option("--skip-beads", "Skip beads CLI installation").option("--skip-router", "Skip claude-code-router installation").option("--skip-sageox", "Skip SageOx CLI installation").action(installCommand);
|
|
8742
8846
|
}
|
|
8743
8847
|
function copyDirectoryRecursive(source, dest) {
|
|
8744
|
-
if (!
|
|
8848
|
+
if (!existsSync34(source)) {
|
|
8745
8849
|
throw new Error(`Source directory not found: ${source}`);
|
|
8746
8850
|
}
|
|
8747
8851
|
mkdirSync14(dest, { recursive: true });
|
|
8748
8852
|
const entries = readdirSync15(source);
|
|
8749
8853
|
for (const entry of entries) {
|
|
8750
|
-
const sourcePath =
|
|
8751
|
-
const destPath =
|
|
8854
|
+
const sourcePath = join33(source, entry);
|
|
8855
|
+
const destPath = join33(dest, entry);
|
|
8752
8856
|
const stat = statSync6(sourcePath);
|
|
8753
8857
|
if (stat.isDirectory()) {
|
|
8754
8858
|
copyDirectoryRecursive(sourcePath, destPath);
|
|
@@ -8848,7 +8952,7 @@ function checkPrerequisites() {
|
|
|
8848
8952
|
message: hasJq ? "installed" : "not found",
|
|
8849
8953
|
fix: "apt install jq / brew install jq"
|
|
8850
8954
|
});
|
|
8851
|
-
const hasTtyd = checkCommand2("ttyd") ||
|
|
8955
|
+
const hasTtyd = checkCommand2("ttyd") || existsSync34(join33(homedir18(), "bin", "ttyd"));
|
|
8852
8956
|
results.push({
|
|
8853
8957
|
name: "ttyd",
|
|
8854
8958
|
passed: hasTtyd,
|
|
@@ -8924,9 +9028,9 @@ async function installCommand(options) {
|
|
|
8924
9028
|
execSync4("brew install mkcert", { stdio: "pipe", timeout: 12e4 });
|
|
8925
9029
|
spinner.succeed("mkcert installed via Homebrew");
|
|
8926
9030
|
} else {
|
|
8927
|
-
const binDir =
|
|
9031
|
+
const binDir = join33(homedir18(), ".local", "bin");
|
|
8928
9032
|
mkdirSync14(binDir, { recursive: true });
|
|
8929
|
-
const mkcertPath =
|
|
9033
|
+
const mkcertPath = join33(binDir, "mkcert");
|
|
8930
9034
|
const arch = process.arch === "x64" ? "amd64" : process.arch;
|
|
8931
9035
|
execSync4(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, {
|
|
8932
9036
|
stdio: "pipe",
|
|
@@ -8945,14 +9049,14 @@ async function installCommand(options) {
|
|
|
8945
9049
|
execSync4("mkcert -install", { stdio: "pipe" });
|
|
8946
9050
|
spinner.succeed("mkcert CA installed");
|
|
8947
9051
|
spinner.start("Generating wildcard certificates...");
|
|
8948
|
-
const traefikCertFile =
|
|
8949
|
-
const traefikKeyFile =
|
|
9052
|
+
const traefikCertFile = join33(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost.pem");
|
|
9053
|
+
const traefikKeyFile = join33(TRAEFIK_CERTS_DIR, "_wildcard.pan.localhost-key.pem");
|
|
8950
9054
|
execSync4(
|
|
8951
9055
|
`mkcert -cert-file "${traefikCertFile}" -key-file "${traefikKeyFile}" "pan.localhost" "*.pan.localhost" "*.localhost" localhost 127.0.0.1 ::1`,
|
|
8952
9056
|
{ stdio: "pipe" }
|
|
8953
9057
|
);
|
|
8954
|
-
const legacyCertFile =
|
|
8955
|
-
const legacyKeyFile =
|
|
9058
|
+
const legacyCertFile = join33(CERTS_DIR, "localhost.pem");
|
|
9059
|
+
const legacyKeyFile = join33(CERTS_DIR, "localhost-key.pem");
|
|
8956
9060
|
copyFileSync(traefikCertFile, legacyCertFile);
|
|
8957
9061
|
copyFileSync(traefikKeyFile, legacyKeyFile);
|
|
8958
9062
|
spinner.succeed("Wildcard certificates generated (*.pan.localhost, *.localhost)");
|
|
@@ -8970,13 +9074,13 @@ async function installCommand(options) {
|
|
|
8970
9074
|
spinner.info("Skipping mkcert (not installed)");
|
|
8971
9075
|
}
|
|
8972
9076
|
}
|
|
8973
|
-
const hasTtyd = checkCommand2("ttyd") ||
|
|
9077
|
+
const hasTtyd = checkCommand2("ttyd") || existsSync34(join33(homedir18(), "bin", "ttyd"));
|
|
8974
9078
|
if (!hasTtyd) {
|
|
8975
9079
|
spinner.start("Installing ttyd (web terminal)...");
|
|
8976
9080
|
try {
|
|
8977
|
-
const binDir =
|
|
9081
|
+
const binDir = join33(homedir18(), "bin");
|
|
8978
9082
|
mkdirSync14(binDir, { recursive: true });
|
|
8979
|
-
const ttydPath =
|
|
9083
|
+
const ttydPath = join33(binDir, "ttyd");
|
|
8980
9084
|
const plat2 = detectPlatform();
|
|
8981
9085
|
let downloadUrl = "";
|
|
8982
9086
|
if (plat2 === "darwin") {
|
|
@@ -9088,9 +9192,9 @@ async function installCommand(options) {
|
|
|
9088
9192
|
if (!hasOxNow) {
|
|
9089
9193
|
spinner.start("Installing SageOx CLI (ox)...");
|
|
9090
9194
|
try {
|
|
9091
|
-
const binDir =
|
|
9195
|
+
const binDir = join33(homedir18(), ".local", "bin");
|
|
9092
9196
|
mkdirSync14(binDir, { recursive: true });
|
|
9093
|
-
const oxPath =
|
|
9197
|
+
const oxPath = join33(binDir, "ox");
|
|
9094
9198
|
const arch = process.arch === "x64" ? "amd64" : process.arch;
|
|
9095
9199
|
const plat2 = detectPlatform();
|
|
9096
9200
|
const platform2 = plat2 === "darwin" ? "darwin" : "linux";
|
|
@@ -9109,7 +9213,7 @@ async function installCommand(options) {
|
|
|
9109
9213
|
if (!options.minimal) {
|
|
9110
9214
|
spinner.start("Setting up Traefik configuration...");
|
|
9111
9215
|
try {
|
|
9112
|
-
if (!
|
|
9216
|
+
if (!existsSync34(join33(TRAEFIK_DIR, "docker-compose.yml"))) {
|
|
9113
9217
|
copyDirectoryRecursive(SOURCE_TRAEFIK_TEMPLATES, TRAEFIK_DIR);
|
|
9114
9218
|
cleanupTemplateFiles();
|
|
9115
9219
|
spinner.succeed("Traefik configuration created from templates");
|
|
@@ -9122,9 +9226,9 @@ async function installCommand(options) {
|
|
|
9122
9226
|
if (generateTlsConfig()) {
|
|
9123
9227
|
spinner.succeed("TLS config generated (tls.yml)");
|
|
9124
9228
|
}
|
|
9125
|
-
const existingCompose =
|
|
9126
|
-
if (
|
|
9127
|
-
const content =
|
|
9229
|
+
const existingCompose = join33(TRAEFIK_DIR, "docker-compose.yml");
|
|
9230
|
+
if (existsSync34(existingCompose)) {
|
|
9231
|
+
const content = readFileSync29(existingCompose, "utf-8");
|
|
9128
9232
|
if (content.includes("panopticon:") && !content.includes("external: true")) {
|
|
9129
9233
|
const patched = content.replace(
|
|
9130
9234
|
/networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
|
|
@@ -9139,8 +9243,8 @@ async function installCommand(options) {
|
|
|
9139
9243
|
console.log(chalk35.yellow("You can set up Traefik manually later"));
|
|
9140
9244
|
}
|
|
9141
9245
|
}
|
|
9142
|
-
const configFile =
|
|
9143
|
-
const configExists =
|
|
9246
|
+
const configFile = join33(PANOPTICON_HOME, "config.toml");
|
|
9247
|
+
const configExists = existsSync34(configFile);
|
|
9144
9248
|
if (!configExists) {
|
|
9145
9249
|
spinner.start("Creating default config...");
|
|
9146
9250
|
} else {
|
|
@@ -9348,82 +9452,27 @@ function getHealthLabel(state) {
|
|
|
9348
9452
|
}
|
|
9349
9453
|
}
|
|
9350
9454
|
|
|
9351
|
-
// src/lib/
|
|
9455
|
+
// src/lib/database/health-events-db.ts
|
|
9352
9456
|
init_esm_shims();
|
|
9353
|
-
|
|
9354
|
-
import Database from "better-sqlite3";
|
|
9355
|
-
import { join as join33 } from "path";
|
|
9356
|
-
import { existsSync as existsSync34, mkdirSync as mkdirSync15 } from "fs";
|
|
9357
|
-
var CLOISTER_DB_PATH = join33(PANOPTICON_HOME, "cloister.db");
|
|
9358
|
-
var RETENTION_DAYS = 7;
|
|
9359
|
-
var db = null;
|
|
9360
|
-
function initHealthDatabase() {
|
|
9361
|
-
if (!existsSync34(PANOPTICON_HOME)) {
|
|
9362
|
-
mkdirSync15(PANOPTICON_HOME, { recursive: true });
|
|
9363
|
-
}
|
|
9364
|
-
db = new Database(CLOISTER_DB_PATH);
|
|
9365
|
-
db.pragma("journal_mode = WAL");
|
|
9366
|
-
db.exec(`
|
|
9367
|
-
CREATE TABLE IF NOT EXISTS health_events (
|
|
9368
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9369
|
-
agent_id TEXT NOT NULL,
|
|
9370
|
-
timestamp TEXT NOT NULL,
|
|
9371
|
-
state TEXT NOT NULL,
|
|
9372
|
-
previous_state TEXT,
|
|
9373
|
-
source TEXT,
|
|
9374
|
-
metadata TEXT
|
|
9375
|
-
);
|
|
9376
|
-
|
|
9377
|
-
CREATE INDEX IF NOT EXISTS idx_agent_timestamp
|
|
9378
|
-
ON health_events(agent_id, timestamp);
|
|
9379
|
-
|
|
9380
|
-
CREATE INDEX IF NOT EXISTS idx_timestamp
|
|
9381
|
-
ON health_events(timestamp);
|
|
9382
|
-
`);
|
|
9383
|
-
cleanupOldEvents(db);
|
|
9384
|
-
return db;
|
|
9385
|
-
}
|
|
9386
|
-
function getHealthDatabase() {
|
|
9387
|
-
if (!db) {
|
|
9388
|
-
return initHealthDatabase();
|
|
9389
|
-
}
|
|
9390
|
-
return db;
|
|
9391
|
-
}
|
|
9392
|
-
function closeHealthDatabase() {
|
|
9393
|
-
if (db) {
|
|
9394
|
-
db.close();
|
|
9395
|
-
db = null;
|
|
9396
|
-
}
|
|
9397
|
-
}
|
|
9457
|
+
init_database();
|
|
9398
9458
|
function writeHealthEvent(event) {
|
|
9399
|
-
const
|
|
9400
|
-
const
|
|
9459
|
+
const db = getDatabase();
|
|
9460
|
+
const result = db.prepare(`
|
|
9401
9461
|
INSERT INTO health_events (agent_id, timestamp, state, previous_state, source, metadata)
|
|
9402
9462
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
9403
|
-
`)
|
|
9404
|
-
const result = stmt.run(
|
|
9463
|
+
`).run(
|
|
9405
9464
|
event.agentId,
|
|
9406
9465
|
event.timestamp,
|
|
9407
9466
|
event.state,
|
|
9408
|
-
event.previousState
|
|
9409
|
-
event.source
|
|
9410
|
-
event.metadata
|
|
9467
|
+
event.previousState ?? null,
|
|
9468
|
+
event.source ?? null,
|
|
9469
|
+
event.metadata ?? null
|
|
9411
9470
|
);
|
|
9412
9471
|
return result.lastInsertRowid;
|
|
9413
9472
|
}
|
|
9414
|
-
function cleanupOldEvents(database = getHealthDatabase(), retentionDays = RETENTION_DAYS) {
|
|
9415
|
-
const cutoffDate = /* @__PURE__ */ new Date();
|
|
9416
|
-
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
|
9417
|
-
const cutoffTimestamp = cutoffDate.toISOString();
|
|
9418
|
-
const stmt = database.prepare(`
|
|
9419
|
-
DELETE FROM health_events
|
|
9420
|
-
WHERE timestamp < ?
|
|
9421
|
-
`);
|
|
9422
|
-
const result = stmt.run(cutoffTimestamp);
|
|
9423
|
-
return result.changes;
|
|
9424
|
-
}
|
|
9425
9473
|
|
|
9426
9474
|
// src/lib/cloister/service.ts
|
|
9475
|
+
init_database();
|
|
9427
9476
|
init_specialists();
|
|
9428
9477
|
|
|
9429
9478
|
// src/lib/runtimes/index.ts
|
|
@@ -9437,10 +9486,10 @@ init_esm_shims();
|
|
|
9437
9486
|
init_agents();
|
|
9438
9487
|
init_tmux();
|
|
9439
9488
|
init_jsonl_parser();
|
|
9440
|
-
import { existsSync as existsSync35, readFileSync as
|
|
9489
|
+
import { existsSync as existsSync35, readFileSync as readFileSync30, statSync as statSync7, mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
9441
9490
|
import { join as join34 } from "path";
|
|
9442
|
-
import { homedir as
|
|
9443
|
-
var CLAUDE_PROJECTS_DIR = join34(
|
|
9491
|
+
import { homedir as homedir19 } from "os";
|
|
9492
|
+
var CLAUDE_PROJECTS_DIR = join34(homedir19(), ".claude", "projects");
|
|
9444
9493
|
var ClaudeCodeRuntime = class {
|
|
9445
9494
|
name = "claude-code";
|
|
9446
9495
|
/**
|
|
@@ -9458,7 +9507,7 @@ var ClaudeCodeRuntime = class {
|
|
|
9458
9507
|
const indexPath = join34(projectDir, "sessions-index.json");
|
|
9459
9508
|
if (existsSync35(indexPath)) {
|
|
9460
9509
|
try {
|
|
9461
|
-
const indexContent =
|
|
9510
|
+
const indexContent = readFileSync30(indexPath, "utf-8");
|
|
9462
9511
|
if (indexContent.includes(workspace)) {
|
|
9463
9512
|
return projectDir;
|
|
9464
9513
|
}
|
|
@@ -9477,7 +9526,7 @@ var ClaudeCodeRuntime = class {
|
|
|
9477
9526
|
return null;
|
|
9478
9527
|
}
|
|
9479
9528
|
try {
|
|
9480
|
-
const indexContent =
|
|
9529
|
+
const indexContent = readFileSync30(indexPath, "utf-8");
|
|
9481
9530
|
const index = JSON.parse(indexContent);
|
|
9482
9531
|
if (index.sessions && Array.isArray(index.sessions)) {
|
|
9483
9532
|
const sessions = index.sessions;
|
|
@@ -9540,12 +9589,12 @@ var ClaudeCodeRuntime = class {
|
|
|
9540
9589
|
* Read active heartbeat file if it exists
|
|
9541
9590
|
*/
|
|
9542
9591
|
getActiveHeartbeat(agentId) {
|
|
9543
|
-
const heartbeatPath = join34(
|
|
9592
|
+
const heartbeatPath = join34(homedir19(), ".panopticon", "heartbeats", `${agentId}.json`);
|
|
9544
9593
|
if (!existsSync35(heartbeatPath)) {
|
|
9545
9594
|
return null;
|
|
9546
9595
|
}
|
|
9547
9596
|
try {
|
|
9548
|
-
const content =
|
|
9597
|
+
const content = readFileSync30(heartbeatPath, "utf-8");
|
|
9549
9598
|
const data = JSON.parse(content);
|
|
9550
9599
|
const timestamp = new Date(data.timestamp);
|
|
9551
9600
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -9650,7 +9699,7 @@ var ClaudeCodeRuntime = class {
|
|
|
9650
9699
|
}
|
|
9651
9700
|
await sendKeysAsync(agentId, message);
|
|
9652
9701
|
const mailDir = join34(getAgentDir(agentId), "mail");
|
|
9653
|
-
|
|
9702
|
+
mkdirSync15(mailDir, { recursive: true });
|
|
9654
9703
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9655
9704
|
writeFileSync13(
|
|
9656
9705
|
join34(mailDir, `${timestamp}.md`),
|
|
@@ -9668,6 +9717,7 @@ ${message}
|
|
|
9668
9717
|
throw new Error(`Agent ${agentId} is not running`);
|
|
9669
9718
|
}
|
|
9670
9719
|
killSession(agentId);
|
|
9720
|
+
saveAgentRuntimeState(agentId, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9671
9721
|
const state = getAgentState(agentId);
|
|
9672
9722
|
if (state) {
|
|
9673
9723
|
state.status = "stopped";
|
|
@@ -9810,9 +9860,9 @@ init_agents();
|
|
|
9810
9860
|
// src/lib/cloister/triggers.ts
|
|
9811
9861
|
init_esm_shims();
|
|
9812
9862
|
init_config2();
|
|
9813
|
-
import { exec as
|
|
9814
|
-
import { promisify as
|
|
9815
|
-
var
|
|
9863
|
+
import { exec as exec13 } from "child_process";
|
|
9864
|
+
import { promisify as promisify13 } from "util";
|
|
9865
|
+
var execAsync13 = promisify13(exec13);
|
|
9816
9866
|
function checkStuckEscalation(health, currentModel, config2) {
|
|
9817
9867
|
const conf = config2 || loadCloisterConfig();
|
|
9818
9868
|
const stuckConfig = conf.handoffs?.auto_triggers?.stuck_escalation;
|
|
@@ -9936,7 +9986,7 @@ async function checkTaskCompletion(issueId, config2) {
|
|
|
9936
9986
|
};
|
|
9937
9987
|
}
|
|
9938
9988
|
try {
|
|
9939
|
-
const { stdout: output } = await
|
|
9989
|
+
const { stdout: output } = await execAsync13(`bd list --json -l ${issueId.toLowerCase()} --status closed`, {
|
|
9940
9990
|
encoding: "utf-8"
|
|
9941
9991
|
});
|
|
9942
9992
|
const tasks = JSON.parse(output);
|
|
@@ -9944,7 +9994,7 @@ async function checkTaskCompletion(issueId, config2) {
|
|
|
9944
9994
|
(t) => t.title.toLowerCase().includes("implement") || t.labels?.includes("implementation")
|
|
9945
9995
|
);
|
|
9946
9996
|
if (implementTask) {
|
|
9947
|
-
const { stdout: openOutput } = await
|
|
9997
|
+
const { stdout: openOutput } = await execAsync13(`bd list --json -l ${issueId.toLowerCase()} --status open`, {
|
|
9948
9998
|
encoding: "utf-8"
|
|
9949
9999
|
});
|
|
9950
10000
|
const openTasks = JSON.parse(openOutput);
|
|
@@ -9988,16 +10038,16 @@ async function checkAllTriggers(agentId, workspace, issueId, currentModel, healt
|
|
|
9988
10038
|
// src/lib/cloister/handoff.ts
|
|
9989
10039
|
init_esm_shims();
|
|
9990
10040
|
init_agents();
|
|
9991
|
-
import { writeFileSync as writeFileSync14, mkdirSync as
|
|
10041
|
+
import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync16 } from "fs";
|
|
9992
10042
|
import { join as join36 } from "path";
|
|
9993
10043
|
|
|
9994
10044
|
// src/lib/cloister/handoff-context.ts
|
|
9995
10045
|
init_esm_shims();
|
|
9996
|
-
import { existsSync as existsSync36, readFileSync as
|
|
10046
|
+
import { existsSync as existsSync36, readFileSync as readFileSync31 } from "fs";
|
|
9997
10047
|
import { join as join35 } from "path";
|
|
9998
|
-
import { exec as
|
|
9999
|
-
import { promisify as
|
|
10000
|
-
var
|
|
10048
|
+
import { exec as exec14 } from "child_process";
|
|
10049
|
+
import { promisify as promisify14 } from "util";
|
|
10050
|
+
var execAsync14 = promisify14(exec14);
|
|
10001
10051
|
async function captureHandoffContext(agentState, targetModel, reason) {
|
|
10002
10052
|
const context = {
|
|
10003
10053
|
issueId: agentState.issueId,
|
|
@@ -10020,11 +10070,11 @@ async function captureFiles(context, workspace) {
|
|
|
10020
10070
|
try {
|
|
10021
10071
|
const stateFile = join35(workspace, ".planning/STATE.md");
|
|
10022
10072
|
if (existsSync36(stateFile)) {
|
|
10023
|
-
context.stateFile =
|
|
10073
|
+
context.stateFile = readFileSync31(stateFile, "utf-8");
|
|
10024
10074
|
}
|
|
10025
10075
|
const claudeMd = join35(workspace, "CLAUDE.md");
|
|
10026
10076
|
if (existsSync36(claudeMd)) {
|
|
10027
|
-
context.claudeMd =
|
|
10077
|
+
context.claudeMd = readFileSync31(claudeMd, "utf-8");
|
|
10028
10078
|
}
|
|
10029
10079
|
} catch (error) {
|
|
10030
10080
|
console.error("Error capturing files:", error);
|
|
@@ -10032,17 +10082,17 @@ async function captureFiles(context, workspace) {
|
|
|
10032
10082
|
}
|
|
10033
10083
|
async function captureGitState(context, workspace) {
|
|
10034
10084
|
try {
|
|
10035
|
-
const { stdout: branch } = await
|
|
10085
|
+
const { stdout: branch } = await execAsync14("git branch --show-current", {
|
|
10036
10086
|
cwd: workspace,
|
|
10037
10087
|
encoding: "utf-8"
|
|
10038
10088
|
});
|
|
10039
10089
|
context.gitBranch = branch.trim();
|
|
10040
|
-
const { stdout: status } = await
|
|
10090
|
+
const { stdout: status } = await execAsync14("git status --porcelain", {
|
|
10041
10091
|
cwd: workspace,
|
|
10042
10092
|
encoding: "utf-8"
|
|
10043
10093
|
});
|
|
10044
10094
|
context.uncommittedFiles = status.split("\n").filter((line) => line.trim()).map((line) => line.substring(3));
|
|
10045
|
-
const { stdout: lastCommit } = await
|
|
10095
|
+
const { stdout: lastCommit } = await execAsync14("git log -1 --oneline", {
|
|
10046
10096
|
cwd: workspace,
|
|
10047
10097
|
encoding: "utf-8"
|
|
10048
10098
|
});
|
|
@@ -10054,7 +10104,7 @@ async function captureGitState(context, workspace) {
|
|
|
10054
10104
|
async function captureBeadsTasks(context, issueId) {
|
|
10055
10105
|
try {
|
|
10056
10106
|
const label = issueId.toLowerCase();
|
|
10057
|
-
const { stdout: output } = await
|
|
10107
|
+
const { stdout: output } = await execAsync14(`bd list --json -l ${label}`, {
|
|
10058
10108
|
encoding: "utf-8"
|
|
10059
10109
|
});
|
|
10060
10110
|
const tasks = JSON.parse(output);
|
|
@@ -10216,7 +10266,7 @@ async function performKillAndSpawn(state, options) {
|
|
|
10216
10266
|
stopAgent(state.id);
|
|
10217
10267
|
const prompt = buildHandoffPrompt(context, options.additionalInstructions);
|
|
10218
10268
|
const handoffDir = join36(getAgentDir(state.id), "handoffs");
|
|
10219
|
-
|
|
10269
|
+
mkdirSync16(handoffDir, { recursive: true });
|
|
10220
10270
|
const handoffFile = join36(handoffDir, `handoff-${Date.now()}.md`);
|
|
10221
10271
|
writeFileSync14(handoffFile, prompt);
|
|
10222
10272
|
const newState = await spawnAgent({
|
|
@@ -10326,13 +10376,13 @@ function sleep(ms) {
|
|
|
10326
10376
|
// src/lib/cloister/handoff-logger.ts
|
|
10327
10377
|
init_esm_shims();
|
|
10328
10378
|
init_paths();
|
|
10329
|
-
import { existsSync as existsSync38, mkdirSync as
|
|
10379
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync17, appendFileSync as appendFileSync3, readFileSync as readFileSync32, writeFileSync as writeFileSync15 } from "fs";
|
|
10330
10380
|
import { join as join37 } from "path";
|
|
10331
10381
|
var HANDOFF_LOG_FILE = join37(PANOPTICON_HOME, "logs", "handoffs.jsonl");
|
|
10332
10382
|
function ensureLogDir() {
|
|
10333
10383
|
const logDir = join37(PANOPTICON_HOME, "logs");
|
|
10334
10384
|
if (!existsSync38(logDir)) {
|
|
10335
|
-
|
|
10385
|
+
mkdirSync17(logDir, { recursive: true });
|
|
10336
10386
|
}
|
|
10337
10387
|
}
|
|
10338
10388
|
function logHandoffEvent(event) {
|
|
@@ -10375,7 +10425,7 @@ function createHandoffEvent(agentId, issueId, context, trigger, success, errorMe
|
|
|
10375
10425
|
// src/lib/cloister/fpp-violations.ts
|
|
10376
10426
|
init_esm_shims();
|
|
10377
10427
|
init_hooks();
|
|
10378
|
-
import { readFileSync as
|
|
10428
|
+
import { readFileSync as readFileSync33, existsSync as existsSync39, writeFileSync as writeFileSync16, mkdirSync as mkdirSync18, unlinkSync as unlinkSync3 } from "fs";
|
|
10379
10429
|
import { join as join38, dirname as dirname11 } from "path";
|
|
10380
10430
|
init_paths();
|
|
10381
10431
|
var DEFAULT_FPP_CONFIG = {
|
|
@@ -10390,7 +10440,7 @@ function loadViolations() {
|
|
|
10390
10440
|
return /* @__PURE__ */ new Map();
|
|
10391
10441
|
}
|
|
10392
10442
|
try {
|
|
10393
|
-
const fileContent =
|
|
10443
|
+
const fileContent = readFileSync33(VIOLATIONS_DATA_FILE, "utf-8");
|
|
10394
10444
|
const persisted = JSON.parse(fileContent);
|
|
10395
10445
|
return new Map(persisted.violations || []);
|
|
10396
10446
|
} catch (error) {
|
|
@@ -10402,14 +10452,14 @@ function saveViolations(violations) {
|
|
|
10402
10452
|
try {
|
|
10403
10453
|
const dir = dirname11(VIOLATIONS_DATA_FILE);
|
|
10404
10454
|
if (!existsSync39(dir)) {
|
|
10405
|
-
|
|
10455
|
+
mkdirSync18(dir, { recursive: true });
|
|
10406
10456
|
}
|
|
10407
10457
|
const persisted = {
|
|
10408
10458
|
violations: Array.from(violations.entries())
|
|
10409
10459
|
};
|
|
10410
10460
|
const tempFile = `${VIOLATIONS_DATA_FILE}.tmp`;
|
|
10411
10461
|
writeFileSync16(tempFile, JSON.stringify(persisted, null, 2));
|
|
10412
|
-
writeFileSync16(VIOLATIONS_DATA_FILE,
|
|
10462
|
+
writeFileSync16(VIOLATIONS_DATA_FILE, readFileSync33(tempFile));
|
|
10413
10463
|
try {
|
|
10414
10464
|
unlinkSync3(tempFile);
|
|
10415
10465
|
} catch (unlinkError) {
|
|
@@ -10499,7 +10549,7 @@ function clearOldViolations(hoursOld = 24) {
|
|
|
10499
10549
|
init_esm_shims();
|
|
10500
10550
|
init_paths();
|
|
10501
10551
|
init_config2();
|
|
10502
|
-
import { readFileSync as
|
|
10552
|
+
import { readFileSync as readFileSync34, existsSync as existsSync40, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19, unlinkSync as unlinkSync4 } from "fs";
|
|
10503
10553
|
import { join as join39, dirname as dirname12 } from "path";
|
|
10504
10554
|
var COST_DATA_FILE = join39(PANOPTICON_HOME, "cost-data.json");
|
|
10505
10555
|
function loadCostData() {
|
|
@@ -10512,7 +10562,7 @@ function loadCostData() {
|
|
|
10512
10562
|
};
|
|
10513
10563
|
}
|
|
10514
10564
|
try {
|
|
10515
|
-
const fileContent =
|
|
10565
|
+
const fileContent = readFileSync34(COST_DATA_FILE, "utf-8");
|
|
10516
10566
|
const persisted = JSON.parse(fileContent);
|
|
10517
10567
|
return {
|
|
10518
10568
|
perAgent: new Map(Object.entries(persisted.perAgent || {})),
|
|
@@ -10534,7 +10584,7 @@ function saveCostData(data) {
|
|
|
10534
10584
|
try {
|
|
10535
10585
|
const dir = dirname12(COST_DATA_FILE);
|
|
10536
10586
|
if (!existsSync40(dir)) {
|
|
10537
|
-
|
|
10587
|
+
mkdirSync19(dir, { recursive: true });
|
|
10538
10588
|
}
|
|
10539
10589
|
const persisted = {
|
|
10540
10590
|
perAgent: Object.fromEntries(data.perAgent),
|
|
@@ -10544,7 +10594,7 @@ function saveCostData(data) {
|
|
|
10544
10594
|
};
|
|
10545
10595
|
const tempFile = `${COST_DATA_FILE}.tmp`;
|
|
10546
10596
|
writeFileSync17(tempFile, JSON.stringify(persisted, null, 2));
|
|
10547
|
-
writeFileSync17(COST_DATA_FILE,
|
|
10597
|
+
writeFileSync17(COST_DATA_FILE, readFileSync34(tempFile));
|
|
10548
10598
|
try {
|
|
10549
10599
|
unlinkSync4(tempFile);
|
|
10550
10600
|
} catch (unlinkError) {
|
|
@@ -10666,11 +10716,11 @@ init_esm_shims();
|
|
|
10666
10716
|
init_paths();
|
|
10667
10717
|
import { writeFileSync as writeFileSync18 } from "fs";
|
|
10668
10718
|
import { join as join40 } from "path";
|
|
10669
|
-
import { exec as
|
|
10670
|
-
import { promisify as
|
|
10719
|
+
import { exec as exec15 } from "child_process";
|
|
10720
|
+
import { promisify as promisify15 } from "util";
|
|
10671
10721
|
init_agents();
|
|
10672
10722
|
init_specialists();
|
|
10673
|
-
var
|
|
10723
|
+
var execAsync15 = promisify15(exec15);
|
|
10674
10724
|
var SESSION_ROTATION_THRESHOLD = 1e5;
|
|
10675
10725
|
var DEFAULT_MEMORY_TIERS = {
|
|
10676
10726
|
recent_summary: 100,
|
|
@@ -10692,7 +10742,7 @@ async function buildMergeAgentMemory(workingDir, tiers = DEFAULT_MEMORY_TIERS) {
|
|
|
10692
10742
|
const merges = [];
|
|
10693
10743
|
try {
|
|
10694
10744
|
const totalMerges = Math.max(tiers.recent_summary, tiers.recent_detailed, tiers.recent_full);
|
|
10695
|
-
const { stdout: gitLog } = await
|
|
10745
|
+
const { stdout: gitLog } = await execAsync15(
|
|
10696
10746
|
`git log --merges --format="%H|%s|%an|%ad|%D" -n ${totalMerges}`,
|
|
10697
10747
|
{ cwd: workingDir, encoding: "utf-8" }
|
|
10698
10748
|
);
|
|
@@ -10727,14 +10777,14 @@ async function buildMergeAgentMemory(workingDir, tiers = DEFAULT_MEMORY_TIERS) {
|
|
|
10727
10777
|
if (merge.branch) memory += `- Branch: ${merge.branch}
|
|
10728
10778
|
`;
|
|
10729
10779
|
try {
|
|
10730
|
-
const { stdout: filesOutput } = await
|
|
10780
|
+
const { stdout: filesOutput } = await execAsync15(`git show --name-only --format= ${merge.hash}`, {
|
|
10731
10781
|
cwd: workingDir,
|
|
10732
10782
|
encoding: "utf-8"
|
|
10733
10783
|
});
|
|
10734
10784
|
const files = filesOutput.trim().split("\n").filter((f) => f);
|
|
10735
10785
|
memory += `- Files changed: ${files.length}
|
|
10736
10786
|
`;
|
|
10737
|
-
const { stdout: diff } = await
|
|
10787
|
+
const { stdout: diff } = await execAsync15(`git show ${merge.hash} --stat`, {
|
|
10738
10788
|
cwd: workingDir,
|
|
10739
10789
|
encoding: "utf-8",
|
|
10740
10790
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -10811,7 +10861,7 @@ async function rotateSpecialistSession(specialistName, workingDir) {
|
|
|
10811
10861
|
}
|
|
10812
10862
|
const tmuxSession = getTmuxSessionName(specialistName);
|
|
10813
10863
|
try {
|
|
10814
|
-
await
|
|
10864
|
+
await execAsync15(`tmux kill-session -t "${tmuxSession}"`);
|
|
10815
10865
|
console.log(`Killed session: ${tmuxSession}`);
|
|
10816
10866
|
} catch (error) {
|
|
10817
10867
|
console.log(`Session ${tmuxSession} not found or already killed`);
|
|
@@ -10861,17 +10911,18 @@ init_config2();
|
|
|
10861
10911
|
init_specialists();
|
|
10862
10912
|
init_agents();
|
|
10863
10913
|
init_tmux();
|
|
10864
|
-
import { readFileSync as
|
|
10914
|
+
import { readFileSync as readFileSync36, writeFileSync as writeFileSync19, existsSync as existsSync42, mkdirSync as mkdirSync20, readdirSync as readdirSync17, statSync as statSync8, rmSync as rmSync5 } from "fs";
|
|
10865
10915
|
import { join as join41 } from "path";
|
|
10866
|
-
import { exec as
|
|
10867
|
-
import { promisify as
|
|
10868
|
-
import { homedir as
|
|
10869
|
-
var
|
|
10870
|
-
var
|
|
10916
|
+
import { exec as exec16, execFile } from "child_process";
|
|
10917
|
+
import { promisify as promisify16 } from "util";
|
|
10918
|
+
import { homedir as homedir20 } from "os";
|
|
10919
|
+
var execAsync16 = promisify16(exec16);
|
|
10920
|
+
var execFileAsync = promisify16(execFile);
|
|
10921
|
+
var REVIEW_STATUS_FILE = join41(homedir20(), ".panopticon", "review-status.json");
|
|
10871
10922
|
function updateTestStatusToTesting(issueId) {
|
|
10872
10923
|
try {
|
|
10873
10924
|
if (!existsSync42(REVIEW_STATUS_FILE)) return;
|
|
10874
|
-
const data = JSON.parse(
|
|
10925
|
+
const data = JSON.parse(readFileSync36(REVIEW_STATUS_FILE, "utf-8"));
|
|
10875
10926
|
const upper = issueId.toUpperCase();
|
|
10876
10927
|
if (data[upper]) {
|
|
10877
10928
|
data[upper].testStatus = "testing";
|
|
@@ -10905,7 +10956,7 @@ var config = { ...DEFAULT_CONFIG };
|
|
|
10905
10956
|
function loadConfig3() {
|
|
10906
10957
|
try {
|
|
10907
10958
|
if (existsSync42(CONFIG_FILE2)) {
|
|
10908
|
-
const content =
|
|
10959
|
+
const content = readFileSync36(CONFIG_FILE2, "utf-8");
|
|
10909
10960
|
const loaded = JSON.parse(content);
|
|
10910
10961
|
config = { ...DEFAULT_CONFIG, ...loaded };
|
|
10911
10962
|
}
|
|
@@ -10916,14 +10967,14 @@ function loadConfig3() {
|
|
|
10916
10967
|
}
|
|
10917
10968
|
function ensureDeaconDir() {
|
|
10918
10969
|
if (!existsSync42(DEACON_DIR)) {
|
|
10919
|
-
|
|
10970
|
+
mkdirSync20(DEACON_DIR, { recursive: true });
|
|
10920
10971
|
}
|
|
10921
10972
|
}
|
|
10922
10973
|
function loadState() {
|
|
10923
10974
|
ensureDeaconDir();
|
|
10924
10975
|
try {
|
|
10925
10976
|
if (existsSync42(STATE_FILE)) {
|
|
10926
|
-
const content =
|
|
10977
|
+
const content = readFileSync36(STATE_FILE, "utf-8");
|
|
10927
10978
|
return JSON.parse(content);
|
|
10928
10979
|
}
|
|
10929
10980
|
} catch (error) {
|
|
@@ -10977,7 +11028,7 @@ function checkHeartbeat(name) {
|
|
|
10977
11028
|
if (!existsSync42(heartbeatFile)) {
|
|
10978
11029
|
return { isResponsive: false };
|
|
10979
11030
|
}
|
|
10980
|
-
const content =
|
|
11031
|
+
const content = readFileSync36(heartbeatFile, "utf-8");
|
|
10981
11032
|
const heartbeat = JSON.parse(content);
|
|
10982
11033
|
const lastActivity = new Date(heartbeat.timestamp).getTime();
|
|
10983
11034
|
const age = Date.now() - lastActivity;
|
|
@@ -11067,7 +11118,7 @@ async function forceKillSpecialist(name, sharedState) {
|
|
|
11067
11118
|
};
|
|
11068
11119
|
}
|
|
11069
11120
|
try {
|
|
11070
|
-
await
|
|
11121
|
+
await execAsync16(`tmux kill-session -t "${tmuxSession}"`);
|
|
11071
11122
|
healthState.lastForceKillTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
11072
11123
|
healthState.forceKillCount++;
|
|
11073
11124
|
healthState.consecutiveFailures = 0;
|
|
@@ -11166,7 +11217,7 @@ async function checkAndSuspendIdleAgents() {
|
|
|
11166
11217
|
try {
|
|
11167
11218
|
const sessionId = runtimeState.sessionId || `session-${agent.id}`;
|
|
11168
11219
|
saveSessionId(agent.id, sessionId);
|
|
11169
|
-
await
|
|
11220
|
+
await execAsync16(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
|
|
11170
11221
|
saveAgentRuntimeState(agent.id, {
|
|
11171
11222
|
state: "suspended",
|
|
11172
11223
|
suspendedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11209,7 +11260,7 @@ async function checkLazyAgent(sessionName) {
|
|
|
11209
11260
|
if (Date.now() - lastSent < LAZY_COOLDOWN_MS) {
|
|
11210
11261
|
return { isLazy: false };
|
|
11211
11262
|
}
|
|
11212
|
-
const { stdout } = await
|
|
11263
|
+
const { stdout } = await execAsync16(
|
|
11213
11264
|
`tmux capture-pane -t "${sessionName}" -p -S -20 2>/dev/null || echo ""`,
|
|
11214
11265
|
{ encoding: "utf-8" }
|
|
11215
11266
|
);
|
|
@@ -11244,11 +11295,11 @@ async function checkLazyAgent(sessionName) {
|
|
|
11244
11295
|
}
|
|
11245
11296
|
async function sendAntiLazyMessage(sessionName) {
|
|
11246
11297
|
try {
|
|
11247
|
-
await
|
|
11298
|
+
await execAsync16(
|
|
11248
11299
|
`tmux send-keys -t "${sessionName}" "${ANTI_LAZY_MESSAGE.replace(/"/g, '\\"')}"`,
|
|
11249
11300
|
{ encoding: "utf-8" }
|
|
11250
11301
|
);
|
|
11251
|
-
await
|
|
11302
|
+
await execAsync16(`tmux send-keys -t "${sessionName}" Enter`, { encoding: "utf-8" });
|
|
11252
11303
|
lazyMessageCooldowns.set(sessionName, Date.now());
|
|
11253
11304
|
console.log(`[deacon] Sent anti-lazy message to ${sessionName}`);
|
|
11254
11305
|
return true;
|
|
@@ -11266,7 +11317,7 @@ function isIssueCompletedOrInReview(agentId) {
|
|
|
11266
11317
|
if (!existsSync42(REVIEW_STATUS_FILE)) {
|
|
11267
11318
|
return false;
|
|
11268
11319
|
}
|
|
11269
|
-
const content =
|
|
11320
|
+
const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
|
|
11270
11321
|
const statuses = JSON.parse(content);
|
|
11271
11322
|
const status = statuses[issueId];
|
|
11272
11323
|
if (!status) {
|
|
@@ -11318,7 +11369,7 @@ var ACTIVE_STATUS_PATTERNS = [
|
|
|
11318
11369
|
];
|
|
11319
11370
|
async function isAgentActiveInTmux(sessionName) {
|
|
11320
11371
|
try {
|
|
11321
|
-
const { stdout } = await
|
|
11372
|
+
const { stdout } = await execAsync16(
|
|
11322
11373
|
`tmux capture-pane -t "${sessionName}" -p -S -5 2>/dev/null || echo ""`,
|
|
11323
11374
|
{ encoding: "utf-8" }
|
|
11324
11375
|
);
|
|
@@ -11365,7 +11416,7 @@ async function checkStuckWorkAgents() {
|
|
|
11365
11416
|
}
|
|
11366
11417
|
let tmuxOutput;
|
|
11367
11418
|
try {
|
|
11368
|
-
const { stdout } = await
|
|
11419
|
+
const { stdout } = await execAsync16(
|
|
11369
11420
|
`tmux capture-pane -t "${agent.id}" -p -S -10 2>/dev/null || echo ""`,
|
|
11370
11421
|
{ encoding: "utf-8" }
|
|
11371
11422
|
);
|
|
@@ -11386,10 +11437,10 @@ async function checkStuckWorkAgents() {
|
|
|
11386
11437
|
console.log(`[deacon] Work agent ${agent.id} stuck thinking for ${thinkingMinutes}m (attempt ${attempts + 1})`);
|
|
11387
11438
|
try {
|
|
11388
11439
|
if (attempts === 0) {
|
|
11389
|
-
await
|
|
11440
|
+
await execAsync16(`tmux send-keys -t "${agent.id}" Escape 2>/dev/null || true`);
|
|
11390
11441
|
actions.push(`Stuck recovery: sent Escape to ${agent.id} (thinking ${thinkingMinutes}m)`);
|
|
11391
11442
|
} else if (attempts === 1) {
|
|
11392
|
-
await
|
|
11443
|
+
await execAsync16(`tmux send-keys -t "${agent.id}" C-c 2>/dev/null || true`);
|
|
11393
11444
|
actions.push(`Stuck recovery: sent Ctrl+C to ${agent.id} (thinking ${thinkingMinutes}m)`);
|
|
11394
11445
|
} else {
|
|
11395
11446
|
const launcherPath = join41(AGENTS_DIR, agent.id, "launcher.sh");
|
|
@@ -11400,9 +11451,9 @@ async function checkStuckWorkAgents() {
|
|
|
11400
11451
|
actions.push(`Stuck recovery failed for ${agent.id}: missing launcher or workspace`);
|
|
11401
11452
|
continue;
|
|
11402
11453
|
}
|
|
11403
|
-
await
|
|
11454
|
+
await execAsync16(`tmux kill-session -t "${agent.id}" 2>/dev/null || true`);
|
|
11404
11455
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
11405
|
-
await
|
|
11456
|
+
await execAsync16(
|
|
11406
11457
|
`tmux new-session -d -s "${agent.id}" -c "${workspace}" "bash ${launcherPath}"`,
|
|
11407
11458
|
{ encoding: "utf-8" }
|
|
11408
11459
|
);
|
|
@@ -11438,7 +11489,7 @@ async function cleanupStaleAgentState() {
|
|
|
11438
11489
|
const agentDir = join41(AGENTS_DIR, dir.name);
|
|
11439
11490
|
try {
|
|
11440
11491
|
try {
|
|
11441
|
-
await
|
|
11492
|
+
await execAsync16(`tmux has-session -t "${dir.name}" 2>/dev/null`);
|
|
11442
11493
|
continue;
|
|
11443
11494
|
} catch {
|
|
11444
11495
|
}
|
|
@@ -11484,7 +11535,7 @@ async function checkOrphanedReviewStatuses() {
|
|
|
11484
11535
|
if (!existsSync42(REVIEW_STATUS_FILE)) {
|
|
11485
11536
|
return actions;
|
|
11486
11537
|
}
|
|
11487
|
-
const content =
|
|
11538
|
+
const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
|
|
11488
11539
|
const statuses = JSON.parse(content);
|
|
11489
11540
|
const reviewAgentSession = getTmuxSessionName("review-agent");
|
|
11490
11541
|
const reviewAgentRunning = sessionExists(reviewAgentSession);
|
|
@@ -11524,6 +11575,52 @@ async function checkOrphanedReviewStatuses() {
|
|
|
11524
11575
|
}
|
|
11525
11576
|
return actions;
|
|
11526
11577
|
}
|
|
11578
|
+
var MERGE_STUCK_STALENESS_MS = 2 * 60 * 1e3;
|
|
11579
|
+
var MERGE_STUCK_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
11580
|
+
var MERGE_STUCK_MAX_ATTEMPTS = 3;
|
|
11581
|
+
var mergeStuckCooldowns = /* @__PURE__ */ new Map();
|
|
11582
|
+
async function checkReadyForMergeStuck() {
|
|
11583
|
+
const actions = [];
|
|
11584
|
+
try {
|
|
11585
|
+
if (!existsSync42(REVIEW_STATUS_FILE)) {
|
|
11586
|
+
return actions;
|
|
11587
|
+
}
|
|
11588
|
+
const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
|
|
11589
|
+
const statuses = JSON.parse(content);
|
|
11590
|
+
const now = Date.now();
|
|
11591
|
+
const apiPort = process.env.API_PORT || process.env.PORT || "3011";
|
|
11592
|
+
const state = loadState();
|
|
11593
|
+
const attemptCounts = state.mergeStuckAttempts ?? {};
|
|
11594
|
+
let stateModified = false;
|
|
11595
|
+
for (const [key, status] of Object.entries(statuses)) {
|
|
11596
|
+
if (!status.readyForMerge) continue;
|
|
11597
|
+
if (status.mergeStatus === "merging" || status.mergeStatus === "merged" || status.mergeStatus === "failed") continue;
|
|
11598
|
+
if (!status.updatedAt) continue;
|
|
11599
|
+
const statusAge = now - new Date(status.updatedAt).getTime();
|
|
11600
|
+
if (statusAge < MERGE_STUCK_STALENESS_MS) continue;
|
|
11601
|
+
const lastAttempt = mergeStuckCooldowns.get(key);
|
|
11602
|
+
if (lastAttempt && now - lastAttempt < MERGE_STUCK_COOLDOWN_MS) continue;
|
|
11603
|
+
const attempts = attemptCounts[key] ?? 0;
|
|
11604
|
+
if (attempts >= MERGE_STUCK_MAX_ATTEMPTS) {
|
|
11605
|
+
console.log(`[deacon] Merge stuck circuit breaker active for ${key} (${attempts}/${MERGE_STUCK_MAX_ATTEMPTS} attempts)`);
|
|
11606
|
+
continue;
|
|
11607
|
+
}
|
|
11608
|
+
const ageMin = Math.round((now - new Date(status.updatedAt).getTime()) / 6e4);
|
|
11609
|
+
const msg = `${key} is readyForMerge (age: ${ageMin}m) \u2014 waiting for human approval`;
|
|
11610
|
+
actions.push(msg);
|
|
11611
|
+
console.log(`[deacon] ${msg}`);
|
|
11612
|
+
mergeStuckCooldowns.set(key, now);
|
|
11613
|
+
}
|
|
11614
|
+
if (stateModified) {
|
|
11615
|
+
state.mergeStuckAttempts = attemptCounts;
|
|
11616
|
+
saveState(state);
|
|
11617
|
+
}
|
|
11618
|
+
} catch (error) {
|
|
11619
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11620
|
+
console.error("[deacon] Error in checkReadyForMergeStuck:", msg);
|
|
11621
|
+
}
|
|
11622
|
+
return actions;
|
|
11623
|
+
}
|
|
11527
11624
|
var deadEndCooldowns = /* @__PURE__ */ new Map();
|
|
11528
11625
|
var DEAD_END_STALENESS_MS = 5 * 60 * 1e3;
|
|
11529
11626
|
var DEAD_END_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
@@ -11533,7 +11630,7 @@ async function checkDeadEndAgents() {
|
|
|
11533
11630
|
if (!existsSync42(REVIEW_STATUS_FILE)) {
|
|
11534
11631
|
return actions;
|
|
11535
11632
|
}
|
|
11536
|
-
const content =
|
|
11633
|
+
const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
|
|
11537
11634
|
const statuses = JSON.parse(content);
|
|
11538
11635
|
const now = Date.now();
|
|
11539
11636
|
for (const [key, status] of Object.entries(statuses)) {
|
|
@@ -11601,7 +11698,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11601
11698
|
const idleMs = now - lastActivity.getTime();
|
|
11602
11699
|
if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;
|
|
11603
11700
|
try {
|
|
11604
|
-
const { stdout: lastLines } = await
|
|
11701
|
+
const { stdout: lastLines } = await execAsync16(
|
|
11605
11702
|
`tmux capture-pane -t "${agent.id}" -p -S -3 2>/dev/null || echo ""`,
|
|
11606
11703
|
{ encoding: "utf-8" }
|
|
11607
11704
|
);
|
|
@@ -11618,7 +11715,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11618
11715
|
const issueKey = issueId.toLowerCase();
|
|
11619
11716
|
if (existsSync42(REVIEW_STATUS_FILE)) {
|
|
11620
11717
|
try {
|
|
11621
|
-
const statuses = JSON.parse(
|
|
11718
|
+
const statuses = JSON.parse(readFileSync36(REVIEW_STATUS_FILE, "utf-8"));
|
|
11622
11719
|
const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];
|
|
11623
11720
|
if (hasStatus) {
|
|
11624
11721
|
console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);
|
|
@@ -11645,7 +11742,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11645
11742
|
if (!agentState?.workspace || !existsSync42(agentState.workspace)) continue;
|
|
11646
11743
|
let hasCommits = false;
|
|
11647
11744
|
try {
|
|
11648
|
-
const { stdout: gitLog } = await
|
|
11745
|
+
const { stdout: gitLog } = await execAsync16(
|
|
11649
11746
|
"git log --oneline -3 2>/dev/null",
|
|
11650
11747
|
{ cwd: agentState.workspace }
|
|
11651
11748
|
);
|
|
@@ -11655,7 +11752,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11655
11752
|
const subdirs = readdirSync17(agentState.workspace, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
11656
11753
|
for (const sub of subdirs) {
|
|
11657
11754
|
try {
|
|
11658
|
-
const { stdout: subLog } = await
|
|
11755
|
+
const { stdout: subLog } = await execAsync16(
|
|
11659
11756
|
"git log --oneline -3 2>/dev/null",
|
|
11660
11757
|
{ cwd: join41(agentState.workspace, sub.name) }
|
|
11661
11758
|
);
|
|
@@ -11692,6 +11789,58 @@ If you still have remaining tasks, continue working on them.`;
|
|
|
11692
11789
|
}
|
|
11693
11790
|
return actions;
|
|
11694
11791
|
}
|
|
11792
|
+
async function patrolWorkAgentResolutions() {
|
|
11793
|
+
const actions = [];
|
|
11794
|
+
try {
|
|
11795
|
+
const agents = listRunningAgents();
|
|
11796
|
+
const specialists = getEnabledSpecialists();
|
|
11797
|
+
const specialistNames = new Set(specialists.map((s) => getTmuxSessionName(s.name)));
|
|
11798
|
+
for (const agent of agents) {
|
|
11799
|
+
if (!agent.id.startsWith("agent-") || specialistNames.has(agent.id)) continue;
|
|
11800
|
+
const runtimeState = getAgentRuntimeState(agent.id);
|
|
11801
|
+
if (!runtimeState?.resolution || runtimeState.resolution === "working" || runtimeState.resolution === "completed") continue;
|
|
11802
|
+
const resolution = runtimeState.resolution;
|
|
11803
|
+
const count = runtimeState.resolutionCount || 0;
|
|
11804
|
+
const issueId = (agent.issueId || agent.id.replace("agent-", "")).toUpperCase();
|
|
11805
|
+
if (resolution === "done" && count >= 2) {
|
|
11806
|
+
console.log(`[deacon] Auto-completing ${agent.id} (${issueId}): resolution=done, count=${count}`);
|
|
11807
|
+
try {
|
|
11808
|
+
const panBin = join41(PANOPTICON_HOME, "bin", "pan");
|
|
11809
|
+
const binExists = existsSync42(panBin);
|
|
11810
|
+
const bin = binExists ? panBin : "pan";
|
|
11811
|
+
await execFileAsync(bin, ["work", "done", issueId, "-c", "Auto-completed by Deacon: evidence showed work complete after 2 nudges"], {
|
|
11812
|
+
timeout: 3e4
|
|
11813
|
+
});
|
|
11814
|
+
saveAgentRuntimeState(agent.id, {
|
|
11815
|
+
resolution: "completed",
|
|
11816
|
+
resolutionCount: count + 1,
|
|
11817
|
+
resolutionUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11818
|
+
});
|
|
11819
|
+
actions.push(`Deacon auto-completed ${issueId} (${agent.id}) after ${count} failed nudges`);
|
|
11820
|
+
addLog("action", `Auto-completed ${issueId}: evidence-complete, ${count} nudges exhausted`, void 0);
|
|
11821
|
+
} catch (err) {
|
|
11822
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11823
|
+
console.error(`[deacon] Failed to auto-complete ${agent.id}:`, msg);
|
|
11824
|
+
actions.push(`Deacon auto-complete failed for ${agent.id}: ${msg}`);
|
|
11825
|
+
}
|
|
11826
|
+
} else if (resolution === "stuck" && count >= 3) {
|
|
11827
|
+
console.log(`[deacon] Poking stuck agent ${agent.id} (${issueId}): count=${count}`);
|
|
11828
|
+
try {
|
|
11829
|
+
const pokeMsg = `Deacon health check: you appear stuck. Please check your current task status, review any errors, and continue working. If work is complete, run: pan work done ${issueId} -c "Implementation complete"`;
|
|
11830
|
+
await sendKeysAsync(agent.id, pokeMsg);
|
|
11831
|
+
actions.push(`Deacon poked stuck agent ${agent.id} (${issueId})`);
|
|
11832
|
+
addLog("action", `Poked stuck agent ${issueId} (count=${count})`, void 0);
|
|
11833
|
+
} catch (err) {
|
|
11834
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11835
|
+
console.error(`[deacon] Failed to poke ${agent.id}:`, msg);
|
|
11836
|
+
}
|
|
11837
|
+
}
|
|
11838
|
+
}
|
|
11839
|
+
} catch (error) {
|
|
11840
|
+
console.error("[deacon] Error in patrolWorkAgentResolutions:", error);
|
|
11841
|
+
}
|
|
11842
|
+
return actions;
|
|
11843
|
+
}
|
|
11695
11844
|
async function runPatrol() {
|
|
11696
11845
|
const state = loadState();
|
|
11697
11846
|
state.patrolCycle++;
|
|
@@ -11712,6 +11861,8 @@ async function runPatrol() {
|
|
|
11712
11861
|
if (killResult.success) {
|
|
11713
11862
|
console.log(`[deacon] Auto-restarting ${specialist.name} with fresh session...`);
|
|
11714
11863
|
clearSessionId(specialist.name);
|
|
11864
|
+
const specialistSession2 = getTmuxSessionName(specialist.name);
|
|
11865
|
+
saveAgentRuntimeState(specialistSession2, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11715
11866
|
const wakeResult = await wakeSpecialist(specialist.name, "", {
|
|
11716
11867
|
waitForReady: true,
|
|
11717
11868
|
startIfNotRunning: true
|
|
@@ -11747,7 +11898,7 @@ async function runPatrol() {
|
|
|
11747
11898
|
if (nextTask) {
|
|
11748
11899
|
console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
|
|
11749
11900
|
try {
|
|
11750
|
-
const { resumeAgent } = await import("../agents-
|
|
11901
|
+
const { resumeAgent } = await import("../agents-DMPT32H7.js");
|
|
11751
11902
|
const message = `# Queued Work
|
|
11752
11903
|
|
|
11753
11904
|
Processing queued task: ${nextTask.payload.issueId}`;
|
|
@@ -11801,9 +11952,15 @@ Processing queued task: ${nextTask.payload.issueId}`;
|
|
|
11801
11952
|
const deadEndActions = await checkDeadEndAgents();
|
|
11802
11953
|
actions.push(...deadEndActions);
|
|
11803
11954
|
for (const a of deadEndActions) addLog("action", a, state.patrolCycle);
|
|
11955
|
+
const mergeStuckActions = await checkReadyForMergeStuck();
|
|
11956
|
+
actions.push(...mergeStuckActions);
|
|
11957
|
+
for (const a of mergeStuckActions) addLog("action", a, state.patrolCycle);
|
|
11804
11958
|
const firstCompletionActions = await checkFirstCompletionAgents();
|
|
11805
11959
|
actions.push(...firstCompletionActions);
|
|
11806
11960
|
for (const a of firstCompletionActions) addLog("action", a, state.patrolCycle);
|
|
11961
|
+
const resolutionActions = await patrolWorkAgentResolutions();
|
|
11962
|
+
actions.push(...resolutionActions);
|
|
11963
|
+
for (const a of resolutionActions) addLog("action", a, state.patrolCycle);
|
|
11807
11964
|
const lazyActions = await checkAndCorrectLazyAgents();
|
|
11808
11965
|
actions.push(...lazyActions);
|
|
11809
11966
|
for (const a of lazyActions) addLog("action", a, state.patrolCycle);
|
|
@@ -11821,6 +11978,38 @@ Processing queued task: ${nextTask.payload.issueId}`;
|
|
|
11821
11978
|
actions.push(massDeathCheck.message);
|
|
11822
11979
|
addLog("error", massDeathCheck.message, state.patrolCycle);
|
|
11823
11980
|
}
|
|
11981
|
+
try {
|
|
11982
|
+
const projectSpecialists = await getAllProjectSpecialistStatuses();
|
|
11983
|
+
for (const projSpec of projectSpecialists) {
|
|
11984
|
+
if (!projSpec.isRunning) {
|
|
11985
|
+
const runtimeState2 = getAgentRuntimeState(projSpec.tmuxSession);
|
|
11986
|
+
if (runtimeState2?.state === "active") {
|
|
11987
|
+
saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11988
|
+
const msg = `Dead-session reset: per-project ${projSpec.specialistType} (${projSpec.projectKey}) was active but session is gone`;
|
|
11989
|
+
actions.push(msg);
|
|
11990
|
+
addLog("action", msg, state.patrolCycle);
|
|
11991
|
+
console.log(`[deacon] ${msg}`);
|
|
11992
|
+
}
|
|
11993
|
+
continue;
|
|
11994
|
+
}
|
|
11995
|
+
const runtimeState = getAgentRuntimeState(projSpec.tmuxSession);
|
|
11996
|
+
const isStuck = runtimeState?.state === "active" && runtimeState.lastActivity ? Date.now() - new Date(runtimeState.lastActivity).getTime() > 15 * 60 * 1e3 : false;
|
|
11997
|
+
if (isStuck) {
|
|
11998
|
+
addLog("warn", `Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing`, state.patrolCycle);
|
|
11999
|
+
console.log(`[deacon] Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing ${projSpec.tmuxSession}`);
|
|
12000
|
+
try {
|
|
12001
|
+
await execAsync16(`tmux kill-session -t "${projSpec.tmuxSession}"`);
|
|
12002
|
+
clearSessionId(projSpec.specialistType, projSpec.projectKey);
|
|
12003
|
+
saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
12004
|
+
actions.push(`Force-killed stuck per-project ${projSpec.specialistType} (${projSpec.projectKey})`);
|
|
12005
|
+
} catch {
|
|
12006
|
+
}
|
|
12007
|
+
}
|
|
12008
|
+
}
|
|
12009
|
+
} catch (error) {
|
|
12010
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
12011
|
+
console.error("[deacon] Error during per-project specialist patrol:", msg);
|
|
12012
|
+
}
|
|
11824
12013
|
saveState(state);
|
|
11825
12014
|
const result = {
|
|
11826
12015
|
cycle: state.patrolCycle,
|
|
@@ -11885,7 +12074,7 @@ function getDeaconStatus() {
|
|
|
11885
12074
|
// src/lib/cloister/service.ts
|
|
11886
12075
|
init_paths();
|
|
11887
12076
|
init_paths();
|
|
11888
|
-
import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as
|
|
12077
|
+
import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync37, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
|
|
11889
12078
|
import { join as join42 } from "path";
|
|
11890
12079
|
var CLOISTER_STATE_FILE = join42(PANOPTICON_HOME, "cloister.state");
|
|
11891
12080
|
function writeStateFile(running, pid) {
|
|
@@ -11908,7 +12097,7 @@ function writeStateFile(running, pid) {
|
|
|
11908
12097
|
function readStateFile() {
|
|
11909
12098
|
try {
|
|
11910
12099
|
if (existsSync43(CLOISTER_STATE_FILE)) {
|
|
11911
|
-
const data = JSON.parse(
|
|
12100
|
+
const data = JSON.parse(readFileSync37(CLOISTER_STATE_FILE, "utf-8"));
|
|
11912
12101
|
if (data.pid) {
|
|
11913
12102
|
try {
|
|
11914
12103
|
process.kill(data.pid, 0);
|
|
@@ -11958,10 +12147,10 @@ var CloisterService = class {
|
|
|
11958
12147
|
this.starting = true;
|
|
11959
12148
|
console.log("\u{1F514} Starting Cloister agent watchdog...");
|
|
11960
12149
|
try {
|
|
11961
|
-
|
|
11962
|
-
console.log(" \u2713
|
|
12150
|
+
getDatabase();
|
|
12151
|
+
console.log(" \u2713 Panopticon database initialized");
|
|
11963
12152
|
} catch (error) {
|
|
11964
|
-
console.error(" \u2717 Failed to initialize
|
|
12153
|
+
console.error(" \u2717 Failed to initialize panopticon database:", error);
|
|
11965
12154
|
}
|
|
11966
12155
|
try {
|
|
11967
12156
|
console.log(" \u2192 Checking specialists...");
|
|
@@ -12014,9 +12203,9 @@ var CloisterService = class {
|
|
|
12014
12203
|
console.error("Failed to stop deacon:", error);
|
|
12015
12204
|
}
|
|
12016
12205
|
try {
|
|
12017
|
-
|
|
12206
|
+
closeDatabase();
|
|
12018
12207
|
} catch (error) {
|
|
12019
|
-
console.error("Failed to close
|
|
12208
|
+
console.error("Failed to close panopticon database:", error);
|
|
12020
12209
|
}
|
|
12021
12210
|
this.emit({ type: "stopped" });
|
|
12022
12211
|
}
|
|
@@ -12133,7 +12322,7 @@ var CloisterService = class {
|
|
|
12133
12322
|
const processedFile = join42(AGENTS_DIR, dir.name, "completed.processed");
|
|
12134
12323
|
if (!existsSync43(completedFile) || existsSync43(processedFile)) continue;
|
|
12135
12324
|
try {
|
|
12136
|
-
const content = JSON.parse(
|
|
12325
|
+
const content = JSON.parse(readFileSync37(completedFile, "utf-8"));
|
|
12137
12326
|
const ageMs = Date.now() - new Date(content.timestamp).getTime();
|
|
12138
12327
|
if (ageMs > 24 * 60 * 60 * 1e3) {
|
|
12139
12328
|
console.log(`\u{1F514} Cloister: Skipping stale completion marker for ${dir.name} (${Math.floor(ageMs / 36e5)}h old)`);
|
|
@@ -12790,10 +12979,10 @@ init_esm_shims();
|
|
|
12790
12979
|
// src/cli/commands/setup/hooks.ts
|
|
12791
12980
|
init_esm_shims();
|
|
12792
12981
|
import chalk39 from "chalk";
|
|
12793
|
-
import { readFileSync as
|
|
12982
|
+
import { readFileSync as readFileSync38, writeFileSync as writeFileSync21, existsSync as existsSync44, mkdirSync as mkdirSync21, copyFileSync as copyFileSync2, chmodSync } from "fs";
|
|
12794
12983
|
import { join as join43 } from "path";
|
|
12795
12984
|
import { execSync as execSync5 } from "child_process";
|
|
12796
|
-
import { homedir as
|
|
12985
|
+
import { homedir as homedir21 } from "os";
|
|
12797
12986
|
function checkJqInstalled() {
|
|
12798
12987
|
try {
|
|
12799
12988
|
execSync5("which jq", { stdio: "pipe" });
|
|
@@ -12869,15 +13058,15 @@ async function setupHooksCommand() {
|
|
|
12869
13058
|
} else {
|
|
12870
13059
|
console.log(chalk39.green("\u2713 jq is installed"));
|
|
12871
13060
|
}
|
|
12872
|
-
const panopticonHome = join43(
|
|
13061
|
+
const panopticonHome = join43(homedir21(), ".panopticon");
|
|
12873
13062
|
const binDir = join43(panopticonHome, "bin");
|
|
12874
13063
|
const heartbeatsDir = join43(panopticonHome, "heartbeats");
|
|
12875
13064
|
if (!existsSync44(binDir)) {
|
|
12876
|
-
|
|
13065
|
+
mkdirSync21(binDir, { recursive: true });
|
|
12877
13066
|
console.log(chalk39.green("\u2713 Created ~/.panopticon/bin/"));
|
|
12878
13067
|
}
|
|
12879
13068
|
if (!existsSync44(heartbeatsDir)) {
|
|
12880
|
-
|
|
13069
|
+
mkdirSync21(heartbeatsDir, { recursive: true });
|
|
12881
13070
|
console.log(chalk39.green("\u2713 Created ~/.panopticon/heartbeats/"));
|
|
12882
13071
|
}
|
|
12883
13072
|
const hookScripts = ["pre-tool-hook", "heartbeat-hook", "stop-hook", "specialist-stop-hook", "record-cost-event.js"];
|
|
@@ -12904,12 +13093,12 @@ async function setupHooksCommand() {
|
|
|
12904
13093
|
chmodSync(scriptDest, 493);
|
|
12905
13094
|
}
|
|
12906
13095
|
console.log(chalk39.green("\u2713 Installed hook scripts (pre-tool, post-tool, stop, specialist-stop)"));
|
|
12907
|
-
const claudeDir = join43(
|
|
13096
|
+
const claudeDir = join43(homedir21(), ".claude");
|
|
12908
13097
|
const settingsPath = join43(claudeDir, "settings.json");
|
|
12909
13098
|
let settings = {};
|
|
12910
13099
|
if (existsSync44(settingsPath)) {
|
|
12911
13100
|
try {
|
|
12912
|
-
const settingsContent =
|
|
13101
|
+
const settingsContent = readFileSync38(settingsPath, "utf-8");
|
|
12913
13102
|
settings = JSON.parse(settingsContent);
|
|
12914
13103
|
console.log(chalk39.green("\u2713 Read existing Claude Code settings"));
|
|
12915
13104
|
} catch (error) {
|
|
@@ -12919,7 +13108,7 @@ async function setupHooksCommand() {
|
|
|
12919
13108
|
} else {
|
|
12920
13109
|
console.log(chalk39.dim("No existing settings.json found, creating new file"));
|
|
12921
13110
|
if (!existsSync44(claudeDir)) {
|
|
12922
|
-
|
|
13111
|
+
mkdirSync21(claudeDir, { recursive: true });
|
|
12923
13112
|
}
|
|
12924
13113
|
}
|
|
12925
13114
|
let python3Available = false;
|
|
@@ -12936,7 +13125,7 @@ async function setupHooksCommand() {
|
|
|
12936
13125
|
let mcpConfig = {};
|
|
12937
13126
|
try {
|
|
12938
13127
|
if (existsSync44(mcpPath)) {
|
|
12939
|
-
mcpConfig = JSON.parse(
|
|
13128
|
+
mcpConfig = JSON.parse(readFileSync38(mcpPath, "utf-8"));
|
|
12940
13129
|
}
|
|
12941
13130
|
} catch {
|
|
12942
13131
|
mcpConfig = {};
|
|
@@ -13127,18 +13316,18 @@ init_specialists();
|
|
|
13127
13316
|
init_paths();
|
|
13128
13317
|
init_tmux();
|
|
13129
13318
|
import chalk41 from "chalk";
|
|
13130
|
-
import { exec as
|
|
13131
|
-
import { promisify as
|
|
13319
|
+
import { exec as exec17 } from "child_process";
|
|
13320
|
+
import { promisify as promisify17 } from "util";
|
|
13132
13321
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
13133
|
-
import { existsSync as existsSync45, mkdirSync as
|
|
13322
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "fs";
|
|
13134
13323
|
import { join as join44 } from "path";
|
|
13135
|
-
var
|
|
13324
|
+
var execAsync17 = promisify17(exec17);
|
|
13136
13325
|
var TASKS_DIR = join44(PANOPTICON_HOME, "specialists", "tasks");
|
|
13137
13326
|
function sendTask(tmuxSession, specialistName, task) {
|
|
13138
13327
|
const isLargeTask = task.length > 500 || task.includes("\n");
|
|
13139
13328
|
if (isLargeTask) {
|
|
13140
13329
|
if (!existsSync45(TASKS_DIR)) {
|
|
13141
|
-
|
|
13330
|
+
mkdirSync22(TASKS_DIR, { recursive: true });
|
|
13142
13331
|
}
|
|
13143
13332
|
const taskFile = join44(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
|
|
13144
13333
|
writeFileSync22(taskFile, task, "utf-8");
|
|
@@ -13194,7 +13383,7 @@ Waking ${status.displayName}...
|
|
|
13194
13383
|
console.log(chalk41.dim("Starting fresh session (no previous session found)"));
|
|
13195
13384
|
}
|
|
13196
13385
|
console.log(chalk41.dim(`Creating tmux session: ${tmuxSession}`));
|
|
13197
|
-
await
|
|
13386
|
+
await execAsync17(
|
|
13198
13387
|
`tmux new-session -d -s "${tmuxSession}" -c "${cwd}" "${claudeCmd}"`,
|
|
13199
13388
|
{ encoding: "utf-8" }
|
|
13200
13389
|
);
|
|
@@ -13318,10 +13507,10 @@ function getAge(date) {
|
|
|
13318
13507
|
init_esm_shims();
|
|
13319
13508
|
init_specialists();
|
|
13320
13509
|
import chalk43 from "chalk";
|
|
13321
|
-
import { exec as
|
|
13322
|
-
import { promisify as
|
|
13510
|
+
import { exec as exec18 } from "child_process";
|
|
13511
|
+
import { promisify as promisify18 } from "util";
|
|
13323
13512
|
import * as readline from "readline";
|
|
13324
|
-
var
|
|
13513
|
+
var execAsync18 = promisify18(exec18);
|
|
13325
13514
|
var ALL_SPECIALISTS = ["merge-agent", "review-agent", "test-agent"];
|
|
13326
13515
|
async function resetCommand(name, options) {
|
|
13327
13516
|
if (options.all) {
|
|
@@ -13371,7 +13560,7 @@ Resetting ${status.displayName}...
|
|
|
13371
13560
|
if (status.isRunning) {
|
|
13372
13561
|
console.log(chalk43.dim("Stopping tmux session..."));
|
|
13373
13562
|
try {
|
|
13374
|
-
await
|
|
13563
|
+
await execAsync18(`tmux kill-session -t "${status.tmuxSession}"`, { encoding: "utf-8" });
|
|
13375
13564
|
console.log(chalk43.green("\u2713 Tmux session stopped"));
|
|
13376
13565
|
} catch (error) {
|
|
13377
13566
|
console.log(chalk43.yellow("\u26A0 Failed to stop tmux session (may not be running)"));
|
|
@@ -13425,7 +13614,7 @@ async function resetAllSpecialists(options) {
|
|
|
13425
13614
|
const tmuxSession = getTmuxSessionName(specialistName);
|
|
13426
13615
|
if (status.isRunning) {
|
|
13427
13616
|
try {
|
|
13428
|
-
await
|
|
13617
|
+
await execAsync18(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" });
|
|
13429
13618
|
console.log(chalk43.dim(` Stopped ${specialistName} tmux session`));
|
|
13430
13619
|
} catch {
|
|
13431
13620
|
}
|
|
@@ -13448,7 +13637,7 @@ init_esm_shims();
|
|
|
13448
13637
|
init_specialists();
|
|
13449
13638
|
init_paths();
|
|
13450
13639
|
import chalk44 from "chalk";
|
|
13451
|
-
import { existsSync as existsSync46, readFileSync as
|
|
13640
|
+
import { existsSync as existsSync46, readFileSync as readFileSync39, writeFileSync as writeFileSync23 } from "fs";
|
|
13452
13641
|
import { join as join45 } from "path";
|
|
13453
13642
|
import * as readline2 from "readline";
|
|
13454
13643
|
var ALL_SPECIALISTS2 = ["merge-agent", "review-agent", "test-agent"];
|
|
@@ -13505,7 +13694,7 @@ ${metadata.displayName} Queue:
|
|
|
13505
13694
|
const hookFile = join45(PANOPTICON_HOME, "agents", specialistName, "hook.json");
|
|
13506
13695
|
if (existsSync46(hookFile)) {
|
|
13507
13696
|
try {
|
|
13508
|
-
const hook = JSON.parse(
|
|
13697
|
+
const hook = JSON.parse(readFileSync39(hookFile, "utf-8"));
|
|
13509
13698
|
hook.items = [];
|
|
13510
13699
|
hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
|
|
13511
13700
|
writeFileSync23(hookFile, JSON.stringify(hook, null, 2), "utf-8");
|
|
@@ -13518,7 +13707,7 @@ ${metadata.displayName} Queue:
|
|
|
13518
13707
|
if (options.resetStatus && issueIds.length > 0) {
|
|
13519
13708
|
if (existsSync46(REVIEW_STATUS_FILE2)) {
|
|
13520
13709
|
try {
|
|
13521
|
-
const statuses = JSON.parse(
|
|
13710
|
+
const statuses = JSON.parse(readFileSync39(REVIEW_STATUS_FILE2, "utf-8"));
|
|
13522
13711
|
let resetCount = 0;
|
|
13523
13712
|
for (const issueId of issueIds) {
|
|
13524
13713
|
const key = Object.keys(statuses).find((k) => k.toLowerCase() === issueId.toLowerCase());
|
|
@@ -13560,14 +13749,14 @@ function confirm2(question) {
|
|
|
13560
13749
|
// src/cli/commands/specialists/done.ts
|
|
13561
13750
|
init_esm_shims();
|
|
13562
13751
|
import chalk45 from "chalk";
|
|
13563
|
-
import { existsSync as existsSync47, readFileSync as
|
|
13752
|
+
import { existsSync as existsSync47, readFileSync as readFileSync40, writeFileSync as writeFileSync24 } from "fs";
|
|
13564
13753
|
import { join as join46 } from "path";
|
|
13565
|
-
import { homedir as
|
|
13566
|
-
var REVIEW_STATUS_FILE3 = join46(
|
|
13754
|
+
import { homedir as homedir22 } from "os";
|
|
13755
|
+
var REVIEW_STATUS_FILE3 = join46(homedir22(), ".panopticon", "review-status.json");
|
|
13567
13756
|
function loadReviewStatuses2() {
|
|
13568
13757
|
try {
|
|
13569
13758
|
if (existsSync47(REVIEW_STATUS_FILE3)) {
|
|
13570
|
-
return JSON.parse(
|
|
13759
|
+
return JSON.parse(readFileSync40(REVIEW_STATUS_FILE3, "utf-8"));
|
|
13571
13760
|
}
|
|
13572
13761
|
} catch (error) {
|
|
13573
13762
|
console.error(chalk45.yellow("Warning: Could not load review statuses"));
|
|
@@ -13677,12 +13866,12 @@ function formatStatus(status) {
|
|
|
13677
13866
|
// src/cli/commands/specialists/logs.ts
|
|
13678
13867
|
init_esm_shims();
|
|
13679
13868
|
import { existsSync as existsSync48 } from "fs";
|
|
13680
|
-
import { exec as
|
|
13681
|
-
import { promisify as
|
|
13682
|
-
var
|
|
13869
|
+
import { exec as exec19 } from "child_process";
|
|
13870
|
+
import { promisify as promisify19 } from "util";
|
|
13871
|
+
var execAsync19 = promisify19(exec19);
|
|
13683
13872
|
async function listLogsCommand(project2, type, options) {
|
|
13684
13873
|
try {
|
|
13685
|
-
const { listRunLogs } = await import("../specialist-logs-
|
|
13874
|
+
const { listRunLogs } = await import("../specialist-logs-QREUJ4HN.js");
|
|
13686
13875
|
const limit = options.limit ? parseInt(options.limit) : 10;
|
|
13687
13876
|
const runs = listRunLogs(project2, type, { limit });
|
|
13688
13877
|
if (options.json) {
|
|
@@ -13727,7 +13916,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
|
|
|
13727
13916
|
}
|
|
13728
13917
|
async function viewLogCommand(project2, type, runId, options) {
|
|
13729
13918
|
try {
|
|
13730
|
-
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-
|
|
13919
|
+
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-QREUJ4HN.js");
|
|
13731
13920
|
const content = getRunLog(project2, type, runId);
|
|
13732
13921
|
if (!content) {
|
|
13733
13922
|
console.error(`\u274C Run log not found: ${runId}`);
|
|
@@ -13740,7 +13929,7 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
13740
13929
|
}
|
|
13741
13930
|
const logPath = getRunLogPath(project2, type, runId);
|
|
13742
13931
|
try {
|
|
13743
|
-
await
|
|
13932
|
+
await execAsync19(`less -R "${logPath}"`);
|
|
13744
13933
|
} catch {
|
|
13745
13934
|
console.log(content);
|
|
13746
13935
|
}
|
|
@@ -13751,8 +13940,8 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
13751
13940
|
}
|
|
13752
13941
|
async function tailLogCommand(project2, type) {
|
|
13753
13942
|
try {
|
|
13754
|
-
const { getRunLogPath } = await import("../specialist-logs-
|
|
13755
|
-
const { getProjectSpecialistMetadata } = await import("../specialists-
|
|
13943
|
+
const { getRunLogPath } = await import("../specialist-logs-QREUJ4HN.js");
|
|
13944
|
+
const { getProjectSpecialistMetadata } = await import("../specialists-2DBBXRCK.js");
|
|
13756
13945
|
const metadata = getProjectSpecialistMetadata(project2, type);
|
|
13757
13946
|
if (!metadata.currentRun) {
|
|
13758
13947
|
console.error(`\u274C No active run for ${project2}/${type}`);
|
|
@@ -13821,7 +14010,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
13821
14010
|
console.log(" Use --force to confirm.");
|
|
13822
14011
|
process.exit(1);
|
|
13823
14012
|
}
|
|
13824
|
-
const { cleanupAllLogs } = await import("../specialist-logs-
|
|
14013
|
+
const { cleanupAllLogs } = await import("../specialist-logs-QREUJ4HN.js");
|
|
13825
14014
|
console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
|
|
13826
14015
|
const results = cleanupAllLogs();
|
|
13827
14016
|
console.log(`
|
|
@@ -13848,8 +14037,8 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
13848
14037
|
console.log(" Use --force to confirm.");
|
|
13849
14038
|
process.exit(1);
|
|
13850
14039
|
}
|
|
13851
|
-
const { cleanupOldLogs } = await import("../specialist-logs-
|
|
13852
|
-
const { getSpecialistRetention } = await import("../projects-
|
|
14040
|
+
const { cleanupOldLogs } = await import("../specialist-logs-QREUJ4HN.js");
|
|
14041
|
+
const { getSpecialistRetention } = await import("../projects-KVM3MN3Y.js");
|
|
13853
14042
|
const retention = getSpecialistRetention(projectOrAll);
|
|
13854
14043
|
console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
|
|
13855
14044
|
console.log(` Retention: ${retention.max_days} days or ${retention.max_runs} runs
|
|
@@ -13887,11 +14076,11 @@ import ora19 from "ora";
|
|
|
13887
14076
|
// src/lib/convoy.ts
|
|
13888
14077
|
init_esm_shims();
|
|
13889
14078
|
init_tmux();
|
|
13890
|
-
import { existsSync as existsSync49, mkdirSync as
|
|
14079
|
+
import { existsSync as existsSync49, mkdirSync as mkdirSync23, writeFileSync as writeFileSync25, readFileSync as readFileSync42, readdirSync as readdirSync19 } from "fs";
|
|
13891
14080
|
import { join as join47 } from "path";
|
|
13892
|
-
import { homedir as
|
|
13893
|
-
import { exec as
|
|
13894
|
-
import { promisify as
|
|
14081
|
+
import { homedir as homedir23 } from "os";
|
|
14082
|
+
import { exec as exec20 } from "child_process";
|
|
14083
|
+
import { promisify as promisify20 } from "util";
|
|
13895
14084
|
import { parse as parseYaml } from "yaml";
|
|
13896
14085
|
|
|
13897
14086
|
// src/lib/convoy-templates.ts
|
|
@@ -13996,8 +14185,8 @@ function getExecutionOrder(template) {
|
|
|
13996
14185
|
// src/lib/convoy.ts
|
|
13997
14186
|
init_paths();
|
|
13998
14187
|
init_work_type_router();
|
|
13999
|
-
var
|
|
14000
|
-
var CONVOY_DIR = join47(
|
|
14188
|
+
var execAsync20 = promisify20(exec20);
|
|
14189
|
+
var CONVOY_DIR = join47(homedir23(), ".panopticon", "convoys");
|
|
14001
14190
|
function getConvoyStateFile(convoyId) {
|
|
14002
14191
|
return join47(CONVOY_DIR, `${convoyId}.json`);
|
|
14003
14192
|
}
|
|
@@ -14006,7 +14195,7 @@ function getConvoyOutputDir(convoyId, template) {
|
|
|
14006
14195
|
return join47(process.cwd(), baseDir, convoyId);
|
|
14007
14196
|
}
|
|
14008
14197
|
function saveConvoyState(state) {
|
|
14009
|
-
|
|
14198
|
+
mkdirSync23(CONVOY_DIR, { recursive: true });
|
|
14010
14199
|
writeFileSync25(getConvoyStateFile(state.id), JSON.stringify(state, null, 2));
|
|
14011
14200
|
}
|
|
14012
14201
|
function loadConvoyState(convoyId) {
|
|
@@ -14015,7 +14204,7 @@ function loadConvoyState(convoyId) {
|
|
|
14015
14204
|
return void 0;
|
|
14016
14205
|
}
|
|
14017
14206
|
try {
|
|
14018
|
-
const content =
|
|
14207
|
+
const content = readFileSync42(stateFile, "utf-8");
|
|
14019
14208
|
return JSON.parse(content);
|
|
14020
14209
|
} catch {
|
|
14021
14210
|
return void 0;
|
|
@@ -14047,7 +14236,7 @@ function parseAgentTemplate(templatePath) {
|
|
|
14047
14236
|
if (!existsSync49(templatePath)) {
|
|
14048
14237
|
throw new Error(`Agent template not found: ${templatePath}`);
|
|
14049
14238
|
}
|
|
14050
|
-
const content =
|
|
14239
|
+
const content = readFileSync42(templatePath, "utf-8");
|
|
14051
14240
|
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/);
|
|
14052
14241
|
if (!frontmatterMatch) {
|
|
14053
14242
|
throw new Error(`Invalid agent template format (missing frontmatter): ${templatePath}`);
|
|
@@ -14111,7 +14300,7 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
|
|
|
14111
14300
|
|
|
14112
14301
|
`;
|
|
14113
14302
|
prompt = contextInstructions + prompt;
|
|
14114
|
-
|
|
14303
|
+
mkdirSync23(convoy.outputDir, { recursive: true });
|
|
14115
14304
|
const promptFile = join47(convoy.outputDir, `${role}-prompt.md`);
|
|
14116
14305
|
writeFileSync25(promptFile, prompt);
|
|
14117
14306
|
const claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;
|
|
@@ -14123,10 +14312,10 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
|
|
|
14123
14312
|
}
|
|
14124
14313
|
});
|
|
14125
14314
|
await new Promise((resolve2) => setTimeout(resolve2, 1500));
|
|
14126
|
-
await
|
|
14127
|
-
await
|
|
14315
|
+
await execAsync20(`tmux load-buffer "${promptFile}"`);
|
|
14316
|
+
await execAsync20(`tmux paste-buffer -t ${agentState.tmuxSession}`);
|
|
14128
14317
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
14129
|
-
await
|
|
14318
|
+
await execAsync20(`tmux send-keys -t ${agentState.tmuxSession} Enter`);
|
|
14130
14319
|
agentState.status = "running";
|
|
14131
14320
|
agentState.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14132
14321
|
saveConvoyState(convoy);
|
|
@@ -14139,7 +14328,7 @@ async function startConvoy(templateName, context) {
|
|
|
14139
14328
|
const timestamp = Date.now();
|
|
14140
14329
|
const convoyId = `convoy-${templateName}-${timestamp}`;
|
|
14141
14330
|
const outputDir = getConvoyOutputDir(convoyId, template);
|
|
14142
|
-
|
|
14331
|
+
mkdirSync23(outputDir, { recursive: true });
|
|
14143
14332
|
const state = {
|
|
14144
14333
|
id: convoyId,
|
|
14145
14334
|
template: templateName,
|
|
@@ -14187,7 +14376,7 @@ async function executePhase(convoy, template, phaseAgents, context) {
|
|
|
14187
14376
|
for (const depRole of deps) {
|
|
14188
14377
|
const depAgent = convoy.agents.find((a) => a.role === depRole);
|
|
14189
14378
|
if (depAgent?.outputFile && existsSync49(depAgent.outputFile)) {
|
|
14190
|
-
agentContext[`${depRole}_output`] =
|
|
14379
|
+
agentContext[`${depRole}_output`] = readFileSync42(depAgent.outputFile, "utf-8");
|
|
14191
14380
|
}
|
|
14192
14381
|
}
|
|
14193
14382
|
spawnPromises.push(spawnConvoyAgent(convoy, agent, agentState, agentContext));
|
|
@@ -14496,7 +14685,7 @@ function registerConvoyCommands(program2) {
|
|
|
14496
14685
|
init_esm_shims();
|
|
14497
14686
|
init_projects();
|
|
14498
14687
|
import chalk50 from "chalk";
|
|
14499
|
-
import { existsSync as existsSync50, readFileSync as
|
|
14688
|
+
import { existsSync as existsSync50, readFileSync as readFileSync43, symlinkSync as symlinkSync2, mkdirSync as mkdirSync24, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
|
|
14500
14689
|
import { join as join48, resolve, dirname as dirname14 } from "path";
|
|
14501
14690
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14502
14691
|
var __filename5 = fileURLToPath4(import.meta.url);
|
|
@@ -14506,7 +14695,7 @@ function installGitHooks(gitDir) {
|
|
|
14506
14695
|
const hooksTarget = join48(gitDir, "hooks");
|
|
14507
14696
|
let installed = 0;
|
|
14508
14697
|
if (!existsSync50(hooksTarget)) {
|
|
14509
|
-
|
|
14698
|
+
mkdirSync24(hooksTarget, { recursive: true });
|
|
14510
14699
|
}
|
|
14511
14700
|
if (!existsSync50(BUNDLED_HOOKS_DIR)) {
|
|
14512
14701
|
return 0;
|
|
@@ -14558,7 +14747,7 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14558
14747
|
if (!linearTeam) {
|
|
14559
14748
|
const projectToml = join48(fullPath, ".panopticon", "project.toml");
|
|
14560
14749
|
if (existsSync50(projectToml)) {
|
|
14561
|
-
const content =
|
|
14750
|
+
const content = readFileSync43(projectToml, "utf-8");
|
|
14562
14751
|
const match = content.match(/team\s*=\s*"([^"]+)"/);
|
|
14563
14752
|
if (match) linearTeam = match[1];
|
|
14564
14753
|
}
|
|
@@ -14607,7 +14796,7 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14607
14796
|
}
|
|
14608
14797
|
const isPolyrepo = !hasRootGit && subRepos.length > 0;
|
|
14609
14798
|
try {
|
|
14610
|
-
const { preTrustDirectory } = await import("../workspace-manager-
|
|
14799
|
+
const { preTrustDirectory } = await import("../workspace-manager-ALBR62AS.js");
|
|
14611
14800
|
preTrustDirectory(fullPath);
|
|
14612
14801
|
} catch {
|
|
14613
14802
|
}
|
|
@@ -14791,9 +14980,9 @@ Project: ${foundKey}
|
|
|
14791
14980
|
init_esm_shims();
|
|
14792
14981
|
init_paths();
|
|
14793
14982
|
import chalk51 from "chalk";
|
|
14794
|
-
import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as
|
|
14983
|
+
import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as readFileSync44 } from "fs";
|
|
14795
14984
|
import { execSync as execSync6 } from "child_process";
|
|
14796
|
-
import { homedir as
|
|
14985
|
+
import { homedir as homedir24 } from "os";
|
|
14797
14986
|
import { join as join49 } from "path";
|
|
14798
14987
|
function checkCommand3(cmd) {
|
|
14799
14988
|
try {
|
|
@@ -14880,7 +15069,7 @@ async function doctorCommand() {
|
|
|
14880
15069
|
fix: "Install Claude Code first"
|
|
14881
15070
|
});
|
|
14882
15071
|
}
|
|
14883
|
-
const envFile = join49(
|
|
15072
|
+
const envFile = join49(homedir24(), ".panopticon.env");
|
|
14884
15073
|
if (existsSync51(envFile)) {
|
|
14885
15074
|
checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
|
|
14886
15075
|
} else {
|
|
@@ -14894,7 +15083,7 @@ async function doctorCommand() {
|
|
|
14894
15083
|
if (process.env.LINEAR_API_KEY) {
|
|
14895
15084
|
checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
|
|
14896
15085
|
} else if (existsSync51(envFile)) {
|
|
14897
|
-
const content =
|
|
15086
|
+
const content = readFileSync44(envFile, "utf-8");
|
|
14898
15087
|
if (content.includes("LINEAR_API_KEY")) {
|
|
14899
15088
|
checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
|
|
14900
15089
|
} else {
|
|
@@ -14962,7 +15151,7 @@ init_esm_shims();
|
|
|
14962
15151
|
init_config();
|
|
14963
15152
|
import { execSync as execSync7 } from "child_process";
|
|
14964
15153
|
import chalk52 from "chalk";
|
|
14965
|
-
import { readFileSync as
|
|
15154
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
14966
15155
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14967
15156
|
import { dirname as dirname15, join as join50 } from "path";
|
|
14968
15157
|
function getCurrentVersion() {
|
|
@@ -14970,7 +15159,7 @@ function getCurrentVersion() {
|
|
|
14970
15159
|
const __filename6 = fileURLToPath5(import.meta.url);
|
|
14971
15160
|
const __dirname6 = dirname15(__filename6);
|
|
14972
15161
|
const pkgPath = join50(__dirname6, "..", "..", "..", "package.json");
|
|
14973
|
-
const pkg = JSON.parse(
|
|
15162
|
+
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
14974
15163
|
return pkg.version;
|
|
14975
15164
|
} catch {
|
|
14976
15165
|
return "unknown";
|
|
@@ -15056,11 +15245,11 @@ init_esm_shims();
|
|
|
15056
15245
|
init_projects();
|
|
15057
15246
|
import chalk53 from "chalk";
|
|
15058
15247
|
import ora21 from "ora";
|
|
15059
|
-
import { existsSync as existsSync52, readFileSync as
|
|
15248
|
+
import { existsSync as existsSync52, readFileSync as readFileSync46, writeFileSync as writeFileSync26, mkdirSync as mkdirSync25, statSync as statSync11 } from "fs";
|
|
15060
15249
|
import { join as join51, dirname as dirname16 } from "path";
|
|
15061
|
-
import { exec as
|
|
15062
|
-
import { promisify as
|
|
15063
|
-
var
|
|
15250
|
+
import { exec as exec21 } from "child_process";
|
|
15251
|
+
import { promisify as promisify21 } from "util";
|
|
15252
|
+
var execAsync21 = promisify21(exec21);
|
|
15064
15253
|
function loadFullProjects() {
|
|
15065
15254
|
const config2 = loadProjectsConfig();
|
|
15066
15255
|
const projects = config2.projects;
|
|
@@ -15076,12 +15265,12 @@ function findFullProjectByTeam(teamPrefix) {
|
|
|
15076
15265
|
) || null;
|
|
15077
15266
|
}
|
|
15078
15267
|
function registerDbCommands(program2) {
|
|
15079
|
-
const
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15268
|
+
const db = program2.command("db").description("Database seeding and management");
|
|
15269
|
+
db.command("snapshot").description("Create a database snapshot from an external source").option("--project <key>", "Project key (e.g., myn)").option("--output <path>", "Output file path").option("--sanitize", "Run sanitization script after snapshot").action(snapshotCommand);
|
|
15270
|
+
db.command("seed <workspaceOrIssue>").description("Seed a workspace database with the configured seed file").option("--force", "Force reseed even if already initialized").option("--file <path>", "Override seed file path").action(seedCommand);
|
|
15271
|
+
db.command("status").description("Check database status for a workspace").argument("[workspaceOrIssue]", "Workspace folder or issue ID").action(statusCommand5);
|
|
15272
|
+
db.command("clean <file>").description("Clean kubectl/stderr garbage from a pg_dump file").option("--output <path>", "Output file (default: overwrite input)").option("--dry-run", "Show what would be cleaned without modifying").action(cleanCommand);
|
|
15273
|
+
db.command("config").description("Show database configuration for a project").argument("[project]", "Project key").action(configCommand);
|
|
15085
15274
|
}
|
|
15086
15275
|
async function snapshotCommand(options) {
|
|
15087
15276
|
const spinner = ora21("Creating database snapshot...").start();
|
|
@@ -15122,7 +15311,7 @@ async function snapshotCommand(options) {
|
|
|
15122
15311
|
const outputPath = options.output || dbConfig.seed_file || join51(projectConfig.path, "infra", "seed", "seed.sql");
|
|
15123
15312
|
const outputDir = dirname16(outputPath);
|
|
15124
15313
|
if (!existsSync52(outputDir)) {
|
|
15125
|
-
|
|
15314
|
+
mkdirSync25(outputDir, { recursive: true });
|
|
15126
15315
|
}
|
|
15127
15316
|
spinner.text = "Running snapshot command...";
|
|
15128
15317
|
let snapshotCmd;
|
|
@@ -15142,10 +15331,10 @@ async function snapshotCommand(options) {
|
|
|
15142
15331
|
}
|
|
15143
15332
|
const fullCmd = `${snapshotCmd} > "${outputPath}" 2>&1`;
|
|
15144
15333
|
try {
|
|
15145
|
-
await
|
|
15334
|
+
await execAsync21(fullCmd, { timeout: 3e5 });
|
|
15146
15335
|
} catch (error) {
|
|
15147
15336
|
if (existsSync52(outputPath)) {
|
|
15148
|
-
const content2 =
|
|
15337
|
+
const content2 = readFileSync46(outputPath, "utf-8");
|
|
15149
15338
|
if (content2.includes("PostgreSQL database dump")) {
|
|
15150
15339
|
spinner.warn("Snapshot completed with warnings (stderr captured)");
|
|
15151
15340
|
console.log(chalk53.dim(" Run `pan db clean` to remove stderr noise from the file"));
|
|
@@ -15158,7 +15347,7 @@ async function snapshotCommand(options) {
|
|
|
15158
15347
|
return;
|
|
15159
15348
|
}
|
|
15160
15349
|
}
|
|
15161
|
-
const content =
|
|
15350
|
+
const content = readFileSync46(outputPath, "utf-8");
|
|
15162
15351
|
if (content.includes("Defaulted container") || content.includes("Unable to use a TTY")) {
|
|
15163
15352
|
spinner.text = "Cleaning kubectl output from snapshot...";
|
|
15164
15353
|
await cleanFile(outputPath);
|
|
@@ -15166,7 +15355,7 @@ async function snapshotCommand(options) {
|
|
|
15166
15355
|
if (options.sanitize && dbConfig.seed_command) {
|
|
15167
15356
|
spinner.text = "Running sanitization...";
|
|
15168
15357
|
try {
|
|
15169
|
-
await
|
|
15358
|
+
await execAsync21(dbConfig.seed_command, { cwd: projectConfig.path });
|
|
15170
15359
|
} catch (error) {
|
|
15171
15360
|
spinner.warn(`Sanitization warning: ${error.message}`);
|
|
15172
15361
|
}
|
|
@@ -15206,7 +15395,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15206
15395
|
const containerName = dbConfig?.container_name?.replace("{{PROJECT}}", projectName) || `${projectName}-postgres-1`;
|
|
15207
15396
|
spinner.text = `Finding database container ${containerName}...`;
|
|
15208
15397
|
try {
|
|
15209
|
-
const { stdout } = await
|
|
15398
|
+
const { stdout } = await execAsync21(`docker ps --filter "name=${containerName}" --format "{{.Names}}"`);
|
|
15210
15399
|
if (!stdout.trim()) {
|
|
15211
15400
|
spinner.fail(`Database container not running: ${containerName}`);
|
|
15212
15401
|
console.log(chalk53.dim("\nStart the workspace containers first:"));
|
|
@@ -15219,7 +15408,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15219
15408
|
}
|
|
15220
15409
|
if (!options.force) {
|
|
15221
15410
|
try {
|
|
15222
|
-
const { stdout } = await
|
|
15411
|
+
const { stdout } = await execAsync21(
|
|
15223
15412
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM flyway_schema_history" -t 2>/dev/null`
|
|
15224
15413
|
);
|
|
15225
15414
|
const count = parseInt(stdout.trim(), 10);
|
|
@@ -15233,7 +15422,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15233
15422
|
if (options.force) {
|
|
15234
15423
|
spinner.text = "Dropping existing database...";
|
|
15235
15424
|
try {
|
|
15236
|
-
await
|
|
15425
|
+
await execAsync21(
|
|
15237
15426
|
`docker exec ${containerName} psql -U postgres -c "DROP DATABASE IF EXISTS myn; CREATE DATABASE myn;"`
|
|
15238
15427
|
);
|
|
15239
15428
|
} catch (error) {
|
|
@@ -15241,10 +15430,10 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15241
15430
|
}
|
|
15242
15431
|
}
|
|
15243
15432
|
spinner.text = "Copying seed file to container...";
|
|
15244
|
-
await
|
|
15433
|
+
await execAsync21(`docker cp "${seedFile}" ${containerName}:/tmp/seed.sql`);
|
|
15245
15434
|
spinner.text = "Executing seed...";
|
|
15246
15435
|
try {
|
|
15247
|
-
await
|
|
15436
|
+
await execAsync21(`docker exec ${containerName} psql -U postgres -d myn -f /tmp/seed.sql`, {
|
|
15248
15437
|
timeout: 6e5
|
|
15249
15438
|
// 10 minute timeout for large seeds
|
|
15250
15439
|
});
|
|
@@ -15254,10 +15443,10 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15254
15443
|
return;
|
|
15255
15444
|
}
|
|
15256
15445
|
}
|
|
15257
|
-
await
|
|
15446
|
+
await execAsync21(`docker exec ${containerName} rm /tmp/seed.sql`);
|
|
15258
15447
|
spinner.succeed("Database seeded successfully");
|
|
15259
15448
|
try {
|
|
15260
|
-
const { stdout } = await
|
|
15449
|
+
const { stdout } = await execAsync21(
|
|
15261
15450
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3" -t`
|
|
15262
15451
|
);
|
|
15263
15452
|
console.log(chalk53.dim("\nRecent migrations:"));
|
|
@@ -15287,7 +15476,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15287
15476
|
const projects = loadFullProjects();
|
|
15288
15477
|
projectConfig = projects.find((p) => cwd.startsWith(p.path));
|
|
15289
15478
|
if (projectConfig) {
|
|
15290
|
-
const { stdout } = await
|
|
15479
|
+
const { stdout } = await execAsync21(
|
|
15291
15480
|
`docker ps --filter "name=${projectConfig.name?.toLowerCase().replace(/\s+/g, "-")}" --filter "name=postgres" --format "{{.Names}}" | head -1`
|
|
15292
15481
|
);
|
|
15293
15482
|
containerName = stdout.trim();
|
|
@@ -15300,7 +15489,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15300
15489
|
return;
|
|
15301
15490
|
}
|
|
15302
15491
|
spinner.text = `Checking container ${containerName}...`;
|
|
15303
|
-
const { stdout: containerStatus } = await
|
|
15492
|
+
const { stdout: containerStatus } = await execAsync21(
|
|
15304
15493
|
`docker ps --filter "name=${containerName}" --format "{{.Status}}"`
|
|
15305
15494
|
);
|
|
15306
15495
|
if (!containerStatus.trim()) {
|
|
@@ -15310,7 +15499,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15310
15499
|
spinner.succeed(`Container: ${containerName}`);
|
|
15311
15500
|
console.log(chalk53.dim(` Status: ${containerStatus.trim()}`));
|
|
15312
15501
|
try {
|
|
15313
|
-
const { stdout: version } = await
|
|
15502
|
+
const { stdout: version } = await execAsync21(
|
|
15314
15503
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT version, description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1" -t`
|
|
15315
15504
|
);
|
|
15316
15505
|
const [ver, desc] = version.trim().split("|").map((s) => s.trim());
|
|
@@ -15319,14 +15508,14 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15319
15508
|
console.log(chalk53.yellow(" Flyway: Not initialized"));
|
|
15320
15509
|
}
|
|
15321
15510
|
try {
|
|
15322
|
-
const { stdout: tableCount } = await
|
|
15511
|
+
const { stdout: tableCount } = await execAsync21(
|
|
15323
15512
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'" -t`
|
|
15324
15513
|
);
|
|
15325
15514
|
console.log(chalk53.dim(` Tables: ${tableCount.trim()}`));
|
|
15326
15515
|
} catch {
|
|
15327
15516
|
}
|
|
15328
15517
|
try {
|
|
15329
|
-
const { stdout: dbSize } = await
|
|
15518
|
+
const { stdout: dbSize } = await execAsync21(
|
|
15330
15519
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT pg_size_pretty(pg_database_size('myn'))" -t`
|
|
15331
15520
|
);
|
|
15332
15521
|
console.log(chalk53.dim(` Size: ${dbSize.trim()}`));
|
|
@@ -15343,7 +15532,7 @@ async function cleanCommand(file, options) {
|
|
|
15343
15532
|
spinner.fail(`File not found: ${file}`);
|
|
15344
15533
|
return;
|
|
15345
15534
|
}
|
|
15346
|
-
const content =
|
|
15535
|
+
const content = readFileSync46(file, "utf-8");
|
|
15347
15536
|
const lines = content.split("\n");
|
|
15348
15537
|
const patternsToRemove = [
|
|
15349
15538
|
/^Defaulted container/,
|
|
@@ -15413,7 +15602,7 @@ async function cleanCommand(file, options) {
|
|
|
15413
15602
|
}
|
|
15414
15603
|
}
|
|
15415
15604
|
async function cleanFile(filePath) {
|
|
15416
|
-
const content =
|
|
15605
|
+
const content = readFileSync46(filePath, "utf-8");
|
|
15417
15606
|
const lines = content.split("\n");
|
|
15418
15607
|
let startIndex = 0;
|
|
15419
15608
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -15496,17 +15685,17 @@ async function configCommand(project2) {
|
|
|
15496
15685
|
init_esm_shims();
|
|
15497
15686
|
import chalk54 from "chalk";
|
|
15498
15687
|
import ora22 from "ora";
|
|
15499
|
-
import { existsSync as existsSync53, readFileSync as
|
|
15688
|
+
import { existsSync as existsSync53, readFileSync as readFileSync47 } from "fs";
|
|
15500
15689
|
import { join as join52 } from "path";
|
|
15501
|
-
import { exec as
|
|
15502
|
-
import { promisify as
|
|
15690
|
+
import { exec as exec22, execSync as execSync8 } from "child_process";
|
|
15691
|
+
import { promisify as promisify22 } from "util";
|
|
15503
15692
|
import { platform } from "os";
|
|
15504
|
-
var
|
|
15693
|
+
var execAsync22 = promisify22(exec22);
|
|
15505
15694
|
function detectPlatform2() {
|
|
15506
15695
|
const os = platform();
|
|
15507
15696
|
if (os === "linux") {
|
|
15508
15697
|
try {
|
|
15509
|
-
const release =
|
|
15698
|
+
const release = readFileSync47("/proc/version", "utf8").toLowerCase();
|
|
15510
15699
|
if (release.includes("microsoft") || release.includes("wsl")) {
|
|
15511
15700
|
return "wsl";
|
|
15512
15701
|
}
|
|
@@ -15518,7 +15707,7 @@ function detectPlatform2() {
|
|
|
15518
15707
|
}
|
|
15519
15708
|
async function isBdAvailable() {
|
|
15520
15709
|
try {
|
|
15521
|
-
await
|
|
15710
|
+
await execAsync22("which bd", { encoding: "utf-8" });
|
|
15522
15711
|
return true;
|
|
15523
15712
|
} catch {
|
|
15524
15713
|
return false;
|
|
@@ -15527,7 +15716,7 @@ async function isBdAvailable() {
|
|
|
15527
15716
|
async function getOldClosedCount(cwd, days) {
|
|
15528
15717
|
try {
|
|
15529
15718
|
const seconds = days * 24 * 60 * 60;
|
|
15530
|
-
const { stdout } = await
|
|
15719
|
+
const { stdout } = await execAsync22(
|
|
15531
15720
|
`bd list --status closed --json 2>/dev/null | jq '[.[] | select(.closed_at != null) | select((now - (.closed_at | fromdateiso8601)) > ${seconds})] | length' 2>/dev/null || echo "0"`,
|
|
15532
15721
|
{ cwd, encoding: "utf-8" }
|
|
15533
15722
|
);
|
|
@@ -15563,7 +15752,7 @@ async function compactCommand(options) {
|
|
|
15563
15752
|
console.log("");
|
|
15564
15753
|
console.log(chalk54.bold("Beads that would be compacted:"));
|
|
15565
15754
|
try {
|
|
15566
|
-
const { stdout: beadsList } = await
|
|
15755
|
+
const { stdout: beadsList } = await execAsync22(
|
|
15567
15756
|
`bd list --status closed --json 2>/dev/null | jq -r '.[] | select(.closed_at != null) | select((now - (.closed_at | fromdateiso8601)) > ${days * 24 * 60 * 60}) | " - \\(.id): \\(.title)"' 2>/dev/null`,
|
|
15568
15757
|
{ cwd, encoding: "utf-8" }
|
|
15569
15758
|
);
|
|
@@ -15574,10 +15763,10 @@ async function compactCommand(options) {
|
|
|
15574
15763
|
return;
|
|
15575
15764
|
}
|
|
15576
15765
|
spinner.text = "Running compaction...";
|
|
15577
|
-
await
|
|
15766
|
+
await execAsync22(`bd admin compact --days ${days}`, { cwd, encoding: "utf-8" });
|
|
15578
15767
|
spinner.succeed(`Compacted ${count} beads older than ${days} days`);
|
|
15579
15768
|
try {
|
|
15580
|
-
await
|
|
15769
|
+
await execAsync22(`git diff --quiet .beads/`, { cwd, encoding: "utf-8" });
|
|
15581
15770
|
console.log(chalk54.dim("No changes to commit (beads already up to date)"));
|
|
15582
15771
|
} catch {
|
|
15583
15772
|
console.log("");
|
|
@@ -15611,17 +15800,17 @@ async function statsCommand() {
|
|
|
15611
15800
|
}
|
|
15612
15801
|
const spinner = ora22("Gathering beads statistics...").start();
|
|
15613
15802
|
try {
|
|
15614
|
-
const { stdout: totalRaw } = await
|
|
15803
|
+
const { stdout: totalRaw } = await execAsync22(`bd list --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15615
15804
|
cwd,
|
|
15616
15805
|
encoding: "utf-8"
|
|
15617
15806
|
});
|
|
15618
15807
|
const total = parseInt(totalRaw.trim(), 10) || 0;
|
|
15619
|
-
const { stdout: openRaw } = await
|
|
15808
|
+
const { stdout: openRaw } = await execAsync22(`bd list --status open --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15620
15809
|
cwd,
|
|
15621
15810
|
encoding: "utf-8"
|
|
15622
15811
|
});
|
|
15623
15812
|
const open = parseInt(openRaw.trim(), 10) || 0;
|
|
15624
|
-
const { stdout: closedRaw } = await
|
|
15813
|
+
const { stdout: closedRaw } = await execAsync22(`bd list --status closed --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15625
15814
|
cwd,
|
|
15626
15815
|
encoding: "utf-8"
|
|
15627
15816
|
});
|
|
@@ -15666,7 +15855,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15666
15855
|
console.log(chalk54.dim("Checking beads version..."));
|
|
15667
15856
|
let currentVersion = "not installed";
|
|
15668
15857
|
try {
|
|
15669
|
-
const { stdout } = await
|
|
15858
|
+
const { stdout } = await execAsync22("bd --version", { encoding: "utf-8" });
|
|
15670
15859
|
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
15671
15860
|
if (match) {
|
|
15672
15861
|
currentVersion = match[1];
|
|
@@ -15675,7 +15864,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15675
15864
|
}
|
|
15676
15865
|
let latestVersion = "unknown";
|
|
15677
15866
|
try {
|
|
15678
|
-
const { stdout } = await
|
|
15867
|
+
const { stdout } = await execAsync22(
|
|
15679
15868
|
"curl -sL https://api.github.com/repos/steveyegge/beads/releases/latest | jq -r .tag_name",
|
|
15680
15869
|
{ encoding: "utf-8" }
|
|
15681
15870
|
);
|
|
@@ -15724,7 +15913,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15724
15913
|
spinner.succeed("beads upgraded via install script");
|
|
15725
15914
|
}
|
|
15726
15915
|
try {
|
|
15727
|
-
const { stdout } = await
|
|
15916
|
+
const { stdout } = await execAsync22("bd --version", { encoding: "utf-8" });
|
|
15728
15917
|
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
15729
15918
|
if (match) {
|
|
15730
15919
|
console.log(chalk54.green(`
|
|
@@ -16231,12 +16420,12 @@ init_esm_shims();
|
|
|
16231
16420
|
init_config();
|
|
16232
16421
|
import chalk59 from "chalk";
|
|
16233
16422
|
import ora27 from "ora";
|
|
16234
|
-
import { exec as
|
|
16235
|
-
import { promisify as
|
|
16236
|
-
import { existsSync as existsSync54, readFileSync as
|
|
16423
|
+
import { exec as exec23 } from "child_process";
|
|
16424
|
+
import { promisify as promisify23 } from "util";
|
|
16425
|
+
import { existsSync as existsSync54, readFileSync as readFileSync48 } from "fs";
|
|
16237
16426
|
import { join as join53 } from "path";
|
|
16238
|
-
import { homedir as
|
|
16239
|
-
var
|
|
16427
|
+
import { homedir as homedir25 } from "os";
|
|
16428
|
+
var execAsync23 = promisify23(exec23);
|
|
16240
16429
|
async function setupCommand() {
|
|
16241
16430
|
console.log("");
|
|
16242
16431
|
console.log(chalk59.bold("\u{1F680} Remote Workspace Setup"));
|
|
@@ -16247,7 +16436,7 @@ async function setupCommand() {
|
|
|
16247
16436
|
console.log("");
|
|
16248
16437
|
console.log(chalk59.bold(" Step 2: SSH Key Configuration"));
|
|
16249
16438
|
console.log("");
|
|
16250
|
-
const sshDir = join53(
|
|
16439
|
+
const sshDir = join53(homedir25(), ".ssh");
|
|
16251
16440
|
const defaultKeyPath = join53(sshDir, "id_ed25519");
|
|
16252
16441
|
const rsaKeyPath = join53(sshDir, "id_rsa");
|
|
16253
16442
|
let sshKeyExists = false;
|
|
@@ -16271,7 +16460,7 @@ async function setupCommand() {
|
|
|
16271
16460
|
if (sshKeyExists) {
|
|
16272
16461
|
const pubKeyPath = `${keyPath}.pub`;
|
|
16273
16462
|
if (existsSync54(pubKeyPath)) {
|
|
16274
|
-
const pubKey =
|
|
16463
|
+
const pubKey = readFileSync48(pubKeyPath, "utf8").trim();
|
|
16275
16464
|
console.log("");
|
|
16276
16465
|
console.log(" Your public key (add this to exe.dev if not already):");
|
|
16277
16466
|
console.log("");
|
|
@@ -16299,7 +16488,7 @@ async function setupCommand() {
|
|
|
16299
16488
|
if (sshKeyExists) {
|
|
16300
16489
|
const pubKeyPath = `${keyPath}.pub`;
|
|
16301
16490
|
if (existsSync54(pubKeyPath)) {
|
|
16302
|
-
const pubKey =
|
|
16491
|
+
const pubKey = readFileSync48(pubKeyPath, "utf8").trim();
|
|
16303
16492
|
console.log(chalk59.dim(` ${pubKey}`));
|
|
16304
16493
|
}
|
|
16305
16494
|
} else {
|
|
@@ -16385,8 +16574,8 @@ import chalk60 from "chalk";
|
|
|
16385
16574
|
// src/lib/env-loader.ts
|
|
16386
16575
|
init_esm_shims();
|
|
16387
16576
|
import { join as join54 } from "path";
|
|
16388
|
-
import { homedir as
|
|
16389
|
-
var ENV_FILE_PATH = join54(
|
|
16577
|
+
import { homedir as homedir26 } from "os";
|
|
16578
|
+
var ENV_FILE_PATH = join54(homedir26(), ".panopticon.env");
|
|
16390
16579
|
function getShadowModeFromEnv() {
|
|
16391
16580
|
const value = process.env.SHADOW_MODE;
|
|
16392
16581
|
if (!value) return false;
|
|
@@ -16480,11 +16669,422 @@ Shadowed issues: ${shadowedIssues.length}`));
|
|
|
16480
16669
|
}
|
|
16481
16670
|
}
|
|
16482
16671
|
|
|
16672
|
+
// src/cli/commands/cost.ts
|
|
16673
|
+
init_esm_shims();
|
|
16674
|
+
init_cost();
|
|
16675
|
+
import { Command } from "commander";
|
|
16676
|
+
import chalk61 from "chalk";
|
|
16677
|
+
|
|
16678
|
+
// src/lib/costs/sync-wal.ts
|
|
16679
|
+
init_esm_shims();
|
|
16680
|
+
init_projects();
|
|
16681
|
+
import { existsSync as existsSync55 } from "fs";
|
|
16682
|
+
import { readdir, readFile } from "fs/promises";
|
|
16683
|
+
import { join as join55 } from "path";
|
|
16684
|
+
|
|
16685
|
+
// src/lib/database/cost-events-db.ts
|
|
16686
|
+
init_esm_shims();
|
|
16687
|
+
init_database();
|
|
16688
|
+
function insertCostEvents(events, sourceFile) {
|
|
16689
|
+
const db = getDatabase();
|
|
16690
|
+
let inserted = 0;
|
|
16691
|
+
let duplicates = 0;
|
|
16692
|
+
const insert = db.prepare(`
|
|
16693
|
+
INSERT OR IGNORE INTO cost_events (
|
|
16694
|
+
ts, agent_id, issue_id, session_type, provider, model,
|
|
16695
|
+
input, output, cache_read, cache_write, cost, request_id,
|
|
16696
|
+
tldr_interceptions, tldr_bypasses, tldr_tokens_saved, tldr_bypass_reasons,
|
|
16697
|
+
source_file
|
|
16698
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16699
|
+
`);
|
|
16700
|
+
const insertMany = db.transaction((evs) => {
|
|
16701
|
+
for (const ev of evs) {
|
|
16702
|
+
const result = insert.run(
|
|
16703
|
+
ev.ts,
|
|
16704
|
+
ev.agentId,
|
|
16705
|
+
ev.issueId,
|
|
16706
|
+
ev.sessionType || "unknown",
|
|
16707
|
+
ev.provider || "anthropic",
|
|
16708
|
+
ev.model,
|
|
16709
|
+
ev.input,
|
|
16710
|
+
ev.output,
|
|
16711
|
+
ev.cacheRead,
|
|
16712
|
+
ev.cacheWrite,
|
|
16713
|
+
ev.cost,
|
|
16714
|
+
ev.requestId ?? null,
|
|
16715
|
+
ev.tldrInterceptions ?? null,
|
|
16716
|
+
ev.tldrBypasses ?? null,
|
|
16717
|
+
ev.tldrTokensSaved ?? null,
|
|
16718
|
+
ev.tldrBypassReasons ? JSON.stringify(ev.tldrBypassReasons) : null,
|
|
16719
|
+
sourceFile ?? null
|
|
16720
|
+
);
|
|
16721
|
+
if (result.changes > 0) {
|
|
16722
|
+
inserted++;
|
|
16723
|
+
} else {
|
|
16724
|
+
duplicates++;
|
|
16725
|
+
}
|
|
16726
|
+
}
|
|
16727
|
+
});
|
|
16728
|
+
insertMany(events);
|
|
16729
|
+
return { inserted, duplicates };
|
|
16730
|
+
}
|
|
16731
|
+
|
|
16732
|
+
// src/lib/costs/sync-wal.ts
|
|
16733
|
+
var DEFAULT_EVENTS_SUBDIR = ".panopticon/events";
|
|
16734
|
+
async function syncWalFromAllProjects() {
|
|
16735
|
+
const result = {
|
|
16736
|
+
imported: 0,
|
|
16737
|
+
duplicates: 0,
|
|
16738
|
+
filesScanned: 0,
|
|
16739
|
+
byProject: {},
|
|
16740
|
+
errors: []
|
|
16741
|
+
};
|
|
16742
|
+
const projects = listProjects();
|
|
16743
|
+
for (const { key, config: config2 } of projects) {
|
|
16744
|
+
const repoPath = config2.events_repo ?? config2.path;
|
|
16745
|
+
const eventsSubdir = config2.events_path ?? DEFAULT_EVENTS_SUBDIR;
|
|
16746
|
+
const eventsDir = join55(repoPath, eventsSubdir);
|
|
16747
|
+
if (!existsSync55(eventsDir)) continue;
|
|
16748
|
+
const projectStats = { imported: 0, duplicates: 0, files: 0 };
|
|
16749
|
+
let files;
|
|
16750
|
+
try {
|
|
16751
|
+
files = (await readdir(eventsDir)).filter((f) => f.endsWith(".jsonl"));
|
|
16752
|
+
} catch (err) {
|
|
16753
|
+
result.errors.push(`${key}: failed to read events dir: ${err}`);
|
|
16754
|
+
continue;
|
|
16755
|
+
}
|
|
16756
|
+
for (const file of files) {
|
|
16757
|
+
const filePath = join55(eventsDir, file);
|
|
16758
|
+
const events = await parseWalFile(filePath, result.errors);
|
|
16759
|
+
if (events.length === 0) continue;
|
|
16760
|
+
try {
|
|
16761
|
+
const { inserted, duplicates } = insertCostEvents(events, filePath);
|
|
16762
|
+
projectStats.imported += inserted;
|
|
16763
|
+
projectStats.duplicates += duplicates;
|
|
16764
|
+
projectStats.files++;
|
|
16765
|
+
result.filesScanned++;
|
|
16766
|
+
} catch (err) {
|
|
16767
|
+
result.errors.push(`${key}/${file}: import failed: ${err}`);
|
|
16768
|
+
}
|
|
16769
|
+
}
|
|
16770
|
+
if (projectStats.files > 0 || projectStats.imported > 0) {
|
|
16771
|
+
result.byProject[key] = projectStats;
|
|
16772
|
+
result.imported += projectStats.imported;
|
|
16773
|
+
result.duplicates += projectStats.duplicates;
|
|
16774
|
+
}
|
|
16775
|
+
}
|
|
16776
|
+
return result;
|
|
16777
|
+
}
|
|
16778
|
+
async function parseWalFile(filePath, errors) {
|
|
16779
|
+
let content;
|
|
16780
|
+
try {
|
|
16781
|
+
content = await readFile(filePath, "utf-8");
|
|
16782
|
+
} catch (err) {
|
|
16783
|
+
errors.push(`Failed to read ${filePath}: ${err}`);
|
|
16784
|
+
return [];
|
|
16785
|
+
}
|
|
16786
|
+
const events = [];
|
|
16787
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
16788
|
+
for (const line of lines) {
|
|
16789
|
+
try {
|
|
16790
|
+
const event = JSON.parse(line);
|
|
16791
|
+
if (event.ts && event.agentId && event.issueId && event.model) {
|
|
16792
|
+
events.push(event);
|
|
16793
|
+
}
|
|
16794
|
+
} catch {
|
|
16795
|
+
}
|
|
16796
|
+
}
|
|
16797
|
+
return events;
|
|
16798
|
+
}
|
|
16799
|
+
|
|
16800
|
+
// src/cli/commands/cost.ts
|
|
16801
|
+
async function runCostSync() {
|
|
16802
|
+
try {
|
|
16803
|
+
console.log(chalk61.bold("Syncing cost events from project WAL files..."));
|
|
16804
|
+
const result = await syncWalFromAllProjects();
|
|
16805
|
+
if (result.filesScanned === 0) {
|
|
16806
|
+
console.log(chalk61.yellow("No WAL files found. Make sure projects are registered and have cost events."));
|
|
16807
|
+
return;
|
|
16808
|
+
}
|
|
16809
|
+
console.log();
|
|
16810
|
+
console.log(`Files scanned: ${result.filesScanned}`);
|
|
16811
|
+
console.log(`Imported: ${chalk61.green(result.imported)} new events`);
|
|
16812
|
+
console.log(`Duplicates: ${chalk61.dim(result.duplicates)} skipped`);
|
|
16813
|
+
if (Object.keys(result.byProject).length > 0) {
|
|
16814
|
+
console.log();
|
|
16815
|
+
console.log(chalk61.bold("By Project:"));
|
|
16816
|
+
for (const [project2, stats] of Object.entries(result.byProject)) {
|
|
16817
|
+
console.log(` ${project2}: ${chalk61.green(stats.imported)} imported, ${stats.files} file(s)`);
|
|
16818
|
+
}
|
|
16819
|
+
}
|
|
16820
|
+
if (result.errors.length > 0) {
|
|
16821
|
+
console.log();
|
|
16822
|
+
console.log(chalk61.yellow(`Warnings (${result.errors.length}):`));
|
|
16823
|
+
for (const err of result.errors) {
|
|
16824
|
+
console.log(` ${chalk61.dim(err)}`);
|
|
16825
|
+
}
|
|
16826
|
+
}
|
|
16827
|
+
} catch (error) {
|
|
16828
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16829
|
+
process.exit(1);
|
|
16830
|
+
}
|
|
16831
|
+
}
|
|
16832
|
+
function createCostCommand() {
|
|
16833
|
+
const cost = new Command("cost").description("Track and report AI usage costs");
|
|
16834
|
+
cost.command("today").description("Show today's cost summary").option("-d, --detail", "Show individual entries").action((options) => {
|
|
16835
|
+
try {
|
|
16836
|
+
const summary = getDailySummary();
|
|
16837
|
+
console.log(chalk61.bold("Today's Cost Summary"));
|
|
16838
|
+
console.log();
|
|
16839
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16840
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16841
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16842
|
+
console.log(` Input: ${summary.totalTokens.input.toLocaleString()}`);
|
|
16843
|
+
console.log(` Output: ${summary.totalTokens.output.toLocaleString()}`);
|
|
16844
|
+
console.log();
|
|
16845
|
+
if (Object.keys(summary.byProvider).length > 0) {
|
|
16846
|
+
console.log(chalk61.bold("By Provider"));
|
|
16847
|
+
for (const [provider, cost2] of Object.entries(summary.byProvider)) {
|
|
16848
|
+
console.log(` ${provider}: ${formatCost(cost2)}`);
|
|
16849
|
+
}
|
|
16850
|
+
console.log();
|
|
16851
|
+
}
|
|
16852
|
+
if (Object.keys(summary.byModel).length > 0) {
|
|
16853
|
+
console.log(chalk61.bold("By Model"));
|
|
16854
|
+
for (const [model, cost2] of Object.entries(summary.byModel)) {
|
|
16855
|
+
console.log(` ${model}: ${formatCost(cost2)}`);
|
|
16856
|
+
}
|
|
16857
|
+
console.log();
|
|
16858
|
+
}
|
|
16859
|
+
if (options.detail) {
|
|
16860
|
+
const entries = readTodayCosts();
|
|
16861
|
+
if (entries.length > 0) {
|
|
16862
|
+
console.log(chalk61.bold("Entries"));
|
|
16863
|
+
for (const entry of entries.slice(-10)) {
|
|
16864
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
16865
|
+
console.log(` ${chalk61.dim(time)} ${entry.model} ${formatCost(entry.cost)} ${entry.operation}`);
|
|
16866
|
+
}
|
|
16867
|
+
if (entries.length > 10) {
|
|
16868
|
+
console.log(chalk61.dim(` ... and ${entries.length - 10} more`));
|
|
16869
|
+
}
|
|
16870
|
+
}
|
|
16871
|
+
}
|
|
16872
|
+
} catch (error) {
|
|
16873
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16874
|
+
process.exit(1);
|
|
16875
|
+
}
|
|
16876
|
+
});
|
|
16877
|
+
cost.command("week").description("Show weekly cost summary").action(() => {
|
|
16878
|
+
try {
|
|
16879
|
+
const summary = getWeeklySummary();
|
|
16880
|
+
console.log(chalk61.bold("Weekly Cost Summary"));
|
|
16881
|
+
console.log(chalk61.dim(`${summary.period.start} to ${summary.period.end}`));
|
|
16882
|
+
console.log();
|
|
16883
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16884
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16885
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16886
|
+
console.log();
|
|
16887
|
+
if (Object.keys(summary.byProvider).length > 0) {
|
|
16888
|
+
console.log(chalk61.bold("By Provider"));
|
|
16889
|
+
for (const [provider, cost2] of Object.entries(summary.byProvider)) {
|
|
16890
|
+
console.log(` ${provider}: ${formatCost(cost2)}`);
|
|
16891
|
+
}
|
|
16892
|
+
console.log();
|
|
16893
|
+
}
|
|
16894
|
+
if (Object.keys(summary.byIssue).length > 0) {
|
|
16895
|
+
console.log(chalk61.bold("Top Issues by Cost"));
|
|
16896
|
+
const sorted = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a).slice(0, 5);
|
|
16897
|
+
for (const [issue, cost2] of sorted) {
|
|
16898
|
+
console.log(` ${issue}: ${formatCost(cost2)}`);
|
|
16899
|
+
}
|
|
16900
|
+
}
|
|
16901
|
+
} catch (error) {
|
|
16902
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16903
|
+
process.exit(1);
|
|
16904
|
+
}
|
|
16905
|
+
});
|
|
16906
|
+
cost.command("month").description("Show monthly cost summary").action(() => {
|
|
16907
|
+
try {
|
|
16908
|
+
const summary = getMonthlySummary();
|
|
16909
|
+
console.log(chalk61.bold("Monthly Cost Summary"));
|
|
16910
|
+
console.log(chalk61.dim(`${summary.period.start} to ${summary.period.end}`));
|
|
16911
|
+
console.log();
|
|
16912
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16913
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16914
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16915
|
+
console.log();
|
|
16916
|
+
if (Object.keys(summary.byProvider).length > 0) {
|
|
16917
|
+
console.log(chalk61.bold("By Provider"));
|
|
16918
|
+
for (const [provider, cost2] of Object.entries(summary.byProvider)) {
|
|
16919
|
+
console.log(` ${provider}: ${formatCost(cost2)}`);
|
|
16920
|
+
}
|
|
16921
|
+
console.log();
|
|
16922
|
+
}
|
|
16923
|
+
if (Object.keys(summary.byModel).length > 0) {
|
|
16924
|
+
console.log(chalk61.bold("By Model"));
|
|
16925
|
+
for (const [model, cost2] of Object.entries(summary.byModel)) {
|
|
16926
|
+
console.log(` ${model}: ${formatCost(cost2)}`);
|
|
16927
|
+
}
|
|
16928
|
+
console.log();
|
|
16929
|
+
}
|
|
16930
|
+
if (Object.keys(summary.byIssue).length > 0) {
|
|
16931
|
+
console.log(chalk61.bold("Top 10 Issues by Cost"));
|
|
16932
|
+
const sorted = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a).slice(0, 10);
|
|
16933
|
+
for (const [issue, cost2] of sorted) {
|
|
16934
|
+
console.log(` ${issue}: ${formatCost(cost2)}`);
|
|
16935
|
+
}
|
|
16936
|
+
}
|
|
16937
|
+
} catch (error) {
|
|
16938
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16939
|
+
process.exit(1);
|
|
16940
|
+
}
|
|
16941
|
+
});
|
|
16942
|
+
cost.command("report").description("Generate a cost report").option("-s, --start <date>", "Start date (YYYY-MM-DD)").option("-e, --end <date>", "End date (YYYY-MM-DD)").action((options) => {
|
|
16943
|
+
try {
|
|
16944
|
+
const end = options.end || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
16945
|
+
const start = options.start || (() => {
|
|
16946
|
+
const d = /* @__PURE__ */ new Date();
|
|
16947
|
+
d.setDate(d.getDate() - 30);
|
|
16948
|
+
return d.toISOString().split("T")[0];
|
|
16949
|
+
})();
|
|
16950
|
+
const report = generateReport(start, end);
|
|
16951
|
+
console.log(report);
|
|
16952
|
+
} catch (error) {
|
|
16953
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16954
|
+
process.exit(1);
|
|
16955
|
+
}
|
|
16956
|
+
});
|
|
16957
|
+
cost.command("issue <issueId>").description("Show costs for a specific issue").option("-d, --days <n>", "Number of days to look back", "30").action((issueId, options) => {
|
|
16958
|
+
try {
|
|
16959
|
+
const entries = readIssueCosts(issueId, parseInt(options.days, 10));
|
|
16960
|
+
if (entries.length === 0) {
|
|
16961
|
+
console.log(chalk61.dim("No costs found for issue:"), issueId);
|
|
16962
|
+
return;
|
|
16963
|
+
}
|
|
16964
|
+
const summary = summarizeCosts(entries);
|
|
16965
|
+
console.log(chalk61.bold(`Costs for ${issueId}`));
|
|
16966
|
+
console.log();
|
|
16967
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16968
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16969
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16970
|
+
console.log();
|
|
16971
|
+
if (Object.keys(summary.byModel).length > 0) {
|
|
16972
|
+
console.log(chalk61.bold("By Model"));
|
|
16973
|
+
for (const [model, cost2] of Object.entries(summary.byModel)) {
|
|
16974
|
+
console.log(` ${model}: ${formatCost(cost2)}`);
|
|
16975
|
+
}
|
|
16976
|
+
}
|
|
16977
|
+
} catch (error) {
|
|
16978
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16979
|
+
process.exit(1);
|
|
16980
|
+
}
|
|
16981
|
+
});
|
|
16982
|
+
const budget = cost.command("budget").description("Manage cost budgets");
|
|
16983
|
+
budget.command("create <name>").description("Create a cost budget").option("-l, --limit <amount>", "Budget limit in USD", "100").option("-t, --type <type>", "Budget type (daily, monthly, project, issue, feature)", "monthly").option("-a, --alert <threshold>", "Alert threshold (0-1)", "0.8").action((name, options) => {
|
|
16984
|
+
try {
|
|
16985
|
+
const newBudget = createBudget({
|
|
16986
|
+
name,
|
|
16987
|
+
type: options.type,
|
|
16988
|
+
limit: parseFloat(options.limit),
|
|
16989
|
+
currency: "USD",
|
|
16990
|
+
alertThreshold: parseFloat(options.alert),
|
|
16991
|
+
enabled: true
|
|
16992
|
+
});
|
|
16993
|
+
console.log(chalk61.green("\u2713 Budget created"));
|
|
16994
|
+
console.log(` ID: ${chalk61.cyan(newBudget.id)}`);
|
|
16995
|
+
console.log(` Name: ${newBudget.name}`);
|
|
16996
|
+
console.log(` Type: ${newBudget.type}`);
|
|
16997
|
+
console.log(` Limit: ${formatCost(newBudget.limit)}`);
|
|
16998
|
+
console.log(` Alert at: ${(newBudget.alertThreshold * 100).toFixed(0)}%`);
|
|
16999
|
+
} catch (error) {
|
|
17000
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17001
|
+
process.exit(1);
|
|
17002
|
+
}
|
|
17003
|
+
});
|
|
17004
|
+
budget.command("list").description("List all budgets").action(() => {
|
|
17005
|
+
try {
|
|
17006
|
+
const budgets = getAllBudgets();
|
|
17007
|
+
if (budgets.length === 0) {
|
|
17008
|
+
console.log(chalk61.dim("No budgets configured"));
|
|
17009
|
+
console.log(chalk61.dim('Create one with: pan cost budget create "Monthly Limit" --limit 100'));
|
|
17010
|
+
return;
|
|
17011
|
+
}
|
|
17012
|
+
console.log(chalk61.bold("Budgets"));
|
|
17013
|
+
console.log();
|
|
17014
|
+
for (const b of budgets) {
|
|
17015
|
+
const status = checkBudget(b.id);
|
|
17016
|
+
const percentStr = `${(status.percentUsed * 100).toFixed(0)}%`;
|
|
17017
|
+
let statusColor = chalk61.green;
|
|
17018
|
+
if (status.exceeded) {
|
|
17019
|
+
statusColor = chalk61.red;
|
|
17020
|
+
} else if (status.alert) {
|
|
17021
|
+
statusColor = chalk61.yellow;
|
|
17022
|
+
}
|
|
17023
|
+
console.log(`${b.enabled ? "\u25CF" : "\u25CB"} ${chalk61.cyan(b.id)} ${b.name}`);
|
|
17024
|
+
console.log(` Type: ${b.type}`);
|
|
17025
|
+
console.log(` Limit: ${formatCost(b.limit)}`);
|
|
17026
|
+
console.log(` Spent: ${statusColor(formatCost(b.spent))} (${statusColor(percentStr)})`);
|
|
17027
|
+
console.log(` Remaining: ${formatCost(status.remaining)}`);
|
|
17028
|
+
console.log();
|
|
17029
|
+
}
|
|
17030
|
+
} catch (error) {
|
|
17031
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17032
|
+
process.exit(1);
|
|
17033
|
+
}
|
|
17034
|
+
});
|
|
17035
|
+
budget.command("check <id>").description("Check budget status").action((id) => {
|
|
17036
|
+
try {
|
|
17037
|
+
const status = checkBudget(id);
|
|
17038
|
+
if (!status.budget) {
|
|
17039
|
+
console.log(chalk61.red("Budget not found:"), id);
|
|
17040
|
+
process.exit(1);
|
|
17041
|
+
}
|
|
17042
|
+
const b = status.budget;
|
|
17043
|
+
const percentStr = `${(status.percentUsed * 100).toFixed(0)}%`;
|
|
17044
|
+
let statusColor = chalk61.green;
|
|
17045
|
+
let statusText = "OK";
|
|
17046
|
+
if (status.exceeded) {
|
|
17047
|
+
statusColor = chalk61.red;
|
|
17048
|
+
statusText = "EXCEEDED";
|
|
17049
|
+
} else if (status.alert) {
|
|
17050
|
+
statusColor = chalk61.yellow;
|
|
17051
|
+
statusText = "WARNING";
|
|
17052
|
+
}
|
|
17053
|
+
console.log(chalk61.bold(b.name));
|
|
17054
|
+
console.log();
|
|
17055
|
+
console.log(`Status: ${statusColor(statusText)}`);
|
|
17056
|
+
console.log(`Limit: ${formatCost(b.limit)}`);
|
|
17057
|
+
console.log(`Spent: ${statusColor(formatCost(b.spent))} (${statusColor(percentStr)})`);
|
|
17058
|
+
console.log(`Remaining: ${formatCost(status.remaining)}`);
|
|
17059
|
+
console.log(`Alert Threshold: ${(b.alertThreshold * 100).toFixed(0)}%`);
|
|
17060
|
+
} catch (error) {
|
|
17061
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17062
|
+
process.exit(1);
|
|
17063
|
+
}
|
|
17064
|
+
});
|
|
17065
|
+
budget.command("delete <id>").description("Delete a budget").action((id) => {
|
|
17066
|
+
try {
|
|
17067
|
+
const success = deleteBudget(id);
|
|
17068
|
+
if (success) {
|
|
17069
|
+
console.log(chalk61.green("\u2713 Budget deleted"));
|
|
17070
|
+
} else {
|
|
17071
|
+
console.log(chalk61.red("Budget not found:"), id);
|
|
17072
|
+
process.exit(1);
|
|
17073
|
+
}
|
|
17074
|
+
} catch (error) {
|
|
17075
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17076
|
+
process.exit(1);
|
|
17077
|
+
}
|
|
17078
|
+
});
|
|
17079
|
+
cost.command("sync").description("Import cost events from per-project WAL files into the local database").action(runCostSync);
|
|
17080
|
+
return cost;
|
|
17081
|
+
}
|
|
17082
|
+
|
|
16483
17083
|
// src/cli/index.ts
|
|
16484
|
-
var PANOPTICON_ENV_FILE =
|
|
16485
|
-
if (
|
|
17084
|
+
var PANOPTICON_ENV_FILE = join56(homedir27(), ".panopticon.env");
|
|
17085
|
+
if (existsSync56(PANOPTICON_ENV_FILE)) {
|
|
16486
17086
|
try {
|
|
16487
|
-
const envContent =
|
|
17087
|
+
const envContent = readFileSync49(PANOPTICON_ENV_FILE, "utf-8");
|
|
16488
17088
|
for (const line of envContent.split("\n")) {
|
|
16489
17089
|
const trimmed = line.trim();
|
|
16490
17090
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -16500,8 +17100,8 @@ if (existsSync55(PANOPTICON_ENV_FILE)) {
|
|
|
16500
17100
|
console.warn("Warning: Failed to load ~/.panopticon.env:", error.message);
|
|
16501
17101
|
}
|
|
16502
17102
|
}
|
|
16503
|
-
var program = new
|
|
16504
|
-
program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(
|
|
17103
|
+
var program = new Command2();
|
|
17104
|
+
program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(readFileSync49(join56(import.meta.dirname, "../../package.json"), "utf-8")).version);
|
|
16505
17105
|
program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
|
|
16506
17106
|
program.command("sync").description("Sync skills/agents/rules to devroot").option("--dry-run", "Show what would be synced").option("--force", "Overwrite files modified since Panopticon installed them").option("--diff", "Show diff for modified files").option("--backup-only", "Only create backup").action(syncCommand);
|
|
16507
17107
|
program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
|
|
@@ -16525,58 +17125,58 @@ program.command("migrate-config").description("Migrate from settings.json to con
|
|
|
16525
17125
|
program.command("status").description("Show running agents (shorthand for work status)").option("--json", "Output as JSON").option("--tldr", "Show TLDR index health across all workspaces").option("--context", "Show context window usage % for each agent").action(statusCommand);
|
|
16526
17126
|
program.command("up").description("Start dashboard (and Traefik if enabled)").option("--detach", "Run in background").option("--skip-traefik", "Skip Traefik startup").action(async (options) => {
|
|
16527
17127
|
const { spawn: spawn2, execSync: execSync9 } = await import("child_process");
|
|
16528
|
-
const { join:
|
|
17128
|
+
const { join: join57, dirname: dirname17 } = await import("path");
|
|
16529
17129
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
16530
|
-
const { readFileSync:
|
|
17130
|
+
const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("fs");
|
|
16531
17131
|
const { parse } = await import("@iarna/toml");
|
|
16532
17132
|
const __dirname6 = dirname17(fileURLToPath6(import.meta.url));
|
|
16533
|
-
const bundledServer =
|
|
16534
|
-
const srcDashboard =
|
|
16535
|
-
const configFile =
|
|
17133
|
+
const bundledServer = join57(__dirname6, "..", "dashboard", "server.js");
|
|
17134
|
+
const srcDashboard = join57(__dirname6, "..", "..", "src", "dashboard");
|
|
17135
|
+
const configFile = join57(process.env.HOME || "", ".panopticon", "config.toml");
|
|
16536
17136
|
let traefikEnabled = false;
|
|
16537
17137
|
let traefikDomain = "pan.localhost";
|
|
16538
17138
|
let dashboardPort = 3010;
|
|
16539
17139
|
let dashboardApiPort = 3011;
|
|
16540
|
-
if (
|
|
17140
|
+
if (existsSync57(configFile)) {
|
|
16541
17141
|
try {
|
|
16542
|
-
const configContent =
|
|
17142
|
+
const configContent = readFileSync50(configFile, "utf-8");
|
|
16543
17143
|
const config2 = parse(configContent);
|
|
16544
17144
|
traefikEnabled = config2.traefik?.enabled === true;
|
|
16545
17145
|
traefikDomain = config2.traefik?.domain || "pan.localhost";
|
|
16546
17146
|
dashboardPort = config2.dashboard?.port || 3010;
|
|
16547
17147
|
dashboardApiPort = config2.dashboard?.api_port || 3011;
|
|
16548
17148
|
} catch (error) {
|
|
16549
|
-
console.log(
|
|
17149
|
+
console.log(chalk62.yellow("Warning: Could not read config.toml"));
|
|
16550
17150
|
}
|
|
16551
17151
|
}
|
|
16552
|
-
console.log(
|
|
17152
|
+
console.log(chalk62.bold("Starting Panopticon...\n"));
|
|
16553
17153
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16554
17154
|
try {
|
|
16555
|
-
const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-
|
|
17155
|
+
const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-5GL3Q7DJ.js");
|
|
16556
17156
|
cleanupStaleTlsSections();
|
|
16557
17157
|
if (generatePanopticonTraefikConfig2()) {
|
|
16558
|
-
console.log(
|
|
17158
|
+
console.log(chalk62.dim(" Regenerated Traefik config from template"));
|
|
16559
17159
|
}
|
|
16560
17160
|
const generatedDomains = ensureProjectCerts2();
|
|
16561
17161
|
for (const domain of generatedDomains) {
|
|
16562
|
-
console.log(
|
|
17162
|
+
console.log(chalk62.dim(` Generated wildcard cert for *.${domain}`));
|
|
16563
17163
|
}
|
|
16564
17164
|
if (generateTlsConfig2()) {
|
|
16565
|
-
console.log(
|
|
17165
|
+
console.log(chalk62.dim(" Generated TLS config (tls.yml)"));
|
|
16566
17166
|
}
|
|
16567
17167
|
} catch {
|
|
16568
|
-
console.log(
|
|
17168
|
+
console.log(chalk62.yellow("Warning: Could not regenerate Traefik config"));
|
|
16569
17169
|
}
|
|
16570
17170
|
try {
|
|
16571
17171
|
const { ensureBaseDomain: ensureBaseDomain2, detectDnsSyncMethod: detectDnsSyncMethod2, syncDnsToWindows: syncDnsToWindows2 } = await import("../dns-7BDJSD3E.js");
|
|
16572
|
-
const dnsMethod = (
|
|
17172
|
+
const dnsMethod = (existsSync57(configFile) ? parse(readFileSync50(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
|
|
16573
17173
|
ensureBaseDomain2(dnsMethod, traefikDomain);
|
|
16574
17174
|
if (dnsMethod === "wsl2hosts") {
|
|
16575
17175
|
syncDnsToWindows2().catch(() => {
|
|
16576
17176
|
});
|
|
16577
17177
|
}
|
|
16578
17178
|
} catch {
|
|
16579
|
-
console.log(
|
|
17179
|
+
console.log(chalk62.yellow(`Warning: Could not ensure DNS for ${traefikDomain}`));
|
|
16580
17180
|
}
|
|
16581
17181
|
} else if (!traefikEnabled) {
|
|
16582
17182
|
try {
|
|
@@ -16585,19 +17185,19 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16585
17185
|
{ encoding: "utf-8" }
|
|
16586
17186
|
).trim();
|
|
16587
17187
|
if (containerCheck.includes("panopticon-traefik")) {
|
|
16588
|
-
console.log(
|
|
16589
|
-
console.log(
|
|
17188
|
+
console.log(chalk62.yellow("\u26A0 Traefik container is running but traefik.enabled is not set in config"));
|
|
17189
|
+
console.log(chalk62.yellow(" Run `pan install` to configure Traefik, or `pan down` to stop it\n"));
|
|
16590
17190
|
}
|
|
16591
17191
|
} catch {
|
|
16592
17192
|
}
|
|
16593
17193
|
}
|
|
16594
17194
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16595
|
-
const traefikDir =
|
|
16596
|
-
if (
|
|
17195
|
+
const traefikDir = join57(process.env.HOME || "", ".panopticon", "traefik");
|
|
17196
|
+
if (existsSync57(traefikDir)) {
|
|
16597
17197
|
try {
|
|
16598
|
-
const composeFile =
|
|
16599
|
-
if (
|
|
16600
|
-
const content =
|
|
17198
|
+
const composeFile = join57(traefikDir, "docker-compose.yml");
|
|
17199
|
+
if (existsSync57(composeFile)) {
|
|
17200
|
+
const content = readFileSync50(composeFile, "utf-8");
|
|
16601
17201
|
if (!content.includes("external: true") && content.includes("panopticon:")) {
|
|
16602
17202
|
const patched = content.replace(
|
|
16603
17203
|
/networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
|
|
@@ -16605,43 +17205,43 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16605
17205
|
);
|
|
16606
17206
|
const { writeFileSync: writeFileSync27 } = await import("fs");
|
|
16607
17207
|
writeFileSync27(composeFile, patched);
|
|
16608
|
-
console.log(
|
|
17208
|
+
console.log(chalk62.dim(" (migrated network config)"));
|
|
16609
17209
|
}
|
|
16610
17210
|
}
|
|
16611
|
-
console.log(
|
|
17211
|
+
console.log(chalk62.dim("Starting Traefik..."));
|
|
16612
17212
|
execSync9("docker compose up -d", {
|
|
16613
17213
|
cwd: traefikDir,
|
|
16614
17214
|
stdio: "pipe"
|
|
16615
17215
|
});
|
|
16616
|
-
console.log(
|
|
16617
|
-
console.log(
|
|
17216
|
+
console.log(chalk62.green("\u2713 Traefik started"));
|
|
17217
|
+
console.log(chalk62.dim(` Dashboard: https://traefik.${traefikDomain}:8080
|
|
16618
17218
|
`));
|
|
16619
17219
|
} catch (error) {
|
|
16620
|
-
console.log(
|
|
16621
|
-
console.log(
|
|
17220
|
+
console.log(chalk62.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
|
|
17221
|
+
console.log(chalk62.dim(" Run with --skip-traefik to suppress this message\n"));
|
|
16622
17222
|
}
|
|
16623
17223
|
}
|
|
16624
17224
|
}
|
|
16625
|
-
const isProduction =
|
|
16626
|
-
const isDevelopment =
|
|
17225
|
+
const isProduction = existsSync57(bundledServer);
|
|
17226
|
+
const isDevelopment = existsSync57(srcDashboard);
|
|
16627
17227
|
if (!isProduction && !isDevelopment) {
|
|
16628
|
-
console.error(
|
|
16629
|
-
console.error(
|
|
17228
|
+
console.error(chalk62.red("Error: Dashboard not found"));
|
|
17229
|
+
console.error(chalk62.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
|
|
16630
17230
|
process.exit(1);
|
|
16631
17231
|
}
|
|
16632
17232
|
if (isDevelopment && !isProduction) {
|
|
16633
17233
|
try {
|
|
16634
17234
|
execSync9("npm --version", { stdio: "pipe" });
|
|
16635
17235
|
} catch {
|
|
16636
|
-
console.error(
|
|
16637
|
-
console.error(
|
|
17236
|
+
console.error(chalk62.red("Error: npm not found in PATH"));
|
|
17237
|
+
console.error(chalk62.dim("Make sure Node.js and npm are installed and in your PATH"));
|
|
16638
17238
|
process.exit(1);
|
|
16639
17239
|
}
|
|
16640
17240
|
}
|
|
16641
17241
|
if (isProduction) {
|
|
16642
|
-
console.log(
|
|
17242
|
+
console.log(chalk62.dim("Starting dashboard (bundled mode)..."));
|
|
16643
17243
|
} else {
|
|
16644
|
-
console.log(
|
|
17244
|
+
console.log(chalk62.dim("Starting dashboard (development mode)..."));
|
|
16645
17245
|
}
|
|
16646
17246
|
if (options.detach) {
|
|
16647
17247
|
const child = isProduction ? spawn2("node", [bundledServer], {
|
|
@@ -16657,7 +17257,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16657
17257
|
let hasError = false;
|
|
16658
17258
|
child.on("error", (err) => {
|
|
16659
17259
|
hasError = true;
|
|
16660
|
-
console.error(
|
|
17260
|
+
console.error(chalk62.red("Failed to start dashboard in background:"), err.message);
|
|
16661
17261
|
process.exit(1);
|
|
16662
17262
|
});
|
|
16663
17263
|
setTimeout(() => {
|
|
@@ -16665,23 +17265,23 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16665
17265
|
child.unref();
|
|
16666
17266
|
}
|
|
16667
17267
|
}, 100);
|
|
16668
|
-
console.log(
|
|
17268
|
+
console.log(chalk62.green("\u2713 Dashboard started in background"));
|
|
16669
17269
|
if (traefikEnabled) {
|
|
16670
|
-
console.log(` Frontend: ${
|
|
16671
|
-
console.log(` API: ${
|
|
17270
|
+
console.log(` Frontend: ${chalk62.cyan(`https://${traefikDomain}`)}`);
|
|
17271
|
+
console.log(` API: ${chalk62.cyan(`https://${traefikDomain}/api`)}`);
|
|
16672
17272
|
} else {
|
|
16673
|
-
console.log(` Frontend: ${
|
|
16674
|
-
console.log(` API: ${
|
|
17273
|
+
console.log(` Frontend: ${chalk62.cyan(`http://localhost:${dashboardPort}`)}`);
|
|
17274
|
+
console.log(` API: ${chalk62.cyan(`http://localhost:${dashboardApiPort}`)}`);
|
|
16675
17275
|
}
|
|
16676
17276
|
} else {
|
|
16677
17277
|
if (traefikEnabled) {
|
|
16678
|
-
console.log(` Frontend: ${
|
|
16679
|
-
console.log(` API: ${
|
|
17278
|
+
console.log(` Frontend: ${chalk62.cyan(`https://${traefikDomain}`)}`);
|
|
17279
|
+
console.log(` API: ${chalk62.cyan(`https://${traefikDomain}/api`)}`);
|
|
16680
17280
|
} else {
|
|
16681
|
-
console.log(` Frontend: ${
|
|
16682
|
-
console.log(` API: ${
|
|
17281
|
+
console.log(` Frontend: ${chalk62.cyan(`http://localhost:${dashboardPort}`)}`);
|
|
17282
|
+
console.log(` API: ${chalk62.cyan(`http://localhost:${dashboardApiPort}`)}`);
|
|
16683
17283
|
}
|
|
16684
|
-
console.log(
|
|
17284
|
+
console.log(chalk62.dim("\nPress Ctrl+C to stop\n"));
|
|
16685
17285
|
const child = isProduction ? spawn2("node", [bundledServer], {
|
|
16686
17286
|
stdio: "inherit",
|
|
16687
17287
|
env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
|
|
@@ -16691,41 +17291,41 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16691
17291
|
shell: true
|
|
16692
17292
|
});
|
|
16693
17293
|
child.on("error", (err) => {
|
|
16694
|
-
console.error(
|
|
17294
|
+
console.error(chalk62.red("Failed to start dashboard:"), err.message);
|
|
16695
17295
|
process.exit(1);
|
|
16696
17296
|
});
|
|
16697
17297
|
}
|
|
16698
17298
|
try {
|
|
16699
17299
|
const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
|
|
16700
17300
|
const projectRoot = process.cwd();
|
|
16701
|
-
const venvPath =
|
|
16702
|
-
if (
|
|
16703
|
-
console.log(
|
|
17301
|
+
const venvPath = join57(projectRoot, ".venv");
|
|
17302
|
+
if (existsSync57(venvPath)) {
|
|
17303
|
+
console.log(chalk62.dim("\nStarting TLDR daemon for project root..."));
|
|
16704
17304
|
const tldrService = getTldrDaemonService2(projectRoot, venvPath);
|
|
16705
17305
|
await tldrService.start(true);
|
|
16706
|
-
console.log(
|
|
17306
|
+
console.log(chalk62.green("\u2713 TLDR daemon started"));
|
|
16707
17307
|
} else {
|
|
16708
|
-
console.log(
|
|
16709
|
-
console.log(
|
|
17308
|
+
console.log(chalk62.dim("\nSkipping TLDR daemon (no .venv found)"));
|
|
17309
|
+
console.log(chalk62.dim(" Run setup to create venv with llm-tldr"));
|
|
16710
17310
|
}
|
|
16711
17311
|
} catch (error) {
|
|
16712
|
-
console.log(
|
|
16713
|
-
console.log(
|
|
17312
|
+
console.log(chalk62.yellow("\u26A0 Failed to start TLDR daemon:"), error?.message || String(error));
|
|
17313
|
+
console.log(chalk62.dim(" TLDR will be unavailable but dashboard will work normally"));
|
|
16714
17314
|
}
|
|
16715
17315
|
});
|
|
16716
17316
|
program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
|
|
16717
17317
|
const { execSync: execSync9 } = await import("child_process");
|
|
16718
|
-
const { join:
|
|
16719
|
-
const { readFileSync:
|
|
17318
|
+
const { join: join57 } = await import("path");
|
|
17319
|
+
const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("fs");
|
|
16720
17320
|
const { parse } = await import("@iarna/toml");
|
|
16721
|
-
console.log(
|
|
16722
|
-
const configFile =
|
|
17321
|
+
console.log(chalk62.bold("Stopping Panopticon...\n"));
|
|
17322
|
+
const configFile = join57(process.env.HOME || "", ".panopticon", "config.toml");
|
|
16723
17323
|
let traefikEnabled = false;
|
|
16724
17324
|
let dashboardPort = 3010;
|
|
16725
17325
|
let dashboardApiPort = 3011;
|
|
16726
|
-
if (
|
|
17326
|
+
if (existsSync57(configFile)) {
|
|
16727
17327
|
try {
|
|
16728
|
-
const configContent =
|
|
17328
|
+
const configContent = readFileSync50(configFile, "utf-8");
|
|
16729
17329
|
const config2 = parse(configContent);
|
|
16730
17330
|
traefikEnabled = config2.traefik?.enabled === true;
|
|
16731
17331
|
dashboardPort = config2.dashboard?.port || 3010;
|
|
@@ -16733,44 +17333,44 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
|
|
|
16733
17333
|
} catch (error) {
|
|
16734
17334
|
}
|
|
16735
17335
|
}
|
|
16736
|
-
console.log(
|
|
17336
|
+
console.log(chalk62.dim("Stopping dashboard..."));
|
|
16737
17337
|
try {
|
|
16738
17338
|
execSync9(`lsof -ti:${dashboardPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
|
|
16739
17339
|
execSync9(`lsof -ti:${dashboardApiPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
|
|
16740
|
-
console.log(
|
|
17340
|
+
console.log(chalk62.green("\u2713 Dashboard stopped"));
|
|
16741
17341
|
} catch {
|
|
16742
|
-
console.log(
|
|
17342
|
+
console.log(chalk62.dim(" No dashboard processes found"));
|
|
16743
17343
|
}
|
|
16744
17344
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16745
|
-
const traefikDir =
|
|
16746
|
-
if (
|
|
16747
|
-
console.log(
|
|
17345
|
+
const traefikDir = join57(process.env.HOME || "", ".panopticon", "traefik");
|
|
17346
|
+
if (existsSync57(traefikDir)) {
|
|
17347
|
+
console.log(chalk62.dim("Stopping Traefik..."));
|
|
16748
17348
|
try {
|
|
16749
17349
|
execSync9("docker compose down", {
|
|
16750
17350
|
cwd: traefikDir,
|
|
16751
17351
|
stdio: "pipe"
|
|
16752
17352
|
});
|
|
16753
|
-
console.log(
|
|
17353
|
+
console.log(chalk62.green("\u2713 Traefik stopped"));
|
|
16754
17354
|
} catch (error) {
|
|
16755
|
-
console.log(
|
|
17355
|
+
console.log(chalk62.yellow("\u26A0 Failed to stop Traefik"));
|
|
16756
17356
|
}
|
|
16757
17357
|
}
|
|
16758
17358
|
}
|
|
16759
17359
|
try {
|
|
16760
17360
|
const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
|
|
16761
|
-
const { exec:
|
|
16762
|
-
const { promisify:
|
|
16763
|
-
const
|
|
17361
|
+
const { exec: exec24 } = await import("child_process");
|
|
17362
|
+
const { promisify: promisify24 } = await import("util");
|
|
17363
|
+
const execAsync24 = promisify24(exec24);
|
|
16764
17364
|
const projectRoot = process.cwd();
|
|
16765
|
-
const venvPath =
|
|
16766
|
-
if (
|
|
16767
|
-
console.log(
|
|
17365
|
+
const venvPath = join57(projectRoot, ".venv");
|
|
17366
|
+
if (existsSync57(venvPath)) {
|
|
17367
|
+
console.log(chalk62.dim("\nStopping TLDR daemon..."));
|
|
16768
17368
|
const tldrService = getTldrDaemonService2(projectRoot, venvPath);
|
|
16769
17369
|
await tldrService.stop();
|
|
16770
|
-
console.log(
|
|
17370
|
+
console.log(chalk62.green("\u2713 TLDR daemon stopped"));
|
|
16771
17371
|
}
|
|
16772
17372
|
} catch (error) {
|
|
16773
|
-
console.log(
|
|
17373
|
+
console.log(chalk62.dim(" (TLDR daemon not running)"));
|
|
16774
17374
|
}
|
|
16775
17375
|
console.log("");
|
|
16776
17376
|
});
|
|
@@ -16782,5 +17382,9 @@ project.command("remove <nameOrPath>").description("Remove a project from the re
|
|
|
16782
17382
|
project.command("init").description("Initialize projects.yaml with example configuration").action(projectInitCommand);
|
|
16783
17383
|
program.command("doctor").description("Check system health and dependencies").action(doctorCommand);
|
|
16784
17384
|
program.command("update").description("Update Panopticon to latest version").option("--check", "Only check for updates, don't install").option("--force", "Force update even if on latest").action(updateCommand2);
|
|
16785
|
-
program.
|
|
17385
|
+
program.addCommand(createCostCommand());
|
|
17386
|
+
program.command("sync-costs").description("Import cost events from per-project WAL files (alias for: pan cost sync)").action(async () => {
|
|
17387
|
+
await program.parseAsync(["cost", "sync"], { from: "user" });
|
|
17388
|
+
});
|
|
17389
|
+
await program.parseAsync();
|
|
16786
17390
|
//# sourceMappingURL=index.js.map
|