march-control-cli 0.1.3

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 (53) hide show
  1. package/README.md +220 -0
  2. package/core/apply.js +152 -0
  3. package/core/backup.js +53 -0
  4. package/core/constants.js +55 -0
  5. package/core/desktop-service.js +219 -0
  6. package/core/desktop-state.js +511 -0
  7. package/core/index.js +1293 -0
  8. package/core/paths.js +71 -0
  9. package/core/presets.js +171 -0
  10. package/core/probe.js +70 -0
  11. package/core/store.js +218 -0
  12. package/core/utils.js +178 -0
  13. package/core/writers/codex.js +102 -0
  14. package/core/writers/index.js +16 -0
  15. package/core/writers/openclaw.js +93 -0
  16. package/core/writers/opencode.js +91 -0
  17. package/desktop/assets/march-mark.svg +21 -0
  18. package/desktop/main.js +192 -0
  19. package/desktop/preload.js +49 -0
  20. package/desktop/renderer/app.js +327 -0
  21. package/desktop/renderer/index.html +130 -0
  22. package/desktop/renderer/styles.css +413 -0
  23. package/package.json +106 -0
  24. package/scripts/desktop-dev.mjs +90 -0
  25. package/scripts/postinstall.mjs +28 -0
  26. package/scripts/serve-site.mjs +51 -0
  27. package/site/app.js +10 -0
  28. package/site/assets/march-mark.svg +22 -0
  29. package/site/index.html +286 -0
  30. package/site/styles.css +566 -0
  31. package/src/App.tsx +1186 -0
  32. package/src/components/layout/app-sidebar.tsx +103 -0
  33. package/src/components/layout/top-toolbar.tsx +44 -0
  34. package/src/components/layout/workspace-tabs.tsx +32 -0
  35. package/src/components/providers/inspector-panel.tsx +84 -0
  36. package/src/components/providers/metric-strip.tsx +26 -0
  37. package/src/components/providers/provider-editor.tsx +87 -0
  38. package/src/components/providers/provider-table.tsx +85 -0
  39. package/src/components/ui/logo-mark.tsx +16 -0
  40. package/src/features/mcp/mcp-view.tsx +45 -0
  41. package/src/features/prompts/prompts-view.tsx +40 -0
  42. package/src/features/providers/providers-view.tsx +40 -0
  43. package/src/features/providers/types.ts +8 -0
  44. package/src/features/skills/skills-view.tsx +44 -0
  45. package/src/hooks/use-control-workspace.ts +184 -0
  46. package/src/index.css +22 -0
  47. package/src/lib/client.ts +944 -0
  48. package/src/lib/query-client.ts +3 -0
  49. package/src/lib/workspace-sections.ts +34 -0
  50. package/src/main.tsx +14 -0
  51. package/src/types.ts +76 -0
  52. package/src/vite-env.d.ts +56 -0
  53. package/src-tauri/README.md +11 -0
