fleetlens 0.14.0 → 0.15.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.
- package/app/apps/web/.next/BUILD_ID +1 -1
- package/app/apps/web/.next/app-path-routes-manifest.json +4 -0
- package/app/apps/web/.next/build-manifest.json +3 -3
- package/app/apps/web/.next/prerender-manifest.json +3 -3
- package/app/apps/web/.next/routes-manifest.json +24 -0
- package/app/apps/web/.next/server/app/_global-error.html +1 -1
- package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
- package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/api/ask/route.js +3 -3
- package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js +2 -2
- package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js +4 -4
- package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/settings/route.js +5 -4
- package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/api/team/autostart/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js +7 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/autostart/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js +8 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/onboarding/start/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/build-manifest.json +9 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js +8 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.map +5 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/api/team/sync-projects/route_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/changelog/page.js +1 -1
- package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js +1 -1
- package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js +1 -1
- package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js +1 -1
- package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/page.js +1 -1
- package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
- package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js +1 -1
- package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js +1 -1
- package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page.js +3 -3
- package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js +1 -1
- package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/settings/page.js +2 -2
- package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/team/onboarding/page/app-paths-manifest.json +3 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/build-manifest.json +17 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/next-font-manifest.json +6 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/react-loadable-manifest.json +1 -0
- package/app/apps/web/.next/server/app/team/onboarding/page/server-reference-manifest.json +4 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js +19 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js.map +5 -0
- package/app/apps/web/.next/server/app/team/onboarding/page.js.nft.json +1 -0
- package/app/apps/web/.next/server/app/team/onboarding/page_client-reference-manifest.js +3 -0
- package/app/apps/web/.next/server/app/team/page.js +3 -2
- package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js +1 -1
- package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
- package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/app-paths-manifest.json +4 -0
- package/app/apps/web/.next/server/chunks/02dz_web__next-internal_server_app_api_team_onboarding_start_route_actions_0zeptl_.js +3 -0
- package/app/apps/web/.next/server/chunks/0fuv_zod_v4_classic_external_0.z.vap.js +39 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__038-.1r._.js +6 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0~utv~v._.js → [root-of-the-server]__03ewxxu._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__03rmbz6._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[externals]__0o43ejh._.js → [root-of-the-server]__065lib8._.js} +2 -2
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0e.f-t.._.js → [root-of-the-server]__0a0~7d6._.js} +2 -2
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0gril0u._.js → [root-of-the-server]__0acikq.._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0et2sqb._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[externals]__09v47rg._.js → [root-of-the-server]__0jbc7tz._.js} +2 -2
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mq0or2._.js +3 -0
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0mvl0qn._.js +3 -0
- package/app/apps/web/.next/server/chunks/{[root-of-the-server]__0n79hpm._.js → [root-of-the-server]__0ow1kn1._.js} +4 -4
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0~xt99s._.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_autostart_route_actions_10-4s71.js +3 -0
- package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team_sync-projects_route_actions_0r_2f5n.js +3 -0
- package/app/apps/web/.next/server/chunks/node_modules_next_036my~r._.js +4 -40
- package/app/apps/web/.next/server/chunks/packages_09wmnv.._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_0o6.gpk._.js +3 -0
- package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
- package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0a7n44-._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0c00i76._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0i.q-1h._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0stu~~r._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0ufi3.w._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_0.97l5.._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/{apps_web_app_settings_0u6z6rj._.js → apps_web_0q2x4a~._.js} +2 -2
- package/app/apps/web/.next/server/chunks/ssr/apps_web_114yo0v._.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web__next-internal_server_app_team_onboarding_page_actions_0pss~.k.js +3 -0
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0c-tpft._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_components_0txtsv5._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
- package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_team-config_ts_10egg08._.js +39 -0
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_0w2y7fz._.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0jxvcfi.js +2 -2
- package/app/apps/web/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0s5yhs4.js +4 -0
- package/app/apps/web/.next/server/chunks/ssr/packages_entries_dist_node_0_271yr.js +1 -1
- package/app/apps/web/.next/server/functions-config-manifest.json +3 -0
- package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/app/apps/web/.next/server/middleware-manifest.json +5 -5
- package/app/apps/web/.next/server/pages/500.html +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/app/apps/web/.next/static/chunks/{0wwcx42ng8i2e.js → 0-3_u.90d79wi.js} +1 -1
- package/app/apps/web/.next/static/chunks/00a9oyw~h5i1p.js +1 -0
- package/app/apps/web/.next/static/chunks/040s42ypidk7s.css +1 -0
- package/app/apps/web/.next/static/chunks/06eiwghjkkbnh.js +1 -0
- package/app/apps/web/.next/static/chunks/06qeg4pt5jdqs.js +1 -0
- package/app/apps/web/.next/static/chunks/{0e_fzy0n8ij-o.js → 0u6gxpqhl0gc7.js} +1 -1
- package/app/apps/web/.next/static/chunks/18cj08-la37o-.js +1 -0
- package/app/apps/web/app/api/team/autostart/route.ts +41 -0
- package/app/apps/web/app/api/team/onboarding/start/route.ts +96 -0
- package/app/apps/web/app/api/team/sync-projects/route.ts +39 -0
- package/app/apps/web/app/globals.css +82 -0
- package/app/apps/web/app/settings/ai-features-form.tsx +1 -1
- package/app/apps/web/app/settings/page.tsx +21 -7
- package/app/apps/web/app/{settings → team}/force-sync-button.tsx +2 -4
- package/app/apps/web/app/team/onboarding/onboarding-wizard.tsx +503 -0
- package/app/apps/web/app/team/onboarding/page.tsx +28 -0
- package/app/apps/web/app/team/page.tsx +48 -17
- package/app/apps/web/app/{settings/team-connection-section.tsx → team/sync-activity-section.tsx} +6 -23
- package/app/apps/web/app/team/synced-projects-form.tsx +201 -0
- package/app/apps/web/app/team/synced-projects-section.tsx +37 -0
- package/app/apps/web/components/autostart-toggle.tsx +59 -0
- package/app/apps/web/components/project-sync-picker.tsx +109 -0
- package/app/apps/web/components/team-chip.tsx +1 -1
- package/app/apps/web/components/team-welcome-banner.tsx +1 -1
- package/app/apps/web/lib/autostart-data.ts +31 -0
- package/app/apps/web/lib/changelog-data.generated.ts +1 -1
- package/app/apps/web/lib/sync-projects-data.ts +36 -0
- package/app/apps/web/lib/team-config.ts +21 -2
- package/app/apps/web/package.json +1 -1
- package/app/apps/web/tsconfig.tsbuildinfo +1 -1
- package/dist/daemon-worker.js +150 -42
- package/dist/index.js +311 -100
- package/package.json +1 -1
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__08agijy._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0i06ito._.js +0 -3
- package/app/apps/web/.next/server/chunks/[root-of-the-server]__0y5p73y._.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_0c-sz9n._.js +0 -3
- package/app/apps/web/.next/server/chunks/packages_0wywuu.._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__09yxy53._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0_hv6aq._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0dh0q7b._.js +0 -3
- package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0yv0m0q._.js +0 -3
- package/app/apps/web/.next/static/chunks/005ad7l-h1-.6.js +0 -1
- package/app/apps/web/.next/static/chunks/0h.ua.-myj.6z.js +0 -1
- package/app/apps/web/.next/static/chunks/0l4r0bqqtdju..css +0 -1
- /package/app/apps/web/.next/static/{3VbAChKD8EChlt8Tm5N5r → C4y6rare_ZCcXXQLc8ysA}/_buildManifest.js +0 -0
- /package/app/apps/web/.next/static/{3VbAChKD8EChlt8Tm5N5r → C4y6rare_ZCcXXQLc8ysA}/_clientMiddlewareManifest.js +0 -0
- /package/app/apps/web/.next/static/{3VbAChKD8EChlt8Tm5N5r → C4y6rare_ZCcXXQLc8ysA}/_ssgManifest.js +0 -0
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Team-sync status page.
|
|
3
3
|
*
|
|
4
4
|
* Shows which team this machine's daemon is paired with and what data flows
|
|
5
|
-
* over the wire on the next push.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* daemon reads.
|
|
5
|
+
* over the wire on the next push. All LLM-enriched fields are shared for
|
|
6
|
+
* every synced project; which projects are synced is the member's own
|
|
7
|
+
* choice (Settings' Synced-projects selection, seeded by the onboarding
|
|
8
|
+
* wizard). The page reads the same on-disk team-config.json the daemon reads.
|
|
9
9
|
*/
|
|
10
10
|
import { readTeamConfig, toTeamConfigView } from "@/lib/team-config";
|
|
11
|
+
import { SyncedProjectsSection } from "./synced-projects-section";
|
|
12
|
+
import { SyncActivitySection } from "./sync-activity-section";
|
|
11
13
|
|
|
12
14
|
export const dynamic = "force-dynamic";
|
|
13
15
|
|
|
@@ -37,7 +39,7 @@ export default function TeamPage() {
|
|
|
37
39
|
return (
|
|
38
40
|
<main className="mx-auto max-w-3xl p-6 space-y-6">
|
|
39
41
|
<h1 className="text-2xl font-semibold">Team sync</h1>
|
|
40
|
-
<section className="
|
|
42
|
+
<section className="af-card border-dashed text-sm space-y-3">
|
|
41
43
|
<p className="font-medium">Not paired with any team.</p>
|
|
42
44
|
<p className="text-gray-600">
|
|
43
45
|
To pair this machine with a Fleetlens team server, run from a
|
|
@@ -56,6 +58,12 @@ fleetlens team join <server-url> <invite-token>
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
const view = toTeamConfigView(config);
|
|
61
|
+
const sp = config.syncProjects;
|
|
62
|
+
const syncSummary = sp
|
|
63
|
+
? sp.autoIncludeNew
|
|
64
|
+
? `Syncing: all projects except ${sp.excluded.length} excluded`
|
|
65
|
+
: `Syncing: only ${sp.included.length} selected project${sp.included.length === 1 ? "" : "s"}`
|
|
66
|
+
: null;
|
|
59
67
|
|
|
60
68
|
return (
|
|
61
69
|
<main className="mx-auto max-w-3xl p-6 space-y-8">
|
|
@@ -66,7 +74,14 @@ fleetlens team join <server-url> <invite-token>
|
|
|
66
74
|
</p>
|
|
67
75
|
</header>
|
|
68
76
|
|
|
69
|
-
|
|
77
|
+
{config.setupPending && (
|
|
78
|
+
<div className="border border-amber-400/40 bg-amber-400/10 rounded-lg p-4 text-sm">
|
|
79
|
+
Setup isn’t finished — nothing is syncing yet.{" "}
|
|
80
|
+
<a className="underline font-medium" href="/team/onboarding">Finish onboarding</a>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<section className="af-card space-y-3">
|
|
70
85
|
<div className="flex items-baseline justify-between">
|
|
71
86
|
<h2 className="text-lg font-medium">Pairing</h2>
|
|
72
87
|
<span className="text-xs uppercase tracking-wide text-green-700 dark:text-green-400">
|
|
@@ -77,7 +92,11 @@ fleetlens team join <server-url> <invite-token>
|
|
|
77
92
|
<dt className="text-gray-500">Team slug</dt>
|
|
78
93
|
<dd className="font-mono">{view.teamSlug}</dd>
|
|
79
94
|
<dt className="text-gray-500">Server</dt>
|
|
80
|
-
<dd className="font-mono break-all">
|
|
95
|
+
<dd className="font-mono break-all">
|
|
96
|
+
<a href={view.serverUrl} target="_blank" rel="noreferrer" className="hover:underline">
|
|
97
|
+
{view.serverUrl} <span aria-hidden>↗</span>
|
|
98
|
+
</a>
|
|
99
|
+
</dd>
|
|
81
100
|
<dt className="text-gray-500">Member ID</dt>
|
|
82
101
|
<dd className="font-mono break-all text-xs">{view.memberId}</dd>
|
|
83
102
|
<dt className="text-gray-500">Bearer token</dt>
|
|
@@ -91,11 +110,18 @@ fleetlens team join <server-url> <invite-token>
|
|
|
91
110
|
</dl>
|
|
92
111
|
</section>
|
|
93
112
|
|
|
94
|
-
<section className="
|
|
113
|
+
<section className="af-card">
|
|
114
|
+
<SyncActivitySection />
|
|
115
|
+
</section>
|
|
116
|
+
|
|
117
|
+
<section className="af-card">
|
|
118
|
+
<SyncedProjectsSection />
|
|
119
|
+
</section>
|
|
120
|
+
|
|
121
|
+
<section className="af-card space-y-3">
|
|
95
122
|
<h2 className="text-lg font-medium">What gets shared</h2>
|
|
96
123
|
<p className="text-sm text-gray-500">
|
|
97
|
-
|
|
98
|
-
There is no per-project or per-field opt-out.
|
|
124
|
+
{syncSummary ?? "Syncing: all projects (no selection set)."}
|
|
99
125
|
</p>
|
|
100
126
|
<ul className="text-sm space-y-2">
|
|
101
127
|
<li>
|
|
@@ -103,8 +129,8 @@ fleetlens team join <server-url> <invite-token>
|
|
|
103
129
|
calls, turns, token totals.
|
|
104
130
|
</li>
|
|
105
131
|
<li>
|
|
106
|
-
<strong>Rich rollup</strong> (Entry-derived counts) for
|
|
107
|
-
|
|
132
|
+
<strong>Rich rollup</strong> (Entry-derived counts) for each
|
|
133
|
+
synced project: working-shape distribution, top skills, subagents
|
|
108
134
|
dispatched, long-autonomous turns, plan-mode usage, brainstorm
|
|
109
135
|
warmups, tool errors, PRs/commits/pushes. Project labels only;
|
|
110
136
|
never raw prompts or agent output.
|
|
@@ -119,13 +145,18 @@ fleetlens team join <server-url> <invite-token>
|
|
|
119
145
|
model calls ride along with the push).
|
|
120
146
|
</li>
|
|
121
147
|
</ul>
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
text
|
|
125
|
-
|
|
148
|
+
<div className="af-panel p-4 text-sm space-y-2">
|
|
149
|
+
<div className="font-medium">What does NOT leave your machine</div>
|
|
150
|
+
<ul className="list-disc list-inside text-gray-600 space-y-1">
|
|
151
|
+
<li>Session transcripts, prompts, or assistant responses</li>
|
|
152
|
+
<li>Absolute paths, file contents, or tool-call payloads</li>
|
|
153
|
+
<li>Anything from projects you exclude above</li>
|
|
154
|
+
<li>Anything from sessions older than the start-of-day rollup window</li>
|
|
155
|
+
</ul>
|
|
156
|
+
</div>
|
|
126
157
|
</section>
|
|
127
158
|
|
|
128
|
-
<section className="
|
|
159
|
+
<section className="af-card space-y-3">
|
|
129
160
|
<h2 className="text-lg font-medium">Disconnect</h2>
|
|
130
161
|
<p className="text-sm text-gray-600">
|
|
131
162
|
To leave the team and stop all syncing, run:
|
package/app/apps/web/app/{settings/team-connection-section.tsx → team/sync-activity-section.tsx}
RENAMED
|
@@ -8,17 +8,17 @@ const HEALTH_COLORS = {
|
|
|
8
8
|
red: "var(--af-error, #ef4444)",
|
|
9
9
|
} as const;
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function SyncActivitySection() {
|
|
12
12
|
const conn = readTeamConnection();
|
|
13
13
|
if (!conn.paired) return null;
|
|
14
14
|
|
|
15
|
-
const {
|
|
15
|
+
const { lastPush, health } = conn;
|
|
16
16
|
const cliAvailable = Boolean(process.env.FLEETLENS_CLI_BIN);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<section id="team" className="space-y-4">
|
|
20
20
|
<div className="flex items-baseline gap-3">
|
|
21
|
-
<h2 className="text-lg font-medium">
|
|
21
|
+
<h2 className="text-lg font-medium">Sync activity</h2>
|
|
22
22
|
<span
|
|
23
23
|
aria-hidden
|
|
24
24
|
style={{
|
|
@@ -29,12 +29,9 @@ export function TeamConnectionSection() {
|
|
|
29
29
|
display: "inline-block",
|
|
30
30
|
}}
|
|
31
31
|
/>
|
|
32
|
-
<span className="text-sm text-gray-500">{team.name}</span>
|
|
33
32
|
</div>
|
|
34
33
|
|
|
35
34
|
<dl className="grid grid-cols-[8rem_1fr] gap-y-1 text-sm text-gray-600">
|
|
36
|
-
<dt>Server</dt><dd className="font-mono text-xs">{team.serverUrl}</dd>
|
|
37
|
-
<dt>Paired</dt><dd>{new Date(member.pairedAt).toLocaleString()}</dd>
|
|
38
35
|
<dt>Last sync</dt>
|
|
39
36
|
<dd suppressHydrationWarning>
|
|
40
37
|
{lastPush.kind === "none"
|
|
@@ -44,7 +41,7 @@ export function TeamConnectionSection() {
|
|
|
44
41
|
</dl>
|
|
45
42
|
|
|
46
43
|
{lastPush.kind === "ok" && (
|
|
47
|
-
<div className="
|
|
44
|
+
<div className="af-panel p-4 space-y-3 text-sm">
|
|
48
45
|
<div className="text-xs uppercase tracking-wide text-gray-500">
|
|
49
46
|
Last push
|
|
50
47
|
{lastPush.payload.dailyRollup ? ` — ${lastPush.payload.dailyRollup.day}` : ""}
|
|
@@ -93,7 +90,7 @@ export function TeamConnectionSection() {
|
|
|
93
90
|
<summary className="cursor-pointer text-gray-500 hover:text-gray-700">
|
|
94
91
|
Show raw JSON payload
|
|
95
92
|
</summary>
|
|
96
|
-
<pre className="mt-2
|
|
93
|
+
<pre className="af-panel mt-2 p-3 font-mono text-[11px] whitespace-pre-wrap overflow-x-auto">
|
|
97
94
|
{JSON.stringify(lastPush.payload, null, 2)}
|
|
98
95
|
</pre>
|
|
99
96
|
</details>
|
|
@@ -101,27 +98,13 @@ export function TeamConnectionSection() {
|
|
|
101
98
|
)}
|
|
102
99
|
|
|
103
100
|
{lastPush.kind === "error" && (
|
|
104
|
-
<div className="
|
|
101
|
+
<div className="af-panel-danger p-4 text-sm">
|
|
105
102
|
<div className="font-medium">Last sync failed</div>
|
|
106
103
|
<div>{lastPush.error}</div>
|
|
107
104
|
</div>
|
|
108
105
|
)}
|
|
109
106
|
|
|
110
107
|
<ForceSyncButton cliAvailable={cliAvailable} />
|
|
111
|
-
|
|
112
|
-
<div className="rounded border border-gray-200 p-4 text-sm space-y-2">
|
|
113
|
-
<div className="font-medium">What does NOT leave your machine</div>
|
|
114
|
-
<ul className="list-disc list-inside text-gray-600 space-y-1">
|
|
115
|
-
<li>Session transcripts, prompts, or assistant responses</li>
|
|
116
|
-
<li>Project names, paths, or repo information</li>
|
|
117
|
-
<li>File contents or tool-call payloads</li>
|
|
118
|
-
<li>Anything from sessions older than the start-of-day rollup window</li>
|
|
119
|
-
</ul>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<p className="text-xs text-gray-500">
|
|
123
|
-
To disconnect from this team, run <code>fleetlens team leave</code> in your terminal.
|
|
124
|
-
</p>
|
|
125
108
|
</section>
|
|
126
109
|
);
|
|
127
110
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { ProjectSyncPicker } from "@/components/project-sync-picker";
|
|
4
|
+
import type { SyncProjectRow } from "@/lib/sync-projects-data";
|
|
5
|
+
import type { SyncProjects } from "@/lib/team-config";
|
|
6
|
+
|
|
7
|
+
const ts = () => new Date().toTimeString().slice(0, 8);
|
|
8
|
+
|
|
9
|
+
export function SyncedProjectsForm({
|
|
10
|
+
projects,
|
|
11
|
+
initial,
|
|
12
|
+
}: {
|
|
13
|
+
projects: SyncProjectRow[];
|
|
14
|
+
initial: SyncProjects;
|
|
15
|
+
}) {
|
|
16
|
+
const [value, setValue] = useState<SyncProjects>(initial);
|
|
17
|
+
// Last-persisted selection — the read-only summary renders from this, so a
|
|
18
|
+
// cancelled edit or in-flight change never lies about what's syncing.
|
|
19
|
+
const [saved, setSaved] = useState<SyncProjects>(initial);
|
|
20
|
+
const [editing, setEditing] = useState(false);
|
|
21
|
+
const [saving, setSaving] = useState(false);
|
|
22
|
+
const [savedMsg, setSavedMsg] = useState<string | null>(null);
|
|
23
|
+
const [log, setLog] = useState<{ text: string; tone: "default" | "warn" | "error" }[] | null>(null);
|
|
24
|
+
const logRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
logRef.current?.scrollTo({ top: logRef.current.scrollHeight });
|
|
28
|
+
}, [log]);
|
|
29
|
+
|
|
30
|
+
const append = (text: string, tone: "default" | "warn" | "error" = "default") =>
|
|
31
|
+
setLog((prev) => [...(prev ?? []), { text: `${ts()} ${text}`, tone }]);
|
|
32
|
+
|
|
33
|
+
// Same NDJSON→SSE stream the onboarding wizard consumes; a changed
|
|
34
|
+
// selection re-pushes FULL history so the server matches it.
|
|
35
|
+
async function streamResync() {
|
|
36
|
+
setLog([]);
|
|
37
|
+
append("re-pushing your full history under the new selection…");
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch("/api/team/onboarding/start", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: { "content-type": "application/json" },
|
|
42
|
+
body: JSON.stringify(value),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok || !res.body) {
|
|
45
|
+
const body = await res.json().catch(() => null);
|
|
46
|
+
append(body?.error ?? `Error ${res.status}`, "error");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const reader = res.body.getReader();
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
let buf = "";
|
|
52
|
+
for (;;) {
|
|
53
|
+
const { done, value: chunk } = await reader.read();
|
|
54
|
+
if (done) break;
|
|
55
|
+
buf += decoder.decode(chunk, { stream: true });
|
|
56
|
+
const frames = buf.split("\n\n");
|
|
57
|
+
buf = frames.pop() ?? "";
|
|
58
|
+
for (const frame of frames) {
|
|
59
|
+
const data = frame.split("\n").find((l) => l.startsWith("data: "))?.slice(6);
|
|
60
|
+
if (!data) continue;
|
|
61
|
+
let ev;
|
|
62
|
+
try {
|
|
63
|
+
ev = JSON.parse(data);
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ev.type === "phase" && ev.phase === "activity") append(`pushing ${ev.totalDays} day(s), newest first…`);
|
|
68
|
+
else if (ev.type === "day")
|
|
69
|
+
append(
|
|
70
|
+
ev.outcome === "pushed"
|
|
71
|
+
? `✓ ${ev.day} pushed (${ev.index}/${ev.total})`
|
|
72
|
+
: `${ev.outcome === "queued" ? "⚠" : "✗"} ${ev.day} ${ev.outcome} (${ev.index}/${ev.total})`,
|
|
73
|
+
ev.outcome === "pushed" ? "default" : ev.outcome === "queued" ? "warn" : "error",
|
|
74
|
+
);
|
|
75
|
+
else if (ev.type === "done") append(`done — ${ev.pushed} day(s) pushed`);
|
|
76
|
+
else if (ev.type === "error") append(ev.message ?? "sync error", "error");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
append("server now matches your selection");
|
|
80
|
+
} catch (err) {
|
|
81
|
+
append(`stream failed: ${(err as Error).message} — the daemon finishes the re-push within ~5 min`, "warn");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function handleSave() {
|
|
86
|
+
setSaving(true);
|
|
87
|
+
setSavedMsg(null);
|
|
88
|
+
setLog(null);
|
|
89
|
+
const res = await fetch("/api/team/sync-projects", {
|
|
90
|
+
method: "PUT",
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
body: JSON.stringify(value),
|
|
93
|
+
});
|
|
94
|
+
const body = await res.json().catch(() => null);
|
|
95
|
+
setSaving(false);
|
|
96
|
+
setSavedMsg(
|
|
97
|
+
res.ok ? (body?.resync ? "Saved — selection changed." : "Saved.") : (body?.error ?? `Error: ${res.status}`),
|
|
98
|
+
);
|
|
99
|
+
if (res.ok) {
|
|
100
|
+
setSaved(value);
|
|
101
|
+
setEditing(false);
|
|
102
|
+
}
|
|
103
|
+
if (body?.resync) void streamResync();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isSynced = (name: string) =>
|
|
107
|
+
saved.excluded.includes(name) ? false : saved.included.includes(name) ? true : saved.autoIncludeNew;
|
|
108
|
+
const synced = projects.filter((p) => isSynced(p.name));
|
|
109
|
+
const privateCount = projects.length - synced.length;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="space-y-4">
|
|
113
|
+
{!editing && (
|
|
114
|
+
<div className="space-y-3">
|
|
115
|
+
<p className="text-sm">
|
|
116
|
+
Syncing <strong>{synced.length}</strong> of {projects.length} projects
|
|
117
|
+
{saved.autoIncludeNew ? " — new projects sync automatically." : " — new projects stay private."}
|
|
118
|
+
</p>
|
|
119
|
+
<div className="flex flex-wrap gap-1.5">
|
|
120
|
+
{synced.slice(0, 8).map((p) => (
|
|
121
|
+
<a
|
|
122
|
+
key={p.name}
|
|
123
|
+
href={`/projects/${encodeURIComponent(p.projectDir)}`}
|
|
124
|
+
className="af-chip"
|
|
125
|
+
>
|
|
126
|
+
{p.name}
|
|
127
|
+
</a>
|
|
128
|
+
))}
|
|
129
|
+
{synced.length > 8 && (
|
|
130
|
+
<span className="px-2 py-0.5 text-xs text-gray-500">+{synced.length - 8} more</span>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
{privateCount > 0 && (
|
|
134
|
+
<p className="text-xs text-gray-500">
|
|
135
|
+
{privateCount} project{privateCount === 1 ? " stays" : "s stay"} private on this machine.
|
|
136
|
+
</p>
|
|
137
|
+
)}
|
|
138
|
+
<div className="flex items-center gap-3">
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
onClick={() => {
|
|
142
|
+
setValue(saved);
|
|
143
|
+
setSavedMsg(null);
|
|
144
|
+
setEditing(true);
|
|
145
|
+
}}
|
|
146
|
+
className="af-btn"
|
|
147
|
+
>
|
|
148
|
+
Edit selection
|
|
149
|
+
</button>
|
|
150
|
+
{savedMsg && <p className="text-sm">{savedMsg}</p>}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
{editing && (
|
|
155
|
+
<>
|
|
156
|
+
<ProjectSyncPicker projects={projects} value={value} onChange={setValue} />
|
|
157
|
+
<div className="flex items-center gap-3">
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={handleSave}
|
|
161
|
+
disabled={saving}
|
|
162
|
+
className="af-btn-primary"
|
|
163
|
+
>
|
|
164
|
+
{saving ? "Saving…" : "Save"}
|
|
165
|
+
</button>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
onClick={() => {
|
|
169
|
+
setValue(saved);
|
|
170
|
+
setEditing(false);
|
|
171
|
+
setSavedMsg(null);
|
|
172
|
+
}}
|
|
173
|
+
disabled={saving}
|
|
174
|
+
className="af-btn"
|
|
175
|
+
>
|
|
176
|
+
Cancel
|
|
177
|
+
</button>
|
|
178
|
+
{savedMsg && <p className="text-sm">{savedMsg}</p>}
|
|
179
|
+
</div>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
182
|
+
{log && (
|
|
183
|
+
<div
|
|
184
|
+
ref={logRef}
|
|
185
|
+
className="max-h-60 overflow-y-auto rounded-lg bg-gray-950 p-3 font-mono text-xs text-gray-200"
|
|
186
|
+
>
|
|
187
|
+
{log.map((line, i) => (
|
|
188
|
+
<div
|
|
189
|
+
key={i}
|
|
190
|
+
className={
|
|
191
|
+
line.tone === "error" ? "text-red-400" : line.tone === "warn" ? "text-amber-400" : undefined
|
|
192
|
+
}
|
|
193
|
+
>
|
|
194
|
+
{line.text}
|
|
195
|
+
</div>
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readTeamConfig } from "@/lib/team-config";
|
|
2
|
+
import { listSyncProjectRows } from "@/lib/sync-projects-data";
|
|
3
|
+
import { SyncedProjectsForm } from "./synced-projects-form";
|
|
4
|
+
|
|
5
|
+
export async function SyncedProjectsSection() {
|
|
6
|
+
const config = readTeamConfig();
|
|
7
|
+
if (!config) return null;
|
|
8
|
+
|
|
9
|
+
if (config.setupPending) {
|
|
10
|
+
return (
|
|
11
|
+
<section className="space-y-2">
|
|
12
|
+
<h2 className="text-lg font-medium">Synced projects</h2>
|
|
13
|
+
<p className="text-sm text-gray-500">
|
|
14
|
+
Setup isn’t finished yet — finish the{" "}
|
|
15
|
+
<a className="underline" href="/team/onboarding">onboarding wizard</a>{" "}
|
|
16
|
+
to choose which projects sync to your team.
|
|
17
|
+
</p>
|
|
18
|
+
</section>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const projects = await listSyncProjectRows();
|
|
23
|
+
return (
|
|
24
|
+
<section className="space-y-3">
|
|
25
|
+
<h2 className="text-lg font-medium">Synced projects</h2>
|
|
26
|
+
<p className="text-sm text-gray-500">
|
|
27
|
+
Choose which projects share metrics with your team. Saving a changed
|
|
28
|
+
selection re-pushes your full history so the server matches it —
|
|
29
|
+
excluded projects disappear, newly included ones backfill.
|
|
30
|
+
</p>
|
|
31
|
+
<SyncedProjectsForm
|
|
32
|
+
projects={projects}
|
|
33
|
+
initial={config.syncProjects ?? { autoIncludeNew: true, included: [], excluded: [] }}
|
|
34
|
+
/>
|
|
35
|
+
</section>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import type { AutostartState } from "@/lib/autostart-data";
|
|
4
|
+
|
|
5
|
+
export function AutostartToggle({ initial }: { initial: AutostartState }) {
|
|
6
|
+
const [state, setState] = useState(initial);
|
|
7
|
+
const [busy, setBusy] = useState(false);
|
|
8
|
+
const [error, setError] = useState<string | null>(null);
|
|
9
|
+
|
|
10
|
+
if (!state.supported) {
|
|
11
|
+
return (
|
|
12
|
+
<p className="text-sm text-gray-500">
|
|
13
|
+
Auto-start uses a macOS LaunchAgent and is macOS-only for now. Add{" "}
|
|
14
|
+
<code className="font-mono text-xs">fleetlens daemon start</code> to your login items manually.
|
|
15
|
+
</p>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function toggle(enabled: boolean) {
|
|
20
|
+
setBusy(true);
|
|
21
|
+
setError(null);
|
|
22
|
+
const res = await fetch("/api/team/autostart", {
|
|
23
|
+
method: "PUT",
|
|
24
|
+
headers: { "content-type": "application/json" },
|
|
25
|
+
body: JSON.stringify({ enabled }),
|
|
26
|
+
});
|
|
27
|
+
const body = await res.json().catch(() => null);
|
|
28
|
+
setBusy(false);
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
setError(body?.error ?? `Error ${res.status}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setState(body);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="space-y-2">
|
|
38
|
+
<label className="flex cursor-pointer items-start gap-2 text-sm">
|
|
39
|
+
<input
|
|
40
|
+
type="checkbox"
|
|
41
|
+
checked={state.installed}
|
|
42
|
+
disabled={busy}
|
|
43
|
+
onChange={(e) => void toggle(e.target.checked)}
|
|
44
|
+
/>
|
|
45
|
+
<span>
|
|
46
|
+
Start Fleetlens at login (dashboard + usage daemon)
|
|
47
|
+
<span className="mt-0.5 block text-xs text-gray-500">
|
|
48
|
+
{state.installed
|
|
49
|
+
? "Enabled — the dashboard and reporting come back after a reboot."
|
|
50
|
+
: state.optedOut
|
|
51
|
+
? "Disabled — you opted out; joining a team won't re-enable it."
|
|
52
|
+
: "Disabled — pairing with a team enables this automatically."}
|
|
53
|
+
</span>
|
|
54
|
+
</span>
|
|
55
|
+
</label>
|
|
56
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import type { SyncProjectRow } from "@/lib/sync-projects-data";
|
|
5
|
+
import type { SyncProjects } from "@/lib/team-config";
|
|
6
|
+
import { formatRelative } from "@/lib/format";
|
|
7
|
+
|
|
8
|
+
/** Compact agent-time label for a picker row — not `formatDuration`'s "1h 30m"
|
|
9
|
+
* form, which reads noisy next to sessions/last-active in a dense list. */
|
|
10
|
+
function formatAgentTimeShort(ms: number): string {
|
|
11
|
+
if (ms < 3_600_000) return `${Math.round(ms / 60_000)}m`;
|
|
12
|
+
return `${Math.round((ms / 3_600_000) * 10) / 10}h`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ProjectSyncPicker({
|
|
16
|
+
projects,
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
}: {
|
|
20
|
+
projects: SyncProjectRow[];
|
|
21
|
+
value: SyncProjects;
|
|
22
|
+
onChange: (v: SyncProjects) => void;
|
|
23
|
+
}) {
|
|
24
|
+
const [query, setQuery] = useState("");
|
|
25
|
+
|
|
26
|
+
function checked(name: string): boolean {
|
|
27
|
+
if (value.excluded.includes(name)) return false;
|
|
28
|
+
if (value.included.includes(name)) return true;
|
|
29
|
+
return value.autoIncludeNew;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toggle(name: string, next: boolean) {
|
|
33
|
+
const included = value.included.filter((n) => n !== name);
|
|
34
|
+
const excluded = value.excluded.filter((n) => n !== name);
|
|
35
|
+
(next ? included : excluded).push(name);
|
|
36
|
+
onChange({ ...value, included, excluded });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const filtered = useMemo(() => {
|
|
40
|
+
const q = query.trim().toLowerCase();
|
|
41
|
+
if (!q) return projects;
|
|
42
|
+
return projects.filter((p) => p.name.toLowerCase().includes(q));
|
|
43
|
+
}, [projects, query]);
|
|
44
|
+
|
|
45
|
+
function selectAll(next: boolean) {
|
|
46
|
+
let included = [...value.included];
|
|
47
|
+
let excluded = [...value.excluded];
|
|
48
|
+
for (const p of filtered) {
|
|
49
|
+
included = included.filter((n) => n !== p.name);
|
|
50
|
+
excluded = excluded.filter((n) => n !== p.name);
|
|
51
|
+
(next ? included : excluded).push(p.name);
|
|
52
|
+
}
|
|
53
|
+
onChange({ ...value, included, excluded });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="space-y-3">
|
|
58
|
+
<div className="flex items-center gap-3">
|
|
59
|
+
<input
|
|
60
|
+
type="text"
|
|
61
|
+
value={query}
|
|
62
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
63
|
+
placeholder="Filter projects…"
|
|
64
|
+
className="flex-1 text-sm"
|
|
65
|
+
/>
|
|
66
|
+
<button type="button" onClick={() => selectAll(true)} className="text-xs text-gray-500 underline hover:text-gray-700">
|
|
67
|
+
{query.trim() ? "Select visible" : "Select all"}
|
|
68
|
+
</button>
|
|
69
|
+
<button type="button" onClick={() => selectAll(false)} className="text-xs text-gray-500 underline hover:text-gray-700">
|
|
70
|
+
{query.trim() ? "Deselect visible" : "Deselect all"}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div className="af-panel max-h-96 divide-y overflow-y-auto">
|
|
75
|
+
{filtered.length === 0 && (
|
|
76
|
+
<div className="p-3 text-sm text-gray-500">No projects match “{query}”.</div>
|
|
77
|
+
)}
|
|
78
|
+
{filtered.map((p) => (
|
|
79
|
+
<label
|
|
80
|
+
key={p.name}
|
|
81
|
+
className="flex cursor-pointer items-center gap-3 p-3 text-sm af-row"
|
|
82
|
+
>
|
|
83
|
+
<input type="checkbox" checked={checked(p.name)} onChange={(e) => toggle(p.name, e.target.checked)} />
|
|
84
|
+
<div className="min-w-0 flex-1">
|
|
85
|
+
<div className="truncate font-mono">{p.name}</div>
|
|
86
|
+
<div className="text-xs text-gray-500">
|
|
87
|
+
{p.sessions} session{p.sessions === 1 ? "" : "s"} · {formatAgentTimeShort(p.agentTimeMs)}
|
|
88
|
+
{p.lastActiveMs !== null && <> · {formatRelative(new Date(p.lastActiveMs).toISOString())}</>}
|
|
89
|
+
{p.worktreeCount > 1 && <> · ×{p.worktreeCount} worktrees</>}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</label>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<label className="flex items-start gap-2 text-sm">
|
|
97
|
+
<input
|
|
98
|
+
type="checkbox"
|
|
99
|
+
checked={value.autoIncludeNew}
|
|
100
|
+
onChange={(e) => onChange({ ...value, autoIncludeNew: e.target.checked })}
|
|
101
|
+
/>
|
|
102
|
+
<span>
|
|
103
|
+
Automatically sync projects that appear later
|
|
104
|
+
<span className="mt-0.5 block text-xs text-gray-500">Unchecked projects never leave this machine.</span>
|
|
105
|
+
</span>
|
|
106
|
+
</label>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -46,7 +46,7 @@ export function TeamWelcomeBanner({
|
|
|
46
46
|
activity totals and current cycle utilization to the team dashboard
|
|
47
47
|
every 5 minutes. Transcripts, prompts, and project content never leave
|
|
48
48
|
your machine.{" "}
|
|
49
|
-
<Link href="/
|
|
49
|
+
<Link href="/team" style={{ color: "var(--af-accent)", textDecoration: "underline" }}>
|
|
50
50
|
See exactly what's shared →
|
|
51
51
|
</Link>
|
|
52
52
|
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { cclensHome } from "@claude-lens/parser/fs";
|
|
5
|
+
|
|
6
|
+
// Mirrors packages/cli/src/commands/autostart.ts state (plist presence +
|
|
7
|
+
// ~/.cclens/autostart.json flags). Reads only — mutations go through the CLI
|
|
8
|
+
// binary so the plist always gets the CLI's own node/script paths baked in.
|
|
9
|
+
|
|
10
|
+
const PLIST = join(homedir(), "Library", "LaunchAgents", "com.fleetlens.daemon.plist");
|
|
11
|
+
|
|
12
|
+
export type AutostartState = {
|
|
13
|
+
supported: boolean;
|
|
14
|
+
installed: boolean;
|
|
15
|
+
optedOut: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function readAutostartState(): AutostartState {
|
|
19
|
+
const supported = process.platform === "darwin";
|
|
20
|
+
const installed = supported && existsSync(PLIST);
|
|
21
|
+
let optedOut = false;
|
|
22
|
+
try {
|
|
23
|
+
const flags = JSON.parse(readFileSync(join(cclensHome(), "autostart.json"), "utf8")) as {
|
|
24
|
+
optedOut?: boolean;
|
|
25
|
+
};
|
|
26
|
+
optedOut = flags.optedOut === true;
|
|
27
|
+
} catch {
|
|
28
|
+
// no flags file — never opted out
|
|
29
|
+
}
|
|
30
|
+
return { supported, installed, optedOut };
|
|
31
|
+
}
|