@guuey/chat 0.12.2 → 0.13.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/dist/index.d.ts CHANGED
@@ -16,6 +16,6 @@ export { defaultChatStrings, humanizeToolName, type ChatStrings, } from "./strin
16
16
  export { transcriptInputsFromHistory } from "./history-inputs.js";
17
17
  export { buildHitlAnswer, grantModeDisplay, hitlPromptsFromFold, type HitlAnswerRecord, type HitlPromptAction, } from "./hitl.js";
18
18
  export { OAUTH_LINK_PARAMS, OAUTH_RETURN_PARAMS, OAUTH_SCHEME, oauthAuthorizeAsk, oauthAuthorizeHref, parseOAuthReturn, stripOAuthReturn, type OAuthAuthorizeAsk, type OAuthReturn, } from "./oauth.js";
19
- export { DEFAULT_CHAT_THEME, GUUEY_CHAT_THEME, GuueyChatPalette, GuueyChatTheme, resolveTheme, } from "./theme.js";
19
+ export { DEFAULT_CHAT_THEME, GUUEY_CHAT_THEME, GuueyChatAccentRamp, GuueyChatErrorRamp, GuueyChatPalette, GuueyChatRamps, GuueyChatRampSet, GuueyChatTheme, GuueyChatToneRamp, resolveTheme, } from "./theme.js";
20
20
  export type { ChatDebugEvent, CitationsItem, CodeItem, CompactionItem, DataResultItem, DisplayItem, ErrorItem, HistoryBoundaryItem, ItemKey, HitlPromptInput, HitlPromptItem, MediaItem, NoticeItem, ProfilePromptInput, ProfilePromptItem, PromptItem, PromptItemInput, ReasoningItem, StatusLineItem, TextItem, ToolGroupItem, ToolItem, TranscriptInputs, TranscriptMessage, TranscriptOverrides, TranscriptPlan, PlanViewSummary, UnknownItem, UserMessageItem, ViewMountItem, ViewRefItem, } from "./types.js";
21
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,aAAa,EACb,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,UAAU,EACV,WAAW,EACX,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,aAAa,EACb,QAAQ,EACR,cAAc,EACd,cAAc,EACd,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,eAAe,EACf,cAAc,EACd,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,cAAc,EACd,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,aAAa,EACb,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,UAAU,EACV,WAAW,EACX,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,aAAa,EACb,QAAQ,EACR,cAAc,EACd,cAAc,EACd,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,OAAO,EACP,eAAe,EACf,cAAc,EACd,SAAS,EACT,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,cAAc,EACd,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -16,4 +16,4 @@ export { defaultChatStrings, humanizeToolName, } from "./strings.js";
16
16
  export { transcriptInputsFromHistory } from "./history-inputs.js";
17
17
  export { buildHitlAnswer, grantModeDisplay, hitlPromptsFromFold, } from "./hitl.js";
18
18
  export { OAUTH_LINK_PARAMS, OAUTH_RETURN_PARAMS, OAUTH_SCHEME, oauthAuthorizeAsk, oauthAuthorizeHref, parseOAuthReturn, stripOAuthReturn, } from "./oauth.js";
19
- export { DEFAULT_CHAT_THEME, GUUEY_CHAT_THEME, GuueyChatPalette, GuueyChatTheme, resolveTheme, } from "./theme.js";
19
+ export { DEFAULT_CHAT_THEME, GUUEY_CHAT_THEME, GuueyChatAccentRamp, GuueyChatErrorRamp, GuueyChatPalette, GuueyChatRamps, GuueyChatRampSet, GuueyChatTheme, GuueyChatToneRamp, resolveTheme, } from "./theme.js";
package/dist/strings.d.ts CHANGED
@@ -106,7 +106,6 @@ export interface ChatStrings {
106
106
  send: string;
107
107
  stop: string;
108
108
  }
109
- /** Humanize a wire tool name: `render_weather-card` → `render weather card`. */
110
109
  export declare function humanizeToolName(wireName: string): string;
111
110
  export declare const defaultChatStrings: ChatStrings;
112
111
  //# sourceMappingURL=strings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../src/strings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../src/strings.ts"],"names":[],"mappings":"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"}
