clay-server 5.2.0-beta.10 → 5.2.0-beta.11

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 (98) hide show
  1. package/README.md +0 -4
  2. package/lib/clay-product-control-tools.js +1 -0
  3. package/lib/clay-product-control.js +14 -11
  4. package/lib/clay-vendor-usage.js +44 -0
  5. package/lib/github-star-count.js +147 -0
  6. package/lib/notes-checklist.js +43 -1
  7. package/lib/notes.js +2 -1
  8. package/lib/project-session-notes.js +33 -8
  9. package/lib/project-sessions.js +63 -24
  10. package/lib/project-start-protocol.js +29 -0
  11. package/lib/project-vendor-login.js +2 -0
  12. package/lib/project.js +3 -0
  13. package/lib/public/app.js +25 -17
  14. package/lib/public/css/contextual-clay.css +1 -1
  15. package/lib/public/css/filebrowser.css +1 -1
  16. package/lib/public/css/mate-loading-scene.css +21 -0
  17. package/lib/public/css/overlays.css +64 -256
  18. package/lib/public/css/project-mate-creation.css +5 -6
  19. package/lib/public/css/project-start.css +108 -0
  20. package/lib/public/css/projects-hub.css +53 -14
  21. package/lib/public/css/settings-shell.css +103 -0
  22. package/lib/public/css/sidebar.css +7 -0
  23. package/lib/public/css/title-bar.css +79 -0
  24. package/lib/public/index.html +50 -63
  25. package/lib/public/modules/admin-settings-invitations.js +2 -2
  26. package/lib/public/modules/admin-settings-projects.js +2 -2
  27. package/lib/public/modules/admin-settings-ui.js +20 -4
  28. package/lib/public/modules/admin-settings-users.js +4 -4
  29. package/lib/public/modules/app-home-hub.js +2 -1
  30. package/lib/public/modules/app-messages.js +10 -2
  31. package/lib/public/modules/app-projects.js +1 -407
  32. package/lib/public/modules/app-rate-limit.js +12 -1
  33. package/lib/public/modules/clay-tool-presentation.js +1 -0
  34. package/lib/public/modules/contextual-clay.js +3 -1
  35. package/lib/public/modules/github-star.js +66 -0
  36. package/lib/public/modules/mate-loading-scene.js +17 -0
  37. package/lib/public/modules/profile.js +11 -3
  38. package/lib/public/modules/project-mate-creation-workspace.js +13 -4
  39. package/lib/public/modules/project-session-visibility-settings.js +19 -16
  40. package/lib/public/modules/project-settings.js +119 -129
  41. package/lib/public/modules/project-start-dialog.js +130 -0
  42. package/lib/public/modules/project-start-folder-state.js +78 -0
  43. package/lib/public/modules/project-start-folder-view.js +190 -0
  44. package/lib/public/modules/project-start-forms.js +66 -0
  45. package/lib/public/modules/project-start-path.js +60 -0
  46. package/lib/public/modules/project-start-state.js +54 -0
  47. package/lib/public/modules/project-start-submit.js +67 -0
  48. package/lib/public/modules/projects-hub-view.js +33 -14
  49. package/lib/public/modules/projects-hub.js +10 -6
  50. package/lib/public/modules/server-settings.js +36 -91
  51. package/lib/public/modules/settings-project-target.js +56 -0
  52. package/lib/public/modules/settings-select.js +1 -1
  53. package/lib/public/modules/settings-shell-registry.js +88 -0
  54. package/lib/public/modules/settings-shell-view.js +104 -0
  55. package/lib/public/modules/settings-shell.js +342 -0
  56. package/lib/public/modules/sidebar-mobile.js +8 -30
  57. package/lib/public/modules/sidebar-projects.js +2 -1
  58. package/lib/public/modules/theme.js +10 -2
  59. package/lib/public/modules/user-settings-layout.js +8 -33
  60. package/lib/public/modules/user-settings-profile.js +1 -1
  61. package/lib/public/modules/user-settings.js +18 -85
  62. package/lib/public/modules/vendor-usage-format.js +93 -0
  63. package/lib/public/modules/vendor-usage-list.js +45 -0
  64. package/lib/public/modules/vendor-usage-model.js +215 -0
  65. package/lib/public/modules/vendor-usage-overview.js +312 -0
  66. package/lib/public/style.css +10 -7
  67. package/lib/public/welcome-art/conversation-desk.png +0 -0
  68. package/lib/public/welcome-art/mate-clay.png +0 -0
  69. package/lib/public/welcome-art/project-clay.png +0 -0
  70. package/lib/sdk-bridge.js +6 -1
  71. package/lib/sdk-message-processor.js +9 -1
  72. package/lib/sdk-quota-snapshot.js +20 -2
  73. package/lib/server-github-stars.js +39 -0
  74. package/lib/server-global-ws.js +21 -17
  75. package/lib/server.js +4 -0
  76. package/lib/session-notes-mcp-server.js +26 -4
  77. package/lib/session-notes-writes.js +88 -0
  78. package/lib/session-visibility.js +20 -8
  79. package/lib/sessions.js +2 -0
  80. package/lib/vendor-usage-acp-readers.js +97 -0
  81. package/lib/vendor-usage-capabilities.js +88 -0
  82. package/lib/vendor-usage-http-readers.js +214 -0
  83. package/lib/vendor-usage-metrics.js +47 -0
  84. package/lib/vendor-usage-native-readers.js +106 -0
  85. package/lib/vendor-usage-overview.js +406 -0
  86. package/lib/vendor-usage-service.js +281 -0
  87. package/lib/ws-schema.js +4 -2
  88. package/lib/yoke/adapters/claude.js +12 -0
  89. package/lib/yoke/adapters/codex-query-contract.js +1 -0
  90. package/lib/yoke/adapters/codex.js +45 -12
  91. package/lib/yoke/claude-hook-installer.js +1 -0
  92. package/lib/yoke/codex-dynamic-tool-names.js +80 -0
  93. package/lib/yoke/codex-model-catalog.js +37 -3
  94. package/lib/yoke/muse-services.js +16 -3
  95. package/lib/yoke/providers/claude-model-descriptions.js +22 -2
  96. package/lib/yoke/vendor-registry.js +6 -2
  97. package/package.json +2 -1
  98. package/lib/public/modules/server-settings-navigation.js +0 -39
