panopticon-cli 0.5.1 → 0.5.4
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-HNMF52RM.js} +7 -6
- 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-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-OWHXCGVO.js → chunk-ID4OYXVH.js} +378 -101
- package/dist/chunk-ID4OYXVH.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-VHKSS7QX.js → chunk-KY2E2Q3T.js} +25 -19
- package/dist/chunk-KY2E2Q3T.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 +1390 -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-DA6pnizT.js +767 -0
- package/dist/dashboard/public/assets/index-DSvt5pPn.css +32 -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 +4728 -2767
- 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-C66TEMXS.js} +6 -5
- package/dist/{specialist-context-WGUUYDWY.js.map → specialist-context-C66TEMXS.js.map} +1 -1
- package/dist/{specialist-logs-XJB5TCKJ.js → specialist-logs-CJKXM3SR.js} +6 -5
- package/dist/{specialists-5LBRHYFA.js → specialists-NXYD4Z62.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/recover-costs-deep.mjs +209 -0
- package/scripts/recover-costs-proportional.mjs +206 -0
- package/scripts/recover-costs.mjs +169 -0
- 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-HNMF52RM.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-CJKXM3SR.js.map} +0 -0
- /package/dist/{traefik-WFMQX2LY.js.map → specialists-NXYD4Z62.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-ID4OYXVH.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-KY2E2Q3T.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-HNMF52RM.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-HNMF52RM.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-HNMF52RM.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,61 @@ 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
|
+
var mergeReadyNotifier = null;
|
|
11583
|
+
async function checkReadyForMergeStuck() {
|
|
11584
|
+
const actions = [];
|
|
11585
|
+
try {
|
|
11586
|
+
if (!existsSync42(REVIEW_STATUS_FILE)) {
|
|
11587
|
+
return actions;
|
|
11588
|
+
}
|
|
11589
|
+
const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
|
|
11590
|
+
const statuses = JSON.parse(content);
|
|
11591
|
+
const now = Date.now();
|
|
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
|
+
console.warn(`[deacon] readyForMerge stuck for ${key} (age: ${ageMin}m, attempts: ${attempts}) \u2014 merge requires manual action via MERGE button`);
|
|
11610
|
+
mergeStuckCooldowns.set(key, now);
|
|
11611
|
+
attemptCounts[key] = attempts + 1;
|
|
11612
|
+
stateModified = true;
|
|
11613
|
+
const msg = `Stuck-merge: ${key} has been readyForMerge for ${ageMin}m \u2014 click MERGE to proceed`;
|
|
11614
|
+
if (mergeReadyNotifier) {
|
|
11615
|
+
mergeReadyNotifier(status.issueId ?? key);
|
|
11616
|
+
actions.push(msg);
|
|
11617
|
+
console.log(`[deacon] merge:ready notification sent for ${key}`);
|
|
11618
|
+
} else {
|
|
11619
|
+
actions.push(msg);
|
|
11620
|
+
console.warn(`[deacon] No mergeReadyNotifier registered \u2014 dashboard will not be notified for ${key}`);
|
|
11621
|
+
}
|
|
11622
|
+
}
|
|
11623
|
+
if (stateModified) {
|
|
11624
|
+
state.mergeStuckAttempts = attemptCounts;
|
|
11625
|
+
saveState(state);
|
|
11626
|
+
}
|
|
11627
|
+
} catch (error) {
|
|
11628
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11629
|
+
console.error("[deacon] Error in checkReadyForMergeStuck:", msg);
|
|
11630
|
+
}
|
|
11631
|
+
return actions;
|
|
11632
|
+
}
|
|
11527
11633
|
var deadEndCooldowns = /* @__PURE__ */ new Map();
|
|
11528
11634
|
var DEAD_END_STALENESS_MS = 5 * 60 * 1e3;
|
|
11529
11635
|
var DEAD_END_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
@@ -11533,7 +11639,7 @@ async function checkDeadEndAgents() {
|
|
|
11533
11639
|
if (!existsSync42(REVIEW_STATUS_FILE)) {
|
|
11534
11640
|
return actions;
|
|
11535
11641
|
}
|
|
11536
|
-
const content =
|
|
11642
|
+
const content = readFileSync36(REVIEW_STATUS_FILE, "utf-8");
|
|
11537
11643
|
const statuses = JSON.parse(content);
|
|
11538
11644
|
const now = Date.now();
|
|
11539
11645
|
for (const [key, status] of Object.entries(statuses)) {
|
|
@@ -11601,7 +11707,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11601
11707
|
const idleMs = now - lastActivity.getTime();
|
|
11602
11708
|
if (idleMs < FIRST_COMPLETION_IDLE_MS) continue;
|
|
11603
11709
|
try {
|
|
11604
|
-
const { stdout: lastLines } = await
|
|
11710
|
+
const { stdout: lastLines } = await execAsync16(
|
|
11605
11711
|
`tmux capture-pane -t "${agent.id}" -p -S -3 2>/dev/null || echo ""`,
|
|
11606
11712
|
{ encoding: "utf-8" }
|
|
11607
11713
|
);
|
|
@@ -11618,7 +11724,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11618
11724
|
const issueKey = issueId.toLowerCase();
|
|
11619
11725
|
if (existsSync42(REVIEW_STATUS_FILE)) {
|
|
11620
11726
|
try {
|
|
11621
|
-
const statuses = JSON.parse(
|
|
11727
|
+
const statuses = JSON.parse(readFileSync36(REVIEW_STATUS_FILE, "utf-8"));
|
|
11622
11728
|
const hasStatus = statuses[issueKey] || statuses[issueId] || statuses[issueId.toUpperCase()];
|
|
11623
11729
|
if (hasStatus) {
|
|
11624
11730
|
console.log(`[deacon] First-completion gate: skipping ${agent.id} \u2014 has review status entry (readyForMerge=${hasStatus.readyForMerge ?? false})`);
|
|
@@ -11645,7 +11751,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11645
11751
|
if (!agentState?.workspace || !existsSync42(agentState.workspace)) continue;
|
|
11646
11752
|
let hasCommits = false;
|
|
11647
11753
|
try {
|
|
11648
|
-
const { stdout: gitLog } = await
|
|
11754
|
+
const { stdout: gitLog } = await execAsync16(
|
|
11649
11755
|
"git log --oneline -3 2>/dev/null",
|
|
11650
11756
|
{ cwd: agentState.workspace }
|
|
11651
11757
|
);
|
|
@@ -11655,7 +11761,7 @@ async function checkFirstCompletionAgents() {
|
|
|
11655
11761
|
const subdirs = readdirSync17(agentState.workspace, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
11656
11762
|
for (const sub of subdirs) {
|
|
11657
11763
|
try {
|
|
11658
|
-
const { stdout: subLog } = await
|
|
11764
|
+
const { stdout: subLog } = await execAsync16(
|
|
11659
11765
|
"git log --oneline -3 2>/dev/null",
|
|
11660
11766
|
{ cwd: join41(agentState.workspace, sub.name) }
|
|
11661
11767
|
);
|
|
@@ -11692,6 +11798,58 @@ If you still have remaining tasks, continue working on them.`;
|
|
|
11692
11798
|
}
|
|
11693
11799
|
return actions;
|
|
11694
11800
|
}
|
|
11801
|
+
async function patrolWorkAgentResolutions() {
|
|
11802
|
+
const actions = [];
|
|
11803
|
+
try {
|
|
11804
|
+
const agents = listRunningAgents();
|
|
11805
|
+
const specialists = getEnabledSpecialists();
|
|
11806
|
+
const specialistNames = new Set(specialists.map((s) => getTmuxSessionName(s.name)));
|
|
11807
|
+
for (const agent of agents) {
|
|
11808
|
+
if (!agent.id.startsWith("agent-") || specialistNames.has(agent.id)) continue;
|
|
11809
|
+
const runtimeState = getAgentRuntimeState(agent.id);
|
|
11810
|
+
if (!runtimeState?.resolution || runtimeState.resolution === "working" || runtimeState.resolution === "completed") continue;
|
|
11811
|
+
const resolution = runtimeState.resolution;
|
|
11812
|
+
const count = runtimeState.resolutionCount || 0;
|
|
11813
|
+
const issueId = (agent.issueId || agent.id.replace("agent-", "")).toUpperCase();
|
|
11814
|
+
if (resolution === "done" && count >= 2) {
|
|
11815
|
+
console.log(`[deacon] Auto-completing ${agent.id} (${issueId}): resolution=done, count=${count}`);
|
|
11816
|
+
try {
|
|
11817
|
+
const panBin = join41(PANOPTICON_HOME, "bin", "pan");
|
|
11818
|
+
const binExists = existsSync42(panBin);
|
|
11819
|
+
const bin = binExists ? panBin : "pan";
|
|
11820
|
+
await execFileAsync(bin, ["work", "done", issueId, "-c", "Auto-completed by Deacon: evidence showed work complete after 2 nudges"], {
|
|
11821
|
+
timeout: 3e4
|
|
11822
|
+
});
|
|
11823
|
+
saveAgentRuntimeState(agent.id, {
|
|
11824
|
+
resolution: "completed",
|
|
11825
|
+
resolutionCount: count + 1,
|
|
11826
|
+
resolutionUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11827
|
+
});
|
|
11828
|
+
actions.push(`Deacon auto-completed ${issueId} (${agent.id}) after ${count} failed nudges`);
|
|
11829
|
+
addLog("action", `Auto-completed ${issueId}: evidence-complete, ${count} nudges exhausted`, void 0);
|
|
11830
|
+
} catch (err) {
|
|
11831
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11832
|
+
console.error(`[deacon] Failed to auto-complete ${agent.id}:`, msg);
|
|
11833
|
+
actions.push(`Deacon auto-complete failed for ${agent.id}: ${msg}`);
|
|
11834
|
+
}
|
|
11835
|
+
} else if (resolution === "stuck" && count >= 3) {
|
|
11836
|
+
console.log(`[deacon] Poking stuck agent ${agent.id} (${issueId}): count=${count}`);
|
|
11837
|
+
try {
|
|
11838
|
+
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"`;
|
|
11839
|
+
await sendKeysAsync(agent.id, pokeMsg);
|
|
11840
|
+
actions.push(`Deacon poked stuck agent ${agent.id} (${issueId})`);
|
|
11841
|
+
addLog("action", `Poked stuck agent ${issueId} (count=${count})`, void 0);
|
|
11842
|
+
} catch (err) {
|
|
11843
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11844
|
+
console.error(`[deacon] Failed to poke ${agent.id}:`, msg);
|
|
11845
|
+
}
|
|
11846
|
+
}
|
|
11847
|
+
}
|
|
11848
|
+
} catch (error) {
|
|
11849
|
+
console.error("[deacon] Error in patrolWorkAgentResolutions:", error);
|
|
11850
|
+
}
|
|
11851
|
+
return actions;
|
|
11852
|
+
}
|
|
11695
11853
|
async function runPatrol() {
|
|
11696
11854
|
const state = loadState();
|
|
11697
11855
|
state.patrolCycle++;
|
|
@@ -11712,6 +11870,8 @@ async function runPatrol() {
|
|
|
11712
11870
|
if (killResult.success) {
|
|
11713
11871
|
console.log(`[deacon] Auto-restarting ${specialist.name} with fresh session...`);
|
|
11714
11872
|
clearSessionId(specialist.name);
|
|
11873
|
+
const specialistSession2 = getTmuxSessionName(specialist.name);
|
|
11874
|
+
saveAgentRuntimeState(specialistSession2, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11715
11875
|
const wakeResult = await wakeSpecialist(specialist.name, "", {
|
|
11716
11876
|
waitForReady: true,
|
|
11717
11877
|
startIfNotRunning: true
|
|
@@ -11747,7 +11907,7 @@ async function runPatrol() {
|
|
|
11747
11907
|
if (nextTask) {
|
|
11748
11908
|
console.log(`[deacon] Auto-resuming suspended ${specialist.name} for queued task: ${nextTask.payload.issueId}`);
|
|
11749
11909
|
try {
|
|
11750
|
-
const { resumeAgent } = await import("../agents-
|
|
11910
|
+
const { resumeAgent } = await import("../agents-HNMF52RM.js");
|
|
11751
11911
|
const message = `# Queued Work
|
|
11752
11912
|
|
|
11753
11913
|
Processing queued task: ${nextTask.payload.issueId}`;
|
|
@@ -11801,9 +11961,15 @@ Processing queued task: ${nextTask.payload.issueId}`;
|
|
|
11801
11961
|
const deadEndActions = await checkDeadEndAgents();
|
|
11802
11962
|
actions.push(...deadEndActions);
|
|
11803
11963
|
for (const a of deadEndActions) addLog("action", a, state.patrolCycle);
|
|
11964
|
+
const mergeStuckActions = await checkReadyForMergeStuck();
|
|
11965
|
+
actions.push(...mergeStuckActions);
|
|
11966
|
+
for (const a of mergeStuckActions) addLog("action", a, state.patrolCycle);
|
|
11804
11967
|
const firstCompletionActions = await checkFirstCompletionAgents();
|
|
11805
11968
|
actions.push(...firstCompletionActions);
|
|
11806
11969
|
for (const a of firstCompletionActions) addLog("action", a, state.patrolCycle);
|
|
11970
|
+
const resolutionActions = await patrolWorkAgentResolutions();
|
|
11971
|
+
actions.push(...resolutionActions);
|
|
11972
|
+
for (const a of resolutionActions) addLog("action", a, state.patrolCycle);
|
|
11807
11973
|
const lazyActions = await checkAndCorrectLazyAgents();
|
|
11808
11974
|
actions.push(...lazyActions);
|
|
11809
11975
|
for (const a of lazyActions) addLog("action", a, state.patrolCycle);
|
|
@@ -11821,6 +11987,38 @@ Processing queued task: ${nextTask.payload.issueId}`;
|
|
|
11821
11987
|
actions.push(massDeathCheck.message);
|
|
11822
11988
|
addLog("error", massDeathCheck.message, state.patrolCycle);
|
|
11823
11989
|
}
|
|
11990
|
+
try {
|
|
11991
|
+
const projectSpecialists = await getAllProjectSpecialistStatuses();
|
|
11992
|
+
for (const projSpec of projectSpecialists) {
|
|
11993
|
+
if (!projSpec.isRunning) {
|
|
11994
|
+
const runtimeState2 = getAgentRuntimeState(projSpec.tmuxSession);
|
|
11995
|
+
if (runtimeState2?.state === "active") {
|
|
11996
|
+
saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11997
|
+
const msg = `Dead-session reset: per-project ${projSpec.specialistType} (${projSpec.projectKey}) was active but session is gone`;
|
|
11998
|
+
actions.push(msg);
|
|
11999
|
+
addLog("action", msg, state.patrolCycle);
|
|
12000
|
+
console.log(`[deacon] ${msg}`);
|
|
12001
|
+
}
|
|
12002
|
+
continue;
|
|
12003
|
+
}
|
|
12004
|
+
const runtimeState = getAgentRuntimeState(projSpec.tmuxSession);
|
|
12005
|
+
const isStuck = runtimeState?.state === "active" && runtimeState.lastActivity ? Date.now() - new Date(runtimeState.lastActivity).getTime() > 15 * 60 * 1e3 : false;
|
|
12006
|
+
if (isStuck) {
|
|
12007
|
+
addLog("warn", `Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing`, state.patrolCycle);
|
|
12008
|
+
console.log(`[deacon] Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing ${projSpec.tmuxSession}`);
|
|
12009
|
+
try {
|
|
12010
|
+
await execAsync16(`tmux kill-session -t "${projSpec.tmuxSession}"`);
|
|
12011
|
+
clearSessionId(projSpec.specialistType, projSpec.projectKey);
|
|
12012
|
+
saveAgentRuntimeState(projSpec.tmuxSession, { state: "idle", lastActivity: (/* @__PURE__ */ new Date()).toISOString() });
|
|
12013
|
+
actions.push(`Force-killed stuck per-project ${projSpec.specialistType} (${projSpec.projectKey})`);
|
|
12014
|
+
} catch {
|
|
12015
|
+
}
|
|
12016
|
+
}
|
|
12017
|
+
}
|
|
12018
|
+
} catch (error) {
|
|
12019
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
12020
|
+
console.error("[deacon] Error during per-project specialist patrol:", msg);
|
|
12021
|
+
}
|
|
11824
12022
|
saveState(state);
|
|
11825
12023
|
const result = {
|
|
11826
12024
|
cycle: state.patrolCycle,
|
|
@@ -11885,7 +12083,7 @@ function getDeaconStatus() {
|
|
|
11885
12083
|
// src/lib/cloister/service.ts
|
|
11886
12084
|
init_paths();
|
|
11887
12085
|
init_paths();
|
|
11888
|
-
import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as
|
|
12086
|
+
import { existsSync as existsSync43, writeFileSync as writeFileSync20, unlinkSync as unlinkSync5, readFileSync as readFileSync37, readdirSync as readdirSync18, renameSync as renameSync3 } from "fs";
|
|
11889
12087
|
import { join as join42 } from "path";
|
|
11890
12088
|
var CLOISTER_STATE_FILE = join42(PANOPTICON_HOME, "cloister.state");
|
|
11891
12089
|
function writeStateFile(running, pid) {
|
|
@@ -11908,7 +12106,7 @@ function writeStateFile(running, pid) {
|
|
|
11908
12106
|
function readStateFile() {
|
|
11909
12107
|
try {
|
|
11910
12108
|
if (existsSync43(CLOISTER_STATE_FILE)) {
|
|
11911
|
-
const data = JSON.parse(
|
|
12109
|
+
const data = JSON.parse(readFileSync37(CLOISTER_STATE_FILE, "utf-8"));
|
|
11912
12110
|
if (data.pid) {
|
|
11913
12111
|
try {
|
|
11914
12112
|
process.kill(data.pid, 0);
|
|
@@ -11958,10 +12156,10 @@ var CloisterService = class {
|
|
|
11958
12156
|
this.starting = true;
|
|
11959
12157
|
console.log("\u{1F514} Starting Cloister agent watchdog...");
|
|
11960
12158
|
try {
|
|
11961
|
-
|
|
11962
|
-
console.log(" \u2713
|
|
12159
|
+
getDatabase();
|
|
12160
|
+
console.log(" \u2713 Panopticon database initialized");
|
|
11963
12161
|
} catch (error) {
|
|
11964
|
-
console.error(" \u2717 Failed to initialize
|
|
12162
|
+
console.error(" \u2717 Failed to initialize panopticon database:", error);
|
|
11965
12163
|
}
|
|
11966
12164
|
try {
|
|
11967
12165
|
console.log(" \u2192 Checking specialists...");
|
|
@@ -12014,9 +12212,9 @@ var CloisterService = class {
|
|
|
12014
12212
|
console.error("Failed to stop deacon:", error);
|
|
12015
12213
|
}
|
|
12016
12214
|
try {
|
|
12017
|
-
|
|
12215
|
+
closeDatabase();
|
|
12018
12216
|
} catch (error) {
|
|
12019
|
-
console.error("Failed to close
|
|
12217
|
+
console.error("Failed to close panopticon database:", error);
|
|
12020
12218
|
}
|
|
12021
12219
|
this.emit({ type: "stopped" });
|
|
12022
12220
|
}
|
|
@@ -12133,7 +12331,7 @@ var CloisterService = class {
|
|
|
12133
12331
|
const processedFile = join42(AGENTS_DIR, dir.name, "completed.processed");
|
|
12134
12332
|
if (!existsSync43(completedFile) || existsSync43(processedFile)) continue;
|
|
12135
12333
|
try {
|
|
12136
|
-
const content = JSON.parse(
|
|
12334
|
+
const content = JSON.parse(readFileSync37(completedFile, "utf-8"));
|
|
12137
12335
|
const ageMs = Date.now() - new Date(content.timestamp).getTime();
|
|
12138
12336
|
if (ageMs > 24 * 60 * 60 * 1e3) {
|
|
12139
12337
|
console.log(`\u{1F514} Cloister: Skipping stale completion marker for ${dir.name} (${Math.floor(ageMs / 36e5)}h old)`);
|
|
@@ -12790,10 +12988,10 @@ init_esm_shims();
|
|
|
12790
12988
|
// src/cli/commands/setup/hooks.ts
|
|
12791
12989
|
init_esm_shims();
|
|
12792
12990
|
import chalk39 from "chalk";
|
|
12793
|
-
import { readFileSync as
|
|
12991
|
+
import { readFileSync as readFileSync38, writeFileSync as writeFileSync21, existsSync as existsSync44, mkdirSync as mkdirSync21, copyFileSync as copyFileSync2, chmodSync } from "fs";
|
|
12794
12992
|
import { join as join43 } from "path";
|
|
12795
12993
|
import { execSync as execSync5 } from "child_process";
|
|
12796
|
-
import { homedir as
|
|
12994
|
+
import { homedir as homedir21 } from "os";
|
|
12797
12995
|
function checkJqInstalled() {
|
|
12798
12996
|
try {
|
|
12799
12997
|
execSync5("which jq", { stdio: "pipe" });
|
|
@@ -12869,15 +13067,15 @@ async function setupHooksCommand() {
|
|
|
12869
13067
|
} else {
|
|
12870
13068
|
console.log(chalk39.green("\u2713 jq is installed"));
|
|
12871
13069
|
}
|
|
12872
|
-
const panopticonHome = join43(
|
|
13070
|
+
const panopticonHome = join43(homedir21(), ".panopticon");
|
|
12873
13071
|
const binDir = join43(panopticonHome, "bin");
|
|
12874
13072
|
const heartbeatsDir = join43(panopticonHome, "heartbeats");
|
|
12875
13073
|
if (!existsSync44(binDir)) {
|
|
12876
|
-
|
|
13074
|
+
mkdirSync21(binDir, { recursive: true });
|
|
12877
13075
|
console.log(chalk39.green("\u2713 Created ~/.panopticon/bin/"));
|
|
12878
13076
|
}
|
|
12879
13077
|
if (!existsSync44(heartbeatsDir)) {
|
|
12880
|
-
|
|
13078
|
+
mkdirSync21(heartbeatsDir, { recursive: true });
|
|
12881
13079
|
console.log(chalk39.green("\u2713 Created ~/.panopticon/heartbeats/"));
|
|
12882
13080
|
}
|
|
12883
13081
|
const hookScripts = ["pre-tool-hook", "heartbeat-hook", "stop-hook", "specialist-stop-hook", "record-cost-event.js"];
|
|
@@ -12904,12 +13102,12 @@ async function setupHooksCommand() {
|
|
|
12904
13102
|
chmodSync(scriptDest, 493);
|
|
12905
13103
|
}
|
|
12906
13104
|
console.log(chalk39.green("\u2713 Installed hook scripts (pre-tool, post-tool, stop, specialist-stop)"));
|
|
12907
|
-
const claudeDir = join43(
|
|
13105
|
+
const claudeDir = join43(homedir21(), ".claude");
|
|
12908
13106
|
const settingsPath = join43(claudeDir, "settings.json");
|
|
12909
13107
|
let settings = {};
|
|
12910
13108
|
if (existsSync44(settingsPath)) {
|
|
12911
13109
|
try {
|
|
12912
|
-
const settingsContent =
|
|
13110
|
+
const settingsContent = readFileSync38(settingsPath, "utf-8");
|
|
12913
13111
|
settings = JSON.parse(settingsContent);
|
|
12914
13112
|
console.log(chalk39.green("\u2713 Read existing Claude Code settings"));
|
|
12915
13113
|
} catch (error) {
|
|
@@ -12919,7 +13117,7 @@ async function setupHooksCommand() {
|
|
|
12919
13117
|
} else {
|
|
12920
13118
|
console.log(chalk39.dim("No existing settings.json found, creating new file"));
|
|
12921
13119
|
if (!existsSync44(claudeDir)) {
|
|
12922
|
-
|
|
13120
|
+
mkdirSync21(claudeDir, { recursive: true });
|
|
12923
13121
|
}
|
|
12924
13122
|
}
|
|
12925
13123
|
let python3Available = false;
|
|
@@ -12936,7 +13134,7 @@ async function setupHooksCommand() {
|
|
|
12936
13134
|
let mcpConfig = {};
|
|
12937
13135
|
try {
|
|
12938
13136
|
if (existsSync44(mcpPath)) {
|
|
12939
|
-
mcpConfig = JSON.parse(
|
|
13137
|
+
mcpConfig = JSON.parse(readFileSync38(mcpPath, "utf-8"));
|
|
12940
13138
|
}
|
|
12941
13139
|
} catch {
|
|
12942
13140
|
mcpConfig = {};
|
|
@@ -13127,18 +13325,18 @@ init_specialists();
|
|
|
13127
13325
|
init_paths();
|
|
13128
13326
|
init_tmux();
|
|
13129
13327
|
import chalk41 from "chalk";
|
|
13130
|
-
import { exec as
|
|
13131
|
-
import { promisify as
|
|
13328
|
+
import { exec as exec17 } from "child_process";
|
|
13329
|
+
import { promisify as promisify17 } from "util";
|
|
13132
13330
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
13133
|
-
import { existsSync as existsSync45, mkdirSync as
|
|
13331
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync22, writeFileSync as writeFileSync22 } from "fs";
|
|
13134
13332
|
import { join as join44 } from "path";
|
|
13135
|
-
var
|
|
13333
|
+
var execAsync17 = promisify17(exec17);
|
|
13136
13334
|
var TASKS_DIR = join44(PANOPTICON_HOME, "specialists", "tasks");
|
|
13137
13335
|
function sendTask(tmuxSession, specialistName, task) {
|
|
13138
13336
|
const isLargeTask = task.length > 500 || task.includes("\n");
|
|
13139
13337
|
if (isLargeTask) {
|
|
13140
13338
|
if (!existsSync45(TASKS_DIR)) {
|
|
13141
|
-
|
|
13339
|
+
mkdirSync22(TASKS_DIR, { recursive: true });
|
|
13142
13340
|
}
|
|
13143
13341
|
const taskFile = join44(TASKS_DIR, `${specialistName}-${Date.now()}.md`);
|
|
13144
13342
|
writeFileSync22(taskFile, task, "utf-8");
|
|
@@ -13194,7 +13392,7 @@ Waking ${status.displayName}...
|
|
|
13194
13392
|
console.log(chalk41.dim("Starting fresh session (no previous session found)"));
|
|
13195
13393
|
}
|
|
13196
13394
|
console.log(chalk41.dim(`Creating tmux session: ${tmuxSession}`));
|
|
13197
|
-
await
|
|
13395
|
+
await execAsync17(
|
|
13198
13396
|
`tmux new-session -d -s "${tmuxSession}" -c "${cwd}" "${claudeCmd}"`,
|
|
13199
13397
|
{ encoding: "utf-8" }
|
|
13200
13398
|
);
|
|
@@ -13318,10 +13516,10 @@ function getAge(date) {
|
|
|
13318
13516
|
init_esm_shims();
|
|
13319
13517
|
init_specialists();
|
|
13320
13518
|
import chalk43 from "chalk";
|
|
13321
|
-
import { exec as
|
|
13322
|
-
import { promisify as
|
|
13519
|
+
import { exec as exec18 } from "child_process";
|
|
13520
|
+
import { promisify as promisify18 } from "util";
|
|
13323
13521
|
import * as readline from "readline";
|
|
13324
|
-
var
|
|
13522
|
+
var execAsync18 = promisify18(exec18);
|
|
13325
13523
|
var ALL_SPECIALISTS = ["merge-agent", "review-agent", "test-agent"];
|
|
13326
13524
|
async function resetCommand(name, options) {
|
|
13327
13525
|
if (options.all) {
|
|
@@ -13371,7 +13569,7 @@ Resetting ${status.displayName}...
|
|
|
13371
13569
|
if (status.isRunning) {
|
|
13372
13570
|
console.log(chalk43.dim("Stopping tmux session..."));
|
|
13373
13571
|
try {
|
|
13374
|
-
await
|
|
13572
|
+
await execAsync18(`tmux kill-session -t "${status.tmuxSession}"`, { encoding: "utf-8" });
|
|
13375
13573
|
console.log(chalk43.green("\u2713 Tmux session stopped"));
|
|
13376
13574
|
} catch (error) {
|
|
13377
13575
|
console.log(chalk43.yellow("\u26A0 Failed to stop tmux session (may not be running)"));
|
|
@@ -13425,7 +13623,7 @@ async function resetAllSpecialists(options) {
|
|
|
13425
13623
|
const tmuxSession = getTmuxSessionName(specialistName);
|
|
13426
13624
|
if (status.isRunning) {
|
|
13427
13625
|
try {
|
|
13428
|
-
await
|
|
13626
|
+
await execAsync18(`tmux kill-session -t "${tmuxSession}"`, { encoding: "utf-8" });
|
|
13429
13627
|
console.log(chalk43.dim(` Stopped ${specialistName} tmux session`));
|
|
13430
13628
|
} catch {
|
|
13431
13629
|
}
|
|
@@ -13448,7 +13646,7 @@ init_esm_shims();
|
|
|
13448
13646
|
init_specialists();
|
|
13449
13647
|
init_paths();
|
|
13450
13648
|
import chalk44 from "chalk";
|
|
13451
|
-
import { existsSync as existsSync46, readFileSync as
|
|
13649
|
+
import { existsSync as existsSync46, readFileSync as readFileSync39, writeFileSync as writeFileSync23 } from "fs";
|
|
13452
13650
|
import { join as join45 } from "path";
|
|
13453
13651
|
import * as readline2 from "readline";
|
|
13454
13652
|
var ALL_SPECIALISTS2 = ["merge-agent", "review-agent", "test-agent"];
|
|
@@ -13505,7 +13703,7 @@ ${metadata.displayName} Queue:
|
|
|
13505
13703
|
const hookFile = join45(PANOPTICON_HOME, "agents", specialistName, "hook.json");
|
|
13506
13704
|
if (existsSync46(hookFile)) {
|
|
13507
13705
|
try {
|
|
13508
|
-
const hook = JSON.parse(
|
|
13706
|
+
const hook = JSON.parse(readFileSync39(hookFile, "utf-8"));
|
|
13509
13707
|
hook.items = [];
|
|
13510
13708
|
hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
|
|
13511
13709
|
writeFileSync23(hookFile, JSON.stringify(hook, null, 2), "utf-8");
|
|
@@ -13518,7 +13716,7 @@ ${metadata.displayName} Queue:
|
|
|
13518
13716
|
if (options.resetStatus && issueIds.length > 0) {
|
|
13519
13717
|
if (existsSync46(REVIEW_STATUS_FILE2)) {
|
|
13520
13718
|
try {
|
|
13521
|
-
const statuses = JSON.parse(
|
|
13719
|
+
const statuses = JSON.parse(readFileSync39(REVIEW_STATUS_FILE2, "utf-8"));
|
|
13522
13720
|
let resetCount = 0;
|
|
13523
13721
|
for (const issueId of issueIds) {
|
|
13524
13722
|
const key = Object.keys(statuses).find((k) => k.toLowerCase() === issueId.toLowerCase());
|
|
@@ -13560,14 +13758,14 @@ function confirm2(question) {
|
|
|
13560
13758
|
// src/cli/commands/specialists/done.ts
|
|
13561
13759
|
init_esm_shims();
|
|
13562
13760
|
import chalk45 from "chalk";
|
|
13563
|
-
import { existsSync as existsSync47, readFileSync as
|
|
13761
|
+
import { existsSync as existsSync47, readFileSync as readFileSync40, writeFileSync as writeFileSync24 } from "fs";
|
|
13564
13762
|
import { join as join46 } from "path";
|
|
13565
|
-
import { homedir as
|
|
13566
|
-
var REVIEW_STATUS_FILE3 = join46(
|
|
13763
|
+
import { homedir as homedir22 } from "os";
|
|
13764
|
+
var REVIEW_STATUS_FILE3 = join46(homedir22(), ".panopticon", "review-status.json");
|
|
13567
13765
|
function loadReviewStatuses2() {
|
|
13568
13766
|
try {
|
|
13569
13767
|
if (existsSync47(REVIEW_STATUS_FILE3)) {
|
|
13570
|
-
return JSON.parse(
|
|
13768
|
+
return JSON.parse(readFileSync40(REVIEW_STATUS_FILE3, "utf-8"));
|
|
13571
13769
|
}
|
|
13572
13770
|
} catch (error) {
|
|
13573
13771
|
console.error(chalk45.yellow("Warning: Could not load review statuses"));
|
|
@@ -13677,12 +13875,12 @@ function formatStatus(status) {
|
|
|
13677
13875
|
// src/cli/commands/specialists/logs.ts
|
|
13678
13876
|
init_esm_shims();
|
|
13679
13877
|
import { existsSync as existsSync48 } from "fs";
|
|
13680
|
-
import { exec as
|
|
13681
|
-
import { promisify as
|
|
13682
|
-
var
|
|
13878
|
+
import { exec as exec19 } from "child_process";
|
|
13879
|
+
import { promisify as promisify19 } from "util";
|
|
13880
|
+
var execAsync19 = promisify19(exec19);
|
|
13683
13881
|
async function listLogsCommand(project2, type, options) {
|
|
13684
13882
|
try {
|
|
13685
|
-
const { listRunLogs } = await import("../specialist-logs-
|
|
13883
|
+
const { listRunLogs } = await import("../specialist-logs-CJKXM3SR.js");
|
|
13686
13884
|
const limit = options.limit ? parseInt(options.limit) : 10;
|
|
13687
13885
|
const runs = listRunLogs(project2, type, { limit });
|
|
13688
13886
|
if (options.json) {
|
|
@@ -13727,7 +13925,7 @@ View a specific run: pan specialists logs ${project2} ${type} <runId>
|
|
|
13727
13925
|
}
|
|
13728
13926
|
async function viewLogCommand(project2, type, runId, options) {
|
|
13729
13927
|
try {
|
|
13730
|
-
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-
|
|
13928
|
+
const { getRunLog, parseLogMetadata, getRunLogPath } = await import("../specialist-logs-CJKXM3SR.js");
|
|
13731
13929
|
const content = getRunLog(project2, type, runId);
|
|
13732
13930
|
if (!content) {
|
|
13733
13931
|
console.error(`\u274C Run log not found: ${runId}`);
|
|
@@ -13740,7 +13938,7 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
13740
13938
|
}
|
|
13741
13939
|
const logPath = getRunLogPath(project2, type, runId);
|
|
13742
13940
|
try {
|
|
13743
|
-
await
|
|
13941
|
+
await execAsync19(`less -R "${logPath}"`);
|
|
13744
13942
|
} catch {
|
|
13745
13943
|
console.log(content);
|
|
13746
13944
|
}
|
|
@@ -13751,8 +13949,8 @@ async function viewLogCommand(project2, type, runId, options) {
|
|
|
13751
13949
|
}
|
|
13752
13950
|
async function tailLogCommand(project2, type) {
|
|
13753
13951
|
try {
|
|
13754
|
-
const { getRunLogPath } = await import("../specialist-logs-
|
|
13755
|
-
const { getProjectSpecialistMetadata } = await import("../specialists-
|
|
13952
|
+
const { getRunLogPath } = await import("../specialist-logs-CJKXM3SR.js");
|
|
13953
|
+
const { getProjectSpecialistMetadata } = await import("../specialists-NXYD4Z62.js");
|
|
13756
13954
|
const metadata = getProjectSpecialistMetadata(project2, type);
|
|
13757
13955
|
if (!metadata.currentRun) {
|
|
13758
13956
|
console.error(`\u274C No active run for ${project2}/${type}`);
|
|
@@ -13821,7 +14019,7 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
13821
14019
|
console.log(" Use --force to confirm.");
|
|
13822
14020
|
process.exit(1);
|
|
13823
14021
|
}
|
|
13824
|
-
const { cleanupAllLogs } = await import("../specialist-logs-
|
|
14022
|
+
const { cleanupAllLogs } = await import("../specialist-logs-CJKXM3SR.js");
|
|
13825
14023
|
console.log("\u{1F9F9} Cleaning up old logs for all projects...\n");
|
|
13826
14024
|
const results = cleanupAllLogs();
|
|
13827
14025
|
console.log(`
|
|
@@ -13848,8 +14046,8 @@ async function cleanupLogsCommand(projectOrAll, type, options) {
|
|
|
13848
14046
|
console.log(" Use --force to confirm.");
|
|
13849
14047
|
process.exit(1);
|
|
13850
14048
|
}
|
|
13851
|
-
const { cleanupOldLogs } = await import("../specialist-logs-
|
|
13852
|
-
const { getSpecialistRetention } = await import("../projects-
|
|
14049
|
+
const { cleanupOldLogs } = await import("../specialist-logs-CJKXM3SR.js");
|
|
14050
|
+
const { getSpecialistRetention } = await import("../projects-KVM3MN3Y.js");
|
|
13853
14051
|
const retention = getSpecialistRetention(projectOrAll);
|
|
13854
14052
|
console.log(`\u{1F9F9} Cleaning up old logs for ${projectOrAll}/${type}...`);
|
|
13855
14053
|
console.log(` Retention: ${retention.max_days} days or ${retention.max_runs} runs
|
|
@@ -13887,11 +14085,11 @@ import ora19 from "ora";
|
|
|
13887
14085
|
// src/lib/convoy.ts
|
|
13888
14086
|
init_esm_shims();
|
|
13889
14087
|
init_tmux();
|
|
13890
|
-
import { existsSync as existsSync49, mkdirSync as
|
|
14088
|
+
import { existsSync as existsSync49, mkdirSync as mkdirSync23, writeFileSync as writeFileSync25, readFileSync as readFileSync42, readdirSync as readdirSync19 } from "fs";
|
|
13891
14089
|
import { join as join47 } from "path";
|
|
13892
|
-
import { homedir as
|
|
13893
|
-
import { exec as
|
|
13894
|
-
import { promisify as
|
|
14090
|
+
import { homedir as homedir23 } from "os";
|
|
14091
|
+
import { exec as exec20 } from "child_process";
|
|
14092
|
+
import { promisify as promisify20 } from "util";
|
|
13895
14093
|
import { parse as parseYaml } from "yaml";
|
|
13896
14094
|
|
|
13897
14095
|
// src/lib/convoy-templates.ts
|
|
@@ -13996,8 +14194,8 @@ function getExecutionOrder(template) {
|
|
|
13996
14194
|
// src/lib/convoy.ts
|
|
13997
14195
|
init_paths();
|
|
13998
14196
|
init_work_type_router();
|
|
13999
|
-
var
|
|
14000
|
-
var CONVOY_DIR = join47(
|
|
14197
|
+
var execAsync20 = promisify20(exec20);
|
|
14198
|
+
var CONVOY_DIR = join47(homedir23(), ".panopticon", "convoys");
|
|
14001
14199
|
function getConvoyStateFile(convoyId) {
|
|
14002
14200
|
return join47(CONVOY_DIR, `${convoyId}.json`);
|
|
14003
14201
|
}
|
|
@@ -14006,7 +14204,7 @@ function getConvoyOutputDir(convoyId, template) {
|
|
|
14006
14204
|
return join47(process.cwd(), baseDir, convoyId);
|
|
14007
14205
|
}
|
|
14008
14206
|
function saveConvoyState(state) {
|
|
14009
|
-
|
|
14207
|
+
mkdirSync23(CONVOY_DIR, { recursive: true });
|
|
14010
14208
|
writeFileSync25(getConvoyStateFile(state.id), JSON.stringify(state, null, 2));
|
|
14011
14209
|
}
|
|
14012
14210
|
function loadConvoyState(convoyId) {
|
|
@@ -14015,7 +14213,7 @@ function loadConvoyState(convoyId) {
|
|
|
14015
14213
|
return void 0;
|
|
14016
14214
|
}
|
|
14017
14215
|
try {
|
|
14018
|
-
const content =
|
|
14216
|
+
const content = readFileSync42(stateFile, "utf-8");
|
|
14019
14217
|
return JSON.parse(content);
|
|
14020
14218
|
} catch {
|
|
14021
14219
|
return void 0;
|
|
@@ -14047,7 +14245,7 @@ function parseAgentTemplate(templatePath) {
|
|
|
14047
14245
|
if (!existsSync49(templatePath)) {
|
|
14048
14246
|
throw new Error(`Agent template not found: ${templatePath}`);
|
|
14049
14247
|
}
|
|
14050
|
-
const content =
|
|
14248
|
+
const content = readFileSync42(templatePath, "utf-8");
|
|
14051
14249
|
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---\n([\s\S]*)$/);
|
|
14052
14250
|
if (!frontmatterMatch) {
|
|
14053
14251
|
throw new Error(`Invalid agent template format (missing frontmatter): ${templatePath}`);
|
|
@@ -14111,7 +14309,7 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
|
|
|
14111
14309
|
|
|
14112
14310
|
`;
|
|
14113
14311
|
prompt = contextInstructions + prompt;
|
|
14114
|
-
|
|
14312
|
+
mkdirSync23(convoy.outputDir, { recursive: true });
|
|
14115
14313
|
const promptFile = join47(convoy.outputDir, `${role}-prompt.md`);
|
|
14116
14314
|
writeFileSync25(promptFile, prompt);
|
|
14117
14315
|
const claudeCmd = `claude --dangerously-skip-permissions --model ${model}`;
|
|
@@ -14123,10 +14321,10 @@ ${context.issueId ? `**Issue ID**: ${context.issueId}` : ""}
|
|
|
14123
14321
|
}
|
|
14124
14322
|
});
|
|
14125
14323
|
await new Promise((resolve2) => setTimeout(resolve2, 1500));
|
|
14126
|
-
await
|
|
14127
|
-
await
|
|
14324
|
+
await execAsync20(`tmux load-buffer "${promptFile}"`);
|
|
14325
|
+
await execAsync20(`tmux paste-buffer -t ${agentState.tmuxSession}`);
|
|
14128
14326
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
14129
|
-
await
|
|
14327
|
+
await execAsync20(`tmux send-keys -t ${agentState.tmuxSession} Enter`);
|
|
14130
14328
|
agentState.status = "running";
|
|
14131
14329
|
agentState.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14132
14330
|
saveConvoyState(convoy);
|
|
@@ -14139,7 +14337,7 @@ async function startConvoy(templateName, context) {
|
|
|
14139
14337
|
const timestamp = Date.now();
|
|
14140
14338
|
const convoyId = `convoy-${templateName}-${timestamp}`;
|
|
14141
14339
|
const outputDir = getConvoyOutputDir(convoyId, template);
|
|
14142
|
-
|
|
14340
|
+
mkdirSync23(outputDir, { recursive: true });
|
|
14143
14341
|
const state = {
|
|
14144
14342
|
id: convoyId,
|
|
14145
14343
|
template: templateName,
|
|
@@ -14187,7 +14385,7 @@ async function executePhase(convoy, template, phaseAgents, context) {
|
|
|
14187
14385
|
for (const depRole of deps) {
|
|
14188
14386
|
const depAgent = convoy.agents.find((a) => a.role === depRole);
|
|
14189
14387
|
if (depAgent?.outputFile && existsSync49(depAgent.outputFile)) {
|
|
14190
|
-
agentContext[`${depRole}_output`] =
|
|
14388
|
+
agentContext[`${depRole}_output`] = readFileSync42(depAgent.outputFile, "utf-8");
|
|
14191
14389
|
}
|
|
14192
14390
|
}
|
|
14193
14391
|
spawnPromises.push(spawnConvoyAgent(convoy, agent, agentState, agentContext));
|
|
@@ -14496,7 +14694,7 @@ function registerConvoyCommands(program2) {
|
|
|
14496
14694
|
init_esm_shims();
|
|
14497
14695
|
init_projects();
|
|
14498
14696
|
import chalk50 from "chalk";
|
|
14499
|
-
import { existsSync as existsSync50, readFileSync as
|
|
14697
|
+
import { existsSync as existsSync50, readFileSync as readFileSync43, symlinkSync as symlinkSync2, mkdirSync as mkdirSync24, readdirSync as readdirSync20, statSync as statSync10 } from "fs";
|
|
14500
14698
|
import { join as join48, resolve, dirname as dirname14 } from "path";
|
|
14501
14699
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14502
14700
|
var __filename5 = fileURLToPath4(import.meta.url);
|
|
@@ -14506,7 +14704,7 @@ function installGitHooks(gitDir) {
|
|
|
14506
14704
|
const hooksTarget = join48(gitDir, "hooks");
|
|
14507
14705
|
let installed = 0;
|
|
14508
14706
|
if (!existsSync50(hooksTarget)) {
|
|
14509
|
-
|
|
14707
|
+
mkdirSync24(hooksTarget, { recursive: true });
|
|
14510
14708
|
}
|
|
14511
14709
|
if (!existsSync50(BUNDLED_HOOKS_DIR)) {
|
|
14512
14710
|
return 0;
|
|
@@ -14558,7 +14756,7 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14558
14756
|
if (!linearTeam) {
|
|
14559
14757
|
const projectToml = join48(fullPath, ".panopticon", "project.toml");
|
|
14560
14758
|
if (existsSync50(projectToml)) {
|
|
14561
|
-
const content =
|
|
14759
|
+
const content = readFileSync43(projectToml, "utf-8");
|
|
14562
14760
|
const match = content.match(/team\s*=\s*"([^"]+)"/);
|
|
14563
14761
|
if (match) linearTeam = match[1];
|
|
14564
14762
|
}
|
|
@@ -14607,7 +14805,7 @@ async function projectAddCommand(projectPath, options = {}) {
|
|
|
14607
14805
|
}
|
|
14608
14806
|
const isPolyrepo = !hasRootGit && subRepos.length > 0;
|
|
14609
14807
|
try {
|
|
14610
|
-
const { preTrustDirectory } = await import("../workspace-manager-
|
|
14808
|
+
const { preTrustDirectory } = await import("../workspace-manager-ALBR62AS.js");
|
|
14611
14809
|
preTrustDirectory(fullPath);
|
|
14612
14810
|
} catch {
|
|
14613
14811
|
}
|
|
@@ -14791,9 +14989,9 @@ Project: ${foundKey}
|
|
|
14791
14989
|
init_esm_shims();
|
|
14792
14990
|
init_paths();
|
|
14793
14991
|
import chalk51 from "chalk";
|
|
14794
|
-
import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as
|
|
14992
|
+
import { existsSync as existsSync51, readdirSync as readdirSync21, readFileSync as readFileSync44 } from "fs";
|
|
14795
14993
|
import { execSync as execSync6 } from "child_process";
|
|
14796
|
-
import { homedir as
|
|
14994
|
+
import { homedir as homedir24 } from "os";
|
|
14797
14995
|
import { join as join49 } from "path";
|
|
14798
14996
|
function checkCommand3(cmd) {
|
|
14799
14997
|
try {
|
|
@@ -14880,7 +15078,7 @@ async function doctorCommand() {
|
|
|
14880
15078
|
fix: "Install Claude Code first"
|
|
14881
15079
|
});
|
|
14882
15080
|
}
|
|
14883
|
-
const envFile = join49(
|
|
15081
|
+
const envFile = join49(homedir24(), ".panopticon.env");
|
|
14884
15082
|
if (existsSync51(envFile)) {
|
|
14885
15083
|
checks.push({ name: "Config File", status: "ok", message: "~/.panopticon.env exists" });
|
|
14886
15084
|
} else {
|
|
@@ -14894,7 +15092,7 @@ async function doctorCommand() {
|
|
|
14894
15092
|
if (process.env.LINEAR_API_KEY) {
|
|
14895
15093
|
checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in environment" });
|
|
14896
15094
|
} else if (existsSync51(envFile)) {
|
|
14897
|
-
const content =
|
|
15095
|
+
const content = readFileSync44(envFile, "utf-8");
|
|
14898
15096
|
if (content.includes("LINEAR_API_KEY")) {
|
|
14899
15097
|
checks.push({ name: "LINEAR_API_KEY", status: "ok", message: "Set in config file" });
|
|
14900
15098
|
} else {
|
|
@@ -14962,7 +15160,7 @@ init_esm_shims();
|
|
|
14962
15160
|
init_config();
|
|
14963
15161
|
import { execSync as execSync7 } from "child_process";
|
|
14964
15162
|
import chalk52 from "chalk";
|
|
14965
|
-
import { readFileSync as
|
|
15163
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
14966
15164
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
14967
15165
|
import { dirname as dirname15, join as join50 } from "path";
|
|
14968
15166
|
function getCurrentVersion() {
|
|
@@ -14970,7 +15168,7 @@ function getCurrentVersion() {
|
|
|
14970
15168
|
const __filename6 = fileURLToPath5(import.meta.url);
|
|
14971
15169
|
const __dirname6 = dirname15(__filename6);
|
|
14972
15170
|
const pkgPath = join50(__dirname6, "..", "..", "..", "package.json");
|
|
14973
|
-
const pkg = JSON.parse(
|
|
15171
|
+
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
14974
15172
|
return pkg.version;
|
|
14975
15173
|
} catch {
|
|
14976
15174
|
return "unknown";
|
|
@@ -15056,11 +15254,11 @@ init_esm_shims();
|
|
|
15056
15254
|
init_projects();
|
|
15057
15255
|
import chalk53 from "chalk";
|
|
15058
15256
|
import ora21 from "ora";
|
|
15059
|
-
import { existsSync as existsSync52, readFileSync as
|
|
15257
|
+
import { existsSync as existsSync52, readFileSync as readFileSync46, writeFileSync as writeFileSync26, mkdirSync as mkdirSync25, statSync as statSync11 } from "fs";
|
|
15060
15258
|
import { join as join51, dirname as dirname16 } from "path";
|
|
15061
|
-
import { exec as
|
|
15062
|
-
import { promisify as
|
|
15063
|
-
var
|
|
15259
|
+
import { exec as exec21 } from "child_process";
|
|
15260
|
+
import { promisify as promisify21 } from "util";
|
|
15261
|
+
var execAsync21 = promisify21(exec21);
|
|
15064
15262
|
function loadFullProjects() {
|
|
15065
15263
|
const config2 = loadProjectsConfig();
|
|
15066
15264
|
const projects = config2.projects;
|
|
@@ -15076,12 +15274,12 @@ function findFullProjectByTeam(teamPrefix) {
|
|
|
15076
15274
|
) || null;
|
|
15077
15275
|
}
|
|
15078
15276
|
function registerDbCommands(program2) {
|
|
15079
|
-
const
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15277
|
+
const db = program2.command("db").description("Database seeding and management");
|
|
15278
|
+
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);
|
|
15279
|
+
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);
|
|
15280
|
+
db.command("status").description("Check database status for a workspace").argument("[workspaceOrIssue]", "Workspace folder or issue ID").action(statusCommand5);
|
|
15281
|
+
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);
|
|
15282
|
+
db.command("config").description("Show database configuration for a project").argument("[project]", "Project key").action(configCommand);
|
|
15085
15283
|
}
|
|
15086
15284
|
async function snapshotCommand(options) {
|
|
15087
15285
|
const spinner = ora21("Creating database snapshot...").start();
|
|
@@ -15122,7 +15320,7 @@ async function snapshotCommand(options) {
|
|
|
15122
15320
|
const outputPath = options.output || dbConfig.seed_file || join51(projectConfig.path, "infra", "seed", "seed.sql");
|
|
15123
15321
|
const outputDir = dirname16(outputPath);
|
|
15124
15322
|
if (!existsSync52(outputDir)) {
|
|
15125
|
-
|
|
15323
|
+
mkdirSync25(outputDir, { recursive: true });
|
|
15126
15324
|
}
|
|
15127
15325
|
spinner.text = "Running snapshot command...";
|
|
15128
15326
|
let snapshotCmd;
|
|
@@ -15142,10 +15340,10 @@ async function snapshotCommand(options) {
|
|
|
15142
15340
|
}
|
|
15143
15341
|
const fullCmd = `${snapshotCmd} > "${outputPath}" 2>&1`;
|
|
15144
15342
|
try {
|
|
15145
|
-
await
|
|
15343
|
+
await execAsync21(fullCmd, { timeout: 3e5 });
|
|
15146
15344
|
} catch (error) {
|
|
15147
15345
|
if (existsSync52(outputPath)) {
|
|
15148
|
-
const content2 =
|
|
15346
|
+
const content2 = readFileSync46(outputPath, "utf-8");
|
|
15149
15347
|
if (content2.includes("PostgreSQL database dump")) {
|
|
15150
15348
|
spinner.warn("Snapshot completed with warnings (stderr captured)");
|
|
15151
15349
|
console.log(chalk53.dim(" Run `pan db clean` to remove stderr noise from the file"));
|
|
@@ -15158,7 +15356,7 @@ async function snapshotCommand(options) {
|
|
|
15158
15356
|
return;
|
|
15159
15357
|
}
|
|
15160
15358
|
}
|
|
15161
|
-
const content =
|
|
15359
|
+
const content = readFileSync46(outputPath, "utf-8");
|
|
15162
15360
|
if (content.includes("Defaulted container") || content.includes("Unable to use a TTY")) {
|
|
15163
15361
|
spinner.text = "Cleaning kubectl output from snapshot...";
|
|
15164
15362
|
await cleanFile(outputPath);
|
|
@@ -15166,7 +15364,7 @@ async function snapshotCommand(options) {
|
|
|
15166
15364
|
if (options.sanitize && dbConfig.seed_command) {
|
|
15167
15365
|
spinner.text = "Running sanitization...";
|
|
15168
15366
|
try {
|
|
15169
|
-
await
|
|
15367
|
+
await execAsync21(dbConfig.seed_command, { cwd: projectConfig.path });
|
|
15170
15368
|
} catch (error) {
|
|
15171
15369
|
spinner.warn(`Sanitization warning: ${error.message}`);
|
|
15172
15370
|
}
|
|
@@ -15206,7 +15404,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15206
15404
|
const containerName = dbConfig?.container_name?.replace("{{PROJECT}}", projectName) || `${projectName}-postgres-1`;
|
|
15207
15405
|
spinner.text = `Finding database container ${containerName}...`;
|
|
15208
15406
|
try {
|
|
15209
|
-
const { stdout } = await
|
|
15407
|
+
const { stdout } = await execAsync21(`docker ps --filter "name=${containerName}" --format "{{.Names}}"`);
|
|
15210
15408
|
if (!stdout.trim()) {
|
|
15211
15409
|
spinner.fail(`Database container not running: ${containerName}`);
|
|
15212
15410
|
console.log(chalk53.dim("\nStart the workspace containers first:"));
|
|
@@ -15219,7 +15417,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15219
15417
|
}
|
|
15220
15418
|
if (!options.force) {
|
|
15221
15419
|
try {
|
|
15222
|
-
const { stdout } = await
|
|
15420
|
+
const { stdout } = await execAsync21(
|
|
15223
15421
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM flyway_schema_history" -t 2>/dev/null`
|
|
15224
15422
|
);
|
|
15225
15423
|
const count = parseInt(stdout.trim(), 10);
|
|
@@ -15233,7 +15431,7 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15233
15431
|
if (options.force) {
|
|
15234
15432
|
spinner.text = "Dropping existing database...";
|
|
15235
15433
|
try {
|
|
15236
|
-
await
|
|
15434
|
+
await execAsync21(
|
|
15237
15435
|
`docker exec ${containerName} psql -U postgres -c "DROP DATABASE IF EXISTS myn; CREATE DATABASE myn;"`
|
|
15238
15436
|
);
|
|
15239
15437
|
} catch (error) {
|
|
@@ -15241,10 +15439,10 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15241
15439
|
}
|
|
15242
15440
|
}
|
|
15243
15441
|
spinner.text = "Copying seed file to container...";
|
|
15244
|
-
await
|
|
15442
|
+
await execAsync21(`docker cp "${seedFile}" ${containerName}:/tmp/seed.sql`);
|
|
15245
15443
|
spinner.text = "Executing seed...";
|
|
15246
15444
|
try {
|
|
15247
|
-
await
|
|
15445
|
+
await execAsync21(`docker exec ${containerName} psql -U postgres -d myn -f /tmp/seed.sql`, {
|
|
15248
15446
|
timeout: 6e5
|
|
15249
15447
|
// 10 minute timeout for large seeds
|
|
15250
15448
|
});
|
|
@@ -15254,10 +15452,10 @@ async function seedCommand(workspaceOrIssue, options) {
|
|
|
15254
15452
|
return;
|
|
15255
15453
|
}
|
|
15256
15454
|
}
|
|
15257
|
-
await
|
|
15455
|
+
await execAsync21(`docker exec ${containerName} rm /tmp/seed.sql`);
|
|
15258
15456
|
spinner.succeed("Database seeded successfully");
|
|
15259
15457
|
try {
|
|
15260
|
-
const { stdout } = await
|
|
15458
|
+
const { stdout } = await execAsync21(
|
|
15261
15459
|
`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
15460
|
);
|
|
15263
15461
|
console.log(chalk53.dim("\nRecent migrations:"));
|
|
@@ -15287,7 +15485,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15287
15485
|
const projects = loadFullProjects();
|
|
15288
15486
|
projectConfig = projects.find((p) => cwd.startsWith(p.path));
|
|
15289
15487
|
if (projectConfig) {
|
|
15290
|
-
const { stdout } = await
|
|
15488
|
+
const { stdout } = await execAsync21(
|
|
15291
15489
|
`docker ps --filter "name=${projectConfig.name?.toLowerCase().replace(/\s+/g, "-")}" --filter "name=postgres" --format "{{.Names}}" | head -1`
|
|
15292
15490
|
);
|
|
15293
15491
|
containerName = stdout.trim();
|
|
@@ -15300,7 +15498,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15300
15498
|
return;
|
|
15301
15499
|
}
|
|
15302
15500
|
spinner.text = `Checking container ${containerName}...`;
|
|
15303
|
-
const { stdout: containerStatus } = await
|
|
15501
|
+
const { stdout: containerStatus } = await execAsync21(
|
|
15304
15502
|
`docker ps --filter "name=${containerName}" --format "{{.Status}}"`
|
|
15305
15503
|
);
|
|
15306
15504
|
if (!containerStatus.trim()) {
|
|
@@ -15310,7 +15508,7 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15310
15508
|
spinner.succeed(`Container: ${containerName}`);
|
|
15311
15509
|
console.log(chalk53.dim(` Status: ${containerStatus.trim()}`));
|
|
15312
15510
|
try {
|
|
15313
|
-
const { stdout: version } = await
|
|
15511
|
+
const { stdout: version } = await execAsync21(
|
|
15314
15512
|
`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
15513
|
);
|
|
15316
15514
|
const [ver, desc] = version.trim().split("|").map((s) => s.trim());
|
|
@@ -15319,14 +15517,14 @@ async function statusCommand5(workspaceOrIssue) {
|
|
|
15319
15517
|
console.log(chalk53.yellow(" Flyway: Not initialized"));
|
|
15320
15518
|
}
|
|
15321
15519
|
try {
|
|
15322
|
-
const { stdout: tableCount } = await
|
|
15520
|
+
const { stdout: tableCount } = await execAsync21(
|
|
15323
15521
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'" -t`
|
|
15324
15522
|
);
|
|
15325
15523
|
console.log(chalk53.dim(` Tables: ${tableCount.trim()}`));
|
|
15326
15524
|
} catch {
|
|
15327
15525
|
}
|
|
15328
15526
|
try {
|
|
15329
|
-
const { stdout: dbSize } = await
|
|
15527
|
+
const { stdout: dbSize } = await execAsync21(
|
|
15330
15528
|
`docker exec ${containerName} psql -U postgres -d myn -c "SELECT pg_size_pretty(pg_database_size('myn'))" -t`
|
|
15331
15529
|
);
|
|
15332
15530
|
console.log(chalk53.dim(` Size: ${dbSize.trim()}`));
|
|
@@ -15343,7 +15541,7 @@ async function cleanCommand(file, options) {
|
|
|
15343
15541
|
spinner.fail(`File not found: ${file}`);
|
|
15344
15542
|
return;
|
|
15345
15543
|
}
|
|
15346
|
-
const content =
|
|
15544
|
+
const content = readFileSync46(file, "utf-8");
|
|
15347
15545
|
const lines = content.split("\n");
|
|
15348
15546
|
const patternsToRemove = [
|
|
15349
15547
|
/^Defaulted container/,
|
|
@@ -15413,7 +15611,7 @@ async function cleanCommand(file, options) {
|
|
|
15413
15611
|
}
|
|
15414
15612
|
}
|
|
15415
15613
|
async function cleanFile(filePath) {
|
|
15416
|
-
const content =
|
|
15614
|
+
const content = readFileSync46(filePath, "utf-8");
|
|
15417
15615
|
const lines = content.split("\n");
|
|
15418
15616
|
let startIndex = 0;
|
|
15419
15617
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -15496,17 +15694,17 @@ async function configCommand(project2) {
|
|
|
15496
15694
|
init_esm_shims();
|
|
15497
15695
|
import chalk54 from "chalk";
|
|
15498
15696
|
import ora22 from "ora";
|
|
15499
|
-
import { existsSync as existsSync53, readFileSync as
|
|
15697
|
+
import { existsSync as existsSync53, readFileSync as readFileSync47 } from "fs";
|
|
15500
15698
|
import { join as join52 } from "path";
|
|
15501
|
-
import { exec as
|
|
15502
|
-
import { promisify as
|
|
15699
|
+
import { exec as exec22, execSync as execSync8 } from "child_process";
|
|
15700
|
+
import { promisify as promisify22 } from "util";
|
|
15503
15701
|
import { platform } from "os";
|
|
15504
|
-
var
|
|
15702
|
+
var execAsync22 = promisify22(exec22);
|
|
15505
15703
|
function detectPlatform2() {
|
|
15506
15704
|
const os = platform();
|
|
15507
15705
|
if (os === "linux") {
|
|
15508
15706
|
try {
|
|
15509
|
-
const release =
|
|
15707
|
+
const release = readFileSync47("/proc/version", "utf8").toLowerCase();
|
|
15510
15708
|
if (release.includes("microsoft") || release.includes("wsl")) {
|
|
15511
15709
|
return "wsl";
|
|
15512
15710
|
}
|
|
@@ -15518,7 +15716,7 @@ function detectPlatform2() {
|
|
|
15518
15716
|
}
|
|
15519
15717
|
async function isBdAvailable() {
|
|
15520
15718
|
try {
|
|
15521
|
-
await
|
|
15719
|
+
await execAsync22("which bd", { encoding: "utf-8" });
|
|
15522
15720
|
return true;
|
|
15523
15721
|
} catch {
|
|
15524
15722
|
return false;
|
|
@@ -15527,7 +15725,7 @@ async function isBdAvailable() {
|
|
|
15527
15725
|
async function getOldClosedCount(cwd, days) {
|
|
15528
15726
|
try {
|
|
15529
15727
|
const seconds = days * 24 * 60 * 60;
|
|
15530
|
-
const { stdout } = await
|
|
15728
|
+
const { stdout } = await execAsync22(
|
|
15531
15729
|
`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
15730
|
{ cwd, encoding: "utf-8" }
|
|
15533
15731
|
);
|
|
@@ -15563,7 +15761,7 @@ async function compactCommand(options) {
|
|
|
15563
15761
|
console.log("");
|
|
15564
15762
|
console.log(chalk54.bold("Beads that would be compacted:"));
|
|
15565
15763
|
try {
|
|
15566
|
-
const { stdout: beadsList } = await
|
|
15764
|
+
const { stdout: beadsList } = await execAsync22(
|
|
15567
15765
|
`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
15766
|
{ cwd, encoding: "utf-8" }
|
|
15569
15767
|
);
|
|
@@ -15574,10 +15772,10 @@ async function compactCommand(options) {
|
|
|
15574
15772
|
return;
|
|
15575
15773
|
}
|
|
15576
15774
|
spinner.text = "Running compaction...";
|
|
15577
|
-
await
|
|
15775
|
+
await execAsync22(`bd admin compact --days ${days}`, { cwd, encoding: "utf-8" });
|
|
15578
15776
|
spinner.succeed(`Compacted ${count} beads older than ${days} days`);
|
|
15579
15777
|
try {
|
|
15580
|
-
await
|
|
15778
|
+
await execAsync22(`git diff --quiet .beads/`, { cwd, encoding: "utf-8" });
|
|
15581
15779
|
console.log(chalk54.dim("No changes to commit (beads already up to date)"));
|
|
15582
15780
|
} catch {
|
|
15583
15781
|
console.log("");
|
|
@@ -15611,17 +15809,17 @@ async function statsCommand() {
|
|
|
15611
15809
|
}
|
|
15612
15810
|
const spinner = ora22("Gathering beads statistics...").start();
|
|
15613
15811
|
try {
|
|
15614
|
-
const { stdout: totalRaw } = await
|
|
15812
|
+
const { stdout: totalRaw } = await execAsync22(`bd list --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15615
15813
|
cwd,
|
|
15616
15814
|
encoding: "utf-8"
|
|
15617
15815
|
});
|
|
15618
15816
|
const total = parseInt(totalRaw.trim(), 10) || 0;
|
|
15619
|
-
const { stdout: openRaw } = await
|
|
15817
|
+
const { stdout: openRaw } = await execAsync22(`bd list --status open --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15620
15818
|
cwd,
|
|
15621
15819
|
encoding: "utf-8"
|
|
15622
15820
|
});
|
|
15623
15821
|
const open = parseInt(openRaw.trim(), 10) || 0;
|
|
15624
|
-
const { stdout: closedRaw } = await
|
|
15822
|
+
const { stdout: closedRaw } = await execAsync22(`bd list --status closed --limit 0 --json 2>/dev/null | jq 'length'`, {
|
|
15625
15823
|
cwd,
|
|
15626
15824
|
encoding: "utf-8"
|
|
15627
15825
|
});
|
|
@@ -15666,7 +15864,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15666
15864
|
console.log(chalk54.dim("Checking beads version..."));
|
|
15667
15865
|
let currentVersion = "not installed";
|
|
15668
15866
|
try {
|
|
15669
|
-
const { stdout } = await
|
|
15867
|
+
const { stdout } = await execAsync22("bd --version", { encoding: "utf-8" });
|
|
15670
15868
|
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
15671
15869
|
if (match) {
|
|
15672
15870
|
currentVersion = match[1];
|
|
@@ -15675,7 +15873,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15675
15873
|
}
|
|
15676
15874
|
let latestVersion = "unknown";
|
|
15677
15875
|
try {
|
|
15678
|
-
const { stdout } = await
|
|
15876
|
+
const { stdout } = await execAsync22(
|
|
15679
15877
|
"curl -sL https://api.github.com/repos/steveyegge/beads/releases/latest | jq -r .tag_name",
|
|
15680
15878
|
{ encoding: "utf-8" }
|
|
15681
15879
|
);
|
|
@@ -15724,7 +15922,7 @@ async function upgradeCommand(checkOnly = false) {
|
|
|
15724
15922
|
spinner.succeed("beads upgraded via install script");
|
|
15725
15923
|
}
|
|
15726
15924
|
try {
|
|
15727
|
-
const { stdout } = await
|
|
15925
|
+
const { stdout } = await execAsync22("bd --version", { encoding: "utf-8" });
|
|
15728
15926
|
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
15729
15927
|
if (match) {
|
|
15730
15928
|
console.log(chalk54.green(`
|
|
@@ -16231,12 +16429,12 @@ init_esm_shims();
|
|
|
16231
16429
|
init_config();
|
|
16232
16430
|
import chalk59 from "chalk";
|
|
16233
16431
|
import ora27 from "ora";
|
|
16234
|
-
import { exec as
|
|
16235
|
-
import { promisify as
|
|
16236
|
-
import { existsSync as existsSync54, readFileSync as
|
|
16432
|
+
import { exec as exec23 } from "child_process";
|
|
16433
|
+
import { promisify as promisify23 } from "util";
|
|
16434
|
+
import { existsSync as existsSync54, readFileSync as readFileSync48 } from "fs";
|
|
16237
16435
|
import { join as join53 } from "path";
|
|
16238
|
-
import { homedir as
|
|
16239
|
-
var
|
|
16436
|
+
import { homedir as homedir25 } from "os";
|
|
16437
|
+
var execAsync23 = promisify23(exec23);
|
|
16240
16438
|
async function setupCommand() {
|
|
16241
16439
|
console.log("");
|
|
16242
16440
|
console.log(chalk59.bold("\u{1F680} Remote Workspace Setup"));
|
|
@@ -16247,7 +16445,7 @@ async function setupCommand() {
|
|
|
16247
16445
|
console.log("");
|
|
16248
16446
|
console.log(chalk59.bold(" Step 2: SSH Key Configuration"));
|
|
16249
16447
|
console.log("");
|
|
16250
|
-
const sshDir = join53(
|
|
16448
|
+
const sshDir = join53(homedir25(), ".ssh");
|
|
16251
16449
|
const defaultKeyPath = join53(sshDir, "id_ed25519");
|
|
16252
16450
|
const rsaKeyPath = join53(sshDir, "id_rsa");
|
|
16253
16451
|
let sshKeyExists = false;
|
|
@@ -16271,7 +16469,7 @@ async function setupCommand() {
|
|
|
16271
16469
|
if (sshKeyExists) {
|
|
16272
16470
|
const pubKeyPath = `${keyPath}.pub`;
|
|
16273
16471
|
if (existsSync54(pubKeyPath)) {
|
|
16274
|
-
const pubKey =
|
|
16472
|
+
const pubKey = readFileSync48(pubKeyPath, "utf8").trim();
|
|
16275
16473
|
console.log("");
|
|
16276
16474
|
console.log(" Your public key (add this to exe.dev if not already):");
|
|
16277
16475
|
console.log("");
|
|
@@ -16299,7 +16497,7 @@ async function setupCommand() {
|
|
|
16299
16497
|
if (sshKeyExists) {
|
|
16300
16498
|
const pubKeyPath = `${keyPath}.pub`;
|
|
16301
16499
|
if (existsSync54(pubKeyPath)) {
|
|
16302
|
-
const pubKey =
|
|
16500
|
+
const pubKey = readFileSync48(pubKeyPath, "utf8").trim();
|
|
16303
16501
|
console.log(chalk59.dim(` ${pubKey}`));
|
|
16304
16502
|
}
|
|
16305
16503
|
} else {
|
|
@@ -16385,8 +16583,8 @@ import chalk60 from "chalk";
|
|
|
16385
16583
|
// src/lib/env-loader.ts
|
|
16386
16584
|
init_esm_shims();
|
|
16387
16585
|
import { join as join54 } from "path";
|
|
16388
|
-
import { homedir as
|
|
16389
|
-
var ENV_FILE_PATH = join54(
|
|
16586
|
+
import { homedir as homedir26 } from "os";
|
|
16587
|
+
var ENV_FILE_PATH = join54(homedir26(), ".panopticon.env");
|
|
16390
16588
|
function getShadowModeFromEnv() {
|
|
16391
16589
|
const value = process.env.SHADOW_MODE;
|
|
16392
16590
|
if (!value) return false;
|
|
@@ -16480,11 +16678,422 @@ Shadowed issues: ${shadowedIssues.length}`));
|
|
|
16480
16678
|
}
|
|
16481
16679
|
}
|
|
16482
16680
|
|
|
16681
|
+
// src/cli/commands/cost.ts
|
|
16682
|
+
init_esm_shims();
|
|
16683
|
+
init_cost();
|
|
16684
|
+
import { Command } from "commander";
|
|
16685
|
+
import chalk61 from "chalk";
|
|
16686
|
+
|
|
16687
|
+
// src/lib/costs/sync-wal.ts
|
|
16688
|
+
init_esm_shims();
|
|
16689
|
+
init_projects();
|
|
16690
|
+
import { existsSync as existsSync55 } from "fs";
|
|
16691
|
+
import { readdir, readFile } from "fs/promises";
|
|
16692
|
+
import { join as join55 } from "path";
|
|
16693
|
+
|
|
16694
|
+
// src/lib/database/cost-events-db.ts
|
|
16695
|
+
init_esm_shims();
|
|
16696
|
+
init_database();
|
|
16697
|
+
function insertCostEvents(events, sourceFile) {
|
|
16698
|
+
const db = getDatabase();
|
|
16699
|
+
let inserted = 0;
|
|
16700
|
+
let duplicates = 0;
|
|
16701
|
+
const insert = db.prepare(`
|
|
16702
|
+
INSERT OR IGNORE INTO cost_events (
|
|
16703
|
+
ts, agent_id, issue_id, session_type, provider, model,
|
|
16704
|
+
input, output, cache_read, cache_write, cost, request_id,
|
|
16705
|
+
tldr_interceptions, tldr_bypasses, tldr_tokens_saved, tldr_bypass_reasons,
|
|
16706
|
+
source_file
|
|
16707
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16708
|
+
`);
|
|
16709
|
+
const insertMany = db.transaction((evs) => {
|
|
16710
|
+
for (const ev of evs) {
|
|
16711
|
+
const result = insert.run(
|
|
16712
|
+
ev.ts,
|
|
16713
|
+
ev.agentId,
|
|
16714
|
+
ev.issueId,
|
|
16715
|
+
ev.sessionType || "unknown",
|
|
16716
|
+
ev.provider || "anthropic",
|
|
16717
|
+
ev.model,
|
|
16718
|
+
ev.input,
|
|
16719
|
+
ev.output,
|
|
16720
|
+
ev.cacheRead,
|
|
16721
|
+
ev.cacheWrite,
|
|
16722
|
+
ev.cost,
|
|
16723
|
+
ev.requestId ?? null,
|
|
16724
|
+
ev.tldrInterceptions ?? null,
|
|
16725
|
+
ev.tldrBypasses ?? null,
|
|
16726
|
+
ev.tldrTokensSaved ?? null,
|
|
16727
|
+
ev.tldrBypassReasons ? JSON.stringify(ev.tldrBypassReasons) : null,
|
|
16728
|
+
sourceFile ?? null
|
|
16729
|
+
);
|
|
16730
|
+
if (result.changes > 0) {
|
|
16731
|
+
inserted++;
|
|
16732
|
+
} else {
|
|
16733
|
+
duplicates++;
|
|
16734
|
+
}
|
|
16735
|
+
}
|
|
16736
|
+
});
|
|
16737
|
+
insertMany(events);
|
|
16738
|
+
return { inserted, duplicates };
|
|
16739
|
+
}
|
|
16740
|
+
|
|
16741
|
+
// src/lib/costs/sync-wal.ts
|
|
16742
|
+
var DEFAULT_EVENTS_SUBDIR = ".panopticon/events";
|
|
16743
|
+
async function syncWalFromAllProjects() {
|
|
16744
|
+
const result = {
|
|
16745
|
+
imported: 0,
|
|
16746
|
+
duplicates: 0,
|
|
16747
|
+
filesScanned: 0,
|
|
16748
|
+
byProject: {},
|
|
16749
|
+
errors: []
|
|
16750
|
+
};
|
|
16751
|
+
const projects = listProjects();
|
|
16752
|
+
for (const { key, config: config2 } of projects) {
|
|
16753
|
+
const repoPath = config2.events_repo ?? config2.path;
|
|
16754
|
+
const eventsSubdir = config2.events_path ?? DEFAULT_EVENTS_SUBDIR;
|
|
16755
|
+
const eventsDir = join55(repoPath, eventsSubdir);
|
|
16756
|
+
if (!existsSync55(eventsDir)) continue;
|
|
16757
|
+
const projectStats = { imported: 0, duplicates: 0, files: 0 };
|
|
16758
|
+
let files;
|
|
16759
|
+
try {
|
|
16760
|
+
files = (await readdir(eventsDir)).filter((f) => f.endsWith(".jsonl"));
|
|
16761
|
+
} catch (err) {
|
|
16762
|
+
result.errors.push(`${key}: failed to read events dir: ${err}`);
|
|
16763
|
+
continue;
|
|
16764
|
+
}
|
|
16765
|
+
for (const file of files) {
|
|
16766
|
+
const filePath = join55(eventsDir, file);
|
|
16767
|
+
const events = await parseWalFile(filePath, result.errors);
|
|
16768
|
+
if (events.length === 0) continue;
|
|
16769
|
+
try {
|
|
16770
|
+
const { inserted, duplicates } = insertCostEvents(events, filePath);
|
|
16771
|
+
projectStats.imported += inserted;
|
|
16772
|
+
projectStats.duplicates += duplicates;
|
|
16773
|
+
projectStats.files++;
|
|
16774
|
+
result.filesScanned++;
|
|
16775
|
+
} catch (err) {
|
|
16776
|
+
result.errors.push(`${key}/${file}: import failed: ${err}`);
|
|
16777
|
+
}
|
|
16778
|
+
}
|
|
16779
|
+
if (projectStats.files > 0 || projectStats.imported > 0) {
|
|
16780
|
+
result.byProject[key] = projectStats;
|
|
16781
|
+
result.imported += projectStats.imported;
|
|
16782
|
+
result.duplicates += projectStats.duplicates;
|
|
16783
|
+
}
|
|
16784
|
+
}
|
|
16785
|
+
return result;
|
|
16786
|
+
}
|
|
16787
|
+
async function parseWalFile(filePath, errors) {
|
|
16788
|
+
let content;
|
|
16789
|
+
try {
|
|
16790
|
+
content = await readFile(filePath, "utf-8");
|
|
16791
|
+
} catch (err) {
|
|
16792
|
+
errors.push(`Failed to read ${filePath}: ${err}`);
|
|
16793
|
+
return [];
|
|
16794
|
+
}
|
|
16795
|
+
const events = [];
|
|
16796
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
16797
|
+
for (const line of lines) {
|
|
16798
|
+
try {
|
|
16799
|
+
const event = JSON.parse(line);
|
|
16800
|
+
if (event.ts && event.agentId && event.issueId && event.model) {
|
|
16801
|
+
events.push(event);
|
|
16802
|
+
}
|
|
16803
|
+
} catch {
|
|
16804
|
+
}
|
|
16805
|
+
}
|
|
16806
|
+
return events;
|
|
16807
|
+
}
|
|
16808
|
+
|
|
16809
|
+
// src/cli/commands/cost.ts
|
|
16810
|
+
async function runCostSync() {
|
|
16811
|
+
try {
|
|
16812
|
+
console.log(chalk61.bold("Syncing cost events from project WAL files..."));
|
|
16813
|
+
const result = await syncWalFromAllProjects();
|
|
16814
|
+
if (result.filesScanned === 0) {
|
|
16815
|
+
console.log(chalk61.yellow("No WAL files found. Make sure projects are registered and have cost events."));
|
|
16816
|
+
return;
|
|
16817
|
+
}
|
|
16818
|
+
console.log();
|
|
16819
|
+
console.log(`Files scanned: ${result.filesScanned}`);
|
|
16820
|
+
console.log(`Imported: ${chalk61.green(result.imported)} new events`);
|
|
16821
|
+
console.log(`Duplicates: ${chalk61.dim(result.duplicates)} skipped`);
|
|
16822
|
+
if (Object.keys(result.byProject).length > 0) {
|
|
16823
|
+
console.log();
|
|
16824
|
+
console.log(chalk61.bold("By Project:"));
|
|
16825
|
+
for (const [project2, stats] of Object.entries(result.byProject)) {
|
|
16826
|
+
console.log(` ${project2}: ${chalk61.green(stats.imported)} imported, ${stats.files} file(s)`);
|
|
16827
|
+
}
|
|
16828
|
+
}
|
|
16829
|
+
if (result.errors.length > 0) {
|
|
16830
|
+
console.log();
|
|
16831
|
+
console.log(chalk61.yellow(`Warnings (${result.errors.length}):`));
|
|
16832
|
+
for (const err of result.errors) {
|
|
16833
|
+
console.log(` ${chalk61.dim(err)}`);
|
|
16834
|
+
}
|
|
16835
|
+
}
|
|
16836
|
+
} catch (error) {
|
|
16837
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16838
|
+
process.exit(1);
|
|
16839
|
+
}
|
|
16840
|
+
}
|
|
16841
|
+
function createCostCommand() {
|
|
16842
|
+
const cost = new Command("cost").description("Track and report AI usage costs");
|
|
16843
|
+
cost.command("today").description("Show today's cost summary").option("-d, --detail", "Show individual entries").action((options) => {
|
|
16844
|
+
try {
|
|
16845
|
+
const summary = getDailySummary();
|
|
16846
|
+
console.log(chalk61.bold("Today's Cost Summary"));
|
|
16847
|
+
console.log();
|
|
16848
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16849
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16850
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16851
|
+
console.log(` Input: ${summary.totalTokens.input.toLocaleString()}`);
|
|
16852
|
+
console.log(` Output: ${summary.totalTokens.output.toLocaleString()}`);
|
|
16853
|
+
console.log();
|
|
16854
|
+
if (Object.keys(summary.byProvider).length > 0) {
|
|
16855
|
+
console.log(chalk61.bold("By Provider"));
|
|
16856
|
+
for (const [provider, cost2] of Object.entries(summary.byProvider)) {
|
|
16857
|
+
console.log(` ${provider}: ${formatCost(cost2)}`);
|
|
16858
|
+
}
|
|
16859
|
+
console.log();
|
|
16860
|
+
}
|
|
16861
|
+
if (Object.keys(summary.byModel).length > 0) {
|
|
16862
|
+
console.log(chalk61.bold("By Model"));
|
|
16863
|
+
for (const [model, cost2] of Object.entries(summary.byModel)) {
|
|
16864
|
+
console.log(` ${model}: ${formatCost(cost2)}`);
|
|
16865
|
+
}
|
|
16866
|
+
console.log();
|
|
16867
|
+
}
|
|
16868
|
+
if (options.detail) {
|
|
16869
|
+
const entries = readTodayCosts();
|
|
16870
|
+
if (entries.length > 0) {
|
|
16871
|
+
console.log(chalk61.bold("Entries"));
|
|
16872
|
+
for (const entry of entries.slice(-10)) {
|
|
16873
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
16874
|
+
console.log(` ${chalk61.dim(time)} ${entry.model} ${formatCost(entry.cost)} ${entry.operation}`);
|
|
16875
|
+
}
|
|
16876
|
+
if (entries.length > 10) {
|
|
16877
|
+
console.log(chalk61.dim(` ... and ${entries.length - 10} more`));
|
|
16878
|
+
}
|
|
16879
|
+
}
|
|
16880
|
+
}
|
|
16881
|
+
} catch (error) {
|
|
16882
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16883
|
+
process.exit(1);
|
|
16884
|
+
}
|
|
16885
|
+
});
|
|
16886
|
+
cost.command("week").description("Show weekly cost summary").action(() => {
|
|
16887
|
+
try {
|
|
16888
|
+
const summary = getWeeklySummary();
|
|
16889
|
+
console.log(chalk61.bold("Weekly Cost Summary"));
|
|
16890
|
+
console.log(chalk61.dim(`${summary.period.start} to ${summary.period.end}`));
|
|
16891
|
+
console.log();
|
|
16892
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16893
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16894
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16895
|
+
console.log();
|
|
16896
|
+
if (Object.keys(summary.byProvider).length > 0) {
|
|
16897
|
+
console.log(chalk61.bold("By Provider"));
|
|
16898
|
+
for (const [provider, cost2] of Object.entries(summary.byProvider)) {
|
|
16899
|
+
console.log(` ${provider}: ${formatCost(cost2)}`);
|
|
16900
|
+
}
|
|
16901
|
+
console.log();
|
|
16902
|
+
}
|
|
16903
|
+
if (Object.keys(summary.byIssue).length > 0) {
|
|
16904
|
+
console.log(chalk61.bold("Top Issues by Cost"));
|
|
16905
|
+
const sorted = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a).slice(0, 5);
|
|
16906
|
+
for (const [issue, cost2] of sorted) {
|
|
16907
|
+
console.log(` ${issue}: ${formatCost(cost2)}`);
|
|
16908
|
+
}
|
|
16909
|
+
}
|
|
16910
|
+
} catch (error) {
|
|
16911
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16912
|
+
process.exit(1);
|
|
16913
|
+
}
|
|
16914
|
+
});
|
|
16915
|
+
cost.command("month").description("Show monthly cost summary").action(() => {
|
|
16916
|
+
try {
|
|
16917
|
+
const summary = getMonthlySummary();
|
|
16918
|
+
console.log(chalk61.bold("Monthly Cost Summary"));
|
|
16919
|
+
console.log(chalk61.dim(`${summary.period.start} to ${summary.period.end}`));
|
|
16920
|
+
console.log();
|
|
16921
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16922
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16923
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16924
|
+
console.log();
|
|
16925
|
+
if (Object.keys(summary.byProvider).length > 0) {
|
|
16926
|
+
console.log(chalk61.bold("By Provider"));
|
|
16927
|
+
for (const [provider, cost2] of Object.entries(summary.byProvider)) {
|
|
16928
|
+
console.log(` ${provider}: ${formatCost(cost2)}`);
|
|
16929
|
+
}
|
|
16930
|
+
console.log();
|
|
16931
|
+
}
|
|
16932
|
+
if (Object.keys(summary.byModel).length > 0) {
|
|
16933
|
+
console.log(chalk61.bold("By Model"));
|
|
16934
|
+
for (const [model, cost2] of Object.entries(summary.byModel)) {
|
|
16935
|
+
console.log(` ${model}: ${formatCost(cost2)}`);
|
|
16936
|
+
}
|
|
16937
|
+
console.log();
|
|
16938
|
+
}
|
|
16939
|
+
if (Object.keys(summary.byIssue).length > 0) {
|
|
16940
|
+
console.log(chalk61.bold("Top 10 Issues by Cost"));
|
|
16941
|
+
const sorted = Object.entries(summary.byIssue).sort(([, a], [, b]) => b - a).slice(0, 10);
|
|
16942
|
+
for (const [issue, cost2] of sorted) {
|
|
16943
|
+
console.log(` ${issue}: ${formatCost(cost2)}`);
|
|
16944
|
+
}
|
|
16945
|
+
}
|
|
16946
|
+
} catch (error) {
|
|
16947
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16948
|
+
process.exit(1);
|
|
16949
|
+
}
|
|
16950
|
+
});
|
|
16951
|
+
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) => {
|
|
16952
|
+
try {
|
|
16953
|
+
const end = options.end || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
16954
|
+
const start = options.start || (() => {
|
|
16955
|
+
const d = /* @__PURE__ */ new Date();
|
|
16956
|
+
d.setDate(d.getDate() - 30);
|
|
16957
|
+
return d.toISOString().split("T")[0];
|
|
16958
|
+
})();
|
|
16959
|
+
const report = generateReport(start, end);
|
|
16960
|
+
console.log(report);
|
|
16961
|
+
} catch (error) {
|
|
16962
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16963
|
+
process.exit(1);
|
|
16964
|
+
}
|
|
16965
|
+
});
|
|
16966
|
+
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) => {
|
|
16967
|
+
try {
|
|
16968
|
+
const entries = readIssueCosts(issueId, parseInt(options.days, 10));
|
|
16969
|
+
if (entries.length === 0) {
|
|
16970
|
+
console.log(chalk61.dim("No costs found for issue:"), issueId);
|
|
16971
|
+
return;
|
|
16972
|
+
}
|
|
16973
|
+
const summary = summarizeCosts(entries);
|
|
16974
|
+
console.log(chalk61.bold(`Costs for ${issueId}`));
|
|
16975
|
+
console.log();
|
|
16976
|
+
console.log(`Total Cost: ${chalk61.green(formatCost(summary.totalCost))}`);
|
|
16977
|
+
console.log(`API Calls: ${summary.entryCount}`);
|
|
16978
|
+
console.log(`Tokens: ${summary.totalTokens.total.toLocaleString()}`);
|
|
16979
|
+
console.log();
|
|
16980
|
+
if (Object.keys(summary.byModel).length > 0) {
|
|
16981
|
+
console.log(chalk61.bold("By Model"));
|
|
16982
|
+
for (const [model, cost2] of Object.entries(summary.byModel)) {
|
|
16983
|
+
console.log(` ${model}: ${formatCost(cost2)}`);
|
|
16984
|
+
}
|
|
16985
|
+
}
|
|
16986
|
+
} catch (error) {
|
|
16987
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
16988
|
+
process.exit(1);
|
|
16989
|
+
}
|
|
16990
|
+
});
|
|
16991
|
+
const budget = cost.command("budget").description("Manage cost budgets");
|
|
16992
|
+
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) => {
|
|
16993
|
+
try {
|
|
16994
|
+
const newBudget = createBudget({
|
|
16995
|
+
name,
|
|
16996
|
+
type: options.type,
|
|
16997
|
+
limit: parseFloat(options.limit),
|
|
16998
|
+
currency: "USD",
|
|
16999
|
+
alertThreshold: parseFloat(options.alert),
|
|
17000
|
+
enabled: true
|
|
17001
|
+
});
|
|
17002
|
+
console.log(chalk61.green("\u2713 Budget created"));
|
|
17003
|
+
console.log(` ID: ${chalk61.cyan(newBudget.id)}`);
|
|
17004
|
+
console.log(` Name: ${newBudget.name}`);
|
|
17005
|
+
console.log(` Type: ${newBudget.type}`);
|
|
17006
|
+
console.log(` Limit: ${formatCost(newBudget.limit)}`);
|
|
17007
|
+
console.log(` Alert at: ${(newBudget.alertThreshold * 100).toFixed(0)}%`);
|
|
17008
|
+
} catch (error) {
|
|
17009
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17010
|
+
process.exit(1);
|
|
17011
|
+
}
|
|
17012
|
+
});
|
|
17013
|
+
budget.command("list").description("List all budgets").action(() => {
|
|
17014
|
+
try {
|
|
17015
|
+
const budgets = getAllBudgets();
|
|
17016
|
+
if (budgets.length === 0) {
|
|
17017
|
+
console.log(chalk61.dim("No budgets configured"));
|
|
17018
|
+
console.log(chalk61.dim('Create one with: pan cost budget create "Monthly Limit" --limit 100'));
|
|
17019
|
+
return;
|
|
17020
|
+
}
|
|
17021
|
+
console.log(chalk61.bold("Budgets"));
|
|
17022
|
+
console.log();
|
|
17023
|
+
for (const b of budgets) {
|
|
17024
|
+
const status = checkBudget(b.id);
|
|
17025
|
+
const percentStr = `${(status.percentUsed * 100).toFixed(0)}%`;
|
|
17026
|
+
let statusColor = chalk61.green;
|
|
17027
|
+
if (status.exceeded) {
|
|
17028
|
+
statusColor = chalk61.red;
|
|
17029
|
+
} else if (status.alert) {
|
|
17030
|
+
statusColor = chalk61.yellow;
|
|
17031
|
+
}
|
|
17032
|
+
console.log(`${b.enabled ? "\u25CF" : "\u25CB"} ${chalk61.cyan(b.id)} ${b.name}`);
|
|
17033
|
+
console.log(` Type: ${b.type}`);
|
|
17034
|
+
console.log(` Limit: ${formatCost(b.limit)}`);
|
|
17035
|
+
console.log(` Spent: ${statusColor(formatCost(b.spent))} (${statusColor(percentStr)})`);
|
|
17036
|
+
console.log(` Remaining: ${formatCost(status.remaining)}`);
|
|
17037
|
+
console.log();
|
|
17038
|
+
}
|
|
17039
|
+
} catch (error) {
|
|
17040
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17041
|
+
process.exit(1);
|
|
17042
|
+
}
|
|
17043
|
+
});
|
|
17044
|
+
budget.command("check <id>").description("Check budget status").action((id) => {
|
|
17045
|
+
try {
|
|
17046
|
+
const status = checkBudget(id);
|
|
17047
|
+
if (!status.budget) {
|
|
17048
|
+
console.log(chalk61.red("Budget not found:"), id);
|
|
17049
|
+
process.exit(1);
|
|
17050
|
+
}
|
|
17051
|
+
const b = status.budget;
|
|
17052
|
+
const percentStr = `${(status.percentUsed * 100).toFixed(0)}%`;
|
|
17053
|
+
let statusColor = chalk61.green;
|
|
17054
|
+
let statusText = "OK";
|
|
17055
|
+
if (status.exceeded) {
|
|
17056
|
+
statusColor = chalk61.red;
|
|
17057
|
+
statusText = "EXCEEDED";
|
|
17058
|
+
} else if (status.alert) {
|
|
17059
|
+
statusColor = chalk61.yellow;
|
|
17060
|
+
statusText = "WARNING";
|
|
17061
|
+
}
|
|
17062
|
+
console.log(chalk61.bold(b.name));
|
|
17063
|
+
console.log();
|
|
17064
|
+
console.log(`Status: ${statusColor(statusText)}`);
|
|
17065
|
+
console.log(`Limit: ${formatCost(b.limit)}`);
|
|
17066
|
+
console.log(`Spent: ${statusColor(formatCost(b.spent))} (${statusColor(percentStr)})`);
|
|
17067
|
+
console.log(`Remaining: ${formatCost(status.remaining)}`);
|
|
17068
|
+
console.log(`Alert Threshold: ${(b.alertThreshold * 100).toFixed(0)}%`);
|
|
17069
|
+
} catch (error) {
|
|
17070
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17071
|
+
process.exit(1);
|
|
17072
|
+
}
|
|
17073
|
+
});
|
|
17074
|
+
budget.command("delete <id>").description("Delete a budget").action((id) => {
|
|
17075
|
+
try {
|
|
17076
|
+
const success = deleteBudget(id);
|
|
17077
|
+
if (success) {
|
|
17078
|
+
console.log(chalk61.green("\u2713 Budget deleted"));
|
|
17079
|
+
} else {
|
|
17080
|
+
console.log(chalk61.red("Budget not found:"), id);
|
|
17081
|
+
process.exit(1);
|
|
17082
|
+
}
|
|
17083
|
+
} catch (error) {
|
|
17084
|
+
console.error(chalk61.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
17085
|
+
process.exit(1);
|
|
17086
|
+
}
|
|
17087
|
+
});
|
|
17088
|
+
cost.command("sync").description("Import cost events from per-project WAL files into the local database").action(runCostSync);
|
|
17089
|
+
return cost;
|
|
17090
|
+
}
|
|
17091
|
+
|
|
16483
17092
|
// src/cli/index.ts
|
|
16484
|
-
var PANOPTICON_ENV_FILE =
|
|
16485
|
-
if (
|
|
17093
|
+
var PANOPTICON_ENV_FILE = join56(homedir27(), ".panopticon.env");
|
|
17094
|
+
if (existsSync56(PANOPTICON_ENV_FILE)) {
|
|
16486
17095
|
try {
|
|
16487
|
-
const envContent =
|
|
17096
|
+
const envContent = readFileSync49(PANOPTICON_ENV_FILE, "utf-8");
|
|
16488
17097
|
for (const line of envContent.split("\n")) {
|
|
16489
17098
|
const trimmed = line.trim();
|
|
16490
17099
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -16500,8 +17109,8 @@ if (existsSync55(PANOPTICON_ENV_FILE)) {
|
|
|
16500
17109
|
console.warn("Warning: Failed to load ~/.panopticon.env:", error.message);
|
|
16501
17110
|
}
|
|
16502
17111
|
}
|
|
16503
|
-
var program = new
|
|
16504
|
-
program.name("pan").description("Multi-agent orchestration for AI coding assistants").version(JSON.parse(
|
|
17112
|
+
var program = new Command2();
|
|
17113
|
+
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
17114
|
program.command("init").description("Initialize Panopticon (~/.panopticon/)").action(initCommand);
|
|
16506
17115
|
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
17116
|
program.command("restore [timestamp]").description("Restore from backup").action(restoreCommand);
|
|
@@ -16525,58 +17134,58 @@ program.command("migrate-config").description("Migrate from settings.json to con
|
|
|
16525
17134
|
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
17135
|
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
17136
|
const { spawn: spawn2, execSync: execSync9 } = await import("child_process");
|
|
16528
|
-
const { join:
|
|
17137
|
+
const { join: join57, dirname: dirname17 } = await import("path");
|
|
16529
17138
|
const { fileURLToPath: fileURLToPath6 } = await import("url");
|
|
16530
|
-
const { readFileSync:
|
|
17139
|
+
const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("fs");
|
|
16531
17140
|
const { parse } = await import("@iarna/toml");
|
|
16532
17141
|
const __dirname6 = dirname17(fileURLToPath6(import.meta.url));
|
|
16533
|
-
const bundledServer =
|
|
16534
|
-
const srcDashboard =
|
|
16535
|
-
const configFile =
|
|
17142
|
+
const bundledServer = join57(__dirname6, "..", "dashboard", "server.js");
|
|
17143
|
+
const srcDashboard = join57(__dirname6, "..", "..", "src", "dashboard");
|
|
17144
|
+
const configFile = join57(process.env.HOME || "", ".panopticon", "config.toml");
|
|
16536
17145
|
let traefikEnabled = false;
|
|
16537
17146
|
let traefikDomain = "pan.localhost";
|
|
16538
17147
|
let dashboardPort = 3010;
|
|
16539
17148
|
let dashboardApiPort = 3011;
|
|
16540
|
-
if (
|
|
17149
|
+
if (existsSync57(configFile)) {
|
|
16541
17150
|
try {
|
|
16542
|
-
const configContent =
|
|
17151
|
+
const configContent = readFileSync50(configFile, "utf-8");
|
|
16543
17152
|
const config2 = parse(configContent);
|
|
16544
17153
|
traefikEnabled = config2.traefik?.enabled === true;
|
|
16545
17154
|
traefikDomain = config2.traefik?.domain || "pan.localhost";
|
|
16546
17155
|
dashboardPort = config2.dashboard?.port || 3010;
|
|
16547
17156
|
dashboardApiPort = config2.dashboard?.api_port || 3011;
|
|
16548
17157
|
} catch (error) {
|
|
16549
|
-
console.log(
|
|
17158
|
+
console.log(chalk62.yellow("Warning: Could not read config.toml"));
|
|
16550
17159
|
}
|
|
16551
17160
|
}
|
|
16552
|
-
console.log(
|
|
17161
|
+
console.log(chalk62.bold("Starting Panopticon...\n"));
|
|
16553
17162
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16554
17163
|
try {
|
|
16555
|
-
const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-
|
|
17164
|
+
const { generatePanopticonTraefikConfig: generatePanopticonTraefikConfig2, ensureProjectCerts: ensureProjectCerts2, generateTlsConfig: generateTlsConfig2, cleanupStaleTlsSections } = await import("../traefik-5GL3Q7DJ.js");
|
|
16556
17165
|
cleanupStaleTlsSections();
|
|
16557
17166
|
if (generatePanopticonTraefikConfig2()) {
|
|
16558
|
-
console.log(
|
|
17167
|
+
console.log(chalk62.dim(" Regenerated Traefik config from template"));
|
|
16559
17168
|
}
|
|
16560
17169
|
const generatedDomains = ensureProjectCerts2();
|
|
16561
17170
|
for (const domain of generatedDomains) {
|
|
16562
|
-
console.log(
|
|
17171
|
+
console.log(chalk62.dim(` Generated wildcard cert for *.${domain}`));
|
|
16563
17172
|
}
|
|
16564
17173
|
if (generateTlsConfig2()) {
|
|
16565
|
-
console.log(
|
|
17174
|
+
console.log(chalk62.dim(" Generated TLS config (tls.yml)"));
|
|
16566
17175
|
}
|
|
16567
17176
|
} catch {
|
|
16568
|
-
console.log(
|
|
17177
|
+
console.log(chalk62.yellow("Warning: Could not regenerate Traefik config"));
|
|
16569
17178
|
}
|
|
16570
17179
|
try {
|
|
16571
17180
|
const { ensureBaseDomain: ensureBaseDomain2, detectDnsSyncMethod: detectDnsSyncMethod2, syncDnsToWindows: syncDnsToWindows2 } = await import("../dns-7BDJSD3E.js");
|
|
16572
|
-
const dnsMethod = (
|
|
17181
|
+
const dnsMethod = (existsSync57(configFile) ? parse(readFileSync50(configFile, "utf-8")).traefik?.dns_sync_method : null) || detectDnsSyncMethod2();
|
|
16573
17182
|
ensureBaseDomain2(dnsMethod, traefikDomain);
|
|
16574
17183
|
if (dnsMethod === "wsl2hosts") {
|
|
16575
17184
|
syncDnsToWindows2().catch(() => {
|
|
16576
17185
|
});
|
|
16577
17186
|
}
|
|
16578
17187
|
} catch {
|
|
16579
|
-
console.log(
|
|
17188
|
+
console.log(chalk62.yellow(`Warning: Could not ensure DNS for ${traefikDomain}`));
|
|
16580
17189
|
}
|
|
16581
17190
|
} else if (!traefikEnabled) {
|
|
16582
17191
|
try {
|
|
@@ -16585,19 +17194,19 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16585
17194
|
{ encoding: "utf-8" }
|
|
16586
17195
|
).trim();
|
|
16587
17196
|
if (containerCheck.includes("panopticon-traefik")) {
|
|
16588
|
-
console.log(
|
|
16589
|
-
console.log(
|
|
17197
|
+
console.log(chalk62.yellow("\u26A0 Traefik container is running but traefik.enabled is not set in config"));
|
|
17198
|
+
console.log(chalk62.yellow(" Run `pan install` to configure Traefik, or `pan down` to stop it\n"));
|
|
16590
17199
|
}
|
|
16591
17200
|
} catch {
|
|
16592
17201
|
}
|
|
16593
17202
|
}
|
|
16594
17203
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16595
|
-
const traefikDir =
|
|
16596
|
-
if (
|
|
17204
|
+
const traefikDir = join57(process.env.HOME || "", ".panopticon", "traefik");
|
|
17205
|
+
if (existsSync57(traefikDir)) {
|
|
16597
17206
|
try {
|
|
16598
|
-
const composeFile =
|
|
16599
|
-
if (
|
|
16600
|
-
const content =
|
|
17207
|
+
const composeFile = join57(traefikDir, "docker-compose.yml");
|
|
17208
|
+
if (existsSync57(composeFile)) {
|
|
17209
|
+
const content = readFileSync50(composeFile, "utf-8");
|
|
16601
17210
|
if (!content.includes("external: true") && content.includes("panopticon:")) {
|
|
16602
17211
|
const patched = content.replace(
|
|
16603
17212
|
/networks:\s*\n\s*panopticon:\s*\n\s*name: panopticon\s*\n\s*driver: bridge/,
|
|
@@ -16605,43 +17214,43 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16605
17214
|
);
|
|
16606
17215
|
const { writeFileSync: writeFileSync27 } = await import("fs");
|
|
16607
17216
|
writeFileSync27(composeFile, patched);
|
|
16608
|
-
console.log(
|
|
17217
|
+
console.log(chalk62.dim(" (migrated network config)"));
|
|
16609
17218
|
}
|
|
16610
17219
|
}
|
|
16611
|
-
console.log(
|
|
17220
|
+
console.log(chalk62.dim("Starting Traefik..."));
|
|
16612
17221
|
execSync9("docker compose up -d", {
|
|
16613
17222
|
cwd: traefikDir,
|
|
16614
17223
|
stdio: "pipe"
|
|
16615
17224
|
});
|
|
16616
|
-
console.log(
|
|
16617
|
-
console.log(
|
|
17225
|
+
console.log(chalk62.green("\u2713 Traefik started"));
|
|
17226
|
+
console.log(chalk62.dim(` Dashboard: https://traefik.${traefikDomain}:8080
|
|
16618
17227
|
`));
|
|
16619
17228
|
} catch (error) {
|
|
16620
|
-
console.log(
|
|
16621
|
-
console.log(
|
|
17229
|
+
console.log(chalk62.yellow("\u26A0 Failed to start Traefik (continuing anyway)"));
|
|
17230
|
+
console.log(chalk62.dim(" Run with --skip-traefik to suppress this message\n"));
|
|
16622
17231
|
}
|
|
16623
17232
|
}
|
|
16624
17233
|
}
|
|
16625
|
-
const isProduction =
|
|
16626
|
-
const isDevelopment =
|
|
17234
|
+
const isProduction = existsSync57(bundledServer);
|
|
17235
|
+
const isDevelopment = existsSync57(srcDashboard);
|
|
16627
17236
|
if (!isProduction && !isDevelopment) {
|
|
16628
|
-
console.error(
|
|
16629
|
-
console.error(
|
|
17237
|
+
console.error(chalk62.red("Error: Dashboard not found"));
|
|
17238
|
+
console.error(chalk62.dim("This may be a corrupted installation. Try reinstalling panopticon-cli."));
|
|
16630
17239
|
process.exit(1);
|
|
16631
17240
|
}
|
|
16632
17241
|
if (isDevelopment && !isProduction) {
|
|
16633
17242
|
try {
|
|
16634
17243
|
execSync9("npm --version", { stdio: "pipe" });
|
|
16635
17244
|
} catch {
|
|
16636
|
-
console.error(
|
|
16637
|
-
console.error(
|
|
17245
|
+
console.error(chalk62.red("Error: npm not found in PATH"));
|
|
17246
|
+
console.error(chalk62.dim("Make sure Node.js and npm are installed and in your PATH"));
|
|
16638
17247
|
process.exit(1);
|
|
16639
17248
|
}
|
|
16640
17249
|
}
|
|
16641
17250
|
if (isProduction) {
|
|
16642
|
-
console.log(
|
|
17251
|
+
console.log(chalk62.dim("Starting dashboard (bundled mode)..."));
|
|
16643
17252
|
} else {
|
|
16644
|
-
console.log(
|
|
17253
|
+
console.log(chalk62.dim("Starting dashboard (development mode)..."));
|
|
16645
17254
|
}
|
|
16646
17255
|
if (options.detach) {
|
|
16647
17256
|
const child = isProduction ? spawn2("node", [bundledServer], {
|
|
@@ -16657,7 +17266,7 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16657
17266
|
let hasError = false;
|
|
16658
17267
|
child.on("error", (err) => {
|
|
16659
17268
|
hasError = true;
|
|
16660
|
-
console.error(
|
|
17269
|
+
console.error(chalk62.red("Failed to start dashboard in background:"), err.message);
|
|
16661
17270
|
process.exit(1);
|
|
16662
17271
|
});
|
|
16663
17272
|
setTimeout(() => {
|
|
@@ -16665,23 +17274,23 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16665
17274
|
child.unref();
|
|
16666
17275
|
}
|
|
16667
17276
|
}, 100);
|
|
16668
|
-
console.log(
|
|
17277
|
+
console.log(chalk62.green("\u2713 Dashboard started in background"));
|
|
16669
17278
|
if (traefikEnabled) {
|
|
16670
|
-
console.log(` Frontend: ${
|
|
16671
|
-
console.log(` API: ${
|
|
17279
|
+
console.log(` Frontend: ${chalk62.cyan(`https://${traefikDomain}`)}`);
|
|
17280
|
+
console.log(` API: ${chalk62.cyan(`https://${traefikDomain}/api`)}`);
|
|
16672
17281
|
} else {
|
|
16673
|
-
console.log(` Frontend: ${
|
|
16674
|
-
console.log(` API: ${
|
|
17282
|
+
console.log(` Frontend: ${chalk62.cyan(`http://localhost:${dashboardPort}`)}`);
|
|
17283
|
+
console.log(` API: ${chalk62.cyan(`http://localhost:${dashboardApiPort}`)}`);
|
|
16675
17284
|
}
|
|
16676
17285
|
} else {
|
|
16677
17286
|
if (traefikEnabled) {
|
|
16678
|
-
console.log(` Frontend: ${
|
|
16679
|
-
console.log(` API: ${
|
|
17287
|
+
console.log(` Frontend: ${chalk62.cyan(`https://${traefikDomain}`)}`);
|
|
17288
|
+
console.log(` API: ${chalk62.cyan(`https://${traefikDomain}/api`)}`);
|
|
16680
17289
|
} else {
|
|
16681
|
-
console.log(` Frontend: ${
|
|
16682
|
-
console.log(` API: ${
|
|
17290
|
+
console.log(` Frontend: ${chalk62.cyan(`http://localhost:${dashboardPort}`)}`);
|
|
17291
|
+
console.log(` API: ${chalk62.cyan(`http://localhost:${dashboardApiPort}`)}`);
|
|
16683
17292
|
}
|
|
16684
|
-
console.log(
|
|
17293
|
+
console.log(chalk62.dim("\nPress Ctrl+C to stop\n"));
|
|
16685
17294
|
const child = isProduction ? spawn2("node", [bundledServer], {
|
|
16686
17295
|
stdio: "inherit",
|
|
16687
17296
|
env: { ...process.env, DASHBOARD_PORT: String(dashboardPort) }
|
|
@@ -16691,41 +17300,41 @@ program.command("up").description("Start dashboard (and Traefik if enabled)").op
|
|
|
16691
17300
|
shell: true
|
|
16692
17301
|
});
|
|
16693
17302
|
child.on("error", (err) => {
|
|
16694
|
-
console.error(
|
|
17303
|
+
console.error(chalk62.red("Failed to start dashboard:"), err.message);
|
|
16695
17304
|
process.exit(1);
|
|
16696
17305
|
});
|
|
16697
17306
|
}
|
|
16698
17307
|
try {
|
|
16699
17308
|
const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
|
|
16700
17309
|
const projectRoot = process.cwd();
|
|
16701
|
-
const venvPath =
|
|
16702
|
-
if (
|
|
16703
|
-
console.log(
|
|
17310
|
+
const venvPath = join57(projectRoot, ".venv");
|
|
17311
|
+
if (existsSync57(venvPath)) {
|
|
17312
|
+
console.log(chalk62.dim("\nStarting TLDR daemon for project root..."));
|
|
16704
17313
|
const tldrService = getTldrDaemonService2(projectRoot, venvPath);
|
|
16705
17314
|
await tldrService.start(true);
|
|
16706
|
-
console.log(
|
|
17315
|
+
console.log(chalk62.green("\u2713 TLDR daemon started"));
|
|
16707
17316
|
} else {
|
|
16708
|
-
console.log(
|
|
16709
|
-
console.log(
|
|
17317
|
+
console.log(chalk62.dim("\nSkipping TLDR daemon (no .venv found)"));
|
|
17318
|
+
console.log(chalk62.dim(" Run setup to create venv with llm-tldr"));
|
|
16710
17319
|
}
|
|
16711
17320
|
} catch (error) {
|
|
16712
|
-
console.log(
|
|
16713
|
-
console.log(
|
|
17321
|
+
console.log(chalk62.yellow("\u26A0 Failed to start TLDR daemon:"), error?.message || String(error));
|
|
17322
|
+
console.log(chalk62.dim(" TLDR will be unavailable but dashboard will work normally"));
|
|
16714
17323
|
}
|
|
16715
17324
|
});
|
|
16716
17325
|
program.command("down").description("Stop dashboard (and Traefik if enabled)").option("--skip-traefik", "Skip Traefik shutdown").action(async (options) => {
|
|
16717
17326
|
const { execSync: execSync9 } = await import("child_process");
|
|
16718
|
-
const { join:
|
|
16719
|
-
const { readFileSync:
|
|
17327
|
+
const { join: join57 } = await import("path");
|
|
17328
|
+
const { readFileSync: readFileSync50, existsSync: existsSync57 } = await import("fs");
|
|
16720
17329
|
const { parse } = await import("@iarna/toml");
|
|
16721
|
-
console.log(
|
|
16722
|
-
const configFile =
|
|
17330
|
+
console.log(chalk62.bold("Stopping Panopticon...\n"));
|
|
17331
|
+
const configFile = join57(process.env.HOME || "", ".panopticon", "config.toml");
|
|
16723
17332
|
let traefikEnabled = false;
|
|
16724
17333
|
let dashboardPort = 3010;
|
|
16725
17334
|
let dashboardApiPort = 3011;
|
|
16726
|
-
if (
|
|
17335
|
+
if (existsSync57(configFile)) {
|
|
16727
17336
|
try {
|
|
16728
|
-
const configContent =
|
|
17337
|
+
const configContent = readFileSync50(configFile, "utf-8");
|
|
16729
17338
|
const config2 = parse(configContent);
|
|
16730
17339
|
traefikEnabled = config2.traefik?.enabled === true;
|
|
16731
17340
|
dashboardPort = config2.dashboard?.port || 3010;
|
|
@@ -16733,44 +17342,44 @@ program.command("down").description("Stop dashboard (and Traefik if enabled)").o
|
|
|
16733
17342
|
} catch (error) {
|
|
16734
17343
|
}
|
|
16735
17344
|
}
|
|
16736
|
-
console.log(
|
|
17345
|
+
console.log(chalk62.dim("Stopping dashboard..."));
|
|
16737
17346
|
try {
|
|
16738
17347
|
execSync9(`lsof -ti:${dashboardPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
|
|
16739
17348
|
execSync9(`lsof -ti:${dashboardApiPort} | xargs kill -9 2>/dev/null || true`, { stdio: "pipe" });
|
|
16740
|
-
console.log(
|
|
17349
|
+
console.log(chalk62.green("\u2713 Dashboard stopped"));
|
|
16741
17350
|
} catch {
|
|
16742
|
-
console.log(
|
|
17351
|
+
console.log(chalk62.dim(" No dashboard processes found"));
|
|
16743
17352
|
}
|
|
16744
17353
|
if (traefikEnabled && !options.skipTraefik) {
|
|
16745
|
-
const traefikDir =
|
|
16746
|
-
if (
|
|
16747
|
-
console.log(
|
|
17354
|
+
const traefikDir = join57(process.env.HOME || "", ".panopticon", "traefik");
|
|
17355
|
+
if (existsSync57(traefikDir)) {
|
|
17356
|
+
console.log(chalk62.dim("Stopping Traefik..."));
|
|
16748
17357
|
try {
|
|
16749
17358
|
execSync9("docker compose down", {
|
|
16750
17359
|
cwd: traefikDir,
|
|
16751
17360
|
stdio: "pipe"
|
|
16752
17361
|
});
|
|
16753
|
-
console.log(
|
|
17362
|
+
console.log(chalk62.green("\u2713 Traefik stopped"));
|
|
16754
17363
|
} catch (error) {
|
|
16755
|
-
console.log(
|
|
17364
|
+
console.log(chalk62.yellow("\u26A0 Failed to stop Traefik"));
|
|
16756
17365
|
}
|
|
16757
17366
|
}
|
|
16758
17367
|
}
|
|
16759
17368
|
try {
|
|
16760
17369
|
const { getTldrDaemonService: getTldrDaemonService2 } = await import("../tldr-daemon-T3THOUGT.js");
|
|
16761
|
-
const { exec:
|
|
16762
|
-
const { promisify:
|
|
16763
|
-
const
|
|
17370
|
+
const { exec: exec24 } = await import("child_process");
|
|
17371
|
+
const { promisify: promisify24 } = await import("util");
|
|
17372
|
+
const execAsync24 = promisify24(exec24);
|
|
16764
17373
|
const projectRoot = process.cwd();
|
|
16765
|
-
const venvPath =
|
|
16766
|
-
if (
|
|
16767
|
-
console.log(
|
|
17374
|
+
const venvPath = join57(projectRoot, ".venv");
|
|
17375
|
+
if (existsSync57(venvPath)) {
|
|
17376
|
+
console.log(chalk62.dim("\nStopping TLDR daemon..."));
|
|
16768
17377
|
const tldrService = getTldrDaemonService2(projectRoot, venvPath);
|
|
16769
17378
|
await tldrService.stop();
|
|
16770
|
-
console.log(
|
|
17379
|
+
console.log(chalk62.green("\u2713 TLDR daemon stopped"));
|
|
16771
17380
|
}
|
|
16772
17381
|
} catch (error) {
|
|
16773
|
-
console.log(
|
|
17382
|
+
console.log(chalk62.dim(" (TLDR daemon not running)"));
|
|
16774
17383
|
}
|
|
16775
17384
|
console.log("");
|
|
16776
17385
|
});
|
|
@@ -16782,5 +17391,9 @@ project.command("remove <nameOrPath>").description("Remove a project from the re
|
|
|
16782
17391
|
project.command("init").description("Initialize projects.yaml with example configuration").action(projectInitCommand);
|
|
16783
17392
|
program.command("doctor").description("Check system health and dependencies").action(doctorCommand);
|
|
16784
17393
|
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.
|
|
17394
|
+
program.addCommand(createCostCommand());
|
|
17395
|
+
program.command("sync-costs").description("Import cost events from per-project WAL files (alias for: pan cost sync)").action(async () => {
|
|
17396
|
+
await program.parseAsync(["cost", "sync"], { from: "user" });
|
|
17397
|
+
});
|
|
17398
|
+
await program.parseAsync();
|
|
16786
17399
|
//# sourceMappingURL=index.js.map
|