ghostykit 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Piloto UI · `ghostykit`
2
2
 
3
3
  **An in-app copilot for React.** Piloto UI is the agent chat from
4
- [Ghosty Studio](https://www.ghosty.studio), packaged as a library. You drop it into an app you already
4
+ [Ghosty Studio](https://www.ghosty.studio) (`/c`), packaged as a library: same components, same
5
+ markup, same look ([side by side](https://raw.githubusercontent.com/blissito/ghostykit/main/docs/screenshots/parity/desktop-done-gs-vs-ghostykit.png)). You drop it into an app you already
5
6
  have. It sees what the user has on screen, streams what the agent says and does, and **stops to ask
6
7
  before anything risky** (human in the loop).
7
8
 
@@ -107,6 +108,7 @@ const ref = useRef<PilotoHandle>(null);
107
108
  agentName="Admin assistant"
108
109
  welcome="How can I help with your crons?"
109
110
  suggestions={["Why did the monitor fail?"]}
111
+ draft={{ text: "Why did the monitor fail?", key: row.id }} // prefilled + focused, not sent
110
112
  context={() => ({ screen: "crons", selected })} // sent with EVERY turn
111
113
  toolVerbs={{ run_cron: { one: "Ran a cron", many: "Ran %n crons", running: "Running the cron…" } }}
112
114
  upload={async (file) => ({ url: await myUpload(file) })}
@@ -130,9 +132,11 @@ your own layout.
130
132
  ### Skins and theming
131
133
 
132
134
  `ghostykit/styles.css` is self-contained and scoped under `.piloto`. It works the same with
133
- **Tailwind 3, Tailwind 4 or no Tailwind** (verified in a Tailwind 3 app: its utilities compute
134
- identically with and without Piloto's CSS). Every color, radius, border width and shadow is a
135
- variable:
135
+ **Tailwind 3, Tailwind 4 or no Tailwind**, and it shields its fields from the host's CSS (verified
136
+ in a Tailwind 3 app **with `@tailwindcss/forms`**: Piloto's textarea gets no border or focus ring, and
137
+ the app's own utilities compute identically with and without Piloto's CSS). Sizes don't depend on
138
+ the host's `html` font size either: `1rem` inside Piloto is `--piloto-rem` (18 px in the Ghosty skin,
139
+ like ghosty.studio; 16 px in the others). Every color, radius, border width and shadow is a variable:
136
140
 
137
141
  ```css
138
142
  .my-app .piloto {
@@ -141,6 +145,7 @@ variable:
141
145
  --piloto-radius-card: 6px;
142
146
  --piloto-border-width: 3px;
143
147
  --piloto-shadow-hero: 6px 6px 0 #0f766e;
148
+ --piloto-rem: 16px;
144
149
  --piloto-font: "Your Sans", sans-serif;
145
150
  }
146
151
  ```
@@ -52,8 +52,16 @@ function parse(raw) {
52
52
  }
53
53
  }
54
54
  function defaultHistory(json) {
55
- const msgs = json?.messages ?? [];
56
- return msgs.filter((m) => typeof m.text === "string").map((m) => ({ role: m.role === "user" ? "user" : "assistant", text: m.text }));
55
+ const j = json;
56
+ const turns = (j?.messages ?? []).filter((m) => typeof m.text === "string").map((m) => ({ role: m.role === "user" ? "user" : "assistant", text: m.text }));
57
+ const permissions = (j?.pending ?? []).filter((p) => typeof p === "object" && p !== null && typeof p.id === "string").map((p) => ({
58
+ id: p.id,
59
+ title: String(p.title ?? ""),
60
+ ...typeof p.tool === "string" ? { tool: p.tool } : {},
61
+ options: Array.isArray(p.options) ? p.options : [],
62
+ ...p.expiresAt != null ? { expiresAt: p.expiresAt } : {}
63
+ }));
64
+ return { turns, permissions };
57
65
  }
58
66
  function uuid() {
59
67
  const c = globalThis.crypto;
@@ -146,9 +154,9 @@ function createPostStreamTransport(target, options = {}) {
146
154
  };
147
155
  },
148
156
  async history() {
149
- if (!historyUrl) return [];
157
+ if (!historyUrl) return { turns: [] };
150
158
  const res = await doFetch(historyUrl, { credentials, headers: { accept: "application/json" } });
151
- if (!res.ok) return [];
159
+ if (!res.ok) return { turns: [] };
152
160
  return (opts.parseHistory ?? defaultHistory)(await res.json().catch(() => null));
153
161
  },
154
162
  async send(input) {
@@ -411,6 +411,13 @@ function resolvePermission(prev, id, optionId, resolved, detail, asked) {
411
411
  s = { ...s, turns };
412
412
  return s;
413
413
  }
414
+ function applyHistory(prev, turns, permissions = []) {
415
+ let s = { ...prev, effects: [] };
416
+ if (turns.length && !prev.turns.length) s.turns = turns;
417
+ const fresh = permissions.filter((p) => !prev.permissions.some((q) => q.id === p.id));
418
+ if (fresh.length) s.permissions = [...prev.permissions, ...fresh];
419
+ return s.turns === prev.turns && !fresh.length ? prev : s;
420
+ }
414
421
  function appendAgentMessages(prev, texts) {
415
422
  if (!texts.length) return prev;
416
423
  return { ...prev, effects: [], turns: [...prev.turns, ...texts.map((text) => ({ role: "assistant", text, arrived: true }))] };
@@ -643,6 +650,7 @@ export {
643
650
  stopTurn,
644
651
  dropLastExchange,
645
652
  resolvePermission,
653
+ applyHistory,
646
654
  appendAgentMessages,
647
655
  reconcileWith,
648
656
  lastUserTurn,
@@ -1,5 +1,5 @@
1
1
  export * from "./types";
2
- export { RESTART_NOTICE, RESUME_TEXT, appendAgentMessages, beginSend, createPilotState, dropLastExchange, expiryMs, lastUserTurn, reconcileWith, reducePilotEvent, resolvePermission, resumeMode, sendAccepted, sendFailed, sendRejectedBusy, stopTurn, } from "./reducer";
2
+ export { RESTART_NOTICE, RESUME_TEXT, appendAgentMessages, applyHistory, beginSend, createPilotState, dropLastExchange, expiryMs, lastUserTurn, reconcileWith, reducePilotEvent, resolvePermission, resumeMode, sendAccepted, sendFailed, sendRejectedBusy, stopTurn, } from "./reducer";
3
3
  export { reconcileTurns } from "./reconcile";
4
4
  export { isNarration, narrationToMarkdown, splitNarration } from "./narration";
5
5
  export { cleanReplyOption, extractReplyOptions, type ReplyOptions } from "./options";
@@ -4,6 +4,7 @@ import {
4
4
  TOOL_VERBS_EN,
5
5
  TOOL_VERBS_ES,
6
6
  appendAgentMessages,
7
+ applyHistory,
7
8
  beginSend,
8
9
  cleanReplyOption,
9
10
  createPilotState,
@@ -28,7 +29,7 @@ import {
28
29
  stopTurn,
29
30
  summarizeTools,
30
31
  toolName
31
- } from "../chunks/chunk-XWOGK2HN.js";
32
+ } from "../chunks/chunk-O6FCE4CS.js";
32
33
  import {
33
34
  applyReferences,
34
35
  cleanUserText,
@@ -41,6 +42,7 @@ export {
41
42
  TOOL_VERBS_EN,
42
43
  TOOL_VERBS_ES,
43
44
  appendAgentMessages,
45
+ applyHistory,
44
46
  applyReferences,
45
47
  beginSend,
46
48
  cleanReplyOption,
@@ -36,6 +36,11 @@ export declare function dropLastExchange(prev: PilotState): PilotState;
36
36
  export declare function resolvePermission(prev: PilotState, id: string, optionId: string, resolved: boolean, detail?: string,
37
37
  /** La tarjeta como estaba al contestarla (el stream pudo retirarla mientras tanto). */
38
38
  asked?: PermissionRequest): PilotState;
39
+ /**
40
+ * El historial guardado (al montar): el hilo, si la pantalla sigue vacía, y las tarjetas de permiso
41
+ * que siguen abiertas (sin duplicar las que el stream ya trajo).
42
+ */
43
+ export declare function applyHistory(prev: PilotState, turns: Turn[], permissions?: PermissionRequest[]): PilotState;
39
44
  /** Un mensaje del agente que entró fuera de un turno (el remate de los subagentes). */
40
45
  export declare function appendAgentMessages(prev: PilotState, texts: string[]): PilotState;
41
46
  /** La copia del hilo cambió: trae los mensajes que el usuario mandó desde OTRO cliente. Sólo en reposo. */
package/dist/fonts.css CHANGED
@@ -1,3 +1,3 @@
1
- /* Opcional: las fuentes de las pieles de casa (Schibsted Grotesk para leer, Big Shoulders Stencil
2
- para el reloj y el sello del pase). Sin esto, Piloto cae a las fuentes del sistema. */
3
- @import url("https://fonts.googleapis.com/css2?family=Big+Shoulders+Stencil+Display:wght@700;800;900&family=Schibsted+Grotesk:ital,wght@0,400..900;1,400..900&display=swap");
1
+ /* Opcional: las fuentes de las pieles de casa — Inter (la de Ghosty) y Big Shoulders Stencil (el
2
+ reloj y el sello del pase de permiso). Sin esto, Piloto cae a las fuentes del sistema. */
3
+ @import url("https://fonts.googleapis.com/css2?family=Big+Shoulders+Stencil+Display:wght@800;900&family=Inter:opsz,wght@14..32,400..800&display=swap");
@@ -16,11 +16,12 @@ export declare function ReplyOptionButtons({ options, onPick }: {
16
16
  /** Tokens del turno, qué tanto del contexto se llevó y cuánto costó. */
17
17
  export declare function UsageStats({ used, size, cost }: Usage): import("react").JSX.Element | null;
18
18
  /** El pie de una respuesta terminada: cuánto tardó, copiar y reintentar. */
19
- export declare function TurnFooter({ text, seconds, onRetry, showCopy }: {
19
+ export declare function TurnFooter({ text, seconds, onRetry, showCopy, showDuration }: {
20
20
  text: string;
21
21
  seconds?: number;
22
22
  onRetry?: () => void;
23
23
  showCopy?: boolean;
24
+ showDuration?: boolean;
24
25
  }): import("react").JSX.Element;
25
26
  /** Las entregas por defecto: una ficha por archivo (las apps pintan las suyas con `renderDeliveries`). */
26
27
  export declare function DeliveryChips({ deliveries }: {
@@ -35,7 +36,8 @@ export interface AgentBubbleProps {
35
36
  onOption?: (text: string) => void;
36
37
  showUsage?: boolean;
37
38
  showCopy?: boolean;
39
+ showDuration?: boolean;
38
40
  /** Las entregas (y lo que la app quiera bajo la respuesta). Sin esto, fichas simples. */
39
41
  renderDeliveries?: (turn: Turn, live: boolean) => ReactNode;
40
42
  }
41
- export declare function AgentBubble({ turn, live, onRetry, onOption, showUsage, showCopy, renderDeliveries }: AgentBubbleProps): import("react").JSX.Element;
43
+ export declare function AgentBubble({ turn, live, onRetry, onOption, showUsage, showCopy, showDuration, renderDeliveries }: AgentBubbleProps): import("react").JSX.Element;
@@ -1,13 +1,17 @@
1
1
  import type { Reference } from "../core/types";
2
- /** Las referencias del mensaje: chips atómicos con ×. Clic en el chip = `onActivate` (resaltar en la app). */
3
- export declare function ReferenceChips({ references, onRemove, onActivate, }: {
2
+ /**
3
+ * Las referencias del mensaje: chips atómicos con ×. Una con `prompt` (un elemento señalado) se pinta
4
+ * con la mira, como en Ghosty; las demás (tarjetas del tablero), con su etiqueta. Clic = `onActivate`.
5
+ */
6
+ export declare function ReferenceChips({ references, onRemove, onActivate }: {
4
7
  references: Reference[];
5
8
  onRemove: (id: string) => void;
6
9
  onActivate?: (ref: Reference) => void;
7
10
  }): import("react").JSX.Element | null;
8
11
  /** Sugerencias para arrancar. Una que termina en «: » se escribe en la caja en vez de mandarse. */
9
- export declare function Suggestions({ items, onPick, onPrefill }: {
12
+ export declare function Suggestions({ items, onPick, onPrefill, disabled }: {
10
13
  items: string[];
11
14
  onPick: (text: string) => void;
12
15
  onPrefill?: (text: string) => void;
16
+ disabled?: boolean;
13
17
  }): import("react").JSX.Element | null;
@@ -37,7 +37,10 @@ export interface ComposerProps {
37
37
  maxFileMb?: number;
38
38
  }
39
39
  export declare function Composer({ onSubmit, onStop, busy, canSteer, placeholder, initialValue, autoFocus, canSeeImages, attachments: withFiles, upload, transcribe, voiceNoteText, models, currentModel, onModelChange, references, onRemoveReference, onActivateReference, extras, maxFiles, maxFileMb, }: ComposerProps): import("react").JSX.Element;
40
- /** El selector de modelo: un menú corto hacia arriba, sin Radix. Cierra con clic fuera y Escape. */
40
+ /**
41
+ * El selector de modelo. PORTADO de Ghosty (`ModelPicker.tsx`): dropdown discreto hacia arriba, sin
42
+ * Radix; cierra con clic fuera y con Escape.
43
+ */
41
44
  export declare function ModelPicker({ models, current, onChange }: {
42
45
  models: ModelOption[];
43
46
  current: string | null;
@@ -0,0 +1,5 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function renderInline(text: string): ReactNode[];
3
+ export declare function InlineMarkdown({ children }: {
4
+ children: string;
5
+ }): import("react").JSX.Element;
@@ -65,6 +65,14 @@ export interface PilotoProps extends UsePilotOptions {
65
65
  * `transport.respondPermission`. Devuelve `{resolved:false}` si ya no estaba abierto.
66
66
  */
67
67
  onPermission?: PermissionHandler;
68
+ /**
69
+ * Una pregunta lista en la caja (sin mandarla) y el foco en ella: al abrir el copiloto desde una
70
+ * fila («Pídeselo»). Cambiar el texto —o `key`, para repetir el mismo— la vuelve a poner.
71
+ */
72
+ draft?: string | {
73
+ text: string;
74
+ key?: string | number;
75
+ } | null;
68
76
  /** Referencias controladas (si no, Piloto las guarda; agrégalas con `ref.addReference`). */
69
77
  references?: Reference[];
70
78
  onReferencesChange?: (refs: Reference[]) => void;
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { ConnectPhase } from "../core/types";
3
- /** Los tres puntos de «escribiendo». Color y animación por CSS (`.piloto-typing`). */
3
+ /** Los tres puntitos de «escribiendo» (color y animación en CSS: `gc-typing`). */
4
4
  export declare function TypingDots(): import("react").JSX.Element;
5
5
  export declare function WaitLabel({ phase, since, elsewhere, onLost, showDuration, }: {
6
6
  phase: ConnectPhase;
@@ -1,22 +1,27 @@
1
- import { type ReactNode } from "react";
2
1
  import type { ToolEntry } from "../core/types";
3
2
  import { type PilotIcon } from "./context";
4
- /** La garra de máquina de peluches: el ícono de «usó una herramienta» que no tiene clase propia. */
3
+ /** La garra de máquina de peluches: el ícono de «usó una herramienta» (baja, cierra y sube en bucle). */
5
4
  export declare function ClawIcon({ className }: {
6
5
  className?: string;
7
6
  }): import("react").JSX.Element;
8
- /** Íconos de casa por `kind`. Lo desconocido cae en la garra. */
7
+ /** Brazo robot agarrando — «Robot grab» de Delapouite (game-icons.net, CC BY 3.0): los pasos técnicos. */
8
+ export declare function RobotArmIcon({ className }: {
9
+ className?: string;
10
+ }): import("react").JSX.Element;
11
+ /** Máquina arcade (Iconoir «arcade», MIT): las herramientas de un MCP conectado. */
12
+ export declare function ArcadeIcon({ className }: {
13
+ className?: string;
14
+ }): import("react").JSX.Element;
15
+ /** Un ícono por tipo de herramienta (los de Ghosty); lo desconocido cae en la garra. */
9
16
  export declare const DEFAULT_TOOL_ICONS: Record<string, PilotIcon>;
10
17
  export declare function AgentSteps({ tools, live }: {
11
18
  tools: ToolEntry[];
12
19
  live: boolean;
13
20
  }): import("react").JSX.Element;
14
- /** El detalle de un paso (lista desplegada): rótulo, detalle y ruta. */
15
21
  export declare function ToolRow({ tool }: {
16
22
  tool: ToolEntry;
17
23
  }): import("react").JSX.Element;
18
24
  /** La narración del agente como avance: una palomita por lo que ya dijo que hacía. */
19
- export declare function Narration({ steps, children }: {
25
+ export declare function Narration({ steps }: {
20
26
  steps: string[];
21
- children?: ReactNode;
22
27
  }): import("react").JSX.Element;
@@ -3,6 +3,7 @@ export { usePilot, useSseTransport, type PermissionAnswer, type PermissionHandle
3
3
  export { AgentBubble, CopyUserMessage, DeliveryChips, ReplyOptionButtons, TurnFooter, UsageStats, UserBubble, type AgentBubbleProps } from "./Bubble";
4
4
  export { AgentSteps, ClawIcon, DEFAULT_TOOL_ICONS, Narration, ToolRow } from "./Steps";
5
5
  export { LinkSafetyModal, Markdown } from "./Markdown";
6
+ export { InlineMarkdown, renderInline } from "./InlineMarkdown";
6
7
  export { useTextAtPace } from "./TextAtPace";
7
8
  export { Composer, ModelPicker, mimeOf, type ComposerProps, type UploadResult } from "./Composer";
8
9
  export { ConfirmCard, DecisionLines } from "./ConfirmCard";
@@ -11,4 +12,4 @@ export { ConnectingState, TypingDots, WaitLabel } from "./Status";
11
12
  export { PilotoContext, usePiloto, type MarkdownOptions, type PilotIcon, type PilotoConfig, type PilotTheme } from "./context";
12
13
  export { STRINGS_EN, STRINGS_ES, type PilotStrings } from "./strings";
13
14
  export * from "../core";
14
- export { PilotHttpError, createPostStreamTransport, createSseTransport, readSse, type PilotListener, type PilotTransport, type PostStreamTransportOptions, type SseTransportOptions, type SubscribeOptions, } from "../transport";
15
+ export { PilotHttpError, createPostStreamTransport, createSseTransport, readSse, type PilotHistory, type PilotListener, type PilotTransport, type PostStreamTransportOptions, type SseTransportOptions, type SubscribeOptions, } from "../transport";