@@ -5,12 +5,16 @@ var { execFileSync } = require("child_process");
5
5
  var providerSettings = require("./yoke/settings");
6
6
  var updateSnooze = require("./update-snooze");
7
7
  var yoke = require("./yoke");
8
+ var startProtocol = require("./project-start-protocol");
9
+ var startReply = startProtocol.withRequestId;
10
+ var browseMeta = startProtocol.browseMeta;
8
11
  var sessionVisibility = require("./session-visibility");
9
12
  var contextUsageModule = require("./context-usage");
10
13
  var sessionDeleteModule = require("./project-session-delete");
11
14
  var sessionFoldersModule = require("./project-session-folders");
12
15
  var elicitationResponse = require("./project-elicitation-response");
13
16
  var sessionCreationModule = require("./project-session-creation");
17
+ var vendorUsageOverviewModule = require("./vendor-usage-overview");
14
18
 
15
19
  function vendorSupportsTui(vendor) {
16
20
  var info = yoke.getVendorInfo(vendor);
@@ -223,6 +227,10 @@ function attachSessions(ctx) {
223
227
  resolveSessionHome: resolveSessionHome, cancelPendingAskUser: cancelPendingAskUser,
224
228
  }));
225
229
 
230
+ function boundedRequestId(msg) {
231
+ return typeof msg.requestId === "string" ? msg.requestId.substring(0, 200) : undefined;
232
+ }
233
+
226
234
  function canAccessProjectSlug(ws, targetSlug) {
227
235
  if (!targetSlug) return false;
228
236
  if (!usersModule || typeof usersModule.isMultiUser !== "function" || !usersModule.isMultiUser()) return true;
@@ -361,6 +369,11 @@ function attachSessions(ctx) {
361
369
  getProjectAccess: getProjectAccess,
362
370
  getVendorAvailability: function (ws) { return ctx.getVendorAvailability(ws); },
363
371
  });
372
+ var vendorUsage = vendorUsageOverviewModule.attachVendorUsageOverview({
373
+ slug: slug, sm: sm, usersModule: usersModule, clients: clients, sendTo: sendTo, getProjectAccess: getProjectAccess,
374
+ getVendorAvailability: function (ws) { return ctx.getVendorAvailability(ws); },
375
+ adapters: ctx.adapters, osUsers: osUsers, getLinuxUserForWs: ctx.getLinuxUserForWs,
376
+ });
364
377
  // Kick off watchers for any TUI sessions already loaded from disk so
365
378
  // their titles refresh as soon as Claude Code rewrites the jsonl, even
366
379
  // before the user clicks them.
@@ -755,13 +768,15 @@ function attachSessions(ctx) {
755
768
  if (msg.type === "set_project_session_visibility_default") {
756
769
  sessionVisibility.handleDefaultChange({
757
770
  slug: slug, isMate: isMate, osUsers: osUsers, usersModule: usersModule, getProjectAccess: getProjectAccess,
758
- opts: opts, sendTo: sendTo,
771
+ getTargetAccess: typeof opts.onGetProjectAccess === "function" ? opts.onGetProjectAccess : null,
772
+ canAccessProjectSlug: canAccessProjectSlug, opts: opts, sendTo: sendTo,
759
773
  }, ws, msg);
760
774
  return true;
761
775
  }
762
776
 
763
777
  if (sessionFolders.handleMessage(ws, msg)) return true;
764
778
  if (sessionCreation.handleMessage(ws, msg)) return true;
779
+ if (vendorUsage.handleMessage(ws, msg)) return true;
765
780
 
766
781
  if (msg.type === "bulk_delete_sessions") {
767
782
  sessionDelete.bulkDeleteSessions(ws, msg);
@@ -1656,8 +1671,9 @@ function attachSessions(ctx) {
1656
1671
  parentDir = path.dirname(absTarget);
1657
1672
  prefix = path.basename(absTarget).toLowerCase();
1658
1673
  }
1659
- if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, parentDir)) {
1660
- sendTo(ws, { type: "browse_dir_result", entries: [], error: "Directory access is not permitted" });
1674
+ var canList = function (candidate) { return !opts.canUsePath || opts.canUsePath(ws._clayUser && ws._clayUser.id, candidate); };
1675
+ if (!canList(parentDir)) {
1676
+ sendTo(ws, startReply(msg, { type: "browse_dir_result", path: msg.path, entries: [], error: "Directory access is not permitted" }));
1661
1677
  return true;
1662
1678
  }
1663
1679
  try {
@@ -1673,9 +1689,9 @@ function attachSessions(ctx) {
1673
1689
  dirEntries.push({ name: d.name, path: path.join(parentDir, d.name) });
1674
1690
  }
1675
1691
  dirEntries.sort(function (a, b) { return a.name.localeCompare(b.name); });
1676
- sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: dirEntries });
1692
+ sendTo(ws, Object.assign(browseMeta(msg, parentDir, prefix, canList), { type: "browse_dir_result", path: msg.path, entries: dirEntries }));
1677
1693
  } catch (e) {
1678
- sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: [], error: e.message });
1694
+ sendTo(ws, startReply(msg, { type: "browse_dir_result", path: msg.path, entries: [], error: e.message }));
1679
1695
  }
