@hitch42/applet 0.1.1-beta.1

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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_internal/client.d.ts +7 -0
  3. package/dist/_internal/client.js +60 -0
  4. package/dist/_internal/server.d.ts +79 -0
  5. package/dist/_internal/server.js +232 -0
  6. package/dist/applet.d.ts +1 -0
  7. package/dist/applet.js +160 -0
  8. package/dist/auth-DaVK0k5R.js +350 -0
  9. package/dist/batch-Qa4B4-Yp.d.ts +30 -0
  10. package/dist/build-CEku1lyb.js +2 -0
  11. package/dist/build-Rk3xDZV7.js +74 -0
  12. package/dist/client.d.ts +48 -0
  13. package/dist/client.js +38 -0
  14. package/dist/contract-DMrpbaLJ.d.ts +23 -0
  15. package/dist/deploy-D3BGDqgC.js +2416 -0
  16. package/dist/generate-6Nbt7uii.js +2 -0
  17. package/dist/generate-CH0VQmUi.js +484 -0
  18. package/dist/index.d.ts +74 -0
  19. package/dist/index.js +136 -0
  20. package/dist/init-BS1Y-eNL.js +240 -0
  21. package/dist/lazy-client-x0DCI98S.js +64 -0
  22. package/dist/load-config-BnxocLAQ.js +56 -0
  23. package/dist/logs-m00pzr_O.js +183 -0
  24. package/dist/manifest-BQCAxATS.d.ts +81 -0
  25. package/dist/manifest-DyoJ1MIL.js +174 -0
  26. package/dist/project-CxN-Dtc7.js +346 -0
  27. package/dist/react.d.ts +37 -0
  28. package/dist/react.js +517 -0
  29. package/dist/server.d.ts +46 -0
  30. package/dist/server.js +104 -0
  31. package/dist/upload-DRbVhuDm.js +114 -0
  32. package/dist/upload-YbRKEqR6.d.ts +112 -0
  33. package/dist/variables-CP7Ce3Np.js +133 -0
  34. package/dist/vite.d.ts +63 -0
  35. package/dist/vite.js +358 -0
  36. package/dist/workflow-CTo2kZF1.js +51 -0
  37. package/dist/workflow-DurM6Fwx.d.ts +28 -0
  38. package/package.json +104 -0
  39. package/scaffold/.agents/skills/hitch-applets/SKILL.md +137 -0
  40. package/scaffold/.agents/skills/hitch-applets/references/client.md +73 -0
  41. package/scaffold/.agents/skills/hitch-applets/references/files.md +109 -0
  42. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +171 -0
  43. package/scaffold/.agents/skills/hitch-applets/references/markdown.md +37 -0
  44. package/scaffold/.agents/skills/hitch-applets/references/records.md +152 -0
  45. package/scaffold/.agents/skills/hitch-applets/references/schedules.md +86 -0
  46. package/scaffold/.agents/skills/hitch-applets/references/server.md +98 -0
  47. package/scaffold/.agents/skills/hitch-applets/references/workflows.md +73 -0
  48. package/scaffold/AGENTS.md +38 -0
  49. package/scaffold/applet/components.json +25 -0
  50. package/scaffold/applet/src/client/components/ui/alert.tsx +73 -0
  51. package/scaffold/applet/src/client/components/ui/avatar.tsx +100 -0
  52. package/scaffold/applet/src/client/components/ui/badge.tsx +55 -0
  53. package/scaffold/applet/src/client/components/ui/button.tsx +90 -0
  54. package/scaffold/applet/src/client/components/ui/card.tsx +85 -0
  55. package/scaffold/applet/src/client/components/ui/checkbox.tsx +35 -0
  56. package/scaffold/applet/src/client/components/ui/dialog.tsx +164 -0
  57. package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +235 -0
  58. package/scaffold/applet/src/client/components/ui/input-group.tsx +144 -0
  59. package/scaffold/applet/src/client/components/ui/input.tsx +22 -0
  60. package/scaffold/applet/src/client/components/ui/label.tsx +29 -0
  61. package/scaffold/applet/src/client/components/ui/popover.tsx +74 -0
  62. package/scaffold/applet/src/client/components/ui/select.tsx +243 -0
  63. package/scaffold/applet/src/client/components/ui/separator.tsx +25 -0
  64. package/scaffold/applet/src/client/components/ui/sheet.tsx +177 -0
  65. package/scaffold/applet/src/client/components/ui/skeleton.tsx +13 -0
  66. package/scaffold/applet/src/client/components/ui/spinner.tsx +16 -0
  67. package/scaffold/applet/src/client/components/ui/switch.tsx +41 -0
  68. package/scaffold/applet/src/client/components/ui/table.tsx +114 -0
  69. package/scaffold/applet/src/client/components/ui/tabs.tsx +78 -0
  70. package/scaffold/applet/src/client/components/ui/textarea.tsx +23 -0
  71. package/scaffold/applet/src/client/lib/utils.ts +6 -0
  72. package/scaffold/applet/src/client/styles/globals.css +136 -0
