@xpufx/paseo-helper-demo 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/client/pill.tsx +1249 -0
  4. package/client/suite-settings.tsx +77 -0
  5. package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
  6. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
  7. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
  8. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
  9. package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
  10. package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
  11. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
  12. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
  13. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
  14. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
  15. package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
  16. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
  17. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
  18. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
  19. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
  20. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
  21. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
  22. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
  23. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
  24. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
  25. package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
  26. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
  27. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
  28. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
  29. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
  30. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
  31. package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
  32. package/client/vendor/paseo-plugin-helper/core/index.ts +64 -0
  33. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
  34. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
  35. package/client/vendor/paseo-plugin-helper/host.ts +277 -0
  36. package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
  37. package/client/vendor/paseo-plugin-helper/index.ts +28 -0
  38. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
  39. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
  40. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
  41. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
  42. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
  43. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
  44. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
  45. package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
  46. package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
  47. package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
  48. package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
  49. package/client/vendor/paseo-plugin-helper/query.ts +66 -0
  50. package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
  51. package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
  52. package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
  53. package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
  54. package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
  55. package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
  56. package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
  57. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
  58. package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
  59. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
  60. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
  61. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
  62. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
  63. package/client/vendor/paseo-plugin-helper/ui/index.ts +25 -0
  64. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +105 -0
  65. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  66. package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
  67. package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
  68. package/package.json +35 -0
  69. package/paseo-plugin.json +6 -0
  70. package/server/demo.ts +195 -0
  71. package/server/suite-settings.ts +23 -0
  72. package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
  73. package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
  74. package/server/vendor/paseo-plugin-helper/index.ts +18 -0
  75. package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
  76. package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
  77. package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
  78. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
  79. package/server/vendor/paseo-plugin-helper/network.ts +91 -0
  80. package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
  81. package/server/vendor/paseo-plugin-helper/process.ts +186 -0
  82. package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
  83. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
  84. package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
  85. package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
  86. package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
  87. package/server/vendor/paseo-plugin-helper/system.ts +128 -0
  88. package/server/vendor/paseo-plugin-helper/task.ts +116 -0
  89. package/server/vendor/paseo-plugin-helper/version.ts +153 -0
  90. package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
  91. package/shared/demo.ts +155 -0
  92. package/shared/vendor/paseo-plugin-helper/README.md +11 -0
  93. package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
  94. package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
  95. package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
  96. package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
  97. package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
  98. package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
  99. package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
  100. package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
  101. package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
  102. package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
  103. package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
  104. package/shared/version.ts +2 -0
