rahman-resources 1.10.0 → 1.11.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.
package/bin/cli.js CHANGED
@@ -1142,6 +1142,12 @@ async function runLift(rest) {
1142
1142
  } else {
1143
1143
  await pull(step.from, step.toAbs);
1144
1144
  }
1145
+ // preview.tsx is the kitab-internal variant-preview harness (VP wave) —
1146
+ // it imports @/shared/preview/* which consumers don't have. Strip it.
1147
+ if (parsed.kind === "rahman") {
1148
+ const previewFile = path.join(step.toAbs, "preview.tsx");
1149
+ if (existsSync(previewFile)) rmSync(previewFile);
1150
+ }
1145
1151
  console.log(kleur.green("ok"));
1146
1152
  }
1147
1153
 
package/lib/manifest.json CHANGED
@@ -356,7 +356,7 @@
356
356
  "slug": "dashboard-ide",
357
357
  "title": "Dashboard — IDE",
358
358
  "category": "dashboard",
359
- "description": "Activity bar + tabs + editor + bottom panel. Editor-first apps (notion, code, doc tools).",
359
+ "description": "Activity bar + tabs + LAZY explorer + editor + bottom panel + status bar. The explorer fetches each folder's children on expand and drops them on collapse (mounted rows ≈ visible rows — node_modules costs nothing until opened); the editor holds one file body at a time. Editor-first apps (notion, code, doc tools).",
360
360
  "source": "synthesized",
361
361
  "repoPath": "cookbook/layouts/dashboard-ide",
