@parlor/scaffold 0.0.0-stage → 0.2.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -2
  3. package/dist/add/addon.d.ts +103 -0
  4. package/dist/add/addon.d.ts.map +1 -0
  5. package/dist/add/addon.js +12 -0
  6. package/dist/add/addon.js.map +1 -0
  7. package/dist/add/plan.d.ts +72 -0
  8. package/dist/add/plan.d.ts.map +1 -0
  9. package/dist/add/plan.js +237 -0
  10. package/dist/add/plan.js.map +1 -0
  11. package/dist/add/run.d.ts +92 -0
  12. package/dist/add/run.d.ts.map +1 -0
  13. package/dist/add/run.js +167 -0
  14. package/dist/add/run.js.map +1 -0
  15. package/dist/add/transaction.d.ts +67 -0
  16. package/dist/add/transaction.d.ts.map +1 -0
  17. package/dist/add/transaction.js +181 -0
  18. package/dist/add/transaction.js.map +1 -0
  19. package/dist/addons/agent.d.ts +58 -0
  20. package/dist/addons/agent.d.ts.map +1 -0
  21. package/dist/addons/agent.js +50 -0
  22. package/dist/addons/agent.js.map +1 -0
  23. package/dist/addons/auth.d.ts +103 -0
  24. package/dist/addons/auth.d.ts.map +1 -0
  25. package/dist/addons/auth.js +93 -0
  26. package/dist/addons/auth.js.map +1 -0
  27. package/dist/addons/index.d.ts +159 -0
  28. package/dist/addons/index.d.ts.map +1 -0
  29. package/dist/addons/index.js +8 -0
  30. package/dist/addons/index.js.map +1 -0
  31. package/dist/catalog.d.ts +3 -0
  32. package/dist/catalog.d.ts.map +1 -0
  33. package/dist/catalog.js +23 -0
  34. package/dist/catalog.js.map +1 -0
  35. package/dist/create.d.ts +45 -0
  36. package/dist/create.d.ts.map +1 -0
  37. package/dist/create.js +162 -0
  38. package/dist/create.js.map +1 -0
  39. package/dist/docs.d.ts +12 -0
  40. package/dist/docs.d.ts.map +1 -0
  41. package/dist/docs.js +70 -0
  42. package/dist/docs.js.map +1 -0
  43. package/dist/edit/inspect.d.ts +15 -0
  44. package/dist/edit/inspect.d.ts.map +1 -0
  45. package/dist/edit/inspect.js +73 -0
  46. package/dist/edit/inspect.js.map +1 -0
  47. package/dist/edit/ops.d.ts +92 -0
  48. package/dist/edit/ops.d.ts.map +1 -0
  49. package/dist/edit/ops.js +516 -0
  50. package/dist/edit/ops.js.map +1 -0
  51. package/dist/edit/source.d.ts +24 -0
  52. package/dist/edit/source.d.ts.map +1 -0
  53. package/dist/edit/source.js +36 -0
  54. package/dist/edit/source.js.map +1 -0
  55. package/dist/edit/syntax.d.ts +39 -0
  56. package/dist/edit/syntax.d.ts.map +1 -0
  57. package/dist/edit/syntax.js +129 -0
  58. package/dist/edit/syntax.js.map +1 -0
  59. package/dist/errors.d.ts +5 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +5 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/files.d.ts +15 -0
  64. package/dist/files.d.ts.map +1 -0
  65. package/dist/files.js +65 -0
  66. package/dist/files.js.map +1 -0
  67. package/dist/fixtures.d.ts +28 -0
  68. package/dist/fixtures.d.ts.map +1 -0
  69. package/dist/fixtures.js +66 -0
  70. package/dist/fixtures.js.map +1 -0
  71. package/dist/git.d.ts +22 -0
  72. package/dist/git.d.ts.map +1 -0
  73. package/dist/git.js +56 -0
  74. package/dist/git.js.map +1 -0
  75. package/dist/identity.d.ts +9 -0
  76. package/dist/identity.d.ts.map +1 -0
  77. package/dist/identity.js +23 -0
  78. package/dist/identity.js.map +1 -0
  79. package/dist/index.d.ts +9 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +8 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/package-manager.d.ts +49 -0
  84. package/dist/package-manager.d.ts.map +1 -0
  85. package/dist/package-manager.js +118 -0
  86. package/dist/package-manager.js.map +1 -0
  87. package/dist/version.d.ts +6 -0
  88. package/dist/version.d.ts.map +1 -0
  89. package/dist/version.js +9 -0
  90. package/dist/version.js.map +1 -0
  91. package/package.json +46 -3
  92. package/templates/auth/auth.ts +19 -0
  93. package/templates/auth/schema/auth.ts +1 -0
  94. package/templates/auth/src/auth/client.ts +3 -0
  95. package/templates/auth/src/auth/sign-in-gate.tsx +17 -0
  96. package/templates/auth/src/auth/sign-out-button.tsx +17 -0
  97. package/templates/auth/src/routes/link.tsx +92 -0
  98. package/templates/auth/src/routes/sign-in.tsx +117 -0
  99. package/templates/default/_gitignore +7 -0
  100. package/templates/default/actions/index.ts +58 -0
  101. package/templates/default/actions/todos.spec.ts +38 -0
  102. package/templates/default/index.html +13 -0
  103. package/templates/default/migrations/0000_init.sql +9 -0
  104. package/templates/default/migrations/meta/0000_snapshot.json +87 -0
  105. package/templates/default/migrations/meta/_journal.json +13 -0
  106. package/templates/default/package.json +44 -0
  107. package/templates/default/parlor.config.ts +12 -0
  108. package/templates/default/schema/index.ts +1 -0
  109. package/templates/default/schema/todos.ts +13 -0
  110. package/templates/default/server.ts +13 -0
  111. package/templates/default/src/agent/agent-button.tsx +120 -0
  112. package/templates/default/src/agent/agent-sidebar.tsx +828 -0
  113. package/templates/default/src/agent/agent.css +5 -0
  114. package/templates/default/src/agent/confirmation-card.tsx +42 -0
  115. package/templates/default/src/agent/sidebar-view.ts +36 -0
  116. package/templates/default/src/agent/views.ts +9 -0
  117. package/templates/default/src/app.css +1 -0
  118. package/templates/default/src/header-actions.tsx +9 -0
  119. package/templates/default/src/main.tsx +27 -0
  120. package/templates/default/src/parlor.d.ts +9 -0
  121. package/templates/default/src/routeTree.gen.ts +59 -0
  122. package/templates/default/src/routes/__root.tsx +29 -0
  123. package/templates/default/src/routes/index.tsx +141 -0
  124. package/templates/default/tsconfig.json +8 -0
  125. package/templates/default/vitest.config.ts +5 -0