@@ -0,0 +1,418 @@
1
+ import type { PluginLogger } from "./logger";
2
+
3
+ export const DEFAULT_BEACON_LABEL_PREFIX = "beacon:";
4
+
5
+ export const BEACON_COLORS = [
6
+ "violet",
7
+ "sky",
8
+ "emerald",
9
+ "orange",
10
+ "pink",
11
+ "indigo",
12
+ "teal",
13
+ "red",
14
+ "amber",
15
+ "blue",
16
+ ] as const;
17
+
18
+ export type BeaconColor = (typeof BEACON_COLORS)[number];
19
+
20
+ export interface BeaconLabelState {
21
+ name: string;
22
+ color?: string;
23
+ titleSuffix?: string;
24
+ title?: string;
25
+ workspaceId?: string;
26
+ }
27
+
28
+ export interface BeaconSetOptions extends BeaconLabelState {
29
+ workspaceId: string;
30
+ }
31
+
32
+ export interface BeaconBlinkOptions {
33
+ workspaceId?: string;
34
+ a: BeaconLabelState;
35
+ b: BeaconLabelState;
36
+ intervalMs?: number;
37
+ rounds?: number;
38
+ restoreOnDone?: boolean;
39
+ }
40
+
41
+ export interface BeaconClearOptions {
42
+ workspaceId?: string;
43
+ name?: string;
44
+ restoreTitle?: boolean;
45
+ }
46
+
47
+ export interface WorkspaceTitleHandle {
48
+ setTitle?: (title: string) => unknown;
49
+ }
50
+
51
+ export interface BeaconDaemonClient {
52
+ setWorkspaceLabel?: (args: unknown) => unknown;
53
+ updateWorkspaceLabel?: (args: unknown) => unknown;
54
+ removeWorkspaceLabel?: (args: unknown) => unknown;
55
+ [key: string]: unknown;
56
+ }
57
+
58
+ export interface WorkspaceBeaconOptions {
59
+ workspaceHandle?: WorkspaceTitleHandle | null;
60
+ daemonClient?: BeaconDaemonClient | null;
61
+ labelPrefix?: string;
62
+ baseTitle?: string;
63
+ logger?: PluginLogger;
64
+ }
65
+
66
+ export interface BeaconSetResult {
67
+ labelApplied: boolean;
68
+ titleApplied: boolean;
69
+ labelName?: string;
70
+ title?: string;
71
+ }
72
+
73
+ export interface BeaconClearResult {
74
+ labelCleared: boolean;
75
+ titleRestored: boolean;
76
+ }
77
+
78
+ export interface BeaconBlinkHandle {
79
+ stop: () => void;
80
+ done: Promise<void>;
81
+ }
82
+
83
+ export function resolveBeaconLabelName(
84
+ name: string,
85
+ prefix: string = DEFAULT_BEACON_LABEL_PREFIX,
86
+ ): string {
87
+ const trimmed = (name ?? "").trim();
88
+ if (!prefix) return trimmed;
89
+ if (trimmed.toLowerCase().startsWith(prefix.toLowerCase())) return trimmed;
90
+ return `${prefix}${trimmed}`;
91
+ }
92
+
93
+ export function normalizeBeaconColor(color: string | undefined): string | undefined {
94
+ if (!color) return undefined;
95
+ const normalized = color.trim().toLowerCase();
96
+ if ((BEACON_COLORS as readonly string[]).includes(normalized)) return normalized;
97
+ return undefined;
98
+ }
99
+
100
+ function isFunction(value: unknown): value is (...args: never[]) => unknown {
101
+ return typeof value === "function";
102
+ }
103
+
104
+ async function safeInvoke(
105
+ fn: () => unknown,
106
+ logger?: PluginLogger,
107
+ ): Promise<boolean> {
108
+ try {
109
+ await fn();
110
+ return true;
111
+ } catch (err) {
112
+ logger?.debug?.(
113
+ `WorkspaceBeacon host call failed: ${err instanceof Error ? err.message : String(err)}`,
114
+ );
115
+ return false;
116
+ }
117
+ }
118
+
119
+ function toTimerKey(workspaceId: string | undefined): string {
120
+ return workspaceId ?? "__default__";
121
+ }
122
+
123
+ export class WorkspaceBeacon {
124
+ private workspaceHandle?: WorkspaceTitleHandle | null;
125
+ private daemonClient?: BeaconDaemonClient | null;
126
+ private labelPrefix: string;
127
+ private baseTitle?: string;
128
+ private logger?: PluginLogger;
129
+ private originalTitles = new Map<string, string>();
130
+ private blinkTimers = new Map<string, { timer: ReturnType<typeof setInterval>; finish: () => void }>();
131
+
132
+ constructor(options: WorkspaceBeaconOptions = {}) {
133
+ this.workspaceHandle = options.workspaceHandle ?? null;
134
+ this.daemonClient = options.daemonClient ?? null;
135
+ this.labelPrefix = options.labelPrefix ?? DEFAULT_BEACON_LABEL_PREFIX;
136
+ this.baseTitle = options.baseTitle;
137
+ this.logger = options.logger;
138
+ }
139
+
140
+ setOptions(options: Partial<WorkspaceBeaconOptions>): void {
141
+ if ("workspaceHandle" in options) this.workspaceHandle = options.workspaceHandle ?? null;
142
+ if ("daemonClient" in options) this.daemonClient = options.daemonClient ?? null;
143
+ if (options.labelPrefix !== undefined) this.labelPrefix = options.labelPrefix;
144
+ if (options.baseTitle !== undefined) this.baseTitle = options.baseTitle;
145
+ if (options.logger !== undefined) this.logger = options.logger;
146
+ }
147
+
148
+ get activeBlinks(): number {
149
+ return this.blinkTimers.size;
150
+ }
151
+
152
+ async set(options: BeaconSetOptions): Promise<BeaconSetResult> {
153
+ const result: BeaconSetResult = { labelApplied: false, titleApplied: false };
154
+ try {
155
+ const workspaceId = options.workspaceId;
156
+ const labelName = options.name ? resolveBeaconLabelName(options.name, this.labelPrefix) : undefined;
157
+ result.labelName = labelName;
158
+
159
+ if (workspaceId && labelName) {
160
+ result.labelApplied = await this.applyLabel(workspaceId, labelName, options.color);
161
+ }
162
+
163
+ const title = this.resolveTitle(options);
164
+ if (title !== undefined) {
165
+ result.titleApplied = await this.applyTitle(workspaceId, title);
166
+ if (result.titleApplied) result.title = title;
167
+ }
168
+ } catch (err) {
169
+ this.logger?.debug?.(
170
+ `WorkspaceBeacon.set failed: ${err instanceof Error ? err.message : String(err)}`,
171
+ );
172
+ }
173
+ return result;
174
+ }
175
+
176
+ blink(options: BeaconBlinkOptions): BeaconBlinkHandle {
177
+ const intervalMs = Math.max(50, options.intervalMs ?? 3000);
178
+ const totalRounds = options.rounds ?? Number.POSITIVE_INFINITY;
179
+ const workspaceId = options.workspaceId ?? options.a.workspaceId ?? options.b.workspaceId;
180
+ const key = toTimerKey(workspaceId);
181
+ this.stopBlink(key);
182
+ let stopped = false;
183
+ let settled = false;
184
+ let timer: ReturnType<typeof setInterval> | undefined;
185
+ let resolveDone!: () => void;
186
+ const done = new Promise<void>((resolve) => {
187
+ resolveDone = resolve;
188
+ });
189
+ const finish = () => {
190
+ if (settled) return;
191
+ settled = true;
192
+ if (timer) clearInterval(timer);
193
+ const current = this.blinkTimers.get(key);
194
+ if (current?.finish === finish) this.blinkTimers.delete(key);
195
+ if (options.restoreOnDone) {
196
+ void this.clear({
197
+ workspaceId,
198
+ name: options.a.name ?? options.b.name,
199
+ restoreTitle: true,
200
+ }).catch((err) =>
201
+ this.logger?.debug?.(
202
+ `WorkspaceBeacon blink restore failed: ${err instanceof Error ? err.message : String(err)}`,
203
+ ),
204
+ );
205
+ }
206
+ resolveDone();
207
+ };
208
+
209
+ try {
210
+ const states: BeaconLabelState[] = [options.a, options.b];
211
+ const tick = () => {
212
+ if (stopped) {
213
+ finish();
214
+ return;
215
+ }
216
+ if (count >= totalRounds) {
217
+ finish();
218
+ return;
219
+ }
220
+ const state = states[count % 2];
221
+ count += 1;
222
+ void this.set({ ...state, workspaceId: state.workspaceId ?? workspaceId ?? "" }).catch((err) =>
223
+ this.logger?.debug?.(
224
+ `WorkspaceBeacon blink tick failed: ${err instanceof Error ? err.message : String(err)}`,
225
+ ),
226
+ );
227
+ if (count >= totalRounds) {
228
+ finish();
229
+ }
230
+ };
231
+ let count = 0;
232
+ void this.set({ ...states[0], workspaceId: states[0].workspaceId ?? workspaceId ?? "" }).catch(
233
+ (err) =>
234
+ this.logger?.debug?.(
235
+ `WorkspaceBeacon blink tick failed: ${err instanceof Error ? err.message : String(err)}`,
236
+ ),
237
+ );
238
+ count = 1;
239
+ if (count >= totalRounds) {
240
+ finish();
241
+ } else {
242
+ timer = setInterval(tick, intervalMs);
243
+ this.blinkTimers.set(key, { timer, finish });
244
+ }
245
+ } catch (err) {
246
+ this.logger?.debug?.(
247
+ `WorkspaceBeacon blink setup failed: ${err instanceof Error ? err.message : String(err)}`,
248
+ );
249
+ finish();
250
+ }
251
+
252
+ return {
253
+ stop: () => {
254
+ stopped = true;
255
+ finish();
256
+ },
257
+ done,
258
+ };
259
+ }
260
+
261
+ async clear(options: BeaconClearOptions = {}): Promise<BeaconClearResult> {
262
+ const result: BeaconClearResult = { labelCleared: false, titleRestored: false };
263
+ try {
264
+ this.stopBlink(toTimerKey(options.workspaceId));
265
+ if (options.workspaceId && options.name) {
266
+ result.labelCleared = await this.detachLabel(
267
+ options.workspaceId,
268
+ resolveBeaconLabelName(options.name, this.labelPrefix),
269
+ );
270
+ } else if (options.workspaceId && !options.name) {
271
+ result.labelCleared = false;
272
+ }
273
+ if (options.restoreTitle !== false) {
274
+ result.titleRestored = await this.restoreTitle(options.workspaceId);
275
+ }
276
+ } catch (err) {
277
+ this.logger?.debug?.(
278
+ `WorkspaceBeacon.clear failed: ${err instanceof Error ? err.message : String(err)}`,
279
+ );
280
+ }
281
+ return result;
282
+ }
283
+
284
+ stopAll(): void {
285
+ for (const entry of this.blinkTimers.values()) {
286
+ clearInterval(entry.timer);
287
+ try {
288
+ entry.finish();
289
+ } catch (err) {
290
+ this.logger?.debug?.(
291
+ `WorkspaceBeacon stopAll finish failed: ${err instanceof Error ? err.message : String(err)}`,
292
+ );
293
+ }
294
+ }
295
+ this.blinkTimers.clear();
296
+ }
297
+
298
+ private resolveTitle(options: BeaconLabelState): string | undefined {
299
+ if (options.title !== undefined) return options.title;
300
+ if (options.titleSuffix === undefined) return undefined;
301
+ const base = this.originalTitles.get(options.workspaceId ?? "") ?? this.baseTitle ?? "";
302
+ return `${base}${options.titleSuffix}`;
303
+ }
304
+
305
+ private async applyLabel(workspaceId: string, labelName: string, color?: string): Promise<boolean> {
306
+ const client = this.daemonClient;
307
+ if (!client || !isFunction(client.setWorkspaceLabel)) return false;
308
+ const label: Record<string, string> = { name: labelName };
309
+ const normalized = normalizeBeaconColor(color);
310
+ if (normalized) label.color = normalized;
311
+ else if (color) label.color = color;
312
+ return safeInvoke(
313
+ () =>
314
+ (client.setWorkspaceLabel as (args: unknown) => unknown)({
315
+ workspaceId,
316
+ label,
317
+ assigned: true,
318
+ }),
319
+ this.logger,
320
+ );
321
+ }
322
+
323
+ private async detachLabel(workspaceId: string, labelName: string): Promise<boolean> {
324
+ const client = this.daemonClient as BeaconDaemonClient | null | undefined;
325
+ if (!client) return false;
326
+ try {
327
+ if (isFunction(client.setWorkspaceLabel)) {
328
+ return await safeInvoke(
329
+ () =>
330
+ (client.setWorkspaceLabel as (args: unknown) => unknown)({
331
+ workspaceId,
332
+ label: { name: labelName },
333
+ assigned: false,
334
+ }),
335
+ this.logger,
336
+ );
337
+ }
338
+ if (isFunction(client.removeWorkspaceLabel)) {
339
+ return await safeInvoke(
340
+ () =>
341
+ (client.removeWorkspaceLabel as (args: unknown) => unknown)({
342
+ workspaceId,
343
+ name: labelName,
344
+ }),
345
+ this.logger,
346
+ );
347
+ }
348
+ return false;
349
+ } catch (err) {
350
+ this.logger?.debug?.(
351
+ `WorkspaceBeacon detachLabel failed: ${err instanceof Error ? err.message : String(err)}`,
352
+ );
353
+ return false;
354
+ }
355
+ }
356
+
357
+ setOriginalTitle(workspaceId: string, title: string): void {
358
+ this.originalTitles.set(workspaceId, title);
359
+ }
360
+
361
+ hasOriginalTitle(workspaceId: string): boolean {
362
+ return this.originalTitles.has(workspaceId);
363
+ }
364
+
365
+ getOriginalTitle(workspaceId: string): string | undefined {
366
+ return this.originalTitles.get(workspaceId);
367
+ }
368
+
369
+ private async applyTitle(workspaceId: string | undefined, title: string): Promise<boolean> {
370
+ const handle = this.workspaceHandle;
371
+ if (!handle || !isFunction(handle.setTitle)) return false;
372
+ const key = workspaceId ?? "";
373
+ if (!this.originalTitles.has(key)) {
374
+ if (this.baseTitle !== undefined) {
375
+ this.originalTitles.set(key, this.baseTitle);
376
+ }
377
+ }
378
+ return safeInvoke(() => (handle.setTitle as (t: string) => unknown)(title), this.logger);
379
+ }
380
+
381
+ private async restoreTitle(workspaceId: string | undefined): Promise<boolean> {
382
+ const handle = this.workspaceHandle;
383
+ if (!handle || !isFunction(handle.setTitle)) return false;
384
+ const key = workspaceId ?? "";
385
+ const original = this.originalTitles.get(key) ?? this.baseTitle;
386
+ if (original === undefined) return false;
387
+ const ok = await safeInvoke(
388
+ () => (handle.setTitle as (t: string) => unknown)(original),
389
+ this.logger,
390
+ );
391
+ if (ok) {
392
+ this.originalTitles.delete(key);
393
+ if (this.baseTitle === original) {
394
+ this.baseTitle = undefined;
395
+ }
396
+ }
397
+ return ok;
398
+ }
399
+
400
+ private stopBlink(key: string): void {
401
+ const entry = this.blinkTimers.get(key);
402
+ if (entry) {
403
+ clearInterval(entry.timer);
404
+ this.blinkTimers.delete(key);
405
+ try {
406
+ entry.finish();
407
+ } catch (err) {
408
+ this.logger?.debug?.(
409
+ `WorkspaceBeacon stopBlink finish failed: ${err instanceof Error ? err.message : String(err)}`,
410
+ );
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ export function createWorkspaceBeacon(options: WorkspaceBeaconOptions = {}): WorkspaceBeacon {
417
+ return new WorkspaceBeacon(options);
418
+ }
package/shared/demo.ts ADDED
@@ -0,0 +1,155 @@
1
+ import {
2
+ defineContract,
3
+ defineSettingsContract,
4
+ type RpcOutput,
5
+ } from "paseo-plugin-helper/shared";
6
+ import { z } from "zod";
7
+
8
+ export const DemoSettingsSchema = z.object({
9
+ showCpuUsage: z.boolean().default(true),
10
+ accentPillLabel: z.string().default("demo"),
11
+ pollingRate: z.enum(["1s", "2s", "5s", "paused"]).default("2s"),
12
+ navigationStyle: z.enum(["tabs", "dropdown"]).default("dropdown"),
13
+ highCpuThreshold: z.number().default(80),
14
+ // Visual Flair Customization Studio (Persisted)
15
+ flairRadius: z.enum(["sharp", "rounded", "pill"]).default("rounded"),
16
+ flairDensity: z.enum(["compact", "comfortable", "spacious"]).default("comfortable"),
17
+ flairSurface: z.enum(["flat", "tinted", "elevated"]).default("flat"),
18
+ flairBorderWidth: z.number().default(1),
19
+ flairUppercase: z.boolean().default(false),
20
+ flairAccentColor: z.string().default("#6366f1"),
21
+ });
22
+
23
+ export type DemoSettings = z.infer<typeof DemoSettingsSchema>;
24
+
25
+ export type DemoNavigationStyle = DemoSettings["navigationStyle"];
26
+
27
+ /**
28
+ * Maps the showcase navigation style to ModalBody scroll ownership.
29
+ * Tabs use a compact pinned navbar (headerMode="pinned": header stays fixed
30
+ * while the body scrolls). The dropdown menu is in-flow inside the header,
31
+ * so it must participate in the scroll flow (headerMode="scroll"): on
32
+ * helper-owned compact surfaces the header renders INSIDE the ScrollView,
33
+ * and on host-owned 0.8 popovers it drops the sticky header, so an open
34
+ * menu scrolls with the content instead of staying visibly pinned while
35
+ * the body scrolls beneath it.
36
+ */
37
+ export function resolveDemoHeaderMode(
38
+ navigationStyle: DemoNavigationStyle,
39
+ ): "pinned" | "scroll" {
40
+ return navigationStyle === "dropdown" ? "scroll" : "pinned";
41
+ }
42
+
43
+ export const demoSettingsContract = defineSettingsContract({
44
+ name: "helper-demo.settings",
45
+ schema: DemoSettingsSchema,
46
+ description: "Showcase demo settings",
47
+ });
48
+
49
+ export const getDemoDataRpc = defineContract({
50
+ name: "helper-demo.get-data",
51
+ description: "Get comprehensive demo metrics, hardware stats, and service items",
52
+ input: z.object({}),
53
+ output: z.object({
54
+ version: z.string(),
55
+ hostname: z.string(),
56
+ platform: z.string(),
57
+ cpuModel: z.string(),
58
+ cpuUsagePercent: z.number(),
59
+ memoryUsedPercent: z.number(),
60
+ memoryUsedBytes: z.number(),
61
+ memoryTotalBytes: z.number(),
62
+ loadAvg: z.array(z.number()),
63
+ uptimeSeconds: z.number(),
64
+ daemonPort: z.number(),
65
+ backgroundTicks: z.number(),
66
+ items: z.array(
67
+ z.object({
68
+ id: z.string(),
69
+ name: z.string(),
70
+ category: z.string(),
71
+ status: z.enum(["running", "idle", "syncing", "warning"]),
72
+ loadPercent: z.number(),
73
+ })
74
+ ),
75
+ }),
76
+ });
77
+
78
+ export const triggerDemoActionRpc = defineContract({
79
+ name: "helper-demo.trigger-action",
80
+ description: "Trigger an RPC action on the daemon",
81
+ input: z.object({
82
+ actionName: z.string(),
83
+ }),
84
+ output: z.object({
85
+ success: z.boolean(),
86
+ message: z.string(),
87
+ }),
88
+ });
89
+
90
+ export type DemoData = RpcOutput<typeof getDemoDataRpc>;
91
+
92
+ export const demoAgentIdentityContract = defineContract({
93
+ name: "helper-demo.agent-identity",
94
+ description: "Get active agent identity and session for self-inspection",
95
+ input: z.object({}),
96
+ output: z.object({
97
+ identity: z
98
+ .object({
99
+ id: z.string().optional(),
100
+ name: z.string().optional(),
101
+ model: z.string().optional(),
102
+ provider: z.string().optional(),
103
+ repo: z.string().optional(),
104
+ branch: z.string().optional(),
105
+ envelopeText: z.string().optional(),
106
+ })
107
+ .nullable(),
108
+ }),
109
+ });
110
+
111
+ export type DemoAgentIdentity = RpcOutput<typeof demoAgentIdentityContract>;
112
+
113
+ export const demoBeaconSetContract = defineContract({
114
+ name: "helper-demo.beacon-set",
115
+ description: "Set workspace status beacon label on the active workspace",
116
+ input: z.object({
117
+ workspaceId: z.string().min(1),
118
+ name: z.string().default("DEMO:ACTIVE"),
119
+ color: z.string().default("sky"),
120
+ }),
121
+ output: z.object({
122
+ success: z.boolean(),
123
+ message: z.string(),
124
+ labelApplied: z.boolean(),
125
+ titleApplied: z.boolean(),
126
+ }),
127
+ });
128
+
129
+ export const demoBeaconBlinkContract = defineContract({
130
+ name: "helper-demo.beacon-blink",
131
+ description: "Blink workspace beacon between emerald and orange states",
132
+ input: z.object({
133
+ workspaceId: z.string().min(1),
134
+ rounds: z.number().int().positive().default(5),
135
+ }),
136
+ output: z.object({
137
+ success: z.boolean(),
138
+ message: z.string(),
139
+ }),
140
+ });
141
+
142
+ export const demoBeaconClearContract = defineContract({
143
+ name: "helper-demo.beacon-clear",
144
+ description: "Clear workspace beacon label and restore original title",
145
+ input: z.object({
146
+ workspaceId: z.string().min(1),
147
+ name: z.string().default("DEMO:ACTIVE"),
148
+ }),
149
+ output: z.object({
150
+ success: z.boolean(),
151
+ message: z.string(),
152
+ labelCleared: z.boolean(),
153
+ titleRestored: z.boolean(),
154
+ }),
155
+ });
@@ -0,0 +1,11 @@
1
+ # Vendored paseo-plugin-helper (Track B of issue #71)
2
+
3
+ Pinned helper version: 0.4.0-beta.12
4
+
5
+ Docs: https://github.com/xpufx/paseo/tree/main/packages/paseo-plugin-helper — npm: https://www.npmjs.com/package/paseo-plugin-helper
6
+
7
+ Zero host requirements (no npm, no registry, no build key): the daemon bundles
8
+ this source directly. Layout: `client/`, `server/`, `shared/` vendor splits
9
+ mapping helper `src/client`, `src/server`, `src/shared` (daemon accepts modules
10
+ only under those dirs). Updating: run `make vendor-sync` and bump the pinned
11
+ version above.
@@ -0,0 +1,35 @@
1
+ export class TimeoutError extends Error {
2
+ readonly timeoutMs: number;
3
+
4
+ constructor(message: string, timeoutMs: number) {
5
+ super(message);
6
+ this.name = "TimeoutError";
7
+ this.timeoutMs = timeoutMs;
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Races a promise against a timeout duration in milliseconds.
13
+ * If the timeout expires before the promise resolves, rejects with a TimeoutError.
14
+ * Automatically cleans up the timer on resolution or rejection.
15
+ */
16
+ export async function withTimeout<T>(
17
+ promise: Promise<T>,
18
+ timeoutMs: number,
19
+ label = "Operation"
20
+ ): Promise<T> {
21
+ let timer: any;
22
+ const timeoutPromise = new Promise<never>((_, reject) => {
23
+ timer = setTimeout(() => {
24
+ reject(new TimeoutError(`${label} timed out after ${timeoutMs}ms`, timeoutMs));
25
+ }, timeoutMs);
26
+ });
27
+
28
+ try {
29
+ return await Promise.race([promise, timeoutPromise]);
30
+ } finally {
31
+ if (timer !== undefined) {
32
+ clearTimeout(timer);
33
+ }
34
+ }
35
+ }