@phenx-inc/ctlsurf 0.2.0 → 0.3.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.
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-BW-SuYuP.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-BCHZUSLb.js → freemarker2-2YWYzawi.js} +1 -1
  7. package/out/renderer/assets/{handlebars-DKx-Fw-H.js → handlebars-EwtUQRsf.js} +1 -1
  8. package/out/renderer/assets/{html-BSCM04uL.js → html-BNZkIDb9.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-BucU1MUc.js → htmlMode-C2dZKrOy.js} +3 -3
  10. package/out/renderer/assets/{index-BsdOeO0U.js → index-Bm_rbVP-.js} +114 -34
  11. package/out/renderer/assets/{index-BzF7I1my.css → index-CrTu3Z4M.css} +21 -0
  12. package/out/renderer/assets/{javascript-bPY5C4uq.js → javascript-busdVZMv.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-BmJotb6E.js → jsonMode-BaVI6jAw.js} +3 -3
  14. package/out/renderer/assets/{liquid-Cja_Pzh3.js → liquid-DG08un1Q.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-hoVZfVKv.js → lspLanguageFeatures-peGVtLxi.js} +1 -1
  16. package/out/renderer/assets/{mdx-C0s81MOq.js → mdx-DogBhUxZ.js} +1 -1
  17. package/out/renderer/assets/{python-CulkBOJr.js → python-Bf-INYXh.js} +1 -1
  18. package/out/renderer/assets/{razor-czmzhwVZ.js → razor-DLrZ2hsF.js} +1 -1
  19. package/out/renderer/assets/{tsMode-B90EqYGx.js → tsMode-B4oEmliC.js} +1 -1
  20. package/out/renderer/assets/{typescript-Ckc6emP2.js → typescript-CjkgfhVK.js} +1 -1
  21. package/out/renderer/assets/{xml-CKh-JyGN.js → xml-0FAXmuVg.js} +1 -1
  22. package/out/renderer/assets/{yaml-B49zLim4.js → yaml-DWxnPuy8.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 +25 -13
  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-BW-SuYuP.js","./lspLanguageFeatures-peGVtLxi.js","./htmlMode-C2dZKrOy.js","./jsonMode-BaVI6jAw.js","./javascript-busdVZMv.js","./typescript-CjkgfhVK.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
  }
@@ -18827,13 +18827,15 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
18827
18827
  const handleResize = () => {
18828
18828
  clearTimeout(resizeTimeout);
18829
18829
  resizeTimeout = setTimeout(() => {
18830
+ const el = containerRef.current;
18831
+ if (!el || el.offsetWidth === 0 || el.offsetHeight === 0) return;
18830
18832
  const state = _terminals.get(tabId);
18831
- if (state) {
18832
- state.fitAddon.fit();
18833
- scrollIfPinned(tabId, state.terminal);
18834
- const { cols, rows } = state.terminal;
18835
- window.worker.resizePty(tabId, cols, rows);
18836
- }
18833
+ if (!state) return;
18834
+ state.fitAddon.fit();
18835
+ const { cols, rows } = state.terminal;
18836
+ if (cols < 10 || rows < 5) return;
18837
+ scrollIfPinned(tabId, state.terminal);
18838
+ window.worker.resizePty(tabId, cols, rows);
18837
18839
  }, 150);
18838
18840
  };
18839
18841
  window.addEventListener("resize", handleResize);
@@ -18866,12 +18868,19 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
18866
18868
  }, [tabId, agent, onSpawn]);
18867
18869
  reactExports.useEffect(() => {
18868
18870
  if (isActive) {
18869
- const state = _terminals.get(tabId);
18870
- if (state) {
18871
- state.fitAddon.fit();
18872
- state.terminal.focus();
18873
- }
18874
18871
  window.worker.setActiveTab(tabId);
18872
+ requestAnimationFrame(() => {
18873
+ setTimeout(() => {
18874
+ const state = _terminals.get(tabId);
18875
+ if (state) {
18876
+ state.fitAddon.fit();
18877
+ state.terminal.focus();
18878
+ scrollIfPinned(tabId, state.terminal);
18879
+ const { cols, rows } = state.terminal;
18880
+ window.worker.resizePty(tabId, cols, rows);
18881
+ }
18882
+ }, 50);
18883
+ });
18875
18884
  }
18876
18885
  }, [isActive, tabId]);
