@phenx-inc/ctlsurf 0.2.0 → 0.3.0

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.
Files changed (35) hide show
  1. package/out/headless/index.mjs +320 -44
  2. package/out/headless/index.mjs.map +4 -4
  3. package/out/main/index.js +275 -15
  4. package/out/preload/index.js +3 -0
  5. package/out/renderer/assets/{cssMode-D3kH1Kju.js → cssMode-DiOmyihM.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-BCHZUSLb.js → freemarker2-BAfv60yb.js} +1 -1
  7. package/out/renderer/assets/{handlebars-DKx-Fw-H.js → handlebars-Ult17NzQ.js} +1 -1
  8. package/out/renderer/assets/{html-BSCM04uL.js → html-DCxh4J-1.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-BucU1MUc.js → htmlMode-CQ5Xenrg.js} +3 -3
  10. package/out/renderer/assets/{index-BsdOeO0U.js → index-BnCJ1IaZ.js} +106 -28
  11. package/out/renderer/assets/{index-BzF7I1my.css → index-CrTu3Z4M.css} +21 -0
  12. package/out/renderer/assets/{javascript-bPY5C4uq.js → javascript-U5dsRcHx.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-BmJotb6E.js → jsonMode-DshPNyVy.js} +3 -3
  14. package/out/renderer/assets/{liquid-Cja_Pzh3.js → liquid-jHHLYTlB.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-hoVZfVKv.js → lspLanguageFeatures-CUafmPGy.js} +1 -1
  16. package/out/renderer/assets/{mdx-C0s81MOq.js → mdx-Ct-tiY6g.js} +1 -1
  17. package/out/renderer/assets/{python-CulkBOJr.js → python-wD3UwKPV.js} +1 -1
  18. package/out/renderer/assets/{razor-czmzhwVZ.js → razor-11ECS4oH.js} +1 -1
  19. package/out/renderer/assets/{tsMode-B90EqYGx.js → tsMode-D-7JexQ_.js} +1 -1
  20. package/out/renderer/assets/{typescript-Ckc6emP2.js → typescript-Cvna1mak.js} +1 -1
  21. package/out/renderer/assets/{xml-CKh-JyGN.js → xml-JsEaImjA.js} +1 -1
  22. package/out/renderer/assets/{yaml-B49zLim4.js → yaml-B8pCNDb_.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/src/main/ctlsurfApi.ts +26 -0
  26. package/src/main/headless.ts +33 -28
  27. package/src/main/index.ts +8 -0
  28. package/src/main/orchestrator.ts +63 -2
  29. package/src/main/timeTracker.ts +223 -0
  30. package/src/main/tui.ts +25 -5
  31. package/src/preload/index.ts +7 -1
  32. package/src/renderer/App.tsx +36 -0
  33. package/src/renderer/components/SettingsDialog.tsx +38 -1
  34. package/src/renderer/components/TerminalPanel.tsx +14 -6
  35. package/src/renderer/styles.css +21 -0
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-D3kH1Kju.js","./lspLanguageFeatures-hoVZfVKv.js","./htmlMode-BucU1MUc.js","./jsonMode-BmJotb6E.js","./javascript-bPY5C4uq.js","./typescript-Ckc6emP2.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-DiOmyihM.js","./lspLanguageFeatures-CUafmPGy.js","./htmlMode-CQ5Xenrg.js","./jsonMode-DshPNyVy.js","./javascript-U5dsRcHx.js","./typescript-Cvna1mak.js"])))=>i.map(i=>d[i]);
2
2
  function getDefaultExportFromCjs(x) {
3
3
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
4
  }
@@ -18866,12 +18866,19 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
18866
18866
  }, [tabId, agent, onSpawn]);
18867
18867
  reactExports.useEffect(() => {
18868
18868
  if (isActive) {
18869
- const state = _terminals.get(tabId);
18870
- if (state) {
18871
- state.fitAddon.fit();
18872
- state.terminal.focus();
18873
- }
18874
18869
  window.worker.setActiveTab(tabId);
18870
+ requestAnimationFrame(() => {
18871
+ setTimeout(() => {
18872
+ const state = _terminals.get(tabId);
18873
+ if (state) {
18874
+ state.fitAddon.fit();
18875
+ state.terminal.focus();
18876
+ scrollIfPinned(tabId, state.terminal);
18877
+ const { cols, rows } = state.terminal;
18878
+ window.worker.resizePty(tabId, cols, rows);
18879
+ }
18880
+ }, 50);
18881
+ });
18875
18882
  }
18876
18883
  }, [isActive, tabId]);