package/dist/strings.js CHANGED
@@ -1,5 +1,28 @@
1
1
  /** Humanize a wire tool name: `render_weather-card` → `render weather card`. */
2
+ /**
3
+ * The ggui generative-UI rail's tool vocabulary, in end-user words
4
+ * (guuey#307): raw wire names ("mcp ggui ggui handshake") read as
5
+ * internals on every card-bearing conversation — and the rail is the
6
+ * platform default (mcp.ggui.ai), so the kit owns its voice the same
7
+ * way it owns the card machinery.
8
+ */
9
+ const GGUI_RAIL_TITLES = {
10
+ ggui_handshake: "Preparing interactive card",
11
+ ggui_render: "Rendering card",
12
+ ggui_consume: "Updating interactive card",
13
+ ggui_update: "Updating card",
14
+ };
2
15
  export function humanizeToolName(wireName) {
16
+ // MCP wire shape: `mcp__<server>__<tool>` (double-underscore separators).
17
+ const mcp = /^mcp__([^_].*?)__(.+)$/.exec(wireName);
18
+ if (mcp) {
19
+ const [, server, tool] = mcp;
20
+ if (server === "ggui" && GGUI_RAIL_TITLES[tool])
21
+ return GGUI_RAIL_TITLES[tool];
22
+ const prettyServer = server.charAt(0).toUpperCase() + server.slice(1).replace(/[_-]+/g, " ");
23
+ const prettyTool = tool.replace(/[_-]+/g, " ").trim();
24
+ return `${prettyServer} · ${prettyTool}`;
25
+ }
3
26
  return wireName.replace(/[_-]+/g, " ").trim();
4
27
  }
