@aprovan/patchwork 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.eslintrc.json +22 -0
  2. package/.github/workflows/publish.yml +41 -0
  3. package/.prettierignore +17 -0
  4. package/LICENSE +373 -0
  5. package/README.md +15 -0
  6. package/apps/chat/.utcp_config.json +14 -0
  7. package/apps/chat/.working/widgets/27060b91-a2a5-4272-b243-6eb904bd4070/main.tsx +107 -0
  8. package/apps/chat/index.html +17 -0
  9. package/apps/chat/node_modules/.bin/autoprefixer +17 -0
  10. package/apps/chat/node_modules/.bin/browserslist +17 -0
  11. package/apps/chat/node_modules/.bin/conc +17 -0
  12. package/apps/chat/node_modules/.bin/concurrently +17 -0
  13. package/apps/chat/node_modules/.bin/copilot-proxy +17 -0
  14. package/apps/chat/node_modules/.bin/jiti +17 -0
  15. package/apps/chat/node_modules/.bin/tailwind +17 -0
  16. package/apps/chat/node_modules/.bin/tailwindcss +17 -0
  17. package/apps/chat/node_modules/.bin/tsc +17 -0
  18. package/apps/chat/node_modules/.bin/tsserver +17 -0
  19. package/apps/chat/node_modules/.bin/tsx +17 -0
  20. package/apps/chat/node_modules/.bin/vite +17 -0
  21. package/apps/chat/package.json +55 -0
  22. package/apps/chat/postcss.config.js +6 -0
  23. package/apps/chat/src/App.tsx +7 -0
  24. package/apps/chat/src/components/ui/avatar.tsx +48 -0
  25. package/apps/chat/src/components/ui/badge.tsx +36 -0
  26. package/apps/chat/src/components/ui/button.tsx +56 -0
  27. package/apps/chat/src/components/ui/card.tsx +86 -0
  28. package/apps/chat/src/components/ui/collapsible.tsx +9 -0
  29. package/apps/chat/src/components/ui/dialog.tsx +60 -0
  30. package/apps/chat/src/components/ui/input.tsx +25 -0
  31. package/apps/chat/src/components/ui/scroll-area.tsx +46 -0
  32. package/apps/chat/src/index.css +190 -0
  33. package/apps/chat/src/lib/utils.ts +6 -0
  34. package/apps/chat/src/main.tsx +10 -0
  35. package/apps/chat/src/pages/ChatPage.tsx +460 -0
  36. package/apps/chat/tailwind.config.js +71 -0
  37. package/apps/chat/tsconfig.json +25 -0
  38. package/apps/chat/vite.config.ts +26 -0
  39. package/package.json +35 -0
  40. package/packages/bobbin/node_modules/.bin/esbuild +14 -0
  41. package/packages/bobbin/node_modules/.bin/jiti +17 -0
  42. package/packages/bobbin/node_modules/.bin/tsc +17 -0
  43. package/packages/bobbin/node_modules/.bin/tsserver +17 -0
  44. package/packages/bobbin/node_modules/.bin/tsup +17 -0
  45. package/packages/bobbin/node_modules/.bin/tsup-node +17 -0
  46. package/packages/bobbin/node_modules/.bin/tsx +17 -0
  47. package/packages/bobbin/package.json +30 -0
  48. package/packages/bobbin/src/Bobbin.tsx +89 -0
  49. package/packages/bobbin/src/components/EditPanel/EditPanel.tsx +376 -0
  50. package/packages/bobbin/src/components/EditPanel/controls/ColorPicker.tsx +138 -0
  51. package/packages/bobbin/src/components/EditPanel/controls/QuickSelectDropdown.tsx +142 -0
  52. package/packages/bobbin/src/components/EditPanel/controls/SliderInput.tsx +94 -0
  53. package/packages/bobbin/src/components/EditPanel/controls/SpacingControl.tsx +285 -0
  54. package/packages/bobbin/src/components/EditPanel/controls/ToggleGroup.tsx +37 -0
  55. package/packages/bobbin/src/components/EditPanel/controls/TokenDropdown.tsx +33 -0
  56. package/packages/bobbin/src/components/EditPanel/sections/AnnotationSection.tsx +136 -0
  57. package/packages/bobbin/src/components/EditPanel/sections/BackgroundSection.tsx +79 -0
  58. package/packages/bobbin/src/components/EditPanel/sections/EffectsSection.tsx +85 -0
  59. package/packages/bobbin/src/components/EditPanel/sections/LayoutSection.tsx +224 -0
  60. package/packages/bobbin/src/components/EditPanel/sections/SectionWrapper.tsx +57 -0
  61. package/packages/bobbin/src/components/EditPanel/sections/SizeSection.tsx +166 -0
  62. package/packages/bobbin/src/components/EditPanel/sections/SpacingSection.tsx +69 -0
  63. package/packages/bobbin/src/components/EditPanel/sections/TypographySection.tsx +148 -0
  64. package/packages/bobbin/src/components/Inspector/Inspector.tsx +221 -0
  65. package/packages/bobbin/src/components/Overlay/ControlHandles.tsx +572 -0
  66. package/packages/bobbin/src/components/Overlay/MarginPaddingOverlay.tsx +229 -0
  67. package/packages/bobbin/src/components/Overlay/SelectionOverlay.tsx +73 -0
  68. package/packages/bobbin/src/components/Pill/Pill.tsx +155 -0
  69. package/packages/bobbin/src/components/ThemeToggle/ThemeToggle.tsx +72 -0
  70. package/packages/bobbin/src/core/changeSerializer.ts +139 -0
  71. package/packages/bobbin/src/core/useBobbin.ts +399 -0
  72. package/packages/bobbin/src/core/useChangeTracker.ts +186 -0
  73. package/packages/bobbin/src/core/useClipboard.ts +21 -0
  74. package/packages/bobbin/src/core/useElementSelection.ts +146 -0
  75. package/packages/bobbin/src/index.ts +46 -0
  76. package/packages/bobbin/src/tokens/borders.ts +19 -0
  77. package/packages/bobbin/src/tokens/colors.ts +150 -0
  78. package/packages/bobbin/src/tokens/index.ts +37 -0
  79. package/packages/bobbin/src/tokens/shadows.ts +10 -0
  80. package/packages/bobbin/src/tokens/spacing.ts +37 -0
  81. package/packages/bobbin/src/tokens/typography.ts +51 -0
  82. package/packages/bobbin/src/types.ts +157 -0
  83. package/packages/bobbin/src/utils/animation.ts +40 -0
  84. package/packages/bobbin/src/utils/dom.ts +36 -0
  85. package/packages/bobbin/src/utils/selectors.ts +76 -0
  86. package/packages/bobbin/tsconfig.json +10 -0
  87. package/packages/bobbin/tsup.config.ts +10 -0
  88. package/packages/compiler/node_modules/.bin/esbuild +17 -0
  89. package/packages/compiler/node_modules/.bin/jiti +17 -0
  90. package/packages/compiler/node_modules/.bin/tsc +17 -0
  91. package/packages/compiler/node_modules/.bin/tsserver +17 -0
  92. package/packages/compiler/node_modules/.bin/tsup +17 -0
  93. package/packages/compiler/node_modules/.bin/tsup-node +17 -0
  94. package/packages/compiler/node_modules/.bin/tsx +17 -0
  95. package/packages/compiler/package.json +38 -0
  96. package/packages/compiler/src/compiler.ts +258 -0
  97. package/packages/compiler/src/images/index.ts +13 -0
  98. package/packages/compiler/src/images/loader.ts +234 -0
  99. package/packages/compiler/src/images/registry.ts +112 -0
  100. package/packages/compiler/src/index.ts +141 -0
  101. package/packages/compiler/src/mount/bridge.ts +399 -0
  102. package/packages/compiler/src/mount/embedded.ts +306 -0
  103. package/packages/compiler/src/mount/iframe.ts +433 -0
  104. package/packages/compiler/src/mount/index.ts +18 -0
  105. package/packages/compiler/src/schemas.ts +169 -0
  106. package/packages/compiler/src/transforms/cdn.ts +411 -0
  107. package/packages/compiler/src/transforms/index.ts +4 -0
  108. package/packages/compiler/src/transforms/vfs.ts +138 -0
  109. package/packages/compiler/src/types.ts +233 -0
  110. package/packages/compiler/src/vfs/backends/indexeddb.ts +66 -0
  111. package/packages/compiler/src/vfs/backends/local-fs.ts +41 -0
  112. package/packages/compiler/src/vfs/backends/s3.ts +60 -0
  113. package/packages/compiler/src/vfs/index.ts +11 -0
  114. package/packages/compiler/src/vfs/project.ts +56 -0
  115. package/packages/compiler/src/vfs/store.ts +53 -0
  116. package/packages/compiler/src/vfs/types.ts +20 -0
  117. package/packages/compiler/tsconfig.json +8 -0
  118. package/packages/compiler/tsup.config.ts +14 -0
  119. package/packages/editor/node_modules/.bin/jiti +17 -0
  120. package/packages/editor/node_modules/.bin/tsc +17 -0
  121. package/packages/editor/node_modules/.bin/tsserver +17 -0
  122. package/packages/editor/node_modules/.bin/tsup +17 -0
  123. package/packages/editor/node_modules/.bin/tsup-node +17 -0
  124. package/packages/editor/node_modules/.bin/tsx +17 -0
  125. package/packages/editor/package.json +45 -0
  126. package/packages/editor/src/components/CodeBlockExtension.tsx +190 -0
  127. package/packages/editor/src/components/CodePreview.tsx +344 -0
  128. package/packages/editor/src/components/MarkdownEditor.tsx +270 -0
  129. package/packages/editor/src/components/ServicesInspector.tsx +118 -0
  130. package/packages/editor/src/components/edit/EditHistory.tsx +89 -0
  131. package/packages/editor/src/components/edit/EditModal.tsx +236 -0
  132. package/packages/editor/src/components/edit/FileTree.tsx +144 -0
  133. package/packages/editor/src/components/edit/api.ts +100 -0
  134. package/packages/editor/src/components/edit/index.ts +6 -0
  135. package/packages/editor/src/components/edit/types.ts +53 -0
  136. package/packages/editor/src/components/edit/useEditSession.ts +164 -0
  137. package/packages/editor/src/components/index.ts +5 -0
  138. package/packages/editor/src/index.ts +72 -0
  139. package/packages/editor/src/lib/code-extractor.ts +210 -0
  140. package/packages/editor/src/lib/diff.ts +308 -0
  141. package/packages/editor/src/lib/index.ts +4 -0
  142. package/packages/editor/src/lib/utils.ts +6 -0
  143. package/packages/editor/src/lib/vfs.ts +106 -0
  144. package/packages/editor/tsconfig.json +10 -0
  145. package/packages/editor/tsup.config.ts +10 -0
  146. package/packages/images/ink/node_modules/.bin/jiti +17 -0
  147. package/packages/images/ink/node_modules/.bin/tsc +17 -0
  148. package/packages/images/ink/node_modules/.bin/tsserver +17 -0
  149. package/packages/images/ink/node_modules/.bin/tsup +17 -0
  150. package/packages/images/ink/node_modules/.bin/tsup-node +17 -0
  151. package/packages/images/ink/node_modules/.bin/tsx +17 -0
  152. package/packages/images/ink/package.json +53 -0
  153. package/packages/images/ink/src/index.ts +48 -0
  154. package/packages/images/ink/src/runner.ts +331 -0
  155. package/packages/images/ink/src/setup.ts +123 -0
  156. package/packages/images/ink/tsconfig.json +10 -0
  157. package/packages/images/ink/tsup.config.ts +11 -0
  158. package/packages/images/shadcn/node_modules/.bin/jiti +17 -0
  159. package/packages/images/shadcn/node_modules/.bin/tsc +17 -0
  160. package/packages/images/shadcn/node_modules/.bin/tsserver +17 -0
  161. package/packages/images/shadcn/node_modules/.bin/tsup +17 -0
  162. package/packages/images/shadcn/node_modules/.bin/tsup-node +17 -0
  163. package/packages/images/shadcn/node_modules/.bin/tsx +17 -0
  164. package/packages/images/shadcn/package.json +82 -0
  165. package/packages/images/shadcn/src/html.ts +341 -0
  166. package/packages/images/shadcn/src/index.ts +37 -0
  167. package/packages/images/shadcn/src/setup.ts +287 -0
  168. package/packages/images/shadcn/tsconfig.json +9 -0
  169. package/packages/images/shadcn/tsup.config.ts +13 -0
  170. package/packages/images/vanilla/node_modules/.bin/jiti +17 -0
  171. package/packages/images/vanilla/node_modules/.bin/tsc +17 -0
  172. package/packages/images/vanilla/node_modules/.bin/tsserver +17 -0
  173. package/packages/images/vanilla/node_modules/.bin/tsup +17 -0
  174. package/packages/images/vanilla/node_modules/.bin/tsup-node +17 -0
  175. package/packages/images/vanilla/node_modules/.bin/tsx +17 -0
  176. package/packages/images/vanilla/package.json +35 -0
  177. package/packages/images/vanilla/src/index.ts +7 -0
  178. package/packages/images/vanilla/src/setup.ts +6 -0
  179. package/packages/images/vanilla/tsconfig.json +9 -0
  180. package/packages/images/vanilla/tsup.config.ts +10 -0
  181. package/packages/patchwork/node_modules/.bin/jiti +17 -0
  182. package/packages/patchwork/node_modules/.bin/tsc +17 -0
  183. package/packages/patchwork/node_modules/.bin/tsserver +17 -0
  184. package/packages/patchwork/node_modules/.bin/tsup +17 -0
  185. package/packages/patchwork/node_modules/.bin/tsup-node +17 -0
  186. package/packages/patchwork/node_modules/.bin/tsx +17 -0
  187. package/packages/patchwork/package.json +27 -0
  188. package/packages/patchwork/src/index.ts +15 -0
  189. package/packages/patchwork/src/services/index.ts +11 -0
  190. package/packages/patchwork/src/services/proxy.ts +213 -0
  191. package/packages/patchwork/src/services/types.ts +28 -0
  192. package/packages/patchwork/src/types.ts +116 -0
  193. package/packages/patchwork/tsconfig.json +8 -0
  194. package/packages/patchwork/tsup.config.ts +14 -0
  195. package/packages/stitchery/node_modules/.bin/jiti +17 -0
  196. package/packages/stitchery/node_modules/.bin/tsc +17 -0
  197. package/packages/stitchery/node_modules/.bin/tsserver +17 -0
  198. package/packages/stitchery/node_modules/.bin/tsup +17 -0
  199. package/packages/stitchery/node_modules/.bin/tsup-node +17 -0
  200. package/packages/stitchery/node_modules/.bin/tsx +17 -0
  201. package/packages/stitchery/package.json +40 -0
  202. package/packages/stitchery/src/cli.ts +116 -0
  203. package/packages/stitchery/src/index.ts +16 -0
  204. package/packages/stitchery/src/prompts.ts +326 -0
  205. package/packages/stitchery/src/server/index.ts +365 -0
  206. package/packages/stitchery/src/server/local-packages.ts +91 -0
  207. package/packages/stitchery/src/server/routes.ts +122 -0
  208. package/packages/stitchery/src/server/services.ts +382 -0
  209. package/packages/stitchery/src/server/vfs-routes.ts +142 -0
  210. package/packages/stitchery/src/types.ts +59 -0
  211. package/packages/stitchery/tsconfig.json +13 -0
  212. package/packages/stitchery/tsup.config.ts +15 -0
  213. package/packages/utcp/node_modules/.bin/jiti +17 -0
  214. package/packages/utcp/node_modules/.bin/tsc +17 -0
  215. package/packages/utcp/node_modules/.bin/tsserver +17 -0
  216. package/packages/utcp/node_modules/.bin/tsup +17 -0
  217. package/packages/utcp/node_modules/.bin/tsup-node +17 -0
  218. package/packages/utcp/node_modules/.bin/tsx +17 -0
  219. package/packages/utcp/package.json +38 -0
  220. package/packages/utcp/src/index.ts +153 -0
  221. package/packages/utcp/tsconfig.json +8 -0
  222. package/packages/utcp/tsup.config.ts +12 -0
  223. package/pnpm-workspace.yaml +3 -0
  224. package/tsconfig.json +18 -0
  225. package/turbo.json +23 -0