362
362
  "pullPaths": [
@@ -365,7 +365,7 @@
365
365
  "files": [],
366
366
  "dependencies": [],
367
367
  "shadcnComponents": [],
368
- "agentRecipe": "Run `npx rr add dashboard-ide`. Compose grid: 48px activity bar + tabs row + editor/inspector flex row + bottom console. Wire activity bar items to dispatch into tabs/inspector store.",
368
+ "agentRecipe": "Run `npx rr add dashboard-ide`. Compose grid: 44px activity bar + tabs row + lazy explorer column + editor/inspector flex row + bottom console + status bar. Explorer rule: fetch ONLY the expanded folder's children (listDir(path) per expand) and drop listing + DOM on collapse; tabs keep paths only and the editor fetches one body on focus. In production swap listDir for the file-explorer slice's FileExplorerAdapter.list(path). Wire activity bar items to dispatch into tabs/inspector store.",
369
369
  "tags": [
370
370
  "dashboard",
371
371
  "ide",
@@ -1359,9 +1359,9 @@
1359
1359
  "features": [
1360
1360
  {
1361
1361
  "slug": "image-editor",
1362
- "title": "Image Editor — Photoshop-style canvas (Konva)",
1362
+ "title": "Image Editor — layered raster editor",
1363
1363
  "category": "ui",
1364
- "description": "A Photoshop-style raster image editor built on Konva. Layers panel (reorder, opacity, visibility, lock, 16 blend modes), free transform (move/scale/rotate/flip via a Transformer), image + text + shape + paint layers, brush & eraser with size/opacity/hardness, non-destructive adjustments + filters (brightness/contrast/hue/saturation/blur/grayscale/invert/sepia, applied via Konva.Filters), canvas resize/aspect presets, and Photoshop-style LAYER STYLES: stroke, drop shadow (angle/distance/size/opacity), outer glow, clipping mask (clip to layer below), per-layer blend mode. One-click BACKGROUND REMOVAL runs fully in-browser via @imgly/background-removal (free, MIT, no API key, no server — downloads a small ONNX model to the browser cache on first use) and drops the cutout back as a transparent layer. Undo/redo, zoom/pan, keyboard shortcuts, and PNG/JPG/WebP export at 1×/2×/3×. Self-contained: image I/O is via props (initialImage / onSave) so it drops into any app with zero backend.",
1364
+ "description": "A Photoshop-style raster image editor built on Konva. Layers panel (reorder, opacity, visibility, lock, 16 blend modes), free transform (move/scale/rotate/flip via a Transformer), image + text + shape + paint layers, brush & eraser with size/opacity/hardness, non-destructive adjustments + filters, canvas resize/aspect presets, and LAYER STYLES: stroke, drop shadow, outer glow, clipping mask. One-click BACKGROUND REMOVAL runs fully in-browser via @imgly/background-removal (free, no API key — downloads a small ONNX model on first use). Undo/redo, zoom/pan, shortcuts, PNG/JPG/WebP export. v2 adds an AI FUNCTION-CALLING layer: every editor operation is a named, schema'd command (EDITOR_COMMANDS registry + useEditorCommands binding) driven by an in-editor chat; the streaming bridge is injectable via configureAgentStream(fn) and everything except the chat works without it. A headless server barrel (server.ts) runs commands against documents with no DOM. Image I/O via props (initialImage / onSave).",
1365
1365
  "source": "rahmanef63/os-vps",
1366
1366
  "install": "npx rahman-resources add image-editor",
1367
1367
  "npmPackages": [],
@@ -1376,7 +1376,29 @@
1376
1376
  "filters",
1377
1377
  "background-removal",
1378
1378
  "paint",
1379
- "crop",
1379
+ "ai",
1380
+ "ui"
1381
+ ]
1382
+ },
1383
+ {
1384
+ "slug": "reel-editor",
1385
+ "title": "Reel — video timeline editor",
1386
+ "category": "ui",
1387
+ "description": "A complete in-browser video editor. Real media clips (image/video/audio) on a layered multi-track timeline — the top row renders frontmost, with ▲▼ reorder and per-track lock/hide/mute. ONE Canvas-2D draw path is shared by the live preview and the realtime MediaRecorder exporter, so what you see is exactly what renders (WebM with real mixed audio: per-clip volume/fades/auto-duck through a streaming audio graph). Per-clip trim/speed (0.25–4×)/reverse, dissolve/wipe/slide transitions via clip overlap, keyframes (opacity/scale/x/y/rotation) with easing + one-click In/Out animation presets, text styling with preset grid, color grading + vignette, filmstrip thumbnails + real waveforms, snapping, split/duplicate. The workspace is config-driven: 6 resizable layout presets (react-resizable-panels v4) incl. quick-import files-pane layouts, plus custom composition size. Drafts auto-save to localStorage. Self-contained: toasts via sonner, the files pane runs on an injectable fs adapter (configureReelFs; in-memory mock by default), and shell hooks (inspector/activity) are inert seams in lib/host.ts.",
1388
+ "source": "rahmanef63/os-vps",
1389
+ "install": "npx rahman-resources add reel-editor",
1390
+ "npmPackages": [],
1391
+ "exampleCode": "\"use client\";\nimport { ReelEditor } from \"@/features/reel-editor\";\n\nexport default function VideoEditorDemo() {\n // Opens with a sample composition. Import media via File menu (local picker\n // works with zero backend — object URLs), arrange clips on layered tracks,\n // add text/transitions/keyframes, then Render → realtime WebM with audio.\n return (\n <div className=\"h-dvh w-full\">\n <ReelEditor />\n </div>\n );\n}",
1392
+ "agentRecipe": "Stack: Next 16 + React 19 + Tailwind 4 + shadcn/ui. An in-browser video timeline editor with realtime WebM export. Fully client-side; no backend required.\n\nSTEP 1 — Install. `npx rr add reel-editor`. Ensure `@/features/reel-editor` resolves in tsconfig paths and Tailwind scans the slice folder.\n\nSTEP 2 — Deps. npm: `lucide-react react-resizable-panels sonner`. shadcn: `npx shadcn@latest add button input slider tooltip dialog dropdown-menu resizable sheet sonner`. Mount `<Toaster />` (sonner) once in your root layout.\n\nSTEP 3 — Mount. Render in a height-bearing box:\n```tsx\n\"use client\";\nimport { ReelEditor } from \"@/features/reel-editor\";\nexport default function Page() {\n return <div className=\"h-dvh\"><ReelEditor /></div>;\n}\n```\nOr register the `reelEditorApp` descriptor in an appshell manifest for windowed hosts.\n\nSTEP 4 — Files pane backend (optional). The quick-import pane ships with an in-memory mock. Wire a real filesystem with `configureReelFs({ list, mkdir, rawUrl })` — list/mkdir mirror a simple fs API, rawUrl resolves a listed path to a fetchable media URL.\n\nSTEP 5 — Export. The Render button records the live canvas + mixed audio to WebM via MediaRecorder in realtime (duration = composition length). Users can also import local media via the file picker — object URLs, no upload needed.",
1393
+ "tags": [
1394
+ "video",
1395
+ "video-editor",
1396
+ "timeline",
1397
+ "nle",
1398
+ "keyframes",
1399
+ "transitions",
1400
+ "webm",
1401
+ "canvas",
1380
1402
  "ui"
1381
1403
  ]
1382
1404
  },
@@ -2381,33 +2403,34 @@
2381
2403
  "slices": [
2382
2404
  {
2383
2405
  "slug": "image-editor",
2384
- "title": "Image Editor — Photoshop-style canvas (Konva)",
2406
+ "title": "Image Editor — layered raster editor",
2385
2407
  "category": "ui",
2386
- "kind": "full",
2387
- "version": "1.0.0",
2388
- "description": "A Photoshop-style raster image editor built on Konva. Layers panel (reorder, opacity, visibility, lock, 16 blend modes), free transform (move/scale/rotate/flip via a Transformer), image + text + shape + paint layers, brush & eraser with size/opacity/hardness, non-destructive adjustments + filters (brightness/contrast/hue/saturation/blur/grayscale/invert/sepia, applied via Konva.Filters), canvas resize/aspect presets, and Photoshop-style LAYER STYLES: stroke, drop shadow (angle/distance/size/opacity), outer glow, clipping mask (clip to layer below), per-layer blend mode. One-click BACKGROUND REMOVAL runs fully in-browser via @imgly/background-removal (free, MIT, no API key, no server — downloads a small ONNX model to the browser cache on first use) and drops the cutout back as a transparent layer. Undo/redo, zoom/pan, keyboard shortcuts, and PNG/JPG/WebP export at 1×/2×/3×. Self-contained: image I/O is via props (initialImage / onSave) so it drops into any app with zero backend.",
2408
+ "kind": "ui",
2409
+ "version": "2.0.0",
2410
+ "description": "A Photoshop-style raster image editor built on Konva. Layers panel (reorder, opacity, visibility, lock, 16 blend modes), free transform (move/scale/rotate/flip via a Transformer), image + text + shape + paint layers, brush & eraser with size/opacity/hardness, non-destructive adjustments + filters, canvas resize/aspect presets, and LAYER STYLES: stroke, drop shadow, outer glow, clipping mask. One-click BACKGROUND REMOVAL runs fully in-browser via @imgly/background-removal (free, no API key — downloads a small ONNX model on first use). Undo/redo, zoom/pan, shortcuts, PNG/JPG/WebP export. v2 adds an AI FUNCTION-CALLING layer: every editor operation is a named, schema'd command (EDITOR_COMMANDS registry + useEditorCommands binding) driven by an in-editor chat; the streaming bridge is injectable via configureAgentStream(fn) and everything except the chat works without it. A headless server barrel (server.ts) runs commands against documents with no DOM. Image I/O via props (initialImage / onSave).",
2389
2411
  "source": "rahmanef63/os-vps",
2390
2412
  "slicePath": "frontend/slices/image-editor",
2391
2413
  "convexPaths": [],
2392
2414
  "npm": [
2415
+ "lucide-react",
2393
2416
  "konva",
2394
2417
  "react-konva",
2395
2418
  "@imgly/background-removal",
2396
- "lucide-react"
2419
+ "class-variance-authority",
2420
+ "radix-ui"
2397
2421
  ],
2398
2422
  "shadcn": [
2399
2423
  "button",
2400
2424
  "input",
2401
- "slider",
2425
+ "label",
2426
+ "separator",
2402
2427
  "select",
2403
- "tabs",
2404
2428
  "scroll-area",
2405
- "separator",
2406
- "tooltip",
2407
- "label",
2408
2429
  "switch",
2409
- "popover",
2410
- "resizable"
2430
+ "dropdown-menu",
2431
+ "tooltip",
2432
+ "resizable",
2433
+ "popover"
2411
2434
  ],
2412
2435
  "env": [],
2413
2436
  "peers": [],
@@ -2421,11 +2444,53 @@
2421
2444
  "filters",
2422
2445
  "background-removal",
2423
2446
  "paint",
2424
- "crop",
2447
+ "ai",
2425
2448
  "ui"
2426
2449
  ],
2427
2450
  "agentRecipe": "Stack: Next 16 + React 19 + Tailwind 4 + shadcn/ui + Konva. A layered raster image editor. Image I/O is via props; background removal runs in-browser (no backend).\n\nSTEP 1 — Install. `npx rr add image-editor`. Ensure `@/features/image-editor` resolves in tsconfig paths and Tailwind scans the slice folder.\n\nSTEP 2 — Deps. npm: `konva react-konva @imgly/background-removal lucide-react`. shadcn: `npx shadcn@latest add button input slider select tabs scroll-area separator tooltip label switch popover`.\n\nSTEP 3 — Mount. It is fully self-contained; the Konva stage is loaded client-only (next/dynamic ssr:false) inside the slice, so just render it in a height-bearing box:\n```tsx\n\"use client\";\nimport { ImageEditor } from \"@/features/image-editor\";\nexport default function Page() {\n return (\n <div className=\"h-dvh\">\n <ImageEditor onSave={(dataUrl) => console.log(dataUrl)} />\n </div>\n );\n}\n```\nProps: `initialImage?` (data/object/remote URL opened on mount), `width?`/`height?` (blank canvas size, default 1080²), `onSave?(dataUrl)` (fires from the Save button with a PNG data URL; omit to hide Save), `className?`.\n\nSTEP 4 — Background removal. The \"Remove BG\" button calls removeImageBackground() from @imgly/background-removal — free, in-browser, no key. First run downloads a small model to the browser cache, then runs locally via WASM. You can also import `removeImageBackground(src) => Promise<pngDataUrl>` directly.\n\nSTEP 5 — Export. PNG/JPG/WebP at 1×/2×/3× via the Export tab, or call `exportStage(stage, {...})` / `stageToDataURL(stage, {...})`. The container owns the box — render inside h-dvh / h-full."
2428
2451
  },
2452
+ {
2453
+ "slug": "reel-editor",
2454
+ "title": "Reel — video timeline editor",
2455
+ "category": "ui",
2456
+ "kind": "ui",
2457
+ "version": "1.0.0",
2458
+ "description": "A complete in-browser video editor. Real media clips (image/video/audio) on a layered multi-track timeline — the top row renders frontmost, with ▲▼ reorder and per-track lock/hide/mute. ONE Canvas-2D draw path is shared by the live preview and the realtime MediaRecorder exporter, so what you see is exactly what renders (WebM with real mixed audio: per-clip volume/fades/auto-duck through a streaming audio graph). Per-clip trim/speed (0.25–4×)/reverse, dissolve/wipe/slide transitions via clip overlap, keyframes (opacity/scale/x/y/rotation) with easing + one-click In/Out animation presets, text styling with preset grid, color grading + vignette, filmstrip thumbnails + real waveforms, snapping, split/duplicate. The workspace is config-driven: 6 resizable layout presets (react-resizable-panels v4) incl. quick-import files-pane layouts, plus custom composition size. Drafts auto-save to localStorage. Self-contained: toasts via sonner, the files pane runs on an injectable fs adapter (configureReelFs; in-memory mock by default), and shell hooks (inspector/activity) are inert seams in lib/host.ts.",
2459
+ "source": "rahmanef63/os-vps",
2460
+ "slicePath": "frontend/slices/reel-editor",
2461
+ "convexPaths": [],
2462
+ "npm": [
2463
+ "lucide-react",
2464
+ "react-resizable-panels",
2465
+ "sonner"
2466
+ ],
2467
+ "shadcn": [
2468
+ "button",
2469
+ "input",
2470
+ "slider",
2471
+ "tooltip",
2472
+ "dialog",
2473
+ "dropdown-menu",
2474
+ "resizable",
2475
+ "sheet",
2476
+ "sonner"
2477
+ ],
2478
+ "env": [],
2479
+ "peers": [],
2480
+ "providers": [],
2481
+ "tags": [
2482
+ "video",
2483
+ "video-editor",
2484
+ "timeline",
2485
+ "nle",
2486
+ "keyframes",
2487
+ "transitions",
2488
+ "webm",
2489
+ "canvas",
2490
+ "ui"
2491
+ ],
2492
+ "agentRecipe": "Stack: Next 16 + React 19 + Tailwind 4 + shadcn/ui. An in-browser video timeline editor with realtime WebM export. Fully client-side; no backend required.\n\nSTEP 1 — Install. `npx rr add reel-editor`. Ensure `@/features/reel-editor` resolves in tsconfig paths and Tailwind scans the slice folder.\n\nSTEP 2 — Deps. npm: `lucide-react react-resizable-panels sonner`. shadcn: `npx shadcn@latest add button input slider tooltip dialog dropdown-menu resizable sheet sonner`. Mount `<Toaster />` (sonner) once in your root layout.\n\nSTEP 3 — Mount. Render in a height-bearing box:\n```tsx\n\"use client\";\nimport { ReelEditor } from \"@/features/reel-editor\";\nexport default function Page() {\n return <div className=\"h-dvh\"><ReelEditor /></div>;\n}\n```\nOr register the `reelEditorApp` descriptor in an appshell manifest for windowed hosts.\n\nSTEP 4 — Files pane backend (optional). The quick-import pane ships with an in-memory mock. Wire a real filesystem with `configureReelFs({ list, mkdir, rawUrl })` — list/mkdir mirror a simple fs API, rawUrl resolves a listed path to a fetchable media URL.\n\nSTEP 5 — Export. The Render button records the live canvas + mixed audio to WebM via MediaRecorder in realtime (duration = composition length). Users can also import local media via the file picker — object URLs, no upload needed."
2493
+ },
2429
2494
  {
2430
2495
  "slug": "file-explorer",
2431
2496
  "title": "File Explorer — Tree + CRUD + Breadcrumb",
@@ -4005,7 +4070,7 @@
4005
4070
  "title": "Theme Presets — unified switcher with bundled tweakcn registry",
4006
4071
  "category": "ui",
4007
4072
  "kind": "ui",
4008
- "version": "0.2.0",
4073
+ "version": "0.3.0",
4009
4074
  "description": "Single unified theme controller for next-themes apps. ThemePresetSwitcher ships a Palette-icon Popover trigger with three stacked sections: (1) sticky light/dark/system mode tabs, (2) sticky preset-count row with a Default reset button, (3) scrollable color-preset list grouped by mood (Profesional / Bold / Hangat / Artistik / Gelap + Lainnya). Hover-to-preview + click-to-commit + restore-on-close semantics. ThemePresetProvider context wraps state so deeply-nested consumers read via useThemePreset() instead of mounting the switcher directly. ThemeColorSync wrapper enables live tweakcn-CSS-variable preview on routes that need it. Tweakcn registry (~30 curated presets after HIDDEN_PRESETS filter drops Doom 64 / Cyberpunk / Neo Brutalism / Bubblegum / Candyland / Pastel Dreams) ships inside the slice as registry-data.json and loads lazily via dynamic import — code-splits into its own chunk, zero consumer public/ setup, no network roundtrip to a hosted URL. localStorage key `host:theme-preset` (rename via slice fork). CK-1F (2026-05-23) — collapsed prior TweakcnSwitcher + ThemePicker + phantom `theme-preset-switcher` catalog entry into this single component.",
4010
4075
  "source": "CareerPack + notion-page-clone",
4011
4076
  "slicePath": "frontend/slices/theme-presets",
@@ -165,6 +165,64 @@
165
165
  "type": "array",
166
166
  "items": { "type": "string" },
167
167
  "description": "Optional sub-provider list when a slice routes between alternatives (e.g., payment slice with midtrans + doku siblings)."
168
+ },
169
+ "previews": {
170
+ "type": "array",
171
+ "description": "Variant-preview declarations (VP wave). Each entry maps a public component to either enum variant axes (leaf slices) or scenario presets (subsystem slices). Rendered by frontend/slices/<slug>/preview.tsx — rr-internal, stripped on `rr add`. See docs/SLICE-PREVIEW-SPEC.md.",
172
+ "items": {
173
+ "type": "object",
174
+ "additionalProperties": false,
175
+ "required": ["component", "kind"],
176
+ "properties": {
177
+ "component": {
178
+ "type": "string",
179
+ "pattern": "^[A-Z][A-Za-z0-9]*$",
180
+ "description": "Public component export this preview renders."
181
+ },
182
+ "kind": {
183
+ "type": "string",
184
+ "enum": ["variants", "scenarios"],
185
+ "description": "variants = enum prop axes (leaf); scenarios = curated presets (subsystem)."
186
+ },
187
+ "axes": {
188
+ "type": "array",
189
+ "items": {
190
+ "type": "object",
191
+ "additionalProperties": false,
192
+ "required": ["prop", "values"],
193
+ "properties": {
194
+ "prop": { "type": "string", "pattern": "^[a-z][A-Za-z0-9]*$" },
195
+ "values": {
196
+ "type": "array",
197
+ "minItems": 1,
198
+ "items": { "type": "string" }
199
+ },
200
+ "default": { "type": "string" },
201
+ "description": { "type": "string" }
202
+ }
203
+ },
204
+ "description": "Enum-only variant axes (max 3 — enforced by gen-preview-registry). preview.tsx maps value strings to real props."
205
+ },
206
+ "scenarios": {
207
+ "type": "array",
208
+ "items": {
209
+ "type": "object",
210
+ "additionalProperties": false,
211
+ "required": ["id"],
212
+ "properties": {
213
+ "id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
214
+ "title": { "type": "string" },
215
+ "description": { "type": "string" }
216
+ }
217
+ },
218
+ "description": "Curated preset configurations for subsystem slices."
219
+ },
220
+ "seed": {
221
+ "type": "string",
222
+ "description": "Human note describing the localStorage demo data (actual seed lives in preview.tsx)."
223
+ }
224
+ }
225
+ }
168
226
  }
169
227
  }
170
228
  }