5
28
  export const defaultChatStrings = {
package/dist/theme.d.ts CHANGED
@@ -34,8 +34,122 @@ export declare const GuueyChatPalette: z.ZodObject<{
34
34
  error: z.ZodString;
35
35
  }, z.core.$loose>;
36
36
  export type GuueyChatPalette = z.infer<typeof GuueyChatPalette>;
37
+ /**
38
+ * STATED ramp slots (theme-as-code, guuey#341 spec §3 — never derived).
39
+ * Closed per-family sets: `accent` carries the full ladder (the
40
+ * 500/600/700 floor is a WRITE/COMPILE rule, not a schema rule — the
41
+ * schema admits any stated subset so a stale stored document still parses);
42
+ * the status families carry their tone-500 slot. Ramps live BESIDE the
43
+ * palettes (`theme.ramps.{light,dark}`), never inside them — the flat
44
+ * palette record is a load-bearing invariant (token lists are DERIVED from
45
+ * its shape by every platform gate).
46
+ */
47
+ export declare const GuueyChatAccentRamp: z.ZodObject<{
48
+ "100": z.ZodOptional<z.ZodString>;
49
+ "300": z.ZodOptional<z.ZodString>;
50
+ "500": z.ZodOptional<z.ZodString>;
51
+ "600": z.ZodOptional<z.ZodString>;
52
+ "700": z.ZodOptional<z.ZodString>;
53
+ "800": z.ZodOptional<z.ZodString>;
54
+ "900": z.ZodOptional<z.ZodString>;
55
+ }, z.core.$loose>;
56
+ export type GuueyChatAccentRamp = z.infer<typeof GuueyChatAccentRamp>;
57
+ export declare const GuueyChatErrorRamp: z.ZodObject<{
58
+ "500": z.ZodOptional<z.ZodString>;
59
+ "600": z.ZodOptional<z.ZodString>;
60
+ "700": z.ZodOptional<z.ZodString>;
61
+ }, z.core.$loose>;
62
+ export type GuueyChatErrorRamp = z.infer<typeof GuueyChatErrorRamp>;
63
+ export declare const GuueyChatToneRamp: z.ZodObject<{
64
+ "500": z.ZodOptional<z.ZodString>;
65
+ }, z.core.$loose>;
66
+ export type GuueyChatToneRamp = z.infer<typeof GuueyChatToneRamp>;
67
+ /** One mode's stated ramp families — every family optional. */
68
+ export declare const GuueyChatRampSet: z.ZodObject<{
69
+ accent: z.ZodOptional<z.ZodObject<{
70
+ "100": z.ZodOptional<z.ZodString>;
71
+ "300": z.ZodOptional<z.ZodString>;
72
+ "500": z.ZodOptional<z.ZodString>;
73
+ "600": z.ZodOptional<z.ZodString>;
74
+ "700": z.ZodOptional<z.ZodString>;
75
+ "800": z.ZodOptional<z.ZodString>;
76
+ "900": z.ZodOptional<z.ZodString>;
77
+ }, z.core.$loose>>;
78
+ error: z.ZodOptional<z.ZodObject<{
79
+ "500": z.ZodOptional<z.ZodString>;
80
+ "600": z.ZodOptional<z.ZodString>;
81
+ "700": z.ZodOptional<z.ZodString>;
82
+ }, z.core.$loose>>;
83
+ success: z.ZodOptional<z.ZodObject<{
84
+ "500": z.ZodOptional<z.ZodString>;
85
+ }, z.core.$loose>>;
86
+ warning: z.ZodOptional<z.ZodObject<{
87
+ "500": z.ZodOptional<z.ZodString>;
88
+ }, z.core.$loose>>;
89
+ info: z.ZodOptional<z.ZodObject<{
90
+ "500": z.ZodOptional<z.ZodString>;
91
+ }, z.core.$loose>>;
92
+ }, z.core.$loose>;
93
+ export type GuueyChatRampSet = z.infer<typeof GuueyChatRampSet>;
94
+ export declare const GuueyChatRamps: z.ZodObject<{
95
+ light: z.ZodOptional<z.ZodObject<{
96
+ accent: z.ZodOptional<z.ZodObject<{
97
+ "100": z.ZodOptional<z.ZodString>;
98
+ "300": z.ZodOptional<z.ZodString>;
99
+ "500": z.ZodOptional<z.ZodString>;
100
+ "600": z.ZodOptional<z.ZodString>;
101
+ "700": z.ZodOptional<z.ZodString>;
102
+ "800": z.ZodOptional<z.ZodString>;
103
+ "900": z.ZodOptional<z.ZodString>;
104
+ }, z.core.$loose>>;
105
+ error: z.ZodOptional<z.ZodObject<{
106
+ "500": z.ZodOptional<z.ZodString>;
107
+ "600": z.ZodOptional<z.ZodString>;
108
+ "700": z.ZodOptional<z.ZodString>;
109
+ }, z.core.$loose>>;
110
+ success: z.ZodOptional<z.ZodObject<{
111
+ "500": z.ZodOptional<z.ZodString>;
112
+ }, z.core.$loose>>;
113
+ warning: z.ZodOptional<z.ZodObject<{
114
+ "500": z.ZodOptional<z.ZodString>;
115
+ }, z.core.$loose>>;
116
+ info: z.ZodOptional<z.ZodObject<{
117
+ "500": z.ZodOptional<z.ZodString>;
118
+ }, z.core.$loose>>;
119
+ }, z.core.$loose>>;
120
+ dark: z.ZodOptional<z.ZodObject<{
121
+ accent: z.ZodOptional<z.ZodObject<{
122
+ "100": z.ZodOptional<z.ZodString>;
123
+ "300": z.ZodOptional<z.ZodString>;
124
+ "500": z.ZodOptional<z.ZodString>;
125
+ "600": z.ZodOptional<z.ZodString>;
126
+ "700": z.ZodOptional<z.ZodString>;
127
+ "800": z.ZodOptional<z.ZodString>;
128
+ "900": z.ZodOptional<z.ZodString>;
129
+ }, z.core.$loose>>;
130
+ error: z.ZodOptional<z.ZodObject<{
131
+ "500": z.ZodOptional<z.ZodString>;
132
+ "600": z.ZodOptional<z.ZodString>;
133
+ "700": z.ZodOptional<z.ZodString>;
134
+ }, z.core.$loose>>;
135
+ success: z.ZodOptional<z.ZodObject<{
136
+ "500": z.ZodOptional<z.ZodString>;
137
+ }, z.core.$loose>>;
138
+ warning: z.ZodOptional<z.ZodObject<{
139
+ "500": z.ZodOptional<z.ZodString>;
140
+ }, z.core.$loose>>;
141
+ info: z.ZodOptional<z.ZodObject<{
142
+ "500": z.ZodOptional<z.ZodString>;
143
+ }, z.core.$loose>>;
144
+ }, z.core.$loose>>;
145
+ }, z.core.$loose>;
146
+ export type GuueyChatRamps = z.infer<typeof GuueyChatRamps>;
37
147
  export declare const GuueyChatTheme: z.ZodObject<{
38
148
  name: z.ZodString;
149
+ mode: z.ZodOptional<z.ZodEnum<{
150
+ light: "light";
151
+ dark: "dark";
152
+ }>>;
39
153
  colors: z.ZodObject<{
40
154
  light: z.ZodObject<{
41
155
  accent: z.ZodString;
@@ -58,6 +172,58 @@ export declare const GuueyChatTheme: z.ZodObject<{
58
172
  error: z.ZodString;
59
173
  }, z.core.$loose>;
60
174
  }, z.core.$loose>;
175
+ ramps: z.ZodOptional<z.ZodObject<{
176
+ light: z.ZodOptional<z.ZodObject<{
177
+ accent: z.ZodOptional<z.ZodObject<{
178
+ "100": z.ZodOptional<z.ZodString>;
179
+ "300": z.ZodOptional<z.ZodString>;
180
+ "500": z.ZodOptional<z.ZodString>;
181
+ "600": z.ZodOptional<z.ZodString>;
182
+ "700": z.ZodOptional<z.ZodString>;
183
+ "800": z.ZodOptional<z.ZodString>;
184
+ "900": z.ZodOptional<z.ZodString>;
185
+ }, z.core.$loose>>;
186
+ error: z.ZodOptional<z.ZodObject<{
187
+ "500": z.ZodOptional<z.ZodString>;
188
+ "600": z.ZodOptional<z.ZodString>;
189
+ "700": z.ZodOptional<z.ZodString>;
190
+ }, z.core.$loose>>;
191
+ success: z.ZodOptional<z.ZodObject<{
192
+ "500": z.ZodOptional<z.ZodString>;
193
+ }, z.core.$loose>>;
194
+ warning: z.ZodOptional<z.ZodObject<{
195
+ "500": z.ZodOptional<z.ZodString>;
196
+ }, z.core.$loose>>;
197
+ info: z.ZodOptional<z.ZodObject<{
198
+ "500": z.ZodOptional<z.ZodString>;
199
+ }, z.core.$loose>>;
200
+ }, z.core.$loose>>;
201
+ dark: z.ZodOptional<z.ZodObject<{
202
+ accent: z.ZodOptional<z.ZodObject<{
203
+ "100": z.ZodOptional<z.ZodString>;
204
+ "300": z.ZodOptional<z.ZodString>;
205
+ "500": z.ZodOptional<z.ZodString>;
206
+ "600": z.ZodOptional<z.ZodString>;
207
+ "700": z.ZodOptional<z.ZodString>;
208
+ "800": z.ZodOptional<z.ZodString>;
209
+ "900": z.ZodOptional<z.ZodString>;
210
+ }, z.core.$loose>>;
211
+ error: z.ZodOptional<z.ZodObject<{
212
+ "500": z.ZodOptional<z.ZodString>;
213
+ "600": z.ZodOptional<z.ZodString>;
214
+ "700": z.ZodOptional<z.ZodString>;
215
+ }, z.core.$loose>>;
216
+ success: z.ZodOptional<z.ZodObject<{
217
+ "500": z.ZodOptional<z.ZodString>;
218
+ }, z.core.$loose>>;
219
+ warning: z.ZodOptional<z.ZodObject<{
220
+ "500": z.ZodOptional<z.ZodString>;
221
+ }, z.core.$loose>>;
222
+ info: z.ZodOptional<z.ZodObject<{
223
+ "500": z.ZodOptional<z.ZodString>;
224
+ }, z.core.$loose>>;
225
+ }, z.core.$loose>>;
226
+ }, z.core.$loose>>;
61
227
  typography: z.ZodObject<{
62
228
  fontFamily: z.ZodOptional<z.ZodString>;
63
229
  monoFontFamily: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;iBAWnB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmBjB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,cA0BhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cA0B9B,CAAC;AA0CF;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,OAAO,EAClB,IAAI,GAAE,cAAmC,GACxC,cAAc,CAgBhB"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;iBAWnB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;iBAUtB,CAAC;AACX,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,kBAAkB;;;;iBAMrB,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,iBAAiB;;iBAAqD,CAAC;AACpF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;iBAQnB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKjB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BjB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,cA0BhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cA0B9B,CAAC;AA4CF;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,OAAO,EAClB,IAAI,GAAE,cAAmC,GACxC,cAAc,CAuBhB"}
package/dist/theme.js CHANGED
@@ -35,11 +35,67 @@ export const GuueyChatPalette = z
35
35
  error: z.string(),
36
36
  })
37
37
  .loose();
38
+ /**
39
+ * STATED ramp slots (theme-as-code, guuey#341 spec §3 — never derived).
40
+ * Closed per-family sets: `accent` carries the full ladder (the
41
+ * 500/600/700 floor is a WRITE/COMPILE rule, not a schema rule — the
42
+ * schema admits any stated subset so a stale stored document still parses);
43
+ * the status families carry their tone-500 slot. Ramps live BESIDE the
44
+ * palettes (`theme.ramps.{light,dark}`), never inside them — the flat
45
+ * palette record is a load-bearing invariant (token lists are DERIVED from
46
+ * its shape by every platform gate).
47
+ */
48
+ export const GuueyChatAccentRamp = z
49
+ .object({
50
+ "100": z.string().optional(),
51
+ "300": z.string().optional(),
52
+ "500": z.string().optional(),
53
+ "600": z.string().optional(),
54
+ "700": z.string().optional(),
55
+ "800": z.string().optional(),
56
+ "900": z.string().optional(),
57
+ })
58
+ .loose();
59
+ export const GuueyChatErrorRamp = z
60
+ .object({
61
+ "500": z.string().optional(),
62
+ "600": z.string().optional(),
63
+ "700": z.string().optional(),
64
+ })
65
+ .loose();
66
+ export const GuueyChatToneRamp = z.object({ "500": z.string().optional() }).loose();
67
+ /** One mode's stated ramp families — every family optional. */
68
+ export const GuueyChatRampSet = z
69
+ .object({
70
+ accent: GuueyChatAccentRamp.optional(),
71
+ error: GuueyChatErrorRamp.optional(),
72
+ success: GuueyChatToneRamp.optional(),
73
+ warning: GuueyChatToneRamp.optional(),
74
+ info: GuueyChatToneRamp.optional(),
75
+ })
76
+ .loose();
77
+ export const GuueyChatRamps = z
78
+ .object({
79
+ light: GuueyChatRampSet.optional(),
80
+ dark: GuueyChatRampSet.optional(),
81
+ })
82
+ .loose();
38
83
  export const GuueyChatTheme = z
39
84
  .object({
40
85
  name: z.string(),
86
+ /**
87
+ * The app's CANONICAL presentation mode (theme-as-code §3) — what the
88
+ * ggui stamp pins its render slice to. OPTIONAL and default-less on
89
+ * purpose: absent = "not stated", and the platform's legacy polarity
90
+ * derivation stands. Distinct from the VIEWER's runtime light/dark
91
+ * choice, which stays a component prop — chat components never read
92
+ * this field.
93
+ */
94
+ mode: z.enum(["light", "dark"]).optional(),
41
95
  /** BOTH palettes always present — mode is the consumer's runtime choice. */
42
96
  colors: z.object({ light: GuueyChatPalette, dark: GuueyChatPalette }).loose(),
97
+ /** Stated ramp slots per mode — see {@link GuueyChatRamps}. */
98
+ ramps: GuueyChatRamps.optional(),
43
99
  typography: z
44
100
  .object({
45
101
  fontFamily: z.string().optional(),
@@ -126,10 +182,12 @@ const PartialPalette = GuueyChatPalette.partial();
126
182
  const PartialTheme = z
127
183
  .object({
128
184
  name: z.string().optional(),
185
+ mode: GuueyChatTheme.shape.mode.optional(),
129
186
  colors: z
130
187
  .object({ light: PartialPalette.optional(), dark: PartialPalette.optional() })
131
188
  .loose()
132
189
  .optional(),
190
+ ramps: GuueyChatRamps.optional(),
133
191
  typography: GuueyChatTheme.shape.typography.optional(),
134
192
  shape: GuueyChatTheme.shape.shape.partial().loose().optional(),
135
193
  })
@@ -167,12 +225,19 @@ export function resolveTheme(candidate, base = DEFAULT_CHAT_THEME) {
167
225
  if (!parsed.success)
168
226
  return { ...base, colors: { light: { ...base.colors.light }, dark: { ...base.colors.dark } } };
169
227
  const p = parsed.data;
228
+ const mode = p.mode ?? base.mode;
229
+ const ramps = mergeRamps(base.ramps, p.ramps);
170
230
  return {
171
231
  name: p.name ?? base.name,
232
+ // `mode`/`ramps` are default-less (the package themes state neither):
233
+ // they appear on the resolved theme only when SOME layer stated them —
234
+ // an absent statement must stay visibly absent, not become a default.
235
+ ...(mode !== undefined ? { mode } : {}),
172
236
  colors: {
173
237
  light: mergePalette(base.colors.light, p.colors?.light),
174
238
  dark: mergePalette(base.colors.dark, p.colors?.dark),
175
239
  },
240
+ ...(ramps !== undefined ? { ramps } : {}),
176
241
  typography: { ...base.typography, ...(p.typography ?? {}) },
177
242
  shape: {
178
243
  radius: p.shape?.radius ?? base.shape.radius,
@@ -180,3 +245,31 @@ export function resolveTheme(candidate, base = DEFAULT_CHAT_THEME) {
180
245
  },
181
246
  };
182
247
  }
248
+ /** Slot-level merge of two ramp statements (candidate slots win per slot). */
249
+ function mergeRampSet(base, over) {
250
+ if (base === undefined)
251
+ return over;
252
+ if (over === undefined)
253
+ return base;
254
+ const merged = { ...base };
255
+ for (const family of ["accent", "error", "success", "warning", "info"]) {
256
+ const b = base[family];
257
+ const o = over[family];
258
+ if (o === undefined)
259
+ continue;
260
+ merged[family] = b === undefined ? o : { ...b, ...o };
261
+ }
262
+ return merged;
263
+ }
264
+ function mergeRamps(base, over) {
265
+ if (base === undefined)
266
+ return over;
267
+ if (over === undefined)
268
+ return base;
269
+ const light = mergeRampSet(base.light, over.light);
270
+ const dark = mergeRampSet(base.dark, over.dark);
271
+ return {
272
+ ...(light !== undefined ? { light } : {}),
273
+ ...(dark !== undefined ? { dark } : {}),
274
+ };
275
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/chat",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "description": "The default end-user transcript UI for guuey agents — the headless view-model (planTranscript), calm/debug presets, the GuueyChatTheme token schema, the ChatStrings i18n seam, and (at ./react) the component kit that renders it: per-category components, <Transcript>, the live + history input assemblers, and stick-to-bottom/windowed/accessible transcript behavior; at ./native, the React Native renderer over the same view-model.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,8 +39,8 @@
39
39
  "@silverprotocol/core": "0.5.1",
40
40
  "@silverprotocol/richtext": "0.5.1",
41
41
  "zod": "^4.3.6",
42
- "@guuey/agent-client": "0.12.2",
43
- "@guuey/mcp-apps-host": "0.12.2"
42
+ "@guuey/agent-client": "0.13.0",
43
+ "@guuey/mcp-apps-host": "0.13.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": ">=18",
package/src/index.ts CHANGED
@@ -46,8 +46,13 @@ export {
46
46
  export {
47
47
  DEFAULT_CHAT_THEME,
48
48
  GUUEY_CHAT_THEME,
49
+ GuueyChatAccentRamp,
50
+ GuueyChatErrorRamp,
49
51
  GuueyChatPalette,
52
+ GuueyChatRamps,
53
+ GuueyChatRampSet,
50
54
  GuueyChatTheme,
55
+ GuueyChatToneRamp,
51
56
  resolveTheme,
52
57
  } from "./theme.js";
53
58
  export type {
package/src/strings.ts CHANGED
@@ -123,7 +123,30 @@ export interface ChatStrings {
123
123
  }
124
124
 
125
125
  /** Humanize a wire tool name: `render_weather-card` → `render weather card`. */
126
+ /**
127
+ * The ggui generative-UI rail's tool vocabulary, in end-user words
128
+ * (guuey#307): raw wire names ("mcp ggui ggui handshake") read as
129
+ * internals on every card-bearing conversation — and the rail is the
130
+ * platform default (mcp.ggui.ai), so the kit owns its voice the same
131
+ * way it owns the card machinery.
132
+ */
133
+ const GGUI_RAIL_TITLES: Record<string, string> = {
134
+ ggui_handshake: "Preparing interactive card",
135
+ ggui_render: "Rendering card",
136
+ ggui_consume: "Updating interactive card",
137
+ ggui_update: "Updating card",
138
+ };
139
+
126
140
  export function humanizeToolName(wireName: string): string {
141
+ // MCP wire shape: `mcp__<server>__<tool>` (double-underscore separators).
142
+ const mcp = /^mcp__([^_].*?)__(.+)$/.exec(wireName);
143
+ if (mcp) {
144
+ const [, server, tool] = mcp;
145
+ if (server === "ggui" && GGUI_RAIL_TITLES[tool]) return GGUI_RAIL_TITLES[tool];
146
+ const prettyServer = server.charAt(0).toUpperCase() + server.slice(1).replace(/[_-]+/g, " ");
147
+ const prettyTool = tool.replace(/[_-]+/g, " ").trim();
148
+ return `${prettyServer} · ${prettyTool}`;
149
+ }
127
150
  return wireName.replace(/[_-]+/g, " ").trim();
128
151
  }
129
152
 
package/src/theme.ts CHANGED
@@ -38,11 +38,77 @@ export const GuueyChatPalette = z
38
38
  .loose();
39
39
  export type GuueyChatPalette = z.infer<typeof GuueyChatPalette>;
40
40
 
41
+ /**
42
+ * STATED ramp slots (theme-as-code, guuey#341 spec §3 — never derived).
43
+ * Closed per-family sets: `accent` carries the full ladder (the
44
+ * 500/600/700 floor is a WRITE/COMPILE rule, not a schema rule — the
45
+ * schema admits any stated subset so a stale stored document still parses);
46
+ * the status families carry their tone-500 slot. Ramps live BESIDE the
47
+ * palettes (`theme.ramps.{light,dark}`), never inside them — the flat
48
+ * palette record is a load-bearing invariant (token lists are DERIVED from
49
+ * its shape by every platform gate).
50
+ */
51
+ export const GuueyChatAccentRamp = z
52
+ .object({
53
+ "100": z.string().optional(),
54
+ "300": z.string().optional(),
55
+ "500": z.string().optional(),
56
+ "600": z.string().optional(),
57
+ "700": z.string().optional(),
58
+ "800": z.string().optional(),
59
+ "900": z.string().optional(),
60
+ })
61
+ .loose();
62
+ export type GuueyChatAccentRamp = z.infer<typeof GuueyChatAccentRamp>;
63
+
64
+ export const GuueyChatErrorRamp = z
65
+ .object({
66
+ "500": z.string().optional(),
67
+ "600": z.string().optional(),
68
+ "700": z.string().optional(),
69
+ })
70
+ .loose();
71
+ export type GuueyChatErrorRamp = z.infer<typeof GuueyChatErrorRamp>;
72
+
73
+ export const GuueyChatToneRamp = z.object({ "500": z.string().optional() }).loose();
74
+ export type GuueyChatToneRamp = z.infer<typeof GuueyChatToneRamp>;
75
+
76
+ /** One mode's stated ramp families — every family optional. */
77
+ export const GuueyChatRampSet = z
78
+ .object({
79
+ accent: GuueyChatAccentRamp.optional(),
80
+ error: GuueyChatErrorRamp.optional(),
81
+ success: GuueyChatToneRamp.optional(),
82
+ warning: GuueyChatToneRamp.optional(),
83
+ info: GuueyChatToneRamp.optional(),
84
+ })
85
+ .loose();
86
+ export type GuueyChatRampSet = z.infer<typeof GuueyChatRampSet>;
87
+
88
+ export const GuueyChatRamps = z
89
+ .object({
90
+ light: GuueyChatRampSet.optional(),
91
+ dark: GuueyChatRampSet.optional(),
92
+ })
93
+ .loose();
94
+ export type GuueyChatRamps = z.infer<typeof GuueyChatRamps>;
95
+
41
96
  export const GuueyChatTheme = z
42
97
  .object({
43
98
  name: z.string(),
99
+ /**
100
+ * The app's CANONICAL presentation mode (theme-as-code §3) — what the
101
+ * ggui stamp pins its render slice to. OPTIONAL and default-less on
102
+ * purpose: absent = "not stated", and the platform's legacy polarity
103
+ * derivation stands. Distinct from the VIEWER's runtime light/dark
104
+ * choice, which stays a component prop — chat components never read
105
+ * this field.
106
+ */
107
+ mode: z.enum(["light", "dark"]).optional(),
44
108
  /** BOTH palettes always present — mode is the consumer's runtime choice. */
45
109
  colors: z.object({ light: GuueyChatPalette, dark: GuueyChatPalette }).loose(),
110
+ /** Stated ramp slots per mode — see {@link GuueyChatRamps}. */
111
+ ramps: GuueyChatRamps.optional(),
46
112
  typography: z
47
113
  .object({
48
114
  fontFamily: z.string().optional(),
@@ -133,10 +199,12 @@ const PartialPalette = GuueyChatPalette.partial();
133
199
  const PartialTheme = z
134
200
  .object({
135
201
  name: z.string().optional(),
202
+ mode: GuueyChatTheme.shape.mode.optional(),
136
203
  colors: z
137
204
  .object({ light: PartialPalette.optional(), dark: PartialPalette.optional() })
138
205
  .loose()
139
206
  .optional(),
207
+ ramps: GuueyChatRamps.optional(),
140
208
  typography: GuueyChatTheme.shape.typography.optional(),
141
209
  shape: GuueyChatTheme.shape.shape.partial().loose().optional(),
142
210
  })
@@ -180,12 +248,19 @@ export function resolveTheme(
180
248
  const parsed = PartialTheme.safeParse(candidate);
181
249
  if (!parsed.success) return { ...base, colors: { light: { ...base.colors.light }, dark: { ...base.colors.dark } } };
182
250
  const p = parsed.data;
251
+ const mode = p.mode ?? base.mode;
252
+ const ramps = mergeRamps(base.ramps, p.ramps);
183
253
  return {
184
254
  name: p.name ?? base.name,
255
+ // `mode`/`ramps` are default-less (the package themes state neither):
256
+ // they appear on the resolved theme only when SOME layer stated them —
257
+ // an absent statement must stay visibly absent, not become a default.
258
+ ...(mode !== undefined ? { mode } : {}),
185
259
  colors: {
186
260
  light: mergePalette(base.colors.light, p.colors?.light),
187
261
  dark: mergePalette(base.colors.dark, p.colors?.dark),
188
262
  },
263
+ ...(ramps !== undefined ? { ramps } : {}),
189
264
  typography: { ...base.typography, ...(p.typography ?? {}) },
190
265
  shape: {
191
266
  radius: p.shape?.radius ?? base.shape.radius,
@@ -193,3 +268,34 @@ export function resolveTheme(
193
268
  },
194
269
  };
195
270
  }
271
+
272
+ /** Slot-level merge of two ramp statements (candidate slots win per slot). */
273
+ function mergeRampSet(
274
+ base: GuueyChatRampSet | undefined,
275
+ over: GuueyChatRampSet | undefined,
276
+ ): GuueyChatRampSet | undefined {
277
+ if (base === undefined) return over;
278
+ if (over === undefined) return base;
279
+ const merged: GuueyChatRampSet = { ...base };
280
+ for (const family of ["accent", "error", "success", "warning", "info"] as const) {
281
+ const b = base[family];
282
+ const o = over[family];
283
+ if (o === undefined) continue;
284
+ merged[family] = b === undefined ? o : { ...b, ...o };
285
+ }
286
+ return merged;
287
+ }
288
+
289
+ function mergeRamps(
290
+ base: GuueyChatRamps | undefined,
291
+ over: GuueyChatRamps | undefined,
292
+ ): GuueyChatRamps | undefined {
293
+ if (base === undefined) return over;
294
+ if (over === undefined) return base;
295
+ const light = mergeRampSet(base.light, over.light);
296
+ const dark = mergeRampSet(base.dark, over.dark);
297
+ return {
298
+ ...(light !== undefined ? { light } : {}),
299
+ ...(dark !== undefined ? { dark } : {}),
300
+ };
301
+ }