Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Vercel Marketplace Neon (the integration injects these values).
DATABASE_URL=
DATABASE_URL_UNPOOLED=

# Deterministic ingestion. Prefer a GitHub App in production; optional
# fine-grained read-only tokens are supported as a local or fallback path.
GITHUB_ACTIVITY_TOKEN=
GITHUB_PUBLIC_ACTIVITY_TOKEN=
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY=
GITHUB_APP_INSTALLATION_IDS=
GITHUB_WEBHOOK_SECRET=

# A cryptographically random value of at least 32 characters and at least
# eight distinct characters. It HMAC-pseudonymizes private identifiers.
TIMELINE_PRIVACY_KEY=

# Optional JSON object keyed by private owner/repository. Values are broad,
# owner-approved labels only, for example:
# {"owner/repository":{"bucket":"Applied AI","domain":"product"}}
TIMELINE_PRIVATE_TAXONOMY=

# Vercel Cron and model access. AI Gateway is the production default; a direct
# OpenAI key is supported for local testing or an explicitly direct deployment.
CRON_SECRET=
AI_GATEWAY_API_KEY=
AI_GATEWAY_ZERO_DATA_RETENTION=false
OPENAI_API_KEY=
9 changes: 9 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ jobs:

- name: Type check
run: bun run typecheck

- name: Test
run: bun test

- name: Agent build
run: bun run build:agent

- name: Production build
run: bun run build
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ mise.local.lock
# vercel
.vercel

# eve local workflow state and build output
.eve/
.output/

