@xpufx/paseo-slash 0.1.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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/client/commands.ts +122 -0
  4. package/client/console.tsx +484 -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/custom-pills.tsx +224 -0
  33. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
  34. package/client/vendor/paseo-plugin-helper/host.ts +277 -0
  35. package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
  36. package/client/vendor/paseo-plugin-helper/index.ts +28 -0
  37. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
  38. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
  39. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
  40. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
  41. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
  42. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
  43. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
  44. package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
  45. package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
  46. package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
  47. package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
  48. package/client/vendor/paseo-plugin-helper/query.ts +66 -0
  49. package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
  50. package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
  51. package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
  52. package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
  53. package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
  54. package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
  55. package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
  56. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
  57. package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
  58. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
  59. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
  60. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
  61. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
  62. package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
  63. package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
  64. package/docs/screenshots/slash-autocomplete.jpg +0 -0
  65. package/docs/screenshots/slash-console.jpg +0 -0
  66. package/package.json +27 -0
  67. package/paseo-plugin.json +4 -0
  68. package/server/orchestrate.ts +81 -0
  69. package/server/resources.ts +132 -0
  70. package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
  71. package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
  72. package/server/vendor/paseo-plugin-helper/index.ts +18 -0
  73. package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
  74. package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
  75. package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
  76. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
  77. package/server/vendor/paseo-plugin-helper/network.ts +91 -0
  78. package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
  79. package/server/vendor/paseo-plugin-helper/process.ts +186 -0
  80. package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
  81. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
  82. package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
  83. package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
  84. package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
  85. package/server/vendor/paseo-plugin-helper/system.ts +128 -0
  86. package/server/vendor/paseo-plugin-helper/task.ts +116 -0
  87. package/server/vendor/paseo-plugin-helper/version.ts +153 -0
  88. package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
  89. package/shared/resources.ts +289 -0
  90. package/shared/vendor/paseo-plugin-helper/README.md +11 -0
  91. package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
  92. package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
  93. package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
  94. package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
  95. package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
  96. package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
  97. package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
  98. package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
  99. package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
  100. package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
  101. package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
  102. package/shared/version.ts +2 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Text-run splitting for search highlighting, shared by every helper surface