18877
18886
  return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "terminal-container", ref: containerRef });
@@ -206549,7 +206558,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
206549
206558
  modeConfigurationDefault$2
206550
206559
  );
206551
206560
  function getMode$3() {
206552
- return __vitePreload(() => import("./cssMode-D3kH1Kju.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
206561
+ return __vitePreload(() => import("./cssMode-BW-SuYuP.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
206553
206562
  }
206554
206563
  languages.onLanguage("less", () => {
206555
206564
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -206654,7 +206663,7 @@ const razorLanguageService = registerHTMLLanguageService(
206654
206663
  );
206655
206664
  const razorDefaults = razorLanguageService.defaults;
206656
206665
  function getMode$2() {
206657
- return __vitePreload(() => import("./htmlMode-BucU1MUc.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
206666
+ return __vitePreload(() => import("./htmlMode-C2dZKrOy.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
206658
206667
  }
206659
206668
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
206660
206669
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -206738,7 +206747,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
206738
206747
  );
206739
206748
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
206740
206749
  function getMode$1() {
206741
- return __vitePreload(() => import("./jsonMode-BmJotb6E.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
206750
+ return __vitePreload(() => import("./jsonMode-BaVI6jAw.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
206742
206751
  }
206743
206752
  languages.register({
206744
206753
  id: "json",
@@ -206984,7 +206993,7 @@ const getJavaScriptWorker = () => {
206984
206993
  return getMode().then((mode) => mode.getJavaScriptWorker());
206985
206994
  };
206986
206995
  function getMode() {
206987
- return __vitePreload(() => import("./tsMode-B90EqYGx.js"), true ? [] : void 0, import.meta.url);
206996
+ return __vitePreload(() => import("./tsMode-B4oEmliC.js"), true ? [] : void 0, import.meta.url);
206988
206997
  }
206989
206998
  languages.onLanguage("typescript", () => {
206990
206999
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -207179,49 +207188,49 @@ registerLanguage({
207179
207188
  extensions: [".ftl", ".ftlh", ".ftlx"],
207180
207189
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
207181
207190
  loader: () => {
207182
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207191
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207183
207192
  }
207184
207193
  });
207185
207194
  registerLanguage({
207186
207195
  id: "freemarker2.tag-angle.interpolation-dollar",
207187
207196
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
207188
207197
  loader: () => {
207189
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207198
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
207190
207199
  }
207191
207200
  });
207192
207201
  registerLanguage({
207193
207202
  id: "freemarker2.tag-bracket.interpolation-dollar",
207194
207203
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
207195
207204
  loader: () => {
207196
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207205
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
207197
207206
  }
207198
207207
  });
207199
207208
  registerLanguage({
207200
207209
  id: "freemarker2.tag-angle.interpolation-bracket",
207201
207210
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
207202
207211
  loader: () => {
207203
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207212
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
207204
207213
  }
207205
207214
  });
207206
207215
  registerLanguage({
207207
207216
  id: "freemarker2.tag-bracket.interpolation-bracket",
207208
207217
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
207209
207218
  loader: () => {
207210
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207219
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
207211
207220
  }
207212
207221
  });
207213
207222
  registerLanguage({
207214
207223
  id: "freemarker2.tag-auto.interpolation-dollar",
207215
207224
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
207216
207225
  loader: () => {
207217
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207226
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
207218
207227
  }
207219
207228
  });
207220
207229
  registerLanguage({
207221
207230
  id: "freemarker2.tag-auto.interpolation-bracket",
207222
207231
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
207223
207232
  loader: () => {
207224
- return __vitePreload(() => import("./freemarker2-BCHZUSLb.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207233
+ return __vitePreload(() => import("./freemarker2-2YWYzawi.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
207225
207234
  }
207226
207235
  });
207227
207236
  registerLanguage({
@@ -207242,7 +207251,7 @@ registerLanguage({
207242
207251
  extensions: [".handlebars", ".hbs"],
207243
207252
  aliases: ["Handlebars", "handlebars", "hbs"],
207244
207253
  mimetypes: ["text/x-handlebars-template"],
207245
- loader: () => __vitePreload(() => import("./handlebars-DKx-Fw-H.js"), true ? [] : void 0, import.meta.url)
207254
+ loader: () => __vitePreload(() => import("./handlebars-EwtUQRsf.js"), true ? [] : void 0, import.meta.url)
207246
207255
  });
207247
207256
  registerLanguage({
207248
207257
  id: "hcl",
@@ -207255,7 +207264,7 @@ registerLanguage({
207255
207264
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
207256
207265
  aliases: ["HTML", "htm", "html", "xhtml"],
207257
207266
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
207258
- loader: () => __vitePreload(() => import("./html-BSCM04uL.js"), true ? [] : void 0, import.meta.url)
207267
+ loader: () => __vitePreload(() => import("./html-BNZkIDb9.js"), true ? [] : void 0, import.meta.url)
207259
207268
  });
207260
207269
  registerLanguage({
207261
207270
  id: "ini",
@@ -207278,7 +207287,7 @@ registerLanguage({
207278
207287
  filenames: ["jakefile"],
207279
207288
  aliases: ["JavaScript", "javascript", "js"],
207280
207289
  mimetypes: ["text/javascript"],
207281
- loader: () => __vitePreload(() => import("./javascript-bPY5C4uq.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207290
+ loader: () => __vitePreload(() => import("./javascript-busdVZMv.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
207282
207291
  });
207283
207292
  registerLanguage({
207284
207293
  id: "julia",
@@ -207317,7 +207326,7 @@ registerLanguage({
207317
207326
  extensions: [".liquid", ".html.liquid"],
207318
207327
  aliases: ["Liquid", "liquid"],
207319
207328
  mimetypes: ["application/liquid"],
207320
- loader: () => __vitePreload(() => import("./liquid-Cja_Pzh3.js"), true ? [] : void 0, import.meta.url)
207329
+ loader: () => __vitePreload(() => import("./liquid-DG08un1Q.js"), true ? [] : void 0, import.meta.url)
207321
207330
  });
207322
207331
  registerLanguage({
207323
207332
  id: "m3",
@@ -207335,7 +207344,7 @@ registerLanguage({
207335
207344
  id: "mdx",
207336
207345
  extensions: [".mdx"],
207337
207346
  aliases: ["MDX", "mdx"],
207338
- loader: () => __vitePreload(() => import("./mdx-C0s81MOq.js"), true ? [] : void 0, import.meta.url)
207347
+ loader: () => __vitePreload(() => import("./mdx-DogBhUxZ.js"), true ? [] : void 0, import.meta.url)
207339
207348
  });
207340
207349
  registerLanguage({
207341
207350
  id: "mips",
@@ -207434,7 +207443,7 @@ registerLanguage({
207434
207443
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
207435
207444
  aliases: ["Python", "py"],
207436
207445
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
207437
- loader: () => __vitePreload(() => import("./python-CulkBOJr.js"), true ? [] : void 0, import.meta.url)
207446
+ loader: () => __vitePreload(() => import("./python-Bf-INYXh.js"), true ? [] : void 0, import.meta.url)
207438
207447
  });
207439
207448
  registerLanguage({
207440
207449
  id: "qsharp",
@@ -207453,7 +207462,7 @@ registerLanguage({
207453
207462
  extensions: [".cshtml"],
207454
207463
  aliases: ["Razor", "razor"],
207455
207464
  mimetypes: ["text/x-cshtml"],
207456
- loader: () => __vitePreload(() => import("./razor-czmzhwVZ.js"), true ? [] : void 0, import.meta.url)
207465
+ loader: () => __vitePreload(() => import("./razor-DLrZ2hsF.js"), true ? [] : void 0, import.meta.url)
207457
207466
  });
207458
207467
  registerLanguage({
207459
207468
  id: "redis",
@@ -207586,7 +207595,7 @@ registerLanguage({
207586
207595
  aliases: ["TypeScript", "ts", "typescript"],
207587
207596
  mimetypes: ["text/typescript"],
207588
207597
  loader: () => {
207589
- return __vitePreload(() => import("./typescript-Ckc6emP2.js"), true ? [] : void 0, import.meta.url);
207598
+ return __vitePreload(() => import("./typescript-CjkgfhVK.js"), true ? [] : void 0, import.meta.url);
207590
207599
  }
207591
207600
  });
207592
207601
  registerLanguage({
@@ -207631,14 +207640,14 @@ registerLanguage({
207631
207640
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
207632
207641
  aliases: ["XML", "xml"],
207633
207642
  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)
207643
+ loader: () => __vitePreload(() => import("./xml-0FAXmuVg.js"), true ? [] : void 0, import.meta.url)
207635
207644
  });
207636
207645
  registerLanguage({
207637
207646
  id: "yaml",
207638
207647
  extensions: [".yaml", ".yml"],
207639
207648
  aliases: ["YAML", "yaml", "YML", "yml"],
207640
207649
  mimetypes: ["application/x-yaml", "text/x-yaml"],
207641
- loader: () => __vitePreload(() => import("./yaml-B49zLim4.js"), true ? [] : void 0, import.meta.url)
207650
+ loader: () => __vitePreload(() => import("./yaml-DWxnPuy8.js"), true ? [] : void 0, import.meta.url)
207642
207651
  });
207643
207652
  var __defProp = Object.defineProperty;
207644
207653
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -211507,6 +211516,8 @@ function SettingsDialog({ open, onClose }) {
211507
211516
  const [apiKey, setApiKey] = reactExports.useState("");
211508
211517
  const [baseUrl, setBaseUrl] = reactExports.useState("");
211509
211518
  const [dataspacePageId, setDataspacePageId] = reactExports.useState("");
211519
+ const [trackTime, setTrackTime] = reactExports.useState(false);
211520
+ const [idleTimeoutMin, setIdleTimeoutMin] = reactExports.useState(15);
211510
211521
  const [saved, setSaved] = reactExports.useState(false);
211511
211522
  const loadProfiles = async () => {
211512
211523
  const data = await window.worker.listProfiles();
@@ -211526,6 +211537,8 @@ function SettingsDialog({ open, onClose }) {
211526
211537
  setApiKey("");
211527
211538
  setBaseUrl(profile.baseUrl);
211528
211539
  setDataspacePageId(profile.dataspacePageId || "");
211540
+ setTrackTime(!!profile.trackTime);
211541
+ setIdleTimeoutMin(profile.idleTimeoutMin ?? 15);
211529
211542
  setSaved(false);
211530
211543
  };
211531
211544
  const startNewProfile = () => {
@@ -211535,6 +211548,8 @@ function SettingsDialog({ open, onClose }) {
211535
211548
  setApiKey("");
211536
211549
  setBaseUrl("http://localhost:8000");
211537
211550
  setDataspacePageId("");
211551
+ setTrackTime(true);
211552
+ setIdleTimeoutMin(15);
211538
211553
  setSaved(false);
211539
211554
  };
211540
211555
  const handleSave = async () => {
@@ -211542,7 +211557,9 @@ function SettingsDialog({ open, onClose }) {
211542
211557
  const data = {
211543
211558
  name: name.trim(),
211544
211559
  baseUrl: baseUrl.trim() || "https://app.ctlsurf.com",
211545
- dataspacePageId: dataspacePageId.trim()
211560
+ dataspacePageId: dataspacePageId.trim(),
211561
+ trackTime,
211562
+ idleTimeoutMin: Math.max(1, Math.floor(idleTimeoutMin)) || 15
211546
211563
  };
211547
211564
  if (apiKey.trim()) {
211548
211565
  data.apiKey = apiKey.trim();
@@ -211622,6 +211639,31 @@ function SettingsDialog({ open, onClose }) {
211622
211639
  }
211623
211640
  )
211624
211641
  ] }),
211642
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "settings-checkbox", children: [
211643
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
211644
+ "input",
211645
+ {
211646
+ type: "checkbox",
211647
+ checked: trackTime,
211648
+ onChange: (e) => setTrackTime(e.target.checked)
211649
+ }
211650
+ ),
211651
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Track time per project" }),
211652
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "settings-hint", children: `Logs each session to a "Time Tracking" datastore on the project's Agent Datastore page.` })
211653
+ ] }),
211654
+ trackTime && /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { children: [
211655
+ "Idle timeout (min)",
211656
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
211657
+ "input",
211658
+ {
211659
+ type: "number",
211660
+ min: 1,
211661
+ value: idleTimeoutMin,
211662
+ onChange: (e) => setIdleTimeoutMin(parseInt(e.target.value, 10) || 15)
211663
+ }
211664
+ ),
211665
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "settings-hint", children: "Gaps longer than this without terminal activity are counted as idle, not work." })
211666
+ ] }),
211625
211667
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-actions", children: [
211626
211668
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-secondary", onClick: () => setEditingId(null), children: "Back" }),
211627
211669
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-primary", onClick: handleSave, children: saved ? "Saved!" : "Save" })
@@ -211687,6 +211729,7 @@ function App() {
211687
211729
  return [{ id, label: "Terminal 1", agent: null, agentStatus: "idle" }];
211688
211730
  });
211689
211731
  const [activeTabId, setActiveTabId] = reactExports.useState(tabs[0].id);
211732
+ const [trackingActive, setTrackingActive] = reactExports.useState(false);
211690
211733
  const [pickerTargetTabId, setPickerTargetTabId] = reactExports.useState(tabs[0].id);
211691
211734
  const [showAgentPicker, setShowAgentPicker] = reactExports.useState(true);
211692
211735
  const visiblePaneIds = findPaneIds(layout2);
@@ -211712,6 +211755,30 @@ function App() {
211712
211755
  });
211713
211756
  return unsub;
211714
211757
  }, []);
211758
+ reactExports.useEffect(() => {
211759
+ let cancelled = false;
211760
+ const refresh = async () => {
211761
+ try {
211762
+ const r = await window.worker.getTracking();
211763
+ if (!cancelled) setTrackingActive(!!r?.active);
211764
+ } catch {
211765
+ }
211766
+ };
211767
+ refresh();
211768
+ const id = setInterval(refresh, 4e3);
211769
+ return () => {
211770
+ cancelled = true;
211771
+ clearInterval(id);
211772
+ };
211773
+ }, [activeTabId]);
211774
+ const handleToggleTracking = reactExports.useCallback(async () => {
211775
+ try {
211776
+ const r = await window.worker.setTracking(!trackingActive);
211777
+ setTrackingActive(!!r?.active);
211778
+ } catch (err) {
211779
+ console.error("[tracking] toggle failed", err);
211780
+ }
211781
+ }, [trackingActive]);
211715
211782
  const cwdRef = reactExports.useRef(null);
211716
211783
  const handleSpawn = reactExports.useCallback(async (tabId, agent) => {
211717
211784
  setTabs((prev) => prev.map((t) => t.id === tabId ? { ...t, agentStatus: "active" } : t));
@@ -211912,6 +211979,19 @@ function App() {
211912
211979
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-title", children: "ctlsurf-worker" }),
211913
211980
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "titlebar-controls", children: [
211914
211981
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "titlebar-btn", onClick: () => setShowSettings(true), title: "Settings", children: "Settings" }),
211982
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
211983
+ "button",
211984
+ {
211985
+ className: `titlebar-btn titlebar-icon-btn ${trackingActive ? "active" : ""}`,
211986
+ onClick: handleToggleTracking,
211987
+ title: trackingActive ? "Time tracking on — click to stop" : "Time tracking off — click to start",
211988
+ "aria-label": "Time tracking",
211989
+ children: [
211990
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "tracking-icon", children: "⏱" }),
211991
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `tracking-dot ${trackingActive ? "on" : "off"}` })
211992
+ ]
211993
+ }
211994
+ ),
211915
211995
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "titlebar-separator" }),
211916
211996
  agents.map((a) => {
211917
211997
  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-CjkgfhVK.js";
2
+ import "./index-Bm_rbVP-.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-Bm_rbVP-.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-peGVtLxi.js";
3
+ import { a, D, h, R, c, i, j, t, k } from "./lspLanguageFeatures-peGVtLxi.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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-Bm_rbVP-.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.1",
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) => {