1680
1696
  return true;
1681
1697
  }
@@ -1685,24 +1701,24 @@ function attachSessions(ctx) {
1685
1701
  var addPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
1686
1702
  var addAbs = path.resolve(addPath);
1687
1703
  if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, addAbs)) {
1688
- sendTo(ws, { type: "add_project_result", ok: false, error: "Directory access is not permitted" });
1704
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Directory access is not permitted" }));
1689
1705
  return true;
1690
1706
  }
1691
1707
  try {
1692
1708
  var addStat = fs.statSync(addAbs);
1693
1709
  if (!addStat.isDirectory()) {
1694
- sendTo(ws, { type: "add_project_result", ok: false, error: "Not a directory" });
1710
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not a directory" }));
1695
1711
  return true;
1696
1712
  }
1697
1713
  } catch (e) {
1698
- sendTo(ws, { type: "add_project_result", ok: false, error: "Directory not found" });
1714
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Directory not found" }));
1699
1715
  return true;
1700
1716
  }
1701
1717
  if (typeof opts.onAddProject === "function") {
1702
1718
  var result = opts.onAddProject(addAbs, ws._clayUser);
1703
- sendTo(ws, Object.assign({}, result, { type: "add_project_result" }));
1719
+ sendTo(ws, startReply(msg, Object.assign({}, result, { type: "add_project_result" })));
1704
1720
  } else {
1705
- sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1721
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not supported" }));
1706
1722
  }
1707
1723
  return true;
1708
1724
  }
@@ -1712,7 +1728,7 @@ function attachSessions(ctx) {
1712
1728
  if (ws._clayUser) {
1713
1729
  var cpPerms = usersModule.getEffectivePermissions(ws._clayUser, osUsers);
1714
1730
  if (!cpPerms.createProject) {
1715
- sendTo(ws, { type: "add_project_result", ok: false, error: "You do not have permission to create projects" });
1731
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "You do not have permission to create projects" }));
1716
1732
  return true;
1717
1733
  }
1718
1734
  }
@@ -1720,14 +1736,14 @@ function attachSessions(ctx) {
1720
1736
  if (msg.type === "create_project") {
1721
1737
  var createName = (msg.name || "").trim();
1722
1738
  if (!createName || !/^[a-zA-Z0-9_-]+$/.test(createName)) {
1723
- sendTo(ws, { type: "add_project_result", ok: false, error: "Invalid name. Use only letters, numbers, dashes, and underscores." });
1739
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Invalid name. Use only letters, numbers, dashes, and underscores." }));
1724
1740
  return true;
1725
1741
  }
1726
1742
  if (typeof opts.onCreateProject === "function") {
1727
1743
  var createResult = opts.onCreateProject(createName, ws._clayUser);
1728
- sendTo(ws, Object.assign({}, createResult, { type: "add_project_result" }));
1744
+ sendTo(ws, startReply(msg, Object.assign({}, createResult, { type: "add_project_result" })));
1729
1745
  } else {
1730
- sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1746
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not supported" }));
1731
1747
  }
1732
1748
  return true;
1733
1749
  }
@@ -1736,16 +1752,16 @@ function attachSessions(ctx) {
1736
1752
  if (msg.type === "clone_project") {
1737
1753
  var cloneUrl = (msg.url || "").trim();
1738
1754
  if (!cloneUrl || (!/^https?:\/\//.test(cloneUrl) && !/^git@/.test(cloneUrl))) {
1739
- sendTo(ws, { type: "add_project_result", ok: false, error: "Invalid URL. Use https:// or git@ format." });
1755
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Invalid URL. Use https:// or git@ format." }));
1740
1756
  return true;
1741
1757
  }
1742
- sendTo(ws, { type: "clone_project_progress", status: "cloning" });
1758
+ sendTo(ws, startReply(msg, { type: "clone_project_progress", status: "cloning" }));
1743
1759
  if (typeof opts.onCloneProject === "function") {
1744
1760
  opts.onCloneProject(cloneUrl, ws._clayUser, function (cloneResult) {
1745
- sendTo(ws, { type: "add_project_result", ok: cloneResult.ok, slug: cloneResult.slug, error: cloneResult.error });
1761
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: cloneResult.ok, slug: cloneResult.slug, error: cloneResult.error }));
1746
1762
  });
1747
1763
  } else {
1748
- sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
1764
+ sendTo(ws, startReply(msg, { type: "add_project_result", ok: false, error: "Not supported" }));
1749
1765
  }
1750
1766
  return true;
1751
1767
  }
