Files
exe.dev user e859245867 Add minimal stock listing app: actions, Postgres schema, stocks page, Claude Agent SDK harness
- Add stocks table (server/db) with list/get/add/update/delete actions, scoped
  per user, backing both the chat agent and the /stocks UI via
  useActionQuery/useActionMutation
- Add mobile-first /stocks page (shadcn table/cards, watchlist, search) and
  wire it into the sidebar, navigation state, and view-screen context
- Install the platform's Claude Agent SDK harness (server/plugins,
  server/routes/api/claude-agent) so the app agent runs on the platform's own
  Claude subscription instead of the Builder.io gateway; fix the vendored
  turn.post.ts route to resolve the session and wrap it in
  runWithRequestContext, since custom /api/ routes aren't auto-wrapped in
  request context the way /_agent-native/actions/* are
- Add shadcn table/badge/dialog/select components (Tabler icons, not lucide)
2026-07-29 12:18:14 +00:00

65 lines
2.0 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { defineAction } from "@agent-native/core/action";
import { and, eq } from "@agent-native/core/db/schema";
import { z } from "zod";
import { getDb } from "../server/db/index.js";
import { stocks } from "../server/db/schema.js";
export default defineAction({
description:
"Add a new stock listing. Fails if the symbol is already listed for this user.",
schema: z.object({
symbol: z.string().min(1).max(10).describe("Ticker symbol, e.g. AAPL"),
companyName: z.string().min(1).describe("Company name"),
exchange: z
.string()
.default("NASDAQ")
.describe("Exchange, e.g. NASDAQ, NYSE"),
sector: z.string().optional().describe("Sector, e.g. Technology"),
currency: z.string().default("USD").describe("Currency code, e.g. USD"),
price: z.coerce.number().default(0).describe("Current price"),
changePercent: z.coerce
.number()
.default(0)
.describe("Price change percent, e.g. 1.5 or -2.3"),
marketCap: z.coerce
.number()
.optional()
.describe("Market capitalization"),
}),
run: async (args, ctx) => {
if (!ctx?.userEmail) throw new Error("Authentication required.");
const db = getDb();
const symbol = args.symbol.toUpperCase();
const existing = await db
.select({ id: stocks.id })
.from(stocks)
.where(and(eq(stocks.ownerEmail, ctx.userEmail), eq(stocks.symbol, symbol)));
if (existing[0]) {
throw new Error(`${symbol} is already listed.`);
}
const now = new Date().toISOString();
const row = {
id: randomUUID(),
symbol,
companyName: args.companyName,
exchange: args.exchange,
sector: args.sector ?? null,
currency: args.currency,
price: args.price,
changePercent: args.changePercent,
marketCap: args.marketCap ?? null,
watchlisted: false,
ownerEmail: ctx.userEmail,
createdAt: now,
updatedAt: now,
};
await db.insert(stocks).values(row);
return row;
},
});