# typescript
*.tsbuildinfo
next-env.d.ts
Expand Down Expand Up @@ -117,3 +121,6 @@ next-env.d.ts
.rulesync/rules/*.local.md
rulesync.local.jsonc
!.rulesync/.aiignore

# Document the required variable names without committing values.
!.env.example
54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
# f0rr0.dev

## Getting Started
Sid Jain's portfolio, writing archive, and rolling work timeline. The site is a
Next.js application designed for Vercel. Its timeline is assembled from GitHub
activity by a deliberately narrow Eve agent and stored in Neon Postgres through
Drizzle.

First, run the development server:
## Local development

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
Use Node 24 and Bun:

## Learn More

To learn more about Next.js, take a look at the following resources:
```sh
bun install --frozen-lockfile
bun run dev
```

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
The website works without secrets. In that mode it uses public GitHub data and a
validated editorial fallback. See [the timeline guide](docs/timeline-agent.md)
for the database, ingestion, agent, and deployment setup.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Validation

## Deploy on Vercel
```sh
bun run format:check
bun run lint
bun run typecheck
bun test
bun run build
bun run build:agent
```

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Database migrations are explicit and are never run during install or build:

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
```sh
bun run db:migrate
```
35 changes: 35 additions & 0 deletions agent/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { GatewayProviderOptions } from "@ai-sdk/gateway";
import { openai } from "@ai-sdk/openai";
import { defineAgent } from "eve";

const gatewayOptions = {
disallowPromptTraining: true,
...(process.env.AI_GATEWAY_ZERO_DATA_RETENTION === "true"
? { zeroDataRetention: true }
: {}),
} satisfies GatewayProviderOptions;

const openAIKey = (process.env.OPENAI_API_KEY ?? "").trim();
const useDirectOpenAI = openAIKey.length > 0;

// Deployment policy: OPENAI direct wins whenever present.
const selectedModel = useDirectOpenAI
? openai("gpt-5.4-mini")
: "openai/gpt-5.4-mini";

export default defineAgent({
limits: {
maxInputTokensPerSession: 120_000,
maxOutputTokensPerSession: 16_000,
sessionTimeoutMs: 30 * 60 * 1000,
},
model: selectedModel,
...(useDirectOpenAI
? {}
: {
modelOptions: {
providerOptions: { gateway: gatewayOptions },
},
}),
reasoning: "medium",
});
4 changes: 4 additions & 0 deletions agent/channels/eve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { localDev, vercelOidc } from "eve/channels/auth";
import { eveChannel } from "eve/channels/eve";

export default eveChannel({ auth: [vercelOidc(), localDev()] });
41 changes: 41 additions & 0 deletions agent/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Timeline editor

You are the restrained editor of Sid Jain's rolling work timeline. Your job is
to turn already-sanitized activity clusters into a concise newspaper-style
edition. You do not ingest GitHub directly and you must never infer private
repository identities, people, clients, code, paths, issue numbers, products,
or exact private counts.

## Required workflow

1. Call `load_activity` exactly once.
2. If coverage is partial, remain conservative. Use only publishable clusters.
3. Submit only `windowStart`, `windowEnd`, and a `selections` array. Each
selection contains one `sourceKey` and an importance. Server code—not you—
writes every title, sentence, date, link, bucket, visibility, and id.
4. Select nine to sixteen entries when at least nine publishable sources exist;
otherwise select every honest source. Never reuse or combine source keys.
5. Preserve the newspaper hierarchy: at most three leads, at most four stories,
and at least forty percent briefs or pulses. Leads are for sustained streaks
or unusually significant configured milestones.
6. Represent every active quarter when the evidence permits. Include a streak
whenever one is supplied. Keep three public issue, pull-request, or repository
dispatches when available, but never let discrete events exceed one third of
the edition.
7. Treat commits as background evidence. Prefer a recurrence or streak over an
isolated commit run. Issues and pull requests are discrete public dispatches;
never select a nearby isolated commit run merely to repeat the same work. If
the server retains both a sustained run and an event, they are intentionally
distinct evidence of progression and collaboration.
8. Set importance no higher than the source permits. Avoid ranking by raw volume
alone; favor progression, recurring attention, visible collaboration, and
specific public milestones.
9. Treat account-wide and anonymous-month sources only as cadence evidence.
Prefer the account-wide streak as the larger consistency story; use monthly
anonymous signals sparingly as compact texture. Never infer a repository,
activity type, theme, or private/public status from an unexplained total.
10. Call `publish_timeline` with the complete selection plan. If validation rejects it,
correct the cited issue and retry once. Do not claim publication until the
tool succeeds.

Write in calm, economical editorial English. Every word must earn its place.
7 changes: 7 additions & 0 deletions agent/schedules/daily-edition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineSchedule } from "eve/schedules";

export default defineSchedule({
cron: "7 4 * * *",
markdown:
"Build and publish today's rolling work-timeline edition from the sanitized activity digest. Follow the required editorial workflow and finish only after publish_timeline succeeds.",
});
3 changes: 3 additions & 0 deletions agent/tools/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/ask_question.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/bash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/glob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/grep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
13 changes: 13 additions & 0 deletions agent/tools/load_activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineTool } from "eve/tools";
import { z } from "zod";

import { activityDigestSchema } from "../../src/lib/timeline-core";
import { loadTimelineActivityDigest } from "../../src/lib/timeline-editorial";

export default defineTool({
description:
"Load the rolling privacy-safe GitHub activity digest. This is the only evidence allowed for an edition.",
execute: async () => await loadTimelineActivityDigest(),
inputSchema: z.object({}).strict(),
outputSchema: activityDigestSchema,
});
39 changes: 39 additions & 0 deletions agent/tools/publish_timeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { defineTool } from "eve/tools";
import { z } from "zod";

import {
createTimelineEdition,
timelineSelectionPlanSchema,
validateTimelinePlanAgainstDigest,
} from "../../src/lib/timeline-core";
import { loadTimelineActivityDigest } from "../../src/lib/timeline-editorial";
import { publishTimelineEdition } from "../../src/lib/timeline-store";

const agentModel = "openai/gpt-5.4-mini";

export default defineTool({
approval: ({ session }) => {
const actor = session.auth.current;
return actor?.authenticator === "app" &&
actor.principalId === "eve:app" &&
actor.principalType === "runtime"
? "not-applicable"
: "denied";
},
description:
"Validate and atomically publish a complete timeline edition supported by the current sanitized digest.",
execute: async (candidate) => {
const digest = await loadTimelineActivityDigest();
const plan = validateTimelinePlanAgainstDigest(candidate, digest);
const edition = createTimelineEdition(plan, digest);
const editionKey = await publishTimelineEdition(edition, agentModel);
return { editionKey, status: "published" as const };
},
inputSchema: timelineSelectionPlanSchema,
outputSchema: z
.object({
editionKey: z.string().regex(/^[a-f\d]{64}$/),
status: z.literal("published"),
})
.strict(),
});
3 changes: 3 additions & 0 deletions agent/tools/read_file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/todo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/web_fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/web_search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
3 changes: 3 additions & 0 deletions agent/tools/write_file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { disableTool } from "eve/tools";

export default disableTool();
Loading
Loading