@@ -1858,30 +1874,41 @@ function attachSessions(ctx) {
1858
1874
 
1859
1875
  // --- Set project title (rename) ---
1860
1876
  if (msg.type === "set_project_title") {
1877
+ var titleRequestId = boundedRequestId(msg);
1861
1878
  if (!msg.slug) {
1862
- sendTo(ws, { type: "set_project_title_result", ok: false, error: "Missing slug" });
1879
+ sendTo(ws, { type: "set_project_title_result", ok: false, error: "Missing slug", requestId: titleRequestId });
1880
+ return true;
1881
+ }
1882
+ // The target is named by the client; it must be a project this account can access.
1883
+ if (!canAccessProjectSlug(ws, msg.slug)) {
1884
+ sendTo(ws, { type: "set_project_title_result", ok: false, error: "Project access is not permitted", slug: msg.slug, requestId: titleRequestId });
1863
1885
  return true;
1864
1886
  }
1865
1887
  if (typeof opts.onSetProjectTitle === "function") {
1866
1888
  var titleResult = opts.onSetProjectTitle(msg.slug, msg.title || null);
1867
- sendTo(ws, Object.assign({}, titleResult, { type: "set_project_title_result", slug: msg.slug }));
1889
+ sendTo(ws, Object.assign({}, titleResult, { type: "set_project_title_result", slug: msg.slug, requestId: titleRequestId }));
1868
1890
  } else {
1869
- sendTo(ws, { type: "set_project_title_result", ok: false, error: "Not supported" });
1891
+ sendTo(ws, { type: "set_project_title_result", ok: false, error: "Not supported", requestId: titleRequestId });
1870
1892
  }
1871
1893
  return true;
1872
1894
  }
1873
1895
 
1874
1896
  // --- Set project icon (emoji) ---
1875
1897
  if (msg.type === "set_project_icon") {
1898
+ var iconRequestId = boundedRequestId(msg);
1876
1899
  if (!msg.slug) {
1877
- sendTo(ws, { type: "set_project_icon_result", ok: false, error: "Missing slug" });
1900
+ sendTo(ws, { type: "set_project_icon_result", ok: false, error: "Missing slug", requestId: iconRequestId });
1901
+ return true;
1902
+ }
1903
+ if (!canAccessProjectSlug(ws, msg.slug)) {
1904
+ sendTo(ws, { type: "set_project_icon_result", ok: false, error: "Project access is not permitted", slug: msg.slug, requestId: iconRequestId });
1878
1905
  return true;
1879
1906
  }
1880
1907
  if (typeof opts.onSetProjectIcon === "function") {
1881
1908
  var iconResult = opts.onSetProjectIcon(msg.slug, msg.icon || null);
1882
- sendTo(ws, { type: "set_project_icon_result", ok: iconResult.ok, slug: msg.slug, error: iconResult.error });
1909
+ sendTo(ws, { type: "set_project_icon_result", ok: iconResult.ok, slug: msg.slug, error: iconResult.error, requestId: iconRequestId });
1883
1910
  } else {
1884
- sendTo(ws, { type: "set_project_icon_result", ok: false, error: "Not supported" });
1911
+ sendTo(ws, { type: "set_project_icon_result", ok: false, error: "Not supported", requestId: iconRequestId });
1885
1912
  }
1886
1913
  return true;
1887
1914
  }
@@ -2007,6 +2034,18 @@ function attachSessions(ctx) {
2007
2034
  }
2008
2035
 
2009
2036
  return {
2037
+ invalidateVendorUsage: function () { vendorUsage.invalidateAll(); },
2038
+ // Canonical read-only inspection: the caller has already proven the owner and
2039
+ // project access; the OS user is re-derived from the live user record.
2040
+ readVendorUsage: function (request) {
2041
+ var installed = null;
2042
+ if (Array.isArray(sm.installedVendors)) {
2043
+ installed = Object.create(null);
2044
+ sm.installedVendors.forEach(function (vendor) { installed[vendor] = true; });
2045
+ }
2046
+ var linuxUser = osUsers && request.owner !== "default" ? ctx.getLinuxUserForSession({ ownerId: request.owner }) : null;
2047
+ return vendorUsage.readForOwner({ owner: request.owner, linuxUser: linuxUser, installed: installed, isCurrent: request.isCurrent });
2048
+ },
2010
2049
  handleSessionsMessage: handleSessionsMessage,
2011
2050
  resolveSessionForView: resolveSessionForView,
2012
2051
  sendSessionFoldersState: sessionFolders.sendStateTo,
@@ -0,0 +1,29 @@
1
+ // Shared reply helpers for the project-start dialog (browse_dir and add/create/clone results).
2
+ // A string requestId on the request is echoed on its reply so the client can drop stale answers.
3
+
4
+ var path = require("path");
5
+
6
+ function requestIdOf(msg) {
7
+ return msg && typeof msg.requestId === "string" && msg.requestId.length <= 64 ? msg.requestId : undefined;
8
+ }
9
+
10
+ // Canonical listing metadata. `dir` is the directory whose children are listed, `prefix` is
11
+ // non-empty when the requested path did not exist or was not a directory, and `parent` is set
12
+ // only when the caller may list the parent as well.
13
+ function browseMeta(msg, dir, prefix, canList) {
14
+ var parent = path.dirname(dir);
15
+ return {
16
+ requestId: requestIdOf(msg),
17
+ dir: dir,
18
+ prefix: prefix || "",
19
+ parent: parent !== dir && (!canList || canList(parent)) ? parent : null,
20
+ };
21
+ }
22
+
23
+ function withRequestId(msg, payload) {
24
+ var id = requestIdOf(msg);
25
+ if (id !== undefined) payload.requestId = id;
26
+ return payload;
27
+ }
28
+
29
+ module.exports = { requestIdOf: requestIdOf, browseMeta: browseMeta, withRequestId: withRequestId };
@@ -171,6 +171,8 @@ function attachVendorLogin(ctx) {
171
171
  // reused by every project, so tearing one down here would kill unrelated
172
172
  // sessions; those runtimes pick up new credentials on their own.
173
173
  function refreshVendorAuth(vendor, linuxUser) {
174
+ // Cached account usage described the previous credentials.
175
+ if (typeof ctx.onVendorAuthChanged === "function") { try { ctx.onVendorAuthChanged(vendor, linuxUser || null); } catch (e) {} }
174
176
  var adapter = adapters[vendor];
175
177
  if (!adapter || typeof adapter.shutdown !== "function") return Promise.resolve(false);
176
178
  if (adapter.shared) {
package/lib/project.js CHANGED
@@ -2187,6 +2187,7 @@ function createProjectContext(opts) {
2187
2187
  resolveDefaultAi: opts.resolveDefaultAi,
2188
2188
  resolveMateSessionDefaults: opts.resolveMateSessionDefaults,
2189
2189
  getVendorAvailability: function (ws) { return _models.getVendorAvailability(ws); },
2190
+ getLinuxUserForWs: getLinuxUserForWs,
2190
2191
  canCreateScheduledTaskInterview: function (ws, session) { return !!(_scheduledTasks && _scheduledTasks.canCreateInterview(ws, session)); },
2191
2192
  canStartScheduledTaskInterview: function (ws, session) { return !!(_scheduledTasks && _scheduledTasks.canStartInterview(ws, session)); },
2192
2193
  onHumanPairStop: function (session) {
@@ -2399,6 +2400,7 @@ function createProjectContext(opts) {
2399
2400
 
2400
2401
  // --- Vendor login flow (delegated to project-vendor-login.js) ---
2401
2402
  var _vendorLogin = attachVendorLogin({
2403
+ onVendorAuthChanged: function () { _sessions.invalidateVendorUsage(); },
2402
2404
  requestAccess: _requestAccess,
2403
2405
  clients: clients,
2404
2406
  slug: slug,
@@ -3040,6 +3042,7 @@ function createProjectContext(opts) {
3040
3042
  requestProductControlReview: _productControl.requestProductControlReview,
3041
3043
  applyProductSessionAction: _productControl.applyProductSessionAction,
3042
3044
  inspectProductSessionQueue: _productControl.inspectProductSessionQueue,
3045
+ readVendorUsage: function (request) { return _sessions.readVendorUsage(request); },
3043
3046
  inspectProductSessionRuntime: _sessions.sessionRuntimeActions.inspect,
3044
3047
  inspectMateLifecycleRuntime: _sessions.sessionRuntimeActions.inspectLifecycle,
3045
3048
  inspectDelegatedFollowUp: _delegatedFollowUp.inspectDelegatedFollowUp,
package/lib/public/app.js CHANGED
@@ -31,7 +31,7 @@ import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-mem
31
31
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton, onRewindComplete, onRewindError } from './modules/rewind.js';
32
32
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
33
33
  import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent, setScheduleBtnDisabled, setScheduleDelayMs, clearScheduleDelay } from './modules/input.js';
34
- import { initQrCode, triggerShare } from './modules/qrcode.js';
34
+ import { initQrCode } from './modules/qrcode.js';
35
35
  import { initFileBrowser, loadRootDirectory, reopenFileViewer, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
36
36
  import { initGitPanel } from './modules/git-panel.js';
37
37
  import { initWorkerPaneLock } from './modules/worker-pane-lock.js';
@@ -46,6 +46,7 @@ import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentThem
46
46
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
47
47
  import { initServerSettings, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved } from './modules/server-settings.js';
48
48
  import { initProjectSettings } from './modules/project-settings.js';
49
+ import { initSettingsShell, openSettings } from './modules/settings-shell.js';
49
50
  import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
50
51
  import { initMcp } from './modules/mcp-ui.js';
51
52
  import { initScheduler, resetScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles, getUpcomingSchedules } from './modules/scheduler.js';
@@ -72,10 +73,15 @@ import { initShellCommand } from './modules/shell-command.js';
72
73
  import { initConnection, connect as _connConnect, setStatus as _connSetStatus, scheduleReconnect as _connScheduleReconnect, cancelReconnect as _connCancelReconnect } from './modules/app-connection.js';
73
74
  import { processMessage as _msgProcessMessage } from './modules/app-messages.js';
74
75
  import { getWs as _getWsRef, setWs as _setWsRef } from './modules/ws-ref.js';
76
+ import { initialProjectStart } from './modules/project-start-state.js';
77
+ import { emptyFolderState } from './modules/project-start-folder-state.js';
78
+ import { openAddProjectModal, initProjectStart } from './modules/project-start-dialog.js';
75
79
  import { initProjectsHub, showProjectsHub, hideProjectsHub } from './modules/projects-hub.js';
76
80
  import { initHomeSurfaceBoot } from './modules/home-surface-boot.js';
77
81
  import { rememberHomePrimarySurface } from './modules/home-surface.js';
78
82
  import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, handleFastModeState as _rlHandleFastModeState, resetRateLimitState } from './modules/app-rate-limit.js';
83
+ import { initVendorUsageOverview } from './modules/vendor-usage-overview.js';
84
+ import { initGithubStar } from './modules/github-star.js';
79
85
  import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
80
86
  import { initDefaultAi } from './modules/default-ai.js';
81
87
  import { requestDefaultVendor } from './modules/default-vendor.js';
@@ -97,7 +103,7 @@ import { initProjectMateCreationWorkspace } from './modules/project-mate-creatio
97
103
  import { initProjectDebatePlanningWorkspace } from './modules/project-debate-planning-workspace.js';
98
104
  import { initProjectSessionNavigation } from './modules/project-session-navigation.js';
99
105
  import { initPanels, updateConfigChip as _panUpdateConfigChip, getModelEffortLevels as _panGetModelEffortLevels, accumulateUsage as _panAccumulateUsage, updateUsagePanel as _panUpdateUsagePanel, resetUsage as _panResetUsage, toggleUsagePanel as _panToggleUsagePanel, formatTokens as _panFormatTokens, updateStatusPanel as _panUpdateStatusPanel, requestProcessStats as _panRequestProcessStats, toggleStatusPanel as _panToggleStatusPanel, accumulateContext as _panAccumulateContext, updateContextPanel as _panUpdateContextPanel, resetContext as _panResetContext, resetContextData as _panResetContextData, minimizeContext as _panMinimizeContext, expandContext as _panExpandContext, toggleContextPanel as _panToggleContextPanel, getContextView as _panGetContextView, renderCtxPopover as _panRenderCtxPopover, hideCtxPopover as _panHideCtxPopover, formatBytes as _panFormatBytes, formatUptime as _panFormatUptime, getModelSupportsEffort as _panGetModelSupportsEffort, getSessionUsage, setSessionUsage, getContextData, setContextData, setContextView as _panSetContextView, applyContextView as _panApplyContextView } from './modules/app-panels.js';
100
- import { initProjects, updateProjectList as _projUpdateProjectList, renderProjectList as _projRenderProjectList, renderTopbarPresence as _projRenderTopbarPresence, switchProject as _projSwitchProject, resetClientState as _projResetClientState, confirmRemoveProject as _projConfirmRemoveProject, handleRemoveProjectCheckResult as _projHandleRemoveProjectCheckResult, handleRemoveProjectResult as _projHandleRemoveProjectResult, openAddProjectModal as _projOpenAddProjectModal, closeAddProjectModal as _projCloseAddProjectModal, handleBrowseDirResult as _projHandleBrowseDirResult, handleAddProjectResult as _projHandleAddProjectResult, handleCloneProgress as _projHandleCloneProgress, showUpdateAvailable as _projShowUpdateAvailable, getCachedProjects, setCachedProjects, getCachedProjectCount, getCachedRemovedProjects, setCachedRemovedProjects } from './modules/app-projects.js';
106
+ import { updateProjectList as _projUpdateProjectList, renderProjectList as _projRenderProjectList, renderTopbarPresence as _projRenderTopbarPresence, switchProject as _projSwitchProject, resetClientState as _projResetClientState, confirmRemoveProject as _projConfirmRemoveProject, handleRemoveProjectCheckResult as _projHandleRemoveProjectCheckResult, handleRemoveProjectResult as _projHandleRemoveProjectResult, showUpdateAvailable as _projShowUpdateAvailable, getCachedProjects, setCachedProjects, getCachedProjectCount, getCachedRemovedProjects, setCachedRemovedProjects } from './modules/app-projects.js';
101
107
  import { initRendering, addToMessages as _renAddToMessages, scrollToBottom as _renScrollToBottom, forceScrollToBottom as _renForceScrollToBottom, addUserMessage as _renAddUserMessage, getMsgTime as _renGetMsgTime, shouldGroupMessage as _renShouldGroupMessage, ensureAssistantBlock as _renEnsureAssistantBlock, addCopyHandler as _renAddCopyHandler, appendDelta as _renAppendDelta, flushStreamBuffer as _renFlushStreamBuffer, finalizeAssistantBlock as _renFinalizeAssistantBlock, addSystemMessage as _renAddSystemMessage, addConflictMessage as _renAddConflictMessage, addContextOverflowMessage as _renAddContextOverflowMessage, showClaudePreThinking as _renShowClaudePreThinking, showMatePreThinking as _renShowMatePreThinking, removeMatePreThinking as _renRemoveMatePreThinking, showSuggestionChips as _renShowSuggestionChips, hideSuggestionChips as _renHideSuggestionChips, getGhostSuggestion as _renGetGhostSuggestion, getTurnCounter, setTurnCounter, getPrependAnchor, setPrependAnchor, getActivityEl, setActivityEl, getIsUserScrolledUp, setIsUserScrolledUp, getStickyBottom, armStickyBottom, disarmStickyBottom } from './modules/app-rendering.js';
102
108
  import { initDm, openDm as _dmOpenDm, enterDmMode as _dmEnterDmMode, exitDmMode as _dmExitDmMode, handleMateCreatedInApp as _dmHandleMateCreatedInApp, buildMateInterviewPrompt as _dmBuildMateInterviewPrompt, updateMateIconStatus as _dmUpdateMateIconStatus, connectMateProject as _dmConnectMateProject, disconnectMateProject as _dmDisconnectMateProject, appendDmMessage as _dmAppendDmMessage, showDmTypingIndicator as _dmShowDmTypingIndicator, handleDmSend as _dmHandleDmSend } from './modules/app-dm.js';
103
109
  import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
@@ -192,7 +198,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
192
198
  function updateProjectList(msg) { _projUpdateProjectList(msg); }
193
199
  function renderTopbarPresence(serverUsers) { _projRenderTopbarPresence(serverUsers); }
194
200
  function renderProjectList() { _projRenderProjectList(); }
195
- // projectListAddBtn listener -> modules/app-projects.js (initProjects)
201
+ // projectListAddBtn listener -> modules/project-start-dialog.js (initProjectStart)
196
202
 
197
203
  // Prevent Cmd+Z / Cmd+Shift+Z from triggering browser back/forward (Arc, etc.)
198
204
  // Always block browser default for Cmd+Z and manually invoke undo/redo via execCommand
@@ -280,6 +286,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
280
286
  createStore({
281
287
  networkSettings: { loaded: false, busy: false, confirming: false, draft: "", saved: "", status: "", errorKind: "", requestId: 0 },
282
288
  vendorLoginBrowser: null,
289
+ githubStarCount: null,
283
290
  sharedBrowser: null,
284
291
  sharedBrowsers: [],
285
292
  mcpSkillsWorkbench: {},
@@ -421,6 +428,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
421
428
  projectsAccessLoaded: false,
422
429
  projectsHubRestoreRequested: false,
423
430
  projectsShowGuide: false,
431
+ projectStart: initialProjectStart(),
432
+ projectStartFolder: emptyFolderState(),
424
433
  onboardingStep: 'welcome',
425
434
  onboardingIntent: null,
426
435
  currentProjectOwnerId: null,
@@ -645,6 +654,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
645
654
  vendorInfo: {},
646
655
  // Rate-limit data is projected only for the displayed session/vendor.
647
656
  rateLimitState: {},
657
+ vendorUsageOverview: null,
658
+ vendorUsageOpen: null,
648
659
  currentMode: "default",
649
660
  effectivePermissionMode: null,
650
661
  permissionCapabilities: { auto: false, mcpOverride: false },
@@ -789,8 +800,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
789
800
  switch (action) {
790
801
  case "createMate": startClayMateCreation(); break;
791
802
  case "openSettings":
792
- var sb = document.getElementById("server-settings-btn");
793
- if (sb) sb.click();
803
+ openSettings();
794
804
  break;
795
805
  case "showHome": showHomeHub(); break;
796
806
  }
@@ -1162,7 +1172,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1162
1172
  basePath: basePath,
1163
1173
  });
1164
1174
 
1165
- // --- User settings (full-screen overlay) ---
1175
+ // --- Unified full-screen settings shell (Personal, Project, Server panes) ---
1176
+ initSettingsShell();
1177
+
1178
+ // --- User settings (personal pane) ---
1166
1179
  initUserSettings({
1167
1180
  basePath: basePath,
1168
1181
  });
@@ -1230,11 +1243,11 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1230
1243
  }
1231
1244
  }
1232
1245
  }).catch(function () {});
1233
- // Hide server settings and update controls for non-admin users in multi-user mode
1246
+ // Hide update controls for non-admin users in multi-user mode. The settings
1247
+ // gear stays for everyone: Personal and Project settings are not admin-only,
1248
+ // and the shell only offers Server settings to administrators.
1234
1249
  checkAdminAccess().then(function (isAdmin) {
1235
1250
  if (store.get('isMultiUserMode') && !isAdmin) {
1236
- var settingsBtn = document.getElementById("server-settings-btn");
1237
- if (settingsBtn) settingsBtn.style.display = "none";
1238
1251
  var updatePill = document.getElementById("update-pill-wrap");
1239
1252
  if (updatePill) updatePill.style.display = "none";
1240
1253
  }
@@ -1262,8 +1275,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1262
1275
 
1263
1276
  // --- QR code ---
1264
1277
  initQrCode();
1265
- var sharePill = document.getElementById("share-pill");
1266
- if (sharePill) sharePill.addEventListener("click", triggerShare);
1267
1278
 
1268
1279
  // --- File browser ---
1269
1280
  initFileBrowser({
@@ -1447,11 +1458,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1447
1458
  function confirmRemoveProject(slug, name) { _projConfirmRemoveProject(slug, name); }
1448
1459
  function handleRemoveProjectCheckResult(msg) { _projHandleRemoveProjectCheckResult(msg); }
1449
1460
  function handleRemoveProjectResult(msg) { _projHandleRemoveProjectResult(msg); }
1450
- function openAddProjectModal() { _projOpenAddProjectModal(); }
1451
- function closeAddProjectModal() { _projCloseAddProjectModal(); }
1452
- function handleBrowseDirResult(msg) { _projHandleBrowseDirResult(msg); }
1453
- function handleAddProjectResult(msg) { _projHandleAddProjectResult(msg); }
1454
- function handleCloneProgress(msg) { _projHandleCloneProgress(msg); }
1455
1461
 
1456
1462
  // --- PWA install prompt -> modules/app-misc.js (set up in initMisc)
1457
1463
 
@@ -1467,9 +1473,11 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1467
1473
 
1468
1474
  // --- Rate Limit module ---
1469
1475
  initRateLimit();
1476
+ initVendorUsageOverview();
1477
+ initGithubStar();
1470
1478
 
1471
1479
  // --- Projects module ---
1472
- initProjects();
1480
+ initProjectStart();
1473
1481
 
1474
1482
  // --- Home Hub module ---
1475
1483
  initProjectMateNavigation();
@@ -81,7 +81,7 @@ html.contextual-pane-boot #connect-overlay { display: none !important; }
81
81
  #contextual-clay button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; color: var(--text); }
82
82
  #contextual-clay button .lucide { width: 15px; height: 15px; }
83
83
  #contextual-clay iframe { border: 0; flex: 1; width: 100%; min-height: 0; background: var(--bg); }
84
- #contextual-clay [data-clay-loading]:not([hidden]) { position: absolute; inset: 0; margin: 0; padding: 18px 12px; display: block; color: var(--text-muted); font-size: 13px; background: var(--bg); pointer-events: none; }
84
+ #contextual-clay [data-clay-loading]:not([hidden]) { position: absolute; inset: 0; margin: 0; padding: 18px 12px; display: flex; z-index: 1; color: var(--text-muted); font-size: 13px; background: var(--bg); pointer-events: none; }
85
85
  body:is(.contextual-chat-pane, .contextual-clay-native) { --content-width: 760px; }
86
86
  body:is(.contextual-chat-pane, .contextual-clay-native) #messages { padding-top: 14px; }
87
87
  body:is(.contextual-chat-pane, .contextual-clay-native) #messages .msg-assistant .md-content,
@@ -390,7 +390,7 @@
390
390
 
391
391
  /* --- Panel fullscreen --- */
392
392
  @media (min-width: 1024px) {
393
- #main-column:has(.panel-fullscreen:not(.hidden)) > .title-bar-content { display: none; }
393
+ #main-column:has(.panel-fullscreen:not(.hidden):not(#shared-browser-panel)) > .title-bar-content { display: none; }
394
394
  #main-panels:has(.panel-fullscreen:not(.hidden)) > #app { display: none; }
395
395
  }
396
396
  @media (max-width: 1023px) {
@@ -0,0 +1,21 @@
1
+ .mate-loading {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ gap: 12px;
7
+ color: var(--text-muted);
8
+ text-align: center;
9
+ }
10
+ .mate-loading[hidden] { display: none; }
11
+ .mate-loading-scene { position: relative; width: 210px; height: 148px; flex-shrink: 0; }
12
+ .mate-loading-avatar { position: absolute; width: 94px; height: 94px; object-fit: contain; left: 58px; top: 22px; }
13
+ .mate-loading-desk { position: absolute; width: 210px; height: auto; left: 0; bottom: 0; }
14
+ .mate-loading-label { margin: 0; max-width: 100%; font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
15
+ .mate-loading-dots { display: flex; gap: 6px; padding: 3px 0; }
16
+ .mate-loading-dots span { width: 6px; height: 6px; border-radius: 45%; background: var(--text-muted); opacity: .45; animation: mate-loading-bob 1.5s ease-in-out infinite; }
17
+ .mate-loading-dots span:nth-child(2) { animation-delay: .18s; }
18
+ .mate-loading-dots span:nth-child(3) { animation-delay: .36s; }
19
+ @keyframes mate-loading-bob { 0%, 60%, 100% { transform: translateY(0); opacity: .4; } 30% { transform: translateY(-4px); opacity: .8; } }
20
+ @media (prefers-reduced-motion: reduce) { .mate-loading-dots span { animation: none; } }
21
+ @media (max-height: 450px) { .mate-loading-scene { zoom: .7; } .mate-loading { gap: 8px; } }