From 0f3bc471643ddb32c64bb68a4929d3d17cb778b8 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:43:54 -0700 Subject: [PATCH 1/6] import { startServer } from "@workshop/http" --- packages/http/src/server.tsx | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/http/src/server.tsx b/packages/http/src/server.tsx index 5a673f9..b584bda 100644 --- a/packages/http/src/server.tsx +++ b/packages/http/src/server.tsx @@ -2,15 +2,27 @@ import { serve } from "bun" import { nanoRemix } from "@workshop/nano-remix" import { join } from "node:path" -const server = serve({ - routes: { - "/*": (req) => nanoRemix(req, { routesDir: join(import.meta.dir, "routes") }), - }, +type StartOptions = { + routesDir?: string +} - development: process.env.NODE_ENV !== "production" && { - hmr: true, - console: true, - }, -}) +function startServer(opts: StartOptions) { + const server = serve({ + routes: { + "/*": (req) => nanoRemix(req, opts), + }, -console.log(`🤖 Server running at ${server.url}`) + development: process.env.NODE_ENV !== "production" && { + hmr: true, + console: true, + }, + }) + + console.log(`🤖 Server running at ${server.url}`) +} + +if (import.meta.main) { + startServer({ routesDir: join(import.meta.dir, "routes") }) +} + +export { startServer } \ No newline at end of file From c3f32120fc75bedfef15e26f245e9a626f933dcd Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:08:07 -0700 Subject: [PATCH 2/6] =?UTF-8?q?attach=C3=A9=20api=20draft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/attache/.cursorrules | 43 +++++++++++ packages/attache/.gitignore | 34 +++++++++ packages/attache/README.md | 8 +++ packages/attache/package.json | 19 +++++ packages/attache/src/server.ts | 128 +++++++++++++++++++++++++++++++++ packages/attache/tsconfig.json | 28 ++++++++ 6 files changed, 260 insertions(+) create mode 100644 packages/attache/.cursorrules create mode 100644 packages/attache/.gitignore create mode 100644 packages/attache/README.md create mode 100644 packages/attache/package.json create mode 100644 packages/attache/src/server.ts create mode 100644 packages/attache/tsconfig.json diff --git a/packages/attache/.cursorrules b/packages/attache/.cursorrules new file mode 100644 index 0000000..35bb9fa --- /dev/null +++ b/packages/attache/.cursorrules @@ -0,0 +1,43 @@ +## Style + +- No semicolons — ever. +- No comments — ever. +- 2‑space indentation. +- Double quotes for strings. +- Trailing commas where ES5 allows (objects, arrays, imports). +- Keep lines <= 100 characters. +- End every file with a single newline. + +## TypeScript + +- This project runs on Bun. +- Assume `strict` mode is on (no implicit `any`). +- Prefer `const`; use `let` only when reassignment is required. +- Avoid the `any` type unless unavoidable. +- Use `import type { … }` when importing only types. +- In TypeScript files put npm imports first, then std imports, then library imports. + +## Tests + +Tests should use the toplevel `test()` and the `assert` library. + +Test files should always live in `test/` off the project root and be named `THING.test.ts` + +### Example Test + +``` +import { test } from "bun:test" +import assert from 'assert' + +test("1 + 2 = 3", () => { + assert.equal(1 + 2, 3) + assert.ok(true) +}) +``` + +## Assistant behaviour + +- Respond in a concise, direct tone. +- Do not ask follow‑up questions unless clarification is essential. + +Stay simple, readable, and stick to these rules. diff --git a/packages/attache/.gitignore b/packages/attache/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/attache/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/attache/README.md b/packages/attache/README.md new file mode 100644 index 0000000..25fa4c3 --- /dev/null +++ b/packages/attache/README.md @@ -0,0 +1,8 @@ +# attaché 💼 + +Stores files in folders, with style. + +## Quickstart + + bun install + bun dev diff --git a/packages/attache/package.json b/packages/attache/package.json new file mode 100644 index 0000000..0172c4f --- /dev/null +++ b/packages/attache/package.json @@ -0,0 +1,19 @@ +{ + "name": "attache", + "module": "index.ts", + "type": "module", + "private": true, + "scripts": { + "dev": "bun run --watch src/server.ts" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "hono": "^4.8.0", + "nanoid": "^5.1.5" + } +} diff --git a/packages/attache/src/server.ts b/packages/attache/src/server.ts new file mode 100644 index 0000000..b41bf0c --- /dev/null +++ b/packages/attache/src/server.ts @@ -0,0 +1,128 @@ +import { Hono } from 'hono' +import { serveStatic } from 'hono/bun' +import { basename, join } from 'path' +import { mkdir, readdir } from "node:fs/promises" +import { nanoid } from 'nanoid' + +const app = new Hono() +const PROJECTS_DIR = './projects' +const PROJECTS_FILE = join(PROJECTS_DIR, 'projects.json') + +type Project = { + id: string + name: string + createdAt: string +} + +app.get('/projects', async c => { + const projects = await loadProjects() + return c.json(projects) +}) + +app.post('/projects', async c => { + const { name } = await c.req.json() + const projects = await loadProjects() + + if (projects.some(p => p.name === name)) + return c.json({ error: "A project with this name already exists" }, 409) + + const id = nanoid(6) + projects.push({ id, name, createdAt: new Date().toISOString() }) + await mkdir(join(PROJECTS_DIR, `project_${id}`), { recursive: true }) + await saveProjects(projects) + return c.json(projects[projects.length - 1]) +}) + +app.patch('/projects/:id', async c => { + const id = c.req.param('id') + const { name } = await c.req.json() + const projects = await loadProjects() + const proj = projects.find(p => p.id === id) + if (!proj) return c.json({ error: 'Not found' }, 404) + proj.name = name + await saveProjects(projects) + return c.json(proj) +}) + +app.get('/project/:id', async c => { + const id = c.req.param('id') + const folder = join(PROJECTS_DIR, `project_${id}`) + try { + return c.json(await readdir(folder, { withFileTypes: true })) + } catch { + return c.json({ error: 'Not found' }, 404) + } +}) + +app.post('/project/:id/upload', async c => { + const id = c.req.param('id') + + if (id !== basename(id)) + return c.json({ error: 'Invalid project id' }, 400) + + const folder = join(PROJECTS_DIR, `project_${id}`) + try { + await mkdir(folder, { recursive: true }) + + const form = await c.req.formData() + const file = form.get('file') + if (!(file instanceof File)) + return c.json({ error: 'Invalid file' }, 400) + + + // strip any directory parts from the uploaded filename + const filename = basename(file.name) + const filepath = join(folder, filename) + await Bun.write(filepath, await file.arrayBuffer()) + + return c.json({ status: 'ok' }) + } catch (err) { + console.error('Upload error:', err) + return c.json({ error: 'Upload failed' }, 500) + } +}) + +app.get('/project/:id/file/:filename', async c => { + const { id, filename } = c.req.param() + const path = join(PROJECTS_DIR, `project_${id}`, filename) + const file = Bun.file(path) + if (!(await file.exists())) return c.json({ error: 'Not found' }, 404) + + return new Response(file, { + headers: { + 'Content-Type': file.type || 'application/octet-stream', + }, + }) +}) + +app.delete('/project/:id/file/:filename', async c => { + const { id, filename } = c.req.param() + const path = join(PROJECTS_DIR, `project_${id}`, filename) + try { + await Bun.file(path).delete() + return c.text('Deleted') + } catch { + return c.json({ error: 'Not found' }, 404) + } +}) + +app.use('/public/*', serveStatic({ root: './' })) + +async function loadProjects(): Promise { + try { + const file = Bun.file(PROJECTS_FILE) + const text = await file.text() + return JSON.parse(text) + } catch { + return [] + } +} + +async function saveProjects(projects: Project[]) { + await Bun.write(PROJECTS_FILE, JSON.stringify(projects, null, 2)) +} + +export default { + port: 3000, + fetch: app.fetch +} \ No newline at end of file diff --git a/packages/attache/tsconfig.json b/packages/attache/tsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/packages/attache/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} From 56a21a14e59b1474d70c076fac0b8b8aa901b867 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Thu, 19 Jun 2025 22:52:08 -0700 Subject: [PATCH 3/6] export Keys so packages can add their own local types --- packages/shared/src/kv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/kv.ts b/packages/shared/src/kv.ts index beb88e7..41c8e73 100644 --- a/packages/shared/src/kv.ts +++ b/packages/shared/src/kv.ts @@ -3,7 +3,7 @@ import { dirname, join } from "node:path" import type { Reminder } from "@/reminders" export type Conversation = { message: string; role: "user" | "assistant" } -type Keys = { +export interface Keys { threads: Record // threadId: previousResponseId reminders: Reminder[] } From ffb6fb074c449c6edd8dcaa172a95d4c178d36b5 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Thu, 19 Jun 2025 22:52:42 -0700 Subject: [PATCH 4/6] barebone html --- packages/attache/README.md | 2 +- packages/attache/package.json | 8 +- packages/attache/src/server.ts | 128 ----------- packages/attache/src/server.tsx | 367 ++++++++++++++++++++++++++++++++ packages/attache/tsconfig.json | 13 +- 5 files changed, 383 insertions(+), 135 deletions(-) delete mode 100644 packages/attache/src/server.ts create mode 100644 packages/attache/src/server.tsx diff --git a/packages/attache/README.md b/packages/attache/README.md index 25fa4c3..90b4588 100644 --- a/packages/attache/README.md +++ b/packages/attache/README.md @@ -1,4 +1,4 @@ -# attaché 💼 +# 💼 Attaché Stores files in folders, with style. diff --git a/packages/attache/package.json b/packages/attache/package.json index 0172c4f..2dd607b 100644 --- a/packages/attache/package.json +++ b/packages/attache/package.json @@ -1,10 +1,11 @@ { "name": "attache", - "module": "index.ts", + "module": "src/server.tsx", "type": "module", "private": true, "scripts": { - "dev": "bun run --watch src/server.ts" + "dev": "bun run --hot src/server.tsx", + "start": "bun run src/server.tsx" }, "devDependencies": { "@types/bun": "latest" @@ -14,6 +15,7 @@ }, "dependencies": { "hono": "^4.8.0", - "nanoid": "^5.1.5" + "nanoid": "^5.1.5", + "@workshop/shared": "workspace:*" } } diff --git a/packages/attache/src/server.ts b/packages/attache/src/server.ts deleted file mode 100644 index b41bf0c..0000000 --- a/packages/attache/src/server.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Hono } from 'hono' -import { serveStatic } from 'hono/bun' -import { basename, join } from 'path' -import { mkdir, readdir } from "node:fs/promises" -import { nanoid } from 'nanoid' - -const app = new Hono() -const PROJECTS_DIR = './projects' -const PROJECTS_FILE = join(PROJECTS_DIR, 'projects.json') - -type Project = { - id: string - name: string - createdAt: string -} - -app.get('/projects', async c => { - const projects = await loadProjects() - return c.json(projects) -}) - -app.post('/projects', async c => { - const { name } = await c.req.json() - const projects = await loadProjects() - - if (projects.some(p => p.name === name)) - return c.json({ error: "A project with this name already exists" }, 409) - - const id = nanoid(6) - projects.push({ id, name, createdAt: new Date().toISOString() }) - await mkdir(join(PROJECTS_DIR, `project_${id}`), { recursive: true }) - await saveProjects(projects) - return c.json(projects[projects.length - 1]) -}) - -app.patch('/projects/:id', async c => { - const id = c.req.param('id') - const { name } = await c.req.json() - const projects = await loadProjects() - const proj = projects.find(p => p.id === id) - if (!proj) return c.json({ error: 'Not found' }, 404) - proj.name = name - await saveProjects(projects) - return c.json(proj) -}) - -app.get('/project/:id', async c => { - const id = c.req.param('id') - const folder = join(PROJECTS_DIR, `project_${id}`) - try { - return c.json(await readdir(folder, { withFileTypes: true })) - } catch { - return c.json({ error: 'Not found' }, 404) - } -}) - -app.post('/project/:id/upload', async c => { - const id = c.req.param('id') - - if (id !== basename(id)) - return c.json({ error: 'Invalid project id' }, 400) - - const folder = join(PROJECTS_DIR, `project_${id}`) - try { - await mkdir(folder, { recursive: true }) - - const form = await c.req.formData() - const file = form.get('file') - if (!(file instanceof File)) - return c.json({ error: 'Invalid file' }, 400) - - - // strip any directory parts from the uploaded filename - const filename = basename(file.name) - const filepath = join(folder, filename) - await Bun.write(filepath, await file.arrayBuffer()) - - return c.json({ status: 'ok' }) - } catch (err) { - console.error('Upload error:', err) - return c.json({ error: 'Upload failed' }, 500) - } -}) - -app.get('/project/:id/file/:filename', async c => { - const { id, filename } = c.req.param() - const path = join(PROJECTS_DIR, `project_${id}`, filename) - const file = Bun.file(path) - if (!(await file.exists())) return c.json({ error: 'Not found' }, 404) - - return new Response(file, { - headers: { - 'Content-Type': file.type || 'application/octet-stream', - }, - }) -}) - -app.delete('/project/:id/file/:filename', async c => { - const { id, filename } = c.req.param() - const path = join(PROJECTS_DIR, `project_${id}`, filename) - try { - await Bun.file(path).delete() - return c.text('Deleted') - } catch { - return c.json({ error: 'Not found' }, 404) - } -}) - -app.use('/public/*', serveStatic({ root: './' })) - -async function loadProjects(): Promise { - try { - const file = Bun.file(PROJECTS_FILE) - const text = await file.text() - return JSON.parse(text) - } catch { - return [] - } -} - -async function saveProjects(projects: Project[]) { - await Bun.write(PROJECTS_FILE, JSON.stringify(projects, null, 2)) -} - -export default { - port: 3000, - fetch: app.fetch -} \ No newline at end of file diff --git a/packages/attache/src/server.tsx b/packages/attache/src/server.tsx new file mode 100644 index 0000000..a1fff73 --- /dev/null +++ b/packages/attache/src/server.tsx @@ -0,0 +1,367 @@ +import { Hono } from 'hono' +import { serveStatic } from 'hono/bun' +import { basename, join } from 'path' +import { mkdir, readdir } from 'node:fs/promises' +import { nanoid } from 'nanoid' +import KV, { type Keys } from '@workshop/shared/kv' +import { Dirent } from 'node:fs' +import { html } from 'hono/html' + +declare global { + interface Window { + location: Location + } + var location: Location +} + +type Project = { + id: string + name: string + createdAt: string +} + +declare module "@workshop/shared/kv" { + interface Keys { + projects: Project[] + } +} + + +const app = new Hono() +const PROJECTS_DIR = './projects' + +app.get('/projects', async c => { + const projects = await loadProjects() + return c.html( + + + + ) +}) + +app.get('/project/:id', async c => { + const id = c.req.param('id') + const { project, files } = await loadProject(id, { files: true }) + if (!project) return c.json({ error: 'Not found' }, 404) + return c.html( + + + + ) +}) + +app.get('/new', async c => { + return c.html( + + + + ) +}) + +const api = new Hono() + +api.get('/projects', async c => { + const projects = await loadProjects() + return c.json(projects) +}) + +api.post('/projects', async c => { + const form = await c.req.formData() + const name = form.get('name') + if (!name) return c.json({ error: "Name is required" }, 400) + if (typeof name !== 'string') return c.json({ error: "Name must be a string" }, 400) + + const projects = await loadProjects() + + if (projects.some(p => p.name === name)) + return c.json({ error: "A project with this name already exists" }, 409) + + const id = nanoid(6) + projects.push({ id, name, createdAt: new Date().toISOString() }) + await mkdir(join(PROJECTS_DIR, `project_${id}`), { recursive: true }) + await saveProjects(projects) + return c.json(projects[projects.length - 1]) +}) + +api.patch('/projects/:id', async c => { + const id = c.req.param('id') + const { name } = await c.req.json() + const { project } = await loadProject(id) + if (!project) return c.json({ error: 'Not found' }, 404) + project.name = name + await saveProject(project) + return c.json(project) +}) + +api.get('/project/:id', async c => { + const id = c.req.param('id') + try { + const { project, files } = await loadProject(id, { files: true }) + return c.json({ files, project }) + } catch { + return c.json({ error: 'Not found' }, 404) + } +}) + +api.post('/project/:id/upload', async c => { + const id = c.req.param('id') + + if (id !== basename(id)) + return c.json({ error: 'Invalid project id' }, 400) + + const folder = join(PROJECTS_DIR, `project_${id}`) + try { + await mkdir(folder, { recursive: true }) + + const form = await c.req.formData() + const file = form.get('file') + if (!(file instanceof File)) + return c.json({ error: 'Invalid file' }, 400) + + + // strip any directory parts from the uploaded filename + const filename = basename(file.name) + const filepath = join(folder, filename) + await Bun.write(filepath, await file.arrayBuffer()) + + return c.json({ status: 'ok' }) + } catch (err) { + console.error('Upload error:', err) + return c.json({ error: 'Upload failed' }, 500) + } +}) + +api.get('/project/:id/file/:filename', async c => { + const { id, filename } = c.req.param() + const path = join(PROJECTS_DIR, `project_${id}`, filename) + const file = Bun.file(path) + if (!(await file.exists())) return c.json({ error: 'Not found' }, 404) + + return new Response(file, { + headers: { + 'Content-Type': file.type || 'application/octet-stream', + }, + }) +}) + +api.delete('/project/:id/file/:filename', async c => { + const { id, filename } = c.req.param() + const path = join(PROJECTS_DIR, `project_${id}`, filename) + try { + await Bun.file(path).delete() + return c.text('Deleted') + } catch { + return c.json({ error: 'Not found' }, 404) + } +}) + +api.delete("/project/:id", async c => { + const id = c.req.param("id") + const { project } = await loadProject(id) + if (!project) return c.json({ error: "Not found" }, 404) + + const projects = await loadProjects() + const index = projects.findIndex(p => p.id === id) + if (index !== -1) { + projects.splice(index, 1) + await saveProjects(projects) + } + return c.json({ status: "ok" }) +}) + +app.route('/api', api) +app.use('/public/*', serveStatic({ root: './' })) + +async function loadProject(id: string, { files }: { files?: boolean } = {}): Promise<{ project: Project | null, files?: string[] }> { + const projects = await loadProjects() + const project = projects.find(p => p.id === id) + if (!project) return { project: null } + + if (files) { + const folder = join(PROJECTS_DIR, `project_${id}`) + const files = (await readdir(folder, { withFileTypes: true })).filter(f => f.isFile()).map(f => f.name) + return { project, files } + } + + return { project } +} + +async function loadProjects(): Promise { + return (await KV.get("projects")) ?? [] +} + +async function saveProject(project: Project) { + const projects = await loadProjects() + const index = projects.findIndex(p => p.id === project.id) + if (index !== -1) { + projects[index] = project + } else { + projects.push(project) + } + await saveProjects(projects) +} + +async function saveProjects(projects: Project[]) { + await KV.set("projects", projects) +} + +const Projects = ({ projects }: { projects: Project[] }) => { + return ( +
+

