@phenx-inc/ctlsurf 0.16.1 → 0.17.1
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/ctlsurf-worker.js +39 -6
- package/out/headless/index.mjs +549 -116
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +416 -38
- package/out/preload/index.js +3 -0
- package/out/renderer/assets/{cssMode-DwHD6JSn.js → cssMode-BtQxI2Eb.js} +3 -3
- package/out/renderer/assets/{freemarker2-Na9-bXYw.js → freemarker2-DRW5dmY5.js} +1 -1
- package/out/renderer/assets/{handlebars-D7gzKLS8.js → handlebars-CFLF3cP7.js} +1 -1
- package/out/renderer/assets/{html-DDK7xN27.js → html-DtnBOkwp.js} +1 -1
- package/out/renderer/assets/{htmlMode-BfbLv-NB.js → htmlMode-DMVysDCg.js} +3 -3
- package/out/renderer/assets/{index-DID1LlMU.css → index-CEjwKjtd.css} +45 -0
- package/out/renderer/assets/{index-HoDaBul3.js → index-Dm70hk5s.js} +252 -26
- package/out/renderer/assets/{javascript-DJjWOxID.js → javascript-ChlyA1sf.js} +2 -2
- package/out/renderer/assets/{jsonMode-BileWbig.js → jsonMode-D1_oHzeN.js} +3 -3
- package/out/renderer/assets/{liquid-DvNU_dI9.js → liquid-D4ftdy1G.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-Bb27EY8c.js → lspLanguageFeatures-Ckqu5ubG.js} +1 -1
- package/out/renderer/assets/{mdx-CLkNPVgD.js → mdx-B9wsVYSC.js} +1 -1
- package/out/renderer/assets/{python-DOJdL4kC.js → python-e23tD6IS.js} +1 -1
- package/out/renderer/assets/{razor-CSr8UlJc.js → razor-CrDyXj3d.js} +1 -1
- package/out/renderer/assets/{tsMode-sbsAvk21.js → tsMode-BwGC5o4O.js} +1 -1
- package/out/renderer/assets/{typescript-r5QB4yRW.js → typescript-Do-98d_O.js} +1 -1
- package/out/renderer/assets/{xml-CYhKh3Pi.js → xml-Dpdu5R3m.js} +1 -1
- package/out/renderer/assets/{yaml-B8DsoaYW.js → yaml-B0Zt7LCJ.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/scripts/extract-electron.js +105 -0
- package/src/main/headless.ts +12 -0
- package/src/main/index.ts +8 -0
- package/src/main/monitor.ts +540 -0
- package/src/main/orchestrator.ts +10 -0
- package/src/main/pty.ts +5 -0
- package/src/main/tui.ts +37 -3
- package/src/preload/index.ts +4 -0
- package/src/renderer/App.tsx +9 -1
- package/src/renderer/components/Resources.tsx +217 -0
- package/src/renderer/components/StatusBar.tsx +6 -1
- package/src/renderer/styles.css +45 -0
package/out/headless/index.mjs
CHANGED
|
@@ -6386,15 +6386,15 @@ function redactSensitive(body) {
|
|
|
6386
6386
|
}
|
|
6387
6387
|
return null;
|
|
6388
6388
|
}
|
|
6389
|
-
async function checkCredentialsFileSafety(
|
|
6389
|
+
async function checkCredentialsFileSafety(path13, onWarn = (m) => console.warn(`anthropic-sdk: ${m}`)) {
|
|
6390
6390
|
if (typeof process === "undefined" || process.platform === "win32")
|
|
6391
6391
|
return;
|
|
6392
|
-
const
|
|
6393
|
-
let resolved =
|
|
6392
|
+
const fs9 = await import("node:fs");
|
|
6393
|
+
let resolved = path13;
|
|
6394
6394
|
let st;
|
|
6395
6395
|
try {
|
|
6396
|
-
resolved = await
|
|
6397
|
-
st = await
|
|
6396
|
+
resolved = await fs9.promises.realpath(path13);
|
|
6397
|
+
st = await fs9.promises.stat(resolved);
|
|
6398
6398
|
} catch {
|
|
6399
6399
|
return;
|
|
6400
6400
|
}
|
|
@@ -6410,27 +6410,27 @@ async function checkCredentialsFileSafety(path12, onWarn = (m) => console.warn(`
|
|
|
6410
6410
|
}
|
|
6411
6411
|
}
|
|
6412
6412
|
async function writeCredentialsFileAtomic(targetPath, data) {
|
|
6413
|
-
const
|
|
6414
|
-
const
|
|
6415
|
-
const dir =
|
|
6416
|
-
await
|
|
6413
|
+
const fs9 = await import("node:fs");
|
|
6414
|
+
const path13 = await import("node:path");
|
|
6415
|
+
const dir = path13.dirname(targetPath);
|
|
6416
|
+
await fs9.promises.mkdir(dir, { recursive: true, mode: 448 });
|
|
6417
6417
|
const tmpPath = `${targetPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6418
6418
|
try {
|
|
6419
|
-
const fh = await
|
|
6419
|
+
const fh = await fs9.promises.open(tmpPath, "w", 384);
|
|
6420
6420
|
try {
|
|
6421
6421
|
await fh.writeFile(JSON.stringify(data, null, 2));
|
|
6422
6422
|
await fh.sync();
|
|
6423
6423
|
} finally {
|
|
6424
6424
|
await fh.close();
|
|
6425
6425
|
}
|
|
6426
|
-
await
|
|
6426
|
+
await fs9.promises.rename(tmpPath, targetPath);
|
|
6427
6427
|
} catch (err) {
|
|
6428
|
-
await
|
|
6428
|
+
await fs9.promises.unlink(tmpPath).catch(() => {
|
|
6429
6429
|
});
|
|
6430
6430
|
throw err;
|
|
6431
6431
|
}
|
|
6432
6432
|
try {
|
|
6433
|
-
const dirFh = await
|
|
6433
|
+
const dirFh = await fs9.promises.open(dir, "r");
|
|
6434
6434
|
try {
|
|
6435
6435
|
await dirFh.sync();
|
|
6436
6436
|
} finally {
|
|
@@ -6794,12 +6794,12 @@ var init_credentials = __esm({
|
|
|
6794
6794
|
return null;
|
|
6795
6795
|
}
|
|
6796
6796
|
validateProfileName(profileName);
|
|
6797
|
-
const
|
|
6798
|
-
const
|
|
6799
|
-
const configPath =
|
|
6797
|
+
const fs9 = await import("node:fs");
|
|
6798
|
+
const path13 = await import("node:path");
|
|
6799
|
+
const configPath = path13.join(rootConfigPath, "configs", `${profileName}.json`);
|
|
6800
6800
|
let configRaw;
|
|
6801
6801
|
try {
|
|
6802
|
-
configRaw = await
|
|
6802
|
+
configRaw = await fs9.promises.readFile(configPath, "utf-8");
|
|
6803
6803
|
} catch (err) {
|
|
6804
6804
|
if (err?.code !== "ENOENT") {
|
|
6805
6805
|
throw new Error(`failed to read config file ${configPath}: ${err}`);
|
|
@@ -6880,37 +6880,37 @@ var init_credentials = __esm({
|
|
|
6880
6880
|
return null;
|
|
6881
6881
|
}
|
|
6882
6882
|
validateProfileName(profileName);
|
|
6883
|
-
const
|
|
6884
|
-
return
|
|
6883
|
+
const path13 = await import("node:path");
|
|
6884
|
+
return path13.join(rootConfigPath, "credentials", `${profileName}.json`);
|
|
6885
6885
|
};
|
|
6886
6886
|
getRootConfigPath = async () => {
|
|
6887
6887
|
if (!supportsLocalConfigFiles()) {
|
|
6888
6888
|
return null;
|
|
6889
6889
|
}
|
|
6890
|
-
const
|
|
6890
|
+
const path13 = await import("node:path");
|
|
6891
6891
|
const configDir = readEnv("ANTHROPIC_CONFIG_DIR");
|
|
6892
6892
|
if (configDir) {
|
|
6893
6893
|
return configDir;
|
|
6894
6894
|
}
|
|
6895
|
-
const
|
|
6896
|
-
if (
|
|
6895
|
+
const os7 = getPlatformHeaders()["X-Stainless-OS"];
|
|
6896
|
+
if (os7 === "Windows") {
|
|
6897
6897
|
const appData = readEnv("APPDATA");
|
|
6898
6898
|
if (appData) {
|
|
6899
|
-
return
|
|
6899
|
+
return path13.join(appData, "Anthropic");
|
|
6900
6900
|
}
|
|
6901
6901
|
const userProfile = readEnv("USERPROFILE");
|
|
6902
6902
|
if (userProfile) {
|
|
6903
|
-
return
|
|
6903
|
+
return path13.join(userProfile, "AppData", "Roaming", "Anthropic");
|
|
6904
6904
|
}
|
|
6905
6905
|
return null;
|
|
6906
6906
|
}
|
|
6907
6907
|
const xdgConfigHome = readEnv("XDG_CONFIG_HOME");
|
|
6908
6908
|
if (xdgConfigHome) {
|
|
6909
|
-
return
|
|
6909
|
+
return path13.join(xdgConfigHome, "anthropic");
|
|
6910
6910
|
}
|
|
6911
6911
|
const home = readEnv("HOME");
|
|
6912
6912
|
if (home) {
|
|
6913
|
-
return
|
|
6913
|
+
return path13.join(home, ".config", "anthropic");
|
|
6914
6914
|
}
|
|
6915
6915
|
return null;
|
|
6916
6916
|
};
|
|
@@ -6927,11 +6927,11 @@ var init_credentials = __esm({
|
|
|
6927
6927
|
if (profileName) {
|
|
6928
6928
|
return profileName;
|
|
6929
6929
|
}
|
|
6930
|
-
const
|
|
6931
|
-
const
|
|
6932
|
-
const filePath =
|
|
6930
|
+
const fs9 = await import("node:fs");
|
|
6931
|
+
const path13 = await import("node:path");
|
|
6932
|
+
const filePath = path13.join(rootConfigPath, "active_config");
|
|
6933
6933
|
try {
|
|
6934
|
-
return (await
|
|
6934
|
+
return (await fs9.promises.readFile(filePath, "utf-8")).trim() || "default";
|
|
6935
6935
|
} catch (err) {
|
|
6936
6936
|
if (err?.code !== "ENOENT") {
|
|
6937
6937
|
throw new Error(`failed to read ${filePath}: ${err}`);
|
|
@@ -6943,21 +6943,21 @@ var init_credentials = __esm({
|
|
|
6943
6943
|
});
|
|
6944
6944
|
|
|
6945
6945
|
// node_modules/@anthropic-ai/sdk/lib/credentials/identity-token.mjs
|
|
6946
|
-
function identityTokenFromFile(
|
|
6947
|
-
if (!
|
|
6946
|
+
function identityTokenFromFile(path13) {
|
|
6947
|
+
if (!path13) {
|
|
6948
6948
|
throw new AnthropicError("Identity token file path is empty");
|
|
6949
6949
|
}
|
|
6950
6950
|
return async () => {
|
|
6951
|
-
const
|
|
6951
|
+
const fs9 = await import("node:fs");
|
|
6952
6952
|
let content;
|
|
6953
6953
|
try {
|
|
6954
|
-
content = await
|
|
6954
|
+
content = await fs9.promises.readFile(path13, "utf-8");
|
|
6955
6955
|
} catch (err) {
|
|
6956
|
-
throw new AnthropicError(`Failed to read identity token file at ${
|
|
6956
|
+
throw new AnthropicError(`Failed to read identity token file at ${path13}: ${err}`);
|
|
6957
6957
|
}
|
|
6958
6958
|
const token = content.trim();
|
|
6959
6959
|
if (!token) {
|
|
6960
|
-
throw new AnthropicError(`Identity token file at ${
|
|
6960
|
+
throw new AnthropicError(`Identity token file at ${path13} is empty`);
|
|
6961
6961
|
}
|
|
6962
6962
|
return token;
|
|
6963
6963
|
};
|
|
@@ -7042,11 +7042,11 @@ var init_oidc_federation = __esm({
|
|
|
7042
7042
|
// node_modules/@anthropic-ai/sdk/lib/credentials/user-oauth.mjs
|
|
7043
7043
|
function userOAuthProvider(config) {
|
|
7044
7044
|
return async (opts) => {
|
|
7045
|
-
const
|
|
7045
|
+
const fs9 = await import("node:fs");
|
|
7046
7046
|
await checkCredentialsFileSafety(config.credentialsPath, config.onSafetyWarning);
|
|
7047
7047
|
let raw;
|
|
7048
7048
|
try {
|
|
7049
|
-
raw = await
|
|
7049
|
+
raw = await fs9.promises.readFile(config.credentialsPath, "utf-8");
|
|
7050
7050
|
} catch (err) {
|
|
7051
7051
|
throw new WorkloadIdentityError(`Credentials file not found at ${config.credentialsPath}: ${err}`);
|
|
7052
7052
|
}
|
|
@@ -7218,11 +7218,11 @@ function resolveIdentityTokenProvider(auth) {
|
|
|
7218
7218
|
}
|
|
7219
7219
|
function cachedExchangeProvider(exchange, credentialsPath, onCacheWriteError, onSafetyWarning) {
|
|
7220
7220
|
return async (opts) => {
|
|
7221
|
-
const
|
|
7221
|
+
const fs9 = await import("node:fs");
|
|
7222
7222
|
await checkCredentialsFileSafety(credentialsPath, onSafetyWarning);
|
|
7223
7223
|
let existing;
|
|
7224
7224
|
try {
|
|
7225
|
-
const raw = await
|
|
7225
|
+
const raw = await fs9.promises.readFile(credentialsPath, "utf-8");
|
|
7226
7226
|
existing = JSON.parse(raw);
|
|
7227
7227
|
const token = existing?.["access_token"];
|
|
7228
7228
|
if (token && !opts?.forceRefresh) {
|
|
@@ -8282,12 +8282,12 @@ var init_path = __esm({
|
|
|
8282
8282
|
"node_modules/@anthropic-ai/sdk/internal/utils/path.mjs"() {
|
|
8283
8283
|
init_error();
|
|
8284
8284
|
EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
8285
|
-
createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
8285
|
+
createPathTagFunction = (pathEncoder = encodeURIPath) => function path13(statics, ...params) {
|
|
8286
8286
|
if (statics.length === 1)
|
|
8287
8287
|
return statics[0];
|
|
8288
8288
|
let postPath = false;
|
|
8289
8289
|
const invalidSegments = [];
|
|
8290
|
-
const
|
|
8290
|
+
const path14 = statics.reduce((previousValue, currentValue, index) => {
|
|
8291
8291
|
if (/[?#]/.test(currentValue)) {
|
|
8292
8292
|
postPath = true;
|
|
8293
8293
|
}
|
|
@@ -8304,7 +8304,7 @@ var init_path = __esm({
|
|
|
8304
8304
|
}
|
|
8305
8305
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
8306
8306
|
}, "");
|
|
8307
|
-
const pathOnly =
|
|
8307
|
+
const pathOnly = path14.split(/[?#]/, 1)[0];
|
|
8308
8308
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
8309
8309
|
let match;
|
|
8310
8310
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -8325,10 +8325,10 @@ var init_path = __esm({
|
|
|
8325
8325
|
}, "");
|
|
8326
8326
|
throw new AnthropicError(`Path parameters result in path with invalid segments:
|
|
8327
8327
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
8328
|
-
${
|
|
8328
|
+
${path14}
|
|
8329
8329
|
${underline}`);
|
|
8330
8330
|
}
|
|
8331
|
-
return
|
|
8331
|
+
return path14;
|
|
8332
8332
|
};
|
|
8333
8333
|
path5 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
8334
8334
|
}
|
|
@@ -9706,15 +9706,15 @@ var require_dist = __commonJS({
|
|
|
9706
9706
|
return `v1,${expectedSignature}`;
|
|
9707
9707
|
}
|
|
9708
9708
|
verifyTimestamp(timestampHeader) {
|
|
9709
|
-
const
|
|
9709
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
9710
9710
|
const timestamp = parseInt(timestampHeader, 10);
|
|
9711
9711
|
if (isNaN(timestamp)) {
|
|
9712
9712
|
throw new WebhookVerificationError("Invalid Signature Headers");
|
|
9713
9713
|
}
|
|
9714
|
-
if (
|
|
9714
|
+
if (now2 - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) {
|
|
9715
9715
|
throw new WebhookVerificationError("Message timestamp too old");
|
|
9716
9716
|
}
|
|
9717
|
-
if (timestamp >
|
|
9717
|
+
if (timestamp > now2 + WEBHOOK_TOLERANCE_IN_SECONDS) {
|
|
9718
9718
|
throw new WebhookVerificationError("Message timestamp too new");
|
|
9719
9719
|
}
|
|
9720
9720
|
return new Date(timestamp * 1e3);
|
|
@@ -16818,9 +16818,9 @@ var init_client = __esm({
|
|
|
16818
16818
|
makeStatusError(status, error, message, headers) {
|
|
16819
16819
|
return APIError.generate(status, error, message, headers);
|
|
16820
16820
|
}
|
|
16821
|
-
buildURL(
|
|
16821
|
+
buildURL(path13, query, defaultBaseURL) {
|
|
16822
16822
|
const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
16823
|
-
const url = isAbsoluteURL(
|
|
16823
|
+
const url = isAbsoluteURL(path13) ? new URL(path13) : new URL(baseURL + (baseURL.endsWith("/") && path13.startsWith("/") ? path13.slice(1) : path13));
|
|
16824
16824
|
const defaultQuery = this.defaultQuery();
|
|
16825
16825
|
const pathQuery = Object.fromEntries(url.searchParams);
|
|
16826
16826
|
if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
|
|
@@ -16892,24 +16892,24 @@ var init_client = __esm({
|
|
|
16892
16892
|
backendMiddleware() {
|
|
16893
16893
|
return [];
|
|
16894
16894
|
}
|
|
16895
|
-
get(
|
|
16896
|
-
return this.methodRequest("get",
|
|
16895
|
+
get(path13, opts) {
|
|
16896
|
+
return this.methodRequest("get", path13, opts);
|
|
16897
16897
|
}
|
|
16898
|
-
post(
|
|
16899
|
-
return this.methodRequest("post",
|
|
16898
|
+
post(path13, opts) {
|
|
16899
|
+
return this.methodRequest("post", path13, opts);
|
|
16900
16900
|
}
|
|
16901
|
-
patch(
|
|
16902
|
-
return this.methodRequest("patch",
|
|
16901
|
+
patch(path13, opts) {
|
|
16902
|
+
return this.methodRequest("patch", path13, opts);
|
|
16903
16903
|
}
|
|
16904
|
-
put(
|
|
16905
|
-
return this.methodRequest("put",
|
|
16904
|
+
put(path13, opts) {
|
|
16905
|
+
return this.methodRequest("put", path13, opts);
|
|
16906
16906
|
}
|
|
16907
|
-
delete(
|
|
16908
|
-
return this.methodRequest("delete",
|
|
16907
|
+
delete(path13, opts) {
|
|
16908
|
+
return this.methodRequest("delete", path13, opts);
|
|
16909
16909
|
}
|
|
16910
|
-
methodRequest(method,
|
|
16910
|
+
methodRequest(method, path13, opts) {
|
|
16911
16911
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
16912
|
-
return { method, path:
|
|
16912
|
+
return { method, path: path13, ...opts2 };
|
|
16913
16913
|
}));
|
|
16914
16914
|
}
|
|
16915
16915
|
request(options, remainingRetries = null) {
|
|
@@ -17023,8 +17023,8 @@ var init_client = __esm({
|
|
|
17023
17023
|
}));
|
|
17024
17024
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
17025
17025
|
}
|
|
17026
|
-
getAPIList(
|
|
17027
|
-
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path:
|
|
17026
|
+
getAPIList(path13, Page2, opts) {
|
|
17027
|
+
return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path13, ...opts2 })) : { method: "get", path: path13, ...opts });
|
|
17028
17028
|
}
|
|
17029
17029
|
requestAPIList(Page2, options) {
|
|
17030
17030
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -17140,14 +17140,14 @@ var init_client = __esm({
|
|
|
17140
17140
|
}
|
|
17141
17141
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
17142
17142
|
const options = { ...inputOptions };
|
|
17143
|
-
const { method, path:
|
|
17143
|
+
const { method, path: path13, query, defaultBaseURL } = options;
|
|
17144
17144
|
if (this._authState.resolution) {
|
|
17145
17145
|
await this._authState.resolution;
|
|
17146
17146
|
}
|
|
17147
17147
|
if (!this._baseURLIsExplicit && this._authState.baseURL && this.baseURL !== this._authState.baseURL) {
|
|
17148
17148
|
this.baseURL = this._authState.baseURL;
|
|
17149
17149
|
}
|
|
17150
|
-
const url = this.buildURL(
|
|
17150
|
+
const url = this.buildURL(path13, query, defaultBaseURL);
|
|
17151
17151
|
if ("timeout" in options)
|
|
17152
17152
|
validatePositiveInteger("timeout", options.timeout);
|
|
17153
17153
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -17288,19 +17288,19 @@ var init_sdk = __esm({
|
|
|
17288
17288
|
// node_modules/electron/index.js
|
|
17289
17289
|
var require_electron = __commonJS({
|
|
17290
17290
|
"node_modules/electron/index.js"(exports, module) {
|
|
17291
|
-
var
|
|
17292
|
-
var
|
|
17293
|
-
var pathFile =
|
|
17291
|
+
var fs9 = __require("fs");
|
|
17292
|
+
var path13 = __require("path");
|
|
17293
|
+
var pathFile = path13.join(__dirname, "path.txt");
|
|
17294
17294
|
function getElectronPath() {
|
|
17295
17295
|
let executablePath;
|
|
17296
|
-
if (
|
|
17297
|
-
executablePath =
|
|
17296
|
+
if (fs9.existsSync(pathFile)) {
|
|
17297
|
+
executablePath = fs9.readFileSync(pathFile, "utf-8");
|
|
17298
17298
|
}
|
|
17299
17299
|
if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
|
|
17300
|
-
return
|
|
17300
|
+
return path13.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || "electron");
|
|
17301
17301
|
}
|
|
17302
17302
|
if (executablePath) {
|
|
17303
|
-
return
|
|
17303
|
+
return path13.join(__dirname, "dist", executablePath);
|
|
17304
17304
|
} else {
|
|
17305
17305
|
throw new Error("Electron failed to install correctly, please delete node_modules/electron and try installing again");
|
|
17306
17306
|
}
|
|
@@ -17314,7 +17314,7 @@ var require_package = __commonJS({
|
|
|
17314
17314
|
"package.json"(exports, module) {
|
|
17315
17315
|
module.exports = {
|
|
17316
17316
|
name: "@phenx-inc/ctlsurf",
|
|
17317
|
-
version: "0.
|
|
17317
|
+
version: "0.17.1",
|
|
17318
17318
|
description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
17319
17319
|
main: "out/main/index.js",
|
|
17320
17320
|
bin: {
|
|
@@ -17452,6 +17452,10 @@ var PtyManager = class {
|
|
|
17452
17452
|
this.process = null;
|
|
17453
17453
|
});
|
|
17454
17454
|
}
|
|
17455
|
+
/** OS pid of the spawned agent (or ssh), null once it has exited. */
|
|
17456
|
+
get pid() {
|
|
17457
|
+
return this.process?.pid ?? null;
|
|
17458
|
+
}
|
|
17455
17459
|
write(data) {
|
|
17456
17460
|
this.process?.write(data);
|
|
17457
17461
|
}
|
|
@@ -17564,8 +17568,8 @@ var CtlsurfApi = class {
|
|
|
17564
17568
|
}
|
|
17565
17569
|
return h;
|
|
17566
17570
|
}
|
|
17567
|
-
async request(method,
|
|
17568
|
-
const url = `${this.baseUrl}${
|
|
17571
|
+
async request(method, path13, body) {
|
|
17572
|
+
const url = `${this.baseUrl}${path13}`;
|
|
17569
17573
|
const opts = {
|
|
17570
17574
|
method,
|
|
17571
17575
|
headers: this.headers()
|
|
@@ -17576,7 +17580,7 @@ var CtlsurfApi = class {
|
|
|
17576
17580
|
const res = await fetch(url, opts);
|
|
17577
17581
|
if (!res.ok) {
|
|
17578
17582
|
const text = await res.text();
|
|
17579
|
-
throw new Error(`ctlsurf API ${method} ${
|
|
17583
|
+
throw new Error(`ctlsurf API ${method} ${path13}: ${res.status} ${text}`);
|
|
17580
17584
|
}
|
|
17581
17585
|
return res.json();
|
|
17582
17586
|
}
|
|
@@ -17747,16 +17751,16 @@ var ConversationBridge = class {
|
|
|
17747
17751
|
this.outputFlushTimer = null;
|
|
17748
17752
|
if (this.outputBuffer.length === 0) return;
|
|
17749
17753
|
const candidate = this.terminalCapture.currentCandidate();
|
|
17750
|
-
const
|
|
17754
|
+
const now2 = Date.now();
|
|
17751
17755
|
if (candidate.length === 0) {
|
|
17752
17756
|
this.outputBuffer = "";
|
|
17753
17757
|
return;
|
|
17754
17758
|
}
|
|
17755
17759
|
if (candidate !== this.pendingCandidate) {
|
|
17756
17760
|
this.pendingCandidate = candidate;
|
|
17757
|
-
this.pendingCandidateChangedAt =
|
|
17761
|
+
this.pendingCandidateChangedAt = now2;
|
|
17758
17762
|
}
|
|
17759
|
-
if (
|
|
17763
|
+
if (now2 - this.pendingCandidateChangedAt >= OUTPUT_STABLE_MS) {
|
|
17760
17764
|
this.flush();
|
|
17761
17765
|
return;
|
|
17762
17766
|
}
|
|
@@ -18271,8 +18275,8 @@ function mostRecentClaudeFile(cwd, root) {
|
|
|
18271
18275
|
}
|
|
18272
18276
|
function recentCodexRollouts(root) {
|
|
18273
18277
|
const dirs = /* @__PURE__ */ new Set();
|
|
18274
|
-
const
|
|
18275
|
-
for (const ms of [
|
|
18278
|
+
const now2 = Date.now();
|
|
18279
|
+
for (const ms of [now2, now2 - 24 * 60 * 60 * 1e3]) {
|
|
18276
18280
|
const d = new Date(ms);
|
|
18277
18281
|
const yyyy = String(d.getFullYear());
|
|
18278
18282
|
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -18386,9 +18390,9 @@ var AgentStateMonitor = class {
|
|
|
18386
18390
|
tabs = /* @__PURE__ */ new Map();
|
|
18387
18391
|
timer = null;
|
|
18388
18392
|
lastAnyAwaiting = false;
|
|
18389
|
-
constructor(sink,
|
|
18393
|
+
constructor(sink, now2 = Date.now) {
|
|
18390
18394
|
this.sink = sink;
|
|
18391
|
-
this.now =
|
|
18395
|
+
this.now = now2;
|
|
18392
18396
|
}
|
|
18393
18397
|
start() {
|
|
18394
18398
|
if (this.timer) return;
|
|
@@ -18404,14 +18408,14 @@ var AgentStateMonitor = class {
|
|
|
18404
18408
|
}
|
|
18405
18409
|
/** Register a tab so its raw PTY output can be classified. */
|
|
18406
18410
|
trackTab(tabId, agentId, cwd) {
|
|
18407
|
-
const
|
|
18411
|
+
const now2 = this.now();
|
|
18408
18412
|
this.tabs.set(tabId, {
|
|
18409
18413
|
agentId,
|
|
18410
18414
|
cwd,
|
|
18411
18415
|
state: "idle",
|
|
18412
18416
|
everBusy: false,
|
|
18413
18417
|
lastBusyAt: 0,
|
|
18414
|
-
lastOutputAt:
|
|
18418
|
+
lastOutputAt: now2,
|
|
18415
18419
|
lastDecisionAt: 0,
|
|
18416
18420
|
hookState: null,
|
|
18417
18421
|
hookAt: 0,
|
|
@@ -18425,16 +18429,16 @@ var AgentStateMonitor = class {
|
|
|
18425
18429
|
feed(tabId, data) {
|
|
18426
18430
|
const rec = this.tabs.get(tabId);
|
|
18427
18431
|
if (!rec) return;
|
|
18428
|
-
const
|
|
18429
|
-
rec.lastOutputAt =
|
|
18432
|
+
const now2 = this.now();
|
|
18433
|
+
rec.lastOutputAt = now2;
|
|
18430
18434
|
const text = rec.tail + stripAnsi2(data);
|
|
18431
18435
|
rec.tail = text.length > TAIL_KEEP_CHARS ? text.slice(-TAIL_KEEP_CHARS) : text;
|
|
18432
18436
|
if (hasBusyMarker(text)) {
|
|
18433
|
-
rec.lastBusyAt =
|
|
18437
|
+
rec.lastBusyAt = now2;
|
|
18434
18438
|
rec.everBusy = true;
|
|
18435
18439
|
}
|
|
18436
18440
|
if (hasApprovalMarker(text)) {
|
|
18437
|
-
rec.lastDecisionAt =
|
|
18441
|
+
rec.lastDecisionAt = now2;
|
|
18438
18442
|
}
|
|
18439
18443
|
}
|
|
18440
18444
|
/**
|
|
@@ -18451,9 +18455,9 @@ var AgentStateMonitor = class {
|
|
|
18451
18455
|
if (!/[\r\n]/.test(data)) return;
|
|
18452
18456
|
const rec = this.tabs.get(tabId);
|
|
18453
18457
|
if (!rec) return;
|
|
18454
|
-
const
|
|
18458
|
+
const now2 = this.now();
|
|
18455
18459
|
rec.everBusy = false;
|
|
18456
|
-
rec.lastBusyAt =
|
|
18460
|
+
rec.lastBusyAt = now2;
|
|
18457
18461
|
rec.lastDecisionAt = 0;
|
|
18458
18462
|
rec.hookState = null;
|
|
18459
18463
|
this.setState(rec, tabId, "busy");
|
|
@@ -18467,28 +18471,28 @@ var AgentStateMonitor = class {
|
|
|
18467
18471
|
*/
|
|
18468
18472
|
applyHookEvent(cwd, kind) {
|
|
18469
18473
|
const target = stripTrailingSep2(cwd);
|
|
18470
|
-
const
|
|
18474
|
+
const now2 = this.now();
|
|
18471
18475
|
for (const [tabId, rec] of this.tabs) {
|
|
18472
18476
|
if (stripTrailingSep2(rec.cwd) !== target) continue;
|
|
18473
18477
|
rec.hookState = kind;
|
|
18474
|
-
rec.hookAt =
|
|
18478
|
+
rec.hookAt = now2;
|
|
18475
18479
|
if (kind === "busy") {
|
|
18476
18480
|
rec.everBusy = false;
|
|
18477
|
-
rec.lastBusyAt =
|
|
18481
|
+
rec.lastBusyAt = now2;
|
|
18478
18482
|
}
|
|
18479
18483
|
this.setState(rec, tabId, kind);
|
|
18480
18484
|
}
|
|
18481
18485
|
}
|
|
18482
18486
|
tick() {
|
|
18483
|
-
const
|
|
18487
|
+
const now2 = this.now();
|
|
18484
18488
|
for (const [tabId, rec] of this.tabs) {
|
|
18485
|
-
if (rec.hookState &&
|
|
18489
|
+
if (rec.hookState && now2 - rec.hookAt < HOOK_AUTHORITY_MS) {
|
|
18486
18490
|
this.setState(rec, tabId, rec.hookState);
|
|
18487
18491
|
continue;
|
|
18488
18492
|
}
|
|
18489
|
-
const busyRecently = rec.lastBusyAt > 0 &&
|
|
18490
|
-
const settled =
|
|
18491
|
-
const decisionRecent = rec.lastDecisionAt > 0 &&
|
|
18493
|
+
const busyRecently = rec.lastBusyAt > 0 && now2 - rec.lastBusyAt < BUSY_GRACE_MS;
|
|
18494
|
+
const settled = now2 - rec.lastOutputAt >= SETTLE_MS;
|
|
18495
|
+
const decisionRecent = rec.lastDecisionAt > 0 && now2 - rec.lastDecisionAt < DECISION_TTL_MS;
|
|
18492
18496
|
let next = null;
|
|
18493
18497
|
if (busyRecently) {
|
|
18494
18498
|
next = "busy";
|
|
@@ -18909,16 +18913,16 @@ var TimeTracker = class {
|
|
|
18909
18913
|
recordActivity(tabId) {
|
|
18910
18914
|
const s = this.sessions.get(tabId);
|
|
18911
18915
|
if (!s || s.ended) return;
|
|
18912
|
-
const
|
|
18913
|
-
if (!isSameLocalDay(s.startedAt,
|
|
18916
|
+
const now2 = Date.now();
|
|
18917
|
+
if (!isSameLocalDay(s.startedAt, now2)) {
|
|
18914
18918
|
void this.rollover(tabId);
|
|
18915
18919
|
return;
|
|
18916
18920
|
}
|
|
18917
|
-
const delta =
|
|
18921
|
+
const delta = now2 - s.lastActivity;
|
|
18918
18922
|
if (delta < s.idleTimeoutMs) {
|
|
18919
18923
|
s.activeMs += delta;
|
|
18920
18924
|
}
|
|
18921
|
-
s.lastActivity =
|
|
18925
|
+
s.lastActivity = now2;
|
|
18922
18926
|
}
|
|
18923
18927
|
async rollover(tabId) {
|
|
18924
18928
|
if (this.rollingOver.has(tabId)) return;
|
|
@@ -19562,10 +19566,10 @@ var SpanTracker = class {
|
|
|
19562
19566
|
lastAccountAt = 0;
|
|
19563
19567
|
activityMs = { agent: 0, human: 0, idle: 0 };
|
|
19564
19568
|
started = false;
|
|
19565
|
-
constructor(session, sink,
|
|
19569
|
+
constructor(session, sink, now2 = Date.now) {
|
|
19566
19570
|
this.session = session;
|
|
19567
19571
|
this.sink = sink;
|
|
19568
|
-
this.now =
|
|
19572
|
+
this.now = now2;
|
|
19569
19573
|
}
|
|
19570
19574
|
/** Begin attribution at session start. */
|
|
19571
19575
|
begin() {
|
|
@@ -20020,10 +20024,10 @@ var ObserverBudget = class {
|
|
|
20020
20024
|
capUsd;
|
|
20021
20025
|
date;
|
|
20022
20026
|
spentUsd = 0;
|
|
20023
|
-
constructor(dir, capUsd,
|
|
20027
|
+
constructor(dir, capUsd, now2 = () => /* @__PURE__ */ new Date()) {
|
|
20024
20028
|
this.filePath = path4.join(dir, "budget.json");
|
|
20025
20029
|
this.capUsd = capUsd;
|
|
20026
|
-
this.date = localDay(
|
|
20030
|
+
this.date = localDay(now2());
|
|
20027
20031
|
try {
|
|
20028
20032
|
const raw = JSON.parse(fs3.readFileSync(this.filePath, "utf-8"));
|
|
20029
20033
|
if (raw?.date === this.date && typeof raw.spentUsd === "number") {
|
|
@@ -20032,8 +20036,8 @@ var ObserverBudget = class {
|
|
|
20032
20036
|
} catch {
|
|
20033
20037
|
}
|
|
20034
20038
|
}
|
|
20035
|
-
addUsage(model, usage,
|
|
20036
|
-
const today = localDay(
|
|
20039
|
+
addUsage(model, usage, now2 = () => /* @__PURE__ */ new Date()) {
|
|
20040
|
+
const today = localDay(now2());
|
|
20037
20041
|
if (today !== this.date) {
|
|
20038
20042
|
this.date = today;
|
|
20039
20043
|
this.spentUsd = 0;
|
|
@@ -20045,8 +20049,8 @@ var ObserverBudget = class {
|
|
|
20045
20049
|
} catch {
|
|
20046
20050
|
}
|
|
20047
20051
|
}
|
|
20048
|
-
overCap(
|
|
20049
|
-
if (localDay(
|
|
20052
|
+
overCap(now2 = () => /* @__PURE__ */ new Date()) {
|
|
20053
|
+
if (localDay(now2()) !== this.date) return false;
|
|
20050
20054
|
return this.spentUsd >= this.capUsd;
|
|
20051
20055
|
}
|
|
20052
20056
|
get spent() {
|
|
@@ -21234,6 +21238,15 @@ var Orchestrator = class {
|
|
|
21234
21238
|
getTabIds() {
|
|
21235
21239
|
return [...this.tabs.keys()];
|
|
21236
21240
|
}
|
|
21241
|
+
// Every open tab's agent and PTY pid, for the resource monitor (monitor.ts).
|
|
21242
|
+
getTabProcesses() {
|
|
21243
|
+
return [...this.tabs].map(([tabId, tab]) => ({
|
|
21244
|
+
tabId,
|
|
21245
|
+
agent: tab.agent,
|
|
21246
|
+
pid: tab.ptyManager.pid,
|
|
21247
|
+
active: tabId === this.activeTabId
|
|
21248
|
+
}));
|
|
21249
|
+
}
|
|
21237
21250
|
// ─── Tracking control (active tab) ──────────────
|
|
21238
21251
|
isActiveTabTracking() {
|
|
21239
21252
|
if (!this.activeTabId) return false;
|
|
@@ -21439,6 +21452,7 @@ var FG_TITLE = `${CSI}38;2;192;202;245m`;
|
|
|
21439
21452
|
var BG_MODAL = `${CSI}48;2;31;35;53m`;
|
|
21440
21453
|
var BG_SELECTED = `${CSI}48;2;42;43;61m`;
|
|
21441
21454
|
var RESET = `${CSI}0m`;
|
|
21455
|
+
var TITLE_QUIET_MS = 50;
|
|
21442
21456
|
var Tui = class {
|
|
21443
21457
|
rows = 0;
|
|
21444
21458
|
cols = 0;
|
|
@@ -21447,8 +21461,13 @@ var Tui = class {
|
|
|
21447
21461
|
cwd: "",
|
|
21448
21462
|
wsStatus: "disconnected",
|
|
21449
21463
|
workerId: null,
|
|
21450
|
-
mode: "terminal"
|
|
21464
|
+
mode: "terminal",
|
|
21465
|
+
resources: ""
|
|
21451
21466
|
};
|
|
21467
|
+
lastPtyWrite = 0;
|
|
21468
|
+
pendingTitle = null;
|
|
21469
|
+
lastTitle = "";
|
|
21470
|
+
titleTimer = null;
|
|
21452
21471
|
constructor() {
|
|
21453
21472
|
this.rows = process.stdout.rows || 24;
|
|
21454
21473
|
this.cols = process.stdout.columns || 80;
|
|
@@ -21464,6 +21483,8 @@ var Tui = class {
|
|
|
21464
21483
|
* Restore terminal to normal state
|
|
21465
21484
|
*/
|
|
21466
21485
|
destroy() {
|
|
21486
|
+
if (this.titleTimer) clearTimeout(this.titleTimer);
|
|
21487
|
+
this.titleTimer = null;
|
|
21467
21488
|
this.write(`${CSI}?25h`);
|
|
21468
21489
|
}
|
|
21469
21490
|
/**
|
|
@@ -21493,19 +21514,35 @@ var Tui = class {
|
|
|
21493
21514
|
* Write PTY output — full passthrough.
|
|
21494
21515
|
*/
|
|
21495
21516
|
writePtyData(data) {
|
|
21517
|
+
this.lastPtyWrite = Date.now();
|
|
21496
21518
|
this.write(data);
|
|
21497
21519
|
}
|
|
21498
21520
|
/**
|
|
21499
21521
|
* Update the terminal window/tab title via OSC escape sequence.
|
|
21500
21522
|
*/
|
|
21501
21523
|
setTerminalTitle(title) {
|
|
21502
|
-
this.
|
|
21524
|
+
this.pendingTitle = title;
|
|
21525
|
+
this.flushTitle();
|
|
21526
|
+
}
|
|
21527
|
+
flushTitle() {
|
|
21528
|
+
if (this.pendingTitle === null || this.titleTimer) return;
|
|
21529
|
+
const wait = TITLE_QUIET_MS - (Date.now() - this.lastPtyWrite);
|
|
21530
|
+
if (wait > 0) {
|
|
21531
|
+
this.titleTimer = setTimeout(() => {
|
|
21532
|
+
this.titleTimer = null;
|
|
21533
|
+
this.flushTitle();
|
|
21534
|
+
}, wait);
|
|
21535
|
+
return;
|
|
21536
|
+
}
|
|
21537
|
+
if (this.pendingTitle !== this.lastTitle) this.write(`${ESC}]0;${this.pendingTitle}\x07`);
|
|
21538
|
+
this.lastTitle = this.pendingTitle;
|
|
21539
|
+
this.pendingTitle = null;
|
|
21503
21540
|
}
|
|
21504
21541
|
/**
|
|
21505
21542
|
* Build a title string from current state for the terminal tab.
|
|
21506
21543
|
*/
|
|
21507
21544
|
updateTerminalTitle() {
|
|
21508
|
-
const { agentName, wsStatus, cwd } = this.state;
|
|
21545
|
+
const { agentName, wsStatus, cwd, resources } = this.state;
|
|
21509
21546
|
const displayCwd = this.shortenPath(cwd);
|
|
21510
21547
|
const statusIcon = {
|
|
21511
21548
|
connected: "\u25CF",
|
|
@@ -21521,7 +21558,9 @@ var Tui = class {
|
|
|
21521
21558
|
pending_approval: "Pending",
|
|
21522
21559
|
no_project: "No Project"
|
|
21523
21560
|
}[wsStatus] || wsStatus;
|
|
21524
|
-
this.setTerminalTitle(
|
|
21561
|
+
this.setTerminalTitle(
|
|
21562
|
+
`ctlsurf \xB7 ${agentName} \xB7 ${statusIcon} ${statusLabel} \xB7 ${displayCwd}` + (resources ? ` \xB7 ${resources}` : "")
|
|
21563
|
+
);
|
|
21525
21564
|
}
|
|
21526
21565
|
/**
|
|
21527
21566
|
* Show an interactive agent picker modal.
|
|
@@ -21696,6 +21735,392 @@ var Tui = class {
|
|
|
21696
21735
|
}
|
|
21697
21736
|
};
|
|
21698
21737
|
|
|
21738
|
+
// src/main/monitor.ts
|
|
21739
|
+
import { execFile as execFile2 } from "child_process";
|
|
21740
|
+
import fs8 from "fs";
|
|
21741
|
+
import os6 from "os";
|
|
21742
|
+
import path12 from "path";
|
|
21743
|
+
var FAST_MS = 3e3;
|
|
21744
|
+
var SLOW_MS = 1e4;
|
|
21745
|
+
var IDLE_STOP_MS = 12e4;
|
|
21746
|
+
var HISTORY_MS = 6e5;
|
|
21747
|
+
var FAST_HOLD_MS = 15e3;
|
|
21748
|
+
var USAGE_SLOW_MS = 3e5;
|
|
21749
|
+
var USAGE_FAST_MS = 6e4;
|
|
21750
|
+
var CODEX_TAIL_BYTES = 512 * 1024;
|
|
21751
|
+
function now() {
|
|
21752
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
21753
|
+
}
|
|
21754
|
+
function round(x, digits = 1) {
|
|
21755
|
+
const f = 10 ** digits;
|
|
21756
|
+
return Math.round(x * f) / f;
|
|
21757
|
+
}
|
|
21758
|
+
function run(cmd, args, opts = {}) {
|
|
21759
|
+
return new Promise((resolve4, reject) => {
|
|
21760
|
+
execFile2(cmd, args, { timeout: opts.timeout ?? 5e3, cwd: opts.cwd, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
|
|
21761
|
+
if (err && !stdout) reject(err);
|
|
21762
|
+
else resolve4(stdout);
|
|
21763
|
+
});
|
|
21764
|
+
});
|
|
21765
|
+
}
|
|
21766
|
+
function serviceFor(agent) {
|
|
21767
|
+
const cmd = isRemoteAgent(agent) ? agent.args.slice(2).join(" ") : path12.basename(agent.command);
|
|
21768
|
+
if (/\bcodex\b/.test(cmd)) return "codex";
|
|
21769
|
+
if (/\bclaude\b/.test(cmd)) return "claude";
|
|
21770
|
+
return null;
|
|
21771
|
+
}
|
|
21772
|
+
function cpuTimes() {
|
|
21773
|
+
let idle = 0;
|
|
21774
|
+
let total = 0;
|
|
21775
|
+
for (const c of os6.cpus()) {
|
|
21776
|
+
const t = c.times;
|
|
21777
|
+
idle += t.idle;
|
|
21778
|
+
total += t.user + t.nice + t.sys + t.idle + t.irq;
|
|
21779
|
+
}
|
|
21780
|
+
return { idle, total };
|
|
21781
|
+
}
|
|
21782
|
+
async function memory() {
|
|
21783
|
+
const total = os6.totalmem();
|
|
21784
|
+
let avail = os6.freemem();
|
|
21785
|
+
try {
|
|
21786
|
+
if (process.platform === "darwin") {
|
|
21787
|
+
const out = await run("vm_stat", []);
|
|
21788
|
+
const page = Number(/page size of (\d+) bytes/.exec(out)?.[1]) || 4096;
|
|
21789
|
+
const pages = (name) => Number(new RegExp(`Pages ${name}:\\s+(\\d+)`).exec(out)?.[1] ?? 0);
|
|
21790
|
+
avail = (pages("free") + pages("inactive") + pages("speculative")) * page;
|
|
21791
|
+
} else if (process.platform === "linux") {
|
|
21792
|
+
const m = /MemAvailable:\s+(\d+) kB/.exec(await fs8.promises.readFile("/proc/meminfo", "utf-8"));
|
|
21793
|
+
if (m) avail = Number(m[1]) * 1024;
|
|
21794
|
+
}
|
|
21795
|
+
} catch {
|
|
21796
|
+
}
|
|
21797
|
+
avail = Math.min(avail, total);
|
|
21798
|
+
return { total, used: total - avail, pct: round(100 * (total - avail) / total) };
|
|
21799
|
+
}
|
|
21800
|
+
async function diskUsage() {
|
|
21801
|
+
if (typeof fs8.promises.statfs !== "function") return null;
|
|
21802
|
+
try {
|
|
21803
|
+
const s = await fs8.promises.statfs(os6.homedir());
|
|
21804
|
+
return { total: s.blocks * s.bsize, free: s.bavail * s.bsize };
|
|
21805
|
+
} catch {
|
|
21806
|
+
return null;
|
|
21807
|
+
}
|
|
21808
|
+
}
|
|
21809
|
+
async function appleGpu() {
|
|
21810
|
+
if (process.platform !== "darwin") return null;
|
|
21811
|
+
try {
|
|
21812
|
+
const out = await run("ioreg", ["-r", "-d", "1", "-w", "0", "-c", "IOAccelerator"], { timeout: 3e3 });
|
|
21813
|
+
const m = /"Device Utilization %"=(\d+)/.exec(out);
|
|
21814
|
+
return m ? Number(m[1]) : null;
|
|
21815
|
+
} catch {
|
|
21816
|
+
return null;
|
|
21817
|
+
}
|
|
21818
|
+
}
|
|
21819
|
+
async function processTable() {
|
|
21820
|
+
const table = /* @__PURE__ */ new Map();
|
|
21821
|
+
if (process.platform === "win32") return table;
|
|
21822
|
+
try {
|
|
21823
|
+
const out = await run("ps", ["-A", "-o", "pid=,ppid=,pcpu=,rss="]);
|
|
21824
|
+
for (const line of out.split("\n")) {
|
|
21825
|
+
const [pid, ppid, cpu, rss] = line.trim().split(/\s+/).map(Number);
|
|
21826
|
+
if (pid) table.set(pid, { ppid, cpu: cpu || 0, rss: (rss || 0) * 1024 });
|
|
21827
|
+
}
|
|
21828
|
+
} catch {
|
|
21829
|
+
}
|
|
21830
|
+
return table;
|
|
21831
|
+
}
|
|
21832
|
+
function agentProcess(tab, table) {
|
|
21833
|
+
const base = {
|
|
21834
|
+
tabId: tab.tabId,
|
|
21835
|
+
name: tab.agent.name,
|
|
21836
|
+
active: tab.active,
|
|
21837
|
+
remote: isRemoteAgent(tab.agent),
|
|
21838
|
+
service: serviceFor(tab.agent),
|
|
21839
|
+
pid: tab.pid
|
|
21840
|
+
};
|
|
21841
|
+
if (tab.pid == null || !table.has(tab.pid)) return { ...base, cpu: null, mem: null, procs: 0 };
|
|
21842
|
+
const children = /* @__PURE__ */ new Map();
|
|
21843
|
+
for (const [pid, row] of table) {
|
|
21844
|
+
const list = children.get(row.ppid);
|
|
21845
|
+
if (list) list.push(pid);
|
|
21846
|
+
else children.set(row.ppid, [pid]);
|
|
21847
|
+
}
|
|
21848
|
+
let cpu = 0;
|
|
21849
|
+
let mem = 0;
|
|
21850
|
+
let procs = 0;
|
|
21851
|
+
const queue = [tab.pid];
|
|
21852
|
+
while (queue.length) {
|
|
21853
|
+
const pid = queue.pop();
|
|
21854
|
+
const row = table.get(pid);
|
|
21855
|
+
if (!row) continue;
|
|
21856
|
+
cpu += row.cpu;
|
|
21857
|
+
mem += row.rss;
|
|
21858
|
+
procs++;
|
|
21859
|
+
queue.push(...children.get(pid) ?? []);
|
|
21860
|
+
}
|
|
21861
|
+
return { ...base, cpu: round(cpu), mem, procs };
|
|
21862
|
+
}
|
|
21863
|
+
function parseClaudeUsage(text) {
|
|
21864
|
+
const windows = [];
|
|
21865
|
+
for (const line of text.split("\n")) {
|
|
21866
|
+
let m = /^\s*Current session:\s*([\d.]+)% used(?:\s*·\s*resets (.+))?/.exec(line);
|
|
21867
|
+
if (m) {
|
|
21868
|
+
windows.push({ label: "session (5h)", short: "5h", pct: Number(m[1]), resets: m[2]?.trim() || null });
|
|
21869
|
+
continue;
|
|
21870
|
+
}
|
|
21871
|
+
m = /^\s*Current week \((.+?)\):\s*([\d.]+)% used(?:\s*·\s*resets (.+))?/.exec(line);
|
|
21872
|
+
if (m) {
|
|
21873
|
+
const all = m[1].trim().toLowerCase() === "all models";
|
|
21874
|
+
windows.push({
|
|
21875
|
+
label: all ? "week, all models" : `week, ${m[1].trim()}`,
|
|
21876
|
+
short: all ? "wk" : null,
|
|
21877
|
+
pct: Number(m[2]),
|
|
21878
|
+
resets: m[3]?.trim() || null
|
|
21879
|
+
});
|
|
21880
|
+
}
|
|
21881
|
+
}
|
|
21882
|
+
return windows;
|
|
21883
|
+
}
|
|
21884
|
+
async function claudeUsage() {
|
|
21885
|
+
const source = "claude /usage";
|
|
21886
|
+
let text = "";
|
|
21887
|
+
try {
|
|
21888
|
+
const out = await run(
|
|
21889
|
+
"claude",
|
|
21890
|
+
["-p", "/usage", "--output-format", "json", "--no-session-persistence"],
|
|
21891
|
+
{ cwd: os6.tmpdir(), timeout: 4e4 }
|
|
21892
|
+
);
|
|
21893
|
+
text = String(JSON.parse(out || "{}").result ?? "");
|
|
21894
|
+
} catch (err) {
|
|
21895
|
+
const msg = err.code === "ENOENT" ? "claude not found on PATH" : String(err);
|
|
21896
|
+
return { ok: false, windows: [], source, ts: now(), error: msg.slice(0, 200) };
|
|
21897
|
+
}
|
|
21898
|
+
const windows = parseClaudeUsage(text);
|
|
21899
|
+
return windows.length ? { ok: true, windows, source, ts: now() } : { ok: false, windows: [], source, ts: now(), error: text.trim().slice(0, 200) || "no usage reported" };
|
|
21900
|
+
}
|
|
21901
|
+
function findKey(obj, key) {
|
|
21902
|
+
if (Array.isArray(obj)) {
|
|
21903
|
+
for (const v of obj) {
|
|
21904
|
+
const found = findKey(v, key);
|
|
21905
|
+
if (found) return found;
|
|
21906
|
+
}
|
|
21907
|
+
} else if (obj && typeof obj === "object") {
|
|
21908
|
+
const rec = obj;
|
|
21909
|
+
if (rec[key] && typeof rec[key] === "object") return rec[key];
|
|
21910
|
+
for (const v of Object.values(rec)) {
|
|
21911
|
+
const found = findKey(v, key);
|
|
21912
|
+
if (found) return found;
|
|
21913
|
+
}
|
|
21914
|
+
}
|
|
21915
|
+
return null;
|
|
21916
|
+
}
|
|
21917
|
+
function codexWindow(minutes) {
|
|
21918
|
+
const m = Number(minutes);
|
|
21919
|
+
if (!Number.isFinite(m)) return { label: "window", short: "win" };
|
|
21920
|
+
if (m === 300) return { label: "5h", short: "5h" };
|
|
21921
|
+
if (m === 1440) return { label: "day", short: "day" };
|
|
21922
|
+
if (m === 10080) return { label: "week", short: "wk" };
|
|
21923
|
+
const s = m < 1440 ? `${Math.floor(m / 60)}h` : `${Math.floor(m / 1440)}d`;
|
|
21924
|
+
return { label: s, short: s };
|
|
21925
|
+
}
|
|
21926
|
+
async function readTail(file, bytes) {
|
|
21927
|
+
const fh = await fs8.promises.open(file, "r");
|
|
21928
|
+
try {
|
|
21929
|
+
const { size } = await fh.stat();
|
|
21930
|
+
const start = Math.max(0, size - bytes);
|
|
21931
|
+
const buf = Buffer.alloc(size - start);
|
|
21932
|
+
await fh.read(buf, 0, buf.length, start);
|
|
21933
|
+
return buf.toString("utf-8");
|
|
21934
|
+
} finally {
|
|
21935
|
+
await fh.close();
|
|
21936
|
+
}
|
|
21937
|
+
}
|
|
21938
|
+
async function codexSessionFiles(root, limit2) {
|
|
21939
|
+
const sortedDirs = async (dir) => (await fs8.promises.readdir(dir, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory()).map((d) => path12.join(dir, d.name)).sort().reverse();
|
|
21940
|
+
const days = [];
|
|
21941
|
+
for (const y of await sortedDirs(root)) {
|
|
21942
|
+
for (const m of await sortedDirs(y)) {
|
|
21943
|
+
days.push(...await sortedDirs(m));
|
|
21944
|
+
if (days.length >= 14) break;
|
|
21945
|
+
}
|
|
21946
|
+
if (days.length >= 14) break;
|
|
21947
|
+
}
|
|
21948
|
+
const files = [];
|
|
21949
|
+
for (const day of days.slice(0, 14)) {
|
|
21950
|
+
for (const name of await fs8.promises.readdir(day).catch(() => [])) {
|
|
21951
|
+
if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
|
|
21952
|
+
const file = path12.join(day, name);
|
|
21953
|
+
try {
|
|
21954
|
+
files.push({ file, mtime: (await fs8.promises.stat(file)).mtimeMs });
|
|
21955
|
+
} catch {
|
|
21956
|
+
}
|
|
21957
|
+
}
|
|
21958
|
+
}
|
|
21959
|
+
return files.sort((a, b) => b.mtime - a.mtime).slice(0, limit2).map((f) => f.file);
|
|
21960
|
+
}
|
|
21961
|
+
async function codexUsage(home = os6.homedir()) {
|
|
21962
|
+
const source = "your most recent Codex session";
|
|
21963
|
+
for (const file of await codexSessionFiles(path12.join(home, ".codex", "sessions"), 5)) {
|
|
21964
|
+
let lines;
|
|
21965
|
+
try {
|
|
21966
|
+
lines = (await readTail(file, CODEX_TAIL_BYTES)).split("\n");
|
|
21967
|
+
} catch {
|
|
21968
|
+
continue;
|
|
21969
|
+
}
|
|
21970
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
21971
|
+
if (!lines[i].includes('"rate_limits"')) continue;
|
|
21972
|
+
let rl;
|
|
21973
|
+
try {
|
|
21974
|
+
rl = findKey(JSON.parse(lines[i]), "rate_limits");
|
|
21975
|
+
} catch {
|
|
21976
|
+
continue;
|
|
21977
|
+
}
|
|
21978
|
+
if (!rl) continue;
|
|
21979
|
+
const windows = [];
|
|
21980
|
+
for (const key of ["primary", "secondary"]) {
|
|
21981
|
+
const w = rl[key];
|
|
21982
|
+
if (!w || typeof w !== "object" || w.used_percent == null) continue;
|
|
21983
|
+
const { label, short } = codexWindow(w.window_minutes);
|
|
21984
|
+
const resets = typeof w.resets_at === "number" ? new Date(w.resets_at * 1e3).toLocaleString(void 0, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }) : null;
|
|
21985
|
+
windows.push({ label, short, pct: Number(w.used_percent), resets });
|
|
21986
|
+
}
|
|
21987
|
+
if (windows.length) {
|
|
21988
|
+
const ts = new Date((await fs8.promises.stat(file)).mtimeMs).toISOString();
|
|
21989
|
+
return { ok: true, windows, plan: rl.plan_type ?? null, source, ts };
|
|
21990
|
+
}
|
|
21991
|
+
}
|
|
21992
|
+
}
|
|
21993
|
+
return { ok: false, windows: [], source, ts: now(), error: "no Codex session with usage yet" };
|
|
21994
|
+
}
|
|
21995
|
+
var FETCH_USAGE = {
|
|
21996
|
+
claude: claudeUsage,
|
|
21997
|
+
codex: () => codexUsage()
|
|
21998
|
+
};
|
|
21999
|
+
var SERVICE_NAME = { claude: "Claude", codex: "Codex" };
|
|
22000
|
+
var ResourceMonitor = class {
|
|
22001
|
+
constructor(tabs) {
|
|
22002
|
+
this.tabs = tabs;
|
|
22003
|
+
}
|
|
22004
|
+
machine = { ok: false, ts: now(), error: "sampling\u2026" };
|
|
22005
|
+
history = [];
|
|
22006
|
+
agents = [];
|
|
22007
|
+
usage = {};
|
|
22008
|
+
usageAt = {};
|
|
22009
|
+
usageBusy = /* @__PURE__ */ new Set();
|
|
22010
|
+
prevCpu = cpuTimes();
|
|
22011
|
+
lastAsk = 0;
|
|
22012
|
+
fastUntil = 0;
|
|
22013
|
+
timer = null;
|
|
22014
|
+
inflight = null;
|
|
22015
|
+
/** Latest sample plus 10 minutes of history. `fast` = the panel is open:
|
|
22016
|
+
* sample every ~3 s and refresh quota every minute. */
|
|
22017
|
+
async snapshot(fast = false) {
|
|
22018
|
+
const t = Date.now();
|
|
22019
|
+
this.lastAsk = t;
|
|
22020
|
+
const speedUp = fast && t >= this.fastUntil;
|
|
22021
|
+
if (fast) this.fastUntil = t + FAST_HOLD_MS;
|
|
22022
|
+
if (!this.timer && !this.inflight || speedUp) await this.tick();
|
|
22023
|
+
else this.refreshUsage(this.tabs());
|
|
22024
|
+
return this.build(fast);
|
|
22025
|
+
}
|
|
22026
|
+
stop() {
|
|
22027
|
+
if (this.timer) clearTimeout(this.timer);
|
|
22028
|
+
this.timer = null;
|
|
22029
|
+
this.lastAsk = 0;
|
|
22030
|
+
}
|
|
22031
|
+
tick() {
|
|
22032
|
+
if (!this.inflight) {
|
|
22033
|
+
if (this.timer) clearTimeout(this.timer);
|
|
22034
|
+
this.timer = null;
|
|
22035
|
+
this.inflight = this.sample().catch((err) => log(`[monitor] sample failed: ${err}`)).finally(() => {
|
|
22036
|
+
this.inflight = null;
|
|
22037
|
+
if (Date.now() - this.lastAsk >= IDLE_STOP_MS) return;
|
|
22038
|
+
this.timer = setTimeout(() => {
|
|
22039
|
+
void this.tick();
|
|
22040
|
+
}, Date.now() < this.fastUntil ? FAST_MS : SLOW_MS);
|
|
22041
|
+
this.timer.unref?.();
|
|
22042
|
+
});
|
|
22043
|
+
}
|
|
22044
|
+
return this.inflight;
|
|
22045
|
+
}
|
|
22046
|
+
async sample() {
|
|
22047
|
+
const tabs = this.tabs();
|
|
22048
|
+
this.refreshUsage(tabs);
|
|
22049
|
+
try {
|
|
22050
|
+
const [mem, disk, gpu, table] = await Promise.all([memory(), diskUsage(), appleGpu(), processTable()]);
|
|
22051
|
+
const cur = cpuTimes();
|
|
22052
|
+
const dTotal = cur.total - this.prevCpu.total;
|
|
22053
|
+
const cpuPct = dTotal > 0 ? round(100 * (1 - (cur.idle - this.prevCpu.idle) / dTotal)) : null;
|
|
22054
|
+
this.prevCpu = cur;
|
|
22055
|
+
this.machine = {
|
|
22056
|
+
ok: true,
|
|
22057
|
+
ts: now(),
|
|
22058
|
+
cpu: { pct: cpuPct, cores: os6.cpus().length, load: os6.loadavg().map((x) => round(x, 2)) },
|
|
22059
|
+
mem,
|
|
22060
|
+
disk,
|
|
22061
|
+
gpu
|
|
22062
|
+
};
|
|
22063
|
+
this.agents = tabs.map((tab) => agentProcess(tab, table));
|
|
22064
|
+
const t = Date.now();
|
|
22065
|
+
this.history.push({ t, cpu: cpuPct, mem: mem.pct, gpu });
|
|
22066
|
+
while (this.history.length && this.history[0].t < t - HISTORY_MS) this.history.shift();
|
|
22067
|
+
} catch (err) {
|
|
22068
|
+
this.machine = { ok: false, ts: now(), error: String(err).slice(0, 200) };
|
|
22069
|
+
}
|
|
22070
|
+
}
|
|
22071
|
+
refreshUsage(tabs) {
|
|
22072
|
+
const every = Date.now() < this.fastUntil ? USAGE_FAST_MS : USAGE_SLOW_MS;
|
|
22073
|
+
for (const s of this.servicesInUse(tabs)) {
|
|
22074
|
+
if (this.usageBusy.has(s) || Date.now() - (this.usageAt[s] ?? 0) < every) continue;
|
|
22075
|
+
this.usageBusy.add(s);
|
|
22076
|
+
FETCH_USAGE[s]().catch((err) => ({ ok: false, windows: [], source: s, ts: now(), error: String(err).slice(0, 200) })).then((u) => {
|
|
22077
|
+
this.usage[s] = u;
|
|
22078
|
+
this.usageAt[s] = Date.now();
|
|
22079
|
+
if (!u.ok) log(`[monitor] ${s} usage unavailable: ${u.error}`);
|
|
22080
|
+
}).finally(() => this.usageBusy.delete(s));
|
|
22081
|
+
}
|
|
22082
|
+
}
|
|
22083
|
+
servicesInUse(tabs) {
|
|
22084
|
+
return [...new Set(tabs.map((t) => serviceFor(t.agent)).filter((s) => s !== null))];
|
|
22085
|
+
}
|
|
22086
|
+
build(fast) {
|
|
22087
|
+
const tabs = this.tabs();
|
|
22088
|
+
const active = tabs.find((t) => t.active);
|
|
22089
|
+
const usage = {};
|
|
22090
|
+
for (const s of this.servicesInUse(tabs)) {
|
|
22091
|
+
if (this.usage[s]) usage[s] = this.usage[s];
|
|
22092
|
+
}
|
|
22093
|
+
return {
|
|
22094
|
+
machine: {
|
|
22095
|
+
...this.machine,
|
|
22096
|
+
label: process.platform === "darwin" ? "This Mac" : os6.hostname(),
|
|
22097
|
+
history: [...this.history]
|
|
22098
|
+
},
|
|
22099
|
+
agents: this.agents,
|
|
22100
|
+
activeService: active ? serviceFor(active.agent) : null,
|
|
22101
|
+
usage,
|
|
22102
|
+
interval: fast ? FAST_MS : SLOW_MS
|
|
22103
|
+
};
|
|
22104
|
+
}
|
|
22105
|
+
};
|
|
22106
|
+
function summarizeSnapshot(s) {
|
|
22107
|
+
const parts = [];
|
|
22108
|
+
const m = s.machine;
|
|
22109
|
+
if (m.ok) {
|
|
22110
|
+
const res = [
|
|
22111
|
+
m.cpu?.pct != null ? `CPU ${Math.round(m.cpu.pct)}%` : "",
|
|
22112
|
+
m.mem ? `RAM ${Math.round(m.mem.pct)}%` : ""
|
|
22113
|
+
].filter(Boolean).join(" ");
|
|
22114
|
+
if (res) parts.push(res);
|
|
22115
|
+
}
|
|
22116
|
+
const u = s.activeService ? s.usage[s.activeService] : void 0;
|
|
22117
|
+
if (s.activeService && u?.ok) {
|
|
22118
|
+
const wins = u.windows.filter((w) => w.short).map((w) => `${w.short} ${Math.round(w.pct)}%`);
|
|
22119
|
+
if (wins.length) parts.push(`${SERVICE_NAME[s.activeService]} ${wins.join(" ")}`);
|
|
22120
|
+
}
|
|
22121
|
+
return parts.join(" \xB7 ");
|
|
22122
|
+
}
|
|
22123
|
+
|
|
21699
22124
|
// src/main/updateCheck.ts
|
|
21700
22125
|
import https from "https";
|
|
21701
22126
|
var NPM_PACKAGE = "@phenx-inc/ctlsurf";
|
|
@@ -21880,6 +22305,13 @@ async function main() {
|
|
|
21880
22305
|
const ptySize = tui.getPtySize();
|
|
21881
22306
|
await orchestrator.spawnAgent(HEADLESS_TAB, agent, args.cwd, { trackTime: trackTimeOverride });
|
|
21882
22307
|
orchestrator.resizePty(HEADLESS_TAB, ptySize.cols, ptySize.rows);
|
|
22308
|
+
const monitor = new ResourceMonitor(() => orchestrator.getTabProcesses());
|
|
22309
|
+
const refreshResources = () => {
|
|
22310
|
+
monitor.snapshot().then((snap) => tui.update({ resources: summarizeSnapshot(snap) })).catch(() => {
|
|
22311
|
+
});
|
|
22312
|
+
};
|
|
22313
|
+
refreshResources();
|
|
22314
|
+
setInterval(refreshResources, 1e4).unref();
|
|
21883
22315
|
if (isCodingAgent(agent)) {
|
|
21884
22316
|
setTimeout(() => {
|
|
21885
22317
|
orchestrator.writePty(HEADLESS_TAB, "hello\r");
|
|
@@ -21908,6 +22340,7 @@ async function main() {
|
|
|
21908
22340
|
if (process.stdin.isTTY) {
|
|
21909
22341
|
process.stdin.setRawMode(false);
|
|
21910
22342
|
}
|
|
22343
|
+
monitor.stop();
|
|
21911
22344
|
tui.destroy();
|
|
21912
22345
|
await orchestrator.shutdown();
|
|
21913
22346
|
process.exit(0);
|