@@ -0,0 +1,106 @@
1
+ import {
2
+ VFSStore,
3
+ LocalFSBackend,
4
+ type VirtualProject,
5
+ type VirtualFile
6
+ } from '@aprovan/patchwork-compiler';
7
+
8
+ /**
9
+ * VFS client for persisting virtual projects to the stitchery server.
10
+ * Uses LocalFSBackend which makes HTTP requests to /vfs routes.
11
+ */
12
+
13
+ // VFS base URL - points to stitchery server's VFS routes
14
+ const VFS_BASE_URL = '/vfs';
15
+
16
+ // Cached VFS config
17
+ let vfsConfigCache: { usePaths: boolean } | null = null;
18
+
19
+ /**
20
+ * Get VFS configuration from the server.
21
+ * Caches the result for subsequent calls.
22
+ */
23
+ export async function getVFSConfig(): Promise<{ usePaths: boolean }> {
24
+ if (vfsConfigCache) return vfsConfigCache;
25
+
26
+ try {
27
+ const res = await fetch(`${VFS_BASE_URL}/config`);
28
+ if (res.ok) {
29
+ vfsConfigCache = await res.json();
30
+ return vfsConfigCache!;
31
+ }
32
+ } catch {
33
+ // Server not available, use default
34
+ }
35
+
36
+ return { usePaths: false };
37
+ }
38
+
39
+ // Create a singleton store instance for dev mode
40
+ let storeInstance: VFSStore | null = null;
41
+
42
+ /**
43
+ * Get the VFS store instance (creates one if needed).
44
+ * Store uses LocalFSBackend to persist to the stitchery server.
45
+ */
46
+ export function getVFSStore(): VFSStore {
47
+ if (!storeInstance) {
48
+ const backend = new LocalFSBackend({ baseUrl: VFS_BASE_URL });
49
+ storeInstance = new VFSStore(backend);
50
+ }
51
+ return storeInstance;
52
+ }
53
+
54
+ /**
55
+ * Save a virtual project to disk via the stitchery server.
56
+ * Projects are saved under their ID in the VFS directory.
57
+ */
58
+ export async function saveProject(project: VirtualProject): Promise<void> {
59
+ const store = getVFSStore();
60
+ await store.saveProject(project);
61
+ }
62
+
63
+ /**
64
+ * Load a project from disk by ID.
65
+ */
66
+ export async function loadProject(id: string): Promise<VirtualProject | null> {
67
+ const store = getVFSStore();
68
+ return store.loadProject(id);
69
+ }
70
+
71
+ /**
72
+ * List all stored project IDs.
73
+ */
74
+ export async function listProjects(): Promise<string[]> {
75
+ const store = getVFSStore();
76
+ const files = await store.listFiles();
77
+
78
+ // Extract unique project IDs (first path segment)
79
+ const projectIds = new Set<string>();
80
+ for (const file of files) {
81
+ const id = file.split('/')[0];
82
+ if (id) projectIds.add(id);
83
+ }
84
+
85
+ return Array.from(projectIds);
86
+ }
87
+
88
+ /**
89
+ * Save a single file to the VFS.
90
+ */
91
+ export async function saveFile(file: VirtualFile): Promise<void> {
92
+ const store = getVFSStore();
93
+ await store.putFile(file);
94
+ }
95
+
96
+ /**
97
+ * Check if VFS is available (stitchery server is running with vfs-dir enabled).
98
+ */
99
+ export async function isVFSAvailable(): Promise<boolean> {
100
+ try {
101
+ const res = await fetch(VFS_BASE_URL, { method: 'HEAD' });
102
+ return res.ok || res.status === 404; // 404 on root is expected if empty
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "jsx": "react-jsx"
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ clean: true,
8
+ external: ['react', 'react-dom'],
9
+ treeshake: true,
10
+ });
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/jiti@1.21.7/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/jiti.js" "$@"
15
+ else
16
+ exec node "$basedir/../../../../../node_modules/.pnpm/jiti@1.21.7/node_modules/jiti/bin/jiti.js" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsc" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../tsup/dist/cli-default.js" "$@"
15
+ else
16
+ exec node "$basedir/../tsup/dist/cli-default.js" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../tsup/dist/cli-node.js" "$@"
15
+ else
16
+ exec node "$basedir/../tsup/dist/cli-node.js" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/tsx@4.21.0/node_modules:/home/runner/work/patchwork/patchwork/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/cli.mjs" "$@"
15
+ else
16
+ exec node "$basedir/../../../../../node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/cli.mjs" "$@"
17
+ fi
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@aprovan/patchwork-ink",
3
+ "version": "0.1.0",
4
+ "description": "Patchwork image: Ink terminal UI for CLI widgets",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./runner": {
14
+ "types": "./dist/runner.d.ts",
15
+ "import": "./dist/runner.js"
16
+ },
17
+ "./setup": {
18
+ "types": "./dist/setup.d.ts",
19
+ "import": "./dist/setup.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "patchwork": {
29
+ "platform": "cli",
30
+ "esbuild": {
31
+ "target": "node20",
32
+ "format": "esm",
33
+ "jsx": "automatic"
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "react": "^18.0.0",
38
+ "ink": "^5.0.0",
39
+ "ink-spinner": "^5.0.0",
40
+ "ink-link": "^4.0.0",
41
+ "ink-box": "^2.0.0",
42
+ "chalk": "^5.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.10.5",
46
+ "@types/react": "^18.0.0",
47
+ "tsup": "^8.3.5",
48
+ "typescript": "^5.7.3"
49
+ },
50
+ "engines": {
51
+ "node": ">=20.0.0"
52
+ }
53
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @aprovan/patchwork-image-ink
3
+ *
4
+ * Ink terminal UI image for CLI widgets.
5
+ * Provides React components for building terminal interfaces.
6
+ */
7
+
8
+ export { setup, cleanup } from './setup.js';
9
+ export type { SetupOptions, InkEnvironment } from './setup.js';
10
+
11
+ // Runner - mounting/execution for terminal widgets
12
+ export {
13
+ run,
14
+ runOnce,
15
+ evaluateWidget,
16
+ renderComponent,
17
+ getGlobals,
18
+ } from './runner.js';
19
+ export type {
20
+ RunnerOptions,
21
+ RunnerInstance,
22
+ CompiledWidget,
23
+ GlobalInjection,
24
+ } from './runner.js';
25
+
26
+ // Re-export Ink components for convenience
27
+ export {
28
+ render,
29
+ Box,
30
+ Text,
31
+ Static,
32
+ Transform,
33
+ Newline,
34
+ Spacer,
35
+ useInput,
36
+ useApp,
37
+ useFocus,
38
+ useFocusManager,
39
+ useStdin,
40
+ useStdout,
41
+ useStderr,
42
+ } from 'ink';
43
+
44
+ // Re-export React for widget authors
45
+ export { default as React } from 'react';
46
+
47
+ // Re-export chalk for styling
48
+ export { default as chalk } from 'chalk';
@@ -0,0 +1,331 @@
1
+ /**
2
+ * @aprovan/patchwork-ink - Runner
3
+ *
4
+ * Provides the runtime mounting logic for Ink terminal widgets.
5
+ * The image owns all ink/react dependencies and mounting code.
6
+ */
7
+
8
+ import type { WriteStream } from 'node:tty';
9
+ import { render } from 'ink';
10
+ import React from 'react';
11
+
12
+ /**
13
+ * Global injections for compiling widgets with this image
14
+ *
15
+ * These tell the compiler which imports to transform into global variable references.
16
+ * The evaluateWidget function will provide these globals at runtime.
17
+ */
18
+ export interface GlobalInjection {
19
+ module: string;
20
+ globalName: string;
21
+ }
22
+
23
+ export function getGlobals(): GlobalInjection[] {
24
+ return [
25
+ { module: 'react', globalName: '__REACT__' },
26
+ { module: 'ink', globalName: '__INK__' },
27
+ ];
28
+ }
29
+
30
+ export interface RunnerOptions {
31
+ /** Service proxy for UTCP calls */
32
+ proxy?: {
33
+ call(
34
+ namespace: string,
35
+ procedure: string,
36
+ args: unknown[],
37
+ ): Promise<unknown>;
38
+ };
39
+ /** Initial props/inputs to pass to widget */
40
+ inputs?: Record<string, unknown>;
41
+ /** Output stream (default: process.stdout) */
42
+ stdout?: WriteStream;
43
+ /** Input stream (default: process.stdin) */
44
+ stdin?: NodeJS.ReadStream;
45
+ /** Exit on Ctrl+C (default: true) */
46
+ exitOnCtrlC?: boolean;
47
+ }
48
+
49
+ export interface RunnerInstance {
50
+ /** Unique mount ID */
51
+ id: string;
52
+ /** Unmount the widget */
53
+ unmount: () => void;
54
+ /** Wait until the widget exits */
55
+ waitUntilExit: () => Promise<void>;
56
+ /** Rerender with new props */
57
+ rerender: (props: Record<string, unknown>) => void;
58
+ /** Clear the terminal output */
59
+ clear: () => void;
60
+ }
61
+
62
+ let mountCounter = 0;
63
+
64
+ function generateMountId(): string {
65
+ return `patchwork-ink-${Date.now()}-${++mountCounter}`;
66
+ }
67
+
68
+ /**
69
+ * Generate namespace globals that proxy calls to a service proxy
70
+ *
71
+ * Given services like ["git.branch", "git.status", "github.repos.get"],
72
+ * generates globals with appropriate methods.
73
+ */
74
+ function generateNamespaceGlobals(
75
+ services: string[],
76
+ proxy: RunnerOptions['proxy'],
77
+ ): Record<string, unknown> {
78
+ if (!proxy) return {};
79
+
80
+ const namespaces: Record<string, Record<string, unknown>> = {};
81
+
82
+ for (const service of services) {
83
+ const parts = service.split('.');
84
+ if (parts.length < 2) continue;
85
+
86
+ const namespace = parts[0] as string;
87
+ const procedurePath = parts.slice(1);
88
+
89
+ if (!namespaces[namespace]) {
90
+ namespaces[namespace] = {};
91
+ }
92
+
93
+ let current = namespaces[namespace] as Record<string, unknown>;
94
+ for (let i = 0; i < procedurePath.length - 1; i++) {
95
+ const key = procedurePath[i] as string;
96
+ if (!current[key]) {
97
+ current[key] = {};
98
+ }
99
+ current = current[key] as Record<string, unknown>;
100
+ }
101
+
102
+ const finalKey = procedurePath[procedurePath.length - 1] as string;
103
+ const fullProcedure = procedurePath.join('.');
104
+ current[finalKey] = (...args: unknown[]) =>
105
+ proxy.call(namespace, fullProcedure, args);
106
+ }
107
+
108
+ return namespaces;
109
+ }
110
+
111
+ /**
112
+ * Extract unique namespace names from services array
113
+ */
114
+ function extractNamespaces(services: string[]): string[] {
115
+ const namespaces = new Set<string>();
116
+ for (const service of services) {
117
+ const parts = service.split('.');
118
+ if (parts[0]) {
119
+ namespaces.add(parts[0]);
120
+ }
121
+ }
122
+ return Array.from(namespaces);
123
+ }
124
+
125
+ /**
126
+ * Inject namespace globals into globalThis
127
+ */
128
+ function injectNamespaceGlobals(namespaces: Record<string, unknown>): void {
129
+ for (const [name, value] of Object.entries(namespaces)) {
130
+ (globalThis as Record<string, unknown>)[name] = value;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Remove namespace globals from globalThis
136
+ */
137
+ function removeNamespaceGlobals(namespaceNames: string[]): void {
138
+ for (const name of namespaceNames) {
139
+ delete (globalThis as Record<string, unknown>)[name];
140
+ }
141
+ }
142
+
143
+ export interface CompiledWidget {
144
+ /** Compiled ESM code */
145
+ code: string;
146
+ /** Content hash for caching */
147
+ hash: string;
148
+ /** Original manifest */
149
+ manifest: {
150
+ name: string;
151
+ services?: string[];
152
+ [key: string]: unknown;
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Run a compiled widget using Ink
158
+ *
159
+ * This is the main entry point for running terminal widgets.
160
+ * The image owns all React/Ink dependencies.
161
+ */
162
+ export async function run(
163
+ widget: CompiledWidget,
164
+ options: RunnerOptions = {},
165
+ ): Promise<RunnerInstance> {
166
+ const {
167
+ proxy,
168
+ inputs = {},
169
+ stdout = process.stdout as WriteStream,
170
+ stdin = process.stdin,
171
+ exitOnCtrlC = true,
172
+ } = options;
173
+ const mountId = generateMountId();
174
+
175
+ // Inject namespace globals for services
176
+ const services = widget.manifest.services || [];
177
+ const namespaceNames = extractNamespaces(services);
178
+ const namespaces = generateNamespaceGlobals(services, proxy);
179
+ injectNamespaceGlobals(namespaces);
180
+
181
+ // Import the widget module from code
182
+ const dataUri = `data:text/javascript;base64,${Buffer.from(
183
+ widget.code,
184
+ ).toString('base64')}`;
185
+
186
+ let module: { default?: unknown };
187
+ try {
188
+ module = await import(/* webpackIgnore: true */ /* @vite-ignore */ dataUri);
189
+ } catch {
190
+ // Fallback: use Function-based loading
191
+ const AsyncFunction = Object.getPrototypeOf(async function () {})
192
+ .constructor as new (argName: string, code: string) => (
193
+ exports: Record<string, unknown>,
194
+ ) => Promise<Record<string, unknown>>;
195
+ const exports: Record<string, unknown> = {};
196
+ const fn = new AsyncFunction('exports', widget.code + '\nreturn exports;');
197
+ module = await fn(exports);
198
+ }
199
+
200
+ const Component = module.default;
201
+ if (!Component) {
202
+ removeNamespaceGlobals(namespaceNames);
203
+ throw new Error('Widget must export a default component');
204
+ }
205
+
206
+ if (typeof Component !== 'function') {
207
+ removeNamespaceGlobals(namespaceNames);
208
+ throw new Error('Widget default export must be a function/component');
209
+ }
210
+
211
+ // Render using Ink
212
+ let currentInputs = { ...inputs };
213
+ const element = React.createElement(
214
+ Component as React.ComponentType,
215
+ currentInputs,
216
+ );
217
+ const instance = render(element, {
218
+ stdout,
219
+ stdin,
220
+ exitOnCtrlC,
221
+ });
222
+
223
+ return {
224
+ id: mountId,
225
+ unmount() {
226
+ instance.unmount();
227
+ removeNamespaceGlobals(namespaceNames);
228
+ },
229
+ waitUntilExit() {
230
+ return instance.waitUntilExit();
231
+ },
232
+ rerender(newInputs: Record<string, unknown>) {
233
+ currentInputs = { ...currentInputs, ...newInputs };
234
+ const newElement = React.createElement(
235
+ Component as React.ComponentType,
236
+ currentInputs,
237
+ );
238
+ instance.rerender(newElement);
239
+ },
240
+ clear() {
241
+ instance.clear();
242
+ },
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Run a widget once and wait for exit
248
+ */
249
+ export async function runOnce(
250
+ widget: CompiledWidget,
251
+ options: RunnerOptions = {},
252
+ ): Promise<void> {
253
+ const instance = await run(widget, options);
254
+ await instance.waitUntilExit();
255
+ instance.unmount();
256
+ }
257
+
258
+ /**
259
+ * Evaluate widget code and return the component
260
+ *
261
+ * This is used for more advanced scenarios where you need
262
+ * direct access to the component.
263
+ */
264
+ export async function evaluateWidget(
265
+ code: string,
266
+ services: Record<string, unknown> = {},
267
+ ): Promise<React.ComponentType<{ services?: Record<string, unknown> }>> {
268
+ // Store services for widget access
269
+ (globalThis as Record<string, unknown>).__PATCHWORK_SERVICES__ = services;
270
+
271
+ // Inject globals that the compiled code expects
272
+ const __EXPORTS__: Record<string, unknown> = {};
273
+ const __REACT__ = React;
274
+ const __INK__ = await import('ink');
275
+
276
+ // Execute the transformed code with injected globals
277
+ const fn = new Function('__EXPORTS__', '__REACT__', '__INK__', code);
278
+ fn(__EXPORTS__, __REACT__, __INK__);
279
+
280
+ const Component =
281
+ __EXPORTS__.default ||
282
+ __EXPORTS__.Widget ||
283
+ Object.values(__EXPORTS__).find(
284
+ (v): v is React.ComponentType => typeof v === 'function',
285
+ );
286
+
287
+ if (!Component) {
288
+ throw new Error('No default export or Widget component found');
289
+ }
290
+
291
+ return Component as React.ComponentType<{
292
+ services?: Record<string, unknown>;
293
+ }>;
294
+ }
295
+
296
+ /**
297
+ * Render a component directly with Ink
298
+ *
299
+ * For cases where you already have an evaluated component.
300
+ */
301
+ export function renderComponent(
302
+ Component: React.ComponentType<Record<string, unknown>>,
303
+ props: Record<string, unknown> = {},
304
+ options: Omit<RunnerOptions, 'proxy' | 'inputs'> = {},
305
+ ): RunnerInstance {
306
+ const {
307
+ stdout = process.stdout as WriteStream,
308
+ stdin = process.stdin,
309
+ exitOnCtrlC = true,
310
+ } = options;
311
+ const mountId = generateMountId();
312
+
313
+ let currentProps = { ...props };
314
+ const element = React.createElement(Component, currentProps);
315
+ const instance = render(element, {
316
+ stdout,
317
+ stdin,
318
+ exitOnCtrlC,
319
+ });
320
+
321
+ return {
322
+ id: mountId,
323
+ unmount: () => instance.unmount(),
324
+ waitUntilExit: () => instance.waitUntilExit(),
325
+ rerender(newProps: Record<string, unknown>) {
326
+ currentProps = { ...currentProps, ...newProps };
327
+ instance.rerender(React.createElement(Component, currentProps));
328
+ },
329
+ clear: () => instance.clear(),
330
+ };
331
+ }