Projects

+ +
+ ) +} + +const Project = ({ project, files }: { project: Project, files: string[] }) => { + return ( +
+
+

{project.name}

+ +
+
+

Files

+
    + {files.map(file => ( +
  • + {file} +
    + + Download + + +
    +
  • + ))} +
+
+ {html` + + `} +
+ ) +} + +const NewProject = () => { + return ( +
+

New Project

+
+
+ + +
+
+ {html` + + `} +
+ ) +} + +const Layout = ({ children, title }: { children: any, title: string }) => { + return ( + + + {title} - 💼 Attaché + + + + +
+ +
{children}
+
+ + + ) +} + +export default { + port: 3000, + fetch: app.fetch +} \ No newline at end of file diff --git a/packages/attache/tsconfig.json b/packages/attache/tsconfig.json index 9c62f74..8da9238 100644 --- a/packages/attache/tsconfig.json +++ b/packages/attache/tsconfig.json @@ -1,11 +1,12 @@ { "compilerOptions": { // Environment setup & latest features - "lib": ["ESNext"], + "lib": ["ESNext", "DOM", "DOM.Iterable"], "target": "ESNext", - "module": "ESNext", + "module": "Preserve", "moduleDetection": "force", "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", "allowJs": true, // Bundler mode @@ -19,10 +20,16 @@ "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + "noPropertyAccessFromIndexSignature": false, + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } } } From 7733cb37378363c4d23b4fc9537a6999b8127f20 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Thu, 19 Jun 2025 23:06:11 -0700 Subject: [PATCH 5/6] uploads --- packages/attache/.gitignore | 3 + packages/attache/src/server.tsx | 140 +++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/packages/attache/.gitignore b/packages/attache/.gitignore index a14702c..b3526ad 100644 --- a/packages/attache/.gitignore +++ b/packages/attache/.gitignore @@ -1,3 +1,6 @@ +# DATA! +projects/ + # dependencies (bun install) node_modules diff --git a/packages/attache/src/server.tsx b/packages/attache/src/server.tsx index a1fff73..a107017 100644 --- a/packages/attache/src/server.tsx +++ b/packages/attache/src/server.tsx @@ -240,6 +240,15 @@ const Project = ({ project, files }: { project: Project, files: string[] }) => {

Files

+
    {files.map(file => (
  • @@ -289,6 +298,27 @@ const Project = ({ project, files }: { project: Project, files: string[] }) => { method: 'DELETE' }).then(() => location.href = '/projects') } + + function showProgress(file) { + const progress = document.getElementById("upload-progress") + const filename = document.getElementById("upload-filename") + const percent = document.getElementById("upload-percent") + const bar = document.getElementById("upload-bar") + + progress.classList.remove("hidden") + filename.textContent = file.name + percent.textContent = "0%" + bar.style.width = "0%" + } + + function updateProgress(percent) { + document.getElementById("upload-percent").textContent = percent + "%" + document.getElementById("upload-bar").style.width = percent + "%" + } + + function hideProgress() { + document.getElementById("upload-progress").classList.add("hidden") + } `}
@@ -343,8 +373,30 @@ const Layout = ({ children, title }: { children: any, title: string }) => { {title} - 💼 Attaché + - +
{children}
+ {html` + + `} ) @@ -363,5 +498,6 @@ const Layout = ({ children, title }: { children: any, title: string }) => { export default { port: 3000, - fetch: app.fetch + fetch: app.fetch, + maxRequestBodySize: 1024 * 1024 * 1024, // 1GB } \ No newline at end of file From 67bed208f78fbdf4f570b4b4609bcba6931a2350 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath <2+defunkt@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:05:58 -0700 Subject: [PATCH 6/6] slightly better READMEs --- packages/attache/README.md | 4 +++- packages/iago/README.md | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/attache/README.md b/packages/attache/README.md index 90b4588..ad6da1f 100644 --- a/packages/attache/README.md +++ b/packages/attache/README.md @@ -1,6 +1,8 @@ # 💼 Attaché -Stores files in folders, with style. +Attaché provides a JSON API and web UI for creating and viewing Projects. + +Each Project is essentially a folder of files that you can upload, download, and preview. ## Quickstart diff --git a/packages/iago/README.md b/packages/iago/README.md index 3fb4cd5..b3bbdfd 100644 --- a/packages/iago/README.md +++ b/packages/iago/README.md @@ -1,15 +1,18 @@ -# iago +# 🐦 Iago -To install dependencies: +Iago is a bun server that takes pictures of index cards pinned to a corkboard and then, +using OpenAI, gives you back the text on the index cards. -```bash -bun install -``` +Requires: -To run: +- Mac Mini +- bun +- imagesnap (`brew install imagesnap`) +- corkboard w/ index cards +- cactus for moral support -```bash -bun run index.ts -``` -This project was created using `bun init` in bun v1.2.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. +Quickstart: + + bun install + bun dev \ No newline at end of file