@@ -15,6 +15,7 @@ export default defineSchema({
15
15
  contactEmail: v.optional(v.string()),
16
16
  brandColor: v.optional(v.string()),
17
17
  themeDefault: v.optional(v.string()), // "light" | "dark" | "system"
18
+ themePreset: v.optional(v.string()), // tweakcn preset name (site-wide default)
18
19
  logoUrl: v.optional(v.string()),
19
20
  faviconUrl: v.optional(v.string()),
20
21
  socials: v.optional(v.string()), // JSON string
@@ -18,6 +18,8 @@ const FIELDS = {
18
18
  contactEmail: v.optional(v.string()),
19
19
  brandColor: v.optional(v.string()),
20
20
  themeDefault: v.optional(v.string()),
21
+ // tweakcn preset name — site-wide color preset ("" = template default)
22
+ themePreset: v.optional(v.string()),
21
23
  logoUrl: v.optional(v.string()),
22
24
  faviconUrl: v.optional(v.string()),
23
25
  socials: v.optional(v.string()),
@@ -11,6 +11,9 @@ export const status = query({
11
11
  const ownerClaimed = !!owner;
12
12
  return {
13
13
  ownerClaimed,
14
+ // JWT auth keys present? (setup-auth.mjs provisions them at build; a
15
+ // deploy key without WriteEnvironmentVariables leaves them missing.)
16
+ authReady: !!process.env.JWT_PRIVATE_KEY,
14
17
  // Onboarding wizard is done once the owner finishes it (onboardedAt set).
15
18
  onboarded: !!settings?.onboardedAt,
16
19
  // Signup is open if no owner has claimed yet, or a key is configured (invites).
@@ -3,6 +3,13 @@
3
3
  // deploy key lets us set the deployment's env. Idempotent: generates the
4
4
  // @convex-dev/auth JWT keys once and skips if they already exist (so sessions
5
5
  // aren't invalidated on every deploy).
6
+ //
7
+ // SAFETY: env-set failures are caught + reported WITHOUT echoing the command
8
+ // (the command contains the private key — an uncaught execFileSync error would
9
+ // dump it into the build log). Permission failures (403
10
+ // WriteEnvironmentVariables — deploy key isn't a production key, or its
11
+ // creator isn't a Convex admin/Project Admin) do NOT fail the build: the site
12
+ // goes live and /setup walks the owner through fixing the key.
6
13
  import { execFileSync } from "node:child_process";
7
14
 
8
15
  if (!process.env.CONVEX_DEPLOY_KEY) {
@@ -24,6 +31,27 @@ function envGet(name) {
24
31
  }
25
32
  }
26
33
 
34
+ // Set one env var. Never rethrows, never prints the value — only convex's own
35
+ // error lines (which contain no secret).
36
+ function envSet(pair, label) {
37
+ try {
38
+ execFileSync("npx", ["convex", "env", "set", pair], {
39
+ encoding: "utf8",
40
+ stdio: ["ignore", "pipe", "pipe"],
41
+ });
42
+ return true;
43
+ } catch (e) {
44
+ const out = `${e.stdout || ""}${e.stderr || ""}`;
45
+ const denied = /403|Unauthorized|WriteEnvironmentVariables/i.test(out);
46
+ console.error(
47
+ `[setup-auth] ✖ gagal set ${label}${denied ? " — deploy key tidak punya izin tulis env (WriteEnvironmentVariables)" : ""}`,
48
+ );
49
+ const firstLines = out.trim().split("\n").slice(0, 2).join("\n");
50
+ if (firstLines) console.error(firstLines);
51
+ return false;
52
+ }
53
+ }
54
+
27
55
  if (envGet("JWT_PRIVATE_KEY")) {
28
56
  console.log("[setup-auth] JWT keys already set — skip.");
29
57
  process.exit(0);
@@ -46,8 +74,32 @@ const site =
46
74
  : "");
47
75
 
48
76
  // NAME=value form: the value starts with "-----BEGIN", which the CLI would else
49
- // parse as a flag.
50
- npx(["convex", "env", "set", `JWT_PRIVATE_KEY=${privateKey}`]);
51
- npx(["convex", "env", "set", `JWKS=${jwks}`]);
52
- if (site) npx(["convex", "env", "set", `SITE_URL=${site}`]);
77
+ // parse as a flag. JWT_PRIVATE_KEY + JWKS must land as a PAIR (same keygen);
78
+ // if the first set succeeds but the second fails, roll the first back so the
79
+ // idempotency check above doesn't lock in a broken half-pair.
80
+ let ok = envSet(`JWT_PRIVATE_KEY=${privateKey}`, "JWT_PRIVATE_KEY");
81
+ if (ok) {
82
+ ok = envSet(`JWKS=${jwks}`, "JWKS");
83
+ if (!ok) {
84
+ try { npx(["convex", "env", "remove", "JWT_PRIVATE_KEY"], true); } catch { /* best effort */ }
85
+ }
86
+ }
87
+ if (ok && site) envSet(`SITE_URL=${site}`, "SITE_URL"); // non-critical
88
+
89
+ if (!ok) {
90
+ console.error(`
91
+ [setup-auth] ──────────────────────────────────────────────────────────
92
+ Kunci login TIDAK terpasang. Penyebab paling umum:
93
+ 1. Deploy key kamu tidak mencentang capability env — di UI deploy key
94
+ Convex terbaru, pastikan key punya: deployment:deploy +
95
+ deployment:env:view + deployment:env:write (atau pilih full access).
96
+ Generate: dashboard.convex.dev → project → Production → Settings →
97
+ Deploy Keys.
98
+ 2. Akun pembuat key bukan admin / Project Admin di team Convex itu.
99
+ Build TETAP dilanjutkan — situs akan live, tapi daftar/login owner
100
+ gagal sampai key diganti di Vercel lalu Redeploy.
101
+ Buka /setup di situsmu — halaman itu memandu langkah perbaikannya.
102
+ ──────────────────────────────────────────────────────────────────────`);
103
+ process.exit(0);
104
+ }
53
105
  console.log("[setup-auth] auth keys provisioned ✔");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rahman-resources",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Rahman Resources (rr) — shadcn-style installer for vertical slices. `npx resources add <slug>` copies slice into your project's `slices/<slug>/`. You own the files.",
5
5
  "type": "module",
6
6
  "license": "MIT",