@@ -0,0 +1,5 @@
1
+ @media (min-width: 64rem) {
2
+ body:has(> [data-agent-sidebar]:not([hidden])) {
3
+ padding-right: 28rem;
4
+ }
5
+ }
@@ -0,0 +1,42 @@
1
+ import { type AgentConfirmation, Chat } from "@parlor/client/react";
2
+ import type { MouseEvent } from "react";
3
+
4
+ export function focusComposer(event: MouseEvent<HTMLElement>) {
5
+ event.currentTarget.closest("section")?.querySelector("textarea")?.focus();
6
+ }
7
+
8
+ export function ConfirmationCard({
9
+ confirmation,
10
+ }: {
11
+ readonly confirmation: AgentConfirmation;
12
+ }) {
13
+ const agent = confirmation.origin.name ?? "An agent";
14
+ return (
15
+ <Chat.Confirm
16
+ confirmation={confirmation}
17
+ className="space-y-2 rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm"
18
+ >
19
+ <Chat.ConfirmLabel>
20
+ <strong>{agent}</strong> wants to run <code>{confirmation.action}</code>
21
+ </Chat.ConfirmLabel>
22
+ <p>{confirmation.description}</p>
23
+ <details>
24
+ <summary className="cursor-pointer text-zinc-500">Details</summary>
25
+ <pre className="mt-1 overflow-x-auto text-xs">
26
+ {JSON.stringify(confirmation.input, null, 2)}
27
+ </pre>
28
+ </details>
29
+ <div className="flex gap-2">
30
+ <Chat.Approve
31
+ onClick={focusComposer}
32
+ className="rounded-md bg-zinc-900 px-3 py-1 font-medium text-white"
33
+ >
34
+ Approve
35
+ </Chat.Approve>
36
+ <Chat.Decline onClick={focusComposer} className="rounded-md border px-3 py-1">
37
+ Decline
38
+ </Chat.Decline>
39
+ </div>
40
+ </Chat.Confirm>
41
+ );
42
+ }
@@ -0,0 +1,36 @@
1
+ import { useSyncExternalStore } from "react";
2
+
3
+ export interface SidebarView {
4
+ readonly open: boolean;
5
+ readonly threadId: string | undefined;
6
+ }
7
+
8
+ export interface SidebarViewStore {
9
+ subscribe(listener: () => void): () => void;
10
+ get(): SidebarView;
11
+ set(patch: Partial<SidebarView>): void;
12
+ }
13
+
14
+ export function createSidebarViewStore(): SidebarViewStore {
15
+ const listeners = new Set<() => void>();
16
+ let view: SidebarView = { open: false, threadId: undefined };
17
+ return {
18
+ subscribe(listener) {
19
+ listeners.add(listener);
20
+ return () => listeners.delete(listener);
21
+ },
22
+ get: () => view,
23
+ set(patch) {
24
+ const next = { ...view, ...patch };
25
+ if (next.open === view.open && next.threadId === view.threadId) return;
26
+ view = next;
27
+ for (const listener of listeners) listener();
28
+ },
29
+ };
30
+ }
31
+
32
+ export const sidebarView = createSidebarViewStore();
33
+
34
+ export function useSidebarView(): SidebarView {
35
+ return useSyncExternalStore(sidebarView.subscribe, sidebarView.get, sidebarView.get);
36
+ }
@@ -0,0 +1,9 @@
1
+ import { defineAgentView } from "@parlor/client";
2
+ import * as z from "zod/mini";
3
+
4
+ export const agentPanel = defineAgentView({
5
+ name: "agentPanel",
6
+ description:
7
+ "The agent sidebar: the chat with the user's agent and the requests waiting for the user's approval. Read-only; it says whether the sidebar is open.",
8
+ schema: z.object({ open: z.boolean() }),
9
+ });
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,9 @@
1
+ import { AgentButton } from "./agent/agent-button.js";
2
+
3
+ export function HeaderActions() {
4
+ return (
5
+ <div className="flex items-center gap-2">
6
+ <AgentButton />
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1,27 @@
1
+ import { createActionClient } from "@parlor/client";
2
+ import { FrameworkProvider } from "@parlor/client/react";
3
+ import { createParlorRouter, mount } from "@parlor/client/router";
4
+ import { RouterProvider } from "@tanstack/react-router";
5
+ import { StrictMode } from "react";
6
+
7
+ import { routeTree } from "./routeTree.gen.js";
8
+
9
+ const client = createActionClient();
10
+ const router = createParlorRouter({ routeTree, client });
11
+
12
+ declare module "@tanstack/react-router" {
13
+ interface Register {
14
+ router: typeof router;
15
+ }
16
+ }
17
+
18
+ mount(document.getElementById("root")!, {
19
+ router,
20
+ element: (
21
+ <StrictMode>
22
+ <FrameworkProvider client={client} router={router}>
23
+ <RouterProvider router={router} />
24
+ </FrameworkProvider>
25
+ </StrictMode>
26
+ ),
27
+ });
@@ -0,0 +1,9 @@
1
+ import type { ActionsOf } from "@parlor/core/registry";
2
+
3
+ import type { registry } from "../actions/index.js";
4
+
5
+ declare module "@parlor/client" {
6
+ interface Register {
7
+ actions: ActionsOf<typeof registry>;
8
+ }
9
+ }
@@ -0,0 +1,59 @@
1
+ /* eslint-disable */
2
+
3
+ // @ts-nocheck
4
+
5
+ // noinspection JSUnusedGlobalSymbols
6
+
7
+ // This file was automatically generated by TanStack Router.
8
+ // You should NOT make any changes in this file as it will be overwritten.
9
+ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
+
11
+ import { Route as rootRouteImport } from "./routes/__root";
12
+ import { Route as IndexRouteImport } from "./routes/index";
13
+
14
+ const IndexRoute = IndexRouteImport.update({
15
+ id: "/",
16
+ path: "/",
17
+ getParentRoute: () => rootRouteImport,
18
+ } as any);
19
+
20
+ export interface FileRoutesByFullPath {
21
+ "/": typeof IndexRoute;
22
+ }
23
+ export interface FileRoutesByTo {
24
+ "/": typeof IndexRoute;
25
+ }
26
+ export interface FileRoutesById {
27
+ __root__: typeof rootRouteImport;
28
+ "/": typeof IndexRoute;
29
+ }
30
+ export interface FileRouteTypes {
31
+ fileRoutesByFullPath: FileRoutesByFullPath;
32
+ fullPaths: "/";
33
+ fileRoutesByTo: FileRoutesByTo;
34
+ to: "/";
35
+ id: "__root__" | "/";
36
+ fileRoutesById: FileRoutesById;
37
+ }
38
+ export interface RootRouteChildren {
39
+ IndexRoute: typeof IndexRoute;
40
+ }
41
+
42
+ declare module "@tanstack/react-router" {
43
+ interface FileRoutesByPath {
44
+ "/": {
45
+ id: "/";
46
+ path: "/";
47
+ fullPath: "/";
48
+ preLoaderRoute: typeof IndexRouteImport;
49
+ parentRoute: typeof rootRouteImport;
50
+ };
51
+ }
52
+ }
53
+
54
+ const rootRouteChildren: RootRouteChildren = {
55
+ IndexRoute: IndexRoute,
56
+ };
57
+ export const routeTree = rootRouteImport
58
+ ._addFileChildren(rootRouteChildren)
59
+ ._addFileTypes<FileRouteTypes>();
@@ -0,0 +1,29 @@
1
+ import type { ParlorRouterContext } from "@parlor/client/router";
2
+ import { createRootRouteWithContext, Link, Outlet } from "@tanstack/react-router";
3
+
4
+ import { agentPanel } from "../agent/views.js";
5
+ import { HeaderActions } from "../header-actions.js";
6
+
7
+ export const Route = createRootRouteWithContext<ParlorRouterContext>()({
8
+ staticData: {
9
+ description: "The app shell: a header with the agent button, then the page.",
10
+ views: [agentPanel],
11
+ },
12
+ component: Layout,
13
+ });
14
+
15
+ function Layout() {
16
+ return (
17
+ <div className="min-h-dvh bg-zinc-50 text-zinc-900">
18
+ <header className="flex items-center justify-between border-b border-zinc-200 bg-white px-6 py-3">
19
+ <Link to="/" className="font-semibold tracking-tight">
20
+ Todos
21
+ </Link>
22
+ <HeaderActions />
23
+ </header>
24
+ <main className="mx-auto max-w-2xl px-6 py-10">
25
+ <Outlet />
26
+ </main>
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,141 @@
1
+ import { defineAgentView } from "@parlor/client";
2
+ import {
3
+ ensureActionQueryData,
4
+ useActionMutation,
5
+ useActionSuspenseQuery,
6
+ useAgentView,
7
+ } from "@parlor/client/react";
8
+ import { createFileRoute } from "@tanstack/react-router";
9
+ import { type FormEvent, useState } from "react";
10
+ import * as z from "zod/mini";
11
+
12
+ const todoView = defineAgentView({
13
+ name: "todo",
14
+ description: "A todo. The key is the todo id that toggleTodo and deleteTodo take.",
15
+ keyed: true,
16
+ schema: z.object({ title: z.string(), done: z.boolean() }),
17
+ });
18
+
19
+ const newTodoView = defineAgentView({
20
+ name: "newTodo",
21
+ description: "The New todo field. Fill it in for the user; they press Add themselves.",
22
+ schema: z.object({ title: z.string() }),
23
+ writable: true,
24
+ });
25
+
26
+ export const Route = createFileRoute("/")({
27
+ staticData: {
28
+ description: "The todo list: add a todo, check it off, or delete it.",
29
+ views: [newTodoView, todoView],
30
+ },
31
+ loader: ({ context }) => ensureActionQueryData(context, "listTodos", {}),
32
+ component: Todos,
33
+ });
34
+
35
+ interface Todo {
36
+ readonly id: string;
37
+ readonly title: string;
38
+ readonly done: boolean;
39
+ }
40
+
41
+ function Todos() {
42
+ const { data: todos } = useActionSuspenseQuery("listTodos", {});
43
+ const toggle = useActionMutation("toggleTodo");
44
+ const remove = useActionMutation("deleteTodo");
45
+
46
+ return (
47
+ <section className="space-y-6">
48
+ <h1 className="text-2xl font-semibold tracking-tight">Todos</h1>
49
+ <NewTodo />
50
+ {todos.length === 0 ? (
51
+ <p className="text-sm text-zinc-500">
52
+ Nothing yet. Add a todo, or ask the agent to.
53
+ </p>
54
+ ) : null}
55
+ <ul
56
+ data-testid="todos"
57
+ className="divide-y divide-zinc-200 rounded-lg border border-zinc-200 bg-white empty:hidden"
58
+ >
59
+ {todos.map((todo) => (
60
+ <TodoRow
61
+ key={todo.id}
62
+ todo={todo}
63
+ onToggle={() => toggle.mutate({ id: todo.id })}
64
+ onDelete={() => remove.mutate({ id: todo.id })}
65
+ />
66
+ ))}
67
+ </ul>
68
+ </section>
69
+ );
70
+ }
71
+
72
+ function TodoRow({
73
+ todo,
74
+ onToggle,
75
+ onDelete,
76
+ }: {
77
+ readonly todo: Todo;
78
+ readonly onToggle: () => void;
79
+ readonly onDelete: () => void;
80
+ }) {
81
+ const { ref } = useAgentView(todoView, {
82
+ key: todo.id,
83
+ get: () => ({ title: todo.title, done: todo.done }),
84
+ });
85
+ return (
86
+ <li ref={ref} className="flex items-center gap-3 px-4 py-3">
87
+ <input
88
+ type="checkbox"
89
+ className="size-4 accent-zinc-900"
90
+ checked={todo.done}
91
+ aria-label={`Done: ${todo.title}`}
92
+ onChange={onToggle}
93
+ />
94
+ <span className={todo.done ? "flex-1 text-zinc-400 line-through" : "flex-1"}>
95
+ {todo.title}
96
+ </span>
97
+ <button
98
+ type="button"
99
+ className="text-sm text-zinc-500 hover:text-red-600"
100
+ onClick={onDelete}
101
+ >
102
+ Delete
103
+ </button>
104
+ </li>
105
+ );
106
+ }
107
+
108
+ function NewTodo() {
109
+ const [title, setTitle] = useState("");
110
+ const { ref } = useAgentView(newTodoView, {
111
+ get: () => ({ title }),
112
+ set: (value) => setTitle(value.title),
113
+ });
114
+ const add = useActionMutation("addTodo", { onSuccess: () => setTitle("") });
115
+
116
+ const submit = (event: FormEvent) => {
117
+ event.preventDefault();
118
+ if (title.trim()) add.mutate({ title });
119
+ };
120
+
121
+ return (
122
+ <form ref={ref} onSubmit={submit} className="flex gap-2">
123
+ <input
124
+ name="title"
125
+ aria-label="New todo"
126
+ placeholder="What needs to be done?"
127
+ autoComplete="off"
128
+ value={title}
129
+ onChange={(event) => setTitle(event.target.value)}
130
+ className="flex-1 rounded-md border border-zinc-300 bg-white px-3 py-2 focus:border-zinc-900 focus:outline-none"
131
+ />
132
+ <button
133
+ type="submit"
134
+ disabled={add.isPending}
135
+ className="rounded-md bg-zinc-900 px-4 py-2 font-medium text-white hover:bg-zinc-700 disabled:opacity-50"
136
+ >
137
+ Add
138
+ </button>
139
+ </form>
140
+ );
141
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@parlor/config/tsconfig",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["node", "vite/client"]
6
+ },
7
+ "include": ["*.ts", "actions", "schema", "src"]
8
+ }
@@ -0,0 +1,5 @@
1
+ import { defineProject } from "vitest/config";
2
+
3
+ export default defineProject({
4
+ test: { include: ["actions/**/*.spec.ts", "src/**/*.spec.ts"], testTimeout: 30_000 },
5
+ });