@phenx-inc/ctlsurf 0.3.13 → 0.3.14
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 +38 -22
- package/out/headless/index.mjs +247 -1
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +303 -46
- package/out/preload/index.js +5 -0
- package/out/renderer/assets/{cssMode-CYoo4t9f.js → cssMode-G_SDogBL.js} +3 -3
- package/out/renderer/assets/{freemarker2--UQnPZsn.js → freemarker2-BzEus0h2.js} +1 -1
- package/out/renderer/assets/{handlebars-DVDrmX0C.js → handlebars-Et995f6O.js} +1 -1
- package/out/renderer/assets/{html-D1-cXoLy.js → html-D4wgKxPD.js} +1 -1
- package/out/renderer/assets/{htmlMode-f5nBuprq.js → htmlMode-DSxpefzL.js} +3 -3
- package/out/renderer/assets/{index-65hyKM_8.css → index-AQ346NMi.css} +386 -0
- package/out/renderer/assets/{index-D23nru43.js → index-ByJTqkiQ.js} +318 -22
- package/out/renderer/assets/{javascript-CcarFzBL.js → javascript-CzLoo8aq.js} +2 -2
- package/out/renderer/assets/{jsonMode-BvF-xK9U.js → jsonMode-BrwPy7fY.js} +3 -3
- package/out/renderer/assets/{liquid-CHLtUKl2.js → liquid-BsfPf6YG.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-B9aNeatS.js → lspLanguageFeatures-CxLZ421s.js} +1 -1
- package/out/renderer/assets/{mdx-HGDrkifZ.js → mdx-CPvHIsAR.js} +1 -1
- package/out/renderer/assets/{python-B_dPzjJ6.js → python-Dr7dCUjG.js} +1 -1
- package/out/renderer/assets/{razor-CHheM4ot.js → razor-a7zjD7Y3.js} +1 -1
- package/out/renderer/assets/{tsMode-CdC3i1gG.js → tsMode-B7KLV2X6.js} +1 -1
- package/out/renderer/assets/{typescript-BX6guVRK.js → typescript-Cjuzf37q.js} +1 -1
- package/out/renderer/assets/{xml-CpS-pOPE.js → xml-Yz9xINtk.js} +1 -1
- package/out/renderer/assets/{yaml-Du0AjOHW.js → yaml-DtKnp5J0.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/ctlsurfApi.ts +11 -0
- package/src/main/index.ts +20 -0
- package/src/main/orchestrator.ts +37 -0
- package/src/main/ticketStore.ts +252 -0
- package/src/preload/index.ts +10 -0
- package/src/renderer/App.tsx +21 -0
- package/src/renderer/components/TicketPanel.tsx +308 -0
- package/src/renderer/styles.css +386 -0
package/bin/ctlsurf-worker.js
CHANGED
|
@@ -460,35 +460,51 @@ function ask(question) {
|
|
|
460
460
|
})
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
463
|
+
// Export CTLSURF_API_KEY to the OS so MCP clients pick it up. On Windows
|
|
464
|
+
// this is a persistent user env var (setx); elsewhere it's an export line
|
|
465
|
+
// in the shell rc. Either way it applies to new shells, not the current one.
|
|
466
|
+
function exportKeyToEnv(key) {
|
|
467
|
+
if (process.platform === 'win32') {
|
|
468
|
+
try {
|
|
469
|
+
execFileSync('setx', ['CTLSURF_API_KEY', key], { stdio: 'ignore' })
|
|
470
|
+
console.log(` ${G}✓${R} Set CTLSURF_API_KEY (user environment)`)
|
|
471
|
+
console.log(` ${D}Open a new terminal for the env var to apply.${R}`)
|
|
472
|
+
} catch {
|
|
473
|
+
console.log(` ${Y}!${R} Could not run setx — set CTLSURF_API_KEY manually.`)
|
|
474
|
+
}
|
|
475
|
+
return
|
|
469
476
|
}
|
|
470
|
-
console.log(` ${G}✓${R} Saved to worker settings.`)
|
|
471
477
|
|
|
472
478
|
const home = os.homedir()
|
|
473
479
|
const rc = ['.zshrc', '.bashrc', '.bash_profile']
|
|
474
480
|
.map((f) => path.join(home, f))
|
|
475
481
|
.find((f) => fs.existsSync(f))
|
|
476
|
-
if (rc)
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
482
|
+
if (!rc) return
|
|
483
|
+
try {
|
|
484
|
+
let content = fs.readFileSync(rc, 'utf-8')
|
|
485
|
+
content = content
|
|
486
|
+
.split('\n')
|
|
487
|
+
.filter((l) => !/^\s*export\s+CTLSURF_API_KEY=/.test(l))
|
|
488
|
+
.join('\n')
|
|
489
|
+
if (content && !content.endsWith('\n')) content += '\n'
|
|
490
|
+
content += `\n# ctlsurf worker API key\nexport CTLSURF_API_KEY="${key}"\n`
|
|
491
|
+
fs.writeFileSync(rc, content)
|
|
492
|
+
console.log(` ${G}✓${R} Exported CTLSURF_API_KEY in ${rc}`)
|
|
493
|
+
console.log(` ${D}Restart your shell or run: source ${rc}${R}`)
|
|
494
|
+
} catch {
|
|
495
|
+
console.log(` ${Y}!${R} Could not update ${rc} — set CTLSURF_API_KEY manually.`)
|
|
491
496
|
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Persist a key to every worker settings.json profile + the OS environment.
|
|
500
|
+
function persistKey(key) {
|
|
501
|
+
for (const p of allSettingsPaths()) {
|
|
502
|
+
const s = normalizeSettings(readSettings(p))
|
|
503
|
+
activeProfile(s).apiKey = key
|
|
504
|
+
writeSettings(p, s)
|
|
505
|
+
}
|
|
506
|
+
console.log(` ${G}✓${R} Saved to worker settings.`)
|
|
507
|
+
exportKeyToEnv(key)
|
|
492
508
|
console.log('')
|
|
493
509
|
}
|
|
494
510
|
|
package/out/headless/index.mjs
CHANGED
|
@@ -5471,7 +5471,7 @@ var require_package = __commonJS({
|
|
|
5471
5471
|
"package.json"(exports, module) {
|
|
5472
5472
|
module.exports = {
|
|
5473
5473
|
name: "@phenx-inc/ctlsurf",
|
|
5474
|
-
version: "0.3.
|
|
5474
|
+
version: "0.3.14",
|
|
5475
5475
|
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",
|
|
5476
5476
|
main: "out/main/index.js",
|
|
5477
5477
|
bin: {
|
|
@@ -5738,6 +5738,14 @@ var CtlsurfApi = class {
|
|
|
5738
5738
|
async updateRow(blockId, rowId, data) {
|
|
5739
5739
|
return this.request("PUT", `/datastore/${blockId}/rows/${rowId}`, { data });
|
|
5740
5740
|
}
|
|
5741
|
+
async queryRows(blockId, opts) {
|
|
5742
|
+
const params = new URLSearchParams();
|
|
5743
|
+
if (opts?.orderBy) params.set("order_by", opts.orderBy);
|
|
5744
|
+
if (opts?.order) params.set("order", opts.order);
|
|
5745
|
+
params.set("limit", String(opts?.limit ?? 200));
|
|
5746
|
+
const qs = params.toString();
|
|
5747
|
+
return this.request("GET", `/datastore/${blockId}/rows${qs ? `?${qs}` : ""}`);
|
|
5748
|
+
}
|
|
5741
5749
|
async getDatastoreSchema(blockId) {
|
|
5742
5750
|
return this.request("GET", `/datastore/${blockId}/schema`);
|
|
5743
5751
|
}
|
|
@@ -6607,6 +6615,213 @@ var TimeTracker = class {
|
|
|
6607
6615
|
}
|
|
6608
6616
|
};
|
|
6609
6617
|
|
|
6618
|
+
// src/main/ticketStore.ts
|
|
6619
|
+
var DATASTORE_TITLE2 = "Tickets";
|
|
6620
|
+
var AGENT_DATASTORE_PAGE_TITLE2 = "Agent Datastore";
|
|
6621
|
+
var SYSTEM_KEY2 = "tickets";
|
|
6622
|
+
var STATUS_OPTIONS = [
|
|
6623
|
+
{ value: "Open", color: "blue" },
|
|
6624
|
+
{ value: "In Progress", color: "yellow" },
|
|
6625
|
+
{ value: "Blocked", color: "red" },
|
|
6626
|
+
{ value: "Done", color: "green" }
|
|
6627
|
+
];
|
|
6628
|
+
var PRIORITY_OPTIONS = [
|
|
6629
|
+
{ value: "Low", color: "gray" },
|
|
6630
|
+
{ value: "Med", color: "yellow" },
|
|
6631
|
+
{ value: "High", color: "red" }
|
|
6632
|
+
];
|
|
6633
|
+
var COLUMNS2 = [
|
|
6634
|
+
{ name: "Title", type: "text" },
|
|
6635
|
+
{ name: "Description", type: "text" },
|
|
6636
|
+
{ name: "Status", type: "select", options: STATUS_OPTIONS },
|
|
6637
|
+
{ name: "Priority", type: "select", options: PRIORITY_OPTIONS },
|
|
6638
|
+
{ name: "Created", type: "date" }
|
|
6639
|
+
];
|
|
6640
|
+
function log3(...args) {
|
|
6641
|
+
try {
|
|
6642
|
+
console.log("[ticket-store]", ...args);
|
|
6643
|
+
} catch {
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
function findPageByTitle2(pages, title) {
|
|
6647
|
+
for (const p of pages) {
|
|
6648
|
+
if (p?.title === title) return p;
|
|
6649
|
+
if (p?.children?.length) {
|
|
6650
|
+
const c = findPageByTitle2(p.children, title);
|
|
6651
|
+
if (c) return c;
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
return null;
|
|
6655
|
+
}
|
|
6656
|
+
var TicketStore = class {
|
|
6657
|
+
api;
|
|
6658
|
+
blockCache = /* @__PURE__ */ new Map();
|
|
6659
|
+
constructor(api) {
|
|
6660
|
+
this.api = api;
|
|
6661
|
+
}
|
|
6662
|
+
/** Resolves (or creates) the project's Tickets datastore and appends a row. */
|
|
6663
|
+
async addTicket(cwd, input) {
|
|
6664
|
+
const title = input.title?.trim();
|
|
6665
|
+
if (!title) return { ok: false, error: "Title is required" };
|
|
6666
|
+
try {
|
|
6667
|
+
const blockId = await this.ensureDatastore(cwd, true);
|
|
6668
|
+
if (!blockId) {
|
|
6669
|
+
return { ok: false, error: "No ctlsurf project found for this folder" };
|
|
6670
|
+
}
|
|
6671
|
+
await this.api.addRow(blockId, {
|
|
6672
|
+
Title: title,
|
|
6673
|
+
Description: input.description?.trim() || "",
|
|
6674
|
+
Status: input.status || "Open",
|
|
6675
|
+
Priority: input.priority || "Med",
|
|
6676
|
+
Created: (/* @__PURE__ */ new Date()).toISOString()
|
|
6677
|
+
});
|
|
6678
|
+
log3(`Added ticket "${title}" for ${cwd}`);
|
|
6679
|
+
return { ok: true };
|
|
6680
|
+
} catch (err) {
|
|
6681
|
+
log3(`addTicket failed for ${cwd}: ${err?.message || err}`);
|
|
6682
|
+
return { ok: false, error: err?.message || String(err) };
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
/** Updates an existing ticket row in the project's Tickets datastore. */
|
|
6686
|
+
async updateTicket(cwd, rowId, input) {
|
|
6687
|
+
const title = input.title?.trim();
|
|
6688
|
+
if (!title) return { ok: false, error: "Title is required" };
|
|
6689
|
+
try {
|
|
6690
|
+
const blockId = await this.ensureDatastore(cwd, false);
|
|
6691
|
+
if (!blockId) return { ok: false, error: "No Tickets datastore for this project" };
|
|
6692
|
+
await this.api.updateRow(blockId, rowId, {
|
|
6693
|
+
Title: title,
|
|
6694
|
+
Description: input.description?.trim() || "",
|
|
6695
|
+
Status: input.status || "Open",
|
|
6696
|
+
Priority: input.priority || "Med"
|
|
6697
|
+
});
|
|
6698
|
+
log3(`Updated ticket ${rowId} for ${cwd}`);
|
|
6699
|
+
return { ok: true };
|
|
6700
|
+
} catch (err) {
|
|
6701
|
+
log3(`updateTicket failed for ${cwd}: ${err?.message || err}`);
|
|
6702
|
+
return { ok: false, error: err?.message || String(err) };
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
/** Lists existing tickets for the project, newest first. Does not create the
|
|
6706
|
+
* datastore — an unconfigured project simply has no tickets yet. */
|
|
6707
|
+
async listTickets(cwd) {
|
|
6708
|
+
try {
|
|
6709
|
+
const blockId = await this.ensureDatastore(cwd, false);
|
|
6710
|
+
if (!blockId) return { ok: true, tickets: [] };
|
|
6711
|
+
const res = await this.api.queryRows(blockId, { orderBy: "Created", order: "desc", limit: 200 });
|
|
6712
|
+
const tickets = (res?.rows || []).map((r) => ({
|
|
6713
|
+
id: r.id,
|
|
6714
|
+
title: String(r.data?.Title ?? ""),
|
|
6715
|
+
description: String(r.data?.Description ?? ""),
|
|
6716
|
+
status: String(r.data?.Status ?? "Open"),
|
|
6717
|
+
priority: String(r.data?.Priority ?? "Med"),
|
|
6718
|
+
created: r.data?.Created ?? r.created_at ?? null
|
|
6719
|
+
}));
|
|
6720
|
+
return { ok: true, tickets };
|
|
6721
|
+
} catch (err) {
|
|
6722
|
+
log3(`listTickets failed for ${cwd}: ${err?.message || err}`);
|
|
6723
|
+
return { ok: false, tickets: [], error: err?.message || String(err) };
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
async ensureDatastore(cwd, create) {
|
|
6727
|
+
const cached = this.blockCache.get(cwd);
|
|
6728
|
+
if (cached) return cached;
|
|
6729
|
+
let folder = null;
|
|
6730
|
+
try {
|
|
6731
|
+
folder = await this.api.findFolderByPath(cwd);
|
|
6732
|
+
} catch {
|
|
6733
|
+
return null;
|
|
6734
|
+
}
|
|
6735
|
+
if (!folder?.id) return null;
|
|
6736
|
+
const folderDetail = await this.api.getFolder(folder.id);
|
|
6737
|
+
const agentPage = findPageByTitle2(folderDetail?.pages || [], AGENT_DATASTORE_PAGE_TITLE2);
|
|
6738
|
+
if (!agentPage?.id) return null;
|
|
6739
|
+
const blockId = await this.findOrAdoptBlock(agentPage.id);
|
|
6740
|
+
if (blockId) {
|
|
6741
|
+
await this.ensureColumns(blockId);
|
|
6742
|
+
this.blockCache.set(cwd, blockId);
|
|
6743
|
+
return blockId;
|
|
6744
|
+
}
|
|
6745
|
+
if (!create) return null;
|
|
6746
|
+
const columns = COLUMNS2.map((c, i) => ({
|
|
6747
|
+
id: `col_${i}`,
|
|
6748
|
+
name: c.name,
|
|
6749
|
+
type: c.type,
|
|
6750
|
+
...c.options ? { options: c.options } : {}
|
|
6751
|
+
}));
|
|
6752
|
+
const created = await this.api.createBlock(agentPage.id, {
|
|
6753
|
+
type: "datastore",
|
|
6754
|
+
title: DATASTORE_TITLE2,
|
|
6755
|
+
props: { columns, system_key: SYSTEM_KEY2 }
|
|
6756
|
+
});
|
|
6757
|
+
if (created?.id) {
|
|
6758
|
+
log3(`Created "${DATASTORE_TITLE2}" datastore on Agent Datastore page for ${cwd}`);
|
|
6759
|
+
this.blockCache.set(cwd, created.id);
|
|
6760
|
+
return created.id;
|
|
6761
|
+
}
|
|
6762
|
+
return null;
|
|
6763
|
+
}
|
|
6764
|
+
/** Finds an existing Tickets block on the page. Prefers a system_key match
|
|
6765
|
+
* (which survives title renames). Falls back to title match for legacy blocks
|
|
6766
|
+
* and backfills system_key on the way out. */
|
|
6767
|
+
async findOrAdoptBlock(pageId) {
|
|
6768
|
+
const summaries = await this.api.getPageBlockSummaries(pageId) || [];
|
|
6769
|
+
const datastoreSummaries = summaries.filter((b) => b?.type === "datastore" && b?.id);
|
|
6770
|
+
if (datastoreSummaries.length === 0) return null;
|
|
6771
|
+
let titleFallbackId = null;
|
|
6772
|
+
let titleFallbackProps = null;
|
|
6773
|
+
for (const s of datastoreSummaries) {
|
|
6774
|
+
try {
|
|
6775
|
+
const block = await this.api.getBlock(s.id);
|
|
6776
|
+
const props = block?.props || {};
|
|
6777
|
+
if (props.system_key === SYSTEM_KEY2) {
|
|
6778
|
+
return s.id;
|
|
6779
|
+
}
|
|
6780
|
+
if (s.title === DATASTORE_TITLE2 && titleFallbackId === null) {
|
|
6781
|
+
titleFallbackId = s.id;
|
|
6782
|
+
titleFallbackProps = props;
|
|
6783
|
+
}
|
|
6784
|
+
} catch (err) {
|
|
6785
|
+
log3(`getBlock(${s.id}) failed during lookup: ${err?.message || err}`);
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
if (titleFallbackId) {
|
|
6789
|
+
try {
|
|
6790
|
+
await this.api.updateBlock(titleFallbackId, {
|
|
6791
|
+
props: { ...titleFallbackProps || {}, system_key: SYSTEM_KEY2 }
|
|
6792
|
+
});
|
|
6793
|
+
log3(`Backfilled system_key on legacy Tickets block ${titleFallbackId}`);
|
|
6794
|
+
} catch (err) {
|
|
6795
|
+
log3(`backfill system_key failed on ${titleFallbackId}: ${err?.message || err}`);
|
|
6796
|
+
}
|
|
6797
|
+
return titleFallbackId;
|
|
6798
|
+
}
|
|
6799
|
+
return null;
|
|
6800
|
+
}
|
|
6801
|
+
async ensureColumns(blockId) {
|
|
6802
|
+
try {
|
|
6803
|
+
const schema = await this.api.getDatastoreSchema(blockId);
|
|
6804
|
+
const existingCols = schema.columns || [];
|
|
6805
|
+
const existingNames = new Set(existingCols.map((c) => c.name));
|
|
6806
|
+
const missing = COLUMNS2.filter((c) => !existingNames.has(c.name));
|
|
6807
|
+
if (missing.length === 0) return;
|
|
6808
|
+
const usedIds = new Set(existingCols.map((c) => c.id));
|
|
6809
|
+
let nextIdx = existingCols.length;
|
|
6810
|
+
const appended = missing.map((c) => {
|
|
6811
|
+
let id = `col_${nextIdx++}`;
|
|
6812
|
+
while (usedIds.has(id)) id = `col_${nextIdx++}`;
|
|
6813
|
+
usedIds.add(id);
|
|
6814
|
+
return { id, name: c.name, type: c.type, ...c.options ? { options: c.options } : {} };
|
|
6815
|
+
});
|
|
6816
|
+
const merged = [...existingCols, ...appended];
|
|
6817
|
+
await this.api.updateDatastoreSchema(blockId, merged);
|
|
6818
|
+
log3(`Added ${missing.length} missing column(s) to existing Tickets datastore: ${missing.map((c) => c.name).join(", ")}`);
|
|
6819
|
+
} catch (err) {
|
|
6820
|
+
log3(`ensureColumns failed: ${err?.message || err}`);
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
};
|
|
6824
|
+
|
|
6610
6825
|
// src/main/orchestrator.ts
|
|
6611
6826
|
var DEFAULT_IDLE_TIMEOUT_MIN = 15;
|
|
6612
6827
|
var DEFAULT_PROFILES = {
|
|
@@ -6629,6 +6844,7 @@ var Orchestrator = class {
|
|
|
6629
6844
|
bridge = new ConversationBridge();
|
|
6630
6845
|
workerWs;
|
|
6631
6846
|
timeTracker = new TimeTracker(this.ctlsurfApi);
|
|
6847
|
+
ticketStore = new TicketStore(this.ctlsurfApi);
|
|
6632
6848
|
// State
|
|
6633
6849
|
tabs = /* @__PURE__ */ new Map();
|
|
6634
6850
|
activeTabId = null;
|
|
@@ -6977,6 +7193,36 @@ var Orchestrator = class {
|
|
|
6977
7193
|
await this.timeTracker.endSession(this.activeTabId);
|
|
6978
7194
|
}
|
|
6979
7195
|
}
|
|
7196
|
+
// ─── Tickets (active tab) ───────────────────────
|
|
7197
|
+
/** cwd of the focused terminal tab, or null if no tab is active. */
|
|
7198
|
+
getActiveTabCwd() {
|
|
7199
|
+
if (!this.activeTabId) return null;
|
|
7200
|
+
return this.tabs.get(this.activeTabId)?.cwd ?? null;
|
|
7201
|
+
}
|
|
7202
|
+
async addTicketForActiveTab(input) {
|
|
7203
|
+
const cwd = this.getActiveTabCwd();
|
|
7204
|
+
if (!cwd) return { ok: false, error: "No active terminal tab" };
|
|
7205
|
+
if (!this.ctlsurfApi.getApiKey()) {
|
|
7206
|
+
return { ok: false, error: "ctlsurf API key not configured" };
|
|
7207
|
+
}
|
|
7208
|
+
return this.ticketStore.addTicket(cwd, input);
|
|
7209
|
+
}
|
|
7210
|
+
async updateTicketForActiveTab(rowId, input) {
|
|
7211
|
+
const cwd = this.getActiveTabCwd();
|
|
7212
|
+
if (!cwd) return { ok: false, error: "No active terminal tab" };
|
|
7213
|
+
if (!this.ctlsurfApi.getApiKey()) {
|
|
7214
|
+
return { ok: false, error: "ctlsurf API key not configured" };
|
|
7215
|
+
}
|
|
7216
|
+
return this.ticketStore.updateTicket(cwd, rowId, input);
|
|
7217
|
+
}
|
|
7218
|
+
async listTicketsForActiveTab() {
|
|
7219
|
+
const cwd = this.getActiveTabCwd();
|
|
7220
|
+
if (!cwd) return { ok: false, tickets: [], error: "No active terminal tab" };
|
|
7221
|
+
if (!this.ctlsurfApi.getApiKey()) {
|
|
7222
|
+
return { ok: false, tickets: [], error: "ctlsurf API key not configured" };
|
|
7223
|
+
}
|
|
7224
|
+
return this.ticketStore.listTickets(cwd);
|
|
7225
|
+
}
|
|
6980
7226
|
// ─── Worker WebSocket ───────────────────────────
|
|
6981
7227
|
connectWorkerWs(agent, cwd) {
|
|
6982
7228
|
const profile = this.getActiveProfile();
|