@@ -0,0 +1,103 @@
1
+ import clsx from "clsx";
2
+ import { Bot, Boxes, ChevronRight, Search, Settings2, ShieldCheck, Sparkles, TerminalSquare } from "lucide-react";
3
+ import type { AppSnapshot, PlatformId } from "../../types";
4
+ import { LogoMark } from "../ui/logo-mark";
5
+
6
+ const platformIcons = {
7
+ codex: Bot,
8
+ opencode: Boxes,
9
+ openclaw: ShieldCheck
10
+ };
11
+
12
+ export function AppSidebar(props: {
13
+ snapshot: AppSnapshot;
14
+ activePlatform: PlatformId;
15
+ onPlatformChange: (platform: PlatformId) => void;
16
+ }) {
17
+ return (
18
+ <aside className="glass flex w-[252px] shrink-0 flex-col rounded-[28px] border border-white/6 px-4 py-5 shadow-[0_24px_80px_rgba(0,0,0,0.45)]">
19
+ <div className="flex items-center gap-3 px-2">
20
+ <LogoMark />
21
+ <div>
22
+ <div className="text-[11px] font-semibold uppercase tracking-[0.34em] text-slate-500">March</div>
23
+ <div className="font-['Space_Grotesk'] text-xl font-bold text-slate-50">Control</div>
24
+ </div>
25
+ </div>
26
+
27
+ <div className="mt-6 rounded-2xl border border-white/6 bg-white/[0.02] px-3 py-2">
28
+ <div className="flex items-center gap-2 text-sm text-slate-400">
29
+ <Search className="h-4 w-4" />
30
+ <span>Search providers / routes / models</span>
31
+ </div>
32
+ </div>
33
+
34
+ <div className="mt-6">
35
+ <div className="px-2 text-[11px] uppercase tracking-[0.3em] text-slate-500">Clients</div>
36
+ <div className="mt-2 space-y-2">
37
+ {props.snapshot.platforms.map((item) => {
38
+ const Icon = platformIcons[item.id];
39
+
40
+ return (
41
+ <button
42
+ key={item.id}
43
+ type="button"
44
+ onClick={() => props.onPlatformChange(item.id)}
45
+ className={clsx(
46
+ "flex w-full items-center gap-3 rounded-2xl border px-3 py-3 text-left transition",
47
+ item.id === props.activePlatform
48
+ ? "border-cyan-300/20 bg-cyan-400/[0.08]"
49
+ : "border-white/6 bg-white/[0.02] hover:bg-white/[0.045]"
50
+ )}
51
+ >
52
+ <div
53
+ className={clsx(
54
+ "rounded-xl p-2",
55
+ item.id === props.activePlatform ? "bg-cyan-400/12 text-cyan-200" : "bg-slate-900/70 text-slate-400"
56
+ )}
57
+ >
58
+ <Icon className="h-4 w-4" />
59
+ </div>
60
+ <div className="min-w-0 flex-1">
61
+ <div className="font-medium text-slate-100">{item.label}</div>
62
+ <div className="truncate text-xs text-slate-500">{item.providerCount} saved routes</div>
63
+ </div>
64
+ <ChevronRight className="h-4 w-4 text-slate-600" />
65
+ </button>
66
+ );
67
+ })}
68
+ </div>
69
+ </div>
70
+
71
+ <div className="mt-6">
72
+ <div className="px-2 text-[11px] uppercase tracking-[0.3em] text-slate-500">Modules</div>
73
+ <div className="mt-2 space-y-2">
74
+ <div className="rounded-2xl border border-white/6 bg-white/[0.02] px-4 py-3">
75
+ <div className="flex items-center gap-2 text-sm text-slate-200">
76
+ <TerminalSquare className="h-4 w-4 text-cyan-300" />
77
+ Provider Workspace
78
+ </div>
79
+ </div>
80
+ <div className="rounded-2xl border border-white/6 bg-white/[0.02] px-4 py-3">
81
+ <div className="flex items-center gap-2 text-sm text-slate-200">
82
+ <Sparkles className="h-4 w-4 text-slate-300" />
83
+ MCP / Prompts / Skills
84
+ </div>
85
+ </div>
86
+ <div className="rounded-2xl border border-white/6 bg-white/[0.02] px-4 py-3">
87
+ <div className="flex items-center gap-2 text-sm text-slate-200">
88
+ <Settings2 className="h-4 w-4 text-slate-300" />
89
+ Desktop Settings
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+
95
+ <div className="mt-auto rounded-[24px] border border-cyan-300/10 bg-gradient-to-br from-cyan-400/[0.07] to-teal-400/[0.05] p-4">
96
+ <div className="text-[11px] uppercase tracking-[0.3em] text-cyan-200/65">Visual Target</div>
97
+ <div className="mt-3 text-sm font-medium leading-6 text-slate-200">
98
+ CC Switch-style structure and density, with your own name and logo only.
99
+ </div>
100
+ </div>
101
+ </aside>
102
+ );
103
+ }
@@ -0,0 +1,44 @@
1
+ export function TopToolbar(props: {
2
+ title: string;
3
+ currentRoute: string | null;
4
+ onRefresh: () => void;
5
+ onProbe: () => void;
6
+ }) {
7
+ return (
8
+ <header className="flex items-center justify-between gap-4 border-b border-white/6 pb-5">
9
+ <div>
10
+ <div className="text-[11px] uppercase tracking-[0.34em] text-slate-500">Desktop Control Center</div>
11
+ <h1 className="mt-2 font-['Space_Grotesk'] text-[38px] font-bold tracking-[-0.05em] text-slate-50">
12
+ {props.title}
13
+ </h1>
14
+ <p className="mt-2 max-w-3xl text-sm leading-6 text-slate-400">
15
+ Closer to the CC Switch control-surface layout, with denser modules and utility-first hierarchy.
16
+ </p>
17
+ </div>
18
+
19
+ <div className="flex items-center gap-3">
20
+ <button className="rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-200">MCP</button>
21
+ <button className="rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-200">Prompts</button>
22
+ <button className="rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-200">Skills</button>
23
+ <div className="rounded-2xl border border-emerald-400/10 bg-emerald-400/[0.05] px-4 py-3">
24
+ <div className="text-[11px] uppercase tracking-[0.28em] text-emerald-300/70">Current Route</div>
25
+ <div className="mt-1 text-sm font-medium text-emerald-200">{props.currentRoute || "No Active Route"}</div>
26
+ </div>
27
+ <button
28
+ type="button"
29
+ onClick={props.onRefresh}
30
+ className="rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-200"
31
+ >
32
+ Refresh
33
+ </button>
34
+ <button
35
+ type="button"
36
+ onClick={props.onProbe}
37
+ className="rounded-2xl border border-cyan-300/18 bg-cyan-400/[0.1] px-4 py-3 text-sm font-medium text-cyan-100"
38
+ >
39
+ Probe
40
+ </button>
41
+ </div>
42
+ </header>
43
+ );
44
+ }
@@ -0,0 +1,32 @@
1
+ import clsx from "clsx";
2
+ import { workspaceSections, type WorkspaceSectionId } from "../../lib/workspace-sections";
3
+
4
+ export function WorkspaceTabs(props: {
5
+ activeSection: WorkspaceSectionId;
6
+ onSectionChange: (section: WorkspaceSectionId) => void;
7
+ }) {
8
+ return (
9
+ <div className="mt-4 flex items-center gap-2 border-b border-white/6 pb-4">
10
+ {workspaceSections.map((section) => {
11
+ const Icon = section.icon;
12
+
13
+ return (
14
+ <button
15
+ key={section.id}
16
+ type="button"
17
+ onClick={() => props.onSectionChange(section.id)}
18
+ className={clsx(
19
+ "inline-flex items-center gap-2 rounded-2xl border px-4 py-3 text-sm transition",
20
+ props.activeSection === section.id
21
+ ? "border-cyan-300/18 bg-cyan-400/[0.1] text-cyan-100"
22
+ : "border-white/8 bg-white/[0.03] text-slate-300 hover:bg-white/[0.05]"
23
+ )}
24
+ >
25
+ <Icon className="h-4 w-4" />
26
+ {section.label}
27
+ </button>
28
+ );
29
+ })}
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,84 @@
1
+ import { Activity, Rocket } from "lucide-react";
2
+ import type { PlatformSnapshot, ProbeResult } from "../../types";
3
+
4
+ export function InspectorPanel(props: { platform: PlatformSnapshot; probeResults?: ProbeResult[] }) {
5
+ return (
6
+ <article className="space-y-4">
7
+ <div className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
8
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Health</div>
9
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
10
+ Route latency
11
+ </h2>
12
+
13
+ <div className="mt-4 space-y-2">
14
+ {(props.probeResults || []).map((result) => (
15
+ <div
16
+ key={result.baseUrl}
17
+ className="flex items-center justify-between rounded-2xl border border-white/6 bg-white/[0.03] px-3 py-3"
18
+ >
19
+ <div className="min-w-0 pr-3">
20
+ <div className="truncate text-sm text-slate-200">{result.baseUrl}</div>
21
+ <div className="mt-1 text-[11px] uppercase tracking-[0.24em] text-slate-500">
22
+ {result.ok ? "Available" : "Failed"}
23
+ </div>
24
+ </div>
25
+ <div className="text-sm font-medium text-cyan-200">{result.latency} ms</div>
26
+ </div>
27
+ ))}
28
+
29
+ {!props.probeResults?.length ? (
30
+ <div className="rounded-2xl border border-white/6 bg-white/[0.03] px-3 py-4 text-sm text-slate-500">
31
+ Click Probe in the toolbar to load latency data.
32
+ </div>
33
+ ) : null}
34
+ </div>
35
+ </div>
36
+
37
+ <div className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
38
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Targets</div>
39
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
40
+ Live config files
41
+ </h2>
42
+
43
+ <div className="mt-4 space-y-2">
44
+ {props.platform.targetFiles.map((file) => (
45
+ <div key={file} className="rounded-2xl border border-white/6 bg-white/[0.03] px-3 py-3 text-sm text-slate-300">
46
+ {file}
47
+ </div>
48
+ ))}
49
+ </div>
50
+ </div>
51
+
52
+ <div className="rounded-[24px] border border-cyan-300/12 bg-gradient-to-br from-cyan-400/[0.08] to-teal-400/[0.05] p-5">
53
+ <div className="flex items-center gap-2 text-sm font-medium text-cyan-100">
54
+ <Rocket className="h-4 w-4" />
55
+ Release Panel
56
+ </div>
57
+ <div className="mt-3 text-sm leading-6 text-slate-300/80">
58
+ Architecture now mirrors the same component-heavy desktop shell direction instead of a single-page mock.
59
+ </div>
60
+ </div>
61
+
62
+ <div className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
63
+ <div className="flex items-center gap-2 text-sm font-medium text-slate-200">
64
+ <Activity className="h-4 w-4 text-emerald-300" />
65
+ Workspace status
66
+ </div>
67
+ <div className="mt-3 space-y-2 text-sm text-slate-400">
68
+ <div className="flex items-center justify-between">
69
+ <span>Provider Sync</span>
70
+ <span className="text-emerald-300">Healthy</span>
71
+ </div>
72
+ <div className="flex items-center justify-between">
73
+ <span>Route Cache</span>
74
+ <span className="text-cyan-200">Ready</span>
75
+ </div>
76
+ <div className="flex items-center justify-between">
77
+ <span>Config Targets</span>
78
+ <span className="text-slate-200">{props.platform.targetFiles.length}</span>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </article>
83
+ );
84
+ }
@@ -0,0 +1,26 @@
1
+ import { CloudLightning, FolderOpen, KeyRound, LayoutGrid } from "lucide-react";
2
+
3
+ const icons = [LayoutGrid, KeyRound, CloudLightning, FolderOpen];
4
+
5
+ export function MetricStrip(props: { items: Array<{ label: string; value: string; hint: string }> }) {
6
+ return (
7
+ <section className="mt-4 grid grid-cols-4 gap-3">
8
+ {props.items.map((item, index) => {
9
+ const Icon = icons[index];
10
+
11
+ return (
12
+ <article key={item.label} className="rounded-[20px] border border-white/6 bg-white/[0.03] px-4 py-4">
13
+ <div className="flex items-center justify-between">
14
+ <div className="text-[11px] uppercase tracking-[0.28em] text-slate-500">{item.label}</div>
15
+ <Icon className="h-4 w-4 text-cyan-300/80" />
16
+ </div>
17
+ <div className="mt-4 font-['Space_Grotesk'] text-2xl font-bold tracking-[-0.04em] text-slate-50">
18
+ {item.value}
19
+ </div>
20
+ <div className="mt-1 text-xs text-slate-500">{item.hint}</div>
21
+ </article>
22
+ );
23
+ })}
24
+ </section>
25
+ );
26
+ }
@@ -0,0 +1,87 @@
1
+ type EditorForm = {
2
+ name: string;
3
+ baseUrl: string;
4
+ apiKey: string;
5
+ model: string;
6
+ };
7
+
8
+ const modelOptions = ["gpt-5.4", "gpt-5.3-codex", "gpt-5.2", "gpt-5.1-codex-max"];
9
+
10
+ export function ProviderEditor(props: {
11
+ form: EditorForm;
12
+ onChange: (next: EditorForm) => void;
13
+ onSubmit: () => void;
14
+ isSaving: boolean;
15
+ }) {
16
+ return (
17
+ <article className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
18
+ <div>
19
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Quick Editor</div>
20
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
21
+ Provider form
22
+ </h2>
23
+ <p className="mt-2 text-sm leading-6 text-slate-400">
24
+ Compact editor panel, closer to the right-side utility workflow used by CC Switch.
25
+ </p>
26
+ </div>
27
+
28
+ <form
29
+ className="mt-5 space-y-4"
30
+ onSubmit={(event) => {
31
+ event.preventDefault();
32
+ props.onSubmit();
33
+ }}
34
+ >
35
+ <label className="block">
36
+ <div className="mb-2 text-[11px] uppercase tracking-[0.26em] text-slate-500">Provider Name</div>
37
+ <input
38
+ value={props.form.name}
39
+ onChange={(event) => props.onChange({ ...props.form, name: event.target.value })}
40
+ className="w-full rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-100 outline-none transition focus:border-cyan-300/24"
41
+ />
42
+ </label>
43
+
44
+ <label className="block">
45
+ <div className="mb-2 text-[11px] uppercase tracking-[0.26em] text-slate-500">Base URL</div>
46
+ <input
47
+ value={props.form.baseUrl}
48
+ onChange={(event) => props.onChange({ ...props.form, baseUrl: event.target.value })}
49
+ className="w-full rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-100 outline-none transition focus:border-cyan-300/24"
50
+ />
51
+ </label>
52
+
53
+ <label className="block">
54
+ <div className="mb-2 text-[11px] uppercase tracking-[0.26em] text-slate-500">API Key</div>
55
+ <input
56
+ type="password"
57
+ value={props.form.apiKey}
58
+ onChange={(event) => props.onChange({ ...props.form, apiKey: event.target.value })}
59
+ className="w-full rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-100 outline-none transition focus:border-cyan-300/24"
60
+ />
61
+ </label>
62
+
63
+ <label className="block">
64
+ <div className="mb-2 text-[11px] uppercase tracking-[0.26em] text-slate-500">Model</div>
65
+ <select
66
+ value={props.form.model}
67
+ onChange={(event) => props.onChange({ ...props.form, model: event.target.value })}
68
+ className="w-full rounded-2xl border border-white/8 bg-white/[0.03] px-4 py-3 text-sm text-slate-100 outline-none transition focus:border-cyan-300/24"
69
+ >
70
+ {modelOptions.map((item) => (
71
+ <option key={item} value={item} className="bg-slate-950">
72
+ {item}
73
+ </option>
74
+ ))}
75
+ </select>
76
+ </label>
77
+
78
+ <button
79
+ type="submit"
80
+ className="w-full rounded-2xl border border-cyan-300/18 bg-cyan-400/[0.12] px-4 py-3 text-sm font-medium text-cyan-100 shadow-[0_12px_28px_rgba(6,182,212,0.12)]"
81
+ >
82
+ {props.isSaving ? "Saving..." : "Save & Apply"}
83
+ </button>
84
+ </form>
85
+ </article>
86
+ );
87
+ }
@@ -0,0 +1,85 @@
1
+ import { Plus } from "lucide-react";
2
+ import clsx from "clsx";
3
+ import type { PlatformSnapshot, Provider } from "../../types";
4
+
5
+ export function ProviderTable(props: {
6
+ platform: PlatformSnapshot;
7
+ onEdit: (provider: Provider) => void;
8
+ onActivate: (providerId: string) => void;
9
+ }) {
10
+ return (
11
+ <article className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
12
+ <div className="flex items-center justify-between">
13
+ <div>
14
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Provider Matrix</div>
15
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
16
+ Saved providers
17
+ </h2>
18
+ <p className="mt-2 text-sm leading-6 text-slate-400">
19
+ The primary working area follows the desktop-table layout instead of stacked marketing cards.
20
+ </p>
21
+ </div>
22
+
23
+ <button className="inline-flex items-center gap-2 rounded-xl border border-white/8 bg-white/[0.03] px-3 py-2 text-xs text-slate-300">
24
+ <Plus className="h-4 w-4" />
25
+ Add
26
+ </button>
27
+ </div>
28
+
29
+ <div className="mt-5 overflow-hidden rounded-[20px] border border-white/6 bg-[#0b121a]">
30
+ <div className="grid grid-cols-[1.2fr_0.8fr_1.2fr_0.72fr] gap-3 border-b border-white/6 px-4 py-3 text-[11px] uppercase tracking-[0.24em] text-slate-500">
31
+ <div>Name</div>
32
+ <div>Model</div>
33
+ <div>Base URL</div>
34
+ <div>Status</div>
35
+ </div>
36
+
37
+ <div className="divide-y divide-white/6">
38
+ {props.platform.providers.map((provider) => (
39
+ <div
40
+ key={provider.id}
41
+ className={clsx(
42
+ "grid grid-cols-[1.2fr_0.8fr_1.2fr_0.72fr] gap-3 px-4 py-4 transition",
43
+ provider.isActive ? "bg-cyan-400/[0.06]" : "hover:bg-white/[0.03]"
44
+ )}
45
+ >
46
+ <div className="min-w-0">
47
+ <div className="truncate text-sm font-medium text-slate-100">{provider.name}</div>
48
+ <div className="mt-1 text-xs text-slate-500">{provider.updatedAt.slice(0, 10)}</div>
49
+ </div>
50
+ <div className="min-w-0">
51
+ <div className="truncate text-sm text-slate-300">{provider.model}</div>
52
+ <div className="mt-1 text-xs text-slate-500">{provider.maskedApiKey}</div>
53
+ </div>
54
+ <div className="min-w-0">
55
+ <div className="truncate text-sm text-slate-300">{provider.baseUrl}</div>
56
+ <button
57
+ type="button"
58
+ onClick={() => props.onEdit(provider)}
59
+ className="mt-2 rounded-lg border border-white/8 bg-white/[0.03] px-2.5 py-1 text-[11px] text-slate-300"
60
+ >
61
+ Edit
62
+ </button>
63
+ </div>
64
+ <div className="flex flex-col items-start gap-2">
65
+ {provider.isActive ? (
66
+ <span className="rounded-full bg-cyan-400/12 px-2.5 py-1 text-[11px] font-medium text-cyan-200">
67
+ Active
68
+ </span>
69
+ ) : (
70
+ <button
71
+ type="button"
72
+ onClick={() => props.onActivate(provider.id)}
73
+ className="rounded-lg border border-cyan-300/14 bg-cyan-400/[0.08] px-3 py-1.5 text-[11px] text-cyan-100"
74
+ >
75
+ Enable
76
+ </button>
77
+ )}
78
+ </div>
79
+ </div>
80
+ ))}
81
+ </div>
82
+ </div>
83
+ </article>
84
+ );
85
+ }
@@ -0,0 +1,16 @@
1
+ export function LogoMark() {
2
+ return (
3
+ <div className="grid h-11 w-11 place-items-center rounded-2xl border border-cyan-300/20 bg-gradient-to-br from-cyan-400 to-teal-400 shadow-[0_12px_28px_rgba(34,211,238,0.18)]">
4
+ <svg viewBox="0 0 64 64" className="h-7 w-7 text-slate-950" fill="none">
5
+ <path
6
+ d="M14 46V21.5c0-1.93 1.57-3.5 3.5-3.5h1.3c1.08 0 2.1.5 2.76 1.35L32 32.8l10.44-13.45A3.49 3.49 0 0 1 45.2 18h1.3c1.93 0 3.5 1.57 3.5 3.5V46"
7
+ stroke="currentColor"
8
+ strokeWidth="5"
9
+ strokeLinecap="round"
10
+ strokeLinejoin="round"
11
+ />
12
+ <path d="M43 48h8" stroke="#f8fafc" strokeWidth="5" strokeLinecap="round" />
13
+ </svg>
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,45 @@
1
+ const connectors = [
2
+ { name: "Filesystem", status: "Connected", detail: "Workspace root mounted with read/write scope" },
3
+ { name: "Browser", status: "Ready", detail: "Web browsing enabled for docs, releases and references" },
4
+ { name: "Git", status: "Healthy", detail: "Repository metadata and branch graph available" },
5
+ { name: "Shell", status: "Allowed", detail: "Local command execution within workspace boundaries" }
6
+ ];
7
+
8
+ export function McpView() {
9
+ return (
10
+ <section className="mt-4 grid min-h-0 flex-1 grid-cols-[1.1fr_0.9fr] gap-4">
11
+ <article className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
12
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Connector Registry</div>
13
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
14
+ MCP endpoints
15
+ </h2>
16
+ <div className="mt-5 space-y-3">
17
+ {connectors.map((item) => (
18
+ <div key={item.name} className="rounded-[20px] border border-white/6 bg-white/[0.03] p-4">
19
+ <div className="flex items-center justify-between">
20
+ <div className="text-sm font-medium text-slate-100">{item.name}</div>
21
+ <div className="rounded-full bg-cyan-400/12 px-2.5 py-1 text-[11px] text-cyan-200">{item.status}</div>
22
+ </div>
23
+ <div className="mt-2 text-sm leading-6 text-slate-400">{item.detail}</div>
24
+ </div>
25
+ ))}
26
+ </div>
27
+ </article>
28
+
29
+ <article className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
30
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Policy Surface</div>
31
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
32
+ Access profile
33
+ </h2>
34
+ <div className="mt-5 space-y-3">
35
+ {["Workspace Write", "Network Access", "Prompt Upgrade", "Parallel Agents"].map((item) => (
36
+ <div key={item} className="flex items-center justify-between rounded-[18px] border border-white/6 bg-white/[0.03] px-4 py-3">
37
+ <span className="text-sm text-slate-200">{item}</span>
38
+ <span className="text-xs uppercase tracking-[0.24em] text-emerald-300">Enabled</span>
39
+ </div>
40
+ ))}
41
+ </div>
42
+ </article>
43
+ </section>
44
+ );
45
+ }
@@ -0,0 +1,40 @@
1
+ const promptCards = [
2
+ { name: "Default Coding", tone: "Direct", notes: "For implementation, debugging and repo-level changes." },
3
+ { name: "UI Review", tone: "Critical", notes: "For visual comparison, layout review and polish passes." },
4
+ { name: "Provider Setup", tone: "Operational", notes: "For relay config, route switching and install flows." }
5
+ ];
6
+
7
+ export function PromptsView() {
8
+ return (
9
+ <section className="mt-4 grid min-h-0 flex-1 grid-cols-[1.18fr_0.82fr] gap-4">
10
+ <article className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
11
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Prompt Library</div>
12
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
13
+ Saved prompt profiles
14
+ </h2>
15
+ <div className="mt-5 space-y-3">
16
+ {promptCards.map((item) => (
17
+ <div key={item.name} className="rounded-[20px] border border-white/6 bg-white/[0.03] p-4">
18
+ <div className="flex items-center justify-between">
19
+ <div className="text-sm font-medium text-slate-100">{item.name}</div>
20
+ <div className="rounded-full bg-white/[0.05] px-2.5 py-1 text-[11px] text-slate-300">{item.tone}</div>
21
+ </div>
22
+ <div className="mt-2 text-sm leading-6 text-slate-400">{item.notes}</div>
23
+ </div>
24
+ ))}
25
+ </div>
26
+ </article>
27
+
28
+ <article className="rounded-[24px] border border-white/6 bg-white/[0.025] p-5">
29
+ <div className="text-[11px] uppercase tracking-[0.32em] text-slate-500">Active Prompt</div>
30
+ <h2 className="mt-2 font-['Space_Grotesk'] text-[22px] font-bold tracking-[-0.04em] text-slate-50">
31
+ Prompt editor
32
+ </h2>
33
+ <div className="mt-5 rounded-[20px] border border-white/6 bg-[#0b121a] p-4 text-sm leading-7 text-slate-300">
34
+ You are a coding assistant for March Control. Prioritize provider workflows, desktop release preparation,
35
+ route health, and concrete implementation over abstract planning.
36
+ </div>
37
+ </article>
38
+ </section>
39
+ );
40
+ }
@@ -0,0 +1,40 @@
1
+ import { InspectorPanel } from "../../components/providers/inspector-panel";
2
+ import { MetricStrip } from "../../components/providers/metric-strip";
3
+ import { ProviderEditor } from "../../components/providers/provider-editor";
4
+ import { ProviderTable } from "../../components/providers/provider-table";
5
+ import type { ReturnTypeUseControlWorkspace } from "./types";
6
+
7
+ export function ProvidersView(props: { workspace: ReturnTypeUseControlWorkspace }) {
8
+ return (
9
+ <>
10
+ <MetricStrip items={props.workspace.metrics} />
11
+
12
+ <section className="mt-4 grid min-h-0 flex-1 grid-cols-[1.2fr_0.94fr_0.72fr] gap-4">
13
+ <ProviderTable
14
+ platform={props.workspace.platform}
15
+ onEdit={props.workspace.openProvider}
16
+ onActivate={(providerId) =>
17
+ props.workspace.activateMutation.mutate({
18
+ platform: props.workspace.platform.id,
19
+ providerId
20
+ })
21
+ }
22
+ />
23
+
24
+ <ProviderEditor
25
+ form={props.workspace.editorForm}
26
+ onChange={props.workspace.setEditorForm}
27
+ onSubmit={() =>
28
+ props.workspace.saveMutation.mutate({
29
+ platform: props.workspace.platform.id,
30
+ ...props.workspace.editorForm
31
+ })
32
+ }
33
+ isSaving={props.workspace.saveMutation.isPending}
34
+ />
35
+
36
+ <InspectorPanel platform={props.workspace.platform} probeResults={props.workspace.probeQuery.data} />
37
+ </section>
38
+ </>
39
+ );
40
+ }
@@ -0,0 +1,8 @@
1
+ import type { useControlWorkspace } from "../../hooks/use-control-workspace";
2
+
3
+ type Workspace = ReturnType<typeof useControlWorkspace>;
4
+
5
+ export type ReturnTypeUseControlWorkspace = Workspace & {
6
+ platform: NonNullable<Workspace["platform"]>;
7
+ snapshot: NonNullable<Workspace["snapshot"]>;
8
+ };