3
+ * that paints matched query text and by the forges search surfaces that scroll
4
+ * to a match. A query is matched literally via `indexOf` on lowercased strings —
5
+ * never compiled as a regular expression — so user input cannot inject a
6
+ * pattern.
7
+ *
8
+ * Remote forge search is fuzzy: a returned issue may match a query it does not
9
+ * contain verbatim. When the literal query is absent the splitter highlights
10
+ * the query's multi-character tokens at word starts, so a fuzzy result still
11
+ * reads. Callers on a single primary label may additionally opt into marking
12
+ * the whole field when not even a token hits (`fallbackToWholeField`); callers
13
+ * that paint many small runs leave it off. `hasFuzzyHighlight` is the
14
+ * whole-surface test for go-to-match.
15
+ *
16
+ * `normalizeSearchQuery` strips surrounding quotes and whitespace for the local
17
+ * filter/highlight paths; remote search keeps the raw query.
18
+ */
19
+
20
+ export interface HighlightPart {
21
+ /** The run's source text, in its original casing. */
22
+ text: string;
23
+ /** True when this run is an occurrence of the (normalized) query. */
24
+ matched: boolean;
25
+ }
26
+
27
+ const TOKEN_PATTERN = /[^\p{L}\p{N}]+/u;
28
+
29
+ /**
30
+ * Normalize a search query for local matching and highlighting: trim, then peel
31
+ * any surrounding pair of matching single/double quotes (`"meta"`, `'meta'`,
32
+ * `""meta""`). Remote forge search keeps the raw query — Forgejo treats quotes
33
+ * as a phrase operator — so only the local paths normalize, letting quoted and
34
+ * unquoted input filter and paint identically. A lone quote is left intact.
35
+ */
36
+ export function normalizeSearchQuery(query: string): string {
37
+ let value = query.trim();
38
+ while (value.length >= 2) {
39
+ const first = value[0];
40
+ const last = value[value.length - 1];
41
+ if ((first !== '"' && first !== "'") || first !== last) break;
42
+ value = value.slice(1, -1).trim();
43
+ }
44
+ return value;
45
+ }
46
+
47
+ export interface HighlightOptions {
48
+ /**
49
+ * When true and neither a literal nor a token hit exists, the whole text is
50
+ * returned as a single matched run so a fuzzy search result is still visibly
51
+ * marked. Off by default: callers that paint many small runs (label chips,
52
+ * markdown spans) must not light every one of them up.
53
+ */
54
+ fallbackToWholeField?: boolean;
55
+ }
56
+
57
+ /**
58
+ * Query pieces used for the token fallback. A query with no literal hit is
59
+ * split on non-alphanumeric boundaries; single-character tokens are dropped so
60
+ * a fuzzy query cannot paint a stray letter in every result.
61
+ */
62
+ function queryTokens(needle: string): string[] {
63
+ return needle.split(TOKEN_PATTERN).filter((token) => token.length >= 2);
64
+ }
65
+
66
+ /** True when `index` begins a word (a word-start query token may prefix it). */
67
+ function isWordStartAt(haystack: string, index: number): boolean {
68
+ return index === 0 || TOKEN_PATTERN.test(haystack[index - 1]);
69
+ }
70
+
71
+ /** Earliest word-start occurrence of `token` in `haystack` at or after `from`. */
72
+ function firstTokenStart(haystack: string, token: string, from: number): number {
73
+ let index = haystack.indexOf(token, from);
74
+ while (index !== -1) {
75
+ if (isWordStartAt(haystack, index)) return index;
76
+ index = haystack.indexOf(token, index + 1);
77
+ }
78
+ return -1;
79
+ }
80
+
81
+ /**
82
+ * Split `text` into alternating unmatched/matched runs for every
83
+ * case-insensitive occurrence of `query`. Surrounding whitespace and matching
84
+ * surrounding quotes on the query are ignored; an empty or whitespace-only
85
+ * query (or empty text) yields the whole text as a single unmatched run.
86
+ *
87
+ * When the literal query is absent the splitter highlights the query's
88
+ * multi-character tokens at word starts. If no token hits either, the text is
89
+ * left unmatched unless the caller opted into `fallbackToWholeField`, which
90
+ * marks the whole field so a fuzzy result is never silently unmarked.
91
+ */
92
+ export function splitHighlightParts(
93
+ text: string,
94
+ query: string,
95
+ options: HighlightOptions = {},
96
+ ): HighlightPart[] {
97
+ const needle = normalizeSearchQuery(query).toLowerCase();
98
+ if (!needle || !text) return [{ text, matched: false }];
99
+ const haystack = text.toLowerCase();
100
+ if (haystack.includes(needle)) return splitLiteralParts(text, haystack, needle);
101
+ const tokens = queryTokens(needle);
102
+ const tokenParts = tokens.length ? splitTokenParts(text, haystack, tokens) : null;
103
+ if (tokenParts) return tokenParts;
104
+ // Whole-field fallback: a fuzzy result may match on text the query never
105
+ // contains, so paint the field rather than leave the row unmarked.
106
+ return options.fallbackToWholeField ? [{ text, matched: true }] : [{ text, matched: false }];
107
+ }
108
+
109
+ /** Runs for every non-overlapping literal occurrence of `needle`. */
110
+ function splitLiteralParts(text: string, haystack: string, needle: string): HighlightPart[] {
111
+ const parts: HighlightPart[] = [];
112
+ let cursor = 0;
113
+ while (cursor < text.length) {
114
+ const hit = haystack.indexOf(needle, cursor);
115
+ if (hit === -1) {
116
+ parts.push({ text: text.slice(cursor), matched: false });
117
+ break;
118
+ }
119
+ if (hit > cursor) parts.push({ text: text.slice(cursor, hit), matched: false });
120
+ parts.push({ text: text.slice(hit, hit + needle.length), matched: true });
121
+ cursor = hit + needle.length;
122
+ }
123
+ return parts.length > 0 ? parts : [{ text, matched: false }];
124
+ }
125
+
126
+ /**
127
+ * Runs matching any `token` beginning at a word boundary, so a fuzzy query
128
+ * paints the token that prefixes a word (`meta` inside `metadata`) and never a
129
+ * fragment mid-word (`eta` inside `metadata`). The longest token wins a shared
130
+ * start; ties fall to the earliest occurrence. Returns null when no token is
131
+ * found, letting the caller apply its whole-field policy.
132
+ */
133
+ function splitTokenParts(
134
+ text: string,
135
+ haystack: string,
136
+ tokens: string[],
137
+ ): HighlightPart[] | null {
138
+ const sorted = [...tokens].sort((a, b) => b.length - a.length);
139
+ const parts: HighlightPart[] = [];
140
+ let matchedAny = false;
141
+ let cursor = 0;
142
+ while (cursor < text.length) {
143
+ let best: { index: number; token: string } | null = null;
144
+ for (const token of sorted) {
145
+ const index = firstTokenStart(haystack, token, cursor);
146
+ if (index === -1) continue;
147
+ if (best === null || index < best.index) best = { index, token };
148
+ }
149
+ if (best === null) {
150
+ parts.push({ text: text.slice(cursor), matched: false });
151
+ break;
152
+ }
153
+ if (best.index > cursor) parts.push({ text: text.slice(cursor, best.index), matched: false });
154
+ parts.push({ text: text.slice(best.index, best.index + best.token.length), matched: true });
155
+ matchedAny = true;
156
+ cursor = best.index + best.token.length;
157
+ }
158
+ return matchedAny ? parts : null;
159
+ }
160
+
161
+ /**
162
+ * Whether `text` contains the literal trimmed query (case-insensitively) —
163
+ * the precise, pre-fuzzy test callers use to tell a true hit from the token
164
+ * fallback.
165
+ */
166
+ export function hasHighlightMatch(text: string, query: string): boolean {
167
+ const needle = normalizeSearchQuery(query).toLowerCase();
168
+ return needle.length > 0 && text.toLowerCase().includes(needle);
169
+ }
170
+
171
+ /**
172
+ * Whether `text` highlights at all for the trimmed `query`: a literal hit or,
173
+ * failing that, a word-start hit for any of the query's multi-character tokens.
174
+ * Unlike `splitHighlightParts` this never reports the whole-field fallback, so
175
+ * it pinpoints the result/section that genuinely mentions a fuzzy query rather
176
+ * than every field.
177
+ */
178
+ export function hasFuzzyHighlight(text: string, query: string): boolean {
179
+ const needle = normalizeSearchQuery(query).toLowerCase();
180
+ if (!needle || !text) return false;
181
+ const haystack = text.toLowerCase();
182
+ if (haystack.includes(needle)) return true;
183
+ return queryTokens(needle).some((token) => firstTokenStart(haystack, token, 0) !== -1);
184
+ }
@@ -0,0 +1,10 @@
1
+ export * from "./types";
2
+ export * from "./rpc";
3
+ export * from "./formatters";
4
+ export * from "./settings";
5
+ export * from "./suite-settings";
6
+ export * from "./async";
7
+ export * from "./suppressed";
8
+ export * from "./custom-pills";
9
+ export * from "./forge";
10
+ export * from "./highlight";
@@ -0,0 +1,72 @@
1
+ import type { ZodType, input as ZodInput, output as ZodOutput } from "zod";
2
+
3
+ export interface PluginRpcContract<
4
+ InputSchema extends ZodType = ZodType,
5
+ OutputSchema extends ZodType = ZodType,
6
+ > {
7
+ name: string;
8
+ input: InputSchema;
9
+ output: OutputSchema;
10
+ }
11
+
12
+ const RPC_NAME = /^[a-z][a-z0-9._-]*$/;
13
+
14
+ /**
15
+ * Define a typed Paseo RPC contract conforming to the Paseo RPC protocol.
16
+ * Implemented locally without a runtime dependency on the Paseo SDK.
17
+ */
18
+ export function defineRpc<InputSchema extends ZodType, OutputSchema extends ZodType>(
19
+ definition: { name: string; input: InputSchema; output: OutputSchema },
20
+ ): PluginRpcContract<InputSchema, OutputSchema> {
21
+ const name = definition.name.trim();
22
+ if (!RPC_NAME.test(name)) {
23
+ throw new Error(`Invalid plugin RPC method: ${definition.name}`);
24
+ }
25
+ return { ...definition, name };
26
+ }
27
+
28
+ export type RpcInput<TContract> = TContract extends PluginRpcContract<infer TInput, any>
29
+ ? ZodInput<TInput>
30
+ : unknown;
31
+
32
+ export type RpcOutput<TContract> = TContract extends PluginRpcContract<any, infer TOutput>
33
+ ? ZodOutput<TOutput>
34
+ : unknown;
35
+
36
+ export interface DefineRpcOptions<
37
+ TName extends string = string,
38
+ TInput extends ZodType = ZodType,
39
+ TOutput extends ZodType = ZodType,
40
+ > {
41
+ name: TName;
42
+ input: TInput;
43
+ output: TOutput;
44
+ description?: string;
45
+ }
46
+
47
+ /**
48
+ * Enhanced helper for defining a typed Paseo RPC contract with optional description.
49
+ */
50
+ export function defineContract<
51
+ TName extends string,
52
+ TInput extends ZodType,
53
+ TOutput extends ZodType,
54
+ >(
55
+ options: DefineRpcOptions<TName, TInput, TOutput>,
56
+ ): PluginRpcContract<TInput, TOutput> & { readonly description?: string } {
57
+ const contract = defineRpc({
58
+ name: options.name,
59
+ input: options.input,
60
+ output: options.output,
61
+ });
62
+
63
+ if (options.description) {
64
+ Object.defineProperty(contract, "description", {
65
+ value: options.description,
66
+ enumerable: true,
67
+ writable: false,
68
+ });
69
+ }
70
+
71
+ return contract as PluginRpcContract<TInput, TOutput> & { readonly description?: string };
72
+ }
@@ -0,0 +1,138 @@
1
+ import { z, type ZodType } from "zod";
2
+ import { defineContract, type PluginRpcContract } from "./rpc";
3
+
4
+ export const SettingsEmptyInputSchema = z
5
+ .union([z.void(), z.record(z.string(), z.unknown())])
6
+ .optional();
7
+ export type SettingsEmptyInput = z.infer<typeof SettingsEmptyInputSchema>;
8
+
9
+ export interface SettingsContract<TSettings extends Record<string, any>> {
10
+ readonly name: string;
11
+ readonly schema: ZodType<TSettings>;
12
+ readonly defaultSettings: TSettings;
13
+ readonly get: PluginRpcContract<ZodType<SettingsEmptyInput>, ZodType<TSettings>>;
14
+ readonly update: PluginRpcContract<ZodType<Partial<TSettings>>, ZodType<TSettings>>;
15
+ readonly reset: PluginRpcContract<ZodType<SettingsEmptyInput>, ZodType<TSettings>>;
16
+ readonly description?: string;
17
+ }
18
+
19
+ export interface DefineSettingsContractOptions<TSettings extends Record<string, any>> {
20
+ /**
21
+ * Unique name for the settings domain (e.g. "top.settings" or "myplugin.config").
22
+ * Automatically normalizes invalid RPC characters.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Zod Object schema representing the full settings shape.
27
+ * Use `.default(...)` on fields to provide default values.
28
+ */
29
+ schema: ZodType<TSettings> & { partial?: () => ZodType<Partial<TSettings>> };
30
+ /**
31
+ * Optional default data override if schema fields do not all specify `.default()`.
32
+ */
33
+ defaultData?: Partial<TSettings>;
34
+ /**
35
+ * Optional human-readable description of the settings.
36
+ */
37
+ description?: string;
38
+ }
39
+
40
+ /**
41
+ * Strips `.default(...)` wrappers recursively from a Zod schema so that
42
+ * omitted fields remain undefined rather than being populated with default values
43
+ * during partial updates.
44
+ */
45
+ function stripDefaults(schema: any): any {
46
+ if (!schema || typeof schema !== "object") {
47
+ return schema;
48
+ }
49
+ if (schema instanceof z.ZodDefault) {
50
+ return stripDefaults(schema._def.innerType);
51
+ }
52
+ if (schema instanceof z.ZodOptional) {
53
+ return stripDefaults(schema._def.innerType).optional();
54
+ }
55
+ if (schema instanceof z.ZodNullable) {
56
+ return stripDefaults(schema._def.innerType).nullable();
57
+ }
58
+ // Handle ZodEffects, ZodBranded, ZodReadonly, ZodCatch
59
+ if (schema._def && schema._def.schema) {
60
+ return stripDefaults(schema._def.schema);
61
+ }
62
+ if (schema instanceof z.ZodObject) {
63
+ const shape = schema.shape;
64
+ const newShape: Record<string, any> = {};
65
+ for (const key of Object.keys(shape)) {
66
+ newShape[key] = stripDefaults(shape[key]).optional();
67
+ }
68
+ let res = z.object(newShape);
69
+ const unknownKeys = (schema._def as any)?.unknownKeys;
70
+ if (unknownKeys === "passthrough") {
71
+ res = res.passthrough() as any;
72
+ } else if (unknownKeys === "strict") {
73
+ res = res.strict() as any;
74
+ }
75
+ return res;
76
+ }
77
+ return typeof schema.optional === "function" ? schema.optional() : schema;
78
+ }
79
+
80
+ /**
81
+ * Defines a pair of typed Paseo RPC contracts (get, update, reset) for plugin settings.
82
+ */
83
+ export function defineSettingsContract<TSettings extends Record<string, any>>(
84
+ options: DefineSettingsContractOptions<TSettings>,
85
+ ): SettingsContract<TSettings> {
86
+ const { name, schema, defaultData, description } = options;
87
+
88
+ let computedDefaults: TSettings;
89
+ try {
90
+ computedDefaults = schema.parse(defaultData ?? {}) as TSettings;
91
+ } catch {
92
+ computedDefaults = (defaultData ?? {}) as TSettings;
93
+ }
94
+
95
+ const sanitizedName = name
96
+ .trim()
97
+ .toLowerCase()
98
+ .replace(/[^a-z0-9._-]/g, "_");
99
+
100
+ const partialSchema = (
101
+ schema instanceof z.ZodObject
102
+ ? stripDefaults(schema)
103
+ : typeof (schema as any).partial === "function"
104
+ ? (schema as any).partial()
105
+ : z.record(z.string(), z.unknown())
106
+ ) as ZodType<Partial<TSettings>>;
107
+
108
+ const getContract = defineContract({
109
+ name: `${sanitizedName}.get`,
110
+ input: SettingsEmptyInputSchema as ZodType<SettingsEmptyInput>,
111
+ output: schema,
112
+ description: description ? `Get ${description}` : `Get ${name} settings`,
113
+ });
114
+
115
+ const updateContract = defineContract({
116
+ name: `${sanitizedName}.update`,
117
+ input: partialSchema,
118
+ output: schema,
119
+ description: description ? `Update ${description}` : `Update ${name} settings`,
120
+ });
121
+
122
+ const resetContract = defineContract({
123
+ name: `${sanitizedName}.reset`,
124
+ input: SettingsEmptyInputSchema as ZodType<SettingsEmptyInput>,
125
+ output: schema,
126
+ description: description ? `Reset ${description}` : `Reset ${name} settings to defaults`,
127
+ });
128
+
129
+ return {
130
+ name: sanitizedName,
131
+ schema,
132
+ defaultSettings: computedDefaults,
133
+ get: getContract,
134
+ update: updateContract,
135
+ reset: resetContract,
136
+ ...(description !== undefined ? { description } : {}),
137
+ };
138
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import { defineSettingsContract } from "./settings";
3
+
4
+ export const SuiteSettingsSchema = z.object({
5
+ suiteTitle: z.string().default("xpufx Suite"),
6
+ accentColor: z.string().default("#6366f1"),
7
+ density: z.enum(["compact", "comfortable", "spacious"]).default("comfortable"),
8
+ showSuiteTabs: z.boolean().default(true),
9
+ });
10
+
11
+ export type SuiteSettings = z.infer<typeof SuiteSettingsSchema>;
12
+
13
+ export const SuiteSettingsContract = defineSettingsContract({
14
+ name: "xpufx.suite.settings",
15
+ schema: SuiteSettingsSchema,
16
+ description: "Shared xpufx suite settings",
17
+ });
@@ -0,0 +1,31 @@
1
+ export interface SuppressedSink {
2
+ debug?(message: string, data?: unknown): void;
3
+ warn?(message: string, data?: unknown): void;
4
+ }
5
+
6
+ function messageOf(error: unknown): string {
7
+ return error instanceof Error ? error.message : String(error);
8
+ }
9
+
10
+ /**
11
+ * Surfaces a caught/suppressed error to the plugin log at debug level
12
+ * (warn when `level: "warn"`). Fire-and-forget `catch(() => undefined)`
13
+ * sites should route through here so `paseo plugin logs` shows them
14
+ * when dev debug logging is enabled.
15
+ */
16
+ export function reportSuppressed(
17
+ sink: Pick<SuppressedSink, "debug" | "warn"> | undefined,
18
+ context: string,
19
+ error: unknown,
20
+ level: "debug" | "warn" = "debug",
21
+ ): void {
22
+ try {
23
+ if (level === "warn") {
24
+ sink?.warn?.(`${context}: ${messageOf(error)}`, error);
25
+ } else {
26
+ sink?.debug?.(`${context}: ${messageOf(error)}`, error);
27
+ }
28
+ } catch {
29
+ // Reporting must never throw.
30
+ }
31
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Structural theme types for Paseo plugins.
3
+ *
4
+ * These interfaces mirror the Paseo host theme shapes without importing any
5
+ * Paseo SDK module, so `paseo-plugin-helper/shared` (and everything
6
+ * built on it) typechecks and bundles identically against Paseo v0.7 and
7
+ * Paseo v0.8 SDKs.
8
+ */
9
+ export interface ThemeColors {
10
+ readonly surface0: string;
11
+ readonly surface1: string;
12
+ readonly surface2: string;
13
+ readonly border: string;
14
+ readonly foreground: string;
15
+ readonly foregroundMuted: string;
16
+ readonly accent: string;
17
+ readonly accentForeground: string;
18
+ readonly statusSuccess: string;
19
+ readonly statusWarning: string;
20
+ readonly statusDanger: string;
21
+ }
22
+
23
+ export interface PluginTheme {
24
+ readonly colors: ThemeColors;
25
+ }
26
+
27
+ export type PlatformType = "ios" | "android" | "web";
28
+
29
+ export interface ResponsiveLayout {
30
+ compact: boolean;
31
+ platform: PlatformType;
32
+ width?: number;
33
+ height?: number;
34
+ }
35
+
36
+ export type StatusVariant = "neutral" | "success" | "warning" | "danger" | "accent" | "info";
@@ -0,0 +1,2 @@
1
+ // Auto-generated by paseo-plugin-helper. Do not edit.
2
+ export const PLUGIN_VERSION = "0.1.0+a50ea39";