@joycostudio/studio 0.0.0-stage → 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.
- package/README.md +271 -2
- package/dist/chunk-QKEAOM2U.js +13 -0
- package/dist/chunk-QKEAOM2U.js.map +1 -0
- package/dist/chunk-VYNEG5Z7.js +75 -0
- package/dist/chunk-VYNEG5Z7.js.map +1 -0
- package/dist/cli.js +687 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +63 -0
- package/dist/client.js +273 -0
- package/dist/client.js.map +1 -0
- package/dist/command-menu.css +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol-BrHDtNkL.d.ts +140 -0
- package/dist/react.d.ts +184 -0
- package/dist/react.js +1527 -0
- package/dist/react.js.map +1 -0
- package/dist/runtime.d.ts +41 -0
- package/dist/runtime.js +546 -0
- package/dist/runtime.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +98 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { DebugSchema } from '@joycostudio/xyz/debug';
|
|
2
|
+
|
|
3
|
+
type Success<T> = {
|
|
4
|
+
ok: true;
|
|
5
|
+
data: T;
|
|
6
|
+
};
|
|
7
|
+
type Failure = {
|
|
8
|
+
ok: false;
|
|
9
|
+
/** snake_case wire code, e.g. `invalid_config`. */
|
|
10
|
+
error: string;
|
|
11
|
+
message?: string;
|
|
12
|
+
};
|
|
13
|
+
type Result<T> = Success<T> | Failure;
|
|
14
|
+
declare const success: <T>(data: T) => Success<T>;
|
|
15
|
+
declare const error: (code: string, message?: string) => Failure;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An SVG icon as data: `[tag, attributes]` children of a 24×24 stroke icon.
|
|
19
|
+
* Lucide's core package (`import { Palette } from 'lucide'`) exports exactly this shape.
|
|
20
|
+
*/
|
|
21
|
+
type ToolIcon = readonly (readonly [tag: string, attrs: Record<string, string | number | undefined>])[];
|
|
22
|
+
/**
|
|
23
|
+
* A tool's contract. Isomorphic on purpose: the runtime loads it in Node to
|
|
24
|
+
* validate and persist, the app loads it in the browser to apply and debug.
|
|
25
|
+
*
|
|
26
|
+
* `TConfig` is the one type that is read, edited, applied, and persisted.
|
|
27
|
+
*/
|
|
28
|
+
type ToolDefinition<TConfig extends object = object> = {
|
|
29
|
+
/** Unique, URL-safe identifier. */
|
|
30
|
+
id: string;
|
|
31
|
+
title?: string;
|
|
32
|
+
/** Shown next to the tool in /studio. Data, not a component, so contracts stay loadable in Node. */
|
|
33
|
+
icon?: ToolIcon;
|
|
34
|
+
/** Repo-relative JSON file the config persists to. Import that same file as `initial`. */
|
|
35
|
+
file: string;
|
|
36
|
+
/** Config used before the runtime answers (and in production). */
|
|
37
|
+
initial: TConfig;
|
|
38
|
+
/**
|
|
39
|
+
* What can be tweaked, as an Xyz/Tweakpane schema bound to `config`.
|
|
40
|
+
* Doubles as the runtime validator: every control's bounds are enforced on write.
|
|
41
|
+
*/
|
|
42
|
+
controls: (config: TConfig) => DebugSchema;
|
|
43
|
+
/** App route that renders this tool's result, shown next to the controls in /studio. */
|
|
44
|
+
preview?: string;
|
|
45
|
+
/** Use a bare iframe for tools that already own their canvas layout and headers. */
|
|
46
|
+
previewLayout?: 'canvas' | 'custom';
|
|
47
|
+
};
|
|
48
|
+
type AnyTool = ToolDefinition<any>;
|
|
49
|
+
type ConfigOf<TTool> = TTool extends ToolDefinition<infer TConfig> ? TConfig : never;
|
|
50
|
+
declare function defineTool<TConfig extends object>(tool: ToolDefinition<TConfig>): ToolDefinition<TConfig>;
|
|
51
|
+
type StudioConfig = {
|
|
52
|
+
tools: readonly AnyTool[];
|
|
53
|
+
/** Runtime WebSocket and HTTP MCP port. Defaults to 5179. */
|
|
54
|
+
port?: number;
|
|
55
|
+
/** Browser origins allowed on the bridge and MCP endpoint. Defaults to localhost origins. */
|
|
56
|
+
origins?: readonly string[];
|
|
57
|
+
/** Running app URL (e.g. http://localhost:3000), used for MCP preview captures. */
|
|
58
|
+
appUrl?: string;
|
|
59
|
+
};
|
|
60
|
+
declare const DEFAULT_PORT = 5179;
|
|
61
|
+
declare function defineConfig(config: StudioConfig): StudioConfig;
|
|
62
|
+
|
|
63
|
+
/** A tool's live state as owned by the runtime. */
|
|
64
|
+
type ToolState<TConfig = unknown> = {
|
|
65
|
+
tool: string;
|
|
66
|
+
/** What is being edited. Bumps on every accepted update. */
|
|
67
|
+
draft: {
|
|
68
|
+
value: TConfig;
|
|
69
|
+
revision: number;
|
|
70
|
+
};
|
|
71
|
+
/** What is on disk. `hash` is null while the file does not exist yet. */
|
|
72
|
+
saved: {
|
|
73
|
+
value: TConfig;
|
|
74
|
+
hash: string | null;
|
|
75
|
+
};
|
|
76
|
+
dirty: boolean;
|
|
77
|
+
/** The file changed on disk while the draft had unsaved edits. */
|
|
78
|
+
conflict: boolean;
|
|
79
|
+
};
|
|
80
|
+
type ToolSummary = {
|
|
81
|
+
id: string;
|
|
82
|
+
title: string;
|
|
83
|
+
file: string;
|
|
84
|
+
icon?: ToolIcon;
|
|
85
|
+
preview?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Every operation the runtime authorizes. The UI, the in-app debug pane, and MCP all go through these. */
|
|
88
|
+
type Operations = {
|
|
89
|
+
'tools.list': {
|
|
90
|
+
params: {};
|
|
91
|
+
result: ToolSummary[];
|
|
92
|
+
};
|
|
93
|
+
'tool.get': {
|
|
94
|
+
params: {
|
|
95
|
+
tool: string;
|
|
96
|
+
};
|
|
97
|
+
result: ToolState;
|
|
98
|
+
};
|
|
99
|
+
/** Agents supply `revision` to avoid replacing a newer draft; browser controls can stream edits without it. */
|
|
100
|
+
'tool.update': {
|
|
101
|
+
params: {
|
|
102
|
+
tool: string;
|
|
103
|
+
value: unknown;
|
|
104
|
+
revision?: number;
|
|
105
|
+
};
|
|
106
|
+
result: ToolState;
|
|
107
|
+
};
|
|
108
|
+
/** `revision` is the draft revision being approved; a newer draft rejects the save. */
|
|
109
|
+
'tool.save': {
|
|
110
|
+
params: {
|
|
111
|
+
tool: string;
|
|
112
|
+
revision: number;
|
|
113
|
+
force?: boolean;
|
|
114
|
+
};
|
|
115
|
+
result: ToolState;
|
|
116
|
+
};
|
|
117
|
+
'tool.discard': {
|
|
118
|
+
params: {
|
|
119
|
+
tool: string;
|
|
120
|
+
revision?: number;
|
|
121
|
+
};
|
|
122
|
+
result: ToolState;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
type Op = keyof Operations;
|
|
126
|
+
type Request<TOp extends Op = Op> = {
|
|
127
|
+
id: number;
|
|
128
|
+
op: TOp;
|
|
129
|
+
params: Operations[TOp]['params'];
|
|
130
|
+
};
|
|
131
|
+
type ServerMessage = {
|
|
132
|
+
type: 'response';
|
|
133
|
+
id: number;
|
|
134
|
+
result: Result<unknown>;
|
|
135
|
+
} | {
|
|
136
|
+
type: 'state';
|
|
137
|
+
state: ToolState;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export { type AnyTool as A, type ConfigOf as C, DEFAULT_PORT as D, type Failure as F, type Op as O, type Result as R, type ServerMessage as S, type ToolDefinition as T, type ToolState as a, type Operations as b, type Request as c, type StudioConfig as d, type Success as e, type ToolIcon as f, type ToolSummary as g, defineConfig as h, defineTool as i, error as j, success as s };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ComponentPropsWithRef, ReactNode, ComponentPropsWithoutRef, ComponentProps } from 'react';
|
|
3
|
+
import { DebugSchema, DebugFolder } from '@joycostudio/xyz/debug';
|
|
4
|
+
import { Select } from 'radix-ui';
|
|
5
|
+
|
|
6
|
+
type Success<T> = {
|
|
7
|
+
ok: true;
|
|
8
|
+
data: T;
|
|
9
|
+
};
|
|
10
|
+
type Failure = {
|
|
11
|
+
ok: false;
|
|
12
|
+
/** snake_case wire code, e.g. `invalid_config`. */
|
|
13
|
+
error: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
};
|
|
16
|
+
type Result<T> = Success<T> | Failure;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An SVG icon as data: `[tag, attributes]` children of a 24×24 stroke icon.
|
|
20
|
+
* Lucide's core package (`import { Palette } from 'lucide'`) exports exactly this shape.
|
|
21
|
+
*/
|
|
22
|
+
type ToolIcon = readonly (readonly [tag: string, attrs: Record<string, string | number | undefined>])[];
|
|
23
|
+
/**
|
|
24
|
+
* A tool's contract. Isomorphic on purpose: the runtime loads it in Node to
|
|
25
|
+
* validate and persist, the app loads it in the browser to apply and debug.
|
|
26
|
+
*
|
|
27
|
+
* `TConfig` is the one type that is read, edited, applied, and persisted.
|
|
28
|
+
*/
|
|
29
|
+
type ToolDefinition<TConfig extends object = object> = {
|
|
30
|
+
/** Unique, URL-safe identifier. */
|
|
31
|
+
id: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
/** Shown next to the tool in /studio. Data, not a component, so contracts stay loadable in Node. */
|
|
34
|
+
icon?: ToolIcon;
|
|
35
|
+
/** Repo-relative JSON file the config persists to. Import that same file as `initial`. */
|
|
36
|
+
file: string;
|
|
37
|
+
/** Config used before the runtime answers (and in production). */
|
|
38
|
+
initial: TConfig;
|
|
39
|
+
/**
|
|
40
|
+
* What can be tweaked, as an Xyz/Tweakpane schema bound to `config`.
|
|
41
|
+
* Doubles as the runtime validator: every control's bounds are enforced on write.
|
|
42
|
+
*/
|
|
43
|
+
controls: (config: TConfig) => DebugSchema;
|
|
44
|
+
/** App route that renders this tool's result, shown next to the controls in /studio. */
|
|
45
|
+
preview?: string;
|
|
46
|
+
/** Use a bare iframe for tools that already own their canvas layout and headers. */
|
|
47
|
+
previewLayout?: 'canvas' | 'custom';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** A tool's live state as owned by the runtime. */
|
|
51
|
+
type ToolState<TConfig = unknown> = {
|
|
52
|
+
tool: string;
|
|
53
|
+
/** What is being edited. Bumps on every accepted update. */
|
|
54
|
+
draft: {
|
|
55
|
+
value: TConfig;
|
|
56
|
+
revision: number;
|
|
57
|
+
};
|
|
58
|
+
/** What is on disk. `hash` is null while the file does not exist yet. */
|
|
59
|
+
saved: {
|
|
60
|
+
value: TConfig;
|
|
61
|
+
hash: string | null;
|
|
62
|
+
};
|
|
63
|
+
dirty: boolean;
|
|
64
|
+
/** The file changed on disk while the draft had unsaved edits. */
|
|
65
|
+
conflict: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type Connection = {
|
|
69
|
+
phase: 'local';
|
|
70
|
+
} | {
|
|
71
|
+
phase: 'connecting';
|
|
72
|
+
} | {
|
|
73
|
+
phase: 'connected';
|
|
74
|
+
} | {
|
|
75
|
+
phase: 'offline';
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type ToolHandle<TConfig extends object = any> = {
|
|
79
|
+
readonly definition: ToolDefinition<TConfig>;
|
|
80
|
+
/** The live config. Read it to apply; writes (from any pane, or your code) sync to the runtime. */
|
|
81
|
+
readonly config: TConfig;
|
|
82
|
+
/** Runtime-owned state; null until the runtime answers (always null without one). */
|
|
83
|
+
readonly state: ToolState<TConfig> | null;
|
|
84
|
+
/** Bumps on every config change. Handy as a React snapshot. */
|
|
85
|
+
readonly version: number;
|
|
86
|
+
/** Called after every config change, local or remote. */
|
|
87
|
+
subscribe(listener: (config: TConfig) => void): () => void;
|
|
88
|
+
/** The tool's Xyz schema bound to the live config. */
|
|
89
|
+
controls(): DebugSchema;
|
|
90
|
+
/** Xyz `Debuggable`: `debugManager.register(handle)` mounts this tool in the app's floating pane. */
|
|
91
|
+
debug(): DebugFolder;
|
|
92
|
+
/** True while `compare(true)` shows the saved config in place of the draft. */
|
|
93
|
+
readonly comparing: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Temporarily show the saved config (true) or go back to the draft (false). Local only: nothing
|
|
96
|
+
* reaches the runtime, and edits made while comparing are dropped when it ends.
|
|
97
|
+
*/
|
|
98
|
+
compare(showSaved: boolean): void;
|
|
99
|
+
save(options?: {
|
|
100
|
+
force?: boolean;
|
|
101
|
+
}): Promise<Result<ToolState<TConfig>>>;
|
|
102
|
+
discard(): Promise<Result<ToolState<TConfig>>>;
|
|
103
|
+
};
|
|
104
|
+
type Studio$1 = {
|
|
105
|
+
readonly tools: readonly ToolHandle[];
|
|
106
|
+
tool<TConfig extends object>(definition: ToolDefinition<TConfig>): ToolHandle<TConfig>;
|
|
107
|
+
readonly connection: Connection;
|
|
108
|
+
/** Bumps on connection or any tool state change. */
|
|
109
|
+
readonly version: number;
|
|
110
|
+
subscribe(listener: () => void): () => void;
|
|
111
|
+
close(): void;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
interface StudioProps {
|
|
115
|
+
studio: Studio$1;
|
|
116
|
+
/** Forward the iframe ref and native props to preserve Studio shortcuts and compare. */
|
|
117
|
+
previewComponent?: ComponentType<ComponentPropsWithRef<'iframe'>> | 'iframe';
|
|
118
|
+
/** Resolve config paths using the host's editor URL or source browser. */
|
|
119
|
+
getConfigFileHref?: (file: string) => string;
|
|
120
|
+
}
|
|
121
|
+
/** The /studio route: every registered tool, its preview, and its Tweakpane inspector. */
|
|
122
|
+
declare function Studio({ studio, previewComponent, getConfigFileHref }: StudioProps): react.JSX.Element | null;
|
|
123
|
+
|
|
124
|
+
type StudioCommandMenuProps = {
|
|
125
|
+
studio: Studio$1;
|
|
126
|
+
/** Path to the host's Studio route. Defaults to /studio. */
|
|
127
|
+
studioPath?: string;
|
|
128
|
+
/** Optional host router navigation (for example, Next.js router.push). */
|
|
129
|
+
navigate?: (href: string) => void;
|
|
130
|
+
};
|
|
131
|
+
/** Mount once in the host layout, including /studio, to jump to any registered tool with ⌘/Ctrl+K. */
|
|
132
|
+
declare function StudioCommandMenu({ studio, studioPath, navigate }: StudioCommandMenuProps): react.JSX.Element;
|
|
133
|
+
|
|
134
|
+
/** Re-render on connection and tool state changes. */
|
|
135
|
+
declare function useStudio(studio: Studio$1): Studio$1;
|
|
136
|
+
/** The tool's live config; re-renders on every change, local or remote. */
|
|
137
|
+
declare function useToolConfig<TConfig extends object>(handle: ToolHandle<TConfig>): TConfig;
|
|
138
|
+
|
|
139
|
+
/** The inset card surface shared by canvas headers, toolbars, and detail panels. */
|
|
140
|
+
declare const StudioSurface: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
141
|
+
asChild?: boolean;
|
|
142
|
+
} & react.RefAttributes<HTMLDivElement>>;
|
|
143
|
+
declare const StudioToolbar: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
|
|
144
|
+
|
|
145
|
+
interface CanvasToolbarProps {
|
|
146
|
+
label: string;
|
|
147
|
+
expanded?: boolean;
|
|
148
|
+
onToggle?: () => void;
|
|
149
|
+
children?: ReactNode;
|
|
150
|
+
}
|
|
151
|
+
/** Pinned by default; auto-hide preserves pointer, keyboard, open-menu, and touch access. */
|
|
152
|
+
declare function CanvasToolbar({ label, expanded, onToggle, children }: CanvasToolbarProps): react.JSX.Element;
|
|
153
|
+
|
|
154
|
+
interface PreviewCanvasProps extends ComponentPropsWithoutRef<'iframe'> {
|
|
155
|
+
expanded?: boolean;
|
|
156
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
157
|
+
onReload?: () => void;
|
|
158
|
+
toolbar?: ReactNode;
|
|
159
|
+
}
|
|
160
|
+
/** Expands the same iframe in place. Native iframe props and the ref reach the iframe. */
|
|
161
|
+
declare const PreviewCanvas: react.ForwardRefExoticComponent<PreviewCanvasProps & react.RefAttributes<HTMLIFrameElement>>;
|
|
162
|
+
|
|
163
|
+
type StudioSelectProps = ComponentProps<typeof Select.Root> & {
|
|
164
|
+
label: string;
|
|
165
|
+
title?: string;
|
|
166
|
+
className?: string;
|
|
167
|
+
};
|
|
168
|
+
/** Compact Radix select shared by canvas and activity toolbars. */
|
|
169
|
+
declare function StudioSelect({ label, title, className, children, ...props }: StudioSelectProps): react.JSX.Element;
|
|
170
|
+
declare function StudioSelectItem({ className, children, ...props }: ComponentProps<typeof Select.Item>): react.JSX.Element;
|
|
171
|
+
|
|
172
|
+
/** Logical padding lets icon-bearing fields override only their leading inset. */
|
|
173
|
+
declare const Input: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & react.RefAttributes<HTMLInputElement>>;
|
|
174
|
+
|
|
175
|
+
/** Shared floating surface. Layout and visibility belong to the caller. */
|
|
176
|
+
declare const Panel: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
|
|
177
|
+
asChild?: boolean;
|
|
178
|
+
} & react.RefAttributes<HTMLDivElement>>;
|
|
179
|
+
/** Raised sections sit inside the panel's 4px padding with concentric corners. */
|
|
180
|
+
declare function PanelHeader({ className, ...props }: ComponentProps<'header'>): react.JSX.Element;
|
|
181
|
+
declare const PanelContent: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & react.RefAttributes<HTMLDivElement>>;
|
|
182
|
+
declare function PanelFooter({ className, ...props }: ComponentProps<'footer'>): react.JSX.Element;
|
|
183
|
+
|
|
184
|
+
export { CanvasToolbar, type CanvasToolbarProps, PreviewCanvas, type PreviewCanvasProps, Studio, StudioCommandMenu, type StudioCommandMenuProps, Input as StudioInput, Panel as StudioPanel, PanelContent as StudioPanelContent, PanelFooter as StudioPanelFooter, PanelHeader as StudioPanelHeader, type StudioProps, StudioSelect, StudioSelectItem, type StudioSelectProps, StudioSurface, StudioToolbar, useStudio, useToolConfig };
|