package/dist/react.js ADDED
@@ -0,0 +1,517 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { Streamdown, defaultRehypePlugins, defaultUrlTransform } from "streamdown";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ import { EditorContent, NodeViewWrapper, ReactNodeViewRenderer, useEditor, useEditorState } from "@tiptap/react";
5
+ import { BetweenHorizontalEnd, BetweenVerticalEnd, Bold, Code, Heading, Italic, Link, List, ListMinus, ListOrdered, ListTodo, Quote, Strikethrough, Table, Trash2 } from "lucide-react";
6
+ import { Button, Toolbar } from "react-aria-components";
7
+ import { twMerge } from "tailwind-merge";
8
+ import { generateHTML } from "@tiptap/core";
9
+ import Image from "@tiptap/extension-image";
10
+ import { TaskItem, TaskList } from "@tiptap/extension-list";
11
+ import { Table as Table$1, TableCell, TableHeader, TableRow, renderTableToMarkdown } from "@tiptap/extension-table";
12
+ import { Placeholder } from "@tiptap/extensions";
13
+ import { Markdown as Markdown$1 } from "@tiptap/markdown";
14
+ import StarterKit from "@tiptap/starter-kit";
15
+ //#region src/react/hitch-file-url.ts
16
+ const HITCH_FILE_PREFIX = "hitch-file://";
17
+ const DOWNLOAD_URL_TTL_MS = 3e6;
18
+ const downloadUrlCache = /* @__PURE__ */ new Map();
19
+ function cachedDownloadUrl(files, id) {
20
+ const cached = downloadUrlCache.get(id);
21
+ if (cached !== void 0 && cached.expiresAt > Date.now()) return cached.request;
22
+ const request = files.download(id).then((file) => file.downloadUrl);
23
+ downloadUrlCache.set(id, {
24
+ request,
25
+ expiresAt: Date.now() + DOWNLOAD_URL_TTL_MS
26
+ });
27
+ return request;
28
+ }
29
+ function seedDownloadUrl(id, url) {
30
+ downloadUrlCache.set(id, {
31
+ request: Promise.resolve(url),
32
+ expiresAt: Date.now() + DOWNLOAD_URL_TTL_MS
33
+ });
34
+ }
35
+ function invalidateDownloadUrl(id) {
36
+ downloadUrlCache.delete(id);
37
+ }
38
+ //#endregion
39
+ //#region src/react/markdown.tsx
40
+ function withHitchFileProtocol() {
41
+ const sanitize = defaultRehypePlugins.sanitize;
42
+ if (!Array.isArray(sanitize)) throw new Error("streamdown's default sanitize plugin is no longer [plugin, schema].");
43
+ const [plugin, schema] = sanitize;
44
+ if (!Array.isArray(schema.protocols?.src)) throw new Error("streamdown's sanitize schema no longer lists src protocols.");
45
+ return Object.values({
46
+ ...defaultRehypePlugins,
47
+ sanitize: [plugin, {
48
+ ...schema,
49
+ protocols: {
50
+ ...schema.protocols,
51
+ src: [...schema.protocols.src, "hitch-file"]
52
+ }
53
+ }]
54
+ });
55
+ }
56
+ const rehypePlugins = withHitchFileProtocol();
57
+ function HitchImage({ src, alt, files, node: _node, ...props }) {
58
+ const id = typeof src === "string" && src.startsWith("hitch-file://") ? src.slice(13) : void 0;
59
+ const [state, setState] = useState({ id });
60
+ useEffect(() => {
61
+ if (id === void 0 || files === void 0) return;
62
+ let active = true;
63
+ cachedDownloadUrl(files, id).then((url) => {
64
+ if (active) setState({
65
+ id,
66
+ url
67
+ });
68
+ }, (error) => {
69
+ invalidateDownloadUrl(id);
70
+ console.error(`Failed to download Hitch file "${id}".`, error);
71
+ if (active) setState({
72
+ id,
73
+ error
74
+ });
75
+ });
76
+ return () => {
77
+ active = false;
78
+ };
79
+ }, [files, id]);
80
+ if (id === void 0) return /* @__PURE__ */ jsx("img", {
81
+ src,
82
+ alt,
83
+ ...props
84
+ });
85
+ if (files === void 0 || state.error !== void 0) return /* @__PURE__ */ jsx("span", {
86
+ className: "text-muted-foreground text-sm",
87
+ children: alt ?? "Image unavailable"
88
+ });
89
+ if (state.id !== id || state.url === void 0) return /* @__PURE__ */ jsx("span", {
90
+ className: "inline-block min-h-16 min-w-32 animate-pulse rounded-md bg-muted p-3 text-muted-foreground text-sm",
91
+ role: "img",
92
+ "aria-label": alt ?? "Loading image",
93
+ children: alt ?? "Loading image"
94
+ });
95
+ return /* @__PURE__ */ jsx("img", {
96
+ src: state.url,
97
+ alt,
98
+ ...props,
99
+ onError: () => {
100
+ invalidateDownloadUrl(id);
101
+ setState({
102
+ id,
103
+ error: /* @__PURE__ */ new Error("image failed to load")
104
+ });
105
+ }
106
+ });
107
+ }
108
+ function Markdown({ children, files, className }) {
109
+ const components = useMemo(() => ({ img: (props) => /* @__PURE__ */ jsx(HitchImage, {
110
+ ...props,
111
+ files
112
+ }) }), [files]);
113
+ return /* @__PURE__ */ jsx(Streamdown, {
114
+ className,
115
+ components,
116
+ rehypePlugins,
117
+ urlTransform: (url, key, node) => url.startsWith("hitch-file://") ? url : defaultUrlTransform(url, key, node),
118
+ children
119
+ });
120
+ }
121
+ //#endregion
122
+ //#region src/react/markdown-editor-extensions.tsx
123
+ function sanitizeAltText(text) {
124
+ return text.replaceAll(/[[\]()\\]/g, "").replaceAll(/\s+/g, " ");
125
+ }
126
+ function tableHasBlockCells(node) {
127
+ return (node.content ?? []).some((row) => (row.content ?? []).some((cell) => cell.content?.length !== 1 || cell.content[0]?.type !== "paragraph"));
128
+ }
129
+ function hitchImageNode(files) {
130
+ return function HitchImageNode({ node }) {
131
+ const src = String(node.attrs.src);
132
+ const alt = typeof node.attrs.alt === "string" ? node.attrs.alt : void 0;
133
+ const id = src.startsWith("hitch-file://") ? src.slice(13) : void 0;
134
+ const [state, setState] = useState({ id });
135
+ useEffect(() => {
136
+ if (id === void 0 || files === void 0) return;
137
+ let active = true;
138
+ cachedDownloadUrl(files, id).then((url) => {
139
+ if (active) setState({
140
+ id,
141
+ url
142
+ });
143
+ }, (error) => {
144
+ invalidateDownloadUrl(id);
145
+ console.error(`Failed to download Hitch file "${id}".`, error);
146
+ if (active) setState({
147
+ id,
148
+ error
149
+ });
150
+ });
151
+ return () => {
152
+ active = false;
153
+ };
154
+ }, [files, id]);
155
+ if (id === void 0) return /* @__PURE__ */ jsx(NodeViewWrapper, {
156
+ as: "span",
157
+ children: /* @__PURE__ */ jsx("img", {
158
+ src,
159
+ alt,
160
+ className: "max-w-full rounded-lg",
161
+ "data-drag-handle": true
162
+ })
163
+ });
164
+ if (files === void 0 || state.error !== void 0) return /* @__PURE__ */ jsx(NodeViewWrapper, {
165
+ as: "span",
166
+ className: "text-muted-foreground text-sm",
167
+ children: alt ?? "Image unavailable"
168
+ });
169
+ if (state.id !== id || state.url === void 0) return /* @__PURE__ */ jsx(NodeViewWrapper, {
170
+ as: "span",
171
+ className: "inline-block min-h-16 min-w-32 animate-pulse rounded-md bg-muted p-3 text-muted-foreground text-sm",
172
+ role: "img",
173
+ "aria-label": alt ?? "Loading image",
174
+ children: alt ?? "Loading image"
175
+ });
176
+ return /* @__PURE__ */ jsx(NodeViewWrapper, {
177
+ as: "span",
178
+ children: /* @__PURE__ */ jsx("img", {
179
+ src: state.url,
180
+ alt,
181
+ className: "max-w-full rounded-lg",
182
+ "data-drag-handle": true,
183
+ onError: () => {
184
+ invalidateDownloadUrl(id);
185
+ setState({
186
+ id,
187
+ error: /* @__PURE__ */ new Error("image failed to load")
188
+ });
189
+ }
190
+ })
191
+ });
192
+ };
193
+ }
194
+ function markdownEditorExtensions(files, placeholder = "") {
195
+ const extensions = [];
196
+ extensions.push(StarterKit.configure({ link: { openOnClick: false } }), Table$1.extend({ renderMarkdown(node, helpers) {
197
+ if (!tableHasBlockCells(node)) return renderTableToMarkdown(node, helpers);
198
+ return `\n${generateHTML({
199
+ type: "doc",
200
+ content: [node]
201
+ }, extensions)}\n`;
202
+ } }), TableRow, TableHeader, TableCell, TaskList, TaskItem.configure({ nested: true }), Image.extend({ addNodeView() {
203
+ return ReactNodeViewRenderer(hitchImageNode(files));
204
+ } }), Markdown$1, Placeholder.configure({
205
+ placeholder,
206
+ showOnlyWhenEditable: false
207
+ }));
208
+ return extensions;
209
+ }
210
+ //#endregion
211
+ //#region src/react/markdown-editor.tsx
212
+ const buttonClassName = "inline-flex size-7 shrink-0 items-center justify-center rounded-2xl border border-transparent text-muted-foreground outline-none transition-all select-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0";
213
+ const editorClassName = "min-h-32 w-full px-2.5 py-2 text-base md:text-sm outline-none whitespace-pre-wrap space-y-4 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_h1]:mt-6 [&_h1]:mb-2 [&_h1]:font-semibold [&_h1]:text-3xl [&_h2]:mt-6 [&_h2]:mb-2 [&_h2]:font-semibold [&_h2]:text-2xl [&_h3]:mt-6 [&_h3]:mb-2 [&_h3]:font-semibold [&_h3]:text-xl [&_h4]:mt-6 [&_h4]:mb-2 [&_h4]:font-semibold [&_h4]:text-lg [&_h5]:mt-6 [&_h5]:mb-2 [&_h5]:font-semibold [&_h5]:text-base [&_h6]:mt-6 [&_h6]:mb-2 [&_h6]:font-semibold [&_h6]:text-sm [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_blockquote]:my-4 [&_blockquote]:border-l-4 [&_blockquote]:border-muted-foreground/30 [&_blockquote]:pl-4 [&_blockquote]:text-muted-foreground [&_blockquote]:italic [&_code]:rounded [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-sm [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-background [&_pre]:p-4 [&_pre]:text-sm [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_a]:font-medium [&_a]:text-primary [&_a]:underline [&_a]:wrap-anywhere [&_hr]:my-4 [&_hr]:border-border [&_table]:my-4 [&_table]:w-full [&_table]:border-collapse [&_th]:border [&_th]:border-border [&_th]:px-4 [&_th]:py-2 [&_th]:text-left [&_th]:font-semibold [&_th]:text-sm [&_td]:border [&_td]:border-border [&_td]:px-4 [&_td]:py-2 [&_td]:text-sm [&_ul[data-type=taskList]]:list-none [&_ul[data-type=taskList]]:pl-0 [&_li[data-type=taskItem]]:flex [&_li[data-type=taskItem]]:items-start [&_li[data-type=taskItem]]:gap-2 [&_li[data-type=taskItem]>div]:flex-1 [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)] [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:h-0 [&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:text-muted-foreground";
214
+ function findImage(view, src) {
215
+ let found;
216
+ view.state.doc.descendants((node, pos) => {
217
+ if (found !== void 0) return false;
218
+ if (node.type.name === "image" && node.attrs.src === src) found = {
219
+ pos,
220
+ size: node.nodeSize
221
+ };
222
+ return found === void 0;
223
+ });
224
+ return found;
225
+ }
226
+ function MarkdownEditor({ value, onChange, files, id, placeholder, required, minLength, maxLength, disabled, className, onUploadError }) {
227
+ const validationRef = useRef(null);
228
+ const lastEmittedRef = useRef(value);
229
+ const filesRef = useRef(files);
230
+ const uploadErrorRef = useRef(onUploadError);
231
+ const pendingUploads = useRef(0);
232
+ useEffect(() => {
233
+ filesRef.current = files;
234
+ uploadErrorRef.current = onUploadError;
235
+ });
236
+ const hasFiles = files !== void 0;
237
+ const stableFiles = useMemo(() => {
238
+ if (!hasFiles) return void 0;
239
+ const current = () => {
240
+ const filesNow = filesRef.current;
241
+ if (filesNow === void 0) throw new Error("files was removed while the editor used it.");
242
+ return filesNow;
243
+ };
244
+ return {
245
+ upload: (file) => current().upload(file),
246
+ download: (fileId) => current().download(fileId)
247
+ };
248
+ }, [hasFiles]);
249
+ const extensions = useMemo(() => markdownEditorExtensions(stableFiles, placeholder), [stableFiles, placeholder]);
250
+ const applyValidity = () => {
251
+ validationRef.current?.setCustomValidity(pendingUploads.current > 0 ? "Wait for the image upload to finish." : minLength !== void 0 && value.length > 0 && value.length < minLength ? `Use at least ${minLength} characters.` : maxLength !== void 0 && value.length > maxLength ? `Use no more than ${maxLength} characters.` : "");
252
+ };
253
+ useEffect(applyValidity);
254
+ const markUploadPending = (delta) => {
255
+ pendingUploads.current += delta;
256
+ applyValidity();
257
+ };
258
+ const editor = useEditor({
259
+ extensions,
260
+ content: value,
261
+ contentType: "markdown",
262
+ editable: !disabled,
263
+ editorProps: {
264
+ attributes: { class: editorClassName },
265
+ handlePaste: (view, event) => {
266
+ const imageFiles = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
267
+ if (filesRef.current === void 0 || imageFiles.length === 0) return false;
268
+ uploadImages(view, imageFiles, view.state.selection.from);
269
+ return true;
270
+ },
271
+ handleDrop: (view, event) => {
272
+ const imageFiles = Array.from(event.dataTransfer?.files ?? []).filter((file) => file.type.startsWith("image/"));
273
+ if (filesRef.current === void 0 || imageFiles.length === 0) return false;
274
+ const position = view.posAtCoords({
275
+ left: event.clientX,
276
+ top: event.clientY
277
+ })?.pos;
278
+ if (position === void 0) return false;
279
+ uploadImages(view, imageFiles, position);
280
+ return true;
281
+ }
282
+ },
283
+ onUpdate: ({ editor: updatedEditor }) => {
284
+ const markdown = updatedEditor.getMarkdown();
285
+ lastEmittedRef.current = markdown;
286
+ onChange(markdown);
287
+ }
288
+ }, [extensions]);
289
+ async function uploadImages(view, imageFiles, position) {
290
+ const uploadFiles = filesRef.current;
291
+ if (uploadFiles === void 0) return;
292
+ markUploadPending(1);
293
+ try {
294
+ const pending = imageFiles.map((file) => ({
295
+ file,
296
+ url: URL.createObjectURL(file)
297
+ }));
298
+ const transaction = view.state.tr;
299
+ let insertPosition = position;
300
+ for (const { file, url } of pending) {
301
+ const node = view.state.schema.nodes.image.create({
302
+ src: url,
303
+ alt: sanitizeAltText(file.name)
304
+ });
305
+ transaction.insert(insertPosition, node);
306
+ insertPosition += node.nodeSize;
307
+ }
308
+ view.dispatch(transaction);
309
+ for (const { file, url } of pending) try {
310
+ const uploaded = await uploadFiles.upload(file);
311
+ seedDownloadUrl(uploaded.id, uploaded.downloadUrl);
312
+ const image = findImage(view, url);
313
+ if (image !== void 0) view.dispatch(view.state.tr.setNodeMarkup(image.pos, void 0, {
314
+ ...view.state.doc.nodeAt(image.pos)?.attrs,
315
+ src: `${HITCH_FILE_PREFIX}${uploaded.id}`
316
+ }));
317
+ } catch (error) {
318
+ const image = findImage(view, url);
319
+ if (image !== void 0) view.dispatch(view.state.tr.delete(image.pos, image.pos + image.size));
320
+ if (uploadErrorRef.current !== void 0) uploadErrorRef.current(error, file);
321
+ else console.error(`Upload of "${file.name}" failed.`, error);
322
+ } finally {
323
+ URL.revokeObjectURL(url);
324
+ }
325
+ } finally {
326
+ markUploadPending(-1);
327
+ }
328
+ }
329
+ useEffect(() => {
330
+ if (editor !== null && value !== lastEmittedRef.current && value !== editor.getMarkdown()) editor.commands.setContent(value, {
331
+ contentType: "markdown",
332
+ emitUpdate: false
333
+ });
334
+ }, [editor, value]);
335
+ useEffect(() => editor?.setEditable(!disabled, false), [disabled, editor]);
336
+ const active = useEditorState({
337
+ editor,
338
+ selector: ({ editor: current }) => ({
339
+ heading: current?.isActive("heading") ?? false,
340
+ bold: current?.isActive("bold") ?? false,
341
+ italic: current?.isActive("italic") ?? false,
342
+ strike: current?.isActive("strike") ?? false,
343
+ code: current?.isActive("code") ?? false,
344
+ link: current?.isActive("link") ?? false,
345
+ bulletList: current?.isActive("bulletList") ?? false,
346
+ orderedList: current?.isActive("orderedList") ?? false,
347
+ taskList: current?.isActive("taskList") ?? false,
348
+ blockquote: current?.isActive("blockquote") ?? false,
349
+ table: current?.isActive("table") ?? false
350
+ })
351
+ });
352
+ const cycleHeading = () => {
353
+ if (editor === null) return;
354
+ const level = [
355
+ 1,
356
+ 2,
357
+ 3
358
+ ].find((item) => editor.isActive("heading", { level: item }));
359
+ if (level === void 0) editor.chain().focus().toggleHeading({ level: 1 }).run();
360
+ else if (level < 3) editor.chain().focus().toggleHeading({ level: level + 1 }).run();
361
+ else editor.chain().focus().setParagraph().run();
362
+ };
363
+ const toggleLink = () => {
364
+ if (editor === null) return;
365
+ if (editor.isActive("link")) return void editor.chain().focus().unsetLink().run();
366
+ const url = window.prompt("Link URL");
367
+ if (!url) return;
368
+ if (editor.state.selection.empty) editor.chain().focus().insertContent({
369
+ type: "text",
370
+ text: url,
371
+ marks: [{
372
+ type: "link",
373
+ attrs: { href: url }
374
+ }]
375
+ }).run();
376
+ else editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
377
+ };
378
+ const items = [
379
+ {
380
+ title: "Heading",
381
+ active: active?.heading,
382
+ icon: Heading,
383
+ command: cycleHeading
384
+ },
385
+ {
386
+ title: "Bold",
387
+ active: active?.bold,
388
+ icon: Bold,
389
+ command: () => editor?.chain().focus().toggleBold().run()
390
+ },
391
+ {
392
+ title: "Italic",
393
+ active: active?.italic,
394
+ icon: Italic,
395
+ command: () => editor?.chain().focus().toggleItalic().run()
396
+ },
397
+ {
398
+ title: "Strikethrough",
399
+ active: active?.strike,
400
+ icon: Strikethrough,
401
+ command: () => editor?.chain().focus().toggleStrike().run()
402
+ },
403
+ {
404
+ title: "Inline code",
405
+ active: active?.code,
406
+ icon: Code,
407
+ command: () => editor?.chain().focus().toggleCode().run()
408
+ },
409
+ {
410
+ title: "Link",
411
+ active: active?.link,
412
+ icon: Link,
413
+ command: toggleLink
414
+ },
415
+ {
416
+ title: "Bulleted list",
417
+ active: active?.bulletList,
418
+ icon: List,
419
+ command: () => editor?.chain().focus().toggleBulletList().run()
420
+ },
421
+ {
422
+ title: "Numbered list",
423
+ active: active?.orderedList,
424
+ icon: ListOrdered,
425
+ command: () => editor?.chain().focus().toggleOrderedList().run()
426
+ },
427
+ {
428
+ title: "Task list",
429
+ active: active?.taskList,
430
+ icon: ListTodo,
431
+ command: () => editor?.chain().focus().toggleTaskList().run()
432
+ },
433
+ {
434
+ title: "Quote",
435
+ active: active?.blockquote,
436
+ icon: Quote,
437
+ command: () => editor?.chain().focus().toggleBlockquote().run()
438
+ },
439
+ {
440
+ title: "Table",
441
+ active: active?.table,
442
+ icon: Table,
443
+ command: () => editor?.chain().focus().insertTable({
444
+ rows: 3,
445
+ cols: 3,
446
+ withHeaderRow: true
447
+ }).run()
448
+ }
449
+ ];
450
+ const tableItems = [
451
+ {
452
+ title: "Add row below",
453
+ icon: BetweenHorizontalEnd,
454
+ command: () => editor?.chain().focus().addRowAfter().run()
455
+ },
456
+ {
457
+ title: "Add column right",
458
+ icon: BetweenVerticalEnd,
459
+ command: () => editor?.chain().focus().addColumnAfter().run()
460
+ },
461
+ {
462
+ title: "Delete row",
463
+ icon: ListMinus,
464
+ command: () => editor?.chain().focus().deleteRow().run()
465
+ },
466
+ {
467
+ title: "Delete column",
468
+ icon: ListMinus,
469
+ iconClassName: "rotate-90",
470
+ command: () => editor?.chain().focus().deleteColumn().run()
471
+ },
472
+ {
473
+ title: "Delete table",
474
+ icon: Trash2,
475
+ command: () => editor?.chain().focus().deleteTable().run()
476
+ }
477
+ ];
478
+ return /* @__PURE__ */ jsxs("div", {
479
+ className: twMerge("overflow-hidden rounded-2xl border border-input bg-background", className),
480
+ children: [
481
+ /* @__PURE__ */ jsxs(Toolbar, {
482
+ className: "flex flex-wrap gap-1 border-b border-input p-1",
483
+ "aria-label": "Markdown formatting",
484
+ children: [items.map(({ title, active: isActive, icon: Icon, command }) => /* @__PURE__ */ jsx(Button, {
485
+ className: twMerge(buttonClassName, isActive && "bg-muted text-foreground"),
486
+ "aria-label": title,
487
+ isDisabled: disabled,
488
+ onPress: command,
489
+ children: /* @__PURE__ */ jsx(Icon, {})
490
+ }, title)), active?.table && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
491
+ "aria-hidden": true,
492
+ className: "mx-1 w-px self-stretch bg-border"
493
+ }), tableItems.map(({ title, icon: Icon, iconClassName, command }) => /* @__PURE__ */ jsx(Button, {
494
+ className: buttonClassName,
495
+ "aria-label": title,
496
+ isDisabled: disabled,
497
+ onPress: command,
498
+ children: /* @__PURE__ */ jsx(Icon, { className: iconClassName })
499
+ }, title))] })]
500
+ }),
501
+ /* @__PURE__ */ jsx(EditorContent, { editor }),
502
+ /* @__PURE__ */ jsx("textarea", {
503
+ ref: validationRef,
504
+ id,
505
+ className: "sr-only",
506
+ tabIndex: -1,
507
+ value,
508
+ required,
509
+ disabled,
510
+ onChange: () => void 0,
511
+ onFocus: () => editor?.commands.focus()
512
+ })
513
+ ]
514
+ });
515
+ }
516
+ //#endregion
517
+ export { Markdown, MarkdownEditor };
@@ -0,0 +1,46 @@
1
+ import { a as createWorkflow, i as WorkflowStep, n as WorkflowDefinition, r as WorkflowDuration } from "./workflow-DurM6Fwx.js";
2
+ import { _ as RegisteredWorkflows, f as RegisteredContract, g as RegisteredVariables, p as RegisteredManifest } from "./manifest-BQCAxATS.js";
3
+ import { t as NarrowContract } from "./contract-DMrpbaLJ.js";
4
+ import { t as WithBatchRecorder } from "./batch-Qa4B4-Yp.js";
5
+ import { o as WithFilesUpload } from "./upload-YbRKEqR6.js";
6
+ import { ContractRouterClient } from "@orpc/contract";
7
+ //#region src/server/workflows-client.d.ts
8
+ type WorkflowRunStatus<Result> = {
9
+ id: string;
10
+ workflow: string;
11
+ status: "in_progress" | "sleeping" | "success" | "error" | "terminated";
12
+ result: Result | null;
13
+ error: string | null;
14
+ sleepUntil: string | null;
15
+ startedAt: string | null;
16
+ finishedAt: string | null;
17
+ };
18
+ type WorkflowRunSummary = Omit<WorkflowRunStatus<unknown>, "result">;
19
+ type WorkflowsClient<Workflows> = { [Code in keyof Workflows]: Workflows[Code] extends WorkflowDefinition<infer Payload, infer Result> ? {
20
+ start(payload: Payload): Promise<{
21
+ id: string;
22
+ }>;
23
+ get(id: string): Promise<WorkflowRunStatus<Result>>;
24
+ list(options?: {
25
+ status?: WorkflowRunStatus<Result>["status"] | "open";
26
+ limit?: number;
27
+ }): Promise<WorkflowRunSummary[]>;
28
+ } : never; };
29
+ //#endregion
30
+ //#region src/server/env.d.ts
31
+ type AppletEnv<Variables extends AppletManifestVariables = RegisteredVariables> = { readonly [Key in keyof Variables]: Variables[Key] extends {
32
+ optional: true;
33
+ } ? string | undefined : string; };
34
+ type AppletManifestVariables = Readonly<Record<string, {
35
+ optional?: boolean;
36
+ }>>;
37
+ declare const env: AppletEnv;
38
+ //#endregion
39
+ //#region src/author/server.d.ts
40
+ type AuthorContract = NarrowContract<RegisteredContract, RegisteredManifest>;
41
+ type AuthorHitchClient = WithBatchRecorder<ContractRouterClient<AuthorContract>>;
42
+ declare const hitch: WithFilesUpload<AuthorHitchClient> & {
43
+ workflows: WorkflowsClient<RegisteredWorkflows>;
44
+ };
45
+ //#endregion
46
+ export { type AppletEnv, type WorkflowDefinition, type WorkflowDuration, type WorkflowStep, createWorkflow, env, hitch };
package/dist/server.js ADDED
@@ -0,0 +1,104 @@
1
+ import { n as withFilesUpload, r as getRegisteredHitchContract } from "./upload-DRbVhuDm.js";
2
+ import { a as getRequestContext, i as getIdentityBearer, n as createWorkflow, s as tryGetRequestContext } from "./workflow-CTo2kZF1.js";
3
+ import { n as withBatchRecorder, t as createLazyClientProxy } from "./lazy-client-x0DCI98S.js";
4
+ import { createORPCClient } from "@orpc/client";
5
+ import { OpenAPILink } from "@orpc/openapi-client/fetch";
6
+ //#region src/hitch/server.ts
7
+ const DEFAULT_API_URL = "https://app.hitch42.com";
8
+ const resolveTransport = () => {
9
+ const context = tryGetRequestContext();
10
+ if (context !== void 0) return {
11
+ baseUrl: context.api.baseUrl,
12
+ fetch: context.api.fetch,
13
+ authorization: `Bearer ${getIdentityBearer(context.identity)}`
14
+ };
15
+ const apiKey = process.env.HITCH_API_KEY;
16
+ if (apiKey !== void 0 && apiKey !== "") {
17
+ const apiUrl = process.env.HITCH_API_URL;
18
+ return {
19
+ baseUrl: apiUrl !== void 0 && apiUrl !== "" ? apiUrl : DEFAULT_API_URL,
20
+ fetch: (request, init) => globalThis.fetch(request, init),
21
+ authorization: `Bearer ${apiKey}`
22
+ };
23
+ }
24
+ throw new Error("Hitch request context is unavailable outside a request. Set HITCH_API_KEY to call the API with an API key.");
25
+ };
26
+ function createHitchClient(contract) {
27
+ return createORPCClient(new OpenAPILink(contract, {
28
+ url: () => resolveTransport().baseUrl,
29
+ fetch: (request, init) => resolveTransport().fetch(request, init),
30
+ headers: () => ({ authorization: resolveTransport().authorization })
31
+ }));
32
+ }
33
+ //#endregion
34
+ //#region src/server/workflows-client.ts
35
+ function mapWorkflowRun(data) {
36
+ const { sleep_until: sleepUntil, started_at: startedAt, finished_at: finishedAt, ...run } = data;
37
+ return {
38
+ ...run,
39
+ sleepUntil,
40
+ startedAt,
41
+ finishedAt
42
+ };
43
+ }
44
+ async function getResponseData(response, action) {
45
+ const body = await response.json();
46
+ if (!response.ok) {
47
+ const { code, message } = body;
48
+ throw new Error(`Workflow ${action} failed (${response.status}): ${code}: ${message}`);
49
+ }
50
+ return body.data;
51
+ }
52
+ function createWorkflowsClient() {
53
+ return new Proxy({}, { get: (_target, property) => {
54
+ const code = String(property);
55
+ return {
56
+ async start(payload) {
57
+ const context = getRequestContext();
58
+ return { id: (await getResponseData(await context.api.fetch(`${context.api.baseUrl}/v1/applets/${encodeURIComponent(context.appletCode)}/workflows/${encodeURIComponent(code)}/runs`, {
59
+ method: "POST",
60
+ headers: {
61
+ authorization: `Bearer ${getIdentityBearer(context.identity)}`,
62
+ "content-type": "application/json"
63
+ },
64
+ body: JSON.stringify({ payload })
65
+ }), "start")).id };
66
+ },
67
+ async get(id) {
68
+ const context = getRequestContext();
69
+ return mapWorkflowRun(await getResponseData(await context.api.fetch(`${context.api.baseUrl}/v1/applets/${encodeURIComponent(context.appletCode)}/workflows/${encodeURIComponent(code)}/runs/${encodeURIComponent(id)}`, {
70
+ method: "GET",
71
+ headers: { authorization: `Bearer ${getIdentityBearer(context.identity)}` }
72
+ }), "get"));
73
+ },
74
+ async list(options) {
75
+ const context = getRequestContext();
76
+ const query = new URLSearchParams({ workflow: code });
77
+ if (options?.status !== void 0) query.set("status", options.status);
78
+ if (options?.limit !== void 0) query.set("limit", String(options.limit));
79
+ return (await getResponseData(await context.api.fetch(`${context.api.baseUrl}/v1/applets/${encodeURIComponent(context.appletCode)}/workflow-runs?${query}`, {
80
+ method: "GET",
81
+ headers: { authorization: `Bearer ${getIdentityBearer(context.identity)}` }
82
+ }), "list")).map(mapWorkflowRun);
83
+ }
84
+ };
85
+ } });
86
+ }
87
+ //#endregion
88
+ //#region src/server/env.ts
89
+ const env = process.env;
90
+ //#endregion
91
+ //#region src/author/server.ts
92
+ function buildHitchClient() {
93
+ const contract = getRegisteredHitchContract();
94
+ if (contract === void 0) throw new Error("`hitch` is unavailable: run `applet generate` first. The framework imports your .hitch automatically at build; in plain node import your .hitch once before calling hitch.");
95
+ return withBatchRecorder(createHitchClient(contract), contract);
96
+ }
97
+ const hitchClient = createLazyClientProxy(buildHitchClient);
98
+ const filesClient = withFilesUpload(hitchClient, () => hitchClient.files);
99
+ const workflows = createWorkflowsClient();
100
+ const hitch = new Proxy(filesClient, { get(target, property, receiver) {
101
+ return property === "workflows" ? workflows : Reflect.get(target, property, receiver);
102
+ } });
103
+ //#endregion
104
+ export { createWorkflow, env, hitch };