create-dstack 0.1.1 → 0.1.2
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.
- package/README.md +2 -0
- package/dist/index.js +3 -108
- package/package.json +1 -1
- package/templates/CLAUDE.md +0 -2
- package/templates/agents.md +0 -2
- package/templates/.cursor/skills/api/SKILL.md +0 -198
- package/templates/.cursor/skills/api-timing-logs/SKILL.md +0 -77
- package/templates/.cursor/skills/brand-styling/SKILL.md +0 -104
- package/templates/.cursor/skills/create-pr/SKILL.md +0 -138
- package/templates/.cursor/skills/frontend-design/SKILL.md +0 -45
- package/templates/.cursor/skills/make-interfaces-feel-better/SKILL.md +0 -122
- package/templates/.cursor/skills/make-interfaces-feel-better/animations.md +0 -381
- package/templates/.cursor/skills/make-interfaces-feel-better/performance.md +0 -88
- package/templates/.cursor/skills/make-interfaces-feel-better/surfaces.md +0 -245
- package/templates/.cursor/skills/make-interfaces-feel-better/typography.md +0 -125
- package/templates/.cursor/skills/react-doctor/SKILL.md +0 -19
- package/templates/.cursor/skills/ux-writing/SKILL.md +0 -453
- package/templates/convex/auth.config.ts +0 -7
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -162,7 +162,7 @@ var require_picocolors = __commonJS((exports, module) => {
|
|
|
162
162
|
|
|
163
163
|
// src/index.ts
|
|
164
164
|
var {$: $2 } = globalThis.Bun;
|
|
165
|
-
import { existsSync, cpSync
|
|
165
|
+
import { existsSync, cpSync } from "fs";
|
|
166
166
|
import { join, resolve, dirname } from "path";
|
|
167
167
|
import { fileURLToPath } from "url";
|
|
168
168
|
|
|
@@ -619,12 +619,6 @@ function z({ input: r = $, output: t = S, overwrite: e = true, hideCursor: s = t
|
|
|
619
619
|
}
|
|
620
620
|
var O = (r) => ("columns" in r) && typeof r.columns == "number" ? r.columns : 80;
|
|
621
621
|
var A = (r) => ("rows" in r) && typeof r.rows == "number" ? r.rows : 20;
|
|
622
|
-
function R(r, t, e, s = e) {
|
|
623
|
-
const i = O(r ?? S);
|
|
624
|
-
return wrapAnsi(t, i - e.length, { hard: true, trim: false }).split(`
|
|
625
|
-
`).map((n, o) => `${o === 0 ? s : e}${n}`).join(`
|
|
626
|
-
`);
|
|
627
|
-
}
|
|
628
622
|
var p = class {
|
|
629
623
|
input;
|
|
630
624
|
output;
|
|
@@ -831,24 +825,6 @@ var H = class extends p {
|
|
|
831
825
|
}
|
|
832
826
|
}
|
|
833
827
|
};
|
|
834
|
-
|
|
835
|
-
class Q extends p {
|
|
836
|
-
get cursor() {
|
|
837
|
-
return this.value ? 0 : 1;
|
|
838
|
-
}
|
|
839
|
-
get _value() {
|
|
840
|
-
return this.cursor === 0;
|
|
841
|
-
}
|
|
842
|
-
constructor(t) {
|
|
843
|
-
super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
|
|
844
|
-
this.value = this._value;
|
|
845
|
-
}), this.on("confirm", (e) => {
|
|
846
|
-
this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = e, this.state = "submit", this.close();
|
|
847
|
-
}), this.on("cursor", () => {
|
|
848
|
-
this.value = !this.value;
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
828
|
var X = { Y: { type: "year", len: 4 }, M: { type: "month", len: 2 }, D: { type: "day", len: 2 } };
|
|
853
829
|
function L(r) {
|
|
854
830
|
return [...r].map((t) => X[t]);
|
|
@@ -1149,61 +1125,6 @@ var V2 = (e) => {
|
|
|
1149
1125
|
return t("green", F2);
|
|
1150
1126
|
}
|
|
1151
1127
|
};
|
|
1152
|
-
var ot2 = (e) => {
|
|
1153
|
-
const i = e.active ?? "Yes", s = e.inactive ?? "No";
|
|
1154
|
-
return new Q({ active: i, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
|
|
1155
|
-
const r = e.withGuide ?? u.withGuide, u2 = `${V2(this.state)} `, n = r ? `${t("gray", d2)} ` : "", o = R(e.output, e.message, n, u2), c2 = `${r ? `${t("gray", d2)}
|
|
1156
|
-
` : ""}${o}
|
|
1157
|
-
`, a = this.value ? i : s;
|
|
1158
|
-
switch (this.state) {
|
|
1159
|
-
case "submit": {
|
|
1160
|
-
const l = r ? `${t("gray", d2)} ` : "";
|
|
1161
|
-
return `${c2}${l}${t("dim", a)}`;
|
|
1162
|
-
}
|
|
1163
|
-
case "cancel": {
|
|
1164
|
-
const l = r ? `${t("gray", d2)} ` : "";
|
|
1165
|
-
return `${c2}${l}${t(["strikethrough", "dim"], a)}${r ? `
|
|
1166
|
-
${t("gray", d2)}` : ""}`;
|
|
1167
|
-
}
|
|
1168
|
-
default: {
|
|
1169
|
-
const l = r ? `${t("cyan", d2)} ` : "", $2 = r ? t("cyan", E2) : "";
|
|
1170
|
-
return `${c2}${l}${this.value ? `${t("green", z2)} ${i}` : `${t("dim", H2)} ${t("dim", i)}`}${e.vertical ? r ? `
|
|
1171
|
-
${t("cyan", d2)} ` : `
|
|
1172
|
-
` : ` ${t("dim", "/")} `}${this.value ? `${t("dim", H2)} ${t("dim", s)}` : `${t("green", z2)} ${s}`}
|
|
1173
|
-
${$2}
|
|
1174
|
-
`;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
} }).prompt();
|
|
1178
|
-
};
|
|
1179
|
-
var O2 = { message: (e = [], { symbol: i = t("gray", d2), secondarySymbol: s = t("gray", d2), output: r = process.stdout, spacing: u2 = 1, withGuide: n } = {}) => {
|
|
1180
|
-
const o = [], c2 = n ?? u.withGuide, a = c2 ? s : "", l = c2 ? `${i} ` : "", $2 = c2 ? `${s} ` : "";
|
|
1181
|
-
for (let p2 = 0;p2 < u2; p2++)
|
|
1182
|
-
o.push(a);
|
|
1183
|
-
const y2 = Array.isArray(e) ? e : e.split(`
|
|
1184
|
-
`);
|
|
1185
|
-
if (y2.length > 0) {
|
|
1186
|
-
const [p2, ...m] = y2;
|
|
1187
|
-
p2.length > 0 ? o.push(`${l}${p2}`) : o.push(c2 ? i : "");
|
|
1188
|
-
for (const g of m)
|
|
1189
|
-
g.length > 0 ? o.push(`${$2}${g}`) : o.push(c2 ? s : "");
|
|
1190
|
-
}
|
|
1191
|
-
r.write(`${o.join(`
|
|
1192
|
-
`)}
|
|
1193
|
-
`);
|
|
1194
|
-
}, info: (e, i) => {
|
|
1195
|
-
O2.message(e, { ...i, symbol: t("blue", he) });
|
|
1196
|
-
}, success: (e, i) => {
|
|
1197
|
-
O2.message(e, { ...i, symbol: t("green", pe) });
|
|
1198
|
-
}, step: (e, i) => {
|
|
1199
|
-
O2.message(e, { ...i, symbol: t("green", F2) });
|
|
1200
|
-
}, warn: (e, i) => {
|
|
1201
|
-
O2.message(e, { ...i, symbol: t("yellow", me) });
|
|
1202
|
-
}, warning: (e, i) => {
|
|
1203
|
-
O2.warn(e, i);
|
|
1204
|
-
}, error: (e, i) => {
|
|
1205
|
-
O2.message(e, { ...i, symbol: t("red", ge) });
|
|
1206
|
-
} };
|
|
1207
1128
|
var pt = (e = "", i) => {
|
|
1208
1129
|
const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", E2)} ` : "";
|
|
1209
1130
|
s.write(`${r}${t("red", e)}
|
|
@@ -1337,11 +1258,6 @@ async function main() {
|
|
|
1337
1258
|
}
|
|
1338
1259
|
projectName = input;
|
|
1339
1260
|
}
|
|
1340
|
-
const useStackAuth = await ot2({ message: "Add Stack Auth?" });
|
|
1341
|
-
if (q(useStackAuth)) {
|
|
1342
|
-
pt("Cancelled.");
|
|
1343
|
-
process.exit(0);
|
|
1344
|
-
}
|
|
1345
1261
|
const projectDir = resolve(process.cwd(), projectName);
|
|
1346
1262
|
if (existsSync(projectDir)) {
|
|
1347
1263
|
pt(`Directory "${projectName}" already exists.`);
|
|
@@ -1349,7 +1265,7 @@ async function main() {
|
|
|
1349
1265
|
}
|
|
1350
1266
|
const s = fe();
|
|
1351
1267
|
s.start("preheating the oven...");
|
|
1352
|
-
await $2`bunx create-next-app@latest ${projectName} --typescript --tailwind --no-eslint --app --src-dir --import-alias "@/*" --use-bun`.quiet();
|
|
1268
|
+
await $2`bunx create-next-app@latest ${projectName} --typescript --tailwind --no-eslint --app --no-src-dir --import-alias "@/*" --use-bun`.quiet();
|
|
1353
1269
|
s.stop("next.js is in the chat");
|
|
1354
1270
|
s.start("calling convex off the bench...");
|
|
1355
1271
|
await $2`bun add convex`.cwd(projectDir).quiet();
|
|
@@ -1360,29 +1276,8 @@ async function main() {
|
|
|
1360
1276
|
s.start("adding the drip (shadcn)...");
|
|
1361
1277
|
await $2`bunx shadcn@latest init -d`.cwd(projectDir).quiet();
|
|
1362
1278
|
s.stop("looking fire already");
|
|
1363
|
-
if (useStackAuth) {
|
|
1364
|
-
s.start("sliding stack auth into the dm...");
|
|
1365
|
-
await $2`bun add @stackframe/stack`.cwd(projectDir).quiet();
|
|
1366
|
-
s.stop("auth is cooked and ready");
|
|
1367
|
-
mkdirSync(join(projectDir, "convex"), { recursive: true });
|
|
1368
|
-
copyTemplate(join(TEMPLATES_DIR, "convex", "auth.config.ts"), join(projectDir, "convex", "auth.config.ts"));
|
|
1369
|
-
appendFileSync(join(projectDir, ".env.local"), [
|
|
1370
|
-
"",
|
|
1371
|
-
"# Stack Auth \u2014 fill in from https://app.stack-auth.com",
|
|
1372
|
-
"NEXT_PUBLIC_STACK_PROJECT_ID=",
|
|
1373
|
-
"NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=",
|
|
1374
|
-
"STACK_SECRET_SERVER_KEY=",
|
|
1375
|
-
""
|
|
1376
|
-
].join(`
|
|
1377
|
-
`));
|
|
1378
|
-
O2.warn(`Stack Auth needs manual steps:
|
|
1379
|
-
` + ` 1. Create a project at ${import_picocolors.default.cyan("https://app.stack-auth.com")}
|
|
1380
|
-
` + ` 2. Fill in ${import_picocolors.default.dim(".env.local")} with your keys
|
|
1381
|
-
` + ` 3. Set the same vars in your Convex dashboard
|
|
1382
|
-
` + ` 4. Run ${import_picocolors.default.cyan("bunx @stackframe/stack-cli@latest init")} inside the project to finish wiring up the provider`);
|
|
1383
|
-
}
|
|
1384
1279
|
s.start("putting the secret sauce on it...");
|
|
1385
|
-
copyTemplate(join(TEMPLATES_DIR, "globals.css"), join(projectDir, "
|
|
1280
|
+
copyTemplate(join(TEMPLATES_DIR, "globals.css"), join(projectDir, "app", "globals.css"));
|
|
1386
1281
|
copyTemplate(join(TEMPLATES_DIR, "oxlint.json"), join(projectDir, "oxlint.json"));
|
|
1387
1282
|
copyTemplate(join(TEMPLATES_DIR, "oxfmt.json"), join(projectDir, "oxfmt.json"));
|
|
1388
1283
|
if (existsSync(join(TEMPLATES_DIR, ".claude"))) {
|
package/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -19,7 +19,6 @@ bunx oxfmt --check . # Check formatting
|
|
|
19
19
|
- React 19
|
|
20
20
|
- TypeScript 5 with strict mode
|
|
21
21
|
- Convex for DB
|
|
22
|
-
- StackAuth for auth, teams, user metadata
|
|
23
22
|
- All database types in Convex
|
|
24
23
|
|
|
25
24
|
## Conventions
|
|
@@ -49,7 +48,6 @@ API routes: mirror domains (app/api/mail/, app/api/profile/)
|
|
|
49
48
|
|
|
50
49
|
## Authentication & Security
|
|
51
50
|
|
|
52
|
-
- User endpoints: StackAuth server session for API routes and Server Actions; `useUser` only in client components
|
|
53
51
|
- System/cron: Bearer token
|
|
54
52
|
- Error handling: `throwOnError` or `if (error)` patterns
|
|
55
53
|
|
package/templates/agents.md
CHANGED
|
@@ -19,7 +19,6 @@ bunx oxfmt --check . # Check formatting
|
|
|
19
19
|
- React 19
|
|
20
20
|
- TypeScript 5 with strict mode
|
|
21
21
|
- Convex for DB
|
|
22
|
-
- StackAuth for auth, teams, user metadata
|
|
23
22
|
- All database types in Convex
|
|
24
23
|
|
|
25
24
|
## Conventions
|
|
@@ -49,7 +48,6 @@ API routes: mirror domains (app/api/mail/, app/api/profile/)
|
|
|
49
48
|
|
|
50
49
|
## Authentication & Security
|
|
51
50
|
|
|
52
|
-
- User endpoints: StackAuth server session for API routes and Server Actions; `useUser` only in client components
|
|
53
51
|
- System/cron: Bearer token
|
|
54
52
|
- Error handling: `throwOnError` or `if (error)` patterns
|
|
55
53
|
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: api
|
|
3
|
-
description: Whenever designing or working on API routes
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# API Development Rules
|
|
7
|
-
|
|
8
|
-
Every API route is either user-authenticated or system-authenticated. No exceptions.
|
|
9
|
-
|
|
10
|
-
## 1. Route Handlers
|
|
11
|
-
|
|
12
|
-
- Use standard Next.js `NextRequest` and `NextResponse` types only
|
|
13
|
-
- No custom middleware that wraps handlers
|
|
14
|
-
|
|
15
|
-
## 2. Authentication
|
|
16
|
-
|
|
17
|
-
### User APIs (browser/app requests)
|
|
18
|
-
|
|
19
|
-
Resolve the signed-in user with **StackAuth on the server**. In Route Handlers and Server Actions, use Stack's server-side session APIs—**not** the React `useUser` hook (that is client-only).
|
|
20
|
-
|
|
21
|
-
After auth, read and write data through **Convex** (`fetchQuery`, `fetchMutation`, `fetchAction` from `convex/nextjs`) so authorization can rely on `ctx.auth` inside Convex functions. Pass the Convex auth token when your setup requires it (same pattern as other OIDC providers: token option on `fetch*`).
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { fetchMutation } from "convex/nextjs";
|
|
25
|
-
import { api } from "@/convex/_generated/api";
|
|
26
|
-
import type { NextRequest } from "next/server";
|
|
27
|
-
|
|
28
|
-
export async function PATCH(request: NextRequest) {
|
|
29
|
-
// 1. Resolve user with StackAuth server APIs; return 401 if missing
|
|
30
|
-
// 2. Build Convex token if your Stack+Convex integration uses JWT forwarding
|
|
31
|
-
const token = await getConvexAuthTokenFromRequest(request);
|
|
32
|
-
const args = await request.json();
|
|
33
|
-
await fetchMutation(api.example.updateThing, args, { token });
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Cron/System APIs
|
|
38
|
-
|
|
39
|
-
Use Bearer token + trusted Convex entry points:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
const authHeader = request.headers.get("authorization");
|
|
43
|
-
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
44
|
-
return new Response("Unauthorized", { status: 401 });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
await fetchMutation(api.jobs.runScheduled, payload);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Use **deployment-appropriate** Convex access for system jobs (e.g. internal mutations or HTTP actions that validate the same secret). Never reuse a "god mode" client for user-initiated work.
|
|
51
|
-
|
|
52
|
-
### Access shape
|
|
53
|
-
|
|
54
|
-
| Caller | Auth | Data layer |
|
|
55
|
-
| ----------------- | ----------------- | ----------------------------------- |
|
|
56
|
-
| User API route | StackAuth session | Convex via `fetch*` + user token |
|
|
57
|
-
| System / cron API | Bearer secret | Convex mutation/action for batch/system work |
|
|
58
|
-
|
|
59
|
-
Never bypass user-level Convex auth for operations that should be scoped to a single user.
|
|
60
|
-
|
|
61
|
-
## 3. Validation
|
|
62
|
-
|
|
63
|
-
**Every API route MUST validate with Zod.** No exceptions.
|
|
64
|
-
|
|
65
|
-
### Validation Order: Fail Fast
|
|
66
|
-
|
|
67
|
-
1. Authenticate
|
|
68
|
-
2. Parse and validate request body immediately
|
|
69
|
-
3. Then do expensive operations (Convex calls, external APIs)
|
|
70
|
-
|
|
71
|
-
### Schema Patterns
|
|
72
|
-
|
|
73
|
-
- Use `.trim()` for strings, `.email()` for emails
|
|
74
|
-
- Use `.refine()` for custom business logic
|
|
75
|
-
- Use `z.discriminatedUnion()` for conditional validation
|
|
76
|
-
|
|
77
|
-
## 4. Error Response Format
|
|
78
|
-
|
|
79
|
-
All errors use Stripe-style format:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
{
|
|
83
|
-
error: {
|
|
84
|
-
type: string; // Required: validation_error, authentication_error, api_error, etc.
|
|
85
|
-
message: string; // Required: Human-readable
|
|
86
|
-
code?: string; // Optional: MISSING_EMAIL, QUOTA_EXCEEDED, etc.
|
|
87
|
-
param?: string; // Optional: Field name for validation errors
|
|
88
|
-
details?: unknown; // Optional: Zod errors array
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Error Types by Status
|
|
94
|
-
|
|
95
|
-
- `400` validation_error - Invalid input
|
|
96
|
-
- `401` authentication_error - Not authenticated
|
|
97
|
-
- `403` authorization_error - Not authorized
|
|
98
|
-
- `404` not_found_error - Resource missing
|
|
99
|
-
- `429` rate_limit_error - Quota exceeded
|
|
100
|
-
- `500` api_error - Internal error
|
|
101
|
-
- `503` service_unavailable_error - External service down
|
|
102
|
-
|
|
103
|
-
### Forbidden Formats
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
// NEVER use these:
|
|
107
|
-
{ success: false, error: "message" }
|
|
108
|
-
{ error: "string" } // Must be object
|
|
109
|
-
{ message: "..." } // Must use error.message
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## 5. Standard API Template
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
116
|
-
import { z } from "zod";
|
|
117
|
-
import { fetchMutation } from "convex/nextjs";
|
|
118
|
-
import { api } from "@/convex/_generated/api";
|
|
119
|
-
|
|
120
|
-
const schema = z.object({
|
|
121
|
-
email: z.string().email(),
|
|
122
|
-
name: z.string().min(1),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
export async function POST(request: NextRequest) {
|
|
126
|
-
const user = await getStackAuthUserFromRequest(request);
|
|
127
|
-
if (!user) {
|
|
128
|
-
return NextResponse.json(
|
|
129
|
-
{
|
|
130
|
-
error: { type: "authentication_error", message: "Unauthorized" },
|
|
131
|
-
},
|
|
132
|
-
{ status: 401 },
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const rawBody = await request.json();
|
|
138
|
-
const data = schema.parse(rawBody);
|
|
139
|
-
|
|
140
|
-
const token = await getConvexAuthTokenFromRequest(request, user);
|
|
141
|
-
const result = await fetchMutation(api.example.createProfile, data, { token });
|
|
142
|
-
|
|
143
|
-
return NextResponse.json({ success: true, data: result });
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (error instanceof z.ZodError) {
|
|
146
|
-
return NextResponse.json(
|
|
147
|
-
{
|
|
148
|
-
error: {
|
|
149
|
-
type: "validation_error",
|
|
150
|
-
message: "Validation failed",
|
|
151
|
-
details: error.issues,
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
{ status: 400 },
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
console.error("API Error:", error);
|
|
158
|
-
return NextResponse.json(
|
|
159
|
-
{
|
|
160
|
-
error: { type: "api_error", message: "Internal server error" },
|
|
161
|
-
},
|
|
162
|
-
{ status: 500 },
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Replace `getStackAuthUserFromRequest` / `getConvexAuthTokenFromRequest` with the project's StackAuth server helpers and Convex token bridge.
|
|
169
|
-
|
|
170
|
-
## 6. Security
|
|
171
|
-
|
|
172
|
-
- Always sanitize inputs: `.trim()`, `.toLowerCase()` for emails
|
|
173
|
-
- Check user quotas before expensive operations via `user.clientReadOnlyMetadata`
|
|
174
|
-
- Use Supabase query builders, never raw SQL with user input
|
|
175
|
-
- Validate environment variables exist before using
|
|
176
|
-
- There is no SQL layer; do not concatenate user input into query strings for external services
|
|
177
|
-
|
|
178
|
-
## 7. Database Best Practices (Convex)
|
|
179
|
-
|
|
180
|
-
- Define schema and indexes in `convex/schema.ts`; query with indexed fields
|
|
181
|
-
- Limit list reads (e.g. `.take(n)` / bounded queries); avoid unbounded scans
|
|
182
|
-
- Use Convex argument validators on every function; keep Zod at the HTTP boundary
|
|
183
|
-
- Use `Promise.allSettled()` for concurrent operations that can fail independently
|
|
184
|
-
- Use `pLimit` for controlled concurrency against external APIs
|
|
185
|
-
|
|
186
|
-
## 8. Logging
|
|
187
|
-
|
|
188
|
-
- Use `console.error()` for errors with context (userId, error message, stack)
|
|
189
|
-
- Use `console.log()` sparingly for important business events
|
|
190
|
-
- Never log sensitive data (passwords, tokens, PII)
|
|
191
|
-
- Logs are auto-captured by Vercel
|
|
192
|
-
|
|
193
|
-
## 9. Testing
|
|
194
|
-
|
|
195
|
-
- Test files in `__tests__/` within API route folders
|
|
196
|
-
- Mock StackAuth server user resolution and Convex `fetch*` helpers when testing handlers in isolation
|
|
197
|
-
- Mock `request.json()` for body parsing
|
|
198
|
-
- Use `{} as NextRequest` if handler doesn't use request object
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: api-timing-logs
|
|
3
|
-
description: Adds readable one-line phase timing logs for API routes and server handlers (local debugging and production correlation). Use when instrumenting slow routes, debugging latency, adding request tracing, or when the user asks for timing logs, speed stats, or structured server logging.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# API phase timing logs (gold standard)
|
|
7
|
-
|
|
8
|
-
## Goals
|
|
9
|
-
|
|
10
|
-
- **Scannable in a terminal**: one line per phase, aligned columns, no nested objects on the happy path.
|
|
11
|
-
- **Grep-friendly**: fixed bracket tag per feature (e.g. `[ck-gen]`, `[mail-send]`).
|
|
12
|
-
- **Minimal PII**: log `user=…` **once** at start; repeat **only** on error lines that need correlation.
|
|
13
|
-
|
|
14
|
-
## Log line shape
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
[TAG] begin user=<full-user-id>
|
|
18
|
-
[TAG] <step> <ms>ms <optional key=value ...>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
- **`TAG`**: short, stable, unique within the codebase (2–8 chars after the bracket is enough if unambiguous).
|
|
22
|
-
- **`step`**: lowercase, use **dots** for sub-phases (`db.company_row`, `storage.sign`, `llm`). Pad `step` to a fixed width (e.g. 18 chars) so `ms` columns line up.
|
|
23
|
-
- **`ms`**: wall time for **that phase only** (`Date.now() - phaseStart`), not cumulative unless the step name says `total`.
|
|
24
|
-
- **Detail tail**: space-separated `key=value`. Prefer counts and compact units over raw dumps.
|
|
25
|
-
|
|
26
|
-
## Helpers (TypeScript pattern)
|
|
27
|
-
|
|
28
|
-
Copy/adapt per route; keep helpers **file-local** unless three+ routes need the same tag.
|
|
29
|
-
|
|
30
|
-
```ts
|
|
31
|
-
const TAG = "[feature-tag]";
|
|
32
|
-
|
|
33
|
-
function elapsedMs(start: number) {
|
|
34
|
-
return Date.now() - start;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function phaseLog(step: string, ms: number, detail?: string) {
|
|
38
|
-
const tail = detail ? ` ${detail}` : "";
|
|
39
|
-
console.info(`${TAG} ${step.padEnd(18)} ${String(ms).padStart(5)}ms${tail}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function phaseWarn(bucket: string, detail: string) {
|
|
43
|
-
console.warn(`${TAG} ${bucket} ${detail}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function phaseErr(step: string, userId: string, detail: string) {
|
|
47
|
-
console.error(`${TAG} ${step} user=${userId} ${detail}`);
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
After auth (or once `userId` is known): `console.info(\`${TAG} begin user=${userId}\`);`then use`phaseLog`for every subsequent phase **without** repeating`userId`.
|
|
52
|
-
|
|
53
|
-
## What to log per phase
|
|
54
|
-
|
|
55
|
-
- **External I/O**: hub pull, DB reads/writes, storage sign/download, HTTP fetches, LLM calls—each gets its own line with duration.
|
|
56
|
-
- **CPU-ish work** only if it can be large (parse/format of huge payloads); otherwise merge with the phase that produced the bytes.
|
|
57
|
-
- **Final line**: `done` or `total` with end-to-end `ms` plus 1–3 summary stats (`docs=`, `llmIn=34500ch`, etc.).
|
|
58
|
-
|
|
59
|
-
## Units and compaction
|
|
60
|
-
|
|
61
|
-
- Prefer **`kch`** (thousands of **characters**) when approximating payload size from string length. Do **not** label char counts as `kB` (misleading).
|
|
62
|
-
- Rough token hints are OK: `~8676tok` with a comment in code that it is approximate (e.g. chars/4).
|
|
63
|
-
- For multiple similar items, one compact tail beats an array of objects:
|
|
64
|
-
`ok=3/3 raw~1200kch [a.json:400kch@800ms, b.pdf:…]`
|
|
65
|
-
- Cap list length in logs (e.g. first 3 files + `+2 more`) if noise gets large.
|
|
66
|
-
|
|
67
|
-
## Errors
|
|
68
|
-
|
|
69
|
-
- Use **`phaseErr`** for failures where you need to find the user in logs: include `user=<id>` and a **short** reason (message string, not full stack objects).
|
|
70
|
-
- Keep generic `console.error` for unexpected exceptions if you already logged `begin` with `user=`.
|
|
71
|
-
|
|
72
|
-
## Anti-patterns
|
|
73
|
-
|
|
74
|
-
- Repeating `userId` on every `info` line.
|
|
75
|
-
- Dumping **full prompts**, document bodies, or tokens in logs.
|
|
76
|
-
- Large **JSON blobs** for routine success paths—use one line + counts.
|
|
77
|
-
- Inconsistent tags (e.g. mixing `console.info("feature: …")` and `[tag] …` styles) in the same route.
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: brand-styling
|
|
3
|
-
description: dstack brand identity, colors, typography, and visual styling guidelines. Use when creating UI components, pages, or any visual elements.
|
|
4
|
-
disable-model-invocation: true
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# dstack branding
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
To access dstack's official brand identity and style resources, use this skill.
|
|
12
|
-
|
|
13
|
-
Keywords: branding, corporate identity, visual identity, styling, brand colors, typography, dstack brand, visual formatting, visual design, dstack
|
|
14
|
-
|
|
15
|
-
## Brand Guidelines
|
|
16
|
-
|
|
17
|
-
### Colors
|
|
18
|
-
|
|
19
|
-
Main Colors:
|
|
20
|
-
|
|
21
|
-
Teal:
|
|
22
|
-
#0da5a1 - Primary brand color, calls to action, active states
|
|
23
|
-
|
|
24
|
-
Background:
|
|
25
|
-
#f7f7f4 - Page background (warm off-white, not pure white)
|
|
26
|
-
|
|
27
|
-
Foreground:
|
|
28
|
-
#26251e - Primary text and dark elements
|
|
29
|
-
|
|
30
|
-
Card:
|
|
31
|
-
#f2f1ed - Card surfaces and elevated containers
|
|
32
|
-
|
|
33
|
-
Muted:
|
|
34
|
-
#6e6c5e - Secondary text and subdued labels
|
|
35
|
-
|
|
36
|
-
Border:
|
|
37
|
-
#d5d4cd - Borders, dividers, and separators
|
|
38
|
-
|
|
39
|
-
Accent Colors:
|
|
40
|
-
|
|
41
|
-
Teal Gradient Start:
|
|
42
|
-
#0da5a1 - Brand gradient start (287deg angle)
|
|
43
|
-
|
|
44
|
-
Teal Gradient End:
|
|
45
|
-
#00d4cf - Brand gradient end (lighter teal)
|
|
46
|
-
|
|
47
|
-
Semantic Colors:
|
|
48
|
-
|
|
49
|
-
Success:
|
|
50
|
-
#25935f - Success states and positive indicators
|
|
51
|
-
|
|
52
|
-
Warning:
|
|
53
|
-
#ec9c13 - Warning states and caution indicators
|
|
54
|
-
|
|
55
|
-
Error:
|
|
56
|
-
#dc2828 - Error states and destructive actions
|
|
57
|
-
|
|
58
|
-
Card Hierarchy (light to dark):
|
|
59
|
-
|
|
60
|
-
#f2f1ed - Card (base surface)
|
|
61
|
-
#f0efeb - Card-01 (slightly elevated)
|
|
62
|
-
#ebeae5 - Card-02 (mid elevation)
|
|
63
|
-
#e6e5e0 - Card-03 (high elevation)
|
|
64
|
-
#e1e0db - Card-04 (highest elevation)
|
|
65
|
-
|
|
66
|
-
### Typography
|
|
67
|
-
|
|
68
|
-
Display/Brand: Manrope (with system sans-serif fallback)
|
|
69
|
-
Body Text: Inter (with system sans-serif fallback)
|
|
70
|
-
Data/Labels: System monospace (SF Mono, Menlo, Consolas)
|
|
71
|
-
|
|
72
|
-
## Features
|
|
73
|
-
|
|
74
|
-
### Smart Font Application
|
|
75
|
-
|
|
76
|
-
- Applies Manrope to brand and display text (logo, hero sections, loading screens, onboarding)
|
|
77
|
-
- Applies Inter at light weight to all body text
|
|
78
|
-
- Applies monospace to data labels, statistics, tabular numbers, status badges, and micro-labels
|
|
79
|
-
- Manrope should use tight letter-spacing for brand moments
|
|
80
|
-
- Standard UI text (form labels, table cells, body copy) stays on Inter
|
|
81
|
-
|
|
82
|
-
### Text Styling
|
|
83
|
-
|
|
84
|
-
- Headings and brand moments: Manrope, tight tracking
|
|
85
|
-
- Body text: Inter, light weight
|
|
86
|
-
- Data and metrics: Monospace, small size (10-11px), uppercase, wide tracking
|
|
87
|
-
- Tabular numbers always use monospace for alignment
|
|
88
|
-
- Smart color selection based on surface — darker text on light surfaces, lighter text on dark
|
|
89
|
-
- Preserves text hierarchy and formatting
|
|
90
|
-
|
|
91
|
-
### Color Application
|
|
92
|
-
|
|
93
|
-
- Warm neutral palette throughout — cream and off-white tones, never stark white
|
|
94
|
-
- Brand teal used for primary actions, focus rings, and interactive highlights
|
|
95
|
-
- Brand gradient reserved for premium or highlight elements — use sparingly
|
|
96
|
-
- Card hierarchy provides layered depth through progressively darker warm neutrals
|
|
97
|
-
- Semantic colors (success, warning, error) only for their intended status meaning
|
|
98
|
-
|
|
99
|
-
### Shape and Accent Colors
|
|
100
|
-
|
|
101
|
-
- Non-text elements use the brand teal or card hierarchy colors
|
|
102
|
-
- Brand gradient cycles from #0da5a1 to #00d4cf at a 287deg angle
|
|
103
|
-
- Maintains visual interest while staying on-brand
|
|
104
|
-
- No emojis anywhere — not in UI, copy, or documentation
|