18877
18884
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "terminal-container", ref: containerRef });
@@ -206549,7 +206556,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
206549
206556
  modeConfigurationDefault$2
206550
206557
  );
206551
206558
  function getMode$3() {
206552
- return __vitePreload(() => import("./cssMode-D3kH1Kju.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
206559
+ return __vitePreload(() => import("./cssMode-DiOmyihM.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
206553
206560
  }
206554
206561
  languages.onLanguage("less", () => {
206555
206562
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -206654,7 +206661,7 @@ const razorLanguageService = registerHTMLLanguageService(
206654
206661
  );
206655
206662
  const razorDefaults = razorLanguageService.defaults;
206656
206663
  function getMode$2() {
206657
- return __vitePreload(() => import("./htmlMode-BucU1MUc.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
206664
+ return __vitePreload(() => import("./htmlMode-CQ5Xenrg.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
206658
206665
  }
206659
206666
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
206660
206667
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -206738,7 +206745,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
206738
206745
  );
206739
206746
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
206740
206747
  function getMode$1() {
206741
- return __vitePreload(() => import("./jsonMode-BmJotb6E.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
206748
+ return __vitePreload(() => import("./jsonMode-DshPNyVy.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
206742
206749
  }
206743
206750
  languages.register({
206744
206751
  id: "json",
@@ -206984,7 +206991,7 @@ const getJavaScriptWorker = () => {
206984
206991
  return getMode().then((mode) => mode.getJavaScriptWorker());
206985
206992
  };
206986
206993
  function getMode() {
206987
- return __vitePreload(() => import("./tsMode-B90EqYGx.js"), true ? [] : void 0, import.meta.url);
206994
+ return __vitePreload(() => import("./tsMode-D-7JexQ_.js"), true ? [] : void 0, import.meta.url);
206988
206995
  }
206989
206996
  languages.onLanguage("typescript", () => {
206990
206997
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -207179,49 +207186,49 @@ registerLanguage({
207179
207186
  extensions: [".ftl", ".ftlh", ".ftlx"],
207180
207187
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
207181
207188
  loader: () => {
207182
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207189
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207183
207190
  }
207184
207191
  });
207185
207192
  registerLanguage({
207186
207193
  id: "freemarker2.tag-angle.interpolation-dollar",
207187
207194
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
207188
207195
  loader: () => {
207189
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207196
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207190
207197
  }
207191
207198
  });
207192
207199
  registerLanguage({
207193
207200
  id: "freemarker2.tag-bracket.interpolation-dollar",
207194
207201
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
207195
207202
  loader: () => {
207196
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207203
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207197
207204
  }
207198
207205
  });
207199
207206
  registerLanguage({
207200
207207
  id: "freemarker2.tag-angle.interpolation-bracket",
207201
207208
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
207202
207209
  loader: () => {
207203
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207210
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207204
207211
  }
207205
207212
  });
207206
207213
  registerLanguage({
207207
207214
  id: "freemarker2.tag-bracket.interpolation-bracket",
207208
207215
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
207209
207216
  loader: () => {
207210
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207217
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207211
207218
  }
207212
207219
  });
207213
207220
  registerLanguage({
207214
207221
  id: "freemarker2.tag-auto.interpolation-dollar",
207215
207222
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
207216
207223
  loader: () => {
207217
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207224
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207218
207225
  }
207219
207226
  });
207220
207227
  registerLanguage({
207221
207228
  id: "freemarker2.tag-auto.interpolation-bracket",
207222
207229
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
207223
207230
  loader: () => {
207224
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207231
+ return __vitePreload(() => import("./freemarker2-BAfv60yb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207225
207232
  }
207226
207233
  });
207227
207234
  registerLanguage({
@@ -207242,7 +207249,7 @@ registerLanguage({
207242
207249
  extensions: [".handlebars", ".hbs"],
207243
207250
  aliases: ["Handlebars", "handlebars", "hbs"],
207244
207251
  mimetypes: ["text/x-handlebars-template"],
207245
- loader: () => __vitePreload(() => import("./handlebars-DKx-Fw-H.js"), true ? [] : void 0, import.meta.url)
207252
+ loader: () => __vitePreload(() => import("./handlebars-Ult17NzQ.js"), true ? [] : void 0, import.meta.url)
207246
207253
  });
207247
207254
  registerLanguage({
207248
207255
  id: "hcl",
@@ -207255,7 +207262,7 @@ registerLanguage({
207255
207262
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
207256
207263
  aliases: ["HTML", "htm", "html", "xhtml"],
207257
207264
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
207258
- loader: () => __vitePreload(() => import("./html-BSCM04uL.js"), true ? [] : void 0, import.meta.url)
207265
+ loader: () => __vitePreload(() => import("./html-DCxh4J-1.js"), true ? [] : void 0, import.meta.url)
207259
207266
  });
207260
207267
  registerLanguage({
207261
207268
  id: "ini",
@@ -207278,7 +207285,7 @@ registerLanguage({
207278
207285
  filenames: ["jakefile"],
207279
207286
  aliases: ["JavaScript", "javascript", "js"],
207280
207287
  mimetypes: ["text/javascript"],
207281
- loader: () => __vitePreload(() => import("./javascript-bPY5C4uq.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207288
+ loader: () => __vitePreload(() => import("./javascript-U5dsRcHx.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207282
207289
  });
207283
207290
  registerLanguage({
207284
207291
  id: "julia",
@@ -207317,7 +207324,7 @@ registerLanguage({
207317
207324
  extensions: [".liquid", ".html.liquid"],
207318
207325
  aliases: ["Liquid", "liquid"],
207319
207326
  mimetypes: ["application/liquid"],
207320
- loader: () => __vitePreload(() => import("./liquid-Cja_Pzh3.js"), true ? [] : void 0, import.meta.url)
207327
+ loader: () => __vitePreload(() => import("./liquid-jHHLYTlB.js"), true ? [] : void 0, import.meta.url)
207321
207328
  });
207322
207329
  registerLanguage({
207323
207330
  id: "m3",
@@ -207335,7 +207342,7 @@ registerLanguage({
207335
207342
  id: "mdx",
207336
207343
  extensions: [".mdx"],
207337
207344
  aliases: ["MDX", "mdx"],
207338
- loader: () => __vitePreload(() => import("./mdx-C0s81MOq.js"), true ? [] : void 0, import.meta.url)
207345
+ loader: () => __vitePreload(() => import("./mdx-Ct-tiY6g.js"), true ? [] : void 0, import.meta.url)
207339
207346
  });
207340
207347
  registerLanguage({
207341
207348
  id: "mips",
@@ -207434,7 +207441,7 @@ registerLanguage({
207434
207441
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
207435
207442
  aliases: ["Python", "py"],
207436
207443
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
207437
- loader: () => __vitePreload(() => import("./python-CulkBOJr.js"), true ? [] : void 0, import.meta.url)
207444
+ loader: () => __vitePreload(() => import("./python-wD3UwKPV.js"), true ? [] : void 0, import.meta.url)
207438
207445
  });
207439
207446
  registerLanguage({
207440
207447
  id: "qsharp",
@@ -207453,7 +207460,7 @@ registerLanguage({
207453
207460
  extensions: [".cshtml"],
207454
207461
  aliases: ["Razor", "razor"],
207455
207462
  mimetypes: ["text/x-cshtml"],
207456
- loader: () => __vitePreload(() => import("./razor-czmzhwVZ.js"), true ? [] : void 0, import.meta.url)
207463
+ loader: () => __vitePreload(() => import("./razor-11ECS4oH.js"), true ? [] : void 0, import.meta.url)
207457
207464
  });
207458
207465
  registerLanguage({
207459
207466
  id: "redis",
@@ -207586,7 +207593,7 @@ registerLanguage({
207586
207593
  aliases: ["TypeScript", "ts", "typescript"],
207587
207594
  mimetypes: ["text/typescript"],
207588
207595
  loader: () => {
207589
- return __vitePreload(() => import("./typescript-Ckc6emP2.js"), true ? [] : void 0, import.meta.url);
207596
+ return __vitePreload(() => import("./typescript-Cvna1mak.js"), true ? [] : void 0, import.meta.url);
207590
207597
  }
207591
207598
  });
207592
207599
  registerLanguage({
@@ -207631,14 +207638,14 @@ registerLanguage({
207631
207638
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
207632
207639
  aliases: ["XML", "xml"],
207633
207640
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
207634
- loader: () => __vitePreload(() => import("./xml-CKh-JyGN.js"), true ? [] : void 0, import.meta.url)
207641
+ loader: () => __vitePreload(() => import("./xml-JsEaImjA.js"), true ? [] : void 0, import.meta.url)
207635
207642
  });
207636
207643
  registerLanguage({
207637
207644
  id: "yaml",
207638
207645
  extensions: [".yaml", ".yml"],
207639
207646
  aliases: ["YAML", "yaml", "YML", "yml"],
207640
207647
  mimetypes: ["application/x-yaml", "text/x-yaml"],
207641
- loader: () => __vitePreload(() => import("./yaml-B49zLim4.js"), true ? [] : void 0, import.meta.url)
207648
+ loader: () => __vitePreload(() => import("./yaml-B8pCNDb_.js"), true ? [] : void 0, import.meta.url)
207642
207649
  });
207643
207650
  var __defProp = Object.defineProperty;
207644
207651
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -211507,6 +211514,8 @@ function SettingsDialog({ open, onClose }) {
211507
211514
  const [apiKey, setApiKey] = reactExports.useState("");
211508
211515
  const [baseUrl, setBaseUrl] = reactExports.useState("");
211509
211516
  const [dataspacePageId, setDataspacePageId] = reactExports.useState("");
211517
+ const [trackTime, setTrackTime] = reactExports.useState(false);
211518
+ const [idleTimeoutMin, setIdleTimeoutMin] = reactExports.useState(15);
211510
211519
  const [saved, setSaved] = reactExports.useState(false);
211511
211520
  const loadProfiles = async () => {
211512
211521
  const data = await window.worker.listProfiles();
@@ -211526,6 +211535,8 @@ function SettingsDialog({ open, onClose }) {
211526
211535
  setApiKey("");
211527
211536
  setBaseUrl(profile.baseUrl);
211528
211537
  setDataspacePageId(profile.dataspacePageId || "");
211538
+ setTrackTime(!!profile.trackTime);
211539
+ setIdleTimeoutMin(profile.idleTimeoutMin ?? 15);
211529
211540
  setSaved(false);
211530
211541
  };
211531
211542
  const startNewProfile = () => {
@@ -211535,6 +211546,8 @@ function SettingsDialog({ open, onClose }) {
211535
211546
  setApiKey("");
211536
211547
  setBaseUrl("http://localhost:8000");
211537
211548
  setDataspacePageId("");
211549
+ setTrackTime(true);
211550
+ setIdleTimeoutMin(15);
211538
211551
  setSaved(false);
211539
211552
  };
211540
211553
  const handleSave = async () => {
@@ -211542,7 +211555,9 @@ function SettingsDialog({ open, onClose }) {
211542
211555
  const data = {
211543
211556
  name: name.trim(),
211544
211557
  baseUrl: baseUrl.trim() || "https://app.ctlsurf.com",
211545
- dataspacePageId: dataspacePageId.trim()
211558
+ dataspacePageId: dataspacePageId.trim(),
211559
+ trackTime,
211560
+ idleTimeoutMin: Math.max(1, Math.floor(idleTimeoutMin)) || 15
211546
211561
  };
211547
211562
  if (apiKey.trim()) {
211548
211563
  data.apiKey = apiKey.trim();
@@ -211622,6 +211637,31 @@ function SettingsDialog({ open, onClose }) {
211622
211637
  }
211623
211638
  )
211624
211639
  ] }),
211640
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "settings-checkbox", children: [
211641
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
211642
+ "input",
211643
+ {
211644
+ type: "checkbox",
211645
+ checked: trackTime,
211646
+ onChange: (e) => setTrackTime(e.target.checked)
211647
+ }
211648
+ ),
211649
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Track time per project" }),
211650
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "settings-hint", children: `Logs each session to a "Time Tracking" datastore on the project's Agent Datastore page.` })
211651
+ ] }),
211652
+ trackTime && /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { children: [
211653
+ "Idle timeout (min)",
211654
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
211655
+ "input",
211656
+ {
211657
+ type: "number",
211658
+ min: 1,
211659
+ value: idleTimeoutMin,
211660
+ onChange: (e) => setIdleTimeoutMin(parseInt(e.target.value, 10) || 15)
211661
+ }
211662
+ ),
211663
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "settings-hint", children: "Gaps longer than this without terminal activity are counted as idle, not work." })
211664
+ ] }),
211625
211665
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-actions", children: [
211626
211666
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-secondary", onClick: () => setEditingId(null), children: "Back" }),
211627
211667
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-primary", onClick: handleSave, children: saved ? "Saved!" : "Save" })
@@ -211687,6 +211727,7 @@ function App() {
211687
211727
  return [{ id, label: "Terminal 1", agent: null, agentStatus: "idle" }];
211688
211728
  });
211689
211729
  const [activeTabId, setActiveTabId] = reactExports.useState(tabs[0].id);
211730
+ const [trackingActive, setTrackingActive] = reactExports.useState(false);
211690
211731
  const [pickerTargetTabId, setPickerTargetTabId] = reactExports.useState(tabs[0].id);
211691
211732
  const [showAgentPicker, setShowAgentPicker] = reactExports.useState(true);
211692
211733
  const visiblePaneIds = findPaneIds(layout2);
@@ -211712,6 +211753,30 @@ function App() {
211712
211753
  });
211713
211754
  return unsub;
211714
211755
  }, []);
211756
+ reactExports.useEffect(() => {
211757
+ let cancelled = false;
211758
+ const refresh = async () => {
211759
+ try {
211760
+ const r = await window.worker.getTracking();
211761
+ if (!cancelled) setTrackingActive(!!r?.active);
211762
+ } catch {
211763
+ }
211764
+ };
211765
+ refresh();
211766
+ const id = setInterval(refresh, 4e3);
211767
+ return () => {
211768
+ cancelled = true;
211769
+ clearInterval(id);
211770
+ };
211771
+ }, [activeTabId]);
211772
+ const handleToggleTracking = reactExports.useCallback(async () => {
211773
+ try {
211774
+ const r = await window.worker.setTracking(!trackingActive);
211775
+ setTrackingActive(!!r?.active);
211776
+ } catch (err) {
211777
+ console.error("[tracking] toggle failed", err);
211778
+ }
211779
+ }, [trackingActive]);
211715
211780
  const cwdRef = reactExports.useRef(null);
211716
211781
  const handleSpawn = reactExports.useCallback(async (tabId, agent) => {
211717
211782
  setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "active" } : t));
@@ -211912,6 +211977,19 @@ function App() {
211912
211977
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-title", children: "ctlsurf-worker" }),
211913
211978
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "titlebar-controls", children: [
211914
211979
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "titlebar-btn", onClick: () => setShowSettings(true), title: "Settings", children: "Settings" }),
211980
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
211981
+ "button",
211982
+ {
211983
+ className: `titlebar-btn titlebar-icon-btn ${trackingActive ? "active" : ""}`,
211984
+ onClick: handleToggleTracking,
211985
+ title: trackingActive ? "Time tracking on — click to stop" : "Time tracking off — click to start",
211986
+ "aria-label": "Time tracking",
211987
+ children: [
211988
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "tracking-icon", children: "⏱" }),
211989
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `tracking-dot ${trackingActive ? "on" : "off"}` })
211990
+ ]
211991
+ }
211992
+ ),
211915
211993
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
211916
211994
  agents.map((a) => {
211917
211995
  const activeTab = tabs.find((t) => t.id === activeTabId);
@@ -7904,6 +7904,27 @@ html, body, #root {
7904
7904
  .status-dot.idle { background: #565f89; }
7905
7905
  .status-dot.pending { background: #e0af68; }
7906
7906
 
7907
+ .tracking-dot {
7908
+ width: 6px;
7909
+ height: 6px;
7910
+ border-radius: 50%;
7911
+ display: inline-block;
7912
+ vertical-align: middle;
7913
+ }
7914
+ .tracking-dot.on { background: #9ece6a; box-shadow: 0 0 4px #9ece6a; }
7915
+ .tracking-dot.off { background: #565f89; }
7916
+
7917
+ .titlebar-icon-btn {
7918
+ display: inline-flex;
7919
+ align-items: center;
7920
+ gap: 5px;
7921
+ padding: 0 8px;
7922
+ }
7923
+ .tracking-icon {
7924
+ font-size: 14px;
7925
+ line-height: 1;
7926
+ }
7927
+
7907
7928
  /* Editor panel */
7908
7929
  .editor-panel {
7909
7930
  display: flex;
@@ -1,5 +1,5 @@
1
- import { conf as conf$1, language as language$1 } from "./typescript-Ckc6emP2.js";
2
- import "./index-BsdOeO0U.js";
1
+ import { conf as conf$1, language as language$1 } from "./typescript-Cvna1mak.js";
2
+ import "./index-BnCJ1IaZ.js";
3
3
  const conf = conf$1;
4
4
  const language = {
5
5
  // Set defaultToken to invalid to see what you do not tokenize yet
@@ -1,6 +1,6 @@
1
- import { c as createWebWorker, l as languages, e as editor } from "./index-BsdOeO0U.js";
2
- import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-hoVZfVKv.js";
3
- import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-hoVZfVKv.js";
1
+ import { c as createWebWorker, l as languages, e as editor } from "./index-BnCJ1IaZ.js";
2
+ import { f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter, H as HoverAdapter, b as DocumentSymbolAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, S as SelectionRangeAdapter, e as DiagnosticsAdapter } from "./lspLanguageFeatures-CUafmPGy.js";
3
+ import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-CUafmPGy.js";
4
4
  const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
5
5
  class WorkerManager {
6
6
  constructor(defaults) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-BsdOeO0U.js";
1
+ import { R as Range$1, l as languages, e as editor, U as Uri, M as MarkerSeverity } from "./index-BnCJ1IaZ.js";
2
2
  var DocumentUri;
3
3
  (function(DocumentUri2) {
4
4
  function is(value) {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["{/*", "*/}"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#",
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-BsdOeO0U.js";
1
+ import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-BnCJ1IaZ.js";
2
2
  class WorkerManager {
3
3
  constructor(_modeId, _defaults) {
4
4
  this._modeId = _modeId;
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const conf = {
3
3
  wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
4
4
  comments: {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["<!--", "-->"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-BsdOeO0U.js";
1
+ import { l as languages } from "./index-BnCJ1IaZ.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#"
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>ctlsurf-worker</title>
7
- <script type="module" crossorigin src="./assets/index-BsdOeO0U.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-BzF7I1my.css">
7
+ <script type="module" crossorigin src="./assets/index-BnCJ1IaZ.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-CrTu3Z4M.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
5
5
  "main": "out/main/index.js",
6
6
  "bin": {
@@ -105,6 +105,32 @@ export class CtlsurfApi {
105
105
  return folder?.pages || []
106
106
  }
107
107
 
108
+ async getFolder(folderId: string): Promise<any> {
109
+ return this.request('GET', `/folders/${folderId}`)
110
+ }
111
+
112
+ // ─── Datastore ───────────────────────────────────────
113
+
114
+ async getPageBlockSummaries(pageId: string): Promise<any[]> {
115
+ return this.request('GET', `/blocks/page/${pageId}/summary`)
116
+ }
117
+
118
+ async addRow(blockId: string, data: Record<string, unknown>): Promise<any> {
119
+ return this.request('POST', `/datastore/${blockId}/rows`, { data })
120
+ }
121
+
122
+ async updateRow(blockId: string, rowId: string, data: Record<string, unknown>): Promise<any> {
123
+ return this.request('PUT', `/datastore/${blockId}/rows/${rowId}`, { data })
124
+ }
125
+
126
+ async getDatastoreSchema(blockId: string): Promise<{ block_id: string; columns: Array<{ id: string; name: string; type: string }> }> {
127
+ return this.request('GET', `/datastore/${blockId}/schema`)
128
+ }
129
+
130
+ async updateDatastoreSchema(blockId: string, columns: Array<{ id: string; name: string; type: string }>): Promise<any> {
131
+ return this.request('PUT', `/datastore/${blockId}/schema`, { columns })
132
+ }
133
+
108
134
  async findFolderByGitRemote(gitRemote: string): Promise<any> {
109
135
  // Search folders by listing all and matching git_remote
110
136
  const folders = await this.request('GET', '/folders')
@@ -70,33 +70,7 @@ async function main() {
70
70
  const tui = new Tui()
71
71
  const agents = getBuiltinAgents()
72
72
 
73
- // ─── Agent selection ────────────────────────────
74
- let agent: AgentConfig
75
-
76
- if (args.agent) {
77
- const found = agents.find(a => a.id === args.agent)
78
- agent = found || {
79
- id: args.agent,
80
- name: args.agent,
81
- command: args.agent,
82
- args: [],
83
- description: `Custom agent: ${args.agent}`,
84
- }
85
- } else {
86
- // Show interactive picker
87
- const selectedIdx = await tui.showAgentPicker(agents)
88
- agent = agents[selectedIdx]
89
- }
90
-
91
- // ─── Start TUI + agent ─────────────────────────
92
-
93
- tui.update({
94
- agentName: agent.name,
95
- cwd: args.cwd,
96
- mode: 'terminal',
97
- })
98
-
99
- tui.init()
73
+ // ─── Orchestrator (loaded early so picker can read profile defaults) ──
100
74
 
101
75
  const orchestrator = new Orchestrator(settingsDir, {
102
76
  onPtyData: (_tabId, data) => {
@@ -125,10 +99,41 @@ async function main() {
125
99
  if (args.apiKey) orchestrator.overrideApiKey(args.apiKey)
126
100
  if (args.baseUrl) orchestrator.overrideBaseUrl(args.baseUrl)
127
101
 
102
+ // ─── Agent selection ────────────────────────────
103
+
104
+ let agent: AgentConfig
105
+ let trackTimeOverride: boolean | undefined
106
+
107
+ if (args.agent) {
108
+ const found = agents.find(a => a.id === args.agent)
109
+ agent = found || {
110
+ id: args.agent,
111
+ name: args.agent,
112
+ command: args.agent,
113
+ args: [],
114
+ description: `Custom agent: ${args.agent}`,
115
+ }
116
+ } else {
117
+ const initialTrackTime = orchestrator.getActiveProfile().trackTime !== false
118
+ const picked = await tui.showAgentPicker(agents, { initialTrackTime })
119
+ agent = agents[picked.agentIdx]
120
+ trackTimeOverride = picked.trackTime
121
+ }
122
+
123
+ // ─── Start TUI + agent ─────────────────────────
124
+
125
+ tui.update({
126
+ agentName: agent.name,
127
+ cwd: args.cwd,
128
+ mode: 'terminal',
129
+ })
130
+
131
+ tui.init()
132
+
128
133
  // Spawn agent with PTY sized to fit the TUI content area
129
134
  const HEADLESS_TAB = 'headless'
130
135
  const ptySize = tui.getPtySize()
131
- await orchestrator.spawnAgent(HEADLESS_TAB, agent, args.cwd)
136
+ await orchestrator.spawnAgent(HEADLESS_TAB, agent, args.cwd, { trackTime: trackTimeOverride })
132
137
  orchestrator.resizePty(HEADLESS_TAB, ptySize.cols, ptySize.rows)
133
138
 
134
139
  // For coding agents, send an initial prompt to kick-start them
package/src/main/index.ts CHANGED
@@ -309,6 +309,14 @@ ipcMain.handle('profiles:save', (_event, id: string, data: any) => {
309
309
  ipcMain.handle('profiles:switch', (_event, id: string) => orchestrator.switchProfile(id))
310
310
  ipcMain.handle('profiles:delete', (_event, id: string) => orchestrator.deleteProfile(id))
311
311
 
312
+ // ─── Tracking IPC ─────────────────────────────────
313
+
314
+ ipcMain.handle('tracking:get', () => ({ active: orchestrator.isActiveTabTracking() }))
315
+ ipcMain.handle('tracking:set', async (_event, enabled: boolean) => {
316
+ await orchestrator.setActiveTabTracking(enabled)
317
+ return { active: orchestrator.isActiveTabTracking() }
318
+ })
319
+
312
320
  // ─── Legacy Settings IPC ──────────────────────────
313
321
 
314
322
  ipcMain.handle('settings:get', (_event, key: string) => {