app-ai-solution-exp 0.1.2
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/bin/app-ai-solution-exp.js +20 -0
- package/out/main/index.js +1631 -0
- package/out/preload/index.js +108 -0
- package/out/renderer/assets/abap-D5KwWAsZ.js +1399 -0
- package/out/renderer/assets/apex-DVGUZ64i.js +331 -0
- package/out/renderer/assets/azcli-BEAhqcuE.js +69 -0
- package/out/renderer/assets/bat-Bqkp9Cfu.js +101 -0
- package/out/renderer/assets/bicep-DIlfshcM.js +110 -0
- package/out/renderer/assets/cameligo-CLaaYNMV.js +175 -0
- package/out/renderer/assets/clojure-fcgFaMHx.js +762 -0
- package/out/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/out/renderer/assets/coffee-CzJ5oEdj.js +233 -0
- package/out/renderer/assets/cpp-CcN6f0ik.js +390 -0
- package/out/renderer/assets/csharp-BJeIuvde.js +327 -0
- package/out/renderer/assets/csp-D_3BK2Wp.js +54 -0
- package/out/renderer/assets/css-i3rI3_64.js +186 -0
- package/out/renderer/assets/css.worker-umuuUiIb.js +53567 -0
- package/out/renderer/assets/cssMode-BlKZF8mS.js +208 -0
- package/out/renderer/assets/cypher-D0--_GAN.js +264 -0
- package/out/renderer/assets/dart-vLMHv35g.js +282 -0
- package/out/renderer/assets/dockerfile--oxj0cAH.js +131 -0
- package/out/renderer/assets/ecl-CeuUgzaZ.js +457 -0
- package/out/renderer/assets/editor.worker-CNgWLVu7.js +13695 -0
- package/out/renderer/assets/elixir-eLfY1jWH.js +570 -0
- package/out/renderer/assets/flow9-ZSTChSMd.js +143 -0
- package/out/renderer/assets/freemarker2-BTb0QKKy.js +995 -0
- package/out/renderer/assets/fsharp-D2uoxuLH.js +218 -0
- package/out/renderer/assets/go-brnMpFrj.js +219 -0
- package/out/renderer/assets/graphql-BeiGgjIU.js +152 -0
- package/out/renderer/assets/handlebars-DKVnkZoO.js +414 -0
- package/out/renderer/assets/hcl-CrX1Es2W.js +184 -0
- package/out/renderer/assets/html-DSHBWNSF.js +303 -0
- package/out/renderer/assets/html.worker-BT47iy49.js +29777 -0
- package/out/renderer/assets/htmlMode-hdSFcfq-.js +224 -0
- package/out/renderer/assets/index-ByP1RhpK.js +309272 -0
- package/out/renderer/assets/index-CgixHIqL.css +11502 -0
- package/out/renderer/assets/ini-BcQysCTb.js +72 -0
- package/out/renderer/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
- package/out/renderer/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
- package/out/renderer/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/out/renderer/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/out/renderer/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/out/renderer/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/out/renderer/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/out/renderer/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/out/renderer/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
- package/out/renderer/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
- package/out/renderer/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/out/renderer/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/out/renderer/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/out/renderer/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/out/renderer/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/out/renderer/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/out/renderer/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
- package/out/renderer/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
- package/out/renderer/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/out/renderer/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/out/renderer/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/out/renderer/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/out/renderer/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/out/renderer/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/out/renderer/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/out/renderer/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/out/renderer/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/out/renderer/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/out/renderer/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/out/renderer/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/out/renderer/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/out/renderer/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/out/renderer/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
- package/out/renderer/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
- package/out/renderer/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/out/renderer/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/out/renderer/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/out/renderer/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/out/renderer/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/out/renderer/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/out/renderer/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
- package/out/renderer/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
- package/out/renderer/assets/java-Dt3iMn2o.js +233 -0
- package/out/renderer/assets/javascript-Ae6t5gfn.js +72 -0
- package/out/renderer/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/out/renderer/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/out/renderer/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/out/renderer/assets/json.worker-D4JVmXIe.js +21424 -0
- package/out/renderer/assets/jsonMode-B3dQSNhP.js +931 -0
- package/out/renderer/assets/julia-Cm3ItYL_.js +512 -0
- package/out/renderer/assets/kotlin-Ddo1SjA5.js +253 -0
- package/out/renderer/assets/less-B7Qaxw-O.js +162 -0
- package/out/renderer/assets/lexon-C1U0m2n9.js +158 -0
- package/out/renderer/assets/liquid-Zgyb8DZ5.js +235 -0
- package/out/renderer/assets/lspLanguageFeatures-CkDn9c2y.js +1841 -0
- package/out/renderer/assets/lua-hNsuGJkO.js +163 -0
- package/out/renderer/assets/m3-6ko6q9-_.js +211 -0
- package/out/renderer/assets/markdown-B0YTnTxW.js +230 -0
- package/out/renderer/assets/mdx-C8e8DFOf.js +159 -0
- package/out/renderer/assets/mips-CJm71dS3.js +199 -0
- package/out/renderer/assets/msdax-BBeIktCY.js +376 -0
- package/out/renderer/assets/mysql-BWiizXSn.js +879 -0
- package/out/renderer/assets/objective-c-B1L1C5EC.js +184 -0
- package/out/renderer/assets/pascal-DMQyD4Xk.js +252 -0
- package/out/renderer/assets/pascaligo-VA_LQ1oU.js +165 -0
- package/out/renderer/assets/perl-DC0Z0tlO.js +627 -0
- package/out/renderer/assets/pgsql-DaSGFTLp.js +852 -0
- package/out/renderer/assets/php-Bkx1qpkQ.js +501 -0
- package/out/renderer/assets/pla-DEV89yYj.js +138 -0
- package/out/renderer/assets/postiats-CVVurEnu.js +908 -0
- package/out/renderer/assets/powerquery-BQ_t1ZiQ.js +891 -0
- package/out/renderer/assets/powershell-BXiKvz7Z.js +240 -0
- package/out/renderer/assets/protobuf-CndvAUGu.js +421 -0
- package/out/renderer/assets/pug-BxCXwerb.js +403 -0
- package/out/renderer/assets/python-8eSQckTG.js +295 -0
- package/out/renderer/assets/qsharp-BWK6YLKm.js +302 -0
- package/out/renderer/assets/r-CtqYUQ6l.js +244 -0
- package/out/renderer/assets/razor-eAkN4Qfv.js +545 -0
- package/out/renderer/assets/redis-O7gSt3oh.js +303 -0
- package/out/renderer/assets/redshift-CvYMMYZY.js +810 -0
- package/out/renderer/assets/restructuredtext-B-KQCVu_.js +175 -0
- package/out/renderer/assets/ruby-DCd4DmAr.js +512 -0
- package/out/renderer/assets/rust-B1c0VCeq.js +344 -0
- package/out/renderer/assets/sb-Chfc_wZF.js +116 -0
- package/out/renderer/assets/scala-DbVzH-3O.js +371 -0
- package/out/renderer/assets/scheme-D7PxodDG.js +109 -0
- package/out/renderer/assets/scss-B42qMyAu.js +261 -0
- package/out/renderer/assets/shell-vZEubQ82.js +222 -0
- package/out/renderer/assets/solidity-yHOxYChb.js +1368 -0
- package/out/renderer/assets/sophia-D7pU0Y1d.js +200 -0
- package/out/renderer/assets/sparql-DxuVdnRl.js +202 -0
- package/out/renderer/assets/sql-BAGepFCR.js +854 -0
- package/out/renderer/assets/st-C-b0Dh53.js +417 -0
- package/out/renderer/assets/swift-BmOZGynf.js +313 -0
- package/out/renderer/assets/systemverilog-BOC0OOdC.js +577 -0
- package/out/renderer/assets/tcl-Bb4GCwBr.js +233 -0
- package/out/renderer/assets/ts.worker-C7hW3aY-.js +225330 -0
- package/out/renderer/assets/tsMode-D7ET908H.js +1265 -0
- package/out/renderer/assets/twig-DvgEGWAV.js +393 -0
- package/out/renderer/assets/typescript-BhqUlrW4.js +337 -0
- package/out/renderer/assets/typespec-R77Ln7Jb.js +128 -0
- package/out/renderer/assets/vb-Bm6ESA0Q.js +373 -0
- package/out/renderer/assets/wgsl-_KPae5vw.js +454 -0
- package/out/renderer/assets/xml-Bg3Ctug6.js +89 -0
- package/out/renderer/assets/yaml-C9aCL4uU.js +200 -0
- package/out/renderer/index.html +13 -0
- package/package.json +110 -0
|
@@ -0,0 +1,1631 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const electron = require("electron");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const child_process = require("child_process");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const nodePty = require("node-pty");
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
const keytar = require("keytar");
|
|
10
|
+
let mainWindow = null;
|
|
11
|
+
function createMainWindow() {
|
|
12
|
+
mainWindow = new electron.BrowserWindow({
|
|
13
|
+
width: 1200,
|
|
14
|
+
height: 800,
|
|
15
|
+
minWidth: 1024,
|
|
16
|
+
minHeight: 600,
|
|
17
|
+
show: false,
|
|
18
|
+
// mostrar apenas quando pronto (evita flash)
|
|
19
|
+
titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "hidden",
|
|
20
|
+
backgroundColor: "#0e0e10",
|
|
21
|
+
// Zinc Dark background — evita flash branco
|
|
22
|
+
webPreferences: {
|
|
23
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
24
|
+
contextIsolation: true,
|
|
25
|
+
// ✅ obrigatório — isola preload do renderer
|
|
26
|
+
nodeIntegration: false,
|
|
27
|
+
// ✅ obrigatório — renderer não acessa Node.js
|
|
28
|
+
sandbox: true,
|
|
29
|
+
// ✅ obrigatório — sandboxing do renderer
|
|
30
|
+
webSecurity: true,
|
|
31
|
+
allowRunningInsecureContent: false
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
mainWindow.webContents.on("before-input-event", (_event, input) => {
|
|
35
|
+
if (!input.control && !input.meta) return;
|
|
36
|
+
const zoom = mainWindow.webContents.getZoomFactor();
|
|
37
|
+
if (input.key === "=" || input.key === "+") {
|
|
38
|
+
mainWindow.webContents.setZoomFactor(Math.min(zoom + 0.1, 3));
|
|
39
|
+
} else if (input.key === "-") {
|
|
40
|
+
mainWindow.webContents.setZoomFactor(Math.max(zoom - 0.1, 0.5));
|
|
41
|
+
} else if (input.key === "0") {
|
|
42
|
+
mainWindow.webContents.setZoomFactor(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
46
|
+
electron.shell.openExternal(url);
|
|
47
|
+
return { action: "deny" };
|
|
48
|
+
});
|
|
49
|
+
mainWindow.once("ready-to-show", () => {
|
|
50
|
+
mainWindow?.show();
|
|
51
|
+
});
|
|
52
|
+
mainWindow.on("closed", () => {
|
|
53
|
+
mainWindow = null;
|
|
54
|
+
});
|
|
55
|
+
mainWindow.on("maximize", () => {
|
|
56
|
+
mainWindow?.webContents.send("window:maximized", true);
|
|
57
|
+
});
|
|
58
|
+
mainWindow.on("unmaximize", () => {
|
|
59
|
+
mainWindow?.webContents.send("window:maximized", false);
|
|
60
|
+
});
|
|
61
|
+
if (process.env["ELECTRON_RENDERER_URL"]) {
|
|
62
|
+
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
|
63
|
+
} else {
|
|
64
|
+
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
|
65
|
+
}
|
|
66
|
+
return mainWindow;
|
|
67
|
+
}
|
|
68
|
+
function getMainWindow() {
|
|
69
|
+
return mainWindow;
|
|
70
|
+
}
|
|
71
|
+
function getCmRoot() {
|
|
72
|
+
return path.join(os.homedir(), ".cm");
|
|
73
|
+
}
|
|
74
|
+
function getProfileDir(profileId) {
|
|
75
|
+
return path.join(getCmRoot(), "profiles", profileId);
|
|
76
|
+
}
|
|
77
|
+
function getHistoryDir(profileId, projectName) {
|
|
78
|
+
return path.join(getCmRoot(), "history", profileId, projectName);
|
|
79
|
+
}
|
|
80
|
+
function getClaudeConfigDir() {
|
|
81
|
+
return path.join(os.homedir(), ".claude");
|
|
82
|
+
}
|
|
83
|
+
function getAgentsDir() {
|
|
84
|
+
return path.join(getClaudeConfigDir(), "agents");
|
|
85
|
+
}
|
|
86
|
+
function getGlobalClaudeMdPath() {
|
|
87
|
+
return path.join(getClaudeConfigDir(), "CLAUDE.md");
|
|
88
|
+
}
|
|
89
|
+
function getClaudeHistoryJsonlPath() {
|
|
90
|
+
return path.join(getClaudeConfigDir(), "history.jsonl");
|
|
91
|
+
}
|
|
92
|
+
function isWindows() {
|
|
93
|
+
return process.platform === "win32";
|
|
94
|
+
}
|
|
95
|
+
function whereExe(name) {
|
|
96
|
+
try {
|
|
97
|
+
const result = child_process.execSync(`where.exe ${name}`, { encoding: "utf8", timeout: 3e3 });
|
|
98
|
+
const first = result.trim().split(/\r?\n/)[0].trim();
|
|
99
|
+
return first && fs.existsSync(first) ? first : null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getClaudeSpawnConfig() {
|
|
105
|
+
if (!isWindows()) {
|
|
106
|
+
return { file: "claude", args: [] };
|
|
107
|
+
}
|
|
108
|
+
const systemRoot = process.env.SystemRoot ?? "C:\\Windows";
|
|
109
|
+
const cmdExe = path.join(systemRoot, "System32", "cmd.exe");
|
|
110
|
+
try {
|
|
111
|
+
const claudeCmdPath = whereExe("claude");
|
|
112
|
+
if (claudeCmdPath) {
|
|
113
|
+
const npmDir = path.dirname(claudeCmdPath);
|
|
114
|
+
const cliJsPath = path.join(
|
|
115
|
+
npmDir,
|
|
116
|
+
"node_modules",
|
|
117
|
+
"@anthropic-ai",
|
|
118
|
+
"claude-code",
|
|
119
|
+
"cli.js"
|
|
120
|
+
);
|
|
121
|
+
if (fs.existsSync(cliJsPath)) {
|
|
122
|
+
const nodePath = whereExe("node");
|
|
123
|
+
if (nodePath) {
|
|
124
|
+
return { file: nodePath, args: [cliJsPath] };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { file: cmdExe, args: ["/c", claudeCmdPath] };
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
return { file: cmdExe, args: ["/k", "claude"] };
|
|
132
|
+
}
|
|
133
|
+
const SUMMARY_TIMEOUT_MS = 3e4;
|
|
134
|
+
function collectGitData(projectPath) {
|
|
135
|
+
const run = (cmd) => {
|
|
136
|
+
try {
|
|
137
|
+
return child_process.execSync(cmd, { cwd: projectPath, encoding: "utf8", timeout: 5e3 }).trim();
|
|
138
|
+
} catch {
|
|
139
|
+
return "";
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
log: run("git log --oneline -8"),
|
|
144
|
+
diffStat: run("git diff --stat HEAD"),
|
|
145
|
+
status: run("git status --short")
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function buildPrompt(devNote, projectName, git) {
|
|
149
|
+
const sections = [
|
|
150
|
+
`Gere um resumo estruturado de sessão de desenvolvimento para o projeto "${projectName}".`,
|
|
151
|
+
"",
|
|
152
|
+
"Use exatamente este formato markdown:",
|
|
153
|
+
"## O que foi feito",
|
|
154
|
+
"## Arquivos modificados",
|
|
155
|
+
"## Decisões tomadas",
|
|
156
|
+
"## Pendências",
|
|
157
|
+
"## Próximos passos",
|
|
158
|
+
""
|
|
159
|
+
];
|
|
160
|
+
if (devNote.trim()) {
|
|
161
|
+
sections.push("### Nota do desenvolvedor", devNote.trim(), "");
|
|
162
|
+
}
|
|
163
|
+
if (git.log) {
|
|
164
|
+
sections.push("### Git log (últimas commits)", "```", git.log, "```", "");
|
|
165
|
+
}
|
|
166
|
+
if (git.diffStat) {
|
|
167
|
+
sections.push("### Arquivos alterados (git diff --stat HEAD)", "```", git.diffStat, "```", "");
|
|
168
|
+
}
|
|
169
|
+
if (git.status) {
|
|
170
|
+
sections.push("### Status atual (git status --short)", "```", git.status, "```", "");
|
|
171
|
+
}
|
|
172
|
+
sections.push("Gere o resumo em português do Brasil. Seja conciso e direto ao ponto.");
|
|
173
|
+
return sections.join("\n");
|
|
174
|
+
}
|
|
175
|
+
function buildFallback(devNote, projectName, git) {
|
|
176
|
+
const lines = [
|
|
177
|
+
`# Resumo de sessão — ${projectName}`,
|
|
178
|
+
`_Gerado automaticamente (sem IA)_`,
|
|
179
|
+
""
|
|
180
|
+
];
|
|
181
|
+
if (devNote.trim()) lines.push("## Nota do desenvolvedor", devNote.trim(), "");
|
|
182
|
+
if (git.log) lines.push("## Git log", "```", git.log, "```", "");
|
|
183
|
+
if (git.diffStat) lines.push("## Arquivos alterados", "```", git.diffStat, "```", "");
|
|
184
|
+
if (git.status) lines.push("## Status", "```", git.status, "```", "");
|
|
185
|
+
if (!devNote.trim() && !git.log && !git.diffStat && !git.status) {
|
|
186
|
+
lines.push("_Nenhuma informação disponível para esta sessão._");
|
|
187
|
+
}
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
function generateSummary(payload) {
|
|
191
|
+
const { profileId, projectName, projectPath, devNote } = payload;
|
|
192
|
+
const profileDir = getProfileDir(profileId);
|
|
193
|
+
const git = collectGitData(projectPath);
|
|
194
|
+
const prompt = buildPrompt(devNote, projectName, git);
|
|
195
|
+
return new Promise((resolve) => {
|
|
196
|
+
const { file, args } = getClaudeSpawnConfig();
|
|
197
|
+
let spawnFile;
|
|
198
|
+
let spawnArgs;
|
|
199
|
+
if (args.length > 0 && args[0] === "/c") {
|
|
200
|
+
spawnFile = file;
|
|
201
|
+
spawnArgs = [...args, "--print", prompt];
|
|
202
|
+
} else if (args.length > 0 && args[0] === "/k") {
|
|
203
|
+
spawnFile = file;
|
|
204
|
+
spawnArgs = ["/c", "claude", "--print", prompt];
|
|
205
|
+
} else if (args.length > 0) {
|
|
206
|
+
spawnFile = file;
|
|
207
|
+
spawnArgs = [...args, "--print", prompt];
|
|
208
|
+
} else {
|
|
209
|
+
spawnFile = file;
|
|
210
|
+
spawnArgs = ["--print", prompt];
|
|
211
|
+
}
|
|
212
|
+
const env = { ...process.env, CLAUDE_CONFIG_DIR: profileDir, NODE_ENV: void 0 };
|
|
213
|
+
let output = "";
|
|
214
|
+
let timedOut = false;
|
|
215
|
+
const child = child_process.spawn(spawnFile, spawnArgs, { env, shell: false });
|
|
216
|
+
const timer = setTimeout(() => {
|
|
217
|
+
timedOut = true;
|
|
218
|
+
child.kill();
|
|
219
|
+
resolve({ success: false, content: buildFallback(devNote, projectName, git), isFallback: true });
|
|
220
|
+
}, SUMMARY_TIMEOUT_MS);
|
|
221
|
+
child.stdout.on("data", (chunk) => {
|
|
222
|
+
output += chunk.toString();
|
|
223
|
+
});
|
|
224
|
+
child.on("close", (code) => {
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
if (timedOut) return;
|
|
227
|
+
const trimmed = output.trim();
|
|
228
|
+
if (code === 0 && trimmed.length > 50) {
|
|
229
|
+
resolve({ success: true, content: trimmed, isFallback: false });
|
|
230
|
+
} else {
|
|
231
|
+
resolve({ success: false, content: buildFallback(devNote, projectName, git), isFallback: true });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
child.on("error", () => {
|
|
235
|
+
clearTimeout(timer);
|
|
236
|
+
if (timedOut) return;
|
|
237
|
+
resolve({ success: false, content: buildFallback(devNote, projectName, git), isFallback: true });
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function checkClaudePath() {
|
|
242
|
+
const command = process.platform === "win32" ? "where claude" : "which claude";
|
|
243
|
+
try {
|
|
244
|
+
const output = child_process.execSync(command, { encoding: "utf8", timeout: 5e3 }).trim();
|
|
245
|
+
const claudePath = output.split("\n")[0].trim();
|
|
246
|
+
if (claudePath) {
|
|
247
|
+
return { found: true, path: claudePath };
|
|
248
|
+
}
|
|
249
|
+
return { found: false, path: null };
|
|
250
|
+
} catch {
|
|
251
|
+
return { found: false, path: null };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function registerClaudeCheckHandlers() {
|
|
255
|
+
electron.ipcMain.handle("claude:checkPath", () => {
|
|
256
|
+
return checkClaudePath();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async function createProfileDirectory(profileId) {
|
|
260
|
+
const dir = getProfileDir(profileId);
|
|
261
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
function profileDirectoryExists(profileId) {
|
|
264
|
+
return fs.existsSync(getProfileDir(profileId));
|
|
265
|
+
}
|
|
266
|
+
function profileHasCredentials(profileId) {
|
|
267
|
+
const credentialsPath = path.join(getProfileDir(profileId), ".credentials.json");
|
|
268
|
+
return fs.existsSync(credentialsPath);
|
|
269
|
+
}
|
|
270
|
+
const MARKER_START = "<!-- CM:CONTEXT:START -->";
|
|
271
|
+
const MARKER_END = "<!-- CM:CONTEXT:END -->";
|
|
272
|
+
function injectContext(projectClaudeMdPath, summaryContent) {
|
|
273
|
+
const block = `${MARKER_START}
|
|
274
|
+
${summaryContent}
|
|
275
|
+
${MARKER_END}
|
|
276
|
+
|
|
277
|
+
`;
|
|
278
|
+
let existing = "";
|
|
279
|
+
const fileExists = fs.existsSync(projectClaudeMdPath);
|
|
280
|
+
if (fileExists) {
|
|
281
|
+
existing = fs.readFileSync(projectClaudeMdPath, "utf8");
|
|
282
|
+
fs.writeFileSync(`${projectClaudeMdPath}.cm-bkp`, existing, "utf8");
|
|
283
|
+
}
|
|
284
|
+
const stripped = removeContextBlock(existing);
|
|
285
|
+
const result = block + stripped;
|
|
286
|
+
fs.mkdirSync(path.dirname(projectClaudeMdPath), { recursive: true });
|
|
287
|
+
fs.writeFileSync(projectClaudeMdPath, result, "utf8");
|
|
288
|
+
}
|
|
289
|
+
function cleanContext(projectClaudeMdPath) {
|
|
290
|
+
if (!fs.existsSync(projectClaudeMdPath)) return;
|
|
291
|
+
const content = fs.readFileSync(projectClaudeMdPath, "utf8");
|
|
292
|
+
const stripped = removeContextBlock(content);
|
|
293
|
+
if (stripped.trim() === "") {
|
|
294
|
+
fs.unlinkSync(projectClaudeMdPath);
|
|
295
|
+
} else {
|
|
296
|
+
fs.writeFileSync(projectClaudeMdPath, stripped, "utf8");
|
|
297
|
+
}
|
|
298
|
+
const backupPath = `${projectClaudeMdPath}.cm-bkp`;
|
|
299
|
+
if (fs.existsSync(backupPath)) {
|
|
300
|
+
try {
|
|
301
|
+
fs.unlinkSync(backupPath);
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function hasContextMarkers(projectClaudeMdPath) {
|
|
307
|
+
if (!fs.existsSync(projectClaudeMdPath)) return false;
|
|
308
|
+
try {
|
|
309
|
+
const content = fs.readFileSync(projectClaudeMdPath, "utf8");
|
|
310
|
+
return content.includes(MARKER_START);
|
|
311
|
+
} catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function removeContextBlock(content) {
|
|
316
|
+
const startIdx = content.indexOf(MARKER_START);
|
|
317
|
+
const endIdx = content.indexOf(MARKER_END);
|
|
318
|
+
if (startIdx === -1 || endIdx === -1) return content;
|
|
319
|
+
const before = content.slice(0, startIdx);
|
|
320
|
+
const after = content.slice(endIdx + MARKER_END.length);
|
|
321
|
+
return (before + after).replace(/^\n\n/, "\n").trimStart();
|
|
322
|
+
}
|
|
323
|
+
function listProjects(profileId) {
|
|
324
|
+
const historyRoot = path.join(getCmRoot(), "history", profileId);
|
|
325
|
+
if (!fs.existsSync(historyRoot)) return [];
|
|
326
|
+
return fs.readdirSync(historyRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
327
|
+
}
|
|
328
|
+
function listSummaries(profileId, projectName) {
|
|
329
|
+
const dir = getHistoryDir(profileId, projectName);
|
|
330
|
+
if (!fs.existsSync(dir)) return [];
|
|
331
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
332
|
+
return files.map((file) => {
|
|
333
|
+
const filePath = path.join(dir, file);
|
|
334
|
+
let preview = "";
|
|
335
|
+
try {
|
|
336
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
337
|
+
preview = content.slice(0, 200);
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
filePath,
|
|
342
|
+
profileId,
|
|
343
|
+
projectName,
|
|
344
|
+
date: file.replace(".md", ""),
|
|
345
|
+
preview
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function readSummary(filePath) {
|
|
350
|
+
try {
|
|
351
|
+
return fs.readFileSync(filePath, "utf8");
|
|
352
|
+
} catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function saveSummary(profileId, projectName, content) {
|
|
357
|
+
const dir = getHistoryDir(profileId, projectName);
|
|
358
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
359
|
+
const now = /* @__PURE__ */ new Date();
|
|
360
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
361
|
+
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}`;
|
|
362
|
+
const filePath = path.join(dir, `${timestamp}.md`);
|
|
363
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
364
|
+
return filePath;
|
|
365
|
+
}
|
|
366
|
+
function readOrphanContent(jsonlPath, maxLines = 50) {
|
|
367
|
+
try {
|
|
368
|
+
const raw = fs.readFileSync(jsonlPath, "utf8");
|
|
369
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
370
|
+
const last = lines.slice(-maxLines);
|
|
371
|
+
const messages = [];
|
|
372
|
+
for (const line of last) {
|
|
373
|
+
try {
|
|
374
|
+
const obj = JSON.parse(line);
|
|
375
|
+
const role = typeof obj.role === "string" ? obj.role : "unknown";
|
|
376
|
+
const content = typeof obj.content === "string" ? obj.content : JSON.stringify(obj.content ?? "");
|
|
377
|
+
const truncated = content.length > 500 ? content.slice(0, 500) + "…" : content;
|
|
378
|
+
messages.push(`[${role}] ${truncated}`);
|
|
379
|
+
} catch {
|
|
380
|
+
messages.push(line.slice(0, 200));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const truncationNotice = lines.length > maxLines ? `
|
|
384
|
+
|
|
385
|
+
_[Exibindo as últimas ${maxLines} de ${lines.length} mensagens]_
|
|
386
|
+
` : "";
|
|
387
|
+
return `${truncationNotice}${messages.join("\n\n")}`;
|
|
388
|
+
} catch {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function detectOrphanSessions(profileId) {
|
|
393
|
+
const projectsRoot = path.join(getProfileDir(profileId), "projects");
|
|
394
|
+
if (!fs.existsSync(projectsRoot)) return [];
|
|
395
|
+
const orphans = [];
|
|
396
|
+
let projectDirs;
|
|
397
|
+
try {
|
|
398
|
+
projectDirs = fs.readdirSync(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
399
|
+
} catch {
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
for (const projectName of projectDirs) {
|
|
403
|
+
const projectDir = path.join(projectsRoot, projectName);
|
|
404
|
+
let jsonlFiles;
|
|
405
|
+
try {
|
|
406
|
+
jsonlFiles = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl"));
|
|
407
|
+
} catch {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const historyDir = getHistoryDir(profileId, projectName);
|
|
411
|
+
const existingSummaries = fs.existsSync(historyDir) ? new Set(fs.readdirSync(historyDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""))) : /* @__PURE__ */ new Set();
|
|
412
|
+
for (const jsonlFile of jsonlFiles) {
|
|
413
|
+
const datePart = jsonlFile.replace(".jsonl", "");
|
|
414
|
+
if (!existingSummaries.has(datePart)) {
|
|
415
|
+
orphans.push({
|
|
416
|
+
profileId,
|
|
417
|
+
projectName,
|
|
418
|
+
jsonlPath: path.join(projectDir, jsonlFile),
|
|
419
|
+
sessionDate: datePart
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return orphans;
|
|
425
|
+
}
|
|
426
|
+
function listNativeSessions() {
|
|
427
|
+
const historyPath = getClaudeHistoryJsonlPath();
|
|
428
|
+
if (!fs.existsSync(historyPath)) return [];
|
|
429
|
+
let raw;
|
|
430
|
+
try {
|
|
431
|
+
raw = fs.readFileSync(historyPath, "utf8");
|
|
432
|
+
} catch {
|
|
433
|
+
return [];
|
|
434
|
+
}
|
|
435
|
+
const sessionsMap = /* @__PURE__ */ new Map();
|
|
436
|
+
for (const line of raw.split("\n").filter((l) => l.trim())) {
|
|
437
|
+
try {
|
|
438
|
+
const obj = JSON.parse(line);
|
|
439
|
+
const sessionId = typeof obj.sessionId === "string" ? obj.sessionId : null;
|
|
440
|
+
const project = typeof obj.project === "string" ? obj.project : null;
|
|
441
|
+
const display = typeof obj.display === "string" ? obj.display : "";
|
|
442
|
+
const timestamp = typeof obj.timestamp === "number" ? obj.timestamp : 0;
|
|
443
|
+
if (!sessionId || !project) continue;
|
|
444
|
+
if (!sessionsMap.has(sessionId)) {
|
|
445
|
+
sessionsMap.set(sessionId, { projectPath: project, messages: [] });
|
|
446
|
+
}
|
|
447
|
+
sessionsMap.get(sessionId).messages.push({ display, timestamp });
|
|
448
|
+
} catch {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const result = [];
|
|
453
|
+
for (const [sessionId, data] of sessionsMap) {
|
|
454
|
+
const projectName = data.projectPath.split(/[\\/]/).pop() ?? data.projectPath;
|
|
455
|
+
const sorted = data.messages.slice().sort((a, b) => a.timestamp - b.timestamp);
|
|
456
|
+
const last = sorted[sorted.length - 1];
|
|
457
|
+
result.push({
|
|
458
|
+
sessionId,
|
|
459
|
+
projectPath: data.projectPath,
|
|
460
|
+
projectName,
|
|
461
|
+
firstMessage: (sorted[0]?.display ?? "").slice(0, 200),
|
|
462
|
+
messageCount: sorted.length,
|
|
463
|
+
lastMessageAt: new Date(last?.timestamp ?? 0).toISOString(),
|
|
464
|
+
messages: sorted
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return result.sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt));
|
|
468
|
+
}
|
|
469
|
+
function formatToolAction(tool) {
|
|
470
|
+
const name = typeof tool.name === "string" ? tool.name : "";
|
|
471
|
+
const input = tool.input;
|
|
472
|
+
const basename = (p) => typeof p === "string" ? p.split(/[\\/]/).pop() ?? p : "";
|
|
473
|
+
switch (name) {
|
|
474
|
+
case "Read":
|
|
475
|
+
return `Read → ${basename(input?.file_path)}`;
|
|
476
|
+
case "Write":
|
|
477
|
+
return `Write → ${basename(input?.file_path)}`;
|
|
478
|
+
case "Edit":
|
|
479
|
+
return `Edit → ${basename(input?.file_path)}`;
|
|
480
|
+
case "Bash": {
|
|
481
|
+
const desc = typeof input?.description === "string" ? input.description : "";
|
|
482
|
+
const cmd = typeof input?.command === "string" ? input.command.slice(0, 80) : "";
|
|
483
|
+
return `Bash → ${desc || cmd}`;
|
|
484
|
+
}
|
|
485
|
+
case "Glob":
|
|
486
|
+
return `Glob → ${typeof input?.pattern === "string" ? input.pattern : ""}`;
|
|
487
|
+
case "Task": {
|
|
488
|
+
const subagent = typeof input?.subagent_type === "string" ? input.subagent_type : "";
|
|
489
|
+
return `Task → ${subagent}`;
|
|
490
|
+
}
|
|
491
|
+
case "TaskOutput":
|
|
492
|
+
return `TaskOutput → aguardou resultado`;
|
|
493
|
+
default:
|
|
494
|
+
return name ? `${name}` : null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const SYSTEM_MESSAGE_PREFIXES = [
|
|
498
|
+
"<task-notification>",
|
|
499
|
+
"<local-command-caveat>",
|
|
500
|
+
"<command-name>",
|
|
501
|
+
"<local-command-stdout>"
|
|
502
|
+
];
|
|
503
|
+
function extractUserAnswers(content) {
|
|
504
|
+
const PREFIX = "User has answered your questions: ";
|
|
505
|
+
if (!content.startsWith(PREFIX)) return null;
|
|
506
|
+
let text = content.slice(PREFIX.length);
|
|
507
|
+
for (const suffix of [". You can now continue", " user notes:", " selected markdown:"]) {
|
|
508
|
+
const idx = text.indexOf(suffix);
|
|
509
|
+
if (idx !== -1) text = text.slice(0, idx);
|
|
510
|
+
}
|
|
511
|
+
const answers = [];
|
|
512
|
+
const regex = /"[^"]*"="([^"]*)"/g;
|
|
513
|
+
let m;
|
|
514
|
+
while ((m = regex.exec(text)) !== null) {
|
|
515
|
+
if (m[1].trim()) answers.push(m[1].trim());
|
|
516
|
+
}
|
|
517
|
+
if (answers.length > 0) return answers.join("\n");
|
|
518
|
+
return text.trim() || null;
|
|
519
|
+
}
|
|
520
|
+
function emptyStats() {
|
|
521
|
+
return {
|
|
522
|
+
totalInputTokens: 0,
|
|
523
|
+
totalOutputTokens: 0,
|
|
524
|
+
totalCacheRead: 0,
|
|
525
|
+
totalCacheCreate: 0,
|
|
526
|
+
totalMessages: 0,
|
|
527
|
+
totalToolCalls: 0,
|
|
528
|
+
durationMs: 0,
|
|
529
|
+
firstTimestamp: "",
|
|
530
|
+
lastTimestamp: "",
|
|
531
|
+
compactionCount: 0,
|
|
532
|
+
compactionTokensIn: 0,
|
|
533
|
+
compactionTokensOut: 0
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function readNativeSessionConversation(sessionId) {
|
|
537
|
+
const projectsDir = path.join(getClaudeConfigDir(), "projects");
|
|
538
|
+
if (!fs.existsSync(projectsDir)) return { messages: [], stats: emptyStats() };
|
|
539
|
+
let jsonlPath = null;
|
|
540
|
+
try {
|
|
541
|
+
for (const proj of fs.readdirSync(projectsDir)) {
|
|
542
|
+
const candidate = path.join(projectsDir, proj, `${sessionId}.jsonl`);
|
|
543
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
544
|
+
jsonlPath = candidate;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch {
|
|
549
|
+
return { messages: [], stats: emptyStats() };
|
|
550
|
+
}
|
|
551
|
+
if (!jsonlPath) return { messages: [], stats: emptyStats() };
|
|
552
|
+
let raw;
|
|
553
|
+
try {
|
|
554
|
+
raw = fs.readFileSync(jsonlPath, "utf8");
|
|
555
|
+
} catch {
|
|
556
|
+
return { messages: [], stats: emptyStats() };
|
|
557
|
+
}
|
|
558
|
+
const result = [];
|
|
559
|
+
let lastToolEntryIdx = -1;
|
|
560
|
+
let lastToolTimestamp = null;
|
|
561
|
+
for (const line of raw.split("\n").filter((l) => l.trim())) {
|
|
562
|
+
try {
|
|
563
|
+
const obj = JSON.parse(line);
|
|
564
|
+
if (obj.isSidechain === true) continue;
|
|
565
|
+
if (obj.type !== "user" && obj.type !== "assistant") continue;
|
|
566
|
+
const msg = obj.message;
|
|
567
|
+
if (!msg) continue;
|
|
568
|
+
const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : "";
|
|
569
|
+
if (obj.type === "user") {
|
|
570
|
+
if (typeof msg.content === "string") {
|
|
571
|
+
const c = msg.content.trim();
|
|
572
|
+
if (!c) continue;
|
|
573
|
+
if (SYSTEM_MESSAGE_PREFIXES.some((p) => c.startsWith(p))) continue;
|
|
574
|
+
result.push({ role: "user", content: c, timestamp });
|
|
575
|
+
} else if (Array.isArray(msg.content)) {
|
|
576
|
+
const items = msg.content;
|
|
577
|
+
const hasToolResult = items.some((item) => item.type === "tool_result");
|
|
578
|
+
if (hasToolResult && lastToolEntryIdx >= 0 && lastToolTimestamp) {
|
|
579
|
+
const startMs = new Date(lastToolTimestamp).getTime();
|
|
580
|
+
const endMs = new Date(timestamp).getTime();
|
|
581
|
+
if (!isNaN(startMs) && !isNaN(endMs) && endMs >= startMs) {
|
|
582
|
+
result[lastToolEntryIdx].durationMs = endMs - startMs;
|
|
583
|
+
}
|
|
584
|
+
lastToolEntryIdx = -1;
|
|
585
|
+
lastToolTimestamp = null;
|
|
586
|
+
}
|
|
587
|
+
for (const item of items) {
|
|
588
|
+
if (item.type === "tool_result" && typeof item.content === "string") {
|
|
589
|
+
const answers = extractUserAnswers(item.content);
|
|
590
|
+
if (answers) {
|
|
591
|
+
result.push({ role: "user", content: answers, timestamp });
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
if (!Array.isArray(msg.content)) continue;
|
|
599
|
+
const items = msg.content;
|
|
600
|
+
const usageRaw = msg.usage;
|
|
601
|
+
const tokens = usageRaw ? {
|
|
602
|
+
input: typeof usageRaw.input_tokens === "number" ? usageRaw.input_tokens : 0,
|
|
603
|
+
output: typeof usageRaw.output_tokens === "number" ? usageRaw.output_tokens : 0,
|
|
604
|
+
cacheRead: typeof usageRaw.cache_read_input_tokens === "number" ? usageRaw.cache_read_input_tokens : 0,
|
|
605
|
+
cacheCreate: typeof usageRaw.cache_creation_input_tokens === "number" ? usageRaw.cache_creation_input_tokens : 0
|
|
606
|
+
} : void 0;
|
|
607
|
+
const parts = [];
|
|
608
|
+
const text = items.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n").trim();
|
|
609
|
+
if (text) parts.push(text);
|
|
610
|
+
const askTool = items.find((c) => c.type === "tool_use" && c.name === "AskUserQuestion");
|
|
611
|
+
if (askTool) {
|
|
612
|
+
const input = askTool.input;
|
|
613
|
+
const questions = Array.isArray(input.questions) ? input.questions : [];
|
|
614
|
+
const qLines = questions.filter((q) => typeof q.question === "string").map((q, i) => `${i + 1}. ${q.question}`);
|
|
615
|
+
if (qLines.length > 0) {
|
|
616
|
+
parts.push(`Perguntas:
|
|
617
|
+
${qLines.join("\n")}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const combined = parts.join("\n\n").trim();
|
|
621
|
+
if (combined) {
|
|
622
|
+
result.push({ role: "assistant", content: combined, timestamp, tokens });
|
|
623
|
+
}
|
|
624
|
+
const toolActions = items.filter((c) => c.type === "tool_use" && c.name !== "AskUserQuestion").map((c) => formatToolAction(c)).filter((line2) => line2 !== null && line2.trim() !== "");
|
|
625
|
+
if (toolActions.length > 0) {
|
|
626
|
+
result.push({ role: "assistant", content: toolActions.join("\n"), timestamp, kind: "tool" });
|
|
627
|
+
lastToolEntryIdx = result.length - 1;
|
|
628
|
+
lastToolTimestamp = timestamp;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
} catch {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
result.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
636
|
+
const stats = emptyStats();
|
|
637
|
+
for (const msg of result) {
|
|
638
|
+
if (msg.tokens) {
|
|
639
|
+
stats.totalInputTokens += msg.tokens.input;
|
|
640
|
+
stats.totalOutputTokens += msg.tokens.output;
|
|
641
|
+
stats.totalCacheRead += msg.tokens.cacheRead;
|
|
642
|
+
stats.totalCacheCreate += msg.tokens.cacheCreate;
|
|
643
|
+
}
|
|
644
|
+
if (msg.kind === "tool") stats.totalToolCalls++;
|
|
645
|
+
else stats.totalMessages++;
|
|
646
|
+
}
|
|
647
|
+
const timestamps = result.map((m) => m.timestamp).filter(Boolean);
|
|
648
|
+
if (timestamps.length >= 2) {
|
|
649
|
+
const first = new Date(timestamps[0]).getTime();
|
|
650
|
+
const last = new Date(timestamps[timestamps.length - 1]).getTime();
|
|
651
|
+
stats.durationMs = Math.max(0, last - first);
|
|
652
|
+
stats.firstTimestamp = timestamps[0];
|
|
653
|
+
stats.lastTimestamp = timestamps[timestamps.length - 1];
|
|
654
|
+
}
|
|
655
|
+
const jsonlDir = path.dirname(jsonlPath);
|
|
656
|
+
let compactionFiles;
|
|
657
|
+
try {
|
|
658
|
+
compactionFiles = fs.readdirSync(jsonlDir).filter((f) => typeof f === "string" && f.startsWith("agent-acompact-") && f.endsWith(".jsonl"));
|
|
659
|
+
} catch {
|
|
660
|
+
compactionFiles = [];
|
|
661
|
+
}
|
|
662
|
+
for (const compFile of compactionFiles) {
|
|
663
|
+
try {
|
|
664
|
+
const compRaw = fs.readFileSync(path.join(jsonlDir, compFile), "utf8");
|
|
665
|
+
for (const line of compRaw.split("\n").filter((l) => l.trim())) {
|
|
666
|
+
try {
|
|
667
|
+
const obj = JSON.parse(line);
|
|
668
|
+
if (obj.type !== "assistant") continue;
|
|
669
|
+
if (obj.sessionId !== sessionId) continue;
|
|
670
|
+
const msg = obj.message;
|
|
671
|
+
if (!msg?.usage) continue;
|
|
672
|
+
const usage = msg.usage;
|
|
673
|
+
stats.compactionTokensIn += typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0;
|
|
674
|
+
stats.compactionTokensOut += typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0;
|
|
675
|
+
stats.compactionCount++;
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return { messages: result, stats };
|
|
683
|
+
}
|
|
684
|
+
function aggregateTokens() {
|
|
685
|
+
const profilesRoot = path.join(getCmRoot(), "profiles");
|
|
686
|
+
if (!fs.existsSync(profilesRoot)) return [];
|
|
687
|
+
const entries = [];
|
|
688
|
+
let profileDirs;
|
|
689
|
+
try {
|
|
690
|
+
profileDirs = fs.readdirSync(profilesRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
691
|
+
} catch {
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
for (const profileId of profileDirs) {
|
|
695
|
+
const projectsRoot = path.join(profilesRoot, profileId, "projects");
|
|
696
|
+
if (!fs.existsSync(projectsRoot)) continue;
|
|
697
|
+
let projectDirs;
|
|
698
|
+
try {
|
|
699
|
+
projectDirs = fs.readdirSync(projectsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
700
|
+
} catch {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
for (const projectName of projectDirs) {
|
|
704
|
+
const projectDir = path.join(projectsRoot, projectName);
|
|
705
|
+
let jsonlFiles;
|
|
706
|
+
try {
|
|
707
|
+
jsonlFiles = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl"));
|
|
708
|
+
} catch {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
for (const jsonlFile of jsonlFiles) {
|
|
712
|
+
const datePart = jsonlFile.replace(".jsonl", "").split("_")[0] ?? jsonlFile.replace(".jsonl", "");
|
|
713
|
+
entries.push(parseJsonlUsage(path.join(projectDir, jsonlFile), profileId, projectName, datePart));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return entries;
|
|
718
|
+
}
|
|
719
|
+
function parseJsonlUsage(filePath, profileId, projectName, date) {
|
|
720
|
+
let inputTokens = 0;
|
|
721
|
+
let outputTokens = 0;
|
|
722
|
+
let model = null;
|
|
723
|
+
let unavailable = true;
|
|
724
|
+
try {
|
|
725
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
726
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
727
|
+
for (const line of lines) {
|
|
728
|
+
try {
|
|
729
|
+
const obj = JSON.parse(line);
|
|
730
|
+
const msg = obj.message;
|
|
731
|
+
if (msg?.usage && typeof msg.usage === "object") {
|
|
732
|
+
const usage = msg.usage;
|
|
733
|
+
inputTokens += typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
734
|
+
outputTokens += typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
735
|
+
if (typeof msg.model === "string" && msg.model) model = msg.model;
|
|
736
|
+
unavailable = false;
|
|
737
|
+
} else if (obj.usage && typeof obj.usage === "object") {
|
|
738
|
+
const usage = obj.usage;
|
|
739
|
+
inputTokens += typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
740
|
+
outputTokens += typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
741
|
+
if (typeof obj.model === "string" && obj.model) model = obj.model;
|
|
742
|
+
unavailable = false;
|
|
743
|
+
}
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
}
|
|
749
|
+
return { profileId, projectName, date, inputTokens, outputTokens, model, unavailable };
|
|
750
|
+
}
|
|
751
|
+
const TIMEOUT_MS = 15e3;
|
|
752
|
+
const MONTH_MAP = {
|
|
753
|
+
Jan: "jan",
|
|
754
|
+
Feb: "fev",
|
|
755
|
+
Mar: "mar",
|
|
756
|
+
Apr: "abr",
|
|
757
|
+
May: "mai",
|
|
758
|
+
Jun: "jun",
|
|
759
|
+
Jul: "jul",
|
|
760
|
+
Aug: "ago",
|
|
761
|
+
Sep: "set",
|
|
762
|
+
Oct: "out",
|
|
763
|
+
Nov: "nov",
|
|
764
|
+
Dec: "dez"
|
|
765
|
+
};
|
|
766
|
+
function stripAnsi(str) {
|
|
767
|
+
return str.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, "");
|
|
768
|
+
}
|
|
769
|
+
function formatTime(timeStr) {
|
|
770
|
+
const withMinutes = /^(\d+):(\d+)(am|pm)$/i.exec(timeStr);
|
|
771
|
+
if (withMinutes) {
|
|
772
|
+
let h = parseInt(withMinutes[1], 10);
|
|
773
|
+
const m = withMinutes[2];
|
|
774
|
+
const period = withMinutes[3].toLowerCase();
|
|
775
|
+
if (period === "pm" && h !== 12) h += 12;
|
|
776
|
+
if (period === "am" && h === 12) h = 0;
|
|
777
|
+
return `${h.toString().padStart(2, "0")}:${m}`;
|
|
778
|
+
}
|
|
779
|
+
const hourOnly = /^(\d+)(am|pm)$/i.exec(timeStr);
|
|
780
|
+
if (hourOnly) {
|
|
781
|
+
let h = parseInt(hourOnly[1], 10);
|
|
782
|
+
const period = hourOnly[2].toLowerCase();
|
|
783
|
+
if (period === "pm" && h !== 12) h += 12;
|
|
784
|
+
if (period === "am" && h === 12) h = 0;
|
|
785
|
+
return `${h.toString().padStart(2, "0")}:00`;
|
|
786
|
+
}
|
|
787
|
+
return timeStr;
|
|
788
|
+
}
|
|
789
|
+
function formatResetLabel(raw) {
|
|
790
|
+
const stripped = raw.replace(/\(.*?\)/g, "").trim();
|
|
791
|
+
const weekdays = ["dom.", "seg.", "ter.", "qua.", "qui.", "sex.", "sáb."];
|
|
792
|
+
const slashMatch = /rese[a-z]*\s*(\d+)\/(\d+),\s*(\S+)/i.exec(stripped);
|
|
793
|
+
if (slashMatch) {
|
|
794
|
+
const part1 = parseInt(slashMatch[1], 10);
|
|
795
|
+
const part2 = parseInt(slashMatch[2], 10);
|
|
796
|
+
let monthIndex = -1;
|
|
797
|
+
let day = -1;
|
|
798
|
+
if (part1 > 12) {
|
|
799
|
+
day = part1;
|
|
800
|
+
monthIndex = part2 - 1;
|
|
801
|
+
} else if (part2 > 12) {
|
|
802
|
+
day = part2;
|
|
803
|
+
monthIndex = part1 - 1;
|
|
804
|
+
} else {
|
|
805
|
+
day = part1;
|
|
806
|
+
monthIndex = part2 - 1;
|
|
807
|
+
}
|
|
808
|
+
const time = formatTime(slashMatch[3]);
|
|
809
|
+
if (monthIndex >= 0 && monthIndex <= 11) {
|
|
810
|
+
const date = /* @__PURE__ */ new Date();
|
|
811
|
+
const currentYear = date.getFullYear();
|
|
812
|
+
date.setFullYear(currentYear, monthIndex, day);
|
|
813
|
+
if (date.getTime() < Date.now() - 30 * 24 * 60 * 60 * 1e3) {
|
|
814
|
+
date.setFullYear(currentYear + 1);
|
|
815
|
+
}
|
|
816
|
+
return `Reinicia ${weekdays[date.getDay()]}, ${time}`;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
const dateMatch = /rese[a-z]*\s*([a-z0-9]+)\s*(\d+),\s*(\S+)/i.exec(stripped);
|
|
820
|
+
if (dateMatch) {
|
|
821
|
+
const monthStr = dateMatch[1].substring(0, 3).toLowerCase();
|
|
822
|
+
const isNumericMonth = !isNaN(parseInt(monthStr, 10));
|
|
823
|
+
let monthIndex = -1;
|
|
824
|
+
if (isNumericMonth) {
|
|
825
|
+
monthIndex = parseInt(monthStr, 10) - 1;
|
|
826
|
+
} else {
|
|
827
|
+
monthIndex = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"].indexOf(monthStr);
|
|
828
|
+
if (monthIndex === -1) {
|
|
829
|
+
monthIndex = ["jan", "fev", "mar", "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez"].indexOf(monthStr);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const day = parseInt(dateMatch[2], 10);
|
|
833
|
+
const time = formatTime(dateMatch[3]);
|
|
834
|
+
if (monthIndex >= 0 && monthIndex <= 11) {
|
|
835
|
+
const date = /* @__PURE__ */ new Date();
|
|
836
|
+
const currentYear = date.getFullYear();
|
|
837
|
+
date.setFullYear(currentYear, monthIndex, day);
|
|
838
|
+
if (date.getTime() < Date.now() - 30 * 24 * 60 * 60 * 1e3) {
|
|
839
|
+
date.setFullYear(currentYear + 1);
|
|
840
|
+
}
|
|
841
|
+
return `Reinicia ${weekdays[date.getDay()]}, ${time}`;
|
|
842
|
+
}
|
|
843
|
+
const monthFallback = MONTH_MAP[dateMatch[1]] ?? dateMatch[1].toLowerCase();
|
|
844
|
+
return `Reinicia em ${day}/${monthFallback} às ${time}`;
|
|
845
|
+
}
|
|
846
|
+
const timeMatch = /rese[a-z]*\s*(\S+)/i.exec(stripped);
|
|
847
|
+
if (timeMatch) {
|
|
848
|
+
return `Reinicia ${weekdays[(/* @__PURE__ */ new Date()).getDay()]}, ${formatTime(timeMatch[1])}`;
|
|
849
|
+
}
|
|
850
|
+
return raw;
|
|
851
|
+
}
|
|
852
|
+
function preTrustHomeDir(profileDir) {
|
|
853
|
+
const claudeJsonPath = path.join(profileDir, ".claude.json");
|
|
854
|
+
const homeDir = os.homedir().replace(/\\/g, "/");
|
|
855
|
+
try {
|
|
856
|
+
let config = {};
|
|
857
|
+
if (fs.existsSync(claudeJsonPath)) {
|
|
858
|
+
config = JSON.parse(fs.readFileSync(claudeJsonPath, "utf8"));
|
|
859
|
+
}
|
|
860
|
+
if (typeof config.projects !== "object" || config.projects === null) {
|
|
861
|
+
config.projects = {};
|
|
862
|
+
}
|
|
863
|
+
const projects = config.projects;
|
|
864
|
+
if (typeof projects[homeDir] !== "object" || projects[homeDir] === null) {
|
|
865
|
+
projects[homeDir] = {};
|
|
866
|
+
}
|
|
867
|
+
const proj = projects[homeDir];
|
|
868
|
+
if (proj.hasTrustDialogAccepted !== true) {
|
|
869
|
+
proj.hasTrustDialogAccepted = true;
|
|
870
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2), "utf8");
|
|
871
|
+
}
|
|
872
|
+
if (!config.theme) {
|
|
873
|
+
config.theme = "dark";
|
|
874
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2), "utf8");
|
|
875
|
+
}
|
|
876
|
+
} catch (err) {
|
|
877
|
+
console.warn("[quota-service] preTrustHomeDir failed:", err);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function preDisableInteractiveContext(profileDir) {
|
|
881
|
+
const claudeJsonPath = path.join(profileDir, ".claude.json");
|
|
882
|
+
try {
|
|
883
|
+
if (!fs.existsSync(claudeJsonPath)) return false;
|
|
884
|
+
const config = JSON.parse(fs.readFileSync(claudeJsonPath, "utf8"));
|
|
885
|
+
const features = config.cachedGrowthBookFeatures;
|
|
886
|
+
if (features?.tengu_oboe !== true) return false;
|
|
887
|
+
features.tengu_oboe = false;
|
|
888
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2), "utf8");
|
|
889
|
+
return true;
|
|
890
|
+
} catch (err) {
|
|
891
|
+
console.warn("[quota-service] preDisableInteractiveContext failed:", err);
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function restoreInteractiveContext(profileDir) {
|
|
896
|
+
const claudeJsonPath = path.join(profileDir, ".claude.json");
|
|
897
|
+
try {
|
|
898
|
+
const config = JSON.parse(fs.readFileSync(claudeJsonPath, "utf8"));
|
|
899
|
+
const features = config.cachedGrowthBookFeatures;
|
|
900
|
+
if (!features) return;
|
|
901
|
+
features.tengu_oboe = true;
|
|
902
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2), "utf8");
|
|
903
|
+
} catch (err) {
|
|
904
|
+
console.warn("[quota-service] restoreInteractiveContext failed:", err);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function parseOutput$1(raw) {
|
|
908
|
+
const clean = stripAnsi(raw);
|
|
909
|
+
const sessionPct = /current\s*session[\s\S]*?(\d+)%\s*used/i.exec(clean);
|
|
910
|
+
const sessionReset = /current\s*session[\s\S]*?(rese[a-z]*\s*.*?\([^)]+\))/i.exec(clean);
|
|
911
|
+
const weekPct = /current\s*week[\s\S]*?(\d+)%\s*used/i.exec(clean);
|
|
912
|
+
const weekReset = /current\s*week[\s\S]*?(rese[a-z]*\s*.*?\([^)]+\))/i.exec(clean);
|
|
913
|
+
if (sessionPct && weekPct) {
|
|
914
|
+
return {
|
|
915
|
+
session: {
|
|
916
|
+
percent: parseInt(sessionPct[1], 10),
|
|
917
|
+
resetLabel: sessionReset ? formatResetLabel(sessionReset[1]) : ""
|
|
918
|
+
},
|
|
919
|
+
week: {
|
|
920
|
+
percent: parseInt(weekPct[1], 10),
|
|
921
|
+
resetLabel: weekReset ? formatResetLabel(weekReset[1]) : ""
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
const rateLimitMatch = /you.{0,40}limit[\s\S]{0,400}?(rese[a-z]*\s*\w+\s*\d+,\s*\S+\s*\()/i.exec(clean);
|
|
926
|
+
if (rateLimitMatch) {
|
|
927
|
+
const rawMatch = rateLimitMatch[1];
|
|
928
|
+
const dateMatch = /rese[a-z]*\s*(\w+)\s*(\d+),\s*(\S+)\s*\(/i.exec(rawMatch);
|
|
929
|
+
if (dateMatch) {
|
|
930
|
+
const resetLabel = formatResetLabel(`Resets ${dateMatch[1]} ${dateMatch[2]}, ${dateMatch[3]}`);
|
|
931
|
+
return {
|
|
932
|
+
session: { percent: 100, resetLabel },
|
|
933
|
+
week: { percent: 100, resetLabel }
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
let ptyMutex = Promise.resolve();
|
|
940
|
+
async function withPtyLock(fn) {
|
|
941
|
+
const oldLock = ptyMutex;
|
|
942
|
+
let release = () => {
|
|
943
|
+
};
|
|
944
|
+
ptyMutex = new Promise((resolve) => {
|
|
945
|
+
release = resolve;
|
|
946
|
+
});
|
|
947
|
+
await oldLock;
|
|
948
|
+
try {
|
|
949
|
+
return await fn();
|
|
950
|
+
} finally {
|
|
951
|
+
setTimeout(release, 1e3);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function getQuota(profileId) {
|
|
955
|
+
return withPtyLock(() => new Promise((resolve) => {
|
|
956
|
+
const profileDir = getProfileDir(profileId);
|
|
957
|
+
preTrustHomeDir(profileDir);
|
|
958
|
+
const contextWasDisabled = preDisableInteractiveContext(profileDir);
|
|
959
|
+
const { file, args } = getClaudeSpawnConfig();
|
|
960
|
+
let spawnFile;
|
|
961
|
+
let spawnArgs;
|
|
962
|
+
if (args.length > 0 && args[0] === "/c") {
|
|
963
|
+
spawnFile = file;
|
|
964
|
+
spawnArgs = [...args];
|
|
965
|
+
} else if (args.length > 0 && args[0] === "/k") {
|
|
966
|
+
spawnFile = file;
|
|
967
|
+
spawnArgs = ["/c", "claude"];
|
|
968
|
+
} else if (args.length > 0) {
|
|
969
|
+
spawnFile = file;
|
|
970
|
+
spawnArgs = [...args];
|
|
971
|
+
} else {
|
|
972
|
+
spawnFile = file;
|
|
973
|
+
spawnArgs = [];
|
|
974
|
+
}
|
|
975
|
+
const env = { ...process.env, CLAUDE_CONFIG_DIR: profileDir, NODE_ENV: void 0, CI: "true" };
|
|
976
|
+
let output = "";
|
|
977
|
+
let done = false;
|
|
978
|
+
let trustConfirmed = false;
|
|
979
|
+
let escSent = false;
|
|
980
|
+
let usageSent = false;
|
|
981
|
+
const finish = (_reason) => {
|
|
982
|
+
if (!done) {
|
|
983
|
+
done = true;
|
|
984
|
+
clearTimeout(timer);
|
|
985
|
+
try {
|
|
986
|
+
term.kill();
|
|
987
|
+
} catch {
|
|
988
|
+
}
|
|
989
|
+
if (contextWasDisabled) restoreInteractiveContext(profileDir);
|
|
990
|
+
const parsed = parseOutput$1(output);
|
|
991
|
+
resolve(parsed);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
const term = nodePty.spawn(spawnFile, spawnArgs, {
|
|
995
|
+
name: "xterm-color",
|
|
996
|
+
cols: 120,
|
|
997
|
+
rows: 30,
|
|
998
|
+
cwd: os.homedir(),
|
|
999
|
+
env
|
|
1000
|
+
});
|
|
1001
|
+
const timer = setTimeout(() => finish("timeout"), TIMEOUT_MS);
|
|
1002
|
+
term.onData((data) => {
|
|
1003
|
+
output += data;
|
|
1004
|
+
const clean = stripAnsi(output);
|
|
1005
|
+
const cleanLower = clean.toLowerCase();
|
|
1006
|
+
if (!trustConfirmed && cleanLower.includes("trustthisfolder")) {
|
|
1007
|
+
trustConfirmed = true;
|
|
1008
|
+
setTimeout(() => {
|
|
1009
|
+
try {
|
|
1010
|
+
term.write("1\r");
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}, 150);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (!escSent && !done && (clean.includes("❯") || clean.includes("?")) && !cleanLower.includes("what do you want")) {
|
|
1017
|
+
if (!escSent && !usageSent) {
|
|
1018
|
+
usageSent = true;
|
|
1019
|
+
setTimeout(() => {
|
|
1020
|
+
try {
|
|
1021
|
+
term.write("/usage");
|
|
1022
|
+
setTimeout(() => {
|
|
1023
|
+
try {
|
|
1024
|
+
term.write("\r");
|
|
1025
|
+
setTimeout(() => {
|
|
1026
|
+
try {
|
|
1027
|
+
term.write("\r");
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
}, 500);
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
1033
|
+
}, 500);
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
}, 1500);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (usageSent && !escSent && !done) {
|
|
1040
|
+
if (parseOutput$1(clean) !== null) {
|
|
1041
|
+
escSent = true;
|
|
1042
|
+
setTimeout(() => {
|
|
1043
|
+
try {
|
|
1044
|
+
term.write("\x1B");
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
setTimeout(() => {
|
|
1048
|
+
finish("success");
|
|
1049
|
+
}, 300);
|
|
1050
|
+
}, 200);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (!escSent && !done && /entertoselect/i.test(cleanLower) && /esctocancel/i.test(cleanLower)) {
|
|
1054
|
+
escSent = true;
|
|
1055
|
+
setTimeout(() => {
|
|
1056
|
+
try {
|
|
1057
|
+
term.write("\x1B");
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}, 150);
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
term.onExit(() => {
|
|
1064
|
+
finish("exit");
|
|
1065
|
+
});
|
|
1066
|
+
}));
|
|
1067
|
+
}
|
|
1068
|
+
function getEmail(profileId) {
|
|
1069
|
+
const profileDir = getProfileDir(profileId);
|
|
1070
|
+
const claudeJsonPath = path.join(profileDir, ".claude.json");
|
|
1071
|
+
try {
|
|
1072
|
+
if (fs.existsSync(claudeJsonPath)) {
|
|
1073
|
+
const config = JSON.parse(fs.readFileSync(claudeJsonPath, "utf8"));
|
|
1074
|
+
if (config.email) {
|
|
1075
|
+
return Promise.resolve(config.email);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
} catch (e) {
|
|
1079
|
+
console.warn("[getEmail] Erro ao ler cache", e);
|
|
1080
|
+
}
|
|
1081
|
+
return withPtyLock(() => new Promise((resolve) => {
|
|
1082
|
+
preTrustHomeDir(profileDir);
|
|
1083
|
+
const contextWasDisabled = preDisableInteractiveContext(profileDir);
|
|
1084
|
+
const { file, args } = getClaudeSpawnConfig();
|
|
1085
|
+
let spawnFile = file;
|
|
1086
|
+
let spawnArgs = args.length > 0 && args[0] === "/k" ? ["/c", "claude"] : [...args];
|
|
1087
|
+
if (spawnArgs.length === 0) spawnArgs = [];
|
|
1088
|
+
const env = { ...process.env, CLAUDE_CONFIG_DIR: profileDir, NODE_ENV: void 0, CI: "true" };
|
|
1089
|
+
let output = "";
|
|
1090
|
+
let done = false;
|
|
1091
|
+
let trustConfirmed = false;
|
|
1092
|
+
let escSent = false;
|
|
1093
|
+
let statusSent = false;
|
|
1094
|
+
const term = nodePty.spawn(spawnFile, spawnArgs, {
|
|
1095
|
+
name: "xterm-color",
|
|
1096
|
+
cols: 120,
|
|
1097
|
+
rows: 30,
|
|
1098
|
+
cwd: os.homedir(),
|
|
1099
|
+
env
|
|
1100
|
+
});
|
|
1101
|
+
const finish = (_reason) => {
|
|
1102
|
+
if (!done) {
|
|
1103
|
+
done = true;
|
|
1104
|
+
clearTimeout(timer);
|
|
1105
|
+
try {
|
|
1106
|
+
term.kill();
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
if (contextWasDisabled) restoreInteractiveContext(profileDir);
|
|
1110
|
+
const clean = stripAnsi(output);
|
|
1111
|
+
const emailMatch = /Email:\s*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(?:Model)?/i.exec(clean);
|
|
1112
|
+
let email = emailMatch ? emailMatch[1].trim() : null;
|
|
1113
|
+
if (email && email.toLowerCase().endsWith("model")) {
|
|
1114
|
+
email = email.slice(0, -5).trim();
|
|
1115
|
+
}
|
|
1116
|
+
if (email) {
|
|
1117
|
+
try {
|
|
1118
|
+
let config = {};
|
|
1119
|
+
if (fs.existsSync(claudeJsonPath)) {
|
|
1120
|
+
config = JSON.parse(fs.readFileSync(claudeJsonPath, "utf8"));
|
|
1121
|
+
}
|
|
1122
|
+
config.email = email;
|
|
1123
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(config, null, 2), "utf8");
|
|
1124
|
+
} catch (e) {
|
|
1125
|
+
console.warn("[getEmail] Erro ao salvar cache", e);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
resolve(email);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
const timer = setTimeout(() => finish("timeout"), TIMEOUT_MS);
|
|
1132
|
+
term.onData((data) => {
|
|
1133
|
+
if (done) return;
|
|
1134
|
+
output += data;
|
|
1135
|
+
const clean = stripAnsi(output);
|
|
1136
|
+
const cleanLower = clean.toLowerCase();
|
|
1137
|
+
if (!trustConfirmed && cleanLower.includes("trustthisfolder")) {
|
|
1138
|
+
trustConfirmed = true;
|
|
1139
|
+
setTimeout(() => {
|
|
1140
|
+
try {
|
|
1141
|
+
term.write("1\r");
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}, 150);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (!escSent && !done && (clean.includes("❯") || clean.includes("?")) && !cleanLower.includes("what do you want")) {
|
|
1148
|
+
if (!escSent && !statusSent) {
|
|
1149
|
+
statusSent = true;
|
|
1150
|
+
setTimeout(() => {
|
|
1151
|
+
try {
|
|
1152
|
+
term.write("/status");
|
|
1153
|
+
setTimeout(() => {
|
|
1154
|
+
try {
|
|
1155
|
+
term.write("\r");
|
|
1156
|
+
setTimeout(() => {
|
|
1157
|
+
try {
|
|
1158
|
+
term.write("\r");
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
}, 500);
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}, 500);
|
|
1165
|
+
} catch {
|
|
1166
|
+
}
|
|
1167
|
+
}, 1500);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (statusSent && !escSent && !done) {
|
|
1171
|
+
if (clean.includes("Login method:") || clean.includes("Email:")) {
|
|
1172
|
+
escSent = true;
|
|
1173
|
+
setTimeout(() => {
|
|
1174
|
+
try {
|
|
1175
|
+
term.write("\x1B");
|
|
1176
|
+
} catch {
|
|
1177
|
+
}
|
|
1178
|
+
setTimeout(() => finish("success"), 300);
|
|
1179
|
+
}, 500);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
}));
|
|
1184
|
+
}
|
|
1185
|
+
function isValidAgentFilename(filename) {
|
|
1186
|
+
return /^[a-z0-9_-]+\.md$/.test(filename);
|
|
1187
|
+
}
|
|
1188
|
+
function registerFilesystemHandlers() {
|
|
1189
|
+
electron.ipcMain.handle("profile:createDirectory", async (_event, profileId) => {
|
|
1190
|
+
if (typeof profileId !== "string" || !profileId.trim()) {
|
|
1191
|
+
throw new Error("profileId inválido");
|
|
1192
|
+
}
|
|
1193
|
+
await createProfileDirectory(profileId);
|
|
1194
|
+
return { success: true };
|
|
1195
|
+
});
|
|
1196
|
+
electron.ipcMain.handle("profile:directoryExists", (_event, profileId) => {
|
|
1197
|
+
if (typeof profileId !== "string" || !profileId.trim()) return false;
|
|
1198
|
+
return profileDirectoryExists(profileId);
|
|
1199
|
+
});
|
|
1200
|
+
electron.ipcMain.handle("profile:hasCredentials", (_event, profileId) => {
|
|
1201
|
+
if (typeof profileId !== "string" || !profileId.trim()) return false;
|
|
1202
|
+
return profileHasCredentials(profileId);
|
|
1203
|
+
});
|
|
1204
|
+
electron.ipcMain.handle("fs:injectContext", (_event, claudeMdPath, summaryContent) => {
|
|
1205
|
+
if (typeof claudeMdPath !== "string" || !claudeMdPath.trim()) {
|
|
1206
|
+
throw new Error("claudeMdPath inválido");
|
|
1207
|
+
}
|
|
1208
|
+
if (typeof summaryContent !== "string") {
|
|
1209
|
+
throw new Error("summaryContent inválido");
|
|
1210
|
+
}
|
|
1211
|
+
injectContext(claudeMdPath, summaryContent);
|
|
1212
|
+
return { success: true };
|
|
1213
|
+
});
|
|
1214
|
+
electron.ipcMain.handle("fs:cleanContext", (_event, claudeMdPath) => {
|
|
1215
|
+
if (typeof claudeMdPath !== "string" || !claudeMdPath.trim()) return { success: false };
|
|
1216
|
+
try {
|
|
1217
|
+
cleanContext(claudeMdPath);
|
|
1218
|
+
return { success: true };
|
|
1219
|
+
} catch {
|
|
1220
|
+
return { success: false };
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
electron.ipcMain.handle("fs:hasContextMarkers", (_event, claudeMdPath) => {
|
|
1224
|
+
if (typeof claudeMdPath !== "string" || !claudeMdPath.trim()) return false;
|
|
1225
|
+
return hasContextMarkers(claudeMdPath);
|
|
1226
|
+
});
|
|
1227
|
+
electron.ipcMain.handle("fs:readAgents", async () => {
|
|
1228
|
+
const agentsDir = getAgentsDir();
|
|
1229
|
+
if (!fs.existsSync(agentsDir)) return [];
|
|
1230
|
+
const files = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
|
|
1231
|
+
return files.map((filename) => {
|
|
1232
|
+
const filePath = path.join(agentsDir, filename);
|
|
1233
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1234
|
+
const preview = content.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
1235
|
+
return { filename, preview: preview.slice(0, 120) };
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
electron.ipcMain.handle("fs:readAgent", (_event, filename) => {
|
|
1239
|
+
if (typeof filename !== "string" || !isValidAgentFilename(filename)) return null;
|
|
1240
|
+
const filePath = path.join(getAgentsDir(), filename);
|
|
1241
|
+
if (!fs.existsSync(filePath)) return null;
|
|
1242
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
1243
|
+
});
|
|
1244
|
+
electron.ipcMain.handle("fs:writeAgent", (_event, filename, content, overwrite) => {
|
|
1245
|
+
if (typeof filename !== "string" || !isValidAgentFilename(filename)) {
|
|
1246
|
+
throw new Error("Nome de arquivo inválido. Use apenas letras minúsculas, números, hífens e underscores.");
|
|
1247
|
+
}
|
|
1248
|
+
if (typeof content !== "string") {
|
|
1249
|
+
throw new Error("Conteúdo inválido");
|
|
1250
|
+
}
|
|
1251
|
+
const agentsDir = getAgentsDir();
|
|
1252
|
+
const filePath = path.join(agentsDir, filename);
|
|
1253
|
+
if (!overwrite && fs.existsSync(filePath)) {
|
|
1254
|
+
throw new Error(`Já existe um agent com este nome: ${filename}`);
|
|
1255
|
+
}
|
|
1256
|
+
if (!fs.existsSync(agentsDir)) {
|
|
1257
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
1258
|
+
}
|
|
1259
|
+
try {
|
|
1260
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
1261
|
+
} catch (err) {
|
|
1262
|
+
const nodeErr = err;
|
|
1263
|
+
if (nodeErr.code === "EACCES") {
|
|
1264
|
+
throw new Error(`Sem permissão para escrever em ${filePath}. Verifique as permissões do arquivo.`);
|
|
1265
|
+
}
|
|
1266
|
+
throw err;
|
|
1267
|
+
}
|
|
1268
|
+
return { success: true };
|
|
1269
|
+
});
|
|
1270
|
+
electron.ipcMain.handle("fs:deleteAgent", (_event, filename) => {
|
|
1271
|
+
if (typeof filename !== "string" || !isValidAgentFilename(filename)) {
|
|
1272
|
+
throw new Error("Nome de arquivo inválido");
|
|
1273
|
+
}
|
|
1274
|
+
const filePath = path.join(getAgentsDir(), filename);
|
|
1275
|
+
if (!fs.existsSync(filePath)) {
|
|
1276
|
+
throw new Error(`Agent não encontrado: ${filename}`);
|
|
1277
|
+
}
|
|
1278
|
+
try {
|
|
1279
|
+
fs.unlinkSync(filePath);
|
|
1280
|
+
} catch (err) {
|
|
1281
|
+
const nodeErr = err;
|
|
1282
|
+
if (nodeErr.code === "EACCES") {
|
|
1283
|
+
throw new Error(`Sem permissão para deletar ${filePath}. Verifique as permissões do arquivo.`);
|
|
1284
|
+
}
|
|
1285
|
+
throw err;
|
|
1286
|
+
}
|
|
1287
|
+
return { success: true };
|
|
1288
|
+
});
|
|
1289
|
+
electron.ipcMain.handle("fs:readClaudeMd", () => {
|
|
1290
|
+
const mdPath = getGlobalClaudeMdPath();
|
|
1291
|
+
if (!fs.existsSync(mdPath)) return { exists: false, content: "" };
|
|
1292
|
+
const content = fs.readFileSync(mdPath, "utf-8");
|
|
1293
|
+
return { exists: true, content };
|
|
1294
|
+
});
|
|
1295
|
+
electron.ipcMain.handle("fs:writeClaudeMd", (_event, content) => {
|
|
1296
|
+
if (typeof content !== "string") {
|
|
1297
|
+
throw new Error("Conteúdo inválido");
|
|
1298
|
+
}
|
|
1299
|
+
const mdPath = getGlobalClaudeMdPath();
|
|
1300
|
+
try {
|
|
1301
|
+
fs.writeFileSync(mdPath, content, "utf-8");
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
const nodeErr = err;
|
|
1304
|
+
if (nodeErr.code === "EACCES") {
|
|
1305
|
+
throw new Error(`Sem permissão para escrever em ${mdPath}. Verifique as permissões do arquivo.`);
|
|
1306
|
+
}
|
|
1307
|
+
throw err;
|
|
1308
|
+
}
|
|
1309
|
+
return { success: true };
|
|
1310
|
+
});
|
|
1311
|
+
electron.ipcMain.handle("usage:aggregate", () => {
|
|
1312
|
+
return aggregateTokens();
|
|
1313
|
+
});
|
|
1314
|
+
electron.ipcMain.handle("quota:get", async (_event, profileId) => {
|
|
1315
|
+
if (typeof profileId !== "string" || !profileId.trim()) return null;
|
|
1316
|
+
return getQuota(profileId);
|
|
1317
|
+
});
|
|
1318
|
+
electron.ipcMain.handle("quota:getEmail", async (_event, profileId) => {
|
|
1319
|
+
if (typeof profileId !== "string" || !profileId.trim()) return null;
|
|
1320
|
+
return getEmail(profileId);
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
const CONTEXT_PERCENT_RE = /(\d+)%\s+of\s+context\s+window\s+used/i;
|
|
1324
|
+
const CONTEXT_TOKENS_RE = /context\s+window[:\s]+(\d+)\s*\/\s*(\d+)\s*tokens/i;
|
|
1325
|
+
const CONTEXT_SLASH_CMD_RE = /\d+[a-zA-Z]?\/\d+[a-zA-Z]?\s+tokens\s+\(([\d.]+)%\)/i;
|
|
1326
|
+
const RATE_LIMIT_PATTERNS = [
|
|
1327
|
+
/rate\s+limit/i,
|
|
1328
|
+
/api\s+rate\s+limit/i,
|
|
1329
|
+
/429/,
|
|
1330
|
+
/you\s+have\s+reached\s+your\s+daily\s+limit/i,
|
|
1331
|
+
/usage\s+limit/i,
|
|
1332
|
+
/too\s+many\s+requests/i
|
|
1333
|
+
];
|
|
1334
|
+
function parseOutput(output, sessionId, win, onContextPercent) {
|
|
1335
|
+
const percentMatch = CONTEXT_PERCENT_RE.exec(output);
|
|
1336
|
+
if (percentMatch) {
|
|
1337
|
+
const percent = Math.min(100, Math.max(0, parseInt(percentMatch[1], 10)));
|
|
1338
|
+
win.webContents.send("monitor:contextUsage", { sessionId, percent });
|
|
1339
|
+
onContextPercent?.(sessionId, percent);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const tokensMatch = CONTEXT_TOKENS_RE.exec(output);
|
|
1343
|
+
if (tokensMatch) {
|
|
1344
|
+
const used = parseInt(tokensMatch[1], 10);
|
|
1345
|
+
const total = parseInt(tokensMatch[2], 10);
|
|
1346
|
+
if (total > 0) {
|
|
1347
|
+
const percent = Math.min(100, Math.round(used / total * 100));
|
|
1348
|
+
win.webContents.send("monitor:contextUsage", { sessionId, percent });
|
|
1349
|
+
onContextPercent?.(sessionId, percent);
|
|
1350
|
+
}
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const slashMatch = CONTEXT_SLASH_CMD_RE.exec(output);
|
|
1354
|
+
if (slashMatch) {
|
|
1355
|
+
const percent = Math.min(100, Math.max(0, Math.round(parseFloat(slashMatch[1]))));
|
|
1356
|
+
win.webContents.send("monitor:contextUsage", { sessionId, percent });
|
|
1357
|
+
onContextPercent?.(sessionId, percent);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
for (const pattern of RATE_LIMIT_PATTERNS) {
|
|
1361
|
+
if (pattern.test(output)) {
|
|
1362
|
+
win.webContents.send("monitor:rateLimit", { sessionId });
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
const MODEL_IDS = {
|
|
1368
|
+
opus: "claude-opus-4-6",
|
|
1369
|
+
sonnet: "claude-sonnet-4-6",
|
|
1370
|
+
haiku: "claude-haiku-4-5-20251001"
|
|
1371
|
+
};
|
|
1372
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1373
|
+
function detectProject(cwd) {
|
|
1374
|
+
try {
|
|
1375
|
+
const toplevel = child_process.execSync("git rev-parse --show-toplevel", {
|
|
1376
|
+
cwd,
|
|
1377
|
+
encoding: "utf8",
|
|
1378
|
+
timeout: 3e3
|
|
1379
|
+
}).trim();
|
|
1380
|
+
return { projectPath: toplevel, projectName: path.basename(toplevel) };
|
|
1381
|
+
} catch {
|
|
1382
|
+
return { projectPath: cwd, projectName: path.basename(cwd) };
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function startSession(payload) {
|
|
1386
|
+
const { profileId, profileName, workingDirectory, model = "sonnet" } = payload;
|
|
1387
|
+
const existingEntry = Array.from(sessions.values()).find(
|
|
1388
|
+
(e) => e.session.profileId === profileId
|
|
1389
|
+
);
|
|
1390
|
+
if (existingEntry) return existingEntry.session;
|
|
1391
|
+
const sessionId = crypto.randomUUID();
|
|
1392
|
+
const profileDir = getProfileDir(profileId);
|
|
1393
|
+
const { projectPath, projectName } = detectProject(workingDirectory || os.homedir());
|
|
1394
|
+
const { file, args } = getClaudeSpawnConfig();
|
|
1395
|
+
const env = {
|
|
1396
|
+
...process.env,
|
|
1397
|
+
CLAUDE_CONFIG_DIR: profileDir,
|
|
1398
|
+
ANTHROPIC_MODEL: MODEL_IDS[model],
|
|
1399
|
+
// Remove variáveis que podem interferir com o output do Claude Code
|
|
1400
|
+
NODE_ENV: void 0
|
|
1401
|
+
};
|
|
1402
|
+
const ptyProcess = nodePty.spawn(file, args, {
|
|
1403
|
+
name: "xterm-color",
|
|
1404
|
+
cols: 80,
|
|
1405
|
+
rows: 24,
|
|
1406
|
+
cwd: projectPath,
|
|
1407
|
+
env
|
|
1408
|
+
});
|
|
1409
|
+
const session = {
|
|
1410
|
+
id: sessionId,
|
|
1411
|
+
profileId,
|
|
1412
|
+
profileName,
|
|
1413
|
+
projectPath,
|
|
1414
|
+
projectName,
|
|
1415
|
+
pid: ptyProcess.pid ?? null,
|
|
1416
|
+
model,
|
|
1417
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1418
|
+
contextPercent: 0,
|
|
1419
|
+
status: "connecting"
|
|
1420
|
+
};
|
|
1421
|
+
ptyProcess.onData((data) => {
|
|
1422
|
+
const win = getMainWindow();
|
|
1423
|
+
if (win) {
|
|
1424
|
+
win.webContents.send("terminal:output", { sessionId, data });
|
|
1425
|
+
parseOutput(data, sessionId, win, updateContextPercent);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
ptyProcess.onExit(() => {
|
|
1429
|
+
const entry = sessions.get(sessionId);
|
|
1430
|
+
if (entry) {
|
|
1431
|
+
entry.session.status = "ended";
|
|
1432
|
+
sessions.delete(sessionId);
|
|
1433
|
+
}
|
|
1434
|
+
const win = getMainWindow();
|
|
1435
|
+
win?.webContents.send("session:ended", { sessionId });
|
|
1436
|
+
});
|
|
1437
|
+
session.status = "active";
|
|
1438
|
+
sessions.set(sessionId, { session, pty: ptyProcess });
|
|
1439
|
+
return session;
|
|
1440
|
+
}
|
|
1441
|
+
function killSession(sessionId) {
|
|
1442
|
+
const entry = sessions.get(sessionId);
|
|
1443
|
+
if (!entry) return false;
|
|
1444
|
+
try {
|
|
1445
|
+
entry.pty.kill();
|
|
1446
|
+
} catch {
|
|
1447
|
+
}
|
|
1448
|
+
sessions.delete(sessionId);
|
|
1449
|
+
return true;
|
|
1450
|
+
}
|
|
1451
|
+
function listSessions() {
|
|
1452
|
+
return Array.from(sessions.values()).map((e) => e.session);
|
|
1453
|
+
}
|
|
1454
|
+
function writeToSession(sessionId, data) {
|
|
1455
|
+
const entry = sessions.get(sessionId);
|
|
1456
|
+
if (!entry) return false;
|
|
1457
|
+
entry.pty.write(data);
|
|
1458
|
+
return true;
|
|
1459
|
+
}
|
|
1460
|
+
function resizeSession(sessionId, cols, rows) {
|
|
1461
|
+
const entry = sessions.get(sessionId);
|
|
1462
|
+
if (!entry) return false;
|
|
1463
|
+
entry.pty.resize(cols, rows);
|
|
1464
|
+
return true;
|
|
1465
|
+
}
|
|
1466
|
+
function updateContextPercent(sessionId, percent) {
|
|
1467
|
+
const entry = sessions.get(sessionId);
|
|
1468
|
+
if (entry) entry.session.contextPercent = percent;
|
|
1469
|
+
}
|
|
1470
|
+
function registerSessionHandlers() {
|
|
1471
|
+
electron.ipcMain.handle("session:start", (_event, payload) => {
|
|
1472
|
+
return startSession(payload);
|
|
1473
|
+
});
|
|
1474
|
+
electron.ipcMain.handle("session:kill", (_event, sessionId) => {
|
|
1475
|
+
return killSession(sessionId);
|
|
1476
|
+
});
|
|
1477
|
+
electron.ipcMain.handle("session:list", () => {
|
|
1478
|
+
return listSessions();
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
function registerTerminalHandlers() {
|
|
1482
|
+
electron.ipcMain.on("terminal:input", (_event, sessionId, data) => {
|
|
1483
|
+
writeToSession(sessionId, data);
|
|
1484
|
+
});
|
|
1485
|
+
electron.ipcMain.on("terminal:resize", (_event, sessionId, cols, rows) => {
|
|
1486
|
+
resizeSession(sessionId, cols, rows);
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
const SERVICE = "ai-solution-exp";
|
|
1490
|
+
async function setToken(profileId, token) {
|
|
1491
|
+
await keytar.setPassword(SERVICE, profileId, token);
|
|
1492
|
+
}
|
|
1493
|
+
async function getToken(profileId) {
|
|
1494
|
+
return keytar.getPassword(SERVICE, profileId);
|
|
1495
|
+
}
|
|
1496
|
+
async function deleteToken(profileId) {
|
|
1497
|
+
return keytar.deletePassword(SERVICE, profileId);
|
|
1498
|
+
}
|
|
1499
|
+
function registerKeychainHandlers() {
|
|
1500
|
+
electron.ipcMain.handle("keychain:set", (_event, profileId, token) => {
|
|
1501
|
+
return setToken(profileId, token);
|
|
1502
|
+
});
|
|
1503
|
+
electron.ipcMain.handle("keychain:get", (_event, profileId) => {
|
|
1504
|
+
return getToken(profileId);
|
|
1505
|
+
});
|
|
1506
|
+
electron.ipcMain.handle("keychain:delete", (_event, profileId) => {
|
|
1507
|
+
return deleteToken(profileId);
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
function registerHistoryHandlers() {
|
|
1511
|
+
electron.ipcMain.handle("history:listProjects", (_event, profileId) => {
|
|
1512
|
+
if (typeof profileId !== "string" || !profileId.trim()) return [];
|
|
1513
|
+
return listProjects(profileId);
|
|
1514
|
+
});
|
|
1515
|
+
electron.ipcMain.handle("history:listSummaries", (_event, profileId, projectName) => {
|
|
1516
|
+
if (typeof profileId !== "string" || !profileId.trim()) return [];
|
|
1517
|
+
if (typeof projectName !== "string" || !projectName.trim()) return [];
|
|
1518
|
+
return listSummaries(profileId, projectName);
|
|
1519
|
+
});
|
|
1520
|
+
electron.ipcMain.handle("history:readSummary", (_event, filePath) => {
|
|
1521
|
+
if (typeof filePath !== "string" || !filePath.trim()) return null;
|
|
1522
|
+
return readSummary(filePath);
|
|
1523
|
+
});
|
|
1524
|
+
electron.ipcMain.handle(
|
|
1525
|
+
"history:saveSummary",
|
|
1526
|
+
(_event, profileId, projectName, content) => {
|
|
1527
|
+
if (typeof profileId !== "string" || !profileId.trim()) throw new Error("profileId inválido");
|
|
1528
|
+
if (typeof projectName !== "string" || !projectName.trim())
|
|
1529
|
+
throw new Error("projectName inválido");
|
|
1530
|
+
if (typeof content !== "string") throw new Error("content inválido");
|
|
1531
|
+
return saveSummary(profileId, projectName, content);
|
|
1532
|
+
}
|
|
1533
|
+
);
|
|
1534
|
+
electron.ipcMain.handle("history:detectOrphan", (_event, profileId) => {
|
|
1535
|
+
if (typeof profileId !== "string" || !profileId.trim()) return [];
|
|
1536
|
+
return detectOrphanSessions(profileId);
|
|
1537
|
+
});
|
|
1538
|
+
electron.ipcMain.handle("history:readOrphanContent", (_event, jsonlPath) => {
|
|
1539
|
+
if (typeof jsonlPath !== "string" || !jsonlPath.trim()) return null;
|
|
1540
|
+
return readOrphanContent(jsonlPath);
|
|
1541
|
+
});
|
|
1542
|
+
electron.ipcMain.handle("history:listNativeSessions", () => {
|
|
1543
|
+
return listNativeSessions();
|
|
1544
|
+
});
|
|
1545
|
+
electron.ipcMain.handle("history:readNativeSessionConversation", (_event, sessionId) => {
|
|
1546
|
+
if (typeof sessionId !== "string" || !sessionId.trim()) return [];
|
|
1547
|
+
return readNativeSessionConversation(sessionId);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
function registerSummaryHandlers() {
|
|
1551
|
+
electron.ipcMain.handle("summary:generate", async (_event, payload) => {
|
|
1552
|
+
const p = payload;
|
|
1553
|
+
if (typeof p?.profileId !== "string" || !p.profileId.trim()) {
|
|
1554
|
+
throw new Error("profileId inválido");
|
|
1555
|
+
}
|
|
1556
|
+
if (typeof p?.projectName !== "string" || !p.projectName.trim()) {
|
|
1557
|
+
throw new Error("projectName inválido");
|
|
1558
|
+
}
|
|
1559
|
+
if (typeof p?.projectPath !== "string") {
|
|
1560
|
+
throw new Error("projectPath inválido");
|
|
1561
|
+
}
|
|
1562
|
+
return generateSummary(p);
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
function registerWindowHandlers() {
|
|
1566
|
+
electron.ipcMain.on("window:minimize", () => {
|
|
1567
|
+
getMainWindow()?.minimize();
|
|
1568
|
+
});
|
|
1569
|
+
electron.ipcMain.on("window:maximize", () => {
|
|
1570
|
+
const win = getMainWindow();
|
|
1571
|
+
if (!win) return;
|
|
1572
|
+
if (win.isMaximized()) {
|
|
1573
|
+
win.unmaximize();
|
|
1574
|
+
} else {
|
|
1575
|
+
win.maximize();
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
electron.ipcMain.on("window:close", () => {
|
|
1579
|
+
getMainWindow()?.close();
|
|
1580
|
+
});
|
|
1581
|
+
electron.ipcMain.handle("window:isMaximized", () => {
|
|
1582
|
+
return getMainWindow()?.isMaximized() ?? false;
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
function registerIpcHandlers() {
|
|
1586
|
+
electron.ipcMain.handle("ping", () => "pong");
|
|
1587
|
+
registerWindowHandlers();
|
|
1588
|
+
registerClaudeCheckHandlers();
|
|
1589
|
+
registerFilesystemHandlers();
|
|
1590
|
+
registerSessionHandlers();
|
|
1591
|
+
registerTerminalHandlers();
|
|
1592
|
+
registerKeychainHandlers();
|
|
1593
|
+
registerHistoryHandlers();
|
|
1594
|
+
registerSummaryHandlers();
|
|
1595
|
+
}
|
|
1596
|
+
const gotTheLock = electron.app.requestSingleInstanceLock();
|
|
1597
|
+
if (!gotTheLock) {
|
|
1598
|
+
electron.app.quit();
|
|
1599
|
+
} else {
|
|
1600
|
+
electron.app.on("second-instance", () => {
|
|
1601
|
+
const win = getMainWindow();
|
|
1602
|
+
if (win) {
|
|
1603
|
+
if (win.isMinimized()) win.restore();
|
|
1604
|
+
win.focus();
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
electron.app.whenReady().then(() => {
|
|
1608
|
+
if (!process.env["ELECTRON_RENDERER_URL"]) {
|
|
1609
|
+
electron.session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
|
1610
|
+
callback({
|
|
1611
|
+
responseHeaders: {
|
|
1612
|
+
...details.responseHeaders,
|
|
1613
|
+
"Content-Security-Policy": [
|
|
1614
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' wss: https:; font-src 'self' data:;"
|
|
1615
|
+
]
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
registerIpcHandlers();
|
|
1621
|
+
createMainWindow();
|
|
1622
|
+
});
|
|
1623
|
+
electron.app.on("window-all-closed", () => {
|
|
1624
|
+
if (process.platform !== "darwin") electron.app.quit();
|
|
1625
|
+
});
|
|
1626
|
+
electron.app.on("activate", () => {
|
|
1627
|
+
if (electron.BrowserWindow.getAllWindows().length === 0) createMainWindow();
|
|
1628
|
+
});
|
|
1629
|
+
electron.app.on("before-quit", () => {
|
|
1630
|
+
});
|
|
1631
|
+
}
|